ls: check fts_children() for errors that may not surface otherwise

In particular, if one simply does a non-recursive `ls` on a directory
that is not accessible, there are some classes of errors that may cause
it to fail that wouldn't be surfaced unless we do an fts_read() that
will recurse into the inaccessible directory.  Catch those kinds of
errors here since we cannot expect to an FTS_ERR/FTS_DNR entry to follow
up on them.

PR:		287451
Reviewed by:	kib
Discusssed with:	des
Differential Revision:	https://reviews.freebsd.org/D51056
This commit is contained in:
Kyle Evans
2026-02-11 13:55:55 -06:00
parent e89454417b
commit 7bf81e39d8
2 changed files with 47 additions and 0 deletions
+17
View File
@@ -707,6 +707,23 @@ traverse(int argc, char *argv[], int options)
output = 1;
}
chp = fts_children(ftsp, ch_options);
if (chp == NULL && errno != 0) {
warn("%s", p->fts_path);
rval = 1;
/*
* Avoid further errors on this entry. We won't
* always get an FTS_ERR/FTS_DNR for errors
* in fts_children(), because opendir could
* have failed early on and that only flags an
* error for fts_read() when we try to recurse
* into it. We catch both the non-recursive and
* the recursive case here.
*/
(void)fts_set(ftsp, p, FTS_SKIP);
break;
}
display(p, chp, options);
if (!f_recursive && chp != NULL)
+30
View File
@@ -476,6 +476,35 @@ b_flag_body()
atf_check -e empty -o match:'y\\vz' -s exit:0 ls -b
}
atf_test_case childerr
childerr_head()
{
atf_set "descr" "Verify that fts_children() in pre-order errors are checked"
atf_set "require.user" "unprivileged"
}
childerr_body()
{
atf_check mkdir -p root/dir root/edir
atf_check touch root/c
# Check that listing an empty directory hasn't regressed into being
# called an error.
atf_check -o match:"total 0" -e empty ls -l root/dir
atf_check chmod 0 root/dir
# If we did not abort after fts_children() properly, then stdout would
# have an output of the total files enumerated (0). Thus, assert that
# it's empty and that we see the correct error on stderr.
atf_check -s not-exit:0 -e match:"Permission denied" ls -l root/dir
# Now ensure that we didn't just stop there, we printed out a directory
# that would've been enumerated later.
atf_check -s not-exit:0 -o match:"^root/edir" \
-e match:"Permission denied" ls -lR root
}
atf_test_case d_flag
d_flag_head()
{
@@ -971,6 +1000,7 @@ atf_init_test_cases()
#atf_add_test_case Z_flag
atf_add_test_case a_flag
atf_add_test_case b_flag
atf_add_test_case childerr
#atf_add_test_case c_flag
atf_add_test_case d_flag
atf_add_test_case f_flag