Skip to content

Commit 82ced16

Browse files
committed
wait: implement waitid()
waitid() has a number of additional features that waitpid() is missing: - WNOWAIT is only accepted for waitid() on Linux (and possibly other platforms) - Support for waiting on PID file descriptors on Linux
1 parent 57d4c86 commit 82ced16

File tree

2 files changed

+213
-2
lines changed

2 files changed

+213
-2
lines changed

src/sys/wait.rs

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ use crate::Result;
66
use cfg_if::cfg_if;
77
use libc::{self, c_int};
88
use std::convert::TryFrom;
9+
#[cfg(any(target_os = "android", target_os = "linux"))]
10+
use std::os::unix::io::RawFd;
911

1012
libc_bitflags!(
1113
/// Controls the behavior of [`waitpid`].
@@ -225,6 +227,55 @@ impl WaitStatus {
225227
WaitStatus::Continued(pid)
226228
})
227229
}
230+
231+
/// Convert a `siginfo_t` as returned by `waitid` to a `WaitStatus`
232+
///
233+
/// # Errors
234+
///
235+
/// Returns an `Error` corresponding to `EINVAL` for invalid values.
236+
#[cfg(any(
237+
target_os = "android",
238+
target_os = "freebsd",
239+
target_os = "haiku",
240+
target_os = "ios",
241+
target_os = "linux",
242+
target_os = "macos",
243+
target_os = "netbsd"
244+
))]
245+
pub fn from_siginfo(siginfo: &libc::siginfo_t) -> Result<WaitStatus> {
246+
let (si_pid, si_status) = unsafe { (siginfo.si_pid(), siginfo.si_status()) };
247+
if si_pid == 0 {
248+
return Ok(WaitStatus::StillAlive);
249+
}
250+
251+
let pid = Pid::from_raw(si_pid);
252+
253+
let status = match siginfo.si_code {
254+
libc::CLD_EXITED => WaitStatus::Exited(pid, si_status),
255+
libc::CLD_KILLED | libc::CLD_DUMPED => WaitStatus::Signaled(
256+
pid,
257+
Signal::try_from(si_status)?,
258+
siginfo.si_code == libc::CLD_DUMPED,
259+
),
260+
libc::CLD_STOPPED => WaitStatus::Stopped(pid, Signal::try_from(si_status)?),
261+
libc::CLD_CONTINUED => WaitStatus::Continued(pid),
262+
#[cfg(any(target_os = "android", target_os = "linux"))]
263+
libc::CLD_TRAPPED => {
264+
if si_status == libc::SIGTRAP | 0x80 {
265+
WaitStatus::PtraceSyscall(pid)
266+
} else {
267+
WaitStatus::PtraceEvent(
268+
pid,
269+
Signal::try_from(si_status & 0xff)?,
270+
(si_status >> 8) as c_int,
271+
)
272+
}
273+
}
274+
_ => return Err(Errno::EINVAL),
275+
};
276+
277+
Ok(status)
278+
}
228279
}
229280

230281
/// Wait for a process to change status
@@ -260,3 +311,63 @@ pub fn waitpid<P: Into<Option<Pid>>>(pid: P, options: Option<WaitPidFlag>) -> Re
260311
pub fn wait() -> Result<WaitStatus> {
261312
waitpid(None, None)
262313
}
314+
315+
/// The ID argument for `waitid`
316+
#[cfg(any(
317+
target_os = "android",
318+
target_os = "freebsd",
319+
target_os = "haiku",
320+
target_os = "ios",
321+
target_os = "linux",
322+
target_os = "macos",
323+
target_os = "netbsd"
324+
))]
325+
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
326+
pub enum Id {
327+
/// Wait for any child
328+
All,
329+
/// Wait for the child whose process ID matches the given PID
330+
Pid(Pid),
331+
/// Wait for the child whose process group ID matches the given PID
332+
///
333+
/// If no PID is given, the caller's process group is used since Linux 5.4.
334+
PGid(Option<Pid>),
335+
/// Wait for the child referred to by the given PID file descriptor
336+
#[cfg(any(target_os = "android", target_os = "linux"))]
337+
PIDFd(RawFd),
338+
}
339+
340+
/// Wait for a process to change status
341+
///
342+
/// See also [waitid(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/waitid.html)
343+
#[cfg(any(
344+
target_os = "android",
345+
target_os = "freebsd",
346+
target_os = "haiku",
347+
target_os = "ios",
348+
target_os = "linux",
349+
target_os = "macos",
350+
target_os = "netbsd"
351+
))]
352+
pub fn waitid(id: Id, flags: WaitPidFlag) -> Result<WaitStatus> {
353+
let (idtype, idval) = match id {
354+
Id::All => (libc::P_ALL, 0),
355+
Id::Pid(pid) => (libc::P_PID, pid.as_raw() as libc::id_t),
356+
Id::PGid(pid) => (
357+
libc::P_PGID,
358+
pid.map(Pid::as_raw).unwrap_or(0) as libc::id_t,
359+
),
360+
#[cfg(any(target_os = "android", target_os = "linux"))]
361+
Id::PIDFd(fd) => (libc::P_PIDFD, fd as libc::id_t),
362+
};
363+
364+
let siginfo = unsafe {
365+
// Memory is zeroed rather than uninitialized, as not all platforms
366+
// initialize the memory in the StillAlive case
367+
let mut siginfo: libc::siginfo_t = std::mem::zeroed();
368+
Errno::result(libc::waitid(idtype, idval, &mut siginfo, flags.bits()))?;
369+
siginfo
370+
};
371+
372+
WaitStatus::from_siginfo(&siginfo)
373+
}

test/sys/test_wait.rs

Lines changed: 102 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,35 @@ fn test_wait_signal() {
2323
}
2424
}
2525

26+
#[test]
27+
#[cfg(any(
28+
target_os = "android",
29+
target_os = "freebsd",
30+
target_os = "haiku",
31+
target_os = "ios",
32+
target_os = "linux",
33+
target_os = "macos",
34+
target_os = "netbsd"
35+
))]
36+
fn test_waitid_signal() {
37+
let _m = crate::FORK_MTX.lock().expect("Mutex got poisoned by another test");
38+
39+
// Safe: The child only calls `pause` and/or `_exit`, which are async-signal-safe.
40+
match unsafe{fork()}.expect("Error: Fork Failed") {
41+
Child => {
42+
pause();
43+
unsafe { _exit(123) }
44+
},
45+
Parent { child } => {
46+
kill(child, Some(SIGKILL)).expect("Error: Kill Failed");
47+
assert_eq!(
48+
waitid(Id::Pid(child), WaitPidFlag::WEXITED),
49+
Ok(WaitStatus::Signaled(child, SIGKILL, false)),
50+
);
51+
},
52+
}
53+
}
54+
2655
#[test]
2756
fn test_wait_exit() {
2857
let _m = crate::FORK_MTX.lock().expect("Mutex got poisoned by another test");
@@ -35,6 +64,21 @@ fn test_wait_exit() {
3564
},
3665
}
3766
}
67+
#[test]
68+
fn test_waitid_exit() {
69+
let _m = crate::FORK_MTX.lock().expect("Mutex got poisoned by another test");
70+
71+
// Safe: Child only calls `_exit`, which is async-signal-safe.
72+
match unsafe{fork()}.expect("Error: Fork Failed") {
73+
Child => unsafe { _exit(12); },
74+
Parent { child } => {
75+
assert_eq!(
76+
waitid(Id::Pid(child), WaitPidFlag::WEXITED),
77+
Ok(WaitStatus::Exited(child, 12)),
78+
);
79+
}
80+
}
81+
}
3882

3983
#[test]
4084
fn test_waitstatus_from_raw() {
@@ -57,6 +101,19 @@ fn test_waitstatus_pid() {
57101
}
58102
}
59103

104+
#[test]
105+
fn test_waitid_pid() {
106+
let _m = crate::FORK_MTX.lock().expect("Mutex got poisoned by another test");
107+
108+
match unsafe { fork() }.unwrap() {
109+
Child => unsafe { _exit(0) },
110+
Parent { child } => {
111+
let status = waitid(Id::Pid(child), WaitPidFlag::WEXITED).unwrap();
112+
assert_eq!(status.pid(), Some(child));
113+
}
114+
}
115+
}
116+
60117
#[cfg(any(target_os = "linux", target_os = "android"))]
61118
// FIXME: qemu-user doesn't implement ptrace on most arches
62119
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
@@ -77,7 +134,7 @@ mod ptrace {
77134
unsafe { _exit(0) }
78135
}
79136

80-
fn ptrace_parent(child: Pid) {
137+
fn ptrace_wait_parent(child: Pid) {
81138
// Wait for the raised SIGTRAP
82139
assert_eq!(waitpid(child, None), Ok(WaitStatus::Stopped(child, SIGTRAP)));
83140
// We want to test a syscall stop and a PTRACE_EVENT stop
@@ -94,14 +151,57 @@ mod ptrace {
94151
assert_eq!(waitpid(child, None), Ok(WaitStatus::Exited(child, 0)));
95152
}
96153

154+
fn ptrace_waitid_parent(child: Pid) {
155+
// Wait for the raised SIGTRAP
156+
//
157+
// Unlike waitpid(), waitid() can distinguish trap events from regular
158+
// stop events, so unlike ptrace_wait_parent(), we get a PtraceEvent here
159+
assert_eq!(
160+
waitid(Id::Pid(child), WaitPidFlag::WEXITED),
161+
Ok(WaitStatus::PtraceEvent(child, SIGTRAP, 0)),
162+
);
163+
// We want to test a syscall stop and a PTRACE_EVENT stop
164+
assert!(ptrace::setoptions(child, Options::PTRACE_O_TRACESYSGOOD | Options::PTRACE_O_TRACEEXIT).is_ok());
165+
166+
// First, stop on the next system call, which will be exit()
167+
assert!(ptrace::syscall(child, None).is_ok());
168+
assert_eq!(
169+
waitid(Id::Pid(child), WaitPidFlag::WEXITED),
170+
Ok(WaitStatus::PtraceSyscall(child)),
171+
);
172+
// Then get the ptrace event for the process exiting
173+
assert!(ptrace::cont(child, None).is_ok());
174+
assert_eq!(
175+
waitid(Id::Pid(child), WaitPidFlag::WEXITED),
176+
Ok(WaitStatus::PtraceEvent(child, SIGTRAP, Event::PTRACE_EVENT_EXIT as i32)),
177+
);
178+
// Finally get the normal wait() result, now that the process has exited
179+
assert!(ptrace::cont(child, None).is_ok());
180+
assert_eq!(
181+
waitid(Id::Pid(child), WaitPidFlag::WEXITED),
182+
Ok(WaitStatus::Exited(child, 0)),
183+
);
184+
}
185+
97186
#[test]
98187
fn test_wait_ptrace() {
99188
require_capability!("test_wait_ptrace", CAP_SYS_PTRACE);
100189
let _m = crate::FORK_MTX.lock().expect("Mutex got poisoned by another test");
101190

102191
match unsafe{fork()}.expect("Error: Fork Failed") {
103192
Child => ptrace_child(),
104-
Parent { child } => ptrace_parent(child),
193+
Parent { child } => ptrace_wait_parent(child),
194+
}
195+
}
196+
197+
#[test]
198+
fn test_waitid_ptrace() {
199+
require_capability!("test_waitid_ptrace", CAP_SYS_PTRACE);
200+
let _m = crate::FORK_MTX.lock().expect("Mutex got poisoned by another test");
201+
202+
match unsafe{fork()}.expect("Error: Fork Failed") {
203+
Child => ptrace_child(),
204+
Parent { child } => ptrace_waitid_parent(child),
105205
}
106206
}
107207
}

0 commit comments

Comments
 (0)