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}