aboutsummaryrefslogtreecommitdiff
path: root/src/libstore/build
diff options
context:
space:
mode:
authorEelco Dolstra <edolstra@gmail.com>2022-11-18 10:39:28 +0100
committerEelco Dolstra <edolstra@gmail.com>2022-11-18 10:39:28 +0100
commit128910ba23f586ba1765a137ecff23cfd22cff89 (patch)
tree2adc969d5ae2fa2f470000e9bbc7b8f8b0c3edd4 /src/libstore/build
parentf1ab082ac4f589a36a9eb0cd98d1cc235eedc419 (diff)
Separate cgroup support from auto-uid-allocation
The new experimental feature 'cgroups' enables the use of cgroups for all builds. This allows better containment and enables setting resource limits and getting some build stats.
Diffstat (limited to 'src/libstore/build')
-rw-r--r--src/libstore/build/local-derivation-goal.cc114
-rw-r--r--src/libstore/build/local-derivation-goal.hh7
2 files changed, 91 insertions, 30 deletions
diff --git a/src/libstore/build/local-derivation-goal.cc b/src/libstore/build/local-derivation-goal.cc
index e652c425c..2d1e093ca 100644
--- a/src/libstore/build/local-derivation-goal.cc
+++ b/src/libstore/build/local-derivation-goal.cc
@@ -15,6 +15,7 @@
#include "topo-sort.hh"
#include "callback.hh"
#include "json-utils.hh"
+#include "cgroup.hh"
#include <regex>
#include <queue>
@@ -129,26 +130,36 @@ void LocalDerivationGoal::killChild()
if (pid != -1) {
worker.childTerminated(this);
- if (buildUser) {
- /* If we're using a build user, then there is a tricky
- race condition: if we kill the build user before the
- child has done its setuid() to the build user uid, then
- it won't be killed, and we'll potentially lock up in
- pid.wait(). So also send a conventional kill to the
- child. */
- ::kill(-pid, SIGKILL); /* ignore the result */
- buildUser->kill();
- pid.wait();
- } else
- pid.kill();
+ /* If we're using a build user, then there is a tricky race
+ condition: if we kill the build user before the child has
+ done its setuid() to the build user uid, then it won't be
+ killed, and we'll potentially lock up in pid.wait(). So
+ also send a conventional kill to the child. */
+ ::kill(-pid, SIGKILL); /* ignore the result */
+
+ killSandbox();
- assert(pid == -1);
+ pid.wait();
}
DerivationGoal::killChild();
}
+void LocalDerivationGoal::killSandbox()
+{
+ if (cgroup) {
+ destroyCgroup(*cgroup);
+ }
+
+ else if (buildUser) {
+ auto uid = buildUser->getUID();
+ assert(uid != 0);
+ killUser(uid);
+ }
+}
+
+
void LocalDerivationGoal::tryLocalBuild() {
unsigned int curBuilds = worker.getNrLocalBuilds();
if (curBuilds >= settings.maxBuildJobs) {
@@ -169,10 +180,6 @@ void LocalDerivationGoal::tryLocalBuild() {
worker.waitForAWhile(shared_from_this());
return;
}
-
- /* Make sure that no other processes are executing under this
- uid. */
- buildUser->kill();
}
actLock.reset();
@@ -263,7 +270,7 @@ void LocalDerivationGoal::cleanupPostChildKill()
malicious user from leaving behind a process that keeps files
open and modifies them after they have been chown'ed to
root. */
- if (buildUser) buildUser->kill();
+ killSandbox();
/* Terminate the recursive Nix daemon. */
stopDaemon();
@@ -356,6 +363,55 @@ static void linkOrCopy(const Path & from, const Path & to)
void LocalDerivationGoal::startBuilder()
{
+ if ((buildUser && buildUser->getUIDCount() != 1)
+ || settings.isExperimentalFeatureEnabled(Xp::Cgroups))
+ {
+ #if __linux__
+ auto ourCgroups = getCgroups("/proc/self/cgroup");
+ auto ourCgroup = ourCgroups[""];
+ if (ourCgroup == "")
+ throw Error("cannot determine cgroup name from /proc/self/cgroup");
+
+ auto ourCgroupPath = canonPath("/sys/fs/cgroup/" + ourCgroup);
+
+ if (!pathExists(ourCgroupPath))
+ throw Error("expected cgroup directory '%s'", ourCgroupPath);
+
+ static std::atomic<unsigned int> counter{0};
+
+ cgroup = buildUser
+ ? fmt("%s/nix-build-uid-%d", ourCgroupPath, buildUser->getUID())
+ : fmt("%s/nix-build-pid-%d-%d", ourCgroupPath, 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);
+ }
+
+ #else
+ throw Error("cgroups are not supported on this platform");
+ #endif
+ }
+
+ /* Make sure that no other processes are executing under the
+ sandbox uids. This must be done before any chownToBuilder()
+ calls. */
+ killSandbox();
+
/* Right platform? */
if (!parsedDrv->canBuildLocally(worker.store))
throw Error("a '%s' with features {%s} is required to build '%s', but I am a '%s' with features {%s}",
@@ -646,13 +702,13 @@ void LocalDerivationGoal::startBuilder()
dirsInChroot.erase(worker.store.printStorePath(*i.second.second));
}
- if (buildUser) {
- if (auto cgroup = buildUser->getCgroup()) {
- chownToBuilder(*cgroup);
- chownToBuilder(*cgroup + "/cgroup.procs");
- chownToBuilder(*cgroup + "/cgroup.threads");
- //chownToBuilder(*cgroup + "/cgroup.subtree_control");
- }
+ 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");
}
#else
@@ -965,10 +1021,8 @@ void LocalDerivationGoal::startBuilder()
}
/* Move the child into its own cgroup. */
- if (buildUser) {
- if (auto cgroup = buildUser->getCgroup())
- writeFile(*cgroup + "/cgroup.procs", fmt("%d", (pid_t) pid));
- }
+ 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");
@@ -1838,7 +1892,7 @@ void LocalDerivationGoal::runChild()
/* 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 (buildUser && buildUser->getUIDCount() != 1 && unshare(CLONE_NEWCGROUP) == -1)
+ if (cgroup && unshare(CLONE_NEWCGROUP) == -1)
throw SysError("unsharing cgroup namespace");
/* Do the chroot(). */
diff --git a/src/libstore/build/local-derivation-goal.hh b/src/libstore/build/local-derivation-goal.hh
index f92280aa1..1ec6b3649 100644
--- a/src/libstore/build/local-derivation-goal.hh
+++ b/src/libstore/build/local-derivation-goal.hh
@@ -15,6 +15,9 @@ struct LocalDerivationGoal : public DerivationGoal
/* The process ID of the builder. */
Pid pid;
+ /* The cgroup of the builder, if any. */
+ std::optional<Path> cgroup;
+
/* The temporary directory. */
Path tmpDir;
@@ -197,6 +200,10 @@ struct LocalDerivationGoal : public DerivationGoal
/* Forcibly kill the child process, if any. */
void killChild() override;
+ /* Kill any processes running under the build user UID or in the
+ cgroup of the build. */
+ void killSandbox();
+
/* Create alternative path calculated from but distinct from the
input, so we can avoid overwriting outputs (or other store paths)
that already exist. */