Skip to content

Commit 0435be2

Browse files
bors[bot]neocturne
andauthored
Merge #1584
1584: wait: implement waitid() r=rtzoeller a=NeoRaider 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 For now support is added for all platforms with waitid() that have proper siginfo_t support in libc. NetBSD support is currently a work in progress [1]. Tests for the signal/exit code are currently skipped on MIPS platforms due to multiple bugs in qemu-user in the translation of siginfo_t (one fixed in January [2], one currently under review [3]). [1] rust-lang/libc#2476 [2] https://lists.nongnu.org/archive/html/qemu-devel/2021-01/msg04810.html [3] https://lists.nongnu.org/archive/html/qemu-devel/2021-10/msg05433.html Co-authored-by: Matthias Schiffer <[email protected]>
2 parents ff08ff7 + df417e2 commit 0435be2

File tree

3 files changed

+229
-2
lines changed

3 files changed

+229
-2
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ This project adheres to [Semantic Versioning](https://semver.org/).
5656
(#[1664](https://github.com/nix-rust/nix/pull/1664))
5757
- Added `MSG_NOSIGNAL` for Android, Dragonfly, FreeBSD, Fuchsia, Haiku, Illumos, Linux, NetBSD, OpenBSD and Solaris.
5858
(#[1670](https://github.com/nix-rust/nix/pull/1670))
59+
- Added `waitid`.
60+
(#[1584](https://github.com/nix-rust/nix/pull/1584))
5961

6062
### Changed
6163

src/sys/wait.rs

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ use crate::Result;
66
use cfg_if::cfg_if;
77
use libc::{self, c_int};
88
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;
914

1015
libc_bitflags!(
1116
/// Controls the behavior of [`waitpid`].
@@ -233,6 +238,61 @@ impl WaitStatus {
233238
WaitStatus::Continued(pid)
234239
})
235240
}
241+
242+
/// Convert a `siginfo_t` as returned by `waitid` to a `WaitStatus`
243+
///
244+
/// # Errors
245+
///
246+
/// Returns an `Error` corresponding to `EINVAL` for invalid values.
247+
///
248+
/// # Safety
249+
///
250+
/// siginfo_t is actually a union, not all fields may be initialized.
251+
/// The functions si_pid() and si_status() must be valid to call on
252+
/// the passed siginfo_t.
253+
#[cfg(any(
254+
target_os = "android",
255+
target_os = "freebsd",
256+
target_os = "haiku",
257+
all(target_os = "linux", not(target_env = "uclibc")),
258+
))]
259+
unsafe fn from_siginfo(siginfo: &libc::siginfo_t) -> Result<WaitStatus> {
260+
let si_pid = siginfo.si_pid();
261+
if si_pid == 0 {
262+
return Ok(WaitStatus::StillAlive);
263+
}
264+
265+
assert_eq!(siginfo.si_signo, libc::SIGCHLD);
266+
267+
let pid = Pid::from_raw(si_pid);
268+
let si_status = siginfo.si_status();
269+
270+
let status = match siginfo.si_code {
271+
libc::CLD_EXITED => WaitStatus::Exited(pid, si_status),
272+
libc::CLD_KILLED | libc::CLD_DUMPED => WaitStatus::Signaled(
273+
pid,
274+
Signal::try_from(si_status)?,
275+
siginfo.si_code == libc::CLD_DUMPED,
276+
),
277+
libc::CLD_STOPPED => WaitStatus::Stopped(pid, Signal::try_from(si_status)?),
278+
libc::CLD_CONTINUED => WaitStatus::Continued(pid),
279+
#[cfg(any(target_os = "android", target_os = "linux"))]
280+
libc::CLD_TRAPPED => {
281+
if si_status == libc::SIGTRAP | 0x80 {
282+
WaitStatus::PtraceSyscall(pid)
283+
} else {
284+
WaitStatus::PtraceEvent(
285+
pid,
286+
Signal::try_from(si_status & 0xff)?,
287+
(si_status >> 8) as c_int,
288+
)
289+
}
290+
}
291+
_ => return Err(Errno::EINVAL),
292+
};
293+
294+
Ok(status)
295+
}
236296
}
237297

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

test/sys/test_wait.rs

Lines changed: 116 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,33 @@ 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+
all(target_os = "linux", not(target_env = "uclibc")),
32+
))]
33+
#[cfg(not(any(target_arch = "mips", target_arch = "mips64")))]
34+
fn test_waitid_signal() {
35+
let _m = crate::FORK_MTX.lock();
36+
37+
// Safe: The child only calls `pause` and/or `_exit`, which are async-signal-safe.
38+
match unsafe{fork()}.expect("Error: Fork Failed") {
39+
Child => {
40+
pause();
41+
unsafe { _exit(123) }
42+
},
43+
Parent { child } => {
44+
kill(child, Some(SIGKILL)).expect("Error: Kill Failed");
45+
assert_eq!(
46+
waitid(Id::Pid(child), WaitPidFlag::WEXITED),
47+
Ok(WaitStatus::Signaled(child, SIGKILL, false)),
48+
);
49+
},
50+
}
51+
}
52+
2653
#[test]
2754
fn test_wait_exit() {
2855
let _m = crate::FORK_MTX.lock();
@@ -36,6 +63,29 @@ fn test_wait_exit() {
3663
}
3764
}
3865

66+
#[test]
67+
#[cfg(any(
68+
target_os = "android",
69+
target_os = "freebsd",
70+
target_os = "haiku",
71+
all(target_os = "linux", not(target_env = "uclibc")),
72+
))]
73+
#[cfg(not(any(target_arch = "mips", target_arch = "mips64")))]
74+
fn test_waitid_exit() {
75+
let _m = crate::FORK_MTX.lock();
76+
77+
// Safe: Child only calls `_exit`, which is async-signal-safe.
78+
match unsafe{fork()}.expect("Error: Fork Failed") {
79+
Child => unsafe { _exit(12); },
80+
Parent { child } => {
81+
assert_eq!(
82+
waitid(Id::Pid(child), WaitPidFlag::WEXITED),
83+
Ok(WaitStatus::Exited(child, 12)),
84+
);
85+
}
86+
}
87+
}
88+
3989
#[test]
4090
fn test_waitstatus_from_raw() {
4191
let pid = Pid::from_raw(1);
@@ -57,6 +107,25 @@ fn test_waitstatus_pid() {
57107
}
58108
}
59109

110+
#[test]
111+
#[cfg(any(
112+
target_os = "android",
113+
target_os = "freebsd",
114+
target_os = "haiku",
115+
all(target_os = "linux", not(target_env = "uclibc")),
116+
))]
117+
fn test_waitid_pid() {
118+
let _m = crate::FORK_MTX.lock();
119+
120+
match unsafe { fork() }.unwrap() {
121+
Child => unsafe { _exit(0) },
122+
Parent { child } => {
123+
let status = waitid(Id::Pid(child), WaitPidFlag::WEXITED).unwrap();
124+
assert_eq!(status.pid(), Some(child));
125+
}
126+
}
127+
}
128+
60129
#[cfg(any(target_os = "linux", target_os = "android"))]
61130
// FIXME: qemu-user doesn't implement ptrace on most arches
62131
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
@@ -77,7 +146,7 @@ mod ptrace {
77146
unsafe { _exit(0) }
78147
}
79148

80-
fn ptrace_parent(child: Pid) {
149+
fn ptrace_wait_parent(child: Pid) {
81150
// Wait for the raised SIGTRAP
82151
assert_eq!(waitpid(child, None), Ok(WaitStatus::Stopped(child, SIGTRAP)));
83152
// We want to test a syscall stop and a PTRACE_EVENT stop
@@ -94,14 +163,59 @@ mod ptrace {
94163
assert_eq!(waitpid(child, None), Ok(WaitStatus::Exited(child, 0)));
95164
}
96165

166+
#[cfg(not(target_env = "uclibc"))]
167+
fn ptrace_waitid_parent(child: Pid) {
168+
// Wait for the raised SIGTRAP
169+
//
170+
// Unlike waitpid(), waitid() can distinguish trap events from regular
171+
// stop events, so unlike ptrace_wait_parent(), we get a PtraceEvent here
172+
assert_eq!(
173+
waitid(Id::Pid(child), WaitPidFlag::WEXITED),
174+
Ok(WaitStatus::PtraceEvent(child, SIGTRAP, 0)),
175+
);
176+
// We want to test a syscall stop and a PTRACE_EVENT stop
177+
assert!(ptrace::setoptions(child, Options::PTRACE_O_TRACESYSGOOD | Options::PTRACE_O_TRACEEXIT).is_ok());
178+
179+
// First, stop on the next system call, which will be exit()
180+
assert!(ptrace::syscall(child, None).is_ok());
181+
assert_eq!(
182+
waitid(Id::Pid(child), WaitPidFlag::WEXITED),
183+
Ok(WaitStatus::PtraceSyscall(child)),
184+
);
185+
// Then get the ptrace event for the process exiting
186+
assert!(ptrace::cont(child, None).is_ok());
187+
assert_eq!(
188+
waitid(Id::Pid(child), WaitPidFlag::WEXITED),
189+
Ok(WaitStatus::PtraceEvent(child, SIGTRAP, Event::PTRACE_EVENT_EXIT as i32)),
190+
);
191+
// Finally get the normal wait() result, now that the process has exited
192+
assert!(ptrace::cont(child, None).is_ok());
193+
assert_eq!(
194+
waitid(Id::Pid(child), WaitPidFlag::WEXITED),
195+
Ok(WaitStatus::Exited(child, 0)),
196+
);
197+
}
198+
97199
#[test]
98200
fn test_wait_ptrace() {
99201
require_capability!("test_wait_ptrace", CAP_SYS_PTRACE);
100202
let _m = crate::FORK_MTX.lock();
101203

102204
match unsafe{fork()}.expect("Error: Fork Failed") {
103205
Child => ptrace_child(),
104-
Parent { child } => ptrace_parent(child),
206+
Parent { child } => ptrace_wait_parent(child),
207+
}
208+
}
209+
210+
#[test]
211+
#[cfg(not(target_env = "uclibc"))]
212+
fn test_waitid_ptrace() {
213+
require_capability!("test_waitid_ptrace", CAP_SYS_PTRACE);
214+
let _m = crate::FORK_MTX.lock();
215+
216+
match unsafe{fork()}.expect("Error: Fork Failed") {
217+
Child => ptrace_child(),
218+
Parent { child } => ptrace_waitid_parent(child),
105219
}
106220
}
107221
}

0 commit comments

Comments
 (0)