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#[cfg(any(
10    target_os = "android",
11    all(target_os = "linux", not(target_env = "uclibc")),
12))]
13use std::os::unix::io::{AsRawFd, BorrowedFd};
14
15libc_bitflags!(
16    /// Controls the behavior of [`waitpid`].
17    pub struct WaitPidFlag: c_int {
18        /// Do not block when there are no processes wishing to report status.
19        WNOHANG;
20        /// Report the status of selected processes which are stopped due to a
21        /// [`SIGTTIN`](crate::sys::signal::Signal::SIGTTIN),
22        /// [`SIGTTOU`](crate::sys::signal::Signal::SIGTTOU),
23        /// [`SIGTSTP`](crate::sys::signal::Signal::SIGTSTP), or
24        /// [`SIGSTOP`](crate::sys::signal::Signal::SIGSTOP) signal.
25        WUNTRACED;
26        /// Report the status of selected processes which have terminated.
27        #[cfg(any(linux_android,
28                  apple_targets,
29                  target_os = "freebsd",
30                  target_os = "haiku",
31                  target_os = "redox",
32                  target_os = "netbsd"))]
33        WEXITED;
34        /// Report the status of selected processes that have continued from a
35        /// job control stop by receiving a
36        /// [`SIGCONT`](crate::sys::signal::Signal::SIGCONT) signal.
37        WCONTINUED;
38        /// An alias for WUNTRACED.
39        #[cfg(any(linux_android,
40                  apple_targets,
41                  target_os = "freebsd",
42                  target_os = "haiku",
43                  target_os = "redox",
44                  target_os = "netbsd"))]
45        WSTOPPED;
46        /// Don't reap, just poll status.
47        #[cfg(any(linux_android,
48                  apple_targets,
49                  target_os = "freebsd",
50                  target_os = "haiku",
51                  target_os = "redox",
52                  target_os = "netbsd"))]
53        WNOWAIT;
54        /// Don't wait on children of other threads in this group
55        #[cfg(any(linux_android, target_os = "redox"))]
56        __WNOTHREAD;
57        /// Wait on all children, regardless of type
58        #[cfg(any(linux_android, target_os = "redox"))]
59        __WALL;
60        /// Wait for "clone" children only.
61        #[cfg(any(linux_android, target_os = "redox"))]
62        __WCLONE;
63    }
64);
65
66/// Possible return values from `wait()` or `waitpid()`.
67///
68/// Each status (other than `StillAlive`) describes a state transition
69/// in a child process `Pid`, such as the process exiting or stopping,
70/// plus additional data about the transition if any.
71///
72/// Note that there are two Linux-specific enum variants, `PtraceEvent`
73/// and `PtraceSyscall`. Portable code should avoid exhaustively
74/// matching on `WaitStatus`.
75#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
76pub enum WaitStatus {
77    /// The process exited normally (as with `exit()` or returning from
78    /// `main`) with the given exit code. This case matches the C macro
79    /// `WIFEXITED(status)`; the second field is `WEXITSTATUS(status)`.
80    Exited(Pid, i32),
81    /// The process was killed by the given signal. The third field
82    /// indicates whether the signal generated a core dump. This case
83    /// matches the C macro `WIFSIGNALED(status)`; the last two fields
84    /// correspond to `WTERMSIG(status)` and `WCOREDUMP(status)`.
85    Signaled(Pid, Signal, bool),
86    /// The process is alive, but was stopped by the given signal. This
87    /// is only reported if `WaitPidFlag::WUNTRACED` was passed. This
88    /// case matches the C macro `WIFSTOPPED(status)`; the second field
89    /// is `WSTOPSIG(status)`.
90    Stopped(Pid, Signal),
91    /// The traced process was stopped by a `PTRACE_EVENT_*` event. See
92    /// [`nix::sys::ptrace`] and [`ptrace`(2)] for more information. All
93    /// currently-defined events use `SIGTRAP` as the signal; the third
94    /// field is the `PTRACE_EVENT_*` value of the event.
95    ///
96    /// [`nix::sys::ptrace`]: ../ptrace/index.html
97    /// [`ptrace`(2)]: https://man7.org/linux/man-pages/man2/ptrace.2.html
98    #[cfg(linux_android)]
99    PtraceEvent(Pid, Signal, c_int),
100    /// The traced process was stopped by execution of a system call,
101    /// and `PTRACE_O_TRACESYSGOOD` is in effect. See [`ptrace`(2)] for
102    /// more information.
103    ///
104    /// [`ptrace`(2)]: https://man7.org/linux/man-pages/man2/ptrace.2.html
105    #[cfg(linux_android)]
106    PtraceSyscall(Pid),
107    /// The process was previously stopped but has resumed execution
108    /// after receiving a `SIGCONT` signal. This is only reported if
109    /// `WaitPidFlag::WCONTINUED` was passed. This case matches the C
110    /// macro `WIFCONTINUED(status)`.
111    Continued(Pid),
112    /// There are currently no state changes to report in any awaited
113    /// child process. This is only returned if `WaitPidFlag::WNOHANG`
114    /// was used (otherwise `wait()` or `waitpid()` would block until
115    /// there was something to report).
116    StillAlive,
117}
118
119impl WaitStatus {
120    /// Extracts the PID from the WaitStatus unless it equals StillAlive.
121    pub fn pid(&self) -> Option<Pid> {
122        use self::WaitStatus::*;
123        match *self {
124            Exited(p, _) | Signaled(p, _, _) | Stopped(p, _) | Continued(p) => {
125                Some(p)
126            }
127            StillAlive => None,
128            #[cfg(linux_android)]
129            PtraceEvent(p, _, _) | PtraceSyscall(p) => Some(p),
130        }
131    }
132}
133
134fn exited(status: i32) -> bool {
135    libc::WIFEXITED(status)
136}
137
138fn exit_status(status: i32) -> i32 {
139    libc::WEXITSTATUS(status)
140}
141
142fn signaled(status: i32) -> bool {
143    libc::WIFSIGNALED(status)
144}
145
146fn term_signal(status: i32) -> Result<Signal> {
147    Signal::try_from(libc::WTERMSIG(status))
148}
149
150fn dumped_core(status: i32) -> bool {
151    libc::WCOREDUMP(status)
152}
153
154fn stopped(status: i32) -> bool {
155    libc::WIFSTOPPED(status)
156}
157
158fn stop_signal(status: i32) -> Result<Signal> {
159    Signal::try_from(libc::WSTOPSIG(status))
160}
161
162#[cfg(linux_android)]
163fn syscall_stop(status: i32) -> bool {
164    // From ptrace(2), setting PTRACE_O_TRACESYSGOOD has the effect
165    // of delivering SIGTRAP | 0x80 as the signal number for syscall
166    // stops. This allows easily distinguishing syscall stops from
167    // genuine SIGTRAP signals.
168    libc::WSTOPSIG(status) == libc::SIGTRAP | 0x80
169}
170
171#[cfg(linux_android)]
172fn stop_additional(status: i32) -> c_int {
173    (status >> 16) as c_int
174}
175
176fn continued(status: i32) -> bool {
177    libc::WIFCONTINUED(status)
178}
179
180impl WaitStatus {
181    /// Convert a raw `wstatus` as returned by `waitpid`/`wait` into a `WaitStatus`
182    ///
183    /// # Errors
184    ///
185    /// Returns an `Error` corresponding to `EINVAL` for invalid status values.
186    ///
187    /// # Examples
188    ///
189    /// Convert a `wstatus` obtained from `libc::waitpid` into a `WaitStatus`:
190    ///
191    /// ```
192    /// use nix::sys::wait::WaitStatus;
193    /// use nix::sys::signal::Signal;
194    /// let pid = nix::unistd::Pid::from_raw(1);
195    /// let status = WaitStatus::from_raw(pid, 0x0002);
196    /// assert_eq!(status, Ok(WaitStatus::Signaled(pid, Signal::SIGINT, false)));
197    /// ```
198    pub fn from_raw(pid: Pid, status: i32) -> Result<WaitStatus> {
199        Ok(if exited(status) {
200            WaitStatus::Exited(pid, exit_status(status))
201        } else if signaled(status) {
202            WaitStatus::Signaled(pid, term_signal(status)?, dumped_core(status))
203        } else if stopped(status) {
204            cfg_if! {
205                if #[cfg(linux_android)] {
206                    fn decode_stopped(pid: Pid, status: i32) -> Result<WaitStatus> {
207                        let status_additional = stop_additional(status);
208                        Ok(if syscall_stop(status) {
209                            WaitStatus::PtraceSyscall(pid)
210                        } else if status_additional == 0 {
211                            WaitStatus::Stopped(pid, stop_signal(status)?)
212                        } else {
213                            WaitStatus::PtraceEvent(pid, stop_signal(status)?,
214                                                    stop_additional(status))
215                        })
216                    }
217                } else {
218                    fn decode_stopped(pid: Pid, status: i32) -> Result<WaitStatus> {
219                        Ok(WaitStatus::Stopped(pid, stop_signal(status)?))
220                    }
221                }
222            }
223            return decode_stopped(pid, status);
224        } else {
225            assert!(continued(status));
226            WaitStatus::Continued(pid)
227        })
228    }
229
230    /// Convert a `siginfo_t` as returned by `waitid` to a `WaitStatus`
231    ///
232    /// # Errors
233    ///
234    /// Returns an `Error` corresponding to `EINVAL` for invalid values.
235    ///
236    /// # Safety
237    ///
238    /// siginfo_t is actually a union, not all fields may be initialized.
239    /// The functions si_pid() and si_status() must be valid to call on
240    /// the passed siginfo_t.
241    #[cfg(any(
242        target_os = "android",
243        target_os = "freebsd",
244        target_os = "haiku",
245        all(target_os = "linux", not(target_env = "uclibc")),
246    ))]
247    unsafe fn from_siginfo(siginfo: &libc::siginfo_t) -> Result<WaitStatus> {
248        let si_pid = unsafe { siginfo.si_pid() };
249        if si_pid == 0 {
250            return Ok(WaitStatus::StillAlive);
251        }
252
253        assert_eq!(siginfo.si_signo, libc::SIGCHLD);
254
255        let pid = Pid::from_raw(si_pid);
256        let si_status = unsafe { siginfo.si_status() };
257
258        let status = match siginfo.si_code {
259            libc::CLD_EXITED => WaitStatus::Exited(pid, si_status),
260            libc::CLD_KILLED | libc::CLD_DUMPED => WaitStatus::Signaled(
261                pid,
262                Signal::try_from(si_status)?,
263                siginfo.si_code == libc::CLD_DUMPED,
264            ),
265            libc::CLD_STOPPED => {
266                WaitStatus::Stopped(pid, Signal::try_from(si_status)?)
267            }
268            libc::CLD_CONTINUED => WaitStatus::Continued(pid),
269            #[cfg(linux_android)]
270            libc::CLD_TRAPPED => {
271                if si_status == libc::SIGTRAP | 0x80 {
272                    WaitStatus::PtraceSyscall(pid)
273                } else {
274                    WaitStatus::PtraceEvent(
275                        pid,
276                        Signal::try_from(si_status & 0xff)?,
277                        (si_status >> 8) as c_int,
278                    )
279                }
280            }
281            _ => return Err(Errno::EINVAL),
282        };
283
284        Ok(status)
285    }
286}
287
288/// Wait for a process to change status
289///
290/// See also [waitpid(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/waitpid.html)
291pub fn waitpid<P: Into<Option<Pid>>>(
292    pid: P,
293    options: Option<WaitPidFlag>,
294) -> Result<WaitStatus> {
295    use self::WaitStatus::*;
296
297    let mut status: i32 = 0;
298
299    let option_bits = match options {
300        Some(bits) => bits.bits(),
301        None => 0,
302    };
303
304    let res = unsafe {
305        libc::waitpid(
306            pid.into().unwrap_or_else(|| Pid::from_raw(-1)).into(),
307            &mut status as *mut c_int,
308            option_bits,
309        )
310    };
311
312    match Errno::result(res)? {
313        0 => Ok(StillAlive),
314        res => WaitStatus::from_raw(Pid::from_raw(res), status),
315    }
316}
317
318/// Wait for any child process to change status or a signal is received.
319///
320/// See also [wait(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/wait.html)
321pub fn wait() -> Result<WaitStatus> {
322    waitpid(None, None)
323}
324
325/// The ID argument for `waitid`
326#[cfg(any(
327    target_os = "android",
328    target_os = "freebsd",
329    target_os = "haiku",
330    all(target_os = "linux", not(target_env = "uclibc")),
331))]
332#[derive(Debug)]
333pub enum Id<'fd> {
334    /// Wait for any child
335    All,
336    /// Wait for the child whose process ID matches the given PID
337    Pid(Pid),
338    /// Wait for the child whose process group ID matches the given PID
339    ///
340    /// If the PID is zero, the caller's process group is used since Linux 5.4.
341    PGid(Pid),
342    /// Wait for the child referred to by the given PID file descriptor
343    #[cfg(linux_android)]
344    PIDFd(BorrowedFd<'fd>),
345    /// A helper variant to resolve the unused parameter (`'fd`) problem on platforms
346    /// other than Linux and Android.
347    #[doc(hidden)]
348    _Unreachable(std::marker::PhantomData<&'fd std::convert::Infallible>),
349}
350
351/// Wait for a process to change status
352///
353/// See also [waitid(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/waitid.html)
354#[cfg(any(
355    target_os = "android",
356    target_os = "freebsd",
357    target_os = "haiku",
358    all(target_os = "linux", not(target_env = "uclibc")),
359))]
360pub fn waitid(id: Id, flags: WaitPidFlag) -> Result<WaitStatus> {
361    let (idtype, idval) = match id {
362        Id::All => (libc::P_ALL, 0),
363        Id::Pid(pid) => (libc::P_PID, pid.as_raw() as libc::id_t),
364        Id::PGid(pid) => (libc::P_PGID, pid.as_raw() as libc::id_t),
365        #[cfg(linux_android)]
366        Id::PIDFd(fd) => (libc::P_PIDFD, fd.as_raw_fd() as libc::id_t),
367        Id::_Unreachable(_) => {
368            unreachable!("This variant could never be constructed")
369        }
370    };
371
372    let siginfo = unsafe {
373        // Memory is zeroed rather than uninitialized, as not all platforms
374        // initialize the memory in the StillAlive case
375        let mut siginfo: libc::siginfo_t = std::mem::zeroed();
376        Errno::result(libc::waitid(idtype, idval, &mut siginfo, flags.bits()))?;
377        siginfo
378    };
379
380    unsafe { WaitStatus::from_siginfo(&siginfo) }
381}