mirror of
https://github.com/NixOS/nix.git
synced 2025-11-18 08:19:35 +01:00
Merge remote-tracking branch 'upstream/master' into path-info
This commit is contained in:
commit
ca0994819d
344 changed files with 12008 additions and 6500 deletions
|
|
@ -86,8 +86,7 @@ void BinaryCacheStore::getFile(const std::string & path, Sink & sink)
|
|||
promise.set_exception(std::current_exception());
|
||||
}
|
||||
}});
|
||||
auto data = promise.get_future().get();
|
||||
sink((unsigned char *) data->data(), data->size());
|
||||
sink(*promise.get_future().get());
|
||||
}
|
||||
|
||||
std::shared_ptr<std::string> BinaryCacheStore::getFile(const std::string & path)
|
||||
|
|
@ -449,7 +448,9 @@ StorePath BinaryCacheStore::addTextToStore(const string & name, const string & s
|
|||
if (!repair && isValidPath(path))
|
||||
return path;
|
||||
|
||||
auto source = StringSource { s };
|
||||
StringSink sink;
|
||||
dumpString(s, sink);
|
||||
auto source = StringSource { *sink.s };
|
||||
return addToStoreCommon(source, repair, CheckSigs, [&](HashResult nar) {
|
||||
ValidPathInfo info {
|
||||
*this,
|
||||
|
|
@ -468,6 +469,24 @@ StorePath BinaryCacheStore::addTextToStore(const string & name, const string & s
|
|||
})->path;
|
||||
}
|
||||
|
||||
std::optional<const Realisation> BinaryCacheStore::queryRealisation(const DrvOutput & id)
|
||||
{
|
||||
auto outputInfoFilePath = realisationsPrefix + "/" + id.to_string() + ".doi";
|
||||
auto rawOutputInfo = getFile(outputInfoFilePath);
|
||||
|
||||
if (rawOutputInfo) {
|
||||
return {Realisation::fromJSON(
|
||||
nlohmann::json::parse(*rawOutputInfo), outputInfoFilePath)};
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
void BinaryCacheStore::registerDrvOutput(const Realisation& info) {
|
||||
auto filePath = realisationsPrefix + "/" + info.id.to_string() + ".doi";
|
||||
upsertFile(filePath, info.toJSON().dump(), "application/json");
|
||||
}
|
||||
|
||||
ref<FSAccessor> BinaryCacheStore::getFSAccessor()
|
||||
{
|
||||
return make_ref<RemoteFSAccessor>(ref<Store>(shared_from_this()), localNarCache);
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ struct BinaryCacheStoreConfig : virtual StoreConfig
|
|||
"enable multi-threading compression, available for xz only currently"};
|
||||
};
|
||||
|
||||
class BinaryCacheStore : public Store, public virtual BinaryCacheStoreConfig
|
||||
class BinaryCacheStore : public virtual BinaryCacheStoreConfig, public virtual Store
|
||||
{
|
||||
|
||||
private:
|
||||
|
|
@ -33,6 +33,9 @@ private:
|
|||
|
||||
protected:
|
||||
|
||||
// The prefix under which realisation infos will be stored
|
||||
const std::string realisationsPrefix = "/realisations";
|
||||
|
||||
BinaryCacheStore(const Params & params);
|
||||
|
||||
public:
|
||||
|
|
@ -99,15 +102,12 @@ public:
|
|||
StorePath addTextToStore(const string & name, const string & s,
|
||||
const StorePathSet & references, RepairFlag repair) override;
|
||||
|
||||
void registerDrvOutput(const Realisation & info) override;
|
||||
|
||||
std::optional<const Realisation> queryRealisation(const DrvOutput &) override;
|
||||
|
||||
void narFromPath(const StorePath & path, Sink & sink) override;
|
||||
|
||||
BuildResult buildDerivation(const StorePath & drvPath, const BasicDerivation & drv,
|
||||
BuildMode buildMode) override
|
||||
{ unsupported("buildDerivation"); }
|
||||
|
||||
void ensurePath(const StorePath & path) override
|
||||
{ unsupported("ensurePath"); }
|
||||
|
||||
ref<FSAccessor> getFSAccessor() override;
|
||||
|
||||
void addSignatures(const StorePath & storePath, const StringSet & sigs) override;
|
||||
|
|
|
|||
|
|
@ -50,6 +50,11 @@
|
|||
#define pivot_root(new_root, put_old) (syscall(SYS_pivot_root, new_root, put_old))
|
||||
#endif
|
||||
|
||||
#if __APPLE__
|
||||
#include <spawn.h>
|
||||
#include <sys/sysctl.h>
|
||||
#endif
|
||||
|
||||
#include <pwd.h>
|
||||
#include <grp.h>
|
||||
|
||||
|
|
@ -82,8 +87,8 @@ void handleDiffHook(
|
|||
printError(chomp(diffRes.second));
|
||||
} catch (Error & error) {
|
||||
ErrorInfo ei = error.info();
|
||||
ei.hint = hintfmt("diff hook execution failed: %s",
|
||||
(error.info().hint.has_value() ? error.info().hint->str() : ""));
|
||||
// FIXME: wrap errors.
|
||||
ei.msg = hintfmt("diff hook execution failed: %s", ei.msg.str());
|
||||
logError(ei);
|
||||
}
|
||||
}
|
||||
|
|
@ -119,6 +124,17 @@ DerivationGoal::DerivationGoal(const StorePath & drvPath, const BasicDerivation
|
|||
, buildMode(buildMode)
|
||||
{
|
||||
this->drv = std::make_unique<BasicDerivation>(BasicDerivation(drv));
|
||||
|
||||
auto outputHashes = staticOutputHashes(worker.store, drv);
|
||||
for (auto &[outputName, outputHash] : outputHashes)
|
||||
initialOutputs.insert({
|
||||
outputName,
|
||||
InitialOutput{
|
||||
.wanted = true, // Will be refined later
|
||||
.outputHash = outputHash
|
||||
}
|
||||
});
|
||||
|
||||
state = &DerivationGoal::haveDerivation;
|
||||
name = fmt(
|
||||
"building of '%s' from in-memory derivation",
|
||||
|
|
@ -231,7 +247,7 @@ void DerivationGoal::getDerivation()
|
|||
return;
|
||||
}
|
||||
|
||||
addWaitee(worker.makeSubstitutionGoal(drvPath));
|
||||
addWaitee(upcast_goal(worker.makeSubstitutionGoal(drvPath)));
|
||||
|
||||
state = &DerivationGoal::loadDerivation;
|
||||
}
|
||||
|
|
@ -253,8 +269,20 @@ void DerivationGoal::loadDerivation()
|
|||
|
||||
assert(worker.store.isValidPath(drvPath));
|
||||
|
||||
auto fullDrv = new Derivation(worker.store.derivationFromPath(drvPath));
|
||||
|
||||
auto outputHashes = staticOutputHashes(worker.store, *fullDrv);
|
||||
for (auto &[outputName, outputHash] : outputHashes)
|
||||
initialOutputs.insert({
|
||||
outputName,
|
||||
InitialOutput{
|
||||
.wanted = true, // Will be refined later
|
||||
.outputHash = outputHash
|
||||
}
|
||||
});
|
||||
|
||||
/* Get the derivation. */
|
||||
drv = std::unique_ptr<BasicDerivation>(new Derivation(worker.store.derivationFromPath(drvPath)));
|
||||
drv = std::unique_ptr<BasicDerivation>(fullDrv);
|
||||
|
||||
haveDerivation();
|
||||
}
|
||||
|
|
@ -304,10 +332,10 @@ void DerivationGoal::haveDerivation()
|
|||
/* Nothing to wait for; tail call */
|
||||
return DerivationGoal::gaveUpOnSubstitution();
|
||||
}
|
||||
addWaitee(worker.makeSubstitutionGoal(
|
||||
addWaitee(upcast_goal(worker.makeSubstitutionGoal(
|
||||
status.known->path,
|
||||
buildMode == bmRepair ? Repair : NoRepair,
|
||||
getDerivationCA(*drv)));
|
||||
getDerivationCA(*drv))));
|
||||
}
|
||||
|
||||
if (waitees.empty()) /* to prevent hang (no wake-up event) */
|
||||
|
|
@ -330,8 +358,13 @@ void DerivationGoal::outputsSubstitutionTried()
|
|||
|
||||
/* If the substitutes form an incomplete closure, then we should
|
||||
build the dependencies of this derivation, but after that, we
|
||||
can still use the substitutes for this derivation itself. */
|
||||
if (nrIncompleteClosure > 0) retrySubstitution = true;
|
||||
can still use the substitutes for this derivation itself.
|
||||
|
||||
If the nrIncompleteClosure != nrFailed, we have another issue as well.
|
||||
In particular, it may be the case that the hole in the closure is
|
||||
an output of the current derivation, which causes a loop if retried.
|
||||
*/
|
||||
if (nrIncompleteClosure > 0 && nrIncompleteClosure == nrFailed) retrySubstitution = true;
|
||||
|
||||
nrFailed = nrNoSubstituters = nrIncompleteClosure = 0;
|
||||
|
||||
|
|
@ -383,7 +416,7 @@ void DerivationGoal::gaveUpOnSubstitution()
|
|||
if (!settings.useSubstitutes)
|
||||
throw Error("dependency '%s' of '%s' does not exist, and substitution is disabled",
|
||||
worker.store.printStorePath(i), worker.store.printStorePath(drvPath));
|
||||
addWaitee(worker.makeSubstitutionGoal(i));
|
||||
addWaitee(upcast_goal(worker.makeSubstitutionGoal(i)));
|
||||
}
|
||||
|
||||
if (waitees.empty()) /* to prevent hang (no wake-up event) */
|
||||
|
|
@ -429,15 +462,12 @@ void DerivationGoal::repairClosure()
|
|||
/* Check each path (slow!). */
|
||||
for (auto & i : outputClosure) {
|
||||
if (worker.pathContentsGood(i)) continue;
|
||||
logError({
|
||||
.name = "Corrupt path in closure",
|
||||
.hint = hintfmt(
|
||||
"found corrupted or missing path '%s' in the output closure of '%s'",
|
||||
worker.store.printStorePath(i), worker.store.printStorePath(drvPath))
|
||||
});
|
||||
printError(
|
||||
"found corrupted or missing path '%s' in the output closure of '%s'",
|
||||
worker.store.printStorePath(i), worker.store.printStorePath(drvPath));
|
||||
auto drvPath2 = outputsToDrv.find(i);
|
||||
if (drvPath2 == outputsToDrv.end())
|
||||
addWaitee(worker.makeSubstitutionGoal(i, Repair));
|
||||
addWaitee(upcast_goal(worker.makeSubstitutionGoal(i, Repair)));
|
||||
else
|
||||
addWaitee(worker.makeDerivationGoal(drvPath2->second, StringSet(), bmRepair));
|
||||
}
|
||||
|
|
@ -488,7 +518,9 @@ void DerivationGoal::inputsRealised()
|
|||
if (useDerivation) {
|
||||
auto & fullDrv = *dynamic_cast<Derivation *>(drv.get());
|
||||
|
||||
if (!fullDrv.inputDrvs.empty() && fullDrv.type() == DerivationType::CAFloating) {
|
||||
if (settings.isExperimentalFeatureEnabled("ca-derivations") &&
|
||||
((!fullDrv.inputDrvs.empty() && derivationIsCA(fullDrv.type()))
|
||||
|| fullDrv.type() == DerivationType::DeferredInputAddressed)) {
|
||||
/* We are be able to resolve this derivation based on the
|
||||
now-known results of dependencies. If so, we become a stub goal
|
||||
aliasing that resolved derivation goal */
|
||||
|
|
@ -497,9 +529,7 @@ void DerivationGoal::inputsRealised()
|
|||
Derivation drvResolved { *std::move(attempt) };
|
||||
|
||||
auto pathResolved = writeDerivation(worker.store, drvResolved);
|
||||
/* Add to memotable to speed up downstream goal's queries with the
|
||||
original derivation. */
|
||||
drvPathResolutions.lock()->insert_or_assign(drvPath, pathResolved);
|
||||
resolvedDrv = drvResolved;
|
||||
|
||||
auto msg = fmt("Resolved derivation: '%s' -> '%s'",
|
||||
worker.store.printStorePath(drvPath),
|
||||
|
|
@ -530,12 +560,12 @@ void DerivationGoal::inputsRealised()
|
|||
if (!optRealizedInput)
|
||||
throw Error(
|
||||
"derivation '%s' requires output '%s' from input derivation '%s', which is supposedly realized already, yet we still don't know what path corresponds to that output",
|
||||
worker.store.printStorePath(drvPath), j, worker.store.printStorePath(drvPath));
|
||||
worker.store.printStorePath(drvPath), j, worker.store.printStorePath(depDrvPath));
|
||||
worker.store.computeFSClosure(*optRealizedInput, inputPaths);
|
||||
} else
|
||||
throw Error(
|
||||
"derivation '%s' requires non-existent output '%s' from input derivation '%s'",
|
||||
worker.store.printStorePath(drvPath), j, worker.store.printStorePath(drvPath));
|
||||
worker.store.printStorePath(drvPath), j, worker.store.printStorePath(depDrvPath));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -588,9 +618,17 @@ void DerivationGoal::tryToBuild()
|
|||
PathSet lockFiles;
|
||||
/* FIXME: Should lock something like the drv itself so we don't build same
|
||||
CA drv concurrently */
|
||||
for (auto & i : drv->outputsAndOptPaths(worker.store))
|
||||
if (i.second.second)
|
||||
lockFiles.insert(worker.store.Store::toRealPath(*i.second.second));
|
||||
if (dynamic_cast<LocalStore *>(&worker.store))
|
||||
/* If we aren't a local store, we might need to use the local store as
|
||||
a build remote, but that would cause a deadlock. */
|
||||
/* FIXME: Make it so we can use ourselves as a build remote even if we
|
||||
are the local store (separate locking for building vs scheduling? */
|
||||
/* FIXME: find some way to lock for scheduling for the other stores so
|
||||
a forking daemon with --store still won't farm out redundant builds.
|
||||
*/
|
||||
for (auto & i : drv->outputsAndOptPaths(worker.store))
|
||||
if (i.second.second)
|
||||
lockFiles.insert(worker.store.Store::toRealPath(*i.second.second));
|
||||
|
||||
if (!outputLocks.lockPaths(lockFiles, "", false)) {
|
||||
if (!actLock)
|
||||
|
|
@ -671,13 +709,15 @@ void DerivationGoal::tryToBuild()
|
|||
}
|
||||
|
||||
void DerivationGoal::tryLocalBuild() {
|
||||
bool buildLocally = buildMode != bmNormal || parsedDrv->willBuildLocally(worker.store);
|
||||
|
||||
/* Make sure that we are allowed to start a build. If this
|
||||
derivation prefers to be done locally, do it even if
|
||||
maxBuildJobs is 0. */
|
||||
/* Make sure that we are allowed to start a build. */
|
||||
if (!dynamic_cast<LocalStore *>(&worker.store)) {
|
||||
throw Error(
|
||||
"unable to build with a primary store that isn't a local store; "
|
||||
"either pass a different '--store' or enable remote builds."
|
||||
"\nhttps://nixos.org/nix/manual/#chap-distributed-builds");
|
||||
}
|
||||
unsigned int curBuilds = worker.getNrLocalBuilds();
|
||||
if (curBuilds >= settings.maxBuildJobs && !(buildLocally && curBuilds == 0)) {
|
||||
if (curBuilds >= settings.maxBuildJobs) {
|
||||
worker.waitForBuildSlot(shared_from_this());
|
||||
outputLocks.unlock();
|
||||
return;
|
||||
|
|
@ -844,14 +884,16 @@ void DerivationGoal::buildDone()
|
|||
So instead, check if the disk is (nearly) full now. If
|
||||
so, we don't mark this build as a permanent failure. */
|
||||
#if HAVE_STATVFS
|
||||
uint64_t required = 8ULL * 1024 * 1024; // FIXME: make configurable
|
||||
struct statvfs st;
|
||||
if (statvfs(worker.store.realStoreDir.c_str(), &st) == 0 &&
|
||||
(uint64_t) st.f_bavail * st.f_bsize < required)
|
||||
diskFull = true;
|
||||
if (statvfs(tmpDir.c_str(), &st) == 0 &&
|
||||
(uint64_t) st.f_bavail * st.f_bsize < required)
|
||||
diskFull = true;
|
||||
if (auto localStore = dynamic_cast<LocalStore *>(&worker.store)) {
|
||||
uint64_t required = 8ULL * 1024 * 1024; // FIXME: make configurable
|
||||
struct statvfs st;
|
||||
if (statvfs(localStore->realStoreDir.c_str(), &st) == 0 &&
|
||||
(uint64_t) st.f_bavail * st.f_bsize < required)
|
||||
diskFull = true;
|
||||
if (statvfs(tmpDir.c_str(), &st) == 0 &&
|
||||
(uint64_t) st.f_bavail * st.f_bsize < required)
|
||||
diskFull = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
deleteTmpDir(false);
|
||||
|
|
@ -872,9 +914,14 @@ void DerivationGoal::buildDone()
|
|||
statusToString(status));
|
||||
|
||||
if (!logger->isVerbose() && !logTail.empty()) {
|
||||
msg += (format("; last %d log lines:") % logTail.size()).str();
|
||||
for (auto & line : logTail)
|
||||
msg += "\n " + line;
|
||||
msg += fmt(";\nlast %d log lines:\n", logTail.size());
|
||||
for (auto & line : logTail) {
|
||||
msg += "> ";
|
||||
msg += line;
|
||||
msg += "\n";
|
||||
}
|
||||
msg += fmt("For full logs, run '" ANSI_BOLD "nix log %s" ANSI_NORMAL "'.",
|
||||
worker.store.printStorePath(drvPath));
|
||||
}
|
||||
|
||||
if (diskFull)
|
||||
|
|
@ -910,10 +957,8 @@ void DerivationGoal::buildDone()
|
|||
|
||||
LogSink(Activity & act) : act(act) { }
|
||||
|
||||
void operator() (const unsigned char * data, size_t len) override {
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
auto c = data[i];
|
||||
|
||||
void operator() (std::string_view data) override {
|
||||
for (auto c : data) {
|
||||
if (c == '\n') {
|
||||
flushLine();
|
||||
} else {
|
||||
|
|
@ -998,7 +1043,37 @@ void DerivationGoal::buildDone()
|
|||
}
|
||||
|
||||
void DerivationGoal::resolvedFinished() {
|
||||
done(BuildResult::Built);
|
||||
assert(resolvedDrv);
|
||||
|
||||
auto resolvedHashes = staticOutputHashes(worker.store, *resolvedDrv);
|
||||
|
||||
// `wantedOutputs` might be empty, which means “all the outputs”
|
||||
auto realWantedOutputs = wantedOutputs;
|
||||
if (realWantedOutputs.empty())
|
||||
realWantedOutputs = resolvedDrv->outputNames();
|
||||
|
||||
for (auto & wantedOutput : realWantedOutputs) {
|
||||
assert(initialOutputs.count(wantedOutput) != 0);
|
||||
assert(resolvedHashes.count(wantedOutput) != 0);
|
||||
auto realisation = worker.store.queryRealisation(
|
||||
DrvOutput{resolvedHashes.at(wantedOutput), wantedOutput}
|
||||
);
|
||||
// We've just built it, but maybe the build failed, in which case the
|
||||
// realisation won't be there
|
||||
if (realisation) {
|
||||
auto newRealisation = *realisation;
|
||||
newRealisation.id = DrvOutput{initialOutputs.at(wantedOutput).outputHash, wantedOutput};
|
||||
worker.store.registerDrvOutput(newRealisation);
|
||||
} else {
|
||||
// If we don't have a realisation, then it must mean that something
|
||||
// failed when building the resolved drv
|
||||
assert(!result.success());
|
||||
}
|
||||
}
|
||||
|
||||
// This is potentially a bit fishy in terms of error reporting. Not sure
|
||||
// how to do it in a cleaner way
|
||||
amDone(nrFailed == 0 ? ecSuccess : ecFailed, ex);
|
||||
}
|
||||
|
||||
HookReply DerivationGoal::tryBuildHook()
|
||||
|
|
@ -1023,7 +1098,14 @@ HookReply DerivationGoal::tryBuildHook()
|
|||
whether the hook wishes to perform the build. */
|
||||
string reply;
|
||||
while (true) {
|
||||
string s = readLine(worker.hook->fromHook.readSide.get());
|
||||
auto s = [&]() {
|
||||
try {
|
||||
return readLine(worker.hook->fromHook.readSide.get());
|
||||
} catch (Error & e) {
|
||||
e.addTrace({}, "while reading the response from the build hook");
|
||||
throw e;
|
||||
}
|
||||
}();
|
||||
if (handleJSONLogMessage(s, worker.act, worker.hook->activities, true))
|
||||
;
|
||||
else if (string(s, 0, 2) == "# ") {
|
||||
|
|
@ -1052,12 +1134,9 @@ HookReply DerivationGoal::tryBuildHook()
|
|||
|
||||
} catch (SysError & e) {
|
||||
if (e.errNo == EPIPE) {
|
||||
logError({
|
||||
.name = "Build hook died",
|
||||
.hint = hintfmt(
|
||||
"build hook died unexpectedly: %s",
|
||||
chomp(drainFD(worker.hook->fromHook.readSide.get())))
|
||||
});
|
||||
printError(
|
||||
"build hook died unexpectedly: %s",
|
||||
chomp(drainFD(worker.hook->fromHook.readSide.get())));
|
||||
worker.hook = 0;
|
||||
return rpDecline;
|
||||
} else
|
||||
|
|
@ -1066,7 +1145,12 @@ HookReply DerivationGoal::tryBuildHook()
|
|||
|
||||
hook = std::move(worker.hook);
|
||||
|
||||
machineName = readLine(hook->fromHook.readSide.get());
|
||||
try {
|
||||
machineName = readLine(hook->fromHook.readSide.get());
|
||||
} catch (Error & e) {
|
||||
e.addTrace({}, "while reading the machine name from the build hook");
|
||||
throw e;
|
||||
}
|
||||
|
||||
/* Tell the hook all the inputs that have to be copied to the
|
||||
remote system. */
|
||||
|
|
@ -1213,12 +1297,15 @@ void DerivationGoal::startBuilder()
|
|||
useChroot = !(derivationIsImpure(derivationType)) && !noChroot;
|
||||
}
|
||||
|
||||
if (worker.store.storeDir != worker.store.realStoreDir) {
|
||||
#if __linux__
|
||||
useChroot = true;
|
||||
#else
|
||||
throw Error("building using a diverted store is not supported on this platform");
|
||||
#endif
|
||||
if (auto localStoreP = dynamic_cast<LocalStore *>(&worker.store)) {
|
||||
auto & localStore = *localStoreP;
|
||||
if (localStore.storeDir != localStore.realStoreDir) {
|
||||
#if __linux__
|
||||
useChroot = true;
|
||||
#else
|
||||
throw Error("building using a diverted store is not supported on this platform");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/* Create a temporary directory where the build will take
|
||||
|
|
@ -1326,13 +1413,9 @@ void DerivationGoal::startBuilder()
|
|||
|
||||
/* Allow a user-configurable set of directories from the
|
||||
host file system. */
|
||||
PathSet dirs = settings.sandboxPaths;
|
||||
PathSet dirs2 = settings.extraSandboxPaths;
|
||||
dirs.insert(dirs2.begin(), dirs2.end());
|
||||
|
||||
dirsInChroot.clear();
|
||||
|
||||
for (auto i : dirs) {
|
||||
for (auto i : settings.sandboxPaths.get()) {
|
||||
if (i.empty()) continue;
|
||||
bool optional = false;
|
||||
if (i[i.size() - 1] == '?') {
|
||||
|
|
@ -1420,12 +1503,6 @@ void DerivationGoal::startBuilder()
|
|||
Samba-in-QEMU. */
|
||||
createDirs(chrootRootDir + "/etc");
|
||||
|
||||
writeFile(chrootRootDir + "/etc/passwd", fmt(
|
||||
"root:x:0:0:Nix build user:%3%:/noshell\n"
|
||||
"nixbld:x:%1%:%2%:Nix build user:%3%:/noshell\n"
|
||||
"nobody:x:65534:65534:Nobody:/:/noshell\n",
|
||||
sandboxUid(), sandboxGid(), settings.sandboxBuildDir));
|
||||
|
||||
/* Declare the build user's group so that programs get a consistent
|
||||
view of the system (e.g., "id -gn"). */
|
||||
writeFile(chrootRootDir + "/etc/group",
|
||||
|
|
@ -1703,12 +1780,10 @@ void DerivationGoal::startBuilder()
|
|||
userNamespaceSync.writeSide = -1;
|
||||
});
|
||||
|
||||
pid_t tmp;
|
||||
auto ss = tokenizeString<std::vector<std::string>>(readLine(builderOut.readSide.get()));
|
||||
assert(ss.size() == 2);
|
||||
usingUserNamespace = ss[0] == "1";
|
||||
if (!string2Int<pid_t>(ss[1], tmp)) abort();
|
||||
pid = tmp;
|
||||
pid = string2Int<pid_t>(ss[1]).value();
|
||||
|
||||
if (usingUserNamespace) {
|
||||
/* Set the UID/GID mapping of the builder's user namespace
|
||||
|
|
@ -1730,6 +1805,14 @@ void DerivationGoal::startBuilder()
|
|||
throw Error("cannot perform a sandboxed build because user namespaces are not enabled; check /proc/sys/user/max_user_namespaces");
|
||||
}
|
||||
|
||||
/* Now that we now the sandbox uid, we can write
|
||||
/etc/passwd. */
|
||||
writeFile(chrootRootDir + "/etc/passwd", fmt(
|
||||
"root:x:0:0:Nix build user:%3%:/noshell\n"
|
||||
"nixbld:x:%1%:%2%:Nix build user:%3%:/noshell\n"
|
||||
"nobody:x:65534:65534:Nobody:/:/noshell\n",
|
||||
sandboxUid(), sandboxGid(), settings.sandboxBuildDir));
|
||||
|
||||
/* Save the mount namespace of the child. We have to do this
|
||||
*before* the child does a chroot. */
|
||||
sandboxMountNamespace = open(fmt("/proc/%d/ns/mnt", (pid_t) pid).c_str(), O_RDONLY);
|
||||
|
|
@ -1755,8 +1838,17 @@ void DerivationGoal::startBuilder()
|
|||
worker.childStarted(shared_from_this(), {builderOut.readSide.get()}, true, true);
|
||||
|
||||
/* Check if setting up the build environment failed. */
|
||||
std::vector<std::string> msgs;
|
||||
while (true) {
|
||||
string msg = readLine(builderOut.readSide.get());
|
||||
string msg = [&]() {
|
||||
try {
|
||||
return readLine(builderOut.readSide.get());
|
||||
} catch (Error & e) {
|
||||
e.addTrace({}, "while waiting for the build environment to initialize (previous messages: %s)",
|
||||
concatStringsSep("|", msgs));
|
||||
throw e;
|
||||
}
|
||||
}();
|
||||
if (string(msg, 0, 1) == "\2") break;
|
||||
if (string(msg, 0, 1) == "\1") {
|
||||
FdSource source(builderOut.readSide.get());
|
||||
|
|
@ -1765,6 +1857,7 @@ void DerivationGoal::startBuilder()
|
|||
throw ex;
|
||||
}
|
||||
debug("sandbox setup: " + msg);
|
||||
msgs.push_back(std::move(msg));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1985,7 +2078,7 @@ void DerivationGoal::writeStructuredAttrs()
|
|||
chownToBuilder(tmpDir + "/.attrs.sh");
|
||||
}
|
||||
|
||||
struct RestrictedStoreConfig : LocalFSStoreConfig
|
||||
struct RestrictedStoreConfig : virtual LocalFSStoreConfig
|
||||
{
|
||||
using LocalFSStoreConfig::LocalFSStoreConfig;
|
||||
const std::string name() { return "Restricted Store"; }
|
||||
|
|
@ -1994,14 +2087,19 @@ struct RestrictedStoreConfig : 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 LocalFSStore, public virtual RestrictedStoreConfig
|
||||
struct RestrictedStore : public virtual RestrictedStoreConfig, public virtual LocalFSStore
|
||||
{
|
||||
ref<LocalStore> next;
|
||||
|
||||
DerivationGoal & goal;
|
||||
|
||||
RestrictedStore(const Params & params, ref<LocalStore> next, DerivationGoal & goal)
|
||||
: StoreConfig(params), Store(params), LocalFSStore(params), next(next), goal(goal)
|
||||
: StoreConfig(params)
|
||||
, LocalFSStoreConfig(params)
|
||||
, RestrictedStoreConfig(params)
|
||||
, Store(params)
|
||||
, LocalFSStore(params)
|
||||
, next(next), goal(goal)
|
||||
{ }
|
||||
|
||||
Path getRealStoreDir() override
|
||||
|
|
@ -2070,6 +2168,14 @@ struct RestrictedStore : public LocalFSStore, public virtual RestrictedStoreConf
|
|||
return path;
|
||||
}
|
||||
|
||||
StorePath addToStoreFromDump(Source & dump, const string & name,
|
||||
FileIngestionMethod method = FileIngestionMethod::Recursive, HashType hashAlgo = htSHA256, RepairFlag repair = NoRepair) override
|
||||
{
|
||||
auto path = next->addToStoreFromDump(dump, name, method, hashAlgo, repair);
|
||||
goal.addDependency(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
void narFromPath(const StorePath & path, Sink & sink) override
|
||||
{
|
||||
if (!goal.isAllowed(path))
|
||||
|
|
@ -2084,6 +2190,16 @@ struct RestrictedStore : public LocalFSStore, public virtual RestrictedStoreConf
|
|||
/* Nothing to be done; 'path' must already be valid. */
|
||||
}
|
||||
|
||||
void registerDrvOutput(const Realisation & info) override
|
||||
// XXX: This should probably be allowed as a no-op if the realisation
|
||||
// corresponds to an allowed derivation
|
||||
{ throw Error("registerDrvOutput"); }
|
||||
|
||||
std::optional<const Realisation> queryRealisation(const DrvOutput & id) override
|
||||
// XXX: This should probably be allowed if the realisation corresponds to
|
||||
// an allowed derivation
|
||||
{ throw Error("queryRealisation"); }
|
||||
|
||||
void buildPaths(const std::vector<StorePathWithOutputs> & paths, BuildMode buildMode) override
|
||||
{
|
||||
if (buildMode != bmNormal) throw Error("unsupported build mode");
|
||||
|
|
@ -2159,7 +2275,8 @@ void DerivationGoal::startDaemon()
|
|||
Store::Params params;
|
||||
params["path-info-cache-size"] = "0";
|
||||
params["store"] = worker.store.storeDir;
|
||||
params["root"] = worker.store.rootDir;
|
||||
if (auto localStore = dynamic_cast<LocalStore *>(&worker.store))
|
||||
params["root"] = localStore->rootDir;
|
||||
params["state"] = "/no-such-path";
|
||||
params["log"] = "/no-such-path";
|
||||
auto store = make_ref<RestrictedStore>(params,
|
||||
|
|
@ -2834,7 +2951,31 @@ void DerivationGoal::runChild()
|
|||
}
|
||||
}
|
||||
|
||||
#if __APPLE__
|
||||
posix_spawnattr_t attrp;
|
||||
|
||||
if (posix_spawnattr_init(&attrp))
|
||||
throw SysError("failed to initialize builder");
|
||||
|
||||
if (posix_spawnattr_setflags(&attrp, POSIX_SPAWN_SETEXEC))
|
||||
throw SysError("failed to initialize builder");
|
||||
|
||||
if (drv->platform == "aarch64-darwin") {
|
||||
// Unset kern.curproc_arch_affinity so we can escape Rosetta
|
||||
int affinity = 0;
|
||||
sysctlbyname("kern.curproc_arch_affinity", NULL, NULL, &affinity, sizeof(affinity));
|
||||
|
||||
cpu_type_t cpu = CPU_TYPE_ARM64;
|
||||
posix_spawnattr_setbinpref_np(&attrp, 1, &cpu, NULL);
|
||||
} else if (drv->platform == "x86_64-darwin") {
|
||||
cpu_type_t cpu = CPU_TYPE_X86_64;
|
||||
posix_spawnattr_setbinpref_np(&attrp, 1, &cpu, NULL);
|
||||
}
|
||||
|
||||
posix_spawn(NULL, builder, NULL, &attrp, stringsToCharPtrs(args).data(), stringsToCharPtrs(envStrs).data());
|
||||
#else
|
||||
execve(builder, stringsToCharPtrs(args).data(), stringsToCharPtrs(envStrs).data());
|
||||
#endif
|
||||
|
||||
throw SysError("executing '%1%'", drv->builder);
|
||||
|
||||
|
|
@ -2862,6 +3003,8 @@ void DerivationGoal::registerOutputs()
|
|||
for (auto & i : drv->outputsAndOptPaths(worker.store)) {
|
||||
if (!i.second.second || !worker.store.isValidPath(*i.second.second))
|
||||
allValid = false;
|
||||
else
|
||||
finalOutputs.insert_or_assign(i.first, *i.second.second);
|
||||
}
|
||||
if (allValid) return;
|
||||
}
|
||||
|
|
@ -3020,10 +3163,7 @@ void DerivationGoal::registerOutputs()
|
|||
auto rewriteOutput = [&]() {
|
||||
/* Apply hash rewriting if necessary. */
|
||||
if (!outputRewrites.empty()) {
|
||||
logWarning({
|
||||
.name = "Rewriting hashes",
|
||||
.hint = hintfmt("rewriting hashes in '%1%'; cross fingers", actualPath),
|
||||
});
|
||||
warn("rewriting hashes in '%1%'; cross fingers", actualPath);
|
||||
|
||||
/* FIXME: this is in-memory. */
|
||||
StringSink sink;
|
||||
|
|
@ -3105,6 +3245,20 @@ void DerivationGoal::registerOutputs()
|
|||
narHashAndSize.first,
|
||||
};
|
||||
newInfo0.narSize = narHashAndSize.second;
|
||||
if (scratchPath != newInfo0.path) {
|
||||
// Also rewrite the output path
|
||||
auto source = sinkToSource([&](Sink & nextSink) {
|
||||
StringSink sink;
|
||||
dumpPath(actualPath, sink);
|
||||
RewritingSink rsink2(oldHashPart, std::string(newInfo0.path.hashPart()), nextSink);
|
||||
rsink2(*sink.s);
|
||||
rsink2.flush();
|
||||
});
|
||||
Path tmpPath = actualPath + ".tmp";
|
||||
restorePath(tmpPath, *source);
|
||||
deletePath(actualPath);
|
||||
movePath(tmpPath, actualPath);
|
||||
}
|
||||
|
||||
assert(newInfo0.ca);
|
||||
return newInfo0;
|
||||
|
|
@ -3142,7 +3296,7 @@ void DerivationGoal::registerOutputs()
|
|||
valid. */
|
||||
worker.hashMismatch = true;
|
||||
delayedException = std::make_exception_ptr(
|
||||
BuildError("hash mismatch in fixed-output derivation '%s':\n wanted: %s\n got: %s",
|
||||
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)));
|
||||
|
|
@ -3152,6 +3306,15 @@ void DerivationGoal::registerOutputs()
|
|||
[&](DerivationOutputCAFloating dof) {
|
||||
return newInfoFromCA(dof);
|
||||
},
|
||||
[&](DerivationOutputDeferred) {
|
||||
// No derivation should reach that point without having been
|
||||
// rewritten first
|
||||
assert(false);
|
||||
// Ugly, but the compiler insists on having this return a value
|
||||
// of type `ValidPathInfo` despite the `assert(false)`, so
|
||||
// let's provide it
|
||||
return *(ValidPathInfo*)0;
|
||||
},
|
||||
}, output.output);
|
||||
|
||||
/* Calculate where we'll move the output files. In the checking case we
|
||||
|
|
@ -3191,7 +3354,13 @@ void DerivationGoal::registerOutputs()
|
|||
}
|
||||
}
|
||||
|
||||
auto localStoreP = dynamic_cast<LocalStore *>(&worker.store);
|
||||
if (!localStoreP)
|
||||
throw Unsupported("can only register outputs with local store, but this is %s", worker.store.getUri());
|
||||
auto & localStore = *localStoreP;
|
||||
|
||||
if (buildMode == bmCheck) {
|
||||
|
||||
if (!worker.store.isValidPath(newInfo.path)) continue;
|
||||
ValidPathInfo oldInfo(*worker.store.queryPathInfo(newInfo.path));
|
||||
if (newInfo.narHash != oldInfo.narHash) {
|
||||
|
|
@ -3216,8 +3385,8 @@ void DerivationGoal::registerOutputs()
|
|||
/* Since we verified the build, it's now ultimately trusted. */
|
||||
if (!oldInfo.ultimate) {
|
||||
oldInfo.ultimate = true;
|
||||
worker.store.signPathInfo(oldInfo);
|
||||
worker.store.registerValidPaths({ std::move(oldInfo) });
|
||||
localStore.signPathInfo(oldInfo);
|
||||
localStore.registerValidPaths({{oldInfo.path, oldInfo}});
|
||||
}
|
||||
|
||||
continue;
|
||||
|
|
@ -3233,13 +3402,13 @@ void DerivationGoal::registerOutputs()
|
|||
}
|
||||
|
||||
if (curRound == nrRounds) {
|
||||
worker.store.optimisePath(actualPath); // FIXME: combine with scanForReferences()
|
||||
localStore.optimisePath(actualPath); // FIXME: combine with scanForReferences()
|
||||
worker.markContentsGood(newInfo.path);
|
||||
}
|
||||
|
||||
newInfo.deriver = drvPath;
|
||||
newInfo.ultimate = true;
|
||||
worker.store.signPathInfo(newInfo);
|
||||
localStore.signPathInfo(newInfo);
|
||||
|
||||
finish(newInfo.path);
|
||||
|
||||
|
|
@ -3247,7 +3416,7 @@ void DerivationGoal::registerOutputs()
|
|||
isn't statically known so that we can safely unlock the path before
|
||||
the next iteration */
|
||||
if (newInfo.ca)
|
||||
worker.store.registerValidPaths({newInfo});
|
||||
localStore.registerValidPaths({{newInfo.path, newInfo}});
|
||||
|
||||
infos.emplace(outputName, std::move(newInfo));
|
||||
}
|
||||
|
|
@ -3281,10 +3450,7 @@ void DerivationGoal::registerOutputs()
|
|||
if (settings.enforceDeterminism)
|
||||
throw NotDeterministic(hint);
|
||||
|
||||
logError({
|
||||
.name = "Output determinism error",
|
||||
.hint = hint
|
||||
});
|
||||
printError(hint);
|
||||
|
||||
curRound = nrRounds; // we know enough, bail out early
|
||||
}
|
||||
|
|
@ -3320,11 +3486,16 @@ void DerivationGoal::registerOutputs()
|
|||
paths referenced by each of them. If there are cycles in the
|
||||
outputs, this will fail. */
|
||||
{
|
||||
auto localStoreP = dynamic_cast<LocalStore *>(&worker.store);
|
||||
if (!localStoreP)
|
||||
throw Unsupported("can only register outputs with local store, but this is %s", worker.store.getUri());
|
||||
auto & localStore = *localStoreP;
|
||||
|
||||
ValidPathInfos infos2;
|
||||
for (auto & [outputName, newInfo] : infos) {
|
||||
infos2.push_back(newInfo);
|
||||
infos2.insert_or_assign(newInfo.path, newInfo);
|
||||
}
|
||||
worker.store.registerValidPaths(infos2);
|
||||
localStore.registerValidPaths(infos2);
|
||||
}
|
||||
|
||||
/* In case of a fixed-output derivation hash mismatch, throw an
|
||||
|
|
@ -3337,21 +3508,14 @@ void DerivationGoal::registerOutputs()
|
|||
means it's safe to link the derivation to the output hash. We must do
|
||||
that for floating CA derivations, which otherwise couldn't be cached,
|
||||
but it's fine to do in all cases. */
|
||||
bool isCaFloating = drv->type() == DerivationType::CAFloating;
|
||||
|
||||
auto drvPathResolved = drvPath;
|
||||
if (!useDerivation && isCaFloating) {
|
||||
/* Once a floating CA derivations reaches this point, it
|
||||
must already be resolved, so we don't bother trying to
|
||||
downcast drv to get would would just be an empty
|
||||
inputDrvs field. */
|
||||
Derivation drv2 { *drv };
|
||||
drvPathResolved = writeDerivation(worker.store, drv2);
|
||||
if (settings.isExperimentalFeatureEnabled("ca-derivations")) {
|
||||
auto outputHashes = staticOutputHashes(worker.store, *drv);
|
||||
for (auto& [outputName, newInfo] : infos)
|
||||
worker.store.registerDrvOutput(Realisation{
|
||||
.id = DrvOutput{outputHashes.at(outputName), outputName},
|
||||
.outPath = newInfo.path});
|
||||
}
|
||||
|
||||
if (useDerivation || isCaFloating)
|
||||
for (auto & [outputName, newInfo] : infos)
|
||||
worker.store.linkDeriverToPath(drvPathResolved, outputName, newInfo.path);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -3529,7 +3693,12 @@ Path DerivationGoal::openLogFile()
|
|||
auto baseName = std::string(baseNameOf(worker.store.printStorePath(drvPath)));
|
||||
|
||||
/* Create a log file. */
|
||||
Path dir = fmt("%s/%s/%s/", worker.store.logDir, worker.store.drvsLogDir, string(baseName, 0, 2));
|
||||
Path logDir;
|
||||
if (auto localStore = dynamic_cast<LocalStore *>(&worker.store))
|
||||
logDir = localStore->logDir;
|
||||
else
|
||||
logDir = settings.nixLogDir;
|
||||
Path dir = fmt("%s/%s/%s/", logDir, LocalFSStore::drvsLogDir, string(baseName, 0, 2));
|
||||
createDirs(dir);
|
||||
|
||||
Path logFileName = fmt("%s/%s%s", dir, string(baseName, 2),
|
||||
|
|
@ -3668,9 +3837,8 @@ void DerivationGoal::checkPathValidity()
|
|||
{
|
||||
bool checkHash = buildMode == bmRepair;
|
||||
for (auto & i : queryPartialDerivationOutputMap()) {
|
||||
InitialOutput info {
|
||||
.wanted = wantOutput(i.first, wantedOutputs),
|
||||
};
|
||||
InitialOutput & info = initialOutputs.at(i.first);
|
||||
info.wanted = wantOutput(i.first, wantedOutputs);
|
||||
if (i.second) {
|
||||
auto outputPath = *i.second;
|
||||
info.known = {
|
||||
|
|
@ -3682,7 +3850,15 @@ void DerivationGoal::checkPathValidity()
|
|||
: PathStatus::Corrupt,
|
||||
};
|
||||
}
|
||||
initialOutputs.insert_or_assign(i.first, info);
|
||||
if (settings.isExperimentalFeatureEnabled("ca-derivations")) {
|
||||
if (auto real = worker.store.queryRealisation(
|
||||
DrvOutput{initialOutputs.at(i.first).outputHash, i.first})) {
|
||||
info.known = {
|
||||
.path = real->outPath,
|
||||
.status = PathStatus::Valid,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,18 +37,21 @@ struct InitialOutputStatus {
|
|||
|
||||
struct InitialOutput {
|
||||
bool wanted;
|
||||
Hash outputHash;
|
||||
std::optional<InitialOutputStatus> known;
|
||||
};
|
||||
|
||||
class DerivationGoal : public Goal
|
||||
struct DerivationGoal : public Goal
|
||||
{
|
||||
private:
|
||||
/* Whether to use an on-disk .drv file. */
|
||||
bool useDerivation;
|
||||
|
||||
/* The path of the derivation. */
|
||||
StorePath drvPath;
|
||||
|
||||
/* The path of the corresponding resolved derivation */
|
||||
std::optional<BasicDerivation> resolvedDrv;
|
||||
|
||||
/* The specific outputs that we need to build. Empty means all of
|
||||
them. */
|
||||
StringSet wantedOutputs;
|
||||
|
|
@ -246,7 +249,6 @@ private:
|
|||
|
||||
friend struct RestrictedStore;
|
||||
|
||||
public:
|
||||
DerivationGoal(const StorePath & drvPath,
|
||||
const StringSet & wantedOutputs, Worker & worker,
|
||||
BuildMode buildMode = bmNormal);
|
||||
|
|
@ -264,17 +266,11 @@ public:
|
|||
|
||||
void work() override;
|
||||
|
||||
StorePath getDrvPath()
|
||||
{
|
||||
return drvPath;
|
||||
}
|
||||
|
||||
/* Add wanted outputs to an already existing derivation goal. */
|
||||
void addWantedOutputs(const StringSet & outputs);
|
||||
|
||||
BuildResult getResult() { return result; }
|
||||
|
||||
private:
|
||||
/* The states. */
|
||||
void getDerivation();
|
||||
void loadDerivation();
|
||||
|
|
@ -318,8 +314,6 @@ private:
|
|||
/* Run the builder's process. */
|
||||
void runChild();
|
||||
|
||||
friend int childEntry(void *);
|
||||
|
||||
/* Check that the derivation outputs all exist and register them
|
||||
as valid. */
|
||||
void registerOutputs();
|
||||
|
|
|
|||
|
|
@ -5,25 +5,10 @@
|
|||
|
||||
namespace nix {
|
||||
|
||||
static void primeCache(Store & store, const std::vector<StorePathWithOutputs> & paths)
|
||||
{
|
||||
StorePathSet willBuild, willSubstitute, unknown;
|
||||
uint64_t downloadSize, narSize;
|
||||
store.queryMissing(paths, willBuild, willSubstitute, unknown, downloadSize, narSize);
|
||||
|
||||
if (!willBuild.empty() && 0 == settings.maxBuildJobs && getMachines().empty())
|
||||
throw Error(
|
||||
"%d derivations need to be built, but neither local builds ('--max-jobs') "
|
||||
"nor remote builds ('--builders') are enabled", willBuild.size());
|
||||
}
|
||||
|
||||
|
||||
void LocalStore::buildPaths(const std::vector<StorePathWithOutputs> & drvPaths, BuildMode buildMode)
|
||||
void Store::buildPaths(const std::vector<StorePathWithOutputs> & drvPaths, BuildMode buildMode)
|
||||
{
|
||||
Worker worker(*this);
|
||||
|
||||
primeCache(*this, drvPaths);
|
||||
|
||||
Goals goals;
|
||||
for (auto & path : drvPaths) {
|
||||
if (path.path.isDerivation())
|
||||
|
|
@ -44,9 +29,8 @@ void LocalStore::buildPaths(const std::vector<StorePathWithOutputs> & drvPaths,
|
|||
ex = i->ex;
|
||||
}
|
||||
if (i->exitCode != Goal::ecSuccess) {
|
||||
DerivationGoal * i2 = dynamic_cast<DerivationGoal *>(i.get());
|
||||
if (i2) failed.insert(i2->getDrvPath());
|
||||
else failed.insert(dynamic_cast<SubstitutionGoal *>(i.get())->getStorePath());
|
||||
if (auto i2 = dynamic_cast<DerivationGoal *>(i.get())) failed.insert(i2->drvPath);
|
||||
else if (auto i2 = dynamic_cast<SubstitutionGoal *>(i.get())) failed.insert(i2->storePath);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -59,7 +43,7 @@ void LocalStore::buildPaths(const std::vector<StorePathWithOutputs> & drvPaths,
|
|||
}
|
||||
}
|
||||
|
||||
BuildResult LocalStore::buildDerivation(const StorePath & drvPath, const BasicDerivation & drv,
|
||||
BuildResult Store::buildDerivation(const StorePath & drvPath, const BasicDerivation & drv,
|
||||
BuildMode buildMode)
|
||||
{
|
||||
Worker worker(*this);
|
||||
|
|
@ -79,13 +63,11 @@ BuildResult LocalStore::buildDerivation(const StorePath & drvPath, const BasicDe
|
|||
}
|
||||
|
||||
|
||||
void LocalStore::ensurePath(const StorePath & path)
|
||||
void Store::ensurePath(const StorePath & path)
|
||||
{
|
||||
/* If the path is already valid, we're done. */
|
||||
if (isValidPath(path)) return;
|
||||
|
||||
primeCache(*this, {{path}});
|
||||
|
||||
Worker worker(*this);
|
||||
GoalPtr goal = worker.makeSubstitutionGoal(path);
|
||||
Goals goals = {goal};
|
||||
|
|
@ -7,7 +7,7 @@ namespace nix {
|
|||
|
||||
/* Forward definition. */
|
||||
struct Goal;
|
||||
struct Worker;
|
||||
class Worker;
|
||||
|
||||
/* A pointer to a goal. */
|
||||
typedef std::shared_ptr<Goal> GoalPtr;
|
||||
|
|
@ -46,7 +46,7 @@ struct Goal : public std::enable_shared_from_this<Goal>
|
|||
unsigned int nrNoSubstituters;
|
||||
|
||||
/* Number of substitution goals we are/were waiting for that
|
||||
failed because othey had unsubstitutable references. */
|
||||
failed because they had unsubstitutable references. */
|
||||
unsigned int nrIncompleteClosure;
|
||||
|
||||
/* Name of this goal for debugging purposes. */
|
||||
|
|
|
|||
|
|
@ -145,15 +145,10 @@ void SubstitutionGoal::tryNext()
|
|||
/* Bail out early if this substituter lacks a valid
|
||||
signature. LocalStore::addToStore() also checks for this, but
|
||||
only after we've downloaded the path. */
|
||||
if (worker.store.requireSigs
|
||||
&& !sub->isTrusted
|
||||
&& !info->checkSignatures(worker.store, worker.store.getPublicKeys()))
|
||||
if (!sub->isTrusted && worker.store.pathInfoIsTrusted(*info))
|
||||
{
|
||||
logWarning({
|
||||
.name = "Invalid path signature",
|
||||
.hint = hintfmt("substituter '%s' does not have a valid signature for path '%s'",
|
||||
sub->getUri(), worker.store.printStorePath(storePath))
|
||||
});
|
||||
warn("substituter '%s' does not have a valid signature for path '%s'",
|
||||
sub->getUri(), worker.store.printStorePath(storePath));
|
||||
tryNext();
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,11 +8,8 @@ namespace nix {
|
|||
|
||||
class Worker;
|
||||
|
||||
class SubstitutionGoal : public Goal
|
||||
struct SubstitutionGoal : public Goal
|
||||
{
|
||||
friend class Worker;
|
||||
|
||||
private:
|
||||
/* The store path that should be realised through a substitute. */
|
||||
StorePath storePath;
|
||||
|
||||
|
|
@ -56,7 +53,6 @@ private:
|
|||
/* Content address for recomputing store path */
|
||||
std::optional<ContentAddress> ca;
|
||||
|
||||
public:
|
||||
SubstitutionGoal(const StorePath & storePath, Worker & worker, RepairFlag repair = NoRepair, std::optional<ContentAddress> ca = std::nullopt);
|
||||
~SubstitutionGoal();
|
||||
|
||||
|
|
@ -82,8 +78,6 @@ public:
|
|||
/* Callback used by the worker to write to the log. */
|
||||
void handleChildOutput(int fd, const string & data) override;
|
||||
void handleEOF(int fd) override;
|
||||
|
||||
StorePath getStorePath() { return storePath; }
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
namespace nix {
|
||||
|
||||
Worker::Worker(LocalStore & store)
|
||||
Worker::Worker(Store & store)
|
||||
: act(*logger, actRealise)
|
||||
, actDerivations(*logger, actBuilds)
|
||||
, actSubstitutions(*logger, actCopyPaths)
|
||||
|
|
@ -43,16 +43,13 @@ std::shared_ptr<DerivationGoal> Worker::makeDerivationGoalCommon(
|
|||
const StringSet & wantedOutputs,
|
||||
std::function<std::shared_ptr<DerivationGoal>()> mkDrvGoal)
|
||||
{
|
||||
WeakGoalPtr & abstract_goal_weak = derivationGoals[drvPath];
|
||||
GoalPtr abstract_goal = abstract_goal_weak.lock(); // FIXME
|
||||
std::shared_ptr<DerivationGoal> goal;
|
||||
if (!abstract_goal) {
|
||||
std::weak_ptr<DerivationGoal> & goal_weak = derivationGoals[drvPath];
|
||||
std::shared_ptr<DerivationGoal> goal = goal_weak.lock();
|
||||
if (!goal) {
|
||||
goal = mkDrvGoal();
|
||||
abstract_goal_weak = goal;
|
||||
goal_weak = goal;
|
||||
wakeUp(goal);
|
||||
} else {
|
||||
goal = std::dynamic_pointer_cast<DerivationGoal>(abstract_goal);
|
||||
assert(goal);
|
||||
goal->addWantedOutputs(wantedOutputs);
|
||||
}
|
||||
return goal;
|
||||
|
|
@ -77,10 +74,10 @@ std::shared_ptr<DerivationGoal> Worker::makeBasicDerivationGoal(const StorePath
|
|||
}
|
||||
|
||||
|
||||
GoalPtr Worker::makeSubstitutionGoal(const StorePath & path, RepairFlag repair, std::optional<ContentAddress> ca)
|
||||
std::shared_ptr<SubstitutionGoal> Worker::makeSubstitutionGoal(const StorePath & path, RepairFlag repair, std::optional<ContentAddress> ca)
|
||||
{
|
||||
WeakGoalPtr & goal_weak = substitutionGoals[path];
|
||||
GoalPtr goal = goal_weak.lock(); // FIXME
|
||||
std::weak_ptr<SubstitutionGoal> & goal_weak = substitutionGoals[path];
|
||||
auto goal = goal_weak.lock(); // FIXME
|
||||
if (!goal) {
|
||||
goal = std::make_shared<SubstitutionGoal>(path, *this, repair, ca);
|
||||
goal_weak = goal;
|
||||
|
|
@ -89,14 +86,14 @@ GoalPtr Worker::makeSubstitutionGoal(const StorePath & path, RepairFlag repair,
|
|||
return goal;
|
||||
}
|
||||
|
||||
|
||||
static void removeGoal(GoalPtr goal, WeakGoalMap & goalMap)
|
||||
template<typename G>
|
||||
static void removeGoal(std::shared_ptr<G> goal, std::map<StorePath, std::weak_ptr<G>> & goalMap)
|
||||
{
|
||||
/* !!! inefficient */
|
||||
for (WeakGoalMap::iterator i = goalMap.begin();
|
||||
for (auto i = goalMap.begin();
|
||||
i != goalMap.end(); )
|
||||
if (i->second.lock() == goal) {
|
||||
WeakGoalMap::iterator j = i; ++j;
|
||||
auto j = i; ++j;
|
||||
goalMap.erase(i);
|
||||
i = j;
|
||||
}
|
||||
|
|
@ -106,8 +103,12 @@ static void removeGoal(GoalPtr goal, WeakGoalMap & goalMap)
|
|||
|
||||
void Worker::removeGoal(GoalPtr goal)
|
||||
{
|
||||
nix::removeGoal(goal, derivationGoals);
|
||||
nix::removeGoal(goal, substitutionGoals);
|
||||
if (auto drvGoal = std::dynamic_pointer_cast<DerivationGoal>(goal))
|
||||
nix::removeGoal(drvGoal, derivationGoals);
|
||||
else if (auto subGoal = std::dynamic_pointer_cast<SubstitutionGoal>(goal))
|
||||
nix::removeGoal(subGoal, substitutionGoals);
|
||||
else
|
||||
assert(false);
|
||||
if (topGoals.find(goal) != topGoals.end()) {
|
||||
topGoals.erase(goal);
|
||||
/* If a top-level goal failed, then kill all other goals
|
||||
|
|
@ -206,7 +207,21 @@ void Worker::waitForAWhile(GoalPtr goal)
|
|||
|
||||
void Worker::run(const Goals & _topGoals)
|
||||
{
|
||||
for (auto & i : _topGoals) topGoals.insert(i);
|
||||
std::vector<nix::StorePathWithOutputs> topPaths;
|
||||
|
||||
for (auto & i : _topGoals) {
|
||||
topGoals.insert(i);
|
||||
if (auto goal = dynamic_cast<DerivationGoal *>(i.get())) {
|
||||
topPaths.push_back({goal->drvPath, goal->wantedOutputs});
|
||||
} else if (auto goal = dynamic_cast<SubstitutionGoal *>(i.get())) {
|
||||
topPaths.push_back({goal->storePath});
|
||||
}
|
||||
}
|
||||
|
||||
/* Call queryMissing() efficiently query substitutes. */
|
||||
StorePathSet willBuild, willSubstitute, unknown;
|
||||
uint64_t downloadSize, narSize;
|
||||
store.queryMissing(topPaths, willBuild, willSubstitute, unknown, downloadSize, narSize);
|
||||
|
||||
debug("entered goal loop");
|
||||
|
||||
|
|
@ -214,7 +229,9 @@ void Worker::run(const Goals & _topGoals)
|
|||
|
||||
checkInterrupt();
|
||||
|
||||
store.autoGC(false);
|
||||
// TODO GC interface?
|
||||
if (auto localStore = dynamic_cast<LocalStore *>(&store))
|
||||
localStore->autoGC(false);
|
||||
|
||||
/* Call every wake goal (in the ordering established by
|
||||
CompareGoalPtrs). */
|
||||
|
|
@ -439,10 +456,7 @@ bool Worker::pathContentsGood(const StorePath & path)
|
|||
}
|
||||
pathContentsGoodCache.insert_or_assign(path, res);
|
||||
if (!res)
|
||||
logError({
|
||||
.name = "Corrupted path",
|
||||
.hint = hintfmt("path '%s' is corrupted or missing!", store.printStorePath(path))
|
||||
});
|
||||
printError("path '%s' is corrupted or missing!", store.printStorePath(path));
|
||||
return res;
|
||||
}
|
||||
|
||||
|
|
@ -452,4 +466,9 @@ void Worker::markContentsGood(const StorePath & path)
|
|||
pathContentsGoodCache.insert_or_assign(path, true);
|
||||
}
|
||||
|
||||
|
||||
GoalPtr upcast_goal(std::shared_ptr<SubstitutionGoal> subGoal) {
|
||||
return subGoal;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,13 +2,28 @@
|
|||
|
||||
#include "types.hh"
|
||||
#include "lock.hh"
|
||||
#include "local-store.hh"
|
||||
#include "store-api.hh"
|
||||
#include "goal.hh"
|
||||
|
||||
#include <future>
|
||||
#include <thread>
|
||||
|
||||
namespace nix {
|
||||
|
||||
/* Forward definition. */
|
||||
class DerivationGoal;
|
||||
struct DerivationGoal;
|
||||
struct SubstitutionGoal;
|
||||
|
||||
/* Workaround for not being able to declare a something like
|
||||
|
||||
class SubstitutionGoal : public Goal;
|
||||
|
||||
even when Goal is a complete type.
|
||||
|
||||
This is still a static cast. The purpose of exporting it is to define it in
|
||||
a place where `SubstitutionGoal` is concrete, and use it in a place where it
|
||||
is opaque. */
|
||||
GoalPtr upcast_goal(std::shared_ptr<SubstitutionGoal> subGoal);
|
||||
|
||||
typedef std::chrono::time_point<std::chrono::steady_clock> steady_time_point;
|
||||
|
||||
|
|
@ -56,8 +71,8 @@ private:
|
|||
|
||||
/* Maps used to prevent multiple instantiations of a goal for the
|
||||
same derivation / path. */
|
||||
WeakGoalMap derivationGoals;
|
||||
WeakGoalMap substitutionGoals;
|
||||
std::map<StorePath, std::weak_ptr<DerivationGoal>> derivationGoals;
|
||||
std::map<StorePath, std::weak_ptr<SubstitutionGoal>> substitutionGoals;
|
||||
|
||||
/* Goals waiting for busy paths to be unlocked. */
|
||||
WeakGoals waitingForAnyGoal;
|
||||
|
|
@ -90,7 +105,7 @@ public:
|
|||
/* Set if at least one derivation is not deterministic in check mode. */
|
||||
bool checkMismatch;
|
||||
|
||||
LocalStore & store;
|
||||
Store & store;
|
||||
|
||||
std::unique_ptr<HookInstance> hook;
|
||||
|
||||
|
|
@ -112,7 +127,7 @@ public:
|
|||
it answers with "decline-permanently", we don't try again. */
|
||||
bool tryBuildHook = true;
|
||||
|
||||
Worker(LocalStore & store);
|
||||
Worker(Store & store);
|
||||
~Worker();
|
||||
|
||||
/* Make a goal (with caching). */
|
||||
|
|
@ -131,7 +146,7 @@ public:
|
|||
const StringSet & wantedOutputs, BuildMode buildMode = bmNormal);
|
||||
|
||||
/* substitution goal */
|
||||
GoalPtr makeSubstitutionGoal(const StorePath & storePath, RepairFlag repair = NoRepair, std::optional<ContentAddress> ca = std::nullopt);
|
||||
std::shared_ptr<SubstitutionGoal> makeSubstitutionGoal(const StorePath & storePath, RepairFlag repair = NoRepair, std::optional<ContentAddress> ca = std::nullopt);
|
||||
|
||||
/* Remove a dead goal. */
|
||||
void removeGoal(GoalPtr goal);
|
||||
|
|
|
|||
|
|
@ -22,10 +22,7 @@ static void createLinks(State & state, const Path & srcDir, const Path & dstDir,
|
|||
srcFiles = readDirectory(srcDir);
|
||||
} catch (SysError & e) {
|
||||
if (e.errNo == ENOTDIR) {
|
||||
logWarning({
|
||||
.name = "Create links - directory",
|
||||
.hint = hintfmt("not including '%s' in the user environment because it's not a directory", srcDir)
|
||||
});
|
||||
warn("not including '%s' in the user environment because it's not a directory", srcDir);
|
||||
return;
|
||||
}
|
||||
throw;
|
||||
|
|
@ -44,10 +41,7 @@ static void createLinks(State & state, const Path & srcDir, const Path & dstDir,
|
|||
throw SysError("getting status of '%1%'", srcFile);
|
||||
} catch (SysError & e) {
|
||||
if (e.errNo == ENOENT || e.errNo == ENOTDIR) {
|
||||
logWarning({
|
||||
.name = "Create links - skipping symlink",
|
||||
.hint = hintfmt("skipping dangling symlink '%s'", dstFile)
|
||||
});
|
||||
warn("skipping dangling symlink '%s'", dstFile);
|
||||
continue;
|
||||
}
|
||||
throw;
|
||||
|
|
|
|||
11
src/libstore/ca-specific-schema.sql
Normal file
11
src/libstore/ca-specific-schema.sql
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
-- Extension of the sql schema for content-addressed derivations.
|
||||
-- Won't be loaded unless the experimental feature `ca-derivations`
|
||||
-- is enabled
|
||||
|
||||
create table if not exists Realisations (
|
||||
drvPath text not null,
|
||||
outputName text not null, -- symbolic output id, usually "out"
|
||||
outputPath integer not null,
|
||||
primary key (drvPath, outputName),
|
||||
foreign key (outputPath) references ValidPaths(id) on delete cascade
|
||||
);
|
||||
|
|
@ -2,21 +2,19 @@
|
|||
#include "util.hh"
|
||||
#include "globals.hh"
|
||||
|
||||
#if HAVE_SODIUM
|
||||
#include <sodium.h>
|
||||
#endif
|
||||
|
||||
namespace nix {
|
||||
|
||||
static std::pair<std::string, std::string> split(const string & s)
|
||||
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 {std::string(s, 0, colon), std::string(s, colon + 1)};
|
||||
return {s.substr(0, colon), s.substr(colon + 1)};
|
||||
}
|
||||
|
||||
Key::Key(const string & s)
|
||||
Key::Key(std::string_view s)
|
||||
{
|
||||
auto ss = split(s);
|
||||
|
||||
|
|
@ -29,62 +27,57 @@ Key::Key(const string & s)
|
|||
key = base64Decode(key);
|
||||
}
|
||||
|
||||
SecretKey::SecretKey(const string & s)
|
||||
std::string Key::to_string() const
|
||||
{
|
||||
return name + ":" + base64Encode(key);
|
||||
}
|
||||
|
||||
SecretKey::SecretKey(std::string_view s)
|
||||
: Key(s)
|
||||
{
|
||||
#if HAVE_SODIUM
|
||||
if (key.size() != crypto_sign_SECRETKEYBYTES)
|
||||
throw Error("secret key is not valid");
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !HAVE_SODIUM
|
||||
[[noreturn]] static void noSodium()
|
||||
std::string SecretKey::signDetached(std::string_view data) const
|
||||
{
|
||||
throw Error("Nix was not compiled with libsodium, required for signed binary cache support");
|
||||
}
|
||||
#endif
|
||||
|
||||
std::string SecretKey::signDetached(const std::string & data) const
|
||||
{
|
||||
#if HAVE_SODIUM
|
||||
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));
|
||||
#else
|
||||
noSodium();
|
||||
#endif
|
||||
}
|
||||
|
||||
PublicKey SecretKey::toPublicKey() const
|
||||
{
|
||||
#if HAVE_SODIUM
|
||||
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));
|
||||
#else
|
||||
noSodium();
|
||||
#endif
|
||||
}
|
||||
|
||||
PublicKey::PublicKey(const string & s)
|
||||
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 HAVE_SODIUM
|
||||
if (key.size() != crypto_sign_PUBLICKEYBYTES)
|
||||
throw Error("public key is not valid");
|
||||
#endif
|
||||
}
|
||||
|
||||
bool verifyDetached(const std::string & data, const std::string & sig,
|
||||
const PublicKeys & publicKeys)
|
||||
{
|
||||
#if HAVE_SODIUM
|
||||
auto ss = split(sig);
|
||||
|
||||
auto key = publicKeys.find(ss.first);
|
||||
auto key = publicKeys.find(std::string(ss.first));
|
||||
if (key == publicKeys.end()) return false;
|
||||
|
||||
auto sig2 = base64Decode(ss.second);
|
||||
|
|
@ -94,9 +87,6 @@ bool verifyDetached(const std::string & data, const std::string & sig,
|
|||
return crypto_sign_verify_detached((unsigned char *) sig2.data(),
|
||||
(unsigned char *) data.data(), data.size(),
|
||||
(unsigned char *) key->second.key.data()) == 0;
|
||||
#else
|
||||
noSodium();
|
||||
#endif
|
||||
}
|
||||
|
||||
PublicKeys getDefaultPublicKeys()
|
||||
|
|
|
|||
|
|
@ -13,32 +13,40 @@ struct Key
|
|||
|
||||
/* Construct Key from a string in the format
|
||||
‘<name>:<key-in-base64>’. */
|
||||
Key(const std::string & s);
|
||||
Key(std::string_view s);
|
||||
|
||||
std::string to_string() const;
|
||||
|
||||
protected:
|
||||
Key(const std::string & name, const std::string & key)
|
||||
: name(name), key(key) { }
|
||||
Key(std::string_view name, std::string && key)
|
||||
: name(name), key(std::move(key)) { }
|
||||
};
|
||||
|
||||
struct PublicKey;
|
||||
|
||||
struct SecretKey : Key
|
||||
{
|
||||
SecretKey(const std::string & s);
|
||||
SecretKey(std::string_view s);
|
||||
|
||||
/* Return a detached signature of the given string. */
|
||||
std::string signDetached(const std::string & s) const;
|
||||
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(const std::string & data);
|
||||
PublicKey(std::string_view data);
|
||||
|
||||
private:
|
||||
PublicKey(const std::string & name, const std::string & key)
|
||||
: Key(name, key) { }
|
||||
PublicKey(std::string_view name, std::string && key)
|
||||
: Key(name, std::move(key)) { }
|
||||
friend struct SecretKey;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -153,10 +153,10 @@ struct TunnelSink : Sink
|
|||
{
|
||||
Sink & to;
|
||||
TunnelSink(Sink & to) : to(to) { }
|
||||
virtual void operator () (const unsigned char * data, size_t len)
|
||||
void operator () (std::string_view data)
|
||||
{
|
||||
to << STDERR_WRITE;
|
||||
writeString(data, len, to);
|
||||
writeString(data, to);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -165,7 +165,7 @@ struct TunnelSource : BufferedSource
|
|||
Source & from;
|
||||
BufferedSink & to;
|
||||
TunnelSource(Source & from, BufferedSink & to) : from(from), to(to) { }
|
||||
size_t readUnbuffered(unsigned char * data, size_t len) override
|
||||
size_t readUnbuffered(char * data, size_t len) override
|
||||
{
|
||||
to << STDERR_READ << len;
|
||||
to.flush();
|
||||
|
|
@ -215,6 +215,8 @@ struct ClientSettings
|
|||
for (auto & s : ss)
|
||||
if (trusted.count(s))
|
||||
subs.push_back(s);
|
||||
else if (!hasSuffix(s, "/") && trusted.count(s + "/"))
|
||||
subs.push_back(s + "/");
|
||||
else
|
||||
warn("ignoring untrusted substituter '%s'", s);
|
||||
res = subs;
|
||||
|
|
@ -231,8 +233,6 @@ struct ClientSettings
|
|||
settings.set(name, value);
|
||||
else if (setSubstituters(settings.substituters))
|
||||
;
|
||||
else if (setSubstituters(settings.extraSubstituters))
|
||||
;
|
||||
else
|
||||
debug("ignoring the client-specified setting '%s', because it is a restricted setting and you are not a trusted user", name);
|
||||
} catch (UsageError & e) {
|
||||
|
|
@ -276,8 +276,17 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
|
||||
case wopQueryValidPaths: {
|
||||
auto paths = worker_proto::read(*store, from, Phantom<StorePathSet> {});
|
||||
|
||||
SubstituteFlag substitute = NoSubstitute;
|
||||
if (GET_PROTOCOL_MINOR(clientVersion) >= 27) {
|
||||
substitute = readInt(from) ? Substitute : NoSubstitute;
|
||||
}
|
||||
|
||||
logger->startWork();
|
||||
auto res = store->queryValidPaths(paths);
|
||||
if (substitute) {
|
||||
store->substitutePaths(paths);
|
||||
}
|
||||
auto res = store->queryValidPaths(paths, substitute);
|
||||
logger->stopWork();
|
||||
worker_proto::write(*store, to, res);
|
||||
break;
|
||||
|
|
@ -859,6 +868,28 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
break;
|
||||
}
|
||||
|
||||
case wopRegisterDrvOutput: {
|
||||
logger->startWork();
|
||||
auto outputId = DrvOutput::parse(readString(from));
|
||||
auto outputPath = StorePath(readString(from));
|
||||
auto resolvedDrv = StorePath(readString(from));
|
||||
store->registerDrvOutput(Realisation{
|
||||
.id = outputId, .outPath = outputPath});
|
||||
logger->stopWork();
|
||||
break;
|
||||
}
|
||||
|
||||
case wopQueryRealisation: {
|
||||
logger->startWork();
|
||||
auto outputId = DrvOutput::parse(readString(from));
|
||||
auto info = store->queryRealisation(outputId);
|
||||
logger->stopWork();
|
||||
std::set<StorePath> outPaths;
|
||||
if (info) outPaths.insert(info->outPath);
|
||||
worker_proto::write(*store, to, outPaths);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw Error("invalid operation %1%", op);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,9 @@ std::optional<StorePath> DerivationOutput::path(const Store & store, std::string
|
|||
[](DerivationOutputCAFloating dof) -> std::optional<StorePath> {
|
||||
return std::nullopt;
|
||||
},
|
||||
[](DerivationOutputDeferred) -> std::optional<StorePath> {
|
||||
return std::nullopt;
|
||||
},
|
||||
}, output);
|
||||
}
|
||||
|
||||
|
|
@ -37,6 +40,7 @@ bool derivationIsCA(DerivationType dt) {
|
|||
case DerivationType::InputAddressed: return false;
|
||||
case DerivationType::CAFixed: return true;
|
||||
case DerivationType::CAFloating: return true;
|
||||
case DerivationType::DeferredInputAddressed: return false;
|
||||
};
|
||||
// Since enums can have non-variant values, but making a `default:` would
|
||||
// disable exhaustiveness warnings.
|
||||
|
|
@ -48,6 +52,7 @@ bool derivationIsFixed(DerivationType dt) {
|
|||
case DerivationType::InputAddressed: return false;
|
||||
case DerivationType::CAFixed: return true;
|
||||
case DerivationType::CAFloating: return false;
|
||||
case DerivationType::DeferredInputAddressed: return false;
|
||||
};
|
||||
assert(false);
|
||||
}
|
||||
|
|
@ -57,6 +62,7 @@ bool derivationIsImpure(DerivationType dt) {
|
|||
case DerivationType::InputAddressed: return false;
|
||||
case DerivationType::CAFixed: return true;
|
||||
case DerivationType::CAFloating: return false;
|
||||
case DerivationType::DeferredInputAddressed: return false;
|
||||
};
|
||||
assert(false);
|
||||
}
|
||||
|
|
@ -180,6 +186,11 @@ static DerivationOutput parseDerivationOutput(const Store & store,
|
|||
};
|
||||
}
|
||||
} else {
|
||||
if (pathS == "") {
|
||||
return DerivationOutput {
|
||||
.output = DerivationOutputDeferred { }
|
||||
};
|
||||
}
|
||||
validatePath(pathS);
|
||||
return DerivationOutput {
|
||||
.output = DerivationOutputInputAddressed {
|
||||
|
|
@ -325,6 +336,11 @@ string Derivation::unparse(const Store & store, bool maskOutputs,
|
|||
s += ','; printUnquotedString(s, makeFileIngestionPrefix(dof.method) + printHashType(dof.hashType));
|
||||
s += ','; printUnquotedString(s, "");
|
||||
},
|
||||
[&](DerivationOutputDeferred) {
|
||||
s += ','; printUnquotedString(s, "");
|
||||
s += ','; printUnquotedString(s, "");
|
||||
s += ','; printUnquotedString(s, "");
|
||||
}
|
||||
}, i.second.output);
|
||||
s += ')';
|
||||
}
|
||||
|
|
@ -389,7 +405,7 @@ std::string outputPathName(std::string_view drvName, std::string_view outputName
|
|||
|
||||
DerivationType BasicDerivation::type() const
|
||||
{
|
||||
std::set<std::string_view> inputAddressedOutputs, fixedCAOutputs, floatingCAOutputs;
|
||||
std::set<std::string_view> inputAddressedOutputs, fixedCAOutputs, floatingCAOutputs, deferredIAOutputs;
|
||||
std::optional<HashType> floatingHashType;
|
||||
for (auto & i : outputs) {
|
||||
std::visit(overloaded {
|
||||
|
|
@ -408,29 +424,34 @@ DerivationType BasicDerivation::type() const
|
|||
throw Error("All floating outputs must use the same hash type");
|
||||
}
|
||||
},
|
||||
[&](DerivationOutputDeferred _) {
|
||||
deferredIAOutputs.insert(i.first);
|
||||
},
|
||||
}, i.second.output);
|
||||
}
|
||||
|
||||
if (inputAddressedOutputs.empty() && fixedCAOutputs.empty() && floatingCAOutputs.empty()) {
|
||||
if (inputAddressedOutputs.empty() && fixedCAOutputs.empty() && floatingCAOutputs.empty() && deferredIAOutputs.empty()) {
|
||||
throw Error("Must have at least one output");
|
||||
} else if (! inputAddressedOutputs.empty() && fixedCAOutputs.empty() && floatingCAOutputs.empty()) {
|
||||
} else if (! inputAddressedOutputs.empty() && fixedCAOutputs.empty() && floatingCAOutputs.empty() && deferredIAOutputs.empty()) {
|
||||
return DerivationType::InputAddressed;
|
||||
} else if (inputAddressedOutputs.empty() && ! fixedCAOutputs.empty() && floatingCAOutputs.empty()) {
|
||||
} else if (inputAddressedOutputs.empty() && ! fixedCAOutputs.empty() && floatingCAOutputs.empty() && deferredIAOutputs.empty()) {
|
||||
if (fixedCAOutputs.size() > 1)
|
||||
// FIXME: Experimental feature?
|
||||
throw Error("Only one fixed output is allowed for now");
|
||||
if (*fixedCAOutputs.begin() != "out")
|
||||
throw Error("Single fixed output must be named \"out\"");
|
||||
return DerivationType::CAFixed;
|
||||
} else if (inputAddressedOutputs.empty() && fixedCAOutputs.empty() && ! floatingCAOutputs.empty()) {
|
||||
} else if (inputAddressedOutputs.empty() && fixedCAOutputs.empty() && ! floatingCAOutputs.empty() && deferredIAOutputs.empty()) {
|
||||
return DerivationType::CAFloating;
|
||||
} else if (inputAddressedOutputs.empty() && fixedCAOutputs.empty() && floatingCAOutputs.empty() && !deferredIAOutputs.empty()) {
|
||||
return DerivationType::DeferredInputAddressed;
|
||||
} else {
|
||||
throw Error("Can't mix derivation output types");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
DrvHashes drvHashes;
|
||||
Sync<DrvHashes> drvHashes;
|
||||
|
||||
/* pathDerivationModulo and hashDerivationModulo are mutually recursive
|
||||
*/
|
||||
|
|
@ -438,20 +459,22 @@ DrvHashes drvHashes;
|
|||
/* Look up the derivation by value and memoize the
|
||||
`hashDerivationModulo` call.
|
||||
*/
|
||||
static const DrvHashModulo & pathDerivationModulo(Store & store, const StorePath & drvPath)
|
||||
static const DrvHashModulo pathDerivationModulo(Store & store, const StorePath & drvPath)
|
||||
{
|
||||
auto h = drvHashes.find(drvPath);
|
||||
if (h == drvHashes.end()) {
|
||||
assert(store.isValidPath(drvPath));
|
||||
// Cache it
|
||||
h = drvHashes.insert_or_assign(
|
||||
drvPath,
|
||||
hashDerivationModulo(
|
||||
store,
|
||||
store.readDerivation(drvPath),
|
||||
false)).first;
|
||||
{
|
||||
auto hashes = drvHashes.lock();
|
||||
auto h = hashes->find(drvPath);
|
||||
if (h != hashes->end()) {
|
||||
return h->second;
|
||||
}
|
||||
}
|
||||
return h->second;
|
||||
auto h = hashDerivationModulo(
|
||||
store,
|
||||
store.readInvalidDerivation(drvPath),
|
||||
false);
|
||||
// Cache it
|
||||
drvHashes.lock()->insert_or_assign(drvPath, h);
|
||||
return h;
|
||||
}
|
||||
|
||||
/* See the header for interface details. These are the implementation details.
|
||||
|
|
@ -473,10 +496,9 @@ static const DrvHashModulo & pathDerivationModulo(Store & store, const StorePath
|
|||
*/
|
||||
DrvHashModulo hashDerivationModulo(Store & store, const Derivation & drv, bool maskOutputs)
|
||||
{
|
||||
bool isDeferred = false;
|
||||
/* Return a fixed hash for fixed-output derivations. */
|
||||
switch (drv.type()) {
|
||||
case DerivationType::CAFloating:
|
||||
throw Error("Regular input-addressed derivations are not yet allowed to depend on CA derivations");
|
||||
case DerivationType::CAFixed: {
|
||||
std::map<std::string, Hash> outputHashes;
|
||||
for (const auto & i : drv.outputs) {
|
||||
|
|
@ -489,8 +511,13 @@ DrvHashModulo hashDerivationModulo(Store & store, const Derivation & drv, bool m
|
|||
}
|
||||
return outputHashes;
|
||||
}
|
||||
case DerivationType::CAFloating:
|
||||
isDeferred = true;
|
||||
break;
|
||||
case DerivationType::InputAddressed:
|
||||
break;
|
||||
case DerivationType::DeferredInputAddressed:
|
||||
break;
|
||||
}
|
||||
|
||||
/* For other derivations, replace the inputs paths with recursive
|
||||
|
|
@ -503,6 +530,10 @@ DrvHashModulo hashDerivationModulo(Store & store, const Derivation & drv, bool m
|
|||
[&](Hash drvHash) {
|
||||
inputs2.insert_or_assign(drvHash.to_string(Base16, false), i.second);
|
||||
},
|
||||
[&](DeferredHash deferredHash) {
|
||||
isDeferred = true;
|
||||
inputs2.insert_or_assign(deferredHash.hash.to_string(Base16, false), i.second);
|
||||
},
|
||||
// CA derivation's output hashes
|
||||
[&](CaOutputHashes outputHashes) {
|
||||
std::set<std::string> justOut = { "out" };
|
||||
|
|
@ -517,7 +548,34 @@ DrvHashModulo hashDerivationModulo(Store & store, const Derivation & drv, bool m
|
|||
}, res);
|
||||
}
|
||||
|
||||
return hashString(htSHA256, drv.unparse(store, maskOutputs, &inputs2));
|
||||
auto hash = hashString(htSHA256, drv.unparse(store, maskOutputs, &inputs2));
|
||||
|
||||
if (isDeferred)
|
||||
return DeferredHash { hash };
|
||||
else
|
||||
return hash;
|
||||
}
|
||||
|
||||
|
||||
std::map<std::string, Hash> staticOutputHashes(Store& store, const Derivation& drv)
|
||||
{
|
||||
std::map<std::string, Hash> res;
|
||||
std::visit(overloaded {
|
||||
[&](Hash drvHash) {
|
||||
for (auto & outputName : drv.outputNames()) {
|
||||
res.insert({outputName, drvHash});
|
||||
}
|
||||
},
|
||||
[&](DeferredHash deferredHash) {
|
||||
for (auto & outputName : drv.outputNames()) {
|
||||
res.insert({outputName, deferredHash.hash});
|
||||
}
|
||||
},
|
||||
[&](CaOutputHashes outputHashes) {
|
||||
res = outputHashes;
|
||||
},
|
||||
}, hashDerivationModulo(store, drv, true));
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -620,6 +678,11 @@ void writeDerivation(Sink & out, const Store & store, const BasicDerivation & dr
|
|||
<< (makeFileIngestionPrefix(dof.method) + printHashType(dof.hashType))
|
||||
<< "";
|
||||
},
|
||||
[&](DerivationOutputDeferred) {
|
||||
out << ""
|
||||
<< ""
|
||||
<< "";
|
||||
},
|
||||
}, i.second.output);
|
||||
}
|
||||
worker_proto::write(store, out, drv.inputSrcs);
|
||||
|
|
@ -645,7 +708,6 @@ std::string downstreamPlaceholder(const Store & store, const StorePath & drvPath
|
|||
}
|
||||
|
||||
|
||||
// N.B. Outputs are left unchanged
|
||||
static void rewriteDerivation(Store & store, BasicDerivation & drv, const StringMap & rewrites) {
|
||||
|
||||
debug("Rewriting the derivation");
|
||||
|
|
@ -666,11 +728,23 @@ static void rewriteDerivation(Store & store, BasicDerivation & drv, const String
|
|||
newEnv.emplace(envName, envValue);
|
||||
}
|
||||
drv.env = newEnv;
|
||||
|
||||
auto hashModulo = hashDerivationModulo(store, Derivation(drv), true);
|
||||
for (auto & [outputName, output] : drv.outputs) {
|
||||
if (std::holds_alternative<DerivationOutputDeferred>(output.output)) {
|
||||
Hash h = std::get<Hash>(hashModulo);
|
||||
auto outPath = store.makeOutputPath(outputName, h, drv.name);
|
||||
drv.env[outputName] = store.printStorePath(outPath);
|
||||
output = DerivationOutput {
|
||||
.output = DerivationOutputInputAddressed {
|
||||
.path = std::move(outPath),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
Sync<DrvPathResolutions> drvPathResolutions;
|
||||
|
||||
std::optional<BasicDerivation> Derivation::tryResolve(Store & store) {
|
||||
BasicDerivation resolved { *this };
|
||||
|
||||
|
|
@ -682,8 +756,13 @@ std::optional<BasicDerivation> Derivation::tryResolve(Store & store) {
|
|||
StringSet newOutputNames;
|
||||
for (auto & outputName : input.second) {
|
||||
auto actualPathOpt = inputDrvOutputs.at(outputName);
|
||||
if (!actualPathOpt)
|
||||
if (!actualPathOpt) {
|
||||
warn("output %s of input %s missing, aborting the resolving",
|
||||
outputName,
|
||||
store.printStorePath(input.first)
|
||||
);
|
||||
return std::nullopt;
|
||||
}
|
||||
auto actualPath = *actualPathOpt;
|
||||
inputRewrites.emplace(
|
||||
downstreamPlaceholder(store, input.first, outputName),
|
||||
|
|
|
|||
|
|
@ -18,8 +18,6 @@ namespace nix {
|
|||
/* The traditional non-fixed-output derivation type. */
|
||||
struct DerivationOutputInputAddressed
|
||||
{
|
||||
/* Will need to become `std::optional<StorePath>` once input-addressed
|
||||
derivations are allowed to depend on cont-addressed derivations */
|
||||
StorePath path;
|
||||
};
|
||||
|
||||
|
|
@ -41,12 +39,18 @@ struct DerivationOutputCAFloating
|
|||
HashType hashType;
|
||||
};
|
||||
|
||||
/* Input-addressed output which depends on a (CA) derivation whose hash isn't
|
||||
* known atm
|
||||
*/
|
||||
struct DerivationOutputDeferred {};
|
||||
|
||||
struct DerivationOutput
|
||||
{
|
||||
std::variant<
|
||||
DerivationOutputInputAddressed,
|
||||
DerivationOutputCAFixed,
|
||||
DerivationOutputCAFloating
|
||||
DerivationOutputCAFloating,
|
||||
DerivationOutputDeferred
|
||||
> output;
|
||||
std::optional<HashType> hashAlgoOpt(const Store & store) const;
|
||||
/* Note, when you use this function you should make sure that you're passing
|
||||
|
|
@ -72,6 +76,7 @@ typedef std::map<string, string> StringPairs;
|
|||
|
||||
enum struct DerivationType : uint8_t {
|
||||
InputAddressed,
|
||||
DeferredInputAddressed,
|
||||
CAFixed,
|
||||
CAFloating,
|
||||
};
|
||||
|
|
@ -167,9 +172,12 @@ std::string outputPathName(std::string_view drvName, std::string_view outputName
|
|||
// whose output hashes are always known since they are fixed up-front.
|
||||
typedef std::map<std::string, Hash> CaOutputHashes;
|
||||
|
||||
struct DeferredHash { Hash hash; };
|
||||
|
||||
typedef std::variant<
|
||||
Hash, // regular DRV normalized hash
|
||||
CaOutputHashes
|
||||
CaOutputHashes, // Fixed-output derivation hashes
|
||||
DeferredHash // Deferred hashes for floating outputs drvs and their dependencies
|
||||
> DrvHashModulo;
|
||||
|
||||
/* Returns hashes with the details of fixed-output subderivations
|
||||
|
|
@ -197,20 +205,17 @@ typedef std::variant<
|
|||
*/
|
||||
DrvHashModulo hashDerivationModulo(Store & store, const Derivation & drv, bool maskOutputs);
|
||||
|
||||
/*
|
||||
Return a map associating each output to a hash that uniquely identifies its
|
||||
derivation (modulo the self-references).
|
||||
*/
|
||||
std::map<std::string, Hash> staticOutputHashes(Store& store, const Derivation& drv);
|
||||
|
||||
/* Memoisation of hashDerivationModulo(). */
|
||||
typedef std::map<StorePath, DrvHashModulo> DrvHashes;
|
||||
|
||||
extern DrvHashes drvHashes; // FIXME: global, not thread-safe
|
||||
|
||||
/* Memoisation of `readDerivation(..).resove()`. */
|
||||
typedef std::map<
|
||||
StorePath,
|
||||
std::optional<StorePath>
|
||||
> DrvPathResolutions;
|
||||
|
||||
// FIXME: global, though at least thread-safe.
|
||||
// FIXME: arguably overlaps with hashDerivationModulo memo table.
|
||||
extern Sync<DrvPathResolutions> drvPathResolutions;
|
||||
extern Sync<DrvHashes> drvHashes;
|
||||
|
||||
bool wantOutput(const string & output, const std::set<string> & wanted);
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ struct DummyStoreConfig : virtual StoreConfig {
|
|||
const std::string name() override { return "Dummy Store"; }
|
||||
};
|
||||
|
||||
struct DummyStore : public Store, public virtual DummyStoreConfig
|
||||
struct DummyStore : public virtual DummyStoreConfig, public virtual Store
|
||||
{
|
||||
DummyStore(const std::string scheme, const std::string uri, const Params & params)
|
||||
: DummyStore(params)
|
||||
|
|
@ -17,6 +17,7 @@ struct DummyStore : public Store, public virtual DummyStoreConfig
|
|||
|
||||
DummyStore(const Params & params)
|
||||
: StoreConfig(params)
|
||||
, DummyStoreConfig(params)
|
||||
, Store(params)
|
||||
{ }
|
||||
|
||||
|
|
@ -54,12 +55,8 @@ struct DummyStore : public Store, public virtual DummyStoreConfig
|
|||
void narFromPath(const StorePath & path, Sink & sink) override
|
||||
{ unsupported("narFromPath"); }
|
||||
|
||||
void ensurePath(const StorePath & path) override
|
||||
{ unsupported("ensurePath"); }
|
||||
|
||||
BuildResult buildDerivation(const StorePath & drvPath, const BasicDerivation & drv,
|
||||
BuildMode buildMode) override
|
||||
{ unsupported("buildDerivation"); }
|
||||
std::optional<const Realisation> queryRealisation(const DrvOutput&) override
|
||||
{ unsupported("queryRealisation"); }
|
||||
};
|
||||
|
||||
static RegisterStoreImplementation<DummyStore, DummyStoreConfig> regDummyStore;
|
||||
|
|
|
|||
|
|
@ -95,18 +95,18 @@ struct curlFileTransfer : public FileTransfer
|
|||
fmt(request.data ? "uploading '%s'" : "downloading '%s'", request.uri),
|
||||
{request.uri}, request.parentAct)
|
||||
, callback(std::move(callback))
|
||||
, finalSink([this](const unsigned char * data, size_t len) {
|
||||
, finalSink([this](std::string_view data) {
|
||||
if (this->request.dataCallback) {
|
||||
auto httpStatus = getHTTPStatus();
|
||||
|
||||
/* Only write data to the sink if this is a
|
||||
successful response. */
|
||||
if (successfulStatuses.count(httpStatus)) {
|
||||
writtenToSink += len;
|
||||
this->request.dataCallback((char *) data, len);
|
||||
writtenToSink += data.size();
|
||||
this->request.dataCallback(data);
|
||||
}
|
||||
} else
|
||||
this->result.data->append((char *) data, len);
|
||||
this->result.data->append(data);
|
||||
})
|
||||
{
|
||||
if (!request.expectedETag.empty())
|
||||
|
|
@ -171,8 +171,8 @@ struct curlFileTransfer : public FileTransfer
|
|||
}
|
||||
|
||||
if (errorSink)
|
||||
(*errorSink)((unsigned char *) contents, realSize);
|
||||
(*decompressionSink)((unsigned char *) contents, realSize);
|
||||
(*errorSink)({(char *) contents, realSize});
|
||||
(*decompressionSink)({(char *) contents, realSize});
|
||||
|
||||
return realSize;
|
||||
} catch (...) {
|
||||
|
|
@ -375,6 +375,13 @@ struct curlFileTransfer : public FileTransfer
|
|||
else if (code == CURLE_OK && successfulStatuses.count(httpStatus))
|
||||
{
|
||||
result.cached = httpStatus == 304;
|
||||
|
||||
// In 2021, GitHub responds to If-None-Match with 304,
|
||||
// but omits ETag. We just use the If-None-Match etag
|
||||
// since 304 implies they are the same.
|
||||
if (httpStatus == 304 && result.etag == "")
|
||||
result.etag = request.expectedETag;
|
||||
|
||||
act.progress(result.bodySize, result.bodySize);
|
||||
done = true;
|
||||
callback(std::move(result));
|
||||
|
|
@ -632,11 +639,7 @@ struct curlFileTransfer : public FileTransfer
|
|||
workerThreadMain();
|
||||
} catch (nix::Interrupted & e) {
|
||||
} catch (std::exception & e) {
|
||||
logError({
|
||||
.name = "File transfer",
|
||||
.hint = hintfmt("unexpected error in download thread: %s",
|
||||
e.what())
|
||||
});
|
||||
printError("unexpected error in download thread: %s", e.what());
|
||||
}
|
||||
|
||||
{
|
||||
|
|
@ -776,7 +779,7 @@ void FileTransfer::download(FileTransferRequest && request, Sink & sink)
|
|||
state->request.notify_one();
|
||||
});
|
||||
|
||||
request.dataCallback = [_state](char * buf, size_t len) {
|
||||
request.dataCallback = [_state](std::string_view data) {
|
||||
|
||||
auto state(_state->lock());
|
||||
|
||||
|
|
@ -794,7 +797,7 @@ void FileTransfer::download(FileTransferRequest && request, Sink & sink)
|
|||
|
||||
/* Append data to the buffer and wake up the calling
|
||||
thread. */
|
||||
state->data.append(buf, len);
|
||||
state->data.append(data);
|
||||
state->avail.notify_one();
|
||||
};
|
||||
|
||||
|
|
@ -840,7 +843,7 @@ void FileTransfer::download(FileTransferRequest && request, Sink & sink)
|
|||
if it's blocked on a full buffer. We don't hold the state
|
||||
lock while doing this to prevent blocking the download
|
||||
thread if sink() takes a long time. */
|
||||
sink((unsigned char *) chunk.data(), chunk.size());
|
||||
sink(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -852,11 +855,10 @@ FileTransferError::FileTransferError(FileTransfer::Error error, std::shared_ptr<
|
|||
// FIXME: Due to https://github.com/NixOS/nix/issues/3841 we don't know how
|
||||
// to print different messages for different verbosity levels. For now
|
||||
// we add some heuristics for detecting when we want to show the response.
|
||||
if (response && (response->size() < 1024 || response->find("<html>") != string::npos)) {
|
||||
err.hint = hintfmt("%1%\n\nresponse body:\n\n%2%", normaltxt(hf.str()), *response);
|
||||
} else {
|
||||
err.hint = hf;
|
||||
}
|
||||
if (response && (response->size() < 1024 || response->find("<html>") != string::npos))
|
||||
err.msg = hintfmt("%1%\n\nresponse body:\n\n%2%", normaltxt(hf.str()), chomp(*response));
|
||||
else
|
||||
err.msg = hf;
|
||||
}
|
||||
|
||||
bool isUri(const string & s)
|
||||
|
|
|
|||
|
|
@ -61,9 +61,9 @@ struct FileTransferRequest
|
|||
bool decompress = true;
|
||||
std::shared_ptr<std::string> data;
|
||||
std::string mimeType;
|
||||
std::function<void(char *, size_t)> dataCallback;
|
||||
std::function<void(std::string_view data)> dataCallback;
|
||||
|
||||
FileTransferRequest(const std::string & uri)
|
||||
FileTransferRequest(std::string_view uri)
|
||||
: uri(uri), parentAct(getCurActivity()) { }
|
||||
|
||||
std::string verb()
|
||||
|
|
|
|||
|
|
@ -25,7 +25,14 @@ public:
|
|||
|
||||
virtual StringSet readDirectory(const Path & path) = 0;
|
||||
|
||||
virtual std::string readFile(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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#include "archive.hh"
|
||||
#include "args.hh"
|
||||
#include "abstract-setting-to-json.hh"
|
||||
#include "compute-levels.hh"
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
|
|
@ -86,6 +87,12 @@ void loadConfFile()
|
|||
for (auto file = files.rbegin(); file != files.rend(); file++) {
|
||||
globalConfig.applyConfigFile(*file);
|
||||
}
|
||||
|
||||
auto nixConfEnv = getEnv("NIX_CONFIG");
|
||||
if (nixConfEnv.has_value()) {
|
||||
globalConfig.applyConfig(nixConfEnv.value(), "NIX_CONFIG");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
std::vector<Path> getUserConfigFiles()
|
||||
|
|
@ -125,6 +132,33 @@ StringSet Settings::getDefaultSystemFeatures()
|
|||
return features;
|
||||
}
|
||||
|
||||
StringSet Settings::getDefaultExtraPlatforms()
|
||||
{
|
||||
StringSet extraPlatforms;
|
||||
|
||||
if (std::string{SYSTEM} == "x86_64-linux" && !isWSL1())
|
||||
extraPlatforms.insert("i686-linux");
|
||||
|
||||
#if __linux__
|
||||
StringSet levels = computeLevels();
|
||||
for (auto iter = levels.begin(); iter != levels.end(); ++iter)
|
||||
extraPlatforms.insert(*iter + "-linux");
|
||||
#elif __APPLE__
|
||||
// Rosetta 2 emulation layer can run x86_64 binaries on aarch64
|
||||
// machines. Note that we can’t force processes from executing
|
||||
// x86_64 in aarch64 environments or vice versa since they can
|
||||
// always exec with their own binary preferences.
|
||||
if (pathExists("/Library/Apple/System/Library/LaunchDaemons/com.apple.oahd.plist")) {
|
||||
if (std::string{SYSTEM} == "x86_64-darwin")
|
||||
extraPlatforms.insert("aarch64-darwin");
|
||||
else if (std::string{SYSTEM} == "aarch64-darwin")
|
||||
extraPlatforms.insert("x86_64-darwin");
|
||||
}
|
||||
#endif
|
||||
|
||||
return extraPlatforms;
|
||||
}
|
||||
|
||||
bool Settings::isExperimentalFeatureEnabled(const std::string & name)
|
||||
{
|
||||
auto & f = experimentalFeatures.get();
|
||||
|
|
@ -154,7 +188,7 @@ NLOHMANN_JSON_SERIALIZE_ENUM(SandboxMode, {
|
|||
{SandboxMode::smDisabled, false},
|
||||
});
|
||||
|
||||
template<> void BaseSetting<SandboxMode>::set(const std::string & str)
|
||||
template<> void BaseSetting<SandboxMode>::set(const std::string & str, bool append)
|
||||
{
|
||||
if (str == "true") value = smEnabled;
|
||||
else if (str == "relaxed") value = smRelaxed;
|
||||
|
|
@ -162,6 +196,11 @@ template<> void BaseSetting<SandboxMode>::set(const std::string & str)
|
|||
else throw UsageError("option '%s' has invalid value '%s'", name, str);
|
||||
}
|
||||
|
||||
template<> bool BaseSetting<SandboxMode>::isAppendable()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
template<> std::string BaseSetting<SandboxMode>::to_string() const
|
||||
{
|
||||
if (value == smEnabled) return "true";
|
||||
|
|
@ -192,16 +231,29 @@ template<> void BaseSetting<SandboxMode>::convertToArg(Args & args, const std::s
|
|||
});
|
||||
}
|
||||
|
||||
void MaxBuildJobsSetting::set(const std::string & str)
|
||||
void MaxBuildJobsSetting::set(const std::string & str, bool append)
|
||||
{
|
||||
if (str == "auto") value = std::max(1U, std::thread::hardware_concurrency());
|
||||
else if (!string2Int(str, value))
|
||||
throw UsageError("configuration setting '%s' should be 'auto' or an integer", name);
|
||||
else {
|
||||
if (auto n = string2Int<decltype(value)>(str))
|
||||
value = *n;
|
||||
else
|
||||
throw UsageError("configuration setting '%s' should be 'auto' or an integer", name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void PluginFilesSetting::set(const std::string & str, bool append)
|
||||
{
|
||||
if (pluginsLoaded)
|
||||
throw UsageError("plugin-files set after plugins were loaded, you may need to move the flag before the subcommand");
|
||||
BaseSetting<Paths>::set(str, append);
|
||||
}
|
||||
|
||||
|
||||
void initPlugins()
|
||||
{
|
||||
assert(!settings.pluginFiles.pluginsLoaded);
|
||||
for (const auto & pluginFile : settings.pluginFiles.get()) {
|
||||
Paths pluginFiles;
|
||||
try {
|
||||
|
|
@ -227,6 +279,9 @@ void initPlugins()
|
|||
unknown settings. */
|
||||
globalConfig.reapplyUnknownSettings();
|
||||
globalConfig.warnUnknownSettings();
|
||||
|
||||
/* Tell the user if they try to set plugin-files after we've already loaded */
|
||||
settings.pluginFiles.pluginsLoaded = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,24 @@ struct MaxBuildJobsSetting : public BaseSetting<unsigned int>
|
|||
options->addSetting(this);
|
||||
}
|
||||
|
||||
void set(const std::string & str) override;
|
||||
void set(const std::string & str, bool append = false) override;
|
||||
};
|
||||
|
||||
struct PluginFilesSetting : public BaseSetting<Paths>
|
||||
{
|
||||
bool pluginsLoaded = false;
|
||||
|
||||
PluginFilesSetting(Config * options,
|
||||
const Paths & def,
|
||||
const std::string & name,
|
||||
const std::string & description,
|
||||
const std::set<std::string> & aliases = {})
|
||||
: BaseSetting<Paths>(def, name, description, aliases)
|
||||
{
|
||||
options->addSetting(this);
|
||||
}
|
||||
|
||||
void set(const std::string & str, bool append = false) override;
|
||||
};
|
||||
|
||||
class Settings : public Config {
|
||||
|
|
@ -34,6 +51,8 @@ class Settings : public Config {
|
|||
|
||||
StringSet getDefaultSystemFeatures();
|
||||
|
||||
StringSet getDefaultExtraPlatforms();
|
||||
|
||||
bool isWSL1();
|
||||
|
||||
public:
|
||||
|
|
@ -413,14 +432,6 @@ public:
|
|||
Setting<bool> sandboxFallback{this, true, "sandbox-fallback",
|
||||
"Whether to disable sandboxing when the kernel doesn't allow it."};
|
||||
|
||||
Setting<PathSet> extraSandboxPaths{
|
||||
this, {}, "extra-sandbox-paths",
|
||||
R"(
|
||||
A list of additional paths appended to `sandbox-paths`. Useful if
|
||||
you want to extend its default value.
|
||||
)",
|
||||
{"build-extra-chroot-dirs", "build-extra-sandbox-paths"}};
|
||||
|
||||
Setting<size_t> buildRepeat{
|
||||
this, 0, "repeat",
|
||||
R"(
|
||||
|
|
@ -553,7 +564,7 @@ public:
|
|||
|
||||
Setting<StringSet> extraPlatforms{
|
||||
this,
|
||||
std::string{SYSTEM} == "x86_64-linux" && !isWSL1() ? StringSet{"i686-linux"} : StringSet{},
|
||||
getDefaultExtraPlatforms(),
|
||||
"extra-platforms",
|
||||
R"(
|
||||
Platforms other than the native one which this machine is capable of
|
||||
|
|
@ -591,7 +602,7 @@ public:
|
|||
|
||||
Setting<Strings> substituters{
|
||||
this,
|
||||
nixStore == "/nix/store" ? Strings{"https://cache.nixos.org/"} : Strings(),
|
||||
Strings{"https://cache.nixos.org/"},
|
||||
"substituters",
|
||||
R"(
|
||||
A list of URLs of substituters, separated by whitespace. The default
|
||||
|
|
@ -599,17 +610,6 @@ public:
|
|||
)",
|
||||
{"binary-caches"}};
|
||||
|
||||
// FIXME: provide a way to add to option values.
|
||||
Setting<Strings> extraSubstituters{
|
||||
this, {}, "extra-substituters",
|
||||
R"(
|
||||
Additional binary caches appended to those specified in
|
||||
`substituters`. When used by unprivileged users, untrusted
|
||||
substituters (i.e. those not listed in `trusted-substituters`) are
|
||||
silently ignored.
|
||||
)",
|
||||
{"extra-binary-caches"}};
|
||||
|
||||
Setting<StringSet> trustedSubstituters{
|
||||
this, {}, "trusted-substituters",
|
||||
R"(
|
||||
|
|
@ -836,7 +836,7 @@ public:
|
|||
Setting<uint64_t> minFreeCheckInterval{this, 5, "min-free-check-interval",
|
||||
"Number of seconds between checking free disk space."};
|
||||
|
||||
Setting<Paths> pluginFiles{
|
||||
PluginFilesSetting pluginFiles{
|
||||
this, {}, "plugin-files",
|
||||
R"(
|
||||
A list of plugin files to be loaded by Nix. Each of these files will
|
||||
|
|
@ -848,6 +848,9 @@ public:
|
|||
command, and RegisterSetting to add new nix config settings. See the
|
||||
constructors for those types for more details.
|
||||
|
||||
Warning! These APIs are inherently unstable and may change from
|
||||
release to release.
|
||||
|
||||
Since these files are loaded into the same address space as Nix
|
||||
itself, they must be DSOs compatible with the instance of Nix
|
||||
running at the time (i.e. compiled against the same headers, not
|
||||
|
|
@ -886,7 +889,7 @@ public:
|
|||
Example `~/.config/nix/nix.conf`:
|
||||
|
||||
```
|
||||
access-tokens = "github.com=23ac...b289 gitlab.mycompany.com=PAT:A123Bp_Cd..EfG gitlab.com=OAuth2:1jklw3jk"
|
||||
access-tokens = github.com=23ac...b289 gitlab.mycompany.com=PAT:A123Bp_Cd..EfG gitlab.com=OAuth2:1jklw3jk
|
||||
```
|
||||
|
||||
Example `~/code/flake.nix`:
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ struct HttpBinaryCacheStoreConfig : virtual BinaryCacheStoreConfig
|
|||
const std::string name() override { return "Http Binary Cache Store"; }
|
||||
};
|
||||
|
||||
class HttpBinaryCacheStore : public BinaryCacheStore, public HttpBinaryCacheStoreConfig
|
||||
class HttpBinaryCacheStore : public virtual HttpBinaryCacheStoreConfig, public virtual BinaryCacheStore
|
||||
{
|
||||
private:
|
||||
|
||||
|
|
@ -36,6 +36,9 @@ public:
|
|||
const Path & _cacheUri,
|
||||
const Params & params)
|
||||
: StoreConfig(params)
|
||||
, BinaryCacheStoreConfig(params)
|
||||
, HttpBinaryCacheStoreConfig(params)
|
||||
, Store(params)
|
||||
, BinaryCacheStore(params)
|
||||
, cacheUri(scheme + "://" + _cacheUri)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ struct LegacySSHStoreConfig : virtual StoreConfig
|
|||
const std::string name() override { return "Legacy SSH Store"; }
|
||||
};
|
||||
|
||||
struct LegacySSHStore : public Store, public virtual LegacySSHStoreConfig
|
||||
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
|
||||
|
|
@ -48,6 +48,7 @@ struct LegacySSHStore : public Store, public virtual LegacySSHStoreConfig
|
|||
|
||||
LegacySSHStore(const string & scheme, const string & host, const Params & params)
|
||||
: StoreConfig(params)
|
||||
, LegacySSHStoreConfig(params)
|
||||
, Store(params)
|
||||
, host(host)
|
||||
, connections(make_ref<Pool<Connection>>(
|
||||
|
|
@ -332,6 +333,10 @@ public:
|
|||
auto conn(connections->get());
|
||||
return conn->remoteVersion;
|
||||
}
|
||||
|
||||
std::optional<const Realisation> queryRealisation(const DrvOutput&) override
|
||||
// TODO: Implement
|
||||
{ unsupported("queryRealisation"); }
|
||||
};
|
||||
|
||||
static RegisterStoreImplementation<LegacySSHStore, LegacySSHStoreConfig> regLegacySSHStore;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ struct LocalBinaryCacheStoreConfig : virtual BinaryCacheStoreConfig
|
|||
const std::string name() override { return "Local Binary Cache Store"; }
|
||||
};
|
||||
|
||||
class LocalBinaryCacheStore : public BinaryCacheStore, public virtual LocalBinaryCacheStoreConfig
|
||||
class LocalBinaryCacheStore : public virtual LocalBinaryCacheStoreConfig, public virtual BinaryCacheStore
|
||||
{
|
||||
private:
|
||||
|
||||
|
|
@ -24,6 +24,9 @@ public:
|
|||
const Path & binaryCacheDir,
|
||||
const Params & params)
|
||||
: StoreConfig(params)
|
||||
, BinaryCacheStoreConfig(params)
|
||||
, LocalBinaryCacheStoreConfig(params)
|
||||
, Store(params)
|
||||
, BinaryCacheStore(params)
|
||||
, binaryCacheDir(binaryCacheDir)
|
||||
{
|
||||
|
|
@ -87,6 +90,7 @@ protected:
|
|||
void LocalBinaryCacheStore::init()
|
||||
{
|
||||
createDirs(binaryCacheDir + "/nar");
|
||||
createDirs(binaryCacheDir + realisationsPrefix);
|
||||
if (writeDebugInfo)
|
||||
createDirs(binaryCacheDir + "/debuginfo");
|
||||
BinaryCacheStore::init();
|
||||
|
|
|
|||
|
|
@ -19,10 +19,10 @@ struct LocalStoreAccessor : public FSAccessor
|
|||
|
||||
LocalStoreAccessor(ref<LocalFSStore> store) : store(store) { }
|
||||
|
||||
Path toRealPath(const Path & path)
|
||||
Path toRealPath(const Path & path, bool requireValidPath = true)
|
||||
{
|
||||
auto storePath = store->toStorePath(path).first;
|
||||
if (!store->isValidPath(storePath))
|
||||
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());
|
||||
}
|
||||
|
|
@ -61,9 +61,9 @@ struct LocalStoreAccessor : public FSAccessor
|
|||
return res;
|
||||
}
|
||||
|
||||
std::string readFile(const Path & path) override
|
||||
std::string readFile(const Path & path, bool requireValidPath = true) override
|
||||
{
|
||||
return nix::readFile(toRealPath(path));
|
||||
return nix::readFile(toRealPath(path, requireValidPath));
|
||||
}
|
||||
|
||||
std::string readLink(const Path & path) override
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ struct LocalFSStoreConfig : virtual StoreConfig
|
|||
"log", "directory where Nix will store state"};
|
||||
};
|
||||
|
||||
class LocalFSStore : public virtual Store, public virtual LocalFSStoreConfig
|
||||
class LocalFSStore : public virtual LocalFSStoreConfig, public virtual Store
|
||||
{
|
||||
public:
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#include "nar-info.hh"
|
||||
#include "references.hh"
|
||||
#include "callback.hh"
|
||||
#include "topo-sort.hh"
|
||||
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
|
|
@ -41,9 +42,68 @@
|
|||
|
||||
namespace nix {
|
||||
|
||||
struct LocalStore::State::Stmts {
|
||||
/* Some precompiled SQLite statements. */
|
||||
SQLiteStmt RegisterValidPath;
|
||||
SQLiteStmt UpdatePathInfo;
|
||||
SQLiteStmt AddReference;
|
||||
SQLiteStmt QueryPathInfo;
|
||||
SQLiteStmt QueryReferences;
|
||||
SQLiteStmt QueryReferrers;
|
||||
SQLiteStmt InvalidatePath;
|
||||
SQLiteStmt AddDerivationOutput;
|
||||
SQLiteStmt RegisterRealisedOutput;
|
||||
SQLiteStmt QueryValidDerivers;
|
||||
SQLiteStmt QueryDerivationOutputs;
|
||||
SQLiteStmt QueryRealisedOutput;
|
||||
SQLiteStmt QueryAllRealisedOutputs;
|
||||
SQLiteStmt QueryPathFromHashPart;
|
||||
SQLiteStmt QueryValidPaths;
|
||||
};
|
||||
|
||||
int getSchema(Path schemaPath)
|
||||
{
|
||||
int curSchema = 0;
|
||||
if (pathExists(schemaPath)) {
|
||||
string s = readFile(schemaPath);
|
||||
auto n = string2Int<int>(s);
|
||||
if (!n)
|
||||
throw Error("'%1%' is corrupt", schemaPath);
|
||||
curSchema = *n;
|
||||
}
|
||||
return curSchema;
|
||||
}
|
||||
|
||||
void migrateCASchema(SQLite& db, Path schemaPath, AutoCloseFD& lockFd)
|
||||
{
|
||||
const int nixCASchemaVersion = 1;
|
||||
int curCASchema = getSchema(schemaPath);
|
||||
if (curCASchema != nixCASchemaVersion) {
|
||||
if (curCASchema > nixCASchemaVersion) {
|
||||
throw Error("current Nix store ca-schema is version %1%, but I only support %2%",
|
||||
curCASchema, nixCASchemaVersion);
|
||||
}
|
||||
|
||||
if (!lockFile(lockFd.get(), ltWrite, false)) {
|
||||
printInfo("waiting for exclusive access to the Nix store for ca drvs...");
|
||||
lockFile(lockFd.get(), ltWrite, true);
|
||||
}
|
||||
|
||||
if (curCASchema == 0) {
|
||||
static const char schema[] =
|
||||
#include "ca-specific-schema.sql.gen.hh"
|
||||
;
|
||||
db.exec(schema);
|
||||
}
|
||||
writeFile(schemaPath, fmt("%d", nixCASchemaVersion));
|
||||
lockFile(lockFd.get(), ltRead, true);
|
||||
}
|
||||
}
|
||||
|
||||
LocalStore::LocalStore(const Params & params)
|
||||
: StoreConfig(params)
|
||||
, LocalFSStoreConfig(params)
|
||||
, LocalStoreConfig(params)
|
||||
, Store(params)
|
||||
, LocalFSStore(params)
|
||||
, realStoreDir_{this, false, rootDir != "" ? rootDir + "/nix/store" : storeDir, "real",
|
||||
|
|
@ -59,6 +119,7 @@ LocalStore::LocalStore(const Params & params)
|
|||
, locksHeld(tokenizeString<PathSet>(getEnv("NIX_HELD_LOCKS").value_or("")))
|
||||
{
|
||||
auto state(_state.lock());
|
||||
state->stmts = std::make_unique<State::Stmts>();
|
||||
|
||||
/* Create missing state directories if they don't already exist. */
|
||||
createDirs(realStoreDir);
|
||||
|
|
@ -89,12 +150,7 @@ LocalStore::LocalStore(const Params & params)
|
|||
|
||||
struct group * gr = getgrnam(settings.buildUsersGroup.get().c_str());
|
||||
if (!gr)
|
||||
logError({
|
||||
.name = "'build-users-group' not found",
|
||||
.hint = hintfmt(
|
||||
"warning: the group '%1%' specified in 'build-users-group' does not exist",
|
||||
settings.buildUsersGroup)
|
||||
});
|
||||
printError("warning: the group '%1%' specified in 'build-users-group' does not exist", settings.buildUsersGroup);
|
||||
else {
|
||||
struct stat st;
|
||||
if (stat(realStoreDir.c_str(), &st))
|
||||
|
|
@ -221,32 +277,58 @@ LocalStore::LocalStore(const Params & params)
|
|||
|
||||
else openDB(*state, false);
|
||||
|
||||
if (settings.isExperimentalFeatureEnabled("ca-derivations")) {
|
||||
migrateCASchema(state->db, dbDir + "/ca-schema", globalLock);
|
||||
}
|
||||
|
||||
/* Prepare SQL statements. */
|
||||
state->stmtRegisterValidPath.create(state->db,
|
||||
state->stmts->RegisterValidPath.create(state->db,
|
||||
"insert into ValidPaths (path, hash, registrationTime, deriver, narSize, ultimate, sigs, ca) values (?, ?, ?, ?, ?, ?, ?, ?);");
|
||||
state->stmtUpdatePathInfo.create(state->db,
|
||||
state->stmts->UpdatePathInfo.create(state->db,
|
||||
"update ValidPaths set narSize = ?, hash = ?, ultimate = ?, sigs = ?, ca = ? where path = ?;");
|
||||
state->stmtAddReference.create(state->db,
|
||||
state->stmts->AddReference.create(state->db,
|
||||
"insert or replace into Refs (referrer, reference) values (?, ?);");
|
||||
state->stmtQueryPathInfo.create(state->db,
|
||||
state->stmts->QueryPathInfo.create(state->db,
|
||||
"select id, hash, registrationTime, deriver, narSize, ultimate, sigs, ca from ValidPaths where path = ?;");
|
||||
state->stmtQueryReferences.create(state->db,
|
||||
state->stmts->QueryReferences.create(state->db,
|
||||
"select path from Refs join ValidPaths on reference = id where referrer = ?;");
|
||||
state->stmtQueryReferrers.create(state->db,
|
||||
state->stmts->QueryReferrers.create(state->db,
|
||||
"select path from Refs join ValidPaths on referrer = id where reference = (select id from ValidPaths where path = ?);");
|
||||
state->stmtInvalidatePath.create(state->db,
|
||||
state->stmts->InvalidatePath.create(state->db,
|
||||
"delete from ValidPaths where path = ?;");
|
||||
state->stmtAddDerivationOutput.create(state->db,
|
||||
state->stmts->AddDerivationOutput.create(state->db,
|
||||
"insert or replace into DerivationOutputs (drv, id, path) values (?, ?, ?);");
|
||||
state->stmtQueryValidDerivers.create(state->db,
|
||||
state->stmts->QueryValidDerivers.create(state->db,
|
||||
"select v.id, v.path from DerivationOutputs d join ValidPaths v on d.drv = v.id where d.path = ?;");
|
||||
state->stmtQueryDerivationOutputs.create(state->db,
|
||||
state->stmts->QueryDerivationOutputs.create(state->db,
|
||||
"select id, path from DerivationOutputs where drv = ?;");
|
||||
// Use "path >= ?" with limit 1 rather than "path like '?%'" to
|
||||
// ensure efficient lookup.
|
||||
state->stmtQueryPathFromHashPart.create(state->db,
|
||||
state->stmts->QueryPathFromHashPart.create(state->db,
|
||||
"select path from ValidPaths where path >= ? limit 1;");
|
||||
state->stmtQueryValidPaths.create(state->db, "select path from ValidPaths");
|
||||
state->stmts->QueryValidPaths.create(state->db, "select path from ValidPaths");
|
||||
if (settings.isExperimentalFeatureEnabled("ca-derivations")) {
|
||||
state->stmts->RegisterRealisedOutput.create(state->db,
|
||||
R"(
|
||||
insert or replace into Realisations (drvPath, outputName, outputPath)
|
||||
values (?, ?, (select id from ValidPaths where path = ?))
|
||||
;
|
||||
)");
|
||||
state->stmts->QueryRealisedOutput.create(state->db,
|
||||
R"(
|
||||
select Output.path from Realisations
|
||||
inner join ValidPaths as Output on Output.id = Realisations.outputPath
|
||||
where drvPath = ? and outputName = ?
|
||||
;
|
||||
)");
|
||||
state->stmts->QueryAllRealisedOutputs.create(state->db,
|
||||
R"(
|
||||
select outputName, Output.path from Realisations
|
||||
inner join ValidPaths as Output on Output.id = Realisations.outputPath
|
||||
where drvPath = ?
|
||||
;
|
||||
)");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -284,16 +366,7 @@ std::string LocalStore::getUri()
|
|||
|
||||
|
||||
int LocalStore::getSchema()
|
||||
{
|
||||
int curSchema = 0;
|
||||
if (pathExists(schemaPath)) {
|
||||
string s = readFile(schemaPath);
|
||||
if (!string2Int(s, curSchema))
|
||||
throw Error("'%1%' is corrupt", schemaPath);
|
||||
}
|
||||
return curSchema;
|
||||
}
|
||||
|
||||
{ return nix::getSchema(schemaPath); }
|
||||
|
||||
void LocalStore::openDB(State & state, bool create)
|
||||
{
|
||||
|
|
@ -573,21 +646,29 @@ void LocalStore::checkDerivationOutputs(const StorePath & drvPath, const Derivat
|
|||
[&](DerivationOutputCAFloating _) {
|
||||
/* Nothing to check */
|
||||
},
|
||||
[&](DerivationOutputDeferred) {
|
||||
},
|
||||
}, i.second.output);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void LocalStore::linkDeriverToPath(const StorePath & deriver, const string & outputName, const StorePath & output)
|
||||
void LocalStore::registerDrvOutput(const Realisation & info)
|
||||
{
|
||||
auto state(_state.lock());
|
||||
return linkDeriverToPath(*state, queryValidPathId(*state, deriver), outputName, output);
|
||||
retrySQLite<void>([&]() {
|
||||
state->stmts->RegisterRealisedOutput.use()
|
||||
(info.id.strHash())
|
||||
(info.id.outputName)
|
||||
(printStorePath(info.outPath))
|
||||
.exec();
|
||||
});
|
||||
}
|
||||
|
||||
void LocalStore::linkDeriverToPath(State & state, uint64_t deriver, const string & outputName, const StorePath & output)
|
||||
void LocalStore::cacheDrvOutputMapping(State & state, const uint64_t deriver, const string & outputName, const StorePath & output)
|
||||
{
|
||||
retrySQLite<void>([&]() {
|
||||
state.stmtAddDerivationOutput.use()
|
||||
state.stmts->AddDerivationOutput.use()
|
||||
(deriver)
|
||||
(outputName)
|
||||
(printStorePath(output))
|
||||
|
|
@ -604,7 +685,7 @@ uint64_t LocalStore::addValidPath(State & state,
|
|||
throw Error("cannot add path '%s' to the Nix store because it claims to be content-addressed but isn't",
|
||||
printStorePath(info.path));
|
||||
|
||||
state.stmtRegisterValidPath.use()
|
||||
state.stmts->RegisterValidPath.use()
|
||||
(printStorePath(info.path))
|
||||
(info.narHash.to_string(Base16, true))
|
||||
(info.registrationTime == 0 ? time(0) : info.registrationTime)
|
||||
|
|
@ -621,7 +702,7 @@ uint64_t LocalStore::addValidPath(State & state,
|
|||
efficiently query whether a path is an output of some
|
||||
derivation. */
|
||||
if (info.path.isDerivation()) {
|
||||
auto drv = readDerivation(info.path);
|
||||
auto drv = readInvalidDerivation(info.path);
|
||||
|
||||
/* Verify that the output paths in the derivation are correct
|
||||
(i.e., follow the scheme for computing output paths from
|
||||
|
|
@ -634,7 +715,7 @@ uint64_t LocalStore::addValidPath(State & state,
|
|||
/* Floating CA derivations have indeterminate output paths until
|
||||
they are built, so don't register anything in that case */
|
||||
if (i.second.second)
|
||||
linkDeriverToPath(state, id, i.first, *i.second.second);
|
||||
cacheDrvOutputMapping(state, id, i.first, *i.second.second);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -652,63 +733,67 @@ void LocalStore::queryPathInfoUncached(const StorePath & path,
|
|||
Callback<std::shared_ptr<const ValidPathInfo>> callback) noexcept
|
||||
{
|
||||
try {
|
||||
callback(retrySQLite<std::shared_ptr<ValidPathInfo>>([&]() {
|
||||
callback(retrySQLite<std::shared_ptr<const ValidPathInfo>>([&]() {
|
||||
auto state(_state.lock());
|
||||
|
||||
/* Get the path info. */
|
||||
auto useQueryPathInfo(state->stmtQueryPathInfo.use()(printStorePath(path)));
|
||||
|
||||
if (!useQueryPathInfo.next())
|
||||
return std::shared_ptr<ValidPathInfo>();
|
||||
|
||||
auto id = useQueryPathInfo.getInt(0);
|
||||
|
||||
auto narHash = Hash::dummy;
|
||||
try {
|
||||
narHash = Hash::parseAnyPrefixed(useQueryPathInfo.getStr(1));
|
||||
} catch (BadHash & e) {
|
||||
throw Error("invalid-path entry for '%s': %s", printStorePath(path), e.what());
|
||||
}
|
||||
|
||||
auto info = std::make_shared<ValidPathInfo>(path, narHash);
|
||||
|
||||
info->id = id;
|
||||
|
||||
info->registrationTime = useQueryPathInfo.getInt(2);
|
||||
|
||||
auto s = (const char *) sqlite3_column_text(state->stmtQueryPathInfo, 3);
|
||||
if (s) info->deriver = parseStorePath(s);
|
||||
|
||||
/* Note that narSize = NULL yields 0. */
|
||||
info->narSize = useQueryPathInfo.getInt(4);
|
||||
|
||||
info->ultimate = useQueryPathInfo.getInt(5) == 1;
|
||||
|
||||
s = (const char *) sqlite3_column_text(state->stmtQueryPathInfo, 6);
|
||||
if (s) info->sigs = tokenizeString<StringSet>(s, " ");
|
||||
|
||||
s = (const char *) sqlite3_column_text(state->stmtQueryPathInfo, 7);
|
||||
if (s) info->ca = parseContentAddressOpt(s);
|
||||
|
||||
/* Get the references. */
|
||||
auto useQueryReferences(state->stmtQueryReferences.use()(info->id));
|
||||
|
||||
while (useQueryReferences.next()) {
|
||||
info->insertReferencePossiblyToSelf(
|
||||
parseStorePath(useQueryReferences.getStr(0)));
|
||||
}
|
||||
|
||||
return info;
|
||||
return queryPathInfoInternal(*state, path);
|
||||
}));
|
||||
|
||||
} catch (...) { callback.rethrow(); }
|
||||
}
|
||||
|
||||
|
||||
std::shared_ptr<const ValidPathInfo> LocalStore::queryPathInfoInternal(State & state, const StorePath & path)
|
||||
{
|
||||
/* Get the path info. */
|
||||
auto useQueryPathInfo(state.stmts->QueryPathInfo.use()(printStorePath(path)));
|
||||
|
||||
if (!useQueryPathInfo.next())
|
||||
return std::shared_ptr<ValidPathInfo>();
|
||||
|
||||
auto id = useQueryPathInfo.getInt(0);
|
||||
|
||||
auto narHash = Hash::dummy;
|
||||
try {
|
||||
narHash = Hash::parseAnyPrefixed(useQueryPathInfo.getStr(1));
|
||||
} catch (BadHash & e) {
|
||||
throw Error("invalid-path entry for '%s': %s", printStorePath(path), e.what());
|
||||
}
|
||||
|
||||
auto info = std::make_shared<ValidPathInfo>(path, narHash);
|
||||
|
||||
info->id = id;
|
||||
|
||||
info->registrationTime = useQueryPathInfo.getInt(2);
|
||||
|
||||
auto s = (const char *) sqlite3_column_text(state.stmts->QueryPathInfo, 3);
|
||||
if (s) info->deriver = parseStorePath(s);
|
||||
|
||||
/* Note that narSize = NULL yields 0. */
|
||||
info->narSize = useQueryPathInfo.getInt(4);
|
||||
|
||||
info->ultimate = useQueryPathInfo.getInt(5) == 1;
|
||||
|
||||
s = (const char *) sqlite3_column_text(state.stmts->QueryPathInfo, 6);
|
||||
if (s) info->sigs = tokenizeString<StringSet>(s, " ");
|
||||
|
||||
s = (const char *) sqlite3_column_text(state.stmts->QueryPathInfo, 7);
|
||||
if (s) info->ca = parseContentAddressOpt(s);
|
||||
|
||||
/* Get the references. */
|
||||
auto useQueryReferences(state.stmts->QueryReferences.use()(info->id));
|
||||
|
||||
while (useQueryReferences.next())
|
||||
info->insertReferencePossiblyToSelf(
|
||||
parseStorePath(useQueryReferences.getStr(0)));
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
|
||||
/* Update path info in the database. */
|
||||
void LocalStore::updatePathInfo(State & state, const ValidPathInfo & info)
|
||||
{
|
||||
state.stmtUpdatePathInfo.use()
|
||||
state.stmts->UpdatePathInfo.use()
|
||||
(info.narSize, info.narSize != 0)
|
||||
(info.narHash.to_string(Base16, true))
|
||||
(info.ultimate ? 1 : 0, info.ultimate)
|
||||
|
|
@ -721,7 +806,7 @@ void LocalStore::updatePathInfo(State & state, const ValidPathInfo & info)
|
|||
|
||||
uint64_t LocalStore::queryValidPathId(State & state, const StorePath & path)
|
||||
{
|
||||
auto use(state.stmtQueryPathInfo.use()(printStorePath(path)));
|
||||
auto use(state.stmts->QueryPathInfo.use()(printStorePath(path)));
|
||||
if (!use.next())
|
||||
throw InvalidPath("path '%s' is not valid", printStorePath(path));
|
||||
return use.getInt(0);
|
||||
|
|
@ -730,7 +815,7 @@ uint64_t LocalStore::queryValidPathId(State & state, const StorePath & path)
|
|||
|
||||
bool LocalStore::isValidPath_(State & state, const StorePath & path)
|
||||
{
|
||||
return state.stmtQueryPathInfo.use()(printStorePath(path)).next();
|
||||
return state.stmts->QueryPathInfo.use()(printStorePath(path)).next();
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -756,7 +841,7 @@ StorePathSet LocalStore::queryAllValidPaths()
|
|||
{
|
||||
return retrySQLite<StorePathSet>([&]() {
|
||||
auto state(_state.lock());
|
||||
auto use(state->stmtQueryValidPaths.use());
|
||||
auto use(state->stmts->QueryValidPaths.use());
|
||||
StorePathSet res;
|
||||
while (use.next()) res.insert(parseStorePath(use.getStr(0)));
|
||||
return res;
|
||||
|
|
@ -766,7 +851,7 @@ StorePathSet LocalStore::queryAllValidPaths()
|
|||
|
||||
void LocalStore::queryReferrers(State & state, const StorePath & path, StorePathSet & referrers)
|
||||
{
|
||||
auto useQueryReferrers(state.stmtQueryReferrers.use()(printStorePath(path)));
|
||||
auto useQueryReferrers(state.stmts->QueryReferrers.use()(printStorePath(path)));
|
||||
|
||||
while (useQueryReferrers.next())
|
||||
referrers.insert(parseStorePath(useQueryReferrers.getStr(0)));
|
||||
|
|
@ -787,7 +872,7 @@ StorePathSet LocalStore::queryValidDerivers(const StorePath & path)
|
|||
return retrySQLite<StorePathSet>([&]() {
|
||||
auto state(_state.lock());
|
||||
|
||||
auto useQueryValidDerivers(state->stmtQueryValidDerivers.use()(printStorePath(path)));
|
||||
auto useQueryValidDerivers(state->stmts->QueryValidDerivers.use()(printStorePath(path)));
|
||||
|
||||
StorePathSet derivers;
|
||||
while (useQueryValidDerivers.next())
|
||||
|
|
@ -798,69 +883,38 @@ StorePathSet LocalStore::queryValidDerivers(const StorePath & path)
|
|||
}
|
||||
|
||||
|
||||
std::map<std::string, std::optional<StorePath>> LocalStore::queryPartialDerivationOutputMap(const StorePath & path_)
|
||||
std::map<std::string, std::optional<StorePath>>
|
||||
LocalStore::queryPartialDerivationOutputMap(const StorePath & path_)
|
||||
{
|
||||
auto path = path_;
|
||||
std::map<std::string, std::optional<StorePath>> outputs;
|
||||
Derivation drv = readDerivation(path);
|
||||
for (auto & [outName, _] : drv.outputs) {
|
||||
outputs.insert_or_assign(outName, std::nullopt);
|
||||
}
|
||||
bool haveCached = false;
|
||||
{
|
||||
auto resolutions = drvPathResolutions.lock();
|
||||
auto resolvedPathOptIter = resolutions->find(path);
|
||||
if (resolvedPathOptIter != resolutions->end()) {
|
||||
auto & [_, resolvedPathOpt] = *resolvedPathOptIter;
|
||||
if (resolvedPathOpt)
|
||||
path = *resolvedPathOpt;
|
||||
haveCached = true;
|
||||
}
|
||||
}
|
||||
/* can't just use else-if instead of `!haveCached` because we need to unlock
|
||||
`drvPathResolutions` before it is locked in `Derivation::resolve`. */
|
||||
if (!haveCached && drv.type() == DerivationType::CAFloating) {
|
||||
/* Try resolve drv and use that path instead. */
|
||||
auto attempt = drv.tryResolve(*this);
|
||||
if (!attempt)
|
||||
/* If we cannot resolve the derivation, we cannot have any path
|
||||
assigned so we return the map of all std::nullopts. */
|
||||
return outputs;
|
||||
/* Just compute store path */
|
||||
auto pathResolved = writeDerivation(*this, *std::move(attempt), NoRepair, true);
|
||||
/* Store in memo table. */
|
||||
/* FIXME: memo logic should not be local-store specific, should have
|
||||
wrapper-method instead. */
|
||||
drvPathResolutions.lock()->insert_or_assign(path, pathResolved);
|
||||
path = std::move(pathResolved);
|
||||
}
|
||||
return retrySQLite<std::map<std::string, std::optional<StorePath>>>([&]() {
|
||||
auto outputs = retrySQLite<std::map<std::string, std::optional<StorePath>>>([&]() {
|
||||
auto state(_state.lock());
|
||||
|
||||
std::map<std::string, std::optional<StorePath>> outputs;
|
||||
uint64_t drvId;
|
||||
try {
|
||||
drvId = queryValidPathId(*state, path);
|
||||
} catch (InvalidPath &) {
|
||||
/* FIXME? if the derivation doesn't exist, we cannot have a mapping
|
||||
for it. */
|
||||
return outputs;
|
||||
}
|
||||
|
||||
auto useQueryDerivationOutputs {
|
||||
state->stmtQueryDerivationOutputs.use()
|
||||
(drvId)
|
||||
};
|
||||
|
||||
while (useQueryDerivationOutputs.next())
|
||||
drvId = queryValidPathId(*state, path);
|
||||
auto use(state->stmts->QueryDerivationOutputs.use()(drvId));
|
||||
while (use.next())
|
||||
outputs.insert_or_assign(
|
||||
useQueryDerivationOutputs.getStr(0),
|
||||
parseStorePath(useQueryDerivationOutputs.getStr(1))
|
||||
);
|
||||
use.getStr(0), parseStorePath(use.getStr(1)));
|
||||
|
||||
return outputs;
|
||||
});
|
||||
}
|
||||
|
||||
if (!settings.isExperimentalFeatureEnabled("ca-derivations"))
|
||||
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)
|
||||
{
|
||||
|
|
@ -871,11 +925,11 @@ std::optional<StorePath> LocalStore::queryPathFromHashPart(const std::string & h
|
|||
return retrySQLite<std::optional<StorePath>>([&]() -> std::optional<StorePath> {
|
||||
auto state(_state.lock());
|
||||
|
||||
auto useQueryPathFromHashPart(state->stmtQueryPathFromHashPart.use()(prefix));
|
||||
auto useQueryPathFromHashPart(state->stmts->QueryPathFromHashPart.use()(prefix));
|
||||
|
||||
if (!useQueryPathFromHashPart.next()) return {};
|
||||
|
||||
const char * s = (const char *) sqlite3_column_text(state->stmtQueryPathFromHashPart, 0);
|
||||
const char * s = (const char *) sqlite3_column_text(state->stmts->QueryPathFromHashPart, 0);
|
||||
if (s && prefix.compare(0, prefix.size(), s, prefix.size()) == 0)
|
||||
return parseStorePath(s);
|
||||
return {};
|
||||
|
|
@ -964,9 +1018,7 @@ void LocalStore::querySubstitutablePathInfos(const StorePathCAMap & paths, Subst
|
|||
|
||||
void LocalStore::registerValidPath(const ValidPathInfo & info)
|
||||
{
|
||||
ValidPathInfos infos;
|
||||
infos.push_back(info);
|
||||
registerValidPaths(infos);
|
||||
registerValidPaths({{info.path, info}});
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -984,7 +1036,7 @@ void LocalStore::registerValidPaths(const ValidPathInfos & infos)
|
|||
SQLiteTxn txn(state->db);
|
||||
StorePathSet paths;
|
||||
|
||||
for (auto & i : infos) {
|
||||
for (auto & [_, i] : infos) {
|
||||
assert(i.narHash.type == htSHA256);
|
||||
if (isValidPath_(*state, i.path))
|
||||
updatePathInfo(*state, i);
|
||||
|
|
@ -993,26 +1045,37 @@ void LocalStore::registerValidPaths(const ValidPathInfos & infos)
|
|||
paths.insert(i.path);
|
||||
}
|
||||
|
||||
for (auto & i : infos) {
|
||||
for (auto & [_, i] : infos) {
|
||||
auto referrer = queryValidPathId(*state, i.path);
|
||||
for (auto & j : i.referencesPossiblyToSelf())
|
||||
state->stmtAddReference.use()(referrer)(queryValidPathId(*state, j)).exec();
|
||||
state->stmts->AddReference.use()(referrer)(queryValidPathId(*state, j)).exec();
|
||||
}
|
||||
|
||||
/* Check that the derivation outputs are correct. We can't do
|
||||
this in addValidPath() above, because the references might
|
||||
not be valid yet. */
|
||||
for (auto & i : infos)
|
||||
for (auto & [_, i] : infos)
|
||||
if (i.path.isDerivation()) {
|
||||
// FIXME: inefficient; we already loaded the derivation in addValidPath().
|
||||
checkDerivationOutputs(i.path, readDerivation(i.path));
|
||||
checkDerivationOutputs(i.path,
|
||||
readInvalidDerivation(i.path));
|
||||
}
|
||||
|
||||
/* Do a topological sort of the paths. This will throw an
|
||||
error if a cycle is detected and roll back the
|
||||
transaction. Cycles can only occur when a derivation
|
||||
has multiple outputs. */
|
||||
topoSortPaths(paths);
|
||||
topoSort(paths,
|
||||
{[&](const StorePath & path) {
|
||||
auto i = infos.find(path);
|
||||
return i == infos.end() ? StorePathSet() : i->second.references;
|
||||
}},
|
||||
{[&](const StorePath & path, const StorePath & parent) {
|
||||
return BuildError(
|
||||
"cycle detected in the references of '%s' from '%s'",
|
||||
printStorePath(path),
|
||||
printStorePath(parent));
|
||||
}});
|
||||
|
||||
txn.commit();
|
||||
});
|
||||
|
|
@ -1025,7 +1088,7 @@ void LocalStore::invalidatePath(State & state, const StorePath & path)
|
|||
{
|
||||
debug("invalidating path '%s'", printStorePath(path));
|
||||
|
||||
state.stmtInvalidatePath.use()(printStorePath(path)).exec();
|
||||
state.stmts->InvalidatePath.use()(printStorePath(path)).exec();
|
||||
|
||||
/* Note that the foreign key constraints on the Refs table take
|
||||
care of deleting the references entries for `path'. */
|
||||
|
|
@ -1036,7 +1099,6 @@ void LocalStore::invalidatePath(State & state, const StorePath & path)
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
const PublicKeys & LocalStore::getPublicKeys()
|
||||
{
|
||||
auto state(_state.lock());
|
||||
|
|
@ -1045,11 +1107,15 @@ const PublicKeys & LocalStore::getPublicKeys()
|
|||
return *state->publicKeys;
|
||||
}
|
||||
|
||||
bool LocalStore::pathInfoIsTrusted(const ValidPathInfo & info)
|
||||
{
|
||||
return requireSigs && !info.checkSignatures(*this, getPublicKeys());
|
||||
}
|
||||
|
||||
void LocalStore::addToStore(const ValidPathInfo & info, Source & source,
|
||||
RepairFlag repair, CheckSigsFlag checkSigs)
|
||||
{
|
||||
if (requireSigs && checkSigs && !info.checkSignatures(*this, getPublicKeys()))
|
||||
if (checkSigs && pathInfoIsTrusted(info))
|
||||
throw Error("cannot add path '%s' because it lacks a valid signature", printStorePath(info.path));
|
||||
|
||||
addTempRoot(info.path);
|
||||
|
|
@ -1089,11 +1155,11 @@ void LocalStore::addToStore(const ValidPathInfo & info, Source & source,
|
|||
auto hashResult = hashSink->finish();
|
||||
|
||||
if (hashResult.first != info.narHash)
|
||||
throw Error("hash mismatch importing path '%s';\n wanted: %s\n got: %s",
|
||||
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));
|
||||
|
||||
if (hashResult.second != info.narSize)
|
||||
throw Error("size mismatch importing path '%s';\n wanted: %s\n got: %s",
|
||||
throw Error("size mismatch importing path '%s';\n specified: %s\n got: %s",
|
||||
printStorePath(info.path), info.narSize, hashResult.second);
|
||||
|
||||
autoGC();
|
||||
|
|
@ -1137,7 +1203,7 @@ StorePath LocalStore::addToStoreFromDump(Source & source0, const string & name,
|
|||
dump.resize(oldSize + want);
|
||||
auto got = 0;
|
||||
try {
|
||||
got = source.read((uint8_t *) dump.data() + oldSize, want);
|
||||
got = source.read(dump.data() + oldSize, want);
|
||||
} catch (EndOfFile &) {
|
||||
inMemory = true;
|
||||
break;
|
||||
|
|
@ -1353,12 +1419,8 @@ bool LocalStore::verifyStore(bool checkContents, RepairFlag repair)
|
|||
Path linkPath = linksDir + "/" + link.name;
|
||||
string hash = hashPath(htSHA256, linkPath).first.to_string(Base32, false);
|
||||
if (hash != link.name) {
|
||||
logError({
|
||||
.name = "Invalid hash",
|
||||
.hint = hintfmt(
|
||||
"link '%s' was modified! expected hash '%s', got '%s'",
|
||||
linkPath, link.name, hash)
|
||||
});
|
||||
printError("link '%s' was modified! expected hash '%s', got '%s'",
|
||||
linkPath, link.name, hash);
|
||||
if (repair) {
|
||||
if (unlink(linkPath.c_str()) == 0)
|
||||
printInfo("removed link '%s'", linkPath);
|
||||
|
|
@ -1391,11 +1453,8 @@ bool LocalStore::verifyStore(bool checkContents, RepairFlag repair)
|
|||
auto current = hashSink->finish();
|
||||
|
||||
if (info->narHash != nullHash && info->narHash != current.first) {
|
||||
logError({
|
||||
.name = "Invalid hash - path modified",
|
||||
.hint = hintfmt("path '%s' was modified! expected hash '%s', got '%s'",
|
||||
printStorePath(i), info->narHash.to_string(Base32, true), current.first.to_string(Base32, true))
|
||||
});
|
||||
printError("path '%s' was modified! expected hash '%s', got '%s'",
|
||||
printStorePath(i), info->narHash.to_string(Base32, true), current.first.to_string(Base32, true));
|
||||
if (repair) repairPath(i); else errors = true;
|
||||
} else {
|
||||
|
||||
|
|
@ -1446,10 +1505,7 @@ void LocalStore::verifyPath(const Path & pathS, const StringSet & store,
|
|||
if (!done.insert(pathS).second) return;
|
||||
|
||||
if (!isStorePath(pathS)) {
|
||||
logError({
|
||||
.name = "Nix path not found",
|
||||
.hint = hintfmt("path '%s' is not in the Nix store", pathS)
|
||||
});
|
||||
printError("path '%s' is not in the Nix store", pathS);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1472,10 +1528,7 @@ void LocalStore::verifyPath(const Path & pathS, const StringSet & store,
|
|||
auto state(_state.lock());
|
||||
invalidatePath(*state, path);
|
||||
} else {
|
||||
logError({
|
||||
.name = "Missing path with referrers",
|
||||
.hint = hintfmt("path '%s' disappeared, but it still has valid referrers!", pathS)
|
||||
});
|
||||
printError("path '%s' disappeared, but it still has valid referrers!", pathS);
|
||||
if (repair)
|
||||
try {
|
||||
repairPath(path);
|
||||
|
|
@ -1565,7 +1618,7 @@ void LocalStore::addSignatures(const StorePath & storePath, const StringSet & si
|
|||
|
||||
SQLiteTxn txn(state->db);
|
||||
|
||||
auto info = std::const_pointer_cast<ValidPathInfo>(std::shared_ptr<const ValidPathInfo>(queryPathInfo(storePath)));
|
||||
auto info = std::const_pointer_cast<ValidPathInfo>(queryPathInfoInternal(*state, storePath));
|
||||
|
||||
info->sigs.insert(sigs.begin(), sigs.end());
|
||||
|
||||
|
|
@ -1603,5 +1656,18 @@ void LocalStore::createUser(const std::string & userName, uid_t userId)
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
std::optional<const Realisation> LocalStore::queryRealisation(
|
||||
const DrvOutput& id) {
|
||||
typedef std::optional<const Realisation> Ret;
|
||||
return retrySQLite<Ret>([&]() -> Ret {
|
||||
auto state(_state.lock());
|
||||
auto use(state->stmts->QueryRealisedOutput.use()(id.strHash())(
|
||||
id.outputName));
|
||||
if (!use.next())
|
||||
return std::nullopt;
|
||||
auto outputPath = parseStorePath(use.getStr(0));
|
||||
return Ret{
|
||||
Realisation{.id = id, .outPath = outputPath}};
|
||||
});
|
||||
}
|
||||
} // namespace nix
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ struct LocalStoreConfig : virtual LocalFSStoreConfig
|
|||
};
|
||||
|
||||
|
||||
class LocalStore : public LocalFSStore, public virtual LocalStoreConfig
|
||||
class LocalStore : public virtual LocalStoreConfig, public virtual LocalFSStore
|
||||
{
|
||||
private:
|
||||
|
||||
|
|
@ -55,19 +55,8 @@ private:
|
|||
/* The SQLite database object. */
|
||||
SQLite db;
|
||||
|
||||
/* Some precompiled SQLite statements. */
|
||||
SQLiteStmt stmtRegisterValidPath;
|
||||
SQLiteStmt stmtUpdatePathInfo;
|
||||
SQLiteStmt stmtAddReference;
|
||||
SQLiteStmt stmtQueryPathInfo;
|
||||
SQLiteStmt stmtQueryReferences;
|
||||
SQLiteStmt stmtQueryReferrers;
|
||||
SQLiteStmt stmtInvalidatePath;
|
||||
SQLiteStmt stmtAddDerivationOutput;
|
||||
SQLiteStmt stmtQueryValidDerivers;
|
||||
SQLiteStmt stmtQueryDerivationOutputs;
|
||||
SQLiteStmt stmtQueryPathFromHashPart;
|
||||
SQLiteStmt stmtQueryValidPaths;
|
||||
struct Stmts;
|
||||
std::unique_ptr<Stmts> stmts;
|
||||
|
||||
/* The file to which we write our temporary roots. */
|
||||
AutoCloseFD fdTempRoots;
|
||||
|
|
@ -90,7 +79,7 @@ private:
|
|||
std::unique_ptr<PublicKeys> publicKeys;
|
||||
};
|
||||
|
||||
Sync<State, std::recursive_mutex> _state;
|
||||
Sync<State> _state;
|
||||
|
||||
public:
|
||||
|
||||
|
|
@ -147,6 +136,8 @@ public:
|
|||
void querySubstitutablePathInfos(const StorePathCAMap & paths,
|
||||
SubstitutablePathInfos & infos) override;
|
||||
|
||||
bool pathInfoIsTrusted(const ValidPathInfo &) override;
|
||||
|
||||
void addToStore(const ValidPathInfo & info, Source & source,
|
||||
RepairFlag repair, CheckSigsFlag checkSigs) override;
|
||||
|
||||
|
|
@ -156,15 +147,6 @@ public:
|
|||
StorePath addTextToStore(const string & name, const string & s,
|
||||
const StorePathSet & references, RepairFlag repair) override;
|
||||
|
||||
void buildPaths(
|
||||
const std::vector<StorePathWithOutputs> & paths,
|
||||
BuildMode buildMode) override;
|
||||
|
||||
BuildResult buildDerivation(const StorePath & drvPath, const BasicDerivation & drv,
|
||||
BuildMode buildMode) override;
|
||||
|
||||
void ensurePath(const StorePath & path) override;
|
||||
|
||||
void addTempRoot(const StorePath & path) override;
|
||||
|
||||
void addIndirectRoot(const Path & path) override;
|
||||
|
|
@ -209,9 +191,7 @@ public:
|
|||
|
||||
void vacuumDB();
|
||||
|
||||
/* Repair the contents of the given path by redownloading it using
|
||||
a substituter (if available). */
|
||||
void repairPath(const StorePath & path);
|
||||
void repairPath(const StorePath & path) override;
|
||||
|
||||
void addSignatures(const StorePath & storePath, const StringSet & sigs) override;
|
||||
|
||||
|
|
@ -219,6 +199,13 @@ public:
|
|||
garbage until it exceeds maxFree. */
|
||||
void autoGC(bool sync = true);
|
||||
|
||||
/* Register the store path 'output' as the output named 'outputName' of
|
||||
derivation 'deriver'. */
|
||||
void registerDrvOutput(const Realisation & info) override;
|
||||
void cacheDrvOutputMapping(State & state, const uint64_t deriver, const string & outputName, const StorePath & output);
|
||||
|
||||
std::optional<const Realisation> queryRealisation(const DrvOutput&) override;
|
||||
|
||||
private:
|
||||
|
||||
int getSchema();
|
||||
|
|
@ -239,6 +226,8 @@ private:
|
|||
void verifyPath(const Path & path, const StringSet & store,
|
||||
PathSet & done, StorePathSet & validPaths, RepairFlag repair, bool & errors);
|
||||
|
||||
std::shared_ptr<const ValidPathInfo> queryPathInfoInternal(State & state, const StorePath & path);
|
||||
|
||||
void updatePathInfo(State & state, const ValidPathInfo & info);
|
||||
|
||||
void upgradeStore6();
|
||||
|
|
@ -287,17 +276,12 @@ private:
|
|||
specified by the ‘secret-key-files’ option. */
|
||||
void signPathInfo(ValidPathInfo & info);
|
||||
|
||||
/* Register the store path 'output' as the output named 'outputName' of
|
||||
derivation 'deriver'. */
|
||||
void linkDeriverToPath(const StorePath & deriver, const string & outputName, const StorePath & output);
|
||||
void linkDeriverToPath(State & state, uint64_t deriver, const string & outputName, const StorePath & output);
|
||||
|
||||
Path getRealStoreDir() override { return realStoreDir; }
|
||||
|
||||
void createUser(const std::string & userName, uid_t userId) override;
|
||||
|
||||
friend class DerivationGoal;
|
||||
friend class SubstitutionGoal;
|
||||
friend struct DerivationGoal;
|
||||
friend struct SubstitutionGoal;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ ifneq ($(sandbox_shell),)
|
|||
libstore_CXXFLAGS += -DSANDBOX_SHELL="\"$(sandbox_shell)\""
|
||||
endif
|
||||
|
||||
$(d)/local-store.cc: $(d)/schema.sql.gen.hh
|
||||
$(d)/local-store.cc: $(d)/schema.sql.gen.hh $(d)/ca-specific-schema.sql.gen.hh
|
||||
|
||||
$(d)/build.cc:
|
||||
|
||||
|
|
@ -58,7 +58,7 @@ $(d)/build.cc:
|
|||
@echo ')foo"' >> $@.tmp
|
||||
@mv $@.tmp $@
|
||||
|
||||
clean-files += $(d)/schema.sql.gen.hh
|
||||
clean-files += $(d)/schema.sql.gen.hh $(d)/ca-specific-schema.sql.gen.hh
|
||||
|
||||
$(eval $(call install-file-in, $(d)/nix-store.pc, $(prefix)/lib/pkgconfig, 0644))
|
||||
|
||||
|
|
|
|||
|
|
@ -80,16 +80,16 @@ string nextComponent(string::const_iterator & p,
|
|||
|
||||
static bool componentsLT(const string & c1, const string & c2)
|
||||
{
|
||||
int n1, n2;
|
||||
bool c1Num = string2Int(c1, n1), c2Num = string2Int(c2, n2);
|
||||
auto n1 = string2Int<int>(c1);
|
||||
auto n2 = string2Int<int>(c2);
|
||||
|
||||
if (c1Num && c2Num) return n1 < n2;
|
||||
else if (c1 == "" && c2Num) return true;
|
||||
if (n1 && n2) return *n1 < *n2;
|
||||
else if (c1 == "" && n2) return true;
|
||||
else if (c1 == "pre" && c2 != "pre") return true;
|
||||
else if (c2 == "pre") return false;
|
||||
/* Assume that `2.3a' < `2.3.1'. */
|
||||
else if (c2Num) return true;
|
||||
else if (c1Num) return false;
|
||||
else if (n2) return true;
|
||||
else if (n1) return false;
|
||||
else return c1 < c2;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ struct NarAccessor : public FSAccessor
|
|||
parents.top()->start = pos;
|
||||
}
|
||||
|
||||
void receiveContents(unsigned char * data, size_t len) override
|
||||
void receiveContents(std::string_view data) override
|
||||
{ }
|
||||
|
||||
void createSymlink(const Path & path, const string & target) override
|
||||
|
|
@ -96,7 +96,7 @@ struct NarAccessor : public FSAccessor
|
|||
NarMember{FSAccessor::Type::tSymlink, false, 0, 0, target});
|
||||
}
|
||||
|
||||
size_t read(unsigned char * data, size_t len) override
|
||||
size_t read(char * data, size_t len) override
|
||||
{
|
||||
auto n = source.read(data, len);
|
||||
pos += n;
|
||||
|
|
@ -203,7 +203,7 @@ struct NarAccessor : public FSAccessor
|
|||
return res;
|
||||
}
|
||||
|
||||
std::string readFile(const Path & path) override
|
||||
std::string readFile(const Path & path, bool requireValidPath = true) override
|
||||
{
|
||||
auto i = get(path);
|
||||
if (i.type != FSAccessor::Type::tRegular)
|
||||
|
|
|
|||
|
|
@ -109,8 +109,10 @@ public:
|
|||
SQLiteStmt(state->db,
|
||||
"delete from NARs where ((present = 0 and timestamp < ?) or (present = 1 and timestamp < ?))")
|
||||
.use()
|
||||
(now - settings.ttlNegativeNarInfoCache)
|
||||
(now - settings.ttlPositiveNarInfoCache)
|
||||
// Use a minimum TTL to prevent --refresh from
|
||||
// nuking the entire disk cache.
|
||||
(now - std::max(settings.ttlNegativeNarInfoCache.get(), 3600U))
|
||||
(now - std::max(settings.ttlPositiveNarInfoCache.get(), 30 * 24 * 3600U))
|
||||
.exec();
|
||||
|
||||
debug("deleted %d entries from the NAR info disk cache", sqlite3_changes(state->db));
|
||||
|
|
|
|||
|
|
@ -46,14 +46,18 @@ NarInfo::NarInfo(const Store & store, const std::string & s, const std::string &
|
|||
else if (name == "FileHash")
|
||||
fileHash = parseHashField(value);
|
||||
else if (name == "FileSize") {
|
||||
if (!string2Int(value, fileSize)) throw corrupt();
|
||||
auto n = string2Int<decltype(fileSize)>(value);
|
||||
if (!n) throw corrupt();
|
||||
fileSize = *n;
|
||||
}
|
||||
else if (name == "NarHash") {
|
||||
narHash = parseHashField(value);
|
||||
haveNarHash = true;
|
||||
}
|
||||
else if (name == "NarSize") {
|
||||
if (!string2Int(value, narSize)) throw corrupt();
|
||||
auto n = string2Int<decltype(narSize)>(value);
|
||||
if (!n) throw corrupt();
|
||||
narSize = *n;
|
||||
}
|
||||
else if (name == "References") {
|
||||
auto refs = tokenizeString<Strings>(value, " ");
|
||||
|
|
|
|||
|
|
@ -126,16 +126,13 @@ void LocalStore::optimisePath_(Activity * act, OptimiseStats & stats,
|
|||
NixOS (example: $fontconfig/var/cache being modified). Skip
|
||||
those files. FIXME: check the modification time. */
|
||||
if (S_ISREG(st.st_mode) && (st.st_mode & S_IWUSR)) {
|
||||
logWarning({
|
||||
.name = "Suspicious file",
|
||||
.hint = hintfmt("skipping suspicious writable file '%1%'", path)
|
||||
});
|
||||
warn("skipping suspicious writable file '%1%'", path);
|
||||
return;
|
||||
}
|
||||
|
||||
/* This can still happen on top-level files. */
|
||||
if (st.st_nlink > 1 && inodeHash.count(st.st_ino)) {
|
||||
debug(format("'%1%' is already linked, with %2% other file(s)") % path % (st.st_nlink - 2));
|
||||
debug("'%s' is already linked, with %d other file(s)", path, st.st_nlink - 2);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -191,10 +188,7 @@ void LocalStore::optimisePath_(Activity * act, OptimiseStats & stats,
|
|||
}
|
||||
|
||||
if (st.st_size != stLink.st_size) {
|
||||
logWarning({
|
||||
.name = "Corrupted link",
|
||||
.hint = hintfmt("removing corrupted link '%1%'", linkPath)
|
||||
});
|
||||
warn("removing corrupted link '%s'", linkPath);
|
||||
unlink(linkPath.c_str());
|
||||
goto retry;
|
||||
}
|
||||
|
|
@ -229,10 +223,7 @@ void LocalStore::optimisePath_(Activity * act, OptimiseStats & stats,
|
|||
/* Atomically replace the old file with the new hard link. */
|
||||
if (rename(tempLink.c_str(), path.c_str()) == -1) {
|
||||
if (unlink(tempLink.c_str()) == -1)
|
||||
logError({
|
||||
.name = "Unlink error",
|
||||
.hint = hintfmt("unable to unlink '%1%'", tempLink)
|
||||
});
|
||||
printError("unable to unlink '%1%'", tempLink);
|
||||
if (errno == EMLINK) {
|
||||
/* Some filesystems generate too many links on the rename,
|
||||
rather than on the original link. (Probably it
|
||||
|
|
|
|||
|
|
@ -101,6 +101,10 @@ bool ParsedDerivation::canBuildLocally(Store & localStore) const
|
|||
&& !drv.isBuiltin())
|
||||
return false;
|
||||
|
||||
if (settings.maxBuildJobs.get() == 0
|
||||
&& !drv.isBuiltin())
|
||||
return false;
|
||||
|
||||
for (auto & feature : getRequiredSystemFeatures())
|
||||
if (!localStore.systemFeatures.get().count(feature)) return false;
|
||||
|
||||
|
|
|
|||
|
|
@ -109,6 +109,6 @@ struct ValidPathInfo : PathReferences<StorePath>
|
|||
virtual ~ValidPathInfo() { }
|
||||
};
|
||||
|
||||
typedef list<ValidPathInfo> ValidPathInfos;
|
||||
typedef std::map<StorePath, ValidPathInfo> ValidPathInfos;
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,9 +21,8 @@ static std::optional<GenerationNumber> parseName(const string & profileName, con
|
|||
string s = string(name, profileName.size() + 1);
|
||||
string::size_type p = s.find("-link");
|
||||
if (p == string::npos) return {};
|
||||
unsigned int n;
|
||||
if (string2Int(string(s, 0, p), n) && n >= 0)
|
||||
return n;
|
||||
if (auto n = string2Int<unsigned int>(s.substr(0, p)))
|
||||
return *n;
|
||||
else
|
||||
return {};
|
||||
}
|
||||
|
|
@ -214,12 +213,12 @@ void deleteGenerationsOlderThan(const Path & profile, const string & timeSpec, b
|
|||
{
|
||||
time_t curTime = time(0);
|
||||
string strDays = string(timeSpec, 0, timeSpec.size() - 1);
|
||||
int days;
|
||||
auto days = string2Int<int>(strDays);
|
||||
|
||||
if (!string2Int(strDays, days) || days < 1)
|
||||
if (!days || *days < 1)
|
||||
throw Error("invalid number of days specifier '%1%'", timeSpec);
|
||||
|
||||
time_t oldTime = curTime - days * 24 * 3600;
|
||||
time_t oldTime = curTime - *days * 24 * 3600;
|
||||
|
||||
deleteGenerationsOlderThan(profile, oldTime, dryRun);
|
||||
}
|
||||
|
|
|
|||
80
src/libstore/realisation.cc
Normal file
80
src/libstore/realisation.cc
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
#include "realisation.hh"
|
||||
#include "store-api.hh"
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace nix {
|
||||
|
||||
MakeError(InvalidDerivationOutputId, Error);
|
||||
|
||||
DrvOutput DrvOutput::parse(const std::string &strRep) {
|
||||
size_t n = strRep.find("!");
|
||||
if (n == strRep.npos)
|
||||
throw InvalidDerivationOutputId("Invalid derivation output id %s", strRep);
|
||||
|
||||
return DrvOutput{
|
||||
.drvHash = Hash::parseAnyPrefixed(strRep.substr(0, n)),
|
||||
.outputName = strRep.substr(n+1),
|
||||
};
|
||||
}
|
||||
|
||||
std::string DrvOutput::to_string() const {
|
||||
return strHash() + "!" + outputName;
|
||||
}
|
||||
|
||||
nlohmann::json Realisation::toJSON() const {
|
||||
return nlohmann::json{
|
||||
{"id", id.to_string()},
|
||||
{"outPath", outPath.to_string()},
|
||||
};
|
||||
}
|
||||
|
||||
Realisation Realisation::fromJSON(
|
||||
const nlohmann::json& json,
|
||||
const std::string& whence) {
|
||||
auto getField = [&](std::string fieldName) -> std::string {
|
||||
auto fieldIterator = json.find(fieldName);
|
||||
if (fieldIterator == json.end())
|
||||
throw Error(
|
||||
"Drv output info file '%1%' is corrupt, missing field %2%",
|
||||
whence, fieldName);
|
||||
return *fieldIterator;
|
||||
};
|
||||
|
||||
return Realisation{
|
||||
.id = DrvOutput::parse(getField("id")),
|
||||
.outPath = StorePath(getField("outPath")),
|
||||
};
|
||||
}
|
||||
|
||||
StorePath RealisedPath::path() const {
|
||||
return std::visit([](auto && arg) { return arg.getPath(); }, raw);
|
||||
}
|
||||
|
||||
void RealisedPath::closure(
|
||||
Store& store,
|
||||
const RealisedPath::Set& startPaths,
|
||||
RealisedPath::Set& ret)
|
||||
{
|
||||
// FIXME: This only builds the store-path closure, not the real realisation
|
||||
// closure
|
||||
StorePathSet initialStorePaths, pathsClosure;
|
||||
for (auto& path : startPaths)
|
||||
initialStorePaths.insert(path.path());
|
||||
store.computeFSClosure(initialStorePaths, pathsClosure);
|
||||
ret.insert(startPaths.begin(), startPaths.end());
|
||||
ret.insert(pathsClosure.begin(), pathsClosure.end());
|
||||
}
|
||||
|
||||
void RealisedPath::closure(Store& store, RealisedPath::Set & ret) const
|
||||
{
|
||||
RealisedPath::closure(store, {*this}, ret);
|
||||
}
|
||||
|
||||
RealisedPath::Set RealisedPath::closure(Store& store) const
|
||||
{
|
||||
RealisedPath::Set ret;
|
||||
closure(store, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace nix
|
||||
76
src/libstore/realisation.hh
Normal file
76
src/libstore/realisation.hh
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
#pragma once
|
||||
|
||||
#include <variant>
|
||||
|
||||
#include "hash.hh"
|
||||
#include "path.hh"
|
||||
#include <nlohmann/json_fwd.hpp>
|
||||
#include "comparator.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
struct DrvOutput {
|
||||
// The hash modulo of the derivation
|
||||
Hash drvHash;
|
||||
std::string outputName;
|
||||
|
||||
std::string to_string() const;
|
||||
|
||||
std::string strHash() const
|
||||
{ return drvHash.to_string(Base16, true); }
|
||||
|
||||
static DrvOutput parse(const std::string &);
|
||||
|
||||
GENERATE_CMP(DrvOutput, me->drvHash, me->outputName);
|
||||
};
|
||||
|
||||
struct Realisation {
|
||||
DrvOutput id;
|
||||
StorePath outPath;
|
||||
|
||||
nlohmann::json toJSON() const;
|
||||
static Realisation fromJSON(const nlohmann::json& json, const std::string& whence);
|
||||
|
||||
StorePath getPath() const { return outPath; }
|
||||
|
||||
GENERATE_CMP(Realisation, me->id, me->outPath);
|
||||
};
|
||||
|
||||
struct OpaquePath {
|
||||
StorePath path;
|
||||
|
||||
StorePath getPath() const { return path; }
|
||||
|
||||
GENERATE_CMP(OpaquePath, me->path);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A store path with all the history of how it went into the store
|
||||
*/
|
||||
struct RealisedPath {
|
||||
/*
|
||||
* A path is either the result of the realisation of a derivation or
|
||||
* an opaque blob that has been directly added to the store
|
||||
*/
|
||||
using Raw = std::variant<Realisation, OpaquePath>;
|
||||
Raw raw;
|
||||
|
||||
using Set = std::set<RealisedPath>;
|
||||
|
||||
RealisedPath(StorePath path) : raw(OpaquePath{path}) {}
|
||||
RealisedPath(Realisation r) : raw(r) {}
|
||||
|
||||
/**
|
||||
* Get the raw store path associated to this
|
||||
*/
|
||||
StorePath path() const;
|
||||
|
||||
void closure(Store& store, Set& ret) const;
|
||||
static void closure(Store& store, const Set& startPaths, Set& ret);
|
||||
Set closure(Store& store) const;
|
||||
|
||||
GENERATE_CMP(RealisedPath, me->raw);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -55,27 +55,23 @@ struct RefScanSink : Sink
|
|||
|
||||
RefScanSink() { }
|
||||
|
||||
void operator () (const unsigned char * data, size_t len);
|
||||
void operator () (std::string_view data) override
|
||||
{
|
||||
/* It's possible that a reference spans the previous and current
|
||||
fragment, so search in the concatenation of the tail of the
|
||||
previous fragment and the start of the current fragment. */
|
||||
string s = tail + std::string(data, 0, refLength);
|
||||
search((const unsigned char *) s.data(), s.size(), hashes, seen);
|
||||
|
||||
search((const unsigned char *) data.data(), data.size(), hashes, seen);
|
||||
|
||||
size_t tailLen = data.size() <= refLength ? data.size() : refLength;
|
||||
tail = std::string(tail, tail.size() < refLength - tailLen ? 0 : tail.size() - (refLength - tailLen));
|
||||
tail.append({data.data() + data.size() - tailLen, tailLen});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
void RefScanSink::operator () (const unsigned char * data, size_t len)
|
||||
{
|
||||
/* It's possible that a reference spans the previous and current
|
||||
fragment, so search in the concatenation of the tail of the
|
||||
previous fragment and the start of the current fragment. */
|
||||
string s = tail + string((const char *) data, len > refLength ? refLength : len);
|
||||
search((const unsigned char *) s.data(), s.size(), hashes, seen);
|
||||
|
||||
search(data, len, hashes, seen);
|
||||
|
||||
size_t tailLen = len <= refLength ? len : refLength;
|
||||
tail =
|
||||
string(tail, tail.size() < refLength - tailLen ? 0 : tail.size() - (refLength - tailLen)) +
|
||||
string((const char *) data + len - tailLen, tailLen);
|
||||
}
|
||||
|
||||
|
||||
std::pair<PathSet, HashResult> scanForReferences(const string & path,
|
||||
const PathSet & refs)
|
||||
{
|
||||
|
|
@ -92,9 +88,6 @@ PathSet scanForReferences(Sink & toTee,
|
|||
TeeSink sink { refsSink, toTee };
|
||||
std::map<string, Path> backMap;
|
||||
|
||||
/* For efficiency (and a higher hit rate), just search for the
|
||||
hash part of the file name. (This assumes that all references
|
||||
have the form `HASH-bla'). */
|
||||
for (auto & i : refs) {
|
||||
auto baseName = std::string(baseNameOf(i));
|
||||
string::size_type pos = baseName.find('-');
|
||||
|
|
@ -129,10 +122,10 @@ RewritingSink::RewritingSink(const std::string & from, const std::string & to, S
|
|||
assert(from.size() == to.size());
|
||||
}
|
||||
|
||||
void RewritingSink::operator () (const unsigned char * data, size_t len)
|
||||
void RewritingSink::operator () (std::string_view data)
|
||||
{
|
||||
std::string s(prev);
|
||||
s.append((const char *) data, len);
|
||||
s.append(data);
|
||||
|
||||
size_t j = 0;
|
||||
while ((j = s.find(from, j)) != string::npos) {
|
||||
|
|
@ -146,14 +139,14 @@ void RewritingSink::operator () (const unsigned char * data, size_t len)
|
|||
|
||||
pos += consumed;
|
||||
|
||||
if (consumed) nextSink((unsigned char *) s.data(), consumed);
|
||||
if (consumed) nextSink(s.substr(0, consumed));
|
||||
}
|
||||
|
||||
void RewritingSink::flush()
|
||||
{
|
||||
if (prev.empty()) return;
|
||||
pos += prev.size();
|
||||
nextSink((unsigned char *) prev.data(), prev.size());
|
||||
nextSink(prev);
|
||||
prev.clear();
|
||||
}
|
||||
|
||||
|
|
@ -163,9 +156,9 @@ HashModuloSink::HashModuloSink(HashType ht, const std::string & modulus)
|
|||
{
|
||||
}
|
||||
|
||||
void HashModuloSink::operator () (const unsigned char * data, size_t len)
|
||||
void HashModuloSink::operator () (std::string_view data)
|
||||
{
|
||||
rewritingSink(data, len);
|
||||
rewritingSink(data);
|
||||
}
|
||||
|
||||
HashResult HashModuloSink::finish()
|
||||
|
|
@ -176,10 +169,8 @@ HashResult HashModuloSink::finish()
|
|||
NAR with self-references and a NAR with some of the
|
||||
self-references already zeroed out do not produce a hash
|
||||
collision. FIXME: proof. */
|
||||
for (auto & pos : rewritingSink.matches) {
|
||||
auto s = fmt("|%d", pos);
|
||||
hashSink((unsigned char *) s.data(), s.size());
|
||||
}
|
||||
for (auto & pos : rewritingSink.matches)
|
||||
hashSink(fmt("|%d", pos));
|
||||
|
||||
auto h = hashSink.finish();
|
||||
return {h.first, rewritingSink.pos};
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ struct RewritingSink : Sink
|
|||
|
||||
RewritingSink(const std::string & from, const std::string & to, Sink & nextSink);
|
||||
|
||||
void operator () (const unsigned char * data, size_t len) override;
|
||||
void operator () (std::string_view data) override;
|
||||
|
||||
void flush();
|
||||
};
|
||||
|
|
@ -31,7 +31,7 @@ struct HashModuloSink : AbstractHashSink
|
|||
|
||||
HashModuloSink(HashType ht, const std::string & modulus);
|
||||
|
||||
void operator () (const unsigned char * data, size_t len) override;
|
||||
void operator () (std::string_view data) override;
|
||||
|
||||
HashResult finish() override;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -43,13 +43,13 @@ void RemoteFSAccessor::addToCache(std::string_view hashPart, const std::string &
|
|||
}
|
||||
}
|
||||
|
||||
std::pair<ref<FSAccessor>, Path> RemoteFSAccessor::fetch(const Path & path_)
|
||||
std::pair<ref<FSAccessor>, Path> RemoteFSAccessor::fetch(const Path & path_, bool requireValidPath)
|
||||
{
|
||||
auto path = canonPath(path_);
|
||||
|
||||
auto [storePath, restPath] = store->toStorePath(path);
|
||||
|
||||
if (!store->isValidPath(storePath))
|
||||
if (requireValidPath && !store->isValidPath(storePath))
|
||||
throw InvalidPath("path '%1%' is not a valid store path", store->printStorePath(storePath));
|
||||
|
||||
auto i = nars.find(std::string(storePath.hashPart()));
|
||||
|
|
@ -75,7 +75,7 @@ std::pair<ref<FSAccessor>, Path> RemoteFSAccessor::fetch(const Path & path_)
|
|||
throw SysError("seeking in '%s'", cacheFile);
|
||||
|
||||
std::string buf(length, 0);
|
||||
readFull(fd.get(), (unsigned char *) buf.data(), length);
|
||||
readFull(fd.get(), buf.data(), length);
|
||||
|
||||
return buf;
|
||||
});
|
||||
|
|
@ -113,9 +113,9 @@ StringSet RemoteFSAccessor::readDirectory(const Path & path)
|
|||
return res.first->readDirectory(res.second);
|
||||
}
|
||||
|
||||
std::string RemoteFSAccessor::readFile(const Path & path)
|
||||
std::string RemoteFSAccessor::readFile(const Path & path, bool requireValidPath)
|
||||
{
|
||||
auto res = fetch(path);
|
||||
auto res = fetch(path, requireValidPath);
|
||||
return res.first->readFile(res.second);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ class RemoteFSAccessor : public FSAccessor
|
|||
|
||||
Path cacheDir;
|
||||
|
||||
std::pair<ref<FSAccessor>, Path> fetch(const Path & path_);
|
||||
std::pair<ref<FSAccessor>, Path> fetch(const Path & path_, bool requireValidPath = true);
|
||||
|
||||
friend class BinaryCacheStore;
|
||||
|
||||
|
|
@ -32,7 +32,7 @@ public:
|
|||
|
||||
StringSet readDirectory(const Path & path) override;
|
||||
|
||||
std::string readFile(const Path & path) override;
|
||||
std::string readFile(const Path & path, bool requireValidPath = true) override;
|
||||
|
||||
std::string readLink(const Path & path) override;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
#include "finally.hh"
|
||||
#include "logging.hh"
|
||||
#include "callback.hh"
|
||||
#include "filetransfer.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
|
@ -76,8 +77,8 @@ void write(const Store & store, Sink & out, const std::optional<ContentAddress>
|
|||
|
||||
/* TODO: Separate these store impls into different files, give them better names */
|
||||
RemoteStore::RemoteStore(const Params & params)
|
||||
: Store(params)
|
||||
, RemoteStoreConfig(params)
|
||||
: RemoteStoreConfig(params)
|
||||
, Store(params)
|
||||
, connections(make_ref<Pool<Connection>>(
|
||||
std::max(1, (int) maxConnections),
|
||||
[this]() {
|
||||
|
|
@ -171,7 +172,8 @@ void RemoteStore::setOptions(Connection & conn)
|
|||
|
||||
if (GET_PROTOCOL_MINOR(conn.daemonVersion) >= 12) {
|
||||
std::map<std::string, Config::SettingInfo> overrides;
|
||||
globalConfig.getSettings(overrides, true);
|
||||
settings.getSettings(overrides, true); // libstore settings
|
||||
fileTransferSettings.getSettings(overrides, true);
|
||||
overrides.erase(settings.keepFailed.name);
|
||||
overrides.erase(settings.keepGoing.name);
|
||||
overrides.erase(settings.tryFallback.name);
|
||||
|
|
@ -257,6 +259,9 @@ StorePathSet RemoteStore::queryValidPaths(const StorePathSet & paths, Substitute
|
|||
} else {
|
||||
conn->to << wopQueryValidPaths;
|
||||
worker_proto::write(*this, conn->to, paths);
|
||||
if (GET_PROTOCOL_MINOR(conn->daemonVersion) >= 27) {
|
||||
conn->to << (settings.buildersUseSubstitutes ? 1 : 0);
|
||||
}
|
||||
conn.processStderr();
|
||||
return worker_proto::read(*this, conn->from, Phantom<StorePathSet> {});
|
||||
}
|
||||
|
|
@ -408,10 +413,10 @@ StorePathSet RemoteStore::queryValidDerivers(const StorePath & path)
|
|||
|
||||
StorePathSet RemoteStore::queryDerivationOutputs(const StorePath & path)
|
||||
{
|
||||
auto conn(getConnection());
|
||||
if (GET_PROTOCOL_MINOR(conn->daemonVersion) >= 0x16) {
|
||||
if (GET_PROTOCOL_MINOR(getProtocol()) >= 0x16) {
|
||||
return Store::queryDerivationOutputs(path);
|
||||
}
|
||||
auto conn(getConnection());
|
||||
conn->to << wopQueryDerivationOutputs << printStorePath(path);
|
||||
conn.processStderr();
|
||||
return worker_proto::read(*this, conn->from, Phantom<StorePathSet> {});
|
||||
|
|
@ -472,9 +477,14 @@ ref<const ValidPathInfo> RemoteStore::addCAToStore(
|
|||
worker_proto::write(*this, conn->to, references);
|
||||
conn->to << repair;
|
||||
|
||||
conn.withFramedSink([&](Sink & sink) {
|
||||
dump.drainInto(sink);
|
||||
});
|
||||
// The dump source may invoke the store, so we need to make some room.
|
||||
connections->incCapacity();
|
||||
{
|
||||
Finally cleanup([&]() { connections->decCapacity(); });
|
||||
conn.withFramedSink([&](Sink & sink) {
|
||||
dump.drainInto(sink);
|
||||
});
|
||||
}
|
||||
|
||||
auto path = parseStorePath(readString(conn->from));
|
||||
return readValidPathInfo(conn, path);
|
||||
|
|
@ -600,6 +610,27 @@ StorePath RemoteStore::addTextToStore(const string & name, const string & s,
|
|||
return addCAToStore(source, name, TextHashMethod{}, references, repair)->path;
|
||||
}
|
||||
|
||||
void RemoteStore::registerDrvOutput(const Realisation & info)
|
||||
{
|
||||
auto conn(getConnection());
|
||||
conn->to << wopRegisterDrvOutput;
|
||||
conn->to << info.id.to_string();
|
||||
conn->to << std::string(info.outPath.to_string());
|
||||
conn.processStderr();
|
||||
}
|
||||
|
||||
std::optional<const Realisation> RemoteStore::queryRealisation(const DrvOutput & id)
|
||||
{
|
||||
auto conn(getConnection());
|
||||
conn->to << wopQueryRealisation;
|
||||
conn->to << id.to_string();
|
||||
conn.processStderr();
|
||||
auto outPaths = worker_proto::read(*this, conn->from, Phantom<std::set<StorePath>>{});
|
||||
if (outPaths.empty())
|
||||
return std::nullopt;
|
||||
return {Realisation{.id = id, .outPath = *outPaths.begin()}};
|
||||
}
|
||||
|
||||
|
||||
void RemoteStore::buildPaths(const std::vector<StorePathWithOutputs> & drvPaths, BuildMode buildMode)
|
||||
{
|
||||
|
|
@ -847,8 +878,8 @@ std::exception_ptr RemoteStore::Connection::processStderr(Sink * sink, Source *
|
|||
else if (msg == STDERR_READ) {
|
||||
if (!source) throw Error("no source");
|
||||
size_t len = readNum<size_t>(from);
|
||||
auto buf = std::make_unique<unsigned char[]>(len);
|
||||
writeString(buf.get(), source->read(buf.get(), len), to);
|
||||
auto buf = std::make_unique<char[]>(len);
|
||||
writeString({(const char *) buf.get(), source->read(buf.get(), len)}, to);
|
||||
to.flush();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ struct RemoteStoreConfig : virtual StoreConfig
|
|||
|
||||
/* FIXME: RemoteStore is a misnomer - should be something like
|
||||
DaemonStore. */
|
||||
class RemoteStore : public virtual Store, public virtual RemoteStoreConfig
|
||||
class RemoteStore : public virtual RemoteStoreConfig, public virtual Store
|
||||
{
|
||||
public:
|
||||
|
||||
|
|
@ -81,6 +81,10 @@ public:
|
|||
StorePath addTextToStore(const string & name, const string & s,
|
||||
const StorePathSet & references, RepairFlag repair) override;
|
||||
|
||||
void registerDrvOutput(const Realisation & info) override;
|
||||
|
||||
std::optional<const Realisation> queryRealisation(const DrvOutput &) override;
|
||||
|
||||
void buildPaths(const std::vector<StorePathWithOutputs> & paths, BuildMode buildMode) override;
|
||||
|
||||
BuildResult buildDerivation(const StorePath & drvPath, const BasicDerivation & drv,
|
||||
|
|
|
|||
|
|
@ -57,6 +57,10 @@ class AwsLogger : public Aws::Utils::Logging::FormattedLogSystem
|
|||
{
|
||||
debug("AWS: %s", chomp(statement));
|
||||
}
|
||||
|
||||
#if !(AWS_VERSION_MAJOR <= 1 && AWS_VERSION_MINOR <= 7 && AWS_VERSION_PATCH <= 115)
|
||||
void Flush() override {}
|
||||
#endif
|
||||
};
|
||||
|
||||
static void initAWS()
|
||||
|
|
@ -162,7 +166,8 @@ S3Helper::FileTransferResult S3Helper::getObject(
|
|||
dynamic_cast<std::stringstream &>(result.GetBody()).str());
|
||||
|
||||
} catch (S3Error & e) {
|
||||
if (e.err != Aws::S3::S3Errors::NO_SUCH_KEY) throw;
|
||||
if ((e.err != Aws::S3::S3Errors::NO_SUCH_KEY) &&
|
||||
(e.err != Aws::S3::S3Errors::ACCESS_DENIED)) throw;
|
||||
}
|
||||
|
||||
auto now2 = std::chrono::steady_clock::now();
|
||||
|
|
@ -172,6 +177,11 @@ S3Helper::FileTransferResult S3Helper::getObject(
|
|||
return res;
|
||||
}
|
||||
|
||||
S3BinaryCacheStore::S3BinaryCacheStore(const Params & params)
|
||||
: BinaryCacheStoreConfig(params)
|
||||
, BinaryCacheStore(params)
|
||||
{ }
|
||||
|
||||
struct S3BinaryCacheStoreConfig : virtual BinaryCacheStoreConfig
|
||||
{
|
||||
using BinaryCacheStoreConfig::BinaryCacheStoreConfig;
|
||||
|
|
@ -190,7 +200,7 @@ struct S3BinaryCacheStoreConfig : virtual BinaryCacheStoreConfig
|
|||
const std::string name() override { return "S3 Binary Cache Store"; }
|
||||
};
|
||||
|
||||
struct S3BinaryCacheStoreImpl : public S3BinaryCacheStore, virtual S3BinaryCacheStoreConfig
|
||||
struct S3BinaryCacheStoreImpl : virtual S3BinaryCacheStoreConfig, public virtual S3BinaryCacheStore
|
||||
{
|
||||
std::string bucketName;
|
||||
|
||||
|
|
@ -203,6 +213,10 @@ struct S3BinaryCacheStoreImpl : public S3BinaryCacheStore, virtual S3BinaryCache
|
|||
const std::string & bucketName,
|
||||
const Params & params)
|
||||
: StoreConfig(params)
|
||||
, BinaryCacheStoreConfig(params)
|
||||
, S3BinaryCacheStoreConfig(params)
|
||||
, Store(params)
|
||||
, BinaryCacheStore(params)
|
||||
, S3BinaryCacheStore(params)
|
||||
, bucketName(bucketName)
|
||||
, s3Helper(profile, region, scheme, endpoint)
|
||||
|
|
@ -398,7 +412,7 @@ struct S3BinaryCacheStoreImpl : public S3BinaryCacheStore, virtual S3BinaryCache
|
|||
printTalkative("downloaded 's3://%s/%s' (%d bytes) in %d ms",
|
||||
bucketName, path, res.data->size(), res.durationMs);
|
||||
|
||||
sink((unsigned char *) res.data->data(), res.data->size());
|
||||
sink(*res.data);
|
||||
} else
|
||||
throw NoSuchBinaryCacheFile("file '%s' does not exist in binary cache '%s'", path, getUri());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,13 +6,11 @@
|
|||
|
||||
namespace nix {
|
||||
|
||||
class S3BinaryCacheStore : public BinaryCacheStore
|
||||
class S3BinaryCacheStore : public virtual BinaryCacheStore
|
||||
{
|
||||
protected:
|
||||
|
||||
S3BinaryCacheStore(const Params & params)
|
||||
: BinaryCacheStore(params)
|
||||
{ }
|
||||
S3BinaryCacheStore(const Params & params);
|
||||
|
||||
public:
|
||||
|
||||
|
|
|
|||
|
|
@ -147,14 +147,14 @@ void SQLiteStmt::Use::exec()
|
|||
int r = step();
|
||||
assert(r != SQLITE_ROW);
|
||||
if (r != SQLITE_DONE)
|
||||
throwSQLiteError(stmt.db, fmt("executing SQLite statement '%s'", stmt.sql));
|
||||
throwSQLiteError(stmt.db, fmt("executing SQLite statement '%s'", sqlite3_expanded_sql(stmt.stmt)));
|
||||
}
|
||||
|
||||
bool SQLiteStmt::Use::next()
|
||||
{
|
||||
int r = step();
|
||||
if (r != SQLITE_DONE && r != SQLITE_ROW)
|
||||
throwSQLiteError(stmt.db, fmt("executing SQLite query '%s'", stmt.sql));
|
||||
throwSQLiteError(stmt.db, fmt("executing SQLite query '%s'", sqlite3_expanded_sql(stmt.stmt)));
|
||||
return r == SQLITE_ROW;
|
||||
}
|
||||
|
||||
|
|
@ -211,7 +211,7 @@ void handleSQLiteBusy(const SQLiteBusy & e)
|
|||
lastWarned = now;
|
||||
logWarning({
|
||||
.name = "Sqlite busy",
|
||||
.hint = hintfmt(e.what())
|
||||
.msg = hintfmt(e.what())
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,12 +20,14 @@ struct SSHStoreConfig : virtual RemoteStoreConfig
|
|||
const std::string name() override { return "SSH Store"; }
|
||||
};
|
||||
|
||||
class SSHStore : public virtual RemoteStore, public virtual SSHStoreConfig
|
||||
class SSHStore : public virtual SSHStoreConfig, public virtual RemoteStore
|
||||
{
|
||||
public:
|
||||
|
||||
SSHStore(const std::string & scheme, const std::string & host, const Params & params)
|
||||
: StoreConfig(params)
|
||||
, RemoteStoreConfig(params)
|
||||
, SSHStoreConfig(params)
|
||||
, Store(params)
|
||||
, RemoteStore(params)
|
||||
, host(host)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@
|
|||
#include "archive.hh"
|
||||
#include "callback.hh"
|
||||
|
||||
#include <regex>
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
|
|
@ -382,12 +384,22 @@ bool Store::PathInfoCacheValue::isKnownNow()
|
|||
return std::chrono::steady_clock::now() < time_point + ttl;
|
||||
}
|
||||
|
||||
std::map<std::string, std::optional<StorePath>> Store::queryPartialDerivationOutputMap(const StorePath & path)
|
||||
{
|
||||
std::map<std::string, std::optional<StorePath>> outputs;
|
||||
auto drv = readInvalidDerivation(path);
|
||||
for (auto& [outputName, output] : drv.outputsAndOptPaths(*this)) {
|
||||
outputs.emplace(outputName, output.second);
|
||||
}
|
||||
return outputs;
|
||||
}
|
||||
|
||||
OutputPathMap Store::queryDerivationOutputMap(const StorePath & path) {
|
||||
auto resp = queryPartialDerivationOutputMap(path);
|
||||
OutputPathMap result;
|
||||
for (auto & [outName, optOutPath] : resp) {
|
||||
if (!optOutPath)
|
||||
throw Error("output '%s' has no store path mapped to it", outName);
|
||||
throw Error("output '%s' of derivation '%s' has no store path mapped to it", outName, printStorePath(path));
|
||||
result.insert_or_assign(outName, *optOutPath);
|
||||
}
|
||||
return result;
|
||||
|
|
@ -538,6 +550,28 @@ void Store::queryPathInfo(const StorePath & storePath,
|
|||
}
|
||||
|
||||
|
||||
void Store::substitutePaths(const StorePathSet & paths)
|
||||
{
|
||||
std::vector<StorePathWithOutputs> paths2;
|
||||
for (auto & path : paths)
|
||||
if (!path.isDerivation())
|
||||
paths2.push_back({path});
|
||||
uint64_t downloadSize, narSize;
|
||||
StorePathSet willBuild, willSubstitute, unknown;
|
||||
queryMissing(paths2,
|
||||
willBuild, willSubstitute, unknown, downloadSize, narSize);
|
||||
|
||||
if (!willSubstitute.empty())
|
||||
try {
|
||||
std::vector<StorePathWithOutputs> subs;
|
||||
for (auto & p : willSubstitute) subs.push_back({p});
|
||||
buildPaths(subs);
|
||||
} catch (Error & e) {
|
||||
logWarning(e.info());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
StorePathSet Store::queryValidPaths(const StorePathSet & paths, SubstituteFlag maybeSubstitute)
|
||||
{
|
||||
struct State
|
||||
|
|
@ -716,21 +750,6 @@ const Store::Stats & Store::getStats()
|
|||
}
|
||||
|
||||
|
||||
void Store::buildPaths(const std::vector<StorePathWithOutputs> & paths, BuildMode buildMode)
|
||||
{
|
||||
StorePathSet paths2;
|
||||
|
||||
for (auto & path : paths) {
|
||||
if (path.path.isDerivation())
|
||||
unsupported("buildPaths");
|
||||
paths2.insert(path.path);
|
||||
}
|
||||
|
||||
if (queryValidPaths(paths2).size() != paths2.size())
|
||||
unsupported("buildPaths");
|
||||
}
|
||||
|
||||
|
||||
void copyStorePath(ref<Store> srcStore, ref<Store> dstStore,
|
||||
const StorePath & storePath, RepairFlag repair, CheckSigsFlag checkSigs)
|
||||
{
|
||||
|
|
@ -767,8 +786,8 @@ void copyStorePath(ref<Store> srcStore, ref<Store> dstStore,
|
|||
}
|
||||
|
||||
auto source = sinkToSource([&](Sink & sink) {
|
||||
LambdaSink progressSink([&](const unsigned char * data, size_t len) {
|
||||
total += len;
|
||||
LambdaSink progressSink([&](std::string_view data) {
|
||||
total += data.size();
|
||||
act.progress(total, info->narSize);
|
||||
});
|
||||
TeeSink tee { sink, progressSink };
|
||||
|
|
@ -896,19 +915,20 @@ std::optional<ValidPathInfo> decodeValidPathInfo(const Store & store, std::istre
|
|||
getline(str, s);
|
||||
auto narHash = Hash::parseAny(s, htSHA256);
|
||||
getline(str, s);
|
||||
uint64_t narSize;
|
||||
if (!string2Int(s, narSize)) throw Error("number expected");
|
||||
hashGiven = { narHash, narSize };
|
||||
auto narSize = string2Int<uint64_t>(s);
|
||||
if (!narSize) throw Error("number expected");
|
||||
hashGiven = { narHash, *narSize };
|
||||
}
|
||||
ValidPathInfo info(store.parseStorePath(path), hashGiven->first);
|
||||
info.narSize = hashGiven->second;
|
||||
std::string deriver;
|
||||
getline(str, deriver);
|
||||
if (deriver != "") info.deriver = store.parseStorePath(deriver);
|
||||
string s; int n;
|
||||
string s;
|
||||
getline(str, s);
|
||||
if (!string2Int(s, n)) throw Error("number expected");
|
||||
while (n--) {
|
||||
auto n = string2Int<int>(s);
|
||||
if (!n) throw Error("number expected");
|
||||
while ((*n)--) {
|
||||
getline(str, s);
|
||||
info.insertReferencePossiblyToSelf(store.parseStorePath(s));
|
||||
}
|
||||
|
|
@ -1060,19 +1080,24 @@ Derivation Store::derivationFromPath(const StorePath & drvPath)
|
|||
return readDerivation(drvPath);
|
||||
}
|
||||
|
||||
|
||||
Derivation Store::readDerivation(const StorePath & drvPath)
|
||||
Derivation readDerivationCommon(Store& store, const StorePath& drvPath, bool requireValidPath)
|
||||
{
|
||||
auto accessor = getFSAccessor();
|
||||
auto accessor = store.getFSAccessor();
|
||||
try {
|
||||
return parseDerivation(*this,
|
||||
accessor->readFile(printStorePath(drvPath)),
|
||||
return parseDerivation(store,
|
||||
accessor->readFile(store.printStorePath(drvPath), requireValidPath),
|
||||
Derivation::nameFromPath(drvPath));
|
||||
} catch (FormatError & e) {
|
||||
throw Error("error parsing derivation '%s': %s", printStorePath(drvPath), e.msg());
|
||||
throw Error("error parsing derivation '%s': %s", store.printStorePath(drvPath), e.msg());
|
||||
}
|
||||
}
|
||||
|
||||
Derivation Store::readDerivation(const StorePath & drvPath)
|
||||
{ return readDerivationCommon(*this, drvPath, true); }
|
||||
|
||||
Derivation Store::readInvalidDerivation(const StorePath & drvPath)
|
||||
{ return readDerivationCommon(*this, drvPath, false); }
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1127,6 +1152,34 @@ std::shared_ptr<Store> openFromNonUri(const std::string & uri, const Store::Para
|
|||
}
|
||||
}
|
||||
|
||||
// The `parseURL` function supports both IPv6 URIs as defined in
|
||||
// RFC2732, but also pure addresses. The latter one is needed here to
|
||||
// connect to a remote store via SSH (it's possible to do e.g. `ssh root@::1`).
|
||||
//
|
||||
// This function now ensures that a usable connection string is available:
|
||||
// * If the store to be opened is not an SSH store, nothing will be done.
|
||||
// * If the URL looks like `root@[::1]` (which is allowed by the URL parser and probably
|
||||
// needed to pass further flags), it
|
||||
// will be transformed into `root@::1` for SSH (same for `[::1]` -> `::1`).
|
||||
// * If the URL looks like `root@::1` it will be left as-is.
|
||||
// * In any other case, the string will be left as-is.
|
||||
static std::string extractConnStr(const std::string &proto, const std::string &connStr)
|
||||
{
|
||||
if (proto.rfind("ssh") != std::string::npos) {
|
||||
std::smatch result;
|
||||
std::regex v6AddrRegex("^((.*)@)?\\[(.*)\\]$");
|
||||
|
||||
if (std::regex_match(connStr, result, v6AddrRegex)) {
|
||||
if (result[1].matched) {
|
||||
return result.str(1) + result.str(3);
|
||||
}
|
||||
return result.str(3);
|
||||
}
|
||||
}
|
||||
|
||||
return connStr;
|
||||
}
|
||||
|
||||
ref<Store> openStore(const std::string & uri_,
|
||||
const Store::Params & extraParams)
|
||||
{
|
||||
|
|
@ -1135,7 +1188,10 @@ ref<Store> openStore(const std::string & uri_,
|
|||
auto parsedUri = parseURL(uri_);
|
||||
params.insert(parsedUri.query.begin(), parsedUri.query.end());
|
||||
|
||||
auto baseURI = parsedUri.authority.value_or("") + parsedUri.path;
|
||||
auto baseURI = extractConnStr(
|
||||
parsedUri.scheme,
|
||||
parsedUri.authority.value_or("") + parsedUri.path
|
||||
);
|
||||
|
||||
for (auto implem : *Implementations::registered) {
|
||||
if (implem.uriSchemes.count(parsedUri.scheme)) {
|
||||
|
|
@ -1180,9 +1236,6 @@ std::list<ref<Store>> getDefaultSubstituters()
|
|||
for (auto uri : settings.substituters.get())
|
||||
addStore(uri);
|
||||
|
||||
for (auto uri : settings.extraSubstituters.get())
|
||||
addStore(uri);
|
||||
|
||||
stores.sort([](ref<Store> & a, ref<Store> & b) {
|
||||
return a->priority < b->priority;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include "realisation.hh"
|
||||
#include "path.hh"
|
||||
#include "hash.hh"
|
||||
#include "content-address.hh"
|
||||
|
|
@ -176,25 +177,7 @@ struct StoreConfig : public Config
|
|||
{
|
||||
using Config::Config;
|
||||
|
||||
/**
|
||||
* When constructing a store implementation, we pass in a map `params` of
|
||||
* parameters that's supposed to initialize the associated config.
|
||||
* To do that, we must use the `StoreConfig(StringMap & params)`
|
||||
* constructor, so we'd like to `delete` its default constructor to enforce
|
||||
* it.
|
||||
*
|
||||
* However, actually deleting it means that all the subclasses of
|
||||
* `StoreConfig` will have their default constructor deleted (because it's
|
||||
* supposed to call the deleted default constructor of `StoreConfig`). But
|
||||
* because we're always using virtual inheritance, the constructors of
|
||||
* child classes will never implicitely call this one, so deleting it will
|
||||
* be more painful than anything else.
|
||||
*
|
||||
* So we `assert(false)` here to ensure at runtime that the right
|
||||
* constructor is always called without having to redefine a custom
|
||||
* constructor for each `*Config` class.
|
||||
*/
|
||||
StoreConfig() { assert(false); }
|
||||
StoreConfig() = delete;
|
||||
|
||||
virtual ~StoreConfig() { }
|
||||
|
||||
|
|
@ -356,6 +339,11 @@ protected:
|
|||
|
||||
public:
|
||||
|
||||
/* If requested, substitute missing paths. This
|
||||
implements nix-copy-closure's --use-substitutes
|
||||
flag. */
|
||||
void substitutePaths(const StorePathSet & paths);
|
||||
|
||||
/* Query which of the given paths is valid. Optionally, try to
|
||||
substitute missing paths. */
|
||||
virtual StorePathSet queryValidPaths(const StorePathSet & paths,
|
||||
|
|
@ -380,6 +368,21 @@ public:
|
|||
void queryPathInfo(const StorePath & path,
|
||||
Callback<ref<const ValidPathInfo>> callback) noexcept;
|
||||
|
||||
/* Check whether the given valid path info is sufficiently attested, by
|
||||
either being signed by a trusted public key or content-addressed, in
|
||||
order to be included in the given store.
|
||||
|
||||
These same checks would be performed in addToStore, but this allows an
|
||||
earlier failure in the case where dependencies need to be added too, but
|
||||
the addToStore wouldn't fail until those dependencies are added. Also,
|
||||
we don't really want to add the dependencies listed in a nar info we
|
||||
don't trust anyyways.
|
||||
*/
|
||||
virtual bool pathInfoIsTrusted(const ValidPathInfo &)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
virtual void queryPathInfoUncached(const StorePath & path,
|
||||
|
|
@ -387,6 +390,8 @@ protected:
|
|||
|
||||
public:
|
||||
|
||||
virtual std::optional<const Realisation> queryRealisation(const DrvOutput &) = 0;
|
||||
|
||||
/* Queries the set of incoming FS references for a store path.
|
||||
The result is not cleared. */
|
||||
virtual void queryReferrers(const StorePath & path, StorePathSet & referrers)
|
||||
|
|
@ -404,8 +409,7 @@ public:
|
|||
/* Query the mapping outputName => outputPath for the given derivation. All
|
||||
outputs are mentioned so ones mising the mapping are mapped to
|
||||
`std::nullopt`. */
|
||||
virtual std::map<std::string, std::optional<StorePath>> queryPartialDerivationOutputMap(const StorePath & path)
|
||||
{ unsupported("queryPartialDerivationOutputMap"); }
|
||||
virtual std::map<std::string, std::optional<StorePath>> queryPartialDerivationOutputMap(const StorePath & path);
|
||||
|
||||
/* Query the mapping outputName=>outputPath for the given derivation.
|
||||
Assume every output has a mapping and throw an exception otherwise. */
|
||||
|
|
@ -459,6 +463,18 @@ public:
|
|||
virtual StorePath addTextToStore(const string & name, const string & s,
|
||||
const StorePathSet & references, RepairFlag repair = NoRepair) = 0;
|
||||
|
||||
/**
|
||||
* Add a mapping indicating that `deriver!outputName` maps to the output path
|
||||
* `output`.
|
||||
*
|
||||
* This is redundant for known-input-addressed and fixed-output derivations
|
||||
* as this information is already present in the drv file, but necessary for
|
||||
* floating-ca derivations and their dependencies as there's no way to
|
||||
* retrieve this information otherwise.
|
||||
*/
|
||||
virtual void registerDrvOutput(const Realisation & output)
|
||||
{ unsupported("registerDrvOutput"); }
|
||||
|
||||
/* Write a NAR dump of a store path. */
|
||||
virtual void narFromPath(const StorePath & path, Sink & sink) = 0;
|
||||
|
||||
|
|
@ -508,17 +524,17 @@ public:
|
|||
explicitly choosing to allow it).
|
||||
*/
|
||||
virtual BuildResult buildDerivation(const StorePath & drvPath, const BasicDerivation & drv,
|
||||
BuildMode buildMode = bmNormal) = 0;
|
||||
BuildMode buildMode = bmNormal);
|
||||
|
||||
/* Ensure that a path is valid. If it is not currently valid, it
|
||||
may be made valid by running a substitute (if defined for the
|
||||
path). */
|
||||
virtual void ensurePath(const StorePath & path) = 0;
|
||||
virtual void ensurePath(const StorePath & path);
|
||||
|
||||
/* Add a store path as a temporary root of the garbage collector.
|
||||
The root disappears as soon as we exit. */
|
||||
virtual void addTempRoot(const StorePath & path)
|
||||
{ unsupported("addTempRoot"); }
|
||||
{ warn("not creating temp root, store doesn't support GC"); }
|
||||
|
||||
/* Add an indirect root, which is merely a symlink to `path' from
|
||||
/nix/var/nix/gcroots/auto/<hash of `path'>. `path' is supposed
|
||||
|
|
@ -593,6 +609,11 @@ public:
|
|||
virtual ref<FSAccessor> getFSAccessor()
|
||||
{ unsupported("getFSAccessor"); }
|
||||
|
||||
/* Repair the contents of the given path by redownloading it using
|
||||
a substituter (if available). */
|
||||
virtual void repairPath(const StorePath & path)
|
||||
{ unsupported("repairPath"); }
|
||||
|
||||
/* Add signatures to the specified store path. The signatures are
|
||||
not verified. */
|
||||
virtual void addSignatures(const StorePath & storePath, const StringSet & sigs)
|
||||
|
|
@ -607,6 +628,9 @@ public:
|
|||
/* Read a derivation (which must already be valid). */
|
||||
Derivation readDerivation(const StorePath & drvPath);
|
||||
|
||||
/* Read a derivation from a potentially invalid path. */
|
||||
Derivation readInvalidDerivation(const StorePath & drvPath);
|
||||
|
||||
/* Place in `out' the set of all store paths in the file system
|
||||
closure of `storePath'; that is, all paths than can be directly
|
||||
or indirectly reached from it. `out' is not cleared. If
|
||||
|
|
|
|||
|
|
@ -15,6 +15,9 @@ namespace nix {
|
|||
|
||||
UDSRemoteStore::UDSRemoteStore(const Params & params)
|
||||
: StoreConfig(params)
|
||||
, LocalFSStoreConfig(params)
|
||||
, RemoteStoreConfig(params)
|
||||
, UDSRemoteStoreConfig(params)
|
||||
, Store(params)
|
||||
, LocalFSStore(params)
|
||||
, RemoteStore(params)
|
||||
|
|
|
|||
|
|
@ -14,15 +14,10 @@ struct UDSRemoteStoreConfig : virtual LocalFSStoreConfig, virtual RemoteStoreCon
|
|||
{
|
||||
}
|
||||
|
||||
UDSRemoteStoreConfig()
|
||||
: UDSRemoteStoreConfig(Store::Params({}))
|
||||
{
|
||||
}
|
||||
|
||||
const std::string name() override { return "Local Daemon Store"; }
|
||||
};
|
||||
|
||||
class UDSRemoteStore : public LocalFSStore, public RemoteStore, public virtual UDSRemoteStoreConfig
|
||||
class UDSRemoteStore : public virtual UDSRemoteStoreConfig, public virtual LocalFSStore, public virtual RemoteStore
|
||||
{
|
||||
public:
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
#pragma once
|
||||
|
||||
#include "store-api.hh"
|
||||
#include "serialise.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
#define WORKER_MAGIC_1 0x6e697863
|
||||
#define WORKER_MAGIC_2 0x6478696f
|
||||
|
||||
#define PROTOCOL_VERSION 0x11a
|
||||
#define PROTOCOL_VERSION 0x11b
|
||||
#define GET_PROTOCOL_MAJOR(x) ((x) & 0xff00)
|
||||
#define GET_PROTOCOL_MINOR(x) ((x) & 0x00ff)
|
||||
|
||||
|
|
@ -50,6 +53,8 @@ typedef enum {
|
|||
wopAddToStoreNar = 39,
|
||||
wopQueryMissing = 40,
|
||||
wopQueryDerivationOutputMap = 41,
|
||||
wopRegisterDrvOutput = 42,
|
||||
wopQueryRealisation = 43,
|
||||
} WorkerOp;
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue