use std::io;
use std::thread;
use signal_hook::consts::signal::{SIGHUP, SIGINT, SIGQUIT, SIGTERM};
use signal_hook::iterator::Signals;
use super::handle_signal;
const DRAINED_SIGNALS: [i32; 4] = [SIGINT, SIGTERM, SIGHUP, SIGQUIT];
const fn exit_code(signal: i32) -> i32 {
match signal {
SIGHUP => 129,
SIGINT => 130,
SIGQUIT => 131,
SIGTERM => 143,
_ => 128,
}
}
#[expect(
unsafe_code,
reason = "reading a signal disposition requires sigaction FFI"
)]
fn is_ignored(signal: i32) -> bool {
let mut current = std::mem::MaybeUninit::<libc::sigaction>::zeroed();
let result = unsafe { libc::sigaction(signal, std::ptr::null(), current.as_mut_ptr()) };
if result != 0 {
return false;
}
let current = unsafe { current.assume_init() };
current.sa_sigaction == libc::SIG_IGN
}
fn is_drained(signal: i32) -> bool {
signal != SIGHUP || !is_ignored(SIGHUP)
}
pub fn install() -> io::Result<()> {
let drained = DRAINED_SIGNALS
.into_iter()
.filter(|signal| is_drained(*signal));
let mut signals = Signals::new(drained)?;
thread::Builder::new()
.name("fallow-signal-listener".into())
.spawn(move || {
for signal in signals.forever() {
handle_signal(exit_code(signal));
}
})?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn each_drained_signal_exits_with_128_plus_its_number() {
for signal in DRAINED_SIGNALS {
assert_eq!(exit_code(signal), 128 + signal, "signal {signal}");
}
}
}