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