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 00:16:10 UTC
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.)

> 
> 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