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}