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(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. |
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 | |
148 | fn exited(status: i32) -> bool { |
149 | libc::WIFEXITED(status) |
150 | } |
151 | |
152 | fn exit_status(status: i32) -> i32 { |
153 | libc::WEXITSTATUS(status) |
154 | } |
155 | |
156 | fn signaled(status: i32) -> bool { |
157 | libc::WIFSIGNALED(status) |
158 | } |
159 | |
160 | fn term_signal(status: i32) -> Result<Signal> { |
161 | Signal::try_from(libc::WTERMSIG(status)) |
162 | } |
163 | |
164 | fn dumped_core(status: i32) -> bool { |
165 | libc::WCOREDUMP(status) |
166 | } |
167 | |
168 | fn stopped(status: i32) -> bool { |
169 | libc::WIFSTOPPED(status) |
170 | } |
171 | |
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" ))] |
177 | 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" ))] |
186 | fn stop_additional(status: i32) -> c_int { |
187 | (status >> 16) as c_int |
188 | } |
189 | |
190 | 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 | /// ``` |
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) |
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: 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) |
335 | pub 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)] |
347 | pub 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 | ))] |
374 | pub 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 | |