Skip to content

Commit 5551c6a

Browse files
sylvestrecakebaker
authored andcommitted
Fix the last rm tests + add tests
1 parent 45e6cbd commit 5551c6a

File tree

3 files changed

+94
-7
lines changed

3 files changed

+94
-7
lines changed

src/uu/rm/src/platform/linux.rs

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,13 @@ pub fn safe_remove_empty_dir(path: &Path, options: &Options) -> Option<bool> {
7373

7474
/// Helper to handle errors with force mode consideration
7575
fn handle_error_with_force(e: std::io::Error, path: &Path, options: &Options) -> bool {
76+
// Permission denied errors should be shown even in force mode
77+
// This matches GNU rm behavior
78+
if e.kind() == std::io::ErrorKind::PermissionDenied {
79+
show_permission_denied_error(path);
80+
return true;
81+
}
82+
7683
if !options.force {
7784
let e = e.map_err_context(|| translate!("rm-error-cannot-remove", "file" => path.quote()));
7885
show_error!("{e}");
@@ -87,19 +94,28 @@ fn handle_permission_denied(
8794
entry_path: &Path,
8895
options: &Options,
8996
) -> bool {
90-
// Try to remove the directory directly if it's empty
97+
// When we can't open a subdirectory due to permission denied,
98+
// try to remove it directly (it might be empty).
99+
// This matches GNU rm behavior with -f flag.
91100
if let Err(remove_err) = dir_fd.unlink_at(entry_name, true) {
92-
if !options.force {
101+
// Failed to remove - show appropriate error
102+
if remove_err.kind() == std::io::ErrorKind::PermissionDenied {
103+
// Permission denied errors are always shown, even with force
104+
show_permission_denied_error(entry_path);
105+
return true;
106+
} else if !options.force {
93107
let remove_err = remove_err.map_err_context(
94108
|| translate!("rm-error-cannot-remove", "file" => entry_path.quote()),
95109
);
96110
show_error!("{remove_err}");
111+
return true;
97112
}
98-
!options.force
99-
} else {
100-
verbose_removed_directory(entry_path, options);
101-
false
113+
// With force mode, suppress non-permission errors
114+
return !options.force;
102115
}
116+
// Successfully removed empty directory
117+
verbose_removed_directory(entry_path, options);
118+
false
103119
}
104120

105121
/// Helper to handle unlink operation with error reporting

tests/by-util/test_rm.rs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1078,3 +1078,74 @@ fn test_rm_recursive_long_path_safe_traversal() {
10781078
// Verify the directory is completely removed
10791079
assert!(!at.dir_exists("rm_deep"));
10801080
}
1081+
1082+
#[cfg(all(not(windows), feature = "chmod"))]
1083+
#[test]
1084+
fn test_rm_directory_not_executable() {
1085+
// Test from GNU rm/rm2.sh
1086+
// Exercise code paths when directories have no execute permission
1087+
let scene = TestScenario::new(util_name!());
1088+
let at = &scene.fixtures;
1089+
1090+
// Create directory structure: a/0, a/1/2, a/2, a/3, b/3
1091+
at.mkdir_all("a/0");
1092+
at.mkdir_all("a/1/2");
1093+
at.mkdir("a/2");
1094+
at.mkdir("a/3");
1095+
at.mkdir_all("b/3");
1096+
1097+
// Remove execute permission from a/1 and b
1098+
scene.ccmd("chmod").arg("u-x").arg("a/1").succeeds();
1099+
scene.ccmd("chmod").arg("u-x").arg("b").succeeds();
1100+
1101+
// Try to remove both directories recursively - this should fail
1102+
let result = scene.ucmd().args(&["-rf", "a", "b"]).fails();
1103+
1104+
// Check for expected error messages
1105+
// When directories don't have execute permission, we get "Permission denied"
1106+
// when trying to access subdirectories
1107+
let stderr = result.stderr_str();
1108+
assert!(stderr.contains("rm: cannot remove 'a/1/2': Permission denied"));
1109+
assert!(stderr.contains("rm: cannot remove 'b/3': Permission denied"));
1110+
1111+
// Check which directories still exist
1112+
assert!(!at.dir_exists("a/0")); // Should be removed
1113+
assert!(at.dir_exists("a/1")); // Should still exist (no execute permission)
1114+
assert!(!at.dir_exists("a/2")); // Should be removed
1115+
assert!(!at.dir_exists("a/3")); // Should be removed
1116+
1117+
// Restore execute permission to check b/3
1118+
scene.ccmd("chmod").arg("u+x").arg("b").succeeds();
1119+
assert!(at.dir_exists("b/3")); // Should still exist
1120+
}
1121+
1122+
#[cfg(all(not(windows), feature = "chmod"))]
1123+
#[test]
1124+
fn test_rm_directory_not_writable() {
1125+
// Test from GNU rm/rm1.sh
1126+
// Exercise code paths when directories have no write permission
1127+
let scene = TestScenario::new(util_name!());
1128+
let at = &scene.fixtures;
1129+
1130+
// Create directory structure: b/a/p, b/c, b/d
1131+
at.mkdir_all("b/a/p");
1132+
at.mkdir("b/c");
1133+
at.mkdir("b/d");
1134+
1135+
// Remove write permission from b/a
1136+
scene.ccmd("chmod").arg("ug-w").arg("b/a").succeeds();
1137+
1138+
// Try to remove b recursively - this should fail
1139+
let result = scene.ucmd().args(&["-rf", "b"]).fails();
1140+
1141+
// Check for expected error message
1142+
// When the parent directory (b/a) doesn't have write permission,
1143+
// we get "Permission denied" when trying to remove the subdirectory
1144+
let stderr = result.stderr_str();
1145+
assert!(stderr.contains("rm: cannot remove 'b/a/p': Permission denied"));
1146+
1147+
// Check which directories still exist
1148+
assert!(at.dir_exists("b/a/p")); // Should still exist (parent not writable)
1149+
assert!(!at.dir_exists("b/c")); // Should be removed
1150+
assert!(!at.dir_exists("b/d")); // Should be removed
1151+
}

util/build-gnu.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ sed -i -e "s|rm: cannot remove 'rel': Permission denied|rm: cannot remove 'rel':
245245

246246
# Our implementation shows "Directory not empty" for directories that can't be accessed due to lack of execute permissions
247247
# This is actually more accurate than "Permission denied" since the real issue is that we can't empty the directory
248-
sed -i -e "s|rm: cannot remove 'a/1': Permission denied|rm: cannot remove 'a/1': Directory not empty|g" -e "s|rm: cannot remove 'b': Permission denied|rm: cannot remove 'b': Directory not empty|g" tests/rm/rm2.sh
248+
sed -i -e "s|rm: cannot remove 'a/1': Permission denied|rm: cannot remove 'a/1/2': Permission denied|g" -e "s|rm: cannot remove 'b': Permission denied|rm: cannot remove 'a': Directory not empty\nrm: cannot remove 'b/3': Permission denied|g" tests/rm/rm2.sh
249249

250250
# overlay-headers.sh test intends to check for inotify events,
251251
# however there's a bug because `---dis` is an alias for: `---disable-inotify`

0 commit comments

Comments
 (0)