Skip to content

Commit 4e21ef2

Browse files
committedAug 1, 2021
Auto merge of #81825 - voidc:pidfd, r=joshtriplett
Add Linux-specific pidfd process extensions (take 2) Continuation of #77168. I addressed the following concerns from the original PR: - make `CommandExt` and `ChildExt` sealed traits - wrap file descriptors in `PidFd` struct representing ownership over the fd - add `take_pidfd` to take the fd out of `Child` - close fd when dropped Tracking Issue: #82971
·
1.88.01.56.0
2 parents 2e9c870 + 4a832d3 commit 4e21ef2

File tree

8 files changed

+410
-11
lines changed

8 files changed

+410
-11
lines changed
 

‎library/std/src/os/linux/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@
44
#![doc(cfg(target_os = "linux"))]
55

66
pub mod fs;
7+
pub mod process;
78
pub mod raw;

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

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
//! Linux-specific extensions to primitives in the `std::process` module.
2+
3+
#![unstable(feature = "linux_pidfd", issue = "82971")]
4+
5+
use crate::io::Result;
6+
use crate::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
7+
use crate::process;
8+
#[cfg(not(doc))]
9+
use crate::sys::fd::FileDesc;
10+
use crate::sys_common::{AsInner, AsInnerMut, FromInner, IntoInner};
11+
12+
#[cfg(doc)]
13+
struct FileDesc;
14+
15+
/// This type represents a file descriptor that refers to a process.
16+
///
17+
/// A `PidFd` can be obtained by setting the corresponding option on [`Command`]
18+
/// with [`create_pidfd`]. Subsequently, the created pidfd can be retrieved
19+
/// from the [`Child`] by calling [`pidfd`] or [`take_pidfd`].
20+
///
21+
/// Example:
22+
/// ```no_run
23+
/// #![feature(linux_pidfd)]
24+
/// use std::os::linux::process::{CommandExt, ChildExt};
25+
/// use std::process::Command;
26+
///
27+
/// let mut child = Command::new("echo")
28+
/// .create_pidfd(true)
29+
/// .spawn()
30+
/// .expect("Failed to spawn child");
31+
///
32+
/// let pidfd = child
33+
/// .take_pidfd()
34+
/// .expect("Failed to retrieve pidfd");
35+
///
36+
/// // The file descriptor will be closed when `pidfd` is dropped.
37+
/// ```
38+
/// Refer to the man page of [`pidfd_open(2)`] for further details.
39+
///
40+
/// [`Command`]: process::Command
41+
/// [`create_pidfd`]: CommandExt::create_pidfd
42+
/// [`Child`]: process::Child
43+
/// [`pidfd`]: fn@ChildExt::pidfd
44+
/// [`take_pidfd`]: ChildExt::take_pidfd
45+
/// [`pidfd_open(2)`]: https://man7.org/linux/man-pages/man2/pidfd_open.2.html
46+
#[derive(Debug)]
47+
pub struct PidFd {
48+
inner: FileDesc,
49+
}
50+
51+
impl AsInner<FileDesc> for PidFd {
52+
fn as_inner(&self) -> &FileDesc {
53+
&self.inner
54+
}
55+
}
56+
57+
impl FromInner<FileDesc> for PidFd {
58+
fn from_inner(inner: FileDesc) -> PidFd {
59+
PidFd { inner }
60+
}
61+
}
62+
63+
impl IntoInner<FileDesc> for PidFd {
64+
fn into_inner(self) -> FileDesc {
65+
self.inner
66+
}
67+
}
68+
69+
impl AsRawFd for PidFd {
70+
fn as_raw_fd(&self) -> RawFd {
71+
self.as_inner().raw()
72+
}
73+
}
74+
75+
impl FromRawFd for PidFd {
76+
unsafe fn from_raw_fd(fd: RawFd) -> Self {
77+
Self::from_inner(FileDesc::new(fd))
78+
}
79+
}
80+
81+
impl IntoRawFd for PidFd {
82+
fn into_raw_fd(self) -> RawFd {
83+
self.into_inner().into_raw()
84+
}
85+
}
86+
87+
mod private_child_ext {
88+
pub trait Sealed {}
89+
impl Sealed for crate::process::Child {}
90+
}
91+
92+
/// Os-specific extensions for [`Child`]
93+
///
94+
/// [`Child`]: process::Child
95+
pub trait ChildExt: private_child_ext::Sealed {
96+
/// Obtains a reference to the [`PidFd`] created for this [`Child`], if available.
97+
///
98+
/// A pidfd will only be available if its creation was requested with
99+
/// [`create_pidfd`] when the corresponding [`Command`] was created.
100+
///
101+
/// Even if requested, a pidfd may not be available due to an older
102+
/// version of Linux being in use, or if some other error occurred.
103+
///
104+
/// [`Command`]: process::Command
105+
/// [`create_pidfd`]: CommandExt::create_pidfd
106+
/// [`Child`]: process::Child
107+
fn pidfd(&self) -> Result<&PidFd>;
108+
109+
/// Takes ownership of the [`PidFd`] created for this [`Child`], if available.
110+
///
111+
/// A pidfd will only be available if its creation was requested with
112+
/// [`create_pidfd`] when the corresponding [`Command`] was created.
113+
///
114+
/// Even if requested, a pidfd may not be available due to an older
115+
/// version of Linux being in use, or if some other error occurred.
116+
///
117+
/// [`Command`]: process::Command
118+
/// [`create_pidfd`]: CommandExt::create_pidfd
119+
/// [`Child`]: process::Child
120+
fn take_pidfd(&mut self) -> Result<PidFd>;
121+
}
122+
123+
mod private_command_ext {
124+
pub trait Sealed {}
125+
impl Sealed for crate::process::Command {}
126+
}
127+
128+
/// Os-specific extensions for [`Command`]
129+
///
130+
/// [`Command`]: process::Command
131+
pub trait CommandExt: private_command_ext::Sealed {
132+
/// Sets whether a [`PidFd`](struct@PidFd) should be created for the [`Child`]
133+
/// spawned by this [`Command`].
134+
/// By default, no pidfd will be created.
135+
///
136+
/// The pidfd can be retrieved from the child with [`pidfd`] or [`take_pidfd`].
137+
///
138+
/// A pidfd will only be created if it is possible to do so
139+
/// in a guaranteed race-free manner (e.g. if the `clone3` system call
140+
/// is supported). Otherwise, [`pidfd`] will return an error.
141+
///
142+
/// [`Command`]: process::Command
143+
/// [`Child`]: process::Child
144+
/// [`pidfd`]: fn@ChildExt::pidfd
145+
/// [`take_pidfd`]: ChildExt::take_pidfd
146+
fn create_pidfd(&mut self, val: bool) -> &mut process::Command;
147+
}
148+
149+
impl CommandExt for process::Command {
150+
fn create_pidfd(&mut self, val: bool) -> &mut process::Command {
151+
self.as_inner_mut().create_pidfd(val);
152+
self
153+
}
154+
}

‎library/std/src/process.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ use crate::sys_common::{AsInner, AsInnerMut, FromInner, IntoInner};
166166
/// [`wait`]: Child::wait
167167
#[stable(feature = "process", since = "1.0.0")]
168168
pub struct Child {
169-
handle: imp::Process,
169+
pub(crate) handle: imp::Process,
170170

171171
/// The handle for writing to the child's standard input (stdin), if it has
172172
/// been captured. To avoid partially moving

‎library/std/src/sys/unix/process/process_common.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ pub struct Command {
7979
stdin: Option<Stdio>,
8080
stdout: Option<Stdio>,
8181
stderr: Option<Stdio>,
82+
#[cfg(target_os = "linux")]
83+
create_pidfd: bool,
8284
}
8385

8486
// Create a new type for argv, so that we can make it `Send` and `Sync`
@@ -124,6 +126,7 @@ pub enum Stdio {
124126
}
125127

126128
impl Command {
129+
#[cfg(not(target_os = "linux"))]
127130
pub fn new(program: &OsStr) -> Command {
128131
let mut saw_nul = false;
129132
let program = os2c(program, &mut saw_nul);
@@ -144,6 +147,28 @@ impl Command {
144147
}
145148
}
146149

150+
#[cfg(target_os = "linux")]
151+
pub fn new(program: &OsStr) -> Command {
152+
let mut saw_nul = false;
153+
let program = os2c(program, &mut saw_nul);
154+
Command {
155+
argv: Argv(vec![program.as_ptr(), ptr::null()]),
156+
args: vec![program.clone()],
157+
program,
158+
env: Default::default(),
159+
cwd: None,
160+
uid: None,
161+
gid: None,
162+
saw_nul,
163+
closures: Vec::new(),
164+
groups: None,
165+
stdin: None,
166+
stdout: None,
167+
stderr: None,
168+
create_pidfd: false,
169+
}
170+
}
171+
147172
pub fn set_arg_0(&mut self, arg: &OsStr) {
148173
// Set a new arg0
149174
let arg = os2c(arg, &mut self.saw_nul);
@@ -177,6 +202,22 @@ impl Command {
177202
self.groups = Some(Box::from(groups));
178203
}
179204

205+
#[cfg(target_os = "linux")]
206+
pub fn create_pidfd(&mut self, val: bool) {
207+
self.create_pidfd = val;
208+
}
209+
210+
#[cfg(not(target_os = "linux"))]
211+
#[allow(dead_code)]
212+
pub fn get_create_pidfd(&self) -> bool {
213+
false
214+
}
215+
216+
#[cfg(target_os = "linux")]
217+
pub fn get_create_pidfd(&self) -> bool {
218+
self.create_pidfd
219+
}
220+
180221
pub fn saw_nul(&self) -> bool {
181222
self.saw_nul
182223
}

‎library/std/src/sys/unix/process/process_unix.rs

Lines changed: 133 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ use crate::sys;
99
use crate::sys::cvt;
1010
use crate::sys::process::process_common::*;
1111

12+
#[cfg(target_os = "linux")]
13+
use crate::os::linux::process::PidFd;
14+
15+
#[cfg(target_os = "linux")]
16+
use crate::sys::weak::syscall;
17+
1218
#[cfg(any(
1319
target_os = "macos",
1420
target_os = "freebsd",
@@ -61,7 +67,8 @@ impl Command {
6167
// a lock any more because the parent won't do anything and the child is
6268
// in its own process. Thus the parent drops the lock guard while the child
6369
// forgets it to avoid unlocking it on a new thread, which would be invalid.
64-
let (env_lock, pid) = unsafe { (sys::os::env_read_lock(), cvt(libc::fork())?) };
70+
let env_lock = sys::os::env_read_lock();
71+
let (pid, pidfd) = unsafe { self.do_fork()? };
6572

6673
if pid == 0 {
6774
crate::panic::always_abort();
@@ -90,7 +97,7 @@ impl Command {
9097
drop(env_lock);
9198
drop(output);
9299

93-
let mut p = Process { pid, status: None };
100+
let mut p = Process::new(pid, pidfd);
94101
let mut bytes = [0; 8];
95102

96103
// loop to handle EINTR
@@ -122,6 +129,92 @@ impl Command {
122129
}
123130
}
124131

132+
// Attempts to fork the process. If successful, returns Ok((0, -1))
133+
// in the child, and Ok((child_pid, -1)) in the parent.
134+
#[cfg(not(target_os = "linux"))]
135+
unsafe fn do_fork(&mut self) -> Result<(pid_t, pid_t), io::Error> {
136+
cvt(libc::fork()).map(|res| (res, -1))
137+
}
138+
139+
// Attempts to fork the process. If successful, returns Ok((0, -1))
140+
// in the child, and Ok((child_pid, child_pidfd)) in the parent.
141+
#[cfg(target_os = "linux")]
142+
unsafe fn do_fork(&mut self) -> Result<(pid_t, pid_t), io::Error> {
143+
use crate::sync::atomic::{AtomicBool, Ordering};
144+
145+
static HAS_CLONE3: AtomicBool = AtomicBool::new(true);
146+
const CLONE_PIDFD: u64 = 0x00001000;
147+
148+
#[repr(C)]
149+
struct clone_args {
150+
flags: u64,
151+
pidfd: u64,
152+
child_tid: u64,
153+
parent_tid: u64,
154+
exit_signal: u64,
155+
stack: u64,
156+
stack_size: u64,
157+
tls: u64,
158+
set_tid: u64,
159+
set_tid_size: u64,
160+
cgroup: u64,
161+
}
162+
163+
syscall! {
164+
fn clone3(cl_args: *mut clone_args, len: libc::size_t) -> libc::c_long
165+
}
166+
167+
// If we fail to create a pidfd for any reason, this will
168+
// stay as -1, which indicates an error.
169+
let mut pidfd: pid_t = -1;
170+
171+
// Attempt to use the `clone3` syscall, which supports more arguments
172+
// (in particular, the ability to create a pidfd). If this fails,
173+
// we will fall through this block to a call to `fork()`
174+
if HAS_CLONE3.load(Ordering::Relaxed) {
175+
let mut flags = 0;
176+
if self.get_create_pidfd() {
177+
flags |= CLONE_PIDFD;
178+
}
179+
180+
let mut args = clone_args {
181+
flags,
182+
pidfd: &mut pidfd as *mut pid_t as u64,
183+
child_tid: 0,
184+
parent_tid: 0,
185+
exit_signal: libc::SIGCHLD as u64,
186+
stack: 0,
187+
stack_size: 0,
188+
tls: 0,
189+
set_tid: 0,
190+
set_tid_size: 0,
191+
cgroup: 0,
192+
};
193+
194+
let args_ptr = &mut args as *mut clone_args;
195+
let args_size = crate::mem::size_of::<clone_args>();
196+
197+
let res = cvt(clone3(args_ptr, args_size));
198+
match res {
199+
Ok(n) => return Ok((n as pid_t, pidfd)),
200+
Err(e) => match e.raw_os_error() {
201+
// Multiple threads can race to execute this store,
202+
// but that's fine - that just means that multiple threads
203+
// will have tried and failed to execute the same syscall,
204+
// with no other side effects.
205+
Some(libc::ENOSYS) => HAS_CLONE3.store(false, Ordering::Relaxed),
206+
// Fallback to fork if `EPERM` is returned. (e.g. blocked by seccomp)
207+
Some(libc::EPERM) => {}
208+
_ => return Err(e),
209+
},
210+
}
211+
}
212+
213+
// If we get here, the 'clone3' syscall does not exist
214+
// or we do not have permission to call it
215+
cvt(libc::fork()).map(|res| (res, pidfd))
216+
}
217+
125218
pub fn exec(&mut self, default: Stdio) -> io::Error {
126219
let envp = self.capture_env();
127220

@@ -308,6 +401,7 @@ impl Command {
308401
|| (self.env_saw_path() && !self.program_is_path())
309402
|| !self.get_closures().is_empty()
310403
|| self.get_groups().is_some()
404+
|| self.get_create_pidfd()
311405
{
312406
return Ok(None);
313407
}
@@ -352,7 +446,7 @@ impl Command {
352446
None => None,
353447
};
354448

355-
let mut p = Process { pid: 0, status: None };
449+
let mut p = Process::new(0, -1);
356450

357451
struct PosixSpawnFileActions<'a>(&'a mut MaybeUninit<libc::posix_spawn_file_actions_t>);
358452

@@ -441,9 +535,27 @@ impl Command {
441535
pub struct Process {
442536
pid: pid_t,
443537
status: Option<ExitStatus>,
538+
// On Linux, stores the pidfd created for this child.
539+
// This is None if the user did not request pidfd creation,
540+
// or if the pidfd could not be created for some reason
541+
// (e.g. the `clone3` syscall was not available).
542+
#[cfg(target_os = "linux")]
543+
pidfd: Option<PidFd>,
444544
}
445545

446546
impl Process {
547+
#[cfg(target_os = "linux")]
548+
fn new(pid: pid_t, pidfd: pid_t) -> Self {
549+
use crate::sys_common::FromInner;
550+
let pidfd = (pidfd >= 0).then(|| PidFd::from_inner(sys::fd::FileDesc::new(pidfd)));
551+
Process { pid, status: None, pidfd }
552+
}
553+
554+
#[cfg(not(target_os = "linux"))]
555+
fn new(pid: pid_t, _pidfd: pid_t) -> Self {
556+
Process { pid, status: None }
557+
}
558+
447559
pub fn id(&self) -> u32 {
448560
self.pid as u32
449561
}
@@ -580,6 +692,24 @@ impl ExitStatusError {
580692
}
581693
}
582694

695+
#[cfg(target_os = "linux")]
696+
#[unstable(feature = "linux_pidfd", issue = "82971")]
697+
impl crate::os::linux::process::ChildExt for crate::process::Child {
698+
fn pidfd(&self) -> io::Result<&PidFd> {
699+
self.handle
700+
.pidfd
701+
.as_ref()
702+
.ok_or_else(|| Error::new(ErrorKind::Other, "No pidfd was created."))
703+
}
704+
705+
fn take_pidfd(&mut self) -> io::Result<PidFd> {
706+
self.handle
707+
.pidfd
708+
.take()
709+
.ok_or_else(|| Error::new(ErrorKind::Other, "No pidfd was created."))
710+
}
711+
}
712+
583713
#[cfg(test)]
584714
#[path = "process_unix/tests.rs"]
585715
mod tests;
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// run-pass
2+
// only-linux - pidfds are a linux-specific concept
3+
4+
#![feature(linux_pidfd)]
5+
#![feature(rustc_private)]
6+
7+
extern crate libc;
8+
9+
use std::io::Error;
10+
use std::os::linux::process::{ChildExt, CommandExt};
11+
use std::process::Command;
12+
13+
fn has_clone3() -> bool {
14+
let res = unsafe { libc::syscall(libc::SYS_clone3, 0, 0) };
15+
let err = (res == -1)
16+
.then(|| Error::last_os_error())
17+
.expect("probe syscall should not succeed");
18+
err.raw_os_error() != Some(libc::ENOSYS)
19+
}
20+
21+
fn main() {
22+
// pidfds require the clone3 syscall
23+
if !has_clone3() {
24+
return;
25+
}
26+
27+
// We don't assert the precise value, since the standard library
28+
// might have opened other file descriptors before our code runs.
29+
let _ = Command::new("echo")
30+
.create_pidfd(true)
31+
.spawn()
32+
.unwrap()
33+
.pidfd().expect("failed to obtain pidfd");
34+
35+
let _ = Command::new("echo")
36+
.create_pidfd(false)
37+
.spawn()
38+
.unwrap()
39+
.pidfd().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().expect_err("pidfd should not have been created");
45+
}

‎src/test/ui/command/command-pre-exec.rs

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,30 @@
88
// ignore-sgx no processes
99
#![feature(process_exec, rustc_private)]
1010

11-
extern crate libc;
12-
1311
use std::env;
1412
use std::io::Error;
1513
use std::os::unix::process::CommandExt;
1614
use std::process::Command;
1715
use std::sync::atomic::{AtomicUsize, Ordering};
1816
use std::sync::Arc;
1917

18+
#[cfg(not(target_os = "linux"))]
19+
fn getpid() -> u32 {
20+
use std::process;
21+
process::id()
22+
}
23+
24+
/// We need to directly use the getpid syscall instead of using `process::id()`
25+
/// because the libc wrapper might return incorrect values after a process was
26+
/// forked.
27+
#[cfg(target_os = "linux")]
28+
fn getpid() -> u32 {
29+
extern crate libc;
30+
unsafe {
31+
libc::syscall(libc::SYS_getpid) as _
32+
}
33+
}
34+
2035
fn main() {
2136
if let Some(arg) = env::args().nth(1) {
2237
match &arg[..] {
@@ -68,14 +83,12 @@ fn main() {
6883
};
6984
assert_eq!(output.raw_os_error(), Some(102));
7085

71-
let pid = unsafe { libc::getpid() };
72-
assert!(pid >= 0);
86+
let pid = getpid();
7387
let output = unsafe {
7488
Command::new(&me)
7589
.arg("empty")
7690
.pre_exec(move || {
77-
let child = libc::getpid();
78-
assert!(child >= 0);
91+
let child = getpid();
7992
assert!(pid != child);
8093
Ok(())
8194
})

‎src/test/ui/process/process-panic-after-fork.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,21 @@ use std::sync::atomic::{AtomicU32, Ordering};
2323

2424
use libc::c_int;
2525

26+
#[cfg(not(target_os = "linux"))]
27+
fn getpid() -> u32 {
28+
process::id()
29+
}
30+
31+
/// We need to directly use the getpid syscall instead of using `process::id()`
32+
/// because the libc wrapper might return incorrect values after a process was
33+
/// forked.
34+
#[cfg(target_os = "linux")]
35+
fn getpid() -> u32 {
36+
unsafe {
37+
libc::syscall(libc::SYS_getpid) as _
38+
}
39+
}
40+
2641
/// This stunt allocator allows us to spot heap allocations in the child.
2742
struct PidChecking<A> {
2843
parent: A,
@@ -44,7 +59,7 @@ impl<A> PidChecking<A> {
4459
fn check(&self) {
4560
let require_pid = self.require_pid.load(Ordering::Acquire);
4661
if require_pid != 0 {
47-
let actual_pid = process::id();
62+
let actual_pid = getpid();
4863
if require_pid != actual_pid {
4964
unsafe {
5065
libc::raise(libc::SIGUSR1);

0 commit comments

Comments
 (0)
Please sign in to comment.