/rust/registry/src/index.crates.io-6f17d22bba15001f/nix-0.26.4/src/sys/wait.rs
Line | Count | Source (jump to first uncovered line) |
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. |
135 | 0 | pub fn pid(&self) -> Option<Pid> { |
136 | | use self::WaitStatus::*; |
137 | 0 | match *self { |
138 | 0 | Exited(p, _) | Signaled(p, _, _) | Stopped(p, _) | Continued(p) => { |
139 | 0 | Some(p) |
140 | | } |
141 | 0 | StillAlive => None, |
142 | | #[cfg(any(target_os = "android", target_os = "linux"))] |
143 | 0 | PtraceEvent(p, _, _) | PtraceSyscall(p) => Some(p), |
144 | | } |
145 | 0 | } |
146 | | } |
147 | | |
148 | 0 | fn exited(status: i32) -> bool { |
149 | 0 | libc::WIFEXITED(status) |
150 | 0 | } |
151 | | |
152 | 0 | fn exit_status(status: i32) -> i32 { |
153 | 0 | libc::WEXITSTATUS(status) |
154 | 0 | } |
155 | | |
156 | 0 | fn signaled(status: i32) -> bool { |
157 | 0 | libc::WIFSIGNALED(status) |
158 | 0 | } |
159 | | |
160 | 0 | fn term_signal(status: i32) -> Result<Signal> { |
161 | 0 | Signal::try_from(libc::WTERMSIG(status)) |
162 | 0 | } |
163 | | |
164 | 0 | fn dumped_core(status: i32) -> bool { |
165 | 0 | libc::WCOREDUMP(status) |
166 | 0 | } |
167 | | |
168 | 0 | fn stopped(status: i32) -> bool { |
169 | 0 | libc::WIFSTOPPED(status) |
170 | 0 | } |
171 | | |
172 | 0 | fn stop_signal(status: i32) -> Result<Signal> { |
173 | 0 | Signal::try_from(libc::WSTOPSIG(status)) |
174 | 0 | } |
175 | | |
176 | | #[cfg(any(target_os = "android", target_os = "linux"))] |
177 | 0 | fn syscall_stop(status: i32) -> bool { |
178 | 0 | // From ptrace(2), setting PTRACE_O_TRACESYSGOOD has the effect |
179 | 0 | // of delivering SIGTRAP | 0x80 as the signal number for syscall |
180 | 0 | // stops. This allows easily distinguishing syscall stops from |
181 | 0 | // genuine SIGTRAP signals. |
182 | 0 | libc::WSTOPSIG(status) == libc::SIGTRAP | 0x80 |
183 | 0 | } |
184 | | |
185 | | #[cfg(any(target_os = "android", target_os = "linux"))] |
186 | 0 | fn stop_additional(status: i32) -> c_int { |
187 | 0 | (status >> 16) as c_int |
188 | 0 | } |
189 | | |
190 | 0 | fn continued(status: i32) -> bool { |
191 | 0 | libc::WIFCONTINUED(status) |
192 | 0 | } |
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 | 0 | pub fn from_raw(pid: Pid, status: i32) -> Result<WaitStatus> { |
213 | 0 | Ok(if exited(status) { |
214 | 0 | WaitStatus::Exited(pid, exit_status(status)) |
215 | 0 | } else if signaled(status) { |
216 | 0 | WaitStatus::Signaled(pid, term_signal(status)?, dumped_core(status)) |
217 | 0 | } else if stopped(status) { |
218 | | cfg_if! { |
219 | | if #[cfg(any(target_os = "android", target_os = "linux"))] { |
220 | 0 | fn decode_stopped(pid: Pid, status: i32) -> Result<WaitStatus> { |
221 | 0 | let status_additional = stop_additional(status); |
222 | 0 | Ok(if syscall_stop(status) { |
223 | 0 | WaitStatus::PtraceSyscall(pid) |
224 | 0 | } else if status_additional == 0 { |
225 | 0 | WaitStatus::Stopped(pid, stop_signal(status)?) |
226 | | } else { |
227 | 0 | WaitStatus::PtraceEvent(pid, stop_signal(status)?, |
228 | 0 | stop_additional(status)) |
229 | | }) |
230 | 0 | } |
231 | | } else { |
232 | | fn decode_stopped(pid: Pid, status: i32) -> Result<WaitStatus> { |
233 | | Ok(WaitStatus::Stopped(pid, stop_signal(status)?)) |
234 | | } |
235 | | } |
236 | | } |
237 | 0 | return decode_stopped(pid, status); |
238 | | } else { |
239 | 0 | assert!(continued(status)); |
240 | 0 | WaitStatus::Continued(pid) |
241 | | }) |
242 | 0 | } |
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 | 0 | unsafe fn from_siginfo(siginfo: &libc::siginfo_t) -> Result<WaitStatus> { |
262 | 0 | let si_pid = siginfo.si_pid(); |
263 | 0 | if si_pid == 0 { |
264 | 0 | return Ok(WaitStatus::StillAlive); |
265 | 0 | } |
266 | 0 |
|
267 | 0 | assert_eq!(siginfo.si_signo, libc::SIGCHLD); |
268 | | |
269 | 0 | let pid = Pid::from_raw(si_pid); |
270 | 0 | let si_status = siginfo.si_status(); |
271 | | |
272 | 0 | let status = match siginfo.si_code { |
273 | 0 | libc::CLD_EXITED => WaitStatus::Exited(pid, si_status), |
274 | | libc::CLD_KILLED | libc::CLD_DUMPED => WaitStatus::Signaled( |
275 | 0 | pid, |
276 | 0 | Signal::try_from(si_status)?, |
277 | 0 | siginfo.si_code == libc::CLD_DUMPED, |
278 | | ), |
279 | | libc::CLD_STOPPED => { |
280 | 0 | WaitStatus::Stopped(pid, Signal::try_from(si_status)?) |
281 | | } |
282 | 0 | libc::CLD_CONTINUED => WaitStatus::Continued(pid), |
283 | | #[cfg(any(target_os = "android", target_os = "linux"))] |
284 | | libc::CLD_TRAPPED => { |
285 | 0 | if si_status == libc::SIGTRAP | 0x80 { |
286 | 0 | WaitStatus::PtraceSyscall(pid) |
287 | | } else { |
288 | | WaitStatus::PtraceEvent( |
289 | 0 | pid, |
290 | 0 | Signal::try_from(si_status & 0xff)?, |
291 | 0 | (si_status >> 8) as c_int, |
292 | | ) |
293 | | } |
294 | | } |
295 | 0 | _ => return Err(Errno::EINVAL), |
296 | | }; |
297 | | |
298 | 0 | Ok(status) |
299 | 0 | } |
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 | 0 | pub fn waitpid<P: Into<Option<Pid>>>( |
306 | 0 | pid: P, |
307 | 0 | options: Option<WaitPidFlag>, |
308 | 0 | ) -> Result<WaitStatus> { |
309 | | use self::WaitStatus::*; |
310 | | |
311 | 0 | let mut status: i32 = 0; |
312 | | |
313 | 0 | let option_bits = match options { |
314 | 0 | Some(bits) => bits.bits(), |
315 | 0 | None => 0, |
316 | | }; |
317 | | |
318 | 0 | let res = unsafe { |
319 | 0 | libc::waitpid( |
320 | 0 | pid.into().unwrap_or_else(|| Pid::from_raw(-1)).into(), |
321 | 0 | &mut status as *mut c_int, |
322 | 0 | option_bits, |
323 | 0 | ) |
324 | 0 | }; |
325 | 0 |
|
326 | 0 | match Errno::result(res)? { |
327 | 0 | 0 => Ok(StillAlive), |
328 | 0 | res => WaitStatus::from_raw(Pid::from_raw(res), status), |
329 | | } |
330 | 0 | } |
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 | 0 | pub fn wait() -> Result<WaitStatus> { |
336 | 0 | waitpid(None, None) |
337 | 0 | } |
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 | | ))] |
370 | 0 | pub fn waitid(id: Id, flags: WaitPidFlag) -> Result<WaitStatus> { |
371 | 0 | let (idtype, idval) = match id { |
372 | 0 | Id::All => (libc::P_ALL, 0), |
373 | 0 | Id::Pid(pid) => (libc::P_PID, pid.as_raw() as libc::id_t), |
374 | 0 | Id::PGid(pid) => (libc::P_PGID, pid.as_raw() as libc::id_t), |
375 | | #[cfg(any(target_os = "android", target_os = "linux"))] |
376 | 0 | Id::PIDFd(fd) => (libc::P_PIDFD, fd as libc::id_t), |
377 | | }; |
378 | | |
379 | 0 | let siginfo = unsafe { |
380 | | // Memory is zeroed rather than uninitialized, as not all platforms |
381 | | // initialize the memory in the StillAlive case |
382 | 0 | let mut siginfo: libc::siginfo_t = std::mem::zeroed(); |
383 | 0 | Errno::result(libc::waitid(idtype, idval, &mut siginfo, flags.bits()))?; |
384 | 0 | siginfo |
385 | 0 | }; |
386 | 0 |
|
387 | 0 | unsafe { WaitStatus::from_siginfo(&siginfo) } |
388 | 0 | } |