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. |
121 | pub fn pid(&self) -> Option<Pid> { |
122 | use self::WaitStatus::*; |
123 | match *self { |
124 | Exited(p: Pid, _) | Signaled(p: Pid, _, _) | Stopped(p: Pid, _) | Continued(p: Pid) => { |
125 | Some(p) |
126 | } |
127 | StillAlive => None, |
128 | #[cfg (linux_android)] |
129 | PtraceEvent(p: Pid, _, _) | PtraceSyscall(p: Pid) => Some(p), |
130 | } |
131 | } |
132 | } |
133 | |
134 | fn exited(status: i32) -> bool { |
135 | libc::WIFEXITED(status) |
136 | } |
137 | |
138 | fn exit_status(status: i32) -> i32 { |
139 | libc::WEXITSTATUS(status) |
140 | } |
141 | |
142 | fn signaled(status: i32) -> bool { |
143 | libc::WIFSIGNALED(status) |
144 | } |
145 | |
146 | fn term_signal(status: i32) -> Result<Signal> { |
147 | Signal::try_from(libc::WTERMSIG(status)) |
148 | } |
149 | |
150 | fn dumped_core(status: i32) -> bool { |
151 | libc::WCOREDUMP(status) |
152 | } |
153 | |
154 | fn stopped(status: i32) -> bool { |
155 | libc::WIFSTOPPED(status) |
156 | } |
157 | |
158 | fn stop_signal(status: i32) -> Result<Signal> { |
159 | Signal::try_from(libc::WSTOPSIG(status)) |
160 | } |
161 | |
162 | #[cfg (linux_android)] |
163 | 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)] |
172 | fn stop_additional(status: i32) -> c_int { |
173 | (status >> 16) as c_int |
174 | } |
175 | |
176 | 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 | /// ``` |
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) |
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: i32 = match options { |
300 | Some(bits: WaitPidFlag) => bits.bits(), |
301 | None => 0, |
302 | }; |
303 | |
304 | let res: i32 = unsafe { |
305 | libc::waitpid( |
306 | pid.into().unwrap_or_else(|| Pid::from_raw(-1)).into(), |
307 | &mut status as *mut c_int, |
308 | options:option_bits, |
309 | ) |
310 | }; |
311 | |
312 | match Errno::result(res)? { |
313 | 0 => Ok(StillAlive), |
314 | res: i32 => WaitStatus::from_raw(Pid::from_raw(pid: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) |
321 | pub fn wait() -> Result<WaitStatus> { |
322 | waitpid(pid:None, options: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 | ))] |
360 | pub fn waitid(id: Id, flags: WaitPidFlag) -> Result<WaitStatus> { |
361 | let (idtype: u32, idval: u32) = match id { |
362 | Id::All => (libc::P_ALL, 0), |
363 | Id::Pid(pid: Pid) => (libc::P_PID, pid.as_raw() as libc::id_t), |
364 | Id::PGid(pid: Pid) => (libc::P_PGID, pid.as_raw() as libc::id_t), |
365 | #[cfg (linux_android)] |
366 | Id::PIDFd(fd: BorrowedFd<'_>) => (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: siginfo_t = 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, id:idval, &mut siginfo, options:flags.bits()))?; |
377 | siginfo |
378 | }; |
379 | |
380 | unsafe { WaitStatus::from_siginfo(&siginfo) } |
381 | } |
382 | |