git: 74bd6fb1eb02 - main - fts: fix fts_accpath regression after fts_children()

Alan Somers <[email protected]>
Newsgroups gmane.os.freebsd.devel.cvs.src,gmane.os.freebsd.current.scm
Message-ID <[email protected]>
The branch main has been updated by asomers:

URL: https://cgit.FreeBSD.org/src/commit/?id=74bd6fb1eb028d15df407efb62628253dab90aa5

commit 74bd6fb1eb028d15df407efb62628253dab90aa5
Author:     Jitendra Bhati <[email protected]>
AuthorDate: 2026-08-04 19:40:32 +0000
Commit:     Alan Somers <[email protected]>
CommitDate: 2026-08-06 22:51:25 +0000

    fts: fix fts_accpath regression after fts_children()
    
    When fts_children() is called, sp->fts_child is set. On the
    next fts_read() call, fts_safe_changedir() was incorrectly
    passed p->fts_dirfd (pointing to the parent directory) instead
    of -1. This caused fts to fchdir to the parent instead of the
    child directory, silently skipping the contents of 3rd-level
    subdirectories. This was observed as a failure in
    nmtree_test:mtree_create which calls fts_children() internally.
    
    Add regression test accpath_correct_after_descent that calls
    fts_children() on each directory entry and verifies files at
    depth 3 are still visited correctly. The test fails with the
    unfixed libc and passes with the fix.
    
    Reported by:    Herbert J. Skuhra <[email protected]>
    Sponsored by:   Google LLC (GSoC 2026)
    Reviewed by:    asomers
    Fixes:          4bd01d6ae01 ("fts: refactor to use fd-relative")
    Pull Request:   https://github.com/freebsd/freebsd-src/pull/2354
---
 lib/libc/gen/fts.c                    |  3 +-
 lib/libc/tests/gen/fts_regress_test.c | 60 +++++++++++++++++++++++++++++++++++
 2 files changed, 61 insertions(+), 2 deletions(-)

diff --git a/lib/libc/gen/fts.c b/lib/libc/gen/fts.c
index 204340e4b87f..5f56d169e076 100644
--- a/lib/libc/gen/fts.c
+++ b/lib/libc/gen/fts.c
@@ -489,8 +489,7 @@ fts_read(FTS *sp)
 		 * FTS_STOP or the fts_info field of the node.
 		 */
 		if (sp->fts_child != NULL) {
-			if (fts_safe_changedir(sp, p,
-			    p->fts_dirfd, p->fts_name)) {
+			if (fts_safe_changedir(sp, p, -1, p->fts_name)) {
 				p->fts_errno = errno;
 				p->fts_flags |= FTS_DONTCHDIR;
 				for (p = sp->fts_child; p != NULL;
diff --git a/lib/libc/tests/gen/fts_regress_test.c b/lib/libc/tests/gen/fts_regress_test.c
index cf4035a65259..7075addf92b4 100644
--- a/lib/libc/tests/gen/fts_regress_test.c
+++ b/lib/libc/tests/gen/fts_regress_test.c
@@ -303,6 +303,65 @@ ATF_TC_BODY(concurrent_modification, tc)
 	pthread_join(thr, NULL);
 }
 
+/*
+ * Regression test for a bug introduced in 4bd01d6ae01.
+ * With the bug, fts fchdir'd to the parent directory instead of the child,
+ * causing the contents of subdirectories at depth 3 to be silently skipped.
+ *
+ */
+ATF_TC(accpath_correct_after_descent);
+ATF_TC_HEAD(accpath_correct_after_descent, tc)
+{
+	atf_tc_set_md_var(tc, "descr",
+	    "fts correctly descends into 3-level directory trees");
+}
+
+ATF_TC_BODY(accpath_correct_after_descent, tc)
+{
+	char *paths[] = { ".", NULL };
+	FTS *fts;
+	FTSENT *ent, *children;
+	bool saw_deep_file = false;
+
+	/*
+	 * Create a 3-level directory tree, mirroring the structure used
+	 * by nmtree_test:mtree_create which also triggered this bug.
+	 */
+	ATF_REQUIRE_EQ(0, mkdir("a", 0755));
+	ATF_REQUIRE_EQ(0, mkdir("a/1", 0755));
+	ATF_REQUIRE_EQ(0, mkdir("a/2", 0755));
+	ATF_REQUIRE_EQ(0, mkdir("b", 0755));
+	ATF_REQUIRE_EQ(0, close(creat("a/1/file", 0644)));
+
+	ATF_REQUIRE((fts = fts_open(paths, FTS_PHYSICAL, NULL)) != NULL);
+
+	while ((ent = fts_read(fts)) != NULL) {
+		if (ent->fts_info == FTS_D) {
+			/*
+			 * Call fts_children() to set sp->fts_child.
+			 * This triggered the bug — when fts_read() then
+			 * descends into the directory, it calls
+			 * fts_safe_changedir(sp, p, p->fts_dirfd, p->fts_name)
+			 * instead of fts_safe_changedir(sp, p, -1, p->fts_name).
+			 * With fts_dirfd pointing to the parent, it fchdir's
+			 * to the wrong directory and contents are skipped.
+			 */
+			children = fts_children(fts, 0);
+			(void)children;
+		}
+
+		if (strcmp(ent->fts_name, "file") == 0 &&
+		    ent->fts_info == FTS_F)
+			saw_deep_file = true;
+	}
+
+	ATF_CHECK_MSG(saw_deep_file,
+	    "did not visit 'a/1/file' at depth 3 — "
+	    "fts_safe_changedir used wrong fd after fts_children()");
+
+	ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");
+}
+
 ATF_TP_ADD_TCS(tp)
 {
 	ATF_TP_ADD_TC(tp, read_no_exec_dir);
@@ -310,6 +369,7 @@ ATF_TP_ADD_TCS(tp)
 	ATF_TP_ADD_TC(tp, readdir_error_detected);
 	ATF_TP_ADD_TC(tp, odirectory_changedir);
 	ATF_TP_ADD_TC(tp, concurrent_modification);
+	ATF_TP_ADD_TC(tp, accpath_correct_after_descent);
 
 	return (atf_no_error());
 }
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.