1 //! Wait for a process to change status
2 use crate::errno::Errno;
3 use crate::sys::signal::Signal;
4 use crate::unistd::Pid;
5 use crate::Result;
6 use cfg_if::cfg_if;
7 use libc::{self, c_int};
8 use std::convert::TryFrom;
9 #[cfg(any(
10     target_os = "android",
11     all(target_os = "linux", not(target_env = "uclibc")),
12 ))]
13 use std::os::unix::io::{AsRawFd, BorrowedFd};
14 
15 libc_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)]
76 pub 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 
119 impl WaitStatus {
120     /// Extracts the PID from the WaitStatus unless it equals StillAlive.
pid(&self) -> Option<Pid>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 
exited(status: i32) -> bool134 fn exited(status: i32) -> bool {
135     libc::WIFEXITED(status)
136 }
137 
exit_status(status: i32) -> i32138 fn exit_status(status: i32) -> i32 {
139     libc::WEXITSTATUS(status)
140 }
141 
signaled(status: i32) -> bool142 fn signaled(status: i32) -> bool {
143     libc::WIFSIGNALED(status)
144 }
145 
term_signal(status: i32) -> Result<Signal>146 fn term_signal(status: i32) -> Result<Signal> {
147     Signal::try_from(libc::WTERMSIG(status))
148 }
149 
dumped_core(status: i32) -> bool150 fn dumped_core(status: i32) -> bool {
151     libc::WCOREDUMP(status)
152 }
153 
stopped(status: i32) -> bool154 fn stopped(status: i32) -> bool {
155     libc::WIFSTOPPED(status)
156 }
157 
stop_signal(status: i32) -> Result<Signal>158 fn stop_signal(status: i32) -> Result<Signal> {
159     Signal::try_from(libc::WSTOPSIG(status))
160 }
161 
162 #[cfg(linux_android)]
syscall_stop(status: i32) -> bool163 fn 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)]
stop_additional(status: i32) -> c_int172 fn stop_additional(status: i32) -> c_int {
173     (status >> 16) as c_int
174 }
175 
continued(status: i32) -> bool176 fn continued(status: i32) -> bool {
177     libc::WIFCONTINUED(status)
178 }
179 
180 impl 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     /// ```
from_raw(pid: Pid, status: i32) -> Result<WaitStatus>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     ))]
from_siginfo(siginfo: &libc::siginfo_t) -> Result<WaitStatus>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)
waitpid<P: Into<Option<Pid>>>( pid: P, options: Option<WaitPidFlag>, ) -> Result<WaitStatus>291 pub 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)
wait() -> Result<WaitStatus>321 pub 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)]
333 pub 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 ))]
waitid(id: Id, flags: WaitPidFlag) -> Result<WaitStatus>360 pub 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 }
382