From 675656ffbaca8b9c7982a3a5301b517b14f5cfa7 Mon Sep 17 00:00:00 2001 From: Sergei Zimmerman Date: Wed, 17 Dec 2025 23:45:18 +0300 Subject: [PATCH] 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