Skip to content

Commit 2a51788

Browse files
committed
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
1 parent 7acacd0 commit 2a51788

File tree

2 files changed

+23
-15
lines changed

2 files changed

+23
-15
lines changed

src/uu/rm/src/platform/unix.rs

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -197,21 +197,13 @@ fn handle_permission_denied(
197197
// When we can't open a subdirectory due to permission denied,
198198
// try to remove it directly (it might be empty).
199199
// This matches GNU rm behavior with -f flag.
200-
if let Err(remove_err) = dir_fd.unlink_at(entry_name, true) {
201-
// Failed to remove - show appropriate error
202-
if remove_err.kind() == std::io::ErrorKind::PermissionDenied {
203-
// Permission denied errors are always shown, even with force
204-
show_permission_denied_error(entry_path);
205-
return true;
206-
} else if !options.force {
207-
let remove_err = remove_err.map_err_context(
208-
|| translate!("rm-error-cannot-remove", "file" => entry_path.quote()),
209-
);
210-
show_error!("{remove_err}");
211-
return true;
212-
}
213-
// With force mode, suppress non-permission errors
214-
return !options.force;
200+
if let Err(_remove_err) = dir_fd.unlink_at(entry_name, true) {
201+
// The directory is not empty (or another error) and we can't read it
202+
// to remove its contents. Report the original permission denied error.
203+
// This matches GNU rm behavior — the real problem is we lack
204+
// permission to traverse the directory.
205+
show_permission_denied_error(entry_path);
206+
return true;
215207
}
216208
// Successfully removed empty directory
217209
verbose_removed_directory(entry_path, options);

tests/by-util/test_rm.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1006,6 +1006,22 @@ fn test_unreadable_and_nonempty_dir() {
10061006
assert!(at.dir_exists("a"));
10071007
}
10081008

1009+
#[cfg(not(windows))]
1010+
#[test]
1011+
fn test_recursive_remove_unreadable_subdir() {
1012+
// Regression test for https://github.com/uutils/coreutils/issues/10966
1013+
let (at, mut ucmd) = at_and_ucmd!();
1014+
at.mkdir_all("foo/bar");
1015+
at.touch("foo/bar/baz");
1016+
at.set_mode("foo/bar", 0o0000);
1017+
1018+
let result = ucmd.args(&["-r", "-f", "foo"]).fails();
1019+
result.stderr_contains("Permission denied");
1020+
result.stderr_contains("foo/bar");
1021+
1022+
at.set_mode("foo/bar", 0o0755);
1023+
}
1024+
10091025
#[cfg(not(windows))]
10101026
#[test]
10111027
fn test_inaccessible_dir() {

0 commit comments

Comments
 (0)