gh-86768: Raise OSError when seeking a pipe on Windows (GH-133137)
serhiy-storchaka <[email protected]> Tue, 11 Aug 2026 10:48:07 -0400 (EDT)
| Newsgroups | gmane.comp.python.cvs |
|---|---|
| Message-ID | <[email protected]> |
https://github.com/python/cpython/commit/d65bf5174423a0abc288d9fa5f3ff0fcaf2bc49f commit: d65bf5174423a0abc288d9fa5f3ff0fcaf2bc49f branch: main author: An Long <[email protected]> committer: serhiy-storchaka <[email protected]> date: 2026-08-11T17:47:52+03:00 summary: gh-86768: Raise OSError when seeking a pipe on Windows (GH-133137) Previously os.lseek() and file seek() silently succeeded for pipes, and seekable() wrongly returned True. Co-authored-by: Serhiy Storchaka <[email protected]> files: A Misc/NEWS.d/next/Windows/2025-04-29-17-55-55.gh-issue-86768.uIDTHc.rst M Doc/whatsnew/3.16.rst M Lib/test/test_os/test_os.py M Lib/test/test_winapi.py M Modules/_io/fileio.c M Modules/posixmodule.c diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst index c16f4ca04f757f9..b017535b96979d9 100644 --- a/Doc/whatsnew/3.16.rst +++ b/Doc/whatsnew/3.16.rst @@ -828,6 +828,13 @@ that may require changes to your code. :exc:`TypeError`. (Contributed by Serhiy Storchaka in :gh:`152587`.) +* On Windows, seeking a pipe now fails instead of silently appearing to + succeed: :func:`os.lseek` and :meth:`~io.IOBase.seek` raise :exc:`OSError`, + and :meth:`~io.IOBase.seekable` returns ``False``. As a consequence, + opening a pipe in a read-write binary mode (``'r+b'`` or ``'w+b'``) now + raises :exc:`io.UnsupportedOperation` unless buffering is disabled. + (Contributed by An Long in :gh:`86768`.) + Build changes ============= diff --git a/Lib/test/test_os/test_os.py b/Lib/test/test_os/test_os.py index 4c1ab96065587e5..7a49cfa0c29ec5b 100644 --- a/Lib/test/test_os/test_os.py +++ b/Lib/test/test_os/test_os.py @@ -2993,6 +2993,14 @@ def test_ftruncate(self): def test_lseek(self): self.check(os.lseek, 0, 0) + @unittest.skipUnless(hasattr(os, 'lseek'), 'test needs os.lseek()') + @unittest.skipUnless(hasattr(os, 'pipe'), "need os.pipe()") + def test_lseek_on_pipe(self): + rfd, wfd = os.pipe() + self.addCleanup(os.close, rfd) + self.addCleanup(os.close, wfd) + self.assertRaises(OSError, os.lseek, rfd, 123, os.SEEK_END) + @unittest.skipUnless(hasattr(os, 'read'), 'test needs os.read()') def test_read(self): self.check(os.read, 1) diff --git a/Lib/test/test_winapi.py b/Lib/test/test_winapi.py index 0ae03a3bf505f73..60f7881a0b0ab99 100644 --- a/Lib/test/test_winapi.py +++ b/Lib/test/test_winapi.py @@ -152,7 +152,7 @@ def test_namedpipe(self): # Pipe instance is available, so this passes _winapi.WaitNamedPipe(pipe_name, 0) - with open(pipe_name, 'w+b') as pipe2: + with open(pipe_name, 'w+b', buffering=0) as pipe2: # No instances available, so this times out # (WinError 121 does not get mapped to TimeoutError) with self.assertRaises(OSError): diff --git a/Misc/NEWS.d/next/Windows/2025-04-29-17-55-55.gh-issue-86768.uIDTHc.rst b/Misc/NEWS.d/next/Windows/2025-04-29-17-55-55.gh-issue-86768.uIDTHc.rst new file mode 100644 index 000000000000000..6085f47c49d5ed7 --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2025-04-29-17-55-55.gh-issue-86768.uIDTHc.rst @@ -0,0 +1,6 @@ +:func:`os.lseek` and :meth:`~io.IOBase.seek` of file objects now raise +:exc:`OSError` for pipes on Windows, and :meth:`~io.IOBase.seekable` now +returns ``False`` for them. Previously seeking a pipe silently appeared to +succeed. As a consequence, opening a pipe in a read-write binary mode +(``'r+b'`` or ``'w+b'``) now raises :exc:`io.UnsupportedOperation` unless +buffering is disabled. diff --git a/Modules/_io/fileio.c b/Modules/_io/fileio.c index 3aeb30dfe24a357..e8e9c132dd3465e 100644 --- a/Modules/_io/fileio.c +++ b/Modules/_io/fileio.c @@ -992,7 +992,14 @@ portable_lseek(fileio *self, PyObject *posobj, int whence, bool suppress_pipe_er Py_BEGIN_ALLOW_THREADS _Py_BEGIN_SUPPRESS_IPH #ifdef MS_WINDOWS - res = _lseeki64(fd, pos, whence); + HANDLE h = (HANDLE)_get_osfhandle(fd); + if (h != INVALID_HANDLE_VALUE && GetFileType(h) == FILE_TYPE_PIPE) { + res = -1; + errno = ESPIPE; + } + else { + res = _lseeki64(fd, pos, whence); + } #else res = lseek(fd, pos, whence); #endif diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index a9dd8647545bc9c..9e84fd400527ea2 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -12037,7 +12037,7 @@ static Py_off_t os_lseek_impl(PyObject *module, int fd, Py_off_t position, int how) /*[clinic end generated code: output=971e1efb6b30bd2f input=32ea0788da7cb44b]*/ { - Py_off_t result; + Py_off_t result = -1; #ifdef SEEK_SET /* Turn 0, 1, 2 into SEEK_{SET,CUR,END} */ @@ -12051,14 +12051,21 @@ os_lseek_impl(PyObject *module, int fd, Py_off_t position, int how) Py_BEGIN_ALLOW_THREADS _Py_BEGIN_SUPPRESS_IPH #ifdef MS_WINDOWS - result = _lseeki64(fd, position, how); + HANDLE h = (HANDLE)_get_osfhandle(fd); + if (h != INVALID_HANDLE_VALUE && GetFileType(h) == FILE_TYPE_PIPE) { + errno = ESPIPE; + } + else { + result = _lseeki64(fd, position, how); + } #else result = lseek(fd, position, how); #endif _Py_END_SUPPRESS_IPH Py_END_ALLOW_THREADS - if (result < 0) + if (result < 0) { posix_error(); + } return result; } _______________________________________________ Python-checkins mailing list -- [email protected] To unsubscribe send an email to [email protected] https://mail.python.org/mailman3//lists/python-checkins.python.org Member address: [email protected]