Coverage Report

Created: 2025-07-12 06:48

/rust/registry/src/index.crates.io-6f17d22bba15001f/rustix-1.0.7/src/process/wait.rs
Line
Count
Source (jump to first uncovered line)
1
//! Wait for processes to change state.
2
//!
3
//! # Safety
4
//!
5
//! This code needs to implement `Send` and `Sync` for `WaitIdStatus` because
6
//! the linux-raw-sys bindings generate a type that doesn't do so
7
//! automatically.
8
#![allow(unsafe_code)]
9
use crate::process::Pid;
10
use crate::{backend, io};
11
use bitflags::bitflags;
12
use core::fmt;
13
14
#[cfg(target_os = "linux")]
15
use crate::fd::BorrowedFd;
16
17
#[cfg(linux_raw)]
18
use crate::backend::process::wait::SiginfoExt as _;
19
20
bitflags! {
21
    /// Options for modifying the behavior of [`wait`]/[`waitpid`].
22
    #[repr(transparent)]
23
    #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
24
    pub struct WaitOptions: u32 {
25
        /// Return immediately if no child has exited.
26
        const NOHANG = bitcast!(backend::process::wait::WNOHANG);
27
        /// Return if a child has stopped (but not traced via [`ptrace`]).
28
        ///
29
        /// [`ptrace`]: https://man7.org/linux/man-pages/man2/ptrace.2.html
30
        #[cfg(not(target_os = "horizon"))]
31
        const UNTRACED = bitcast!(backend::process::wait::WUNTRACED);
32
        /// Return if a stopped child has been resumed by delivery of
33
        /// [`Signal::Cont`].
34
        ///
35
        /// [`Signal::Cont`]: crate::process::Signal::Cont
36
        #[cfg(not(target_os = "horizon"))]
37
        const CONTINUED = bitcast!(backend::process::wait::WCONTINUED);
38
39
        /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
40
        const _ = !0;
41
    }
42
}
43
44
#[cfg(not(any(
45
    target_os = "horizon",
46
    target_os = "openbsd",
47
    target_os = "redox",
48
    target_os = "wasi"
49
)))]
50
bitflags! {
51
    /// Options for modifying the behavior of [`waitid`].
52
    #[repr(transparent)]
53
    #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
54
    pub struct WaitIdOptions: u32 {
55
        /// Return immediately if no child has exited.
56
        const NOHANG = bitcast!(backend::process::wait::WNOHANG);
57
        /// Return if a stopped child has been resumed by delivery of
58
        /// [`Signal::Cont`].
59
        ///
60
        /// [`Signal::Cont`]: crate::process::Signal::Cont
61
        const CONTINUED = bitcast!(backend::process::wait::WCONTINUED);
62
        /// Wait for processed that have exited.
63
        #[cfg(not(target_os = "cygwin"))]
64
        const EXITED = bitcast!(backend::process::wait::WEXITED);
65
        /// Keep processed in a waitable state.
66
        #[cfg(not(target_os = "cygwin"))]
67
        const NOWAIT = bitcast!(backend::process::wait::WNOWAIT);
68
        /// Wait for processes that have been stopped.
69
        #[cfg(not(target_os = "cygwin"))]
70
        const STOPPED = bitcast!(backend::process::wait::WSTOPPED);
71
72
        /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
73
        const _ = !0;
74
    }
75
}
76
77
/// The status of a child process after calling [`wait`]/[`waitpid`].
78
#[derive(Clone, Copy)]
79
#[repr(transparent)]
80
pub struct WaitStatus(i32);
81
82
impl WaitStatus {
83
    /// Creates a `WaitStatus` out of an integer.
84
    #[inline]
85
0
    pub(crate) fn new(status: i32) -> Self {
86
0
        Self(status)
87
0
    }
88
89
    /// Converts a `WaitStatus` into its raw representation as an integer.
90
    #[inline]
91
0
    pub const fn as_raw(self) -> i32 {
92
0
        self.0
93
0
    }
94
95
    /// Returns whether the process is currently stopped.
96
    #[inline]
97
    #[doc(alias = "WIFSTOPPED")]
98
0
    pub fn stopped(self) -> bool {
99
0
        backend::process::wait::WIFSTOPPED(self.0)
100
0
    }
101
102
    /// Returns whether the process has exited normally.
103
    #[inline]
104
    #[doc(alias = "WIFEXITED")]
105
0
    pub fn exited(self) -> bool {
106
0
        backend::process::wait::WIFEXITED(self.0)
107
0
    }
108
109
    /// Returns whether the process was terminated by a signal.
110
    #[inline]
111
    #[doc(alias = "WIFSIGNALED")]
112
0
    pub fn signaled(self) -> bool {
113
0
        backend::process::wait::WIFSIGNALED(self.0)
114
0
    }
115
116
    /// Returns whether the process has continued from a job control stop.
117
    #[inline]
118
    #[doc(alias = "WIFCONTINUED")]
119
0
    pub fn continued(self) -> bool {
120
0
        backend::process::wait::WIFCONTINUED(self.0)
121
0
    }
122
123
    /// Returns the number of the signal that stopped the process, if the
124
    /// process was stopped by a signal.
125
    #[inline]
126
    #[doc(alias = "WSTOPSIG")]
127
0
    pub fn stopping_signal(self) -> Option<i32> {
128
0
        if self.stopped() {
129
0
            Some(backend::process::wait::WSTOPSIG(self.0))
130
        } else {
131
0
            None
132
        }
133
0
    }
134
135
    /// Returns the exit status number returned by the process, if it exited
136
    /// normally.
137
    #[inline]
138
    #[doc(alias = "WEXITSTATUS")]
139
0
    pub fn exit_status(self) -> Option<i32> {
140
0
        if self.exited() {
141
0
            Some(backend::process::wait::WEXITSTATUS(self.0))
142
        } else {
143
0
            None
144
        }
145
0
    }
146
147
    /// Returns the number of the signal that terminated the process, if the
148
    /// process was terminated by a signal.
149
    #[inline]
150
    #[doc(alias = "WTERMSIG")]
151
0
    pub fn terminating_signal(self) -> Option<i32> {
152
0
        if self.signaled() {
153
0
            Some(backend::process::wait::WTERMSIG(self.0))
154
        } else {
155
0
            None
156
        }
157
0
    }
158
}
159
160
impl fmt::Debug for WaitStatus {
161
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
162
0
        let mut s = f.debug_struct("WaitStatus");
163
0
        s.field("stopped", &self.stopped());
164
0
        s.field("exited", &self.exited());
165
0
        s.field("signaled", &self.signaled());
166
0
        s.field("continued", &self.continued());
167
0
        if let Some(stopping_signal) = self.stopping_signal() {
168
0
            s.field("stopping_signal", &stopping_signal);
169
0
        }
170
0
        if let Some(exit_status) = self.exit_status() {
171
0
            s.field("exit_status", &exit_status);
172
0
        }
173
0
        if let Some(terminating_signal) = self.terminating_signal() {
174
0
            s.field("terminating_signal", &terminating_signal);
175
0
        }
176
0
        s.finish()
177
0
    }
178
}
179
180
/// The status of a process after calling [`waitid`].
181
#[derive(Clone, Copy)]
182
#[repr(transparent)]
183
#[cfg(not(any(
184
    target_os = "horizon",
185
    target_os = "openbsd",
186
    target_os = "redox",
187
    target_os = "wasi"
188
)))]
189
pub struct WaitIdStatus(pub(crate) backend::c::siginfo_t);
190
191
#[cfg(linux_raw)]
192
// SAFETY: `siginfo_t` does contain some raw pointers, such as the `si_ptr`
193
// and the `si_addr` fields, however it's up to users to use those correctly.
194
unsafe impl Send for WaitIdStatus {}
195
196
#[cfg(linux_raw)]
197
// SAFETY: Same as with `Send`.
198
unsafe impl Sync for WaitIdStatus {}
199
200
#[cfg(not(any(
201
    target_os = "horizon",
202
    target_os = "openbsd",
203
    target_os = "redox",
204
    target_os = "wasi"
205
)))]
206
impl WaitIdStatus {
207
    /// Returns whether the process is currently stopped.
208
    #[inline]
209
0
    pub fn stopped(&self) -> bool {
210
0
        self.raw_code() == bitcast!(backend::c::CLD_STOPPED)
211
0
    }
212
213
    /// Returns whether the process is currently trapped.
214
    #[inline]
215
0
    pub fn trapped(&self) -> bool {
216
0
        self.raw_code() == bitcast!(backend::c::CLD_TRAPPED)
217
0
    }
218
219
    /// Returns whether the process has exited normally.
220
    #[inline]
221
0
    pub fn exited(&self) -> bool {
222
0
        self.raw_code() == bitcast!(backend::c::CLD_EXITED)
223
0
    }
224
225
    /// Returns whether the process was terminated by a signal and did not
226
    /// create a core file.
227
    #[inline]
228
0
    pub fn killed(&self) -> bool {
229
0
        self.raw_code() == bitcast!(backend::c::CLD_KILLED)
230
0
    }
231
232
    /// Returns whether the process was terminated by a signal and did create a
233
    /// core file.
234
    #[inline]
235
0
    pub fn dumped(&self) -> bool {
236
0
        self.raw_code() == bitcast!(backend::c::CLD_DUMPED)
237
0
    }
238
239
    /// Returns whether the process has continued from a job control stop.
240
    #[inline]
241
0
    pub fn continued(&self) -> bool {
242
0
        self.raw_code() == bitcast!(backend::c::CLD_CONTINUED)
243
0
    }
244
245
    /// Returns the number of the signal that stopped the process, if the
246
    /// process was stopped by a signal.
247
    #[inline]
248
    #[cfg(not(any(target_os = "emscripten", target_os = "fuchsia", target_os = "netbsd")))]
249
0
    pub fn stopping_signal(&self) -> Option<i32> {
250
0
        if self.stopped() {
251
0
            Some(self.si_status())
252
        } else {
253
0
            None
254
        }
255
0
    }
256
257
    /// Returns the number of the signal that trapped the process, if the
258
    /// process was trapped by a signal.
259
    #[inline]
260
    #[cfg(not(any(target_os = "emscripten", target_os = "fuchsia", target_os = "netbsd")))]
261
0
    pub fn trapping_signal(&self) -> Option<i32> {
262
0
        if self.trapped() {
263
0
            Some(self.si_status())
264
        } else {
265
0
            None
266
        }
267
0
    }
268
269
    /// Returns the exit status number returned by the process, if it exited
270
    /// normally.
271
    #[inline]
272
    #[cfg(not(any(target_os = "emscripten", target_os = "fuchsia", target_os = "netbsd")))]
273
0
    pub fn exit_status(&self) -> Option<i32> {
274
0
        if self.exited() {
275
0
            Some(self.si_status())
276
        } else {
277
0
            None
278
        }
279
0
    }
280
281
    /// Returns the number of the signal that terminated the process, if the
282
    /// process was terminated by a signal.
283
    #[inline]
284
    #[cfg(not(any(target_os = "emscripten", target_os = "fuchsia", target_os = "netbsd")))]
285
0
    pub fn terminating_signal(&self) -> Option<i32> {
286
0
        if self.killed() || self.dumped() {
287
0
            Some(self.si_status())
288
        } else {
289
0
            None
290
        }
291
0
    }
292
293
    /// Return the raw `si_signo` value returned from `waitid`.
294
    #[cfg(linux_raw)]
295
0
    pub fn raw_signo(&self) -> crate::ffi::c_int {
296
0
        self.0.si_signo()
297
0
    }
298
299
    /// Return the raw `si_signo` value returned from `waitid`.
300
    #[cfg(not(linux_raw))]
301
    pub fn raw_signo(&self) -> crate::ffi::c_int {
302
        self.0.si_signo
303
    }
304
305
    /// Return the raw `si_errno` value returned from `waitid`.
306
    #[cfg(linux_raw)]
307
0
    pub fn raw_errno(&self) -> crate::ffi::c_int {
308
0
        self.0.si_errno()
309
0
    }
310
311
    /// Return the raw `si_errno` value returned from `waitid`.
312
    #[cfg(not(linux_raw))]
313
    pub fn raw_errno(&self) -> crate::ffi::c_int {
314
        self.0.si_errno
315
    }
316
317
    /// Return the raw `si_code` value returned from `waitid`.
318
    #[cfg(linux_raw)]
319
0
    pub fn raw_code(&self) -> crate::ffi::c_int {
320
0
        self.0.si_code()
321
0
    }
322
323
    /// Return the raw `si_code` value returned from `waitid`.
324
    #[cfg(not(linux_raw))]
325
    pub fn raw_code(&self) -> crate::ffi::c_int {
326
        self.0.si_code
327
    }
328
329
    // This is disabled on NetBSD because the libc crate's `si_status()`
330
    // implementation doesn't appear to match what's in NetBSD's headers and we
331
    // don't get a meaningful value returned.
332
    // TODO: Report this upstream.
333
    #[cfg(not(any(target_os = "emscripten", target_os = "fuchsia", target_os = "netbsd")))]
334
    #[allow(unsafe_code)]
335
0
    fn si_status(&self) -> crate::ffi::c_int {
336
0
        // SAFETY: POSIX [specifies] that the `siginfo_t` returned by a
337
0
        // `waitid` call always has a valid `si_status` value.
338
0
        //
339
0
        // [specifies]: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/signal.h.html
340
0
        unsafe { self.0.si_status() }
341
0
    }
342
}
343
344
#[cfg(not(any(
345
    target_os = "horizon",
346
    target_os = "openbsd",
347
    target_os = "redox",
348
    target_os = "wasi"
349
)))]
350
impl fmt::Debug for WaitIdStatus {
351
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
352
0
        let mut s = f.debug_struct("WaitIdStatus");
353
0
        s.field("stopped", &self.stopped());
354
0
        s.field("exited", &self.exited());
355
0
        s.field("killed", &self.killed());
356
0
        s.field("trapped", &self.trapped());
357
0
        s.field("dumped", &self.dumped());
358
0
        s.field("continued", &self.continued());
359
        #[cfg(not(any(target_os = "emscripten", target_os = "fuchsia", target_os = "netbsd")))]
360
0
        if let Some(stopping_signal) = self.stopping_signal() {
361
0
            s.field("stopping_signal", &stopping_signal);
362
0
        }
363
        #[cfg(not(any(target_os = "emscripten", target_os = "fuchsia", target_os = "netbsd")))]
364
0
        if let Some(trapping_signal) = self.trapping_signal() {
365
0
            s.field("trapping_signal", &trapping_signal);
366
0
        }
367
        #[cfg(not(any(target_os = "emscripten", target_os = "fuchsia", target_os = "netbsd")))]
368
0
        if let Some(exit_status) = self.exit_status() {
369
0
            s.field("exit_status", &exit_status);
370
0
        }
371
        #[cfg(not(any(target_os = "emscripten", target_os = "fuchsia", target_os = "netbsd")))]
372
0
        if let Some(terminating_signal) = self.terminating_signal() {
373
0
            s.field("terminating_signal", &terminating_signal);
374
0
        }
375
0
        s.finish()
376
0
    }
377
}
378
379
/// The identifier to wait on in a call to [`waitid`].
380
#[cfg(not(any(target_os = "openbsd", target_os = "redox", target_os = "wasi")))]
381
#[derive(Debug, Clone)]
382
#[non_exhaustive]
383
pub enum WaitId<'a> {
384
    /// Wait on all processes.
385
    #[doc(alias = "P_ALL")]
386
    All,
387
388
    /// Wait for a specific process ID.
389
    #[doc(alias = "P_PID")]
390
    Pid(Pid),
391
392
    /// Wait for a specific process group ID, or the calling process' group ID.
393
    #[doc(alias = "P_PGID")]
394
    Pgid(Option<Pid>),
395
396
    /// Wait for a specific process file descriptor.
397
    #[cfg(target_os = "linux")]
398
    #[doc(alias = "P_PIDFD")]
399
    PidFd(BorrowedFd<'a>),
400
401
    /// Eat the lifetime for non-Linux platforms.
402
    #[doc(hidden)]
403
    #[cfg(not(target_os = "linux"))]
404
    __EatLifetime(core::marker::PhantomData<&'a ()>),
405
}
406
407
/// `waitpid(pid, waitopts)`—Wait for a specific process to change state.
408
///
409
/// If the pid is `None`, the call will wait for any child process whose
410
/// process group id matches that of the calling process. Otherwise, the call
411
/// will wait for the child process with the given pid.
412
///
413
/// On Success, returns the status of the selected process.
414
///
415
/// If `NOHANG` was specified in the options, and the selected child process
416
/// didn't change state, returns `None`.
417
///
418
/// To wait for a given process group (the `< -1` case of `waitpid`), use
419
/// [`waitpgid`] or [`waitid`]. To wait for any process (the `-1` case of
420
/// `waitpid`), use [`wait`].
421
///
422
/// # References
423
///  - [POSIX]
424
///  - [Linux]
425
///
426
/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9799919799/functions/wait.html
427
/// [Linux]: https://man7.org/linux/man-pages/man2/waitpid.2.html
428
#[doc(alias = "wait4")]
429
#[cfg(not(target_os = "wasi"))]
430
#[inline]
431
0
pub fn waitpid(pid: Option<Pid>, waitopts: WaitOptions) -> io::Result<Option<(Pid, WaitStatus)>> {
432
0
    backend::process::syscalls::waitpid(pid, waitopts)
433
0
}
434
435
/// `waitpid(-pgid, waitopts)`—Wait for a process in a specific process group
436
/// to change state.
437
///
438
/// The call will wait for any child process with the given pgid.
439
///
440
/// On Success, returns the status of the selected process.
441
///
442
/// If `NOHANG` was specified in the options, and no selected child process
443
/// changed state, returns `None`.
444
///
445
/// # References
446
///  - [POSIX]
447
///  - [Linux]
448
///
449
/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9799919799/functions/wait.html
450
/// [Linux]: https://man7.org/linux/man-pages/man2/waitpid.2.html
451
#[cfg(not(target_os = "wasi"))]
452
#[inline]
453
0
pub fn waitpgid(pgid: Pid, waitopts: WaitOptions) -> io::Result<Option<(Pid, WaitStatus)>> {
454
0
    backend::process::syscalls::waitpgid(pgid, waitopts)
455
0
}
456
457
/// `wait(waitopts)`—Wait for any of the children of calling process to
458
/// change state.
459
///
460
/// On success, returns the pid of the child process whose state changed, and
461
/// the status of said process.
462
///
463
/// If `NOHANG` was specified in the options, and the selected child process
464
/// didn't change state, returns `None`.
465
///
466
/// # References
467
///  - [POSIX]
468
///  - [Linux]
469
///
470
/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9799919799/functions/wait.html
471
/// [Linux]: https://man7.org/linux/man-pages/man2/waitpid.2.html
472
#[cfg(not(target_os = "wasi"))]
473
#[inline]
474
0
pub fn wait(waitopts: WaitOptions) -> io::Result<Option<(Pid, WaitStatus)>> {
475
0
    backend::process::syscalls::wait(waitopts)
476
0
}
477
478
/// `waitid(_, _, _, opts)`—Wait for the specified child process to change
479
/// state.
480
#[cfg(not(any(
481
    target_os = "cygwin",
482
    target_os = "horizon",
483
    target_os = "openbsd",
484
    target_os = "redox",
485
    target_os = "wasi",
486
)))]
487
#[inline]
488
0
pub fn waitid<'a, Id: Into<WaitId<'a>>>(
489
0
    id: Id,
490
0
    options: WaitIdOptions,
491
0
) -> io::Result<Option<WaitIdStatus>> {
492
0
    backend::process::syscalls::waitid(id.into(), options)
493
0
}