diff --git a/Doc/library/os.rst b/Doc/library/os.rst index c3ed6a976926fd..ef08e1bfa51a6f 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -3451,7 +3451,10 @@ features: .. attribute:: stx_mnt_id - Mount ID. + Mount identifier. + + Equal to ``None`` if :data:`STATX_MNT_ID` is missing from + :attr:`~os.statx_result.stx_mask`. .. availability:: Linux >= 4.11 with glibc >= 2.28 and build-time kernel userspace API headers >= 5.8. @@ -3460,6 +3463,9 @@ features: Direct I/O memory buffer alignment requirement. + Equal to ``None`` if :data:`STATX_DIOALIGN` is missing from + :attr:`~os.statx_result.stx_mask`. + .. availability:: Linux >= 4.11 with glibc >= 2.28 and build-time kernel userspace API headers >= 6.1. @@ -3467,12 +3473,18 @@ features: Direct I/O file offset alignment requirement. + Equal to ``None`` if :data:`STATX_DIOALIGN` is missing from + :attr:`~os.statx_result.stx_mask`. + .. availability:: Linux >= 4.11 with glibc >= 2.28 and build-time kernel userspace API headers >= 6.1. .. attribute:: stx_subvol - Subvolume ID. + Subvolume identifier. + + Equal to ``None`` if :data:`STATX_SUBVOL` is missing from + :attr:`~os.statx_result.stx_mask`. .. availability:: Linux >= 4.11 with glibc >= 2.28 and build-time kernel userspace API headers >= 6.10. @@ -3481,6 +3493,9 @@ features: Minimum size for direct I/O with torn-write protection. + Equal to ``None`` if :data:`STATX_WRITE_ATOMIC` is missing from + :attr:`~os.statx_result.stx_mask`. + .. availability:: Linux >= 4.11 with glibc >= 2.28 and build-time kernel userspace API headers >= 6.11. @@ -3488,6 +3503,9 @@ features: Maximum size for direct I/O with torn-write protection. + Equal to ``None`` if :data:`STATX_WRITE_ATOMIC` is missing from + :attr:`~os.statx_result.stx_mask`. + .. availability:: Linux >= 4.11 with glibc >= 2.28 and build-time kernel userspace API headers >= 6.11. @@ -3495,6 +3513,9 @@ features: Maximum optimized size for direct I/O with torn-write protection. + Equal to ``None`` if :data:`STATX_WRITE_ATOMIC` is missing from + :attr:`~os.statx_result.stx_mask`. + .. availability:: Linux >= 4.11 with glibc >= 2.28 and build-time kernel userspace API headers >= 6.16. @@ -3502,6 +3523,9 @@ features: Maximum iovecs for direct I/O with torn-write protection. + Equal to ``None`` if :data:`STATX_WRITE_ATOMIC` is missing from + :attr:`~os.statx_result.stx_mask`. + .. availability:: Linux >= 4.11 with glibc >= 2.28 and build-time kernel userspace API headers >= 6.11. @@ -3509,6 +3533,9 @@ features: Direct I/O file offset alignment requirement for reads. + Equal to ``None`` if :data:`STATX_DIO_READ_ALIGN` is missing from + :attr:`~os.statx_result.stx_mask`. + .. availability:: Linux >= 4.11 with glibc >= 2.28 and build-time kernel userspace API headers >= 6.14. diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 1338be22e18678..3a96294015ded6 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -3319,6 +3319,8 @@ typedef struct { struct statx stx; } Py_statx_result; +#define Py_statx_result_CAST(op) _Py_CAST(Py_statx_result*, (op)) + #define M(attr, type, offset, doc) \ {attr, type, offset, Py_READONLY, PyDoc_STR(doc)} #define MM(attr, type, member, doc) \ @@ -3330,85 +3332,166 @@ 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 + {NULL}, +}; + +#undef MX +#undef MM +#undef M + + +#define STATX_GET_UINT(ATTR, MEMBER, MASK) \ + static PyObject* \ + pystatx_result_get_##ATTR(PyObject *op, void *Py_UNUSED(context)) \ + { \ + Py_statx_result *self = Py_statx_result_CAST(op); \ + if (!(self->stx.stx_mask & MASK)) { \ + Py_RETURN_NONE; \ + } \ + unsigned long value = self->stx.MEMBER; \ + return PyLong_FromUnsignedLong(value); \ + } + +STATX_GET_UINT(st_uid, stx_uid, STATX_UID) +STATX_GET_UINT(st_gid, stx_gid, STATX_GID) +STATX_GET_UINT(st_nlink, stx_nlink, STATX_NLINK) #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"), +STATX_GET_UINT(stx_dio_mem_align, stx_dio_mem_align, STATX_DIOALIGN) +STATX_GET_UINT(stx_dio_offset_align, stx_dio_offset_align, STATX_DIOALIGN) #endif -#ifdef STATX_SUBVOL - MM(stx_subvol, Py_T_ULONGLONG, subvol, "subvolume ID"), +#ifdef STATX_DIO_READ_ALIGN +STATX_GET_UINT(stx_dio_read_offset_align, stx_dio_read_offset_align, + STATX_DIO_READ_ALIGN) #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_segments_max, Py_T_UINT, atomic_write_segments_max, - "maximum iovecs for direct I/O with torn-write protection"), +STATX_GET_UINT(stx_atomic_write_unit_min, stx_atomic_write_unit_min, + STATX_WRITE_ATOMIC) +STATX_GET_UINT(stx_atomic_write_unit_max, stx_atomic_write_unit_max, + STATX_WRITE_ATOMIC) +STATX_GET_UINT(stx_atomic_write_segments_max, stx_atomic_write_segments_max, + STATX_WRITE_ATOMIC) #endif #ifdef HAVE_STRUCT_STATX_STX_ATOMIC_WRITE_UNIT_MAX_OPT - 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"), +STATX_GET_UINT(stx_atomic_write_unit_max_opt, stx_atomic_write_unit_max_opt, + STATX_WRITE_ATOMIC) #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"), + + +#define STATX_GET_ULONGLONG(ATTR, MEMBER, MASK) \ + static PyObject* \ + pystatx_result_get_##ATTR(PyObject *op, void *Py_UNUSED(context)) \ + { \ + Py_statx_result *self = Py_statx_result_CAST(op); \ + if (!(self->stx.stx_mask & MASK)) { \ + Py_RETURN_NONE; \ + } \ + unsigned long long value = self->stx.MEMBER; \ + return PyLong_FromUnsignedLongLong(value); \ + } + +STATX_GET_ULONGLONG(st_blocks, stx_blocks, STATX_BLOCKS) +STATX_GET_ULONGLONG(st_ino, stx_ino, STATX_INO) +STATX_GET_ULONGLONG(st_size, stx_size, STATX_SIZE) +#ifdef STATX_MNT_ID +STATX_GET_ULONGLONG(stx_mnt_id, stx_mnt_id, STATX_MNT_ID) +#endif +#ifdef STATX_SUBVOL +STATX_GET_ULONGLONG(stx_subvol, stx_subvol, STATX_SUBVOL) #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); -} +#define STATX_GET_DOUBLE(ATTR, MEMBER, MASK) \ + static PyObject* \ + pystatx_result_get_##ATTR(PyObject *op, void *Py_UNUSED(context)) \ + { \ + Py_statx_result *self = Py_statx_result_CAST(op); \ + if (!(self->stx.stx_mask & MASK)) { \ + Py_RETURN_NONE; \ + } \ + double sec = self->MEMBER; \ + return PyFloat_FromDouble(sec); \ + } + +STATX_GET_DOUBLE(st_atime, atime_sec, STATX_ATIME) +STATX_GET_DOUBLE(st_birthtime, btime_sec, STATX_BTIME) +STATX_GET_DOUBLE(st_ctime, ctime_sec, STATX_CTIME) +STATX_GET_DOUBLE(st_mtime, mtime_sec, STATX_MTIME) + +#define STATX_GET_NSEC(ATTR, MEMBER, MASK) \ + static PyObject* \ + pystatx_result_get_##ATTR(PyObject *op, void *context) \ + { \ + Py_statx_result *self = Py_statx_result_CAST(op); \ + if (!(self->stx.stx_mask & MASK)) { \ + Py_RETURN_NONE; \ + } \ + struct statx_timestamp *ts = &self->stx.MEMBER; \ + _posixstate *state = PyType_GetModuleState(Py_TYPE(op)); \ + assert(state != NULL); \ + return stat_nanosecond_timestamp(state, ts->tv_sec, ts->tv_nsec); \ + } + +STATX_GET_NSEC(st_atime_ns, stx_atime, STATX_ATIME) +STATX_GET_NSEC(st_birthtime_ns, stx_btime, STATX_BTIME) +STATX_GET_NSEC(st_ctime_ns, stx_ctime, STATX_CTIME) +STATX_GET_NSEC(st_mtime_ns, stx_mtime, STATX_MTIME) -/* 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))} +#define G(attr, doc) \ + {#attr, pystatx_result_get_##attr, NULL, PyDoc_STR(doc), NULL} 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"), + G(st_nlink, "number of hard links"), + G(st_uid, "user ID of owner"), + G(st_gid, "group ID of owner"), + G(st_ino, "inode"), + G(st_size, "total size, in bytes"), + G(st_blocks, "number of blocks allocated"), + G(st_atime, "time of last access"), + G(st_atime_ns, "time of last access in nanoseconds"), + G(st_birthtime, "time of creation"), + G(st_birthtime_ns, "time of creation in nanoseconds"), + G(st_ctime, "time of last change"), + G(st_ctime_ns, "time of last change in nanoseconds"), + G(st_mtime, "time of last modification"), + G(st_mtime_ns, "time of last modification in nanoseconds"), +#ifdef STATX_MNT_ID + G(stx_mnt_id, "mount ID"), +#endif +#ifdef STATX_DIOALIGN + G(stx_dio_mem_align, "direct I/O memory buffer alignment"), + G(stx_dio_offset_align, "direct I/O file offset alignment"), +#endif +#ifdef STATX_SUBVOL + G(stx_subvol, "subvolume ID"), +#endif +#ifdef STATX_WRITE_ATOMIC + G(stx_atomic_write_unit_min, + "minimum size for direct I/O with torn-write protection"), + G(stx_atomic_write_unit_max, + "maximum size for direct I/O with torn-write protection"), + G(stx_atomic_write_segments_max, + "maximum iovecs for direct I/O with torn-write protection"), +#endif +#ifdef STATX_DIO_READ_ALIGN + G(stx_dio_read_offset_align, "direct I/O file offset alignment for reads"), +#endif +#ifdef HAVE_STRUCT_STATX_STX_ATOMIC_WRITE_UNIT_MAX_OPT + G(stx_atomic_write_unit_max_opt, + "maximum optimized size for direct I/O with torn-write protection"), +#endif {NULL}, }; -#undef GM +#undef G static PyObject * pystatx_result_repr(PyObject *op) @@ -3446,24 +3529,19 @@ pystatx_result_repr(PyObject *op) 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 (o == Py_None) { + continue; + } + + WRITE_ASCII(", "); + WRITE_ASCII(d->name); + WRITE_ASCII("="); if (PyUnicodeWriter_WriteRepr(writer, o) < 0) { Py_DECREF(o); goto error;