multibg-wayland/src/signal.rs

275 lines
7 KiB
Rust

use std::{
ffi::c_int,
io,
mem::{ManuallyDrop, MaybeUninit},
os::fd::{AsRawFd, BorrowedFd, FromRawFd, OwnedFd},
ptr,
sync::atomic::{AtomicI32, Ordering::Relaxed},
};
use libc::{
raise, SA_RESETHAND, SA_RESTART, SIG_DFL, SIG_ERR,
SIGHUP, SIGINT, SIGUSR1, SIGUSR2, SIGTERM,
sigaction, sigemptyset, signal, sigset_t, write,
};
use rustix::{
fd::AsFd,
io::read_uninit,
};
use crate::poll::pipe_cloexec_nonblock;
const TERM_SIGNALS: [c_int; 3] = [SIGHUP, SIGINT, SIGTERM];
const OTHER_SIGNALS: [c_int; 2] = [SIGUSR1, SIGUSR2];
const TERM: u8 = 1 << 0;
const INT: u8 = 1 << 1;
const HUP: u8 = 1 << 2;
const USR1: u8 = 1 << 3;
const USR2: u8 = 1 << 4;
static PIPE_FD: AtomicI32 = AtomicI32::new(-1);
pub struct SignalPipe {
read_half: OwnedFd,
}
impl SignalPipe {
pub fn new() -> io::Result<SignalPipe> {
unsafe {
let (read_half, write_half) = pipe_cloexec_nonblock()?;
PIPE_FD.compare_exchange(
-1,
write_half.as_raw_fd(),
Relaxed,
Relaxed,
).unwrap();
let _ = ManuallyDrop::new(write_half);
let ret = SignalPipe { read_half };
let sigset_empty = sigset_empty()?;
for signum in TERM_SIGNALS {
sigaction_set_handler(
signum,
handle_termination_signals,
sigset_empty,
SA_RESTART | SA_RESETHAND,
)?;
}
for signum in OTHER_SIGNALS {
sigaction_set_handler(
signum,
handle_other_signals,
sigset_empty,
SA_RESTART,
)?;
}
Ok(ret)
}
}
pub fn read(&self) -> io::Result<SignalFlags> {
let mut buf = [MaybeUninit::<u8>::uninit(); 64];
let mut flags = 0;
for byte in read_uninit(&self.read_half, &mut buf)?.0 {
assert_ne!(*byte, 0);
flags |= *byte;
}
Ok(SignalFlags(flags))
}
}
impl Drop for SignalPipe {
fn drop(&mut self) {
for signum in OTHER_SIGNALS {
sigaction_reset_default(signum).unwrap();
}
for signum in TERM_SIGNALS {
sigaction_reset_default(signum).unwrap();
}
let write_half_fd = PIPE_FD.swap(-1, Relaxed);
assert_ne!(write_half_fd, -1);
drop(unsafe { OwnedFd::from_raw_fd(write_half_fd) });
}
}
impl AsFd for SignalPipe {
fn as_fd(&self) -> BorrowedFd {
self.read_half.as_fd()
}
}
#[derive(Clone, Copy)]
pub struct SignalFlags(u8);
impl SignalFlags {
pub fn any_termination(self) -> Option<&'static str> {
if self.0 & TERM != 0 {
Some("TERM")
} else if self.0 & INT != 0 {
Some("INT")
} else if self.0 & HUP != 0 {
Some("HUP")
} else {
None
}
}
pub fn has_usr1(self) -> bool {
self.0 & USR1 != 0
}
pub fn has_usr2(self) -> bool {
self.0 & USR2 != 0
}
}
fn sigset_empty() -> io::Result<sigset_t> {
unsafe {
let mut sigset = MaybeUninit::uninit();
if sigemptyset(sigset.as_mut_ptr()) < 0 {
return Err(io::Error::last_os_error())
}
Ok(sigset.assume_init())
}
}
unsafe fn sigaction_set_handler(
signum: c_int,
handler: extern "C" fn(c_int),
mask: sigset_t,
flags: c_int
) -> io::Result<()> {
unsafe {
if sigaction(
signum,
&sigaction {
sa_sigaction: handler as _,
sa_mask: mask,
sa_flags: flags,
sa_restorer: None,
},
ptr::null_mut(),
) < 0 {
return Err(io::Error::last_os_error())
}
Ok(())
}
}
fn sigaction_reset_default(signum: c_int) -> io::Result<()> {
unsafe {
if signal(signum, SIG_DFL) == SIG_ERR {
return Err(io::Error::last_os_error())
}
Ok(())
}
}
extern "C" fn handle_termination_signals(signum: c_int) {
unsafe {
let _errno_guard = ErrnoGuard::new();
let byte: u8 = match signum {
SIGTERM => TERM,
SIGINT => INT,
SIGHUP => HUP,
_ => 0,
};
// In case of an error termination signals will have SA_RESETHAND set
// so re-raise the signal to invoke the default handler
if write(
PIPE_FD.load(Relaxed),
ptr::from_ref(&byte).cast(),
1,
) != 1 {
raise(signum);
}
}
}
extern "C" fn handle_other_signals(signum: c_int) {
unsafe {
let _errno_guard = ErrnoGuard::new();
let byte: u8 = match signum {
SIGUSR1 => USR1,
SIGUSR2 => USR2,
_ => 0,
};
// In case of an error ignore non-termination signals
let _ = write(
PIPE_FD.load(Relaxed),
ptr::from_ref(&byte).cast(),
1,
);
}
}
struct ErrnoGuard(i32);
impl ErrnoGuard {
unsafe fn new() -> ErrnoGuard {
ErrnoGuard(errno())
}
}
impl Drop for ErrnoGuard {
fn drop(&mut self) {
set_errno(self.0)
}
}
// Based on the Rust Standard Library:
// .rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/sys/pal/unix/os.rs
// with changes for stable rust from the errno crate:
// https://github.com/lambda-fairy/rust-errno/blob/main/src/unix.rs
// under licence MIT OR Apache-2.0
unsafe extern "C" {
#[cfg_attr(
any(
target_os = "linux",
target_os = "emscripten",
target_os = "fuchsia",
target_os = "l4re",
target_os = "hurd",
target_os = "dragonfly",
),
link_name = "__errno_location"
)]
#[cfg_attr(
any(
target_os = "netbsd",
target_os = "openbsd",
target_os = "cygwin",
target_os = "android",
target_os = "redox",
target_os = "nuttx",
target_env = "newlib",
target_os = "vxworks",
),
link_name = "__errno"
)]
#[cfg_attr(
any(target_os = "solaris", target_os = "illumos"),
link_name = "___errno"
)]
#[cfg_attr(target_os = "nto", link_name = "__get_errno_ptr")]
#[cfg_attr(
any(target_os = "freebsd", target_vendor = "apple"),
link_name = "__error"
)]
#[cfg_attr(target_os = "haiku", link_name = "_errnop")]
#[cfg_attr(target_os = "aix", link_name = "_Errno")]
// SAFETY: this will always return the same pointer on a given thread.
fn errno_location() -> *mut c_int;
}
/// Returns the platform-specific value of errno
#[inline]
fn errno() -> i32 {
unsafe { (*errno_location()) as i32 }
}
/// Sets the platform-specific value of errno
// needed for readdir and syscall!
#[inline]
fn set_errno(e: i32) {
unsafe { *errno_location() = e as c_int }
}