mirror of
https://github.com/python/cpython.git
synced 2025-11-02 14:41:33 +00:00
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:
parent
27acaf1cb7
commit
fe9ac7fc8c
18 changed files with 1018 additions and 71 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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));
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
1
Include/internal/pycore_runtime_init_generated.h
generated
1
Include/internal/pycore_runtime_init_generated.h
generated
|
|
@ -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), \
|
||||||
|
|
|
||||||
|
|
@ -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));
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
15
Lib/stat.py
15
Lib/stat.py
|
|
@ -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 *
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
140
Modules/clinic/posixmodule.c.h
generated
140
Modules/clinic/posixmodule.c.h
generated
|
|
@ -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]*/
|
||||||
|
|
|
||||||
|
|
@ -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
6
configure
generated
vendored
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 \
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue