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