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.
.. 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)
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
.. 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`.)
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
-------

View file

@ -1867,6 +1867,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));

View file

@ -590,6 +590,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)

View file

@ -1865,6 +1865,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), \

View file

@ -2148,6 +2148,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));

View file

@ -136,6 +136,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()
@ -157,6 +159,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()
@ -195,6 +199,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

View file

@ -200,6 +200,21 @@ def filemode(mode):
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
try:
from _stat import *

View file

@ -630,6 +630,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) * 100_000)
nanosecondy = getattr(result, name + "_ns") // 10_000
self.assertAlmostEqual(floaty, nanosecondy, delta=2, msg=name)
def check_stat_attributes(self, fname):
result = os.stat(fname)
@ -650,21 +658,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']
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]
@ -725,6 +727,88 @@ def test_stat_result_pickle(self):
unpickled = pickle.loads(p)
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()')
def test_statvfs_attributes(self):
result = os.statvfs(self.fname)

View file

@ -668,22 +668,65 @@ def test_fstat(self):
finally:
fp.close()
def test_stat(self):
self.assertTrue(posix.stat(os_helper.TESTFN))
self.assertTrue(posix.stat(os.fsencode(os_helper.TESTFN)))
def check_statlike_path(self, func):
self.assertTrue(func(os_helper.TESTFN))
self.assertTrue(func(os.fsencode(os_helper.TESTFN)))
self.assertTrue(func(os_helper.FakePath(os_helper.TESTFN)))
self.assertRaisesRegex(TypeError,
'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,
'should be string, bytes, os.PathLike or integer, not',
posix.stat, None)
func, None)
self.assertRaisesRegex(TypeError,
'should be string, bytes, os.PathLike or integer, not',
posix.stat, list(os_helper.TESTFN))
func, list(os_helper.TESTFN))
self.assertRaisesRegex(TypeError,
'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()")
def test_mkfifo(self):
@ -1629,33 +1672,42 @@ 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):
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()")
def test_utime_dir_fd(self):
with self.prepare_file() as (dir_fd, name, fullname):

View file

@ -210,6 +210,7 @@ Médéric Boquien
Matias Bordese
Jonas Borgström
Jurjen Bos
Jeffrey Bosboom
Peter Bosch
Dan Boswell
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;
}
#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__,
"access($module, /, path, mode, *, dir_fd=None, effective_ids=False,\n"
" follow_symlinks=True)\n"
@ -12793,6 +12927,10 @@ exit:
#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) */
@ -13472,4 +13610,4 @@ exit:
#ifndef OS__EMSCRIPTEN_LOG_METHODDEF
#define 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 ------------------------------------------------------
#include <stddef.h> // offsetof()
#include <stdio.h> // ctermid()
#include <stdlib.h> // system()
@ -408,6 +409,31 @@ extern char *ctermid_r(char *);
# define STRUCT_STAT struct stat
#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)
# define EX_OK EXIT_SUCCESS
@ -1169,6 +1195,9 @@ typedef struct {
#endif
newfunc statresult_new_orig;
PyObject *StatResultType;
#ifdef HAVE_STATX
PyObject *StatxResultType;
#endif
PyObject *StatVFSResultType;
PyObject *TerminalSizeType;
PyObject *TimesResultType;
@ -2549,6 +2578,9 @@ _posix_clear(PyObject *module)
Py_CLEAR(state->SchedParamType);
#endif
Py_CLEAR(state->StatResultType);
#ifdef HAVE_STATX
Py_CLEAR(state->StatxResultType);
#endif
Py_CLEAR(state->StatVFSResultType);
Py_CLEAR(state->TerminalSizeType);
Py_CLEAR(state->TimesResultType);
@ -2574,6 +2606,9 @@ _posix_traverse(PyObject *module, visitproc visit, void *arg)
Py_VISIT(state->SchedParamType);
#endif
Py_VISIT(state->StatResultType);
#ifdef HAVE_STATX
Py_VISIT(state->StatxResultType);
#endif
Py_VISIT(state->StatVFSResultType);
Py_VISIT(state->TerminalSizeType);
Py_VISIT(state->TimesResultType);
@ -2594,12 +2629,44 @@ _posix_free(void *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
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) {
@ -2618,50 +2685,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 = stat_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*
@ -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]
os.access -> bool
@ -17051,6 +17387,7 @@ os__emscripten_log_impl(PyObject *module, const char *arg)
static PyMethodDef posix_methods[] = {
OS_STAT_METHODDEF
OS_STATX_METHODDEF
OS_ACCESS_METHODDEF
OS_TTYNAME_METHODDEF
OS_CHDIR_METHODDEF
@ -17897,6 +18234,49 @@ all_ins(PyObject *m)
if (PyModule_Add(m, "NODEV", _PyLong_FromDev(NODEV))) return -1;
#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 (PyModule_AddIntConstant(m, "_COPYFILE_DATA", COPYFILE_DATA)) return -1;
if (PyModule_AddIntConstant(m, "_COPYFILE_STAT", COPYFILE_STAT)) return -1;
@ -18168,6 +18548,24 @@ posixmodule_exec(PyObject *m)
}
#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 */
if (PyModule_Add(m, "environ", convertenviron()) != 0) {
return -1;

6
configure generated vendored
View file

@ -20191,6 +20191,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

View file

@ -5251,7 +5251,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 \

View file

@ -1285,6 +1285,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