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