1 use crate::process::Pid;
2 use crate::{backend, io};
3 use bitflags::bitflags;
4
5 #[cfg(target_os = "linux")]
6 use crate::fd::BorrowedFd;
7
8 #[cfg(linux_raw)]
9 use crate::backend::process::wait::SiginfoExt;
10
11 bitflags! {
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")))]
32 bitflags! {
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)]
57 pub struct WaitStatus(u32);
58
59 impl WaitStatus {
60 /// Creates a `WaitStatus` out of an integer.
61 #[inline]
new(status: u32) -> Self62 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]
as_raw(self) -> u3268 pub const fn as_raw(self) -> u32 {
69 self.0
70 }
71
72 /// Returns whether the process is currently stopped.
73 #[inline]
stopped(self) -> bool74 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]
exited(self) -> bool80 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]
signaled(self) -> bool86 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]
continued(self) -> bool92 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]
stopping_signal(self) -> Option<u32>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]
exit_status(self) -> Option<u32>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]
terminating_signal(self) -> Option<u32>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")))]
134 pub struct WaitidStatus(pub(crate) backend::c::siginfo_t);
135
136 #[cfg(not(any(target_os = "openbsd", target_os = "redox", target_os = "wasi")))]
137 impl WaitidStatus {
138 /// Returns whether the process is currently stopped.
139 #[inline]
stopped(&self) -> bool140 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]
trapped(&self) -> bool146 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]
exited(&self) -> bool152 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]
killed(&self) -> bool159 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]
dumped(&self) -> bool166 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]
continued(&self) -> bool172 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")))]
stopping_signal(&self) -> Option<u32>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")))]
trapping_signal(&self) -> Option<u32>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")))]
exit_status(&self) -> Option<u32>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")))]
terminating_signal(&self) -> Option<u32>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]
as_raw(&self) -> &backend::c::siginfo_t226 pub const fn as_raw(&self) -> &backend::c::siginfo_t {
227 &self.0
228 }
229
230 #[cfg(linux_raw)]
si_code(&self) -> u32231 fn si_code(&self) -> u32 {
232 self.0.si_code() as u32 // CLD_ consts are unsigned
233 }
234
235 #[cfg(not(linux_raw))]
si_code(&self) -> backend::c::c_int236 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)]
si_status(&self) -> backend::c::c_int242 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]
255 pub 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]
waitpid(pid: Option<Pid>, waitopts: WaitOptions) -> io::Result<Option<WaitStatus>>309 pub fn waitpid(pid: Option<Pid>, waitopts: WaitOptions) -> io::Result<Option<WaitStatus>> {
310 Ok(backend::process::syscalls::waitpid(pid, waitopts)?.map(|(_, status)| 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]
waitpgid(pgid: Pid, waitopts: WaitOptions) -> io::Result<Option<WaitStatus>>331 pub fn waitpgid(pgid: Pid, waitopts: WaitOptions) -> io::Result<Option<WaitStatus>> {
332 Ok(backend::process::syscalls::waitpgid(pgid, waitopts)?.map(|(_, status)| 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]
wait(waitopts: WaitOptions) -> io::Result<Option<(Pid, WaitStatus)>>352 pub 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]
waitid<'a>( id: impl Into<WaitId<'a>>, options: WaitidOptions, ) -> io::Result<Option<WaitidStatus>>360 pub fn waitid<'a>(
361 id: impl Into<WaitId<'a>>,
362 options: WaitidOptions,
363 ) -> io::Result<Option<WaitidStatus>> {
364 backend::process::syscalls::waitid(id.into(), options)
365 }
366