Skip to content

Commit

Permalink
Rollup merge of rust-lang#81825 - voidc:pidfd, r=joshtriplett
Browse files Browse the repository at this point in the history
Add Linux-specific pidfd process extensions (take 2)

Continuation of rust-lang#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: rust-lang#82971
  • Loading branch information
Dylan-DPC authored Mar 14, 2021
2 parents 9320b12 + f23b30c commit ca4cdc2
Show file tree
Hide file tree
Showing 6 changed files with 312 additions and 5 deletions.
1 change: 1 addition & 0 deletions library/std/src/os/linux/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
#![stable(feature = "raw_ext", since = "1.1.0")]

pub mod fs;
pub mod process;
pub mod raw;
150 changes: 150 additions & 0 deletions library/std/src/os/linux/process.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
//! Linux-specific extensions to primitives in the `std::process` module.
#![unstable(feature = "linux_pidfd", issue = "82971")]

use crate::io::Result;
use crate::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
use crate::process;
use crate::sys::fd::FileDesc;
use crate::sys_common::{AsInner, AsInnerMut, FromInner, IntoInner};

/// This type represents a file descriptor that refers to a process.
///
/// A `PidFd` can be obtained by setting the corresponding option on [`Command`]
/// with [`create_pidfd`]. Subsequently, the created pidfd can be retrieved
/// from the [`Child`] by calling [`pidfd`] or [`take_pidfd`].
///
/// Example:
/// ```no_run
/// #![feature(linux_pidfd)]
/// use std::os::linux::process::{CommandExt, ChildExt};
/// use std::process::Command;
///
/// let mut child = Command::new("echo")
/// .create_pidfd(true)
/// .spawn()
/// .expect("Failed to spawn child");
///
/// let pidfd = child
/// .take_pidfd()
/// .expect("Failed to retrieve pidfd");
///
/// // The file descriptor will be closed when `pidfd` is dropped.
/// ```
/// Refer to the man page of [`pidfd_open(2)`] for further details.
///
/// [`Command`]: process::Command
/// [`create_pidfd`]: CommandExt::create_pidfd
/// [`Child`]: process::Child
/// [`pidfd`]: fn@ChildExt::pidfd
/// [`take_pidfd`]: ChildExt::take_pidfd
/// [`pidfd_open(2)`]: https://man7.org/linux/man-pages/man2/pidfd_open.2.html
#[derive(Debug)]
pub struct PidFd {
inner: FileDesc,
}

impl AsInner<FileDesc> for PidFd {
fn as_inner(&self) -> &FileDesc {
&self.inner
}
}

impl FromInner<FileDesc> for PidFd {
fn from_inner(inner: FileDesc) -> PidFd {
PidFd { inner }
}
}

impl IntoInner<FileDesc> for PidFd {
fn into_inner(self) -> FileDesc {
self.inner
}
}

impl AsRawFd for PidFd {
fn as_raw_fd(&self) -> RawFd {
self.as_inner().raw()
}
}

impl FromRawFd for PidFd {
unsafe fn from_raw_fd(fd: RawFd) -> Self {
Self::from_inner(FileDesc::new(fd))
}
}

impl IntoRawFd for PidFd {
fn into_raw_fd(self) -> RawFd {
self.into_inner().into_raw()
}
}

mod private_child_ext {
pub trait Sealed {}
impl Sealed for crate::process::Child {}
}

/// Os-specific extensions for [`Child`]
///
/// [`Child`]: process::Child
pub trait ChildExt: private_child_ext::Sealed {
/// Obtains a reference to the [`PidFd`] created for this [`Child`], if available.
///
/// A pidfd will only be available if its creation was requested with
/// [`create_pidfd`] when the corresponding [`Command`] was created.
///
/// Even if requested, a pidfd may not be available due to an older
/// version of Linux being in use, or if some other error occurred.
///
/// [`Command`]: process::Command
/// [`create_pidfd`]: CommandExt::create_pidfd
/// [`Child`]: process::Child
fn pidfd(&self) -> Result<&PidFd>;

/// Takes ownership of the [`PidFd`] created for this [`Child`], if available.
///
/// A pidfd will only be available if its creation was requested with
/// [`create_pidfd`] when the corresponding [`Command`] was created.
///
/// Even if requested, a pidfd may not be available due to an older
/// version of Linux being in use, or if some other error occurred.
///
/// [`Command`]: process::Command
/// [`create_pidfd`]: CommandExt::create_pidfd
/// [`Child`]: process::Child
fn take_pidfd(&mut self) -> Result<PidFd>;
}

mod private_command_ext {
pub trait Sealed {}
impl Sealed for crate::process::Command {}
}

/// Os-specific extensions for [`Command`]
///
/// [`Command`]: process::Command
pub trait CommandExt: private_command_ext::Sealed {
/// Sets whether a [`PidFd`](struct@PidFd) should be created for the [`Child`]
/// spawned by this [`Command`].
/// By default, no pidfd will be created.
///
/// The pidfd can be retrieved from the child with [`pidfd`] or [`take_pidfd`].
///
/// A pidfd will only be created if it is possible to do so
/// in a guaranteed race-free manner (e.g. if the `clone3` system call
/// is supported). Otherwise, [`pidfd`] will return an error.
///
/// [`Command`]: process::Command
/// [`Child`]: process::Child
/// [`pidfd`]: fn@ChildExt::pidfd
/// [`take_pidfd`]: ChildExt::take_pidfd
fn create_pidfd(&mut self, val: bool) -> &mut process::Command;
}

impl CommandExt for process::Command {
fn create_pidfd(&mut self, val: bool) -> &mut process::Command {
self.as_inner_mut().create_pidfd(val);
self
}
}
2 changes: 1 addition & 1 deletion library/std/src/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ use crate::sys_common::{AsInner, AsInnerMut, FromInner, IntoInner};
/// [`wait`]: Child::wait
#[stable(feature = "process", since = "1.0.0")]
pub struct Child {
handle: imp::Process,
pub(crate) handle: imp::Process,

/// The handle for writing to the child's standard input (stdin), if it has
/// been captured. To avoid partially moving
Expand Down
6 changes: 6 additions & 0 deletions library/std/src/sys/unix/process/process_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ pub struct Command {
stdin: Option<Stdio>,
stdout: Option<Stdio>,
stderr: Option<Stdio>,
pub(crate) create_pidfd: bool,
}

// Create a new type for argv, so that we can make it `Send` and `Sync`
Expand Down Expand Up @@ -141,6 +142,7 @@ impl Command {
stdin: None,
stdout: None,
stderr: None,
create_pidfd: false,
}
}

Expand Down Expand Up @@ -177,6 +179,10 @@ impl Command {
self.groups = Some(Box::from(groups));
}

pub fn create_pidfd(&mut self, val: bool) {
self.create_pidfd = val;
}

pub fn saw_nul(&self) -> bool {
self.saw_nul
}
Expand Down
131 changes: 127 additions & 4 deletions library/std/src/sys/unix/process/process_unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,20 @@ use crate::fmt;
use crate::io::{self, Error, ErrorKind};
use crate::mem;
use crate::ptr;
use crate::sync::atomic::{AtomicBool, Ordering};
use crate::sys;
use crate::sys::cvt;
use crate::sys::process::process_common::*;
use crate::sys_common::FromInner;

#[cfg(target_os = "linux")]
use crate::os::linux::process::PidFd;

#[cfg(target_os = "vxworks")]
use libc::RTP_ID as pid_t;

#[cfg(not(target_os = "vxworks"))]
use libc::{c_int, gid_t, pid_t, uid_t};
use libc::{c_int, c_long, gid_t, pid_t, uid_t};

////////////////////////////////////////////////////////////////////////////////
// Command
Expand Down Expand Up @@ -48,7 +53,8 @@ impl Command {
// a lock any more because the parent won't do anything and the child is
// in its own process. Thus the parent drops the lock guard while the child
// forgets it to avoid unlocking it on a new thread, which would be invalid.
let (env_lock, result) = unsafe { (sys::os::env_lock(), cvt(libc::fork())?) };
let env_lock = unsafe { sys::os::env_lock() };
let (result, pidfd) = self.do_fork()?;

let pid = unsafe {
match result {
Expand Down Expand Up @@ -81,7 +87,7 @@ impl Command {
}
};

let mut p = Process { pid, status: None };
let mut p = Process::new(pid, pidfd);
drop(output);
let mut bytes = [0; 8];

Expand Down Expand Up @@ -114,6 +120,87 @@ impl Command {
}
}

// Attempts to fork the process. If successful, returns
// Ok((0, -1)) in the child, and Ok((child_pid, child_pidfd)) in the parent.
fn do_fork(&mut self) -> Result<(pid_t, pid_t), io::Error> {
// If we fail to create a pidfd for any reason, this will
// stay as -1, which indicates an error
let mut pidfd: pid_t = -1;

// On Linux, attempt to use the `clone3` syscall, which
// supports more arguments (in particular, the ability to create a pidfd).
// If this fails, we will fall through this block to a call to `fork()`
#[cfg(target_os = "linux")]
{
static HAS_CLONE3: AtomicBool = AtomicBool::new(true);

const CLONE_PIDFD: u64 = 0x00001000;

#[repr(C)]
struct clone_args {
flags: u64,
pidfd: u64,
child_tid: u64,
parent_tid: u64,
exit_signal: u64,
stack: u64,
stack_size: u64,
tls: u64,
set_tid: u64,
set_tid_size: u64,
cgroup: u64,
}

syscall! {
fn clone3(cl_args: *mut clone_args, len: libc::size_t) -> c_long
}

if HAS_CLONE3.load(Ordering::Relaxed) {
let mut flags = 0;
if self.create_pidfd {
flags |= CLONE_PIDFD;
}

let mut args = clone_args {
flags,
pidfd: &mut pidfd as *mut pid_t as u64,
child_tid: 0,
parent_tid: 0,
exit_signal: libc::SIGCHLD as u64,
stack: 0,
stack_size: 0,
tls: 0,
set_tid: 0,
set_tid_size: 0,
cgroup: 0,
};

let args_ptr = &mut args as *mut clone_args;
let args_size = crate::mem::size_of::<clone_args>();

let res = cvt(unsafe { clone3(args_ptr, args_size) });
match res {
Ok(n) => return Ok((n as pid_t, pidfd)),
Err(e) => match e.raw_os_error() {
// Multiple threads can race to execute this store,
// but that's fine - that just means that multiple threads
// will have tried and failed to execute the same syscall,
// with no other side effects.
Some(libc::ENOSYS) => HAS_CLONE3.store(false, Ordering::Relaxed),
// Fallback to fork if `EPERM` is returned. (e.g. blocked by seccomp)
Some(libc::EPERM) => {}
_ => return Err(e),
},
}
}
}

// If we get here, we are either not on Linux,
// or we are on Linux and the 'clone3' syscall does not exist
// or we do not have permission to call it
cvt(unsafe { libc::fork() }).map(|res| (res, pidfd))
}

pub fn exec(&mut self, default: Stdio) -> io::Error {
let envp = self.capture_env();

Expand Down Expand Up @@ -297,6 +384,7 @@ impl Command {
|| (self.env_saw_path() && !self.program_is_path())
|| !self.get_closures().is_empty()
|| self.get_groups().is_some()
|| self.create_pidfd
{
return Ok(None);
}
Expand Down Expand Up @@ -341,7 +429,7 @@ impl Command {
None => None,
};

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

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

Expand Down Expand Up @@ -430,9 +518,26 @@ impl Command {
pub struct Process {
pid: pid_t,
status: Option<ExitStatus>,
// On Linux, stores the pidfd created for this child.
// This is None if the user did not request pidfd creation,
// or if the pidfd could not be created for some reason
// (e.g. the `clone3` syscall was not available).
#[cfg(target_os = "linux")]
pidfd: Option<PidFd>,
}

impl Process {
#[cfg(target_os = "linux")]
fn new(pid: pid_t, pidfd: pid_t) -> Self {
let pidfd = (pidfd >= 0).then(|| PidFd::from_inner(sys::fd::FileDesc::new(pidfd)));
Process { pid, status: None, pidfd }
}

#[cfg(not(target_os = "linux"))]
fn new(pid: pid_t, _pidfd: pid_t) -> Self {
Process { pid, status: None }
}

pub fn id(&self) -> u32 {
self.pid as u32
}
Expand Down Expand Up @@ -546,6 +651,24 @@ impl fmt::Display for ExitStatus {
}
}

#[cfg(target_os = "linux")]
#[unstable(feature = "linux_pidfd", issue = "82971")]
impl crate::os::linux::process::ChildExt for crate::process::Child {
fn pidfd(&self) -> io::Result<&PidFd> {
self.handle
.pidfd
.as_ref()
.ok_or_else(|| Error::new(ErrorKind::Other, "No pidfd was created."))
}

fn take_pidfd(&mut self) -> io::Result<PidFd> {
self.handle
.pidfd
.take()
.ok_or_else(|| Error::new(ErrorKind::Other, "No pidfd was created."))
}
}

#[cfg(test)]
#[path = "process_unix/tests.rs"]
mod tests;
Loading

0 comments on commit ca4cdc2

Please sign in to comment.