Skip to content

Commit d65bf51

Browse files
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 <storchaka@gmail.com>
1 parent 4b04d5a commit d65bf51

6 files changed

Lines changed: 40 additions & 5 deletions

File tree

Doc/whatsnew/3.16.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -828,6 +828,13 @@ that may require changes to your code.
828828
:exc:`TypeError`.
829829
(Contributed by Serhiy Storchaka in :gh:`152587`.)
830830

831+
* On Windows, seeking a pipe now fails instead of silently appearing to
832+
succeed: :func:`os.lseek` and :meth:`~io.IOBase.seek` raise :exc:`OSError`,
833+
and :meth:`~io.IOBase.seekable` returns ``False``. As a consequence,
834+
opening a pipe in a read-write binary mode (``'r+b'`` or ``'w+b'``) now
835+
raises :exc:`io.UnsupportedOperation` unless buffering is disabled.
836+
(Contributed by An Long in :gh:`86768`.)
837+
831838

832839
Build changes
833840
=============

Lib/test/test_os/test_os.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2993,6 +2993,14 @@ def test_ftruncate(self):
29932993
def test_lseek(self):
29942994
self.check(os.lseek, 0, 0)
29952995

2996+
@unittest.skipUnless(hasattr(os, 'lseek'), 'test needs os.lseek()')
2997+
@unittest.skipUnless(hasattr(os, 'pipe'), "need os.pipe()")
2998+
def test_lseek_on_pipe(self):
2999+
rfd, wfd = os.pipe()
3000+
self.addCleanup(os.close, rfd)
3001+
self.addCleanup(os.close, wfd)
3002+
self.assertRaises(OSError, os.lseek, rfd, 123, os.SEEK_END)
3003+
29963004
@unittest.skipUnless(hasattr(os, 'read'), 'test needs os.read()')
29973005
def test_read(self):
29983006
self.check(os.read, 1)

Lib/test/test_winapi.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ def test_namedpipe(self):
152152
# Pipe instance is available, so this passes
153153
_winapi.WaitNamedPipe(pipe_name, 0)
154154

155-
with open(pipe_name, 'w+b') as pipe2:
155+
with open(pipe_name, 'w+b', buffering=0) as pipe2:
156156
# No instances available, so this times out
157157
# (WinError 121 does not get mapped to TimeoutError)
158158
with self.assertRaises(OSError):
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
:func:`os.lseek` and :meth:`~io.IOBase.seek` of file objects now raise
2+
:exc:`OSError` for pipes on Windows, and :meth:`~io.IOBase.seekable` now
3+
returns ``False`` for them. Previously seeking a pipe silently appeared to
4+
succeed. As a consequence, opening a pipe in a read-write binary mode
5+
(``'r+b'`` or ``'w+b'``) now raises :exc:`io.UnsupportedOperation` unless
6+
buffering is disabled.

Modules/_io/fileio.c

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -992,7 +992,14 @@ portable_lseek(fileio *self, PyObject *posobj, int whence, bool suppress_pipe_er
992992
Py_BEGIN_ALLOW_THREADS
993993
_Py_BEGIN_SUPPRESS_IPH
994994
#ifdef MS_WINDOWS
995-
res = _lseeki64(fd, pos, whence);
995+
HANDLE h = (HANDLE)_get_osfhandle(fd);
996+
if (h != INVALID_HANDLE_VALUE && GetFileType(h) == FILE_TYPE_PIPE) {
997+
res = -1;
998+
errno = ESPIPE;
999+
}
1000+
else {
1001+
res = _lseeki64(fd, pos, whence);
1002+
}
9961003
#else
9971004
res = lseek(fd, pos, whence);
9981005
#endif

Modules/posixmodule.c

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12037,7 +12037,7 @@ static Py_off_t
1203712037
os_lseek_impl(PyObject *module, int fd, Py_off_t position, int how)
1203812038
/*[clinic end generated code: output=971e1efb6b30bd2f input=32ea0788da7cb44b]*/
1203912039
{
12040-
Py_off_t result;
12040+
Py_off_t result = -1;
1204112041

1204212042
#ifdef SEEK_SET
1204312043
/* 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)
1205112051
Py_BEGIN_ALLOW_THREADS
1205212052
_Py_BEGIN_SUPPRESS_IPH
1205312053
#ifdef MS_WINDOWS
12054-
result = _lseeki64(fd, position, how);
12054+
HANDLE h = (HANDLE)_get_osfhandle(fd);
12055+
if (h != INVALID_HANDLE_VALUE && GetFileType(h) == FILE_TYPE_PIPE) {
12056+
errno = ESPIPE;
12057+
}
12058+
else {
12059+
result = _lseeki64(fd, position, how);
12060+
}
1205512061
#else
1205612062
result = lseek(fd, position, how);
1205712063
#endif
1205812064
_Py_END_SUPPRESS_IPH
1205912065
Py_END_ALLOW_THREADS
12060-
if (result < 0)
12066+
if (result < 0) {
1206112067
posix_error();
12068+
}
1206212069

1206312070
return result;
1206412071
}

0 commit comments

Comments
 (0)