brush_core/sys/unix/
signal.rs

1//! Signal processing utilities
2
3use crate::{error, sys, traps};
4
5pub(crate) use nix::sys::signal::Signal;
6
7pub(crate) fn continue_process(pid: sys::process::ProcessId) -> Result<(), error::Error> {
8    nix::sys::signal::kill(nix::unistd::Pid::from_raw(pid), nix::sys::signal::SIGCONT)
9        .map_err(|_errno| error::ErrorKind::FailedToSendSignal)?;
10    Ok(())
11}
12
13/// Sends a signal to a specific process.
14///
15/// # Arguments
16/// * `pid` - The process ID to send the signal to
17/// * `signal` - The signal to send (must be a real signal, not a trap signal)
18pub fn kill_process(
19    pid: sys::process::ProcessId,
20    signal: traps::TrapSignal,
21) -> Result<(), error::Error> {
22    let translated_signal = match signal {
23        traps::TrapSignal::Signal(signal) => signal,
24        traps::TrapSignal::Debug
25        | traps::TrapSignal::Err
26        | traps::TrapSignal::Exit
27        | traps::TrapSignal::Return => {
28            return Err(error::ErrorKind::InvalidSignal(signal.to_string()).into());
29        }
30    };
31
32    nix::sys::signal::kill(nix::unistd::Pid::from_raw(pid), translated_signal)
33        .map_err(|_errno| error::ErrorKind::FailedToSendSignal)?;
34
35    Ok(())
36}
37
38pub(crate) fn lead_new_process_group() -> Result<(), error::Error> {
39    nix::unistd::setpgid(nix::unistd::Pid::from_raw(0), nix::unistd::Pid::from_raw(0))?;
40    Ok(())
41}
42
43pub(crate) fn tstp_signal_listener() -> Result<tokio::signal::unix::Signal, error::Error> {
44    let signal = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::from_raw(
45        nix::libc::SIGTSTP,
46    ))?;
47    Ok(signal)
48}
49
50pub(crate) fn chld_signal_listener() -> Result<tokio::signal::unix::Signal, error::Error> {
51    let signal = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::child())?;
52    Ok(signal)
53}
54
55pub(crate) use tokio::signal::ctrl_c as await_ctrl_c;
56
57pub(crate) fn mask_sigttou() -> Result<(), error::Error> {
58    let ignore = nix::sys::signal::SigAction::new(
59        nix::sys::signal::SigHandler::SigIgn,
60        nix::sys::signal::SaFlags::empty(),
61        nix::sys::signal::SigSet::empty(),
62    );
63
64    // SAFETY:
65    // Setting the signal action should be safe here. The unsafe concerns
66    // for calling `sigaction` are primarily around ensuring that any provided
67    // signal handler functions are only performing operations that are
68    // safe to do in a signal handler context. Here we are not providing
69    // a custom handler, just asking the OS to ignore the signal.
70    unsafe { nix::sys::signal::sigaction(nix::sys::signal::Signal::SIGTTOU, &ignore) }?;
71
72    Ok(())
73}
74
75pub(crate) fn poll_for_stopped_children() -> Result<bool, error::Error> {
76    let mut found_stopped = false;
77
78    loop {
79        let wait_status = waitid_all(
80            nix::sys::wait::WaitPidFlag::WUNTRACED | nix::sys::wait::WaitPidFlag::WNOHANG,
81        );
82        match wait_status {
83            Ok(nix::sys::wait::WaitStatus::Stopped(_stopped_pid, _signal)) => {
84                found_stopped = true;
85            }
86            Ok(_) => break,
87            Err(nix::errno::Errno::ECHILD) => break,
88            Err(e) => return Err(e.into()),
89        }
90    }
91
92    Ok(found_stopped)
93}
94
95#[cfg(not(target_os = "macos"))]
96fn waitid_all(
97    flags: nix::sys::wait::WaitPidFlag,
98) -> Result<nix::sys::wait::WaitStatus, nix::errno::Errno> {
99    nix::sys::wait::waitid(nix::sys::wait::Id::All, flags)
100}
101
102//
103// N.B. These functions were mostly copied from nix::sys::wait (https://github.com/nix-rust/nix, MIT license)
104// to enable use of the `waitid` call on macOS. Ideally nix would expose it on macOS and we would
105// remove this code.
106//
107
108#[cfg(target_os = "macos")]
109fn waitid_all(
110    flags: nix::sys::wait::WaitPidFlag,
111) -> Result<nix::sys::wait::WaitStatus, nix::errno::Errno> {
112    // SAFETY:
113    // Code copied from nix::sys::wait implementation of waitid for other platforms.
114    let siginfo = unsafe {
115        // Memory is zeroed rather than uninitialized, as not all platforms
116        // initialize the memory in the StillAlive case
117        let mut siginfo: nix::libc::siginfo_t = std::mem::zeroed();
118        nix::errno::Errno::result(nix::libc::waitid(
119            nix::libc::P_ALL,
120            0,
121            &raw mut siginfo,
122            flags.bits(),
123        ))?;
124        siginfo
125    };
126
127    siginfo_to_wait_status(siginfo)
128}
129
130#[cfg(target_os = "macos")]
131fn siginfo_to_wait_status(
132    siginfo: nix::libc::siginfo_t,
133) -> Result<nix::sys::wait::WaitStatus, nix::errno::Errno> {
134    // SAFETY:
135    // Code copied from nix::sys::wait implementation of waitid for other platforms.
136    let si_pid = unsafe { siginfo.si_pid() };
137    if si_pid == 0 {
138        return Ok(nix::sys::wait::WaitStatus::StillAlive);
139    }
140
141    let pid = nix::unistd::Pid::from_raw(si_pid);
142
143    // SAFETY:
144    // Code copied from nix::sys::wait implementation of waitid for other platforms.
145    let si_status = unsafe { siginfo.si_status() };
146
147    let status = match siginfo.si_code {
148        nix::libc::CLD_EXITED => nix::sys::wait::WaitStatus::Exited(pid, si_status),
149        nix::libc::CLD_KILLED | nix::libc::CLD_DUMPED => nix::sys::wait::WaitStatus::Signaled(
150            pid,
151            nix::sys::signal::Signal::try_from(si_status)?,
152            siginfo.si_code == nix::libc::CLD_DUMPED,
153        ),
154        nix::libc::CLD_STOPPED => {
155            nix::sys::wait::WaitStatus::Stopped(pid, nix::sys::signal::Signal::try_from(si_status)?)
156        }
157        nix::libc::CLD_CONTINUED => nix::sys::wait::WaitStatus::Continued(pid),
158        _ => return Err(nix::errno::Errno::EINVAL),
159    };
160
161    Ok(status)
162}