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