mirror of
https://github.com/uutils/coreutils.git
synced 2026-06-10 15:48:22 -07:00
refactor: migrate signals.rs and lib.rs from nix to csignal/rustix
Replace all nix signal and poll APIs in uucore: - signals.rs: nix::sys::signal -> csignal wrappers - signals.rs: nix::poll -> rustix::event::poll - signals.rs: nix::sys::stat -> rustix::fs::fstat + libc constants - lib.rs: nix sigaction -> csignal::set_signal_action - dd/progress.rs: update install_sigusr1_handler to use libc::SIGUSR1 Return types change from nix::Result/Errno to std::io::Result.
This commit is contained in:
@@ -459,8 +459,8 @@ extern "C" fn sigusr1_handler(_: std::os::raw::c_int) {
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub(crate) fn install_sigusr1_handler() -> Result<(), nix::errno::Errno> {
|
||||
uucore::signals::install_signal_handler(nix::sys::signal::Signal::SIGUSR1, sigusr1_handler)
|
||||
pub(crate) fn install_sigusr1_handler() -> std::io::Result<()> {
|
||||
uucore::signals::install_signal_handler(libc::SIGUSR1, sigusr1_handler)
|
||||
}
|
||||
|
||||
/// Return a closure that can be used in its own thread to print progress info.
|
||||
|
||||
@@ -10,15 +10,172 @@
|
||||
//! It provides a way to convert signal names to their corresponding values and vice versa.
|
||||
//! It also provides a way to ignore the SIGINT signal and enable pipe errors.
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 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)]
|
||||
use nix::errno::Errno;
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
use nix::libc;
|
||||
#[cfg(unix)]
|
||||
use nix::sys::signal::{
|
||||
SaFlags, SigAction, SigHandler, SigHandler::SigDfl, SigHandler::SigIgn, SigSet, Signal,
|
||||
Signal::SIGINT, Signal::SIGPIPE, sigaction, signal,
|
||||
};
|
||||
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::<libc::sighandler_t, extern "C" fn(libc::c_int)>(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::<libc::sigset_t>::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::<libc::sigset_t>::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<SigHandler> {
|
||||
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;
|
||||
@@ -485,42 +642,41 @@ pub fn signal_list_value_by_name_or_number(spec: &str) -> Option<usize> {
|
||||
|
||||
/// Restores SIGPIPE to default behavior (process terminates on broken pipe).
|
||||
#[cfg(unix)]
|
||||
pub fn enable_pipe_errors() -> Result<(), Errno> {
|
||||
// We pass the error as is, the return value would just be Ok(SigDfl), so we can safely ignore it.
|
||||
// SAFETY: this function is safe as long as we do not use a custom SigHandler -- we use the default one.
|
||||
unsafe { signal(SIGPIPE, SigDfl) }.map(|_| ())
|
||||
pub fn enable_pipe_errors() -> std::io::Result<()> {
|
||||
// SAFETY: SigDfl is always safe.
|
||||
unsafe { csignal::set_signal_handler(libc::SIGPIPE, csignal::SigHandler::SigDfl) }.map(|_| ())
|
||||
}
|
||||
|
||||
/// Ignores SIGPIPE signal (broken pipe errors are returned instead of terminating).
|
||||
/// Use this to override the default SIGPIPE handling when you need to handle
|
||||
/// broken pipe errors gracefully (e.g., tee with --output-error).
|
||||
#[cfg(unix)]
|
||||
pub fn disable_pipe_errors() -> Result<(), Errno> {
|
||||
// SAFETY: this function is safe as long as we do not use a custom SigHandler -- we use the default one.
|
||||
unsafe { signal(SIGPIPE, SigIgn) }.map(|_| ())
|
||||
pub fn disable_pipe_errors() -> std::io::Result<()> {
|
||||
// SAFETY: SigIgn is always safe.
|
||||
unsafe { csignal::set_signal_handler(libc::SIGPIPE, csignal::SigHandler::SigIgn) }.map(|_| ())
|
||||
}
|
||||
|
||||
/// Ignores the SIGINT signal.
|
||||
#[cfg(unix)]
|
||||
pub fn ignore_interrupts() -> Result<(), Errno> {
|
||||
// We pass the error as is, the return value would just be Ok(SigIgn), so we can safely ignore it.
|
||||
// SAFETY: this function is safe as long as we do not use a custom SigHandler -- we use the default one.
|
||||
unsafe { signal(SIGINT, SigIgn) }.map(|_| ())
|
||||
pub fn ignore_interrupts() -> std::io::Result<()> {
|
||||
// SAFETY: SigIgn is always safe.
|
||||
unsafe { csignal::set_signal_handler(libc::SIGINT, csignal::SigHandler::SigIgn) }.map(|_| ())
|
||||
}
|
||||
|
||||
/// Installs a signal handler. The handler must be async-signal-safe.
|
||||
#[cfg(unix)]
|
||||
pub fn install_signal_handler(
|
||||
sig: Signal,
|
||||
handler: extern "C" fn(std::os::raw::c_int),
|
||||
) -> Result<(), Errno> {
|
||||
let action = SigAction::new(
|
||||
SigHandler::Handler(handler),
|
||||
SaFlags::SA_RESTART,
|
||||
SigSet::empty(),
|
||||
);
|
||||
unsafe { sigaction(sig, &action) }?;
|
||||
Ok(())
|
||||
sig: libc::c_int,
|
||||
handler: extern "C" fn(libc::c_int),
|
||||
) -> std::io::Result<()> {
|
||||
unsafe {
|
||||
csignal::set_signal_action(
|
||||
sig,
|
||||
csignal::SigHandler::Handler(handler),
|
||||
csignal::sa_flags::SA_RESTART,
|
||||
&csignal::SigSet::empty(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Detect closed stdin/stdout before Rust reopens them as /dev/null (see issue #2873)
|
||||
@@ -651,10 +807,9 @@ pub const fn sigpipe_was_ignored() -> bool {
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn ensure_stdout_not_broken() -> std::io::Result<bool> {
|
||||
use nix::{
|
||||
poll::{PollFd, PollFlags, PollTimeout, poll},
|
||||
sys::stat::{SFlag, fstat},
|
||||
};
|
||||
use rustix::event::{PollFd, PollFlags, poll};
|
||||
use rustix::fs::fstat;
|
||||
use rustix::time::Timespec;
|
||||
use std::io::stdout;
|
||||
use std::os::fd::AsFd;
|
||||
|
||||
@@ -662,32 +817,31 @@ pub fn ensure_stdout_not_broken() -> std::io::Result<bool> {
|
||||
|
||||
// First, check that stdout is a fifo and return true if it's not the case
|
||||
let stat = fstat(out.as_fd())?;
|
||||
if !SFlag::from_bits_truncate(stat.st_mode).contains(SFlag::S_IFIFO) {
|
||||
if (stat.st_mode & libc::S_IFMT) != libc::S_IFIFO {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
// POLLRDBAND is the flag used by GNU tee.
|
||||
let mut pfds = [PollFd::new(out.as_fd(), PollFlags::POLLRDBAND)];
|
||||
let mut pfds = [PollFd::new(&out, PollFlags::RDBAND)];
|
||||
|
||||
// Then, ensure that the pipe is not broken.
|
||||
// Use ZERO timeout to return immediately - we just want to check the current state.
|
||||
let res = poll(&mut pfds, PollTimeout::ZERO)?;
|
||||
// Use 0 timeout to return immediately - we just want to check the current state.
|
||||
let zero_timeout = Timespec {
|
||||
tv_sec: 0,
|
||||
tv_nsec: 0,
|
||||
};
|
||||
let res = poll(&mut pfds, Some(&zero_timeout))?;
|
||||
|
||||
if res > 0 {
|
||||
// poll returned with events ready - check if POLLERR is set (pipe broken)
|
||||
let error = pfds.iter().any(|pfd| {
|
||||
if let Some(revents) = pfd.revents() {
|
||||
revents.contains(PollFlags::POLLERR)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
let error = pfds
|
||||
.iter()
|
||||
.any(|pfd| pfd.revents().contains(PollFlags::ERR));
|
||||
return Ok(!error);
|
||||
}
|
||||
|
||||
// res == 0 means no events ready (timeout reached immediately with ZERO timeout).
|
||||
// res == 0 means no events ready (timeout reached immediately with 0 timeout).
|
||||
// This means the pipe is healthy (not broken).
|
||||
// res < 0 would be an error, but nix returns Err in that case.
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
|
||||
+12
-12
@@ -131,11 +131,7 @@ pub use crate::features::smack;
|
||||
//## core functions
|
||||
|
||||
#[cfg(unix)]
|
||||
use nix::errno::Errno;
|
||||
#[cfg(unix)]
|
||||
use nix::sys::signal::{
|
||||
SaFlags, SigAction, SigHandler::SigDfl, SigSet, Signal::SIGBUS, Signal::SIGSEGV, sigaction,
|
||||
};
|
||||
use crate::signals::csignal;
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::io::{BufRead, BufReader};
|
||||
@@ -151,17 +147,21 @@ use std::sync::{LazyLock, atomic::Ordering};
|
||||
/// Disables the custom signal handlers installed by Rust for stack-overflow handling. With those custom signal handlers processes ignore the first SIGBUS and SIGSEGV signal they receive.
|
||||
/// See <https://github.com/rust-lang/rust/blob/8ac1525e091d3db28e67adcbbd6db1e1deaa37fb/src/libstd/sys/unix/stack_overflow.rs#L71-L92> for details.
|
||||
#[cfg(unix)]
|
||||
pub fn disable_rust_signal_handlers() -> Result<(), Errno> {
|
||||
pub fn disable_rust_signal_handlers() -> std::io::Result<()> {
|
||||
unsafe {
|
||||
sigaction(
|
||||
SIGSEGV,
|
||||
&SigAction::new(SigDfl, SaFlags::empty(), SigSet::all()),
|
||||
csignal::set_signal_action(
|
||||
libc::SIGSEGV,
|
||||
csignal::SigHandler::SigDfl,
|
||||
0,
|
||||
&csignal::SigSet::all(),
|
||||
)
|
||||
}?;
|
||||
unsafe {
|
||||
sigaction(
|
||||
SIGBUS,
|
||||
&SigAction::new(SigDfl, SaFlags::empty(), SigSet::all()),
|
||||
csignal::set_signal_action(
|
||||
libc::SIGBUS,
|
||||
csignal::SigHandler::SigDfl,
|
||||
0,
|
||||
&csignal::SigSet::all(),
|
||||
)
|
||||
}?;
|
||||
Ok(())
|
||||
|
||||
Reference in New Issue
Block a user