From 43956d89af565f0edbca5c4007a59221e921fbf0 Mon Sep 17 00:00:00 2001 From: AN Long Date: Mon, 28 Apr 2025 23:56:41 +0900 Subject: [PATCH 01/12] Implement os.lseek with SetFilePointerEx on Windows --- Modules/posixmodule.c | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 8d7131b2eacf95..22204ed2efa9d9 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -11406,7 +11406,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=f096e754c5367504]*/ { - Py_off_t result; + Py_off_t result = 0; #ifdef SEEK_SET /* Turn 0, 1, 2 into SEEK_{SET,CUR,END} */ @@ -11420,7 +11420,36 @@ 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); + switch (how) { + case SEEK_SET: + how = FILE_BEGIN; + break; + case SEEK_CUR: + how = FILE_CURRENT; + break; + case SEEK_END: + how = FILE_END; + break; + } + HANDLE h = (HANDLE)_get_osfhandle(fd); + if (h == INVALID_HANDLE_VALUE) { + result = -1; + } + if (result >= 0) { + if (GetFileType(h) != FILE_TYPE_DISK) { + // Only file is seekable + result = -1; + } + } + if (result >= 0) { + LARGE_INTEGER distance, newdistance; + distance.QuadPart = position; + if (SetFilePointerEx(h, distance, &newdistance, how)) { + result = newdistance.QuadPart; + } else { + result = -1; + } + } #else result = lseek(fd, position, how); #endif From b1a5cbbcf16024cf38682e7290ea891db3f8b8c1 Mon Sep 17 00:00:00 2001 From: AN Long Date: Tue, 29 Apr 2025 15:23:41 +0900 Subject: [PATCH 02/12] Add test --- Lib/test/test_os.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 333179a71e3cdc..8a69b2387d0bb1 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -2577,6 +2577,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) From 7fe34903261d31412cbd599d7f7809b4208298e3 Mon Sep 17 00:00:00 2001 From: AN Long Date: Tue, 29 Apr 2025 18:17:50 +0900 Subject: [PATCH 03/12] Add news entry --- .../next/Windows/2025-04-29-17-55-55.gh-issue-86768.uIDTHc.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Windows/2025-04-29-17-55-55.gh-issue-86768.uIDTHc.rst 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 00000000000000..6f02f60dc092a7 --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2025-04-29-17-55-55.gh-issue-86768.uIDTHc.rst @@ -0,0 +1,2 @@ +:exc:`OSError` will be raised if call :func:`os.lseek` with non seekable +object on Windows. From 9a0d10f311c9edf7ee49146df46a82d6f048288a Mon Sep 17 00:00:00 2001 From: AN Long Date: Wed, 30 Apr 2025 00:26:26 +0900 Subject: [PATCH 04/12] Check error --- Modules/posixmodule.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 22204ed2efa9d9..3437cdf30b2352 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -219,6 +219,7 @@ # if defined(MS_WINDOWS_DESKTOP) || defined(MS_WINDOWS_SYSTEM) # define HAVE_SYMLINK # endif /* MS_WINDOWS_DESKTOP | MS_WINDOWS_SYSTEM */ +extern int winerror_to_errno(int); #endif @@ -11438,6 +11439,7 @@ os_lseek_impl(PyObject *module, int fd, Py_off_t position, int how) if (result >= 0) { if (GetFileType(h) != FILE_TYPE_DISK) { // Only file is seekable + errno = ESPIPE; result = -1; } } @@ -11455,8 +11457,14 @@ os_lseek_impl(PyObject *module, int fd, Py_off_t position, int how) #endif _Py_END_SUPPRESS_IPH Py_END_ALLOW_THREADS - if (result < 0) + if (result < 0) { +#ifdef MS_WINDOWS + if (errno == 0) { + errno = winerror_to_errno(GetLastError()); + } +#endif posix_error(); + } return result; } From 2a8efe28d8df365091faf21b90097cd53cd1982c Mon Sep 17 00:00:00 2001 From: AN Long Date: Sat, 10 May 2025 22:07:58 +0900 Subject: [PATCH 05/12] Simplify the implementation --- Modules/posixmodule.c | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 3437cdf30b2352..75554b7000072a 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -11421,17 +11421,6 @@ os_lseek_impl(PyObject *module, int fd, Py_off_t position, int how) Py_BEGIN_ALLOW_THREADS _Py_BEGIN_SUPPRESS_IPH #ifdef MS_WINDOWS - switch (how) { - case SEEK_SET: - how = FILE_BEGIN; - break; - case SEEK_CUR: - how = FILE_CURRENT; - break; - case SEEK_END: - how = FILE_END; - break; - } HANDLE h = (HANDLE)_get_osfhandle(fd); if (h == INVALID_HANDLE_VALUE) { result = -1; @@ -11444,13 +11433,7 @@ os_lseek_impl(PyObject *module, int fd, Py_off_t position, int how) } } if (result >= 0) { - LARGE_INTEGER distance, newdistance; - distance.QuadPart = position; - if (SetFilePointerEx(h, distance, &newdistance, how)) { - result = newdistance.QuadPart; - } else { - result = -1; - } + result = _lseeki64(fd, position, how); } #else result = lseek(fd, position, how); @@ -11458,11 +11441,6 @@ os_lseek_impl(PyObject *module, int fd, Py_off_t position, int how) _Py_END_SUPPRESS_IPH Py_END_ALLOW_THREADS if (result < 0) { -#ifdef MS_WINDOWS - if (errno == 0) { - errno = winerror_to_errno(GetLastError()); - } -#endif posix_error(); } From 2f851cf874c17d9eb9d877fcdd64c207ffbd42dd Mon Sep 17 00:00:00 2001 From: AN Long Date: Sun, 11 May 2025 22:22:22 +0900 Subject: [PATCH 06/12] Simplify the implmentation --- Modules/posixmodule.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 75554b7000072a..c790e46ce8ec4c 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -11407,7 +11407,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=f096e754c5367504]*/ { - Py_off_t result = 0; + Py_off_t result = -1; #ifdef SEEK_SET /* Turn 0, 1, 2 into SEEK_{SET,CUR,END} */ @@ -11422,19 +11422,14 @@ os_lseek_impl(PyObject *module, int fd, Py_off_t position, int how) _Py_BEGIN_SUPPRESS_IPH #ifdef MS_WINDOWS HANDLE h = (HANDLE)_get_osfhandle(fd); - if (h == INVALID_HANDLE_VALUE) { - result = -1; - } - if (result >= 0) { - if (GetFileType(h) != FILE_TYPE_DISK) { + if (h != INVALID_HANDLE_VALUE) { + if (GetFileType(h) == FILE_TYPE_DISK) { + result = _lseeki64(fd, position, how); + } else { // Only file is seekable errno = ESPIPE; - result = -1; } } - if (result >= 0) { - result = _lseeki64(fd, position, how); - } #else result = lseek(fd, position, how); #endif From 6aad19e8a0537df016bc621954b7c3311f42e715 Mon Sep 17 00:00:00 2001 From: AN Long Date: Sun, 11 May 2025 23:39:14 +0900 Subject: [PATCH 07/12] Update by review --- Modules/posixmodule.c | 1 - 1 file changed, 1 deletion(-) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index c790e46ce8ec4c..14580ec7c4c800 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -219,7 +219,6 @@ # if defined(MS_WINDOWS_DESKTOP) || defined(MS_WINDOWS_SYSTEM) # define HAVE_SYMLINK # endif /* MS_WINDOWS_DESKTOP | MS_WINDOWS_SYSTEM */ -extern int winerror_to_errno(int); #endif From 7e1ca5d9208578abb29b2da91c69e72f4f5d7fde Mon Sep 17 00:00:00 2001 From: AN Long Date: Mon, 9 Feb 2026 23:06:11 +0900 Subject: [PATCH 08/12] Only reject FILE_TYPE_PIPE --- Modules/posixmodule.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 156795376b126a..1a1a10e532b561 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -11931,11 +11931,10 @@ os_lseek_impl(PyObject *module, int fd, Py_off_t position, int how) #ifdef MS_WINDOWS HANDLE h = (HANDLE)_get_osfhandle(fd); if (h != INVALID_HANDLE_VALUE) { - if (GetFileType(h) == FILE_TYPE_DISK) { - result = _lseeki64(fd, position, how); - } else { - // Only file is seekable + if (GetFileType(h) == FILE_TYPE_PIPE) { errno = ESPIPE; + } else { + result = _lseeki64(fd, position, how); } } #else From 99ba2b0fb3e307ad8c4c3a6c04da3a660ae28668 Mon Sep 17 00:00:00 2001 From: AN Long Date: Mon, 9 Feb 2026 23:09:36 +0900 Subject: [PATCH 09/12] Check in fileio.c also --- Modules/_io/fileio.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Modules/_io/fileio.c b/Modules/_io/fileio.c index 5d7741fdd830a5..c0baef31b88a39 100644 --- a/Modules/_io/fileio.c +++ b/Modules/_io/fileio.c @@ -989,7 +989,13 @@ 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 From b17892bf3456f709fa224bb4df1428291e1a6aef Mon Sep 17 00:00:00 2001 From: AN Long Date: Mon, 9 Feb 2026 23:39:11 +0900 Subject: [PATCH 10/12] Format codes --- Modules/posixmodule.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 1a1a10e532b561..62de1417ce4f32 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -11930,12 +11930,10 @@ os_lseek_impl(PyObject *module, int fd, Py_off_t position, int how) _Py_BEGIN_SUPPRESS_IPH #ifdef MS_WINDOWS HANDLE h = (HANDLE)_get_osfhandle(fd); - if (h != INVALID_HANDLE_VALUE) { - if (GetFileType(h) == FILE_TYPE_PIPE) { - errno = ESPIPE; - } else { - result = _lseeki64(fd, position, how); - } + if (h != INVALID_HANDLE_VALUE && GetFileType(h) == FILE_TYPE_PIPE) { + errno = ESPIPE; + } else { + result = _lseeki64(fd, position, how); } #else result = lseek(fd, position, how); From f35b44af5eaa3a32be17c9d7133a11ccbf1eb532 Mon Sep 17 00:00:00 2001 From: AN Long Date: Tue, 10 Feb 2026 00:18:29 +0900 Subject: [PATCH 11/12] Apply suggestions from code review Co-authored-by: Serhiy Storchaka --- Modules/_io/fileio.c | 3 ++- Modules/posixmodule.c | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Modules/_io/fileio.c b/Modules/_io/fileio.c index c0baef31b88a39..14f3285e30eb64 100644 --- a/Modules/_io/fileio.c +++ b/Modules/_io/fileio.c @@ -993,7 +993,8 @@ portable_lseek(fileio *self, PyObject *posobj, int whence, bool suppress_pipe_er if (h != INVALID_HANDLE_VALUE && GetFileType(h) == FILE_TYPE_PIPE) { res = -1; errno = ESPIPE; - } else { + } + else { res = _lseeki64(fd, pos, whence); } #else diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 62de1417ce4f32..ce6bf7c79dca3a 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -11932,7 +11932,8 @@ os_lseek_impl(PyObject *module, int fd, Py_off_t position, int how) HANDLE h = (HANDLE)_get_osfhandle(fd); if (h != INVALID_HANDLE_VALUE && GetFileType(h) == FILE_TYPE_PIPE) { errno = ESPIPE; - } else { + } + else { result = _lseeki64(fd, position, how); } #else From d0a2498d28aa37f36d7ea3458339dce296926a38 Mon Sep 17 00:00:00 2001 From: AN Long Date: Tue, 10 Feb 2026 00:45:34 +0900 Subject: [PATCH 12/12] Remove the buffering in named pipe test because pipe is not seekable --- Lib/test/test_winapi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_winapi.py b/Lib/test/test_winapi.py index a1c0b80d47e4d4..d2add8621b9b83 100644 --- a/Lib/test/test_winapi.py +++ b/Lib/test/test_winapi.py @@ -144,7 +144,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):