use std::sync::atomic::{AtomicBool, AtomicI32, Ordering};
static CAUGHT_SIGNAL: AtomicI32 = AtomicI32::new(0);
pub(crate) static INTERRUPTED: AtomicBool = AtomicBool::new(false);
extern "C" fn handler(sig: libc::c_int) {
let _ = CAUGHT_SIGNAL.compare_exchange(0, sig, Ordering::SeqCst, Ordering::SeqCst);
INTERRUPTED.store(true, Ordering::SeqCst);
}
pub(crate) struct InterruptGuard {
prev_sigint: libc::sigaction,
prev_sigterm: libc::sigaction,
}
impl InterruptGuard {
pub(crate) fn install() -> Self {
CAUGHT_SIGNAL.store(0, Ordering::SeqCst);
INTERRUPTED.store(false, Ordering::SeqCst);
unsafe {
let mut sa: libc::sigaction = std::mem::zeroed();
sa.sa_sigaction = handler as *const () as usize;
libc::sigemptyset(&mut sa.sa_mask);
sa.sa_flags = 0;
let mut prev_sigint: libc::sigaction = std::mem::zeroed();
let mut prev_sigterm: libc::sigaction = std::mem::zeroed();
libc::sigaction(libc::SIGINT, &sa, &mut prev_sigint);
libc::sigaction(libc::SIGTERM, &sa, &mut prev_sigterm);
Self {
prev_sigint,
prev_sigterm,
}
}
}
pub(crate) fn interrupted(&self) -> Option<libc::c_int> {
caught()
}
}
pub(crate) fn caught() -> Option<libc::c_int> {
match CAUGHT_SIGNAL.load(Ordering::SeqCst) {
0 => None,
sig => Some(sig),
}
}
impl Drop for InterruptGuard {
fn drop(&mut self) {
unsafe {
libc::sigaction(libc::SIGINT, &self.prev_sigint, std::ptr::null_mut());
libc::sigaction(libc::SIGTERM, &self.prev_sigterm, std::ptr::null_mut());
}
}
}
pub(crate) fn reraise(sig: libc::c_int) -> ! {
unsafe {
let mut dfl: libc::sigaction = std::mem::zeroed();
dfl.sa_sigaction = libc::SIG_DFL;
libc::sigemptyset(&mut dfl.sa_mask);
libc::sigaction(sig, &dfl, std::ptr::null_mut());
libc::raise(sig);
}
std::process::exit(128 + sig)
}
#[cfg(test)]
mod tests {
use super::*;
fn current(sig: libc::c_int) -> usize {
unsafe {
let mut cur: libc::sigaction = std::mem::zeroed();
libc::sigaction(sig, std::ptr::null(), &mut cur);
cur.sa_sigaction
}
}
#[test]
fn install_records_and_restores() {
let (mut pre_int, mut pre_term) = unsafe {
(
std::mem::zeroed::<libc::sigaction>(),
std::mem::zeroed::<libc::sigaction>(),
)
};
unsafe {
let mut ign: libc::sigaction = std::mem::zeroed();
ign.sa_sigaction = libc::SIG_IGN;
libc::sigemptyset(&mut ign.sa_mask);
libc::sigaction(libc::SIGINT, &ign, &mut pre_int);
libc::sigaction(libc::SIGTERM, &ign, &mut pre_term);
}
assert_eq!(current(libc::SIGINT), libc::SIG_IGN, "sentinel in place");
let guard = InterruptGuard::install();
assert_eq!(guard.interrupted(), None, "no signal before delivery");
assert!(
!INTERRUPTED.load(Ordering::SeqCst),
"install clears the interrupted flag"
);
let want = handler as *const () as usize;
assert_eq!(
current(libc::SIGINT),
want,
"install points SIGINT at handler"
);
assert_eq!(
current(libc::SIGTERM),
want,
"install points SIGTERM at handler"
);
handler(libc::SIGINT);
assert_eq!(guard.interrupted(), Some(libc::SIGINT));
assert!(
INTERRUPTED.load(Ordering::SeqCst),
"handler sets the interrupted flag so a gix checkout can abort"
);
handler(libc::SIGTERM);
assert_eq!(guard.interrupted(), Some(libc::SIGINT));
drop(guard);
assert_eq!(current(libc::SIGINT), libc::SIG_IGN, "drop restores SIGINT");
assert_eq!(
current(libc::SIGTERM),
libc::SIG_IGN,
"drop restores SIGTERM"
);
unsafe {
libc::sigaction(libc::SIGINT, &pre_int, std::ptr::null_mut());
libc::sigaction(libc::SIGTERM, &pre_term, std::ptr::null_mut());
}
}
}