rm: report permission denied for unreadable subdirectories

When rm -rf encounters a subdirectory without read permission,
handle_permission_denied attempts unlink_at on it. If that fails
with ENOTEMPTY, force mode was silently swallowing the error,
causing the parent removal to fail with a misleading
"Directory not empty" message instead.

Now always reports permission denied when we cannot open a
subdirectory and cannot remove it directly.

Fixes #10966
This commit is contained in:
Karthik Vinayan
2026-02-16 15:06:20 +01:00
committed by Daniel Hofstetter
parent e93361bab6
commit cf3b6d45ae
2 changed files with 23 additions and 15 deletions
+7 -15
View File
@@ -197,21 +197,13 @@ fn handle_permission_denied(
// When we can't open a subdirectory due to permission denied,
// try to remove it directly (it might be empty).
// This matches GNU rm behavior with -f flag.
if let Err(remove_err) = dir_fd.unlink_at(entry_name, true) {
// Failed to remove - show appropriate error
if remove_err.kind() == std::io::ErrorKind::PermissionDenied {
// Permission denied errors are always shown, even with force
show_permission_denied_error(entry_path);
return true;
} else if !options.force {
let remove_err = remove_err.map_err_context(
|| translate!("rm-error-cannot-remove", "file" => entry_path.quote()),
);
show_error!("{remove_err}");
return true;
}
// With force mode, suppress non-permission errors
return !options.force;
if let Err(_remove_err) = dir_fd.unlink_at(entry_name, true) {
// The directory is not empty (or another error) and we can't read it
// to remove its contents. Report the original permission denied error.
// This matches GNU rm behavior — the real problem is we lack
// permission to traverse the directory.
show_permission_denied_error(entry_path);
return true;
}
// Successfully removed empty directory
verbose_removed_directory(entry_path, options);
+16
View File
@@ -1006,6 +1006,22 @@ fn test_unreadable_and_nonempty_dir() {
assert!(at.dir_exists("a"));
}
#[cfg(not(windows))]
#[test]
fn test_recursive_remove_unreadable_subdir() {
// Regression test for https://github.com/uutils/coreutils/issues/10966
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir_all("foo/bar");
at.touch("foo/bar/baz");
at.set_mode("foo/bar", 0o0000);
let result = ucmd.args(&["-r", "-f", "foo"]).fails();
result.stderr_contains("Permission denied");
result.stderr_contains("foo/bar");
at.set_mode("foo/bar", 0o0755);
}
#[cfg(not(windows))]
#[test]
fn test_inaccessible_dir() {