1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
// Copyright 2020 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

//! Implementation of the Syslog trait for Linux.

use std::fs::File;
use std::io::Write;
use std::mem;
use std::os::unix::io::AsRawFd;
use std::os::unix::io::FromRawFd;
use std::os::unix::net::UnixDatagram;
use std::ptr::null;

use libc::closelog;
use libc::fcntl;
use libc::localtime_r;
use libc::openlog;
use libc::time;
use libc::time_t;
use libc::tm;
use libc::F_GETFD;
use libc::LOG_NDELAY;
use libc::LOG_PERROR;
use libc::LOG_PID;
use libc::LOG_USER;
use once_cell::sync::OnceCell;

use super::super::getpid;
use crate::syslog::Error;
use crate::syslog::Facility;
use crate::syslog::Priority;
use crate::syslog::Syslog;
use crate::RawDescriptor;

/// Global syslog socket derived from the fd opened by `openlog()`.
/// This is initialized in `PlatformSyslog::new()`.
static SYSLOG_SOCKET: OnceCell<UnixDatagram> = OnceCell::new();

pub struct PlatformSyslog {}

struct SyslogSocket {}

impl Write for SyslogSocket {
    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
        if let Some(socket) = SYSLOG_SOCKET.get() {
            // If `send()` fails, there is nothing we can do about it, so just ignore the result.
            let _ = socket.send(buf);
        }

        // Abandon all hope but this is fine
        Ok(buf.len())
    }

    fn flush(&mut self) -> std::io::Result<()> {
        Ok(())
    }
}

impl Syslog for PlatformSyslog {
    fn new(
        proc_name: String,
        facility: Facility,
    ) -> Result<(Option<Box<dyn log::Log + Send>>, Option<RawDescriptor>), Error> {
        // Calling openlog_and_get_socket() more than once will cause the previous syslogger FD to
        // be closed, invalidating the log::Log object in an unsafe manner. The OnceCell
        // get_or_try_init() ensures we only call it once.
        //
        // b/238923791 is tracking fixing this problem.
        let socket = SYSLOG_SOCKET.get_or_try_init(openlog_and_get_socket)?;
        let mut builder = env_logger::Builder::new();

        // Everything is filtered layer above
        builder.filter_level(log::LevelFilter::Trace);

        let fd = socket.as_raw_fd();
        builder.target(env_logger::Target::Pipe(Box::new(SyslogSocket {})));
        builder.format(move |buf, record| {
            const MONTHS: [&str; 12] = [
                "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
            ];

            let tm = get_localtime();
            let pri: Priority = record.level().into();
            let prifac = (pri as u8) | (facility as u8);
            write!(
                buf,
                "<{}>{} {:02} {:02}:{:02}:{:02} {}[{}]: ",
                prifac,
                MONTHS[tm.tm_mon as usize],
                tm.tm_mday,
                tm.tm_hour,
                tm.tm_min,
                tm.tm_sec,
                &proc_name,
                getpid()
            )?;
            if let Some(path) = record.file() {
                write!(buf, " [{}", path)?;
                if let Some(line) = record.line() {
                    write!(buf, ":{}", line)?;
                }
                write!(buf, "] ")?;
            }
            writeln!(buf, "{}", record.args())
        });
        // https://github.com/env-logger-rs/env_logger/issues/208
        builder.is_test(true);
        Ok((Some(Box::new(builder.build())), Some(fd)))
    }
}

// Uses libc's openlog function to get a socket to the syslogger. By getting the socket this way, as
// opposed to connecting to the syslogger directly, libc's internal state gets initialized for other
// libraries (e.g. minijail) that make use of libc's syslog function. Note that this function
// depends on no other threads or signal handlers being active in this process because they might
// create FDs.
//
// TODO(zachr): Once https://android-review.googlesource.com/470998 lands, there won't be any
// libraries in use that hard depend on libc's syslogger. Remove this and go back to making the
// connection directly once minjail is ready.
fn openlog_and_get_socket() -> Result<UnixDatagram, Error> {
    // SAFETY:
    // closelog first in case there was already a file descriptor open.  Safe because it takes no
    // arguments and just closes an open file descriptor.  Does nothing if the file descriptor
    // was not already open.
    unsafe {
        closelog();
    }

    // Ordinarily libc's FD for the syslog connection can't be accessed, but we can guess that the
    // FD that openlog will be getting is the lowest unused FD. To guarantee that an FD is opened in
    // this function we use the LOG_NDELAY to tell openlog to connect to the syslog now. To get the
    // lowest unused FD, we open a dummy file (which the manual says will always return the lowest
    // fd), and then close that fd. Voilà, we now know the lowest numbered FD. The call to openlog
    // will make use of that FD, and then we just wrap a `UnixDatagram` around it for ease of use.
    let fd = File::open("/dev/null")
        .map_err(Error::GetLowestFd)?
        .as_raw_fd();

    // SAFETY: See comments for each unsafe line in the block.
    unsafe {
        // Safe because openlog accesses no pointers because `ident` is null, only valid flags are
        // used, and it returns no error.
        openlog(null(), LOG_NDELAY | LOG_PERROR | LOG_PID, LOG_USER);
        // For safety, ensure the fd we guessed is valid. The `fcntl` call itself only reads the
        // file descriptor table of the current process, which is trivially safe.
        if fcntl(fd, F_GETFD) >= 0 {
            Ok(UnixDatagram::from_raw_fd(fd))
        } else {
            Err(Error::InvalidFd)
        }
    }
}

fn get_localtime() -> tm {
    // SAFETY: See comments for each unsafe line in the block.
    unsafe {
        // Safe because tm is just a struct of plain data.
        let mut tm: tm = mem::zeroed();
        let mut now: time_t = 0;
        // Safe because we give time a valid pointer and can never fail.
        time(&mut now as *mut _);
        // Safe because we give localtime_r valid pointers and can never fail.
        localtime_r(&now, &mut tm as *mut _);
        tm
    }
}