use crate::{
RuntimeError, futures::signal::signal::SignalReleasePolicy, modules::int_check::IntCheck,
};
use std::{
fmt, mem, ptr,
sync::atomic::{AtomicBool, AtomicU32, Ordering},
};
const MAX_SIGNAL: usize = 32;
static COUNTS: [AtomicU32; MAX_SIGNAL] = [const { AtomicU32::new(0) }; MAX_SIGNAL];
static CAUGHT: [AtomicBool; MAX_SIGNAL] = [const { AtomicBool::new(false) }; MAX_SIGNAL];
static HELD: [AtomicBool; MAX_SIGNAL] = [const { AtomicBool::new(false) }; MAX_SIGNAL];
static WATCHERS: [AtomicU32; MAX_SIGNAL] = [const { AtomicU32::new(0) }; MAX_SIGNAL];
extern "C" fn count_one(signo: libc::c_int) {
if let Some(count) = COUNTS.get(signo as usize) {
count.fetch_add(1, Ordering::Relaxed);
}
}
pub(crate) fn catch(
signo: libc::c_int,
policy: SignalReleasePolicy,
) -> Result<Option<Watcher>, RuntimeError> {
let slot = catchable(signo)?;
if policy == SignalReleasePolicy::Hold {
HELD[slot].store(true, Ordering::Release);
}
let watch = match policy {
SignalReleasePolicy::Hold => None,
SignalReleasePolicy::OnDrop => Some(Watcher::new(signo, slot)),
};
install(signo, slot)?;
Ok(watch)
}
#[inline(always)]
pub(crate) fn count(signo: libc::c_int) -> u32 {
COUNTS
.get(signo as usize)
.map_or(0, |count| count.load(Ordering::Relaxed))
}
pub(crate) fn release(signo: libc::c_int) -> Result<(), RuntimeError> {
let slot = catchable(signo)?;
HELD[slot].store(false, Ordering::Release);
restore(signo, slot)
}
fn catchable(signo: libc::c_int) -> Result<usize, RuntimeError> {
let slot = sendable(signo)?;
if signo == libc::SIGKILL || signo == libc::SIGSTOP {
return Err(RuntimeError::BadSignal);
}
Ok(slot)
}
pub(crate) fn sendable(signo: libc::c_int) -> Result<usize, RuntimeError> {
if signo <= 0 || signo as usize >= MAX_SIGNAL {
return Err(RuntimeError::BadSignal);
}
Ok(signo as usize)
}
fn install(signo: libc::c_int, slot: usize) -> Result<(), RuntimeError> {
if CAUGHT[slot].swap(true, Ordering::AcqRel) {
return Ok(());
}
let mut action: libc::sigaction = unsafe { mem::zeroed() };
action.sa_sigaction = count_one as extern "C" fn(libc::c_int) as libc::sighandler_t;
action.sa_flags = libc::SA_RESTART;
unsafe { libc::sigemptyset(&mut action.sa_mask) };
let set = unsafe { libc::sigaction(signo, &action, ptr::null_mut()) }.check();
if set.is_err() {
CAUGHT[slot].store(false, Ordering::Release);
}
set?;
Ok(())
}
fn restore(signo: libc::c_int, slot: usize) -> Result<(), RuntimeError> {
if !CAUGHT[slot].swap(false, Ordering::AcqRel) {
return Ok(());
}
let mut action: libc::sigaction = unsafe { mem::zeroed() };
action.sa_sigaction = libc::SIG_DFL;
unsafe { libc::sigemptyset(&mut action.sa_mask) };
unsafe { libc::sigaction(signo, &action, ptr::null_mut()) }.check()?;
Ok(())
}
pub(crate) struct Watcher {
signo: libc::c_int,
slot: usize,
}
impl Watcher {
fn new(signo: libc::c_int, slot: usize) -> Self {
WATCHERS[slot].fetch_add(1, Ordering::AcqRel);
Self { signo, slot }
}
}
impl Clone for Watcher {
fn clone(&self) -> Self {
Self::new(self.signo, self.slot)
}
}
impl Drop for Watcher {
fn drop(&mut self) {
if WATCHERS[self.slot].fetch_sub(1, Ordering::AcqRel) != 1 {
return;
}
if HELD[self.slot].load(Ordering::Acquire) {
return;
}
let _ = restore(self.signo, self.slot);
}
}
impl fmt::Debug for Watcher {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("Watcher")
.field("signal", &self.signo)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn uncatchable_signals_are_refused() {
assert_eq!(catchable(libc::SIGKILL), Err(RuntimeError::BadSignal));
assert_eq!(catchable(libc::SIGSTOP), Err(RuntimeError::BadSignal));
assert_eq!(catchable(0), Err(RuntimeError::BadSignal));
assert_eq!(catchable(-1), Err(RuntimeError::BadSignal));
assert_eq!(
catchable(MAX_SIGNAL as libc::c_int),
Err(RuntimeError::BadSignal)
);
assert!(catchable(libc::SIGUSR1).is_ok());
}
#[test]
fn the_uncatchable_can_still_be_sent() {
assert!(sendable(libc::SIGKILL).is_ok());
assert!(sendable(libc::SIGSTOP).is_ok());
assert_eq!(sendable(0), Err(RuntimeError::BadSignal));
}
}