use std::collections::hash_map::Entry;
use std::collections::{BTreeMap, HashMap};
use std::io::Error;
use std::mem;
use std::ptr;
#[allow(deprecated)]
use std::sync::ONCE_INIT;
use std::sync::{Arc, Mutex, MutexGuard, Once};
use arc_swap::IndependentArcSwap;
use libc::{c_int, c_void, sigaction, siginfo_t, sigset_t, SIG_BLOCK, SIG_SETMASK};
use libc::{SIGFPE, SIGILL, SIGKILL, SIGSEGV, SIGSTOP};
#[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,
}
#[allow(unknown_lints, bare_trait_objects)]
type Action = Fn(&siginfo_t) + Send + Sync;
#[derive(Clone)]
struct Slot {
prev: sigaction,
actions: BTreeMap<ActionId, Arc<Action>>,
}
impl Slot {
fn new(signal: libc::c_int) -> Result<Self, Error> {
let mut new: libc::sigaction = unsafe { mem::zeroed() };
new.sa_sigaction = handler as usize;
let flags = libc::SA_RESTART | libc::SA_NOCLDSTOP;
#[allow(unused_assignments)]
let mut siginfo = flags;
siginfo = libc::SA_SIGINFO as _;
let flags = flags | siginfo;
new.sa_flags = flags as _;
let mut old: libc::sigaction = unsafe { mem::zeroed() };
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<c_int, Slot>;
struct GlobalData {
all_signals: IndependentArcSwap<AllSignals>,
rcu_lock: Mutex<u64>,
}
static mut GLOBAL_DATA: Option<GlobalData> = None;
#[allow(deprecated)]
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: IndependentArcSwap::from_pointee(HashMap::new()),
rcu_lock: Mutex::new(0),
});
});
Self::get()
}
fn load(&self) -> (AllSignals, MutexGuard<u64>) {
let lock = self.rcu_lock.lock().unwrap();
let signals = AllSignals::clone(&self.all_signals.load());
(signals, lock)
}
fn store(&self, signals: AllSignals, lock: MutexGuard<u64>) {
let signals = Arc::new(signals);
self.all_signals.store(signals);
drop(lock);
}
}
extern "C" fn handler(sig: c_int, info: *mut siginfo_t, data: *mut c_void) {
let signals = GlobalData::get().all_signals.load_signal_safe();
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 {
unsafe {
#[allow(unused_assignments)]
let mut siginfo = slot.prev.sa_flags;
siginfo = libc::SA_SIGINFO as _;
if slot.prev.sa_flags & siginfo == 0 {
let action = mem::transmute::<usize, extern "C" fn(c_int)>(fptr);
action(sig);
} else {
type SigAction = extern "C" fn(c_int, *mut siginfo_t, *mut c_void);
let action = mem::transmute::<usize, SigAction>(fptr);
action(sig, info, data);
}
}
}
let info = unsafe { info.as_ref().unwrap() };
for action in slot.actions.values() {
action(info);
}
}
}
fn block_signal(signal: c_int) -> Result<sigset_t, Error> {
unsafe {
#[allow(deprecated)]
let mut newsigs: sigset_t = mem::uninitialized();
libc::sigemptyset(&mut newsigs);
libc::sigaddset(&mut newsigs, signal);
#[allow(deprecated)]
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<F: FnOnce() -> Result<(), Error>>(signal: c_int, f: F) -> Result<(), Error> {
let old_signals = block_signal(signal)?;
let result = f();
let restored = restore_signals(old_signals);
result.and(restored)
}
pub const FORBIDDEN: &[c_int] = FORBIDDEN_IMPL;
const FORBIDDEN_IMPL: &[c_int] = &[SIGKILL, SIGSTOP, SIGILL, SIGFPE, SIGSEGV];
pub unsafe fn register<F>(signal: c_int, action: F) -> Result<SigId, Error>
where
F: Fn() + Sync + Send + 'static,
{
register_sigaction_impl(signal, move |_: &_| action())
}
pub unsafe fn register_sigaction<F>(signal: c_int, action: F) -> Result<SigId, Error>
where
F: Fn(&siginfo_t) + Sync + Send + 'static,
{
register_sigaction_impl(signal, action)
}
unsafe fn register_sigaction_impl<F>(signal: c_int, action: F) -> Result<SigId, Error>
where
F: Fn(&siginfo_t) + Sync + Send + 'static,
{
assert!(
!FORBIDDEN.contains(&signal),
"Attempted to register forbidden signal {}",
signal,
);
register_unchecked_impl(signal, action)
}
pub unsafe fn register_signal_unchecked<F>(signal: c_int, action: F) -> Result<SigId, Error>
where
F: Fn() + Sync + Send + 'static,
{
register_unchecked_impl(signal, move |_: &_| action())
}
pub unsafe fn register_unchecked<F>(signal: c_int, action: F) -> Result<SigId, Error>
where
F: Fn(&siginfo_t) + Sync + Send + 'static,
{
register_unchecked_impl(signal, action)
}
unsafe fn register_unchecked_impl<F>(signal: c_int, action: F) -> Result<SigId, Error>
where
F: Fn(&siginfo_t) + Sync + Send + 'static,
{
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
}
pub fn unregister_signal(signal: c_int) -> bool {
let globals = GlobalData::ensure();
let (mut signals, lock) = globals.load();
let mut replace = false;
if let Some(slot) = signals.get_mut(&signal) {
if !slot.actions.is_empty() {
slot.actions.clear();
replace = true;
}
}
if replace {
globals.store(signals, lock);
}
replace
}
#[cfg(test)]
mod tests {
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::thread;
use std::time::Duration;
use libc::{pid_t, SIGUSR1, SIGUSR2};
use super::*;
#[test]
#[should_panic]
fn panic_forbidden() {
let _ = unsafe { register(SIGILL, || ()) };
}
#[test]
fn forbidden_raw() {
unsafe { register_signal_unchecked(SIGFPE, || std::process::abort()).unwrap() };
}
#[test]
fn signal_without_pid() {
let status = Arc::new(AtomicUsize::new(0));
let action = {
let status = Arc::clone(&status);
move || {
status.store(1, Ordering::Relaxed);
}
};
unsafe {
register(SIGUSR2, action).unwrap();
libc::raise(SIGUSR2);
}
for _ in 0..10 {
thread::sleep(Duration::from_millis(100));
let current = status.load(Ordering::Relaxed);
match current {
0 => continue,
_ if current == 1 => return,
_ => panic!("Wrong result value {}", current),
}
}
panic!("Timed out waiting for the signal");
}
#[test]
fn signal_with_pid() {
let status = Arc::new(AtomicUsize::new(0));
let action = {
let status = Arc::clone(&status);
move |siginfo: &siginfo_t| {
#[repr(C)]
struct SigInfo {
_fields: [c_int; 3],
#[cfg(all(target_pointer_width = "64", target_os = "linux"))]
_pad: c_int,
pid: pid_t,
}
let s: &SigInfo = unsafe {
(siginfo as *const _ as usize as *const SigInfo)
.as_ref()
.unwrap()
};
status.store(s.pid as usize, Ordering::Relaxed);
}
};
let pid;
unsafe {
pid = libc::getpid();
register_sigaction(SIGUSR2, action).unwrap();
libc::raise(SIGUSR2);
}
for _ in 0..10 {
thread::sleep(Duration::from_millis(100));
let current = status.load(Ordering::Relaxed);
match current {
0 => continue,
_ if current == pid as usize => return,
_ => panic!("Wrong status value {}", current),
}
}
panic!("Timed out waiting for the signal");
}
#[test]
fn register_unregister() {
let signal = unsafe { register(SIGUSR1, || ()).unwrap() };
assert!(unregister(signal));
assert!(!unregister(signal));
}
}