1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-12-17 06:21:06 +01:00

Merge branch 'master' into path-setting

This commit is contained in:
John Ericson 2025-11-26 17:57:45 -05:00
commit 37cf990b41
145 changed files with 2949 additions and 625 deletions

View file

@ -371,13 +371,13 @@ void RootArgs::parseCmdline(const Strings & _cmdline, bool allowShebang)
d.completer(*completions, d.n, d.prefix);
}
Path Args::getCommandBaseDir() const
std::filesystem::path Args::getCommandBaseDir() const
{
assert(parent);
return parent->getCommandBaseDir();
}
Path RootArgs::getCommandBaseDir() const
std::filesystem::path RootArgs::getCommandBaseDir() const
{
return commandBaseDir;
}

View file

@ -7,6 +7,7 @@
#include "nix/util/file-system.hh"
#include "nix/util/processes.hh"
#include "nix/util/signals.hh"
#include "nix/util/environment-variables.hh"
#include <math.h>
#ifdef __APPLE__
@ -65,13 +66,27 @@ void setStackSize(size_t stackSize)
struct rlimit limit;
if (getrlimit(RLIMIT_STACK, &limit) == 0 && static_cast<size_t>(limit.rlim_cur) < stackSize) {
savedStackSize = limit.rlim_cur;
limit.rlim_cur = std::min(static_cast<rlim_t>(stackSize), limit.rlim_max);
if (limit.rlim_max < static_cast<rlim_t>(stackSize)) {
if (getEnv("_NIX_TEST_NO_ENVIRONMENT_WARNINGS") != "1") {
logger->log(
lvlWarn,
HintFmt(
"Stack size hard limit is %1%, which is less than the desired %2%. If possible, increase the hard limit, e.g. with 'ulimit -Hs %3%'.",
limit.rlim_max,
stackSize,
stackSize / 1024)
.str());
}
}
auto requestedSize = std::min(static_cast<rlim_t>(stackSize), limit.rlim_max);
limit.rlim_cur = requestedSize;
if (setrlimit(RLIMIT_STACK, &limit) != 0) {
logger->log(
lvlError,
HintFmt(
"Failed to increase stack size from %1% to %2% (maximum allowed stack size: %3%): %4%",
"Failed to increase stack size from %1% to %2% (desired: %3%, maximum allowed: %4%): %5%",
savedStackSize,
requestedSize,
stackSize,
limit.rlim_max,
std::strerror(errno))
@ -109,7 +124,7 @@ std::optional<Path> getSelfExe()
{
static auto cached = []() -> std::optional<Path> {
#if defined(__linux__) || defined(__GNU__)
return readLink("/proc/self/exe");
return readLink(std::filesystem::path{"/proc/self/exe"});
#elif defined(__APPLE__)
char buf[1024];
uint32_t size = sizeof(buf);

View file

@ -101,9 +101,11 @@ Path absPath(PathView path, std::optional<PathView> dir, bool resolveSymlinks)
return canonPath(path, resolveSymlinks);
}
std::filesystem::path absPath(const std::filesystem::path & path, bool resolveSymlinks)
std::filesystem::path
absPath(const std::filesystem::path & path, const std::filesystem::path * dir_, bool resolveSymlinks)
{
return absPath(path.string(), std::nullopt, resolveSymlinks);
std::optional<std::string> dir = dir_ ? std::optional<std::string>{dir_->string()} : std::nullopt;
return absPath(PathView{path.string()}, dir.transform([](auto & p) { return PathView(p); }), resolveSymlinks);
}
Path canonPath(PathView path, bool resolveSymlinks)
@ -242,10 +244,15 @@ bool pathAccessible(const std::filesystem::path & path)
}
}
Path readLink(const Path & path)
std::filesystem::path readLink(const std::filesystem::path & path)
{
checkInterrupt();
return std::filesystem::read_symlink(path).string();
return std::filesystem::read_symlink(path);
}
Path readLink(const Path & path)
{
return readLink(std::filesystem::path{path}).string();
}
std::string readFile(const Path & path)
@ -669,16 +676,16 @@ void AutoUnmount::cancel()
//////////////////////////////////////////////////////////////////////
std::string defaultTempDir()
std::filesystem::path defaultTempDir()
{
return getEnvNonEmpty("TMPDIR").value_or("/tmp");
}
Path createTempDir(const Path & tmpRoot, const Path & prefix, mode_t mode)
std::filesystem::path createTempDir(const std::filesystem::path & tmpRoot, const std::string & prefix, mode_t mode)
{
while (1) {
checkInterrupt();
Path tmpDir = makeTempPath(tmpRoot, prefix);
std::filesystem::path tmpDir = makeTempPath(tmpRoot, prefix);
if (mkdir(
tmpDir.c_str()
#ifndef _WIN32 // TODO abstract mkdir perms for Windows
@ -720,11 +727,11 @@ std::pair<AutoCloseFD, Path> createTempFile(const Path & prefix)
return {std::move(fd), tmpl};
}
Path makeTempPath(const Path & root, const Path & suffix)
std::filesystem::path makeTempPath(const std::filesystem::path & root, const std::string & suffix)
{
// start the counter at a random value to minimize issues with preexisting temp paths
static std::atomic<uint32_t> counter(std::random_device{}());
auto tmpRoot = canonPath(root.empty() ? defaultTempDir() : root, true);
auto tmpRoot = canonPath(root.empty() ? defaultTempDir().string() : root.string(), true);
return fmt("%1%/%2%-%3%-%4%", tmpRoot, suffix, getpid(), counter.fetch_add(1, std::memory_order_relaxed));
}

View file

@ -84,7 +84,8 @@ void RestoreSink::createDirectory(const CanonPath & path, DirectoryCreatedCallba
RestoreSink dirSink{startFsync};
dirSink.dstPath = append(dstPath, path);
dirSink.dirFd = ::openat(dirFd.get(), path.rel_c_str(), O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
dirSink.dirFd =
unix::openFileEnsureBeneathNoSymlinks(dirFd.get(), path, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
if (!dirSink.dirFd)
throw SysError("opening directory '%s'", dirSink.dstPath.string());
@ -169,7 +170,7 @@ void RestoreSink::createRegularFile(const CanonPath & path, std::function<void(C
constexpr int flags = O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC;
if (!dirFd)
return ::open(p.c_str(), flags, 0666);
return ::openat(dirFd.get(), path.rel_c_str(), flags, 0666);
return unix::openFileEnsureBeneathNoSymlinks(dirFd.get(), path, flags, 0666);
}();
#endif
;

View file

@ -59,7 +59,7 @@ public:
*
* This only returns the correct value after parseCmdline() has run.
*/
virtual Path getCommandBaseDir() const;
virtual std::filesystem::path getCommandBaseDir() const;
protected:

View file

@ -38,7 +38,7 @@ protected:
*
* @see getCommandBaseDir()
*/
Path commandBaseDir = ".";
std::filesystem::path commandBaseDir = ".";
public:
/** Parse the command line, throwing a UsageError if something goes
@ -48,7 +48,7 @@ public:
std::shared_ptr<Completions> completions;
Path getCommandBaseDir() const override;
std::filesystem::path getCommandBaseDir() const override;
protected:

View file

@ -1,6 +1,7 @@
#pragma once
///@file
#include "nix/util/canon-path.hh"
#include "nix/util/types.hh"
#include "nix/util/error.hh"
@ -203,6 +204,26 @@ void closeOnExec(Descriptor fd);
} // namespace unix
#endif
#ifdef __linux__
namespace linux {
/**
* Wrapper around Linux's openat2 syscall introduced in Linux 5.6.
*
* @see https://man7.org/linux/man-pages/man2/openat2.2.html
* @see https://man7.org/linux/man-pages/man2/open_how.2type.html
v*
* @param flags O_* flags
* @param mode Mode for O_{CREAT,TMPFILE}
* @param resolve RESOLVE_* flags
*
* @return nullopt if openat2 is not supported by the kernel.
*/
std::optional<Descriptor> openat2(Descriptor dirFd, const char * path, uint64_t flags, uint64_t mode, uint64_t resolve);
} // namespace linux
#endif
#if defined(_WIN32) && _WIN32_WINNT >= 0x0600
namespace windows {
@ -212,6 +233,45 @@ std::wstring handleToFileName(Descriptor handle);
} // namespace windows
#endif
#ifndef _WIN32
namespace unix {
struct SymlinkNotAllowed : public Error
{
CanonPath path;
SymlinkNotAllowed(CanonPath path)
/* Can't provide better error message, since the parent directory is only known to the caller. */
: Error("relative path '%s' points to a symlink, which is not allowed", path.rel())
, path(std::move(path))
{
}
};
/**
* Safe(r) function to open \param path file relative to \param dirFd, while
* disallowing escaping from a directory and resolving any symlinks in the
* process.
*
* @note When not on Linux or when openat2 is not available this is implemented
* via openat single path component traversal. Uses RESOLVE_BENEATH with openat2
* or O_RESOLVE_BENEATH.
*
* @note Since this is Unix-only path is specified as CanonPath, which models
* Unix-style paths and ensures that there are no .. or . components.
*
* @param flags O_* flags
* @param mode Mode for O_{CREAT,TMPFILE}
*
* @pre path.isRoot() is false
*
* @throws SymlinkNotAllowed if any path components
*/
Descriptor openFileEnsureBeneathNoSymlinks(Descriptor dirFd, const CanonPath & path, int flags, mode_t mode = 0);
} // namespace unix
#endif
MakeError(EndOfFile, Error);
} // namespace nix

View file

@ -55,7 +55,8 @@ inline Path absPath(const Path & path, std::optional<PathView> dir = {}, bool re
return absPath(PathView{path}, dir, resolveSymlinks);
}
std::filesystem::path absPath(const std::filesystem::path & path, bool resolveSymlinks = false);
std::filesystem::path
absPath(const std::filesystem::path & path, const std::filesystem::path * dir = nullptr, bool resolveSymlinks = false);
/**
* Canonicalise a path by removing all `.` or `..` components and
@ -152,6 +153,12 @@ bool pathAccessible(const std::filesystem::path & path);
*/
Path readLink(const Path & path);
/**
* Read the contents (target) of a symbolic link. The result is not
* in any way canonicalised.
*/
std::filesystem::path readLink(const std::filesystem::path & path);
/**
* Open a `Descriptor` with read-only access to the given directory.
*/
@ -327,7 +334,8 @@ typedef std::unique_ptr<DIR, DIRDeleter> AutoCloseDir;
/**
* Create a temporary directory.
*/
Path createTempDir(const Path & tmpRoot = "", const Path & prefix = "nix", mode_t mode = 0755);
std::filesystem::path
createTempDir(const std::filesystem::path & tmpRoot = "", const std::string & prefix = "nix", mode_t mode = 0755);
/**
* Create a temporary file, returning a file handle and its path.
@ -337,7 +345,7 @@ std::pair<AutoCloseFD, Path> createTempFile(const Path & prefix = "nix");
/**
* Return `TMPDIR`, or the default temporary directory if unset or empty.
*/
Path defaultTempDir();
std::filesystem::path defaultTempDir();
/**
* Interpret `exe` as a location in the ambient file system and return
@ -351,7 +359,7 @@ bool isExecutableFileAmbient(const std::filesystem::path & exe);
* The constructed path looks like `<root><suffix>-<pid>-<unique>`. To create a
* path nested in a directory, provide a suffix starting with `/`.
*/
Path makeTempPath(const Path & root, const Path & suffix = ".tmp");
std::filesystem::path makeTempPath(const std::filesystem::path & root, const std::string & suffix = ".tmp");
/**
* Used in various places.

View file

@ -6,18 +6,30 @@
#include "nix/util/experimental-features.hh"
// Following https://github.com/nlohmann/json#how-can-i-use-get-for-non-default-constructiblenon-copyable-types
#define JSON_IMPL_INNER_TO(TYPE) \
struct adl_serializer<TYPE> \
{ \
static void to_json(json & json, const TYPE & t); \
}
#define JSON_IMPL_INNER_FROM(TYPE) \
struct adl_serializer<TYPE> \
{ \
static TYPE from_json(const json & json); \
}
#define JSON_IMPL_INNER(TYPE) \
struct adl_serializer<TYPE> \
{ \
static TYPE from_json(const json & json); \
static void to_json(json & json, const TYPE & t); \
};
}
#define JSON_IMPL(TYPE) \
namespace nlohmann { \
using namespace nix; \
template<> \
JSON_IMPL_INNER(TYPE) \
#define JSON_IMPL(TYPE) \
namespace nlohmann { \
using namespace nix; \
template<> \
JSON_IMPL_INNER(TYPE); \
}
#define JSON_IMPL_WITH_XP_FEATURES(TYPE) \

View file

@ -188,23 +188,23 @@ using namespace nix;
#define ARG fso::Regular<RegularContents>
template<typename RegularContents>
JSON_IMPL_INNER(ARG)
JSON_IMPL_INNER(ARG);
#undef ARG
#define ARG fso::DirectoryT<Child>
template<typename Child>
JSON_IMPL_INNER(ARG)
JSON_IMPL_INNER(ARG);
#undef ARG
template<>
JSON_IMPL_INNER(fso::Symlink)
JSON_IMPL_INNER(fso::Symlink);
template<>
JSON_IMPL_INNER(fso::Opaque)
JSON_IMPL_INNER(fso::Opaque);
#define ARG fso::VariantT<RegularContents, recur>
template<typename RegularContents, bool recur>
JSON_IMPL_INNER(ARG)
JSON_IMPL_INNER(ARG);
#undef ARG
} // namespace nlohmann

View file

@ -447,18 +447,27 @@ struct LengthSource : Source
*/
struct LambdaSink : Sink
{
typedef std::function<void(std::string_view data)> lambda_t;
typedef std::function<void(std::string_view data)> data_t;
typedef std::function<void()> cleanup_t;
lambda_t lambda;
data_t dataFun;
cleanup_t cleanupFun;
LambdaSink(const lambda_t & lambda)
: lambda(lambda)
LambdaSink(
const data_t & dataFun, const cleanup_t & cleanupFun = []() {})
: dataFun(dataFun)
, cleanupFun(cleanupFun)
{
}
~LambdaSink()
{
cleanupFun();
}
void operator()(std::string_view data) override
{
lambda(data);
dataFun(data);
}
};

View file

@ -1,6 +1,8 @@
#pragma once
///@file
#include <filesystem>
#include "nix/util/types.hh"
#ifndef _WIN32
@ -15,43 +17,43 @@ std::string getUserName();
/**
* @return the given user's home directory from /etc/passwd.
*/
Path getHomeOf(uid_t userId);
std::filesystem::path getHomeOf(uid_t userId);
#endif
/**
* @return $HOME or the user's home directory from /etc/passwd.
*/
Path getHome();
std::filesystem::path getHome();
/**
* @return $NIX_CACHE_HOME or $XDG_CACHE_HOME/nix or $HOME/.cache/nix.
*/
Path getCacheDir();
std::filesystem::path getCacheDir();
/**
* @return $NIX_CONFIG_HOME or $XDG_CONFIG_HOME/nix or $HOME/.config/nix.
*/
Path getConfigDir();
std::filesystem::path getConfigDir();
/**
* @return the directories to search for user configuration files
*/
std::vector<Path> getConfigDirs();
std::vector<std::filesystem::path> getConfigDirs();
/**
* @return $NIX_DATA_HOME or $XDG_DATA_HOME/nix or $HOME/.local/share/nix.
*/
Path getDataDir();
std::filesystem::path getDataDir();
/**
* @return $NIX_STATE_HOME or $XDG_STATE_HOME/nix or $HOME/.local/state/nix.
*/
Path getStateDir();
std::filesystem::path getStateDir();
/**
* Create the Nix state directory and return the path to it.
*/
Path createNixStateDir();
std::filesystem::path createNixStateDir();
/**
* Perform tilde expansion on a path, replacing tilde with the user's

View file

@ -6,6 +6,7 @@
#include "nix/util/logging.hh"
#include "nix/util/strings.hh"
#include <filesystem>
#include <functional>
#include <map>
#include <sstream>
@ -58,6 +59,12 @@ Strings quoteStrings(const C & c, char quote = '\'')
return res;
}
inline Strings quoteFSPaths(const std::set<std::filesystem::path> & paths, char quote = '\'')
{
return paths | std::views::transform([&](const auto & p) { return quoteString(p.string(), quote); })
| std::ranges::to<Strings>();
}
/**
* Remove trailing whitespace from a string.
*

View file

@ -1,3 +1,4 @@
#include "nix/util/canon-path.hh"
#include "nix/util/file-system.hh"
#include "nix/util/signals.hh"
#include "nix/util/finally.hh"
@ -7,6 +8,14 @@
#include <unistd.h>
#include <poll.h>
#if defined(__linux__) && defined(__NR_openat2)
# define HAVE_OPENAT2 1
# include <sys/syscall.h>
# include <linux/openat2.h>
#else
# define HAVE_OPENAT2 0
#endif
#include "util-config-private.hh"
#include "util-unix-config-private.hh"
@ -223,4 +232,107 @@ void unix::closeOnExec(int fd)
throw SysError("setting close-on-exec flag");
}
#ifdef __linux__
namespace linux {
std::optional<Descriptor> openat2(Descriptor dirFd, const char * path, uint64_t flags, uint64_t mode, uint64_t resolve)
{
# if HAVE_OPENAT2
/* Cache the result of whether openat2 is not supported. */
static std::atomic_flag unsupported{};
if (!unsupported.test()) {
/* No glibc wrapper yet, but there's a patch:
* https://patchwork.sourceware.org/project/glibc/patch/20251029200519.3203914-1-adhemerval.zanella@linaro.org/
*/
auto how = ::open_how{.flags = flags, .mode = mode, .resolve = resolve};
auto res = ::syscall(__NR_openat2, dirFd, path, &how, sizeof(how));
/* Cache that the syscall is not supported. */
if (res < 0 && errno == ENOSYS) {
unsupported.test_and_set();
return std::nullopt;
}
return res;
}
# endif
return std::nullopt;
}
} // namespace linux
#endif
static Descriptor
openFileEnsureBeneathNoSymlinksIterative(Descriptor dirFd, const CanonPath & path, int flags, mode_t mode)
{
AutoCloseFD parentFd;
auto nrComponents = std::ranges::distance(path);
assert(nrComponents >= 1);
auto components = std::views::take(path, nrComponents - 1); /* Everything but last component */
auto getParentFd = [&]() { return parentFd ? parentFd.get() : dirFd; };
/* This rather convoluted loop is necessary to avoid TOCTOU when validating that
no inner path component is a symlink. */
for (auto it = components.begin(); it != components.end(); ++it) {
auto component = std::string(*it); /* Copy into a string to make NUL terminated. */
assert(component != ".." && !component.starts_with('/')); /* In case invariant is broken somehow.. */
AutoCloseFD parentFd2 = ::openat(
getParentFd(), /* First iteration uses dirFd. */
component.c_str(),
O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC
#ifdef __linux__
| O_PATH /* Linux-specific optimization. Files are open only for path resolution purposes. */
#endif
#ifdef __FreeBSD__
| O_RESOLVE_BENEATH /* Further guard against any possible SNAFUs. */
#endif
);
if (!parentFd2) {
/* Construct the CanonPath for error message. */
auto path2 = std::ranges::fold_left(components.begin(), ++it, CanonPath::root, [](auto lhs, auto rhs) {
lhs.push(rhs);
return lhs;
});
if (errno == ENOTDIR) /* Path component might be a symlink. */ {
struct ::stat st;
if (::fstatat(getParentFd(), component.c_str(), &st, AT_SYMLINK_NOFOLLOW) == 0 && S_ISLNK(st.st_mode))
throw unix::SymlinkNotAllowed(path2);
errno = ENOTDIR; /* Restore the errno. */
} else if (errno == ELOOP) {
throw unix::SymlinkNotAllowed(path2);
}
return INVALID_DESCRIPTOR;
}
parentFd = std::move(parentFd2);
}
auto res = ::openat(getParentFd(), std::string(path.baseName().value()).c_str(), flags | O_NOFOLLOW, mode);
if (res < 0 && errno == ELOOP)
throw unix::SymlinkNotAllowed(path);
return res;
}
Descriptor unix::openFileEnsureBeneathNoSymlinks(Descriptor dirFd, const CanonPath & path, int flags, mode_t mode)
{
assert(!path.rel().starts_with('/')); /* Just in case the invariant is somehow broken. */
assert(!path.isRoot());
#ifdef __linux__
auto maybeFd = linux::openat2(
dirFd, path.rel_c_str(), flags, static_cast<uint64_t>(mode), RESOLVE_BENEATH | RESOLVE_NO_SYMLINKS);
if (maybeFd) {
if (*maybeFd < 0 && errno == ELOOP)
throw unix::SymlinkNotAllowed(path);
return *maybeFd;
}
#endif
return openFileEnsureBeneathNoSymlinksIterative(dirFd, path, flags, mode);
}
} // namespace nix

View file

@ -18,7 +18,7 @@ std::string getUserName()
return name;
}
Path getHomeOf(uid_t userId)
std::filesystem::path getHomeOf(uid_t userId)
{
std::vector<char> buf(16384);
struct passwd pwbuf;
@ -28,9 +28,9 @@ Path getHomeOf(uid_t userId)
return pw->pw_dir;
}
Path getHome()
std::filesystem::path getHome()
{
static Path homeDir = []() {
static std::filesystem::path homeDir = []() {
std::optional<std::string> unownedUserHomeDir = {};
auto homeDir = getEnv("HOME");
if (homeDir) {

View file

@ -5,7 +5,7 @@
namespace nix {
Path getCacheDir()
std::filesystem::path getCacheDir()
{
auto dir = getEnv("NIX_CACHE_HOME");
if (dir) {
@ -13,14 +13,14 @@ Path getCacheDir()
} else {
auto xdgDir = getEnv("XDG_CACHE_HOME");
if (xdgDir) {
return *xdgDir + "/nix";
return std::filesystem::path{*xdgDir} / "nix";
} else {
return getHome() + "/.cache/nix";
return getHome() / ".cache" / "nix";
}
}
}
Path getConfigDir()
std::filesystem::path getConfigDir()
{
auto dir = getEnv("NIX_CONFIG_HOME");
if (dir) {
@ -28,26 +28,27 @@ Path getConfigDir()
} else {
auto xdgDir = getEnv("XDG_CONFIG_HOME");
if (xdgDir) {
return *xdgDir + "/nix";
return std::filesystem::path{*xdgDir} / "nix";
} else {
return getHome() + "/.config/nix";
return getHome() / ".config" / "nix";
}
}
}
std::vector<Path> getConfigDirs()
std::vector<std::filesystem::path> getConfigDirs()
{
Path configHome = getConfigDir();
std::filesystem::path configHome = getConfigDir();
auto configDirs = getEnv("XDG_CONFIG_DIRS").value_or("/etc/xdg");
std::vector<Path> result = tokenizeString<std::vector<std::string>>(configDirs, ":");
for (auto & p : result) {
p += "/nix";
auto tokens = tokenizeString<std::vector<std::string>>(configDirs, ":");
std::vector<std::filesystem::path> result;
result.push_back(configHome);
for (auto & token : tokens) {
result.push_back(std::filesystem::path{token} / "nix");
}
result.insert(result.begin(), configHome);
return result;
}
Path getDataDir()
std::filesystem::path getDataDir()
{
auto dir = getEnv("NIX_DATA_HOME");
if (dir) {
@ -55,14 +56,14 @@ Path getDataDir()
} else {
auto xdgDir = getEnv("XDG_DATA_HOME");
if (xdgDir) {
return *xdgDir + "/nix";
return std::filesystem::path{*xdgDir} / "nix";
} else {
return getHome() + "/.local/share/nix";
return getHome() / ".local" / "share" / "nix";
}
}
}
Path getStateDir()
std::filesystem::path getStateDir()
{
auto dir = getEnv("NIX_STATE_HOME");
if (dir) {
@ -70,16 +71,16 @@ Path getStateDir()
} else {
auto xdgDir = getEnv("XDG_STATE_HOME");
if (xdgDir) {
return *xdgDir + "/nix";
return std::filesystem::path{*xdgDir} / "nix";
} else {
return getHome() + "/.local/state/nix";
return getHome() / ".local" / "state" / "nix";
}
}
}
Path createNixStateDir()
std::filesystem::path createNixStateDir()
{
Path dir = getStateDir();
std::filesystem::path dir = getStateDir();
createDirs(dir);
return dir;
}

View file

@ -35,10 +35,10 @@ std::string getUserName()
return name;
}
Path getHome()
std::filesystem::path getHome()
{
static Path homeDir = []() {
Path homeDir = getEnv("USERPROFILE").value_or("C:\\Users\\Default");
static std::filesystem::path homeDir = []() {
std::filesystem::path homeDir = getEnv("USERPROFILE").value_or("C:\\Users\\Default");
assert(!homeDir.empty());
return canonPath(homeDir);
}();