From 5c77fe1ab5538f0667dd108f6705ecf7665e76b8 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 8 Aug 2025 11:20:49 +0200 Subject: [PATCH] readlink: fix handling of non-UTF-8 filenames --- src/uu/readlink/src/readlink.rs | 16 +++++++++------- tests/by-util/test_readlink.rs | 25 +++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/src/uu/readlink/src/readlink.rs b/src/uu/readlink/src/readlink.rs index f634c970a..c5e28ca8b 100644 --- a/src/uu/readlink/src/readlink.rs +++ b/src/uu/readlink/src/readlink.rs @@ -6,6 +6,7 @@ // spell-checker:ignore (ToDO) errno use clap::{Arg, ArgAction, Command}; +use std::ffi::OsString; use std::fs; use std::io::{Write, stdout}; use std::path::{Path, PathBuf}; @@ -54,9 +55,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { MissingHandling::Normal }; - let files: Vec = matches - .get_many::(ARG_FILES) - .map(|v| v.map(ToString::to_string).collect()) + let files: Vec = matches + .get_many::(ARG_FILES) + .map(|v| v.map(PathBuf::from).collect()) .unwrap_or_default(); if files.is_empty() { @@ -78,7 +79,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { }; for f in &files { - let p = PathBuf::from(f); + let p = f; let path_result = if res_mode == ResolveMode::None { fs::read_link(&p) } else { @@ -93,7 +94,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { return if verbose { Err(USimpleError::new( 1, - err.map_err_context(move || f.maybe_quote().to_string()) + err.map_err_context(move || f.to_string_lossy().to_string()) .to_string(), )) } else { @@ -171,13 +172,14 @@ pub fn uu_app() -> Command { .arg( Arg::new(ARG_FILES) .action(ArgAction::Append) + .value_parser(clap::value_parser!(OsString)) .value_hint(clap::ValueHint::AnyPath), ) } fn show(path: &Path, line_ending: Option) -> std::io::Result<()> { - let path = path.to_str().unwrap(); - print!("{path}"); + use uucore::display::print_verbatim; + print_verbatim(path)?; if let Some(line_ending) = line_ending { print!("{line_ending}"); } diff --git a/tests/by-util/test_readlink.rs b/tests/by-util/test_readlink.rs index f837f2b4a..06b92ec2a 100644 --- a/tests/by-util/test_readlink.rs +++ b/tests/by-util/test_readlink.rs @@ -373,3 +373,28 @@ fn test_delimiters() { .stderr_contains("ignoring --no-newline with multiple arguments") .stdout_is("/a\n/a\n"); } + +#[test] +#[cfg(target_os = "linux")] +fn test_readlink_non_utf8_paths() { + use std::ffi::OsStr; + use std::os::unix::ffi::OsStrExt; + + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + // Create a target file and a symlink with non-UTF-8 bytes in the name + at.touch("target_file"); + let non_utf8_bytes = b"symlink_\xFF\xFE"; + let non_utf8_name = OsStr::from_bytes(non_utf8_bytes); + + // Create symlink using std::os::unix::fs::symlink + std::os::unix::fs::symlink(at.plus_as_string("target_file"), at.plus(non_utf8_name)).unwrap(); + + // Test that readlink handles non-UTF-8 symlink names without crashing + let result = scene.ucmd().arg(non_utf8_name).succeeds(); + + // The result should contain the target path + let output = result.stdout_str_lossy(); + assert!(output.contains("target_file")); +}