[PATCH] linux: Fix stat layout divergence under pre-POSIX.1-2008 macros [BZ #32119]

Adhemerval Zanella Netto adhemerval.zanella@linaro.org
Sat Aug 15 14:01:27 GMT 2026


Sign, I forgot to properly tested on armhf and the CI triggered a
regression because the patch does not update the internal  __stat64_t64.
I will send a new version.

On 14/08/26 12:53, Adhemerval Zanella wrote:
> The struct stat layout must not depend on the feature-test macros in
> use. Currently there are three divergences:
> 
>   - bits/struct_stat_time64_helper.h tests __BYTE_ORDER without
>     including <bits/endian.h>.  In strict pre-POSIX.1-2008 modes both
>     operands are undefined, the big-endian member order is selected
>     on little-endian targets, displacing the time64 nanosecond fields
>     on targets whose bits/struct_stat.h does not already include
>     <bits/endian.h> (x86, mips o32/n32, microblazeel).
> 
>   - The same helper adds two reserved words only when POSIX.1-2008
>     interfaces are hidden, making the public time64 structure larger
>     under older feature-test macros.
> 
>   - The generic bits/struct_stat.h declares the strict-mode timestamps
>     as a bare seconds/nanoseconds pair with no endianness handling or
>     explicit padding.  On 32-bit ports with 64-bit time_t whose ABI
>     aligns 64-bit types to 4 bytes, the timestamps end up packed 12
>     bytes apart instead of 16 (arc); on big-endian the nanoseconds also
>     land on the wrong side of the padding (or1k).  This triggers with
>     plain -D_POSIX_C_SOURCE=200112L, without any _TIME_BITS opt-in,
>     since time_t is always 64-bit on such ports.
> 
> Fix the helper by including <bits/endian.h>, and give the generic
> header the same endianness-aware nanosecond padding as
> <bits/types/struct_timespec.h>.
> 
> For the reserved words, instead of removing them unify all
> feature-test-macro modes on the largest layout.  The internal
> __stat64_t64 does not write on them, so libc keeps writing the same
> number of bytes and previously built objects using the smaller
> default-mode structure remain correct (newly built objects will
> over-allocate bytes).
> 
> The resulting sizeof (struct stat) changes, with _TIME_BITS=64 for
> the first two rows (the time32 layouts are unchanged) and with any
> feature-test macro combination on the time64-only arc and or1k:
> 
>   ABI                            default and       pre-POSIX.1-2008
>                                  POSIX.1-2008      strict modes
>   i686, csky, m68k, microblaze,
>   sh                             108 -> 116        116 (unchanged)
>   arm, hppa, mips o32/n32,
>   powerpc32, sparc32             112 -> 120        120 (unchanged)
>   arc, or1k                      128 (unchanged)   116 -> 128
> 
> Add a compile-only Python test based on glibcextract that compares
> sizeof and the member offsets of struct stat across several feature
> modes on all configurations.
> 
> Co-authored-by: Matthias Goergens <matthias.goergens@gmail.com>
> ---
>  sysdeps/unix/sysv/linux/Makefile              | 11 +++
>  sysdeps/unix/sysv/linux/bits/struct_stat.h    | 36 +++++---
>  .../linux/bits/struct_stat_time64_helper.h    | 13 ++-
>  sysdeps/unix/sysv/linux/struct_stat_time64.h  |  2 +
>  .../unix/sysv/linux/tst-stat-layout-time64.py | 91 +++++++++++++++++++
>  5 files changed, 138 insertions(+), 15 deletions(-)
>  create mode 100644 sysdeps/unix/sysv/linux/tst-stat-layout-time64.py
> 
> diff --git a/sysdeps/unix/sysv/linux/Makefile b/sysdeps/unix/sysv/linux/Makefile
> index 14a56d5cc3f..a5811977d4a 100644
> --- a/sysdeps/unix/sysv/linux/Makefile
> +++ b/sysdeps/unix/sysv/linux/Makefile
> @@ -413,6 +413,17 @@ $(objpfx)tst-sched-consts.out: ../sysdeps/unix/sysv/linux/tst-sched-consts.py
>  	  < /dev/null > $@ 2>&1; $(evaluate-test)
>  $(objpfx)tst-sched-consts.out: $(sysdeps-linux-python-deps)
>  
> +tests-special += \
> +  $(objpfx)tst-stat-layout-time64.out \
> +  # tests-special
> +$(objpfx)tst-stat-layout-time64.out: \
> +  ../sysdeps/unix/sysv/linux/tst-stat-layout-time64.py
> +	$(sysdeps-linux-python) \
> +	  ../sysdeps/unix/sysv/linux/tst-stat-layout-time64.py \
> +	    $(sysdeps-linux-python-cc) \
> +	  < /dev/null > $@ 2>&1; $(evaluate-test)
> +$(objpfx)tst-stat-layout-time64.out: $(sysdeps-linux-python-deps)
> +
>  tst-rseq-disable-TUNABLES += glibc.pthread.rseq=0
>  tst-rseq-disable-static-TUNABLES += glibc.pthread.rseq=0
>  
> diff --git a/sysdeps/unix/sysv/linux/bits/struct_stat.h b/sysdeps/unix/sysv/linux/bits/struct_stat.h
> index e912c3f6ba5..cdbe1f8f762 100644
> --- a/sysdeps/unix/sysv/linux/bits/struct_stat.h
> +++ b/sysdeps/unix/sysv/linux/bits/struct_stat.h
> @@ -41,6 +41,22 @@
>    int __##name##_pad __attribute__((__aligned__ (__alignof__ (type64)))); type name
>  #endif
>  
> +/* The pre-POSIX.1-2008 timestamp fields must match the layout of the
> +   'struct timespec' members used in the POSIX.1-2008 case, including the
> +   padding required when the seconds field is wider than the word size.  */
> +#if __WORDSIZE == 64 \
> +  || (defined __SYSCALL_WORDSIZE && __SYSCALL_WORDSIZE == 64) \
> +  || (__TIMESIZE == 32 && !defined __USE_TIME64_REDIRECTS)
> +# define __fieldts(name) \
> +  __time_t name; unsigned long int name ## nsec
> +#elif __BYTE_ORDER == __BIG_ENDIAN
> +# define __fieldts(name) \
> +  __time_t name; int: 32; unsigned long int name ## nsec
> +#else
> +# define __fieldts(name) \
> +  __time_t name; unsigned long int name ## nsec; int: 32
> +#endif
> +
>  struct stat
>    {
>      __dev_t st_dev;		/* Device.  */
> @@ -69,12 +85,9 @@ struct stat
>  # define st_mtime st_mtim.tv_sec
>  # define st_ctime st_ctim.tv_sec
>  #else
> -    __time_t st_atime;			/* Time of last access.  */
> -    unsigned long int st_atimensec;	/* Nscecs of last access.  */
> -    __time_t st_mtime;			/* Time of last modification.  */
> -    unsigned long int st_mtimensec;	/* Nsecs of last modification.  */
> -    __time_t st_ctime;			/* Time of last status change.  */
> -    unsigned long int st_ctimensec;	/* Nsecs of last status change.  */
> +    __fieldts (st_atime);		/* Time of last access.  */
> +    __fieldts (st_mtime);		/* Time of last modification.  */
> +    __fieldts (st_ctime);		/* Time of last status change.  */
>  #endif
>      int __glibc_reserved[2];
>    };
> @@ -107,17 +120,16 @@ struct stat64
>      struct timespec st_mtim;		/* Time of last modification.  */
>      struct timespec st_ctim;		/* Time of last status change.  */
>  #else
> -    __time_t st_atime;			/* Time of last access.  */
> -    unsigned long int st_atimensec;	/* Nscecs of last access.  */
> -    __time_t st_mtime;			/* Time of last modification.  */
> -    unsigned long int st_mtimensec;	/* Nsecs of last modification.  */
> -    __time_t st_ctime;			/* Time of last status change.  */
> -    unsigned long int st_ctimensec;	/* Nsecs of last status change.  */
> +    __fieldts (st_atime);		/* Time of last access.  */
> +    __fieldts (st_mtime);		/* Time of last modification.  */
> +    __fieldts (st_ctime);		/* Time of last status change.  */
>  #endif
>      int __glibc_reserved[2];
>    };
>  #endif
>  
> +#undef __fieldts
> +
>  /* Tell code we have these members.  */
>  #define	_STATBUF_ST_BLKSIZE
>  #define _STATBUF_ST_RDEV
> diff --git a/sysdeps/unix/sysv/linux/bits/struct_stat_time64_helper.h b/sysdeps/unix/sysv/linux/bits/struct_stat_time64_helper.h
> index e0b78b314ad..7cbb664d6df 100644
> --- a/sysdeps/unix/sysv/linux/bits/struct_stat_time64_helper.h
> +++ b/sysdeps/unix/sysv/linux/bits/struct_stat_time64_helper.h
> @@ -16,6 +16,8 @@
>     License along with the GNU C Library.  If not, see
>     <https://www.gnu.org/licenses/>.  */
>  
> +#include <bits/endian.h>
> +
>    /* Content of internal __stat64_t64 struct.  */
>    __dev_t st_dev;		/* Device.  */
>    __ino64_t st_ino;		/* file serial number.	*/
> @@ -59,8 +61,13 @@
>    __fieldts64 (st_mtime);
>    __fieldts64 (st_ctime);
>  
> -  unsigned long int __glibc_reserved4;
> -  unsigned long int __glibc_reserved5;
> -
>  # undef __fieldts64
>  #endif
> +
> +#ifndef __struct_stat_time64_internal
> +  /* Trailing padding, never set by the stat functions.  It keeps
> +     sizeof (struct stat) as the largest one for all feature-test-macro
> +     modes.  */
> +  unsigned long int __glibc_reserved4;
> +  unsigned long int __glibc_reserved5;
> +#endif
> diff --git a/sysdeps/unix/sysv/linux/struct_stat_time64.h b/sysdeps/unix/sysv/linux/struct_stat_time64.h
> index e3b878e1bed..38e603febdb 100644
> --- a/sysdeps/unix/sysv/linux/struct_stat_time64.h
> +++ b/sysdeps/unix/sysv/linux/struct_stat_time64.h
> @@ -27,8 +27,10 @@
>  struct __stat64_t64
>    {
>  # define __struct_timespec struct __timespec64
> +# define __struct_stat_time64_internal 1
>  # include <bits/struct_stat_time64_helper.h>
>    };
> +# undef __struct_stat_time64_internal
>  #endif /* __TIMESIZE == 64  */
>  
>  #endif /* _BITS_STRUCT_STAT_TIME64_H  */
> diff --git a/sysdeps/unix/sysv/linux/tst-stat-layout-time64.py b/sysdeps/unix/sysv/linux/tst-stat-layout-time64.py
> new file mode 100644
> index 00000000000..288a03bb661
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/tst-stat-layout-time64.py
> @@ -0,0 +1,91 @@
> +#!/usr/bin/python3
> +# Check that feature-test macros do not change the time64 stat layout.
> +# Copyright (C) 2026 Free Software Foundation, Inc.
> +# This file is part of the GNU C Library.
> +#
> +# The GNU C Library is free software; you can redistribute it and/or
> +# modify it under the terms of the GNU Lesser General Public
> +# License as published by the Free Software Foundation; either
> +# version 2.1 of the License, or (at your option) any later version.
> +#
> +# The GNU C Library is distributed in the hope that it will be useful,
> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +# Lesser General Public License for more details.
> +#
> +# You should have received a copy of the GNU Lesser General Public
> +# License along with the GNU C Library; if not, see
> +# <https://www.gnu.org/licenses/>.
> +
> +import argparse
> +import sys
> +
> +import glibcextract
> +
> +MODES = {
> +    'POSIX.1-1996': '#define _POSIX_C_SOURCE 199506L',
> +    'POSIX.1-2001': '#define _POSIX_C_SOURCE 200112L',
> +    'POSIX.1-2008': '#define _POSIX_C_SOURCE 200809L',
> +    'XPG6': '#define _XOPEN_SOURCE 600',
> +}
> +
> +
> +def compute_stat_layout(cc, mode_define):
> +    sym_data = [
> +        '#undef _GNU_SOURCE',
> +        mode_define,
> +        '#define _TIME_BITS 64',
> +        '#define _FILE_OFFSET_BITS 64',
> +        '#include <stddef.h>',
> +        '#include <sys/stat.h>',
> +        'START',
> +        ('sizeof_stat', 'sizeof (struct stat)'),
> +        ('st_dev', 'offsetof (struct stat, st_dev)'),
> +        ('st_ino', 'offsetof (struct stat, st_ino)'),
> +        ('st_mode', 'offsetof (struct stat, st_mode)'),
> +        ('st_nlink', 'offsetof (struct stat, st_nlink)'),
> +        ('st_uid', 'offsetof (struct stat, st_uid)'),
> +        ('st_gid', 'offsetof (struct stat, st_gid)'),
> +        ('st_rdev', 'offsetof (struct stat, st_rdev)'),
> +        ('st_size', 'offsetof (struct stat, st_size)'),
> +        ('st_blksize', 'offsetof (struct stat, st_blksize)'),
> +        ('st_blocks', 'offsetof (struct stat, st_blocks)'),
> +        ('st_atime', 'offsetof (struct stat, st_atime)'),
> +        ('st_mtime', 'offsetof (struct stat, st_mtime)'),
> +        ('st_ctime', 'offsetof (struct stat, st_ctime)'),
> +        '#ifdef __USE_XOPEN2K8',
> +        ('st_atimensec', 'offsetof (struct stat, st_atim.tv_nsec)'),
> +        ('st_mtimensec', 'offsetof (struct stat, st_mtim.tv_nsec)'),
> +        ('st_ctimensec', 'offsetof (struct stat, st_ctim.tv_nsec)'),
> +        '#else',
> +        ('st_atimensec', 'offsetof (struct stat, st_atimensec)'),
> +        ('st_mtimensec', 'offsetof (struct stat, st_mtimensec)'),
> +        ('st_ctimensec', 'offsetof (struct stat, st_ctimensec)'),
> +        '#endif',
> +    ]
> +    return glibcextract.compute_c_consts(sym_data, cc)
> +
> +
> +def main():
> +    parser = argparse.ArgumentParser(
> +        description='Check that feature-test macros do not change '
> +        'the time64 stat layout.')
> +    parser.add_argument('--cc', metavar='CC',
> +                        help='C compiler (including options) to use')
> +    args = parser.parse_args()
> +    default_layout = compute_stat_layout(args.cc, '#define _GNU_SOURCE 1')
> +    status = 0
> +    for mode, mode_define in sorted(MODES.items()):
> +        mode_layout = compute_stat_layout(args.cc, mode_define)
> +        for name, value in default_layout.items():
> +            if mode_layout[name] != value:
> +                print('FAIL: %s: %s is %s, %s in default mode'
> +                      % (mode, name, mode_layout[name], value))
> +                status = 1
> +    if status == 0:
> +        print('PASS: struct stat layout is feature-test-macro invariant')
> +    sys.exit(status)
> +
> +
> +if __name__ == '__main__':
> +    main()



More information about the Libc-alpha mailing list