mirror of
https://github.com/python/cpython.git
synced 2025-11-09 10:01:42 +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.
|
||||
|
||||
|
||||
.. 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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
-------
|
||||
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
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(manual_reset), \
|
||||
INIT_ID(mapping), \
|
||||
INIT_ID(mask), \
|
||||
INIT_ID(match), \
|
||||
INIT_ID(max_length), \
|
||||
INIT_ID(maxdigits), \
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
15
Lib/stat.py
15
Lib/stat.py
|
|
@ -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 *
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -210,6 +210,7 @@ Médéric Boquien
|
|||
Matias Bordese
|
||||
Jonas Borgström
|
||||
Jurjen Bos
|
||||
Jeffrey Bosboom
|
||||
Peter Bosch
|
||||
Dan Boswell
|
||||
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;
|
||||
}
|
||||
|
||||
#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]*/
|
||||
|
|
|
|||
|
|
@ -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
6
configure
generated
vendored
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 \
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue