Skip site navigation (1)Skip section navigation (2)
Date:      Fri, 29 May 2026 17:45:18 +0000
From:      Dag-Erling=?utf-8?Q? Sm=C3=B8rg?=rav <des@FreeBSD.org>
To:        src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org
Subject:   git: b2b95249ae0e - main - fts: Check link count before using it
Message-ID:  <6a19d0ae.3fbb7.5b206217@gitrepo.freebsd.org>

index | next in thread | raw e-mail

The branch main has been updated by des:

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

commit b2b95249ae0e24a6e24ad4286da56f1aff7a6db0
Author:     Dag-Erling Smørgrav <des@FreeBSD.org>
AuthorDate: 2026-05-29 17:45:06 +0000
Commit:     Dag-Erling Smørgrav <des@FreeBSD.org>
CommitDate: 2026-05-29 17:45:06 +0000

    fts: Check link count before using it
    
    * Check the range of the link count before trying to use it.
    
    * Rewrite the comment explaining what the link count is used for.
    
    MFC after:      1 week
    Sponsored by:   Klara, Inc.
    Reviewed by:    kevans
    Differential Revision:  https://reviews.freebsd.org/D57324
---
 lib/libc/gen/fts.c | 44 +++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 39 insertions(+), 5 deletions(-)

diff --git a/lib/libc/gen/fts.c b/lib/libc/gen/fts.c
index 4aa386d777cd..e8063ecb646e 100644
--- a/lib/libc/gen/fts.c
+++ b/lib/libc/gen/fts.c
@@ -41,6 +41,7 @@
 #include <fcntl.h>
 #include <fts.h>
 #include <stdalign.h>
+#include <stdint.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
@@ -114,6 +115,19 @@ static const char *ufslike_filesystems[] = {
 	0
 };
 
+/*
+ * POSIX provides nlink_t but unfortunately not NLINK_MAX.
+ */
+#define NLINK_MAX \
+	_Generic((nlink_t)0,						\
+		int16_t: INT16_MAX,					\
+		uint16_t: UINT16_MAX,					\
+		int32_t: INT32_MAX,					\
+		uint32_t: UINT32_MAX,					\
+		int64_t: INT64_MAX,					\
+		uint64_t: UINT64_MAX,					\
+		default: 0)
+
 static FTS *
 __fts_open(FTS *sp, char * const *argv)
 {
@@ -736,7 +750,7 @@ fts_build(FTS *sp, int type)
 	int cderrno, descend, oflag, saved_errno, nostat, doadjust,
 	    readdir_errno;
 	long level;
-	long nlinks;	/* has to be signed because -1 is a magic value */
+	int64_t nlinks;	/* has to be signed because -1 is a magic value */
 	size_t dnamlen, len, maxlen, nitems;
 
 	/* Set current node pointer. */
@@ -759,16 +773,36 @@ fts_build(FTS *sp, int type)
 	}
 
 	/*
-	 * Nlinks is the number of possible entries of type directory in the
-	 * directory if we're cheating on stat calls, 0 if we're not doing
-	 * any stat calls at all, -1 if we're doing stats on everything.
+	 * In the FTS_PHYSICAL | FTS_NOSTAT case, we want to avoid calling
+	 * fstat() unnecessarily, but we still need to call it for
+	 * subdirectories.  The current directory's link count provides an
+	 * upper bound on the number of subdirectories we may encounter
+	 * (including . and .. in the FTS_SEEDOT case).  We initialize
+	 * nlinks to the current directory's link count, then decrement it
+	 * every time we encounter a directory, so when we hit zero we can
+	 * save some time by not calling fstat() on subsequent entries.
+	 *
+	 * If FTS_NOSTAT is not set, or the link count is less than two
+	 * (which should not be possible) or equal to NLINK_MAX (which
+	 * suggests that the actual value could be higher), or the current
+	 * filesystem is not known to provide reliable link counts, we
+	 * initialize nlinks to -1 and fstat() everything.
+	 *
+	 * In the rare case where we don't need to stat anything, even
+	 * subdirectories, we initialize nlinks to 0 regardless of the
+	 * actual link count.
+	 *
+	 * Note that we ignore the FTS_NOSTAT flag in the FTS_LOGICAL
+	 * case, although we could choose to only stat symbolic links.
+	 * Implementing this is left as an exercise for the reader.
 	 */
 	if (type == BNAMES) {
 		nlinks = 0;
 		/* Be quiet about nostat, GCC. */
 		nostat = 0;
 	} else if (ISSET(FTS_NOSTAT) && ISSET(FTS_PHYSICAL)) {
-		if (fts_ufslinks(sp, cur))
+		if (cur->fts_nlink >= 2 && cur->fts_nlink < NLINK_MAX &&
+		    cur->fts_nlink <= INT64_MAX && fts_ufslinks(sp, cur))
 			nlinks = cur->fts_nlink - (ISSET(FTS_SEEDOT) ? 0 : 2);
 		else
 			nlinks = -1;


home | help

Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?6a19d0ae.3fbb7.5b206217>