Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 30 additions & 40 deletions src/uu/env/src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ use std::mem::zeroed;
#[cfg(unix)]
use std::os::unix::ffi::OsStrExt;

use uucore::display::{Quotable, print_all_env_vars};
use uucore::display::{OsWrite, Quotable, print_all_env_vars};
use uucore::error::{ExitCode, UError, UResult, USimpleError, UUsageError, strip_errno};
use uucore::line_ending::LineEnding;
#[cfg(all(unix, not(target_os = "fuchsia")))]
Expand Down Expand Up @@ -111,6 +111,7 @@ struct Options<'a> {
sets: Vec<(Cow<'a, OsStr>, Cow<'a, OsStr>)>,
program: Vec<&'a OsStr>,
argv0: Option<&'a OsStr>,
debug: bool,
#[cfg(all(unix, not(target_os = "fuchsia")))]
ignore_signal: SignalRequest,
#[cfg(all(unix, not(target_os = "fuchsia")))]
Expand Down Expand Up @@ -532,19 +533,10 @@ fn to_error(text: &NativeIntStr, e: &EnvError) -> Box<dyn UError> {
}
}

fn debug_print_args(args: &[OsString]) {
let mut error = stderr().lock();
let _ = writeln!(error, "input args:");
for (i, arg) in args.iter().enumerate() {
let _ = writeln!(error, "arg[{i}]: {}", arg.quote());
}
}

fn check_and_handle_string_args(
arg: &OsString,
prefix_to_test: &str,
all_args: &mut Vec<OsString>,
do_debug_print_args: Option<&Vec<OsString>>,
require_non_empty_payload: bool,
strip_optional_leading_equals: bool,
located: Option<(&[OsString], usize)>,
Expand All @@ -555,10 +547,6 @@ fn check_and_handle_string_args(
return Ok(false);
}

if let Some(input_args) = do_debug_print_args {
debug_print_args(input_args); // do it here, such that its also printed when we get an error/panic during parsing
}

let remaining_arg = if strip_optional_leading_equals {
if let Some(stripped_remaining_arg) = remaining_arg.strip_prefix(&*NCvt::convert("=")) {
stripped_remaining_arg
Expand All @@ -585,12 +573,10 @@ fn check_and_handle_string_args(
#[derive(Default)]
struct EnvAppData {
do_debug_printing: bool,
do_input_debug_printing: Option<bool>,
had_string_argument: bool,
}

struct ParsedArguments {
original_args: Vec<OsString>,
matches: clap::ArgMatches,
#[cfg(all(unix, not(target_os = "fuchsia")))]
signal_apply_all: BTreeSet<&'static str>,
Expand Down Expand Up @@ -650,7 +636,6 @@ impl EnvAppData {
b,
"--split-string",
&mut all_args,
None,
true,
true,
located,
Expand All @@ -662,7 +647,6 @@ impl EnvAppData {
b,
"-S",
&mut all_args,
None,
true,
false,
located,
Expand All @@ -674,7 +658,6 @@ impl EnvAppData {
b,
"-vS",
&mut all_args,
None,
true,
false,
located,
Expand All @@ -687,14 +670,12 @@ impl EnvAppData {
b,
"-vvS",
&mut all_args,
Some(original_args),
true,
false,
located,
)? =>
{
self.do_debug_printing = true;
self.do_input_debug_printing = Some(false); // already done
self.had_string_argument = true;
}
b if b == "--split-string" || b == "-S" || b == "-vS" || b == "-vvS" => {
Expand All @@ -706,10 +687,6 @@ impl EnvAppData {
if b == "-vS" || b == "-vvS" {
self.do_debug_printing = true;
}
if b == "-vvS" {
debug_print_args(original_args);
self.do_input_debug_printing = Some(false);
}

let native_next_arg = NCvt::convert(next_arg);
// The string is the argument after this one, detached.
Expand Down Expand Up @@ -797,7 +774,6 @@ impl EnvAppData {
}
};
Ok(ParsedArguments {
original_args,
matches,
#[cfg(all(unix, not(target_os = "fuchsia")))]
signal_apply_all,
Expand All @@ -806,23 +782,16 @@ impl EnvAppData {

fn run_env(&mut self, original_args: impl uucore::Args) -> UResult<()> {
let ParsedArguments {
original_args,
matches,
#[cfg(all(unix, not(target_os = "fuchsia")))]
signal_apply_all,
} = self.parse_arguments(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 Some(true) == self.do_input_debug_printing {
debug_print_args(&original_args);
self.do_input_debug_printing = Some(false);
}

let mut opts = make_options(
&matches,
self.do_debug_printing,
#[cfg(all(unix, not(target_os = "fuchsia")))]
&signal_apply_all,
)?;
Expand Down Expand Up @@ -870,7 +839,7 @@ impl EnvAppData {
// no program provided, so just dump all env vars to stdout
print_all_env_vars(opts.line_ending)?;
} else {
return self.run_program(&opts, self.do_debug_printing);
return self.run_program(&opts);
}

Ok(())
Expand All @@ -885,11 +854,8 @@ impl EnvAppData {
/// - 125: if the env command itself fails
/// - 126: if the program is found but cannot be invoked
/// - 127: if the program cannot be found
fn run_program(
&mut self,
opts: &Options<'_>,
do_debug_printing: bool,
) -> Result<(), Box<dyn UError>> {
fn run_program(&mut self, opts: &Options<'_>) -> Result<(), Box<dyn UError>> {
let do_debug_printing = opts.debug;
let prog = Cow::from(opts.program[0]);

let arg0 = match opts.argv0 {
Expand Down Expand Up @@ -993,6 +959,10 @@ impl EnvAppData {
fn apply_removal_of_all_env_vars(opts: &Options<'_>) {
// remove all env vars if told to ignore presets
if opts.ignore_env {
if opts.debug {
let mut error = stderr().lock();
let _ = writeln!(error, "cleaning environ");
}
for (ref name, _) in env::vars_os() {
unsafe {
env::remove_var(name);
Expand All @@ -1004,6 +974,7 @@ fn apply_removal_of_all_env_vars(opts: &Options<'_>) {
#[cfg_attr(not(unix), allow(clippy::elidable_lifetime_names))]
fn make_options<'a>(
matches: &'a clap::ArgMatches,
debug: bool,
#[cfg(all(unix, not(target_os = "fuchsia")))] signal_apply_all: &BTreeSet<&'static str>,
) -> UResult<Options<'a>> {
let ignore_env = matches.get_flag("ignore-environment");
Expand Down Expand Up @@ -1041,6 +1012,7 @@ fn make_options<'a>(
sets: vec![],
program: vec![],
argv0,
debug,
#[cfg(all(unix, not(target_os = "fuchsia")))]
ignore_signal,
#[cfg(all(unix, not(target_os = "fuchsia")))]
Expand Down Expand Up @@ -1077,6 +1049,12 @@ fn make_options<'a>(

fn apply_unset_env_vars(opts: &Options<'_>) -> Result<(), Box<dyn UError>> {
for name in &opts.unsets {
if opts.debug {
let mut error = stderr().lock();
let _ = error.write_all(b"unset: ");
let _ = error.write_all_os(name);
let _ = error.write_all(b"\n");
}
let native_name = NativeStr::new(name);
if name.is_empty()
|| native_name.contains('\0').unwrap()
Expand Down Expand Up @@ -1104,6 +1082,10 @@ fn apply_change_directory(opts: &Options<'_>) -> Result<(), Box<dyn UError>> {
}

if let Some(d) = opts.running_directory {
if opts.debug {
let mut error = stderr().lock();
let _ = writeln!(error, "chdir: {}", d.quote());
}
match env::set_current_dir(d) {
Ok(()) => d,
Err(error) => {
Expand Down Expand Up @@ -1149,6 +1131,14 @@ fn apply_specified_env_vars(opts: &Options<'_>) {
);
continue;
}
if opts.debug {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to_string_lossy() mangles non-UTF-8 args - GNU passes the raw bytes through. Write the OsStr bytes to stderr instead. Same at line 1065.

let mut error = stderr().lock();
let _ = error.write_all(b"setenv: ");
let _ = error.write_all_os(name);
let _ = error.write_all(b"=");
let _ = error.write_all_os(val);
let _ = error.write_all(b"\n");
}
unsafe {
env::set_var(name, val);
}
Expand Down
4 changes: 3 additions & 1 deletion src/uucore/src/lib/mods/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use std::env;
use std::ffi::OsStr;
use std::fmt;
use std::fs::File;
use std::io::{self, BufWriter, Stdout, StdoutLock, Write as _};
use std::io::{self, BufWriter, Stderr, StderrLock, Stdout, StdoutLock, Write as _};

// These used to be defined here, but they live in their own crate now.
pub use os_display::{Quotable, Quoted};
Expand Down Expand Up @@ -104,6 +104,8 @@ pub trait OsWrite: io::Write {
impl OsWrite for File {}
impl OsWrite for Stdout {}
impl OsWrite for StdoutLock<'_> {}
impl OsWrite for Stderr {}
impl OsWrite for StderrLock<'_> {}
// A future smarter Windows implementation can first flush the BufWriter before
// doing a raw write.
impl<W: OsWrite> OsWrite for BufWriter<W> {}
Expand Down
63 changes: 49 additions & 14 deletions tests/by-util/test_env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,12 +210,6 @@ fn test_debug_2() {
.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",
Expand Down Expand Up @@ -259,12 +253,6 @@ fn test_debug2_part_of_string_arg() {
.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",
Expand All @@ -274,6 +262,54 @@ fn test_debug2_part_of_string_arg() {
);
}

#[test]
fn test_debug_trace_environment_changes() {
let result = new_ucmd!().args(&["-i", "-v", "A=1"]).succeeds();
assert_eq!(result.stdout_str(), "A=1\n");
assert_eq!(result.stderr_str(), "cleaning environ\nsetenv: A=1\n");
}

#[test]
fn test_debug_2_output_is_debug_1() {
let ts = TestScenario::new(util_name!());
let v = ts.ucmd().args(&["-i", "-v", "A=1"]).succeeds();
let vv = ts.ucmd().args(&["-i", "-vv", "A=1"]).succeeds();
assert_eq!(v.stderr_str(), vv.stderr_str());
assert_eq!(v.stdout_str(), vv.stdout_str());
}

#[test]
fn test_debug_trace_unset() {
let result = new_ucmd!()
.env("ENV_VERBOSE_UNSET_ME", "x")
.args(&["-v", "-u", "ENV_VERBOSE_UNSET_ME"])
.succeeds();
assert_eq!(result.stderr_str(), "unset: ENV_VERBOSE_UNSET_ME\n");

// unsetting a variable that does not exist is also traced
let result = new_ucmd!()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GNU does trace it. Should expect unset: ENV_VERBOSE_NOT_SET

.args(&["-v", "-u", "ENV_VERBOSE_NOT_SET"])
.succeeds();
assert_eq!(result.stderr_str(), "unset: ENV_VERBOSE_NOT_SET\n");
}

#[cfg(feature = "echo")]
#[test]
fn test_debug_trace_chdir() {
let ts = TestScenario::new(util_name!());
let result = ts
.ucmd()
.args(&["-v", "-C", "."])
.arg(&ts.bin_path)
.args(&["echo", "hello"])
.succeeds();
assert!(
result
.stderr_str()
.starts_with("chdir: '.'\nexecuting: ")
);
}

#[test]
fn test_file_option() {
let out = new_ucmd!()
Expand Down Expand Up @@ -650,8 +686,7 @@ fn test_split_string_into_args_debug_output_whitespace_handling() {
assert_eq!(out.stdout_str(), "xAx\nxBx\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\
"executing: printf\
\n arg[0]= 'printf'\n arg[1]= $'x%sx\\n'\n arg[2]= 'A'\n arg[3]= 'B'\n"
);
}
Expand Down
Loading