mirror of
https://github.com/NixOS/nix
synced 2025-07-02 05:11:47 +02:00
Merge remote-tracking branch 'origin/master' into handle-missing-gc-socket
This commit is contained in:
commit
5703c31325
1298 changed files with 33858 additions and 15133 deletions
|
@ -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,8 @@
|
|||
#include "nar-accessor.hh"
|
||||
#include "thread-pool.hh"
|
||||
#include "callback.hh"
|
||||
#include "signals.hh"
|
||||
#include "archive.hh"
|
||||
|
||||
#include <chrono>
|
||||
#include <future>
|
||||
|
@ -26,7 +28,8 @@ BinaryCacheStore::BinaryCacheStore(const Params & params)
|
|||
, Store(params)
|
||||
{
|
||||
if (secretKeyFile != "")
|
||||
secretKey = std::unique_ptr<SecretKey>(new SecretKey(readFile(secretKeyFile)));
|
||||
signer = std::make_unique<LocalSigner>(
|
||||
SecretKey { readFile(secretKeyFile) });
|
||||
|
||||
StringSink sink;
|
||||
sink << narVersionMagic1;
|
||||
|
@ -142,9 +145,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 +167,8 @@ ref<const ValidPathInfo> BinaryCacheStore::addToStoreCommon(
|
|||
auto [fileHash, fileSize] = fileHashSink.finish();
|
||||
narInfo->fileHash = fileHash;
|
||||
narInfo->fileSize = fileSize;
|
||||
narInfo->url = "nar/" + narInfo->fileHash->to_string(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 +198,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 +209,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 +234,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 +253,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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -272,7 +275,7 @@ ref<const ValidPathInfo> BinaryCacheStore::addToStoreCommon(
|
|||
stats.narWriteCompressionTimeMs += duration;
|
||||
|
||||
/* Atomically write the NAR info file.*/
|
||||
if (secretKey) narInfo->sign(*this, *secretKey);
|
||||
if (signer) narInfo->sign(*this, *signer);
|
||||
|
||||
writeNarInfo(narInfo);
|
||||
|
||||
|
@ -299,26 +302,60 @@ 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)
|
||||
StorePath BinaryCacheStore::addToStoreFromDump(
|
||||
Source & dump,
|
||||
std::string_view name,
|
||||
ContentAddressMethod method,
|
||||
HashAlgorithm hashAlgo,
|
||||
const StorePathSet & references,
|
||||
RepairFlag repair)
|
||||
{
|
||||
if (method != FileIngestionMethod::Recursive || hashAlgo != htSHA256)
|
||||
unsupported("addToStoreFromDump");
|
||||
return addToStoreCommon(dump, repair, CheckSigs, [&](HashResult nar) {
|
||||
std::optional<Hash> caHash;
|
||||
std::string nar;
|
||||
|
||||
if (auto * dump2p = dynamic_cast<StringSource *>(&dump)) {
|
||||
auto & dump2 = *dump2p;
|
||||
// Hack, this gives us a "replayable" source so we can compute
|
||||
// multiple hashes more easily.
|
||||
caHash = hashString(HashAlgorithm::SHA256, dump2.s);
|
||||
switch (method.getFileIngestionMethod()) {
|
||||
case FileIngestionMethod::Recursive:
|
||||
// The dump is already NAR in this case, just use it.
|
||||
nar = dump2.s;
|
||||
break;
|
||||
case FileIngestionMethod::Flat:
|
||||
// The dump is Flat, so we need to convert it to NAR with a
|
||||
// single file.
|
||||
StringSink s;
|
||||
dumpString(dump2.s, s);
|
||||
nar = std::move(s.s);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// Otherwise, we have to do th same hashing as NAR so our single
|
||||
// hash will suffice for both purposes.
|
||||
if (method != FileIngestionMethod::Recursive || hashAlgo != HashAlgorithm::SHA256)
|
||||
unsupported("addToStoreFromDump");
|
||||
}
|
||||
StringSource narDump { nar };
|
||||
|
||||
// Use `narDump` if we wrote to `nar`.
|
||||
Source & narDump2 = nar.size() > 0
|
||||
? static_cast<Source &>(narDump)
|
||||
: dump;
|
||||
|
||||
return addToStoreCommon(narDump2, repair, CheckSigs, [&](HashResult nar) {
|
||||
ValidPathInfo info {
|
||||
*this,
|
||||
name,
|
||||
FixedOutputInfo {
|
||||
.hash = {
|
||||
.method = method,
|
||||
.hash = nar.first,
|
||||
},
|
||||
.references = {
|
||||
ContentAddressWithReferences::fromParts(
|
||||
method,
|
||||
caHash ? *caHash : nar.first,
|
||||
{
|
||||
.others = references,
|
||||
// caller is not capable of creating a self-reference, because this is content-addressed without modulus
|
||||
.self = false,
|
||||
},
|
||||
},
|
||||
}),
|
||||
nar.first,
|
||||
};
|
||||
info.narSize = nar.second;
|
||||
|
@ -401,73 +438,35 @@ void BinaryCacheStore::queryPathInfoUncached(const StorePath & storePath,
|
|||
|
||||
StorePath BinaryCacheStore::addToStore(
|
||||
std::string_view name,
|
||||
const Path & srcPath,
|
||||
FileIngestionMethod method,
|
||||
HashType hashAlgo,
|
||||
SourceAccessor & accessor,
|
||||
const CanonPath & path,
|
||||
ContentAddressMethod method,
|
||||
HashAlgorithm hashAlgo,
|
||||
const StorePathSet & references,
|
||||
PathFilter & filter,
|
||||
RepairFlag repair,
|
||||
const StorePathSet & references)
|
||||
RepairFlag repair)
|
||||
{
|
||||
/* FIXME: Make BinaryCacheStore::addToStoreCommon support
|
||||
non-recursive+sha256 so we can just use the default
|
||||
implementation of this method in terms of addToStoreFromDump. */
|
||||
|
||||
HashSink sink { hashAlgo };
|
||||
if (method == FileIngestionMethod::Recursive) {
|
||||
dumpPath(srcPath, sink, filter);
|
||||
} else {
|
||||
readFile(srcPath, sink);
|
||||
}
|
||||
auto h = sink.finish().first;
|
||||
auto h = hashPath(accessor, path, method.getFileIngestionMethod(), hashAlgo, filter).first;
|
||||
|
||||
auto source = sinkToSource([&](Sink & sink) {
|
||||
dumpPath(srcPath, sink, filter);
|
||||
accessor.dumpPath(path, sink, filter);
|
||||
});
|
||||
return addToStoreCommon(*source, repair, CheckSigs, [&](HashResult nar) {
|
||||
ValidPathInfo info {
|
||||
*this,
|
||||
name,
|
||||
FixedOutputInfo {
|
||||
.hash = {
|
||||
.method = method,
|
||||
.hash = h,
|
||||
},
|
||||
.references = {
|
||||
ContentAddressWithReferences::fromParts(
|
||||
method,
|
||||
h,
|
||||
{
|
||||
.others = references,
|
||||
// caller is not capable of creating a self-reference, because this is content-addressed without modulus
|
||||
.self = false,
|
||||
},
|
||||
},
|
||||
nar.first,
|
||||
};
|
||||
info.narSize = nar.second;
|
||||
return info;
|
||||
})->path;
|
||||
}
|
||||
|
||||
StorePath BinaryCacheStore::addTextToStore(
|
||||
std::string_view name,
|
||||
std::string_view s,
|
||||
const StorePathSet & references,
|
||||
RepairFlag repair)
|
||||
{
|
||||
auto textHash = hashString(htSHA256, s);
|
||||
auto path = makeTextPath(name, TextInfo { { textHash }, references });
|
||||
|
||||
if (!repair && isValidPath(path))
|
||||
return path;
|
||||
|
||||
StringSink sink;
|
||||
dumpString(s, sink);
|
||||
StringSource source(sink.s);
|
||||
return addToStoreCommon(source, repair, CheckSigs, [&](HashResult nar) {
|
||||
ValidPathInfo info {
|
||||
*this,
|
||||
std::string { name },
|
||||
TextInfo {
|
||||
{ .hash = textHash },
|
||||
references,
|
||||
},
|
||||
}),
|
||||
nar.first,
|
||||
};
|
||||
info.narSize = nar.second;
|
||||
|
@ -507,9 +506,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)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#pragma once
|
||||
///@file
|
||||
|
||||
#include "crypto.hh"
|
||||
#include "signature/local-keys.hh"
|
||||
#include "store-api.hh"
|
||||
#include "log-store.hh"
|
||||
|
||||
|
@ -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.
|
||||
|
@ -57,8 +57,7 @@ class BinaryCacheStore : public virtual BinaryCacheStoreConfig,
|
|||
{
|
||||
|
||||
private:
|
||||
|
||||
std::unique_ptr<SecretKey> secretKey;
|
||||
std::unique_ptr<Signer> signer;
|
||||
|
||||
protected:
|
||||
|
||||
|
@ -123,22 +122,22 @@ public:
|
|||
void addToStore(const ValidPathInfo & info, Source & narSource,
|
||||
RepairFlag repair, CheckSigsFlag checkSigs) override;
|
||||
|
||||
StorePath addToStoreFromDump(Source & dump, std::string_view name,
|
||||
FileIngestionMethod method, HashType hashAlgo, RepairFlag repair, const StorePathSet & references) override;
|
||||
StorePath addToStoreFromDump(
|
||||
Source & dump,
|
||||
std::string_view name,
|
||||
ContentAddressMethod method,
|
||||
HashAlgorithm hashAlgo,
|
||||
const StorePathSet & references,
|
||||
RepairFlag repair) override;
|
||||
|
||||
StorePath addToStore(
|
||||
std::string_view name,
|
||||
const Path & srcPath,
|
||||
FileIngestionMethod method,
|
||||
HashType hashAlgo,
|
||||
PathFilter & filter,
|
||||
RepairFlag repair,
|
||||
const StorePathSet & references) override;
|
||||
|
||||
StorePath addTextToStore(
|
||||
std::string_view name,
|
||||
std::string_view s,
|
||||
SourceAccessor & accessor,
|
||||
const CanonPath & srcPath,
|
||||
ContentAddressMethod method,
|
||||
HashAlgorithm hashAlgo,
|
||||
const StorePathSet & references,
|
||||
PathFilter & filter,
|
||||
RepairFlag repair) override;
|
||||
|
||||
void registerDrvOutput(const Realisation & info) override;
|
||||
|
@ -148,7 +147,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;
|
||||
|
||||
|
|
18
src/libstore/build-result.cc
Normal file
18
src/libstore/build-result.cc
Normal file
|
@ -0,0 +1,18 @@
|
|||
#include "build-result.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
GENERATE_CMP_EXT(
|
||||
,
|
||||
BuildResult,
|
||||
me->status,
|
||||
me->errorMsg,
|
||||
me->timesBuilt,
|
||||
me->isNonDeterministic,
|
||||
me->builtOutputs,
|
||||
me->startTime,
|
||||
me->stopTime,
|
||||
me->cpuUser,
|
||||
me->cpuSystem);
|
||||
|
||||
}
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include "realisation.hh"
|
||||
#include "derived-path.hh"
|
||||
#include "comparator.hh"
|
||||
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
|
@ -100,6 +101,8 @@ struct BuildResult
|
|||
*/
|
||||
std::optional<std::chrono::microseconds> cpuUser, cpuSystem;
|
||||
|
||||
DECLARE_CMP(BuildResult);
|
||||
|
||||
bool success()
|
||||
{
|
||||
return status == Built || status == Substituted || status == AlreadyValid || status == ResolvesToAlreadyValid;
|
||||
|
|
37
src/libstore/build/child.cc
Normal file
37
src/libstore/build/child.cc
Normal 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);
|
||||
}
|
||||
|
||||
}
|
11
src/libstore/build/child.hh
Normal file
11
src/libstore/build/child.hh
Normal file
|
@ -0,0 +1,11 @@
|
|||
#pragma once
|
||||
///@file
|
||||
|
||||
namespace nix {
|
||||
|
||||
/**
|
||||
* Common initialisation performed in child processes.
|
||||
*/
|
||||
void commonChildInit();
|
||||
|
||||
}
|
|
@ -8,7 +8,8 @@
|
|||
#include "util.hh"
|
||||
#include "archive.hh"
|
||||
#include "compression.hh"
|
||||
#include "worker-protocol.hh"
|
||||
#include "common-protocol.hh"
|
||||
#include "common-protocol-impl.hh"
|
||||
#include "topo-sort.hh"
|
||||
#include "callback.hh"
|
||||
#include "local-store.hh" // TODO remove, along with remaining downcasts
|
||||
|
@ -64,7 +65,7 @@ namespace nix {
|
|||
|
||||
DerivationGoal::DerivationGoal(const StorePath & drvPath,
|
||||
const OutputsSpec & wantedOutputs, Worker & worker, BuildMode buildMode)
|
||||
: Goal(worker, DerivedPath::Built { .drvPath = drvPath, .outputs = wantedOutputs })
|
||||
: Goal(worker, DerivedPath::Built { .drvPath = makeConstantStorePathRef(drvPath), .outputs = wantedOutputs })
|
||||
, useDerivation(true)
|
||||
, drvPath(drvPath)
|
||||
, wantedOutputs(wantedOutputs)
|
||||
|
@ -73,7 +74,7 @@ DerivationGoal::DerivationGoal(const StorePath & drvPath,
|
|||
state = &DerivationGoal::getDerivation;
|
||||
name = fmt(
|
||||
"building of '%s' from .drv file",
|
||||
DerivedPath::Built { drvPath, wantedOutputs }.to_string(worker.store));
|
||||
DerivedPath::Built { makeConstantStorePathRef(drvPath), wantedOutputs }.to_string(worker.store));
|
||||
trace("created");
|
||||
|
||||
mcExpectedBuilds = std::make_unique<MaintainCount<uint64_t>>(worker.expectedBuilds);
|
||||
|
@ -83,7 +84,7 @@ DerivationGoal::DerivationGoal(const StorePath & drvPath,
|
|||
|
||||
DerivationGoal::DerivationGoal(const StorePath & drvPath, const BasicDerivation & drv,
|
||||
const OutputsSpec & wantedOutputs, Worker & worker, BuildMode buildMode)
|
||||
: Goal(worker, DerivedPath::Built { .drvPath = drvPath, .outputs = wantedOutputs })
|
||||
: Goal(worker, DerivedPath::Built { .drvPath = makeConstantStorePathRef(drvPath), .outputs = wantedOutputs })
|
||||
, useDerivation(false)
|
||||
, drvPath(drvPath)
|
||||
, wantedOutputs(wantedOutputs)
|
||||
|
@ -94,7 +95,7 @@ DerivationGoal::DerivationGoal(const StorePath & drvPath, const BasicDerivation
|
|||
state = &DerivationGoal::haveDerivation;
|
||||
name = fmt(
|
||||
"building of '%s' from in-memory derivation",
|
||||
DerivedPath::Built { drvPath, drv.outputNames() }.to_string(worker.store));
|
||||
DerivedPath::Built { makeConstantStorePathRef(drvPath), drv.outputNames() }.to_string(worker.store));
|
||||
trace("created");
|
||||
|
||||
mcExpectedBuilds = std::make_unique<MaintainCount<uint64_t>>(worker.expectedBuilds);
|
||||
|
@ -195,10 +196,19 @@ void DerivationGoal::loadDerivation()
|
|||
things being garbage collected while we're busy. */
|
||||
worker.evalStore.addTempRoot(drvPath);
|
||||
|
||||
assert(worker.evalStore.isValidPath(drvPath));
|
||||
/* Get the derivation. It is probably in the eval store, but it might be inthe main store:
|
||||
|
||||
/* Get the derivation. */
|
||||
drv = std::make_unique<Derivation>(worker.evalStore.readDerivation(drvPath));
|
||||
- Resolved derivation are resolved against main store realisations, and so must be stored there.
|
||||
|
||||
- Dynamic derivations are built, and so are found in the main store.
|
||||
*/
|
||||
for (auto * drvStore : { &worker.evalStore, &worker.store }) {
|
||||
if (drvStore->isValidPath(drvPath)) {
|
||||
drv = std::make_unique<Derivation>(drvStore->readDerivation(drvPath));
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert(drv);
|
||||
|
||||
haveDerivation();
|
||||
}
|
||||
|
@ -367,27 +377,48 @@ void DerivationGoal::gaveUpOnSubstitution()
|
|||
|
||||
/* The inputs must be built before we can build this goal. */
|
||||
inputDrvOutputs.clear();
|
||||
if (useDerivation)
|
||||
for (auto & i : dynamic_cast<Derivation *>(drv.get())->inputDrvs) {
|
||||
if (useDerivation) {
|
||||
std::function<void(ref<SingleDerivedPath>, const DerivedPathMap<StringSet>::ChildNode &)> addWaiteeDerivedPath;
|
||||
|
||||
addWaiteeDerivedPath = [&](ref<SingleDerivedPath> inputDrv, const DerivedPathMap<StringSet>::ChildNode & inputNode) {
|
||||
if (!inputNode.value.empty())
|
||||
addWaitee(worker.makeGoal(
|
||||
DerivedPath::Built {
|
||||
.drvPath = inputDrv,
|
||||
.outputs = inputNode.value,
|
||||
},
|
||||
buildMode == bmRepair ? bmRepair : bmNormal));
|
||||
for (const auto & [outputName, childNode] : inputNode.childMap)
|
||||
addWaiteeDerivedPath(
|
||||
make_ref<SingleDerivedPath>(SingleDerivedPath::Built { inputDrv, outputName }),
|
||||
childNode);
|
||||
};
|
||||
|
||||
for (const auto & [inputDrvPath, inputNode] : dynamic_cast<Derivation *>(drv.get())->inputDrvs.map) {
|
||||
/* Ensure that pure, non-fixed-output derivations don't
|
||||
depend on impure derivations. */
|
||||
if (experimentalFeatureSettings.isEnabled(Xp::ImpureDerivations) && drv->type().isPure() && !drv->type().isFixed()) {
|
||||
auto inputDrv = worker.evalStore.readDerivation(i.first);
|
||||
auto inputDrv = worker.evalStore.readDerivation(inputDrvPath);
|
||||
if (!inputDrv.type().isPure())
|
||||
throw Error("pure derivation '%s' depends on impure derivation '%s'",
|
||||
worker.store.printStorePath(drvPath),
|
||||
worker.store.printStorePath(i.first));
|
||||
worker.store.printStorePath(inputDrvPath));
|
||||
}
|
||||
|
||||
addWaitee(worker.makeDerivationGoal(i.first, i.second, buildMode == bmRepair ? bmRepair : bmNormal));
|
||||
addWaiteeDerivedPath(makeConstantStorePathRef(inputDrvPath), inputNode);
|
||||
}
|
||||
}
|
||||
|
||||
/* Copy the input sources from the eval store to the build
|
||||
store. */
|
||||
store.
|
||||
|
||||
Note that some inputs might not be in the eval store because they
|
||||
are (resolved) derivation outputs in a resolved derivation. */
|
||||
if (&worker.evalStore != &worker.store) {
|
||||
RealisedPath::Set inputSrcs;
|
||||
for (auto & i : drv->inputSrcs)
|
||||
inputSrcs.insert(i);
|
||||
if (worker.evalStore.isValidPath(i))
|
||||
inputSrcs.insert(i);
|
||||
copyClosure(worker.evalStore, worker.store, inputSrcs);
|
||||
}
|
||||
|
||||
|
@ -435,7 +466,7 @@ void DerivationGoal::repairClosure()
|
|||
std::map<StorePath, StorePath> outputsToDrv;
|
||||
for (auto & i : inputClosure)
|
||||
if (i.isDerivation()) {
|
||||
auto depOutputs = worker.store.queryPartialDerivationOutputMap(i);
|
||||
auto depOutputs = worker.store.queryPartialDerivationOutputMap(i, &worker.evalStore);
|
||||
for (auto & j : depOutputs)
|
||||
if (j.second)
|
||||
outputsToDrv.insert_or_assign(*j.second, i);
|
||||
|
@ -451,7 +482,12 @@ void DerivationGoal::repairClosure()
|
|||
if (drvPath2 == outputsToDrv.end())
|
||||
addWaitee(upcast_goal(worker.makePathSubstitutionGoal(i, Repair)));
|
||||
else
|
||||
addWaitee(worker.makeDerivationGoal(drvPath2->second, OutputsSpec::All(), bmRepair));
|
||||
addWaitee(worker.makeGoal(
|
||||
DerivedPath::Built {
|
||||
.drvPath = makeConstantStorePathRef(drvPath2->second),
|
||||
.outputs = OutputsSpec::All { },
|
||||
},
|
||||
bmRepair));
|
||||
}
|
||||
|
||||
if (waitees.empty()) {
|
||||
|
@ -508,7 +544,7 @@ void DerivationGoal::inputsRealised()
|
|||
return ia.deferred;
|
||||
},
|
||||
[&](const DerivationType::ContentAddressed & ca) {
|
||||
return !fullDrv.inputDrvs.empty() && (
|
||||
return !fullDrv.inputDrvs.map.empty() && (
|
||||
ca.fixed
|
||||
/* Can optionally resolve if fixed, which is good
|
||||
for avoiding unnecessary rebuilds. */
|
||||
|
@ -520,9 +556,9 @@ void DerivationGoal::inputsRealised()
|
|||
[&](const DerivationType::Impure &) {
|
||||
return true;
|
||||
}
|
||||
}, drvType.raw());
|
||||
}, drvType.raw);
|
||||
|
||||
if (resolveDrv && !fullDrv.inputDrvs.empty()) {
|
||||
if (resolveDrv && !fullDrv.inputDrvs.map.empty()) {
|
||||
experimentalFeatureSettings.require(Xp::CaDerivations);
|
||||
|
||||
/* We are be able to resolve this derivation based on the
|
||||
|
@ -535,10 +571,10 @@ 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) };
|
||||
Derivation drvResolved { std::move(*attempt) };
|
||||
|
||||
auto pathResolved = writeDerivation(worker.store, drvResolved);
|
||||
|
||||
|
@ -559,11 +595,13 @@ void DerivationGoal::inputsRealised()
|
|||
return;
|
||||
}
|
||||
|
||||
for (auto & [depDrvPath, wantedDepOutputs] : fullDrv.inputDrvs) {
|
||||
std::function<void(const StorePath &, const DerivedPathMap<StringSet>::ChildNode &)> accumInputPaths;
|
||||
|
||||
accumInputPaths = [&](const StorePath & depDrvPath, const DerivedPathMap<StringSet>::ChildNode & inputNode) {
|
||||
/* Add the relevant output closures of the input derivation
|
||||
`i' as input paths. Only add the closures of output paths
|
||||
that are specified as inputs. */
|
||||
for (auto & j : wantedDepOutputs) {
|
||||
auto getOutput = [&](const std::string & outputName) {
|
||||
/* TODO (impure derivations-induced tech debt):
|
||||
Tracking input derivation outputs statefully through the
|
||||
goals is error prone and has led to bugs.
|
||||
|
@ -575,21 +613,36 @@ void DerivationGoal::inputsRealised()
|
|||
a representation in the store, which is a usability problem
|
||||
in itself. When implementing this logic entirely with lookups
|
||||
make sure that they're cached. */
|
||||
if (auto outPath = get(inputDrvOutputs, { depDrvPath, j })) {
|
||||
worker.store.computeFSClosure(*outPath, inputPaths);
|
||||
if (auto outPath = get(inputDrvOutputs, { depDrvPath, outputName })) {
|
||||
return *outPath;
|
||||
}
|
||||
else {
|
||||
auto outMap = worker.evalStore.queryDerivationOutputMap(depDrvPath);
|
||||
auto outMapPath = outMap.find(j);
|
||||
auto outMap = [&]{
|
||||
for (auto * drvStore : { &worker.evalStore, &worker.store })
|
||||
if (drvStore->isValidPath(depDrvPath))
|
||||
return worker.store.queryDerivationOutputMap(depDrvPath, drvStore);
|
||||
assert(false);
|
||||
}();
|
||||
|
||||
auto outMapPath = outMap.find(outputName);
|
||||
if (outMapPath == outMap.end()) {
|
||||
throw Error(
|
||||
"derivation '%s' requires non-existent output '%s' from input derivation '%s'",
|
||||
worker.store.printStorePath(drvPath), j, worker.store.printStorePath(depDrvPath));
|
||||
worker.store.printStorePath(drvPath), outputName, worker.store.printStorePath(depDrvPath));
|
||||
}
|
||||
worker.store.computeFSClosure(outMapPath->second, inputPaths);
|
||||
return outMapPath->second;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for (auto & outputName : inputNode.value)
|
||||
worker.store.computeFSClosure(getOutput(outputName), inputPaths);
|
||||
|
||||
for (auto & [outputName, childNode] : inputNode.childMap)
|
||||
accumInputPaths(getOutput(outputName), childNode);
|
||||
};
|
||||
|
||||
for (auto & [depDrvPath, depNode] : fullDrv.inputDrvs.map)
|
||||
accumInputPaths(depDrvPath, depNode);
|
||||
}
|
||||
|
||||
/* Second, the input sources. */
|
||||
|
@ -995,10 +1048,11 @@ void DerivationGoal::buildDone()
|
|||
}
|
||||
|
||||
else {
|
||||
assert(derivationType);
|
||||
st =
|
||||
dynamic_cast<NotDeterministic*>(&e) ? BuildResult::NotDeterministic :
|
||||
statusOk(status) ? BuildResult::OutputRejected :
|
||||
!derivationType.isSandboxed() || diskFull ? BuildResult::TransientFailure :
|
||||
!derivationType->isSandboxed() || diskFull ? BuildResult::TransientFailure :
|
||||
BuildResult::PermanentFailure;
|
||||
}
|
||||
|
||||
|
@ -1050,8 +1104,12 @@ void DerivationGoal::resolvedFinished()
|
|||
auto newRealisation = realisation;
|
||||
newRealisation.id = DrvOutput { initialOutput->outputHash, outputName };
|
||||
newRealisation.signatures.clear();
|
||||
if (!drv->type().isFixed())
|
||||
newRealisation.dependentRealisations = drvOutputReferences(worker.store, *drv, realisation.outPath);
|
||||
if (!drv->type().isFixed()) {
|
||||
auto & drvStore = worker.evalStore.isValidPath(drvPath)
|
||||
? worker.evalStore
|
||||
: worker.store;
|
||||
newRealisation.dependentRealisations = drvOutputReferences(worker.store, *drv, realisation.outPath, &drvStore);
|
||||
}
|
||||
signRealisation(newRealisation);
|
||||
worker.store.registerDrvOutput(newRealisation);
|
||||
}
|
||||
|
@ -1150,9 +1208,11 @@ HookReply DerivationGoal::tryBuildHook()
|
|||
throw;
|
||||
}
|
||||
|
||||
CommonProto::WriteConn conn { hook->sink };
|
||||
|
||||
/* Tell the hook all the inputs that have to be copied to the
|
||||
remote system. */
|
||||
workerProtoWrite(worker.store, hook->sink, inputPaths);
|
||||
CommonProto::write(worker.store, conn, inputPaths);
|
||||
|
||||
/* Tell the hooks the missing outputs that have to be copied back
|
||||
from the remote system. */
|
||||
|
@ -1163,7 +1223,7 @@ HookReply DerivationGoal::tryBuildHook()
|
|||
if (buildMode != bmCheck && status.known && status.known->isValid()) continue;
|
||||
missingOutputs.insert(outputName);
|
||||
}
|
||||
workerProtoWrite(worker.store, hook->sink, missingOutputs);
|
||||
CommonProto::write(worker.store, conn, missingOutputs);
|
||||
}
|
||||
|
||||
hook->sink = FdSink();
|
||||
|
@ -1280,9 +1340,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();
|
||||
|
@ -1325,7 +1402,10 @@ std::map<std::string, std::optional<StorePath>> DerivationGoal::queryPartialDeri
|
|||
res.insert_or_assign(name, output.path(worker.store, drv->name, name));
|
||||
return res;
|
||||
} else {
|
||||
return worker.store.queryPartialDerivationOutputMap(drvPath);
|
||||
for (auto * drvStore : { &worker.evalStore, &worker.store })
|
||||
if (drvStore->isValidPath(drvPath))
|
||||
return worker.store.queryPartialDerivationOutputMap(drvPath, drvStore);
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1338,7 +1418,10 @@ OutputPathMap DerivationGoal::queryDerivationOutputMap()
|
|||
res.insert_or_assign(name, *output.second);
|
||||
return res;
|
||||
} else {
|
||||
return worker.store.queryDerivationOutputMap(drvPath);
|
||||
for (auto * drvStore : { &worker.evalStore, &worker.store })
|
||||
if (drvStore->isValidPath(drvPath))
|
||||
return worker.store.queryDerivationOutputMap(drvPath, drvStore);
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1355,7 +1438,7 @@ std::pair<bool, SingleDrvOutputs> DerivationGoal::checkPathValidity()
|
|||
[&](const OutputsSpec::Names & names) {
|
||||
return static_cast<StringSet>(names);
|
||||
},
|
||||
}, wantedOutputs.raw());
|
||||
}, wantedOutputs.raw);
|
||||
SingleDrvOutputs validOutputs;
|
||||
|
||||
for (auto & i : queryPartialDerivationOutputMap()) {
|
||||
|
@ -1437,6 +1520,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));
|
||||
|
@ -1482,12 +1566,13 @@ void DerivationGoal::waiteeDone(GoalPtr waitee, ExitCode result)
|
|||
auto * dg = dynamic_cast<DerivationGoal *>(&*waitee);
|
||||
if (!dg) return;
|
||||
|
||||
auto outputs = fullDrv.inputDrvs.find(dg->drvPath);
|
||||
if (outputs == fullDrv.inputDrvs.end()) return;
|
||||
auto * nodeP = fullDrv.inputDrvs.findSlot(DerivedPath::Opaque { .path = dg->drvPath });
|
||||
if (!nodeP) return;
|
||||
auto & outputs = nodeP->value;
|
||||
|
||||
for (auto & outputName : outputs->second) {
|
||||
for (auto & outputName : outputs) {
|
||||
auto buildResult = dg->getBuildResult(DerivedPath::Built {
|
||||
.drvPath = dg->drvPath,
|
||||
.drvPath = makeConstantStorePathRef(dg->drvPath),
|
||||
.outputs = OutputsSpec::Names { outputName },
|
||||
});
|
||||
if (buildResult.success()) {
|
||||
|
|
|
@ -50,6 +50,9 @@ struct InitialOutput {
|
|||
std::optional<InitialOutputStatus> known;
|
||||
};
|
||||
|
||||
/**
|
||||
* A goal for building some or all of the outputs of a derivation.
|
||||
*/
|
||||
struct DerivationGoal : public Goal
|
||||
{
|
||||
/**
|
||||
|
@ -66,8 +69,7 @@ struct DerivationGoal : public Goal
|
|||
std::shared_ptr<DerivationGoal> resolvedDrvGoal;
|
||||
|
||||
/**
|
||||
* The specific outputs that we need to build. Empty means all of
|
||||
* them.
|
||||
* The specific outputs that we need to build.
|
||||
*/
|
||||
OutputsSpec wantedOutputs;
|
||||
|
||||
|
@ -184,7 +186,7 @@ struct DerivationGoal : public Goal
|
|||
/**
|
||||
* The sort of derivation we are building.
|
||||
*/
|
||||
DerivationType derivationType;
|
||||
std::optional<DerivationType> derivationType;
|
||||
|
||||
typedef void (DerivationGoal::*GoalState)();
|
||||
GoalState state;
|
||||
|
@ -334,7 +336,9 @@ struct DerivationGoal : public Goal
|
|||
|
||||
StorePathSet exportReferences(const StorePathSet & storePaths);
|
||||
|
||||
JobCategory jobCategory() override { return JobCategory::Build; };
|
||||
JobCategory jobCategory() const override {
|
||||
return JobCategory::Build;
|
||||
};
|
||||
};
|
||||
|
||||
MakeError(NotDeterministic, BuildError);
|
||||
|
|
|
@ -73,7 +73,9 @@ public:
|
|||
void work() override;
|
||||
void handleEOF(int fd) override;
|
||||
|
||||
JobCategory jobCategory() override { return JobCategory::Substitution; };
|
||||
JobCategory jobCategory() const override {
|
||||
return JobCategory::Substitution;
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
@ -25,17 +25,19 @@ void Store::buildPaths(const std::vector<DerivedPath> & reqs, BuildMode buildMod
|
|||
ex = std::move(i->ex);
|
||||
}
|
||||
if (i->exitCode != Goal::ecSuccess) {
|
||||
if (auto i2 = dynamic_cast<DerivationGoal *>(i.get())) failed.insert(i2->drvPath);
|
||||
else if (auto i2 = dynamic_cast<PathSubstitutionGoal *>(i.get())) failed.insert(i2->storePath);
|
||||
if (auto i2 = dynamic_cast<DerivationGoal *>(i.get()))
|
||||
failed.insert(std::string { i2->drvPath.to_string() });
|
||||
else if (auto i2 = dynamic_cast<PathSubstitutionGoal *>(i.get()))
|
||||
failed.insert(std::string { i2->storePath.to_string()});
|
||||
}
|
||||
}
|
||||
|
||||
if (failed.size() == 1 && ex) {
|
||||
ex->status = worker.exitStatus();
|
||||
ex->status = worker.failingExitStatus();
|
||||
throw std::move(*ex);
|
||||
} else if (!failed.empty()) {
|
||||
if (ex) logError(ex->info());
|
||||
throw Error(worker.exitStatus(), "build of %s failed", showPaths(failed));
|
||||
throw Error(worker.failingExitStatus(), "build of %s failed", concatStringsSep(", ", quoteStrings(failed)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -77,7 +79,7 @@ BuildResult Store::buildDerivation(const StorePath & drvPath, const BasicDerivat
|
|||
try {
|
||||
worker.run(Goals{goal});
|
||||
return goal->getBuildResult(DerivedPath::Built {
|
||||
.drvPath = drvPath,
|
||||
.drvPath = makeConstantStorePathRef(drvPath),
|
||||
.outputs = OutputsSpec::All {},
|
||||
});
|
||||
} catch (Error & e) {
|
||||
|
@ -102,10 +104,10 @@ void Store::ensurePath(const StorePath & path)
|
|||
|
||||
if (goal->exitCode != Goal::ecSuccess) {
|
||||
if (goal->ex) {
|
||||
goal->ex->status = worker.exitStatus();
|
||||
goal->ex->status = worker.failingExitStatus();
|
||||
throw std::move(*goal->ex);
|
||||
} else
|
||||
throw Error(worker.exitStatus(), "path '%s' does not exist and cannot be created", printStorePath(path));
|
||||
throw Error(worker.failingExitStatus(), "path '%s' does not exist and cannot be created", printStorePath(path));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -124,11 +126,14 @@ void Store::repairPath(const StorePath & path)
|
|||
auto info = queryPathInfo(path);
|
||||
if (info->deriver && isValidPath(*info->deriver)) {
|
||||
goals.clear();
|
||||
// FIXME: Should just build the specific output we need.
|
||||
goals.insert(worker.makeDerivationGoal(*info->deriver, OutputsSpec::All { }, bmRepair));
|
||||
goals.insert(worker.makeGoal(DerivedPath::Built {
|
||||
.drvPath = makeConstantStorePathRef(*info->deriver),
|
||||
// FIXME: Should just build the specific output we need.
|
||||
.outputs = OutputsSpec::All { },
|
||||
}, bmRepair));
|
||||
worker.run(goals);
|
||||
} else
|
||||
throw Error(worker.exitStatus(), "cannot repair path '%s'", printStorePath(path));
|
||||
throw Error(worker.failingExitStatus(), "cannot repair path '%s'", printStorePath(path));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ bool CompareGoalPtrs::operator() (const GoalPtr & a, const GoalPtr & b) const {
|
|||
}
|
||||
|
||||
|
||||
BuildResult Goal::getBuildResult(const DerivedPath & req) {
|
||||
BuildResult Goal::getBuildResult(const DerivedPath & req) const {
|
||||
BuildResult res { buildResult };
|
||||
|
||||
if (auto pbp = std::get_if<DerivedPath::Built>(&req)) {
|
||||
|
|
|
@ -41,7 +41,13 @@ typedef std::map<StorePath, WeakGoalPtr> WeakGoalMap;
|
|||
* of each category in parallel.
|
||||
*/
|
||||
enum struct JobCategory {
|
||||
/**
|
||||
* A build of a derivation; it will use CPU and disk resources.
|
||||
*/
|
||||
Build,
|
||||
/**
|
||||
* A substitution an arbitrary store object; it will use network resources.
|
||||
*/
|
||||
Substitution,
|
||||
};
|
||||
|
||||
|
@ -110,7 +116,7 @@ public:
|
|||
* sake of both privacy and determinism, and this "safe accessor"
|
||||
* ensures we don't.
|
||||
*/
|
||||
BuildResult getBuildResult(const DerivedPath &);
|
||||
BuildResult getBuildResult(const DerivedPath &) const;
|
||||
|
||||
/**
|
||||
* Exception containing an error message, if any.
|
||||
|
@ -144,7 +150,7 @@ public:
|
|||
|
||||
void trace(std::string_view s);
|
||||
|
||||
std::string getName()
|
||||
std::string getName() const
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
@ -162,7 +168,11 @@ public:
|
|||
|
||||
virtual void cleanup() { }
|
||||
|
||||
virtual JobCategory jobCategory() = 0;
|
||||
/**
|
||||
* @brief Hint for the scheduler, which concurrency limit applies.
|
||||
* @see JobCategory
|
||||
*/
|
||||
virtual JobCategory jobCategory() const = 0;
|
||||
};
|
||||
|
||||
void addToWeakGoals(WeakGoals & goals, GoalPtr p);
|
||||
|
|
|
@ -1,18 +1,20 @@
|
|||
#include "globals.hh"
|
||||
#include "hook-instance.hh"
|
||||
#include "file-system.hh"
|
||||
#include "child.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
HookInstance::HookInstance()
|
||||
{
|
||||
debug("starting build hook '%s'", settings.buildHook);
|
||||
debug("starting build hook '%s'", concatStringsSep(" ", settings.buildHook.get()));
|
||||
|
||||
auto buildHookArgs = tokenizeString<std::list<std::string>>(settings.buildHook.get());
|
||||
auto buildHookArgs = settings.buildHook.get();
|
||||
|
||||
if (buildHookArgs.empty())
|
||||
throw Error("'build-hook' setting is empty");
|
||||
|
||||
auto buildHook = buildHookArgs.front();
|
||||
auto buildHook = canonPath(buildHookArgs.front());
|
||||
buildHookArgs.pop_front();
|
||||
|
||||
Strings args;
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include "logging.hh"
|
||||
#include "serialise.hh"
|
||||
#include "processes.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#include "local-derivation-goal.hh"
|
||||
#include "gc-store.hh"
|
||||
#include "indirect-root-store.hh"
|
||||
#include "hook-instance.hh"
|
||||
#include "worker.hh"
|
||||
#include "builtins.hh"
|
||||
|
@ -10,13 +10,17 @@
|
|||
#include "archive.hh"
|
||||
#include "compression.hh"
|
||||
#include "daemon.hh"
|
||||
#include "worker-protocol.hh"
|
||||
#include "topo-sort.hh"
|
||||
#include "callback.hh"
|
||||
#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 "posix-source-accessor.hh"
|
||||
|
||||
#include <regex>
|
||||
#include <queue>
|
||||
|
@ -65,8 +69,9 @@ void handleDiffHook(
|
|||
const Path & tryA, const Path & tryB,
|
||||
const Path & drvPath, const Path & tmpDir)
|
||||
{
|
||||
auto diffHook = settings.diffHook;
|
||||
if (diffHook != "" && settings.runDiffHook) {
|
||||
auto & diffHookOpt = settings.diffHook.get();
|
||||
if (diffHookOpt && settings.runDiffHook) {
|
||||
auto & diffHook = *diffHookOpt;
|
||||
try {
|
||||
auto diffRes = runProgram(RunOptions {
|
||||
.program = diffHook,
|
||||
|
@ -178,6 +183,8 @@ void LocalDerivationGoal::tryLocalBuild()
|
|||
return;
|
||||
}
|
||||
|
||||
assert(derivationType);
|
||||
|
||||
/* Are we doing a chroot build? */
|
||||
{
|
||||
auto noChroot = parsedDrv->getBoolAttr("__noChroot");
|
||||
|
@ -195,7 +202,7 @@ void LocalDerivationGoal::tryLocalBuild()
|
|||
else if (settings.sandboxMode == smDisabled)
|
||||
useChroot = false;
|
||||
else if (settings.sandboxMode == smRelaxed)
|
||||
useChroot = derivationType.isSandboxed() && !noChroot;
|
||||
useChroot = derivationType->isSandboxed() && !noChroot;
|
||||
}
|
||||
|
||||
auto & localStore = getLocalStore();
|
||||
|
@ -225,7 +232,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;
|
||||
}
|
||||
|
@ -384,26 +391,27 @@ void LocalDerivationGoal::cleanupPostOutputsRegisteredModeNonCheck()
|
|||
cleanupPostOutputsRegisteredModeCheck();
|
||||
}
|
||||
|
||||
|
||||
#if __linux__
|
||||
static void linkOrCopy(const Path & from, const Path & to)
|
||||
{
|
||||
if (link(from.c_str(), to.c_str()) == -1) {
|
||||
/* Hard-linking fails if we exceed the maximum link count on a
|
||||
file (e.g. 32000 of ext3), which is quite possible after a
|
||||
'nix-store --optimise'. FIXME: actually, why don't we just
|
||||
bind-mount in this case?
|
||||
|
||||
It can also fail with EPERM in BeegFS v7 and earlier versions
|
||||
which don't allow hard-links to other directories */
|
||||
if (errno != EMLINK && errno != EPERM)
|
||||
throw SysError("linking '%s' to '%s'", to, from);
|
||||
copyPath(from, to);
|
||||
static void doBind(const Path & source, const Path & target, bool optional = false) {
|
||||
debug("bind mounting '%1%' to '%2%'", source, target);
|
||||
struct stat st;
|
||||
if (stat(source.c_str(), &st) == -1) {
|
||||
if (optional && errno == ENOENT)
|
||||
return;
|
||||
else
|
||||
throw SysError("getting attributes of path '%1%'", source);
|
||||
}
|
||||
}
|
||||
if (S_ISDIR(st.st_mode))
|
||||
createDirs(target);
|
||||
else {
|
||||
createDirs(dirOf(target));
|
||||
writeFile(target, "");
|
||||
}
|
||||
if (mount(source.c_str(), target.c_str(), "", MS_BIND | MS_REC, 0) == -1)
|
||||
throw SysError("bind mount from '%1%' to '%2%' failed", source, target);
|
||||
};
|
||||
#endif
|
||||
|
||||
|
||||
void LocalDerivationGoal::startBuilder()
|
||||
{
|
||||
if ((buildUser && buildUser->getUIDCount() != 1)
|
||||
|
@ -578,7 +586,7 @@ void LocalDerivationGoal::startBuilder()
|
|||
|
||||
/* Allow a user-configurable set of directories from the
|
||||
host file system. */
|
||||
dirsInChroot.clear();
|
||||
pathsInChroot.clear();
|
||||
|
||||
for (auto i : settings.sandboxPaths.get()) {
|
||||
if (i.empty()) continue;
|
||||
|
@ -589,15 +597,19 @@ void LocalDerivationGoal::startBuilder()
|
|||
}
|
||||
size_t p = i.find('=');
|
||||
if (p == std::string::npos)
|
||||
dirsInChroot[i] = {i, optional};
|
||||
pathsInChroot[i] = {i, optional};
|
||||
else
|
||||
dirsInChroot[i.substr(0, p)] = {i.substr(p + 1), optional};
|
||||
pathsInChroot[i.substr(0, p)] = {i.substr(p + 1), optional};
|
||||
}
|
||||
dirsInChroot[tmpDirInSandbox] = tmpDir;
|
||||
if (hasPrefix(worker.store.storeDir, tmpDirInSandbox))
|
||||
{
|
||||
throw Error("`sandbox-build-dir` must not contain the storeDir");
|
||||
}
|
||||
pathsInChroot[tmpDirInSandbox] = tmpDir;
|
||||
|
||||
/* Add the closure of store paths to the chroot. */
|
||||
StorePathSet closure;
|
||||
for (auto & i : dirsInChroot)
|
||||
for (auto & i : pathsInChroot)
|
||||
try {
|
||||
if (worker.store.isInStore(i.second.source))
|
||||
worker.store.computeFSClosure(worker.store.toStorePath(i.second.source).first, closure);
|
||||
|
@ -608,7 +620,7 @@ void LocalDerivationGoal::startBuilder()
|
|||
}
|
||||
for (auto & i : closure) {
|
||||
auto p = worker.store.printStorePath(i);
|
||||
dirsInChroot.insert_or_assign(p, p);
|
||||
pathsInChroot.insert_or_assign(p, p);
|
||||
}
|
||||
|
||||
PathSet allowedPaths = settings.allowedImpureHostPrefixes;
|
||||
|
@ -636,14 +648,14 @@ void LocalDerivationGoal::startBuilder()
|
|||
|
||||
/* Allow files in __impureHostDeps to be missing; e.g.
|
||||
macOS 11+ has no /usr/lib/libSystem*.dylib */
|
||||
dirsInChroot[i] = {i, true};
|
||||
pathsInChroot[i] = {i, true};
|
||||
}
|
||||
|
||||
#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);
|
||||
|
||||
|
@ -684,7 +696,7 @@ void LocalDerivationGoal::startBuilder()
|
|||
"nogroup:x:65534:\n", sandboxGid()));
|
||||
|
||||
/* Create /etc/hosts with localhost entry. */
|
||||
if (derivationType.isSandboxed())
|
||||
if (derivationType->isSandboxed())
|
||||
writeFile(chrootRootDir + "/etc/hosts", "127.0.0.1 localhost\n::1 localhost\n");
|
||||
|
||||
/* Make the closure of the inputs available in the chroot,
|
||||
|
@ -704,15 +716,12 @@ void LocalDerivationGoal::startBuilder()
|
|||
for (auto & i : inputPaths) {
|
||||
auto p = worker.store.printStorePath(i);
|
||||
Path r = worker.store.toRealPath(p);
|
||||
if (S_ISDIR(lstat(r).st_mode))
|
||||
dirsInChroot.insert_or_assign(p, r);
|
||||
else
|
||||
linkOrCopy(r, chrootRootDir + p);
|
||||
pathsInChroot.insert_or_assign(p, r);
|
||||
}
|
||||
|
||||
/* If we're repairing, checking or rebuilding part of a
|
||||
multiple-outputs derivation, it's possible that we're
|
||||
rebuilding a path that is in settings.dirsInChroot
|
||||
rebuilding a path that is in settings.sandbox-paths
|
||||
(typically the dependencies of /bin/sh). Throw them
|
||||
out. */
|
||||
for (auto & i : drv->outputsAndOptPaths(worker.store)) {
|
||||
|
@ -722,7 +731,7 @@ void LocalDerivationGoal::startBuilder()
|
|||
is already in the sandbox, so we don't need to worry about
|
||||
removing it. */
|
||||
if (i.second.second)
|
||||
dirsInChroot.erase(worker.store.printStorePath(*i.second.second));
|
||||
pathsInChroot.erase(worker.store.printStorePath(*i.second.second));
|
||||
}
|
||||
|
||||
if (cgroup) {
|
||||
|
@ -780,9 +789,9 @@ void LocalDerivationGoal::startBuilder()
|
|||
} else {
|
||||
auto p = line.find('=');
|
||||
if (p == std::string::npos)
|
||||
dirsInChroot[line] = line;
|
||||
pathsInChroot[line] = line;
|
||||
else
|
||||
dirsInChroot[line.substr(0, p)] = line.substr(p + 1);
|
||||
pathsInChroot[line.substr(0, p)] = line.substr(p + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -888,7 +897,7 @@ void LocalDerivationGoal::startBuilder()
|
|||
us.
|
||||
*/
|
||||
|
||||
if (derivationType.isSandboxed())
|
||||
if (derivationType->isSandboxed())
|
||||
privateNetwork = true;
|
||||
|
||||
userNamespaceSync.create();
|
||||
|
@ -907,15 +916,13 @@ void LocalDerivationGoal::startBuilder()
|
|||
openSlave();
|
||||
|
||||
/* Drop additional groups here because we can't do it
|
||||
after we've created the new user namespace. FIXME:
|
||||
this means that if we're not root in the parent
|
||||
namespace, we can't drop additional groups; they will
|
||||
be mapped to nogroup in the child namespace. There does
|
||||
not seem to be a workaround for this. (But who can tell
|
||||
from reading user_namespaces(7)?)
|
||||
See also https://lwn.net/Articles/621612/. */
|
||||
if (getuid() == 0 && setgroups(0, 0) == -1)
|
||||
throw SysError("setgroups failed");
|
||||
after we've created the new user namespace. */
|
||||
if (setgroups(0, 0) == -1) {
|
||||
if (errno != EPERM)
|
||||
throw SysError("setgroups failed");
|
||||
if (settings.requireDropSupplementaryGroups)
|
||||
throw Error("setgroups failed. Set the require-drop-supplementary-groups option to false to skip this step.");
|
||||
}
|
||||
|
||||
ProcessOptions options;
|
||||
options.cloneFlags = CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWIPC | CLONE_NEWUTS | CLONE_PARENT | SIGCHLD;
|
||||
|
@ -1060,8 +1067,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(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);
|
||||
|
@ -1118,7 +1125,7 @@ void LocalDerivationGoal::initEnv()
|
|||
derivation, tell the builder, so that for instance `fetchurl'
|
||||
can skip checking the output. On older Nixes, this environment
|
||||
variable won't be set, so `fetchurl' will do the check. */
|
||||
if (derivationType.isFixed()) env["NIX_OUTPUT_CHECKED"] = "1";
|
||||
if (derivationType->isFixed()) env["NIX_OUTPUT_CHECKED"] = "1";
|
||||
|
||||
/* *Only* if this is a fixed-output derivation, propagate the
|
||||
values of the environment variables specified in the
|
||||
|
@ -1129,9 +1136,19 @@ void LocalDerivationGoal::initEnv()
|
|||
to the builder is generally impure, but the output of
|
||||
fixed-output derivations is by definition pure (since we
|
||||
already know the cryptographic hash of the output). */
|
||||
if (!derivationType.isSandboxed()) {
|
||||
for (auto & i : parsedDrv->getStringsAttr("impureEnvVars").value_or(Strings()))
|
||||
env[i] = getEnv(i).value_or("");
|
||||
if (!derivationType->isSandboxed()) {
|
||||
auto & impureEnv = settings.impureEnv.get();
|
||||
if (!impureEnv.empty())
|
||||
experimentalFeatureSettings.require(Xp::ConfigurableImpureEnv);
|
||||
|
||||
for (auto & i : parsedDrv->getStringsAttr("impureEnvVars").value_or(Strings())) {
|
||||
auto envVar = impureEnv.find(i);
|
||||
if (envVar != impureEnv.end()) {
|
||||
env[i] = envVar->second;
|
||||
} else {
|
||||
env[i] = getEnv(i).value_or("");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Currently structured log messages piggyback on stderr, but we
|
||||
|
@ -1169,6 +1186,19 @@ void LocalDerivationGoal::writeStructuredAttrs()
|
|||
}
|
||||
|
||||
|
||||
static StorePath pathPartOfReq(const SingleDerivedPath & req)
|
||||
{
|
||||
return std::visit(overloaded {
|
||||
[&](const SingleDerivedPath::Opaque & bo) {
|
||||
return bo.path;
|
||||
},
|
||||
[&](const SingleDerivedPath::Built & bfd) {
|
||||
return pathPartOfReq(*bfd.drvPath);
|
||||
},
|
||||
}, req.raw());
|
||||
}
|
||||
|
||||
|
||||
static StorePath pathPartOfReq(const DerivedPath & req)
|
||||
{
|
||||
return std::visit(overloaded {
|
||||
|
@ -1176,7 +1206,7 @@ static StorePath pathPartOfReq(const DerivedPath & req)
|
|||
return bo.path;
|
||||
},
|
||||
[&](const DerivedPath::Built & bfd) {
|
||||
return bfd.drvPath;
|
||||
return pathPartOfReq(*bfd.drvPath);
|
||||
},
|
||||
}, req.raw());
|
||||
}
|
||||
|
@ -1197,7 +1227,7 @@ struct RestrictedStoreConfig : virtual LocalFSStoreConfig
|
|||
/* A wrapper around LocalStore that only allows building/querying of
|
||||
paths that are in the input closures of the build or were added via
|
||||
recursive Nix calls. */
|
||||
struct RestrictedStore : public virtual RestrictedStoreConfig, public virtual LocalFSStore, public virtual GcStore
|
||||
struct RestrictedStore : public virtual RestrictedStoreConfig, public virtual IndirectRootStore, public virtual GcStore
|
||||
{
|
||||
ref<LocalStore> next;
|
||||
|
||||
|
@ -1248,11 +1278,13 @@ struct RestrictedStore : public virtual RestrictedStoreConfig, public virtual Lo
|
|||
void queryReferrers(const StorePath & path, StorePathSet & referrers) override
|
||||
{ }
|
||||
|
||||
std::map<std::string, std::optional<StorePath>> queryPartialDerivationOutputMap(const StorePath & path) override
|
||||
std::map<std::string, std::optional<StorePath>> queryPartialDerivationOutputMap(
|
||||
const StorePath & path,
|
||||
Store * evalStore = nullptr) override
|
||||
{
|
||||
if (!goal.isAllowed(path))
|
||||
throw InvalidPath("cannot query output map for unknown path '%s' in recursive Nix", printStorePath(path));
|
||||
return next->queryPartialDerivationOutputMap(path);
|
||||
return next->queryPartialDerivationOutputMap(path, evalStore);
|
||||
}
|
||||
|
||||
std::optional<StorePath> queryPathFromHashPart(const std::string & hashPart) override
|
||||
|
@ -1260,12 +1292,13 @@ struct RestrictedStore : public virtual RestrictedStoreConfig, public virtual Lo
|
|||
|
||||
StorePath addToStore(
|
||||
std::string_view name,
|
||||
const Path & srcPath,
|
||||
FileIngestionMethod method,
|
||||
HashType hashAlgo,
|
||||
SourceAccessor & accessor,
|
||||
const CanonPath & srcPath,
|
||||
ContentAddressMethod method,
|
||||
HashAlgorithm hashAlgo,
|
||||
const StorePathSet & references,
|
||||
PathFilter & filter,
|
||||
RepairFlag repair,
|
||||
const StorePathSet & references) override
|
||||
RepairFlag repair) override
|
||||
{ throw Error("addToStore"); }
|
||||
|
||||
void addToStore(const ValidPathInfo & info, Source & narSource,
|
||||
|
@ -1275,26 +1308,15 @@ struct RestrictedStore : public virtual RestrictedStoreConfig, public virtual Lo
|
|||
goal.addDependency(info.path);
|
||||
}
|
||||
|
||||
StorePath addTextToStore(
|
||||
std::string_view name,
|
||||
std::string_view s,
|
||||
const StorePathSet & references,
|
||||
RepairFlag repair = NoRepair) override
|
||||
{
|
||||
auto path = next->addTextToStore(name, s, references, repair);
|
||||
goal.addDependency(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
StorePath addToStoreFromDump(
|
||||
Source & dump,
|
||||
std::string_view name,
|
||||
FileIngestionMethod method,
|
||||
HashType hashAlgo,
|
||||
RepairFlag repair,
|
||||
const StorePathSet & references) override
|
||||
ContentAddressMethod method,
|
||||
HashAlgorithm hashAlgo,
|
||||
const StorePathSet & references,
|
||||
RepairFlag repair) override
|
||||
{
|
||||
auto path = next->addToStoreFromDump(dump, name, method, hashAlgo, repair, references);
|
||||
auto path = next->addToStoreFromDump(dump, name, method, hashAlgo, references, repair);
|
||||
goal.addDependency(path);
|
||||
return path;
|
||||
}
|
||||
|
@ -1428,7 +1450,8 @@ void LocalDerivationGoal::startDaemon()
|
|||
Store::Params params;
|
||||
params["path-info-cache-size"] = "0";
|
||||
params["store"] = worker.store.storeDir;
|
||||
params["root"] = getLocalStore().rootDir;
|
||||
if (auto & optRoot = getLocalStore().rootDir.get())
|
||||
params["root"] = *optRoot;
|
||||
params["state"] = "/no-such-path";
|
||||
params["log"] = "/no-such-path";
|
||||
auto store = make_ref<RestrictedStore>(params,
|
||||
|
@ -1534,41 +1557,33 @@ void LocalDerivationGoal::addDependency(const StorePath & path)
|
|||
|
||||
Path source = worker.store.Store::toRealPath(path);
|
||||
Path target = chrootRootDir + worker.store.printStorePath(path);
|
||||
debug("bind-mounting %s -> %s", target, source);
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
auto st = lstat(source);
|
||||
/* Bind-mount the path into the sandbox. This requires
|
||||
entering its mount namespace, which is not possible
|
||||
in multithreaded programs. So we do this in a
|
||||
child process.*/
|
||||
Pid child(startProcess([&]() {
|
||||
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
if (usingUserNamespace && (setns(sandboxUserNamespace.get(), 0) == -1))
|
||||
throw SysError("entering sandbox user namespace");
|
||||
|
||||
/* Bind-mount the path into the sandbox. This requires
|
||||
entering its mount namespace, which is not possible
|
||||
in multithreaded programs. So we do this in a
|
||||
child process.*/
|
||||
Pid child(startProcess([&]() {
|
||||
if (setns(sandboxMountNamespace.get(), 0) == -1)
|
||||
throw SysError("entering sandbox mount namespace");
|
||||
|
||||
if (usingUserNamespace && (setns(sandboxUserNamespace.get(), 0) == -1))
|
||||
throw SysError("entering sandbox user namespace");
|
||||
doBind(source, target);
|
||||
|
||||
if (setns(sandboxMountNamespace.get(), 0) == -1)
|
||||
throw SysError("entering sandbox mount namespace");
|
||||
_exit(0);
|
||||
}));
|
||||
|
||||
createDirs(target);
|
||||
|
||||
if (mount(source.c_str(), target.c_str(), "", MS_BIND, 0) == -1)
|
||||
throw SysError("bind mount from '%s' to '%s' failed", source, target);
|
||||
|
||||
_exit(0);
|
||||
}));
|
||||
|
||||
int status = child.wait();
|
||||
if (status != 0)
|
||||
throw Error("could not add path '%s' to sandbox", worker.store.printStorePath(path));
|
||||
|
||||
} else
|
||||
linkOrCopy(source, target);
|
||||
int status = child.wait();
|
||||
if (status != 0)
|
||||
throw Error("could not add path '%s' to sandbox", worker.store.printStorePath(path));
|
||||
|
||||
#else
|
||||
throw Error("don't know how to make path '%s' (produced by a recursive Nix call) appear in the sandbox",
|
||||
|
@ -1600,6 +1615,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");
|
||||
|
@ -1758,7 +1775,7 @@ void LocalDerivationGoal::runChild()
|
|||
/* Set up a nearly empty /dev, unless the user asked to
|
||||
bind-mount the host /dev. */
|
||||
Strings ss;
|
||||
if (dirsInChroot.find("/dev") == dirsInChroot.end()) {
|
||||
if (pathsInChroot.find("/dev") == pathsInChroot.end()) {
|
||||
createDirs(chrootRootDir + "/dev/shm");
|
||||
createDirs(chrootRootDir + "/dev/pts");
|
||||
ss.push_back("/dev/full");
|
||||
|
@ -1778,7 +1795,7 @@ void LocalDerivationGoal::runChild()
|
|||
/* Fixed-output derivations typically need to access the
|
||||
network, so give them access to /etc/resolv.conf and so
|
||||
on. */
|
||||
if (!derivationType.isSandboxed()) {
|
||||
if (!derivationType->isSandboxed()) {
|
||||
// Only use nss functions to resolve hosts and
|
||||
// services. Don’t use it for anything else that may
|
||||
// be configured for this system. This limits the
|
||||
|
@ -1793,34 +1810,15 @@ void LocalDerivationGoal::runChild()
|
|||
ss.push_back(path);
|
||||
|
||||
if (settings.caFile != "")
|
||||
dirsInChroot.try_emplace("/etc/ssl/certs/ca-certificates.crt", settings.caFile, true);
|
||||
pathsInChroot.try_emplace("/etc/ssl/certs/ca-certificates.crt", settings.caFile, true);
|
||||
}
|
||||
|
||||
for (auto & i : ss) dirsInChroot.emplace(i, i);
|
||||
for (auto & i : ss) pathsInChroot.emplace(i, i);
|
||||
|
||||
/* Bind-mount all the directories from the "host"
|
||||
filesystem that we want in the chroot
|
||||
environment. */
|
||||
auto doBind = [&](const Path & source, const Path & target, bool optional = false) {
|
||||
debug("bind mounting '%1%' to '%2%'", source, target);
|
||||
struct stat st;
|
||||
if (stat(source.c_str(), &st) == -1) {
|
||||
if (optional && errno == ENOENT)
|
||||
return;
|
||||
else
|
||||
throw SysError("getting attributes of path '%1%'", source);
|
||||
}
|
||||
if (S_ISDIR(st.st_mode))
|
||||
createDirs(target);
|
||||
else {
|
||||
createDirs(dirOf(target));
|
||||
writeFile(target, "");
|
||||
}
|
||||
if (mount(source.c_str(), target.c_str(), "", MS_BIND | MS_REC, 0) == -1)
|
||||
throw SysError("bind mount from '%1%' to '%2%' failed", source, target);
|
||||
};
|
||||
|
||||
for (auto & i : dirsInChroot) {
|
||||
for (auto & i : pathsInChroot) {
|
||||
if (i.second.source == "/proc") continue; // backwards compatibility
|
||||
|
||||
#if HAVE_EMBEDDED_SANDBOX_SHELL
|
||||
|
@ -1861,7 +1859,7 @@ void LocalDerivationGoal::runChild()
|
|||
if /dev/ptx/ptmx exists). */
|
||||
if (pathExists("/dev/pts/ptmx") &&
|
||||
!pathExists(chrootRootDir + "/dev/ptmx")
|
||||
&& !dirsInChroot.count("/dev/pts"))
|
||||
&& !pathsInChroot.count("/dev/pts"))
|
||||
{
|
||||
if (mount("none", (chrootRootDir + "/dev/pts").c_str(), "devpts", 0, "newinstance,mode=0620") == 0)
|
||||
{
|
||||
|
@ -1996,7 +1994,7 @@ void LocalDerivationGoal::runChild()
|
|||
/* We build the ancestry before adding all inputPaths to the store because we know they'll
|
||||
all have the same parents (the store), and there might be lots of inputs. This isn't
|
||||
particularly efficient... I doubt it'll be a bottleneck in practice */
|
||||
for (auto & i : dirsInChroot) {
|
||||
for (auto & i : pathsInChroot) {
|
||||
Path cur = i.first;
|
||||
while (cur.compare("/") != 0) {
|
||||
cur = dirOf(cur);
|
||||
|
@ -2004,7 +2002,7 @@ void LocalDerivationGoal::runChild()
|
|||
}
|
||||
}
|
||||
|
||||
/* And we want the store in there regardless of how empty dirsInChroot. We include the innermost
|
||||
/* And we want the store in there regardless of how empty pathsInChroot. We include the innermost
|
||||
path component this time, since it's typically /nix/store and we care about that. */
|
||||
Path cur = worker.store.storeDir;
|
||||
while (cur.compare("/") != 0) {
|
||||
|
@ -2015,7 +2013,7 @@ void LocalDerivationGoal::runChild()
|
|||
/* Add all our input paths to the chroot */
|
||||
for (auto & i : inputPaths) {
|
||||
auto p = worker.store.printStorePath(i);
|
||||
dirsInChroot[p] = p;
|
||||
pathsInChroot[p] = p;
|
||||
}
|
||||
|
||||
/* Violations will go to the syslog if you set this. Unfortunately the destination does not appear to be configurable */
|
||||
|
@ -2029,7 +2027,7 @@ void LocalDerivationGoal::runChild()
|
|||
#include "sandbox-defaults.sb"
|
||||
;
|
||||
|
||||
if (!derivationType.isSandboxed())
|
||||
if (!derivationType->isSandboxed())
|
||||
sandboxProfile +=
|
||||
#include "sandbox-network.sb"
|
||||
;
|
||||
|
@ -2046,7 +2044,7 @@ void LocalDerivationGoal::runChild()
|
|||
without file-write* allowed, access() incorrectly returns EPERM
|
||||
*/
|
||||
sandboxProfile += "(allow file-read* file-write* process-exec\n";
|
||||
for (auto & i : dirsInChroot) {
|
||||
for (auto & i : pathsInChroot) {
|
||||
if (i.first != i.second.source)
|
||||
throw Error(
|
||||
"can't map '%1%' to '%2%': mismatched impure paths not supported on Darwin",
|
||||
|
@ -2301,7 +2299,6 @@ SingleDrvOutputs LocalDerivationGoal::registerOutputs()
|
|||
bool discardReferences = false;
|
||||
if (auto structuredAttrs = parsedDrv->getStructuredAttrs()) {
|
||||
if (auto udr = get(*structuredAttrs, "unsafeDiscardReferences")) {
|
||||
experimentalFeatureSettings.require(Xp::DiscardReferences);
|
||||
if (auto output = get(*udr, outputName)) {
|
||||
if (!output->is_boolean())
|
||||
throw Error("attribute 'unsafeDiscardReferences.\"%s\"' of derivation '%s' must be a Boolean", outputName, drvPath.to_string());
|
||||
|
@ -2447,8 +2444,7 @@ SingleDrvOutputs LocalDerivationGoal::registerOutputs()
|
|||
throw BuildError(
|
||||
"output path %1% without valid stats info",
|
||||
actualPath);
|
||||
if (outputHash.method == ContentAddressMethod { FileIngestionMethod::Flat } ||
|
||||
outputHash.method == ContentAddressMethod { TextIngestionMethod {} })
|
||||
if (outputHash.method.getFileIngestionMethod() == FileIngestionMethod::Flat)
|
||||
{
|
||||
/* The output path should be a regular file without execute permission. */
|
||||
if (!S_ISREG(st->st_mode) || (st->st_mode & S_IXUSR) != 0)
|
||||
|
@ -2460,38 +2456,23 @@ SingleDrvOutputs LocalDerivationGoal::registerOutputs()
|
|||
rewriteOutput(outputRewrites);
|
||||
/* FIXME optimize and deduplicate with addToStore */
|
||||
std::string oldHashPart { scratchPath->hashPart() };
|
||||
HashModuloSink caSink { outputHash.hashType, oldHashPart };
|
||||
std::visit(overloaded {
|
||||
[&](const TextIngestionMethod &) {
|
||||
readFile(actualPath, caSink);
|
||||
},
|
||||
[&](const FileIngestionMethod & m2) {
|
||||
switch (m2) {
|
||||
case FileIngestionMethod::Recursive:
|
||||
dumpPath(actualPath, caSink);
|
||||
break;
|
||||
case FileIngestionMethod::Flat:
|
||||
readFile(actualPath, caSink);
|
||||
break;
|
||||
}
|
||||
},
|
||||
}, outputHash.method.raw);
|
||||
auto got = caSink.finish().first;
|
||||
auto got = ({
|
||||
HashModuloSink caSink { outputHash.hashAlgo, oldHashPart };
|
||||
PosixSourceAccessor accessor;
|
||||
dumpPath(
|
||||
accessor, CanonPath { actualPath },
|
||||
caSink,
|
||||
outputHash.method.getFileIngestionMethod());
|
||||
caSink.finish().first;
|
||||
});
|
||||
|
||||
auto optCA = ContentAddressWithReferences::fromPartsOpt(
|
||||
outputHash.method,
|
||||
std::move(got),
|
||||
rewriteRefs());
|
||||
if (!optCA) {
|
||||
// TODO track distinct failure modes separately (at the time of
|
||||
// writing there is just one but `nullopt` is unclear) so this
|
||||
// message can't get out of sync.
|
||||
throw BuildError("output path '%s' has illegal content address, probably a spurious self-reference with text hashing");
|
||||
}
|
||||
ValidPathInfo newInfo0 {
|
||||
worker.store,
|
||||
outputPathName(drv->name, outputName),
|
||||
*std::move(optCA),
|
||||
ContentAddressWithReferences::fromParts(
|
||||
outputHash.method,
|
||||
std::move(got),
|
||||
rewriteRefs()),
|
||||
Hash::dummy,
|
||||
};
|
||||
if (*scratchPath != newInfo0.path) {
|
||||
|
@ -2505,9 +2486,14 @@ SingleDrvOutputs LocalDerivationGoal::registerOutputs()
|
|||
std::string(newInfo0.path.hashPart())}});
|
||||
}
|
||||
|
||||
HashResult narHashAndSize = hashPath(htSHA256, actualPath);
|
||||
newInfo0.narHash = narHashAndSize.first;
|
||||
newInfo0.narSize = narHashAndSize.second;
|
||||
{
|
||||
PosixSourceAccessor accessor;
|
||||
HashResult narHashAndSize = hashPath(
|
||||
accessor, CanonPath { actualPath },
|
||||
FileIngestionMethod::Recursive, HashAlgorithm::SHA256);
|
||||
newInfo0.narHash = narHashAndSize.first;
|
||||
newInfo0.narSize = narHashAndSize.second;
|
||||
}
|
||||
|
||||
assert(newInfo0.ca);
|
||||
return newInfo0;
|
||||
|
@ -2525,7 +2511,10 @@ SingleDrvOutputs LocalDerivationGoal::registerOutputs()
|
|||
std::string { scratchPath->hashPart() },
|
||||
std::string { requiredFinalPath.hashPart() });
|
||||
rewriteOutput(outputRewrites);
|
||||
auto narHashAndSize = hashPath(htSHA256, actualPath);
|
||||
PosixSourceAccessor accessor;
|
||||
HashResult narHashAndSize = hashPath(
|
||||
accessor, CanonPath { actualPath },
|
||||
FileIngestionMethod::Recursive, HashAlgorithm::SHA256);
|
||||
ValidPathInfo newInfo0 { requiredFinalPath, narHashAndSize.first };
|
||||
newInfo0.narSize = narHashAndSize.second;
|
||||
auto refs = rewriteRefs();
|
||||
|
@ -2536,16 +2525,16 @@ SingleDrvOutputs LocalDerivationGoal::registerOutputs()
|
|||
},
|
||||
|
||||
[&](const DerivationOutput::CAFixed & dof) {
|
||||
auto wanted = dof.ca.getHash();
|
||||
auto & wanted = dof.ca.hash;
|
||||
|
||||
auto newInfo0 = newInfoFromCA(DerivationOutput::CAFloating {
|
||||
.method = dof.ca.getMethod(),
|
||||
.hashType = wanted.type,
|
||||
.method = dof.ca.method,
|
||||
.hashAlgo = wanted.algo,
|
||||
});
|
||||
|
||||
/* Check wanted hash */
|
||||
assert(newInfo0.ca);
|
||||
auto got = newInfo0.ca->getHash();
|
||||
auto & got = newInfo0.ca->hash;
|
||||
if (wanted != got) {
|
||||
/* Throw an error after registering the path as
|
||||
valid. */
|
||||
|
@ -2553,8 +2542,8 @@ SingleDrvOutputs LocalDerivationGoal::registerOutputs()
|
|||
delayedException = std::make_exception_ptr(
|
||||
BuildError("hash mismatch in fixed-output derivation '%s':\n specified: %s\n got: %s",
|
||||
worker.store.printStorePath(drvPath),
|
||||
wanted.to_string(SRI, true),
|
||||
got.to_string(SRI, true)));
|
||||
wanted.to_string(HashFormat::SRI, true),
|
||||
got.to_string(HashFormat::SRI, true)));
|
||||
}
|
||||
if (!newInfo0.references.empty())
|
||||
delayedException = std::make_exception_ptr(
|
||||
|
@ -2577,11 +2566,11 @@ SingleDrvOutputs LocalDerivationGoal::registerOutputs()
|
|||
[&](const DerivationOutput::Impure & doi) {
|
||||
return newInfoFromCA(DerivationOutput::CAFloating {
|
||||
.method = doi.method,
|
||||
.hashType = doi.hashType,
|
||||
.hashAlgo = doi.hashAlgo,
|
||||
});
|
||||
},
|
||||
|
||||
}, output->raw());
|
||||
}, output->raw);
|
||||
|
||||
/* FIXME: set proper permissions in restorePath() so
|
||||
we don't have to do another traversal. */
|
||||
|
@ -2935,11 +2924,11 @@ bool LocalDerivationGoal::isReadDesc(int fd)
|
|||
}
|
||||
|
||||
|
||||
StorePath LocalDerivationGoal::makeFallbackPath(std::string_view outputName)
|
||||
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));
|
||||
}
|
||||
|
||||
|
||||
|
@ -2947,7 +2936,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());
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include "derivation-goal.hh"
|
||||
#include "local-store.hh"
|
||||
#include "processes.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
@ -86,8 +87,8 @@ struct LocalDerivationGoal : public DerivationGoal
|
|||
: source(source), optional(optional)
|
||||
{ }
|
||||
};
|
||||
typedef map<Path, ChrootPath> DirsInChroot; // maps target path to source path
|
||||
DirsInChroot dirsInChroot;
|
||||
typedef map<Path, ChrootPath> PathsInChroot; // maps target path to source path
|
||||
PathsInChroot pathsInChroot;
|
||||
|
||||
typedef map<std::string, std::string> Environment;
|
||||
Environment env;
|
||||
|
@ -120,14 +121,6 @@ struct LocalDerivationGoal : public DerivationGoal
|
|||
*/
|
||||
OutputPathMap scratchOutputs;
|
||||
|
||||
/**
|
||||
* Path registration info from the previous round, if we're
|
||||
* building multiple times. Since this contains the hash, it
|
||||
* allows us to compare whether two rounds produced the same
|
||||
* result.
|
||||
*/
|
||||
std::map<Path, ValidPathInfo> prevInfos;
|
||||
|
||||
uid_t sandboxUid() { return usingUserNamespace ? (!buildUser || buildUser->getUIDCount() == 1 ? 1000 : 0) : buildUser->getUID(); }
|
||||
gid_t sandboxGid() { return usingUserNamespace ? (!buildUser || buildUser->getUIDCount() == 1 ? 100 : 0) : buildUser->getGID(); }
|
||||
|
||||
|
@ -272,8 +265,10 @@ struct LocalDerivationGoal : public DerivationGoal
|
|||
|
||||
/**
|
||||
* Forcibly kill the child process, if any.
|
||||
*
|
||||
* Called by destructor, can't be overridden
|
||||
*/
|
||||
void killChild() override;
|
||||
void killChild() override final;
|
||||
|
||||
/**
|
||||
* Kill any processes running under the build user UID or in the
|
||||
|
@ -295,7 +290,7 @@ struct LocalDerivationGoal : public DerivationGoal
|
|||
* @todo Add option to randomize, so we can audit whether our
|
||||
* rewrites caught everything
|
||||
*/
|
||||
StorePath makeFallbackPath(std::string_view outputName);
|
||||
StorePath makeFallbackPath(OutputNameView outputName);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -68,6 +68,7 @@ R""(
|
|||
(allow file*
|
||||
(literal "/dev/null")
|
||||
(literal "/dev/random")
|
||||
(literal "/dev/stderr")
|
||||
(literal "/dev/stdin")
|
||||
(literal "/dev/stdout")
|
||||
(literal "/dev/tty")
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include "substitution-goal.hh"
|
||||
#include "nar-info.hh"
|
||||
#include "finally.hh"
|
||||
#include "signals.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
@ -217,6 +218,8 @@ void PathSubstitutionGoal::tryToRun()
|
|||
|
||||
thr = std::thread([this]() {
|
||||
try {
|
||||
ReceiveInterrupts receiveInterrupts;
|
||||
|
||||
/* Wake up the worker loop when we're done. */
|
||||
Finally updateStats([this]() { outPipe.writeSide.close(); });
|
||||
|
||||
|
|
|
@ -114,9 +114,12 @@ public:
|
|||
void handleChildOutput(int fd, std::string_view data) override;
|
||||
void handleEOF(int fd) override;
|
||||
|
||||
void cleanup() override;
|
||||
/* Called by destructor, can't be overridden */
|
||||
void cleanup() override final;
|
||||
|
||||
JobCategory jobCategory() override { return JobCategory::Substitution; };
|
||||
JobCategory jobCategory() const override {
|
||||
return JobCategory::Substitution;
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
@ -111,7 +112,10 @@ GoalPtr Worker::makeGoal(const DerivedPath & req, BuildMode buildMode)
|
|||
{
|
||||
return std::visit(overloaded {
|
||||
[&](const DerivedPath::Built & bfd) -> GoalPtr {
|
||||
return makeDerivationGoal(bfd.drvPath, bfd.outputs, buildMode);
|
||||
if (auto bop = std::get_if<DerivedPath::Opaque>(&*bfd.drvPath))
|
||||
return makeDerivationGoal(bop->path, bfd.outputs, buildMode);
|
||||
else
|
||||
throw UnimplementedError("Building dynamic derivations in one shot is not yet implemented.");
|
||||
},
|
||||
[&](const DerivedPath::Opaque & bo) -> GoalPtr {
|
||||
return makePathSubstitutionGoal(bo.path, buildMode == bmRepair ? Repair : NoRepair);
|
||||
|
@ -195,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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -208,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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -265,7 +282,10 @@ void Worker::run(const Goals & _topGoals)
|
|||
for (auto & i : _topGoals) {
|
||||
topGoals.insert(i);
|
||||
if (auto goal = dynamic_cast<DerivationGoal *>(i.get())) {
|
||||
topPaths.push_back(DerivedPath::Built{goal->drvPath, goal->wantedOutputs});
|
||||
topPaths.push_back(DerivedPath::Built {
|
||||
.drvPath = makeConstantStorePathRef(goal->drvPath),
|
||||
.outputs = goal->wantedOutputs,
|
||||
});
|
||||
} else if (auto goal = dynamic_cast<PathSubstitutionGoal *>(i.get())) {
|
||||
topPaths.push_back(DerivedPath::Opaque{goal->storePath});
|
||||
}
|
||||
|
@ -468,16 +488,9 @@ void Worker::waitForInput()
|
|||
}
|
||||
|
||||
|
||||
unsigned int Worker::exitStatus()
|
||||
unsigned int Worker::failingExitStatus()
|
||||
{
|
||||
/*
|
||||
* 1100100
|
||||
* ^^^^
|
||||
* |||`- timeout
|
||||
* ||`-- output hash mismatch
|
||||
* |`--- build failure
|
||||
* `---- not deterministic
|
||||
*/
|
||||
// See API docs in header for explanation
|
||||
unsigned int mask = 0;
|
||||
bool buildFailure = permanentFailure || timedOut || hashMismatch;
|
||||
if (buildFailure)
|
||||
|
@ -506,8 +519,10 @@ 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(
|
||||
*store.getFSAccessor(), CanonPath { store.printStorePath(path) },
|
||||
FileIngestionMethod::Recursive, info->narHash.algo);
|
||||
Hash nullHash(HashAlgorithm::SHA256);
|
||||
res = info->narHash == nullHash || info->narHash == current.first;
|
||||
}
|
||||
pathContentsGoodCache.insert_or_assign(path, res);
|
||||
|
@ -523,10 +538,13 @@ void Worker::markContentsGood(const StorePath & path)
|
|||
}
|
||||
|
||||
|
||||
GoalPtr upcast_goal(std::shared_ptr<PathSubstitutionGoal> subGoal) {
|
||||
GoalPtr upcast_goal(std::shared_ptr<PathSubstitutionGoal> subGoal)
|
||||
{
|
||||
return subGoal;
|
||||
}
|
||||
GoalPtr upcast_goal(std::shared_ptr<DrvOutputSubstitutionGoal> subGoal) {
|
||||
|
||||
GoalPtr upcast_goal(std::shared_ptr<DrvOutputSubstitutionGoal> subGoal)
|
||||
{
|
||||
return subGoal;
|
||||
}
|
||||
|
||||
|
|
|
@ -34,7 +34,6 @@ GoalPtr upcast_goal(std::shared_ptr<DrvOutputSubstitutionGoal> subGoal);
|
|||
|
||||
typedef std::chrono::time_point<std::chrono::steady_clock> steady_time_point;
|
||||
|
||||
|
||||
/**
|
||||
* A mapping used to remember for each child process to what goal it
|
||||
* belongs, and file descriptors for receiving log data and output
|
||||
|
@ -280,7 +279,28 @@ public:
|
|||
*/
|
||||
void waitForInput();
|
||||
|
||||
unsigned int exitStatus();
|
||||
/***
|
||||
* The exit status in case of failure.
|
||||
*
|
||||
* In the case of a build failure, returned value follows this
|
||||
* bitmask:
|
||||
*
|
||||
* ```
|
||||
* 0b1100100
|
||||
* ^^^^
|
||||
* |||`- timeout
|
||||
* ||`-- output hash mismatch
|
||||
* |`--- build failure
|
||||
* `---- not deterministic
|
||||
* ```
|
||||
*
|
||||
* In other words, the failure code is at least 100 (0b1100100), but
|
||||
* might also be greater.
|
||||
*
|
||||
* Otherwise (no build failure, but some other sort of failure by
|
||||
* assumption), this returned value is 1.
|
||||
*/
|
||||
unsigned int failingExitStatus();
|
||||
|
||||
/**
|
||||
* Check whether the given valid path exists and has the right
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include "buildenv.hh"
|
||||
#include "derivations.hh"
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
@ -174,15 +175,19 @@ void builtinBuildenv(const BasicDerivation & drv)
|
|||
/* Convert the stuff we get from the environment back into a
|
||||
* coherent data type. */
|
||||
Packages pkgs;
|
||||
auto derivations = tokenizeString<Strings>(getAttr("derivations"));
|
||||
while (!derivations.empty()) {
|
||||
/* !!! We're trusting the caller to structure derivations env var correctly */
|
||||
auto active = derivations.front(); derivations.pop_front();
|
||||
auto priority = stoi(derivations.front()); derivations.pop_front();
|
||||
auto outputs = stoi(derivations.front()); derivations.pop_front();
|
||||
for (auto n = 0; n < outputs; n++) {
|
||||
auto path = derivations.front(); derivations.pop_front();
|
||||
pkgs.emplace_back(path, active != "false", priority);
|
||||
{
|
||||
auto derivations = tokenizeString<Strings>(getAttr("derivations"));
|
||||
|
||||
auto itemIt = derivations.begin();
|
||||
while (itemIt != derivations.end()) {
|
||||
/* !!! We're trusting the caller to structure derivations env var correctly */
|
||||
const bool active = "false" != *itemIt++;
|
||||
const int priority = stoi(*itemIt++);
|
||||
const size_t outputs = stoul(*itemIt++);
|
||||
|
||||
for (size_t n {0}; n < outputs; n++) {
|
||||
pkgs.emplace_back(std::move(*itemIt++), active, priority);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#pragma once
|
||||
///@file
|
||||
|
||||
#include "derivations.hh"
|
||||
#include "store-api.hh"
|
||||
|
||||
namespace nix {
|
||||
|
|
|
@ -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(Base16, false));
|
||||
fetch(hashedMirror + printHashAlgo(h.algo) + "/" + h.to_string(HashFormat::Base16, false));
|
||||
return;
|
||||
} catch (Error & e) {
|
||||
debug(e.what());
|
||||
|
|
41
src/libstore/common-protocol-impl.hh
Normal file
41
src/libstore/common-protocol-impl.hh
Normal file
|
@ -0,0 +1,41 @@
|
|||
#pragma once
|
||||
/**
|
||||
* @file
|
||||
*
|
||||
* Template implementations (as opposed to mere declarations).
|
||||
*
|
||||
* This file is an exmample of the "impl.hh" pattern. See the
|
||||
* contributing guide.
|
||||
*/
|
||||
|
||||
#include "common-protocol.hh"
|
||||
#include "length-prefixed-protocol-helper.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
/* protocol-agnostic templates */
|
||||
|
||||
#define COMMON_USE_LENGTH_PREFIX_SERIALISER(TEMPLATE, T) \
|
||||
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 StoreDirConfig & store, CommonProto::WriteConn conn, const T & t) \
|
||||
{ \
|
||||
LengthPrefixedProtoHelper<CommonProto, T >::write(store, conn, t); \
|
||||
}
|
||||
|
||||
COMMON_USE_LENGTH_PREFIX_SERIALISER(template<typename T>, std::vector<T>)
|
||||
COMMON_USE_LENGTH_PREFIX_SERIALISER(template<typename T>, std::set<T>)
|
||||
COMMON_USE_LENGTH_PREFIX_SERIALISER(template<typename... Ts>, std::tuple<Ts...>)
|
||||
|
||||
#define COMMA_ ,
|
||||
COMMON_USE_LENGTH_PREFIX_SERIALISER(
|
||||
template<typename K COMMA_ typename V>,
|
||||
std::map<K COMMA_ V>)
|
||||
#undef COMMA_
|
||||
|
||||
|
||||
/* protocol-specific templates */
|
||||
|
||||
}
|
97
src/libstore/common-protocol.cc
Normal file
97
src/libstore/common-protocol.cc
Normal file
|
@ -0,0 +1,97 @@
|
|||
#include "serialise.hh"
|
||||
#include "path-with-outputs.hh"
|
||||
#include "store-api.hh"
|
||||
#include "build-result.hh"
|
||||
#include "common-protocol.hh"
|
||||
#include "common-protocol-impl.hh"
|
||||
#include "archive.hh"
|
||||
#include "derivations.hh"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace nix {
|
||||
|
||||
/* protocol-agnostic definitions */
|
||||
|
||||
std::string CommonProto::Serialise<std::string>::read(const StoreDirConfig & store, CommonProto::ReadConn conn)
|
||||
{
|
||||
return readString(conn.from);
|
||||
}
|
||||
|
||||
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 StoreDirConfig & store, CommonProto::ReadConn conn)
|
||||
{
|
||||
return store.parseStorePath(readString(conn.from));
|
||||
}
|
||||
|
||||
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 StoreDirConfig & store, CommonProto::ReadConn conn)
|
||||
{
|
||||
return ContentAddress::parse(readString(conn.from));
|
||||
}
|
||||
|
||||
void CommonProto::Serialise<ContentAddress>::write(const StoreDirConfig & store, CommonProto::WriteConn conn, const ContentAddress & ca)
|
||||
{
|
||||
conn.to << renderContentAddress(ca);
|
||||
}
|
||||
|
||||
|
||||
Realisation CommonProto::Serialise<Realisation>::read(const StoreDirConfig & store, CommonProto::ReadConn conn)
|
||||
{
|
||||
std::string rawInput = readString(conn.from);
|
||||
return Realisation::fromJSON(
|
||||
nlohmann::json::parse(rawInput),
|
||||
"remote-protocol"
|
||||
);
|
||||
}
|
||||
|
||||
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 StoreDirConfig & store, CommonProto::ReadConn conn)
|
||||
{
|
||||
return DrvOutput::parse(readString(conn.from));
|
||||
}
|
||||
|
||||
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 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 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 StoreDirConfig & store, CommonProto::ReadConn conn)
|
||||
{
|
||||
return ContentAddress::parseOpt(readString(conn.from));
|
||||
}
|
||||
|
||||
void CommonProto::Serialise<std::optional<ContentAddress>>::write(const StoreDirConfig & store, CommonProto::WriteConn conn, const std::optional<ContentAddress> & caOpt)
|
||||
{
|
||||
conn.to << (caOpt ? renderContentAddress(*caOpt) : "");
|
||||
}
|
||||
|
||||
}
|
106
src/libstore/common-protocol.hh
Normal file
106
src/libstore/common-protocol.hh
Normal file
|
@ -0,0 +1,106 @@
|
|||
#pragma once
|
||||
///@file
|
||||
|
||||
#include "serialise.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
struct StoreDirConfig;
|
||||
struct Source;
|
||||
|
||||
// items being serialized
|
||||
class StorePath;
|
||||
struct ContentAddress;
|
||||
struct DrvOutput;
|
||||
struct Realisation;
|
||||
|
||||
|
||||
/**
|
||||
* Shared serializers between the worker protocol, serve protocol, and a
|
||||
* few others.
|
||||
*
|
||||
* This `struct` is basically just a `namespace`; We use a type rather
|
||||
* than a namespace just so we can use it as a template argument.
|
||||
*/
|
||||
struct CommonProto
|
||||
{
|
||||
/**
|
||||
* A unidirectional read connection, to be used by the read half of the
|
||||
* canonical serializers below.
|
||||
*/
|
||||
struct ReadConn {
|
||||
Source & from;
|
||||
};
|
||||
|
||||
/**
|
||||
* A unidirectional write connection, to be used by the write half of the
|
||||
* canonical serializers below.
|
||||
*/
|
||||
struct WriteConn {
|
||||
Sink & to;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct Serialise;
|
||||
|
||||
/**
|
||||
* Wrapper function around `CommonProto::Serialise<T>::write` that allows us to
|
||||
* infer the type instead of having to write it down explicitly.
|
||||
*/
|
||||
template<typename T>
|
||||
static void write(const StoreDirConfig & store, WriteConn conn, const T & t)
|
||||
{
|
||||
CommonProto::Serialise<T>::write(store, conn, t);
|
||||
}
|
||||
};
|
||||
|
||||
#define DECLARE_COMMON_SERIALISER(T) \
|
||||
struct CommonProto::Serialise< T > \
|
||||
{ \
|
||||
static T read(const StoreDirConfig & store, CommonProto::ReadConn conn); \
|
||||
static void write(const StoreDirConfig & store, CommonProto::WriteConn conn, const T & str); \
|
||||
}
|
||||
|
||||
template<>
|
||||
DECLARE_COMMON_SERIALISER(std::string);
|
||||
template<>
|
||||
DECLARE_COMMON_SERIALISER(StorePath);
|
||||
template<>
|
||||
DECLARE_COMMON_SERIALISER(ContentAddress);
|
||||
template<>
|
||||
DECLARE_COMMON_SERIALISER(DrvOutput);
|
||||
template<>
|
||||
DECLARE_COMMON_SERIALISER(Realisation);
|
||||
|
||||
template<typename T>
|
||||
DECLARE_COMMON_SERIALISER(std::vector<T>);
|
||||
template<typename T>
|
||||
DECLARE_COMMON_SERIALISER(std::set<T>);
|
||||
template<typename... Ts>
|
||||
DECLARE_COMMON_SERIALISER(std::tuple<Ts...>);
|
||||
|
||||
#define COMMA_ ,
|
||||
template<typename K, typename V>
|
||||
DECLARE_COMMON_SERIALISER(std::map<K COMMA_ V>);
|
||||
#undef COMMA_
|
||||
|
||||
/**
|
||||
* These use the empty string for the null case, relying on the fact
|
||||
* that the underlying types never serialize to the empty string.
|
||||
*
|
||||
* We do this instead of a generic std::optional<T> instance because
|
||||
* ordinal tags (0 or 1, here) are a bit of a compatability hazard. For
|
||||
* the same reason, we don't have a std::variant<T..> instances (ordinal
|
||||
* tags 0...n).
|
||||
*
|
||||
* We could the generic instances and then these as specializations for
|
||||
* compatability, but that's proven a bit finnicky, and also makes the
|
||||
* worker protocol harder to implement in other languages where such
|
||||
* specializations may not be allowed.
|
||||
*/
|
||||
template<>
|
||||
DECLARE_COMMON_SERIALISER(std::optional<StorePath>);
|
||||
template<>
|
||||
DECLARE_COMMON_SERIALISER(std::optional<ContentAddress>);
|
||||
|
||||
}
|
|
@ -4,11 +4,6 @@
|
|||
|
||||
namespace nix {
|
||||
|
||||
std::string FixedOutputHash::printMethodAlgo() const
|
||||
{
|
||||
return makeFileIngestionPrefix(method) + printHashType(hash.type);
|
||||
}
|
||||
|
||||
std::string makeFileIngestionPrefix(FileIngestionMethod m)
|
||||
{
|
||||
switch (m) {
|
||||
|
@ -34,45 +29,57 @@ std::string ContentAddressMethod::renderPrefix() const
|
|||
|
||||
ContentAddressMethod ContentAddressMethod::parsePrefix(std::string_view & m)
|
||||
{
|
||||
ContentAddressMethod method = FileIngestionMethod::Flat;
|
||||
if (splitPrefix(m, "r:"))
|
||||
method = FileIngestionMethod::Recursive;
|
||||
else if (splitPrefix(m, "text:"))
|
||||
method = TextIngestionMethod {};
|
||||
return method;
|
||||
if (splitPrefix(m, "r:")) {
|
||||
return FileIngestionMethod::Recursive;
|
||||
}
|
||||
else if (splitPrefix(m, "text:")) {
|
||||
return TextIngestionMethod {};
|
||||
}
|
||||
return FileIngestionMethod::Flat;
|
||||
}
|
||||
|
||||
std::string ContentAddressMethod::render(HashAlgorithm ha) const
|
||||
{
|
||||
return std::visit(overloaded {
|
||||
[&](const TextIngestionMethod & th) {
|
||||
return std::string{"text:"} + printHashAlgo(ha);
|
||||
},
|
||||
[&](const FileIngestionMethod & fim) {
|
||||
return "fixed:" + makeFileIngestionPrefix(fim) + printHashAlgo(ha);
|
||||
}
|
||||
}, raw);
|
||||
}
|
||||
|
||||
FileIngestionMethod ContentAddressMethod::getFileIngestionMethod() const
|
||||
{
|
||||
return std::visit(overloaded {
|
||||
[&](const TextIngestionMethod & th) {
|
||||
return FileIngestionMethod::Flat;
|
||||
},
|
||||
[&](const FileIngestionMethod & fim) {
|
||||
return fim;
|
||||
}
|
||||
}, raw);
|
||||
}
|
||||
|
||||
std::string ContentAddress::render() const
|
||||
{
|
||||
return std::visit(overloaded {
|
||||
[](const TextHash & th) {
|
||||
return "text:"
|
||||
+ th.hash.to_string(Base32, true);
|
||||
[](const TextIngestionMethod &) -> std::string {
|
||||
return "text:";
|
||||
},
|
||||
[](const FixedOutputHash & fsh) {
|
||||
[](const FileIngestionMethod & method) {
|
||||
return "fixed:"
|
||||
+ makeFileIngestionPrefix(fsh.method)
|
||||
+ fsh.hash.to_string(Base32, true);
|
||||
}
|
||||
}, raw);
|
||||
}
|
||||
|
||||
std::string ContentAddressMethod::render(HashType ht) const
|
||||
{
|
||||
return std::visit(overloaded {
|
||||
[&](const TextIngestionMethod & th) {
|
||||
return std::string{"text:"} + printHashType(ht);
|
||||
+ makeFileIngestionPrefix(method);
|
||||
},
|
||||
[&](const FileIngestionMethod & fim) {
|
||||
return "fixed:" + makeFileIngestionPrefix(fim) + printHashType(ht);
|
||||
}
|
||||
}, raw);
|
||||
}, method.raw)
|
||||
+ 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 };
|
||||
|
||||
|
@ -84,31 +91,31 @@ static std::pair<ContentAddressMethod, HashType> parseContentAddressMethodPrefix
|
|||
prefix = *optPrefix;
|
||||
}
|
||||
|
||||
auto parseHashType_ = [&](){
|
||||
auto parseHashAlgorithm_ = [&](){
|
||||
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 std::move(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 = parseHashAlgorithm_();
|
||||
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 = parseHashAlgorithm_();
|
||||
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);
|
||||
|
@ -118,25 +125,15 @@ ContentAddress ContentAddress::parse(std::string_view rawCa)
|
|||
{
|
||||
auto rest = rawCa;
|
||||
|
||||
auto [caMethod, hashType_] = parseContentAddressMethodPrefix(rest);
|
||||
auto hashType = hashType_; // work around clang bug
|
||||
auto [caMethod, hashAlgo] = parseContentAddressMethodPrefix(rest);
|
||||
|
||||
return std::visit(overloaded {
|
||||
[&](TextIngestionMethod &) {
|
||||
return ContentAddress(TextHash {
|
||||
.hash = Hash::parseNonSRIUnprefixed(rest, hashType)
|
||||
});
|
||||
},
|
||||
[&](FileIngestionMethod & fim) {
|
||||
return ContentAddress(FixedOutputHash {
|
||||
.method = fim,
|
||||
.hash = Hash::parseNonSRIUnprefixed(rest, hashType),
|
||||
});
|
||||
},
|
||||
}, caMethod.raw);
|
||||
return ContentAddress {
|
||||
.method = std::move(caMethod),
|
||||
.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
|
||||
|
@ -156,52 +153,10 @@ std::string renderContentAddress(std::optional<ContentAddress> ca)
|
|||
return ca ? ca->render() : "";
|
||||
}
|
||||
|
||||
ContentAddress ContentAddress::fromParts(
|
||||
ContentAddressMethod method, Hash hash) noexcept
|
||||
{
|
||||
return std::visit(overloaded {
|
||||
[&](TextIngestionMethod _) -> ContentAddress {
|
||||
return TextHash {
|
||||
.hash = std::move(hash),
|
||||
};
|
||||
},
|
||||
[&](FileIngestionMethod m2) -> ContentAddress {
|
||||
return FixedOutputHash {
|
||||
.method = std::move(m2),
|
||||
.hash = std::move(hash),
|
||||
};
|
||||
},
|
||||
}, method.raw);
|
||||
}
|
||||
|
||||
ContentAddressMethod ContentAddress::getMethod() const
|
||||
{
|
||||
return std::visit(overloaded {
|
||||
[](const TextHash & th) -> ContentAddressMethod {
|
||||
return TextIngestionMethod {};
|
||||
},
|
||||
[](const FixedOutputHash & fsh) -> ContentAddressMethod {
|
||||
return fsh.method;
|
||||
},
|
||||
}, raw);
|
||||
}
|
||||
|
||||
const Hash & ContentAddress::getHash() const
|
||||
{
|
||||
return std::visit(overloaded {
|
||||
[](const TextHash & th) -> auto & {
|
||||
return th.hash;
|
||||
},
|
||||
[](const FixedOutputHash & fsh) -> auto & {
|
||||
return fsh.hash;
|
||||
},
|
||||
}, raw);
|
||||
}
|
||||
|
||||
std::string ContentAddress::printMethodAlgo() const
|
||||
{
|
||||
return getMethod().renderPrefix()
|
||||
+ printHashType(getHash().type);
|
||||
return method.renderPrefix()
|
||||
+ printHashAlgo(hash.algo);
|
||||
}
|
||||
|
||||
bool StoreReferences::empty() const
|
||||
|
@ -217,42 +172,41 @@ size_t StoreReferences::size() const
|
|||
ContentAddressWithReferences ContentAddressWithReferences::withoutRefs(const ContentAddress & ca) noexcept
|
||||
{
|
||||
return std::visit(overloaded {
|
||||
[&](const TextHash & h) -> ContentAddressWithReferences {
|
||||
[&](const TextIngestionMethod &) -> ContentAddressWithReferences {
|
||||
return TextInfo {
|
||||
.hash = h,
|
||||
.hash = ca.hash,
|
||||
.references = {},
|
||||
};
|
||||
},
|
||||
[&](const FixedOutputHash & h) -> ContentAddressWithReferences {
|
||||
[&](const FileIngestionMethod & method) -> ContentAddressWithReferences {
|
||||
return FixedOutputInfo {
|
||||
.hash = h,
|
||||
.method = method,
|
||||
.hash = ca.hash,
|
||||
.references = {},
|
||||
};
|
||||
},
|
||||
}, ca.raw);
|
||||
}, ca.method.raw);
|
||||
}
|
||||
|
||||
std::optional<ContentAddressWithReferences> ContentAddressWithReferences::fromPartsOpt(
|
||||
ContentAddressMethod method, Hash hash, StoreReferences refs) noexcept
|
||||
ContentAddressWithReferences ContentAddressWithReferences::fromParts(
|
||||
ContentAddressMethod method, Hash hash, StoreReferences refs)
|
||||
{
|
||||
return std::visit(overloaded {
|
||||
[&](TextIngestionMethod _) -> std::optional<ContentAddressWithReferences> {
|
||||
[&](TextIngestionMethod _) -> ContentAddressWithReferences {
|
||||
if (refs.self)
|
||||
return std::nullopt;
|
||||
throw Error("self-reference not allowed with text hashing");
|
||||
return ContentAddressWithReferences {
|
||||
TextInfo {
|
||||
.hash = { .hash = std::move(hash) },
|
||||
.hash = std::move(hash),
|
||||
.references = std::move(refs.others),
|
||||
}
|
||||
};
|
||||
},
|
||||
[&](FileIngestionMethod m2) -> std::optional<ContentAddressWithReferences> {
|
||||
[&](FileIngestionMethod m2) -> ContentAddressWithReferences {
|
||||
return ContentAddressWithReferences {
|
||||
FixedOutputInfo {
|
||||
.hash = {
|
||||
.method = m2,
|
||||
.hash = std::move(hash),
|
||||
},
|
||||
.method = m2,
|
||||
.hash = std::move(hash),
|
||||
.references = std::move(refs),
|
||||
}
|
||||
};
|
||||
|
@ -267,7 +221,7 @@ ContentAddressMethod ContentAddressWithReferences::getMethod() const
|
|||
return TextIngestionMethod {};
|
||||
},
|
||||
[](const FixedOutputInfo & fsh) -> ContentAddressMethod {
|
||||
return fsh.hash.method;
|
||||
return fsh.method;
|
||||
},
|
||||
}, raw);
|
||||
}
|
||||
|
@ -276,10 +230,10 @@ Hash ContentAddressWithReferences::getHash() const
|
|||
{
|
||||
return std::visit(overloaded {
|
||||
[](const TextInfo & th) {
|
||||
return th.hash.hash;
|
||||
return th.hash;
|
||||
},
|
||||
[](const FixedOutputInfo & fsh) {
|
||||
return fsh.hash.hash;
|
||||
return fsh.hash;
|
||||
},
|
||||
}, raw);
|
||||
}
|
||||
|
|
|
@ -4,7 +4,9 @@
|
|||
#include <variant>
|
||||
#include "hash.hh"
|
||||
#include "path.hh"
|
||||
#include "file-content-address.hh"
|
||||
#include "comparator.hh"
|
||||
#include "variant-wrapper.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
@ -30,22 +32,6 @@ namespace nix {
|
|||
*/
|
||||
struct TextIngestionMethod : std::monostate { };
|
||||
|
||||
/**
|
||||
* An enumeration of the main ways we can serialize file system
|
||||
* objects.
|
||||
*/
|
||||
enum struct FileIngestionMethod : uint8_t {
|
||||
/**
|
||||
* Flat-file hashing. Directly ingest the contents of a single file
|
||||
*/
|
||||
Flat = false,
|
||||
/**
|
||||
* Recursive (or NAR) hashing. Serializes the file-system object in Nix
|
||||
* Archive format and ingest that
|
||||
*/
|
||||
Recursive = true
|
||||
};
|
||||
|
||||
/**
|
||||
* Compute the prefix to the hash algorithm which indicates how the
|
||||
* files were ingested.
|
||||
|
@ -53,7 +39,7 @@ enum struct FileIngestionMethod : uint8_t {
|
|||
std::string makeFileIngestionPrefix(FileIngestionMethod m);
|
||||
|
||||
/**
|
||||
* An enumeration of all the ways we can serialize file system objects.
|
||||
* An enumeration of all the ways we can content-address store objects.
|
||||
*
|
||||
* Just the type of a content address. Combine with the hash itself, and
|
||||
* we have a `ContentAddress` as defined below. Combine that, in turn,
|
||||
|
@ -71,11 +57,7 @@ struct ContentAddressMethod
|
|||
|
||||
GENERATE_CMP(ContentAddressMethod, me->raw);
|
||||
|
||||
/* The moral equivalent of `using Raw::Raw;` */
|
||||
ContentAddressMethod(auto &&... arg)
|
||||
: raw(std::forward<decltype(arg)>(arg)...)
|
||||
{ }
|
||||
|
||||
MAKE_WRAPPER_CONSTRUCTOR(ContentAddressMethod);
|
||||
|
||||
/**
|
||||
* Parse the prefix tag which indicates how the files
|
||||
|
@ -97,7 +79,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
|
||||
|
@ -105,7 +87,15 @@ struct ContentAddressMethod
|
|||
*
|
||||
* The rough inverse of `parse()`.
|
||||
*/
|
||||
std::string render(HashType ht) const;
|
||||
std::string render(HashAlgorithm ht) const;
|
||||
|
||||
/**
|
||||
* Get the underlying way to content-address file system objects.
|
||||
*
|
||||
* Different ways of hashing store objects may use the same method
|
||||
* for hashing file systeme objects.
|
||||
*/
|
||||
FileIngestionMethod getFileIngestionMethod() const;
|
||||
};
|
||||
|
||||
|
||||
|
@ -113,64 +103,31 @@ struct ContentAddressMethod
|
|||
* Mini content address
|
||||
*/
|
||||
|
||||
/**
|
||||
* Somewhat obscure, used by \ref Derivation derivations and
|
||||
* `builtins.toFile` currently.
|
||||
*/
|
||||
struct TextHash {
|
||||
/**
|
||||
* Hash of the contents of the text/file.
|
||||
*/
|
||||
Hash hash;
|
||||
|
||||
GENERATE_CMP(TextHash, me->hash);
|
||||
};
|
||||
|
||||
/**
|
||||
* Used by most store objects that are content-addressed.
|
||||
*/
|
||||
struct FixedOutputHash {
|
||||
/**
|
||||
* How the file system objects are serialized
|
||||
*/
|
||||
FileIngestionMethod method;
|
||||
/**
|
||||
* Hash of that serialization
|
||||
*/
|
||||
Hash hash;
|
||||
|
||||
std::string printMethodAlgo() const;
|
||||
|
||||
GENERATE_CMP(FixedOutputHash, me->method, me->hash);
|
||||
};
|
||||
|
||||
/**
|
||||
* We've accumulated several types of content-addressed paths over the
|
||||
* years; fixed-output derivations support multiple hash algorithms and
|
||||
* serialisation methods (flat file vs NAR). Thus, ‘ca’ has one of the
|
||||
* following forms:
|
||||
*
|
||||
* - ‘text:sha256:<sha256 hash of file contents>’: For paths
|
||||
* computed by Store::makeTextPath() / Store::addTextToStore().
|
||||
* - `TextIngestionMethod`:
|
||||
* ‘text:sha256:<sha256 hash of file contents>’
|
||||
*
|
||||
* - ‘fixed:<r?>:<ht>:<h>’: For paths computed by
|
||||
* Store::makeFixedOutputPath() / Store::addToStore().
|
||||
* - `FixedIngestionMethod`:
|
||||
* ‘fixed:<r?>:<hash type>:<hash of file contents>’
|
||||
*/
|
||||
struct ContentAddress
|
||||
{
|
||||
typedef std::variant<
|
||||
TextHash,
|
||||
FixedOutputHash
|
||||
> Raw;
|
||||
/**
|
||||
* How the file system objects are serialized
|
||||
*/
|
||||
ContentAddressMethod method;
|
||||
|
||||
Raw raw;
|
||||
/**
|
||||
* Hash of that serialization
|
||||
*/
|
||||
Hash hash;
|
||||
|
||||
GENERATE_CMP(ContentAddress, me->raw);
|
||||
|
||||
/* The moral equivalent of `using Raw::Raw;` */
|
||||
ContentAddress(auto &&... arg)
|
||||
: raw(std::forward<decltype(arg)>(arg)...)
|
||||
{ }
|
||||
GENERATE_CMP(ContentAddress, me->method, me->hash);
|
||||
|
||||
/**
|
||||
* Compute the content-addressability assertion
|
||||
|
@ -183,20 +140,6 @@ struct ContentAddress
|
|||
|
||||
static std::optional<ContentAddress> parseOpt(std::string_view rawCaOpt);
|
||||
|
||||
/**
|
||||
* Create a `ContentAddress` from 2 parts:
|
||||
*
|
||||
* @param method Way ingesting the file system data.
|
||||
*
|
||||
* @param hash Hash of ingested file system data.
|
||||
*/
|
||||
static ContentAddress fromParts(
|
||||
ContentAddressMethod method, Hash hash) noexcept;
|
||||
|
||||
ContentAddressMethod getMethod() const;
|
||||
|
||||
const Hash & getHash() const;
|
||||
|
||||
std::string printMethodAlgo() const;
|
||||
};
|
||||
|
||||
|
@ -219,7 +162,8 @@ std::string renderContentAddress(std::optional<ContentAddress> ca);
|
|||
* References to other store objects are tracked with store paths, self
|
||||
* references however are tracked with a boolean.
|
||||
*/
|
||||
struct StoreReferences {
|
||||
struct StoreReferences
|
||||
{
|
||||
/**
|
||||
* References to other store objects
|
||||
*/
|
||||
|
@ -246,8 +190,13 @@ struct StoreReferences {
|
|||
};
|
||||
|
||||
// This matches the additional info that we need for makeTextPath
|
||||
struct TextInfo {
|
||||
TextHash hash;
|
||||
struct TextInfo
|
||||
{
|
||||
/**
|
||||
* Hash of the contents of the text/file.
|
||||
*/
|
||||
Hash hash;
|
||||
|
||||
/**
|
||||
* References to other store objects only; self references
|
||||
* disallowed
|
||||
|
@ -257,8 +206,18 @@ struct TextInfo {
|
|||
GENERATE_CMP(TextInfo, me->hash, me->references);
|
||||
};
|
||||
|
||||
struct FixedOutputInfo {
|
||||
FixedOutputHash hash;
|
||||
struct FixedOutputInfo
|
||||
{
|
||||
/**
|
||||
* How the file system objects are serialized
|
||||
*/
|
||||
FileIngestionMethod method;
|
||||
|
||||
/**
|
||||
* Hash of that serialization
|
||||
*/
|
||||
Hash hash;
|
||||
|
||||
/**
|
||||
* References to other store objects or this one.
|
||||
*/
|
||||
|
@ -283,10 +242,7 @@ struct ContentAddressWithReferences
|
|||
|
||||
GENERATE_CMP(ContentAddressWithReferences, me->raw);
|
||||
|
||||
/* The moral equivalent of `using Raw::Raw;` */
|
||||
ContentAddressWithReferences(auto &&... arg)
|
||||
: raw(std::forward<decltype(arg)>(arg)...)
|
||||
{ }
|
||||
MAKE_WRAPPER_CONSTRUCTOR(ContentAddressWithReferences);
|
||||
|
||||
/**
|
||||
* Create a `ContentAddressWithReferences` from a mere
|
||||
|
@ -303,11 +259,12 @@ struct ContentAddressWithReferences
|
|||
*
|
||||
* @param refs References to other store objects or oneself.
|
||||
*
|
||||
* Do note that not all combinations are supported; `nullopt` is
|
||||
* returns for invalid combinations.
|
||||
* @note note that all combinations are supported. This is a
|
||||
* *partial function* and exceptions will be thrown for invalid
|
||||
* combinations.
|
||||
*/
|
||||
static std::optional<ContentAddressWithReferences> fromPartsOpt(
|
||||
ContentAddressMethod method, Hash hash, StoreReferences refs) noexcept;
|
||||
static ContentAddressWithReferences fromParts(
|
||||
ContentAddressMethod method, Hash hash, StoreReferences refs);
|
||||
|
||||
ContentAddressMethod getMethod() const;
|
||||
|
||||
|
|
|
@ -1,116 +0,0 @@
|
|||
#include "crypto.hh"
|
||||
#include "util.hh"
|
||||
#include "globals.hh"
|
||||
|
||||
#include <sodium.h>
|
||||
|
||||
namespace nix {
|
||||
|
||||
static std::pair<std::string_view, std::string_view> split(std::string_view s)
|
||||
{
|
||||
size_t colon = s.find(':');
|
||||
if (colon == std::string::npos || colon == 0)
|
||||
return {"", ""};
|
||||
return {s.substr(0, colon), s.substr(colon + 1)};
|
||||
}
|
||||
|
||||
Key::Key(std::string_view s)
|
||||
{
|
||||
auto ss = split(s);
|
||||
|
||||
name = ss.first;
|
||||
key = ss.second;
|
||||
|
||||
if (name == "" || key == "")
|
||||
throw Error("secret key is corrupt");
|
||||
|
||||
key = base64Decode(key);
|
||||
}
|
||||
|
||||
std::string Key::to_string() const
|
||||
{
|
||||
return name + ":" + base64Encode(key);
|
||||
}
|
||||
|
||||
SecretKey::SecretKey(std::string_view s)
|
||||
: Key(s)
|
||||
{
|
||||
if (key.size() != crypto_sign_SECRETKEYBYTES)
|
||||
throw Error("secret key is not valid");
|
||||
}
|
||||
|
||||
std::string SecretKey::signDetached(std::string_view data) const
|
||||
{
|
||||
unsigned char sig[crypto_sign_BYTES];
|
||||
unsigned long long sigLen;
|
||||
crypto_sign_detached(sig, &sigLen, (unsigned char *) data.data(), data.size(),
|
||||
(unsigned char *) key.data());
|
||||
return name + ":" + base64Encode(std::string((char *) sig, sigLen));
|
||||
}
|
||||
|
||||
PublicKey SecretKey::toPublicKey() const
|
||||
{
|
||||
unsigned char pk[crypto_sign_PUBLICKEYBYTES];
|
||||
crypto_sign_ed25519_sk_to_pk(pk, (unsigned char *) key.data());
|
||||
return PublicKey(name, std::string((char *) pk, crypto_sign_PUBLICKEYBYTES));
|
||||
}
|
||||
|
||||
SecretKey SecretKey::generate(std::string_view name)
|
||||
{
|
||||
unsigned char pk[crypto_sign_PUBLICKEYBYTES];
|
||||
unsigned char sk[crypto_sign_SECRETKEYBYTES];
|
||||
if (crypto_sign_keypair(pk, sk) != 0)
|
||||
throw Error("key generation failed");
|
||||
|
||||
return SecretKey(name, std::string((char *) sk, crypto_sign_SECRETKEYBYTES));
|
||||
}
|
||||
|
||||
PublicKey::PublicKey(std::string_view s)
|
||||
: Key(s)
|
||||
{
|
||||
if (key.size() != crypto_sign_PUBLICKEYBYTES)
|
||||
throw Error("public key is not valid");
|
||||
}
|
||||
|
||||
bool verifyDetached(const std::string & data, const std::string & sig,
|
||||
const PublicKeys & publicKeys)
|
||||
{
|
||||
auto ss = split(sig);
|
||||
|
||||
auto key = publicKeys.find(std::string(ss.first));
|
||||
if (key == publicKeys.end()) return false;
|
||||
|
||||
auto sig2 = base64Decode(ss.second);
|
||||
if (sig2.size() != crypto_sign_BYTES)
|
||||
throw Error("signature is not valid");
|
||||
|
||||
return crypto_sign_verify_detached((unsigned char *) sig2.data(),
|
||||
(unsigned char *) data.data(), data.size(),
|
||||
(unsigned char *) key->second.key.data()) == 0;
|
||||
}
|
||||
|
||||
PublicKeys getDefaultPublicKeys()
|
||||
{
|
||||
PublicKeys publicKeys;
|
||||
|
||||
// FIXME: filter duplicates
|
||||
|
||||
for (auto s : settings.trustedPublicKeys.get()) {
|
||||
PublicKey key(s);
|
||||
publicKeys.emplace(key.name, key);
|
||||
}
|
||||
|
||||
for (auto secretKeyFile : settings.secretKeyFiles.get()) {
|
||||
try {
|
||||
SecretKey secretKey(readFile(secretKeyFile));
|
||||
publicKeys.emplace(secretKey.name, secretKey.toPublicKey());
|
||||
} catch (SysError & e) {
|
||||
/* Ignore unreadable key files. That's normal in a
|
||||
multi-user installation. */
|
||||
}
|
||||
}
|
||||
|
||||
return publicKeys;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
#pragma once
|
||||
///@file
|
||||
|
||||
#include "types.hh"
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace nix {
|
||||
|
||||
struct Key
|
||||
{
|
||||
std::string name;
|
||||
std::string key;
|
||||
|
||||
/**
|
||||
* Construct Key from a string in the format
|
||||
* ‘<name>:<key-in-base64>’.
|
||||
*/
|
||||
Key(std::string_view s);
|
||||
|
||||
std::string to_string() const;
|
||||
|
||||
protected:
|
||||
Key(std::string_view name, std::string && key)
|
||||
: name(name), key(std::move(key)) { }
|
||||
};
|
||||
|
||||
struct PublicKey;
|
||||
|
||||
struct SecretKey : Key
|
||||
{
|
||||
SecretKey(std::string_view s);
|
||||
|
||||
/**
|
||||
* Return a detached signature of the given string.
|
||||
*/
|
||||
std::string signDetached(std::string_view s) const;
|
||||
|
||||
PublicKey toPublicKey() const;
|
||||
|
||||
static SecretKey generate(std::string_view name);
|
||||
|
||||
private:
|
||||
SecretKey(std::string_view name, std::string && key)
|
||||
: Key(name, std::move(key)) { }
|
||||
};
|
||||
|
||||
struct PublicKey : Key
|
||||
{
|
||||
PublicKey(std::string_view data);
|
||||
|
||||
private:
|
||||
PublicKey(std::string_view name, std::string && key)
|
||||
: Key(name, std::move(key)) { }
|
||||
friend struct SecretKey;
|
||||
};
|
||||
|
||||
typedef std::map<std::string, PublicKey> PublicKeys;
|
||||
|
||||
/**
|
||||
* @return true iff ‘sig’ is a correct signature over ‘data’ using one
|
||||
* of the given public keys.
|
||||
*/
|
||||
bool verifyDetached(const std::string & data, const std::string & sig,
|
||||
const PublicKeys & publicKeys);
|
||||
|
||||
PublicKeys getDefaultPublicKeys();
|
||||
|
||||
}
|
|
@ -1,11 +1,13 @@
|
|||
#include "daemon.hh"
|
||||
#include "monitor-fd.hh"
|
||||
#include "worker-protocol.hh"
|
||||
#include "worker-protocol-impl.hh"
|
||||
#include "build-result.hh"
|
||||
#include "store-api.hh"
|
||||
#include "store-cast.hh"
|
||||
#include "gc-store.hh"
|
||||
#include "log-store.hh"
|
||||
#include "indirect-root-store.hh"
|
||||
#include "path-with-outputs.hh"
|
||||
#include "finally.hh"
|
||||
#include "archive.hh"
|
||||
|
@ -43,9 +45,9 @@ struct TunnelLogger : public Logger
|
|||
|
||||
Sync<State> state_;
|
||||
|
||||
unsigned int clientVersion;
|
||||
WorkerProto::Version clientVersion;
|
||||
|
||||
TunnelLogger(FdSink & to, unsigned int clientVersion)
|
||||
TunnelLogger(FdSink & to, WorkerProto::Version clientVersion)
|
||||
: to(to), clientVersion(clientVersion) { }
|
||||
|
||||
void enqueueMsg(const std::string & s)
|
||||
|
@ -259,25 +261,22 @@ struct ClientSettings
|
|||
}
|
||||
};
|
||||
|
||||
static std::vector<DerivedPath> readDerivedPaths(Store & store, unsigned int clientVersion, Source & from)
|
||||
{
|
||||
std::vector<DerivedPath> reqs;
|
||||
if (GET_PROTOCOL_MINOR(clientVersion) >= 30) {
|
||||
reqs = WorkerProto<std::vector<DerivedPath>>::read(store, from);
|
||||
} else {
|
||||
for (auto & s : readStrings<Strings>(from))
|
||||
reqs.push_back(parsePathWithOutputs(store, s).toDerivedPath());
|
||||
}
|
||||
return reqs;
|
||||
}
|
||||
|
||||
static void performOp(TunnelLogger * logger, ref<Store> store,
|
||||
TrustedFlag trusted, RecursiveFlag recursive, unsigned int clientVersion,
|
||||
Source & from, BufferedSink & to, unsigned int op)
|
||||
TrustedFlag trusted, RecursiveFlag recursive, WorkerProto::Version clientVersion,
|
||||
Source & from, BufferedSink & to, WorkerProto::Op op)
|
||||
{
|
||||
WorkerProto::ReadConn rconn {
|
||||
.from = from,
|
||||
.version = clientVersion,
|
||||
};
|
||||
WorkerProto::WriteConn wconn {
|
||||
.to = to,
|
||||
.version = clientVersion,
|
||||
};
|
||||
|
||||
switch (op) {
|
||||
|
||||
case wopIsValidPath: {
|
||||
case WorkerProto::Op::IsValidPath: {
|
||||
auto path = store->parseStorePath(readString(from));
|
||||
logger->startWork();
|
||||
bool result = store->isValidPath(path);
|
||||
|
@ -286,8 +285,8 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
break;
|
||||
}
|
||||
|
||||
case wopQueryValidPaths: {
|
||||
auto paths = WorkerProto<StorePathSet>::read(*store, from);
|
||||
case WorkerProto::Op::QueryValidPaths: {
|
||||
auto paths = WorkerProto::Serialise<StorePathSet>::read(*store, rconn);
|
||||
|
||||
SubstituteFlag substitute = NoSubstitute;
|
||||
if (GET_PROTOCOL_MINOR(clientVersion) >= 27) {
|
||||
|
@ -300,11 +299,11 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
}
|
||||
auto res = store->queryValidPaths(paths, substitute);
|
||||
logger->stopWork();
|
||||
workerProtoWrite(*store, to, res);
|
||||
WorkerProto::write(*store, wconn, res);
|
||||
break;
|
||||
}
|
||||
|
||||
case wopHasSubstitutes: {
|
||||
case WorkerProto::Op::HasSubstitutes: {
|
||||
auto path = store->parseStorePath(readString(from));
|
||||
logger->startWork();
|
||||
StorePathSet paths; // FIXME
|
||||
|
@ -315,45 +314,45 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
break;
|
||||
}
|
||||
|
||||
case wopQuerySubstitutablePaths: {
|
||||
auto paths = WorkerProto<StorePathSet>::read(*store, from);
|
||||
case WorkerProto::Op::QuerySubstitutablePaths: {
|
||||
auto paths = WorkerProto::Serialise<StorePathSet>::read(*store, rconn);
|
||||
logger->startWork();
|
||||
auto res = store->querySubstitutablePaths(paths);
|
||||
logger->stopWork();
|
||||
workerProtoWrite(*store, to, res);
|
||||
WorkerProto::write(*store, wconn, res);
|
||||
break;
|
||||
}
|
||||
|
||||
case wopQueryPathHash: {
|
||||
case WorkerProto::Op::QueryPathHash: {
|
||||
auto path = store->parseStorePath(readString(from));
|
||||
logger->startWork();
|
||||
auto hash = store->queryPathInfo(path)->narHash;
|
||||
logger->stopWork();
|
||||
to << hash.to_string(Base16, false);
|
||||
to << hash.to_string(HashFormat::Base16, false);
|
||||
break;
|
||||
}
|
||||
|
||||
case wopQueryReferences:
|
||||
case wopQueryReferrers:
|
||||
case wopQueryValidDerivers:
|
||||
case wopQueryDerivationOutputs: {
|
||||
case WorkerProto::Op::QueryReferences:
|
||||
case WorkerProto::Op::QueryReferrers:
|
||||
case WorkerProto::Op::QueryValidDerivers:
|
||||
case WorkerProto::Op::QueryDerivationOutputs: {
|
||||
auto path = store->parseStorePath(readString(from));
|
||||
logger->startWork();
|
||||
StorePathSet paths;
|
||||
if (op == wopQueryReferences)
|
||||
if (op == WorkerProto::Op::QueryReferences)
|
||||
for (auto & i : store->queryPathInfo(path)->references)
|
||||
paths.insert(i);
|
||||
else if (op == wopQueryReferrers)
|
||||
else if (op == WorkerProto::Op::QueryReferrers)
|
||||
store->queryReferrers(path, paths);
|
||||
else if (op == wopQueryValidDerivers)
|
||||
else if (op == WorkerProto::Op::QueryValidDerivers)
|
||||
paths = store->queryValidDerivers(path);
|
||||
else paths = store->queryDerivationOutputs(path);
|
||||
logger->stopWork();
|
||||
workerProtoWrite(*store, to, paths);
|
||||
WorkerProto::write(*store, wconn, paths);
|
||||
break;
|
||||
}
|
||||
|
||||
case wopQueryDerivationOutputNames: {
|
||||
case WorkerProto::Op::QueryDerivationOutputNames: {
|
||||
auto path = store->parseStorePath(readString(from));
|
||||
logger->startWork();
|
||||
auto names = store->readDerivation(path).outputNames();
|
||||
|
@ -362,16 +361,16 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
break;
|
||||
}
|
||||
|
||||
case wopQueryDerivationOutputMap: {
|
||||
case WorkerProto::Op::QueryDerivationOutputMap: {
|
||||
auto path = store->parseStorePath(readString(from));
|
||||
logger->startWork();
|
||||
auto outputs = store->queryPartialDerivationOutputMap(path);
|
||||
logger->stopWork();
|
||||
workerProtoWrite(*store, to, outputs);
|
||||
WorkerProto::write(*store, wconn, outputs);
|
||||
break;
|
||||
}
|
||||
|
||||
case wopQueryDeriver: {
|
||||
case WorkerProto::Op::QueryDeriver: {
|
||||
auto path = store->parseStorePath(readString(from));
|
||||
logger->startWork();
|
||||
auto info = store->queryPathInfo(path);
|
||||
|
@ -380,7 +379,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
break;
|
||||
}
|
||||
|
||||
case wopQueryPathFromHashPart: {
|
||||
case WorkerProto::Op::QueryPathFromHashPart: {
|
||||
auto hashPart = readString(from);
|
||||
logger->startWork();
|
||||
auto path = store->queryPathFromHashPart(hashPart);
|
||||
|
@ -389,11 +388,11 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
break;
|
||||
}
|
||||
|
||||
case wopAddToStore: {
|
||||
case WorkerProto::Op::AddToStore: {
|
||||
if (GET_PROTOCOL_MINOR(clientVersion) >= 25) {
|
||||
auto name = readString(from);
|
||||
auto camStr = readString(from);
|
||||
auto refs = WorkerProto<StorePathSet>::read(*store, from);
|
||||
auto refs = WorkerProto::Serialise<StorePathSet>::read(*store, rconn);
|
||||
bool repairBool;
|
||||
from >> repairBool;
|
||||
auto repair = RepairFlag{repairBool};
|
||||
|
@ -401,31 +400,18 @@ 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)
|
||||
throw UnimplementedError("When adding text-hashed data called '%s', only SHA-256 is supported but '%s' was given",
|
||||
name, printHashType(hashType));
|
||||
// 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);
|
||||
return store->queryPathInfo(path);
|
||||
},
|
||||
}, contentAddressMethod.raw);
|
||||
// TODO these two steps are essentially RemoteStore::addCAToStore. Move it up to Store.
|
||||
auto path = store->addToStoreFromDump(source, name, contentAddressMethod, hashAlgo, refs, repair);
|
||||
return store->queryPathInfo(path);
|
||||
}();
|
||||
logger->stopWork();
|
||||
|
||||
pathInfo->write(to, *store, GET_PROTOCOL_MINOR(clientVersion));
|
||||
WorkerProto::Serialise<ValidPathInfo>::write(*store, wconn, *pathInfo);
|
||||
} else {
|
||||
HashType hashAlgo;
|
||||
HashAlgorithm hashAlgo;
|
||||
std::string baseName;
|
||||
FileIngestionMethod method;
|
||||
{
|
||||
|
@ -441,7 +427,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) {
|
||||
|
@ -455,13 +441,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");
|
||||
}
|
||||
|
@ -475,7 +461,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
break;
|
||||
}
|
||||
|
||||
case wopAddMultipleToStore: {
|
||||
case WorkerProto::Op::AddMultipleToStore: {
|
||||
bool repair, dontCheckSigs;
|
||||
from >> repair >> dontCheckSigs;
|
||||
if (!trusted && dontCheckSigs)
|
||||
|
@ -492,18 +478,21 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
break;
|
||||
}
|
||||
|
||||
case wopAddTextToStore: {
|
||||
case WorkerProto::Op::AddTextToStore: {
|
||||
std::string suffix = readString(from);
|
||||
std::string s = readString(from);
|
||||
auto refs = WorkerProto<StorePathSet>::read(*store, from);
|
||||
auto refs = WorkerProto::Serialise<StorePathSet>::read(*store, rconn);
|
||||
logger->startWork();
|
||||
auto path = store->addTextToStore(suffix, s, refs, NoRepair);
|
||||
auto path = ({
|
||||
StringSource source { s };
|
||||
store->addToStoreFromDump(source, suffix, TextIngestionMethod {}, HashAlgorithm::SHA256, refs, NoRepair);
|
||||
});
|
||||
logger->stopWork();
|
||||
to << store->printStorePath(path);
|
||||
break;
|
||||
}
|
||||
|
||||
case wopExportPath: {
|
||||
case WorkerProto::Op::ExportPath: {
|
||||
auto path = store->parseStorePath(readString(from));
|
||||
readInt(from); // obsolete
|
||||
logger->startWork();
|
||||
|
@ -514,7 +503,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
break;
|
||||
}
|
||||
|
||||
case wopImportPaths: {
|
||||
case WorkerProto::Op::ImportPaths: {
|
||||
logger->startWork();
|
||||
TunnelSource source(from, to);
|
||||
auto paths = store->importPaths(source,
|
||||
|
@ -526,8 +515,8 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
break;
|
||||
}
|
||||
|
||||
case wopBuildPaths: {
|
||||
auto drvs = readDerivedPaths(*store, clientVersion, from);
|
||||
case WorkerProto::Op::BuildPaths: {
|
||||
auto drvs = WorkerProto::Serialise<DerivedPaths>::read(*store, rconn);
|
||||
BuildMode mode = bmNormal;
|
||||
if (GET_PROTOCOL_MINOR(clientVersion) >= 15) {
|
||||
mode = (BuildMode) readInt(from);
|
||||
|
@ -551,8 +540,8 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
break;
|
||||
}
|
||||
|
||||
case wopBuildPathsWithResults: {
|
||||
auto drvs = readDerivedPaths(*store, clientVersion, from);
|
||||
case WorkerProto::Op::BuildPathsWithResults: {
|
||||
auto drvs = WorkerProto::Serialise<DerivedPaths>::read(*store, rconn);
|
||||
BuildMode mode = bmNormal;
|
||||
mode = (BuildMode) readInt(from);
|
||||
|
||||
|
@ -567,14 +556,23 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
auto results = store->buildPathsWithResults(drvs, mode);
|
||||
logger->stopWork();
|
||||
|
||||
workerProtoWrite(*store, to, results);
|
||||
WorkerProto::write(*store, wconn, results);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case wopBuildDerivation: {
|
||||
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();
|
||||
|
@ -636,20 +634,11 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
|
||||
auto res = store->buildDerivation(drvPath, drv, buildMode);
|
||||
logger->stopWork();
|
||||
to << res.status << res.errorMsg;
|
||||
if (GET_PROTOCOL_MINOR(clientVersion) >= 29) {
|
||||
to << res.timesBuilt << res.isNonDeterministic << res.startTime << res.stopTime;
|
||||
}
|
||||
if (GET_PROTOCOL_MINOR(clientVersion) >= 28) {
|
||||
DrvOutputs builtOutputs;
|
||||
for (auto & [output, realisation] : res.builtOutputs)
|
||||
builtOutputs.insert_or_assign(realisation.id, realisation);
|
||||
workerProtoWrite(*store, to, builtOutputs);
|
||||
}
|
||||
WorkerProto::write(*store, wconn, res);
|
||||
break;
|
||||
}
|
||||
|
||||
case wopEnsurePath: {
|
||||
case WorkerProto::Op::EnsurePath: {
|
||||
auto path = store->parseStorePath(readString(from));
|
||||
logger->startWork();
|
||||
store->ensurePath(path);
|
||||
|
@ -658,7 +647,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
break;
|
||||
}
|
||||
|
||||
case wopAddTempRoot: {
|
||||
case WorkerProto::Op::AddTempRoot: {
|
||||
auto path = store->parseStorePath(readString(from));
|
||||
logger->startWork();
|
||||
store->addTempRoot(path);
|
||||
|
@ -667,12 +656,27 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
break;
|
||||
}
|
||||
|
||||
case wopAddIndirectRoot: {
|
||||
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));
|
||||
|
||||
logger->startWork();
|
||||
auto & gcStore = require<GcStore>(*store);
|
||||
gcStore.addIndirectRoot(path);
|
||||
auto & indirectRootStore = require<IndirectRootStore>(*store);
|
||||
indirectRootStore.addIndirectRoot(path);
|
||||
logger->stopWork();
|
||||
|
||||
to << 1;
|
||||
|
@ -680,14 +684,14 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
}
|
||||
|
||||
// Obsolete.
|
||||
case wopSyncWithGC: {
|
||||
case WorkerProto::Op::SyncWithGC: {
|
||||
logger->startWork();
|
||||
logger->stopWork();
|
||||
to << 1;
|
||||
break;
|
||||
}
|
||||
|
||||
case wopFindRoots: {
|
||||
case WorkerProto::Op::FindRoots: {
|
||||
logger->startWork();
|
||||
auto & gcStore = require<GcStore>(*store);
|
||||
Roots roots = gcStore.findRoots(!trusted);
|
||||
|
@ -706,10 +710,10 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
break;
|
||||
}
|
||||
|
||||
case wopCollectGarbage: {
|
||||
case WorkerProto::Op::CollectGarbage: {
|
||||
GCOptions options;
|
||||
options.action = (GCOptions::GCAction) readInt(from);
|
||||
options.pathsToDelete = WorkerProto<StorePathSet>::read(*store, from);
|
||||
options.pathsToDelete = WorkerProto::Serialise<StorePathSet>::read(*store, rconn);
|
||||
from >> options.ignoreLiveness >> options.maxFreed;
|
||||
// obsolete fields
|
||||
readInt(from);
|
||||
|
@ -730,7 +734,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
break;
|
||||
}
|
||||
|
||||
case wopSetOptions: {
|
||||
case WorkerProto::Op::SetOptions: {
|
||||
|
||||
ClientSettings clientSettings;
|
||||
|
||||
|
@ -767,7 +771,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
break;
|
||||
}
|
||||
|
||||
case wopQuerySubstitutablePathInfo: {
|
||||
case WorkerProto::Op::QuerySubstitutablePathInfo: {
|
||||
auto path = store->parseStorePath(readString(from));
|
||||
logger->startWork();
|
||||
SubstitutablePathInfos infos;
|
||||
|
@ -779,22 +783,22 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
else {
|
||||
to << 1
|
||||
<< (i->second.deriver ? store->printStorePath(*i->second.deriver) : "");
|
||||
workerProtoWrite(*store, to, i->second.references);
|
||||
WorkerProto::write(*store, wconn, i->second.references);
|
||||
to << i->second.downloadSize
|
||||
<< i->second.narSize;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case wopQuerySubstitutablePathInfos: {
|
||||
case WorkerProto::Op::QuerySubstitutablePathInfos: {
|
||||
SubstitutablePathInfos infos;
|
||||
StorePathCAMap pathsMap = {};
|
||||
if (GET_PROTOCOL_MINOR(clientVersion) < 22) {
|
||||
auto paths = WorkerProto<StorePathSet>::read(*store, from);
|
||||
auto paths = WorkerProto::Serialise<StorePathSet>::read(*store, rconn);
|
||||
for (auto & path : paths)
|
||||
pathsMap.emplace(path, std::nullopt);
|
||||
} else
|
||||
pathsMap = WorkerProto<StorePathCAMap>::read(*store, from);
|
||||
pathsMap = WorkerProto::Serialise<StorePathCAMap>::read(*store, rconn);
|
||||
logger->startWork();
|
||||
store->querySubstitutablePathInfos(pathsMap, infos);
|
||||
logger->stopWork();
|
||||
|
@ -802,21 +806,21 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
for (auto & i : infos) {
|
||||
to << store->printStorePath(i.first)
|
||||
<< (i.second.deriver ? store->printStorePath(*i.second.deriver) : "");
|
||||
workerProtoWrite(*store, to, i.second.references);
|
||||
WorkerProto::write(*store, wconn, i.second.references);
|
||||
to << i.second.downloadSize << i.second.narSize;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case wopQueryAllValidPaths: {
|
||||
case WorkerProto::Op::QueryAllValidPaths: {
|
||||
logger->startWork();
|
||||
auto paths = store->queryAllValidPaths();
|
||||
logger->stopWork();
|
||||
workerProtoWrite(*store, to, paths);
|
||||
WorkerProto::write(*store, wconn, paths);
|
||||
break;
|
||||
}
|
||||
|
||||
case wopQueryPathInfo: {
|
||||
case WorkerProto::Op::QueryPathInfo: {
|
||||
auto path = store->parseStorePath(readString(from));
|
||||
std::shared_ptr<const ValidPathInfo> info;
|
||||
logger->startWork();
|
||||
|
@ -829,7 +833,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
if (info) {
|
||||
if (GET_PROTOCOL_MINOR(clientVersion) >= 17)
|
||||
to << 1;
|
||||
info->write(to, *store, GET_PROTOCOL_MINOR(clientVersion), false);
|
||||
WorkerProto::write(*store, wconn, static_cast<const UnkeyedValidPathInfo &>(*info));
|
||||
} else {
|
||||
assert(GET_PROTOCOL_MINOR(clientVersion) >= 17);
|
||||
to << 0;
|
||||
|
@ -837,14 +841,14 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
break;
|
||||
}
|
||||
|
||||
case wopOptimiseStore:
|
||||
case WorkerProto::Op::OptimiseStore:
|
||||
logger->startWork();
|
||||
store->optimiseStore();
|
||||
logger->stopWork();
|
||||
to << 1;
|
||||
break;
|
||||
|
||||
case wopVerifyStore: {
|
||||
case WorkerProto::Op::VerifyStore: {
|
||||
bool checkContents, repair;
|
||||
from >> checkContents >> repair;
|
||||
logger->startWork();
|
||||
|
@ -856,19 +860,17 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
break;
|
||||
}
|
||||
|
||||
case wopAddSignatures: {
|
||||
case WorkerProto::Op::AddSignatures: {
|
||||
auto path = store->parseStorePath(readString(from));
|
||||
StringSet sigs = readStrings<StringSet>(from);
|
||||
logger->startWork();
|
||||
if (!trusted)
|
||||
throw Error("you are not privileged to add signatures");
|
||||
store->addSignatures(path, sigs);
|
||||
logger->stopWork();
|
||||
to << 1;
|
||||
break;
|
||||
}
|
||||
|
||||
case wopNarFromPath: {
|
||||
case WorkerProto::Op::NarFromPath: {
|
||||
auto path = store->parseStorePath(readString(from));
|
||||
logger->startWork();
|
||||
logger->stopWork();
|
||||
|
@ -876,15 +878,15 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
break;
|
||||
}
|
||||
|
||||
case wopAddToStoreNar: {
|
||||
case WorkerProto::Op::AddToStoreNar: {
|
||||
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);
|
||||
info.references = WorkerProto<StorePathSet>::read(*store, from);
|
||||
info.references = WorkerProto::Serialise<StorePathSet>::read(*store, rconn);
|
||||
from >> info.registrationTime >> info.narSize >> info.ultimate;
|
||||
info.sigs = readStrings<StringSet>(from);
|
||||
info.ca = ContentAddress::parseOpt(readString(from));
|
||||
|
@ -911,7 +913,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);
|
||||
}
|
||||
|
@ -928,21 +930,21 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
break;
|
||||
}
|
||||
|
||||
case wopQueryMissing: {
|
||||
auto targets = readDerivedPaths(*store, clientVersion, from);
|
||||
case WorkerProto::Op::QueryMissing: {
|
||||
auto targets = WorkerProto::Serialise<DerivedPaths>::read(*store, rconn);
|
||||
logger->startWork();
|
||||
StorePathSet willBuild, willSubstitute, unknown;
|
||||
uint64_t downloadSize, narSize;
|
||||
store->queryMissing(targets, willBuild, willSubstitute, unknown, downloadSize, narSize);
|
||||
logger->stopWork();
|
||||
workerProtoWrite(*store, to, willBuild);
|
||||
workerProtoWrite(*store, to, willSubstitute);
|
||||
workerProtoWrite(*store, to, unknown);
|
||||
WorkerProto::write(*store, wconn, willBuild);
|
||||
WorkerProto::write(*store, wconn, willSubstitute);
|
||||
WorkerProto::write(*store, wconn, unknown);
|
||||
to << downloadSize << narSize;
|
||||
break;
|
||||
}
|
||||
|
||||
case wopRegisterDrvOutput: {
|
||||
case WorkerProto::Op::RegisterDrvOutput: {
|
||||
logger->startWork();
|
||||
if (GET_PROTOCOL_MINOR(clientVersion) < 31) {
|
||||
auto outputId = DrvOutput::parse(readString(from));
|
||||
|
@ -950,14 +952,14 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
store->registerDrvOutput(Realisation{
|
||||
.id = outputId, .outPath = outputPath});
|
||||
} else {
|
||||
auto realisation = WorkerProto<Realisation>::read(*store, from);
|
||||
auto realisation = WorkerProto::Serialise<Realisation>::read(*store, rconn);
|
||||
store->registerDrvOutput(realisation);
|
||||
}
|
||||
logger->stopWork();
|
||||
break;
|
||||
}
|
||||
|
||||
case wopQueryRealisation: {
|
||||
case WorkerProto::Op::QueryRealisation: {
|
||||
logger->startWork();
|
||||
auto outputId = DrvOutput::parse(readString(from));
|
||||
auto info = store->queryRealisation(outputId);
|
||||
|
@ -965,16 +967,16 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
if (GET_PROTOCOL_MINOR(clientVersion) < 31) {
|
||||
std::set<StorePath> outPaths;
|
||||
if (info) outPaths.insert(info->outPath);
|
||||
workerProtoWrite(*store, to, outPaths);
|
||||
WorkerProto::write(*store, wconn, outPaths);
|
||||
} else {
|
||||
std::set<Realisation> realisations;
|
||||
if (info) realisations.insert(*info);
|
||||
workerProtoWrite(*store, to, realisations);
|
||||
WorkerProto::write(*store, wconn, realisations);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case wopAddBuildLog: {
|
||||
case WorkerProto::Op::AddBuildLog: {
|
||||
StorePath path{readString(from)};
|
||||
logger->startWork();
|
||||
if (!trusted)
|
||||
|
@ -991,6 +993,10 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
break;
|
||||
}
|
||||
|
||||
case WorkerProto::Op::QueryFailedPaths:
|
||||
case WorkerProto::Op::ClearFailedPaths:
|
||||
throw Error("Removed operation %1%", op);
|
||||
|
||||
default:
|
||||
throw Error("invalid operation %1%", op);
|
||||
}
|
||||
|
@ -1010,7 +1016,7 @@ void processConnection(
|
|||
if (magic != WORKER_MAGIC_1) throw Error("protocol mismatch");
|
||||
to << WORKER_MAGIC_2 << PROTOCOL_VERSION;
|
||||
to.flush();
|
||||
unsigned int clientVersion = readInt(from);
|
||||
WorkerProto::Version clientVersion = readInt(from);
|
||||
|
||||
if (clientVersion < 0x10a)
|
||||
throw Error("the Nix client version is too old");
|
||||
|
@ -1045,7 +1051,11 @@ void processConnection(
|
|||
auto temp = trusted
|
||||
? store->isTrustedClient()
|
||||
: std::optional { NotTrusted };
|
||||
workerProtoWrite(*store, to, temp);
|
||||
WorkerProto::WriteConn wconn {
|
||||
.to = to,
|
||||
.version = clientVersion,
|
||||
};
|
||||
WorkerProto::write(*store, wconn, temp);
|
||||
}
|
||||
|
||||
/* Send startup error messages to the client. */
|
||||
|
@ -1058,9 +1068,9 @@ void processConnection(
|
|||
|
||||
/* Process client requests. */
|
||||
while (true) {
|
||||
WorkerOp op;
|
||||
WorkerProto::Op op;
|
||||
try {
|
||||
op = (WorkerOp) readInt(from);
|
||||
op = (enum WorkerProto::Op) readInt(from);
|
||||
} catch (Interrupted & e) {
|
||||
break;
|
||||
} catch (EndOfFile & e) {
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -6,9 +6,10 @@
|
|||
#include "hash.hh"
|
||||
#include "content-address.hh"
|
||||
#include "repair-flag.hh"
|
||||
#include "derived-path.hh"
|
||||
#include "derived-path-map.hh"
|
||||
#include "sync.hh"
|
||||
#include "comparator.hh"
|
||||
#include "variant-wrapper.hh"
|
||||
|
||||
#include <map>
|
||||
#include <variant>
|
||||
|
@ -16,112 +17,114 @@
|
|||
|
||||
namespace nix {
|
||||
|
||||
class Store;
|
||||
struct StoreDirConfig;
|
||||
|
||||
/* Abstract syntax of derivations. */
|
||||
|
||||
/**
|
||||
* The traditional non-fixed-output derivation type.
|
||||
*/
|
||||
struct DerivationOutputInputAddressed
|
||||
{
|
||||
StorePath path;
|
||||
|
||||
GENERATE_CMP(DerivationOutputInputAddressed, me->path);
|
||||
};
|
||||
|
||||
/**
|
||||
* Fixed-output derivations, whose output paths are content
|
||||
* addressed according to that fixed output.
|
||||
*/
|
||||
struct DerivationOutputCAFixed
|
||||
{
|
||||
/**
|
||||
* Method and hash used for expected hash computation.
|
||||
*
|
||||
* References are not allowed by fiat.
|
||||
*/
|
||||
ContentAddress ca;
|
||||
|
||||
/**
|
||||
* Return the \ref StorePath "store path" corresponding to this output
|
||||
*
|
||||
* @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, std::string_view outputName) const;
|
||||
|
||||
GENERATE_CMP(DerivationOutputCAFixed, me->ca);
|
||||
};
|
||||
|
||||
/**
|
||||
* Floating-output derivations, whose output paths are content
|
||||
* addressed, but not fixed, and so are dynamically calculated from
|
||||
* whatever the output ends up being.
|
||||
* */
|
||||
struct DerivationOutputCAFloating
|
||||
{
|
||||
/**
|
||||
* How the file system objects will be serialized for hashing
|
||||
*/
|
||||
ContentAddressMethod method;
|
||||
|
||||
/**
|
||||
* How the serialization will be hashed
|
||||
*/
|
||||
HashType hashType;
|
||||
|
||||
GENERATE_CMP(DerivationOutputCAFloating, me->method, me->hashType);
|
||||
};
|
||||
|
||||
/**
|
||||
* Input-addressed output which depends on a (CA) derivation whose hash
|
||||
* isn't known yet.
|
||||
*/
|
||||
struct DerivationOutputDeferred {
|
||||
GENERATE_CMP(DerivationOutputDeferred);
|
||||
};
|
||||
|
||||
/**
|
||||
* Impure output which is moved to a content-addressed location (like
|
||||
* CAFloating) but isn't registered as a realization.
|
||||
*/
|
||||
struct DerivationOutputImpure
|
||||
{
|
||||
/**
|
||||
* How the file system objects will be serialized for hashing
|
||||
*/
|
||||
ContentAddressMethod method;
|
||||
|
||||
/**
|
||||
* How the serialization will be hashed
|
||||
*/
|
||||
HashType hashType;
|
||||
|
||||
GENERATE_CMP(DerivationOutputImpure, me->method, me->hashType);
|
||||
};
|
||||
|
||||
typedef std::variant<
|
||||
DerivationOutputInputAddressed,
|
||||
DerivationOutputCAFixed,
|
||||
DerivationOutputCAFloating,
|
||||
DerivationOutputDeferred,
|
||||
DerivationOutputImpure
|
||||
> _DerivationOutputRaw;
|
||||
|
||||
/**
|
||||
* A single output of a BasicDerivation (and Derivation).
|
||||
*/
|
||||
struct DerivationOutput : _DerivationOutputRaw
|
||||
struct DerivationOutput
|
||||
{
|
||||
using Raw = _DerivationOutputRaw;
|
||||
using Raw::Raw;
|
||||
/**
|
||||
* The traditional non-fixed-output derivation type.
|
||||
*/
|
||||
struct InputAddressed
|
||||
{
|
||||
StorePath path;
|
||||
|
||||
using InputAddressed = DerivationOutputInputAddressed;
|
||||
using CAFixed = DerivationOutputCAFixed;
|
||||
using CAFloating = DerivationOutputCAFloating;
|
||||
using Deferred = DerivationOutputDeferred;
|
||||
using Impure = DerivationOutputImpure;
|
||||
GENERATE_CMP(InputAddressed, me->path);
|
||||
};
|
||||
|
||||
/**
|
||||
* Fixed-output derivations, whose output paths are content
|
||||
* addressed according to that fixed output.
|
||||
*/
|
||||
struct CAFixed
|
||||
{
|
||||
/**
|
||||
* Method and hash used for expected hash computation.
|
||||
*
|
||||
* References are not allowed by fiat.
|
||||
*/
|
||||
ContentAddress ca;
|
||||
|
||||
/**
|
||||
* Return the \ref StorePath "store path" corresponding to this output
|
||||
*
|
||||
* @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 StoreDirConfig & store, std::string_view drvName, OutputNameView outputName) const;
|
||||
|
||||
GENERATE_CMP(CAFixed, me->ca);
|
||||
};
|
||||
|
||||
/**
|
||||
* Floating-output derivations, whose output paths are content
|
||||
* addressed, but not fixed, and so are dynamically calculated from
|
||||
* whatever the output ends up being.
|
||||
* */
|
||||
struct CAFloating
|
||||
{
|
||||
/**
|
||||
* How the file system objects will be serialized for hashing
|
||||
*/
|
||||
ContentAddressMethod method;
|
||||
|
||||
/**
|
||||
* How the serialization will be hashed
|
||||
*/
|
||||
HashAlgorithm hashAlgo;
|
||||
|
||||
GENERATE_CMP(CAFloating, me->method, me->hashAlgo);
|
||||
};
|
||||
|
||||
/**
|
||||
* Input-addressed output which depends on a (CA) derivation whose hash
|
||||
* isn't known yet.
|
||||
*/
|
||||
struct Deferred {
|
||||
GENERATE_CMP(Deferred);
|
||||
};
|
||||
|
||||
/**
|
||||
* Impure output which is moved to a content-addressed location (like
|
||||
* CAFloating) but isn't registered as a realization.
|
||||
*/
|
||||
struct Impure
|
||||
{
|
||||
/**
|
||||
* How the file system objects will be serialized for hashing
|
||||
*/
|
||||
ContentAddressMethod method;
|
||||
|
||||
/**
|
||||
* How the serialization will be hashed
|
||||
*/
|
||||
HashAlgorithm hashAlgo;
|
||||
|
||||
GENERATE_CMP(Impure, me->method, me->hashAlgo);
|
||||
};
|
||||
|
||||
typedef std::variant<
|
||||
InputAddressed,
|
||||
CAFixed,
|
||||
CAFloating,
|
||||
Deferred,
|
||||
Impure
|
||||
> Raw;
|
||||
|
||||
Raw raw;
|
||||
|
||||
GENERATE_CMP(DerivationOutput, me->raw);
|
||||
|
||||
MAKE_WRAPPER_CONSTRUCTOR(DerivationOutput);
|
||||
|
||||
/**
|
||||
* Force choosing a variant
|
||||
*/
|
||||
DerivationOutput() = delete;
|
||||
|
||||
/**
|
||||
* \note when you use this function you should make sure that you're
|
||||
|
@ -129,23 +132,19 @@ struct DerivationOutput : _DerivationOutputRaw
|
|||
* the safer interface provided by
|
||||
* BasicDerivation::outputsAndOptPaths
|
||||
*/
|
||||
std::optional<StorePath> path(const Store & store, std::string_view drvName, std::string_view outputName) const;
|
||||
|
||||
inline const Raw & raw() const {
|
||||
return static_cast<const Raw &>(*this);
|
||||
}
|
||||
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,
|
||||
std::string_view outputName) const;
|
||||
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,
|
||||
std::string_view outputName,
|
||||
OutputNameView outputName,
|
||||
const nlohmann::json & json,
|
||||
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
|
||||
};
|
||||
|
@ -167,61 +166,71 @@ typedef std::map<std::string, std::pair<DerivationOutput, std::optional<StorePat
|
|||
*/
|
||||
typedef std::map<StorePath, StringSet> DerivationInputs;
|
||||
|
||||
/**
|
||||
* Input-addressed derivation types
|
||||
*/
|
||||
struct DerivationType_InputAddressed {
|
||||
struct DerivationType {
|
||||
/**
|
||||
* True iff the derivation type can't be determined statically,
|
||||
* for instance because it (transitively) depends on a content-addressed
|
||||
* derivation.
|
||||
*/
|
||||
bool deferred;
|
||||
};
|
||||
|
||||
/**
|
||||
* Content-addressed derivation types
|
||||
*/
|
||||
struct DerivationType_ContentAddressed {
|
||||
/**
|
||||
* Whether the derivation should be built safely inside a sandbox.
|
||||
* Input-addressed derivation types
|
||||
*/
|
||||
bool sandboxed;
|
||||
struct InputAddressed {
|
||||
/**
|
||||
* True iff the derivation type can't be determined statically,
|
||||
* for instance because it (transitively) depends on a content-addressed
|
||||
* derivation.
|
||||
*/
|
||||
bool deferred;
|
||||
|
||||
GENERATE_CMP(InputAddressed, me->deferred);
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether the derivation's outputs' content-addresses are "fixed"
|
||||
* or "floating.
|
||||
*
|
||||
* - Fixed: content-addresses are written down as part of the
|
||||
* derivation itself. If the outputs don't end up matching the
|
||||
* build fails.
|
||||
*
|
||||
* - Floating: content-addresses are not written down, we do not
|
||||
* know them until we perform the build.
|
||||
* Content-addressed derivation types
|
||||
*/
|
||||
bool fixed;
|
||||
};
|
||||
struct ContentAddressed {
|
||||
/**
|
||||
* Whether the derivation should be built safely inside a sandbox.
|
||||
*/
|
||||
bool sandboxed;
|
||||
/**
|
||||
* Whether the derivation's outputs' content-addresses are "fixed"
|
||||
* or "floating".
|
||||
*
|
||||
* - Fixed: content-addresses are written down as part of the
|
||||
* derivation itself. If the outputs don't end up matching the
|
||||
* build fails.
|
||||
*
|
||||
* - Floating: content-addresses are not written down, we do not
|
||||
* know them until we perform the build.
|
||||
*/
|
||||
bool fixed;
|
||||
|
||||
/**
|
||||
* Impure derivation type
|
||||
*
|
||||
* This is similar at buil-time to the content addressed, not standboxed, not fixed
|
||||
* type, but has some restrictions on its usage.
|
||||
*/
|
||||
struct DerivationType_Impure {
|
||||
};
|
||||
GENERATE_CMP(ContentAddressed, me->sandboxed, me->fixed);
|
||||
};
|
||||
|
||||
typedef std::variant<
|
||||
DerivationType_InputAddressed,
|
||||
DerivationType_ContentAddressed,
|
||||
DerivationType_Impure
|
||||
> _DerivationTypeRaw;
|
||||
/**
|
||||
* Impure derivation type
|
||||
*
|
||||
* This is similar at buil-time to the content addressed, not standboxed, not fixed
|
||||
* type, but has some restrictions on its usage.
|
||||
*/
|
||||
struct Impure {
|
||||
GENERATE_CMP(Impure);
|
||||
};
|
||||
|
||||
struct DerivationType : _DerivationTypeRaw {
|
||||
using Raw = _DerivationTypeRaw;
|
||||
using Raw::Raw;
|
||||
using InputAddressed = DerivationType_InputAddressed;
|
||||
using ContentAddressed = DerivationType_ContentAddressed;
|
||||
using Impure = DerivationType_Impure;
|
||||
typedef std::variant<
|
||||
InputAddressed,
|
||||
ContentAddressed,
|
||||
Impure
|
||||
> Raw;
|
||||
|
||||
Raw raw;
|
||||
|
||||
GENERATE_CMP(DerivationType, me->raw);
|
||||
|
||||
MAKE_WRAPPER_CONSTRUCTOR(DerivationType);
|
||||
|
||||
/**
|
||||
* Force choosing a variant
|
||||
*/
|
||||
DerivationType() = delete;
|
||||
|
||||
/**
|
||||
* Do the outputs of the derivation have paths calculated from their
|
||||
|
@ -257,10 +266,6 @@ struct DerivationType : _DerivationTypeRaw {
|
|||
* closure, or if fixed output.
|
||||
*/
|
||||
bool hasKnownOutputPaths() const;
|
||||
|
||||
inline const Raw & raw() const {
|
||||
return static_cast<const Raw &>(*this);
|
||||
}
|
||||
};
|
||||
|
||||
struct BasicDerivation
|
||||
|
@ -299,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);
|
||||
|
||||
|
@ -313,18 +318,20 @@ struct BasicDerivation
|
|||
me->name);
|
||||
};
|
||||
|
||||
class Store;
|
||||
|
||||
struct Derivation : BasicDerivation
|
||||
{
|
||||
/**
|
||||
* inputs that are sub-derivations
|
||||
*/
|
||||
DerivationInputs inputDrvs;
|
||||
DerivedPathMap<std::set<OutputName>> inputDrvs;
|
||||
|
||||
/**
|
||||
* Print a derivation.
|
||||
*/
|
||||
std::string unparse(const Store & store, bool maskOutputs,
|
||||
std::map<std::string, StringSet> * actualInputs = nullptr) const;
|
||||
std::string unparse(const StoreDirConfig & store, bool maskOutputs,
|
||||
DerivedPathMap<StringSet>::ChildNode::Map * actualInputs = nullptr) const;
|
||||
|
||||
/**
|
||||
* Return the underlying basic derivation but with these changes:
|
||||
|
@ -335,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
|
||||
|
@ -360,10 +367,11 @@ 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 nlohmann::json & json);
|
||||
const StoreDirConfig & store,
|
||||
const nlohmann::json & json,
|
||||
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
|
||||
|
||||
GENERATE_CMP(Derivation,
|
||||
static_cast<const BasicDerivation &>(*me),
|
||||
|
@ -384,7 +392,11 @@ StorePath writeDerivation(Store & store,
|
|||
/**
|
||||
* Read a derivation from a file.
|
||||
*/
|
||||
Derivation parseDerivation(const Store & store, std::string && s, std::string_view name);
|
||||
Derivation parseDerivation(
|
||||
const StoreDirConfig & store,
|
||||
std::string && s,
|
||||
std::string_view name,
|
||||
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
|
||||
|
||||
/**
|
||||
* \todo Remove.
|
||||
|
@ -400,7 +412,7 @@ bool isDerivation(std::string_view fileName);
|
|||
* This is usually <drv-name>-<output-name>, but is just <drv-name> when
|
||||
* the output name is "out".
|
||||
*/
|
||||
std::string outputPathName(std::string_view drvName, std::string_view outputName);
|
||||
std::string outputPathName(std::string_view drvName, OutputNameView outputName);
|
||||
|
||||
|
||||
/**
|
||||
|
@ -483,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
|
||||
|
@ -494,7 +506,7 @@ void writeDerivation(Sink & out, const Store & store, const BasicDerivation & dr
|
|||
* own outputs without needing to use the hash of a derivation in
|
||||
* itself, making the hash near-impossible to calculate.
|
||||
*/
|
||||
std::string hashPlaceholder(const std::string_view outputName);
|
||||
std::string hashPlaceholder(const OutputNameView outputName);
|
||||
|
||||
extern const Hash impureOutputHash;
|
||||
|
||||
|
|
70
src/libstore/derived-path-map.cc
Normal file
70
src/libstore/derived-path-map.cc
Normal file
|
@ -0,0 +1,70 @@
|
|||
#include "derived-path-map.hh"
|
||||
#include "util.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
template<typename V>
|
||||
typename DerivedPathMap<V>::ChildNode & DerivedPathMap<V>::ensureSlot(const SingleDerivedPath & k)
|
||||
{
|
||||
std::function<ChildNode &(const SingleDerivedPath & )> initIter;
|
||||
initIter = [&](const auto & k) -> auto & {
|
||||
return std::visit(overloaded {
|
||||
[&](const SingleDerivedPath::Opaque & bo) -> auto & {
|
||||
// will not overwrite if already there
|
||||
return map[bo.path];
|
||||
},
|
||||
[&](const SingleDerivedPath::Built & bfd) -> auto & {
|
||||
auto & n = initIter(*bfd.drvPath);
|
||||
return n.childMap[bfd.output];
|
||||
},
|
||||
}, k.raw());
|
||||
};
|
||||
return initIter(k);
|
||||
}
|
||||
|
||||
template<typename V>
|
||||
typename DerivedPathMap<V>::ChildNode * DerivedPathMap<V>::findSlot(const SingleDerivedPath & k)
|
||||
{
|
||||
std::function<ChildNode *(const SingleDerivedPath & )> initIter;
|
||||
initIter = [&](const auto & k) {
|
||||
return std::visit(overloaded {
|
||||
[&](const SingleDerivedPath::Opaque & bo) {
|
||||
auto it = map.find(bo.path);
|
||||
return it != map.end()
|
||||
? &it->second
|
||||
: nullptr;
|
||||
},
|
||||
[&](const SingleDerivedPath::Built & bfd) {
|
||||
auto * n = initIter(*bfd.drvPath);
|
||||
if (!n) return (ChildNode *)nullptr;
|
||||
|
||||
auto it = n->childMap.find(bfd.output);
|
||||
return it != n->childMap.end()
|
||||
? &it->second
|
||||
: nullptr;
|
||||
},
|
||||
}, k.raw());
|
||||
};
|
||||
return initIter(k);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// instantiations
|
||||
|
||||
namespace nix {
|
||||
|
||||
GENERATE_CMP_EXT(
|
||||
template<>,
|
||||
DerivedPathMap<std::set<std::string>>::ChildNode,
|
||||
me->value,
|
||||
me->childMap);
|
||||
|
||||
GENERATE_CMP_EXT(
|
||||
template<>,
|
||||
DerivedPathMap<std::set<std::string>>,
|
||||
me->map);
|
||||
|
||||
template struct DerivedPathMap<std::set<std::string>>;
|
||||
|
||||
};
|
96
src/libstore/derived-path-map.hh
Normal file
96
src/libstore/derived-path-map.hh
Normal file
|
@ -0,0 +1,96 @@
|
|||
#pragma once
|
||||
///@file
|
||||
|
||||
#include "types.hh"
|
||||
#include "derived-path.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
/**
|
||||
* A simple Trie, of sorts. Conceptually a map of `SingleDerivedPath` to
|
||||
* values.
|
||||
*
|
||||
* Concretely, an n-ary tree, as described below. A
|
||||
* `SingleDerivedPath::Opaque` maps to the value of an immediate child
|
||||
* of the root node. A `SingleDerivedPath::Built` maps to a deeper child
|
||||
* node: the `SingleDerivedPath::Built::drvPath` is first mapped to a a
|
||||
* child node (inductively), and then the
|
||||
* `SingleDerivedPath::Built::output` is used to look up that child's
|
||||
* child via its map. In this manner, every `SingleDerivedPath` is
|
||||
* mapped to a child node.
|
||||
*
|
||||
* @param V A type to instantiate for each output. It should probably
|
||||
* should be an "optional" type so not every interior node has to have a
|
||||
* value. `* const Something` or `std::optional<Something>` would be
|
||||
* good choices for "optional" types.
|
||||
*/
|
||||
template<typename V>
|
||||
struct DerivedPathMap {
|
||||
/**
|
||||
* A child node (non-root node).
|
||||
*/
|
||||
struct ChildNode {
|
||||
/**
|
||||
* Value of this child node.
|
||||
*
|
||||
* @see DerivedPathMap for what `V` should be.
|
||||
*/
|
||||
V value;
|
||||
|
||||
/**
|
||||
* The map type for the root node.
|
||||
*/
|
||||
using Map = std::map<OutputName, ChildNode>;
|
||||
|
||||
/**
|
||||
* The map of the root node.
|
||||
*/
|
||||
Map childMap;
|
||||
|
||||
DECLARE_CMP(ChildNode);
|
||||
};
|
||||
|
||||
/**
|
||||
* The map type for the root node.
|
||||
*/
|
||||
using Map = std::map<StorePath, ChildNode>;
|
||||
|
||||
/**
|
||||
* The map of root node.
|
||||
*/
|
||||
Map map;
|
||||
|
||||
DECLARE_CMP(DerivedPathMap);
|
||||
|
||||
/**
|
||||
* Find the node for `k`, creating it if needed.
|
||||
*
|
||||
* The node is referred to as a "slot" on the assumption that `V` is
|
||||
* some sort of optional type, so the given key can be set or unset
|
||||
* by changing this node.
|
||||
*/
|
||||
ChildNode & ensureSlot(const SingleDerivedPath & k);
|
||||
|
||||
/**
|
||||
* Like `ensureSlot` but does not create the slot if it doesn't exist.
|
||||
*
|
||||
* Read the entire description of `ensureSlot` to understand an
|
||||
* important caveat here that "have slot" does *not* imply "key is
|
||||
* set in map". To ensure a key is set one would need to get the
|
||||
* child node (with `findSlot` or `ensureSlot`) *and* check the
|
||||
* `ChildNode::value`.
|
||||
*/
|
||||
ChildNode * findSlot(const SingleDerivedPath & k);
|
||||
};
|
||||
|
||||
|
||||
DECLARE_CMP_EXT(
|
||||
template<>,
|
||||
DerivedPathMap<std::set<std::string>>::,
|
||||
DerivedPathMap<std::set<std::string>>);
|
||||
DECLARE_CMP_EXT(
|
||||
template<>,
|
||||
DerivedPathMap<std::set<std::string>>::ChildNode::,
|
||||
DerivedPathMap<std::set<std::string>>::ChildNode);
|
||||
|
||||
}
|
|
@ -8,80 +8,133 @@
|
|||
|
||||
namespace nix {
|
||||
|
||||
nlohmann::json DerivedPath::Opaque::toJSON(ref<Store> store) const {
|
||||
#define CMP_ONE(CHILD_TYPE, MY_TYPE, FIELD, COMPARATOR) \
|
||||
bool MY_TYPE ::operator COMPARATOR (const MY_TYPE & other) const \
|
||||
{ \
|
||||
const MY_TYPE* me = this; \
|
||||
auto fields1 = std::tie(*me->drvPath, me->FIELD); \
|
||||
me = &other; \
|
||||
auto fields2 = std::tie(*me->drvPath, me->FIELD); \
|
||||
return fields1 COMPARATOR fields2; \
|
||||
}
|
||||
#define CMP(CHILD_TYPE, MY_TYPE, FIELD) \
|
||||
CMP_ONE(CHILD_TYPE, MY_TYPE, FIELD, ==) \
|
||||
CMP_ONE(CHILD_TYPE, MY_TYPE, FIELD, !=) \
|
||||
CMP_ONE(CHILD_TYPE, MY_TYPE, FIELD, <)
|
||||
|
||||
CMP(SingleDerivedPath, SingleDerivedPathBuilt, output)
|
||||
|
||||
CMP(SingleDerivedPath, DerivedPathBuilt, outputs)
|
||||
|
||||
#undef CMP
|
||||
#undef CMP_ONE
|
||||
|
||||
nlohmann::json DerivedPath::Opaque::toJSON(const StoreDirConfig & store) const
|
||||
{
|
||||
return store.printStorePath(path);
|
||||
}
|
||||
|
||||
nlohmann::json SingleDerivedPath::Built::toJSON(Store & store) const {
|
||||
nlohmann::json res;
|
||||
res["path"] = store->printStorePath(path);
|
||||
res["drvPath"] = drvPath->toJSON(store);
|
||||
// Fallback for the input-addressed derivation case: We expect to always be
|
||||
// able to print the output paths, so let’s do it
|
||||
// FIXME try-resolve on drvPath
|
||||
const auto outputMap = store.queryPartialDerivationOutputMap(resolveDerivedPath(store, *drvPath));
|
||||
res["output"] = output;
|
||||
auto outputPathIter = outputMap.find(output);
|
||||
if (outputPathIter == outputMap.end())
|
||||
res["outputPath"] = nullptr;
|
||||
else if (std::optional p = outputPathIter->second)
|
||||
res["outputPath"] = store.printStorePath(*p);
|
||||
else
|
||||
res["outputPath"] = nullptr;
|
||||
return res;
|
||||
}
|
||||
|
||||
nlohmann::json DerivedPath::Built::toJSON(ref<Store> store) const {
|
||||
nlohmann::json DerivedPath::Built::toJSON(Store & store) const {
|
||||
nlohmann::json res;
|
||||
res["drvPath"] = store->printStorePath(drvPath);
|
||||
res["drvPath"] = drvPath->toJSON(store);
|
||||
// Fallback for the input-addressed derivation case: We expect to always be
|
||||
// able to print the output paths, so let’s do it
|
||||
const auto outputMap = store->queryPartialDerivationOutputMap(drvPath);
|
||||
// FIXME try-resolve on drvPath
|
||||
const auto outputMap = store.queryPartialDerivationOutputMap(resolveDerivedPath(store, *drvPath));
|
||||
for (const auto & [output, outputPathOpt] : outputMap) {
|
||||
if (!outputs.contains(output)) continue;
|
||||
if (outputPathOpt)
|
||||
res["outputs"][output] = store->printStorePath(*outputPathOpt);
|
||||
res["outputs"][output] = store.printStorePath(*outputPathOpt);
|
||||
else
|
||||
res["outputs"][output] = nullptr;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
nlohmann::json BuiltPath::Built::toJSON(ref<Store> store) const {
|
||||
nlohmann::json res;
|
||||
res["drvPath"] = store->printStorePath(drvPath);
|
||||
for (const auto& [output, path] : outputs) {
|
||||
res["outputs"][output] = store->printStorePath(path);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
StorePathSet BuiltPath::outPaths() const
|
||||
nlohmann::json SingleDerivedPath::toJSON(Store & store) const
|
||||
{
|
||||
return std::visit(
|
||||
overloaded{
|
||||
[](const BuiltPath::Opaque & p) { return StorePathSet{p.path}; },
|
||||
[](const BuiltPath::Built & b) {
|
||||
StorePathSet res;
|
||||
for (auto & [_, path] : b.outputs)
|
||||
res.insert(path);
|
||||
return res;
|
||||
},
|
||||
}, raw()
|
||||
);
|
||||
return std::visit([&](const auto & buildable) {
|
||||
return buildable.toJSON(store);
|
||||
}, raw());
|
||||
}
|
||||
|
||||
std::string DerivedPath::Opaque::to_string(const Store & store) const
|
||||
nlohmann::json DerivedPath::toJSON(Store & store) const
|
||||
{
|
||||
return std::visit([&](const auto & buildable) {
|
||||
return buildable.toJSON(store);
|
||||
}, raw());
|
||||
}
|
||||
|
||||
std::string DerivedPath::Opaque::to_string(const StoreDirConfig & store) const
|
||||
{
|
||||
return store.printStorePath(path);
|
||||
}
|
||||
|
||||
std::string DerivedPath::Built::to_string(const Store & store) const
|
||||
std::string SingleDerivedPath::Built::to_string(const StoreDirConfig & store) const
|
||||
{
|
||||
return store.printStorePath(drvPath)
|
||||
return drvPath->to_string(store) + "^" + output;
|
||||
}
|
||||
|
||||
std::string SingleDerivedPath::Built::to_string_legacy(const StoreDirConfig & store) const
|
||||
{
|
||||
return drvPath->to_string(store) + "!" + output;
|
||||
}
|
||||
|
||||
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 store.printStorePath(drvPath)
|
||||
+ '!'
|
||||
return drvPath->to_string_legacy(store)
|
||||
+ "!"
|
||||
+ outputs.to_string();
|
||||
}
|
||||
|
||||
std::string DerivedPath::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 StoreDirConfig & store) const
|
||||
{
|
||||
return std::visit(
|
||||
[&](const auto & req) { return req.to_string(store); },
|
||||
raw());
|
||||
}
|
||||
|
||||
std::string SingleDerivedPath::to_string_legacy(const StoreDirConfig & store) const
|
||||
{
|
||||
return std::visit(overloaded {
|
||||
[&](const DerivedPath::Built & req) { return req.to_string(store); },
|
||||
[&](const DerivedPath::Opaque & req) { return req.to_string(store); },
|
||||
[&](const SingleDerivedPath::Built & req) { return req.to_string_legacy(store); },
|
||||
[&](const SingleDerivedPath::Opaque & req) { return req.to_string(store); },
|
||||
}, 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); },
|
||||
|
@ -90,66 +143,161 @@ 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)};
|
||||
}
|
||||
|
||||
DerivedPath::Built DerivedPath::Built::parse(const Store & store, std::string_view drvS, std::string_view outputsS)
|
||||
void drvRequireExperiment(
|
||||
const SingleDerivedPath & drv,
|
||||
const ExperimentalFeatureSettings & xpSettings)
|
||||
{
|
||||
std::visit(overloaded {
|
||||
[&](const SingleDerivedPath::Opaque &) {
|
||||
// plain drv path; no experimental features required.
|
||||
},
|
||||
[&](const SingleDerivedPath::Built &) {
|
||||
xpSettings.require(Xp::DynamicDerivations);
|
||||
},
|
||||
}, drv.raw());
|
||||
}
|
||||
|
||||
SingleDerivedPath::Built SingleDerivedPath::Built::parse(
|
||||
const StoreDirConfig & store, ref<SingleDerivedPath> drv,
|
||||
OutputNameView output,
|
||||
const ExperimentalFeatureSettings & xpSettings)
|
||||
{
|
||||
drvRequireExperiment(*drv, xpSettings);
|
||||
return {
|
||||
.drvPath = store.parseStorePath(drvS),
|
||||
.drvPath = drv,
|
||||
.output = std::string { output },
|
||||
};
|
||||
}
|
||||
|
||||
DerivedPath::Built DerivedPath::Built::parse(
|
||||
const StoreDirConfig & store, ref<SingleDerivedPath> drv,
|
||||
OutputNameView outputsS,
|
||||
const ExperimentalFeatureSettings & xpSettings)
|
||||
{
|
||||
drvRequireExperiment(*drv, xpSettings);
|
||||
return {
|
||||
.drvPath = drv,
|
||||
.outputs = OutputsSpec::parse(outputsS),
|
||||
};
|
||||
}
|
||||
|
||||
static inline DerivedPath parseWith(const Store & store, std::string_view s, std::string_view separator)
|
||||
static SingleDerivedPath parseWithSingle(
|
||||
const StoreDirConfig & store, std::string_view s, std::string_view separator,
|
||||
const ExperimentalFeatureSettings & xpSettings)
|
||||
{
|
||||
size_t n = s.find(separator);
|
||||
size_t n = s.rfind(separator);
|
||||
return n == s.npos
|
||||
? (SingleDerivedPath) SingleDerivedPath::Opaque::parse(store, s)
|
||||
: (SingleDerivedPath) SingleDerivedPath::Built::parse(store,
|
||||
make_ref<SingleDerivedPath>(parseWithSingle(
|
||||
store,
|
||||
s.substr(0, n),
|
||||
separator,
|
||||
xpSettings)),
|
||||
s.substr(n + 1),
|
||||
xpSettings);
|
||||
}
|
||||
|
||||
SingleDerivedPath SingleDerivedPath::parse(
|
||||
const StoreDirConfig & store,
|
||||
std::string_view s,
|
||||
const ExperimentalFeatureSettings & xpSettings)
|
||||
{
|
||||
return parseWithSingle(store, s, "^", xpSettings);
|
||||
}
|
||||
|
||||
SingleDerivedPath SingleDerivedPath::parseLegacy(
|
||||
const StoreDirConfig & store,
|
||||
std::string_view s,
|
||||
const ExperimentalFeatureSettings & xpSettings)
|
||||
{
|
||||
return parseWithSingle(store, s, "!", xpSettings);
|
||||
}
|
||||
|
||||
static DerivedPath parseWith(
|
||||
const StoreDirConfig & store, std::string_view s, std::string_view separator,
|
||||
const ExperimentalFeatureSettings & xpSettings)
|
||||
{
|
||||
size_t n = s.rfind(separator);
|
||||
return n == s.npos
|
||||
? (DerivedPath) DerivedPath::Opaque::parse(store, s)
|
||||
: (DerivedPath) DerivedPath::Built::parse(store, s.substr(0, n), s.substr(n + 1));
|
||||
: (DerivedPath) DerivedPath::Built::parse(store,
|
||||
make_ref<SingleDerivedPath>(parseWithSingle(
|
||||
store,
|
||||
s.substr(0, n),
|
||||
separator,
|
||||
xpSettings)),
|
||||
s.substr(n + 1),
|
||||
xpSettings);
|
||||
}
|
||||
|
||||
DerivedPath DerivedPath::parse(const Store & store, std::string_view s)
|
||||
DerivedPath DerivedPath::parse(
|
||||
const StoreDirConfig & store,
|
||||
std::string_view s,
|
||||
const ExperimentalFeatureSettings & xpSettings)
|
||||
{
|
||||
return parseWith(store, s, "^");
|
||||
return parseWith(store, s, "^", xpSettings);
|
||||
}
|
||||
|
||||
DerivedPath DerivedPath::parseLegacy(const Store & store, std::string_view s)
|
||||
DerivedPath DerivedPath::parseLegacy(
|
||||
const StoreDirConfig & store,
|
||||
std::string_view s,
|
||||
const ExperimentalFeatureSettings & xpSettings)
|
||||
{
|
||||
return parseWith(store, s, "!");
|
||||
return parseWith(store, s, "!", xpSettings);
|
||||
}
|
||||
|
||||
RealisedPath::Set BuiltPath::toRealisedPaths(Store & store) const
|
||||
DerivedPath DerivedPath::fromSingle(const SingleDerivedPath & req)
|
||||
{
|
||||
RealisedPath::Set res;
|
||||
std::visit(
|
||||
overloaded{
|
||||
[&](const BuiltPath::Opaque & p) { res.insert(p.path); },
|
||||
[&](const BuiltPath::Built & p) {
|
||||
auto drvHashes =
|
||||
staticOutputHashes(store, store.readDerivation(p.drvPath));
|
||||
for (auto& [outputName, outputPath] : p.outputs) {
|
||||
if (experimentalFeatureSettings.isEnabled(
|
||||
Xp::CaDerivations)) {
|
||||
auto drvOutput = get(drvHashes, outputName);
|
||||
if (!drvOutput)
|
||||
throw Error(
|
||||
"the derivation '%s' has unrealised output '%s' (derived-path.cc/toRealisedPaths)",
|
||||
store.printStorePath(p.drvPath), outputName);
|
||||
auto thisRealisation = store.queryRealisation(
|
||||
DrvOutput{*drvOutput, outputName});
|
||||
assert(thisRealisation); // We’ve built it, so we must
|
||||
// have the realisation
|
||||
res.insert(*thisRealisation);
|
||||
} else {
|
||||
res.insert(outputPath);
|
||||
}
|
||||
}
|
||||
},
|
||||
return std::visit(overloaded {
|
||||
[&](const SingleDerivedPath::Opaque & o) -> DerivedPath {
|
||||
return o;
|
||||
},
|
||||
raw());
|
||||
return res;
|
||||
[&](const SingleDerivedPath::Built & b) -> DerivedPath {
|
||||
return DerivedPath::Built {
|
||||
.drvPath = b.drvPath,
|
||||
.outputs = OutputsSpec::Names { b.output },
|
||||
};
|
||||
},
|
||||
}, req.raw());
|
||||
}
|
||||
|
||||
const StorePath & SingleDerivedPath::Built::getBaseStorePath() const
|
||||
{
|
||||
return drvPath->getBaseStorePath();
|
||||
}
|
||||
|
||||
const StorePath & DerivedPath::Built::getBaseStorePath() const
|
||||
{
|
||||
return drvPath->getBaseStorePath();
|
||||
}
|
||||
|
||||
template<typename DP>
|
||||
static inline const StorePath & getBaseStorePath_(const DP & derivedPath)
|
||||
{
|
||||
return std::visit(overloaded {
|
||||
[&](const typename DP::Built & bfd) -> auto & {
|
||||
return bfd.drvPath->getBaseStorePath();
|
||||
},
|
||||
[&](const typename DP::Opaque & bo) -> auto & {
|
||||
return bo.path;
|
||||
},
|
||||
}, derivedPath.raw());
|
||||
}
|
||||
|
||||
const StorePath & SingleDerivedPath::getBaseStorePath() const
|
||||
{
|
||||
return getBaseStorePath_(*this);
|
||||
}
|
||||
|
||||
const StorePath & DerivedPath::getBaseStorePath() const
|
||||
{
|
||||
return getBaseStorePath_(*this);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
#pragma once
|
||||
///@file
|
||||
|
||||
#include "util.hh"
|
||||
#include "path.hh"
|
||||
#include "realisation.hh"
|
||||
#include "outputs-spec.hh"
|
||||
#include "comparator.hh"
|
||||
#include "config.hh"
|
||||
|
||||
#include <variant>
|
||||
|
||||
|
@ -13,6 +12,9 @@
|
|||
|
||||
namespace nix {
|
||||
|
||||
struct StoreDirConfig;
|
||||
|
||||
// TODO stop needing this, `toJSON` below should be pure
|
||||
class Store;
|
||||
|
||||
/**
|
||||
|
@ -25,15 +27,135 @@ class Store;
|
|||
struct DerivedPathOpaque {
|
||||
StorePath path;
|
||||
|
||||
nlohmann::json toJSON(ref<Store> store) const;
|
||||
std::string to_string(const Store & store) const;
|
||||
static DerivedPathOpaque parse(const Store & store, std::string_view);
|
||||
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);
|
||||
};
|
||||
|
||||
struct SingleDerivedPath;
|
||||
|
||||
/**
|
||||
* A derived path that is built from a derivation
|
||||
* A single derived path that is built from a derivation
|
||||
*
|
||||
* Built derived paths are pair of a derivation and an output name. They are
|
||||
* evaluated by building the derivation, and then taking the resulting output
|
||||
* path of the given output name.
|
||||
*/
|
||||
struct SingleDerivedPathBuilt {
|
||||
ref<SingleDerivedPath> drvPath;
|
||||
OutputName output;
|
||||
|
||||
/**
|
||||
* Get the store path this is ultimately derived from (by realising
|
||||
* and projecting outputs).
|
||||
*
|
||||
* Note that this is *not* a property of the store object being
|
||||
* referred to, but just of this path --- how we happened to be
|
||||
* referring to that store object. In other words, this means this
|
||||
* function breaks "referential transparency". It should therefore
|
||||
* be used only with great care.
|
||||
*/
|
||||
const StorePath & getBaseStorePath() const;
|
||||
|
||||
/**
|
||||
* Uses `^` as the separator
|
||||
*/
|
||||
std::string to_string(const StoreDirConfig & store) const;
|
||||
/**
|
||||
* Uses `!` as the separator
|
||||
*/
|
||||
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 StoreDirConfig & store, ref<SingleDerivedPath> drvPath,
|
||||
OutputNameView outputs,
|
||||
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
|
||||
nlohmann::json toJSON(Store & store) const;
|
||||
|
||||
DECLARE_CMP(SingleDerivedPathBuilt);
|
||||
};
|
||||
|
||||
using _SingleDerivedPathRaw = std::variant<
|
||||
DerivedPathOpaque,
|
||||
SingleDerivedPathBuilt
|
||||
>;
|
||||
|
||||
/**
|
||||
* A "derived path" is a very simple sort of expression (not a Nix
|
||||
* language expression! But an expression in a the general sense) that
|
||||
* evaluates to (concrete) store path. It is either:
|
||||
*
|
||||
* - opaque, in which case it is just a concrete store path with
|
||||
* possibly no known derivation
|
||||
*
|
||||
* - built, in which case it is a pair of a derivation path and an
|
||||
* output name.
|
||||
*/
|
||||
struct SingleDerivedPath : _SingleDerivedPathRaw {
|
||||
using Raw = _SingleDerivedPathRaw;
|
||||
using Raw::Raw;
|
||||
|
||||
using Opaque = DerivedPathOpaque;
|
||||
using Built = SingleDerivedPathBuilt;
|
||||
|
||||
inline const Raw & raw() const {
|
||||
return static_cast<const Raw &>(*this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the store path this is ultimately derived from (by realising
|
||||
* and projecting outputs).
|
||||
*
|
||||
* Note that this is *not* a property of the store object being
|
||||
* referred to, but just of this path --- how we happened to be
|
||||
* referring to that store object. In other words, this means this
|
||||
* function breaks "referential transparency". It should therefore
|
||||
* be used only with great care.
|
||||
*/
|
||||
const StorePath & getBaseStorePath() const;
|
||||
|
||||
/**
|
||||
* Uses `^` as the separator
|
||||
*/
|
||||
std::string to_string(const StoreDirConfig & store) const;
|
||||
/**
|
||||
* Uses `!` as the separator
|
||||
*/
|
||||
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 StoreDirConfig & store,
|
||||
std::string_view,
|
||||
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
|
||||
/**
|
||||
* Uses `!` as the separator
|
||||
*
|
||||
* @param xpSettings Stop-gap to avoid globals during unit tests.
|
||||
*/
|
||||
static SingleDerivedPath parseLegacy(
|
||||
const StoreDirConfig & store,
|
||||
std::string_view,
|
||||
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
|
||||
nlohmann::json toJSON(Store & store) const;
|
||||
};
|
||||
|
||||
static inline ref<SingleDerivedPath> makeConstantStorePathRef(StorePath drvPath)
|
||||
{
|
||||
return make_ref<SingleDerivedPath>(SingleDerivedPath::Opaque { drvPath });
|
||||
}
|
||||
|
||||
/**
|
||||
* A set of derived paths that are built from a derivation
|
||||
*
|
||||
* Built derived paths are pair of a derivation and some output names.
|
||||
* They are evaluated by building the derivation, and then replacing the
|
||||
|
@ -45,24 +167,41 @@ struct DerivedPathOpaque {
|
|||
* output name.
|
||||
*/
|
||||
struct DerivedPathBuilt {
|
||||
StorePath drvPath;
|
||||
ref<SingleDerivedPath> drvPath;
|
||||
OutputsSpec outputs;
|
||||
|
||||
/**
|
||||
* Get the store path this is ultimately derived from (by realising
|
||||
* and projecting outputs).
|
||||
*
|
||||
* Note that this is *not* a property of the store object being
|
||||
* referred to, but just of this path --- how we happened to be
|
||||
* referring to that store object. In other words, this means this
|
||||
* function breaks "referential transparency". It should therefore
|
||||
* be used only with great care.
|
||||
*/
|
||||
const StorePath & getBaseStorePath() const;
|
||||
|
||||
/**
|
||||
* 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, std::string_view drvPath, std::string_view outputs);
|
||||
nlohmann::json toJSON(ref<Store> store) const;
|
||||
static DerivedPathBuilt parse(
|
||||
const StoreDirConfig & store, ref<SingleDerivedPath>,
|
||||
std::string_view,
|
||||
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
|
||||
nlohmann::json toJSON(Store & store) const;
|
||||
|
||||
GENERATE_CMP(DerivedPathBuilt, me->drvPath, me->outputs);
|
||||
DECLARE_CMP(DerivedPathBuilt);
|
||||
};
|
||||
|
||||
using _DerivedPathRaw = std::variant<
|
||||
|
@ -72,13 +211,13 @@ using _DerivedPathRaw = std::variant<
|
|||
|
||||
/**
|
||||
* A "derived path" is a very simple sort of expression that evaluates
|
||||
* to (concrete) store path. It is either:
|
||||
* to one or more (concrete) store paths. It is either:
|
||||
*
|
||||
* - opaque, in which case it is just a concrete store path with
|
||||
* - opaque, in which case it is just a single concrete store path with
|
||||
* possibly no known derivation
|
||||
*
|
||||
* - built, in which case it is a pair of a derivation path and an
|
||||
* output name.
|
||||
* - built, in which case it is a pair of a derivation path and some
|
||||
* output names.
|
||||
*/
|
||||
struct DerivedPath : _DerivedPathRaw {
|
||||
using Raw = _DerivedPathRaw;
|
||||
|
@ -92,64 +231,64 @@ struct DerivedPath : _DerivedPathRaw {
|
|||
}
|
||||
|
||||
/**
|
||||
* Uses `^` as the separator
|
||||
* Get the store path this is ultimately derived from (by realising
|
||||
* and projecting outputs).
|
||||
*
|
||||
* Note that this is *not* a property of the store object being
|
||||
* referred to, but just of this path --- how we happened to be
|
||||
* referring to that store object. In other words, this means this
|
||||
* function breaks "referential transparency". It should therefore
|
||||
* be used only with great care.
|
||||
*/
|
||||
std::string to_string(const Store & store) const;
|
||||
/**
|
||||
* Uses `!` as the separator
|
||||
*/
|
||||
std::string to_string_legacy(const Store & store) const;
|
||||
const StorePath & getBaseStorePath() const;
|
||||
|
||||
/**
|
||||
* Uses `^` as the separator
|
||||
*/
|
||||
static DerivedPath parse(const Store & store, std::string_view);
|
||||
std::string to_string(const StoreDirConfig & store) const;
|
||||
/**
|
||||
* Uses `!` as the separator
|
||||
*/
|
||||
static DerivedPath parseLegacy(const Store & store, std::string_view);
|
||||
};
|
||||
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 StoreDirConfig & store,
|
||||
std::string_view,
|
||||
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
|
||||
/**
|
||||
* Uses `!` as the separator
|
||||
*
|
||||
* @param xpSettings Stop-gap to avoid globals during unit tests.
|
||||
*/
|
||||
static DerivedPath parseLegacy(
|
||||
const StoreDirConfig & store,
|
||||
std::string_view,
|
||||
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
|
||||
|
||||
/**
|
||||
* A built derived path with hints in the form of optional concrete output paths.
|
||||
*
|
||||
* See 'BuiltPath' for more an explanation.
|
||||
*/
|
||||
struct BuiltPathBuilt {
|
||||
StorePath drvPath;
|
||||
std::map<std::string, StorePath> outputs;
|
||||
|
||||
nlohmann::json toJSON(ref<Store> store) const;
|
||||
static BuiltPathBuilt parse(const Store & store, std::string_view);
|
||||
|
||||
GENERATE_CMP(BuiltPathBuilt, me->drvPath, me->outputs);
|
||||
};
|
||||
|
||||
using _BuiltPathRaw = std::variant<
|
||||
DerivedPath::Opaque,
|
||||
BuiltPathBuilt
|
||||
>;
|
||||
|
||||
/**
|
||||
* A built path. Similar to a DerivedPath, but enriched with the corresponding
|
||||
* output path(s).
|
||||
*/
|
||||
struct BuiltPath : _BuiltPathRaw {
|
||||
using Raw = _BuiltPathRaw;
|
||||
using Raw::Raw;
|
||||
|
||||
using Opaque = DerivedPathOpaque;
|
||||
using Built = BuiltPathBuilt;
|
||||
|
||||
inline const Raw & raw() const {
|
||||
return static_cast<const Raw &>(*this);
|
||||
}
|
||||
|
||||
StorePathSet outPaths() const;
|
||||
RealisedPath::Set toRealisedPaths(Store & store) const;
|
||||
/**
|
||||
* Convert a `SingleDerivedPath` to a `DerivedPath`.
|
||||
*/
|
||||
static DerivedPath fromSingle(const SingleDerivedPath &);
|
||||
|
||||
nlohmann::json toJSON(Store & store) const;
|
||||
};
|
||||
|
||||
typedef std::vector<DerivedPath> DerivedPaths;
|
||||
typedef std::vector<BuiltPath> BuiltPaths;
|
||||
|
||||
/**
|
||||
* Used by various parser functions to require experimental features as
|
||||
* needed.
|
||||
*
|
||||
* Somewhat unfortunate this cannot just be an implementation detail for
|
||||
* this module.
|
||||
*
|
||||
* @param xpSettings Stop-gap to avoid globals during unit tests.
|
||||
*/
|
||||
void drvRequireExperiment(
|
||||
const SingleDerivedPath & drv,
|
||||
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
|
||||
}
|
||||
|
|
|
@ -5,35 +5,54 @@ namespace nix {
|
|||
|
||||
std::string DownstreamPlaceholder::render() const
|
||||
{
|
||||
return "/" + hash.to_string(Base32, false);
|
||||
return "/" + hash.to_string(HashFormat::Nix32, false);
|
||||
}
|
||||
|
||||
|
||||
DownstreamPlaceholder DownstreamPlaceholder::unknownCaOutput(
|
||||
const StorePath & drvPath,
|
||||
std::string_view outputName)
|
||||
OutputNameView outputName,
|
||||
const ExperimentalFeatureSettings & xpSettings)
|
||||
{
|
||||
xpSettings.require(Xp::CaDerivations);
|
||||
auto drvNameWithExtension = drvPath.name();
|
||||
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)
|
||||
};
|
||||
}
|
||||
|
||||
DownstreamPlaceholder DownstreamPlaceholder::unknownDerivation(
|
||||
const DownstreamPlaceholder & placeholder,
|
||||
std::string_view outputName,
|
||||
OutputNameView outputName,
|
||||
const ExperimentalFeatureSettings & xpSettings)
|
||||
{
|
||||
xpSettings.require(Xp::DynamicDerivations);
|
||||
auto compressed = compressHash(placeholder.hash, 20);
|
||||
auto clearText = "nix-computed-output:"
|
||||
+ compressed.to_string(Base32, false)
|
||||
+ compressed.to_string(HashFormat::Nix32, false)
|
||||
+ ":" + std::string { outputName };
|
||||
return DownstreamPlaceholder {
|
||||
hashString(htSHA256, clearText)
|
||||
hashString(HashAlgorithm::SHA256, clearText)
|
||||
};
|
||||
}
|
||||
|
||||
DownstreamPlaceholder DownstreamPlaceholder::fromSingleDerivedPathBuilt(
|
||||
const SingleDerivedPath::Built & b,
|
||||
const ExperimentalFeatureSettings & xpSettings)
|
||||
{
|
||||
return std::visit(overloaded {
|
||||
[&](const SingleDerivedPath::Opaque & o) {
|
||||
return DownstreamPlaceholder::unknownCaOutput(o.path, b.output, xpSettings);
|
||||
},
|
||||
[&](const SingleDerivedPath::Built & b2) {
|
||||
return DownstreamPlaceholder::unknownDerivation(
|
||||
DownstreamPlaceholder::fromSingleDerivedPathBuilt(b2, xpSettings),
|
||||
b.output,
|
||||
xpSettings);
|
||||
},
|
||||
}, b.drvPath->raw());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include "hash.hh"
|
||||
#include "path.hh"
|
||||
#include "derived-path.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
@ -52,10 +53,13 @@ public:
|
|||
*
|
||||
* The derivation itself is known (we have a store path for it), but
|
||||
* the output doesn't yet have a known store path.
|
||||
*
|
||||
* @param xpSettings Stop-gap to avoid globals during unit tests.
|
||||
*/
|
||||
static DownstreamPlaceholder unknownCaOutput(
|
||||
const StorePath & drvPath,
|
||||
std::string_view outputName);
|
||||
OutputNameView outputName,
|
||||
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
|
||||
|
||||
/**
|
||||
* Create a placehold for the output of an unknown derivation.
|
||||
|
@ -68,7 +72,19 @@ public:
|
|||
*/
|
||||
static DownstreamPlaceholder unknownDerivation(
|
||||
const DownstreamPlaceholder & drvPlaceholder,
|
||||
std::string_view outputName,
|
||||
OutputNameView outputName,
|
||||
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
|
||||
|
||||
/**
|
||||
* Convenience constructor that handles both cases (unknown
|
||||
* content-addressed output and unknown derivation), delegating as
|
||||
* needed to `unknownCaOutput` and `unknownDerivation`.
|
||||
*
|
||||
* Recursively builds up a placeholder from a
|
||||
* `SingleDerivedPath::Built.drvPath` chain.
|
||||
*/
|
||||
static DownstreamPlaceholder fromSingleDerivedPathBuilt(
|
||||
const SingleDerivedPath::Built & built,
|
||||
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
|
||||
};
|
||||
|
||||
|
|
|
@ -58,13 +58,6 @@ struct DummyStore : public virtual DummyStoreConfig, public virtual Store
|
|||
RepairFlag repair, CheckSigsFlag checkSigs) override
|
||||
{ unsupported("addToStore"); }
|
||||
|
||||
StorePath addTextToStore(
|
||||
std::string_view name,
|
||||
std::string_view s,
|
||||
const StorePathSet & references,
|
||||
RepairFlag repair) override
|
||||
{ unsupported("addTextToStore"); }
|
||||
|
||||
void narFromPath(const StorePath & path, Sink & sink) override
|
||||
{ unsupported("narFromPath"); }
|
||||
|
||||
|
@ -72,7 +65,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"); }
|
||||
};
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
#include "serialise.hh"
|
||||
#include "store-api.hh"
|
||||
#include "archive.hh"
|
||||
#include "worker-protocol.hh"
|
||||
#include "common-protocol.hh"
|
||||
#include "common-protocol-impl.hh"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
|
@ -29,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);
|
||||
|
@ -38,14 +39,16 @@ 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(Base32, true), hash.to_string(Base32, true));
|
||||
printStorePath(path), info->narHash.to_string(HashFormat::Nix32, true), hash.to_string(HashFormat::Nix32, true));
|
||||
|
||||
teeSink
|
||||
<< exportMagic
|
||||
<< printStorePath(path);
|
||||
workerProtoWrite(*this, teeSink, info->references);
|
||||
CommonProto::write(*this,
|
||||
CommonProto::WriteConn { .to = teeSink },
|
||||
info->references);
|
||||
teeSink
|
||||
<< (info->deriver ? printStorePath(*info->deriver) : "")
|
||||
<< 0;
|
||||
|
@ -62,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);
|
||||
|
@ -73,9 +76,10 @@ StorePaths Store::importPaths(Source & source, CheckSigsFlag checkSigs)
|
|||
|
||||
//Activity act(*logger, lvlInfo, "importing path '%s'", info.path);
|
||||
|
||||
auto references = WorkerProto<StorePathSet>::read(*this, source);
|
||||
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 != "")
|
||||
|
|
|
@ -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>
|
||||
|
@ -863,6 +864,8 @@ void FileTransfer::download(FileTransferRequest && request, Sink & sink)
|
|||
}
|
||||
|
||||
chunk = std::move(state->data);
|
||||
/* Reset state->data after the move, since we check data.empty() */
|
||||
state->data = "";
|
||||
|
||||
state->request.notify_one();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -71,19 +71,36 @@ struct GCResults
|
|||
};
|
||||
|
||||
|
||||
/**
|
||||
* Mix-in class for \ref Store "stores" which expose a notion of garbage
|
||||
* collection.
|
||||
*
|
||||
* Garbage collection will allow deleting paths which are not
|
||||
* transitively "rooted".
|
||||
*
|
||||
* The notion of GC roots actually not part of this class.
|
||||
*
|
||||
* - The base `Store` class has `Store::addTempRoot()` because for a store
|
||||
* that doesn't support garbage collection at all, a temporary GC root is
|
||||
* safely implementable as no-op.
|
||||
*
|
||||
* @todo actually this is not so good because stores are *views*.
|
||||
* Some views have only a no-op temp roots even though others to the
|
||||
* same store allow triggering GC. For instance one can't add a root
|
||||
* over ssh, but that doesn't prevent someone from gc-ing that store
|
||||
* accesed via SSH locally).
|
||||
*
|
||||
* - The derived `LocalFSStore` class has `LocalFSStore::addPermRoot`,
|
||||
* which is not part of this class because it relies on the notion of
|
||||
* an ambient file system. There are stores (`ssh-ng://`, for one),
|
||||
* that *do* support garbage collection but *don't* expose any file
|
||||
* system, and `LocalFSStore::addPermRoot` thus does not make sense
|
||||
* for them.
|
||||
*/
|
||||
struct GcStore : public virtual Store
|
||||
{
|
||||
inline static std::string operationName = "Garbage collection";
|
||||
|
||||
/**
|
||||
* Add an indirect root, which is merely a symlink to `path` from
|
||||
* `/nix/var/nix/gcroots/auto/<hash of path>`. `path` is supposed
|
||||
* to be a symlink to a store path. The garbage collector will
|
||||
* automatically remove the indirect root when it finds that
|
||||
* `path` has disappeared.
|
||||
*/
|
||||
virtual void addIndirectRoot(const Path & path) = 0;
|
||||
|
||||
/**
|
||||
* Find the roots of the garbage collector. Each root is a pair
|
||||
* `(link, storepath)` where `link` is the path of the symlink
|
||||
|
|
|
@ -1,8 +1,14 @@
|
|||
#include "derivations.hh"
|
||||
#include "globals.hh"
|
||||
#include "local-store.hh"
|
||||
#include "local-fs-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>
|
||||
|
@ -44,13 +50,13 @@ static void makeSymlink(const Path & link, const Path & target)
|
|||
|
||||
void LocalStore::addIndirectRoot(const Path & path)
|
||||
{
|
||||
std::string hash = hashString(htSHA1, path).to_string(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);
|
||||
}
|
||||
|
||||
|
||||
Path LocalFSStore::addPermRoot(const StorePath & storePath, const Path & _gcRoot)
|
||||
Path IndirectRootStore::addPermRoot(const StorePath & storePath, const Path & _gcRoot)
|
||||
{
|
||||
Path gcRoot(canonPath(_gcRoot));
|
||||
|
||||
|
@ -110,6 +116,11 @@ void LocalStore::createTempRootsFile()
|
|||
|
||||
void LocalStore::addTempRoot(const StorePath & path)
|
||||
{
|
||||
if (readOnly) {
|
||||
debug("Read-only store doesn't support creating lock files for temp roots, but nothing can be deleted anyways.");
|
||||
return;
|
||||
}
|
||||
|
||||
createTempRootsFile();
|
||||
|
||||
/* Open/create the global GC lock file. */
|
||||
|
@ -141,7 +152,7 @@ void LocalStore::addTempRoot(const StorePath & path)
|
|||
/* The garbage collector may have exited or not
|
||||
created the socket yet, so we need to restart. */
|
||||
if (e.errNo == ECONNREFUSED || e.errNo == ENOENT) {
|
||||
debug("GC socket connection refused: %s", e.msg())
|
||||
debug("GC socket connection refused: %s", e.msg());
|
||||
fdRootsSocket->close();
|
||||
goto restart;
|
||||
}
|
||||
|
@ -319,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) {
|
||||
|
@ -330,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)]
|
||||
|
@ -568,7 +574,7 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
|
|||
/* On macOS, accepted sockets inherit the
|
||||
non-blocking flag from the server socket, so
|
||||
explicitly make it blocking. */
|
||||
if (fcntl(fdServer.get(), F_SETFL, fcntl(fdServer.get(), F_GETFL) & ~O_NONBLOCK) == -1)
|
||||
if (fcntl(fdClient.get(), F_SETFL, fcntl(fdClient.get(), F_GETFL) & ~O_NONBLOCK) == -1)
|
||||
abort();
|
||||
|
||||
while (true) {
|
||||
|
@ -777,7 +783,7 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
|
|||
}
|
||||
};
|
||||
|
||||
/* Synchronisation point for testing, see tests/gc-non-blocking.sh. */
|
||||
/* Synchronisation point for testing, see tests/functional/gc-non-blocking.sh. */
|
||||
if (auto p = getEnv("_NIX_TEST_GC_SYNC"))
|
||||
readFile(*p);
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
@ -14,16 +15,21 @@
|
|||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#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"
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <sys/sysctl.h>
|
||||
#endif
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
@ -100,12 +106,22 @@ Settings::Settings()
|
|||
if (!pathExists(nixExePath)) {
|
||||
nixExePath = getSelfExe().value_or("nix");
|
||||
}
|
||||
buildHook = nixExePath + " __build-remote";
|
||||
buildHook = {
|
||||
nixExePath,
|
||||
"__build-remote",
|
||||
};
|
||||
}
|
||||
|
||||
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. */
|
||||
|
@ -113,7 +129,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");
|
||||
|
@ -151,6 +167,29 @@ unsigned int Settings::getDefaultCores()
|
|||
return concurrency;
|
||||
}
|
||||
|
||||
#if __APPLE__
|
||||
static bool hasVirt() {
|
||||
|
||||
int hasVMM;
|
||||
int hvSupport;
|
||||
size_t size;
|
||||
|
||||
size = sizeof(hasVMM);
|
||||
if (sysctlbyname("kern.hv_vmm_present", &hasVMM, &size, NULL, 0) == 0) {
|
||||
if (hasVMM)
|
||||
return false;
|
||||
}
|
||||
|
||||
// whether the kernel and hardware supports virt
|
||||
size = sizeof(hvSupport);
|
||||
if (sysctlbyname("kern.hv_support", &hvSupport, &size, NULL, 0) == 0) {
|
||||
return hvSupport == 1;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
StringSet Settings::getDefaultSystemFeatures()
|
||||
{
|
||||
/* For backwards compatibility, accept some "features" that are
|
||||
|
@ -167,6 +206,11 @@ StringSet Settings::getDefaultSystemFeatures()
|
|||
features.insert("kvm");
|
||||
#endif
|
||||
|
||||
#if __APPLE__
|
||||
if (hasVirt())
|
||||
features.insert("apple-virt");
|
||||
#endif
|
||||
|
||||
return features;
|
||||
}
|
||||
|
||||
|
@ -363,9 +407,6 @@ void initLibStore() {
|
|||
|
||||
initLibUtil();
|
||||
|
||||
if (sodium_init() == -1)
|
||||
throw Error("could not initialise libsodium");
|
||||
|
||||
loadConfFile();
|
||||
|
||||
preloadNSS();
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
|
||||
#include "types.hh"
|
||||
#include "config.hh"
|
||||
#include "util.hh"
|
||||
#include "environment-variables.hh"
|
||||
#include "experimental-features.hh"
|
||||
|
||||
#include <map>
|
||||
#include <limits>
|
||||
|
@ -116,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",
|
||||
|
@ -142,7 +144,7 @@ public:
|
|||
*/
|
||||
bool verboseBuild = true;
|
||||
|
||||
Setting<size_t> logLines{this, 10, "log-lines",
|
||||
Setting<size_t> logLines{this, 25, "log-lines",
|
||||
"The number of lines of the tail of "
|
||||
"the log to show if a build fails."};
|
||||
|
||||
|
@ -182,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
|
||||
|
@ -193,18 +197,28 @@ public:
|
|||
Setting<std::string> thisSystem{
|
||||
this, SYSTEM, "system",
|
||||
R"(
|
||||
This option specifies the canonical Nix system name of the current
|
||||
installation, such as `i686-linux` or `x86_64-darwin`. Nix can only
|
||||
build derivations whose `system` attribute equals the value
|
||||
specified here. In general, it never makes sense to modify this
|
||||
value from its default, since you can use it to ‘lie’ about the
|
||||
platform you are building on (e.g., perform a Mac OS build on a
|
||||
Linux machine; the result would obviously be wrong). It only makes
|
||||
sense if the Nix binaries can run on multiple platforms, e.g.,
|
||||
‘universal binaries’ that run on `x86_64-linux` and `i686-linux`.
|
||||
The system type of the current Nix installation.
|
||||
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).
|
||||
|
||||
It defaults to the canonical Nix system name detected by `configure`
|
||||
at build time.
|
||||
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:
|
||||
|
||||
- `x86_64-linux`
|
||||
- `x86_64-darwin`
|
||||
- `i686-linux`
|
||||
- `aarch64-linux`
|
||||
- `aarch64-darwin`
|
||||
- `armv6l-linux`
|
||||
- `armv7l-linux`
|
||||
|
||||
In general, you do not have to modify this setting.
|
||||
While you can force Nix to run a Darwin-specific `builder` executable on a Linux machine, the result would obviously be wrong.
|
||||
|
||||
This value is available in the Nix language as
|
||||
[`builtins.currentSystem`](@docroot@/language/builtin-constants.md#builtins-currentSystem)
|
||||
if the
|
||||
[`eval-system`](#conf-eval-system)
|
||||
configuration option is set as the empty string.
|
||||
)"};
|
||||
|
||||
Setting<time_t> maxSilentTime{
|
||||
|
@ -236,7 +250,7 @@ public:
|
|||
)",
|
||||
{"build-timeout"}};
|
||||
|
||||
PathSetting buildHook{this, true, "", "build-hook",
|
||||
Setting<Strings> buildHook{this, {}, "build-hook",
|
||||
R"(
|
||||
The path to the helper program that executes remote builds.
|
||||
|
||||
|
@ -255,16 +269,19 @@ public:
|
|||
For the exact format and examples, see [the manual chapter on remote builds](../advanced-topics/distributed-builds.md)
|
||||
)"};
|
||||
|
||||
Setting<bool> alwaysAllowSubstitutes{
|
||||
this, false, "always-allow-substitutes",
|
||||
R"(
|
||||
If set to `true`, Nix will ignore the [`allowSubstitutes`](@docroot@/language/advanced-attributes.md) attribute in derivations and always attempt to use [available substituters](#conf-substituters).
|
||||
)"};
|
||||
|
||||
Setting<bool> buildersUseSubstitutes{
|
||||
this, false, "builders-use-substitutes",
|
||||
R"(
|
||||
If set to `true`, Nix will instruct remote build machines to use
|
||||
their own binary substitutes if available. In practical terms, this
|
||||
means that remote hosts will fetch as many build dependencies as
|
||||
possible from their own substitutes (e.g, from `cache.nixos.org`),
|
||||
instead of waiting for this host to upload them all. This can
|
||||
drastically reduce build times if the network connection between
|
||||
this computer and the remote build host is slow.
|
||||
If set to `true`, Nix will instruct [remote build machines](#conf-builders) to use their own [`substituters`](#conf-substituters) if available.
|
||||
|
||||
It means that remote build hosts will fetch as many dependencies as possible from their own substituters (e.g, from `cache.nixos.org`) instead of waiting for the local machine to upload them all.
|
||||
This can drastically reduce build times if the network connection between the local machine and the remote build host is slow.
|
||||
)"};
|
||||
|
||||
Setting<off_t> reservedSize{this, 8 * 1024 * 1024, "gc-reserved-space",
|
||||
|
@ -337,7 +354,7 @@ public:
|
|||
users in `build-users-group`.
|
||||
|
||||
UIDs are allocated starting at 872415232 (0x34000000) on Linux and 56930 on macOS.
|
||||
)"};
|
||||
)", {}, true, Xp::AutoAllocateUids};
|
||||
|
||||
Setting<uint32_t> startId{this,
|
||||
#if __linux__
|
||||
|
@ -524,13 +541,31 @@ public:
|
|||
Setting<bool> sandboxFallback{this, true, "sandbox-fallback",
|
||||
"Whether to disable sandboxing when the kernel doesn't allow it."};
|
||||
|
||||
Setting<bool> requireDropSupplementaryGroups{this, getuid() == 0, "require-drop-supplementary-groups",
|
||||
R"(
|
||||
Following the principle of least privilege,
|
||||
Nix will attempt to drop supplementary groups when building with sandboxing.
|
||||
|
||||
However this can fail under some circumstances.
|
||||
For example, if the user lacks the `CAP_SETGID` capability.
|
||||
Search `setgroups(2)` for `EPERM` to find more detailed information on this.
|
||||
|
||||
If you encounter such a failure, setting this option to `false` will let you ignore it and continue.
|
||||
But before doing so, you should consider the security implications carefully.
|
||||
Not dropping supplementary groups means the build sandbox will be less restricted than intended.
|
||||
|
||||
This option defaults to `true` when the user is root
|
||||
(since `root` usually has permissions to call setgroups)
|
||||
and `false` otherwise.
|
||||
)"};
|
||||
|
||||
#if __linux__
|
||||
Setting<std::string> sandboxShmSize{
|
||||
this, "50%", "sandbox-dev-shm-size",
|
||||
R"(
|
||||
This option determines the maximum size of the `tmpfs` filesystem
|
||||
mounted on `/dev/shm` in Linux sandboxes. For the format, see the
|
||||
description of the `size` option of `tmpfs` in mount8. The default
|
||||
description of the `size` option of `tmpfs` in mount(8). The default
|
||||
is `50%`.
|
||||
)"};
|
||||
|
||||
|
@ -556,8 +591,8 @@ public:
|
|||
line.
|
||||
)"};
|
||||
|
||||
PathSetting diffHook{
|
||||
this, true, "", "diff-hook",
|
||||
OptionalPathSetting diffHook{
|
||||
this, std::nullopt, "diff-hook",
|
||||
R"(
|
||||
Absolute path to an executable capable of diffing build
|
||||
results. The hook is executed if `run-diff-hook` is true, and the
|
||||
|
@ -596,7 +631,7 @@ public:
|
|||
|
||||
At least one of the following condition must be met
|
||||
for Nix to accept copying a store object from another
|
||||
Nix store (such as a substituter):
|
||||
Nix store (such as a [substituter](#conf-substituters)):
|
||||
|
||||
- the store object has been signed using a key in the trusted keys list
|
||||
- the [`require-sigs`](#conf-require-sigs) option has been set to `false`
|
||||
|
@ -652,47 +687,80 @@ public:
|
|||
getDefaultExtraPlatforms(),
|
||||
"extra-platforms",
|
||||
R"(
|
||||
Platforms other than the native one which this machine is capable of
|
||||
building for. This can be useful for supporting additional
|
||||
architectures on compatible machines: i686-linux can be built on
|
||||
x86\_64-linux machines (and the default for this setting reflects
|
||||
this); armv7 is backwards-compatible with armv6 and armv5tel; some
|
||||
aarch64 machines can also natively run 32-bit ARM code; and
|
||||
qemu-user may be used to support non-native platforms (though this
|
||||
may be slow and buggy). Most values for this are not enabled by
|
||||
default because build systems will often misdetect the target
|
||||
platform and generate incompatible code, so you may wish to
|
||||
cross-check the results of using this option against proper
|
||||
natively-built versions of your derivations.
|
||||
)", {}, false};
|
||||
System types of executables that can be run on this machine.
|
||||
|
||||
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 the [`system` option](#conf-system).
|
||||
|
||||
Setting this can be useful to build derivations locally on compatible machines:
|
||||
- `i686-linux` executables can be run on `x86_64-linux` machines (set by default)
|
||||
- `x86_64-darwin` executables can be run on macOS `aarch64-darwin` with Rosetta 2 (set by default where applicable)
|
||||
- `armv6` and `armv5tel` executables can be run on `armv7`
|
||||
- some `aarch64` machines can also natively run 32-bit ARM code
|
||||
- `qemu-user` may be used to support non-native platforms (though this
|
||||
may be slow and buggy)
|
||||
|
||||
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.
|
||||
)",
|
||||
{},
|
||||
// Don't document the machine-specific default value
|
||||
false};
|
||||
|
||||
Setting<StringSet> systemFeatures{
|
||||
this,
|
||||
getDefaultSystemFeatures(),
|
||||
"system-features",
|
||||
R"(
|
||||
A set of system “features” supported by this machine, e.g. `kvm`.
|
||||
Derivations can express a dependency on such features through the
|
||||
derivation attribute `requiredSystemFeatures`. For example, the
|
||||
attribute
|
||||
A set of system “features” supported by this machine.
|
||||
|
||||
requiredSystemFeatures = [ "kvm" ];
|
||||
This complements the [`system`](#conf-system) and [`extra-platforms`](#conf-extra-platforms) configuration options and the corresponding [`system`](@docroot@/language/derivations.md#attr-system) attribute on derivations.
|
||||
|
||||
ensures that the derivation can only be built on a machine with the
|
||||
`kvm` feature.
|
||||
A derivation can require system features in the [`requiredSystemFeatures` attribute](@docroot@/language/advanced-attributes.md#adv-attr-requiredSystemFeatures), and the machine to build the derivation must have them.
|
||||
|
||||
This setting by default includes `kvm` if `/dev/kvm` is accessible,
|
||||
and the pseudo-features `nixos-test`, `benchmark` and `big-parallel`
|
||||
that are used in Nixpkgs to route builds to specific machines.
|
||||
)", {}, false};
|
||||
System features are user-defined, but Nix sets the following defaults:
|
||||
|
||||
- `apple-virt`
|
||||
|
||||
Included on Darwin if virtualization is available.
|
||||
|
||||
- `kvm`
|
||||
|
||||
Included on Linux if `/dev/kvm` is accessible.
|
||||
|
||||
- `nixos-test`, `benchmark`, `big-parallel`
|
||||
|
||||
These historical pseudo-features are always enabled for backwards compatibility, as they are used in Nixpkgs to route Hydra builds to specific machines.
|
||||
|
||||
- `ca-derivations`
|
||||
|
||||
Included by default if the [`ca-derivations` experimental feature](@docroot@/contributing/experimental-features.md#xp-feature-ca-derivations) is enabled.
|
||||
|
||||
This system feature is implicitly required by derivations with the [`__contentAddressed` attribute](@docroot@/language/advanced-attributes.md#adv-attr-__contentAddressed).
|
||||
|
||||
- `recursive-nix`
|
||||
|
||||
Included by default if the [`recursive-nix` experimental feature](@docroot@/contributing/experimental-features.md#xp-feature-recursive-nix) is enabled.
|
||||
|
||||
- `uid-range`
|
||||
|
||||
On Linux, Nix can run builds in a user namespace where they run as root (UID 0) and have 65,536 UIDs available.
|
||||
This is primarily useful for running containers such as `systemd-nspawn` inside a Nix build. For an example, see [`tests/systemd-nspawn/nix`][nspawn].
|
||||
|
||||
[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.
|
||||
)",
|
||||
{},
|
||||
// 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.
|
||||
|
@ -700,8 +768,8 @@ public:
|
|||
|
||||
At least one of the following conditions must be met for Nix to use a substituter:
|
||||
|
||||
- the substituter is in the [`trusted-substituters`](#conf-trusted-substituters) list
|
||||
- the user calling Nix is in the [`trusted-users`](#conf-trusted-users) list
|
||||
- The substituter is in the [`trusted-substituters`](#conf-trusted-substituters) list
|
||||
- The user calling Nix is in the [`trusted-users`](#conf-trusted-users) list
|
||||
|
||||
In addition, each store path should be trusted as described in [`trusted-public-keys`](#conf-trusted-public-keys)
|
||||
)",
|
||||
|
@ -710,12 +778,10 @@ public:
|
|||
Setting<StringSet> trustedSubstituters{
|
||||
this, {}, "trusted-substituters",
|
||||
R"(
|
||||
A list of [URLs of Nix stores](@docroot@/command-ref/new-cli/nix3-help-stores.md#store-url-format),
|
||||
separated by whitespace. These are
|
||||
not used by default, but can be enabled by users of the Nix daemon
|
||||
by specifying `--option substituters urls` on the command
|
||||
line. Unprivileged users are only allowed to pass a subset of the
|
||||
URLs listed in `substituters` and `trusted-substituters`.
|
||||
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`.
|
||||
)",
|
||||
{"trusted-binary-caches"}};
|
||||
|
||||
|
@ -880,7 +946,9 @@ public:
|
|||
may be useful in certain scenarios (e.g. to spin up containers or
|
||||
set up userspace network interfaces in tests).
|
||||
)"};
|
||||
#endif
|
||||
|
||||
#if HAVE_ACL_SUPPORT
|
||||
Setting<StringSet> ignoredAcls{
|
||||
this, {"security.selinux", "system.nfs4_acl", "security.csm"}, "ignored-acls",
|
||||
R"(
|
||||
|
@ -994,7 +1062,7 @@ public:
|
|||
| `~/.nix-defexpr` | `$XDG_STATE_HOME/nix/defexpr` |
|
||||
| `~/.nix-channels` | `$XDG_STATE_HOME/nix/channels` |
|
||||
|
||||
If you already have Nix installed and are using [profiles](@docroot@/package-management/profiles.md) or [channels](@docroot@/package-management/channels.md), you should migrate manually when you enable this option.
|
||||
If you already have Nix installed and are using [profiles](@docroot@/package-management/profiles.md) or [channels](@docroot@/command-ref/nix-channel.md), you should migrate manually when you enable this option.
|
||||
If `$XDG_STATE_HOME` is not set, use `$HOME/.local/state/nix` instead of `$XDG_STATE_HOME/nix`.
|
||||
This can be achieved with the following shell commands:
|
||||
|
||||
|
@ -1007,6 +1075,35 @@ public:
|
|||
```
|
||||
)"
|
||||
};
|
||||
|
||||
Setting<StringMap> impureEnv {this, {}, "impure-env",
|
||||
R"(
|
||||
A list of items, each in the format of:
|
||||
|
||||
- `name=value`: Set environment variable `name` to `value`.
|
||||
|
||||
If the user is trusted (see `trusted-users` option), when building
|
||||
a fixed-output derivation, environment variables set in this option
|
||||
will be passed to the builder if they are listed in [`impureEnvVars`](@docroot@/language/advanced-attributes.md##adv-attr-impureEnvVars).
|
||||
|
||||
This option is useful for, e.g., setting `https_proxy` for
|
||||
fixed-output derivations and in a multi-user Nix installation, or
|
||||
setting private access tokens when fetching a private repository.
|
||||
)",
|
||||
{}, // aliases
|
||||
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.
|
||||
)"
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
|
|
72
src/libstore/indirect-root-store.hh
Normal file
72
src/libstore/indirect-root-store.hh
Normal file
|
@ -0,0 +1,72 @@
|
|||
#pragma once
|
||||
///@file
|
||||
|
||||
#include "local-fs-store.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
/**
|
||||
* Mix-in class for implementing permanent roots as a pair of a direct
|
||||
* (strong) reference and indirect weak reference to the first
|
||||
* 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
|
||||
{
|
||||
inline static std::string operationName = "Indirect GC roots registration";
|
||||
|
||||
/**
|
||||
* Implementation of `LocalFSStore::addPermRoot` where the permanent
|
||||
* root is a pair of
|
||||
*
|
||||
* - The user-facing symlink which all implementations must create
|
||||
*
|
||||
* - An additional weak reference known as the "indirect root" that
|
||||
* points to that symlink.
|
||||
*
|
||||
* The garbage collector will automatically remove the indirect root
|
||||
* when it finds that the symlink has disappeared.
|
||||
*
|
||||
* The implementation of this method is concrete, but it delegates
|
||||
* to `addIndirectRoot()` which is abstract.
|
||||
*/
|
||||
Path addPermRoot(const StorePath & storePath, const Path & gcRoot) override final;
|
||||
|
||||
/**
|
||||
* Add an indirect root, which is a weak reference to the
|
||||
* user-facing symlink created by `addPermRoot()`.
|
||||
*
|
||||
* @param path user-facing and user-controlled symlink to a store
|
||||
* path.
|
||||
*
|
||||
* The form this weak-reference takes is implementation-specific.
|
||||
*/
|
||||
virtual void addIndirectRoot(const Path & path) = 0;
|
||||
};
|
||||
|
||||
}
|
31
src/libstore/keys.cc
Normal file
31
src/libstore/keys.cc
Normal file
|
@ -0,0 +1,31 @@
|
|||
#include "file-system.hh"
|
||||
#include "globals.hh"
|
||||
#include "keys.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
PublicKeys getDefaultPublicKeys()
|
||||
{
|
||||
PublicKeys publicKeys;
|
||||
|
||||
// FIXME: filter duplicates
|
||||
|
||||
for (auto s : settings.trustedPublicKeys.get()) {
|
||||
PublicKey key(s);
|
||||
publicKeys.emplace(key.name, key);
|
||||
}
|
||||
|
||||
for (auto secretKeyFile : settings.secretKeyFiles.get()) {
|
||||
try {
|
||||
SecretKey secretKey(readFile(secretKeyFile));
|
||||
publicKeys.emplace(secretKey.name, secretKey.toPublicKey());
|
||||
} catch (SysError & e) {
|
||||
/* Ignore unreadable key files. That's normal in a
|
||||
multi-user installation. */
|
||||
}
|
||||
}
|
||||
|
||||
return publicKeys;
|
||||
}
|
||||
|
||||
}
|
10
src/libstore/keys.hh
Normal file
10
src/libstore/keys.hh
Normal file
|
@ -0,0 +1,10 @@
|
|||
#pragma once
|
||||
///@file
|
||||
|
||||
#include "signature/local-keys.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
PublicKeys getDefaultPublicKeys();
|
||||
|
||||
}
|
|
@ -1,422 +1,368 @@
|
|||
#include "legacy-ssh-store.hh"
|
||||
#include "ssh-store-config.hh"
|
||||
#include "archive.hh"
|
||||
#include "pool.hh"
|
||||
#include "remote-store.hh"
|
||||
#include "serve-protocol.hh"
|
||||
#include "serve-protocol-impl.hh"
|
||||
#include "build-result.hh"
|
||||
#include "store-api.hh"
|
||||
#include "path-with-outputs.hh"
|
||||
#include "worker-protocol.hh"
|
||||
#include "ssh.hh"
|
||||
#include "derivations.hh"
|
||||
#include "callback.hh"
|
||||
|
||||
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;
|
||||
int remoteVersion;
|
||||
bool good = true;
|
||||
};
|
||||
|
||||
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 << cmdQueryPathInfos << 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 = WorkerProto<StorePathSet>::read(*this, conn->from);
|
||||
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
|
||||
<< cmdAddToStoreNar
|
||||
<< printStorePath(info.path)
|
||||
<< (info.deriver ? printStorePath(*info.deriver) : "")
|
||||
<< info.narHash.to_string(Base16, false);
|
||||
workerProtoWrite(*this, conn->to, 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
|
||||
<< cmdImportPaths
|
||||
<< 1;
|
||||
try {
|
||||
copyNAR(source, conn->to);
|
||||
} catch (...) {
|
||||
conn->good = false;
|
||||
throw;
|
||||
}
|
||||
conn->to
|
||||
<< exportMagic
|
||||
<< printStorePath(info.path);
|
||||
workerProtoWrite(*this, conn->to, 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 << cmdDumpStorePath << 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
|
||||
<< cmdBuildDerivation
|
||||
<< printStorePath(drvPath);
|
||||
writeDerivation(conn->to, *this, drv);
|
||||
|
||||
putBuildSettings(*conn);
|
||||
|
||||
conn->to.flush();
|
||||
|
||||
BuildResult status;
|
||||
status.status = (BuildResult::Status) readInt(conn->from);
|
||||
conn->from >> status.errorMsg;
|
||||
|
||||
if (GET_PROTOCOL_MINOR(conn->remoteVersion) >= 3)
|
||||
conn->from >> status.timesBuilt >> status.isNonDeterministic >> status.startTime >> status.stopTime;
|
||||
if (GET_PROTOCOL_MINOR(conn->remoteVersion) >= 6) {
|
||||
auto builtOutputs = WorkerProto<DrvOutputs>::read(*this, conn->from);
|
||||
for (auto && [output, realisation] : builtOutputs)
|
||||
status.builtOutputs.insert_or_assign(
|
||||
std::move(output.outputName),
|
||||
std::move(realisation));
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
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 << cmdBuildPaths;
|
||||
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));
|
||||
},
|
||||
}, 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
|
||||
<< cmdQueryClosure
|
||||
<< includeOutputs;
|
||||
workerProtoWrite(*this, conn->to, paths);
|
||||
conn->to.flush();
|
||||
|
||||
for (auto & i : WorkerProto<StorePathSet>::read(*this, conn->from))
|
||||
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
|
||||
<< cmdQueryValidPaths
|
||||
<< false // lock
|
||||
<< maybeSubstitute;
|
||||
workerProtoWrite(*this, conn->to, paths);
|
||||
conn->to.flush();
|
||||
|
||||
return WorkerProto<StorePathSet>::read(*this, conn->from);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
}
|
||||
|
|
126
src/libstore/legacy-ssh-store.hh
Normal file
126
src/libstore/legacy-ssh-store.hh
Normal file
|
@ -0,0 +1,126 @@
|
|||
#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,
|
||||
SourceAccessor & accessor,
|
||||
const CanonPath & srcPath,
|
||||
ContentAddressMethod method,
|
||||
HashAlgorithm hashAlgo,
|
||||
const StorePathSet & references,
|
||||
PathFilter & filter,
|
||||
RepairFlag repair) override
|
||||
{ unsupported("addToStore"); }
|
||||
|
||||
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"); }
|
||||
};
|
||||
|
||||
}
|
162
src/libstore/length-prefixed-protocol-helper.hh
Normal file
162
src/libstore/length-prefixed-protocol-helper.hh
Normal file
|
@ -0,0 +1,162 @@
|
|||
#pragma once
|
||||
/**
|
||||
* @file Reusable serialisers for serialization container types in a
|
||||
* length-prefixed manner.
|
||||
*
|
||||
* Used by both the Worker and Serve protocols.
|
||||
*/
|
||||
|
||||
#include "types.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
struct StoreDirConfig;
|
||||
|
||||
/**
|
||||
* Reusable serialisers for serialization container types in a
|
||||
* length-prefixed manner.
|
||||
*
|
||||
* @param T The type of the collection being serialised
|
||||
*
|
||||
* @param Inner This the most important parameter; this is the "inner"
|
||||
* protocol. The user of this will substitute `MyProtocol` or similar
|
||||
* when making a `MyProtocol::Serialiser<Collection<T>>`. Note that the
|
||||
* inside is allowed to call to call `Inner::Serialiser` on different
|
||||
* types. This is especially important for `std::map` which doesn't have
|
||||
* a single `T` but one `K` and one `V`.
|
||||
*/
|
||||
template<class Inner, typename T>
|
||||
struct LengthPrefixedProtoHelper;
|
||||
|
||||
/*!
|
||||
* \typedef LengthPrefixedProtoHelper::S
|
||||
*
|
||||
* Read this as simply `using S = Inner::Serialise;`.
|
||||
*
|
||||
* It would be nice to use that directly, but C++ doesn't seem to allow
|
||||
* it. The `typename` keyword needed to refer to `Inner` seems to greedy
|
||||
* (low precedence), and then C++ complains that `Serialise` is not a
|
||||
* type parameter but a real type.
|
||||
*
|
||||
* Making this `S` alias seems to be the only way to avoid these issues.
|
||||
*/
|
||||
|
||||
#define LENGTH_PREFIXED_PROTO_HELPER(Inner, T) \
|
||||
struct LengthPrefixedProtoHelper< Inner, T > \
|
||||
{ \
|
||||
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>; \
|
||||
}
|
||||
|
||||
template<class Inner, typename T>
|
||||
LENGTH_PREFIXED_PROTO_HELPER(Inner, std::vector<T>);
|
||||
|
||||
template<class Inner, typename T>
|
||||
LENGTH_PREFIXED_PROTO_HELPER(Inner, std::set<T>);
|
||||
|
||||
template<class Inner, typename... Ts>
|
||||
LENGTH_PREFIXED_PROTO_HELPER(Inner, std::tuple<Ts...>);
|
||||
|
||||
template<class Inner, typename K, typename V>
|
||||
#define _X std::map<K, V>
|
||||
LENGTH_PREFIXED_PROTO_HELPER(Inner, _X);
|
||||
#undef _X
|
||||
|
||||
template<class Inner, typename T>
|
||||
std::vector<T>
|
||||
LengthPrefixedProtoHelper<Inner, std::vector<T>>::read(
|
||||
const StoreDirConfig & store, typename Inner::ReadConn conn)
|
||||
{
|
||||
std::vector<T> resSet;
|
||||
auto size = readNum<size_t>(conn.from);
|
||||
while (size--) {
|
||||
resSet.push_back(S<T>::read(store, conn));
|
||||
}
|
||||
return resSet;
|
||||
}
|
||||
|
||||
template<class Inner, typename T>
|
||||
void
|
||||
LengthPrefixedProtoHelper<Inner, std::vector<T>>::write(
|
||||
const StoreDirConfig & store, typename Inner::WriteConn conn, const std::vector<T> & resSet)
|
||||
{
|
||||
conn.to << resSet.size();
|
||||
for (auto & key : resSet) {
|
||||
S<T>::write(store, conn, key);
|
||||
}
|
||||
}
|
||||
|
||||
template<class Inner, typename T>
|
||||
std::set<T>
|
||||
LengthPrefixedProtoHelper<Inner, std::set<T>>::read(
|
||||
const StoreDirConfig & store, typename Inner::ReadConn conn)
|
||||
{
|
||||
std::set<T> resSet;
|
||||
auto size = readNum<size_t>(conn.from);
|
||||
while (size--) {
|
||||
resSet.insert(S<T>::read(store, conn));
|
||||
}
|
||||
return resSet;
|
||||
}
|
||||
|
||||
template<class Inner, typename T>
|
||||
void
|
||||
LengthPrefixedProtoHelper<Inner, std::set<T>>::write(
|
||||
const StoreDirConfig & store, typename Inner::WriteConn conn, const std::set<T> & resSet)
|
||||
{
|
||||
conn.to << resSet.size();
|
||||
for (auto & key : resSet) {
|
||||
S<T>::write(store, conn, key);
|
||||
}
|
||||
}
|
||||
|
||||
template<class Inner, typename K, typename V>
|
||||
std::map<K, V>
|
||||
LengthPrefixedProtoHelper<Inner, std::map<K, V>>::read(
|
||||
const StoreDirConfig & store, typename Inner::ReadConn conn)
|
||||
{
|
||||
std::map<K, V> resMap;
|
||||
auto size = readNum<size_t>(conn.from);
|
||||
while (size--) {
|
||||
auto k = S<K>::read(store, conn);
|
||||
auto v = S<V>::read(store, conn);
|
||||
resMap.insert_or_assign(std::move(k), std::move(v));
|
||||
}
|
||||
return resMap;
|
||||
}
|
||||
|
||||
template<class Inner, typename K, typename V>
|
||||
void
|
||||
LengthPrefixedProtoHelper<Inner, std::map<K, V>>::write(
|
||||
const StoreDirConfig & store, typename Inner::WriteConn conn, const std::map<K, V> & resMap)
|
||||
{
|
||||
conn.to << resMap.size();
|
||||
for (auto & i : resMap) {
|
||||
S<K>::write(store, conn, i.first);
|
||||
S<V>::write(store, conn, i.second);
|
||||
}
|
||||
}
|
||||
|
||||
template<class Inner, typename... Ts>
|
||||
std::tuple<Ts...>
|
||||
LengthPrefixedProtoHelper<Inner, std::tuple<Ts...>>::read(
|
||||
const StoreDirConfig & store, typename Inner::ReadConn conn)
|
||||
{
|
||||
return std::tuple<Ts...> {
|
||||
S<Ts>::read(store, conn)...,
|
||||
};
|
||||
}
|
||||
|
||||
template<class Inner, typename... Ts>
|
||||
void
|
||||
LengthPrefixedProtoHelper<Inner, std::tuple<Ts...>>::write(
|
||||
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), ...);
|
||||
}, res);
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -11,26 +11,22 @@ 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 PathSetting rootDir{(StoreConfig*) this, true, "",
|
||||
const OptionalPathSetting rootDir{this, std::nullopt,
|
||||
"root",
|
||||
"Directory prefixed to all other paths."};
|
||||
|
||||
const PathSetting stateDir{(StoreConfig*) this, false,
|
||||
rootDir != "" ? rootDir + "/nix/var/nix" : settings.nixStateDir,
|
||||
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, false,
|
||||
rootDir != "" ? rootDir + "/nix/var/log/nix" : settings.nixLogDir,
|
||||
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, false,
|
||||
rootDir != "" ? rootDir + "/nix/store" : storeDir, "real",
|
||||
const PathSetting realStoreDir{this,
|
||||
rootDir.get() ? *rootDir.get() + "/nix/store" : storeDir, "real",
|
||||
"Physical path of the Nix store."};
|
||||
};
|
||||
|
||||
|
@ -40,18 +36,30 @@ class LocalFSStore : public virtual LocalFSStoreConfig,
|
|||
public virtual LogStore
|
||||
{
|
||||
public:
|
||||
inline static std::string operationName = "Local Filesystem Store";
|
||||
|
||||
const static std::string drvsLogDir;
|
||||
|
||||
LocalFSStore(const Params & params);
|
||||
|
||||
void narFromPath(const StorePath & path, Sink & sink) override;
|
||||
ref<FSAccessor> getFSAccessor() override;
|
||||
ref<SourceAccessor> getFSAccessor(bool requireValidPath) override;
|
||||
|
||||
/**
|
||||
* Register a permanent GC root.
|
||||
* Creates symlink from the `gcRoot` to the `storePath` and
|
||||
* registers the `gcRoot` as a permanent GC root. The `gcRoot`
|
||||
* symlink lives outside the store and is created and owned by the
|
||||
* user.
|
||||
*
|
||||
* @param gcRoot The location of the symlink.
|
||||
*
|
||||
* @param storePath The store object being rooted. The symlink will
|
||||
* point to `toRealPath(store.printStorePath(storePath))`.
|
||||
*
|
||||
* How the permanent GC root corresponding to this symlink is
|
||||
* managed is implementation-specific.
|
||||
*/
|
||||
Path addPermRoot(const StorePath & storePath, const Path & gcRoot);
|
||||
virtual Path addPermRoot(const StorePath & storePath, const Path & gcRoot) = 0;
|
||||
|
||||
virtual Path getRealStoreDir() { return realStoreDir; }
|
||||
|
||||
|
|
|
@ -4,17 +4,24 @@
|
|||
#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 "posix-source-accessor.hh"
|
||||
#include "keys.hh"
|
||||
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
#include <memory>
|
||||
#include <new>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/select.h>
|
||||
|
@ -32,7 +39,6 @@
|
|||
#include <sys/statvfs.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/xattr.h>
|
||||
#endif
|
||||
|
||||
#ifdef __CYGWIN__
|
||||
|
@ -190,7 +196,11 @@ LocalStore::LocalStore(const Params & params)
|
|||
|
||||
/* Create missing state directories if they don't already exist. */
|
||||
createDirs(realStoreDir);
|
||||
makeStoreWritable();
|
||||
if (readOnly) {
|
||||
experimentalFeatureSettings.require(Xp::ReadOnlyLocalStore);
|
||||
} else {
|
||||
makeStoreWritable();
|
||||
}
|
||||
createDirs(linksDir);
|
||||
Path profilesDir = stateDir + "/profiles";
|
||||
createDirs(profilesDir);
|
||||
|
@ -204,8 +214,10 @@ LocalStore::LocalStore(const Params & params)
|
|||
|
||||
for (auto & perUserDir : {profilesDir + "/per-user", gcRootsDir + "/per-user"}) {
|
||||
createDirs(perUserDir);
|
||||
if (chmod(perUserDir.c_str(), 0755) == -1)
|
||||
throw SysError("could not set permissions on '%s' to 755", perUserDir);
|
||||
if (!readOnly) {
|
||||
if (chmod(perUserDir.c_str(), 0755) == -1)
|
||||
throw SysError("could not set permissions on '%s' to 755", perUserDir);
|
||||
}
|
||||
}
|
||||
|
||||
/* Optionally, create directories and set permissions for a
|
||||
|
@ -269,10 +281,12 @@ LocalStore::LocalStore(const Params & params)
|
|||
|
||||
/* Acquire the big fat lock in shared mode to make sure that no
|
||||
schema upgrade is in progress. */
|
||||
Path globalLockPath = dbDir + "/big-lock";
|
||||
globalLock = openLockFile(globalLockPath.c_str(), true);
|
||||
if (!readOnly) {
|
||||
Path globalLockPath = dbDir + "/big-lock";
|
||||
globalLock = openLockFile(globalLockPath.c_str(), true);
|
||||
}
|
||||
|
||||
if (!lockFile(globalLock.get(), ltRead, false)) {
|
||||
if (!readOnly && !lockFile(globalLock.get(), ltRead, false)) {
|
||||
printInfo("waiting for the big Nix store lock...");
|
||||
lockFile(globalLock.get(), ltRead, true);
|
||||
}
|
||||
|
@ -280,6 +294,14 @@ LocalStore::LocalStore(const Params & params)
|
|||
/* Check the current database schema and if necessary do an
|
||||
upgrade. */
|
||||
int curSchema = getSchema();
|
||||
if (readOnly && curSchema < nixSchemaVersion) {
|
||||
debug("current schema version: %d", curSchema);
|
||||
debug("supported schema version: %d", nixSchemaVersion);
|
||||
throw Error(curSchema == 0 ?
|
||||
"database does not exist, and cannot be created in read-only mode" :
|
||||
"database schema needs migrating, but this cannot be done in read-only mode");
|
||||
}
|
||||
|
||||
if (curSchema > nixSchemaVersion)
|
||||
throw Error("current Nix store schema is version %1%, but I only support %2%",
|
||||
curSchema, nixSchemaVersion);
|
||||
|
@ -344,7 +366,11 @@ LocalStore::LocalStore(const Params & params)
|
|||
else openDB(*state, false);
|
||||
|
||||
if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations)) {
|
||||
migrateCASchema(state->db, dbDir + "/ca-schema", globalLock);
|
||||
if (!readOnly) {
|
||||
migrateCASchema(state->db, dbDir + "/ca-schema", globalLock);
|
||||
} else {
|
||||
throw Error("need to migrate to content-addressed schema, but this cannot be done in read-only mode");
|
||||
}
|
||||
}
|
||||
|
||||
/* Prepare SQL statements. */
|
||||
|
@ -475,13 +501,20 @@ int LocalStore::getSchema()
|
|||
|
||||
void LocalStore::openDB(State & state, bool create)
|
||||
{
|
||||
if (access(dbDir.c_str(), R_OK | W_OK))
|
||||
if (create && readOnly) {
|
||||
throw Error("cannot create database while in read-only mode");
|
||||
}
|
||||
|
||||
if (access(dbDir.c_str(), R_OK | (readOnly ? 0 : W_OK)))
|
||||
throw SysError("Nix database directory '%1%' is not writable", dbDir);
|
||||
|
||||
/* Open the Nix database. */
|
||||
std::string dbPath = dbDir + "/db.sqlite";
|
||||
auto & db(state.db);
|
||||
state.db = SQLite(dbPath, create);
|
||||
auto openMode = readOnly ? SQLiteOpenMode::Immutable
|
||||
: create ? SQLiteOpenMode::Normal
|
||||
: SQLiteOpenMode::NoCreate;
|
||||
state.db = SQLite(dbPath, openMode);
|
||||
|
||||
#ifdef __CYGWIN__
|
||||
/* The cygwin version of sqlite3 has a patch which calls
|
||||
|
@ -553,164 +586,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);
|
||||
|
@ -799,7 +674,7 @@ uint64_t LocalStore::addValidPath(State & state,
|
|||
|
||||
state.stmts->RegisterValidPath.use()
|
||||
(printStorePath(info.path))
|
||||
(info.narHash.to_string(Base16, true))
|
||||
(info.narHash.to_string(HashFormat::Base16, true))
|
||||
(info.registrationTime == 0 ? time(0) : info.registrationTime)
|
||||
(info.deriver ? printStorePath(*info.deriver) : "", (bool) info.deriver)
|
||||
(info.narSize, info.narSize != 0)
|
||||
|
@ -906,7 +781,7 @@ void LocalStore::updatePathInfo(State & state, const ValidPathInfo & info)
|
|||
{
|
||||
state.stmts->UpdatePathInfo.use()
|
||||
(info.narSize, info.narSize != 0)
|
||||
(info.narHash.to_string(Base16, true))
|
||||
(info.narHash.to_string(HashFormat::Base16, true))
|
||||
(info.ultimate ? 1 : 0, info.ultimate)
|
||||
(concatStringsSep(" ", info.sigs), !info.sigs.empty())
|
||||
(renderContentAddress(info.ca), (bool) info.ca)
|
||||
|
@ -995,10 +870,9 @@ StorePathSet LocalStore::queryValidDerivers(const StorePath & path)
|
|||
|
||||
|
||||
std::map<std::string, std::optional<StorePath>>
|
||||
LocalStore::queryPartialDerivationOutputMap(const StorePath & path_)
|
||||
LocalStore::queryStaticPartialDerivationOutputMap(const StorePath & path)
|
||||
{
|
||||
auto path = path_;
|
||||
auto outputs = retrySQLite<std::map<std::string, std::optional<StorePath>>>([&]() {
|
||||
return retrySQLite<std::map<std::string, std::optional<StorePath>>>([&]() {
|
||||
auto state(_state.lock());
|
||||
std::map<std::string, std::optional<StorePath>> outputs;
|
||||
uint64_t drvId;
|
||||
|
@ -1010,21 +884,6 @@ LocalStore::queryPartialDerivationOutputMap(const StorePath & path_)
|
|||
|
||||
return outputs;
|
||||
});
|
||||
|
||||
if (!experimentalFeatureSettings.isEnabled(Xp::CaDerivations))
|
||||
return outputs;
|
||||
|
||||
auto drv = readInvalidDerivation(path);
|
||||
auto drvHashes = staticOutputHashes(*this, drv);
|
||||
for (auto& [outputName, hash] : drvHashes) {
|
||||
auto realisation = queryRealisation(DrvOutput{hash, outputName});
|
||||
if (realisation)
|
||||
outputs.insert_or_assign(outputName, realisation->outPath);
|
||||
else
|
||||
outputs.insert({outputName, std::nullopt});
|
||||
}
|
||||
|
||||
return outputs;
|
||||
}
|
||||
|
||||
std::optional<StorePath> LocalStore::queryPathFromHashPart(const std::string & hashPart)
|
||||
|
@ -1100,7 +959,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
|
||||
|
@ -1185,6 +1044,15 @@ void LocalStore::addToStore(const ValidPathInfo & info, Source & source,
|
|||
if (checkSigs && pathInfoIsUntrusted(info))
|
||||
throw Error("cannot add path '%s' because it lacks a signature by a trusted key", printStorePath(info.path));
|
||||
|
||||
/* In case we are not interested in reading the NAR: discard it. */
|
||||
bool narRead = false;
|
||||
Finally cleanup = [&]() {
|
||||
if (!narRead) {
|
||||
NullParseSink sink;
|
||||
parseDump(sink, source);
|
||||
}
|
||||
};
|
||||
|
||||
addTempRoot(info.path);
|
||||
|
||||
if (repair || !isValidPath(info.path)) {
|
||||
|
@ -1205,44 +1073,46 @@ 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 };
|
||||
|
||||
narRead = true;
|
||||
restorePath(realPath, wrapperSource);
|
||||
|
||||
auto hashResult = hashSink.finish();
|
||||
|
||||
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(Base32, true), hashResult.first.to_string(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",
|
||||
printStorePath(info.path), info.narSize, hashResult.second);
|
||||
|
||||
if (info.ca) {
|
||||
if (auto foHash = std::get_if<FixedOutputHash>(&info.ca->raw)) {
|
||||
auto actualFoHash = hashCAPath(
|
||||
foHash->method,
|
||||
foHash->hash.type,
|
||||
info.path
|
||||
);
|
||||
if (foHash->hash != actualFoHash.hash) {
|
||||
throw Error("ca hash mismatch importing path '%s';\n specified: %s\n got: %s",
|
||||
printStorePath(info.path),
|
||||
foHash->hash.to_string(Base32, true),
|
||||
actualFoHash.hash.to_string(Base32, true));
|
||||
}
|
||||
}
|
||||
if (auto textHash = std::get_if<TextHash>(&info.ca->raw)) {
|
||||
auto actualTextHash = hashString(htSHA256, readFile(realPath));
|
||||
if (textHash->hash != actualTextHash) {
|
||||
throw Error("ca hash mismatch importing path '%s';\n specified: %s\n got: %s",
|
||||
printStorePath(info.path),
|
||||
textHash->hash.to_string(Base32, true),
|
||||
actualTextHash.to_string(Base32, true));
|
||||
}
|
||||
auto & specified = *info.ca;
|
||||
auto actualHash = ({
|
||||
HashModuloSink caSink {
|
||||
specified.hash.algo,
|
||||
std::string { info.path.hashPart() },
|
||||
};
|
||||
PosixSourceAccessor accessor;
|
||||
dumpPath(
|
||||
*getFSAccessor(false),
|
||||
CanonPath { printStorePath(info.path) },
|
||||
caSink,
|
||||
specified.method.getFileIngestionMethod());
|
||||
ContentAddress {
|
||||
.method = specified.method,
|
||||
.hash = caSink.finish().first,
|
||||
};
|
||||
});
|
||||
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::Nix32, true),
|
||||
actualHash.hash.to_string(HashFormat::Nix32, true));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1260,8 +1130,13 @@ 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)
|
||||
StorePath LocalStore::addToStoreFromDump(
|
||||
Source & source0,
|
||||
std::string_view name,
|
||||
ContentAddressMethod method,
|
||||
HashAlgorithm hashAlgo,
|
||||
const StorePathSet & references,
|
||||
RepairFlag repair)
|
||||
{
|
||||
/* For computing the store path. */
|
||||
auto hashSink = std::make_unique<HashSink>(hashAlgo);
|
||||
|
@ -1275,7 +1150,11 @@ StorePath LocalStore::addToStoreFromDump(Source & source0, std::string_view name
|
|||
path. */
|
||||
bool inMemory = false;
|
||||
|
||||
std::string dump;
|
||||
struct Free {
|
||||
void operator()(void* v) { free(v); }
|
||||
};
|
||||
std::unique_ptr<char, Free> dumpBuffer(nullptr);
|
||||
std::string_view dump;
|
||||
|
||||
/* Fill out buffer, and decide whether we are working strictly in
|
||||
memory based on whether we break out because the buffer is full
|
||||
|
@ -1284,13 +1163,18 @@ StorePath LocalStore::addToStoreFromDump(Source & source0, std::string_view name
|
|||
auto oldSize = dump.size();
|
||||
constexpr size_t chunkSize = 65536;
|
||||
auto want = std::min(chunkSize, settings.narBufferSize - oldSize);
|
||||
dump.resize(oldSize + want);
|
||||
if (auto tmp = realloc(dumpBuffer.get(), oldSize + want)) {
|
||||
dumpBuffer.release();
|
||||
dumpBuffer.reset((char*) tmp);
|
||||
} else {
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
auto got = 0;
|
||||
Finally cleanup([&]() {
|
||||
dump.resize(oldSize + got);
|
||||
dump = {dumpBuffer.get(), dump.size() + got};
|
||||
});
|
||||
try {
|
||||
got = source.read(dump.data() + oldSize, want);
|
||||
got = source.read(dumpBuffer.get() + oldSize, want);
|
||||
} catch (EndOfFile &) {
|
||||
inMemory = true;
|
||||
break;
|
||||
|
@ -1311,27 +1195,22 @@ StorePath LocalStore::addToStoreFromDump(Source & source0, std::string_view name
|
|||
delTempDir = std::make_unique<AutoDelete>(tempDir);
|
||||
tempPath = tempDir + "/x";
|
||||
|
||||
if (method == FileIngestionMethod::Recursive)
|
||||
restorePath(tempPath, bothSource);
|
||||
else
|
||||
writeFile(tempPath, bothSource);
|
||||
restorePath(tempPath, bothSource, method.getFileIngestionMethod());
|
||||
|
||||
dump.clear();
|
||||
dumpBuffer.reset();
|
||||
dump = {};
|
||||
}
|
||||
|
||||
auto [hash, size] = hashSink->finish();
|
||||
|
||||
ContentAddressWithReferences desc = FixedOutputInfo {
|
||||
.hash = {
|
||||
.method = method,
|
||||
.hash = hash,
|
||||
},
|
||||
.references = {
|
||||
auto desc = ContentAddressWithReferences::fromParts(
|
||||
method,
|
||||
hash,
|
||||
{
|
||||
.others = references,
|
||||
// caller is not capable of creating a self-reference, because this is content-addressed without modulus
|
||||
.self = false,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
auto dstPath = makeFixedOutputPathFromCA(name, desc);
|
||||
|
||||
|
@ -1354,11 +1233,8 @@ StorePath LocalStore::addToStoreFromDump(Source & source0, std::string_view name
|
|||
|
||||
if (inMemory) {
|
||||
StringSource dumpSource { dump };
|
||||
/* Restore from the NAR in memory. */
|
||||
if (method == FileIngestionMethod::Recursive)
|
||||
restorePath(realPath, dumpSource);
|
||||
else
|
||||
writeFile(realPath, dumpSource);
|
||||
/* Restore from the buffer in memory. */
|
||||
restorePath(realPath, dumpSource, method.getFileIngestionMethod());
|
||||
} else {
|
||||
/* Move the temporary path we restored above. */
|
||||
moveFile(tempPath, realPath);
|
||||
|
@ -1367,8 +1243,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();
|
||||
}
|
||||
|
@ -1394,55 +1270,6 @@ StorePath LocalStore::addToStoreFromDump(Source & source0, std::string_view name
|
|||
}
|
||||
|
||||
|
||||
StorePath LocalStore::addTextToStore(
|
||||
std::string_view name,
|
||||
std::string_view s,
|
||||
const StorePathSet & references, RepairFlag repair)
|
||||
{
|
||||
auto hash = hashString(htSHA256, s);
|
||||
auto dstPath = makeTextPath(name, TextInfo {
|
||||
{ .hash = hash },
|
||||
references,
|
||||
});
|
||||
|
||||
addTempRoot(dstPath);
|
||||
|
||||
if (repair || !isValidPath(dstPath)) {
|
||||
|
||||
auto realPath = Store::toRealPath(dstPath);
|
||||
|
||||
PathLocks outputLock({realPath});
|
||||
|
||||
if (repair || !isValidPath(dstPath)) {
|
||||
|
||||
deletePath(realPath);
|
||||
|
||||
autoGC();
|
||||
|
||||
writeFile(realPath, s);
|
||||
|
||||
canonicalisePathMetaData(realPath, {});
|
||||
|
||||
StringSink sink;
|
||||
dumpString(s, sink);
|
||||
auto narHash = hashString(htSHA256, sink.s);
|
||||
|
||||
optimisePath(realPath, repair);
|
||||
|
||||
ValidPathInfo info { dstPath, narHash };
|
||||
info.narSize = sink.s.size();
|
||||
info.references = references;
|
||||
info.ca = TextHash { .hash = hash };
|
||||
registerValidPath(info);
|
||||
}
|
||||
|
||||
outputLock.setDeletion(true);
|
||||
}
|
||||
|
||||
return dstPath;
|
||||
}
|
||||
|
||||
|
||||
/* Create a temporary directory in the store that won't be
|
||||
garbage-collected until the returned FD is closed. */
|
||||
std::pair<Path, AutoCloseFD> LocalStore::createTempDirInStore()
|
||||
|
@ -1497,17 +1324,33 @@ bool LocalStore::verifyStore(bool checkContents, RepairFlag repair)
|
|||
auto fdGCLock = openGCLock();
|
||||
FdLock gcLock(fdGCLock.get(), ltRead, true, "waiting for the big garbage collector lock...");
|
||||
|
||||
StringSet store;
|
||||
for (auto & i : readDirectory(realStoreDir)) store.insert(i.name);
|
||||
|
||||
/* Check whether all valid paths actually exist. */
|
||||
printInfo("checking path existence...");
|
||||
|
||||
StorePathSet validPaths;
|
||||
PathSet done;
|
||||
|
||||
for (auto & i : queryAllValidPaths())
|
||||
verifyPath(printStorePath(i), store, done, validPaths, repair, errors);
|
||||
{
|
||||
StorePathSet storePathsInStoreDir;
|
||||
/* Why aren't we using `queryAllValidPaths`? Because that would
|
||||
tell us about all the paths than the database knows about. Here we
|
||||
want to know about all the store paths in the store directory,
|
||||
regardless of what the database thinks.
|
||||
|
||||
We will end up cross-referencing these two sources of truth (the
|
||||
database and the filesystem) in the loop below, in order to catch
|
||||
invalid states.
|
||||
*/
|
||||
for (auto & i : readDirectory(realStoreDir)) {
|
||||
try {
|
||||
storePathsInStoreDir.insert({i.name});
|
||||
} catch (BadStorePath &) { }
|
||||
}
|
||||
|
||||
/* Check whether all valid paths actually exist. */
|
||||
printInfo("checking path existence...");
|
||||
|
||||
StorePathSet done;
|
||||
|
||||
for (auto & i : queryAllValidPaths())
|
||||
verifyPath(i, storePathsInStoreDir, done, validPaths, repair, errors);
|
||||
}
|
||||
|
||||
/* Optionally, check the content hashes (slow). */
|
||||
if (checkContents) {
|
||||
|
@ -1517,7 +1360,10 @@ 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(Base32, false);
|
||||
PosixSourceAccessor accessor;
|
||||
std::string hash = hashPath(
|
||||
accessor, CanonPath { linkPath },
|
||||
FileIngestionMethod::Recursive, HashAlgorithm::SHA256).first.to_string(HashFormat::Nix32, false);
|
||||
if (hash != link.name) {
|
||||
printError("link '%s' was modified! expected hash '%s', got '%s'",
|
||||
linkPath, link.name, hash);
|
||||
|
@ -1534,7 +1380,7 @@ bool LocalStore::verifyStore(bool checkContents, RepairFlag repair)
|
|||
|
||||
printInfo("checking store hashes...");
|
||||
|
||||
Hash nullHash(htSHA256);
|
||||
Hash nullHash(HashAlgorithm::SHA256);
|
||||
|
||||
for (auto & i : validPaths) {
|
||||
try {
|
||||
|
@ -1543,14 +1389,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(Base32, true), current.first.to_string(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 {
|
||||
|
||||
|
@ -1593,32 +1439,27 @@ bool LocalStore::verifyStore(bool checkContents, RepairFlag repair)
|
|||
}
|
||||
|
||||
|
||||
void LocalStore::verifyPath(const Path & pathS, const StringSet & store,
|
||||
PathSet & done, StorePathSet & validPaths, RepairFlag repair, bool & errors)
|
||||
void LocalStore::verifyPath(const StorePath & path, const StorePathSet & storePathsInStoreDir,
|
||||
StorePathSet & done, StorePathSet & validPaths, RepairFlag repair, bool & errors)
|
||||
{
|
||||
checkInterrupt();
|
||||
|
||||
if (!done.insert(pathS).second) return;
|
||||
if (!done.insert(path).second) return;
|
||||
|
||||
if (!isStorePath(pathS)) {
|
||||
printError("path '%s' is not in the Nix store", pathS);
|
||||
return;
|
||||
}
|
||||
|
||||
auto path = parseStorePath(pathS);
|
||||
|
||||
if (!store.count(std::string(path.to_string()))) {
|
||||
if (!storePathsInStoreDir.count(path)) {
|
||||
/* Check any referrers first. If we can invalidate them
|
||||
first, then we can invalidate this path as well. */
|
||||
bool canInvalidate = true;
|
||||
StorePathSet referrers; queryReferrers(path, referrers);
|
||||
for (auto & i : referrers)
|
||||
if (i != path) {
|
||||
verifyPath(printStorePath(i), store, done, validPaths, repair, errors);
|
||||
verifyPath(i, storePathsInStoreDir, done, validPaths, repair, errors);
|
||||
if (validPaths.count(i))
|
||||
canInvalidate = false;
|
||||
}
|
||||
|
||||
auto pathS = printStorePath(path);
|
||||
|
||||
if (canInvalidate) {
|
||||
printInfo("path '%s' disappeared, removing from database...", pathS);
|
||||
auto state(_state.lock());
|
||||
|
@ -1738,7 +1579,8 @@ void LocalStore::signRealisation(Realisation & realisation)
|
|||
|
||||
for (auto & secretKeyFile : secretKeyFiles.get()) {
|
||||
SecretKey secretKey(readFile(secretKeyFile));
|
||||
realisation.sign(secretKey);
|
||||
LocalSigner signer(std::move(secretKey));
|
||||
realisation.sign(signer);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1750,7 +1592,8 @@ void LocalStore::signPathInfo(ValidPathInfo & info)
|
|||
|
||||
for (auto & secretKeyFile : secretKeyFiles.get()) {
|
||||
SecretKey secretKey(readFile(secretKeyFile));
|
||||
info.sign(*this, secretKey);
|
||||
LocalSigner signer(std::move(secretKey));
|
||||
info.sign(*this, signer);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1829,36 +1672,6 @@ void LocalStore::queryRealisationUncached(const DrvOutput & id,
|
|||
}
|
||||
}
|
||||
|
||||
FixedOutputHash LocalStore::hashCAPath(
|
||||
const FileIngestionMethod & method, const HashType & hashType,
|
||||
const StorePath & path)
|
||||
{
|
||||
return hashCAPath(method, hashType, Store::toRealPath(path), path.hashPart());
|
||||
}
|
||||
|
||||
FixedOutputHash LocalStore::hashCAPath(
|
||||
const FileIngestionMethod & method,
|
||||
const HashType & hashType,
|
||||
const Path & path,
|
||||
const std::string_view pathHash
|
||||
)
|
||||
{
|
||||
HashModuloSink caSink ( hashType, std::string(pathHash) );
|
||||
switch (method) {
|
||||
case FileIngestionMethod::Recursive:
|
||||
dumpPath(path, caSink);
|
||||
break;
|
||||
case FileIngestionMethod::Flat:
|
||||
readFile(path, caSink);
|
||||
break;
|
||||
}
|
||||
auto hash = caSink.finish().first;
|
||||
return FixedOutputHash{
|
||||
.method = method,
|
||||
.hash = hash,
|
||||
};
|
||||
}
|
||||
|
||||
void LocalStore::addBuildLog(const StorePath & drvPath, std::string_view log)
|
||||
{
|
||||
assert(drvPath.isDerivation());
|
||||
|
|
|
@ -5,10 +5,8 @@
|
|||
|
||||
#include "pathlocks.hh"
|
||||
#include "store-api.hh"
|
||||
#include "local-fs-store.hh"
|
||||
#include "gc-store.hh"
|
||||
#include "indirect-root-store.hh"
|
||||
#include "sync.hh"
|
||||
#include "util.hh"
|
||||
|
||||
#include <chrono>
|
||||
#include <future>
|
||||
|
@ -41,17 +39,36 @@ 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{this,
|
||||
false,
|
||||
"read-only",
|
||||
R"(
|
||||
Allow this store to be opened when its [database](@docroot@/glossary.md#gloss-nix-database) is on a read-only filesystem.
|
||||
|
||||
Normally Nix will attempt to open the store database in read-write mode, even for querying (when write access is not needed), causing it to fail if the database is on a read-only filesystem.
|
||||
|
||||
Enable read-only mode to disable locking and open the SQLite database with the [`immutable` parameter](https://www.sqlite.org/c3ref/open.html) set.
|
||||
|
||||
> **Warning**
|
||||
> Do not use this unless the filesystem is read-only.
|
||||
>
|
||||
> Using it when the filesystem is writable can cause incorrect query results or corruption errors if the database is changed by another process.
|
||||
> While the filesystem the database resides on might appear to be read-only, consider whether another user or system might have write access to it.
|
||||
)"};
|
||||
|
||||
const std::string name() override { return "Local Store"; }
|
||||
|
||||
std::string doc() override;
|
||||
};
|
||||
|
||||
class LocalStore : public virtual LocalStoreConfig, public virtual LocalFSStore, public virtual GcStore
|
||||
class LocalStore : public virtual LocalStoreConfig
|
||||
, public virtual IndirectRootStore
|
||||
, public virtual GcStore
|
||||
{
|
||||
private:
|
||||
|
||||
|
@ -148,7 +165,7 @@ public:
|
|||
|
||||
StorePathSet queryValidDerivers(const StorePath & path) override;
|
||||
|
||||
std::map<std::string, std::optional<StorePath>> queryPartialDerivationOutputMap(const StorePath & path) override;
|
||||
std::map<std::string, std::optional<StorePath>> queryStaticPartialDerivationOutputMap(const StorePath & path) override;
|
||||
|
||||
std::optional<StorePath> queryPathFromHashPart(const std::string & hashPart) override;
|
||||
|
||||
|
@ -160,12 +177,11 @@ public:
|
|||
void addToStore(const ValidPathInfo & info, Source & source,
|
||||
RepairFlag repair, CheckSigsFlag checkSigs) override;
|
||||
|
||||
StorePath addToStoreFromDump(Source & dump, std::string_view name,
|
||||
FileIngestionMethod method, HashType hashAlgo, RepairFlag repair, const StorePathSet & references) override;
|
||||
|
||||
StorePath addTextToStore(
|
||||
StorePath addToStoreFromDump(
|
||||
Source & dump,
|
||||
std::string_view name,
|
||||
std::string_view s,
|
||||
ContentAddressMethod method,
|
||||
HashAlgorithm hashAlgo,
|
||||
const StorePathSet & references,
|
||||
RepairFlag repair) override;
|
||||
|
||||
|
@ -192,6 +208,12 @@ private:
|
|||
|
||||
public:
|
||||
|
||||
/**
|
||||
* Implementation of IndirectRootStore::addIndirectRoot().
|
||||
*
|
||||
* The weak reference merely is a symlink to `path' from
|
||||
* /nix/var/nix/gcroots/auto/<hash of `path'>.
|
||||
*/
|
||||
void addIndirectRoot(const Path & path) override;
|
||||
|
||||
private:
|
||||
|
@ -269,6 +291,10 @@ public:
|
|||
|
||||
private:
|
||||
|
||||
/**
|
||||
* Retrieve the current version of the database schema.
|
||||
* If the database does not exist yet, the version returned will be 0.
|
||||
*/
|
||||
int getSchema();
|
||||
|
||||
void openDB(State & state, bool create);
|
||||
|
@ -286,8 +312,8 @@ private:
|
|||
*/
|
||||
void invalidatePathChecked(const StorePath & path);
|
||||
|
||||
void verifyPath(const Path & path, const StringSet & store,
|
||||
PathSet & done, StorePathSet & validPaths, RepairFlag repair, bool & errors);
|
||||
void verifyPath(const StorePath & path, const StorePathSet & store,
|
||||
StorePathSet & done, StorePathSet & validPaths, RepairFlag repair, bool & errors);
|
||||
|
||||
std::shared_ptr<const ValidPathInfo> queryPathInfoInternal(State & state, const StorePath & path);
|
||||
|
||||
|
@ -323,19 +349,6 @@ private:
|
|||
void signPathInfo(ValidPathInfo & info);
|
||||
void signRealisation(Realisation &);
|
||||
|
||||
// XXX: Make a generic `Store` method
|
||||
FixedOutputHash hashCAPath(
|
||||
const FileIngestionMethod & method,
|
||||
const HashType & hashType,
|
||||
const StorePath & path);
|
||||
|
||||
FixedOutputHash hashCAPath(
|
||||
const FileIngestionMethod & method,
|
||||
const HashType & hashType,
|
||||
const Path & path,
|
||||
const std::string_view pathHash
|
||||
);
|
||||
|
||||
void addBuildLog(const StorePath & drvPath, std::string_view log) override;
|
||||
|
||||
friend struct LocalDerivationGoal;
|
||||
|
@ -344,38 +357,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);
|
||||
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ libstore_SOURCES := $(wildcard $(d)/*.cc $(d)/builtins/*.cc $(d)/build/*.cc)
|
|||
|
||||
libstore_LIBS = libutil
|
||||
|
||||
libstore_LDFLAGS += $(SQLITE3_LIBS) $(LIBCURL_LIBS) $(SODIUM_LIBS) -pthread
|
||||
libstore_LDFLAGS += $(SQLITE3_LIBS) $(LIBCURL_LIBS) $(THREAD_LDFLAGS)
|
||||
ifdef HOST_LINUX
|
||||
libstore_LDFLAGS += -ldl
|
||||
endif
|
||||
|
@ -16,15 +16,15 @@ endif
|
|||
$(foreach file,$(libstore_FILES),$(eval $(call install-data-in,$(d)/$(file),$(datadir)/nix/sandbox)))
|
||||
|
||||
ifeq ($(ENABLE_S3), 1)
|
||||
libstore_LDFLAGS += -laws-cpp-sdk-transfer -laws-cpp-sdk-s3 -laws-cpp-sdk-core -laws-crt-cpp
|
||||
libstore_LDFLAGS += -laws-cpp-sdk-transfer -laws-cpp-sdk-s3 -laws-cpp-sdk-core -laws-crt-cpp
|
||||
endif
|
||||
|
||||
ifdef HOST_SOLARIS
|
||||
libstore_LDFLAGS += -lsocket
|
||||
libstore_LDFLAGS += -lsocket
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_SECCOMP), 1)
|
||||
libstore_LDFLAGS += $(LIBSECCOMP_LIBS)
|
||||
libstore_LDFLAGS += $(LIBSECCOMP_LIBS)
|
||||
endif
|
||||
|
||||
libstore_CXXFLAGS += \
|
||||
|
@ -48,9 +48,9 @@ $(d)/embedded-sandbox-shell.gen.hh: $(sandbox_shell)
|
|||
$(trace-gen) hexdump -v -e '1/1 "0x%x," "\n"' < $< > $@.tmp
|
||||
@mv $@.tmp $@
|
||||
else
|
||||
ifneq ($(sandbox_shell),)
|
||||
libstore_CXXFLAGS += -DSANDBOX_SHELL="\"$(sandbox_shell)\""
|
||||
endif
|
||||
ifneq ($(sandbox_shell),)
|
||||
libstore_CXXFLAGS += -DSANDBOX_SHELL="\"$(sandbox_shell)\""
|
||||
endif
|
||||
endif
|
||||
|
||||
$(d)/local-store.cc: $(d)/schema.sql.gen.hh $(d)/ca-specific-schema.sql.gen.hh
|
||||
|
@ -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)))
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include "lock.hh"
|
||||
#include "file-system.hh"
|
||||
#include "globals.hh"
|
||||
#include "pathlocks.hh"
|
||||
|
||||
|
@ -7,6 +8,31 @@
|
|||
|
||||
namespace nix {
|
||||
|
||||
#if __linux__
|
||||
|
||||
static std::vector<gid_t> get_group_list(const char *username, gid_t group_id)
|
||||
{
|
||||
std::vector<gid_t> gids;
|
||||
gids.resize(32); // Initial guess
|
||||
|
||||
auto getgroupl_failed {[&] {
|
||||
int ngroups = gids.size();
|
||||
int err = getgrouplist(username, group_id, gids.data(), &ngroups);
|
||||
gids.resize(ngroups);
|
||||
return err == -1;
|
||||
}};
|
||||
|
||||
// The first error means that the vector was not big enough.
|
||||
// If it happens again, there is some different problem.
|
||||
if (getgroupl_failed() && getgroupl_failed()) {
|
||||
throw SysError("failed to get list of supplementary groups for '%s'", username);
|
||||
}
|
||||
|
||||
return gids;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
struct SimpleUserLock : UserLock
|
||||
{
|
||||
AutoCloseFD fdUserLock;
|
||||
|
@ -67,37 +93,14 @@ struct SimpleUserLock : UserLock
|
|||
throw Error("the Nix user should not be a member of '%s'", settings.buildUsersGroup);
|
||||
|
||||
#if __linux__
|
||||
/* Get the list of supplementary groups of this build
|
||||
user. This is usually either empty or contains a
|
||||
group such as "kvm". */
|
||||
int ngroups = 32; // arbitrary initial guess
|
||||
std::vector<gid_t> gids;
|
||||
gids.resize(ngroups);
|
||||
|
||||
int err = getgrouplist(
|
||||
pw->pw_name, pw->pw_gid,
|
||||
gids.data(),
|
||||
&ngroups);
|
||||
|
||||
/* Our initial size of 32 wasn't sufficient, the
|
||||
correct size has been stored in ngroups, so we try
|
||||
again. */
|
||||
if (err == -1) {
|
||||
gids.resize(ngroups);
|
||||
err = getgrouplist(
|
||||
pw->pw_name, pw->pw_gid,
|
||||
gids.data(),
|
||||
&ngroups);
|
||||
}
|
||||
|
||||
// If it failed once more, then something must be broken.
|
||||
if (err == -1)
|
||||
throw Error("failed to get list of supplementary groups for '%s'", pw->pw_name);
|
||||
/* Get the list of supplementary groups of this user. This is
|
||||
* usually either empty or contains a group such as "kvm". */
|
||||
|
||||
// Finally, trim back the GID list to its real size.
|
||||
for (auto i = 0; i < ngroups; i++)
|
||||
if (gids[i] != lock->gid)
|
||||
lock->supplementaryGIDs.push_back(gids[i]);
|
||||
for (auto gid : get_group_list(pw->pw_name, pw->pw_gid)) {
|
||||
if (gid != lock->gid)
|
||||
lock->supplementaryGIDs.push_back(gid);
|
||||
}
|
||||
#endif
|
||||
|
||||
return lock;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#include "machines.hh"
|
||||
#include "util.hh"
|
||||
#include "globals.hh"
|
||||
#include "store-api.hh"
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
@ -52,10 +52,8 @@ std::map<StorePath, StorePath> makeContentAddressed(
|
|||
dstStore,
|
||||
path.name(),
|
||||
FixedOutputInfo {
|
||||
.hash = {
|
||||
.method = FileIngestionMethod::Recursive,
|
||||
.hash = narModuloHash,
|
||||
},
|
||||
.method = FileIngestionMethod::Recursive,
|
||||
.hash = narModuloHash,
|
||||
.references = std::move(refs),
|
||||
},
|
||||
Hash::dummy,
|
||||
|
@ -68,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);
|
||||
|
@ -80,4 +78,15 @@ std::map<StorePath, StorePath> makeContentAddressed(
|
|||
return remappings;
|
||||
}
|
||||
|
||||
StorePath makeContentAddressed(
|
||||
Store & srcStore,
|
||||
Store & dstStore,
|
||||
const StorePath & fromPath)
|
||||
{
|
||||
auto remappings = makeContentAddressed(srcStore, dstStore, StorePathSet { fromPath });
|
||||
auto i = remappings.find(fromPath);
|
||||
assert(i != remappings.end());
|
||||
return i->second;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -5,9 +5,20 @@
|
|||
|
||||
namespace nix {
|
||||
|
||||
/** Rewrite a closure of store paths to be completely content addressed.
|
||||
*/
|
||||
std::map<StorePath, StorePath> makeContentAddressed(
|
||||
Store & srcStore,
|
||||
Store & dstStore,
|
||||
const StorePathSet & storePaths);
|
||||
const StorePathSet & rootPaths);
|
||||
|
||||
/** Rewrite a closure of a store path to be completely content addressed.
|
||||
*
|
||||
* This is a convenience function for the case where you only have one root path.
|
||||
*/
|
||||
StorePath makeContentAddressed(
|
||||
Store & srcStore,
|
||||
Store & dstStore,
|
||||
const StorePath & rootPath);
|
||||
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
@ -88,7 +89,7 @@ const ContentAddress * getDerivationCA(const BasicDerivation & drv)
|
|||
auto out = drv.outputs.find("out");
|
||||
if (out == drv.outputs.end())
|
||||
return nullptr;
|
||||
if (auto dof = std::get_if<DerivationOutput::CAFixed>(&out->second)) {
|
||||
if (auto dof = std::get_if<DerivationOutput::CAFixed>(&out->second.raw)) {
|
||||
return &dof->ca;
|
||||
}
|
||||
return nullptr;
|
||||
|
@ -125,14 +126,26 @@ void Store::queryMissing(const std::vector<DerivedPath> & targets,
|
|||
|
||||
std::function<void(DerivedPath)> doPath;
|
||||
|
||||
std::function<void(ref<SingleDerivedPath>, const DerivedPathMap<StringSet>::ChildNode &)> enqueueDerivedPaths;
|
||||
|
||||
enqueueDerivedPaths = [&](ref<SingleDerivedPath> inputDrv, const DerivedPathMap<StringSet>::ChildNode & inputNode) {
|
||||
if (!inputNode.value.empty())
|
||||
pool.enqueue(std::bind(doPath, DerivedPath::Built { inputDrv, inputNode.value }));
|
||||
for (const auto & [outputName, childNode] : inputNode.childMap)
|
||||
enqueueDerivedPaths(
|
||||
make_ref<SingleDerivedPath>(SingleDerivedPath::Built { inputDrv, outputName }),
|
||||
childNode);
|
||||
};
|
||||
|
||||
auto mustBuildDrv = [&](const StorePath & drvPath, const Derivation & drv) {
|
||||
{
|
||||
auto state(state_.lock());
|
||||
state->willBuild.insert(drvPath);
|
||||
}
|
||||
|
||||
for (auto & i : drv.inputDrvs)
|
||||
pool.enqueue(std::bind(doPath, DerivedPath::Built { i.first, i.second }));
|
||||
for (const auto & [inputDrv, inputNode] : drv.inputDrvs.map) {
|
||||
enqueueDerivedPaths(makeConstantStorePathRef(inputDrv), inputNode);
|
||||
}
|
||||
};
|
||||
|
||||
auto checkOutput = [&](
|
||||
|
@ -176,10 +189,18 @@ void Store::queryMissing(const std::vector<DerivedPath> & targets,
|
|||
|
||||
std::visit(overloaded {
|
||||
[&](const DerivedPath::Built & bfd) {
|
||||
if (!isValidPath(bfd.drvPath)) {
|
||||
auto drvPathP = std::get_if<DerivedPath::Opaque>(&*bfd.drvPath);
|
||||
if (!drvPathP) {
|
||||
// TODO make work in this case.
|
||||
warn("Ignoring dynamic derivation %s while querying missing paths; not yet implemented", bfd.drvPath->to_string(*this));
|
||||
return;
|
||||
}
|
||||
auto & drvPath = drvPathP->path;
|
||||
|
||||
if (!isValidPath(drvPath)) {
|
||||
// FIXME: we could try to substitute the derivation.
|
||||
auto state(state_.lock());
|
||||
state->unknown.insert(bfd.drvPath);
|
||||
state->unknown.insert(drvPath);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -187,7 +208,7 @@ void Store::queryMissing(const std::vector<DerivedPath> & targets,
|
|||
/* true for regular derivations, and CA derivations for which we
|
||||
have a trust mapping for all wanted outputs. */
|
||||
auto knownOutputPaths = true;
|
||||
for (auto & [outputName, pathOpt] : queryPartialDerivationOutputMap(bfd.drvPath)) {
|
||||
for (auto & [outputName, pathOpt] : queryPartialDerivationOutputMap(drvPath)) {
|
||||
if (!pathOpt) {
|
||||
knownOutputPaths = false;
|
||||
break;
|
||||
|
@ -197,15 +218,45 @@ void Store::queryMissing(const std::vector<DerivedPath> & targets,
|
|||
}
|
||||
if (knownOutputPaths && invalid.empty()) return;
|
||||
|
||||
auto drv = make_ref<Derivation>(derivationFromPath(bfd.drvPath));
|
||||
ParsedDerivation parsedDrv(StorePath(bfd.drvPath), *drv);
|
||||
auto drv = make_ref<Derivation>(derivationFromPath(drvPath));
|
||||
ParsedDerivation parsedDrv(StorePath(drvPath), *drv);
|
||||
|
||||
if (!knownOutputPaths && settings.useSubstitutes && parsedDrv.substitutesAllowed()) {
|
||||
experimentalFeatureSettings.require(Xp::CaDerivations);
|
||||
|
||||
// If there are unknown output paths, attempt to find if the
|
||||
// paths are known to substituters through a realisation.
|
||||
auto outputHashes = staticOutputHashes(*this, *drv);
|
||||
knownOutputPaths = true;
|
||||
|
||||
for (auto [outputName, hash] : outputHashes) {
|
||||
if (!bfd.outputs.contains(outputName))
|
||||
continue;
|
||||
|
||||
bool found = false;
|
||||
for (auto &sub : getDefaultSubstituters()) {
|
||||
auto realisation = sub->queryRealisation({hash, outputName});
|
||||
if (!realisation)
|
||||
continue;
|
||||
found = true;
|
||||
if (!isValidPath(realisation->outPath))
|
||||
invalid.insert(realisation->outPath);
|
||||
break;
|
||||
}
|
||||
if (!found) {
|
||||
// Some paths did not have a realisation, this must be built.
|
||||
knownOutputPaths = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (knownOutputPaths && settings.useSubstitutes && parsedDrv.substitutesAllowed()) {
|
||||
auto drvState = make_ref<Sync<DrvState>>(DrvState(invalid.size()));
|
||||
for (auto & output : invalid)
|
||||
pool.enqueue(std::bind(checkOutput, bfd.drvPath, drv, output, drvState));
|
||||
pool.enqueue(std::bind(checkOutput, drvPath, drv, output, drvState));
|
||||
} else
|
||||
mustBuildDrv(bfd.drvPath, *drv);
|
||||
mustBuildDrv(drvPath, *drv);
|
||||
|
||||
},
|
||||
[&](const DerivedPath::Opaque & bo) {
|
||||
|
@ -280,28 +331,48 @@ std::map<DrvOutput, StorePath> drvOutputReferences(
|
|||
std::map<DrvOutput, StorePath> drvOutputReferences(
|
||||
Store & store,
|
||||
const Derivation & drv,
|
||||
const StorePath & outputPath)
|
||||
const StorePath & outputPath,
|
||||
Store * evalStore_)
|
||||
{
|
||||
auto & evalStore = evalStore_ ? *evalStore_ : store;
|
||||
|
||||
std::set<Realisation> inputRealisations;
|
||||
|
||||
for (const auto & [inputDrv, outputNames] : drv.inputDrvs) {
|
||||
const auto outputHashes =
|
||||
staticOutputHashes(store, store.readDerivation(inputDrv));
|
||||
for (const auto & outputName : outputNames) {
|
||||
auto outputHash = get(outputHashes, outputName);
|
||||
if (!outputHash)
|
||||
throw Error(
|
||||
"output '%s' of derivation '%s' isn't realised", outputName,
|
||||
store.printStorePath(inputDrv));
|
||||
auto thisRealisation = store.queryRealisation(
|
||||
DrvOutput{*outputHash, outputName});
|
||||
if (!thisRealisation)
|
||||
throw Error(
|
||||
"output '%s' of derivation '%s' isn't built", outputName,
|
||||
store.printStorePath(inputDrv));
|
||||
inputRealisations.insert(*thisRealisation);
|
||||
std::function<void(const StorePath &, const DerivedPathMap<StringSet>::ChildNode &)> accumRealisations;
|
||||
|
||||
accumRealisations = [&](const StorePath & inputDrv, const DerivedPathMap<StringSet>::ChildNode & inputNode) {
|
||||
if (!inputNode.value.empty()) {
|
||||
auto outputHashes =
|
||||
staticOutputHashes(evalStore, evalStore.readDerivation(inputDrv));
|
||||
for (const auto & outputName : inputNode.value) {
|
||||
auto outputHash = get(outputHashes, outputName);
|
||||
if (!outputHash)
|
||||
throw Error(
|
||||
"output '%s' of derivation '%s' isn't realised", outputName,
|
||||
store.printStorePath(inputDrv));
|
||||
auto thisRealisation = store.queryRealisation(
|
||||
DrvOutput{*outputHash, outputName});
|
||||
if (!thisRealisation)
|
||||
throw Error(
|
||||
"output '%s' of derivation '%s' isn’t built", outputName,
|
||||
store.printStorePath(inputDrv));
|
||||
inputRealisations.insert(*thisRealisation);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!inputNode.value.empty()) {
|
||||
auto d = makeConstantStorePathRef(inputDrv);
|
||||
for (const auto & [outputName, childNode] : inputNode.childMap) {
|
||||
SingleDerivedPath next = SingleDerivedPath::Built { d, outputName };
|
||||
accumRealisations(
|
||||
// TODO deep resolutions for dynamic derivations, issue #8947, would go here.
|
||||
resolveDerivedPath(store, next, evalStore_),
|
||||
childNode);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for (const auto & [inputDrv, inputNode] : drv.inputDrvs.map)
|
||||
accumRealisations(inputDrv, inputNode);
|
||||
|
||||
auto info = store.queryPathInfo(outputPath);
|
||||
|
||||
|
@ -310,45 +381,90 @@ std::map<DrvOutput, StorePath> drvOutputReferences(
|
|||
|
||||
OutputPathMap resolveDerivedPath(Store & store, const DerivedPath::Built & bfd, Store * evalStore_)
|
||||
{
|
||||
auto & evalStore = evalStore_ ? *evalStore_ : store;
|
||||
auto drvPath = resolveDerivedPath(store, *bfd.drvPath, evalStore_);
|
||||
|
||||
OutputPathMap outputs;
|
||||
auto drv = evalStore.readDerivation(bfd.drvPath);
|
||||
auto outputHashes = staticOutputHashes(store, drv);
|
||||
auto drvOutputs = drv.outputsAndOptPaths(store);
|
||||
auto outputNames = std::visit(overloaded {
|
||||
auto outputsOpt_ = store.queryPartialDerivationOutputMap(drvPath, evalStore_);
|
||||
|
||||
auto outputsOpt = std::visit(overloaded {
|
||||
[&](const OutputsSpec::All &) {
|
||||
StringSet names;
|
||||
for (auto & [outputName, _] : drv.outputs)
|
||||
names.insert(outputName);
|
||||
return names;
|
||||
// Keep all outputs
|
||||
return std::move(outputsOpt_);
|
||||
},
|
||||
[&](const OutputsSpec::Names & names) {
|
||||
return static_cast<std::set<std::string>>(names);
|
||||
// Get just those mentioned by name
|
||||
std::map<std::string, std::optional<StorePath>> outputsOpt;
|
||||
for (auto & output : names) {
|
||||
auto * pOutputPathOpt = get(outputsOpt_, output);
|
||||
if (!pOutputPathOpt)
|
||||
throw Error(
|
||||
"the derivation '%s' doesn't have an output named '%s'",
|
||||
bfd.drvPath->to_string(store), output);
|
||||
outputsOpt.insert_or_assign(output, std::move(*pOutputPathOpt));
|
||||
}
|
||||
return outputsOpt;
|
||||
},
|
||||
}, bfd.outputs.raw());
|
||||
for (auto & output : outputNames) {
|
||||
auto outputHash = get(outputHashes, output);
|
||||
if (!outputHash)
|
||||
throw Error(
|
||||
"the derivation '%s' doesn't have an output named '%s'",
|
||||
store.printStorePath(bfd.drvPath), output);
|
||||
if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations)) {
|
||||
DrvOutput outputId { *outputHash, output };
|
||||
auto realisation = store.queryRealisation(outputId);
|
||||
if (!realisation)
|
||||
throw MissingRealisation(outputId);
|
||||
outputs.insert_or_assign(output, realisation->outPath);
|
||||
} else {
|
||||
// If ca-derivations isn't enabled, assume that
|
||||
// the output path is statically known.
|
||||
auto drvOutput = get(drvOutputs, output);
|
||||
assert(drvOutput);
|
||||
assert(drvOutput->second);
|
||||
outputs.insert_or_assign(output, *drvOutput->second);
|
||||
}
|
||||
}, bfd.outputs.raw);
|
||||
|
||||
OutputPathMap outputs;
|
||||
for (auto & [outputName, outputPathOpt] : outputsOpt) {
|
||||
if (!outputPathOpt)
|
||||
throw MissingRealisation(bfd.drvPath->to_string(store), outputName);
|
||||
auto & outputPath = *outputPathOpt;
|
||||
outputs.insert_or_assign(outputName, outputPath);
|
||||
}
|
||||
return outputs;
|
||||
}
|
||||
|
||||
|
||||
StorePath resolveDerivedPath(Store & store, const SingleDerivedPath & req, Store * evalStore_)
|
||||
{
|
||||
auto & evalStore = evalStore_ ? *evalStore_ : store;
|
||||
|
||||
return std::visit(overloaded {
|
||||
[&](const SingleDerivedPath::Opaque & bo) {
|
||||
return bo.path;
|
||||
},
|
||||
[&](const SingleDerivedPath::Built & bfd) {
|
||||
auto drvPath = resolveDerivedPath(store, *bfd.drvPath, evalStore_);
|
||||
auto outputPaths = evalStore.queryPartialDerivationOutputMap(drvPath, evalStore_);
|
||||
if (outputPaths.count(bfd.output) == 0)
|
||||
throw Error("derivation '%s' does not have an output named '%s'",
|
||||
store.printStorePath(drvPath), bfd.output);
|
||||
auto & optPath = outputPaths.at(bfd.output);
|
||||
if (!optPath)
|
||||
throw MissingRealisation(bfd.drvPath->to_string(store), bfd.output);
|
||||
return *optPath;
|
||||
},
|
||||
}, req.raw());
|
||||
}
|
||||
|
||||
|
||||
OutputPathMap resolveDerivedPath(Store & store, const DerivedPath::Built & bfd)
|
||||
{
|
||||
auto drvPath = resolveDerivedPath(store, *bfd.drvPath);
|
||||
auto outputMap = store.queryDerivationOutputMap(drvPath);
|
||||
auto outputsLeft = std::visit(overloaded {
|
||||
[&](const OutputsSpec::All &) {
|
||||
return StringSet {};
|
||||
},
|
||||
[&](const OutputsSpec::Names & names) {
|
||||
return static_cast<StringSet>(names);
|
||||
},
|
||||
}, bfd.outputs.raw);
|
||||
for (auto iter = outputMap.begin(); iter != outputMap.end();) {
|
||||
auto & outputName = iter->first;
|
||||
if (bfd.outputs.contains(outputName)) {
|
||||
outputsLeft.erase(outputName);
|
||||
++iter;
|
||||
} else {
|
||||
iter = outputMap.erase(iter);
|
||||
}
|
||||
}
|
||||
if (!outputsLeft.empty())
|
||||
throw Error("derivation '%s' does not have an outputs %s",
|
||||
store.printStorePath(drvPath),
|
||||
concatStringsSep(", ", quoteStrings(std::get<OutputsSpec::Names>(bfd.outputs.raw))));
|
||||
return outputMap;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
18
src/libstore/mounted-ssh-store.md
Normal file
18
src/libstore/mounted-ssh-store.md
Normal 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.
|
||||
)"
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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(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(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)
|
||||
|
|
|
@ -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(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(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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
#include "util.hh"
|
||||
#include "local-store.hh"
|
||||
#include "globals.hh"
|
||||
#include "signals.hh"
|
||||
#include "posix-fs-canonicalise.hh"
|
||||
#include "posix-source-accessor.hh"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
@ -145,17 +147,27 @@ 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(Base32, true));
|
||||
Hash hash = ({
|
||||
PosixSourceAccessor accessor;
|
||||
hashPath(
|
||||
accessor, CanonPath { path },
|
||||
FileIngestionMethod::Recursive, HashAlgorithm::SHA256).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(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 != ({
|
||||
PosixSourceAccessor accessor;
|
||||
hashPath(
|
||||
accessor, CanonPath { linkPath },
|
||||
FileIngestionMethod::Recursive, HashAlgorithm::SHA256).first;
|
||||
})))
|
||||
{
|
||||
// XXX: Consider overwriting linkPath with our valid version.
|
||||
warn("removing corrupted link '%s'", linkPath);
|
||||
|
|
|
@ -17,7 +17,7 @@ bool OutputsSpec::contains(const std::string & outputName) const
|
|||
[&](const OutputsSpec::Names & outputNames) {
|
||||
return outputNames.count(outputName) > 0;
|
||||
},
|
||||
}, raw());
|
||||
}, raw);
|
||||
}
|
||||
|
||||
static std::string outputSpecRegexStr =
|
||||
|
@ -49,7 +49,7 @@ OutputsSpec OutputsSpec::parse(std::string_view s)
|
|||
std::optional spec = parseOpt(s);
|
||||
if (!spec)
|
||||
throw Error("invalid outputs specifier '%s'", s);
|
||||
return *spec;
|
||||
return std::move(*spec);
|
||||
}
|
||||
|
||||
|
||||
|
@ -63,7 +63,7 @@ std::optional<std::pair<std::string_view, ExtendedOutputsSpec>> ExtendedOutputsS
|
|||
auto specOpt = OutputsSpec::parseOpt(s.substr(found + 1));
|
||||
if (!specOpt)
|
||||
return std::nullopt;
|
||||
return std::pair { s.substr(0, found), ExtendedOutputsSpec::Explicit { *std::move(specOpt) } };
|
||||
return std::pair { s.substr(0, found), ExtendedOutputsSpec::Explicit { std::move(*specOpt) } };
|
||||
}
|
||||
|
||||
|
||||
|
@ -85,7 +85,7 @@ std::string OutputsSpec::to_string() const
|
|||
[&](const OutputsSpec::Names & outputNames) -> std::string {
|
||||
return concatStringsSep(",", outputNames);
|
||||
},
|
||||
}, raw());
|
||||
}, raw);
|
||||
}
|
||||
|
||||
|
||||
|
@ -98,7 +98,7 @@ std::string ExtendedOutputsSpec::to_string() const
|
|||
[&](const ExtendedOutputsSpec::Explicit & outputSpec) -> std::string {
|
||||
return "^" + outputSpec.to_string();
|
||||
},
|
||||
}, raw());
|
||||
}, raw);
|
||||
}
|
||||
|
||||
|
||||
|
@ -118,9 +118,9 @@ OutputsSpec OutputsSpec::union_(const OutputsSpec & that) const
|
|||
ret.insert(thoseNames.begin(), thoseNames.end());
|
||||
return ret;
|
||||
},
|
||||
}, that.raw());
|
||||
}, that.raw);
|
||||
},
|
||||
}, raw());
|
||||
}, raw);
|
||||
}
|
||||
|
||||
|
||||
|
@ -142,9 +142,9 @@ bool OutputsSpec::isSubsetOf(const OutputsSpec & that) const
|
|||
ret = false;
|
||||
return ret;
|
||||
},
|
||||
}, raw());
|
||||
}, raw);
|
||||
},
|
||||
}, that.raw());
|
||||
}, that.raw);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -169,7 +169,7 @@ void adl_serializer<OutputsSpec>::to_json(json & json, OutputsSpec t) {
|
|||
[&](const OutputsSpec::Names & names) {
|
||||
json = names;
|
||||
},
|
||||
}, t.raw());
|
||||
}, t.raw);
|
||||
}
|
||||
|
||||
|
||||
|
@ -189,7 +189,7 @@ void adl_serializer<ExtendedOutputsSpec>::to_json(json & json, ExtendedOutputsSp
|
|||
[&](const ExtendedOutputsSpec::Explicit & e) {
|
||||
adl_serializer<OutputsSpec>::to_json(json, e);
|
||||
},
|
||||
}, t.raw());
|
||||
}, t.raw);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -6,63 +6,70 @@
|
|||
#include <set>
|
||||
#include <variant>
|
||||
|
||||
#include "comparator.hh"
|
||||
#include "json-impls.hh"
|
||||
#include "comparator.hh"
|
||||
#include "variant-wrapper.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
/**
|
||||
* A non-empty set of outputs, specified by name
|
||||
* An (owned) output name. Just a type alias used to make code more
|
||||
* readible.
|
||||
*/
|
||||
struct OutputNames : std::set<std::string> {
|
||||
using std::set<std::string>::set;
|
||||
|
||||
/* These need to be "inherited manually" */
|
||||
|
||||
OutputNames(const std::set<std::string> & s)
|
||||
: std::set<std::string>(s)
|
||||
{ assert(!empty()); }
|
||||
|
||||
/**
|
||||
* Needs to be "inherited manually"
|
||||
*/
|
||||
OutputNames(std::set<std::string> && s)
|
||||
: std::set<std::string>(s)
|
||||
{ assert(!empty()); }
|
||||
|
||||
/* This set should always be non-empty, so we delete this
|
||||
constructor in order make creating empty ones by mistake harder.
|
||||
*/
|
||||
OutputNames() = delete;
|
||||
};
|
||||
typedef std::string OutputName;
|
||||
|
||||
/**
|
||||
* The set of all outputs, without needing to name them explicitly
|
||||
* A borrowed output name. Just a type alias used to make code more
|
||||
* readible.
|
||||
*/
|
||||
struct AllOutputs : std::monostate { };
|
||||
typedef std::string_view OutputNameView;
|
||||
|
||||
typedef std::variant<AllOutputs, OutputNames> _OutputsSpecRaw;
|
||||
struct OutputsSpec {
|
||||
/**
|
||||
* A non-empty set of outputs, specified by name
|
||||
*/
|
||||
struct Names : std::set<OutputName> {
|
||||
using std::set<OutputName>::set;
|
||||
|
||||
struct OutputsSpec : _OutputsSpecRaw {
|
||||
using Raw = _OutputsSpecRaw;
|
||||
using Raw::Raw;
|
||||
/* These need to be "inherited manually" */
|
||||
|
||||
Names(const std::set<OutputName> & s)
|
||||
: std::set<OutputName>(s)
|
||||
{ assert(!empty()); }
|
||||
|
||||
/**
|
||||
* Needs to be "inherited manually"
|
||||
*/
|
||||
Names(std::set<OutputName> && s)
|
||||
: std::set<OutputName>(s)
|
||||
{ assert(!empty()); }
|
||||
|
||||
/* This set should always be non-empty, so we delete this
|
||||
constructor in order make creating empty ones by mistake harder.
|
||||
*/
|
||||
Names() = delete;
|
||||
};
|
||||
|
||||
/**
|
||||
* The set of all outputs, without needing to name them explicitly
|
||||
*/
|
||||
struct All : std::monostate { };
|
||||
|
||||
typedef std::variant<All, Names> Raw;
|
||||
|
||||
Raw raw;
|
||||
|
||||
GENERATE_CMP(OutputsSpec, me->raw);
|
||||
|
||||
MAKE_WRAPPER_CONSTRUCTOR(OutputsSpec);
|
||||
|
||||
/**
|
||||
* Force choosing a variant
|
||||
*/
|
||||
OutputsSpec() = delete;
|
||||
|
||||
using Names = OutputNames;
|
||||
using All = AllOutputs;
|
||||
|
||||
inline const Raw & raw() const {
|
||||
return static_cast<const Raw &>(*this);
|
||||
}
|
||||
|
||||
inline Raw & raw() {
|
||||
return static_cast<Raw &>(*this);
|
||||
}
|
||||
|
||||
bool contains(const std::string & output) const;
|
||||
bool contains(const OutputName & output) const;
|
||||
|
||||
/**
|
||||
* Create a new OutputsSpec which is the union of this and that.
|
||||
|
@ -84,20 +91,22 @@ struct OutputsSpec : _OutputsSpecRaw {
|
|||
std::string to_string() const;
|
||||
};
|
||||
|
||||
struct DefaultOutputs : std::monostate { };
|
||||
|
||||
typedef std::variant<DefaultOutputs, OutputsSpec> _ExtendedOutputsSpecRaw;
|
||||
|
||||
struct ExtendedOutputsSpec : _ExtendedOutputsSpecRaw {
|
||||
using Raw = _ExtendedOutputsSpecRaw;
|
||||
using Raw::Raw;
|
||||
|
||||
using Default = DefaultOutputs;
|
||||
struct ExtendedOutputsSpec {
|
||||
struct Default : std::monostate { };
|
||||
using Explicit = OutputsSpec;
|
||||
|
||||
inline const Raw & raw() const {
|
||||
return static_cast<const Raw &>(*this);
|
||||
}
|
||||
typedef std::variant<Default, Explicit> Raw;
|
||||
|
||||
Raw raw;
|
||||
|
||||
GENERATE_CMP(ExtendedOutputsSpec, me->raw);
|
||||
|
||||
MAKE_WRAPPER_CONSTRUCTOR(ExtendedOutputsSpec);
|
||||
|
||||
/**
|
||||
* Force choosing a variant
|
||||
*/
|
||||
ExtendedOutputsSpec() = delete;
|
||||
|
||||
/**
|
||||
* Parse a string of the form 'prefix^output1,...outputN' or
|
||||
|
|
|
@ -122,7 +122,7 @@ bool ParsedDerivation::willBuildLocally(Store & localStore) const
|
|||
|
||||
bool ParsedDerivation::substitutesAllowed() const
|
||||
{
|
||||
return getBoolAttr("allowSubstitutes", true);
|
||||
return settings.alwaysAllowSubstitutes ? true : getBoolAttr("allowSubstitutes", true);
|
||||
}
|
||||
|
||||
bool ParsedDerivation::useUidRange() const
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,25 +1,46 @@
|
|||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include "path-info.hh"
|
||||
#include "worker-protocol.hh"
|
||||
#include "store-api.hh"
|
||||
#include "json-utils.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
GENERATE_CMP_EXT(
|
||||
,
|
||||
UnkeyedValidPathInfo,
|
||||
me->deriver,
|
||||
me->narHash,
|
||||
me->references,
|
||||
me->registrationTime,
|
||||
me->narSize,
|
||||
//me->id,
|
||||
me->ultimate,
|
||||
me->sigs,
|
||||
me->ca);
|
||||
|
||||
GENERATE_CMP_EXT(
|
||||
,
|
||||
ValidPathInfo,
|
||||
me->path,
|
||||
static_cast<const UnkeyedValidPathInfo &>(*me));
|
||||
|
||||
std::string ValidPathInfo::fingerprint(const Store & store) const
|
||||
{
|
||||
if (narSize == 0)
|
||||
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(Base32, true) + ";"
|
||||
+ std::to_string(narSize) + ";"
|
||||
"1;" + store.printStorePath(path) + ";"
|
||||
+ narHash.to_string(HashFormat::Nix32, true) + ";"
|
||||
+ std::to_string(narSize) + ";"
|
||||
+ concatStringsSep(",", store.printStorePathSet(references));
|
||||
}
|
||||
|
||||
|
||||
void ValidPathInfo::sign(const Store & store, const SecretKey & secretKey)
|
||||
void ValidPathInfo::sign(const Store & store, const Signer & signer)
|
||||
{
|
||||
sigs.insert(secretKey.signDetached(fingerprint(store)));
|
||||
sigs.insert(signer.signDetached(fingerprint(store)));
|
||||
}
|
||||
|
||||
std::optional<ContentAddressWithReferences> ValidPathInfo::contentAddressWithReferences() const
|
||||
|
@ -28,14 +49,14 @@ std::optional<ContentAddressWithReferences> ValidPathInfo::contentAddressWithRef
|
|||
return std::nullopt;
|
||||
|
||||
return std::visit(overloaded {
|
||||
[&](const TextHash & th) -> ContentAddressWithReferences {
|
||||
[&](const TextIngestionMethod &) -> ContentAddressWithReferences {
|
||||
assert(references.count(path) == 0);
|
||||
return TextInfo {
|
||||
.hash = th,
|
||||
.hash = ca->hash,
|
||||
.references = references,
|
||||
};
|
||||
},
|
||||
[&](const FixedOutputHash & foh) -> ContentAddressWithReferences {
|
||||
[&](const FileIngestionMethod & m2) -> ContentAddressWithReferences {
|
||||
auto refs = references;
|
||||
bool hasSelfReference = false;
|
||||
if (refs.count(path)) {
|
||||
|
@ -43,14 +64,15 @@ std::optional<ContentAddressWithReferences> ValidPathInfo::contentAddressWithRef
|
|||
refs.erase(path);
|
||||
}
|
||||
return FixedOutputInfo {
|
||||
.hash = foh,
|
||||
.method = m2,
|
||||
.hash = ca->hash,
|
||||
.references = {
|
||||
.others = std::move(refs),
|
||||
.self = hasSelfReference,
|
||||
},
|
||||
};
|
||||
},
|
||||
}, ca->raw);
|
||||
}, ca->method.raw);
|
||||
}
|
||||
|
||||
bool ValidPathInfo::isContentAddressed(const Store & store) const
|
||||
|
@ -97,69 +119,122 @@ Strings ValidPathInfo::shortRefs() const
|
|||
return refs;
|
||||
}
|
||||
|
||||
|
||||
ValidPathInfo::ValidPathInfo(
|
||||
const Store & store,
|
||||
std::string_view name,
|
||||
ContentAddressWithReferences && ca,
|
||||
Hash narHash)
|
||||
: path(store.makeFixedOutputPathFromCA(name, ca))
|
||||
, narHash(narHash)
|
||||
: UnkeyedValidPathInfo(narHash)
|
||||
, path(store.makeFixedOutputPathFromCA(name, ca))
|
||||
{
|
||||
std::visit(overloaded {
|
||||
[this](TextInfo && ti) {
|
||||
this->references = std::move(ti.references);
|
||||
this->ca = std::move((TextHash &&) ti);
|
||||
this->ca = ContentAddress {
|
||||
.method = TextIngestionMethod {},
|
||||
.hash = std::move(ti.hash),
|
||||
};
|
||||
},
|
||||
[this](FixedOutputInfo && foi) {
|
||||
this->references = std::move(foi.references.others);
|
||||
if (foi.references.self)
|
||||
this->references.insert(path);
|
||||
this->ca = std::move((FixedOutputHash &&) foi);
|
||||
this->ca = ContentAddress {
|
||||
.method = std::move(foi.method),
|
||||
.hash = std::move(foi.hash),
|
||||
};
|
||||
},
|
||||
}, std::move(ca).raw);
|
||||
}
|
||||
|
||||
|
||||
ValidPathInfo ValidPathInfo::read(Source & source, const Store & store, unsigned int format)
|
||||
{
|
||||
return read(source, store, format, store.parseStorePath(readString(source)));
|
||||
}
|
||||
|
||||
ValidPathInfo ValidPathInfo::read(Source & source, const Store & store, unsigned int format, StorePath && path)
|
||||
{
|
||||
auto deriver = readString(source);
|
||||
auto narHash = Hash::parseAny(readString(source), htSHA256);
|
||||
ValidPathInfo info(path, narHash);
|
||||
if (deriver != "") info.deriver = store.parseStorePath(deriver);
|
||||
info.references = WorkerProto<StorePathSet>::read(store, source);
|
||||
source >> info.registrationTime >> info.narSize;
|
||||
if (format >= 16) {
|
||||
source >> info.ultimate;
|
||||
info.sigs = readStrings<StringSet>(source);
|
||||
info.ca = ContentAddress::parseOpt(readString(source));
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
|
||||
void ValidPathInfo::write(
|
||||
Sink & sink,
|
||||
nlohmann::json UnkeyedValidPathInfo::toJSON(
|
||||
const Store & store,
|
||||
unsigned int format,
|
||||
bool includePath) const
|
||||
bool includeImpureInfo,
|
||||
HashFormat hashFormat) const
|
||||
{
|
||||
if (includePath)
|
||||
sink << store.printStorePath(path);
|
||||
sink << (deriver ? store.printStorePath(*deriver) : "")
|
||||
<< narHash.to_string(Base16, false);
|
||||
workerProtoWrite(store, sink, references);
|
||||
sink << registrationTime << narSize;
|
||||
if (format >= 16) {
|
||||
sink << ultimate
|
||||
<< sigs
|
||||
<< renderContentAddress(ca);
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#pragma once
|
||||
///@file
|
||||
|
||||
#include "crypto.hh"
|
||||
#include "signature/signer.hh"
|
||||
#include "path.hh"
|
||||
#include "hash.hh"
|
||||
#include "content-address.hh"
|
||||
|
@ -29,12 +29,11 @@ struct SubstitutablePathInfo
|
|||
uint64_t narSize;
|
||||
};
|
||||
|
||||
typedef std::map<StorePath, SubstitutablePathInfo> SubstitutablePathInfos;
|
||||
using SubstitutablePathInfos = std::map<StorePath, SubstitutablePathInfo>;
|
||||
|
||||
|
||||
struct ValidPathInfo
|
||||
struct UnkeyedValidPathInfo
|
||||
{
|
||||
StorePath path;
|
||||
std::optional<StorePath> deriver;
|
||||
/**
|
||||
* \todo document this
|
||||
|
@ -43,7 +42,7 @@ struct ValidPathInfo
|
|||
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
|
||||
|
@ -72,13 +71,31 @@ struct ValidPathInfo
|
|||
*/
|
||||
std::optional<ContentAddress> ca;
|
||||
|
||||
bool operator == (const ValidPathInfo & i) const
|
||||
{
|
||||
return
|
||||
path == i.path
|
||||
&& narHash == i.narHash
|
||||
&& references == i.references;
|
||||
}
|
||||
UnkeyedValidPathInfo(const UnkeyedValidPathInfo & other) = default;
|
||||
|
||||
UnkeyedValidPathInfo(Hash narHash) : narHash(narHash) { };
|
||||
|
||||
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 {
|
||||
StorePath path;
|
||||
|
||||
DECLARE_CMP(ValidPathInfo);
|
||||
|
||||
/**
|
||||
* Return a fingerprint of the store path to be used in binary
|
||||
|
@ -90,13 +107,13 @@ struct ValidPathInfo
|
|||
*/
|
||||
std::string fingerprint(const Store & store) const;
|
||||
|
||||
void sign(const Store & store, const SecretKey & secretKey);
|
||||
void sign(const Store & store, const Signer & signer);
|
||||
|
||||
/**
|
||||
* @return The `ContentAddressWithReferences` that determines the
|
||||
* store path for a content-addressed store object, `std::nullopt`
|
||||
* for an input-addressed store object.
|
||||
*/
|
||||
/**
|
||||
* @return The `ContentAddressWithReferences` that determines the
|
||||
* store path for a content-addressed store object, `std::nullopt`
|
||||
* for an input-addressed store object.
|
||||
*/
|
||||
std::optional<ContentAddressWithReferences> contentAddressWithReferences() const;
|
||||
|
||||
/**
|
||||
|
@ -122,20 +139,15 @@ struct ValidPathInfo
|
|||
|
||||
ValidPathInfo(const ValidPathInfo & other) = default;
|
||||
|
||||
ValidPathInfo(StorePath && path, Hash narHash) : path(std::move(path)), narHash(narHash) { };
|
||||
ValidPathInfo(const StorePath & path, Hash narHash) : path(path), narHash(narHash) { };
|
||||
ValidPathInfo(StorePath && path, UnkeyedValidPathInfo info) : UnkeyedValidPathInfo(info), path(std::move(path)) { };
|
||||
ValidPathInfo(const StorePath & path, UnkeyedValidPathInfo info) : UnkeyedValidPathInfo(info), path(path) { };
|
||||
|
||||
ValidPathInfo(const Store & store,
|
||||
std::string_view name, ContentAddressWithReferences && ca, Hash narHash);
|
||||
|
||||
virtual ~ValidPathInfo() { }
|
||||
|
||||
static ValidPathInfo read(Source & source, const Store & store, unsigned int format);
|
||||
static ValidPathInfo read(Source & source, const Store & store, unsigned int format, StorePath && path);
|
||||
|
||||
void write(Sink & sink, const Store & store, unsigned int format, bool includePath = true) const;
|
||||
};
|
||||
|
||||
typedef std::map<StorePath, ValidPathInfo> ValidPathInfos;
|
||||
using ValidPathInfos = std::map<StorePath, ValidPathInfo>;
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma once
|
||||
///@file
|
||||
|
||||
#include "references.hh"
|
||||
#include "path.hh"
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
|
||||
namespace nix {
|
||||
|
||||
static constexpr std::string_view nameRegexStr = R"([0-9a-zA-Z\+\-\._\?=]+)";
|
||||
static constexpr std::string_view nameRegexStr = R"([0-9a-zA-Z\+\-_\?=][0-9a-zA-Z\+\-\._\?=]*)";
|
||||
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
@ -16,10 +16,16 @@ std::string StorePathWithOutputs::to_string(const Store & store) const
|
|||
DerivedPath StorePathWithOutputs::toDerivedPath() const
|
||||
{
|
||||
if (!outputs.empty()) {
|
||||
return DerivedPath::Built { path, OutputsSpec::Names { outputs } };
|
||||
return DerivedPath::Built {
|
||||
.drvPath = makeConstantStorePathRef(path),
|
||||
.outputs = OutputsSpec::Names { outputs },
|
||||
};
|
||||
} else if (path.isDerivation()) {
|
||||
assert(outputs.empty());
|
||||
return DerivedPath::Built { path, OutputsSpec::All { } };
|
||||
return DerivedPath::Built {
|
||||
.drvPath = makeConstantStorePathRef(path),
|
||||
.outputs = OutputsSpec::All { },
|
||||
};
|
||||
} else {
|
||||
return DerivedPath::Opaque { path };
|
||||
}
|
||||
|
@ -34,29 +40,36 @@ std::vector<DerivedPath> toDerivedPaths(const std::vector<StorePathWithOutputs>
|
|||
}
|
||||
|
||||
|
||||
std::variant<StorePathWithOutputs, StorePath> StorePathWithOutputs::tryFromDerivedPath(const DerivedPath & p)
|
||||
StorePathWithOutputs::ParseResult StorePathWithOutputs::tryFromDerivedPath(const DerivedPath & p)
|
||||
{
|
||||
return std::visit(overloaded {
|
||||
[&](const DerivedPath::Opaque & bo) -> std::variant<StorePathWithOutputs, StorePath> {
|
||||
[&](const DerivedPath::Opaque & bo) -> StorePathWithOutputs::ParseResult {
|
||||
if (bo.path.isDerivation()) {
|
||||
// drv path gets interpreted as "build", not "get drv file itself"
|
||||
return bo.path;
|
||||
}
|
||||
return StorePathWithOutputs { bo.path };
|
||||
},
|
||||
[&](const DerivedPath::Built & bfd) -> std::variant<StorePathWithOutputs, StorePath> {
|
||||
return StorePathWithOutputs {
|
||||
.path = bfd.drvPath,
|
||||
// Use legacy encoding of wildcard as empty set
|
||||
.outputs = std::visit(overloaded {
|
||||
[&](const OutputsSpec::All &) -> StringSet {
|
||||
return {};
|
||||
},
|
||||
[&](const OutputsSpec::Names & outputs) {
|
||||
return static_cast<StringSet>(outputs);
|
||||
},
|
||||
}, bfd.outputs.raw()),
|
||||
};
|
||||
[&](const DerivedPath::Built & bfd) -> StorePathWithOutputs::ParseResult {
|
||||
return std::visit(overloaded {
|
||||
[&](const SingleDerivedPath::Opaque & bo) -> StorePathWithOutputs::ParseResult {
|
||||
return StorePathWithOutputs {
|
||||
.path = bo.path,
|
||||
// Use legacy encoding of wildcard as empty set
|
||||
.outputs = std::visit(overloaded {
|
||||
[&](const OutputsSpec::All &) -> StringSet {
|
||||
return {};
|
||||
},
|
||||
[&](const OutputsSpec::Names & outputs) {
|
||||
return static_cast<StringSet>(outputs);
|
||||
},
|
||||
}, bfd.outputs.raw),
|
||||
};
|
||||
},
|
||||
[&](const SingleDerivedPath::Built &) -> StorePathWithOutputs::ParseResult {
|
||||
return std::monostate {};
|
||||
},
|
||||
}, bfd.drvPath->raw());
|
||||
},
|
||||
}, p.raw());
|
||||
}
|
||||
|
@ -72,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) };
|
||||
|
|
|
@ -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,25 +21,27 @@ 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;
|
||||
|
||||
static std::variant<StorePathWithOutputs, StorePath> tryFromDerivedPath(const DerivedPath &);
|
||||
typedef std::variant<StorePathWithOutputs, StorePath, std::monostate> ParseResult;
|
||||
|
||||
static StorePathWithOutputs::ParseResult tryFromDerivedPath(const DerivedPath &);
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
#include "store-api.hh"
|
||||
|
||||
#include <sodium.h>
|
||||
#include "store-dir-config.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
@ -11,6 +9,8 @@ static void checkName(std::string_view path, std::string_view name)
|
|||
if (name.size() > StorePath::MaxPathLen)
|
||||
throw BadStorePath("store path '%s' has a name longer than %d characters",
|
||||
path, StorePath::MaxPathLen);
|
||||
if (name[0] == '.')
|
||||
throw BadStorePath("store path '%s' starts with illegal character '.'", path);
|
||||
// See nameRegexStr for the definition
|
||||
for (auto c : name)
|
||||
if (!((c >= '0' && c <= '9')
|
||||
|
@ -33,7 +33,7 @@ StorePath::StorePath(std::string_view _baseName)
|
|||
}
|
||||
|
||||
StorePath::StorePath(const Hash & hash, std::string_view _name)
|
||||
: baseName((hash.to_string(Base32, false) + "-").append(std::string(_name)))
|
||||
: baseName((hash.to_string(HashFormat::Nix32, false) + "-").append(std::string(_name)))
|
||||
{
|
||||
checkName(baseName, name());
|
||||
}
|
||||
|
@ -47,12 +47,10 @@ StorePath StorePath::dummy("ffffffffffffffffffffffffffffffff-x");
|
|||
|
||||
StorePath StorePath::random(std::string_view name)
|
||||
{
|
||||
Hash hash(htSHA1);
|
||||
randombytes_buf(hash.hash, hash.hashSize);
|
||||
return StorePath(hash, name);
|
||||
return StorePath(Hash::random(HashAlgorithm::SHA1), 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)
|
||||
|
@ -60,7 +58,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);
|
||||
|
@ -69,24 +67,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));
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "pathlocks.hh"
|
||||
#include "util.hh"
|
||||
#include "sync.hh"
|
||||
#include "signals.hh"
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstdlib>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#pragma once
|
||||
///@file
|
||||
|
||||
#include "util.hh"
|
||||
#include "file-descriptor.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
|
171
src/libstore/posix-fs-canonicalise.cc
Normal file
171
src/libstore/posix-fs-canonicalise.cc
Normal file
|
@ -0,0 +1,171 @@
|
|||
#if HAVE_ACL_SUPPORT
|
||||
# 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);
|
||||
|
||||
#if HAVE_ACL_SUPPORT
|
||||
/* 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);
|
||||
}
|
||||
|
||||
}
|
45
src/libstore/posix-fs-canonicalise.hh
Normal file
45
src/libstore/posix-fs-canonicalise.hh
Normal 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);
|
||||
|
||||
}
|
|
@ -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>
|
||||
|
@ -183,7 +183,7 @@ void deleteGenerationsGreaterThan(const Path & profile, GenerationNumber max, bo
|
|||
iterDropUntil(gens, i, [&](auto & g) { return g.number == curGen; });
|
||||
|
||||
// Skip over `max` generations, preserving them
|
||||
for (auto keep = 0; i != gens.rend() && keep < max; ++i, ++keep);
|
||||
for (GenerationNumber keep = 0; i != gens.rend() && keep < max; ++i, ++keep);
|
||||
|
||||
// Delete the rest
|
||||
for (; i != gens.rend(); ++i)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "realisation.hh"
|
||||
#include "store-api.hh"
|
||||
#include "closure.hh"
|
||||
#include "signature/local-keys.hh"
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace nix {
|
||||
|
@ -113,9 +114,9 @@ std::string Realisation::fingerprint() const
|
|||
return serialized.dump();
|
||||
}
|
||||
|
||||
void Realisation::sign(const SecretKey & secretKey)
|
||||
void Realisation::sign(const Signer &signer)
|
||||
{
|
||||
signatures.insert(secretKey.signDetached(fingerprint()));
|
||||
signatures.insert(signer.signDetached(fingerprint()));
|
||||
}
|
||||
|
||||
bool Realisation::checkSignature(const PublicKeys & publicKeys, const std::string & sig) const
|
||||
|
|
|
@ -5,9 +5,10 @@
|
|||
|
||||
#include "hash.hh"
|
||||
#include "path.hh"
|
||||
#include "derived-path.hh"
|
||||
#include <nlohmann/json_fwd.hpp>
|
||||
#include "comparator.hh"
|
||||
#include "crypto.hh"
|
||||
#include "signature/signer.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
@ -33,12 +34,12 @@ struct DrvOutput {
|
|||
/**
|
||||
* The name of the output.
|
||||
*/
|
||||
std::string outputName;
|
||||
OutputName outputName;
|
||||
|
||||
std::string to_string() const;
|
||||
|
||||
std::string strHash() const
|
||||
{ return drvHash.to_string(Base16, true); }
|
||||
{ return drvHash.to_string(HashFormat::Base16, true); }
|
||||
|
||||
static DrvOutput parse(const std::string &);
|
||||
|
||||
|
@ -63,7 +64,7 @@ struct Realisation {
|
|||
static Realisation fromJSON(const nlohmann::json& json, const std::string& whence);
|
||||
|
||||
std::string fingerprint() const;
|
||||
void sign(const SecretKey &);
|
||||
void sign(const Signer &);
|
||||
bool checkSignature(const PublicKeys & publicKeys, const std::string & sig) const;
|
||||
size_t checkSignatures(const PublicKeys & publicKeys) const;
|
||||
|
||||
|
@ -83,7 +84,7 @@ struct Realisation {
|
|||
* Since these are the outputs of a single derivation, we know the
|
||||
* output names are unique so we can use them as the map key.
|
||||
*/
|
||||
typedef std::map<std::string, Realisation> SingleDrvOutputs;
|
||||
typedef std::map<OutputName, Realisation> SingleDrvOutputs;
|
||||
|
||||
/**
|
||||
* Collection type for multiple derivations' outputs' `Realisation`s.
|
||||
|
@ -143,9 +144,13 @@ class MissingRealisation : public Error
|
|||
{
|
||||
public:
|
||||
MissingRealisation(DrvOutput & outputId)
|
||||
: Error( "cannot operate on an output of the "
|
||||
: MissingRealisation(outputId.outputName, outputId.strHash())
|
||||
{}
|
||||
MissingRealisation(std::string_view drv, OutputName outputName)
|
||||
: Error( "cannot operate on output '%s' of the "
|
||||
"unbuilt derivation '%s'",
|
||||
outputId.to_string())
|
||||
outputName,
|
||||
drv)
|
||||
{}
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
133
src/libstore/remote-store-connection.hh
Normal file
133
src/libstore/remote-store-connection.hh
Normal file
|
@ -0,0 +1,133 @@
|
|||
#pragma once
|
||||
///@file
|
||||
|
||||
#include "remote-store.hh"
|
||||
#include "worker-protocol.hh"
|
||||
#include "pool.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
/**
|
||||
* Bidirectional connection (send and receive) used by the Remote Store
|
||||
* implementation.
|
||||
*
|
||||
* Contains `Source` and `Sink` for actual communication, along with
|
||||
* other information learned when negotiating the connection.
|
||||
*/
|
||||
struct RemoteStore::Connection
|
||||
{
|
||||
/**
|
||||
* Send with this.
|
||||
*/
|
||||
FdSink to;
|
||||
|
||||
/**
|
||||
* Receive with this.
|
||||
*/
|
||||
FdSource from;
|
||||
|
||||
/**
|
||||
* Worker protocol version used for the connection.
|
||||
*
|
||||
* Despite its name, I think it is actually the maximum version both
|
||||
* sides support. (If the maximum doesn't exist, we would fail to
|
||||
* establish a connection and produce a value of this type.)
|
||||
*/
|
||||
WorkerProto::Version daemonVersion;
|
||||
|
||||
/**
|
||||
* Whether the remote side trusts us or not.
|
||||
*
|
||||
* 3 values: "yes", "no", or `std::nullopt` for "unknown".
|
||||
*
|
||||
* Note that the "remote side" might not be just the end daemon, but
|
||||
* also an intermediary forwarder that can make its own trusting
|
||||
* decisions. This would be the intersection of all their trust
|
||||
* decisions, since it takes only one link in the chain to start
|
||||
* denying operations.
|
||||
*/
|
||||
std::optional<TrustedFlag> remoteTrustsUs;
|
||||
|
||||
/**
|
||||
* The version of the Nix daemon that is processing our requests.
|
||||
*
|
||||
* Do note, it may or may not communicating with another daemon,
|
||||
* rather than being an "end" `LocalStore` or similar.
|
||||
*/
|
||||
std::optional<std::string> daemonNixVersion;
|
||||
|
||||
/**
|
||||
* Time this connection was established.
|
||||
*/
|
||||
std::chrono::time_point<std::chrono::steady_clock> startTime;
|
||||
|
||||
/**
|
||||
* Coercion to `WorkerProto::ReadConn`. This makes it easy to use the
|
||||
* factored out worker protocol searlizers with a
|
||||
* `RemoteStore::Connection`.
|
||||
*
|
||||
* The worker protocol connection types are unidirectional, unlike
|
||||
* this type.
|
||||
*/
|
||||
operator WorkerProto::ReadConn ()
|
||||
{
|
||||
return WorkerProto::ReadConn {
|
||||
.from = from,
|
||||
.version = daemonVersion,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Coercion to `WorkerProto::WriteConn`. This makes it easy to use the
|
||||
* factored out worker protocol searlizers with a
|
||||
* `RemoteStore::Connection`.
|
||||
*
|
||||
* The worker protocol connection types are unidirectional, unlike
|
||||
* this type.
|
||||
*/
|
||||
operator WorkerProto::WriteConn ()
|
||||
{
|
||||
return WorkerProto::WriteConn {
|
||||
.to = to,
|
||||
.version = daemonVersion,
|
||||
};
|
||||
}
|
||||
|
||||
virtual ~Connection();
|
||||
|
||||
virtual void closeWrite() = 0;
|
||||
|
||||
std::exception_ptr processStderr(Sink * sink = 0, Source * source = 0, bool flush = true);
|
||||
};
|
||||
|
||||
/**
|
||||
* A wrapper around Pool<RemoteStore::Connection>::Handle that marks
|
||||
* the connection as bad (causing it to be closed) if a non-daemon
|
||||
* exception is thrown before the handle is closed. Such an exception
|
||||
* causes a deviation from the expected protocol and therefore a
|
||||
* desynchronization between the client and daemon.
|
||||
*/
|
||||
struct RemoteStore::ConnectionHandle
|
||||
{
|
||||
Pool<RemoteStore::Connection>::Handle handle;
|
||||
bool daemonException = false;
|
||||
|
||||
ConnectionHandle(Pool<RemoteStore::Connection>::Handle && handle)
|
||||
: handle(std::move(handle))
|
||||
{ }
|
||||
|
||||
ConnectionHandle(ConnectionHandle && h)
|
||||
: handle(std::move(h.handle))
|
||||
{ }
|
||||
|
||||
~ConnectionHandle();
|
||||
|
||||
RemoteStore::Connection & operator * () { return *handle; }
|
||||
RemoteStore::Connection * operator -> () { return &*handle; }
|
||||
|
||||
void processStderr(Sink * sink = 0, Source * source = 0, bool flush = true);
|
||||
|
||||
void withFramedSink(std::function<void(Sink & sink)> fun);
|
||||
};
|
||||
|
||||
}
|
|
@ -5,7 +5,9 @@
|
|||
#include "remote-fs-accessor.hh"
|
||||
#include "build-result.hh"
|
||||
#include "remote-store.hh"
|
||||
#include "remote-store-connection.hh"
|
||||
#include "worker-protocol.hh"
|
||||
#include "worker-protocol-impl.hh"
|
||||
#include "archive.hh"
|
||||
#include "globals.hh"
|
||||
#include "derivations.hh"
|
||||
|
@ -14,11 +16,13 @@
|
|||
#include "logging.hh"
|
||||
#include "callback.hh"
|
||||
#include "filetransfer.hh"
|
||||
#include "signals.hh"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
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)
|
||||
|
@ -100,7 +104,7 @@ void RemoteStore::initConnection(Connection & conn)
|
|||
}
|
||||
|
||||
if (GET_PROTOCOL_MINOR(conn.daemonVersion) >= 35) {
|
||||
conn.remoteTrustsUs = WorkerProto<std::optional<TrustedFlag>>::read(*this, conn.from);
|
||||
conn.remoteTrustsUs = WorkerProto::Serialise<std::optional<TrustedFlag>>::read(*this, conn);
|
||||
} else {
|
||||
// We don't know the answer; protocol to old.
|
||||
conn.remoteTrustsUs = std::nullopt;
|
||||
|
@ -119,7 +123,7 @@ void RemoteStore::initConnection(Connection & conn)
|
|||
|
||||
void RemoteStore::setOptions(Connection & conn)
|
||||
{
|
||||
conn.to << wopSetOptions
|
||||
conn.to << WorkerProto::Op::SetOptions
|
||||
<< settings.keepFailed
|
||||
<< settings.keepGoing
|
||||
<< settings.tryFallback
|
||||
|
@ -157,48 +161,42 @@ void RemoteStore::setOptions(Connection & conn)
|
|||
}
|
||||
|
||||
|
||||
/* A wrapper around Pool<RemoteStore::Connection>::Handle that marks
|
||||
the connection as bad (causing it to be closed) if a non-daemon
|
||||
exception is thrown before the handle is closed. Such an exception
|
||||
causes a deviation from the expected protocol and therefore a
|
||||
desynchronization between the client and daemon. */
|
||||
struct ConnectionHandle
|
||||
RemoteStore::ConnectionHandle::~ConnectionHandle()
|
||||
{
|
||||
Pool<RemoteStore::Connection>::Handle handle;
|
||||
bool daemonException = false;
|
||||
|
||||
ConnectionHandle(Pool<RemoteStore::Connection>::Handle && handle)
|
||||
: handle(std::move(handle))
|
||||
{ }
|
||||
|
||||
ConnectionHandle(ConnectionHandle && h)
|
||||
: handle(std::move(h.handle))
|
||||
{ }
|
||||
|
||||
~ConnectionHandle()
|
||||
{
|
||||
if (!daemonException && std::uncaught_exceptions()) {
|
||||
handle.markBad();
|
||||
debug("closing daemon connection because of an exception");
|
||||
}
|
||||
if (!daemonException && std::uncaught_exceptions()) {
|
||||
handle.markBad();
|
||||
debug("closing daemon connection because of an exception");
|
||||
}
|
||||
}
|
||||
|
||||
RemoteStore::Connection * operator -> () { return &*handle; }
|
||||
|
||||
void processStderr(Sink * sink = 0, Source * source = 0, bool flush = true)
|
||||
{
|
||||
auto ex = handle->processStderr(sink, source, flush);
|
||||
if (ex) {
|
||||
daemonException = true;
|
||||
void RemoteStore::ConnectionHandle::processStderr(Sink * sink, Source * source, bool flush)
|
||||
{
|
||||
auto ex = handle->processStderr(sink, source, flush);
|
||||
if (ex) {
|
||||
daemonException = true;
|
||||
try {
|
||||
std::rethrow_exception(ex);
|
||||
} catch (const Error & e) {
|
||||
// Nix versions before #4628 did not have an adequate behavior for reporting that the derivation format was upgraded.
|
||||
// To avoid having to add compatibility logic in many places, we expect to catch almost all occurrences of the
|
||||
// old incomprehensible error here, so that we can explain to users what's going on when their daemon is
|
||||
// older than #4628 (2023).
|
||||
if (experimentalFeatureSettings.isEnabled(Xp::DynamicDerivations) &&
|
||||
GET_PROTOCOL_MINOR(handle->daemonVersion) <= 35)
|
||||
{
|
||||
auto m = e.msg();
|
||||
if (m.find("parsing derivation") != std::string::npos &&
|
||||
m.find("expected string") != std::string::npos &&
|
||||
m.find("Derive([") != std::string::npos)
|
||||
throw Error("%s, this might be because the daemon is too old to understand dependencies on dynamic derivations. Check to see if the raw derivation is in the form '%s'", std::move(m), "DrvWithVersion(..)");
|
||||
}
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void withFramedSink(std::function<void(Sink & sink)> fun);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
ConnectionHandle RemoteStore::getConnection()
|
||||
RemoteStore::ConnectionHandle RemoteStore::getConnection()
|
||||
{
|
||||
return ConnectionHandle(connections->get());
|
||||
}
|
||||
|
@ -211,7 +209,7 @@ void RemoteStore::setOptions()
|
|||
bool RemoteStore::isValidPathUncached(const StorePath & path)
|
||||
{
|
||||
auto conn(getConnection());
|
||||
conn->to << wopIsValidPath << printStorePath(path);
|
||||
conn->to << WorkerProto::Op::IsValidPath << printStorePath(path);
|
||||
conn.processStderr();
|
||||
return readInt(conn->from);
|
||||
}
|
||||
|
@ -226,13 +224,13 @@ StorePathSet RemoteStore::queryValidPaths(const StorePathSet & paths, Substitute
|
|||
if (isValidPath(i)) res.insert(i);
|
||||
return res;
|
||||
} else {
|
||||
conn->to << wopQueryValidPaths;
|
||||
workerProtoWrite(*this, conn->to, paths);
|
||||
conn->to << WorkerProto::Op::QueryValidPaths;
|
||||
WorkerProto::write(*this, *conn, paths);
|
||||
if (GET_PROTOCOL_MINOR(conn->daemonVersion) >= 27) {
|
||||
conn->to << (settings.buildersUseSubstitutes ? 1 : 0);
|
||||
conn->to << maybeSubstitute;
|
||||
}
|
||||
conn.processStderr();
|
||||
return WorkerProto<StorePathSet>::read(*this, conn->from);
|
||||
return WorkerProto::Serialise<StorePathSet>::read(*this, *conn);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -240,9 +238,9 @@ StorePathSet RemoteStore::queryValidPaths(const StorePathSet & paths, Substitute
|
|||
StorePathSet RemoteStore::queryAllValidPaths()
|
||||
{
|
||||
auto conn(getConnection());
|
||||
conn->to << wopQueryAllValidPaths;
|
||||
conn->to << WorkerProto::Op::QueryAllValidPaths;
|
||||
conn.processStderr();
|
||||
return WorkerProto<StorePathSet>::read(*this, conn->from);
|
||||
return WorkerProto::Serialise<StorePathSet>::read(*this, *conn);
|
||||
}
|
||||
|
||||
|
||||
|
@ -252,16 +250,16 @@ StorePathSet RemoteStore::querySubstitutablePaths(const StorePathSet & paths)
|
|||
if (GET_PROTOCOL_MINOR(conn->daemonVersion) < 12) {
|
||||
StorePathSet res;
|
||||
for (auto & i : paths) {
|
||||
conn->to << wopHasSubstitutes << printStorePath(i);
|
||||
conn->to << WorkerProto::Op::HasSubstitutes << printStorePath(i);
|
||||
conn.processStderr();
|
||||
if (readInt(conn->from)) res.insert(i);
|
||||
}
|
||||
return res;
|
||||
} else {
|
||||
conn->to << wopQuerySubstitutablePaths;
|
||||
workerProtoWrite(*this, conn->to, paths);
|
||||
conn->to << WorkerProto::Op::QuerySubstitutablePaths;
|
||||
WorkerProto::write(*this, *conn, paths);
|
||||
conn.processStderr();
|
||||
return WorkerProto<StorePathSet>::read(*this, conn->from);
|
||||
return WorkerProto::Serialise<StorePathSet>::read(*this, *conn);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -276,14 +274,14 @@ void RemoteStore::querySubstitutablePathInfos(const StorePathCAMap & pathsMap, S
|
|||
|
||||
for (auto & i : pathsMap) {
|
||||
SubstitutablePathInfo info;
|
||||
conn->to << wopQuerySubstitutablePathInfo << printStorePath(i.first);
|
||||
conn->to << WorkerProto::Op::QuerySubstitutablePathInfo << printStorePath(i.first);
|
||||
conn.processStderr();
|
||||
unsigned int reply = readInt(conn->from);
|
||||
if (reply == 0) continue;
|
||||
auto deriver = readString(conn->from);
|
||||
if (deriver != "")
|
||||
info.deriver = parseStorePath(deriver);
|
||||
info.references = WorkerProto<StorePathSet>::read(*this, conn->from);
|
||||
info.references = WorkerProto::Serialise<StorePathSet>::read(*this, *conn);
|
||||
info.downloadSize = readLongLong(conn->from);
|
||||
info.narSize = readLongLong(conn->from);
|
||||
infos.insert_or_assign(i.first, std::move(info));
|
||||
|
@ -291,14 +289,14 @@ void RemoteStore::querySubstitutablePathInfos(const StorePathCAMap & pathsMap, S
|
|||
|
||||
} else {
|
||||
|
||||
conn->to << wopQuerySubstitutablePathInfos;
|
||||
conn->to << WorkerProto::Op::QuerySubstitutablePathInfos;
|
||||
if (GET_PROTOCOL_MINOR(conn->daemonVersion) < 22) {
|
||||
StorePathSet paths;
|
||||
for (auto & path : pathsMap)
|
||||
paths.insert(path.first);
|
||||
workerProtoWrite(*this, conn->to, paths);
|
||||
WorkerProto::write(*this, *conn, paths);
|
||||
} else
|
||||
workerProtoWrite(*this, conn->to, pathsMap);
|
||||
WorkerProto::write(*this, *conn, pathsMap);
|
||||
conn.processStderr();
|
||||
size_t count = readNum<size_t>(conn->from);
|
||||
for (size_t n = 0; n < count; n++) {
|
||||
|
@ -306,7 +304,7 @@ void RemoteStore::querySubstitutablePathInfos(const StorePathCAMap & pathsMap, S
|
|||
auto deriver = readString(conn->from);
|
||||
if (deriver != "")
|
||||
info.deriver = parseStorePath(deriver);
|
||||
info.references = WorkerProto<StorePathSet>::read(*this, conn->from);
|
||||
info.references = WorkerProto::Serialise<StorePathSet>::read(*this, *conn);
|
||||
info.downloadSize = readLongLong(conn->from);
|
||||
info.narSize = readLongLong(conn->from);
|
||||
}
|
||||
|
@ -322,7 +320,7 @@ void RemoteStore::queryPathInfoUncached(const StorePath & path,
|
|||
std::shared_ptr<const ValidPathInfo> info;
|
||||
{
|
||||
auto conn(getConnection());
|
||||
conn->to << wopQueryPathInfo << printStorePath(path);
|
||||
conn->to << WorkerProto::Op::QueryPathInfo << printStorePath(path);
|
||||
try {
|
||||
conn.processStderr();
|
||||
} catch (Error & e) {
|
||||
|
@ -336,7 +334,8 @@ void RemoteStore::queryPathInfoUncached(const StorePath & path,
|
|||
if (!valid) throw InvalidPath("path '%s' is not valid", printStorePath(path));
|
||||
}
|
||||
info = std::make_shared<ValidPathInfo>(
|
||||
ValidPathInfo::read(conn->from, *this, GET_PROTOCOL_MINOR(conn->daemonVersion), StorePath{path}));
|
||||
StorePath{path},
|
||||
WorkerProto::Serialise<UnkeyedValidPathInfo>::read(*this, *conn));
|
||||
}
|
||||
callback(std::move(info));
|
||||
} catch (...) { callback.rethrow(); }
|
||||
|
@ -347,9 +346,9 @@ void RemoteStore::queryReferrers(const StorePath & path,
|
|||
StorePathSet & referrers)
|
||||
{
|
||||
auto conn(getConnection());
|
||||
conn->to << wopQueryReferrers << printStorePath(path);
|
||||
conn->to << WorkerProto::Op::QueryReferrers << printStorePath(path);
|
||||
conn.processStderr();
|
||||
for (auto & i : WorkerProto<StorePathSet>::read(*this, conn->from))
|
||||
for (auto & i : WorkerProto::Serialise<StorePathSet>::read(*this, *conn))
|
||||
referrers.insert(i);
|
||||
}
|
||||
|
||||
|
@ -357,9 +356,9 @@ void RemoteStore::queryReferrers(const StorePath & path,
|
|||
StorePathSet RemoteStore::queryValidDerivers(const StorePath & path)
|
||||
{
|
||||
auto conn(getConnection());
|
||||
conn->to << wopQueryValidDerivers << printStorePath(path);
|
||||
conn->to << WorkerProto::Op::QueryValidDerivers << printStorePath(path);
|
||||
conn.processStderr();
|
||||
return WorkerProto<StorePathSet>::read(*this, conn->from);
|
||||
return WorkerProto::Serialise<StorePathSet>::read(*this, *conn);
|
||||
}
|
||||
|
||||
|
||||
|
@ -369,40 +368,49 @@ StorePathSet RemoteStore::queryDerivationOutputs(const StorePath & path)
|
|||
return Store::queryDerivationOutputs(path);
|
||||
}
|
||||
auto conn(getConnection());
|
||||
conn->to << wopQueryDerivationOutputs << printStorePath(path);
|
||||
conn->to << WorkerProto::Op::QueryDerivationOutputs << printStorePath(path);
|
||||
conn.processStderr();
|
||||
return WorkerProto<StorePathSet>::read(*this, conn->from);
|
||||
return WorkerProto::Serialise<StorePathSet>::read(*this, *conn);
|
||||
}
|
||||
|
||||
|
||||
std::map<std::string, std::optional<StorePath>> RemoteStore::queryPartialDerivationOutputMap(const StorePath & path)
|
||||
std::map<std::string, std::optional<StorePath>> RemoteStore::queryPartialDerivationOutputMap(const StorePath & path, Store * evalStore_)
|
||||
{
|
||||
if (GET_PROTOCOL_MINOR(getProtocol()) >= 0x16) {
|
||||
auto conn(getConnection());
|
||||
conn->to << wopQueryDerivationOutputMap << printStorePath(path);
|
||||
conn.processStderr();
|
||||
return WorkerProto<std::map<std::string, std::optional<StorePath>>>::read(*this, conn->from);
|
||||
if (!evalStore_) {
|
||||
auto conn(getConnection());
|
||||
conn->to << WorkerProto::Op::QueryDerivationOutputMap << printStorePath(path);
|
||||
conn.processStderr();
|
||||
return WorkerProto::Serialise<std::map<std::string, std::optional<StorePath>>>::read(*this, *conn);
|
||||
} else {
|
||||
auto & evalStore = *evalStore_;
|
||||
auto outputs = evalStore.queryStaticPartialDerivationOutputMap(path);
|
||||
// union with the first branch overriding the statically-known ones
|
||||
// when non-`std::nullopt`.
|
||||
for (auto && [outputName, optPath] : queryPartialDerivationOutputMap(path, nullptr)) {
|
||||
if (optPath)
|
||||
outputs.insert_or_assign(std::move(outputName), std::move(optPath));
|
||||
else
|
||||
outputs.insert({std::move(outputName), std::nullopt});
|
||||
}
|
||||
return outputs;
|
||||
}
|
||||
} else {
|
||||
auto & evalStore = evalStore_ ? *evalStore_ : *this;
|
||||
// Fallback for old daemon versions.
|
||||
// For floating-CA derivations (and their co-dependencies) this is an
|
||||
// under-approximation as it only returns the paths that can be inferred
|
||||
// from the derivation itself (and not the ones that are known because
|
||||
// the have been built), but as old stores don't handle floating-CA
|
||||
// derivations this shouldn't matter
|
||||
auto derivation = readDerivation(path);
|
||||
auto outputsWithOptPaths = derivation.outputsAndOptPaths(*this);
|
||||
std::map<std::string, std::optional<StorePath>> ret;
|
||||
for (auto & [outputName, outputAndPath] : outputsWithOptPaths) {
|
||||
ret.emplace(outputName, outputAndPath.second);
|
||||
}
|
||||
return ret;
|
||||
return evalStore.queryStaticPartialDerivationOutputMap(path);
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<StorePath> RemoteStore::queryPathFromHashPart(const std::string & hashPart)
|
||||
{
|
||||
auto conn(getConnection());
|
||||
conn->to << wopQueryPathFromHashPart << hashPart;
|
||||
conn->to << WorkerProto::Op::QueryPathFromHashPart << hashPart;
|
||||
conn.processStderr();
|
||||
Path path = readString(conn->from);
|
||||
if (path.empty()) return {};
|
||||
|
@ -411,12 +419,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_;
|
||||
|
@ -424,10 +432,10 @@ ref<const ValidPathInfo> RemoteStore::addCAToStore(
|
|||
if (GET_PROTOCOL_MINOR(conn->daemonVersion) >= 25) {
|
||||
|
||||
conn->to
|
||||
<< wopAddToStore
|
||||
<< WorkerProto::Op::AddToStore
|
||||
<< name
|
||||
<< caMethod.render(hashType);
|
||||
workerProtoWrite(*this, conn->to, references);
|
||||
<< caMethod.render(hashAlgo);
|
||||
WorkerProto::write(*this, *conn, references);
|
||||
conn->to << repair;
|
||||
|
||||
// The dump source may invoke the store, so we need to make some room.
|
||||
|
@ -440,28 +448,28 @@ ref<const ValidPathInfo> RemoteStore::addCAToStore(
|
|||
}
|
||||
|
||||
return make_ref<ValidPathInfo>(
|
||||
ValidPathInfo::read(conn->from, *this, GET_PROTOCOL_MINOR(conn->daemonVersion)));
|
||||
WorkerProto::Serialise<ValidPathInfo>::read(*this, *conn));
|
||||
}
|
||||
else {
|
||||
if (repair) throw Error("repairing is not supported when building through the Nix daemon protocol < 1.25");
|
||||
|
||||
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 << wopAddTextToStore << name << s;
|
||||
workerProtoWrite(*this, conn->to, references);
|
||||
conn->to << WorkerProto::Op::AddTextToStore << name << s;
|
||||
WorkerProto::write(*this, *conn, references);
|
||||
conn.processStderr();
|
||||
},
|
||||
[&](const FileIngestionMethod & fim) -> void {
|
||||
conn->to
|
||||
<< wopAddToStore
|
||||
<< 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;
|
||||
|
@ -496,10 +504,15 @@ ref<const ValidPathInfo> RemoteStore::addCAToStore(
|
|||
}
|
||||
|
||||
|
||||
StorePath RemoteStore::addToStoreFromDump(Source & dump, std::string_view name,
|
||||
FileIngestionMethod method, HashType hashType, RepairFlag repair, const StorePathSet & references)
|
||||
StorePath RemoteStore::addToStoreFromDump(
|
||||
Source & dump,
|
||||
std::string_view name,
|
||||
ContentAddressMethod method,
|
||||
HashAlgorithm hashAlgo,
|
||||
const StorePathSet & references,
|
||||
RepairFlag repair)
|
||||
{
|
||||
return addCAToStore(dump, name, method, hashType, references, repair)->path;
|
||||
return addCAToStore(dump, name, method, hashAlgo, references, repair)->path;
|
||||
}
|
||||
|
||||
|
||||
|
@ -509,7 +522,7 @@ void RemoteStore::addToStore(const ValidPathInfo & info, Source & source,
|
|||
auto conn(getConnection());
|
||||
|
||||
if (GET_PROTOCOL_MINOR(conn->daemonVersion) < 18) {
|
||||
conn->to << wopImportPaths;
|
||||
conn->to << WorkerProto::Op::ImportPaths;
|
||||
|
||||
auto source2 = sinkToSource([&](Sink & sink) {
|
||||
sink << 1 // == path follows
|
||||
|
@ -518,7 +531,7 @@ void RemoteStore::addToStore(const ValidPathInfo & info, Source & source,
|
|||
sink
|
||||
<< exportMagic
|
||||
<< printStorePath(info.path);
|
||||
workerProtoWrite(*this, sink, info.references);
|
||||
WorkerProto::write(*this, *conn, info.references);
|
||||
sink
|
||||
<< (info.deriver ? printStorePath(*info.deriver) : "")
|
||||
<< 0 // == no legacy signature
|
||||
|
@ -528,16 +541,16 @@ void RemoteStore::addToStore(const ValidPathInfo & info, Source & source,
|
|||
|
||||
conn.processStderr(0, source2.get());
|
||||
|
||||
auto importedPaths = WorkerProto<StorePathSet>::read(*this, conn->from);
|
||||
auto importedPaths = WorkerProto::Serialise<StorePathSet>::read(*this, *conn);
|
||||
assert(importedPaths.size() <= 1);
|
||||
}
|
||||
|
||||
else {
|
||||
conn->to << wopAddToStoreNar
|
||||
conn->to << WorkerProto::Op::AddToStoreNar
|
||||
<< printStorePath(info.path)
|
||||
<< (info.deriver ? printStorePath(*info.deriver) : "")
|
||||
<< info.narHash.to_string(Base16, false);
|
||||
workerProtoWrite(*this, conn->to, info.references);
|
||||
<< info.narHash.to_string(HashFormat::Base16, false);
|
||||
WorkerProto::write(*this, *conn, info.references);
|
||||
conn->to << info.registrationTime << info.narSize
|
||||
<< info.ultimate << info.sigs << renderContentAddress(info.ca)
|
||||
<< repair << !checkSigs;
|
||||
|
@ -565,7 +578,12 @@ void RemoteStore::addMultipleToStore(
|
|||
auto source = sinkToSource([&](Sink & sink) {
|
||||
sink << pathsToCopy.size();
|
||||
for (auto & [pathInfo, pathSource] : pathsToCopy) {
|
||||
pathInfo.write(sink, *this, 16);
|
||||
WorkerProto::Serialise<ValidPathInfo>::write(*this,
|
||||
WorkerProto::WriteConn {
|
||||
.to = sink,
|
||||
.version = 16,
|
||||
},
|
||||
pathInfo);
|
||||
pathSource->drainInto(sink);
|
||||
}
|
||||
});
|
||||
|
@ -581,7 +599,7 @@ void RemoteStore::addMultipleToStore(
|
|||
if (GET_PROTOCOL_MINOR(getConnection()->daemonVersion) >= 32) {
|
||||
auto conn(getConnection());
|
||||
conn->to
|
||||
<< wopAddMultipleToStore
|
||||
<< WorkerProto::Op::AddMultipleToStore
|
||||
<< repair
|
||||
<< !checkSigs;
|
||||
conn.withFramedSink([&](Sink & sink) {
|
||||
|
@ -592,25 +610,15 @@ void RemoteStore::addMultipleToStore(
|
|||
}
|
||||
|
||||
|
||||
StorePath RemoteStore::addTextToStore(
|
||||
std::string_view name,
|
||||
std::string_view s,
|
||||
const StorePathSet & references,
|
||||
RepairFlag repair)
|
||||
{
|
||||
StringSource source(s);
|
||||
return addCAToStore(source, name, TextIngestionMethod {}, htSHA256, references, repair)->path;
|
||||
}
|
||||
|
||||
void RemoteStore::registerDrvOutput(const Realisation & info)
|
||||
{
|
||||
auto conn(getConnection());
|
||||
conn->to << wopRegisterDrvOutput;
|
||||
conn->to << WorkerProto::Op::RegisterDrvOutput;
|
||||
if (GET_PROTOCOL_MINOR(conn->daemonVersion) < 31) {
|
||||
conn->to << info.id.to_string();
|
||||
conn->to << std::string(info.outPath.to_string());
|
||||
} else {
|
||||
workerProtoWrite(*this, conn->to, info);
|
||||
WorkerProto::write(*this, *conn, info);
|
||||
}
|
||||
conn.processStderr();
|
||||
}
|
||||
|
@ -626,20 +634,20 @@ void RemoteStore::queryRealisationUncached(const DrvOutput & id,
|
|||
return callback(nullptr);
|
||||
}
|
||||
|
||||
conn->to << wopQueryRealisation;
|
||||
conn->to << WorkerProto::Op::QueryRealisation;
|
||||
conn->to << id.to_string();
|
||||
conn.processStderr();
|
||||
|
||||
auto real = [&]() -> std::shared_ptr<const Realisation> {
|
||||
if (GET_PROTOCOL_MINOR(conn->daemonVersion) < 31) {
|
||||
auto outPaths = WorkerProto<std::set<StorePath>>::read(
|
||||
*this, conn->from);
|
||||
auto outPaths = WorkerProto::Serialise<std::set<StorePath>>::read(
|
||||
*this, *conn);
|
||||
if (outPaths.empty())
|
||||
return nullptr;
|
||||
return std::make_shared<const Realisation>(Realisation { .id = id, .outPath = *outPaths.begin() });
|
||||
} else {
|
||||
auto realisations = WorkerProto<std::set<Realisation>>::read(
|
||||
*this, conn->from);
|
||||
auto realisations = WorkerProto::Serialise<std::set<Realisation>>::read(
|
||||
*this, *conn);
|
||||
if (realisations.empty())
|
||||
return nullptr;
|
||||
return std::make_shared<const Realisation>(*realisations.begin());
|
||||
|
@ -650,30 +658,6 @@ void RemoteStore::queryRealisationUncached(const DrvOutput & id,
|
|||
} catch (...) { return callback.rethrow(); }
|
||||
}
|
||||
|
||||
static void writeDerivedPaths(RemoteStore & store, ConnectionHandle & conn, const std::vector<DerivedPath> & reqs)
|
||||
{
|
||||
if (GET_PROTOCOL_MINOR(conn->daemonVersion) >= 30) {
|
||||
workerProtoWrite(store, conn->to, reqs);
|
||||
} else {
|
||||
Strings ss;
|
||||
for (auto & p : reqs) {
|
||||
auto sOrDrvPath = StorePathWithOutputs::tryFromDerivedPath(p);
|
||||
std::visit(overloaded {
|
||||
[&](const StorePathWithOutputs & s) {
|
||||
ss.push_back(s.to_string(store));
|
||||
},
|
||||
[&](const StorePath & drvPath) {
|
||||
throw Error("trying to request '%s', but daemon protocol %d.%d is too old (< 1.29) to request a derivation file",
|
||||
store.printStorePath(drvPath),
|
||||
GET_PROTOCOL_MAJOR(conn->daemonVersion),
|
||||
GET_PROTOCOL_MINOR(conn->daemonVersion));
|
||||
},
|
||||
}, sOrDrvPath);
|
||||
}
|
||||
conn->to << ss;
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteStore::copyDrvsFromEvalStore(
|
||||
const std::vector<DerivedPath> & paths,
|
||||
std::shared_ptr<Store> evalStore)
|
||||
|
@ -682,9 +666,16 @@ void RemoteStore::copyDrvsFromEvalStore(
|
|||
/* The remote doesn't have a way to access evalStore, so copy
|
||||
the .drvs. */
|
||||
RealisedPath::Set drvPaths2;
|
||||
for (auto & i : paths)
|
||||
if (auto p = std::get_if<DerivedPath::Built>(&i))
|
||||
drvPaths2.insert(p->drvPath);
|
||||
for (const auto & i : paths) {
|
||||
std::visit(overloaded {
|
||||
[&](const DerivedPath::Opaque & bp) {
|
||||
// Do nothing, path is hopefully there already
|
||||
},
|
||||
[&](const DerivedPath::Built & bp) {
|
||||
drvPaths2.insert(bp.drvPath->getBaseStorePath());
|
||||
},
|
||||
}, i.raw());
|
||||
}
|
||||
copyClosure(*evalStore, *this, drvPaths2);
|
||||
}
|
||||
}
|
||||
|
@ -694,9 +685,9 @@ void RemoteStore::buildPaths(const std::vector<DerivedPath> & drvPaths, BuildMod
|
|||
copyDrvsFromEvalStore(drvPaths, evalStore);
|
||||
|
||||
auto conn(getConnection());
|
||||
conn->to << wopBuildPaths;
|
||||
conn->to << WorkerProto::Op::BuildPaths;
|
||||
assert(GET_PROTOCOL_MINOR(conn->daemonVersion) >= 13);
|
||||
writeDerivedPaths(*this, conn, drvPaths);
|
||||
WorkerProto::write(*this, *conn, drvPaths);
|
||||
if (GET_PROTOCOL_MINOR(conn->daemonVersion) >= 15)
|
||||
conn->to << buildMode;
|
||||
else
|
||||
|
@ -719,11 +710,11 @@ std::vector<KeyedBuildResult> RemoteStore::buildPathsWithResults(
|
|||
auto & conn = *conn_;
|
||||
|
||||
if (GET_PROTOCOL_MINOR(conn->daemonVersion) >= 34) {
|
||||
conn->to << wopBuildPathsWithResults;
|
||||
writeDerivedPaths(*this, conn, paths);
|
||||
conn->to << WorkerProto::Op::BuildPathsWithResults;
|
||||
WorkerProto::write(*this, *conn, paths);
|
||||
conn->to << buildMode;
|
||||
conn.processStderr();
|
||||
return WorkerProto<std::vector<KeyedBuildResult>>::read(*this, conn->from);
|
||||
return WorkerProto::Serialise<std::vector<KeyedBuildResult>>::read(*this, *conn);
|
||||
} else {
|
||||
// Avoid deadlock.
|
||||
conn_.reset();
|
||||
|
@ -754,7 +745,8 @@ std::vector<KeyedBuildResult> RemoteStore::buildPathsWithResults(
|
|||
};
|
||||
|
||||
OutputPathMap outputs;
|
||||
auto drv = evalStore->readDerivation(bfd.drvPath);
|
||||
auto drvPath = resolveDerivedPath(*evalStore, *bfd.drvPath);
|
||||
auto drv = evalStore->readDerivation(drvPath);
|
||||
const auto outputHashes = staticOutputHashes(*evalStore, drv); // FIXME: expensive
|
||||
auto built = resolveDerivedPath(*this, bfd, &*evalStore);
|
||||
for (auto & [output, outputPath] : built) {
|
||||
|
@ -762,7 +754,7 @@ std::vector<KeyedBuildResult> RemoteStore::buildPathsWithResults(
|
|||
if (!outputHash)
|
||||
throw Error(
|
||||
"the derivation '%s' doesn't have an output named '%s'",
|
||||
printStorePath(bfd.drvPath), output);
|
||||
printStorePath(drvPath), output);
|
||||
auto outputId = DrvOutput{ *outputHash, output };
|
||||
if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations)) {
|
||||
auto realisation =
|
||||
|
@ -795,31 +787,18 @@ BuildResult RemoteStore::buildDerivation(const StorePath & drvPath, const BasicD
|
|||
BuildMode buildMode)
|
||||
{
|
||||
auto conn(getConnection());
|
||||
conn->to << wopBuildDerivation << printStorePath(drvPath);
|
||||
conn->to << WorkerProto::Op::BuildDerivation << printStorePath(drvPath);
|
||||
writeDerivation(conn->to, *this, drv);
|
||||
conn->to << buildMode;
|
||||
conn.processStderr();
|
||||
BuildResult res;
|
||||
res.status = (BuildResult::Status) readInt(conn->from);
|
||||
conn->from >> res.errorMsg;
|
||||
if (GET_PROTOCOL_MINOR(conn->daemonVersion) >= 29) {
|
||||
conn->from >> res.timesBuilt >> res.isNonDeterministic >> res.startTime >> res.stopTime;
|
||||
}
|
||||
if (GET_PROTOCOL_MINOR(conn->daemonVersion) >= 28) {
|
||||
auto builtOutputs = WorkerProto<DrvOutputs>::read(*this, conn->from);
|
||||
for (auto && [output, realisation] : builtOutputs)
|
||||
res.builtOutputs.insert_or_assign(
|
||||
std::move(output.outputName),
|
||||
std::move(realisation));
|
||||
}
|
||||
return res;
|
||||
return WorkerProto::Serialise<BuildResult>::read(*this, *conn);
|
||||
}
|
||||
|
||||
|
||||
void RemoteStore::ensurePath(const StorePath & path)
|
||||
{
|
||||
auto conn(getConnection());
|
||||
conn->to << wopEnsurePath << printStorePath(path);
|
||||
conn->to << WorkerProto::Op::EnsurePath << printStorePath(path);
|
||||
conn.processStderr();
|
||||
readInt(conn->from);
|
||||
}
|
||||
|
@ -828,16 +807,7 @@ void RemoteStore::ensurePath(const StorePath & path)
|
|||
void RemoteStore::addTempRoot(const StorePath & path)
|
||||
{
|
||||
auto conn(getConnection());
|
||||
conn->to << wopAddTempRoot << printStorePath(path);
|
||||
conn.processStderr();
|
||||
readInt(conn->from);
|
||||
}
|
||||
|
||||
|
||||
void RemoteStore::addIndirectRoot(const Path & path)
|
||||
{
|
||||
auto conn(getConnection());
|
||||
conn->to << wopAddIndirectRoot << path;
|
||||
conn->to << WorkerProto::Op::AddTempRoot << printStorePath(path);
|
||||
conn.processStderr();
|
||||
readInt(conn->from);
|
||||
}
|
||||
|
@ -846,7 +816,7 @@ void RemoteStore::addIndirectRoot(const Path & path)
|
|||
Roots RemoteStore::findRoots(bool censor)
|
||||
{
|
||||
auto conn(getConnection());
|
||||
conn->to << wopFindRoots;
|
||||
conn->to << WorkerProto::Op::FindRoots;
|
||||
conn.processStderr();
|
||||
size_t count = readNum<size_t>(conn->from);
|
||||
Roots result;
|
||||
|
@ -864,8 +834,8 @@ void RemoteStore::collectGarbage(const GCOptions & options, GCResults & results)
|
|||
auto conn(getConnection());
|
||||
|
||||
conn->to
|
||||
<< wopCollectGarbage << options.action;
|
||||
workerProtoWrite(*this, conn->to, options.pathsToDelete);
|
||||
<< WorkerProto::Op::CollectGarbage << options.action;
|
||||
WorkerProto::write(*this, *conn, options.pathsToDelete);
|
||||
conn->to << options.ignoreLiveness
|
||||
<< options.maxFreed
|
||||
/* removed options */
|
||||
|
@ -887,7 +857,7 @@ void RemoteStore::collectGarbage(const GCOptions & options, GCResults & results)
|
|||
void RemoteStore::optimiseStore()
|
||||
{
|
||||
auto conn(getConnection());
|
||||
conn->to << wopOptimiseStore;
|
||||
conn->to << WorkerProto::Op::OptimiseStore;
|
||||
conn.processStderr();
|
||||
readInt(conn->from);
|
||||
}
|
||||
|
@ -896,7 +866,7 @@ void RemoteStore::optimiseStore()
|
|||
bool RemoteStore::verifyStore(bool checkContents, RepairFlag repair)
|
||||
{
|
||||
auto conn(getConnection());
|
||||
conn->to << wopVerifyStore << checkContents << repair;
|
||||
conn->to << WorkerProto::Op::VerifyStore << checkContents << repair;
|
||||
conn.processStderr();
|
||||
return readInt(conn->from);
|
||||
}
|
||||
|
@ -905,7 +875,7 @@ bool RemoteStore::verifyStore(bool checkContents, RepairFlag repair)
|
|||
void RemoteStore::addSignatures(const StorePath & storePath, const StringSet & sigs)
|
||||
{
|
||||
auto conn(getConnection());
|
||||
conn->to << wopAddSignatures << printStorePath(storePath) << sigs;
|
||||
conn->to << WorkerProto::Op::AddSignatures << printStorePath(storePath) << sigs;
|
||||
conn.processStderr();
|
||||
readInt(conn->from);
|
||||
}
|
||||
|
@ -921,12 +891,12 @@ void RemoteStore::queryMissing(const std::vector<DerivedPath> & targets,
|
|||
// Don't hold the connection handle in the fallback case
|
||||
// to prevent a deadlock.
|
||||
goto fallback;
|
||||
conn->to << wopQueryMissing;
|
||||
writeDerivedPaths(*this, conn, targets);
|
||||
conn->to << WorkerProto::Op::QueryMissing;
|
||||
WorkerProto::write(*this, *conn, targets);
|
||||
conn.processStderr();
|
||||
willBuild = WorkerProto<StorePathSet>::read(*this, conn->from);
|
||||
willSubstitute = WorkerProto<StorePathSet>::read(*this, conn->from);
|
||||
unknown = WorkerProto<StorePathSet>::read(*this, conn->from);
|
||||
willBuild = WorkerProto::Serialise<StorePathSet>::read(*this, *conn);
|
||||
willSubstitute = WorkerProto::Serialise<StorePathSet>::read(*this, *conn);
|
||||
unknown = WorkerProto::Serialise<StorePathSet>::read(*this, *conn);
|
||||
conn->from >> downloadSize >> narSize;
|
||||
return;
|
||||
}
|
||||
|
@ -940,7 +910,7 @@ void RemoteStore::queryMissing(const std::vector<DerivedPath> & targets,
|
|||
void RemoteStore::addBuildLog(const StorePath & drvPath, std::string_view log)
|
||||
{
|
||||
auto conn(getConnection());
|
||||
conn->to << wopAddBuildLog << drvPath.to_string();
|
||||
conn->to << WorkerProto::Op::AddBuildLog << drvPath.to_string();
|
||||
StringSource source(log);
|
||||
conn.withFramedSink([&](Sink & sink) {
|
||||
source.drainInto(sink);
|
||||
|
@ -992,12 +962,12 @@ RemoteStore::Connection::~Connection()
|
|||
void RemoteStore::narFromPath(const StorePath & path, Sink & sink)
|
||||
{
|
||||
auto conn(connections->get());
|
||||
conn->to << wopNarFromPath << printStorePath(path);
|
||||
conn->to << WorkerProto::Op::NarFromPath << printStorePath(path);
|
||||
conn->processStderr();
|
||||
copyNAR(conn->from, sink);
|
||||
}
|
||||
|
||||
ref<FSAccessor> RemoteStore::getFSAccessor()
|
||||
ref<SourceAccessor> RemoteStore::getFSAccessor(bool requireValidPath)
|
||||
{
|
||||
return make_ref<RemoteFSAccessor>(ref<Store>(shared_from_this()));
|
||||
}
|
||||
|
@ -1087,7 +1057,7 @@ std::exception_ptr RemoteStore::Connection::processStderr(Sink * sink, Source *
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
void ConnectionHandle::withFramedSink(std::function<void(Sink & sink)> fun)
|
||||
void RemoteStore::ConnectionHandle::withFramedSink(std::function<void(Sink & sink)> fun)
|
||||
{
|
||||
(*this)->to.flush();
|
||||
|
||||
|
@ -1098,6 +1068,7 @@ void ConnectionHandle::withFramedSink(std::function<void(Sink & sink)> fun)
|
|||
std::thread stderrThread([&]()
|
||||
{
|
||||
try {
|
||||
ReceiveInterrupts receiveInterrupts;
|
||||
processStderr(nullptr, nullptr, false);
|
||||
} catch (...) {
|
||||
ex = std::current_exception();
|
||||
|
|
|
@ -17,16 +17,15 @@ class Pid;
|
|||
struct FdSink;
|
||||
struct FdSource;
|
||||
template<typename T> class Pool;
|
||||
struct ConnectionHandle;
|
||||
|
||||
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."};
|
||||
|
@ -63,7 +62,7 @@ public:
|
|||
|
||||
StorePathSet queryDerivationOutputs(const StorePath & path) override;
|
||||
|
||||
std::map<std::string, std::optional<StorePath>> queryPartialDerivationOutputMap(const StorePath & path) override;
|
||||
std::map<std::string, std::optional<StorePath>> queryPartialDerivationOutputMap(const StorePath & path, Store * evalStore = nullptr) override;
|
||||
std::optional<StorePath> queryPathFromHashPart(const std::string & hashPart) override;
|
||||
|
||||
StorePathSet querySubstitutablePaths(const StorePathSet & paths) override;
|
||||
|
@ -75,18 +74,23 @@ 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.
|
||||
* Add a content-addressable store path. `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;
|
||||
StorePath addToStoreFromDump(
|
||||
Source & dump,
|
||||
std::string_view name,
|
||||
ContentAddressMethod method = FileIngestionMethod::Recursive,
|
||||
HashAlgorithm hashAlgo = HashAlgorithm::SHA256,
|
||||
const StorePathSet & references = StorePathSet(),
|
||||
RepairFlag repair = NoRepair) override;
|
||||
|
||||
void addToStore(const ValidPathInfo & info, Source & nar,
|
||||
RepairFlag repair, CheckSigsFlag checkSigs) override;
|
||||
|
@ -102,12 +106,6 @@ public:
|
|||
RepairFlag repair,
|
||||
CheckSigsFlag checkSigs) override;
|
||||
|
||||
StorePath addTextToStore(
|
||||
std::string_view name,
|
||||
std::string_view s,
|
||||
const StorePathSet & references,
|
||||
RepairFlag repair) override;
|
||||
|
||||
void registerDrvOutput(const Realisation & info) override;
|
||||
|
||||
void queryRealisationUncached(const DrvOutput &,
|
||||
|
@ -127,8 +125,6 @@ public:
|
|||
|
||||
void addTempRoot(const StorePath & path) override;
|
||||
|
||||
void addIndirectRoot(const Path & path) override;
|
||||
|
||||
Roots findRoots(bool censor) override;
|
||||
|
||||
void collectGarbage(const GCOptions & options, GCResults & results) override;
|
||||
|
@ -166,21 +162,7 @@ public:
|
|||
|
||||
void flushBadConnections();
|
||||
|
||||
struct Connection
|
||||
{
|
||||
FdSink to;
|
||||
FdSource from;
|
||||
unsigned int daemonVersion;
|
||||
std::optional<TrustedFlag> remoteTrustsUs;
|
||||
std::optional<std::string> daemonNixVersion;
|
||||
std::chrono::time_point<std::chrono::steady_clock> startTime;
|
||||
|
||||
virtual ~Connection();
|
||||
|
||||
virtual void closeWrite() = 0;
|
||||
|
||||
std::exception_ptr processStderr(Sink * sink = 0, Source * source = 0, bool flush = true);
|
||||
};
|
||||
struct Connection;
|
||||
|
||||
ref<Connection> openConnectionWrapper();
|
||||
|
||||
|
@ -196,11 +178,13 @@ protected:
|
|||
|
||||
void setOptions() override;
|
||||
|
||||
struct ConnectionHandle;
|
||||
|
||||
ConnectionHandle getConnection();
|
||||
|
||||
friend struct ConnectionHandle;
|
||||
|
||||
virtual ref<FSAccessor> getFSAccessor() override;
|
||||
virtual ref<SourceAccessor> getFSAccessor(bool requireValidPath) override;
|
||||
|
||||
virtual void narFromPath(const StorePath & path, Sink & sink) override;
|
||||
|
||||
|
@ -213,5 +197,4 @@ private:
|
|||
std::shared_ptr<Store> evalStore);
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
#pragma once
|
||||
///@file
|
||||
|
||||
namespace nix {
|
||||
|
||||
enum RepairFlag : bool { NoRepair = false, Repair = true };
|
||||
|
||||
}
|
|
@ -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
|
||||
`us–east-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"; }
|
||||
|
|
|
@ -2,7 +2,103 @@ R"(
|
|||
|
||||
**Store URL format**: `s3://`*bucket-name*
|
||||
|
||||
This store allows reading and writing a binary cache stored in an AWS
|
||||
S3 bucket.
|
||||
This store allows reading and writing a binary cache stored in an AWS S3 (or S3-compatible service) bucket.
|
||||
This store shares many idioms with the [HTTP Binary Cache Store](#http-binary-cache-store).
|
||||
|
||||
For AWS S3, the binary cache URL for a bucket named `example-nix-cache` will be exactly <s3://example-nix-cache>.
|
||||
For S3 compatible binary caches, consult that cache's documentation.
|
||||
|
||||
### Anonymous reads to your S3-compatible binary cache
|
||||
|
||||
> If your binary cache is publicly accessible and does not require authentication,
|
||||
> it is simplest to use the [HTTP Binary Cache Store] rather than S3 Binary Cache Store with
|
||||
> <https://example-nix-cache.s3.amazonaws.com> instead of <s3://example-nix-cache>.
|
||||
|
||||
Your bucket will need a
|
||||
[bucket policy](https://docs.aws.amazon.com/AmazonS3/v1/userguide/bucket-policies.html)
|
||||
like the following to be accessible:
|
||||
|
||||
```json
|
||||
{
|
||||
"Id": "DirectReads",
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Sid": "AllowDirectReads",
|
||||
"Action": [
|
||||
"s3:GetObject",
|
||||
"s3:GetBucketLocation"
|
||||
],
|
||||
"Effect": "Allow",
|
||||
"Resource": [
|
||||
"arn:aws:s3:::example-nix-cache",
|
||||
"arn:aws:s3:::example-nix-cache/*"
|
||||
],
|
||||
"Principal": "*"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Authentication
|
||||
|
||||
Nix will use the
|
||||
[default credential provider chain](https://docs.aws.amazon.com/sdk-for-cpp/v1/developer-guide/credentials.html)
|
||||
for authenticating requests to Amazon S3.
|
||||
|
||||
Note that this means Nix will read environment variables and files with different idioms than with Nix's own settings, as implemented by the AWS SDK.
|
||||
Consult the documentation linked above for further details.
|
||||
|
||||
### Authenticated reads to your S3 binary cache
|
||||
|
||||
Your bucket will need a bucket policy allowing the desired users to perform the `s3:GetObject` and `s3:GetBucketLocation` action on all objects in the bucket.
|
||||
The [anonymous policy given above](#anonymous-reads-to-your-s3-compatible-binary-cache) can be updated to have a restricted `Principal` to support this.
|
||||
|
||||
### Authenticated writes to your S3-compatible binary cache
|
||||
|
||||
Your account will need an IAM policy to support uploading to the bucket:
|
||||
|
||||
```json
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Sid": "UploadToCache",
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"s3:AbortMultipartUpload",
|
||||
"s3:GetBucketLocation",
|
||||
"s3:GetObject",
|
||||
"s3:ListBucket",
|
||||
"s3:ListBucketMultipartUploads",
|
||||
"s3:ListMultipartUploadParts",
|
||||
"s3:PutObject"
|
||||
],
|
||||
"Resource": [
|
||||
"arn:aws:s3:::example-nix-cache",
|
||||
"arn:aws:s3:::example-nix-cache/*"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Examples
|
||||
|
||||
With bucket policies and authentication set up as described above, uploading works via [`nix copy`](@docroot@/command-ref/new-cli/nix3-copy.md) (experimental).
|
||||
|
||||
- To upload with a specific credential profile for Amazon S3:
|
||||
|
||||
```console
|
||||
$ nix copy nixpkgs.hello \
|
||||
--to 's3://example-nix-cache?profile=cache-upload®ion=eu-west-2'
|
||||
```
|
||||
|
||||
- To upload to an S3-compatible binary cache:
|
||||
|
||||
```console
|
||||
$ nix copy nixpkgs.hello --to \
|
||||
's3://example-nix-cache?profile=cache-upload&scheme=https&endpoint=minio.example.com'
|
||||
```
|
||||
|
||||
)"
|
||||
|
|
59
src/libstore/serve-protocol-impl.hh
Normal file
59
src/libstore/serve-protocol-impl.hh
Normal file
|
@ -0,0 +1,59 @@
|
|||
#pragma once
|
||||
/**
|
||||
* @file
|
||||
*
|
||||
* Template implementations (as opposed to mere declarations).
|
||||
*
|
||||
* This file is an exmample of the "impl.hh" pattern. See the
|
||||
* contributing guide.
|
||||
*/
|
||||
|
||||
#include "serve-protocol.hh"
|
||||
#include "length-prefixed-protocol-helper.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
/* protocol-agnostic templates */
|
||||
|
||||
#define SERVE_USE_LENGTH_PREFIX_SERIALISER(TEMPLATE, T) \
|
||||
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 StoreDirConfig & store, ServeProto::WriteConn conn, const T & t) \
|
||||
{ \
|
||||
LengthPrefixedProtoHelper<ServeProto, T >::write(store, conn, t); \
|
||||
}
|
||||
|
||||
SERVE_USE_LENGTH_PREFIX_SERIALISER(template<typename T>, std::vector<T>)
|
||||
SERVE_USE_LENGTH_PREFIX_SERIALISER(template<typename T>, std::set<T>)
|
||||
SERVE_USE_LENGTH_PREFIX_SERIALISER(template<typename... Ts>, std::tuple<Ts...>)
|
||||
|
||||
#define COMMA_ ,
|
||||
SERVE_USE_LENGTH_PREFIX_SERIALISER(
|
||||
template<typename K COMMA_ typename V>,
|
||||
std::map<K COMMA_ V>)
|
||||
#undef COMMA_
|
||||
|
||||
/**
|
||||
* Use `CommonProto` where possible.
|
||||
*/
|
||||
template<typename T>
|
||||
struct ServeProto::Serialise
|
||||
{
|
||||
static T read(const StoreDirConfig & store, ServeProto::ReadConn conn)
|
||||
{
|
||||
return CommonProto::Serialise<T>::read(store,
|
||||
CommonProto::ReadConn { .from = conn.from });
|
||||
}
|
||||
static void write(const StoreDirConfig & store, ServeProto::WriteConn conn, const T & t)
|
||||
{
|
||||
CommonProto::Serialise<T>::write(store,
|
||||
CommonProto::WriteConn { .to = conn.to },
|
||||
t);
|
||||
}
|
||||
};
|
||||
|
||||
/* protocol-specific templates */
|
||||
|
||||
}
|
137
src/libstore/serve-protocol.cc
Normal file
137
src/libstore/serve-protocol.cc
Normal file
|
@ -0,0 +1,137 @@
|
|||
#include "serialise.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>
|
||||
|
||||
namespace nix {
|
||||
|
||||
/* protocol-specific definitions */
|
||||
|
||||
BuildResult ServeProto::Serialise<BuildResult>::read(const StoreDirConfig & store, ServeProto::ReadConn conn)
|
||||
{
|
||||
BuildResult status;
|
||||
status.status = (BuildResult::Status) readInt(conn.from);
|
||||
conn.from >> status.errorMsg;
|
||||
|
||||
if (GET_PROTOCOL_MINOR(conn.version) >= 3)
|
||||
conn.from
|
||||
>> status.timesBuilt
|
||||
>> status.isNonDeterministic
|
||||
>> status.startTime
|
||||
>> status.stopTime;
|
||||
if (GET_PROTOCOL_MINOR(conn.version) >= 6) {
|
||||
auto builtOutputs = ServeProto::Serialise<DrvOutputs>::read(store, conn);
|
||||
for (auto && [output, realisation] : builtOutputs)
|
||||
status.builtOutputs.insert_or_assign(
|
||||
std::move(output.outputName),
|
||||
std::move(realisation));
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
void ServeProto::Serialise<BuildResult>::write(const StoreDirConfig & store, ServeProto::WriteConn conn, const BuildResult & status)
|
||||
{
|
||||
conn.to
|
||||
<< status.status
|
||||
<< status.errorMsg;
|
||||
|
||||
if (GET_PROTOCOL_MINOR(conn.version) >= 3)
|
||||
conn.to
|
||||
<< status.timesBuilt
|
||||
<< status.isNonDeterministic
|
||||
<< status.startTime
|
||||
<< status.stopTime;
|
||||
if (GET_PROTOCOL_MINOR(conn.version) >= 6) {
|
||||
DrvOutputs builtOutputs;
|
||||
for (auto & [output, realisation] : status.builtOutputs)
|
||||
builtOutputs.insert_or_assign(realisation.id, realisation);
|
||||
ServeProto::write(store, conn, builtOutputs);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
#pragma once
|
||||
///@file
|
||||
|
||||
#include "common-protocol.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
#define SERVE_MAGIC_1 0x390c9deb
|
||||
|
@ -10,16 +12,174 @@ namespace nix {
|
|||
#define GET_PROTOCOL_MAJOR(x) ((x) & 0xff00)
|
||||
#define GET_PROTOCOL_MINOR(x) ((x) & 0x00ff)
|
||||
|
||||
typedef enum {
|
||||
cmdQueryValidPaths = 1,
|
||||
cmdQueryPathInfos = 2,
|
||||
cmdDumpStorePath = 3,
|
||||
cmdImportPaths = 4,
|
||||
cmdExportPaths = 5,
|
||||
cmdBuildPaths = 6,
|
||||
cmdQueryClosure = 7,
|
||||
cmdBuildDerivation = 8,
|
||||
cmdAddToStoreNar = 9,
|
||||
} ServeCommand;
|
||||
|
||||
struct StoreDirConfig;
|
||||
struct Source;
|
||||
|
||||
// items being serialised
|
||||
struct BuildResult;
|
||||
struct UnkeyedValidPathInfo;
|
||||
|
||||
|
||||
/**
|
||||
* The "serve protocol", used by ssh:// stores.
|
||||
*
|
||||
* This `struct` is basically just a `namespace`; We use a type rather
|
||||
* than a namespace just so we can use it as a template argument.
|
||||
*/
|
||||
struct ServeProto
|
||||
{
|
||||
/**
|
||||
* Enumeration of all the request types for the protocol.
|
||||
*/
|
||||
enum struct Command : uint64_t;
|
||||
|
||||
/**
|
||||
* Version type for the protocol.
|
||||
*
|
||||
* @todo Convert to struct with separate major vs minor fields.
|
||||
*/
|
||||
using Version = unsigned int;
|
||||
|
||||
/**
|
||||
* A unidirectional read connection, to be used by the read half of the
|
||||
* canonical serializers below.
|
||||
*/
|
||||
struct ReadConn {
|
||||
Source & from;
|
||||
Version version;
|
||||
};
|
||||
|
||||
/**
|
||||
* A unidirectional write connection, to be used by the write half of the
|
||||
* canonical serializers below.
|
||||
*/
|
||||
struct WriteConn {
|
||||
Sink & to;
|
||||
Version version;
|
||||
};
|
||||
|
||||
/**
|
||||
* Data type for canonical pairs of serialisers for the serve protocol.
|
||||
*
|
||||
* See https://en.cppreference.com/w/cpp/language/adl for the broader
|
||||
* concept of what is going on here.
|
||||
*/
|
||||
template<typename T>
|
||||
struct Serialise;
|
||||
// This is the definition of `Serialise` we *want* to put here, but
|
||||
// do not do so.
|
||||
//
|
||||
// See `worker-protocol.hh` for a longer explanation.
|
||||
#if 0
|
||||
{
|
||||
static T read(const StoreDirConfig & store, ReadConn conn);
|
||||
static void write(const StoreDirConfig & store, WriteConn conn, const T & t);
|
||||
};
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Wrapper function around `ServeProto::Serialise<T>::write` that allows us to
|
||||
* infer the type instead of having to write it down explicitly.
|
||||
*/
|
||||
template<typename 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
|
||||
{
|
||||
QueryValidPaths = 1,
|
||||
QueryPathInfos = 2,
|
||||
DumpStorePath = 3,
|
||||
ImportPaths = 4,
|
||||
ExportPaths = 5,
|
||||
BuildPaths = 6,
|
||||
QueryClosure = 7,
|
||||
BuildDerivation = 8,
|
||||
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.
|
||||
*
|
||||
* @todo Switch to using `ServeProto::Serialize` instead probably. But
|
||||
* this was not done at this time so there would be less churn.
|
||||
*/
|
||||
inline Sink & operator << (Sink & sink, ServeProto::Command op)
|
||||
{
|
||||
return sink << (uint64_t) op;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience for debugging.
|
||||
*
|
||||
* @todo Perhaps render known opcodes more nicely.
|
||||
*/
|
||||
inline std::ostream & operator << (std::ostream & s, ServeProto::Command op)
|
||||
{
|
||||
return s << (uint64_t) op;
|
||||
}
|
||||
|
||||
/**
|
||||
* Declare a canonical serialiser pair for the worker protocol.
|
||||
*
|
||||
* We specialise the struct merely to indicate that we are implementing
|
||||
* the function for the given type.
|
||||
*
|
||||
* Some sort of `template<...>` must be used with the caller for this to
|
||||
* be legal specialization syntax. See below for what that looks like in
|
||||
* practice.
|
||||
*/
|
||||
#define DECLARE_SERVE_SERIALISER(T) \
|
||||
struct ServeProto::Serialise< 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>);
|
||||
template<typename T>
|
||||
DECLARE_SERVE_SERIALISER(std::set<T>);
|
||||
template<typename... Ts>
|
||||
DECLARE_SERVE_SERIALISER(std::tuple<Ts...>);
|
||||
|
||||
#define COMMA_ ,
|
||||
template<typename K, typename V>
|
||||
DECLARE_SERVE_SERIALISER(std::map<K COMMA_ V>);
|
||||
#undef COMMA_
|
||||
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue