From 4c84d83c9336342b1147d9e0fabf12e6dc5e1b65 Mon Sep 17 00:00:00 2001 From: Socialpranker <0630039863abc@gmail.com> Date: Sat, 5 Sep 2026 19:42:26 +0200 Subject: [PATCH] env: trace the environment changes under -v -v printed only the exec, not what it did to the environment first, so the three lines GNU emits before it were missing: $ env -v -u A FOO=1 true unset: A setenv: FOO=1 executing: true arg[0]= 'true' and 'cleaning environ' for -i. The individual unsets are not logged when -i is in effect, since it has already emptied the environment. --- src/uu/env/src/env.rs | 32 ++++++++++++++++++++++++++------ tests/by-util/test_env.rs | 29 +++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/src/uu/env/src/env.rs b/src/uu/env/src/env.rs index 579382f9bea..1ebc5f2329f 100644 --- a/src/uu/env/src/env.rs +++ b/src/uu/env/src/env.rs @@ -830,14 +830,14 @@ impl EnvAppData { // NOTE: we manually set and unset the env vars below rather than using Command::env() to more // easily handle the case where no command is given - apply_removal_of_all_env_vars(&opts); + apply_removal_of_all_env_vars(&opts, self.do_debug_printing); // load .env-style config file prior to those given on the command-line load_config_file(&mut opts)?; - apply_unset_env_vars(&opts)?; + apply_unset_env_vars(&opts, self.do_debug_printing)?; - apply_specified_env_vars(&opts); + apply_specified_env_vars(&opts, self.do_debug_printing); #[cfg(all(unix, not(target_os = "fuchsia")))] { @@ -990,9 +990,12 @@ impl EnvAppData { } } -fn apply_removal_of_all_env_vars(opts: &Options<'_>) { +fn apply_removal_of_all_env_vars(opts: &Options<'_>, do_debug_printing: bool) { // remove all env vars if told to ignore presets if opts.ignore_env { + if do_debug_printing { + let _ = writeln!(stderr(), "cleaning environ"); + } for (ref name, _) in env::vars_os() { unsafe { env::remove_var(name); @@ -1075,7 +1078,13 @@ fn make_options<'a>( Ok(opts) } -fn apply_unset_env_vars(opts: &Options<'_>) -> Result<(), Box> { +fn apply_unset_env_vars( + opts: &Options<'_>, + do_debug_printing: bool, +) -> Result<(), Box> { + // -i has already emptied the environment, and GNU does not log the + // individual unsets in that case. + let do_debug_printing = do_debug_printing && !opts.ignore_env; for name in &opts.unsets { let native_name = NativeStr::new(name); if name.is_empty() @@ -1087,6 +1096,9 @@ fn apply_unset_env_vars(opts: &Options<'_>) -> Result<(), Box> { translate!("env-error-cannot-unset-invalid", "name" => name.quote()), )); } + if do_debug_printing { + let _ = writeln!(stderr(), "unset: {}", name.to_string_lossy()); + } unsafe { env::remove_var(name); } @@ -1117,7 +1129,7 @@ fn apply_change_directory(opts: &Options<'_>) -> Result<(), Box> { Ok(()) } -fn apply_specified_env_vars(opts: &Options<'_>) { +fn apply_specified_env_vars(opts: &Options<'_>, do_debug_printing: bool) { // set specified env vars for (name, val) in &opts.sets { /* @@ -1149,6 +1161,14 @@ fn apply_specified_env_vars(opts: &Options<'_>) { ); continue; } + if do_debug_printing { + let _ = writeln!( + stderr(), + "setenv: {}={}", + name.to_string_lossy(), + val.to_string_lossy() + ); + } unsafe { env::set_var(name, val); } diff --git a/tests/by-util/test_env.rs b/tests/by-util/test_env.rs index ec7228f6c38..a64701c9346 100644 --- a/tests/by-util/test_env.rs +++ b/tests/by-util/test_env.rs @@ -265,6 +265,7 @@ fn test_debug2_part_of_string_arg() { r"arg\[2\]: '[^\n]+(\/|\\)coreutils(.exe)?'\n", r"arg\[3\]: 'echo'\n", r"arg\[4\]: 'hello2'\n", + r"setenv: FOO=BAR\n", r"executing: [^\n]+(\/|\\)coreutils(.exe)?\n", r" arg\[0\]= '[^\n]+(\/|\\)coreutils(.exe)?'\n", r" arg\[1\]= 'echo'\n", @@ -2260,3 +2261,31 @@ env: no terminating quote in -S string at position 18 for quote ''' .stderr_is("env: no terminating quote in -S string at position 18 for quote '''\n"); } } + +/// -v traces what it does to the environment before exec, as GNU does: +/// "cleaning environ" for -i, one "unset:" line per -u and one "setenv:" +/// line per assignment. +#[test] +fn test_debug_traces_environment_changes() { + new_ucmd!() + .args(&["-v", "-u", "A", "-u", "B", "FOO=1", "true"]) + .succeeds() + .stderr_contains("unset: A\nunset: B\nsetenv: FOO=1\nexecuting: true\n"); + + new_ucmd!() + .args(&["-v", "-i", "FOO=1", "true"]) + .succeeds() + .stderr_contains("cleaning environ\nsetenv: FOO=1\nexecuting: true\n"); + + // -i has already emptied the environment, so the unset is not logged. + new_ucmd!() + .args(&["-v", "-i", "-u", "PATH", "true"]) + .succeeds() + .stderr_contains("cleaning environ\nexecuting: true\n"); + + // Without -v nothing is traced. + new_ucmd!() + .args(&["-i", "-u", "A", "FOO=1", "true"]) + .succeeds() + .no_stderr(); +}