From 7a819bd93ab5224a70dc54eabed1b624939c2848 Mon Sep 17 00:00:00 2001 From: emirmrkaljevic <93012621+emirmrkaljevic@users.noreply.github.com> Date: Thu, 22 Oct 2026 15:10:33 +0200 Subject: [PATCH 1/3] shred: report directories as 'failed to open for writing: Is a directory' like GNU --- src/uu/shred/locales/en-US.ftl | 1 - src/uu/shred/locales/fr-FR.ftl | 1 - src/uu/shred/src/shred.rs | 23 +++++++++-------------- tests/by-util/test_shred.rs | 22 ++++++++++++---------- 4 files changed, 21 insertions(+), 26 deletions(-) diff --git a/src/uu/shred/locales/en-US.ftl b/src/uu/shred/locales/en-US.ftl index b2f749a0b82..82683954c29 100644 --- a/src/uu/shred/locales/en-US.ftl +++ b/src/uu/shred/locales/en-US.ftl @@ -43,7 +43,6 @@ shred-cannot-open-random-source = cannot open random source: {$source} shred-invalid-file-size = invalid file size: {$size} shred-no-such-file-or-directory = {$file}: No such file or directory shred-failed-to-open-for-writing-not-a-directory = {$file}: failed to open for writing: Not a directory -shred-failed-to-open-for-writing-is-a-directory = {$file}: failed to open for writing: Is a directory shred-not-a-file = {$file}: Not a file # Option help text diff --git a/src/uu/shred/locales/fr-FR.ftl b/src/uu/shred/locales/fr-FR.ftl index d03b9d74ad8..3d039b5d311 100644 --- a/src/uu/shred/locales/fr-FR.ftl +++ b/src/uu/shred/locales/fr-FR.ftl @@ -42,7 +42,6 @@ shred-cannot-open-random-source = impossible d'ouvrir la source aléatoire : {$s shred-invalid-file-size = taille de fichier invalide : {$size} shred-no-such-file-or-directory = {$file} : Aucun fichier ou répertoire de ce type shred-failed-to-open-for-writing-not-a-directory = {$file} : impossible d'ouvrir en écriture : N'est pas un répertoire -shred-failed-to-open-for-writing-is-a-directory = {$file} : impossible d'ouvrir en écriture : Est un répertoire shred-not-a-file = {$file} : N'est pas un fichier # Texte d'aide des options diff --git a/src/uu/shred/src/shred.rs b/src/uu/shred/src/shred.rs index c3e8bf40bb7..63b34c2196e 100644 --- a/src/uu/shred/src/shred.rs +++ b/src/uu/shred/src/shred.rs @@ -651,19 +651,13 @@ fn wipe_file( display_path }; - if path_str.as_encoded_bytes().ends_with(b"/") { - if path.is_dir() { - return Err(USimpleError::new( - 1, - translate_text!("shred-failed-to-open-for-writing-is-a-directory", "file" => display_path.maybe_quote()), - )); - } - if fs::metadata(path).is_err_and(|e| e.kind() == io::ErrorKind::NotADirectory) { - return Err(USimpleError::new( - 1, - translate_text!("shred-failed-to-open-for-writing-not-a-directory", "file" => display_path.maybe_quote()), - )); - } + if path_str.as_encoded_bytes().ends_with(b"/") + && fs::metadata(path).is_err_and(|e| e.kind() == io::ErrorKind::NotADirectory) + { + return Err(USimpleError::new( + 1, + translate_text!("shred-failed-to-open-for-writing-not-a-directory", "file" => display_path.maybe_quote()), + )); } // On OpenBSD, /dev/fd/1 is a character device rather than a symlink. @@ -692,7 +686,8 @@ fn wipe_file( // "no such file" error while a permission error falls through to the // open-for-writing below, which surfaces the real reason. match get_metadata() { - Ok(md) if !md.is_file() => { + // Directories fall through so opening them for writing reports EISDIR. + Ok(md) if !md.is_file() && !md.is_dir() => { return Err(USimpleError::new( 1, translate_text!("shred-not-a-file", "file" => display_path.maybe_quote()), diff --git a/tests/by-util/test_shred.rs b/tests/by-util/test_shred.rs index 519c4ceca61..81414c7c5a9 100644 --- a/tests/by-util/test_shred.rs +++ b/tests/by-util/test_shred.rs @@ -474,16 +474,18 @@ fn test_shred_trailing_slash_on_file() { .stderr_contains("Not a directory"); } -#[test] -fn test_shred_trailing_slash_on_dir() { - let scene = TestScenario::new(util_name!()); - let at = &scene.fixtures; +#[rstest::rstest] +#[case("d")] +#[case("d/")] +#[cfg(unix)] +fn test_shred_on_dir(#[case] arg: &str) { + let (at, mut ucmd) = at_and_ucmd!(); at.mkdir("d"); - scene - .ucmd() - .arg("d/") - .fails() - .stderr_contains("Is a directory"); + ucmd.arg(arg) + .fails_with_code(1) + .stderr_only(format!( + "shred: {arg}: failed to open for writing: Is a directory\n" + )); } #[test] @@ -588,7 +590,7 @@ fn test_couldnt_rename() { #[rstest::rstest] #[case(&["1e3"], "shred: 1e3: No such file or directory\n")] -#[case(&["007"], "shred: 007: Not a file\n")] +#[case(&["007"], "shred: 007: failed to open for writing: Is a directory\n")] #[cfg(not(windows))] fn test_numeric_looking_file_names_are_kept_verbatim( #[case] args: &[&str], From 9ae52c0b2ea2249a40ebcbf1d55efaadc0ef551e Mon Sep 17 00:00:00 2001 From: emirmrkaljevic <93012621+emirmrkaljevic@users.noreply.github.com> Date: Sat, 24 Oct 2026 08:38:30 +0200 Subject: [PATCH 2/3] shred: use strip_errno for the trailing slash open error Build the 'failed to open for writing' message from the io::Error with strip_errno instead of a dedicated 'Not a directory' string, and drop the now unused locale key. --- src/uu/shred/locales/en-US.ftl | 1 - src/uu/shred/locales/fr-FR.ftl | 1 - src/uu/shred/src/shred.rs | 9 +++++++-- tests/by-util/test_shred.rs | 8 +++----- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/uu/shred/locales/en-US.ftl b/src/uu/shred/locales/en-US.ftl index 82683954c29..41af9150ad7 100644 --- a/src/uu/shred/locales/en-US.ftl +++ b/src/uu/shred/locales/en-US.ftl @@ -42,7 +42,6 @@ shred-invalid-number-of-passes = invalid number of passes: {$passes} shred-cannot-open-random-source = cannot open random source: {$source} shred-invalid-file-size = invalid file size: {$size} shred-no-such-file-or-directory = {$file}: No such file or directory -shred-failed-to-open-for-writing-not-a-directory = {$file}: failed to open for writing: Not a directory shred-not-a-file = {$file}: Not a file # Option help text diff --git a/src/uu/shred/locales/fr-FR.ftl b/src/uu/shred/locales/fr-FR.ftl index 3d039b5d311..aa248254a35 100644 --- a/src/uu/shred/locales/fr-FR.ftl +++ b/src/uu/shred/locales/fr-FR.ftl @@ -41,7 +41,6 @@ shred-invalid-number-of-passes = nombre de passes invalide : {$passes} shred-cannot-open-random-source = impossible d'ouvrir la source aléatoire : {$source} shred-invalid-file-size = taille de fichier invalide : {$size} shred-no-such-file-or-directory = {$file} : Aucun fichier ou répertoire de ce type -shred-failed-to-open-for-writing-not-a-directory = {$file} : impossible d'ouvrir en écriture : N'est pas un répertoire shred-not-a-file = {$file} : N'est pas un fichier # Texte d'aide des options diff --git a/src/uu/shred/src/shred.rs b/src/uu/shred/src/shred.rs index 63b34c2196e..6387b5e9ddc 100644 --- a/src/uu/shred/src/shred.rs +++ b/src/uu/shred/src/shred.rs @@ -652,11 +652,16 @@ fn wipe_file( }; if path_str.as_encoded_bytes().ends_with(b"/") - && fs::metadata(path).is_err_and(|e| e.kind() == io::ErrorKind::NotADirectory) + && let Err(e) = fs::metadata(path) + && e.kind() == io::ErrorKind::NotADirectory { return Err(USimpleError::new( 1, - translate_text!("shred-failed-to-open-for-writing-not-a-directory", "file" => display_path.maybe_quote()), + format!( + "{}: {}", + translate_text!("shred-failed-to-open-for-writing", "file" => display_path.maybe_quote()), + strip_errno(&e) + ), )); } diff --git a/tests/by-util/test_shred.rs b/tests/by-util/test_shred.rs index 81414c7c5a9..ed7b988d2ce 100644 --- a/tests/by-util/test_shred.rs +++ b/tests/by-util/test_shred.rs @@ -481,11 +481,9 @@ fn test_shred_trailing_slash_on_file() { fn test_shred_on_dir(#[case] arg: &str) { let (at, mut ucmd) = at_and_ucmd!(); at.mkdir("d"); - ucmd.arg(arg) - .fails_with_code(1) - .stderr_only(format!( - "shred: {arg}: failed to open for writing: Is a directory\n" - )); + ucmd.arg(arg).fails_with_code(1).stderr_only(format!( + "shred: {arg}: failed to open for writing: Is a directory\n" + )); } #[test] From aa9beaed680d66af79f49a1a049c7d5493a2c7b2 Mon Sep 17 00:00:00 2001 From: emirmrkaljevic <93012621+emirmrkaljevic@users.noreply.github.com> Date: Tue, 27 Oct 2026 10:06:09 +0100 Subject: [PATCH 3/3] shred: don't expect the Unix errno text on Windows for a/ --- tests/by-util/test_shred.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/by-util/test_shred.rs b/tests/by-util/test_shred.rs index ed7b988d2ce..103c1fe4e97 100644 --- a/tests/by-util/test_shred.rs +++ b/tests/by-util/test_shred.rs @@ -467,11 +467,11 @@ fn test_shred_trailing_slash_on_file() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; at.touch("a"); - scene - .ucmd() - .arg("a/") - .fails() - .stderr_contains("Not a directory"); + let result = scene.ucmd().arg("a/").fails(); + result.stderr_contains("a/: failed to open for writing: "); + // Windows reports this as "The directory name is invalid." + #[cfg(unix)] + result.stderr_contains("Not a directory"); } #[rstest::rstest]