• 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 //! if let Err(e) = syslog::init() {
16 //!     println!("failed to initiailize syslog: {}", e);
17 //!     return;
18 //! }
19 //! warn!("this is your {} warning", "final");
20 //! error!("something went horribly wrong: {}", "out of RAMs");
21 //! ```
22 
23 use super::{target_os::syslog::PlatformSyslog, RawDescriptor};
24 use std::{
25     env,
26     ffi::{OsStr, OsString},
27     fmt::{
28         Display, {self},
29     },
30     fs::File,
31     io,
32     io::{stderr, Cursor, Write},
33     os::unix::io::{AsRawFd, RawFd},
34     path::PathBuf,
35     sync::{MutexGuard, Once},
36 };
37 
38 use remain::sorted;
39 use sync::Mutex;
40 use thiserror::Error as ThisError;
41 
42 /// The priority (i.e. severity) of a syslog message.
43 ///
44 /// See syslog man pages for information on their semantics.
45 #[derive(Copy, Clone, Debug)]
46 pub enum Priority {
47     Emergency = 0,
48     Alert = 1,
49     Critical = 2,
50     Error = 3,
51     Warning = 4,
52     Notice = 5,
53     Info = 6,
54     Debug = 7,
55 }
56 
57 impl Display for Priority {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result58     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
59         use self::Priority::*;
60 
61         let string = match self {
62             Emergency => "EMERGENCY",
63             Alert => "ALERT",
64             Critical => "CRITICAL",
65             Error => "ERROR",
66             Warning => "WARNING",
67             Notice => "NOTICE",
68             Info => "INFO",
69             Debug => "DEBUG",
70         };
71 
72         write!(f, "{}", string)
73     }
74 }
75 
76 /// The facility of a syslog message.
77 ///
78 /// See syslog man pages for information on their semantics.
79 #[derive(Copy, Clone)]
80 pub enum Facility {
81     Kernel = 0,
82     User = 1 << 3,
83     Mail = 2 << 3,
84     Daemon = 3 << 3,
85     Auth = 4 << 3,
86     Syslog = 5 << 3,
87     Lpr = 6 << 3,
88     News = 7 << 3,
89     Uucp = 8 << 3,
90     Local0 = 16 << 3,
91     Local1 = 17 << 3,
92     Local2 = 18 << 3,
93     Local3 = 19 << 3,
94     Local4 = 20 << 3,
95     Local5 = 21 << 3,
96     Local6 = 22 << 3,
97     Local7 = 23 << 3,
98 }
99 
100 /// Errors returned by `syslog::init()`.
101 #[sorted]
102 #[derive(ThisError, Debug)]
103 pub enum Error {
104     /// Error while attempting to connect socket.
105     #[error("failed to connect socket: {0}")]
106     Connect(io::Error),
107     /// There was an error using `open` to get the lowest file descriptor.
108     #[error("failed to get lowest file descriptor: {0}")]
109     GetLowestFd(io::Error),
110     /// The guess of libc's file descriptor for the syslog connection was invalid.
111     #[error("guess of fd for syslog connection was invalid")]
112     InvalidFd,
113     /// Initialization was never attempted.
114     #[error("initialization was never attempted")]
115     NeverInitialized,
116     /// Initialization has previously failed and can not be retried.
117     #[error("initialization previously failed and cannot be retried")]
118     Poisoned,
119     /// Error while creating socket.
120     #[error("failed to create socket: {0}")]
121     Socket(io::Error),
122 }
123 
get_proc_name() -> Option<String>124 fn get_proc_name() -> Option<String> {
125     env::args_os()
126         .next()
127         .map(PathBuf::from)
128         .and_then(|s| s.file_name().map(OsStr::to_os_string))
129         .map(OsString::into_string)
130         .and_then(Result::ok)
131 }
132 
133 struct State {
134     stderr: bool,
135     file: Option<File>,
136     proc_name: Option<String>,
137     syslog: PlatformSyslog,
138 }
139 
140 impl State {
new() -> Result<State, Error>141     fn new() -> Result<State, Error> {
142         Ok(State {
143             stderr: true,
144             file: None,
145             proc_name: get_proc_name(),
146             syslog: PlatformSyslog::new()?,
147         })
148     }
149 }
150 
151 static STATE_ONCE: Once = Once::new();
152 static mut STATE: *const Mutex<State> = 0 as *const _;
153 
new_mutex_ptr<T>(inner: T) -> *const Mutex<T>154 fn new_mutex_ptr<T>(inner: T) -> *const Mutex<T> {
155     Box::into_raw(Box::new(Mutex::new(inner)))
156 }
157 
158 /// Initialize the syslog connection and internal variables.
159 ///
160 /// This should only be called once per process before any other threads have been spawned or any
161 /// signal handlers have been registered. Every call made after the first will have no effect
162 /// besides return `Ok` or `Err` appropriately.
init() -> Result<(), Error>163 pub fn init() -> Result<(), Error> {
164     let mut err = Error::Poisoned;
165     STATE_ONCE.call_once(|| match State::new() {
166         // Safe because STATE mutation is guarded by `Once`.
167         Ok(state) => unsafe { STATE = new_mutex_ptr(state) },
168         Err(e) => err = e,
169     });
170 
171     if unsafe { STATE.is_null() } {
172         Err(err)
173     } else {
174         Ok(())
175     }
176 }
177 
lock() -> Result<MutexGuard<'static, State>, Error>178 fn lock() -> Result<MutexGuard<'static, State>, Error> {
179     // Safe because we assume that STATE is always in either a valid or NULL state.
180     let state_ptr = unsafe { STATE };
181     if state_ptr.is_null() {
182         return Err(Error::NeverInitialized);
183     }
184     // Safe because STATE only mutates once and we checked for NULL.
185     let state = unsafe { &*state_ptr };
186     let guard = state.lock();
187     Ok(guard)
188 }
189 
190 // Attempts to lock and retrieve the state. Returns from the function silently on failure.
191 macro_rules! lock {
192     () => {
193         match lock() {
194             Ok(s) => s,
195             _ => return,
196         }
197     };
198 }
199 
200 /// Replaces the process name reported in each syslog message.
201 ///
202 /// The default process name is the _file name_ of `argv[0]`. For example, if this program was
203 /// invoked as
204 ///
205 /// ```bash
206 /// $ path/to/app --delete everything
207 /// ```
208 ///
209 /// the default process name would be _app_.
210 ///
211 /// Does nothing if syslog was never initialized.
set_proc_name<T: Into<String>>(proc_name: T)212 pub fn set_proc_name<T: Into<String>>(proc_name: T) {
213     let mut state = lock!();
214     state.proc_name = Some(proc_name.into());
215 }
216 
217 pub(crate) trait Syslog {
new() -> Result<Self, Error> where Self: Sized218     fn new() -> Result<Self, Error>
219     where
220         Self: Sized;
221 
222     /// Enables or disables echoing log messages to the syslog.
223     ///
224     /// The default behavior is **enabled**.
225     ///
226     /// If `enable` goes from `true` to `false`, the syslog connection is closed. The connection is
227     /// reopened if `enable` is set to `true` after it became `false`.
228     ///
229     /// Returns an error if syslog was never initialized or the syslog connection failed to be
230     /// established.
231     ///
232     /// # Arguments
233     /// * `enable` - `true` to enable echoing to syslog, `false` to disable echoing to syslog.
enable(&mut self, enable: bool) -> Result<(), Error>234     fn enable(&mut self, enable: bool) -> Result<(), Error>;
235 
log( &self, proc_name: Option<&str>, pri: Priority, fac: Facility, file_line: Option<(&str, u32)>, args: fmt::Arguments, )236     fn log(
237         &self,
238         proc_name: Option<&str>,
239         pri: Priority,
240         fac: Facility,
241         file_line: Option<(&str, u32)>,
242         args: fmt::Arguments,
243     );
244 
push_fds(&self, fds: &mut Vec<RawFd>)245     fn push_fds(&self, fds: &mut Vec<RawFd>);
246 }
247 
248 /// Enables or disables echoing log messages to the syslog.
249 ///
250 /// The default behavior is **enabled**.
251 ///
252 /// If `enable` goes from `true` to `false`, the syslog connection is closed. The connection is
253 /// reopened if `enable` is set to `true` after it became `false`.
254 ///
255 /// Returns an error if syslog was never initialized or the syslog connection failed to be
256 /// established.
257 ///
258 /// # Arguments
259 /// * `enable` - `true` to enable echoing to syslog, `false` to disable echoing to syslog.
echo_syslog(enable: bool) -> Result<(), Error>260 pub fn echo_syslog(enable: bool) -> Result<(), Error> {
261     let state_ptr = unsafe { STATE };
262     if state_ptr.is_null() {
263         return Err(Error::NeverInitialized);
264     }
265     let mut state = lock().map_err(|_| Error::Poisoned)?;
266 
267     state.syslog.enable(enable)
268 }
269 
270 /// Replaces the optional `File` to echo log messages to.
271 ///
272 /// The default behavior is to not echo to a file. Passing `None` to this function restores that
273 /// behavior.
274 ///
275 /// Does nothing if syslog was never initialized.
276 ///
277 /// # Arguments
278 /// * `file` - `Some(file)` to echo to `file`, `None` to disable echoing to the file previously passed to `echo_file`.
echo_file(file: Option<File>)279 pub fn echo_file(file: Option<File>) {
280     let mut state = lock!();
281     state.file = file;
282 }
283 
284 /// Enables or disables echoing log messages to the `std::io::stderr()`.
285 ///
286 /// The default behavior is **enabled**.
287 ///
288 /// Does nothing if syslog was never initialized.
289 ///
290 /// # Arguments
291 /// * `enable` - `true` to enable echoing to stderr, `false` to disable echoing to stderr.
echo_stderr(enable: bool)292 pub fn echo_stderr(enable: bool) {
293     let mut state = lock!();
294     state.stderr = enable;
295 }
296 
297 /// Retrieves the file descriptors owned by the global syslogger.
298 ///
299 /// Does nothing if syslog was never initialized. If their are any file descriptors, they will be
300 /// pushed into `fds`.
301 ///
302 /// Note that the `stderr` file descriptor is never added, as it is not owned by syslog.
push_fds(fds: &mut Vec<RawFd>)303 pub fn push_fds(fds: &mut Vec<RawFd>) {
304     let state = lock!();
305     state.syslog.push_fds(fds);
306     fds.extend(state.file.iter().map(|f| f.as_raw_fd()));
307 }
308 
309 /// Does the same as push_fds, but using the RawDescriptorType
push_descriptors(descriptors: &mut Vec<RawDescriptor>)310 pub fn push_descriptors(descriptors: &mut Vec<RawDescriptor>) {
311     push_fds(descriptors)
312 }
313 
314 /// Records a log message with the given details.
315 ///
316 /// Note that this will fail silently if syslog was not initialized.
317 ///
318 /// # Arguments
319 /// * `pri` - The `Priority` (i.e. severity) of the log message.
320 /// * `fac` - The `Facility` of the log message. Usually `Facility::User` should be used.
321 /// * `file_line` - Optional tuple of the name of the file that generated the
322 ///                 log and the line number within that file.
323 /// * `args` - The log's message to record, in the form of `format_args!()`  return value
324 ///
325 /// # Examples
326 ///
327 /// ```
328 /// # use sys_util::syslog;
329 /// # if let Err(e) = syslog::init() {
330 /// #     println!("failed to initiailize syslog: {}", e);
331 /// #     return;
332 /// # }
333 /// syslog::log(syslog::Priority::Error,
334 ///             syslog::Facility::User,
335 ///             Some((file!(), line!())),
336 ///             format_args!("hello syslog"));
337 /// ```
log(pri: Priority, fac: Facility, file_line: Option<(&str, u32)>, args: fmt::Arguments)338 pub fn log(pri: Priority, fac: Facility, file_line: Option<(&str, u32)>, args: fmt::Arguments) {
339     let mut state = lock!();
340     let mut buf = [0u8; 1024];
341 
342     state.syslog.log(
343         state.proc_name.as_ref().map(|s| s.as_ref()),
344         pri,
345         fac,
346         file_line,
347         args,
348     );
349 
350     let res = {
351         let mut buf_cursor = Cursor::new(&mut buf[..]);
352         if let Some((file_name, line)) = &file_line {
353             write!(&mut buf_cursor, "[{}:{}:{}] ", pri, file_name, line)
354         } else {
355             Ok(())
356         }
357         .and_then(|()| writeln!(&mut buf_cursor, "{}", args))
358         .map(|()| buf_cursor.position() as usize)
359     };
360     if let Ok(len) = &res {
361         if let Some(file) = &mut state.file {
362             let _ = file.write_all(&buf[..*len]);
363         }
364         if state.stderr {
365             let _ = stderr().write_all(&buf[..*len]);
366         }
367     }
368 }
369 
370 /// A macro for logging at an arbitrary priority level.
371 ///
372 /// Note that this will fail silently if syslog was not initialized.
373 #[macro_export]
374 macro_rules! log {
375     ($pri:expr, $($args:tt)+) => ({
376         $crate::syslog::log($pri, $crate::syslog::Facility::User, Some((file!(), line!())), format_args!($($args)+))
377     })
378 }
379 
380 /// A macro for logging an error.
381 ///
382 /// Note that this will fail silently if syslog was not initialized.
383 #[macro_export]
384 macro_rules! error {
385     ($($args:tt)+) => ($crate::log!($crate::syslog::Priority::Error, $($args)*))
386 }
387 
388 /// A macro for logging a warning.
389 ///
390 /// Note that this will fail silently if syslog was not initialized.
391 #[macro_export]
392 macro_rules! warn {
393     ($($args:tt)+) => ($crate::log!($crate::syslog::Priority::Warning, $($args)*))
394 }
395 
396 /// A macro for logging info.
397 ///
398 /// Note that this will fail silently if syslog was not initialized.
399 #[macro_export]
400 macro_rules! info {
401     ($($args:tt)+) => ($crate::log!($crate::syslog::Priority::Info, $($args)*))
402 }
403 
404 /// A macro for logging debug information.
405 ///
406 /// Note that this will fail silently if syslog was not initialized.
407 #[macro_export]
408 macro_rules! debug {
409     ($($args:tt)+) => ($crate::log!($crate::syslog::Priority::Debug, $($args)*))
410 }
411 
412 // Struct that implements io::Write to be used for writing directly to the syslog
413 pub struct Syslogger {
414     buf: String,
415     priority: Priority,
416     facility: Facility,
417 }
418 
419 impl Syslogger {
new(p: Priority, f: Facility) -> Syslogger420     pub fn new(p: Priority, f: Facility) -> Syslogger {
421         Syslogger {
422             buf: String::new(),
423             priority: p,
424             facility: f,
425         }
426     }
427 }
428 
429 impl io::Write for Syslogger {
write(&mut self, buf: &[u8]) -> io::Result<usize>430     fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
431         let parsed_str = String::from_utf8_lossy(buf);
432         self.buf.push_str(&parsed_str);
433 
434         if let Some(last_newline_idx) = self.buf.rfind('\n') {
435             for line in self.buf[..last_newline_idx].lines() {
436                 log(self.priority, self.facility, None, format_args!("{}", line));
437             }
438 
439             self.buf.drain(..=last_newline_idx);
440         }
441 
442         Ok(buf.len())
443     }
444 
flush(&mut self) -> io::Result<()>445     fn flush(&mut self) -> io::Result<()> {
446         Ok(())
447     }
448 }
449 
450 #[cfg(test)]
451 mod tests {
452     use super::*;
453 
454     use libc::{shm_open, shm_unlink, O_CREAT, O_EXCL, O_RDWR};
455 
456     use std::{
457         ffi::CStr,
458         io::{Read, Seek, SeekFrom},
459         os::unix::io::FromRawFd,
460     };
461 
462     #[test]
init_syslog()463     fn init_syslog() {
464         init().unwrap();
465     }
466 
467     #[test]
fds()468     fn fds() {
469         init().unwrap();
470         let mut fds = Vec::new();
471         push_fds(&mut fds);
472         assert!(!fds.is_empty());
473         for fd in fds {
474             assert!(fd >= 0);
475         }
476     }
477 
478     #[test]
syslog_log()479     fn syslog_log() {
480         init().unwrap();
481         log(
482             Priority::Error,
483             Facility::User,
484             Some((file!(), line!())),
485             format_args!("hello syslog"),
486         );
487     }
488 
489     #[test]
proc_name()490     fn proc_name() {
491         init().unwrap();
492         log(
493             Priority::Error,
494             Facility::User,
495             Some((file!(), line!())),
496             format_args!("before proc name"),
497         );
498         set_proc_name("sys_util-test");
499         log(
500             Priority::Error,
501             Facility::User,
502             Some((file!(), line!())),
503             format_args!("after proc name"),
504         );
505     }
506 
507     #[test]
syslog_file()508     fn syslog_file() {
509         init().unwrap();
510         let shm_name = CStr::from_bytes_with_nul(b"/crosvm_shm\0").unwrap();
511         let mut file = unsafe {
512             shm_unlink(shm_name.as_ptr());
513             let fd = shm_open(shm_name.as_ptr(), O_RDWR | O_CREAT | O_EXCL, 0o666);
514             assert!(fd >= 0, "error creating shared memory;");
515             shm_unlink(shm_name.as_ptr());
516             File::from_raw_fd(fd)
517         };
518 
519         let syslog_file = file.try_clone().expect("error cloning shared memory file");
520         echo_file(Some(syslog_file));
521 
522         const TEST_STR: &str = "hello shared memory file";
523         log(
524             Priority::Error,
525             Facility::User,
526             Some((file!(), line!())),
527             format_args!("{}", TEST_STR),
528         );
529 
530         file.seek(SeekFrom::Start(0))
531             .expect("error seeking shared memory file");
532         let mut buf = String::new();
533         file.read_to_string(&mut buf)
534             .expect("error reading shared memory file");
535         assert!(buf.contains(TEST_STR));
536     }
537 
538     #[test]
macros()539     fn macros() {
540         init().unwrap();
541         error!("this is an error {}", 3);
542         warn!("this is a warning {}", "uh oh");
543         info!("this is info {}", true);
544         debug!("this is debug info {:?}", Some("helpful stuff"));
545     }
546 
547     #[test]
syslogger_char()548     fn syslogger_char() {
549         init().unwrap();
550         let mut syslogger = Syslogger::new(Priority::Info, Facility::Daemon);
551 
552         let string = "Writing chars to syslog";
553         for c in string.chars() {
554             syslogger.write_all(&[c as u8]).expect("error writing char");
555         }
556 
557         syslogger
558             .write_all(&[b'\n'])
559             .expect("error writing newline char");
560     }
561 
562     #[test]
syslogger_line()563     fn syslogger_line() {
564         init().unwrap();
565         let mut syslogger = Syslogger::new(Priority::Info, Facility::Daemon);
566 
567         let s = "Writing string to syslog\n";
568         syslogger
569             .write_all(s.as_bytes())
570             .expect("error writing string");
571     }
572 
573     #[test]
syslogger_partial()574     fn syslogger_partial() {
575         init().unwrap();
576         let mut syslogger = Syslogger::new(Priority::Info, Facility::Daemon);
577 
578         let s = "Writing partial string";
579         // Should not log because there is no newline character
580         syslogger
581             .write_all(s.as_bytes())
582             .expect("error writing string");
583     }
584 }
585