-
Notifications
You must be signed in to change notification settings - Fork 13.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Linux-specific pidfd process extensions
Background: Over the last year, pidfd support was added to the Linux kernel. This allows interacting with other processes. In particular, this allows waiting on a child process with a timeout in a race-free way, bypassing all of the awful signal-handler tricks that are usually required. Pidfds can be obtained for a child process (as well as any other process) via the `pidfd_open` syscall. Unfortunately, this requires several conditions to hold in order to be race-free (i.e. the pid is not reused). Per `man pidfd_open`: ``` · the disposition of SIGCHLD has not been explicitly set to SIG_IGN (see sigaction(2)); · the SA_NOCLDWAIT flag was not specified while establishing a han‐ dler for SIGCHLD or while setting the disposition of that signal to SIG_DFL (see sigaction(2)); and · the zombie process was not reaped elsewhere in the program (e.g., either by an asynchronously executed signal handler or by wait(2) or similar in another thread). If any of these conditions does not hold, then the child process (along with a PID file descriptor that refers to it) should instead be created using clone(2) with the CLONE_PIDFD flag. ``` Sadly, these conditions are impossible to guarantee once any libraries are used. For example, C code runnng in a different thread could call `wait()`, which is impossible to detect from Rust code trying to open a pidfd. While pid reuse issues should (hopefully) be rare in practice, we can do better. By passing the `CLONE_PIDFD` flag to `clone()` or `clone3()`, we can obtain a pidfd for the child process in a guaranteed race-free manner. This PR: This PR adds Linux-specific process extension methods to allow obtaining pidfds for processes spawned via the standard `Command` API. Other than being made available to user code, the standard library does not make use of these pidfds in any way. In particular, the implementation of `Child::wait` is completely unchanged. Two Linux-specific helper methods are added: `CommandExt::create_pidfd` and `ChildExt::pidfd`. These methods are intended to serve as a building block for libraries to build higher-level abstractions - in particular, waiting on a process with a timeout. I've included a basic test, which verifies that pidfds are created iff the `create_pidfd` method is used. This test is somewhat special - it should always succeed on systems with the `clone3` system call available, and always fail on systems without `clone3` available. I'm not sure how to best ensure this programatically. This PR relies on the newer `clone3` system call to pass the `CLONE_FD`, rather than the older `clone` system call. `clone3` was added to Linux in the same release as pidfds, so this shouldn't unnecessarily limit the kernel versions that this code supports. Unresolved questions: * What should the name of the feature gate be for these newly added methods? * Should the `pidfd` method distinguish between an error occurring and `create_pidfd` not being called?
- Loading branch information
Showing
6 changed files
with
192 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,4 +3,5 @@ | |
#![stable(feature = "raw_ext", since = "1.1.0")] | ||
|
||
pub mod fs; | ||
pub mod process; | ||
pub mod raw; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
//! Linux-specific extensions to primitives in the `std::process` module. | ||
#![unstable(feature = "linux_pidfd", issue = "none")] | ||
|
||
use crate::process; | ||
use crate::sys_common::AsInnerMut; | ||
use crate::io::Result; | ||
|
||
/// Os-specific extensions to [`process::Child`] | ||
/// | ||
/// [`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>; | ||
} | ||
|
||
/// Os-specific extensions to [`process::Command`] | ||
/// | ||
/// [`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. | ||
/// | ||
/// The pidfd can be retrieved from the child via [`ChildExt::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. | ||
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 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
// run-pass | ||
// linux-only - 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. | ||
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"); | ||
} |