From 148d2f71fd8b9311f1bb3104648db6c78640eaad Mon Sep 17 00:00:00 2001 From: Cody Maloney Date: Wed, 22 Jan 2025 16:46:31 -0800 Subject: [PATCH 01/23] gh-129205: Add os.readinto API Add a new OS api which will read data directly into a caller provided writeable buffer protocol object. --- Modules/clinic/posixmodule.c.h | 53 +++++++++++++++++++++++++++++++++- Modules/posixmodule.c | 24 +++++++++++++++ 2 files changed, 76 insertions(+), 1 deletion(-) diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index 96bf21dced92f0..e0c6116ce89524 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -7577,6 +7577,57 @@ os_read(PyObject *module, PyObject *const *args, Py_ssize_t nargs) return return_value; } +PyDoc_STRVAR(os_readinto__doc__, +"readinto($module, fd, buffer, /)\n" +"--\n" +"\n" +"Read into a :ref:`buffer protocol ` object from a file descriptor.\n" +"\n" +"The buffer should be mutable and accept bytes. On success, returns the number of\n" +"bytes read. Less bytes may be read than the size of the buffer. Will retry the\n" +"underlying system call when interrupted by a signal. For other errors, the\n" +"system call will not be retried."); + +#define OS_READINTO_METHODDEF \ + {"readinto", _PyCFunction_CAST(os_readinto), METH_FASTCALL, os_readinto__doc__}, + +static Py_ssize_t +os_readinto_impl(PyObject *module, int fd, Py_buffer *buffer); + +static PyObject * +os_readinto(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + int fd; + Py_buffer buffer = {NULL, NULL}; + Py_ssize_t _return_value; + + if (!_PyArg_CheckPositional("readinto", nargs, 2, 2)) { + goto exit; + } + fd = PyLong_AsInt(args[0]); + if (fd == -1 && PyErr_Occurred()) { + goto exit; + } + if (PyObject_GetBuffer(args[1], &buffer, PyBUF_WRITABLE) < 0) { + _PyArg_BadArgument("readinto", "argument 2", "read-write bytes-like object", args[1]); + goto exit; + } + _return_value = os_readinto_impl(module, fd, &buffer); + if ((_return_value == -1) && PyErr_Occurred()) { + goto exit; + } + return_value = PyLong_FromSsize_t(_return_value); + +exit: + /* Cleanup for buffer */ + if (buffer.obj) { + PyBuffer_Release(&buffer); + } + + return return_value; +} + #if defined(HAVE_READV) PyDoc_STRVAR(os_readv__doc__, @@ -13140,4 +13191,4 @@ os__emscripten_debugger(PyObject *module, PyObject *Py_UNUSED(ignored)) #ifndef OS__EMSCRIPTEN_DEBUGGER_METHODDEF #define OS__EMSCRIPTEN_DEBUGGER_METHODDEF #endif /* !defined(OS__EMSCRIPTEN_DEBUGGER_METHODDEF) */ -/*[clinic end generated code: output=34cb96bd07bcef90 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=c2f04dda4ea1a399 input=a9049054013a1b77]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index fb9e55a57703fc..791f8dc73df829 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -11433,6 +11433,29 @@ os_read_impl(PyObject *module, int fd, Py_ssize_t length) return buffer; } +/*[clinic input] +os.readinto -> Py_ssize_t + fd: int + buffer: Py_buffer(accept={rwbuffer}) + / + +Read into a :ref:`buffer protocol ` object from a file descriptor. + +The buffer should be mutable and accept bytes. On success, returns the number of +bytes read. Less bytes may be read than the size of the buffer. Will retry the +underlying system call when interrupted by a signal. For other errors, the +system call will not be retried. +[clinic start generated code]*/ + +static Py_ssize_t +os_readinto_impl(PyObject *module, int fd, Py_buffer *buffer) +/*[clinic end generated code: output=8091a3513c683a80 input=810c820f4d9b1c6b]*/ +{ + // Cap to max read size to prevent overflow in cast to size_t for _Py_read. + size_t length = Py_MIN(buffer->len, _PY_READ_MAX); + return _Py_read(fd, buffer->buf, length); +} + #if (defined(HAVE_SENDFILE) && (defined(__FreeBSD__) || defined(__DragonFly__) \ || defined(__APPLE__))) \ || defined(HAVE_READV) || defined(HAVE_PREADV) || defined (HAVE_PREADV2) \ @@ -16973,6 +16996,7 @@ static PyMethodDef posix_methods[] = { OS_LOCKF_METHODDEF OS_LSEEK_METHODDEF OS_READ_METHODDEF + OS_READINTO_METHODDEF OS_READV_METHODDEF OS_PREAD_METHODDEF OS_PREADV_METHODDEF From 3b34285ca6896f494512b005fb98dd43aac18b8e Mon Sep 17 00:00:00 2001 From: Cody Maloney Date: Wed, 22 Jan 2025 16:55:03 -0800 Subject: [PATCH 02/23] Add blurb --- .../next/Library/2025-01-22-16-54-25.gh-issue-129205.FMqrUt.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2025-01-22-16-54-25.gh-issue-129205.FMqrUt.rst diff --git a/Misc/NEWS.d/next/Library/2025-01-22-16-54-25.gh-issue-129205.FMqrUt.rst b/Misc/NEWS.d/next/Library/2025-01-22-16-54-25.gh-issue-129205.FMqrUt.rst new file mode 100644 index 00000000000000..5effc59c2f1c93 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-01-22-16-54-25.gh-issue-129205.FMqrUt.rst @@ -0,0 +1 @@ +Add :meth:`os.readinto` to read into a :ref:`buffer protocol ` from a file descriptor. From a56f3370f7ca169ee5596e34e14f7dcb3ab93c95 Mon Sep 17 00:00:00 2001 From: Cody Maloney Date: Wed, 22 Jan 2025 17:49:49 -0800 Subject: [PATCH 03/23] Add tests --- Lib/test/_test_eintr.py | 31 +++++++++++++++++++ Lib/test/test_os.py | 68 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+) diff --git a/Lib/test/_test_eintr.py b/Lib/test/_test_eintr.py index 493932d6c6d441..045c8798b46de8 100644 --- a/Lib/test/_test_eintr.py +++ b/Lib/test/_test_eintr.py @@ -152,6 +152,37 @@ def test_read(self): self.assertEqual(data, os.read(rd, len(data))) self.assertEqual(proc.wait(), 0) + def test_readinto(self): + rd, wr = os.pipe() + self.addCleanup(os.close, rd) + # wr closed explicitly by parent + + # the payload below are smaller than PIPE_BUF, hence the writes will be + # atomic + datas = [b"hello", b"world", b"spam"] + bufs = [bytearray(5), bytearray(5), bytearray(4)] + + code = '\n'.join(( + 'import os, sys, time', + '', + 'wr = int(sys.argv[1])', + 'datas = %r' % datas, + 'sleep_time = %r' % self.sleep_time, + '', + 'for data in datas:', + ' # let the parent block on read()', + ' time.sleep(sleep_time)', + ' os.write(wr, data)', + )) + + proc = self.subprocess(code, str(wr), pass_fds=[wr]) + with kill_on_error(proc): + os.close(wr) + for data, buffer in zip(datas, bufs): + os.readinto(rd, buffer) + self.assertEqual(data, buffer) + self.assertEqual(proc.wait(), 0) + def test_write(self): rd, wr = os.pipe() self.addCleanup(os.close, wr) diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index d2c4dff3c9a0e5..36674ae88711e7 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -230,6 +230,47 @@ def test_read(self): self.assertEqual(type(s), bytes) self.assertEqual(s, b"spam") + def test_readinto(self): + with open(os_helper.TESTFN, "w+b") as fobj: + fobj.write(b"spam") + fobj.flush() + fd = fobj.fileno() + os.lseek(fd, 0, 0) + buffer = bytearray(8) + s = os.readinto(fd, buffer) + self.assertEqual(type(s), int) + self.assertEqual(s, 4) + # Should overwrite the first 4 bytes of the buffer. + self.assertEqual(bytes(buffer), b"spam\0\0\0\0") + + # Readinto at EOF shold return 0 and not touch buffer + buffer[:] = b"notspam\0" + s = os.readinto(fd, buffer) + self.assertEqual(type(s), int) + self.assertEqual(s, 0) + self.assertEqual(bytes(buffer), b"notspam\0") + s = os.readinto(fd, buffer) + self.assertEqual(s, 0) + self.assertEqual(bytes(buffer), b"notspam\0") + + def test_readinto_badbuffer(self): + with open(os_helper.TESTFN, "w+b") as fobj: + fobj.write(b"spam") + fobj.flush() + fd = fobj.fileno() + os.lseek(fd, 0, 0) + + for bad_arg in ("test", bytes(), 14): + with self.subTest(f"{type(bad_arg)}"): + with self.assertRaises(TypeError): + os.readinto(fd, bad_arg) + + # No data should have been read with the bad arguments. + buffer = bytearray(8) + s = os.readinto(fd, buffer) + self.assertEqual(s, 4) + self.assertEqual(bytes(buffer), b"spam\0\0\0\0") + @support.cpython_only # Skip the test on 32-bit platforms: the number of bytes must fit in a # Py_ssize_t type @@ -249,6 +290,29 @@ def test_large_read(self, size): # operating system is free to return less bytes than requested. self.assertEqual(data, b'test') + + @support.cpython_only + # Skip the test on 32-bit platforms: the number of bytes must fit in a + # Py_ssize_t type + @unittest.skipUnless(INT_MAX < PY_SSIZE_T_MAX, + "needs INT_MAX < PY_SSIZE_T_MAX") + @support.bigmemtest(size=INT_MAX + 10, memuse=1, dry_run=False) + def test_large_readinto(self, size): + self.addCleanup(os_helper.unlink, os_helper.TESTFN) + create_file(os_helper.TESTFN, b'test') + + # Issue #21932: For readinto the buffer contains the length rather than + # a length being passed explicitly to read, shold still get capped to a + # valid size / not raise an OverflowError for sizes larger than INT_MAX. + buffer = bytearray(INT_MAX+10) + with open(os_helper.TESTFN, "rb") as fp: + length = os.readinto(fp.fileno(), buffer) + + # The test does not try to read more than 2 GiB at once because the + # operating system is free to return less bytes than requested. + self.assertEqual(length, 4) + self.assertEqual(buffer[:4], b'test') + def test_write(self): # os.write() accepts bytes- and buffer-like objects but not strings fd = os.open(os_helper.TESTFN, os.O_CREAT | os.O_WRONLY) @@ -2467,6 +2531,10 @@ def test_lseek(self): def test_read(self): self.check(os.read, 1) + @unittest.skipUnless(hasattr(os, 'readinto'), 'test needs os.readinto()') + def test_readinto(self): + self.check(os.readinto, bytearray(5)) + @unittest.skipUnless(hasattr(os, 'readv'), 'test needs os.readv()') def test_readv(self): buf = bytearray(10) From b75bc9b3e0ec9942be88ef82cb7e545761b07d69 Mon Sep 17 00:00:00 2001 From: Cody Maloney Date: Wed, 22 Jan 2025 18:48:17 -0800 Subject: [PATCH 04/23] Change from :meth: to :func: --- .../next/Library/2025-01-22-16-54-25.gh-issue-129205.FMqrUt.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2025-01-22-16-54-25.gh-issue-129205.FMqrUt.rst b/Misc/NEWS.d/next/Library/2025-01-22-16-54-25.gh-issue-129205.FMqrUt.rst index 5effc59c2f1c93..888bd06386205c 100644 --- a/Misc/NEWS.d/next/Library/2025-01-22-16-54-25.gh-issue-129205.FMqrUt.rst +++ b/Misc/NEWS.d/next/Library/2025-01-22-16-54-25.gh-issue-129205.FMqrUt.rst @@ -1 +1 @@ -Add :meth:`os.readinto` to read into a :ref:`buffer protocol ` from a file descriptor. +Add :func:`os.readinto` to read into a :ref:`buffer protocol ` from a file descriptor. From 992d5f51e5d8c7f8e4af1d011ae7fb9127d84708 Mon Sep 17 00:00:00 2001 From: Cody Maloney Date: Wed, 22 Jan 2025 18:50:57 -0800 Subject: [PATCH 05/23] Remove length cap, and early exit on negative len. Py_ssize_t can't get as big as Py_size_t, _Py_read checks max length --- Modules/posixmodule.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 791f8dc73df829..d9f19831cb1a97 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -11451,9 +11451,11 @@ static Py_ssize_t os_readinto_impl(PyObject *module, int fd, Py_buffer *buffer) /*[clinic end generated code: output=8091a3513c683a80 input=810c820f4d9b1c6b]*/ { - // Cap to max read size to prevent overflow in cast to size_t for _Py_read. - size_t length = Py_MIN(buffer->len, _PY_READ_MAX); - return _Py_read(fd, buffer->buf, length); + if (buffer->len < 0) { + errno = EINVAL + return posix_error(); + } + return _Py_read(fd, buffer->buf, buffer->len); } #if (defined(HAVE_SENDFILE) && (defined(__FreeBSD__) || defined(__DragonFly__) \ From bbb0e6a411fe87259ccd3a767e3f1ab5baf9bd82 Mon Sep 17 00:00:00 2001 From: Cody Maloney Date: Wed, 22 Jan 2025 19:04:56 -0800 Subject: [PATCH 06/23] Fix build issues with last commit. --- Modules/posixmodule.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index d9f19831cb1a97..3726995220abec 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -11452,8 +11452,9 @@ os_readinto_impl(PyObject *module, int fd, Py_buffer *buffer) /*[clinic end generated code: output=8091a3513c683a80 input=810c820f4d9b1c6b]*/ { if (buffer->len < 0) { - errno = EINVAL - return posix_error(); + errno = EINVAL; + PyErr_SetFromErrno(PyExc_OSError); + return -1; } return _Py_read(fd, buffer->buf, buffer->len); } From 6512788ebae21ede0b4ffbe0a1be39e6a35d5405 Mon Sep 17 00:00:00 2001 From: Cody Maloney Date: Wed, 22 Jan 2025 20:24:13 -0800 Subject: [PATCH 07/23] blurb: Just a literal / neither :func: nor :meth: worked --- .../next/Library/2025-01-22-16-54-25.gh-issue-129205.FMqrUt.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2025-01-22-16-54-25.gh-issue-129205.FMqrUt.rst b/Misc/NEWS.d/next/Library/2025-01-22-16-54-25.gh-issue-129205.FMqrUt.rst index 888bd06386205c..3542450fc47119 100644 --- a/Misc/NEWS.d/next/Library/2025-01-22-16-54-25.gh-issue-129205.FMqrUt.rst +++ b/Misc/NEWS.d/next/Library/2025-01-22-16-54-25.gh-issue-129205.FMqrUt.rst @@ -1 +1 @@ -Add :func:`os.readinto` to read into a :ref:`buffer protocol ` from a file descriptor. +Add ``os.readinto`` to read into a :ref:`buffer protocol ` from a file descriptor. From b4b4c281d013424bc32c0a824e9888d50c33f88a Mon Sep 17 00:00:00 2001 From: Cody Maloney Date: Wed, 22 Jan 2025 20:57:58 -0800 Subject: [PATCH 08/23] Test zero byte long bytearrays, fix typo --- Lib/test/test_os.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 36674ae88711e7..cbbf3a0051cafa 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -253,6 +253,13 @@ def test_readinto(self): self.assertEqual(s, 0) self.assertEqual(bytes(buffer), b"notspam\0") + # Readinto a 0 length bytearray when at EOF should return 0 + self.assertEqual(os.readinto(fd, bytearray()), 0) + + # Readinto a 0 length bytearray with data available should return 0. + os.lseek(fd, 0, 0) + self.assertEqual(os.readinto(fd, bytearray()), 0) + def test_readinto_badbuffer(self): with open(os_helper.TESTFN, "w+b") as fobj: fobj.write(b"spam") @@ -302,7 +309,7 @@ def test_large_readinto(self, size): create_file(os_helper.TESTFN, b'test') # Issue #21932: For readinto the buffer contains the length rather than - # a length being passed explicitly to read, shold still get capped to a + # a length being passed explicitly to read, should still get capped to a # valid size / not raise an OverflowError for sizes larger than INT_MAX. buffer = bytearray(INT_MAX+10) with open(os_helper.TESTFN, "rb") as fp: From 4da3feca8dc5066733eeccd1816f0daeb9829456 Mon Sep 17 00:00:00 2001 From: Cody Maloney Date: Thu, 23 Jan 2025 09:08:09 -0800 Subject: [PATCH 09/23] Only check set bytes in readinto test --- Lib/test/test_os.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index cbbf3a0051cafa..a6f9dfbd4447c8 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -236,22 +236,22 @@ def test_readinto(self): fobj.flush() fd = fobj.fileno() os.lseek(fd, 0, 0) - buffer = bytearray(8) + buffer = bytearray(7) s = os.readinto(fd, buffer) self.assertEqual(type(s), int) self.assertEqual(s, 4) # Should overwrite the first 4 bytes of the buffer. - self.assertEqual(bytes(buffer), b"spam\0\0\0\0") + self.assertEqual(buffer[:4], b"spam") - # Readinto at EOF shold return 0 and not touch buffer - buffer[:] = b"notspam\0" + # Readinto at EOF should return 0 and not touch buffer. + buffer[:] = b"notspam" s = os.readinto(fd, buffer) self.assertEqual(type(s), int) self.assertEqual(s, 0) - self.assertEqual(bytes(buffer), b"notspam\0") + self.assertEqual(bytes(buffer), b"notspam") s = os.readinto(fd, buffer) self.assertEqual(s, 0) - self.assertEqual(bytes(buffer), b"notspam\0") + self.assertEqual(bytes(buffer), b"notspam") # Readinto a 0 length bytearray when at EOF should return 0 self.assertEqual(os.readinto(fd, bytearray()), 0) From c5f3df079cab2cdc5794c2060ded45f413613c02 Mon Sep 17 00:00:00 2001 From: Cody Maloney Date: Thu, 23 Jan 2025 09:19:33 -0800 Subject: [PATCH 10/23] Add Whats New, os.rst, tweak NEWS --- Doc/library/os.rst | 22 +++++++++++++++++++ Doc/whatsnew/3.14.rst | 3 +++ ...-01-22-16-54-25.gh-issue-129205.FMqrUt.rst | 2 +- 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 2445b008eb5a75..1e204027688018 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -1659,6 +1659,28 @@ or `the MSDN `_ on Windo :exc:`InterruptedError` exception (see :pep:`475` for the rationale). +.. function:: readinto(fd, buffer, /) + + Read from a file descriptor *fd* into a mutable + :ref:`buffer protocol ` *buffer*. + + The *buffer* should be mutable and :term:`bytes-like + objects `. On success, returns the number of + bytes read. Less bytes may be read than the size of the buffer. Will retry the + underlying system call when interrupted by a signal. For other errors, the + system call will not be retried. + + .. note:: + + This function is intended for low-level I/O and must be applied to a file + descriptor as returned by :func:`os.open` or :func:`pipe`. To read a + "file object" returned by the built-in function :func:`open` or by + :func:`popen` or :func:`fdopen`, or :data:`sys.stdin`, use its + :meth:`~file.readinto` or :meth:`~file.read`. + + .. versionadded:: 3.14 + + .. function:: sendfile(out_fd, in_fd, offset, count) sendfile(out_fd, in_fd, offset, count, headers=(), trailers=(), flags=0) diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 531c5ed6226fe4..98c057a2067515 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -561,6 +561,9 @@ os to the :mod:`os` module. (Contributed by James Roy in :gh:`127688`.) +* Add the :func:`os.readinto` function to read into a + :ref:`buffer protocol ` from a file descriptor. + (Contributed by Cody Maloney in :gh:`129205`.) pathlib ------- diff --git a/Misc/NEWS.d/next/Library/2025-01-22-16-54-25.gh-issue-129205.FMqrUt.rst b/Misc/NEWS.d/next/Library/2025-01-22-16-54-25.gh-issue-129205.FMqrUt.rst index 3542450fc47119..888bd06386205c 100644 --- a/Misc/NEWS.d/next/Library/2025-01-22-16-54-25.gh-issue-129205.FMqrUt.rst +++ b/Misc/NEWS.d/next/Library/2025-01-22-16-54-25.gh-issue-129205.FMqrUt.rst @@ -1 +1 @@ -Add ``os.readinto`` to read into a :ref:`buffer protocol ` from a file descriptor. +Add :func:`os.readinto` to read into a :ref:`buffer protocol ` from a file descriptor. From 8cc70cd97c636ee92e56d9204a8b89c79f57b477 Mon Sep 17 00:00:00 2001 From: Cody Maloney Date: Thu, 23 Jan 2025 09:25:19 -0800 Subject: [PATCH 11/23] Update clinic posixmodule.c to be better for help docs --- Modules/clinic/posixmodule.c.h | 6 +++--- Modules/posixmodule.c | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index e0c6116ce89524..af3292b3833f09 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -7581,9 +7581,9 @@ PyDoc_STRVAR(os_readinto__doc__, "readinto($module, fd, buffer, /)\n" "--\n" "\n" -"Read into a :ref:`buffer protocol ` object from a file descriptor.\n" +"Read into a Buffer Protocol object from a file descriptor.\n" "\n" -"The buffer should be mutable and accept bytes. On success, returns the number of\n" +"The buffer should be mutable and bytes-like. On success, returns the number of\n" "bytes read. Less bytes may be read than the size of the buffer. Will retry the\n" "underlying system call when interrupted by a signal. For other errors, the\n" "system call will not be retried."); @@ -13191,4 +13191,4 @@ os__emscripten_debugger(PyObject *module, PyObject *Py_UNUSED(ignored)) #ifndef OS__EMSCRIPTEN_DEBUGGER_METHODDEF #define OS__EMSCRIPTEN_DEBUGGER_METHODDEF #endif /* !defined(OS__EMSCRIPTEN_DEBUGGER_METHODDEF) */ -/*[clinic end generated code: output=c2f04dda4ea1a399 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=c2333102aef2f39a input=a9049054013a1b77]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 3726995220abec..967e64313c26fe 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -11439,9 +11439,9 @@ os.readinto -> Py_ssize_t buffer: Py_buffer(accept={rwbuffer}) / -Read into a :ref:`buffer protocol ` object from a file descriptor. +Read into a Buffer Protocol object from a file descriptor. -The buffer should be mutable and accept bytes. On success, returns the number of +The buffer should be mutable and bytes-like. On success, returns the number of bytes read. Less bytes may be read than the size of the buffer. Will retry the underlying system call when interrupted by a signal. For other errors, the system call will not be retried. @@ -11449,7 +11449,7 @@ system call will not be retried. static Py_ssize_t os_readinto_impl(PyObject *module, int fd, Py_buffer *buffer) -/*[clinic end generated code: output=8091a3513c683a80 input=810c820f4d9b1c6b]*/ +/*[clinic end generated code: output=8091a3513c683a80 input=2d815e709ab6a85b]*/ { if (buffer->len < 0) { errno = EINVAL; From 6f8ad36896d4c5c887b4bbb5255a817741570539 Mon Sep 17 00:00:00 2001 From: Cody Maloney Date: Thu, 23 Jan 2025 09:29:33 -0800 Subject: [PATCH 12/23] bytes-like objects to bytes-like object, only one buffer --- Doc/library/os.rst | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 1e204027688018..a0de767f92f647 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -1664,11 +1664,10 @@ or `the MSDN `_ on Windo Read from a file descriptor *fd* into a mutable :ref:`buffer protocol ` *buffer*. - The *buffer* should be mutable and :term:`bytes-like - objects `. On success, returns the number of - bytes read. Less bytes may be read than the size of the buffer. Will retry the - underlying system call when interrupted by a signal. For other errors, the - system call will not be retried. + The *buffer* should be mutable and :term:`bytes-like `. On + success, returns the number of bytes read. Less bytes may be read than the + size of the buffer. Will retry the underlying system call when interrupted by + a signal. For other errors, the system call will not be retried. .. note:: From 62f531c4aa431b5ea191f6fc95b1ececa202e15f Mon Sep 17 00:00:00 2001 From: Cody Maloney Date: Thu, 23 Jan 2025 13:50:51 -0800 Subject: [PATCH 13/23] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Doc/library/os.rst | 8 ++++---- Lib/test/test_os.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index a0de767f92f647..59394a208a5a06 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -1666,18 +1666,18 @@ or `the MSDN `_ on Windo The *buffer* should be mutable and :term:`bytes-like `. On success, returns the number of bytes read. Less bytes may be read than the - size of the buffer. Will retry the underlying system call when interrupted by - a signal. For other errors, the system call will not be retried. + size of the buffer. The underlying system call will be retried when + interrupted by a signal. For other errors, it will not be retried. .. note:: This function is intended for low-level I/O and must be applied to a file - descriptor as returned by :func:`os.open` or :func:`pipe`. To read a + descriptor as returned by :func:`os.open` or :func:`os.pipe`. To read a "file object" returned by the built-in function :func:`open` or by :func:`popen` or :func:`fdopen`, or :data:`sys.stdin`, use its :meth:`~file.readinto` or :meth:`~file.read`. - .. versionadded:: 3.14 + .. versionadded:: next .. function:: sendfile(out_fd, in_fd, offset, count) diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index a6f9dfbd4447c8..1efb4f624e699d 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -311,7 +311,7 @@ def test_large_readinto(self, size): # Issue #21932: For readinto the buffer contains the length rather than # a length being passed explicitly to read, should still get capped to a # valid size / not raise an OverflowError for sizes larger than INT_MAX. - buffer = bytearray(INT_MAX+10) + buffer = bytearray(INT_MAX + 10) with open(os_helper.TESTFN, "rb") as fp: length = os.readinto(fp.fileno(), buffer) From c8f5800d5fab4407bf8644394d0a2a5e20a45688 Mon Sep 17 00:00:00 2001 From: Cody Maloney Date: Thu, 23 Jan 2025 15:09:11 -0800 Subject: [PATCH 14/23] Iterate on docs, guarantee no negative return --- Doc/library/os.rst | 14 ++++++++++---- Modules/clinic/posixmodule.c.h | 15 ++++++++++----- Modules/posixmodule.c | 23 +++++++++++++++++------ 3 files changed, 37 insertions(+), 15 deletions(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 59394a208a5a06..2cca8bbfbdd577 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -1667,15 +1667,21 @@ or `the MSDN `_ on Windo The *buffer* should be mutable and :term:`bytes-like `. On success, returns the number of bytes read. Less bytes may be read than the size of the buffer. The underlying system call will be retried when - interrupted by a signal. For other errors, it will not be retried. + interrupted by a signal. Other errors will not be retried and an error will + be raised. + + Returns 0 if the fd is at end of file or if the provided buffer is + length 0 (can be used to check for errors without reading data). Never + returns a negative value. .. note:: This function is intended for low-level I/O and must be applied to a file descriptor as returned by :func:`os.open` or :func:`os.pipe`. To read a - "file object" returned by the built-in function :func:`open` or by - :func:`popen` or :func:`fdopen`, or :data:`sys.stdin`, use its - :meth:`~file.readinto` or :meth:`~file.read`. + "file object" returned by the built-in function :func:`open`, or + :data:`sys.stdin`, use its member functions, for example + :meth:`io.BufferedIOBase.readinto`, :meth:`io.BufferedIOBase.read`, or + :meth:`io.TextIOBase.read` .. versionadded:: next diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index af3292b3833f09..e79af63c2605cd 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -7583,10 +7583,15 @@ PyDoc_STRVAR(os_readinto__doc__, "\n" "Read into a Buffer Protocol object from a file descriptor.\n" "\n" -"The buffer should be mutable and bytes-like. On success, returns the number of\n" -"bytes read. Less bytes may be read than the size of the buffer. Will retry the\n" -"underlying system call when interrupted by a signal. For other errors, the\n" -"system call will not be retried."); +"The buffer should be mutable and bytes-like.\n" +"\n" +"On success, returns the number of bytes read. Less bytes may be read than the\n" +"size of the buffer without reaching end of stream. Will retry the underlying\n" +"system call when interrupted by a signal. Other errors will not be retried and\n" +"an error will be raised.\n" +"\n" +"Returns 0 if the fd is at end of file or the provided buffer is length 0 (can be\n" +"used to check for errors without reading data). Never returns a negative value."); #define OS_READINTO_METHODDEF \ {"readinto", _PyCFunction_CAST(os_readinto), METH_FASTCALL, os_readinto__doc__}, @@ -13191,4 +13196,4 @@ os__emscripten_debugger(PyObject *module, PyObject *Py_UNUSED(ignored)) #ifndef OS__EMSCRIPTEN_DEBUGGER_METHODDEF #define OS__EMSCRIPTEN_DEBUGGER_METHODDEF #endif /* !defined(OS__EMSCRIPTEN_DEBUGGER_METHODDEF) */ -/*[clinic end generated code: output=c2333102aef2f39a input=a9049054013a1b77]*/ +/*[clinic end generated code: output=84e2430fb1a3cff1 input=a9049054013a1b77]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 967e64313c26fe..94edd614c94082 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -11441,22 +11441,33 @@ os.readinto -> Py_ssize_t Read into a Buffer Protocol object from a file descriptor. -The buffer should be mutable and bytes-like. On success, returns the number of -bytes read. Less bytes may be read than the size of the buffer. Will retry the -underlying system call when interrupted by a signal. For other errors, the -system call will not be retried. +The buffer should be mutable and bytes-like. + +On success, returns the number of bytes read. Less bytes may be read than the +size of the buffer without reaching end of stream. Will retry the underlying +system call when interrupted by a signal. Other errors will not be retried and +an error will be raised. + +Returns 0 if the fd is at end of file or the provided buffer is length 0 (can be +used to check for errors without reading data). Never returns a negative value. [clinic start generated code]*/ static Py_ssize_t os_readinto_impl(PyObject *module, int fd, Py_buffer *buffer) -/*[clinic end generated code: output=8091a3513c683a80 input=2d815e709ab6a85b]*/ +/*[clinic end generated code: output=8091a3513c683a80 input=7485bbbb143bf7e8]*/ { if (buffer->len < 0) { + assert(!PyErr_Occurred()); errno = EINVAL; PyErr_SetFromErrno(PyExc_OSError); return -1; } - return _Py_read(fd, buffer->buf, buffer->len); + Py_ssize_t result = _Py_read(fd, buffer->buf, buffer->len); + /* Ensure negative is never returned without an error. Simplifies calling + code. _Py_read should succeed, possibly reading 0 bytes, _or_ set an + error. */ + assert(result >= 0 || (result == -1 && PyErr_Occurred())); + return result; } #if (defined(HAVE_SENDFILE) && (defined(__FreeBSD__) || defined(__DragonFly__) \ From 2457b63f883924f3ff40d0b13a77d72c4fa9c9c2 Mon Sep 17 00:00:00 2001 From: Cody Maloney Date: Thu, 23 Jan 2025 15:35:20 -0800 Subject: [PATCH 15/23] Test non blocking behavior, test more bad args --- Lib/test/test_os.py | 47 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 1efb4f624e699d..d1bdf784b0df16 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -236,6 +236,7 @@ def test_readinto(self): fobj.flush() fd = fobj.fileno() os.lseek(fd, 0, 0) + # Oversized so readinto without hitting end. buffer = bytearray(7) s = os.readinto(fd, buffer) self.assertEqual(type(s), int) @@ -260,7 +261,37 @@ def test_readinto(self): os.lseek(fd, 0, 0) self.assertEqual(os.readinto(fd, bytearray()), 0) - def test_readinto_badbuffer(self): + @unittest.skipUnless(hasattr(os, 'get_blocking'), + 'needs os.get_blocking() and os.set_blocking()') + @unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()") + def test_readinto_non_blocking(self): + # Verify behavior of a readinto which would block on a non-blocking fd. + r, w = os.pipe() + try: + os.set_blocking(r, False) + with self.assertRaises(BlockingIOError): + os.readinto(r, bytearray(5)) + + # Pass some data through + os.write(w, b"spam") + self.assertEqual(os.readinto(r, bytearray(4)), 4) + + # Still don't block or return 0. + with self.assertRaises(BlockingIOError): + os.readinto(r, bytearray(5)) + + # At EOF should return size 0 + os.close(w) + w = None + self.assertEqual(os.readinto(r, bytearray(5)), 0) + self.assertEqual(os.readinto(r, bytearray(5)), 0) # Still EOF + + finally: + os.close(r) + if w is not None: + os.close(w) + + def test_readinto_badarg(self): with open(os_helper.TESTFN, "w+b") as fobj: fobj.write(b"spam") fobj.flush() @@ -268,15 +299,23 @@ def test_readinto_badbuffer(self): os.lseek(fd, 0, 0) for bad_arg in ("test", bytes(), 14): - with self.subTest(f"{type(bad_arg)}"): + with self.subTest(f"bad buffer {type(bad_arg)}"): with self.assertRaises(TypeError): os.readinto(fd, bad_arg) + with self.subTest("doesn't work on file objects"): + with self.assertRaises(TypeError): + os.readinto(fobj, bytearray(5)) + + # takes two args + with self.assertRaises(TypeError): + os.readinto(fd) + # No data should have been read with the bad arguments. - buffer = bytearray(8) + buffer = bytearray(4) s = os.readinto(fd, buffer) self.assertEqual(s, 4) - self.assertEqual(bytes(buffer), b"spam\0\0\0\0") + self.assertEqual(buffer, b"spam") @support.cpython_only # Skip the test on 32-bit platforms: the number of bytes must fit in a From 25125f280b32eee3d7978268e6336ff49d6d838e Mon Sep 17 00:00:00 2001 From: Cody Maloney Date: Thu, 23 Jan 2025 16:01:31 -0800 Subject: [PATCH 16/23] Apply suggestions from code review Co-authored-by: Victor Stinner --- Doc/library/os.rst | 3 ++- Modules/posixmodule.c | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 2cca8bbfbdd577..5d38c918a04f06 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -1667,7 +1667,8 @@ or `the MSDN `_ on Windo The *buffer* should be mutable and :term:`bytes-like `. On success, returns the number of bytes read. Less bytes may be read than the size of the buffer. The underlying system call will be retried when - interrupted by a signal. Other errors will not be retried and an error will + interrupted by a signal, unless the signal handler raises an exception. + Other errors will not be retried and an error will be raised. Returns 0 if the fd is at end of file or if the provided buffer is diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 94edd614c94082..164c900dff8f90 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -11439,7 +11439,7 @@ os.readinto -> Py_ssize_t buffer: Py_buffer(accept={rwbuffer}) / -Read into a Buffer Protocol object from a file descriptor. +Read into a buffer object from a file descriptor. The buffer should be mutable and bytes-like. From 52a83fab96f7c9f5e2f5ed4a4b3cb9ee92c28a5f Mon Sep 17 00:00:00 2001 From: Cody Maloney Date: Thu, 23 Jan 2025 16:06:26 -0800 Subject: [PATCH 17/23] Tweak Whats New and NEWS, assert buf->length --- Doc/whatsnew/3.14.rst | 2 +- .../Library/2025-01-22-16-54-25.gh-issue-129205.FMqrUt.rst | 2 +- Modules/posixmodule.c | 7 +------ 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 98c057a2067515..1d9d93233787e4 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -562,7 +562,7 @@ os (Contributed by James Roy in :gh:`127688`.) * Add the :func:`os.readinto` function to read into a - :ref:`buffer protocol ` from a file descriptor. + :ref:`buffer object ` from a file descriptor. (Contributed by Cody Maloney in :gh:`129205`.) pathlib diff --git a/Misc/NEWS.d/next/Library/2025-01-22-16-54-25.gh-issue-129205.FMqrUt.rst b/Misc/NEWS.d/next/Library/2025-01-22-16-54-25.gh-issue-129205.FMqrUt.rst index 888bd06386205c..c4ed76408f32f6 100644 --- a/Misc/NEWS.d/next/Library/2025-01-22-16-54-25.gh-issue-129205.FMqrUt.rst +++ b/Misc/NEWS.d/next/Library/2025-01-22-16-54-25.gh-issue-129205.FMqrUt.rst @@ -1 +1 @@ -Add :func:`os.readinto` to read into a :ref:`buffer protocol ` from a file descriptor. +Add :func:`os.readinto` to read into a :ref:`buffer object ` from a file descriptor. diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 164c900dff8f90..965dafd0865c82 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -11456,12 +11456,7 @@ static Py_ssize_t os_readinto_impl(PyObject *module, int fd, Py_buffer *buffer) /*[clinic end generated code: output=8091a3513c683a80 input=7485bbbb143bf7e8]*/ { - if (buffer->len < 0) { - assert(!PyErr_Occurred()); - errno = EINVAL; - PyErr_SetFromErrno(PyExc_OSError); - return -1; - } + assert(buffer->len >= 0); Py_ssize_t result = _Py_read(fd, buffer->buf, buffer->len); /* Ensure negative is never returned without an error. Simplifies calling code. _Py_read should succeed, possibly reading 0 bytes, _or_ set an From a7a0775ffcb57366da75f6ab4ea213a8766300e3 Mon Sep 17 00:00:00 2001 From: Cody Maloney Date: Thu, 23 Jan 2025 16:28:06 -0800 Subject: [PATCH 18/23] clinic --- Modules/clinic/posixmodule.c.h | 4 ++-- Modules/posixmodule.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index e79af63c2605cd..6c05d9eac36c88 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -7581,7 +7581,7 @@ PyDoc_STRVAR(os_readinto__doc__, "readinto($module, fd, buffer, /)\n" "--\n" "\n" -"Read into a Buffer Protocol object from a file descriptor.\n" +"Read into a buffer object from a file descriptor.\n" "\n" "The buffer should be mutable and bytes-like.\n" "\n" @@ -13196,4 +13196,4 @@ os__emscripten_debugger(PyObject *module, PyObject *Py_UNUSED(ignored)) #ifndef OS__EMSCRIPTEN_DEBUGGER_METHODDEF #define OS__EMSCRIPTEN_DEBUGGER_METHODDEF #endif /* !defined(OS__EMSCRIPTEN_DEBUGGER_METHODDEF) */ -/*[clinic end generated code: output=84e2430fb1a3cff1 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=c0f95415cf1db474 input=a9049054013a1b77]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 965dafd0865c82..e060be102a5794 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -11454,7 +11454,7 @@ used to check for errors without reading data). Never returns a negative value. static Py_ssize_t os_readinto_impl(PyObject *module, int fd, Py_buffer *buffer) -/*[clinic end generated code: output=8091a3513c683a80 input=7485bbbb143bf7e8]*/ +/*[clinic end generated code: output=8091a3513c683a80 input=2a0ac4256f469f93]*/ { assert(buffer->len >= 0); Py_ssize_t result = _Py_read(fd, buffer->buf, buffer->len); From e1aec8ec7b97db9dc8554a17078266cfea9583a3 Mon Sep 17 00:00:00 2001 From: Cody Maloney Date: Thu, 23 Jan 2025 16:29:30 -0800 Subject: [PATCH 19/23] Tweka os doc --- Doc/library/os.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 5d38c918a04f06..877e6880bf1f32 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -1662,7 +1662,7 @@ or `the MSDN `_ on Windo .. function:: readinto(fd, buffer, /) Read from a file descriptor *fd* into a mutable - :ref:`buffer protocol ` *buffer*. + :ref:`buffer object ` *buffer*. The *buffer* should be mutable and :term:`bytes-like `. On success, returns the number of bytes read. Less bytes may be read than the From 051b0c2db00beb03e95aa4ce976f9427ceeb721f Mon Sep 17 00:00:00 2001 From: Cody Maloney Date: Thu, 23 Jan 2025 16:31:34 -0800 Subject: [PATCH 20/23] minor doc tweak --- Doc/library/os.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 877e6880bf1f32..543f468c0ea167 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -1668,8 +1668,7 @@ or `the MSDN `_ on Windo success, returns the number of bytes read. Less bytes may be read than the size of the buffer. The underlying system call will be retried when interrupted by a signal, unless the signal handler raises an exception. - Other errors will not be retried and an error will - be raised. + Other errors will not be retried and an error will be raised. Returns 0 if the fd is at end of file or if the provided buffer is length 0 (can be used to check for errors without reading data). Never From 9356bc9df5687176704ac362acefd1d77f33582f Mon Sep 17 00:00:00 2001 From: Cody Maloney Date: Fri, 24 Jan 2025 11:24:24 -0800 Subject: [PATCH 21/23] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Doc/library/os.rst | 6 +++--- Doc/whatsnew/3.14.rst | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 543f468c0ea167..5ea721f3108994 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -1670,9 +1670,9 @@ or `the MSDN `_ on Windo interrupted by a signal, unless the signal handler raises an exception. Other errors will not be retried and an error will be raised. - Returns 0 if the fd is at end of file or if the provided buffer is - length 0 (can be used to check for errors without reading data). Never - returns a negative value. + Returns 0 if *fd* is at end of file or if the provided *buffer* has + length 0 (which can be used to check for errors without reading data). + A negative value is never returned by this function. .. note:: diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 1d9d93233787e4..374587489bdb62 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -565,6 +565,7 @@ os :ref:`buffer object ` from a file descriptor. (Contributed by Cody Maloney in :gh:`129205`.) + pathlib ------- From 06e98e837d1521de9670d0fe78faf53ec1cca4b0 Mon Sep 17 00:00:00 2001 From: Cody Maloney Date: Fri, 24 Jan 2025 11:30:04 -0800 Subject: [PATCH 22/23] Sync Doc and help --- Doc/library/os.rst | 2 +- Modules/clinic/posixmodule.c.h | 18 +++++++++--------- Modules/posixmodule.c | 20 ++++++++++---------- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 5ea721f3108994..7d3596622862ea 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -1672,7 +1672,7 @@ or `the MSDN `_ on Windo Returns 0 if *fd* is at end of file or if the provided *buffer* has length 0 (which can be used to check for errors without reading data). - A negative value is never returned by this function. + Never returns negative. .. note:: diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index 6c05d9eac36c88..abeb9c3e3e12b1 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -7583,15 +7583,15 @@ PyDoc_STRVAR(os_readinto__doc__, "\n" "Read into a buffer object from a file descriptor.\n" "\n" -"The buffer should be mutable and bytes-like.\n" +"The buffer should be mutable and bytes-like. On success, returns the number of\n" +"bytes read. Less bytes may be read than the size of the buffer. The underlying\n" +"system call will be retried when interrupted by a signal, unless the signal\n" +"handler raises an exception. Other errors will not be retried and an error will\n" +"be raised.\n" "\n" -"On success, returns the number of bytes read. Less bytes may be read than the\n" -"size of the buffer without reaching end of stream. Will retry the underlying\n" -"system call when interrupted by a signal. Other errors will not be retried and\n" -"an error will be raised.\n" -"\n" -"Returns 0 if the fd is at end of file or the provided buffer is length 0 (can be\n" -"used to check for errors without reading data). Never returns a negative value."); +"Returns 0 if *fd* is at end of file or if the provided *buffer* has length 0\n" +"(which can be used to check for errors without reading data). Never returns\n" +"negative."); #define OS_READINTO_METHODDEF \ {"readinto", _PyCFunction_CAST(os_readinto), METH_FASTCALL, os_readinto__doc__}, @@ -13196,4 +13196,4 @@ os__emscripten_debugger(PyObject *module, PyObject *Py_UNUSED(ignored)) #ifndef OS__EMSCRIPTEN_DEBUGGER_METHODDEF #define OS__EMSCRIPTEN_DEBUGGER_METHODDEF #endif /* !defined(OS__EMSCRIPTEN_DEBUGGER_METHODDEF) */ -/*[clinic end generated code: output=c0f95415cf1db474 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=8318c26fc2cd236c input=a9049054013a1b77]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index e060be102a5794..a35a848a7ca4b8 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -11441,20 +11441,20 @@ os.readinto -> Py_ssize_t Read into a buffer object from a file descriptor. -The buffer should be mutable and bytes-like. - -On success, returns the number of bytes read. Less bytes may be read than the -size of the buffer without reaching end of stream. Will retry the underlying -system call when interrupted by a signal. Other errors will not be retried and -an error will be raised. - -Returns 0 if the fd is at end of file or the provided buffer is length 0 (can be -used to check for errors without reading data). Never returns a negative value. +The buffer should be mutable and bytes-like. On success, returns the number of +bytes read. Less bytes may be read than the size of the buffer. The underlying +system call will be retried when interrupted by a signal, unless the signal +handler raises an exception. Other errors will not be retried and an error will +be raised. + +Returns 0 if *fd* is at end of file or if the provided *buffer* has length 0 +(which can be used to check for errors without reading data). Never returns +negative. [clinic start generated code]*/ static Py_ssize_t os_readinto_impl(PyObject *module, int fd, Py_buffer *buffer) -/*[clinic end generated code: output=8091a3513c683a80 input=2a0ac4256f469f93]*/ +/*[clinic end generated code: output=8091a3513c683a80 input=d40074d0a68de575]*/ { assert(buffer->len >= 0); Py_ssize_t result = _Py_read(fd, buffer->buf, buffer->len); From 060a670dac2dc26fc96f730c18dfc57cb3091ae4 Mon Sep 17 00:00:00 2001 From: Cody Maloney Date: Fri, 24 Jan 2025 11:42:54 -0800 Subject: [PATCH 23/23] Remove bufs, zip from test, just make a local bytearray --- Lib/test/_test_eintr.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib/test/_test_eintr.py b/Lib/test/_test_eintr.py index 045c8798b46de8..ae799808ca067e 100644 --- a/Lib/test/_test_eintr.py +++ b/Lib/test/_test_eintr.py @@ -160,7 +160,6 @@ def test_readinto(self): # the payload below are smaller than PIPE_BUF, hence the writes will be # atomic datas = [b"hello", b"world", b"spam"] - bufs = [bytearray(5), bytearray(5), bytearray(4)] code = '\n'.join(( 'import os, sys, time', @@ -178,9 +177,10 @@ def test_readinto(self): proc = self.subprocess(code, str(wr), pass_fds=[wr]) with kill_on_error(proc): os.close(wr) - for data, buffer in zip(datas, bufs): - os.readinto(rd, buffer) - self.assertEqual(data, buffer) + for data in datas: + buffer = bytearray(len(data)) + self.assertEqual(os.readinto(rd, buffer), len(data)) + self.assertEqual(buffer, data) self.assertEqual(proc.wait(), 0) def test_write(self):