Skip to content

Commit fecdc72

Browse files
committed
more fixe
1 parent 1376176 commit fecdc72

File tree

2 files changed

+262
-284
lines changed

2 files changed

+262
-284
lines changed

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

Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
// This file is part of the uutils coreutils package.
2+
//
3+
// For the full copyright and license information, please view the LICENSE
4+
// file that was distributed with this source code.
5+
6+
// Linux-specific implementations for the rm utility
7+
8+
use std::fs;
9+
use std::path::Path;
10+
use uucore::display::Quotable;
11+
use uucore::error::FromIo;
12+
use uucore::safe_traversal::DirFd;
13+
use uucore::show_error;
14+
use uucore::translate;
15+
16+
use super::super::{
17+
InteractiveMode, Options, is_dir_empty, is_readable_metadata, prompt_descend, prompt_dir,
18+
prompt_file, remove_file, show_permission_denied_error, show_removal_error,
19+
verbose_removed_directory, verbose_removed_file,
20+
};
21+
22+
/// Whether the given file or directory is readable.
23+
pub fn is_readable(path: &Path) -> bool {
24+
match fs::metadata(path) {
25+
Err(_) => false,
26+
Ok(metadata) => is_readable_metadata(&metadata),
27+
}
28+
}
29+
30+
/// Helper function to remove directory handling special cases
31+
pub fn remove_dir_with_special_cases(path: &Path, options: &Options, error_occurred: bool) -> bool {
32+
match fs::remove_dir(path) {
33+
Err(_) if !error_occurred && !is_readable(path) => {
34+
// For compatibility with GNU test case
35+
// `tests/rm/unread2.sh`, show "Permission denied" in this
36+
// case instead of "Directory not empty".
37+
show_permission_denied_error(path);
38+
true
39+
}
40+
Err(_) if !error_occurred && path.read_dir().is_err() => {
41+
// For compatibility with GNU test case on Linux
42+
// Check if directory is readable by attempting to read it
43+
show_permission_denied_error(path);
44+
true
45+
}
46+
Err(e) if !error_occurred => {
47+
// Check if directory is readable - if not, show permission denied
48+
// for compatibility with GNU rm behavior
49+
if is_readable(path) {
50+
show_removal_error(e, path)
51+
} else {
52+
show_permission_denied_error(path);
53+
true
54+
}
55+
}
56+
Err(_) => {
57+
// If we already had errors while
58+
// trying to remove the children, then there is no need to
59+
// show another error message as we return from each level
60+
// of the recursion.
61+
error_occurred
62+
}
63+
Ok(_) => {
64+
verbose_removed_directory(path, options);
65+
false
66+
}
67+
}
68+
}
69+
70+
pub fn safe_remove_dir_recursive(path: &Path, options: &Options) -> bool {
71+
// Base case 1: this is a file or a symbolic link.
72+
if !path.is_dir() || path.is_symlink() {
73+
return remove_file(path, options);
74+
}
75+
76+
// Try to open the directory using DirFd for secure traversal
77+
let dir_fd = match DirFd::open(path) {
78+
Ok(fd) => fd,
79+
Err(e) => {
80+
// If we can't open the directory for safe traversal,
81+
// handle the error appropriately and try to remove if possible
82+
if e.kind() == std::io::ErrorKind::PermissionDenied {
83+
// Try to remove the directory directly if it's empty
84+
match fs::remove_dir(path) {
85+
Ok(_) => {
86+
verbose_removed_directory(path, options);
87+
return false;
88+
}
89+
Err(_remove_err) => {
90+
// If we can't read the directory AND can't remove it,
91+
// show permission denied error for GNU compatibility
92+
return show_permission_denied_error(path);
93+
}
94+
}
95+
}
96+
return show_removal_error(e, path);
97+
}
98+
};
99+
100+
let error = safe_remove_dir_recursive_impl(path, &dir_fd, options);
101+
102+
// After processing all children, remove the directory itself
103+
if error {
104+
error
105+
} else {
106+
// Ask user permission if needed
107+
if options.interactive == InteractiveMode::Always && !prompt_dir(path, options) {
108+
return false;
109+
}
110+
111+
// Before trying to remove the directory, check if it's actually empty
112+
// This handles the case where some children weren't removed due to user "no" responses
113+
if !is_dir_empty(path) {
114+
// Directory is not empty, so we can't/shouldn't remove it
115+
// In interactive mode, this might be expected if user said "no" to some children
116+
// In non-interactive mode, this indicates an error (some children couldn't be removed)
117+
if options.interactive == InteractiveMode::Always {
118+
return false;
119+
}
120+
// Try to remove the directory anyway and let the system tell us why it failed
121+
// Use false for error_occurred since this is the main error we want to report
122+
return remove_dir_with_special_cases(path, options, false);
123+
}
124+
125+
// Directory is empty and user approved removal
126+
remove_dir_with_special_cases(path, options, error)
127+
}
128+
}
129+
130+
pub fn safe_remove_dir_recursive_impl(path: &Path, dir_fd: &DirFd, options: &Options) -> bool {
131+
// Read directory entries using safe traversal
132+
let entries = match dir_fd.read_dir() {
133+
Ok(entries) => entries,
134+
Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => {
135+
// This is not considered an error - just like the original
136+
return false;
137+
}
138+
Err(e) => {
139+
if !options.force {
140+
show_error!(
141+
"{}",
142+
e.map_err_context(
143+
|| translate!("rm-error-cannot-remove", "file" => path.quote())
144+
)
145+
);
146+
}
147+
return !options.force;
148+
}
149+
};
150+
151+
let mut error = false;
152+
153+
// Process each entry
154+
for entry_name in entries {
155+
let entry_path = path.join(&entry_name);
156+
157+
// Get metadata for the entry using fstatat
158+
let entry_stat = match dir_fd.stat_at(&entry_name, false) {
159+
Ok(stat) => stat,
160+
Err(e) => {
161+
if !options.force {
162+
let e = e.map_err_context(
163+
|| translate!("rm-error-cannot-remove", "file" => entry_path.quote()),
164+
);
165+
show_error!("{e}");
166+
}
167+
error = !options.force;
168+
continue;
169+
}
170+
};
171+
172+
// Check if it's a directory
173+
let is_dir = (entry_stat.st_mode & libc::S_IFMT) == libc::S_IFDIR;
174+
175+
if is_dir {
176+
// Ask user if they want to descend into this directory
177+
if options.interactive == InteractiveMode::Always
178+
&& !is_dir_empty(&entry_path)
179+
&& !prompt_descend(&entry_path)
180+
{
181+
continue;
182+
}
183+
184+
// Recursively remove subdirectory using safe traversal
185+
let child_dir_fd = match dir_fd.open_subdir(&entry_name) {
186+
Ok(fd) => fd,
187+
Err(e) => {
188+
// If we can't open the subdirectory for safe traversal,
189+
// try to handle it as best we can with safe operations
190+
if e.kind() == std::io::ErrorKind::PermissionDenied {
191+
// For permission denied, try to remove the directory directly
192+
// if it's empty, or show appropriate error
193+
if let Err(remove_err) = dir_fd.unlink_at(&entry_name, true) {
194+
if !options.force {
195+
let remove_err = remove_err.map_err_context(
196+
|| translate!("rm-error-cannot-remove", "file" => entry_path.quote()),
197+
);
198+
show_error!("{remove_err}");
199+
}
200+
error = !options.force;
201+
} else {
202+
verbose_removed_directory(&entry_path, options);
203+
}
204+
} else {
205+
// For other errors, report and continue
206+
if !options.force {
207+
let e = e.map_err_context(
208+
|| translate!("rm-error-cannot-remove", "file" => entry_path.quote()),
209+
);
210+
show_error!("{e}");
211+
}
212+
error = !options.force;
213+
}
214+
continue;
215+
}
216+
};
217+
218+
let child_error = safe_remove_dir_recursive_impl(&entry_path, &child_dir_fd, options);
219+
error = error || child_error;
220+
221+
// Ask user permission if needed for this subdirectory
222+
if !child_error
223+
&& options.interactive == InteractiveMode::Always
224+
&& !prompt_dir(&entry_path, options)
225+
{
226+
continue;
227+
}
228+
229+
// Remove the now-empty subdirectory using safe unlinkat
230+
if !child_error {
231+
if let Err(e) = dir_fd.unlink_at(&entry_name, true) {
232+
let e = e.map_err_context(
233+
|| translate!("rm-error-cannot-remove", "file" => entry_path.quote()),
234+
);
235+
show_error!("{e}");
236+
error = true;
237+
} else {
238+
verbose_removed_directory(&entry_path, options);
239+
}
240+
}
241+
} else {
242+
// Remove file - check if user wants to remove it first
243+
if prompt_file(&entry_path, options) {
244+
if let Err(e) = dir_fd.unlink_at(&entry_name, false) {
245+
let e = e.map_err_context(
246+
|| translate!("rm-error-cannot-remove", "file" => entry_path.quote()),
247+
);
248+
show_error!("{e}");
249+
error = true;
250+
} else {
251+
verbose_removed_file(&entry_path, options);
252+
}
253+
}
254+
}
255+
}
256+
257+
error
258+
}

0 commit comments

Comments
 (0)