[glibc/azanella/bz32119-stat-time64] linux: Fix stat layout divergence under pre-POSIX.1-2008 macros [BZ #32119]
Adhemerval Zanella via Glibc-cvs <[email protected]>
| Newsgroups | gmane.comp.lib.glibc.cvs |
|---|---|
| Message-ID | <[email protected]> |
https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=ecf95727a78731465b33290bc7374114ab38b63e commit ecf95727a78731465b33290bc7374114ab38b63e Author: Adhemerval Zanella <[email protected]> Date: Wed Aug 5 15:28:02 2026 +0000 linux: Fix stat layout divergence under pre-POSIX.1-2008 macros [BZ #32119] The struct stat layout must not depend on the feature-test macros in use: the kernel and libc always fill the same layout, and the pre-POSIX.1-2008 branches only exist to avoid declaring members with the reserved timespec names. Three divergences crept in: - 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, they compare equal, and 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 although stat never populates the extra bytes. - 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, shifting st_mtime and st_ctime and shrinking the structure (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 unconditionally 64-bit on such ports. Fix the helper by including <bits/endian.h> and removing the reserved words, restoring the pre-refactoring (4e8521333be) time64 layout; and give the generic header the same endianness-aware nanosecond padding as <bits/types/struct_timespec.h>. 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. Signed-off-by: Matthias Goergens <[email protected]> Diff: --- sysdeps/unix/sysv/linux/Makefile | 11 +++ sysdeps/unix/sysv/linux/bits/struct_stat.h | 36 ++++++--- .../sysv/linux/bits/struct_stat_time64_helper.h | 5 +- sysdeps/unix/sysv/linux/tst-stat-layout-time64.py | 94 ++++++++++++++++++++++ 4 files changed, 131 insertions(+), 15 deletions(-) diff --git a/sysdeps/unix/sysv/linux/Makefile b/sysdeps/unix/sysv/linux/Makefile index 14a56d5cc3..a5811977d4 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 e912c3f6ba..cdbe1f8f76 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 e0b78b314a..0ce85352cb 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,5 @@ __fieldts64 (st_mtime); __fieldts64 (st_ctime); - unsigned long int __glibc_reserved4; - unsigned long int __glibc_reserved5; - # undef __fieldts64 #endif 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 0000000000..2c6dd66a49 --- /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()