#![expect(
unsafe_code,
reason = "signal dispositions are set through libc; the handler only stores to atomics"
)]
use std::io;
use std::sync::atomic::{AtomicBool, AtomicI32, Ordering};
pub(crate) static CANCEL: AtomicBool = AtomicBool::new(false);
static RECEIVED: AtomicI32 = AtomicI32::new(0);
const ENDING_SIGNALS: [libc::c_int; 2] = [libc::SIGINT, libc::SIGTERM];
extern "C" fn record(signal: libc::c_int) {
RECEIVED.store(signal, Ordering::Relaxed);
CANCEL.store(true, Ordering::Release);
}
pub(crate) fn install() -> io::Result<()> {
for signal in ENDING_SIGNALS {
let mut current: libc::sigaction = unsafe { std::mem::zeroed() };
if unsafe { libc::sigaction(signal, std::ptr::null(), &raw mut current) } != 0 {
return Err(io::Error::last_os_error());
}
if current.sa_sigaction == libc::SIG_IGN {
continue;
}
let mut action: libc::sigaction = unsafe { std::mem::zeroed() };
let handler: extern "C" fn(libc::c_int) = record;
action.sa_sigaction = handler as libc::sighandler_t;
action.sa_flags = libc::SA_RESTART;
if unsafe { libc::sigaction(signal, &raw const action, std::ptr::null_mut()) } != 0 {
return Err(io::Error::last_os_error());
}
}
Ok(())
}
pub(crate) fn die_of_recorded_signal() -> io::Error {
let signal = RECEIVED.load(Ordering::Relaxed);
unsafe { libc::signal(signal, libc::SIG_DFL) };
unsafe { libc::raise(signal) };
io::Error::other(format!(
"signal {signal} was raised but did not end the process"
))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_installed_handler_records_the_signal_and_sets_cancel() {
install().unwrap();
assert_eq!(unsafe { libc::raise(libc::SIGINT) }, 0);
assert!(CANCEL.load(Ordering::Acquire));
assert_eq!(RECEIVED.load(Ordering::Relaxed), libc::SIGINT);
}
}