@@ -449,17 +449,70 @@ impl Command {
449
449
use crate :: mem:: MaybeUninit ;
450
450
use crate :: sys:: weak:: weak;
451
451
use crate :: sys:: { self , cvt_nz, on_broken_pipe_flag_used} ;
452
+ #[ cfg( target_os = "linux" ) ]
453
+ use core:: sync:: atomic:: { AtomicU8 , Ordering } ;
452
454
453
455
if self . get_gid ( ) . is_some ( )
454
456
|| self . get_uid ( ) . is_some ( )
455
457
|| ( self . env_saw_path ( ) && !self . program_is_path ( ) )
456
458
|| !self . get_closures ( ) . is_empty ( )
457
459
|| self . get_groups ( ) . is_some ( )
458
- || self . get_create_pidfd ( )
459
460
{
460
461
return Ok ( None ) ;
461
462
}
462
463
464
+ cfg_if:: cfg_if! {
465
+ if #[ cfg( target_os = "linux" ) ] {
466
+ weak! {
467
+ fn pidfd_spawnp(
468
+ * mut libc:: c_int,
469
+ * const libc:: c_char,
470
+ * const libc:: posix_spawn_file_actions_t,
471
+ * const libc:: posix_spawnattr_t,
472
+ * const * mut libc:: c_char,
473
+ * const * mut libc:: c_char
474
+ ) -> libc:: c_int
475
+ }
476
+
477
+ weak! { fn pidfd_getpid( libc:: c_int) -> libc:: c_int }
478
+
479
+ static PIDFD_SPAWN_SUPPORTED : AtomicU8 = AtomicU8 :: new( 0 ) ;
480
+ 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 ;
485
+
486
+ 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
+ return Ok ( None ) ;
490
+ }
491
+ if flag == UNKNOWN {
492
+ let mut support = NO ;
493
+ 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
+ } ;
502
+ }
503
+ PIDFD_SPAWN_SUPPORTED . store( support, Ordering :: Relaxed ) ;
504
+ if support != YES {
505
+ return Ok ( None ) ;
506
+ }
507
+ }
508
+ }
509
+ } else {
510
+ if self . get_create_pidfd( ) {
511
+ unreachable!( "only implemented on linux" )
512
+ }
513
+ }
514
+ }
515
+
463
516
// Only glibc 2.24+ posix_spawn() supports returning ENOENT directly.
464
517
#[ cfg( all( target_os = "linux" , target_env = "gnu" ) ) ]
465
518
{
@@ -543,9 +596,6 @@ impl Command {
543
596
544
597
let pgroup = self . get_pgroup ( ) ;
545
598
546
- // Safety: -1 indicates we don't have a pidfd.
547
- let mut p = unsafe { Process :: new ( 0 , -1 ) } ;
548
-
549
599
struct PosixSpawnFileActions < ' a > ( & ' a mut MaybeUninit < libc:: posix_spawn_file_actions_t > ) ;
550
600
551
601
impl Drop for PosixSpawnFileActions < ' _ > {
@@ -640,6 +690,47 @@ impl Command {
640
690
#[ cfg( target_os = "nto" ) ]
641
691
let spawn_fn = retrying_libc_posix_spawnp;
642
692
693
+ #[ cfg( target_os = "linux" ) ]
694
+ if self . get_create_pidfd ( ) {
695
+ let mut pidfd: libc:: c_int = -1 ;
696
+ let spawn_res = pidfd_spawnp. get ( ) . unwrap ( ) (
697
+ & mut pidfd,
698
+ self . get_program_cstr ( ) . as_ptr ( ) ,
699
+ file_actions. 0 . as_ptr ( ) ,
700
+ attrs. 0 . as_ptr ( ) ,
701
+ self . get_argv ( ) . as_ptr ( ) as * const _ ,
702
+ envp as * const _ ,
703
+ ) ;
704
+
705
+ let spawn_res = cvt_nz ( spawn_res) ;
706
+ if let Err ( ref e) = spawn_res
707
+ && e. raw_os_error ( ) == Some ( libc:: ENOSYS )
708
+ {
709
+ PIDFD_SPAWN_SUPPORTED . store ( NO , Ordering :: Relaxed ) ;
710
+ return Ok ( None ) ;
711
+ }
712
+ spawn_res?;
713
+
714
+ let pid = match cvt ( pidfd_getpid. get ( ) . unwrap ( ) ( pidfd) ) {
715
+ Ok ( pid) => pid,
716
+ Err ( e) => {
717
+ // The child has been spawned and we are holding its pidfd.
718
+ // But we cannot obtain its pid even though pidfd_getpid support was verified earlier.
719
+ // This might happen if libc can't open procfs because the file descriptor limit has been reached.
720
+ libc:: close ( pidfd) ;
721
+ return Err ( Error :: new (
722
+ e. kind ( ) ,
723
+ "pidfd_spawnp succeeded but the child's PID could not be obtained" ,
724
+ ) ) ;
725
+ }
726
+ } ;
727
+
728
+ return Ok ( Some ( Process :: new ( pid, pidfd) ) ) ;
729
+ }
730
+
731
+ // Safety: -1 indicates we don't have a pidfd.
732
+ let mut p = Process :: new ( 0 , -1 ) ;
733
+
643
734
let spawn_res = spawn_fn (
644
735
& mut p. pid ,
645
736
self . get_program_cstr ( ) . as_ptr ( ) ,
0 commit comments