Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions library/std/src/os/freebsd/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@

pub mod fs;
pub mod net;
pub mod process;
pub mod raw;
202 changes: 202 additions & 0 deletions library/std/src/os/freebsd/process.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
//! FreeBSD-specific extensions to primitives in the [`std::process`] module.
//!
//! [`std::process`]: crate::process
// FIXME this file is identical to src/os/linux/process, except for documentation. Can we combine
// the two?

#![unstable(feature = "linux_pidfd", issue = "82971")]

use crate::io::Result;
use crate::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd};
use crate::process::{self, ExitStatus};
use crate::sealed::Sealed;
use crate::sys::{AsInner, AsInnerMut, FromInner, IntoInner};
#[cfg(not(doc))]
use crate::sys::{fd::FileDesc, freebsd::pidfd::PidFd as InnerPidFd};

#[cfg(doc)]
struct InnerPidFd;

/// 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 [`into_pidfd`].
///
/// Example:
/// ```no_run
/// #![feature(linux_pidfd)]
/// use std::os::freebsd::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
/// .into_pidfd()
/// .expect("Failed to retrieve pidfd");
///
/// // The file descriptor will be closed when `pidfd` is dropped.
/// ```
/// Refer to the man page of [`pdfork(2)`] for further details.
///
/// [`Command`]: process::Command
/// [`create_pidfd`]: CommandExt::create_pidfd
/// [`Child`]: process::Child
/// [`pidfd`]: fn@ChildExt::pidfd
/// [`into_pidfd`]: ChildExt::into_pidfd
/// [`pdfork(2)`]: https://man.freebsd.org/cgi/man.cgi?query=pdfork
#[derive(Debug)]
#[repr(transparent)]
pub struct PidFd {
inner: InnerPidFd,
}

impl PidFd {
/// Forces the child process to exit.
///
/// Unlike [`Child::kill`] it is possible to attempt to kill
/// reaped children since PidFd does not suffer from pid recycling
/// races. But doing so will return an Error.
///
/// [`Child::kill`]: process::Child::kill
pub fn kill(&self) -> Result<()> {
self.inner.kill()
}

/// Waits for the child to exit completely, returning the status that it exited with.
pub fn wait(&self) -> Result<ExitStatus> {
self.inner.wait().map(FromInner::from_inner)
}

/// Attempts to collect the exit status of the child if it has already exited.
pub fn try_wait(&self) -> Result<Option<ExitStatus>> {
Ok(self.inner.try_wait()?.map(FromInner::from_inner))
}
}

impl AsInner<InnerPidFd> for PidFd {
#[inline]
fn as_inner(&self) -> &InnerPidFd {
&self.inner
}
}

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

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

impl AsRawFd for PidFd {
#[inline]
fn as_raw_fd(&self) -> RawFd {
self.as_inner().as_inner().as_raw_fd()
}
}

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

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

impl AsFd for PidFd {
fn as_fd(&self) -> BorrowedFd<'_> {
self.as_inner().as_inner().as_fd()
}
}

impl From<OwnedFd> for PidFd {
fn from(fd: OwnedFd) -> Self {
Self::from_inner(InnerPidFd::from_inner(FileDesc::from_inner(fd)))
}
}

impl From<PidFd> for OwnedFd {
fn from(pid_fd: PidFd) -> Self {
pid_fd.into_inner().into_inner().into_inner()
}
}

/// Os-specific extensions for [`Child`]
///
/// [`Child`]: process::Child
pub trait ChildExt: 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 if some other error occurred.
///
/// [`Command`]: process::Command
/// [`create_pidfd`]: CommandExt::create_pidfd
/// [`Child`]: process::Child
fn pidfd(&self) -> Result<&PidFd>;

/// Returns the [`PidFd`] created for this [`Child`], if available.
/// Otherwise self is returned.
///
/// A pidfd will only be available if its creation was requested with
/// [`create_pidfd`] when the corresponding [`Command`] was created.
///
/// Taking ownership of the PidFd consumes the Child to avoid pid reuse
/// races. Use [`pidfd`] and [`BorrowedFd::try_clone_to_owned`] if
/// you don't want to disassemble the Child yet.
///
/// [`Command`]: process::Command
/// [`create_pidfd`]: CommandExt::create_pidfd
/// [`pidfd`]: ChildExt::pidfd
/// [`Child`]: process::Child
fn into_pidfd(self) -> crate::result::Result<PidFd, Self>
where
Self: Sized;
}

/// Os-specific extensions for [`Command`]
///
/// [`Command`]: process::Command
pub trait CommandExt: 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 [`into_pidfd`].
///
/// A pidfd will only be created if it is possible to do so
/// in a guaranteed race-free manner. Otherwise, [`pidfd`] will return an error.
///
/// If a pidfd has been successfully created and not been taken from the `Child`
/// then calls to `kill()`, `wait()` and `try_wait()` will use the pidfd
/// instead of the pid. This can prevent pid recycling races, e.g.
/// those caused by rogue libraries in the same process prematurely reaping
/// zombie children via `waitpid(-1, ...)` calls.
///
/// [`Command`]: process::Command
/// [`Child`]: process::Child
/// [`pidfd`]: fn@ChildExt::pidfd
/// [`into_pidfd`]: ChildExt::into_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
}
}
7 changes: 5 additions & 2 deletions library/std/src/process/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -478,8 +478,11 @@ fn env_empty() {
#[cfg(not(windows))]
#[cfg_attr(any(target_os = "emscripten", target_env = "sgx"), ignore)]
fn debug_print() {
const PIDFD: &'static str =
if cfg!(target_os = "linux") { " create_pidfd: false,\n" } else { "" };
const PIDFD: &'static str = if cfg!(any(target_os = "freebsd", target_os = "linux")) {
" create_pidfd: false,\n"
} else {
""
};

let mut command = Command::new("some-boring-name");

Expand Down
1 change: 1 addition & 0 deletions library/std/src/sys/pal/unix/freebsd/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod pidfd;
111 changes: 111 additions & 0 deletions library/std/src/sys/pal/unix/freebsd/pidfd.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
//! [`procdesc(4)`] support for FreeBSD
//!
//! `procdesc` is a file-descriptor-oriented interface to process signalling and control. It's
//! been available since FreeBSD 9.0, which is older than the oldest version of FreeBSD ever
//! supported by Rust, so there is no need for backwards-compatibility shims.
//!
//! Compared to Linux's process descriptors, there are a few differences:
//!
//! Feature | FreeBSD | Linux
//! ---------------+-------------------------------+---------------------------------------------
//! Creation | pdfork() | Any process-creation syscall plus pidfd_open
//! Monitoring | kevent(), poll(), select() | epoll(), poll(), select()
//! Convert to pid | pdgetpid() | ioctl(PIDFD_GET_INFO)
//! Signalling | pdkill() | pidfd_send_signal
//! Waiting | Any wait() variant, with pid | waitid(P_PIDFD)
//! Reaping | Any wait() variant, or close()| Any wait() variant
//! ---------------+-------------------------------+---------------------------------------------
//!
//! [`procdesc(4)`]: https://man.freebsd.org/cgi/man.cgi?query=procdesc
use crate::io;
use crate::os::fd::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
use crate::sys::fd::FileDesc;
use crate::sys::process::ExitStatus;
use crate::sys::{AsInner, FromInner, IntoInner, cvt};

#[cfg(test)]
mod tests;

#[derive(Debug)]
pub(crate) struct PidFd(FileDesc);

impl PidFd {
pub fn kill(&self) -> io::Result<()> {
self.send_signal(libc::SIGKILL)
}

pub(crate) fn pid(&self) -> io::Result<u32> {
let mut pid = 0;
cvt(unsafe { libc::pdgetpid(self.0.as_raw_fd(), &mut pid) })?;
Ok(pid as u32)
}

fn waitid(&self, options: libc::c_int) -> io::Result<Option<ExitStatus>> {
// FreeBSD's wait(2) family of functions doesn't yet work with process descriptors
// directly. Using waitpid and pdgetpid in combination is technically racy, because the
// pid might get recycled after waitpid and before dropping the PidFd. That will be fixed
// in FreeBSD 16.0.
//
// A race-free method for older releases of FreeBSD would be to use kevent with
// EVFILT_PROCDESC to get the process's exit status, and then close the process descriptor
// to reap it. But closing the process descriptor would require a &mut self reference,
// which this API does not allow.
//
// The process descriptor will eventually be closed by OwnedFd::drop .
let mut siginfo: libc::siginfo_t = unsafe { crate::mem::zeroed() };
cvt(unsafe {
libc::waitid(libc::P_PID, self.pid()? as libc::id_t, &mut siginfo, options)
})?;
if unsafe { siginfo.si_pid() } == 0 {
Ok(None)
} else {
Ok(Some(ExitStatus::from_waitid_siginfo(siginfo)))
}
}

pub(crate) fn send_signal(&self, signal: i32) -> io::Result<()> {
cvt(unsafe { libc::pdkill(self.0.as_raw_fd(), signal) }).map(drop)
}

pub fn wait(&self) -> io::Result<ExitStatus> {
let r = self.waitid(libc::WEXITED)?;
match r {
Some(exit_status) => Ok(exit_status),
None => unreachable!("waitid with WEXITED should not return None"),
}
}

pub fn try_wait(&self) -> io::Result<Option<ExitStatus>> {
self.waitid(libc::WEXITED | libc::WNOHANG)
}
}

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

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

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

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

impl IntoRawFd for PidFd {
fn into_raw_fd(self) -> RawFd {
self.0.into_raw_fd()
}
}
1 change: 1 addition & 0 deletions library/std/src/sys/pal/unix/freebsd/pidfd/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include!("../../linux/pidfd/tests.rs");
12 changes: 10 additions & 2 deletions library/std/src/sys/pal/unix/linux/pidfd/tests.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use super::PidFd as InternalPidFd;
use crate::assert_matches::assert_matches;
use crate::os::fd::AsRawFd;
#[cfg(target_os = "freebsd")]
use crate::os::freebsd::process::{ChildExt, CommandExt as _};
#[cfg(target_os = "linux")]
use crate::os::linux::process::{ChildExt, CommandExt as _};
use crate::os::unix::process::{CommandExt as _, ExitStatusExt};
use crate::process::Command;
Expand Down Expand Up @@ -107,6 +109,12 @@ fn test_pidfd() {
assert_matches!(res, Err(e) if e.raw_os_error() == Some(libc::ESRCH));
}

#[cfg(target_os = "freebsd")]
fn probe_pidfd_support() -> bool {
InternalPidFd::current_process().is_ok()
true
}

#[cfg(target_os = "linux")]
fn probe_pidfd_support() -> bool {
super::PidFd::current_process().is_ok()
}
2 changes: 2 additions & 0 deletions library/std/src/sys/pal/unix/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

use crate::io;

#[cfg(target_os = "freebsd")]
pub mod freebsd;
#[cfg(target_os = "fuchsia")]
pub mod fuchsia;
pub mod futex;
Expand Down
Loading
Loading