Add PidFd type and seal traits

Improve docs

Split do_fork into two

Make do_fork unsafe

Add target attribute to create_pidfd field in Command

Add method to get create_pidfd value
This commit is contained in:
Dominik Stolz 2021-02-06 14:15:49 +01:00
parent ef03de2e6a
commit 619fd96868
4 changed files with 289 additions and 120 deletions

View File

@ -2,40 +2,142 @@
#![unstable(feature = "linux_pidfd", issue = "none")]
use crate::process;
use crate::sys_common::AsInnerMut;
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};
/// Os-specific extensions to [`process::Child`]
/// This type represents a file descriptor that refers to a process.
///
/// [`process::Child`]: crate::process::Child
pub trait ChildExt {
/// Obtains the pidfd created for this child process, if available.
///
/// A pidfd will only ever be available if `create_pidfd(true)` was called
/// when the corresponding `Command` was created.
///
/// Even if `create_pidfd(true)` is called, a pidfd may not be available
/// due to an older version of Linux being in use, or if
/// some other error occured.
///
/// See `man pidfd_open` for more details about pidfds.
fn pidfd(&self) -> Result<i32>;
/// 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
#[derive(Debug)]
pub struct PidFd {
inner: FileDesc,
}
/// Os-specific extensions to [`process::Command`]
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`]
///
/// [`process::Command`]: crate::process::Command
pub trait CommandExt {
/// Sets whether or this `Command` will attempt to create a pidfd
/// for the child. If this method is never called, a pidfd will
/// not be crated.
/// [`Child`]: process::Child
pub trait ChildExt: private_child_ext::Sealed {
/// Obtains a reference to the [`PidFd`] created for this [`Child`], if available.
///
/// The pidfd can be retrieved from the child via [`ChildExt::pidfd`]
/// 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, [`ChildExit::pidfd`] will return an error.
/// 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;
}

View File

@ -79,7 +79,8 @@ pub struct Command {
stdin: Option<Stdio>,
stdout: Option<Stdio>,
stderr: Option<Stdio>,
pub(crate) make_pidfd: bool,
#[cfg(target_os = "linux")]
create_pidfd: bool,
}
// Create a new type for argv, so that we can make it `Send` and `Sync`
@ -125,6 +126,7 @@ pub enum Stdio {
}
impl Command {
#[cfg(not(target_os = "linux"))]
pub fn new(program: &OsStr) -> Command {
let mut saw_nul = false;
let program = os2c(program, &mut saw_nul);
@ -142,7 +144,28 @@ impl Command {
stdin: None,
stdout: None,
stderr: None,
make_pidfd: false,
}
}
#[cfg(target_os = "linux")]
pub fn new(program: &OsStr) -> Command {
let mut saw_nul = false;
let program = os2c(program, &mut saw_nul);
Command {
argv: Argv(vec![program.as_ptr(), ptr::null()]),
args: vec![program.clone()],
program,
env: Default::default(),
cwd: None,
uid: None,
gid: None,
saw_nul,
closures: Vec::new(),
groups: None,
stdin: None,
stdout: None,
stderr: None,
create_pidfd: false,
}
}
@ -178,9 +201,21 @@ impl Command {
pub fn groups(&mut self, groups: &[gid_t]) {
self.groups = Some(Box::from(groups));
}
#[cfg(target_os = "linux")]
pub fn create_pidfd(&mut self, val: bool) {
self.make_pidfd = val;
self.create_pidfd = val;
}
#[cfg(not(target_os = "linux"))]
#[allow(dead_code)]
pub fn get_create_pidfd(&self) -> bool {
false
}
#[cfg(target_os = "linux")]
pub fn get_create_pidfd(&self) -> bool {
self.create_pidfd
}
pub fn saw_nul(&self) -> bool {

View File

@ -8,7 +8,9 @@ use crate::ptr;
use crate::sys;
use crate::sys::cvt;
use crate::sys::process::process_common::*;
use crate::sync::atomic::{AtomicBool, Ordering};
#[cfg(target_os = "linux")]
use crate::os::linux::process::PidFd;
#[cfg(target_os = "linux")]
use crate::sys::weak::syscall;
@ -66,7 +68,7 @@ impl Command {
// 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 = sys::os::env_read_lock();
let (pid, pidfd) = self.do_fork()?;
let (pid, pidfd) = unsafe { self.do_fork()? };
if pid == 0 {
crate::panic::always_abort();
@ -95,7 +97,7 @@ impl Command {
drop(env_lock);
drop(output);
let mut p = Process { pid, status: None, pidfd };
let mut p = Process::new(pid, pidfd);
let mut bytes = [0; 8];
// loop to handle EINTR
@ -127,84 +129,91 @@ 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<(libc::c_long, libc::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: libc::pid_t = -1;
// On Linux, attempt to use the `clone3` syscall, which
// supports more argument (in particular, the ability to create a pidfd).
// If this fails, we will fall through this block to a call to `fork()`
cfg_if::cfg_if! {
if #[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) -> libc::c_long
}
if HAS_CLONE3.load(Ordering::Relaxed) {
let mut flags = 0;
if self.make_pidfd {
flags |= CLONE_PIDFD;
}
let mut args = clone_args {
flags,
pidfd: &mut pidfd as *mut libc::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, 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),
_ => 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
cvt(unsafe { libc::fork() }.into()).map(|res| (res, pidfd))
// Attempts to fork the process. If successful, returns Ok((0, -1))
// in the child, and Ok((child_pid, -1)) in the parent.
#[cfg(not(target_os = "linux"))]
unsafe fn do_fork(&mut self) -> Result<(pid_t, pid_t), io::Error> {
cvt(libc::fork()).map(|res| (res, -1))
}
// Attempts to fork the process. If successful, returns Ok((0, -1))
// in the child, and Ok((child_pid, child_pidfd)) in the parent.
#[cfg(target_os = "linux")]
unsafe fn do_fork(&mut self) -> Result<(pid_t, pid_t), io::Error> {
use crate::sync::atomic::{AtomicBool, Ordering};
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) -> libc::c_long
}
// 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;
// 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()`
if HAS_CLONE3.load(Ordering::Relaxed) {
let mut flags = 0;
if self.get_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(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, the 'clone3' syscall does not exist
// or we do not have permission to call it
cvt(libc::fork()).map(|res| (res, pidfd))
}
pub fn exec(&mut self, default: Stdio) -> io::Error {
let envp = self.capture_env();
@ -360,6 +369,8 @@ impl Command {
#[cfg(not(any(
target_os = "macos",
target_os = "freebsd",
all(target_os = "linux", target_env = "gnu"),
all(target_os = "linux", target_env = "musl"),
)))]
fn posix_spawn(
&mut self,
@ -374,6 +385,8 @@ impl Command {
#[cfg(any(
target_os = "macos",
target_os = "freebsd",
all(target_os = "linux", target_env = "gnu"),
all(target_os = "linux", target_env = "musl"),
))]
fn posix_spawn(
&mut self,
@ -388,6 +401,7 @@ impl Command {
|| (self.env_saw_path() && !self.program_is_path())
|| !self.get_closures().is_empty()
|| self.get_groups().is_some()
|| self.get_create_pidfd()
{
return Ok(None);
}
@ -432,7 +446,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>);
@ -522,14 +536,26 @@ pub struct Process {
pid: pid_t,
status: Option<ExitStatus>,
// On Linux, stores the pidfd created for this child.
// This is -1 if the user did not request pidfd creation,
// 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: libc::c_int,
pidfd: Option<PidFd>,
}
impl Process {
#[cfg(target_os = "linux")]
fn new(pid: pid_t, pidfd: pid_t) -> Self {
use crate::sys_common::FromInner;
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
}
@ -669,12 +695,18 @@ impl ExitStatusError {
#[cfg(target_os = "linux")]
#[unstable(feature = "linux_pidfd", issue = "none")]
impl crate::os::linux::process::ChildExt for crate::process::Child {
fn pidfd(&self) -> crate::io::Result<i32> {
if self.handle.pidfd > 0 {
Ok(self.handle.pidfd)
} else {
Err(crate::io::Error::from(crate::io::ErrorKind::Other))
}
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."))
}
}

View File

@ -1,13 +1,13 @@
// run-pass
// linux-only - pidfds are a linux-specific concept
// only-linux - pidfds are a linux-specific concept
#![feature(linux_pidfd)]
use std::os::linux::process::{CommandExt, ChildExt};
use std::process::Command;
fn main() {
// We don't assert the precise value, since the standard libarary
// may be opened other file descriptors before our code ran.
// We don't assert the precise value, since the standard library
// might have opened other file descriptors before our code runs.
let _ = Command::new("echo")
.create_pidfd(true)
.spawn()