mirror of
https://github.com/uutils/coreutils
synced 2024-12-12 06:12:39 +00:00
feature: env argv0 overwrite (unix only)
This commit is contained in:
parent
56e59da558
commit
eca8130a4a
2 changed files with 297 additions and 18 deletions
82
src/uu/env/src/env.rs
vendored
82
src/uu/env/src/env.rs
vendored
|
@ -27,7 +27,7 @@ use std::io::{self, Write};
|
|||
use std::ops::Deref;
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::process::ExitStatusExt;
|
||||
use std::os::unix::process::{CommandExt, ExitStatusExt};
|
||||
use std::process::{self};
|
||||
use uucore::display::Quotable;
|
||||
use uucore::error::{ExitCode, UError, UResult, USimpleError, UUsageError};
|
||||
|
@ -48,6 +48,7 @@ struct Options<'a> {
|
|||
unsets: Vec<&'a OsStr>,
|
||||
sets: Vec<(Cow<'a, OsStr>, Cow<'a, OsStr>)>,
|
||||
program: Vec<&'a OsStr>,
|
||||
argv0: Option<&'a OsStr>,
|
||||
}
|
||||
|
||||
// print name=value env pairs on screen
|
||||
|
@ -173,7 +174,7 @@ pub fn uu_app() -> Command {
|
|||
Arg::new("debug")
|
||||
.short('v')
|
||||
.long("debug")
|
||||
.action(ArgAction::SetTrue)
|
||||
.action(ArgAction::Count)
|
||||
.help("print verbose information for each processing step"),
|
||||
)
|
||||
.arg(
|
||||
|
@ -184,6 +185,16 @@ pub fn uu_app() -> Command {
|
|||
.action(ArgAction::Set)
|
||||
.value_parser(ValueParser::os_string())
|
||||
.help("process and split S into separate arguments; used to pass multiple arguments on shebang lines")
|
||||
).arg(
|
||||
Arg::new("argv0")
|
||||
.overrides_with("argv0")
|
||||
.short('a')
|
||||
.long("argv0")
|
||||
.value_name("a")
|
||||
.action(ArgAction::Set)
|
||||
.value_parser(ValueParser::os_string())
|
||||
.help("Override the zeroth argument passed to the command being executed.\
|
||||
Without this option a default value of `command` is used.")
|
||||
)
|
||||
.arg(
|
||||
Arg::new("vars")
|
||||
|
@ -248,6 +259,7 @@ fn check_and_handle_string_args(
|
|||
#[derive(Default)]
|
||||
struct EnvAppData {
|
||||
do_debug_printing: bool,
|
||||
do_input_debug_printing: Option<bool>,
|
||||
had_string_argument: bool,
|
||||
}
|
||||
|
||||
|
@ -273,14 +285,19 @@ impl EnvAppData {
|
|||
b if check_and_handle_string_args(b, "-S", &mut all_args, None)? => {
|
||||
self.had_string_argument = true;
|
||||
}
|
||||
b if check_and_handle_string_args(b, "-vS", &mut all_args, None)? => {
|
||||
self.do_debug_printing = true;
|
||||
self.had_string_argument = true;
|
||||
}
|
||||
b if check_and_handle_string_args(
|
||||
b,
|
||||
"-vS",
|
||||
"-vvS",
|
||||
&mut all_args,
|
||||
Some(original_args),
|
||||
)? =>
|
||||
{
|
||||
self.do_debug_printing = true;
|
||||
self.do_input_debug_printing = Some(false); // already done
|
||||
self.had_string_argument = true;
|
||||
}
|
||||
_ => {
|
||||
|
@ -323,10 +340,15 @@ impl EnvAppData {
|
|||
fn run_env(&mut self, original_args: impl uucore::Args) -> UResult<()> {
|
||||
let (original_args, matches) = self.parse_arguments(original_args)?;
|
||||
|
||||
let did_debug_printing_before = self.do_debug_printing; // could have been done already as part of the "-vS" string parsing
|
||||
let do_debug_printing = self.do_debug_printing || matches.get_flag("debug");
|
||||
if do_debug_printing && !did_debug_printing_before {
|
||||
debug_print_args(&original_args);
|
||||
self.do_debug_printing = self.do_debug_printing || (0 != matches.get_count("debug"));
|
||||
self.do_input_debug_printing = self
|
||||
.do_input_debug_printing
|
||||
.or(Some(matches.get_count("debug") >= 2));
|
||||
if let Some(value) = self.do_input_debug_printing {
|
||||
if value {
|
||||
debug_print_args(&original_args);
|
||||
self.do_input_debug_printing = Some(false);
|
||||
}
|
||||
}
|
||||
|
||||
let mut opts = make_options(&matches)?;
|
||||
|
@ -349,7 +371,7 @@ impl EnvAppData {
|
|||
// no program provided, so just dump all env vars to stdout
|
||||
print_env(opts.line_ending);
|
||||
} else {
|
||||
return self.run_program(opts, do_debug_printing);
|
||||
return self.run_program(opts, self.do_debug_printing);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -361,14 +383,11 @@ impl EnvAppData {
|
|||
do_debug_printing: bool,
|
||||
) -> Result<(), Box<dyn UError>> {
|
||||
let prog = Cow::from(opts.program[0]);
|
||||
#[cfg(unix)]
|
||||
let mut arg0 = prog.clone();
|
||||
#[cfg(not(unix))]
|
||||
let arg0 = prog.clone();
|
||||
let args = &opts.program[1..];
|
||||
if do_debug_printing {
|
||||
eprintln!("executable: {}", prog.quote());
|
||||
for (i, arg) in args.iter().enumerate() {
|
||||
eprintln!("arg[{}]: {}", i, arg.quote());
|
||||
}
|
||||
}
|
||||
// we need to execute a command
|
||||
|
||||
/*
|
||||
* On Unix-like systems Command::status either ends up calling either fork or posix_spawnp
|
||||
|
@ -376,7 +395,36 @@ impl EnvAppData {
|
|||
* standard library contains many checks and fail-safes to ensure the process ends up being
|
||||
* created. This is much simpler than dealing with the hassles of calling execvp directly.
|
||||
*/
|
||||
match process::Command::new(&*prog).args(args).status() {
|
||||
let mut cmd = process::Command::new(&*prog);
|
||||
cmd.args(args);
|
||||
|
||||
if let Some(_argv0) = opts.argv0 {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
cmd.arg0(_argv0);
|
||||
arg0 = Cow::Borrowed(_argv0);
|
||||
if do_debug_printing {
|
||||
eprintln!("argv0: {}", arg0.quote());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
return Err(USimpleError::new(
|
||||
2,
|
||||
"--argv0 is currently not supported on this platform",
|
||||
));
|
||||
}
|
||||
|
||||
if do_debug_printing {
|
||||
eprintln!("executing: {}", prog.maybe_quote());
|
||||
let arg_prefix = " arg";
|
||||
eprintln!("{}[{}]= {}", arg_prefix, 0, arg0.quote());
|
||||
for (i, arg) in args.iter().enumerate() {
|
||||
eprintln!("{}[{}]= {}", arg_prefix, i + 1, arg.quote());
|
||||
}
|
||||
}
|
||||
|
||||
match cmd.status() {
|
||||
Ok(exit) if !exit.success() => {
|
||||
#[cfg(unix)]
|
||||
if let Some(exit_code) = exit.code() {
|
||||
|
@ -443,6 +491,7 @@ fn make_options(matches: &clap::ArgMatches) -> UResult<Options<'_>> {
|
|||
Some(v) => v.map(|s| s.as_os_str()).collect(),
|
||||
None => Vec::with_capacity(0),
|
||||
};
|
||||
let argv0 = matches.get_one::<OsString>("argv0").map(|s| s.as_os_str());
|
||||
|
||||
let mut opts = Options {
|
||||
ignore_env,
|
||||
|
@ -452,6 +501,7 @@ fn make_options(matches: &clap::ArgMatches) -> UResult<Options<'_>> {
|
|||
unsets,
|
||||
sets: vec![],
|
||||
program: vec![],
|
||||
argv0,
|
||||
};
|
||||
|
||||
let mut begin_prog_opts = false;
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
use crate::common::util::expected_result;
|
||||
use crate::common::util::TestScenario;
|
||||
use ::env::native_int_str::{Convert, NCvt};
|
||||
use regex::Regex;
|
||||
use std::env;
|
||||
use std::path::Path;
|
||||
use tempfile::tempdir;
|
||||
|
@ -55,6 +56,99 @@ fn test_if_windows_batch_files_can_be_executed() {
|
|||
assert!(result.stdout_str().contains("Hello Windows World!"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_debug_1() {
|
||||
let ts = TestScenario::new(util_name!());
|
||||
let result = ts
|
||||
.ucmd()
|
||||
.arg("-v")
|
||||
.arg(&ts.bin_path)
|
||||
.args(&["echo", "hello"])
|
||||
.succeeds();
|
||||
result.stderr_matches(
|
||||
&Regex::new(concat!(
|
||||
r"executing: [^\n]+(\/|\\)coreutils(\.exe)?\n",
|
||||
r" arg\[0\]= '[^\n]+(\/|\\)coreutils(\.exe)?'\n",
|
||||
r" arg\[1\]= 'echo'\n",
|
||||
r" arg\[2\]= 'hello'"
|
||||
))
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_debug_2() {
|
||||
let ts = TestScenario::new(util_name!());
|
||||
let result = ts
|
||||
.ucmd()
|
||||
.arg("-vv")
|
||||
.arg(ts.bin_path)
|
||||
.args(&["echo", "hello2"])
|
||||
.succeeds();
|
||||
result.stderr_matches(
|
||||
&Regex::new(concat!(
|
||||
r"input args:\n",
|
||||
r"arg\[0\]: 'env'\n",
|
||||
r"arg\[1\]: '-vv'\n",
|
||||
r"arg\[2\]: '[^\n]+(\/|\\)coreutils(.exe)?'\n",
|
||||
r"arg\[3\]: 'echo'\n",
|
||||
r"arg\[4\]: 'hello2'\n",
|
||||
r"executing: [^\n]+(\/|\\)coreutils(.exe)?\n",
|
||||
r" arg\[0\]= '[^\n]+(\/|\\)coreutils(.exe)?'\n",
|
||||
r" arg\[1\]= 'echo'\n",
|
||||
r" arg\[2\]= 'hello2'"
|
||||
))
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_debug1_part_of_string_arg() {
|
||||
let ts = TestScenario::new(util_name!());
|
||||
|
||||
let result = ts
|
||||
.ucmd()
|
||||
.arg("-vS FOO=BAR")
|
||||
.arg(ts.bin_path)
|
||||
.args(&["echo", "hello1"])
|
||||
.succeeds();
|
||||
result.stderr_matches(
|
||||
&Regex::new(concat!(
|
||||
r"executing: [^\n]+(\/|\\)coreutils(\.exe)?\n",
|
||||
r" arg\[0\]= '[^\n]+(\/|\\)coreutils(\.exe)?'\n",
|
||||
r" arg\[1\]= 'echo'\n",
|
||||
r" arg\[2\]= 'hello1'"
|
||||
))
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_debug2_part_of_string_arg() {
|
||||
let ts = TestScenario::new(util_name!());
|
||||
let result = ts
|
||||
.ucmd()
|
||||
.arg("-vvS FOO=BAR")
|
||||
.arg(ts.bin_path)
|
||||
.args(&["echo", "hello2"])
|
||||
.succeeds();
|
||||
result.stderr_matches(
|
||||
&Regex::new(concat!(
|
||||
r"input args:\n",
|
||||
r"arg\[0\]: 'env'\n",
|
||||
r"arg\[1\]: '-vvS FOO=BAR'\n",
|
||||
r"arg\[2\]: '[^\n]+(\/|\\)coreutils(.exe)?'\n",
|
||||
r"arg\[3\]: 'echo'\n",
|
||||
r"arg\[4\]: 'hello2'\n",
|
||||
r"executing: [^\n]+(\/|\\)coreutils(.exe)?\n",
|
||||
r" arg\[0\]= '[^\n]+(\/|\\)coreutils(.exe)?'\n",
|
||||
r" arg\[1\]= 'echo'\n",
|
||||
r" arg\[2\]= 'hello2'"
|
||||
))
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_file_option() {
|
||||
let out = new_ucmd!()
|
||||
|
@ -345,10 +439,15 @@ fn test_split_string_into_args_debug_output_whitespace_handling() {
|
|||
|
||||
let out = scene
|
||||
.ucmd()
|
||||
.args(&["-vS printf x%sx\\n A \t B \x0B\x0C\r\n"])
|
||||
.args(&["-vvS printf x%sx\\n A \t B \x0B\x0C\r\n"])
|
||||
.succeeds();
|
||||
assert_eq!(out.stdout_str(), "xAx\nxBx\n");
|
||||
assert_eq!(out.stderr_str(), "input args:\narg[0]: 'env'\narg[1]: $'-vS printf x%sx\\\\n A \\t B \\x0B\\x0C\\r\\n'\nexecutable: 'printf'\narg[0]: $'x%sx\\n'\narg[1]: 'A'\narg[2]: 'B'\n");
|
||||
assert_eq!(
|
||||
out.stderr_str(),
|
||||
"input args:\narg[0]: 'env'\narg[1]: $\
|
||||
'-vvS printf x%sx\\\\n A \\t B \\x0B\\x0C\\r\\n'\nexecuting: printf\
|
||||
\n arg[0]= 'printf'\n arg[1]= $'x%sx\\n'\n arg[2]= 'A'\n arg[3]= 'B'\n"
|
||||
);
|
||||
}
|
||||
|
||||
// FixMe: This test fails on MACOS:
|
||||
|
@ -564,6 +663,136 @@ fn test_env_with_gnu_reference_empty_executable_double_quotes() {
|
|||
.stderr_is("env: '': No such file or directory\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_env_overwrite_arg0() {
|
||||
let ts = TestScenario::new(util_name!());
|
||||
|
||||
let bin = ts.bin_path.clone();
|
||||
|
||||
ts.ucmd()
|
||||
.args(&["--argv0", "echo"])
|
||||
.arg(&bin)
|
||||
.args(&["-n", "hello", "world!"])
|
||||
.succeeds()
|
||||
.stdout_is("hello world!")
|
||||
.stderr_is("");
|
||||
|
||||
ts.ucmd()
|
||||
.args(&["-a", "dirname"])
|
||||
.arg(bin)
|
||||
.args(&["aa/bb/cc"])
|
||||
.succeeds()
|
||||
.stdout_is("aa/bb\n")
|
||||
.stderr_is("");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_env_arg_argv0_overwrite() {
|
||||
let ts = TestScenario::new(util_name!());
|
||||
|
||||
let bin = ts.bin_path.clone();
|
||||
|
||||
// overwrite --argv0 by --argv0
|
||||
ts.ucmd()
|
||||
.args(&["--argv0", "dirname"])
|
||||
.args(&["--argv0", "echo"])
|
||||
.arg(&bin)
|
||||
.args(&["aa/bb/cc"])
|
||||
.succeeds()
|
||||
.stdout_is("aa/bb/cc\n")
|
||||
.stderr_is("");
|
||||
|
||||
// overwrite -a by -a
|
||||
ts.ucmd()
|
||||
.args(&["-a", "dirname"])
|
||||
.args(&["-a", "echo"])
|
||||
.arg(&bin)
|
||||
.args(&["aa/bb/cc"])
|
||||
.succeeds()
|
||||
.stdout_is("aa/bb/cc\n")
|
||||
.stderr_is("");
|
||||
|
||||
// overwrite --argv0 by -a
|
||||
ts.ucmd()
|
||||
.args(&["--argv0", "dirname"])
|
||||
.args(&["-a", "echo"])
|
||||
.arg(&bin)
|
||||
.args(&["aa/bb/cc"])
|
||||
.succeeds()
|
||||
.stdout_is("aa/bb/cc\n")
|
||||
.stderr_is("");
|
||||
|
||||
// overwrite -a by --argv0
|
||||
ts.ucmd()
|
||||
.args(&["-a", "dirname"])
|
||||
.args(&["--argv0", "echo"])
|
||||
.arg(&bin)
|
||||
.args(&["aa/bb/cc"])
|
||||
.succeeds()
|
||||
.stdout_is("aa/bb/cc\n")
|
||||
.stderr_is("");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_env_arg_argv0_overwrite_mixed_with_string_args() {
|
||||
let ts = TestScenario::new(util_name!());
|
||||
|
||||
let bin = ts.bin_path.clone();
|
||||
|
||||
// string arg following normal
|
||||
ts.ucmd()
|
||||
.args(&["-S--argv0 dirname"])
|
||||
.args(&["--argv0", "echo"])
|
||||
.arg(&bin)
|
||||
.args(&["aa/bb/cc"])
|
||||
.succeeds()
|
||||
.stdout_is("aa/bb/cc\n")
|
||||
.stderr_is("");
|
||||
|
||||
// normal following string arg
|
||||
ts.ucmd()
|
||||
.args(&["-a", "dirname"])
|
||||
.args(&["-S-a echo"])
|
||||
.arg(&bin)
|
||||
.args(&["aa/bb/cc"])
|
||||
.succeeds()
|
||||
.stdout_is("aa/bb/cc\n")
|
||||
.stderr_is("");
|
||||
|
||||
// one large string arg
|
||||
ts.ucmd()
|
||||
.args(&["-S--argv0 dirname -a echo"])
|
||||
.arg(&bin)
|
||||
.args(&["aa/bb/cc"])
|
||||
.succeeds()
|
||||
.stdout_is("aa/bb/cc\n")
|
||||
.stderr_is("");
|
||||
|
||||
// two string args
|
||||
ts.ucmd()
|
||||
.args(&["-S-a dirname"])
|
||||
.args(&["-S--argv0 echo"])
|
||||
.arg(&bin)
|
||||
.args(&["aa/bb/cc"])
|
||||
.succeeds()
|
||||
.stdout_is("aa/bb/cc\n")
|
||||
.stderr_is("");
|
||||
|
||||
// three args: normal, string, normal
|
||||
ts.ucmd()
|
||||
.args(&["-a", "sleep"])
|
||||
.args(&["-S-a dirname"])
|
||||
.args(&["-a", "echo"])
|
||||
.arg(&bin)
|
||||
.args(&["aa/bb/cc"])
|
||||
.succeeds()
|
||||
.stdout_is("aa/bb/cc\n")
|
||||
.stderr_is("");
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests_split_iterator {
|
||||
|
||||
|
|
Loading…
Reference in a new issue