use nix::sys::signal::{self, SaFlags, SigAction, SigHandler};
pub struct SaGuard<const N: usize> {
old_sigactions: [(signal::Signal, Option<signal::SigAction>); N],
old_sigmask: signal::SigSet,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SuppressionMode {
IgnoreAndBlock,
BlockOnly,
}
impl<const N: usize> SaGuard<N> {
pub fn new_with_modes(
signals: &[(signal::Signal, SuppressionMode); N],
) -> anyhow::Result<Self> {
let mut suppressed_signals = signal::SigSet::empty();
for (signal, _) in signals {
suppressed_signals.add(*signal);
}
let mut old_sigmask = signal::SigSet::empty();
signal::sigprocmask(
signal::SigmaskHow::SIG_BLOCK,
Some(&suppressed_signals),
Some(&mut old_sigmask),
)?;
let mut old_sigactions = [(signal::Signal::SIGINT, None); N];
for (i, &(signal, mode)) in signals.iter().enumerate() {
let old_sigaction = match mode {
SuppressionMode::IgnoreAndBlock => Some(unsafe {
signal::sigaction(
signal,
&SigAction::new(
SigHandler::SigIgn,
SaFlags::empty(),
signal::SigSet::empty(),
),
)?
}),
SuppressionMode::BlockOnly => None,
};
old_sigactions[i] = (signal, old_sigaction);
}
Ok(Self {
old_sigactions,
old_sigmask,
})
}
}
impl<const N: usize> Drop for SaGuard<N> {
fn drop(&mut self) {
for &(signal, old_sigaction) in &self.old_sigactions {
if let Some(old_sigaction) = old_sigaction {
unsafe {
let _ = signal::sigaction(signal, &old_sigaction);
}
}
}
let _ = signal::sigprocmask(
signal::SigmaskHow::SIG_SETMASK,
Some(&self.old_sigmask),
None,
);
}
}
#[cfg(test)]
mod single_threaded_tests {
use super::*;
use core::sync::atomic::{AtomicBool, Ordering};
use nix::sys::signal::{self, Signal};
use nix::unistd::Pid;
#[test]
#[cfg_attr(miri, ignore)]
fn signal_is_ignored_while_guard_is_active() {
let _guard =
SaGuard::<1>::new_with_modes(&[(Signal::SIGURG, SuppressionMode::IgnoreAndBlock)])
.unwrap();
signal::kill(Pid::this(), Signal::SIGURG).unwrap();
}
#[test]
#[cfg_attr(miri, ignore)]
fn original_handler_restored_after_drop() {
static HANDLER_CALLED: AtomicBool = AtomicBool::new(false);
extern "C" fn custom_handler(_: libc::c_int) {
HANDLER_CALLED.store(true, Ordering::SeqCst);
}
let custom_action = SigAction::new(
SigHandler::Handler(custom_handler),
SaFlags::empty(),
signal::SigSet::empty(),
);
let prev = unsafe { signal::sigaction(Signal::SIGWINCH, &custom_action).unwrap() };
{
let _guard = SaGuard::<1>::new_with_modes(&[(
Signal::SIGWINCH,
SuppressionMode::IgnoreAndBlock,
)])
.unwrap();
signal::kill(Pid::this(), Signal::SIGWINCH).unwrap();
assert!(
!HANDLER_CALLED.load(Ordering::SeqCst),
"custom handler should not fire while guard is active"
);
}
HANDLER_CALLED.store(false, Ordering::SeqCst);
unsafe {
libc::raise(Signal::SIGWINCH as libc::c_int);
}
assert!(
HANDLER_CALLED.load(Ordering::SeqCst),
"custom handler should fire after guard is dropped"
);
unsafe {
signal::sigaction(Signal::SIGWINCH, &prev).unwrap();
}
}
#[test]
#[cfg_attr(miri, ignore)]
fn multiple_signals_ignored() {
let _guard = SaGuard::<2>::new_with_modes(&[
(Signal::SIGURG, SuppressionMode::IgnoreAndBlock),
(Signal::SIGWINCH, SuppressionMode::IgnoreAndBlock),
])
.unwrap();
signal::kill(Pid::this(), Signal::SIGURG).unwrap();
signal::kill(Pid::this(), Signal::SIGWINCH).unwrap();
}
#[test]
#[cfg_attr(miri, ignore)]
fn block_only_defers_signal_delivery() -> anyhow::Result<()> {
static SIGURG_COUNT: AtomicBool = AtomicBool::new(false);
extern "C" fn sigurg_handler(_: libc::c_int) {
SIGURG_COUNT.store(true, Ordering::SeqCst);
}
let sig = Signal::SIGURG;
let old_action = unsafe {
signal::sigaction(
sig,
&SigAction::new(
SigHandler::Handler(sigurg_handler),
SaFlags::empty(),
signal::SigSet::empty(),
),
)?
};
SIGURG_COUNT.store(false, Ordering::SeqCst);
{
let _guard = SaGuard::<1>::new_with_modes(&[(sig, SuppressionMode::BlockOnly)])?;
signal::raise(sig)?;
assert!(
!SIGURG_COUNT.load(Ordering::SeqCst),
"Handler should not be called while signal is blocked by BlockOnly guard"
);
} assert!(
SIGURG_COUNT.load(Ordering::SeqCst),
"Handler should be called after BlockOnly guard drops and pending signal is delivered"
);
unsafe {
signal::sigaction(sig, &old_action)?;
}
Ok(())
}
}