From 55db458aa73e9f9b54a021202e829d4a17aebfad Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 20 Oct 2025 19:03:51 +0200 Subject: [PATCH 1/5] gh-83714: Use "stx_" prefix for all os.statx_result members --- Doc/library/os.rst | 2 +- Lib/test/test_os/test_os.py | 54 ++++++++++++++-------------- Lib/test/test_os/test_posix.py | 15 +++++--- Modules/posixmodule.c | 64 +++++++++++++++++----------------- 4 files changed, 71 insertions(+), 64 deletions(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 50f6886c89df9e..dbb9ec7761168e 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -3412,7 +3412,7 @@ features: Information about a file returned by :func:`os.statx`. :class:`!statx_result` has all the attributes that :class:`~stat_result` has - on Linux, making it :term:`duck-typing` compatible, but + on Linux, but using ``stx_`` prefix instead of ``st_``. :class:`!statx_result` is not a subclass of :class:`~stat_result` and cannot be used as a tuple. diff --git a/Lib/test/test_os/test_os.py b/Lib/test/test_os/test_os.py index 67599537736c8f..f8d1e660dfc0b4 100644 --- a/Lib/test/test_os/test_os.py +++ b/Lib/test/test_os/test_os.py @@ -750,7 +750,8 @@ def check_statx_attributes(self, filename): result = os.statx(filename, maximal_mask) basic_result = os.stat(filename) - time_attributes = ('st_atime', 'st_mtime', 'st_ctime', 'st_birthtime') + time_attributes = ('stx_atime', 'stx_mtime', 'stx_ctime', + 'stx_birthtime') # gh-83714: st_birthtime can be None on tmpfs even if STATX_BTIME mask # is used time_attributes = [name for name in time_attributes @@ -759,43 +760,44 @@ def check_statx_attributes(self, filename): # 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), + ('stx_mode', os.STATX_TYPE | os.STATX_MODE), + ('stx_nlink', os.STATX_NLINK), + ('stx_uid', os.STATX_UID), + ('stx_gid', os.STATX_GID), + ('stx_atime', os.STATX_ATIME), + ('stx_atime_ns', os.STATX_ATIME), + ('stx_mtime', os.STATX_MTIME), + ('stx_mtime_ns', os.STATX_MTIME), + ('stx_ctime', os.STATX_CTIME), + ('stx_ctime_ns', os.STATX_CTIME), + ('stx_ino', os.STATX_INO), + ('stx_size', os.STATX_SIZE), + ('stx_blocks', os.STATX_BLOCKS), + ('stx_birthtime', os.STATX_BTIME), + ('stx_birthtime_ns', os.STATX_BTIME), # unconditionally valid members - ('st_blksize', 0), - ('st_rdev', 0), - ('st_dev', 0), + ('stx_blksize', 0), + ('stx_rdev', 0), + ('stx_dev', 0), ) for name, bits in requirements: - if result.stx_mask & bits == bits and hasattr(basic_result, name): + st_name = "st_" + name[4:] + if result.stx_mask & bits == bits and hasattr(basic_result, st_name): x = getattr(result, name) - b = getattr(basic_result, name) + b = getattr(basic_result, st_name) self.assertEqual(type(x), type(b)) if isinstance(x, float): self.assertAlmostEqual(x, b, msg=name) else: self.assertEqual(x, b, msg=name) - self.assertEqual(result.stx_rdev_major, os.major(result.st_rdev)) - self.assertEqual(result.stx_rdev_minor, os.minor(result.st_rdev)) - self.assertEqual(result.stx_dev_major, os.major(result.st_dev)) - self.assertEqual(result.stx_dev_minor, os.minor(result.st_dev)) + self.assertEqual(result.stx_rdev_major, os.major(result.stx_rdev)) + self.assertEqual(result.stx_rdev_minor, os.minor(result.stx_rdev)) + self.assertEqual(result.stx_dev_major, os.major(result.stx_dev)) + self.assertEqual(result.stx_dev_minor, os.minor(result.stx_dev)) members = [name for name in dir(result) - if name.startswith('st_') or name.startswith('stx_')] + if name.startswith('stx_')] for name in members: try: setattr(result, name, 1) diff --git a/Lib/test/test_os/test_posix.py b/Lib/test/test_os/test_posix.py index 905f0201253951..de24719a1ca455 100644 --- a/Lib/test/test_os/test_posix.py +++ b/Lib/test/test_os/test_posix.py @@ -1672,17 +1672,22 @@ 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) - def check_statlike_dir_fd(self, func): + def check_statlike_dir_fd(self, func, prefix): with self.prepare() as (dir_fd, name, fullname): with open(fullname, 'w') as outfile: outfile.write("testline\n") self.addCleanup(posix.unlink, fullname) + def get(result, attr): + return getattr(result, prefix + attr) + s1 = func(fullname) s2 = func(name, dir_fd=dir_fd) - self.assertEqual((s1.st_dev, s1.st_ino), (s2.st_dev, s2.st_ino)) + self.assertEqual((get(s1, "dev"), get(s1, "ino")), + (get(s2, "dev"), get(s2, "ino"))) s2 = func(fullname, dir_fd=None) - self.assertEqual((s1.st_dev, s1.st_ino), (s2.st_dev, s2.st_ino)) + self.assertEqual((get(s1, "dev"), get(s1, "ino")), + (get(s2, "dev"), get(s2, "ino"))) self.assertRaisesRegex(TypeError, 'should be integer or None, not', func, name, dir_fd=posix.getcwd()) @@ -1700,13 +1705,13 @@ def check_statlike_dir_fd(self, func): @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) + self.check_statlike_dir_fd(posix.stat, prefix="st_") @unittest.skipUnless(hasattr(posix, 'statx'), "test needs os.statx()") def test_statx_dir_fd(self): def func(path, **kwargs): return posix.statx(path, os.STATX_INO, **kwargs) - self.check_statlike_dir_fd(func) + self.check_statlike_dir_fd(func, prefix="stx_") @unittest.skipUnless(os.utime in os.supports_dir_fd, "test needs dir_fd support in os.utime()") def test_utime_dir_fd(self): diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index d0b9eed8c8208b..7bc3eb6189ca67 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -3330,17 +3330,17 @@ typedef struct { static PyMemberDef pystatx_result_members[] = { MM(stx_mask, Py_T_UINT, mask, "member validity mask"), - MM(st_blksize, Py_T_UINT, blksize, "blocksize for filesystem I/O"), + MM(stx_blksize, Py_T_UINT, blksize, "blocksize for filesystem I/O"), MM(stx_attributes, Py_T_ULONGLONG, attributes, "Linux inode attribute bits"), - MM(st_mode, Py_T_USHORT, mode, "protection bits"), + MM(stx_mode, Py_T_USHORT, mode, "protection bits"), MM(stx_attributes_mask, Py_T_ULONGLONG, attributes_mask, "Mask of supported bits in stx_attributes"), MM(stx_rdev_major, Py_T_UINT, rdev_major, "represented device major number"), MM(stx_rdev_minor, Py_T_UINT, rdev_minor, "represented device minor number"), - MX(st_rdev, Py_T_ULONGLONG, rdev, "device type (if inode device)"), + MX(stx_rdev, Py_T_ULONGLONG, rdev, "device type (if inode device)"), MM(stx_dev_major, Py_T_UINT, dev_major, "containing device major number"), MM(stx_dev_minor, Py_T_UINT, dev_minor, "containing device minor number"), - MX(st_dev, Py_T_ULONGLONG, dev, "device"), + MX(stx_dev, Py_T_ULONGLONG, dev, "device"), {NULL}, }; @@ -3361,9 +3361,9 @@ static PyMemberDef pystatx_result_members[] = { return PyLong_FromUnsignedLong(value); \ } -STATX_GET_UINT(st_uid, stx_uid, STATX_UID) -STATX_GET_UINT(st_gid, stx_gid, STATX_GID) -STATX_GET_UINT(st_nlink, stx_nlink, STATX_NLINK) +STATX_GET_UINT(stx_uid, stx_uid, STATX_UID) +STATX_GET_UINT(stx_gid, stx_gid, STATX_GID) +STATX_GET_UINT(stx_nlink, stx_nlink, STATX_NLINK) #ifdef HAVE_STRUCT_STATX_STX_DIO_MEM_ALIGN STATX_GET_UINT(stx_dio_mem_align, stx_dio_mem_align, STATX_DIOALIGN) STATX_GET_UINT(stx_dio_offset_align, stx_dio_offset_align, STATX_DIOALIGN) @@ -3398,9 +3398,9 @@ STATX_GET_UINT(stx_atomic_write_unit_max_opt, stx_atomic_write_unit_max_opt, return PyLong_FromUnsignedLongLong(value); \ } -STATX_GET_ULONGLONG(st_blocks, stx_blocks, STATX_BLOCKS) -STATX_GET_ULONGLONG(st_ino, stx_ino, STATX_INO) -STATX_GET_ULONGLONG(st_size, stx_size, STATX_SIZE) +STATX_GET_ULONGLONG(stx_blocks, stx_blocks, STATX_BLOCKS) +STATX_GET_ULONGLONG(stx_ino, stx_ino, STATX_INO) +STATX_GET_ULONGLONG(stx_size, stx_size, STATX_SIZE) #ifdef HAVE_STRUCT_STATX_STX_MNT_ID STATX_GET_ULONGLONG(stx_mnt_id, stx_mnt_id, STATX_MNT_ID) #endif @@ -3421,10 +3421,10 @@ STATX_GET_ULONGLONG(stx_subvol, stx_subvol, STATX_SUBVOL) return PyFloat_FromDouble(sec); \ } -STATX_GET_DOUBLE(st_atime, atime_sec, STATX_ATIME) -STATX_GET_DOUBLE(st_birthtime, btime_sec, STATX_BTIME) -STATX_GET_DOUBLE(st_ctime, ctime_sec, STATX_CTIME) -STATX_GET_DOUBLE(st_mtime, mtime_sec, STATX_MTIME) +STATX_GET_DOUBLE(stx_atime, atime_sec, STATX_ATIME) +STATX_GET_DOUBLE(stx_birthtime, btime_sec, STATX_BTIME) +STATX_GET_DOUBLE(stx_ctime, ctime_sec, STATX_CTIME) +STATX_GET_DOUBLE(stx_mtime, mtime_sec, STATX_MTIME) #define STATX_GET_NSEC(ATTR, MEMBER, MASK) \ static PyObject* \ @@ -3440,29 +3440,29 @@ STATX_GET_DOUBLE(st_mtime, mtime_sec, STATX_MTIME) return stat_nanosecond_timestamp(state, ts->tv_sec, ts->tv_nsec); \ } -STATX_GET_NSEC(st_atime_ns, stx_atime, STATX_ATIME) -STATX_GET_NSEC(st_birthtime_ns, stx_btime, STATX_BTIME) -STATX_GET_NSEC(st_ctime_ns, stx_ctime, STATX_CTIME) -STATX_GET_NSEC(st_mtime_ns, stx_mtime, STATX_MTIME) +STATX_GET_NSEC(stx_atime_ns, stx_atime, STATX_ATIME) +STATX_GET_NSEC(stx_birthtime_ns, stx_btime, STATX_BTIME) +STATX_GET_NSEC(stx_ctime_ns, stx_ctime, STATX_CTIME) +STATX_GET_NSEC(stx_mtime_ns, stx_mtime, STATX_MTIME) #define G(attr, doc) \ {#attr, pystatx_result_get_##attr, NULL, PyDoc_STR(doc), NULL} static PyGetSetDef pystatx_result_getset[] = { - G(st_nlink, "number of hard links"), - G(st_uid, "user ID of owner"), - G(st_gid, "group ID of owner"), - G(st_ino, "inode"), - G(st_size, "total size, in bytes"), - G(st_blocks, "number of blocks allocated"), - G(st_atime, "time of last access"), - G(st_atime_ns, "time of last access in nanoseconds"), - G(st_birthtime, "time of creation"), - G(st_birthtime_ns, "time of creation in nanoseconds"), - G(st_ctime, "time of last change"), - G(st_ctime_ns, "time of last change in nanoseconds"), - G(st_mtime, "time of last modification"), - G(st_mtime_ns, "time of last modification in nanoseconds"), + G(stx_nlink, "number of hard links"), + G(stx_uid, "user ID of owner"), + G(stx_gid, "group ID of owner"), + G(stx_ino, "inode"), + G(stx_size, "total size, in bytes"), + G(stx_blocks, "number of blocks allocated"), + G(stx_atime, "time of last access"), + G(stx_atime_ns, "time of last access in nanoseconds"), + G(stx_birthtime, "time of creation"), + G(stx_birthtime_ns, "time of creation in nanoseconds"), + G(stx_ctime, "time of last change"), + G(stx_ctime_ns, "time of last change in nanoseconds"), + G(stx_mtime, "time of last modification"), + G(stx_mtime_ns, "time of last modification in nanoseconds"), #ifdef HAVE_STRUCT_STATX_STX_MNT_ID G(stx_mnt_id, "mount ID"), #endif From 1f0a688c7b21eeb825b9c5c4d513f0b97191ca07 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 21 Oct 2025 19:26:46 +0200 Subject: [PATCH 2/5] Document all statx_result members Rename stx_birthtime to stx_btime, and rename stx_birthtime_ns to stx_btime_ns. --- Doc/library/os.rst | 216 ++++++++++++++++++++++++++---------- Lib/test/test_os/test_os.py | 5 +- Modules/posixmodule.c | 8 +- 3 files changed, 165 insertions(+), 64 deletions(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index dbb9ec7761168e..6bc53f43dd1173 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -3416,128 +3416,230 @@ features: :class:`!statx_result` is not a subclass of :class:`~stat_result` and cannot be used as a tuple. - :class:`!statx_result` has the following additional attributes: + :class:`!statx_result` has the following attributes: .. attribute:: stx_mask Bitmask of :const:`STATX_* ` constants specifying the information retrieved, which may differ from what was requested. - .. attribute:: stx_attributes_mask + .. attribute:: stx_atime - Bitmask of :const:`STATX_ATTR_* ` constants - specifying the attributes bits supported for this file. + Time of most recent access expressed in seconds. + + Equal to ``None`` if :data:`STATX_ATIME` is missing from + :attr:`~statx_result.stx_mask`. + + .. attribute:: stx_atime_ns + + Time of most recent access expressed in nanoseconds as an integer. + + Equal to ``None`` if :data:`STATX_ATIME` is missing from + :attr:`~statx_result.stx_mask`. + + .. attribute:: stx_atomic_write_segments_max + + Maximum iovecs for direct I/O with torn-write protection. + + Equal to ``None`` if :data:`STATX_WRITE_ATOMIC` is missing from + :attr:`~statx_result.stx_mask`. + + .. availability:: Linux >= 4.11 with glibc >= 2.28 and build-time kernel + userspace API headers >= 6.11. + + .. attribute:: stx_atomic_write_unit_min + + Minimum size for direct I/O with torn-write protection. + + Equal to ``None`` if :data:`STATX_WRITE_ATOMIC` is missing from + :attr:`~statx_result.stx_mask`. + + .. availability:: Linux >= 4.11 with glibc >= 2.28 and build-time kernel + userspace API headers >= 6.11. + + .. attribute:: stx_atomic_write_unit_max + + Maximum size for direct I/O with torn-write protection. + + Equal to ``None`` if :data:`STATX_WRITE_ATOMIC` is missing from + :attr:`~statx_result.stx_mask`. + + .. availability:: Linux >= 4.11 with glibc >= 2.28 and build-time kernel + userspace API headers >= 6.11. + + .. attribute:: stx_atomic_write_unit_max_opt + + Maximum optimized size for direct I/O with torn-write protection. + + Equal to ``None`` if :data:`STATX_WRITE_ATOMIC` is missing from + :attr:`~statx_result.stx_mask`. + + .. availability:: Linux >= 4.11 with glibc >= 2.28 and build-time kernel + userspace API headers >= 6.16. .. attribute:: stx_attributes Bitmask of :const:`STATX_ATTR_* ` constants specifying the attributes of this file. - .. attribute:: stx_dev_major + .. attribute:: stx_attributes_mask - Major number of the device on which this file resides. + A mask indicating which bits in :attr:`stx_attributes` are supported by + the VFS and the filesystem. - .. attribute:: stx_dev_minor + .. attribute:: stx_blksize - Minor number of the device on which this file resides. + "Preferred" blocksize for efficient file system I/O. Writing to a file in + smaller chunks may cause an inefficient read-modify-rewrite. - .. attribute:: stx_rdev_major + .. attribute:: stx_blocks - Major number of the device this file represents. + Number of 512-byte blocks allocated for file. + This may be smaller than :attr:`stx_size`/512 when the file has holes. - .. attribute:: stx_rdev_minor + .. attribute:: stx_btime - Minor number of the device this file represents. + Time of file creation expressed in seconds. - .. attribute:: stx_mnt_id + Equal to ``None`` if :data:`STATX_BTIME` is missing from + :attr:`~statx_result.stx_mask`. - Mount identifier. + .. attribute:: stx_btime_ns - Equal to ``None`` if :data:`STATX_MNT_ID` is missing from - :attr:`~os.statx_result.stx_mask`. + Time of file creation expressed in nanoseconds as an integer. - .. availability:: Linux >= 4.11 with glibc >= 2.28 and build-time kernel - userspace API headers >= 5.8. + Equal to ``None`` if :data:`STATX_BTIME` is missing from + :attr:`~statx_result.stx_mask`. - .. attribute:: stx_dio_mem_align + .. attribute:: stx_ctime - Direct I/O memory buffer alignment requirement. + Time of most recent metadata change expressed in seconds. - Equal to ``None`` if :data:`STATX_DIOALIGN` is missing from - :attr:`~os.statx_result.stx_mask`. + Equal to ``None`` if :data:`STATX_CTIME` is missing from + :attr:`~statx_result.stx_mask`. - .. availability:: Linux >= 4.11 with glibc >= 2.28 and build-time kernel - userspace API headers >= 6.1. + .. attribute:: stx_ctime_ns + + Time of most recent metadata change expressed in nanoseconds as an + integer. + + Equal to ``None`` if :data:`STATX_CTIME` is missing from + :attr:`~statx_result.stx_mask`. + + .. attribute:: stx_dev + + Identifier of the device on which this file resides. + + .. attribute:: stx_dev_major + + Major number of the device on which this file resides. + + .. attribute:: stx_dev_minor + + Minor number of the device on which this file resides. .. attribute:: stx_dio_offset_align Direct I/O file offset alignment requirement. Equal to ``None`` if :data:`STATX_DIOALIGN` is missing from - :attr:`~os.statx_result.stx_mask`. + :attr:`~statx_result.stx_mask`. .. availability:: Linux >= 4.11 with glibc >= 2.28 and build-time kernel userspace API headers >= 6.1. - .. attribute:: stx_subvol + .. attribute:: stx_dio_mem_align - Subvolume identifier. + Direct I/O memory buffer alignment requirement. - Equal to ``None`` if :data:`STATX_SUBVOL` is missing from - :attr:`~os.statx_result.stx_mask`. + Equal to ``None`` if :data:`STATX_DIOALIGN` is missing from + :attr:`~statx_result.stx_mask`. .. availability:: Linux >= 4.11 with glibc >= 2.28 and build-time kernel - userspace API headers >= 6.10. + userspace API headers >= 6.1. - .. attribute:: stx_atomic_write_unit_min + .. attribute:: stx_dio_read_offset_align - Minimum size for direct I/O with torn-write protection. + Direct I/O file offset alignment requirement for reads. - Equal to ``None`` if :data:`STATX_WRITE_ATOMIC` is missing from - :attr:`~os.statx_result.stx_mask`. + Equal to ``None`` if :data:`STATX_DIO_READ_ALIGN` is missing from + :attr:`~statx_result.stx_mask`. .. availability:: Linux >= 4.11 with glibc >= 2.28 and build-time kernel - userspace API headers >= 6.11. + userspace API headers >= 6.14. - .. attribute:: stx_atomic_write_unit_max + .. attribute:: stx_gid - Maximum size for direct I/O with torn-write protection. + Group identifier of the file owner. - Equal to ``None`` if :data:`STATX_WRITE_ATOMIC` is missing from - :attr:`~os.statx_result.stx_mask`. + .. attribute:: stx_ino - .. availability:: Linux >= 4.11 with glibc >= 2.28 and build-time kernel - userspace API headers >= 6.11. + The inode number. - .. attribute:: stx_atomic_write_unit_max_opt + .. attribute:: stx_mnt_id - Maximum optimized size for direct I/O with torn-write protection. + Mount identifier. - Equal to ``None`` if :data:`STATX_WRITE_ATOMIC` is missing from - :attr:`~os.statx_result.stx_mask`. + Equal to ``None`` if :data:`STATX_MNT_ID` is missing from + :attr:`~statx_result.stx_mask`. .. availability:: Linux >= 4.11 with glibc >= 2.28 and build-time kernel - userspace API headers >= 6.16. + userspace API headers >= 5.8. - .. attribute:: stx_atomic_write_segments_max + .. attribute:: stx_mode - Maximum iovecs for direct I/O with torn-write protection. + File mode: file type and file mode bits (permissions). - Equal to ``None`` if :data:`STATX_WRITE_ATOMIC` is missing from - :attr:`~os.statx_result.stx_mask`. + .. attribute:: stx_mtime - .. availability:: Linux >= 4.11 with glibc >= 2.28 and build-time kernel - userspace API headers >= 6.11. + Time of most recent content modification expressed in seconds. - .. attribute:: stx_dio_read_offset_align + Equal to ``None`` if :data:`STATX_MTIME` is missing from + :attr:`~statx_result.stx_mask`. - Direct I/O file offset alignment requirement for reads. + .. attribute:: stx_mtime_ns - Equal to ``None`` if :data:`STATX_DIO_READ_ALIGN` is missing from - :attr:`~os.statx_result.stx_mask`. + Time of most recent content modification expressed in nanoseconds as an + integer. + + Equal to ``None`` if :data:`STATX_MTIME` is missing from + :attr:`~statx_result.stx_mask`. + + .. attribute:: stx_nlink + + Number of hard links. + + .. attribute:: stx_rdev + + Type of device if an inode device. + + .. attribute:: stx_rdev_major + + Major number of the device this file represents. + + .. attribute:: stx_rdev_minor + + Minor number of the device this file represents. + + .. attribute:: stx_size + + Size of the file in bytes, if it is a regular file or a symbolic link. + The size of a symbolic link is the length of the pathname it contains, + without a terminating null byte. + + .. attribute:: stx_subvol + + Subvolume identifier. + + Equal to ``None`` if :data:`STATX_SUBVOL` is missing from + :attr:`~statx_result.stx_mask`. .. availability:: Linux >= 4.11 with glibc >= 2.28 and build-time kernel - userspace API headers >= 6.14. + userspace API headers >= 6.10. + + .. attribute:: stx_uid + + User identifier of the file owner. .. seealso:: The :manpage:`statx(2)` man page. diff --git a/Lib/test/test_os/test_os.py b/Lib/test/test_os/test_os.py index f8d1e660dfc0b4..9a40c5c2a1f1f3 100644 --- a/Lib/test/test_os/test_os.py +++ b/Lib/test/test_os/test_os.py @@ -750,9 +750,8 @@ def check_statx_attributes(self, filename): result = os.statx(filename, maximal_mask) basic_result = os.stat(filename) - time_attributes = ('stx_atime', 'stx_mtime', 'stx_ctime', - 'stx_birthtime') - # gh-83714: st_birthtime can be None on tmpfs even if STATX_BTIME mask + time_attributes = ('stx_atime', 'stx_btime', 'stx_ctime', 'stx_mtime') + # gh-83714: stx_btime can be None on tmpfs even if STATX_BTIME mask # is used time_attributes = [name for name in time_attributes if getattr(result, name) is not None] diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 7bc3eb6189ca67..bfc86f755eab22 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -3422,7 +3422,7 @@ STATX_GET_ULONGLONG(stx_subvol, stx_subvol, STATX_SUBVOL) } STATX_GET_DOUBLE(stx_atime, atime_sec, STATX_ATIME) -STATX_GET_DOUBLE(stx_birthtime, btime_sec, STATX_BTIME) +STATX_GET_DOUBLE(stx_btime, btime_sec, STATX_BTIME) STATX_GET_DOUBLE(stx_ctime, ctime_sec, STATX_CTIME) STATX_GET_DOUBLE(stx_mtime, mtime_sec, STATX_MTIME) @@ -3441,7 +3441,7 @@ STATX_GET_DOUBLE(stx_mtime, mtime_sec, STATX_MTIME) } STATX_GET_NSEC(stx_atime_ns, stx_atime, STATX_ATIME) -STATX_GET_NSEC(stx_birthtime_ns, stx_btime, STATX_BTIME) +STATX_GET_NSEC(stx_btime_ns, stx_btime, STATX_BTIME) STATX_GET_NSEC(stx_ctime_ns, stx_ctime, STATX_CTIME) STATX_GET_NSEC(stx_mtime_ns, stx_mtime, STATX_MTIME) @@ -3457,8 +3457,8 @@ static PyGetSetDef pystatx_result_getset[] = { G(stx_blocks, "number of blocks allocated"), G(stx_atime, "time of last access"), G(stx_atime_ns, "time of last access in nanoseconds"), - G(stx_birthtime, "time of creation"), - G(stx_birthtime_ns, "time of creation in nanoseconds"), + G(stx_btime, "time of creation"), + G(stx_btime_ns, "time of creation in nanoseconds"), G(stx_ctime, "time of last change"), G(stx_ctime_ns, "time of last change in nanoseconds"), G(stx_mtime, "time of last modification"), From 12c49f7a737c4f6daaba74f2b54c60b3f599e2b1 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 21 Oct 2025 19:54:05 +0200 Subject: [PATCH 3/5] Simplify posixmodule.c macros Remove now redundant MEMBER parameter. --- Doc/library/os.rst | 20 +++++++++++++++++++- Modules/posixmodule.c | 43 +++++++++++++++++++------------------------ 2 files changed, 38 insertions(+), 25 deletions(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 6bc53f43dd1173..21aae4fb00f4cb 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -3497,6 +3497,9 @@ features: Number of 512-byte blocks allocated for file. This may be smaller than :attr:`stx_size`/512 when the file has holes. + Equal to ``None`` if :data:`STATX_BLOCKS` is missing from + :attr:`~statx_result.stx_mask`. + .. attribute:: stx_btime Time of file creation expressed in seconds. @@ -3572,9 +3575,15 @@ features: Group identifier of the file owner. + Equal to ``None`` if :data:`STATX_GID` is missing from + :attr:`~statx_result.stx_mask`. + .. attribute:: stx_ino - The inode number. + Inode number. + + Equal to ``None`` if :data:`STATX_INO` is missing from + :attr:`~statx_result.stx_mask`. .. attribute:: stx_mnt_id @@ -3609,6 +3618,9 @@ features: Number of hard links. + Equal to ``None`` if :data:`STATX_NLINK` is missing from + :attr:`~statx_result.stx_mask`. + .. attribute:: stx_rdev Type of device if an inode device. @@ -3627,6 +3639,9 @@ features: The size of a symbolic link is the length of the pathname it contains, without a terminating null byte. + Equal to ``None`` if :data:`STATX_SIZE` is missing from + :attr:`~statx_result.stx_mask`. + .. attribute:: stx_subvol Subvolume identifier. @@ -3641,6 +3656,9 @@ features: User identifier of the file owner. + Equal to ``None`` if :data:`STATX_UID` is missing from + :attr:`~statx_result.stx_mask`. + .. seealso:: The :manpage:`statx(2)` man page. .. availability:: Linux >= 4.11 with glibc >= 2.28. diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index bfc86f755eab22..433acdcb538f11 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -3349,7 +3349,7 @@ static PyMemberDef pystatx_result_members[] = { #undef M -#define STATX_GET_UINT(ATTR, MEMBER, MASK) \ +#define STATX_GET_UINT(ATTR, MASK) \ static PyObject* \ pystatx_result_get_##ATTR(PyObject *op, void *Py_UNUSED(context)) \ { \ @@ -3357,36 +3357,31 @@ static PyMemberDef pystatx_result_members[] = { if (!(self->stx.stx_mask & MASK)) { \ Py_RETURN_NONE; \ } \ - unsigned long value = self->stx.MEMBER; \ + unsigned long value = self->stx.ATTR; \ return PyLong_FromUnsignedLong(value); \ } -STATX_GET_UINT(stx_uid, stx_uid, STATX_UID) -STATX_GET_UINT(stx_gid, stx_gid, STATX_GID) -STATX_GET_UINT(stx_nlink, stx_nlink, STATX_NLINK) +STATX_GET_UINT(stx_uid, STATX_UID) +STATX_GET_UINT(stx_gid, STATX_GID) +STATX_GET_UINT(stx_nlink, STATX_NLINK) #ifdef HAVE_STRUCT_STATX_STX_DIO_MEM_ALIGN -STATX_GET_UINT(stx_dio_mem_align, stx_dio_mem_align, STATX_DIOALIGN) -STATX_GET_UINT(stx_dio_offset_align, stx_dio_offset_align, STATX_DIOALIGN) +STATX_GET_UINT(stx_dio_mem_align, STATX_DIOALIGN) +STATX_GET_UINT(stx_dio_offset_align, STATX_DIOALIGN) #endif #ifdef HAVE_STRUCT_STATX_STX_DIO_READ_OFFSET_ALIGN -STATX_GET_UINT(stx_dio_read_offset_align, stx_dio_read_offset_align, - STATX_DIO_READ_ALIGN) +STATX_GET_UINT(stx_dio_read_offset_align, STATX_DIO_READ_ALIGN) #endif #ifdef HAVE_STRUCT_STATX_STX_ATOMIC_WRITE_UNIT_MIN -STATX_GET_UINT(stx_atomic_write_unit_min, stx_atomic_write_unit_min, - STATX_WRITE_ATOMIC) -STATX_GET_UINT(stx_atomic_write_unit_max, stx_atomic_write_unit_max, - STATX_WRITE_ATOMIC) -STATX_GET_UINT(stx_atomic_write_segments_max, stx_atomic_write_segments_max, - STATX_WRITE_ATOMIC) +STATX_GET_UINT(stx_atomic_write_unit_min, STATX_WRITE_ATOMIC) +STATX_GET_UINT(stx_atomic_write_unit_max, STATX_WRITE_ATOMIC) +STATX_GET_UINT(stx_atomic_write_segments_max, STATX_WRITE_ATOMIC) #endif #ifdef HAVE_STRUCT_STATX_STX_ATOMIC_WRITE_UNIT_MAX_OPT -STATX_GET_UINT(stx_atomic_write_unit_max_opt, stx_atomic_write_unit_max_opt, - STATX_WRITE_ATOMIC) +STATX_GET_UINT(stx_atomic_write_unit_max_opt, STATX_WRITE_ATOMIC) #endif -#define STATX_GET_ULONGLONG(ATTR, MEMBER, MASK) \ +#define STATX_GET_ULONGLONG(ATTR, MASK) \ static PyObject* \ pystatx_result_get_##ATTR(PyObject *op, void *Py_UNUSED(context)) \ { \ @@ -3394,18 +3389,18 @@ STATX_GET_UINT(stx_atomic_write_unit_max_opt, stx_atomic_write_unit_max_opt, if (!(self->stx.stx_mask & MASK)) { \ Py_RETURN_NONE; \ } \ - unsigned long long value = self->stx.MEMBER; \ + unsigned long long value = self->stx.ATTR; \ return PyLong_FromUnsignedLongLong(value); \ } -STATX_GET_ULONGLONG(stx_blocks, stx_blocks, STATX_BLOCKS) -STATX_GET_ULONGLONG(stx_ino, stx_ino, STATX_INO) -STATX_GET_ULONGLONG(stx_size, stx_size, STATX_SIZE) +STATX_GET_ULONGLONG(stx_blocks, STATX_BLOCKS) +STATX_GET_ULONGLONG(stx_ino, STATX_INO) +STATX_GET_ULONGLONG(stx_size, STATX_SIZE) #ifdef HAVE_STRUCT_STATX_STX_MNT_ID -STATX_GET_ULONGLONG(stx_mnt_id, stx_mnt_id, STATX_MNT_ID) +STATX_GET_ULONGLONG(stx_mnt_id, STATX_MNT_ID) #endif #ifdef HAVE_STRUCT_STATX_STX_SUBVOL -STATX_GET_ULONGLONG(stx_subvol, stx_subvol, STATX_SUBVOL) +STATX_GET_ULONGLONG(stx_subvol, STATX_SUBVOL) #endif From f4676e1ff7d162b01c8b232813030002c71682be Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 21 Oct 2025 21:43:48 +0200 Subject: [PATCH 4/5] Remove comparison to stat_result --- Doc/library/os.rst | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 21aae4fb00f4cb..8f7b9ac15a0d22 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -3411,11 +3411,6 @@ features: Information about a file returned by :func:`os.statx`. - :class:`!statx_result` has all the attributes that :class:`~stat_result` has - on Linux, but using ``stx_`` prefix instead of ``st_``. - :class:`!statx_result` is not a subclass of :class:`~stat_result` and cannot - be used as a tuple. - :class:`!statx_result` has the following attributes: .. attribute:: stx_mask From 895326e69152bdad7ef4c117b4a10cc915c3ce90 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 22 Oct 2025 10:32:53 +0200 Subject: [PATCH 5/5] Credit myself in os.statx() doc --- Doc/whatsnew/3.15.rst | 2 +- .../Library/2025-09-18-21-25-41.gh-issue-83714.TQjDWZ.rst | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 8503a4c7f973fc..22e9cfde06f877 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -438,7 +438,7 @@ os * Add :func:`os.statx` on Linux kernel versions 4.11 and later with glibc versions 2.28 and later. - (Contributed by Jeffrey Bosboom in :gh:`83714`.) + (Contributed by Jeffrey Bosboom and Victor Stinner in :gh:`83714`.) os.path diff --git a/Misc/NEWS.d/next/Library/2025-09-18-21-25-41.gh-issue-83714.TQjDWZ.rst b/Misc/NEWS.d/next/Library/2025-09-18-21-25-41.gh-issue-83714.TQjDWZ.rst index 7229a361147ee2..3653eb9a114a35 100644 --- a/Misc/NEWS.d/next/Library/2025-09-18-21-25-41.gh-issue-83714.TQjDWZ.rst +++ b/Misc/NEWS.d/next/Library/2025-09-18-21-25-41.gh-issue-83714.TQjDWZ.rst @@ -1,2 +1,2 @@ -Implement :func:`os.statx` on Linux kernel versions 4.11 and later with -glibc versions 2.28 and later. Contributed by Jeffrey Bosboom. +Implement :func:`os.statx` on Linux kernel versions 4.11 and later with glibc +versions 2.28 and later. Contributed by Jeffrey Bosboom and Victor Stinner.