Re: 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: Fri, 25 Sep 2026 01:57:04 UTC
On 9/24/26 17:16, Mark Millard wrote:
> On 9/24/26 16:40, Warner Losh wrote:
>> No. Symbol versioning.
> 
> jevents has no symbol versioning for its own, internal implementation of
> a function it decided to name fts_compare as far as I can tell. That
> same fts_compare implementation is passed in everywhere that the same
> libc.so.7 naming is in use and then is called no matter what,
> independent of which fts.h vintage it was built with.
> 
> If symbol versioning applies here, how does it work?
> 
> (Why I ended up with the mix of an older jevents with a post-change
> environment is a separate issue and is my fault. But nothing rejected or
> fixed the combination. libc.so.8 vs. libc.so.7 would have rejected.)

Your note caused me to somewhat better isolate the structure of the mix:

The kernel is updated  to  : 1600026
The world  is updating from: 1600019 (so: old fts.h)
The world  is updating to  : 1600026 (or trying to)

# readelf -a
/usr/obj/BUILDs/main-CA76-dbg-clang/usr/main-src/arm64.aarch64/lib/libc/libc.so.7
| grep fts_open@
0000000000243d10  00000c7a00000402 R_AARCH64_JUMP_SLOT
00000000000c0c40 fts_open@@FBSD_1.9 + 0
  3194: 00000000000c0c40   128 FUNC    GLOBAL DEFAULT    14
fts_open@@FBSD_1.9
  3195: 00000000000c29c0  1016 FUNC    GLOBAL DEFAULT    14
fts_open@FBSD_1.0
  3196: 00000000000c4260   924 FUNC    GLOBAL DEFAULT    14
fts_open@FBSD_1.1
  3197: 00000000000c5b60   124 FUNC    GLOBAL DEFAULT    14
fts_open@FBSD_1.5

(Associated with building from modern 1600026 source at the time.)

jevents got:

# readelf -a
/usr/obj/BUILDs/main-CA76-dbg-clang/usr/main-src/arm64.aarch64/lib/libpmc/pmu-events/jevents
| grep fts_open@
0000000000037680  0000001300000402 R_AARCH64_JUMP_SLOT
0000000000000000 fts_open@FBSD_1.9 + 0
    19: 0000000000000000     0 FUNC    GLOBAL DEFAULT   UND
fts_open@FBSD_1.9


But, as a build-tool, jevents was built with /usr/include/fts.h instead
of with the /usr/main-src/ tree that I use for the build if the update.
(/usr/src/ is from pkgbase.):

# diff -u /usr/include/fts.h /usr/main-src/include/fts.h
--- /usr/include/fts.h	2025-05-11 07:29:42.791259000 -0700
+++ /usr/main-src/include/fts.h	2026-09-18 16:13:35.766402000 -0700
@@ -1,4 +1,4 @@
-/*-
+/*
  * SPDX-License-Identifier: BSD-3-Clause
  *
  * Copyright (c) 1989, 1993
@@ -92,6 +92,8 @@
 	char *fts_path;			/* root path */
 	int fts_errno;			/* errno for this node */
 	int fts_symfd;			/* fd for symlink */
+	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) */

@@ -146,6 +148,8 @@
 #define	 fts_get_stream(ftsent)	((ftsent)->fts_fts)
 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 *));

Thus the result was a jevents using:

) the old /usr/include/fts.h header of its "host" environment
but also:
) the new symbol version fts_open@@FBSD_1.9 was used.

Nothing rejected the bad combination (other than a later SIGSEGV).

I do not see how /usr/include/fts.h file content would end up somehow in
control over fts_open@@FBSD_1.5 vs. fts_open@@FBSD_1.9 being used for
the likes of the jevents build.


Mark


> 
>>
>> Warner
>>
>> On Thu, Sep 24, 2026, 5:37 PM Mark Millard <marklmi@yahoo.com
>> <mailto:marklmi@yahoo.com>> wrote:
>>
>>     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
>>     <mailto: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
>>     <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 <http://yahoo.com>
>>
>>
> 
> 


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