commit 5846f0ca4ee850efffb412accdcfc957ce0f7103 Author: Michal 'vorner' Vaner Date: Wed Jun 13 21:12:21 2018 +0200 Initial implementation diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3d69c04 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/target +**/*.rs.bk +Cargo.lock +tags diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..8a45000 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "signal-hook" +version = "0.1.0" +authors = ["Michal 'vorner' Vaner "] + +[dependencies] +arc-swap = "~0.1" +libc = "~0.2" diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..e69de29 diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..f68e1a1 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,197 @@ +extern crate arc_swap; +extern crate libc; + +use std::collections::hash_map::Entry; +use std::collections::{BTreeMap, HashMap}; +use std::io::Error; +use std::mem; +use std::panic::{self, AssertUnwindSafe}; +use std::process; +use std::ptr; +use std::sync::{Arc, Mutex, MutexGuard, Once, ONCE_INIT}; +use std::thread; + +use arc_swap::ArcSwap; +use libc::{c_int, c_void, sigaction, siginfo_t, sigset_t, SIG_BLOCK, SIG_SETMASK}; + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] +struct ActionId(u64); + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct SigId { + signal: c_int, + action: ActionId, +} + +#[derive(Clone)] +struct Slot { + prev: sigaction, + actions: BTreeMap>, +} + +impl Slot { + fn new(signal: libc::c_int) -> Result { + // C data structure, expected to be zeroed out. + let mut new: libc::sigaction = unsafe { mem::zeroed() }; + new.sa_sigaction = handler as usize; + #[cfg(target_os = "android")] + fn flags() -> libc::c_ulong { + (libc::SA_RESTART as libc::c_ulong) + | libc::SA_SIGINFO + | (libc::SA_NOCLDSTOP as libc::c_ulong) + } + #[cfg(not(target_os = "android"))] + fn flags() -> libc::c_int { + libc::SA_RESTART | libc::SA_SIGINFO | libc::SA_NOCLDSTOP + } + new.sa_flags = flags(); + // C data structure, expected to be zeroed out. + let mut old: libc::sigaction = unsafe { mem::zeroed() }; + // FFI ‒ pointers are valid, it doesn't take ownership. + if unsafe { libc::sigaction(signal, &new, &mut old) } != 0 { + return Err(Error::last_os_error()); + } + Ok(Slot { + prev: old, + actions: BTreeMap::new(), + }) + } +} + +type AllSignals = HashMap; + +struct GlobalData { + all_signals: ArcSwap, + rcu_lock: Mutex, +} + +static mut GLOBAL_DATA: Option = None; +static GLOBAL_INIT: Once = ONCE_INIT; + +impl GlobalData { + fn get() -> &'static Self { + unsafe { GLOBAL_DATA.as_ref().unwrap() } + } + fn ensure() -> &'static Self { + GLOBAL_INIT.call_once(|| unsafe { + GLOBAL_DATA = Some(GlobalData { + all_signals: ArcSwap::from(Arc::new(HashMap::new())), + rcu_lock: Mutex::new(0), + }); + }); + Self::get() + } + fn load(&self) -> (AllSignals, MutexGuard) { + let lock = self.rcu_lock.lock().unwrap(); + let signals = AllSignals::clone(&self.all_signals.load()); + (signals, lock) + } + fn store(&self, signals: AllSignals, _lock: MutexGuard) { + let mut previous = self.all_signals.swap(Arc::new(signals)); + while let Err(failed_unwrap) = Arc::try_unwrap(previous) { + previous = failed_unwrap; + thread::yield_now(); + } + } +} + +extern "C" fn handler(sig: c_int, info: *mut siginfo_t, data: *mut c_void) { + let result = panic::catch_unwind(AssertUnwindSafe(|| { + let signals = GlobalData::get().all_signals.load(); + + if let Some(ref slot) = signals.get(&sig) { + let fptr = slot.prev.sa_sigaction; + if fptr != 0 && fptr != libc::SIG_DFL && fptr != libc::SIG_IGN { + // FFI ‒ calling the original signal handler. + unsafe { + if slot.prev.sa_flags & libc::SA_SIGINFO == 0 { + let action = mem::transmute::(fptr); + action(sig); + } else { + type SigAction = extern "C" fn(c_int, *mut siginfo_t, *mut c_void); + let action = mem::transmute::(fptr); + action(sig, info, data); + } + } + } + + for action in slot.actions.values() { + action(); + } + } + })); + if result.is_err() { + eprintln!("Panic inside a signal handler for {}", sig); + process::abort(); + } +} + +fn block_signal(signal: c_int) -> Result { + unsafe { + let mut newsigs: sigset_t = mem::uninitialized(); + libc::sigemptyset(&mut newsigs); + libc::sigaddset(&mut newsigs, signal); + let mut oldsigs: sigset_t = mem::uninitialized(); + libc::sigemptyset(&mut oldsigs); + if libc::sigprocmask(SIG_BLOCK, &newsigs, &mut oldsigs) == 0 { + Ok(oldsigs) + } else { + Err(Error::last_os_error()) + } + } +} + +fn restore_signals(signals: libc::sigset_t) -> Result<(), Error> { + if unsafe { libc::sigprocmask(SIG_SETMASK, &signals, ptr::null_mut()) } == 0 { + Ok(()) + } else { + Err(Error::last_os_error()) + } +} + +fn without_signal Result<(), Error>>(signal: c_int, f: F) -> Result<(), Error> { + let old_signals = block_signal(signal)?; + let result = f(); + let restored = restore_signals(old_signals); + // In case of errors in both, prefer the one in result. + result.and(restored) +} + +pub unsafe fn register_action(signal: c_int, action: Box) -> Result { + let globals = GlobalData::ensure(); + let (mut signals, mut lock) = globals.load(); + let id = ActionId(*lock); + *lock += 1; + let action = Arc::from(action); + without_signal(signal, || { + match signals.entry(signal) { + Entry::Occupied(mut occupied) => { + assert!(occupied.get_mut().actions.insert(id, action).is_none()); + } + Entry::Vacant(place) => { + let mut slot = Slot::new(signal)?; + slot.actions.insert(id, action); + place.insert(slot); + } + } + + globals.store(signals, lock); + + Ok(()) + })?; + + Ok(SigId { signal, action: id }) +} + +pub fn unregister(id: SigId) -> bool { + let globals = GlobalData::ensure(); + let (mut signals, lock) = globals.load(); + let mut replace = false; + if let Some(slot) = signals.get_mut(&id.signal) { + replace = slot.actions.remove(&id.action).is_some(); + } + if replace { + globals.store(signals, lock); + } + replace +}