/rust/registry/src/index.crates.io-6f17d22bba15001f/rustix-1.0.7/src/process/wait.rs
Line | Count | Source (jump to first uncovered line) |
1 | | //! Wait for processes to change state. |
2 | | //! |
3 | | //! # Safety |
4 | | //! |
5 | | //! This code needs to implement `Send` and `Sync` for `WaitIdStatus` because |
6 | | //! the linux-raw-sys bindings generate a type that doesn't do so |
7 | | //! automatically. |
8 | | #![allow(unsafe_code)] |
9 | | use crate::process::Pid; |
10 | | use crate::{backend, io}; |
11 | | use bitflags::bitflags; |
12 | | use core::fmt; |
13 | | |
14 | | #[cfg(target_os = "linux")] |
15 | | use crate::fd::BorrowedFd; |
16 | | |
17 | | #[cfg(linux_raw)] |
18 | | use crate::backend::process::wait::SiginfoExt as _; |
19 | | |
20 | | bitflags! { |
21 | | /// Options for modifying the behavior of [`wait`]/[`waitpid`]. |
22 | | #[repr(transparent)] |
23 | | #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] |
24 | | pub struct WaitOptions: u32 { |
25 | | /// Return immediately if no child has exited. |
26 | | const NOHANG = bitcast!(backend::process::wait::WNOHANG); |
27 | | /// Return if a child has stopped (but not traced via [`ptrace`]). |
28 | | /// |
29 | | /// [`ptrace`]: https://man7.org/linux/man-pages/man2/ptrace.2.html |
30 | | #[cfg(not(target_os = "horizon"))] |
31 | | const UNTRACED = bitcast!(backend::process::wait::WUNTRACED); |
32 | | /// Return if a stopped child has been resumed by delivery of |
33 | | /// [`Signal::Cont`]. |
34 | | /// |
35 | | /// [`Signal::Cont`]: crate::process::Signal::Cont |
36 | | #[cfg(not(target_os = "horizon"))] |
37 | | const CONTINUED = bitcast!(backend::process::wait::WCONTINUED); |
38 | | |
39 | | /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags> |
40 | | const _ = !0; |
41 | | } |
42 | | } |
43 | | |
44 | | #[cfg(not(any( |
45 | | target_os = "horizon", |
46 | | target_os = "openbsd", |
47 | | target_os = "redox", |
48 | | target_os = "wasi" |
49 | | )))] |
50 | | bitflags! { |
51 | | /// Options for modifying the behavior of [`waitid`]. |
52 | | #[repr(transparent)] |
53 | | #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] |
54 | | pub struct WaitIdOptions: u32 { |
55 | | /// Return immediately if no child has exited. |
56 | | const NOHANG = bitcast!(backend::process::wait::WNOHANG); |
57 | | /// Return if a stopped child has been resumed by delivery of |
58 | | /// [`Signal::Cont`]. |
59 | | /// |
60 | | /// [`Signal::Cont`]: crate::process::Signal::Cont |
61 | | const CONTINUED = bitcast!(backend::process::wait::WCONTINUED); |
62 | | /// Wait for processed that have exited. |
63 | | #[cfg(not(target_os = "cygwin"))] |
64 | | const EXITED = bitcast!(backend::process::wait::WEXITED); |
65 | | /// Keep processed in a waitable state. |
66 | | #[cfg(not(target_os = "cygwin"))] |
67 | | const NOWAIT = bitcast!(backend::process::wait::WNOWAIT); |
68 | | /// Wait for processes that have been stopped. |
69 | | #[cfg(not(target_os = "cygwin"))] |
70 | | const STOPPED = bitcast!(backend::process::wait::WSTOPPED); |
71 | | |
72 | | /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags> |
73 | | const _ = !0; |
74 | | } |
75 | | } |
76 | | |
77 | | /// The status of a child process after calling [`wait`]/[`waitpid`]. |
78 | | #[derive(Clone, Copy)] |
79 | | #[repr(transparent)] |
80 | | pub struct WaitStatus(i32); |
81 | | |
82 | | impl WaitStatus { |
83 | | /// Creates a `WaitStatus` out of an integer. |
84 | | #[inline] |
85 | 0 | pub(crate) fn new(status: i32) -> Self { |
86 | 0 | Self(status) |
87 | 0 | } |
88 | | |
89 | | /// Converts a `WaitStatus` into its raw representation as an integer. |
90 | | #[inline] |
91 | 0 | pub const fn as_raw(self) -> i32 { |
92 | 0 | self.0 |
93 | 0 | } |
94 | | |
95 | | /// Returns whether the process is currently stopped. |
96 | | #[inline] |
97 | | #[doc(alias = "WIFSTOPPED")] |
98 | 0 | pub fn stopped(self) -> bool { |
99 | 0 | backend::process::wait::WIFSTOPPED(self.0) |
100 | 0 | } |
101 | | |
102 | | /// Returns whether the process has exited normally. |
103 | | #[inline] |
104 | | #[doc(alias = "WIFEXITED")] |
105 | 0 | pub fn exited(self) -> bool { |
106 | 0 | backend::process::wait::WIFEXITED(self.0) |
107 | 0 | } |
108 | | |
109 | | /// Returns whether the process was terminated by a signal. |
110 | | #[inline] |
111 | | #[doc(alias = "WIFSIGNALED")] |
112 | 0 | pub fn signaled(self) -> bool { |
113 | 0 | backend::process::wait::WIFSIGNALED(self.0) |
114 | 0 | } |
115 | | |
116 | | /// Returns whether the process has continued from a job control stop. |
117 | | #[inline] |
118 | | #[doc(alias = "WIFCONTINUED")] |
119 | 0 | pub fn continued(self) -> bool { |
120 | 0 | backend::process::wait::WIFCONTINUED(self.0) |
121 | 0 | } |
122 | | |
123 | | /// Returns the number of the signal that stopped the process, if the |
124 | | /// process was stopped by a signal. |
125 | | #[inline] |
126 | | #[doc(alias = "WSTOPSIG")] |
127 | 0 | pub fn stopping_signal(self) -> Option<i32> { |
128 | 0 | if self.stopped() { |
129 | 0 | Some(backend::process::wait::WSTOPSIG(self.0)) |
130 | | } else { |
131 | 0 | None |
132 | | } |
133 | 0 | } |
134 | | |
135 | | /// Returns the exit status number returned by the process, if it exited |
136 | | /// normally. |
137 | | #[inline] |
138 | | #[doc(alias = "WEXITSTATUS")] |
139 | 0 | pub fn exit_status(self) -> Option<i32> { |
140 | 0 | if self.exited() { |
141 | 0 | Some(backend::process::wait::WEXITSTATUS(self.0)) |
142 | | } else { |
143 | 0 | None |
144 | | } |
145 | 0 | } |
146 | | |
147 | | /// Returns the number of the signal that terminated the process, if the |
148 | | /// process was terminated by a signal. |
149 | | #[inline] |
150 | | #[doc(alias = "WTERMSIG")] |
151 | 0 | pub fn terminating_signal(self) -> Option<i32> { |
152 | 0 | if self.signaled() { |
153 | 0 | Some(backend::process::wait::WTERMSIG(self.0)) |
154 | | } else { |
155 | 0 | None |
156 | | } |
157 | 0 | } |
158 | | } |
159 | | |
160 | | impl fmt::Debug for WaitStatus { |
161 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
162 | 0 | let mut s = f.debug_struct("WaitStatus"); |
163 | 0 | s.field("stopped", &self.stopped()); |
164 | 0 | s.field("exited", &self.exited()); |
165 | 0 | s.field("signaled", &self.signaled()); |
166 | 0 | s.field("continued", &self.continued()); |
167 | 0 | if let Some(stopping_signal) = self.stopping_signal() { |
168 | 0 | s.field("stopping_signal", &stopping_signal); |
169 | 0 | } |
170 | 0 | if let Some(exit_status) = self.exit_status() { |
171 | 0 | s.field("exit_status", &exit_status); |
172 | 0 | } |
173 | 0 | if let Some(terminating_signal) = self.terminating_signal() { |
174 | 0 | s.field("terminating_signal", &terminating_signal); |
175 | 0 | } |
176 | 0 | s.finish() |
177 | 0 | } |
178 | | } |
179 | | |
180 | | /// The status of a process after calling [`waitid`]. |
181 | | #[derive(Clone, Copy)] |
182 | | #[repr(transparent)] |
183 | | #[cfg(not(any( |
184 | | target_os = "horizon", |
185 | | target_os = "openbsd", |
186 | | target_os = "redox", |
187 | | target_os = "wasi" |
188 | | )))] |
189 | | pub struct WaitIdStatus(pub(crate) backend::c::siginfo_t); |
190 | | |
191 | | #[cfg(linux_raw)] |
192 | | // SAFETY: `siginfo_t` does contain some raw pointers, such as the `si_ptr` |
193 | | // and the `si_addr` fields, however it's up to users to use those correctly. |
194 | | unsafe impl Send for WaitIdStatus {} |
195 | | |
196 | | #[cfg(linux_raw)] |
197 | | // SAFETY: Same as with `Send`. |
198 | | unsafe impl Sync for WaitIdStatus {} |
199 | | |
200 | | #[cfg(not(any( |
201 | | target_os = "horizon", |
202 | | target_os = "openbsd", |
203 | | target_os = "redox", |
204 | | target_os = "wasi" |
205 | | )))] |
206 | | impl WaitIdStatus { |
207 | | /// Returns whether the process is currently stopped. |
208 | | #[inline] |
209 | 0 | pub fn stopped(&self) -> bool { |
210 | 0 | self.raw_code() == bitcast!(backend::c::CLD_STOPPED) |
211 | 0 | } |
212 | | |
213 | | /// Returns whether the process is currently trapped. |
214 | | #[inline] |
215 | 0 | pub fn trapped(&self) -> bool { |
216 | 0 | self.raw_code() == bitcast!(backend::c::CLD_TRAPPED) |
217 | 0 | } |
218 | | |
219 | | /// Returns whether the process has exited normally. |
220 | | #[inline] |
221 | 0 | pub fn exited(&self) -> bool { |
222 | 0 | self.raw_code() == bitcast!(backend::c::CLD_EXITED) |
223 | 0 | } |
224 | | |
225 | | /// Returns whether the process was terminated by a signal and did not |
226 | | /// create a core file. |
227 | | #[inline] |
228 | 0 | pub fn killed(&self) -> bool { |
229 | 0 | self.raw_code() == bitcast!(backend::c::CLD_KILLED) |
230 | 0 | } |
231 | | |
232 | | /// Returns whether the process was terminated by a signal and did create a |
233 | | /// core file. |
234 | | #[inline] |
235 | 0 | pub fn dumped(&self) -> bool { |
236 | 0 | self.raw_code() == bitcast!(backend::c::CLD_DUMPED) |
237 | 0 | } |
238 | | |
239 | | /// Returns whether the process has continued from a job control stop. |
240 | | #[inline] |
241 | 0 | pub fn continued(&self) -> bool { |
242 | 0 | self.raw_code() == bitcast!(backend::c::CLD_CONTINUED) |
243 | 0 | } |
244 | | |
245 | | /// Returns the number of the signal that stopped the process, if the |
246 | | /// process was stopped by a signal. |
247 | | #[inline] |
248 | | #[cfg(not(any(target_os = "emscripten", target_os = "fuchsia", target_os = "netbsd")))] |
249 | 0 | pub fn stopping_signal(&self) -> Option<i32> { |
250 | 0 | if self.stopped() { |
251 | 0 | Some(self.si_status()) |
252 | | } else { |
253 | 0 | None |
254 | | } |
255 | 0 | } |
256 | | |
257 | | /// Returns the number of the signal that trapped the process, if the |
258 | | /// process was trapped by a signal. |
259 | | #[inline] |
260 | | #[cfg(not(any(target_os = "emscripten", target_os = "fuchsia", target_os = "netbsd")))] |
261 | 0 | pub fn trapping_signal(&self) -> Option<i32> { |
262 | 0 | if self.trapped() { |
263 | 0 | Some(self.si_status()) |
264 | | } else { |
265 | 0 | None |
266 | | } |
267 | 0 | } |
268 | | |
269 | | /// Returns the exit status number returned by the process, if it exited |
270 | | /// normally. |
271 | | #[inline] |
272 | | #[cfg(not(any(target_os = "emscripten", target_os = "fuchsia", target_os = "netbsd")))] |
273 | 0 | pub fn exit_status(&self) -> Option<i32> { |
274 | 0 | if self.exited() { |
275 | 0 | Some(self.si_status()) |
276 | | } else { |
277 | 0 | None |
278 | | } |
279 | 0 | } |
280 | | |
281 | | /// Returns the number of the signal that terminated the process, if the |
282 | | /// process was terminated by a signal. |
283 | | #[inline] |
284 | | #[cfg(not(any(target_os = "emscripten", target_os = "fuchsia", target_os = "netbsd")))] |
285 | 0 | pub fn terminating_signal(&self) -> Option<i32> { |
286 | 0 | if self.killed() || self.dumped() { |
287 | 0 | Some(self.si_status()) |
288 | | } else { |
289 | 0 | None |
290 | | } |
291 | 0 | } |
292 | | |
293 | | /// Return the raw `si_signo` value returned from `waitid`. |
294 | | #[cfg(linux_raw)] |
295 | 0 | pub fn raw_signo(&self) -> crate::ffi::c_int { |
296 | 0 | self.0.si_signo() |
297 | 0 | } |
298 | | |
299 | | /// Return the raw `si_signo` value returned from `waitid`. |
300 | | #[cfg(not(linux_raw))] |
301 | | pub fn raw_signo(&self) -> crate::ffi::c_int { |
302 | | self.0.si_signo |
303 | | } |
304 | | |
305 | | /// Return the raw `si_errno` value returned from `waitid`. |
306 | | #[cfg(linux_raw)] |
307 | 0 | pub fn raw_errno(&self) -> crate::ffi::c_int { |
308 | 0 | self.0.si_errno() |
309 | 0 | } |
310 | | |
311 | | /// Return the raw `si_errno` value returned from `waitid`. |
312 | | #[cfg(not(linux_raw))] |
313 | | pub fn raw_errno(&self) -> crate::ffi::c_int { |
314 | | self.0.si_errno |
315 | | } |
316 | | |
317 | | /// Return the raw `si_code` value returned from `waitid`. |
318 | | #[cfg(linux_raw)] |
319 | 0 | pub fn raw_code(&self) -> crate::ffi::c_int { |
320 | 0 | self.0.si_code() |
321 | 0 | } |
322 | | |
323 | | /// Return the raw `si_code` value returned from `waitid`. |
324 | | #[cfg(not(linux_raw))] |
325 | | pub fn raw_code(&self) -> crate::ffi::c_int { |
326 | | self.0.si_code |
327 | | } |
328 | | |
329 | | // This is disabled on NetBSD because the libc crate's `si_status()` |
330 | | // implementation doesn't appear to match what's in NetBSD's headers and we |
331 | | // don't get a meaningful value returned. |
332 | | // TODO: Report this upstream. |
333 | | #[cfg(not(any(target_os = "emscripten", target_os = "fuchsia", target_os = "netbsd")))] |
334 | | #[allow(unsafe_code)] |
335 | 0 | fn si_status(&self) -> crate::ffi::c_int { |
336 | 0 | // SAFETY: POSIX [specifies] that the `siginfo_t` returned by a |
337 | 0 | // `waitid` call always has a valid `si_status` value. |
338 | 0 | // |
339 | 0 | // [specifies]: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/signal.h.html |
340 | 0 | unsafe { self.0.si_status() } |
341 | 0 | } |
342 | | } |
343 | | |
344 | | #[cfg(not(any( |
345 | | target_os = "horizon", |
346 | | target_os = "openbsd", |
347 | | target_os = "redox", |
348 | | target_os = "wasi" |
349 | | )))] |
350 | | impl fmt::Debug for WaitIdStatus { |
351 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
352 | 0 | let mut s = f.debug_struct("WaitIdStatus"); |
353 | 0 | s.field("stopped", &self.stopped()); |
354 | 0 | s.field("exited", &self.exited()); |
355 | 0 | s.field("killed", &self.killed()); |
356 | 0 | s.field("trapped", &self.trapped()); |
357 | 0 | s.field("dumped", &self.dumped()); |
358 | 0 | s.field("continued", &self.continued()); |
359 | | #[cfg(not(any(target_os = "emscripten", target_os = "fuchsia", target_os = "netbsd")))] |
360 | 0 | if let Some(stopping_signal) = self.stopping_signal() { |
361 | 0 | s.field("stopping_signal", &stopping_signal); |
362 | 0 | } |
363 | | #[cfg(not(any(target_os = "emscripten", target_os = "fuchsia", target_os = "netbsd")))] |
364 | 0 | if let Some(trapping_signal) = self.trapping_signal() { |
365 | 0 | s.field("trapping_signal", &trapping_signal); |
366 | 0 | } |
367 | | #[cfg(not(any(target_os = "emscripten", target_os = "fuchsia", target_os = "netbsd")))] |
368 | 0 | if let Some(exit_status) = self.exit_status() { |
369 | 0 | s.field("exit_status", &exit_status); |
370 | 0 | } |
371 | | #[cfg(not(any(target_os = "emscripten", target_os = "fuchsia", target_os = "netbsd")))] |
372 | 0 | if let Some(terminating_signal) = self.terminating_signal() { |
373 | 0 | s.field("terminating_signal", &terminating_signal); |
374 | 0 | } |
375 | 0 | s.finish() |
376 | 0 | } |
377 | | } |
378 | | |
379 | | /// The identifier to wait on in a call to [`waitid`]. |
380 | | #[cfg(not(any(target_os = "openbsd", target_os = "redox", target_os = "wasi")))] |
381 | | #[derive(Debug, Clone)] |
382 | | #[non_exhaustive] |
383 | | pub enum WaitId<'a> { |
384 | | /// Wait on all processes. |
385 | | #[doc(alias = "P_ALL")] |
386 | | All, |
387 | | |
388 | | /// Wait for a specific process ID. |
389 | | #[doc(alias = "P_PID")] |
390 | | Pid(Pid), |
391 | | |
392 | | /// Wait for a specific process group ID, or the calling process' group ID. |
393 | | #[doc(alias = "P_PGID")] |
394 | | Pgid(Option<Pid>), |
395 | | |
396 | | /// Wait for a specific process file descriptor. |
397 | | #[cfg(target_os = "linux")] |
398 | | #[doc(alias = "P_PIDFD")] |
399 | | PidFd(BorrowedFd<'a>), |
400 | | |
401 | | /// Eat the lifetime for non-Linux platforms. |
402 | | #[doc(hidden)] |
403 | | #[cfg(not(target_os = "linux"))] |
404 | | __EatLifetime(core::marker::PhantomData<&'a ()>), |
405 | | } |
406 | | |
407 | | /// `waitpid(pid, waitopts)`—Wait for a specific process to change state. |
408 | | /// |
409 | | /// If the pid is `None`, the call will wait for any child process whose |
410 | | /// process group id matches that of the calling process. Otherwise, the call |
411 | | /// will wait for the child process with the given pid. |
412 | | /// |
413 | | /// On Success, returns the status of the selected process. |
414 | | /// |
415 | | /// If `NOHANG` was specified in the options, and the selected child process |
416 | | /// didn't change state, returns `None`. |
417 | | /// |
418 | | /// To wait for a given process group (the `< -1` case of `waitpid`), use |
419 | | /// [`waitpgid`] or [`waitid`]. To wait for any process (the `-1` case of |
420 | | /// `waitpid`), use [`wait`]. |
421 | | /// |
422 | | /// # References |
423 | | /// - [POSIX] |
424 | | /// - [Linux] |
425 | | /// |
426 | | /// [POSIX]: https://pubs.opengroup.org/onlinepubs/9799919799/functions/wait.html |
427 | | /// [Linux]: https://man7.org/linux/man-pages/man2/waitpid.2.html |
428 | | #[doc(alias = "wait4")] |
429 | | #[cfg(not(target_os = "wasi"))] |
430 | | #[inline] |
431 | 0 | pub fn waitpid(pid: Option<Pid>, waitopts: WaitOptions) -> io::Result<Option<(Pid, WaitStatus)>> { |
432 | 0 | backend::process::syscalls::waitpid(pid, waitopts) |
433 | 0 | } |
434 | | |
435 | | /// `waitpid(-pgid, waitopts)`—Wait for a process in a specific process group |
436 | | /// to change state. |
437 | | /// |
438 | | /// The call will wait for any child process with the given pgid. |
439 | | /// |
440 | | /// On Success, returns the status of the selected process. |
441 | | /// |
442 | | /// If `NOHANG` was specified in the options, and no selected child process |
443 | | /// changed state, returns `None`. |
444 | | /// |
445 | | /// # References |
446 | | /// - [POSIX] |
447 | | /// - [Linux] |
448 | | /// |
449 | | /// [POSIX]: https://pubs.opengroup.org/onlinepubs/9799919799/functions/wait.html |
450 | | /// [Linux]: https://man7.org/linux/man-pages/man2/waitpid.2.html |
451 | | #[cfg(not(target_os = "wasi"))] |
452 | | #[inline] |
453 | 0 | pub fn waitpgid(pgid: Pid, waitopts: WaitOptions) -> io::Result<Option<(Pid, WaitStatus)>> { |
454 | 0 | backend::process::syscalls::waitpgid(pgid, waitopts) |
455 | 0 | } |
456 | | |
457 | | /// `wait(waitopts)`—Wait for any of the children of calling process to |
458 | | /// change state. |
459 | | /// |
460 | | /// On success, returns the pid of the child process whose state changed, and |
461 | | /// the status of said process. |
462 | | /// |
463 | | /// If `NOHANG` was specified in the options, and the selected child process |
464 | | /// didn't change state, returns `None`. |
465 | | /// |
466 | | /// # References |
467 | | /// - [POSIX] |
468 | | /// - [Linux] |
469 | | /// |
470 | | /// [POSIX]: https://pubs.opengroup.org/onlinepubs/9799919799/functions/wait.html |
471 | | /// [Linux]: https://man7.org/linux/man-pages/man2/waitpid.2.html |
472 | | #[cfg(not(target_os = "wasi"))] |
473 | | #[inline] |
474 | 0 | pub fn wait(waitopts: WaitOptions) -> io::Result<Option<(Pid, WaitStatus)>> { |
475 | 0 | backend::process::syscalls::wait(waitopts) |
476 | 0 | } |
477 | | |
478 | | /// `waitid(_, _, _, opts)`—Wait for the specified child process to change |
479 | | /// state. |
480 | | #[cfg(not(any( |
481 | | target_os = "cygwin", |
482 | | target_os = "horizon", |
483 | | target_os = "openbsd", |
484 | | target_os = "redox", |
485 | | target_os = "wasi", |
486 | | )))] |
487 | | #[inline] |
488 | 0 | pub fn waitid<'a, Id: Into<WaitId<'a>>>( |
489 | 0 | id: Id, |
490 | 0 | options: WaitIdOptions, |
491 | 0 | ) -> io::Result<Option<WaitIdStatus>> { |
492 | 0 | backend::process::syscalls::waitid(id.into(), options) |
493 | 0 | } |