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