use core::cell::{Cell, UnsafeCell};
use core::ffi::{c_int, c_void};
use core::mem::MaybeUninit;
use core::pin::Pin;
use core::task::{Context, Poll};
use std::io;
use std::os::fd::{BorrowedFd, RawFd};
use std::sync::OnceLock;
use std::sync::atomic::{AtomicBool, AtomicI32, AtomicU8, Ordering};
use super::{Listener, SLOTS};
use crate::io::Handle;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct SignalKind(c_int);
impl SignalKind {
pub const fn from_raw(signum: c_int) -> Self {
Self(signum)
}
pub const fn as_raw(self) -> c_int {
self.0
}
pub const fn alarm() -> Self {
Self(libc::SIGALRM)
}
pub const fn child() -> Self {
Self(libc::SIGCHLD)
}
pub const fn hangup() -> Self {
Self(libc::SIGHUP)
}
pub const fn interrupt() -> Self {
Self(libc::SIGINT)
}
pub const fn pipe() -> Self {
Self(libc::SIGPIPE)
}
pub const fn quit() -> Self {
Self(libc::SIGQUIT)
}
pub const fn terminate() -> Self {
Self(libc::SIGTERM)
}
pub const fn user_defined1() -> Self {
Self(libc::SIGUSR1)
}
pub const fn user_defined2() -> Self {
Self(libc::SIGUSR2)
}
pub const fn window_change() -> Self {
Self(libc::SIGWINCH)
}
}
impl From<c_int> for SignalKind {
fn from(signum: c_int) -> Self {
Self(signum)
}
}
const FORBIDDEN: [c_int; 5] = [
libc::SIGILL,
libc::SIGFPE,
libc::SIGKILL,
libc::SIGSEGV,
libc::SIGSTOP,
];
#[derive(Debug)]
pub struct Signal {
listener: Listener,
}
pub fn signal(kind: SignalKind) -> io::Result<Signal> {
let signum = kind.0;
if signum <= 0 || signum as usize >= SLOTS {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"not a signal number",
));
}
if FORBIDDEN.contains(&signum) {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"this signal cannot be listened to",
));
}
install(signum)?;
Ok(Signal {
listener: Listener::new(signum as usize),
})
}
impl Signal {
pub async fn recv(&mut self) -> Option<()> {
core::future::poll_fn(|cx| self.poll_recv(cx)).await
}
pub fn poll_recv(&mut self, cx: &mut Context<'_>) -> Poll<Option<()>> {
self.listener.poll_recv(cx, ensure_pump)
}
}
impl futures_core::Stream for Signal {
type Item = ();
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<()>> {
self.get_mut().poll_recv(cx)
}
}
struct Wake {
write: RawFd,
read: RawFd,
len: usize,
}
static WAKE_WRITE: AtomicI32 = AtomicI32::new(-1);
static WAKE_LEN: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(1);
fn wake() -> io::Result<&'static Wake> {
static WAKE: OnceLock<Result<Wake, i32>> = OnceLock::new();
let made = WAKE.get_or_init(|| {
let wake = make_wake().map_err(|err| err.raw_os_error().unwrap_or(libc::EIO))?;
WAKE_LEN.store(wake.len, Ordering::SeqCst);
WAKE_WRITE.store(wake.write, Ordering::SeqCst);
Ok(wake)
});
made.as_ref()
.map_err(|&errno| io::Error::from_raw_os_error(errno))
}
#[cfg(any(target_os = "linux", target_os = "android"))]
fn make_wake() -> io::Result<Wake> {
let fd = unsafe { libc::eventfd(0, libc::EFD_NONBLOCK | libc::EFD_CLOEXEC) };
if fd < 0 {
return Err(io::Error::last_os_error());
}
Ok(Wake {
write: fd,
read: fd,
len: size_of::<u64>(),
})
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
fn make_wake() -> io::Result<Wake> {
let mut fds = [-1; 2];
if unsafe { libc::pipe(fds.as_mut_ptr()) } != 0 {
return Err(io::Error::last_os_error());
}
for fd in fds {
let set = unsafe {
libc::fcntl(fd, libc::F_SETFD, libc::FD_CLOEXEC) == 0
&& libc::fcntl(fd, libc::F_SETFL, libc::O_NONBLOCK) == 0
};
if !set {
let err = io::Error::last_os_error();
unsafe {
libc::close(fds[0]);
libc::close(fds[1]);
}
return Err(err);
}
}
Ok(Wake {
write: fds[1],
read: fds[0],
len: 1,
})
}
const IDLE: u8 = 0;
const INSTALLING: u8 = 1;
const INSTALLED: u8 = 2;
struct Handler {
state: AtomicU8,
previous: UnsafeCell<MaybeUninit<libc::sigaction>>,
chains: AtomicBool,
}
unsafe impl Sync for Handler {}
static HANDLERS: [Handler; SLOTS] = [const {
Handler {
state: AtomicU8::new(IDLE),
previous: UnsafeCell::new(MaybeUninit::uninit()),
chains: AtomicBool::new(false),
}
}; SLOTS];
fn install(signum: c_int) -> io::Result<()> {
let handler = &HANDLERS[signum as usize];
loop {
match handler
.state
.compare_exchange(IDLE, INSTALLING, Ordering::AcqRel, Ordering::Acquire)
{
Ok(_) => break,
Err(INSTALLED) => return Ok(()),
Err(_) => std::thread::yield_now(),
}
}
let installed = wake().and_then(|_| {
let mut previous: libc::sigaction = unsafe { core::mem::zeroed() };
if unsafe { libc::sigaction(signum, core::ptr::null(), &mut previous) } != 0 {
return Err(io::Error::last_os_error());
}
if !handler.chains.load(Ordering::Acquire) {
unsafe { (*handler.previous.get()).write(previous) };
handler.chains.store(true, Ordering::Release);
}
let mut action: libc::sigaction = unsafe { core::mem::zeroed() };
action.sa_sigaction = on_signal as *const () as libc::sighandler_t;
action.sa_flags = libc::SA_SIGINFO | libc::SA_RESTART | libc::SA_ONSTACK;
unsafe {
libc::sigemptyset(&mut action.sa_mask);
if libc::sigaction(signum, &action, core::ptr::null_mut()) != 0 {
return Err(io::Error::last_os_error());
}
}
Ok(())
});
let state = if installed.is_ok() { INSTALLED } else { IDLE };
handler.state.store(state, Ordering::Release);
installed
}
extern "C" fn on_signal(signum: c_int, info: *mut libc::siginfo_t, context: *mut c_void) {
let errno = unsafe { *errno_location() };
super::delivered(signum as usize);
let fd = WAKE_WRITE.load(Ordering::SeqCst);
if fd >= 0 {
let one = 1u64;
unsafe {
libc::write(
fd,
(&one as *const u64).cast(),
WAKE_LEN.load(Ordering::SeqCst),
)
};
}
if let Some(handler) = HANDLERS.get(signum as usize)
&& handler.chains.load(Ordering::Acquire)
{
let previous = unsafe { (*handler.previous.get()).assume_init_ref() };
let action = previous.sa_sigaction;
if action != libc::SIG_DFL && action != libc::SIG_IGN {
if previous.sa_flags & libc::SA_SIGINFO != 0 {
let chained: extern "C" fn(c_int, *mut libc::siginfo_t, *mut c_void) =
unsafe { core::mem::transmute(action) };
chained(signum, info, context);
} else {
let chained: extern "C" fn(c_int) = unsafe { core::mem::transmute(action) };
chained(signum);
}
}
}
unsafe { *errno_location() = errno };
}
#[cfg(target_os = "linux")]
fn errno_location() -> *mut c_int {
unsafe { libc::__errno_location() }
}
#[cfg(any(target_os = "android", target_os = "netbsd", target_os = "openbsd"))]
fn errno_location() -> *mut c_int {
unsafe { libc::__errno() }
}
#[cfg(any(
target_os = "macos",
target_os = "ios",
target_os = "freebsd",
target_os = "dragonfly"
))]
fn errno_location() -> *mut c_int {
unsafe { libc::__error() }
}
#[cfg(target_os = "illumos")]
fn errno_location() -> *mut c_int {
unsafe { libc::___errno() }
}
thread_local! {
static PUMPING: Cell<bool> = const { Cell::new(false) };
}
static SERVICE_PUMP: AtomicBool = AtomicBool::new(false);
#[cfg(any(target_os = "linux", target_os = "android"))]
pub(crate) fn listen_raw(signum: c_int) -> io::Result<()> {
install(signum)
}
pub(crate) fn ensure_pump() {
let on_shard = crate::rt::context::current(|shard| shard.is_some());
let started = if on_shard {
PUMPING.try_with(|p| p.replace(true)).unwrap_or(true)
} else {
SERVICE_PUMP.swap(true, Ordering::SeqCst)
};
if started {
return;
}
let made = wake().and_then(|wake| {
Handle::new(unsafe { BorrowedFd::borrow_raw(wake.read) })
});
match made {
Ok(handle) => crate::rt::runtime::spawn_service(pump(handle, on_shard)),
Err(_) => pump_ended(on_shard),
}
}
fn pump_ended(on_shard: bool) {
if on_shard {
let _ = PUMPING.try_with(|p| p.set(false));
} else {
SERVICE_PUMP.store(false, Ordering::SeqCst);
}
}
async fn pump(wake: Handle<BorrowedFd<'static>>, on_shard: bool) {
let mut buf = [0u8; 64];
loop {
let read = core::future::poll_fn(|cx| wake.source().poll_read(cx, &mut buf, false)).await;
super::dispatch();
if read.is_err() {
break;
}
}
pump_ended(on_shard);
}