rustix/process/
wait.rs

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)]
9use crate::process::Pid;
10use crate::{backend, io};
11use bitflags::bitflags;
12use core::fmt;
13
14#[cfg(target_os = "linux")]
15use crate::fd::BorrowedFd;
16
17#[cfg(linux_raw)]
18use crate::backend::process::wait::SiginfoExt as _;
19
20bitflags! {
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)))]
50bitflags! {
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)]
80pub struct WaitStatus(i32);
81
82impl WaitStatus {
83    /// Creates a `WaitStatus` out of an integer.
84    #[inline]
85    pub(crate) fn new(status: i32) -> Self {
86        Self(status)
87    }
88
89    /// Converts a `WaitStatus` into its raw representation as an integer.
90    #[inline]
91    pub const fn as_raw(self) -> i32 {
92        self.0
93    }
94
95    /// Returns whether the process is currently stopped.
96    #[inline]
97    #[doc(alias = "WIFSTOPPED")]
98    pub fn stopped(self) -> bool {
99        backend::process::wait::WIFSTOPPED(self.0)
100    }
101
102    /// Returns whether the process has exited normally.
103    #[inline]
104    #[doc(alias = "WIFEXITED")]
105    pub fn exited(self) -> bool {
106        backend::process::wait::WIFEXITED(self.0)
107    }
108
109    /// Returns whether the process was terminated by a signal.
110    #[inline]
111    #[doc(alias = "WIFSIGNALED")]
112    pub fn signaled(self) -> bool {
113        backend::process::wait::WIFSIGNALED(self.0)
114    }
115
116    /// Returns whether the process has continued from a job control stop.
117    #[inline]
118    #[doc(alias = "WIFCONTINUED")]
119    pub fn continued(self) -> bool {
120        backend::process::wait::WIFCONTINUED(self.0)
121    }
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    pub fn stopping_signal(self) -> Option<i32> {
128        if self.stopped() {
129            Some(backend::process::wait::WSTOPSIG(self.0))
130        } else {
131            None
132        }
133    }
134
135    /// Returns the exit status number returned by the process, if it exited
136    /// normally.
137    #[inline]
138    #[doc(alias = "WEXITSTATUS")]
139    pub fn exit_status(self) -> Option<i32> {
140        if self.exited() {
141            Some(backend::process::wait::WEXITSTATUS(self.0))
142        } else {
143            None
144        }
145    }
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    pub fn terminating_signal(self) -> Option<i32> {
152        if self.signaled() {
153            Some(backend::process::wait::WTERMSIG(self.0))
154        } else {
155            None
156        }
157    }
158}
159
160impl fmt::Debug for WaitStatus {
161    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
162        let mut s = f.debug_struct("WaitStatus");
163        s.field("stopped", &self.stopped());
164        s.field("exited", &self.exited());
165        s.field("signaled", &self.signaled());
166        s.field("continued", &self.continued());
167        if let Some(stopping_signal) = self.stopping_signal() {
168            s.field("stopping_signal", &stopping_signal);
169        }
170        if let Some(exit_status) = self.exit_status() {
171            s.field("exit_status", &exit_status);
172        }
173        if let Some(terminating_signal) = self.terminating_signal() {
174            s.field("terminating_signal", &terminating_signal);
175        }
176        s.finish()
177    }
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)))]
189pub 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.
194unsafe impl Send for WaitIdStatus {}
195
196#[cfg(linux_raw)]
197// SAFETY: Same as with `Send`.
198unsafe 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)))]
206impl WaitIdStatus {
207    /// Returns whether the process is currently stopped.
208    #[inline]
209    pub fn stopped(&self) -> bool {
210        self.raw_code() == bitcast!(backend::c::CLD_STOPPED)
211    }
212
213    /// Returns whether the process is currently trapped.
214    #[inline]
215    pub fn trapped(&self) -> bool {
216        self.raw_code() == bitcast!(backend::c::CLD_TRAPPED)
217    }
218
219    /// Returns whether the process has exited normally.
220    #[inline]
221    pub fn exited(&self) -> bool {
222        self.raw_code() == bitcast!(backend::c::CLD_EXITED)
223    }
224
225    /// Returns whether the process was terminated by a signal and did not
226    /// create a core file.
227    #[inline]
228    pub fn killed(&self) -> bool {
229        self.raw_code() == bitcast!(backend::c::CLD_KILLED)
230    }
231
232    /// Returns whether the process was terminated by a signal and did create a
233    /// core file.
234    #[inline]
235    pub fn dumped(&self) -> bool {
236        self.raw_code() == bitcast!(backend::c::CLD_DUMPED)
237    }
238
239    /// Returns whether the process has continued from a job control stop.
240    #[inline]
241    pub fn continued(&self) -> bool {
242        self.raw_code() == bitcast!(backend::c::CLD_CONTINUED)
243    }
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    pub fn stopping_signal(&self) -> Option<i32> {
250        if self.stopped() {
251            Some(self.si_status())
252        } else {
253            None
254        }
255    }
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    pub fn trapping_signal(&self) -> Option<i32> {
262        if self.trapped() {
263            Some(self.si_status())
264        } else {
265            None
266        }
267    }
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    pub fn exit_status(&self) -> Option<i32> {
274        if self.exited() {
275            Some(self.si_status())
276        } else {
277            None
278        }
279    }
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    pub fn terminating_signal(&self) -> Option<i32> {
286        if self.killed() || self.dumped() {
287            Some(self.si_status())
288        } else {
289            None
290        }
291    }
292
293    /// Return the raw `si_signo` value returned from `waitid`.
294    #[cfg(linux_raw)]
295    pub fn raw_signo(&self) -> crate::ffi::c_int {
296        self.0.si_signo()
297    }
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    pub fn raw_errno(&self) -> crate::ffi::c_int {
308        self.0.si_errno()
309    }
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    pub fn raw_code(&self) -> crate::ffi::c_int {
320        self.0.si_code()
321    }
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    fn si_status(&self) -> crate::ffi::c_int {
336        // SAFETY: POSIX [specifies] that the `siginfo_t` returned by a
337        // `waitid` call always has a valid `si_status` value.
338        //
339        // [specifies]: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/signal.h.html
340        unsafe { self.0.si_status() }
341    }
342}
343
344#[cfg(not(any(
345    target_os = "horizon",
346    target_os = "openbsd",
347    target_os = "redox",
348    target_os = "wasi"
349)))]
350impl fmt::Debug for WaitIdStatus {
351    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
352        let mut s = f.debug_struct("WaitIdStatus");
353        s.field("stopped", &self.stopped());
354        s.field("exited", &self.exited());
355        s.field("killed", &self.killed());
356        s.field("trapped", &self.trapped());
357        s.field("dumped", &self.dumped());
358        s.field("continued", &self.continued());
359        #[cfg(not(any(target_os = "emscripten", target_os = "fuchsia", target_os = "netbsd")))]
360        if let Some(stopping_signal) = self.stopping_signal() {
361            s.field("stopping_signal", &stopping_signal);
362        }
363        #[cfg(not(any(target_os = "emscripten", target_os = "fuchsia", target_os = "netbsd")))]
364        if let Some(trapping_signal) = self.trapping_signal() {
365            s.field("trapping_signal", &trapping_signal);
366        }
367        #[cfg(not(any(target_os = "emscripten", target_os = "fuchsia", target_os = "netbsd")))]
368        if let Some(exit_status) = self.exit_status() {
369            s.field("exit_status", &exit_status);
370        }
371        #[cfg(not(any(target_os = "emscripten", target_os = "fuchsia", target_os = "netbsd")))]
372        if let Some(terminating_signal) = self.terminating_signal() {
373            s.field("terminating_signal", &terminating_signal);
374        }
375        s.finish()
376    }
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]
383pub 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]
431pub fn waitpid(pid: Option<Pid>, waitopts: WaitOptions) -> io::Result<Option<(Pid, WaitStatus)>> {
432    backend::process::syscalls::waitpid(pid, waitopts)
433}
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]
453pub fn waitpgid(pgid: Pid, waitopts: WaitOptions) -> io::Result<Option<(Pid, WaitStatus)>> {
454    backend::process::syscalls::waitpgid(pgid, waitopts)
455}
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]
474pub fn wait(waitopts: WaitOptions) -> io::Result<Option<(Pid, WaitStatus)>> {
475    backend::process::syscalls::wait(waitopts)
476}
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]
488pub fn waitid<'a, Id: Into<WaitId<'a>>>(
489    id: Id,
490    options: WaitIdOptions,
491) -> io::Result<Option<WaitIdStatus>> {
492    backend::process::syscalls::waitid(id.into(), options)
493}