Skip to content

Commit 8e83822

Browse files
authored
Merge pull request #8365 from yuankunzhang/env-exec
env: use execvp instead of Command::status on Unix systems
2 parents 86e9b86 + cab307a commit 8e83822

File tree

1 file changed

+85
-50
lines changed

1 file changed

+85
-50
lines changed

src/uu/env/src/env.rs

Lines changed: 85 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -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;
2426
use std::borrow::Cow;
2527
use std::collections::HashMap;
2628
use std::env;
29+
#[cfg(unix)]
30+
use std::ffi::CString;
2731
use std::ffi::{OsStr, OsString};
2832
use std::io::{self, Write};
29-
3033
#[cfg(unix)]
3134
use std::os::unix::ffi::OsStrExt;
32-
#[cfg(unix)]
33-
use std::os::unix::process::{CommandExt, ExitStatusExt};
34-
use std::process::{self};
35+
3536
use uucore::display::Quotable;
3637
use uucore::error::{ExitCode, UError, UResult, USimpleError, UUsageError};
3738
use 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]
913942
pub 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

Comments
 (0)