rustix/process/
wait.rs

1use crate::process::Pid;
2use crate::{backend, io};
3use bitflags::bitflags;
4
5#[cfg(target_os = "linux")]
6use crate::fd::BorrowedFd;
7
8#[cfg(linux_raw)]
9use crate::backend::process::wait::SiginfoExt;
10
11bitflags! {
12    /// Options for modifying the behavior of [`wait`]/[`waitpid`].
13    #[repr(transparent)]
14    #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
15    pub struct WaitOptions: u32 {
16        /// Return immediately if no child has exited.
17        const NOHANG = bitcast!(backend::process::wait::WNOHANG);
18        /// Return if a child has stopped (but not traced via [`ptrace`]).
19        ///
20        /// [`ptrace`]: https://man7.org/linux/man-pages/man2/ptrace.2.html
21        const UNTRACED = bitcast!(backend::process::wait::WUNTRACED);
22        /// Return if a stopped child has been resumed by delivery of
23        /// [`Signal::Cont`].
24        ///
25        /// [`Signal::Cont`]: crate::process::Signal::Cont
26        const CONTINUED = bitcast!(backend::process::wait::WCONTINUED);
27
28        /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
29        const _ = !0;
30    }
31}
32
33#[cfg(not(any(target_os = "openbsd", target_os = "redox", target_os = "wasi")))]
34bitflags! {
35    /// Options for modifying the behavior of [`waitid`].
36    #[repr(transparent)]
37    #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
38    pub struct WaitidOptions: u32 {
39        /// Return immediately if no child has exited.
40        const NOHANG = bitcast!(backend::process::wait::WNOHANG);
41        /// Return if a stopped child has been resumed by delivery of
42        /// [`Signal::Cont`].
43        ///
44        /// [`Signal::Cont`]: crate::process::Signal::Cont
45        const CONTINUED = bitcast!(backend::process::wait::WCONTINUED);
46        /// Wait for processed that have exited.
47        const EXITED = bitcast!(backend::process::wait::WEXITED);
48        /// Keep processed in a waitable state.
49        const NOWAIT = bitcast!(backend::process::wait::WNOWAIT);
50        /// Wait for processes that have been stopped.
51        const STOPPED = bitcast!(backend::process::wait::WSTOPPED);
52
53        /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
54        const _ = !0;
55    }
56}
57
58/// The status of a child process after calling [`wait`]/[`waitpid`].
59#[derive(Debug, Clone, Copy)]
60#[repr(transparent)]
61pub struct WaitStatus(u32);
62
63impl WaitStatus {
64    /// Creates a `WaitStatus` out of an integer.
65    #[inline]
66    pub(crate) fn new(status: u32) -> Self {
67        Self(status)
68    }
69
70    /// Converts a `WaitStatus` into its raw representation as an integer.
71    #[inline]
72    pub const fn as_raw(self) -> u32 {
73        self.0
74    }
75
76    /// Returns whether the process is currently stopped.
77    #[inline]
78    pub fn stopped(self) -> bool {
79        backend::process::wait::WIFSTOPPED(self.0 as _)
80    }
81
82    /// Returns whether the process has exited normally.
83    #[inline]
84    pub fn exited(self) -> bool {
85        backend::process::wait::WIFEXITED(self.0 as _)
86    }
87
88    /// Returns whether the process was terminated by a signal.
89    #[inline]
90    pub fn signaled(self) -> bool {
91        backend::process::wait::WIFSIGNALED(self.0 as _)
92    }
93
94    /// Returns whether the process has continued from a job control stop.
95    #[inline]
96    pub fn continued(self) -> bool {
97        backend::process::wait::WIFCONTINUED(self.0 as _)
98    }
99
100    /// Returns the number of the signal that stopped the process, if the
101    /// process was stopped by a signal.
102    #[inline]
103    pub fn stopping_signal(self) -> Option<u32> {
104        if self.stopped() {
105            Some(backend::process::wait::WSTOPSIG(self.0 as _) as _)
106        } else {
107            None
108        }
109    }
110
111    /// Returns the exit status number returned by the process, if it exited
112    /// normally.
113    #[inline]
114    pub fn exit_status(self) -> Option<u32> {
115        if self.exited() {
116            Some(backend::process::wait::WEXITSTATUS(self.0 as _) as _)
117        } else {
118            None
119        }
120    }
121
122    /// Returns the number of the signal that terminated the process, if the
123    /// process was terminated by a signal.
124    #[inline]
125    pub fn terminating_signal(self) -> Option<u32> {
126        if self.signaled() {
127            Some(backend::process::wait::WTERMSIG(self.0 as _) as _)
128        } else {
129            None
130        }
131    }
132}
133
134/// The status of a process after calling [`waitid`].
135#[derive(Clone, Copy)]
136#[repr(transparent)]
137#[cfg(not(any(target_os = "openbsd", target_os = "redox", target_os = "wasi")))]
138pub struct WaitidStatus(pub(crate) backend::c::siginfo_t);
139
140#[cfg(not(any(target_os = "openbsd", target_os = "redox", target_os = "wasi")))]
141impl WaitidStatus {
142    /// Returns whether the process is currently stopped.
143    #[inline]
144    pub fn stopped(&self) -> bool {
145        self.si_code() == backend::c::CLD_STOPPED
146    }
147
148    /// Returns whether the process is currently trapped.
149    #[inline]
150    pub fn trapped(&self) -> bool {
151        self.si_code() == backend::c::CLD_TRAPPED
152    }
153
154    /// Returns whether the process has exited normally.
155    #[inline]
156    pub fn exited(&self) -> bool {
157        self.si_code() == backend::c::CLD_EXITED
158    }
159
160    /// Returns whether the process was terminated by a signal and did not
161    /// create a core file.
162    #[inline]
163    pub fn killed(&self) -> bool {
164        self.si_code() == backend::c::CLD_KILLED
165    }
166
167    /// Returns whether the process was terminated by a signal and did create a
168    /// core file.
169    #[inline]
170    pub fn dumped(&self) -> bool {
171        self.si_code() == backend::c::CLD_DUMPED
172    }
173
174    /// Returns whether the process has continued from a job control stop.
175    #[inline]
176    pub fn continued(&self) -> bool {
177        self.si_code() == backend::c::CLD_CONTINUED
178    }
179
180    /// Returns the number of the signal that stopped the process, if the
181    /// process was stopped by a signal.
182    #[inline]
183    #[cfg(not(any(target_os = "emscripten", target_os = "fuchsia", target_os = "netbsd")))]
184    pub fn stopping_signal(&self) -> Option<u32> {
185        if self.stopped() {
186            Some(self.si_status() as _)
187        } else {
188            None
189        }
190    }
191
192    /// Returns the number of the signal that trapped the process, if the
193    /// process was trapped by a signal.
194    #[inline]
195    #[cfg(not(any(target_os = "emscripten", target_os = "fuchsia", target_os = "netbsd")))]
196    pub fn trapping_signal(&self) -> Option<u32> {
197        if self.trapped() {
198            Some(self.si_status() as _)
199        } else {
200            None
201        }
202    }
203
204    /// Returns the exit status number returned by the process, if it exited
205    /// normally.
206    #[inline]
207    #[cfg(not(any(target_os = "emscripten", target_os = "fuchsia", target_os = "netbsd")))]
208    pub fn exit_status(&self) -> Option<u32> {
209        if self.exited() {
210            Some(self.si_status() as _)
211        } else {
212            None
213        }
214    }
215
216    /// Returns the number of the signal that terminated the process, if the
217    /// process was terminated by a signal.
218    #[inline]
219    #[cfg(not(any(target_os = "emscripten", target_os = "fuchsia", target_os = "netbsd")))]
220    pub fn terminating_signal(&self) -> Option<u32> {
221        if self.killed() || self.dumped() {
222            Some(self.si_status() as _)
223        } else {
224            None
225        }
226    }
227
228    /// Returns a reference to the raw platform-specific `siginfo_t` struct.
229    #[inline]
230    pub const fn as_raw(&self) -> &backend::c::siginfo_t {
231        &self.0
232    }
233
234    #[cfg(linux_raw)]
235    fn si_code(&self) -> u32 {
236        self.0.si_code() as u32 // CLD_ consts are unsigned
237    }
238
239    #[cfg(not(linux_raw))]
240    fn si_code(&self) -> backend::c::c_int {
241        self.0.si_code
242    }
243
244    #[cfg(not(any(target_os = "emscripten", target_os = "fuchsia", target_os = "netbsd")))]
245    #[allow(unsafe_code)]
246    fn si_status(&self) -> backend::c::c_int {
247        // SAFETY: POSIX [specifies] that the `siginfo_t` returned by a
248        // `waitid` call always has a valid `si_status` value.
249        //
250        // [specifies]: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/signal.h.html
251        unsafe { self.0.si_status() }
252    }
253}
254
255/// The identifier to wait on in a call to [`waitid`].
256#[cfg(not(any(target_os = "openbsd", target_os = "redox", target_os = "wasi")))]
257#[derive(Debug, Clone)]
258#[non_exhaustive]
259pub enum WaitId<'a> {
260    /// Wait on all processes.
261    #[doc(alias = "P_ALL")]
262    All,
263
264    /// Wait for a specific process ID.
265    #[doc(alias = "P_PID")]
266    Pid(Pid),
267
268    /// Wait for a specific process group ID, or the calling process' group ID.
269    #[doc(alias = "P_PGID")]
270    Pgid(Option<Pid>),
271
272    /// Wait for a specific process file descriptor.
273    #[cfg(target_os = "linux")]
274    #[doc(alias = "P_PIDFD")]
275    PidFd(BorrowedFd<'a>),
276
277    /// Eat the lifetime for non-Linux platforms.
278    #[doc(hidden)]
279    #[cfg(not(target_os = "linux"))]
280    __EatLifetime(core::marker::PhantomData<&'a ()>),
281}
282
283/// `waitpid(pid, waitopts)`—Wait for a specific process to change state.
284///
285/// If the pid is `None`, the call will wait for any child process whose
286/// process group id matches that of the calling process.
287///
288/// Otherwise, the call will wait for the child process with the given pid.
289///
290/// On Success, returns the status of the selected process.
291///
292/// If `NOHANG` was specified in the options, and the selected child process
293/// didn't change state, returns `None`.
294///
295/// # Bugs
296///
297/// This function does not currently support waiting for given process group
298/// (the < 0 case of `waitpid`); to do that, currently the [`waitpgid`] or
299/// [`waitid`] function must be used.
300///
301/// This function does not currently support waiting for any process (the
302/// `-1` case of `waitpid`); to do that, currently the [`wait`] function must
303/// be used.
304///
305/// # References
306///  - [POSIX]
307///  - [Linux]
308///
309/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9799919799/functions/wait.html
310/// [Linux]: https://man7.org/linux/man-pages/man2/waitpid.2.html
311#[cfg(not(target_os = "wasi"))]
312#[inline]
313pub fn waitpid(pid: Option<Pid>, waitopts: WaitOptions) -> io::Result<Option<WaitStatus>> {
314    Ok(backend::process::syscalls::waitpid(pid, waitopts)?.map(|(_, status)| status))
315}
316
317/// `waitpid(-pgid, waitopts)`—Wait for a process in a specific process group
318/// to change state.
319///
320/// The call will wait for any child process with the given pgid.
321///
322/// On Success, returns the status of the selected process.
323///
324/// If `NOHANG` was specified in the options, and no selected child process
325/// changed state, returns `None`.
326///
327/// # References
328///  - [POSIX]
329///  - [Linux]
330///
331/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9799919799/functions/wait.html
332/// [Linux]: https://man7.org/linux/man-pages/man2/waitpid.2.html
333#[cfg(not(target_os = "wasi"))]
334#[inline]
335pub fn waitpgid(pgid: Pid, waitopts: WaitOptions) -> io::Result<Option<WaitStatus>> {
336    Ok(backend::process::syscalls::waitpgid(pgid, waitopts)?.map(|(_, status)| status))
337}
338
339/// `wait(waitopts)`—Wait for any of the children of calling process to
340/// change state.
341///
342/// On success, returns the pid of the child process whose state changed, and
343/// the status of said process.
344///
345/// If `NOHANG` was specified in the options, and the selected child process
346/// didn't change state, returns `None`.
347///
348/// # References
349///  - [POSIX]
350///  - [Linux]
351///
352/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9799919799/functions/wait.html
353/// [Linux]: https://man7.org/linux/man-pages/man2/waitpid.2.html
354#[cfg(not(target_os = "wasi"))]
355#[inline]
356pub fn wait(waitopts: WaitOptions) -> io::Result<Option<(Pid, WaitStatus)>> {
357    backend::process::syscalls::wait(waitopts)
358}
359
360/// `waitid(_, _, _, opts)`—Wait for the specified child process to change
361/// state.
362#[cfg(not(any(target_os = "openbsd", target_os = "redox", target_os = "wasi")))]
363#[inline]
364pub fn waitid<'a>(
365    id: impl Into<WaitId<'a>>,
366    options: WaitidOptions,
367) -> io::Result<Option<WaitidStatus>> {
368    backend::process::syscalls::waitid(id.into(), options)
369}