1
1
Fork 0
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:
Eelco Dolstra 2025-07-07 19:22:39 +02:00
commit 175406c313
284 changed files with 9123 additions and 4178 deletions

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -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);

View file

@ -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()))

View file

@ -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) {

View file

@ -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)));
}

View file

@ -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());
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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>>;
};

View file

@ -14,7 +14,7 @@
#endif
#ifdef __linux__
# include "nix/util/namespaces.hh"
# include "nix/util/linux-namespaces.hh"
#endif
#include <unistd.h>

View file

@ -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());
}

View file

@ -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

View file

@ -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.
*/

View 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;
};
};
}

View file

@ -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"

View file

@ -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;
};
};

View file

@ -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(); };

View file

@ -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();
}

View file

@ -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();

View file

@ -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"
*/

View file

@ -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.
*/

View file

@ -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.
*/

View file

@ -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

View file

@ -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 {

View file

@ -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 {

View file

@ -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

View file

@ -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",

View file

@ -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;

View file

@ -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',

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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.
*/

View file

@ -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);

View file

@ -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.

View file

@ -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);

View file

@ -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"`

View file

@ -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);
}
}
};

View file

@ -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);
};

View file

@ -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.
*/

View file

@ -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;

View file

@ -2,4 +2,5 @@ include_dirs += include_directories('../..')
headers += files(
'personality.hh',
# hack for trailing newline
)

View file

@ -1,5 +1,6 @@
sources += files(
'personality.cc',
# hack for trailing newline
)
subdir('include/nix/store')

View file

@ -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"}) {

View file

@ -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',

View file

@ -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);
}

View file

@ -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)) {

View file

@ -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{

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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.
*

View file

@ -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

View file

@ -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());

View 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

View 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. Dont 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

View file

@ -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.
*/

View file

@ -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

View file

@ -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)