Should /usr/include/fts.h adding fts_dirfd and __fts_reserved[3] to FTSENT (a.k.a. struct _ftsent) require a change from libc.so.7 to .8 because of some resulting binary incompatibilities?

From: Mark Millard <marklmi_at_yahoo.com>
Date: Thu, 24 Sep 2026 23:37:09 UTC
The SHLIB_MAJOR last changed on 2006-05-21 and is:

LIB=c
SHLIB_MAJOR= 7

Changes to the binary interface for function (pointers) passed into any
FreeBSD libc inteferface functions lead to incompatibilities. In this
case FTSENT (a.k.a. struct _ftsent) is used via function arguments in:

FTS	*fts_open(char * const *, int,
	    int (*)(const FTSENT * const *, const FTSENT * const *));
FTS	*fts_openat(int, char * const *, int,
	    int (*)(const FTSENT * const *, const FTSENT * const *));
#ifdef __BLOCKS__
FTS	*fts_open_b(char * const *, int,
	    int (^)(const FTSENT * const *, const FTSENT * const *));

but, as of 2026-08-03, the content of the *FTSENT (i.e.,
struct _ftsent) was updated in a non-binary compatible way:

author	Jitendra Bhati <bhatijitendra2022@gmail.com>	2026-06-12 17:07:55
+0000
committer	Alan Somers <asomers@FreeBSD.org>	2026-08-03 19:12:28 +0000
commit	4bd01d6ae01632501b63438b8d9a401db9744a78 (patch)
tree	e896c1844fa0fa5897efc4e591ebe02f59a64e61 /include/fts.h
parent	9590878fca68e62c63d607da73139698e204d0f0 (diff)

. . .

Sponsored by:	Google LLC (GSoC 2026)
Reviewed by:	asomers
Pull Request:	https://github.com/freebsd/freebsd-src/pull/2303
Diffstat (limited to 'include/fts.h')
-rw-r--r--	include/fts.h	2	
1 files changed, 2 insertions, 0 deletions
diff --git a/include/fts.h b/include/fts.h
index 479905bda463..0308b8ff880b 100644
--- a/include/fts.h
+++ b/include/fts.h
@@ -92,6 +92,8 @@ struct _ftsent {
 	char *fts_path;			/* root path */
 	int fts_errno;			/* errno for this node */
 	int fts_symfd;			/* fd for symlink */
+	int fts_dirfd;                  /* fd for parent directory */
+	int __fts_reserved[3];          /* reserved for future use */
 	__size_t fts_pathlen;		/* strlen(fts_path) */
 	__size_t fts_namelen;		/* strlen(fts_name) */

There is a lot more after what is visible in the diff (starting with the
inserted fields to have it all together):

	int fts_dirfd;                  /* fd for this directory, if a directory */
	int __fts_reserved[3];          /* reserved for future use */
	__size_t fts_pathlen;		/* strlen(fts_path) */
	__size_t fts_namelen;		/* strlen(fts_name) */

	__ino_t fts_ino;		/* inode */
	__dev_t fts_dev;		/* device */
	__nlink_t fts_nlink;		/* link count */

#define	FTS_ROOTPARENTLEVEL	-1
#define	FTS_ROOTLEVEL		 0
	long fts_level;			/* depth (-1 to N) */

#define	FTS_D		 1		/* preorder directory */
. . .
#define	FTS_W		14		/* whiteout object */
	int fts_info;			/* user status for FTSENT structure */

#define	FTS_DONTCHDIR	 0x01		/* don't chdir .. to the parent */
#define	FTS_SYMFOLLOW	 0x02		/* followed a symlink to get here */
#define	FTS_ISW		 0x04		/* this is a whiteout object */
	unsigned fts_flags;		/* private flags for FTSENT structure */

#define	FTS_AGAIN	 1		/* read node again */
...
#define	FTS_SKIP	 4		/* discard node */
	int fts_instr;			/* fts_set() instructions */

	struct stat *fts_statp;		/* stat(2) information */
	char *fts_name;			/* file name */
	FTS *fts_fts;			/* back pointer to main FTS */
};


An example is in src/tree/lib/libpmc/pmu-events/jevents.c that has:

#include <fts.h>

static int
#if defined(__linux__) || defined(__APPLE__)
fts_compare(const FTSENT **a, const FTSENT **b)
#else
fts_compare(const FTSENT * const *a, const FTSENT * const *b)
#endif
{
	return (strcmp((*a)->fts_name, (*b)->fts_name));
}

Note that the compile time offset for ->fts_name change changes
depending on which header version was used and that it will not track
the environment that jevents is run in.

The same jevent executable file's fts_compare code (non-linux/non-apple)
is compatible with only one of:

) a libc.so.7 *FTSENT from before the 2026-08-03 change
vs.
) a libc.so.7 *FTSENT from after  the 2026-08-03 change


I have observed SIGSEGV with backtraces that show the failure at such a
fts_compare (*?)->fts_name based deference. That is what eventually
resulted in my noticing the above in this unfamiliar code.


-- 
===
Mark Millard
marklmi at yahoo.com