Skip to content

Commit 10e1f5d

Browse files
committed
Auto merge of #124101 - the8472:pidfd-methods, r=cuviper
Add PidFd::{kill, wait, try_wait} #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: #82971
2 parents fcae626 + 8abf149 commit 10e1f5d

File tree

7 files changed

+265
-124
lines changed

7 files changed

+265
-124
lines changed

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

Lines changed: 66 additions & 23 deletions
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

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: 87 additions & 0 deletions
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+
}

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