nix/sys/
wait.rs

1//! Wait for a process to change status
2use crate::errno::Errno;
3use crate::sys::signal::Signal;
4use crate::unistd::Pid;
5use crate::Result;
6use cfg_if::cfg_if;
7use libc::{self, c_int};
8use std::convert::TryFrom;
9
10libc_bitflags!(
11    /// Controls the behavior of [`waitpid`].
12    pub struct WaitPidFlag: c_int {
13        /// Do not block when there are no processes wishing to report status.
14        WNOHANG;
15        /// Report the status of selected processes which are stopped due to a
16        /// [`SIGTTIN`](crate::sys::signal::Signal::SIGTTIN),
17        /// [`SIGTTOU`](crate::sys::signal::Signal::SIGTTOU),
18        /// [`SIGTSTP`](crate::sys::signal::Signal::SIGTSTP), or
19        /// [`SIGSTOP`](crate::sys::signal::Signal::SIGSTOP) signal.
20        WUNTRACED;
21        /// Report the status of selected processes which have terminated.
22        #[cfg(any(target_os = "android",
23                  target_os = "freebsd",
24                  target_os = "haiku",
25                  target_os = "ios",
26                  target_os = "linux",
27                  target_os = "redox",
28                  target_os = "macos",
29                  target_os = "netbsd"))]
30        WEXITED;
31        /// Report the status of selected processes that have continued from a
32        /// job control stop by receiving a
33        /// [`SIGCONT`](crate::sys::signal::Signal::SIGCONT) signal.
34        WCONTINUED;
35        /// An alias for WUNTRACED.
36        #[cfg(any(target_os = "android",
37                  target_os = "freebsd",
38                  target_os = "haiku",
39                  target_os = "ios",
40                  target_os = "linux",
41                  target_os = "redox",
42                  target_os = "macos",
43                  target_os = "netbsd"))]
44        WSTOPPED;
45        /// Don't reap, just poll status.
46        #[cfg(any(target_os = "android",
47                  target_os = "freebsd",
48                  target_os = "haiku",
49                  target_os = "ios",
50                  target_os = "linux",
51                  target_os = "redox",
52                  target_os = "macos",
53                  target_os = "netbsd"))]
54        WNOWAIT;
55        /// Don't wait on children of other threads in this group
56        #[cfg(any(target_os = "android", target_os = "linux", target_os = "redox"))]
57        __WNOTHREAD;
58        /// Wait on all children, regardless of type
59        #[cfg(any(target_os = "android", target_os = "linux", target_os = "redox"))]
60        __WALL;
61        /// Wait for "clone" children only.
62        #[cfg(any(target_os = "android", target_os = "linux", target_os = "redox"))]
63        __WCLONE;
64    }
65);
66
67/// Possible return values from `wait()` or `waitpid()`.
68///
69/// Each status (other than `StillAlive`) describes a state transition
70/// in a child process `Pid`, such as the process exiting or stopping,
71/// plus additional data about the transition if any.
72///
73/// Note that there are two Linux-specific enum variants, `PtraceEvent`
74/// and `PtraceSyscall`. Portable code should avoid exhaustively
75/// matching on `WaitStatus`.
76#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
77pub enum WaitStatus {
78    /// The process exited normally (as with `exit()` or returning from
79    /// `main`) with the given exit code. This case matches the C macro
80    /// `WIFEXITED(status)`; the second field is `WEXITSTATUS(status)`.
81    Exited(Pid, i32),
82    /// The process was killed by the given signal. The third field
83    /// indicates whether the signal generated a core dump. This case
84    /// matches the C macro `WIFSIGNALED(status)`; the last two fields
85    /// correspond to `WTERMSIG(status)` and `WCOREDUMP(status)`.
86    Signaled(Pid, Signal, bool),
87    /// The process is alive, but was stopped by the given signal. This
88    /// is only reported if `WaitPidFlag::WUNTRACED` was passed. This
89    /// case matches the C macro `WIFSTOPPED(status)`; the second field
90    /// is `WSTOPSIG(status)`.
91    Stopped(Pid, Signal),
92    /// The traced process was stopped by a `PTRACE_EVENT_*` event. See
93    /// [`nix::sys::ptrace`] and [`ptrace`(2)] for more information. All
94    /// currently-defined events use `SIGTRAP` as the signal; the third
95    /// field is the `PTRACE_EVENT_*` value of the event.
96    ///
97    /// [`nix::sys::ptrace`]: ../ptrace/index.html
98    /// [`ptrace`(2)]: https://man7.org/linux/man-pages/man2/ptrace.2.html
99    #[cfg(any(target_os = "linux", target_os = "android"))]
100    PtraceEvent(Pid, Signal, c_int),
101    /// The traced process was stopped by execution of a system call,
102    /// and `PTRACE_O_TRACESYSGOOD` is in effect. See [`ptrace`(2)] for
103    /// more information.
104    ///
105    /// [`ptrace`(2)]: https://man7.org/linux/man-pages/man2/ptrace.2.html
106    #[cfg(any(target_os = "linux", target_os = "android"))]
107    PtraceSyscall(Pid),
108    /// The process was previously stopped but has resumed execution
109    /// after receiving a `SIGCONT` signal. This is only reported if
110    /// `WaitPidFlag::WCONTINUED` was passed. This case matches the C
111    /// macro `WIFCONTINUED(status)`.
112    Continued(Pid),
113    /// There are currently no state changes to report in any awaited
114    /// child process. This is only returned if `WaitPidFlag::WNOHANG`
115    /// was used (otherwise `wait()` or `waitpid()` would block until
116    /// there was something to report).
117    StillAlive,
118}
119
120impl WaitStatus {
121    /// Extracts the PID from the WaitStatus unless it equals StillAlive.
122    pub fn pid(&self) -> Option<Pid> {
123        use self::WaitStatus::*;
124        match *self {
125            Exited(p, _) | Signaled(p, _, _) | Stopped(p, _) | Continued(p) => Some(p),
126            StillAlive => None,
127            #[cfg(any(target_os = "android", target_os = "linux"))]
128            PtraceEvent(p, _, _) | PtraceSyscall(p) => Some(p),
129        }
130    }
131}
132
133fn exited(status: i32) -> bool {
134    libc::WIFEXITED(status)
135}
136
137fn exit_status(status: i32) -> i32 {
138    libc::WEXITSTATUS(status)
139}
140
141fn signaled(status: i32) -> bool {
142    libc::WIFSIGNALED(status)
143}
144
145fn term_signal(status: i32) -> Result<Signal> {
146    Signal::try_from(libc::WTERMSIG(status))
147}
148
149fn dumped_core(status: i32) -> bool {
150    libc::WCOREDUMP(status)
151}
152
153fn stopped(status: i32) -> bool {
154    libc::WIFSTOPPED(status)
155}
156
157fn stop_signal(status: i32) -> Result<Signal> {
158    Signal::try_from(libc::WSTOPSIG(status))
159}
160
161#[cfg(any(target_os = "android", target_os = "linux"))]
162fn syscall_stop(status: i32) -> bool {
163    // From ptrace(2), setting PTRACE_O_TRACESYSGOOD has the effect
164    // of delivering SIGTRAP | 0x80 as the signal number for syscall
165    // stops. This allows easily distinguishing syscall stops from
166    // genuine SIGTRAP signals.
167    libc::WSTOPSIG(status) == libc::SIGTRAP | 0x80
168}
169
170#[cfg(any(target_os = "android", target_os = "linux"))]
171fn stop_additional(status: i32) -> c_int {
172    (status >> 16) as c_int
173}
174
175fn continued(status: i32) -> bool {
176    libc::WIFCONTINUED(status)
177}
178
179impl WaitStatus {
180    /// Convert a raw `wstatus` as returned by `waitpid`/`wait` into a `WaitStatus`
181    ///
182    /// # Errors
183    ///
184    /// Returns an `Error` corresponding to `EINVAL` for invalid status values.
185    ///
186    /// # Examples
187    ///
188    /// Convert a `wstatus` obtained from `libc::waitpid` into a `WaitStatus`:
189    ///
190    /// ```
191    /// use nix::sys::wait::WaitStatus;
192    /// use nix::sys::signal::Signal;
193    /// let pid = nix::unistd::Pid::from_raw(1);
194    /// let status = WaitStatus::from_raw(pid, 0x0002);
195    /// assert_eq!(status, Ok(WaitStatus::Signaled(pid, Signal::SIGINT, false)));
196    /// ```
197    pub fn from_raw(pid: Pid, status: i32) -> Result<WaitStatus> {
198        Ok(if exited(status) {
199            WaitStatus::Exited(pid, exit_status(status))
200        } else if signaled(status) {
201            WaitStatus::Signaled(pid, term_signal(status)?, dumped_core(status))
202        } else if stopped(status) {
203            cfg_if! {
204                if #[cfg(any(target_os = "android", target_os = "linux"))] {
205                    fn decode_stopped(pid: Pid, status: i32) -> Result<WaitStatus> {
206                        let status_additional = stop_additional(status);
207                        Ok(if syscall_stop(status) {
208                            WaitStatus::PtraceSyscall(pid)
209                        } else if status_additional == 0 {
210                            WaitStatus::Stopped(pid, stop_signal(status)?)
211                        } else {
212                            WaitStatus::PtraceEvent(pid, stop_signal(status)?,
213                                                    stop_additional(status))
214                        })
215                    }
216                } else {
217                    fn decode_stopped(pid: Pid, status: i32) -> Result<WaitStatus> {
218                        Ok(WaitStatus::Stopped(pid, stop_signal(status)?))
219                    }
220                }
221            }
222            return decode_stopped(pid, status);
223        } else {
224            assert!(continued(status));
225            WaitStatus::Continued(pid)
226        })
227    }
228}
229
230/// Wait for a process to change status
231///
232/// See also [waitpid(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/waitpid.html)
233pub fn waitpid<P: Into<Option<Pid>>>(pid: P, options: Option<WaitPidFlag>) -> Result<WaitStatus> {
234    use self::WaitStatus::*;
235
236    let mut status: i32 = 0;
237
238    let option_bits = match options {
239        Some(bits) => bits.bits(),
240        None => 0,
241    };
242
243    let res = unsafe {
244        libc::waitpid(
245            pid.into().unwrap_or_else(|| Pid::from_raw(-1)).into(),
246            &mut status as *mut c_int,
247            option_bits,
248        )
249    };
250
251    match Errno::result(res)? {
252        0 => Ok(StillAlive),
253        res => WaitStatus::from_raw(Pid::from_raw(res), status),
254    }
255}
256
257/// Wait for any child process to change status or a signal is received.
258///
259/// See also [wait(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/wait.html)
260pub fn wait() -> Result<WaitStatus> {
261    waitpid(None, None)
262}