From 64d9847e37a2f7a98e802f013c33af658b1a9231 Mon Sep 17 00:00:00 2001 From: Josh Megnauth Date: Sun, 20 Jul 2025 02:52:36 -0400 Subject: [PATCH] Linux support for syslog.h Linux's syslog is a local socket, so this uses the recent UDS work. * Most of redox.rs is refactored into mod.rs now which has all of the C facing functions and consts. * I wrapped the global logger in a Mutex instead of a RwLock. The logger is almost always locked for writing so a Mutex is simpler as RwLock provides no benefits. * I implemented LOG_PERROR which also prints errors to stderr as well as the log. * Syslog should be sys/syslog.h with syslog.h as an alias (the original code only had syslog.h). --- include/syslog.h | 1 + src/header/mod.rs | 3 +- .../{syslog => sys_syslog}/cbindgen.toml | 3 +- src/header/sys_syslog/linux.rs | 74 ++++++ src/header/sys_syslog/logger.rs | 174 +++++++++++++++ src/header/sys_syslog/mod.rs | 111 +++++++++ src/header/sys_syslog/redox.rs | 25 +++ src/header/syslog/mod.rs | 9 - src/header/syslog/redox.rs | 211 ------------------ tests/syslog/syslog.c | 30 ++- 10 files changed, 412 insertions(+), 229 deletions(-) create mode 100644 include/syslog.h rename src/header/{syslog => sys_syslog}/cbindgen.toml (73%) create mode 100644 src/header/sys_syslog/linux.rs create mode 100644 src/header/sys_syslog/logger.rs create mode 100644 src/header/sys_syslog/mod.rs create mode 100644 src/header/sys_syslog/redox.rs delete mode 100644 src/header/syslog/mod.rs delete mode 100644 src/header/syslog/redox.rs diff --git a/include/syslog.h b/include/syslog.h new file mode 100644 index 0000000000..830b4928ad --- /dev/null +++ b/include/syslog.h @@ -0,0 +1 @@ +#include diff --git a/src/header/mod.rs b/src/header/mod.rs index 0037bb6faa..cbd1480738 100644 --- a/src/header/mod.rs +++ b/src/header/mod.rs @@ -98,13 +98,12 @@ pub mod arch_x64_user; #[cfg(not(target_arch = "x86"))] // TODO: x86 pub mod sys_procfs; pub mod sys_random; +pub mod sys_syslog; pub mod sys_types; pub mod sys_uio; pub mod sys_un; pub mod sys_utsname; pub mod sys_wait; -// TODO: syslog.h -pub mod syslog; pub mod tar; // TODO: term.h (deprecated) pub mod termios; diff --git a/src/header/syslog/cbindgen.toml b/src/header/sys_syslog/cbindgen.toml similarity index 73% rename from src/header/syslog/cbindgen.toml rename to src/header/sys_syslog/cbindgen.toml index d11110d7a4..082186f4ea 100644 --- a/src/header/syslog/cbindgen.toml +++ b/src/header/sys_syslog/cbindgen.toml @@ -1,5 +1,5 @@ sys_includes = ["bits/syslog.h"] -include_guard = "_RELIBC_SYSLOG_H" +include_guard = "_RELIBC_SYS_SYSLOG_H" language = "C" style = "Type" no_includes = true @@ -7,4 +7,3 @@ cpp_compat = true [enum] prefix_with_name = true - diff --git a/src/header/sys_syslog/linux.rs b/src/header/sys_syslog/linux.rs new file mode 100644 index 0000000000..0ffb56fa8f --- /dev/null +++ b/src/header/sys_syslog/linux.rs @@ -0,0 +1,74 @@ +use crate::{ + error::{Errno, Result}, + fs::File, + header::{ + sys_socket::{ + connect, + constants::{AF_UNIX, SOCK_CLOEXEC, SOCK_DGRAM}, + sockaddr, socket, + }, + sys_un::sockaddr_un, + unistd::close, + }, + io::BufWriter, + platform::ERRNO, +}; + +use super::logger::LogSink; + +/// Unix Domain Socket connected to /dev/log. +pub struct LogFile(BufWriter); + +impl LogSink for LogFile { + type Sink = BufWriter; + + fn open() -> Result + where + Self: Sized, + { + let log_addr = { + let path = c"/dev/log"; + let mut sockaddr: sockaddr_un = unsafe { core::mem::zeroed() }; + sockaddr.sun_family = AF_UNIX as _; + unsafe { + core::ptr::copy_nonoverlapping( + path.as_ptr(), + sockaddr.sun_path.as_mut_ptr(), + path.count_bytes(), + ) + }; + path + }; + let log_fd = { + let result = unsafe { socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0) }; + if result > 0 { + Ok(result) + } else { + Err(Errno(ERRNO.get())) + } + }?; + + // SAFETY: + // * connect handles invalid descriptors. + // * log_addr is a sockaddr_un so the size is correct. + if unsafe { + connect( + log_fd, + &raw const log_addr as *const sockaddr, + size_of::(), + ) < 0 + } { + // In case close sets ERRNO. + let e = ERRNO.get(); + close(log_fd); + return Err(Errno(e)); + } + + Ok(Self(BufWriter::new(File::new(log_fd)))) + } + + #[inline(always)] + fn writer(&mut self) -> &mut Self::Sink { + &mut self.0 + } +} diff --git a/src/header/sys_syslog/logger.rs b/src/header/sys_syslog/logger.rs new file mode 100644 index 0000000000..53182290f0 --- /dev/null +++ b/src/header/sys_syslog/logger.rs @@ -0,0 +1,174 @@ +use alloc::{ + borrow::ToOwned, + string::{String, ToString}, + vec::Vec, +}; +use core::{ffi::VaList, ptr::null_mut}; + +use crate::{ + c_str::CStr, + error::Result, + header::{ + stdio::{fprintf, printf::printf, stderr}, + time::time, + unistd::getpid, + }, + io::Write, + platform::{self, types::*}, + sync::Mutex, +}; + +use bitflags::bitflags; +use chrono::{DateTime, Utc}; + +use super::{sys::LogFile, LOG_CONS, LOG_NDELAY, LOG_NOWAIT, LOG_ODELAY, LOG_PERROR, LOG_PID}; + +pub(super) static LOGGER: Mutex> = Mutex::new(LogParams::new(None)); + +pub struct LogParams { + /// Identity prepended to each log message. POSIX does not specific what to do when it's empty, + /// but the program name is a common default. + ident: String, + pub opt: Config, + pub facility: c_int, + pub mask: c_int, + writer: Option, +} + +impl LogParams { + pub const fn new(writer: Option) -> Self { + LogParams { + ident: String::new(), + opt: Config::DelayOpen, + facility: super::LOG_USER, + mask: 0xff, + writer, + } + } + + pub fn write_log(&mut self, mut priority: c_int, message: *const c_char, mut ap: VaList) { + let mut epoch: i64 = 0; + unsafe { + epoch = time(null_mut()); + } + let currtime: DateTime = + DateTime::from_timestamp(epoch, 0).expect("Couldn't retrieve broken-down time."); + let currtime_s = currtime.format("%b %e %T %Y"); + let pid = self.opt.contains(Config::Pid).then(|| getpid()); + if ((priority & super::LOG_FACMASK) == 0) { + priority |= self.facility + }; + + // journald from systemd rewrites log messages from syslog into its own style. We'll + // still use the same style as other libc even though it's implementation specific. + let mut buffer = if let Some(pid) = pid { + format!("<{}>{} {}{}: ", priority, currtime_s, self.ident, pid).into_bytes() + } else { + format!("<{}>{} {}: ", priority, currtime_s, self.ident).into_bytes() + }; + // SAFETY: + // * Assumes caller passed in a valid C string; printf should handle that invariant. + // * `buffer` grows to fit the formatted string. + unsafe { printf(&mut buffer, message, ap) }; + buffer.extend(b"\n\0"); + + if self.maybe_open_logger().is_ok() { + if self + .writer + .as_mut() + .map(|w| w.writer().write_all(&buffer).is_err()) + .unwrap_or(true) + { + // Try reopening the log file once and retrying as musl does. + if !self + .open_logger() + .is_ok() + .then(|| { + self.writer + .as_mut() + .map(|w| w.writer().write_all(&buffer).is_ok()) + .unwrap_or_default() + }) + .unwrap_or_default() + && self.opt.contains(Config::Console) + { + // TODO: Log error to /dev/console & Redox equivalent + } + } + } + if self.opt.contains(Config::PError) { + // SAFETY: `buffer` is a valid byte string that is NUL terminated. + unsafe { + fprintf(stderr, c"%s".as_ptr(), buffer.as_ptr() as *const c_char); + } + } + } + + /// Set or clear log identity from a C string. + /// + /// Null or empty identities are valid as it just resets the global ident. + pub fn set_identity_cstr(&mut self, ident: Option>) { + let ident = ident + .and_then(|ident| (!ident.is_empty()).then(|| ident.to_str().ok())) + .flatten() + .map(ToString::to_string); + self.set_identity(ident); + } + + /// Set or clear log identity. + /// + /// The log identity is prepended to each message. If unset, the program name will be used as a + /// default. + pub fn set_identity(&mut self, ident: Option) { + self.ident = ident.unwrap_or_else(|| { + unsafe { CStr::from_nullable_ptr(platform::program_invocation_short_name) } + .and_then(|name| name.to_str().ok()) + .unwrap_or_default() + .to_owned() + }); + } + + /// Open the internal [`LogFile`] if it's not open. + pub fn maybe_open_logger(&mut self) -> Result<()> { + if self.writer.is_none() { + self.open_logger() + } else { + Ok(()) + } + } + + /// Open or reopen the internal [`LogFile`]. + pub fn open_logger(&mut self) -> Result<()> { + L::open().map(|file| { + self.writer.replace(file); + }) + } + + /// Close the open writer to the system logger (optional). + pub fn close(&mut self) { + self.writer.take(); + } +} + +/// Operating system specific log handling. +pub(super) trait LogSink { + type Sink: Write; + + fn open() -> Result + where + Self: Sized; + + fn writer(&mut self) -> &mut Self::Sink; +} + +bitflags! { + #[derive(Clone, Copy)] + pub struct Config: c_int { + const Pid = LOG_PID; + const Console = LOG_CONS; + const DelayOpen = LOG_ODELAY; + const NoDelay = LOG_NDELAY; + const NoWait = LOG_NOWAIT; + const PError = LOG_PERROR; + } +} diff --git a/src/header/sys_syslog/mod.rs b/src/header/sys_syslog/mod.rs new file mode 100644 index 0000000000..9c3025ce8a --- /dev/null +++ b/src/header/sys_syslog/mod.rs @@ -0,0 +1,111 @@ +// This is syslog.h implemented based on POSIX.1-2017 +// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/syslog.h.html + +#[cfg(target_os = "redox")] +#[path = "redox.rs"] +pub mod sys; + +#[cfg(target_os = "linux")] +#[path = "linux.rs"] +pub mod sys; + +pub mod logger; + +use core::ffi::VaList; + +use crate::{c_str::CStr, platform::types::*}; +use logger::LOGGER; + +// Values for logopt +pub const LOG_PID: c_int = 0x01; +pub const LOG_CONS: c_int = 0x02; +pub const LOG_ODELAY: c_int = 0x04; +pub const LOG_NDELAY: c_int = 0x08; +pub const LOG_NOWAIT: c_int = 0x10; +pub const LOG_PERROR: c_int = 0x20; + +// Facilities +// Note: in this case I relied more on MUSL than on POSIX1.2017 +// as it appears there were some Linux-specific facilities +// Which could be used by programs we want to port over +// And GNU Libc had these too +pub const LOG_KERN: c_int = 0 << 3; +pub const LOG_USER: c_int = 1 << 3; +pub const LOG_MAIL: c_int = 2 << 3; +pub const LOG_DAEMON: c_int = 3 << 3; +pub const LOG_AUTH: c_int = 4 << 3; +pub const LOG_SYSLOG: c_int = 5 << 3; +pub const LOG_LPR: c_int = 6 << 3; +pub const LOG_NEWS: c_int = 7 << 3; +pub const LOG_UUCP: c_int = 8 << 3; +pub const LOG_CRON: c_int = 9 << 3; +pub const LOG_AUTHPRIV: c_int = 10 << 3; +pub const LOG_FTP: c_int = 11 << 3; +pub const LOG_LOCAL0: c_int = 16 << 3; +pub const LOG_LOCAL1: c_int = 17 << 3; +pub const LOG_LOCAL2: c_int = 18 << 3; +pub const LOG_LOCAL3: c_int = 19 << 3; +pub const LOG_LOCAL4: c_int = 20 << 3; +pub const LOG_LOCAL5: c_int = 21 << 3; +pub const LOG_LOCAL6: c_int = 22 << 3; +pub const LOG_LOCAL7: c_int = 23 << 3; +pub const LOG_NFACILITIES: c_int = 24; + +// Priorities +pub const LOG_EMERG: c_int = 0; +pub const LOG_ALERT: c_int = 1; +pub const LOG_CRIT: c_int = 2; +pub const LOG_ERR: c_int = 3; +pub const LOG_WARNING: c_int = 4; +pub const LOG_NOTICE: c_int = 5; +pub const LOG_INFO: c_int = 6; +pub const LOG_DEBUG: c_int = 7; + +// Internal constant for extracting facility from a packed facility-priority bitfield. +// TODO: Remove or use a packed i32 like musl. +const LOG_FACMASK: c_int = 0x3f8; + +#[no_mangle] +pub extern "C" fn setlogmask(maskpri: c_int) -> c_int { + let mut params = LOGGER.lock(); + let ret = params.mask; + if (maskpri != 0) { + params.mask = maskpri; + } + ret +} + +#[no_mangle] +pub unsafe extern "C" fn openlog(ident: *const c_char, opt: c_int, facility: c_int) { + let ident = unsafe { CStr::from_nullable_ptr(ident) }; + let conf = logger::Config::from_bits_truncate(opt); + + let mut params = LOGGER.lock(); + params.set_identity_cstr(ident); + params.opt = conf; + params.facility = facility; + + // Ensure log is ready to write now instead of checking on the first message. + if conf.contains(logger::Config::NoDelay) { + params.open_logger(); + } +} + +#[no_mangle] +pub unsafe extern "C" fn vsyslog(priority: c_int, message: *const c_char, mut ap: VaList) { + let mut logger = LOGGER.lock(); + if (((logger.mask & (1 << (priority & 7))) == 0) || ((priority & !0x3ff) != 0)) { + return; + }; + logger.write_log(priority, message, ap); +} + +#[no_mangle] +pub unsafe extern "C" fn syslog(priority: c_int, message: *const c_char, mut __valist: ...) { + vsyslog(priority, message, __valist.as_va_list()); +} + +#[no_mangle] +pub extern "C" fn closelog() { + LOGGER.lock().close(); +} diff --git a/src/header/sys_syslog/redox.rs b/src/header/sys_syslog/redox.rs new file mode 100644 index 0000000000..69539e1b14 --- /dev/null +++ b/src/header/sys_syslog/redox.rs @@ -0,0 +1,25 @@ +use crate::{ + error::{Errno, Result}, + fs::File, + header::fcntl, + io::BufWriter, +}; + +use super::logger::LogSink; + +/// Write logs to Redox's log scheme. +pub struct LogFile(BufWriter); + +impl LogSink for LogFile { + type Sink = BufWriter; + + #[inline(always)] + fn open() -> Result { + File::open(c"/scheme/log".into(), fcntl::O_WRONLY).map(|file| Self(BufWriter::new(file))) + } + + #[inline(always)] + fn writer(&mut self) -> &mut Self::Sink { + &mut self.0 + } +} diff --git a/src/header/syslog/mod.rs b/src/header/syslog/mod.rs deleted file mode 100644 index 22f5d631ad..0000000000 --- a/src/header/syslog/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -// This is syslog.h implemented based on POSIX.1-2017 -// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/syslog.h.html - -#[cfg(target_os = "redox")] -pub use self::sys::*; - -#[cfg(target_os = "redox")] -#[path = "redox.rs"] -pub mod sys; diff --git a/src/header/syslog/redox.rs b/src/header/syslog/redox.rs deleted file mode 100644 index dcf10f4dda..0000000000 --- a/src/header/syslog/redox.rs +++ /dev/null @@ -1,211 +0,0 @@ -use crate::{ - c_str::CStr, - error::Errno, - fs::File, - header::{ - fcntl, - stdio::printf::printf, - string::{strlen, strncpy}, - time::time, - unistd::getpid, - }, - io::Write, - platform::types::*, - sync::{ - rwlock::{ReadGuard, RwLock, WriteGuard}, - Once, - }, -}; -use alloc::string::String; -use chrono::{DateTime, Utc}; -use core::{ffi::VaList, ptr::null_mut}; - -// Values for logopt -pub const LOG_PID: c_int = 0x01; -pub const LOG_CONS: c_int = 0x02; -pub const LOG_ODELAY: c_int = 0x04; -pub const LOG_NDELAY: c_int = 0x08; -pub const LOG_NOWAIT: c_int = 0x10; - -// Facilities -// Note: in this case I relied more on MUSL than on POSIX1.2017 -// as it appears there were some Linux-specific facilities -// Which could be used by programs we want to port over -// And GNU Libc had these too -pub const LOG_KERN: c_int = 0 << 3; -pub const LOG_USER: c_int = 1 << 3; -pub const LOG_MAIL: c_int = 2 << 3; -pub const LOG_DAEMON: c_int = 3 << 3; -pub const LOG_AUTH: c_int = 4 << 3; -pub const LOG_SYSLOG: c_int = 5 << 3; -pub const LOG_LPR: c_int = 6 << 3; -pub const LOG_NEWS: c_int = 7 << 3; -pub const LOG_UUCP: c_int = 8 << 3; -pub const LOG_CRON: c_int = 9 << 3; -pub const LOG_AUTHPRIV: c_int = 10 << 3; -pub const LOG_FTP: c_int = 11 << 3; -pub const LOG_LOCAL0: c_int = 16 << 3; -pub const LOG_LOCAL1: c_int = 17 << 3; -pub const LOG_LOCAL2: c_int = 18 << 3; -pub const LOG_LOCAL3: c_int = 19 << 3; -pub const LOG_LOCAL4: c_int = 20 << 3; -pub const LOG_LOCAL5: c_int = 21 << 3; -pub const LOG_LOCAL6: c_int = 22 << 3; -pub const LOG_LOCAL7: c_int = 23 << 3; -pub const LOG_NFACILITIES: c_int = 24; - -// Priorities -pub const LOG_EMERG: c_int = 0; -pub const LOG_ALERT: c_int = 1; -pub const LOG_CRIT: c_int = 2; -pub const LOG_ERR: c_int = 3; -pub const LOG_WARNING: c_int = 4; -pub const LOG_NOTICE: c_int = 5; -pub const LOG_INFO: c_int = 6; -pub const LOG_DEBUG: c_int = 7; - -pub const LOG_FACMASK: c_int = 0x3f8; - -struct LogParams { - log_ident: String, - log_opt: i32, - log_facility: i32, - log_mask: i32, -} - -impl LogParams { - fn new() -> Self { - LogParams { - log_ident: String::new(), - log_opt: 0, - log_facility: LOG_USER, - log_mask: 0xff, - } - } -} - -static LOGFILELOCK: RwLock> = RwLock::new(None); -static PARAMSLOCK: Once> = Once::new(); - -fn paramslock() -> &'static RwLock { - PARAMSLOCK.call_once(|| RwLock::new(LogParams::new())) -} - -fn logfile<'a>() -> ReadGuard<'a, Option> { - LOGFILELOCK.read() -} - -fn logfile_mut<'a>() -> WriteGuard<'a, Option> { - LOGFILELOCK.write() -} - -fn logparams<'a>() -> ReadGuard<'a, LogParams> { - paramslock().read() -} - -fn logparams_mut<'a>() -> WriteGuard<'a, LogParams> { - paramslock().write() -} - -#[no_mangle] -pub extern "C" fn setlogmask(maskpri: c_int) -> c_int { - let mut params = logparams_mut(); - let ret = params.log_mask; - if (maskpri != 0) { - params.log_mask = maskpri; - } - ret -} - -#[no_mangle] -pub extern "C" fn openlog(ident: *const c_char, opt: c_int, facility: c_int) { - let new_ident: &str; - unsafe { - let conv_ident = CStr::from_ptr(ident); - new_ident = conv_ident.to_str().unwrap(); - } - let mut params = logparams_mut(); - if !new_ident.is_empty() { - params.log_ident = new_ident.into(); - } - params.log_opt = opt; - params.log_facility = facility; - if ((opt & LOG_NDELAY) != 0) { - let mut guard = logfile_mut(); - match *guard { - None => { - *guard = Some( - File::open(c"/scheme/log".into(), fcntl::O_WRONLY) - .expect("Could not open log file"), - ); - } - _ => (), - } - } -} - -fn _vsyslog(mut priority: i32, message: *const c_char, mut ap: VaList) { - { - let mut guard = logfile_mut(); - match *guard { - None => { - *guard = Some( - File::open(c"/scheme/log".into(), fcntl::O_WRONLY) - .expect("Could not open log file"), - ); - } - _ => (), - } - } - //Note: trait Local not available due to a dependency loop, so we have to query the time differently - let mut epoch: i64 = 0; - unsafe { - epoch = time(null_mut()); - } - let currtime: DateTime = - DateTime::from_timestamp(epoch, 0).expect("Couldn't retrieve broken-down time."); - let currtime_s = currtime.format("%b %e %T %Y"); - let mut params = logparams_mut(); - let pid = if (params.log_opt & LOG_PID) != 0 { - getpid() - } else { - 0 - }; - if ((priority & LOG_FACMASK) == 0) { - priority |= params.log_facility - }; - let mut final_logmsg = format!("<{}>{} {}{}: ", priority, currtime_s, params.log_ident, pid); - let mut guard = logfile_mut(); - if let Some(filehandle) = guard.as_mut() { - filehandle.write(final_logmsg.as_bytes()); - unsafe { - let _ = printf(&mut *filehandle, message, ap); - } - filehandle.write("\n".as_bytes()); - } -} - -#[no_mangle] -pub extern "C" fn vsyslog(priority: c_int, message: *const c_char, mut ap: VaList) { - { - let params = logparams(); - if (((params.log_mask & (1 << (priority & 7))) == 0) || ((priority & !0x3ff) != 0)) { - return (); - }; - } - _vsyslog(priority, message, ap); -} - -#[no_mangle] -pub unsafe extern "C" fn syslog(priority: c_int, message: *const c_char, mut __valist: ...) { - vsyslog(priority, message, __valist.as_va_list()); -} - -#[no_mangle] -pub extern "C" fn closelog() { - let mut guard = logfile_mut(); - match guard.as_mut() { - Some(log_file) => *guard = None, - _ => (), - } -} diff --git a/tests/syslog/syslog.c b/tests/syslog/syslog.c index c49211e9d8..e4e9d80d06 100644 --- a/tests/syslog/syslog.c +++ b/tests/syslog/syslog.c @@ -1,9 +1,29 @@ #include +#include -int main() { - int extraarg = 5; - openlog("testprog", LOG_PID, LOG_USER); - syslog(LOG_EMERG, "This is a test message with extra: %d", extraarg); +int main(void) { + // Test that syslog succeeds without explicitly calling openlog + syslog(LOG_INFO, "Testing syslog; disregard"); closelog(); - return 0; + + // The rest of the tests use LOG_PERROR so we get verifiable output on stderr + openlog("relibc_test", + LOG_CONS | LOG_PERROR | LOG_NDELAY, + LOG_LOCAL2 + ); + + // Basic + syslog(LOG_EMERG, "This is a test message with extra: %d", 5); + + // (This isn't available in Redox yet) + // Squelch all logs with a priority less than WARNING. + // setlogmask(LOG_UPTO(LOG_WARNING)); + + // First line should be emitted while the second shouldn't. + syslog(LOG_WARNING, "Foo has been bar'd"); + syslog(LOG_NOTICE, "I am a very spammy log message. Ha!"); + + // TODO: LOG_MASK after implemented + + return EXIT_SUCCESS; }