• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2017 The Chromium OS Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 //! Facilities for sending log message to syslog.
6 //!
7 //! Every function exported by this module is thread-safe. Each function will silently fail until
8 //! `syslog::init()` is called and returns `Ok`.
9 //!
10 //! # Examples
11 //!
12 //! ```
13 //! use sys_util::{error, syslog, warn};
14 //!
15 //! fn main() {
16 //!     if let Err(e) = syslog::init() {
17 //!         println!("failed to initiailize syslog: {}", e);
18 //!         return;
19 //!     }
20 //!     warn!("this is your {} warning", "final");
21 //!     error!("something went horribly wrong: {}", "out of RAMs");
22 //! }
23 //! ```
24 
25 use std::env;
26 use std::ffi::{OsStr, OsString};
27 use std::fmt::{self, Display};
28 use std::fs::File;
29 use std::io;
30 use std::io::{stderr, Cursor, ErrorKind, Write};
31 use std::mem;
32 use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
33 use std::os::unix::net::UnixDatagram;
34 use std::path::PathBuf;
35 use std::ptr::null;
36 use std::sync::{MutexGuard, Once, ONCE_INIT};
37 
38 use libc::{
39     closelog, fcntl, localtime_r, openlog, time, time_t, tm, F_GETFD, LOG_NDELAY, LOG_PERROR,
40     LOG_PID, LOG_USER,
41 };
42 
43 use sync::Mutex;
44 
45 use crate::getpid;
46 
47 const SYSLOG_PATH: &str = "/dev/log";
48 
49 /// The priority (i.e. severity) of a syslog message.
50 ///
51 /// See syslog man pages for information on their semantics.
52 #[derive(Copy, Clone, Debug)]
53 pub enum Priority {
54     Emergency = 0,
55     Alert = 1,
56     Critical = 2,
57     Error = 3,
58     Warning = 4,
59     Notice = 5,
60     Info = 6,
61     Debug = 7,
62 }
63 
64 impl Display for Priority {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result65     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
66         use self::Priority::*;
67 
68         let string = match self {
69             Emergency => "EMERGENCY",
70             Alert => "ALERT",
71             Critical => "CRITICAL",
72             Error => "ERROR",
73             Warning => "WARNING",
74             Notice => "NOTICE",
75             Info => "INFO",
76             Debug => "DEBUG",
77         };
78 
79         write!(f, "{}", string)
80     }
81 }
82 
83 /// The facility of a syslog message.
84 ///
85 /// See syslog man pages for information on their semantics.
86 #[derive(Copy, Clone)]
87 pub enum Facility {
88     Kernel = 0,
89     User = 1 << 3,
90     Mail = 2 << 3,
91     Daemon = 3 << 3,
92     Auth = 4 << 3,
93     Syslog = 5 << 3,
94     Lpr = 6 << 3,
95     News = 7 << 3,
96     Uucp = 8 << 3,
97     Local0 = 16 << 3,
98     Local1 = 17 << 3,
99     Local2 = 18 << 3,
100     Local3 = 19 << 3,
101     Local4 = 20 << 3,
102     Local5 = 21 << 3,
103     Local6 = 22 << 3,
104     Local7 = 23 << 3,
105 }
106 
107 /// Errors returned by `syslog::init()`.
108 #[derive(Debug)]
109 pub enum Error {
110     /// Initialization was never attempted.
111     NeverInitialized,
112     /// Initialization has previously failed and can not be retried.
113     Poisoned,
114     /// Error while creating socket.
115     Socket(io::Error),
116     /// Error while attempting to connect socket.
117     Connect(io::Error),
118     // There was an error using `open` to get the lowest file descriptor.
119     GetLowestFd(io::Error),
120     // The guess of libc's file descriptor for the syslog connection was invalid.
121     InvalidFd,
122 }
123 
124 impl Display for Error {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result125     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
126         use self::Error::*;
127 
128         match self {
129             NeverInitialized => write!(f, "initialization was never attempted"),
130             Poisoned => write!(f, "initialization previously failed and cannot be retried"),
131             Socket(e) => write!(f, "failed to create socket: {}", e),
132             Connect(e) => write!(f, "failed to connect socket: {}", e),
133             GetLowestFd(e) => write!(f, "failed to get lowest file descriptor: {}", e),
134             InvalidFd => write!(f, "guess of fd for syslog connection was invalid"),
135         }
136     }
137 }
138 
get_proc_name() -> Option<String>139 fn get_proc_name() -> Option<String> {
140     env::args_os()
141         .next()
142         .map(PathBuf::from)
143         .and_then(|s| s.file_name().map(OsStr::to_os_string))
144         .map(OsString::into_string)
145         .and_then(Result::ok)
146 }
147 
148 // Uses libc's openlog function to get a socket to the syslogger. By getting the socket this way, as
149 // opposed to connecting to the syslogger directly, libc's internal state gets initialized for other
150 // libraries (e.g. minijail) that make use of libc's syslog function. Note that this function
151 // depends on no other threads or signal handlers being active in this process because they might
152 // create FDs.
153 //
154 // TODO(zachr): Once https://android-review.googlesource.com/470998 lands, there won't be any
155 // libraries in use that hard depend on libc's syslogger. Remove this and go back to making the
156 // connection directly once minjail is ready.
openlog_and_get_socket() -> Result<UnixDatagram, Error>157 fn openlog_and_get_socket() -> Result<UnixDatagram, Error> {
158     // closelog first in case there was already a file descriptor open.  Safe because it takes no
159     // arguments and just closes an open file descriptor.  Does nothing if the file descriptor
160     // was not already open.
161     unsafe {
162         closelog();
163     }
164 
165     // Ordinarily libc's FD for the syslog connection can't be accessed, but we can guess that the
166     // FD that openlog will be getting is the lowest unused FD. To guarantee that an FD is opened in
167     // this function we use the LOG_NDELAY to tell openlog to connect to the syslog now. To get the
168     // lowest unused FD, we open a dummy file (which the manual says will always return the lowest
169     // fd), and then close that fd. Voilà, we now know the lowest numbered FD. The call to openlog
170     // will make use of that FD, and then we just wrap a `UnixDatagram` around it for ease of use.
171     let fd = File::open("/dev/null")
172         .map_err(Error::GetLowestFd)?
173         .as_raw_fd();
174 
175     unsafe {
176         // Safe because openlog accesses no pointers because `ident` is null, only valid flags are
177         // used, and it returns no error.
178         openlog(null(), LOG_NDELAY | LOG_PERROR | LOG_PID, LOG_USER);
179         // For safety, ensure the fd we guessed is valid. The `fcntl` call itself only reads the
180         // file descriptor table of the current process, which is trivially safe.
181         if fcntl(fd, F_GETFD) >= 0 {
182             Ok(UnixDatagram::from_raw_fd(fd))
183         } else {
184             Err(Error::InvalidFd)
185         }
186     }
187 }
188 
189 struct State {
190     stderr: bool,
191     socket: Option<UnixDatagram>,
192     file: Option<File>,
193     proc_name: Option<String>,
194 }
195 
196 impl State {
new() -> Result<State, Error>197     fn new() -> Result<State, Error> {
198         let s = openlog_and_get_socket()?;
199         Ok(State {
200             stderr: true,
201             socket: Some(s),
202             file: None,
203             proc_name: get_proc_name(),
204         })
205     }
206 }
207 
208 static STATE_ONCE: Once = ONCE_INIT;
209 static mut STATE: *const Mutex<State> = 0 as *const _;
210 
new_mutex_ptr<T>(inner: T) -> *const Mutex<T>211 fn new_mutex_ptr<T>(inner: T) -> *const Mutex<T> {
212     Box::into_raw(Box::new(Mutex::new(inner)))
213 }
214 
215 /// Initialize the syslog connection and internal variables.
216 ///
217 /// This should only be called once per process before any other threads have been spawned or any
218 /// signal handlers have been registered. Every call made after the first will have no effect
219 /// besides return `Ok` or `Err` appropriately.
init() -> Result<(), Error>220 pub fn init() -> Result<(), Error> {
221     let mut err = Error::Poisoned;
222     STATE_ONCE.call_once(|| match State::new() {
223         // Safe because STATE mutation is guarded by `Once`.
224         Ok(state) => unsafe { STATE = new_mutex_ptr(state) },
225         Err(e) => err = e,
226     });
227 
228     if unsafe { STATE.is_null() } {
229         Err(err)
230     } else {
231         Ok(())
232     }
233 }
234 
lock() -> Result<MutexGuard<'static, State>, Error>235 fn lock() -> Result<MutexGuard<'static, State>, Error> {
236     // Safe because we assume that STATE is always in either a valid or NULL state.
237     let state_ptr = unsafe { STATE };
238     if state_ptr.is_null() {
239         return Err(Error::NeverInitialized);
240     }
241     // Safe because STATE only mutates once and we checked for NULL.
242     let state = unsafe { &*state_ptr };
243     let guard = state.lock();
244     Ok(guard)
245 }
246 
247 // Attempts to lock and retrieve the state. Returns from the function silently on failure.
248 macro_rules! lock {
249     () => {
250         match lock() {
251             Ok(s) => s,
252             _ => return,
253         };
254     };
255 }
256 
257 /// Replaces the process name reported in each syslog message.
258 ///
259 /// The default process name is the _file name_ of `argv[0]`. For example, if this program was
260 /// invoked as
261 ///
262 /// ```bash
263 /// $ path/to/app --delete everything
264 /// ```
265 ///
266 /// the default process name would be _app_.
267 ///
268 /// Does nothing if syslog was never initialized.
set_proc_name<T: Into<String>>(proc_name: T)269 pub fn set_proc_name<T: Into<String>>(proc_name: T) {
270     let mut state = lock!();
271     state.proc_name = Some(proc_name.into());
272 }
273 
274 /// Enables or disables echoing log messages to the syslog.
275 ///
276 /// The default behavior is **enabled**.
277 ///
278 /// If `enable` goes from `true` to `false`, the syslog connection is closed. The connection is
279 /// reopened if `enable` is set to `true` after it became `false`.
280 ///
281 /// Returns an error if syslog was never initialized or the syslog connection failed to be
282 /// established.
283 ///
284 /// # Arguments
285 /// * `enable` - `true` to enable echoing to syslog, `false` to disable echoing to syslog.
echo_syslog(enable: bool) -> Result<(), Error>286 pub fn echo_syslog(enable: bool) -> Result<(), Error> {
287     let state_ptr = unsafe { STATE };
288     if state_ptr.is_null() {
289         return Err(Error::NeverInitialized);
290     }
291     let mut state = lock().map_err(|_| Error::Poisoned)?;
292 
293     match state.socket.take() {
294         Some(_) if enable => {}
295         Some(s) => {
296             // Because `openlog_and_get_socket` actually just "borrows" the syslog FD, this module
297             // does not own the syslog connection and therefore should not destroy it.
298             mem::forget(s);
299         }
300         None if enable => {
301             let s = openlog_and_get_socket()?;
302             state.socket = Some(s);
303         }
304         _ => {}
305     }
306     Ok(())
307 }
308 
309 /// Replaces the optional `File` to echo log messages to.
310 ///
311 /// The default behavior is to not echo to a file. Passing `None` to this function restores that
312 /// behavior.
313 ///
314 /// Does nothing if syslog was never initialized.
315 ///
316 /// # Arguments
317 /// * `file` - `Some(file)` to echo to `file`, `None` to disable echoing to the file previously passed to `echo_file`.
echo_file(file: Option<File>)318 pub fn echo_file(file: Option<File>) {
319     let mut state = lock!();
320     state.file = file;
321 }
322 
323 /// Enables or disables echoing log messages to the `std::io::stderr()`.
324 ///
325 /// The default behavior is **enabled**.
326 ///
327 /// Does nothing if syslog was never initialized.
328 ///
329 /// # Arguments
330 /// * `enable` - `true` to enable echoing to stderr, `false` to disable echoing to stderr.
echo_stderr(enable: bool)331 pub fn echo_stderr(enable: bool) {
332     let mut state = lock!();
333     state.stderr = enable;
334 }
335 
336 /// Retrieves the file descriptors owned by the global syslogger.
337 ///
338 /// Does nothing if syslog was never initialized. If their are any file descriptors, they will be
339 /// pushed into `fds`.
340 ///
341 /// Note that the `stderr` file descriptor is never added, as it is not owned by syslog.
push_fds(fds: &mut Vec<RawFd>)342 pub fn push_fds(fds: &mut Vec<RawFd>) {
343     let state = lock!();
344     fds.extend(state.socket.iter().map(|s| s.as_raw_fd()));
345     fds.extend(state.file.iter().map(|f| f.as_raw_fd()));
346 }
347 
348 /// Should only be called after `init()` was called.
send_buf(socket: &UnixDatagram, buf: &[u8])349 fn send_buf(socket: &UnixDatagram, buf: &[u8]) {
350     const SEND_RETRY: usize = 2;
351 
352     for _ in 0..SEND_RETRY {
353         match socket.send(&buf[..]) {
354             Ok(_) => break,
355             Err(e) => match e.kind() {
356                 ErrorKind::ConnectionRefused
357                 | ErrorKind::ConnectionReset
358                 | ErrorKind::ConnectionAborted
359                 | ErrorKind::NotConnected => {
360                     let res = socket.connect(SYSLOG_PATH);
361                     if res.is_err() {
362                         break;
363                     }
364                 }
365                 _ => {}
366             },
367         }
368     }
369 }
370 
get_localtime() -> tm371 fn get_localtime() -> tm {
372     unsafe {
373         // Safe because tm is just a struct of plain data.
374         let mut tm: tm = mem::zeroed();
375         let mut now: time_t = 0;
376         // Safe because we give time a valid pointer and can never fail.
377         time(&mut now as *mut _);
378         // Safe because we give localtime_r valid pointers and can never fail.
379         localtime_r(&now, &mut tm as *mut _);
380         tm
381     }
382 }
383 
384 /// Records a log message with the given details.
385 ///
386 /// Note that this will fail silently if syslog was not initialized.
387 ///
388 /// # Arguments
389 /// * `pri` - The `Priority` (i.e. severity) of the log message.
390 /// * `fac` - The `Facility` of the log message. Usually `Facility::User` should be used.
391 /// * `file_line` - Optional tuple of the name of the file that generated the
392 ///                 log and the line number within that file.
393 /// * `args` - The log's message to record, in the form of `format_args!()`  return value
394 ///
395 /// # Examples
396 ///
397 /// ```
398 /// # use sys_util::syslog;
399 /// # fn main() {
400 /// #   if let Err(e) = syslog::init() {
401 /// #       println!("failed to initiailize syslog: {}", e);
402 /// #       return;
403 /// #   }
404 /// syslog::log(syslog::Priority::Error,
405 ///             syslog::Facility::User,
406 ///             Some((file!(), line!())),
407 ///             format_args!("hello syslog"));
408 /// # }
409 /// ```
log(pri: Priority, fac: Facility, file_line: Option<(&str, u32)>, args: fmt::Arguments)410 pub fn log(pri: Priority, fac: Facility, file_line: Option<(&str, u32)>, args: fmt::Arguments) {
411     const MONTHS: [&str; 12] = [
412         "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
413     ];
414 
415     let mut state = lock!();
416     let mut buf = [0u8; 1024];
417     if let Some(socket) = &state.socket {
418         let tm = get_localtime();
419         let prifac = (pri as u8) | (fac as u8);
420         let res = {
421             let mut buf_cursor = Cursor::new(&mut buf[..]);
422             write!(
423                 &mut buf_cursor,
424                 "<{}>{} {:02} {:02}:{:02}:{:02} {}[{}]: ",
425                 prifac,
426                 MONTHS[tm.tm_mon as usize],
427                 tm.tm_mday,
428                 tm.tm_hour,
429                 tm.tm_min,
430                 tm.tm_sec,
431                 state.proc_name.as_ref().map(|s| s.as_ref()).unwrap_or("-"),
432                 getpid()
433             )
434             .and_then(|()| {
435                 if let Some((file_name, line)) = &file_line {
436                     write!(&mut buf_cursor, " [{}:{}] ", file_name, line)
437                 } else {
438                     Ok(())
439                 }
440             })
441             .and_then(|()| write!(&mut buf_cursor, "{}", args))
442             .and_then(|()| Ok(buf_cursor.position() as usize))
443         };
444 
445         if let Ok(len) = &res {
446             send_buf(&socket, &buf[..*len])
447         }
448     }
449 
450     let res = {
451         let mut buf_cursor = Cursor::new(&mut buf[..]);
452         if let Some((file_name, line)) = &file_line {
453             write!(&mut buf_cursor, "[{}:{}:{}] ", pri, file_name, line)
454         } else {
455             Ok(())
456         }
457         .and_then(|()| writeln!(&mut buf_cursor, "{}", args))
458         .and_then(|()| Ok(buf_cursor.position() as usize))
459     };
460     if let Ok(len) = &res {
461         if let Some(file) = &mut state.file {
462             let _ = file.write_all(&buf[..*len]);
463         }
464         if state.stderr {
465             let _ = stderr().write_all(&buf[..*len]);
466         }
467     }
468 }
469 
470 /// A macro for logging at an arbitrary priority level.
471 ///
472 /// Note that this will fail silently if syslog was not initialized.
473 #[macro_export]
474 macro_rules! log {
475     ($pri:expr, $($args:tt)+) => ({
476         $crate::syslog::log($pri, $crate::syslog::Facility::User, Some((file!(), line!())), format_args!($($args)+))
477     })
478 }
479 
480 /// A macro for logging an error.
481 ///
482 /// Note that this will fail silently if syslog was not initialized.
483 #[macro_export]
484 macro_rules! error {
485     ($($args:tt)+) => ($crate::log!($crate::syslog::Priority::Error, $($args)*))
486 }
487 
488 /// A macro for logging a warning.
489 ///
490 /// Note that this will fail silently if syslog was not initialized.
491 #[macro_export]
492 macro_rules! warn {
493     ($($args:tt)+) => ($crate::log!($crate::syslog::Priority::Warning, $($args)*))
494 }
495 
496 /// A macro for logging info.
497 ///
498 /// Note that this will fail silently if syslog was not initialized.
499 #[macro_export]
500 macro_rules! info {
501     ($($args:tt)+) => ($crate::log!($crate::syslog::Priority::Info, $($args)*))
502 }
503 
504 /// A macro for logging debug information.
505 ///
506 /// Note that this will fail silently if syslog was not initialized.
507 #[macro_export]
508 macro_rules! debug {
509     ($($args:tt)+) => ($crate::log!($crate::syslog::Priority::Debug, $($args)*))
510 }
511 
512 // Struct that implements io::Write to be used for writing directly to the syslog
513 pub struct Syslogger {
514     buf: String,
515     priority: Priority,
516     facility: Facility,
517 }
518 
519 impl Syslogger {
new(p: Priority, f: Facility) -> Syslogger520     pub fn new(p: Priority, f: Facility) -> Syslogger {
521         Syslogger {
522             buf: String::new(),
523             priority: p,
524             facility: f,
525         }
526     }
527 }
528 
529 impl io::Write for Syslogger {
write(&mut self, buf: &[u8]) -> io::Result<usize>530     fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
531         let parsed_str = String::from_utf8_lossy(buf);
532         self.buf.push_str(&parsed_str);
533 
534         if let Some(last_newline_idx) = self.buf.rfind('\n') {
535             for line in self.buf[..last_newline_idx].lines() {
536                 log(self.priority, self.facility, None, format_args!("{}", line));
537             }
538 
539             self.buf.drain(..=last_newline_idx);
540         }
541 
542         Ok(buf.len())
543     }
544 
flush(&mut self) -> io::Result<()>545     fn flush(&mut self) -> io::Result<()> {
546         Ok(())
547     }
548 }
549 
550 #[cfg(test)]
551 mod tests {
552     use super::*;
553 
554     use libc::{shm_open, shm_unlink, O_CREAT, O_EXCL, O_RDWR};
555 
556     use std::ffi::CStr;
557     use std::io::{Read, Seek, SeekFrom};
558     use std::os::unix::io::FromRawFd;
559 
560     #[test]
init_syslog()561     fn init_syslog() {
562         init().unwrap();
563     }
564 
565     #[test]
fds()566     fn fds() {
567         init().unwrap();
568         let mut fds = Vec::new();
569         push_fds(&mut fds);
570         assert!(fds.len() >= 1);
571         for fd in fds {
572             assert!(fd >= 0);
573         }
574     }
575 
576     #[test]
syslog_log()577     fn syslog_log() {
578         init().unwrap();
579         log(
580             Priority::Error,
581             Facility::User,
582             Some((file!(), line!())),
583             format_args!("hello syslog"),
584         );
585     }
586 
587     #[test]
proc_name()588     fn proc_name() {
589         init().unwrap();
590         log(
591             Priority::Error,
592             Facility::User,
593             Some((file!(), line!())),
594             format_args!("before proc name"),
595         );
596         set_proc_name("sys_util-test");
597         log(
598             Priority::Error,
599             Facility::User,
600             Some((file!(), line!())),
601             format_args!("after proc name"),
602         );
603     }
604 
605     #[test]
syslog_file()606     fn syslog_file() {
607         init().unwrap();
608         let shm_name = CStr::from_bytes_with_nul(b"/crosvm_shm\0").unwrap();
609         let mut file = unsafe {
610             shm_unlink(shm_name.as_ptr());
611             let fd = shm_open(shm_name.as_ptr(), O_RDWR | O_CREAT | O_EXCL, 0666);
612             assert!(fd >= 0, "error creating shared memory;");
613             File::from_raw_fd(fd)
614         };
615 
616         let syslog_file = file.try_clone().expect("error cloning shared memory file");
617         echo_file(Some(syslog_file));
618 
619         const TEST_STR: &'static str = "hello shared memory file";
620         log(
621             Priority::Error,
622             Facility::User,
623             Some((file!(), line!())),
624             format_args!("{}", TEST_STR),
625         );
626 
627         file.seek(SeekFrom::Start(0))
628             .expect("error seeking shared memory file");
629         let mut buf = String::new();
630         file.read_to_string(&mut buf)
631             .expect("error reading shared memory file");
632         assert!(buf.contains(TEST_STR));
633     }
634 
635     #[test]
macros()636     fn macros() {
637         init().unwrap();
638         error!("this is an error {}", 3);
639         warn!("this is a warning {}", "uh oh");
640         info!("this is info {}", true);
641         debug!("this is debug info {:?}", Some("helpful stuff"));
642     }
643 
644     #[test]
syslogger_char()645     fn syslogger_char() {
646         init().unwrap();
647         let mut syslogger = Syslogger::new(Priority::Info, Facility::Daemon);
648 
649         let string = "Writing chars to syslog";
650         for c in string.chars() {
651             syslogger.write(&[c as u8]).expect("error writing char");
652         }
653 
654         syslogger
655             .write(&['\n' as u8])
656             .expect("error writing newline char");
657     }
658 
659     #[test]
syslogger_line()660     fn syslogger_line() {
661         init().unwrap();
662         let mut syslogger = Syslogger::new(Priority::Info, Facility::Daemon);
663 
664         let s = "Writing string to syslog\n";
665         syslogger
666             .write(&s.as_bytes())
667             .expect("error writing string");
668     }
669 
670     #[test]
syslogger_partial()671     fn syslogger_partial() {
672         init().unwrap();
673         let mut syslogger = Syslogger::new(Priority::Info, Facility::Daemon);
674 
675         let s = "Writing partial string";
676         // Should not log because there is no newline character
677         syslogger
678             .write(&s.as_bytes())
679             .expect("error writing string");
680     }
681 }
682