Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 934e728

Browse files
committedJun 22, 2024
Auto merge of rust-lang#124101 - the8472:pidfd-methods, r=cuviper
Add PidFd::{kill, wait, try_wait} rust-lang#117957 changed `Child` kill/wait/try_wait to use its pidfd instead of the pid, when one is available. This PR extracts those implementations and makes them available on `PidFd` directly. The `PidFd` implementations differ significantly from the corresponding `Child` methods: * the methods can be called after the child has been reaped, which will result in an error but will be safe. This state is not observable in `Child` unless something stole the zombie child * the `ExitStatus` is not kept, meaning that only the first time a wait succeeds it will be returned * `wait` does not close stdin * `wait` only requires `&self` instead of `&mut self` since there is no state to maintain and subsequent calls are safe Tracking issue: rust-lang#82971
2 parents bb602cf + c2ec99b commit 934e728

File tree

7 files changed

+265
-124
lines changed

7 files changed

+265
-124
lines changed
 

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

+66-23
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,20 @@
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
///
2020
/// A `PidFd` can be obtained by setting the corresponding option on [`Command`]
2121
/// with [`create_pidfd`]. Subsequently, the created pidfd can be retrieved
22-
/// from the [`Child`] by calling [`pidfd`] or [`take_pidfd`].
22+
/// from the [`Child`] by calling [`pidfd`] or [`into_pidfd`].
2323
///
2424
/// Example:
2525
/// ```no_run
@@ -33,7 +33,7 @@ struct FileDesc;
3333
/// .expect("Failed to spawn child");
3434
///
3535
/// let pidfd = child
36-
/// .take_pidfd()
36+
/// .into_pidfd()
3737
/// .expect("Failed to retrieve pidfd");
3838
///
3939
/// // The file descriptor will be closed when `pidfd` is dropped.
@@ -44,66 +44,101 @@ struct FileDesc;
4444
/// [`create_pidfd`]: CommandExt::create_pidfd
4545
/// [`Child`]: process::Child
4646
/// [`pidfd`]: fn@ChildExt::pidfd
47-
/// [`take_pidfd`]: ChildExt::take_pidfd
47+
/// [`into_pidfd`]: ChildExt::into_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

@@ -124,18 +159,26 @@ pub trait ChildExt: Sealed {
124159
/// [`Child`]: process::Child
125160
fn pidfd(&self) -> Result<&PidFd>;
126161

127-
/// Takes ownership of the [`PidFd`] created for this [`Child`], if available.
162+
/// Returns the [`PidFd`] created for this [`Child`], if available.
163+
/// Otherwise self is returned.
128164
///
129165
/// A pidfd will only be available if its creation was requested with
130166
/// [`create_pidfd`] when the corresponding [`Command`] was created.
131167
///
168+
/// Taking ownership of the PidFd consumes the Child to avoid pid reuse
169+
/// races. Use [`pidfd`] and [`BorrowedFd::try_clone_to_owned`] if
170+
/// you don't want to disassemble the Child yet.
171+
///
132172
/// Even if requested, a pidfd may not be available due to an older
133173
/// version of Linux being in use, or if some other error occurred.
134174
///
135175
/// [`Command`]: process::Command
136176
/// [`create_pidfd`]: CommandExt::create_pidfd
177+
/// [`pidfd`]: ChildExt::pidfd
137178
/// [`Child`]: process::Child
138-
fn take_pidfd(&mut self) -> Result<PidFd>;
179+
fn into_pidfd(self) -> crate::result::Result<PidFd, Self>
180+
where
181+
Self: Sized;
139182
}
140183

141184
/// Os-specific extensions for [`Command`]
@@ -146,7 +189,7 @@ pub trait CommandExt: Sealed {
146189
/// spawned by this [`Command`].
147190
/// By default, no pidfd will be created.
148191
///
149-
/// The pidfd can be retrieved from the child with [`pidfd`] or [`take_pidfd`].
192+
/// The pidfd can be retrieved from the child with [`pidfd`] or [`into_pidfd`].
150193
///
151194
/// A pidfd will only be created if it is possible to do so
152195
/// in a guaranteed race-free manner. Otherwise, [`pidfd`] will return an error.
@@ -160,7 +203,7 @@ pub trait CommandExt: Sealed {
160203
/// [`Command`]: process::Command
161204
/// [`Child`]: process::Child
162205
/// [`pidfd`]: fn@ChildExt::pidfd
163-
/// [`take_pidfd`]: ChildExt::take_pidfd
206+
/// [`into_pidfd`]: ChildExt::into_pidfd
164207
fn create_pidfd(&mut self, val: bool) -> &mut process::Command;
165208
}
166209

‎std/src/sys/pal/unix/linux/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub mod pidfd;

‎std/src/sys/pal/unix/linux/pidfd.rs

+76
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+
}
+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
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 child = Command::new("sleep")
54+
.arg("1000")
55+
.create_pidfd(true)
56+
.spawn()
57+
.expect("executing 'sleep' failed");
58+
59+
let fd = child.into_pidfd().unwrap();
60+
61+
assert_matches!(fd.try_wait(), Ok(None));
62+
fd.kill().expect("kill failed");
63+
fd.kill().expect("sending kill twice failed");
64+
let status = fd.wait().expect("1st wait failed");
65+
assert_eq!(status.signal(), Some(libc::SIGKILL));
66+
67+
// Trying to wait again for a reaped child is safe since there's no pid-recycling race.
68+
// But doing so will return an error.
69+
let res = fd.wait();
70+
assert_matches!(res, Err(e) if e.raw_os_error() == Some(libc::ECHILD));
71+
72+
// Ditto for additional attempts to kill an already-dead child.
73+
let res = fd.kill();
74+
assert_matches!(res, Err(e) if e.raw_os_error() == Some(libc::ESRCH));
75+
}
76+
77+
fn probe_pidfd_support() -> bool {
78+
// pidfds require the pidfd_open syscall
79+
let our_pid = crate::process::id();
80+
let pidfd = unsafe { libc::syscall(libc::SYS_pidfd_open, our_pid, 0) };
81+
if pidfd >= 0 {
82+
unsafe { libc::close(pidfd as RawFd) };
83+
true
84+
} else {
85+
false
86+
}
87+
}

‎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")]
There was a problem loading the remainder of the diff.

0 commit comments

Comments
 (0)
Failed to load comments.