From b8702bd5df391150504c48e32dbe1782817b1f77 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 8 Aug 2025 10:32:40 +0200 Subject: [PATCH] ln: fix handling of non-UTF-8 filenames --- src/uu/ln/src/ln.rs | 14 +++++++------ tests/by-util/test_ln.rs | 43 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/src/uu/ln/src/ln.rs b/src/uu/ln/src/ln.rs index 8b21bd093..d047ab12e 100644 --- a/src/uu/ln/src/ln.rs +++ b/src/uu/ln/src/ln.rs @@ -34,7 +34,7 @@ pub struct Settings { symbolic: bool, relative: bool, logical: bool, - target_dir: Option, + target_dir: Option, no_target_dir: bool, no_dereference: bool, verbose: bool, @@ -102,7 +102,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { /* the list of files */ let paths: Vec = matches - .get_many::(ARG_FILES) + .get_many::(ARG_FILES) .unwrap() .map(PathBuf::from) .collect(); @@ -131,8 +131,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { logical, relative: matches.get_flag(options::RELATIVE), target_dir: matches - .get_one::(options::TARGET_DIRECTORY) - .map(String::from), + .get_one::(options::TARGET_DIRECTORY) + .map(PathBuf::from), no_target_dir: matches.get_flag(options::NO_TARGET_DIRECTORY), no_dereference: matches.get_flag(options::NO_DEREFERENCE), verbose: matches.get_flag(options::VERBOSE), @@ -210,6 +210,7 @@ pub fn uu_app() -> Command { .help(translate!("ln-help-target-directory")) .value_name("DIRECTORY") .value_hint(clap::ValueHint::DirPath) + .value_parser(clap::value_parser!(OsString)) .conflicts_with(options::NO_TARGET_DIRECTORY), ) .arg( @@ -238,6 +239,7 @@ pub fn uu_app() -> Command { Arg::new(ARG_FILES) .action(ArgAction::Append) .value_hint(clap::ValueHint::AnyPath) + .value_parser(clap::value_parser!(OsString)) .required(true) .num_args(1..), ) @@ -245,9 +247,9 @@ pub fn uu_app() -> Command { fn exec(files: &[PathBuf], settings: &Settings) -> UResult<()> { // Handle cases where we create links in a directory first. - if let Some(ref name) = settings.target_dir { + if let Some(ref target_path) = settings.target_dir { // 4th form: a directory is specified by -t. - return link_files_in_dir(files, &PathBuf::from(name), settings); + return link_files_in_dir(files, target_path, settings); } if !settings.no_target_dir { if files.len() == 1 { diff --git a/tests/by-util/test_ln.rs b/tests/by-util/test_ln.rs index 998595035..bdcbe20ec 100644 --- a/tests/by-util/test_ln.rs +++ b/tests/by-util/test_ln.rs @@ -843,3 +843,46 @@ fn test_ln_seen_file() { ); } } + +#[test] +#[cfg(target_os = "linux")] +fn test_ln_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 test file with non-UTF-8 bytes in the name + let non_utf8_bytes = b"test_\xFF\xFE.txt"; + let non_utf8_name = OsStr::from_bytes(non_utf8_bytes); + let non_utf8_link_bytes = b"link_\xFF\xFE.txt"; + let non_utf8_link_name = OsStr::from_bytes(non_utf8_link_bytes); + + // Create the actual file + at.touch(non_utf8_name); + + // Test creating a hard link with non-UTF-8 file names + scene.ucmd() + .arg(non_utf8_name) + .arg(non_utf8_link_name) + .succeeds(); + + // Both files should exist + assert!(at.file_exists(non_utf8_name)); + assert!(at.file_exists(non_utf8_link_name)); + + // Test creating a symbolic link with non-UTF-8 file names + let symlink_bytes = b"symlink_\xFF\xFE.txt"; + let symlink_name = OsStr::from_bytes(symlink_bytes); + + scene.ucmd() + .args(&["-s"]) + .arg(non_utf8_name) + .arg(symlink_name) + .succeeds(); + + // Check if symlink was created successfully + let symlink_path = at.plus(symlink_name); + assert!(symlink_path.is_symlink()); +}