Merge remote-tracking branch 'origin/recursive-nix'
This commit is contained in:
commit
ac2bc721d8
|
@ -13,6 +13,7 @@
|
|||
#include "nar-info.hh"
|
||||
#include "parsed-derivations.hh"
|
||||
#include "machines.hh"
|
||||
#include "daemon.hh"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
|
@ -33,6 +34,7 @@
|
|||
#include <sys/select.h>
|
||||
#include <sys/resource.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <fcntl.h>
|
||||
#include <netdb.h>
|
||||
#include <unistd.h>
|
||||
|
@ -765,9 +767,6 @@ private:
|
|||
immediate input paths). */
|
||||
PathSet inputPaths;
|
||||
|
||||
/* Referenceable paths (i.e., input and output paths). */
|
||||
PathSet allPaths;
|
||||
|
||||
/* Outputs that are already valid. If we're repairing, these are
|
||||
the outputs that are valid *and* not corrupt. */
|
||||
PathSet validPaths;
|
||||
|
@ -805,9 +804,13 @@ private:
|
|||
/* Pipe for the builder's standard output/error. */
|
||||
Pipe builderOut;
|
||||
|
||||
/* Pipe for synchronising updates to the builder user namespace. */
|
||||
/* Pipe for synchronising updates to the builder namespaces. */
|
||||
Pipe userNamespaceSync;
|
||||
|
||||
/* The mount namespace of the builder, used to add additional
|
||||
paths to the sandbox as a result of recursive Nix calls. */
|
||||
AutoCloseFD sandboxMountNamespace;
|
||||
|
||||
/* The build hook. */
|
||||
std::unique_ptr<HookInstance> hook;
|
||||
|
||||
|
@ -886,6 +889,29 @@ private:
|
|||
/* The remote machine on which we're building. */
|
||||
std::string machineName;
|
||||
|
||||
/* The recursive Nix daemon socket. */
|
||||
AutoCloseFD daemonSocket;
|
||||
|
||||
/* The daemon main thread. */
|
||||
std::thread daemonThread;
|
||||
|
||||
/* The daemon worker threads. */
|
||||
std::vector<std::thread> daemonWorkerThreads;
|
||||
|
||||
/* Paths that were added via recursive Nix calls. */
|
||||
PathSet addedPaths;
|
||||
|
||||
/* Recursive Nix calls are only allowed to build or realize paths
|
||||
in the original input closure or added via a recursive Nix call
|
||||
(so e.g. you can't do 'nix-store -r /nix/store/<bla>' where
|
||||
/nix/store/<bla> is some arbitrary path in a binary cache). */
|
||||
bool isAllowed(const Path & path)
|
||||
{
|
||||
return inputPaths.count(path) || addedPaths.count(path);
|
||||
}
|
||||
|
||||
friend class RestrictedStore;
|
||||
|
||||
public:
|
||||
DerivationGoal(const Path & drvPath, const StringSet & wantedOutputs,
|
||||
Worker & worker, BuildMode buildMode = bmNormal);
|
||||
|
@ -945,6 +971,14 @@ private:
|
|||
/* Write a JSON file containing the derivation attributes. */
|
||||
void writeStructuredAttrs();
|
||||
|
||||
void startDaemon();
|
||||
|
||||
void stopDaemon();
|
||||
|
||||
/* Add 'path' to the set of paths that may be referenced by the
|
||||
outputs, and make it appear in the sandbox. */
|
||||
void addDependency(const Path & path);
|
||||
|
||||
/* Make a file owned by the builder. */
|
||||
void chownToBuilder(const Path & path);
|
||||
|
||||
|
@ -1046,6 +1080,7 @@ DerivationGoal::~DerivationGoal()
|
|||
/* Careful: we should never ever throw an exception from a
|
||||
destructor. */
|
||||
try { killChild(); } catch (...) { ignoreException(); }
|
||||
try { stopDaemon(); } catch (...) { ignoreException(); }
|
||||
try { deleteTmpDir(false); } catch (...) { ignoreException(); }
|
||||
try { closeLogFile(); } catch (...) { ignoreException(); }
|
||||
}
|
||||
|
@ -1335,12 +1370,6 @@ void DerivationGoal::inputsRealised()
|
|||
/* Gather information necessary for computing the closure and/or
|
||||
running the build hook. */
|
||||
|
||||
/* The outputs are referenceable paths. */
|
||||
for (auto & i : drv->outputs) {
|
||||
debug(format("building path '%1%'") % i.second.path);
|
||||
allPaths.insert(i.second.path);
|
||||
}
|
||||
|
||||
/* Determine the full set of input paths. */
|
||||
|
||||
/* First, the input derivations. */
|
||||
|
@ -1365,8 +1394,6 @@ void DerivationGoal::inputsRealised()
|
|||
|
||||
debug(format("added input paths %1%") % showPaths(inputPaths));
|
||||
|
||||
allPaths.insert(inputPaths.begin(), inputPaths.end());
|
||||
|
||||
/* Is this a fixed-output derivation? */
|
||||
fixedOutput = drv->isFixedOutput();
|
||||
|
||||
|
@ -1530,6 +1557,8 @@ void DerivationGoal::buildDone()
|
|||
uid and then messing around with our output. */
|
||||
Finally releaseBuildUser([&]() { buildUser.reset(); });
|
||||
|
||||
sandboxMountNamespace = -1;
|
||||
|
||||
/* Since we got an EOF on the logger pipe, the builder is presumed
|
||||
to have terminated. In fact, the builder could also have
|
||||
simply have closed its end of the pipe, so just to be sure,
|
||||
|
@ -1561,6 +1590,9 @@ void DerivationGoal::buildDone()
|
|||
root. */
|
||||
if (buildUser) buildUser->kill();
|
||||
|
||||
/* Terminate the recursive Nix daemon. */
|
||||
stopDaemon();
|
||||
|
||||
bool diskFull = false;
|
||||
|
||||
try {
|
||||
|
@ -2213,6 +2245,11 @@ void DerivationGoal::startBuilder()
|
|||
}
|
||||
}
|
||||
|
||||
/* Fire up a Nix daemon to process recursive Nix calls from the
|
||||
builder. */
|
||||
if (parsedDrv->getRequiredSystemFeatures().count("recursive-nix"))
|
||||
startDaemon();
|
||||
|
||||
/* Run the builder. */
|
||||
printMsg(lvlChatty, format("executing builder '%1%'") % drv->builder);
|
||||
|
||||
|
@ -2387,6 +2424,12 @@ void DerivationGoal::startBuilder()
|
|||
writeFile("/proc/" + std::to_string(pid) + "/gid_map",
|
||||
(format("%d %d 1") % sandboxGid % hostGid).str());
|
||||
|
||||
/* Save the mount namespace of the child. We have to do this
|
||||
*before* the child does a chroot. */
|
||||
sandboxMountNamespace = open(fmt("/proc/%d/ns/mnt", (pid_t) pid).c_str(), O_RDONLY);
|
||||
if (sandboxMountNamespace.get() == -1)
|
||||
throw SysError("getting sandbox mount namespace");
|
||||
|
||||
/* Signal the builder that we've updated its user namespace. */
|
||||
writeFull(userNamespaceSync.writeSide.get(), "1");
|
||||
userNamespaceSync.writeSide = -1;
|
||||
|
@ -2629,6 +2672,319 @@ void DerivationGoal::writeStructuredAttrs()
|
|||
}
|
||||
|
||||
|
||||
/* A wrapper around LocalStore that only allows building/querying of
|
||||
paths that are in the input closures of the build or were added via
|
||||
recursive Nix calls. */
|
||||
struct RestrictedStore : public LocalFSStore
|
||||
{
|
||||
ref<LocalStore> next;
|
||||
|
||||
DerivationGoal & goal;
|
||||
|
||||
RestrictedStore(const Params & params, ref<LocalStore> next, DerivationGoal & goal)
|
||||
: Store(params), LocalFSStore(params), next(next), goal(goal)
|
||||
{ }
|
||||
|
||||
Path getRealStoreDir() override
|
||||
{ return next->realStoreDir; }
|
||||
|
||||
std::string getUri() override
|
||||
{ return next->getUri(); }
|
||||
|
||||
PathSet queryAllValidPaths() override
|
||||
{
|
||||
PathSet paths;
|
||||
for (auto & p : goal.inputPaths) paths.insert(p);
|
||||
for (auto & p : goal.addedPaths) paths.insert(p);
|
||||
return paths;
|
||||
}
|
||||
|
||||
void queryPathInfoUncached(const Path & path,
|
||||
Callback<std::shared_ptr<const ValidPathInfo>> callback) noexcept override
|
||||
{
|
||||
if (goal.isAllowed(path)) {
|
||||
try {
|
||||
/* Censor impure information. */
|
||||
auto info = std::make_shared<ValidPathInfo>(*next->queryPathInfo(path));
|
||||
info->deriver.clear();
|
||||
info->registrationTime = 0;
|
||||
info->ultimate = false;
|
||||
info->sigs.clear();
|
||||
callback(info);
|
||||
} catch (InvalidPath &) {
|
||||
callback(nullptr);
|
||||
}
|
||||
} else
|
||||
callback(nullptr);
|
||||
};
|
||||
|
||||
void queryReferrers(const Path & path, PathSet & referrers) override
|
||||
{ }
|
||||
|
||||
PathSet queryDerivationOutputs(const Path & path) override
|
||||
{ throw Error("queryDerivationOutputs"); }
|
||||
|
||||
StringSet queryDerivationOutputNames(const Path & path) override
|
||||
{ throw Error("queryDerivationOutputNames"); }
|
||||
|
||||
Path queryPathFromHashPart(const string & hashPart) override
|
||||
{ throw Error("queryPathFromHashPart"); }
|
||||
|
||||
Path addToStore(const string & name, const Path & srcPath,
|
||||
bool recursive = true, HashType hashAlgo = htSHA256,
|
||||
PathFilter & filter = defaultPathFilter, RepairFlag repair = NoRepair) override
|
||||
{ throw Error("addToStore"); }
|
||||
|
||||
void addToStore(const ValidPathInfo & info, Source & narSource,
|
||||
RepairFlag repair = NoRepair, CheckSigsFlag checkSigs = CheckSigs,
|
||||
std::shared_ptr<FSAccessor> accessor = 0) override
|
||||
{
|
||||
next->addToStore(info, narSource, repair, checkSigs, accessor);
|
||||
goal.addDependency(info.path);
|
||||
}
|
||||
|
||||
Path addToStoreFromDump(const string & dump, const string & name,
|
||||
bool recursive = true, HashType hashAlgo = htSHA256, RepairFlag repair = NoRepair) override
|
||||
{
|
||||
auto path = next->addToStoreFromDump(dump, name, recursive, hashAlgo, repair);
|
||||
goal.addDependency(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
Path addTextToStore(const string & name, const string & s,
|
||||
const PathSet & references, RepairFlag repair = NoRepair) override
|
||||
{
|
||||
auto path = next->addTextToStore(name, s, references, repair);
|
||||
goal.addDependency(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
void narFromPath(const Path & path, Sink & sink) override
|
||||
{
|
||||
if (!goal.isAllowed(path))
|
||||
throw InvalidPath("cannot dump unknown path '%s' in recursive Nix", path);
|
||||
LocalFSStore::narFromPath(path, sink);
|
||||
}
|
||||
|
||||
void ensurePath(const Path & path) override
|
||||
{
|
||||
if (!goal.isAllowed(path))
|
||||
throw InvalidPath("cannot substitute unknown path '%s' in recursive Nix", path);
|
||||
/* Nothing to be done; 'path' must already be valid. */
|
||||
}
|
||||
|
||||
void buildPaths(const PathSet & paths, BuildMode buildMode) override
|
||||
{
|
||||
if (buildMode != bmNormal) throw Error("unsupported build mode");
|
||||
|
||||
PathSet newPaths;
|
||||
|
||||
for (auto & path : paths) {
|
||||
DrvPathWithOutputs i = parseDrvPathWithOutputs(path);
|
||||
if (isDerivation(i.first)) {
|
||||
if (!goal.isAllowed(i.first))
|
||||
throw InvalidPath("cannot build unknown path '%s' in recursive Nix", i.first);
|
||||
auto drv = derivationFromPath(i.first);
|
||||
for (auto & output : drv.outputs)
|
||||
if (wantOutput(output.first, i.second))
|
||||
newPaths.insert(output.second.path);
|
||||
} else if (!goal.isAllowed(path))
|
||||
throw InvalidPath("cannot build unknown path '%s' in recursive Nix", path);
|
||||
}
|
||||
|
||||
next->buildPaths(paths, buildMode);
|
||||
|
||||
PathSet closure;
|
||||
next->computeFSClosure(newPaths, closure);
|
||||
for (auto & path : closure)
|
||||
goal.addDependency(path);
|
||||
}
|
||||
|
||||
BuildResult buildDerivation(const Path & drvPath, const BasicDerivation & drv,
|
||||
BuildMode buildMode = bmNormal) override
|
||||
{ unsupported("buildDerivation"); }
|
||||
|
||||
void addTempRoot(const Path & path)
|
||||
{ }
|
||||
|
||||
void addIndirectRoot(const Path & path)
|
||||
{ }
|
||||
|
||||
Roots findRoots()
|
||||
{ return Roots(); }
|
||||
|
||||
void collectGarbage(const GCOptions & options, GCResults & results)
|
||||
{ }
|
||||
|
||||
void addSignatures(const Path & storePath, const StringSet & sigs)
|
||||
{ unsupported("addSignatures"); }
|
||||
|
||||
void queryMissing(const PathSet & targets,
|
||||
PathSet & willBuild, PathSet & willSubstitute, PathSet & unknown,
|
||||
unsigned long long & downloadSize, unsigned long long & narSize)
|
||||
{
|
||||
/* 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. */
|
||||
|
||||
PathSet allowed;
|
||||
for (auto & path : targets) {
|
||||
DrvPathWithOutputs i = parseDrvPathWithOutputs(path);
|
||||
if (goal.isAllowed(i.first))
|
||||
allowed.insert(i.first);
|
||||
else
|
||||
unknown.insert(i.first);
|
||||
}
|
||||
|
||||
next->queryMissing(allowed, willBuild, willSubstitute,
|
||||
unknown, downloadSize, narSize);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
void DerivationGoal::startDaemon()
|
||||
{
|
||||
settings.requireExperimentalFeature("recursive-nix");
|
||||
|
||||
Store::Params params;
|
||||
params["path-info-cache-size"] = "0";
|
||||
params["store"] = worker.store.storeDir;
|
||||
params["root"] = worker.store.rootDir;
|
||||
params["state"] = "/no-such-path";
|
||||
params["log"] = "/no-such-path";
|
||||
auto store = make_ref<RestrictedStore>(params,
|
||||
ref<LocalStore>(std::dynamic_pointer_cast<LocalStore>(worker.store.shared_from_this())),
|
||||
*this);
|
||||
|
||||
addedPaths.clear();
|
||||
|
||||
auto socketName = ".nix-socket";
|
||||
Path socketPath = tmpDir + "/" + socketName;
|
||||
env["NIX_REMOTE"] = "unix://" + tmpDirInSandbox + "/" + socketName;
|
||||
|
||||
daemonSocket = createUnixDomainSocket(socketPath, 0600);
|
||||
|
||||
chownToBuilder(socketPath);
|
||||
|
||||
daemonThread = std::thread([this, store]() {
|
||||
|
||||
while (true) {
|
||||
|
||||
/* Accept a connection. */
|
||||
struct sockaddr_un remoteAddr;
|
||||
socklen_t remoteAddrLen = sizeof(remoteAddr);
|
||||
|
||||
AutoCloseFD remote = accept(daemonSocket.get(),
|
||||
(struct sockaddr *) &remoteAddr, &remoteAddrLen);
|
||||
if (!remote) {
|
||||
if (errno == EINTR) continue;
|
||||
if (errno == EINVAL) break;
|
||||
throw SysError("accepting connection");
|
||||
}
|
||||
|
||||
closeOnExec(remote.get());
|
||||
|
||||
debug("received daemon connection");
|
||||
|
||||
auto workerThread = std::thread([this, store, remote{std::move(remote)}]() {
|
||||
FdSource from(remote.get());
|
||||
FdSink to(remote.get());
|
||||
try {
|
||||
daemon::processConnection(store, from, to,
|
||||
daemon::NotTrusted, daemon::Recursive, "nobody", 65535);
|
||||
debug("terminated daemon connection");
|
||||
} catch (SysError &) {
|
||||
ignoreException();
|
||||
}
|
||||
});
|
||||
|
||||
daemonWorkerThreads.push_back(std::move(workerThread));
|
||||
}
|
||||
|
||||
debug("daemon shutting down");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
void DerivationGoal::stopDaemon()
|
||||
{
|
||||
if (daemonSocket && shutdown(daemonSocket.get(), SHUT_RDWR) == -1)
|
||||
throw SysError("shutting down daemon socket");
|
||||
|
||||
if (daemonThread.joinable())
|
||||
daemonThread.join();
|
||||
|
||||
// FIXME: should prune worker threads more quickly.
|
||||
// FIXME: shutdown the client socket to speed up worker termination.
|
||||
for (auto & thread : daemonWorkerThreads)
|
||||
thread.join();
|
||||
daemonWorkerThreads.clear();
|
||||
|
||||
daemonSocket = -1;
|
||||
}
|
||||
|
||||
|
||||
void DerivationGoal::addDependency(const Path & path)
|
||||
{
|
||||
worker.store.assertStorePath(path);
|
||||
|
||||
if (isAllowed(path)) return;
|
||||
|
||||
addedPaths.insert(path);
|
||||
|
||||
/* If we're doing a sandbox build, then we have to make the path
|
||||
appear in the sandbox. */
|
||||
if (useChroot) {
|
||||
|
||||
debug("materialising '%s' in the sandbox", path);
|
||||
|
||||
#if __linux__
|
||||
|
||||
Path source = worker.store.toRealPath(path);
|
||||
Path target = chrootRootDir + path;
|
||||
debug("bind-mounting %s -> %s", target, source);
|
||||
|
||||
if (pathExists(target))
|
||||
throw Error("store path '%s' already exists in the sandbox", path);
|
||||
|
||||
struct stat st;
|
||||
if (lstat(source.c_str(), &st))
|
||||
throw SysError("getting attributes of path '%s'", source);
|
||||
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
|
||||
/* Bind-mount the path into the sandbox. This requires
|
||||
entering its mount namespace, which is not possible
|
||||
in multithreaded programs. So we do this in a
|
||||
child process.*/
|
||||
Pid child(startProcess([&]() {
|
||||
|
||||
if (setns(sandboxMountNamespace.get(), 0) == -1)
|
||||
throw SysError("entering sandbox mount namespace");
|
||||
|
||||
createDirs(target);
|
||||
|
||||
if (mount(source.c_str(), target.c_str(), "", MS_BIND, 0) == -1)
|
||||
throw SysError("bind mount from '%s' to '%s' failed", source, target);
|
||||
|
||||
_exit(0);
|
||||
}));
|
||||
|
||||
int status = child.wait();
|
||||
if (status != 0)
|
||||
throw Error("could not add path '%s' to sandbox", path);
|
||||
|
||||
} else
|
||||
linkOrCopy(source, target);
|
||||
|
||||
#else
|
||||
throw Error("don't know how to make path '%s' (produced by a recursive Nix call) appear in the sandbox", path);
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void DerivationGoal::chownToBuilder(const Path & path)
|
||||
{
|
||||
if (!buildUser) return;
|
||||
|
@ -2764,15 +3120,30 @@ void DerivationGoal::runChild()
|
|||
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_REC|MS_PRIVATE, 0) == -1) {
|
||||
throw SysError("unable to make '/' private mount");
|
||||
}
|
||||
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(format("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 + worker.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;
|
||||
|
@ -2874,6 +3245,19 @@ void DerivationGoal::runChild()
|
|||
}
|
||||
}
|
||||
|
||||
/* 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");
|
||||
|
||||
/* Do the chroot(). */
|
||||
if (chdir(chrootRootDir.c_str()) == -1)
|
||||
throw SysError(format("cannot change directory to '%1%'") % chrootRootDir);
|
||||
|
@ -3192,6 +3576,14 @@ void DerivationGoal::registerOutputs()
|
|||
|
||||
std::exception_ptr delayedException;
|
||||
|
||||
/* The paths that can be referenced are the input closures, the
|
||||
output paths, and any paths that have been built via recursive
|
||||
Nix calls. */
|
||||
PathSet referenceablePaths;
|
||||
for (auto & p : inputPaths) referenceablePaths.insert(p);
|
||||
for (auto & i : drv->outputs) referenceablePaths.insert(i.second.path);
|
||||
for (auto & p : addedPaths) referenceablePaths.insert(p);
|
||||
|
||||
/* Check whether the output paths were created, and grep each
|
||||
output path to determine what other paths it references. Also make all
|
||||
output paths read-only. */
|
||||
|
@ -3327,7 +3719,7 @@ void DerivationGoal::registerOutputs()
|
|||
verify later on whether nobody has messed with the store. */
|
||||
debug("scanning for references inside '%1%'", path);
|
||||
HashResult hash;
|
||||
PathSet references = scanForReferences(actualPath, allPaths, hash);
|
||||
PathSet references = scanForReferences(actualPath, referenceablePaths, hash);
|
||||
|
||||
if (buildMode == bmCheck) {
|
||||
if (!worker.store.isValidPath(path)) continue;
|
||||
|
|
|
@ -186,8 +186,75 @@ struct RetrieveRegularNARSink : ParseSink
|
|||
}
|
||||
};
|
||||
|
||||
struct ClientSettings
|
||||
{
|
||||
bool keepFailed;
|
||||
bool keepGoing;
|
||||
bool tryFallback;
|
||||
Verbosity verbosity;
|
||||
unsigned int maxBuildJobs;
|
||||
time_t maxSilentTime;
|
||||
bool verboseBuild;
|
||||
unsigned int buildCores;
|
||||
bool useSubstitutes;
|
||||
StringMap overrides;
|
||||
|
||||
void apply(TrustedFlag trusted)
|
||||
{
|
||||
settings.keepFailed = keepFailed;
|
||||
settings.keepGoing = keepGoing;
|
||||
settings.tryFallback = tryFallback;
|
||||
nix::verbosity = verbosity;
|
||||
settings.maxBuildJobs.assign(maxBuildJobs);
|
||||
settings.maxSilentTime = maxSilentTime;
|
||||
settings.verboseBuild = verboseBuild;
|
||||
settings.buildCores = buildCores;
|
||||
settings.useSubstitutes = useSubstitutes;
|
||||
|
||||
for (auto & i : overrides) {
|
||||
auto & name(i.first);
|
||||
auto & value(i.second);
|
||||
|
||||
auto setSubstituters = [&](Setting<Strings> & res) {
|
||||
if (name != res.name && res.aliases.count(name) == 0)
|
||||
return false;
|
||||
StringSet trusted = settings.trustedSubstituters;
|
||||
for (auto & s : settings.substituters.get())
|
||||
trusted.insert(s);
|
||||
Strings subs;
|
||||
auto ss = tokenizeString<Strings>(value);
|
||||
for (auto & s : ss)
|
||||
if (trusted.count(s))
|
||||
subs.push_back(s);
|
||||
else
|
||||
warn("ignoring untrusted substituter '%s'", s);
|
||||
res = subs;
|
||||
return true;
|
||||
};
|
||||
|
||||
try {
|
||||
if (name == "ssh-auth-sock") // obsolete
|
||||
;
|
||||
else if (trusted
|
||||
|| name == settings.buildTimeout.name
|
||||
|| name == "connect-timeout"
|
||||
|| (name == "builders" && value == ""))
|
||||
settings.set(name, value);
|
||||
else if (setSubstituters(settings.substituters))
|
||||
;
|
||||
else if (setSubstituters(settings.extraSubstituters))
|
||||
;
|
||||
else
|
||||
warn("ignoring the user-specified setting '%s', because it is a restricted setting and you are not a trusted user", name);
|
||||
} catch (UsageError & e) {
|
||||
warn(e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static void performOp(TunnelLogger * logger, ref<Store> store,
|
||||
bool trusted, unsigned int clientVersion,
|
||||
TrustedFlag trusted, RecursiveFlag recursive, unsigned int clientVersion,
|
||||
Source & from, BufferedSink & to, unsigned int op)
|
||||
{
|
||||
switch (op) {
|
||||
|
@ -464,70 +531,37 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
}
|
||||
|
||||
case wopSetOptions: {
|
||||
settings.keepFailed = readInt(from);
|
||||
settings.keepGoing = readInt(from);
|
||||
settings.tryFallback = readInt(from);
|
||||
verbosity = (Verbosity) readInt(from);
|
||||
settings.maxBuildJobs.assign(readInt(from));
|
||||
settings.maxSilentTime = readInt(from);
|
||||
|
||||
ClientSettings clientSettings;
|
||||
|
||||
clientSettings.keepFailed = readInt(from);
|
||||
clientSettings.keepGoing = readInt(from);
|
||||
clientSettings.tryFallback = readInt(from);
|
||||
clientSettings.verbosity = (Verbosity) readInt(from);
|
||||
clientSettings.maxBuildJobs = readInt(from);
|
||||
clientSettings.maxSilentTime = readInt(from);
|
||||
readInt(from); // obsolete useBuildHook
|
||||
settings.verboseBuild = lvlError == (Verbosity) readInt(from);
|
||||
clientSettings.verboseBuild = lvlError == (Verbosity) readInt(from);
|
||||
readInt(from); // obsolete logType
|
||||
readInt(from); // obsolete printBuildTrace
|
||||
settings.buildCores = readInt(from);
|
||||
settings.useSubstitutes = readInt(from);
|
||||
clientSettings.buildCores = readInt(from);
|
||||
clientSettings.useSubstitutes = readInt(from);
|
||||
|
||||
StringMap overrides;
|
||||
if (GET_PROTOCOL_MINOR(clientVersion) >= 12) {
|
||||
unsigned int n = readInt(from);
|
||||
for (unsigned int i = 0; i < n; i++) {
|
||||
string name = readString(from);
|
||||
string value = readString(from);
|
||||
overrides.emplace(name, value);
|
||||
clientSettings.overrides.emplace(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
logger->startWork();
|
||||
|
||||
for (auto & i : overrides) {
|
||||
auto & name(i.first);
|
||||
auto & value(i.second);
|
||||
|
||||
auto setSubstituters = [&](Setting<Strings> & res) {
|
||||
if (name != res.name && res.aliases.count(name) == 0)
|
||||
return false;
|
||||
StringSet trusted = settings.trustedSubstituters;
|
||||
for (auto & s : settings.substituters.get())
|
||||
trusted.insert(s);
|
||||
Strings subs;
|
||||
auto ss = tokenizeString<Strings>(value);
|
||||
for (auto & s : ss)
|
||||
if (trusted.count(s))
|
||||
subs.push_back(s);
|
||||
else
|
||||
warn("ignoring untrusted substituter '%s'", s);
|
||||
res = subs;
|
||||
return true;
|
||||
};
|
||||
|
||||
try {
|
||||
if (name == "ssh-auth-sock") // obsolete
|
||||
;
|
||||
else if (trusted
|
||||
|| name == settings.buildTimeout.name
|
||||
|| name == "connect-timeout"
|
||||
|| (name == "builders" && value == ""))
|
||||
settings.set(name, value);
|
||||
else if (setSubstituters(settings.substituters))
|
||||
;
|
||||
else if (setSubstituters(settings.extraSubstituters))
|
||||
;
|
||||
else
|
||||
warn("ignoring the user-specified setting '%s', because it is a restricted setting and you are not a trusted user", name);
|
||||
} catch (UsageError & e) {
|
||||
warn(e.what());
|
||||
}
|
||||
}
|
||||
// FIXME: use some setting in recursive mode. Will need to use
|
||||
// non-global variables.
|
||||
if (!recursive)
|
||||
clientSettings.apply(trusted);
|
||||
|
||||
logger->stopWork();
|
||||
break;
|
||||
|
@ -694,11 +728,12 @@ void processConnection(
|
|||
ref<Store> store,
|
||||
FdSource & from,
|
||||
FdSink & to,
|
||||
bool trusted,
|
||||
TrustedFlag trusted,
|
||||
RecursiveFlag recursive,
|
||||
const std::string & userName,
|
||||
uid_t userId)
|
||||
{
|
||||
MonitorFdHup monitor(from.fd);
|
||||
auto monitor = !recursive ? std::make_unique<MonitorFdHup>(from.fd) : nullptr;
|
||||
|
||||
/* Exchange the greeting. */
|
||||
unsigned int magic = readInt(from);
|
||||
|
@ -712,7 +747,9 @@ void processConnection(
|
|||
|
||||
auto tunnelLogger = new TunnelLogger(to, clientVersion);
|
||||
auto prevLogger = nix::logger;
|
||||
logger = tunnelLogger;
|
||||
// FIXME
|
||||
if (!recursive)
|
||||
logger = tunnelLogger;
|
||||
|
||||
unsigned int opCount = 0;
|
||||
|
||||
|
@ -721,8 +758,10 @@ void processConnection(
|
|||
prevLogger->log(lvlDebug, fmt("%d operations", opCount));
|
||||
});
|
||||
|
||||
if (GET_PROTOCOL_MINOR(clientVersion) >= 14 && readInt(from))
|
||||
setAffinityTo(readInt(from));
|
||||
if (GET_PROTOCOL_MINOR(clientVersion) >= 14 && readInt(from)) {
|
||||
auto affinity = readInt(from);
|
||||
setAffinityTo(affinity);
|
||||
}
|
||||
|
||||
readInt(from); // obsolete reserveSpace
|
||||
|
||||
|
@ -760,7 +799,7 @@ void processConnection(
|
|||
opCount++;
|
||||
|
||||
try {
|
||||
performOp(tunnelLogger, store, trusted, clientVersion, from, to, op);
|
||||
performOp(tunnelLogger, store, trusted, recursive, clientVersion, from, to, op);
|
||||
} catch (Error & e) {
|
||||
/* If we're not in a state where we can send replies, then
|
||||
something went wrong processing the input of the
|
||||
|
|
|
@ -3,11 +3,15 @@
|
|||
|
||||
namespace nix::daemon {
|
||||
|
||||
enum TrustedFlag : bool { NotTrusted = false, Trusted = true };
|
||||
enum RecursiveFlag : bool { NotRecursive = false, Recursive = true };
|
||||
|
||||
void processConnection(
|
||||
ref<Store> store,
|
||||
FdSource & from,
|
||||
FdSink & to,
|
||||
bool trusted,
|
||||
TrustedFlag trusted,
|
||||
RecursiveFlag recursive,
|
||||
const std::string & userName,
|
||||
uid_t userId);
|
||||
|
||||
|
|
|
@ -95,7 +95,7 @@ StringSet Settings::getDefaultSystemFeatures()
|
|||
/* For backwards compatibility, accept some "features" that are
|
||||
used in Nixpkgs to route builds to certain machines but don't
|
||||
actually require anything special on the machines. */
|
||||
StringSet features{"nixos-test", "benchmark", "big-parallel"};
|
||||
StringSet features{"nixos-test", "benchmark", "big-parallel", "recursive-nix"};
|
||||
|
||||
#if __linux__
|
||||
if (access("/dev/kvm", R_OK | W_OK) == 0)
|
||||
|
|
|
@ -193,7 +193,7 @@ static void daemonLoop(char * * argv)
|
|||
|
||||
closeOnExec(remote.get());
|
||||
|
||||
bool trusted = false;
|
||||
TrustedFlag trusted = NotTrusted;
|
||||
PeerInfo peer = getPeerInfo(remote.get());
|
||||
|
||||
struct passwd * pw = peer.uidKnown ? getpwuid(peer.uid) : 0;
|
||||
|
@ -206,7 +206,7 @@ static void daemonLoop(char * * argv)
|
|||
Strings allowedUsers = settings.allowedUsers;
|
||||
|
||||
if (matchUser(user, group, trustedUsers))
|
||||
trusted = true;
|
||||
trusted = Trusted;
|
||||
|
||||
if ((!trusted && !matchUser(user, group, allowedUsers)) || group == settings.buildUsersGroup)
|
||||
throw Error(format("user '%1%' is not allowed to connect to the Nix daemon") % user);
|
||||
|
@ -240,7 +240,7 @@ static void daemonLoop(char * * argv)
|
|||
/* Handle the connection. */
|
||||
FdSource from(remote.get());
|
||||
FdSink to(remote.get());
|
||||
processConnection(openUncachedStore(), from, to, trusted, user, peer.uid);
|
||||
processConnection(openUncachedStore(), from, to, trusted, NotRecursive, user, peer.uid);
|
||||
|
||||
exit(0);
|
||||
}, options);
|
||||
|
@ -322,7 +322,7 @@ static int _main(int argc, char * * argv)
|
|||
} else {
|
||||
FdSource from(STDIN_FILENO);
|
||||
FdSink to(STDOUT_FILENO);
|
||||
processConnection(openUncachedStore(), from, to, true, "root", 0);
|
||||
processConnection(openUncachedStore(), from, to, Trusted, NotRecursive, "root", 0);
|
||||
}
|
||||
} else {
|
||||
daemonLoop(argv);
|
||||
|
|
|
@ -30,7 +30,8 @@ nix_tests = \
|
|||
search.sh \
|
||||
nix-copy-ssh.sh \
|
||||
post-hook.sh \
|
||||
function-trace.sh
|
||||
function-trace.sh \
|
||||
recursive.sh
|
||||
# parallel.sh
|
||||
|
||||
install-tests += $(foreach x, $(nix_tests), tests/$(x))
|
||||
|
|
69
tests/recursive.sh
Normal file
69
tests/recursive.sh
Normal file
|
@ -0,0 +1,69 @@
|
|||
source common.sh
|
||||
|
||||
clearStore
|
||||
|
||||
export unreachable=$(nix add-to-store ./recursive.sh)
|
||||
|
||||
nix --experimental-features 'nix-command recursive-nix' build -o $TEST_ROOT/result -L '(
|
||||
with import ./config.nix;
|
||||
with import <nix/config.nix>;
|
||||
mkDerivation {
|
||||
name = "recursive";
|
||||
dummy = builtins.toFile "dummy" "bla bla";
|
||||
SHELL = shell;
|
||||
|
||||
# Note: this is a string without context.
|
||||
unreachable = builtins.getEnv "unreachable";
|
||||
|
||||
requiredSystemFeatures = [ "recursive-nix" ];
|
||||
|
||||
buildCommand = '\'\''
|
||||
mkdir $out
|
||||
PATH=${nixBinDir}:$PATH
|
||||
opts="--experimental-features nix-command"
|
||||
|
||||
# Check that we can query/build paths in our input closure.
|
||||
nix $opts path-info $dummy
|
||||
nix $opts build $dummy
|
||||
|
||||
# Make sure we cannot query/build paths not in out input closure.
|
||||
[[ -e $unreachable ]]
|
||||
(! nix $opts path-info $unreachable)
|
||||
(! nix $opts build $unreachable)
|
||||
|
||||
# Add something to the store.
|
||||
echo foobar > foobar
|
||||
foobar=$(nix $opts add-to-store ./foobar)
|
||||
|
||||
nix $opts path-info $foobar
|
||||
nix $opts build $foobar
|
||||
|
||||
# Add it to our closure.
|
||||
ln -s $foobar $out/foobar
|
||||
|
||||
[[ $(nix $opts path-info --all | wc -l) -eq 3 ]]
|
||||
|
||||
# Build a derivation.
|
||||
nix $opts build -L '\''(
|
||||
derivation {
|
||||
name = "inner1";
|
||||
builder = builtins.getEnv "SHELL";
|
||||
system = builtins.getEnv "system";
|
||||
fnord = builtins.toFile "fnord" "fnord";
|
||||
args = [ "-c" "echo $fnord blaat > $out" ];
|
||||
}
|
||||
)'\''
|
||||
|
||||
[[ $(nix $opts path-info --json ./result) =~ fnord ]]
|
||||
|
||||
ln -s $(nix $opts path-info ./result) $out/inner1
|
||||
'\'\'';
|
||||
})
|
||||
'
|
||||
|
||||
[[ $(cat $TEST_ROOT/result/inner1) =~ blaat ]]
|
||||
|
||||
# Make sure the recursively created paths are in the closure.
|
||||
nix path-info -r $TEST_ROOT/result | grep foobar
|
||||
nix path-info -r $TEST_ROOT/result | grep fnord
|
||||
nix path-info -r $TEST_ROOT/result | grep inner1
|
Loading…
Reference in a new issue