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).
This commit is contained in:
Josh Megnauth
2025-07-20 02:52:36 -04:00
parent 9e1ff3185e
commit 64d9847e37
10 changed files with 412 additions and 229 deletions
+1
View File
@@ -0,0 +1 @@
#include <sys/syslog.h>
+1 -2
View File
@@ -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;
@@ -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
+74
View File
@@ -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<File>);
impl LogSink for LogFile {
type Sink = BufWriter<File>;
fn open() -> Result<Self>
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::<sockaddr_un>(),
) < 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
}
}
+174
View File
@@ -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<LogParams<LogFile>> = Mutex::new(LogParams::new(None));
pub struct LogParams<L: LogSink> {
/// 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<L>,
}
impl<L: LogSink> LogParams<L> {
pub const fn new(writer: Option<L>) -> 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<Utc> =
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<CStr<'_>>) {
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<String>) {
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<Self>
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;
}
}
+111
View File
@@ -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();
}
+25
View File
@@ -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<File>);
impl LogSink for LogFile {
type Sink = BufWriter<File>;
#[inline(always)]
fn open() -> Result<Self> {
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
}
}
-9
View File
@@ -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;
-211
View File
@@ -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<Option<File>> = RwLock::new(None);
static PARAMSLOCK: Once<RwLock<LogParams>> = Once::new();
fn paramslock() -> &'static RwLock<LogParams> {
PARAMSLOCK.call_once(|| RwLock::new(LogParams::new()))
}
fn logfile<'a>() -> ReadGuard<'a, Option<File>> {
LOGFILELOCK.read()
}
fn logfile_mut<'a>() -> WriteGuard<'a, Option<File>> {
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<Utc> =
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,
_ => (),
}
}
+25 -5
View File
@@ -1,9 +1,29 @@
#include <syslog.h>
#include <stdlib.h>
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;
}