mirror of
https://github.com/jiangcuo/nix.git
synced 2026-02-04 12:41:27 +00:00
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 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 bugs in qemu-user's translation of siginfo_t (fixed in [2] and [3]; the second fix is not in a released qemu version yet). [1] https://github.com/rust-lang/libc/pull/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
This commit is contained in:
parent
ff08ff7732
commit
df417e295b
@ -56,6 +56,8 @@ This project adheres to [Semantic Versioning](https://semver.org/).
|
||||
(#[1664](https://github.com/nix-rust/nix/pull/1664))
|
||||
- Added `MSG_NOSIGNAL` for Android, Dragonfly, FreeBSD, Fuchsia, Haiku, Illumos, Linux, NetBSD, OpenBSD and Solaris.
|
||||
(#[1670](https://github.com/nix-rust/nix/pull/1670))
|
||||
- Added `waitid`.
|
||||
(#[1584](https://github.com/nix-rust/nix/pull/1584))
|
||||
|
||||
### Changed
|
||||
|
||||
|
||||
111
src/sys/wait.rs
111
src/sys/wait.rs
@ -6,6 +6,11 @@ use crate::Result;
|
||||
use cfg_if::cfg_if;
|
||||
use libc::{self, c_int};
|
||||
use std::convert::TryFrom;
|
||||
#[cfg(any(
|
||||
target_os = "android",
|
||||
all(target_os = "linux", not(target_env = "uclibc")),
|
||||
))]
|
||||
use std::os::unix::io::RawFd;
|
||||
|
||||
libc_bitflags!(
|
||||
/// Controls the behavior of [`waitpid`].
|
||||
@ -233,6 +238,61 @@ impl WaitStatus {
|
||||
WaitStatus::Continued(pid)
|
||||
})
|
||||
}
|
||||
|
||||
/// Convert a `siginfo_t` as returned by `waitid` to a `WaitStatus`
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an `Error` corresponding to `EINVAL` for invalid values.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// siginfo_t is actually a union, not all fields may be initialized.
|
||||
/// The functions si_pid() and si_status() must be valid to call on
|
||||
/// the passed siginfo_t.
|
||||
#[cfg(any(
|
||||
target_os = "android",
|
||||
target_os = "freebsd",
|
||||
target_os = "haiku",
|
||||
all(target_os = "linux", not(target_env = "uclibc")),
|
||||
))]
|
||||
unsafe fn from_siginfo(siginfo: &libc::siginfo_t) -> Result<WaitStatus> {
|
||||
let si_pid = siginfo.si_pid();
|
||||
if si_pid == 0 {
|
||||
return Ok(WaitStatus::StillAlive);
|
||||
}
|
||||
|
||||
assert_eq!(siginfo.si_signo, libc::SIGCHLD);
|
||||
|
||||
let pid = Pid::from_raw(si_pid);
|
||||
let si_status = siginfo.si_status();
|
||||
|
||||
let status = match siginfo.si_code {
|
||||
libc::CLD_EXITED => WaitStatus::Exited(pid, si_status),
|
||||
libc::CLD_KILLED | libc::CLD_DUMPED => WaitStatus::Signaled(
|
||||
pid,
|
||||
Signal::try_from(si_status)?,
|
||||
siginfo.si_code == libc::CLD_DUMPED,
|
||||
),
|
||||
libc::CLD_STOPPED => WaitStatus::Stopped(pid, Signal::try_from(si_status)?),
|
||||
libc::CLD_CONTINUED => WaitStatus::Continued(pid),
|
||||
#[cfg(any(target_os = "android", target_os = "linux"))]
|
||||
libc::CLD_TRAPPED => {
|
||||
if si_status == libc::SIGTRAP | 0x80 {
|
||||
WaitStatus::PtraceSyscall(pid)
|
||||
} else {
|
||||
WaitStatus::PtraceEvent(
|
||||
pid,
|
||||
Signal::try_from(si_status & 0xff)?,
|
||||
(si_status >> 8) as c_int,
|
||||
)
|
||||
}
|
||||
}
|
||||
_ => return Err(Errno::EINVAL),
|
||||
};
|
||||
|
||||
Ok(status)
|
||||
}
|
||||
}
|
||||
|
||||
/// Wait for a process to change status
|
||||
@ -268,3 +328,54 @@ pub fn waitpid<P: Into<Option<Pid>>>(pid: P, options: Option<WaitPidFlag>) -> Re
|
||||
pub fn wait() -> Result<WaitStatus> {
|
||||
waitpid(None, None)
|
||||
}
|
||||
|
||||
/// The ID argument for `waitid`
|
||||
#[cfg(any(
|
||||
target_os = "android",
|
||||
target_os = "freebsd",
|
||||
target_os = "haiku",
|
||||
all(target_os = "linux", not(target_env = "uclibc")),
|
||||
))]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum Id {
|
||||
/// Wait for any child
|
||||
All,
|
||||
/// Wait for the child whose process ID matches the given PID
|
||||
Pid(Pid),
|
||||
/// Wait for the child whose process group ID matches the given PID
|
||||
///
|
||||
/// If the PID is zero, the caller's process group is used since Linux 5.4.
|
||||
PGid(Pid),
|
||||
/// Wait for the child referred to by the given PID file descriptor
|
||||
#[cfg(any(target_os = "android", target_os = "linux"))]
|
||||
PIDFd(RawFd),
|
||||
}
|
||||
|
||||
/// Wait for a process to change status
|
||||
///
|
||||
/// See also [waitid(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/waitid.html)
|
||||
#[cfg(any(
|
||||
target_os = "android",
|
||||
target_os = "freebsd",
|
||||
target_os = "haiku",
|
||||
all(target_os = "linux", not(target_env = "uclibc")),
|
||||
))]
|
||||
pub fn waitid(id: Id, flags: WaitPidFlag) -> Result<WaitStatus> {
|
||||
let (idtype, idval) = match id {
|
||||
Id::All => (libc::P_ALL, 0),
|
||||
Id::Pid(pid) => (libc::P_PID, pid.as_raw() as libc::id_t),
|
||||
Id::PGid(pid) => (libc::P_PGID, pid.as_raw() as libc::id_t),
|
||||
#[cfg(any(target_os = "android", target_os = "linux"))]
|
||||
Id::PIDFd(fd) => (libc::P_PIDFD, fd as libc::id_t),
|
||||
};
|
||||
|
||||
let siginfo = unsafe {
|
||||
// Memory is zeroed rather than uninitialized, as not all platforms
|
||||
// initialize the memory in the StillAlive case
|
||||
let mut siginfo: libc::siginfo_t = std::mem::zeroed();
|
||||
Errno::result(libc::waitid(idtype, idval, &mut siginfo, flags.bits()))?;
|
||||
siginfo
|
||||
};
|
||||
|
||||
unsafe { WaitStatus::from_siginfo(&siginfo) }
|
||||
}
|
||||
|
||||
@ -23,6 +23,33 @@ fn test_wait_signal() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(
|
||||
target_os = "android",
|
||||
target_os = "freebsd",
|
||||
target_os = "haiku",
|
||||
all(target_os = "linux", not(target_env = "uclibc")),
|
||||
))]
|
||||
#[cfg(not(any(target_arch = "mips", target_arch = "mips64")))]
|
||||
fn test_waitid_signal() {
|
||||
let _m = crate::FORK_MTX.lock();
|
||||
|
||||
// Safe: The child only calls `pause` and/or `_exit`, which are async-signal-safe.
|
||||
match unsafe{fork()}.expect("Error: Fork Failed") {
|
||||
Child => {
|
||||
pause();
|
||||
unsafe { _exit(123) }
|
||||
},
|
||||
Parent { child } => {
|
||||
kill(child, Some(SIGKILL)).expect("Error: Kill Failed");
|
||||
assert_eq!(
|
||||
waitid(Id::Pid(child), WaitPidFlag::WEXITED),
|
||||
Ok(WaitStatus::Signaled(child, SIGKILL, false)),
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wait_exit() {
|
||||
let _m = crate::FORK_MTX.lock();
|
||||
@ -36,6 +63,29 @@ fn test_wait_exit() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(
|
||||
target_os = "android",
|
||||
target_os = "freebsd",
|
||||
target_os = "haiku",
|
||||
all(target_os = "linux", not(target_env = "uclibc")),
|
||||
))]
|
||||
#[cfg(not(any(target_arch = "mips", target_arch = "mips64")))]
|
||||
fn test_waitid_exit() {
|
||||
let _m = crate::FORK_MTX.lock();
|
||||
|
||||
// Safe: Child only calls `_exit`, which is async-signal-safe.
|
||||
match unsafe{fork()}.expect("Error: Fork Failed") {
|
||||
Child => unsafe { _exit(12); },
|
||||
Parent { child } => {
|
||||
assert_eq!(
|
||||
waitid(Id::Pid(child), WaitPidFlag::WEXITED),
|
||||
Ok(WaitStatus::Exited(child, 12)),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_waitstatus_from_raw() {
|
||||
let pid = Pid::from_raw(1);
|
||||
@ -57,6 +107,25 @@ fn test_waitstatus_pid() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(
|
||||
target_os = "android",
|
||||
target_os = "freebsd",
|
||||
target_os = "haiku",
|
||||
all(target_os = "linux", not(target_env = "uclibc")),
|
||||
))]
|
||||
fn test_waitid_pid() {
|
||||
let _m = crate::FORK_MTX.lock();
|
||||
|
||||
match unsafe { fork() }.unwrap() {
|
||||
Child => unsafe { _exit(0) },
|
||||
Parent { child } => {
|
||||
let status = waitid(Id::Pid(child), WaitPidFlag::WEXITED).unwrap();
|
||||
assert_eq!(status.pid(), Some(child));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
// FIXME: qemu-user doesn't implement ptrace on most arches
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
@ -77,7 +146,7 @@ mod ptrace {
|
||||
unsafe { _exit(0) }
|
||||
}
|
||||
|
||||
fn ptrace_parent(child: Pid) {
|
||||
fn ptrace_wait_parent(child: Pid) {
|
||||
// Wait for the raised SIGTRAP
|
||||
assert_eq!(waitpid(child, None), Ok(WaitStatus::Stopped(child, SIGTRAP)));
|
||||
// We want to test a syscall stop and a PTRACE_EVENT stop
|
||||
@ -94,6 +163,39 @@ mod ptrace {
|
||||
assert_eq!(waitpid(child, None), Ok(WaitStatus::Exited(child, 0)));
|
||||
}
|
||||
|
||||
#[cfg(not(target_env = "uclibc"))]
|
||||
fn ptrace_waitid_parent(child: Pid) {
|
||||
// Wait for the raised SIGTRAP
|
||||
//
|
||||
// Unlike waitpid(), waitid() can distinguish trap events from regular
|
||||
// stop events, so unlike ptrace_wait_parent(), we get a PtraceEvent here
|
||||
assert_eq!(
|
||||
waitid(Id::Pid(child), WaitPidFlag::WEXITED),
|
||||
Ok(WaitStatus::PtraceEvent(child, SIGTRAP, 0)),
|
||||
);
|
||||
// We want to test a syscall stop and a PTRACE_EVENT stop
|
||||
assert!(ptrace::setoptions(child, Options::PTRACE_O_TRACESYSGOOD | Options::PTRACE_O_TRACEEXIT).is_ok());
|
||||
|
||||
// First, stop on the next system call, which will be exit()
|
||||
assert!(ptrace::syscall(child, None).is_ok());
|
||||
assert_eq!(
|
||||
waitid(Id::Pid(child), WaitPidFlag::WEXITED),
|
||||
Ok(WaitStatus::PtraceSyscall(child)),
|
||||
);
|
||||
// Then get the ptrace event for the process exiting
|
||||
assert!(ptrace::cont(child, None).is_ok());
|
||||
assert_eq!(
|
||||
waitid(Id::Pid(child), WaitPidFlag::WEXITED),
|
||||
Ok(WaitStatus::PtraceEvent(child, SIGTRAP, Event::PTRACE_EVENT_EXIT as i32)),
|
||||
);
|
||||
// Finally get the normal wait() result, now that the process has exited
|
||||
assert!(ptrace::cont(child, None).is_ok());
|
||||
assert_eq!(
|
||||
waitid(Id::Pid(child), WaitPidFlag::WEXITED),
|
||||
Ok(WaitStatus::Exited(child, 0)),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wait_ptrace() {
|
||||
require_capability!("test_wait_ptrace", CAP_SYS_PTRACE);
|
||||
@ -101,7 +203,19 @@ mod ptrace {
|
||||
|
||||
match unsafe{fork()}.expect("Error: Fork Failed") {
|
||||
Child => ptrace_child(),
|
||||
Parent { child } => ptrace_parent(child),
|
||||
Parent { child } => ptrace_wait_parent(child),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(target_env = "uclibc"))]
|
||||
fn test_waitid_ptrace() {
|
||||
require_capability!("test_waitid_ptrace", CAP_SYS_PTRACE);
|
||||
let _m = crate::FORK_MTX.lock();
|
||||
|
||||
match unsafe{fork()}.expect("Error: Fork Failed") {
|
||||
Child => ptrace_child(),
|
||||
Parent { child } => ptrace_waitid_parent(child),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user