use std::{
io,
mem::{self, MaybeUninit},
os::fd::OwnedFd,
panic, ptr,
sync::{
OnceLock,
atomic::{AtomicU32, Ordering},
},
task::{Context, Poll, ready},
};
use libc::c_int;
use rustix::io::{read, write};
use super::{Set, Signal, lock, wake};
use crate::{AsyncIo, Mode, Runtime, log::error, poll::pipe::pipe};
pub(super) struct Watch<M>
where
M: Mode,
{
pipe: AsyncIo<OwnedFd, M>,
}
impl<M> Watch<M>
where
M: Mode,
{
pub(super) fn new(runtime: &Runtime<M>) -> io::Result<Self> {
let read = Pipe::get()?.read.try_clone()?;
Ok(Self {
pipe: AsyncIo::from_nonblocking(runtime, read)?,
})
}
pub(super) fn poll_deliver(&self, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
let delivered = ready!(self.pipe.poll_read_with(cx, |pipe| {
let mut registry = lock();
empty(pipe)?;
Ok(registry.deliver(Set(ARRIVED.swap(0, Ordering::SeqCst))))
}))?;
if let Some(panic) = wake(delivered.into_iter().flatten()) {
panic::resume_unwind(panic);
}
Poll::Ready(Ok(()))
}
}
pub(super) fn arrived() -> Set {
let Some(pipe) = PIPE.get() else {
return Set::EMPTY;
};
let _ = empty(&pipe.read);
Set(ARRIVED.swap(0, Ordering::SeqCst))
}
#[cfg(test)]
pub(crate) fn fill_pipe() -> io::Result<()> {
let pipe = Pipe::get()?;
let page = [0; 4096];
while write(&pipe.write, &page).is_ok() {}
while write(&pipe.write, &page[..1]).is_ok() {}
Ok(())
}
pub(super) struct Handlers {
previous: [Option<libc::sigaction>; Signal::ALL.len()],
}
impl Handlers {
pub(super) const fn new() -> Self {
Self {
previous: [const { None }; Signal::ALL.len()],
}
}
pub(super) fn take(&mut self, signal: Signal) -> io::Result<()> {
let mut action: libc::sigaction = unsafe { mem::zeroed() };
action.sa_sigaction = handler();
action.sa_flags = libc::SA_RESTART;
unsafe { libc::sigemptyset(&mut action.sa_mask) };
let mut previous = MaybeUninit::uninit();
if unsafe { libc::sigaction(raw(signal), &action, previous.as_mut_ptr()) } != 0 {
return Err(io::Error::last_os_error());
}
self.previous[signal as usize] = Some(unsafe { previous.assume_init() });
Ok(())
}
pub(super) fn give_back(&mut self, signal: Signal) {
let Some(previous) = self.previous[signal as usize].take() else {
return;
};
if let Err(e) = restore(raw(signal), &previous) {
error!("Failed to put back the handler of {:?}: {}", signal, e);
}
}
}
fn restore(raw: c_int, previous: &libc::sigaction) -> io::Result<()> {
let mut current = MaybeUninit::<libc::sigaction>::uninit();
if unsafe { libc::sigaction(raw, ptr::null(), current.as_mut_ptr()) } != 0 {
return Err(io::Error::last_os_error());
}
let current = unsafe { current.assume_init() };
if current.sa_sigaction != handler() {
return Ok(());
}
if unsafe { libc::sigaction(raw, previous, ptr::null_mut()) } != 0 {
return Err(io::Error::last_os_error());
}
Ok(())
}
fn handler() -> libc::sighandler_t {
handle as extern "C" fn(c_int) as libc::sighandler_t
}
pub(crate) extern "C" fn handle(raw: c_int) {
let Some(signal) = from_raw(raw) else {
return;
};
if ARRIVED.fetch_or(Set::from(signal).0, Ordering::SeqCst) != 0 {
return;
}
let Some(pipe) = PIPE.get() else {
return;
};
let errno = errno::errno();
let _ = write(&pipe.write, &[0]);
errno::set_errno(errno);
}
fn empty(pipe: &OwnedFd) -> io::Result<()> {
let mut bytes = [0; 64];
let mut filled = read(pipe, &mut bytes)?;
while filled == bytes.len() {
match read(pipe, &mut bytes) {
Ok(more) => filled = more,
Err(_) => break,
}
}
Ok(())
}
fn raw(signal: Signal) -> c_int {
match signal {
Signal::Interrupt => libc::SIGINT,
Signal::Terminate => libc::SIGTERM,
Signal::Hangup => libc::SIGHUP,
Signal::Quit => libc::SIGQUIT,
Signal::User1 => libc::SIGUSR1,
Signal::User2 => libc::SIGUSR2,
Signal::Child => libc::SIGCHLD,
Signal::WindowChange => libc::SIGWINCH,
}
}
fn from_raw(raw: c_int) -> Option<Signal> {
Signal::ALL
.iter()
.copied()
.find(|signal| self::raw(*signal) == raw)
}
static ARRIVED: AtomicU32 = AtomicU32::new(0);
static PIPE: OnceLock<Pipe> = OnceLock::new();
struct Pipe {
read: OwnedFd,
write: OwnedFd,
}
impl Pipe {
fn get() -> io::Result<&'static Self> {
if let Some(pipe) = PIPE.get() {
return Ok(pipe);
}
let (read, write) = pipe()?;
Ok(PIPE.get_or_init(|| Self { read, write }))
}
}