diff --git a/Doc/deprecations/pending-removal-in-3.20.rst b/Doc/deprecations/pending-removal-in-3.20.rst index c86979c8ff91e98..21a561e7952afdc 100644 --- a/Doc/deprecations/pending-removal-in-3.20.rst +++ b/Doc/deprecations/pending-removal-in-3.20.rst @@ -8,6 +8,7 @@ Pending removal in Python 3.20 - :mod:`argparse` - :mod:`csv` - :mod:`!ctypes.macholib` + - :mod:`imaplib` - :mod:`ipaddress` - :mod:`json` - :mod:`logging` (``__date__`` also deprecated) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index c3ed6a976926fdd..ef08e1bfa51a6f5 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/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index 29f82fc12da1cc4..882b05e87319fa7 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -16,9 +16,8 @@ Objects, values and types single: data :dfn:`Objects` are Python's abstraction for data. All data in a Python program -is represented by objects or by relations between objects. (In a sense, and in -conformance to Von Neumann's model of a "stored program computer", code is also -represented by objects.) +is represented by objects or by relations between objects. Even code is +represented by objects. .. index:: pair: built-in function; id @@ -29,9 +28,6 @@ represented by objects.) single: mutable object single: immutable object -.. XXX it *is* now possible in some cases to change an object's - type, under certain controlled conditions - Every object has an identity, a type and a value. An object's *identity* never changes once it has been created; you may think of it as the object's address in memory. The :keyword:`is` operator compares the identity of two objects; the @@ -1185,6 +1181,7 @@ Special attributes single: __module__ (class attribute) single: __dict__ (class attribute) single: __bases__ (class attribute) + single: __base__ (class attribute) single: __doc__ (class attribute) single: __annotations__ (class attribute) single: __annotate__ (class attribute) @@ -1219,6 +1216,13 @@ Special attributes In most cases, for a class defined as ``class X(A, B, C)``, ``X.__bases__`` will be exactly equal to ``(A, B, C)``. + * - .. attribute:: type.__base__ + - .. impl-detail:: + + The single base class in the inheritance chain that is responsible + for the memory layout of instances. This attribute corresponds to + :c:member:`~PyTypeObject.tp_base` at the C level. + * - .. attribute:: type.__doc__ - The class's documentation string, or ``None`` if undefined. Not inherited by subclasses. diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index d3ae7c21a0358bc..231ae565192efe6 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -682,6 +682,8 @@ mimetypes --------- * Add ``application/toml``. (Contributed by Gil Forcada in :gh:`139959`.) +* Rename ``application/x-texinfo`` to ``application/texinfo``. + (Contributed by Charlie Lin in :gh:`140165`) pathlib @@ -825,6 +827,7 @@ New deprecations - :mod:`argparse` - :mod:`csv` - :mod:`!ctypes.macholib` + - :mod:`imaplib` - :mod:`ipaddress` - :mod:`json` - :mod:`logging` (``__date__`` also deprecated) diff --git a/Lib/imaplib.py b/Lib/imaplib.py index cbe129b3e7c2145..c176736548188c3 100644 --- a/Lib/imaplib.py +++ b/Lib/imaplib.py @@ -21,8 +21,6 @@ # GET/SETANNOTATION contributed by Tomas Lindroos June 2005. # IDLE contributed by Forest August 2024. -__version__ = "2.60" - import binascii, errno, random, re, socket, subprocess, sys, time, calendar from datetime import datetime, timezone, timedelta from io import DEFAULT_BUFFER_SIZE @@ -247,7 +245,6 @@ def _connect(self): self._cmd_log_idx = 0 self._cmd_log = {} # Last '_cmd_log_len' interactions if self.debug >= 1: - self._mesg('imaplib version %s' % __version__) self._mesg('new IMAP4 connection, tag=%s' % self.tagpre) self.welcome = self._get_response() @@ -1965,3 +1962,12 @@ def run(cmd, args): ''' % sys.argv[0]) raise + + +def __getattr__(name): + if name == "__version__": + from warnings import _deprecated + + _deprecated("__version__", remove=(3, 20)) + return "2.60" # Do not change + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") diff --git a/Lib/mimetypes.py b/Lib/mimetypes.py index 6c0efb671975d4d..48a9f430d45262d 100644 --- a/Lib/mimetypes.py +++ b/Lib/mimetypes.py @@ -500,6 +500,8 @@ def _default_mime_types(): '.ps' : 'application/postscript', '.ai' : 'application/postscript', '.eps' : 'application/postscript', + '.texi' : 'application/texinfo', + '.texinfo': 'application/texinfo', '.toml' : 'application/toml', '.trig' : 'application/trig', '.m3u' : 'application/vnd.apple.mpegurl', @@ -549,8 +551,6 @@ def _default_mime_types(): '.tar' : 'application/x-tar', '.tcl' : 'application/x-tcl', '.tex' : 'application/x-tex', - '.texi' : 'application/x-texinfo', - '.texinfo': 'application/x-texinfo', '.roff' : 'application/x-troff', '.t' : 'application/x-troff', '.tr' : 'application/x-troff', diff --git a/Lib/test/test_imaplib.py b/Lib/test/test_imaplib.py index 999e6e69ab41eaf..430fa71fa29f59c 100644 --- a/Lib/test/test_imaplib.py +++ b/Lib/test/test_imaplib.py @@ -1117,5 +1117,15 @@ def test_ssl_verified(self): client.shutdown() +class TestModule(unittest.TestCase): + def test_deprecated__version__(self): + with self.assertWarnsRegex( + DeprecationWarning, + "'__version__' is deprecated and slated for removal in Python 3.20", + ) as cm: + getattr(imaplib, "__version__") + self.assertEqual(cm.filename, __file__) + + if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_mimetypes.py b/Lib/test/test_mimetypes.py index a29815bf49b1e7d..746984ec0ca9dfc 100644 --- a/Lib/test/test_mimetypes.py +++ b/Lib/test/test_mimetypes.py @@ -230,6 +230,7 @@ def check_extensions(): ("application/gzip", ".gz"), ("application/ogg", ".ogx"), ("application/postscript", ".ps"), + ("application/texinfo", ".texi"), ("application/toml", ".toml"), ("application/vnd.apple.mpegurl", ".m3u"), ("application/vnd.ms-excel", ".xls"), @@ -247,7 +248,6 @@ def check_extensions(): ("application/x-debian-package", ".deb"), ("application/x-httpd-php", ".php"), ("application/x-rpm", ".rpm"), - ("application/x-texinfo", ".texi"), ("application/x-troff", ".roff"), ("application/xml", ".xsl"), ("application/yaml", ".yaml"), diff --git a/Lib/test/test_tokenize.py b/Lib/test/test_tokenize.py index 8fdd03f347b632c..d274726eed2e659 100644 --- a/Lib/test/test_tokenize.py +++ b/Lib/test/test_tokenize.py @@ -1495,6 +1495,61 @@ def test_cookie_second_line_noncommented_first_line(self): expected = [b"print('\xc2\xa3')\n"] self.assertEqual(consumed_lines, expected) + def test_first_non_utf8_coding_line(self): + lines = ( + b'#coding:iso-8859-15 \xa4\n', + b'print(something)\n' + ) + encoding, consumed_lines = tokenize.detect_encoding(self.get_readline(lines)) + self.assertEqual(encoding, 'iso-8859-15') + self.assertEqual(consumed_lines, list(lines[:1])) + + def test_first_utf8_coding_line_error(self): + lines = ( + b'#coding:ascii \xc3\xa4\n', + b'print(something)\n' + ) + with self.assertRaises(SyntaxError): + tokenize.detect_encoding(self.get_readline(lines)) + + def test_second_non_utf8_coding_line(self): + lines = ( + b'#!/usr/bin/python\n', + b'#coding:iso-8859-15 \xa4\n', + b'print(something)\n' + ) + encoding, consumed_lines = tokenize.detect_encoding(self.get_readline(lines)) + self.assertEqual(encoding, 'iso-8859-15') + self.assertEqual(consumed_lines, list(lines[:2])) + + def test_second_utf8_coding_line_error(self): + lines = ( + b'#!/usr/bin/python\n', + b'#coding:ascii \xc3\xa4\n', + b'print(something)\n' + ) + with self.assertRaises(SyntaxError): + tokenize.detect_encoding(self.get_readline(lines)) + + def test_non_utf8_shebang(self): + lines = ( + b'#!/home/\xa4/bin/python\n', + b'#coding:iso-8859-15\n', + b'print(something)\n' + ) + encoding, consumed_lines = tokenize.detect_encoding(self.get_readline(lines)) + self.assertEqual(encoding, 'iso-8859-15') + self.assertEqual(consumed_lines, list(lines[:2])) + + def test_utf8_shebang_error(self): + lines = ( + b'#!/home/\xc3\xa4/bin/python\n', + b'#coding:ascii\n', + b'print(something)\n' + ) + with self.assertRaises(SyntaxError): + tokenize.detect_encoding(self.get_readline(lines)) + def test_cookie_second_line_empty_first_line(self): lines = ( b'\n', @@ -1548,6 +1603,28 @@ def test_double_coding_utf8(self): self.assertEqual(encoding, 'utf-8') self.assertEqual(consumed_lines, list(lines[:1])) + def test_nul_in_first_coding_line(self): + lines = ( + b'#coding:iso8859-15\x00\n', + b'\n', + b'\n', + b'print(something)\n' + ) + with self.assertRaisesRegex(SyntaxError, + "source code cannot contain null bytes"): + tokenize.detect_encoding(self.get_readline(lines)) + + def test_nul_in_second_coding_line(self): + lines = ( + b'#!/usr/bin/python\n', + b'#coding:iso8859-15\x00\n', + b'\n', + b'print(something)\n' + ) + with self.assertRaisesRegex(SyntaxError, + "source code cannot contain null bytes"): + tokenize.detect_encoding(self.get_readline(lines)) + def test_latin1_normalization(self): # See get_normal_name() in Parser/tokenizer/helpers.c. encodings = ("latin-1", "iso-8859-1", "iso-latin-1", "latin-1-unix", diff --git a/Lib/tokenize.py b/Lib/tokenize.py index 7e71755068e1df3..1f31258ce361c94 100644 --- a/Lib/tokenize.py +++ b/Lib/tokenize.py @@ -36,7 +36,7 @@ from token import EXACT_TOKEN_TYPES import _tokenize -cookie_re = re.compile(r'^[ \t\f]*#.*?coding[:=][ \t]*([-\w.]+)', re.ASCII) +cookie_re = re.compile(br'^[ \t\f]*#.*?coding[:=][ \t]*([-\w.]+)', re.ASCII) blank_re = re.compile(br'^[ \t\f]*(?:[#\r\n]|$)', re.ASCII) import token @@ -385,22 +385,23 @@ def read_or_stop(): except StopIteration: return b'' - def find_cookie(line): + def check(line, encoding): + # Check if the line matches the encoding. + if 0 in line: + raise SyntaxError("source code cannot contain null bytes") try: - # Decode as UTF-8. Either the line is an encoding declaration, - # in which case it should be pure ASCII, or it must be UTF-8 - # per default encoding. - line_string = line.decode('utf-8') + line.decode(encoding) except UnicodeDecodeError: msg = "invalid or missing encoding declaration" if filename is not None: msg = '{} for {!r}'.format(msg, filename) raise SyntaxError(msg) - match = cookie_re.match(line_string) + def find_cookie(line): + match = cookie_re.match(line) if not match: return None - encoding = _get_normal_name(match.group(1)) + encoding = _get_normal_name(match.group(1).decode()) try: codec = lookup(encoding) except LookupError: @@ -433,18 +434,23 @@ def find_cookie(line): encoding = find_cookie(first) if encoding: + check(first, encoding) return encoding, [first] if not blank_re.match(first): + check(first, default) return default, [first] second = read_or_stop() if not second: + check(first, default) return default, [first] encoding = find_cookie(second) if encoding: + check(first + second, encoding) return encoding, [first, second] + check(first + second, default) return default, [first, second] diff --git a/Misc/NEWS.d/next/Library/2025-09-30-12-52-54.gh-issue-63161.mECM1A.rst b/Misc/NEWS.d/next/Library/2025-09-30-12-52-54.gh-issue-63161.mECM1A.rst new file mode 100644 index 000000000000000..3daed20d099a8a6 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-09-30-12-52-54.gh-issue-63161.mECM1A.rst @@ -0,0 +1,3 @@ +Fix :func:`tokenize.detect_encoding`. Support non-UTF-8 shebang and comments +if non-UTF-8 encoding is specified. Detect decoding error for non-UTF-8 +encoding. Detect null bytes in source code. diff --git a/Misc/NEWS.d/next/Library/2025-10-15-15-10-34.gh-issue-140166.NtxRez.rst b/Misc/NEWS.d/next/Library/2025-10-15-15-10-34.gh-issue-140166.NtxRez.rst new file mode 100644 index 000000000000000..c140db9dcd51507 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-10-15-15-10-34.gh-issue-140166.NtxRez.rst @@ -0,0 +1 @@ +:mod:`mimetypes`: Per the `IANA assignment `_, update the MIME type for the ``.texi`` and ``.texinfo`` file formats to ``application/texinfo``, instead of ``application/x-texinfo``. diff --git a/Misc/NEWS.d/next/Library/2025-10-18-14-30-21.gh-issue-76007.peEgcr.rst b/Misc/NEWS.d/next/Library/2025-10-18-14-30-21.gh-issue-76007.peEgcr.rst new file mode 100644 index 000000000000000..be56b2ca6a1c97f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-10-18-14-30-21.gh-issue-76007.peEgcr.rst @@ -0,0 +1 @@ +Deprecate ``__version__`` from a :mod:`imaplib`. Patch by Hugo van Kemenade. diff --git a/Modules/_testinternalcapi/test_lock.c b/Modules/_testinternalcapi/test_lock.c index 8d8cb992b0e07fc..ded76ca9fe68194 100644 --- a/Modules/_testinternalcapi/test_lock.c +++ b/Modules/_testinternalcapi/test_lock.c @@ -91,7 +91,8 @@ test_lock_two_threads(PyObject *self, PyObject *obj) } while (v != 3 && iters < 200); // both the "locked" and the "has parked" bits should be set - assert(test_data.m._bits == 3); + v = _Py_atomic_load_uint8_relaxed(&test_data.m._bits); + assert(v == 3); PyMutex_Unlock(&test_data.m); PyEvent_Wait(&test_data.done); diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index e9a3b6d29b88a65..1cfb068680cc4b7 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;