Skip to content

find: implement -ignore_readdir_race and -noignore_readdir_race - #903

Open
DominicThemiya wants to merge 4 commits into
uutils:mainfrom
DominicThemiya:ignore-readdir-race
Open

DominicThemiya wants to merge 4 commits into
uutils:mainfrom
DominicThemiya:ignore-readdir-race

Conversation

@DominicThemiya

@DominicThemiya DominicThemiya commented Oct 13, 2026 •

Copy link
Copy Markdown

Closes #377

What

find rejected both options as unknown predicates:

$ find dir -ignore_readdir_race -name a
find: unknown predicate `-ignore_readdir_race'

This adds them as global options, matching GNU find 4.11.0:

  • They apply to the whole expression wherever they appear.
  • The last of -ignore_readdir_race / -noignore_readdir_race wins.
  • With -ignore_readdir_race, a file or directory that vanishes after its parent directory was read is not reported during the walk.
  • With -ignore_readdir_race, -delete on a file that has already disappeared prints nothing and evaluates to true.
  • Starting points that don't exist are still reported, as GNU find does.
$ mkdir -p d/b && touch d/a
$ find d -sorted -name a -exec rm -rf d/b \; -print
d/a
Error: d/b: No such file or directory (os error 2)                  # exit 1, unchanged
$ mkdir d/b
$ find d -ignore_readdir_race -sorted -name a -exec rm -rf d/b \; -print
d/a                                                                 # exit 0

How

  • Config gets an ignore_readdir_race flag, set while parsing the expression.
  • In process_dir, a walk error is skipped when the flag is set, is_not_found() is true and the depth is greater than 0, so starting points are still reported.
  • process_dir passes the flag to every MatcherIO, including the one used for finished_dir/finished, so DeleteMatcher reads it when it runs rather than when it is built. That is what makes the option work when it comes after -delete.
  • DeleteMatcher treats ErrorKind::NotFound as success when the flag is set.
  • Both options are listed in --help.

Not covered

  • GNU also silences the error when a test that needs stat (e.g. -size) hits a vanished file. Here each matcher prints its own error, so that needs a wider change (related to CodeHealth: tidy up Errors #15) and is left for a follow-up.

Tests

  • find::matchers::delete::tests::delete_matcher_vanished_file
  • delete_vanished_file_is_an_error_by_default (tests/test_find.rs)
  • delete_vanished_file_with_ignore_readdir_race (tests/test_find.rs)
  • vanished_dir_during_walk_is_an_error_by_default (tests/test_find.rs)
  • vanished_dir_during_walk_with_ignore_readdir_race (tests/test_find.rs)
  • missing_start_point_still_reported_with_ignore_readdir_race (tests/test_find.rs)

The walk tests use -sorted so that a is always visited before b.

cargo test, cargo fmt --check and cargo clippy --all-targets -- -D warnings pass locally. The GNU testsuite (util/build-gnu.sh, including the DejaGnu tests) gives the same results on main and on this branch: 495 tests, 425 passed, no test changed status.

Both options were rejected as unknown predicates. They are now accepted
as global options: they apply wherever they appear in the expression,
and the last one given wins, as in GNU find.

With -ignore_readdir_race, -delete no longer reports a file that has
already disappeared, and evaluates to true.

Errors from tests that stat a vanished file (e.g. -size) are still
reported; those are printed by each matcher and are left for a follow-up.
@codecov

codecov Bot commented Oct 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.72727% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 92.60%. Comparing base (2b15f5d) to head (6dc7f51).

Files with missing lines Patch % Lines
src/find/matchers/delete.rs 95.00% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #903      +/-   ##
==========================================
+ Coverage   92.52%   92.60%   +0.08%     
==========================================
  Files          36       36              
  Lines        7694     7738      +44     
  Branches      391      392       +1     
==========================================
+ Hits         7119     7166      +47     
+ Misses        437      433       -4     
- Partials      138      139       +1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@codspeed

codspeed Bot commented Oct 13, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

⚠️ 15 benchmarks spent significant time in system calls

System calls cannot be consistently instrumented, so they are not included in the measure, which understates the real cost. Please switch to the Walltime instrument to accurately measure system calls.

Measurement and system calls

✅ 20 untouched benchmarks


Comparing DominicThemiya:ignore-readdir-race (6dc7f51) with main (2b15f5d)

Open in CodSpeed

@github-actions

Copy link
Copy Markdown

Commit 6dc7f51 has test result changes:

bfs testsuite:

Test results comparison:
  Current:   TOTAL: 317 / PASSED: 276 / FAILED: 35 / SKIPPED: 6
  Reference: TOTAL: 317 / PASSED: 274 / FAILED: 37 / SKIPPED: 6

Changes from main branch:
  TOTAL: +0
  PASSED: +2
  FAILED: -2

Test improvements (2):
  + gnu/ignore_readdir_race
  + gnu/ignore_readdir_race_notdir

Comment thread src/find/mod.rs
}
Ok(entry) => {
let mut matcher_io = matchers::MatcherIO::new(deps);
matcher_io.set_ignore_readdir_race(config.ignore_readdir_race);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

what about the Err(err) branch just above?
a file or dir vanishing during the walk ends up there and is still reported with exit 1.
that's the main readdir race case, no? please handle it too (with is_not_found()), or change "Closes" to a reference

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good catch, thanks. Handled in 1fc1ff1: a walk error is now skipped when the option is set, is_not_found() is true and the depth is greater than 0, so missing starting points are still reported, as GNU does. Covered by vanished_dir_during_walk_is_an_error_by_default, vanished_dir_during_walk_with_ignore_readdir_race and missing_start_point_still_reported_with_ignore_readdir_race.

Comment thread src/find/mod.rs
}
Ok(entry) => {
let mut matcher_io = matchers::MatcherIO::new(deps);
matcher_io.set_ignore_readdir_race(config.ignore_readdir_race);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

the MatcherIO built after the loop for finished_dir/finished doesn't get the flag, could you please set it there too?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done in cdcd9e7.

Comment thread src/find/mod.rs Outdated
assert_eq!(
deps.get_output_as_string(),
fix_up_slashes("./test_data/simple/subdir\n./test_data/simple/subdir/ABBBC\n")
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this only checks that the option is parsed, the integration tests already cover it. do we need it?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Agreed, the integration tests cover parsing. Removed in d511206.

Comment thread src/find/mod.rs
-d / -depth Process directory contents before directory itself
-mount / -xdev Do not descend into other file systems
-noleaf Do not optimize by assuming 2+ hard links
-ignore_readdir_race Do not report files that vanish during the search

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

did you run the GNU testsuite for this?
git clone https://git.savannah.gnu.org/git/findutils.git ../findutils.gnu
bash util/build-gnu.sh tests/find/.sh

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Yes, I ran it locally with util/build-gnu.sh (full suite, including the DejaGnu tests) on main and on this branch: 495 tests, 425 passed in both, and no test changed status. GNU's suite has no -ignore_readdir_race test, though; the bfs suite covers it, and CI on the first commit showed gnu/ignore_readdir_race and gnu/ignore_readdir_race_notdir going from failing to passing.

…_race

A file or directory removed after its parent directory was read reaches
the walk error branch and was still reported with exit status 1, even
with -ignore_readdir_race.  Skip such "not found" errors when the option
is set.

Starting points (depth 0) are still reported, as GNU find does.
The MatcherIO built after the walk for finished_dir() and finished()
did not get the flag, unlike the one built for each entry.
The integration tests already pass both options and check their effect.
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.

Implement -ignore_readdir_race and -noignore_readdir_race

2 participants