pub(crate) use nix::sys::signal::Signal;
use crate::{error, sys, traps};
pub(crate) fn continue_process(pid: sys::process::ProcessId) -> Result<(), error::Error> {
nix::sys::signal::kill(nix::unistd::Pid::from_raw(pid), nix::sys::signal::SIGCONT)
.map_err(|_errno| error::ErrorKind::FailedToSendSignal)?;
Ok(())
}
pub(crate) fn continue_process_group(pgid: sys::process::ProcessId) -> Result<(), error::Error> {
nix::sys::signal::killpg(nix::unistd::Pid::from_raw(pgid), nix::sys::signal::SIGCONT)
.map_err(|_errno| error::ErrorKind::FailedToSendSignal)?;
Ok(())
}
pub fn kill_process(
pid: sys::process::ProcessId,
signal: traps::TrapSignal,
) -> Result<(), error::Error> {
nix::sys::signal::kill(nix::unistd::Pid::from_raw(pid), real_signal(signal)?)
.map_err(|_errno| error::ErrorKind::FailedToSendSignal)?;
Ok(())
}
pub(crate) fn kill_process_group(
pgid: sys::process::ProcessId,
signal: traps::TrapSignal,
) -> Result<(), error::Error> {
nix::sys::signal::killpg(nix::unistd::Pid::from_raw(pgid), real_signal(signal)?)
.map_err(|_errno| error::ErrorKind::FailedToSendSignal)?;
Ok(())
}
pub(crate) const fn continue_stopped_on(signal: traps::TrapSignal) -> bool {
matches!(
signal,
traps::TrapSignal::Signal(Signal::SIGTERM | Signal::SIGHUP)
)
}
fn real_signal(signal: traps::TrapSignal) -> Result<Signal, error::Error> {
match signal {
traps::TrapSignal::Signal(signal) => Ok(signal),
traps::TrapSignal::Debug
| traps::TrapSignal::Err
| traps::TrapSignal::Exit
| traps::TrapSignal::Return => {
Err(error::ErrorKind::InvalidSignal(signal.to_string()).into())
}
}
}
pub fn check_signalable(pid: sys::process::ProcessId) -> Result<(), error::Error> {
nix::sys::signal::kill(nix::unistd::Pid::from_raw(pid), None).map_err(signal_error)?;
Ok(())
}
pub(crate) fn check_process_group_signalable(
pgid: sys::process::ProcessId,
) -> Result<(), error::Error> {
nix::sys::signal::killpg(nix::unistd::Pid::from_raw(pgid), None).map_err(signal_error)?;
Ok(())
}
const fn signal_error(errno: nix::errno::Errno) -> error::ErrorKind {
match errno {
nix::errno::Errno::ESRCH => error::ErrorKind::NoSuchProcess,
nix::errno::Errno::EPERM => error::ErrorKind::PermissionDenied,
_ => error::ErrorKind::FailedToSendSignal,
}
}
pub(crate) fn lead_new_process_group() -> Result<(), error::Error> {
nix::unistd::setpgid(nix::unistd::Pid::from_raw(0), nix::unistd::Pid::from_raw(0))?;
Ok(())
}
pub(crate) fn tstp_signal_listener() -> Result<tokio::signal::unix::Signal, error::Error> {
let signal = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::from_raw(
nix::libc::SIGTSTP,
))?;
Ok(signal)
}
pub(crate) fn chld_signal_listener() -> Result<tokio::signal::unix::Signal, error::Error> {
let signal = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::child())?;
Ok(signal)
}
pub use tokio::signal::ctrl_c as await_ctrl_c;
pub(crate) fn mask_sigttou() -> Result<(), error::Error> {
let ignore = nix::sys::signal::SigAction::new(
nix::sys::signal::SigHandler::SigIgn,
nix::sys::signal::SaFlags::empty(),
nix::sys::signal::SigSet::empty(),
);
unsafe { nix::sys::signal::sigaction(nix::sys::signal::Signal::SIGTTOU, &ignore) }?;
Ok(())
}
pub(crate) fn poll_for_stopped_children(
pid: sys::process::ProcessId,
) -> Result<bool, error::Error> {
let mut found_stopped = false;
loop {
let wait_status = waitid_for(
pid,
nix::sys::wait::WaitPidFlag::WUNTRACED | nix::sys::wait::WaitPidFlag::WNOHANG,
);
match wait_status {
Ok(nix::sys::wait::WaitStatus::Stopped(_stopped_pid, _signal)) => {
found_stopped = true;
}
Ok(_) => break,
Err(nix::errno::Errno::ECHILD) => break,
Err(e) => return Err(e.into()),
}
}
Ok(found_stopped)
}
pub(crate) fn poll_for_stopped_child(pid: sys::process::ProcessId) -> Result<bool, error::Error> {
let flags = nix::sys::wait::WaitPidFlag::WUNTRACED | nix::sys::wait::WaitPidFlag::WNOHANG;
match waitid_one(pid, flags) {
Ok(nix::sys::wait::WaitStatus::Stopped(..)) => Ok(true),
Ok(_) | Err(nix::errno::Errno::ECHILD) => Ok(false),
Err(e) => Err(e.into()),
}
}
#[cfg(not(any(target_os = "netbsd", target_os = "openbsd")))]
fn waitid_for(
pid: sys::process::ProcessId,
flags: nix::sys::wait::WaitPidFlag,
) -> Result<nix::sys::wait::WaitStatus, nix::errno::Errno> {
waitid_one(pid, flags)
}
#[cfg(any(target_os = "netbsd", target_os = "openbsd"))]
fn waitid_for(
_pid: sys::process::ProcessId,
flags: nix::sys::wait::WaitPidFlag,
) -> Result<nix::sys::wait::WaitStatus, nix::errno::Errno> {
waitid_all(flags)
}
#[cfg(not(any(target_os = "macos", target_os = "netbsd", target_os = "openbsd")))]
fn waitid_one(
pid: sys::process::ProcessId,
flags: nix::sys::wait::WaitPidFlag,
) -> Result<nix::sys::wait::WaitStatus, nix::errno::Errno> {
nix::sys::wait::waitid(
nix::sys::wait::Id::Pid(nix::unistd::Pid::from_raw(pid)),
flags,
)
}
#[cfg(any(target_os = "netbsd", target_os = "openbsd"))]
#[allow(clippy::unnecessary_wraps, reason = "matches other platforms")]
const fn waitid_one(
_pid: sys::process::ProcessId,
_flags: nix::sys::wait::WaitPidFlag,
) -> Result<nix::sys::wait::WaitStatus, nix::errno::Errno> {
Ok(nix::sys::wait::WaitStatus::StillAlive)
}
#[cfg(any(target_os = "netbsd", target_os = "openbsd"))]
fn waitid_all(
flags: nix::sys::wait::WaitPidFlag,
) -> Result<nix::sys::wait::WaitStatus, nix::errno::Errno> {
nix::sys::wait::waitpid(None, Some(flags))
}
#[cfg(target_os = "macos")]
fn waitid_one(
pid: sys::process::ProcessId,
flags: nix::sys::wait::WaitPidFlag,
) -> Result<nix::sys::wait::WaitStatus, nix::errno::Errno> {
#[expect(clippy::cast_sign_loss, reason = "process IDs are positive")]
waitid_macos(nix::libc::P_PID, pid as nix::libc::id_t, flags)
}
#[cfg(target_os = "macos")]
fn waitid_macos(
idtype: nix::libc::idtype_t,
id: nix::libc::id_t,
flags: nix::sys::wait::WaitPidFlag,
) -> Result<nix::sys::wait::WaitStatus, nix::errno::Errno> {
let mut siginfo: nix::libc::siginfo_t = unsafe { std::mem::zeroed() };
nix::errno::Errno::result(unsafe {
nix::libc::waitid(idtype, id, &raw mut siginfo, flags.bits())
})?;
siginfo_to_wait_status(siginfo)
}
#[cfg(target_os = "macos")]
fn siginfo_to_wait_status(
siginfo: nix::libc::siginfo_t,
) -> Result<nix::sys::wait::WaitStatus, nix::errno::Errno> {
let si_pid = unsafe { siginfo.si_pid() };
if si_pid == 0 {
return Ok(nix::sys::wait::WaitStatus::StillAlive);
}
let pid = nix::unistd::Pid::from_raw(si_pid);
let si_status = unsafe { siginfo.si_status() };
let status = match siginfo.si_code {
nix::libc::CLD_EXITED => nix::sys::wait::WaitStatus::Exited(pid, si_status),
nix::libc::CLD_KILLED | nix::libc::CLD_DUMPED => nix::sys::wait::WaitStatus::Signaled(
pid,
nix::sys::signal::Signal::try_from(si_status)?,
siginfo.si_code == nix::libc::CLD_DUMPED,
),
nix::libc::CLD_STOPPED => {
nix::sys::wait::WaitStatus::Stopped(pid, nix::sys::signal::Signal::try_from(si_status)?)
}
nix::libc::CLD_CONTINUED => nix::sys::wait::WaitStatus::Continued(pid),
_ => return Err(nix::errno::Errno::EINVAL),
};
Ok(status)
}