diff --git a/Cargo.toml b/Cargo.toml index 0665795ed2..213e716b78 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,6 +64,7 @@ resource = [] sched = ["process"] signal = ["process"] socket = ["memoffset"] +syslog = [] term = [] time = [] ucontext = ["signal"] @@ -80,7 +81,7 @@ semver = "1.0.7" nix = { path = ".", features = ["acct", "aio", "dir", "env", "event", "fanotify", "feature", "fs", "hostname", "inotify", "ioctl", "kmod", "mman", "mount", "mqueue", "net", "personality", "poll", "pthread", "ptrace", "quota", "process", "reboot", - "resource", "sched", "signal", "socket", "term", "time", "ucontext", "uio", + "resource", "sched", "signal", "socket", "syslog", "term", "time", "ucontext", "uio", "user", "zerocopy"] } [target.'cfg(any(target_os = "android", target_os = "linux"))'.dev-dependencies] diff --git a/changelog/2537.added.md b/changelog/2537.added.md new file mode 100644 index 0000000000..110b82ab34 --- /dev/null +++ b/changelog/2537.added.md @@ -0,0 +1 @@ +Add support for `syslog`, `openlog`, `closelog` on all `unix`. diff --git a/src/lib.rs b/src/lib.rs index 685d9ab364..bd8e552f61 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,6 +34,7 @@ //! * `sched` - Manipulate process's scheduling //! * `socket` - Sockets, whether for networking or local use //! * `signal` - Send and receive signals to processes +//! * `syslog` - System logging //! * `term` - Terminal control APIs //! * `time` - Query the operating system's clocks //! * `ucontext` - User thread context @@ -79,6 +80,7 @@ feature = "sched", feature = "socket", feature = "signal", + feature = "syslog", feature = "term", feature = "time", feature = "ucontext", @@ -200,6 +202,11 @@ feature! { pub mod spawn; } +feature! { + #![feature = "syslog"] + pub mod syslog; +} + use std::ffi::{CStr, CString, OsStr}; use std::mem::MaybeUninit; use std::os::unix::ffi::OsStrExt; diff --git a/src/syslog.rs b/src/syslog.rs new file mode 100644 index 0000000000..5f1915a6ce --- /dev/null +++ b/src/syslog.rs @@ -0,0 +1,216 @@ +//! Interfaces for controlling system log. + +use crate::{NixPath, Result}; +use std::ffi::OsStr; +use std::ptr; + +/// Logging options of subsequent [`syslog`] calls can be set by calling [`openlog`]. +/// +/// The parameter `ident` is a string that will be prepended to every message. The `logopt` +/// argument specifies logging options. The `facility` parameter encodes a default facility to be +/// assigned to all messages that do not have an explicit facility encoded. +// +// On Linux, the `ident` argument needs to have static lifetime according to the +// man page: +// +// The argument ident in the call of openlog() is probably stored as-is. Thus, +// if the string it points to is changed, syslog() may start prepending the changed +// string, and if the string it points to ceases to exist, the results are +// undefined. Most portable is to use a string constant. +#[cfg(target_os = "linux")] +pub fn openlog( + ident: Option<&'static std::ffi::CStr>, + logopt: LogFlags, + facility: Facility, +) -> Result<()> { + let logopt = logopt.bits(); + let facility = facility as libc::c_int; + match ident { + None => unsafe { + libc::openlog(ptr::null(), logopt, facility); + }, + Some(ident) => ident.with_nix_path(|ident| unsafe { + libc::openlog(ident.as_ptr(), logopt, facility); + })?, + } + + Ok(()) +} + +/// Logging options of subsequent [`syslog`] calls can be set by calling [`openlog`]. +/// +/// The parameter `ident` is a string that will be prepended to every message. The `logopt` +/// argument specifies logging options. The `facility` parameter encodes a default facility to be +/// assigned to all messages that do not have an explicit facility encoded. +#[cfg(not(target_os = "linux"))] +pub fn openlog + ?Sized>( + ident: Option<&S>, + logopt: LogFlags, + facility: Facility, +) -> Result<()> { + let logopt = logopt.bits(); + let facility = facility as libc::c_int; + match ident.map(OsStr::new) { + None => unsafe { + libc::openlog(ptr::null(), logopt, facility); + }, + Some(ident) => ident.with_nix_path(|ident| unsafe { + libc::openlog(ident.as_ptr(), logopt, facility); + })?, + } + + Ok(()) +} + +/// Writes message to the system message logger. +/// +/// The message is then written to the system console, log files, logged-in users, or forwarded +/// to other machines as appropriate. +/// +/// # Examples +/// +/// ```rust +/// use nix::syslog::{openlog, syslog, Facility, LogFlags, Severity, Priority}; +/// +/// let priority = Priority::new(Severity::LOG_EMERG, Facility::LOG_USER); +/// syslog(priority, "Hello, nix!").unwrap(); +/// +/// // use `format!` to format the message +/// let name = "syslog"; +/// syslog(priority, &format!("Hello, {name}!")).unwrap(); +/// ``` +pub fn syslog(priority: P, message: &S) -> Result<()> +where + P: Into, + S: AsRef + ?Sized, +{ + let priority = priority.into(); + let formatter = OsStr::new("%s"); + let message = OsStr::new(message); + formatter.with_nix_path(|formatter| { + message.with_nix_path(|message| unsafe { + libc::syslog(priority.0, formatter.as_ptr(), message.as_ptr()) + }) + })??; + Ok(()) +} + +/// Closes the log file. +pub fn closelog() { + unsafe { libc::closelog() } +} + +/// The priority for a log message. +#[derive(Debug, Clone, Copy)] +pub struct Priority(libc::c_int); + +impl Priority { + /// Create a new priority from a facility and severity level. + pub fn new(severity: Severity, facility: Facility) -> Self { + let priority = (facility as libc::c_int) | (severity as libc::c_int); + Priority(priority) + } +} + +impl From for Priority { + fn from(severity: Severity) -> Self { + let priority = severity as libc::c_int; + Priority(priority) + } +} + +libc_bitflags! { + /// Options for system logging. + pub struct LogFlags: libc::c_int { + /// Log the process id with each message: useful for identifying instantiations of + /// daemons. + LOG_PID; + /// If syslog() cannot pass the message to syslogd(8) it will attempt to write the + /// message to the console ("/dev/console"). + LOG_CONS; + /// The converse of [`LOG_NDELAY`][LogFlags::LOG_NDELAY]; opening of the connection is + /// delayed until `syslog` is called. + /// + /// This is the default, and need not be specified. + LOG_ODELAY; + /// Open the connection to syslogd(8) immediately. Normally the open is delayed until + /// the first message is logged. Useful for programs that need to manage the order in + /// which file descriptors are allocated. + LOG_NDELAY; + /// Write the message to standard error output as well to the system log. + #[cfg(not(any(target_os = "redox", target_os = "illumos")))] + LOG_PERROR; + } +} + +libc_enum! { + /// Severity levels for log messages. + #[repr(i32)] + #[non_exhaustive] + pub enum Severity { + /// A panic condition. + /// + /// This is normally broadcast to all users. + LOG_EMERG, + /// A condition that should be corrected immediately, such as a corrupted system database. + LOG_ALERT, + /// Critical conditions, e.g., hard device errors. + LOG_CRIT, + /// Errors. + LOG_ERR, + /// Warning messages. + LOG_WARNING, + /// Conditions that are not error conditions, but should possibly be handled specially. + LOG_NOTICE, + /// Informational messages. + LOG_INFO, + /// Messages that contain information normally of use only when debugging a program. + LOG_DEBUG, + } +} + +libc_enum! { + /// Facilities for log messages. + #[repr(i32)] + #[non_exhaustive] + pub enum Facility { + /// Messages generated by the kernel. + /// + /// These cannot be generated by any user processes. + LOG_KERN, + /// Messages generated by random user processes. + /// + /// This is the default facility identifier if none is specified. + LOG_USER, + /// The mail system. + LOG_MAIL, + /// System daemons, such as routed(8), that are not provided for explicitly by other facilities. + LOG_DAEMON, + /// The authorization system: login(1), su(1), getty(8), etc. + LOG_AUTH, + /// Messages generated internally by syslogd(8). + LOG_SYSLOG, + /// The line printer spooling system: cups-lpd(8), cupsd(8), etc. + LOG_LPR, + /// The network news system. + LOG_NEWS, + /// The uucp system. + LOG_UUCP, + /// Reserved for local use. + LOG_LOCAL0, + /// Reserved for local use. + LOG_LOCAL1, + /// Reserved for local use. + LOG_LOCAL2, + /// Reserved for local use. + LOG_LOCAL3, + /// Reserved for local use. + LOG_LOCAL4, + /// Reserved for local use. + LOG_LOCAL5, + /// Reserved for local use. + LOG_LOCAL6, + /// Reserved for local use. + LOG_LOCAL7, + } +} diff --git a/test/test.rs b/test/test.rs index 56084622f7..3ca7c0a755 100644 --- a/test/test.rs +++ b/test/test.rs @@ -42,6 +42,8 @@ mod test_sendfile; ))] mod test_spawn; +mod test_syslog; + mod test_time; mod test_unistd; diff --git a/test/test_syslog.rs b/test/test_syslog.rs new file mode 100644 index 0000000000..fc8f908070 --- /dev/null +++ b/test/test_syslog.rs @@ -0,0 +1,38 @@ +use nix::syslog::{openlog, syslog, Facility, LogFlags, Severity}; + +#[test] +fn test_syslog_hello_world() { + let flags = LogFlags::LOG_PID; + + #[cfg(not(target_os = "linux"))] + openlog(None::<&str>, flags, Facility::LOG_USER).unwrap(); + #[cfg(target_os = "linux")] + openlog(None, flags, Facility::LOG_USER).unwrap(); + + syslog(Severity::LOG_EMERG, "Hello, nix!").unwrap(); + let name = "syslog"; + syslog(Severity::LOG_NOTICE, &format!("Hello, {name}!")).unwrap(); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_openlog_with_ident() { + use std::ffi::CStr; + + const IDENT: &CStr = unsafe { + CStr::from_bytes_with_nul_unchecked(b"test_openlog_with_ident\0") + }; + + let flags = LogFlags::LOG_PID; + openlog(Some(IDENT), flags, Facility::LOG_USER).unwrap(); + syslog(Severity::LOG_EMERG, "Hello, ident!").unwrap(); +} + +#[test] +#[cfg(not(target_os = "linux"))] +fn test_openlog_with_ident() { + let flags = LogFlags::LOG_PID; + openlog(Some("test_openlog_with_ident"), flags, Facility::LOG_USER) + .unwrap(); + syslog(Severity::LOG_EMERG, "Hello, ident!").unwrap(); +}