From c54cc035f0fdc325b8dec7ceadab0bb57e1c80e7 Mon Sep 17 00:00:00 2001 From: can1357 Date: Wed, 18 Mar 2026 23:48:44 +0100 Subject: [PATCH 1/2] ln: accept non-utf8 source names in target-dir modes --- src/uu/ln/src/ln.rs | 13 +++--------- tests/by-util/test_ln.rs | 45 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 10 deletions(-) diff --git a/src/uu/ln/src/ln.rs b/src/uu/ln/src/ln.rs index f1b7ff2bfec..0e094cb5385 100644 --- a/src/uu/ln/src/ln.rs +++ b/src/uu/ln/src/ln.rs @@ -335,22 +335,15 @@ fn link_files_in_dir(files: &[PathBuf], target_dir: &Path, settings: &Settings) } } target_dir.to_path_buf() - } else if let Some(name) = srcpath.as_os_str().to_str() { - match Path::new(name).file_name() { + } else { + match srcpath.file_name() { Some(basename) => target_dir.join(basename), // This can be None only for "." or "..". Trying // to create a link with such name will fail with // EEXIST, which agrees with the behavior of GNU // coreutils. - None => target_dir.join(name), + None => target_dir.join(srcpath), } - } else { - show_error!( - "{}", - translate!("ln-error-cannot-stat", "path" => srcpath.quote()) - ); - all_successful = false; - continue; }; if linked_destinations.contains(&targetpath) { diff --git a/tests/by-util/test_ln.rs b/tests/by-util/test_ln.rs index 560a7364f2f..bc06956e8c6 100644 --- a/tests/by-util/test_ln.rs +++ b/tests/by-util/test_ln.rs @@ -378,6 +378,51 @@ fn test_symlink_target_dir() { assert_eq!(at.resolve_link(file_b_link), file_b); } +#[test] +#[cfg(target_os = "linux")] +fn test_symlink_target_dir_non_utf8_source_name() { + use std::ffi::OsStr; + use std::fs; + use std::os::unix::ffi::OsStrExt; + + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let target_dir = "test_symlink_target_dir_non_utf8"; + let source_bytes = b"source_\xFF\xFE"; + let source_name = OsStr::from_bytes(source_bytes); + + at.mkdir(target_dir); + at.touch(source_name); + + scene + .ucmd() + .args(&["-s", "-t", target_dir]) + .arg(source_name) + .succeeds() + .no_stderr(); + + let target_dir_path = at.plus(target_dir); + let mut dir_entries = fs::read_dir(&target_dir_path) + .expect("reading target directory entries after creating symlink"); + let created_entry = dir_entries + .next() + .expect("finding created entry in target directory") + .expect("reading created target-directory entry"); + assert!( + dir_entries.next().is_none(), + "expected only one created entry in target directory" + ); + assert_eq!( + created_entry.file_name().as_os_str().as_bytes(), + source_bytes + ); + + let created_link_path = target_dir_path.join(source_name); + let created_link_target = fs::read_link(&created_link_path) + .expect("reading created symlink target in target directory"); + assert_eq!(created_link_target.as_os_str().as_bytes(), source_bytes); +} + #[test] fn test_symlink_target_dir_from_dir() { let (at, mut ucmd) = at_and_ucmd!(); From 830791952916c13c77b448503ef5337d3d0246f7 Mon Sep 17 00:00:00 2001 From: can1357 Date: Wed, 8 Apr 2026 03:38:57 +0200 Subject: [PATCH 2/2] ln: remove unused ln-error-cannot-stat locale strings --- src/uu/ln/locales/en-US.ftl | 1 - src/uu/ln/locales/fr-FR.ftl | 1 - 2 files changed, 2 deletions(-) diff --git a/src/uu/ln/locales/en-US.ftl b/src/uu/ln/locales/en-US.ftl index 337d5c2a0d9..5afa83d4314 100644 --- a/src/uu/ln/locales/en-US.ftl +++ b/src/uu/ln/locales/en-US.ftl @@ -29,7 +29,6 @@ ln-error-missing-destination = missing destination file operand after {$operand} ln-error-extra-operand = extra operand {$operand} Try '{$program} --help' for more information. ln-error-could-not-update = Could not update {$target}: {$error} -ln-error-cannot-stat = cannot stat {$path}: No such file or directory ln-error-will-not-overwrite = will not overwrite just-created {$target} with {$source} ln-prompt-replace = replace {$file}? ln-cannot-backup = cannot backup {$file} diff --git a/src/uu/ln/locales/fr-FR.ftl b/src/uu/ln/locales/fr-FR.ftl index d8ba0996722..2be821c2734 100644 --- a/src/uu/ln/locales/fr-FR.ftl +++ b/src/uu/ln/locales/fr-FR.ftl @@ -30,7 +30,6 @@ ln-error-missing-destination = opérande de fichier de destination manquant apr ln-error-extra-operand = opérande supplémentaire {$operand} Essayez « {$program} --help » pour plus d'informations. ln-error-could-not-update = Impossible de mettre à jour {$target} : {$error} -ln-error-cannot-stat = impossible d'analyser {$path} : Aucun fichier ou répertoire de ce nom ln-error-will-not-overwrite = ne remplacera pas le fichier {$target} qui vient d'être créé par {$source} ln-prompt-replace = remplacer {$file} ? ln-cannot-backup = impossible de sauvegarder {$file}