From 7d7cd6e07b65d7583f1c7648fc042fbe30352313 Mon Sep 17 00:00:00 2001 From: qmuntal Date: Thu, 21 Aug 2025 10:45:23 +0200 Subject: [PATCH] internal/poll: don't call SetFilePointerEx in Seek for overlapped handles Overlapped handles don't have the file pointer updated when performing I/O operations, so there is no need to call syscall.SetFilePointerEx in FD.Seek. Updating the in-memory offset is sufficient. Updates #74951 (provides a more complete fix) Change-Id: Ibede6625cdbd501fc92cfdf8ce2782ec291af2b6 Reviewed-on: https://go-review.googlesource.com/c/go/+/698035 LUCI-TryBot-Result: Go LUCI Reviewed-by: Dmitri Shuralyov Reviewed-by: Damien Neil --- src/internal/poll/fd_windows.go | 28 +++++++++++++++---- .../syscall/windows/syscall_windows.go | 2 ++ .../syscall/windows/zsyscall_windows.go | 9 ++++++ src/os/os_windows_test.go | 23 +++++++++++++-- 4 files changed, 55 insertions(+), 7 deletions(-) diff --git a/src/internal/poll/fd_windows.go b/src/internal/poll/fd_windows.go index 23500d3e39..4d466e5b64 100644 --- a/src/internal/poll/fd_windows.go +++ b/src/internal/poll/fd_windows.go @@ -1165,11 +1165,29 @@ func (fd *FD) Seek(offset int64, whence int) (int64, error) { } defer fd.readWriteUnlock() - if !fd.isBlocking && whence == io.SeekCurrent { - // Windows doesn't keep the file pointer for overlapped file handles. - // We do it ourselves in case to account for any read or write - // operations that may have occurred. - offset += fd.offset + if !fd.isBlocking { + // Windows doesn't use the file pointer for overlapped file handles, + // there is no point on calling syscall.Seek. + var newOffset int64 + switch whence { + case io.SeekStart: + newOffset = offset + case io.SeekCurrent: + newOffset = fd.offset + offset + case io.SeekEnd: + var size int64 + if err := windows.GetFileSizeEx(fd.Sysfd, &size); err != nil { + return 0, err + } + newOffset = size + offset + default: + return 0, windows.ERROR_INVALID_PARAMETER + } + if newOffset < 0 { + return 0, windows.ERROR_NEGATIVE_SEEK + } + fd.setOffset(newOffset) + return newOffset, nil } n, err := syscall.Seek(fd.Sysfd, offset, whence) fd.setOffset(n) diff --git a/src/internal/syscall/windows/syscall_windows.go b/src/internal/syscall/windows/syscall_windows.go index 905cabc81e..b60648ea29 100644 --- a/src/internal/syscall/windows/syscall_windows.go +++ b/src/internal/syscall/windows/syscall_windows.go @@ -39,6 +39,7 @@ const ( ERROR_NOT_SUPPORTED syscall.Errno = 50 ERROR_CALL_NOT_IMPLEMENTED syscall.Errno = 120 ERROR_INVALID_NAME syscall.Errno = 123 + ERROR_NEGATIVE_SEEK syscall.Errno = 131 ERROR_LOCK_FAILED syscall.Errno = 167 ERROR_IO_INCOMPLETE syscall.Errno = 996 ERROR_NO_TOKEN syscall.Errno = 1008 @@ -195,6 +196,7 @@ const ( //sys SetFileInformationByHandle(handle syscall.Handle, fileInformationClass uint32, buf unsafe.Pointer, bufsize uint32) (err error) = kernel32.SetFileInformationByHandle //sys VirtualQuery(address uintptr, buffer *MemoryBasicInformation, length uintptr) (err error) = kernel32.VirtualQuery //sys GetTempPath2(buflen uint32, buf *uint16) (n uint32, err error) = GetTempPath2W +//sys GetFileSizeEx(handle syscall.Handle, size *int64) (err error) = kernel32.GetFileSizeEx const ( // flags for CreateToolhelp32Snapshot diff --git a/src/internal/syscall/windows/zsyscall_windows.go b/src/internal/syscall/windows/zsyscall_windows.go index 53f456c5cf..fb8bc80629 100644 --- a/src/internal/syscall/windows/zsyscall_windows.go +++ b/src/internal/syscall/windows/zsyscall_windows.go @@ -73,6 +73,7 @@ var ( procGetConsoleCP = modkernel32.NewProc("GetConsoleCP") procGetCurrentThread = modkernel32.NewProc("GetCurrentThread") procGetFileInformationByHandleEx = modkernel32.NewProc("GetFileInformationByHandleEx") + procGetFileSizeEx = modkernel32.NewProc("GetFileSizeEx") procGetFinalPathNameByHandleW = modkernel32.NewProc("GetFinalPathNameByHandleW") procGetModuleFileNameW = modkernel32.NewProc("GetModuleFileNameW") procGetModuleHandleW = modkernel32.NewProc("GetModuleHandleW") @@ -326,6 +327,14 @@ func GetFileInformationByHandleEx(handle syscall.Handle, class uint32, info *byt return } +func GetFileSizeEx(handle syscall.Handle, size *int64) (err error) { + r1, _, e1 := syscall.Syscall(procGetFileSizeEx.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(size)), 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + func GetFinalPathNameByHandle(file syscall.Handle, filePath *uint16, filePathSize uint32, flags uint32) (n uint32, err error) { r0, _, e1 := syscall.SyscallN(procGetFinalPathNameByHandleW.Addr(), uintptr(file), uintptr(unsafe.Pointer(filePath)), uintptr(filePathSize), uintptr(flags)) n = uint32(r0) diff --git a/src/os/os_windows_test.go b/src/os/os_windows_test.go index 8258c4ff4b..cd2413d26d 100644 --- a/src/os/os_windows_test.go +++ b/src/os/os_windows_test.go @@ -1868,10 +1868,26 @@ func TestFileOverlappedSeek(t *testing.T) { if n != int64(len(buf)) { t.Errorf("expected file pointer to be at offset %d, got %d", len(buf), n) } + if n, err = f.Seek(1, io.SeekStart); err != nil { + t.Fatal(err) + } else if n != 1 { + t.Errorf("expected file pointer to be at offset %d, got %d", 1, n) + } + if n, err = f.Seek(-1, io.SeekEnd); err != nil { + t.Fatal(err) + } else if n != int64(len(content)-1) { + t.Errorf("expected file pointer to be at offset %d, got %d", len(content)-1, n) + } + if _, err := f.Seek(-1, io.SeekStart); !errors.Is(err, windows.ERROR_NEGATIVE_SEEK) { + t.Errorf("expected ERROR_NEGATIVE_SEEK, got %v", err) + } + if _, err := f.Seek(0, -1); !errors.Is(err, windows.ERROR_INVALID_PARAMETER) { + t.Errorf("expected ERROR_INVALID_PARAMETER, got %v", err) + } } -func TestFileOverlappedReadAtVolume(t *testing.T) { - // Test that we can use File.ReadAt with an overlapped volume handle. +func TestFileOverlappedReadAtSeekVolume(t *testing.T) { + // Test that we can use File.ReadAt and File.Seek with an overlapped volume handle. // See https://go.dev/issues/74951. t.Parallel() name := `\\.\` + filepath.VolumeName(t.TempDir()) @@ -1888,6 +1904,9 @@ func TestFileOverlappedReadAtVolume(t *testing.T) { if _, err := f.ReadAt(buf[:], 0); err != nil { t.Fatal(err) } + if _, err := f.Seek(0, io.SeekCurrent); err != nil { + t.Fatal(err) + } } func TestPipe(t *testing.T) { -- 2.52.0