From 99c9efd785e2a611a5b96d01a6d957449598bd08 Mon Sep 17 00:00:00 2001 From: Jeffrey Bosboom Date: Sun, 6 Jul 2025 00:55:25 -0700 Subject: [PATCH 01/18] bpo-39533: Use statx on Linux 4.11 and later in os.stat The libc statx wrapper is required at build time, but weakly linked, and posix_do_stat will fall back to calling stat if statx is not available at runtime due to an old libc or an old kernel. --- Doc/library/os.rst | 36 +++++++ Doc/library/stat.rst | 19 ++++ Lib/stat.py | 15 +++ Misc/ACKS | 1 + Modules/posixmodule.c | 156 ++++++++++++++++++++++++++- Tools/c-analyzer/cpython/ignored.tsv | 1 + 6 files changed, 224 insertions(+), 4 deletions(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 1e54cfec609bd2..acc8e1cfc3712a 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -3227,6 +3227,9 @@ features: .. versionchanged:: 3.12 ``st_birthtime`` is now available on Windows. + .. versionchanged:: next + ``st_birthtime`` is now available on Linux kernel 4.11 and later when + supported by the filesystem. .. attribute:: st_birthtime_ns @@ -3276,6 +3279,34 @@ features: User defined flags for file. + .. attribute:: st_attributes + + Linux file attributes. + See the :const:`!STATX_ATTR* ` + constants in the :mod:`stat` module. + + .. versionadded: next + + .. attribute:: st_attributes_mask + + Linux file attributes supported by the filesystem containing the file. + + .. versionadded: next + + .. attribute:: st_mnt_id + + Mount ID of the mount containing the file, corresponding to the first + field in ``/proc/self/mountinfo``. + + .. versionadded: next + + .. attribute:: stx_subvol + + ID for the subvolume containing the file, or None if the filesystem does + not support subvolumes. + + .. versionadded: next + On other Unix systems (such as FreeBSD), the following attributes may be available (but may be only filled out if root tries to use them): @@ -3367,6 +3398,11 @@ features: Added the :attr:`st_birthtime` member on Windows. + .. versionchanged:: next + Added the :attr:`st_birthtime`, :attr:`st_attributes`, + :attr:`st_attributes_mask`, :attr:`st_mnt_id`, and :attr:`st_subvol` + members on Linux. + .. function:: statvfs(path) diff --git a/Doc/library/stat.rst b/Doc/library/stat.rst index 8434b2e8c75cf4..9e957ae198659b 100644 --- a/Doc/library/stat.rst +++ b/Doc/library/stat.rst @@ -493,3 +493,22 @@ constants, but are not an exhaustive list. IO_REPARSE_TAG_APPEXECLINK .. versionadded:: 3.8 + +On Linux, the following constants are available for comparing against the +``st_attributes`` and ``st_attributes_mask`` members returned by +:func:`os.stat`. See the `statx(2) man page +` for more detail on the +meaning of these constants. + +.. data:: STATX_ATTR_COMPRESSED + STATX_ATTR_IMMUTABLE + STATX_ATTR_APPEND + STATX_ATTR_NODUMP + STATX_ATTR_ENCRYPTED + STATX_ATTR_AUTOMOUNT + STATX_ATTR_MOUNT_ROOT + STATX_ATTR_VERITY + STATX_ATTR_DAX + STATX_ATTR_WRITE_ATOMIC + + .. versionadded:: next diff --git a/Lib/stat.py b/Lib/stat.py index 1b4ed1ebc940ef..ef8f98758f8ddf 100644 --- a/Lib/stat.py +++ b/Lib/stat.py @@ -200,6 +200,21 @@ def filemode(mode): FILE_ATTRIBUTE_VIRTUAL = 65536 +# Linux STATX_ATTR constants for interpreting os.stat()'s +# "st_attributes" and "st_attributes_mask" members + +STATX_ATTR_COMPRESSED = 0x00000004 +STATX_ATTR_IMMUTABLE = 0x00000010 +STATX_ATTR_APPEND = 0x00000020 +STATX_ATTR_NODUMP = 0x00000040 +STATX_ATTR_ENCRYPTED = 0x00000800 +STATX_ATTR_AUTOMOUNT = 0x00001000 +STATX_ATTR_MOUNT_ROOT = 0x00002000 +STATX_ATTR_VERITY = 0x00100000 +STATX_ATTR_DAX = 0x00200000 +STATX_ATTR_WRITE_ATOMIC = 0x00400000 + + # If available, use C implementation try: from _stat import * diff --git a/Misc/ACKS b/Misc/ACKS index d1490e1e46ccfd..c8879d37acc884 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -209,6 +209,7 @@ Médéric Boquien Matias Bordese Jonas Borgström Jurjen Bos +Jeffrey Bosboom Peter Bosch Dan Boswell Eric Bouck diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index b570f81b7cf7c2..49a2d8bc6c0989 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -408,6 +408,18 @@ extern char *ctermid_r(char *); # define STRUCT_STAT struct stat #endif +#if defined(__linux__) && defined(STATX_BASIC_STATS) +# pragma weak statx +# define HAVE_LINUX_STATX 1 +# define HAVE_LINUX_STATX_RUNTIME (statx != NULL) +/* provide definitions introduced later than statx itself */ +# define _Py_STATX_MNT_ID 0x00001000U +# define _Py_STATX_SUBVOL 0x00008000U +# define _Py_STATX_MASK (STATX_BASIC_STATS | STATX_BTIME | _Py_STATX_MNT_ID | _Py_STATX_SUBVOL) +# define _Py_STATX_MNT_ID_OFFSET 144 +# define _Py_STATX_SUBVOL_OFFSET 160 +#endif + #if !defined(EX_OK) && defined(EXIT_SUCCESS) # define EX_OK EXIT_SUCCESS @@ -2353,10 +2365,10 @@ static PyStructSequence_Field stat_result_fields[] = { #ifdef HAVE_STRUCT_STAT_ST_GEN {"st_gen", "generation number"}, #endif -#if defined(HAVE_STRUCT_STAT_ST_BIRTHTIME) || defined(MS_WINDOWS) +#if defined(HAVE_STRUCT_STAT_ST_BIRTHTIME) || defined(HAVE_LINUX_STATX) || defined(MS_WINDOWS) {"st_birthtime", "time of creation"}, #endif -#ifdef MS_WINDOWS +#if defined(HAVE_LINUX_STATX) || defined(MS_WINDOWS) {"st_birthtime_ns", "time of creation in nanoseconds"}, #endif #ifdef HAVE_STRUCT_STAT_ST_FILE_ATTRIBUTES @@ -2367,6 +2379,12 @@ static PyStructSequence_Field stat_result_fields[] = { #endif #ifdef HAVE_STRUCT_STAT_ST_REPARSE_TAG {"st_reparse_tag", "Windows reparse tag"}, +#endif +#ifdef HAVE_LINUX_STATX + {"st_attributes", "Linux file attribute bits"}, + {"st_attributes_mask", "Linux file attribute bits supported by this file"}, + {"st_mnt_id", "Linux mount ID for /proc/self/mountinfo"}, + {"st_subvol", "Linux subvolume identifier"}, #endif {0} }; @@ -2401,13 +2419,13 @@ static PyStructSequence_Field stat_result_fields[] = { #define ST_GEN_IDX ST_FLAGS_IDX #endif -#if defined(HAVE_STRUCT_STAT_ST_BIRTHTIME) || defined(MS_WINDOWS) +#if defined(HAVE_STRUCT_STAT_ST_BIRTHTIME) || defined(HAVE_LINUX_STATX) || defined(MS_WINDOWS) #define ST_BIRTHTIME_IDX (ST_GEN_IDX+1) #else #define ST_BIRTHTIME_IDX ST_GEN_IDX #endif -#ifdef MS_WINDOWS +#if defined(HAVE_LINUX_STATX) || defined(MS_WINDOWS) #define ST_BIRTHTIME_NS_IDX (ST_BIRTHTIME_IDX+1) #else #define ST_BIRTHTIME_NS_IDX ST_BIRTHTIME_IDX @@ -2431,6 +2449,13 @@ static PyStructSequence_Field stat_result_fields[] = { #define ST_REPARSE_TAG_IDX ST_FSTYPE_IDX #endif +#ifdef HAVE_LINUX_STATX +#define ST_ATTRIBUTES_IDX (ST_REPARSE_TAG_IDX+1) +#define ST_ATTRIBUTES_MASK_IDX (ST_ATTRIBUTES_IDX+1) +#define ST_MNT_ID_IDX (ST_ATTRIBUTES_MASK_IDX+1) +#define ST_SUBVOL_IDX (ST_MNT_ID_IDX+1) +#endif + static PyStructSequence_Desc stat_result_desc = { "stat_result", /* name */ stat_result__doc__, /* doc */ @@ -2790,6 +2815,99 @@ _pystat_fromstructstat(PyObject *module, STRUCT_STAT *st) #undef SET_ITEM } +#ifdef HAVE_LINUX_STATX +static PyObject* +_pystat_fromstructstatx(PyObject *module, struct statx *st) +{ + assert(!PyErr_Occurred()); + + PyObject *StatResultType = get_posix_state(module)->StatResultType; + PyObject *v = PyStructSequence_New((PyTypeObject *)StatResultType); + if (v == NULL) { + return NULL; + } + +#define SET_ITEM(pos, expr) \ + do { \ + PyObject *obj = (expr); \ + if (obj == NULL) { \ + goto error; \ + } \ + PyStructSequence_SET_ITEM(v, (pos), obj); \ + } while (0) + + SET_ITEM(0, PyLong_FromLong((long)st->stx_mode)); + static_assert(sizeof(unsigned long long) >= sizeof(st->stx_ino), + "statx.stx_ino is larger than unsigned long long"); + SET_ITEM(1, PyLong_FromUnsignedLongLong(st->stx_ino)); + dev_t dev = makedev(st->stx_dev_major, st->stx_dev_minor); + SET_ITEM(2, _PyLong_FromDev(dev)); + + SET_ITEM(3, PyLong_FromLong((long)st->stx_nlink)); + SET_ITEM(4, _PyLong_FromUid(st->stx_uid)); + SET_ITEM(5, _PyLong_FromGid(st->stx_gid)); + static_assert(sizeof(long long) >= sizeof(st->stx_size), + "statx.stx_size is larger than long long"); + SET_ITEM(6, PyLong_FromLongLong(st->stx_size)); + + if (fill_time(module, v, 7, 10, 13, st->stx_atime.tv_sec, + st->stx_atime.tv_nsec) < 0) { + goto error; + } + if (fill_time(module, v, 8, 11, 14, st->stx_mtime.tv_sec, + st->stx_mtime.tv_nsec) < 0) { + goto error; + } + if (fill_time(module, v, 9, 12, 15, st->stx_ctime.tv_sec, + st->stx_ctime.tv_nsec) < 0) { + goto error; + } + if (st->stx_mask & STATX_BTIME) { + if (fill_time(module, v, -1, ST_BIRTHTIME_IDX, ST_BIRTHTIME_NS_IDX, + st->stx_btime.tv_sec, st->stx_btime.tv_nsec) < 0) { + goto error; + } + } + else { + SET_ITEM(ST_BIRTHTIME_IDX, PyFloat_FromDouble(0.0)); + SET_ITEM(ST_BIRTHTIME_NS_IDX, _PyLong_GetZero()); + } + + SET_ITEM(ST_BLKSIZE_IDX, PyLong_FromLong((long)st->stx_blksize)); + SET_ITEM(ST_BLOCKS_IDX, PyLong_FromLong((long)st->stx_blocks)); + dev_t rdev = makedev(st->stx_rdev_major, st->stx_rdev_minor); + SET_ITEM(ST_RDEV_IDX, _PyLong_FromDev(rdev)); + + SET_ITEM(ST_ATTRIBUTES_IDX, PyLong_FromUnsignedLong(st->stx_attributes)); + SET_ITEM(ST_ATTRIBUTES_MASK_IDX, PyLong_FromLong(st->stx_attributes_mask)); + + if (st->stx_mask & _Py_STATX_MNT_ID) { + void* stx_mnt_id = ((unsigned char*)st) + _Py_STATX_MNT_ID_OFFSET; + SET_ITEM(ST_MNT_ID_IDX, PyLong_FromUnsignedNativeBytes(stx_mnt_id, 8, -1)); + } + else { + SET_ITEM(ST_MNT_ID_IDX, Py_None); + } + + if (st->stx_mask & _Py_STATX_SUBVOL) { + void* stx_subvol = ((unsigned char*)st) + _Py_STATX_SUBVOL_OFFSET; + SET_ITEM(ST_SUBVOL_IDX, PyLong_FromUnsignedNativeBytes(stx_subvol, 8, -1)); + } + else { + SET_ITEM(ST_SUBVOL_IDX, Py_None); + } + + assert(!PyErr_Occurred()); + return v; + +error: + Py_DECREF(v); + return NULL; + +#undef SET_ITEM +} +#endif /* HAVE_LINUX_STATX */ + /* POSIX methods */ @@ -2814,6 +2932,36 @@ posix_do_stat(PyObject *module, const char *function_name, path_t *path, fd_and_follow_symlinks_invalid("stat", path->fd, follow_symlinks)) return NULL; +#ifdef HAVE_LINUX_STATX + struct statx stx = {}; + static int statx_works = -1; + if (HAVE_LINUX_STATX_RUNTIME && statx_works != 0) { + int flags = AT_NO_AUTOMOUNT; + flags |= follow_symlinks ? 0 : AT_SYMLINK_NOFOLLOW; + Py_BEGIN_ALLOW_THREADS + if (path->fd != -1) { + flags |= AT_EMPTY_PATH; + result = statx(path->fd, "", flags, _Py_STATX_MASK, &stx); + } + else { + result = statx(dir_fd, path->narrow, flags, _Py_STATX_MASK, &stx); + } + Py_END_ALLOW_THREADS + + if (result == -1) { + if (statx_works == -1) { + statx_works = (errno != ENOSYS); + } + if (statx_works) { + return path_error(path); + } + } + else { + return _pystat_fromstructstatx(module, &stx); + } + } +#endif /* HAVE_LINUX_STATX */ + Py_BEGIN_ALLOW_THREADS if (path->fd != -1) result = FSTAT(path->fd, &st); diff --git a/Tools/c-analyzer/cpython/ignored.tsv b/Tools/c-analyzer/cpython/ignored.tsv index 15b18f5286b399..dc007c13d990be 100644 --- a/Tools/c-analyzer/cpython/ignored.tsv +++ b/Tools/c-analyzer/cpython/ignored.tsv @@ -21,6 +21,7 @@ Python/fileutils.c set_inheritable ioctl_works - # (set lazily, *after* first init) # XXX Is this thread-safe? Modules/posixmodule.c os_dup2_impl dup3_works - +Modules/posixmodule.c posix_do_stat statx_works - ## guards around resource init Python/thread_pthread.h PyThread__init_thread lib_initialized - From cebd5b7ab8922a53730a675f7dd96793b4618515 Mon Sep 17 00:00:00 2001 From: Jeffrey Bosboom Date: Mon, 7 Jul 2025 22:03:02 -0700 Subject: [PATCH 02/18] Fix versionadded documentation syntax --- Doc/library/os.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index acc8e1cfc3712a..dd98a2853d9681 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -3285,27 +3285,27 @@ features: See the :const:`!STATX_ATTR* ` constants in the :mod:`stat` module. - .. versionadded: next + .. versionadded:: next .. attribute:: st_attributes_mask Linux file attributes supported by the filesystem containing the file. - .. versionadded: next + .. versionadded:: next .. attribute:: st_mnt_id Mount ID of the mount containing the file, corresponding to the first field in ``/proc/self/mountinfo``. - .. versionadded: next + .. versionadded:: next .. attribute:: stx_subvol ID for the subvolume containing the file, or None if the filesystem does not support subvolumes. - .. versionadded: next + .. versionadded:: next On other Unix systems (such as FreeBSD), the following attributes may be available (but may be only filled out if root tries to use them): From ffd7a899119b1997ed9fa1890ca7280fcc49383a Mon Sep 17 00:00:00 2001 From: Jeffrey Bosboom Date: Mon, 7 Jul 2025 22:26:58 -0700 Subject: [PATCH 03/18] Add blurb --- .../Library/2025-07-07-22-19-48.gh-issue-83714.F_vbIm.rst | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2025-07-07-22-19-48.gh-issue-83714.F_vbIm.rst diff --git a/Misc/NEWS.d/next/Library/2025-07-07-22-19-48.gh-issue-83714.F_vbIm.rst b/Misc/NEWS.d/next/Library/2025-07-07-22-19-48.gh-issue-83714.F_vbIm.rst new file mode 100644 index 00000000000000..4290ec038b8d3c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-07-07-22-19-48.gh-issue-83714.F_vbIm.rst @@ -0,0 +1,6 @@ +Populate :attr:`~os.stat_result.st_birthtime`, +:attr:`~os.stat_result.st_attributes`, +:attr:`~os.stat_result.st_attributes_mask`, +:attr:`~os.stat_result.st_mnt_id`, and :attr:`~os.stat_result.st_subvol` +in :class:`os.stat_result` on Linux kernel 4.11 and later using the +statx system call. Contributed by Jeffrey Bosboom. From d53650a978942c07dde63134e53f088345650dc2 Mon Sep 17 00:00:00 2001 From: Jeffrey Bosboom Date: Mon, 7 Jul 2025 22:31:33 -0700 Subject: [PATCH 04/18] Fix typo stx_subvol -> st_subvol in docs --- Doc/library/os.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index dd98a2853d9681..260a2225a4501a 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -3300,7 +3300,7 @@ features: .. versionadded:: next - .. attribute:: stx_subvol + .. attribute:: st_subvol ID for the subvolume containing the file, or None if the filesystem does not support subvolumes. From 4e4e917296ba688fc8945cf2263a222797f81ecb Mon Sep 17 00:00:00 2001 From: Jeffrey Bosboom Date: Wed, 3 Sep 2025 19:17:57 -0700 Subject: [PATCH 05/18] Allow statx to be explicitly configured out for old glibc --- Modules/posixmodule.c | 3 +- aclocal.m4 | 91 +++++++++++-------- configure | 206 ++++++++++++++++++++++++++++++++---------- configure.ac | 16 ++++ pyconfig.h.in | 3 + 5 files changed, 230 insertions(+), 89 deletions(-) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 49a2d8bc6c0989..dad5f48e3401a4 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -408,9 +408,8 @@ extern char *ctermid_r(char *); # define STRUCT_STAT struct stat #endif -#if defined(__linux__) && defined(STATX_BASIC_STATS) +#ifdef HAVE_LINUX_STATX # pragma weak statx -# define HAVE_LINUX_STATX 1 # define HAVE_LINUX_STATX_RUNTIME (statx != NULL) /* provide definitions introduced later than statx itself */ # define _Py_STATX_MNT_ID 0x00001000U diff --git a/aclocal.m4 b/aclocal.m4 index 920c2b38560faa..465b10ba7f79b8 100644 --- a/aclocal.m4 +++ b/aclocal.m4 @@ -1,6 +1,6 @@ -# generated automatically by aclocal 1.16.5 -*- Autoconf -*- +# generated automatically by aclocal 1.18.1 -*- Autoconf -*- -# Copyright (C) 1996-2021 Free Software Foundation, Inc. +# Copyright (C) 1996-2025 Free Software Foundation, Inc. # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -44,12 +44,12 @@ m4_ifndef([AC_CONFIG_MACRO_DIRS], [m4_defun([_AM_CONFIG_MACRO_DIRS], [])m4_defun # Early versions of this macro (i.e., before serial 12) would not work # when interprocedural optimization (via link-time optimization) was # enabled. This would happen when, say, the GCC/clang "-flto" flag, or the -# ICC "-ipo" flag was used, for example. The problem was that under -# these conditions, the compiler did not allocate for and write the special +# ICC "-ipo" flag was used, for example. The problem was that under these +# conditions, the compiler did not allocate for and write the special # float value in the data segment of the object file, since doing so might -# not prove optimal once more context was available. Thus, the special value -# (in platform-dependent binary form) could not be found in the object file, -# and the macro would fail. +# not prove optimal once more context was available. Thus, the special +# value (in platform-dependent binary form) could not be found in the +# object file, and the macro would fail. # # The solution to the above problem was to: # @@ -68,19 +68,19 @@ m4_ifndef([AC_CONFIG_MACRO_DIRS], [m4_defun([_AM_CONFIG_MACRO_DIRS], [])m4_defun # program binary that contains the value, which the macro can then find. # # How does the exit code depend on the special value residing in memory? -# Memory, unlike variables and registers, can be addressed indirectly at run -# time. The exit code of this test program is a result of indirectly reading -# and writing to the memory region where the special value is supposed to -# reside. The actual memory addresses used and the values to be written are -# derived from the the program input ("argv") and are therefore not known at -# compile or link time. The compiler has no choice but to defer the -# computation to run time, and to prepare by allocating and populating the -# data segment with the special value. For further details, refer to the -# source code of the test program. -# -# Note that the test program is never meant to be run. It only exists to host -# a double float value in a given platform's binary format. Thus, error -# handling is not included. +# Memory, unlike variables and registers, can be addressed indirectly at +# run time. The exit code of this test program is a result of indirectly +# reading and writing to the memory region where the special value is +# supposed to reside. The actual memory addresses used and the values to +# be written are derived from the the program input ("argv") and are +# therefore not known at compile or link time. The compiler has no choice +# but to defer the computation to run time, and to prepare by allocating +# and populating the data segment with the special value. For further +# details, refer to the source code of the test program. +# +# Note that the test program is never meant to be run. It only exists to +# host a double float value in a given platform's binary format. Thus, +# error handling is not included. # # LICENSE # @@ -91,7 +91,7 @@ m4_ifndef([AC_CONFIG_MACRO_DIRS], [m4_defun([_AM_CONFIG_MACRO_DIRS], [])m4_defun # and this notice are preserved. This file is offered as-is, without any # warranty. -#serial 14 +#serial 13 AC_DEFUN([AX_C_FLOAT_WORDS_BIGENDIAN], [AC_CACHE_CHECK(whether float word ordering is bigendian, @@ -112,10 +112,10 @@ int main (int argc, char *argv[]) ]])], [ -if grep noonsees conftest* > /dev/null ; then +if grep noonsees conftest$EXEEXT >/dev/null ; then ax_cv_c_float_words_bigendian=yes fi -if grep seesnoon conftest* >/dev/null ; then +if grep seesnoon conftest$EXEEXT >/dev/null ; then if test "$ax_cv_c_float_words_bigendian" = unknown; then ax_cv_c_float_words_bigendian=no else @@ -181,14 +181,24 @@ esac # and this notice are preserved. This file is offered as-is, without any # warranty. -#serial 6 +#serial 11 AC_DEFUN([AX_CHECK_COMPILE_FLAG], [AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_IF AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl -AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [ +AC_CACHE_CHECK([whether the _AC_LANG compiler accepts $1], CACHEVAR, [ ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS - _AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1" + if test x"m4_case(_AC_LANG, + [C], [$GCC], + [C++], [$GXX], + [Fortran], [$GFC], + [Fortran 77], [$G77], + [Objective C], [$GOBJC], + [Objective C++], [$GOBJCXX], + [no])" = xyes ; then + add_gnu_werror="-Werror" + fi + _AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1 $add_gnu_werror" AC_COMPILE_IFELSE([m4_default([$5],[AC_LANG_PROGRAM()])], [AS_VAR_SET(CACHEVAR,[yes])], [AS_VAR_SET(CACHEVAR,[no])]) @@ -224,7 +234,7 @@ AS_VAR_POPDEF([CACHEVAR])dnl # and this notice are preserved. This file is offered as-is, without any # warranty. -#serial 11 +#serial 12 AU_ALIAS([AC_CHECK_DEFINED], [AC_CHECK_DEFINE]) AC_DEFUN([AC_CHECK_DEFINE],[ @@ -264,8 +274,8 @@ AC_CACHE_CHECK([for $2], ac_var, dnl AC_LANG_FUNC_LINK_TRY [AC_LINK_IFELSE([AC_LANG_PROGRAM([$1 #undef $2 - char $2 ();],[ - char (*f) () = $2; + char $2 (void);],[ + char (*f) (void) = $2; return f != $2; ])], [AS_VAR_SET(ac_var, yes)], [AS_VAR_SET(ac_var, no)])]) @@ -399,7 +409,7 @@ AC_DEFUN([AX_CHECK_OPENSSL], [ ]) # pkg.m4 - Macros to locate and use pkg-config. -*- Autoconf -*- -# serial 12 (pkg-config-0.29.2) +# serial 13 (pkgconf) dnl Copyright © 2004 Scott James Remnant . dnl Copyright © 2012-2015 Dan Nicholson @@ -415,9 +425,7 @@ dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU dnl General Public License for more details. dnl dnl You should have received a copy of the GNU General Public License -dnl along with this program; if not, write to the Free Software -dnl Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA -dnl 02111-1307, USA. +dnl along with this program; if not, see . dnl dnl As a special exception to the GNU General Public License, if you dnl distribute this file as part of a program that contains a @@ -446,8 +454,8 @@ m4_if(m4_version_compare(PKG_MACROS_VERSION, [$1]), -1, [m4_fatal([pkg.m4 version $1 or higher is required but ]PKG_MACROS_VERSION[ found])]) ])dnl PKG_PREREQ -dnl PKG_PROG_PKG_CONFIG([MIN-VERSION]) -dnl ---------------------------------- +dnl PKG_PROG_PKG_CONFIG([MIN-VERSION], [ACTION-IF-NOT-FOUND]) +dnl --------------------------------------------------------- dnl Since: 0.16 dnl dnl Search for the pkg-config tool and set the PKG_CONFIG variable to @@ -455,6 +463,12 @@ dnl first found in the path. Checks that the version of pkg-config found dnl is at least MIN-VERSION. If MIN-VERSION is not specified, 0.9.0 is dnl used since that's the first version where most current features of dnl pkg-config existed. +dnl +dnl If pkg-config is not found or older than specified, it will result +dnl in an empty PKG_CONFIG variable. To avoid widespread issues with +dnl scripts not checking it, ACTION-IF-NOT-FOUND defaults to aborting. +dnl You can specify [PKG_CONFIG=false] as an action instead, which would +dnl result in pkg-config tests failing, but no bogus error messages. AC_DEFUN([PKG_PROG_PKG_CONFIG], [m4_pattern_forbid([^_?PKG_[A-Z_]+$]) m4_pattern_allow([^PKG_CONFIG(_(PATH|LIBDIR|SYSROOT_DIR|ALLOW_SYSTEM_(CFLAGS|LIBS)))?$]) @@ -475,6 +489,9 @@ if test -n "$PKG_CONFIG"; then AC_MSG_RESULT([no]) PKG_CONFIG="" fi +fi +if test -z "$PKG_CONFIG"; then + m4_default([$2], [AC_MSG_ERROR([pkg-config not found])]) fi[]dnl ])dnl PKG_PROG_PKG_CONFIG @@ -744,7 +761,7 @@ AS_IF([test "$AS_TR_SH([with_]m4_tolower([$1]))" = "yes"], # AM_CONDITIONAL -*- Autoconf -*- -# Copyright (C) 1997-2021 Free Software Foundation, Inc. +# Copyright (C) 1997-2025 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -775,7 +792,7 @@ AC_CONFIG_COMMANDS_PRE( Usually this means the macro was only invoked conditionally.]]) fi])]) -# Copyright (C) 2006-2021 Free Software Foundation, Inc. +# Copyright (C) 2006-2025 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, diff --git a/configure b/configure index 9df366697b8546..d6df3abe711c77 100755 --- a/configure +++ b/configure @@ -1138,6 +1138,7 @@ with_openssl_rpath with_ssl_default_suites with_builtin_hashlib_hashes enable_test_modules +with_linux_statx ' ac_precious_vars='build_alias host_alias @@ -1962,6 +1963,8 @@ Optional Packages: --with-builtin-hashlib-hashes=md5,sha1,sha2,sha3,blake2 builtin hash modules, md5, sha1, sha2, sha3 (with shake), blake2 + --without-linux-statx don't provide os.statx nor + os.stat_result.st_birthtime Some influential environment variables: PKG_CONFIG path to pkg-config utility @@ -4076,6 +4079,9 @@ printf "%s\n" "yes" >&6; } printf "%s\n" "no" >&6; } PKG_CONFIG="" fi +fi +if test -z "$PKG_CONFIG"; then + as_fn_error $? "pkg-config not found" "$LINENO" 5 fi ;; #( no) : @@ -8415,15 +8421,18 @@ if test "$Py_OPT" = 'true' ; then if test "x$ac_cv_gcc_compat" = xyes then : - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -fno-semantic-interposition" >&5 -printf %s "checking whether C compiler accepts -fno-semantic-interposition... " >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the C compiler accepts -fno-semantic-interposition" >&5 +printf %s "checking whether the C compiler accepts -fno-semantic-interposition... " >&6; } if test ${ax_cv_check_cflags__Werror__fno_semantic_interposition+y} then : printf %s "(cached) " >&6 else case e in #( e) ax_check_save_flags=$CFLAGS - CFLAGS="$CFLAGS -Werror -fno-semantic-interposition" + if test x"$GCC" = xyes ; then + add_gnu_werror="-Werror" + fi + CFLAGS="$CFLAGS -Werror -fno-semantic-interposition $add_gnu_werror" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -8553,15 +8562,18 @@ if test "$Py_LTO" = 'true' ; then case $ac_cv_cc_name in clang) LDFLAGS_NOLTO="-fno-lto" - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -flto=thin" >&5 -printf %s "checking whether C compiler accepts -flto=thin... " >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the C compiler accepts -flto=thin" >&5 +printf %s "checking whether the C compiler accepts -flto=thin... " >&6; } if test ${ax_cv_check_cflags___flto_thin+y} then : printf %s "(cached) " >&6 else case e in #( e) ax_check_save_flags=$CFLAGS - CFLAGS="$CFLAGS -flto=thin" + if test x"$GCC" = xyes ; then + add_gnu_werror="-Werror" + fi + CFLAGS="$CFLAGS -flto=thin $add_gnu_werror" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -8738,15 +8750,18 @@ printf "%s\n" "$as_me: llvm-ar found via xcrun: ${LLVM_AR}" >&6;} if test $Py_LTO_POLICY = default then # Check that ThinLTO is accepted. - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -flto=thin" >&5 -printf %s "checking whether C compiler accepts -flto=thin... " >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the C compiler accepts -flto=thin" >&5 +printf %s "checking whether the C compiler accepts -flto=thin... " >&6; } if test ${ax_cv_check_cflags___flto_thin+y} then : printf %s "(cached) " >&6 else case e in #( e) ax_check_save_flags=$CFLAGS - CFLAGS="$CFLAGS -flto=thin" + if test x"$GCC" = xyes ; then + add_gnu_werror="-Werror" + fi + CFLAGS="$CFLAGS -flto=thin $add_gnu_werror" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -8795,15 +8810,18 @@ fi if test $Py_LTO_POLICY = default then # Check that ThinLTO is accepted - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -flto=thin" >&5 -printf %s "checking whether C compiler accepts -flto=thin... " >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the C compiler accepts -flto=thin" >&5 +printf %s "checking whether the C compiler accepts -flto=thin... " >&6; } if test ${ax_cv_check_cflags___flto_thin+y} then : printf %s "(cached) " >&6 else case e in #( e) ax_check_save_flags=$CFLAGS - CFLAGS="$CFLAGS -flto=thin" + if test x"$GCC" = xyes ; then + add_gnu_werror="-Werror" + fi + CFLAGS="$CFLAGS -flto=thin $add_gnu_werror" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -9080,15 +9098,18 @@ if test "$Py_BOLT" = 'true' ; then # -fno-reorder-blocks-and-partition is required for bolt to work. # Possibly GCC only. - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -fno-reorder-blocks-and-partition" >&5 -printf %s "checking whether C compiler accepts -fno-reorder-blocks-and-partition... " >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the C compiler accepts -fno-reorder-blocks-and-partition" >&5 +printf %s "checking whether the C compiler accepts -fno-reorder-blocks-and-partition... " >&6; } if test ${ax_cv_check_cflags___fno_reorder_blocks_and_partition+y} then : printf %s "(cached) " >&6 else case e in #( e) ax_check_save_flags=$CFLAGS - CFLAGS="$CFLAGS -fno-reorder-blocks-and-partition" + if test x"$GCC" = xyes ; then + add_gnu_werror="-Werror" + fi + CFLAGS="$CFLAGS -fno-reorder-blocks-and-partition $add_gnu_werror" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -9736,15 +9757,18 @@ printf "%s\n" "$enable_safety" >&6; } if test "$enable_safety" = "yes" then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -fstack-protector-strong" >&5 -printf %s "checking whether C compiler accepts -fstack-protector-strong... " >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the C compiler accepts -fstack-protector-strong" >&5 +printf %s "checking whether the C compiler accepts -fstack-protector-strong... " >&6; } if test ${ax_cv_check_cflags__Werror__fstack_protector_strong+y} then : printf %s "(cached) " >&6 else case e in #( e) ax_check_save_flags=$CFLAGS - CFLAGS="$CFLAGS -Werror -fstack-protector-strong" + if test x"$GCC" = xyes ; then + add_gnu_werror="-Werror" + fi + CFLAGS="$CFLAGS -Werror -fstack-protector-strong $add_gnu_werror" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -9778,15 +9802,18 @@ printf "%s\n" "$as_me: WARNING: -fstack-protector-strong not supported" >&2;} ;; esac fi - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Wtrampolines" >&5 -printf %s "checking whether C compiler accepts -Wtrampolines... " >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the C compiler accepts -Wtrampolines" >&5 +printf %s "checking whether the C compiler accepts -Wtrampolines... " >&6; } if test ${ax_cv_check_cflags__Werror__Wtrampolines+y} then : printf %s "(cached) " >&6 else case e in #( e) ax_check_save_flags=$CFLAGS - CFLAGS="$CFLAGS -Werror -Wtrampolines" + if test x"$GCC" = xyes ; then + add_gnu_werror="-Werror" + fi + CFLAGS="$CFLAGS -Werror -Wtrampolines $add_gnu_werror" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -9820,15 +9847,18 @@ printf "%s\n" "$as_me: WARNING: -Wtrampolines not supported" >&2;} ;; esac fi - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Wimplicit-fallthrough" >&5 -printf %s "checking whether C compiler accepts -Wimplicit-fallthrough... " >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the C compiler accepts -Wimplicit-fallthrough" >&5 +printf %s "checking whether the C compiler accepts -Wimplicit-fallthrough... " >&6; } if test ${ax_cv_check_cflags__Werror__Wimplicit_fallthrough+y} then : printf %s "(cached) " >&6 else case e in #( e) ax_check_save_flags=$CFLAGS - CFLAGS="$CFLAGS -Werror -Wimplicit-fallthrough" + if test x"$GCC" = xyes ; then + add_gnu_werror="-Werror" + fi + CFLAGS="$CFLAGS -Werror -Wimplicit-fallthrough $add_gnu_werror" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -9862,15 +9892,18 @@ printf "%s\n" "$as_me: WARNING: -Wimplicit-fallthrough not supported" >&2;} ;; esac fi - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Werror=format-security" >&5 -printf %s "checking whether C compiler accepts -Werror=format-security... " >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the C compiler accepts -Werror=format-security" >&5 +printf %s "checking whether the C compiler accepts -Werror=format-security... " >&6; } if test ${ax_cv_check_cflags__Werror__Werror_format_security+y} then : printf %s "(cached) " >&6 else case e in #( e) ax_check_save_flags=$CFLAGS - CFLAGS="$CFLAGS -Werror -Werror=format-security" + if test x"$GCC" = xyes ; then + add_gnu_werror="-Werror" + fi + CFLAGS="$CFLAGS -Werror -Werror=format-security $add_gnu_werror" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -9904,15 +9937,18 @@ printf "%s\n" "$as_me: WARNING: -Werror=format-security not supported" >&2;} ;; esac fi - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Wbidi-chars=any" >&5 -printf %s "checking whether C compiler accepts -Wbidi-chars=any... " >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the C compiler accepts -Wbidi-chars=any" >&5 +printf %s "checking whether the C compiler accepts -Wbidi-chars=any... " >&6; } if test ${ax_cv_check_cflags__Werror__Wbidi_chars_any+y} then : printf %s "(cached) " >&6 else case e in #( e) ax_check_save_flags=$CFLAGS - CFLAGS="$CFLAGS -Werror -Wbidi-chars=any" + if test x"$GCC" = xyes ; then + add_gnu_werror="-Werror" + fi + CFLAGS="$CFLAGS -Werror -Wbidi-chars=any $add_gnu_werror" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -9946,15 +9982,18 @@ printf "%s\n" "$as_me: WARNING: -Wbidi-chars=any not supported" >&2;} ;; esac fi - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Wall" >&5 -printf %s "checking whether C compiler accepts -Wall... " >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the C compiler accepts -Wall" >&5 +printf %s "checking whether the C compiler accepts -Wall... " >&6; } if test ${ax_cv_check_cflags__Werror__Wall+y} then : printf %s "(cached) " >&6 else case e in #( e) ax_check_save_flags=$CFLAGS - CFLAGS="$CFLAGS -Werror -Wall" + if test x"$GCC" = xyes ; then + add_gnu_werror="-Werror" + fi + CFLAGS="$CFLAGS -Werror -Wall $add_gnu_werror" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -10012,15 +10051,18 @@ printf "%s\n" "$enable_slower_safety" >&6; } if test "$enable_slower_safety" = "yes" then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -D_FORTIFY_SOURCE=3" >&5 -printf %s "checking whether C compiler accepts -D_FORTIFY_SOURCE=3... " >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the C compiler accepts -D_FORTIFY_SOURCE=3" >&5 +printf %s "checking whether the C compiler accepts -D_FORTIFY_SOURCE=3... " >&6; } if test ${ax_cv_check_cflags__Werror__D_FORTIFY_SOURCE_3+y} then : printf %s "(cached) " >&6 else case e in #( e) ax_check_save_flags=$CFLAGS - CFLAGS="$CFLAGS -Werror -D_FORTIFY_SOURCE=3" + if test x"$GCC" = xyes ; then + add_gnu_werror="-Werror" + fi + CFLAGS="$CFLAGS -Werror -D_FORTIFY_SOURCE=3 $add_gnu_werror" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -10621,15 +10663,18 @@ printf "%s\n" "$CC" >&6; } # Error on unguarded use of new symbols, which will fail at runtime for # users on older versions of macOS - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Wunguarded-availability" >&5 -printf %s "checking whether C compiler accepts -Wunguarded-availability... " >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the C compiler accepts -Wunguarded-availability" >&5 +printf %s "checking whether the C compiler accepts -Wunguarded-availability... " >&6; } if test ${ax_cv_check_cflags__Werror__Wunguarded_availability+y} then : printf %s "(cached) " >&6 else case e in #( e) ax_check_save_flags=$CFLAGS - CFLAGS="$CFLAGS -Werror -Wunguarded-availability" + if test x"$GCC" = xyes ; then + add_gnu_werror="-Werror" + fi + CFLAGS="$CFLAGS -Werror -Wunguarded-availability $add_gnu_werror" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -13364,15 +13409,18 @@ then : withval=$with_memory_sanitizer; { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $withval" >&5 printf "%s\n" "$withval" >&6; } -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -fsanitize=memory" >&5 -printf %s "checking whether C compiler accepts -fsanitize=memory... " >&6; } +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the C compiler accepts -fsanitize=memory" >&5 +printf %s "checking whether the C compiler accepts -fsanitize=memory... " >&6; } if test ${ax_cv_check_cflags___fsanitize_memory+y} then : printf %s "(cached) " >&6 else case e in #( e) ax_check_save_flags=$CFLAGS - CFLAGS="$CFLAGS -fsanitize=memory" + if test x"$GCC" = xyes ; then + add_gnu_werror="-Werror" + fi + CFLAGS="$CFLAGS -fsanitize=memory $add_gnu_werror" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -25842,10 +25890,10 @@ if ac_fn_c_try_link "$LINENO" then : -if grep noonsees conftest* > /dev/null ; then +if grep noonsees conftest$EXEEXT >/dev/null ; then ax_cv_c_float_words_bigendian=yes fi -if grep seesnoon conftest* >/dev/null ; then +if grep seesnoon conftest$EXEEXT >/dev/null ; then if test "$ax_cv_c_float_words_bigendian" = unknown; then ax_cv_c_float_words_bigendian=no else @@ -30994,6 +31042,58 @@ fi printf "%s\n" "$TEST_MODULES" >&6; } +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for --without-linux-statx" >&5 +printf %s "checking for --without-linux-statx... " >&6; } + +# Check whether --with-linux-statx was given. +if test ${with_linux_statx+y} +then : + withval=$with_linux_statx; +else case e in #( + e) case $ac_sys_system in #( + Linux*) : + with_linux_statx=yes ;; #( + *) : + with_linux_statx=no ;; +esac ;; +esac +fi + +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $with_linux_statx" >&5 +printf "%s\n" "$with_linux_statx" >&6; } +if test "x$with_linux_statx" != xno +then : + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + #include + +int +main (void) +{ +void* p = statx + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + +printf "%s\n" "#define HAVE_LINUX_STATX 1" >>confdefs.h + + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +printf "%s\n" "yes" >&6; } +else case e in #( + e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf "%s\n" "no" >&6; } + as_fn_error $? "statx syscall wrapper function not available; upgrade to + glibc 2.28 or later or use --without-linux-statx" "$LINENO" 5 + ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext +fi + # gh-109054: Check if -latomic is needed to get atomic functions. # On Linux aarch64, GCC may require programs and libraries to be linked # explicitly to libatomic. Call _Py_atomic_or_uint64() which may require @@ -32578,15 +32678,18 @@ fi if test "$ac_sys_system" != "Linux-android" -a "$ac_sys_system" != "WASI" || \ { test -n "$ANDROID_API_LEVEL" && test "$ANDROID_API_LEVEL" -ge 28; } then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -msse -msse2 -msse3 -msse4.1 -msse4.2" >&5 -printf %s "checking whether C compiler accepts -msse -msse2 -msse3 -msse4.1 -msse4.2... " >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the C compiler accepts -msse -msse2 -msse3 -msse4.1 -msse4.2" >&5 +printf %s "checking whether the C compiler accepts -msse -msse2 -msse3 -msse4.1 -msse4.2... " >&6; } if test ${ax_cv_check_cflags__Werror__msse__msse2__msse3__msse4_1__msse4_2+y} then : printf %s "(cached) " >&6 else case e in #( e) ax_check_save_flags=$CFLAGS - CFLAGS="$CFLAGS -Werror -msse -msse2 -msse3 -msse4.1 -msse4.2" + if test x"$GCC" = xyes ; then + add_gnu_werror="-Werror" + fi + CFLAGS="$CFLAGS -Werror -msse -msse2 -msse3 -msse4.1 -msse4.2 $add_gnu_werror" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -32655,15 +32758,18 @@ fi if test "$ac_sys_system" != "Linux-android" -a "$ac_sys_system" != "WASI" || \ { test -n "$ANDROID_API_LEVEL" && test "$ANDROID_API_LEVEL" -ge 28; } then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -mavx2" >&5 -printf %s "checking whether C compiler accepts -mavx2... " >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the C compiler accepts -mavx2" >&5 +printf %s "checking whether the C compiler accepts -mavx2... " >&6; } if test ${ax_cv_check_cflags__Werror__mavx2+y} then : printf %s "(cached) " >&6 else case e in #( e) ax_check_save_flags=$CFLAGS - CFLAGS="$CFLAGS -Werror -mavx2" + if test x"$GCC" = xyes ; then + add_gnu_werror="-Werror" + fi + CFLAGS="$CFLAGS -Werror -mavx2 $add_gnu_werror" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ diff --git a/configure.ac b/configure.ac index cb7f2144345b37..e469cc8882c87e 100644 --- a/configure.ac +++ b/configure.ac @@ -7618,6 +7618,22 @@ AC_ARG_ENABLE([test-modules], AC_MSG_RESULT([$TEST_MODULES]) AC_SUBST([TEST_MODULES]) +AC_MSG_CHECKING([for --without-linux-statx]) +AC_ARG_WITH([linux-statx], + [AS_HELP_STRING([--without-linux-statx], [don't provide os.statx nor os.stat_result.st_birthtime])], + [], [AS_CASE($ac_sys_system, [Linux*], [with_linux_statx=yes], [with_linux_statx=no])]) +AC_MSG_RESULT([$with_linux_statx]) +AS_IF([test "x$with_linux_statx" != xno], + [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ + #include + ]], [[void* p = statx]])], + [AC_DEFINE(HAVE_LINUX_STATX, 1, Define if you have the 'statx' function.) + AC_MSG_RESULT(yes)], + [AC_MSG_RESULT(no) + AC_MSG_ERROR([statx syscall wrapper function not available; upgrade to + glibc 2.28 or later or use --without-linux-statx]) + ])]) + # gh-109054: Check if -latomic is needed to get atomic functions. # On Linux aarch64, GCC may require programs and libraries to be linked # explicitly to libatomic. Call _Py_atomic_or_uint64() which may require diff --git a/pyconfig.h.in b/pyconfig.h.in index d7c496fccc682c..30f88f18766e2d 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -766,6 +766,9 @@ /* Define to 1 if you have the header file. */ #undef HAVE_LINUX_SOUNDCARD_H +/* Define if you have the 'statx' function. */ +#undef HAVE_LINUX_STATX + /* Define to 1 if you have the header file. */ #undef HAVE_LINUX_TIPC_H From bc3155b1ebeb9658a5b754377e4e19324c84364e Mon Sep 17 00:00:00 2001 From: Jeffrey Bosboom Date: Thu, 4 Sep 2025 00:56:55 -0700 Subject: [PATCH 06/18] Implement os.statx Co-authored-by: Erin of Yukis --- .../pycore_global_objects_fini_generated.h | 2 + Include/internal/pycore_global_strings.h | 2 + .../internal/pycore_runtime_init_generated.h | 2 + .../internal/pycore_unicodeobject_generated.h | 8 + Lib/os.py | 6 + Lib/stat.py | 4 +- ...5-07-07-22-19-48.gh-issue-83714.F_vbIm.rst | 9 +- Modules/clinic/posixmodule.c.h | 143 ++++++- Modules/posixmodule.c | 351 ++++++++++++++---- 9 files changed, 456 insertions(+), 71 deletions(-) diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h index c461bc1786ddf4..c50bc9d29c2bdf 100644 --- a/Include/internal/pycore_global_objects_fini_generated.h +++ b/Include/internal/pycore_global_objects_fini_generated.h @@ -1080,6 +1080,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(loop)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(manual_reset)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(mapping)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(mask)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(match)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(max_length)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(maxdigits)); @@ -1256,6 +1257,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(sub_key)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(subcalls)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(symmetric_difference_update)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(sync)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(tabsize)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(tag)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(target)); diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h index 72c2051bd97660..0cd43ce65fb59f 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -571,6 +571,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(loop) STRUCT_FOR_ID(manual_reset) STRUCT_FOR_ID(mapping) + STRUCT_FOR_ID(mask) STRUCT_FOR_ID(match) STRUCT_FOR_ID(max_length) STRUCT_FOR_ID(maxdigits) @@ -747,6 +748,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(sub_key) STRUCT_FOR_ID(subcalls) STRUCT_FOR_ID(symmetric_difference_update) + STRUCT_FOR_ID(sync) STRUCT_FOR_ID(tabsize) STRUCT_FOR_ID(tag) STRUCT_FOR_ID(target) diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h index d378fcae26cf35..ca6d9403cc7efc 100644 --- a/Include/internal/pycore_runtime_init_generated.h +++ b/Include/internal/pycore_runtime_init_generated.h @@ -1078,6 +1078,7 @@ extern "C" { INIT_ID(loop), \ INIT_ID(manual_reset), \ INIT_ID(mapping), \ + INIT_ID(mask), \ INIT_ID(match), \ INIT_ID(max_length), \ INIT_ID(maxdigits), \ @@ -1254,6 +1255,7 @@ extern "C" { INIT_ID(sub_key), \ INIT_ID(subcalls), \ INIT_ID(symmetric_difference_update), \ + INIT_ID(sync), \ INIT_ID(tabsize), \ INIT_ID(tag), \ INIT_ID(target), \ diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h index e516211f6c6cbc..d11900a53552c6 100644 --- a/Include/internal/pycore_unicodeobject_generated.h +++ b/Include/internal/pycore_unicodeobject_generated.h @@ -2072,6 +2072,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); assert(PyUnicode_GET_LENGTH(string) != 1); + string = &_Py_ID(mask); + _PyUnicode_InternStatic(interp, &string); + assert(_PyUnicode_CheckConsistency(string, 1)); + assert(PyUnicode_GET_LENGTH(string) != 1); string = &_Py_ID(match); _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); @@ -2776,6 +2780,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); assert(PyUnicode_GET_LENGTH(string) != 1); + string = &_Py_ID(sync); + _PyUnicode_InternStatic(interp, &string); + assert(_PyUnicode_CheckConsistency(string, 1)); + assert(PyUnicode_GET_LENGTH(string) != 1); string = &_Py_ID(tabsize); _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); diff --git a/Lib/os.py b/Lib/os.py index 12926c832f5ba5..0f3e7f82d94e6f 100644 --- a/Lib/os.py +++ b/Lib/os.py @@ -131,6 +131,8 @@ def _add(str, fn): _add("HAVE_UNLINKAT", "unlink") _add("HAVE_UNLINKAT", "rmdir") _add("HAVE_UTIMENSAT", "utime") + if _exists("statx"): + _set.add(statx) supports_dir_fd = _set _set = set() @@ -152,6 +154,8 @@ def _add(str, fn): _add("HAVE_FPATHCONF", "pathconf") if _exists("statvfs") and _exists("fstatvfs"): # mac os x10.3 _add("HAVE_FSTATVFS", "statvfs") + if _exists("statx"): + _set.add(statx) supports_fd = _set _set = set() @@ -190,6 +194,8 @@ def _add(str, fn): _add("HAVE_FSTATAT", "stat") _add("HAVE_UTIMENSAT", "utime") _add("MS_WINDOWS", "stat") + if _exists("statx"): + _set.add(statx) supports_follow_symlinks = _set del _set diff --git a/Lib/stat.py b/Lib/stat.py index ef8f98758f8ddf..ab1b25b9d6351c 100644 --- a/Lib/stat.py +++ b/Lib/stat.py @@ -200,8 +200,8 @@ def filemode(mode): FILE_ATTRIBUTE_VIRTUAL = 65536 -# Linux STATX_ATTR constants for interpreting os.stat()'s -# "st_attributes" and "st_attributes_mask" members +# Linux STATX_ATTR constants for interpreting os.statx()'s +# "stx_attributes" and "stx_attributes_mask" members STATX_ATTR_COMPRESSED = 0x00000004 STATX_ATTR_IMMUTABLE = 0x00000010 diff --git a/Misc/NEWS.d/next/Library/2025-07-07-22-19-48.gh-issue-83714.F_vbIm.rst b/Misc/NEWS.d/next/Library/2025-07-07-22-19-48.gh-issue-83714.F_vbIm.rst index 4290ec038b8d3c..ffbf5232e0d2b0 100644 --- a/Misc/NEWS.d/next/Library/2025-07-07-22-19-48.gh-issue-83714.F_vbIm.rst +++ b/Misc/NEWS.d/next/Library/2025-07-07-22-19-48.gh-issue-83714.F_vbIm.rst @@ -1,6 +1,3 @@ -Populate :attr:`~os.stat_result.st_birthtime`, -:attr:`~os.stat_result.st_attributes`, -:attr:`~os.stat_result.st_attributes_mask`, -:attr:`~os.stat_result.st_mnt_id`, and :attr:`~os.stat_result.st_subvol` -in :class:`os.stat_result` on Linux kernel 4.11 and later using the -statx system call. Contributed by Jeffrey Bosboom. +Provide :func:`os.statx` and populate :attr:`~os.stat_result.st_birthtime` +in the return value of :func:`os.stat` on Linux kernel 4.11 and later using +the statx system call. Contributed by Jeffrey Bosboom and Erin of Yukis. diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index 3621a0625411d3..d4c0c7c91398a5 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -186,6 +186,143 @@ os_lstat(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kw return return_value; } +#if defined(HAVE_LINUX_STATX) + +PyDoc_STRVAR(os_statx__doc__, +"statx($module, /, path, mask, *, dir_fd=None, follow_symlinks=True,\n" +" sync=None, raw=False)\n" +"--\n" +"\n" +"Perform a statx system call on the given path.\n" +"\n" +" path\n" +" Path to be examined; can be string, bytes, a path-like object or\n" +" open-file-descriptor int.\n" +" mask\n" +" A bitmask of STATX_* constants defining the requested information.\n" +" dir_fd\n" +" If not None, it should be a file descriptor open to a directory,\n" +" and path should be a relative string; path will then be relative to\n" +" that directory.\n" +" follow_symlinks\n" +" If False, and the last element of the path is a symbolic link,\n" +" statx will examine the symbolic link itself instead of the file\n" +" the link points to.\n" +" sync\n" +" If True, statx will return up-to-date values, even if doing so is\n" +" expensive. If False, statx will return cached values if possible.\n" +" If None, statx lets the operating system decide.\n" +" raw\n" +" If False, fields that were not requested or that are not valid will be\n" +" None. If True, all fields will be initialized with the (possibly fake)\n" +" kernel-provided value; use stx_mask to check validity.\n" +"\n" +"It\'s an error to use dir_fd or follow_symlinks when specifying path as\n" +" an open file descriptor."); + +#define OS_STATX_METHODDEF \ + {"statx", _PyCFunction_CAST(os_statx), METH_FASTCALL|METH_KEYWORDS, os_statx__doc__}, + +static PyObject * +os_statx_impl(PyObject *module, path_t *path, unsigned int mask, int dir_fd, + int follow_symlinks, int sync, int raw); + +static PyObject * +os_statx(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 6 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + Py_hash_t ob_hash; + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_hash = -1, + .ob_item = { &_Py_ID(path), &_Py_ID(mask), &_Py_ID(dir_fd), &_Py_ID(follow_symlinks), &_Py_ID(sync), &_Py_ID(raw), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"path", "mask", "dir_fd", "follow_symlinks", "sync", "raw", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "statx", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[6]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 2; + path_t path = PATH_T_INITIALIZE_P("statx", "path", 0, 0, 0, 1); + unsigned int mask; + int dir_fd = DEFAULT_DIR_FD; + int follow_symlinks = 1; + int sync = -1; + int raw = 0; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 2, /*maxpos*/ 2, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { + goto exit; + } + if (!path_converter(args[0], &path)) { + goto exit; + } + mask = (unsigned int)PyLong_AsUnsignedLongMask(args[1]); + if (mask == (unsigned int)-1 && PyErr_Occurred()) { + goto exit; + } + if (!noptargs) { + goto skip_optional_kwonly; + } + if (args[2]) { + if (!dir_fd_converter(args[2], &dir_fd)) { + goto exit; + } + if (!--noptargs) { + goto skip_optional_kwonly; + } + } + if (args[3]) { + follow_symlinks = PyObject_IsTrue(args[3]); + if (follow_symlinks < 0) { + goto exit; + } + if (!--noptargs) { + goto skip_optional_kwonly; + } + } + if (args[4]) { + if (!optional_bool_converter(args[4], &sync)) { + goto exit; + } + if (!--noptargs) { + goto skip_optional_kwonly; + } + } + raw = PyObject_IsTrue(args[5]); + if (raw < 0) { + goto exit; + } +skip_optional_kwonly: + return_value = os_statx_impl(module, &path, mask, dir_fd, follow_symlinks, sync, raw); + +exit: + /* Cleanup for path */ + path_cleanup(&path); + + return return_value; +} + +#endif /* defined(HAVE_LINUX_STATX) */ + PyDoc_STRVAR(os_access__doc__, "access($module, /, path, mode, *, dir_fd=None, effective_ids=False,\n" " follow_symlinks=True)\n" @@ -12727,6 +12864,10 @@ os__emscripten_debugger(PyObject *module, PyObject *Py_UNUSED(ignored)) #endif /* defined(__EMSCRIPTEN__) */ +#ifndef OS_STATX_METHODDEF + #define OS_STATX_METHODDEF +#endif /* !defined(OS_STATX_METHODDEF) */ + #ifndef OS_TTYNAME_METHODDEF #define OS_TTYNAME_METHODDEF #endif /* !defined(OS_TTYNAME_METHODDEF) */ @@ -13398,4 +13539,4 @@ os__emscripten_debugger(PyObject *module, PyObject *Py_UNUSED(ignored)) #ifndef OS__EMSCRIPTEN_DEBUGGER_METHODDEF #define OS__EMSCRIPTEN_DEBUGGER_METHODDEF #endif /* !defined(OS__EMSCRIPTEN_DEBUGGER_METHODDEF) */ -/*[clinic end generated code: output=ae64df0389746258 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=7412c1de31240cc7 input=a9049054013a1b77]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index dad5f48e3401a4..a279b7d2da4a44 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -410,14 +410,27 @@ extern char *ctermid_r(char *); #ifdef HAVE_LINUX_STATX # pragma weak statx -# define HAVE_LINUX_STATX_RUNTIME (statx != NULL) -/* provide definitions introduced later than statx itself */ -# define _Py_STATX_MNT_ID 0x00001000U -# define _Py_STATX_SUBVOL 0x00008000U -# define _Py_STATX_MASK (STATX_BASIC_STATS | STATX_BTIME | _Py_STATX_MNT_ID | _Py_STATX_SUBVOL) -# define _Py_STATX_MNT_ID_OFFSET 144 -# define _Py_STATX_SUBVOL_OFFSET 160 -#endif +/* provide constants introduced later than statx itself */ +# ifndef STATX_MNT_ID +# define STATX_MNT_ID 0x00001000U +# endif +# ifndef STATX_DIOALIGN +# define STATX_DIOALIGN 0x00002000U +# endif +# ifndef STATX_MNT_ID_UNIQUE +# define STATX_MNT_ID_UNIQUE 0x00004000U +# endif +# ifndef STATX_SUBVOL +# define STATX_SUBVOL 0x00008000U +# endif +# ifndef STATX_WRITE_ATOMIC +# define STATX_WRITE_ATOMIC 0x00010000U +# endif +# ifndef STATX_DIO_READ_ALIGN +# define STATX_DIO_READ_ALIGN 0x00020000U +# endif +# define _Py_STATX_KNOWN (STATX_BASIC_STATS | STATX_MNT_ID | STATX_DIOALIGN | STATX_MNT_ID_UNIQUE | STATX_SUBVOL | STATX_WRITE_ATOMIC | STATX_DIO_READ_ALIGN) +#endif /* HAVE_LINUX_STATX */ #if !defined(EX_OK) && defined(EXIT_SUCCESS) @@ -2380,10 +2393,18 @@ static PyStructSequence_Field stat_result_fields[] = { {"st_reparse_tag", "Windows reparse tag"}, #endif #ifdef HAVE_LINUX_STATX - {"st_attributes", "Linux file attribute bits"}, - {"st_attributes_mask", "Linux file attribute bits supported by this file"}, - {"st_mnt_id", "Linux mount ID for /proc/self/mountinfo"}, - {"st_subvol", "Linux subvolume identifier"}, + {"stx_mask", "Linux member validity bitmask"}, + {"stx_attributes", "Linux file attribute bits"}, + {"stx_attributes_mask", "Linux file attribute bits supported by this file"}, + {"stx_mnt_id", "Linux mount ID"}, + {"stx_dio_mem_align", "Linux direct IO buffer alignment"}, + {"stx_dio_offset_align", "Linux direct IO file offset alignment"}, + {"stx_subvol", "Linux subvolume identifier"}, + {"stx_atomic_write_unit_min", "Linux atomic write unit minimum"}, + {"stx_atomic_write_unit_max", "Linux atomic write unit maximum"}, + {"stx_atomic_write_segments_max", "Linux atomic write segment maximum"}, + {"stx_dio_read_offset_align", "Linux direct IO buffer and offset alignment for reads"}, + {"stx_atomic_write_unit_max", "Linux atomic write unit optimized maximum"}, #endif {0} }; @@ -2449,10 +2470,18 @@ static PyStructSequence_Field stat_result_fields[] = { #endif #ifdef HAVE_LINUX_STATX -#define ST_ATTRIBUTES_IDX (ST_REPARSE_TAG_IDX+1) -#define ST_ATTRIBUTES_MASK_IDX (ST_ATTRIBUTES_IDX+1) -#define ST_MNT_ID_IDX (ST_ATTRIBUTES_MASK_IDX+1) -#define ST_SUBVOL_IDX (ST_MNT_ID_IDX+1) +#define STX_MASK_IDX (ST_REPARSE_TAG_IDX+1) +#define STX_ATTRIBUTES_IDX (STX_MASK_IDX+1) +#define STX_ATTRIBUTES_MASK_IDX (STX_ATTRIBUTES_IDX+1) +#define STX_MNT_ID_IDX (STX_ATTRIBUTES_MASK_IDX+1) +#define STX_DIO_MEM_ALIGN_IDX (STX_MNT_ID_IDX+1) +#define STX_DIO_OFFSET_ALIGN_IDX (STX_DIO_MEM_ALIGN_IDX+1) +#define STX_SUBVOL_IDX (STX_DIO_OFFSET_ALIGN_IDX+1) +#define STX_ATOMIC_WRITE_UNIT_MIN_IDX (STX_SUBVOL_IDX+1) +#define STX_ATOMIC_WRITE_UNIT_MAX_IDX (STX_ATOMIC_WRITE_UNIT_MIN_IDX+1) +#define STX_ATOMIC_WRITE_SEGMENTS_MAX_IDX (STX_ATOMIC_WRITE_UNIT_MAX_IDX+1) +#define STX_DIO_READ_OFFSET_ALIGN_IDX (STX_ATOMIC_WRITE_SEGMENTS_MAX_IDX+1) +#define STX_ATOMIC_WRITE_UNIT_MAX_OPT_IDX (STX_DIO_READ_OFFSET_ALIGN_IDX+1) #endif static PyStructSequence_Desc stat_result_desc = { @@ -2784,6 +2813,12 @@ _pystat_fromstructstat(PyObject *module, STRUCT_STAT *st) #endif SET_ITEM(ST_BIRTHTIME_IDX, PyFloat_FromDouble(bsec + bnsec * 1e-9)); } +#elif defined(HAVE_LINUX_STATX) + /* We were built with statx support, so stat_result.st_birthtime[_ns] + exists, but we fell back to stat because statx isn't available at + runtime. User programs assume st_birthtime is not None. */ + SET_ITEM(ST_BIRTHTIME_IDX, PyFloat_FromDouble(0.0)); + SET_ITEM(ST_BIRTHTIME_NS_IDX, _PyLong_GetZero()); #elif defined(MS_WINDOWS) if (fill_time(module, v, -1, ST_BIRTHTIME_IDX, ST_BIRTHTIME_NS_IDX, st->st_birthtime, st->st_birthtime_nsec) < 0) { @@ -2803,6 +2838,20 @@ _pystat_fromstructstat(PyObject *module, STRUCT_STAT *st) #ifdef HAVE_STRUCT_STAT_ST_REPARSE_TAG SET_ITEM(ST_REPARSE_TAG_IDX, PyLong_FromUnsignedLong(st->st_reparse_tag)); #endif +#ifdef HAVE_LINUX_STATX + SET_ITEM(STX_MASK_IDX, PyLong_FromUnsignedLong(STATX_BASIC_STATS)); + SET_ITEM(STX_ATTRIBUTES_IDX, Py_None); + SET_ITEM(STX_ATTRIBUTES_MASK_IDX, Py_None); + SET_ITEM(STX_MNT_ID_IDX, Py_None); + SET_ITEM(STX_DIO_MEM_ALIGN_IDX, Py_None); + SET_ITEM(STX_DIO_OFFSET_ALIGN_IDX, Py_None); + SET_ITEM(STX_SUBVOL_IDX, Py_None); + SET_ITEM(STX_ATOMIC_WRITE_UNIT_MIN_IDX, Py_None); + SET_ITEM(STX_ATOMIC_WRITE_UNIT_MAX_IDX, Py_None); + SET_ITEM(STX_ATOMIC_WRITE_SEGMENTS_MAX_IDX, Py_None); + SET_ITEM(STX_DIO_READ_OFFSET_ALIGN_IDX, Py_None); + SET_ITEM(STX_ATOMIC_WRITE_UNIT_MAX_OPT_IDX, Py_None); +#endif assert(!PyErr_Occurred()); return v; @@ -2810,13 +2859,11 @@ _pystat_fromstructstat(PyObject *module, STRUCT_STAT *st) error: Py_DECREF(v); return NULL; - -#undef SET_ITEM } #ifdef HAVE_LINUX_STATX static PyObject* -_pystat_fromstructstatx(PyObject *module, struct statx *st) +_pystat_fromstructstatx(PyObject *module, struct statx *st, unsigned int mask) { assert(!PyErr_Occurred()); @@ -2826,75 +2873,104 @@ _pystat_fromstructstatx(PyObject *module, struct statx *st) return NULL; } -#define SET_ITEM(pos, expr) \ + /* Per the comment in /usr/include/linux/stat.h, even if a bit is cleared + in stx_mask, the corresponding field is "set to an appropriate + fabricated value" or cleared, never left uninitialized. */ +#define SET_ITEM_IF(pos, bit, expr) \ do { \ - PyObject *obj = (expr); \ + PyObject *obj = mask & (bit) ? (expr) : Py_None; \ if (obj == NULL) { \ goto error; \ } \ PyStructSequence_SET_ITEM(v, (pos), obj); \ } while (0) - SET_ITEM(0, PyLong_FromLong((long)st->stx_mode)); + SET_ITEM_IF(0, STATX_TYPE | STATX_MODE, + PyLong_FromLong((long)st->stx_mode)); static_assert(sizeof(unsigned long long) >= sizeof(st->stx_ino), "statx.stx_ino is larger than unsigned long long"); - SET_ITEM(1, PyLong_FromUnsignedLongLong(st->stx_ino)); + SET_ITEM_IF(1, STATX_INO, PyLong_FromUnsignedLongLong(st->stx_ino)); dev_t dev = makedev(st->stx_dev_major, st->stx_dev_minor); SET_ITEM(2, _PyLong_FromDev(dev)); - SET_ITEM(3, PyLong_FromLong((long)st->stx_nlink)); - SET_ITEM(4, _PyLong_FromUid(st->stx_uid)); - SET_ITEM(5, _PyLong_FromGid(st->stx_gid)); + SET_ITEM_IF(3, STATX_NLINK, PyLong_FromLong((long)st->stx_nlink)); + SET_ITEM_IF(4, STATX_UID, _PyLong_FromUid(st->stx_uid)); + SET_ITEM_IF(5, STATX_GID, _PyLong_FromGid(st->stx_gid)); static_assert(sizeof(long long) >= sizeof(st->stx_size), "statx.stx_size is larger than long long"); - SET_ITEM(6, PyLong_FromLongLong(st->stx_size)); + SET_ITEM_IF(6, STATX_SIZE, PyLong_FromLongLong(st->stx_size)); - if (fill_time(module, v, 7, 10, 13, st->stx_atime.tv_sec, + if (mask & STATX_ATIME) { + if (fill_time(module, v, 7, 10, 13, st->stx_atime.tv_sec, st->stx_atime.tv_nsec) < 0) { - goto error; + goto error; + } } - if (fill_time(module, v, 8, 11, 14, st->stx_mtime.tv_sec, - st->stx_mtime.tv_nsec) < 0) { - goto error; + else { + SET_ITEM(7, Py_None); + SET_ITEM(10, Py_None); + SET_ITEM(13, Py_None); } - if (fill_time(module, v, 9, 12, 15, st->stx_ctime.tv_sec, - st->stx_ctime.tv_nsec) < 0) { - goto error; + if (mask & STATX_MTIME) { + if (fill_time(module, v, 8, 11, 14, st->stx_mtime.tv_sec, + st->stx_mtime.tv_nsec) < 0) { + goto error; + } + } + else { + SET_ITEM(8, Py_None); + SET_ITEM(11, Py_None); + SET_ITEM(14, Py_None); } - if (st->stx_mask & STATX_BTIME) { + if (mask & STATX_CTIME) { + if (fill_time(module, v, 9, 12, 15, st->stx_ctime.tv_sec, + st->stx_ctime.tv_nsec) < 0) { + goto error; + } + } + else { + SET_ITEM(9, Py_None); + SET_ITEM(12, Py_None); + SET_ITEM(15, Py_None); + } + if (mask & STATX_BTIME) { if (fill_time(module, v, -1, ST_BIRTHTIME_IDX, ST_BIRTHTIME_NS_IDX, st->stx_btime.tv_sec, st->stx_btime.tv_nsec) < 0) { goto error; } } else { - SET_ITEM(ST_BIRTHTIME_IDX, PyFloat_FromDouble(0.0)); - SET_ITEM(ST_BIRTHTIME_NS_IDX, _PyLong_GetZero()); + SET_ITEM(ST_BIRTHTIME_IDX, Py_None); + SET_ITEM(ST_BIRTHTIME_NS_IDX, Py_None); } SET_ITEM(ST_BLKSIZE_IDX, PyLong_FromLong((long)st->stx_blksize)); - SET_ITEM(ST_BLOCKS_IDX, PyLong_FromLong((long)st->stx_blocks)); + SET_ITEM_IF(ST_BLOCKS_IDX, STATX_BLOCKS, + PyLong_FromLong((long)st->stx_blocks)); dev_t rdev = makedev(st->stx_rdev_major, st->stx_rdev_minor); SET_ITEM(ST_RDEV_IDX, _PyLong_FromDev(rdev)); - SET_ITEM(ST_ATTRIBUTES_IDX, PyLong_FromUnsignedLong(st->stx_attributes)); - SET_ITEM(ST_ATTRIBUTES_MASK_IDX, PyLong_FromLong(st->stx_attributes_mask)); - - if (st->stx_mask & _Py_STATX_MNT_ID) { - void* stx_mnt_id = ((unsigned char*)st) + _Py_STATX_MNT_ID_OFFSET; - SET_ITEM(ST_MNT_ID_IDX, PyLong_FromUnsignedNativeBytes(stx_mnt_id, 8, -1)); - } - else { - SET_ITEM(ST_MNT_ID_IDX, Py_None); - } - - if (st->stx_mask & _Py_STATX_SUBVOL) { - void* stx_subvol = ((unsigned char*)st) + _Py_STATX_SUBVOL_OFFSET; - SET_ITEM(ST_SUBVOL_IDX, PyLong_FromUnsignedNativeBytes(stx_subvol, 8, -1)); - } - else { - SET_ITEM(ST_SUBVOL_IDX, Py_None); - } + SET_ITEM(STX_MASK_IDX, PyLong_FromUnsignedLong(st->stx_mask & mask)); + SET_ITEM(STX_ATTRIBUTES_IDX, PyLong_FromUnsignedLong(st->stx_attributes)); + SET_ITEM(STX_ATTRIBUTES_MASK_IDX, PyLong_FromLong(st->stx_attributes_mask)); +#define SET_ITEM_IF_OFFSET(pos, bit, offset, len) \ + do { \ + void *addr = ((unsigned char*)st) + offset; \ + PyObject *obj = mask & (bit) ? PyLong_FromUnsignedNativeBytes(addr, len, -1) : Py_None; \ + if (obj == NULL) { \ + goto error; \ + } \ + PyStructSequence_SET_ITEM(v, (pos), obj); \ + } while (0) + SET_ITEM_IF_OFFSET(STX_MNT_ID_IDX, STATX_MNT_ID | STATX_MNT_ID_UNIQUE, 144, 8); + SET_ITEM_IF_OFFSET(STX_DIO_MEM_ALIGN_IDX, STATX_DIOALIGN, 152, 4); + SET_ITEM_IF_OFFSET(STX_DIO_OFFSET_ALIGN_IDX, STATX_DIOALIGN, 156, 4); + SET_ITEM_IF_OFFSET(STX_SUBVOL_IDX, STATX_SUBVOL, 160, 8); + SET_ITEM_IF_OFFSET(STX_ATOMIC_WRITE_UNIT_MIN_IDX, STATX_WRITE_ATOMIC, 168, 4); + SET_ITEM_IF_OFFSET(STX_ATOMIC_WRITE_UNIT_MAX_IDX, STATX_WRITE_ATOMIC, 172, 4); + SET_ITEM_IF_OFFSET(STX_ATOMIC_WRITE_SEGMENTS_MAX_IDX, STATX_WRITE_ATOMIC, 176, 4); + SET_ITEM_IF_OFFSET(STX_DIO_READ_OFFSET_ALIGN_IDX, STATX_DIO_READ_ALIGN, 180, 4); + SET_ITEM_IF_OFFSET(STX_ATOMIC_WRITE_UNIT_MAX_OPT_IDX, STATX_WRITE_ATOMIC, 184, 4); assert(!PyErr_Occurred()); return v; @@ -2903,9 +2979,11 @@ _pystat_fromstructstatx(PyObject *module, struct statx *st) Py_DECREF(v); return NULL; -#undef SET_ITEM +#undef SET_ITEM_IF_OFFSET +#undef SET_ITEM_IF } #endif /* HAVE_LINUX_STATX */ +#undef SET_ITEM /* POSIX methods */ @@ -2934,16 +3012,18 @@ posix_do_stat(PyObject *module, const char *function_name, path_t *path, #ifdef HAVE_LINUX_STATX struct statx stx = {}; static int statx_works = -1; - if (HAVE_LINUX_STATX_RUNTIME && statx_works != 0) { + if (statx != NULL && statx_works != 0) { + unsigned int mask = STATX_BASIC_STATS | STATX_BTIME; int flags = AT_NO_AUTOMOUNT; flags |= follow_symlinks ? 0 : AT_SYMLINK_NOFOLLOW; + Py_BEGIN_ALLOW_THREADS if (path->fd != -1) { flags |= AT_EMPTY_PATH; - result = statx(path->fd, "", flags, _Py_STATX_MASK, &stx); + result = statx(path->fd, "", flags, mask, &stx); } else { - result = statx(dir_fd, path->narrow, flags, _Py_STATX_MASK, &stx); + result = statx(dir_fd, path->narrow, flags, mask, &stx); } Py_END_ALLOW_THREADS @@ -2956,7 +3036,7 @@ posix_do_stat(PyObject *module, const char *function_name, path_t *path, } } else { - return _pystat_fromstructstatx(module, &stx); + return _pystat_fromstructstatx(module, &stx, mask); } } #endif /* HAVE_LINUX_STATX */ @@ -3408,6 +3488,107 @@ os_lstat_impl(PyObject *module, path_t *path, int dir_fd) } +#ifdef HAVE_LINUX_STATX +static int +optional_bool_converter(PyObject *arg, void *addr) { + int value; + if (arg == Py_None) { + value = -1; + } + else { + value = Py_IsTrue(arg); + if (value < 0) { + return 0; + } + } + *((int *)addr) = value; + return 1; +} + +/*[python input] +class optional_bool_converter(CConverter): + type = 'int' + converter = 'optional_bool_converter' +[python start generated code]*/ +/*[python end generated code: output=da39a3ee5e6b4b0d input=47de85b300eeb19e]*/ + +/*[clinic input] + +os.statx + + path : path_t(allow_fd=True) + Path to be examined; can be string, bytes, a path-like object or + open-file-descriptor int. + + mask: unsigned_int(bitwise=True) + A bitmask of STATX_* constants defining the requested information. + + * + + dir_fd : dir_fd = None + If not None, it should be a file descriptor open to a directory, + and path should be a relative string; path will then be relative to + that directory. + + follow_symlinks: bool = True + If False, and the last element of the path is a symbolic link, + statx will examine the symbolic link itself instead of the file + the link points to. + + sync: optional_bool(c_default='-1') = None + If True, statx will return up-to-date values, even if doing so is + expensive. If False, statx will return cached values if possible. + If None, statx lets the operating system decide. + + raw: bool = False + If False, fields that were not requested or that are not valid will be + None. If True, all fields will be initialized with the (possibly fake) + kernel-provided value; use stx_mask to check validity. + +Perform a statx system call on the given path. + +It's an error to use dir_fd or follow_symlinks when specifying path as + an open file descriptor. + +[clinic start generated code]*/ + +static PyObject * +os_statx_impl(PyObject *module, path_t *path, unsigned int mask, int dir_fd, + int follow_symlinks, int sync, int raw) +/*[clinic end generated code: output=94261132ec9b507e input=de4f8caad620361b]*/ +{ + if (path_and_dir_fd_invalid("stat", path, dir_fd) || + dir_fd_and_fd_invalid("stat", dir_fd, path->fd) || + fd_and_follow_symlinks_invalid("stat", path->fd, follow_symlinks)) + return NULL; + + int result; + struct statx stx = {}; + /* Future bits may refer to members beyond the current size of struct + statx, so we need to mask them off to prevent memory corruption. */ + mask &= _Py_STATX_KNOWN; + int flags = AT_NO_AUTOMOUNT | (follow_symlinks ? 0 : AT_SYMLINK_NOFOLLOW); + if (sync != -1) { + flags |= sync ? AT_STATX_FORCE_SYNC : AT_STATX_DONT_SYNC; + } + + Py_BEGIN_ALLOW_THREADS + if (path->fd != -1) { + result = statx(path->fd, "", flags | AT_EMPTY_PATH, mask, &stx); + } + else { + result = statx(dir_fd, path->narrow, flags, mask, &stx); + } + Py_END_ALLOW_THREADS + + if (result != 0) { + return path_error(path); + } + return _pystat_fromstructstatx(module, &stx, raw ? _Py_STATX_KNOWN : mask & stx.stx_mask); +} +#endif + + /*[clinic input] os.access -> bool @@ -17123,6 +17304,7 @@ os__emscripten_debugger_impl(PyObject *module) static PyMethodDef posix_methods[] = { OS_STAT_METHODDEF + OS_STATX_METHODDEF OS_ACCESS_METHODDEF OS_TTYNAME_METHODDEF OS_CHDIR_METHODDEF @@ -17960,6 +18142,30 @@ all_ins(PyObject *m) #endif #endif /* HAVE_EVENTFD && EFD_CLOEXEC */ +#ifdef HAVE_LINUX_STATX + if (PyModule_AddIntMacro(m, STATX_TYPE)) return -1; + if (PyModule_AddIntMacro(m, STATX_MODE)) return -1; + if (PyModule_AddIntMacro(m, STATX_NLINK)) return -1; + if (PyModule_AddIntMacro(m, STATX_UID)) return -1; + if (PyModule_AddIntMacro(m, STATX_GID)) return -1; + if (PyModule_AddIntMacro(m, STATX_ATIME)) return -1; + if (PyModule_AddIntMacro(m, STATX_MTIME)) return -1; + if (PyModule_AddIntMacro(m, STATX_CTIME)) return -1; + if (PyModule_AddIntMacro(m, STATX_INO)) return -1; + if (PyModule_AddIntMacro(m, STATX_SIZE)) return -1; + if (PyModule_AddIntMacro(m, STATX_BLOCKS)) return -1; + if (PyModule_AddIntMacro(m, STATX_BASIC_STATS)) return -1; + if (PyModule_AddIntMacro(m, STATX_BTIME)) return -1; + if (PyModule_AddIntMacro(m, STATX_MNT_ID)) return -1; + if (PyModule_AddIntMacro(m, STATX_DIOALIGN)) return -1; + if (PyModule_AddIntMacro(m, STATX_MNT_ID_UNIQUE)) return -1; + if (PyModule_AddIntMacro(m, STATX_SUBVOL)) return -1; + if (PyModule_AddIntMacro(m, STATX_WRITE_ATOMIC)) return -1; + if (PyModule_AddIntMacro(m, STATX_DIO_READ_ALIGN)) return -1; + /* STATX_ALL intentionally omitted because it is deprecated */ + /* STATX_ATTR_* constants are in the stat module */ +#endif /* HAVE_LINUX_STATX */ + #if defined(__APPLE__) if (PyModule_AddIntConstant(m, "_COPYFILE_DATA", COPYFILE_DATA)) return -1; if (PyModule_AddIntConstant(m, "_COPYFILE_STAT", COPYFILE_STAT)) return -1; @@ -18231,6 +18437,27 @@ posixmodule_exec(PyObject *m) } #endif +#ifdef HAVE_LINUX_STATX + /* We retract os.statx in three cases: + - the weakly-linked statx wrapper function is not available (old libc) + - the wrapper function fails with EINVAL on sync flags (glibc's + emulation of statx via stat fails in this way) + - the wrapper function fails with ENOSYS (libc built without fallback + running on an old kernel) */ + struct statx stx; + if (statx == NULL + || (statx(-1, "/", AT_STATX_DONT_SYNC, 0, &stx) == -1 + && (errno == EINVAL || errno == ENOSYS))) { + PyObject* dct = PyModule_GetDict(m); + if (dct == NULL) { + return -1; + } + if (PyDict_PopString(dct, "statx", NULL) < 0) { + return -1; + } + } +#endif + /* Initialize environ dictionary */ if (PyModule_Add(m, "environ", convertenviron()) != 0) { return -1; From b0e827681f45588ce67cd0924b3c320bb64351ff Mon Sep 17 00:00:00 2001 From: Jeffrey Bosboom Date: Thu, 4 Sep 2025 14:41:55 -0700 Subject: [PATCH 07/18] Use correct function name in os.statx argument validation errors --- Modules/posixmodule.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index a279b7d2da4a44..be89161ea24526 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -3557,9 +3557,9 @@ os_statx_impl(PyObject *module, path_t *path, unsigned int mask, int dir_fd, int follow_symlinks, int sync, int raw) /*[clinic end generated code: output=94261132ec9b507e input=de4f8caad620361b]*/ { - if (path_and_dir_fd_invalid("stat", path, dir_fd) || - dir_fd_and_fd_invalid("stat", dir_fd, path->fd) || - fd_and_follow_symlinks_invalid("stat", path->fd, follow_symlinks)) + if (path_and_dir_fd_invalid("statx", path, dir_fd) || + dir_fd_and_fd_invalid("statx", dir_fd, path->fd) || + fd_and_follow_symlinks_invalid("statx", path->fd, follow_symlinks)) return NULL; int result; From f71315efb5b76a9a642721443af68f4ced75f9f6 Mon Sep 17 00:00:00 2001 From: Jeffrey Bosboom Date: Tue, 9 Sep 2025 23:16:26 -0700 Subject: [PATCH 08/18] Move statx-specific members from os.stat_result to new os.statx_result --- Modules/posixmodule.c | 356 ++++++++++++++++++++++-------------------- 1 file changed, 187 insertions(+), 169 deletions(-) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 4b721ec93d9017..99e3c9a78edbb8 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -429,7 +429,7 @@ extern char *ctermid_r(char *); # ifndef STATX_DIO_READ_ALIGN # define STATX_DIO_READ_ALIGN 0x00020000U # endif -# define _Py_STATX_KNOWN (STATX_BASIC_STATS | STATX_MNT_ID | STATX_DIOALIGN | STATX_MNT_ID_UNIQUE | STATX_SUBVOL | STATX_WRITE_ATOMIC | STATX_DIO_READ_ALIGN) +# define _Py_STATX_KNOWN (STATX_BASIC_STATS | STATX_BTIME | STATX_MNT_ID | STATX_DIOALIGN | STATX_MNT_ID_UNIQUE | STATX_SUBVOL | STATX_WRITE_ATOMIC | STATX_DIO_READ_ALIGN) #endif /* HAVE_LINUX_STATX */ @@ -1183,6 +1183,9 @@ typedef struct { #endif newfunc statresult_new_orig; PyObject *StatResultType; +#ifdef HAVE_LINUX_STATX + PyObject *StatxResultType; +#endif PyObject *StatVFSResultType; PyObject *TerminalSizeType; PyObject *TimesResultType; @@ -2391,20 +2394,6 @@ static PyStructSequence_Field stat_result_fields[] = { #endif #ifdef HAVE_STRUCT_STAT_ST_REPARSE_TAG {"st_reparse_tag", "Windows reparse tag"}, -#endif -#ifdef HAVE_LINUX_STATX - {"stx_mask", "Linux member validity bitmask"}, - {"stx_attributes", "Linux file attribute bits"}, - {"stx_attributes_mask", "Linux file attribute bits supported by this file"}, - {"stx_mnt_id", "Linux mount ID"}, - {"stx_dio_mem_align", "Linux direct IO buffer alignment"}, - {"stx_dio_offset_align", "Linux direct IO file offset alignment"}, - {"stx_subvol", "Linux subvolume identifier"}, - {"stx_atomic_write_unit_min", "Linux atomic write unit minimum"}, - {"stx_atomic_write_unit_max", "Linux atomic write unit maximum"}, - {"stx_atomic_write_segments_max", "Linux atomic write segment maximum"}, - {"stx_dio_read_offset_align", "Linux direct IO buffer and offset alignment for reads"}, - {"stx_atomic_write_unit_max", "Linux atomic write unit optimized maximum"}, #endif {0} }; @@ -2469,21 +2458,6 @@ static PyStructSequence_Field stat_result_fields[] = { #define ST_REPARSE_TAG_IDX ST_FSTYPE_IDX #endif -#ifdef HAVE_LINUX_STATX -#define STX_MASK_IDX (ST_REPARSE_TAG_IDX+1) -#define STX_ATTRIBUTES_IDX (STX_MASK_IDX+1) -#define STX_ATTRIBUTES_MASK_IDX (STX_ATTRIBUTES_IDX+1) -#define STX_MNT_ID_IDX (STX_ATTRIBUTES_MASK_IDX+1) -#define STX_DIO_MEM_ALIGN_IDX (STX_MNT_ID_IDX+1) -#define STX_DIO_OFFSET_ALIGN_IDX (STX_DIO_MEM_ALIGN_IDX+1) -#define STX_SUBVOL_IDX (STX_DIO_OFFSET_ALIGN_IDX+1) -#define STX_ATOMIC_WRITE_UNIT_MIN_IDX (STX_SUBVOL_IDX+1) -#define STX_ATOMIC_WRITE_UNIT_MAX_IDX (STX_ATOMIC_WRITE_UNIT_MIN_IDX+1) -#define STX_ATOMIC_WRITE_SEGMENTS_MAX_IDX (STX_ATOMIC_WRITE_UNIT_MAX_IDX+1) -#define STX_DIO_READ_OFFSET_ALIGN_IDX (STX_ATOMIC_WRITE_SEGMENTS_MAX_IDX+1) -#define STX_ATOMIC_WRITE_UNIT_MAX_OPT_IDX (STX_DIO_READ_OFFSET_ALIGN_IDX+1) -#endif - static PyStructSequence_Desc stat_result_desc = { "stat_result", /* name */ stat_result__doc__, /* doc */ @@ -2592,6 +2566,9 @@ _posix_clear(PyObject *module) Py_CLEAR(state->SchedParamType); #endif Py_CLEAR(state->StatResultType); +#ifdef HAVE_LINUX_STATX + Py_CLEAR(state->StatxResultType); +#endif Py_CLEAR(state->StatVFSResultType); Py_CLEAR(state->TerminalSizeType); Py_CLEAR(state->TimesResultType); @@ -2617,6 +2594,9 @@ _posix_traverse(PyObject *module, visitproc visit, void *arg) Py_VISIT(state->SchedParamType); #endif Py_VISIT(state->StatResultType); +#ifdef HAVE_LINUX_STATX + Py_VISIT(state->StatxResultType); +#endif Py_VISIT(state->StatVFSResultType); Py_VISIT(state->TerminalSizeType); Py_VISIT(state->TimesResultType); @@ -2637,12 +2617,45 @@ _posix_free(void *module) _posix_clear((PyObject *)module); } +#define SEC_TO_NS (1000000000LL) +static PyObject * +nanosecond_timestamp(_posixstate *state, time_t sec, unsigned long nsec) { + /* 1677-09-21 00:12:44 to 2262-04-11 23:47:15 UTC inclusive */ + if ((LLONG_MIN/SEC_TO_NS) <= sec && sec <= (LLONG_MAX/SEC_TO_NS - 1)) { + return PyLong_FromLongLong(sec * SEC_TO_NS + nsec); + } + else { + PyObject *s_in_ns = NULL; + PyObject *s = _PyLong_FromTime_t(sec); + PyObject *ns_fractional = PyLong_FromUnsignedLong(nsec); + if (s == NULL || ns_fractional == NULL) { + goto exit; + } + + s_in_ns = PyNumber_Multiply(s, state->billion); + if (s_in_ns == NULL) { + goto exit; + } + + PyObject *ns_total = PyNumber_Add(s_in_ns, ns_fractional); + if (ns_total == NULL) { + goto exit; + } + return ns_total; + + exit: + Py_XDECREF(s); + Py_XDECREF(ns_fractional); + Py_XDECREF(s_in_ns); + return NULL; + } +} + static int fill_time(_posixstate *state, PyObject *v, int s_index, int f_index, int ns_index, time_t sec, unsigned long nsec) { assert(!PyErr_Occurred()); -#define SEC_TO_NS (1000000000LL) assert(nsec < SEC_TO_NS); if (s_index >= 0) { @@ -2661,50 +2674,18 @@ fill_time(_posixstate *state, PyObject *v, int s_index, int f_index, PyStructSequence_SET_ITEM(v, f_index, float_s); } - int res = -1; if (ns_index >= 0) { - /* 1677-09-21 00:12:44 to 2262-04-11 23:47:15 UTC inclusive */ - if ((LLONG_MIN/SEC_TO_NS) <= sec && sec <= (LLONG_MAX/SEC_TO_NS - 1)) { - PyObject *ns_total = PyLong_FromLongLong(sec * SEC_TO_NS + nsec); - if (ns_total == NULL) { - return -1; - } - PyStructSequence_SET_ITEM(v, ns_index, ns_total); - assert(!PyErr_Occurred()); - res = 0; - } - else { - PyObject *s_in_ns = NULL; - PyObject *ns_total = NULL; - PyObject *s = _PyLong_FromTime_t(sec); - PyObject *ns_fractional = PyLong_FromUnsignedLong(nsec); - if (s == NULL || ns_fractional == NULL) { - goto exit; - } - - s_in_ns = PyNumber_Multiply(s, state->billion); - if (s_in_ns == NULL) { - goto exit; - } - - ns_total = PyNumber_Add(s_in_ns, ns_fractional); - if (ns_total == NULL) { - goto exit; - } - PyStructSequence_SET_ITEM(v, ns_index, ns_total); - assert(!PyErr_Occurred()); - res = 0; - - exit: - Py_XDECREF(s); - Py_XDECREF(ns_fractional); - Py_XDECREF(s_in_ns); + PyObject *ns_total = nanosecond_timestamp(state, sec, nsec); + if (ns_total == NULL) { + return -1; } + PyStructSequence_SET_ITEM(v, ns_index, ns_total); } - return res; - #undef SEC_TO_NS + assert(!PyErr_Occurred()); + return 0; } +#undef SEC_TO_NS #ifdef MS_WINDOWS static PyObject* @@ -2853,20 +2834,6 @@ _pystat_fromstructstat(PyObject *module, STRUCT_STAT *st) #ifdef HAVE_STRUCT_STAT_ST_REPARSE_TAG SET_ITEM(ST_REPARSE_TAG_IDX, PyLong_FromUnsignedLong(st->st_reparse_tag)); #endif -#ifdef HAVE_LINUX_STATX - SET_ITEM(STX_MASK_IDX, PyLong_FromUnsignedLong(STATX_BASIC_STATS)); - SET_ITEM(STX_ATTRIBUTES_IDX, Py_None); - SET_ITEM(STX_ATTRIBUTES_MASK_IDX, Py_None); - SET_ITEM(STX_MNT_ID_IDX, Py_None); - SET_ITEM(STX_DIO_MEM_ALIGN_IDX, Py_None); - SET_ITEM(STX_DIO_OFFSET_ALIGN_IDX, Py_None); - SET_ITEM(STX_SUBVOL_IDX, Py_None); - SET_ITEM(STX_ATOMIC_WRITE_UNIT_MIN_IDX, Py_None); - SET_ITEM(STX_ATOMIC_WRITE_UNIT_MAX_IDX, Py_None); - SET_ITEM(STX_ATOMIC_WRITE_SEGMENTS_MAX_IDX, Py_None); - SET_ITEM(STX_DIO_READ_OFFSET_ALIGN_IDX, Py_None); - SET_ITEM(STX_ATOMIC_WRITE_UNIT_MAX_OPT_IDX, Py_None); -#endif assert(!PyErr_Occurred()); return v; @@ -2878,11 +2845,12 @@ _pystat_fromstructstat(PyObject *module, STRUCT_STAT *st) #ifdef HAVE_LINUX_STATX static PyObject* -_pystat_fromstructstatx(PyObject *module, struct statx *st, unsigned int mask) +_pystat_fromstructstatx(PyObject *module, struct statx *st) { assert(!PyErr_Occurred()); - PyObject *StatResultType = get_posix_state(module)->StatResultType; + _posixstate *state = get_posix_state(module); + PyObject *StatResultType = state->StatResultType; PyObject *v = PyStructSequence_New((PyTypeObject *)StatResultType); if (v == NULL) { return NULL; @@ -2891,111 +2859,50 @@ _pystat_fromstructstatx(PyObject *module, struct statx *st, unsigned int mask) /* Per the comment in /usr/include/linux/stat.h, even if a bit is cleared in stx_mask, the corresponding field is "set to an appropriate fabricated value" or cleared, never left uninitialized. */ -#define SET_ITEM_IF(pos, bit, expr) \ - do { \ - PyObject *obj = mask & (bit) ? (expr) : Py_None; \ - if (obj == NULL) { \ - goto error; \ - } \ - PyStructSequence_SET_ITEM(v, (pos), obj); \ - } while (0) - - SET_ITEM_IF(0, STATX_TYPE | STATX_MODE, - PyLong_FromLong((long)st->stx_mode)); + SET_ITEM(0, PyLong_FromLong((long)st->stx_mode)); static_assert(sizeof(unsigned long long) >= sizeof(st->stx_ino), - "statx.stx_ino is larger than unsigned long long"); - SET_ITEM_IF(1, STATX_INO, PyLong_FromUnsignedLongLong(st->stx_ino)); + "stat.st_ino is larger than unsigned long long"); + SET_ITEM(1, PyLong_FromUnsignedLongLong(st->stx_ino)); + dev_t dev = makedev(st->stx_dev_major, st->stx_dev_minor); SET_ITEM(2, _PyLong_FromDev(dev)); - SET_ITEM_IF(3, STATX_NLINK, PyLong_FromLong((long)st->stx_nlink)); - SET_ITEM_IF(4, STATX_UID, _PyLong_FromUid(st->stx_uid)); - SET_ITEM_IF(5, STATX_GID, _PyLong_FromGid(st->stx_gid)); + SET_ITEM(3, PyLong_FromLong((long)st->stx_nlink)); + SET_ITEM(4, _PyLong_FromUid(st->stx_uid)); + SET_ITEM(5, _PyLong_FromGid(st->stx_gid)); static_assert(sizeof(long long) >= sizeof(st->stx_size), - "statx.stx_size is larger than long long"); - SET_ITEM_IF(6, STATX_SIZE, PyLong_FromLongLong(st->stx_size)); + "stat.st_size is larger than long long"); + SET_ITEM(6, PyLong_FromLongLong(st->stx_size)); - if (mask & STATX_ATIME) { - if (fill_time(module, v, 7, 10, 13, st->stx_atime.tv_sec, + if (fill_time(state, v, 7, 10, 13, st->stx_atime.tv_sec, st->stx_atime.tv_nsec) < 0) { - goto error; - } - } - else { - SET_ITEM(7, Py_None); - SET_ITEM(10, Py_None); - SET_ITEM(13, Py_None); + goto error; } - if (mask & STATX_MTIME) { - if (fill_time(module, v, 8, 11, 14, st->stx_mtime.tv_sec, + if (fill_time(state, v, 8, 11, 14, st->stx_mtime.tv_sec, st->stx_mtime.tv_nsec) < 0) { - goto error; - } - } - else { - SET_ITEM(8, Py_None); - SET_ITEM(11, Py_None); - SET_ITEM(14, Py_None); + goto error; } - if (mask & STATX_CTIME) { - if (fill_time(module, v, 9, 12, 15, st->stx_ctime.tv_sec, + if (fill_time(state, v, 9, 12, 15, st->stx_ctime.tv_sec, st->stx_ctime.tv_nsec) < 0) { - goto error; - } - } - else { - SET_ITEM(9, Py_None); - SET_ITEM(12, Py_None); - SET_ITEM(15, Py_None); - } - if (mask & STATX_BTIME) { - if (fill_time(module, v, -1, ST_BIRTHTIME_IDX, ST_BIRTHTIME_NS_IDX, - st->stx_btime.tv_sec, st->stx_btime.tv_nsec) < 0) { - goto error; - } + goto error; } - else { - SET_ITEM(ST_BIRTHTIME_IDX, Py_None); - SET_ITEM(ST_BIRTHTIME_NS_IDX, Py_None); + if (fill_time(state, v, -1, ST_BIRTHTIME_IDX, ST_BIRTHTIME_NS_IDX, + st->stx_btime.tv_sec, st->stx_btime.tv_nsec) < 0) { + goto error; } SET_ITEM(ST_BLKSIZE_IDX, PyLong_FromLong((long)st->stx_blksize)); - SET_ITEM_IF(ST_BLOCKS_IDX, STATX_BLOCKS, - PyLong_FromLong((long)st->stx_blocks)); + SET_ITEM(ST_BLOCKS_IDX, PyLong_FromLong((long)st->stx_blocks)); + dev_t rdev = makedev(st->stx_rdev_major, st->stx_rdev_minor); SET_ITEM(ST_RDEV_IDX, _PyLong_FromDev(rdev)); - SET_ITEM(STX_MASK_IDX, PyLong_FromUnsignedLong(st->stx_mask & mask)); - SET_ITEM(STX_ATTRIBUTES_IDX, PyLong_FromUnsignedLong(st->stx_attributes)); - SET_ITEM(STX_ATTRIBUTES_MASK_IDX, PyLong_FromLong(st->stx_attributes_mask)); -#define SET_ITEM_IF_OFFSET(pos, bit, offset, len) \ - do { \ - void *addr = ((unsigned char*)st) + offset; \ - PyObject *obj = mask & (bit) ? PyLong_FromUnsignedNativeBytes(addr, len, -1) : Py_None; \ - if (obj == NULL) { \ - goto error; \ - } \ - PyStructSequence_SET_ITEM(v, (pos), obj); \ - } while (0) - SET_ITEM_IF_OFFSET(STX_MNT_ID_IDX, STATX_MNT_ID | STATX_MNT_ID_UNIQUE, 144, 8); - SET_ITEM_IF_OFFSET(STX_DIO_MEM_ALIGN_IDX, STATX_DIOALIGN, 152, 4); - SET_ITEM_IF_OFFSET(STX_DIO_OFFSET_ALIGN_IDX, STATX_DIOALIGN, 156, 4); - SET_ITEM_IF_OFFSET(STX_SUBVOL_IDX, STATX_SUBVOL, 160, 8); - SET_ITEM_IF_OFFSET(STX_ATOMIC_WRITE_UNIT_MIN_IDX, STATX_WRITE_ATOMIC, 168, 4); - SET_ITEM_IF_OFFSET(STX_ATOMIC_WRITE_UNIT_MAX_IDX, STATX_WRITE_ATOMIC, 172, 4); - SET_ITEM_IF_OFFSET(STX_ATOMIC_WRITE_SEGMENTS_MAX_IDX, STATX_WRITE_ATOMIC, 176, 4); - SET_ITEM_IF_OFFSET(STX_DIO_READ_OFFSET_ALIGN_IDX, STATX_DIO_READ_ALIGN, 180, 4); - SET_ITEM_IF_OFFSET(STX_ATOMIC_WRITE_UNIT_MAX_OPT_IDX, STATX_WRITE_ATOMIC, 184, 4); - assert(!PyErr_Occurred()); return v; error: Py_DECREF(v); return NULL; - -#undef SET_ITEM_IF_OFFSET -#undef SET_ITEM_IF } #endif /* HAVE_LINUX_STATX */ #undef SET_ITEM @@ -3051,7 +2958,7 @@ posix_do_stat(PyObject *module, const char *function_name, path_t *path, } } else { - return _pystat_fromstructstatx(module, &stx, mask); + return _pystat_fromstructstatx(module, &stx); } } #endif /* HAVE_LINUX_STATX */ @@ -3505,6 +3412,31 @@ os_lstat_impl(PyObject *module, path_t *path, int dir_fd) #ifdef HAVE_LINUX_STATX +static int +statx_result_traverse(PyObject *self, visitproc visit, void *arg) { + Py_VISIT(Py_TYPE(self)); + return PyObject_VisitManagedDict((PyObject*)self, visit, arg); +} + +static int +statx_result_clear(PyObject *self) { + PyObject_ClearManagedDict(self); + return 0; +} + +static PyType_Slot statx_result_slots[] = { + {Py_tp_traverse, statx_result_traverse}, + {Py_tp_clear, statx_result_clear}, + {0, NULL}, +}; + +static PyType_Spec statx_result_spec = { + .name = "statx_result", + .basicsize = sizeof(PyObject), + .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HEAPTYPE | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_MANAGED_DICT | Py_TPFLAGS_IMMUTABLETYPE, + .slots = statx_result_slots, +}; + static int optional_bool_converter(PyObject *arg, void *addr) { int value; @@ -3600,7 +3532,86 @@ os_statx_impl(PyObject *module, path_t *path, unsigned int mask, int dir_fd, if (result != 0) { return path_error(path); } - return _pystat_fromstructstatx(module, &stx, raw ? _Py_STATX_KNOWN : mask & stx.stx_mask); + + unsigned int fill_mask = raw ? _Py_STATX_KNOWN : mask & stx.stx_mask; + + _posixstate *state = get_posix_state(module); + PyTypeObject *tp = (PyTypeObject *)state->StatxResultType; + PyObject *v = tp->tp_alloc(tp, 0); + +#define SET_ATTR_IF(name, cond, expr) \ + do { \ + PyObject *obj = (cond) ? (expr) : Py_None; \ + if (obj == NULL) { \ + goto error; \ + } \ + if (PyObject_SetAttrString(v, (name), obj) == -1) { \ + Py_DECREF(obj); \ + goto error; \ + } \ + } while (0) +#define SET_ATTR(name, expr) SET_ATTR_IF((name), true, (expr)) +#define SET_ATTR_IF_BIT(name, bit, expr) SET_ATTR_IF((name), fill_mask & (bit), (expr)) + + SET_ATTR("stx_mask", PyLong_FromUnsignedLong(stx.stx_mask)); + SET_ATTR("st_blksize", PyLong_FromUnsignedLong(stx.stx_blksize)); + SET_ATTR("stx_attributes", PyLong_FromUnsignedLongLong(stx.stx_attributes)); + SET_ATTR_IF_BIT("st_nlink", STATX_NLINK, PyLong_FromUnsignedLong(stx.stx_nlink)); + SET_ATTR_IF_BIT("st_uid", STATX_UID, _PyLong_FromUid(stx.stx_uid)); + SET_ATTR_IF_BIT("st_gid", STATX_GID, _PyLong_FromUid(stx.stx_gid)); + SET_ATTR_IF_BIT("st_mode", STATX_TYPE | STATX_MODE, PyLong_FromUnsignedLong(stx.stx_mode)); + SET_ATTR_IF_BIT("st_ino", STATX_INO, PyLong_FromUnsignedLongLong(stx.stx_ino)); + SET_ATTR_IF_BIT("st_size", STATX_SIZE, PyLong_FromUnsignedLongLong(stx.stx_size)); + SET_ATTR_IF_BIT("st_blocks", STATX_BLOCKS, PyLong_FromUnsignedLongLong(stx.stx_blocks)); + SET_ATTR("stx_attributes_mask", PyLong_FromUnsignedLongLong(stx.stx_attributes_mask)); + +#define SET_ATTR_IF_BIT_TS(name, bit, ts) \ + do { \ + if (fill_mask & (bit)) { \ + SET_ATTR((name), PyFloat_FromDouble((double)(ts).tv_sec + 1e-9 * (ts).tv_nsec)); \ + SET_ATTR(name "_ns", nanosecond_timestamp(state, (ts).tv_sec, (ts).tv_nsec)); \ + } \ + else { \ + SET_ATTR((name), Py_None); \ + SET_ATTR(name "_ns", Py_None); \ + } \ + } while (0) + SET_ATTR_IF_BIT_TS("st_atime", STATX_ATIME, stx.stx_atime); + SET_ATTR_IF_BIT_TS("st_mtime", STATX_MTIME, stx.stx_mtime); + SET_ATTR_IF_BIT_TS("st_ctime", STATX_CTIME, stx.stx_ctime); + SET_ATTR_IF_BIT_TS("st_birthtime", STATX_BTIME, stx.stx_btime); + + bool rdev_relevant = (stx.stx_mask & STATX_TYPE) && (S_ISBLK(stx.stx_mode) || S_ISCHR(stx.stx_mode)); + SET_ATTR_IF("st_rdev", rdev_relevant || raw, _PyLong_FromDev(makedev(stx.stx_rdev_major, stx.stx_rdev_minor))); + SET_ATTR("st_dev", _PyLong_FromDev(makedev(stx.stx_dev_major, stx.stx_dev_minor))); + +#define SET_ATTR_IF_BIT_OFFSET(name, bit, offset, length) \ + do { \ + void *addr = (unsigned char *)&stx + (offset); \ + SET_ATTR_IF_BIT((name), (bit), PyLong_FromUnsignedNativeBytes(addr, (length), -1)); \ + } while (0) + SET_ATTR_IF_BIT_OFFSET("stx_mnt_id", STATX_MNT_ID | STATX_MNT_ID_UNIQUE, 144, 8); + SET_ATTR_IF_BIT_OFFSET("stx_dio_mem_align", STATX_DIOALIGN, 152, 4); + SET_ATTR_IF_BIT_OFFSET("stx_dio_offset_align", STATX_DIOALIGN, 156, 4); + SET_ATTR_IF_BIT_OFFSET("stx_subvol", STATX_SUBVOL, 160, 8); + SET_ATTR_IF_BIT_OFFSET("stx_atomic_write_unit_min", STATX_WRITE_ATOMIC, 168, 4); + SET_ATTR_IF_BIT_OFFSET("stx_atomic_write_unit_max", STATX_WRITE_ATOMIC, 172, 4); + SET_ATTR_IF_BIT_OFFSET("stx_atomic_write_segments_max", STATX_WRITE_ATOMIC, 176, 4); + SET_ATTR_IF_BIT_OFFSET("stx_dio_read_offset_align", STATX_DIO_READ_ALIGN, 180, 4); + SET_ATTR_IF_BIT_OFFSET("stx_atomic_write_unit_max_opt", STATX_WRITE_ATOMIC, 184, 4); + +#undef SET_ATTR_IF_BIT_OFFSET +#undef SET_ATTR_IF_BIT_TS +#undef SET_ATTR_IF_BIT +#undef SET_ATTR +#undef SET_ATTR_IF + + assert(!PyErr_Occurred()); + return v; + +error: + Py_DECREF(v); + return Py_None; } #endif @@ -18521,6 +18532,13 @@ posixmodule_exec(PyObject *m) return -1; } } + else { + statx_result_spec.name = "os.statx_result"; + state->StatxResultType = PyType_FromModuleAndSpec(m, &statx_result_spec, NULL); + if (PyModule_AddObjectRef(m, "statx_result", state->StatxResultType) < 0) { + return -1; + } + } #endif /* Initialize environ dictionary */ From 1f1e959947f781c710cd4309fa0b0810b361b95e Mon Sep 17 00:00:00 2001 From: Jeffrey Bosboom Date: Wed, 10 Sep 2025 00:12:38 -0700 Subject: [PATCH 09/18] make regen-configure --- aclocal.m4 | 91 +++++++++++++------------------- configure | 151 ++++++++++++++++++----------------------------------- 2 files changed, 87 insertions(+), 155 deletions(-) diff --git a/aclocal.m4 b/aclocal.m4 index 465b10ba7f79b8..920c2b38560faa 100644 --- a/aclocal.m4 +++ b/aclocal.m4 @@ -1,6 +1,6 @@ -# generated automatically by aclocal 1.18.1 -*- Autoconf -*- +# generated automatically by aclocal 1.16.5 -*- Autoconf -*- -# Copyright (C) 1996-2025 Free Software Foundation, Inc. +# Copyright (C) 1996-2021 Free Software Foundation, Inc. # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -44,12 +44,12 @@ m4_ifndef([AC_CONFIG_MACRO_DIRS], [m4_defun([_AM_CONFIG_MACRO_DIRS], [])m4_defun # Early versions of this macro (i.e., before serial 12) would not work # when interprocedural optimization (via link-time optimization) was # enabled. This would happen when, say, the GCC/clang "-flto" flag, or the -# ICC "-ipo" flag was used, for example. The problem was that under these -# conditions, the compiler did not allocate for and write the special +# ICC "-ipo" flag was used, for example. The problem was that under +# these conditions, the compiler did not allocate for and write the special # float value in the data segment of the object file, since doing so might -# not prove optimal once more context was available. Thus, the special -# value (in platform-dependent binary form) could not be found in the -# object file, and the macro would fail. +# not prove optimal once more context was available. Thus, the special value +# (in platform-dependent binary form) could not be found in the object file, +# and the macro would fail. # # The solution to the above problem was to: # @@ -68,19 +68,19 @@ m4_ifndef([AC_CONFIG_MACRO_DIRS], [m4_defun([_AM_CONFIG_MACRO_DIRS], [])m4_defun # program binary that contains the value, which the macro can then find. # # How does the exit code depend on the special value residing in memory? -# Memory, unlike variables and registers, can be addressed indirectly at -# run time. The exit code of this test program is a result of indirectly -# reading and writing to the memory region where the special value is -# supposed to reside. The actual memory addresses used and the values to -# be written are derived from the the program input ("argv") and are -# therefore not known at compile or link time. The compiler has no choice -# but to defer the computation to run time, and to prepare by allocating -# and populating the data segment with the special value. For further -# details, refer to the source code of the test program. -# -# Note that the test program is never meant to be run. It only exists to -# host a double float value in a given platform's binary format. Thus, -# error handling is not included. +# Memory, unlike variables and registers, can be addressed indirectly at run +# time. The exit code of this test program is a result of indirectly reading +# and writing to the memory region where the special value is supposed to +# reside. The actual memory addresses used and the values to be written are +# derived from the the program input ("argv") and are therefore not known at +# compile or link time. The compiler has no choice but to defer the +# computation to run time, and to prepare by allocating and populating the +# data segment with the special value. For further details, refer to the +# source code of the test program. +# +# Note that the test program is never meant to be run. It only exists to host +# a double float value in a given platform's binary format. Thus, error +# handling is not included. # # LICENSE # @@ -91,7 +91,7 @@ m4_ifndef([AC_CONFIG_MACRO_DIRS], [m4_defun([_AM_CONFIG_MACRO_DIRS], [])m4_defun # and this notice are preserved. This file is offered as-is, without any # warranty. -#serial 13 +#serial 14 AC_DEFUN([AX_C_FLOAT_WORDS_BIGENDIAN], [AC_CACHE_CHECK(whether float word ordering is bigendian, @@ -112,10 +112,10 @@ int main (int argc, char *argv[]) ]])], [ -if grep noonsees conftest$EXEEXT >/dev/null ; then +if grep noonsees conftest* > /dev/null ; then ax_cv_c_float_words_bigendian=yes fi -if grep seesnoon conftest$EXEEXT >/dev/null ; then +if grep seesnoon conftest* >/dev/null ; then if test "$ax_cv_c_float_words_bigendian" = unknown; then ax_cv_c_float_words_bigendian=no else @@ -181,24 +181,14 @@ esac # and this notice are preserved. This file is offered as-is, without any # warranty. -#serial 11 +#serial 6 AC_DEFUN([AX_CHECK_COMPILE_FLAG], [AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_IF AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl -AC_CACHE_CHECK([whether the _AC_LANG compiler accepts $1], CACHEVAR, [ +AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [ ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS - if test x"m4_case(_AC_LANG, - [C], [$GCC], - [C++], [$GXX], - [Fortran], [$GFC], - [Fortran 77], [$G77], - [Objective C], [$GOBJC], - [Objective C++], [$GOBJCXX], - [no])" = xyes ; then - add_gnu_werror="-Werror" - fi - _AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1 $add_gnu_werror" + _AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1" AC_COMPILE_IFELSE([m4_default([$5],[AC_LANG_PROGRAM()])], [AS_VAR_SET(CACHEVAR,[yes])], [AS_VAR_SET(CACHEVAR,[no])]) @@ -234,7 +224,7 @@ AS_VAR_POPDEF([CACHEVAR])dnl # and this notice are preserved. This file is offered as-is, without any # warranty. -#serial 12 +#serial 11 AU_ALIAS([AC_CHECK_DEFINED], [AC_CHECK_DEFINE]) AC_DEFUN([AC_CHECK_DEFINE],[ @@ -274,8 +264,8 @@ AC_CACHE_CHECK([for $2], ac_var, dnl AC_LANG_FUNC_LINK_TRY [AC_LINK_IFELSE([AC_LANG_PROGRAM([$1 #undef $2 - char $2 (void);],[ - char (*f) (void) = $2; + char $2 ();],[ + char (*f) () = $2; return f != $2; ])], [AS_VAR_SET(ac_var, yes)], [AS_VAR_SET(ac_var, no)])]) @@ -409,7 +399,7 @@ AC_DEFUN([AX_CHECK_OPENSSL], [ ]) # pkg.m4 - Macros to locate and use pkg-config. -*- Autoconf -*- -# serial 13 (pkgconf) +# serial 12 (pkg-config-0.29.2) dnl Copyright © 2004 Scott James Remnant . dnl Copyright © 2012-2015 Dan Nicholson @@ -425,7 +415,9 @@ dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU dnl General Public License for more details. dnl dnl You should have received a copy of the GNU General Public License -dnl along with this program; if not, see . +dnl along with this program; if not, write to the Free Software +dnl Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +dnl 02111-1307, USA. dnl dnl As a special exception to the GNU General Public License, if you dnl distribute this file as part of a program that contains a @@ -454,8 +446,8 @@ m4_if(m4_version_compare(PKG_MACROS_VERSION, [$1]), -1, [m4_fatal([pkg.m4 version $1 or higher is required but ]PKG_MACROS_VERSION[ found])]) ])dnl PKG_PREREQ -dnl PKG_PROG_PKG_CONFIG([MIN-VERSION], [ACTION-IF-NOT-FOUND]) -dnl --------------------------------------------------------- +dnl PKG_PROG_PKG_CONFIG([MIN-VERSION]) +dnl ---------------------------------- dnl Since: 0.16 dnl dnl Search for the pkg-config tool and set the PKG_CONFIG variable to @@ -463,12 +455,6 @@ dnl first found in the path. Checks that the version of pkg-config found dnl is at least MIN-VERSION. If MIN-VERSION is not specified, 0.9.0 is dnl used since that's the first version where most current features of dnl pkg-config existed. -dnl -dnl If pkg-config is not found or older than specified, it will result -dnl in an empty PKG_CONFIG variable. To avoid widespread issues with -dnl scripts not checking it, ACTION-IF-NOT-FOUND defaults to aborting. -dnl You can specify [PKG_CONFIG=false] as an action instead, which would -dnl result in pkg-config tests failing, but no bogus error messages. AC_DEFUN([PKG_PROG_PKG_CONFIG], [m4_pattern_forbid([^_?PKG_[A-Z_]+$]) m4_pattern_allow([^PKG_CONFIG(_(PATH|LIBDIR|SYSROOT_DIR|ALLOW_SYSTEM_(CFLAGS|LIBS)))?$]) @@ -489,9 +475,6 @@ if test -n "$PKG_CONFIG"; then AC_MSG_RESULT([no]) PKG_CONFIG="" fi -fi -if test -z "$PKG_CONFIG"; then - m4_default([$2], [AC_MSG_ERROR([pkg-config not found])]) fi[]dnl ])dnl PKG_PROG_PKG_CONFIG @@ -761,7 +744,7 @@ AS_IF([test "$AS_TR_SH([with_]m4_tolower([$1]))" = "yes"], # AM_CONDITIONAL -*- Autoconf -*- -# Copyright (C) 1997-2025 Free Software Foundation, Inc. +# Copyright (C) 1997-2021 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -792,7 +775,7 @@ AC_CONFIG_COMMANDS_PRE( Usually this means the macro was only invoked conditionally.]]) fi])]) -# Copyright (C) 2006-2025 Free Software Foundation, Inc. +# Copyright (C) 2006-2021 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, diff --git a/configure b/configure index bb5d4b4a807846..171e67c2b1d8ec 100755 --- a/configure +++ b/configure @@ -4079,9 +4079,6 @@ printf "%s\n" "yes" >&6; } printf "%s\n" "no" >&6; } PKG_CONFIG="" fi -fi -if test -z "$PKG_CONFIG"; then - as_fn_error $? "pkg-config not found" "$LINENO" 5 fi ;; #( no) : @@ -8432,18 +8429,15 @@ printf "%s\n" "$as_me: WARNING: CFLAGS contains -O0 which may conflict with --en if test "x$ac_cv_gcc_compat" = xyes then : - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the C compiler accepts -fno-semantic-interposition" >&5 -printf %s "checking whether the C compiler accepts -fno-semantic-interposition... " >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -fno-semantic-interposition" >&5 +printf %s "checking whether C compiler accepts -fno-semantic-interposition... " >&6; } if test ${ax_cv_check_cflags__Werror__fno_semantic_interposition+y} then : printf %s "(cached) " >&6 else case e in #( e) ax_check_save_flags=$CFLAGS - if test x"$GCC" = xyes ; then - add_gnu_werror="-Werror" - fi - CFLAGS="$CFLAGS -Werror -fno-semantic-interposition $add_gnu_werror" + CFLAGS="$CFLAGS -Werror -fno-semantic-interposition" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -8573,18 +8567,15 @@ if test "$Py_LTO" = 'true' ; then case $ac_cv_cc_name in clang) LDFLAGS_NOLTO="-fno-lto" - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the C compiler accepts -flto=thin" >&5 -printf %s "checking whether the C compiler accepts -flto=thin... " >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -flto=thin" >&5 +printf %s "checking whether C compiler accepts -flto=thin... " >&6; } if test ${ax_cv_check_cflags___flto_thin+y} then : printf %s "(cached) " >&6 else case e in #( e) ax_check_save_flags=$CFLAGS - if test x"$GCC" = xyes ; then - add_gnu_werror="-Werror" - fi - CFLAGS="$CFLAGS -flto=thin $add_gnu_werror" + CFLAGS="$CFLAGS -flto=thin" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -8761,18 +8752,15 @@ printf "%s\n" "$as_me: llvm-ar found via xcrun: ${LLVM_AR}" >&6;} if test $Py_LTO_POLICY = default then # Check that ThinLTO is accepted. - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the C compiler accepts -flto=thin" >&5 -printf %s "checking whether the C compiler accepts -flto=thin... " >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -flto=thin" >&5 +printf %s "checking whether C compiler accepts -flto=thin... " >&6; } if test ${ax_cv_check_cflags___flto_thin+y} then : printf %s "(cached) " >&6 else case e in #( e) ax_check_save_flags=$CFLAGS - if test x"$GCC" = xyes ; then - add_gnu_werror="-Werror" - fi - CFLAGS="$CFLAGS -flto=thin $add_gnu_werror" + CFLAGS="$CFLAGS -flto=thin" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -8821,18 +8809,15 @@ fi if test $Py_LTO_POLICY = default then # Check that ThinLTO is accepted - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the C compiler accepts -flto=thin" >&5 -printf %s "checking whether the C compiler accepts -flto=thin... " >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -flto=thin" >&5 +printf %s "checking whether C compiler accepts -flto=thin... " >&6; } if test ${ax_cv_check_cflags___flto_thin+y} then : printf %s "(cached) " >&6 else case e in #( e) ax_check_save_flags=$CFLAGS - if test x"$GCC" = xyes ; then - add_gnu_werror="-Werror" - fi - CFLAGS="$CFLAGS -flto=thin $add_gnu_werror" + CFLAGS="$CFLAGS -flto=thin" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -9109,18 +9094,15 @@ if test "$Py_BOLT" = 'true' ; then # -fno-reorder-blocks-and-partition is required for bolt to work. # Possibly GCC only. - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the C compiler accepts -fno-reorder-blocks-and-partition" >&5 -printf %s "checking whether the C compiler accepts -fno-reorder-blocks-and-partition... " >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -fno-reorder-blocks-and-partition" >&5 +printf %s "checking whether C compiler accepts -fno-reorder-blocks-and-partition... " >&6; } if test ${ax_cv_check_cflags___fno_reorder_blocks_and_partition+y} then : printf %s "(cached) " >&6 else case e in #( e) ax_check_save_flags=$CFLAGS - if test x"$GCC" = xyes ; then - add_gnu_werror="-Werror" - fi - CFLAGS="$CFLAGS -fno-reorder-blocks-and-partition $add_gnu_werror" + CFLAGS="$CFLAGS -fno-reorder-blocks-and-partition" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -9769,18 +9751,15 @@ printf "%s\n" "$enable_safety" >&6; } if test "$enable_safety" = "yes" then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the C compiler accepts -fstack-protector-strong" >&5 -printf %s "checking whether the C compiler accepts -fstack-protector-strong... " >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -fstack-protector-strong" >&5 +printf %s "checking whether C compiler accepts -fstack-protector-strong... " >&6; } if test ${ax_cv_check_cflags__Werror__fstack_protector_strong+y} then : printf %s "(cached) " >&6 else case e in #( e) ax_check_save_flags=$CFLAGS - if test x"$GCC" = xyes ; then - add_gnu_werror="-Werror" - fi - CFLAGS="$CFLAGS -Werror -fstack-protector-strong $add_gnu_werror" + CFLAGS="$CFLAGS -Werror -fstack-protector-strong" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -9814,18 +9793,15 @@ printf "%s\n" "$as_me: WARNING: -fstack-protector-strong not supported" >&2;} ;; esac fi - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the C compiler accepts -Wtrampolines" >&5 -printf %s "checking whether the C compiler accepts -Wtrampolines... " >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Wtrampolines" >&5 +printf %s "checking whether C compiler accepts -Wtrampolines... " >&6; } if test ${ax_cv_check_cflags__Werror__Wtrampolines+y} then : printf %s "(cached) " >&6 else case e in #( e) ax_check_save_flags=$CFLAGS - if test x"$GCC" = xyes ; then - add_gnu_werror="-Werror" - fi - CFLAGS="$CFLAGS -Werror -Wtrampolines $add_gnu_werror" + CFLAGS="$CFLAGS -Werror -Wtrampolines" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -9859,18 +9835,15 @@ printf "%s\n" "$as_me: WARNING: -Wtrampolines not supported" >&2;} ;; esac fi - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the C compiler accepts -Wimplicit-fallthrough" >&5 -printf %s "checking whether the C compiler accepts -Wimplicit-fallthrough... " >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Wimplicit-fallthrough" >&5 +printf %s "checking whether C compiler accepts -Wimplicit-fallthrough... " >&6; } if test ${ax_cv_check_cflags__Werror__Wimplicit_fallthrough+y} then : printf %s "(cached) " >&6 else case e in #( e) ax_check_save_flags=$CFLAGS - if test x"$GCC" = xyes ; then - add_gnu_werror="-Werror" - fi - CFLAGS="$CFLAGS -Werror -Wimplicit-fallthrough $add_gnu_werror" + CFLAGS="$CFLAGS -Werror -Wimplicit-fallthrough" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -9904,18 +9877,15 @@ printf "%s\n" "$as_me: WARNING: -Wimplicit-fallthrough not supported" >&2;} ;; esac fi - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the C compiler accepts -Werror=format-security" >&5 -printf %s "checking whether the C compiler accepts -Werror=format-security... " >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Werror=format-security" >&5 +printf %s "checking whether C compiler accepts -Werror=format-security... " >&6; } if test ${ax_cv_check_cflags__Werror__Werror_format_security+y} then : printf %s "(cached) " >&6 else case e in #( e) ax_check_save_flags=$CFLAGS - if test x"$GCC" = xyes ; then - add_gnu_werror="-Werror" - fi - CFLAGS="$CFLAGS -Werror -Werror=format-security $add_gnu_werror" + CFLAGS="$CFLAGS -Werror -Werror=format-security" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -9949,18 +9919,15 @@ printf "%s\n" "$as_me: WARNING: -Werror=format-security not supported" >&2;} ;; esac fi - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the C compiler accepts -Wbidi-chars=any" >&5 -printf %s "checking whether the C compiler accepts -Wbidi-chars=any... " >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Wbidi-chars=any" >&5 +printf %s "checking whether C compiler accepts -Wbidi-chars=any... " >&6; } if test ${ax_cv_check_cflags__Werror__Wbidi_chars_any+y} then : printf %s "(cached) " >&6 else case e in #( e) ax_check_save_flags=$CFLAGS - if test x"$GCC" = xyes ; then - add_gnu_werror="-Werror" - fi - CFLAGS="$CFLAGS -Werror -Wbidi-chars=any $add_gnu_werror" + CFLAGS="$CFLAGS -Werror -Wbidi-chars=any" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -9994,18 +9961,15 @@ printf "%s\n" "$as_me: WARNING: -Wbidi-chars=any not supported" >&2;} ;; esac fi - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the C compiler accepts -Wall" >&5 -printf %s "checking whether the C compiler accepts -Wall... " >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Wall" >&5 +printf %s "checking whether C compiler accepts -Wall... " >&6; } if test ${ax_cv_check_cflags__Werror__Wall+y} then : printf %s "(cached) " >&6 else case e in #( e) ax_check_save_flags=$CFLAGS - if test x"$GCC" = xyes ; then - add_gnu_werror="-Werror" - fi - CFLAGS="$CFLAGS -Werror -Wall $add_gnu_werror" + CFLAGS="$CFLAGS -Werror -Wall" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -10063,18 +10027,15 @@ printf "%s\n" "$enable_slower_safety" >&6; } if test "$enable_slower_safety" = "yes" then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the C compiler accepts -D_FORTIFY_SOURCE=3" >&5 -printf %s "checking whether the C compiler accepts -D_FORTIFY_SOURCE=3... " >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -D_FORTIFY_SOURCE=3" >&5 +printf %s "checking whether C compiler accepts -D_FORTIFY_SOURCE=3... " >&6; } if test ${ax_cv_check_cflags__Werror__D_FORTIFY_SOURCE_3+y} then : printf %s "(cached) " >&6 else case e in #( e) ax_check_save_flags=$CFLAGS - if test x"$GCC" = xyes ; then - add_gnu_werror="-Werror" - fi - CFLAGS="$CFLAGS -Werror -D_FORTIFY_SOURCE=3 $add_gnu_werror" + CFLAGS="$CFLAGS -Werror -D_FORTIFY_SOURCE=3" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -10675,18 +10636,15 @@ printf "%s\n" "$CC" >&6; } # Error on unguarded use of new symbols, which will fail at runtime for # users on older versions of macOS - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the C compiler accepts -Wunguarded-availability" >&5 -printf %s "checking whether the C compiler accepts -Wunguarded-availability... " >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Wunguarded-availability" >&5 +printf %s "checking whether C compiler accepts -Wunguarded-availability... " >&6; } if test ${ax_cv_check_cflags__Werror__Wunguarded_availability+y} then : printf %s "(cached) " >&6 else case e in #( e) ax_check_save_flags=$CFLAGS - if test x"$GCC" = xyes ; then - add_gnu_werror="-Werror" - fi - CFLAGS="$CFLAGS -Werror -Wunguarded-availability $add_gnu_werror" + CFLAGS="$CFLAGS -Werror -Wunguarded-availability" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -13421,18 +13379,15 @@ then : withval=$with_memory_sanitizer; { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $withval" >&5 printf "%s\n" "$withval" >&6; } -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the C compiler accepts -fsanitize=memory" >&5 -printf %s "checking whether the C compiler accepts -fsanitize=memory... " >&6; } +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -fsanitize=memory" >&5 +printf %s "checking whether C compiler accepts -fsanitize=memory... " >&6; } if test ${ax_cv_check_cflags___fsanitize_memory+y} then : printf %s "(cached) " >&6 else case e in #( e) ax_check_save_flags=$CFLAGS - if test x"$GCC" = xyes ; then - add_gnu_werror="-Werror" - fi - CFLAGS="$CFLAGS -fsanitize=memory $add_gnu_werror" + CFLAGS="$CFLAGS -fsanitize=memory" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -25922,10 +25877,10 @@ if ac_fn_c_try_link "$LINENO" then : -if grep noonsees conftest$EXEEXT >/dev/null ; then +if grep noonsees conftest* > /dev/null ; then ax_cv_c_float_words_bigendian=yes fi -if grep seesnoon conftest$EXEEXT >/dev/null ; then +if grep seesnoon conftest* >/dev/null ; then if test "$ax_cv_c_float_words_bigendian" = unknown; then ax_cv_c_float_words_bigendian=no else @@ -32604,18 +32559,15 @@ fi if test "$ac_sys_system" != "Linux-android" -a "$ac_sys_system" != "WASI" || \ { test -n "$ANDROID_API_LEVEL" && test "$ANDROID_API_LEVEL" -ge 28; } then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the C compiler accepts -msse -msse2 -msse3 -msse4.1 -msse4.2" >&5 -printf %s "checking whether the C compiler accepts -msse -msse2 -msse3 -msse4.1 -msse4.2... " >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -msse -msse2 -msse3 -msse4.1 -msse4.2" >&5 +printf %s "checking whether C compiler accepts -msse -msse2 -msse3 -msse4.1 -msse4.2... " >&6; } if test ${ax_cv_check_cflags__Werror__msse__msse2__msse3__msse4_1__msse4_2+y} then : printf %s "(cached) " >&6 else case e in #( e) ax_check_save_flags=$CFLAGS - if test x"$GCC" = xyes ; then - add_gnu_werror="-Werror" - fi - CFLAGS="$CFLAGS -Werror -msse -msse2 -msse3 -msse4.1 -msse4.2 $add_gnu_werror" + CFLAGS="$CFLAGS -Werror -msse -msse2 -msse3 -msse4.1 -msse4.2" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -32684,18 +32636,15 @@ fi if test "$ac_sys_system" != "Linux-android" -a "$ac_sys_system" != "WASI" || \ { test -n "$ANDROID_API_LEVEL" && test "$ANDROID_API_LEVEL" -ge 28; } then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the C compiler accepts -mavx2" >&5 -printf %s "checking whether the C compiler accepts -mavx2... " >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -mavx2" >&5 +printf %s "checking whether C compiler accepts -mavx2... " >&6; } if test ${ax_cv_check_cflags__Werror__mavx2+y} then : printf %s "(cached) " >&6 else case e in #( e) ax_check_save_flags=$CFLAGS - if test x"$GCC" = xyes ; then - add_gnu_werror="-Werror" - fi - CFLAGS="$CFLAGS -Werror -mavx2 $add_gnu_werror" + CFLAGS="$CFLAGS -Werror -mavx2" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ From 74403978c32f666a664fb2c6d2f15573c7fe60c0 Mon Sep 17 00:00:00 2001 From: Jeffrey Bosboom Date: Sat, 13 Sep 2025 01:22:55 -0700 Subject: [PATCH 10/18] Reimplement os.statx_result using PyMemberDef and PyGetSetDef; remove raw kwarg We no longer return None for invalid attributes for the benefit of drop-in replacement of os.stat when not all basic stats are required or sync=False is acceptable. Such code was already accepting faked values from os.stat, so can also accept faked values from os.statx. Remove the raw kwarg as it would have no effect. --- Lib/test/test_os.py | 103 +++++++++++-- Modules/clinic/posixmodule.c.h | 32 ++-- Modules/posixmodule.c | 268 +++++++++++++++++++++------------ 3 files changed, 273 insertions(+), 130 deletions(-) diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index cd15aa10f16de8..bd6e8d73d4abc9 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -640,6 +640,14 @@ def setUp(self): self.addCleanup(os_helper.unlink, self.fname) create_file(self.fname, b"ABC") + def check_timestamp_agreement(self, result, names): + # Make sure that the st_?time and st_?time_ns fields roughly agree + # (they should always agree up to around tens-of-microseconds) + for name in names: + floaty = int(getattr(result, name) * 100000) + nanosecondy = getattr(result, name + "_ns") // 10000 + self.assertAlmostEqual(floaty, nanosecondy, delta=2, msg=name) + def check_stat_attributes(self, fname): result = os.stat(fname) @@ -660,21 +668,15 @@ def trunc(x): return x result[getattr(stat, name)]) self.assertIn(attr, members) - # Make sure that the st_?time and st_?time_ns fields roughly agree - # (they should always agree up to around tens-of-microseconds) - for name in 'st_atime st_mtime st_ctime'.split(): - floaty = int(getattr(result, name) * 100000) - nanosecondy = getattr(result, name + "_ns") // 10000 - self.assertAlmostEqual(floaty, nanosecondy, delta=2) - - # Ensure both birthtime and birthtime_ns roughly agree, if present + time_attributes = 'st_atime st_mtime st_ctime'.split() try: - floaty = int(result.st_birthtime * 100000) - nanosecondy = result.st_birthtime_ns // 10000 + result.st_birthtime + result.st_birthtime_ns except AttributeError: pass else: - self.assertAlmostEqual(floaty, nanosecondy, delta=2) + time_attributes.append('st_birthtime') + self.check_timestamp_agreement(result, time_attributes) try: result[200] @@ -724,6 +726,85 @@ def test_stat_attributes_bytes(self): self.skipTest("cannot encode %a for the filesystem" % self.fname) self.check_stat_attributes(fname) + def check_statx_attributes(self, fname): + maximal_mask = 0 + for name in dir(os): + if name.startswith('STATX_'): + maximal_mask |= getattr(os, name) + result = os.statx(self.fname, maximal_mask) + + time_attributes = 'st_atime st_mtime st_ctime st_birthtime'.split() + self.check_timestamp_agreement(result, time_attributes) + + # Check that valid attributes match os.stat. + requirements = ( + ('st_mode', os.STATX_TYPE | os.STATX_MODE), + ('st_nlink', os.STATX_NLINK), + ('st_uid', os.STATX_UID), + ('st_gid', os.STATX_GID), + ('st_atime', os.STATX_ATIME), + ('st_atime_ns', os.STATX_ATIME), + ('st_mtime', os.STATX_MTIME), + ('st_mtime_ns', os.STATX_MTIME), + ('st_ctime', os.STATX_CTIME), + ('st_ctime_ns', os.STATX_CTIME), + ('st_ino', os.STATX_INO), + ('st_size', os.STATX_SIZE), + ('st_blocks', os.STATX_BLOCKS), + ('st_birthtime', os.STATX_BTIME), + ('st_birthtime_ns', os.STATX_BTIME), + # unconditionally valid members + ('st_blksize', maximal_mask), + ('st_dev', maximal_mask), + ('st_rdev', maximal_mask), + ) + basic_result = os.stat(self.fname) + for name, bits in requirements: + if result.stx_mask & bits: + x = getattr(result, name) + b = getattr(basic_result, name) + if isinstance(x, float): + self.assertAlmostEqual(x, b, msg=name) + else: + self.assertEqual(x, b, msg=name) + + # Access all the attributes multiple times to test cache refcounting. + members = [name for name in dir(result) + if name.startswith('st_') or name.startswith('stx_')] + for _ in range(10): + for name in members: + getattr(result, name) + + for name in members: + try: + setattr(result, name, 1) + self.fail("No exception raised") + except AttributeError: + pass + + self.assertTrue(result.stx_attributes & result.stx_attributes_mask + == result.stx_attributes) + + @unittest.skipUnless(hasattr(os, 'statx'), 'test needs os.statx()') + def test_statx_attributes(self): + self.check_statx_attributes(self.fname) + + @unittest.skipUnless(hasattr(os, 'statx'), 'test needs os.statx()') + def test_statx_attributes_bytes(self): + try: + fname = self.fname.encode(sys.getfilesystemencoding()) + except UnicodeEncodeError: + self.skipTest("cannot encode %a for the filesystem" % self.fname) + self.check_statx_attributes(fname) + + @unittest.skipUnless(hasattr(os, 'statx'), 'test needs os.statx()') + def test_statx_sync(self): + # Test sync= kwarg parsing. (We can't predict if or how the result + # will change.) + for sync in (False, True): + with self.subTest(sync=sync): + os.statx(self.fname, os.STATX_BASIC_STATS, sync=sync) + def test_stat_result_pickle(self): result = os.stat(self.fname) for proto in range(pickle.HIGHEST_PROTOCOL + 1): diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index 8d2a308633712f..677c1b25c028ea 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -190,7 +190,7 @@ os_lstat(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kw PyDoc_STRVAR(os_statx__doc__, "statx($module, /, path, mask, *, dir_fd=None, follow_symlinks=True,\n" -" sync=None, raw=False)\n" +" sync=None)\n" "--\n" "\n" "Perform a statx system call on the given path.\n" @@ -212,10 +212,6 @@ PyDoc_STRVAR(os_statx__doc__, " If True, statx will return up-to-date values, even if doing so is\n" " expensive. If False, statx will return cached values if possible.\n" " If None, statx lets the operating system decide.\n" -" raw\n" -" If False, fields that were not requested or that are not valid will be\n" -" None. If True, all fields will be initialized with the (possibly fake)\n" -" kernel-provided value; use stx_mask to check validity.\n" "\n" "It\'s an error to use dir_fd or follow_symlinks when specifying path as\n" " an open file descriptor."); @@ -225,7 +221,7 @@ PyDoc_STRVAR(os_statx__doc__, static PyObject * os_statx_impl(PyObject *module, path_t *path, unsigned int mask, int dir_fd, - int follow_symlinks, int sync, int raw); + int follow_symlinks, int sync); static PyObject * os_statx(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) @@ -233,7 +229,7 @@ os_statx(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kw PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) - #define NUM_KEYWORDS 6 + #define NUM_KEYWORDS 5 static struct { PyGC_Head _this_is_not_used; PyObject_VAR_HEAD @@ -242,7 +238,7 @@ os_statx(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kw } _kwtuple = { .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) .ob_hash = -1, - .ob_item = { &_Py_ID(path), &_Py_ID(mask), &_Py_ID(dir_fd), &_Py_ID(follow_symlinks), &_Py_ID(sync), &_Py_ID(raw), }, + .ob_item = { &_Py_ID(path), &_Py_ID(mask), &_Py_ID(dir_fd), &_Py_ID(follow_symlinks), &_Py_ID(sync), }, }; #undef NUM_KEYWORDS #define KWTUPLE (&_kwtuple.ob_base.ob_base) @@ -251,21 +247,20 @@ os_statx(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kw # define KWTUPLE NULL #endif // !Py_BUILD_CORE - static const char * const _keywords[] = {"path", "mask", "dir_fd", "follow_symlinks", "sync", "raw", NULL}; + static const char * const _keywords[] = {"path", "mask", "dir_fd", "follow_symlinks", "sync", NULL}; static _PyArg_Parser _parser = { .keywords = _keywords, .fname = "statx", .kwtuple = KWTUPLE, }; #undef KWTUPLE - PyObject *argsbuf[6]; + PyObject *argsbuf[5]; Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 2; path_t path = PATH_T_INITIALIZE_P("statx", "path", 0, 0, 0, 1); unsigned int mask; int dir_fd = DEFAULT_DIR_FD; int follow_symlinks = 1; int sync = -1; - int raw = 0; args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, /*minpos*/ 2, /*maxpos*/ 2, /*minkw*/ 0, /*varpos*/ 0, argsbuf); @@ -311,20 +306,11 @@ os_statx(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kw goto skip_optional_kwonly; } } - if (args[4]) { - if (!optional_bool_converter(args[4], &sync)) { - goto exit; - } - if (!--noptargs) { - goto skip_optional_kwonly; - } - } - raw = PyObject_IsTrue(args[5]); - if (raw < 0) { + if (!optional_bool_converter(args[4], &sync)) { goto exit; } skip_optional_kwonly: - return_value = os_statx_impl(module, &path, mask, dir_fd, follow_symlinks, sync, raw); + return_value = os_statx_impl(module, &path, mask, dir_fd, follow_symlinks, sync); exit: /* Cleanup for path */ @@ -13597,4 +13583,4 @@ os__emscripten_log(PyObject *module, PyObject *const *args, Py_ssize_t nargs, Py #ifndef OS__EMSCRIPTEN_LOG_METHODDEF #define OS__EMSCRIPTEN_LOG_METHODDEF #endif /* !defined(OS__EMSCRIPTEN_LOG_METHODDEF) */ -/*[clinic end generated code: output=67333d54e0a2fff4 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=c9bf5ab9744910bc input=a9049054013a1b77]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 99e3c9a78edbb8..2ff89cf472db22 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -40,6 +40,7 @@ // --- System includes ------------------------------------------------------ +#include // offsetof() #include // ctermid() #include // system() @@ -3412,28 +3413,178 @@ os_lstat_impl(PyObject *module, path_t *path, int dir_fd) #ifdef HAVE_LINUX_STATX +#define STATX_RESULT_CACHE_SLOTS 17 +typedef struct { + PyObject_HEAD + struct statx stx; +#if STATX_RESULT_CACHE_SLOTS > 0 + PyObject *cache[STATX_RESULT_CACHE_SLOTS]; +#endif +} statx_result; + +static PyObject * +_PyFloat_FromStatxTimestamp(struct statx_timestamp ts) { + return PyFloat_FromDouble((double)ts.tv_sec + 1e-9 * ts.tv_nsec); +} + +/* The reserved space in struct statx was originally defined as arrays of u64. + Accessing u32 members (or other future non-u64 members) directly by offset + would be a strict aliasing violation, so use memcpy. */ +#define DECLARE_UNCACHED_GET(name, type, func) \ + static PyObject * \ + statx_result_get_##name(PyObject *op, void *context) { \ + statx_result *self = (statx_result *) op; \ + uint16_t offset = (uintptr_t)context; \ + type val; \ + memcpy(&val, (void *)self + offset, sizeof(val)); \ + return func(val); \ + } + +DECLARE_UNCACHED_GET(uid, uint32_t, _PyLong_FromUid) +DECLARE_UNCACHED_GET(gid, uint32_t, _PyLong_FromGid) +DECLARE_UNCACHED_GET(sec, struct statx_timestamp, _PyFloat_FromStatxTimestamp) +/* Don't use these uncached getters directly -- define members instead. */ +DECLARE_UNCACHED_GET(u16, uint16_t, PyLong_FromUInt32) +DECLARE_UNCACHED_GET(u32, uint32_t, PyLong_FromUInt32) +DECLARE_UNCACHED_GET(u64, uint64_t, PyLong_FromUInt64) +#undef DECLARE_UNCACHED_GET + +static PyObject * +statx_result_get_nsec(PyObject *op, void *context) { + statx_result *self = (statx_result *) op; + uint16_t offset = (uintptr_t)context; + struct statx_timestamp val; + memcpy(&val, (void *)self + offset, sizeof(val)); + _posixstate *state = PyType_GetModuleState(Py_TYPE(op)); + assert(state != NULL); + return nanosecond_timestamp(state, val.tv_sec, val.tv_nsec); +} + +static PyObject * +statx_result_get_dev(PyObject *op, void *context) { + statx_result *self = (statx_result *) op; + uint16_t offset = (uintptr_t)context; + uint32_t major, minor; + memcpy(&major, (void *)self + offset, sizeof(major)); + memcpy(&minor, (void *)self + offset + sizeof(major), sizeof(minor)); + return _PyLong_FromDev(makedev(major, minor)); +} + +#define DECLARE_CACHED_GET(func) \ + static PyObject * \ + func##_cached(PyObject *op, void *context) { \ + statx_result *self = (statx_result *) op; \ + uint16_t slot = (uintptr_t)context >> 16; \ + assert(slot < STATX_RESULT_CACHE_SLOTS); \ + PyObject *cached = FT_ATOMIC_LOAD_PTR(self->cache[slot]); \ + if (cached != NULL) { \ + return Py_NewRef(cached); \ + } \ + Py_BEGIN_CRITICAL_SECTION(self); \ + cached = self->cache[slot]; \ + if (cached == NULL) { \ + cached = func(op, context); \ + if (cached != NULL) { \ + FT_ATOMIC_STORE_PTR(self->cache[slot], cached); \ + } \ + } \ + Py_END_CRITICAL_SECTION(); \ + return Py_XNewRef(cached); \ + } + +DECLARE_CACHED_GET(statx_result_get_uid) +DECLARE_CACHED_GET(statx_result_get_gid) +DECLARE_CACHED_GET(statx_result_get_sec) +DECLARE_CACHED_GET(statx_result_get_u16) +DECLARE_CACHED_GET(statx_result_get_u32) +DECLARE_CACHED_GET(statx_result_get_u64) +DECLARE_CACHED_GET(statx_result_get_nsec) +DECLARE_CACHED_GET(statx_result_get_dev) +#undef DECLARE_CACHED_GET + +/* The low 16 bits of the context pointer are the offset from the start of + statx_result to the struct statx member; the next 16 bits are the cache + index or -1. The high 32 bits (if present) are unused. */ +#define WHERE_NAME_CACHE(name, index) (void *)(offsetof(statx_result, stx.stx_##name) | (index << 16)) +#define WHERE_OFFSET_CACHE(offset, index) (void *)(offsetof(statx_result, stx) + offset | (index << 16)) +#define WHERE_NAME(name) WHERE_NAME_CACHE(name, (uint16_t)-1) +#define WHERE_OFFSET(offset) WHERE_OFFSET_CACHE(offset, (uint16_t)-1) +static PyGetSetDef statx_result_getset[] = { + {"stx_mask", statx_result_get_u32_cached, NULL, "member validity mask", WHERE_NAME_CACHE(mask, 0)}, + {"stx_attributes", statx_result_get_u64_cached, NULL, "Linux inode attribute bits", WHERE_NAME_CACHE(attributes, 1)}, + {"st_uid", statx_result_get_uid_cached, NULL, "user ID of owner", WHERE_NAME_CACHE(uid, 2)}, + {"st_gid", statx_result_get_gid_cached, NULL, "group ID of owner", WHERE_NAME_CACHE(uid, 3)}, + {"st_mode", statx_result_get_u16_cached, NULL, "protection bits", WHERE_NAME_CACHE(mode, 4)}, + {"st_ino", statx_result_get_u64_cached, NULL, "inode", WHERE_NAME_CACHE(ino, 5)}, + {"st_size", statx_result_get_u64_cached, NULL, "total size, in bytes", WHERE_NAME_CACHE(size, 6)}, + {"stx_attributes_mask", statx_result_get_u64_cached, NULL, "Linux inode attribute bits supported for this file", WHERE_NAME_CACHE(attributes_mask, 7)}, + {"st_atime", statx_result_get_sec_cached, NULL, "time of last access", WHERE_NAME_CACHE(atime, 8)}, + {"st_atime_ns", statx_result_get_nsec_cached, NULL, "time of last access in nanoseconds", WHERE_NAME_CACHE(atime, 9)}, + {"st_birthtime", statx_result_get_sec_cached, NULL, "time of creation", WHERE_NAME_CACHE(btime, 10)}, + {"st_birthtime_ns", statx_result_get_nsec_cached, NULL, "time of creation in nanoseconds", WHERE_NAME_CACHE(btime, 11)}, + {"st_ctime", statx_result_get_sec_cached, NULL, "time of last change", WHERE_NAME_CACHE(ctime, 12)}, + {"st_ctime_ns", statx_result_get_nsec_cached, NULL, "time of last change in nanoseconds", WHERE_NAME_CACHE(ctime, 13)}, + {"st_mtime", statx_result_get_sec_cached, NULL, "time of last modification", WHERE_NAME_CACHE(mtime, 14)}, + {"st_mtime_ns", statx_result_get_nsec_cached, NULL, "time of last modification in nanoseconds", WHERE_NAME_CACHE(mtime, 15)}, + {"st_rdev", statx_result_get_dev, NULL, "device type (if inode device)", WHERE_NAME(rdev_major)}, + {"st_dev", statx_result_get_dev_cached, NULL, "device", WHERE_NAME_CACHE(dev_major, 16)}, + {NULL}, +}; +#undef WHERE_OFFSET +#undef WHERE_NAME +#undef WHERE_OFFSET_CACHE +#undef WHERE_NAME_CACHE + +static PyMemberDef statx_result_members[] = { + {"st_blksize", Py_T_UINT, offsetof(statx_result, stx.stx_blksize), Py_READONLY, "blocksize for filesystem I/O"}, + {"st_nlink", Py_T_UINT, offsetof(statx_result, stx.stx_nlink), Py_READONLY, "number of hard links"}, + {"st_blocks", Py_T_ULONGLONG, offsetof(statx_result, stx.stx_blocks), Py_READONLY, "number of blocks allocated"}, + {"stx_mnt_id", Py_T_ULONGLONG, offsetof(statx_result, stx) + 144, Py_READONLY, "mount ID"}, + {"stx_dio_mem_align", Py_T_UINT, offsetof(statx_result, stx) + 152, Py_READONLY, "direct I/O memory buffer alignment"}, + {"stx_dio_offset_align", Py_T_UINT, offsetof(statx_result, stx) + 156, Py_READONLY, "direct I/O file offset alignment"}, + {"stx_subvol", Py_T_ULONGLONG, offsetof(statx_result, stx) + 160, Py_READONLY, "subvolume ID"}, + {"stx_atomic_write_unit_min", Py_T_UINT, offsetof(statx_result, stx) + 168, Py_READONLY, "minimum size for direct I/O with torn-write protection"}, + {"stx_atomic_write_unit_max", Py_T_UINT, offsetof(statx_result, stx) + 172, Py_READONLY, "maximum size for direct I/O with torn-write protection"}, + {"stx_atomic_write_segments_max", Py_T_UINT, offsetof(statx_result, stx) + 176, Py_READONLY, "maximum iovecs for direct I/O with torn-write protection"}, + {"stx_dio_read_offset_align", Py_T_UINT, offsetof(statx_result, stx) + 180, Py_READONLY, "direct I/O file offset alignment for reads"}, + {"stx_atomic_write_unit_max_opt", Py_T_UINT, offsetof(statx_result, stx) + 184, Py_READONLY, "maximum optimized size for direct I/O with torn-write protection"}, + {NULL}, +}; + static int statx_result_traverse(PyObject *self, visitproc visit, void *arg) { Py_VISIT(Py_TYPE(self)); - return PyObject_VisitManagedDict((PyObject*)self, visit, arg); + /* self->cache only points to longs and floats */ + return 0; } -static int -statx_result_clear(PyObject *self) { - PyObject_ClearManagedDict(self); - return 0; +static void +statx_result_dealloc(PyObject *op) { + statx_result *self = (statx_result *) op; + PyTypeObject *tp = Py_TYPE(self); + PyObject_GC_UnTrack(self); +#if STATX_RESULT_CACHE_SLOTS > 0 + for (int i = 0; i < STATX_RESULT_CACHE_SLOTS; ++i) { + Py_CLEAR(self->cache[i]); + } +#endif + tp->tp_free(self); + Py_DECREF(tp); } static PyType_Slot statx_result_slots[] = { {Py_tp_traverse, statx_result_traverse}, - {Py_tp_clear, statx_result_clear}, + {Py_tp_dealloc, statx_result_dealloc}, + {Py_tp_getset, statx_result_getset}, + {Py_tp_members, statx_result_members}, {0, NULL}, }; static PyType_Spec statx_result_spec = { .name = "statx_result", - .basicsize = sizeof(PyObject), - .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HEAPTYPE | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_MANAGED_DICT | Py_TPFLAGS_IMMUTABLETYPE, + .basicsize = sizeof(statx_result), + .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HEAPTYPE | Py_TPFLAGS_HAVE_GC | + Py_TPFLAGS_IMMUTABLETYPE | Py_TPFLAGS_DISALLOW_INSTANTIATION, .slots = statx_result_slots, }; @@ -3488,11 +3639,6 @@ os.statx expensive. If False, statx will return cached values if possible. If None, statx lets the operating system decide. - raw: bool = False - If False, fields that were not requested or that are not valid will be - None. If True, all fields will be initialized with the (possibly fake) - kernel-provided value; use stx_mask to check validity. - Perform a statx system call on the given path. It's an error to use dir_fd or follow_symlinks when specifying path as @@ -3502,8 +3648,8 @@ It's an error to use dir_fd or follow_symlinks when specifying path as static PyObject * os_statx_impl(PyObject *module, path_t *path, unsigned int mask, int dir_fd, - int follow_symlinks, int sync, int raw) -/*[clinic end generated code: output=94261132ec9b507e input=de4f8caad620361b]*/ + int follow_symlinks, int sync) +/*[clinic end generated code: output=fe385235585f3d07 input=148c4fce440ca53a]*/ { if (path_and_dir_fd_invalid("statx", path, dir_fd) || dir_fd_and_fd_invalid("statx", dir_fd, path->fd) || @@ -3511,7 +3657,6 @@ os_statx_impl(PyObject *module, path_t *path, unsigned int mask, int dir_fd, return NULL; int result; - struct statx stx = {}; /* Future bits may refer to members beyond the current size of struct statx, so we need to mask them off to prevent memory corruption. */ mask &= _Py_STATX_KNOWN; @@ -3520,98 +3665,29 @@ os_statx_impl(PyObject *module, path_t *path, unsigned int mask, int dir_fd, flags |= sync ? AT_STATX_FORCE_SYNC : AT_STATX_DONT_SYNC; } + _posixstate *state = get_posix_state(module); + PyTypeObject *tp = (PyTypeObject *)state->StatxResultType; + statx_result *v = (statx_result *)tp->tp_alloc(tp, 0); + if (v == NULL) { + return NULL; + } + Py_BEGIN_ALLOW_THREADS if (path->fd != -1) { - result = statx(path->fd, "", flags | AT_EMPTY_PATH, mask, &stx); + result = statx(path->fd, "", flags | AT_EMPTY_PATH, mask, &v->stx); } else { - result = statx(dir_fd, path->narrow, flags, mask, &stx); + result = statx(dir_fd, path->narrow, flags, mask, &v->stx); } Py_END_ALLOW_THREADS if (result != 0) { + Py_DECREF(v); return path_error(path); } - unsigned int fill_mask = raw ? _Py_STATX_KNOWN : mask & stx.stx_mask; - - _posixstate *state = get_posix_state(module); - PyTypeObject *tp = (PyTypeObject *)state->StatxResultType; - PyObject *v = tp->tp_alloc(tp, 0); - -#define SET_ATTR_IF(name, cond, expr) \ - do { \ - PyObject *obj = (cond) ? (expr) : Py_None; \ - if (obj == NULL) { \ - goto error; \ - } \ - if (PyObject_SetAttrString(v, (name), obj) == -1) { \ - Py_DECREF(obj); \ - goto error; \ - } \ - } while (0) -#define SET_ATTR(name, expr) SET_ATTR_IF((name), true, (expr)) -#define SET_ATTR_IF_BIT(name, bit, expr) SET_ATTR_IF((name), fill_mask & (bit), (expr)) - - SET_ATTR("stx_mask", PyLong_FromUnsignedLong(stx.stx_mask)); - SET_ATTR("st_blksize", PyLong_FromUnsignedLong(stx.stx_blksize)); - SET_ATTR("stx_attributes", PyLong_FromUnsignedLongLong(stx.stx_attributes)); - SET_ATTR_IF_BIT("st_nlink", STATX_NLINK, PyLong_FromUnsignedLong(stx.stx_nlink)); - SET_ATTR_IF_BIT("st_uid", STATX_UID, _PyLong_FromUid(stx.stx_uid)); - SET_ATTR_IF_BIT("st_gid", STATX_GID, _PyLong_FromUid(stx.stx_gid)); - SET_ATTR_IF_BIT("st_mode", STATX_TYPE | STATX_MODE, PyLong_FromUnsignedLong(stx.stx_mode)); - SET_ATTR_IF_BIT("st_ino", STATX_INO, PyLong_FromUnsignedLongLong(stx.stx_ino)); - SET_ATTR_IF_BIT("st_size", STATX_SIZE, PyLong_FromUnsignedLongLong(stx.stx_size)); - SET_ATTR_IF_BIT("st_blocks", STATX_BLOCKS, PyLong_FromUnsignedLongLong(stx.stx_blocks)); - SET_ATTR("stx_attributes_mask", PyLong_FromUnsignedLongLong(stx.stx_attributes_mask)); - -#define SET_ATTR_IF_BIT_TS(name, bit, ts) \ - do { \ - if (fill_mask & (bit)) { \ - SET_ATTR((name), PyFloat_FromDouble((double)(ts).tv_sec + 1e-9 * (ts).tv_nsec)); \ - SET_ATTR(name "_ns", nanosecond_timestamp(state, (ts).tv_sec, (ts).tv_nsec)); \ - } \ - else { \ - SET_ATTR((name), Py_None); \ - SET_ATTR(name "_ns", Py_None); \ - } \ - } while (0) - SET_ATTR_IF_BIT_TS("st_atime", STATX_ATIME, stx.stx_atime); - SET_ATTR_IF_BIT_TS("st_mtime", STATX_MTIME, stx.stx_mtime); - SET_ATTR_IF_BIT_TS("st_ctime", STATX_CTIME, stx.stx_ctime); - SET_ATTR_IF_BIT_TS("st_birthtime", STATX_BTIME, stx.stx_btime); - - bool rdev_relevant = (stx.stx_mask & STATX_TYPE) && (S_ISBLK(stx.stx_mode) || S_ISCHR(stx.stx_mode)); - SET_ATTR_IF("st_rdev", rdev_relevant || raw, _PyLong_FromDev(makedev(stx.stx_rdev_major, stx.stx_rdev_minor))); - SET_ATTR("st_dev", _PyLong_FromDev(makedev(stx.stx_dev_major, stx.stx_dev_minor))); - -#define SET_ATTR_IF_BIT_OFFSET(name, bit, offset, length) \ - do { \ - void *addr = (unsigned char *)&stx + (offset); \ - SET_ATTR_IF_BIT((name), (bit), PyLong_FromUnsignedNativeBytes(addr, (length), -1)); \ - } while (0) - SET_ATTR_IF_BIT_OFFSET("stx_mnt_id", STATX_MNT_ID | STATX_MNT_ID_UNIQUE, 144, 8); - SET_ATTR_IF_BIT_OFFSET("stx_dio_mem_align", STATX_DIOALIGN, 152, 4); - SET_ATTR_IF_BIT_OFFSET("stx_dio_offset_align", STATX_DIOALIGN, 156, 4); - SET_ATTR_IF_BIT_OFFSET("stx_subvol", STATX_SUBVOL, 160, 8); - SET_ATTR_IF_BIT_OFFSET("stx_atomic_write_unit_min", STATX_WRITE_ATOMIC, 168, 4); - SET_ATTR_IF_BIT_OFFSET("stx_atomic_write_unit_max", STATX_WRITE_ATOMIC, 172, 4); - SET_ATTR_IF_BIT_OFFSET("stx_atomic_write_segments_max", STATX_WRITE_ATOMIC, 176, 4); - SET_ATTR_IF_BIT_OFFSET("stx_dio_read_offset_align", STATX_DIO_READ_ALIGN, 180, 4); - SET_ATTR_IF_BIT_OFFSET("stx_atomic_write_unit_max_opt", STATX_WRITE_ATOMIC, 184, 4); - -#undef SET_ATTR_IF_BIT_OFFSET -#undef SET_ATTR_IF_BIT_TS -#undef SET_ATTR_IF_BIT -#undef SET_ATTR -#undef SET_ATTR_IF - assert(!PyErr_Occurred()); - return v; - -error: - Py_DECREF(v); - return Py_None; + return (PyObject *)v; } #endif From 534e33fccd2caaea2f5970359b7aba46e9c08116 Mon Sep 17 00:00:00 2001 From: Jeffrey Bosboom Date: Sat, 13 Sep 2025 01:35:37 -0700 Subject: [PATCH 11/18] Remove statx-specific members from os.stat_result documentation These members were removed from os.stat_result in a previous commit. --- Doc/library/os.rst | 36 +++++------------------------------- 1 file changed, 5 insertions(+), 31 deletions(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 2ca61de244e084..5f5c7c43911d9b 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -3235,6 +3235,9 @@ features: :exc:`AttributeError`. .. versionadded:: 3.12 + .. versionchanged:: next + ``st_birthtime`` is now available on Linux kernel 4.11 and later when + supported by the filesystem. .. note:: @@ -3276,34 +3279,6 @@ features: User defined flags for file. - .. attribute:: st_attributes - - Linux file attributes. - See the :const:`!STATX_ATTR* ` - constants in the :mod:`stat` module. - - .. versionadded:: next - - .. attribute:: st_attributes_mask - - Linux file attributes supported by the filesystem containing the file. - - .. versionadded:: next - - .. attribute:: st_mnt_id - - Mount ID of the mount containing the file, corresponding to the first - field in ``/proc/self/mountinfo``. - - .. versionadded:: next - - .. attribute:: st_subvol - - ID for the subvolume containing the file, or None if the filesystem does - not support subvolumes. - - .. versionadded:: next - On other Unix systems (such as FreeBSD), the following attributes may be available (but may be only filled out if root tries to use them): @@ -3396,9 +3371,8 @@ features: Added the :attr:`st_birthtime` member on Windows. .. versionchanged:: next - Added the :attr:`st_birthtime`, :attr:`st_attributes`, - :attr:`st_attributes_mask`, :attr:`st_mnt_id`, and :attr:`st_subvol` - members on Linux. + Added the :attr:`st_birthtime` and :attr:`st_birthtime_ns` members on + Linux. .. function:: statvfs(path) From 7dc75d057400d8f3e2e6b45d4fa2886e106ff4f5 Mon Sep 17 00:00:00 2001 From: Jeffrey Bosboom Date: Sat, 13 Sep 2025 02:02:41 -0700 Subject: [PATCH 12/18] Simplify statx configure check --- Modules/clinic/posixmodule.c.h | 6 ++-- Modules/posixmodule.c | 36 ++++++++++---------- configure | 61 ++++------------------------------ configure.ac | 18 +--------- pyconfig.h.in | 6 ++-- 5 files changed, 31 insertions(+), 96 deletions(-) diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index 677c1b25c028ea..79b2fed0621821 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -186,7 +186,7 @@ os_lstat(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kw return return_value; } -#if defined(HAVE_LINUX_STATX) +#if defined(HAVE_STATX) PyDoc_STRVAR(os_statx__doc__, "statx($module, /, path, mask, *, dir_fd=None, follow_symlinks=True,\n" @@ -319,7 +319,7 @@ os_statx(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kw return return_value; } -#endif /* defined(HAVE_LINUX_STATX) */ +#endif /* defined(HAVE_STATX) */ PyDoc_STRVAR(os_access__doc__, "access($module, /, path, mode, *, dir_fd=None, effective_ids=False,\n" @@ -13583,4 +13583,4 @@ os__emscripten_log(PyObject *module, PyObject *const *args, Py_ssize_t nargs, Py #ifndef OS__EMSCRIPTEN_LOG_METHODDEF #define OS__EMSCRIPTEN_LOG_METHODDEF #endif /* !defined(OS__EMSCRIPTEN_LOG_METHODDEF) */ -/*[clinic end generated code: output=c9bf5ab9744910bc input=a9049054013a1b77]*/ +/*[clinic end generated code: output=43915d63088debf2 input=a9049054013a1b77]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 2ff89cf472db22..438f6ee5f4ba14 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -409,7 +409,7 @@ extern char *ctermid_r(char *); # define STRUCT_STAT struct stat #endif -#ifdef HAVE_LINUX_STATX +#ifdef HAVE_STATX # pragma weak statx /* provide constants introduced later than statx itself */ # ifndef STATX_MNT_ID @@ -431,7 +431,7 @@ extern char *ctermid_r(char *); # define STATX_DIO_READ_ALIGN 0x00020000U # endif # define _Py_STATX_KNOWN (STATX_BASIC_STATS | STATX_BTIME | STATX_MNT_ID | STATX_DIOALIGN | STATX_MNT_ID_UNIQUE | STATX_SUBVOL | STATX_WRITE_ATOMIC | STATX_DIO_READ_ALIGN) -#endif /* HAVE_LINUX_STATX */ +#endif /* HAVE_STATX */ #if !defined(EX_OK) && defined(EXIT_SUCCESS) @@ -1184,7 +1184,7 @@ typedef struct { #endif newfunc statresult_new_orig; PyObject *StatResultType; -#ifdef HAVE_LINUX_STATX +#ifdef HAVE_STATX PyObject *StatxResultType; #endif PyObject *StatVFSResultType; @@ -2381,10 +2381,10 @@ static PyStructSequence_Field stat_result_fields[] = { #ifdef HAVE_STRUCT_STAT_ST_GEN {"st_gen", "generation number"}, #endif -#if defined(HAVE_STRUCT_STAT_ST_BIRTHTIME) || defined(HAVE_LINUX_STATX) || defined(MS_WINDOWS) +#if defined(HAVE_STRUCT_STAT_ST_BIRTHTIME) || defined(HAVE_STATX) || defined(MS_WINDOWS) {"st_birthtime", "time of creation"}, #endif -#if defined(HAVE_LINUX_STATX) || defined(MS_WINDOWS) +#if defined(HAVE_STATX) || defined(MS_WINDOWS) {"st_birthtime_ns", "time of creation in nanoseconds"}, #endif #ifdef HAVE_STRUCT_STAT_ST_FILE_ATTRIBUTES @@ -2429,13 +2429,13 @@ static PyStructSequence_Field stat_result_fields[] = { #define ST_GEN_IDX ST_FLAGS_IDX #endif -#if defined(HAVE_STRUCT_STAT_ST_BIRTHTIME) || defined(HAVE_LINUX_STATX) || defined(MS_WINDOWS) +#if defined(HAVE_STRUCT_STAT_ST_BIRTHTIME) || defined(HAVE_STATX) || defined(MS_WINDOWS) #define ST_BIRTHTIME_IDX (ST_GEN_IDX+1) #else #define ST_BIRTHTIME_IDX ST_GEN_IDX #endif -#if defined(HAVE_LINUX_STATX) || defined(MS_WINDOWS) +#if defined(HAVE_STATX) || defined(MS_WINDOWS) #define ST_BIRTHTIME_NS_IDX (ST_BIRTHTIME_IDX+1) #else #define ST_BIRTHTIME_NS_IDX ST_BIRTHTIME_IDX @@ -2567,7 +2567,7 @@ _posix_clear(PyObject *module) Py_CLEAR(state->SchedParamType); #endif Py_CLEAR(state->StatResultType); -#ifdef HAVE_LINUX_STATX +#ifdef HAVE_STATX Py_CLEAR(state->StatxResultType); #endif Py_CLEAR(state->StatVFSResultType); @@ -2595,7 +2595,7 @@ _posix_traverse(PyObject *module, visitproc visit, void *arg) Py_VISIT(state->SchedParamType); #endif Py_VISIT(state->StatResultType); -#ifdef HAVE_LINUX_STATX +#ifdef HAVE_STATX Py_VISIT(state->StatxResultType); #endif Py_VISIT(state->StatVFSResultType); @@ -2810,7 +2810,7 @@ _pystat_fromstructstat(PyObject *module, STRUCT_STAT *st) #endif SET_ITEM(ST_BIRTHTIME_IDX, PyFloat_FromDouble(bsec + bnsec * 1e-9)); } -#elif defined(HAVE_LINUX_STATX) +#elif defined(HAVE_STATX) /* We were built with statx support, so stat_result.st_birthtime[_ns] exists, but we fell back to stat because statx isn't available at runtime. User programs assume st_birthtime is not None. */ @@ -2844,7 +2844,7 @@ _pystat_fromstructstat(PyObject *module, STRUCT_STAT *st) return NULL; } -#ifdef HAVE_LINUX_STATX +#ifdef HAVE_STATX static PyObject* _pystat_fromstructstatx(PyObject *module, struct statx *st) { @@ -2905,7 +2905,7 @@ _pystat_fromstructstatx(PyObject *module, struct statx *st) Py_DECREF(v); return NULL; } -#endif /* HAVE_LINUX_STATX */ +#endif /* HAVE_STATX */ #undef SET_ITEM /* POSIX methods */ @@ -2932,7 +2932,7 @@ posix_do_stat(PyObject *module, const char *function_name, path_t *path, fd_and_follow_symlinks_invalid("stat", path->fd, follow_symlinks)) return NULL; -#ifdef HAVE_LINUX_STATX +#ifdef HAVE_STATX struct statx stx = {}; static int statx_works = -1; if (statx != NULL && statx_works != 0) { @@ -2962,7 +2962,7 @@ posix_do_stat(PyObject *module, const char *function_name, path_t *path, return _pystat_fromstructstatx(module, &stx); } } -#endif /* HAVE_LINUX_STATX */ +#endif /* HAVE_STATX */ Py_BEGIN_ALLOW_THREADS if (path->fd != -1) @@ -3412,7 +3412,7 @@ os_lstat_impl(PyObject *module, path_t *path, int dir_fd) } -#ifdef HAVE_LINUX_STATX +#ifdef HAVE_STATX #define STATX_RESULT_CACHE_SLOTS 17 typedef struct { PyObject_HEAD @@ -18294,7 +18294,7 @@ all_ins(PyObject *m) #endif #endif /* HAVE_EVENTFD && EFD_CLOEXEC */ -#ifdef HAVE_LINUX_STATX +#ifdef HAVE_STATX if (PyModule_AddIntMacro(m, STATX_TYPE)) return -1; if (PyModule_AddIntMacro(m, STATX_MODE)) return -1; if (PyModule_AddIntMacro(m, STATX_NLINK)) return -1; @@ -18316,7 +18316,7 @@ all_ins(PyObject *m) if (PyModule_AddIntMacro(m, STATX_DIO_READ_ALIGN)) return -1; /* STATX_ALL intentionally omitted because it is deprecated */ /* STATX_ATTR_* constants are in the stat module */ -#endif /* HAVE_LINUX_STATX */ +#endif /* HAVE_STATX */ #if defined(__APPLE__) if (PyModule_AddIntConstant(m, "_COPYFILE_DATA", COPYFILE_DATA)) return -1; @@ -18589,7 +18589,7 @@ posixmodule_exec(PyObject *m) } #endif -#ifdef HAVE_LINUX_STATX +#ifdef HAVE_STATX /* We retract os.statx in three cases: - the weakly-linked statx wrapper function is not available (old libc) - the wrapper function fails with EINVAL on sync flags (glibc's diff --git a/configure b/configure index 171e67c2b1d8ec..03cdd5a37679a7 100755 --- a/configure +++ b/configure @@ -1138,7 +1138,6 @@ with_openssl_rpath with_ssl_default_suites with_builtin_hashlib_hashes enable_test_modules -with_linux_statx ' ac_precious_vars='build_alias host_alias @@ -1963,8 +1962,6 @@ Optional Packages: --with-builtin-hashlib-hashes=md5,sha1,sha2,sha3,blake2 builtin hash modules, md5, sha1, sha2, sha3 (with shake), blake2 - --without-linux-statx don't provide os.statx nor - os.stat_result.st_birthtime Some influential environment variables: PKG_CONFIG path to pkg-config utility @@ -20178,6 +20175,12 @@ if test "x$ac_cv_func_splice" = xyes then : printf "%s\n" "#define HAVE_SPLICE 1" >>confdefs.h +fi +ac_fn_c_check_func "$LINENO" "statx" "ac_cv_func_statx" +if test "x$ac_cv_func_statx" = xyes +then : + printf "%s\n" "#define HAVE_STATX 1" >>confdefs.h + fi ac_fn_c_check_func "$LINENO" "strftime" "ac_cv_func_strftime" if test "x$ac_cv_func_strftime" = xyes @@ -30925,58 +30928,6 @@ fi printf "%s\n" "$TEST_MODULES" >&6; } -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for --without-linux-statx" >&5 -printf %s "checking for --without-linux-statx... " >&6; } - -# Check whether --with-linux-statx was given. -if test ${with_linux_statx+y} -then : - withval=$with_linux_statx; -else case e in #( - e) case $ac_sys_system in #( - Linux*) : - with_linux_statx=yes ;; #( - *) : - with_linux_statx=no ;; -esac ;; -esac -fi - -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $with_linux_statx" >&5 -printf "%s\n" "$with_linux_statx" >&6; } -if test "x$with_linux_statx" != xno -then : - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ - - #include - -int -main (void) -{ -void* p = statx - ; - return 0; -} -_ACEOF -if ac_fn_c_try_compile "$LINENO" -then : - -printf "%s\n" "#define HAVE_LINUX_STATX 1" >>confdefs.h - - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 -printf "%s\n" "yes" >&6; } -else case e in #( - e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 -printf "%s\n" "no" >&6; } - as_fn_error $? "statx syscall wrapper function not available; upgrade to - glibc 2.28 or later or use --without-linux-statx" "$LINENO" 5 - ;; -esac -fi -rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext -fi - # gh-109054: Check if -latomic is needed to get atomic functions. # On Linux aarch64, GCC may require programs and libraries to be linked # explicitly to libatomic. Call _Py_atomic_or_uint64() which may require diff --git a/configure.ac b/configure.ac index 014b31c5a57a1d..a5d2de5e5da895 100644 --- a/configure.ac +++ b/configure.ac @@ -5252,7 +5252,7 @@ AC_CHECK_FUNCS([ \ setitimer setlocale setpgid setpgrp setpriority setregid setresgid \ setresuid setreuid setsid setuid setvbuf shutdown sigaction sigaltstack \ sigfillset siginterrupt sigpending sigrelse sigtimedwait sigwait \ - sigwaitinfo snprintf splice strftime strlcpy strsignal symlinkat sync \ + sigwaitinfo snprintf splice statx strftime strlcpy strsignal symlinkat sync \ sysconf tcgetpgrp tcsetpgrp tempnam timegm times tmpfile \ tmpnam tmpnam_r truncate ttyname_r umask uname unlinkat unlockpt utimensat utimes vfork \ wait wait3 wait4 waitid waitpid wcscoll wcsftime wcsxfrm wmemcmp writev \ @@ -7595,22 +7595,6 @@ AC_ARG_ENABLE([test-modules], AC_MSG_RESULT([$TEST_MODULES]) AC_SUBST([TEST_MODULES]) -AC_MSG_CHECKING([for --without-linux-statx]) -AC_ARG_WITH([linux-statx], - [AS_HELP_STRING([--without-linux-statx], [don't provide os.statx nor os.stat_result.st_birthtime])], - [], [AS_CASE($ac_sys_system, [Linux*], [with_linux_statx=yes], [with_linux_statx=no])]) -AC_MSG_RESULT([$with_linux_statx]) -AS_IF([test "x$with_linux_statx" != xno], - [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ - #include - ]], [[void* p = statx]])], - [AC_DEFINE(HAVE_LINUX_STATX, 1, Define if you have the 'statx' function.) - AC_MSG_RESULT(yes)], - [AC_MSG_RESULT(no) - AC_MSG_ERROR([statx syscall wrapper function not available; upgrade to - glibc 2.28 or later or use --without-linux-statx]) - ])]) - # gh-109054: Check if -latomic is needed to get atomic functions. # On Linux aarch64, GCC may require programs and libraries to be linked # explicitly to libatomic. Call _Py_atomic_or_uint64() which may require diff --git a/pyconfig.h.in b/pyconfig.h.in index 0c9dd59efaf0c0..3c5cb53ea3ea7d 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -766,9 +766,6 @@ /* Define to 1 if you have the header file. */ #undef HAVE_LINUX_SOUNDCARD_H -/* Define if you have the 'statx' function. */ -#undef HAVE_LINUX_STATX - /* Define to 1 if you have the header file. */ #undef HAVE_LINUX_TIPC_H @@ -1282,6 +1279,9 @@ /* Define to 1 if you have the 'statvfs' function. */ #undef HAVE_STATVFS +/* Define to 1 if you have the 'statx' function. */ +#undef HAVE_STATX + /* Define if you have struct stat.st_mtim.tv_nsec */ #undef HAVE_STAT_TV_NSEC From 894efb576db2bed115e4862a43f89a52b57e7afc Mon Sep 17 00:00:00 2001 From: Jeffrey Bosboom Date: Sun, 14 Sep 2025 00:11:34 -0700 Subject: [PATCH 13/18] Document os.statx --- Doc/library/os.rst | 103 +++++++++++++++++++++++++++++++++++++++++++ Doc/library/stat.rst | 8 ++-- 2 files changed, 107 insertions(+), 4 deletions(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 5f5c7c43911d9b..33c1539b0e43bb 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -3375,6 +3375,109 @@ features: Linux. +.. function:: statx(path, mask, *, dir_fd=None, follow_symlinks=True, sync=None) + + Get the status of a file or file descriptor by performing a :c:func:`statx` + system call on the given path. *path* may be specified as either a string or + bytes -- directly or indirectly through the :class:`PathLike` interface -- + or as an open file descriptor. *mask* is a combination of the module-level + :const:`STATX_* ` constants specifying the information to + retrieve. Returns a :class:`statx_result` object whose + :attr:`~os.statx_result.stx_mask` attribute specifies the information + actually retrieved (which may differ from *mask*). + + The optional parameter *sync* controls the freshness of the returned + information. ``sync=True`` requests that the kernel return up-to-date + information, even when doing so is expensive (for example, requiring a + round-trip to the server for a file on a network filesystem). + ``sync=False`` requests that the kernel return cached information if + available. + + This function supports :ref:`specifying a file descriptor `, + :ref:`paths relative to directory descriptors `, and + :ref:`not following symlinks `. + + .. seealso:: The :manpage:`statx(2)` man page. + + .. availability:: Linux >= 4.11 with glibc >= 2.28. + + .. versionadded:: next + + +.. class:: statx_result + + Object whose attributes correspond roughly to the members of the + :c:struct:`statx` structure. It is used for the result of :func:`os.statx`. + :class:`!statx_result` has all of the attributes of :class:`stat_result` + available on Linux, but is not a subclass of :class:`stat_result` nor a + tuple. :class:`!statx_result` has the following additional attributes: + + .. attribute:: stx_mask + + Bitmask of :const:`STATX_* ` constants specifying the + information retrieved, which may differ from what was requested depending + on the filesystem, filesystem type, and kernel version. All attributes + of this class are accessible regardless of the value of + :attr:`!stx_mask`, and they may have useful fictitious values. For + example, for a file on a network filesystem, :const:`STATX_UID` and + :const:`STATX_GID` may be unset because file ownership on the server is + based on an external user database, but :attr:`!st_uid` and + :attr:`!st_gid` may contain the IDs of the local user who controls the + mount. + + .. attribute:: stx_attributes_mask + + Bitmask of :const:`!STATX_ATTR_* ` constants + specifying the attributes bits supported for this file. + + .. attribute:: stx_attributes + + Bitmask of :const:`!STATX_ATTR_* ` constants + specifying the attributes of this file. + + .. attribute:: stx_mnt_id + .. attribute:: stx_dio_mem_align + .. attribute:: stx_dio_offset_align + .. attribute:: stx_subvol + .. attribute:: stx_atomic_write_unit_min + .. attribute:: stx_atomic_write_unit_max + .. attribute:: stx_atomic_write_segments_max + .. attribute:: stx_dio_read_offset_align + .. attribute:: stx_atomic_write_unit_max_opt + + .. seealso:: The :manpage:`statx(2)` man page. + + .. availability:: Linux >= 4.11 with glibc >= 2.28. + + .. versionadded:: next + +.. data:: STATX_TYPE + STATX_MODE + STATX_NLINK + STATX_UID + STATX_GID + STATX_ATIME + STATX_MTIME + STATX_CTIME + STATX_INO + STATX_SIZE + STATX_BLOCKS + STATX_BASIC_STATS + STATX_BTIME + STATX_MNT_ID + STATX_DIOALIGN + STATX_MNT_ID_UNIQUE + STATX_SUBVOL + STATX_WRITE_ATOMIC + STATX_DIO_READ_ALIGN + + Bitflags for use as the *mask* parameter to :func:`os.statx`. + + .. availability:: Linux >= 4.11 with glibc >= 2.28. + + .. versionadded:: next + + .. function:: statvfs(path) Perform a :c:func:`!statvfs` system call on the given path. The return value is diff --git a/Doc/library/stat.rst b/Doc/library/stat.rst index 9e957ae198659b..1cbec3ab847c5f 100644 --- a/Doc/library/stat.rst +++ b/Doc/library/stat.rst @@ -494,10 +494,10 @@ constants, but are not an exhaustive list. .. versionadded:: 3.8 -On Linux, the following constants are available for comparing against the -``st_attributes`` and ``st_attributes_mask`` members returned by -:func:`os.stat`. See the `statx(2) man page -` for more detail on the +On Linux, the following file attribute constants are available for use when +testing bits in the :attr:`~os.statx_result.stx_attributes` and +:attr:`~os.statx_result.stx_attributes_mask` members returned by +:func:`os.statx`. See the :manpage:`statx(2)` man page for more detail on the meaning of these constants. .. data:: STATX_ATTR_COMPRESSED From d5bc601440c38405fa36cedebae8216bb9734bfc Mon Sep 17 00:00:00 2001 From: Jeffrey Bosboom Date: Sun, 14 Sep 2025 21:13:18 -0700 Subject: [PATCH 14/18] Prepare for review --- Doc/library/os.rst | 32 ++++++++-- Lib/test/test_os.py | 34 +++++------ Modules/posixmodule.c | 133 +++++++++++++++++++++++++++++------------- 3 files changed, 138 insertions(+), 61 deletions(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 33c1539b0e43bb..ecf696f277363e 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -3225,8 +3225,7 @@ features: .. versionchanged:: 3.12 ``st_birthtime`` is now available on Windows. .. versionchanged:: next - ``st_birthtime`` is now available on Linux kernel 4.11 and later when - supported by the filesystem. + ``st_birthtime`` is now available on Linux. .. attribute:: st_birthtime_ns @@ -3236,8 +3235,7 @@ features: .. versionadded:: 3.12 .. versionchanged:: next - ``st_birthtime`` is now available on Linux kernel 4.11 and later when - supported by the filesystem. + ``st_birthtime_ns`` is now available on Linux. .. note:: @@ -3436,15 +3434,41 @@ features: specifying the attributes of this file. .. attribute:: stx_mnt_id + + Mount ID. + .. attribute:: stx_dio_mem_align + + Direct I/O memory buffer alignment requirement. + .. attribute:: stx_dio_offset_align + + Direct I/O file offset alignment requirement. + .. attribute:: stx_subvol + + Subvolume ID. + .. attribute:: stx_atomic_write_unit_min + + Minimum size for direct I/O with torn-write protection. + .. attribute:: stx_atomic_write_unit_max + + Maximum size for direct I/O with torn-write protection. + .. attribute:: stx_atomic_write_segments_max + + Maximum iovecs for direct I/O with torn-write protection. + .. attribute:: stx_dio_read_offset_align + + Direct I/O file offset alignment requirement for reads. + .. attribute:: stx_atomic_write_unit_max_opt + Maximum optimized size for direct I/O with torn-write protection. + .. seealso:: The :manpage:`statx(2)` man page. .. availability:: Linux >= 4.11 with glibc >= 2.28. diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index bd6e8d73d4abc9..dc75e6096d2e95 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -726,6 +726,17 @@ def test_stat_attributes_bytes(self): self.skipTest("cannot encode %a for the filesystem" % self.fname) self.check_stat_attributes(fname) + def test_stat_result_pickle(self): + result = os.stat(self.fname) + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + with self.subTest(f'protocol {proto}'): + p = pickle.dumps(result, proto) + self.assertIn(b'stat_result', p) + if proto < 4: + self.assertIn(b'cos\nstat_result\n', p) + unpickled = pickle.loads(p) + self.assertEqual(result, unpickled) + def check_statx_attributes(self, fname): maximal_mask = 0 for name in dir(os): @@ -754,13 +765,13 @@ def check_statx_attributes(self, fname): ('st_birthtime', os.STATX_BTIME), ('st_birthtime_ns', os.STATX_BTIME), # unconditionally valid members - ('st_blksize', maximal_mask), - ('st_dev', maximal_mask), - ('st_rdev', maximal_mask), + ('st_blksize', 0), + ('st_dev', 0), + ('st_rdev', 0), ) basic_result = os.stat(self.fname) for name, bits in requirements: - if result.stx_mask & bits: + if result.stx_mask & bits == bits: x = getattr(result, name) b = getattr(basic_result, name) if isinstance(x, float): @@ -782,8 +793,8 @@ def check_statx_attributes(self, fname): except AttributeError: pass - self.assertTrue(result.stx_attributes & result.stx_attributes_mask - == result.stx_attributes) + self.assertEqual(result.stx_attributes & result.stx_attributes_mask, + result.stx_attributes) @unittest.skipUnless(hasattr(os, 'statx'), 'test needs os.statx()') def test_statx_attributes(self): @@ -805,17 +816,6 @@ def test_statx_sync(self): with self.subTest(sync=sync): os.statx(self.fname, os.STATX_BASIC_STATS, sync=sync) - def test_stat_result_pickle(self): - result = os.stat(self.fname) - for proto in range(pickle.HIGHEST_PROTOCOL + 1): - with self.subTest(f'protocol {proto}'): - p = pickle.dumps(result, proto) - self.assertIn(b'stat_result', p) - if proto < 4: - self.assertIn(b'cos\nstat_result\n', p) - unpickled = pickle.loads(p) - self.assertEqual(result, unpickled) - @unittest.skipUnless(hasattr(os, 'statvfs'), 'test needs os.statvfs()') def test_statvfs_attributes(self): result = os.statvfs(self.fname) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 438f6ee5f4ba14..26ac40e8525427 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -430,7 +430,10 @@ extern char *ctermid_r(char *); # ifndef STATX_DIO_READ_ALIGN # define STATX_DIO_READ_ALIGN 0x00020000U # endif -# define _Py_STATX_KNOWN (STATX_BASIC_STATS | STATX_BTIME | STATX_MNT_ID | STATX_DIOALIGN | STATX_MNT_ID_UNIQUE | STATX_SUBVOL | STATX_WRITE_ATOMIC | STATX_DIO_READ_ALIGN) +# define _Py_STATX_KNOWN (STATX_BASIC_STATS | STATX_BTIME | STATX_MNT_ID | \ + STATX_DIOALIGN | STATX_MNT_ID_UNIQUE | \ + STATX_SUBVOL | STATX_WRITE_ATOMIC | \ + STATX_DIO_READ_ALIGN) #endif /* HAVE_STATX */ @@ -3475,7 +3478,6 @@ statx_result_get_dev(PyObject *op, void *context) { func##_cached(PyObject *op, void *context) { \ statx_result *self = (statx_result *) op; \ uint16_t slot = (uintptr_t)context >> 16; \ - assert(slot < STATX_RESULT_CACHE_SLOTS); \ PyObject *cached = FT_ATOMIC_LOAD_PTR(self->cache[slot]); \ if (cached != NULL) { \ return Py_NewRef(cached); \ @@ -3505,52 +3507,89 @@ DECLARE_CACHED_GET(statx_result_get_dev) /* The low 16 bits of the context pointer are the offset from the start of statx_result to the struct statx member; the next 16 bits are the cache index or -1. The high 32 bits (if present) are unused. */ -#define WHERE_NAME_CACHE(name, index) (void *)(offsetof(statx_result, stx.stx_##name) | (index << 16)) -#define WHERE_OFFSET_CACHE(offset, index) (void *)(offsetof(statx_result, stx) + offset | (index << 16)) -#define WHERE_NAME(name) WHERE_NAME_CACHE(name, (uint16_t)-1) -#define WHERE_OFFSET(offset) WHERE_OFFSET_CACHE(offset, (uint16_t)-1) +#define OFFSET_CACHE_CONTEXT(offset, index) \ + (void *)((offsetof(statx_result, stx) + offset) | ((size_t)(index) << 16)) +#define OFFSET_CONTEXT(offset) OFFSET_CACHE_CONTEXT(offset, (uint16_t)-1) +#define MEMBER_CACHE_CONTEXT(name, index) \ + OFFSET_CACHE_CONTEXT(offsetof(struct statx, stx_##name), index) +#define MEMBER_CONTEXT(name) MEMBER_CACHE_CONTEXT(name, (uint16_t)-1) + +#define G(attr, type, doc, context) \ + {attr, statx_result_get_##type, NULL, PyDoc_STR(doc), context} +#define GMC(attr, type, member, index, doc) \ + G(#attr, type##_cached, doc, MEMBER_CACHE_CONTEXT(member, index)) +#define GM(attr, type, member, doc) \ + G(#attr, type, doc, MEMBER_CONTEXT(member)) +#define GOC(attr, type, offset, index, doc) \ + G(#attr, type##_cached, doc, OFFSET_CACHE_CONTEXT(offset, index)) +#define GO(attr, type, offset, doc) \ + G(#attr, type, doc, OFFSET_CONTEXT(offset)) + static PyGetSetDef statx_result_getset[] = { - {"stx_mask", statx_result_get_u32_cached, NULL, "member validity mask", WHERE_NAME_CACHE(mask, 0)}, - {"stx_attributes", statx_result_get_u64_cached, NULL, "Linux inode attribute bits", WHERE_NAME_CACHE(attributes, 1)}, - {"st_uid", statx_result_get_uid_cached, NULL, "user ID of owner", WHERE_NAME_CACHE(uid, 2)}, - {"st_gid", statx_result_get_gid_cached, NULL, "group ID of owner", WHERE_NAME_CACHE(uid, 3)}, - {"st_mode", statx_result_get_u16_cached, NULL, "protection bits", WHERE_NAME_CACHE(mode, 4)}, - {"st_ino", statx_result_get_u64_cached, NULL, "inode", WHERE_NAME_CACHE(ino, 5)}, - {"st_size", statx_result_get_u64_cached, NULL, "total size, in bytes", WHERE_NAME_CACHE(size, 6)}, - {"stx_attributes_mask", statx_result_get_u64_cached, NULL, "Linux inode attribute bits supported for this file", WHERE_NAME_CACHE(attributes_mask, 7)}, - {"st_atime", statx_result_get_sec_cached, NULL, "time of last access", WHERE_NAME_CACHE(atime, 8)}, - {"st_atime_ns", statx_result_get_nsec_cached, NULL, "time of last access in nanoseconds", WHERE_NAME_CACHE(atime, 9)}, - {"st_birthtime", statx_result_get_sec_cached, NULL, "time of creation", WHERE_NAME_CACHE(btime, 10)}, - {"st_birthtime_ns", statx_result_get_nsec_cached, NULL, "time of creation in nanoseconds", WHERE_NAME_CACHE(btime, 11)}, - {"st_ctime", statx_result_get_sec_cached, NULL, "time of last change", WHERE_NAME_CACHE(ctime, 12)}, - {"st_ctime_ns", statx_result_get_nsec_cached, NULL, "time of last change in nanoseconds", WHERE_NAME_CACHE(ctime, 13)}, - {"st_mtime", statx_result_get_sec_cached, NULL, "time of last modification", WHERE_NAME_CACHE(mtime, 14)}, - {"st_mtime_ns", statx_result_get_nsec_cached, NULL, "time of last modification in nanoseconds", WHERE_NAME_CACHE(mtime, 15)}, - {"st_rdev", statx_result_get_dev, NULL, "device type (if inode device)", WHERE_NAME(rdev_major)}, - {"st_dev", statx_result_get_dev_cached, NULL, "device", WHERE_NAME_CACHE(dev_major, 16)}, + GMC(stx_mask, u32, mask, 0, "member validity mask"), + GMC(stx_attributes, u64, attributes, 1, "Linux inode attribute bits"), + GMC(st_uid, uid, uid, 2, "user ID of owner"), + GMC(st_gid, gid, gid, 3, "group ID of owner"), + GMC(st_mode, u16, mode, 4, "protection bits"), + GMC(st_ino, u64, ino, 5, "inode"), + GMC(st_size, u64, size, 6, "total size, in bytes"), + GMC(stx_attributes_mask, u64, attributes_mask, 7, + "Linux inode attribute bits supported for this file"), + GMC(st_atime, sec, atime, 8, "time of last access"), + GMC(st_atime_ns, nsec, atime, 9, "time of last access in nanoseconds"), + GMC(st_birthtime, sec, btime, 10, "time of creation"), + GMC(st_birthtime_ns, nsec, btime, 11, "time of creation in nanoseconds"), + GMC(st_ctime, sec, ctime, 12, "time of last change"), + GMC(st_ctime_ns, nsec, ctime, 13, "time of last change in nanoseconds"), + GMC(st_mtime, sec, mtime, 14, "time of last modification"), + GMC(st_mtime_ns, nsec, mtime, 15, + "time of last modification in nanoseconds"), + GM(st_rdev, dev, rdev_major, "device type (if inode device)"), + GMC(st_dev, dev, dev_major, 16, "device"), {NULL}, }; -#undef WHERE_OFFSET -#undef WHERE_NAME -#undef WHERE_OFFSET_CACHE -#undef WHERE_NAME_CACHE + +#undef GO +#undef GOC +#undef GM +#undef GMC +#undef G +#undef MEMBER_CONTEXT +#undef MEMBER_CACHE_CONTEXT +#undef OFFSET_CONTEXT +#undef OFFSET_CACHE_CONTEXT + +#define MO(attr, type, offset, doc) \ + {#attr, type, offsetof(statx_result, stx) + offset, Py_READONLY, PyDoc_STR(doc)} +#define MM(attr, type, member, doc) \ + MO(attr, type, offsetof(struct statx, stx_##member), doc) static PyMemberDef statx_result_members[] = { - {"st_blksize", Py_T_UINT, offsetof(statx_result, stx.stx_blksize), Py_READONLY, "blocksize for filesystem I/O"}, - {"st_nlink", Py_T_UINT, offsetof(statx_result, stx.stx_nlink), Py_READONLY, "number of hard links"}, - {"st_blocks", Py_T_ULONGLONG, offsetof(statx_result, stx.stx_blocks), Py_READONLY, "number of blocks allocated"}, - {"stx_mnt_id", Py_T_ULONGLONG, offsetof(statx_result, stx) + 144, Py_READONLY, "mount ID"}, - {"stx_dio_mem_align", Py_T_UINT, offsetof(statx_result, stx) + 152, Py_READONLY, "direct I/O memory buffer alignment"}, - {"stx_dio_offset_align", Py_T_UINT, offsetof(statx_result, stx) + 156, Py_READONLY, "direct I/O file offset alignment"}, - {"stx_subvol", Py_T_ULONGLONG, offsetof(statx_result, stx) + 160, Py_READONLY, "subvolume ID"}, - {"stx_atomic_write_unit_min", Py_T_UINT, offsetof(statx_result, stx) + 168, Py_READONLY, "minimum size for direct I/O with torn-write protection"}, - {"stx_atomic_write_unit_max", Py_T_UINT, offsetof(statx_result, stx) + 172, Py_READONLY, "maximum size for direct I/O with torn-write protection"}, - {"stx_atomic_write_segments_max", Py_T_UINT, offsetof(statx_result, stx) + 176, Py_READONLY, "maximum iovecs for direct I/O with torn-write protection"}, - {"stx_dio_read_offset_align", Py_T_UINT, offsetof(statx_result, stx) + 180, Py_READONLY, "direct I/O file offset alignment for reads"}, - {"stx_atomic_write_unit_max_opt", Py_T_UINT, offsetof(statx_result, stx) + 184, Py_READONLY, "maximum optimized size for direct I/O with torn-write protection"}, + MM(st_blksize, Py_T_UINT, blksize, "blocksize for filesystem I/O"), + MM(st_nlink, Py_T_UINT, nlink, "number of hard links"), + MM(st_blocks, Py_T_ULONGLONG, blocks, "number of blocks allocated"), + MO(stx_mnt_id, Py_T_ULONGLONG, 144, "mount ID"), + MO(stx_dio_mem_align, Py_T_UINT, 152, + "direct I/O memory buffer alignment"), + MO(stx_dio_offset_align, Py_T_UINT, 156, + "direct I/O file offset alignment"), + MO(stx_subvol, Py_T_ULONGLONG, 160, "subvolume ID"), + MO(stx_atomic_write_unit_min, Py_T_UINT, 168, + "minimum size for direct I/O with torn-write protection"), + MO(stx_atomic_write_unit_max, Py_T_UINT, 172, + "maximum size for direct I/O with torn-write protection"), + MO(stx_atomic_write_segments_max, Py_T_UINT, 176, + "maximum iovecs for direct I/O with torn-write protection"), + MO(stx_dio_read_offset_align, Py_T_UINT, 180, + "direct I/O file offset alignment for reads"), + MO(stx_atomic_write_unit_max_opt, Py_T_UINT, 184, + "maximum optimized size for direct I/O with torn-write protection"), {NULL}, }; +#undef MM +#undef MO + static int statx_result_traverse(PyObject *self, visitproc visit, void *arg) { Py_VISIT(Py_TYPE(self)); @@ -18590,6 +18629,20 @@ posixmodule_exec(PyObject *m) #endif #ifdef HAVE_STATX +#ifndef NDEBUG + /* Verify the cache slot assignment. */ + size_t used_slots = 0; + for (int i = 0; statx_result_getset[i].name != NULL; ++i) { + uint16_t slot = (uintptr_t)statx_result_getset[i].closure >> 16; + if (slot != (uint16_t)-1) { + assert(slot < STATX_RESULT_CACHE_SLOTS); + assert((used_slots & (1 << slot)) == 0); + used_slots |= 1 << slot; + } + } + assert(used_slots == (1 << STATX_RESULT_CACHE_SLOTS) - 1); +#endif + /* We retract os.statx in three cases: - the weakly-linked statx wrapper function is not available (old libc) - the wrapper function fails with EINVAL on sync flags (glibc's From 5b36cf0e6d93126cb25c7689c9a2513d2a463a81 Mon Sep 17 00:00:00 2001 From: Jeffrey Bosboom Date: Sun, 14 Sep 2025 21:37:07 -0700 Subject: [PATCH 15/18] Document st_birthtime fallback behavior --- Doc/library/os.rst | 6 ++++-- Modules/posixmodule.c | 5 ++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index ecf696f277363e..efd6eada71265c 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -3225,7 +3225,8 @@ features: .. versionchanged:: 3.12 ``st_birthtime`` is now available on Windows. .. versionchanged:: next - ``st_birthtime`` is now available on Linux. + ``st_birthtime`` is now present on Linux. The value will be ``0.0`` + on kernel versions < 4.11 or if not supported by the filesystem. .. attribute:: st_birthtime_ns @@ -3235,7 +3236,8 @@ features: .. versionadded:: 3.12 .. versionchanged:: next - ``st_birthtime_ns`` is now available on Linux. + ``st_birthtime_ns`` is now present on Linux. The value will be ``0`` + on kernel versions < 4.11 or if not supported by the filesystem. .. note:: diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 26ac40e8525427..d073257ef5eed7 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -2816,7 +2816,10 @@ _pystat_fromstructstat(PyObject *module, STRUCT_STAT *st) #elif defined(HAVE_STATX) /* We were built with statx support, so stat_result.st_birthtime[_ns] exists, but we fell back to stat because statx isn't available at - runtime. User programs assume st_birthtime is not None. */ + runtime. structseq members are _Py_T_OBJECT (for which NULL means None, + not AttributeError), but user programs assume st_birthtime is not None. + When the statx syscall wrapper is available but the syscall itself is + not, we end up setting the birthtime to 0, so do that here too. */ SET_ITEM(ST_BIRTHTIME_IDX, PyFloat_FromDouble(0.0)); SET_ITEM(ST_BIRTHTIME_NS_IDX, _PyLong_GetZero()); #elif defined(MS_WINDOWS) From 6be85a302d22689f4cfc26891b06fc3f4d304853 Mon Sep 17 00:00:00 2001 From: Jeffrey Bosboom Date: Tue, 16 Sep 2025 22:06:38 -0700 Subject: [PATCH 16/18] Implement os.stat with statx only if the statx syscall works glibc doesn't remember that statx previously failed with ENOSYS, so on old kernels, glibc's emulation of statx via stat has the overhead of a failed syscall. Only call the statx wrapper under the same conditions that we make os.statx available. statx_works was always a global C variable, but now its scope is the file instead of just posix_do_stat. Whether statx works is a process-global property, so using a global C variable is appropriate. Previously access to statx_works was unsynchronized, following the pattern of dup3_works. Now we rely on posixmodule_exec happening-before any thread can call os.stat. --- Modules/posixmodule.c | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index d073257ef5eed7..3fb78345d7e26b 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -2817,9 +2817,8 @@ _pystat_fromstructstat(PyObject *module, STRUCT_STAT *st) /* We were built with statx support, so stat_result.st_birthtime[_ns] exists, but we fell back to stat because statx isn't available at runtime. structseq members are _Py_T_OBJECT (for which NULL means None, - not AttributeError), but user programs assume st_birthtime is not None. - When the statx syscall wrapper is available but the syscall itself is - not, we end up setting the birthtime to 0, so do that here too. */ + not AttributeError), but user programs assume st_birthtime, when + present, is not None. */ SET_ITEM(ST_BIRTHTIME_IDX, PyFloat_FromDouble(0.0)); SET_ITEM(ST_BIRTHTIME_NS_IDX, _PyLong_GetZero()); #elif defined(MS_WINDOWS) @@ -2916,6 +2915,10 @@ _pystat_fromstructstatx(PyObject *module, struct statx *st) /* POSIX methods */ +#ifdef HAVE_STATX +/* set to 1 in posixmodule_exec after a successful test call */ +static int statx_works = 0; +#endif static PyObject * posix_do_stat(PyObject *module, const char *function_name, path_t *path, @@ -2940,8 +2943,7 @@ posix_do_stat(PyObject *module, const char *function_name, path_t *path, #ifdef HAVE_STATX struct statx stx = {}; - static int statx_works = -1; - if (statx != NULL && statx_works != 0) { + if (statx_works != 0) { unsigned int mask = STATX_BASIC_STATS | STATX_BTIME; int flags = AT_NO_AUTOMOUNT; flags |= follow_symlinks ? 0 : AT_SYMLINK_NOFOLLOW; @@ -2957,12 +2959,7 @@ posix_do_stat(PyObject *module, const char *function_name, path_t *path, Py_END_ALLOW_THREADS if (result == -1) { - if (statx_works == -1) { - statx_works = (errno != ENOSYS); - } - if (statx_works) { - return path_error(path); - } + return path_error(path); } else { return _pystat_fromstructstatx(module, &stx); @@ -18646,12 +18643,15 @@ posixmodule_exec(PyObject *m) assert(used_slots == (1 << STATX_RESULT_CACHE_SLOTS) - 1); #endif - /* We retract os.statx in three cases: + /* statx is available unless: - the weakly-linked statx wrapper function is not available (old libc) + - the wrapper function fails with ENOSYS (libc built without fallback + running on an old kernel) - the wrapper function fails with EINVAL on sync flags (glibc's emulation of statx via stat fails in this way) - - the wrapper function fails with ENOSYS (libc built without fallback - running on an old kernel) */ + In the last case, it is safe to use statx to implement os.stat, but + because glibc unconditionally makes the statx syscall before falling + back to fstatat, doing so is measurably slower. */ struct statx stx; if (statx == NULL || (statx(-1, "/", AT_STATX_DONT_SYNC, 0, &stx) == -1 @@ -18665,6 +18665,8 @@ posixmodule_exec(PyObject *m) } } else { + statx_works = 1; + statx_result_spec.name = "os.statx_result"; state->StatxResultType = PyType_FromModuleAndSpec(m, &statx_result_spec, NULL); if (PyModule_AddObjectRef(m, "statx_result", state->StatxResultType) < 0) { From c32ee72bc6e705de61cfebf5d9c49e7e86df2f73 Mon Sep 17 00:00:00 2001 From: Jeffrey Bosboom Date: Tue, 16 Sep 2025 23:36:20 -0700 Subject: [PATCH 17/18] Test statx PathLike and dir_fd handling Also two minor fixes to statx tests in test_os.py. --- Lib/test/test_os.py | 8 ++++++-- Lib/test/test_posix.py | 30 +++++++++++++++++++----------- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index dc75e6096d2e95..c85af87d25e2cd 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -668,7 +668,7 @@ def trunc(x): return x result[getattr(stat, name)]) self.assertIn(attr, members) - time_attributes = 'st_atime st_mtime st_ctime'.split() + time_attributes = ['st_atime', 'st_mtime', 'st_ctime'] try: result.st_birthtime result.st_birthtime_ns @@ -744,7 +744,7 @@ def check_statx_attributes(self, fname): maximal_mask |= getattr(os, name) result = os.statx(self.fname, maximal_mask) - time_attributes = 'st_atime st_mtime st_ctime st_birthtime'.split() + time_attributes = ('st_atime', 'st_mtime', 'st_ctime', 'st_birthtime') self.check_timestamp_agreement(result, time_attributes) # Check that valid attributes match os.stat. @@ -808,6 +808,10 @@ def test_statx_attributes_bytes(self): self.skipTest("cannot encode %a for the filesystem" % self.fname) self.check_statx_attributes(fname) + @unittest.skipUnless(hasattr(os, 'statx'), 'test needs os.statx()') + def test_statx_attributes_pathlike(self): + self.check_statx_attributes(FakePath(self.fname)) + @unittest.skipUnless(hasattr(os, 'statx'), 'test needs os.statx()') def test_statx_sync(self): # Test sync= kwarg parsing. (We can't predict if or how the result diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py index 2af11888b17c1d..fc23a24466b791 100644 --- a/Lib/test/test_posix.py +++ b/Lib/test/test_posix.py @@ -1611,33 +1611,41 @@ def test_chown_dir_fd(self): with self.prepare_file() as (dir_fd, name, fullname): posix.chown(name, os.getuid(), os.getgid(), dir_fd=dir_fd) - @unittest.skipUnless(os.stat in os.supports_dir_fd, "test needs dir_fd support in os.stat()") - def test_stat_dir_fd(self): + def check_statlike_dir_fd(self, func): with self.prepare() as (dir_fd, name, fullname): with open(fullname, 'w') as outfile: outfile.write("testline\n") self.addCleanup(posix.unlink, fullname) - s1 = posix.stat(fullname) - s2 = posix.stat(name, dir_fd=dir_fd) - self.assertEqual(s1, s2) - s2 = posix.stat(fullname, dir_fd=None) - self.assertEqual(s1, s2) + s1 = func(fullname) + s2 = func(name, dir_fd=dir_fd) + self.assertEqual((s1.st_dev, s1.st_ino), (s2.st_dev, s2.st_ino)) + s2 = func(fullname, dir_fd=None) + self.assertEqual((s1.st_dev, s1.st_ino), (s2.st_dev, s2.st_ino)) self.assertRaisesRegex(TypeError, 'should be integer or None, not', - posix.stat, name, dir_fd=posix.getcwd()) + func, name, dir_fd=posix.getcwd()) self.assertRaisesRegex(TypeError, 'should be integer or None, not', - posix.stat, name, dir_fd=float(dir_fd)) + func, name, dir_fd=float(dir_fd)) self.assertRaises(OverflowError, - posix.stat, name, dir_fd=10**20) + func, name, dir_fd=10**20) for fd in False, True: with self.assertWarnsRegex(RuntimeWarning, 'bool is used as a file descriptor') as cm: with self.assertRaises(OSError): - posix.stat('nonexisting', dir_fd=fd) + func('nonexisting', dir_fd=fd) self.assertEqual(cm.filename, __file__) + @unittest.skipUnless(os.stat in os.supports_dir_fd, "test needs dir_fd support in os.stat()") + def test_stat_dir_fd(self): + self.check_statlike_dir_fd(posix.stat) + + @unittest.skipUnless(hasattr(posix, 'statx'), "test needs os.statx()") + def test_statx_dir_fd(self): + func = lambda path, **kwargs: posix.statx(path, os.STATX_INO, **kwargs) + self.check_statlike_dir_fd(func) + @unittest.skipUnless(os.utime in os.supports_dir_fd, "test needs dir_fd support in os.utime()") def test_utime_dir_fd(self): with self.prepare_file() as (dir_fd, name, fullname): From c84f783861ee8e0c8482971ac4b2ce16a2fb6e1d Mon Sep 17 00:00:00 2001 From: Jeffrey Bosboom Date: Tue, 16 Sep 2025 23:58:39 -0700 Subject: [PATCH 18/18] statx docs fixes --- Doc/library/os.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index efd6eada71265c..ecff10274d5115 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -3377,7 +3377,7 @@ features: .. function:: statx(path, mask, *, dir_fd=None, follow_symlinks=True, sync=None) - Get the status of a file or file descriptor by performing a :c:func:`statx` + Get the status of a file or file descriptor by performing a :c:func:`!statx` system call on the given path. *path* may be specified as either a string or bytes -- directly or indirectly through the :class:`PathLike` interface -- or as an open file descriptor. *mask* is a combination of the module-level @@ -3389,9 +3389,10 @@ features: The optional parameter *sync* controls the freshness of the returned information. ``sync=True`` requests that the kernel return up-to-date information, even when doing so is expensive (for example, requiring a - round-trip to the server for a file on a network filesystem). + round trip to the server for a file on a network filesystem). ``sync=False`` requests that the kernel return cached information if - available. + available. ``sync=None`` expresses no preference, in which case the kernel + will return information as fresh as :func:`~os.stat` does. This function supports :ref:`specifying a file descriptor `, :ref:`paths relative to directory descriptors `, and @@ -3407,7 +3408,7 @@ features: .. class:: statx_result Object whose attributes correspond roughly to the members of the - :c:struct:`statx` structure. It is used for the result of :func:`os.statx`. + :c:struct:`!statx` structure. It is used for the result of :func:`os.statx`. :class:`!statx_result` has all of the attributes of :class:`stat_result` available on Linux, but is not a subclass of :class:`stat_result` nor a tuple. :class:`!statx_result` has the following additional attributes: