The struct stat layout must not depend on the feature-test macros in
use. Currently there are three issues:
- 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, which displaces 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 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>.
All feature-test-macro modes now use the layout of the latest
standard (default and POSIX.1-2008 with _TIME_BITS=64), which is also
the internal __stat64_t64 layout. The stat functions never set the
reserved words, so older strict-mode objects that allocate the larger
structure keep working; and with the public and internal layouts
identical, callback interfaces that write into libc-owned buffers
(e.g. glob with GLOB_ALTDIRFUNC) stay correct.
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 (unchanged) 116 -> 108
arm, hppa, mips o32/n32,
powerpc32, sparc32 112 (unchanged) 120 -> 112
arc, or1k 128 (unchanged) 116 -> 128
A new compile-only test checks that sizeof and the member offsets of
struct stat do not vary across feature-test-macro modes.
Co-authored-by: Matthias Goergens <matthias.goergens@gmail.com>
Signed-off-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 | 5 +-
.../unix/sysv/linux/tst-stat-layout-time64.py | 94 +++++++++++++++++++
4 files changed, 131 insertions(+), 15 deletions(-)
create mode 100644 sysdeps/unix/sysv/linux/tst-stat-layout-time64.py
Hi Adhemerval,
The code in v3 is the same as the squashed commit I checked in August
[1]; only the commit message is new.
I applied it to master (30f988fbe795). On native x86_64 and i686,
make check for io, misc, posix and conform shows no failures. I also
ran the new test through its Makefile rule with glibc configured for
real cross compilers (Debian's, plus build-many-glibcs.py for or1k,
csky and microblazeel): 17 configurations covering i686, arm, powerpc,
mips and mipsel (o32 and n32), riscv32, arc, or1k, m68k, sh4, hppa,
sparcv9, csky and microblazeel. It passes on all of them, and against
the unpatched headers it fails on all but riscv32, so it catches the
bug. Beyond the test, sizeof and every member offset of struct stat
and struct stat64 match the _GNU_SOURCE layout in all 13 feature-test
modes I tried, and the size table in the commit message is right.
One correction to my August mail: or1k aligns 64-bit types to 4
bytes, not 8, so before the fix its strict-mode layout was 116 bytes,
as on arc and as your table says.
Small points, none blocking:
- The commit message says the missing <bits/endian.h> displaces the
nanosecond fields on mips o32/n32. That only happens on
little-endian mips ("mipsel o32/n32"); big-endian mips only changes
size.
- The sentence ending "the big-endian member order is selected" lost
the "they compare equal, and" from the August wording.
- tst-stat-layout-time64.py: "#otherwise" is missing a space.
Tested-by: Matthias Goergens <matthias.goergens@gmail.com>
Thanks,
Matthias
[1] https://sourceware.org/git/?p=glibc.git;a=commit;h=ecf95727a78731465b33290bc7374114ab38b63e
@@ -425,6 +425,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
@@ -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
@@ -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
new file mode 100644
@@ -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()