• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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::RawFd;
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(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)]
88 pub 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 
133 impl WaitStatus {
134     /// Extracts the PID from the WaitStatus unless it equals StillAlive.
pid(&self) -> Option<Pid>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 
exited(status: i32) -> bool148 fn exited(status: i32) -> bool {
149     libc::WIFEXITED(status)
150 }
151 
exit_status(status: i32) -> i32152 fn exit_status(status: i32) -> i32 {
153     libc::WEXITSTATUS(status)
154 }
155 
signaled(status: i32) -> bool156 fn signaled(status: i32) -> bool {
157     libc::WIFSIGNALED(status)
158 }
159 
term_signal(status: i32) -> Result<Signal>160 fn term_signal(status: i32) -> Result<Signal> {
161     Signal::try_from(libc::WTERMSIG(status))
162 }
163 
dumped_core(status: i32) -> bool164 fn dumped_core(status: i32) -> bool {
165     libc::WCOREDUMP(status)
166 }
167 
stopped(status: i32) -> bool168 fn stopped(status: i32) -> bool {
169     libc::WIFSTOPPED(status)
170 }
171 
stop_signal(status: i32) -> Result<Signal>172 fn stop_signal(status: i32) -> Result<Signal> {
173     Signal::try_from(libc::WSTOPSIG(status))
174 }
175 
176 #[cfg(any(target_os = "android", target_os = "linux"))]
syscall_stop(status: i32) -> bool177 fn 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"))]
stop_additional(status: i32) -> c_int186 fn stop_additional(status: i32) -> c_int {
187     (status >> 16) as c_int
188 }
189 
continued(status: i32) -> bool190 fn continued(status: i32) -> bool {
191     libc::WIFCONTINUED(status)
192 }
193 
194 impl 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     /// ```
from_raw(pid: Pid, status: i32) -> Result<WaitStatus>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     ))]
from_siginfo(siginfo: &libc::siginfo_t) -> Result<WaitStatus>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)
waitpid<P: Into<Option<Pid>>>( pid: P, options: Option<WaitPidFlag>, ) -> Result<WaitStatus>305 pub 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)
wait() -> Result<WaitStatus>335 pub 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)]
347 pub 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 ))]
waitid(id: Id, flags: WaitPidFlag) -> Result<WaitStatus>370 pub 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 }
389