Re: [PATCH 0/2] linux: Keep time64 stat layout independent of feature macros
Adhemerval Zanella Netto <[email protected]> Wed, 5 Aug 2026 12:42:29 -0300
| Newsgroups | gmane.comp.lib.glibc.alpha |
|---|---|
| Organization | Linaro |
| Message-ID | <[email protected]> |
On 05/08/26 12:23, Adhemerval Zanella Netto wrote:
>
>
> On 05/08/26 05:03, Matthias Goergens wrote:
>> Bug 32119 reports that feature-test macros change the public time64
>> struct stat layout on 32-bit targets. The series separates two causes.
>>
>> Patch 1 includes the endian definitions used by the shared helper. Without
>> them, strict pre-POSIX.1-2008 modes select big-endian timestamp ordering on
>> little-endian targets. This is an unambiguous layout bug.
>>
>> Patch 2 removes two trailing reserved words which the helper exposes only in
>> those strict modes. History shows that they were imported accidentally when
>> the public and internal time64 declarations were consolidated. The stat
>> implementation does not write them, and the default public and internal
>> layouts omit them.
>>
>> Patch 2 nevertheless changes sizeof(struct stat) for 32-bit objects already
>> compiled with both 64-bit time and strict pre-POSIX.1-2008 feature macros.
>> Please advise whether preserving that niche accidental layout is required by
>> glibc ABI policy. Patch 1 is independent if the size correction is unsuitable.
>
> I still strictly an ABI break, but I also think the blast radius is quite minimal.
> The fields were unused by glibc, so it would be mostly between TU built with
> different flags.
>
> And I do not think it would worth adding compat symbols, but I also think it
> would be good to backport this fix to all affected branches.
>
>>
>> An i386 build and the io tests pass. The regression compares timestamp
>> offsets and sizes across translation units built in default and POSIX.1-2001
>> modes. Compile-time layout models also pass for 32-bit Arm, PowerPC, and MIPS;
>> these models are supplementary and are not full cross builds.
>
> I think it would be better to move this test to script that uses glibcextracy.py,
> so these tests can be checked in cross-compiling mode. Something like:
>
> 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/tst-stat-layout-time64.py b/sysdeps/unix/sysv/linux/tst-stat-layout-time64.py
> new file mode 100644
> index 00000000000..2c6dd66a491
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/tst-stat-layout-time64.py
> @@ -0,0 +1,94 @@
> +#!/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):
> + # The nanosecond members are named differently in the two helper
> + # branches: st_Xtim.tv_nsec with __USE_XOPEN2K8, st_Xtimensec
> + #otherwise.
> + 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()
>
>
> And this approach uncovered a similar issue on arc and ork1, which are both
> 32-bit with 64-bit time_t by default. Different than riscv32, another 32-bit
> with 64-bit time_t, alignof(int64) == 4 and thus 'struct stat' size and
> internal layout (sigh...) differ.
>
> The fix would require to change de generic implementation
> sysdeps/unix/sysv/linux/bits/struct_stat.h:
>
> iff --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
>
> I am not sure if this would require a different bug, but we will need to fix
> it along with BZ#34466.
I squashed your proposed change with the extra one the generic and the python
tests [1], this should be complete change for all ABIs. I will test on some
ABIs, but it should only really affect 32 bit ABIs.
Ideally this should be backported up to 2.34.
[1] https://sourceware.org/git/?p=glibc.git;a=commit;h=ecf95727a78731465b33290bc7374114ab38b63e