Skip to content

Commit 99c9efd

Browse files
committed
bpo-39533: Use statx on Linux 4.11 and later in os.stat
The libc statx wrapper is required at build time, but weakly linked, and posix_do_stat will fall back to calling stat if statx is not available at runtime due to an old libc or an old kernel.
1 parent 06e347b commit 99c9efd

File tree

6 files changed

+224
-4
lines changed

6 files changed

+224
-4
lines changed

Doc/library/os.rst

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3227,6 +3227,9 @@ features:
32273227

32283228
.. versionchanged:: 3.12
32293229
``st_birthtime`` is now available on Windows.
3230+
.. versionchanged:: next
3231+
``st_birthtime`` is now available on Linux kernel 4.11 and later when
3232+
supported by the filesystem.
32303233

32313234
.. attribute:: st_birthtime_ns
32323235

@@ -3276,6 +3279,34 @@ features:
32763279

32773280
User defined flags for file.
32783281

3282+
.. attribute:: st_attributes
3283+
3284+
Linux file attributes.
3285+
See the :const:`!STATX_ATTR* <stat.STATX_ATTR_COMPRESSED>`
3286+
constants in the :mod:`stat` module.
3287+
3288+
.. versionadded: next
3289+
3290+
.. attribute:: st_attributes_mask
3291+
3292+
Linux file attributes supported by the filesystem containing the file.
3293+
3294+
.. versionadded: next
3295+
3296+
.. attribute:: st_mnt_id
3297+
3298+
Mount ID of the mount containing the file, corresponding to the first
3299+
field in ``/proc/self/mountinfo``.
3300+
3301+
.. versionadded: next
3302+
3303+
.. attribute:: stx_subvol
3304+
3305+
ID for the subvolume containing the file, or None if the filesystem does
3306+
not support subvolumes.
3307+
3308+
.. versionadded: next
3309+
32793310
On other Unix systems (such as FreeBSD), the following attributes may be
32803311
available (but may be only filled out if root tries to use them):
32813312

@@ -3367,6 +3398,11 @@ features:
33673398

33683399
Added the :attr:`st_birthtime` member on Windows.
33693400

3401+
.. versionchanged:: next
3402+
Added the :attr:`st_birthtime`, :attr:`st_attributes`,
3403+
:attr:`st_attributes_mask`, :attr:`st_mnt_id`, and :attr:`st_subvol`
3404+
members on Linux.
3405+
33703406

33713407
.. function:: statvfs(path)
33723408

Doc/library/stat.rst

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,3 +493,22 @@ constants, but are not an exhaustive list.
493493
IO_REPARSE_TAG_APPEXECLINK
494494

495495
.. versionadded:: 3.8
496+
497+
On Linux, the following constants are available for comparing against the
498+
``st_attributes`` and ``st_attributes_mask`` members returned by
499+
:func:`os.stat`. See the `statx(2) man page
500+
<https://man.archlinux.org/man/statx.2#File_attributes>` for more detail on the
501+
meaning of these constants.
502+
503+
.. data:: STATX_ATTR_COMPRESSED
504+
STATX_ATTR_IMMUTABLE
505+
STATX_ATTR_APPEND
506+
STATX_ATTR_NODUMP
507+
STATX_ATTR_ENCRYPTED
508+
STATX_ATTR_AUTOMOUNT
509+
STATX_ATTR_MOUNT_ROOT
510+
STATX_ATTR_VERITY
511+
STATX_ATTR_DAX
512+
STATX_ATTR_WRITE_ATOMIC
513+
514+
.. versionadded:: next

Lib/stat.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,21 @@ def filemode(mode):
200200
FILE_ATTRIBUTE_VIRTUAL = 65536
201201

202202

203+
# Linux STATX_ATTR constants for interpreting os.stat()'s
204+
# "st_attributes" and "st_attributes_mask" members
205+
206+
STATX_ATTR_COMPRESSED = 0x00000004
207+
STATX_ATTR_IMMUTABLE = 0x00000010
208+
STATX_ATTR_APPEND = 0x00000020
209+
STATX_ATTR_NODUMP = 0x00000040
210+
STATX_ATTR_ENCRYPTED = 0x00000800
211+
STATX_ATTR_AUTOMOUNT = 0x00001000
212+
STATX_ATTR_MOUNT_ROOT = 0x00002000
213+
STATX_ATTR_VERITY = 0x00100000
214+
STATX_ATTR_DAX = 0x00200000
215+
STATX_ATTR_WRITE_ATOMIC = 0x00400000
216+
217+
203218
# If available, use C implementation
204219
try:
205220
from _stat import *

Misc/ACKS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ Médéric Boquien
209209
Matias Bordese
210210
Jonas Borgström
211211
Jurjen Bos
212+
Jeffrey Bosboom
212213
Peter Bosch
213214
Dan Boswell
214215
Eric Bouck

Modules/posixmodule.c

Lines changed: 152 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,18 @@ extern char *ctermid_r(char *);
408408
# define STRUCT_STAT struct stat
409409
#endif
410410

411+
#if defined(__linux__) && defined(STATX_BASIC_STATS)
412+
# pragma weak statx
413+
# define HAVE_LINUX_STATX 1
414+
# define HAVE_LINUX_STATX_RUNTIME (statx != NULL)
415+
/* provide definitions introduced later than statx itself */
416+
# define _Py_STATX_MNT_ID 0x00001000U
417+
# define _Py_STATX_SUBVOL 0x00008000U
418+
# define _Py_STATX_MASK (STATX_BASIC_STATS | STATX_BTIME | _Py_STATX_MNT_ID | _Py_STATX_SUBVOL)
419+
# define _Py_STATX_MNT_ID_OFFSET 144
420+
# define _Py_STATX_SUBVOL_OFFSET 160
421+
#endif
422+
411423

412424
#if !defined(EX_OK) && defined(EXIT_SUCCESS)
413425
# define EX_OK EXIT_SUCCESS
@@ -2353,10 +2365,10 @@ static PyStructSequence_Field stat_result_fields[] = {
23532365
#ifdef HAVE_STRUCT_STAT_ST_GEN
23542366
{"st_gen", "generation number"},
23552367
#endif
2356-
#if defined(HAVE_STRUCT_STAT_ST_BIRTHTIME) || defined(MS_WINDOWS)
2368+
#if defined(HAVE_STRUCT_STAT_ST_BIRTHTIME) || defined(HAVE_LINUX_STATX) || defined(MS_WINDOWS)
23572369
{"st_birthtime", "time of creation"},
23582370
#endif
2359-
#ifdef MS_WINDOWS
2371+
#if defined(HAVE_LINUX_STATX) || defined(MS_WINDOWS)
23602372
{"st_birthtime_ns", "time of creation in nanoseconds"},
23612373
#endif
23622374
#ifdef HAVE_STRUCT_STAT_ST_FILE_ATTRIBUTES
@@ -2367,6 +2379,12 @@ static PyStructSequence_Field stat_result_fields[] = {
23672379
#endif
23682380
#ifdef HAVE_STRUCT_STAT_ST_REPARSE_TAG
23692381
{"st_reparse_tag", "Windows reparse tag"},
2382+
#endif
2383+
#ifdef HAVE_LINUX_STATX
2384+
{"st_attributes", "Linux file attribute bits"},
2385+
{"st_attributes_mask", "Linux file attribute bits supported by this file"},
2386+
{"st_mnt_id", "Linux mount ID for /proc/self/mountinfo"},
2387+
{"st_subvol", "Linux subvolume identifier"},
23702388
#endif
23712389
{0}
23722390
};
@@ -2401,13 +2419,13 @@ static PyStructSequence_Field stat_result_fields[] = {
24012419
#define ST_GEN_IDX ST_FLAGS_IDX
24022420
#endif
24032421

2404-
#if defined(HAVE_STRUCT_STAT_ST_BIRTHTIME) || defined(MS_WINDOWS)
2422+
#if defined(HAVE_STRUCT_STAT_ST_BIRTHTIME) || defined(HAVE_LINUX_STATX) || defined(MS_WINDOWS)
24052423
#define ST_BIRTHTIME_IDX (ST_GEN_IDX+1)
24062424
#else
24072425
#define ST_BIRTHTIME_IDX ST_GEN_IDX
24082426
#endif
24092427

2410-
#ifdef MS_WINDOWS
2428+
#if defined(HAVE_LINUX_STATX) || defined(MS_WINDOWS)
24112429
#define ST_BIRTHTIME_NS_IDX (ST_BIRTHTIME_IDX+1)
24122430
#else
24132431
#define ST_BIRTHTIME_NS_IDX ST_BIRTHTIME_IDX
@@ -2431,6 +2449,13 @@ static PyStructSequence_Field stat_result_fields[] = {
24312449
#define ST_REPARSE_TAG_IDX ST_FSTYPE_IDX
24322450
#endif
24332451

2452+
#ifdef HAVE_LINUX_STATX
2453+
#define ST_ATTRIBUTES_IDX (ST_REPARSE_TAG_IDX+1)
2454+
#define ST_ATTRIBUTES_MASK_IDX (ST_ATTRIBUTES_IDX+1)
2455+
#define ST_MNT_ID_IDX (ST_ATTRIBUTES_MASK_IDX+1)
2456+
#define ST_SUBVOL_IDX (ST_MNT_ID_IDX+1)
2457+
#endif
2458+
24342459
static PyStructSequence_Desc stat_result_desc = {
24352460
"stat_result", /* name */
24362461
stat_result__doc__, /* doc */
@@ -2790,6 +2815,99 @@ _pystat_fromstructstat(PyObject *module, STRUCT_STAT *st)
27902815
#undef SET_ITEM
27912816
}
27922817

2818+
#ifdef HAVE_LINUX_STATX
2819+
static PyObject*
2820+
_pystat_fromstructstatx(PyObject *module, struct statx *st)
2821+
{
2822+
assert(!PyErr_Occurred());
2823+
2824+
PyObject *StatResultType = get_posix_state(module)->StatResultType;
2825+
PyObject *v = PyStructSequence_New((PyTypeObject *)StatResultType);
2826+
if (v == NULL) {
2827+
return NULL;
2828+
}
2829+
2830+
#define SET_ITEM(pos, expr) \
2831+
do { \
2832+
PyObject *obj = (expr); \
2833+
if (obj == NULL) { \
2834+
goto error; \
2835+
} \
2836+
PyStructSequence_SET_ITEM(v, (pos), obj); \
2837+
} while (0)
2838+
2839+
SET_ITEM(0, PyLong_FromLong((long)st->stx_mode));
2840+
static_assert(sizeof(unsigned long long) >= sizeof(st->stx_ino),
2841+
"statx.stx_ino is larger than unsigned long long");
2842+
SET_ITEM(1, PyLong_FromUnsignedLongLong(st->stx_ino));
2843+
dev_t dev = makedev(st->stx_dev_major, st->stx_dev_minor);
2844+
SET_ITEM(2, _PyLong_FromDev(dev));
2845+
2846+
SET_ITEM(3, PyLong_FromLong((long)st->stx_nlink));
2847+
SET_ITEM(4, _PyLong_FromUid(st->stx_uid));
2848+
SET_ITEM(5, _PyLong_FromGid(st->stx_gid));
2849+
static_assert(sizeof(long long) >= sizeof(st->stx_size),
2850+
"statx.stx_size is larger than long long");
2851+
SET_ITEM(6, PyLong_FromLongLong(st->stx_size));
2852+
2853+
if (fill_time(module, v, 7, 10, 13, st->stx_atime.tv_sec,
2854+
st->stx_atime.tv_nsec) < 0) {
2855+
goto error;
2856+
}
2857+
if (fill_time(module, v, 8, 11, 14, st->stx_mtime.tv_sec,
2858+
st->stx_mtime.tv_nsec) < 0) {
2859+
goto error;
2860+
}
2861+
if (fill_time(module, v, 9, 12, 15, st->stx_ctime.tv_sec,
2862+
st->stx_ctime.tv_nsec) < 0) {
2863+
goto error;
2864+
}
2865+
if (st->stx_mask & STATX_BTIME) {
2866+
if (fill_time(module, v, -1, ST_BIRTHTIME_IDX, ST_BIRTHTIME_NS_IDX,
2867+
st->stx_btime.tv_sec, st->stx_btime.tv_nsec) < 0) {
2868+
goto error;
2869+
}
2870+
}
2871+
else {
2872+
SET_ITEM(ST_BIRTHTIME_IDX, PyFloat_FromDouble(0.0));
2873+
SET_ITEM(ST_BIRTHTIME_NS_IDX, _PyLong_GetZero());
2874+
}
2875+
2876+
SET_ITEM(ST_BLKSIZE_IDX, PyLong_FromLong((long)st->stx_blksize));
2877+
SET_ITEM(ST_BLOCKS_IDX, PyLong_FromLong((long)st->stx_blocks));
2878+
dev_t rdev = makedev(st->stx_rdev_major, st->stx_rdev_minor);
2879+
SET_ITEM(ST_RDEV_IDX, _PyLong_FromDev(rdev));
2880+
2881+
SET_ITEM(ST_ATTRIBUTES_IDX, PyLong_FromUnsignedLong(st->stx_attributes));
2882+
SET_ITEM(ST_ATTRIBUTES_MASK_IDX, PyLong_FromLong(st->stx_attributes_mask));
2883+
2884+
if (st->stx_mask & _Py_STATX_MNT_ID) {
2885+
void* stx_mnt_id = ((unsigned char*)st) + _Py_STATX_MNT_ID_OFFSET;
2886+
SET_ITEM(ST_MNT_ID_IDX, PyLong_FromUnsignedNativeBytes(stx_mnt_id, 8, -1));
2887+
}
2888+
else {
2889+
SET_ITEM(ST_MNT_ID_IDX, Py_None);
2890+
}
2891+
2892+
if (st->stx_mask & _Py_STATX_SUBVOL) {
2893+
void* stx_subvol = ((unsigned char*)st) + _Py_STATX_SUBVOL_OFFSET;
2894+
SET_ITEM(ST_SUBVOL_IDX, PyLong_FromUnsignedNativeBytes(stx_subvol, 8, -1));
2895+
}
2896+
else {
2897+
SET_ITEM(ST_SUBVOL_IDX, Py_None);
2898+
}
2899+
2900+
assert(!PyErr_Occurred());
2901+
return v;
2902+
2903+
error:
2904+
Py_DECREF(v);
2905+
return NULL;
2906+
2907+
#undef SET_ITEM
2908+
}
2909+
#endif /* HAVE_LINUX_STATX */
2910+
27932911
/* POSIX methods */
27942912

27952913

@@ -2814,6 +2932,36 @@ posix_do_stat(PyObject *module, const char *function_name, path_t *path,
28142932
fd_and_follow_symlinks_invalid("stat", path->fd, follow_symlinks))
28152933
return NULL;
28162934

2935+
#ifdef HAVE_LINUX_STATX
2936+
struct statx stx = {};
2937+
static int statx_works = -1;
2938+
if (HAVE_LINUX_STATX_RUNTIME && statx_works != 0) {
2939+
int flags = AT_NO_AUTOMOUNT;
2940+
flags |= follow_symlinks ? 0 : AT_SYMLINK_NOFOLLOW;
2941+
Py_BEGIN_ALLOW_THREADS
2942+
if (path->fd != -1) {
2943+
flags |= AT_EMPTY_PATH;
2944+
result = statx(path->fd, "", flags, _Py_STATX_MASK, &stx);
2945+
}
2946+
else {
2947+
result = statx(dir_fd, path->narrow, flags, _Py_STATX_MASK, &stx);
2948+
}
2949+
Py_END_ALLOW_THREADS
2950+
2951+
if (result == -1) {
2952+
if (statx_works == -1) {
2953+
statx_works = (errno != ENOSYS);
2954+
}
2955+
if (statx_works) {
2956+
return path_error(path);
2957+
}
2958+
}
2959+
else {
2960+
return _pystat_fromstructstatx(module, &stx);
2961+
}
2962+
}
2963+
#endif /* HAVE_LINUX_STATX */
2964+
28172965
Py_BEGIN_ALLOW_THREADS
28182966
if (path->fd != -1)
28192967
result = FSTAT(path->fd, &st);

Tools/c-analyzer/cpython/ignored.tsv

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Python/fileutils.c set_inheritable ioctl_works -
2121
# (set lazily, *after* first init)
2222
# XXX Is this thread-safe?
2323
Modules/posixmodule.c os_dup2_impl dup3_works -
24+
Modules/posixmodule.c posix_do_stat statx_works -
2425

2526
## guards around resource init
2627
Python/thread_pthread.h PyThread__init_thread lib_initialized -

0 commit comments

Comments
 (0)