Skip to content

Commit 4c9a96e

Browse files
committed
more fine-grained feature-detection for pidfd spawning
we now distinguish between pidfd_spawn support, pidfd-via-fork/exec and not-supported
1 parent bf06e43 commit 4c9a96e

File tree

1 file changed

+33
-21
lines changed

1 file changed

+33
-21
lines changed

std/src/sys/pal/unix/process/process_unix.rs

+33-21
Original file line numberDiff line numberDiff line change
@@ -476,35 +476,47 @@ impl Command {
476476

477477
weak! { fn pidfd_getpid(libc::c_int) -> libc::c_int }
478478

479-
static PIDFD_SPAWN_SUPPORTED: AtomicU8 = AtomicU8::new(0);
479+
static PIDFD_SUPPORTED: AtomicU8 = AtomicU8::new(0);
480480
const UNKNOWN: u8 = 0;
481-
const YES: u8 = 1;
482-
// NO currently forces a fallback to fork/exec. We could be more nuanced here and keep using spawn
483-
// if we know pidfd's aren't supported at all and the fallback would be futile.
484-
const NO: u8 = 2;
481+
const SPAWN: u8 = 1;
482+
// Obtaining a pidfd via the fork+exec path might work
483+
const FORK_EXEC: u8 = 2;
484+
// Neither pidfd_spawn nor fork/exec will get us a pidfd.
485+
// Instead we'll just posix_spawn if the other preconditions are met.
486+
const NO: u8 = 3;
485487

486488
if self.get_create_pidfd() {
487-
let flag = PIDFD_SPAWN_SUPPORTED.load(Ordering::Relaxed);
488-
if flag == NO || pidfd_spawnp.get().is_none() || pidfd_getpid.get().is_none() {
489+
let mut support = PIDFD_SUPPORTED.load(Ordering::Relaxed);
490+
if support == FORK_EXEC {
489491
return Ok(None);
490492
}
491-
if flag == UNKNOWN {
492-
let mut support = NO;
493+
if support == UNKNOWN {
494+
support = NO;
493495
let our_pid = crate::process::id();
494-
let pidfd =
495-
unsafe { libc::syscall(libc::SYS_pidfd_open, our_pid, 0) } as libc::c_int;
496-
if pidfd >= 0 {
497-
let pid = unsafe { pidfd_getpid.get().unwrap()(pidfd) } as u32;
498-
unsafe { libc::close(pidfd) };
499-
if pid == our_pid {
500-
support = YES
501-
};
496+
let pidfd = cvt(unsafe { libc::syscall(libc::SYS_pidfd_open, our_pid, 0) } as c_int);
497+
match pidfd {
498+
Ok(pidfd) => {
499+
support = FORK_EXEC;
500+
if let Some(Ok(pid)) = pidfd_getpid.get().map(|f| cvt(unsafe { f(pidfd) } as i32)) {
501+
if pidfd_spawnp.get().is_some() && pid as u32 == our_pid {
502+
support = SPAWN
503+
}
504+
}
505+
unsafe { libc::close(pidfd) };
506+
}
507+
Err(e) if e.raw_os_error() == Some(libc::EMFILE) => {
508+
// We're temporarily(?) out of file descriptors. In this case obtaining a pidfd would also fail
509+
// Don't update the support flag so we can probe again later.
510+
return Err(e)
511+
}
512+
_ => {}
502513
}
503-
PIDFD_SPAWN_SUPPORTED.store(support, Ordering::Relaxed);
504-
if support != YES {
514+
PIDFD_SUPPORTED.store(support, Ordering::Relaxed);
515+
if support == FORK_EXEC {
505516
return Ok(None);
506517
}
507518
}
519+
core::assert_matches::debug_assert_matches!(support, SPAWN | NO);
508520
}
509521
} else {
510522
if self.get_create_pidfd() {
@@ -691,7 +703,7 @@ impl Command {
691703
let spawn_fn = retrying_libc_posix_spawnp;
692704

693705
#[cfg(target_os = "linux")]
694-
if self.get_create_pidfd() {
706+
if self.get_create_pidfd() && PIDFD_SUPPORTED.load(Ordering::Relaxed) == SPAWN {
695707
let mut pidfd: libc::c_int = -1;
696708
let spawn_res = pidfd_spawnp.get().unwrap()(
697709
&mut pidfd,
@@ -706,7 +718,7 @@ impl Command {
706718
if let Err(ref e) = spawn_res
707719
&& e.raw_os_error() == Some(libc::ENOSYS)
708720
{
709-
PIDFD_SPAWN_SUPPORTED.store(NO, Ordering::Relaxed);
721+
PIDFD_SUPPORTED.store(FORK_EXEC, Ordering::Relaxed);
710722
return Ok(None);
711723
}
712724
spawn_res?;

0 commit comments

Comments
 (0)