Skip to content

Commit fe51531

Browse files
committed
feat(rm): implement --one-file-system and --preserve-root=all
1 parent 2916d2b commit fe51531

6 files changed

Lines changed: 374 additions & 21 deletions

File tree

src/uu/rm/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ path = "src/rm.rs"
2121
thiserror = { workspace = true }
2222
clap = { workspace = true }
2323
uucore = { workspace = true, features = ["fs", "parser", "safe-traversal"] }
24-
fluent = { workspace = true }
2524
indicatif = { workspace = true }
25+
fluent = { workspace = true }
2626

2727
[target.'cfg(unix)'.dependencies]
2828
libc = { workspace = true }

src/uu/rm/locales/en-US.ftl

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,7 @@ rm-help-prompt-once = prompt once before removing more than three files, or when
2121
rm-help-interactive = prompt according to WHEN: never, once (-I), or always (-i). Without WHEN,
2222
prompts always
2323
rm-help-one-file-system = when removing a hierarchy recursively, skip any directory that is on a file
24-
system different from that of the corresponding command line argument (NOT
25-
IMPLEMENTED)
24+
system different from that of the corresponding command line argument
2625
rm-help-no-preserve-root = do not treat '/' specially
2726
rm-help-preserve-root = do not remove '/' (default)
2827
rm-help-recursive = remove directories and their contents recursively
@@ -41,7 +40,9 @@ rm-error-cannot-remove-permission-denied = cannot remove {$file}: Permission den
4140
rm-error-cannot-remove-is-directory = cannot remove {$file}: Is a directory
4241
rm-error-dangerous-recursive-operation = it is dangerous to operate recursively on '/'
4342
rm-error-use-no-preserve-root = use --no-preserve-root to override this failsafe
44-
rm-error-refusing-to-remove-directory = refusing to remove '.' or '..' directory: skipping {$path}
43+
rm-error-refusing-to-remove-directory = refusing to remove '.' or '..' directory: skipping '{$path}'
44+
rm-error-skipping-directory-on-different-device = skipping '{$path}', since it's on a different device
45+
rm-error-preserve-root-all = and --preserve-root=all is in effect
4546
rm-error-cannot-remove = cannot remove {$file}
4647
4748
# Verbose messages

src/uu/rm/locales/fr-FR.ftl

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,7 @@ rm-help-prompt-once = demander une fois avant de supprimer plus de trois fichier
2121
rm-help-interactive = demander selon QUAND : never, once (-I), ou always (-i). Sans QUAND,
2222
demande toujours
2323
rm-help-one-file-system = lors de la suppression récursive d'une hiérarchie, ignorer tout répertoire situé sur un
24-
système de fichiers différent de celui de l'argument de ligne de commande correspondant (NON
25-
IMPLÉMENTÉ)
24+
système de fichiers différent de celui de l'argument de ligne de commande correspondant
2625
rm-help-no-preserve-root = ne pas traiter '/' spécialement
2726
rm-help-preserve-root = ne pas supprimer '/' (par défaut)
2827
rm-help-recursive = supprimer les répertoires et leur contenu récursivement
@@ -41,7 +40,9 @@ rm-error-cannot-remove-permission-denied = impossible de supprimer {$file} : Per
4140
rm-error-cannot-remove-is-directory = impossible de supprimer {$file} : C'est un répertoire
4241
rm-error-dangerous-recursive-operation = il est dangereux d'opérer récursivement sur '/'
4342
rm-error-use-no-preserve-root = utilisez --no-preserve-root pour outrepasser cette protection
44-
rm-error-refusing-to-remove-directory = refus de supprimer le répertoire '.' ou '..' : ignorer {$path}
43+
rm-error-refusing-to-remove-directory = refus de supprimer le répertoire '.' ou '..' : ignorer '{$path}'
44+
rm-error-skipping-directory-on-different-device = ignorer '{$path}', car il se trouve sur un périphérique différent
45+
rm-error-preserve-root-all = et --preserve-root=all est actif
4546
rm-error-cannot-remove = impossible de supprimer {$file}
4647
4748
# Messages verbeux

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

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
use indicatif::ProgressBar;
1111
use std::ffi::OsStr;
1212
use std::fs;
13+
#[cfg(unix)]
14+
use std::os::unix::fs::MetadataExt;
15+
1316
use std::io::{IsTerminal, stdin};
1417
use std::os::unix::fs::PermissionsExt;
1518
use std::path::Path;
@@ -22,8 +25,8 @@ use uucore::translate;
2225

2326
use super::super::{
2427
InteractiveMode, Options, is_dir_empty, is_readable_metadata, prompt_descend, remove_file,
25-
show_permission_denied_error, show_removal_error, verbose_removed_directory,
26-
verbose_removed_file,
28+
should_skip_different_device_with_dev, show_permission_denied_error, show_removal_error,
29+
verbose_removed_directory, verbose_removed_file,
2730
};
2831

2932
#[inline]
@@ -276,6 +279,7 @@ pub fn safe_remove_dir_recursive(
276279
path: &Path,
277280
options: &Options,
278281
progress_bar: Option<&ProgressBar>,
282+
current_dev_id: Option<u64>,
279283
) -> bool {
280284
// Base case 1: this is a file or a symbolic link.
281285
// Use lstat to avoid race condition between check and use
@@ -289,6 +293,16 @@ pub fn safe_remove_dir_recursive(
289293
}
290294
};
291295

296+
let check_device = options.one_fs || options.preserve_root_all;
297+
#[cfg(unix)]
298+
let current_dev_id = if check_device {
299+
current_dev_id.or_else(|| path.symlink_metadata().ok().map(|m| m.dev()))
300+
} else {
301+
None
302+
};
303+
#[cfg(not(unix))]
304+
let current_dev_id: Option<u64> = None;
305+
292306
// Try to open the directory using DirFd for secure traversal
293307
let dir_fd = match DirFd::open(path) {
294308
Ok(fd) => fd,
@@ -309,7 +323,12 @@ pub fn safe_remove_dir_recursive(
309323
}
310324
};
311325

312-
let error = safe_remove_dir_recursive_impl(path, &dir_fd, options);
326+
let error = safe_remove_dir_recursive_impl(
327+
path,
328+
&dir_fd,
329+
options,
330+
if check_device { current_dev_id } else { None },
331+
);
313332

314333
// After processing all children, remove the directory itself
315334
if error {
@@ -345,7 +364,12 @@ pub fn safe_remove_dir_recursive(
345364
}
346365
}
347366

348-
pub fn safe_remove_dir_recursive_impl(path: &Path, dir_fd: &DirFd, options: &Options) -> bool {
367+
pub fn safe_remove_dir_recursive_impl(
368+
path: &Path,
369+
dir_fd: &DirFd,
370+
options: &Options,
371+
parent_dev_id: Option<u64>,
372+
) -> bool {
349373
// Read directory entries using safe traversal
350374
let entries = match dir_fd.read_dir() {
351375
Ok(entries) => entries,
@@ -361,6 +385,7 @@ pub fn safe_remove_dir_recursive_impl(path: &Path, dir_fd: &DirFd, options: &Opt
361385
};
362386

363387
let mut error = false;
388+
let check_device = options.one_fs || options.preserve_root_all;
364389

365390
// Process each entry
366391
for entry_name in entries {
@@ -379,6 +404,17 @@ pub fn safe_remove_dir_recursive_impl(path: &Path, dir_fd: &DirFd, options: &Opt
379404
let is_dir = (entry_stat.st_mode & libc::S_IFMT) == libc::S_IFDIR;
380405

381406
if is_dir {
407+
if check_device
408+
&& should_skip_different_device_with_dev(
409+
parent_dev_id,
410+
entry_stat.st_dev,
411+
options,
412+
&entry_path,
413+
)
414+
{
415+
error = true;
416+
continue;
417+
}
382418
// Ask user if they want to descend into this directory
383419
if options.interactive == InteractiveMode::Always
384420
&& !is_dir_empty(&entry_path)
@@ -407,7 +443,16 @@ pub fn safe_remove_dir_recursive_impl(path: &Path, dir_fd: &DirFd, options: &Opt
407443
}
408444
};
409445

410-
let child_error = safe_remove_dir_recursive_impl(&entry_path, &child_dir_fd, options);
446+
let child_error = safe_remove_dir_recursive_impl(
447+
&entry_path,
448+
&child_dir_fd,
449+
options,
450+
if check_device {
451+
Some(entry_stat.st_dev)
452+
} else {
453+
None
454+
},
455+
);
411456
error = error || child_error;
412457

413458
// Ask user permission if needed for this subdirectory

0 commit comments

Comments
 (0)