Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 21 additions & 18 deletions src/libexpr/eval.cc
Original file line number Diff line number Diff line change
Expand Up @@ -236,24 +236,17 @@ EvalState::EvalState(
{CanonPath(store->storeDir), store->getFSAccessor(settings.pureEval)},
}))
, rootFS([&] {
auto accessor = [&]() -> decltype(rootFS) {
/* In pure eval mode, we provide a filesystem that only
contains the Nix store. */
if (settings.pureEval)
return storeFS;

/* 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 realStoreDir = dirOf(store->toRealPath(StorePath::dummy));
if (store->storeDir != realStoreDir)
return makeUnionSourceAccessor({getFSSourceAccessor(), storeFS});

return getFSSourceAccessor();
}();
/* In pure eval mode, we provide a filesystem that only
contains the Nix store.

Otherwise, use a union accessor to make the augmented 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, and also for lazy
mounted fetchTree. */
auto accessor = settings.pureEval ? storeFS.cast<SourceAccessor>()
: makeUnionSourceAccessor({getFSSourceAccessor(), storeFS});

/* Apply access control if needed. */
if (settings.restrictEval || settings.pureEval)
Expand Down Expand Up @@ -3133,6 +3126,11 @@ SourcePath EvalState::findFile(const LookupPath & lookupPath, const std::string_
auto res = (r / CanonPath(suffix)).resolveSymlinks();
if (res.pathExists())
return res;

// Backward compatibility hack: throw an exception if access
// to this path is not allowed.
if (auto accessor = res.accessor.dynamic_pointer_cast<FilteringSourceAccessor>())
accessor->checkAccess(res.path);
Comment on lines +3129 to +3133
Copy link
Member

Choose a reason for hiding this comment

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

This stuff has been on lazy trees for a while I know, but I guess a this point I am skeptical it is still needed? Especially with #14081

Copy link
Member Author

Choose a reason for hiding this comment

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

No, they're still needed.

}

if (hasPrefix(path, "nix/"))
Expand Down Expand Up @@ -3199,6 +3197,11 @@ std::optional<SourcePath> EvalState::resolveLookupPathPath(const LookupPath::Pat
if (path.resolveSymlinks().pathExists())
return finish(std::move(path));
else {
// Backward compatibility hack: throw an exception if access
// to this path is not allowed.
if (auto accessor = path.accessor.dynamic_pointer_cast<FilteringSourceAccessor>())
accessor->checkAccess(path.path);

logWarning({.msg = HintFmt("Nix search path entry '%1%' does not exist, ignoring", value)});
}
}
Expand Down
6 changes: 6 additions & 0 deletions src/libexpr/include/nix/expr/eval.hh
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class Store;
namespace fetchers {
struct Settings;
struct InputCache;
struct Input;
} // namespace fetchers
struct EvalSettings;
class EvalState;
Expand Down Expand Up @@ -570,6 +571,11 @@ public:

void checkURI(const std::string & uri);

/**
* Mount an input on the Nix store.
*/
StorePath mountInput(fetchers::Input & input, const fetchers::Input & originalInput, ref<SourceAccessor> accessor);

/**
* Parse a Nix expression from the specified file.
*/
Expand Down
25 changes: 25 additions & 0 deletions src/libexpr/paths.cc
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#include "nix/store/store-api.hh"
#include "nix/expr/eval.hh"
#include "nix/util/mounted-source-accessor.hh"
#include "nix/fetchers/fetch-to-store.hh"

namespace nix {

Expand All @@ -18,4 +20,27 @@ SourcePath EvalState::storePath(const StorePath & path)
return {rootFS, CanonPath{store->printStorePath(path)}};
}

StorePath
EvalState::mountInput(fetchers::Input & input, const fetchers::Input & originalInput, ref<SourceAccessor> accessor)
{
auto storePath = fetchToStore(fetchSettings, *store, accessor, FetchMode::Copy, input.getName());

allowPath(storePath); // FIXME: should just whitelist the entire virtual store
Copy link
Member

Choose a reason for hiding this comment

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

I would like this part done sooner rather than later


storeFS->mount(CanonPath(store->printStorePath(storePath)), accessor);

auto narHash = store->queryPathInfo(storePath)->narHash;
input.attrs.insert_or_assign("narHash", narHash.to_string(HashFormat::SRI, true));

if (originalInput.getNarHash() && narHash != *originalInput.getNarHash())
throw Error(
(unsigned int) 102,
Copy link
Contributor

Choose a reason for hiding this comment

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

Isn't there an enum for this?

Copy link
Member Author

Choose a reason for hiding this comment

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

Don't think so... There is Worker::failingExitStatus() which should probably be factored out.

Copy link
Member

Choose a reason for hiding this comment

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

I was meaning to clean that up and unify with BuildResult statuses at some point, fwiw.

"NAR hash mismatch in input '%s', expected '%s' but got '%s'",
originalInput.to_string(),
narHash.to_string(HashFormat::SRI, true),
originalInput.getNarHash()->to_string(HashFormat::SRI, true));

return storePath;
}

} // namespace nix
7 changes: 4 additions & 3 deletions src/libexpr/primops/fetchTree.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "nix/util/url.hh"
#include "nix/expr/value-to-json.hh"
#include "nix/fetchers/fetch-to-store.hh"
#include "nix/fetchers/input-cache.hh"

#include <nlohmann/json.hpp>

Expand Down Expand Up @@ -218,11 +219,11 @@ static void fetchTree(
throw Error("input '%s' is not allowed to use the '__final' attribute", input.to_string());
}

auto [storePath, input2] = input.fetchToStore(state.store);
auto cachedInput = state.inputCache->getAccessor(state.store, input, fetchers::UseRegistries::No);

state.allowPath(storePath);
auto storePath = state.mountInput(cachedInput.lockedInput, input, cachedInput.accessor);

emitTreeAttrs(state, storePath, input2, v, params.emptyRevFallback, false);
emitTreeAttrs(state, storePath, cachedInput.lockedInput, v, params.emptyRevFallback, false);
}

static void prim_fetchTree(EvalState & state, const PosIdx pos, Value ** args, Value & v)
Expand Down
15 changes: 12 additions & 3 deletions src/libfetchers/fetch-to-store.cc
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "nix/fetchers/fetch-to-store.hh"
#include "nix/fetchers/fetchers.hh"
#include "nix/fetchers/fetch-settings.hh"
#include "nix/util/environment-variables.hh"

namespace nix {

Expand All @@ -27,14 +28,22 @@ StorePath fetchToStore(

std::optional<fetchers::Cache::Key> cacheKey;

if (!filter && path.accessor->fingerprint) {
cacheKey = makeFetchToStoreCacheKey(std::string{name}, *path.accessor->fingerprint, method, path.path.abs());
auto [subpath, fingerprint] = filter ? std::pair<CanonPath, std::optional<std::string>>{path.path, std::nullopt}
: path.accessor->getFingerprint(path.path);

if (fingerprint) {
cacheKey = makeFetchToStoreCacheKey(std::string{name}, *fingerprint, method, subpath.abs());
if (auto res = settings.getCache()->lookupStorePath(*cacheKey, store)) {
debug("store path cache hit for '%s'", path);
return res->storePath;
}
} else
} else {
static auto barf = getEnv("_NIX_TEST_BARF_ON_UNCACHEABLE").value_or("") == "1";
if (barf && !filter)
throw Error("source path '%s' is uncacheable (filter=%d)", path, (bool) filter);
// FIXME: could still provide in-memory caching keyed on `SourcePath`.
debug("source path '%s' is uncacheable", path);
}

Activity act(
*logger,
Expand Down
6 changes: 4 additions & 2 deletions src/libfetchers/fetchers.cc
Original file line number Diff line number Diff line change
Expand Up @@ -356,8 +356,10 @@ std::pair<ref<SourceAccessor>, Input> Input::getAccessorUnchecked(ref<Store> sto

auto [accessor, result] = scheme->getAccessor(store, *this);

assert(!accessor->fingerprint);
accessor->fingerprint = result.getFingerprint(store);
if (!accessor->fingerprint)
accessor->fingerprint = result.getFingerprint(store);
else
result.cachedFingerprint = accessor->fingerprint;

return {accessor, std::move(result)};
}
Expand Down
20 changes: 19 additions & 1 deletion src/libfetchers/filtering-source-accessor.cc
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,26 @@ std::string FilteringSourceAccessor::readFile(const CanonPath & path)
return next->readFile(prefix / path);
}

void FilteringSourceAccessor::readFile(const CanonPath & path, Sink & sink, std::function<void(uint64_t)> sizeCallback)
{
checkAccess(path);
return next->readFile(prefix / path, sink, sizeCallback);
}

bool FilteringSourceAccessor::pathExists(const CanonPath & path)
{
return isAllowed(path) && next->pathExists(prefix / path);
}

std::optional<SourceAccessor::Stat> FilteringSourceAccessor::maybeLstat(const CanonPath & path)
{
return isAllowed(path) ? next->maybeLstat(prefix / path) : std::nullopt;
}

SourceAccessor::Stat FilteringSourceAccessor::lstat(const CanonPath & path)
{
checkAccess(path);
return next->maybeLstat(prefix / path);
return next->lstat(prefix / path);
}

SourceAccessor::DirEntries FilteringSourceAccessor::readDirectory(const CanonPath & path)
Expand All @@ -49,6 +60,13 @@ std::string FilteringSourceAccessor::showPath(const CanonPath & path)
return displayPrefix + next->showPath(prefix / path) + displaySuffix;
}

std::pair<CanonPath, std::optional<std::string>> FilteringSourceAccessor::getFingerprint(const CanonPath & path)
{
if (fingerprint)
return {path, fingerprint};
return next->getFingerprint(prefix / path);
}

void FilteringSourceAccessor::checkAccess(const CanonPath & path)
{
if (!isAllowed(path))
Expand Down
5 changes: 2 additions & 3 deletions src/libfetchers/git.cc
Original file line number Diff line number Diff line change
Expand Up @@ -893,8 +893,7 @@ struct GitInputScheme : InputScheme
return makeFingerprint(*rev);
else {
auto repoInfo = getRepoInfo(input);
if (auto repoPath = repoInfo.getPath();
repoPath && repoInfo.workdirInfo.headRev && repoInfo.workdirInfo.submodules.empty()) {
if (auto repoPath = repoInfo.getPath(); repoPath && repoInfo.workdirInfo.submodules.empty()) {
/* Calculate a fingerprint that takes into account the
deleted and modified/added files. */
HashSink hashSink{HashAlgorithm::SHA512};
Expand All @@ -907,7 +906,7 @@ struct GitInputScheme : InputScheme
writeString("deleted:", hashSink);
writeString(file.abs(), hashSink);
}
return makeFingerprint(*repoInfo.workdirInfo.headRev)
return makeFingerprint(repoInfo.workdirInfo.headRev.value_or(nullRev))
+ ";d=" + hashSink.finish().hash.to_string(HashFormat::Base16, false);
}
return std::nullopt;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,12 @@ struct FilteringSourceAccessor : SourceAccessor

std::string readFile(const CanonPath & path) override;

void readFile(const CanonPath & path, Sink & sink, std::function<void(uint64_t)> sizeCallback) override;

bool pathExists(const CanonPath & path) override;

Stat lstat(const CanonPath & path) override;

std::optional<Stat> maybeLstat(const CanonPath & path) override;

DirEntries readDirectory(const CanonPath & path) override;
Expand All @@ -46,6 +50,8 @@ struct FilteringSourceAccessor : SourceAccessor

std::string showPath(const CanonPath & path) override;

std::pair<CanonPath, std::optional<std::string>> getFingerprint(const CanonPath & path) override;

/**
* Call `makeNotAllowedError` to throw a `RestrictedPathError`
* exception if `isAllowed()` returns `false` for `path`.
Expand Down
42 changes: 15 additions & 27 deletions src/libfetchers/path.cc
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,6 @@ struct PathInputScheme : InputScheme

auto absPath = getAbsPath(input);

Activity act(*logger, lvlTalkative, actUnknown, fmt("copying %s to the store", absPath));

// FIXME: check whether access to 'path' is allowed.
auto storePath = store->maybeParseStorePath(absPath.string());

Expand All @@ -133,43 +131,33 @@ struct PathInputScheme : InputScheme

time_t mtime = 0;
if (!storePath || storePath->name() != "source" || !store->isValidPath(*storePath)) {
Activity act(*logger, lvlTalkative, actUnknown, fmt("copying %s to the store", absPath));
// FIXME: try to substitute storePath.
auto src = sinkToSource(
[&](Sink & sink) { mtime = dumpPathAndGetMtime(absPath.string(), sink, defaultPathFilter); });
storePath = store->addToStoreFromDump(*src, "source");
}

// To avoid copying the path again to the /nix/store, we need to add a cache entry.
ContentAddressMethod method = ContentAddressMethod::Raw::NixArchive;
auto fp = getFingerprint(store, input);
if (fp) {
auto cacheKey = makeFetchToStoreCacheKey(input.getName(), *fp, method, "/");
input.settings->getCache()->upsert(cacheKey, *store, {}, *storePath);
}
auto accessor = ref{store->getFSAccessor(*storePath)};

// To prevent `fetchToStore()` copying the path again to Nix
// store, pre-create an entry in the fetcher cache.
auto info = store->queryPathInfo(*storePath);
accessor->fingerprint =
fmt("path:%s", store->queryPathInfo(*storePath)->narHash.to_string(HashFormat::SRI, true));
input.settings->getCache()->upsert(
makeFetchToStoreCacheKey(
input.getName(), *accessor->fingerprint, ContentAddressMethod::Raw::NixArchive, "/"),
*store,
{},
*storePath);

/* Trust the lastModified value supplied by the user, if
any. It's not a "secure" attribute so we don't care. */
if (!input.getLastModified())
input.attrs.insert_or_assign("lastModified", uint64_t(mtime));

return {ref{store->getFSAccessor(*storePath)}, std::move(input)};
}

std::optional<std::string> getFingerprint(ref<Store> store, const Input & input) const override
{
if (isRelative(input))
return std::nullopt;

/* If this path is in the Nix store, use the hash of the
store object and the subpath. */
auto path = getAbsPath(input);
try {
auto [storePath, subPath] = store->toStorePath(path.string());
auto info = store->queryPathInfo(storePath);
return fmt("path:%s:%s", info->narHash.to_string(HashFormat::Base16, false), subPath);
} catch (Error &) {
return std::nullopt;
}
return {accessor, std::move(input)};
}

std::optional<ExperimentalFeature> experimentalFeature() const override
Expand Down
35 changes: 11 additions & 24 deletions src/libflake/flake.cc
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,6 @@ using namespace flake;

namespace flake {

static StorePath copyInputToStore(
EvalState & state, fetchers::Input & input, const fetchers::Input & originalInput, ref<SourceAccessor> accessor)
{
auto storePath = fetchToStore(*input.settings, *state.store, accessor, FetchMode::Copy, input.getName());

state.allowPath(storePath);

auto narHash = state.store->queryPathInfo(storePath)->narHash;
input.attrs.insert_or_assign("narHash", narHash.to_string(HashFormat::SRI, true));

assert(!originalInput.getNarHash() || storePath == originalInput.computeStorePath(*state.store));

return storePath;
}

static void forceTrivialValue(EvalState & state, Value & value, const PosIdx pos)
{
if (value.isThunk() && value.isTrivial())
Expand Down Expand Up @@ -360,11 +345,14 @@ static Flake getFlake(
lockedRef = FlakeRef(std::move(cachedInput2.lockedInput), newLockedRef.subdir);
}

// Copy the tree to the store.
auto storePath = copyInputToStore(state, lockedRef.input, originalRef.input, cachedInput.accessor);

// Re-parse flake.nix from the store.
return readFlake(state, originalRef, resolvedRef, lockedRef, state.storePath(storePath), lockRootAttrPath);
return readFlake(
state,
originalRef,
resolvedRef,
lockedRef,
state.storePath(state.mountInput(lockedRef.input, originalRef.input, cachedInput.accessor)),
lockRootAttrPath);
}

Flake getFlake(EvalState & state, const FlakeRef & originalRef, fetchers::UseRegistries useRegistries)
Expand Down Expand Up @@ -721,11 +709,10 @@ lockFlake(const Settings & settings, EvalState & state, const FlakeRef & topRef,

auto lockedRef = FlakeRef(std::move(cachedInput.lockedInput), input.ref->subdir);

// FIXME: allow input to be lazy.
auto storePath = copyInputToStore(
state, lockedRef.input, input.ref->input, cachedInput.accessor);

return {state.storePath(storePath), lockedRef};
return {
state.storePath(
state.mountInput(lockedRef.input, input.ref->input, cachedInput.accessor)),
lockedRef};
}
}();

Expand Down
Loading
Loading