From 6a74110c59b0d31b956876a61819e82686d549c1 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 27 Mar 2026 14:20:10 +0100 Subject: [PATCH] refactor: add libc-based signal wrappers in uucore::signals::csignal Add a `csignal` module with thin libc-based wrappers for signal operations that rustix does not cover: signal(), sigaction(), sigprocmask(), SigSet, SigHandler, and SigmaskHow. These wrappers will replace nix's signal module as part of the nix-to-rustix migration. Also add libc as a non-optional unix dependency for uucore. --- src/uucore/Cargo.toml | 1 + src/uucore/src/lib/features/signals.rs | 167 +++++++++++++++++++++++++ 2 files changed, 168 insertions(+) diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index 9d0e0d0f3..3aaa135b6 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -93,6 +93,7 @@ unic-langid = { workspace = true } thiserror = { workspace = true } [target.'cfg(unix)'.dependencies] +libc = { workspace = true } nix = { workspace = true, features = [ "dir", "fs", diff --git a/src/uucore/src/lib/features/signals.rs b/src/uucore/src/lib/features/signals.rs index 44d53a016..fccba7050 100644 --- a/src/uucore/src/lib/features/signals.rs +++ b/src/uucore/src/lib/features/signals.rs @@ -18,6 +18,173 @@ use nix::sys::signal::{ Signal::SIGINT, Signal::SIGPIPE, sigaction, signal, }; +// --------------------------------------------------------------------------- +// Thin libc-based signal wrappers +// +// These replace nix's signal module for operations that rustix does not cover +// (sigaction, signal(), SigSet, SigHandler, sigprocmask). +// --------------------------------------------------------------------------- +#[cfg(unix)] +pub mod csignal { + use std::io; + use std::mem::MaybeUninit; + + /// Signal handler disposition. + #[derive(Debug, Clone, Copy)] + pub enum SigHandler { + /// Default signal action. + SigDfl, + /// Ignore the signal. + SigIgn, + /// Call the given handler function. + Handler(extern "C" fn(libc::c_int)), + } + + impl SigHandler { + fn to_sigaction_handler(self) -> libc::sighandler_t { + match self { + Self::SigDfl => libc::SIG_DFL, + Self::SigIgn => libc::SIG_IGN, + Self::Handler(f) => f as libc::sighandler_t, + } + } + + fn from_sigaction_handler(handler: libc::sighandler_t) -> Self { + if handler == libc::SIG_DFL { + Self::SigDfl + } else if handler == libc::SIG_IGN { + Self::SigIgn + } else { + // SAFETY: the handler is a valid function pointer set by a previous signal() call + Self::Handler(unsafe { + std::mem::transmute::(handler) + }) + } + } + } + + /// Wrapper around `libc::sigset_t`. + #[derive(Clone)] + pub struct SigSet(libc::sigset_t); + + impl SigSet { + /// Creates an empty signal set. + pub fn empty() -> Self { + let mut set = MaybeUninit::::uninit(); + // SAFETY: sigemptyset initializes the sigset_t + unsafe { libc::sigemptyset(set.as_mut_ptr()) }; + Self(unsafe { set.assume_init() }) + } + + /// Creates a signal set with all signals. + pub fn all() -> Self { + let mut set = MaybeUninit::::uninit(); + // SAFETY: sigfillset initializes the sigset_t + unsafe { libc::sigfillset(set.as_mut_ptr()) }; + Self(unsafe { set.assume_init() }) + } + + /// Adds a signal to the set. + pub fn add(&mut self, signum: libc::c_int) { + // SAFETY: sigaddset operates on a valid sigset_t + unsafe { libc::sigaddset(&raw mut self.0, signum) }; + } + + /// Returns a pointer to the inner sigset_t. + pub fn as_ptr(&self) -> *const libc::sigset_t { + &raw const self.0 + } + } + + /// Flags for `sigaction`. + pub mod sa_flags { + pub const SA_RESTART: libc::c_int = libc::SA_RESTART as libc::c_int; + } + + /// How to modify the signal mask in `sigprocmask`. + #[derive(Debug, Clone, Copy)] + pub enum SigmaskHow { + /// Block the signals in the set. + Block, + /// Unblock the signals in the set. + Unblock, + /// Set the signal mask to the given set. + SetMask, + } + + impl SigmaskHow { + fn as_libc(self) -> libc::c_int { + match self { + Self::Block => libc::SIG_BLOCK, + Self::Unblock => libc::SIG_UNBLOCK, + Self::SetMask => libc::SIG_SETMASK, + } + } + } + + fn last_os_error() -> io::Error { + io::Error::last_os_error() + } + + /// Set the disposition of a signal using `libc::signal()`. + /// + /// Returns the previous signal handler. + /// + /// # Safety + /// If `handler` is `SigHandler::Handler(f)`, the function `f` must be + /// async-signal-safe. + pub unsafe fn set_signal_handler( + signum: libc::c_int, + handler: SigHandler, + ) -> io::Result { + let prev = unsafe { libc::signal(signum, handler.to_sigaction_handler()) }; + if prev == libc::SIG_ERR { + Err(last_os_error()) + } else { + Ok(SigHandler::from_sigaction_handler(prev)) + } + } + + /// Install a signal action using `libc::sigaction()`. + /// + /// # Safety + /// If the handler is a function pointer, it must be async-signal-safe. + pub unsafe fn set_signal_action( + signum: libc::c_int, + handler: SigHandler, + flags: libc::c_int, + mask: &SigSet, + ) -> io::Result<()> { + let mut sa: libc::sigaction = unsafe { std::mem::zeroed() }; + sa.sa_sigaction = handler.to_sigaction_handler(); + sa.sa_flags = flags as _; + sa.sa_mask = mask.0; + + let ret = unsafe { libc::sigaction(signum, &raw const sa, std::ptr::null_mut()) }; + if ret == -1 { + Err(last_os_error()) + } else { + Ok(()) + } + } + + /// Block/unblock/set signal mask using `libc::sigprocmask()`. + pub fn sigprocmask( + how: SigmaskHow, + set: Option<&SigSet>, + oldset: Option<&mut SigSet>, + ) -> io::Result<()> { + let set_ptr = set.map_or(std::ptr::null(), SigSet::as_ptr); + let oldset_ptr = oldset.map_or(std::ptr::null_mut(), |s| &raw mut s.0); + let ret = unsafe { libc::sigprocmask(how.as_libc(), set_ptr, oldset_ptr) }; + if ret == -1 { + Err(last_os_error()) + } else { + Ok(()) + } + } +} + /// The default signal value. pub static DEFAULT_SIGNAL: usize = 15;