use core::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::atomic::{AtomicBool, AtomicI32, AtomicU8, Ordering};
use std::sync::{Arc, OnceLock};
use super::{Listener, SLOTS};
use crate::io::Handle;
use crate::rt::shard::Shared;
#[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() }
}
static SERVICE_PUMP: AtomicBool = AtomicBool::new(false);
enum Place {
Shard(Arc<Shared>),
Service,
}
impl Place {
fn flag(&self) -> &AtomicBool {
match self {
Place::Shard(shared) => &shared.signal_pump,
Place::Service => &SERVICE_PUMP,
}
}
}
struct Pumping(Place);
impl Drop for Pumping {
fn drop(&mut self) {
self.0.flag().store(false, Ordering::SeqCst);
super::wake_every_listener();
}
}
#[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 place = crate::rt::context::current(|shard| match shard {
Some(shard) => (!shard.shared.signal_pump.swap(true, Ordering::SeqCst))
.then(|| Place::Shard(Arc::clone(&shard.shared))),
None => (!SERVICE_PUMP.swap(true, Ordering::SeqCst)).then_some(Place::Service),
});
let Some(place) = place else {
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, Pumping(place))),
Err(_) => place.flag().store(false, Ordering::SeqCst),
}
}
async fn pump(wake: Handle<BorrowedFd<'static>>, pumping: Pumping) {
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;
}
}
drop(pumping);
}