Skip to content

Commit

Permalink
feature: env argv0 overwrite (unix only)
Browse files Browse the repository at this point in the history
  • Loading branch information
cre4ture committed Mar 30, 2024
1 parent 3a6bf34 commit fbb5f79
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 18 deletions.
78 changes: 62 additions & 16 deletions src/uu/env/src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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
Expand Down Expand Up @@ -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(
Expand All @@ -184,6 +185,15 @@ 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")
.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")
Expand Down Expand Up @@ -248,6 +258,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,
}

Expand All @@ -273,14 +284,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;
}

Check warning on line 290 in src/uu/env/src/env.rs

View check run for this annotation

Codecov / codecov/patch

src/uu/env/src/env.rs#L288-L290

Added lines #L288 - L290 were not covered by tests
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;
}
_ => {
Expand Down Expand Up @@ -323,10 +339,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);

Check warning on line 349 in src/uu/env/src/env.rs

View check run for this annotation

Codecov / codecov/patch

src/uu/env/src/env.rs#L348-L349

Added lines #L348 - L349 were not covered by tests
}
}

let mut opts = make_options(&matches)?;
Expand All @@ -349,7 +370,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(())
Expand All @@ -361,22 +382,45 @@ impl EnvAppData {
do_debug_printing: bool,
) -> Result<(), Box<dyn UError>> {
let prog = Cow::from(opts.program[0]);
let mut 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
* (which ends up calling clone). Keep using the current process would be ideal, but the
* 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());

Check warning on line 403 in src/uu/env/src/env.rs

View check run for this annotation

Codecov / codecov/patch

src/uu/env/src/env.rs#L403

Added line #L403 was not covered by tests
}
}

#[cfg(not(unix))]
return Err(USimpleError::new(

Check warning on line 408 in src/uu/env/src/env.rs

View check run for this annotation

Codecov / codecov/patch

src/uu/env/src/env.rs#L408

Added line #L408 was not covered by tests
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() {
Expand Down Expand Up @@ -443,6 +487,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,
Expand All @@ -452,6 +497,7 @@ fn make_options(matches: &clap::ArgMatches) -> UResult<Options<'_>> {
unsets,
sets: vec![],
program: vec![],
argv0,
};

let mut begin_prog_opts = false;
Expand Down
33 changes: 31 additions & 2 deletions tests/by-util/test_env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -345,10 +345,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:
Expand Down Expand Up @@ -564,6 +569,30 @@ 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("");
}

#[cfg(test)]
mod tests_split_iterator {

Expand Down

0 comments on commit fbb5f79

Please sign in to comment.