Skip to content

Commit 0787c73

Browse files
committed
Add PidFd::{kill, wait, try_wait}
1 parent 894f7a4 commit 0787c73

File tree

7 files changed

+251
-117
lines changed

7 files changed

+251
-117
lines changed

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

Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@
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;
1111
#[cfg(not(doc))]
12-
use crate::sys::fd::FileDesc;
12+
use crate::sys::{fd::FileDesc, linux::pidfd::PidFd as InnerPidFd};
1313
use crate::sys_common::{AsInner, AsInnerMut, FromInner, IntoInner};
1414

1515
#[cfg(doc)]
16-
struct FileDesc;
16+
struct InnerPidFd;
1717

1818
/// This type represents a file descriptor that refers to a process.
1919
///
@@ -47,63 +47,98 @@ struct FileDesc;
4747
/// [`take_pidfd`]: ChildExt::take_pidfd
4848
/// [`pidfd_open(2)`]: https://man7.org/linux/man-pages/man2/pidfd_open.2.html
4949
#[derive(Debug)]
50+
#[repr(transparent)]
5051
pub struct PidFd {
51-
inner: FileDesc,
52+
inner: InnerPidFd,
5253
}
5354

54-
impl AsInner<FileDesc> for PidFd {
55+
impl PidFd {
56+
/// Forces the child process to exit.
57+
///
58+
/// Unlike [`Child::kill`] it is possible to attempt to kill
59+
/// reaped children since PidFd does not suffer from pid recycling
60+
/// races. But doing so will return an Error.
61+
///
62+
/// [`Child::kill`]: process::Child::kill
63+
pub fn kill(&self) -> Result<()> {
64+
self.inner.kill()
65+
}
66+
67+
/// Waits for the child to exit completely, returning the status that it exited with.
68+
///
69+
/// Unlike [`Child::wait`] it does not ensure that the stdin handle is closed.
70+
/// Additionally it will not return an `ExitStatus` if the child
71+
/// has already been reaped. Instead an error will be returned.
72+
///
73+
/// [`Child::wait`]: process::Child::wait
74+
pub fn wait(&self) -> Result<ExitStatus> {
75+
self.inner.wait().map(FromInner::from_inner)
76+
}
77+
78+
/// Attempts to collect the exit status of the child if it has already exited.
79+
///
80+
/// Unlike [`Child::try_wait`] this method will return an Error
81+
/// if the child has already been reaped.
82+
///
83+
/// [`Child::try_wait`]: process::Child::try_wait
84+
pub fn try_wait(&self) -> Result<Option<ExitStatus>> {
85+
Ok(self.inner.try_wait()?.map(FromInner::from_inner))
86+
}
87+
}
88+
89+
impl AsInner<InnerPidFd> for PidFd {
5590
#[inline]
56-
fn as_inner(&self) -> &FileDesc {
91+
fn as_inner(&self) -> &InnerPidFd {
5792
&self.inner
5893
}
5994
}
6095

61-
impl FromInner<FileDesc> for PidFd {
62-
fn from_inner(inner: FileDesc) -> PidFd {
96+
impl FromInner<InnerPidFd> for PidFd {
97+
fn from_inner(inner: InnerPidFd) -> PidFd {
6398
PidFd { inner }
6499
}
65100
}
66101

67-
impl IntoInner<FileDesc> for PidFd {
68-
fn into_inner(self) -> FileDesc {
102+
impl IntoInner<InnerPidFd> for PidFd {
103+
fn into_inner(self) -> InnerPidFd {
69104
self.inner
70105
}
71106
}
72107

73108
impl AsRawFd for PidFd {
74109
#[inline]
75110
fn as_raw_fd(&self) -> RawFd {
76-
self.as_inner().as_raw_fd()
111+
self.as_inner().as_inner().as_raw_fd()
77112
}
78113
}
79114

80115
impl FromRawFd for PidFd {
81116
unsafe fn from_raw_fd(fd: RawFd) -> Self {
82-
Self::from_inner(FileDesc::from_raw_fd(fd))
117+
Self::from_inner(InnerPidFd::from_raw_fd(fd))
83118
}
84119
}
85120

86121
impl IntoRawFd for PidFd {
87122
fn into_raw_fd(self) -> RawFd {
88-
self.into_inner().into_raw_fd()
123+
self.into_inner().into_inner().into_raw_fd()
89124
}
90125
}
91126

92127
impl AsFd for PidFd {
93128
fn as_fd(&self) -> BorrowedFd<'_> {
94-
self.as_inner().as_fd()
129+
self.as_inner().as_inner().as_fd()
95130
}
96131
}
97132

98133
impl From<OwnedFd> for PidFd {
99134
fn from(fd: OwnedFd) -> Self {
100-
Self::from_inner(FileDesc::from_inner(fd))
135+
Self::from_inner(InnerPidFd::from_inner(FileDesc::from_inner(fd)))
101136
}
102137
}
103138

104139
impl From<PidFd> for OwnedFd {
105140
fn from(pid_fd: PidFd) -> Self {
106-
pid_fd.into_inner().into_inner()
141+
pid_fd.into_inner().into_inner().into_inner()
107142
}
108143
}
109144

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

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

Lines changed: 2 additions & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy