Skip to content

libutil: replace string-based Path with std::filesystem::path across core libraries#15313

Merged
Ericson2314 merged 1 commit into
NixOS:masterfrom
obsidiansystems:no-path-libutil
Feb 24, 2026
Merged

libutil: replace string-based Path with std::filesystem::path across core libraries#15313
Ericson2314 merged 1 commit into
NixOS:masterfrom
obsidiansystems:no-path-libutil

Conversation

@amaanq
Copy link
Copy Markdown
Member

@amaanq amaanq commented Feb 21, 2026

Motivation

This takes the std::filesystem::path migration from the CLI layer into the core libraries (libutil, libstore, libexpr, libfetchers, libflake), converting function signatures, settings fields, and locals throughout file-system.hh, archive.hh, configuration.hh, and their implementations. PathSetting becomes Setting<std::filesystem::path> in local-overlay-store.hh and local-store.hh with .get() calls at use sites, and several writeFile overloads collapse now that the Path-based wrappers in file-system.hh are gone.

Context


Add 👍 to pull requests you find important.

The Nix maintainer team uses a GitHub project board to schedule and track reviews.

@github-actions github-actions Bot added new-cli Relating to the "nix" command store Issues and pull requests concerning the Nix store fetching Networking with the outside (non-Nix) world, input locking c api Nix as a C library with a stable interface labels Feb 21, 2026
@amaanq amaanq changed the title No path libutil libutil: replace string-based Path with std::filesystem::path across core libraries Feb 21, 2026
@amaanq amaanq force-pushed the no-path-libutil branch 2 times, most recently from 1d54304 to 73960d1 Compare February 21, 2026 08:02
Comment thread src/libcmd/repl-interacter.cc
Comment thread src/libexpr/eval.cc Outdated
@github-actions github-actions Bot added the repl The Read Eval Print Loop, "nix repl" command and debugger label Feb 23, 2026
Comment thread src/libexpr/parser.y
Comment thread src/libexpr/paths.cc Outdated
Comment thread src/libexpr/primops.cc Outdated
Comment thread src/libexpr/primops.cc Outdated
Comment thread src/libfetchers/git-utils.cc Outdated
Comment thread src/libfetchers/path.cc
Comment thread src/libfetchers/path.cc Outdated
Comment thread src/libfetchers/registry.cc
Comment thread src/libfetchers/registry.cc Outdated
Comment thread src/libflake/flakeref.cc Outdated
Comment thread src/libflake/flakeref.cc Outdated
Comment thread src/libstore-test-support/include/nix/store/tests/nix_api_store.hh Outdated
Comment thread src/libstore/gc.cc Outdated
Comment thread src/libstore/gc.cc Outdated
Comment thread src/libstore/gc.cc Outdated
Comment thread src/libstore/gc.cc Outdated
Comment thread src/libstore/gc.cc Outdated
Comment thread src/libstore/gc.cc
auto deleteFromStore = [&](std::string_view baseName, bool isKnownPath) {
Path path = storeDir + "/" + std::string(baseName);
Path realPath = config->realStoreDir + "/" + std::string(baseName);
auto realPath = config->realStoreDir.get() / std::string(baseName);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh we should be incredibly careful with the use of operator/ because if RHS iss absolute everything will blow up and we'll have a thousand CVEs on our hands.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that's a good point, here deleteFromStore is only called with either a store path or an entry from readdir, which will only give us the filename component.

Comment thread src/libstore/gc.cc
AutoCloseFD tmpDirFd = openDirectory(realPath);
if (!tmpDirFd || !lockFile(tmpDirFd.get(), ltWrite, false)) {
debug("skipping locked tempdir '%s'", realPath);
debug("skipping locked tempdir '%s'", PathFmt(realPath));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
debug("skipping locked tempdir '%s'", PathFmt(realPath));
debug("skipping locked tempdir %s", PathFmt(realPath));

if (st.st_uid != 0 || st.st_gid != gr->gr_gid || (st.st_mode & ~S_IFMT) != perm) {
if (chown(config->realStoreDir.get().c_str(), 0, gr->gr_gid) == -1)
throw SysError("changing ownership of path '%1%'", config->realStoreDir);
throw SysError("changing ownership of path '%s'", PathFmt(config->realStoreDir.get()));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
throw SysError("changing ownership of path '%s'", PathFmt(config->realStoreDir.get()));
throw SysError("changing ownership of path %s", PathFmt(config->realStoreDir.get()));

0600);
if (!fdGCLock)
throw SysError("opening global GC lock '%1%'", fnGCLock);
throw SysError("opening global GC lock '%1%'", PathFmt(fnGCLock));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
throw SysError("opening global GC lock '%1%'", PathFmt(fnGCLock));
throw SysError("opening global GC lock %1%", PathFmt(fnGCLock));

AutoCloseDir dir(opendir(path.c_str()));
if (!dir)
throw SysError("opening directory '%1%'", path);
throw SysError("opening directory '%s'", PathFmt(path));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
throw SysError("opening directory '%s'", PathFmt(path));
throw SysError("opening directory %s", PathFmt(path));

}
if (errno)
throw SysError("reading directory '%1%'", path);
throw SysError("reading directory '%s'", PathFmt(path));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
throw SysError("reading directory '%s'", PathFmt(path));
throw SysError("reading directory %s", PathFmt(path));

if (std::regex_search(path, std::regex("\\.app/Contents/.+$"))) {
debug("'%1%' is not allowed to be linked in macOS", path);
if (std::regex_search(path.string(), std::regex("\\.app/Contents/.+$"))) {
debug("'%s' is not allowed to be linked in macOS", PathFmt(path));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
debug("'%s' is not allowed to be linked in macOS", PathFmt(path));
debug("%s is not allowed to be linked in macOS", PathFmt(path));

/* This can still happen on top-level files. */
if (st.st_nlink > 1 && inodeHash.count(st.st_ino)) {
debug("'%s' is already linked, with %d other file(s)", path, st.st_nlink - 2);
debug("'%s' is already linked, with %d other file(s)", PathFmt(path), st.st_nlink - 2);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
debug("'%s' is already linked, with %d other file(s)", PathFmt(path), st.st_nlink - 2);
debug("%s is already linked, with %d other file(s)", PathFmt(path), st.st_nlink - 2);

.hash;
});
debug("'%1%' has hash '%2%'", path, hash.to_string(HashFormat::Nix32, true));
debug("'%s' has hash '%s'", PathFmt(path), hash.to_string(HashFormat::Nix32, true));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
debug("'%s' has hash '%s'", PathFmt(path), hash.to_string(HashFormat::Nix32, true));
debug("%s has hash '%s'", PathFmt(path), hash.to_string(HashFormat::Nix32, true));

Comment thread src/libstore/store-api.cc
if (!isInStore(path))
throw BadStorePath("path '%1%' is not in the Nix store", path);
if (!isInStore(path.string()))
throw BadStorePath("path '%1%' is not in the Nix store", PathFmt(path));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
throw BadStorePath("path '%1%' is not in the Nix store", PathFmt(path));
throw BadStorePath("path %1% is not in the Nix store", PathFmt(path));

@Ericson2314
Copy link
Copy Markdown
Member

Sorry I missed those pathfmt quotes @xokdvium

}
} catch (Error & e) {
e.addTrace({}, "writing file '%1%'", path);
e.addTrace({}, "writing file '%s'", PathFmt(path));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
e.addTrace({}, "writing file '%s'", PathFmt(path));
e.addTrace({}, "writing file %s", PathFmt(path));

AutoCloseFD fd = toDescriptor(open(path.parent_path().c_str(), O_RDONLY, 0));
if (!fd)
throw SysError("opening file '%1%'", path);
throw SysError("opening file '%s'", PathFmt(path));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
throw SysError("opening file '%s'", PathFmt(path));
throw SysError("opening file %s", PathFmt(path));

AutoCloseFD fd = toDescriptor(open(path.string().c_str(), O_RDONLY, 0));
if (!fd)
throw SysError("opening file '%1%'", path);
throw SysError("opening file '%s'", PathFmt(path));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
throw SysError("opening file '%s'", PathFmt(path));
throw SysError("opening file %s", PathFmt(path));

)
== -1)
throw SysError("creating directory '%1%'", path);
throw SysError("creating directory '%s'", PathFmt(path));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
throw SysError("creating directory '%s'", PathFmt(path));
throw SysError("creating directory %s", PathFmt(path));

std::pair<AutoCloseFD, std::filesystem::path> createTempFile(const std::filesystem::path & prefix)
{
Path tmpl(defaultTempDir().string() + "/" + prefix + ".XXXXXX");
std::filesystem::path tmpl(defaultTempDir() / (prefix.string() + ".XXXXXX"));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if prefix is an absolute path? Please add an assert at least

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah good point, as of now prefix is always a hardcoded literal (like nix-shell) but an assert would be good here.

static std::atomic<uint32_t> counter(std::random_device{}());
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));
return tmpRoot / fmt("%s-%s-%s", suffix, getpid(), counter.fetch_add(1, std::memory_order_relaxed));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if suffix is an absolute path? This will discard LHS

{
auto setUpdateLock = [&](auto && fileName) {
uploadLock = openLockFile(currentLoad + "/" + escapeUri(fileName) + ".upload-lock", true);
uploadLock = openLockFile(currentLoad / (escapeUri(fileName) + ".upload-lock"), true);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can escapeUri return an absolute path?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no it replaces all instances of / with _

amaanq added a commit to obsidiansystems/nix that referenced this pull request Feb 24, 2026
Drop single quotes around PathFmt format args in error and debug
messages across gc.cc, local-store.cc, optimise-store.cc,
store-api.cc, and file-system.cc; PathFmt already handles
presentation so the extra quoting is redundant.

Add an assert in createTempFile that prefix is not absolute,
since operator/ silently discards LHS when RHS is absolute.
amaanq added a commit to obsidiansystems/nix that referenced this pull request Feb 24, 2026
amaanq added a commit to obsidiansystems/nix that referenced this pull request Feb 24, 2026
amaanq added a commit to obsidiansystems/nix that referenced this pull request Feb 24, 2026
Ericson2314 pushed a commit to obsidiansystems/nix that referenced this pull request Feb 24, 2026
- file-system: drop redundant quotes around `PathFmt` and assert relative paths
- libutil, libstore: fix mingw cross-compilation breakages
Ericson2314 pushed a commit to obsidiansystems/nix that referenced this pull request Feb 24, 2026
- file-system: drop redundant quotes around `PathFmt` and assert relative paths
- libutil, libstore: fix mingw cross-compilation breakages
amaanq added a commit to obsidiansystems/nix that referenced this pull request Feb 25, 2026
- file-system: drop redundant quotes around `PathFmt` and assert relative paths
- libutil, libstore: fix mingw cross-compilation breakages
const auto & localSettings = config->getLocalSettings();
const auto & gcSettings = localSettings.getGCSettings();
createDirs(gcRootsDir);
if (!pathExists(gcRootsDir)) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This accidentally reverts 9799023

brittonr pushed a commit to brittonr/nix that referenced this pull request Apr 1, 2026
libutil: replace string-based Path with std::filesystem::path across core libraries
brittonr pushed a commit to brittonr/nix that referenced this pull request Apr 1, 2026
- file-system: drop redundant quotes around `PathFmt` and assert relative paths
- libutil, libstore: fix mingw cross-compilation breakages
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c api Nix as a C library with a stable interface fetching Networking with the outside (non-Nix) world, input locking new-cli Relating to the "nix" command repl The Read Eval Print Loop, "nix repl" command and debugger store Issues and pull requests concerning the Nix store with-tests Issues related to testing. PRs with tests have some priority

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants