Skip to content

Conversation

@shskwmt
Copy link
Contributor

@shskwmt shskwmt commented Mar 25, 2025

Fixes #7011

Implement --one-file-system and --preserve-root=all options for the rm command.

Comment on lines 565 to 580
match validate_single_filesystem(path) {
Ok(()) => true,
Err(additional_reason) => {
if !additional_reason.is_empty() {
show_error!("{}", additional_reason);
}
show_error!(
"skipping {}, since it's on a different device",
path.quote()
);
if options.preserve_root == PreserveRoot::YesAll {
show_error!("and --preserve-root=all is in effect");
}
false
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can probably be simplified with something like:

Suggested change
match validate_single_filesystem(path) {
Ok(()) => true,
Err(additional_reason) => {
if !additional_reason.is_empty() {
show_error!("{}", additional_reason);
}
show_error!(
"skipping {}, since it's on a different device",
path.quote()
);
if options.preserve_root == PreserveRoot::YesAll {
show_error!("and --preserve-root=all is in effect");
}
false
}
}
let result = validate_single_filesystem(path);
if result.is_ok() {
return true;
}
if let Err(additional_reason) = result {
if !additional_reason.is_empty() {
show_error!("{}", additional_reason);
}
}
show_error!(
"skipping {}, since it's on a different device",
path.quote()
);
if options.preserve_root == PreserveRoot::YesAll {
show_error!("and --preserve-root=all is in effect");
}
false

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for pointing that out! I've implemented the suggested simplification.

c091369

@github-actions
Copy link

GNU testsuite comparison:

Skip an intermittent issue tests/misc/stdbuf (fails in this run but passes in the 'main' branch)
Skipping an intermittent issue tests/timeout/timeout (passes in this run but fails in the 'main' branch)

@sylvestre
Copy link
Contributor

some jobs are failing :)

@github-actions
Copy link

GNU testsuite comparison:

Skip an intermittent issue tests/timeout/timeout (fails in this run but passes in the 'main' branch)
Skipping an intermittent issue tests/misc/stdbuf (passes in this run but fails in the 'main' branch)

@github-actions
Copy link

GNU testsuite comparison:

Skipping an intermittent issue tests/misc/stdbuf (passes in this run but fails in the 'main' branch)

@sylvestre
Copy link
Contributor

does
bash -v util/run-gnu-test.sh tests/rm/one-file-system.sh
passes on your system ?

@github-actions
Copy link

GNU testsuite comparison:

Skip an intermittent issue tests/misc/stdbuf (fails in this run but passes in the 'main' branch)

@shskwmt
Copy link
Contributor Author

shskwmt commented Mar 29, 2025

The following is the test result. It has passed.

PASS: tests/rm/one-file-system.sh
============================================================================
Testsuite summary for GNU coreutils 9.6-dirty
============================================================================
# TOTAL: 1
# PASS:  1
# SKIP:  0
# XFAIL: 0
# FAIL:  0
# XPASS: 0
# ERROR: 0
============================================================================

@shskwmt
Copy link
Contributor Author

shskwmt commented Mar 30, 2025

I tried again and it failed, so I will fix it.

FAIL: tests/rm/one-file-system
==============================

--- exp 2025-03-31 00:11:32.336030079 +0900
+++ out 2025-03-31 00:11:32.336030079 +0900
@@ -1 +1 @@
-rm: skipping 'a', since it's on a different device
+rm: cannot remove 'a/b': Device or resource busy
FAIL tests/rm/one-file-system.sh (exit status: 1)

============================================================================
Testsuite summary for GNU coreutils 9.6-dirty
============================================================================
# TOTAL: 1
# PASS:  0
# SKIP:  0
# XFAIL: 0
# FAIL:  1
# XPASS: 0
# ERROR: 0
============================================================================
See ./tests/test-suite.log for debugging.
Some test(s) failed.  Please report this to bug-coreutils@gnu.org,
together with the test-suite.log file (gzipped) and your system
information.  Thanks.
============================================================================

@github-actions
Copy link

github-actions bot commented Apr 3, 2025

GNU testsuite comparison:

GNU test failed: tests/chgrp/default-no-deref. tests/chgrp/default-no-deref is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/chgrp/recurse. tests/chgrp/recurse is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/chmod/thru-dangling. tests/chmod/thru-dangling is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/chown/deref. tests/chown/deref is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/chown/preserve-root. tests/chown/preserve-root is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/cp/abuse. tests/cp/abuse is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/cp/attr-existing. tests/cp/attr-existing is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/cp/cp-HL. tests/cp/cp-HL is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/cp/cross-dev-symlink. tests/cp/cross-dev-symlink is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/cp/keep-directory-symlink. tests/cp/keep-directory-symlink is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/cp/link-deref. tests/cp/link-deref is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/cp/link-no-deref. tests/cp/link-no-deref is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/cp/link-preserve. tests/cp/link-preserve is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/cp/link-symlink. tests/cp/link-symlink is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/cp/no-deref-link1. tests/cp/no-deref-link1 is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/cp/no-deref-link2. tests/cp/no-deref-link2 is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/cp/no-deref-link3. tests/cp/no-deref-link3 is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/cp/preserve-slink-time. tests/cp/preserve-slink-time is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/cp/r-vs-symlink. tests/cp/r-vs-symlink is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/cp/same-file. tests/cp/same-file is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/cp/slink-2-slink. tests/cp/slink-2-slink is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/cp/thru-dangling. tests/cp/thru-dangling is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/dd/misc. tests/dd/misc is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/du/deref. tests/du/deref is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/du/deref-args. tests/du/deref-args is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/du/long-sloop. tests/du/long-sloop is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/du/no-deref. tests/du/no-deref is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/du/one-file-system. tests/du/one-file-system is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/du/trailing-slash. tests/du/trailing-slash is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/env/env. tests/env/env is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/ln/hard-to-sym. tests/ln/hard-to-sym is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/ln/relative. tests/ln/relative is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/ln/sf-1. tests/ln/sf-1 is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/ln/target-1. tests/ln/target-1 is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/ls/classify. tests/ls/classify is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/ls/dangle. tests/ls/dangle is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/ls/dired. tests/ls/dired is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/ls/file-type. tests/ls/file-type is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/ls/follow-slink. tests/ls/follow-slink is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/ls/group-dirs. tests/ls/group-dirs is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/ls/hyperlink. tests/ls/hyperlink is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/ls/no-arg. tests/ls/no-arg is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/ls/selinux-segfault. tests/ls/selinux-segfault is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/ls/slink-acl. tests/ls/slink-acl is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/ls/stat-dtype. tests/ls/stat-dtype is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/ls/stat-failed. tests/ls/stat-failed is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/ls/stat-free-color. tests/ls/stat-free-color is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/ls/symlink-loop. tests/ls/symlink-loop is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/ls/symlink-quote. tests/ls/symlink-quote is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/misc/realpath. tests/misc/realpath is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/mv/atomic. tests/mv/atomic is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/mv/part-symlink. tests/mv/part-symlink is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/mv/symlink-onto-hardlink-to-self. tests/mv/symlink-onto-hardlink-to-self is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/pwd/pwd-option. tests/pwd/pwd-option is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/readlink/can-e. tests/readlink/can-e is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/readlink/can-f. tests/readlink/can-f is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/readlink/can-m. tests/readlink/can-m is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/readlink/multi. tests/readlink/multi is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/readlink/readlink-fp-loop. tests/readlink/readlink-fp-loop is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/readlink/readlink-root. tests/readlink/readlink-root is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/readlink/rl-1. tests/readlink/rl-1 is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/rm/ext3-perf. tests/rm/ext3-perf is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/rm/fail-eacces. tests/rm/fail-eacces is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/rm/rm2. tests/rm/rm2 is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/rm/rm3. tests/rm/rm3 is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/rmdir/symlink-errors. tests/rmdir/symlink-errors is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/stat/stat-slash. tests/stat/stat-slash is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/touch/no-dereference. tests/touch/no-dereference is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/touch/trailing-slash. tests/touch/trailing-slash is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/truncate/truncate-dangling-symlink. tests/truncate/truncate-dangling-symlink is passing on 'main'. Maybe you have to rebase?
Skipping an intermittent issue tests/timeout/timeout (passes in this run but fails in the 'main' branch)
Congrats! The gnu test tests/rm/one-file-system is no longer failing!

@github-actions
Copy link

github-actions bot commented Apr 3, 2025

GNU testsuite comparison:

Skipping an intermittent issue tests/timeout/timeout (passes in this run but fails in the 'main' branch)
Congrats! The gnu test tests/rm/one-file-system is no longer failing!

@sylvestre sylvestre force-pushed the rm-one-file-system branch from 2b9e697 to 3882f38 Compare April 4, 2025 16:49
@github-actions
Copy link

github-actions bot commented Apr 4, 2025

GNU testsuite comparison:

Skipping an intermittent issue tests/misc/stdbuf (passes in this run but fails in the 'main' branch)
Skipping an intermittent issue tests/timeout/timeout (passes in this run but fails in the 'main' branch)
Congrats! The gnu test tests/rm/one-file-system is no longer failing!

@sylvestre
Copy link
Contributor

Sorry, i think i made a mistake with the conflict resolution :( could you please fix it? thanks

@shskwmt
Copy link
Contributor Author

shskwmt commented May 4, 2025

No warries, I'll fix it.
Thanks for reaching out!

@shskwmt shskwmt force-pushed the rm-one-file-system branch from 5e09b83 to 42a24fb Compare May 7, 2025 14:43
@github-actions
Copy link

github-actions bot commented May 7, 2025

GNU testsuite comparison:

Skip an intermittent issue tests/timeout/timeout (fails in this run but passes in the 'main' branch)
Skipping an intermittent issue tests/misc/tee (passes in this run but fails in the 'main' branch)
Congrats! The gnu test tests/rm/one-file-system is no longer failing!

@sylvestre
Copy link
Contributor

sorry, i missed it :(
it needs to be rebased, could you please do this? sorry :(

@shskwmt shskwmt force-pushed the rm-one-file-system branch from 42a24fb to f4adf86 Compare December 6, 2025 09:54
@github-actions
Copy link

github-actions bot commented Dec 6, 2025

GNU testsuite comparison:

Congrats! The gnu test tests/du/files0-from is no longer failing!

@shskwmt shskwmt force-pushed the rm-one-file-system branch from f4adf86 to 5dd9c19 Compare December 6, 2025 10:25
This comment enhances the safety of recursive deletion in `rm` by introducing two key features to
prevent accidental data loss.

`--one-file-system`: When specified, `rm` will not traverse into directories that are on a
different file system from the one on which the traversal began, this prevents `rm -r` from
accidentally deleting data on mounted volumes.

`--preserve-root=all`: The `--preserve-root` option is enhanced to accept an optional argument,
`all`. When set to `all`, `rm` will refuse to remove any directory that is a mount point. The
default behavior (`--preserve-root` without an argument) remains, protecting only the root
directory (`/`).
@shskwmt shskwmt force-pushed the rm-one-file-system branch from 5dd9c19 to 8d0a839 Compare December 6, 2025 10:36
@github-actions
Copy link

github-actions bot commented Dec 6, 2025

GNU testsuite comparison:

Skipping an intermittent issue tests/tail/overlay-headers (passes in this run but fails in the 'main' branch)

@shskwmt
Copy link
Contributor Author

shskwmt commented Dec 6, 2025

I've finished rebasing the branch.
Could you please review it?

} else {
match matches
.get_one::<String>(OPT_PRESERVE_ROOT)
.unwrap()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please remove the unwrap()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed:
a0df78a

.map_err(|err| format!("cannot canonicalize {}: {err}", path.quote()))?;

// Get parent path, handling root case
let parent_canon = child_canon.parent().ok_or("")?.to_path_buf();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we avoid the empty strings in ok_or(") ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed:
2617af7

fn handle_dir(path: &Path, options: &Options, progress_bar: Option<&ProgressBar>) -> bool {
let mut had_err = false;

if let Err(additional_reason) = check_one_fs(path, options) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same code as in line 621, please dedup

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed:
f1c3436

@ChrisDryden
Copy link
Collaborator

I see the Smack tests are failing in the CI here and it appears that its due to no space left on device. I'm guessing its caused when downloading the arch linux img

@codspeed-hq
Copy link

codspeed-hq bot commented Jan 19, 2026

Merging this PR will not alter performance

✅ 142 untouched benchmarks
⏩ 180 skipped benchmarks1


Comparing shskwmt:rm-one-file-system (4556d29) with main (cbbff30)

Open in CodSpeed

Footnotes

  1. 180 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

- Replace `.unwrap()` with `.map()` to handle options safely without risking panics.
- Update the abbreviation check to compare against the `PreserveRoot` enum directly.
@github-actions
Copy link

GNU testsuite comparison:

Skip an intermittent issue tests/tty/tty-eof (fails in this run but passes in the 'main' branch)

Replace empty error strings with descriptive messages when path parent
or mount point resolution fails. This aids in debugging by identifying
specifically which path caused the resolution failure during the
one-file-system check.
@github-actions
Copy link

GNU testsuite comparison:

Skip an intermittent issue tests/tail/follow-name (fails in this run but passes in the 'main' branch)
Congrats! The gnu test tests/dd/stderr is no longer failing!
Congrats! The gnu test tests/tac/tac-2-nonseekable is no longer failing!
Congrats! The gnu test tests/tail/follow-stdin is no longer failing!

Introduce `check_and_report_one_fs` to encapsulate the logic for
verifying filesystem boundaries (`-x`) and root preservation.

This removes code duplication between `remove_dir_recursive` and
`handle_dir`, ensuring consistent error reporting for "different device"
skips and `--preserve-root=all` violations.
@github-actions
Copy link

GNU testsuite comparison:

Skip an intermittent issue tests/tail/follow-name (fails in this run but passes in the 'main' branch)
Congrats! The gnu test tests/dd/stderr is no longer failing!
Congrats! The gnu test tests/tac/tac-2-nonseekable is no longer failing!
Congrats! The gnu test tests/tail/follow-stdin is no longer failing!

Split the initialization of `stat_path` in `Filesystem::new` into distinct
`#[cfg(unix)]` and `#[cfg(windows)]` blocks. This improves readability and
ensures that Windows builds consistently use the volume ID (`dev_id`).
@github-actions
Copy link

GNU testsuite comparison:

Skip an intermittent issue tests/tail/follow-name (fails in this run but passes in the 'main' branch)
Congrats! The gnu test tests/dd/stderr is no longer failing!
Congrats! The gnu test tests/tac/tac-2-nonseekable is no longer failing!
Congrats! The gnu test tests/tail/follow-stdin is no longer failing!

@github-actions
Copy link

GNU testsuite comparison:

Skipping an intermittent issue tests/shuf/shuf-reservoir (passes in this run but fails in the 'main' branch)
Skipping an intermittent issue tests/sort/sort-stale-thread-mem (passes in this run but fails in the 'main' branch)

@sylvestre
Copy link
Contributor

@shskwmt

any idea why we don't get "Congrats! The gnu test tests/rm/one-file-system is no longer failing!" anymore? thanks

@ChrisDryden
Copy link
Collaborator

It appears that one of the error messages is returning differently in the test:

--- exp	2026-01-21 10:59:43.543481609 +0000
+++ out	2026-01-21 10:59:43.546481572 +0000
@@ -1 +1 @@
-rm: skipping 'a/b', since it's on a different device
+rm: cannot remove 'a/b': Device or resource busy
FAIL tests/rm/one-file-system.sh (exit status: 1)

@ChrisDryden
Copy link
Collaborator

I'm taking a deeper look into the implementation and I'm curious about the decision to use the mount tables. The when using stat on the files you are able to get the device id from the st_dev value.

When reading from the mount tables it brings in the potential for a race condition and that appears to be whats causing the GNU tests to fail.

@ChrisDryden
Copy link
Collaborator

Would you be interested if I cherry-pick your PR and just modify the mount part to swap with the stat? Sorry that the feedback is getting there so much later than when you made the PR, but was wondering if this was something you were still interested in following up on?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

rm --one-file-system should fail wih rm: skipping 'a/b', since it's on a different device

3 participants