@@ -18,20 +18,21 @@ use native_int_str::{
1818 Convert , NCvt , NativeIntStr , NativeIntString , NativeStr , from_native_int_representation_owned,
1919} ;
2020#[ cfg( unix) ]
21- use nix:: sys:: signal:: {
22- SaFlags , SigAction , SigHandler , SigHandler :: SigIgn , SigSet , Signal , raise, sigaction, signal,
23- } ;
21+ use nix:: libc;
22+ #[ cfg( unix) ]
23+ use nix:: sys:: signal:: { SigHandler :: SigIgn , Signal , signal} ;
24+ #[ cfg( unix) ]
25+ use nix:: unistd:: execvp;
2426use std:: borrow:: Cow ;
2527use std:: collections:: HashMap ;
2628use std:: env;
29+ #[ cfg( unix) ]
30+ use std:: ffi:: CString ;
2731use std:: ffi:: { OsStr , OsString } ;
2832use std:: io:: { self , Write } ;
29-
3033#[ cfg( unix) ]
3134use std:: os:: unix:: ffi:: OsStrExt ;
32- #[ cfg( unix) ]
33- use std:: os:: unix:: process:: { CommandExt , ExitStatusExt } ;
34- use std:: process:: { self } ;
35+
3536use uucore:: display:: Quotable ;
3637use uucore:: error:: { ExitCode , UError , UResult , USimpleError , UUsageError } ;
3738use uucore:: line_ending:: LineEnding ;
@@ -605,6 +606,15 @@ impl EnvAppData {
605606 Ok ( ( ) )
606607 }
607608
609+ /// Run the program specified by the options.
610+ ///
611+ /// Note that the env command must exec the program, not spawn it. See
612+ /// <https://github.com/uutils/coreutils/issues/8361> for more information.
613+ ///
614+ /// Exit status:
615+ /// - 125: if the env command itself fails
616+ /// - 126: if the program is found but cannot be invoked
617+ /// - 127: if the program cannot be found
608618 fn run_program (
609619 & mut self ,
610620 opts : & Options < ' _ > ,
@@ -617,19 +627,9 @@ impl EnvAppData {
617627 let arg0 = prog. clone ( ) ;
618628 let args = & opts. program [ 1 ..] ;
619629
620- /*
621- * On Unix-like systems Command::status either ends up calling either fork or posix_spawnp
622- * (which ends up calling clone). Keep using the current process would be ideal, but the
623- * standard library contains many checks and fail-safes to ensure the process ends up being
624- * created. This is much simpler than dealing with the hassles of calling execvp directly.
625- */
626- let mut cmd = process:: Command :: new ( & * prog) ;
627- cmd. args ( args) ;
628-
629630 if let Some ( _argv0) = opts. argv0 {
630631 #[ cfg( unix) ]
631632 {
632- cmd. arg0 ( _argv0) ;
633633 arg0 = Cow :: Borrowed ( _argv0) ;
634634 if do_debug_printing {
635635 eprintln ! ( "argv0: {}" , arg0. quote( ) ) ;
@@ -652,40 +652,70 @@ impl EnvAppData {
652652 }
653653 }
654654
655- match cmd. status ( ) {
656- Ok ( exit) if !exit. success ( ) => {
657- #[ cfg( unix) ]
658- {
659- if let Some ( exit_code) = exit. code ( ) {
660- return Err ( exit_code. into ( ) ) ;
661- }
655+ #[ cfg( unix) ]
656+ {
657+ // Convert program name to CString.
658+ let Ok ( prog_cstring) = CString :: new ( prog. as_bytes ( ) ) else {
659+ return Err ( self . make_error_no_such_file_or_dir ( & prog) ) ;
660+ } ;
661+
662+ // Prepare arguments for execvp.
663+ let mut argv = Vec :: new ( ) ;
664+
665+ // Convert arg0 to CString.
666+ let Ok ( arg0_cstring) = CString :: new ( arg0. as_bytes ( ) ) else {
667+ return Err ( self . make_error_no_such_file_or_dir ( & prog) ) ;
668+ } ;
669+ argv. push ( arg0_cstring) ;
670+
671+ // Convert remaining arguments to CString.
672+ for arg in args {
673+ let Ok ( arg_cstring) = CString :: new ( arg. as_bytes ( ) ) else {
674+ return Err ( self . make_error_no_such_file_or_dir ( & prog) ) ;
675+ } ;
676+ argv. push ( arg_cstring) ;
677+ }
662678
663- // `exit.code()` returns `None` on Unix when the process is terminated by a signal.
664- // See std::os::unix::process::ExitStatusExt for more information. This prints out
665- // the interrupted process and the signal it received.
666- let signal_code = exit. signal ( ) . unwrap ( ) ;
667- let signal = Signal :: try_from ( signal_code) . unwrap ( ) ;
668-
669- // We have to disable any handler that's installed by default.
670- // This ensures that we exit on this signal.
671- // For example, `SIGSEGV` and `SIGBUS` have default handlers installed in Rust.
672- // We ignore the errors because there is not much we can do if that fails anyway.
673- // SAFETY: The function is unsafe because installing functions is unsafe, but we are
674- // just defaulting to default behavior and not installing a function. Hence, the call
675- // is safe.
676- let _ = unsafe {
677- sigaction (
678- signal,
679- & SigAction :: new ( SigHandler :: SigDfl , SaFlags :: empty ( ) , SigSet :: all ( ) ) ,
679+ // Execute the program using execvp. this replaces the current
680+ // process. The execvp function takes care of appending a NULL
681+ // argument to the argument list so that we don't have to.
682+ match execvp ( & prog_cstring, & argv) {
683+ Err ( nix:: errno:: Errno :: ENOENT ) => Err ( self . make_error_no_such_file_or_dir ( & prog) ) ,
684+ Err ( nix:: errno:: Errno :: EACCES ) => {
685+ uucore:: show_error!(
686+ "{}" ,
687+ get_message_with_args(
688+ "env-error-permission-denied" ,
689+ HashMap :: from( [ ( "program" . to_string( ) , prog. quote( ) . to_string( ) ) ] )
680690 )
681- } ;
682-
683- let _ = raise ( signal) ;
691+ ) ;
692+ Err ( 126 . into ( ) )
693+ }
694+ Err ( _) => {
695+ uucore:: show_error!(
696+ "{}" ,
697+ get_message_with_args(
698+ "env-error-unknown" ,
699+ HashMap :: from( [ ( "error" . to_string( ) , "execvp failed" . to_string( ) ) ] )
700+ )
701+ ) ;
702+ Err ( 126 . into ( ) )
703+ }
704+ Ok ( _) => {
705+ unreachable ! ( "execvp should never return on success" )
684706 }
685- return Err ( exit. code ( ) . unwrap ( ) . into ( ) ) ;
686707 }
687- Err ( ref err) => {
688- return match err. kind ( ) {
708+ }
709+
710+ #[ cfg( not( unix) ) ]
711+ {
712+ // Fallback to Command::status for non-Unix systems
713+ let mut cmd = std:: process:: Command :: new ( & * prog) ;
714+ cmd. args ( args) ;
715+
716+ match cmd. status ( ) {
717+ Ok ( exit) if !exit. success ( ) => Err ( exit. code ( ) . unwrap_or ( 1 ) . into ( ) ) ,
718+ Err ( ref err) => match err. kind ( ) {
689719 io:: ErrorKind :: NotFound | io:: ErrorKind :: InvalidInput => {
690720 Err ( self . make_error_no_such_file_or_dir ( & prog) )
691721 }
@@ -709,11 +739,10 @@ impl EnvAppData {
709739 ) ;
710740 Err ( 126 . into ( ) )
711741 }
712- } ;
742+ } ,
743+ Ok ( _) => Ok ( ( ) ) ,
713744 }
714- Ok ( _) => ( ) ,
715745 }
716- Ok ( ( ) )
717746 }
718747}
719748
@@ -911,6 +940,12 @@ fn ignore_signal(sig: Signal) -> UResult<()> {
911940
912941#[ uucore:: main]
913942pub fn uumain ( args : impl uucore:: Args ) -> UResult < ( ) > {
943+ // Rust ignores SIGPIPE (see https://github.com/rust-lang/rust/issues/62569).
944+ // We restore its default action here.
945+ #[ cfg( unix) ]
946+ unsafe {
947+ libc:: signal ( libc:: SIGPIPE , libc:: SIG_DFL ) ;
948+ }
914949 EnvAppData :: default ( ) . run_env ( args)
915950}
916951
0 commit comments