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
This commit is contained in:
bors 2021-08-01 16:45:47 +00:00
commit 4e21ef2a4e
8 changed files with 410 additions and 11 deletions

View file

@ -0,0 +1,45 @@
// run-pass
// only-linux - pidfds are a linux-specific concept
#![feature(linux_pidfd)]
#![feature(rustc_private)]
extern crate libc;
use std::io::Error;
use std::os::linux::process::{ChildExt, CommandExt};
use std::process::Command;
fn has_clone3() -> bool {
let res = unsafe { libc::syscall(libc::SYS_clone3, 0, 0) };
let err = (res == -1)
.then(|| Error::last_os_error())
.expect("probe syscall should not succeed");
err.raw_os_error() != Some(libc::ENOSYS)
}
fn main() {
// pidfds require the clone3 syscall
if !has_clone3() {
return;
}
// 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()
.unwrap()
.pidfd().expect("failed to obtain pidfd");
let _ = Command::new("echo")
.create_pidfd(false)
.spawn()
.unwrap()
.pidfd().expect_err("pidfd should not have been created when create_pid(false) is set");
let _ = Command::new("echo")
.spawn()
.unwrap()
.pidfd().expect_err("pidfd should not have been created");
}

View file

@ -8,8 +8,6 @@
// ignore-sgx no processes
#![feature(process_exec, rustc_private)]
extern crate libc;
use std::env;
use std::io::Error;
use std::os::unix::process::CommandExt;
@ -17,6 +15,23 @@ use std::process::Command;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
#[cfg(not(target_os = "linux"))]
fn getpid() -> u32 {
use std::process;
process::id()
}
/// We need to directly use the getpid syscall instead of using `process::id()`
/// because the libc wrapper might return incorrect values after a process was
/// forked.
#[cfg(target_os = "linux")]
fn getpid() -> u32 {
extern crate libc;
unsafe {
libc::syscall(libc::SYS_getpid) as _
}
}
fn main() {
if let Some(arg) = env::args().nth(1) {
match &arg[..] {
@ -68,14 +83,12 @@ fn main() {
};
assert_eq!(output.raw_os_error(), Some(102));
let pid = unsafe { libc::getpid() };
assert!(pid >= 0);
let pid = getpid();
let output = unsafe {
Command::new(&me)
.arg("empty")
.pre_exec(move || {
let child = libc::getpid();
assert!(child >= 0);
let child = getpid();
assert!(pid != child);
Ok(())
})

View file

@ -23,6 +23,21 @@ use std::sync::atomic::{AtomicU32, Ordering};
use libc::c_int;
#[cfg(not(target_os = "linux"))]
fn getpid() -> u32 {
process::id()
}
/// We need to directly use the getpid syscall instead of using `process::id()`
/// because the libc wrapper might return incorrect values after a process was
/// forked.
#[cfg(target_os = "linux")]
fn getpid() -> u32 {
unsafe {
libc::syscall(libc::SYS_getpid) as _
}
}
/// This stunt allocator allows us to spot heap allocations in the child.
struct PidChecking<A> {
parent: A,
@ -44,7 +59,7 @@ impl<A> PidChecking<A> {
fn check(&self) {
let require_pid = self.require_pid.load(Ordering::Acquire);
if require_pid != 0 {
let actual_pid = process::id();
let actual_pid = getpid();
if require_pid != actual_pid {
unsafe {
libc::raise(libc::SIGUSR1);