#![deny(
missing_docs,
missing_debug_implementations,
missing_copy_implementations,
trivial_casts,
trivial_numeric_casts,
unstable_features,
unused_import_braces,
unused_qualifications
)]
use std::convert::TryFrom;
use std::sync::{
mpsc::{self, Sender},
Mutex, Once,
};
use std::thread;
use lazy_static::lazy_static;
use libc;
use fnv::FnvHashMap;
#[derive(Debug, Clone, Copy)]
pub enum Error {
CallFailed,
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::CallFailed => write!(
f,
"Addy function call failed to send. The MPSC and/or event loop thread has closed."
),
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None
}
}
type CBPointer = Box<dyn Fn(Signal) -> () + Send>;
struct CBP(CBPointer);
impl std::fmt::Debug for CBP {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("CBPointer")
}
}
#[derive(Debug)]
enum Action {
Call(Signal),
Register(Signal, String, CBP),
Remove(Signal, String),
Clear(Signal),
Ignore(Signal),
Default(Signal),
Release(Signal),
Resume(Signal),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(i32)]
pub enum Signal {
SIGHUP = libc::SIGHUP,
SIGINT = libc::SIGINT,
SIGQUIT = libc::SIGQUIT,
SIGILL = libc::SIGILL,
SIGTRAP = libc::SIGTRAP,
SIGABRT = libc::SIGABRT,
SIGBUS = libc::SIGBUS,
SIGFPE = libc::SIGFPE,
SIGKILL = libc::SIGKILL,
SIGUSR1 = libc::SIGUSR1,
SIGSEGV = libc::SIGSEGV,
SIGUSR2 = libc::SIGUSR2,
SIGPIPE = libc::SIGPIPE,
SIGALRM = libc::SIGALRM,
SIGTERM = libc::SIGTERM,
#[cfg(all(
any(target_os = "android", target_os = "emscripten", target_os = "linux"),
not(any(target_arch = "mips", target_arch = "mips64", target_arch = "sparc64"))
))]
SIGSTKFLT = libc::SIGSTKFLT,
SIGCHLD = libc::SIGCHLD,
SIGCONT = libc::SIGCONT,
SIGSTOP = libc::SIGSTOP,
SIGTSTP = libc::SIGTSTP,
SIGTTIN = libc::SIGTTIN,
SIGTTOU = libc::SIGTTOU,
SIGURG = libc::SIGURG,
SIGXCPU = libc::SIGXCPU,
SIGXFSZ = libc::SIGXFSZ,
SIGVTALRM = libc::SIGVTALRM,
SIGPROF = libc::SIGPROF,
SIGWINCH = libc::SIGWINCH,
SIGIO = libc::SIGIO,
#[cfg(any(target_os = "android", target_os = "emscripten", target_os = "linux"))]
SIGPWR = libc::SIGPWR,
SIGSYS = libc::SIGSYS,
#[cfg(not(any(target_os = "android", target_os = "emscripten", target_os = "linux")))]
SIGEMT = libc::SIGEMT,
#[cfg(not(any(target_os = "android", target_os = "emscripten", target_os = "linux")))]
SIGINFO = libc::SIGINFO,
}
pub use self::Signal::*;
impl Signal {
pub fn as_str(self) -> &'static str {
match self {
SIGHUP => "SIGHUP",
SIGINT => "SIGINT",
SIGQUIT => "SIGQUIT",
SIGILL => "SIGILL",
SIGTRAP => "SIGTRAP",
SIGABRT => "SIGABRT",
SIGBUS => "SIGBUS",
SIGFPE => "SIGFPE",
SIGKILL => "SIGKILL",
SIGUSR1 => "SIGUSR1",
SIGSEGV => "SIGSEGV",
SIGUSR2 => "SIGUSR2",
SIGPIPE => "SIGPIPE",
SIGALRM => "SIGALRM",
SIGTERM => "SIGTERM",
#[cfg(all(
any(target_os = "android", target_os = "emscripten", target_os = "linux"),
not(any(target_arch = "mips", target_arch = "mips64", target_arch = "sparc64"))
))]
SIGSTKFLT => "SIGSTKFLT",
SIGCHLD => "SIGCHLD",
SIGCONT => "SIGCONT",
SIGSTOP => "SIGSTOP",
SIGTSTP => "SIGTSTP",
SIGTTIN => "SIGTTIN",
SIGTTOU => "SIGTTOU",
SIGURG => "SIGURG",
SIGXCPU => "SIGXCPU",
SIGXFSZ => "SIGXFSZ",
SIGVTALRM => "SIGVTALRM",
SIGPROF => "SIGPROF",
SIGWINCH => "SIGWINCH",
SIGIO => "SIGIO",
#[cfg(any(target_os = "android", target_os = "emscripten", target_os = "linux"))]
SIGPWR => "SIGPWR",
SIGSYS => "SIGSYS",
#[cfg(not(any(target_os = "android", target_os = "emscripten", target_os = "linux")))]
SIGEMT => "SIGEMT",
#[cfg(not(any(target_os = "android", target_os = "emscripten", target_os = "linux")))]
SIGINFO => "SIGINFO",
}
}
}
impl AsRef<str> for Signal {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl std::fmt::Display for Signal {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.write_str(self.as_ref())
}
}
#[cfg(all(
any(target_os = "linux", target_os = "android", target_os = "emscripten"),
not(any(target_arch = "mips", target_arch = "mips64", target_arch = "sparc64"))
))]
const SIGNALS: [Signal; 31] = [
SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGTRAP, SIGABRT, SIGBUS, SIGFPE, SIGKILL, SIGUSR1, SIGSEGV,
SIGUSR2, SIGPIPE, SIGALRM, SIGTERM, SIGSTKFLT, SIGCHLD, SIGCONT, SIGSTOP, SIGTSTP, SIGTTIN,
SIGTTOU, SIGURG, SIGXCPU, SIGXFSZ, SIGVTALRM, SIGPROF, SIGWINCH, SIGIO, SIGPWR, SIGSYS,
];
#[cfg(all(
any(target_os = "linux", target_os = "android", target_os = "emscripten"),
any(target_arch = "mips", target_arch = "mips64", target_arch = "sparc64")
))]
const SIGNALS: [Signal; 30] = [
SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGTRAP, SIGABRT, SIGBUS, SIGFPE, SIGKILL, SIGUSR1, SIGSEGV,
SIGUSR2, SIGPIPE, SIGALRM, SIGTERM, SIGCHLD, SIGCONT, SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU,
SIGURG, SIGXCPU, SIGXFSZ, SIGVTALRM, SIGPROF, SIGWINCH, SIGIO, SIGPWR, SIGSYS,
];
#[cfg(not(any(target_os = "linux", target_os = "android", target_os = "emscripten")))]
const SIGNALS: [Signal; 31] = [
SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGTRAP, SIGABRT, SIGBUS, SIGFPE, SIGKILL, SIGUSR1, SIGSEGV,
SIGUSR2, SIGPIPE, SIGALRM, SIGTERM, SIGCHLD, SIGCONT, SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU,
SIGURG, SIGXCPU, SIGXFSZ, SIGVTALRM, SIGPROF, SIGWINCH, SIGIO, SIGSYS, SIGEMT, SIGINFO,
];
const NUM_SIGNALS: libc::c_int = 32;
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct SignalIterator {
next: usize,
}
impl Iterator for SignalIterator {
type Item = Signal;
fn next(&mut self) -> Option<Signal> {
if self.next < SIGNALS.len() {
let next_signal = SIGNALS[self.next];
self.next += 1;
Some(next_signal)
} else {
None
}
}
}
impl Signal {
pub fn iterator() -> SignalIterator {
SignalIterator { next: 0 }
}
}
type CVoid = *mut libc::c_void;
fn c_handler(signal: Signal, _info: CVoid, _ucontext: CVoid) {
let sender;
unsafe {
sender = SENDER.as_ref().unwrap().clone();
}
let _ = sender.send(Action::Call(signal));
}
#[derive(Debug)]
pub struct SignalHandle {
signal: Signal,
sender: Sender<Action>,
}
type SignalResult<'a> = Result<&'a mut SignalHandle, Error>;
impl SignalHandle {
pub fn register<'a, A, F>(&'a mut self, name: A, cb: F) -> SignalResult
where
A: AsRef<str>,
F: Fn(Signal) -> () + Send + 'static,
{
let cb = CBP(Box::new(cb));
let name = String::from(name.as_ref());
self.sender
.send(Action::Register(self.signal, name, cb))
.map_err(|_| Error::CallFailed)?;
Ok(self)
}
pub fn remove<'a, A>(&'a mut self, name: A) -> SignalResult
where
A: AsRef<str>,
{
let name = String::from(name.as_ref());
self.sender
.send(Action::Remove(self.signal, name))
.map_err(|_| Error::CallFailed)?;
Ok(self)
}
pub fn clear<'a>(&'a mut self) -> SignalResult {
self.sender
.send(Action::Clear(self.signal))
.map_err(|_| Error::CallFailed)?;
Ok(self)
}
pub fn release<'a>(&'a mut self) -> SignalResult {
self.sender
.send(Action::Release(self.signal))
.map_err(|_| Error::CallFailed)?;
Ok(self)
}
pub fn ignore<'a>(&'a mut self) -> SignalResult {
self.sender
.send(Action::Ignore(self.signal))
.map_err(|_| Error::CallFailed)?;
Ok(self)
}
pub fn default<'a>(&'a mut self) -> SignalResult {
self.sender
.send(Action::Default(self.signal))
.map_err(|_| Error::CallFailed)?;
Ok(self)
}
pub fn resume<'a>(&'a mut self) -> SignalResult {
self.sender
.send(Action::Resume(self.signal))
.map_err(|_| Error::CallFailed)?;
Ok(self)
}
pub fn enable<'a>(&'a mut self) -> SignalResult {
self.sender
.send(Action::Resume(self.signal))
.map_err(|_| Error::CallFailed)?;
Ok(self)
}
}
static SETUP: Once = Once::new();
lazy_static! {
static ref SAFE_SENDER: Mutex<Option<Sender<Action>>> = {
Mutex::new(None)
};
}
static mut SENDER: Option<Sender<Action>> = None;
type NameToCallback = FnvHashMap<String, CBP>;
type SignalToCallbacks<T> = FnvHashMap<Signal, T>;
fn setup() {
SETUP.call_once(|| {
let (sender, receiver) = mpsc::channel::<Action>();
{
let mut guard = SAFE_SENDER.lock().unwrap();
guard.replace(sender.clone());
}
unsafe {
SENDER.replace(sender.clone());
}
thread::spawn(move || {
let nsig = usize::try_from(NUM_SIGNALS).unwrap(); let mut handlers = SignalToCallbacks::<NameToCallback>::with_capacity_and_hasher(
nsig,
Default::default(),
);
let mut active: [bool; NUM_SIGNALS as usize] = [false; 32];
let mut sigset = std::mem::MaybeUninit::uninit();
let _ = unsafe { libc::sigfillset(sigset.as_mut_ptr()) };
let sigset = unsafe { sigset.assume_init() };
#[allow(non_snake_case)]
let SA_DEFAULT: libc::sigaction = libc::sigaction {
sa_sigaction: libc::SIG_DFL,
sa_mask: sigset,
sa_flags: libc::SA_SIGINFO,
#[cfg(target_os = "linux")]
sa_restorer: None,
};
#[allow(non_snake_case)]
let SA_IGNORE: libc::sigaction = libc::sigaction {
sa_sigaction: libc::SIG_IGN,
sa_mask: sigset,
sa_flags: libc::SA_SIGINFO,
#[cfg(target_os = "linux")]
sa_restorer: None,
};
#[allow(non_snake_case)]
let SA_CALLBACK: libc::sigaction = libc::sigaction {
sa_sigaction: c_handler as libc::sighandler_t,
sa_mask: sigset,
sa_flags: libc::SA_SIGINFO,
#[cfg(target_os = "linux")]
sa_restorer: None,
};
let ignore = move |signal: Signal| unsafe {
libc::sigaction(signal as libc::c_int, &SA_IGNORE, std::ptr::null_mut());
};
let default = move |signal: Signal| unsafe {
libc::sigaction(signal as libc::c_int, &SA_DEFAULT, std::ptr::null_mut());
};
fn index(signal: Signal) -> usize {
usize::try_from(signal as libc::c_int).unwrap()
}
let set_all_to_default = || {
for signal in Signal::iterator() {
default(signal);
}
};
let _ = std::panic::catch_unwind(|| {
set_all_to_default();
});
let mut messages = receiver.iter();
while let Some(action) = messages.next() {
match action {
Action::Call(signal) => {
if let Some(callbacks) = handlers.get(&signal) {
let callbacks = callbacks.iter();
for (_, cb) in callbacks {
cb.0(signal);
}
}
}
Action::Register(signal, name, cb) => {
let callbacks = handlers.entry(signal).or_default();
callbacks.insert(name, cb);
}
Action::Remove(signal, name) => {
if let Some(callbacks) = handlers.get_mut(&signal) {
callbacks.remove(&name);
}
}
Action::Clear(signal) => {
handlers.remove(&signal);
}
Action::Ignore(signal) => {
ignore(signal);
active[index(signal)] = false;
}
Action::Default(signal) => {
default(signal);
active[index(signal)] = false;
}
Action::Release(signal) => {
handlers.remove(&signal);
default(signal);
active[index(signal)] = false;
}
Action::Resume(signal) => {
if !active[index(signal)] {
unsafe {
libc::sigaction(
signal as libc::c_int,
&SA_CALLBACK,
std::ptr::null_mut(),
);
}
active[index(signal)] = true;
}
}
}
}
set_all_to_default();
}); });
#[cfg(feature = "nightly")]
while !SETUP.is_completed() { }
}
pub fn mediate<S: Into<Signal>>(signal: S) -> SignalHandle {
let signal = signal.into();
setup();
let sender;
{
let guard = SAFE_SENDER.lock().unwrap();
sender = guard.as_ref().unwrap().clone();
}
SignalHandle { signal, sender }
}
#[doc(hidden)]
pub fn medicate(signal: Signal) {
mediate(signal);
}
#[doc(hidden)]
pub fn intercept(signal: Signal) {
mediate(signal);
}