From e5e0ce23347301c973d231ea3adc5177536952a0 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 18 Feb 2025 21:38:40 +0100 Subject: [PATCH 01/10] Remove redundant call to canonPath() The CanonPath constructor already does that. --- src/libexpr/eval.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 8aef85dc5..29daabe49 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -2051,7 +2051,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v) else if (firstType == nPath) { if (!context.empty()) state.error("a string that refers to a store path cannot be appended to a path").atPos(pos).withFrame(env, *this).debugThrow(); - v.mkPath(state.rootPath(CanonPath(canonPath(str())))); + v.mkPath(state.rootPath(CanonPath(str()))); } else v.mkStringMove(c_str(), context); } From 774b92439863cf3a4db85ccef39448c007de5d64 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 18 Feb 2025 23:03:30 +0100 Subject: [PATCH 02/10] Add a storeFS accessor for paths resulting from IFD Hopefully fixes #11503. --- src/libexpr/eval.cc | 18 +++++++----------- src/libexpr/eval.hh | 28 +++++++++++++++++----------- src/libexpr/paths.cc | 7 +++++++ src/libexpr/primops.cc | 17 ++++------------- 4 files changed, 35 insertions(+), 35 deletions(-) diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 29daabe49..efffc69f1 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -255,6 +255,12 @@ EvalState::EvalState( throw RestrictedPathError("access to absolute path '%1%' is forbidden %2%", path, modeInformation); })) : getFSSourceAccessor()) + , storeFS( + makeMountedSourceAccessor( + { + {CanonPath::root, makeEmptySourceAccessor()}, + {CanonPath(store->storeDir), makeFSSourceAccessor(dirOf(store->toRealPath(StorePath::dummy)))} + })) , corepkgsFS(make_ref()) , internalFS(make_ref()) , derivationInternal{corepkgsFS->addFile( @@ -422,16 +428,6 @@ void EvalState::checkURI(const std::string & uri) } -Path EvalState::toRealPath(const Path & path, const NixStringContext & context) -{ - // FIXME: check whether 'path' is in 'context'. - return - !context.empty() && store->isInStore(path) - ? store->toRealPath(path) - : path; -} - - Value * EvalState::addConstant(const std::string & name, Value & v, Constant info) { Value * v2 = allocValue(); @@ -2432,7 +2428,7 @@ SourcePath EvalState::coerceToPath(const PosIdx pos, Value & v, NixStringContext auto path = coerceToString(pos, v, context, errorCtx, false, false, true).toOwned(); if (path == "" || path[0] != '/') error("string '%1%' doesn't represent an absolute path", path).withTrace(pos, errorCtx).debugThrow(); - return rootPath(CanonPath(path)); + return stringWithContextToPath(path, context); } diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index 767578343..6a601a587 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -250,6 +250,11 @@ public: */ const ref rootFS; + /** + * The accessor for the store. + */ + const ref storeFS; + /** * The in-memory filesystem for paths. */ @@ -389,6 +394,18 @@ public: */ SourcePath rootPath(PathView path); + /** + * Convert `s` to a path. If `context` is not empty, the resulting + * path will use the `storeFS` accessor; otherwise it will use + * `rootFS`. When using a chroot store, this allows us to + * distinguish between store paths resulting from + * import-from-derivation and sources stored in the actual + * /nix/store. + */ + SourcePath stringWithContextToPath( + std::string_view s, + const NixStringContext & context); + /** * Allow access to a path. */ @@ -412,17 +429,6 @@ public: void checkURI(const std::string & uri); - /** - * When using a diverted store and 'path' is in the Nix store, map - * 'path' to the diverted location (e.g. /nix/store/foo is mapped - * to /home/alice/my-nix/nix/store/foo). However, this is only - * done if the context is not empty, since otherwise we're - * probably trying to read from the actual /nix/store. This is - * intended to distinguish between import-from-derivation and - * sources stored in the actual /nix/store. - */ - Path toRealPath(const Path & path, const NixStringContext & context); - /** * Parse a Nix expression from the specified file. */ diff --git a/src/libexpr/paths.cc b/src/libexpr/paths.cc index 50d0d9895..ea28b5a7e 100644 --- a/src/libexpr/paths.cc +++ b/src/libexpr/paths.cc @@ -1,4 +1,5 @@ #include "eval.hh" +#include "store-api.hh" namespace nix { @@ -12,4 +13,10 @@ SourcePath EvalState::rootPath(PathView path) return {rootFS, CanonPath(absPath(path))}; } +SourcePath EvalState::stringWithContextToPath(std::string_view s, const NixStringContext & context) +{ + auto path = CanonPath(s); + return !context.empty() ? SourcePath{storeFS, std::move(path)} : rootPath(std::move(path)); +} + } diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 51d2991e7..88d09e563 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -143,10 +143,9 @@ static SourcePath realisePath(EvalState & state, const PosIdx pos, Value & v, st auto path = state.coerceToPath(noPos, v, context, "while realising the context of a path"); try { - if (!context.empty() && path.accessor == state.rootFS) { + if (!context.empty() && path.accessor == state.storeFS) { auto rewrites = state.realiseContext(context); - auto realPath = state.toRealPath(rewriteStrings(path.path.abs(), rewrites), context); - path = {path.accessor, CanonPath(realPath)}; + path = {path.accessor, CanonPath(rewriteStrings(path.path.abs(), rewrites))}; } return resolveSymlinks ? path.resolveSymlinks(*resolveSymlinks) : path; } catch (Error & e) { @@ -2481,19 +2480,11 @@ static void addPath( try { StorePathSet refs; - if (path.accessor == state.rootFS && state.store->isInStore(path.path.abs())) { + if (path.accessor == state.storeFS && state.store->isInStore(path.path.abs())) { // FIXME: handle CA derivation outputs (where path needs to // be rewritten to the actual output). auto rewrites = state.realiseContext(context); - path = {state.rootFS, CanonPath(state.toRealPath(rewriteStrings(path.path.abs(), rewrites), context))}; - - try { - auto [storePath, subPath] = state.store->toStorePath(path.path.abs()); - // FIXME: we should scanForReferences on the path before adding it - refs = state.store->queryPathInfo(storePath)->references; - path = {state.rootFS, CanonPath(state.store->toRealPath(storePath) + subPath)}; - } catch (Error &) { // FIXME: should be InvalidPathError - } + path = {path.accessor, CanonPath(rewriteStrings(path.path.abs(), rewrites))}; } std::unique_ptr filter; From 641733fd26bbc728b9337b8a356c67c0293ccf8c Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 18 Feb 2025 23:28:47 +0100 Subject: [PATCH 03/10] Add test --- tests/functional/chroot-store.sh | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/functional/chroot-store.sh b/tests/functional/chroot-store.sh index ccde3e90b..7300f04ba 100755 --- a/tests/functional/chroot-store.sh +++ b/tests/functional/chroot-store.sh @@ -2,6 +2,28 @@ source common.sh +# Regression test for #11503. +mkdir -p "$TEST_ROOT/directory" +cat > "$TEST_ROOT/directory/default.nix" < "$TEST_ROOT"/example.txt mkdir -p "$TEST_ROOT/x" From c3d8799f9f969de14983ddd7d145c58e8566e22c Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 19 Feb 2025 11:53:21 +0100 Subject: [PATCH 04/10] MountedSourceAccessor: Remove redundant pathExists() method --- src/libutil/mounted-source-accessor.cc | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/libutil/mounted-source-accessor.cc b/src/libutil/mounted-source-accessor.cc index 45c8adbd8..97e5f10a4 100644 --- a/src/libutil/mounted-source-accessor.cc +++ b/src/libutil/mounted-source-accessor.cc @@ -23,12 +23,6 @@ struct MountedSourceAccessor : SourceAccessor return accessor->readFile(subpath); } - bool pathExists(const CanonPath & path) override - { - auto [accessor, subpath] = resolve(path); - return accessor->pathExists(subpath); - } - std::optional maybeLstat(const CanonPath & path) override { auto [accessor, subpath] = resolve(path); From 5b7c240ebd013fd20741487f73d15a9478a52507 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 19 Feb 2025 12:45:59 +0100 Subject: [PATCH 05/10] Add a UnionSourceAccessor --- src/libutil/meson.build | 1 + src/libutil/source-accessor.hh | 6 ++ src/libutil/union-source-accessor.cc | 82 ++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+) create mode 100644 src/libutil/union-source-accessor.cc diff --git a/src/libutil/meson.build b/src/libutil/meson.build index b6a33135a..df459f0e5 100644 --- a/src/libutil/meson.build +++ b/src/libutil/meson.build @@ -167,6 +167,7 @@ sources = files( 'tarfile.cc', 'terminal.cc', 'thread-pool.cc', + 'union-source-accessor.cc', 'unix-domain-socket.cc', 'url.cc', 'users.cc', diff --git a/src/libutil/source-accessor.hh b/src/libutil/source-accessor.hh index b79f8af92..79ae092ac 100644 --- a/src/libutil/source-accessor.hh +++ b/src/libutil/source-accessor.hh @@ -216,4 +216,10 @@ ref makeFSSourceAccessor(std::filesystem::path root); ref makeMountedSourceAccessor(std::map> mounts); +/** + * Construct an accessor that presents a "union" view of a vector of + * underlying accessors. Earlier accessors take precedence over later. + */ +ref makeUnionSourceAccessor(std::vector> && accessors); + } diff --git a/src/libutil/union-source-accessor.cc b/src/libutil/union-source-accessor.cc new file mode 100644 index 000000000..cf16f20fb --- /dev/null +++ b/src/libutil/union-source-accessor.cc @@ -0,0 +1,82 @@ +#include "source-accessor.hh" + +namespace nix { + +struct UnionSourceAccessor : SourceAccessor +{ + std::vector> accessors; + + UnionSourceAccessor(std::vector> _accessors) + : accessors(std::move(_accessors)) + { + displayPrefix.clear(); + } + + std::string readFile(const CanonPath & path) override + { + for (auto & accessor : accessors) { + auto st = accessor->maybeLstat(path); + if (st && st->type == Type::tRegular) + return accessor->readFile(path); + } + throw FileNotFound("path '%s' does not exist", showPath(path)); + } + + std::optional maybeLstat(const CanonPath & path) override + { + for (auto & accessor : accessors) { + auto st = accessor->maybeLstat(path); + if (st) + return st; + } + return std::nullopt; + } + + DirEntries readDirectory(const CanonPath & path) override + { + DirEntries result; + for (auto & accessor : accessors) { + auto st = accessor->maybeLstat(path); + if (!st || st->type != Type::tDirectory) + continue; + for (auto & entry : accessor->readDirectory(path)) + // Don't override entries from previous accessors. + result.insert(entry); + } + return result; + } + + std::string readLink(const CanonPath & path) override + { + for (auto & accessor : accessors) { + auto st = accessor->maybeLstat(path); + if (st && st->type == Type::tSymlink) + return accessor->readLink(path); + } + throw FileNotFound("path '%s' does not exist", showPath(path)); + } + + std::string showPath(const CanonPath & path) override + { + for (auto & accessor : accessors) + return accessor->showPath(path); + return SourceAccessor::showPath(path); + } + + std::optional getPhysicalPath(const CanonPath & path) override + { + for (auto & accessor : accessors) { + auto p = accessor->getPhysicalPath(path); + if (p) + return p; + } + return std::nullopt; + } +}; + +ref makeUnionSourceAccessor(std::vector> && accessors) +{ + return make_ref(std::move(accessors)); +} + +} From 99e78c37f7a00bc9d0573f548f5c23ebf5d42677 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 19 Feb 2025 12:46:22 +0100 Subject: [PATCH 06/10] Use UnionSourceAccessor to mount the chroot store on top of the real store directory --- src/libcmd/common-eval-args.cc | 6 ++-- src/libexpr/eval.cc | 52 ++++++++++++++++++++++------------ src/libexpr/eval.hh | 17 ----------- src/libexpr/paths.cc | 6 ---- src/libexpr/primops.cc | 4 +-- src/libflake/flake/flake.cc | 6 ++-- 6 files changed, 42 insertions(+), 49 deletions(-) diff --git a/src/libcmd/common-eval-args.cc b/src/libcmd/common-eval-args.cc index adf816494..87aeb4f2d 100644 --- a/src/libcmd/common-eval-args.cc +++ b/src/libcmd/common-eval-args.cc @@ -37,7 +37,7 @@ EvalSettings evalSettings { auto [accessor, lockedRef] = flakeRef.resolve(state.store).lazyFetch(state.store); auto storePath = nix::fetchToStore(*state.store, SourcePath(accessor), FetchMode::Copy, lockedRef.input.getName()); state.allowPath(storePath); - return state.rootPath(state.store->toRealPath(storePath)); + return state.rootPath(state.store->printStorePath(storePath)); }, }, }, @@ -179,7 +179,7 @@ SourcePath lookupFileArg(EvalState & state, std::string_view s, const Path * bas state.fetchSettings, EvalSettings::resolvePseudoUrl(s)); auto storePath = fetchToStore(*state.store, SourcePath(accessor), FetchMode::Copy); - return state.rootPath(CanonPath(state.store->toRealPath(storePath))); + return state.rootPath(CanonPath(state.store->printStorePath(storePath))); } else if (hasPrefix(s, "flake:")) { @@ -188,7 +188,7 @@ SourcePath lookupFileArg(EvalState & state, std::string_view s, const Path * bas auto [accessor, lockedRef] = flakeRef.resolve(state.store).lazyFetch(state.store); auto storePath = nix::fetchToStore(*state.store, SourcePath(accessor), FetchMode::Copy, lockedRef.input.getName()); state.allowPath(storePath); - return state.rootPath(CanonPath(state.store->toRealPath(storePath))); + return state.rootPath(CanonPath(state.store->printStorePath(storePath))); } else if (s.size() > 2 && s.at(0) == '<' && s.at(s.size() - 1) == '>') { diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index efffc69f1..ab2eb98e5 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -246,21 +246,37 @@ EvalState::EvalState( , repair(NoRepair) , emptyBindings(0) , rootFS( - settings.restrictEval || settings.pureEval - ? ref(AllowListSourceAccessor::create(getFSSourceAccessor(), {}, - [&settings](const CanonPath & path) -> RestrictedPathError { - auto modeInformation = settings.pureEval - ? "in pure evaluation mode (use '--impure' to override)" - : "in restricted mode"; - throw RestrictedPathError("access to absolute path '%1%' is forbidden %2%", path, modeInformation); - })) - : getFSSourceAccessor()) - , storeFS( - makeMountedSourceAccessor( - { - {CanonPath::root, makeEmptySourceAccessor()}, - {CanonPath(store->storeDir), makeFSSourceAccessor(dirOf(store->toRealPath(StorePath::dummy)))} - })) + ({ + auto accessor = getFSSourceAccessor(); + + /* If we have a chroot store, make a union accessor to + make the chroot store available at its logical location + while still having the underlying directory + available. This is necessary for instance if we're + evaluating a file from the physical /nix/store while + using a chroot store. */ + auto realStoreDir = dirOf(store->toRealPath(StorePath::dummy)); + if (store->storeDir != realStoreDir) { + auto storeFS = makeMountedSourceAccessor( + { + {CanonPath::root, makeEmptySourceAccessor()}, + {CanonPath(store->storeDir), makeFSSourceAccessor(realStoreDir)} + }); + accessor = makeUnionSourceAccessor({accessor, storeFS}); + } + + /* Apply access control if needed. */ + if (settings.restrictEval || settings.pureEval) + accessor = AllowListSourceAccessor::create(accessor, {}, + [&settings](const CanonPath & path) -> RestrictedPathError { + auto modeInformation = settings.pureEval + ? "in pure evaluation mode (use '--impure' to override)" + : "in restricted mode"; + throw RestrictedPathError("access to absolute path '%1%' is forbidden %2%", path, modeInformation); + }); + + accessor; + })) , corepkgsFS(make_ref()) , internalFS(make_ref()) , derivationInternal{corepkgsFS->addFile( @@ -350,7 +366,7 @@ void EvalState::allowPath(const Path & path) void EvalState::allowPath(const StorePath & storePath) { if (auto rootFS2 = rootFS.dynamic_pointer_cast()) - rootFS2->allowPrefix(CanonPath(store->toRealPath(storePath))); + rootFS2->allowPrefix(CanonPath(store->printStorePath(storePath))); } void EvalState::allowClosure(const StorePath & storePath) @@ -2428,7 +2444,7 @@ SourcePath EvalState::coerceToPath(const PosIdx pos, Value & v, NixStringContext auto path = coerceToString(pos, v, context, errorCtx, false, false, true).toOwned(); if (path == "" || path[0] != '/') error("string '%1%' doesn't represent an absolute path", path).withTrace(pos, errorCtx).debugThrow(); - return stringWithContextToPath(path, context); + return rootPath(path); } @@ -3082,7 +3098,7 @@ std::optional EvalState::resolveLookupPathPath(const LookupPath::Pat fetchSettings, EvalSettings::resolvePseudoUrl(value)); auto storePath = fetchToStore(*store, SourcePath(accessor), FetchMode::Copy); - return finish(rootPath(store->toRealPath(storePath))); + return finish(rootPath(store->printStorePath(storePath))); } catch (Error & e) { logWarning({ .msg = HintFmt("Nix search path entry '%1%' cannot be downloaded, ignoring", value) diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index 6a601a587..aca2e8bfd 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -250,11 +250,6 @@ public: */ const ref rootFS; - /** - * The accessor for the store. - */ - const ref storeFS; - /** * The in-memory filesystem for paths. */ @@ -394,18 +389,6 @@ public: */ SourcePath rootPath(PathView path); - /** - * Convert `s` to a path. If `context` is not empty, the resulting - * path will use the `storeFS` accessor; otherwise it will use - * `rootFS`. When using a chroot store, this allows us to - * distinguish between store paths resulting from - * import-from-derivation and sources stored in the actual - * /nix/store. - */ - SourcePath stringWithContextToPath( - std::string_view s, - const NixStringContext & context); - /** * Allow access to a path. */ diff --git a/src/libexpr/paths.cc b/src/libexpr/paths.cc index ea28b5a7e..474ea2d74 100644 --- a/src/libexpr/paths.cc +++ b/src/libexpr/paths.cc @@ -13,10 +13,4 @@ SourcePath EvalState::rootPath(PathView path) return {rootFS, CanonPath(absPath(path))}; } -SourcePath EvalState::stringWithContextToPath(std::string_view s, const NixStringContext & context) -{ - auto path = CanonPath(s); - return !context.empty() ? SourcePath{storeFS, std::move(path)} : rootPath(std::move(path)); -} - } diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 88d09e563..87f7eeb18 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -143,7 +143,7 @@ static SourcePath realisePath(EvalState & state, const PosIdx pos, Value & v, st auto path = state.coerceToPath(noPos, v, context, "while realising the context of a path"); try { - if (!context.empty() && path.accessor == state.storeFS) { + if (!context.empty() && path.accessor == state.rootFS) { auto rewrites = state.realiseContext(context); path = {path.accessor, CanonPath(rewriteStrings(path.path.abs(), rewrites))}; } @@ -2480,7 +2480,7 @@ static void addPath( try { StorePathSet refs; - if (path.accessor == state.storeFS && state.store->isInStore(path.path.abs())) { + if (path.accessor == state.rootFS && state.store->isInStore(path.path.abs())) { // FIXME: handle CA derivation outputs (where path needs to // be rewritten to the actual output). auto rewrites = state.realiseContext(context); diff --git a/src/libflake/flake/flake.cc b/src/libflake/flake/flake.cc index ec0c8dde9..eebd6ec2d 100644 --- a/src/libflake/flake/flake.cc +++ b/src/libflake/flake/flake.cc @@ -337,7 +337,7 @@ static Flake readFlake( auto storePath = fetchToStore(*state.store, setting.value->path(), FetchMode::Copy); flake.config.settings.emplace( state.symbols[setting.name], - state.store->toRealPath(storePath)); + state.store->printStorePath(storePath)); } else if (setting.value->type() == nInt) flake.config.settings.emplace( @@ -423,7 +423,7 @@ static Flake getFlake( auto storePath = copyInputToStore(state, lockedRef.input, originalRef.input, accessor); // Re-parse flake.nix from the store. - return readFlake(state, originalRef, resolvedRef, lockedRef, state.rootPath(state.store->toRealPath(storePath)), lockRootAttrPath); + return readFlake(state, originalRef, resolvedRef, lockedRef, state.rootPath(state.store->printStorePath(storePath)), lockRootAttrPath); } Flake getFlake(EvalState & state, const FlakeRef & originalRef, bool useRegistries) @@ -784,7 +784,7 @@ LockedFlake lockFlake( // FIXME: allow input to be lazy. auto storePath = copyInputToStore(state, lockedRef.input, input.ref->input, accessor); - return {state.rootPath(state.store->toRealPath(storePath)), lockedRef}; + return {state.rootPath(state.store->printStorePath(storePath)), lockedRef}; } }(); From 584ddd1b4d3e4f83ba0d4e65453a5973512bbee1 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 19 Feb 2025 13:34:42 +0100 Subject: [PATCH 07/10] UnionSourceAccessor: Don't filter out underlying files of the wrong type https://github.com/NixOS/nix/pull/12512#discussion_r1961567140 --- src/libexpr/paths.cc | 1 - src/libutil/union-source-accessor.cc | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/libexpr/paths.cc b/src/libexpr/paths.cc index 474ea2d74..50d0d9895 100644 --- a/src/libexpr/paths.cc +++ b/src/libexpr/paths.cc @@ -1,5 +1,4 @@ #include "eval.hh" -#include "store-api.hh" namespace nix { diff --git a/src/libutil/union-source-accessor.cc b/src/libutil/union-source-accessor.cc index cf16f20fb..eec0850c2 100644 --- a/src/libutil/union-source-accessor.cc +++ b/src/libutil/union-source-accessor.cc @@ -16,7 +16,7 @@ struct UnionSourceAccessor : SourceAccessor { for (auto & accessor : accessors) { auto st = accessor->maybeLstat(path); - if (st && st->type == Type::tRegular) + if (st) return accessor->readFile(path); } throw FileNotFound("path '%s' does not exist", showPath(path)); @@ -37,7 +37,7 @@ struct UnionSourceAccessor : SourceAccessor DirEntries result; for (auto & accessor : accessors) { auto st = accessor->maybeLstat(path); - if (!st || st->type != Type::tDirectory) + if (!st) continue; for (auto & entry : accessor->readDirectory(path)) // Don't override entries from previous accessors. @@ -50,7 +50,7 @@ struct UnionSourceAccessor : SourceAccessor { for (auto & accessor : accessors) { auto st = accessor->maybeLstat(path); - if (st && st->type == Type::tSymlink) + if (st) return accessor->readLink(path); } throw FileNotFound("path '%s' does not exist", showPath(path)); From 4206d95996a2da037b2c76f53bfd632f663ac4dc Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 19 Feb 2025 21:31:25 +0100 Subject: [PATCH 08/10] Remove sourcePathToStorePath() It's no longer needed now that all store paths inside the evaluator are logical rather than real paths. --- src/libflake/flake/flake.cc | 17 +---------------- src/libflake/flake/flake.hh | 10 ---------- src/nix/flake.cc | 4 ++-- 3 files changed, 3 insertions(+), 28 deletions(-) diff --git a/src/libflake/flake/flake.cc b/src/libflake/flake/flake.cc index eebd6ec2d..4a72f0399 100644 --- a/src/libflake/flake/flake.cc +++ b/src/libflake/flake/flake.cc @@ -921,21 +921,6 @@ LockedFlake lockFlake( } } -std::pair sourcePathToStorePath( - ref store, - const SourcePath & _path) -{ - auto path = _path.path.abs(); - - if (auto store2 = store.dynamic_pointer_cast()) { - auto realStoreDir = store2->getRealStoreDir(); - if (isInDir(path, realStoreDir)) - path = store2->storeDir + path.substr(realStoreDir.size()); - } - - return store->toStorePath(path); -} - void callFlake(EvalState & state, const LockedFlake & lockedFlake, Value & vRes) @@ -953,7 +938,7 @@ void callFlake(EvalState & state, auto lockedNode = node.dynamic_pointer_cast(); - auto [storePath, subdir] = sourcePathToStorePath(state.store, sourcePath); + auto [storePath, subdir] = state.store->toStorePath(sourcePath.path.abs()); emitTreeAttrs( state, diff --git a/src/libflake/flake/flake.hh b/src/libflake/flake/flake.hh index 8d9b9a698..d8cd9aac0 100644 --- a/src/libflake/flake/flake.hh +++ b/src/libflake/flake/flake.hh @@ -234,16 +234,6 @@ void callFlake( const LockedFlake & lockedFlake, Value & v); -/** - * Map a `SourcePath` to the corresponding store path. This is a - * temporary hack to support chroot stores while we don't have full - * lazy trees. FIXME: Remove this once we can pass a sourcePath rather - * than a storePath to call-flake.nix. - */ -std::pair sourcePathToStorePath( - ref store, - const SourcePath & path); - } void emitTreeAttrs( diff --git a/src/nix/flake.cc b/src/nix/flake.cc index 4b32020af..4615dabaa 100644 --- a/src/nix/flake.cc +++ b/src/nix/flake.cc @@ -216,7 +216,7 @@ struct CmdFlakeMetadata : FlakeCommand, MixJSON auto & flake = lockedFlake.flake; // Currently, all flakes are in the Nix store via the rootFS accessor. - auto storePath = store->printStorePath(sourcePathToStorePath(store, flake.path).first); + auto storePath = store->printStorePath(store->toStorePath(flake.path.path.abs()).first); if (json) { nlohmann::json j; @@ -1079,7 +1079,7 @@ struct CmdFlakeArchive : FlakeCommand, MixJSON, MixDryRun StorePathSet sources; - auto storePath = sourcePathToStorePath(store, flake.flake.path).first; + auto storePath = store->toStorePath(flake.flake.path.path.abs()).first; sources.insert(storePath); From 8dc2b2715bc5dc401dd83a1104332c674b335c72 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 19 Feb 2025 23:13:11 +0100 Subject: [PATCH 09/10] In pure eval mode, restrict rootFS to just the Nix store Note that in pure mode, we don't need to use the union FS even when using a chroot store, since the user shouldn't have access to the physical /nix/store. --- src/libexpr/eval.cc | 21 +++++++++++++-------- src/libutil/mounted-source-accessor.cc | 6 ++++++ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index ab2eb98e5..c0842dbbd 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -247,22 +247,27 @@ EvalState::EvalState( , emptyBindings(0) , rootFS( ({ + /* In pure eval mode, we provide a filesystem that only + contains the Nix store. + + If we have a chroot store and pure eval is not enabled, + use a union accessor to make the chroot store available + at its logical location while still having the + underlying directory available. This is necessary for + instance if we're evaluating a file from the physical + /nix/store while using a chroot store. */ auto accessor = getFSSourceAccessor(); - /* If we have a chroot store, make a union accessor to - make the chroot store available at its logical location - while still having the underlying directory - available. This is necessary for instance if we're - evaluating a file from the physical /nix/store while - using a chroot store. */ auto realStoreDir = dirOf(store->toRealPath(StorePath::dummy)); - if (store->storeDir != realStoreDir) { + if (settings.pureEval || store->storeDir != realStoreDir) { auto storeFS = makeMountedSourceAccessor( { {CanonPath::root, makeEmptySourceAccessor()}, {CanonPath(store->storeDir), makeFSSourceAccessor(realStoreDir)} }); - accessor = makeUnionSourceAccessor({accessor, storeFS}); + accessor = settings.pureEval + ? storeFS + : makeUnionSourceAccessor({accessor, storeFS}); } /* Apply access control if needed. */ diff --git a/src/libutil/mounted-source-accessor.cc b/src/libutil/mounted-source-accessor.cc index 97e5f10a4..79223d155 100644 --- a/src/libutil/mounted-source-accessor.cc +++ b/src/libutil/mounted-source-accessor.cc @@ -63,6 +63,12 @@ struct MountedSourceAccessor : SourceAccessor path.pop(); } } + + std::optional getPhysicalPath(const CanonPath & path) override + { + auto [accessor, subpath] = resolve(path); + return accessor->getPhysicalPath(subpath); + } }; ref makeMountedSourceAccessor(std::map> mounts) From ec7dc56f4edd8ec99ac87e76f2cd0819f13631f2 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 20 Feb 2025 01:09:18 +0100 Subject: [PATCH 10/10] Remove unused variable --- src/libexpr/primops.cc | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 87f7eeb18..7c9ce7104 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -2478,8 +2478,6 @@ static void addPath( const NixStringContext & context) { try { - StorePathSet refs; - if (path.accessor == state.rootFS && state.store->isInStore(path.path.abs())) { // FIXME: handle CA derivation outputs (where path needs to // be rewritten to the actual output).