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 8aef85dc5..c0842dbbd 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -246,15 +246,42 @@ 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()) + ({ + /* 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(); + + auto realStoreDir = dirOf(store->toRealPath(StorePath::dummy)); + if (settings.pureEval || store->storeDir != realStoreDir) { + auto storeFS = makeMountedSourceAccessor( + { + {CanonPath::root, makeEmptySourceAccessor()}, + {CanonPath(store->storeDir), makeFSSourceAccessor(realStoreDir)} + }); + accessor = settings.pureEval + ? storeFS + : 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( @@ -344,7 +371,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) @@ -422,16 +449,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(); @@ -2051,7 +2068,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); } @@ -2432,7 +2449,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 rootPath(path); } @@ -3086,7 +3103,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 767578343..aca2e8bfd 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -412,17 +412,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/primops.cc b/src/libexpr/primops.cc index 51d2991e7..7c9ce7104 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -145,8 +145,7 @@ static SourcePath realisePath(EvalState & state, const PosIdx pos, Value & v, st try { if (!context.empty() && path.accessor == state.rootFS) { 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) { @@ -2479,21 +2478,11 @@ 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). 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; diff --git a/src/libflake/flake/flake.cc b/src/libflake/flake/flake.cc index ec0c8dde9..4a72f0399 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}; } }(); @@ -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/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/mounted-source-accessor.cc b/src/libutil/mounted-source-accessor.cc index 45c8adbd8..79223d155 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); @@ -69,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) 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..eec0850c2 --- /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) + 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) + 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) + 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)); +} + +} 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); 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"