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 crate::platform::{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 crate::platform::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::platform::syslog::log($pri, $crate::platform::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::platform::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::platform::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::platform::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::platform::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 // ANDROID: b/227789997
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 // ANDROID: b/227789997
508 // #[test]
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