From: Pádraig Brady
Date: Wed, 22 Jul 2026 18:23:26 +0000 (+0100)
Subject: chcon,chgrp,chmod,chown,du,ls: ignore missing files while traversing
X-Git-Url: http://git.ipfire.org/gitweb/?a=commitdiff_plain;h=30866b30e2d32b7cc8e2eea3719aae995ffccbd3;p=thirdparty%2Fcoreutils.git
chcon,chgrp,chmod,chown,du,ls: ignore missing files while traversing
* src/fts-missing.h: Add helper routines to determine
if an FTS entry is skippable. I.e., that is traversed
and not specified on the command line.
* src/chcon.c (process_file): Setup to ignore skippable files.
(change_file_context): Ignore missing files if appropriate.
* src/chmod.c (process_file): Setup to ignore skippable files.
Ignore missing files if appropriate.
(mode_changed): Ignore missing files if appropriate.
* src/chown-core.c (change_file_owner): Setup to ignore skippable files.
Ignore missing files if appropriate.
* src/du.c (process_file): Likewise.
* src/ls.c (print_dir): Ignore missing files if appropriate.
(get_link_name): Likewise.
(gobble_file): Likewise. Also handle the dangling symlink case.
* src/system.h (ignorable_traversal_errno): Match ENOENT for missing
files.
* tests/chcon/traversal-missing.sh: A new test.
* tests/misc/traversal-missing.sh: Likewise.
* tests/local.mk: Reference the new tests.
* NEWS: Mention the bug fix.
Fixes https://bugs.gnu.org/81444
---
diff --git a/NEWS b/NEWS
index 73c7af9d1b..76354e9e9c 100644
--- a/NEWS
+++ b/NEWS
@@ -4,6 +4,10 @@ GNU coreutils NEWS -*- outline -*-
** Bug fixes
+ 'chcon', 'chgrp', 'chmod', 'chown', 'du', 'ls' which traverse hierarchies with
+ -R, no longer fail merely because files may be being removed in parallel.
+ [This bug was present in "the beginning".]
+
'comm - -' no longer closes standard input twice. Previously it would
mistakenly exit with a nonzero status.
[This bug was present in "the beginning".]
diff --git a/src/chcon.c b/src/chcon.c
index 91b78148d0..4ab2d75701 100644
--- a/src/chcon.c
+++ b/src/chcon.c
@@ -22,6 +22,7 @@
#include "system.h"
#include "dev-ino.h"
+#include "fts-missing.h"
#include "ignore-value.h"
#include "quote.h"
#include "root-dev-ino.h"
@@ -137,7 +138,7 @@ compute_context_from_mask (char const *context, context_t *ret)
Return 0 if successful, 1 if errors occurred. */
static int
-change_file_context (int fd, char const *file)
+change_file_context (int fd, char const *file, bool ignore_missing)
{
char *file_context = NULL;
context_t context IF_LINT (= NULL);
@@ -152,6 +153,8 @@ change_file_context (int fd, char const *file)
if (status < 0 && errno != ENODATA)
{
+ if (ignore_missing && ignorable_traversal_errno (errno))
+ return 0;
error (0, errno, _("failed to get security context of %s"),
quoteaf (file));
return 1;
@@ -188,9 +191,12 @@ change_file_context (int fd, char const *file)
if (fail)
{
- errors = 1;
- error (0, errno, _("failed to change context of %s to %s"),
- quoteaf_n (0, file), quote_n (1, context_string));
+ if (! (ignore_missing && ignorable_traversal_errno (errno)))
+ {
+ errors = 1;
+ error (0, errno, _("failed to change context of %s to %s"),
+ quoteaf_n (0, file), quote_n (1, context_string));
+ }
}
}
@@ -214,6 +220,10 @@ process_file (FTS *fts, FTSENT *ent)
char const *file = ent->fts_accpath;
const struct stat *file_stats = ent->fts_statp;
bool ok = true;
+ bool ignore_missing = ignore_missing_fts_entry (ent);
+
+ if (ignore_missing && ignorable_fts_error (ent))
+ return true;
switch (ent->fts_info)
{
@@ -295,7 +305,7 @@ process_file (FTS *fts, FTSENT *ent)
printf (_("changing security context of %s\n"),
quoteaf (file_full_name));
- if (change_file_context (fts->fts_cwd_fd, file) != 0)
+ if (change_file_context (fts->fts_cwd_fd, file, ignore_missing) != 0)
ok = false;
}
diff --git a/src/chmod.c b/src/chmod.c
index 8b57172707..68f37e1810 100644
--- a/src/chmod.c
+++ b/src/chmod.c
@@ -25,6 +25,7 @@
#include "assure.h"
#include "dev-ino.h"
#include "filemode.h"
+#include "fts-missing.h"
#include "ignore-value.h"
#include "modechange.h"
#include "quote.h"
@@ -125,8 +126,10 @@ static struct option const long_options[] =
static bool
mode_changed (int dir_fd, char const *file, char const *file_full_name,
- mode_t old_mode, mode_t new_mode)
+ mode_t old_mode, mode_t new_mode, bool ignore_missing,
+ bool *file_missing)
{
+ *file_missing = false;
if (new_mode & (S_ISUID | S_ISGID | S_ISVTX))
{
/* The new mode contains unusual bits that the call to chmod may
@@ -136,7 +139,9 @@ mode_changed (int dir_fd, char const *file, char const *file_full_name,
if (fstatat (dir_fd, file, &new_stats, 0) != 0)
{
- if (! force_silent)
+ *file_missing = (ignore_missing
+ && ignorable_traversal_errno (errno));
+ if (! *file_missing && ! force_silent)
error (0, errno, _("getting new attributes of %s"),
quoteaf (file_full_name));
return false;
@@ -215,6 +220,10 @@ process_file (FTS *fts, FTSENT *ent)
struct change_status ch = {0};
ch.status = CH_NO_STAT;
struct stat stat_buf;
+ bool ignore_missing = ignore_missing_fts_entry (ent);
+
+ if (ignore_missing && ignorable_fts_error (ent))
+ return true;
switch (ent->fts_info)
{
@@ -267,6 +276,9 @@ process_file (FTS *fts, FTSENT *ent)
{
if (fstatat (fts->fts_cwd_fd, file, &stat_buf, 0) != 0)
{
+ if (ignore_missing
+ && ignorable_traversal_errno (errno))
+ return true;
if (! force_silent)
error (0, errno, _("cannot dereference %s"),
quoteaf (file_full_name));
@@ -314,6 +326,9 @@ process_file (FTS *fts, FTSENT *ent)
ch.status = CH_SUCCEEDED;
else
{
+ if (ignore_missing
+ && ignorable_traversal_errno (errno))
+ return true;
if (! is_ENOTSUP (errno))
{
if (! force_silent)
@@ -328,10 +343,16 @@ process_file (FTS *fts, FTSENT *ent)
if (verbosity != V_off)
{
+ bool file_missing;
if (ch.status == CH_SUCCEEDED
&& !mode_changed (fts->fts_cwd_fd, file, file_full_name,
- ch.old_mode, ch.new_mode))
- ch.status = CH_NO_CHANGE_REQUESTED;
+ ch.old_mode, ch.new_mode, ignore_missing,
+ &file_missing))
+ {
+ if (file_missing)
+ return true;
+ ch.status = CH_NO_CHANGE_REQUESTED;
+ }
if (ch.status == CH_SUCCEEDED || verbosity == V_high)
describe_change (file_full_name, &ch);
diff --git a/src/chown-core.c b/src/chown-core.c
index e5e355fdb8..21f1e3d97a 100644
--- a/src/chown-core.c
+++ b/src/chown-core.c
@@ -25,6 +25,7 @@
#include "system.h"
#include "assure.h"
#include "chown-core.h"
+#include "fts-missing.h"
#include "ignore-value.h"
#include "root-dev-ino.h"
#include "xfts.h"
@@ -284,6 +285,10 @@ change_file_owner (FTS *fts, FTSENT *ent,
char const *file_full_name = ent->fts_path;
char const *file = ent->fts_accpath;
bool ok = true;
+ bool ignore_missing = ignore_missing_fts_entry (ent);
+
+ if (ignore_missing && ignorable_fts_error (ent))
+ return true;
switch (ent->fts_info)
{
@@ -381,6 +386,9 @@ change_file_owner (FTS *fts, FTSENT *ent,
{
if (fstatat (fts->fts_cwd_fd, file, &stat_buf, 0) != 0)
{
+ if (ignore_missing
+ && ignorable_traversal_errno (errno))
+ return true;
if (! chopt->force_silent)
error (0, errno, _("cannot dereference %s"),
quoteaf (file_full_name));
@@ -469,11 +477,17 @@ change_file_owner (FTS *fts, FTSENT *ent,
by some other user and operating on files in a directory
where M has write access. */
- if (do_chown && !ok && ! chopt->force_silent)
- error (0, errno, (uid != (uid_t) -1
- ? _("changing ownership of %s")
- : _("changing group of %s")),
- quoteaf (file_full_name));
+ if (do_chown && !ok)
+ {
+ if (ignore_missing
+ && ignorable_traversal_errno (errno))
+ return true;
+ if (! chopt->force_silent)
+ error (0, errno, (uid != (uid_t) -1
+ ? _("changing ownership of %s")
+ : _("changing group of %s")),
+ quoteaf (file_full_name));
+ }
}
if (chopt->verbosity != V_off)
diff --git a/src/du.c b/src/du.c
index 9b80633711..eefb24f05a 100644
--- a/src/du.c
+++ b/src/du.c
@@ -32,6 +32,7 @@
#include "assure.h"
#include "di-set.h"
#include "exclude.h"
+#include "fts-missing.h"
#include "human.h"
#include "mountlist.h"
#include "quote.h"
@@ -554,6 +555,10 @@ process_file (FTS *fts, FTSENT *ent)
char const *file = ent->fts_path;
const struct stat *sb = ent->fts_statp;
int info = ent->fts_info;
+ bool ignore_missing = ignore_missing_fts_entry (ent);
+
+ if (ignore_missing && ignorable_fts_error (ent))
+ return true;
if (info == FTS_DNR)
{
@@ -574,6 +579,9 @@ process_file (FTS *fts, FTSENT *ent)
MAYBE_UNUSED FTSENT const *e = fts_read (fts);
affirm (e == ent);
info = ent->fts_info;
+
+ if (ignore_missing && ignorable_fts_error (ent))
+ return true;
}
if (info == FTS_NS || info == FTS_SLNONE)
diff --git a/src/fts-missing.h b/src/fts-missing.h
new file mode 100644
index 0000000000..cb007bca9d
--- /dev/null
+++ b/src/fts-missing.h
@@ -0,0 +1,39 @@
+/* fts-missing.h -- helpers for files missing during an FTS traversal.
+
+ Copyright (C) 2026 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