about summary refs log tree commit diff
path: root/src/libstore/build.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/libstore/build.cc')
-rw-r--r--src/libstore/build.cc144
1 files changed, 93 insertions, 51 deletions
diff --git a/src/libstore/build.cc b/src/libstore/build.cc
index e0eb702a4f82..b61ea5298e1e 100644
--- a/src/libstore/build.cc
+++ b/src/libstore/build.cc
@@ -768,7 +768,14 @@ private:
     GoalState state;
 
     /* Stuff we need to pass to initChild(). */
-    typedef map<Path, Path> DirsInChroot; // maps target path to source path
+    struct ChrootPath {
+        Path source;
+        bool optional;
+        ChrootPath(Path source = "", bool optional = false)
+            : source(source), optional(optional)
+        { }
+    };
+    typedef map<Path, ChrootPath> DirsInChroot; // maps target path to source path
     DirsInChroot dirsInChroot;
     typedef map<string, string> Environment;
     Environment env;
@@ -804,6 +811,9 @@ private:
        result. */
     ValidPathInfos prevInfos;
 
+    const uid_t sandboxUid = 1000;
+    const gid_t sandboxGid = 100;
+
 public:
     DerivationGoal(const Path & drvPath, const StringSet & wantedOutputs,
         Worker & worker, BuildMode buildMode = bmNormal);
@@ -1011,7 +1021,7 @@ void DerivationGoal::loadDerivation()
     trace("loading derivation");
 
     if (nrFailed != 0) {
-        printMsg(lvlError, format("cannot build missing derivation ‘%1%’") % drvPath);
+        printError(format("cannot build missing derivation ‘%1%’") % drvPath);
         done(BuildResult::MiscFailure);
         return;
     }
@@ -1165,7 +1175,7 @@ void DerivationGoal::repairClosure()
     PathSet broken;
     for (auto & i : outputClosure) {
         if (worker.pathContentsGood(i)) continue;
-        printMsg(lvlError, format("found corrupted or missing path ‘%1%’ in the output closure of ‘%2%’") % i % drvPath);
+        printError(format("found corrupted or missing path ‘%1%’ in the output closure of ‘%2%’") % i % drvPath);
         Path drvPath2 = outputsToDrv[i];
         if (drvPath2 == "")
             addWaitee(worker.makeSubstitutionGoal(i, true));
@@ -1198,7 +1208,7 @@ void DerivationGoal::inputsRealised()
     if (nrFailed != 0) {
         if (!useDerivation)
             throw Error(format("some dependencies of ‘%1%’ are missing") % drvPath);
-        printMsg(lvlError,
+        printError(
             format("cannot build derivation ‘%1%’: %2% dependencies couldn't be built")
             % drvPath % nrFailed);
         done(BuildResult::DependencyFailed);
@@ -1363,7 +1373,7 @@ void DerivationGoal::tryToBuild()
         startBuilder();
 
     } catch (BuildError & e) {
-        printMsg(lvlError, e.msg());
+        printError(e.msg());
         outputLocks.unlock();
         buildUser.release();
         worker.permanentFailure = true;
@@ -1512,7 +1522,7 @@ void DerivationGoal::buildDone()
 
     } catch (BuildError & e) {
         if (!hook)
-            printMsg(lvlError, e.msg());
+            printError(e.msg());
         outputLocks.unlock();
         buildUser.release();
 
@@ -1641,7 +1651,7 @@ void DerivationGoal::startBuilder()
         nrRounds > 1 ? "building path(s) %1% (round %2%/%3%)" :
         "building path(s) %1%");
     f.exceptions(boost::io::all_error_bits ^ boost::io::too_many_args_bit);
-    printMsg(lvlInfo, f % showPaths(missingPaths) % curRound % nrRounds);
+    printInfo(f % showPaths(missingPaths) % curRound % nrRounds);
 
     /* Right platform? */
     if (!drv->canBuildLocally()) {
@@ -1862,20 +1872,30 @@ void DerivationGoal::startBuilder()
 
         dirsInChroot.clear();
 
-        for (auto & i : dirs) {
+        for (auto i : dirs) {
+            if (i.empty()) continue;
+            bool optional = false;
+            if (i[i.size() - 1] == '?') {
+                optional = true;
+                i.pop_back();
+            }
             size_t p = i.find('=');
             if (p == string::npos)
-                dirsInChroot[i] = i;
+                dirsInChroot[i] = {i, optional};
             else
-                dirsInChroot[string(i, 0, p)] = string(i, p + 1);
+                dirsInChroot[string(i, 0, p)] = {string(i, p + 1), optional};
         }
         dirsInChroot[tmpDirInSandbox] = tmpDir;
 
         /* Add the closure of store paths to the chroot. */
         PathSet closure;
         for (auto & i : dirsInChroot)
-            if (worker.store.isInStore(i.second))
-                worker.store.computeFSClosure(worker.store.toStorePath(i.second), closure);
+            try {
+                if (worker.store.isInStore(i.second.source))
+                    worker.store.computeFSClosure(worker.store.toStorePath(i.second.source), closure);
+            } catch (Error & e) {
+                throw Error(format("while processing ‘build-sandbox-paths’: %s") % e.what());
+            }
         for (auto & i : closure)
             dirsInChroot[i] = i;
 
@@ -1937,14 +1957,18 @@ void DerivationGoal::startBuilder()
         createDirs(chrootRootDir + "/etc");
 
         writeFile(chrootRootDir + "/etc/passwd",
-            "root:x:0:0:Nix build user:/:/noshell\n"
-            "nobody:x:65534:65534:Nobody:/:/noshell\n");
+            (format(
+                "root:x:0:0:Nix build user:/:/noshell\n"
+                "nixbld:x:%1%:%2%:Nix build user:/:/noshell\n"
+                "nobody:x:65534:65534:Nobody:/:/noshell\n") % sandboxUid % sandboxGid).str());
 
         /* Declare the build user's group so that programs get a consistent
            view of the system (e.g., "id -gn"). */
         writeFile(chrootRootDir + "/etc/group",
-            "root:x:0:\n"
-            "nobody:x:65534:\n");
+            (format(
+                "root:x:0:\n"
+                "nixbld:!:%1%:\n"
+                "nogroup:x:65534:\n") % sandboxGid).str());
 
         /* Create /etc/hosts with localhost entry. */
         if (!fixedOutput)
@@ -2126,7 +2150,12 @@ void DerivationGoal::startBuilder()
         Pid helper = startProcess([&]() {
 
             /* Drop additional groups here because we can't do it
-               after we've created the new user namespace. */
+               after we've created the new user namespace.  FIXME:
+               this means that if we're not root in the parent
+               namespace, we can't drop additional groups; they will
+               be mapped to nogroup in the child namespace. There does
+               not seem to be a workaround for this. (But who can tell
+               from reading user_namespaces(7)?)*/
             if (getuid() == 0 && setgroups(0, 0) == -1)
                 throw SysError("setgroups failed");
 
@@ -2159,19 +2188,19 @@ void DerivationGoal::startBuilder()
         if (!string2Int<pid_t>(readLine(builderOut.readSide.get()), tmp)) abort();
         pid = tmp;
 
-        /* Set the UID/GID mapping of the builder's user
-           namespace such that root maps to the build user, or to the
-           calling user (if build users are disabled). */
-        uid_t targetUid = buildUser.enabled() ? buildUser.getUID() : getuid();
-        uid_t targetGid = buildUser.enabled() ? buildUser.getGID() : getgid();
+        /* 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.enabled() ? buildUser.getUID() : getuid();
+        uid_t hostGid = buildUser.enabled() ? buildUser.getGID() : getgid();
 
         writeFile("/proc/" + std::to_string(pid) + "/uid_map",
-            (format("0 %d 1") % targetUid).str());
+            (format("%d %d 1") % sandboxUid % hostUid).str());
 
         writeFile("/proc/" + std::to_string(pid) + "/setgroups", "deny");
 
         writeFile("/proc/" + std::to_string(pid) + "/gid_map",
-            (format("0 %d 1") % targetGid).str());
+            (format("%d %d 1") % sandboxGid % hostGid).str());
 
         /* Signal the builder that we've updated its user
            namespace. */
@@ -2200,7 +2229,7 @@ void DerivationGoal::startBuilder()
             if (msg.size() == 1) break;
             throw Error(string(msg, 1));
         }
-        printMsg(lvlDebug, msg);
+        debug(msg);
     }
 }
 
@@ -2284,6 +2313,8 @@ void DerivationGoal::runChild()
                 ss.push_back("/dev/tty");
                 ss.push_back("/dev/urandom");
                 ss.push_back("/dev/zero");
+                ss.push_back("/dev/ptmx");
+                ss.push_back("/dev/pts");
                 createSymlink("/proc/self/fd", chrootRootDir + "/dev/fd");
                 createSymlink("/proc/self/fd/0", chrootRootDir + "/dev/stdin");
                 createSymlink("/proc/self/fd/1", chrootRootDir + "/dev/stdout");
@@ -2307,12 +2338,16 @@ void DerivationGoal::runChild()
                environment. */
             for (auto & i : dirsInChroot) {
                 struct stat st;
-                Path source = i.second;
+                Path source = i.second.source;
                 Path target = chrootRootDir + i.first;
                 if (source == "/proc") continue; // backwards compatibility
                 debug(format("bind mounting ‘%1%’ to ‘%2%’") % source % target);
-                if (stat(source.c_str(), &st) == -1)
-                    throw SysError(format("getting attributes of path ‘%1%’") % source);
+                if (stat(source.c_str(), &st) == -1) {
+                    if (i.second.optional && errno == ENOENT)
+                        continue;
+                    else
+                        throw SysError(format("getting attributes of path ‘%1%’") % source);
+                }
                 if (S_ISDIR(st.st_mode))
                     createDirs(target);
                 else {
@@ -2330,9 +2365,14 @@ void DerivationGoal::runChild()
 
             /* 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, 0) == -1)
+            if (pathExists("/dev/shm") && mount("none", (chrootRootDir + "/dev/shm").c_str(), "tmpfs", 0,
+                    fmt("size=%s", settings.get("sandbox-dev-shm-size", std::string("50%"))).c_str()) == -1)
                 throw SysError("mounting /dev/shm");
 
+#if 0
+            // FIXME: can't figure out how to do this in a user
+            // namespace.
+
             /* 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
@@ -2349,6 +2389,7 @@ void DerivationGoal::runChild()
                    Linux versions, it is created with permissions 0.  */
                 chmod_(chrootRootDir + "/dev/pts/ptmx", 0666);
             }
+#endif
 
             /* Do the chroot(). */
             if (chdir(chrootRootDir.c_str()) == -1)
@@ -2369,11 +2410,12 @@ void DerivationGoal::runChild()
             if (rmdir("real-root") == -1)
                 throw SysError("cannot remove real-root directory");
 
-            /* Become root in the user namespace, which corresponds to
-               the build user or calling user in the parent namespace. */
-            if (setgid(0) == -1)
+            /* 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(0) == -1)
+            if (setuid(sandboxUid) == -1)
                 throw SysError("setuid failed");
 
             setUser = false;
@@ -2685,7 +2727,7 @@ void DerivationGoal::registerOutputs()
         /* Apply hash rewriting if necessary. */
         bool rewritten = false;
         if (!outputRewrites.empty()) {
-            printMsg(lvlError, format("warning: rewriting hashes in ‘%1%’; cross fingers") % path);
+            printError(format("warning: rewriting hashes in ‘%1%’; cross fingers") % path);
 
             /* Canonicalise first.  This ensures that the path we're
                rewriting doesn't contain a hard link to /etc/shadow or
@@ -2724,7 +2766,7 @@ void DerivationGoal::registerOutputs()
             Hash h2 = recursive ? hashPath(h.type, actualPath).first : hashFile(h.type, actualPath);
             if (buildMode == bmHash) {
                 Path dest = worker.store.makeFixedOutputPath(recursive, h2, drv->env["name"]);
-                printMsg(lvlError, format("build produced path ‘%1%’ with %2% hash ‘%3%’")
+                printError(format("build produced path ‘%1%’ with %2% hash ‘%3%’")
                     % dest % printHashType(h.type) % printHash16or32(h2));
                 if (worker.store.isValidPath(dest))
                     return;
@@ -2948,7 +2990,7 @@ void DerivationGoal::deleteTmpDir(bool force)
 {
     if (tmpDir != "") {
         if (settings.keepFailed && !force) {
-            printMsg(lvlError,
+            printError(
                 format("note: keeping build directory ‘%2%’")
                 % drvPath % tmpDir);
             chmod(tmpDir.c_str(), 0755);
@@ -2967,7 +3009,7 @@ void DerivationGoal::handleChildOutput(int fd, const string & data)
     {
         logSize += data.size();
         if (settings.maxLogSize && logSize > settings.maxLogSize) {
-            printMsg(lvlError,
+            printError(
                 format("%1% killed after writing more than %2% bytes of log output")
                 % getName() % settings.maxLogSize);
             killChild();
@@ -2990,7 +3032,7 @@ void DerivationGoal::handleChildOutput(int fd, const string & data)
     }
 
     if (hook && fd == hook->fromHook.readSide.get())
-        printMsg(lvlError, data); // FIXME?
+        printError(data); // FIXME?
 }
 
 
@@ -3004,7 +3046,7 @@ void DerivationGoal::handleEOF(int fd)
 void DerivationGoal::flushLine()
 {
     if (settings.verboseBuild)
-        printMsg(lvlInfo, filterANSIEscapes(currentLogLine, true));
+        printError(filterANSIEscapes(currentLogLine, true));
     else {
         logTail.push_back(currentLogLine);
         if (logTail.size() > settings.logLines) logTail.pop_front();
@@ -3217,7 +3259,7 @@ void SubstitutionGoal::tryNext()
        signature. LocalStore::addToStore() also checks for this, but
        only after we've downloaded the path. */
     if (worker.store.requireSigs && !info->checkSignatures(worker.store, worker.store.publicKeys)) {
-        printMsg(lvlInfo, format("warning: substituter ‘%s’ does not have a valid signature for path ‘%s’")
+        printInfo(format("warning: substituter ‘%s’ does not have a valid signature for path ‘%s’")
             % sub->getUri() % storePath);
         tryNext();
         return;
@@ -3268,7 +3310,7 @@ void SubstitutionGoal::tryToRun()
         return;
     }
 
-    printMsg(lvlInfo, format("fetching path ‘%1%’...") % storePath);
+    printInfo(format("fetching path ‘%1%’...") % storePath);
 
     outPipe.create();
 
@@ -3304,7 +3346,7 @@ void SubstitutionGoal::finished()
     try {
         promise.get_future().get();
     } catch (Error & e) {
-        printMsg(lvlInfo, e.msg());
+        printInfo(e.msg());
 
         /* Try the next substitute. */
         state = &SubstitutionGoal::tryNext;
@@ -3466,7 +3508,7 @@ void Worker::childTerminated(Goal * goal, bool wakeSleepers)
 {
     auto i = std::find_if(children.begin(), children.end(),
         [&](const Child & child) { return child.goal2 == goal; });
-    assert(i != children.end());
+    if (i == children.end()) return;
 
     if (i->inBuildSlot) {
         assert(nrLocalBuilds > 0);
@@ -3598,7 +3640,7 @@ void Worker::waitForInput()
     if (!waitingForAWhile.empty()) {
         useTimeout = true;
         if (lastWokenUp == 0)
-            printMsg(lvlError, "waiting for locks or build slots...");
+            printError("waiting for locks or build slots...");
         if (lastWokenUp == 0 || lastWokenUp > before) lastWokenUp = before;
         timeout.tv_sec = std::max((time_t) 1, (time_t) (lastWokenUp + settings.pollInterval - before));
     } else lastWokenUp = 0;
@@ -3661,7 +3703,7 @@ void Worker::waitForInput()
             j->respectTimeouts &&
             after - j->lastOutput >= (time_t) settings.maxSilentTime)
         {
-            printMsg(lvlError,
+            printError(
                 format("%1% timed out after %2% seconds of silence")
                 % goal->getName() % settings.maxSilentTime);
             goal->timedOut();
@@ -3672,7 +3714,7 @@ void Worker::waitForInput()
             j->respectTimeouts &&
             after - j->timeStarted >= (time_t) settings.buildTimeout)
         {
-            printMsg(lvlError,
+            printError(
                 format("%1% timed out after %2% seconds")
                 % goal->getName() % settings.buildTimeout);
             goal->timedOut();
@@ -3700,7 +3742,7 @@ bool Worker::pathContentsGood(const Path & path)
 {
     std::map<Path, bool>::iterator i = pathContentsGoodCache.find(path);
     if (i != pathContentsGoodCache.end()) return i->second;
-    printMsg(lvlInfo, format("checking path ‘%1%’...") % path);
+    printInfo(format("checking path ‘%1%’...") % path);
     auto info = store.queryPathInfo(path);
     bool res;
     if (!pathExists(path))
@@ -3711,7 +3753,7 @@ bool Worker::pathContentsGood(const Path & path)
         res = info->narHash == nullHash || info->narHash == current.first;
     }
     pathContentsGoodCache[path] = res;
-    if (!res) printMsg(lvlError, format("path ‘%1%’ is corrupted or missing!") % path);
+    if (!res) printError(format("path ‘%1%’ is corrupted or missing!") % path);
     return res;
 }
 
@@ -3749,7 +3791,7 @@ void LocalStore::buildPaths(const PathSet & drvPaths, BuildMode buildMode)
         }
 
     if (!failed.empty())
-        throw Error(format("build of %1% failed") % showPaths(failed), worker.exitStatus());
+        throw Error(worker.exitStatus(), "build of %s failed", showPaths(failed));
 }
 
 
@@ -3785,7 +3827,7 @@ void LocalStore::ensurePath(const Path & path)
     worker.run(goals);
 
     if (goal->getExitCode() != Goal::ecSuccess)
-        throw Error(format("path ‘%1%’ does not exist and cannot be created") % path, worker.exitStatus());
+        throw Error(worker.exitStatus(), "path ‘%s’ does not exist and cannot be created", path);
 }
 
 
@@ -3806,7 +3848,7 @@ void LocalStore::repairPath(const Path & path)
             goals.insert(worker.makeDerivationGoal(deriver, StringSet(), bmRepair));
             worker.run(goals);
         } else
-            throw Error(format("cannot repair path ‘%1%’") % path, worker.exitStatus());
+            throw Error(worker.exitStatus(), "cannot repair path ‘%s’", path);
     }
 }