Skip to main content

relibc/header/sys_syslog/
logger.rs

1use alloc::{borrow::ToOwned, string::String};
2use core::{ffi::VaList, ptr::null_mut};
3
4use crate::{
5    c_str::CStr,
6    error::Result,
7    header::{
8        stdio::{fprintf, printf::printf, stderr},
9        time::time,
10        unistd::getpid,
11    },
12    io::Write,
13    platform::{
14        self,
15        types::{c_char, c_int},
16    },
17    sync::Mutex,
18};
19
20use bitflags::bitflags;
21use chrono::{DateTime, Utc};
22
23use super::{
24    LOG_ALERT, LOG_AUTH, LOG_AUTHPRIV, LOG_CONS, LOG_CRIT, LOG_CRON, LOG_DAEMON, LOG_DEBUG,
25    LOG_EMERG, LOG_ERR, LOG_FTP, LOG_INFO, LOG_KERN, LOG_LOCAL0, LOG_LOCAL1, LOG_LOCAL2,
26    LOG_LOCAL3, LOG_LOCAL4, LOG_LOCAL5, LOG_LOCAL6, LOG_LOCAL7, LOG_LPR, LOG_MAIL, LOG_MASK,
27    LOG_NDELAY, LOG_NEWS, LOG_NOTICE, LOG_NOWAIT, LOG_ODELAY, LOG_PERROR, LOG_PID, LOG_SYSLOG,
28    LOG_UPTO, LOG_USER, LOG_UUCP, LOG_WARNING, sys::LogFile,
29};
30
31pub(super) static LOGGER: Mutex<LogParams<LogFile>> = Mutex::new(LogParams::new(None));
32
33pub(super) struct LogParams<L: LogSink> {
34    /// Identity prepended to each log message. POSIX does not specific what to do when it's empty,
35    /// but the program name is a common default.
36    ident: String,
37    pub opt: Config,
38    pub mask: Priority,
39    writer: Option<L>,
40}
41
42impl<L: LogSink> LogParams<L> {
43    pub const fn new(writer: Option<L>) -> Self {
44        LogParams {
45            ident: String::new(),
46            opt: Config::DelayOpen,
47            mask: Priority::from_bits_truncate(Priority::User.bits() | Priority::UpToDebug.bits()),
48            writer,
49        }
50    }
51
52    pub fn write_log(&mut self, priority: Priority, message: CStr<'_>, ap: VaList) {
53        if message.is_empty() {
54            return;
55        }
56        if self.ident.is_empty() {
57            self.set_identity(None);
58        }
59
60        let epoch = unsafe { time(null_mut()) };
61        let currtime: DateTime<Utc> = DateTime::from_timestamp(epoch, 0).unwrap_or_default();
62        let currtime_s = currtime.format("%b %e %T %Y");
63        let pid = self.opt.contains(Config::Pid).then(|| getpid());
64
65        // journald from systemd rewrites log messages from syslog into its own style. We'll
66        // still use the same style as other libc even though it's implementation specific.
67        let mut buffer = if let Some(pid) = pid {
68            format!(
69                "<{}>{} {}{}: ",
70                priority.bits(),
71                currtime_s,
72                self.ident,
73                pid
74            )
75            .into_bytes()
76        } else {
77            format!("<{}>{} {}: ", priority.bits(), currtime_s, self.ident).into_bytes()
78        };
79        let prefix = buffer.len();
80
81        // SAFETY:
82        // * Assumes caller passed in a valid C string; printf should handle that invariant.
83        // * `buffer` grows to fit the formatted string.
84        unsafe { printf(&mut buffer, message, ap) };
85        buffer.extend(b"\n\0");
86
87        if self.maybe_open_logger().is_ok()
88            && self
89                .writer
90                .as_mut()
91                .map(|w| w.writer().write_all(&buffer).is_err())
92                .unwrap_or(true)
93        {
94            // Try reopening the log file once and retrying as musl does.
95            if !(self.open_logger().is_ok()
96                && self
97                    .writer
98                    .as_mut()
99                    .and_then(|w| w.writer().write_all(&buffer).ok())
100                    .is_some())
101                && self.opt.contains(Config::Console)
102            {
103                // TODO: Log error to /dev/console & Redox equivalent
104            }
105        }
106        if self.opt.contains(Config::PError) {
107            // SAFETY:
108            // * `ident` is a valid byte string that is NUL terminated when set.
109            // * `buffer` is a valid byte string that is NUL terminated above.
110            unsafe {
111                // musl and glibc only print the message rather than the prefix + message to stderr
112                fprintf(
113                    stderr,
114                    c"%s: %s".as_ptr(),
115                    self.ident.as_ptr().cast::<c_char>(),
116                    buffer[prefix..].as_ptr().cast::<c_char>(),
117                );
118            }
119        }
120    }
121
122    /// Set or clear log identity from a C string.
123    ///
124    /// Null or empty identities are valid as it just resets the global ident.
125    pub fn set_identity_cstr(&mut self, ident: Option<CStr<'_>>) {
126        let ident = ident
127            .and_then(|ident| (!ident.is_empty()).then(|| ident.to_str().ok()))
128            .flatten();
129        self.set_identity(ident);
130    }
131
132    /// Set or clear log identity.
133    ///
134    /// The log identity is prepended to each message. If unset, the program name will be used as a
135    /// default.
136    pub fn set_identity(&mut self, ident: Option<&str>) {
137        self.ident = ident
138            .map(|ident| {
139                let ident = ident.bytes().chain([0]).collect();
140                // SAFETY: Already validated
141                unsafe { String::from_utf8_unchecked(ident) }
142            })
143            .unwrap_or_else(|| {
144                unsafe { CStr::from_nullable_ptr(platform::program_invocation_short_name) }
145                    .and_then(|name| {
146                        let name = name.to_str().ok()?.bytes().chain([0]).collect();
147                        // SAFETY: Validated above
148                        Some(unsafe { String::from_utf8_unchecked(name) })
149                    })
150                    .unwrap_or_else(|| "\0".to_owned())
151            });
152    }
153
154    /// Open the internal [`LogFile`] if it's not open.
155    pub fn maybe_open_logger(&mut self) -> Result<()> {
156        if self.writer.is_none() {
157            self.open_logger()
158        } else {
159            Ok(())
160        }
161    }
162
163    /// Open or reopen the internal [`LogFile`].
164    pub fn open_logger(&mut self) -> Result<()> {
165        L::open().map(|file| {
166            self.writer.replace(file);
167        })
168    }
169
170    /// Close the open writer to the system logger (optional).
171    pub fn close(&mut self) {
172        self.writer.take();
173    }
174}
175
176/// Operating system specific log handling.
177pub(super) trait LogSink {
178    type Sink: Write;
179
180    fn open() -> Result<Self>
181    where
182        Self: Sized;
183
184    fn writer(&mut self) -> &mut Self::Sink;
185}
186
187bitflags! {
188    #[derive(Clone, Copy)]
189    pub struct Config: c_int {
190        const Pid = LOG_PID;
191        const Console = LOG_CONS;
192        const DelayOpen = LOG_ODELAY;
193        const NoDelay = LOG_NDELAY;
194        const NoWait = LOG_NOWAIT;
195        const PError = LOG_PERROR;
196    }
197}
198
199bitflags! {
200    /// Packed Facility-Priority bit field.
201    #[derive(Clone, Copy)]
202    pub struct Priority: c_int {
203        const Emerg = LOG_EMERG;
204        const Alert = LOG_ALERT;
205        const Crit = LOG_CRIT;
206        const Err = LOG_ERR;
207        const Warn = LOG_WARNING;
208        const Notice = LOG_NOTICE;
209        const Info = LOG_INFO;
210        const Debug = LOG_DEBUG;
211
212        const UpToEmerg = LOG_UPTO(LOG_EMERG);
213        const UpToAlert = LOG_UPTO(LOG_ALERT);
214        const UpToCrit = LOG_UPTO(LOG_CRIT);
215        const UpToErr = LOG_UPTO(LOG_ERR);
216        const UpToWarn = LOG_UPTO(LOG_WARNING);
217        const UpToNotice = LOG_UPTO(LOG_NOTICE);
218        const UpToInfo = LOG_UPTO(LOG_INFO);
219        const UpToDebug = LOG_UPTO(LOG_DEBUG);
220
221        const Kern = LOG_KERN;
222        const User = LOG_USER;
223        const Mail = LOG_MAIL;
224        const Daemon = LOG_DAEMON;
225        const Auth = LOG_AUTH;
226        const Syslog = LOG_SYSLOG;
227        const Printer = LOG_LPR;
228        const News = LOG_NEWS;
229        const UUCP = LOG_UUCP;
230        const CRON = LOG_CRON;
231        const AuthPriv = LOG_AUTHPRIV;
232        const FTP = LOG_FTP;
233        const Local0 = LOG_LOCAL0;
234        const Local1 = LOG_LOCAL1;
235        const Local2 = LOG_LOCAL2;
236        const Local3 = LOG_LOCAL3;
237        const Local4 = LOG_LOCAL4;
238        const Local5 = LOG_LOCAL5;
239        const Local6 = LOG_LOCAL6;
240        const Local7 = LOG_LOCAL7;
241
242        // Internal constants for extracting facility or priority from a packed bitfield.
243        const FacilityMask = 0x3ff;
244        const PriorityMask = !Self::FacilityMask.bits();
245    }
246}
247
248impl Priority {
249    /// Keep facility but replace severity mask.
250    pub fn with_mask(self, mask: c_int) -> Option<Self> {
251        // Fail on invalid bits and drop invalid facility bits.
252        let mask = Self::from_bits(mask)? & Self::FacilityMask;
253        let facility = self & Self::PriorityMask;
254        Some(mask | facility)
255    }
256
257    /// Keep mask but replace facility.
258    pub fn with_facility(self, facility: c_int) -> Option<Self> {
259        // Fail on invalid bits and drop invalid priority bits.
260        let facility = Self::from_bits(facility)? & Self::PriorityMask;
261        let mask = self & Self::FacilityMask;
262        Some(mask | facility)
263    }
264
265    /// Returns if a message of `priority` should be retained by this mask.
266    pub fn should_log(self, priority: Self) -> bool {
267        (self & Self::FacilityMask)
268            .contains(Priority::from_bits_truncate(LOG_MASK(priority.bits())))
269    }
270}