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(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: Pid, _) | Signaled(p: Pid, _, _) | Stopped(p: Pid, _) | Continued(p: Pid) => {
139 Some(p)
140 }
141 StillAlive => None,
142 #[cfg(any(target_os = "android", target_os = "linux"))]
143 PtraceEvent(p: Pid, _, _) | PtraceSyscall(p: Pid) => 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: i32 = match options {
314 Some(bits: WaitPidFlag) => bits.bits(),
315 None => 0,
316 };
317
318 let res: i32 = unsafe {
319 libc::waitpid(
320 pid:pid.into().unwrap_or_else(|| Pid::from_raw(-1)).into(),
321 &mut status as *mut c_int,
322 options:option_bits,
323 )
324 };
325
326 match Errno::result(res)? {
327 0 => Ok(StillAlive),
328 res: i32 => WaitStatus::from_raw(Pid::from_raw(pid: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(pid:None, options: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)]
347pub enum Id<'fd> {
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(BorrowedFd<'fd>),
359 /// A helper variant to resolve the unused parameter (`'fd`) problem on platforms
360 /// other than Linux and Android.
361 #[doc(hidden)]
362 _Unreachable(std::marker::PhantomData<&'fd std::convert::Infallible>),
363}
364
365/// Wait for a process to change status
366///
367/// See also [waitid(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/waitid.html)
368#[cfg(any(
369 target_os = "android",
370 target_os = "freebsd",
371 target_os = "haiku",
372 all(target_os = "linux", not(target_env = "uclibc")),
373))]
374pub fn waitid(id: Id, flags: WaitPidFlag) -> Result<WaitStatus> {
375 let (idtype: u32, idval: u32) = match id {
376 Id::All => (libc::P_ALL, 0),
377 Id::Pid(pid: Pid) => (libc::P_PID, pid.as_raw() as libc::id_t),
378 Id::PGid(pid: Pid) => (libc::P_PGID, pid.as_raw() as libc::id_t),
379 #[cfg(any(target_os = "android", target_os = "linux"))]
380 Id::PIDFd(fd: BorrowedFd<'_>) => (libc::P_PIDFD, fd.as_raw_fd() as libc::id_t),
381 Id::_Unreachable(_) => unreachable!("This variant could never be constructed"),
382 };
383
384 let siginfo: siginfo_t = unsafe {
385 // Memory is zeroed rather than uninitialized, as not all platforms
386 // initialize the memory in the StillAlive case
387 let mut siginfo: libc::siginfo_t = std::mem::zeroed();
388 Errno::result(libc::waitid(idtype, id:idval, &mut siginfo, options:flags.bits()))?;
389 siginfo
390 };
391
392 unsafe { WaitStatus::from_siginfo(&siginfo) }
393}
394