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

Adhemerval Zanella Netto <[email protected]>
Newsgroups gmane.comp.lib.glibc.alpha
Organization Linaro
Message-ID <[email protected]>
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 <[email protected]>
> ---
>  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()
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.