mirror of
https://github.com/NixOS/nix.git
synced 2025-12-02 07:00:59 +01:00
Merge remote-tracking branch 'origin/2.30-maintenance' into sync-2.30.0
This commit is contained in:
commit
175406c313
284 changed files with 9123 additions and 4178 deletions
1257
src/libstore/build/derivation-building-goal.cc
Normal file
1257
src/libstore/build/derivation-building-goal.cc
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -12,7 +12,7 @@ DrvOutputSubstitutionGoal::DrvOutputSubstitutionGoal(
|
|||
Worker & worker,
|
||||
RepairFlag repair,
|
||||
std::optional<ContentAddress> ca)
|
||||
: Goal(worker)
|
||||
: Goal(worker, init())
|
||||
, id(id)
|
||||
{
|
||||
name = fmt("substitution of '%s'", id.to_string());
|
||||
|
|
@ -139,7 +139,7 @@ Goal::Co DrvOutputSubstitutionGoal::realisationFetched(Goals waitees, std::share
|
|||
|
||||
if (nrFailed > 0) {
|
||||
debug("The output path of the derivation output '%s' could not be substituted", id.to_string());
|
||||
co_return amDone(nrNoSubstituters > 0 || nrIncompleteClosure > 0 ? ecIncompleteClosure : ecFailed);
|
||||
co_return amDone(nrNoSubstituters > 0 ? ecNoSubstituters : ecFailed);
|
||||
}
|
||||
|
||||
worker.store.registerDrvOutput(*outputInfo);
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ void Store::buildPaths(const std::vector<DerivedPath> & reqs, BuildMode buildMod
|
|||
if (i->exitCode != Goal::ecSuccess) {
|
||||
#ifndef _WIN32 // TODO Enable building on Windows
|
||||
if (auto i2 = dynamic_cast<DerivationGoal *>(i.get()))
|
||||
failed.insert(printStorePath(i2->drvPath));
|
||||
failed.insert(i2->drvReq->to_string(*this));
|
||||
else
|
||||
#endif
|
||||
if (auto i2 = dynamic_cast<PathSubstitutionGoal *>(i.get()))
|
||||
|
|
|
|||
|
|
@ -151,11 +151,11 @@ Goal::Done Goal::amDone(ExitCode result, std::optional<Error> ex)
|
|||
trace("done");
|
||||
assert(top_co);
|
||||
assert(exitCode == ecBusy);
|
||||
assert(result == ecSuccess || result == ecFailed || result == ecNoSubstituters || result == ecIncompleteClosure);
|
||||
assert(result == ecSuccess || result == ecFailed || result == ecNoSubstituters);
|
||||
exitCode = result;
|
||||
|
||||
if (ex) {
|
||||
if (!waiters.empty())
|
||||
if (!preserveException && !waiters.empty())
|
||||
logError(ex->info());
|
||||
else
|
||||
this->ex = std::move(*ex);
|
||||
|
|
@ -170,12 +170,10 @@ Goal::Done Goal::amDone(ExitCode result, std::optional<Error> ex)
|
|||
|
||||
goal->trace(fmt("waitee '%s' done; %d left", name, goal->waitees.size()));
|
||||
|
||||
if (result == ecFailed || result == ecNoSubstituters || result == ecIncompleteClosure) ++goal->nrFailed;
|
||||
if (result == ecFailed || result == ecNoSubstituters) ++goal->nrFailed;
|
||||
|
||||
if (result == ecNoSubstituters) ++goal->nrNoSubstituters;
|
||||
|
||||
if (result == ecIncompleteClosure) ++goal->nrIncompleteClosure;
|
||||
|
||||
if (goal->waitees.empty()) {
|
||||
worker.wakeUp(goal);
|
||||
} else if (result == ecFailed && !settings.keepGoing) {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
namespace nix {
|
||||
|
||||
PathSubstitutionGoal::PathSubstitutionGoal(const StorePath & storePath, Worker & worker, RepairFlag repair, std::optional<ContentAddress> ca)
|
||||
: Goal(worker)
|
||||
: Goal(worker, init())
|
||||
, storePath(storePath)
|
||||
, repair(repair)
|
||||
, ca(ca)
|
||||
|
|
@ -181,7 +181,7 @@ Goal::Co PathSubstitutionGoal::tryToRun(StorePath subPath, nix::ref<Store> sub,
|
|||
|
||||
if (nrFailed > 0) {
|
||||
co_return done(
|
||||
nrNoSubstituters > 0 || nrIncompleteClosure > 0 ? ecIncompleteClosure : ecFailed,
|
||||
nrNoSubstituters > 0 ? ecNoSubstituters : ecFailed,
|
||||
BuildResult::DependencyFailed,
|
||||
fmt("some references of path '%s' could not be realised", worker.store.printStorePath(storePath)));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include "nix/store/build/substitution-goal.hh"
|
||||
#include "nix/store/build/drv-output-substitution-goal.hh"
|
||||
#include "nix/store/build/derivation-goal.hh"
|
||||
#include "nix/store/build/derivation-building-goal.hh"
|
||||
#ifndef _WIN32 // TODO Enable building on Windows
|
||||
# include "nix/store/build/hook-instance.hh"
|
||||
#endif
|
||||
|
|
@ -41,13 +42,23 @@ Worker::~Worker()
|
|||
assert(expectedNarSize == 0);
|
||||
}
|
||||
|
||||
template<class G, typename... Args>
|
||||
std::shared_ptr<G> Worker::initGoalIfNeeded(std::weak_ptr<G> & goal_weak, Args && ...args)
|
||||
{
|
||||
if (auto goal = goal_weak.lock()) return goal;
|
||||
|
||||
auto goal = std::make_shared<G>(args...);
|
||||
goal_weak = goal;
|
||||
wakeUp(goal);
|
||||
return goal;
|
||||
}
|
||||
|
||||
std::shared_ptr<DerivationGoal> Worker::makeDerivationGoalCommon(
|
||||
const StorePath & drvPath,
|
||||
ref<const SingleDerivedPath> drvReq,
|
||||
const OutputsSpec & wantedOutputs,
|
||||
std::function<std::shared_ptr<DerivationGoal>()> mkDrvGoal)
|
||||
{
|
||||
std::weak_ptr<DerivationGoal> & goal_weak = derivationGoals[drvPath];
|
||||
std::weak_ptr<DerivationGoal> & goal_weak = derivationGoals.ensureSlot(*drvReq).value;
|
||||
std::shared_ptr<DerivationGoal> goal = goal_weak.lock();
|
||||
if (!goal) {
|
||||
goal = mkDrvGoal();
|
||||
|
|
@ -60,29 +71,30 @@ std::shared_ptr<DerivationGoal> Worker::makeDerivationGoalCommon(
|
|||
}
|
||||
|
||||
|
||||
std::shared_ptr<DerivationGoal> Worker::makeDerivationGoal(const StorePath & drvPath,
|
||||
std::shared_ptr<DerivationGoal> Worker::makeDerivationGoal(ref<const SingleDerivedPath> drvReq,
|
||||
const OutputsSpec & wantedOutputs, BuildMode buildMode)
|
||||
{
|
||||
return makeDerivationGoalCommon(drvPath, wantedOutputs, [&]() -> std::shared_ptr<DerivationGoal> {
|
||||
return std::make_shared<DerivationGoal>(drvPath, wantedOutputs, *this, buildMode);
|
||||
return makeDerivationGoalCommon(drvReq, wantedOutputs, [&]() -> std::shared_ptr<DerivationGoal> {
|
||||
return std::make_shared<DerivationGoal>(drvReq, wantedOutputs, *this, buildMode);
|
||||
});
|
||||
}
|
||||
|
||||
std::shared_ptr<DerivationGoal> Worker::makeBasicDerivationGoal(const StorePath & drvPath,
|
||||
const BasicDerivation & drv, const OutputsSpec & wantedOutputs, BuildMode buildMode)
|
||||
{
|
||||
return makeDerivationGoalCommon(drvPath, wantedOutputs, [&]() -> std::shared_ptr<DerivationGoal> {
|
||||
return makeDerivationGoalCommon(makeConstantStorePathRef(drvPath), wantedOutputs, [&]() -> std::shared_ptr<DerivationGoal> {
|
||||
return std::make_shared<DerivationGoal>(drvPath, drv, wantedOutputs, *this, buildMode);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
std::shared_ptr<PathSubstitutionGoal> Worker::makePathSubstitutionGoal(const StorePath & path, RepairFlag repair, std::optional<ContentAddress> ca)
|
||||
std::shared_ptr<DerivationBuildingGoal> Worker::makeDerivationBuildingGoal(const StorePath & drvPath,
|
||||
const Derivation & drv, BuildMode buildMode)
|
||||
{
|
||||
std::weak_ptr<PathSubstitutionGoal> & goal_weak = substitutionGoals[path];
|
||||
std::weak_ptr<DerivationBuildingGoal> & goal_weak = derivationBuildingGoals[drvPath];
|
||||
auto goal = goal_weak.lock(); // FIXME
|
||||
if (!goal) {
|
||||
goal = std::make_shared<PathSubstitutionGoal>(path, *this, repair, ca);
|
||||
goal = std::make_shared<DerivationBuildingGoal>(drvPath, drv, *this, buildMode);
|
||||
goal_weak = goal;
|
||||
wakeUp(goal);
|
||||
}
|
||||
|
|
@ -90,16 +102,15 @@ std::shared_ptr<PathSubstitutionGoal> Worker::makePathSubstitutionGoal(const Sto
|
|||
}
|
||||
|
||||
|
||||
std::shared_ptr<PathSubstitutionGoal> Worker::makePathSubstitutionGoal(const StorePath & path, RepairFlag repair, std::optional<ContentAddress> ca)
|
||||
{
|
||||
return initGoalIfNeeded(substitutionGoals[path], path, *this, repair, ca);
|
||||
}
|
||||
|
||||
|
||||
std::shared_ptr<DrvOutputSubstitutionGoal> Worker::makeDrvOutputSubstitutionGoal(const DrvOutput& id, RepairFlag repair, std::optional<ContentAddress> ca)
|
||||
{
|
||||
std::weak_ptr<DrvOutputSubstitutionGoal> & goal_weak = drvOutputSubstitutionGoals[id];
|
||||
auto goal = goal_weak.lock(); // FIXME
|
||||
if (!goal) {
|
||||
goal = std::make_shared<DrvOutputSubstitutionGoal>(id, *this, repair, ca);
|
||||
goal_weak = goal;
|
||||
wakeUp(goal);
|
||||
}
|
||||
return goal;
|
||||
return initGoalIfNeeded(drvOutputSubstitutionGoals[id], id, *this, repair, ca);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -107,10 +118,7 @@ GoalPtr Worker::makeGoal(const DerivedPath & req, BuildMode buildMode)
|
|||
{
|
||||
return std::visit(overloaded {
|
||||
[&](const DerivedPath::Built & bfd) -> GoalPtr {
|
||||
if (auto bop = std::get_if<DerivedPath::Opaque>(&*bfd.drvPath))
|
||||
return makeDerivationGoal(bop->path, bfd.outputs, buildMode);
|
||||
else
|
||||
throw UnimplementedError("Building dynamic derivations in one shot is not yet implemented.");
|
||||
return makeDerivationGoal(bfd.drvPath, bfd.outputs, buildMode);
|
||||
},
|
||||
[&](const DerivedPath::Opaque & bo) -> GoalPtr {
|
||||
return makePathSubstitutionGoal(bo.path, buildMode == bmRepair ? Repair : NoRepair);
|
||||
|
|
@ -119,27 +127,48 @@ GoalPtr Worker::makeGoal(const DerivedPath & req, BuildMode buildMode)
|
|||
}
|
||||
|
||||
|
||||
template<typename K, typename V, typename F>
|
||||
static void cullMap(std::map<K, V> & goalMap, F f)
|
||||
{
|
||||
for (auto i = goalMap.begin(); i != goalMap.end();)
|
||||
if (!f(i->second))
|
||||
i = goalMap.erase(i);
|
||||
else ++i;
|
||||
}
|
||||
|
||||
|
||||
template<typename K, typename G>
|
||||
static void removeGoal(std::shared_ptr<G> goal, std::map<K, std::weak_ptr<G>> & goalMap)
|
||||
{
|
||||
/* !!! inefficient */
|
||||
for (auto i = goalMap.begin();
|
||||
i != goalMap.end(); )
|
||||
if (i->second.lock() == goal) {
|
||||
auto j = i; ++j;
|
||||
goalMap.erase(i);
|
||||
i = j;
|
||||
}
|
||||
else ++i;
|
||||
cullMap(goalMap, [&](const std::weak_ptr<G> & gp) -> bool {
|
||||
return gp.lock() != goal;
|
||||
});
|
||||
}
|
||||
|
||||
template<typename K>
|
||||
static void removeGoal(std::shared_ptr<DerivationGoal> goal, std::map<K, DerivedPathMap<std::weak_ptr<DerivationGoal>>::ChildNode> & goalMap);
|
||||
|
||||
template<typename K>
|
||||
static void removeGoal(std::shared_ptr<DerivationGoal> goal, std::map<K, DerivedPathMap<std::weak_ptr<DerivationGoal>>::ChildNode> & goalMap)
|
||||
{
|
||||
/* !!! inefficient */
|
||||
cullMap(goalMap, [&](DerivedPathMap<std::weak_ptr<DerivationGoal>>::ChildNode & node) -> bool {
|
||||
if (node.value.lock() == goal)
|
||||
node.value.reset();
|
||||
removeGoal(goal, node.childMap);
|
||||
return !node.value.expired() || !node.childMap.empty();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
void Worker::removeGoal(GoalPtr goal)
|
||||
{
|
||||
if (auto drvGoal = std::dynamic_pointer_cast<DerivationGoal>(goal))
|
||||
nix::removeGoal(drvGoal, derivationGoals);
|
||||
else
|
||||
if (auto subGoal = std::dynamic_pointer_cast<PathSubstitutionGoal>(goal))
|
||||
nix::removeGoal(drvGoal, derivationGoals.map);
|
||||
else if (auto drvBuildingGoal = std::dynamic_pointer_cast<DerivationBuildingGoal>(goal))
|
||||
nix::removeGoal(drvBuildingGoal, derivationBuildingGoals);
|
||||
else if (auto subGoal = std::dynamic_pointer_cast<PathSubstitutionGoal>(goal))
|
||||
nix::removeGoal(subGoal, substitutionGoals);
|
||||
else if (auto subGoal = std::dynamic_pointer_cast<DrvOutputSubstitutionGoal>(goal))
|
||||
nix::removeGoal(subGoal, drvOutputSubstitutionGoals);
|
||||
|
|
@ -202,6 +231,9 @@ void Worker::childStarted(GoalPtr goal, const std::set<MuxablePipePollState::Com
|
|||
case JobCategory::Build:
|
||||
nrLocalBuilds++;
|
||||
break;
|
||||
case JobCategory::Administration:
|
||||
/* Intentionally not limited, see docs */
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
|
@ -225,6 +257,9 @@ void Worker::childTerminated(Goal * goal, bool wakeSleepers)
|
|||
assert(nrLocalBuilds > 0);
|
||||
nrLocalBuilds--;
|
||||
break;
|
||||
case JobCategory::Administration:
|
||||
/* Intentionally not limited, see docs */
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
|
@ -279,7 +314,7 @@ void Worker::run(const Goals & _topGoals)
|
|||
topGoals.insert(i);
|
||||
if (auto goal = dynamic_cast<DerivationGoal *>(i.get())) {
|
||||
topPaths.push_back(DerivedPath::Built {
|
||||
.drvPath = makeConstantStorePathRef(goal->drvPath),
|
||||
.drvPath = goal->drvReq,
|
||||
.outputs = goal->wantedOutputs,
|
||||
});
|
||||
} else
|
||||
|
|
@ -289,9 +324,7 @@ void Worker::run(const Goals & _topGoals)
|
|||
}
|
||||
|
||||
/* Call queryMissing() to efficiently query substitutes. */
|
||||
StorePathSet willBuild, willSubstitute, unknown;
|
||||
uint64_t downloadSize, narSize;
|
||||
store.queryMissing(topPaths, willBuild, willSubstitute, unknown, downloadSize, narSize);
|
||||
store.queryMissing(topPaths);
|
||||
|
||||
debug("entered goal loop");
|
||||
|
||||
|
|
@ -327,23 +360,14 @@ void Worker::run(const Goals & _topGoals)
|
|||
else if (awake.empty() && 0U == settings.maxBuildJobs) {
|
||||
if (getMachines().empty())
|
||||
throw Error(
|
||||
R"(
|
||||
Unable to start any build;
|
||||
either increase '--max-jobs' or enable remote builds.
|
||||
|
||||
For more information run 'man nix.conf' and search for '/machines'.
|
||||
)"
|
||||
);
|
||||
"Unable to start any build; either increase '--max-jobs' or enable remote builds.\n"
|
||||
"\n"
|
||||
"For more information run 'man nix.conf' and search for '/machines'.");
|
||||
else
|
||||
throw Error(
|
||||
R"(
|
||||
Unable to start any build;
|
||||
remote machines may not have all required system features.
|
||||
|
||||
For more information run 'man nix.conf' and search for '/machines'.
|
||||
)"
|
||||
);
|
||||
|
||||
"Unable to start any build; remote machines may not have all required system features.\n"
|
||||
"\n"
|
||||
"For more information run 'man nix.conf' and search for '/machines'.");
|
||||
} else assert(!awake.empty());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -949,14 +949,12 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
case WorkerProto::Op::QueryMissing: {
|
||||
auto targets = WorkerProto::Serialise<DerivedPaths>::read(*store, rconn);
|
||||
logger->startWork();
|
||||
StorePathSet willBuild, willSubstitute, unknown;
|
||||
uint64_t downloadSize, narSize;
|
||||
store->queryMissing(targets, willBuild, willSubstitute, unknown, downloadSize, narSize);
|
||||
auto missing = store->queryMissing(targets);
|
||||
logger->stopWork();
|
||||
WorkerProto::write(*store, wconn, willBuild);
|
||||
WorkerProto::write(*store, wconn, willSubstitute);
|
||||
WorkerProto::write(*store, wconn, unknown);
|
||||
conn.to << downloadSize << narSize;
|
||||
WorkerProto::write(*store, wconn, missing.willBuild);
|
||||
WorkerProto::write(*store, wconn, missing.willSubstitute);
|
||||
WorkerProto::write(*store, wconn, missing.unknown);
|
||||
conn.to << missing.downloadSize << missing.narSize;
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -412,7 +412,7 @@ Derivation parseDerivation(
|
|||
expect(str, "rvWithVersion(");
|
||||
auto versionS = parseString(str);
|
||||
if (*versionS == "xp-dyn-drv") {
|
||||
// Only verison we have so far
|
||||
// Only version we have so far
|
||||
version = DerivationATermVersion::DynamicDerivations;
|
||||
xpSettings.require(Xp::DynamicDerivations);
|
||||
} else {
|
||||
|
|
@ -553,7 +553,7 @@ static void unparseDerivedPathMapNode(const StoreDirConfig & store, std::string
|
|||
* derivation?
|
||||
*
|
||||
* In other words, does it on the output of derivation that is itself an
|
||||
* ouput of a derivation? This corresponds to a dependency that is an
|
||||
* output of a derivation? This corresponds to a dependency that is an
|
||||
* inductive derived path with more than one layer of
|
||||
* `DerivedPath::Built`.
|
||||
*/
|
||||
|
|
@ -1333,6 +1333,11 @@ nlohmann::json Derivation::toJSON(const StoreDirConfig & store) const
|
|||
res["args"] = args;
|
||||
res["env"] = env;
|
||||
|
||||
if (auto it = env.find("__json"); it != env.end()) {
|
||||
res["env"].erase("__json");
|
||||
res["structuredAttrs"] = nlohmann::json::parse(it->second);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
|
@ -1396,7 +1401,17 @@ Derivation Derivation::fromJSON(
|
|||
res.platform = getString(valueAt(json, "system"));
|
||||
res.builder = getString(valueAt(json, "builder"));
|
||||
res.args = getStringList(valueAt(json, "args"));
|
||||
res.env = getStringMap(valueAt(json, "env"));
|
||||
|
||||
auto envJson = valueAt(json, "env");
|
||||
try {
|
||||
res.env = getStringMap(envJson);
|
||||
} catch (Error & e) {
|
||||
e.addTrace({}, "while reading key 'env'");
|
||||
throw;
|
||||
}
|
||||
|
||||
if (auto structuredAttrs = get(json, "structuredAttrs"))
|
||||
res.env.insert_or_assign("__json", structuredAttrs->dump());
|
||||
|
||||
return res;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ typename DerivedPathMap<V>::ChildNode * DerivedPathMap<V>::findSlot(const Single
|
|||
|
||||
// instantiations
|
||||
|
||||
#include "nix/store/build/derivation-goal.hh"
|
||||
namespace nix {
|
||||
|
||||
template<>
|
||||
|
|
@ -68,4 +69,7 @@ std::strong_ordering DerivedPathMap<StringSet>::ChildNode::operator <=> (
|
|||
template struct DerivedPathMap<StringSet>::ChildNode;
|
||||
template struct DerivedPathMap<StringSet>;
|
||||
|
||||
template struct DerivedPathMap<std::weak_ptr<DerivationGoal>>;
|
||||
|
||||
|
||||
};
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
# include "nix/util/namespaces.hh"
|
||||
# include "nix/util/linux-namespaces.hh"
|
||||
#endif
|
||||
|
||||
#include <unistd.h>
|
||||
|
|
|
|||
|
|
@ -790,7 +790,7 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
|
|||
deleteFromStore(path.to_string());
|
||||
referrersCache.erase(path);
|
||||
} catch (PathInUse &e) {
|
||||
// If we end up here, it's likely a new occurence
|
||||
// If we end up here, it's likely a new occurrence
|
||||
// of https://github.com/NixOS/nix/issues/11923
|
||||
printError("BUG: %s", e.what());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ Settings::Settings()
|
|||
builders = concatStringsSep("\n", ss);
|
||||
}
|
||||
|
||||
#if defined(__linux__) && defined(SANDBOX_SHELL)
|
||||
#if (defined(__linux__) || defined(__FreeBSD__)) && defined(SANDBOX_SHELL)
|
||||
sandboxPaths = tokenizeString<StringSet>("/bin/sh=" SANDBOX_SHELL);
|
||||
#endif
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ struct BuildResult
|
|||
{
|
||||
/**
|
||||
* @note This is directly used in the nix-store --serve protocol.
|
||||
* That means we need to worry about compatability across versions.
|
||||
* That means we need to worry about compatibility across versions.
|
||||
* Therefore, don't remove status codes, and only add new status
|
||||
* codes at the end of the list.
|
||||
*/
|
||||
|
|
|
|||
194
src/libstore/include/nix/store/build/derivation-building-goal.hh
Normal file
194
src/libstore/include/nix/store/build/derivation-building-goal.hh
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
#pragma once
|
||||
///@file
|
||||
|
||||
#include "nix/store/parsed-derivations.hh"
|
||||
#include "nix/store/derivations.hh"
|
||||
#include "nix/store/derivation-options.hh"
|
||||
#include "nix/store/build/derivation-building-misc.hh"
|
||||
#include "nix/store/outputs-spec.hh"
|
||||
#include "nix/store/store-api.hh"
|
||||
#include "nix/store/pathlocks.hh"
|
||||
#include "nix/store/build/goal.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
using std::map;
|
||||
|
||||
#ifndef _WIN32 // TODO enable build hook on Windows
|
||||
struct HookInstance;
|
||||
struct DerivationBuilder;
|
||||
#endif
|
||||
|
||||
typedef enum {rpAccept, rpDecline, rpPostpone} HookReply;
|
||||
|
||||
/** Used internally */
|
||||
void runPostBuildHook(
|
||||
Store & store,
|
||||
Logger & logger,
|
||||
const StorePath & drvPath,
|
||||
const StorePathSet & outputPaths);
|
||||
|
||||
/**
|
||||
* A goal for building some or all of the outputs of a derivation.
|
||||
*/
|
||||
struct DerivationBuildingGoal : public Goal
|
||||
{
|
||||
/** The path of the derivation. */
|
||||
StorePath drvPath;
|
||||
|
||||
/**
|
||||
* The derivation stored at drvPath.
|
||||
*/
|
||||
std::unique_ptr<Derivation> drv;
|
||||
|
||||
std::unique_ptr<StructuredAttrs> parsedDrv;
|
||||
std::unique_ptr<DerivationOptions> drvOptions;
|
||||
|
||||
/**
|
||||
* The remainder is state held during the build.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Locks on (fixed) output paths.
|
||||
*/
|
||||
PathLocks outputLocks;
|
||||
|
||||
/**
|
||||
* All input paths (that is, the union of FS closures of the
|
||||
* immediate input paths).
|
||||
*/
|
||||
StorePathSet inputPaths;
|
||||
|
||||
std::map<std::string, InitialOutput> initialOutputs;
|
||||
|
||||
/**
|
||||
* File descriptor for the log file.
|
||||
*/
|
||||
AutoCloseFD fdLogFile;
|
||||
std::shared_ptr<BufferedSink> logFileSink, logSink;
|
||||
|
||||
/**
|
||||
* Number of bytes received from the builder's stdout/stderr.
|
||||
*/
|
||||
unsigned long logSize;
|
||||
|
||||
/**
|
||||
* The most recent log lines.
|
||||
*/
|
||||
std::list<std::string> logTail;
|
||||
|
||||
std::string currentLogLine;
|
||||
size_t currentLogLinePos = 0; // to handle carriage return
|
||||
|
||||
std::string currentHookLine;
|
||||
|
||||
#ifndef _WIN32 // TODO enable build hook on Windows
|
||||
/**
|
||||
* The build hook.
|
||||
*/
|
||||
std::unique_ptr<HookInstance> hook;
|
||||
|
||||
std::unique_ptr<DerivationBuilder> builder;
|
||||
#endif
|
||||
|
||||
BuildMode buildMode;
|
||||
|
||||
std::unique_ptr<MaintainCount<uint64_t>> mcRunningBuilds;
|
||||
|
||||
std::unique_ptr<Activity> act;
|
||||
|
||||
/**
|
||||
* Activity that denotes waiting for a lock.
|
||||
*/
|
||||
std::unique_ptr<Activity> actLock;
|
||||
|
||||
std::map<ActivityId, Activity> builderActivities;
|
||||
|
||||
/**
|
||||
* The remote machine on which we're building.
|
||||
*/
|
||||
std::string machineName;
|
||||
|
||||
DerivationBuildingGoal(const StorePath & drvPath, const Derivation & drv,
|
||||
Worker & worker,
|
||||
BuildMode buildMode = bmNormal);
|
||||
~DerivationBuildingGoal();
|
||||
|
||||
void timedOut(Error && ex) override;
|
||||
|
||||
std::string key() override;
|
||||
|
||||
/**
|
||||
* The states.
|
||||
*/
|
||||
Co gaveUpOnSubstitution();
|
||||
Co tryToBuild();
|
||||
Co hookDone();
|
||||
|
||||
/**
|
||||
* Is the build hook willing to perform the build?
|
||||
*/
|
||||
HookReply tryBuildHook();
|
||||
|
||||
/**
|
||||
* Open a log file and a pipe to it.
|
||||
*/
|
||||
Path openLogFile();
|
||||
|
||||
/**
|
||||
* Close the log file.
|
||||
*/
|
||||
void closeLogFile();
|
||||
|
||||
bool isReadDesc(Descriptor fd);
|
||||
|
||||
/**
|
||||
* Callback used by the worker to write to the log.
|
||||
*/
|
||||
void handleChildOutput(Descriptor fd, std::string_view data) override;
|
||||
void handleEOF(Descriptor fd) override;
|
||||
void flushLine();
|
||||
|
||||
/**
|
||||
* Wrappers around the corresponding Store methods that first consult the
|
||||
* derivation. This is currently needed because when there is no drv file
|
||||
* there also is no DB entry.
|
||||
*/
|
||||
std::map<std::string, std::optional<StorePath>> queryPartialDerivationOutputMap();
|
||||
|
||||
/**
|
||||
* Update 'initialOutputs' to determine the current status of the
|
||||
* outputs of the derivation. Also returns a Boolean denoting
|
||||
* whether all outputs are valid and non-corrupt, and a
|
||||
* 'SingleDrvOutputs' structure containing the valid outputs.
|
||||
*/
|
||||
std::pair<bool, SingleDrvOutputs> checkPathValidity();
|
||||
|
||||
/**
|
||||
* Aborts if any output is not valid or corrupt, and otherwise
|
||||
* returns a 'SingleDrvOutputs' structure containing all outputs.
|
||||
*/
|
||||
SingleDrvOutputs assertPathValidity();
|
||||
|
||||
/**
|
||||
* Forcibly kill the child process, if any.
|
||||
*/
|
||||
void killChild();
|
||||
|
||||
void started();
|
||||
|
||||
Done done(
|
||||
BuildResult::Status status,
|
||||
SingleDrvOutputs builtOutputs = {},
|
||||
std::optional<Error> ex = {});
|
||||
|
||||
void appendLogTailErrorMsg(std::string & msg);
|
||||
|
||||
StorePathSet exportReferences(const StorePathSet & storePaths);
|
||||
|
||||
JobCategory jobCategory() const override {
|
||||
return JobCategory::Build;
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
#pragma once
|
||||
/**
|
||||
* @file Misc type defitions for both local building and remote (RPC building)
|
||||
* @file Misc type definitions for both local building and remote (RPC building)
|
||||
*/
|
||||
|
||||
#include "nix/util/hash.hh"
|
||||
|
|
|
|||
|
|
@ -14,13 +14,6 @@ namespace nix {
|
|||
|
||||
using std::map;
|
||||
|
||||
#ifndef _WIN32 // TODO enable build hook on Windows
|
||||
struct HookInstance;
|
||||
struct DerivationBuilder;
|
||||
#endif
|
||||
|
||||
typedef enum {rpAccept, rpDecline, rpPostpone} HookReply;
|
||||
|
||||
/** Used internally */
|
||||
void runPostBuildHook(
|
||||
Store & store,
|
||||
|
|
@ -33,13 +26,8 @@ void runPostBuildHook(
|
|||
*/
|
||||
struct DerivationGoal : public Goal
|
||||
{
|
||||
/**
|
||||
* Whether to use an on-disk .drv file.
|
||||
*/
|
||||
bool useDerivation;
|
||||
|
||||
/** The path of the derivation. */
|
||||
StorePath drvPath;
|
||||
ref<const SingleDerivedPath> drvReq;
|
||||
|
||||
/**
|
||||
* The specific outputs that we need to build.
|
||||
|
|
@ -54,7 +42,7 @@ struct DerivationGoal : public Goal
|
|||
* The goal state machine is progressing based on the current value of
|
||||
* `wantedOutputs. No actions are needed.
|
||||
*/
|
||||
OutputsUnmodifedDontNeed,
|
||||
OutputsUnmodifiedDontNeed,
|
||||
/**
|
||||
* `wantedOutputs` has been extended, but the state machine is
|
||||
* proceeding according to its old value, so we need to restart.
|
||||
|
|
@ -71,116 +59,32 @@ struct DerivationGoal : public Goal
|
|||
/**
|
||||
* Whether additional wanted outputs have been added.
|
||||
*/
|
||||
NeedRestartForMoreOutputs needRestart = NeedRestartForMoreOutputs::OutputsUnmodifedDontNeed;
|
||||
NeedRestartForMoreOutputs needRestart = NeedRestartForMoreOutputs::OutputsUnmodifiedDontNeed;
|
||||
|
||||
/**
|
||||
* See `retrySubstitution`; just for that field.
|
||||
*/
|
||||
enum RetrySubstitution {
|
||||
/**
|
||||
* No issues have yet arose, no need to restart.
|
||||
*/
|
||||
NoNeed,
|
||||
/**
|
||||
* Something failed and there is an incomplete closure. Let's retry
|
||||
* substituting.
|
||||
*/
|
||||
YesNeed,
|
||||
/**
|
||||
* We are current or have already retried substitution, and whether or
|
||||
* not something goes wrong we will not retry again.
|
||||
*/
|
||||
AlreadyRetried,
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether to retry substituting the outputs after building the
|
||||
* inputs. This is done in case of an incomplete closure.
|
||||
*/
|
||||
RetrySubstitution retrySubstitution = RetrySubstitution::NoNeed;
|
||||
|
||||
/**
|
||||
* The derivation stored at drvPath.
|
||||
* The derivation stored at `drvReq`.
|
||||
*/
|
||||
std::unique_ptr<Derivation> drv;
|
||||
|
||||
std::unique_ptr<StructuredAttrs> parsedDrv;
|
||||
std::unique_ptr<DerivationOptions> drvOptions;
|
||||
|
||||
/**
|
||||
* The remainder is state held during the build.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Locks on (fixed) output paths.
|
||||
*/
|
||||
PathLocks outputLocks;
|
||||
|
||||
/**
|
||||
* All input paths (that is, the union of FS closures of the
|
||||
* immediate input paths).
|
||||
*/
|
||||
StorePathSet inputPaths;
|
||||
|
||||
std::map<std::string, InitialOutput> initialOutputs;
|
||||
|
||||
/**
|
||||
* File descriptor for the log file.
|
||||
*/
|
||||
AutoCloseFD fdLogFile;
|
||||
std::shared_ptr<BufferedSink> logFileSink, logSink;
|
||||
|
||||
/**
|
||||
* Number of bytes received from the builder's stdout/stderr.
|
||||
*/
|
||||
unsigned long logSize;
|
||||
|
||||
/**
|
||||
* The most recent log lines.
|
||||
*/
|
||||
std::list<std::string> logTail;
|
||||
|
||||
std::string currentLogLine;
|
||||
size_t currentLogLinePos = 0; // to handle carriage return
|
||||
|
||||
std::string currentHookLine;
|
||||
|
||||
#ifndef _WIN32 // TODO enable build hook on Windows
|
||||
/**
|
||||
* The build hook.
|
||||
*/
|
||||
std::unique_ptr<HookInstance> hook;
|
||||
|
||||
std::unique_ptr<DerivationBuilder> builder;
|
||||
#endif
|
||||
|
||||
BuildMode buildMode;
|
||||
|
||||
std::unique_ptr<MaintainCount<uint64_t>> mcExpectedBuilds, mcRunningBuilds;
|
||||
std::unique_ptr<MaintainCount<uint64_t>> mcExpectedBuilds;
|
||||
|
||||
std::unique_ptr<Activity> act;
|
||||
|
||||
/**
|
||||
* Activity that denotes waiting for a lock.
|
||||
*/
|
||||
std::unique_ptr<Activity> actLock;
|
||||
|
||||
std::map<ActivityId, Activity> builderActivities;
|
||||
|
||||
/**
|
||||
* The remote machine on which we're building.
|
||||
*/
|
||||
std::string machineName;
|
||||
|
||||
DerivationGoal(const StorePath & drvPath,
|
||||
DerivationGoal(ref<const SingleDerivedPath> drvReq,
|
||||
const OutputsSpec & wantedOutputs, Worker & worker,
|
||||
BuildMode buildMode = bmNormal);
|
||||
DerivationGoal(const StorePath & drvPath, const BasicDerivation & drv,
|
||||
const OutputsSpec & wantedOutputs, Worker & worker,
|
||||
BuildMode buildMode = bmNormal);
|
||||
~DerivationGoal();
|
||||
~DerivationGoal() = default;
|
||||
|
||||
void timedOut(Error && ex) override;
|
||||
void timedOut(Error && ex) override { unreachable(); };
|
||||
|
||||
std::string key() override;
|
||||
|
||||
|
|
@ -192,43 +96,16 @@ struct DerivationGoal : public Goal
|
|||
/**
|
||||
* The states.
|
||||
*/
|
||||
Co init() override;
|
||||
Co haveDerivation();
|
||||
Co gaveUpOnSubstitution();
|
||||
Co tryToBuild();
|
||||
Co hookDone();
|
||||
|
||||
/**
|
||||
* Is the build hook willing to perform the build?
|
||||
*/
|
||||
HookReply tryBuildHook();
|
||||
|
||||
/**
|
||||
* Open a log file and a pipe to it.
|
||||
*/
|
||||
Path openLogFile();
|
||||
|
||||
/**
|
||||
* Close the log file.
|
||||
*/
|
||||
void closeLogFile();
|
||||
|
||||
bool isReadDesc(Descriptor fd);
|
||||
|
||||
/**
|
||||
* Callback used by the worker to write to the log.
|
||||
*/
|
||||
void handleChildOutput(Descriptor fd, std::string_view data) override;
|
||||
void handleEOF(Descriptor fd) override;
|
||||
void flushLine();
|
||||
Co loadDerivation();
|
||||
Co haveDerivation(StorePath drvPath);
|
||||
|
||||
/**
|
||||
* Wrappers around the corresponding Store methods that first consult the
|
||||
* derivation. This is currently needed because when there is no drv file
|
||||
* there also is no DB entry.
|
||||
*/
|
||||
std::map<std::string, std::optional<StorePath>> queryPartialDerivationOutputMap();
|
||||
OutputPathMap queryDerivationOutputMap();
|
||||
std::map<std::string, std::optional<StorePath>> queryPartialDerivationOutputMap(const StorePath & drvPath);
|
||||
OutputPathMap queryDerivationOutputMap(const StorePath & drvPath);
|
||||
|
||||
/**
|
||||
* Update 'initialOutputs' to determine the current status of the
|
||||
|
|
@ -236,34 +113,24 @@ struct DerivationGoal : public Goal
|
|||
* whether all outputs are valid and non-corrupt, and a
|
||||
* 'SingleDrvOutputs' structure containing the valid outputs.
|
||||
*/
|
||||
std::pair<bool, SingleDrvOutputs> checkPathValidity();
|
||||
std::pair<bool, SingleDrvOutputs> checkPathValidity(const StorePath & drvPath);
|
||||
|
||||
/**
|
||||
* Aborts if any output is not valid or corrupt, and otherwise
|
||||
* returns a 'SingleDrvOutputs' structure containing all outputs.
|
||||
*/
|
||||
SingleDrvOutputs assertPathValidity();
|
||||
SingleDrvOutputs assertPathValidity(const StorePath & drvPath);
|
||||
|
||||
/**
|
||||
* Forcibly kill the child process, if any.
|
||||
*/
|
||||
void killChild();
|
||||
|
||||
Co repairClosure();
|
||||
|
||||
void started();
|
||||
Co repairClosure(StorePath drvPath);
|
||||
|
||||
Done done(
|
||||
const StorePath & drvPath,
|
||||
BuildResult::Status status,
|
||||
SingleDrvOutputs builtOutputs = {},
|
||||
std::optional<Error> ex = {});
|
||||
|
||||
void appendLogTailErrorMsg(std::string & msg);
|
||||
|
||||
StorePathSet exportReferences(const StorePathSet & storePaths);
|
||||
|
||||
JobCategory jobCategory() const override {
|
||||
return JobCategory::Build;
|
||||
return JobCategory::Administration;
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ public:
|
|||
typedef void (DrvOutputSubstitutionGoal::*GoalState)();
|
||||
GoalState state;
|
||||
|
||||
Co init() override;
|
||||
Co init();
|
||||
Co realisationFetched(Goals waitees, std::shared_ptr<const Realisation> outputInfo, nix::ref<nix::Store> sub);
|
||||
|
||||
void timedOut(Error && ex) override { unreachable(); };
|
||||
|
|
|
|||
|
|
@ -50,6 +50,16 @@ enum struct JobCategory {
|
|||
* A substitution an arbitrary store object; it will use network resources.
|
||||
*/
|
||||
Substitution,
|
||||
/**
|
||||
* A goal that does no "real" work by itself, and just exists to depend on
|
||||
* other goals which *do* do real work. These goals therefore are not
|
||||
* limited.
|
||||
*
|
||||
* These goals cannot infinitely create themselves, so there is no risk of
|
||||
* a "fork bomb" type situation (which would be a problem even though the
|
||||
* goal do no real work) either.
|
||||
*/
|
||||
Administration,
|
||||
};
|
||||
|
||||
struct Goal : public std::enable_shared_from_this<Goal>
|
||||
|
|
@ -61,7 +71,7 @@ private:
|
|||
Goals waitees;
|
||||
|
||||
public:
|
||||
typedef enum {ecBusy, ecSuccess, ecFailed, ecNoSubstituters, ecIncompleteClosure} ExitCode;
|
||||
typedef enum {ecBusy, ecSuccess, ecFailed, ecNoSubstituters} ExitCode;
|
||||
|
||||
/**
|
||||
* Backlink to the worker.
|
||||
|
|
@ -85,12 +95,6 @@ public:
|
|||
*/
|
||||
size_t nrNoSubstituters = 0;
|
||||
|
||||
/**
|
||||
* Number of substitution goals we are/were waiting for that
|
||||
* failed because they had unsubstitutable references.
|
||||
*/
|
||||
size_t nrIncompleteClosure = 0;
|
||||
|
||||
/**
|
||||
* Name of this goal for debugging purposes.
|
||||
*/
|
||||
|
|
@ -344,17 +348,6 @@ protected:
|
|||
*/
|
||||
std::optional<Co> top_co;
|
||||
|
||||
/**
|
||||
* The entry point for the goal
|
||||
*/
|
||||
virtual Co init() = 0;
|
||||
|
||||
/**
|
||||
* Wrapper around @ref init since virtual functions
|
||||
* can't be used in constructors.
|
||||
*/
|
||||
inline Co init_wrapper();
|
||||
|
||||
/**
|
||||
* Signals that the goal is done.
|
||||
* `co_return` the result. If you're not inside a coroutine, you can ignore
|
||||
|
|
@ -377,13 +370,24 @@ public:
|
|||
*/
|
||||
BuildResult getBuildResult(const DerivedPath &) const;
|
||||
|
||||
/**
|
||||
* Hack to say that this goal should not log `ex`, but instead keep
|
||||
* it around. Set by a waitee which sees itself as the designated
|
||||
* continuation of this goal, responsible for reporting its
|
||||
* successes or failures.
|
||||
*
|
||||
* @todo this is yet another not-nice hack in the goal system that
|
||||
* we ought to get rid of. See #11927
|
||||
*/
|
||||
bool preserveException = false;
|
||||
|
||||
/**
|
||||
* Exception containing an error message, if any.
|
||||
*/
|
||||
std::optional<Error> ex;
|
||||
|
||||
Goal(Worker & worker)
|
||||
: worker(worker), top_co(init_wrapper())
|
||||
Goal(Worker & worker, Co init)
|
||||
: worker(worker), top_co(std::move(init))
|
||||
{
|
||||
// top_co shouldn't have a goal already, should be nullptr.
|
||||
assert(!top_co->handle.promise().goal);
|
||||
|
|
@ -446,7 +450,3 @@ template<typename... ArgTypes>
|
|||
struct std::coroutine_traits<nix::Goal::Co, ArgTypes...> {
|
||||
using promise_type = nix::Goal::promise_type;
|
||||
};
|
||||
|
||||
nix::Goal::Co nix::Goal::init_wrapper() {
|
||||
co_return init();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ public:
|
|||
/**
|
||||
* The states.
|
||||
*/
|
||||
Co init() override;
|
||||
Co init();
|
||||
Co gotInfo();
|
||||
Co tryToRun(StorePath subPath, nix::ref<Store> sub, std::shared_ptr<const ValidPathInfo> info, bool & substituterFailed);
|
||||
Co finished();
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include "nix/util/types.hh"
|
||||
#include "nix/store/store-api.hh"
|
||||
#include "nix/store/derived-path-map.hh"
|
||||
#include "nix/store/build/goal.hh"
|
||||
#include "nix/store/realisation.hh"
|
||||
#include "nix/util/muxable-pipe.hh"
|
||||
|
|
@ -14,6 +15,7 @@ namespace nix {
|
|||
|
||||
/* Forward definition. */
|
||||
struct DerivationGoal;
|
||||
struct DerivationBuildingGoal;
|
||||
struct PathSubstitutionGoal;
|
||||
class DrvOutputSubstitutionGoal;
|
||||
|
||||
|
|
@ -103,7 +105,10 @@ private:
|
|||
* Maps used to prevent multiple instantiations of a goal for the
|
||||
* same derivation / path.
|
||||
*/
|
||||
std::map<StorePath, std::weak_ptr<DerivationGoal>> derivationGoals;
|
||||
|
||||
DerivedPathMap<std::weak_ptr<DerivationGoal>> derivationGoals;
|
||||
|
||||
std::map<StorePath, std::weak_ptr<DerivationBuildingGoal>> derivationBuildingGoals;
|
||||
std::map<StorePath, std::weak_ptr<PathSubstitutionGoal>> substitutionGoals;
|
||||
std::map<DrvOutput, std::weak_ptr<DrvOutputSubstitutionGoal>> drvOutputSubstitutionGoals;
|
||||
|
||||
|
|
@ -196,17 +201,27 @@ public:
|
|||
* @ref DerivationGoal "derivation goal"
|
||||
*/
|
||||
private:
|
||||
template<class G, typename... Args>
|
||||
std::shared_ptr<G> initGoalIfNeeded(std::weak_ptr<G> & goal_weak, Args && ...args);
|
||||
|
||||
std::shared_ptr<DerivationGoal> makeDerivationGoalCommon(
|
||||
const StorePath & drvPath, const OutputsSpec & wantedOutputs,
|
||||
ref<const SingleDerivedPath> drvReq, const OutputsSpec & wantedOutputs,
|
||||
std::function<std::shared_ptr<DerivationGoal>()> mkDrvGoal);
|
||||
public:
|
||||
std::shared_ptr<DerivationGoal> makeDerivationGoal(
|
||||
const StorePath & drvPath,
|
||||
ref<const SingleDerivedPath> drvReq,
|
||||
const OutputsSpec & wantedOutputs, BuildMode buildMode = bmNormal);
|
||||
std::shared_ptr<DerivationGoal> makeBasicDerivationGoal(
|
||||
const StorePath & drvPath, const BasicDerivation & drv,
|
||||
const OutputsSpec & wantedOutputs, BuildMode buildMode = bmNormal);
|
||||
|
||||
/**
|
||||
* @ref DerivationBuildingGoal "derivation goal"
|
||||
*/
|
||||
std::shared_ptr<DerivationBuildingGoal> makeDerivationBuildingGoal(
|
||||
const StorePath & drvPath, const Derivation & drv,
|
||||
BuildMode buildMode = bmNormal);
|
||||
|
||||
/**
|
||||
* @ref PathSubstitutionGoal "substitution goal"
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
*
|
||||
* Template implementations (as opposed to mere declarations).
|
||||
*
|
||||
* This file is an exmample of the "impl.hh" pattern. See the
|
||||
* This file is an example of the "impl.hh" pattern. See the
|
||||
* contributing guide.
|
||||
*/
|
||||
|
||||
|
|
|
|||
|
|
@ -89,12 +89,12 @@ DECLARE_COMMON_SERIALISER(std::map<K COMMA_ V>);
|
|||
* that the underlying types never serialize to the empty string.
|
||||
*
|
||||
* We do this instead of a generic std::optional<T> instance because
|
||||
* ordinal tags (0 or 1, here) are a bit of a compatability hazard. For
|
||||
* ordinal tags (0 or 1, here) are a bit of a compatibility hazard. For
|
||||
* the same reason, we don't have a std::variant<T..> instances (ordinal
|
||||
* tags 0...n).
|
||||
*
|
||||
* We could the generic instances and then these as specializations for
|
||||
* compatability, but that's proven a bit finnicky, and also makes the
|
||||
* compatibility, but that's proven a bit finnicky, and also makes the
|
||||
* worker protocol harder to implement in other languages where such
|
||||
* specializations may not be allowed.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ struct DerivationOptions
|
|||
/**
|
||||
* Parse this information from its legacy encoding as part of the
|
||||
* environment. This should not be used with nice greenfield formats
|
||||
* (e.g. JSON) but is necessary for supporing old formats (e.g.
|
||||
* (e.g. JSON) but is necessary for supporting old formats (e.g.
|
||||
* ATerm).
|
||||
*/
|
||||
static DerivationOptions
|
||||
|
|
|
|||
|
|
@ -214,7 +214,7 @@ struct DerivationType {
|
|||
/**
|
||||
* Impure derivation type
|
||||
*
|
||||
* This is similar at buil-time to the content addressed, not standboxed, not fixed
|
||||
* This is similar at build-time to the content addressed, not standboxed, not fixed
|
||||
* type, but has some restrictions on its usage.
|
||||
*/
|
||||
struct Impure {
|
||||
|
|
|
|||
|
|
@ -21,8 +21,11 @@ namespace nix {
|
|||
*
|
||||
* @param V A type to instantiate for each output. It should probably
|
||||
* should be an "optional" type so not every interior node has to have a
|
||||
* value. `* const Something` or `std::optional<Something>` would be
|
||||
* good choices for "optional" types.
|
||||
* value. For example, the scheduler uses
|
||||
* `DerivedPathMap<std::weak_ptr<DerivationCreationAndRealisationGoal>>` to
|
||||
* remember which goals correspond to which outputs. `* const Something`
|
||||
* or `std::optional<Something>` would also be good choices for
|
||||
* "optional" types.
|
||||
*/
|
||||
template<typename V>
|
||||
struct DerivedPathMap {
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ struct GCResults
|
|||
* Some views have only a no-op temp roots even though others to the
|
||||
* same store allow triggering GC. For instance one can't add a root
|
||||
* over ssh, but that doesn't prevent someone from gc-ing that store
|
||||
* accesed via SSH locally).
|
||||
* accessed via SSH locally).
|
||||
*
|
||||
* - The derived `LocalFSStore` class has `LocalFSStore::addPermRoot`,
|
||||
* which is not part of this class because it relies on the notion of
|
||||
|
|
|
|||
|
|
@ -515,7 +515,7 @@ public:
|
|||
R"(
|
||||
If set to `true` (the default), build logs written to
|
||||
`/nix/var/log/nix/drvs` are compressed on the fly using bzip2.
|
||||
Otherwise, they aren't compressed.
|
||||
Otherwise, they are not compressed.
|
||||
)",
|
||||
{"build-compress-log"}};
|
||||
|
||||
|
|
@ -637,8 +637,8 @@ public:
|
|||
location in the sandbox; for instance, `/bin=/nix-bin` mounts
|
||||
the path `/nix-bin` as `/bin` inside the sandbox. If *source* is
|
||||
followed by `?`, then it is not an error if *source* does not exist;
|
||||
for example, `/dev/nvidiactl?` specifies that `/dev/nvidiactl` only
|
||||
be mounted in the sandbox if it exists in the host filesystem.
|
||||
for example, `/dev/nvidiactl?` specifies that `/dev/nvidiactl`
|
||||
only be mounted in the sandbox if it exists in the host filesystem.
|
||||
|
||||
If the source is in the Nix store, then its closure is added to
|
||||
the sandbox as well.
|
||||
|
|
@ -682,7 +682,9 @@ public:
|
|||
description of the `size` option of `tmpfs` in mount(8). The default
|
||||
is `50%`.
|
||||
)"};
|
||||
#endif
|
||||
|
||||
#if defined(__linux__) || defined(__FreeBSD__)
|
||||
Setting<Path> sandboxBuildDir{this, "/build", "sandbox-build-dir",
|
||||
R"(
|
||||
*Linux only*
|
||||
|
|
@ -695,14 +697,7 @@ public:
|
|||
|
||||
Setting<std::optional<Path>> buildDir{this, std::nullopt, "build-dir",
|
||||
R"(
|
||||
The directory on the host, in which derivations' temporary build directories are created.
|
||||
|
||||
If not set, Nix uses the system temporary directory indicated by the `TMPDIR` environment variable.
|
||||
Note that builds are often performed by the Nix daemon, so its `TMPDIR` is used, and not that of the Nix command line interface.
|
||||
|
||||
This is also the location where [`--keep-failed`](@docroot@/command-ref/opt-common.md#opt-keep-failed) leaves its files.
|
||||
|
||||
If Nix runs without sandbox, or if the platform does not support sandboxing with bind mounts (e.g. macOS), then the [`builder`](@docroot@/language/derivations.md#attr-builder)'s environment contains this directory instead of the virtual location [`sandbox-build-dir`](#conf-sandbox-build-dir).
|
||||
Override the `build-dir` store setting for all stores that have this setting.
|
||||
)"};
|
||||
|
||||
Setting<PathSet> allowedImpureHostPrefixes{this, {}, "allowed-impure-host-deps",
|
||||
|
|
|
|||
|
|
@ -34,7 +34,39 @@ struct OptimiseStats
|
|||
uint64_t bytesFreed = 0;
|
||||
};
|
||||
|
||||
struct LocalStoreConfig : std::enable_shared_from_this<LocalStoreConfig>, virtual LocalFSStoreConfig
|
||||
struct LocalBuildStoreConfig : virtual LocalFSStoreConfig
|
||||
{
|
||||
|
||||
private:
|
||||
/**
|
||||
Input for computing the build directory. See `getBuildDir()`.
|
||||
*/
|
||||
Setting<std::optional<Path>> buildDir{this, std::nullopt, "build-dir",
|
||||
R"(
|
||||
The directory on the host, in which derivations' temporary build directories are created.
|
||||
|
||||
If not set, Nix will use the `builds` subdirectory of its configured state directory.
|
||||
|
||||
Note that builds are often performed by the Nix daemon, so its `build-dir` applies.
|
||||
|
||||
Nix will create this directory automatically with suitable permissions if it does not exist.
|
||||
Otherwise its permissions must allow all users to traverse the directory (i.e. it must have `o+x` set, in unix parlance) for non-sandboxed builds to work correctly.
|
||||
|
||||
This is also the location where [`--keep-failed`](@docroot@/command-ref/opt-common.md#opt-keep-failed) leaves its files.
|
||||
|
||||
If Nix runs without sandbox, or if the platform does not support sandboxing with bind mounts (e.g. macOS), then the [`builder`](@docroot@/language/derivations.md#attr-builder)'s environment will contain this directory, instead of the virtual location [`sandbox-build-dir`](#conf-sandbox-build-dir).
|
||||
|
||||
> **Warning**
|
||||
>
|
||||
> `build-dir` must not be set to a world-writable directory.
|
||||
> Placing temporary build directories in a world-writable place allows other users to access or modify build data that is currently in use.
|
||||
> This alone is merely an impurity, but combined with another factor this has allowed malicious derivations to escape the build sandbox.
|
||||
)"};
|
||||
public:
|
||||
Path getBuildDir() const;
|
||||
};
|
||||
|
||||
struct LocalStoreConfig : std::enable_shared_from_this<LocalStoreConfig>, virtual LocalFSStoreConfig, virtual LocalBuildStoreConfig
|
||||
{
|
||||
using LocalFSStoreConfig::LocalFSStoreConfig;
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ headers = [config_pub_h] + files(
|
|||
'binary-cache-store.hh',
|
||||
'build-result.hh',
|
||||
'build/derivation-goal.hh',
|
||||
'build/derivation-building-goal.hh',
|
||||
'build/derivation-building-misc.hh',
|
||||
'build/drv-output-substitution-goal.hh',
|
||||
'build/goal.hh',
|
||||
|
|
|
|||
|
|
@ -13,13 +13,13 @@ namespace nix {
|
|||
|
||||
/**
|
||||
* An (owned) output name. Just a type alias used to make code more
|
||||
* readible.
|
||||
* readable.
|
||||
*/
|
||||
typedef std::string OutputName;
|
||||
|
||||
/**
|
||||
* A borrowed output name. Just a type alias used to make code more
|
||||
* readible.
|
||||
* readable.
|
||||
*/
|
||||
typedef std::string_view OutputNameView;
|
||||
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ struct UnkeyedValidPathInfo
|
|||
Hash narHash;
|
||||
|
||||
/**
|
||||
* Other store objects this store object referes to.
|
||||
* Other store objects this store object refers to.
|
||||
*/
|
||||
StorePathSet references;
|
||||
|
||||
|
|
|
|||
|
|
@ -149,9 +149,7 @@ struct RemoteStore :
|
|||
|
||||
void addSignatures(const StorePath & storePath, const StringSet & sigs) override;
|
||||
|
||||
void queryMissing(const std::vector<DerivedPath> & targets,
|
||||
StorePathSet & willBuild, StorePathSet & willSubstitute, StorePathSet & unknown,
|
||||
uint64_t & downloadSize, uint64_t & narSize) override;
|
||||
MissingPaths queryMissing(const std::vector<DerivedPath> & targets) override;
|
||||
|
||||
void addBuildLog(const StorePath & drvPath, std::string_view log) override;
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
*
|
||||
* Template implementations (as opposed to mere declarations).
|
||||
*
|
||||
* This file is an exmample of the "impl.hh" pattern. See the
|
||||
* This file is an example of the "impl.hh" pattern. See the
|
||||
* contributing guide.
|
||||
*/
|
||||
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ public:
|
|||
*
|
||||
* Current implementation is to use `fcntl` with `F_SETPIPE_SZ`,
|
||||
* which is Linux-only. For this implementation, `size` must
|
||||
* convertable to an `int`. In other words, it must be within
|
||||
* convertible to an `int`. In other words, it must be within
|
||||
* `[0, INT_MAX]`.
|
||||
*/
|
||||
void trySetBufferSize(size_t size);
|
||||
|
|
|
|||
|
|
@ -71,6 +71,18 @@ struct KeyedBuildResult;
|
|||
|
||||
typedef std::map<StorePath, std::optional<ContentAddress>> StorePathCAMap;
|
||||
|
||||
/**
|
||||
* Information about what paths will be built or substituted, returned
|
||||
* by Store::queryMissing().
|
||||
*/
|
||||
struct MissingPaths
|
||||
{
|
||||
StorePathSet willBuild;
|
||||
StorePathSet willSubstitute;
|
||||
StorePathSet unknown;
|
||||
uint64_t downloadSize{0};
|
||||
uint64_t narSize{0};
|
||||
};
|
||||
|
||||
/**
|
||||
* About the class hierarchy of the store types:
|
||||
|
|
@ -382,7 +394,7 @@ public:
|
|||
|
||||
/**
|
||||
* Query the mapping outputName => outputPath for the given
|
||||
* derivation. All outputs are mentioned so ones mising the mapping
|
||||
* derivation. All outputs are mentioned so ones missing the mapping
|
||||
* are mapped to `std::nullopt`.
|
||||
*/
|
||||
virtual std::map<std::string, std::optional<StorePath>> queryPartialDerivationOutputMap(
|
||||
|
|
@ -694,9 +706,7 @@ public:
|
|||
* derivations that will be built, and the set of output paths that
|
||||
* will be substituted.
|
||||
*/
|
||||
virtual void queryMissing(const std::vector<DerivedPath> & targets,
|
||||
StorePathSet & willBuild, StorePathSet & willSubstitute, StorePathSet & unknown,
|
||||
uint64_t & downloadSize, uint64_t & narSize);
|
||||
virtual MissingPaths queryMissing(const std::vector<DerivedPath> & targets);
|
||||
|
||||
/**
|
||||
* Sort a set of paths topologically under the references
|
||||
|
|
@ -809,7 +819,7 @@ protected:
|
|||
|
||||
/**
|
||||
* Helper for methods that are not unsupported: this is used for
|
||||
* default definitions for virtual methods that are meant to be overriden.
|
||||
* default definitions for virtual methods that are meant to be overridden.
|
||||
*
|
||||
* @todo Using this should be a last resort. It is better to make
|
||||
* the method "virtual pure" and/or move it to a subclass.
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ struct MixStoreDirMethods
|
|||
|
||||
/**
|
||||
* Read-only variant of addToStore(). It returns the store
|
||||
* path for the given file sytem object.
|
||||
* path for the given file system object.
|
||||
*/
|
||||
std::pair<StorePath, Hash> computeStorePath(
|
||||
std::string_view name,
|
||||
|
|
@ -125,7 +125,7 @@ struct StoreDirConfigBase : Config
|
|||
*/
|
||||
struct StoreDirConfig : StoreDirConfigBase, MixStoreDirMethods
|
||||
{
|
||||
using Params = std::map<std::string, std::string>;
|
||||
using Params = StringMap;
|
||||
|
||||
StoreDirConfig(const Params & params);
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ namespace nix {
|
|||
|
||||
/**
|
||||
* A parsed Store URI (URI is a slight misnomer...), parsed but not yet
|
||||
* resolved to a specific instance and query parms validated.
|
||||
* resolved to a specific instance and query params validated.
|
||||
*
|
||||
* Supported values are:
|
||||
*
|
||||
|
|
@ -41,7 +41,7 @@ namespace nix {
|
|||
*/
|
||||
struct StoreReference
|
||||
{
|
||||
using Params = std::map<std::string, std::string>;
|
||||
using Params = StringMap;
|
||||
|
||||
/**
|
||||
* Special store reference `""` or `"auto"`
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
* those implementations.
|
||||
*
|
||||
* Consumers of an arbitrary store from a URL/JSON configuration instead
|
||||
* just need the defintions `nix/store/store-open.hh`; those do use this
|
||||
* just need the definitions `nix/store/store-open.hh`; those do use this
|
||||
* but only as an implementation. Consumers of a specific extra type of
|
||||
* store can skip both these, and just use the definition of the store
|
||||
* in question directly.
|
||||
|
|
@ -71,7 +71,7 @@ struct Implementations
|
|||
};
|
||||
auto [it, didInsert] = registered().insert({TConfig::name(), std::move(factory)});
|
||||
if (!didInsert) {
|
||||
throw Error("Already registred store with name '%s'", it->first);
|
||||
throw Error("Already registered store with name '%s'", it->first);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ struct WorkerProto::BasicClientConnection : WorkerProto::BasicConnection
|
|||
|
||||
/**
|
||||
* After calling handshake, must call this to exchange some basic
|
||||
* information abou the connection.
|
||||
* information about the connection.
|
||||
*/
|
||||
ClientHandshakeInfo postHandshake(const StoreDirConfig & store);
|
||||
|
||||
|
|
@ -157,7 +157,7 @@ struct WorkerProto::BasicServerConnection : WorkerProto::BasicConnection
|
|||
|
||||
/**
|
||||
* After calling handshake, must call this to exchange some basic
|
||||
* information abou the connection.
|
||||
* information about the connection.
|
||||
*/
|
||||
void postHandshake(const StoreDirConfig & store, const ClientHandshakeInfo & info);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
*
|
||||
* Template implementations (as opposed to mere declarations).
|
||||
*
|
||||
* This file is an exmample of the "impl.hh" pattern. See the
|
||||
* This file is an example of the "impl.hh" pattern. See the
|
||||
* contributing guide.
|
||||
*/
|
||||
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ struct WorkerProto
|
|||
struct BasicServerConnection;
|
||||
|
||||
/**
|
||||
* Extra information provided as part of protocol negotation.
|
||||
* Extra information provided as part of protocol negotiation.
|
||||
*/
|
||||
struct ClientHandshakeInfo;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,4 +2,5 @@ include_dirs += include_directories('../..')
|
|||
|
||||
headers += files(
|
||||
'personality.hh',
|
||||
# hack for trailing newline
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
sources += files(
|
||||
'personality.cc',
|
||||
# hack for trailing newline
|
||||
)
|
||||
|
||||
subdir('include/nix/store')
|
||||
|
|
|
|||
|
|
@ -77,6 +77,16 @@ std::string LocalStoreConfig::doc()
|
|||
;
|
||||
}
|
||||
|
||||
Path LocalBuildStoreConfig::getBuildDir() const
|
||||
{
|
||||
return
|
||||
settings.buildDir.get().has_value()
|
||||
? *settings.buildDir.get()
|
||||
: buildDir.get().has_value()
|
||||
? *buildDir.get()
|
||||
: stateDir.get() + "/builds";
|
||||
}
|
||||
|
||||
ref<Store> LocalStore::Config::openStore() const
|
||||
{
|
||||
return make_ref<LocalStore>(ref{shared_from_this()});
|
||||
|
|
@ -133,7 +143,7 @@ LocalStore::LocalStore(ref<const Config> config)
|
|||
Path gcRootsDir = config->stateDir + "/gcroots";
|
||||
if (!pathExists(gcRootsDir)) {
|
||||
createDirs(gcRootsDir);
|
||||
createSymlink(profilesDir, gcRootsDir + "/profiles");
|
||||
replaceSymlink(profilesDir, gcRootsDir + "/profiles");
|
||||
}
|
||||
|
||||
for (auto & perUserDir : {profilesDir + "/per-user", gcRootsDir + "/per-user"}) {
|
||||
|
|
|
|||
|
|
@ -256,6 +256,7 @@ sources = files(
|
|||
'binary-cache-store.cc',
|
||||
'build-result.cc',
|
||||
'build/derivation-goal.cc',
|
||||
'build/derivation-building-goal.cc',
|
||||
'build/drv-output-substitution-goal.cc',
|
||||
'build/entry-points.cc',
|
||||
'build/goal.cc',
|
||||
|
|
|
|||
|
|
@ -98,23 +98,17 @@ const ContentAddress * getDerivationCA(const BasicDerivation & drv)
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
void Store::queryMissing(const std::vector<DerivedPath> & targets,
|
||||
StorePathSet & willBuild_, StorePathSet & willSubstitute_, StorePathSet & unknown_,
|
||||
uint64_t & downloadSize_, uint64_t & narSize_)
|
||||
MissingPaths Store::queryMissing(const std::vector<DerivedPath> & targets)
|
||||
{
|
||||
Activity act(*logger, lvlDebug, actUnknown, "querying info about missing paths");
|
||||
|
||||
downloadSize_ = narSize_ = 0;
|
||||
|
||||
// FIXME: make async.
|
||||
ThreadPool pool(fileTransferSettings.httpConnections);
|
||||
|
||||
struct State
|
||||
{
|
||||
std::unordered_set<std::string> done;
|
||||
StorePathSet & unknown, & willSubstitute, & willBuild;
|
||||
uint64_t & downloadSize;
|
||||
uint64_t & narSize;
|
||||
MissingPaths res;
|
||||
};
|
||||
|
||||
struct DrvState
|
||||
|
|
@ -125,7 +119,7 @@ void Store::queryMissing(const std::vector<DerivedPath> & targets,
|
|||
DrvState(size_t left) : left(left) { }
|
||||
};
|
||||
|
||||
Sync<State> state_(State{{}, unknown_, willSubstitute_, willBuild_, downloadSize_, narSize_});
|
||||
Sync<State> state_;
|
||||
|
||||
std::function<void(DerivedPath)> doPath;
|
||||
|
||||
|
|
@ -143,7 +137,7 @@ void Store::queryMissing(const std::vector<DerivedPath> & targets,
|
|||
auto mustBuildDrv = [&](const StorePath & drvPath, const Derivation & drv) {
|
||||
{
|
||||
auto state(state_.lock());
|
||||
state->willBuild.insert(drvPath);
|
||||
state->res.willBuild.insert(drvPath);
|
||||
}
|
||||
|
||||
for (const auto & [inputDrv, inputNode] : drv.inputDrvs.map) {
|
||||
|
|
@ -203,7 +197,7 @@ void Store::queryMissing(const std::vector<DerivedPath> & targets,
|
|||
if (!isValidPath(drvPath)) {
|
||||
// FIXME: we could try to substitute the derivation.
|
||||
auto state(state_.lock());
|
||||
state->unknown.insert(drvPath);
|
||||
state->res.unknown.insert(drvPath);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -282,7 +276,7 @@ void Store::queryMissing(const std::vector<DerivedPath> & targets,
|
|||
|
||||
if (infos.empty()) {
|
||||
auto state(state_.lock());
|
||||
state->unknown.insert(bo.path);
|
||||
state->res.unknown.insert(bo.path);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -291,9 +285,9 @@ void Store::queryMissing(const std::vector<DerivedPath> & targets,
|
|||
|
||||
{
|
||||
auto state(state_.lock());
|
||||
state->willSubstitute.insert(bo.path);
|
||||
state->downloadSize += info->second.downloadSize;
|
||||
state->narSize += info->second.narSize;
|
||||
state->res.willSubstitute.insert(bo.path);
|
||||
state->res.downloadSize += info->second.downloadSize;
|
||||
state->res.narSize += info->second.narSize;
|
||||
}
|
||||
|
||||
for (auto & ref : info->second.references)
|
||||
|
|
@ -306,6 +300,8 @@ void Store::queryMissing(const std::vector<DerivedPath> & targets,
|
|||
pool.enqueue(std::bind(doPath, path));
|
||||
|
||||
pool.process();
|
||||
|
||||
return std::move(state_.lock()->res);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -331,7 +331,7 @@ Path getDefaultProfile()
|
|||
if (!pathExists(profileLink)) {
|
||||
replaceSymlink(profile, profileLink);
|
||||
}
|
||||
// Backwards compatibiliy measure: Make root's profile available as
|
||||
// Backwards compatibility measure: Make root's profile available as
|
||||
// `.../default` as it's what NixOS and most of the init scripts expect
|
||||
Path globalProfileLink = settings.nixStateDir + "/profiles/default";
|
||||
if (isRootUser() && !pathExists(globalProfileLink)) {
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ Realisation Realisation::fromJSON(
|
|||
|
||||
std::map <DrvOutput, StorePath> dependentRealisations;
|
||||
if (auto jsonDependencies = json.find("dependentRealisations"); jsonDependencies != json.end())
|
||||
for (auto & [jsonDepId, jsonDepOutPath] : jsonDependencies->get<std::map<std::string, std::string>>())
|
||||
for (auto & [jsonDepId, jsonDepOutPath] : jsonDependencies->get<StringMap>())
|
||||
dependentRealisations.insert({DrvOutput::parse(jsonDepId), StorePath(jsonDepOutPath)});
|
||||
|
||||
return Realisation{
|
||||
|
|
|
|||
|
|
@ -855,9 +855,7 @@ void RemoteStore::addSignatures(const StorePath & storePath, const StringSet & s
|
|||
}
|
||||
|
||||
|
||||
void RemoteStore::queryMissing(const std::vector<DerivedPath> & targets,
|
||||
StorePathSet & willBuild, StorePathSet & willSubstitute, StorePathSet & unknown,
|
||||
uint64_t & downloadSize, uint64_t & narSize)
|
||||
MissingPaths RemoteStore::queryMissing(const std::vector<DerivedPath> & targets)
|
||||
{
|
||||
{
|
||||
auto conn(getConnection());
|
||||
|
|
@ -868,16 +866,16 @@ void RemoteStore::queryMissing(const std::vector<DerivedPath> & targets,
|
|||
conn->to << WorkerProto::Op::QueryMissing;
|
||||
WorkerProto::write(*this, *conn, targets);
|
||||
conn.processStderr();
|
||||
willBuild = WorkerProto::Serialise<StorePathSet>::read(*this, *conn);
|
||||
willSubstitute = WorkerProto::Serialise<StorePathSet>::read(*this, *conn);
|
||||
unknown = WorkerProto::Serialise<StorePathSet>::read(*this, *conn);
|
||||
conn->from >> downloadSize >> narSize;
|
||||
return;
|
||||
MissingPaths res;
|
||||
res.willBuild = WorkerProto::Serialise<StorePathSet>::read(*this, *conn);
|
||||
res.willSubstitute = WorkerProto::Serialise<StorePathSet>::read(*this, *conn);
|
||||
res.unknown = WorkerProto::Serialise<StorePathSet>::read(*this, *conn);
|
||||
conn->from >> res.downloadSize >> res.narSize;
|
||||
return res;
|
||||
}
|
||||
|
||||
fallback:
|
||||
return Store::queryMissing(targets, willBuild, willSubstitute,
|
||||
unknown, downloadSize, narSize);
|
||||
return Store::queryMissing(targets);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -143,13 +143,7 @@ struct RestrictedStore : public virtual IndirectRootStore, public virtual GcStor
|
|||
unsupported("addSignatures");
|
||||
}
|
||||
|
||||
void queryMissing(
|
||||
const std::vector<DerivedPath> & targets,
|
||||
StorePathSet & willBuild,
|
||||
StorePathSet & willSubstitute,
|
||||
StorePathSet & unknown,
|
||||
uint64_t & downloadSize,
|
||||
uint64_t & narSize) override;
|
||||
MissingPaths queryMissing(const std::vector<DerivedPath> & targets) override;
|
||||
|
||||
virtual std::optional<std::string> getBuildLogExact(const StorePath & path) override
|
||||
{
|
||||
|
|
@ -306,19 +300,14 @@ std::vector<KeyedBuildResult> RestrictedStore::buildPathsWithResults(
|
|||
return results;
|
||||
}
|
||||
|
||||
void RestrictedStore::queryMissing(
|
||||
const std::vector<DerivedPath> & targets,
|
||||
StorePathSet & willBuild,
|
||||
StorePathSet & willSubstitute,
|
||||
StorePathSet & unknown,
|
||||
uint64_t & downloadSize,
|
||||
uint64_t & narSize)
|
||||
MissingPaths RestrictedStore::queryMissing(const std::vector<DerivedPath> & targets)
|
||||
{
|
||||
/* This is slightly impure since it leaks information to the
|
||||
client about what paths will be built/substituted or are
|
||||
already present. Probably not a big deal. */
|
||||
|
||||
std::vector<DerivedPath> allowed;
|
||||
StorePathSet unknown;
|
||||
for (auto & req : targets) {
|
||||
if (goal.isAllowed(req))
|
||||
allowed.emplace_back(req);
|
||||
|
|
@ -326,7 +315,12 @@ void RestrictedStore::queryMissing(
|
|||
unknown.insert(pathPartOfReq(req));
|
||||
}
|
||||
|
||||
next->queryMissing(allowed, willBuild, willSubstitute, unknown, downloadSize, narSize);
|
||||
auto res = next->queryMissing(allowed);
|
||||
|
||||
for (auto & p : unknown)
|
||||
res.unknown.insert(p);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ std::string MountedSSHStoreConfig::doc()
|
|||
* store.
|
||||
*
|
||||
* MountedSSHStore is very similar to UDSRemoteStore --- ignoring the
|
||||
* superficial differnce of SSH vs Unix domain sockets, they both are
|
||||
* superficial difference of SSH vs Unix domain sockets, they both are
|
||||
* accessing remote stores, and they both assume the store will be
|
||||
* mounted in the local filesystem.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ SSHMaster::SSHMaster(
|
|||
throw Error("invalid SSH host name '%s'", host);
|
||||
|
||||
auto state(state_.lock());
|
||||
state->tmpDir = std::make_unique<AutoDelete>(createTempDir("", "nix", true, true, 0700));
|
||||
state->tmpDir = std::make_unique<AutoDelete>(createTempDir("", "nix", 0700));
|
||||
}
|
||||
|
||||
void SSHMaster::addCommonSSHOpts(Strings & args)
|
||||
|
|
@ -83,7 +83,7 @@ bool SSHMaster::isMasterRunning() {
|
|||
Strings createSSHEnv()
|
||||
{
|
||||
// Copy the environment and set SHELL=/bin/sh
|
||||
std::map<std::string, std::string> env = getEnv();
|
||||
StringMap env = getEnv();
|
||||
|
||||
// SSH will invoke the "user" shell for -oLocalCommand, but that means
|
||||
// $SHELL. To keep things simple and avoid potential issues with other
|
||||
|
|
|
|||
|
|
@ -337,10 +337,10 @@ digraph graphname {
|
|||
node [shape=box]
|
||||
fileSource -> narSink
|
||||
narSink [style=dashed]
|
||||
narSink -> unsualHashTee [style = dashed, label = "Recursive && !SHA-256"]
|
||||
narSink -> unusualHashTee [style = dashed, label = "Recursive && !SHA-256"]
|
||||
narSink -> narHashSink [style = dashed, label = "else"]
|
||||
unsualHashTee -> narHashSink
|
||||
unsualHashTee -> caHashSink
|
||||
unusualHashTee -> narHashSink
|
||||
unusualHashTee -> caHashSink
|
||||
fileSource -> parseSink
|
||||
parseSink [style=dashed]
|
||||
parseSink-> fileSink [style = dashed, label = "Flat"]
|
||||
|
|
@ -794,15 +794,12 @@ void Store::substitutePaths(const StorePathSet & paths)
|
|||
for (auto & path : paths)
|
||||
if (!path.isDerivation())
|
||||
paths2.emplace_back(DerivedPath::Opaque{path});
|
||||
uint64_t downloadSize, narSize;
|
||||
StorePathSet willBuild, willSubstitute, unknown;
|
||||
queryMissing(paths2,
|
||||
willBuild, willSubstitute, unknown, downloadSize, narSize);
|
||||
auto missing = queryMissing(paths2);
|
||||
|
||||
if (!willSubstitute.empty())
|
||||
if (!missing.willSubstitute.empty())
|
||||
try {
|
||||
std::vector<DerivedPath> subs;
|
||||
for (auto & p : willSubstitute) subs.emplace_back(DerivedPath::Opaque{p});
|
||||
for (auto & p : missing.willSubstitute) subs.emplace_back(DerivedPath::Opaque{p});
|
||||
buildPaths(subs);
|
||||
} catch (Error & e) {
|
||||
logWarning(e.info());
|
||||
|
|
|
|||
209
src/libstore/unix/build/darwin-derivation-builder.cc
Normal file
209
src/libstore/unix/build/darwin-derivation-builder.cc
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
#ifdef __APPLE__
|
||||
|
||||
# include <spawn.h>
|
||||
# include <sys/sysctl.h>
|
||||
# include <sandbox.h>
|
||||
|
||||
/* This definition is undocumented but depended upon by all major browsers. */
|
||||
extern "C" int
|
||||
sandbox_init_with_parameters(const char * profile, uint64_t flags, const char * const parameters[], char ** errorbuf);
|
||||
|
||||
namespace nix {
|
||||
|
||||
struct DarwinDerivationBuilder : DerivationBuilderImpl
|
||||
{
|
||||
PathsInChroot pathsInChroot;
|
||||
|
||||
/**
|
||||
* Whether full sandboxing is enabled. Note that macOS builds
|
||||
* always have *some* sandboxing (see sandbox-minimal.sb).
|
||||
*/
|
||||
bool useSandbox;
|
||||
|
||||
DarwinDerivationBuilder(
|
||||
Store & store,
|
||||
std::unique_ptr<DerivationBuilderCallbacks> miscMethods,
|
||||
DerivationBuilderParams params,
|
||||
bool useSandbox)
|
||||
: DerivationBuilderImpl(store, std::move(miscMethods), std::move(params))
|
||||
, useSandbox(useSandbox)
|
||||
{
|
||||
}
|
||||
|
||||
void prepareSandbox() override
|
||||
{
|
||||
pathsInChroot = getPathsInSandbox();
|
||||
}
|
||||
|
||||
void setUser() override
|
||||
{
|
||||
DerivationBuilderImpl::setUser();
|
||||
|
||||
/* This has to appear before import statements. */
|
||||
std::string sandboxProfile = "(version 1)\n";
|
||||
|
||||
if (useSandbox) {
|
||||
|
||||
/* Lots and lots and lots of file functions freak out if they can't stat their full ancestry */
|
||||
PathSet ancestry;
|
||||
|
||||
/* We build the ancestry before adding all inputPaths to the store because we know they'll
|
||||
all have the same parents (the store), and there might be lots of inputs. This isn't
|
||||
particularly efficient... I doubt it'll be a bottleneck in practice */
|
||||
for (auto & i : pathsInChroot) {
|
||||
Path cur = i.first;
|
||||
while (cur.compare("/") != 0) {
|
||||
cur = dirOf(cur);
|
||||
ancestry.insert(cur);
|
||||
}
|
||||
}
|
||||
|
||||
/* And we want the store in there regardless of how empty pathsInChroot. We include the innermost
|
||||
path component this time, since it's typically /nix/store and we care about that. */
|
||||
Path cur = store.storeDir;
|
||||
while (cur.compare("/") != 0) {
|
||||
ancestry.insert(cur);
|
||||
cur = dirOf(cur);
|
||||
}
|
||||
|
||||
/* Add all our input paths to the chroot */
|
||||
for (auto & i : inputPaths) {
|
||||
auto p = store.printStorePath(i);
|
||||
pathsInChroot.insert_or_assign(p, p);
|
||||
}
|
||||
|
||||
/* Violations will go to the syslog if you set this. Unfortunately the destination does not appear to be
|
||||
* configurable */
|
||||
if (settings.darwinLogSandboxViolations) {
|
||||
sandboxProfile += "(deny default)\n";
|
||||
} else {
|
||||
sandboxProfile += "(deny default (with no-log))\n";
|
||||
}
|
||||
|
||||
sandboxProfile +=
|
||||
# include "sandbox-defaults.sb"
|
||||
;
|
||||
|
||||
if (!derivationType.isSandboxed())
|
||||
sandboxProfile +=
|
||||
# include "sandbox-network.sb"
|
||||
;
|
||||
|
||||
/* Add the output paths we'll use at build-time to the chroot */
|
||||
sandboxProfile += "(allow file-read* file-write* process-exec\n";
|
||||
for (auto & [_, path] : scratchOutputs)
|
||||
sandboxProfile += fmt("\t(subpath \"%s\")\n", store.printStorePath(path));
|
||||
|
||||
sandboxProfile += ")\n";
|
||||
|
||||
/* Our inputs (transitive dependencies and any impurities computed above)
|
||||
|
||||
without file-write* allowed, access() incorrectly returns EPERM
|
||||
*/
|
||||
sandboxProfile += "(allow file-read* file-write* process-exec\n";
|
||||
|
||||
// We create multiple allow lists, to avoid exceeding a limit in the darwin sandbox interpreter.
|
||||
// See https://github.com/NixOS/nix/issues/4119
|
||||
// We split our allow groups approximately at half the actual limit, 1 << 16
|
||||
const size_t breakpoint = sandboxProfile.length() + (1 << 14);
|
||||
for (auto & i : pathsInChroot) {
|
||||
|
||||
if (sandboxProfile.length() >= breakpoint) {
|
||||
debug("Sandbox break: %d %d", sandboxProfile.length(), breakpoint);
|
||||
sandboxProfile += ")\n(allow file-read* file-write* process-exec\n";
|
||||
}
|
||||
|
||||
if (i.first != i.second.source)
|
||||
throw Error(
|
||||
"can't map '%1%' to '%2%': mismatched impure paths not supported on Darwin",
|
||||
i.first,
|
||||
i.second.source);
|
||||
|
||||
std::string path = i.first;
|
||||
auto optSt = maybeLstat(path.c_str());
|
||||
if (!optSt) {
|
||||
if (i.second.optional)
|
||||
continue;
|
||||
throw SysError("getting attributes of required path '%s", path);
|
||||
}
|
||||
if (S_ISDIR(optSt->st_mode))
|
||||
sandboxProfile += fmt("\t(subpath \"%s\")\n", path);
|
||||
else
|
||||
sandboxProfile += fmt("\t(literal \"%s\")\n", path);
|
||||
}
|
||||
sandboxProfile += ")\n";
|
||||
|
||||
/* Allow file-read* on full directory hierarchy to self. Allows realpath() */
|
||||
sandboxProfile += "(allow file-read*\n";
|
||||
for (auto & i : ancestry) {
|
||||
sandboxProfile += fmt("\t(literal \"%s\")\n", i);
|
||||
}
|
||||
sandboxProfile += ")\n";
|
||||
|
||||
sandboxProfile += drvOptions.additionalSandboxProfile;
|
||||
} else
|
||||
sandboxProfile +=
|
||||
# include "sandbox-minimal.sb"
|
||||
;
|
||||
|
||||
debug("Generated sandbox profile:");
|
||||
debug(sandboxProfile);
|
||||
|
||||
/* The tmpDir in scope points at the temporary build directory for our derivation. Some packages try different
|
||||
mechanisms to find temporary directories, so we want to open up a broader place for them to put their files,
|
||||
if needed. */
|
||||
Path globalTmpDir = canonPath(defaultTempDir(), true);
|
||||
|
||||
/* They don't like trailing slashes on subpath directives */
|
||||
while (!globalTmpDir.empty() && globalTmpDir.back() == '/')
|
||||
globalTmpDir.pop_back();
|
||||
|
||||
if (getEnv("_NIX_TEST_NO_SANDBOX") != "1") {
|
||||
Strings sandboxArgs;
|
||||
sandboxArgs.push_back("_GLOBAL_TMP_DIR");
|
||||
sandboxArgs.push_back(globalTmpDir);
|
||||
if (drvOptions.allowLocalNetworking) {
|
||||
sandboxArgs.push_back("_ALLOW_LOCAL_NETWORKING");
|
||||
sandboxArgs.push_back("1");
|
||||
}
|
||||
char * sandbox_errbuf = nullptr;
|
||||
if (sandbox_init_with_parameters(
|
||||
sandboxProfile.c_str(), 0, stringsToCharPtrs(sandboxArgs).data(), &sandbox_errbuf)) {
|
||||
writeFull(
|
||||
STDERR_FILENO,
|
||||
fmt("failed to configure sandbox: %s\n", sandbox_errbuf ? sandbox_errbuf : "(null)"));
|
||||
_exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void execBuilder(const Strings & args, const Strings & envStrs) override
|
||||
{
|
||||
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, drv.builder.c_str(), NULL, &attrp, stringsToCharPtrs(args).data(), stringsToCharPtrs(envStrs).data());
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load diff
883
src/libstore/unix/build/linux-derivation-builder.cc
Normal file
883
src/libstore/unix/build/linux-derivation-builder.cc
Normal file
|
|
@ -0,0 +1,883 @@
|
|||
#ifdef __linux__
|
||||
|
||||
# include "nix/store/personality.hh"
|
||||
# include "nix/util/cgroup.hh"
|
||||
# include "nix/util/linux-namespaces.hh"
|
||||
# include "linux/fchmodat2-compat.hh"
|
||||
|
||||
# include <sys/ioctl.h>
|
||||
# include <net/if.h>
|
||||
# include <netinet/ip.h>
|
||||
# include <sys/mman.h>
|
||||
# include <sched.h>
|
||||
# include <sys/param.h>
|
||||
# include <sys/mount.h>
|
||||
# include <sys/syscall.h>
|
||||
|
||||
# if HAVE_SECCOMP
|
||||
# include <seccomp.h>
|
||||
# endif
|
||||
|
||||
# define pivot_root(new_root, put_old) (syscall(SYS_pivot_root, new_root, put_old))
|
||||
|
||||
namespace nix {
|
||||
|
||||
static void setupSeccomp()
|
||||
{
|
||||
if (!settings.filterSyscalls)
|
||||
return;
|
||||
|
||||
# if HAVE_SECCOMP
|
||||
scmp_filter_ctx ctx;
|
||||
|
||||
if (!(ctx = seccomp_init(SCMP_ACT_ALLOW)))
|
||||
throw SysError("unable to initialize seccomp mode 2");
|
||||
|
||||
Finally cleanup([&]() { seccomp_release(ctx); });
|
||||
|
||||
constexpr std::string_view nativeSystem = NIX_LOCAL_SYSTEM;
|
||||
|
||||
if (nativeSystem == "x86_64-linux" && seccomp_arch_add(ctx, SCMP_ARCH_X86) != 0)
|
||||
throw SysError("unable to add 32-bit seccomp architecture");
|
||||
|
||||
if (nativeSystem == "x86_64-linux" && seccomp_arch_add(ctx, SCMP_ARCH_X32) != 0)
|
||||
throw SysError("unable to add X32 seccomp architecture");
|
||||
|
||||
if (nativeSystem == "aarch64-linux" && seccomp_arch_add(ctx, SCMP_ARCH_ARM) != 0)
|
||||
printError(
|
||||
"unable to add ARM seccomp architecture; this may result in spurious build failures if running 32-bit ARM processes");
|
||||
|
||||
if (nativeSystem == "mips64-linux" && seccomp_arch_add(ctx, SCMP_ARCH_MIPS) != 0)
|
||||
printError("unable to add mips seccomp architecture");
|
||||
|
||||
if (nativeSystem == "mips64-linux" && seccomp_arch_add(ctx, SCMP_ARCH_MIPS64N32) != 0)
|
||||
printError("unable to add mips64-*abin32 seccomp architecture");
|
||||
|
||||
if (nativeSystem == "mips64el-linux" && seccomp_arch_add(ctx, SCMP_ARCH_MIPSEL) != 0)
|
||||
printError("unable to add mipsel seccomp architecture");
|
||||
|
||||
if (nativeSystem == "mips64el-linux" && seccomp_arch_add(ctx, SCMP_ARCH_MIPSEL64N32) != 0)
|
||||
printError("unable to add mips64el-*abin32 seccomp architecture");
|
||||
|
||||
/* Prevent builders from creating setuid/setgid binaries. */
|
||||
for (int perm : {S_ISUID, S_ISGID}) {
|
||||
if (seccomp_rule_add(
|
||||
ctx,
|
||||
SCMP_ACT_ERRNO(EPERM),
|
||||
SCMP_SYS(chmod),
|
||||
1,
|
||||
SCMP_A1(SCMP_CMP_MASKED_EQ, (scmp_datum_t) perm, (scmp_datum_t) perm))
|
||||
!= 0)
|
||||
throw SysError("unable to add seccomp rule");
|
||||
|
||||
if (seccomp_rule_add(
|
||||
ctx,
|
||||
SCMP_ACT_ERRNO(EPERM),
|
||||
SCMP_SYS(fchmod),
|
||||
1,
|
||||
SCMP_A1(SCMP_CMP_MASKED_EQ, (scmp_datum_t) perm, (scmp_datum_t) perm))
|
||||
!= 0)
|
||||
throw SysError("unable to add seccomp rule");
|
||||
|
||||
if (seccomp_rule_add(
|
||||
ctx,
|
||||
SCMP_ACT_ERRNO(EPERM),
|
||||
SCMP_SYS(fchmodat),
|
||||
1,
|
||||
SCMP_A2(SCMP_CMP_MASKED_EQ, (scmp_datum_t) perm, (scmp_datum_t) perm))
|
||||
!= 0)
|
||||
throw SysError("unable to add seccomp rule");
|
||||
|
||||
if (seccomp_rule_add(
|
||||
ctx,
|
||||
SCMP_ACT_ERRNO(EPERM),
|
||||
NIX_SYSCALL_FCHMODAT2,
|
||||
1,
|
||||
SCMP_A2(SCMP_CMP_MASKED_EQ, (scmp_datum_t) perm, (scmp_datum_t) perm))
|
||||
!= 0)
|
||||
throw SysError("unable to add seccomp rule");
|
||||
}
|
||||
|
||||
/* Prevent builders from using EAs or ACLs. Not all filesystems
|
||||
support these, and they're not allowed in the Nix store because
|
||||
they're not representable in the NAR serialisation. */
|
||||
if (seccomp_rule_add(ctx, SCMP_ACT_ERRNO(ENOTSUP), SCMP_SYS(getxattr), 0) != 0
|
||||
|| seccomp_rule_add(ctx, SCMP_ACT_ERRNO(ENOTSUP), SCMP_SYS(lgetxattr), 0) != 0
|
||||
|| seccomp_rule_add(ctx, SCMP_ACT_ERRNO(ENOTSUP), SCMP_SYS(fgetxattr), 0) != 0
|
||||
|| seccomp_rule_add(ctx, SCMP_ACT_ERRNO(ENOTSUP), SCMP_SYS(setxattr), 0) != 0
|
||||
|| seccomp_rule_add(ctx, SCMP_ACT_ERRNO(ENOTSUP), SCMP_SYS(lsetxattr), 0) != 0
|
||||
|| seccomp_rule_add(ctx, SCMP_ACT_ERRNO(ENOTSUP), SCMP_SYS(fsetxattr), 0) != 0)
|
||||
throw SysError("unable to add seccomp rule");
|
||||
|
||||
if (seccomp_attr_set(ctx, SCMP_FLTATR_CTL_NNP, settings.allowNewPrivileges ? 0 : 1) != 0)
|
||||
throw SysError("unable to set 'no new privileges' seccomp attribute");
|
||||
|
||||
if (seccomp_load(ctx) != 0)
|
||||
throw SysError("unable to load seccomp BPF program");
|
||||
# else
|
||||
throw Error(
|
||||
"seccomp is not supported on this platform; "
|
||||
"you can bypass this error by setting the option 'filter-syscalls' to false, but note that untrusted builds can then create setuid binaries!");
|
||||
# endif
|
||||
}
|
||||
|
||||
static void doBind(const Path & source, const Path & target, bool optional = false)
|
||||
{
|
||||
debug("bind mounting '%1%' to '%2%'", source, target);
|
||||
|
||||
auto bindMount = [&]() {
|
||||
if (mount(source.c_str(), target.c_str(), "", MS_BIND | MS_REC, 0) == -1)
|
||||
throw SysError("bind mount from '%1%' to '%2%' failed", source, target);
|
||||
};
|
||||
|
||||
auto maybeSt = maybeLstat(source);
|
||||
if (!maybeSt) {
|
||||
if (optional)
|
||||
return;
|
||||
else
|
||||
throw SysError("getting attributes of path '%1%'", source);
|
||||
}
|
||||
auto st = *maybeSt;
|
||||
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
createDirs(target);
|
||||
bindMount();
|
||||
} else if (S_ISLNK(st.st_mode)) {
|
||||
// Symlinks can (apparently) not be bind-mounted, so just copy it
|
||||
createDirs(dirOf(target));
|
||||
copyFile(std::filesystem::path(source), std::filesystem::path(target), false);
|
||||
} else {
|
||||
createDirs(dirOf(target));
|
||||
writeFile(target, "");
|
||||
bindMount();
|
||||
}
|
||||
}
|
||||
|
||||
struct LinuxDerivationBuilder : DerivationBuilderImpl
|
||||
{
|
||||
using DerivationBuilderImpl::DerivationBuilderImpl;
|
||||
|
||||
void enterChroot() override
|
||||
{
|
||||
setupSeccomp();
|
||||
|
||||
linux::setPersonality(drv.platform);
|
||||
}
|
||||
};
|
||||
|
||||
struct ChrootLinuxDerivationBuilder : LinuxDerivationBuilder
|
||||
{
|
||||
/**
|
||||
* Pipe for synchronising updates to the builder namespaces.
|
||||
*/
|
||||
Pipe userNamespaceSync;
|
||||
|
||||
/**
|
||||
* The mount namespace and user namespace of the builder, used to add additional
|
||||
* paths to the sandbox as a result of recursive Nix calls.
|
||||
*/
|
||||
AutoCloseFD sandboxMountNamespace;
|
||||
AutoCloseFD sandboxUserNamespace;
|
||||
|
||||
/**
|
||||
* On Linux, whether we're doing the build in its own user
|
||||
* namespace.
|
||||
*/
|
||||
bool usingUserNamespace = true;
|
||||
|
||||
/**
|
||||
* The root of the chroot environment.
|
||||
*/
|
||||
Path chrootRootDir;
|
||||
|
||||
/**
|
||||
* RAII object to delete the chroot directory.
|
||||
*/
|
||||
std::shared_ptr<AutoDelete> autoDelChroot;
|
||||
|
||||
PathsInChroot pathsInChroot;
|
||||
|
||||
/**
|
||||
* The cgroup of the builder, if any.
|
||||
*/
|
||||
std::optional<Path> cgroup;
|
||||
|
||||
using LinuxDerivationBuilder::LinuxDerivationBuilder;
|
||||
|
||||
void deleteTmpDir(bool force) override
|
||||
{
|
||||
autoDelChroot.reset(); /* this runs the destructor */
|
||||
|
||||
DerivationBuilderImpl::deleteTmpDir(force);
|
||||
}
|
||||
|
||||
uid_t sandboxUid()
|
||||
{
|
||||
return usingUserNamespace ? (!buildUser || buildUser->getUIDCount() == 1 ? 1000 : 0) : buildUser->getUID();
|
||||
}
|
||||
|
||||
gid_t sandboxGid()
|
||||
{
|
||||
return usingUserNamespace ? (!buildUser || buildUser->getUIDCount() == 1 ? 100 : 0) : buildUser->getGID();
|
||||
}
|
||||
|
||||
bool needsHashRewrite() override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<UserLock> getBuildUser() override
|
||||
{
|
||||
return acquireUserLock(drvOptions.useUidRange(drv) ? 65536 : 1, true);
|
||||
}
|
||||
|
||||
void setBuildTmpDir() override
|
||||
{
|
||||
/* If sandboxing is enabled, put the actual TMPDIR underneath
|
||||
an inaccessible root-owned directory, to prevent outside
|
||||
access.
|
||||
|
||||
On macOS, we don't use an actual chroot, so this isn't
|
||||
possible. Any mitigation along these lines would have to be
|
||||
done directly in the sandbox profile. */
|
||||
tmpDir = topTmpDir + "/build";
|
||||
createDir(tmpDir, 0700);
|
||||
}
|
||||
|
||||
Path tmpDirInSandbox() override
|
||||
{
|
||||
/* In a sandbox, for determinism, always use the same temporary
|
||||
directory. */
|
||||
return settings.sandboxBuildDir;
|
||||
}
|
||||
|
||||
void prepareUser() override
|
||||
{
|
||||
if ((buildUser && buildUser->getUIDCount() != 1) || settings.useCgroups) {
|
||||
experimentalFeatureSettings.require(Xp::Cgroups);
|
||||
|
||||
/* If we're running from the daemon, then this will return the
|
||||
root cgroup of the service. Otherwise, it will return the
|
||||
current cgroup. */
|
||||
auto rootCgroup = getRootCgroup();
|
||||
auto cgroupFS = getCgroupFS();
|
||||
if (!cgroupFS)
|
||||
throw Error("cannot determine the cgroups file system");
|
||||
auto rootCgroupPath = canonPath(*cgroupFS + "/" + rootCgroup);
|
||||
if (!pathExists(rootCgroupPath))
|
||||
throw Error("expected cgroup directory '%s'", rootCgroupPath);
|
||||
|
||||
static std::atomic<unsigned int> counter{0};
|
||||
|
||||
cgroup = buildUser ? fmt("%s/nix-build-uid-%d", rootCgroupPath, buildUser->getUID())
|
||||
: fmt("%s/nix-build-pid-%d-%d", rootCgroupPath, getpid(), counter++);
|
||||
|
||||
debug("using cgroup '%s'", *cgroup);
|
||||
|
||||
/* When using a build user, record the cgroup we used for that
|
||||
user so that if we got interrupted previously, we can kill
|
||||
any left-over cgroup first. */
|
||||
if (buildUser) {
|
||||
auto cgroupsDir = settings.nixStateDir + "/cgroups";
|
||||
createDirs(cgroupsDir);
|
||||
|
||||
auto cgroupFile = fmt("%s/%d", cgroupsDir, buildUser->getUID());
|
||||
|
||||
if (pathExists(cgroupFile)) {
|
||||
auto prevCgroup = readFile(cgroupFile);
|
||||
destroyCgroup(prevCgroup);
|
||||
}
|
||||
|
||||
writeFile(cgroupFile, *cgroup);
|
||||
}
|
||||
}
|
||||
|
||||
// Kill any processes left in the cgroup or build user.
|
||||
DerivationBuilderImpl::prepareUser();
|
||||
}
|
||||
|
||||
void prepareSandbox() override
|
||||
{
|
||||
/* Create a temporary directory in which we set up the chroot
|
||||
environment using bind-mounts. We put it in the Nix store
|
||||
so that the build outputs can be moved efficiently from the
|
||||
chroot to their final location. */
|
||||
auto chrootParentDir = store.Store::toRealPath(drvPath) + ".chroot";
|
||||
deletePath(chrootParentDir);
|
||||
|
||||
/* Clean up the chroot directory automatically. */
|
||||
autoDelChroot = std::make_shared<AutoDelete>(chrootParentDir);
|
||||
|
||||
printMsg(lvlChatty, "setting up chroot environment in '%1%'", chrootParentDir);
|
||||
|
||||
if (mkdir(chrootParentDir.c_str(), 0700) == -1)
|
||||
throw SysError("cannot create '%s'", chrootRootDir);
|
||||
|
||||
chrootRootDir = chrootParentDir + "/root";
|
||||
|
||||
if (mkdir(chrootRootDir.c_str(), buildUser && buildUser->getUIDCount() != 1 ? 0755 : 0750) == -1)
|
||||
throw SysError("cannot create '%1%'", chrootRootDir);
|
||||
|
||||
if (buildUser
|
||||
&& chown(
|
||||
chrootRootDir.c_str(), buildUser->getUIDCount() != 1 ? buildUser->getUID() : 0, buildUser->getGID())
|
||||
== -1)
|
||||
throw SysError("cannot change ownership of '%1%'", chrootRootDir);
|
||||
|
||||
/* Create a writable /tmp in the chroot. Many builders need
|
||||
this. (Of course they should really respect $TMPDIR
|
||||
instead.) */
|
||||
Path chrootTmpDir = chrootRootDir + "/tmp";
|
||||
createDirs(chrootTmpDir);
|
||||
chmod_(chrootTmpDir, 01777);
|
||||
|
||||
/* Create a /etc/passwd with entries for the build user and the
|
||||
nobody account. The latter is kind of a hack to support
|
||||
Samba-in-QEMU. */
|
||||
createDirs(chrootRootDir + "/etc");
|
||||
if (drvOptions.useUidRange(drv))
|
||||
chownToBuilder(chrootRootDir + "/etc");
|
||||
|
||||
if (drvOptions.useUidRange(drv) && (!buildUser || buildUser->getUIDCount() < 65536))
|
||||
throw Error("feature 'uid-range' requires the setting '%s' to be enabled", settings.autoAllocateUids.name);
|
||||
|
||||
/* Declare the build user's group so that programs get a consistent
|
||||
view of the system (e.g., "id -gn"). */
|
||||
writeFile(
|
||||
chrootRootDir + "/etc/group",
|
||||
fmt("root:x:0:\n"
|
||||
"nixbld:!:%1%:\n"
|
||||
"nogroup:x:65534:\n",
|
||||
sandboxGid()));
|
||||
|
||||
/* Create /etc/hosts with localhost entry. */
|
||||
if (derivationType.isSandboxed())
|
||||
writeFile(chrootRootDir + "/etc/hosts", "127.0.0.1 localhost\n::1 localhost\n");
|
||||
|
||||
/* Make the closure of the inputs available in the chroot,
|
||||
rather than the whole Nix store. This prevents any access
|
||||
to undeclared dependencies. Directories are bind-mounted,
|
||||
while other inputs are hard-linked (since only directories
|
||||
can be bind-mounted). !!! As an extra security
|
||||
precaution, make the fake Nix store only writable by the
|
||||
build user. */
|
||||
Path chrootStoreDir = chrootRootDir + store.storeDir;
|
||||
createDirs(chrootStoreDir);
|
||||
chmod_(chrootStoreDir, 01775);
|
||||
|
||||
if (buildUser && chown(chrootStoreDir.c_str(), 0, buildUser->getGID()) == -1)
|
||||
throw SysError("cannot change ownership of '%1%'", chrootStoreDir);
|
||||
|
||||
pathsInChroot = getPathsInSandbox();
|
||||
|
||||
for (auto & i : inputPaths) {
|
||||
auto p = store.printStorePath(i);
|
||||
pathsInChroot.insert_or_assign(p, store.toRealPath(p));
|
||||
}
|
||||
|
||||
/* If we're repairing, checking or rebuilding part of a
|
||||
multiple-outputs derivation, it's possible that we're
|
||||
rebuilding a path that is in settings.sandbox-paths
|
||||
(typically the dependencies of /bin/sh). Throw them
|
||||
out. */
|
||||
for (auto & i : drv.outputsAndOptPaths(store)) {
|
||||
/* If the name isn't known a priori (i.e. floating
|
||||
content-addressing derivation), the temporary location we use
|
||||
should be fresh. Freshness means it is impossible that the path
|
||||
is already in the sandbox, so we don't need to worry about
|
||||
removing it. */
|
||||
if (i.second.second)
|
||||
pathsInChroot.erase(store.printStorePath(*i.second.second));
|
||||
}
|
||||
|
||||
if (cgroup) {
|
||||
if (mkdir(cgroup->c_str(), 0755) != 0)
|
||||
throw SysError("creating cgroup '%s'", *cgroup);
|
||||
chownToBuilder(*cgroup);
|
||||
chownToBuilder(*cgroup + "/cgroup.procs");
|
||||
chownToBuilder(*cgroup + "/cgroup.threads");
|
||||
// chownToBuilder(*cgroup + "/cgroup.subtree_control");
|
||||
}
|
||||
}
|
||||
|
||||
Strings getPreBuildHookArgs() override
|
||||
{
|
||||
assert(!chrootRootDir.empty());
|
||||
return Strings({store.printStorePath(drvPath), chrootRootDir});
|
||||
}
|
||||
|
||||
Path realPathInSandbox(const Path & p) override
|
||||
{
|
||||
// FIXME: why the needsHashRewrite() conditional?
|
||||
return !needsHashRewrite() ? chrootRootDir + p : store.toRealPath(p);
|
||||
}
|
||||
|
||||
void startChild() override
|
||||
{
|
||||
/* Set up private namespaces for the build:
|
||||
|
||||
- The PID namespace causes the build to start as PID 1.
|
||||
Processes outside of the chroot are not visible to those
|
||||
on the inside, but processes inside the chroot are
|
||||
visible from the outside (though with different PIDs).
|
||||
|
||||
- The private mount namespace ensures that all the bind
|
||||
mounts we do will only show up in this process and its
|
||||
children, and will disappear automatically when we're
|
||||
done.
|
||||
|
||||
- The private network namespace ensures that the builder
|
||||
cannot talk to the outside world (or vice versa). It
|
||||
only has a private loopback interface. (Fixed-output
|
||||
derivations are not run in a private network namespace
|
||||
to allow functions like fetchurl to work.)
|
||||
|
||||
- The IPC namespace prevents the builder from communicating
|
||||
with outside processes using SysV IPC mechanisms (shared
|
||||
memory, message queues, semaphores). It also ensures
|
||||
that all IPC objects are destroyed when the builder
|
||||
exits.
|
||||
|
||||
- The UTS namespace ensures that builders see a hostname of
|
||||
localhost rather than the actual hostname.
|
||||
|
||||
We use a helper process to do the clone() to work around
|
||||
clone() being broken in multi-threaded programs due to
|
||||
at-fork handlers not being run. Note that we use
|
||||
CLONE_PARENT to ensure that the real builder is parented to
|
||||
us.
|
||||
*/
|
||||
|
||||
userNamespaceSync.create();
|
||||
|
||||
usingUserNamespace = userNamespacesSupported();
|
||||
|
||||
Pipe sendPid;
|
||||
sendPid.create();
|
||||
|
||||
Pid helper = startProcess([&]() {
|
||||
sendPid.readSide.close();
|
||||
|
||||
/* We need to open the slave early, before
|
||||
CLONE_NEWUSER. Otherwise we get EPERM when running as
|
||||
root. */
|
||||
openSlave();
|
||||
|
||||
try {
|
||||
/* Drop additional groups here because we can't do it
|
||||
after we've created the new user namespace. */
|
||||
if (setgroups(0, 0) == -1) {
|
||||
if (errno != EPERM)
|
||||
throw SysError("setgroups failed");
|
||||
if (settings.requireDropSupplementaryGroups)
|
||||
throw Error(
|
||||
"setgroups failed. Set the require-drop-supplementary-groups option to false to skip this step.");
|
||||
}
|
||||
|
||||
ProcessOptions options;
|
||||
options.cloneFlags = CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWIPC | CLONE_NEWUTS | CLONE_PARENT | SIGCHLD;
|
||||
if (derivationType.isSandboxed())
|
||||
options.cloneFlags |= CLONE_NEWNET;
|
||||
if (usingUserNamespace)
|
||||
options.cloneFlags |= CLONE_NEWUSER;
|
||||
|
||||
pid_t child = startProcess([&]() { runChild(); }, options);
|
||||
|
||||
writeFull(sendPid.writeSide.get(), fmt("%d\n", child));
|
||||
_exit(0);
|
||||
} catch (...) {
|
||||
handleChildException(true);
|
||||
_exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
sendPid.writeSide.close();
|
||||
|
||||
if (helper.wait() != 0) {
|
||||
processSandboxSetupMessages();
|
||||
// Only reached if the child process didn't send an exception.
|
||||
throw Error("unable to start build process");
|
||||
}
|
||||
|
||||
userNamespaceSync.readSide = -1;
|
||||
|
||||
/* Close the write side to prevent runChild() from hanging
|
||||
reading from this. */
|
||||
Finally cleanup([&]() { userNamespaceSync.writeSide = -1; });
|
||||
|
||||
auto ss = tokenizeString<std::vector<std::string>>(readLine(sendPid.readSide.get()));
|
||||
assert(ss.size() == 1);
|
||||
pid = string2Int<pid_t>(ss[0]).value();
|
||||
|
||||
if (usingUserNamespace) {
|
||||
/* Set the UID/GID mapping of the builder's user namespace
|
||||
such that the sandbox user maps to the build user, or to
|
||||
the calling user (if build users are disabled). */
|
||||
uid_t hostUid = buildUser ? buildUser->getUID() : getuid();
|
||||
uid_t hostGid = buildUser ? buildUser->getGID() : getgid();
|
||||
uid_t nrIds = buildUser ? buildUser->getUIDCount() : 1;
|
||||
|
||||
writeFile("/proc/" + std::to_string(pid) + "/uid_map", fmt("%d %d %d", sandboxUid(), hostUid, nrIds));
|
||||
|
||||
if (!buildUser || buildUser->getUIDCount() == 1)
|
||||
writeFile("/proc/" + std::to_string(pid) + "/setgroups", "deny");
|
||||
|
||||
writeFile("/proc/" + std::to_string(pid) + "/gid_map", fmt("%d %d %d", sandboxGid(), hostGid, nrIds));
|
||||
} else {
|
||||
debug("note: not using a user namespace");
|
||||
if (!buildUser)
|
||||
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- and user 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);
|
||||
if (sandboxMountNamespace.get() == -1)
|
||||
throw SysError("getting sandbox mount namespace");
|
||||
|
||||
if (usingUserNamespace) {
|
||||
sandboxUserNamespace = open(fmt("/proc/%d/ns/user", (pid_t) pid).c_str(), O_RDONLY);
|
||||
if (sandboxUserNamespace.get() == -1)
|
||||
throw SysError("getting sandbox user namespace");
|
||||
}
|
||||
|
||||
/* Move the child into its own cgroup. */
|
||||
if (cgroup)
|
||||
writeFile(*cgroup + "/cgroup.procs", fmt("%d", (pid_t) pid));
|
||||
|
||||
/* Signal the builder that we've updated its user namespace. */
|
||||
writeFull(userNamespaceSync.writeSide.get(), "1");
|
||||
}
|
||||
|
||||
void enterChroot() override
|
||||
{
|
||||
userNamespaceSync.writeSide = -1;
|
||||
|
||||
if (drainFD(userNamespaceSync.readSide.get()) != "1")
|
||||
throw Error("user namespace initialisation failed");
|
||||
|
||||
userNamespaceSync.readSide = -1;
|
||||
|
||||
if (derivationType.isSandboxed()) {
|
||||
|
||||
/* Initialise the loopback interface. */
|
||||
AutoCloseFD fd(socket(PF_INET, SOCK_DGRAM, IPPROTO_IP));
|
||||
if (!fd)
|
||||
throw SysError("cannot open IP socket");
|
||||
|
||||
struct ifreq ifr;
|
||||
strcpy(ifr.ifr_name, "lo");
|
||||
ifr.ifr_flags = IFF_UP | IFF_LOOPBACK | IFF_RUNNING;
|
||||
if (ioctl(fd.get(), SIOCSIFFLAGS, &ifr) == -1)
|
||||
throw SysError("cannot set loopback interface flags");
|
||||
}
|
||||
|
||||
/* Set the hostname etc. to fixed values. */
|
||||
char hostname[] = "localhost";
|
||||
if (sethostname(hostname, sizeof(hostname)) == -1)
|
||||
throw SysError("cannot set host name");
|
||||
char domainname[] = "(none)"; // kernel default
|
||||
if (setdomainname(domainname, sizeof(domainname)) == -1)
|
||||
throw SysError("cannot set domain name");
|
||||
|
||||
/* Make all filesystems private. This is necessary
|
||||
because subtrees may have been mounted as "shared"
|
||||
(MS_SHARED). (Systemd does this, for instance.) Even
|
||||
though we have a private mount namespace, mounting
|
||||
filesystems on top of a shared subtree still propagates
|
||||
outside of the namespace. Making a subtree private is
|
||||
local to the namespace, though, so setting MS_PRIVATE
|
||||
does not affect the outside world. */
|
||||
if (mount(0, "/", 0, MS_PRIVATE | MS_REC, 0) == -1)
|
||||
throw SysError("unable to make '/' private");
|
||||
|
||||
/* Bind-mount chroot directory to itself, to treat it as a
|
||||
different filesystem from /, as needed for pivot_root. */
|
||||
if (mount(chrootRootDir.c_str(), chrootRootDir.c_str(), 0, MS_BIND, 0) == -1)
|
||||
throw SysError("unable to bind mount '%1%'", chrootRootDir);
|
||||
|
||||
/* Bind-mount the sandbox's Nix store onto itself so that
|
||||
we can mark it as a "shared" subtree, allowing bind
|
||||
mounts made in *this* mount namespace to be propagated
|
||||
into the child namespace created by the
|
||||
unshare(CLONE_NEWNS) call below.
|
||||
|
||||
Marking chrootRootDir as MS_SHARED causes pivot_root()
|
||||
to fail with EINVAL. Don't know why. */
|
||||
Path chrootStoreDir = chrootRootDir + store.storeDir;
|
||||
|
||||
if (mount(chrootStoreDir.c_str(), chrootStoreDir.c_str(), 0, MS_BIND, 0) == -1)
|
||||
throw SysError("unable to bind mount the Nix store", chrootStoreDir);
|
||||
|
||||
if (mount(0, chrootStoreDir.c_str(), 0, MS_SHARED, 0) == -1)
|
||||
throw SysError("unable to make '%s' shared", chrootStoreDir);
|
||||
|
||||
/* Set up a nearly empty /dev, unless the user asked to
|
||||
bind-mount the host /dev. */
|
||||
Strings ss;
|
||||
if (pathsInChroot.find("/dev") == pathsInChroot.end()) {
|
||||
createDirs(chrootRootDir + "/dev/shm");
|
||||
createDirs(chrootRootDir + "/dev/pts");
|
||||
ss.push_back("/dev/full");
|
||||
if (store.config.systemFeatures.get().count("kvm") && pathExists("/dev/kvm"))
|
||||
ss.push_back("/dev/kvm");
|
||||
ss.push_back("/dev/null");
|
||||
ss.push_back("/dev/random");
|
||||
ss.push_back("/dev/tty");
|
||||
ss.push_back("/dev/urandom");
|
||||
ss.push_back("/dev/zero");
|
||||
createSymlink("/proc/self/fd", chrootRootDir + "/dev/fd");
|
||||
createSymlink("/proc/self/fd/0", chrootRootDir + "/dev/stdin");
|
||||
createSymlink("/proc/self/fd/1", chrootRootDir + "/dev/stdout");
|
||||
createSymlink("/proc/self/fd/2", chrootRootDir + "/dev/stderr");
|
||||
}
|
||||
|
||||
/* Fixed-output derivations typically need to access the
|
||||
network, so give them access to /etc/resolv.conf and so
|
||||
on. */
|
||||
if (!derivationType.isSandboxed()) {
|
||||
// Only use nss functions to resolve hosts and
|
||||
// services. Don’t use it for anything else that may
|
||||
// be configured for this system. This limits the
|
||||
// potential impurities introduced in fixed-outputs.
|
||||
writeFile(chrootRootDir + "/etc/nsswitch.conf", "hosts: files dns\nservices: files\n");
|
||||
|
||||
/* N.B. it is realistic that these paths might not exist. It
|
||||
happens when testing Nix building fixed-output derivations
|
||||
within a pure derivation. */
|
||||
for (auto & path : {"/etc/resolv.conf", "/etc/services", "/etc/hosts"})
|
||||
if (pathExists(path))
|
||||
ss.push_back(path);
|
||||
|
||||
if (settings.caFile != "") {
|
||||
Path caFile = settings.caFile;
|
||||
if (pathExists(caFile))
|
||||
pathsInChroot.try_emplace("/etc/ssl/certs/ca-certificates.crt", canonPath(caFile, true), true);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto & i : ss) {
|
||||
// For backwards-compatibility, resolve all the symlinks in the
|
||||
// chroot paths.
|
||||
auto canonicalPath = canonPath(i, true);
|
||||
pathsInChroot.emplace(i, canonicalPath);
|
||||
}
|
||||
|
||||
/* Bind-mount all the directories from the "host"
|
||||
filesystem that we want in the chroot
|
||||
environment. */
|
||||
for (auto & i : pathsInChroot) {
|
||||
if (i.second.source == "/proc")
|
||||
continue; // backwards compatibility
|
||||
|
||||
# if HAVE_EMBEDDED_SANDBOX_SHELL
|
||||
if (i.second.source == "__embedded_sandbox_shell__") {
|
||||
static unsigned char sh[] = {
|
||||
# include "embedded-sandbox-shell.gen.hh"
|
||||
};
|
||||
auto dst = chrootRootDir + i.first;
|
||||
createDirs(dirOf(dst));
|
||||
writeFile(dst, std::string_view((const char *) sh, sizeof(sh)));
|
||||
chmod_(dst, 0555);
|
||||
} else
|
||||
# endif
|
||||
{
|
||||
doBind(i.second.source, chrootRootDir + i.first, i.second.optional);
|
||||
}
|
||||
}
|
||||
|
||||
/* Bind a new instance of procfs on /proc. */
|
||||
createDirs(chrootRootDir + "/proc");
|
||||
if (mount("none", (chrootRootDir + "/proc").c_str(), "proc", 0, 0) == -1)
|
||||
throw SysError("mounting /proc");
|
||||
|
||||
/* Mount sysfs on /sys. */
|
||||
if (buildUser && buildUser->getUIDCount() != 1) {
|
||||
createDirs(chrootRootDir + "/sys");
|
||||
if (mount("none", (chrootRootDir + "/sys").c_str(), "sysfs", 0, 0) == -1)
|
||||
throw SysError("mounting /sys");
|
||||
}
|
||||
|
||||
/* Mount a new tmpfs on /dev/shm to ensure that whatever
|
||||
the builder puts in /dev/shm is cleaned up automatically. */
|
||||
if (pathExists("/dev/shm")
|
||||
&& mount(
|
||||
"none",
|
||||
(chrootRootDir + "/dev/shm").c_str(),
|
||||
"tmpfs",
|
||||
0,
|
||||
fmt("size=%s", settings.sandboxShmSize).c_str())
|
||||
== -1)
|
||||
throw SysError("mounting /dev/shm");
|
||||
|
||||
/* Mount a new devpts on /dev/pts. Note that this
|
||||
requires the kernel to be compiled with
|
||||
CONFIG_DEVPTS_MULTIPLE_INSTANCES=y (which is the case
|
||||
if /dev/ptx/ptmx exists). */
|
||||
if (pathExists("/dev/pts/ptmx") && !pathExists(chrootRootDir + "/dev/ptmx")
|
||||
&& !pathsInChroot.count("/dev/pts")) {
|
||||
if (mount("none", (chrootRootDir + "/dev/pts").c_str(), "devpts", 0, "newinstance,mode=0620") == 0) {
|
||||
createSymlink("/dev/pts/ptmx", chrootRootDir + "/dev/ptmx");
|
||||
|
||||
/* Make sure /dev/pts/ptmx is world-writable. With some
|
||||
Linux versions, it is created with permissions 0. */
|
||||
chmod_(chrootRootDir + "/dev/pts/ptmx", 0666);
|
||||
} else {
|
||||
if (errno != EINVAL)
|
||||
throw SysError("mounting /dev/pts");
|
||||
doBind("/dev/pts", chrootRootDir + "/dev/pts");
|
||||
doBind("/dev/ptmx", chrootRootDir + "/dev/ptmx");
|
||||
}
|
||||
}
|
||||
|
||||
/* Make /etc unwritable */
|
||||
if (!drvOptions.useUidRange(drv))
|
||||
chmod_(chrootRootDir + "/etc", 0555);
|
||||
|
||||
/* Unshare this mount namespace. This is necessary because
|
||||
pivot_root() below changes the root of the mount
|
||||
namespace. This means that the call to setns() in
|
||||
addDependency() would hide the host's filesystem,
|
||||
making it impossible to bind-mount paths from the host
|
||||
Nix store into the sandbox. Therefore, we save the
|
||||
pre-pivot_root namespace in
|
||||
sandboxMountNamespace. Since we made /nix/store a
|
||||
shared subtree above, this allows addDependency() to
|
||||
make paths appear in the sandbox. */
|
||||
if (unshare(CLONE_NEWNS) == -1)
|
||||
throw SysError("unsharing mount namespace");
|
||||
|
||||
/* Unshare the cgroup namespace. This means
|
||||
/proc/self/cgroup will show the child's cgroup as '/'
|
||||
rather than whatever it is in the parent. */
|
||||
if (cgroup && unshare(CLONE_NEWCGROUP) == -1)
|
||||
throw SysError("unsharing cgroup namespace");
|
||||
|
||||
/* Do the chroot(). */
|
||||
if (chdir(chrootRootDir.c_str()) == -1)
|
||||
throw SysError("cannot change directory to '%1%'", chrootRootDir);
|
||||
|
||||
if (mkdir("real-root", 0500) == -1)
|
||||
throw SysError("cannot create real-root directory");
|
||||
|
||||
if (pivot_root(".", "real-root") == -1)
|
||||
throw SysError("cannot pivot old root directory onto '%1%'", (chrootRootDir + "/real-root"));
|
||||
|
||||
if (chroot(".") == -1)
|
||||
throw SysError("cannot change root directory to '%1%'", chrootRootDir);
|
||||
|
||||
if (umount2("real-root", MNT_DETACH) == -1)
|
||||
throw SysError("cannot unmount real root filesystem");
|
||||
|
||||
if (rmdir("real-root") == -1)
|
||||
throw SysError("cannot remove real-root directory");
|
||||
|
||||
LinuxDerivationBuilder::enterChroot();
|
||||
}
|
||||
|
||||
void setUser() override
|
||||
{
|
||||
/* Switch to the sandbox uid/gid in the user namespace,
|
||||
which corresponds to the build user or calling user in
|
||||
the parent namespace. */
|
||||
if (setgid(sandboxGid()) == -1)
|
||||
throw SysError("setgid failed");
|
||||
if (setuid(sandboxUid()) == -1)
|
||||
throw SysError("setuid failed");
|
||||
}
|
||||
|
||||
std::variant<std::pair<BuildResult::Status, Error>, SingleDrvOutputs> unprepareBuild() override
|
||||
{
|
||||
sandboxMountNamespace = -1;
|
||||
sandboxUserNamespace = -1;
|
||||
|
||||
return DerivationBuilderImpl::unprepareBuild();
|
||||
}
|
||||
|
||||
void killSandbox(bool getStats) override
|
||||
{
|
||||
if (cgroup) {
|
||||
auto stats = destroyCgroup(*cgroup);
|
||||
if (getStats) {
|
||||
buildResult.cpuUser = stats.cpuUser;
|
||||
buildResult.cpuSystem = stats.cpuSystem;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
DerivationBuilderImpl::killSandbox(getStats);
|
||||
}
|
||||
|
||||
void cleanupBuild() override
|
||||
{
|
||||
DerivationBuilderImpl::cleanupBuild();
|
||||
|
||||
/* Move paths out of the chroot for easier debugging of
|
||||
build failures. */
|
||||
if (buildMode == bmNormal)
|
||||
for (auto & [_, status] : initialOutputs) {
|
||||
if (!status.known)
|
||||
continue;
|
||||
if (buildMode != bmCheck && status.known->isValid())
|
||||
continue;
|
||||
auto p = store.toRealPath(status.known->path);
|
||||
if (pathExists(chrootRootDir + p))
|
||||
std::filesystem::rename((chrootRootDir + p), p);
|
||||
}
|
||||
}
|
||||
|
||||
void addDependency(const StorePath & path) override
|
||||
{
|
||||
if (isAllowed(path))
|
||||
return;
|
||||
|
||||
addedPaths.insert(path);
|
||||
|
||||
debug("materialising '%s' in the sandbox", store.printStorePath(path));
|
||||
|
||||
Path source = store.Store::toRealPath(path);
|
||||
Path target = chrootRootDir + store.printStorePath(path);
|
||||
|
||||
if (pathExists(target)) {
|
||||
// There is a similar debug message in doBind, so only run it in this block to not have double messages.
|
||||
debug("bind-mounting %s -> %s", target, source);
|
||||
throw Error("store path '%s' already exists in the sandbox", store.printStorePath(path));
|
||||
}
|
||||
|
||||
/* Bind-mount the path into the sandbox. This requires
|
||||
entering its mount namespace, which is not possible
|
||||
in multithreaded programs. So we do this in a
|
||||
child process.*/
|
||||
Pid child(startProcess([&]() {
|
||||
if (usingUserNamespace && (setns(sandboxUserNamespace.get(), 0) == -1))
|
||||
throw SysError("entering sandbox user namespace");
|
||||
|
||||
if (setns(sandboxMountNamespace.get(), 0) == -1)
|
||||
throw SysError("entering sandbox mount namespace");
|
||||
|
||||
doBind(source, target);
|
||||
|
||||
_exit(0);
|
||||
}));
|
||||
|
||||
int status = child.wait();
|
||||
if (status != 0)
|
||||
throw Error("could not add path '%s' to sandbox", store.printStorePath(path));
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -104,14 +104,6 @@ struct DerivationBuilderCallbacks
|
|||
*/
|
||||
virtual void closeLogFile() = 0;
|
||||
|
||||
/**
|
||||
* Aborts if any output is not valid or corrupt, and otherwise
|
||||
* returns a 'SingleDrvOutputs' structure containing all outputs.
|
||||
*
|
||||
* @todo Probably should just be in `DerivationGoal`.
|
||||
*/
|
||||
virtual SingleDrvOutputs assertPathValidity() = 0;
|
||||
|
||||
virtual void appendLogTailErrorMsg(std::string & msg) = 0;
|
||||
|
||||
/**
|
||||
|
|
@ -145,11 +137,6 @@ struct DerivationBuilderCallbacks
|
|||
*/
|
||||
struct DerivationBuilder : RestrictionContext
|
||||
{
|
||||
/**
|
||||
* User selected for running the builder.
|
||||
*/
|
||||
std::unique_ptr<UserLock> buildUser;
|
||||
|
||||
/**
|
||||
* The process ID of the builder.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#include "nix/store/globals.hh"
|
||||
#include "nix/store/pathlocks.hh"
|
||||
#include "nix/util/users.hh"
|
||||
#include "nix/util/logging.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
|
@ -196,7 +197,7 @@ bool useBuildUsers()
|
|||
#ifdef __linux__
|
||||
static bool b = (settings.buildUsersGroup != "" || settings.autoAllocateUids) && isRootUser();
|
||||
return b;
|
||||
#elif defined(__APPLE__)
|
||||
#elif defined(__APPLE__) && defined(__FreeBSD__)
|
||||
static bool b = settings.buildUsersGroup != "" && isRootUser();
|
||||
return b;
|
||||
#else
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ bool PathLocks::lockPaths(const PathSet & paths, const std::string & waitMsg, bo
|
|||
}
|
||||
}
|
||||
|
||||
debug("lock aquired on '%1%'", lockPath);
|
||||
debug("lock acquired on '%1%'", lockPath);
|
||||
|
||||
struct _stat st;
|
||||
if (_fstat(fromDescriptorReadOnly(fd.get()), &st) == -1)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue