xref: /aosp_15_r20/external/crosvm/base/src/sys/linux/linux/syslog.rs (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
1 // Copyright 2020 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 //! Implementation of the Syslog trait for Linux.
6 
7 use std::fs::File;
8 use std::io::Write;
9 use std::mem;
10 use std::os::unix::io::AsRawFd;
11 use std::os::unix::io::FromRawFd;
12 use std::os::unix::net::UnixDatagram;
13 use std::ptr::null;
14 
15 use libc::closelog;
16 use libc::fcntl;
17 use libc::localtime_r;
18 use libc::openlog;
19 use libc::time;
20 use libc::time_t;
21 use libc::tm;
22 use libc::F_GETFD;
23 use libc::LOG_NDELAY;
24 use libc::LOG_PERROR;
25 use libc::LOG_PID;
26 use libc::LOG_USER;
27 use once_cell::sync::OnceCell;
28 
29 use super::super::getpid;
30 use crate::syslog::Error;
31 use crate::syslog::Facility;
32 use crate::syslog::Priority;
33 use crate::syslog::Syslog;
34 use crate::RawDescriptor;
35 
36 /// Global syslog socket derived from the fd opened by `openlog()`.
37 /// This is initialized in `PlatformSyslog::new()`.
38 static SYSLOG_SOCKET: OnceCell<UnixDatagram> = OnceCell::new();
39 
40 pub struct PlatformSyslog {}
41 
42 struct SyslogSocket {}
43 
44 impl Write for SyslogSocket {
write(&mut self, buf: &[u8]) -> std::io::Result<usize>45     fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
46         if let Some(socket) = SYSLOG_SOCKET.get() {
47             // If `send()` fails, there is nothing we can do about it, so just ignore the result.
48             let _ = socket.send(buf);
49         }
50 
51         // Abandon all hope but this is fine
52         Ok(buf.len())
53     }
54 
flush(&mut self) -> std::io::Result<()>55     fn flush(&mut self) -> std::io::Result<()> {
56         Ok(())
57     }
58 }
59 
60 impl Syslog for PlatformSyslog {
new( proc_name: String, facility: Facility, ) -> Result<(Option<Box<dyn log::Log + Send>>, Option<RawDescriptor>), Error>61     fn new(
62         proc_name: String,
63         facility: Facility,
64     ) -> Result<(Option<Box<dyn log::Log + Send>>, Option<RawDescriptor>), Error> {
65         // Calling openlog_and_get_socket() more than once will cause the previous syslogger FD to
66         // be closed, invalidating the log::Log object in an unsafe manner. The OnceCell
67         // get_or_try_init() ensures we only call it once.
68         //
69         // b/238923791 is tracking fixing this problem.
70         let socket = SYSLOG_SOCKET.get_or_try_init(openlog_and_get_socket)?;
71         let mut builder = env_logger::Builder::new();
72 
73         // Everything is filtered layer above
74         builder.filter_level(log::LevelFilter::Trace);
75 
76         let fd = socket.as_raw_fd();
77         builder.target(env_logger::Target::Pipe(Box::new(SyslogSocket {})));
78         builder.format(move |buf, record| {
79             const MONTHS: [&str; 12] = [
80                 "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
81             ];
82 
83             let tm = get_localtime();
84             let pri: Priority = record.level().into();
85             let prifac = (pri as u8) | (facility as u8);
86             write!(
87                 buf,
88                 "<{}>{} {:02} {:02}:{:02}:{:02} {}[{}]: ",
89                 prifac,
90                 MONTHS[tm.tm_mon as usize],
91                 tm.tm_mday,
92                 tm.tm_hour,
93                 tm.tm_min,
94                 tm.tm_sec,
95                 &proc_name,
96                 getpid()
97             )?;
98             if let Some(path) = record.file() {
99                 write!(buf, " [{}", path)?;
100                 if let Some(line) = record.line() {
101                     write!(buf, ":{}", line)?;
102                 }
103                 write!(buf, "] ")?;
104             }
105             writeln!(buf, "{}", record.args())
106         });
107         // https://github.com/env-logger-rs/env_logger/issues/208
108         builder.is_test(true);
109         Ok((Some(Box::new(builder.build())), Some(fd)))
110     }
111 }
112 
113 // Uses libc's openlog function to get a socket to the syslogger. By getting the socket this way, as
114 // opposed to connecting to the syslogger directly, libc's internal state gets initialized for other
115 // libraries (e.g. minijail) that make use of libc's syslog function. Note that this function
116 // depends on no other threads or signal handlers being active in this process because they might
117 // create FDs.
118 //
119 // TODO(zachr): Once https://android-review.googlesource.com/470998 lands, there won't be any
120 // libraries in use that hard depend on libc's syslogger. Remove this and go back to making the
121 // connection directly once minjail is ready.
openlog_and_get_socket() -> Result<UnixDatagram, Error>122 fn openlog_and_get_socket() -> Result<UnixDatagram, Error> {
123     // SAFETY:
124     // closelog first in case there was already a file descriptor open.  Safe because it takes no
125     // arguments and just closes an open file descriptor.  Does nothing if the file descriptor
126     // was not already open.
127     unsafe {
128         closelog();
129     }
130 
131     // Ordinarily libc's FD for the syslog connection can't be accessed, but we can guess that the
132     // FD that openlog will be getting is the lowest unused FD. To guarantee that an FD is opened in
133     // this function we use the LOG_NDELAY to tell openlog to connect to the syslog now. To get the
134     // lowest unused FD, we open a dummy file (which the manual says will always return the lowest
135     // fd), and then close that fd. Voilà, we now know the lowest numbered FD. The call to openlog
136     // will make use of that FD, and then we just wrap a `UnixDatagram` around it for ease of use.
137     let fd = File::open("/dev/null")
138         .map_err(Error::GetLowestFd)?
139         .as_raw_fd();
140 
141     // SAFETY: See comments for each unsafe line in the block.
142     unsafe {
143         // Safe because openlog accesses no pointers because `ident` is null, only valid flags are
144         // used, and it returns no error.
145         openlog(null(), LOG_NDELAY | LOG_PERROR | LOG_PID, LOG_USER);
146         // For safety, ensure the fd we guessed is valid. The `fcntl` call itself only reads the
147         // file descriptor table of the current process, which is trivially safe.
148         if fcntl(fd, F_GETFD) >= 0 {
149             Ok(UnixDatagram::from_raw_fd(fd))
150         } else {
151             Err(Error::InvalidFd)
152         }
153     }
154 }
155 
get_localtime() -> tm156 fn get_localtime() -> tm {
157     // SAFETY: See comments for each unsafe line in the block.
158     unsafe {
159         // Safe because tm is just a struct of plain data.
160         let mut tm: tm = mem::zeroed();
161         let mut now: time_t = 0;
162         // Safe because we give time a valid pointer and can never fail.
163         time(&mut now as *mut _);
164         // Safe because we give localtime_r valid pointers and can never fail.
165         localtime_r(&now, &mut tm as *mut _);
166         tm
167     }
168 }
169