find: ignore more vanished subdirectories with -ignore_readdir_race

Similar to commit 889d001ab750 which handles vanished files better,
also fix the race for subdirectories (FTS_DNR).

Reproducer:
In average, the following produced 6-10 failures out of 1000 runs:
  $ mkdir testdir
  $ while :; do mkdir testdir/foo; rmdir testdir/foo; done &
  $ for f in $(seq 1000); do \
      find testdir -ignore_readdir_race -ls ; done >/dev/null
  find: 'testdir/foo': No such file or directory
  find: 'testdir/foo': No such file or directory

* find/ftsfind.c (consider_visiting): Return when FTS returned ENOENT
for FTS_DNR, i.e., unreadable directory, with -ignore_readdir_race.
* tests/find/readdir_race.sh: Add test.
* tests/local.mk (sh_tests): Reference it.
* NEWS (Bug Fixes): Amend and improve description of the previous fix.

See also https://savannah.gnu.org/bugs/?45930
This commit is contained in:
Bernhard Voelker 2024-12-21 19:01:10 +01:00
parent eac8aecaad
commit aff4e48c11
4 changed files with 56 additions and 3 deletions

5
NEWS
View File

@ -4,8 +4,9 @@ GNU findutils NEWS - User visible changes. -*- outline -*- (allout)
** Bug Fixes
'find -ignore_readdir_race' now has a race between FTS read and the visiting
of the entry when the file was removed. [#45930]
'find -ignore_readdir_race' now better handles races between FTS reading a
directory and visiting its entries when the file or directory was meanwhile
removed. [#45930]
To fix a POSIX compatibility bug, -exec foo Z{} + is no longer a
complete predicate, because '+' is only a terminator when it follows

View File

@ -304,6 +304,9 @@ consider_visiting (FTS *p, FTSENT *ent)
}
if (ent->fts_info == FTS_DNR)
{
/* Ignore ENOENT error for vanished directories. */
if (ENOENT == ent->fts_errno && options.ignore_readdir_race)
return;
nonfatal_target_file_error (ent->fts_errno, ent->fts_path);
if (options.do_dir_first)
{
@ -355,7 +358,7 @@ consider_visiting (FTS *p, FTSENT *ent)
}
else
{
/* Ignore unlink() error for vanished files. */
/* Ignore ENOENT error for vanished files. */
if (ENOENT == ent->fts_errno && options.ignore_readdir_race)
return;

48
tests/find/readdir_race.sh Executable file
View File

@ -0,0 +1,48 @@
#!/bin/sh
# Verify that -ignore_readdir_race properly handles vanished files/directories.
# Copyright (C) 2024 Free Software Foundation, Inc.
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
. "${srcdir=.}/tests/init.sh"; fu_path_prepend_
print_ver_ find
# Require seq(1) for this test - which may not be available
# on some systems, e.g on some *BSDs.
seq 2 >/dev/null 2>&1 \
|| skip_ "required utility 'seq' missing"
mkdir testdir || framework_failure_
# Constantly create and remove a subdirectory in the background.
# Disable shell debugging for this part.
# Terminate the background process later again.
endless_mkdir_rmdir() {
{ set +x; } 2>/dev/null
while :; do mkdir testdir/foo; rmdir testdir/foo; done
}
endless_mkdir_rmdir & pid=$!
cleanup_() { kill $pid 2>/dev/null && wait $pid; }
# Now run find(1) many times.
> err
for f in $(seq 1000); do \
find testdir -ignore_readdir_race -ls 2>> err || fail=1; \
done > out
test 1000 -le $( wc -l < out ) || fail=1
compare /dev/null err || fail=1
Exit $fail

View File

@ -131,6 +131,7 @@ sh_tests = \
tests/find/newer.sh \
tests/find/opt-numeric-arg.sh \
tests/find/sv-bug-66365-exec.sh \
tests/find/readdir_race.sh \
tests/find/user-group-max.sh \
tests/xargs/conflicting_opts.sh \
tests/xargs/verbose-quote.sh \