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 | /// |
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")))] |
34 | bitflags! { |
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)] |
61 | pub struct WaitStatus(u32); |
62 | |
63 | impl 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")))] |
138 | pub struct WaitidStatus(pub(crate) backend::c::siginfo_t); |
139 | |
140 | #[cfg(not(any(target_os = "openbsd", target_os = "redox", target_os = "wasi")))] |
141 | impl 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] |
259 | pub 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] |
313 | pub fn waitpid(pid: Option<Pid>, waitopts: WaitOptions) -> io::Result<Option<WaitStatus>> { |
314 | Ok(backend::process::syscalls::waitpid(pid, waitopts)?.map(|(_, status: WaitStatus)| 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] |
335 | pub fn waitpgid(pgid: Pid, waitopts: WaitOptions) -> io::Result<Option<WaitStatus>> { |
336 | Ok(backend::process::syscalls::waitpgid(pgid, waitopts)?.map(|(_, status: WaitStatus)| 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] |
356 | pub 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] |
364 | pub 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 | } |
370 |
Definitions
Learn Rust with the experts
Find out more