mirror of
https://github.com/NixOS/nix.git
synced 2025-12-02 15:11:00 +01:00
Merge remote-tracking branch 'detsys/detsys-main' into fix-deep-overrides
This commit is contained in:
commit
0e352a6cec
51 changed files with 722 additions and 461 deletions
|
|
@ -262,6 +262,19 @@ struct EvalSettings : Config
|
|||
R"(
|
||||
If set to true, flakes and trees fetched by [`builtins.fetchTree`](@docroot@/language/builtins.md#builtins-fetchTree) are only copied to the Nix store when they're used as a dependency of a derivation. This avoids copying (potentially large) source trees unnecessarily.
|
||||
)"};
|
||||
|
||||
// FIXME: this setting should really be in libflake, but it's
|
||||
// currently needed in mountInput().
|
||||
Setting<bool> lazyLocks{
|
||||
this,
|
||||
false,
|
||||
"lazy-locks",
|
||||
R"(
|
||||
If enabled, Nix only includes NAR hashes in lock file entries if they're necessary to lock the input (i.e. when there is no other attribute that allows the content to be verified, like a Git revision).
|
||||
This is not backward compatible with older versions of Nix.
|
||||
If disabled, lock file entries always contain a NAR hash.
|
||||
)"
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -77,25 +77,30 @@ StorePath EvalState::mountInput(
|
|||
|
||||
allowPath(storePath); // FIXME: should just whitelist the entire virtual store
|
||||
|
||||
std::optional<Hash> _narHash;
|
||||
|
||||
auto getNarHash = [&]() {
|
||||
if (!_narHash) {
|
||||
if (store->isValidPath(storePath))
|
||||
_narHash = store->queryPathInfo(storePath)->narHash;
|
||||
else
|
||||
_narHash = fetchToStore2(*store, accessor, FetchMode::DryRun, input.getName()).second;
|
||||
}
|
||||
return _narHash;
|
||||
};
|
||||
|
||||
storeFS->mount(CanonPath(store->printStorePath(storePath)), accessor);
|
||||
|
||||
if (requireLockable && (!settings.lazyTrees || !input.isLocked()) && !input.getNarHash()) {
|
||||
// FIXME: use fetchToStore to make it cache this
|
||||
auto narHash = accessor->hashPath(CanonPath::root);
|
||||
input.attrs.insert_or_assign("narHash", narHash.to_string(HashFormat::SRI, true));
|
||||
}
|
||||
if (requireLockable && (!settings.lazyTrees || !settings.lazyLocks || !input.isLocked()) && !input.getNarHash())
|
||||
input.attrs.insert_or_assign("narHash", getNarHash()->to_string(HashFormat::SRI, true));
|
||||
|
||||
// FIXME: what to do with the NAR hash in lazy mode?
|
||||
if (!settings.lazyTrees && originalInput.getNarHash()) {
|
||||
auto expected = originalInput.computeStorePath(*store);
|
||||
if (storePath != expected)
|
||||
throw Error(
|
||||
(unsigned int) 102,
|
||||
"NAR hash mismatch in input '%s', expected '%s' but got '%s'",
|
||||
originalInput.to_string(),
|
||||
store->printStorePath(storePath),
|
||||
store->printStorePath(expected));
|
||||
}
|
||||
if (originalInput.getNarHash() && *getNarHash() != *originalInput.getNarHash())
|
||||
throw Error(
|
||||
(unsigned int) 102,
|
||||
"NAR hash mismatch in input '%s', expected '%s' but got '%s'",
|
||||
originalInput.to_string(),
|
||||
getNarHash()->to_string(HashFormat::SRI, true),
|
||||
originalInput.getNarHash()->to_string(HashFormat::SRI, true));
|
||||
|
||||
return storePath;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,19 +3,16 @@
|
|||
|
||||
namespace nix {
|
||||
|
||||
fetchers::Cache::Key makeFetchToStoreCacheKey(
|
||||
const std::string &name,
|
||||
const std::string &fingerprint,
|
||||
fetchers::Cache::Key makeSourcePathToHashCacheKey(
|
||||
const std::string & fingerprint,
|
||||
ContentAddressMethod method,
|
||||
const std::string &path)
|
||||
const std::string & path)
|
||||
{
|
||||
return fetchers::Cache::Key{"fetchToStore", {
|
||||
{"name", name},
|
||||
return fetchers::Cache::Key{"sourcePathToHash", {
|
||||
{"fingerprint", fingerprint},
|
||||
{"method", std::string{method.render()}},
|
||||
{"path", path}
|
||||
}};
|
||||
|
||||
}
|
||||
|
||||
StorePath fetchToStore(
|
||||
|
|
@ -27,39 +24,81 @@ StorePath fetchToStore(
|
|||
PathFilter * filter,
|
||||
RepairFlag repair)
|
||||
{
|
||||
// FIXME: add an optimisation for the case where the accessor is
|
||||
// a `PosixSourceAccessor` pointing to a store path.
|
||||
return fetchToStore2(store, path, mode, name, method, filter, repair).first;
|
||||
}
|
||||
|
||||
std::pair<StorePath, Hash> fetchToStore2(
|
||||
Store & store,
|
||||
const SourcePath & path,
|
||||
FetchMode mode,
|
||||
std::string_view name,
|
||||
ContentAddressMethod method,
|
||||
PathFilter * filter,
|
||||
RepairFlag repair)
|
||||
{
|
||||
std::optional<fetchers::Cache::Key> cacheKey;
|
||||
std::optional<std::string> fingerprint;
|
||||
|
||||
if (!filter && (fingerprint = path.accessor->getFingerprint(path.path))) {
|
||||
cacheKey = makeFetchToStoreCacheKey(std::string{name}, *fingerprint, method, path.path.abs());
|
||||
if (auto res = fetchers::getCache()->lookupStorePath(*cacheKey, store, mode == FetchMode::DryRun)) {
|
||||
debug("store path cache hit for '%s'", path);
|
||||
return res->storePath;
|
||||
auto [subpath, fingerprint] =
|
||||
filter
|
||||
? std::pair<CanonPath, std::optional<std::string>>{path.path, std::nullopt}
|
||||
: path.accessor->getFingerprint(path.path);
|
||||
|
||||
if (fingerprint) {
|
||||
cacheKey = makeSourcePathToHashCacheKey(*fingerprint, method, subpath.abs());
|
||||
if (auto res = fetchers::getCache()->lookup(*cacheKey)) {
|
||||
auto hash = Hash::parseSRI(fetchers::getStrAttr(*res, "hash"));
|
||||
auto storePath = store.makeFixedOutputPathFromCA(name,
|
||||
ContentAddressWithReferences::fromParts(method, hash, {}));
|
||||
if (mode == FetchMode::DryRun || store.isValidPath(storePath)) {
|
||||
debug("source path '%s' cache hit in '%s' (hash '%s')", path, store.printStorePath(storePath), hash.to_string(HashFormat::SRI, true));
|
||||
return {storePath, hash};
|
||||
}
|
||||
debug("source path '%s' not in store", path);
|
||||
}
|
||||
} else
|
||||
debug("source path '%s' is uncacheable (%d, %d)", path, filter, (bool) fingerprint);
|
||||
} else {
|
||||
static auto barf = getEnv("_NIX_TEST_BARF_ON_UNCACHEABLE").value_or("") == "1";
|
||||
if (barf)
|
||||
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, lvlChatty, actUnknown,
|
||||
fmt(mode == FetchMode::DryRun ? "hashing '%s'" : "copying '%s' to the store", path));
|
||||
|
||||
auto filter2 = filter ? *filter : defaultPathFilter;
|
||||
|
||||
auto storePath =
|
||||
auto [storePath, hash] =
|
||||
mode == FetchMode::DryRun
|
||||
? store.computeStorePath(
|
||||
name, path, method, HashAlgorithm::SHA256, {}, filter2).first
|
||||
: store.addToStore(
|
||||
name, path, method, HashAlgorithm::SHA256, {}, filter2, repair);
|
||||
|
||||
debug(mode == FetchMode::DryRun ? "hashed '%s'" : "copied '%s' to '%s'", path, store.printStorePath(storePath));
|
||||
? ({
|
||||
auto [storePath, hash] = store.computeStorePath(
|
||||
name, path, method, HashAlgorithm::SHA256, {}, filter2);
|
||||
debug("hashed '%s' to '%s' (hash '%s')", path, store.printStorePath(storePath), hash.to_string(HashFormat::SRI, true));
|
||||
std::make_pair(storePath, hash);
|
||||
})
|
||||
: ({
|
||||
// FIXME: ideally addToStore() would return the hash
|
||||
// right away (like computeStorePath()).
|
||||
auto storePath = store.addToStore(
|
||||
name, path, method, HashAlgorithm::SHA256, {}, filter2, repair);
|
||||
auto info = store.queryPathInfo(storePath);
|
||||
assert(info->references.empty());
|
||||
auto hash =
|
||||
method == ContentAddressMethod::Raw::NixArchive
|
||||
? info->narHash
|
||||
: ({
|
||||
if (!info->ca || info->ca->method != method)
|
||||
throw Error("path '%s' lacks a CA field", store.printStorePath(storePath));
|
||||
info->ca->hash;
|
||||
});
|
||||
debug("copied '%s' to '%s' (hash '%s')", path, store.printStorePath(storePath), hash.to_string(HashFormat::SRI, true));
|
||||
std::make_pair(storePath, hash);
|
||||
});
|
||||
|
||||
if (cacheKey)
|
||||
fetchers::getCache()->upsert(*cacheKey, store, {}, storePath);
|
||||
fetchers::getCache()->upsert(*cacheKey, {{"hash", hash.to_string(HashFormat::SRI, true)}});
|
||||
|
||||
return storePath;
|
||||
return {storePath, hash};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -338,8 +338,7 @@ std::pair<ref<SourceAccessor>, Input> Input::getAccessorUnchecked(ref<Store> sto
|
|||
|
||||
auto accessor = make_ref<SubstitutedSourceAccessor>(makeStorePathAccessor(store, storePath));
|
||||
|
||||
if (auto fingerprint = getFingerprint(store))
|
||||
accessor->setFingerprint(*fingerprint);
|
||||
accessor->fingerprint = getFingerprint(store);
|
||||
|
||||
// FIXME: ideally we would use the `showPath()` of the
|
||||
// "real" accessor for this fetcher type.
|
||||
|
|
@ -353,10 +352,8 @@ std::pair<ref<SourceAccessor>, Input> Input::getAccessorUnchecked(ref<Store> sto
|
|||
|
||||
auto [accessor, result] = scheme->getAccessor(store, *this);
|
||||
|
||||
assert(!accessor->getFingerprint(CanonPath::root));
|
||||
|
||||
if (auto fingerprint = getFingerprint(store))
|
||||
accessor->setFingerprint(*fingerprint);
|
||||
if (!accessor->fingerprint)
|
||||
accessor->fingerprint = result.getFingerprint(store);
|
||||
|
||||
return {accessor, std::move(result)};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,16 +58,13 @@ std::string FilteringSourceAccessor::showPath(const CanonPath & path)
|
|||
return displayPrefix + next->showPath(prefix / path) + displaySuffix;
|
||||
}
|
||||
|
||||
std::optional<std::string> FilteringSourceAccessor::getFingerprint(const CanonPath & path)
|
||||
std::pair<CanonPath, std::optional<std::string>> FilteringSourceAccessor::getFingerprint(const CanonPath & path)
|
||||
{
|
||||
if (fingerprint)
|
||||
return {path, fingerprint};
|
||||
return next->getFingerprint(prefix / path);
|
||||
}
|
||||
|
||||
void FilteringSourceAccessor::setFingerprint(std::string fingerprint)
|
||||
{
|
||||
next->setFingerprint(std::move(fingerprint));
|
||||
}
|
||||
|
||||
void FilteringSourceAccessor::checkAccess(const CanonPath & path)
|
||||
{
|
||||
if (!isAllowed(path))
|
||||
|
|
|
|||
|
|
@ -860,7 +860,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};
|
||||
|
|
@ -873,7 +873,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().first.to_string(HashFormat::Base16, false);
|
||||
}
|
||||
return std::nullopt;
|
||||
|
|
@ -882,7 +882,8 @@ struct GitInputScheme : InputScheme
|
|||
|
||||
bool isLocked(const Input & input) const override
|
||||
{
|
||||
return (bool) input.getRev();
|
||||
auto rev = input.getRev();
|
||||
return rev && rev != nullRev;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,16 @@ StorePath fetchToStore(
|
|||
PathFilter * filter = nullptr,
|
||||
RepairFlag repair = NoRepair);
|
||||
|
||||
fetchers::Cache::Key makeFetchToStoreCacheKey(
|
||||
const std::string & name, const std::string & fingerprint, ContentAddressMethod method, const std::string & path);
|
||||
std::pair<StorePath, Hash> fetchToStore2(
|
||||
Store & store,
|
||||
const SourcePath & path,
|
||||
FetchMode mode,
|
||||
std::string_view name = "source",
|
||||
ContentAddressMethod method = ContentAddressMethod::Raw::NixArchive,
|
||||
PathFilter * filter = nullptr,
|
||||
RepairFlag repair = NoRepair);
|
||||
|
||||
fetchers::Cache::Key
|
||||
makeSourcePathToHashCacheKey(const std::string & fingerprint, ContentAddressMethod method, const std::string & path);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,9 +50,7 @@ struct FilteringSourceAccessor : SourceAccessor
|
|||
|
||||
std::string showPath(const CanonPath & path) override;
|
||||
|
||||
std::optional<std::string> getFingerprint(const CanonPath & path) override;
|
||||
|
||||
void setFingerprint(std::string fingerprint) override;
|
||||
std::pair<CanonPath, std::optional<std::string>> getFingerprint(const CanonPath & path) override;
|
||||
|
||||
/**
|
||||
* Call `makeNotAllowedError` to throw a `RestrictedPathError`
|
||||
|
|
|
|||
|
|
@ -144,37 +144,22 @@ struct PathInputScheme : InputScheme
|
|||
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, "/");
|
||||
fetchers::getCache()->upsert(cacheKey, *store, {}, *storePath);
|
||||
}
|
||||
auto accessor = makeStorePathAccessor(store, *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));
|
||||
fetchers::getCache()->upsert(
|
||||
makeSourcePathToHashCacheKey(*accessor->fingerprint, ContentAddressMethod::Raw::NixArchive, "/"),
|
||||
{{"hash", info->narHash.to_string(HashFormat::SRI, true)}});
|
||||
|
||||
/* 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 {makeStorePathAccessor(store, *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)};
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ test(
|
|||
this_exe,
|
||||
env : {
|
||||
'_NIX_TEST_UNIT_DATA': meson.current_source_dir() / 'data',
|
||||
'HOME': meson.current_build_dir() / 'test-home',
|
||||
},
|
||||
protocol : 'gtest',
|
||||
)
|
||||
|
|
|
|||
|
|
@ -56,17 +56,13 @@ mkMesonExecutable (finalAttrs: {
|
|||
{
|
||||
meta.broken = !stdenv.hostPlatform.emulatorAvailable buildPackages;
|
||||
}
|
||||
(
|
||||
lib.optionalString stdenv.hostPlatform.isWindows ''
|
||||
export HOME="$PWD/home-dir"
|
||||
mkdir -p "$HOME"
|
||||
''
|
||||
+ ''
|
||||
export _NIX_TEST_UNIT_DATA=${resolvePath ./data}
|
||||
${stdenv.hostPlatform.emulator buildPackages} ${lib.getExe finalAttrs.finalPackage}
|
||||
touch $out
|
||||
''
|
||||
);
|
||||
(''
|
||||
export _NIX_TEST_UNIT_DATA=${resolvePath ./data}
|
||||
export HOME="$TMPDIR/home"
|
||||
mkdir -p "$HOME"
|
||||
${stdenv.hostPlatform.emulator buildPackages} ${lib.getExe finalAttrs.finalPackage}
|
||||
touch $out
|
||||
'');
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -562,7 +562,7 @@ LockedFlake lockFlake(
|
|||
|
||||
/* Get the input flake, resolve 'path:./...'
|
||||
flakerefs relative to the parent flake. */
|
||||
auto getInputFlake = [&](const FlakeRef & ref)
|
||||
auto getInputFlake = [&](const FlakeRef & ref, const fetchers::UseRegistries useRegistries)
|
||||
{
|
||||
if (auto resolvedPath = resolveRelativePath()) {
|
||||
return readFlake(state, ref, ref, ref, *resolvedPath, inputAttrPath);
|
||||
|
|
@ -653,7 +653,7 @@ LockedFlake lockFlake(
|
|||
}
|
||||
|
||||
if (mustRefetch) {
|
||||
auto inputFlake = getInputFlake(oldLock->lockedRef);
|
||||
auto inputFlake = getInputFlake(oldLock->lockedRef, useRegistriesInputs);
|
||||
nodePaths.emplace(childNode, inputFlake.path.parent());
|
||||
computeLocks(inputFlake.inputs, childNode, inputAttrPath, oldLock, followsPrefix,
|
||||
inputFlake.path, false);
|
||||
|
|
@ -678,7 +678,8 @@ LockedFlake lockFlake(
|
|||
nuked the next time we update the lock
|
||||
file. That is, overrides are sticky unless you
|
||||
use --no-write-lock-file. */
|
||||
auto ref = (input2.ref && explicitCliOverrides.contains(inputAttrPath)) ? *input2.ref : *input.ref;
|
||||
auto inputIsOverride = explicitCliOverrides.contains(inputAttrPath);
|
||||
auto ref = (input2.ref && inputIsOverride) ? *input2.ref : *input.ref;
|
||||
|
||||
/* Warn against the use of indirect flakerefs
|
||||
(but only at top-level since we don't want
|
||||
|
|
@ -704,7 +705,7 @@ LockedFlake lockFlake(
|
|||
};
|
||||
|
||||
if (input.isFlake) {
|
||||
auto inputFlake = getInputFlake(*input.ref);
|
||||
auto inputFlake = getInputFlake(*input.ref, inputIsOverride ? fetchers::UseRegistries::All : useRegistriesInputs);
|
||||
|
||||
auto childNode = make_ref<LockedNode>(
|
||||
inputFlake.lockedRef,
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ struct PluginSettings : Config
|
|||
itself, they must be DSOs compatible with the instance of Nix
|
||||
running at the time (i.e. compiled against the same headers, not
|
||||
linked to any incompatible libraries). They should not be linked to
|
||||
any Nix libraries directly, as those are already at load
|
||||
any Nix libraries directly, as those are already available at load
|
||||
time.
|
||||
|
||||
If an entry in the list is a directory, all files in the directory
|
||||
|
|
|
|||
|
|
@ -100,6 +100,7 @@ test(
|
|||
this_exe,
|
||||
env : {
|
||||
'_NIX_TEST_UNIT_DATA': meson.current_source_dir() / 'data',
|
||||
'HOME': meson.current_build_dir() / 'test-home',
|
||||
},
|
||||
protocol : 'gtest',
|
||||
)
|
||||
|
|
|
|||
|
|
@ -28,10 +28,6 @@ TEST_F(nix_api_store_test, nix_store_get_uri)
|
|||
|
||||
TEST_F(nix_api_util_context, nix_store_get_storedir_default)
|
||||
{
|
||||
if (nix::getEnv("HOME").value_or("") == "/homeless-shelter") {
|
||||
// skipping test in sandbox because nix_store_open tries to create /nix/var/nix/profiles
|
||||
GTEST_SKIP();
|
||||
}
|
||||
nix_libstore_init(ctx);
|
||||
Store * store = nix_store_open(ctx, nullptr, nullptr);
|
||||
assert_ctx_ok();
|
||||
|
|
@ -136,10 +132,6 @@ TEST_F(nix_api_store_test, nix_store_real_path)
|
|||
|
||||
TEST_F(nix_api_util_context, nix_store_real_path_relocated)
|
||||
{
|
||||
if (nix::getEnv("HOME").value_or("") == "/homeless-shelter") {
|
||||
// Can't open default store from within sandbox
|
||||
GTEST_SKIP();
|
||||
}
|
||||
auto tmp = nix::createTempDir();
|
||||
std::string storeRoot = tmp + "/store";
|
||||
std::string stateDir = tmp + "/state";
|
||||
|
|
@ -179,13 +171,7 @@ TEST_F(nix_api_util_context, nix_store_real_path_relocated)
|
|||
|
||||
TEST_F(nix_api_util_context, nix_store_real_path_binary_cache)
|
||||
{
|
||||
if (nix::getEnv("HOME").value_or("") == "/homeless-shelter") {
|
||||
// TODO: override NIX_CACHE_HOME?
|
||||
// skipping test in sandbox because narinfo cache can't be written
|
||||
GTEST_SKIP();
|
||||
}
|
||||
|
||||
Store * store = nix_store_open(ctx, "https://cache.nixos.org", nullptr);
|
||||
Store * store = nix_store_open(ctx, nix::fmt("file://%s/binary-cache", nix::createTempDir()).c_str(), nullptr);
|
||||
assert_ctx_ok();
|
||||
ASSERT_NE(store, nullptr);
|
||||
|
||||
|
|
|
|||
|
|
@ -73,17 +73,13 @@ mkMesonExecutable (finalAttrs: {
|
|||
{
|
||||
meta.broken = !stdenv.hostPlatform.emulatorAvailable buildPackages;
|
||||
}
|
||||
(
|
||||
lib.optionalString stdenv.hostPlatform.isWindows ''
|
||||
export HOME="$PWD/home-dir"
|
||||
mkdir -p "$HOME"
|
||||
''
|
||||
+ ''
|
||||
export _NIX_TEST_UNIT_DATA=${data + "/src/libstore-tests/data"}
|
||||
${stdenv.hostPlatform.emulator buildPackages} ${lib.getExe finalAttrs.finalPackage}
|
||||
touch $out
|
||||
''
|
||||
);
|
||||
(''
|
||||
export _NIX_TEST_UNIT_DATA=${data + "/src/libstore-tests/data"}
|
||||
export HOME="$TMPDIR/home"
|
||||
mkdir -p "$HOME"
|
||||
${stdenv.hostPlatform.emulator buildPackages} ${lib.getExe finalAttrs.finalPackage}
|
||||
touch $out
|
||||
'');
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ struct FileTransferSettings : Config
|
|||
)"};
|
||||
|
||||
Setting<unsigned int> tries{this, 5, "download-attempts",
|
||||
"The number of times Nix will attempt to download a file before giving up."};
|
||||
"The number of times Nix attempts to download a file before giving up."};
|
||||
|
||||
Setting<size_t> downloadBufferSize{this, 64 * 1024 * 1024, "download-buffer-size",
|
||||
R"(
|
||||
|
|
|
|||
|
|
@ -247,7 +247,7 @@ LocalStore::LocalStore(ref<const Config> config)
|
|||
else if (curSchema == 0) { /* new store */
|
||||
curSchema = nixSchemaVersion;
|
||||
openDB(*state, true);
|
||||
writeFile(schemaPath, fmt("%1%", curSchema), 0666, true);
|
||||
writeFile(schemaPath, fmt("%1%", curSchema), 0666, FsSync::Yes);
|
||||
}
|
||||
|
||||
else if (curSchema < nixSchemaVersion) {
|
||||
|
|
@ -298,7 +298,7 @@ LocalStore::LocalStore(ref<const Config> config)
|
|||
txn.commit();
|
||||
}
|
||||
|
||||
writeFile(schemaPath, fmt("%1%", nixSchemaVersion), 0666, true);
|
||||
writeFile(schemaPath, fmt("%1%", nixSchemaVersion), 0666, FsSync::Yes);
|
||||
|
||||
lockFile(globalLock.get(), ltRead, true);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -129,6 +129,11 @@ private:
|
|||
*/
|
||||
Path topTmpDir;
|
||||
|
||||
/**
|
||||
* The file descriptor of the temporary directory.
|
||||
*/
|
||||
AutoCloseFD tmpDirFd;
|
||||
|
||||
/**
|
||||
* The path of the temporary directory in the sandbox.
|
||||
*/
|
||||
|
|
@ -325,9 +330,24 @@ private:
|
|||
|
||||
/**
|
||||
* Make a file owned by the builder.
|
||||
*
|
||||
* SAFETY: this function is prone to TOCTOU as it receives a path and not a descriptor.
|
||||
* It's only safe to call in a child of a directory only visible to the owner.
|
||||
*/
|
||||
void chownToBuilder(const Path & path);
|
||||
|
||||
/**
|
||||
* Make a file owned by the builder addressed by its file descriptor.
|
||||
*/
|
||||
void chownToBuilder(int fd, const Path & path);
|
||||
|
||||
/**
|
||||
* Create a file in `tmpDir` owned by the builder.
|
||||
*/
|
||||
void writeBuilderFile(
|
||||
const std::string & name,
|
||||
std::string_view contents);
|
||||
|
||||
/**
|
||||
* Run the builder's process.
|
||||
*/
|
||||
|
|
@ -900,7 +920,14 @@ void DerivationBuilderImpl::startBuilder()
|
|||
} else {
|
||||
tmpDir = topTmpDir;
|
||||
}
|
||||
chownToBuilder(tmpDir);
|
||||
|
||||
/* The TOCTOU between the previous mkdir call and this open call is unavoidable due to
|
||||
POSIX semantics.*/
|
||||
tmpDirFd = AutoCloseFD{open(tmpDir.c_str(), O_RDONLY | O_NOFOLLOW | O_DIRECTORY)};
|
||||
if (!tmpDirFd)
|
||||
throw SysError("failed to open the build temporary directory descriptor '%1%'", tmpDir);
|
||||
|
||||
chownToBuilder(tmpDirFd.get(), tmpDir);
|
||||
|
||||
for (auto & [outputName, status] : initialOutputs) {
|
||||
/* Set scratch path we'll actually use during the build.
|
||||
|
|
@ -1485,9 +1512,7 @@ void DerivationBuilderImpl::initTmpDir()
|
|||
} else {
|
||||
auto hash = hashString(HashAlgorithm::SHA256, i.first);
|
||||
std::string fn = ".attr-" + hash.to_string(HashFormat::Nix32, false);
|
||||
Path p = tmpDir + "/" + fn;
|
||||
writeFile(p, rewriteStrings(i.second, inputRewrites));
|
||||
chownToBuilder(p);
|
||||
writeBuilderFile(fn, rewriteStrings(i.second, inputRewrites));
|
||||
env[i.first + "Path"] = tmpDirInSandbox + "/" + fn;
|
||||
}
|
||||
}
|
||||
|
|
@ -1596,11 +1621,9 @@ void DerivationBuilderImpl::writeStructuredAttrs()
|
|||
|
||||
auto jsonSh = StructuredAttrs::writeShell(json);
|
||||
|
||||
writeFile(tmpDir + "/.attrs.sh", rewriteStrings(jsonSh, inputRewrites));
|
||||
chownToBuilder(tmpDir + "/.attrs.sh");
|
||||
writeBuilderFile(".attrs.sh", rewriteStrings(jsonSh, inputRewrites));
|
||||
env["NIX_ATTRS_SH_FILE"] = tmpDirInSandbox + "/.attrs.sh";
|
||||
writeFile(tmpDir + "/.attrs.json", rewriteStrings(json.dump(), inputRewrites));
|
||||
chownToBuilder(tmpDir + "/.attrs.json");
|
||||
writeBuilderFile(".attrs.json", rewriteStrings(json.dump(), inputRewrites));
|
||||
env["NIX_ATTRS_JSON_FILE"] = tmpDirInSandbox + "/.attrs.json";
|
||||
}
|
||||
}
|
||||
|
|
@ -1854,6 +1877,24 @@ void setupSeccomp()
|
|||
#endif
|
||||
}
|
||||
|
||||
void DerivationBuilderImpl::chownToBuilder(int fd, const Path & path)
|
||||
{
|
||||
if (!buildUser) return;
|
||||
if (fchown(fd, buildUser->getUID(), buildUser->getGID()) == -1)
|
||||
throw SysError("cannot change ownership of file '%1%'", path);
|
||||
}
|
||||
|
||||
void DerivationBuilderImpl::writeBuilderFile(
|
||||
const std::string & name,
|
||||
std::string_view contents)
|
||||
{
|
||||
auto path = std::filesystem::path(tmpDir) / name;
|
||||
AutoCloseFD fd{openat(tmpDirFd.get(), name.c_str(), O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC | O_EXCL | O_NOFOLLOW, 0666)};
|
||||
if (!fd)
|
||||
throw SysError("creating file %s", path);
|
||||
writeFile(fd, path, contents);
|
||||
chownToBuilder(fd.get(), path);
|
||||
}
|
||||
|
||||
void DerivationBuilderImpl::runChild()
|
||||
{
|
||||
|
|
@ -3065,6 +3106,15 @@ void DerivationBuilderImpl::checkOutputs(const std::map<std::string, ValidPathIn
|
|||
void DerivationBuilderImpl::deleteTmpDir(bool force)
|
||||
{
|
||||
if (topTmpDir != "") {
|
||||
/* As an extra precaution, even in the event of `deletePath` failing to
|
||||
* clean up, the `tmpDir` will be chowned as if we were to move
|
||||
* it inside the Nix store.
|
||||
*
|
||||
* This hardens against an attack which smuggles a file descriptor
|
||||
* to make use of the temporary directory.
|
||||
*/
|
||||
chmod(topTmpDir.c_str(), 0000);
|
||||
|
||||
/* Don't keep temporary directories for builtins because they
|
||||
might have privileged stuff (like a copy of netrc). */
|
||||
if (settings.keepFailed && !force && !drv.isBuiltin()) {
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ void restorePath(
|
|||
{
|
||||
switch (method) {
|
||||
case FileSerialisationMethod::Flat:
|
||||
writeFile(path, source, 0666, startFsync);
|
||||
writeFile(path, source, 0666, startFsync ? FsSync::Yes : FsSync::No);
|
||||
break;
|
||||
case FileSerialisationMethod::NixArchive:
|
||||
restorePath(path, source, startFsync);
|
||||
|
|
|
|||
|
|
@ -303,7 +303,7 @@ void readFile(const Path & path, Sink & sink, bool memory_map)
|
|||
}
|
||||
|
||||
|
||||
void writeFile(const Path & path, std::string_view s, mode_t mode, bool sync)
|
||||
void writeFile(const Path & path, std::string_view s, mode_t mode, FsSync sync)
|
||||
{
|
||||
AutoCloseFD fd = toDescriptor(open(path.c_str(), O_WRONLY | O_TRUNC | O_CREAT
|
||||
// TODO
|
||||
|
|
@ -313,22 +313,29 @@ void writeFile(const Path & path, std::string_view s, mode_t mode, bool sync)
|
|||
, mode));
|
||||
if (!fd)
|
||||
throw SysError("opening file '%1%'", path);
|
||||
try {
|
||||
writeFull(fd.get(), s);
|
||||
} catch (Error & e) {
|
||||
e.addTrace({}, "writing file '%1%'", path);
|
||||
throw;
|
||||
}
|
||||
if (sync)
|
||||
fd.fsync();
|
||||
// Explicitly close to make sure exceptions are propagated.
|
||||
|
||||
writeFile(fd, path, s, mode, sync);
|
||||
|
||||
/* Close explicitly to propagate the exceptions. */
|
||||
fd.close();
|
||||
if (sync)
|
||||
syncParent(path);
|
||||
}
|
||||
|
||||
void writeFile(AutoCloseFD & fd, const Path & origPath, std::string_view s, mode_t mode, FsSync sync)
|
||||
{
|
||||
assert(fd);
|
||||
try {
|
||||
writeFull(fd.get(), s);
|
||||
|
||||
void writeFile(const Path & path, Source & source, mode_t mode, bool sync)
|
||||
if (sync == FsSync::Yes)
|
||||
fd.fsync();
|
||||
|
||||
} catch (Error & e) {
|
||||
e.addTrace({}, "writing file '%1%'", origPath);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void writeFile(const Path & path, Source & source, mode_t mode, FsSync sync)
|
||||
{
|
||||
AutoCloseFD fd = toDescriptor(open(path.c_str(), O_WRONLY | O_TRUNC | O_CREAT
|
||||
// TODO
|
||||
|
|
@ -352,11 +359,11 @@ void writeFile(const Path & path, Source & source, mode_t mode, bool sync)
|
|||
e.addTrace({}, "writing file '%1%'", path);
|
||||
throw;
|
||||
}
|
||||
if (sync)
|
||||
if (sync == FsSync::Yes)
|
||||
fd.fsync();
|
||||
// Explicitly close to make sure exceptions are propagated.
|
||||
fd.close();
|
||||
if (sync)
|
||||
if (sync == FsSync::Yes)
|
||||
syncParent(path);
|
||||
}
|
||||
|
||||
|
|
@ -419,7 +426,8 @@ static void _deletePath(Descriptor parentfd, const std::filesystem::path & path,
|
|||
#ifndef _WIN32
|
||||
checkInterrupt();
|
||||
|
||||
std::string name(baseNameOf(path.native()));
|
||||
std::string name(path.filename());
|
||||
assert(name != "." && name != ".." && !name.empty());
|
||||
|
||||
struct stat st;
|
||||
if (fstatat(parentfd, name.c_str(), &st,
|
||||
|
|
@ -460,7 +468,7 @@ static void _deletePath(Descriptor parentfd, const std::filesystem::path & path,
|
|||
throw SysError("chmod %1%", path);
|
||||
}
|
||||
|
||||
int fd = openat(parentfd, path.c_str(), O_RDONLY);
|
||||
int fd = openat(parentfd, name.c_str(), O_RDONLY | O_DIRECTORY | O_NOFOLLOW);
|
||||
if (fd == -1)
|
||||
throw SysError("opening directory %1%", path);
|
||||
AutoCloseDir dir(fdopendir(fd));
|
||||
|
|
@ -472,7 +480,7 @@ static void _deletePath(Descriptor parentfd, const std::filesystem::path & path,
|
|||
checkInterrupt();
|
||||
std::string childName = dirent->d_name;
|
||||
if (childName == "." || childName == "..") continue;
|
||||
_deletePath(dirfd(dir.get()), path + "/" + childName, bytesFreed, ex);
|
||||
_deletePath(dirfd(dir.get()), path / childName, bytesFreed, ex);
|
||||
}
|
||||
if (errno) throw SysError("reading directory %1%", path);
|
||||
}
|
||||
|
|
@ -497,14 +505,13 @@ static void _deletePath(Descriptor parentfd, const std::filesystem::path & path,
|
|||
|
||||
static void _deletePath(const std::filesystem::path & path, uint64_t & bytesFreed)
|
||||
{
|
||||
Path dir = dirOf(path.string());
|
||||
if (dir == "")
|
||||
dir = "/";
|
||||
assert(path.is_absolute());
|
||||
assert(path.parent_path() != path);
|
||||
|
||||
AutoCloseFD dirfd = toDescriptor(open(dir.c_str(), O_RDONLY));
|
||||
AutoCloseFD dirfd = toDescriptor(open(path.parent_path().string().c_str(), O_RDONLY));
|
||||
if (!dirfd) {
|
||||
if (errno == ENOENT) return;
|
||||
throw SysError("opening directory '%1%'", path);
|
||||
throw SysError("opening directory %s", path.parent_path());
|
||||
}
|
||||
|
||||
std::exception_ptr ex;
|
||||
|
|
|
|||
|
|
@ -175,21 +175,27 @@ std::string readFile(const Path & path);
|
|||
std::string readFile(const std::filesystem::path & path);
|
||||
void readFile(const Path & path, Sink & sink, bool memory_map = true);
|
||||
|
||||
enum struct FsSync { Yes, No };
|
||||
|
||||
/**
|
||||
* Write a string to a file.
|
||||
*/
|
||||
void writeFile(const Path & path, std::string_view s, mode_t mode = 0666, bool sync = false);
|
||||
static inline void writeFile(const std::filesystem::path & path, std::string_view s, mode_t mode = 0666, bool sync = false)
|
||||
void writeFile(const Path & path, std::string_view s, mode_t mode = 0666, FsSync sync = FsSync::No);
|
||||
|
||||
static inline void writeFile(const std::filesystem::path & path, std::string_view s, mode_t mode = 0666, FsSync sync = FsSync::No)
|
||||
{
|
||||
return writeFile(path.string(), s, mode, sync);
|
||||
}
|
||||
|
||||
void writeFile(const Path & path, Source & source, mode_t mode = 0666, bool sync = false);
|
||||
static inline void writeFile(const std::filesystem::path & path, Source & source, mode_t mode = 0666, bool sync = false)
|
||||
void writeFile(const Path & path, Source & source, mode_t mode = 0666, FsSync sync = FsSync::No);
|
||||
|
||||
static inline void writeFile(const std::filesystem::path & path, Source & source, mode_t mode = 0666, FsSync sync = FsSync::No)
|
||||
{
|
||||
return writeFile(path.string(), source, mode, sync);
|
||||
}
|
||||
|
||||
void writeFile(AutoCloseFD & fd, const Path & origPath, std::string_view s, mode_t mode = 0666, FsSync sync = FsSync::No);
|
||||
|
||||
/**
|
||||
* Flush a path's parent directory to disk.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -52,16 +52,6 @@ struct ForwardingSourceAccessor : SourceAccessor
|
|||
{
|
||||
return next->getPhysicalPath(path);
|
||||
}
|
||||
|
||||
std::optional<std::string> getFingerprint(const CanonPath & path) override
|
||||
{
|
||||
return next->getFingerprint(path);
|
||||
}
|
||||
|
||||
void setFingerprint(std::string fingerprint) override
|
||||
{
|
||||
next->setFingerprint(std::move(fingerprint));
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -177,28 +177,32 @@ struct SourceAccessor : std::enable_shared_from_this<SourceAccessor>
|
|||
SymlinkResolution mode = SymlinkResolution::Full);
|
||||
|
||||
/**
|
||||
* Return a string that uniquely represents the contents of this
|
||||
* accessor. This is used for caching lookups (see
|
||||
* `fetchToStore()`).
|
||||
*
|
||||
* Fingerprints are generally for the entire accessor, but this
|
||||
* method takes a `path` argument to support accessors like
|
||||
* `MountedSourceAccessor` that combine multiple underlying
|
||||
* accessors. A fingerprint should only be returned if it uniquely
|
||||
* represents everything under `path`.
|
||||
* A string that uniquely represents the contents of this
|
||||
* accessor. This is used for caching lookups (see `fetchToStore()`).
|
||||
*/
|
||||
virtual std::optional<std::string> getFingerprint(const CanonPath & path)
|
||||
{
|
||||
return _fingerprint;
|
||||
}
|
||||
std::optional<std::string> fingerprint;
|
||||
|
||||
virtual void setFingerprint(std::string fingerprint)
|
||||
/**
|
||||
* Return the fingerprint for `path`. This is usually the
|
||||
* fingerprint of the current accessor, but for composite
|
||||
* accessors (like `MountedSourceAccessor`), we want to return the
|
||||
* fingerprint of the "inner" accessor if the current one lacks a
|
||||
* fingerprint.
|
||||
*
|
||||
* So this method is intended to return the most-outer accessor
|
||||
* that has a fingerprint for `path`. It also returns the path that `path`
|
||||
* corresponds to in that accessor.
|
||||
*
|
||||
* For example: in a `MountedSourceAccessor` that has
|
||||
* `/nix/store/foo` mounted,
|
||||
* `getFingerprint("/nix/store/foo/bar")` will return the path
|
||||
* `/bar` and the fingerprint of the `/nix/store/foo` accessor.
|
||||
*/
|
||||
virtual std::pair<CanonPath, std::optional<std::string>> getFingerprint(const CanonPath & path)
|
||||
{
|
||||
_fingerprint = std::move(fingerprint);
|
||||
return {path, fingerprint};
|
||||
}
|
||||
|
||||
std::optional<std::string> _fingerprint;
|
||||
|
||||
/**
|
||||
* Return the maximum last-modified time of the files in this
|
||||
* tree, if available.
|
||||
|
|
|
|||
|
|
@ -187,6 +187,10 @@ void MemorySink::createSymlink(const CanonPath & path, const std::string & targe
|
|||
ref<SourceAccessor> makeEmptySourceAccessor()
|
||||
{
|
||||
static auto empty = make_ref<MemorySourceAccessor>().cast<SourceAccessor>();
|
||||
/* Don't forget to clear the display prefix, as the default constructed
|
||||
SourceAccessor has the «unknown» prefix. Since this accessor is supposed
|
||||
to mimic an empty root directory the prefix needs to be empty. */
|
||||
empty->setPathDisplay("");
|
||||
return empty;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -91,12 +91,11 @@ struct MountedSourceAccessorImpl : MountedSourceAccessor
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
std::optional<std::string> getFingerprint(const CanonPath & path) override
|
||||
std::pair<CanonPath, std::optional<std::string>> getFingerprint(const CanonPath & path) override
|
||||
{
|
||||
if (fingerprint)
|
||||
return {path, fingerprint};
|
||||
auto [accessor, subpath] = resolve(path);
|
||||
// FIXME: check that there are no mounts underneath the mount
|
||||
// point of `accessor`, since that would invalidate the
|
||||
// fingerprint. (However we don't have such at the moment.)
|
||||
return accessor->getFingerprint(subpath);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -72,6 +72,18 @@ struct UnionSourceAccessor : SourceAccessor
|
|||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::pair<CanonPath, std::optional<std::string>> getFingerprint(const CanonPath & path) override
|
||||
{
|
||||
if (fingerprint)
|
||||
return {path, fingerprint};
|
||||
for (auto & accessor : accessors) {
|
||||
auto [subpath, fingerprint] = accessor->getFingerprint(path);
|
||||
if (fingerprint)
|
||||
return {subpath, fingerprint};
|
||||
}
|
||||
return {path, std::nullopt};
|
||||
}
|
||||
};
|
||||
|
||||
ref<SourceAccessor> makeUnionSourceAccessor(std::vector<ref<SourceAccessor>> && accessors)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue