Skip to content

Commit 43c6d01

Browse files
committed
Add PidFd::{kill, wait, try_wait}
1 parent 99d0186 commit 43c6d01

File tree

7 files changed

+244
-117
lines changed

7 files changed

+244
-117
lines changed

library/std/src/os/linux/process.rs

+52-16
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@
66

77
use crate::io::Result;
88
use crate::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd};
9-
use crate::process;
9+
use crate::process::{self, ExitStatus};
1010
use crate::sealed::Sealed;
11-
#[cfg(not(doc))]
1211
use crate::sys::fd::FileDesc;
12+
#[cfg(not(doc))]
13+
use crate::sys::linux::pidfd::PidFd as InnerPidFd;
1314
use crate::sys_common::{AsInner, AsInnerMut, FromInner, IntoInner};
1415

1516
#[cfg(doc)]
16-
struct FileDesc;
17+
struct InnerPidFd;
1718

1819
/// This type represents a file descriptor that refers to a process.
1920
///
@@ -47,63 +48,98 @@ struct FileDesc;
4748
/// [`take_pidfd`]: ChildExt::take_pidfd
4849
/// [`pidfd_open(2)`]: https://man7.org/linux/man-pages/man2/pidfd_open.2.html
4950
#[derive(Debug)]
51+
#[repr(transparent)]
5052
pub struct PidFd {
51-
inner: FileDesc,
53+
inner: InnerPidFd,
54+
}
55+
56+
impl PidFd {
57+
/// Forces the child process to exit.
58+
///
59+
/// Unlike [`Child::kill`] it is possible to attempt to kill
60+
/// reaped children since PidFd does not suffer from pid recycling
61+
/// races. But doing so will return an Error.
62+
///
63+
/// [`Child::kill`]: process::Child::kill
64+
pub fn kill(&self) -> Result<()> {
65+
self.inner.kill()
66+
}
67+
68+
/// Waits for the child to exit completely, returning the status that it exited with.
69+
///
70+
/// Unlike [`Child::wait`] it does not ensure that the stdin handle is closed.
71+
/// Additionally it will not return an `ExitStatus` if the child
72+
/// has alrady been reaped. Instead an error will be returned.
73+
///
74+
/// [`Child::wait`]: process::Child::wait
75+
pub fn wait(&self) -> Result<ExitStatus> {
76+
self.inner.wait().map(FromInner::from_inner)
77+
}
78+
79+
/// Attempts to collect the exit status of the child if it has already exited.
80+
///
81+
/// Unlike [`Child::try_wait`] this method will return an Error
82+
/// if the child has already been reaped.
83+
///
84+
/// [`Child::try_wait`]: process::Child::try_wait
85+
pub fn try_wait(&self) -> Result<Option<ExitStatus>> {
86+
Ok(self.inner.try_wait()?.map(FromInner::from_inner))
87+
}
5288
}
5389

54-
impl AsInner<FileDesc> for PidFd {
90+
impl AsInner<InnerPidFd> for PidFd {
5591
#[inline]
56-
fn as_inner(&self) -> &FileDesc {
92+
fn as_inner(&self) -> &InnerPidFd {
5793
&self.inner
5894
}
5995
}
6096

61-
impl FromInner<FileDesc> for PidFd {
62-
fn from_inner(inner: FileDesc) -> PidFd {
97+
impl FromInner<InnerPidFd> for PidFd {
98+
fn from_inner(inner: InnerPidFd) -> PidFd {
6399
PidFd { inner }
64100
}
65101
}
66102

67-
impl IntoInner<FileDesc> for PidFd {
68-
fn into_inner(self) -> FileDesc {
103+
impl IntoInner<InnerPidFd> for PidFd {
104+
fn into_inner(self) -> InnerPidFd {
69105
self.inner
70106
}
71107
}
72108

73109
impl AsRawFd for PidFd {
74110
#[inline]
75111
fn as_raw_fd(&self) -> RawFd {
76-
self.as_inner().as_raw_fd()
112+
self.as_inner().as_inner().as_raw_fd()
77113
}
78114
}
79115

80116
impl FromRawFd for PidFd {
81117
unsafe fn from_raw_fd(fd: RawFd) -> Self {
82-
Self::from_inner(FileDesc::from_raw_fd(fd))
118+
Self::from_inner(InnerPidFd::from_raw_fd(fd))
83119
}
84120
}
85121

86122
impl IntoRawFd for PidFd {
87123
fn into_raw_fd(self) -> RawFd {
88-
self.into_inner().into_raw_fd()
124+
self.into_inner().into_inner().into_raw_fd()
89125
}
90126
}
91127

92128
impl AsFd for PidFd {
93129
fn as_fd(&self) -> BorrowedFd<'_> {
94-
self.as_inner().as_fd()
130+
self.as_inner().as_inner().as_fd()
95131
}
96132
}
97133

98134
impl From<OwnedFd> for PidFd {
99135
fn from(fd: OwnedFd) -> Self {
100-
Self::from_inner(FileDesc::from_inner(fd))
136+
Self::from_inner(InnerPidFd::from_inner(FileDesc::from_inner(fd)))
101137
}
102138
}
103139

104140
impl From<PidFd> for OwnedFd {
105141
fn from(pid_fd: PidFd) -> Self {
106-
pid_fd.into_inner().into_inner()
142+
pid_fd.into_inner().into_inner().into_inner()
107143
}
108144
}
109145

Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub mod pidfd;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
use crate::sys_common::{AsInner, FromInner, IntoInner};
2+
use crate::sys::pal::unix::fd::FileDesc;
3+
use crate::sys::process::ExitStatus;
4+
use crate::sys::cvt;
5+
use crate::io;
6+
use crate::os::fd::{AsRawFd, FromRawFd, RawFd};
7+
8+
#[cfg(test)]
9+
mod tests;
10+
11+
#[derive(Debug)]
12+
pub(crate) struct PidFd(FileDesc);
13+
14+
impl PidFd {
15+
pub fn kill(&self) -> io::Result<()> {
16+
return cvt(unsafe {
17+
libc::syscall(libc::SYS_pidfd_send_signal, self.0.as_raw_fd(), libc::SIGKILL, crate::ptr::null::<()>(), 0,)
18+
}).map(drop);
19+
}
20+
21+
22+
pub fn wait(&self) -> io::Result<ExitStatus> {
23+
let mut siginfo: libc::siginfo_t = unsafe { crate::mem::zeroed() };
24+
cvt(unsafe {
25+
libc::waitid(libc::P_PIDFD, self.0.as_raw_fd() as u32, &mut siginfo, libc::WEXITED)
26+
})?;
27+
return Ok(ExitStatus::from_waitid_siginfo(siginfo));
28+
}
29+
30+
pub fn try_wait(&self) -> io::Result<Option<ExitStatus>> {
31+
let mut siginfo: libc::siginfo_t = unsafe { crate::mem::zeroed() };
32+
33+
cvt(unsafe {
34+
libc::waitid(
35+
libc::P_PIDFD,
36+
self.0.as_raw_fd() as u32,
37+
&mut siginfo,
38+
libc::WEXITED | libc::WNOHANG,
39+
)
40+
})?;
41+
if unsafe { siginfo.si_pid() } == 0 {
42+
return Ok(None);
43+
}
44+
return Ok(Some(ExitStatus::from_waitid_siginfo(siginfo)));
45+
}
46+
}
47+
48+
impl AsInner<FileDesc> for PidFd {
49+
fn as_inner(&self) -> &FileDesc {
50+
&self.0
51+
}
52+
}
53+
54+
impl IntoInner<FileDesc> for PidFd {
55+
fn into_inner(self) -> FileDesc {
56+
self.0
57+
}
58+
}
59+
60+
impl FromInner<FileDesc> for PidFd {
61+
fn from_inner(inner: FileDesc) -> Self {
62+
Self(inner)
63+
}
64+
}
65+
66+
impl FromRawFd for PidFd {
67+
unsafe fn from_raw_fd(fd: RawFd) -> Self {
68+
Self(FileDesc::from_raw_fd(fd))
69+
}
70+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
use crate::os::unix::process::ExitStatusExt;
2+
use crate::process::Command;
3+
use crate::assert_matches::assert_matches;
4+
use crate::os::fd::{AsRawFd, RawFd};
5+
use crate::os::linux::process::{ChildExt, CommandExt};
6+
7+
8+
#[test]
9+
fn test_command_pidfd() {
10+
let pidfd_open_available = probe_pidfd_support();
11+
12+
// always exercise creation attempts
13+
let mut child = Command::new("false").create_pidfd(true).spawn().unwrap();
14+
15+
// but only check if we know that the kernel supports pidfds.
16+
// We don't assert the precise value, since the standard library
17+
// might have opened other file descriptors before our code runs.
18+
if pidfd_open_available {
19+
assert!(child.pidfd().is_ok());
20+
}
21+
if let Ok(pidfd) = child.pidfd() {
22+
let flags = super::cvt(unsafe { libc::fcntl(pidfd.as_raw_fd(), libc::F_GETFD) }).unwrap();
23+
assert!(flags & libc::FD_CLOEXEC != 0);
24+
}
25+
let status = child.wait().expect("error waiting on pidfd");
26+
assert_eq!(status.code(), Some(1));
27+
28+
let mut child = Command::new("sleep").arg("1000").create_pidfd(true).spawn().unwrap();
29+
assert_matches!(child.try_wait(), Ok(None));
30+
child.kill().expect("failed to kill child");
31+
let status = child.wait().expect("error waiting on pidfd");
32+
assert_eq!(status.signal(), Some(libc::SIGKILL));
33+
34+
let _ = Command::new("echo")
35+
.create_pidfd(false)
36+
.spawn()
37+
.unwrap()
38+
.pidfd()
39+
.expect_err("pidfd should not have been created when create_pid(false) is set");
40+
41+
let _ = Command::new("echo")
42+
.spawn()
43+
.unwrap()
44+
.pidfd()
45+
.expect_err("pidfd should not have been created");
46+
}
47+
48+
#[test]
49+
fn test_pidfd() {
50+
if !probe_pidfd_support() {
51+
return;
52+
}
53+
54+
let mut child = Command::new("sleep").arg("1000").create_pidfd(true).spawn().expect("executing 'sleep' failed");
55+
56+
let fd = child.take_pidfd().unwrap();
57+
drop(child);
58+
59+
assert_matches!(fd.try_wait(), Ok(None));
60+
fd.kill().expect("kill failed");
61+
fd.kill().expect("sending kill twice failed");
62+
let status = fd.wait().expect("1st wait failed");
63+
assert_eq!(status.signal(), Some(libc::SIGKILL));
64+
65+
// Trying to wait again for a reaped child is safe since there's no pid-recycling race.
66+
// But doing so will return an error.
67+
let res = fd.wait();
68+
assert_matches!(res, Err(e) if e.raw_os_error() == Some(libc::ECHILD));
69+
70+
// Ditto for additional attempts to kill an already-dead child.
71+
let res = fd.kill();
72+
assert_matches!(res, Err(e) if e.raw_os_error() == Some(libc::ESRCH));
73+
}
74+
75+
76+
fn probe_pidfd_support() -> bool {
77+
// pidfds require the pidfd_open syscall
78+
let our_pid = crate::process::id();
79+
let pidfd = unsafe { libc::syscall(libc::SYS_pidfd_open, our_pid, 0) };
80+
if pidfd >= 0 {
81+
unsafe { libc::close(pidfd as RawFd) };
82+
true
83+
} else {
84+
false
85+
}
86+
}

library/std/src/sys/pal/unix/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ pub mod io;
2020
pub mod kernel_copy;
2121
#[cfg(target_os = "l4re")]
2222
mod l4re;
23+
#[cfg(target_os = "linux")]
24+
pub mod linux;
2325
#[cfg(not(target_os = "l4re"))]
2426
pub mod net;
2527
#[cfg(target_os = "l4re")]

0 commit comments

Comments
 (0)