Merge branches 'build-1', 'build-2', 'build-3', 'build-4', 'build-5', 'build-6', 'build-7' and 'build-8' into split_build_cc
This commit is contained in:
parent
a4f0fecb03
3bab1c5bb0
819fe848ac
159054f730
4bdff7d1b0
d24ffe0eb1
eed53ed87a
dbc588651c
bcb67e1ed8
commit
8cc510fb79
3724
src/libstore/build/derivation-goal.cc
Normal file
3724
src/libstore/build/derivation-goal.cc
Normal file
File diff suppressed because it is too large
Load diff
88
src/libstore/build/goal.cc
Normal file
88
src/libstore/build/goal.cc
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
#include "build.hh"
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
|
||||||
|
bool CompareGoalPtrs::operator() (const GoalPtr & a, const GoalPtr & b) const {
|
||||||
|
string s1 = a->key();
|
||||||
|
string s2 = b->key();
|
||||||
|
return s1 < s2;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void addToWeakGoals(WeakGoals & goals, GoalPtr p)
|
||||||
|
{
|
||||||
|
// FIXME: necessary?
|
||||||
|
// FIXME: O(n)
|
||||||
|
for (auto & i : goals)
|
||||||
|
if (i.lock() == p) return;
|
||||||
|
goals.push_back(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Goal::addWaitee(GoalPtr waitee)
|
||||||
|
{
|
||||||
|
waitees.insert(waitee);
|
||||||
|
addToWeakGoals(waitee->waiters, shared_from_this());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Goal::waiteeDone(GoalPtr waitee, ExitCode result)
|
||||||
|
{
|
||||||
|
assert(waitees.find(waitee) != waitees.end());
|
||||||
|
waitees.erase(waitee);
|
||||||
|
|
||||||
|
trace(fmt("waitee '%s' done; %d left", waitee->name, waitees.size()));
|
||||||
|
|
||||||
|
if (result == ecFailed || result == ecNoSubstituters || result == ecIncompleteClosure) ++nrFailed;
|
||||||
|
|
||||||
|
if (result == ecNoSubstituters) ++nrNoSubstituters;
|
||||||
|
|
||||||
|
if (result == ecIncompleteClosure) ++nrIncompleteClosure;
|
||||||
|
|
||||||
|
if (waitees.empty() || (result == ecFailed && !settings.keepGoing)) {
|
||||||
|
|
||||||
|
/* If we failed and keepGoing is not set, we remove all
|
||||||
|
remaining waitees. */
|
||||||
|
for (auto & goal : waitees) {
|
||||||
|
WeakGoals waiters2;
|
||||||
|
for (auto & j : goal->waiters)
|
||||||
|
if (j.lock() != shared_from_this()) waiters2.push_back(j);
|
||||||
|
goal->waiters = waiters2;
|
||||||
|
}
|
||||||
|
waitees.clear();
|
||||||
|
|
||||||
|
worker.wakeUp(shared_from_this());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Goal::amDone(ExitCode result, std::optional<Error> ex)
|
||||||
|
{
|
||||||
|
trace("done");
|
||||||
|
assert(exitCode == ecBusy);
|
||||||
|
assert(result == ecSuccess || result == ecFailed || result == ecNoSubstituters || result == ecIncompleteClosure);
|
||||||
|
exitCode = result;
|
||||||
|
|
||||||
|
if (ex) {
|
||||||
|
if (!waiters.empty())
|
||||||
|
logError(ex->info());
|
||||||
|
else
|
||||||
|
this->ex = std::move(*ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto & i : waiters) {
|
||||||
|
GoalPtr goal = i.lock();
|
||||||
|
if (goal) goal->waiteeDone(shared_from_this(), result);
|
||||||
|
}
|
||||||
|
waiters.clear();
|
||||||
|
worker.removeGoal(shared_from_this());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Goal::trace(const FormatOrString & fs)
|
||||||
|
{
|
||||||
|
debug("%1%: %2%", name, fs.s);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
71
src/libstore/build/hook-instance.cc
Normal file
71
src/libstore/build/hook-instance.cc
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
#include "build.hh"
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
HookInstance::HookInstance()
|
||||||
|
{
|
||||||
|
debug("starting build hook '%s'", settings.buildHook);
|
||||||
|
|
||||||
|
/* Create a pipe to get the output of the child. */
|
||||||
|
fromHook.create();
|
||||||
|
|
||||||
|
/* Create the communication pipes. */
|
||||||
|
toHook.create();
|
||||||
|
|
||||||
|
/* Create a pipe to get the output of the builder. */
|
||||||
|
builderOut.create();
|
||||||
|
|
||||||
|
/* Fork the hook. */
|
||||||
|
pid = startProcess([&]() {
|
||||||
|
|
||||||
|
commonChildInit(fromHook);
|
||||||
|
|
||||||
|
if (chdir("/") == -1) throw SysError("changing into /");
|
||||||
|
|
||||||
|
/* Dup the communication pipes. */
|
||||||
|
if (dup2(toHook.readSide.get(), STDIN_FILENO) == -1)
|
||||||
|
throw SysError("dupping to-hook read side");
|
||||||
|
|
||||||
|
/* Use fd 4 for the builder's stdout/stderr. */
|
||||||
|
if (dup2(builderOut.writeSide.get(), 4) == -1)
|
||||||
|
throw SysError("dupping builder's stdout/stderr");
|
||||||
|
|
||||||
|
/* Hack: pass the read side of that fd to allow build-remote
|
||||||
|
to read SSH error messages. */
|
||||||
|
if (dup2(builderOut.readSide.get(), 5) == -1)
|
||||||
|
throw SysError("dupping builder's stdout/stderr");
|
||||||
|
|
||||||
|
Strings args = {
|
||||||
|
std::string(baseNameOf(settings.buildHook.get())),
|
||||||
|
std::to_string(verbosity),
|
||||||
|
};
|
||||||
|
|
||||||
|
execv(settings.buildHook.get().c_str(), stringsToCharPtrs(args).data());
|
||||||
|
|
||||||
|
throw SysError("executing '%s'", settings.buildHook);
|
||||||
|
});
|
||||||
|
|
||||||
|
pid.setSeparatePG(true);
|
||||||
|
fromHook.writeSide = -1;
|
||||||
|
toHook.readSide = -1;
|
||||||
|
|
||||||
|
sink = FdSink(toHook.writeSide.get());
|
||||||
|
std::map<std::string, Config::SettingInfo> settings;
|
||||||
|
globalConfig.getSettings(settings);
|
||||||
|
for (auto & setting : settings)
|
||||||
|
sink << 1 << setting.first << setting.second.value;
|
||||||
|
sink << 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
HookInstance::~HookInstance()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
toHook.writeSide = -1;
|
||||||
|
if (pid != -1) pid.kill();
|
||||||
|
} catch (...) {
|
||||||
|
ignoreException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
123
src/libstore/build/local-store-build.cc
Normal file
123
src/libstore/build/local-store-build.cc
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
#include "build.hh"
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
Worker worker(*this);
|
||||||
|
|
||||||
|
primeCache(*this, drvPaths);
|
||||||
|
|
||||||
|
Goals goals;
|
||||||
|
for (auto & path : drvPaths) {
|
||||||
|
if (path.path.isDerivation())
|
||||||
|
goals.insert(worker.makeDerivationGoal(path.path, path.outputs, buildMode));
|
||||||
|
else
|
||||||
|
goals.insert(worker.makeSubstitutionGoal(path.path, buildMode == bmRepair ? Repair : NoRepair));
|
||||||
|
}
|
||||||
|
|
||||||
|
worker.run(goals);
|
||||||
|
|
||||||
|
StorePathSet failed;
|
||||||
|
std::optional<Error> ex;
|
||||||
|
for (auto & i : goals) {
|
||||||
|
if (i->ex) {
|
||||||
|
if (ex)
|
||||||
|
logError(i->ex->info());
|
||||||
|
else
|
||||||
|
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 (failed.size() == 1 && ex) {
|
||||||
|
ex->status = worker.exitStatus();
|
||||||
|
throw *ex;
|
||||||
|
} else if (!failed.empty()) {
|
||||||
|
if (ex) logError(ex->info());
|
||||||
|
throw Error(worker.exitStatus(), "build of %s failed", showPaths(failed));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BuildResult LocalStore::buildDerivation(const StorePath & drvPath, const BasicDerivation & drv,
|
||||||
|
BuildMode buildMode)
|
||||||
|
{
|
||||||
|
Worker worker(*this);
|
||||||
|
auto goal = worker.makeBasicDerivationGoal(drvPath, drv, {}, buildMode);
|
||||||
|
|
||||||
|
BuildResult result;
|
||||||
|
|
||||||
|
try {
|
||||||
|
worker.run(Goals{goal});
|
||||||
|
result = goal->getResult();
|
||||||
|
} catch (Error & e) {
|
||||||
|
result.status = BuildResult::MiscFailure;
|
||||||
|
result.errorMsg = e.msg();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void LocalStore::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};
|
||||||
|
|
||||||
|
worker.run(goals);
|
||||||
|
|
||||||
|
if (goal->exitCode != Goal::ecSuccess) {
|
||||||
|
if (goal->ex) {
|
||||||
|
goal->ex->status = worker.exitStatus();
|
||||||
|
throw *goal->ex;
|
||||||
|
} else
|
||||||
|
throw Error(worker.exitStatus(), "path '%s' does not exist and cannot be created", printStorePath(path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void LocalStore::repairPath(const StorePath & path)
|
||||||
|
{
|
||||||
|
Worker worker(*this);
|
||||||
|
GoalPtr goal = worker.makeSubstitutionGoal(path, Repair);
|
||||||
|
Goals goals = {goal};
|
||||||
|
|
||||||
|
worker.run(goals);
|
||||||
|
|
||||||
|
if (goal->exitCode != Goal::ecSuccess) {
|
||||||
|
/* Since substituting the path didn't work, if we have a valid
|
||||||
|
deriver, then rebuild the deriver. */
|
||||||
|
auto info = queryPathInfo(path);
|
||||||
|
if (info->deriver && isValidPath(*info->deriver)) {
|
||||||
|
goals.clear();
|
||||||
|
goals.insert(worker.makeDerivationGoal(*info->deriver, StringSet(), bmRepair));
|
||||||
|
worker.run(goals);
|
||||||
|
} else
|
||||||
|
throw Error(worker.exitStatus(), "cannot repair path '%s'", printStorePath(path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
295
src/libstore/build/substitution-goal.cc
Normal file
295
src/libstore/build/substitution-goal.cc
Normal file
|
@ -0,0 +1,295 @@
|
||||||
|
#include "build.hh"
|
||||||
|
#include "nar-info.hh"
|
||||||
|
#include "finally.hh"
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
SubstitutionGoal::SubstitutionGoal(const StorePath & storePath, Worker & worker, RepairFlag repair, std::optional<ContentAddress> ca)
|
||||||
|
: Goal(worker)
|
||||||
|
, storePath(storePath)
|
||||||
|
, repair(repair)
|
||||||
|
, ca(ca)
|
||||||
|
{
|
||||||
|
state = &SubstitutionGoal::init;
|
||||||
|
name = fmt("substitution of '%s'", worker.store.printStorePath(this->storePath));
|
||||||
|
trace("created");
|
||||||
|
maintainExpectedSubstitutions = std::make_unique<MaintainCount<uint64_t>>(worker.expectedSubstitutions);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
SubstitutionGoal::~SubstitutionGoal()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (thr.joinable()) {
|
||||||
|
// FIXME: signal worker thread to quit.
|
||||||
|
thr.join();
|
||||||
|
worker.childTerminated(this);
|
||||||
|
}
|
||||||
|
} catch (...) {
|
||||||
|
ignoreException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void SubstitutionGoal::work()
|
||||||
|
{
|
||||||
|
(this->*state)();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void SubstitutionGoal::init()
|
||||||
|
{
|
||||||
|
trace("init");
|
||||||
|
|
||||||
|
worker.store.addTempRoot(storePath);
|
||||||
|
|
||||||
|
/* If the path already exists we're done. */
|
||||||
|
if (!repair && worker.store.isValidPath(storePath)) {
|
||||||
|
amDone(ecSuccess);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.readOnlyMode)
|
||||||
|
throw Error("cannot substitute path '%s' - no write access to the Nix store", worker.store.printStorePath(storePath));
|
||||||
|
|
||||||
|
subs = settings.useSubstitutes ? getDefaultSubstituters() : std::list<ref<Store>>();
|
||||||
|
|
||||||
|
tryNext();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void SubstitutionGoal::tryNext()
|
||||||
|
{
|
||||||
|
trace("trying next substituter");
|
||||||
|
|
||||||
|
if (subs.size() == 0) {
|
||||||
|
/* None left. Terminate this goal and let someone else deal
|
||||||
|
with it. */
|
||||||
|
debug("path '%s' is required, but there is no substituter that can build it", worker.store.printStorePath(storePath));
|
||||||
|
|
||||||
|
/* Hack: don't indicate failure if there were no substituters.
|
||||||
|
In that case the calling derivation should just do a
|
||||||
|
build. */
|
||||||
|
amDone(substituterFailed ? ecFailed : ecNoSubstituters);
|
||||||
|
|
||||||
|
if (substituterFailed) {
|
||||||
|
worker.failedSubstitutions++;
|
||||||
|
worker.updateProgress();
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub = subs.front();
|
||||||
|
subs.pop_front();
|
||||||
|
|
||||||
|
if (ca) {
|
||||||
|
subPath = sub->makeFixedOutputPathFromCA(storePath.name(), *ca);
|
||||||
|
if (sub->storeDir == worker.store.storeDir)
|
||||||
|
assert(subPath == storePath);
|
||||||
|
} else if (sub->storeDir != worker.store.storeDir) {
|
||||||
|
tryNext();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// FIXME: make async
|
||||||
|
info = sub->queryPathInfo(subPath ? *subPath : storePath);
|
||||||
|
} catch (InvalidPath &) {
|
||||||
|
tryNext();
|
||||||
|
return;
|
||||||
|
} catch (SubstituterDisabled &) {
|
||||||
|
if (settings.tryFallback) {
|
||||||
|
tryNext();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw;
|
||||||
|
} catch (Error & e) {
|
||||||
|
if (settings.tryFallback) {
|
||||||
|
logError(e.info());
|
||||||
|
tryNext();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info->path != storePath) {
|
||||||
|
if (info->isContentAddressed(*sub) && info->references.empty()) {
|
||||||
|
auto info2 = std::make_shared<ValidPathInfo>(*info);
|
||||||
|
info2->path = storePath;
|
||||||
|
info = info2;
|
||||||
|
} else {
|
||||||
|
printError("asked '%s' for '%s' but got '%s'",
|
||||||
|
sub->getUri(), worker.store.printStorePath(storePath), sub->printStorePath(info->path));
|
||||||
|
tryNext();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update the total expected download size. */
|
||||||
|
auto narInfo = std::dynamic_pointer_cast<const NarInfo>(info);
|
||||||
|
|
||||||
|
maintainExpectedNar = std::make_unique<MaintainCount<uint64_t>>(worker.expectedNarSize, info->narSize);
|
||||||
|
|
||||||
|
maintainExpectedDownload =
|
||||||
|
narInfo && narInfo->fileSize
|
||||||
|
? std::make_unique<MaintainCount<uint64_t>>(worker.expectedDownloadSize, narInfo->fileSize)
|
||||||
|
: nullptr;
|
||||||
|
|
||||||
|
worker.updateProgress();
|
||||||
|
|
||||||
|
/* 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()))
|
||||||
|
{
|
||||||
|
logWarning({
|
||||||
|
.name = "Invalid path signature",
|
||||||
|
.hint = hintfmt("substituter '%s' does not have a valid signature for path '%s'",
|
||||||
|
sub->getUri(), worker.store.printStorePath(storePath))
|
||||||
|
});
|
||||||
|
tryNext();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* To maintain the closure invariant, we first have to realise the
|
||||||
|
paths referenced by this one. */
|
||||||
|
for (auto & i : info->references)
|
||||||
|
if (i != storePath) /* ignore self-references */
|
||||||
|
addWaitee(worker.makeSubstitutionGoal(i));
|
||||||
|
|
||||||
|
if (waitees.empty()) /* to prevent hang (no wake-up event) */
|
||||||
|
referencesValid();
|
||||||
|
else
|
||||||
|
state = &SubstitutionGoal::referencesValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void SubstitutionGoal::referencesValid()
|
||||||
|
{
|
||||||
|
trace("all references realised");
|
||||||
|
|
||||||
|
if (nrFailed > 0) {
|
||||||
|
debug("some references of path '%s' could not be realised", worker.store.printStorePath(storePath));
|
||||||
|
amDone(nrNoSubstituters > 0 || nrIncompleteClosure > 0 ? ecIncompleteClosure : ecFailed);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto & i : info->references)
|
||||||
|
if (i != storePath) /* ignore self-references */
|
||||||
|
assert(worker.store.isValidPath(i));
|
||||||
|
|
||||||
|
state = &SubstitutionGoal::tryToRun;
|
||||||
|
worker.wakeUp(shared_from_this());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void SubstitutionGoal::tryToRun()
|
||||||
|
{
|
||||||
|
trace("trying to run");
|
||||||
|
|
||||||
|
/* Make sure that we are allowed to start a build. Note that even
|
||||||
|
if maxBuildJobs == 0 (no local builds allowed), we still allow
|
||||||
|
a substituter to run. This is because substitutions cannot be
|
||||||
|
distributed to another machine via the build hook. */
|
||||||
|
if (worker.getNrLocalBuilds() >= std::max(1U, (unsigned int) settings.maxBuildJobs)) {
|
||||||
|
worker.waitForBuildSlot(shared_from_this());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
maintainRunningSubstitutions = std::make_unique<MaintainCount<uint64_t>>(worker.runningSubstitutions);
|
||||||
|
worker.updateProgress();
|
||||||
|
|
||||||
|
outPipe.create();
|
||||||
|
|
||||||
|
promise = std::promise<void>();
|
||||||
|
|
||||||
|
thr = std::thread([this]() {
|
||||||
|
try {
|
||||||
|
/* Wake up the worker loop when we're done. */
|
||||||
|
Finally updateStats([this]() { outPipe.writeSide = -1; });
|
||||||
|
|
||||||
|
Activity act(*logger, actSubstitute, Logger::Fields{worker.store.printStorePath(storePath), sub->getUri()});
|
||||||
|
PushActivity pact(act.id);
|
||||||
|
|
||||||
|
copyStorePath(ref<Store>(sub), ref<Store>(worker.store.shared_from_this()),
|
||||||
|
subPath ? *subPath : storePath, repair, sub->isTrusted ? NoCheckSigs : CheckSigs);
|
||||||
|
|
||||||
|
promise.set_value();
|
||||||
|
} catch (...) {
|
||||||
|
promise.set_exception(std::current_exception());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
worker.childStarted(shared_from_this(), {outPipe.readSide.get()}, true, false);
|
||||||
|
|
||||||
|
state = &SubstitutionGoal::finished;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void SubstitutionGoal::finished()
|
||||||
|
{
|
||||||
|
trace("substitute finished");
|
||||||
|
|
||||||
|
thr.join();
|
||||||
|
worker.childTerminated(this);
|
||||||
|
|
||||||
|
try {
|
||||||
|
promise.get_future().get();
|
||||||
|
} catch (std::exception & e) {
|
||||||
|
printError(e.what());
|
||||||
|
|
||||||
|
/* Cause the parent build to fail unless --fallback is given,
|
||||||
|
or the substitute has disappeared. The latter case behaves
|
||||||
|
the same as the substitute never having existed in the
|
||||||
|
first place. */
|
||||||
|
try {
|
||||||
|
throw;
|
||||||
|
} catch (SubstituteGone &) {
|
||||||
|
} catch (...) {
|
||||||
|
substituterFailed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Try the next substitute. */
|
||||||
|
state = &SubstitutionGoal::tryNext;
|
||||||
|
worker.wakeUp(shared_from_this());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
worker.markContentsGood(storePath);
|
||||||
|
|
||||||
|
printMsg(lvlChatty, "substitution of path '%s' succeeded", worker.store.printStorePath(storePath));
|
||||||
|
|
||||||
|
maintainRunningSubstitutions.reset();
|
||||||
|
|
||||||
|
maintainExpectedSubstitutions.reset();
|
||||||
|
worker.doneSubstitutions++;
|
||||||
|
|
||||||
|
if (maintainExpectedDownload) {
|
||||||
|
auto fileSize = maintainExpectedDownload->delta;
|
||||||
|
maintainExpectedDownload.reset();
|
||||||
|
worker.doneDownloadSize += fileSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
worker.doneNarSize += maintainExpectedNar->delta;
|
||||||
|
maintainExpectedNar.reset();
|
||||||
|
|
||||||
|
worker.updateProgress();
|
||||||
|
|
||||||
|
amDone(ecSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void SubstitutionGoal::handleChildOutput(int fd, const string & data)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void SubstitutionGoal::handleEOF(int fd)
|
||||||
|
{
|
||||||
|
if (fd == outPipe.readSide.get()) worker.wakeUp(shared_from_this());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
451
src/libstore/build/worker.cc
Normal file
451
src/libstore/build/worker.cc
Normal file
|
@ -0,0 +1,451 @@
|
||||||
|
#include "build.hh"
|
||||||
|
|
||||||
|
#include <poll.h>
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
Worker::Worker(LocalStore & store)
|
||||||
|
: act(*logger, actRealise)
|
||||||
|
, actDerivations(*logger, actBuilds)
|
||||||
|
, actSubstitutions(*logger, actCopyPaths)
|
||||||
|
, store(store)
|
||||||
|
{
|
||||||
|
/* Debugging: prevent recursive workers. */
|
||||||
|
nrLocalBuilds = 0;
|
||||||
|
lastWokenUp = steady_time_point::min();
|
||||||
|
permanentFailure = false;
|
||||||
|
timedOut = false;
|
||||||
|
hashMismatch = false;
|
||||||
|
checkMismatch = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Worker::~Worker()
|
||||||
|
{
|
||||||
|
/* Explicitly get rid of all strong pointers now. After this all
|
||||||
|
goals that refer to this worker should be gone. (Otherwise we
|
||||||
|
are in trouble, since goals may call childTerminated() etc. in
|
||||||
|
their destructors). */
|
||||||
|
topGoals.clear();
|
||||||
|
|
||||||
|
assert(expectedSubstitutions == 0);
|
||||||
|
assert(expectedDownloadSize == 0);
|
||||||
|
assert(expectedNarSize == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::shared_ptr<DerivationGoal> Worker::makeDerivationGoalCommon(
|
||||||
|
const StorePath & drvPath,
|
||||||
|
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) {
|
||||||
|
goal = mkDrvGoal();
|
||||||
|
abstract_goal_weak = goal;
|
||||||
|
wakeUp(goal);
|
||||||
|
} else {
|
||||||
|
goal = std::dynamic_pointer_cast<DerivationGoal>(abstract_goal);
|
||||||
|
assert(goal);
|
||||||
|
goal->addWantedOutputs(wantedOutputs);
|
||||||
|
}
|
||||||
|
return goal;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::shared_ptr<DerivationGoal> Worker::makeDerivationGoal(const StorePath & drvPath,
|
||||||
|
const StringSet & wantedOutputs, BuildMode buildMode)
|
||||||
|
{
|
||||||
|
return makeDerivationGoalCommon(drvPath, wantedOutputs, [&]() {
|
||||||
|
return std::make_shared<DerivationGoal>(drvPath, wantedOutputs, *this, buildMode);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::shared_ptr<DerivationGoal> Worker::makeBasicDerivationGoal(const StorePath & drvPath,
|
||||||
|
const BasicDerivation & drv, const StringSet & wantedOutputs, BuildMode buildMode)
|
||||||
|
{
|
||||||
|
return makeDerivationGoalCommon(drvPath, wantedOutputs, [&]() {
|
||||||
|
return std::make_shared<DerivationGoal>(drvPath, drv, wantedOutputs, *this, buildMode);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
GoalPtr Worker::makeSubstitutionGoal(const StorePath & path, RepairFlag repair, std::optional<ContentAddress> ca)
|
||||||
|
{
|
||||||
|
WeakGoalPtr & goal_weak = substitutionGoals[path];
|
||||||
|
GoalPtr goal = goal_weak.lock(); // FIXME
|
||||||
|
if (!goal) {
|
||||||
|
goal = std::make_shared<SubstitutionGoal>(path, *this, repair, ca);
|
||||||
|
goal_weak = goal;
|
||||||
|
wakeUp(goal);
|
||||||
|
}
|
||||||
|
return goal;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void removeGoal(GoalPtr goal, WeakGoalMap & goalMap)
|
||||||
|
{
|
||||||
|
/* !!! inefficient */
|
||||||
|
for (WeakGoalMap::iterator i = goalMap.begin();
|
||||||
|
i != goalMap.end(); )
|
||||||
|
if (i->second.lock() == goal) {
|
||||||
|
WeakGoalMap::iterator j = i; ++j;
|
||||||
|
goalMap.erase(i);
|
||||||
|
i = j;
|
||||||
|
}
|
||||||
|
else ++i;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Worker::removeGoal(GoalPtr goal)
|
||||||
|
{
|
||||||
|
nix::removeGoal(goal, derivationGoals);
|
||||||
|
nix::removeGoal(goal, substitutionGoals);
|
||||||
|
if (topGoals.find(goal) != topGoals.end()) {
|
||||||
|
topGoals.erase(goal);
|
||||||
|
/* If a top-level goal failed, then kill all other goals
|
||||||
|
(unless keepGoing was set). */
|
||||||
|
if (goal->exitCode == Goal::ecFailed && !settings.keepGoing)
|
||||||
|
topGoals.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Wake up goals waiting for any goal to finish. */
|
||||||
|
for (auto & i : waitingForAnyGoal) {
|
||||||
|
GoalPtr goal = i.lock();
|
||||||
|
if (goal) wakeUp(goal);
|
||||||
|
}
|
||||||
|
|
||||||
|
waitingForAnyGoal.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Worker::wakeUp(GoalPtr goal)
|
||||||
|
{
|
||||||
|
goal->trace("woken up");
|
||||||
|
addToWeakGoals(awake, goal);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
unsigned Worker::getNrLocalBuilds()
|
||||||
|
{
|
||||||
|
return nrLocalBuilds;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Worker::childStarted(GoalPtr goal, const set<int> & fds,
|
||||||
|
bool inBuildSlot, bool respectTimeouts)
|
||||||
|
{
|
||||||
|
Child child;
|
||||||
|
child.goal = goal;
|
||||||
|
child.goal2 = goal.get();
|
||||||
|
child.fds = fds;
|
||||||
|
child.timeStarted = child.lastOutput = steady_time_point::clock::now();
|
||||||
|
child.inBuildSlot = inBuildSlot;
|
||||||
|
child.respectTimeouts = respectTimeouts;
|
||||||
|
children.emplace_back(child);
|
||||||
|
if (inBuildSlot) nrLocalBuilds++;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Worker::childTerminated(Goal * goal, bool wakeSleepers)
|
||||||
|
{
|
||||||
|
auto i = std::find_if(children.begin(), children.end(),
|
||||||
|
[&](const Child & child) { return child.goal2 == goal; });
|
||||||
|
if (i == children.end()) return;
|
||||||
|
|
||||||
|
if (i->inBuildSlot) {
|
||||||
|
assert(nrLocalBuilds > 0);
|
||||||
|
nrLocalBuilds--;
|
||||||
|
}
|
||||||
|
|
||||||
|
children.erase(i);
|
||||||
|
|
||||||
|
if (wakeSleepers) {
|
||||||
|
|
||||||
|
/* Wake up goals waiting for a build slot. */
|
||||||
|
for (auto & j : wantingToBuild) {
|
||||||
|
GoalPtr goal = j.lock();
|
||||||
|
if (goal) wakeUp(goal);
|
||||||
|
}
|
||||||
|
|
||||||
|
wantingToBuild.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Worker::waitForBuildSlot(GoalPtr goal)
|
||||||
|
{
|
||||||
|
debug("wait for build slot");
|
||||||
|
if (getNrLocalBuilds() < settings.maxBuildJobs)
|
||||||
|
wakeUp(goal); /* we can do it right away */
|
||||||
|
else
|
||||||
|
addToWeakGoals(wantingToBuild, goal);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Worker::waitForAnyGoal(GoalPtr goal)
|
||||||
|
{
|
||||||
|
debug("wait for any goal");
|
||||||
|
addToWeakGoals(waitingForAnyGoal, goal);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Worker::waitForAWhile(GoalPtr goal)
|
||||||
|
{
|
||||||
|
debug("wait for a while");
|
||||||
|
addToWeakGoals(waitingForAWhile, goal);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Worker::run(const Goals & _topGoals)
|
||||||
|
{
|
||||||
|
for (auto & i : _topGoals) topGoals.insert(i);
|
||||||
|
|
||||||
|
debug("entered goal loop");
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
|
||||||
|
checkInterrupt();
|
||||||
|
|
||||||
|
store.autoGC(false);
|
||||||
|
|
||||||
|
/* Call every wake goal (in the ordering established by
|
||||||
|
CompareGoalPtrs). */
|
||||||
|
while (!awake.empty() && !topGoals.empty()) {
|
||||||
|
Goals awake2;
|
||||||
|
for (auto & i : awake) {
|
||||||
|
GoalPtr goal = i.lock();
|
||||||
|
if (goal) awake2.insert(goal);
|
||||||
|
}
|
||||||
|
awake.clear();
|
||||||
|
for (auto & goal : awake2) {
|
||||||
|
checkInterrupt();
|
||||||
|
goal->work();
|
||||||
|
if (topGoals.empty()) break; // stuff may have been cancelled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (topGoals.empty()) break;
|
||||||
|
|
||||||
|
/* Wait for input. */
|
||||||
|
if (!children.empty() || !waitingForAWhile.empty())
|
||||||
|
waitForInput();
|
||||||
|
else {
|
||||||
|
if (awake.empty() && 0 == settings.maxBuildJobs)
|
||||||
|
{
|
||||||
|
if (getMachines().empty())
|
||||||
|
throw Error("unable to start any build; either increase '--max-jobs' "
|
||||||
|
"or enable remote builds."
|
||||||
|
"\nhttps://nixos.org/nix/manual/#chap-distributed-builds");
|
||||||
|
else
|
||||||
|
throw Error("unable to start any build; remote machines may not have "
|
||||||
|
"all required system features."
|
||||||
|
"\nhttps://nixos.org/nix/manual/#chap-distributed-builds");
|
||||||
|
|
||||||
|
}
|
||||||
|
assert(!awake.empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If --keep-going is not set, it's possible that the main goal
|
||||||
|
exited while some of its subgoals were still active. But if
|
||||||
|
--keep-going *is* set, then they must all be finished now. */
|
||||||
|
assert(!settings.keepGoing || awake.empty());
|
||||||
|
assert(!settings.keepGoing || wantingToBuild.empty());
|
||||||
|
assert(!settings.keepGoing || children.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Worker::waitForInput()
|
||||||
|
{
|
||||||
|
printMsg(lvlVomit, "waiting for children");
|
||||||
|
|
||||||
|
/* Process output from the file descriptors attached to the
|
||||||
|
children, namely log output and output path creation commands.
|
||||||
|
We also use this to detect child termination: if we get EOF on
|
||||||
|
the logger pipe of a build, we assume that the builder has
|
||||||
|
terminated. */
|
||||||
|
|
||||||
|
bool useTimeout = false;
|
||||||
|
long timeout = 0;
|
||||||
|
auto before = steady_time_point::clock::now();
|
||||||
|
|
||||||
|
/* If we're monitoring for silence on stdout/stderr, or if there
|
||||||
|
is a build timeout, then wait for input until the first
|
||||||
|
deadline for any child. */
|
||||||
|
auto nearest = steady_time_point::max(); // nearest deadline
|
||||||
|
if (settings.minFree.get() != 0)
|
||||||
|
// Periodicallty wake up to see if we need to run the garbage collector.
|
||||||
|
nearest = before + std::chrono::seconds(10);
|
||||||
|
for (auto & i : children) {
|
||||||
|
if (!i.respectTimeouts) continue;
|
||||||
|
if (0 != settings.maxSilentTime)
|
||||||
|
nearest = std::min(nearest, i.lastOutput + std::chrono::seconds(settings.maxSilentTime));
|
||||||
|
if (0 != settings.buildTimeout)
|
||||||
|
nearest = std::min(nearest, i.timeStarted + std::chrono::seconds(settings.buildTimeout));
|
||||||
|
}
|
||||||
|
if (nearest != steady_time_point::max()) {
|
||||||
|
timeout = std::max(1L, (long) std::chrono::duration_cast<std::chrono::seconds>(nearest - before).count());
|
||||||
|
useTimeout = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If we are polling goals that are waiting for a lock, then wake
|
||||||
|
up after a few seconds at most. */
|
||||||
|
if (!waitingForAWhile.empty()) {
|
||||||
|
useTimeout = true;
|
||||||
|
if (lastWokenUp == steady_time_point::min() || lastWokenUp > before) lastWokenUp = before;
|
||||||
|
timeout = std::max(1L,
|
||||||
|
(long) std::chrono::duration_cast<std::chrono::seconds>(
|
||||||
|
lastWokenUp + std::chrono::seconds(settings.pollInterval) - before).count());
|
||||||
|
} else lastWokenUp = steady_time_point::min();
|
||||||
|
|
||||||
|
if (useTimeout)
|
||||||
|
vomit("sleeping %d seconds", timeout);
|
||||||
|
|
||||||
|
/* Use select() to wait for the input side of any logger pipe to
|
||||||
|
become `available'. Note that `available' (i.e., non-blocking)
|
||||||
|
includes EOF. */
|
||||||
|
std::vector<struct pollfd> pollStatus;
|
||||||
|
std::map <int, int> fdToPollStatus;
|
||||||
|
for (auto & i : children) {
|
||||||
|
for (auto & j : i.fds) {
|
||||||
|
pollStatus.push_back((struct pollfd) { .fd = j, .events = POLLIN });
|
||||||
|
fdToPollStatus[j] = pollStatus.size() - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (poll(pollStatus.data(), pollStatus.size(),
|
||||||
|
useTimeout ? timeout * 1000 : -1) == -1) {
|
||||||
|
if (errno == EINTR) return;
|
||||||
|
throw SysError("waiting for input");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto after = steady_time_point::clock::now();
|
||||||
|
|
||||||
|
/* Process all available file descriptors. FIXME: this is
|
||||||
|
O(children * fds). */
|
||||||
|
decltype(children)::iterator i;
|
||||||
|
for (auto j = children.begin(); j != children.end(); j = i) {
|
||||||
|
i = std::next(j);
|
||||||
|
|
||||||
|
checkInterrupt();
|
||||||
|
|
||||||
|
GoalPtr goal = j->goal.lock();
|
||||||
|
assert(goal);
|
||||||
|
|
||||||
|
set<int> fds2(j->fds);
|
||||||
|
std::vector<unsigned char> buffer(4096);
|
||||||
|
for (auto & k : fds2) {
|
||||||
|
if (pollStatus.at(fdToPollStatus.at(k)).revents) {
|
||||||
|
ssize_t rd = ::read(k, buffer.data(), buffer.size());
|
||||||
|
// FIXME: is there a cleaner way to handle pt close
|
||||||
|
// than EIO? Is this even standard?
|
||||||
|
if (rd == 0 || (rd == -1 && errno == EIO)) {
|
||||||
|
debug("%1%: got EOF", goal->getName());
|
||||||
|
goal->handleEOF(k);
|
||||||
|
j->fds.erase(k);
|
||||||
|
} else if (rd == -1) {
|
||||||
|
if (errno != EINTR)
|
||||||
|
throw SysError("%s: read failed", goal->getName());
|
||||||
|
} else {
|
||||||
|
printMsg(lvlVomit, "%1%: read %2% bytes",
|
||||||
|
goal->getName(), rd);
|
||||||
|
string data((char *) buffer.data(), rd);
|
||||||
|
j->lastOutput = after;
|
||||||
|
goal->handleChildOutput(k, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (goal->exitCode == Goal::ecBusy &&
|
||||||
|
0 != settings.maxSilentTime &&
|
||||||
|
j->respectTimeouts &&
|
||||||
|
after - j->lastOutput >= std::chrono::seconds(settings.maxSilentTime))
|
||||||
|
{
|
||||||
|
goal->timedOut(Error(
|
||||||
|
"%1% timed out after %2% seconds of silence",
|
||||||
|
goal->getName(), settings.maxSilentTime));
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (goal->exitCode == Goal::ecBusy &&
|
||||||
|
0 != settings.buildTimeout &&
|
||||||
|
j->respectTimeouts &&
|
||||||
|
after - j->timeStarted >= std::chrono::seconds(settings.buildTimeout))
|
||||||
|
{
|
||||||
|
goal->timedOut(Error(
|
||||||
|
"%1% timed out after %2% seconds",
|
||||||
|
goal->getName(), settings.buildTimeout));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!waitingForAWhile.empty() && lastWokenUp + std::chrono::seconds(settings.pollInterval) <= after) {
|
||||||
|
lastWokenUp = after;
|
||||||
|
for (auto & i : waitingForAWhile) {
|
||||||
|
GoalPtr goal = i.lock();
|
||||||
|
if (goal) wakeUp(goal);
|
||||||
|
}
|
||||||
|
waitingForAWhile.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
unsigned int Worker::exitStatus()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* 1100100
|
||||||
|
* ^^^^
|
||||||
|
* |||`- timeout
|
||||||
|
* ||`-- output hash mismatch
|
||||||
|
* |`--- build failure
|
||||||
|
* `---- not deterministic
|
||||||
|
*/
|
||||||
|
unsigned int mask = 0;
|
||||||
|
bool buildFailure = permanentFailure || timedOut || hashMismatch;
|
||||||
|
if (buildFailure)
|
||||||
|
mask |= 0x04; // 100
|
||||||
|
if (timedOut)
|
||||||
|
mask |= 0x01; // 101
|
||||||
|
if (hashMismatch)
|
||||||
|
mask |= 0x02; // 102
|
||||||
|
if (checkMismatch) {
|
||||||
|
mask |= 0x08; // 104
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mask)
|
||||||
|
mask |= 0x60;
|
||||||
|
return mask ? mask : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool Worker::pathContentsGood(const StorePath & path)
|
||||||
|
{
|
||||||
|
auto i = pathContentsGoodCache.find(path);
|
||||||
|
if (i != pathContentsGoodCache.end()) return i->second;
|
||||||
|
printInfo("checking path '%s'...", store.printStorePath(path));
|
||||||
|
auto info = store.queryPathInfo(path);
|
||||||
|
bool res;
|
||||||
|
if (!pathExists(store.printStorePath(path)))
|
||||||
|
res = false;
|
||||||
|
else {
|
||||||
|
HashResult current = hashPath(info->narHash.type, store.printStorePath(path));
|
||||||
|
Hash nullHash(htSHA256);
|
||||||
|
res = info->narHash == nullHash || info->narHash == current.first;
|
||||||
|
}
|
||||||
|
pathContentsGoodCache.insert_or_assign(path, res);
|
||||||
|
if (!res)
|
||||||
|
logError({
|
||||||
|
.name = "Corrupted path",
|
||||||
|
.hint = hintfmt("path '%s' is corrupted or missing!", store.printStorePath(path))
|
||||||
|
});
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Worker::markContentsGood(const StorePath & path)
|
||||||
|
{
|
||||||
|
pathContentsGoodCache.insert_or_assign(path, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
93
src/libstore/lock.cc
Normal file
93
src/libstore/lock.cc
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
#include "lock.hh"
|
||||||
|
#include "globals.hh"
|
||||||
|
#include "pathlocks.hh"
|
||||||
|
|
||||||
|
#include <grp.h>
|
||||||
|
#include <pwd.h>
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
UserLock::UserLock()
|
||||||
|
{
|
||||||
|
assert(settings.buildUsersGroup != "");
|
||||||
|
createDirs(settings.nixStateDir + "/userpool");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UserLock::findFreeUser() {
|
||||||
|
if (enabled()) return true;
|
||||||
|
|
||||||
|
/* Get the members of the build-users-group. */
|
||||||
|
struct group * gr = getgrnam(settings.buildUsersGroup.get().c_str());
|
||||||
|
if (!gr)
|
||||||
|
throw Error("the group '%1%' specified in 'build-users-group' does not exist",
|
||||||
|
settings.buildUsersGroup);
|
||||||
|
gid = gr->gr_gid;
|
||||||
|
|
||||||
|
/* Copy the result of getgrnam. */
|
||||||
|
Strings users;
|
||||||
|
for (char * * p = gr->gr_mem; *p; ++p) {
|
||||||
|
debug("found build user '%1%'", *p);
|
||||||
|
users.push_back(*p);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (users.empty())
|
||||||
|
throw Error("the build users group '%1%' has no members",
|
||||||
|
settings.buildUsersGroup);
|
||||||
|
|
||||||
|
/* Find a user account that isn't currently in use for another
|
||||||
|
build. */
|
||||||
|
for (auto & i : users) {
|
||||||
|
debug("trying user '%1%'", i);
|
||||||
|
|
||||||
|
struct passwd * pw = getpwnam(i.c_str());
|
||||||
|
if (!pw)
|
||||||
|
throw Error("the user '%1%' in the group '%2%' does not exist",
|
||||||
|
i, settings.buildUsersGroup);
|
||||||
|
|
||||||
|
|
||||||
|
fnUserLock = (format("%1%/userpool/%2%") % settings.nixStateDir % pw->pw_uid).str();
|
||||||
|
|
||||||
|
AutoCloseFD fd = open(fnUserLock.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0600);
|
||||||
|
if (!fd)
|
||||||
|
throw SysError("opening user lock '%1%'", fnUserLock);
|
||||||
|
|
||||||
|
if (lockFile(fd.get(), ltWrite, false)) {
|
||||||
|
fdUserLock = std::move(fd);
|
||||||
|
user = i;
|
||||||
|
uid = pw->pw_uid;
|
||||||
|
|
||||||
|
/* Sanity check... */
|
||||||
|
if (uid == getuid() || uid == geteuid())
|
||||||
|
throw Error("the Nix user should not be a member of '%1%'",
|
||||||
|
settings.buildUsersGroup);
|
||||||
|
|
||||||
|
#if __linux__
|
||||||
|
/* Get the list of supplementary groups of this build user. This
|
||||||
|
is usually either empty or contains a group such as "kvm". */
|
||||||
|
supplementaryGIDs.resize(10);
|
||||||
|
int ngroups = supplementaryGIDs.size();
|
||||||
|
int err = getgrouplist(pw->pw_name, pw->pw_gid,
|
||||||
|
supplementaryGIDs.data(), &ngroups);
|
||||||
|
if (err == -1)
|
||||||
|
throw Error("failed to get list of supplementary groups for '%1%'", pw->pw_name);
|
||||||
|
|
||||||
|
supplementaryGIDs.resize(ngroups);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
isEnabled = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UserLock::kill()
|
||||||
|
{
|
||||||
|
killUser(uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
37
src/libstore/lock.hh
Normal file
37
src/libstore/lock.hh
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "sync.hh"
|
||||||
|
#include "types.hh"
|
||||||
|
#include "util.hh"
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
class UserLock
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
Path fnUserLock;
|
||||||
|
AutoCloseFD fdUserLock;
|
||||||
|
|
||||||
|
bool isEnabled = false;
|
||||||
|
string user;
|
||||||
|
uid_t uid = 0;
|
||||||
|
gid_t gid = 0;
|
||||||
|
std::vector<gid_t> supplementaryGIDs;
|
||||||
|
|
||||||
|
public:
|
||||||
|
UserLock();
|
||||||
|
|
||||||
|
void kill();
|
||||||
|
|
||||||
|
string getUser() { return user; }
|
||||||
|
uid_t getUID() { assert(uid); return uid; }
|
||||||
|
uid_t getGID() { assert(gid); return gid; }
|
||||||
|
std::vector<gid_t> getSupplementaryGIDs() { return supplementaryGIDs; }
|
||||||
|
|
||||||
|
bool findFreeUser();
|
||||||
|
|
||||||
|
bool enabled() { return isEnabled; }
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in a new issue