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