#![cfg(unix)]
use std::os::unix::io::RawFd;
use std::sync::atomic::{AtomicBool, AtomicI32, Ordering};
use std::sync::{mpsc, Arc, Mutex};
static WRITE_FD: AtomicI32 = AtomicI32::new(-1);
static INITIALIZED: AtomicBool = AtomicBool::new(false);
const HANDLED_SIGNALS: &[libc::c_int] = &[
libc::SIGHUP,
libc::SIGINT,
libc::SIGILL,
libc::SIGABRT,
libc::SIGFPE,
libc::SIGPIPE,
libc::SIGALRM,
libc::SIGTERM,
libc::SIGUSR1,
libc::SIGUSR2,
libc::SIGWINCH,
libc::SIGCONT,
libc::SIGURG,
];
extern "C" fn pipe_handler(sig: libc::c_int) {
let fd = WRITE_FD.load(Ordering::Relaxed);
if fd >= 0 {
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
let byte = sig as u8;
unsafe {
libc::write(fd, std::ptr::addr_of!(byte).cast::<libc::c_void>(), 1);
}
}
}
unsafe fn install_handler(signum: libc::c_int) {
let mut sa: libc::sigaction = std::mem::zeroed();
sa.sa_sigaction = pipe_handler as *const () as libc::sighandler_t;
sa.sa_flags = libc::SA_RESTART;
libc::sigaction(signum, &sa, std::ptr::null_mut());
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub enum Signal {
Hup,
Int,
Ill,
Abrt,
Fpe,
Pipe,
Alrm,
Term,
Usr1,
Usr2,
Winch,
Cont,
Urg,
}
impl Signal {
pub fn is_terminating(&self) -> bool {
matches!(
self,
Signal::Int | Signal::Term | Signal::Ill | Signal::Abrt | Signal::Fpe
)
}
fn from_raw(n: u8) -> Option<Self> {
match libc::c_int::from(n) {
libc::SIGHUP => Some(Signal::Hup),
libc::SIGINT => Some(Signal::Int),
libc::SIGILL => Some(Signal::Ill),
libc::SIGABRT => Some(Signal::Abrt),
libc::SIGFPE => Some(Signal::Fpe),
libc::SIGPIPE => Some(Signal::Pipe),
libc::SIGALRM => Some(Signal::Alrm),
libc::SIGTERM => Some(Signal::Term),
libc::SIGUSR1 => Some(Signal::Usr1),
libc::SIGUSR2 => Some(Signal::Usr2),
libc::SIGWINCH => Some(Signal::Winch),
libc::SIGCONT => Some(Signal::Cont),
libc::SIGURG => Some(Signal::Urg),
_ => None,
}
}
}
impl std::fmt::Display for Signal {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let name = match self {
Signal::Hup => "SIGHUP",
Signal::Int => "SIGINT",
Signal::Ill => "SIGILL",
Signal::Abrt => "SIGABRT",
Signal::Fpe => "SIGFPE",
Signal::Pipe => "SIGPIPE",
Signal::Alrm => "SIGALRM",
Signal::Term => "SIGTERM",
Signal::Usr1 => "SIGUSR1",
Signal::Usr2 => "SIGUSR2",
Signal::Winch => "SIGWINCH",
Signal::Cont => "SIGCONT",
Signal::Urg => "SIGURG",
};
f.write_str(name)
}
}
#[derive(Debug)]
pub enum SignalError {
Disconnected,
AlreadyInitialized,
OsError(std::io::Error),
}
impl Clone for SignalError {
fn clone(&self) -> Self {
match self {
Self::Disconnected => Self::Disconnected,
Self::AlreadyInitialized => Self::AlreadyInitialized,
Self::OsError(e) => Self::OsError(std::io::Error::from(e.kind())),
}
}
}
impl PartialEq for SignalError {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Disconnected, Self::Disconnected) => true,
(Self::AlreadyInitialized, Self::AlreadyInitialized) => true,
(Self::OsError(a), Self::OsError(b)) => a.kind() == b.kind(),
_ => false,
}
}
}
impl Eq for SignalError {}
impl std::fmt::Display for SignalError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SignalError::Disconnected => f.write_str("signal channel disconnected"),
SignalError::AlreadyInitialized => f.write_str("a Signals instance is already active"),
SignalError::OsError(e) => write!(f, "OS error during signal channel setup: {e}"),
}
}
}
impl std::error::Error for SignalError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
SignalError::OsError(e) => Some(e),
_ => None,
}
}
}
type Senders = Arc<Mutex<Vec<mpsc::Sender<Signal>>>>;
#[derive(Debug)]
struct SignalsInner {
write_fd: RawFd,
senders: Senders,
}
impl Drop for SignalsInner {
fn drop(&mut self) {
unsafe { libc::close(self.write_fd) };
WRITE_FD.store(-1, Ordering::Relaxed);
INITIALIZED.store(false, Ordering::Relaxed);
}
}
#[derive(Clone, Debug)]
pub struct Signals(Arc<SignalsInner>);
impl Signals {
pub fn new() -> Result<Self, SignalError> {
if INITIALIZED
.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
.is_err()
{
return Err(SignalError::AlreadyInitialized);
}
Self::try_init().map_err(|e| {
INITIALIZED.store(false, Ordering::Relaxed);
e
})
}
fn try_init() -> Result<Self, SignalError> {
let mut fds = [0i32; 2];
if unsafe { libc::pipe(fds.as_mut_ptr()) } != 0 {
return Err(SignalError::OsError(std::io::Error::last_os_error()));
}
let read_fd = fds[0];
let write_fd = fds[1];
unsafe {
let fl = libc::fcntl(write_fd, libc::F_GETFL);
if fl == -1
|| libc::fcntl(write_fd, libc::F_SETFL, fl | libc::O_NONBLOCK) == -1
|| libc::fcntl(write_fd, libc::F_SETFD, libc::FD_CLOEXEC) == -1
|| libc::fcntl(read_fd, libc::F_SETFD, libc::FD_CLOEXEC) == -1
{
let e = std::io::Error::last_os_error();
libc::close(write_fd);
libc::close(read_fd);
return Err(SignalError::OsError(e));
}
}
WRITE_FD.store(write_fd, Ordering::Relaxed);
let senders: Senders = Arc::new(Mutex::new(Vec::new()));
let thread_senders = Arc::clone(&senders);
std::thread::Builder::new()
.name("signal-msg".into())
.spawn(move || {
let mut buf = [0u8; 64];
loop {
let n =
unsafe { libc::read(read_fd, buf.as_mut_ptr().cast::<libc::c_void>(), 64) };
if n < 0 {
if std::io::Error::last_os_error().kind() == std::io::ErrorKind::Interrupted
{
continue; }
break; }
if n == 0 {
break; }
#[allow(clippy::cast_sign_loss)]
let received = &buf[..n as usize];
let mut locked = thread_senders.lock().unwrap_or_else(|p| p.into_inner());
for &byte in received {
if let Some(sig) = Signal::from_raw(byte) {
locked.retain(|s| s.send(sig).is_ok());
}
}
}
unsafe { libc::close(read_fd) };
})
.map_err(|e| {
unsafe {
libc::close(write_fd);
libc::close(read_fd);
}
WRITE_FD.store(-1, Ordering::Relaxed);
SignalError::OsError(e)
})?;
for &signum in HANDLED_SIGNALS {
unsafe { install_handler(signum) };
}
Ok(Signals(Arc::new(SignalsInner { write_fd, senders })))
}
#[must_use]
pub fn subscribe(&self) -> Receiver {
let (tx, rx) = mpsc::channel();
self.0
.senders
.lock()
.unwrap_or_else(|p| p.into_inner())
.push(tx);
Receiver(rx)
}
}
#[derive(Debug)]
pub struct Receiver(mpsc::Receiver<Signal>);
impl Receiver {
pub fn listen(&self) -> Result<Signal, SignalError> {
self.0.recv().map_err(|_| SignalError::Disconnected)
}
pub fn try_listen(&self) -> Result<Option<Signal>, SignalError> {
match self.0.try_recv() {
Ok(sig) => Ok(Some(sig)),
Err(mpsc::TryRecvError::Empty) => Ok(None),
Err(mpsc::TryRecvError::Disconnected) => Err(SignalError::Disconnected),
}
}
}
impl Iterator for Receiver {
type Item = Signal;
fn next(&mut self) -> Option<Self::Item> {
self.0.recv().ok()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_terminating_exit_signals() {
assert!(Signal::Int.is_terminating());
assert!(Signal::Term.is_terminating());
assert!(Signal::Ill.is_terminating());
assert!(Signal::Abrt.is_terminating());
assert!(Signal::Fpe.is_terminating());
}
#[test]
fn test_is_terminating_non_exit_signals() {
assert!(!Signal::Hup.is_terminating());
assert!(!Signal::Pipe.is_terminating());
assert!(!Signal::Alrm.is_terminating());
assert!(!Signal::Usr1.is_terminating());
assert!(!Signal::Usr2.is_terminating());
assert!(!Signal::Winch.is_terminating());
assert!(!Signal::Cont.is_terminating());
assert!(!Signal::Urg.is_terminating());
}
#[test]
fn test_display_new_signals() {
assert_eq!(Signal::Usr1.to_string(), "SIGUSR1");
assert_eq!(Signal::Usr2.to_string(), "SIGUSR2");
assert_eq!(Signal::Winch.to_string(), "SIGWINCH");
assert_eq!(Signal::Cont.to_string(), "SIGCONT");
assert_eq!(Signal::Urg.to_string(), "SIGURG");
}
#[test]
fn test_display_existing_signals() {
assert_eq!(Signal::Hup.to_string(), "SIGHUP");
assert_eq!(Signal::Int.to_string(), "SIGINT");
assert_eq!(Signal::Ill.to_string(), "SIGILL");
assert_eq!(Signal::Abrt.to_string(), "SIGABRT");
assert_eq!(Signal::Fpe.to_string(), "SIGFPE");
assert_eq!(Signal::Pipe.to_string(), "SIGPIPE");
assert_eq!(Signal::Alrm.to_string(), "SIGALRM");
assert_eq!(Signal::Term.to_string(), "SIGTERM");
}
#[test]
fn test_from_raw_new_signals() {
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
{
assert_eq!(Signal::from_raw(libc::SIGUSR1 as u8), Some(Signal::Usr1));
assert_eq!(Signal::from_raw(libc::SIGUSR2 as u8), Some(Signal::Usr2));
assert_eq!(Signal::from_raw(libc::SIGWINCH as u8), Some(Signal::Winch));
assert_eq!(Signal::from_raw(libc::SIGCONT as u8), Some(Signal::Cont));
assert_eq!(Signal::from_raw(libc::SIGURG as u8), Some(Signal::Urg));
}
}
#[test]
fn test_from_raw_unknown_returns_none() {
assert_eq!(Signal::from_raw(0), None);
assert_eq!(Signal::from_raw(255), None);
}
#[test]
fn test_receiver_iterator_yields_signals() {
let (tx, rx) = mpsc::channel();
let receiver = Receiver(rx);
tx.send(Signal::Usr1).unwrap();
tx.send(Signal::Winch).unwrap();
tx.send(Signal::Int).unwrap();
drop(tx);
let collected: Vec<Signal> = receiver.collect();
assert_eq!(collected, vec![Signal::Usr1, Signal::Winch, Signal::Int]);
}
#[test]
fn test_receiver_iterator_ends_on_disconnect() {
let (tx, rx) = mpsc::channel();
let mut receiver = Receiver(rx);
tx.send(Signal::Cont).unwrap();
drop(tx);
assert_eq!(receiver.next(), Some(Signal::Cont));
assert_eq!(receiver.next(), None);
}
#[test]
fn test_receiver_iterator_with_take_while() {
let (tx, rx) = mpsc::channel();
let receiver = Receiver(rx);
tx.send(Signal::Usr1).unwrap();
tx.send(Signal::Winch).unwrap();
tx.send(Signal::Int).unwrap(); tx.send(Signal::Usr2).unwrap();
drop(tx);
let non_term: Vec<Signal> = receiver.take_while(|s| !s.is_terminating()).collect();
assert_eq!(non_term, vec![Signal::Usr1, Signal::Winch]);
}
}