From 675656ffbaca8b9c7982a3a5301b517b14f5cfa7 Mon Sep 17 00:00:00 2001 From: Sergei Zimmerman Date: Wed, 17 Dec 2025 23:45:18 +0300 Subject: [PATCH 1/4] libutil: Fix canonPath, makeTempPath and createTempDir on windows This at least makes canonPath not consider the drive letter as a path component. There still some issues with it on windows, but at least this gets us through some of the libutil-tests. Also since we don't want to change which env variables nix considers we don't use std::filesystem::temp_directory_path and implement the windows version directly. --- src/libutil-tests/file-system.cc | 7 ++++++ src/libutil/file-system.cc | 10 --------- .../include/nix/util/file-path-impl.hh | 22 +++++++++++++++++++ src/libutil/include/nix/util/file-system.hh | 2 ++ src/libutil/unix/file-system.cc | 6 +++++ src/libutil/windows/file-system.cc | 14 ++++++++++-- 6 files changed, 49 insertions(+), 12 deletions(-) diff --git a/src/libutil-tests/file-system.cc b/src/libutil-tests/file-system.cc index 1551227cb..f1087bd7a 100644 --- a/src/libutil-tests/file-system.cc +++ b/src/libutil-tests/file-system.cc @@ -416,4 +416,11 @@ TEST(FdSource, restartWorks) EXPECT_EQ(source.drain(), ""); } +TEST(createTempDir, works) +{ + auto tmpDir = createTempDir(); + nix::AutoDelete delTmpDir(tmpDir, /*recursive=*/true); + ASSERT_TRUE(std::filesystem::is_directory(tmpDir)); +} + } // namespace nix diff --git a/src/libutil/file-system.cc b/src/libutil/file-system.cc index 4851d8cfb..000259777 100644 --- a/src/libutil/file-system.cc +++ b/src/libutil/file-system.cc @@ -115,9 +115,6 @@ Path canonPath(PathView path, bool resolveSymlinks) if (!isAbsolute(path)) throw Error("not an absolute path: '%1%'", path); - // For Windows - auto rootName = std::filesystem::path{path}.root_name(); - /* This just exists because we cannot set the target of `remaining` (the callback parameter) directly to a newly-constructed string, since it is `std::string_view`. */ @@ -147,8 +144,6 @@ Path canonPath(PathView path, bool resolveSymlinks) } }); - if (!rootName.empty()) - ret = rootName.string() + std::move(ret); return ret; } @@ -672,11 +667,6 @@ void AutoUnmount::cancel() ////////////////////////////////////////////////////////////////////// -std::filesystem::path defaultTempDir() -{ - return getEnvNonEmpty("TMPDIR").value_or("/tmp"); -} - std::filesystem::path createTempDir(const std::filesystem::path & tmpRoot, const std::string & prefix, mode_t mode) { while (1) { diff --git a/src/libutil/include/nix/util/file-path-impl.hh b/src/libutil/include/nix/util/file-path-impl.hh index 91c1a58cd..174eeb1be 100644 --- a/src/libutil/include/nix/util/file-path-impl.hh +++ b/src/libutil/include/nix/util/file-path-impl.hh @@ -40,6 +40,11 @@ struct UnixPathTrait { return path.rfind('/', from); } + + static size_t rootNameLen(StringView) + { + return 0; + } }; /** @@ -83,6 +88,18 @@ struct WindowsPathTrait size_t p2 = path.rfind(preferredSep, from); return p1 == String::npos ? p2 : p2 == String::npos ? p1 : std::max(p1, p2); } + + static size_t rootNameLen(StringView path) + { + if (path.size() >= 2 && path[1] == ':') { + char driveLetter = path[0]; + if ((driveLetter >= 'A' && driveLetter <= 'Z') || (driveLetter >= 'a' && driveLetter <= 'z')) + return 2; + } + /* TODO: This needs to also handle UNC paths. + * https://learn.microsoft.com/en-us/dotnet/standard/io/file-path-formats#unc-paths */ + return 0; + } }; template @@ -116,6 +133,11 @@ typename PathDict::String canonPathInner(typename PathDict::StringView remaining typename PathDict::String result; result.reserve(256); + if (auto rootNameLength = PathDict::rootNameLen(remaining)) { + result += remaining.substr(0, rootNameLength); /* Copy drive letter verbatim. */ + remaining.remove_prefix(rootNameLength); + } + while (true) { /* Skip slashes. */ diff --git a/src/libutil/include/nix/util/file-system.hh b/src/libutil/include/nix/util/file-system.hh index ba409fac8..ab1a90647 100644 --- a/src/libutil/include/nix/util/file-system.hh +++ b/src/libutil/include/nix/util/file-system.hh @@ -362,6 +362,8 @@ std::pair createTempFile(const Path & prefix = "nix"); /** * Return `TMPDIR`, or the default temporary directory if unset or empty. + * Uses GetTempPathW on windows which respects TMP, TEMP, USERPROFILE env variables. + * Does not resolve symlinks and the returned path might not be directory or exist at all. */ std::filesystem::path defaultTempDir(); diff --git a/src/libutil/unix/file-system.cc b/src/libutil/unix/file-system.cc index 77b83858f..6069c2d36 100644 --- a/src/libutil/unix/file-system.cc +++ b/src/libutil/unix/file-system.cc @@ -9,6 +9,7 @@ #include #include "nix/util/file-system.hh" +#include "nix/util/environment-variables.hh" #include "util-unix-config-private.hh" @@ -19,6 +20,11 @@ Descriptor openDirectory(const std::filesystem::path & path) return open(path.c_str(), O_RDONLY | O_DIRECTORY | O_CLOEXEC); } +std::filesystem::path defaultTempDir() +{ + return getEnvNonEmpty("TMPDIR").value_or("/tmp"); +} + void setWriteTime( const std::filesystem::path & path, time_t accessedTime, time_t modificationTime, std::optional optIsSymlink) { diff --git a/src/libutil/windows/file-system.cc b/src/libutil/windows/file-system.cc index 22572772e..0c021777b 100644 --- a/src/libutil/windows/file-system.cc +++ b/src/libutil/windows/file-system.cc @@ -1,9 +1,11 @@ #include "nix/util/file-system.hh" +#include "nix/util/windows-error.hh" #include "nix/util/logging.hh" -#ifdef _WIN32 namespace nix { +using namespace nix::windows; + void setWriteTime( const std::filesystem::path & path, time_t accessedTime, time_t modificationTime, std::optional optIsSymlink) { @@ -28,5 +30,13 @@ Descriptor openDirectory(const std::filesystem::path & path) NULL); } +std::filesystem::path defaultTempDir() +{ + wchar_t buf[MAX_PATH + 1]; + DWORD len = GetTempPathW(MAX_PATH + 1, buf); + if (len == 0 || len > MAX_PATH) + throw WinError("getting default temporary directory"); + return std::filesystem::path(buf); +} + } // namespace nix -#endif From f274a7273ac4930a74ad6172152e1e2f824cedf0 Mon Sep 17 00:00:00 2001 From: Sergei Zimmerman Date: Wed, 17 Dec 2025 23:47:40 +0300 Subject: [PATCH 2/4] libutil: Implement deletePath on windows via std::filesystem::remove_all It doesn't track the number of bytes deleted, but since this code is security critical also we can split unix and windows implementations. If the need arises we can implement a smarter recursive deletion function ourselves in the future. Review with --color-moved. --- src/libutil/file-system.cc | 150 ---------------------------- src/libutil/unix/file-system.cc | 152 +++++++++++++++++++++++++++++ src/libutil/windows/file-system.cc | 14 +++ 3 files changed, 166 insertions(+), 150 deletions(-) diff --git a/src/libutil/file-system.cc b/src/libutil/file-system.cc index 000259777..e06b022fc 100644 --- a/src/libutil/file-system.cc +++ b/src/libutil/file-system.cc @@ -375,14 +375,6 @@ void syncParent(const Path & path) fd.fsync(); } -#ifdef __FreeBSD__ -# define MOUNTEDPATHS_PARAM , std::set & mountedPaths -# define MOUNTEDPATHS_ARG , mountedPaths -#else -# define MOUNTEDPATHS_PARAM -# define MOUNTEDPATHS_ARG -#endif - void recursiveSync(const Path & path) { /* If it's a file or symlink, just fsync and return. */ @@ -427,129 +419,6 @@ void recursiveSync(const Path & path) } } -static void _deletePath( - Descriptor parentfd, - const std::filesystem::path & path, - uint64_t & bytesFreed, - std::exception_ptr & ex MOUNTEDPATHS_PARAM) -{ -#ifndef _WIN32 - checkInterrupt(); - -# ifdef __FreeBSD__ - // In case of emergency (unmount fails for some reason) not recurse into mountpoints. - // This prevents us from tearing up the nullfs-mounted nix store. - if (mountedPaths.find(path) != mountedPaths.end()) { - return; - } -# endif - - std::string name(path.filename()); - assert(name != "." && name != ".." && !name.empty()); - - struct stat st; - if (fstatat(parentfd, name.c_str(), &st, AT_SYMLINK_NOFOLLOW) == -1) { - if (errno == ENOENT) - return; - throw SysError("getting status of %1%", path); - } - - if (!S_ISDIR(st.st_mode)) { - /* We are about to delete a file. Will it likely free space? */ - - switch (st.st_nlink) { - /* Yes: last link. */ - case 1: - bytesFreed += st.st_size; - break; - /* Maybe: yes, if 'auto-optimise-store' or manual optimisation - was performed. Instead of checking for real let's assume - it's an optimised file and space will be freed. - - In worst case we will double count on freed space for files - with exactly two hardlinks for unoptimised packages. - */ - case 2: - bytesFreed += st.st_size; - break; - /* No: 3+ links. */ - default: - break; - } - } - - if (S_ISDIR(st.st_mode)) { - /* Make the directory accessible. */ - const auto PERM_MASK = S_IRUSR | S_IWUSR | S_IXUSR; - if ((st.st_mode & PERM_MASK) != PERM_MASK) { - if (fchmodat(parentfd, name.c_str(), st.st_mode | PERM_MASK, 0) == -1) - throw SysError("chmod %1%", path); - } - - int fd = openat(parentfd, name.c_str(), O_RDONLY | O_DIRECTORY | O_NOFOLLOW); - if (fd == -1) - throw SysError("opening directory %1%", path); - AutoCloseDir dir(fdopendir(fd)); - if (!dir) - throw SysError("opening directory %1%", path); - - struct dirent * dirent; - while (errno = 0, dirent = readdir(dir.get())) { /* sic */ - checkInterrupt(); - std::string childName = dirent->d_name; - if (childName == "." || childName == "..") - continue; - _deletePath(dirfd(dir.get()), path / childName, bytesFreed, ex MOUNTEDPATHS_ARG); - } - if (errno) - throw SysError("reading directory %1%", path); - } - - int flags = S_ISDIR(st.st_mode) ? AT_REMOVEDIR : 0; - if (unlinkat(parentfd, name.c_str(), flags) == -1) { - if (errno == ENOENT) - return; - try { - throw SysError("cannot unlink %1%", path); - } catch (...) { - if (!ex) - ex = std::current_exception(); - else - ignoreExceptionExceptInterrupt(); - } - } -#else - // TODO implement - throw UnimplementedError("_deletePath"); -#endif -} - -static void _deletePath(const std::filesystem::path & path, uint64_t & bytesFreed MOUNTEDPATHS_PARAM) -{ - assert(path.is_absolute()); - assert(path.parent_path() != path); - - AutoCloseFD dirfd = toDescriptor(open(path.parent_path().string().c_str(), O_RDONLY)); - if (!dirfd) { - if (errno == ENOENT) - return; - throw SysError("opening directory %s", path.parent_path()); - } - - std::exception_ptr ex; - - _deletePath(dirfd.get(), path, bytesFreed, ex MOUNTEDPATHS_ARG); - - if (ex) - std::rethrow_exception(ex); -} - -void deletePath(const std::filesystem::path & path) -{ - uint64_t dummy; - deletePath(path, dummy); -} - void createDir(const Path & path, mode_t mode) { if (mkdir( @@ -572,25 +441,6 @@ void createDirs(const std::filesystem::path & path) } } -void deletePath(const std::filesystem::path & path, uint64_t & bytesFreed) -{ - // Activity act(*logger, lvlDebug, "recursively deleting path '%1%'", path); -#ifdef __FreeBSD__ - std::set mountedPaths; - struct statfs * mntbuf; - int count; - if ((count = getmntinfo(&mntbuf, MNT_WAIT)) < 0) { - throw SysError("getmntinfo"); - } - - for (int i = 0; i < count; i++) { - mountedPaths.emplace(mntbuf[i].f_mntonname); - } -#endif - bytesFreed = 0; - _deletePath(path, bytesFreed MOUNTEDPATHS_ARG); -} - ////////////////////////////////////////////////////////////////////// AutoDelete::AutoDelete() diff --git a/src/libutil/unix/file-system.cc b/src/libutil/unix/file-system.cc index 6069c2d36..7086cb258 100644 --- a/src/libutil/unix/file-system.cc +++ b/src/libutil/unix/file-system.cc @@ -10,6 +10,8 @@ #include "nix/util/file-system.hh" #include "nix/util/environment-variables.hh" +#include "nix/util/signals.hh" +#include "nix/util/util.hh" #include "util-unix-config-private.hh" @@ -72,4 +74,154 @@ void setWriteTime( #endif } +#ifdef __FreeBSD__ +# define MOUNTEDPATHS_PARAM , std::set & mountedPaths +# define MOUNTEDPATHS_ARG , mountedPaths +#else +# define MOUNTEDPATHS_PARAM +# define MOUNTEDPATHS_ARG +#endif + +static void _deletePath( + Descriptor parentfd, + const std::filesystem::path & path, + uint64_t & bytesFreed, + std::exception_ptr & ex MOUNTEDPATHS_PARAM) +{ +#ifndef _WIN32 + checkInterrupt(); + +# ifdef __FreeBSD__ + // In case of emergency (unmount fails for some reason) not recurse into mountpoints. + // This prevents us from tearing up the nullfs-mounted nix store. + if (mountedPaths.find(path) != mountedPaths.end()) { + return; + } +# endif + + std::string name(path.filename()); + assert(name != "." && name != ".." && !name.empty()); + + struct stat st; + if (fstatat(parentfd, name.c_str(), &st, AT_SYMLINK_NOFOLLOW) == -1) { + if (errno == ENOENT) + return; + throw SysError("getting status of %1%", path); + } + + if (!S_ISDIR(st.st_mode)) { + /* We are about to delete a file. Will it likely free space? */ + + switch (st.st_nlink) { + /* Yes: last link. */ + case 1: + bytesFreed += st.st_size; + break; + /* Maybe: yes, if 'auto-optimise-store' or manual optimisation + was performed. Instead of checking for real let's assume + it's an optimised file and space will be freed. + + In worst case we will double count on freed space for files + with exactly two hardlinks for unoptimised packages. + */ + case 2: + bytesFreed += st.st_size; + break; + /* No: 3+ links. */ + default: + break; + } + } + + if (S_ISDIR(st.st_mode)) { + /* Make the directory accessible. */ + const auto PERM_MASK = S_IRUSR | S_IWUSR | S_IXUSR; + if ((st.st_mode & PERM_MASK) != PERM_MASK) { + if (fchmodat(parentfd, name.c_str(), st.st_mode | PERM_MASK, 0) == -1) + throw SysError("chmod %1%", path); + } + + int fd = openat(parentfd, name.c_str(), O_RDONLY | O_DIRECTORY | O_NOFOLLOW); + if (fd == -1) + throw SysError("opening directory %1%", path); + AutoCloseDir dir(fdopendir(fd)); + if (!dir) + throw SysError("opening directory %1%", path); + + struct dirent * dirent; + while (errno = 0, dirent = readdir(dir.get())) { /* sic */ + checkInterrupt(); + std::string childName = dirent->d_name; + if (childName == "." || childName == "..") + continue; + _deletePath(dirfd(dir.get()), path / childName, bytesFreed, ex MOUNTEDPATHS_ARG); + } + if (errno) + throw SysError("reading directory %1%", path); + } + + int flags = S_ISDIR(st.st_mode) ? AT_REMOVEDIR : 0; + if (unlinkat(parentfd, name.c_str(), flags) == -1) { + if (errno == ENOENT) + return; + try { + throw SysError("cannot unlink %1%", path); + } catch (...) { + if (!ex) + ex = std::current_exception(); + else + ignoreExceptionExceptInterrupt(); + } + } +#else + // TODO implement + throw UnimplementedError("_deletePath"); +#endif +} + +static void _deletePath(const std::filesystem::path & path, uint64_t & bytesFreed MOUNTEDPATHS_PARAM) +{ + assert(path.is_absolute()); + assert(path.parent_path() != path); + + AutoCloseFD dirfd = toDescriptor(open(path.parent_path().string().c_str(), O_RDONLY)); + if (!dirfd) { + if (errno == ENOENT) + return; + throw SysError("opening directory %s", path.parent_path()); + } + + std::exception_ptr ex; + + _deletePath(dirfd.get(), path, bytesFreed, ex MOUNTEDPATHS_ARG); + + if (ex) + std::rethrow_exception(ex); +} + +void deletePath(const std::filesystem::path & path) +{ + uint64_t dummy; + deletePath(path, dummy); +} + +void deletePath(const std::filesystem::path & path, uint64_t & bytesFreed) +{ + // Activity act(*logger, lvlDebug, "recursively deleting path '%1%'", path); +#ifdef __FreeBSD__ + std::set mountedPaths; + struct statfs * mntbuf; + int count; + if ((count = getmntinfo(&mntbuf, MNT_WAIT)) < 0) { + throw SysError("getmntinfo"); + } + + for (int i = 0; i < count; i++) { + mountedPaths.emplace(mntbuf[i].f_mntonname); + } +#endif + bytesFreed = 0; + _deletePath(path, bytesFreed MOUNTEDPATHS_ARG); +} + } // namespace nix diff --git a/src/libutil/windows/file-system.cc b/src/libutil/windows/file-system.cc index 0c021777b..79931d591 100644 --- a/src/libutil/windows/file-system.cc +++ b/src/libutil/windows/file-system.cc @@ -39,4 +39,18 @@ std::filesystem::path defaultTempDir() return std::filesystem::path(buf); } +void deletePath(const std::filesystem::path & path) +{ + std::error_code ec; + std::filesystem::remove_all(path, ec); + if (ec && ec != std::errc::no_such_file_or_directory) + throw SysError(ec.default_error_condition().value(), "recursively deleting %1%", path); +} + +void deletePath(const std::filesystem::path & path, uint64_t & bytesFreed) +{ + bytesFreed = 0; + deletePath(path); +} + } // namespace nix From 89dc57f6aae1c5c3dd6ecd76502f171cd85d8f42 Mon Sep 17 00:00:00 2001 From: Sergei Zimmerman Date: Wed, 17 Dec 2025 23:47:53 +0300 Subject: [PATCH 3/4] 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 From 0695630eb581220b587f1b4d229498bbb099a0f9 Mon Sep 17 00:00:00 2001 From: Sergei Zimmerman Date: Wed, 17 Dec 2025 23:48:35 +0300 Subject: [PATCH 4/4] libutil: Fix FdSource::read on Windows We need to signal the EOF condition, otherwise the read never terminates. --- src/libutil/serialise.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libutil/serialise.cc b/src/libutil/serialise.cc index 53731557a..6af23b2e8 100644 --- a/src/libutil/serialise.cc +++ b/src/libutil/serialise.cc @@ -163,11 +163,11 @@ size_t FdSource::readUnbuffered(char * data, size_t len) _good = false; throw SysError("reading from file"); } +#endif if (n == 0) { _good = false; throw EndOfFile(std::string(*endOfFileError)); } -#endif read += n; return n; }