From d1c200e10b68b9fd2f2f827a369db9b7fca024a1 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 18 Dec 2025 22:12:41 +0100 Subject: [PATCH] printenv skips environment variables with invalid UTF-8 closes: #9701 --- src/uu/printenv/src/printenv.rs | 50 ++++++++++++++++++++++++++++----- tests/by-util/test_printenv.rs | 29 +++++++++++++++++++ 2 files changed, 72 insertions(+), 7 deletions(-) diff --git a/src/uu/printenv/src/printenv.rs b/src/uu/printenv/src/printenv.rs index 47801fd378e..d61ccb345ff 100644 --- a/src/uu/printenv/src/printenv.rs +++ b/src/uu/printenv/src/printenv.rs @@ -5,9 +5,14 @@ use clap::{Arg, ArgAction, Command}; use std::env; +#[cfg(unix)] +use std::io::{self, Write}; use uucore::translate; use uucore::{error::UResult, format_usage}; +#[cfg(unix)] +use std::os::unix::ffi::OsStrExt; + static OPT_NULL: &str = "null"; static ARG_VARIABLES: &str = "variables"; @@ -21,15 +26,34 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .map(|v| v.map(ToString::to_string).collect()) .unwrap_or_default(); - let separator = if matches.get_flag(OPT_NULL) { - "\x00" + let separator: &[u8] = if matches.get_flag(OPT_NULL) { + b"\x00" } else { - "\n" + b"\n" }; + #[cfg(unix)] + let mut stdout = io::stdout().lock(); + if variables.is_empty() { - for (env_var, value) in env::vars() { - print!("{env_var}={value}{separator}"); + for (env_var, value) in env::vars_os() { + #[cfg(unix)] + { + stdout.write_all(env_var.as_bytes())?; + stdout.write_all(b"=")?; + stdout.write_all(value.as_bytes())?; + stdout.write_all(separator)?; + } + #[cfg(not(unix))] + { + // On non-Unix, use lossy conversion as OsStrExt is not available + print!( + "{}={}{}", + env_var.to_string_lossy(), + value.to_string_lossy(), + String::from_utf8_lossy(separator) + ); + } } return Ok(()); } @@ -41,8 +65,20 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { error_found = true; continue; } - if let Ok(var) = env::var(env_var) { - print!("{var}{separator}"); + if let Some(var) = env::var_os(&env_var) { + #[cfg(unix)] + { + stdout.write_all(var.as_bytes())?; + stdout.write_all(separator)?; + } + #[cfg(not(unix))] + { + print!( + "{}{}", + var.to_string_lossy(), + String::from_utf8_lossy(separator) + ); + } } else { error_found = true; } diff --git a/tests/by-util/test_printenv.rs b/tests/by-util/test_printenv.rs index 4c1b436bc04..bcdd2c2e55b 100644 --- a/tests/by-util/test_printenv.rs +++ b/tests/by-util/test_printenv.rs @@ -2,6 +2,10 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. +#[cfg(unix)] +use std::ffi::OsString; +#[cfg(unix)] +use std::os::unix::ffi::OsStringExt; use uutests::new_ucmd; #[test] @@ -90,3 +94,28 @@ fn test_null_separator() { .stdout_is("FOO\x00VALUE\x00"); } } + +#[test] +#[cfg(unix)] +#[cfg(not(any(target_os = "freebsd", target_os = "android", target_os = "openbsd")))] +fn test_non_utf8_value() { + // Environment variable values can contain non-UTF-8 bytes on Unix. + // printenv should output them correctly, matching GNU behavior. + // Reproduces: LD_PRELOAD=$'/tmp/lib.so\xff' printenv LD_PRELOAD + let value_with_invalid_utf8 = OsString::from_vec(b"/tmp/lib.so\xff".to_vec()); + + let result = new_ucmd!() + .env("LD_PRELOAD", &value_with_invalid_utf8) + .arg("LD_PRELOAD") + .run(); + + // Use byte-based assertions to avoid UTF-8 conversion issues + // when the test framework tries to format error messages + assert!( + result.succeeded(), + "Command failed with exit code: {:?}, stderr: {:?}", + result.code(), + String::from_utf8_lossy(result.stderr()) + ); + result.stdout_is_bytes(b"/tmp/lib.so\xff\n"); +}