1 // Copyright 2020 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 //! Implementation of the Syslog trait for Linux.
6 
7 use std::fmt;
8 use std::fs::File;
9 use std::io::{Cursor, ErrorKind, Write};
10 use std::mem;
11 use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
12 use std::os::unix::net::UnixDatagram;
13 use std::ptr::null;
14 
15 use libc::{
16     closelog, fcntl, localtime_r, openlog, time, time_t, tm, F_GETFD, LOG_NDELAY, LOG_PERROR,
17     LOG_PID, LOG_USER,
18 };
19 
20 use crate::getpid;
21 use crate::syslog::{Error, Facility, Priority, Syslog};
22 
23 const SYSLOG_PATH: &str = "/dev/log";
24 
25 pub struct PlatformSyslog {
26     socket: Option<UnixDatagram>,
27 }
28 
29 impl Syslog for PlatformSyslog {
new() -> Result<Self, Error>30     fn new() -> Result<Self, Error> {
31         Ok(Self {
32             socket: Some(openlog_and_get_socket()?),
33         })
34     }
35 
enable(&mut self, enable: bool) -> Result<(), Error>36     fn enable(&mut self, enable: bool) -> Result<(), Error> {
37         match self.socket.take() {
38             Some(_) if enable => {}
39             Some(s) => {
40                 // Because `openlog_and_get_socket` actually just "borrows" the syslog FD, this module
41                 // does not own the syslog connection and therefore should not destroy it.
42                 mem::forget(s);
43             }
44             None if enable => {
45                 let s = openlog_and_get_socket()?;
46                 self.socket = Some(s);
47             }
48             _ => {}
49         }
50         Ok(())
51     }
52 
push_fds(&self, fds: &mut Vec<RawFd>)53     fn push_fds(&self, fds: &mut Vec<RawFd>) {
54         fds.extend(self.socket.iter().map(|s| s.as_raw_fd()));
55     }
56 
log( &self, proc_name: Option<&str>, pri: Priority, fac: Facility, file_line: Option<(&str, u32)>, args: fmt::Arguments, )57     fn log(
58         &self,
59         proc_name: Option<&str>,
60         pri: Priority,
61         fac: Facility,
62         file_line: Option<(&str, u32)>,
63         args: fmt::Arguments,
64     ) {
65         const MONTHS: [&str; 12] = [
66             "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
67         ];
68 
69         let mut buf = [0u8; 1024];
70         if let Some(socket) = &self.socket {
71             let tm = get_localtime();
72             let prifac = (pri as u8) | (fac as u8);
73             let res = {
74                 let mut buf_cursor = Cursor::new(&mut buf[..]);
75                 write!(
76                     &mut buf_cursor,
77                     "<{}>{} {:02} {:02}:{:02}:{:02} {}[{}]: ",
78                     prifac,
79                     MONTHS[tm.tm_mon as usize],
80                     tm.tm_mday,
81                     tm.tm_hour,
82                     tm.tm_min,
83                     tm.tm_sec,
84                     proc_name.unwrap_or("-"),
85                     getpid()
86                 )
87                 .and_then(|()| {
88                     if let Some((file_name, line)) = &file_line {
89                         write!(&mut buf_cursor, " [{}:{}] ", file_name, line)
90                     } else {
91                         Ok(())
92                     }
93                 })
94                 .and_then(|()| write!(&mut buf_cursor, "{}", args))
95                 .map(|()| buf_cursor.position() as usize)
96             };
97 
98             if let Ok(len) = &res {
99                 send_buf(&socket, &buf[..*len])
100             }
101         }
102     }
103 }
104 
105 // Uses libc's openlog function to get a socket to the syslogger. By getting the socket this way, as
106 // opposed to connecting to the syslogger directly, libc's internal state gets initialized for other
107 // libraries (e.g. minijail) that make use of libc's syslog function. Note that this function
108 // depends on no other threads or signal handlers being active in this process because they might
109 // create FDs.
110 //
111 // TODO(zachr): Once https://android-review.googlesource.com/470998 lands, there won't be any
112 // libraries in use that hard depend on libc's syslogger. Remove this and go back to making the
113 // connection directly once minjail is ready.
openlog_and_get_socket() -> Result<UnixDatagram, Error>114 fn openlog_and_get_socket() -> Result<UnixDatagram, Error> {
115     // closelog first in case there was already a file descriptor open.  Safe because it takes no
116     // arguments and just closes an open file descriptor.  Does nothing if the file descriptor
117     // was not already open.
118     unsafe {
119         closelog();
120     }
121 
122     // Ordinarily libc's FD for the syslog connection can't be accessed, but we can guess that the
123     // FD that openlog will be getting is the lowest unused FD. To guarantee that an FD is opened in
124     // this function we use the LOG_NDELAY to tell openlog to connect to the syslog now. To get the
125     // lowest unused FD, we open a dummy file (which the manual says will always return the lowest
126     // fd), and then close that fd. Voilà, we now know the lowest numbered FD. The call to openlog
127     // will make use of that FD, and then we just wrap a `UnixDatagram` around it for ease of use.
128     let fd = File::open("/dev/null")
129         .map_err(Error::GetLowestFd)?
130         .as_raw_fd();
131 
132     unsafe {
133         // Safe because openlog accesses no pointers because `ident` is null, only valid flags are
134         // used, and it returns no error.
135         openlog(null(), LOG_NDELAY | LOG_PERROR | LOG_PID, LOG_USER);
136         // For safety, ensure the fd we guessed is valid. The `fcntl` call itself only reads the
137         // file descriptor table of the current process, which is trivially safe.
138         if fcntl(fd, F_GETFD) >= 0 {
139             Ok(UnixDatagram::from_raw_fd(fd))
140         } else {
141             Err(Error::InvalidFd)
142         }
143     }
144 }
145 
146 /// Should only be called after `init()` was called.
send_buf(socket: &UnixDatagram, buf: &[u8])147 fn send_buf(socket: &UnixDatagram, buf: &[u8]) {
148     const SEND_RETRY: usize = 2;
149 
150     for _ in 0..SEND_RETRY {
151         match socket.send(buf) {
152             Ok(_) => break,
153             Err(e) => match e.kind() {
154                 ErrorKind::ConnectionRefused
155                 | ErrorKind::ConnectionReset
156                 | ErrorKind::ConnectionAborted
157                 | ErrorKind::NotConnected => {
158                     let res = socket.connect(SYSLOG_PATH);
159                     if res.is_err() {
160                         break;
161                     }
162                 }
163                 _ => {}
164             },
165         }
166     }
167 }
168 
get_localtime() -> tm169 fn get_localtime() -> tm {
170     unsafe {
171         // Safe because tm is just a struct of plain data.
172         let mut tm: tm = mem::zeroed();
173         let mut now: time_t = 0;
174         // Safe because we give time a valid pointer and can never fail.
175         time(&mut now as *mut _);
176         // Safe because we give localtime_r valid pointers and can never fail.
177         localtime_r(&now, &mut tm as *mut _);
178         tm
179     }
180 }
181