1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-11-18 08:19:35 +01:00

Merge remote-tracking branch 'upstream/master' into overlayfs-store

This commit is contained in:
John Ericson 2023-12-11 13:12:09 -05:00
commit 245af3ea02
538 changed files with 14282 additions and 7312 deletions

View file

@ -2,7 +2,7 @@
#include "binary-cache-store.hh"
#include "compression.hh"
#include "derivations.hh"
#include "fs-accessor.hh"
#include "source-accessor.hh"
#include "globals.hh"
#include "nar-info.hh"
#include "sync.hh"
@ -11,6 +11,7 @@
#include "nar-accessor.hh"
#include "thread-pool.hh"
#include "callback.hh"
#include "signals.hh"
#include <chrono>
#include <future>
@ -142,9 +143,9 @@ ref<const ValidPathInfo> BinaryCacheStore::addToStoreCommon(
/* Read the NAR simultaneously into a CompressionSink+FileSink (to
write the compressed NAR to disk), into a HashSink (to get the
NAR hash), and into a NarAccessor (to get the NAR listing). */
HashSink fileHashSink { htSHA256 };
std::shared_ptr<FSAccessor> narAccessor;
HashSink narHashSink { htSHA256 };
HashSink fileHashSink { HashAlgorithm::SHA256 };
std::shared_ptr<SourceAccessor> narAccessor;
HashSink narHashSink { HashAlgorithm::SHA256 };
{
FdSink fileSink(fdTemp.get());
TeeSink teeSinkCompressed { fileSink, fileHashSink };
@ -164,8 +165,8 @@ ref<const ValidPathInfo> BinaryCacheStore::addToStoreCommon(
auto [fileHash, fileSize] = fileHashSink.finish();
narInfo->fileHash = fileHash;
narInfo->fileSize = fileSize;
narInfo->url = "nar/" + narInfo->fileHash->to_string(HashFormat::Base32, false) + ".nar"
+ (compression == "xz" ? ".xz" :
narInfo->url = "nar/" + narInfo->fileHash->to_string(HashFormat::Nix32, false) + ".nar"
+ (compression == "xz" ? ".xz" :
compression == "bzip2" ? ".bz2" :
compression == "zstd" ? ".zst" :
compression == "lzip" ? ".lzip" :
@ -195,7 +196,7 @@ ref<const ValidPathInfo> BinaryCacheStore::addToStoreCommon(
if (writeNARListing) {
nlohmann::json j = {
{"version", 1},
{"root", listNar(ref<FSAccessor>(narAccessor), "", true)},
{"root", listNar(ref<SourceAccessor>(narAccessor), CanonPath::root, true)},
};
upsertFile(std::string(info.path.hashPart()) + ".ls", j.dump(), "application/json");
@ -206,9 +207,9 @@ ref<const ValidPathInfo> BinaryCacheStore::addToStoreCommon(
specify the NAR file and member containing the debug info. */
if (writeDebugInfo) {
std::string buildIdDir = "/lib/debug/.build-id";
CanonPath buildIdDir("lib/debug/.build-id");
if (narAccessor->stat(buildIdDir).type == FSAccessor::tDirectory) {
if (auto st = narAccessor->maybeLstat(buildIdDir); st && st->type == SourceAccessor::tDirectory) {
ThreadPool threadPool(25);
@ -231,17 +232,17 @@ ref<const ValidPathInfo> BinaryCacheStore::addToStoreCommon(
std::regex regex1("^[0-9a-f]{2}$");
std::regex regex2("^[0-9a-f]{38}\\.debug$");
for (auto & s1 : narAccessor->readDirectory(buildIdDir)) {
auto dir = buildIdDir + "/" + s1;
for (auto & [s1, _type] : narAccessor->readDirectory(buildIdDir)) {
auto dir = buildIdDir + s1;
if (narAccessor->stat(dir).type != FSAccessor::tDirectory
if (narAccessor->lstat(dir).type != SourceAccessor::tDirectory
|| !std::regex_match(s1, regex1))
continue;
for (auto & s2 : narAccessor->readDirectory(dir)) {
auto debugPath = dir + "/" + s2;
for (auto & [s2, _type] : narAccessor->readDirectory(dir)) {
auto debugPath = dir + s2;
if (narAccessor->stat(debugPath).type != FSAccessor::tRegular
if (narAccessor->lstat(debugPath).type != SourceAccessor::tRegular
|| !std::regex_match(s2, regex2))
continue;
@ -250,7 +251,7 @@ ref<const ValidPathInfo> BinaryCacheStore::addToStoreCommon(
std::string key = "debuginfo/" + buildId;
std::string target = "../" + narInfo->url;
threadPool.enqueue(std::bind(doFile, std::string(debugPath, 1), key, target));
threadPool.enqueue(std::bind(doFile, std::string(debugPath.rel()), key, target));
}
}
@ -300,9 +301,9 @@ void BinaryCacheStore::addToStore(const ValidPathInfo & info, Source & narSource
}
StorePath BinaryCacheStore::addToStoreFromDump(Source & dump, std::string_view name,
FileIngestionMethod method, HashType hashAlgo, RepairFlag repair, const StorePathSet & references)
FileIngestionMethod method, HashAlgorithm hashAlgo, RepairFlag repair, const StorePathSet & references)
{
if (method != FileIngestionMethod::Recursive || hashAlgo != htSHA256)
if (method != FileIngestionMethod::Recursive || hashAlgo != HashAlgorithm::SHA256)
unsupported("addToStoreFromDump");
return addToStoreCommon(dump, repair, CheckSigs, [&](HashResult nar) {
ValidPathInfo info {
@ -398,13 +399,13 @@ void BinaryCacheStore::queryPathInfoUncached(const StorePath & storePath,
}
StorePath BinaryCacheStore::addToStore(
std::string_view name,
const Path & srcPath,
FileIngestionMethod method,
HashType hashAlgo,
PathFilter & filter,
RepairFlag repair,
const StorePathSet & references)
std::string_view name,
const Path & srcPath,
FileIngestionMethod method,
HashAlgorithm hashAlgo,
PathFilter & filter,
RepairFlag repair,
const StorePathSet & references)
{
/* FIXME: Make BinaryCacheStore::addToStoreCommon support
non-recursive+sha256 so we can just use the default
@ -447,7 +448,7 @@ StorePath BinaryCacheStore::addTextToStore(
const StorePathSet & references,
RepairFlag repair)
{
auto textHash = hashString(htSHA256, s);
auto textHash = hashString(HashAlgorithm::SHA256, s);
auto path = makeTextPath(name, TextInfo { { textHash }, references });
if (!repair && isValidPath(path))
@ -503,9 +504,9 @@ void BinaryCacheStore::registerDrvOutput(const Realisation& info) {
upsertFile(filePath, info.toJSON().dump(), "application/json");
}
ref<FSAccessor> BinaryCacheStore::getFSAccessor()
ref<SourceAccessor> BinaryCacheStore::getFSAccessor(bool requireValidPath)
{
return make_ref<RemoteFSAccessor>(ref<Store>(shared_from_this()), localNarCache);
return make_ref<RemoteFSAccessor>(ref<Store>(shared_from_this()), requireValidPath, localNarCache);
}
void BinaryCacheStore::addSignatures(const StorePath & storePath, const StringSet & sigs)

View file

@ -17,28 +17,28 @@ struct BinaryCacheStoreConfig : virtual StoreConfig
{
using StoreConfig::StoreConfig;
const Setting<std::string> compression{(StoreConfig*) this, "xz", "compression",
const Setting<std::string> compression{this, "xz", "compression",
"NAR compression method (`xz`, `bzip2`, `gzip`, `zstd`, or `none`)."};
const Setting<bool> writeNARListing{(StoreConfig*) this, false, "write-nar-listing",
const Setting<bool> writeNARListing{this, false, "write-nar-listing",
"Whether to write a JSON file that lists the files in each NAR."};
const Setting<bool> writeDebugInfo{(StoreConfig*) this, false, "index-debug-info",
const Setting<bool> writeDebugInfo{this, false, "index-debug-info",
R"(
Whether to index DWARF debug info files by build ID. This allows [`dwarffs`](https://github.com/edolstra/dwarffs) to
fetch debug info on demand
)"};
const Setting<Path> secretKeyFile{(StoreConfig*) this, "", "secret-key",
const Setting<Path> secretKeyFile{this, "", "secret-key",
"Path to the secret key used to sign the binary cache."};
const Setting<Path> localNarCache{(StoreConfig*) this, "", "local-nar-cache",
const Setting<Path> localNarCache{this, "", "local-nar-cache",
"Path to a local cache of NARs fetched from this binary cache, used by commands such as `nix store cat`."};
const Setting<bool> parallelCompression{(StoreConfig*) this, false, "parallel-compression",
const Setting<bool> parallelCompression{this, false, "parallel-compression",
"Enable multi-threaded compression of NARs. This is currently only available for `xz` and `zstd`."};
const Setting<int> compressionLevel{(StoreConfig*) this, -1, "compression-level",
const Setting<int> compressionLevel{this, -1, "compression-level",
R"(
The *preset level* to be used when compressing NARs.
The meaning and accepted values depend on the compression method selected.
@ -124,16 +124,16 @@ public:
RepairFlag repair, CheckSigsFlag checkSigs) override;
StorePath addToStoreFromDump(Source & dump, std::string_view name,
FileIngestionMethod method, HashType hashAlgo, RepairFlag repair, const StorePathSet & references) override;
FileIngestionMethod method, HashAlgorithm hashAlgo, RepairFlag repair, const StorePathSet & references) override;
StorePath addToStore(
std::string_view name,
const Path & srcPath,
FileIngestionMethod method,
HashType hashAlgo,
PathFilter & filter,
RepairFlag repair,
const StorePathSet & references) override;
std::string_view name,
const Path & srcPath,
FileIngestionMethod method,
HashAlgorithm hashAlgo,
PathFilter & filter,
RepairFlag repair,
const StorePathSet & references) override;
StorePath addTextToStore(
std::string_view name,
@ -148,7 +148,7 @@ public:
void narFromPath(const StorePath & path, Sink & sink) override;
ref<FSAccessor> getFSAccessor() override;
ref<SourceAccessor> getFSAccessor(bool requireValidPath) override;
void addSignatures(const StorePath & storePath, const StringSet & sigs) override;

View file

@ -0,0 +1,37 @@
#include "child.hh"
#include "current-process.hh"
#include "logging.hh"
#include <fcntl.h>
#include <unistd.h>
namespace nix {
void commonChildInit()
{
logger = makeSimpleLogger();
const static std::string pathNullDevice = "/dev/null";
restoreProcessContext(false);
/* Put the child in a separate session (and thus a separate
process group) so that it has no controlling terminal (meaning
that e.g. ssh cannot open /dev/tty) and it doesn't receive
terminal signals. */
if (setsid() == -1)
throw SysError("creating a new session");
/* Dup stderr to stdout. */
if (dup2(STDERR_FILENO, STDOUT_FILENO) == -1)
throw SysError("cannot dup stderr into stdout");
/* Reroute stdin to /dev/null. */
int fdDevNull = open(pathNullDevice.c_str(), O_RDWR);
if (fdDevNull == -1)
throw SysError("cannot open '%1%'", pathNullDevice);
if (dup2(fdDevNull, STDIN_FILENO) == -1)
throw SysError("cannot dup null device into stdin");
close(fdDevNull);
}
}

View file

@ -0,0 +1,11 @@
#pragma once
///@file
namespace nix {
/**
* Common initialisation performed in child processes.
*/
void commonChildInit();
}

View file

@ -558,7 +558,7 @@ void DerivationGoal::inputsRealised()
inputDrvOutputs statefully, sometimes it gets out of sync with
the real source of truth (store). So we query the store
directly if there's a problem. */
attempt = fullDrv.tryResolve(worker.store);
attempt = fullDrv.tryResolve(worker.store, &worker.evalStore);
}
assert(attempt);
Derivation drvResolved { std::move(*attempt) };
@ -1317,9 +1317,26 @@ void DerivationGoal::handleChildOutput(int fd, std::string_view data)
auto s = handleJSONLogMessage(*json, worker.act, hook->activities, true);
// ensure that logs from a builder using `ssh-ng://` as protocol
// are also available to `nix log`.
if (s && !isWrittenToLog && logSink && (*json)["type"] == resBuildLogLine) {
auto f = (*json)["fields"];
(*logSink)((f.size() > 0 ? f.at(0).get<std::string>() : "") + "\n");
if (s && !isWrittenToLog && logSink) {
const auto type = (*json)["type"];
const auto fields = (*json)["fields"];
if (type == resBuildLogLine) {
(*logSink)((fields.size() > 0 ? fields[0].get<std::string>() : "") + "\n");
} else if (type == resSetPhase && ! fields.is_null()) {
const auto phase = fields[0];
if (! phase.is_null()) {
// nixpkgs' stdenv produces lines in the log to signal
// phase changes.
// We want to get the same lines in case of remote builds.
// The format is:
// @nix { "action": "setPhase", "phase": "$curPhase" }
const auto logLine = nlohmann::json::object({
{"action", "setPhase"},
{"phase", phase}
});
(*logSink)("@nix " + logLine.dump(-1, ' ', false, nlohmann::json::error_handler_t::replace) + "\n");
}
}
}
}
currentHookLine.clear();
@ -1474,6 +1491,7 @@ void DerivationGoal::done(
SingleDrvOutputs builtOutputs,
std::optional<Error> ex)
{
outputLocks.unlock();
buildResult.status = status;
if (ex)
buildResult.errorMsg = fmt("%s", normaltxt(ex->info().msg));

View file

@ -15,7 +15,7 @@ void Store::buildPaths(const std::vector<DerivedPath> & reqs, BuildMode buildMod
worker.run(goals);
StorePathSet failed;
StringSet failed;
std::optional<Error> ex;
for (auto & i : goals) {
if (i->ex) {
@ -26,9 +26,9 @@ void Store::buildPaths(const std::vector<DerivedPath> & reqs, BuildMode buildMod
}
if (i->exitCode != Goal::ecSuccess) {
if (auto i2 = dynamic_cast<DerivationGoal *>(i.get()))
failed.insert(i2->drvPath);
failed.insert(std::string { i2->drvPath.to_string() });
else if (auto i2 = dynamic_cast<PathSubstitutionGoal *>(i.get()))
failed.insert(i2->storePath);
failed.insert(std::string { i2->storePath.to_string()});
}
}
@ -37,7 +37,7 @@ void Store::buildPaths(const std::vector<DerivedPath> & reqs, BuildMode buildMod
throw std::move(*ex);
} else if (!failed.empty()) {
if (ex) logError(ex->info());
throw Error(worker.failingExitStatus(), "build of %s failed", showPaths(failed));
throw Error(worker.failingExitStatus(), "build of %s failed", concatStringsSep(", ", quoteStrings(failed)));
}
}

View file

@ -1,5 +1,7 @@
#include "globals.hh"
#include "hook-instance.hh"
#include "file-system.hh"
#include "child.hh"
namespace nix {

View file

@ -3,6 +3,7 @@
#include "logging.hh"
#include "serialise.hh"
#include "processes.hh"
namespace nix {

View file

@ -15,7 +15,11 @@
#include "json-utils.hh"
#include "cgroup.hh"
#include "personality.hh"
#include "current-process.hh"
#include "namespaces.hh"
#include "child.hh"
#include "unix-domain-socket.hh"
#include "posix-fs-canonicalise.hh"
#include <regex>
#include <queue>
@ -227,7 +231,7 @@ void LocalDerivationGoal::tryLocalBuild()
if (!buildUser) {
if (!actLock)
actLock = std::make_unique<Activity>(*logger, lvlWarn, actBuildWaiting,
fmt("waiting for UID to build '%s'", yellowtxt(worker.store.printStorePath(drvPath))));
fmt("waiting for a free build user ID for '%s'", yellowtxt(worker.store.printStorePath(drvPath))));
worker.waitForAWhile(shared_from_this());
return;
}
@ -649,8 +653,8 @@ void LocalDerivationGoal::startBuilder()
#if __linux__
/* Create a temporary directory in which we set up the chroot
environment using bind-mounts. We put it in the Nix store
to ensure that we can create hard-links to non-directory
inputs in the fake Nix store in the chroot (see below). */
so that the build outputs can be moved efficiently from the
chroot to their final location. */
chrootRootDir = worker.store.Store::toRealPath(drvPath) + ".chroot";
deletePath(chrootRootDir);
@ -1062,8 +1066,8 @@ void LocalDerivationGoal::initTmpDir() {
if (passAsFile.find(i.first) == passAsFile.end()) {
env[i.first] = i.second;
} else {
auto hash = hashString(htSHA256, i.first);
std::string fn = ".attr-" + hash.to_string(HashFormat::Base32, false);
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);
@ -1286,13 +1290,13 @@ struct RestrictedStore : public virtual RestrictedStoreConfig, public virtual In
{ throw Error("queryPathFromHashPart"); }
StorePath addToStore(
std::string_view name,
const Path & srcPath,
FileIngestionMethod method,
HashType hashAlgo,
PathFilter & filter,
RepairFlag repair,
const StorePathSet & references) override
std::string_view name,
const Path & srcPath,
FileIngestionMethod method,
HashAlgorithm hashAlgo,
PathFilter & filter,
RepairFlag repair,
const StorePathSet & references) override
{ throw Error("addToStore"); }
void addToStore(const ValidPathInfo & info, Source & narSource,
@ -1314,12 +1318,12 @@ struct RestrictedStore : public virtual RestrictedStoreConfig, public virtual In
}
StorePath addToStoreFromDump(
Source & dump,
std::string_view name,
FileIngestionMethod method,
HashType hashAlgo,
RepairFlag repair,
const StorePathSet & references) override
Source & dump,
std::string_view name,
FileIngestionMethod method,
HashAlgorithm hashAlgo,
RepairFlag repair,
const StorePathSet & references) override
{
auto path = next->addToStoreFromDump(dump, name, method, hashAlgo, repair, references);
goal.addDependency(path);
@ -1563,10 +1567,11 @@ void LocalDerivationGoal::addDependency(const StorePath & path)
Path source = worker.store.Store::toRealPath(path);
Path target = chrootRootDir + worker.store.printStorePath(path);
if (pathExists(target))
if (pathExists(target)) {
// There is a similar debug message in doBind, so only run it in this block to not have double messages.
debug("bind-mounting %s -> %s", target, source);
throw Error("store path '%s' already exists in the sandbox", worker.store.printStorePath(path));
}
/* Bind-mount the path into the sandbox. This requires
entering its mount namespace, which is not possible
@ -1619,6 +1624,8 @@ void setupSeccomp()
seccomp_release(ctx);
});
constexpr std::string_view nativeSystem = SYSTEM;
if (nativeSystem == "x86_64-linux" &&
seccomp_arch_add(ctx, SCMP_ARCH_X86) != 0)
throw SysError("unable to add 32-bit seccomp architecture");
@ -2459,7 +2466,7 @@ SingleDrvOutputs LocalDerivationGoal::registerOutputs()
rewriteOutput(outputRewrites);
/* FIXME optimize and deduplicate with addToStore */
std::string oldHashPart { scratchPath->hashPart() };
HashModuloSink caSink { outputHash.hashType, oldHashPart };
HashModuloSink caSink {outputHash.hashAlgo, oldHashPart };
std::visit(overloaded {
[&](const TextIngestionMethod &) {
readFile(actualPath, caSink);
@ -2504,7 +2511,7 @@ SingleDrvOutputs LocalDerivationGoal::registerOutputs()
std::string(newInfo0.path.hashPart())}});
}
HashResult narHashAndSize = hashPath(htSHA256, actualPath);
HashResult narHashAndSize = hashPath(HashAlgorithm::SHA256, actualPath);
newInfo0.narHash = narHashAndSize.first;
newInfo0.narSize = narHashAndSize.second;
@ -2524,7 +2531,7 @@ SingleDrvOutputs LocalDerivationGoal::registerOutputs()
std::string { scratchPath->hashPart() },
std::string { requiredFinalPath.hashPart() });
rewriteOutput(outputRewrites);
auto narHashAndSize = hashPath(htSHA256, actualPath);
auto narHashAndSize = hashPath(HashAlgorithm::SHA256, actualPath);
ValidPathInfo newInfo0 { requiredFinalPath, narHashAndSize.first };
newInfo0.narSize = narHashAndSize.second;
auto refs = rewriteRefs();
@ -2539,7 +2546,7 @@ SingleDrvOutputs LocalDerivationGoal::registerOutputs()
auto newInfo0 = newInfoFromCA(DerivationOutput::CAFloating {
.method = dof.ca.method,
.hashType = wanted.type,
.hashAlgo = wanted.algo,
});
/* Check wanted hash */
@ -2576,7 +2583,7 @@ SingleDrvOutputs LocalDerivationGoal::registerOutputs()
[&](const DerivationOutput::Impure & doi) {
return newInfoFromCA(DerivationOutput::CAFloating {
.method = doi.method,
.hashType = doi.hashType,
.hashAlgo = doi.hashAlgo,
});
},
@ -2938,7 +2945,7 @@ StorePath LocalDerivationGoal::makeFallbackPath(OutputNameView outputName)
{
return worker.store.makeStorePath(
"rewrite:" + std::string(drvPath.to_string()) + ":name:" + std::string(outputName),
Hash(htSHA256), outputPathName(drv->name, outputName));
Hash(HashAlgorithm::SHA256), outputPathName(drv->name, outputName));
}
@ -2946,7 +2953,7 @@ StorePath LocalDerivationGoal::makeFallbackPath(const StorePath & path)
{
return worker.store.makeStorePath(
"rewrite:" + std::string(drvPath.to_string()) + ":" + std::string(path.to_string()),
Hash(htSHA256), path.name());
Hash(HashAlgorithm::SHA256), path.name());
}

View file

@ -3,6 +3,7 @@
#include "derivation-goal.hh"
#include "local-store.hh"
#include "processes.hh"
namespace nix {

View file

@ -4,6 +4,7 @@
#include "drv-output-substitution-goal.hh"
#include "local-derivation-goal.hh"
#include "hook-instance.hh"
#include "signals.hh"
#include <poll.h>
@ -198,8 +199,16 @@ void Worker::childStarted(GoalPtr goal, const std::set<int> & fds,
child.respectTimeouts = respectTimeouts;
children.emplace_back(child);
if (inBuildSlot) {
if (goal->jobCategory() == JobCategory::Substitution) nrSubstitutions++;
else nrLocalBuilds++;
switch (goal->jobCategory()) {
case JobCategory::Substitution:
nrSubstitutions++;
break;
case JobCategory::Build:
nrLocalBuilds++;
break;
default:
abort();
}
}
}
@ -211,12 +220,17 @@ void Worker::childTerminated(Goal * goal, bool wakeSleepers)
if (i == children.end()) return;
if (i->inBuildSlot) {
if (goal->jobCategory() == JobCategory::Substitution) {
switch (goal->jobCategory()) {
case JobCategory::Substitution:
assert(nrSubstitutions > 0);
nrSubstitutions--;
} else {
break;
case JobCategory::Build:
assert(nrLocalBuilds > 0);
nrLocalBuilds--;
break;
default:
abort();
}
}
@ -505,8 +519,8 @@ bool Worker::pathContentsGood(const StorePath & path)
if (!pathExists(store.printStorePath(path)))
res = false;
else {
HashResult current = hashPath(info->narHash.type, store.printStorePath(path));
Hash nullHash(htSHA256);
HashResult current = hashPath(info->narHash.algo, store.printStorePath(path));
Hash nullHash(HashAlgorithm::SHA256);
res = info->narHash == nullHash || info->narHash == current.first;
}
pathContentsGoodCache.insert_or_assign(path, res);

View file

@ -1,4 +1,5 @@
#include "buildenv.hh"
#include "derivations.hh"
#include <sys/stat.h>
#include <sys/types.h>

View file

@ -1,7 +1,6 @@
#pragma once
///@file
#include "derivations.hh"
#include "store-api.hh"
namespace nix {

View file

@ -63,9 +63,9 @@ void builtinFetchurl(const BasicDerivation & drv, const std::string & netrcData)
for (auto hashedMirror : settings.hashedMirrors.get())
try {
if (!hasSuffix(hashedMirror, "/")) hashedMirror += '/';
std::optional<HashType> ht = parseHashTypeOpt(getAttr("outputHashAlgo"));
std::optional<HashAlgorithm> ht = parseHashAlgoOpt(getAttr("outputHashAlgo"));
Hash h = newHashAllowEmpty(getAttr("outputHash"), ht);
fetch(hashedMirror + printHashType(h.type) + "/" + h.to_string(HashFormat::Base16, false));
fetch(hashedMirror + printHashAlgo(h.algo) + "/" + h.to_string(HashFormat::Base16, false));
return;
} catch (Error & e) {
debug(e.what());

View file

@ -16,11 +16,11 @@ namespace nix {
/* protocol-agnostic templates */
#define COMMON_USE_LENGTH_PREFIX_SERIALISER(TEMPLATE, T) \
TEMPLATE T CommonProto::Serialise< T >::read(const Store & store, CommonProto::ReadConn conn) \
TEMPLATE T CommonProto::Serialise< T >::read(const StoreDirConfig & store, CommonProto::ReadConn conn) \
{ \
return LengthPrefixedProtoHelper<CommonProto, T >::read(store, conn); \
} \
TEMPLATE void CommonProto::Serialise< T >::write(const Store & store, CommonProto::WriteConn conn, const T & t) \
TEMPLATE void CommonProto::Serialise< T >::write(const StoreDirConfig & store, CommonProto::WriteConn conn, const T & t) \
{ \
LengthPrefixedProtoHelper<CommonProto, T >::write(store, conn, t); \
}

View file

@ -1,5 +1,4 @@
#include "serialise.hh"
#include "util.hh"
#include "path-with-outputs.hh"
#include "store-api.hh"
#include "build-result.hh"
@ -14,40 +13,40 @@ namespace nix {
/* protocol-agnostic definitions */
std::string CommonProto::Serialise<std::string>::read(const Store & store, CommonProto::ReadConn conn)
std::string CommonProto::Serialise<std::string>::read(const StoreDirConfig & store, CommonProto::ReadConn conn)
{
return readString(conn.from);
}
void CommonProto::Serialise<std::string>::write(const Store & store, CommonProto::WriteConn conn, const std::string & str)
void CommonProto::Serialise<std::string>::write(const StoreDirConfig & store, CommonProto::WriteConn conn, const std::string & str)
{
conn.to << str;
}
StorePath CommonProto::Serialise<StorePath>::read(const Store & store, CommonProto::ReadConn conn)
StorePath CommonProto::Serialise<StorePath>::read(const StoreDirConfig & store, CommonProto::ReadConn conn)
{
return store.parseStorePath(readString(conn.from));
}
void CommonProto::Serialise<StorePath>::write(const Store & store, CommonProto::WriteConn conn, const StorePath & storePath)
void CommonProto::Serialise<StorePath>::write(const StoreDirConfig & store, CommonProto::WriteConn conn, const StorePath & storePath)
{
conn.to << store.printStorePath(storePath);
}
ContentAddress CommonProto::Serialise<ContentAddress>::read(const Store & store, CommonProto::ReadConn conn)
ContentAddress CommonProto::Serialise<ContentAddress>::read(const StoreDirConfig & store, CommonProto::ReadConn conn)
{
return ContentAddress::parse(readString(conn.from));
}
void CommonProto::Serialise<ContentAddress>::write(const Store & store, CommonProto::WriteConn conn, const ContentAddress & ca)
void CommonProto::Serialise<ContentAddress>::write(const StoreDirConfig & store, CommonProto::WriteConn conn, const ContentAddress & ca)
{
conn.to << renderContentAddress(ca);
}
Realisation CommonProto::Serialise<Realisation>::read(const Store & store, CommonProto::ReadConn conn)
Realisation CommonProto::Serialise<Realisation>::read(const StoreDirConfig & store, CommonProto::ReadConn conn)
{
std::string rawInput = readString(conn.from);
return Realisation::fromJSON(
@ -56,41 +55,41 @@ Realisation CommonProto::Serialise<Realisation>::read(const Store & store, Commo
);
}
void CommonProto::Serialise<Realisation>::write(const Store & store, CommonProto::WriteConn conn, const Realisation & realisation)
void CommonProto::Serialise<Realisation>::write(const StoreDirConfig & store, CommonProto::WriteConn conn, const Realisation & realisation)
{
conn.to << realisation.toJSON().dump();
}
DrvOutput CommonProto::Serialise<DrvOutput>::read(const Store & store, CommonProto::ReadConn conn)
DrvOutput CommonProto::Serialise<DrvOutput>::read(const StoreDirConfig & store, CommonProto::ReadConn conn)
{
return DrvOutput::parse(readString(conn.from));
}
void CommonProto::Serialise<DrvOutput>::write(const Store & store, CommonProto::WriteConn conn, const DrvOutput & drvOutput)
void CommonProto::Serialise<DrvOutput>::write(const StoreDirConfig & store, CommonProto::WriteConn conn, const DrvOutput & drvOutput)
{
conn.to << drvOutput.to_string();
}
std::optional<StorePath> CommonProto::Serialise<std::optional<StorePath>>::read(const Store & store, CommonProto::ReadConn conn)
std::optional<StorePath> CommonProto::Serialise<std::optional<StorePath>>::read(const StoreDirConfig & store, CommonProto::ReadConn conn)
{
auto s = readString(conn.from);
return s == "" ? std::optional<StorePath> {} : store.parseStorePath(s);
}
void CommonProto::Serialise<std::optional<StorePath>>::write(const Store & store, CommonProto::WriteConn conn, const std::optional<StorePath> & storePathOpt)
void CommonProto::Serialise<std::optional<StorePath>>::write(const StoreDirConfig & store, CommonProto::WriteConn conn, const std::optional<StorePath> & storePathOpt)
{
conn.to << (storePathOpt ? store.printStorePath(*storePathOpt) : "");
}
std::optional<ContentAddress> CommonProto::Serialise<std::optional<ContentAddress>>::read(const Store & store, CommonProto::ReadConn conn)
std::optional<ContentAddress> CommonProto::Serialise<std::optional<ContentAddress>>::read(const StoreDirConfig & store, CommonProto::ReadConn conn)
{
return ContentAddress::parseOpt(readString(conn.from));
}
void CommonProto::Serialise<std::optional<ContentAddress>>::write(const Store & store, CommonProto::WriteConn conn, const std::optional<ContentAddress> & caOpt)
void CommonProto::Serialise<std::optional<ContentAddress>>::write(const StoreDirConfig & store, CommonProto::WriteConn conn, const std::optional<ContentAddress> & caOpt)
{
conn.to << (caOpt ? renderContentAddress(*caOpt) : "");
}

View file

@ -5,7 +5,7 @@
namespace nix {
class Store;
struct StoreDirConfig;
struct Source;
// items being serialized
@ -48,7 +48,7 @@ struct CommonProto
* infer the type instead of having to write it down explicitly.
*/
template<typename T>
static void write(const Store & store, WriteConn conn, const T & t)
static void write(const StoreDirConfig & store, WriteConn conn, const T & t)
{
CommonProto::Serialise<T>::write(store, conn, t);
}
@ -57,8 +57,8 @@ struct CommonProto
#define DECLARE_COMMON_SERIALISER(T) \
struct CommonProto::Serialise< T > \
{ \
static T read(const Store & store, CommonProto::ReadConn conn); \
static void write(const Store & store, CommonProto::WriteConn conn, const T & str); \
static T read(const StoreDirConfig & store, CommonProto::ReadConn conn); \
static void write(const StoreDirConfig & store, CommonProto::WriteConn conn, const T & str); \
}
template<>

View file

@ -38,14 +38,14 @@ ContentAddressMethod ContentAddressMethod::parsePrefix(std::string_view & m)
return FileIngestionMethod::Flat;
}
std::string ContentAddressMethod::render(HashType ht) const
std::string ContentAddressMethod::render(HashAlgorithm ha) const
{
return std::visit(overloaded {
[&](const TextIngestionMethod & th) {
return std::string{"text:"} + printHashType(ht);
return std::string{"text:"} + printHashAlgo(ha);
},
[&](const FileIngestionMethod & fim) {
return "fixed:" + makeFileIngestionPrefix(fim) + printHashType(ht);
return "fixed:" + makeFileIngestionPrefix(fim) + printHashAlgo(ha);
}
}, raw);
}
@ -61,13 +61,13 @@ std::string ContentAddress::render() const
+ makeFileIngestionPrefix(method);
},
}, method.raw)
+ this->hash.to_string(HashFormat::Base32, true);
+ this->hash.to_string(HashFormat::Nix32, true);
}
/**
* Parses content address strings up to the hash.
*/
static std::pair<ContentAddressMethod, HashType> parseContentAddressMethodPrefix(std::string_view & rest)
static std::pair<ContentAddressMethod, HashAlgorithm> parseContentAddressMethodPrefix(std::string_view & rest)
{
std::string_view wholeInput { rest };
@ -83,27 +83,27 @@ static std::pair<ContentAddressMethod, HashType> parseContentAddressMethodPrefix
auto hashTypeRaw = splitPrefixTo(rest, ':');
if (!hashTypeRaw)
throw UsageError("content address hash must be in form '<algo>:<hash>', but found: %s", wholeInput);
HashType hashType = parseHashType(*hashTypeRaw);
return hashType;
HashAlgorithm hashAlgo = parseHashAlgo(*hashTypeRaw);
return hashAlgo;
};
// Switch on prefix
if (prefix == "text") {
// No parsing of the ingestion method, "text" only support flat.
HashType hashType = parseHashType_();
HashAlgorithm hashAlgo = parseHashType_();
return {
TextIngestionMethod {},
std::move(hashType),
std::move(hashAlgo),
};
} else if (prefix == "fixed") {
// Parse method
auto method = FileIngestionMethod::Flat;
if (splitPrefix(rest, "r:"))
method = FileIngestionMethod::Recursive;
HashType hashType = parseHashType_();
HashAlgorithm hashAlgo = parseHashType_();
return {
std::move(method),
std::move(hashType),
std::move(hashAlgo),
};
} else
throw UsageError("content address prefix '%s' is unrecognized. Recogonized prefixes are 'text' or 'fixed'", prefix);
@ -113,15 +113,15 @@ ContentAddress ContentAddress::parse(std::string_view rawCa)
{
auto rest = rawCa;
auto [caMethod, hashType] = parseContentAddressMethodPrefix(rest);
auto [caMethod, hashAlgo] = parseContentAddressMethodPrefix(rest);
return ContentAddress {
.method = std::move(caMethod),
.hash = Hash::parseNonSRIUnprefixed(rest, hashType),
.hash = Hash::parseNonSRIUnprefixed(rest, hashAlgo),
};
}
std::pair<ContentAddressMethod, HashType> ContentAddressMethod::parse(std::string_view caMethod)
std::pair<ContentAddressMethod, HashAlgorithm> ContentAddressMethod::parse(std::string_view caMethod)
{
std::string asPrefix = std::string{caMethod} + ":";
// parseContentAddressMethodPrefix takes its argument by reference
@ -144,7 +144,7 @@ std::string renderContentAddress(std::optional<ContentAddress> ca)
std::string ContentAddress::printMethodAlgo() const
{
return method.renderPrefix()
+ printHashType(hash.type);
+ printHashAlgo(hash.algo);
}
bool StoreReferences::empty() const

View file

@ -39,12 +39,12 @@ enum struct FileIngestionMethod : uint8_t {
/**
* Flat-file hashing. Directly ingest the contents of a single file
*/
Flat = false,
Flat = 0,
/**
* Recursive (or NAR) hashing. Serializes the file-system object in Nix
* Archive format and ingest that
*/
Recursive = true
Recursive = 1
};
/**
@ -94,7 +94,7 @@ struct ContentAddressMethod
/**
* Parse a content addressing method and hash type.
*/
static std::pair<ContentAddressMethod, HashType> parse(std::string_view rawCaMethod);
static std::pair<ContentAddressMethod, HashAlgorithm> parse(std::string_view rawCaMethod);
/**
* Render a content addressing method and hash type in a
@ -102,7 +102,7 @@ struct ContentAddressMethod
*
* The rough inverse of `parse()`.
*/
std::string render(HashType ht) const;
std::string render(HashAlgorithm ha) const;
};

View file

@ -1,4 +1,5 @@
#include "crypto.hh"
#include "file-system.hh"
#include "util.hh"
#include "globals.hh"

View file

@ -400,22 +400,22 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
logger->startWork();
auto pathInfo = [&]() {
// NB: FramedSource must be out of scope before logger->stopWork();
auto [contentAddressMethod, hashType_] = ContentAddressMethod::parse(camStr);
auto hashType = hashType_; // work around clang bug
auto [contentAddressMethod, hashAlgo_] = ContentAddressMethod::parse(camStr);
auto hashAlgo = hashAlgo_; // work around clang bug
FramedSource source(from);
// TODO this is essentially RemoteStore::addCAToStore. Move it up to Store.
return std::visit(overloaded {
[&](const TextIngestionMethod &) {
if (hashType != htSHA256)
if (hashAlgo != HashAlgorithm::SHA256)
throw UnimplementedError("When adding text-hashed data called '%s', only SHA-256 is supported but '%s' was given",
name, printHashType(hashType));
name, printHashAlgo(hashAlgo));
// We could stream this by changing Store
std::string contents = source.drain();
auto path = store->addTextToStore(name, contents, refs, repair);
return store->queryPathInfo(path);
},
[&](const FileIngestionMethod & fim) {
auto path = store->addToStoreFromDump(source, name, fim, hashType, repair, refs);
auto path = store->addToStoreFromDump(source, name, fim, hashAlgo, repair, refs);
return store->queryPathInfo(path);
},
}, contentAddressMethod.raw);
@ -424,7 +424,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
WorkerProto::Serialise<ValidPathInfo>::write(*store, wconn, *pathInfo);
} else {
HashType hashAlgo;
HashAlgorithm hashAlgo;
std::string baseName;
FileIngestionMethod method;
{
@ -440,7 +440,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
hashAlgoRaw = "sha256";
method = FileIngestionMethod::Recursive;
}
hashAlgo = parseHashType(hashAlgoRaw);
hashAlgo = parseHashAlgo(hashAlgoRaw);
}
auto dumpSource = sinkToSource([&](Sink & saved) {
@ -454,13 +454,13 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
eagerly consume the entire stream it's given, past the
length of the Nar. */
TeeSource savedNARSource(from, saved);
ParseSink sink; /* null sink; just parse the NAR */
NullParseSink sink; /* just parse the NAR */
parseDump(sink, savedNARSource);
} else {
/* Incrementally parse the NAR file, stripping the
metadata, and streaming the sole file we expect into
`saved`. */
RetrieveRegularNARSink savedRegular { saved };
RegularFileSink savedRegular { saved };
parseDump(savedRegular, from);
if (!savedRegular.regular) throw Error("regular file expected");
}
@ -574,6 +574,15 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
case WorkerProto::Op::BuildDerivation: {
auto drvPath = store->parseStorePath(readString(from));
BasicDerivation drv;
/*
* Note: unlike wopEnsurePath, this operation reads a
* derivation-to-be-realized from the client with
* readDerivation(Source,Store) rather than reading it from
* the local store with Store::readDerivation(). Since the
* derivation-to-be-realized is not registered in the store
* it cannot be trusted that its outPath was calculated
* correctly.
*/
readDerivation(from, *store, drv, Derivation::nameFromPath(drvPath));
BuildMode buildMode = (BuildMode) readInt(from);
logger->startWork();
@ -657,6 +666,21 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
break;
}
case WorkerProto::Op::AddPermRoot: {
if (!trusted)
throw Error(
"you are not privileged to create perm roots\n\n"
"hint: you can just do this client-side without special privileges, and probably want to do that instead.");
auto storePath = WorkerProto::Serialise<StorePath>::read(*store, rconn);
Path gcRoot = absPath(readString(from));
logger->startWork();
auto & localFSStore = require<LocalFSStore>(*store);
localFSStore.addPermRoot(storePath, gcRoot);
logger->stopWork();
to << gcRoot;
break;
}
case WorkerProto::Op::AddIndirectRoot: {
Path path = absPath(readString(from));
@ -868,7 +892,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
bool repair, dontCheckSigs;
auto path = store->parseStorePath(readString(from));
auto deriver = readString(from);
auto narHash = Hash::parseAny(readString(from), htSHA256);
auto narHash = Hash::parseAny(readString(from), HashAlgorithm::SHA256);
ValidPathInfo info { path, narHash };
if (deriver != "")
info.deriver = store->parseStorePath(deriver);
@ -899,7 +923,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
source = std::make_unique<TunnelSource>(from, to);
else {
TeeSource tee { from, saved };
ParseSink ether;
NullParseSink ether;
parseDump(ether, tee);
source = std::make_unique<StringSource>(saved.s);
}

View file

@ -6,13 +6,12 @@
#include "split.hh"
#include "common-protocol.hh"
#include "common-protocol-impl.hh"
#include "fs-accessor.hh"
#include <boost/container/small_vector.hpp>
#include <nlohmann/json.hpp>
namespace nix {
std::optional<StorePath> DerivationOutput::path(const Store & store, std::string_view drvName, OutputNameView outputName) const
std::optional<StorePath> DerivationOutput::path(const StoreDirConfig & store, std::string_view drvName, OutputNameView outputName) const
{
return std::visit(overloaded {
[](const DerivationOutput::InputAddressed & doi) -> std::optional<StorePath> {
@ -36,7 +35,7 @@ std::optional<StorePath> DerivationOutput::path(const Store & store, std::string
}
StorePath DerivationOutput::CAFixed::path(const Store & store, std::string_view drvName, OutputNameView outputName) const
StorePath DerivationOutput::CAFixed::path(const StoreDirConfig & store, std::string_view drvName, OutputNameView outputName) const
{
return store.makeFixedOutputPathFromCA(
outputPathName(drvName, outputName),
@ -152,11 +151,10 @@ StorePath writeDerivation(Store & store,
/* Read string `s' from stream `str'. */
static void expect(std::istream & str, std::string_view s)
{
char s2[s.size()];
str.read(s2, s.size());
std::string_view s2View { s2, s.size() };
if (s2View != s)
throw FormatError("expected string '%s', got '%s'", s, s2View);
for (auto & c : s) {
if (str.get() != c)
throw FormatError("expected string '%1%'", s);
}
}
@ -216,26 +214,26 @@ static StringSet parseStrings(std::istream & str, bool arePaths)
static DerivationOutput parseDerivationOutput(
const Store & store,
std::string_view pathS, std::string_view hashAlgo, std::string_view hashS,
const StoreDirConfig & store,
std::string_view pathS, std::string_view hashAlgoStr, std::string_view hashS,
const ExperimentalFeatureSettings & xpSettings)
{
if (hashAlgo != "") {
ContentAddressMethod method = ContentAddressMethod::parsePrefix(hashAlgo);
if (hashAlgoStr != "") {
ContentAddressMethod method = ContentAddressMethod::parsePrefix(hashAlgoStr);
if (method == TextIngestionMethod {})
xpSettings.require(Xp::DynamicDerivations);
const auto hashType = parseHashType(hashAlgo);
const auto hashAlgo = parseHashAlgo(hashAlgoStr);
if (hashS == "impure") {
xpSettings.require(Xp::ImpureDerivations);
if (pathS != "")
throw FormatError("impure derivation output should not specify output path");
return DerivationOutput::Impure {
.method = std::move(method),
.hashType = std::move(hashType),
.hashAlgo = std::move(hashAlgo),
};
} else if (hashS != "") {
validatePath(pathS);
auto hash = Hash::parseNonSRIUnprefixed(hashS, hashType);
auto hash = Hash::parseNonSRIUnprefixed(hashS, hashAlgo);
return DerivationOutput::CAFixed {
.ca = ContentAddress {
.method = std::move(method),
@ -248,7 +246,7 @@ static DerivationOutput parseDerivationOutput(
throw FormatError("content-addressed derivation output should not specify output path");
return DerivationOutput::CAFloating {
.method = std::move(method),
.hashType = std::move(hashType),
.hashAlgo = std::move(hashAlgo),
};
}
} else {
@ -263,7 +261,7 @@ static DerivationOutput parseDerivationOutput(
}
static DerivationOutput parseDerivationOutput(
const Store & store, std::istringstream & str,
const StoreDirConfig & store, std::istringstream & str,
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings)
{
expect(str, ","); const auto pathS = parseString(str);
@ -292,7 +290,7 @@ enum struct DerivationATermVersion {
};
static DerivedPathMap<StringSet>::ChildNode parseDerivedPathMapNode(
const Store & store,
const StoreDirConfig & store,
std::istringstream & str,
DerivationATermVersion version)
{
@ -339,7 +337,7 @@ static DerivedPathMap<StringSet>::ChildNode parseDerivedPathMapNode(
Derivation parseDerivation(
const Store & store, std::string && s, std::string_view name,
const StoreDirConfig & store, std::string && s, std::string_view name,
const ExperimentalFeatureSettings & xpSettings)
{
Derivation drv;
@ -353,7 +351,7 @@ Derivation parseDerivation(
expect(str, "erive(");
version = DerivationATermVersion::Traditional;
break;
case 'r':
case 'r': {
expect(str, "rvWithVersion(");
auto versionS = parseString(str);
if (versionS == "xp-dyn-drv") {
@ -366,6 +364,9 @@ Derivation parseDerivation(
expect(str, ",");
break;
}
default:
throw Error("derivation does not start with 'Derive' or 'DrvWithVersion'");
}
/* Parse the list of outputs. */
expect(str, "[");
@ -469,7 +470,7 @@ static void printUnquotedStrings(std::string & res, ForwardIterator i, ForwardIt
}
static void unparseDerivedPathMapNode(const Store & store, std::string & s, const DerivedPathMap<StringSet>::ChildNode & node)
static void unparseDerivedPathMapNode(const StoreDirConfig & store, std::string & s, const DerivedPathMap<StringSet>::ChildNode & node)
{
s += ',';
if (node.childMap.empty()) {
@ -510,7 +511,7 @@ static bool hasDynamicDrvDep(const Derivation & drv)
}
std::string Derivation::unparse(const Store & store, bool maskOutputs,
std::string Derivation::unparse(const StoreDirConfig & store, bool maskOutputs,
DerivedPathMap<StringSet>::ChildNode::Map * actualInputs) const
{
std::string s;
@ -546,7 +547,7 @@ std::string Derivation::unparse(const Store & store, bool maskOutputs,
},
[&](const DerivationOutput::CAFloating & dof) {
s += ','; printUnquotedString(s, "");
s += ','; printUnquotedString(s, dof.method.renderPrefix() + printHashType(dof.hashType));
s += ','; printUnquotedString(s, dof.method.renderPrefix() + printHashAlgo(dof.hashAlgo));
s += ','; printUnquotedString(s, "");
},
[&](const DerivationOutput::Deferred &) {
@ -557,7 +558,7 @@ std::string Derivation::unparse(const Store & store, bool maskOutputs,
[&](const DerivationOutput::Impure & doi) {
// FIXME
s += ','; printUnquotedString(s, "");
s += ','; printUnquotedString(s, doi.method.renderPrefix() + printHashType(doi.hashType));
s += ','; printUnquotedString(s, doi.method.renderPrefix() + printHashAlgo(doi.hashAlgo));
s += ','; printUnquotedString(s, "impure");
}
}, i.second.raw);
@ -630,7 +631,7 @@ DerivationType BasicDerivation::type() const
floatingCAOutputs,
deferredIAOutputs,
impureOutputs;
std::optional<HashType> floatingHashType;
std::optional<HashAlgorithm> floatingHashAlgo;
for (auto & i : outputs) {
std::visit(overloaded {
@ -642,10 +643,10 @@ DerivationType BasicDerivation::type() const
},
[&](const DerivationOutput::CAFloating & dof) {
floatingCAOutputs.insert(i.first);
if (!floatingHashType) {
floatingHashType = dof.hashType;
if (!floatingHashAlgo) {
floatingHashAlgo = dof.hashAlgo;
} else {
if (*floatingHashType != dof.hashType)
if (*floatingHashAlgo != dof.hashAlgo)
throw Error("all floating outputs must use the same hash type");
}
},
@ -773,7 +774,7 @@ DrvHash hashDerivationModulo(Store & store, const Derivation & drv, bool maskOut
std::map<std::string, Hash> outputHashes;
for (const auto & i : drv.outputs) {
auto & dof = std::get<DerivationOutput::CAFixed>(i.second.raw);
auto hash = hashString(htSHA256, "fixed:out:"
auto hash = hashString(HashAlgorithm::SHA256, "fixed:out:"
+ dof.ca.printMethodAlgo() + ":"
+ dof.ca.hash.to_string(HashFormat::Base16, false) + ":"
+ store.printStorePath(dof.path(store, drv.name, i.first)));
@ -824,7 +825,7 @@ DrvHash hashDerivationModulo(Store & store, const Derivation & drv, bool maskOut
}
}
auto hash = hashString(htSHA256, drv.unparse(store, maskOutputs, &inputs2));
auto hash = hashString(HashAlgorithm::SHA256, drv.unparse(store, maskOutputs, &inputs2));
std::map<std::string, Hash> outputHashes;
for (const auto & [outputName, _] : drv.outputs) {
@ -844,7 +845,7 @@ std::map<std::string, Hash> staticOutputHashes(Store & store, const Derivation &
}
static DerivationOutput readDerivationOutput(Source & in, const Store & store)
static DerivationOutput readDerivationOutput(Source & in, const StoreDirConfig & store)
{
const auto pathS = readString(in);
const auto hashAlgo = readString(in);
@ -861,7 +862,7 @@ StringSet BasicDerivation::outputNames() const
return names;
}
DerivationOutputsAndOptPaths BasicDerivation::outputsAndOptPaths(const Store & store) const
DerivationOutputsAndOptPaths BasicDerivation::outputsAndOptPaths(const StoreDirConfig & store) const
{
DerivationOutputsAndOptPaths outsAndOptPaths;
for (auto & [outputName, output] : outputs)
@ -883,7 +884,7 @@ std::string_view BasicDerivation::nameFromPath(const StorePath & drvPath)
}
Source & readDerivation(Source & in, const Store & store, BasicDerivation & drv, std::string_view name)
Source & readDerivation(Source & in, const StoreDirConfig & store, BasicDerivation & drv, std::string_view name)
{
drv.name = name;
@ -911,7 +912,7 @@ Source & readDerivation(Source & in, const Store & store, BasicDerivation & drv,
}
void writeDerivation(Sink & out, const Store & store, const BasicDerivation & drv)
void writeDerivation(Sink & out, const StoreDirConfig & store, const BasicDerivation & drv)
{
out << drv.outputs.size();
for (auto & i : drv.outputs) {
@ -929,7 +930,7 @@ void writeDerivation(Sink & out, const Store & store, const BasicDerivation & dr
},
[&](const DerivationOutput::CAFloating & dof) {
out << ""
<< (dof.method.renderPrefix() + printHashType(dof.hashType))
<< (dof.method.renderPrefix() + printHashAlgo(dof.hashAlgo))
<< "";
},
[&](const DerivationOutput::Deferred &) {
@ -939,7 +940,7 @@ void writeDerivation(Sink & out, const Store & store, const BasicDerivation & dr
},
[&](const DerivationOutput::Impure & doi) {
out << ""
<< (doi.method.renderPrefix() + printHashType(doi.hashType))
<< (doi.method.renderPrefix() + printHashAlgo(doi.hashAlgo))
<< "impure";
},
}, i.second.raw);
@ -957,7 +958,7 @@ void writeDerivation(Sink & out, const Store & store, const BasicDerivation & dr
std::string hashPlaceholder(const OutputNameView outputName)
{
// FIXME: memoize?
return "/" + hashString(htSHA256, concatStrings("nix-output:", outputName)).to_string(HashFormat::Base32, false);
return "/" + hashString(HashAlgorithm::SHA256, concatStrings("nix-output:", outputName)).to_string(HashFormat::Nix32, false);
}
@ -1001,13 +1002,13 @@ static void rewriteDerivation(Store & store, BasicDerivation & drv, const String
}
std::optional<BasicDerivation> Derivation::tryResolve(Store & store) const
std::optional<BasicDerivation> Derivation::tryResolve(Store & store, Store * evalStore) const
{
std::map<std::pair<StorePath, std::string>, StorePath> inputDrvOutputs;
std::function<void(const StorePath &, const DerivedPathMap<StringSet>::ChildNode &)> accum;
accum = [&](auto & inputDrv, auto & node) {
for (auto & [outputName, outputPath] : store.queryPartialDerivationOutputMap(inputDrv)) {
for (auto & [outputName, outputPath] : store.queryPartialDerivationOutputMap(inputDrv, evalStore)) {
if (outputPath) {
inputDrvOutputs.insert_or_assign({inputDrv, outputName}, *outputPath);
if (auto p = get(node.childMap, outputName))
@ -1149,10 +1150,10 @@ void Derivation::checkInvariants(Store & store, const StorePath & drvPath) const
}
const Hash impureOutputHash = hashString(htSHA256, "impure");
const Hash impureOutputHash = hashString(HashAlgorithm::SHA256, "impure");
nlohmann::json DerivationOutput::toJSON(
const Store & store, std::string_view drvName, OutputNameView outputName) const
const StoreDirConfig & store, std::string_view drvName, OutputNameView outputName) const
{
nlohmann::json res = nlohmann::json::object();
std::visit(overloaded {
@ -1166,11 +1167,11 @@ nlohmann::json DerivationOutput::toJSON(
// FIXME print refs?
},
[&](const DerivationOutput::CAFloating & dof) {
res["hashAlgo"] = dof.method.renderPrefix() + printHashType(dof.hashType);
res["hashAlgo"] = dof.method.renderPrefix() + printHashAlgo(dof.hashAlgo);
},
[&](const DerivationOutput::Deferred &) {},
[&](const DerivationOutput::Impure & doi) {
res["hashAlgo"] = doi.method.renderPrefix() + printHashType(doi.hashType);
res["hashAlgo"] = doi.method.renderPrefix() + printHashAlgo(doi.hashAlgo);
res["impure"] = true;
},
}, raw);
@ -1179,7 +1180,7 @@ nlohmann::json DerivationOutput::toJSON(
DerivationOutput DerivationOutput::fromJSON(
const Store & store, std::string_view drvName, OutputNameView outputName,
const StoreDirConfig & store, std::string_view drvName, OutputNameView outputName,
const nlohmann::json & _json,
const ExperimentalFeatureSettings & xpSettings)
{
@ -1190,15 +1191,15 @@ DerivationOutput DerivationOutput::fromJSON(
for (const auto & [key, _] : json)
keys.insert(key);
auto methodAlgo = [&]() -> std::pair<ContentAddressMethod, HashType> {
std::string hashAlgo = json["hashAlgo"];
auto methodAlgo = [&]() -> std::pair<ContentAddressMethod, HashAlgorithm> {
std::string hashAlgoStr = json["hashAlgo"];
// remaining to parse, will be mutated by parsers
std::string_view s = hashAlgo;
std::string_view s = hashAlgoStr;
ContentAddressMethod method = ContentAddressMethod::parsePrefix(s);
if (method == TextIngestionMethod {})
xpSettings.require(Xp::DynamicDerivations);
auto hashType = parseHashType(s);
return { std::move(method), std::move(hashType) };
auto hashAlgo = parseHashAlgo(s);
return { std::move(method), std::move(hashAlgo) };
};
if (keys == (std::set<std::string_view> { "path" })) {
@ -1208,11 +1209,11 @@ DerivationOutput DerivationOutput::fromJSON(
}
else if (keys == (std::set<std::string_view> { "path", "hashAlgo", "hash" })) {
auto [method, hashType] = methodAlgo();
auto [method, hashAlgo] = methodAlgo();
auto dof = DerivationOutput::CAFixed {
.ca = ContentAddress {
.method = std::move(method),
.hash = Hash::parseNonSRIUnprefixed((std::string) json["hash"], hashType),
.hash = Hash::parseNonSRIUnprefixed((std::string) json["hash"], hashAlgo),
},
};
if (dof.path(store, drvName, outputName) != store.parseStorePath((std::string) json["path"]))
@ -1222,10 +1223,10 @@ DerivationOutput DerivationOutput::fromJSON(
else if (keys == (std::set<std::string_view> { "hashAlgo" })) {
xpSettings.require(Xp::CaDerivations);
auto [method, hashType] = methodAlgo();
auto [method, hashAlgo] = methodAlgo();
return DerivationOutput::CAFloating {
.method = std::move(method),
.hashType = std::move(hashType),
.hashAlgo = std::move(hashAlgo),
};
}
@ -1235,10 +1236,10 @@ DerivationOutput DerivationOutput::fromJSON(
else if (keys == (std::set<std::string_view> { "hashAlgo", "impure" })) {
xpSettings.require(Xp::ImpureDerivations);
auto [method, hashType] = methodAlgo();
auto [method, hashAlgo] = methodAlgo();
return DerivationOutput::Impure {
.method = std::move(method),
.hashType = hashType,
.hashAlgo = hashAlgo,
};
}
@ -1248,7 +1249,7 @@ DerivationOutput DerivationOutput::fromJSON(
}
nlohmann::json Derivation::toJSON(const Store & store) const
nlohmann::json Derivation::toJSON(const StoreDirConfig & store) const
{
nlohmann::json res = nlohmann::json::object();
@ -1301,7 +1302,7 @@ nlohmann::json Derivation::toJSON(const Store & store) const
Derivation Derivation::fromJSON(
const Store & store,
const StoreDirConfig & store,
const nlohmann::json & json,
const ExperimentalFeatureSettings & xpSettings)
{

View file

@ -17,7 +17,7 @@
namespace nix {
class Store;
struct StoreDirConfig;
/* Abstract syntax of derivations. */
@ -55,7 +55,7 @@ struct DerivationOutput
* @param drvName The name of the derivation this is an output of, without the `.drv`.
* @param outputName The name of this output.
*/
StorePath path(const Store & store, std::string_view drvName, OutputNameView outputName) const;
StorePath path(const StoreDirConfig & store, std::string_view drvName, OutputNameView outputName) const;
GENERATE_CMP(CAFixed, me->ca);
};
@ -75,9 +75,9 @@ struct DerivationOutput
/**
* How the serialization will be hashed
*/
HashType hashType;
HashAlgorithm hashAlgo;
GENERATE_CMP(CAFloating, me->method, me->hashType);
GENERATE_CMP(CAFloating, me->method, me->hashAlgo);
};
/**
@ -102,9 +102,9 @@ struct DerivationOutput
/**
* How the serialization will be hashed
*/
HashType hashType;
HashAlgorithm hashAlgo;
GENERATE_CMP(Impure, me->method, me->hashType);
GENERATE_CMP(Impure, me->method, me->hashAlgo);
};
typedef std::variant<
@ -132,17 +132,17 @@ struct DerivationOutput
* the safer interface provided by
* BasicDerivation::outputsAndOptPaths
*/
std::optional<StorePath> path(const Store & store, std::string_view drvName, OutputNameView outputName) const;
std::optional<StorePath> path(const StoreDirConfig & store, std::string_view drvName, OutputNameView outputName) const;
nlohmann::json toJSON(
const Store & store,
const StoreDirConfig & store,
std::string_view drvName,
OutputNameView outputName) const;
/**
* @param xpSettings Stop-gap to avoid globals during unit tests.
*/
static DerivationOutput fromJSON(
const Store & store,
const StoreDirConfig & store,
std::string_view drvName,
OutputNameView outputName,
const nlohmann::json & json,
@ -304,7 +304,7 @@ struct BasicDerivation
* augmented with knowledge of the Store paths they would be written
* into.
*/
DerivationOutputsAndOptPaths outputsAndOptPaths(const Store & store) const;
DerivationOutputsAndOptPaths outputsAndOptPaths(const StoreDirConfig & store) const;
static std::string_view nameFromPath(const StorePath & storePath);
@ -318,6 +318,8 @@ struct BasicDerivation
me->name);
};
class Store;
struct Derivation : BasicDerivation
{
/**
@ -328,7 +330,7 @@ struct Derivation : BasicDerivation
/**
* Print a derivation.
*/
std::string unparse(const Store & store, bool maskOutputs,
std::string unparse(const StoreDirConfig & store, bool maskOutputs,
DerivedPathMap<StringSet>::ChildNode::Map * actualInputs = nullptr) const;
/**
@ -340,7 +342,7 @@ struct Derivation : BasicDerivation
* 2. Input placeholders are replaced with realized input store
* paths.
*/
std::optional<BasicDerivation> tryResolve(Store & store) const;
std::optional<BasicDerivation> tryResolve(Store & store, Store * evalStore = nullptr) const;
/**
* Like the above, but instead of querying the Nix database for
@ -365,9 +367,9 @@ struct Derivation : BasicDerivation
Derivation(const BasicDerivation & bd) : BasicDerivation(bd) { }
Derivation(BasicDerivation && bd) : BasicDerivation(std::move(bd)) { }
nlohmann::json toJSON(const Store & store) const;
nlohmann::json toJSON(const StoreDirConfig & store) const;
static Derivation fromJSON(
const Store & store,
const StoreDirConfig & store,
const nlohmann::json & json,
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
@ -391,7 +393,7 @@ StorePath writeDerivation(Store & store,
* Read a derivation from a file.
*/
Derivation parseDerivation(
const Store & store,
const StoreDirConfig & store,
std::string && s,
std::string_view name,
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
@ -493,8 +495,8 @@ extern Sync<DrvHashes> drvHashes;
struct Source;
struct Sink;
Source & readDerivation(Source & in, const Store & store, BasicDerivation & drv, std::string_view name);
void writeDerivation(Sink & out, const Store & store, const BasicDerivation & drv);
Source & readDerivation(Source & in, const StoreDirConfig & store, BasicDerivation & drv, std::string_view name);
void writeDerivation(Sink & out, const StoreDirConfig & store, const BasicDerivation & drv);
/**
* This creates an opaque and almost certainly unique string

View file

@ -1,4 +1,5 @@
#include "derived-path-map.hh"
#include "util.hh"
namespace nix {

View file

@ -1,4 +1,5 @@
#pragma once
///@file
#include "types.hh"
#include "derived-path.hh"

View file

@ -1,4 +1,5 @@
#include "derived-path.hh"
#include "derivations.hh"
#include "store-api.hh"
#include <nlohmann/json.hpp>
@ -32,7 +33,7 @@ CMP(SingleDerivedPath, DerivedPathBuilt, outputs)
#undef CMP
#undef CMP_ONE
nlohmann::json DerivedPath::Opaque::toJSON(const Store & store) const
nlohmann::json DerivedPath::Opaque::toJSON(const StoreDirConfig & store) const
{
return store.printStorePath(path);
}
@ -86,50 +87,50 @@ nlohmann::json DerivedPath::toJSON(Store & store) const
}, raw());
}
std::string DerivedPath::Opaque::to_string(const Store & store) const
std::string DerivedPath::Opaque::to_string(const StoreDirConfig & store) const
{
return store.printStorePath(path);
}
std::string SingleDerivedPath::Built::to_string(const Store & store) const
std::string SingleDerivedPath::Built::to_string(const StoreDirConfig & store) const
{
return drvPath->to_string(store) + "^" + output;
}
std::string SingleDerivedPath::Built::to_string_legacy(const Store & store) const
std::string SingleDerivedPath::Built::to_string_legacy(const StoreDirConfig & store) const
{
return drvPath->to_string(store) + "!" + output;
}
std::string DerivedPath::Built::to_string(const Store & store) const
std::string DerivedPath::Built::to_string(const StoreDirConfig & store) const
{
return drvPath->to_string(store)
+ '^'
+ outputs.to_string();
}
std::string DerivedPath::Built::to_string_legacy(const Store & store) const
std::string DerivedPath::Built::to_string_legacy(const StoreDirConfig & store) const
{
return drvPath->to_string_legacy(store)
+ "!"
+ outputs.to_string();
}
std::string SingleDerivedPath::to_string(const Store & store) const
std::string SingleDerivedPath::to_string(const StoreDirConfig & store) const
{
return std::visit(
[&](const auto & req) { return req.to_string(store); },
raw());
}
std::string DerivedPath::to_string(const Store & store) const
std::string DerivedPath::to_string(const StoreDirConfig & store) const
{
return std::visit(
[&](const auto & req) { return req.to_string(store); },
raw());
}
std::string SingleDerivedPath::to_string_legacy(const Store & store) const
std::string SingleDerivedPath::to_string_legacy(const StoreDirConfig & store) const
{
return std::visit(overloaded {
[&](const SingleDerivedPath::Built & req) { return req.to_string_legacy(store); },
@ -137,7 +138,7 @@ std::string SingleDerivedPath::to_string_legacy(const Store & store) const
}, this->raw());
}
std::string DerivedPath::to_string_legacy(const Store & store) const
std::string DerivedPath::to_string_legacy(const StoreDirConfig & store) const
{
return std::visit(overloaded {
[&](const DerivedPath::Built & req) { return req.to_string_legacy(store); },
@ -146,7 +147,7 @@ std::string DerivedPath::to_string_legacy(const Store & store) const
}
DerivedPath::Opaque DerivedPath::Opaque::parse(const Store & store, std::string_view s)
DerivedPath::Opaque DerivedPath::Opaque::parse(const StoreDirConfig & store, std::string_view s)
{
return {store.parseStorePath(s)};
}
@ -166,7 +167,7 @@ void drvRequireExperiment(
}
SingleDerivedPath::Built SingleDerivedPath::Built::parse(
const Store & store, ref<SingleDerivedPath> drv,
const StoreDirConfig & store, ref<SingleDerivedPath> drv,
OutputNameView output,
const ExperimentalFeatureSettings & xpSettings)
{
@ -178,7 +179,7 @@ SingleDerivedPath::Built SingleDerivedPath::Built::parse(
}
DerivedPath::Built DerivedPath::Built::parse(
const Store & store, ref<SingleDerivedPath> drv,
const StoreDirConfig & store, ref<SingleDerivedPath> drv,
OutputNameView outputsS,
const ExperimentalFeatureSettings & xpSettings)
{
@ -190,7 +191,7 @@ DerivedPath::Built DerivedPath::Built::parse(
}
static SingleDerivedPath parseWithSingle(
const Store & store, std::string_view s, std::string_view separator,
const StoreDirConfig & store, std::string_view s, std::string_view separator,
const ExperimentalFeatureSettings & xpSettings)
{
size_t n = s.rfind(separator);
@ -207,7 +208,7 @@ static SingleDerivedPath parseWithSingle(
}
SingleDerivedPath SingleDerivedPath::parse(
const Store & store,
const StoreDirConfig & store,
std::string_view s,
const ExperimentalFeatureSettings & xpSettings)
{
@ -215,7 +216,7 @@ SingleDerivedPath SingleDerivedPath::parse(
}
SingleDerivedPath SingleDerivedPath::parseLegacy(
const Store & store,
const StoreDirConfig & store,
std::string_view s,
const ExperimentalFeatureSettings & xpSettings)
{
@ -223,7 +224,7 @@ SingleDerivedPath SingleDerivedPath::parseLegacy(
}
static DerivedPath parseWith(
const Store & store, std::string_view s, std::string_view separator,
const StoreDirConfig & store, std::string_view s, std::string_view separator,
const ExperimentalFeatureSettings & xpSettings)
{
size_t n = s.rfind(separator);
@ -240,7 +241,7 @@ static DerivedPath parseWith(
}
DerivedPath DerivedPath::parse(
const Store & store,
const StoreDirConfig & store,
std::string_view s,
const ExperimentalFeatureSettings & xpSettings)
{
@ -248,7 +249,7 @@ DerivedPath DerivedPath::parse(
}
DerivedPath DerivedPath::parseLegacy(
const Store & store,
const StoreDirConfig & store,
std::string_view s,
const ExperimentalFeatureSettings & xpSettings)
{

View file

@ -1,10 +1,10 @@
#pragma once
///@file
#include "util.hh"
#include "path.hh"
#include "outputs-spec.hh"
#include "comparator.hh"
#include "config.hh"
#include <variant>
@ -12,6 +12,9 @@
namespace nix {
struct StoreDirConfig;
// TODO stop needing this, `toJSON` below should be pure
class Store;
/**
@ -24,9 +27,9 @@ class Store;
struct DerivedPathOpaque {
StorePath path;
std::string to_string(const Store & store) const;
static DerivedPathOpaque parse(const Store & store, std::string_view);
nlohmann::json toJSON(const Store & store) const;
std::string to_string(const StoreDirConfig & store) const;
static DerivedPathOpaque parse(const StoreDirConfig & store, std::string_view);
nlohmann::json toJSON(const StoreDirConfig & store) const;
GENERATE_CMP(DerivedPathOpaque, me->path);
};
@ -59,18 +62,18 @@ struct SingleDerivedPathBuilt {
/**
* Uses `^` as the separator
*/
std::string to_string(const Store & store) const;
std::string to_string(const StoreDirConfig & store) const;
/**
* Uses `!` as the separator
*/
std::string to_string_legacy(const Store & store) const;
std::string to_string_legacy(const StoreDirConfig & store) const;
/**
* The caller splits on the separator, so it works for both variants.
*
* @param xpSettings Stop-gap to avoid globals during unit tests.
*/
static SingleDerivedPathBuilt parse(
const Store & store, ref<SingleDerivedPath> drvPath,
const StoreDirConfig & store, ref<SingleDerivedPath> drvPath,
OutputNameView outputs,
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
nlohmann::json toJSON(Store & store) const;
@ -120,18 +123,18 @@ struct SingleDerivedPath : _SingleDerivedPathRaw {
/**
* Uses `^` as the separator
*/
std::string to_string(const Store & store) const;
std::string to_string(const StoreDirConfig & store) const;
/**
* Uses `!` as the separator
*/
std::string to_string_legacy(const Store & store) const;
std::string to_string_legacy(const StoreDirConfig & store) const;
/**
* Uses `^` as the separator
*
* @param xpSettings Stop-gap to avoid globals during unit tests.
*/
static SingleDerivedPath parse(
const Store & store,
const StoreDirConfig & store,
std::string_view,
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
/**
@ -140,7 +143,7 @@ struct SingleDerivedPath : _SingleDerivedPathRaw {
* @param xpSettings Stop-gap to avoid globals during unit tests.
*/
static SingleDerivedPath parseLegacy(
const Store & store,
const StoreDirConfig & store,
std::string_view,
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
nlohmann::json toJSON(Store & store) const;
@ -182,18 +185,18 @@ struct DerivedPathBuilt {
/**
* Uses `^` as the separator
*/
std::string to_string(const Store & store) const;
std::string to_string(const StoreDirConfig & store) const;
/**
* Uses `!` as the separator
*/
std::string to_string_legacy(const Store & store) const;
std::string to_string_legacy(const StoreDirConfig & store) const;
/**
* The caller splits on the separator, so it works for both variants.
*
* @param xpSettings Stop-gap to avoid globals during unit tests.
*/
static DerivedPathBuilt parse(
const Store & store, ref<SingleDerivedPath>,
const StoreDirConfig & store, ref<SingleDerivedPath>,
std::string_view,
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
nlohmann::json toJSON(Store & store) const;
@ -242,18 +245,18 @@ struct DerivedPath : _DerivedPathRaw {
/**
* Uses `^` as the separator
*/
std::string to_string(const Store & store) const;
std::string to_string(const StoreDirConfig & store) const;
/**
* Uses `!` as the separator
*/
std::string to_string_legacy(const Store & store) const;
std::string to_string_legacy(const StoreDirConfig & store) const;
/**
* Uses `^` as the separator
*
* @param xpSettings Stop-gap to avoid globals during unit tests.
*/
static DerivedPath parse(
const Store & store,
const StoreDirConfig & store,
std::string_view,
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
/**
@ -262,7 +265,7 @@ struct DerivedPath : _DerivedPathRaw {
* @param xpSettings Stop-gap to avoid globals during unit tests.
*/
static DerivedPath parseLegacy(
const Store & store,
const StoreDirConfig & store,
std::string_view,
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);

View file

@ -5,7 +5,7 @@ namespace nix {
std::string DownstreamPlaceholder::render() const
{
return "/" + hash.to_string(HashFormat::Base32, false);
return "/" + hash.to_string(HashFormat::Nix32, false);
}
@ -19,7 +19,7 @@ DownstreamPlaceholder DownstreamPlaceholder::unknownCaOutput(
auto drvName = drvNameWithExtension.substr(0, drvNameWithExtension.size() - 4);
auto clearText = "nix-upstream-output:" + std::string { drvPath.hashPart() } + ":" + outputPathName(drvName, outputName);
return DownstreamPlaceholder {
hashString(htSHA256, clearText)
hashString(HashAlgorithm::SHA256, clearText)
};
}
@ -31,10 +31,10 @@ DownstreamPlaceholder DownstreamPlaceholder::unknownDerivation(
xpSettings.require(Xp::DynamicDerivations);
auto compressed = compressHash(placeholder.hash, 20);
auto clearText = "nix-computed-output:"
+ compressed.to_string(HashFormat::Base32, false)
+ compressed.to_string(HashFormat::Nix32, false)
+ ":" + std::string { outputName };
return DownstreamPlaceholder {
hashString(htSHA256, clearText)
hashString(HashAlgorithm::SHA256, clearText)
};
}

View file

@ -72,7 +72,7 @@ struct DummyStore : public virtual DummyStoreConfig, public virtual Store
Callback<std::shared_ptr<const Realisation>> callback) noexcept override
{ callback(nullptr); }
virtual ref<FSAccessor> getFSAccessor() override
virtual ref<SourceAccessor> getFSAccessor(bool requireValidPath) override
{ unsupported("getFSAccessor"); }
};

View file

@ -30,7 +30,7 @@ void Store::exportPath(const StorePath & path, Sink & sink)
{
auto info = queryPathInfo(path);
HashSink hashSink(htSHA256);
HashSink hashSink(HashAlgorithm::SHA256);
TeeSink teeSink(sink, hashSink);
narFromPath(path, teeSink);
@ -39,9 +39,9 @@ void Store::exportPath(const StorePath & path, Sink & sink)
filesystem corruption from spreading to other machines.
Don't complain if the stored hash is zero (unknown). */
Hash hash = hashSink.currentHash().first;
if (hash != info->narHash && info->narHash != Hash(info->narHash.type))
if (hash != info->narHash && info->narHash != Hash(info->narHash.algo))
throw Error("hash of path '%s' has changed from '%s' to '%s'!",
printStorePath(path), info->narHash.to_string(HashFormat::Base32, true), hash.to_string(HashFormat::Base32, true));
printStorePath(path), info->narHash.to_string(HashFormat::Nix32, true), hash.to_string(HashFormat::Nix32, true));
teeSink
<< exportMagic
@ -65,7 +65,7 @@ StorePaths Store::importPaths(Source & source, CheckSigsFlag checkSigs)
/* Extract the NAR from the source. */
StringSink saved;
TeeSource tee { source, saved };
ParseSink ether;
NullParseSink ether;
parseDump(ether, tee);
uint32_t magic = readInt(source);
@ -79,7 +79,7 @@ StorePaths Store::importPaths(Source & source, CheckSigsFlag checkSigs)
auto references = CommonProto::Serialise<StorePathSet>::read(*this,
CommonProto::ReadConn { .from = source });
auto deriver = readString(source);
auto narHash = hashString(htSHA256, saved.s);
auto narHash = hashString(HashAlgorithm::SHA256, saved.s);
ValidPathInfo info { path, narHash };
if (deriver != "")

View file

@ -1,11 +1,12 @@
#include "filetransfer.hh"
#include "util.hh"
#include "namespaces.hh"
#include "globals.hh"
#include "store-api.hh"
#include "s3.hh"
#include "compression.hh"
#include "finally.hh"
#include "callback.hh"
#include "signals.hh"
#if ENABLE_S3
#include <aws/core/client/ClientConfiguration.h>

View file

@ -1,52 +0,0 @@
#pragma once
///@file
#include "types.hh"
namespace nix {
/**
* An abstract class for accessing a filesystem-like structure, such
* as a (possibly remote) Nix store or the contents of a NAR file.
*/
class FSAccessor
{
public:
enum Type { tMissing, tRegular, tSymlink, tDirectory };
struct Stat
{
Type type = tMissing;
/**
* regular files only
*/
uint64_t fileSize = 0;
/**
* regular files only
*/
bool isExecutable = false; // regular files only
/**
* regular files only
*/
uint64_t narOffset = 0; // regular files only
};
virtual ~FSAccessor() { }
virtual Stat stat(const Path & path) = 0;
virtual StringSet readDirectory(const Path & path) = 0;
/**
* Read a file inside the store.
*
* If `requireValidPath` is set to `true` (the default), the path must be
* inside a valid store path, otherwise it just needs to be physically
* present (but not necessarily properly registered)
*/
virtual std::string readFile(const Path & path, bool requireValidPath = true) = 0;
virtual std::string readLink(const Path & path) = 0;
};
}

View file

@ -2,6 +2,13 @@
#include "globals.hh"
#include "local-store.hh"
#include "finally.hh"
#include "unix-domain-socket.hh"
#include "signals.hh"
#if !defined(__linux__)
// For shelling out to lsof
# include "processes.hh"
#endif
#include <functional>
#include <queue>
@ -43,7 +50,7 @@ static void makeSymlink(const Path & link, const Path & target)
void LocalStore::addIndirectRoot(const Path & path)
{
std::string hash = hashString(htSHA1, path).to_string(HashFormat::Base32, false);
std::string hash = hashString(HashAlgorithm::SHA1, path).to_string(HashFormat::Nix32, false);
Path realRoot = canonPath(fmt("%1%/%2%/auto/%3%", stateDir, gcRootsDir, hash));
makeSymlink(realRoot, path);
}
@ -323,9 +330,7 @@ typedef std::unordered_map<Path, std::unordered_set<std::string>> UncheckedRoots
static void readProcLink(const std::string & file, UncheckedRoots & roots)
{
/* 64 is the starting buffer size gnu readlink uses... */
auto bufsiz = ssize_t{64};
try_again:
constexpr auto bufsiz = PATH_MAX;
char buf[bufsiz];
auto res = readlink(file.c_str(), buf, bufsiz);
if (res == -1) {
@ -334,10 +339,7 @@ try_again:
throw SysError("reading symlink");
}
if (res == bufsiz) {
if (SSIZE_MAX / 2 < bufsiz)
throw Error("stupidly long symlink");
bufsiz *= 2;
goto try_again;
throw Error("overly long symlink starting with '%1%'", std::string_view(buf, bufsiz));
}
if (res > 0 && buf[0] == '/')
roots[std::string(static_cast<char *>(buf), res)]

View file

@ -1,7 +1,8 @@
#include "globals.hh"
#include "util.hh"
#include "current-process.hh"
#include "archive.hh"
#include "args.hh"
#include "users.hh"
#include "abstract-setting-to-json.hh"
#include "compute-levels.hh"
@ -17,9 +18,13 @@
#include <sodium/core.h>
#ifdef __GLIBC__
#include <gnu/lib-names.h>
#include <nss.h>
#include <dlfcn.h>
# include <gnu/lib-names.h>
# include <nss.h>
# include <dlfcn.h>
#endif
#if __APPLE__
# include "processes.hh"
#endif
#include "config-impl.hh"
@ -111,7 +116,14 @@ Settings::Settings()
void loadConfFile()
{
globalConfig.applyConfigFile(settings.nixConfDir + "/nix.conf");
auto applyConfigFile = [&](const Path & path) {
try {
std::string contents = readFile(path);
globalConfig.applyConfig(contents, path);
} catch (SysError &) { }
};
applyConfigFile(settings.nixConfDir + "/nix.conf");
/* We only want to send overrides to the daemon, i.e. stuff from
~/.nix/nix.conf or the command line. */
@ -119,7 +131,7 @@ void loadConfFile()
auto files = settings.nixUserConfFiles;
for (auto file = files.rbegin(); file != files.rend(); file++) {
globalConfig.applyConfigFile(*file);
applyConfigFile(*file);
}
auto nixConfEnv = getEnv("NIX_CONFIG");

View file

@ -3,7 +3,7 @@
#include "types.hh"
#include "config.hh"
#include "util.hh"
#include "environment-variables.hh"
#include "experimental-features.hh"
#include <map>
@ -117,10 +117,11 @@ public:
Setting<std::string> storeUri{this, getEnv("NIX_REMOTE").value_or("auto"), "store",
R"(
The [URL of the Nix store](@docroot@/command-ref/new-cli/nix3-help-stores.md#store-url-format)
The [URL of the Nix store](@docroot@/store/types/index.md#store-url-format)
to use for most operations.
See [`nix help-stores`](@docroot@/command-ref/new-cli/nix3-help-stores.md)
for supported store types and settings.
See the
[Store Types](@docroot@/store/types/index.md)
section of the manual for supported store types and settings.
)"};
Setting<bool> keepFailed{this, false, "keep-failed",
@ -183,7 +184,9 @@ public:
command line switch and defaults to `1`. The value `0` means that
the builder should use all available CPU cores in the system.
)",
{"build-cores"}, false};
{"build-cores"},
// Don't document the machine-specific default value
false};
/**
* Read-only mode. Don't copy stuff to the store, don't change
@ -198,7 +201,7 @@ public:
Nix will only build a given [derivation](@docroot@/language/derivations.md) locally when its `system` attribute equals any of the values specified here or in [`extra-platforms`](#conf-extra-platforms).
The default value is set when Nix itself is compiled for the system it will run on.
The following system types are widely used, as [Nix is actively supported on these platforms](@docroot@/contributing/hacking.md#platforms):
The following system types are widely used, as Nix is actively supported on these platforms:
- `x86_64-linux`
- `x86_64-darwin`
@ -699,7 +702,10 @@ public:
Build systems will usually detect the target platform to be the current physical system and therefore produce machine code incompatible with what may be intended in the derivation.
You should design your derivation's `builder` accordingly and cross-check the results when using this option against natively-built versions of your derivation.
)", {}, false};
)",
{},
// Don't document the machine-specific default value
false};
Setting<StringSet> systemFeatures{
this,
@ -744,15 +750,18 @@ public:
[nspawn]: https://github.com/NixOS/nix/blob/67bcb99700a0da1395fa063d7c6586740b304598/tests/systemd-nspawn.nix.
Included by default on Linux if the [`auto-allocate-uids`](#conf-auto-allocate-uids) setting is enabled.
)", {}, false};
)",
{},
// Don't document the machine-specific default value
false};
Setting<Strings> substituters{
this,
Strings{"https://cache.nixos.org/"},
"substituters",
R"(
A list of [URLs of Nix stores](@docroot@/command-ref/new-cli/nix3-help-stores.md#store-url-format) to be used as substituters, separated by whitespace.
A substituter is an additional [store]{@docroot@/glossary.md##gloss-store} from which Nix can obtain [store objects](@docroot@/glossary.md#gloss-store-object) instead of building them.
A list of [URLs of Nix stores](@docroot@/store/types/index.md#store-url-format) to be used as substituters, separated by whitespace.
A substituter is an additional [store](@docroot@/glossary.md#gloss-store) from which Nix can obtain [store objects](@docroot@/glossary.md#gloss-store-object) instead of building them.
Substituters are tried based on their priority value, which each substituter can set independently.
Lower value means higher priority.
@ -770,7 +779,7 @@ public:
Setting<StringSet> trustedSubstituters{
this, {}, "trusted-substituters",
R"(
A list of [Nix store URLs](@docroot@/command-ref/new-cli/nix3-help-stores.md#store-url-format), separated by whitespace.
A list of [Nix store URLs](@docroot@/store/types/index.md#store-url-format), separated by whitespace.
These are not used by default, but users of the Nix daemon can enable them by specifying [`substituters`](#conf-substituters).
Unprivileged users (those set in only [`allowed-users`](#conf-allowed-users) but not [`trusted-users`](#conf-trusted-users)) can pass as `substituters` only those URLs listed in `trusted-substituters`.
@ -1084,6 +1093,16 @@ public:
true, // document default
Xp::ConfigurableImpureEnv
};
Setting<std::string> upgradeNixStorePathUrl{
this,
"https://github.com/NixOS/nixpkgs/raw/master/nixos/modules/installer/tools/nix-fallback-paths.nix",
"upgrade-nix-store-path-url",
R"(
Used by `nix upgrade-nix`, the URL of the file that contains the
store paths of the latest Nix release.
)"
};
};

View file

@ -11,6 +11,30 @@ namespace nix {
* reference.
*
* See methods for details on the operations it represents.
*
* @note
* To understand the purpose of this class, it might help to do some
* "closed-world" rather than "open-world" reasoning, and consider the
* problem it solved for us. This class was factored out from
* `LocalFSStore` in order to support the following table, which
* contains 4 concrete store types (non-abstract classes, exposed to the
* user), and how they implemented the two GC root methods:
*
* @note
* | | `addPermRoot()` | `addIndirectRoot()` |
* |-------------------|-----------------|---------------------|
* | `LocalStore` | local | local |
* | `UDSRemoteStore` | local | remote |
* | `SSHStore` | doesn't have | doesn't have |
* | `MountedSSHStore` | remote | doesn't have |
*
* @note
* Note how only the local implementations of `addPermRoot()` need
* `addIndirectRoot()`; that is what this class enforces. Without it,
* and with `addPermRoot()` and `addIndirectRoot()` both `virtual`, we
* would accidentally be allowing for a combinatorial explosion of
* possible implementations many of which make no sense. Having this and
* that invariant enforced cuts down that space.
*/
struct IndirectRootStore : public virtual LocalFSStore
{

View file

@ -1,3 +1,4 @@
#include "legacy-ssh-store.hh"
#include "ssh-store-config.hh"
#include "archive.hh"
#include "pool.hh"
@ -13,432 +14,355 @@
namespace nix {
struct LegacySSHStoreConfig : virtual CommonSSHStoreConfig
std::string LegacySSHStoreConfig::doc()
{
using CommonSSHStoreConfig::CommonSSHStoreConfig;
return
#include "legacy-ssh-store.md"
;
}
const Setting<Path> remoteProgram{(StoreConfig*) this, "nix-store", "remote-program",
"Path to the `nix-store` executable on the remote machine."};
const Setting<int> maxConnections{(StoreConfig*) this, 1, "max-connections",
"Maximum number of concurrent SSH connections."};
const std::string name() override { return "SSH Store"; }
std::string doc() override
{
return
#include "legacy-ssh-store.md"
;
}
};
struct LegacySSHStore : public virtual LegacySSHStoreConfig, public virtual Store
struct LegacySSHStore::Connection
{
// Hack for getting remote build log output.
// Intentionally not in `LegacySSHStoreConfig` so that it doesn't appear in
// the documentation
const Setting<int> logFD{(StoreConfig*) this, -1, "log-fd", "file descriptor to which SSH's stderr is connected"};
struct Connection
{
std::unique_ptr<SSHMaster::Connection> sshConn;
FdSink to;
FdSource from;
ServeProto::Version remoteVersion;
bool good = true;
/**
* Coercion to `ServeProto::ReadConn`. This makes it easy to use the
* factored out serve protocol searlizers with a
* `LegacySSHStore::Connection`.
*
* The serve protocol connection types are unidirectional, unlike
* this type.
*/
operator ServeProto::ReadConn ()
{
return ServeProto::ReadConn {
.from = from,
.version = remoteVersion,
};
}
/*
* Coercion to `ServeProto::WriteConn`. This makes it easy to use the
* factored out serve protocol searlizers with a
* `LegacySSHStore::Connection`.
*
* The serve protocol connection types are unidirectional, unlike
* this type.
*/
operator ServeProto::WriteConn ()
{
return ServeProto::WriteConn {
.to = to,
.version = remoteVersion,
};
}
};
std::string host;
ref<Pool<Connection>> connections;
SSHMaster master;
static std::set<std::string> uriSchemes() { return {"ssh"}; }
LegacySSHStore(const std::string & scheme, const std::string & host, const Params & params)
: StoreConfig(params)
, CommonSSHStoreConfig(params)
, LegacySSHStoreConfig(params)
, Store(params)
, host(host)
, connections(make_ref<Pool<Connection>>(
std::max(1, (int) maxConnections),
[this]() { return openConnection(); },
[](const ref<Connection> & r) { return r->good; }
))
, master(
host,
sshKey,
sshPublicHostKey,
// Use SSH master only if using more than 1 connection.
connections->capacity() > 1,
compress,
logFD)
{
}
ref<Connection> openConnection()
{
auto conn = make_ref<Connection>();
conn->sshConn = master.startCommand(
fmt("%s --serve --write", remoteProgram)
+ (remoteStore.get() == "" ? "" : " --store " + shellEscape(remoteStore.get())));
conn->to = FdSink(conn->sshConn->in.get());
conn->from = FdSource(conn->sshConn->out.get());
try {
conn->to << SERVE_MAGIC_1 << SERVE_PROTOCOL_VERSION;
conn->to.flush();
StringSink saved;
try {
TeeSource tee(conn->from, saved);
unsigned int magic = readInt(tee);
if (magic != SERVE_MAGIC_2)
throw Error("'nix-store --serve' protocol mismatch from '%s'", host);
} catch (SerialisationError & e) {
/* In case the other side is waiting for our input,
close it. */
conn->sshConn->in.close();
auto msg = conn->from.drain();
throw Error("'nix-store --serve' protocol mismatch from '%s', got '%s'",
host, chomp(saved.s + msg));
}
conn->remoteVersion = readInt(conn->from);
if (GET_PROTOCOL_MAJOR(conn->remoteVersion) != 0x200)
throw Error("unsupported 'nix-store --serve' protocol version on '%s'", host);
} catch (EndOfFile & e) {
throw Error("cannot connect to '%1%'", host);
}
return conn;
};
std::string getUri() override
{
return *uriSchemes().begin() + "://" + host;
}
void queryPathInfoUncached(const StorePath & path,
Callback<std::shared_ptr<const ValidPathInfo>> callback) noexcept override
{
try {
auto conn(connections->get());
/* No longer support missing NAR hash */
assert(GET_PROTOCOL_MINOR(conn->remoteVersion) >= 4);
debug("querying remote host '%s' for info on '%s'", host, printStorePath(path));
conn->to << ServeProto::Command::QueryPathInfos << PathSet{printStorePath(path)};
conn->to.flush();
auto p = readString(conn->from);
if (p.empty()) return callback(nullptr);
auto path2 = parseStorePath(p);
assert(path == path2);
/* Hash will be set below. FIXME construct ValidPathInfo at end. */
auto info = std::make_shared<ValidPathInfo>(path, Hash::dummy);
auto deriver = readString(conn->from);
if (deriver != "")
info->deriver = parseStorePath(deriver);
info->references = ServeProto::Serialise<StorePathSet>::read(*this, *conn);
readLongLong(conn->from); // download size
info->narSize = readLongLong(conn->from);
{
auto s = readString(conn->from);
if (s == "")
throw Error("NAR hash is now mandatory");
info->narHash = Hash::parseAnyPrefixed(s);
}
info->ca = ContentAddress::parseOpt(readString(conn->from));
info->sigs = readStrings<StringSet>(conn->from);
auto s = readString(conn->from);
assert(s == "");
callback(std::move(info));
} catch (...) { callback.rethrow(); }
}
void addToStore(const ValidPathInfo & info, Source & source,
RepairFlag repair, CheckSigsFlag checkSigs) override
{
debug("adding path '%s' to remote host '%s'", printStorePath(info.path), host);
auto conn(connections->get());
if (GET_PROTOCOL_MINOR(conn->remoteVersion) >= 5) {
conn->to
<< ServeProto::Command::AddToStoreNar
<< printStorePath(info.path)
<< (info.deriver ? printStorePath(*info.deriver) : "")
<< info.narHash.to_string(HashFormat::Base16, false);
ServeProto::write(*this, *conn, info.references);
conn->to
<< info.registrationTime
<< info.narSize
<< info.ultimate
<< info.sigs
<< renderContentAddress(info.ca);
try {
copyNAR(source, conn->to);
} catch (...) {
conn->good = false;
throw;
}
conn->to.flush();
} else {
conn->to
<< ServeProto::Command::ImportPaths
<< 1;
try {
copyNAR(source, conn->to);
} catch (...) {
conn->good = false;
throw;
}
conn->to
<< exportMagic
<< printStorePath(info.path);
ServeProto::write(*this, *conn, info.references);
conn->to
<< (info.deriver ? printStorePath(*info.deriver) : "")
<< 0
<< 0;
conn->to.flush();
}
if (readInt(conn->from) != 1)
throw Error("failed to add path '%s' to remote host '%s'", printStorePath(info.path), host);
}
void narFromPath(const StorePath & path, Sink & sink) override
{
auto conn(connections->get());
conn->to << ServeProto::Command::DumpStorePath << printStorePath(path);
conn->to.flush();
copyNAR(conn->from, sink);
}
std::optional<StorePath> queryPathFromHashPart(const std::string & hashPart) override
{ unsupported("queryPathFromHashPart"); }
StorePath addToStore(
std::string_view name,
const Path & srcPath,
FileIngestionMethod method,
HashType hashAlgo,
PathFilter & filter,
RepairFlag repair,
const StorePathSet & references) override
{ unsupported("addToStore"); }
StorePath addTextToStore(
std::string_view name,
std::string_view s,
const StorePathSet & references,
RepairFlag repair) override
{ unsupported("addTextToStore"); }
private:
void putBuildSettings(Connection & conn)
{
conn.to
<< settings.maxSilentTime
<< settings.buildTimeout;
if (GET_PROTOCOL_MINOR(conn.remoteVersion) >= 2)
conn.to
<< settings.maxLogSize;
if (GET_PROTOCOL_MINOR(conn.remoteVersion) >= 3)
conn.to
<< 0 // buildRepeat hasn't worked for ages anyway
<< 0;
if (GET_PROTOCOL_MINOR(conn.remoteVersion) >= 7) {
conn.to << ((int) settings.keepFailed);
}
}
public:
BuildResult buildDerivation(const StorePath & drvPath, const BasicDerivation & drv,
BuildMode buildMode) override
{
auto conn(connections->get());
conn->to
<< ServeProto::Command::BuildDerivation
<< printStorePath(drvPath);
writeDerivation(conn->to, *this, drv);
putBuildSettings(*conn);
conn->to.flush();
return ServeProto::Serialise<BuildResult>::read(*this, *conn);
}
void buildPaths(const std::vector<DerivedPath> & drvPaths, BuildMode buildMode, std::shared_ptr<Store> evalStore) override
{
if (evalStore && evalStore.get() != this)
throw Error("building on an SSH store is incompatible with '--eval-store'");
auto conn(connections->get());
conn->to << ServeProto::Command::BuildPaths;
Strings ss;
for (auto & p : drvPaths) {
auto sOrDrvPath = StorePathWithOutputs::tryFromDerivedPath(p);
std::visit(overloaded {
[&](const StorePathWithOutputs & s) {
ss.push_back(s.to_string(*this));
},
[&](const StorePath & drvPath) {
throw Error("wanted to fetch '%s' but the legacy ssh protocol doesn't support merely substituting drv files via the build paths command. It would build them instead. Try using ssh-ng://", printStorePath(drvPath));
},
[&](std::monostate) {
throw Error("wanted build derivation that is itself a build product, but the legacy ssh protocol doesn't support that. Try using ssh-ng://");
},
}, sOrDrvPath);
}
conn->to << ss;
putBuildSettings(*conn);
conn->to.flush();
BuildResult result;
result.status = (BuildResult::Status) readInt(conn->from);
if (!result.success()) {
conn->from >> result.errorMsg;
throw Error(result.status, result.errorMsg);
}
}
void ensurePath(const StorePath & path) override
{ unsupported("ensurePath"); }
virtual ref<FSAccessor> getFSAccessor() override
{ unsupported("getFSAccessor"); }
std::unique_ptr<SSHMaster::Connection> sshConn;
FdSink to;
FdSource from;
ServeProto::Version remoteVersion;
bool good = true;
/**
* The default instance would schedule the work on the client side, but
* for consistency with `buildPaths` and `buildDerivation` it should happen
* on the remote side.
* Coercion to `ServeProto::ReadConn`. This makes it easy to use the
* factored out serve protocol searlizers with a
* `LegacySSHStore::Connection`.
*
* We make this fail for now so we can add implement this properly later
* without it being a breaking change.
* The serve protocol connection types are unidirectional, unlike
* this type.
*/
void repairPath(const StorePath & path) override
{ unsupported("repairPath"); }
void computeFSClosure(const StorePathSet & paths,
StorePathSet & out, bool flipDirection = false,
bool includeOutputs = false, bool includeDerivers = false) override
operator ServeProto::ReadConn ()
{
if (flipDirection || includeDerivers) {
Store::computeFSClosure(paths, out, flipDirection, includeOutputs, includeDerivers);
return;
}
auto conn(connections->get());
conn->to
<< ServeProto::Command::QueryClosure
<< includeOutputs;
ServeProto::write(*this, *conn, paths);
conn->to.flush();
for (auto & i : ServeProto::Serialise<StorePathSet>::read(*this, *conn))
out.insert(i);
return ServeProto::ReadConn {
.from = from,
.version = remoteVersion,
};
}
StorePathSet queryValidPaths(const StorePathSet & paths,
SubstituteFlag maybeSubstitute = NoSubstitute) override
{
auto conn(connections->get());
conn->to
<< ServeProto::Command::QueryValidPaths
<< false // lock
<< maybeSubstitute;
ServeProto::write(*this, *conn, paths);
conn->to.flush();
return ServeProto::Serialise<StorePathSet>::read(*this, *conn);
}
void connect() override
{
auto conn(connections->get());
}
unsigned int getProtocol() override
{
auto conn(connections->get());
return conn->remoteVersion;
}
/**
* The legacy ssh protocol doesn't support checking for trusted-user.
* Try using ssh-ng:// instead if you want to know.
/*
* Coercion to `ServeProto::WriteConn`. This makes it easy to use the
* factored out serve protocol searlizers with a
* `LegacySSHStore::Connection`.
*
* The serve protocol connection types are unidirectional, unlike
* this type.
*/
std::optional<TrustedFlag> isTrustedClient() override
operator ServeProto::WriteConn ()
{
return std::nullopt;
return ServeProto::WriteConn {
.to = to,
.version = remoteVersion,
};
}
void queryRealisationUncached(const DrvOutput &,
Callback<std::shared_ptr<const Realisation>> callback) noexcept override
// TODO: Implement
{ unsupported("queryRealisation"); }
};
LegacySSHStore::LegacySSHStore(const std::string & scheme, const std::string & host, const Params & params)
: StoreConfig(params)
, CommonSSHStoreConfig(params)
, LegacySSHStoreConfig(params)
, Store(params)
, host(host)
, connections(make_ref<Pool<Connection>>(
std::max(1, (int) maxConnections),
[this]() { return openConnection(); },
[](const ref<Connection> & r) { return r->good; }
))
, master(
host,
sshKey,
sshPublicHostKey,
// Use SSH master only if using more than 1 connection.
connections->capacity() > 1,
compress,
logFD)
{
}
ref<LegacySSHStore::Connection> LegacySSHStore::openConnection()
{
auto conn = make_ref<Connection>();
conn->sshConn = master.startCommand(
fmt("%s --serve --write", remoteProgram)
+ (remoteStore.get() == "" ? "" : " --store " + shellEscape(remoteStore.get())));
conn->to = FdSink(conn->sshConn->in.get());
conn->from = FdSource(conn->sshConn->out.get());
try {
conn->to << SERVE_MAGIC_1 << SERVE_PROTOCOL_VERSION;
conn->to.flush();
StringSink saved;
try {
TeeSource tee(conn->from, saved);
unsigned int magic = readInt(tee);
if (magic != SERVE_MAGIC_2)
throw Error("'nix-store --serve' protocol mismatch from '%s'", host);
} catch (SerialisationError & e) {
/* In case the other side is waiting for our input,
close it. */
conn->sshConn->in.close();
auto msg = conn->from.drain();
throw Error("'nix-store --serve' protocol mismatch from '%s', got '%s'",
host, chomp(saved.s + msg));
}
conn->remoteVersion = readInt(conn->from);
if (GET_PROTOCOL_MAJOR(conn->remoteVersion) != 0x200)
throw Error("unsupported 'nix-store --serve' protocol version on '%s'", host);
} catch (EndOfFile & e) {
throw Error("cannot connect to '%1%'", host);
}
return conn;
};
std::string LegacySSHStore::getUri()
{
return *uriSchemes().begin() + "://" + host;
}
void LegacySSHStore::queryPathInfoUncached(const StorePath & path,
Callback<std::shared_ptr<const ValidPathInfo>> callback) noexcept
{
try {
auto conn(connections->get());
/* No longer support missing NAR hash */
assert(GET_PROTOCOL_MINOR(conn->remoteVersion) >= 4);
debug("querying remote host '%s' for info on '%s'", host, printStorePath(path));
conn->to << ServeProto::Command::QueryPathInfos << PathSet{printStorePath(path)};
conn->to.flush();
auto p = readString(conn->from);
if (p.empty()) return callback(nullptr);
auto path2 = parseStorePath(p);
assert(path == path2);
auto info = std::make_shared<ValidPathInfo>(
path,
ServeProto::Serialise<UnkeyedValidPathInfo>::read(*this, *conn));
if (info->narHash == Hash::dummy)
throw Error("NAR hash is now mandatory");
auto s = readString(conn->from);
assert(s == "");
callback(std::move(info));
} catch (...) { callback.rethrow(); }
}
void LegacySSHStore::addToStore(const ValidPathInfo & info, Source & source,
RepairFlag repair, CheckSigsFlag checkSigs)
{
debug("adding path '%s' to remote host '%s'", printStorePath(info.path), host);
auto conn(connections->get());
if (GET_PROTOCOL_MINOR(conn->remoteVersion) >= 5) {
conn->to
<< ServeProto::Command::AddToStoreNar
<< printStorePath(info.path)
<< (info.deriver ? printStorePath(*info.deriver) : "")
<< info.narHash.to_string(HashFormat::Base16, false);
ServeProto::write(*this, *conn, info.references);
conn->to
<< info.registrationTime
<< info.narSize
<< info.ultimate
<< info.sigs
<< renderContentAddress(info.ca);
try {
copyNAR(source, conn->to);
} catch (...) {
conn->good = false;
throw;
}
conn->to.flush();
} else {
conn->to
<< ServeProto::Command::ImportPaths
<< 1;
try {
copyNAR(source, conn->to);
} catch (...) {
conn->good = false;
throw;
}
conn->to
<< exportMagic
<< printStorePath(info.path);
ServeProto::write(*this, *conn, info.references);
conn->to
<< (info.deriver ? printStorePath(*info.deriver) : "")
<< 0
<< 0;
conn->to.flush();
}
if (readInt(conn->from) != 1)
throw Error("failed to add path '%s' to remote host '%s'", printStorePath(info.path), host);
}
void LegacySSHStore::narFromPath(const StorePath & path, Sink & sink)
{
auto conn(connections->get());
conn->to << ServeProto::Command::DumpStorePath << printStorePath(path);
conn->to.flush();
copyNAR(conn->from, sink);
}
void LegacySSHStore::putBuildSettings(Connection & conn)
{
ServeProto::write(*this, conn, ServeProto::BuildOptions {
.maxSilentTime = settings.maxSilentTime,
.buildTimeout = settings.buildTimeout,
.maxLogSize = settings.maxLogSize,
.nrRepeats = 0, // buildRepeat hasn't worked for ages anyway
.enforceDeterminism = 0,
.keepFailed = settings.keepFailed,
});
}
BuildResult LegacySSHStore::buildDerivation(const StorePath & drvPath, const BasicDerivation & drv,
BuildMode buildMode)
{
auto conn(connections->get());
conn->to
<< ServeProto::Command::BuildDerivation
<< printStorePath(drvPath);
writeDerivation(conn->to, *this, drv);
putBuildSettings(*conn);
conn->to.flush();
return ServeProto::Serialise<BuildResult>::read(*this, *conn);
}
void LegacySSHStore::buildPaths(const std::vector<DerivedPath> & drvPaths, BuildMode buildMode, std::shared_ptr<Store> evalStore)
{
if (evalStore && evalStore.get() != this)
throw Error("building on an SSH store is incompatible with '--eval-store'");
auto conn(connections->get());
conn->to << ServeProto::Command::BuildPaths;
Strings ss;
for (auto & p : drvPaths) {
auto sOrDrvPath = StorePathWithOutputs::tryFromDerivedPath(p);
std::visit(overloaded {
[&](const StorePathWithOutputs & s) {
ss.push_back(s.to_string(*this));
},
[&](const StorePath & drvPath) {
throw Error("wanted to fetch '%s' but the legacy ssh protocol doesn't support merely substituting drv files via the build paths command. It would build them instead. Try using ssh-ng://", printStorePath(drvPath));
},
[&](std::monostate) {
throw Error("wanted build derivation that is itself a build product, but the legacy ssh protocol doesn't support that. Try using ssh-ng://");
},
}, sOrDrvPath);
}
conn->to << ss;
putBuildSettings(*conn);
conn->to.flush();
BuildResult result;
result.status = (BuildResult::Status) readInt(conn->from);
if (!result.success()) {
conn->from >> result.errorMsg;
throw Error(result.status, result.errorMsg);
}
}
void LegacySSHStore::computeFSClosure(const StorePathSet & paths,
StorePathSet & out, bool flipDirection,
bool includeOutputs, bool includeDerivers)
{
if (flipDirection || includeDerivers) {
Store::computeFSClosure(paths, out, flipDirection, includeOutputs, includeDerivers);
return;
}
auto conn(connections->get());
conn->to
<< ServeProto::Command::QueryClosure
<< includeOutputs;
ServeProto::write(*this, *conn, paths);
conn->to.flush();
for (auto & i : ServeProto::Serialise<StorePathSet>::read(*this, *conn))
out.insert(i);
}
StorePathSet LegacySSHStore::queryValidPaths(const StorePathSet & paths,
SubstituteFlag maybeSubstitute)
{
auto conn(connections->get());
conn->to
<< ServeProto::Command::QueryValidPaths
<< false // lock
<< maybeSubstitute;
ServeProto::write(*this, *conn, paths);
conn->to.flush();
return ServeProto::Serialise<StorePathSet>::read(*this, *conn);
}
void LegacySSHStore::connect()
{
auto conn(connections->get());
}
unsigned int LegacySSHStore::getProtocol()
{
auto conn(connections->get());
return conn->remoteVersion;
}
/**
* The legacy ssh protocol doesn't support checking for trusted-user.
* Try using ssh-ng:// instead if you want to know.
*/
std::optional<TrustedFlag> isTrustedClient()
{
return std::nullopt;
}
static RegisterStoreImplementation<LegacySSHStore, LegacySSHStoreConfig> regLegacySSHStore;
}

View file

@ -0,0 +1,132 @@
#pragma once
///@file
#include "ssh-store-config.hh"
#include "store-api.hh"
#include "ssh.hh"
#include "callback.hh"
#include "pool.hh"
namespace nix {
struct LegacySSHStoreConfig : virtual CommonSSHStoreConfig
{
using CommonSSHStoreConfig::CommonSSHStoreConfig;
const Setting<Path> remoteProgram{this, "nix-store", "remote-program",
"Path to the `nix-store` executable on the remote machine."};
const Setting<int> maxConnections{this, 1, "max-connections",
"Maximum number of concurrent SSH connections."};
const std::string name() override { return "SSH Store"; }
std::string doc() override;
};
struct LegacySSHStore : public virtual LegacySSHStoreConfig, public virtual Store
{
// Hack for getting remote build log output.
// Intentionally not in `LegacySSHStoreConfig` so that it doesn't appear in
// the documentation
const Setting<int> logFD{this, -1, "log-fd", "file descriptor to which SSH's stderr is connected"};
struct Connection;
std::string host;
ref<Pool<Connection>> connections;
SSHMaster master;
static std::set<std::string> uriSchemes() { return {"ssh"}; }
LegacySSHStore(const std::string & scheme, const std::string & host, const Params & params);
ref<Connection> openConnection();
std::string getUri() override;
void queryPathInfoUncached(const StorePath & path,
Callback<std::shared_ptr<const ValidPathInfo>> callback) noexcept override;
void addToStore(const ValidPathInfo & info, Source & source,
RepairFlag repair, CheckSigsFlag checkSigs) override;
void narFromPath(const StorePath & path, Sink & sink) override;
std::optional<StorePath> queryPathFromHashPart(const std::string & hashPart) override
{ unsupported("queryPathFromHashPart"); }
StorePath addToStore(
std::string_view name,
const Path & srcPath,
FileIngestionMethod method,
HashAlgorithm hashAlgo,
PathFilter & filter,
RepairFlag repair,
const StorePathSet & references) override
{ unsupported("addToStore"); }
StorePath addTextToStore(
std::string_view name,
std::string_view s,
const StorePathSet & references,
RepairFlag repair) override
{ unsupported("addTextToStore"); }
private:
void putBuildSettings(Connection & conn);
public:
BuildResult buildDerivation(const StorePath & drvPath, const BasicDerivation & drv,
BuildMode buildMode) override;
void buildPaths(const std::vector<DerivedPath> & drvPaths, BuildMode buildMode, std::shared_ptr<Store> evalStore) override;
void ensurePath(const StorePath & path) override
{ unsupported("ensurePath"); }
virtual ref<SourceAccessor> getFSAccessor(bool requireValidPath) override
{ unsupported("getFSAccessor"); }
/**
* The default instance would schedule the work on the client side, but
* for consistency with `buildPaths` and `buildDerivation` it should happen
* on the remote side.
*
* We make this fail for now so we can add implement this properly later
* without it being a breaking change.
*/
void repairPath(const StorePath & path) override
{ unsupported("repairPath"); }
void computeFSClosure(const StorePathSet & paths,
StorePathSet & out, bool flipDirection = false,
bool includeOutputs = false, bool includeDerivers = false) override;
StorePathSet queryValidPaths(const StorePathSet & paths,
SubstituteFlag maybeSubstitute = NoSubstitute) override;
void connect() override;
unsigned int getProtocol() override;
/**
* The legacy ssh protocol doesn't support checking for trusted-user.
* Try using ssh-ng:// instead if you want to know.
*/
std::optional<TrustedFlag> isTrustedClient() override
{
return std::nullopt;
}
void queryRealisationUncached(const DrvOutput &,
Callback<std::shared_ptr<const Realisation>> callback) noexcept override
// TODO: Implement
{ unsupported("queryRealisation"); }
};
}

View file

@ -10,7 +10,7 @@
namespace nix {
class Store;
struct StoreDirConfig;
/**
* Reusable serialisers for serialization container types in a
@ -44,8 +44,8 @@ struct LengthPrefixedProtoHelper;
#define LENGTH_PREFIXED_PROTO_HELPER(Inner, T) \
struct LengthPrefixedProtoHelper< Inner, T > \
{ \
static T read(const Store & store, typename Inner::ReadConn conn); \
static void write(const Store & store, typename Inner::WriteConn conn, const T & str); \
static T read(const StoreDirConfig & store, typename Inner::ReadConn conn); \
static void write(const StoreDirConfig & store, typename Inner::WriteConn conn, const T & str); \
private: \
template<typename U> using S = typename Inner::template Serialise<U>; \
}
@ -67,7 +67,7 @@ LENGTH_PREFIXED_PROTO_HELPER(Inner, _X);
template<class Inner, typename T>
std::vector<T>
LengthPrefixedProtoHelper<Inner, std::vector<T>>::read(
const Store & store, typename Inner::ReadConn conn)
const StoreDirConfig & store, typename Inner::ReadConn conn)
{
std::vector<T> resSet;
auto size = readNum<size_t>(conn.from);
@ -80,7 +80,7 @@ LengthPrefixedProtoHelper<Inner, std::vector<T>>::read(
template<class Inner, typename T>
void
LengthPrefixedProtoHelper<Inner, std::vector<T>>::write(
const Store & store, typename Inner::WriteConn conn, const std::vector<T> & resSet)
const StoreDirConfig & store, typename Inner::WriteConn conn, const std::vector<T> & resSet)
{
conn.to << resSet.size();
for (auto & key : resSet) {
@ -91,7 +91,7 @@ LengthPrefixedProtoHelper<Inner, std::vector<T>>::write(
template<class Inner, typename T>
std::set<T>
LengthPrefixedProtoHelper<Inner, std::set<T>>::read(
const Store & store, typename Inner::ReadConn conn)
const StoreDirConfig & store, typename Inner::ReadConn conn)
{
std::set<T> resSet;
auto size = readNum<size_t>(conn.from);
@ -104,7 +104,7 @@ LengthPrefixedProtoHelper<Inner, std::set<T>>::read(
template<class Inner, typename T>
void
LengthPrefixedProtoHelper<Inner, std::set<T>>::write(
const Store & store, typename Inner::WriteConn conn, const std::set<T> & resSet)
const StoreDirConfig & store, typename Inner::WriteConn conn, const std::set<T> & resSet)
{
conn.to << resSet.size();
for (auto & key : resSet) {
@ -115,7 +115,7 @@ LengthPrefixedProtoHelper<Inner, std::set<T>>::write(
template<class Inner, typename K, typename V>
std::map<K, V>
LengthPrefixedProtoHelper<Inner, std::map<K, V>>::read(
const Store & store, typename Inner::ReadConn conn)
const StoreDirConfig & store, typename Inner::ReadConn conn)
{
std::map<K, V> resMap;
auto size = readNum<size_t>(conn.from);
@ -130,7 +130,7 @@ LengthPrefixedProtoHelper<Inner, std::map<K, V>>::read(
template<class Inner, typename K, typename V>
void
LengthPrefixedProtoHelper<Inner, std::map<K, V>>::write(
const Store & store, typename Inner::WriteConn conn, const std::map<K, V> & resMap)
const StoreDirConfig & store, typename Inner::WriteConn conn, const std::map<K, V> & resMap)
{
conn.to << resMap.size();
for (auto & i : resMap) {
@ -142,7 +142,7 @@ LengthPrefixedProtoHelper<Inner, std::map<K, V>>::write(
template<class Inner, typename... Ts>
std::tuple<Ts...>
LengthPrefixedProtoHelper<Inner, std::tuple<Ts...>>::read(
const Store & store, typename Inner::ReadConn conn)
const StoreDirConfig & store, typename Inner::ReadConn conn)
{
return std::tuple<Ts...> {
S<Ts>::read(store, conn)...,
@ -152,7 +152,7 @@ LengthPrefixedProtoHelper<Inner, std::tuple<Ts...>>::read(
template<class Inner, typename... Ts>
void
LengthPrefixedProtoHelper<Inner, std::tuple<Ts...>>::write(
const Store & store, typename Inner::WriteConn conn, const std::tuple<Ts...> & res)
const StoreDirConfig & store, typename Inner::WriteConn conn, const std::tuple<Ts...> & res)
{
std::apply([&]<typename... Us>(const Us &... args) {
(S<Us>::write(store, conn, args), ...);

View file

@ -1,5 +1,5 @@
#include "archive.hh"
#include "fs-accessor.hh"
#include "posix-source-accessor.hh"
#include "store-api.hh"
#include "local-fs-store.hh"
#include "globals.hh"
@ -13,69 +13,53 @@ LocalFSStore::LocalFSStore(const Params & params)
{
}
struct LocalStoreAccessor : public FSAccessor
struct LocalStoreAccessor : PosixSourceAccessor
{
ref<LocalFSStore> store;
bool requireValidPath;
LocalStoreAccessor(ref<LocalFSStore> store) : store(store) { }
LocalStoreAccessor(ref<LocalFSStore> store, bool requireValidPath)
: store(store)
, requireValidPath(requireValidPath)
{ }
Path toRealPath(const Path & path, bool requireValidPath = true)
CanonPath toRealPath(const CanonPath & path)
{
auto storePath = store->toStorePath(path).first;
auto [storePath, rest] = store->toStorePath(path.abs());
if (requireValidPath && !store->isValidPath(storePath))
throw InvalidPath("path '%1%' is not a valid store path", store->printStorePath(storePath));
return store->getRealStoreDir() + std::string(path, store->storeDir.size());
return CanonPath(store->getRealStoreDir()) + storePath.to_string() + CanonPath(rest);
}
FSAccessor::Stat stat(const Path & path) override
std::optional<Stat> maybeLstat(const CanonPath & path) override
{
auto realPath = toRealPath(path);
struct stat st;
if (lstat(realPath.c_str(), &st)) {
if (errno == ENOENT || errno == ENOTDIR) return {Type::tMissing, 0, false};
throw SysError("getting status of '%1%'", path);
}
if (!S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode) && !S_ISLNK(st.st_mode))
throw Error("file '%1%' has unsupported type", path);
return {
S_ISREG(st.st_mode) ? Type::tRegular :
S_ISLNK(st.st_mode) ? Type::tSymlink :
Type::tDirectory,
S_ISREG(st.st_mode) ? (uint64_t) st.st_size : 0,
S_ISREG(st.st_mode) && st.st_mode & S_IXUSR};
return PosixSourceAccessor::maybeLstat(toRealPath(path));
}
StringSet readDirectory(const Path & path) override
DirEntries readDirectory(const CanonPath & path) override
{
auto realPath = toRealPath(path);
auto entries = nix::readDirectory(realPath);
StringSet res;
for (auto & entry : entries)
res.insert(entry.name);
return res;
return PosixSourceAccessor::readDirectory(toRealPath(path));
}
std::string readFile(const Path & path, bool requireValidPath = true) override
void readFile(
const CanonPath & path,
Sink & sink,
std::function<void(uint64_t)> sizeCallback) override
{
return nix::readFile(toRealPath(path, requireValidPath));
return PosixSourceAccessor::readFile(toRealPath(path), sink, sizeCallback);
}
std::string readLink(const Path & path) override
std::string readLink(const CanonPath & path) override
{
return nix::readLink(toRealPath(path));
return PosixSourceAccessor::readLink(toRealPath(path));
}
};
ref<FSAccessor> LocalFSStore::getFSAccessor()
ref<SourceAccessor> LocalFSStore::getFSAccessor(bool requireValidPath)
{
return make_ref<LocalStoreAccessor>(ref<LocalFSStore>(
std::dynamic_pointer_cast<LocalFSStore>(shared_from_this())));
std::dynamic_pointer_cast<LocalFSStore>(shared_from_this())),
requireValidPath);
}
void LocalFSStore::narFromPath(const StorePath & path, Sink & sink)

View file

@ -11,25 +11,21 @@ struct LocalFSStoreConfig : virtual StoreConfig
{
using StoreConfig::StoreConfig;
// FIXME: the (StoreConfig*) cast works around a bug in gcc that causes
// it to omit the call to the Setting constructor. Clang works fine
// either way.
const OptionalPathSetting rootDir{(StoreConfig*) this, std::nullopt,
const OptionalPathSetting rootDir{this, std::nullopt,
"root",
"Directory prefixed to all other paths."};
const PathSetting stateDir{(StoreConfig*) this,
const PathSetting stateDir{this,
rootDir.get() ? *rootDir.get() + "/nix/var/nix" : settings.nixStateDir,
"state",
"Directory where Nix will store state."};
const PathSetting logDir{(StoreConfig*) this,
const PathSetting logDir{this,
rootDir.get() ? *rootDir.get() + "/nix/var/log/nix" : settings.nixLogDir,
"log",
"directory where Nix will store log files."};
const PathSetting realStoreDir{(StoreConfig*) this,
const PathSetting realStoreDir{this,
rootDir.get() ? *rootDir.get() + "/nix/store" : storeDir, "real",
"Physical path of the Nix store."};
};
@ -47,7 +43,7 @@ public:
LocalFSStore(const Params & params);
void narFromPath(const StorePath & path, Sink & sink) override;
ref<FSAccessor> getFSAccessor() override;
ref<SourceAccessor> getFSAccessor(bool requireValidPath) override;
/**
* Creates symlink from the `gcRoot` to the `storePath` and

View file

@ -1,5 +1,7 @@
#include "local-overlay-store.hh"
#include "callback.hh"
#include "realisation.hh"
#include "processes.hh"
#include "url.hh"
#include <regex>

View file

@ -4,12 +4,15 @@
#include "pathlocks.hh"
#include "worker-protocol.hh"
#include "derivations.hh"
#include "realisation.hh"
#include "nar-info.hh"
#include "references.hh"
#include "callback.hh"
#include "topo-sort.hh"
#include "finally.hh"
#include "compression.hh"
#include "signals.hh"
#include "posix-fs-canonicalise.hh"
#include <iostream>
#include <algorithm>
@ -32,7 +35,6 @@
#include <sys/statvfs.h>
#include <sys/mount.h>
#include <sys/ioctl.h>
#include <sys/xattr.h>
#endif
#ifdef __CYGWIN__
@ -586,164 +588,6 @@ void LocalStore::makeStoreWritable()
}
const time_t mtimeStore = 1; /* 1 second into the epoch */
static void canonicaliseTimestampAndPermissions(const Path & path, const struct stat & st)
{
if (!S_ISLNK(st.st_mode)) {
/* Mask out all type related bits. */
mode_t mode = st.st_mode & ~S_IFMT;
if (mode != 0444 && mode != 0555) {
mode = (st.st_mode & S_IFMT)
| 0444
| (st.st_mode & S_IXUSR ? 0111 : 0);
if (chmod(path.c_str(), mode) == -1)
throw SysError("changing mode of '%1%' to %2$o", path, mode);
}
}
if (st.st_mtime != mtimeStore) {
struct timeval times[2];
times[0].tv_sec = st.st_atime;
times[0].tv_usec = 0;
times[1].tv_sec = mtimeStore;
times[1].tv_usec = 0;
#if HAVE_LUTIMES
if (lutimes(path.c_str(), times) == -1)
if (errno != ENOSYS ||
(!S_ISLNK(st.st_mode) && utimes(path.c_str(), times) == -1))
#else
if (!S_ISLNK(st.st_mode) && utimes(path.c_str(), times) == -1)
#endif
throw SysError("changing modification time of '%1%'", path);
}
}
void canonicaliseTimestampAndPermissions(const Path & path)
{
canonicaliseTimestampAndPermissions(path, lstat(path));
}
static void canonicalisePathMetaData_(
const Path & path,
std::optional<std::pair<uid_t, uid_t>> uidRange,
InodesSeen & inodesSeen)
{
checkInterrupt();
#if __APPLE__
/* Remove flags, in particular UF_IMMUTABLE which would prevent
the file from being garbage-collected. FIXME: Use
setattrlist() to remove other attributes as well. */
if (lchflags(path.c_str(), 0)) {
if (errno != ENOTSUP)
throw SysError("clearing flags of path '%1%'", path);
}
#endif
auto st = lstat(path);
/* Really make sure that the path is of a supported type. */
if (!(S_ISREG(st.st_mode) || S_ISDIR(st.st_mode) || S_ISLNK(st.st_mode)))
throw Error("file '%1%' has an unsupported type", path);
#if __linux__
/* Remove extended attributes / ACLs. */
ssize_t eaSize = llistxattr(path.c_str(), nullptr, 0);
if (eaSize < 0) {
if (errno != ENOTSUP && errno != ENODATA)
throw SysError("querying extended attributes of '%s'", path);
} else if (eaSize > 0) {
std::vector<char> eaBuf(eaSize);
if ((eaSize = llistxattr(path.c_str(), eaBuf.data(), eaBuf.size())) < 0)
throw SysError("querying extended attributes of '%s'", path);
for (auto & eaName: tokenizeString<Strings>(std::string(eaBuf.data(), eaSize), std::string("\000", 1))) {
if (settings.ignoredAcls.get().count(eaName)) continue;
if (lremovexattr(path.c_str(), eaName.c_str()) == -1)
throw SysError("removing extended attribute '%s' from '%s'", eaName, path);
}
}
#endif
/* Fail if the file is not owned by the build user. This prevents
us from messing up the ownership/permissions of files
hard-linked into the output (e.g. "ln /etc/shadow $out/foo").
However, ignore files that we chown'ed ourselves previously to
ensure that we don't fail on hard links within the same build
(i.e. "touch $out/foo; ln $out/foo $out/bar"). */
if (uidRange && (st.st_uid < uidRange->first || st.st_uid > uidRange->second)) {
if (S_ISDIR(st.st_mode) || !inodesSeen.count(Inode(st.st_dev, st.st_ino)))
throw BuildError("invalid ownership on file '%1%'", path);
mode_t mode = st.st_mode & ~S_IFMT;
assert(S_ISLNK(st.st_mode) || (st.st_uid == geteuid() && (mode == 0444 || mode == 0555) && st.st_mtime == mtimeStore));
return;
}
inodesSeen.insert(Inode(st.st_dev, st.st_ino));
canonicaliseTimestampAndPermissions(path, st);
/* Change ownership to the current uid. If it's a symlink, use
lchown if available, otherwise don't bother. Wrong ownership
of a symlink doesn't matter, since the owning user can't change
the symlink and can't delete it because the directory is not
writable. The only exception is top-level paths in the Nix
store (since that directory is group-writable for the Nix build
users group); we check for this case below. */
if (st.st_uid != geteuid()) {
#if HAVE_LCHOWN
if (lchown(path.c_str(), geteuid(), getegid()) == -1)
#else
if (!S_ISLNK(st.st_mode) &&
chown(path.c_str(), geteuid(), getegid()) == -1)
#endif
throw SysError("changing owner of '%1%' to %2%",
path, geteuid());
}
if (S_ISDIR(st.st_mode)) {
DirEntries entries = readDirectory(path);
for (auto & i : entries)
canonicalisePathMetaData_(path + "/" + i.name, uidRange, inodesSeen);
}
}
void canonicalisePathMetaData(
const Path & path,
std::optional<std::pair<uid_t, uid_t>> uidRange,
InodesSeen & inodesSeen)
{
canonicalisePathMetaData_(path, uidRange, inodesSeen);
/* On platforms that don't have lchown(), the top-level path can't
be a symlink, since we can't change its ownership. */
auto st = lstat(path);
if (st.st_uid != geteuid()) {
assert(S_ISLNK(st.st_mode));
throw Error("wrong ownership of top-level store path '%1%'", path);
}
}
void canonicalisePathMetaData(const Path & path,
std::optional<std::pair<uid_t, uid_t>> uidRange)
{
InodesSeen inodesSeen;
canonicalisePathMetaData(path, uidRange, inodesSeen);
}
void LocalStore::registerDrvOutput(const Realisation & info, CheckSigsFlag checkSigs)
{
experimentalFeatureSettings.require(Xp::CaDerivations);
@ -1117,7 +961,7 @@ void LocalStore::registerValidPaths(const ValidPathInfos & infos)
StorePathSet paths;
for (auto & [_, i] : infos) {
assert(i.narHash.type == htSHA256);
assert(i.narHash.algo == HashAlgorithm::SHA256);
if (isValidPath_(*state, i.path))
updatePathInfo(*state, i);
else
@ -1206,7 +1050,7 @@ void LocalStore::addToStore(const ValidPathInfo & info, Source & source,
bool narRead = false;
Finally cleanup = [&]() {
if (!narRead) {
ParseSink sink;
NullParseSink sink;
parseDump(sink, source);
}
};
@ -1231,7 +1075,7 @@ void LocalStore::addToStore(const ValidPathInfo & info, Source & source,
/* While restoring the path from the NAR, compute the hash
of the NAR. */
HashSink hashSink(htSHA256);
HashSink hashSink(HashAlgorithm::SHA256);
TeeSource wrapperSource { source, hashSink };
@ -1242,7 +1086,7 @@ void LocalStore::addToStore(const ValidPathInfo & info, Source & source,
if (hashResult.first != info.narHash)
throw Error("hash mismatch importing path '%s';\n specified: %s\n got: %s",
printStorePath(info.path), info.narHash.to_string(HashFormat::Base32, true), hashResult.first.to_string(HashFormat::Base32, true));
printStorePath(info.path), info.narHash.to_string(HashFormat::Nix32, true), hashResult.first.to_string(HashFormat::Nix32, true));
if (hashResult.second != info.narSize)
throw Error("size mismatch importing path '%s';\n specified: %s\n got: %s",
@ -1252,14 +1096,14 @@ void LocalStore::addToStore(const ValidPathInfo & info, Source & source,
auto & specified = *info.ca;
auto actualHash = hashCAPath(
specified.method,
specified.hash.type,
specified.hash.algo,
info.path
);
if (specified.hash != actualHash.hash) {
throw Error("ca hash mismatch importing path '%s';\n specified: %s\n got: %s",
printStorePath(info.path),
specified.hash.to_string(HashFormat::Base32, true),
actualHash.hash.to_string(HashFormat::Base32, true));
specified.hash.to_string(HashFormat::Nix32, true),
actualHash.hash.to_string(HashFormat::Nix32, true));
}
}
@ -1278,7 +1122,7 @@ void LocalStore::addToStore(const ValidPathInfo & info, Source & source,
StorePath LocalStore::addToStoreFromDump(Source & source0, std::string_view name,
FileIngestionMethod method, HashType hashAlgo, RepairFlag repair, const StorePathSet & references)
FileIngestionMethod method, HashAlgorithm hashAlgo, RepairFlag repair, const StorePathSet & references)
{
/* For computing the store path. */
auto hashSink = std::make_unique<HashSink>(hashAlgo);
@ -1382,8 +1226,8 @@ StorePath LocalStore::addToStoreFromDump(Source & source0, std::string_view name
/* For computing the nar hash. In recursive SHA-256 mode, this
is the same as the store hash, so no need to do it again. */
auto narHash = std::pair { hash, size };
if (method != FileIngestionMethod::Recursive || hashAlgo != htSHA256) {
HashSink narSink { htSHA256 };
if (method != FileIngestionMethod::Recursive || hashAlgo != HashAlgorithm::SHA256) {
HashSink narSink { HashAlgorithm::SHA256 };
dumpPath(realPath, narSink);
narHash = narSink.finish();
}
@ -1414,7 +1258,7 @@ StorePath LocalStore::addTextToStore(
std::string_view s,
const StorePathSet & references, RepairFlag repair)
{
auto hash = hashString(htSHA256, s);
auto hash = hashString(HashAlgorithm::SHA256, s);
auto dstPath = makeTextPath(name, TextInfo {
.hash = hash,
.references = references,
@ -1440,7 +1284,7 @@ StorePath LocalStore::addTextToStore(
StringSink sink;
dumpString(s, sink);
auto narHash = hashString(htSHA256, sink.s);
auto narHash = hashString(HashAlgorithm::SHA256, sink.s);
optimisePath(realPath, repair);
@ -1523,7 +1367,7 @@ bool LocalStore::verifyStore(bool checkContents, RepairFlag repair)
for (auto & link : readDirectory(linksDir)) {
printMsg(lvlTalkative, "checking contents of '%s'", link.name);
Path linkPath = linksDir + "/" + link.name;
std::string hash = hashPath(htSHA256, linkPath).first.to_string(HashFormat::Base32, false);
std::string hash = hashPath(HashAlgorithm::SHA256, linkPath).first.to_string(HashFormat::Nix32, false);
if (hash != link.name) {
printError("link '%s' was modified! expected hash '%s', got '%s'",
linkPath, link.name, hash);
@ -1540,7 +1384,7 @@ bool LocalStore::verifyStore(bool checkContents, RepairFlag repair)
printInfo("checking store hashes...");
Hash nullHash(htSHA256);
Hash nullHash(HashAlgorithm::SHA256);
for (auto & i : validPaths) {
try {
@ -1549,14 +1393,14 @@ bool LocalStore::verifyStore(bool checkContents, RepairFlag repair)
/* Check the content hash (optionally - slow). */
printMsg(lvlTalkative, "checking contents of '%s'", printStorePath(i));
auto hashSink = HashSink(info->narHash.type);
auto hashSink = HashSink(info->narHash.algo);
dumpPath(Store::toRealPath(i), hashSink);
auto current = hashSink.finish();
if (info->narHash != nullHash && info->narHash != current.first) {
printError("path '%s' was modified! expected hash '%s', got '%s'",
printStorePath(i), info->narHash.to_string(HashFormat::Base32, true), current.first.to_string(HashFormat::Base32, true));
printStorePath(i), info->narHash.to_string(HashFormat::Nix32, true), current.first.to_string(HashFormat::Nix32, true));
if (repair) repairPath(i); else errors = true;
} else {
@ -1868,20 +1712,20 @@ void LocalStore::queryRealisationUncached(const DrvOutput & id,
}
ContentAddress LocalStore::hashCAPath(
const ContentAddressMethod & method, const HashType & hashType,
const ContentAddressMethod & method, const HashAlgorithm & hashAlgo,
const StorePath & path)
{
return hashCAPath(method, hashType, Store::toRealPath(path), path.hashPart());
return hashCAPath(method, hashAlgo, Store::toRealPath(path), path.hashPart());
}
ContentAddress LocalStore::hashCAPath(
const ContentAddressMethod & method,
const HashType & hashType,
const HashAlgorithm & hashAlgo,
const Path & path,
const std::string_view pathHash
)
{
HashModuloSink caSink ( hashType, std::string(pathHash) );
HashModuloSink caSink ( hashAlgo, std::string(pathHash) );
std::visit(overloaded {
[&](const TextIngestionMethod &) {
readFile(path, caSink);

View file

@ -7,7 +7,6 @@
#include "store-api.hh"
#include "indirect-root-store.hh"
#include "sync.hh"
#include "util.hh"
#include <chrono>
#include <future>
@ -40,12 +39,12 @@ struct LocalStoreConfig : virtual LocalFSStoreConfig
{
using LocalFSStoreConfig::LocalFSStoreConfig;
Setting<bool> requireSigs{(StoreConfig*) this,
Setting<bool> requireSigs{this,
settings.requireSigs,
"require-sigs",
"Whether store paths copied into this store should have a trusted signature."};
Setting<bool> readOnly{(StoreConfig*) this,
Setting<bool> readOnly{this,
false,
"read-only",
R"(
@ -179,7 +178,7 @@ public:
RepairFlag repair, CheckSigsFlag checkSigs) override;
StorePath addToStoreFromDump(Source & dump, std::string_view name,
FileIngestionMethod method, HashType hashAlgo, RepairFlag repair, const StorePathSet & references) override;
FileIngestionMethod method, HashAlgorithm hashAlgo, RepairFlag repair, const StorePathSet & references) override;
StorePath addTextToStore(
std::string_view name,
@ -382,12 +381,12 @@ private:
// XXX: Make a generic `Store` method
ContentAddress hashCAPath(
const ContentAddressMethod & method,
const HashType & hashType,
const HashAlgorithm & hashAlgo,
const StorePath & path);
ContentAddress hashCAPath(
const ContentAddressMethod & method,
const HashType & hashType,
const HashAlgorithm & hashAlgo,
const Path & path,
const std::string_view pathHash
);
@ -400,38 +399,4 @@ private:
friend struct DerivationGoal;
};
typedef std::pair<dev_t, ino_t> Inode;
typedef std::set<Inode> InodesSeen;
/**
* "Fix", or canonicalise, the meta-data of the files in a store path
* after it has been built. In particular:
*
* - the last modification date on each file is set to 1 (i.e.,
* 00:00:01 1/1/1970 UTC)
*
* - the permissions are set of 444 or 555 (i.e., read-only with or
* without execute permission; setuid bits etc. are cleared)
*
* - the owner and group are set to the Nix user and group, if we're
* running as root.
*
* If uidRange is not empty, this function will throw an error if it
* encounters files owned by a user outside of the closed interval
* [uidRange->first, uidRange->second].
*/
void canonicalisePathMetaData(
const Path & path,
std::optional<std::pair<uid_t, uid_t>> uidRange,
InodesSeen & inodesSeen);
void canonicalisePathMetaData(
const Path & path,
std::optional<std::pair<uid_t, uid_t>> uidRange);
void canonicaliseTimestampAndPermissions(const Path & path);
MakeError(PathInUse, Error);
}

View file

@ -59,7 +59,7 @@ $(d)/build.cc:
clean-files += $(d)/schema.sql.gen.hh $(d)/ca-specific-schema.sql.gen.hh
$(eval $(call install-file-in, $(d)/nix-store.pc, $(libdir)/pkgconfig, 0644))
$(eval $(call install-file-in, $(buildprefix)$(d)/nix-store.pc, $(libdir)/pkgconfig, 0644))
$(foreach i, $(wildcard src/libstore/builtins/*.hh), \
$(eval $(call install-file-in, $(i), $(includedir)/nix/builtins, 0644)))

View file

@ -1,4 +1,5 @@
#include "lock.hh"
#include "file-system.hh"
#include "globals.hh"
#include "pathlocks.hh"

View file

@ -1,5 +1,4 @@
#include "machines.hh"
#include "util.hh"
#include "globals.hh"
#include "store-api.hh"

View file

@ -43,7 +43,7 @@ std::map<StorePath, StorePath> makeContentAddressed(
sink.s = rewriteStrings(sink.s, rewrites);
HashModuloSink hashModuloSink(htSHA256, oldHashPart);
HashModuloSink hashModuloSink(HashAlgorithm::SHA256, oldHashPart);
hashModuloSink(sink.s);
auto narModuloHash = hashModuloSink.finish().first;
@ -66,7 +66,7 @@ std::map<StorePath, StorePath> makeContentAddressed(
rsink2(sink.s);
rsink2.flush();
info.narHash = hashString(htSHA256, sink2.s);
info.narHash = hashString(HashAlgorithm::SHA256, sink2.s);
info.narSize = sink.s.size();
StringSource source(sink2.s);

View file

@ -4,6 +4,7 @@
#include "local-store.hh"
#include "store-api.hh"
#include "thread-pool.hh"
#include "realisation.hh"
#include "topo-sort.hh"
#include "callback.hh"
#include "closure.hh"

View file

@ -0,0 +1,18 @@
R"(
**Store URL format**: `mounted-ssh-ng://[username@]hostname`
Experimental store type that allows full access to a Nix store on a remote machine,
and additionally requires that store be mounted in the local file system.
The mounting of that store is not managed by Nix, and must by managed manually.
It could be accomplished with SSHFS or NFS, for example.
The local file system is used to optimize certain operations.
For example, rather than serializing Nix archives and sending over the Nix channel,
we can directly access the file system data via the mount-point.
The local file system is also used to make certain operations possible that wouldn't otherwise be.
For example, persistent GC roots can be created if they reside on the same file system as the remote store:
the remote side will create the symlinks necessary to avoid race conditions.
)"

View file

@ -11,13 +11,7 @@ namespace nix {
struct NarMember
{
FSAccessor::Type type = FSAccessor::Type::tMissing;
bool isExecutable = false;
/* If this is a regular file, position of the contents of this
file in the NAR. */
uint64_t start = 0, size = 0;
SourceAccessor::Stat stat;
std::string target;
@ -25,7 +19,7 @@ struct NarMember
std::map<std::string, NarMember> children;
};
struct NarAccessor : public FSAccessor
struct NarAccessor : public SourceAccessor
{
std::optional<const std::string> nar;
@ -57,7 +51,7 @@ struct NarAccessor : public FSAccessor
acc.root = std::move(member);
parents.push(&acc.root);
} else {
if (parents.top()->type != FSAccessor::Type::tDirectory)
if (parents.top()->stat.type != Type::tDirectory)
throw Error("NAR file missing parent directory of path '%s'", path);
auto result = parents.top()->children.emplace(baseNameOf(path), std::move(member));
parents.push(&result.first->second);
@ -66,12 +60,22 @@ struct NarAccessor : public FSAccessor
void createDirectory(const Path & path) override
{
createMember(path, {FSAccessor::Type::tDirectory, false, 0, 0});
createMember(path, NarMember{ .stat = {
.type = Type::tDirectory,
.fileSize = 0,
.isExecutable = false,
.narOffset = 0
} });
}
void createRegularFile(const Path & path) override
{
createMember(path, {FSAccessor::Type::tRegular, false, 0, 0});
createMember(path, NarMember{ .stat = {
.type = Type::tRegular,
.fileSize = 0,
.isExecutable = false,
.narOffset = 0
} });
}
void closeRegularFile() override
@ -79,14 +83,14 @@ struct NarAccessor : public FSAccessor
void isExecutable() override
{
parents.top()->isExecutable = true;
parents.top()->stat.isExecutable = true;
}
void preallocateContents(uint64_t size) override
{
assert(size <= std::numeric_limits<uint64_t>::max());
parents.top()->size = (uint64_t) size;
parents.top()->start = pos;
auto & st = parents.top()->stat;
st.fileSize = size;
st.narOffset = pos;
}
void receiveContents(std::string_view data) override
@ -95,7 +99,9 @@ struct NarAccessor : public FSAccessor
void createSymlink(const Path & path, const std::string & target) override
{
createMember(path,
NarMember{FSAccessor::Type::tSymlink, false, 0, 0, target});
NarMember{
.stat = {.type = Type::tSymlink},
.target = target});
}
size_t read(char * data, size_t len) override
@ -130,18 +136,19 @@ struct NarAccessor : public FSAccessor
std::string type = v["type"];
if (type == "directory") {
member.type = FSAccessor::Type::tDirectory;
for (auto i = v["entries"].begin(); i != v["entries"].end(); ++i) {
std::string name = i.key();
recurse(member.children[name], i.value());
member.stat = {.type = Type::tDirectory};
for (const auto &[name, function] : v["entries"].items()) {
recurse(member.children[name], function);
}
} else if (type == "regular") {
member.type = FSAccessor::Type::tRegular;
member.size = v["size"];
member.isExecutable = v.value("executable", false);
member.start = v["narOffset"];
member.stat = {
.type = Type::tRegular,
.fileSize = v["size"],
.isExecutable = v.value("executable", false),
.narOffset = v["narOffset"]
};
} else if (type == "symlink") {
member.type = FSAccessor::Type::tSymlink;
member.stat = {.type = Type::tSymlink};
member.target = v.value("target", "");
} else return;
};
@ -150,134 +157,122 @@ struct NarAccessor : public FSAccessor
recurse(root, v);
}
NarMember * find(const Path & path)
NarMember * find(const CanonPath & path)
{
Path canon = path == "" ? "" : canonPath(path);
NarMember * current = &root;
auto end = path.end();
for (auto it = path.begin(); it != end; ) {
// because it != end, the remaining component is non-empty so we need
// a directory
if (current->type != FSAccessor::Type::tDirectory) return nullptr;
// skip slash (canonPath above ensures that this is always a slash)
assert(*it == '/');
it += 1;
// lookup current component
auto next = std::find(it, end, '/');
auto child = current->children.find(std::string(it, next));
for (const auto & i : path) {
if (current->stat.type != Type::tDirectory) return nullptr;
auto child = current->children.find(std::string(i));
if (child == current->children.end()) return nullptr;
current = &child->second;
it = next;
}
return current;
}
NarMember & get(const Path & path) {
NarMember & get(const CanonPath & path) {
auto result = find(path);
if (result == nullptr)
if (!result)
throw Error("NAR file does not contain path '%1%'", path);
return *result;
}
Stat stat(const Path & path) override
std::optional<Stat> maybeLstat(const CanonPath & path) override
{
auto i = find(path);
if (i == nullptr)
return {FSAccessor::Type::tMissing, 0, false};
return {i->type, i->size, i->isExecutable, i->start};
if (!i)
return std::nullopt;
return i->stat;
}
StringSet readDirectory(const Path & path) override
DirEntries readDirectory(const CanonPath & path) override
{
auto i = get(path);
if (i.type != FSAccessor::Type::tDirectory)
if (i.stat.type != Type::tDirectory)
throw Error("path '%1%' inside NAR file is not a directory", path);
StringSet res;
for (auto & child : i.children)
res.insert(child.first);
DirEntries res;
for (const auto & child : i.children)
res.insert_or_assign(child.first, std::nullopt);
return res;
}
std::string readFile(const Path & path, bool requireValidPath = true) override
std::string readFile(const CanonPath & path) override
{
auto i = get(path);
if (i.type != FSAccessor::Type::tRegular)
if (i.stat.type != Type::tRegular)
throw Error("path '%1%' inside NAR file is not a regular file", path);
if (getNarBytes) return getNarBytes(i.start, i.size);
if (getNarBytes) return getNarBytes(*i.stat.narOffset, *i.stat.fileSize);
assert(nar);
return std::string(*nar, i.start, i.size);
return std::string(*nar, *i.stat.narOffset, *i.stat.fileSize);
}
std::string readLink(const Path & path) override
std::string readLink(const CanonPath & path) override
{
auto i = get(path);
if (i.type != FSAccessor::Type::tSymlink)
if (i.stat.type != Type::tSymlink)
throw Error("path '%1%' inside NAR file is not a symlink", path);
return i.target;
}
};
ref<FSAccessor> makeNarAccessor(std::string && nar)
ref<SourceAccessor> makeNarAccessor(std::string && nar)
{
return make_ref<NarAccessor>(std::move(nar));
}
ref<FSAccessor> makeNarAccessor(Source & source)
ref<SourceAccessor> makeNarAccessor(Source & source)
{
return make_ref<NarAccessor>(source);
}
ref<FSAccessor> makeLazyNarAccessor(const std::string & listing,
ref<SourceAccessor> makeLazyNarAccessor(const std::string & listing,
GetNarBytes getNarBytes)
{
return make_ref<NarAccessor>(listing, getNarBytes);
}
using nlohmann::json;
json listNar(ref<FSAccessor> accessor, const Path & path, bool recurse)
json listNar(ref<SourceAccessor> accessor, const CanonPath & path, bool recurse)
{
auto st = accessor->stat(path);
auto st = accessor->lstat(path);
json obj = json::object();
switch (st.type) {
case FSAccessor::Type::tRegular:
case SourceAccessor::Type::tRegular:
obj["type"] = "regular";
obj["size"] = st.fileSize;
if (st.fileSize)
obj["size"] = *st.fileSize;
if (st.isExecutable)
obj["executable"] = true;
if (st.narOffset)
obj["narOffset"] = st.narOffset;
if (st.narOffset && *st.narOffset)
obj["narOffset"] = *st.narOffset;
break;
case FSAccessor::Type::tDirectory:
case SourceAccessor::Type::tDirectory:
obj["type"] = "directory";
{
obj["entries"] = json::object();
json &res2 = obj["entries"];
for (auto & name : accessor->readDirectory(path)) {
for (const auto & [name, type] : accessor->readDirectory(path)) {
if (recurse) {
res2[name] = listNar(accessor, path + "/" + name, true);
res2[name] = listNar(accessor, path + name, true);
} else
res2[name] = json::object();
}
}
break;
case FSAccessor::Type::tSymlink:
case SourceAccessor::Type::tSymlink:
obj["type"] = "symlink";
obj["target"] = accessor->readLink(path);
break;
case FSAccessor::Type::tMissing:
default:
throw Error("path '%s' does not exist in NAR", path);
case SourceAccessor::Type::tMisc:
assert(false); // cannot happen for NARs
}
return obj;
}

View file

@ -1,10 +1,11 @@
#pragma once
///@file
#include "source-accessor.hh"
#include <functional>
#include <nlohmann/json_fwd.hpp>
#include "fs-accessor.hh"
namespace nix {
@ -14,9 +15,9 @@ struct Source;
* Return an object that provides access to the contents of a NAR
* file.
*/
ref<FSAccessor> makeNarAccessor(std::string && nar);
ref<SourceAccessor> makeNarAccessor(std::string && nar);
ref<FSAccessor> makeNarAccessor(Source & source);
ref<SourceAccessor> makeNarAccessor(Source & source);
/**
* Create a NAR accessor from a NAR listing (in the format produced by
@ -24,9 +25,9 @@ ref<FSAccessor> makeNarAccessor(Source & source);
* readFile() method of the accessor to get the contents of files
* inside the NAR.
*/
typedef std::function<std::string(uint64_t, uint64_t)> GetNarBytes;
using GetNarBytes = std::function<std::string(uint64_t, uint64_t)>;
ref<FSAccessor> makeLazyNarAccessor(
ref<SourceAccessor> makeLazyNarAccessor(
const std::string & listing,
GetNarBytes getNarBytes);
@ -34,6 +35,6 @@ ref<FSAccessor> makeLazyNarAccessor(
* Write a JSON representation of the contents of a NAR (except file
* contents).
*/
nlohmann::json listNar(ref<FSAccessor> accessor, const Path & path, bool recurse);
nlohmann::json listNar(ref<SourceAccessor> accessor, const CanonPath & path, bool recurse);
}

View file

@ -1,4 +1,5 @@
#include "nar-info-disk-cache.hh"
#include "users.hh"
#include "sync.hh"
#include "sqlite.hh"
#include "globals.hh"
@ -332,9 +333,9 @@ public:
(std::string(info->path.name()))
(narInfo ? narInfo->url : "", narInfo != 0)
(narInfo ? narInfo->compression : "", narInfo != 0)
(narInfo && narInfo->fileHash ? narInfo->fileHash->to_string(HashFormat::Base32, true) : "", narInfo && narInfo->fileHash)
(narInfo && narInfo->fileHash ? narInfo->fileHash->to_string(HashFormat::Nix32, true) : "", narInfo && narInfo->fileHash)
(narInfo ? narInfo->fileSize : 0, narInfo != 0 && narInfo->fileSize)
(info->narHash.to_string(HashFormat::Base32, true))
(info->narHash.to_string(HashFormat::Nix32, true))
(info->narSize)
(concatStringsSep(" ", info->shortRefs()))
(info->deriver ? std::string(info->deriver->to_string()) : "", (bool) info->deriver)

View file

@ -4,6 +4,15 @@
namespace nix {
GENERATE_CMP_EXT(
,
NarInfo,
me->url,
me->compression,
me->fileHash,
me->fileSize,
static_cast<const ValidPathInfo &>(*me));
NarInfo::NarInfo(const Store & store, const std::string & s, const std::string & whence)
: ValidPathInfo(StorePath(StorePath::dummy), Hash(Hash::dummy)) // FIXME: hack
{
@ -29,12 +38,12 @@ NarInfo::NarInfo(const Store & store, const std::string & s, const std::string &
while (pos < s.size()) {
size_t colon = s.find(':', pos);
if (colon == std::string::npos) throw corrupt("expecting ':'");
if (colon == s.npos) throw corrupt("expecting ':'");
std::string name(s, pos, colon - pos);
size_t eol = s.find('\n', colon + 2);
if (eol == std::string::npos) throw corrupt("expecting '\\n'");
if (eol == s.npos) throw corrupt("expecting '\\n'");
std::string value(s, colon + 2, eol - colon - 2);
@ -104,11 +113,11 @@ std::string NarInfo::to_string(const Store & store) const
res += "URL: " + url + "\n";
assert(compression != "");
res += "Compression: " + compression + "\n";
assert(fileHash && fileHash->type == htSHA256);
res += "FileHash: " + fileHash->to_string(HashFormat::Base32, true) + "\n";
assert(fileHash && fileHash->algo == HashAlgorithm::SHA256);
res += "FileHash: " + fileHash->to_string(HashFormat::Nix32, true) + "\n";
res += "FileSize: " + std::to_string(fileSize) + "\n";
assert(narHash.type == htSHA256);
res += "NarHash: " + narHash.to_string(HashFormat::Base32, true) + "\n";
assert(narHash.algo == HashAlgorithm::SHA256);
res += "NarHash: " + narHash.to_string(HashFormat::Nix32, true) + "\n";
res += "NarSize: " + std::to_string(narSize) + "\n";
res += "References: " + concatStringsSep(" ", shortRefs()) + "\n";
@ -125,4 +134,59 @@ std::string NarInfo::to_string(const Store & store) const
return res;
}
nlohmann::json NarInfo::toJSON(
const Store & store,
bool includeImpureInfo,
HashFormat hashFormat) const
{
using nlohmann::json;
auto jsonObject = ValidPathInfo::toJSON(store, includeImpureInfo, hashFormat);
if (includeImpureInfo) {
if (!url.empty())
jsonObject["url"] = url;
if (!compression.empty())
jsonObject["compression"] = compression;
if (fileHash)
jsonObject["downloadHash"] = fileHash->to_string(hashFormat, true);
if (fileSize)
jsonObject["downloadSize"] = fileSize;
}
return jsonObject;
}
NarInfo NarInfo::fromJSON(
const Store & store,
const StorePath & path,
const nlohmann::json & json)
{
using nlohmann::detail::value_t;
NarInfo res {
ValidPathInfo {
path,
UnkeyedValidPathInfo::fromJSON(store, json),
}
};
if (json.contains("url"))
res.url = ensureType(valueAt(json, "url"), value_t::string);
if (json.contains("compression"))
res.compression = ensureType(valueAt(json, "compression"), value_t::string);
if (json.contains("downloadHash"))
res.fileHash = Hash::parseAny(
static_cast<const std::string &>(
ensureType(valueAt(json, "downloadHash"), value_t::string)),
std::nullopt);
if (json.contains("downloadSize"))
res.fileSize = ensureType(valueAt(json, "downloadSize"), value_t::number_integer);
return res;
}
}

View file

@ -17,14 +17,25 @@ struct NarInfo : ValidPathInfo
uint64_t fileSize = 0;
NarInfo() = delete;
NarInfo(const Store & store, std::string && name, ContentAddressWithReferences && ca, Hash narHash)
NarInfo(const Store & store, std::string name, ContentAddressWithReferences ca, Hash narHash)
: ValidPathInfo(store, std::move(name), std::move(ca), narHash)
{ }
NarInfo(StorePath && path, Hash narHash) : ValidPathInfo(std::move(path), narHash) { }
NarInfo(StorePath path, Hash narHash) : ValidPathInfo(std::move(path), narHash) { }
NarInfo(const ValidPathInfo & info) : ValidPathInfo(info) { }
NarInfo(const Store & store, const std::string & s, const std::string & whence);
DECLARE_CMP(NarInfo);
std::string to_string(const Store & store) const;
nlohmann::json toJSON(
const Store & store,
bool includeImpureInfo,
HashFormat hashFormat) const override;
static NarInfo fromJSON(
const Store & store,
const StorePath & path,
const nlohmann::json & json);
};
}

View file

@ -1,6 +1,7 @@
#include "util.hh"
#include "local-store.hh"
#include "globals.hh"
#include "signals.hh"
#include "posix-fs-canonicalise.hh"
#include <cstdlib>
#include <cstring>
@ -145,17 +146,17 @@ void LocalStore::optimisePath_(Activity * act, OptimiseStats & stats,
Also note that if `path' is a symlink, then we're hashing the
contents of the symlink (i.e. the result of readlink()), not
the contents of the target (which may not even exist). */
Hash hash = hashPath(htSHA256, path).first;
debug("'%1%' has hash '%2%'", path, hash.to_string(HashFormat::Base32, true));
Hash hash = hashPath(HashAlgorithm::SHA256, path).first;
debug("'%1%' has hash '%2%'", path, hash.to_string(HashFormat::Nix32, true));
/* Check if this is a known hash. */
Path linkPath = linksDir + "/" + hash.to_string(HashFormat::Base32, false);
Path linkPath = linksDir + "/" + hash.to_string(HashFormat::Nix32, false);
/* Maybe delete the link, if it has been corrupted. */
if (pathExists(linkPath)) {
auto stLink = lstat(linkPath);
if (st.st_size != stLink.st_size
|| (repair && hash != hashPath(htSHA256, linkPath).first))
|| (repair && hash != hashPath(HashAlgorithm::SHA256, linkPath).first))
{
// XXX: Consider overwriting linkPath with our valid version.
warn("removing corrupted link '%s'", linkPath);

View file

@ -132,6 +132,41 @@ bool ParsedDerivation::useUidRange() const
static std::regex shVarName("[A-Za-z_][A-Za-z0-9_]*");
/**
* Write a JSON representation of store object metadata, such as the
* hash and the references.
*/
static nlohmann::json pathInfoToJSON(
Store & store,
const StorePathSet & storePaths)
{
nlohmann::json::array_t jsonList = nlohmann::json::array();
for (auto & storePath : storePaths) {
auto info = store.queryPathInfo(storePath);
auto & jsonPath = jsonList.emplace_back(
info->toJSON(store, false, HashFormat::Nix32));
// Add the path to the object whose metadata we are including.
jsonPath["path"] = store.printStorePath(storePath);
jsonPath["valid"] = true;
jsonPath["closureSize"] = ({
uint64_t totalNarSize = 0;
StorePathSet closure;
store.computeFSClosure(info->path, closure, false, false);
for (auto & p : closure) {
auto info = store.queryPathInfo(p);
totalNarSize += info->narSize;
}
totalNarSize;
});
}
return jsonList;
}
std::optional<nlohmann::json> ParsedDerivation::prepareStructuredAttrs(Store & store, const StorePathSet & inputPaths)
{
auto structuredAttrs = getStructuredAttrs();
@ -152,8 +187,8 @@ std::optional<nlohmann::json> ParsedDerivation::prepareStructuredAttrs(Store & s
StorePathSet storePaths;
for (auto & p : *i)
storePaths.insert(store.parseStorePath(p.get<std::string>()));
json[i.key()] = store.pathInfoToJSON(
store.exportReferences(storePaths, inputPaths), false, true);
json[i.key()] = pathInfoToJSON(store,
store.exportReferences(storePaths, inputPaths));
}
}

View file

@ -1,5 +1,8 @@
#include <nlohmann/json.hpp>
#include "path-info.hh"
#include "store-api.hh"
#include "json-utils.hh"
namespace nix {
@ -28,9 +31,9 @@ std::string ValidPathInfo::fingerprint(const Store & store) const
throw Error("cannot calculate fingerprint of path '%s' because its size is not known",
store.printStorePath(path));
return
"1;" + store.printStorePath(path) + ";"
+ narHash.to_string(HashFormat::Base32, true) + ";"
+ std::to_string(narSize) + ";"
"1;" + store.printStorePath(path) + ";"
+ narHash.to_string(HashFormat::Nix32, true) + ";"
+ std::to_string(narSize) + ";"
+ concatStringsSep(",", store.printStorePathSet(references));
}
@ -144,4 +147,94 @@ ValidPathInfo::ValidPathInfo(
}, std::move(ca).raw);
}
nlohmann::json UnkeyedValidPathInfo::toJSON(
const Store & store,
bool includeImpureInfo,
HashFormat hashFormat) const
{
using nlohmann::json;
auto jsonObject = json::object();
jsonObject["narHash"] = narHash.to_string(hashFormat, true);
jsonObject["narSize"] = narSize;
{
auto& jsonRefs = (jsonObject["references"] = json::array());
for (auto & ref : references)
jsonRefs.emplace_back(store.printStorePath(ref));
}
if (ca)
jsonObject["ca"] = renderContentAddress(ca);
if (includeImpureInfo) {
if (deriver)
jsonObject["deriver"] = store.printStorePath(*deriver);
if (registrationTime)
jsonObject["registrationTime"] = registrationTime;
if (ultimate)
jsonObject["ultimate"] = ultimate;
if (!sigs.empty()) {
for (auto & sig : sigs)
jsonObject["signatures"].push_back(sig);
}
}
return jsonObject;
}
UnkeyedValidPathInfo UnkeyedValidPathInfo::fromJSON(
const Store & store,
const nlohmann::json & json)
{
using nlohmann::detail::value_t;
UnkeyedValidPathInfo res {
Hash(Hash::dummy),
};
ensureType(json, value_t::object);
res.narHash = Hash::parseAny(
static_cast<const std::string &>(
ensureType(valueAt(json, "narHash"), value_t::string)),
std::nullopt);
res.narSize = ensureType(valueAt(json, "narSize"), value_t::number_integer);
try {
auto & references = ensureType(valueAt(json, "references"), value_t::array);
for (auto & input : references)
res.references.insert(store.parseStorePath(static_cast<const std::string &>
(input)));
} catch (Error & e) {
e.addTrace({}, "while reading key 'references'");
throw;
}
if (json.contains("ca"))
res.ca = ContentAddress::parse(
static_cast<const std::string &>(
ensureType(valueAt(json, "ca"), value_t::string)));
if (json.contains("deriver"))
res.deriver = store.parseStorePath(
static_cast<const std::string &>(
ensureType(valueAt(json, "deriver"), value_t::string)));
if (json.contains("registrationTime"))
res.registrationTime = ensureType(valueAt(json, "registrationTime"), value_t::number_integer);
if (json.contains("ultimate"))
res.ultimate = ensureType(valueAt(json, "ultimate"), value_t::boolean);
if (json.contains("signatures"))
res.sigs = valueAt(json, "signatures");
return res;
}
}

View file

@ -29,7 +29,7 @@ struct SubstitutablePathInfo
uint64_t narSize;
};
typedef std::map<StorePath, SubstitutablePathInfo> SubstitutablePathInfos;
using SubstitutablePathInfos = std::map<StorePath, SubstitutablePathInfo>;
struct UnkeyedValidPathInfo
@ -42,7 +42,7 @@ struct UnkeyedValidPathInfo
StorePathSet references;
time_t registrationTime = 0;
uint64_t narSize = 0; // 0 = unknown
uint64_t id; // internal use only
uint64_t id = 0; // internal use only
/**
* Whether the path is ultimately trusted, that is, it's a
@ -78,6 +78,18 @@ struct UnkeyedValidPathInfo
DECLARE_CMP(UnkeyedValidPathInfo);
virtual ~UnkeyedValidPathInfo() { }
/**
* @param includeImpureInfo If true, variable elements such as the
* registration time are included.
*/
virtual nlohmann::json toJSON(
const Store & store,
bool includeImpureInfo,
HashFormat hashFormat) const;
static UnkeyedValidPathInfo fromJSON(
const Store & store,
const nlohmann::json & json);
};
struct ValidPathInfo : UnkeyedValidPathInfo {
@ -136,6 +148,6 @@ struct ValidPathInfo : UnkeyedValidPathInfo {
virtual ~ValidPathInfo() { }
};
typedef std::map<StorePath, ValidPathInfo> ValidPathInfos;
using ValidPathInfos = std::map<StorePath, ValidPathInfo>;
}

View file

@ -1,6 +1,5 @@
#include "path-references.hh"
#include "hash.hh"
#include "util.hh"
#include "archive.hh"
#include <map>
@ -50,7 +49,7 @@ std::pair<StorePathSet, HashResult> scanForReferences(
const std::string & path,
const StorePathSet & refs)
{
HashSink hashSink { htSHA256 };
HashSink hashSink { HashAlgorithm::SHA256 };
auto found = scanForReferences(hashSink, path, refs);
auto hash = hashSink.finish();
return std::pair<StorePathSet, HashResult>(found, hash);

View file

@ -1,4 +1,5 @@
#pragma once
///@file
#include "references.hh"
#include "path.hh"

View file

@ -5,7 +5,7 @@
namespace nix {
std::string StorePathWithOutputs::to_string(const Store & store) const
std::string StorePathWithOutputs::to_string(const StoreDirConfig & store) const
{
return outputs.empty()
? store.printStorePath(path)
@ -85,7 +85,7 @@ std::pair<std::string_view, StringSet> parsePathWithOutputs(std::string_view s)
}
StorePathWithOutputs parsePathWithOutputs(const Store & store, std::string_view pathWithOutputs)
StorePathWithOutputs parsePathWithOutputs(const StoreDirConfig & store, std::string_view pathWithOutputs)
{
auto [path, outputs] = parsePathWithOutputs(pathWithOutputs);
return StorePathWithOutputs { store.parseStorePath(path), std::move(outputs) };

View file

@ -6,6 +6,8 @@
namespace nix {
struct StoreDirConfig;
/**
* This is a deprecated old type just for use by the old CLI, and older
* versions of the RPC protocols. In new code don't use it; you want
@ -19,7 +21,7 @@ struct StorePathWithOutputs
StorePath path;
std::set<std::string> outputs;
std::string to_string(const Store & store) const;
std::string to_string(const StoreDirConfig & store) const;
DerivedPath toDerivedPath() const;
@ -32,14 +34,14 @@ std::vector<DerivedPath> toDerivedPaths(const std::vector<StorePathWithOutputs>)
std::pair<std::string_view, StringSet> parsePathWithOutputs(std::string_view s);
class Store;
/**
* Split a string specifying a derivation and a set of outputs
* (/nix/store/hash-foo!out1,out2,...) into the derivation path
* and the outputs.
*/
StorePathWithOutputs parsePathWithOutputs(const Store & store, std::string_view pathWithOutputs);
StorePathWithOutputs parsePathWithOutputs(const StoreDirConfig & store, std::string_view pathWithOutputs);
class Store;
StorePathWithOutputs followLinksToStorePathWithOutputs(const Store & store, std::string_view pathWithOutputs);

View file

@ -1,4 +1,4 @@
#include "store-api.hh"
#include "store-dir-config.hh"
#include <sodium.h>
@ -35,7 +35,7 @@ StorePath::StorePath(std::string_view _baseName)
}
StorePath::StorePath(const Hash & hash, std::string_view _name)
: baseName((hash.to_string(HashFormat::Base32, false) + "-").append(std::string(_name)))
: baseName((hash.to_string(HashFormat::Nix32, false) + "-").append(std::string(_name)))
{
checkName(baseName, name());
}
@ -49,12 +49,12 @@ StorePath StorePath::dummy("ffffffffffffffffffffffffffffffff-x");
StorePath StorePath::random(std::string_view name)
{
Hash hash(htSHA1);
Hash hash(HashAlgorithm::SHA1);
randombytes_buf(hash.hash, hash.hashSize);
return StorePath(hash, name);
}
StorePath Store::parseStorePath(std::string_view path) const
StorePath StoreDirConfig::parseStorePath(std::string_view path) const
{
auto p = canonPath(std::string(path));
if (dirOf(p) != storeDir)
@ -62,7 +62,7 @@ StorePath Store::parseStorePath(std::string_view path) const
return StorePath(baseNameOf(p));
}
std::optional<StorePath> Store::maybeParseStorePath(std::string_view path) const
std::optional<StorePath> StoreDirConfig::maybeParseStorePath(std::string_view path) const
{
try {
return parseStorePath(path);
@ -71,24 +71,24 @@ std::optional<StorePath> Store::maybeParseStorePath(std::string_view path) const
}
}
bool Store::isStorePath(std::string_view path) const
bool StoreDirConfig::isStorePath(std::string_view path) const
{
return (bool) maybeParseStorePath(path);
}
StorePathSet Store::parseStorePathSet(const PathSet & paths) const
StorePathSet StoreDirConfig::parseStorePathSet(const PathSet & paths) const
{
StorePathSet res;
for (auto & i : paths) res.insert(parseStorePath(i));
return res;
}
std::string Store::printStorePath(const StorePath & path) const
std::string StoreDirConfig::printStorePath(const StorePath & path) const
{
return (storeDir + "/").append(path.to_string());
}
PathSet Store::printStorePathSet(const StorePathSet & paths) const
PathSet StoreDirConfig::printStorePathSet(const StorePathSet & paths) const
{
PathSet res;
for (auto & i : paths) res.insert(printStorePath(i));

View file

@ -1,6 +1,7 @@
#include "pathlocks.hh"
#include "util.hh"
#include "sync.hh"
#include "signals.hh"
#include <cerrno>
#include <cstdlib>

View file

@ -1,7 +1,7 @@
#pragma once
///@file
#include "util.hh"
#include "file-descriptor.hh"
namespace nix {

View file

@ -0,0 +1,171 @@
#if HAVE_SYS_XATTR_H
# include <sys/xattr.h>
#endif
#include "posix-fs-canonicalise.hh"
#include "file-system.hh"
#include "signals.hh"
#include "util.hh"
#include "globals.hh"
#include "store-api.hh"
namespace nix {
const time_t mtimeStore = 1; /* 1 second into the epoch */
static void canonicaliseTimestampAndPermissions(const Path & path, const struct stat & st)
{
if (!S_ISLNK(st.st_mode)) {
/* Mask out all type related bits. */
mode_t mode = st.st_mode & ~S_IFMT;
if (mode != 0444 && mode != 0555) {
mode = (st.st_mode & S_IFMT)
| 0444
| (st.st_mode & S_IXUSR ? 0111 : 0);
if (chmod(path.c_str(), mode) == -1)
throw SysError("changing mode of '%1%' to %2$o", path, mode);
}
}
if (st.st_mtime != mtimeStore) {
struct timeval times[2];
times[0].tv_sec = st.st_atime;
times[0].tv_usec = 0;
times[1].tv_sec = mtimeStore;
times[1].tv_usec = 0;
#if HAVE_LUTIMES
if (lutimes(path.c_str(), times) == -1)
if (errno != ENOSYS ||
(!S_ISLNK(st.st_mode) && utimes(path.c_str(), times) == -1))
#else
if (!S_ISLNK(st.st_mode) && utimes(path.c_str(), times) == -1)
#endif
throw SysError("changing modification time of '%1%'", path);
}
}
void canonicaliseTimestampAndPermissions(const Path & path)
{
canonicaliseTimestampAndPermissions(path, lstat(path));
}
static void canonicalisePathMetaData_(
const Path & path,
std::optional<std::pair<uid_t, uid_t>> uidRange,
InodesSeen & inodesSeen)
{
checkInterrupt();
#if __APPLE__
/* Remove flags, in particular UF_IMMUTABLE which would prevent
the file from being garbage-collected. FIXME: Use
setattrlist() to remove other attributes as well. */
if (lchflags(path.c_str(), 0)) {
if (errno != ENOTSUP)
throw SysError("clearing flags of path '%1%'", path);
}
#endif
auto st = lstat(path);
/* Really make sure that the path is of a supported type. */
if (!(S_ISREG(st.st_mode) || S_ISDIR(st.st_mode) || S_ISLNK(st.st_mode)))
throw Error("file '%1%' has an unsupported type", path);
#ifdef HAVE_SYS_XATTR_H
/* Remove extended attributes / ACLs. */
ssize_t eaSize = llistxattr(path.c_str(), nullptr, 0);
if (eaSize < 0) {
if (errno != ENOTSUP && errno != ENODATA)
throw SysError("querying extended attributes of '%s'", path);
} else if (eaSize > 0) {
std::vector<char> eaBuf(eaSize);
if ((eaSize = llistxattr(path.c_str(), eaBuf.data(), eaBuf.size())) < 0)
throw SysError("querying extended attributes of '%s'", path);
for (auto & eaName: tokenizeString<Strings>(std::string(eaBuf.data(), eaSize), std::string("\000", 1))) {
if (settings.ignoredAcls.get().count(eaName)) continue;
if (lremovexattr(path.c_str(), eaName.c_str()) == -1)
throw SysError("removing extended attribute '%s' from '%s'", eaName, path);
}
}
#endif
/* Fail if the file is not owned by the build user. This prevents
us from messing up the ownership/permissions of files
hard-linked into the output (e.g. "ln /etc/shadow $out/foo").
However, ignore files that we chown'ed ourselves previously to
ensure that we don't fail on hard links within the same build
(i.e. "touch $out/foo; ln $out/foo $out/bar"). */
if (uidRange && (st.st_uid < uidRange->first || st.st_uid > uidRange->second)) {
if (S_ISDIR(st.st_mode) || !inodesSeen.count(Inode(st.st_dev, st.st_ino)))
throw BuildError("invalid ownership on file '%1%'", path);
mode_t mode = st.st_mode & ~S_IFMT;
assert(S_ISLNK(st.st_mode) || (st.st_uid == geteuid() && (mode == 0444 || mode == 0555) && st.st_mtime == mtimeStore));
return;
}
inodesSeen.insert(Inode(st.st_dev, st.st_ino));
canonicaliseTimestampAndPermissions(path, st);
/* Change ownership to the current uid. If it's a symlink, use
lchown if available, otherwise don't bother. Wrong ownership
of a symlink doesn't matter, since the owning user can't change
the symlink and can't delete it because the directory is not
writable. The only exception is top-level paths in the Nix
store (since that directory is group-writable for the Nix build
users group); we check for this case below. */
if (st.st_uid != geteuid()) {
#if HAVE_LCHOWN
if (lchown(path.c_str(), geteuid(), getegid()) == -1)
#else
if (!S_ISLNK(st.st_mode) &&
chown(path.c_str(), geteuid(), getegid()) == -1)
#endif
throw SysError("changing owner of '%1%' to %2%",
path, geteuid());
}
if (S_ISDIR(st.st_mode)) {
DirEntries entries = readDirectory(path);
for (auto & i : entries)
canonicalisePathMetaData_(path + "/" + i.name, uidRange, inodesSeen);
}
}
void canonicalisePathMetaData(
const Path & path,
std::optional<std::pair<uid_t, uid_t>> uidRange,
InodesSeen & inodesSeen)
{
canonicalisePathMetaData_(path, uidRange, inodesSeen);
/* On platforms that don't have lchown(), the top-level path can't
be a symlink, since we can't change its ownership. */
auto st = lstat(path);
if (st.st_uid != geteuid()) {
assert(S_ISLNK(st.st_mode));
throw Error("wrong ownership of top-level store path '%1%'", path);
}
}
void canonicalisePathMetaData(const Path & path,
std::optional<std::pair<uid_t, uid_t>> uidRange)
{
InodesSeen inodesSeen;
canonicalisePathMetaData(path, uidRange, inodesSeen);
}
}

View file

@ -0,0 +1,45 @@
#pragma once
///@file
#include <sys/stat.h>
#include <sys/time.h>
#include "types.hh"
#include "error.hh"
namespace nix {
typedef std::pair<dev_t, ino_t> Inode;
typedef std::set<Inode> InodesSeen;
/**
* "Fix", or canonicalise, the meta-data of the files in a store path
* after it has been built. In particular:
*
* - the last modification date on each file is set to 1 (i.e.,
* 00:00:01 1/1/1970 UTC)
*
* - the permissions are set of 444 or 555 (i.e., read-only with or
* without execute permission; setuid bits etc. are cleared)
*
* - the owner and group are set to the Nix user and group, if we're
* running as root.
*
* If uidRange is not empty, this function will throw an error if it
* encounters files owned by a user outside of the closed interval
* [uidRange->first, uidRange->second].
*/
void canonicalisePathMetaData(
const Path & path,
std::optional<std::pair<uid_t, uid_t>> uidRange,
InodesSeen & inodesSeen);
void canonicalisePathMetaData(
const Path & path,
std::optional<std::pair<uid_t, uid_t>> uidRange);
void canonicaliseTimestampAndPermissions(const Path & path);
MakeError(PathInUse, Error);
}

View file

@ -1,7 +1,7 @@
#include "profiles.hh"
#include "store-api.hh"
#include "local-fs-store.hh"
#include "util.hh"
#include "users.hh"
#include <sys/types.h>
#include <sys/stat.h>

View file

@ -8,8 +8,9 @@
namespace nix {
RemoteFSAccessor::RemoteFSAccessor(ref<Store> store, const Path & cacheDir)
RemoteFSAccessor::RemoteFSAccessor(ref<Store> store, bool requireValidPath, const Path & cacheDir)
: store(store)
, requireValidPath(requireValidPath)
, cacheDir(cacheDir)
{
if (cacheDir != "")
@ -22,7 +23,7 @@ Path RemoteFSAccessor::makeCacheFile(std::string_view hashPart, const std::strin
return fmt("%s/%s.%s", cacheDir, hashPart, ext);
}
ref<FSAccessor> RemoteFSAccessor::addToCache(std::string_view hashPart, std::string && nar)
ref<SourceAccessor> RemoteFSAccessor::addToCache(std::string_view hashPart, std::string && nar)
{
if (cacheDir != "") {
try {
@ -38,7 +39,7 @@ ref<FSAccessor> RemoteFSAccessor::addToCache(std::string_view hashPart, std::str
if (cacheDir != "") {
try {
nlohmann::json j = listNar(narAccessor, "", true);
nlohmann::json j = listNar(narAccessor, CanonPath::root, true);
writeFile(makeCacheFile(hashPart, "ls"), j.dump());
} catch (...) {
ignoreException();
@ -48,11 +49,10 @@ ref<FSAccessor> RemoteFSAccessor::addToCache(std::string_view hashPart, std::str
return narAccessor;
}
std::pair<ref<FSAccessor>, Path> RemoteFSAccessor::fetch(const Path & path_, bool requireValidPath)
std::pair<ref<SourceAccessor>, CanonPath> RemoteFSAccessor::fetch(const CanonPath & path)
{
auto path = canonPath(path_);
auto [storePath, restPath] = store->toStorePath(path);
auto [storePath, restPath_] = store->toStorePath(path.abs());
auto restPath = CanonPath(restPath_);
if (requireValidPath && !store->isValidPath(storePath))
throw InvalidPath("path '%1%' is not a valid store path", store->printStorePath(storePath));
@ -63,7 +63,7 @@ std::pair<ref<FSAccessor>, Path> RemoteFSAccessor::fetch(const Path & path_, boo
std::string listing;
Path cacheFile;
if (cacheDir != "" && pathExists(cacheFile = makeCacheFile(storePath.hashPart(), "nar"))) {
if (cacheDir != "" && nix::pathExists(cacheFile = makeCacheFile(storePath.hashPart(), "nar"))) {
try {
listing = nix::readFile(makeCacheFile(storePath.hashPart(), "ls"));
@ -101,25 +101,25 @@ std::pair<ref<FSAccessor>, Path> RemoteFSAccessor::fetch(const Path & path_, boo
return {addToCache(storePath.hashPart(), std::move(sink.s)), restPath};
}
FSAccessor::Stat RemoteFSAccessor::stat(const Path & path)
std::optional<SourceAccessor::Stat> RemoteFSAccessor::maybeLstat(const CanonPath & path)
{
auto res = fetch(path);
return res.first->stat(res.second);
return res.first->maybeLstat(res.second);
}
StringSet RemoteFSAccessor::readDirectory(const Path & path)
SourceAccessor::DirEntries RemoteFSAccessor::readDirectory(const CanonPath & path)
{
auto res = fetch(path);
return res.first->readDirectory(res.second);
}
std::string RemoteFSAccessor::readFile(const Path & path, bool requireValidPath)
std::string RemoteFSAccessor::readFile(const CanonPath & path)
{
auto res = fetch(path, requireValidPath);
auto res = fetch(path);
return res.first->readFile(res.second);
}
std::string RemoteFSAccessor::readLink(const Path & path)
std::string RemoteFSAccessor::readLink(const CanonPath & path)
{
auto res = fetch(path);
return res.first->readLink(res.second);

View file

@ -1,40 +1,43 @@
#pragma once
///@file
#include "fs-accessor.hh"
#include "source-accessor.hh"
#include "ref.hh"
#include "store-api.hh"
namespace nix {
class RemoteFSAccessor : public FSAccessor
class RemoteFSAccessor : public SourceAccessor
{
ref<Store> store;
std::map<std::string, ref<FSAccessor>> nars;
std::map<std::string, ref<SourceAccessor>> nars;
bool requireValidPath;
Path cacheDir;
std::pair<ref<FSAccessor>, Path> fetch(const Path & path_, bool requireValidPath = true);
std::pair<ref<SourceAccessor>, CanonPath> fetch(const CanonPath & path);
friend class BinaryCacheStore;
Path makeCacheFile(std::string_view hashPart, const std::string & ext);
ref<FSAccessor> addToCache(std::string_view hashPart, std::string && nar);
ref<SourceAccessor> addToCache(std::string_view hashPart, std::string && nar);
public:
RemoteFSAccessor(ref<Store> store,
bool requireValidPath = true,
const /* FIXME: use std::optional */ Path & cacheDir = "");
Stat stat(const Path & path) override;
std::optional<Stat> maybeLstat(const CanonPath & path) override;
StringSet readDirectory(const Path & path) override;
DirEntries readDirectory(const CanonPath & path) override;
std::string readFile(const Path & path, bool requireValidPath = true) override;
std::string readFile(const CanonPath & path) override;
std::string readLink(const Path & path) override;
std::string readLink(const CanonPath & path) override;
};
}

View file

@ -1,3 +1,6 @@
#pragma once
///@file
#include "remote-store.hh"
#include "worker-protocol.hh"
#include "pool.hh"

View file

@ -20,7 +20,7 @@
namespace nix {
/* TODO: Separate these store impls into different files, give them better names */
/* TODO: Separate these store types into different files, give them better names */
RemoteStore::RemoteStore(const Params & params)
: RemoteStoreConfig(params)
, Store(params)
@ -417,12 +417,12 @@ std::optional<StorePath> RemoteStore::queryPathFromHashPart(const std::string &
ref<const ValidPathInfo> RemoteStore::addCAToStore(
Source & dump,
std::string_view name,
ContentAddressMethod caMethod,
HashType hashType,
const StorePathSet & references,
RepairFlag repair)
Source & dump,
std::string_view name,
ContentAddressMethod caMethod,
HashAlgorithm hashAlgo,
const StorePathSet & references,
RepairFlag repair)
{
std::optional<ConnectionHandle> conn_(getConnection());
auto & conn = *conn_;
@ -432,7 +432,7 @@ ref<const ValidPathInfo> RemoteStore::addCAToStore(
conn->to
<< WorkerProto::Op::AddToStore
<< name
<< caMethod.render(hashType);
<< caMethod.render(hashAlgo);
WorkerProto::write(*this, *conn, references);
conn->to << repair;
@ -453,9 +453,9 @@ ref<const ValidPathInfo> RemoteStore::addCAToStore(
std::visit(overloaded {
[&](const TextIngestionMethod & thm) -> void {
if (hashType != htSHA256)
if (hashAlgo != HashAlgorithm::SHA256)
throw UnimplementedError("When adding text-hashed data called '%s', only SHA-256 is supported but '%s' was given",
name, printHashType(hashType));
name, printHashAlgo(hashAlgo));
std::string s = dump.drain();
conn->to << WorkerProto::Op::AddTextToStore << name << s;
WorkerProto::write(*this, *conn, references);
@ -465,9 +465,9 @@ ref<const ValidPathInfo> RemoteStore::addCAToStore(
conn->to
<< WorkerProto::Op::AddToStore
<< name
<< ((hashType == htSHA256 && fim == FileIngestionMethod::Recursive) ? 0 : 1) /* backwards compatibility hack */
<< ((hashAlgo == HashAlgorithm::SHA256 && fim == FileIngestionMethod::Recursive) ? 0 : 1) /* backwards compatibility hack */
<< (fim == FileIngestionMethod::Recursive ? 1 : 0)
<< printHashType(hashType);
<< printHashAlgo(hashAlgo);
try {
conn->to.written = 0;
@ -503,9 +503,9 @@ ref<const ValidPathInfo> RemoteStore::addCAToStore(
StorePath RemoteStore::addToStoreFromDump(Source & dump, std::string_view name,
FileIngestionMethod method, HashType hashType, RepairFlag repair, const StorePathSet & references)
FileIngestionMethod method, HashAlgorithm hashAlgo, RepairFlag repair, const StorePathSet & references)
{
return addCAToStore(dump, name, method, hashType, references, repair)->path;
return addCAToStore(dump, name, method, hashAlgo, references, repair)->path;
}
@ -610,7 +610,7 @@ StorePath RemoteStore::addTextToStore(
RepairFlag repair)
{
StringSource source(s);
return addCAToStore(source, name, TextIngestionMethod {}, htSHA256, references, repair)->path;
return addCAToStore(source, name, TextIngestionMethod {}, HashAlgorithm::SHA256, references, repair)->path;
}
void RemoteStore::registerDrvOutput(const Realisation & info)
@ -970,7 +970,7 @@ void RemoteStore::narFromPath(const StorePath & path, Sink & sink)
copyNAR(conn->from, sink);
}
ref<FSAccessor> RemoteStore::getFSAccessor()
ref<SourceAccessor> RemoteStore::getFSAccessor(bool requireValidPath)
{
return make_ref<RemoteFSAccessor>(ref<Store>(shared_from_this()));
}

View file

@ -22,10 +22,10 @@ struct RemoteStoreConfig : virtual StoreConfig
{
using StoreConfig::StoreConfig;
const Setting<int> maxConnections{(StoreConfig*) this, 1, "max-connections",
const Setting<int> maxConnections{this, 1, "max-connections",
"Maximum number of concurrent connections to the Nix daemon."};
const Setting<unsigned int> maxConnectionAge{(StoreConfig*) this,
const Setting<unsigned int> maxConnectionAge{this,
std::numeric_limits<unsigned int>::max(),
"max-connection-age",
"Maximum age of a connection before it is closed."};
@ -74,18 +74,18 @@ public:
* Add a content-addressable store path. `dump` will be drained.
*/
ref<const ValidPathInfo> addCAToStore(
Source & dump,
std::string_view name,
ContentAddressMethod caMethod,
HashType hashType,
const StorePathSet & references,
RepairFlag repair);
Source & dump,
std::string_view name,
ContentAddressMethod caMethod,
HashAlgorithm hashAlgo,
const StorePathSet & references,
RepairFlag repair);
/**
* Add a content-addressable store path. Does not support references. `dump` will be drained.
*/
StorePath addToStoreFromDump(Source & dump, std::string_view name,
FileIngestionMethod method = FileIngestionMethod::Recursive, HashType hashAlgo = htSHA256, RepairFlag repair = NoRepair, const StorePathSet & references = StorePathSet()) override;
FileIngestionMethod method = FileIngestionMethod::Recursive, HashAlgorithm hashAlgo = HashAlgorithm::SHA256, RepairFlag repair = NoRepair, const StorePathSet & references = StorePathSet()) override;
void addToStore(const ValidPathInfo & info, Source & nar,
RepairFlag repair, CheckSigsFlag checkSigs) override;
@ -185,7 +185,7 @@ protected:
friend struct ConnectionHandle;
virtual ref<FSAccessor> getFSAccessor() override;
virtual ref<SourceAccessor> getFSAccessor(bool requireValidPath) override;
virtual void narFromPath(const StorePath & path, Sink & sink) override;

View file

@ -193,20 +193,20 @@ struct S3BinaryCacheStoreConfig : virtual BinaryCacheStoreConfig
{
using BinaryCacheStoreConfig::BinaryCacheStoreConfig;
const Setting<std::string> profile{(StoreConfig*) this, "", "profile",
const Setting<std::string> profile{this, "", "profile",
R"(
The name of the AWS configuration profile to use. By default
Nix will use the `default` profile.
)"};
const Setting<std::string> region{(StoreConfig*) this, Aws::Region::US_EAST_1, "region",
const Setting<std::string> region{this, Aws::Region::US_EAST_1, "region",
R"(
The region of the S3 bucket. If your bucket is not in
`useast-1`, you should always explicitly specify the region
parameter.
)"};
const Setting<std::string> scheme{(StoreConfig*) this, "", "scheme",
const Setting<std::string> scheme{this, "", "scheme",
R"(
The scheme used for S3 requests, `https` (default) or `http`. This
option allows you to disable HTTPS for binary caches which don't
@ -218,7 +218,7 @@ struct S3BinaryCacheStoreConfig : virtual BinaryCacheStoreConfig
> information.
)"};
const Setting<std::string> endpoint{(StoreConfig*) this, "", "endpoint",
const Setting<std::string> endpoint{this, "", "endpoint",
R"(
The URL of the endpoint of an S3-compatible service such as MinIO.
Do not specify this setting if you're using Amazon S3.
@ -229,13 +229,13 @@ struct S3BinaryCacheStoreConfig : virtual BinaryCacheStoreConfig
> addressing instead of virtual host based addressing.
)"};
const Setting<std::string> narinfoCompression{(StoreConfig*) this, "", "narinfo-compression",
const Setting<std::string> narinfoCompression{this, "", "narinfo-compression",
"Compression method for `.narinfo` files."};
const Setting<std::string> lsCompression{(StoreConfig*) this, "", "ls-compression",
const Setting<std::string> lsCompression{this, "", "ls-compression",
"Compression method for `.ls` files."};
const Setting<std::string> logCompression{(StoreConfig*) this, "", "log-compression",
const Setting<std::string> logCompression{this, "", "log-compression",
R"(
Compression method for `log/*` files. It is recommended to
use a compression method supported by most web browsers
@ -243,11 +243,11 @@ struct S3BinaryCacheStoreConfig : virtual BinaryCacheStoreConfig
)"};
const Setting<bool> multipartUpload{
(StoreConfig*) this, false, "multipart-upload",
this, false, "multipart-upload",
"Whether to use multi-part uploads."};
const Setting<uint64_t> bufferSize{
(StoreConfig*) this, 5 * 1024 * 1024, "buffer-size",
this, 5 * 1024 * 1024, "buffer-size",
"Size (in bytes) of each part in multi-part uploads."};
const std::string name() override { return "S3 Binary Cache Store"; }

View file

@ -16,11 +16,11 @@ namespace nix {
/* protocol-agnostic templates */
#define SERVE_USE_LENGTH_PREFIX_SERIALISER(TEMPLATE, T) \
TEMPLATE T ServeProto::Serialise< T >::read(const Store & store, ServeProto::ReadConn conn) \
TEMPLATE T ServeProto::Serialise< T >::read(const StoreDirConfig & store, ServeProto::ReadConn conn) \
{ \
return LengthPrefixedProtoHelper<ServeProto, T >::read(store, conn); \
} \
TEMPLATE void ServeProto::Serialise< T >::write(const Store & store, ServeProto::WriteConn conn, const T & t) \
TEMPLATE void ServeProto::Serialise< T >::write(const StoreDirConfig & store, ServeProto::WriteConn conn, const T & t) \
{ \
LengthPrefixedProtoHelper<ServeProto, T >::write(store, conn, t); \
}
@ -41,12 +41,12 @@ SERVE_USE_LENGTH_PREFIX_SERIALISER(
template<typename T>
struct ServeProto::Serialise
{
static T read(const Store & store, ServeProto::ReadConn conn)
static T read(const StoreDirConfig & store, ServeProto::ReadConn conn)
{
return CommonProto::Serialise<T>::read(store,
CommonProto::ReadConn { .from = conn.from });
}
static void write(const Store & store, ServeProto::WriteConn conn, const T & t)
static void write(const StoreDirConfig & store, ServeProto::WriteConn conn, const T & t)
{
CommonProto::Serialise<T>::write(store,
CommonProto::WriteConn { .to = conn.to },

View file

@ -1,11 +1,11 @@
#include "serialise.hh"
#include "util.hh"
#include "path-with-outputs.hh"
#include "store-api.hh"
#include "build-result.hh"
#include "serve-protocol.hh"
#include "serve-protocol-impl.hh"
#include "archive.hh"
#include "path-info.hh"
#include <nlohmann/json.hpp>
@ -13,7 +13,7 @@ namespace nix {
/* protocol-specific definitions */
BuildResult ServeProto::Serialise<BuildResult>::read(const Store & store, ServeProto::ReadConn conn)
BuildResult ServeProto::Serialise<BuildResult>::read(const StoreDirConfig & store, ServeProto::ReadConn conn)
{
BuildResult status;
status.status = (BuildResult::Status) readInt(conn.from);
@ -35,7 +35,7 @@ BuildResult ServeProto::Serialise<BuildResult>::read(const Store & store, ServeP
return status;
}
void ServeProto::Serialise<BuildResult>::write(const Store & store, ServeProto::WriteConn conn, const BuildResult & status)
void ServeProto::Serialise<BuildResult>::write(const StoreDirConfig & store, ServeProto::WriteConn conn, const BuildResult & status)
{
conn.to
<< status.status
@ -55,4 +55,83 @@ void ServeProto::Serialise<BuildResult>::write(const Store & store, ServeProto::
}
}
UnkeyedValidPathInfo ServeProto::Serialise<UnkeyedValidPathInfo>::read(const StoreDirConfig & store, ReadConn conn)
{
/* Hash should be set below unless very old `nix-store --serve`.
Caller should assert that it did set it. */
UnkeyedValidPathInfo info { Hash::dummy };
auto deriver = readString(conn.from);
if (deriver != "")
info.deriver = store.parseStorePath(deriver);
info.references = ServeProto::Serialise<StorePathSet>::read(store, conn);
readLongLong(conn.from); // download size, unused
info.narSize = readLongLong(conn.from);
if (GET_PROTOCOL_MINOR(conn.version) >= 4) {
auto s = readString(conn.from);
if (!s.empty())
info.narHash = Hash::parseAnyPrefixed(s);
info.ca = ContentAddress::parseOpt(readString(conn.from));
info.sigs = readStrings<StringSet>(conn.from);
}
return info;
}
void ServeProto::Serialise<UnkeyedValidPathInfo>::write(const StoreDirConfig & store, WriteConn conn, const UnkeyedValidPathInfo & info)
{
conn.to
<< (info.deriver ? store.printStorePath(*info.deriver) : "");
ServeProto::write(store, conn, info.references);
// !!! Maybe we want compression?
conn.to
<< info.narSize // downloadSize, lie a little
<< info.narSize;
if (GET_PROTOCOL_MINOR(conn.version) >= 4)
conn.to
<< info.narHash.to_string(HashFormat::Nix32, true)
<< renderContentAddress(info.ca)
<< info.sigs;
}
ServeProto::BuildOptions ServeProto::Serialise<ServeProto::BuildOptions>::read(const StoreDirConfig & store, ReadConn conn)
{
BuildOptions options;
options.maxSilentTime = readInt(conn.from);
options.buildTimeout = readInt(conn.from);
if (GET_PROTOCOL_MINOR(conn.version) >= 2)
options.maxLogSize = readNum<unsigned long>(conn.from);
if (GET_PROTOCOL_MINOR(conn.version) >= 3) {
options.nrRepeats = readInt(conn.from);
options.enforceDeterminism = readInt(conn.from);
}
if (GET_PROTOCOL_MINOR(conn.version) >= 7) {
options.keepFailed = (bool) readInt(conn.from);
}
return options;
}
void ServeProto::Serialise<ServeProto::BuildOptions>::write(const StoreDirConfig & store, WriteConn conn, const ServeProto::BuildOptions & options)
{
conn.to
<< options.maxSilentTime
<< options.buildTimeout;
if (GET_PROTOCOL_MINOR(conn.version) >= 2)
conn.to
<< options.maxLogSize;
if (GET_PROTOCOL_MINOR(conn.version) >= 3)
conn.to
<< options.nrRepeats
<< options.enforceDeterminism;
if (GET_PROTOCOL_MINOR(conn.version) >= 7) {
conn.to << ((int) options.keepFailed);
}
}
}

View file

@ -13,11 +13,12 @@ namespace nix {
#define GET_PROTOCOL_MINOR(x) ((x) & 0x00ff)
class Store;
struct StoreDirConfig;
struct Source;
// items being serialised
struct BuildResult;
struct UnkeyedValidPathInfo;
/**
@ -72,8 +73,8 @@ struct ServeProto
// See `worker-protocol.hh` for a longer explanation.
#if 0
{
static T read(const Store & store, ReadConn conn);
static void write(const Store & store, WriteConn conn, const T & t);
static T read(const StoreDirConfig & store, ReadConn conn);
static void write(const StoreDirConfig & store, WriteConn conn, const T & t);
};
#endif
@ -82,10 +83,17 @@ struct ServeProto
* infer the type instead of having to write it down explicitly.
*/
template<typename T>
static void write(const Store & store, WriteConn conn, const T & t)
static void write(const StoreDirConfig & store, WriteConn conn, const T & t)
{
ServeProto::Serialise<T>::write(store, conn, t);
}
/**
* Options for building shared between
* `ServeProto::Command::BuildPaths` and
* `ServeProto::Command::BuildDerivation`.
*/
struct BuildOptions;
};
enum struct ServeProto::Command : uint64_t
@ -101,6 +109,22 @@ enum struct ServeProto::Command : uint64_t
AddToStoreNar = 9,
};
struct ServeProto::BuildOptions {
/**
* Default value in this and every other field is so tests pass when
* testing older deserialisers which do not set all the fields.
*/
time_t maxSilentTime = -1;
time_t buildTimeout = -1;
size_t maxLogSize = -1;
size_t nrRepeats = -1;
bool enforceDeterminism = -1;
bool keepFailed = -1;
bool operator == (const ServeProto::BuildOptions &) const = default;
};
/**
* Convenience for sending operation codes.
*
@ -135,12 +159,16 @@ inline std::ostream & operator << (std::ostream & s, ServeProto::Command op)
#define DECLARE_SERVE_SERIALISER(T) \
struct ServeProto::Serialise< T > \
{ \
static T read(const Store & store, ServeProto::ReadConn conn); \
static void write(const Store & store, ServeProto::WriteConn conn, const T & t); \
static T read(const StoreDirConfig & store, ServeProto::ReadConn conn); \
static void write(const StoreDirConfig & store, ServeProto::WriteConn conn, const T & t); \
};
template<>
DECLARE_SERVE_SERIALISER(BuildResult);
template<>
DECLARE_SERVE_SERIALISER(UnkeyedValidPathInfo);
template<>
DECLARE_SERVE_SERIALISER(ServeProto::BuildOptions);
template<typename T>
DECLARE_SERVE_SERIALISER(std::vector<T>);

View file

@ -2,6 +2,7 @@
#include "globals.hh"
#include "util.hh"
#include "url.hh"
#include "signals.hh"
#include <sqlite3.h>

View file

@ -9,18 +9,18 @@ struct CommonSSHStoreConfig : virtual StoreConfig
{
using StoreConfig::StoreConfig;
const Setting<Path> sshKey{(StoreConfig*) this, "", "ssh-key",
const Setting<Path> sshKey{this, "", "ssh-key",
"Path to the SSH private key used to authenticate to the remote machine."};
const Setting<std::string> sshPublicHostKey{(StoreConfig*) this, "", "base64-ssh-public-host-key",
const Setting<std::string> sshPublicHostKey{this, "", "base64-ssh-public-host-key",
"The public host key of the remote machine."};
const Setting<bool> compress{(StoreConfig*) this, false, "compress",
const Setting<bool> compress{this, false, "compress",
"Whether to enable SSH compression."};
const Setting<std::string> remoteStore{(StoreConfig*) this, "", "remote-store",
const Setting<std::string> remoteStore{this, "", "remote-store",
R"(
[Store URL](@docroot@/command-ref/new-cli/nix3-help-stores.md#store-url-format)
[Store URL](@docroot@/store/types/index.md#store-url-format)
to be used on the remote machine. The default is `auto`
(i.e. use the Nix daemon or `/nix/store` directly).
)"};

View file

@ -3,9 +3,10 @@
#include "local-fs-store.hh"
#include "remote-store.hh"
#include "remote-store-connection.hh"
#include "remote-fs-accessor.hh"
#include "source-accessor.hh"
#include "archive.hh"
#include "worker-protocol.hh"
#include "worker-protocol-impl.hh"
#include "pool.hh"
#include "ssh.hh"
@ -16,7 +17,7 @@ struct SSHStoreConfig : virtual RemoteStoreConfig, virtual CommonSSHStoreConfig
using RemoteStoreConfig::RemoteStoreConfig;
using CommonSSHStoreConfig::CommonSSHStoreConfig;
const Setting<Path> remoteProgram{(StoreConfig*) this, "nix-daemon", "remote-program",
const Setting<Path> remoteProgram{this, "nix-daemon", "remote-program",
"Path to the `nix-daemon` executable on the remote machine."};
const std::string name() override { return "Experimental SSH Store"; }
@ -78,6 +79,8 @@ protected:
std::string host;
std::vector<std::string> extraRemoteProgramArgs;
SSHMaster master;
void setOptions(RemoteStore::Connection & conn) override
@ -91,6 +94,121 @@ protected:
};
};
struct MountedSSHStoreConfig : virtual SSHStoreConfig, virtual LocalFSStoreConfig
{
using SSHStoreConfig::SSHStoreConfig;
using LocalFSStoreConfig::LocalFSStoreConfig;
MountedSSHStoreConfig(StringMap params)
: StoreConfig(params)
, RemoteStoreConfig(params)
, CommonSSHStoreConfig(params)
, SSHStoreConfig(params)
, LocalFSStoreConfig(params)
{
}
const std::string name() override { return "Experimental SSH Store with filesytem mounted"; }
std::string doc() override
{
return
#include "mounted-ssh-store.md"
;
}
std::optional<ExperimentalFeature> experimentalFeature() const override
{
return ExperimentalFeature::MountedSSHStore;
}
};
/**
* The mounted ssh store assumes that filesystems on the remote host are
* shared with the local host. This means that the remote nix store is
* available locally and is therefore treated as a local filesystem
* store.
*
* MountedSSHStore is very similar to UDSRemoteStore --- ignoring the
* superficial differnce of SSH vs Unix domain sockets, they both are
* accessing remote stores, and they both assume the store will be
* mounted in the local filesystem.
*
* The difference lies in how they manage GC roots. See addPermRoot
* below for details.
*/
class MountedSSHStore : public virtual MountedSSHStoreConfig, public virtual SSHStore, public virtual LocalFSStore
{
public:
MountedSSHStore(const std::string & scheme, const std::string & host, const Params & params)
: StoreConfig(params)
, RemoteStoreConfig(params)
, CommonSSHStoreConfig(params)
, SSHStoreConfig(params)
, LocalFSStoreConfig(params)
, MountedSSHStoreConfig(params)
, Store(params)
, RemoteStore(params)
, SSHStore(scheme, host, params)
, LocalFSStore(params)
{
extraRemoteProgramArgs = {
"--process-ops",
};
}
static std::set<std::string> uriSchemes()
{
return {"mounted-ssh-ng"};
}
std::string getUri() override
{
return *uriSchemes().begin() + "://" + host;
}
void narFromPath(const StorePath & path, Sink & sink) override
{
return LocalFSStore::narFromPath(path, sink);
}
ref<SourceAccessor> getFSAccessor(bool requireValidPath) override
{
return LocalFSStore::getFSAccessor(requireValidPath);
}
std::optional<std::string> getBuildLogExact(const StorePath & path) override
{
return LocalFSStore::getBuildLogExact(path);
}
/**
* This is the key difference from UDSRemoteStore: UDSRemote store
* has the client create the direct root, and the remote side create
* the indirect root.
*
* We could also do that, but the race conditions (will the remote
* side see the direct root the client made?) seems bigger.
*
* In addition, the remote-side will have a process associated with
* the authenticating user handling the connection (even if there
* is a system-wide daemon or similar). This process can safely make
* the direct and indirect roots without there being such a risk of
* privilege escalation / symlinks in directories owned by the
* originating requester that they cannot delete.
*/
Path addPermRoot(const StorePath & path, const Path & gcRoot) override
{
auto conn(getConnection());
conn->to << WorkerProto::Op::AddPermRoot;
WorkerProto::write(*this, *conn, path);
WorkerProto::write(*this, *conn, gcRoot);
conn.processStderr();
return readString(conn->from);
}
};
ref<RemoteStore::Connection> SSHStore::openConnection()
{
auto conn = make_ref<Connection>();
@ -98,6 +216,8 @@ ref<RemoteStore::Connection> SSHStore::openConnection()
std::string command = remoteProgram + " --stdio";
if (remoteStore.get() != "")
command += " --store " + shellEscape(remoteStore.get());
for (auto & arg : extraRemoteProgramArgs)
command += " " + shellEscape(arg);
conn->sshConn = master.startCommand(command);
conn->to = FdSink(conn->sshConn->in.get());
@ -106,5 +226,6 @@ ref<RemoteStore::Connection> SSHStore::openConnection()
}
static RegisterStoreImplementation<SSHStore, SSHStoreConfig> regSSHStore;
static RegisterStoreImplementation<MountedSSHStore, MountedSSHStoreConfig> regMountedSSHStore;
}

View file

@ -1,5 +1,8 @@
#include "ssh.hh"
#include "finally.hh"
#include "current-process.hh"
#include "environment-variables.hh"
#include "util.hh"
namespace nix {
@ -111,8 +114,10 @@ std::unique_ptr<SSHMaster::Connection> SSHMaster::startCommand(const std::string
reply = readLine(out.readSide.get());
} catch (EndOfFile & e) { }
if (reply != "started")
if (reply != "started") {
printTalkative("SSH stdout first line: %s", reply);
throw Error("failed to start SSH connection to '%s'", host);
}
}
conn->out = std::move(out.readSide);
@ -129,7 +134,6 @@ Path SSHMaster::startMaster()
if (state->sshMaster != -1) return state->socketPath;
state->socketPath = (Path) *state->tmpDir + "/ssh.sock";
Pipe out;
@ -141,7 +145,8 @@ Path SSHMaster::startMaster()
logger->pause();
Finally cleanup = [&]() { logger->resume(); };
bool wasMasterRunning = isMasterRunning();
if (isMasterRunning())
return state->socketPath;
state->sshMaster = startProcess([&]() {
restoreProcessContext();
@ -162,14 +167,14 @@ Path SSHMaster::startMaster()
out.writeSide = -1;
if (!wasMasterRunning) {
std::string reply;
try {
reply = readLine(out.readSide.get());
} catch (EndOfFile & e) { }
std::string reply;
try {
reply = readLine(out.readSide.get());
} catch (EndOfFile & e) { }
if (reply != "started")
throw Error("failed to start SSH master connection to '%s'", host);
if (reply != "started") {
printTalkative("SSH master stdout first line: %s", reply);
throw Error("failed to start SSH master connection to '%s'", host);
}
return state->socketPath;

View file

@ -1,8 +1,9 @@
#pragma once
///@file
#include "util.hh"
#include "sync.hh"
#include "processes.hh"
#include "file-system.hh"
namespace nix {

View file

@ -1,6 +1,8 @@
#include "crypto.hh"
#include "fs-accessor.hh"
#include "source-accessor.hh"
#include "globals.hh"
#include "derived-path.hh"
#include "realisation.hh"
#include "derivations.hh"
#include "store-api.hh"
#include "util.hh"
@ -14,6 +16,8 @@
// FIXME this should not be here, see TODO below on
// `addMultipleToStore`.
#include "worker-protocol.hh"
#include "signals.hh"
#include "users.hh"
#include <nlohmann/json.hpp>
#include <regex>
@ -23,13 +27,13 @@ using json = nlohmann::json;
namespace nix {
bool Store::isInStore(PathView path) const
bool StoreDirConfig::isInStore(PathView path) const
{
return isInDir(path, storeDir);
}
std::pair<StorePath, Path> Store::toStorePath(PathView path) const
std::pair<StorePath, Path> StoreDirConfig::toStorePath(PathView path) const
{
if (!isInStore(path))
throw Error("path '%1%' is not in the Nix store", path);
@ -143,25 +147,25 @@ StorePath Store::followLinksToStorePath(std::string_view path) const
*/
StorePath Store::makeStorePath(std::string_view type,
StorePath StoreDirConfig::makeStorePath(std::string_view type,
std::string_view hash, std::string_view name) const
{
/* e.g., "source:sha256:1abc...:/nix/store:foo.tar.gz" */
auto s = std::string(type) + ":" + std::string(hash)
+ ":" + storeDir + ":" + std::string(name);
auto h = compressHash(hashString(htSHA256, s), 20);
auto h = compressHash(hashString(HashAlgorithm::SHA256, s), 20);
return StorePath(h, name);
}
StorePath Store::makeStorePath(std::string_view type,
StorePath StoreDirConfig::makeStorePath(std::string_view type,
const Hash & hash, std::string_view name) const
{
return makeStorePath(type, hash.to_string(HashFormat::Base16, true), name);
}
StorePath Store::makeOutputPath(std::string_view id,
StorePath StoreDirConfig::makeOutputPath(std::string_view id,
const Hash & hash, std::string_view name) const
{
return makeStorePath("output:" + std::string { id }, hash, outputPathName(name, id));
@ -172,7 +176,7 @@ StorePath Store::makeOutputPath(std::string_view id,
hacky, but we can't put them in, say, <s2> (per the grammar above)
since that would be ambiguous. */
static std::string makeType(
const Store & store,
const StoreDirConfig & store,
std::string && type,
const StoreReferences & references)
{
@ -185,14 +189,14 @@ static std::string makeType(
}
StorePath Store::makeFixedOutputPath(std::string_view name, const FixedOutputInfo & info) const
StorePath StoreDirConfig::makeFixedOutputPath(std::string_view name, const FixedOutputInfo & info) const
{
if (info.hash.type == htSHA256 && info.method == FileIngestionMethod::Recursive) {
if (info.hash.algo == HashAlgorithm::SHA256 && info.method == FileIngestionMethod::Recursive) {
return makeStorePath(makeType(*this, "source", info.references), info.hash, name);
} else {
assert(info.references.size() == 0);
return makeStorePath("output:out",
hashString(htSHA256,
hashString(HashAlgorithm::SHA256,
"fixed:out:"
+ makeFileIngestionPrefix(info.method)
+ info.hash.to_string(HashFormat::Base16, true) + ":"),
@ -201,9 +205,9 @@ StorePath Store::makeFixedOutputPath(std::string_view name, const FixedOutputInf
}
StorePath Store::makeTextPath(std::string_view name, const TextInfo & info) const
StorePath StoreDirConfig::makeTextPath(std::string_view name, const TextInfo & info) const
{
assert(info.hash.type == htSHA256);
assert(info.hash.algo == HashAlgorithm::SHA256);
return makeStorePath(
makeType(*this, "text", StoreReferences {
.others = info.references,
@ -214,7 +218,7 @@ StorePath Store::makeTextPath(std::string_view name, const TextInfo & info) cons
}
StorePath Store::makeFixedOutputPathFromCA(std::string_view name, const ContentAddressWithReferences & ca) const
StorePath StoreDirConfig::makeFixedOutputPathFromCA(std::string_view name, const ContentAddressWithReferences & ca) const
{
// New template
return std::visit(overloaded {
@ -228,12 +232,12 @@ StorePath Store::makeFixedOutputPathFromCA(std::string_view name, const ContentA
}
std::pair<StorePath, Hash> Store::computeStorePathFromDump(
Source & dump,
std::string_view name,
FileIngestionMethod method,
HashType hashAlgo,
const StorePathSet & references) const
std::pair<StorePath, Hash> StoreDirConfig::computeStorePathFromDump(
Source & dump,
std::string_view name,
FileIngestionMethod method,
HashAlgorithm hashAlgo,
const StorePathSet & references) const
{
HashSink sink(hashAlgo);
dump.drainInto(sink);
@ -247,26 +251,26 @@ std::pair<StorePath, Hash> Store::computeStorePathFromDump(
}
StorePath Store::computeStorePathForText(
StorePath StoreDirConfig::computeStorePathForText(
std::string_view name,
std::string_view s,
const StorePathSet & references) const
{
return makeTextPath(name, TextInfo {
.hash = hashString(htSHA256, s),
.hash = hashString(HashAlgorithm::SHA256, s),
.references = references,
});
}
StorePath Store::addToStore(
std::string_view name,
const Path & _srcPath,
FileIngestionMethod method,
HashType hashAlgo,
PathFilter & filter,
RepairFlag repair,
const StorePathSet & references)
std::string_view name,
const Path & _srcPath,
FileIngestionMethod method,
HashAlgorithm hashAlgo,
PathFilter & filter,
RepairFlag repair,
const StorePathSet & references)
{
Path srcPath(absPath(_srcPath));
auto source = sinkToSource([&](Sink & sink) {
@ -401,19 +405,19 @@ digraph graphname {
}
*/
ValidPathInfo Store::addToStoreSlow(std::string_view name, const Path & srcPath,
FileIngestionMethod method, HashType hashAlgo,
std::optional<Hash> expectedCAHash)
FileIngestionMethod method, HashAlgorithm hashAlgo,
std::optional<Hash> expectedCAHash)
{
HashSink narHashSink { htSHA256 };
HashSink narHashSink { HashAlgorithm::SHA256 };
HashSink caHashSink { hashAlgo };
/* Note that fileSink and unusualHashTee must be mutually exclusive, since
they both write to caHashSink. Note that that requisite is currently true
because the former is only used in the flat case. */
RetrieveRegularNARSink fileSink { caHashSink };
RegularFileSink fileSink { caHashSink };
TeeSink unusualHashTee { narHashSink, caHashSink };
auto & narSink = method == FileIngestionMethod::Recursive && hashAlgo != htSHA256
auto & narSink = method == FileIngestionMethod::Recursive && hashAlgo != HashAlgorithm::SHA256
? static_cast<Sink &>(unusualHashTee)
: narHashSink;
@ -428,10 +432,10 @@ ValidPathInfo Store::addToStoreSlow(std::string_view name, const Path & srcPath,
information to narSink. */
TeeSource tapped { *fileSource, narSink };
ParseSink blank;
NullParseSink blank;
auto & parseSink = method == FileIngestionMethod::Flat
? fileSink
: blank;
? (ParseSink &) fileSink
: (ParseSink &) blank;
/* The information that flows from tapped (besides being replicated in
narSink), is now put in parseSink. */
@ -441,7 +445,7 @@ ValidPathInfo Store::addToStoreSlow(std::string_view name, const Path & srcPath,
finish. */
auto [narHash, narSize] = narHashSink.finish();
auto hash = method == FileIngestionMethod::Recursive && hashAlgo == htSHA256
auto hash = method == FileIngestionMethod::Recursive && hashAlgo == HashAlgorithm::SHA256
? narHash
: caHashSink.finish().first;
@ -543,8 +547,8 @@ std::map<std::string, std::optional<StorePath>> Store::queryPartialDerivationOut
return outputs;
}
OutputPathMap Store::queryDerivationOutputMap(const StorePath & path) {
auto resp = queryPartialDerivationOutputMap(path);
OutputPathMap Store::queryDerivationOutputMap(const StorePath & path, Store * evalStore) {
auto resp = queryPartialDerivationOutputMap(path, evalStore);
OutputPathMap result;
for (auto & [outName, optOutPath] : resp) {
if (!optOutPath)
@ -819,7 +823,7 @@ void Store::substitutePaths(const StorePathSet & paths)
std::vector<DerivedPath> paths2;
for (auto & path : paths)
if (!path.isDerivation())
paths2.push_back(DerivedPath::Opaque{path});
paths2.emplace_back(DerivedPath::Opaque{path});
uint64_t downloadSize, narSize;
StorePathSet willBuild, willSubstitute, unknown;
queryMissing(paths2,
@ -949,96 +953,6 @@ StorePathSet Store::exportReferences(const StorePathSet & storePaths, const Stor
return paths;
}
json Store::pathInfoToJSON(const StorePathSet & storePaths,
bool includeImpureInfo, bool showClosureSize,
HashFormat hashFormat,
AllowInvalidFlag allowInvalid)
{
json::array_t jsonList = json::array();
for (auto & storePath : storePaths) {
auto& jsonPath = jsonList.emplace_back(json::object());
try {
auto info = queryPathInfo(storePath);
jsonPath["path"] = printStorePath(info->path);
jsonPath["valid"] = true;
jsonPath["narHash"] = info->narHash.to_string(hashFormat, true);
jsonPath["narSize"] = info->narSize;
{
auto& jsonRefs = (jsonPath["references"] = json::array());
for (auto & ref : info->references)
jsonRefs.emplace_back(printStorePath(ref));
}
if (info->ca)
jsonPath["ca"] = renderContentAddress(info->ca);
std::pair<uint64_t, uint64_t> closureSizes;
if (showClosureSize) {
closureSizes = getClosureSize(info->path);
jsonPath["closureSize"] = closureSizes.first;
}
if (includeImpureInfo) {
if (info->deriver)
jsonPath["deriver"] = printStorePath(*info->deriver);
if (info->registrationTime)
jsonPath["registrationTime"] = info->registrationTime;
if (info->ultimate)
jsonPath["ultimate"] = info->ultimate;
if (!info->sigs.empty()) {
for (auto & sig : info->sigs)
jsonPath["signatures"].push_back(sig);
}
auto narInfo = std::dynamic_pointer_cast<const NarInfo>(
std::shared_ptr<const ValidPathInfo>(info));
if (narInfo) {
if (!narInfo->url.empty())
jsonPath["url"] = narInfo->url;
if (narInfo->fileHash)
jsonPath["downloadHash"] = narInfo->fileHash->to_string(hashFormat, true);
if (narInfo->fileSize)
jsonPath["downloadSize"] = narInfo->fileSize;
if (showClosureSize)
jsonPath["closureDownloadSize"] = closureSizes.second;
}
}
} catch (InvalidPath &) {
jsonPath["path"] = printStorePath(storePath);
jsonPath["valid"] = false;
}
}
return jsonList;
}
std::pair<uint64_t, uint64_t> Store::getClosureSize(const StorePath & storePath)
{
uint64_t totalNarSize = 0, totalDownloadSize = 0;
StorePathSet closure;
computeFSClosure(storePath, closure, false, false);
for (auto & p : closure) {
auto info = queryPathInfo(p);
totalNarSize += info->narSize;
auto narInfo = std::dynamic_pointer_cast<const NarInfo>(
std::shared_ptr<const ValidPathInfo>(info));
if (narInfo)
totalDownloadSize += narInfo->fileSize;
}
return {totalNarSize, totalDownloadSize};
}
const Store::Stats & Store::getStats()
{
@ -1291,7 +1205,7 @@ std::optional<ValidPathInfo> decodeValidPathInfo(const Store & store, std::istre
if (!hashGiven) {
std::string s;
getline(str, s);
auto narHash = Hash::parseAny(s, htSHA256);
auto narHash = Hash::parseAny(s, HashAlgorithm::SHA256);
getline(str, s);
auto narSize = string2Int<uint64_t>(s);
if (!narSize) throw Error("number expected");
@ -1315,7 +1229,7 @@ std::optional<ValidPathInfo> decodeValidPathInfo(const Store & store, std::istre
}
std::string Store::showPaths(const StorePathSet & paths)
std::string StoreDirConfig::showPaths(const StorePathSet & paths)
{
std::string s;
for (auto & i : paths) {
@ -1338,12 +1252,12 @@ Derivation Store::derivationFromPath(const StorePath & drvPath)
return readDerivation(drvPath);
}
Derivation readDerivationCommon(Store& store, const StorePath& drvPath, bool requireValidPath)
static Derivation readDerivationCommon(Store & store, const StorePath & drvPath, bool requireValidPath)
{
auto accessor = store.getFSAccessor();
auto accessor = store.getFSAccessor(requireValidPath);
try {
return parseDerivation(store,
accessor->readFile(store.printStorePath(drvPath), requireValidPath),
accessor->readFile(CanonPath(store.printStorePath(drvPath))),
Derivation::nameFromPath(drvPath));
} catch (FormatError & e) {
throw Error("error parsing derivation '%s': %s", store.printStorePath(drvPath), e.msg());

View file

@ -1,8 +1,6 @@
#pragma once
///@file
#include "nar-info.hh"
#include "realisation.hh"
#include "path.hh"
#include "derived-path.hh"
#include "hash.hh"
@ -14,6 +12,7 @@
#include "config.hh"
#include "path-info.hh"
#include "repair-flag.hh"
#include "store-dir-config.hh"
#include <nlohmann/json_fwd.hpp>
#include <atomic>
@ -30,7 +29,7 @@
namespace nix {
/**
* About the class hierarchy of the store implementations:
* About the class hierarchy of the store types:
*
* Each store type `Foo` consists of two classes:
*
@ -64,13 +63,17 @@ MakeError(InvalidPath, Error);
MakeError(Unsupported, Error);
MakeError(SubstituteGone, Error);
MakeError(SubstituterDisabled, Error);
MakeError(BadStorePath, Error);
MakeError(InvalidStoreURI, Error);
struct Realisation;
struct RealisedPath;
struct DrvOutput;
struct BasicDerivation;
struct Derivation;
class FSAccessor;
struct SourceAccessor;
class NarInfoDiskCache;
class Store;
@ -80,7 +83,6 @@ typedef std::map<std::string, StorePath> OutputPathMap;
enum CheckSigsFlag : bool { NoCheckSigs = false, CheckSigs = true };
enum SubstituteFlag : bool { NoSubstitute = false, Substitute = true };
enum AllowInvalidFlag : bool { DisallowInvalid = false, AllowInvalid = true };
/**
* Magic header of exportPath() output (obsolete).
@ -97,11 +99,11 @@ struct KeyedBuildResult;
typedef std::map<StorePath, std::optional<ContentAddress>> StorePathCAMap;
struct StoreConfig : public Config
struct StoreConfig : public StoreDirConfig
{
typedef std::map<std::string, std::string> Params;
using Config::Config;
using StoreDirConfig::StoreDirConfig;
StoreConfig() = delete;
@ -131,15 +133,6 @@ struct StoreConfig : public Config
return std::nullopt;
}
const PathSetting storeDir_{this, settings.nixStore,
"store",
R"(
Logical location of the Nix store, usually
`/nix/store`. Note that you can only copy store paths
between stores if they have the same `store` setting.
)"};
const Path storeDir = storeDir_;
const Setting<int> pathInfoCacheSize{this, 65536, "path-info-cache-size",
"Size of the in-memory store path metadata cache."};
@ -168,7 +161,10 @@ struct StoreConfig : public Config
Optional [system features](@docroot@/command-ref/conf-file.md#conf-system-features) available on the system this store uses to build derivations.
Example: `"kvm"`
)" };
)",
{},
// Don't document the machine-specific default value
false};
};
class Store : public std::enable_shared_from_this<Store>, public virtual StoreConfig
@ -224,45 +220,6 @@ public:
virtual std::string getUri() = 0;
StorePath parseStorePath(std::string_view path) const;
std::optional<StorePath> maybeParseStorePath(std::string_view path) const;
std::string printStorePath(const StorePath & path) const;
/**
* Deprecated
*
* \todo remove
*/
StorePathSet parseStorePathSet(const PathSet & paths) const;
PathSet printStorePathSet(const StorePathSet & path) const;
/**
* Display a set of paths in human-readable form (i.e., between quotes
* and separated by commas).
*/
std::string showPaths(const StorePathSet & paths);
/**
* @return true if path is in the Nix store (but not the Nix
* store itself).
*/
bool isInStore(PathView path) const;
/**
* @return true if path is a store path, i.e. a direct child of the
* Nix store.
*/
bool isStorePath(std::string_view path) const;
/**
* Split a path like /nix/store/<hash>-<name>/<bla> into
* /nix/store/<hash>-<name> and /<bla>.
*/
std::pair<StorePath, Path> toStorePath(PathView path) const;
/**
* Follow symlinks until we end up with a path in the Nix store.
*/
@ -274,55 +231,6 @@ public:
*/
StorePath followLinksToStorePath(std::string_view path) const;
/**
* Constructs a unique store path name.
*/
StorePath makeStorePath(std::string_view type,
std::string_view hash, std::string_view name) const;
StorePath makeStorePath(std::string_view type,
const Hash & hash, std::string_view name) const;
StorePath makeOutputPath(std::string_view id,
const Hash & hash, std::string_view name) const;
StorePath makeFixedOutputPath(std::string_view name, const FixedOutputInfo & info) const;
StorePath makeTextPath(std::string_view name, const TextInfo & info) const;
StorePath makeFixedOutputPathFromCA(std::string_view name, const ContentAddressWithReferences & ca) const;
/**
* Read-only variant of addToStoreFromDump(). It returns the store
* path to which a NAR or flat file would be written.
*/
std::pair<StorePath, Hash> computeStorePathFromDump(
Source & dump,
std::string_view name,
FileIngestionMethod method = FileIngestionMethod::Recursive,
HashType hashAlgo = htSHA256,
const StorePathSet & references = {}) const;
/**
* Preparatory part of addTextToStore().
*
* !!! Computation of the path should take the references given to
* addTextToStore() into account, otherwise we have a (relatively
* minor) security hole: a caller can register a source file with
* bogus references. If there are too many references, the path may
* not be garbage collected when it has to be (not really a problem,
* the caller could create a root anyway), or it may be garbage
* collected when it shouldn't be (more serious).
*
* Hashing the references would solve this (bogus references would
* simply yield a different store path, so other users wouldn't be
* affected), but it has some backwards compatibility issues (the
* hashing scheme changes), so I'm not doing that for now.
*/
StorePath computeStorePathForText(
std::string_view name,
std::string_view s,
const StorePathSet & references) const;
/**
* Check whether a path is valid.
*/
@ -461,7 +369,7 @@ public:
* Query the mapping outputName=>outputPath for the given derivation.
* Assume every output has a mapping and throw an exception otherwise.
*/
OutputPathMap queryDerivationOutputMap(const StorePath & path);
OutputPathMap queryDerivationOutputMap(const StorePath & path, Store * evalStore = nullptr);
/**
* Query the full store path given the hash part of a valid store
@ -519,13 +427,13 @@ public:
* libutil/archive.hh).
*/
virtual StorePath addToStore(
std::string_view name,
const Path & srcPath,
FileIngestionMethod method = FileIngestionMethod::Recursive,
HashType hashAlgo = htSHA256,
PathFilter & filter = defaultPathFilter,
RepairFlag repair = NoRepair,
const StorePathSet & references = StorePathSet());
std::string_view name,
const Path & srcPath,
FileIngestionMethod method = FileIngestionMethod::Recursive,
HashAlgorithm hashAlgo = HashAlgorithm::SHA256,
PathFilter & filter = defaultPathFilter,
RepairFlag repair = NoRepair,
const StorePathSet & references = StorePathSet());
/**
* Copy the contents of a path to the store and register the
@ -533,8 +441,8 @@ public:
* memory.
*/
ValidPathInfo addToStoreSlow(std::string_view name, const Path & srcPath,
FileIngestionMethod method = FileIngestionMethod::Recursive, HashType hashAlgo = htSHA256,
std::optional<Hash> expectedCAHash = {});
FileIngestionMethod method = FileIngestionMethod::Recursive, HashAlgorithm hashAlgo = HashAlgorithm::SHA256,
std::optional<Hash> expectedCAHash = {});
/**
* Like addToStore(), but the contents of the path are contained
@ -546,8 +454,8 @@ public:
* \todo remove?
*/
virtual StorePath addToStoreFromDump(Source & dump, std::string_view name,
FileIngestionMethod method = FileIngestionMethod::Recursive, HashType hashAlgo = htSHA256, RepairFlag repair = NoRepair,
const StorePathSet & references = StorePathSet())
FileIngestionMethod method = FileIngestionMethod::Recursive, HashAlgorithm hashAlgo = HashAlgorithm::SHA256, RepairFlag repair = NoRepair,
const StorePathSet & references = StorePathSet())
{ unsupported("addToStoreFromDump"); }
/**
@ -665,28 +573,6 @@ public:
std::string makeValidityRegistration(const StorePathSet & paths,
bool showDerivers, bool showHash);
/**
* Write a JSON representation of store path metadata, such as the
* hash and the references.
*
* @param includeImpureInfo If true, variable elements such as the
* registration time are included.
*
* @param showClosureSize If true, the closure size of each path is
* included.
*/
nlohmann::json pathInfoToJSON(const StorePathSet & storePaths,
bool includeImpureInfo, bool showClosureSize,
HashFormat hashFormat = HashFormat::Base32,
AllowInvalidFlag allowInvalid = DisallowInvalid);
/**
* @return the size of the closure of the specified path, that is,
* the sum of the size of the NAR serialisation of each path in the
* closure.
*/
std::pair<uint64_t, uint64_t> getClosureSize(const StorePath & storePath);
/**
* Optimise the disk space usage of the Nix store by hard-linking files
* with the same contents.
@ -703,7 +589,7 @@ public:
/**
* @return An object to access files in the Nix store.
*/
virtual ref<FSAccessor> getFSAccessor() = 0;
virtual ref<SourceAccessor> getFSAccessor(bool requireValidPath = true) = 0;
/**
* Repair the contents of the given path by redownloading it using
@ -908,7 +794,7 @@ void copyStorePath(
*/
std::map<StorePath, StorePath> copyPaths(
Store & srcStore, Store & dstStore,
const RealisedPath::Set &,
const std::set<RealisedPath> &,
RepairFlag repair = NoRepair,
CheckSigsFlag checkSigs = CheckSigs,
SubstituteFlag substitute = NoSubstitute);
@ -925,7 +811,7 @@ std::map<StorePath, StorePath> copyPaths(
*/
void copyClosure(
Store & srcStore, Store & dstStore,
const RealisedPath::Set & paths,
const std::set<RealisedPath> & paths,
RepairFlag repair = NoRepair,
CheckSigsFlag checkSigs = CheckSigs,
SubstituteFlag substitute = NoSubstitute);
@ -982,7 +868,7 @@ OutputPathMap resolveDerivedPath(Store &, const DerivedPath::Built &, Store * ev
* - ssh://[user@]<host>: A remote Nix store accessed by running
* nix-store --serve via SSH.
*
* You can pass parameters to the store implementation by appending
* You can pass parameters to the store type by appending
* ?key=value&key=value&... to the URI.
*/
ref<Store> openStore(const std::string & uri = settings.storeUri.get(),

View file

@ -0,0 +1,126 @@
#pragma once
#include "path.hh"
#include "hash.hh"
#include "content-address.hh"
#include "globals.hh"
#include "config.hh"
#include <map>
#include <string>
#include <variant>
namespace nix {
MakeError(BadStorePath, Error);
struct StoreDirConfig : public Config
{
using Config::Config;
StoreDirConfig() = delete;
virtual ~StoreDirConfig() = default;
const PathSetting storeDir_{this, settings.nixStore,
"store",
R"(
Logical location of the Nix store, usually
`/nix/store`. Note that you can only copy store paths
between stores if they have the same `store` setting.
)"};
const Path storeDir = storeDir_;
// pure methods
StorePath parseStorePath(std::string_view path) const;
std::optional<StorePath> maybeParseStorePath(std::string_view path) const;
std::string printStorePath(const StorePath & path) const;
/**
* Deprecated
*
* \todo remove
*/
StorePathSet parseStorePathSet(const PathSet & paths) const;
PathSet printStorePathSet(const StorePathSet & path) const;
/**
* Display a set of paths in human-readable form (i.e., between quotes
* and separated by commas).
*/
std::string showPaths(const StorePathSet & paths);
/**
* @return true if path is in the Nix store (but not the Nix
* store itself).
*/
bool isInStore(PathView path) const;
/**
* @return true if path is a store path, i.e. a direct child of the
* Nix store.
*/
bool isStorePath(std::string_view path) const;
/**
* Split a path like /nix/store/<hash>-<name>/<bla> into
* /nix/store/<hash>-<name> and /<bla>.
*/
std::pair<StorePath, Path> toStorePath(PathView path) const;
/**
* Constructs a unique store path name.
*/
StorePath makeStorePath(std::string_view type,
std::string_view hash, std::string_view name) const;
StorePath makeStorePath(std::string_view type,
const Hash & hash, std::string_view name) const;
StorePath makeOutputPath(std::string_view id,
const Hash & hash, std::string_view name) const;
StorePath makeFixedOutputPath(std::string_view name, const FixedOutputInfo & info) const;
StorePath makeTextPath(std::string_view name, const TextInfo & info) const;
StorePath makeFixedOutputPathFromCA(std::string_view name, const ContentAddressWithReferences & ca) const;
/**
* Read-only variant of addToStoreFromDump(). It returns the store
* path to which a NAR or flat file would be written.
*/
std::pair<StorePath, Hash> computeStorePathFromDump(
Source & dump,
std::string_view name,
FileIngestionMethod method = FileIngestionMethod::Recursive,
HashAlgorithm hashAlgo = HashAlgorithm::SHA256,
const StorePathSet & references = {}) const;
/**
* Preparatory part of addTextToStore().
*
* !!! Computation of the path should take the references given to
* addTextToStore() into account, otherwise we have a (relatively
* minor) security hole: a caller can register a source file with
* bogus references. If there are too many references, the path may
* not be garbage collected when it has to be (not really a problem,
* the caller could create a root anyway), or it may be garbage
* collected when it shouldn't be (more serious).
*
* Hashing the references would solve this (bogus references would
* simply yield a different store path, so other users wouldn't be
* affected), but it has some backwards compatibility issues (the
* hashing scheme changes), so I'm not doing that for now.
*/
StorePath computeStorePathForText(
std::string_view name,
std::string_view s,
const StorePathSet & references) const;
};
}

View file

@ -1,23 +0,0 @@
#pragma once
///@file
namespace nix {
/**
* The path to the `unit-test-data` directory. See the contributing
* guide in the manual for further details.
*/
static Path getUnitTestData() {
return getEnv("_NIX_TEST_UNIT_DATA").value();
}
/**
* Whether we should update "golden masters" instead of running tests
* against them. See the contributing guide in the manual for further
* details.
*/
static bool testAccept() {
return getEnv("_NIX_TEST_ACCEPT") == "1";
}
}

View file

@ -1,205 +0,0 @@
#include <regex>
#include <nlohmann/json.hpp>
#include <gtest/gtest.h>
#include "common-protocol.hh"
#include "common-protocol-impl.hh"
#include "build-result.hh"
#include "tests/protocol.hh"
#include "tests/characterization.hh"
namespace nix {
const char commonProtoDir[] = "common-protocol";
class CommonProtoTest : public ProtoTest<CommonProto, commonProtoDir>
{
public:
/**
* Golden test for `T` reading
*/
template<typename T>
void readTest(PathView testStem, T value)
{
if (testAccept())
{
GTEST_SKIP() << "Cannot read golden master because another test is also updating it";
}
else
{
auto expected = readFile(goldenMaster(testStem));
T got = ({
StringSource from { expected };
CommonProto::Serialise<T>::read(
*store,
CommonProto::ReadConn { .from = from });
});
ASSERT_EQ(got, value);
}
}
/**
* Golden test for `T` write
*/
template<typename T>
void writeTest(PathView testStem, const T & value)
{
auto file = goldenMaster(testStem);
StringSink to;
CommonProto::write(
*store,
CommonProto::WriteConn { .to = to },
value);
if (testAccept())
{
createDirs(dirOf(file));
writeFile(file, to.s);
GTEST_SKIP() << "Updating golden master";
}
else
{
auto expected = readFile(file);
ASSERT_EQ(to.s, expected);
}
}
};
#define CHARACTERIZATION_TEST(NAME, STEM, VALUE) \
TEST_F(CommonProtoTest, NAME ## _read) { \
readTest(STEM, VALUE); \
} \
TEST_F(CommonProtoTest, NAME ## _write) { \
writeTest(STEM, VALUE); \
}
CHARACTERIZATION_TEST(
string,
"string",
(std::tuple<std::string, std::string, std::string, std::string, std::string> {
"",
"hi",
"white rabbit",
"大白兔",
"oh no \0\0\0 what was that!",
}))
CHARACTERIZATION_TEST(
storePath,
"store-path",
(std::tuple<StorePath, StorePath> {
StorePath { "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo" },
StorePath { "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo-bar" },
}))
CHARACTERIZATION_TEST(
contentAddress,
"content-address",
(std::tuple<ContentAddress, ContentAddress, ContentAddress> {
ContentAddress {
.method = TextIngestionMethod {},
.hash = hashString(HashType::htSHA256, "Derive(...)"),
},
ContentAddress {
.method = FileIngestionMethod::Flat,
.hash = hashString(HashType::htSHA1, "blob blob..."),
},
ContentAddress {
.method = FileIngestionMethod::Recursive,
.hash = hashString(HashType::htSHA256, "(...)"),
},
}))
CHARACTERIZATION_TEST(
drvOutput,
"drv-output",
(std::tuple<DrvOutput, DrvOutput> {
{
.drvHash = Hash::parseSRI("sha256-FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc="),
.outputName = "baz",
},
DrvOutput {
.drvHash = Hash::parseSRI("sha256-b4afnqKCO9oWXgYHb9DeQ2berSwOjS27rSd9TxXDc/U="),
.outputName = "quux",
},
}))
CHARACTERIZATION_TEST(
realisation,
"realisation",
(std::tuple<Realisation, Realisation> {
Realisation {
.id = DrvOutput {
.drvHash = Hash::parseSRI("sha256-FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc="),
.outputName = "baz",
},
.outPath = StorePath { "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo" },
.signatures = { "asdf", "qwer" },
},
Realisation {
.id = {
.drvHash = Hash::parseSRI("sha256-FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc="),
.outputName = "baz",
},
.outPath = StorePath { "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo" },
.signatures = { "asdf", "qwer" },
.dependentRealisations = {
{
DrvOutput {
.drvHash = Hash::parseSRI("sha256-b4afnqKCO9oWXgYHb9DeQ2berSwOjS27rSd9TxXDc/U="),
.outputName = "quux",
},
StorePath { "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo" },
},
},
},
}))
CHARACTERIZATION_TEST(
vector,
"vector",
(std::tuple<std::vector<std::string>, std::vector<std::string>, std::vector<std::string>, std::vector<std::vector<std::string>>> {
{ },
{ "" },
{ "", "foo", "bar" },
{ {}, { "" }, { "", "1", "2" } },
}))
CHARACTERIZATION_TEST(
set,
"set",
(std::tuple<std::set<std::string>, std::set<std::string>, std::set<std::string>, std::set<std::set<std::string>>> {
{ },
{ "" },
{ "", "foo", "bar" },
{ {}, { "" }, { "", "1", "2" } },
}))
CHARACTERIZATION_TEST(
optionalStorePath,
"optional-store-path",
(std::tuple<std::optional<StorePath>, std::optional<StorePath>> {
std::nullopt,
std::optional {
StorePath { "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo-bar" },
},
}))
CHARACTERIZATION_TEST(
optionalContentAddress,
"optional-content-address",
(std::tuple<std::optional<ContentAddress>, std::optional<ContentAddress>> {
std::nullopt,
std::optional {
ContentAddress {
.method = FileIngestionMethod::Flat,
.hash = hashString(HashType::htSHA1, "blob blob..."),
},
},
}))
}

View file

@ -1,369 +0,0 @@
#include <nlohmann/json.hpp>
#include <gtest/gtest.h>
#include "experimental-features.hh"
#include "derivations.hh"
#include "tests/libstore.hh"
namespace nix {
class DerivationTest : public LibStoreTest
{
public:
/**
* We set these in tests rather than the regular globals so we don't have
* to worry about race conditions if the tests run concurrently.
*/
ExperimentalFeatureSettings mockXpSettings;
};
class CaDerivationTest : public DerivationTest
{
void SetUp() override
{
mockXpSettings.set("experimental-features", "ca-derivations");
}
};
class DynDerivationTest : public DerivationTest
{
void SetUp() override
{
mockXpSettings.set("experimental-features", "dynamic-derivations ca-derivations");
}
};
class ImpureDerivationTest : public DerivationTest
{
void SetUp() override
{
mockXpSettings.set("experimental-features", "impure-derivations");
}
};
TEST_F(DerivationTest, BadATerm_version) {
ASSERT_THROW(
parseDerivation(
*store,
R"(DrvWithVersion("invalid-version",[],[("/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep2.drv",["cat","dog"])],["/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep1"],"wasm-sel4","foo",["bar","baz"],[("BIG_BAD","WOLF")]))",
"whatever",
mockXpSettings),
FormatError);
}
TEST_F(DynDerivationTest, BadATerm_oldVersionDynDeps) {
ASSERT_THROW(
parseDerivation(
*store,
R"(Derive([],[("/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep2.drv",(["cat","dog"],[("cat",["kitten"]),("goose",["gosling"])]))],["/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep1"],"wasm-sel4","foo",["bar","baz"],[("BIG_BAD","WOLF")]))",
"dyn-dep-derivation",
mockXpSettings),
FormatError);
}
#define TEST_JSON(FIXTURE, NAME, STR, VAL, DRV_NAME, OUTPUT_NAME) \
TEST_F(FIXTURE, DerivationOutput_ ## NAME ## _to_json) { \
using nlohmann::literals::operator "" _json; \
ASSERT_EQ( \
STR ## _json, \
(DerivationOutput { VAL }).toJSON( \
*store, \
DRV_NAME, \
OUTPUT_NAME)); \
} \
\
TEST_F(FIXTURE, DerivationOutput_ ## NAME ## _from_json) { \
using nlohmann::literals::operator "" _json; \
ASSERT_EQ( \
DerivationOutput { VAL }, \
DerivationOutput::fromJSON( \
*store, \
DRV_NAME, \
OUTPUT_NAME, \
STR ## _json, \
mockXpSettings)); \
}
TEST_JSON(DerivationTest, inputAddressed,
R"({
"path": "/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-drv-name-output-name"
})",
(DerivationOutput::InputAddressed {
.path = store->parseStorePath("/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-drv-name-output-name"),
}),
"drv-name", "output-name")
TEST_JSON(DerivationTest, caFixedFlat,
R"({
"hashAlgo": "sha256",
"hash": "894517c9163c896ec31a2adbd33c0681fd5f45b2c0ef08a64c92a03fb97f390f",
"path": "/nix/store/rhcg9h16sqvlbpsa6dqm57sbr2al6nzg-drv-name-output-name"
})",
(DerivationOutput::CAFixed {
.ca = {
.method = FileIngestionMethod::Flat,
.hash = Hash::parseAnyPrefixed("sha256-iUUXyRY8iW7DGirb0zwGgf1fRbLA7wimTJKgP7l/OQ8="),
},
}),
"drv-name", "output-name")
TEST_JSON(DerivationTest, caFixedNAR,
R"({
"hashAlgo": "r:sha256",
"hash": "894517c9163c896ec31a2adbd33c0681fd5f45b2c0ef08a64c92a03fb97f390f",
"path": "/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-drv-name-output-name"
})",
(DerivationOutput::CAFixed {
.ca = {
.method = FileIngestionMethod::Recursive,
.hash = Hash::parseAnyPrefixed("sha256-iUUXyRY8iW7DGirb0zwGgf1fRbLA7wimTJKgP7l/OQ8="),
},
}),
"drv-name", "output-name")
TEST_JSON(DynDerivationTest, caFixedText,
R"({
"hashAlgo": "text:sha256",
"hash": "894517c9163c896ec31a2adbd33c0681fd5f45b2c0ef08a64c92a03fb97f390f",
"path": "/nix/store/6s1zwabh956jvhv4w9xcdb5jiyanyxg1-drv-name-output-name"
})",
(DerivationOutput::CAFixed {
.ca = {
.hash = Hash::parseAnyPrefixed("sha256-iUUXyRY8iW7DGirb0zwGgf1fRbLA7wimTJKgP7l/OQ8="),
},
}),
"drv-name", "output-name")
TEST_JSON(CaDerivationTest, caFloating,
R"({
"hashAlgo": "r:sha256"
})",
(DerivationOutput::CAFloating {
.method = FileIngestionMethod::Recursive,
.hashType = htSHA256,
}),
"drv-name", "output-name")
TEST_JSON(DerivationTest, deferred,
R"({ })",
DerivationOutput::Deferred { },
"drv-name", "output-name")
TEST_JSON(ImpureDerivationTest, impure,
R"({
"hashAlgo": "r:sha256",
"impure": true
})",
(DerivationOutput::Impure {
.method = FileIngestionMethod::Recursive,
.hashType = htSHA256,
}),
"drv-name", "output-name")
#undef TEST_JSON
#define TEST_JSON(FIXTURE, NAME, STR, VAL) \
TEST_F(FIXTURE, Derivation_ ## NAME ## _to_json) { \
using nlohmann::literals::operator "" _json; \
ASSERT_EQ( \
STR ## _json, \
(VAL).toJSON(*store)); \
} \
\
TEST_F(FIXTURE, Derivation_ ## NAME ## _from_json) { \
using nlohmann::literals::operator "" _json; \
ASSERT_EQ( \
(VAL), \
Derivation::fromJSON( \
*store, \
STR ## _json, \
mockXpSettings)); \
}
#define TEST_ATERM(FIXTURE, NAME, STR, VAL, DRV_NAME) \
TEST_F(FIXTURE, Derivation_ ## NAME ## _to_aterm) { \
ASSERT_EQ( \
STR, \
(VAL).unparse(*store, false)); \
} \
\
TEST_F(FIXTURE, Derivation_ ## NAME ## _from_aterm) { \
auto parsed = parseDerivation( \
*store, \
STR, \
DRV_NAME, \
mockXpSettings); \
ASSERT_EQ( \
(VAL).toJSON(*store), \
parsed.toJSON(*store)); \
ASSERT_EQ( \
(VAL), \
parsed); \
}
Derivation makeSimpleDrv(const Store & store) {
Derivation drv;
drv.name = "simple-derivation";
drv.inputSrcs = {
store.parseStorePath("/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep1"),
};
drv.inputDrvs = {
.map = {
{
store.parseStorePath("/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep2.drv"),
{
.value = {
"cat",
"dog",
},
},
},
},
};
drv.platform = "wasm-sel4";
drv.builder = "foo";
drv.args = {
"bar",
"baz",
};
drv.env = {
{
"BIG_BAD",
"WOLF",
},
};
return drv;
}
TEST_JSON(DerivationTest, simple,
R"({
"name": "simple-derivation",
"inputSrcs": [
"/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep1"
],
"inputDrvs": {
"/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep2.drv": {
"dynamicOutputs": {},
"outputs": [
"cat",
"dog"
]
}
},
"system": "wasm-sel4",
"builder": "foo",
"args": [
"bar",
"baz"
],
"env": {
"BIG_BAD": "WOLF"
},
"outputs": {}
})",
makeSimpleDrv(*store))
TEST_ATERM(DerivationTest, simple,
R"(Derive([],[("/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep2.drv",["cat","dog"])],["/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep1"],"wasm-sel4","foo",["bar","baz"],[("BIG_BAD","WOLF")]))",
makeSimpleDrv(*store),
"simple-derivation")
Derivation makeDynDepDerivation(const Store & store) {
Derivation drv;
drv.name = "dyn-dep-derivation";
drv.inputSrcs = {
store.parseStorePath("/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep1"),
};
drv.inputDrvs = {
.map = {
{
store.parseStorePath("/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep2.drv"),
DerivedPathMap<StringSet>::ChildNode {
.value = {
"cat",
"dog",
},
.childMap = {
{
"cat",
DerivedPathMap<StringSet>::ChildNode {
.value = {
"kitten",
},
},
},
{
"goose",
DerivedPathMap<StringSet>::ChildNode {
.value = {
"gosling",
},
},
},
},
},
},
},
};
drv.platform = "wasm-sel4";
drv.builder = "foo";
drv.args = {
"bar",
"baz",
};
drv.env = {
{
"BIG_BAD",
"WOLF",
},
};
return drv;
}
TEST_JSON(DynDerivationTest, dynDerivationDeps,
R"({
"name": "dyn-dep-derivation",
"inputSrcs": [
"/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep1"
],
"inputDrvs": {
"/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep2.drv": {
"dynamicOutputs": {
"cat": {
"dynamicOutputs": {},
"outputs": ["kitten"]
},
"goose": {
"dynamicOutputs": {},
"outputs": ["gosling"]
}
},
"outputs": [
"cat",
"dog"
]
}
},
"system": "wasm-sel4",
"builder": "foo",
"args": [
"bar",
"baz"
],
"env": {
"BIG_BAD": "WOLF"
},
"outputs": {}
})",
makeDynDepDerivation(*store))
TEST_ATERM(DynDerivationTest, dynDerivationDeps,
R"(DrvWithVersion("xp-dyn-drv",[],[("/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep2.drv",(["cat","dog"],[("cat",["kitten"]),("goose",["gosling"])]))],["/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep1"],"wasm-sel4","foo",["bar","baz"],[("BIG_BAD","WOLF")]))",
makeDynDepDerivation(*store),
"dyn-dep-derivation")
#undef TEST_JSON
#undef TEST_ATERM
}

View file

@ -1,153 +0,0 @@
#include <regex>
#include <nlohmann/json.hpp>
#include <gtest/gtest.h>
#include <rapidcheck/gtest.h>
#include "tests/derived-path.hh"
#include "tests/libstore.hh"
namespace rc {
using namespace nix;
Gen<DerivedPath::Opaque> Arbitrary<DerivedPath::Opaque>::arbitrary()
{
return gen::just(DerivedPath::Opaque {
.path = *gen::arbitrary<StorePath>(),
});
}
Gen<SingleDerivedPath::Built> Arbitrary<SingleDerivedPath::Built>::arbitrary()
{
return gen::just(SingleDerivedPath::Built {
.drvPath = make_ref<SingleDerivedPath>(*gen::arbitrary<SingleDerivedPath>()),
.output = (*gen::arbitrary<StorePathName>()).name,
});
}
Gen<DerivedPath::Built> Arbitrary<DerivedPath::Built>::arbitrary()
{
return gen::just(DerivedPath::Built {
.drvPath = make_ref<SingleDerivedPath>(*gen::arbitrary<SingleDerivedPath>()),
.outputs = *gen::arbitrary<OutputsSpec>(),
});
}
Gen<SingleDerivedPath> Arbitrary<SingleDerivedPath>::arbitrary()
{
switch (*gen::inRange<uint8_t>(0, std::variant_size_v<SingleDerivedPath::Raw>)) {
case 0:
return gen::just<SingleDerivedPath>(*gen::arbitrary<SingleDerivedPath::Opaque>());
case 1:
return gen::just<SingleDerivedPath>(*gen::arbitrary<SingleDerivedPath::Built>());
default:
assert(false);
}
}
Gen<DerivedPath> Arbitrary<DerivedPath>::arbitrary()
{
switch (*gen::inRange<uint8_t>(0, std::variant_size_v<DerivedPath::Raw>)) {
case 0:
return gen::just<DerivedPath>(*gen::arbitrary<DerivedPath::Opaque>());
case 1:
return gen::just<DerivedPath>(*gen::arbitrary<DerivedPath::Built>());
default:
assert(false);
}
}
}
namespace nix {
class DerivedPathTest : public LibStoreTest
{
};
/**
* Round trip (string <-> data structure) test for
* `DerivedPath::Opaque`.
*/
TEST_F(DerivedPathTest, opaque) {
std::string_view opaque = "/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x";
auto elem = DerivedPath::parse(*store, opaque);
auto * p = std::get_if<DerivedPath::Opaque>(&elem);
ASSERT_TRUE(p);
ASSERT_EQ(p->path, store->parseStorePath(opaque));
ASSERT_EQ(elem.to_string(*store), opaque);
}
/**
* Round trip (string <-> data structure) test for a simpler
* `DerivedPath::Built`.
*/
TEST_F(DerivedPathTest, built_opaque) {
std::string_view built = "/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x.drv^bar,foo";
auto elem = DerivedPath::parse(*store, built);
auto * p = std::get_if<DerivedPath::Built>(&elem);
ASSERT_TRUE(p);
ASSERT_EQ(p->outputs, ((OutputsSpec) OutputsSpec::Names { "foo", "bar" }));
ASSERT_EQ(*p->drvPath, ((SingleDerivedPath) SingleDerivedPath::Opaque {
.path = store->parseStorePath(built.substr(0, 49)),
}));
ASSERT_EQ(elem.to_string(*store), built);
}
/**
* Round trip (string <-> data structure) test for a more complex,
* inductive `DerivedPath::Built`.
*/
TEST_F(DerivedPathTest, built_built) {
/**
* We set these in tests rather than the regular globals so we don't have
* to worry about race conditions if the tests run concurrently.
*/
ExperimentalFeatureSettings mockXpSettings;
mockXpSettings.set("experimental-features", "dynamic-derivations ca-derivations");
std::string_view built = "/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x.drv^foo^bar,baz";
auto elem = DerivedPath::parse(*store, built, mockXpSettings);
auto * p = std::get_if<DerivedPath::Built>(&elem);
ASSERT_TRUE(p);
ASSERT_EQ(p->outputs, ((OutputsSpec) OutputsSpec::Names { "bar", "baz" }));
auto * drvPath = std::get_if<SingleDerivedPath::Built>(&*p->drvPath);
ASSERT_TRUE(drvPath);
ASSERT_EQ(drvPath->output, "foo");
ASSERT_EQ(*drvPath->drvPath, ((SingleDerivedPath) SingleDerivedPath::Opaque {
.path = store->parseStorePath(built.substr(0, 49)),
}));
ASSERT_EQ(elem.to_string(*store), built);
}
/**
* Without the right experimental features enabled, we cannot parse a
* complex inductive derived path.
*/
TEST_F(DerivedPathTest, built_built_xp) {
ASSERT_THROW(
DerivedPath::parse(*store, "/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x.drv^foo^bar,baz"),
MissingExperimentalFeature);
}
#ifndef COVERAGE
RC_GTEST_FIXTURE_PROP(
DerivedPathTest,
prop_legacy_round_rip,
(const DerivedPath & o))
{
RC_ASSERT(o == DerivedPath::parseLegacy(*store, o.to_string_legacy(*store)));
}
RC_GTEST_FIXTURE_PROP(
DerivedPathTest,
prop_round_rip,
(const DerivedPath & o))
{
RC_ASSERT(o == DerivedPath::parse(*store, o.to_string(*store)));
}
#endif
}

View file

@ -1,39 +0,0 @@
#pragma once
///@file
#include <rapidcheck/gen/Arbitrary.h>
#include <derived-path.hh>
#include "tests/path.hh"
#include "tests/outputs-spec.hh"
namespace rc {
using namespace nix;
template<>
struct Arbitrary<SingleDerivedPath::Opaque> {
static Gen<SingleDerivedPath::Opaque> arbitrary();
};
template<>
struct Arbitrary<SingleDerivedPath::Built> {
static Gen<SingleDerivedPath::Built> arbitrary();
};
template<>
struct Arbitrary<SingleDerivedPath> {
static Gen<SingleDerivedPath> arbitrary();
};
template<>
struct Arbitrary<DerivedPath::Built> {
static Gen<DerivedPath::Built> arbitrary();
};
template<>
struct Arbitrary<DerivedPath> {
static Gen<DerivedPath> arbitrary();
};
}

View file

@ -1,41 +0,0 @@
#include <gtest/gtest.h>
#include "downstream-placeholder.hh"
namespace nix {
TEST(DownstreamPlaceholder, unknownCaOutput) {
/**
* We set these in tests rather than the regular globals so we don't have
* to worry about race conditions if the tests run concurrently.
*/
ExperimentalFeatureSettings mockXpSettings;
mockXpSettings.set("experimental-features", "ca-derivations");
ASSERT_EQ(
DownstreamPlaceholder::unknownCaOutput(
StorePath { "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo.drv" },
"out",
mockXpSettings).render(),
"/0c6rn30q4frawknapgwq386zq358m8r6msvywcvc89n6m5p2dgbz");
}
TEST(DownstreamPlaceholder, unknownDerivation) {
/**
* Same reason as above
*/
ExperimentalFeatureSettings mockXpSettings;
mockXpSettings.set("experimental-features", "dynamic-derivations ca-derivations");
ASSERT_EQ(
DownstreamPlaceholder::unknownDerivation(
DownstreamPlaceholder::unknownCaOutput(
StorePath { "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo.drv.drv" },
"out",
mockXpSettings),
"out",
mockXpSettings).render(),
"/0gn6agqxjyyalf0dpihgyf49xq5hqxgw100f0wydnj6yqrhqsb3w");
}
}

View file

@ -1,26 +0,0 @@
#pragma once
///@file
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include "store-api.hh"
namespace nix {
class LibStoreTest : public ::testing::Test {
public:
static void SetUpTestSuite() {
initLibStore();
}
protected:
LibStoreTest()
: store(openStore("dummy://"))
{ }
ref<Store> store;
};
} /* namespace nix */

View file

@ -1,29 +0,0 @@
check: libstore-tests-exe_RUN
programs += libstore-tests-exe
libstore-tests-exe_NAME = libnixstore-tests
libstore-tests-exe_DIR := $(d)
libstore-tests-exe_INSTALL_DIR :=
libstore-tests-exe_LIBS = libstore-tests
libstore-tests-exe_LDFLAGS := $(GTEST_LIBS)
libraries += libstore-tests
libstore-tests_NAME = libnixstore-tests
libstore-tests_DIR := $(d)
libstore-tests_INSTALL_DIR :=
libstore-tests_SOURCES := $(wildcard $(d)/*.cc)
libstore-tests_CXXFLAGS += -I src/libstore -I src/libutil
libstore-tests_LIBS = libutil-tests libstore libutil
libstore-tests_LDFLAGS := -lrapidcheck $(GTEST_LIBS)

View file

@ -1,169 +0,0 @@
#include "machines.hh"
#include "globals.hh"
#include <gmock/gmock-matchers.h>
using testing::Contains;
using testing::ElementsAre;
using testing::EndsWith;
using testing::Eq;
using testing::Field;
using testing::SizeIs;
using nix::absPath;
using nix::FormatError;
using nix::getMachines;
using nix::Machine;
using nix::Machines;
using nix::pathExists;
using nix::Settings;
using nix::settings;
class Environment : public ::testing::Environment {
public:
void SetUp() override { settings.thisSystem = "TEST_ARCH-TEST_OS"; }
};
testing::Environment* const foo_env =
testing::AddGlobalTestEnvironment(new Environment);
TEST(machines, getMachinesWithEmptyBuilders) {
settings.builders = "";
Machines actual = getMachines();
ASSERT_THAT(actual, SizeIs(0));
}
TEST(machines, getMachinesUriOnly) {
settings.builders = "nix@scratchy.labs.cs.uu.nl";
Machines actual = getMachines();
ASSERT_THAT(actual, SizeIs(1));
EXPECT_THAT(actual[0], Field(&Machine::storeUri, Eq("ssh://nix@scratchy.labs.cs.uu.nl")));
EXPECT_THAT(actual[0], Field(&Machine::systemTypes, ElementsAre("TEST_ARCH-TEST_OS")));
EXPECT_THAT(actual[0], Field(&Machine::sshKey, SizeIs(0)));
EXPECT_THAT(actual[0], Field(&Machine::maxJobs, Eq(1)));
EXPECT_THAT(actual[0], Field(&Machine::speedFactor, Eq(1)));
EXPECT_THAT(actual[0], Field(&Machine::supportedFeatures, SizeIs(0)));
EXPECT_THAT(actual[0], Field(&Machine::mandatoryFeatures, SizeIs(0)));
EXPECT_THAT(actual[0], Field(&Machine::sshPublicHostKey, SizeIs(0)));
}
TEST(machines, getMachinesDefaults) {
settings.builders = "nix@scratchy.labs.cs.uu.nl - - - - - - -";
Machines actual = getMachines();
ASSERT_THAT(actual, SizeIs(1));
EXPECT_THAT(actual[0], Field(&Machine::storeUri, Eq("ssh://nix@scratchy.labs.cs.uu.nl")));
EXPECT_THAT(actual[0], Field(&Machine::systemTypes, ElementsAre("TEST_ARCH-TEST_OS")));
EXPECT_THAT(actual[0], Field(&Machine::sshKey, SizeIs(0)));
EXPECT_THAT(actual[0], Field(&Machine::maxJobs, Eq(1)));
EXPECT_THAT(actual[0], Field(&Machine::speedFactor, Eq(1)));
EXPECT_THAT(actual[0], Field(&Machine::supportedFeatures, SizeIs(0)));
EXPECT_THAT(actual[0], Field(&Machine::mandatoryFeatures, SizeIs(0)));
EXPECT_THAT(actual[0], Field(&Machine::sshPublicHostKey, SizeIs(0)));
}
TEST(machines, getMachinesWithNewLineSeparator) {
settings.builders = "nix@scratchy.labs.cs.uu.nl\nnix@itchy.labs.cs.uu.nl";
Machines actual = getMachines();
ASSERT_THAT(actual, SizeIs(2));
EXPECT_THAT(actual, Contains(Field(&Machine::storeUri, EndsWith("nix@scratchy.labs.cs.uu.nl"))));
EXPECT_THAT(actual, Contains(Field(&Machine::storeUri, EndsWith("nix@itchy.labs.cs.uu.nl"))));
}
TEST(machines, getMachinesWithSemicolonSeparator) {
settings.builders = "nix@scratchy.labs.cs.uu.nl ; nix@itchy.labs.cs.uu.nl";
Machines actual = getMachines();
EXPECT_THAT(actual, SizeIs(2));
EXPECT_THAT(actual, Contains(Field(&Machine::storeUri, EndsWith("nix@scratchy.labs.cs.uu.nl"))));
EXPECT_THAT(actual, Contains(Field(&Machine::storeUri, EndsWith("nix@itchy.labs.cs.uu.nl"))));
}
TEST(machines, getMachinesWithCorrectCompleteSingleBuilder) {
settings.builders = "nix@scratchy.labs.cs.uu.nl i686-linux "
"/home/nix/.ssh/id_scratchy_auto 8 3 kvm "
"benchmark SSH+HOST+PUBLIC+KEY+BASE64+ENCODED==";
Machines actual = getMachines();
ASSERT_THAT(actual, SizeIs(1));
EXPECT_THAT(actual[0], Field(&Machine::storeUri, EndsWith("nix@scratchy.labs.cs.uu.nl")));
EXPECT_THAT(actual[0], Field(&Machine::systemTypes, ElementsAre("i686-linux")));
EXPECT_THAT(actual[0], Field(&Machine::sshKey, Eq("/home/nix/.ssh/id_scratchy_auto")));
EXPECT_THAT(actual[0], Field(&Machine::maxJobs, Eq(8)));
EXPECT_THAT(actual[0], Field(&Machine::speedFactor, Eq(3)));
EXPECT_THAT(actual[0], Field(&Machine::supportedFeatures, ElementsAre("kvm")));
EXPECT_THAT(actual[0], Field(&Machine::mandatoryFeatures, ElementsAre("benchmark")));
EXPECT_THAT(actual[0], Field(&Machine::sshPublicHostKey, Eq("SSH+HOST+PUBLIC+KEY+BASE64+ENCODED==")));
}
TEST(machines,
getMachinesWithCorrectCompleteSingleBuilderWithTabColumnDelimiter) {
settings.builders =
"nix@scratchy.labs.cs.uu.nl\ti686-linux\t/home/nix/.ssh/"
"id_scratchy_auto\t8\t3\tkvm\tbenchmark\tSSH+HOST+PUBLIC+"
"KEY+BASE64+ENCODED==";
Machines actual = getMachines();
ASSERT_THAT(actual, SizeIs(1));
EXPECT_THAT(actual[0], Field(&Machine::storeUri, EndsWith("nix@scratchy.labs.cs.uu.nl")));
EXPECT_THAT(actual[0], Field(&Machine::systemTypes, ElementsAre("i686-linux")));
EXPECT_THAT(actual[0], Field(&Machine::sshKey, Eq("/home/nix/.ssh/id_scratchy_auto")));
EXPECT_THAT(actual[0], Field(&Machine::maxJobs, Eq(8)));
EXPECT_THAT(actual[0], Field(&Machine::speedFactor, Eq(3)));
EXPECT_THAT(actual[0], Field(&Machine::supportedFeatures, ElementsAre("kvm")));
EXPECT_THAT(actual[0], Field(&Machine::mandatoryFeatures, ElementsAre("benchmark")));
EXPECT_THAT(actual[0], Field(&Machine::sshPublicHostKey, Eq("SSH+HOST+PUBLIC+KEY+BASE64+ENCODED==")));
}
TEST(machines, getMachinesWithMultiOptions) {
settings.builders = "nix@scratchy.labs.cs.uu.nl Arch1,Arch2 - - - "
"SupportedFeature1,SupportedFeature2 "
"MandatoryFeature1,MandatoryFeature2";
Machines actual = getMachines();
ASSERT_THAT(actual, SizeIs(1));
EXPECT_THAT(actual[0], Field(&Machine::storeUri, EndsWith("nix@scratchy.labs.cs.uu.nl")));
EXPECT_THAT(actual[0], Field(&Machine::systemTypes, ElementsAre("Arch1", "Arch2")));
EXPECT_THAT(actual[0], Field(&Machine::supportedFeatures, ElementsAre("SupportedFeature1", "SupportedFeature2")));
EXPECT_THAT(actual[0], Field(&Machine::mandatoryFeatures, ElementsAre("MandatoryFeature1", "MandatoryFeature2")));
}
TEST(machines, getMachinesWithIncorrectFormat) {
settings.builders = "nix@scratchy.labs.cs.uu.nl - - eight";
EXPECT_THROW(getMachines(), FormatError);
settings.builders = "nix@scratchy.labs.cs.uu.nl - - -1";
EXPECT_THROW(getMachines(), FormatError);
settings.builders = "nix@scratchy.labs.cs.uu.nl - - 8 three";
EXPECT_THROW(getMachines(), FormatError);
settings.builders = "nix@scratchy.labs.cs.uu.nl - - 8 -3";
EXPECT_THROW(getMachines(), FormatError);
settings.builders = "nix@scratchy.labs.cs.uu.nl - - 8 3 - - BAD_BASE64";
EXPECT_THROW(getMachines(), FormatError);
}
TEST(machines, getMachinesWithCorrectFileReference) {
auto path = absPath("src/libstore/tests/test-data/machines.valid");
ASSERT_TRUE(pathExists(path));
settings.builders = std::string("@") + path;
Machines actual = getMachines();
ASSERT_THAT(actual, SizeIs(3));
EXPECT_THAT(actual, Contains(Field(&Machine::storeUri, EndsWith("nix@scratchy.labs.cs.uu.nl"))));
EXPECT_THAT(actual, Contains(Field(&Machine::storeUri, EndsWith("nix@itchy.labs.cs.uu.nl"))));
EXPECT_THAT(actual, Contains(Field(&Machine::storeUri, EndsWith("nix@poochie.labs.cs.uu.nl"))));
}
TEST(machines, getMachinesWithCorrectFileReferenceToEmptyFile) {
auto path = "/dev/null";
ASSERT_TRUE(pathExists(path));
settings.builders = std::string("@") + path;
Machines actual = getMachines();
ASSERT_THAT(actual, SizeIs(0));
}
TEST(machines, getMachinesWithIncorrectFileReference) {
settings.builders = std::string("@") + absPath("/not/a/file");
Machines actual = getMachines();
ASSERT_THAT(actual, SizeIs(0));
}
TEST(machines, getMachinesWithCorrectFileReferenceToIncorrectFile) {
settings.builders = std::string("@") + absPath("src/libstore/tests/test-data/machines.bad_format");
EXPECT_THROW(getMachines(), FormatError);
}

View file

@ -1,123 +0,0 @@
#include "nar-info-disk-cache.hh"
#include <gtest/gtest.h>
#include <rapidcheck/gtest.h>
#include "sqlite.hh"
#include <sqlite3.h>
namespace nix {
TEST(NarInfoDiskCacheImpl, create_and_read) {
// This is a large single test to avoid some setup overhead.
int prio = 12345;
bool wantMassQuery = true;
Path tmpDir = createTempDir();
AutoDelete delTmpDir(tmpDir);
Path dbPath(tmpDir + "/test-narinfo-disk-cache.sqlite");
int savedId;
int barId;
SQLite db;
SQLiteStmt getIds;
{
auto cache = getTestNarInfoDiskCache(dbPath);
// Set up "background noise" and check that different caches receive different ids
{
auto bc1 = cache->createCache("https://bar", "/nix/storedir", wantMassQuery, prio);
auto bc2 = cache->createCache("https://xyz", "/nix/storedir", false, 12);
ASSERT_NE(bc1, bc2);
barId = bc1;
}
// Check that the fields are saved and returned correctly. This does not test
// the select statement yet, because of in-memory caching.
savedId = cache->createCache("http://foo", "/nix/storedir", wantMassQuery, prio);;
{
auto r = cache->upToDateCacheExists("http://foo");
ASSERT_TRUE(r);
ASSERT_EQ(r->priority, prio);
ASSERT_EQ(r->wantMassQuery, wantMassQuery);
ASSERT_EQ(savedId, r->id);
}
// We're going to pay special attention to the id field because we had a bug
// that changed it.
db = SQLite(dbPath);
getIds.create(db, "select id from BinaryCaches where url = 'http://foo'");
{
auto q(getIds.use());
ASSERT_TRUE(q.next());
ASSERT_EQ(savedId, q.getInt(0));
ASSERT_FALSE(q.next());
}
// Pretend that the caches are older, but keep one up to date, as "background noise"
db.exec("update BinaryCaches set timestamp = timestamp - 1 - 7 * 24 * 3600 where url <> 'https://xyz';");
// This shows that the in-memory cache works
{
auto r = cache->upToDateCacheExists("http://foo");
ASSERT_TRUE(r);
ASSERT_EQ(r->priority, prio);
ASSERT_EQ(r->wantMassQuery, wantMassQuery);
}
}
{
// We can't clear the in-memory cache, so we use a new cache object. This is
// more realistic anyway.
auto cache2 = getTestNarInfoDiskCache(dbPath);
{
auto r = cache2->upToDateCacheExists("http://foo");
ASSERT_FALSE(r);
}
// "Update", same data, check that the id number is reused
cache2->createCache("http://foo", "/nix/storedir", wantMassQuery, prio);
{
auto r = cache2->upToDateCacheExists("http://foo");
ASSERT_TRUE(r);
ASSERT_EQ(r->priority, prio);
ASSERT_EQ(r->wantMassQuery, wantMassQuery);
ASSERT_EQ(r->id, savedId);
}
{
auto q(getIds.use());
ASSERT_TRUE(q.next());
auto currentId = q.getInt(0);
ASSERT_FALSE(q.next());
ASSERT_EQ(currentId, savedId);
}
// Check that the fields can be modified, and the id remains the same
{
auto r0 = cache2->upToDateCacheExists("https://bar");
ASSERT_FALSE(r0);
cache2->createCache("https://bar", "/nix/storedir", !wantMassQuery, prio + 10);
auto r = cache2->upToDateCacheExists("https://bar");
ASSERT_EQ(r->wantMassQuery, !wantMassQuery);
ASSERT_EQ(r->priority, prio + 10);
ASSERT_EQ(r->id, barId);
}
// // Force update (no use case yet; we only retrieve cache metadata when stale based on timestamp)
// {
// cache2->createCache("https://bar", "/nix/storedir", wantMassQuery, prio + 20);
// auto r = cache2->upToDateCacheExists("https://bar");
// ASSERT_EQ(r->wantMassQuery, wantMassQuery);
// ASSERT_EQ(r->priority, prio + 20);
// }
}
}
}

View file

@ -1,239 +0,0 @@
#include "outputs-spec.hh"
#include <nlohmann/json.hpp>
#include <gtest/gtest.h>
#include <rapidcheck/gtest.h>
namespace nix {
#ifndef NDEBUG
TEST(OutputsSpec, no_empty_names) {
ASSERT_DEATH(OutputsSpec::Names { std::set<std::string> { } }, "");
}
#endif
#define TEST_DONT_PARSE(NAME, STR) \
TEST(OutputsSpec, bad_ ## NAME) { \
std::optional OutputsSpecOpt = \
OutputsSpec::parseOpt(STR); \
ASSERT_FALSE(OutputsSpecOpt); \
}
TEST_DONT_PARSE(empty, "")
TEST_DONT_PARSE(garbage, "&*()")
TEST_DONT_PARSE(double_star, "**")
TEST_DONT_PARSE(star_first, "*,foo")
TEST_DONT_PARSE(star_second, "foo,*")
#undef TEST_DONT_PARSE
TEST(OutputsSpec, all) {
std::string_view str = "*";
OutputsSpec expected = OutputsSpec::All { };
ASSERT_EQ(OutputsSpec::parse(str), expected);
ASSERT_EQ(expected.to_string(), str);
}
TEST(OutputsSpec, names_out) {
std::string_view str = "out";
OutputsSpec expected = OutputsSpec::Names { "out" };
ASSERT_EQ(OutputsSpec::parse(str), expected);
ASSERT_EQ(expected.to_string(), str);
}
TEST(OutputsSpec, names_underscore) {
std::string_view str = "a_b";
OutputsSpec expected = OutputsSpec::Names { "a_b" };
ASSERT_EQ(OutputsSpec::parse(str), expected);
ASSERT_EQ(expected.to_string(), str);
}
TEST(OutputsSpec, names_numberic) {
std::string_view str = "01";
OutputsSpec expected = OutputsSpec::Names { "01" };
ASSERT_EQ(OutputsSpec::parse(str), expected);
ASSERT_EQ(expected.to_string(), str);
}
TEST(OutputsSpec, names_out_bin) {
OutputsSpec expected = OutputsSpec::Names { "out", "bin" };
ASSERT_EQ(OutputsSpec::parse("out,bin"), expected);
// N.B. This normalization is OK.
ASSERT_EQ(expected.to_string(), "bin,out");
}
#define TEST_SUBSET(X, THIS, THAT) \
X((OutputsSpec { THIS }).isSubsetOf(THAT));
TEST(OutputsSpec, subsets_all_all) {
TEST_SUBSET(ASSERT_TRUE, OutputsSpec::All { }, OutputsSpec::All { });
}
TEST(OutputsSpec, subsets_names_all) {
TEST_SUBSET(ASSERT_TRUE, OutputsSpec::Names { "a" }, OutputsSpec::All { });
}
TEST(OutputsSpec, subsets_names_names_eq) {
TEST_SUBSET(ASSERT_TRUE, OutputsSpec::Names { "a" }, OutputsSpec::Names { "a" });
}
TEST(OutputsSpec, subsets_names_names_noneq) {
TEST_SUBSET(ASSERT_TRUE, OutputsSpec::Names { "a" }, (OutputsSpec::Names { "a", "b" }));
}
TEST(OutputsSpec, not_subsets_all_names) {
TEST_SUBSET(ASSERT_FALSE, OutputsSpec::All { }, OutputsSpec::Names { "a" });
}
TEST(OutputsSpec, not_subsets_names_names) {
TEST_SUBSET(ASSERT_FALSE, (OutputsSpec::Names { "a", "b" }), (OutputsSpec::Names { "a" }));
}
#undef TEST_SUBSET
#define TEST_UNION(RES, THIS, THAT) \
ASSERT_EQ(OutputsSpec { RES }, (OutputsSpec { THIS }).union_(THAT));
TEST(OutputsSpec, union_all_all) {
TEST_UNION(OutputsSpec::All { }, OutputsSpec::All { }, OutputsSpec::All { });
}
TEST(OutputsSpec, union_all_names) {
TEST_UNION(OutputsSpec::All { }, OutputsSpec::All { }, OutputsSpec::Names { "a" });
}
TEST(OutputsSpec, union_names_all) {
TEST_UNION(OutputsSpec::All { }, OutputsSpec::Names { "a" }, OutputsSpec::All { });
}
TEST(OutputsSpec, union_names_names) {
TEST_UNION((OutputsSpec::Names { "a", "b" }), OutputsSpec::Names { "a" }, OutputsSpec::Names { "b" });
}
#undef TEST_UNION
#define TEST_DONT_PARSE(NAME, STR) \
TEST(ExtendedOutputsSpec, bad_ ## NAME) { \
std::optional extendedOutputsSpecOpt = \
ExtendedOutputsSpec::parseOpt(STR); \
ASSERT_FALSE(extendedOutputsSpecOpt); \
}
TEST_DONT_PARSE(carot_empty, "^")
TEST_DONT_PARSE(prefix_carot_empty, "foo^")
TEST_DONT_PARSE(garbage, "^&*()")
TEST_DONT_PARSE(double_star, "^**")
TEST_DONT_PARSE(star_first, "^*,foo")
TEST_DONT_PARSE(star_second, "^foo,*")
#undef TEST_DONT_PARSE
TEST(ExtendedOutputsSpec, defeault) {
std::string_view str = "foo";
auto [prefix, extendedOutputsSpec] = ExtendedOutputsSpec::parse(str);
ASSERT_EQ(prefix, "foo");
ExtendedOutputsSpec expected = ExtendedOutputsSpec::Default { };
ASSERT_EQ(extendedOutputsSpec, expected);
ASSERT_EQ(std::string { prefix } + expected.to_string(), str);
}
TEST(ExtendedOutputsSpec, all) {
std::string_view str = "foo^*";
auto [prefix, extendedOutputsSpec] = ExtendedOutputsSpec::parse(str);
ASSERT_EQ(prefix, "foo");
ExtendedOutputsSpec expected = OutputsSpec::All { };
ASSERT_EQ(extendedOutputsSpec, expected);
ASSERT_EQ(std::string { prefix } + expected.to_string(), str);
}
TEST(ExtendedOutputsSpec, out) {
std::string_view str = "foo^out";
auto [prefix, extendedOutputsSpec] = ExtendedOutputsSpec::parse(str);
ASSERT_EQ(prefix, "foo");
ExtendedOutputsSpec expected = OutputsSpec::Names { "out" };
ASSERT_EQ(extendedOutputsSpec, expected);
ASSERT_EQ(std::string { prefix } + expected.to_string(), str);
}
TEST(ExtendedOutputsSpec, out_bin) {
auto [prefix, extendedOutputsSpec] = ExtendedOutputsSpec::parse("foo^out,bin");
ASSERT_EQ(prefix, "foo");
ExtendedOutputsSpec expected = OutputsSpec::Names { "out", "bin" };
ASSERT_EQ(extendedOutputsSpec, expected);
ASSERT_EQ(std::string { prefix } + expected.to_string(), "foo^bin,out");
}
TEST(ExtendedOutputsSpec, many_carrot) {
auto [prefix, extendedOutputsSpec] = ExtendedOutputsSpec::parse("foo^bar^out,bin");
ASSERT_EQ(prefix, "foo^bar");
ExtendedOutputsSpec expected = OutputsSpec::Names { "out", "bin" };
ASSERT_EQ(extendedOutputsSpec, expected);
ASSERT_EQ(std::string { prefix } + expected.to_string(), "foo^bar^bin,out");
}
#define TEST_JSON(TYPE, NAME, STR, VAL) \
\
TEST(TYPE, NAME ## _to_json) { \
using nlohmann::literals::operator "" _json; \
ASSERT_EQ( \
STR ## _json, \
((nlohmann::json) TYPE { VAL })); \
} \
\
TEST(TYPE, NAME ## _from_json) { \
using nlohmann::literals::operator "" _json; \
ASSERT_EQ( \
TYPE { VAL }, \
(STR ## _json).get<TYPE>()); \
}
TEST_JSON(OutputsSpec, all, R"(["*"])", OutputsSpec::All { })
TEST_JSON(OutputsSpec, name, R"(["a"])", OutputsSpec::Names { "a" })
TEST_JSON(OutputsSpec, names, R"(["a","b"])", (OutputsSpec::Names { "a", "b" }))
TEST_JSON(ExtendedOutputsSpec, def, R"(null)", ExtendedOutputsSpec::Default { })
TEST_JSON(ExtendedOutputsSpec, all, R"(["*"])", ExtendedOutputsSpec::Explicit { OutputsSpec::All { } })
TEST_JSON(ExtendedOutputsSpec, name, R"(["a"])", ExtendedOutputsSpec::Explicit { OutputsSpec::Names { "a" } })
TEST_JSON(ExtendedOutputsSpec, names, R"(["a","b"])", (ExtendedOutputsSpec::Explicit { OutputsSpec::Names { "a", "b" } }))
#undef TEST_JSON
}
namespace rc {
using namespace nix;
Gen<OutputsSpec> Arbitrary<OutputsSpec>::arbitrary()
{
switch (*gen::inRange<uint8_t>(0, std::variant_size_v<OutputsSpec::Raw>)) {
case 0:
return gen::just((OutputsSpec) OutputsSpec::All { });
case 1:
return gen::just((OutputsSpec) OutputsSpec::Names {
*gen::nonEmpty(gen::container<StringSet>(gen::map(
gen::arbitrary<StorePathName>(),
[](StorePathName n) { return n.name; }))),
});
default:
assert(false);
}
}
}
namespace nix {
#ifndef COVERAGE
RC_GTEST_PROP(
OutputsSpec,
prop_round_rip,
(const OutputsSpec & o))
{
RC_ASSERT(o == OutputsSpec::parse(o.to_string()));
}
#endif
}

View file

@ -1,18 +0,0 @@
#pragma once
///@file
#include <rapidcheck/gen/Arbitrary.h>
#include <outputs-spec.hh>
#include <tests/path.hh>
namespace rc {
using namespace nix;
template<>
struct Arbitrary<OutputsSpec> {
static Gen<OutputsSpec> arbitrary();
};
}

View file

@ -1,162 +0,0 @@
#include <regex>
#include <nlohmann/json.hpp>
#include <gtest/gtest.h>
#include <rapidcheck/gtest.h>
#include "path-regex.hh"
#include "store-api.hh"
#include "tests/hash.hh"
#include "tests/libstore.hh"
#include "tests/path.hh"
namespace nix {
#define STORE_DIR "/nix/store/"
#define HASH_PART "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q"
class StorePathTest : public LibStoreTest
{
};
static std::regex nameRegex { std::string { nameRegexStr } };
#define TEST_DONT_PARSE(NAME, STR) \
TEST_F(StorePathTest, bad_ ## NAME) { \
std::string_view str = \
STORE_DIR HASH_PART "-" STR; \
ASSERT_THROW( \
store->parseStorePath(str), \
BadStorePath); \
std::string name { STR }; \
EXPECT_FALSE(std::regex_match(name, nameRegex)); \
}
TEST_DONT_PARSE(empty, "")
TEST_DONT_PARSE(garbage, "&*()")
TEST_DONT_PARSE(double_star, "**")
TEST_DONT_PARSE(star_first, "*,foo")
TEST_DONT_PARSE(star_second, "foo,*")
TEST_DONT_PARSE(bang, "foo!o")
TEST_DONT_PARSE(dotfile, ".gitignore")
#undef TEST_DONT_PARSE
#define TEST_DO_PARSE(NAME, STR) \
TEST_F(StorePathTest, good_ ## NAME) { \
std::string_view str = \
STORE_DIR HASH_PART "-" STR; \
auto p = store->parseStorePath(str); \
std::string name { p.name() }; \
EXPECT_TRUE(std::regex_match(name, nameRegex)); \
}
// 0-9 a-z A-Z + - . _ ? =
TEST_DO_PARSE(numbers, "02345")
TEST_DO_PARSE(lower_case, "foo")
TEST_DO_PARSE(upper_case, "FOO")
TEST_DO_PARSE(plus, "foo+bar")
TEST_DO_PARSE(dash, "foo-dev")
TEST_DO_PARSE(underscore, "foo_bar")
TEST_DO_PARSE(period, "foo.txt")
TEST_DO_PARSE(question_mark, "foo?why")
TEST_DO_PARSE(equals_sign, "foo=foo")
#undef TEST_DO_PARSE
// For rapidcheck
void showValue(const StorePath & p, std::ostream & os) {
os << p.to_string();
}
}
namespace rc {
using namespace nix;
Gen<StorePathName> Arbitrary<StorePathName>::arbitrary()
{
auto len = *gen::inRange<size_t>(
1,
StorePath::MaxPathLen - std::string_view { HASH_PART }.size());
std::string pre;
pre.reserve(len);
for (size_t c = 0; c < len; ++c) {
switch (auto i = *gen::inRange<uint8_t>(0, 10 + 2 * 26 + 6)) {
case 0 ... 9:
pre += '0' + i;
case 10 ... 35:
pre += 'A' + (i - 10);
break;
case 36 ... 61:
pre += 'a' + (i - 36);
break;
case 62:
pre += '+';
break;
case 63:
pre += '-';
break;
case 64:
// names aren't permitted to start with a period,
// so just fall through to the next case here
if (c != 0) {
pre += '.';
break;
}
case 65:
pre += '_';
break;
case 66:
pre += '?';
break;
case 67:
pre += '=';
break;
default:
assert(false);
}
}
return gen::just(StorePathName {
.name = std::move(pre),
});
}
Gen<StorePath> Arbitrary<StorePath>::arbitrary()
{
return gen::just(StorePath {
*gen::arbitrary<Hash>(),
(*gen::arbitrary<StorePathName>()).name,
});
}
} // namespace rc
namespace nix {
#ifndef COVERAGE
RC_GTEST_FIXTURE_PROP(
StorePathTest,
prop_regex_accept,
(const StorePath & p))
{
RC_ASSERT(std::regex_match(std::string { p.name() }, nameRegex));
}
RC_GTEST_FIXTURE_PROP(
StorePathTest,
prop_round_rip,
(const StorePath & p))
{
RC_ASSERT(p == store->parseStorePath(store->printStorePath(p)));
}
#endif
}

View file

@ -1,29 +0,0 @@
#pragma once
///@file
#include <rapidcheck/gen/Arbitrary.h>
#include <path.hh>
namespace nix {
struct StorePathName {
std::string name;
};
}
namespace rc {
using namespace nix;
template<>
struct Arbitrary<StorePathName> {
static Gen<StorePathName> arbitrary();
};
template<>
struct Arbitrary<StorePath> {
static Gen<StorePath> arbitrary();
};
}

Some files were not shown because too many files have changed in this diff Show more