#![forbid(missing_docs)]
#[cfg(not(target_os = "linux"))]
compile_error!("pidfd only works on Linux");
use std::io;
use std::mem::MaybeUninit;
use std::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd};
use std::os::unix::process::ExitStatusExt;
use std::process::ExitStatus;
#[cfg(feature = "async")]
use async_io::Async;
fn syscall_result(ret: libc::c_long) -> io::Result<libc::c_long> {
if ret == -1 {
Err(io::Error::last_os_error())
} else {
Ok(ret)
}
}
fn pidfd_open(pid: libc::pid_t, flags: libc::c_uint) -> io::Result<OwnedFd> {
let ret = syscall_result(unsafe { libc::syscall(libc::SYS_pidfd_open, pid, flags) })?;
Ok(unsafe { OwnedFd::from_raw_fd(ret as RawFd) })
}
fn waitid_pidfd(pidfd: BorrowedFd) -> io::Result<(libc::siginfo_t, libc::rusage)> {
let mut siginfo = MaybeUninit::uninit();
let mut rusage = MaybeUninit::uninit();
unsafe {
syscall_result(libc::syscall(
libc::SYS_waitid,
libc::P_PIDFD,
pidfd,
siginfo.as_mut_ptr(),
libc::WEXITED,
rusage.as_mut_ptr(),
))?;
Ok((siginfo.assume_init(), rusage.assume_init()))
}
}
pub struct PidFd(OwnedFd);
impl PidFd {
pub fn from_pid(pid: libc::pid_t) -> io::Result<Self> {
Ok(Self(pidfd_open(pid, 0)?))
}
pub fn wait(&self) -> io::Result<ExitInfo> {
let (siginfo, rusage) = waitid_pidfd(self.0.as_fd())?;
Ok(ExitInfo { siginfo, rusage })
}
}
impl AsRawFd for PidFd {
fn as_raw_fd(&self) -> RawFd {
self.0.as_raw_fd()
}
}
impl AsFd for PidFd {
fn as_fd(&self) -> BorrowedFd<'_> {
self.0.as_fd()
}
}
pub struct ExitInfo {
pub siginfo: libc::siginfo_t,
pub rusage: libc::rusage,
}
impl ExitInfo {
pub fn status(&self) -> ExitStatus {
let si_status = unsafe { self.siginfo.si_status() };
let raw_status = if self.siginfo.si_code == libc::CLD_EXITED {
si_status << 8
} else {
si_status
};
ExitStatus::from_raw(raw_status)
}
}
#[cfg(feature = "async")]
pub struct AsyncPidFd(Async<PidFd>);
#[cfg(feature = "async")]
impl AsyncPidFd {
pub fn from_pid(pid: libc::pid_t) -> io::Result<Self> {
Ok(Self(Async::new(PidFd::from_pid(pid)?)?))
}
pub async fn wait(&self) -> io::Result<ExitInfo> {
self.0.readable().await?;
self.0.get_ref().wait()
}
}
#[cfg(test)]
mod test {
use super::*;
use std::process::Command;
fn spawn_and_status(cmd: &mut Command) -> io::Result<ExitStatus> {
let child = cmd.spawn()?;
let pidfd = PidFd::from_pid(child.id() as libc::pid_t)?;
Ok(pidfd.wait()?.status())
}
#[test]
fn test() -> io::Result<()> {
let status = spawn_and_status(&mut Command::new("/bin/true"))?;
assert_eq!(status.code(), Some(0));
assert_eq!(status.signal(), None);
let status = spawn_and_status(&mut Command::new("/bin/false"))?;
assert_eq!(status.code(), Some(1));
assert_eq!(status.signal(), None);
let status = spawn_and_status(Command::new("/bin/sh").arg("-c").arg("kill -9 $$"))?;
assert_eq!(status.code(), None);
assert_eq!(status.signal(), Some(9));
Ok(())
}
fn assert_echild(ret: io::Result<ExitInfo>) {
if let Err(e) = ret {
assert_eq!(e.raw_os_error(), Some(libc::ECHILD));
} else {
panic!("Expected an error!");
}
}
#[test]
fn test_wait_twice() -> io::Result<()> {
let child = Command::new("/bin/true").spawn()?;
let pidfd = PidFd::from_pid(child.id() as libc::pid_t)?;
let status = pidfd.wait()?.status();
assert!(status.success());
let ret = pidfd.wait();
assert_echild(ret);
Ok(())
}
#[cfg(feature = "async")]
async fn async_spawn_and_status(cmd: &mut Command) -> io::Result<ExitStatus> {
let child = cmd.spawn()?;
let pidfd = AsyncPidFd::from_pid(child.id() as libc::pid_t)?;
Ok(pidfd.wait().await?.status())
}
#[cfg(feature = "async")]
#[test]
fn test_async() -> io::Result<()> {
use futures_lite::future;
future::block_on(async {
let (status1, status2) = future::try_zip(
async_spawn_and_status(&mut Command::new("/bin/true")),
async_spawn_and_status(&mut Command::new("/bin/false")),
)
.await?;
assert_eq!(status1.code(), Some(0));
assert_eq!(status2.code(), Some(1));
Ok(())
})
}
#[cfg(feature = "async")]
#[test]
fn test_async_concurrent() -> io::Result<()> {
use futures_lite::future::{self, FutureExt};
future::block_on(async {
let status = async_spawn_and_status(
Command::new("/bin/sh")
.arg("-c")
.arg("read line")
.stdin(std::process::Stdio::piped()),
)
.or(async_spawn_and_status(&mut Command::new("/bin/false")))
.await?;
assert_eq!(status.code(), Some(1));
Ok(())
})
}
#[cfg(feature = "async")]
#[test]
fn test_async_wait_twice() -> io::Result<()> {
futures_lite::future::block_on(async {
let child = Command::new("/bin/true").spawn()?;
let pidfd = AsyncPidFd::from_pid(child.id() as libc::pid_t)?;
let status = pidfd.wait().await?.status();
assert!(status.success());
let ret = pidfd.wait().await;
assert_echild(ret);
Ok(())
})
}
}