gh-83714: Implement os.statx() function (#139178)

Co-authored-by: Cody Maloney <cmaloney@users.noreply.github.com>
Co-authored-by: Victor Stinner <vstinner@python.org>
This commit is contained in:
Jeffrey Bosboom 2025-10-15 06:44:08 -07:00 committed by GitHub
parent 27acaf1cb7
commit fe9ac7fc8c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 1018 additions and 71 deletions

View file

@ -3383,6 +3383,214 @@ features:
Added the :attr:`st_birthtime` member on Windows. Added the :attr:`st_birthtime` member on Windows.
.. function:: statx(path, mask, *, flags=0, dir_fd=None, follow_symlinks=True)
Get the status of a file or file descriptor by performing a :c:func:`!statx`
system call on the given path.
*path* is a :term:`path-like object` or an open file descriptor. *mask* is a
combination of the module-level :const:`STATX_* <STATX_TYPE>` constants
specifying the information to retrieve. *flags* is a combination of the
module-level :const:`AT_STATX_* <AT_STATX_FORCE_SYNC>` constants and/or
:const:`AT_NO_AUTOMOUNT`. Returns a :class:`statx_result` object whose
:attr:`~os.statx_result.stx_mask` attribute specifies the information
actually retrieved (which may differ from *mask*).
This function supports :ref:`specifying a file descriptor <path_fd>`,
:ref:`paths relative to directory descriptors <dir_fd>`, and
:ref:`not following symlinks <follow_symlinks>`.
.. seealso:: The :manpage:`statx(2)` man page.
.. availability:: Linux >= 4.11 with glibc >= 2.28.
.. versionadded:: next
.. class:: statx_result
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
: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:
.. attribute:: stx_mask
Bitmask of :const:`STATX_* <STATX_TYPE>` constants specifying the
information retrieved, which may differ from what was requested.
.. attribute:: stx_attributes_mask
Bitmask of :const:`STATX_ATTR_* <stat.STATX_ATTR_COMPRESSED>` constants
specifying the attributes bits supported for this file.
.. attribute:: stx_attributes
Bitmask of :const:`STATX_ATTR_* <stat.STATX_ATTR_COMPRESSED>` constants
specifying the attributes of this file.
.. 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_rdev_major
Major number of the device this file represents.
.. attribute:: stx_rdev_minor
Minor number of the device this file represents.
.. attribute:: stx_mnt_id
Mount ID.
.. availability:: Linux >= 4.11 with glibc >= 2.28 and build-time kernel
userspace API headers >= 5.8.
.. attribute:: stx_dio_mem_align
Direct I/O memory buffer alignment requirement.
.. availability:: Linux >= 4.11 with glibc >= 2.28 and build-time kernel
userspace API headers >= 6.1.
.. attribute:: stx_dio_offset_align
Direct I/O file offset alignment requirement.
.. availability:: Linux >= 4.11 with glibc >= 2.28 and build-time kernel
userspace API headers >= 6.1.
.. attribute:: stx_subvol
Subvolume ID.
.. availability:: Linux >= 4.11 with glibc >= 2.28 and build-time kernel
userspace API headers >= 6.10.
.. attribute:: stx_atomic_write_unit_min
Minimum size for direct I/O with torn-write protection.
.. 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.
.. 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.
.. availability:: Linux >= 4.11 with glibc >= 2.28 and build-time kernel
userspace API headers >= 6.11.
.. attribute:: stx_atomic_write_segments_max
Maximum iovecs for direct I/O with torn-write protection.
.. availability:: Linux >= 4.11 with glibc >= 2.28 and build-time kernel
userspace API headers >= 6.11.
.. attribute:: stx_dio_read_offset_align
Direct I/O file offset alignment requirement for reads.
.. availability:: Linux >= 4.11 with glibc >= 2.28 and build-time kernel
userspace API headers >= 6.14.
.. 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 in the *mask* parameter to :func:`os.statx`. Flags
including and after :const:`!STATX_MNT_ID` are only available when their
corresponding members in :class:`statx_result` are available.
.. availability:: Linux >= 4.11 with glibc >= 2.28.
.. versionadded:: next
.. data:: AT_STATX_FORCE_SYNC
A flag for the :func:`os.statx` function. 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).
.. availability:: Linux >= 4.11 with glibc >= 2.28.
.. versionadded:: next
.. data:: AT_STATX_DONT_SYNC
A flag for the :func:`os.statx` function. Requests that the kernel return
cached information if possible.
.. availability:: Linux >= 4.11 with glibc >= 2.28.
.. versionadded:: next
.. data:: AT_STATX_SYNC_AS_STAT
A flag for the :func:`os.statx` function. This flag is defined as ``0``, so
it has no effect, but it can be used to explicitly indicate neither
:data:`AT_STATX_FORCE_SYNC` nor :data:`AT_STATX_DONT_SYNC` is being passed.
In the absence of the other two flags, the kernel will generally return
information as fresh as :func:`os.stat` would return.
.. availability:: Linux >= 4.11 with glibc >= 2.28.
.. versionadded:: next
.. data:: AT_NO_AUTOMOUNT
If the final component of a path is an automount point, operate on the
automount point instead of performing the automount. On Linux,
:func:`os.stat`, :func:`os.fstat` and :func:`os.lstat` always behave this
way.
.. availability:: Linux.
.. versionadded:: next
.. function:: statvfs(path) .. function:: statvfs(path)
Perform a :c:func:`!statvfs` system call on the given path. The return value is Perform a :c:func:`!statvfs` system call on the given path. The return value is

View file

@ -493,3 +493,22 @@ constants, but are not an exhaustive list.
IO_REPARSE_TAG_APPEXECLINK IO_REPARSE_TAG_APPEXECLINK
.. versionadded:: 3.8 .. versionadded:: 3.8
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
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

View file

@ -433,6 +433,14 @@ mmap
(Contributed by Serhiy Storchaka in :gh:`78502`.) (Contributed by Serhiy Storchaka in :gh:`78502`.)
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`.)
os.path os.path
------- -------

View file

@ -1867,6 +1867,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(loop)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(loop));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(manual_reset)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(manual_reset));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(mapping)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(mapping));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(mask));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(match)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(match));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(max_length)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(max_length));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(maxdigits)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(maxdigits));

View file

@ -590,6 +590,7 @@ struct _Py_global_strings {
STRUCT_FOR_ID(loop) STRUCT_FOR_ID(loop)
STRUCT_FOR_ID(manual_reset) STRUCT_FOR_ID(manual_reset)
STRUCT_FOR_ID(mapping) STRUCT_FOR_ID(mapping)
STRUCT_FOR_ID(mask)
STRUCT_FOR_ID(match) STRUCT_FOR_ID(match)
STRUCT_FOR_ID(max_length) STRUCT_FOR_ID(max_length)
STRUCT_FOR_ID(maxdigits) STRUCT_FOR_ID(maxdigits)

View file

@ -1865,6 +1865,7 @@ extern "C" {
INIT_ID(loop), \ INIT_ID(loop), \
INIT_ID(manual_reset), \ INIT_ID(manual_reset), \
INIT_ID(mapping), \ INIT_ID(mapping), \
INIT_ID(mask), \
INIT_ID(match), \ INIT_ID(match), \
INIT_ID(max_length), \ INIT_ID(max_length), \
INIT_ID(maxdigits), \ INIT_ID(maxdigits), \

View file

@ -2148,6 +2148,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
_PyUnicode_InternStatic(interp, &string); _PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1)); assert(_PyUnicode_CheckConsistency(string, 1));
assert(PyUnicode_GET_LENGTH(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); string = &_Py_ID(match);
_PyUnicode_InternStatic(interp, &string); _PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1)); assert(_PyUnicode_CheckConsistency(string, 1));

View file

@ -136,6 +136,8 @@ def _add(str, fn):
_add("HAVE_UNLINKAT", "unlink") _add("HAVE_UNLINKAT", "unlink")
_add("HAVE_UNLINKAT", "rmdir") _add("HAVE_UNLINKAT", "rmdir")
_add("HAVE_UTIMENSAT", "utime") _add("HAVE_UTIMENSAT", "utime")
if _exists("statx"):
_set.add(statx)
supports_dir_fd = _set supports_dir_fd = _set
_set = set() _set = set()
@ -157,6 +159,8 @@ def _add(str, fn):
_add("HAVE_FPATHCONF", "pathconf") _add("HAVE_FPATHCONF", "pathconf")
if _exists("statvfs") and _exists("fstatvfs"): # mac os x10.3 if _exists("statvfs") and _exists("fstatvfs"): # mac os x10.3
_add("HAVE_FSTATVFS", "statvfs") _add("HAVE_FSTATVFS", "statvfs")
if _exists("statx"):
_set.add(statx)
supports_fd = _set supports_fd = _set
_set = set() _set = set()
@ -195,6 +199,8 @@ def _add(str, fn):
_add("HAVE_FSTATAT", "stat") _add("HAVE_FSTATAT", "stat")
_add("HAVE_UTIMENSAT", "utime") _add("HAVE_UTIMENSAT", "utime")
_add("MS_WINDOWS", "stat") _add("MS_WINDOWS", "stat")
if _exists("statx"):
_set.add(statx)
supports_follow_symlinks = _set supports_follow_symlinks = _set
del _set del _set

View file

@ -200,6 +200,21 @@ def filemode(mode):
FILE_ATTRIBUTE_VIRTUAL = 65536 FILE_ATTRIBUTE_VIRTUAL = 65536
# Linux STATX_ATTR constants for interpreting os.statx()'s
# "stx_attributes" and "stx_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 # If available, use C implementation
try: try:
from _stat import * from _stat import *

View file

@ -630,6 +630,14 @@ def setUp(self):
self.addCleanup(os_helper.unlink, self.fname) self.addCleanup(os_helper.unlink, self.fname)
create_file(self.fname, b"ABC") 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) * 100_000)
nanosecondy = getattr(result, name + "_ns") // 10_000
self.assertAlmostEqual(floaty, nanosecondy, delta=2, msg=name)
def check_stat_attributes(self, fname): def check_stat_attributes(self, fname):
result = os.stat(fname) result = os.stat(fname)
@ -650,21 +658,15 @@ def trunc(x): return x
result[getattr(stat, name)]) result[getattr(stat, name)])
self.assertIn(attr, members) self.assertIn(attr, members)
# Make sure that the st_?time and st_?time_ns fields roughly agree time_attributes = ['st_atime', 'st_mtime', 'st_ctime']
# (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
try: try:
floaty = int(result.st_birthtime * 100000) result.st_birthtime
nanosecondy = result.st_birthtime_ns // 10000 result.st_birthtime_ns
except AttributeError: except AttributeError:
pass pass
else: else:
self.assertAlmostEqual(floaty, nanosecondy, delta=2) time_attributes.append('st_birthtime')
self.check_timestamp_agreement(result, time_attributes)
try: try:
result[200] result[200]
@ -725,6 +727,88 @@ def test_stat_result_pickle(self):
unpickled = pickle.loads(p) unpickled = pickle.loads(p)
self.assertEqual(result, unpickled) self.assertEqual(result, unpickled)
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')
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', 0),
('st_rdev', 0),
('st_dev', 0),
)
basic_result = os.stat(self.fname)
for name, bits in requirements:
if result.stx_mask & bits == bits and hasattr(basic_result, name):
x = getattr(result, name)
b = getattr(basic_result, 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))
members = [name for name in dir(result)
if name.startswith('st_') or name.startswith('stx_')]
for name in members:
try:
setattr(result, name, 1)
self.fail("No exception raised")
except AttributeError:
pass
self.assertEqual(result.stx_attributes & result.stx_attributes_mask,
result.stx_attributes)
# statx_result is not a tuple or tuple-like object.
with self.assertRaisesRegex(TypeError, 'not subscriptable'):
result[0]
with self.assertRaisesRegex(TypeError, 'cannot unpack'):
_, _ = result
@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_attributes_pathlike(self):
self.check_statx_attributes(FakePath(self.fname))
@unittest.skipUnless(hasattr(os, 'statvfs'), 'test needs os.statvfs()') @unittest.skipUnless(hasattr(os, 'statvfs'), 'test needs os.statvfs()')
def test_statvfs_attributes(self): def test_statvfs_attributes(self):
result = os.statvfs(self.fname) result = os.statvfs(self.fname)

View file

@ -668,22 +668,65 @@ def test_fstat(self):
finally: finally:
fp.close() fp.close()
def test_stat(self): def check_statlike_path(self, func):
self.assertTrue(posix.stat(os_helper.TESTFN)) self.assertTrue(func(os_helper.TESTFN))
self.assertTrue(posix.stat(os.fsencode(os_helper.TESTFN))) self.assertTrue(func(os.fsencode(os_helper.TESTFN)))
self.assertTrue(func(os_helper.FakePath(os_helper.TESTFN)))
self.assertRaisesRegex(TypeError, self.assertRaisesRegex(TypeError,
'should be string, bytes, os.PathLike or integer, not', 'should be string, bytes, os.PathLike or integer, not',
posix.stat, bytearray(os.fsencode(os_helper.TESTFN))) func, bytearray(os.fsencode(os_helper.TESTFN)))
self.assertRaisesRegex(TypeError, self.assertRaisesRegex(TypeError,
'should be string, bytes, os.PathLike or integer, not', 'should be string, bytes, os.PathLike or integer, not',
posix.stat, None) func, None)
self.assertRaisesRegex(TypeError, self.assertRaisesRegex(TypeError,
'should be string, bytes, os.PathLike or integer, not', 'should be string, bytes, os.PathLike or integer, not',
posix.stat, list(os_helper.TESTFN)) func, list(os_helper.TESTFN))
self.assertRaisesRegex(TypeError, self.assertRaisesRegex(TypeError,
'should be string, bytes, os.PathLike or integer, not', 'should be string, bytes, os.PathLike or integer, not',
posix.stat, list(os.fsencode(os_helper.TESTFN))) func, list(os.fsencode(os_helper.TESTFN)))
def test_stat(self):
self.check_statlike_path(posix.stat)
@unittest.skipUnless(hasattr(posix, 'statx'), 'test needs posix.statx()')
def test_statx(self):
def func(path, **kwargs):
return posix.statx(path, posix.STATX_BASIC_STATS, **kwargs)
self.check_statlike_path(func)
@unittest.skipUnless(hasattr(posix, 'statx'), 'test needs posix.statx()')
def test_statx_flags(self):
# glibc's fallback implementation of statx via the stat family fails
# with EINVAL on the (nonzero) sync flags. If you see this failure,
# update your kernel and/or seccomp syscall filter.
valid_flag_names = ('AT_NO_AUTOMOUNT', 'AT_STATX_SYNC_AS_STAT',
'AT_STATX_FORCE_SYNC', 'AT_STATX_DONT_SYNC')
for flag_name in valid_flag_names:
flag = getattr(posix, flag_name)
with self.subTest(msg=flag_name, flags=flag):
posix.statx(os_helper.TESTFN, posix.STATX_BASIC_STATS,
flags=flag)
# These flags are not exposed to Python because their functionality is
# implemented via kwargs instead.
kwarg_equivalent_flags = (
(0x0100, 'AT_SYMLINK_NOFOLLOW', 'follow_symlinks'),
(0x0400, 'AT_SYMLINK_FOLLOW', 'follow_symlinks'),
(0x1000, 'AT_EMPTY_PATH', 'dir_fd'),
)
for flag, flag_name, kwarg_name in kwarg_equivalent_flags:
with self.subTest(msg=flag_name, flags=flag):
with self.assertRaisesRegex(ValueError, kwarg_name):
posix.statx(os_helper.TESTFN, posix.STATX_BASIC_STATS,
flags=flag)
with self.subTest(msg="AT_STATX_FORCE_SYNC | AT_STATX_DONT_SYNC"):
with self.assertRaises(OSError) as ctx:
flags = posix.AT_STATX_FORCE_SYNC | posix.AT_STATX_DONT_SYNC
posix.statx(os_helper.TESTFN, posix.STATX_BASIC_STATS,
flags=flags)
self.assertEqual(ctx.exception.errno, errno.EINVAL)
@unittest.skipUnless(hasattr(posix, 'mkfifo'), "don't have mkfifo()") @unittest.skipUnless(hasattr(posix, 'mkfifo'), "don't have mkfifo()")
def test_mkfifo(self): def test_mkfifo(self):
@ -1629,33 +1672,42 @@ def test_chown_dir_fd(self):
with self.prepare_file() as (dir_fd, name, fullname): with self.prepare_file() as (dir_fd, name, fullname):
posix.chown(name, os.getuid(), os.getgid(), dir_fd=dir_fd) 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 check_statlike_dir_fd(self, func):
def test_stat_dir_fd(self):
with self.prepare() as (dir_fd, name, fullname): with self.prepare() as (dir_fd, name, fullname):
with open(fullname, 'w') as outfile: with open(fullname, 'w') as outfile:
outfile.write("testline\n") outfile.write("testline\n")
self.addCleanup(posix.unlink, fullname) self.addCleanup(posix.unlink, fullname)
s1 = posix.stat(fullname) s1 = func(fullname)
s2 = posix.stat(name, dir_fd=dir_fd) s2 = func(name, dir_fd=dir_fd)
self.assertEqual(s1, s2) self.assertEqual((s1.st_dev, s1.st_ino), (s2.st_dev, s2.st_ino))
s2 = posix.stat(fullname, dir_fd=None) s2 = func(fullname, dir_fd=None)
self.assertEqual(s1, s2) self.assertEqual((s1.st_dev, s1.st_ino), (s2.st_dev, s2.st_ino))
self.assertRaisesRegex(TypeError, 'should be integer or None, not', 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', 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, self.assertRaises(OverflowError,
posix.stat, name, dir_fd=10**20) func, name, dir_fd=10**20)
for fd in False, True: for fd in False, True:
with self.assertWarnsRegex(RuntimeWarning, with self.assertWarnsRegex(RuntimeWarning,
'bool is used as a file descriptor') as cm: 'bool is used as a file descriptor') as cm:
with self.assertRaises(OSError): with self.assertRaises(OSError):
posix.stat('nonexisting', dir_fd=fd) func('nonexisting', dir_fd=fd)
self.assertEqual(cm.filename, __file__) 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):
def func(path, **kwargs):
return 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()") @unittest.skipUnless(os.utime in os.supports_dir_fd, "test needs dir_fd support in os.utime()")
def test_utime_dir_fd(self): def test_utime_dir_fd(self):
with self.prepare_file() as (dir_fd, name, fullname): with self.prepare_file() as (dir_fd, name, fullname):

View file

@ -210,6 +210,7 @@ Médéric Boquien
Matias Bordese Matias Bordese
Jonas Borgström Jonas Borgström
Jurjen Bos Jurjen Bos
Jeffrey Bosboom
Peter Bosch Peter Bosch
Dan Boswell Dan Boswell
Eric Bouck Eric Bouck

View file

@ -0,0 +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.

View file

@ -186,6 +186,140 @@ exit:
return return_value; return return_value;
} }
#if defined(HAVE_STATX)
PyDoc_STRVAR(os_statx__doc__,
"statx($module, /, path, mask, *, flags=0, dir_fd=None,\n"
" follow_symlinks=True)\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"
" flags\n"
" A bitmask of AT_NO_AUTOMOUNT and/or AT_STATX_* flags.\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"
"\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 flags,
int dir_fd, int follow_symlinks);
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 5
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(flags), &_Py_ID(dir_fd), &_Py_ID(follow_symlinks), },
};
#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", "flags", "dir_fd", "follow_symlinks", NULL};
static _PyArg_Parser _parser = {
.keywords = _keywords,
.fname = "statx",
.kwtuple = KWTUPLE,
};
#undef KWTUPLE
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 flags = 0;
int dir_fd = DEFAULT_DIR_FD;
int follow_symlinks = 1;
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;
}
{
Py_ssize_t _bytes = PyLong_AsNativeBytes(args[1], &mask, sizeof(unsigned int),
Py_ASNATIVEBYTES_NATIVE_ENDIAN |
Py_ASNATIVEBYTES_ALLOW_INDEX |
Py_ASNATIVEBYTES_UNSIGNED_BUFFER);
if (_bytes < 0) {
goto exit;
}
if ((size_t)_bytes > sizeof(unsigned int)) {
if (PyErr_WarnEx(PyExc_DeprecationWarning,
"integer value out of range", 1) < 0)
{
goto exit;
}
}
}
if (!noptargs) {
goto skip_optional_kwonly;
}
if (args[2]) {
flags = PyLong_AsInt(args[2]);
if (flags == -1 && PyErr_Occurred()) {
goto exit;
}
if (!--noptargs) {
goto skip_optional_kwonly;
}
}
if (args[3]) {
if (!dir_fd_converter(args[3], &dir_fd)) {
goto exit;
}
if (!--noptargs) {
goto skip_optional_kwonly;
}
}
follow_symlinks = PyObject_IsTrue(args[4]);
if (follow_symlinks < 0) {
goto exit;
}
skip_optional_kwonly:
return_value = os_statx_impl(module, &path, mask, flags, dir_fd, follow_symlinks);
exit:
/* Cleanup for path */
path_cleanup(&path);
return return_value;
}
#endif /* defined(HAVE_STATX) */
PyDoc_STRVAR(os_access__doc__, PyDoc_STRVAR(os_access__doc__,
"access($module, /, path, mode, *, dir_fd=None, effective_ids=False,\n" "access($module, /, path, mode, *, dir_fd=None, effective_ids=False,\n"
" follow_symlinks=True)\n" " follow_symlinks=True)\n"
@ -12793,6 +12927,10 @@ exit:
#endif /* defined(__EMSCRIPTEN__) */ #endif /* defined(__EMSCRIPTEN__) */
#ifndef OS_STATX_METHODDEF
#define OS_STATX_METHODDEF
#endif /* !defined(OS_STATX_METHODDEF) */
#ifndef OS_TTYNAME_METHODDEF #ifndef OS_TTYNAME_METHODDEF
#define OS_TTYNAME_METHODDEF #define OS_TTYNAME_METHODDEF
#endif /* !defined(OS_TTYNAME_METHODDEF) */ #endif /* !defined(OS_TTYNAME_METHODDEF) */
@ -13472,4 +13610,4 @@ exit:
#ifndef OS__EMSCRIPTEN_LOG_METHODDEF #ifndef OS__EMSCRIPTEN_LOG_METHODDEF
#define OS__EMSCRIPTEN_LOG_METHODDEF #define OS__EMSCRIPTEN_LOG_METHODDEF
#endif /* !defined(OS__EMSCRIPTEN_LOG_METHODDEF) */ #endif /* !defined(OS__EMSCRIPTEN_LOG_METHODDEF) */
/*[clinic end generated code: output=67f0df7cd5a7de20 input=a9049054013a1b77]*/ /*[clinic end generated code: output=44f7a1a16dad2e08 input=a9049054013a1b77]*/

View file

@ -40,6 +40,7 @@
// --- System includes ------------------------------------------------------ // --- System includes ------------------------------------------------------
#include <stddef.h> // offsetof()
#include <stdio.h> // ctermid() #include <stdio.h> // ctermid()
#include <stdlib.h> // system() #include <stdlib.h> // system()
@ -408,6 +409,31 @@ extern char *ctermid_r(char *);
# define STRUCT_STAT struct stat # define STRUCT_STAT struct stat
#endif #endif
#ifdef HAVE_STATX
/* until we can assume glibc 2.28 at runtime, we must weakly link */
# pragma weak statx
static const unsigned int _Py_STATX_KNOWN = (STATX_BASIC_STATS | STATX_BTIME
#ifdef STATX_MNT_ID
| STATX_MNT_ID
#endif
#ifdef STATX_DIOALIGN
| STATX_DIOALIGN
#endif
#ifdef STATX_MNT_ID_UNIQUE
| STATX_MNT_ID_UNIQUE
#endif
#ifdef STATX_SUBVOL
| STATX_SUBVOL
#endif
#ifdef STATX_WRITE_ATOMIC
| STATX_WRITE_ATOMIC
#endif
#ifdef STATX_DIO_READ_ALIGN
| STATX_DIO_READ_ALIGN
#endif
);
#endif /* HAVE_STATX */
#if !defined(EX_OK) && defined(EXIT_SUCCESS) #if !defined(EX_OK) && defined(EXIT_SUCCESS)
# define EX_OK EXIT_SUCCESS # define EX_OK EXIT_SUCCESS
@ -1169,6 +1195,9 @@ typedef struct {
#endif #endif
newfunc statresult_new_orig; newfunc statresult_new_orig;
PyObject *StatResultType; PyObject *StatResultType;
#ifdef HAVE_STATX
PyObject *StatxResultType;
#endif
PyObject *StatVFSResultType; PyObject *StatVFSResultType;
PyObject *TerminalSizeType; PyObject *TerminalSizeType;
PyObject *TimesResultType; PyObject *TimesResultType;
@ -2549,6 +2578,9 @@ _posix_clear(PyObject *module)
Py_CLEAR(state->SchedParamType); Py_CLEAR(state->SchedParamType);
#endif #endif
Py_CLEAR(state->StatResultType); Py_CLEAR(state->StatResultType);
#ifdef HAVE_STATX
Py_CLEAR(state->StatxResultType);
#endif
Py_CLEAR(state->StatVFSResultType); Py_CLEAR(state->StatVFSResultType);
Py_CLEAR(state->TerminalSizeType); Py_CLEAR(state->TerminalSizeType);
Py_CLEAR(state->TimesResultType); Py_CLEAR(state->TimesResultType);
@ -2574,6 +2606,9 @@ _posix_traverse(PyObject *module, visitproc visit, void *arg)
Py_VISIT(state->SchedParamType); Py_VISIT(state->SchedParamType);
#endif #endif
Py_VISIT(state->StatResultType); Py_VISIT(state->StatResultType);
#ifdef HAVE_STATX
Py_VISIT(state->StatxResultType);
#endif
Py_VISIT(state->StatVFSResultType); Py_VISIT(state->StatVFSResultType);
Py_VISIT(state->TerminalSizeType); Py_VISIT(state->TerminalSizeType);
Py_VISIT(state->TimesResultType); Py_VISIT(state->TimesResultType);
@ -2594,12 +2629,44 @@ _posix_free(void *module)
_posix_clear((PyObject *)module); _posix_clear((PyObject *)module);
} }
#define SEC_TO_NS (1000000000LL)
static PyObject *
stat_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 *ns_total = NULL;
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;
}
ns_total = PyNumber_Add(s_in_ns, ns_fractional);
exit:
Py_XDECREF(s);
Py_XDECREF(ns_fractional);
Py_XDECREF(s_in_ns);
return ns_total;
}
}
static int static int
fill_time(_posixstate *state, PyObject *v, int s_index, int f_index, fill_time(_posixstate *state, PyObject *v, int s_index, int f_index,
int ns_index, time_t sec, unsigned long nsec) int ns_index, time_t sec, unsigned long nsec)
{ {
assert(!PyErr_Occurred()); assert(!PyErr_Occurred());
#define SEC_TO_NS (1000000000LL)
assert(nsec < SEC_TO_NS); assert(nsec < SEC_TO_NS);
if (s_index >= 0) { if (s_index >= 0) {
@ -2618,50 +2685,18 @@ fill_time(_posixstate *state, PyObject *v, int s_index, int f_index,
PyStructSequence_SET_ITEM(v, f_index, float_s); PyStructSequence_SET_ITEM(v, f_index, float_s);
} }
int res = -1;
if (ns_index >= 0) { if (ns_index >= 0) {
/* 1677-09-21 00:12:44 to 2262-04-11 23:47:15 UTC inclusive */ PyObject *ns_total = stat_nanosecond_timestamp(state, sec, nsec);
if ((LLONG_MIN/SEC_TO_NS) <= sec && sec <= (LLONG_MAX/SEC_TO_NS - 1)) { if (ns_total == NULL) {
PyObject *ns_total = PyLong_FromLongLong(sec * SEC_TO_NS + nsec); return -1;
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);
} }
PyStructSequence_SET_ITEM(v, ns_index, ns_total);
} }
return res; assert(!PyErr_Occurred());
#undef SEC_TO_NS return 0;
} }
#undef SEC_TO_NS
#ifdef MS_WINDOWS #ifdef MS_WINDOWS
static PyObject* static PyObject*
@ -3276,6 +3311,307 @@ os_lstat_impl(PyObject *module, path_t *path, int dir_fd)
} }
#ifdef HAVE_STATX
typedef struct {
PyObject_HEAD
double atime_sec, btime_sec, ctime_sec, mtime_sec;
dev_t rdev, dev;
struct statx stx;
} Py_statx_result;
#define M(attr, type, offset, doc) \
{attr, type, offset, Py_READONLY, PyDoc_STR(doc)}
#define MM(attr, type, member, doc) \
M(#attr, type, offsetof(Py_statx_result, stx.stx_##member), doc)
#define MX(attr, type, member, doc) \
M(#attr, type, offsetof(Py_statx_result, member), doc)
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_attributes, Py_T_ULONGLONG, attributes, "Linux inode attribute bits"),
MM(st_nlink, Py_T_UINT, nlink, "number of hard links"),
MM(st_uid, Py_T_UINT, uid, "user ID of owner"),
MM(st_gid, Py_T_UINT, gid, "group ID of owner"),
MM(st_mode, Py_T_USHORT, mode, "protection bits"),
MM(st_ino, Py_T_ULONGLONG, ino, "inode"),
MM(st_size, Py_T_ULONGLONG, size, "total size, in bytes"),
MM(st_blocks, Py_T_ULONGLONG, blocks, "number of blocks allocated"),
MM(stx_attributes_mask, Py_T_ULONGLONG, attributes_mask,
"Mask of supported bits in stx_attributes"),
MX(st_atime, Py_T_DOUBLE, atime_sec, "time of last access"),
MX(st_birthtime, Py_T_DOUBLE, btime_sec, "time of creation"),
MX(st_ctime, Py_T_DOUBLE, ctime_sec, "time of last change"),
MX(st_mtime, Py_T_DOUBLE, mtime_sec, "time of last modification"),
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)"),
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"),
#ifdef STATX_MNT_ID
MM(stx_mnt_id, Py_T_ULONGLONG, mnt_id, "mount ID"),
#endif
#ifdef STATX_DIOALIGN
MM(stx_dio_mem_align, Py_T_UINT, dio_mem_align,
"direct I/O memory buffer alignment"),
MM(stx_dio_offset_align, Py_T_UINT, dio_offset_align,
"direct I/O file offset alignment"),
#endif
#ifdef STATX_SUBVOL
MM(stx_subvol, Py_T_ULONGLONG, subvol, "subvolume ID"),
#endif
#ifdef STATX_WRITE_ATOMIC
MM(stx_atomic_write_unit_min, Py_T_UINT, atomic_write_unit_min,
"minimum size for direct I/O with torn-write protection"),
MM(stx_atomic_write_unit_max, Py_T_UINT, atomic_write_unit_max,
"maximum size for direct I/O with torn-write protection"),
MM(stx_atomic_write_unit_max_opt, Py_T_UINT, atomic_write_unit_max_opt,
"maximum optimized size for direct I/O with torn-write protection"),
MM(stx_atomic_write_segments_max, Py_T_UINT, atomic_write_segments_max,
"maximum iovecs for direct I/O with torn-write protection"),
#endif
#ifdef STATX_DIO_READ_ALIGN
MM(stx_dio_read_offset_align, Py_T_UINT, dio_read_offset_align,
"direct I/O file offset alignment for reads"),
#endif
{NULL},
};
#undef MX
#undef MM
#undef M
static PyObject *
pystatx_result_get_nsec(PyObject *op, void *context)
{
uint16_t offset = (uintptr_t)context;
struct statx_timestamp *ts = (void*)op + offset;
_posixstate *state = PyType_GetModuleState(Py_TYPE(op));
assert(state != NULL);
return stat_nanosecond_timestamp(state, ts->tv_sec, ts->tv_nsec);
}
/* The low 16 bits of the context pointer are the offset from the start of
Py_statx_result to the struct statx member. */
#define GM(attr, type, member, doc) \
{#attr, pystatx_result_get_##type, NULL, PyDoc_STR(doc), \
(void *)(offsetof(Py_statx_result, stx.stx_##member))}
static PyGetSetDef pystatx_result_getset[] = {
GM(st_atime_ns, nsec, atime, "time of last access in nanoseconds"),
GM(st_birthtime_ns, nsec, btime, "time of creation in nanoseconds"),
GM(st_ctime_ns, nsec, ctime, "time of last change in nanoseconds"),
GM(st_mtime_ns, nsec, mtime, "time of last modification in nanoseconds"),
{NULL},
};
#undef GM
static PyObject *
pystatx_result_repr(PyObject *op)
{
PyUnicodeWriter *writer = PyUnicodeWriter_Create(0);
if (writer == NULL) {
return NULL;
}
#define WRITE_ASCII(s) \
do { \
if (PyUnicodeWriter_WriteASCII(writer, s, strlen(s)) < 0) { \
goto error; \
} \
} while (0)
WRITE_ASCII("os.statx_result(");
for (size_t i = 0; i < Py_ARRAY_LENGTH(pystatx_result_members) - 1; ++i) {
if (i > 0) {
WRITE_ASCII(", ");
}
PyMemberDef *d = &pystatx_result_members[i];
WRITE_ASCII(d->name);
WRITE_ASCII("=");
PyObject *o = PyMember_GetOne((const char *)op, d);
if (o == NULL) {
goto error;
}
if (PyUnicodeWriter_WriteRepr(writer, o) < 0) {
Py_DECREF(o);
goto error;
}
Py_DECREF(o);
}
if (Py_ARRAY_LENGTH(pystatx_result_members) > 1
&& Py_ARRAY_LENGTH(pystatx_result_getset) > 1) {
WRITE_ASCII(", ");
}
for (size_t i = 0; i < Py_ARRAY_LENGTH(pystatx_result_getset) - 1; ++i) {
if (i > 0) {
WRITE_ASCII(", ");
}
PyGetSetDef *d = &pystatx_result_getset[i];
WRITE_ASCII(d->name);
WRITE_ASCII("=");
PyObject *o = d->get(op, d->closure);
if (o == NULL) {
goto error;
}
if (PyUnicodeWriter_WriteRepr(writer, o) < 0) {
Py_DECREF(o);
goto error;
}
Py_DECREF(o);
}
WRITE_ASCII(")");
return PyUnicodeWriter_Finish(writer);
#undef WRITE_ASCII
error:
PyUnicodeWriter_Discard(writer);
return NULL;
}
static int
pystatx_result_traverse(PyObject *self, visitproc visit, void *arg)
{
Py_VISIT(Py_TYPE(self));
return 0;
}
static void
pystatx_result_dealloc(PyObject *op)
{
Py_statx_result *self = (Py_statx_result *) op;
PyTypeObject *tp = Py_TYPE(self);
PyObject_GC_UnTrack(self);
tp->tp_free(self);
Py_DECREF(tp);
}
static PyType_Slot pystatx_result_slots[] = {
{Py_tp_repr, pystatx_result_repr},
{Py_tp_traverse, pystatx_result_traverse},
{Py_tp_dealloc, pystatx_result_dealloc},
{Py_tp_members, pystatx_result_members},
{Py_tp_getset, pystatx_result_getset},
{0, NULL},
};
static PyType_Spec pystatx_result_spec = {
.name = "os.statx_result",
.basicsize = sizeof(Py_statx_result),
.flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HEAPTYPE | Py_TPFLAGS_HAVE_GC |
Py_TPFLAGS_IMMUTABLETYPE | Py_TPFLAGS_DISALLOW_INSTANTIATION,
.slots = pystatx_result_slots,
};
/*[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.
*
flags: int = 0
A bitmask of AT_NO_AUTOMOUNT and/or AT_STATX_* flags.
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.
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 flags,
int dir_fd, int follow_symlinks)
/*[clinic end generated code: output=e3765979ac6fe15b input=f0116380c5dc4f2f]*/
{
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;
}
/* reject flags covered by kwargs, but allow unknown flags that may be
future AT_STATX_* extensions */
if (flags & (AT_SYMLINK_NOFOLLOW | AT_SYMLINK_FOLLOW)) {
PyErr_Format(PyExc_ValueError,
"use follow_symlinks kwarg instead of AT_SYMLINK_* flag");
return NULL;
}
if (flags & AT_EMPTY_PATH) {
PyErr_Format(PyExc_ValueError,
"use dir_fd kwarg instead of AT_EMPTY_PATH flag");
return NULL;
}
/* 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;
_posixstate *state = get_posix_state(module);
PyTypeObject *tp = (PyTypeObject *)state->StatxResultType;
Py_statx_result *v = (Py_statx_result *)tp->tp_alloc(tp, 0);
if (v == NULL) {
return NULL;
}
int result;
Py_BEGIN_ALLOW_THREADS
if (path->fd != -1) {
result = statx(path->fd, "", flags | AT_EMPTY_PATH, mask, &v->stx);
}
else {
result = statx(dir_fd, path->narrow, flags, mask, &v->stx);
}
Py_END_ALLOW_THREADS
if (result != 0) {
Py_DECREF(v);
return path_error(path);
}
v->atime_sec = ((double)v->stx.stx_atime.tv_sec
+ 1e-9 * v->stx.stx_atime.tv_nsec);
v->btime_sec = ((double)v->stx.stx_btime.tv_sec
+ 1e-9 * v->stx.stx_btime.tv_nsec);
v->ctime_sec = ((double)v->stx.stx_ctime.tv_sec
+ 1e-9 * v->stx.stx_ctime.tv_nsec);
v->mtime_sec = ((double)v->stx.stx_mtime.tv_sec
+ 1e-9 * v->stx.stx_mtime.tv_nsec);
v->rdev = makedev(v->stx.stx_rdev_major, v->stx.stx_rdev_minor);
v->dev = makedev(v->stx.stx_dev_major, v->stx.stx_dev_minor);
assert(!PyErr_Occurred());
return (PyObject *)v;
}
#endif /* HAVE_STATX */
/*[clinic input] /*[clinic input]
os.access -> bool os.access -> bool
@ -17051,6 +17387,7 @@ os__emscripten_log_impl(PyObject *module, const char *arg)
static PyMethodDef posix_methods[] = { static PyMethodDef posix_methods[] = {
OS_STAT_METHODDEF OS_STAT_METHODDEF
OS_STATX_METHODDEF
OS_ACCESS_METHODDEF OS_ACCESS_METHODDEF
OS_TTYNAME_METHODDEF OS_TTYNAME_METHODDEF
OS_CHDIR_METHODDEF OS_CHDIR_METHODDEF
@ -17897,6 +18234,49 @@ all_ins(PyObject *m)
if (PyModule_Add(m, "NODEV", _PyLong_FromDev(NODEV))) return -1; if (PyModule_Add(m, "NODEV", _PyLong_FromDev(NODEV))) return -1;
#endif #endif
#ifdef AT_NO_AUTOMOUNT
if (PyModule_AddIntMacro(m, AT_NO_AUTOMOUNT)) return -1;
#endif
#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;
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;
#ifdef STATX_MNT_ID
if (PyModule_AddIntMacro(m, STATX_MNT_ID)) return -1;
#endif
#ifdef STATX_DIOALIGN
if (PyModule_AddIntMacro(m, STATX_DIOALIGN)) return -1;
#endif
#ifdef STATX_MNT_ID_UNIQUE
if (PyModule_AddIntMacro(m, STATX_MNT_ID_UNIQUE)) return -1;
#endif
#ifdef STATX_SUBVOL
if (PyModule_AddIntMacro(m, STATX_SUBVOL)) return -1;
#endif
#ifdef STATX_WRITE_ATOMIC
if (PyModule_AddIntMacro(m, STATX_WRITE_ATOMIC)) return -1;
#endif
#ifdef STATX_DIO_READ_ALIGN
if (PyModule_AddIntMacro(m, STATX_DIO_READ_ALIGN)) return -1;
#endif
/* STATX_ALL intentionally omitted because it is deprecated */
if (PyModule_AddIntMacro(m, AT_STATX_SYNC_AS_STAT)) return -1;
if (PyModule_AddIntMacro(m, AT_STATX_FORCE_SYNC)) return -1;
if (PyModule_AddIntMacro(m, AT_STATX_DONT_SYNC)) return -1;
/* STATX_ATTR_* constants are in the stat module */
#endif /* HAVE_STATX */
#if defined(__APPLE__) #if defined(__APPLE__)
if (PyModule_AddIntConstant(m, "_COPYFILE_DATA", COPYFILE_DATA)) return -1; if (PyModule_AddIntConstant(m, "_COPYFILE_DATA", COPYFILE_DATA)) return -1;
if (PyModule_AddIntConstant(m, "_COPYFILE_STAT", COPYFILE_STAT)) return -1; if (PyModule_AddIntConstant(m, "_COPYFILE_STAT", COPYFILE_STAT)) return -1;
@ -18168,6 +18548,24 @@ posixmodule_exec(PyObject *m)
} }
#endif #endif
#ifdef HAVE_STATX
if (statx == NULL) {
PyObject* dct = PyModule_GetDict(m);
if (dct == NULL) {
return -1;
}
if (PyDict_PopString(dct, "statx", NULL) < 0) {
return -1;
}
}
else {
state->StatxResultType = PyType_FromModuleAndSpec(m, &pystatx_result_spec, NULL);
if (PyModule_AddObjectRef(m, "statx_result", state->StatxResultType) < 0) {
return -1;
}
}
#endif
/* Initialize environ dictionary */ /* Initialize environ dictionary */
if (PyModule_Add(m, "environ", convertenviron()) != 0) { if (PyModule_Add(m, "environ", convertenviron()) != 0) {
return -1; return -1;

6
configure generated vendored
View file

@ -20191,6 +20191,12 @@ if test "x$ac_cv_func_splice" = xyes
then : then :
printf "%s\n" "#define HAVE_SPLICE 1" >>confdefs.h 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 fi
ac_fn_c_check_func "$LINENO" "strftime" "ac_cv_func_strftime" ac_fn_c_check_func "$LINENO" "strftime" "ac_cv_func_strftime"
if test "x$ac_cv_func_strftime" = xyes if test "x$ac_cv_func_strftime" = xyes

View file

@ -5251,7 +5251,7 @@ AC_CHECK_FUNCS([ \
setitimer setlocale setpgid setpgrp setpriority setregid setresgid \ setitimer setlocale setpgid setpgrp setpriority setregid setresgid \
setresuid setreuid setsid setuid setvbuf shutdown sigaction sigaltstack \ setresuid setreuid setsid setuid setvbuf shutdown sigaction sigaltstack \
sigfillset siginterrupt sigpending sigrelse sigtimedwait sigwait \ 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 \ sysconf tcgetpgrp tcsetpgrp tempnam timegm times tmpfile \
tmpnam tmpnam_r truncate ttyname_r umask uname unlinkat unlockpt utimensat utimes vfork \ tmpnam tmpnam_r truncate ttyname_r umask uname unlinkat unlockpt utimensat utimes vfork \
wait wait3 wait4 waitid waitpid wcscoll wcsftime wcsxfrm wmemcmp writev \ wait wait3 wait4 waitid waitpid wcscoll wcsftime wcsxfrm wmemcmp writev \

View file

@ -1285,6 +1285,9 @@
/* Define to 1 if you have the 'statvfs' function. */ /* Define to 1 if you have the 'statvfs' function. */
#undef HAVE_STATVFS #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 */ /* Define if you have struct stat.st_mtim.tv_nsec */
#undef HAVE_STAT_TV_NSEC #undef HAVE_STAT_TV_NSEC