use std::mem::{self, ManuallyDrop, MaybeUninit};
use std::os::unix::process::ExitStatusExt;
use std::pin::Pin;
use std::process::{Child, ExitStatus};
use std::task::{self, Poll};
use std::{fmt, io, ptr};
use crate::op::{FdOp, OpState, fd_operation, operation};
use crate::{AsyncFd, SubmissionQueue, man_link, new_flag, sys, syscall};
#[cfg(any(target_os = "android", target_os = "linux"))]
pub use crate::sys::process::ToDirect;
pub fn wait_on(sq: SubmissionQueue, process: &Child) -> WaitId {
wait(sq, WaitOn::Process(process.id()))
}
#[doc = man_link!(waitid(2))]
#[doc(alias = "waitid")]
pub fn wait(sq: SubmissionQueue, wait: WaitOn) -> WaitId {
let info = unsafe { mem::zeroed() };
WaitId::new(sq, info, (wait, WaitOption(0)))
}
#[doc(alias = "idtype")]
#[doc(alias = "idtype_t")]
#[derive(Copy, Clone, Debug)]
pub enum WaitOn {
#[doc(alias = "P_PID")]
Process(u32),
#[doc(alias = "P_PGID")]
#[cfg(any(target_os = "android", target_os = "linux"))]
Group(u32),
#[doc(alias = "P_ALL")]
#[cfg(any(target_os = "android", target_os = "linux"))]
All,
}
new_flag!(
pub struct WaitOption(u32) {
UNTRACED = libc::WUNTRACED,
STOPPED = libc::WSTOPPED,
EXITED = libc::WEXITED,
CONTINUED = libc::WCONTINUED,
NO_WAIT = libc::WNOWAIT,
}
);
#[repr(transparent)]
pub struct WaitInfo(pub(crate) libc::siginfo_t);
impl WaitInfo {
pub fn pid(&self) -> i32 {
unsafe { self.0.si_pid() }
}
pub fn real_user_id(&self) -> u32 {
unsafe { self.0.si_uid() }
}
pub fn signal(&self) -> Signal {
Signal(self.0.si_signo)
}
pub fn status(&self) -> ExitStatus {
unsafe { ExitStatus::from_raw(self.0.si_status()) }
}
pub fn code(&self) -> ChildStatus {
ChildStatus(self.0.si_code)
}
}
unsafe impl Send for WaitInfo {}
unsafe impl Sync for WaitInfo {}
impl fmt::Debug for WaitInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("WaitInfo")
.field("pid", &self.pid())
.field("real_user_id", &self.real_user_id())
.field("signal", &self.signal())
.field("status", &self.status())
.field("code", &self.code())
.finish()
}
}
new_flag!(
pub struct ChildStatus(i32) {
EXITED = libc::CLD_EXITED,
KILLED = libc::CLD_KILLED,
DUMPED = libc::CLD_DUMPED,
STOPPED = libc::CLD_STOPPED,
TRAPPED = libc::CLD_TRAPPED,
CONTINUED = libc::CLD_CONTINUED,
}
);
operation!(
pub struct WaitId(sys::process::WaitIdOp) -> io::Result<WaitInfo>;
);
impl WaitId {
pub fn flags(mut self, options: WaitOption) -> Self {
if let Some((_, o)) = self.state.args_mut() {
*o = options;
}
self
}
pub fn waiting_on(&self) -> WaitOn {
self.state.args().0
}
}
pub struct Signals {
pub(crate) fd: AsyncFd,
pub(crate) signals: SignalSet,
}
impl Signals {
pub fn from_set(sq: SubmissionQueue, signals: SignalSet) -> io::Result<Signals> {
log::trace!(signals:?; "setting up signal handling");
Signals::new(sq, signals) }
pub fn from_signals<I>(sq: SubmissionQueue, signals: I) -> io::Result<Signals>
where
I: IntoIterator<Item = Signal>,
{
let mut set = SignalSet::empty()?;
for signal in signals {
set.add(signal)?;
}
Signals::from_set(sq, set)
}
pub fn for_all_signals(sq: SubmissionQueue) -> io::Result<Signals> {
let mut set = SignalSet::all()?;
for signal in [Signal::STOP, Signal::KILL] {
syscall!(sigdelset(&raw mut set.0, signal.0))?;
}
Signals::from_set(sq, set)
}
pub fn receive<'fd>(&'fd self) -> ReceiveSignal<'fd> {
ReceiveSignal::new(&self.fd, MaybeUninit::uninit(), ())
}
pub fn receive_signals(self) -> ReceiveSignals {
ReceiveSignals {
signals: self,
state: <sys::process::ReceiveSignalOp as FdOp>::State::new(MaybeUninit::uninit(), ()),
}
}
pub fn set(&self) -> &SignalSet {
&self.signals
}
}
impl fmt::Debug for Signals {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Signals")
.field("fd", &self.fd)
.field("signals", &self.signals)
.finish()
}
}
fd_operation!(
pub struct ReceiveSignal(sys::process::ReceiveSignalOp) -> io::Result<SignalInfo>;
);
#[must_use = "`AsyncIterator`s do nothing unless polled"]
#[derive(Debug)]
pub struct ReceiveSignals {
signals: Signals,
state: <sys::process::ReceiveSignalOp as FdOp>::State,
}
impl ReceiveSignals {
pub fn poll_next(
self: Pin<&mut Self>,
ctx: &mut task::Context<'_>,
) -> Poll<Option<io::Result<SignalInfo>>> {
let this = unsafe { Pin::get_unchecked_mut(self) };
let result = sys::process::ReceiveSignalOp::poll(&mut this.state, ctx, &this.signals.fd);
match result {
Poll::Ready(Ok(info)) => {
this.state.reset(MaybeUninit::uninit(), ());
Poll::Ready(Some(Ok(info)))
}
Poll::Ready(Err(err)) => Poll::Ready(Some(Err(err))),
Poll::Pending => Poll::Pending,
}
}
pub fn into_inner(self) -> Signals {
let mut this = ManuallyDrop::new(self);
let ReceiveSignals { signals, state } = &mut *this;
unsafe {
OpState::drop(state, signals.fd.sq());
ptr::drop_in_place(state);
}
unsafe { ptr::read(signals) }
}
pub fn set(&self) -> &SignalSet {
self.signals.set()
}
}
#[cfg(feature = "nightly")]
impl std::async_iter::AsyncIterator for ReceiveSignals {
type Item = io::Result<SignalInfo>;
fn poll_next(self: Pin<&mut Self>, ctx: &mut task::Context<'_>) -> Poll<Option<Self::Item>> {
self.poll_next(ctx)
}
}
impl Drop for ReceiveSignals {
fn drop(&mut self) {
unsafe { OpState::drop(&mut self.state, self.signals.fd.sq()) }
}
}
#[repr(transparent)]
pub struct SignalInfo(pub(crate) crate::sys::process::SignalInfo);
impl SignalInfo {
pub fn signal(&self) -> Signal {
crate::sys::process::signal(&self.0)
}
#[cfg(any(target_os = "android", target_os = "linux"))]
pub fn pid(&self) -> u32 {
crate::sys::process::pid(&self.0)
}
#[cfg(any(target_os = "android", target_os = "linux"))]
pub fn real_user_id(&self) -> u32 {
crate::sys::process::real_user_id(&self.0)
}
}
impl fmt::Debug for SignalInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut f = f.debug_struct("SignalInfo");
f.field("signal", &self.signal());
#[cfg(any(target_os = "android", target_os = "linux"))]
f.field("pid", &self.pid());
#[cfg(any(target_os = "android", target_os = "linux"))]
f.field("real_user_id", &self.real_user_id());
f.finish()
}
}
#[repr(transparent)]
pub struct SignalSet(pub(crate) libc::sigset_t);
impl SignalSet {
#[doc(alias = "sigemptyset")]
pub fn empty() -> io::Result<SignalSet> {
let mut set: MaybeUninit<libc::sigset_t> = MaybeUninit::uninit();
syscall!(sigemptyset(set.as_mut_ptr()))?;
Ok(SignalSet(unsafe { set.assume_init() }))
}
#[doc(alias = "sigfillset")]
pub fn all() -> io::Result<SignalSet> {
let mut set: MaybeUninit<libc::sigset_t> = MaybeUninit::uninit();
syscall!(sigfillset(set.as_mut_ptr()))?;
Ok(SignalSet(unsafe { set.assume_init() }))
}
#[doc(alias = "sigismember")]
pub fn contains(&self, signal: Signal) -> bool {
unsafe { libc::sigismember(&raw const self.0, signal.0) == 1 }
}
#[doc(alias = "sigaddset")]
pub fn add(&mut self, signal: Signal) -> io::Result<()> {
syscall!(sigaddset(&raw mut self.0, signal.0)).map(|_| ())
}
}
impl fmt::Debug for SignalSet {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let signals = Signal::ALL_VALUES
.iter()
.filter(|signal| self.contains(**signal));
f.debug_set().entries(signals).finish()
}
}
new_flag!(
#[doc = man_link!(signal(7))]
pub struct Signal(i32) {
HUP = libc::SIGHUP,
INTERRUPT = libc::SIGINT,
QUIT = libc::SIGQUIT,
ILLEGAL = libc::SIGILL,
TRAP = libc::SIGTRAP,
ABORT = libc::SIGABRT,
IOT = libc::SIGIOT,
KILL = libc::SIGKILL,
BUS = libc::SIGBUS,
FP_ERROR = libc::SIGFPE,
USER1 = libc::SIGUSR1,
USER2 = libc::SIGUSR2,
SEG_VAULT = libc::SIGSEGV,
PIPE = libc::SIGPIPE,
ALARM = libc::SIGALRM,
TERMINATION = libc::SIGTERM,
CHILD = libc::SIGCHLD,
CONTINUE = libc::SIGCONT,
STOP = libc::SIGSTOP,
TERM_STOP = libc::SIGTSTP,
TTY_IN = libc::SIGTTIN,
TTY_OUT = libc::SIGTTOU,
URGENT = libc::SIGURG,
EXCEEDED_CPU = libc::SIGXCPU,
EXCEEDED_FILE_SIZE = libc::SIGXFSZ,
VIRTUAL_ALARM = libc::SIGVTALRM,
PROFILE_ALARM = libc::SIGPROF,
WINDOW_RESIZE = libc::SIGWINCH,
IO = libc::SIGIO,
#[cfg(any(target_os = "android", target_os = "linux"))]
POLL = libc::SIGPOLL,
#[cfg(any(target_os = "android", target_os = "linux"))]
PWR = libc::SIGPWR,
SYS = libc::SIGSYS,
}
);
impl Signal {
pub const fn should_exit(self) -> bool {
matches!(self, Signal::INTERRUPT | Signal::TERMINATION | Signal::QUIT)
}
}
impl fmt::Display for Signal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match *self {
Signal::HUP => "hangup",
Signal::INTERRUPT => "interrupt",
Signal::QUIT => "quit",
Signal::ILLEGAL => "illegal",
Signal::TRAP => "trap",
Signal::ABORT => "abort",
#[allow(unreachable_patterns)] Signal::IOT => "iot",
Signal::KILL => "kill",
Signal::BUS => "bus",
Signal::FP_ERROR => "floating-point-error",
Signal::USER1 => "user-1",
Signal::USER2 => "user-2",
Signal::SEG_VAULT => "segfault",
Signal::PIPE => "pipe",
Signal::ALARM => "alarm",
Signal::TERMINATION => "termination",
Signal::CHILD => "child",
Signal::CONTINUE => "continue",
Signal::STOP => "stop",
Signal::TERM_STOP => "terminal stop",
Signal::TTY_IN => "terminal-input",
Signal::TTY_OUT => "terminal-output",
Signal::URGENT => "urgent",
Signal::EXCEEDED_CPU => "exceeded-cpu",
Signal::EXCEEDED_FILE_SIZE => "excess-file-size",
Signal::VIRTUAL_ALARM => "virtual-alarm",
Signal::PROFILE_ALARM => "profile-alarm",
Signal::WINDOW_RESIZE => "window-change",
Signal::IO => "I/O",
#[cfg(any(target_os = "android", target_os = "linux"))]
#[allow(unreachable_patterns)] Signal::POLL => "poll",
#[cfg(any(target_os = "android", target_os = "linux"))]
Signal::PWR => "power-failure",
Signal::SYS => "bad-syscall",
_ => return write!(f, "Signal({})", self.0),
})
}
}
#[derive(Copy, Clone, Debug)]
pub enum To {
Process(u32),
AllSameGroup,
AllInGroup(u32),
All,
}
impl To {
pub fn this_process() -> To {
To::Process(std::process::id())
}
pub fn child(child: &std::process::Child) -> To {
To::Process(child.id())
}
}
pub fn send_signal(to: To, signal: Signal) -> io::Result<()> {
kill(to, signal.0)
}
pub fn send_signal_check(to: To) -> io::Result<()> {
kill(to, 0)
}
fn kill(to: To, signal: libc::c_int) -> io::Result<()> {
let pid = match to {
To::Process(pid) => pid.cast_signed(),
To::AllSameGroup => 0,
To::AllInGroup(id) => -(id.cast_signed()),
To::All => -1,
};
syscall!(kill(pid, signal))?;
Ok(())
}