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