From 89dc57f6aae1c5c3dd6ecd76502f171cd85d8f42 Mon Sep 17 00:00:00 2001 From: Sergei Zimmerman Date: Wed, 17 Dec 2025 23:47:53 +0300 Subject: [PATCH] libutil: Implement HANDLE-based lseek for Windows For windows we should live fully in the HANDLE land instead of converting back-n-forth (which sometimes is destructive). Using native API is much better for this. --- src/libutil-tests/file-system.cc | 10 ++-- .../include/nix/util/file-descriptor.hh | 10 ++++ src/libutil/nar-accessor.cc | 3 +- src/libutil/serialise.cc | 4 +- src/libutil/windows/file-descriptor.cc | 55 ++++++++++++++----- 5 files changed, 60 insertions(+), 22 deletions(-) diff --git a/src/libutil-tests/file-system.cc b/src/libutil-tests/file-system.cc index f1087bd7a..49c123e79 100644 --- a/src/libutil-tests/file-system.cc +++ b/src/libutil-tests/file-system.cc @@ -388,14 +388,13 @@ TEST(openFileEnsureBeneathNoSymlinks, works) TEST(createAnonymousTempFile, works) { auto fd = createAnonymousTempFile(); - auto fd_ = fromDescriptorReadOnly(fd.get()); writeFull(fd.get(), "test"); - lseek(fd_, 0, SEEK_SET); + lseek(fd.get(), 0, SEEK_SET); FdSource source{fd.get()}; EXPECT_EQ(source.drain(), "test"); - lseek(fd_, 0, SEEK_END); + lseek(fd.get(), 0, SEEK_END); writeFull(fd.get(), "test"); - lseek(fd_, 0, SEEK_SET); + lseek(fd.get(), 0, SEEK_SET); EXPECT_EQ(source.drain(), "testtest"); } @@ -406,9 +405,8 @@ TEST(createAnonymousTempFile, works) TEST(FdSource, restartWorks) { auto fd = createAnonymousTempFile(); - auto fd_ = fromDescriptorReadOnly(fd.get()); writeFull(fd.get(), "hello world"); - lseek(fd_, 0, SEEK_SET); + lseek(fd.get(), 0, SEEK_SET); FdSource source{fd.get()}; EXPECT_EQ(source.drain(), "hello world"); source.restart(); diff --git a/src/libutil/include/nix/util/file-descriptor.hh b/src/libutil/include/nix/util/file-descriptor.hh index 441ec4d4f..aa6d4e8f0 100644 --- a/src/libutil/include/nix/util/file-descriptor.hh +++ b/src/libutil/include/nix/util/file-descriptor.hh @@ -261,4 +261,14 @@ Descriptor openFileEnsureBeneathNoSymlinks(Descriptor dirFd, const CanonPath & p MakeError(EndOfFile, Error); +#ifdef _WIN32 + +/** + * Windows specific replacement for POSIX `lseek` that operates on a `HANDLE` and not + * a file descriptor. + */ +off_t lseek(Descriptor fd, off_t offset, int whence); + +#endif + } // namespace nix diff --git a/src/libutil/nar-accessor.cc b/src/libutil/nar-accessor.cc index 5644ca408..e43d43aac 100644 --- a/src/libutil/nar-accessor.cc +++ b/src/libutil/nar-accessor.cc @@ -1,4 +1,5 @@ #include "nix/util/nar-accessor.hh" +#include "nix/util/file-descriptor.hh" #include "nix/util/archive.hh" #include @@ -281,7 +282,7 @@ GetNarBytes seekableGetNarBytes(const Path & path) GetNarBytes seekableGetNarBytes(Descriptor fd) { return [fd](uint64_t offset, uint64_t length) { - if (::lseek(fromDescriptorReadOnly(fd), offset, SEEK_SET) == -1) + if (lseek(fd, offset, SEEK_SET) == -1) throw SysError("seeking in file"); std::string buf(length, 0); diff --git a/src/libutil/serialise.cc b/src/libutil/serialise.cc index 9791b4fed..53731557a 100644 --- a/src/libutil/serialise.cc +++ b/src/libutil/serialise.cc @@ -1,4 +1,5 @@ #include "nix/util/serialise.hh" +#include "nix/util/file-descriptor.hh" #include "nix/util/compression.hh" #include "nix/util/signals.hh" #include "nix/util/util.hh" @@ -207,8 +208,7 @@ void FdSource::restart() throw Error("can't seek to the start of a file"); buffer.reset(); read = bufPosIn = bufPosOut = 0; - int fd_ = fromDescriptorReadOnly(fd); - if (lseek(fd_, 0, SEEK_SET) == -1) + if (lseek(fd, 0, SEEK_SET) == -1) throw SysError("seeking to the start of a file"); } diff --git a/src/libutil/windows/file-descriptor.cc b/src/libutil/windows/file-descriptor.cc index 3c3e7ea45..57994eac0 100644 --- a/src/libutil/windows/file-descriptor.cc +++ b/src/libutil/windows/file-descriptor.cc @@ -5,13 +5,12 @@ #include "nix/util/windows-error.hh" #include "nix/util/file-path.hh" -#ifdef _WIN32 -# include -# include -# include -# include -# define WIN32_LEAN_AND_MEAN -# include +#include +#include +#include +#include +#define WIN32_LEAN_AND_MEAN +#include namespace nix { @@ -46,16 +45,16 @@ void writeFull(HANDLE handle, std::string_view s, bool allowInterrupts) if (allowInterrupts) checkInterrupt(); DWORD res; -# if _WIN32_WINNT >= 0x0600 +#if _WIN32_WINNT >= 0x0600 auto path = handleToPath(handle); // debug; do it before because handleToPath changes lasterror if (!WriteFile(handle, s.data(), s.size(), &res, NULL)) { throw WinError("writing to file %1%:%2%", handle, path); } -# else +#else if (!WriteFile(handle, s.data(), s.size(), &res, NULL)) { throw WinError("writing to file %1%", handle); } -# endif +#endif if (res > 0) s.remove_prefix(res); } @@ -120,7 +119,7 @@ void Pipe::create() ////////////////////////////////////////////////////////////////////// -# if _WIN32_WINNT >= 0x0600 +#if _WIN32_WINNT >= 0x0600 std::wstring windows::handleToFileName(HANDLE handle) { @@ -149,7 +148,37 @@ Path windows::handleToPath(HANDLE handle) return os_string_to_string(handleToFileName(handle)); } -# endif +#endif + +off_t lseek(HANDLE h, off_t offset, int whence) +{ + DWORD method; + switch (whence) { + case SEEK_SET: + method = FILE_BEGIN; + break; + case SEEK_CUR: + method = FILE_CURRENT; + break; + case SEEK_END: + method = FILE_END; + break; + default: + throw Error("lseek: invalid whence %d", whence); + } + + LARGE_INTEGER li; + li.QuadPart = offset; + LARGE_INTEGER newPos; + + if (!SetFilePointerEx(h, li, &newPos, method)) { + /* Convert to a POSIX error, since caller code works with this as if it were + a POSIX lseek. */ + errno = std::error_code(GetLastError(), std::system_category()).default_error_condition().value(); + return -1; + } + + return newPos.QuadPart; +} } // namespace nix -#endif