Coverage Report

Created: 2025-02-25 06:39

/rust/registry/src/index.crates.io-6f17d22bba15001f/nix-0.26.4/src/sys/wait.rs
Line
Count
Source (jump to first uncovered line)
1
//! Wait for a process to change status
2
use crate::errno::Errno;
3
use crate::sys::signal::Signal;
4
use crate::unistd::Pid;
5
use crate::Result;
6
use cfg_if::cfg_if;
7
use libc::{self, c_int};
8
use std::convert::TryFrom;
9
#[cfg(any(
10
    target_os = "android",
11
    all(target_os = "linux", not(target_env = "uclibc")),
12
))]
13
use std::os::unix::io::RawFd;
14
15
libc_bitflags!(
16
    /// Controls the behavior of [`waitpid`].
17
    pub struct WaitPidFlag: c_int {
18
        /// Do not block when there are no processes wishing to report status.
19
        WNOHANG;
20
        /// Report the status of selected processes which are stopped due to a
21
        /// [`SIGTTIN`](crate::sys::signal::Signal::SIGTTIN),
22
        /// [`SIGTTOU`](crate::sys::signal::Signal::SIGTTOU),
23
        /// [`SIGTSTP`](crate::sys::signal::Signal::SIGTSTP), or
24
        /// [`SIGSTOP`](crate::sys::signal::Signal::SIGSTOP) signal.
25
        WUNTRACED;
26
        /// Report the status of selected processes which have terminated.
27
        #[cfg(any(target_os = "android",
28
                  target_os = "freebsd",
29
                  target_os = "haiku",
30
                  target_os = "ios",
31
                  target_os = "linux",
32
                  target_os = "redox",
33
                  target_os = "macos",
34
                  target_os = "netbsd"))]
35
        #[cfg_attr(docsrs, doc(cfg(all())))]
36
        WEXITED;
37
        /// Report the status of selected processes that have continued from a
38
        /// job control stop by receiving a
39
        /// [`SIGCONT`](crate::sys::signal::Signal::SIGCONT) signal.
40
        WCONTINUED;
41
        /// An alias for WUNTRACED.
42
        #[cfg(any(target_os = "android",
43
                  target_os = "freebsd",
44
                  target_os = "haiku",
45
                  target_os = "ios",
46
                  target_os = "linux",
47
                  target_os = "redox",
48
                  target_os = "macos",
49
                  target_os = "netbsd"))]
50
        #[cfg_attr(docsrs, doc(cfg(all())))]
51
        WSTOPPED;
52
        /// Don't reap, just poll status.
53
        #[cfg(any(target_os = "android",
54
                  target_os = "freebsd",
55
                  target_os = "haiku",
56
                  target_os = "ios",
57
                  target_os = "linux",
58
                  target_os = "redox",
59
                  target_os = "macos",
60
                  target_os = "netbsd"))]
61
        #[cfg_attr(docsrs, doc(cfg(all())))]
62
        WNOWAIT;
63
        /// Don't wait on children of other threads in this group
64
        #[cfg(any(target_os = "android", target_os = "linux", target_os = "redox"))]
65
        #[cfg_attr(docsrs, doc(cfg(all())))]
66
        __WNOTHREAD;
67
        /// Wait on all children, regardless of type
68
        #[cfg(any(target_os = "android", target_os = "linux", target_os = "redox"))]
69
        #[cfg_attr(docsrs, doc(cfg(all())))]
70
        __WALL;
71
        /// Wait for "clone" children only.
72
        #[cfg(any(target_os = "android", target_os = "linux", target_os = "redox"))]
73
        #[cfg_attr(docsrs, doc(cfg(all())))]
74
        __WCLONE;
75
    }
76
);
77
78
/// Possible return values from `wait()` or `waitpid()`.
79
///
80
/// Each status (other than `StillAlive`) describes a state transition
81
/// in a child process `Pid`, such as the process exiting or stopping,
82
/// plus additional data about the transition if any.
83
///
84
/// Note that there are two Linux-specific enum variants, `PtraceEvent`
85
/// and `PtraceSyscall`. Portable code should avoid exhaustively
86
/// matching on `WaitStatus`.
87
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
88
pub enum WaitStatus {
89
    /// The process exited normally (as with `exit()` or returning from
90
    /// `main`) with the given exit code. This case matches the C macro
91
    /// `WIFEXITED(status)`; the second field is `WEXITSTATUS(status)`.
92
    Exited(Pid, i32),
93
    /// The process was killed by the given signal. The third field
94
    /// indicates whether the signal generated a core dump. This case
95
    /// matches the C macro `WIFSIGNALED(status)`; the last two fields
96
    /// correspond to `WTERMSIG(status)` and `WCOREDUMP(status)`.
97
    Signaled(Pid, Signal, bool),
98
    /// The process is alive, but was stopped by the given signal. This
99
    /// is only reported if `WaitPidFlag::WUNTRACED` was passed. This
100
    /// case matches the C macro `WIFSTOPPED(status)`; the second field
101
    /// is `WSTOPSIG(status)`.
102
    Stopped(Pid, Signal),
103
    /// The traced process was stopped by a `PTRACE_EVENT_*` event. See
104
    /// [`nix::sys::ptrace`] and [`ptrace`(2)] for more information. All
105
    /// currently-defined events use `SIGTRAP` as the signal; the third
106
    /// field is the `PTRACE_EVENT_*` value of the event.
107
    ///
108
    /// [`nix::sys::ptrace`]: ../ptrace/index.html
109
    /// [`ptrace`(2)]: https://man7.org/linux/man-pages/man2/ptrace.2.html
110
    #[cfg(any(target_os = "linux", target_os = "android"))]
111
    #[cfg_attr(docsrs, doc(cfg(all())))]
112
    PtraceEvent(Pid, Signal, c_int),
113
    /// The traced process was stopped by execution of a system call,
114
    /// and `PTRACE_O_TRACESYSGOOD` is in effect. See [`ptrace`(2)] for
115
    /// more information.
116
    ///
117
    /// [`ptrace`(2)]: https://man7.org/linux/man-pages/man2/ptrace.2.html
118
    #[cfg(any(target_os = "linux", target_os = "android"))]
119
    #[cfg_attr(docsrs, doc(cfg(all())))]
120
    PtraceSyscall(Pid),
121
    /// The process was previously stopped but has resumed execution
122
    /// after receiving a `SIGCONT` signal. This is only reported if
123
    /// `WaitPidFlag::WCONTINUED` was passed. This case matches the C
124
    /// macro `WIFCONTINUED(status)`.
125
    Continued(Pid),
126
    /// There are currently no state changes to report in any awaited
127
    /// child process. This is only returned if `WaitPidFlag::WNOHANG`
128
    /// was used (otherwise `wait()` or `waitpid()` would block until
129
    /// there was something to report).
130
    StillAlive,
131
}
132
133
impl WaitStatus {
134
    /// Extracts the PID from the WaitStatus unless it equals StillAlive.
135
0
    pub fn pid(&self) -> Option<Pid> {
136
        use self::WaitStatus::*;
137
0
        match *self {
138
0
            Exited(p, _) | Signaled(p, _, _) | Stopped(p, _) | Continued(p) => {
139
0
                Some(p)
140
            }
141
0
            StillAlive => None,
142
            #[cfg(any(target_os = "android", target_os = "linux"))]
143
0
            PtraceEvent(p, _, _) | PtraceSyscall(p) => Some(p),
144
        }
145
0
    }
146
}
147
148
0
fn exited(status: i32) -> bool {
149
0
    libc::WIFEXITED(status)
150
0
}
151
152
0
fn exit_status(status: i32) -> i32 {
153
0
    libc::WEXITSTATUS(status)
154
0
}
155
156
0
fn signaled(status: i32) -> bool {
157
0
    libc::WIFSIGNALED(status)
158
0
}
159
160
0
fn term_signal(status: i32) -> Result<Signal> {
161
0
    Signal::try_from(libc::WTERMSIG(status))
162
0
}
163
164
0
fn dumped_core(status: i32) -> bool {
165
0
    libc::WCOREDUMP(status)
166
0
}
167
168
0
fn stopped(status: i32) -> bool {
169
0
    libc::WIFSTOPPED(status)
170
0
}
171
172
0
fn stop_signal(status: i32) -> Result<Signal> {
173
0
    Signal::try_from(libc::WSTOPSIG(status))
174
0
}
175
176
#[cfg(any(target_os = "android", target_os = "linux"))]
177
0
fn syscall_stop(status: i32) -> bool {
178
0
    // From ptrace(2), setting PTRACE_O_TRACESYSGOOD has the effect
179
0
    // of delivering SIGTRAP | 0x80 as the signal number for syscall
180
0
    // stops. This allows easily distinguishing syscall stops from
181
0
    // genuine SIGTRAP signals.
182
0
    libc::WSTOPSIG(status) == libc::SIGTRAP | 0x80
183
0
}
184
185
#[cfg(any(target_os = "android", target_os = "linux"))]
186
0
fn stop_additional(status: i32) -> c_int {
187
0
    (status >> 16) as c_int
188
0
}
189
190
0
fn continued(status: i32) -> bool {
191
0
    libc::WIFCONTINUED(status)
192
0
}
193
194
impl WaitStatus {
195
    /// Convert a raw `wstatus` as returned by `waitpid`/`wait` into a `WaitStatus`
196
    ///
197
    /// # Errors
198
    ///
199
    /// Returns an `Error` corresponding to `EINVAL` for invalid status values.
200
    ///
201
    /// # Examples
202
    ///
203
    /// Convert a `wstatus` obtained from `libc::waitpid` into a `WaitStatus`:
204
    ///
205
    /// ```
206
    /// use nix::sys::wait::WaitStatus;
207
    /// use nix::sys::signal::Signal;
208
    /// let pid = nix::unistd::Pid::from_raw(1);
209
    /// let status = WaitStatus::from_raw(pid, 0x0002);
210
    /// assert_eq!(status, Ok(WaitStatus::Signaled(pid, Signal::SIGINT, false)));
211
    /// ```
212
0
    pub fn from_raw(pid: Pid, status: i32) -> Result<WaitStatus> {
213
0
        Ok(if exited(status) {
214
0
            WaitStatus::Exited(pid, exit_status(status))
215
0
        } else if signaled(status) {
216
0
            WaitStatus::Signaled(pid, term_signal(status)?, dumped_core(status))
217
0
        } else if stopped(status) {
218
            cfg_if! {
219
                if #[cfg(any(target_os = "android", target_os = "linux"))] {
220
0
                    fn decode_stopped(pid: Pid, status: i32) -> Result<WaitStatus> {
221
0
                        let status_additional = stop_additional(status);
222
0
                        Ok(if syscall_stop(status) {
223
0
                            WaitStatus::PtraceSyscall(pid)
224
0
                        } else if status_additional == 0 {
225
0
                            WaitStatus::Stopped(pid, stop_signal(status)?)
226
                        } else {
227
0
                            WaitStatus::PtraceEvent(pid, stop_signal(status)?,
228
0
                                                    stop_additional(status))
229
                        })
230
0
                    }
231
                } else {
232
                    fn decode_stopped(pid: Pid, status: i32) -> Result<WaitStatus> {
233
                        Ok(WaitStatus::Stopped(pid, stop_signal(status)?))
234
                    }
235
                }
236
            }
237
0
            return decode_stopped(pid, status);
238
        } else {
239
0
            assert!(continued(status));
240
0
            WaitStatus::Continued(pid)
241
        })
242
0
    }
243
244
    /// Convert a `siginfo_t` as returned by `waitid` to a `WaitStatus`
245
    ///
246
    /// # Errors
247
    ///
248
    /// Returns an `Error` corresponding to `EINVAL` for invalid values.
249
    ///
250
    /// # Safety
251
    ///
252
    /// siginfo_t is actually a union, not all fields may be initialized.
253
    /// The functions si_pid() and si_status() must be valid to call on
254
    /// the passed siginfo_t.
255
    #[cfg(any(
256
        target_os = "android",
257
        target_os = "freebsd",
258
        target_os = "haiku",
259
        all(target_os = "linux", not(target_env = "uclibc")),
260
    ))]
261
0
    unsafe fn from_siginfo(siginfo: &libc::siginfo_t) -> Result<WaitStatus> {
262
0
        let si_pid = siginfo.si_pid();
263
0
        if si_pid == 0 {
264
0
            return Ok(WaitStatus::StillAlive);
265
0
        }
266
0
267
0
        assert_eq!(siginfo.si_signo, libc::SIGCHLD);
268
269
0
        let pid = Pid::from_raw(si_pid);
270
0
        let si_status = siginfo.si_status();
271
272
0
        let status = match siginfo.si_code {
273
0
            libc::CLD_EXITED => WaitStatus::Exited(pid, si_status),
274
            libc::CLD_KILLED | libc::CLD_DUMPED => WaitStatus::Signaled(
275
0
                pid,
276
0
                Signal::try_from(si_status)?,
277
0
                siginfo.si_code == libc::CLD_DUMPED,
278
            ),
279
            libc::CLD_STOPPED => {
280
0
                WaitStatus::Stopped(pid, Signal::try_from(si_status)?)
281
            }
282
0
            libc::CLD_CONTINUED => WaitStatus::Continued(pid),
283
            #[cfg(any(target_os = "android", target_os = "linux"))]
284
            libc::CLD_TRAPPED => {
285
0
                if si_status == libc::SIGTRAP | 0x80 {
286
0
                    WaitStatus::PtraceSyscall(pid)
287
                } else {
288
                    WaitStatus::PtraceEvent(
289
0
                        pid,
290
0
                        Signal::try_from(si_status & 0xff)?,
291
0
                        (si_status >> 8) as c_int,
292
                    )
293
                }
294
            }
295
0
            _ => return Err(Errno::EINVAL),
296
        };
297
298
0
        Ok(status)
299
0
    }
300
}
301
302
/// Wait for a process to change status
303
///
304
/// See also [waitpid(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/waitpid.html)
305
0
pub fn waitpid<P: Into<Option<Pid>>>(
306
0
    pid: P,
307
0
    options: Option<WaitPidFlag>,
308
0
) -> Result<WaitStatus> {
309
    use self::WaitStatus::*;
310
311
0
    let mut status: i32 = 0;
312
313
0
    let option_bits = match options {
314
0
        Some(bits) => bits.bits(),
315
0
        None => 0,
316
    };
317
318
0
    let res = unsafe {
319
0
        libc::waitpid(
320
0
            pid.into().unwrap_or_else(|| Pid::from_raw(-1)).into(),
321
0
            &mut status as *mut c_int,
322
0
            option_bits,
323
0
        )
324
0
    };
325
0
326
0
    match Errno::result(res)? {
327
0
        0 => Ok(StillAlive),
328
0
        res => WaitStatus::from_raw(Pid::from_raw(res), status),
329
    }
330
0
}
331
332
/// Wait for any child process to change status or a signal is received.
333
///
334
/// See also [wait(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/wait.html)
335
0
pub fn wait() -> Result<WaitStatus> {
336
0
    waitpid(None, None)
337
0
}
338
339
/// The ID argument for `waitid`
340
#[cfg(any(
341
    target_os = "android",
342
    target_os = "freebsd",
343
    target_os = "haiku",
344
    all(target_os = "linux", not(target_env = "uclibc")),
345
))]
346
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
347
pub enum Id {
348
    /// Wait for any child
349
    All,
350
    /// Wait for the child whose process ID matches the given PID
351
    Pid(Pid),
352
    /// Wait for the child whose process group ID matches the given PID
353
    ///
354
    /// If the PID is zero, the caller's process group is used since Linux 5.4.
355
    PGid(Pid),
356
    /// Wait for the child referred to by the given PID file descriptor
357
    #[cfg(any(target_os = "android", target_os = "linux"))]
358
    PIDFd(RawFd),
359
}
360
361
/// Wait for a process to change status
362
///
363
/// See also [waitid(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/waitid.html)
364
#[cfg(any(
365
    target_os = "android",
366
    target_os = "freebsd",
367
    target_os = "haiku",
368
    all(target_os = "linux", not(target_env = "uclibc")),
369
))]
370
0
pub fn waitid(id: Id, flags: WaitPidFlag) -> Result<WaitStatus> {
371
0
    let (idtype, idval) = match id {
372
0
        Id::All => (libc::P_ALL, 0),
373
0
        Id::Pid(pid) => (libc::P_PID, pid.as_raw() as libc::id_t),
374
0
        Id::PGid(pid) => (libc::P_PGID, pid.as_raw() as libc::id_t),
375
        #[cfg(any(target_os = "android", target_os = "linux"))]
376
0
        Id::PIDFd(fd) => (libc::P_PIDFD, fd as libc::id_t),
377
    };
378
379
0
    let siginfo = unsafe {
380
        // Memory is zeroed rather than uninitialized, as not all platforms
381
        // initialize the memory in the StillAlive case
382
0
        let mut siginfo: libc::siginfo_t = std::mem::zeroed();
383
0
        Errno::result(libc::waitid(idtype, idval, &mut siginfo, flags.bits()))?;
384
0
        siginfo
385
0
    };
386
0
387
0
    unsafe { WaitStatus::from_siginfo(&siginfo) }
388
0
}