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: Warner Losh <imp_at_bsdimp.com>
Date: Thu, 24 Sep 2026 23:40:47 UTC
No. Symbol versioning.

Warner

On Thu, Sep 24, 2026, 5:37 PM Mark Millard <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>    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
>
>
>