From 6925bea22b2214cd3ea01f6f5fa1a1111758f974 Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Wed, 7 Jan 2026 11:29:33 -0700 Subject: [PATCH 1/2] Implement the "linux_pidfd" feature for FreeBSD FreeBSD's procdesc(4)[^1] is a file-descriptor-oriented interface to process signalling and control. It's a race-free replacement for fork() and kill(), and it's compatible with capsicum[^2]. Its drawbacks are that there isn't yet a way to use it with posix_spawn, and there's no capsicum-compatible way to use it with wait(). But it's still sufficient for the linux_pidfd feature. [^1]: https://man.freebsd.org/cgi/man.cgi?query=procdesc [^2]: https://man.freebsd.org/cgi/man.cgi?query=capsicum --- library/std/src/os/freebsd/mod.rs | 1 + library/std/src/os/freebsd/process.rs | 202 ++++++++++++++++++ library/std/src/process/tests.rs | 7 +- library/std/src/sys/pal/unix/freebsd/mod.rs | 1 + library/std/src/sys/pal/unix/freebsd/pidfd.rs | 111 ++++++++++ .../src/sys/pal/unix/freebsd/pidfd/tests.rs | 1 + .../std/src/sys/pal/unix/linux/pidfd/tests.rs | 12 +- library/std/src/sys/pal/unix/mod.rs | 2 + library/std/src/sys/process/unix/common.rs | 12 +- library/std/src/sys/process/unix/unix.rs | 69 ++++-- 10 files changed, 389 insertions(+), 29 deletions(-) create mode 100644 library/std/src/os/freebsd/process.rs create mode 100644 library/std/src/sys/pal/unix/freebsd/mod.rs create mode 100644 library/std/src/sys/pal/unix/freebsd/pidfd.rs create mode 100644 library/std/src/sys/pal/unix/freebsd/pidfd/tests.rs diff --git a/library/std/src/os/freebsd/mod.rs b/library/std/src/os/freebsd/mod.rs index 39912e6970d45..e37c9efb47291 100644 --- a/library/std/src/os/freebsd/mod.rs +++ b/library/std/src/os/freebsd/mod.rs @@ -4,4 +4,5 @@ pub mod fs; pub mod net; +pub mod process; pub mod raw; diff --git a/library/std/src/os/freebsd/process.rs b/library/std/src/os/freebsd/process.rs new file mode 100644 index 0000000000000..e0f77b5cc391b --- /dev/null +++ b/library/std/src/os/freebsd/process.rs @@ -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 { + 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> { + Ok(self.inner.try_wait()?.map(FromInner::from_inner)) + } +} + +impl AsInner for PidFd { + #[inline] + fn as_inner(&self) -> &InnerPidFd { + &self.inner + } +} + +impl FromInner for PidFd { + fn from_inner(inner: InnerPidFd) -> PidFd { + PidFd { inner } + } +} + +impl IntoInner 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 for PidFd { + fn from(fd: OwnedFd) -> Self { + Self::from_inner(InnerPidFd::from_inner(FileDesc::from_inner(fd))) + } +} + +impl From 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 + 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 + } +} diff --git a/library/std/src/process/tests.rs b/library/std/src/process/tests.rs index 12c5130defe5a..b6734f072a382 100644 --- a/library/std/src/process/tests.rs +++ b/library/std/src/process/tests.rs @@ -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"); diff --git a/library/std/src/sys/pal/unix/freebsd/mod.rs b/library/std/src/sys/pal/unix/freebsd/mod.rs new file mode 100644 index 0000000000000..88aa1e3deccf8 --- /dev/null +++ b/library/std/src/sys/pal/unix/freebsd/mod.rs @@ -0,0 +1 @@ +pub mod pidfd; diff --git a/library/std/src/sys/pal/unix/freebsd/pidfd.rs b/library/std/src/sys/pal/unix/freebsd/pidfd.rs new file mode 100644 index 0000000000000..837a4e57ed4a4 --- /dev/null +++ b/library/std/src/sys/pal/unix/freebsd/pidfd.rs @@ -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 { + 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> { + // 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 { + 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> { + self.waitid(libc::WEXITED | libc::WNOHANG) + } +} + +impl AsInner for PidFd { + fn as_inner(&self) -> &FileDesc { + &self.0 + } +} + +impl IntoInner for PidFd { + fn into_inner(self) -> FileDesc { + self.0 + } +} + +impl FromInner 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() + } +} diff --git a/library/std/src/sys/pal/unix/freebsd/pidfd/tests.rs b/library/std/src/sys/pal/unix/freebsd/pidfd/tests.rs new file mode 100644 index 0000000000000..d837af6997854 --- /dev/null +++ b/library/std/src/sys/pal/unix/freebsd/pidfd/tests.rs @@ -0,0 +1 @@ +include!("../../linux/pidfd/tests.rs"); diff --git a/library/std/src/sys/pal/unix/linux/pidfd/tests.rs b/library/std/src/sys/pal/unix/linux/pidfd/tests.rs index 0330f28e647d3..e5dbf6ee8bfbf 100644 --- a/library/std/src/sys/pal/unix/linux/pidfd/tests.rs +++ b/library/std/src/sys/pal/unix/linux/pidfd/tests.rs @@ -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; @@ -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() } diff --git a/library/std/src/sys/pal/unix/mod.rs b/library/std/src/sys/pal/unix/mod.rs index 6127bb98f80ef..69ae369556e2b 100644 --- a/library/std/src/sys/pal/unix/mod.rs +++ b/library/std/src/sys/pal/unix/mod.rs @@ -2,6 +2,8 @@ use crate::io; +#[cfg(target_os = "freebsd")] +pub mod freebsd; #[cfg(target_os = "fuchsia")] pub mod fuchsia; pub mod futex; diff --git a/library/std/src/sys/process/unix/common.rs b/library/std/src/sys/process/unix/common.rs index f6bbfed61ef31..8c5f6df570a0f 100644 --- a/library/std/src/sys/process/unix/common.rs +++ b/library/std/src/sys/process/unix/common.rs @@ -99,7 +99,7 @@ pub struct Command { stdin: Option, stdout: Option, stderr: Option, - #[cfg(target_os = "linux")] + #[cfg(any(target_os = "freebsd", target_os = "linux"))] create_pidfd: bool, pgroup: Option, setsid: bool, @@ -179,7 +179,7 @@ impl Command { stdin: None, stdout: None, stderr: None, - #[cfg(target_os = "linux")] + #[cfg(any(target_os = "freebsd", target_os = "linux"))] create_pidfd: false, pgroup: None, setsid: false, @@ -222,18 +222,18 @@ impl Command { self.setsid = setsid; } - #[cfg(target_os = "linux")] + #[cfg(any(target_os = "freebsd", target_os = "linux"))] pub fn create_pidfd(&mut self, val: bool) { self.create_pidfd = val; } - #[cfg(not(target_os = "linux"))] + #[cfg(not(any(target_os = "freebsd", target_os = "linux")))] #[allow(dead_code)] pub fn get_create_pidfd(&self) -> bool { false } - #[cfg(target_os = "linux")] + #[cfg(any(target_os = "freebsd", target_os = "linux"))] pub fn get_create_pidfd(&self) -> bool { self.create_pidfd } @@ -523,7 +523,7 @@ impl fmt::Debug for Command { debug_command.field("pgroup", &self.pgroup); } - #[cfg(target_os = "linux")] + #[cfg(any(target_os = "freebsd", target_os = "linux"))] { debug_command.field("create_pidfd", &self.create_pidfd); } diff --git a/library/std/src/sys/process/unix/unix.rs b/library/std/src/sys/process/unix/unix.rs index 460d5ec0f0747..6088de006630f 100644 --- a/library/std/src/sys/process/unix/unix.rs +++ b/library/std/src/sys/process/unix/unix.rs @@ -15,6 +15,8 @@ use crate::io::{self, Error, ErrorKind}; use crate::num::NonZero; use crate::process::StdioPipes; use crate::sys::cvt; +#[cfg(target_os = "freebsd")] +use crate::sys::pal::freebsd::pidfd::PidFd; #[cfg(target_os = "linux")] use crate::sys::pal::linux::pidfd::PidFd; use crate::{fmt, mem, sys}; @@ -91,7 +93,7 @@ impl Command { // The child calls `mem::forget` to leak the lock, which is crucial because // releasing a lock is not async-signal-safe. let env_lock = sys::env::env_read_lock(); - let pid = unsafe { self.do_fork()? }; + let (pid, pidfd) = unsafe { self.do_fork() }?; if pid == 0 { crate::panic::always_abort(); @@ -127,9 +129,6 @@ impl Command { #[cfg(target_os = "linux")] let pidfd = if self.get_create_pidfd() { self.recv_pidfd(&input) } else { -1 }; - #[cfg(not(target_os = "linux"))] - let pidfd = -1; - // Safety: We obtained the pidfd (on Linux) using SOCK_SEQPACKET, so it's valid. let mut p = unsafe { Process::new(pid, pidfd) }; let mut bytes = [0; 8]; @@ -176,16 +175,36 @@ impl Command { "`fork`+`exec`-based process spawning is not supported on this target", ); + /// Forks the process, optionally creating a process descriptor. + /// + /// Returns (child pid, child process descriptor) in the parent. + /// Returns (0, -1) in the child. + #[cfg(target_os = "freebsd")] + unsafe fn do_fork(&mut self) -> Result<(pid_t, pid_t), io::Error> { + if self.get_create_pidfd() { + let mut pidfd = -1; + let pid = cvt(libc::pdfork(&mut pidfd, libc::PD_CLOEXEC | libc::PD_DAEMON))?; + Ok((pid, pidfd)) + } else { + Ok((cvt(libc::fork())?, -1)) + } + } + #[cfg(any(target_os = "tvos", target_os = "watchos"))] - unsafe fn do_fork(&mut self) -> Result { + unsafe fn do_fork(&mut self) -> Result<(pid_t, pid_t), io::Error> { return Err(Self::ERR_APPLE_TV_WATCH_NO_FORK_EXEC); } // Attempts to fork the process. If successful, returns Ok((0, -1)) // in the child, and Ok((child_pid, -1)) in the parent. - #[cfg(not(any(target_os = "watchos", target_os = "tvos", target_os = "nto")))] - unsafe fn do_fork(&mut self) -> Result { - cvt(libc::fork()) + #[cfg(not(any( + target_os = "freebsd", + target_os = "watchos", + target_os = "tvos", + target_os = "nto" + )))] + unsafe fn do_fork(&mut self) -> Result<(pid_t, pid_t), io::Error> { + Ok((cvt(libc::fork())?, -1)) } // On QNX Neutrino, fork can fail with EBADF in case "another thread might have opened @@ -193,7 +212,7 @@ impl Command { // Documentation says "... or try calling fork() again". This is what we do here. // See also https://www.qnx.com/developers/docs/7.1/#com.qnx.doc.neutrino.lib_ref/topic/f/fork.html #[cfg(target_os = "nto")] - unsafe fn do_fork(&mut self) -> Result { + unsafe fn do_fork(&mut self) -> Result<(pid_t, pid_t), io::Error> { use crate::sys::io::errno; let mut delay = MIN_FORKSPAWN_SLEEP; @@ -216,7 +235,7 @@ impl Command { delay *= 2; continue; } else { - return cvt(r); + return Ok((cvt(r), -1)); } } } @@ -468,6 +487,12 @@ impl Command { } cfg_select! { + target_os = "freebsd" => { + if self.get_create_pidfd() { + // FreeBSD does not support posix_spawn with procdesc(4), as of 15.0-RELEASE + return Ok(None); + } + } target_os = "linux" => { use crate::sys::weak::weak; @@ -950,20 +975,20 @@ impl Command { pub struct Process { pid: pid_t, status: Option, - // On Linux, stores the pidfd created for this child. + // On FreeBSD and 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 `pidfd_open` syscall was not available). - #[cfg(target_os = "linux")] + #[cfg(any(target_os = "freebsd", target_os = "linux"))] pidfd: Option, } impl Process { - #[cfg(target_os = "linux")] + #[cfg(any(target_os = "freebsd", target_os = "linux"))] /// # Safety /// - /// `pidfd` must either be -1 (representing no file descriptor) or a valid, exclusively owned file - /// descriptor (See [I/O Safety]). + /// `pidfd` must either be -1 (representing no file descriptor) or a valid, exclusively owned + /// file descriptor (See [I/O Safety]). /// /// [I/O Safety]: crate::io#io-safety unsafe fn new(pid: pid_t, pidfd: pid_t) -> Self { @@ -974,7 +999,7 @@ impl Process { Process { pid, status: None, pidfd } } - #[cfg(not(target_os = "linux"))] + #[cfg(not(any(target_os = "freebsd", target_os = "linux")))] unsafe fn new(pid: pid_t, _pidfd: pid_t) -> Self { Process { pid, status: None } } @@ -1060,7 +1085,7 @@ impl ExitStatus { ExitStatus(status) } - #[cfg(target_os = "linux")] + #[cfg(any(target_os = "freebsd", target_os = "linux"))] pub fn from_waitid_siginfo(siginfo: libc::siginfo_t) -> ExitStatus { let status = unsafe { siginfo.si_status() }; @@ -1262,16 +1287,22 @@ impl ExitStatusError { } } -#[cfg(target_os = "linux")] +#[cfg(any(target_os = "freebsd", target_os = "linux"))] mod linux_child_ext { use crate::io::ErrorKind; + #[cfg(target_os = "freebsd")] + use crate::os::freebsd::process as os; + #[cfg(target_os = "linux")] use crate::os::linux::process as os; use crate::sys::FromInner; + #[cfg(target_os = "freebsd")] + use crate::sys::pal::freebsd::pidfd as imp; + #[cfg(target_os = "linux")] use crate::sys::pal::linux::pidfd as imp; use crate::{io, mem}; #[unstable(feature = "linux_pidfd", issue = "82971")] - impl crate::os::linux::process::ChildExt for crate::process::Child { + impl os::ChildExt for crate::process::Child { fn pidfd(&self) -> io::Result<&os::PidFd> { self.handle .pidfd From 87f7a97ca4ee301f60df780d70b212c4c1e2e7fc Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Thu, 8 Jan 2026 09:18:02 -0700 Subject: [PATCH 2/2] Silence a warning on Linux --- library/std/src/sys/process/unix/unix.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/library/std/src/sys/process/unix/unix.rs b/library/std/src/sys/process/unix/unix.rs index 6088de006630f..bb18d8bd365e4 100644 --- a/library/std/src/sys/process/unix/unix.rs +++ b/library/std/src/sys/process/unix/unix.rs @@ -93,6 +93,7 @@ impl Command { // The child calls `mem::forget` to leak the lock, which is crucial because // releasing a lock is not async-signal-safe. let env_lock = sys::env::env_read_lock(); + #[cfg_attr(not(target_os = "freebsd"), allow(unused))] let (pid, pidfd) = unsafe { self.do_fork() }?; if pid == 0 {