summary refs log tree commit diff
path: root/nix
diff options
context:
space:
mode:
Diffstat (limited to 'nix')
-rw-r--r--nix/libstore/build.cc313
-rw-r--r--nix/libstore/gc.cc69
-rw-r--r--nix/libstore/globals.cc92
-rw-r--r--nix/libstore/globals.hh26
-rw-r--r--nix/libstore/local-store.cc80
-rw-r--r--nix/libstore/local-store.hh19
-rw-r--r--nix/libstore/optimise-store.cc16
-rw-r--r--nix/libstore/remote-store.cc31
-rw-r--r--nix/libstore/remote-store.hh5
-rw-r--r--nix/libstore/store-api.hh4
-rw-r--r--nix/libstore/worker-protocol.hh2
-rw-r--r--nix/libutil/archive.cc171
-rw-r--r--nix/libutil/archive.hh12
-rw-r--r--nix/libutil/serialise.cc27
-rw-r--r--nix/libutil/serialise.hh11
-rw-r--r--nix/libutil/types.hh17
-rw-r--r--nix/libutil/util.cc176
-rw-r--r--nix/libutil/util.hh30
-rw-r--r--nix/nix-daemon/guix-daemon.cc27
-rw-r--r--nix/nix-daemon/nix-daemon.cc134
-rw-r--r--nix/scripts/substitute-binary.in11
-rw-r--r--nix/scripts/substitute.in11
22 files changed, 757 insertions, 527 deletions
diff --git a/nix/libstore/build.cc b/nix/libstore/build.cc
index f38cd29940..009fcb2c0c 100644
--- a/nix/libstore/build.cc
+++ b/nix/libstore/build.cc
@@ -57,9 +57,8 @@
 #include <netinet/ip.h>
 #endif
 
-#if HAVE_SYS_PERSONALITY_H
+#if __linux__
 #include <sys/personality.h>
-#define CAN_DO_LINUX32_BUILDS
 #endif
 
 #if HAVE_STATVFS
@@ -85,8 +84,12 @@ class Goal;
 typedef std::shared_ptr<Goal> GoalPtr;
 typedef std::weak_ptr<Goal> WeakGoalPtr;
 
+struct CompareGoalPtrs {
+    bool operator() (const GoalPtr & a, const GoalPtr & b);
+};
+
 /* Set of goals. */
-typedef set<GoalPtr> Goals;
+typedef set<GoalPtr, CompareGoalPtrs> Goals;
 typedef list<WeakGoalPtr> WeakGoals;
 
 /* A map of paths to goals (and the other way around). */
@@ -173,11 +176,20 @@ public:
        (important!), etc. */
     virtual void cancel(bool timeout) = 0;
 
+    virtual string key() = 0;
+
 protected:
     void amDone(ExitCode result);
 };
 
 
+bool CompareGoalPtrs::operator() (const GoalPtr & a, const GoalPtr & b) {
+    string s1 = a->key();
+    string s2 = b->key();
+    return s1 < s2;
+}
+
+
 /* A mapping used to remember for each child process to what goal it
    belongs, and file descriptors for receiving log data and output
    path creation commands. */
@@ -238,6 +250,9 @@ public:
        failure). */
     bool permanentFailure;
 
+    /* Set if at least one derivation had a timeout. */
+    bool timedOut;
+
     LocalStore & store;
 
     std::shared_ptr<HookInstance> hook;
@@ -301,6 +316,7 @@ public:
 void addToWeakGoals(WeakGoals & goals, GoalPtr p)
 {
     // FIXME: necessary?
+    // FIXME: O(n)
     foreach (WeakGoals::iterator, i, goals)
         if (i->lock() == p) return;
     goals.push_back(p);
@@ -374,8 +390,6 @@ void Goal::trace(const format & f)
 /* Common initialisation performed in child processes. */
 static void commonChildInit(Pipe & logPipe)
 {
-    restoreAffinity();
-
     /* Put the child in a separate session (and thus a separate
        process group) so that it has no controlling terminal (meaning
        that e.g. ssh cannot open /dev/tty) and it doesn't receive
@@ -590,7 +604,9 @@ HookInstance::HookInstance()
 {
     debug("starting build hook");
 
-    Path buildHook = absPath(getEnv("NIX_BUILD_HOOK"));
+    Path buildHook = getEnv("NIX_BUILD_HOOK");
+    if (string(buildHook, 0, 1) != "/") buildHook = settings.nixLibexecDir + "/nix/" + buildHook;
+    buildHook = canonPath(buildHook);
 
     /* Create a pipe to get the output of the child. */
     fromHook.create();
@@ -602,44 +618,30 @@ HookInstance::HookInstance()
     builderOut.create();
 
     /* Fork the hook. */
-    pid = maybeVfork();
-    switch (pid) {
-
-    case -1:
-        throw SysError("unable to fork");
+    pid = startProcess([&]() {
 
-    case 0:
-        try { /* child */
+        commonChildInit(fromHook);
 
-            commonChildInit(fromHook);
+        if (chdir("/") == -1) throw SysError("changing into `/");
 
-            if (chdir("/") == -1) throw SysError("changing into `/");
+        /* Dup the communication pipes. */
+        if (dup2(toHook.readSide, STDIN_FILENO) == -1)
+            throw SysError("dupping to-hook read side");
 
-            /* Dup the communication pipes. */
-            if (dup2(toHook.readSide, STDIN_FILENO) == -1)
-                throw SysError("dupping to-hook read side");
+        /* Use fd 4 for the builder's stdout/stderr. */
+        if (dup2(builderOut.writeSide, 4) == -1)
+            throw SysError("dupping builder's stdout/stderr");
 
-            /* Use fd 4 for the builder's stdout/stderr. */
-            if (dup2(builderOut.writeSide, 4) == -1)
-                throw SysError("dupping builder's stdout/stderr");
+        execl(buildHook.c_str(), buildHook.c_str(), settings.thisSystem.c_str(),
+            (format("%1%") % settings.maxSilentTime).str().c_str(),
+            (format("%1%") % settings.printBuildTrace).str().c_str(),
+            (format("%1%") % settings.buildTimeout).str().c_str(),
+            NULL);
 
-            execl(buildHook.c_str(), buildHook.c_str(), settings.thisSystem.c_str(),
-                (format("%1%") % settings.maxSilentTime).str().c_str(),
-                (format("%1%") % settings.printBuildTrace).str().c_str(),
-                (format("%1%") % settings.buildTimeout).str().c_str(),
-                NULL);
+        throw SysError(format("executing `%1%'") % buildHook);
+    });
 
-            throw SysError(format("executing `%1%'") % buildHook);
-
-        } catch (std::exception & e) {
-            writeToStderr("build hook error: " + string(e.what()) + "\n");
-        }
-        _exit(1);
-    }
-
-    /* parent */
     pid.setSeparatePG(true);
-    pid.setKillSignal(SIGTERM);
     fromHook.writeSide.close();
     toHook.readSide.close();
 }
@@ -648,7 +650,8 @@ HookInstance::HookInstance()
 HookInstance::~HookInstance()
 {
     try {
-        pid.kill();
+        toHook.writeSide.close();
+        pid.kill(true);
     } catch (...) {
         ignoreException();
     }
@@ -784,17 +787,21 @@ private:
        outputs to allow hard links between outputs. */
     InodesSeen inodesSeen;
 
-    /* Magic exit code denoting that setting up the child environment
-       failed.  (It's possible that the child actually returns the
-       exit code, but ah well.) */
-    const static int childSetupFailed = 189;
-
 public:
     DerivationGoal(const Path & drvPath, const StringSet & wantedOutputs, Worker & worker, BuildMode buildMode = bmNormal);
     ~DerivationGoal();
 
     void cancel(bool timeout);
 
+    string key()
+    {
+        /* Ensure that derivations get built in order of their name,
+           i.e. a derivation named "aardvark" always comes before
+           "baboon". And substitution goals always happen before
+           derivation goals (due to "b$"). */
+        return "b$" + storePathToName(drvPath) + "$" + drvPath;
+    }
+
     void work();
 
     Path getDrvPath()
@@ -879,13 +886,9 @@ DerivationGoal::~DerivationGoal()
 {
     /* Careful: we should never ever throw an exception from a
        destructor. */
-    try {
-        killChild();
-        deleteTmpDir(false);
-        closeLogFile();
-    } catch (...) {
-        ignoreException();
-    }
+    try { killChild(); } catch (...) { ignoreException(); }
+    try { deleteTmpDir(false); } catch (...) { ignoreException(); }
+    try { closeLogFile(); } catch (...) { ignoreException(); }
 }
 
 
@@ -956,6 +959,11 @@ void DerivationGoal::init()
     /* The first thing to do is to make sure that the derivation
        exists.  If it doesn't, it may be created through a
        substitute. */
+    if (buildMode == bmNormal && worker.store.isValidPath(drvPath)) {
+        haveDerivation();
+        return;
+    }
+
     addWaitee(worker.makeSubstitutionGoal(drvPath));
 
     state = &DerivationGoal::haveDerivation;
@@ -1209,7 +1217,7 @@ static string get(const StringPairs & map, const string & key)
 static bool canBuildLocally(const string & platform)
 {
     return platform == settings.thisSystem
-#ifdef CAN_DO_LINUX32_BUILDS
+#if __linux__
         || (platform == "i686-linux" && settings.thisSystem == "x86_64-linux")
 #endif
         ;
@@ -1433,9 +1441,6 @@ void DerivationGoal::buildDone()
                     if (pathExists(chrootRootDir + *i))
                         rename((chrootRootDir + *i).c_str(), i->c_str());
 
-            if (WIFEXITED(status) && WEXITSTATUS(status) == childSetupFailed)
-                throw Error(format("failed to set up the build environment for `%1%'") % drvPath);
-
             if (diskFull)
                 printMsg(lvlError, "note: build failure may have been caused by lack of free disk space");
 
@@ -1469,37 +1474,41 @@ void DerivationGoal::buildDone()
         outputLocks.unlock();
 
     } catch (BuildError & e) {
-        printMsg(lvlError, e.msg());
+        if (!hook)
+            printMsg(lvlError, e.msg());
         outputLocks.unlock();
         buildUser.release();
 
-        /* When using a build hook, the hook will return a remote
-           build failure using exit code 100.  Anything else is a hook
-           problem. */
-        bool hookError = hook &&
-            (!WIFEXITED(status) || WEXITSTATUS(status) != 100);
+        if (hook && WIFEXITED(status) && WEXITSTATUS(status) == 101) {
+            if (settings.printBuildTrace)
+                printMsg(lvlError, format("@ build-failed %1% - timeout") % drvPath);
+            worker.timedOut = true;
+        }
 
-        if (settings.printBuildTrace) {
-            if (hook && hookError)
+        else if (hook && (!WIFEXITED(status) || WEXITSTATUS(status) != 100)) {
+            if (settings.printBuildTrace)
                 printMsg(lvlError, format("@ hook-failed %1% - %2% %3%")
                     % drvPath % status % e.msg());
-            else
+        }
+
+        else {
+            if (settings.printBuildTrace)
                 printMsg(lvlError, format("@ build-failed %1% - %2% %3%")
                     % drvPath % 1 % e.msg());
+            worker.permanentFailure = !fixedOutput && !diskFull;
+
+            /* Register the outputs of this build as "failed" so we
+               won't try to build them again (negative caching).
+               However, don't do this for fixed-output derivations,
+               since they're likely to fail for transient reasons
+               (e.g., fetchurl not being able to access the network).
+               Hook errors (like communication problems with the
+               remote machine) shouldn't be cached either. */
+            if (settings.cacheFailure && !fixedOutput && !diskFull)
+                foreach (DerivationOutputs::iterator, i, drv.outputs)
+                    worker.store.registerFailedPath(i->second.path);
         }
 
-        /* Register the outputs of this build as "failed" so we won't
-           try to build them again (negative caching).  However, don't
-           do this for fixed-output derivations, since they're likely
-           to fail for transient reasons (e.g., fetchurl not being
-           able to access the network).  Hook errors (like
-           communication problems with the remote machine) shouldn't
-           be cached either. */
-        if (settings.cacheFailure && !hookError && !fixedOutput)
-            foreach (DerivationOutputs::iterator, i, drv.outputs)
-                worker.store.registerFailedPath(i->second.path);
-
-        worker.permanentFailure = !hookError && !fixedOutput && !diskFull;
         amDone(ecFailed);
         return;
     }
@@ -1825,12 +1834,15 @@ void DerivationGoal::startBuilder()
 
         /* Bind-mount a user-configurable set of directories from the
            host file system. */
-        foreach (StringSet::iterator, i, settings.dirsInChroot) {
-            size_t p = i->find('=');
+        PathSet dirs = tokenizeString<StringSet>(settings.get("build-chroot-dirs", string(DEFAULT_CHROOT_DIRS)));
+        PathSet dirs2 = tokenizeString<StringSet>(settings.get("build-extra-chroot-dirs", string("")));
+        dirs.insert(dirs2.begin(), dirs2.end());
+        for (auto & i : dirs) {
+            size_t p = i.find('=');
             if (p == string::npos)
-                dirsInChroot[*i] = *i;
+                dirsInChroot[i] = i;
             else
-                dirsInChroot[string(*i, 0, p)] = string(*i, p + 1);
+                dirsInChroot[string(i, 0, p)] = string(i, p + 1);
         }
         dirsInChroot[tmpDir] = tmpDir;
 
@@ -1969,10 +1981,15 @@ void DerivationGoal::startBuilder()
     worker.childStarted(shared_from_this(), pid,
         singleton<set<int> >(builderOut.readSide), true, true);
 
+    /* Check if setting up the build environment failed. */
+    string msg = readLine(builderOut.readSide);
+    if (!msg.empty()) throw Error(msg);
+
     if (settings.printBuildTrace) {
         printMsg(lvlError, format("@ build-started %1% - %2% %3%")
             % drvPath % drv.platform % logFile);
     }
+
 }
 
 
@@ -1981,10 +1998,14 @@ void DerivationGoal::initChild()
     /* Warning: in the child we should absolutely not make any SQLite
        calls! */
 
-    bool inSetup = true;
-
     try { /* child */
 
+        _writeToStderr = 0;
+
+        restoreAffinity();
+
+        commonChildInit(builderOut);
+
 #if CHROOT_ENABLED
         if (useChroot) {
             /* Initialise the loopback interface. */
@@ -2088,9 +2109,9 @@ void DerivationGoal::initChild()
                     throw SysError("mounting /dev/pts");
                 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);
+                /* Make sure /dev/pts/ptmx is world-writable.  With some
+                   Linux versions, it is created with permissions 0.  */
+                chmod_(chrootRootDir + "/dev/pts/ptmx", 0666);
             }
 
             /* Do the chroot().  Below we do a chdir() to the
@@ -2103,15 +2124,13 @@ void DerivationGoal::initChild()
         }
 #endif
 
-        commonChildInit(builderOut);
-
         if (chdir(tmpDir.c_str()) == -1)
             throw SysError(format("changing into `%1%'") % tmpDir);
 
         /* Close all other file descriptors. */
         closeMostFDs(set<int>());
 
-#ifdef CAN_DO_LINUX32_BUILDS
+#if __linux__
         /* Change the personality to 32-bit if we're doing an
            i686-linux build on an x86_64-linux machine. */
         struct utsname utsbuf;
@@ -2119,7 +2138,7 @@ void DerivationGoal::initChild()
         if (drv.platform == "i686-linux" &&
             (settings.thisSystem == "x86_64-linux" ||
              (!strcmp(utsbuf.sysname, "Linux") && !strcmp(utsbuf.machine, "x86_64")))) {
-            if (personality(0x0008 | 0x8000000 /* == PER_LINUX32_3GB */) == -1)
+            if (personality(PER_LINUX32) == -1)
                 throw SysError("cannot set i686-linux personality");
         }
 
@@ -2129,6 +2148,11 @@ void DerivationGoal::initChild()
             int cur = personality(0xffffffff);
             if (cur != -1) personality(cur | 0x0020000 /* == UNAME26 */);
         }
+
+        /* Disable address space randomization for improved
+           determinism. */
+        int cur = personality(0xffffffff);
+        if (cur != -1) personality(cur | ADDR_NO_RANDOMIZE);
 #endif
 
         /* Fill in the environment. */
@@ -2167,21 +2191,28 @@ void DerivationGoal::initChild()
         /* Fill in the arguments. */
         string builderBasename = baseNameOf(drv.builder);
         args.push_back(builderBasename.c_str());
-        foreach (Strings::iterator, i, drv.args)
-            args.push_back(rewriteHashes(*i, rewritesToTmp).c_str());
+        foreach (Strings::iterator, i, drv.args) {
+            auto re = rewriteHashes(*i, rewritesToTmp);
+            auto cstr = new char[re.length()+1];
+            std::strcpy(cstr, re.c_str());
+
+            args.push_back(cstr);
+        }
         args.push_back(0);
 
         restoreSIGPIPE();
 
+        /* Indicate that we managed to set up the build environment. */
+        writeToStderr("\n");
+
         /* Execute the program.  This should not return. */
-        inSetup = false;
         execve(program.c_str(), (char * *) &args[0], (char * *) envArr);
 
         throw SysError(format("executing `%1%'") % drv.builder);
 
     } catch (std::exception & e) {
-        writeToStderr("build error: " + string(e.what()) + "\n");
-        _exit(inSetup ? childSetupFailed : 1);
+        writeToStderr("while setting up the build environment: " + string(e.what()) + "\n");
+        _exit(1);
     }
 
     abort(); /* never reached */
@@ -2333,7 +2364,7 @@ void DerivationGoal::registerOutputs()
         if (buildMode == bmCheck) {
             ValidPathInfo info = worker.store.queryPathInfo(path);
             if (hash.first != info.hash)
-                throw Error(format("derivation `%2%' may not be deterministic: hash mismatch in output `%1%'") % drvPath % path);
+                throw Error(format("derivation `%1%' may not be deterministic: hash mismatch in output `%2%'") % drvPath % path);
             continue;
         }
 
@@ -2347,16 +2378,36 @@ void DerivationGoal::registerOutputs()
                 debug(format("referenced input: `%1%'") % *i);
         }
 
-        /* If the derivation specifies an `allowedReferences'
-           attribute (containing a list of paths that the output may
-           refer to), check that all references are in that list.  !!!
-           allowedReferences should really be per-output. */
-        if (drv.env.find("allowedReferences") != drv.env.end()) {
-            PathSet allowed = parseReferenceSpecifiers(drv, get(drv.env, "allowedReferences"));
-            foreach (PathSet::iterator, i, references)
-                if (allowed.find(*i) == allowed.end())
-                    throw BuildError(format("output is not allowed to refer to path `%1%'") % *i);
-        }
+        /* Enforce `allowedReferences' and friends. */
+        auto checkRefs = [&](const string & attrName, bool allowed, bool recursive) {
+            if (drv.env.find(attrName) == drv.env.end()) return;
+
+            PathSet spec = parseReferenceSpecifiers(drv, get(drv.env, attrName));
+
+            PathSet used;
+            if (recursive) {
+                /* Our requisites are the union of the closures of our references. */
+                for (auto & i : references)
+                    /* Don't call computeFSClosure on ourselves. */
+                    if (actualPath != i)
+                        computeFSClosure(worker.store, i, used);
+            } else
+                used = references;
+
+            for (auto & i : used)
+                if (allowed) {
+                    if (spec.find(i) == spec.end())
+                        throw BuildError(format("output (`%1%') is not allowed to refer to path `%2%'") % actualPath % i);
+                } else {
+                    if (spec.find(i) != spec.end())
+                        throw BuildError(format("output (`%1%') is not allowed to refer to path `%2%'") % actualPath % i);
+                }
+        };
+
+        checkRefs("allowedReferences", true, false);
+        checkRefs("allowedRequisites", true, true);
+        checkRefs("disallowedReferences", false, false);
+        checkRefs("disallowedRequisites", false, true);
 
         worker.store.optimisePath(path); // FIXME: combine with scanForReferences()
 
@@ -2586,6 +2637,13 @@ public:
 
     void cancel(bool timeout);
 
+    string key()
+    {
+        /* "a$" ensures substitution goals happen before derivation
+           goals. */
+        return "a$" + storePathToName(storePath) + "$" + storePath;
+    }
+
     void work();
 
     /* The states. */
@@ -2781,32 +2839,18 @@ void SubstitutionGoal::tryToRun()
     const char * * argArr = strings2CharPtrs(args);
 
     /* Fork the substitute program. */
-    pid = maybeVfork();
-
-    switch (pid) {
+    pid = startProcess([&]() {
 
-    case -1:
-        throw SysError("unable to fork");
+        commonChildInit(logPipe);
 
-    case 0:
-        try { /* child */
+        if (dup2(outPipe.writeSide, STDOUT_FILENO) == -1)
+            throw SysError("cannot dup output pipe into stdout");
 
-            commonChildInit(logPipe);
+        execv(sub.c_str(), (char * *) argArr);
 
-            if (dup2(outPipe.writeSide, STDOUT_FILENO) == -1)
-                throw SysError("cannot dup output pipe into stdout");
+        throw SysError(format("executing `%1%'") % sub);
+    });
 
-            execv(sub.c_str(), (char * *) argArr);
-
-            throw SysError(format("executing `%1%'") % sub);
-
-        } catch (std::exception & e) {
-            writeToStderr("substitute error: " + string(e.what()) + "\n");
-        }
-        _exit(1);
-    }
-
-    /* parent */
     pid.setSeparatePG(true);
     pid.setKillSignal(SIGTERM);
     outPipe.writeSide.close();
@@ -2944,6 +2988,7 @@ Worker::Worker(LocalStore & store)
     nrLocalBuilds = 0;
     lastWokenUp = 0;
     permanentFailure = false;
+    timedOut = false;
 }
 
 
@@ -3109,15 +3154,19 @@ void Worker::run(const Goals & _topGoals)
 
         checkInterrupt();
 
-        /* Call every wake goal. */
+        /* Call every wake goal (in the ordering established by
+           CompareGoalPtrs). */
         while (!awake.empty() && !topGoals.empty()) {
-            WeakGoals awake2(awake);
+            Goals awake2;
+            for (auto & i : awake) {
+                GoalPtr goal = i.lock();
+                if (goal) awake2.insert(goal);
+            }
             awake.clear();
-            foreach (WeakGoals::iterator, i, awake2) {
+            for (auto & goal : awake2) {
                 checkInterrupt();
-                GoalPtr goal = i->lock();
-                if (goal) goal->work();
-                if (topGoals.empty()) break;
+                goal->work();
+                if (topGoals.empty()) break; // stuff may have been cancelled
             }
         }
 
@@ -3255,6 +3304,7 @@ void Worker::waitForInput()
                 format("%1% timed out after %2% seconds of silence")
                 % goal->getName() % settings.maxSilentTime);
             goal->cancel(true);
+            timedOut = true;
         }
 
         else if (goal->getExitCode() == Goal::ecBusy &&
@@ -3266,6 +3316,7 @@ void Worker::waitForInput()
                 format("%1% timed out after %2% seconds")
                 % goal->getName() % settings.buildTimeout);
             goal->cancel(true);
+            timedOut = true;
         }
     }
 
@@ -3282,7 +3333,7 @@ void Worker::waitForInput()
 
 unsigned int Worker::exitStatus()
 {
-    return permanentFailure ? 100 : 1;
+    return timedOut ? 101 : (permanentFailure ? 100 : 1);
 }
 
 
diff --git a/nix/libstore/gc.cc b/nix/libstore/gc.cc
index f90edac1cd..f98e02c1e2 100644
--- a/nix/libstore/gc.cc
+++ b/nix/libstore/gc.cc
@@ -115,7 +115,10 @@ Path addPermRoot(StoreAPI & store, const Path & _storePath,
                     % gcRoot % rootsDir);
         }
 
-        makeSymlink(gcRoot, storePath);
+        if (baseNameOf(gcRoot) == baseNameOf(storePath))
+            writeFile(gcRoot, "");
+        else
+            makeSymlink(gcRoot, storePath);
     }
 
     /* Check that the root can be found by the garbage collector.
@@ -142,11 +145,6 @@ Path addPermRoot(StoreAPI & store, const Path & _storePath,
 }
 
 
-/* The file to which we write our temporary roots. */
-static Path fnTempRoots;
-static AutoCloseFD fdTempRoots;
-
-
 void LocalStore::addTempRoot(const Path & path)
 {
     /* Create the temporary roots file for this process. */
@@ -201,27 +199,6 @@ void LocalStore::addTempRoot(const Path & path)
 }
 
 
-void removeTempRoots()
-{
-    if (fdTempRoots != -1) {
-        fdTempRoots.close();
-        unlink(fnTempRoots.c_str());
-    }
-}
-
-
-/* Automatically clean up the temporary roots file when we exit. */
-struct RemoveTempRoots
-{
-    ~RemoveTempRoots()
-    {
-        removeTempRoots();
-    }
-};
-
-static RemoveTempRoots autoRemoveTempRoots __attribute__((unused));
-
-
 typedef std::shared_ptr<AutoCloseFD> FDPtr;
 typedef list<FDPtr> FDs;
 
@@ -230,11 +207,11 @@ static void readTempRoots(PathSet & tempRoots, FDs & fds)
 {
     /* Read the `temproots' directory for per-process temporary root
        files. */
-    Strings tempRootFiles = readDirectory(
+    DirEntries tempRootFiles = readDirectory(
         (format("%1%/%2%") % settings.nixStateDir % tempRootsDir).str());
 
-    foreach (Strings::iterator, i, tempRootFiles) {
-        Path path = (format("%1%/%2%/%3%") % settings.nixStateDir % tempRootsDir % *i).str();
+    for (auto & i : tempRootFiles) {
+        Path path = (format("%1%/%2%/%3%") % settings.nixStateDir % tempRootsDir % i.name).str();
 
         debug(format("reading temporary root file `%1%'") % path);
         FDPtr fd(new AutoCloseFD(open(path.c_str(), O_RDWR, 0666)));
@@ -294,19 +271,19 @@ static void foundRoot(StoreAPI & store,
 }
 
 
-static void findRoots(StoreAPI & store, const Path & path, Roots & roots)
+static void findRoots(StoreAPI & store, const Path & path, unsigned char type, Roots & roots)
 {
     try {
 
-        struct stat st = lstat(path);
+        if (type == DT_UNKNOWN)
+            type = getFileType(path);
 
-        if (S_ISDIR(st.st_mode)) {
-            Strings names = readDirectory(path);
-            foreach (Strings::iterator, i, names)
-                findRoots(store, path + "/" + *i, roots);
+        if (type == DT_DIR) {
+            for (auto & i : readDirectory(path))
+                findRoots(store, path + "/" + i.name, i.type, roots);
         }
 
-        else if (S_ISLNK(st.st_mode)) {
+        else if (type == DT_LNK) {
             Path target = readLink(path);
             if (isInStore(target))
                 foundRoot(store, path, target, roots);
@@ -328,6 +305,12 @@ static void findRoots(StoreAPI & store, const Path & path, Roots & roots)
             }
         }
 
+        else if (type == DT_REG) {
+            Path storePath = settings.nixStore + "/" + baseNameOf(path);
+            if (store.isValidPath(storePath))
+                roots[path] = storePath;
+        }
+
     }
 
     catch (SysError & e) {
@@ -345,9 +328,10 @@ Roots LocalStore::findRoots()
     Roots roots;
 
     /* Process direct roots in {gcroots,manifests,profiles}. */
-    nix::findRoots(*this, settings.nixStateDir + "/" + gcRootsDir, roots);
-    nix::findRoots(*this, settings.nixStateDir + "/manifests", roots);
-    nix::findRoots(*this, settings.nixStateDir + "/profiles", roots);
+    nix::findRoots(*this, settings.nixStateDir + "/" + gcRootsDir, DT_UNKNOWN, roots);
+    if (pathExists(settings.nixStateDir + "/manifests"))
+        nix::findRoots(*this, settings.nixStateDir + "/manifests", DT_UNKNOWN, roots);
+    nix::findRoots(*this, settings.nixStateDir + "/profiles", DT_UNKNOWN, roots);
 
     return roots;
 }
@@ -449,7 +433,6 @@ void LocalStore::deletePathRecursive(GCState & state, const Path & path)
         // if the path was not valid, need to determine the actual
         // size.
         state.bytesInvalidated += size;
-        // Mac OS X cannot rename directories if they are read-only.
         if (chmod(path.c_str(), st.st_mode | S_IWUSR) == -1)
             throw SysError(format("making `%1%' writable") % path);
         Path tmp = state.trashDir + "/" + baseNameOf(path);
@@ -649,7 +632,7 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
 
     /* After this point the set of roots or temporary roots cannot
        increase, since we hold locks on everything.  So everything
-       that is not reachable from `roots'. */
+       that is not reachable from `roots' is garbage. */
 
     if (state.shouldDelete) {
         if (pathExists(state.trashDir)) deleteGarbage(state, state.trashDir);
@@ -741,7 +724,7 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
     }
 
     /* While we're at it, vacuum the database. */
-    if (options.action == GCOptions::gcDeleteDead) vacuumDB();
+    //if (options.action == GCOptions::gcDeleteDead) vacuumDB();
 }
 
 
diff --git a/nix/libstore/globals.cc b/nix/libstore/globals.cc
index 86fa56739c..bb08a7d0b0 100644
--- a/nix/libstore/globals.cc
+++ b/nix/libstore/globals.cc
@@ -2,6 +2,7 @@
 
 #include "globals.hh"
 #include "util.hh"
+#include "archive.hh"
 
 #include <map>
 #include <algorithm>
@@ -55,6 +56,7 @@ Settings::Settings()
     envKeepDerivations = false;
     lockCPU = getEnv("NIX_AFFINITY_HACK", "1") == "1";
     showTrace = false;
+    enableImportNative = false;
 }
 
 
@@ -112,35 +114,61 @@ void Settings::set(const string & name, const string & value)
 }
 
 
+string Settings::get(const string & name, const string & def)
+{
+    auto i = settings.find(name);
+    if (i == settings.end()) return def;
+    return i->second;
+}
+
+
+Strings Settings::get(const string & name, const Strings & def)
+{
+    auto i = settings.find(name);
+    if (i == settings.end()) return def;
+    return tokenizeString<Strings>(i->second);
+}
+
+
+bool Settings::get(const string & name, bool def)
+{
+    bool res = def;
+    _get(res, name);
+    return res;
+}
+
+
 void Settings::update()
 {
-    get(tryFallback, "build-fallback");
-    get(maxBuildJobs, "build-max-jobs");
-    get(buildCores, "build-cores");
-    get(thisSystem, "system");
-    get(maxSilentTime, "build-max-silent-time");
-    get(buildTimeout, "build-timeout");
-    get(reservedSize, "gc-reserved-space");
-    get(fsyncMetadata, "fsync-metadata");
-    get(useSQLiteWAL, "use-sqlite-wal");
-    get(syncBeforeRegistering, "sync-before-registering");
-    get(useSubstitutes, "build-use-substitutes");
-    get(buildUsersGroup, "build-users-group");
-    get(useChroot, "build-use-chroot");
-    get(dirsInChroot, "build-chroot-dirs");
-    get(impersonateLinux26, "build-impersonate-linux-26");
-    get(keepLog, "build-keep-log");
-    get(compressLog, "build-compress-log");
-    get(maxLogSize, "build-max-log-size");
-    get(cacheFailure, "build-cache-failure");
-    get(pollInterval, "build-poll-interval");
-    get(checkRootReachability, "gc-check-reachability");
-    get(gcKeepOutputs, "gc-keep-outputs");
-    get(gcKeepDerivations, "gc-keep-derivations");
-    get(autoOptimiseStore, "auto-optimise-store");
-    get(envKeepDerivations, "env-keep-derivations");
-    get(sshSubstituterHosts, "ssh-substituter-hosts");
-    get(useSshSubstituter, "use-ssh-substituter");
+    _get(tryFallback, "build-fallback");
+    _get(maxBuildJobs, "build-max-jobs");
+    _get(buildCores, "build-cores");
+    _get(thisSystem, "system");
+    _get(maxSilentTime, "build-max-silent-time");
+    _get(buildTimeout, "build-timeout");
+    _get(reservedSize, "gc-reserved-space");
+    _get(fsyncMetadata, "fsync-metadata");
+    _get(useSQLiteWAL, "use-sqlite-wal");
+    _get(syncBeforeRegistering, "sync-before-registering");
+    _get(useSubstitutes, "build-use-substitutes");
+    _get(buildUsersGroup, "build-users-group");
+    _get(useChroot, "build-use-chroot");
+    _get(impersonateLinux26, "build-impersonate-linux-26");
+    _get(keepLog, "build-keep-log");
+    _get(compressLog, "build-compress-log");
+    _get(maxLogSize, "build-max-log-size");
+    _get(cacheFailure, "build-cache-failure");
+    _get(pollInterval, "build-poll-interval");
+    _get(checkRootReachability, "gc-check-reachability");
+    _get(gcKeepOutputs, "gc-keep-outputs");
+    _get(gcKeepDerivations, "gc-keep-derivations");
+    _get(autoOptimiseStore, "auto-optimise-store");
+    _get(envKeepDerivations, "env-keep-derivations");
+    _get(sshSubstituterHosts, "ssh-substituter-hosts");
+    _get(useSshSubstituter, "use-ssh-substituter");
+    _get(logServers, "log-servers");
+    _get(enableImportNative, "allow-unsafe-native-code-during-evaluation");
+    _get(useCaseHack, "use-case-hack");
 
     string subs = getEnv("NIX_SUBSTITUTERS", "default");
     if (subs == "default") {
@@ -158,7 +186,7 @@ void Settings::update()
 }
 
 
-void Settings::get(string & res, const string & name)
+void Settings::_get(string & res, const string & name)
 {
     SettingsMap::iterator i = settings.find(name);
     if (i == settings.end()) return;
@@ -166,7 +194,7 @@ void Settings::get(string & res, const string & name)
 }
 
 
-void Settings::get(bool & res, const string & name)
+void Settings::_get(bool & res, const string & name)
 {
     SettingsMap::iterator i = settings.find(name);
     if (i == settings.end()) return;
@@ -177,7 +205,7 @@ void Settings::get(bool & res, const string & name)
 }
 
 
-void Settings::get(StringSet & res, const string & name)
+void Settings::_get(StringSet & res, const string & name)
 {
     SettingsMap::iterator i = settings.find(name);
     if (i == settings.end()) return;
@@ -186,7 +214,7 @@ void Settings::get(StringSet & res, const string & name)
     res.insert(ss.begin(), ss.end());
 }
 
-void Settings::get(Strings & res, const string & name)
+void Settings::_get(Strings & res, const string & name)
 {
     SettingsMap::iterator i = settings.find(name);
     if (i == settings.end()) return;
@@ -194,7 +222,7 @@ void Settings::get(Strings & res, const string & name)
 }
 
 
-template<class N> void Settings::get(N & res, const string & name)
+template<class N> void Settings::_get(N & res, const string & name)
 {
     SettingsMap::iterator i = settings.find(name);
     if (i == settings.end()) return;
diff --git a/nix/libstore/globals.hh b/nix/libstore/globals.hh
index 711c365294..c17e10d7c3 100644
--- a/nix/libstore/globals.hh
+++ b/nix/libstore/globals.hh
@@ -21,6 +21,12 @@ struct Settings {
 
     void set(const string & name, const string & value);
 
+    string get(const string & name, const string & def);
+
+    Strings get(const string & name, const Strings & def);
+
+    bool get(const string & name, bool def);
+
     void update();
 
     string pack();
@@ -142,10 +148,6 @@ struct Settings {
     /* Whether to build in chroot. */
     bool useChroot;
 
-    /* The directories from the host filesystem to be included in the
-       chroot. */
-    StringSet dirsInChroot;
-
     /* Set of ssh connection strings for the ssh substituter */
     Strings sshSubstituterHosts;
 
@@ -197,14 +199,20 @@ struct Settings {
     /* Whether to show a stack trace if Nix evaluation fails. */
     bool showTrace;
 
+    /* A list of URL prefixes that can return Nix build logs. */
+    Strings logServers;
+
+    /* Whether the importNative primop should be enabled */
+    bool enableImportNative;
+
 private:
     SettingsMap settings, overrides;
 
-    void get(string & res, const string & name);
-    void get(bool & res, const string & name);
-    void get(StringSet & res, const string & name);
-    void get(Strings & res, const string & name);
-    template<class N> void get(N & res, const string & name);
+    void _get(string & res, const string & name);
+    void _get(bool & res, const string & name);
+    void _get(StringSet & res, const string & name);
+    void _get(Strings & res, const string & name);
+    template<class N> void _get(N & res, const string & name);
 };
 
 
diff --git a/nix/libstore/local-store.cc b/nix/libstore/local-store.cc
index 2c3d65215c..a115f65847 100644
--- a/nix/libstore/local-store.cc
+++ b/nix/libstore/local-store.cc
@@ -358,7 +358,17 @@ LocalStore::~LocalStore()
             i->second.to.close();
             i->second.from.close();
             i->second.error.close();
-            i->second.pid.wait(true);
+            if (i->second.pid != -1)
+                i->second.pid.wait(true);
+        }
+    } catch (...) {
+        ignoreException();
+    }
+
+    try {
+        if (fdTempRoots != -1) {
+            fdTempRoots.close();
+            unlink(fnTempRoots.c_str());
         }
     } catch (...) {
         ignoreException();
@@ -551,9 +561,9 @@ static void canonicalisePathMetaData_(const Path & path, uid_t fromUid, InodesSe
     if (lstat(path.c_str(), &st))
         throw SysError(format("getting attributes of path `%1%'") % path);
 
-    /* Really make sure that the path is of a supported type.  This
-       has already been checked in dumpPath(). */
-    assert(S_ISREG(st.st_mode) || S_ISDIR(st.st_mode) || S_ISLNK(st.st_mode));
+    /* Really make sure that the path is of a supported type. */
+    if (!(S_ISREG(st.st_mode) || S_ISDIR(st.st_mode) || S_ISLNK(st.st_mode)))
+        throw Error(format("file ‘%1%’ has an unsupported type") % path);
 
     /* Fail if the file is not owned by the build user.  This prevents
        us from messing up the ownership/permissions of files
@@ -593,9 +603,9 @@ static void canonicalisePathMetaData_(const Path & path, uid_t fromUid, InodesSe
     }
 
     if (S_ISDIR(st.st_mode)) {
-        Strings names = readDirectory(path);
-        foreach (Strings::iterator, i, names)
-            canonicalisePathMetaData_(path + "/" + *i, fromUid, inodesSeen);
+        DirEntries entries = readDirectory(path);
+        for (auto & i : entries)
+            canonicalisePathMetaData_(path + "/" + i.name, fromUid, inodesSeen);
     }
 }
 
@@ -1083,31 +1093,16 @@ void LocalStore::startSubstituter(const Path & substituter, RunningSubstituter &
 
     setSubstituterEnv();
 
-    run.pid = maybeVfork();
-
-    switch (run.pid) {
-
-    case -1:
-        throw SysError("unable to fork");
-
-    case 0: /* child */
-        try {
-            restoreAffinity();
-            if (dup2(toPipe.readSide, STDIN_FILENO) == -1)
-                throw SysError("dupping stdin");
-            if (dup2(fromPipe.writeSide, STDOUT_FILENO) == -1)
-                throw SysError("dupping stdout");
-            if (dup2(errorPipe.writeSide, STDERR_FILENO) == -1)
-                throw SysError("dupping stderr");
-            execl(substituter.c_str(), substituter.c_str(), "--query", NULL);
-            throw SysError(format("executing `%1%'") % substituter);
-        } catch (std::exception & e) {
-            std::cerr << "error: " << e.what() << std::endl;
-        }
-        _exit(1);
-    }
-
-    /* Parent. */
+    run.pid = startProcess([&]() {
+        if (dup2(toPipe.readSide, STDIN_FILENO) == -1)
+            throw SysError("dupping stdin");
+        if (dup2(fromPipe.writeSide, STDOUT_FILENO) == -1)
+            throw SysError("dupping stdout");
+        if (dup2(errorPipe.writeSide, STDERR_FILENO) == -1)
+            throw SysError("dupping stderr");
+        execl(substituter.c_str(), substituter.c_str(), "--query", NULL);
+        throw SysError(format("executing `%1%'") % substituter);
+    });
 
     run.program = baseNameOf(substituter);
     run.to = toPipe.writeSide.borrow();
@@ -1171,7 +1166,7 @@ string LocalStore::getLineFromSubstituter(RunningSubstituter & run)
             while (((p = err.find('\n')) != string::npos)
 		   || ((p = err.find('\r')) != string::npos)) {
 	        string thing(err, 0, p + 1);
-	        writeToStderr(run.program + ": " + thing);
+		writeToStderr(run.program + ": " + thing);
                 err = string(err, p + 1);
             }
         }
@@ -1503,7 +1498,8 @@ void LocalStore::exportPath(const Path & path, bool sign,
 {
     assertStorePath(path);
 
-    addTempRoot(path);
+    printMsg(lvlInfo, format("exporting path `%1%'") % path);
+
     if (!isValidPath(path))
         throw Error(format("path `%1%' is not valid") % path);
 
@@ -1613,8 +1609,6 @@ Path LocalStore::importPath(bool requireSignature, Source & source)
 
     Path dstPath = readStorePath(hashAndReadSource);
 
-    printMsg(lvlInfo, format("importing path `%1%'") % dstPath);
-
     PathSet references = readStorePaths<PathSet>(hashAndReadSource);
 
     Path deriver = readString(hashAndReadSource);
@@ -1747,8 +1741,8 @@ bool LocalStore::verifyStore(bool checkContents, bool repair)
     /* Acquire the global GC lock to prevent a garbage collection. */
     AutoCloseFD fdGCLock = openGCLock(ltWrite);
 
-    Paths entries = readDirectory(settings.nixStore);
-    PathSet store(entries.begin(), entries.end());
+    PathSet store;
+    for (auto & i : readDirectory(settings.nixStore)) store.insert(i.name);
 
     /* Check whether all valid paths actually exist. */
     printMsg(lvlInfo, "checking path existence...");
@@ -1898,9 +1892,8 @@ void LocalStore::markContentsGood(const Path & path)
 PathSet LocalStore::queryValidPathsOld()
 {
     PathSet paths;
-    Strings entries = readDirectory(settings.nixDBPath + "/info");
-    foreach (Strings::iterator, i, entries)
-        if (i->at(0) != '.') paths.insert(settings.nixStore + "/" + *i);
+    for (auto & i : readDirectory(settings.nixDBPath + "/info"))
+        if (i.name.at(0) != '.') paths.insert(settings.nixStore + "/" + i.name);
     return paths;
 }
 
@@ -1987,9 +1980,8 @@ static void makeMutable(const Path & path)
     if (!S_ISDIR(st.st_mode) && !S_ISREG(st.st_mode)) return;
 
     if (S_ISDIR(st.st_mode)) {
-        Strings names = readDirectory(path);
-        foreach (Strings::iterator, i, names)
-            makeMutable(path + "/" + *i);
+        for (auto & i : readDirectory(path))
+            makeMutable(path + "/" + i.name);
     }
 
     /* The O_NOFOLLOW is important to prevent us from changing the
diff --git a/nix/libstore/local-store.hh b/nix/libstore/local-store.hh
index 54331e448a..e0aabdba42 100644
--- a/nix/libstore/local-store.hh
+++ b/nix/libstore/local-store.hh
@@ -1,16 +1,12 @@
 #pragma once
 
 #include <string>
+#include <unordered_set>
 
 #include "store-api.hh"
 #include "util.hh"
 #include "pathlocks.hh"
 
-#if HAVE_TR1_UNORDERED_SET
-#include <tr1/unordered_set>
-#endif
-
-
 
 class sqlite3;
 class sqlite3_stmt;
@@ -171,6 +167,9 @@ public:
        files with the same contents. */
     void optimiseStore(OptimiseStats & stats);
 
+    /* Generic variant of the above method.  */
+    void optimiseStore();
+
     /* Optimise a single store path. */
     void optimisePath(const Path & path);
 
@@ -245,6 +244,10 @@ private:
 
     bool didSetSubstituterEnv;
 
+    /* The file to which we write our temporary roots. */
+    Path fnTempRoots;
+    AutoCloseFD fdTempRoots;
+
     int getSchema();
 
     void openDB(bool create);
@@ -306,11 +309,7 @@ private:
 
     void checkDerivationOutputs(const Path & drvPath, const Derivation & drv);
 
-#if HAVE_TR1_UNORDERED_SET
-    typedef std::tr1::unordered_set<ino_t> InodeHash;
-#else
-    typedef std::set<ino_t> InodeHash;
-#endif
+    typedef std::unordered_set<ino_t> InodeHash;
 
     InodeHash loadInodeHash();
     Strings readDirectoryIgnoringInodes(const Path & path, const InodeHash & inodeHash);
diff --git a/nix/libstore/optimise-store.cc b/nix/libstore/optimise-store.cc
index 67ee94a4bd..8ba9d1a263 100644
--- a/nix/libstore/optimise-store.cc
+++ b/nix/libstore/optimise-store.cc
@@ -225,6 +225,22 @@ void LocalStore::optimiseStore(OptimiseStats & stats)
     }
 }
 
+static string showBytes(unsigned long long bytes)
+{
+    return (format("%.2f MiB") % (bytes / (1024.0 * 1024.0))).str();
+}
+
+void LocalStore::optimiseStore()
+{
+    OptimiseStats stats;
+
+    optimiseStore(stats);
+
+    printMsg(lvlError,
+        format("%1% freed by hard-linking %2% files")
+        % showBytes(stats.bytesFreed)
+        % stats.filesLinked);
+}
 
 void LocalStore::optimisePath(const Path & path)
 {
diff --git a/nix/libstore/remote-store.cc b/nix/libstore/remote-store.cc
index 4619206932..448d9b6bc1 100644
--- a/nix/libstore/remote-store.cc
+++ b/nix/libstore/remote-store.cc
@@ -87,8 +87,7 @@ void RemoteStore::openConnection(bool reserveSpace)
         processStderr();
     }
     catch (Error & e) {
-        throw Error(format("cannot start worker (%1%)")
-            % e.msg());
+        throw Error(format("cannot start daemon worker: %1%") % e.msg());
     }
 
     setOptions();
@@ -133,8 +132,6 @@ RemoteStore::~RemoteStore()
     try {
         to.flush();
         fdSocket.close();
-        if (child != -1)
-            child.wait(true);
     } catch (...) {
         ignoreException();
     }
@@ -402,8 +399,23 @@ Path RemoteStore::addToStore(const Path & _srcPath,
     writeInt((hashAlgo == htSHA256 && recursive) ? 0 : 1, to);
     writeInt(recursive ? 1 : 0, to);
     writeString(printHashType(hashAlgo), to);
-    dumpPath(srcPath, to, filter);
-    processStderr();
+
+    try {
+        to.written = 0;
+        to.warn = true;
+        dumpPath(srcPath, to, filter);
+        to.warn = false;
+        processStderr();
+    } catch (SysError & e) {
+        /* Daemon closed while we were sending the path. Probably OOM
+           or I/O error. */
+        if (e.errNo == EPIPE)
+            try {
+                processStderr();
+            } catch (EndOfFile & e) { }
+        throw;
+    }
+
     return readStorePath(from);
 }
 
@@ -564,6 +576,13 @@ void RemoteStore::clearFailedPaths(const PathSet & paths)
     readInt(from);
 }
 
+void RemoteStore::optimiseStore()
+{
+    openConnection();
+    writeInt(wopOptimiseStore, to);
+    processStderr();
+    readInt(from);
+}
 
 void RemoteStore::processStderr(Sink * sink, Source * source)
 {
diff --git a/nix/libstore/remote-store.hh b/nix/libstore/remote-store.hh
index 04b60fce4b..98774c10b3 100644
--- a/nix/libstore/remote-store.hh
+++ b/nix/libstore/remote-store.hh
@@ -82,12 +82,13 @@ public:
     PathSet queryFailedPaths();
 
     void clearFailedPaths(const PathSet & paths);
-    
+
+    void optimiseStore();
+
 private:
     AutoCloseFD fdSocket;
     FdSink to;
     FdSource from;
-    Pid child;
     unsigned int daemonVersion;
     bool initialised;
 
diff --git a/nix/libstore/store-api.hh b/nix/libstore/store-api.hh
index b635fee2cf..3109f100ef 100644
--- a/nix/libstore/store-api.hh
+++ b/nix/libstore/store-api.hh
@@ -250,6 +250,10 @@ public:
        `nix-store --register-validity'. */
     string makeValidityRegistration(const PathSet & paths,
         bool showDerivers, bool showHash);
+
+    /* Optimise the disk space usage of the Nix store by hard-linking files
+       with the same contents. */
+    virtual void optimiseStore() = 0;
 };
 
 
diff --git a/nix/libstore/worker-protocol.hh b/nix/libstore/worker-protocol.hh
index 9317f89c37..4b040b77ce 100644
--- a/nix/libstore/worker-protocol.hh
+++ b/nix/libstore/worker-protocol.hh
@@ -12,7 +12,6 @@ namespace nix {
 
 
 typedef enum {
-    wopQuit = 0,
     wopIsValidPath = 1,
     wopHasSubstitutes = 3,
     wopQueryPathHash = 4,
@@ -43,6 +42,7 @@ typedef enum {
     wopQueryValidPaths = 31,
     wopQuerySubstitutablePaths = 32,
     wopQueryValidDerivers = 33,
+    wopOptimiseStore = 34
 } WorkerOp;
 
 
diff --git a/nix/libutil/archive.cc b/nix/libutil/archive.cc
index 70a1c580dd..6856ea0f28 100644
--- a/nix/libutil/archive.cc
+++ b/nix/libutil/archive.cc
@@ -1,10 +1,14 @@
+#define _XOPEN_SOURCE 600
+
 #include "config.h"
 
 #include <cerrno>
 #include <algorithm>
 #include <vector>
+#include <map>
+
+#include <strings.h> // for strcasecmp
 
-#define _XOPEN_SOURCE 600
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <unistd.h>
@@ -18,39 +22,21 @@
 namespace nix {
 
 
+bool useCaseHack =
+#if __APPLE__
+    true;
+#else
+    false;
+#endif
+
 static string archiveVersion1 = "nix-archive-1";
 
+static string caseHackSuffix = "~nix~case~hack~";
 
 PathFilter defaultPathFilter;
 
 
-static void dump(const string & path, Sink & sink, PathFilter & filter);
-
-
-static void dumpEntries(const Path & path, Sink & sink, PathFilter & filter)
-{
-    Strings names = readDirectory(path);
-    vector<string> names2(names.begin(), names.end());
-    sort(names2.begin(), names2.end());
-
-    for (vector<string>::iterator i = names2.begin();
-         i != names2.end(); ++i)
-    {
-        Path entry = path + "/" + *i;
-        if (filter(entry)) {
-            writeString("entry", sink);
-            writeString("(", sink);
-            writeString("name", sink);
-            writeString(*i, sink);
-            writeString("node", sink);
-            dump(entry, sink, filter);
-            writeString(")", sink);
-        }
-    }
-}
-
-
-static void dumpContents(const Path & path, size_t size, 
+static void dumpContents(const Path & path, size_t size,
     Sink & sink)
 {
     writeString("contents", sink);
@@ -58,7 +44,7 @@ static void dumpContents(const Path & path, size_t size,
 
     AutoCloseFD fd = open(path.c_str(), O_RDONLY);
     if (fd == -1) throw SysError(format("opening file `%1%'") % path);
-    
+
     unsigned char buf[65536];
     size_t left = size;
 
@@ -89,12 +75,40 @@ static void dump(const Path & path, Sink & sink, PathFilter & filter)
             writeString("", sink);
         }
         dumpContents(path, (size_t) st.st_size, sink);
-    } 
+    }
 
     else if (S_ISDIR(st.st_mode)) {
         writeString("type", sink);
         writeString("directory", sink);
-        dumpEntries(path, sink, filter);
+
+        /* If we're on a case-insensitive system like Mac OS X, undo
+           the case hack applied by restorePath(). */
+        std::map<string, string> unhacked;
+        for (auto & i : readDirectory(path))
+            if (useCaseHack) {
+                string name(i.name);
+                size_t pos = i.name.find(caseHackSuffix);
+                if (pos != string::npos) {
+                    printMsg(lvlDebug, format("removing case hack suffix from `%1%'") % (path + "/" + i.name));
+                    name.erase(pos);
+                }
+                if (unhacked.find(name) != unhacked.end())
+                    throw Error(format("file name collision in between `%1%' and `%2%'")
+                        % (path + "/" + unhacked[name]) % (path + "/" + i.name));
+                unhacked[name] = i.name;
+            } else
+                unhacked[i.name] = i.name;
+
+        for (auto & i : unhacked)
+            if (filter(path + "/" + i.first)) {
+                writeString("entry", sink);
+                writeString("(", sink);
+                writeString("name", sink);
+                writeString(i.first, sink);
+                writeString("node", sink);
+                dump(path + "/" + i.second, sink, filter);
+                writeString(")", sink);
+            }
     }
 
     else if (S_ISLNK(st.st_mode)) {
@@ -123,6 +137,7 @@ static SerialisationError badArchive(string s)
 }
 
 
+#if 0
 static void skipGeneric(Source & source)
 {
     if (readString(source) == "(") {
@@ -130,43 +145,13 @@ static void skipGeneric(Source & source)
             skipGeneric(source);
     }
 }
-
-
-static void parse(ParseSink & sink, Source & source, const Path & path);
-
-
-
-static void parseEntry(ParseSink & sink, Source & source, const Path & path)
-{
-    string s, name;
-
-    s = readString(source);
-    if (s != "(") throw badArchive("expected open tag");
-
-    while (1) {
-        checkInterrupt();
-
-        s = readString(source);
-
-        if (s == ")") {
-            break;
-        } else if (s == "name") {
-            name = readString(source);
-        } else if (s == "node") {
-            if (s == "") throw badArchive("entry name missing");
-            parse(sink, source, path + "/" + name);
-        } else {
-            throw badArchive("unknown field " + s);
-            skipGeneric(source);
-        }
-    }
-}
+#endif
 
 
 static void parseContents(ParseSink & sink, Source & source, const Path & path)
 {
     unsigned long long size = readLongLong(source);
-    
+
     sink.preallocateContents(size);
 
     unsigned long long left = size;
@@ -185,6 +170,15 @@ static void parseContents(ParseSink & sink, Source & source, const Path & path)
 }
 
 
+struct CaseInsensitiveCompare
+{
+    bool operator() (const string & a, const string & b) const
+    {
+        return strcasecmp(a.c_str(), b.c_str()) < 0;
+    }
+};
+
+
 static void parse(ParseSink & sink, Source & source, const Path & path)
 {
     string s;
@@ -194,6 +188,8 @@ static void parse(ParseSink & sink, Source & source, const Path & path)
 
     enum { tpUnknown, tpRegular, tpDirectory, tpSymlink } type = tpUnknown;
 
+    std::map<Path, int, CaseInsensitiveCompare> names;
+
     while (1) {
         checkInterrupt();
 
@@ -221,9 +217,9 @@ static void parse(ParseSink & sink, Source & source, const Path & path)
             else if (t == "symlink") {
                 type = tpSymlink;
             }
-            
+
             else throw badArchive("unknown file type " + t);
-            
+
         }
 
         else if (s == "contents" && type == tpRegular) {
@@ -236,7 +232,40 @@ static void parse(ParseSink & sink, Source & source, const Path & path)
         }
 
         else if (s == "entry" && type == tpDirectory) {
-            parseEntry(sink, source, path);
+            string name, prevName;
+
+            s = readString(source);
+            if (s != "(") throw badArchive("expected open tag");
+
+            while (1) {
+                checkInterrupt();
+
+                s = readString(source);
+
+                if (s == ")") {
+                    break;
+                } else if (s == "name") {
+                    name = readString(source);
+                    if (name.empty() || name == "." || name == ".." || name.find('/') != string::npos || name.find((char) 0) != string::npos)
+                        throw Error(format("NAR contains invalid file name `%1%'") % name);
+                    if (name <= prevName)
+                        throw Error("NAR directory is not sorted");
+                    prevName = name;
+                    if (useCaseHack) {
+                        auto i = names.find(name);
+                        if (i != names.end()) {
+                            printMsg(lvlDebug, format("case collision between `%1%' and `%2%'") % i->first % name);
+                            name += caseHackSuffix;
+                            name += int2String(++i->second);
+                        } else
+                            names[name] = 0;
+                    }
+                } else if (s == "node") {
+                    if (s.empty()) throw badArchive("entry name missing");
+                    parse(sink, source, path + "/" + name);
+                } else
+                    throw badArchive("unknown field " + s);
+            }
         }
 
         else if (s == "target" && type == tpSymlink) {
@@ -244,17 +273,15 @@ static void parse(ParseSink & sink, Source & source, const Path & path)
             sink.createSymlink(path, target);
         }
 
-        else {
+        else
             throw badArchive("unknown field " + s);
-            skipGeneric(source);
-        }
     }
 }
 
 
 void parseDump(ParseSink & sink, Source & source)
 {
-    string version;    
+    string version;
     try {
         version = readString(source);
     } catch (SerialisationError & e) {
@@ -323,7 +350,7 @@ struct RestoreSink : ParseSink
     }
 };
 
- 
+
 void restorePath(const Path & path, Source & source)
 {
     RestoreSink sink;
@@ -331,5 +358,5 @@ void restorePath(const Path & path, Source & source)
     parseDump(sink, source);
 }
 
- 
+
 }
diff --git a/nix/libutil/archive.hh b/nix/libutil/archive.hh
index ccac92074d..c216e9768f 100644
--- a/nix/libutil/archive.hh
+++ b/nix/libutil/archive.hh
@@ -28,7 +28,7 @@ namespace nix {
 
    where:
 
-     attrs(as) = concat(map(attr, as)) + encN(0) 
+     attrs(as) = concat(map(attr, as)) + encN(0)
      attrs((a, b)) = encS(a) + encS(b)
 
      encS(s) = encN(len(s)) + s + (padding until next 64-bit boundary)
@@ -58,7 +58,7 @@ void dumpPath(const Path & path, Sink & sink,
 struct ParseSink
 {
     virtual void createDirectory(const Path & path) { };
-    
+
     virtual void createRegularFile(const Path & path) { };
     virtual void isExecutable() { };
     virtual void preallocateContents(unsigned long long size) { };
@@ -66,10 +66,14 @@ struct ParseSink
 
     virtual void createSymlink(const Path & path, const string & target) { };
 };
-    
+
 void parseDump(ParseSink & sink, Source & source);
 
 void restorePath(const Path & path, Source & source);
 
- 
+
+// FIXME: global variables are bad m'kay.
+extern bool useCaseHack;
+
+
 }
diff --git a/nix/libutil/serialise.cc b/nix/libutil/serialise.cc
index 6b71f52c15..9241750750 100644
--- a/nix/libutil/serialise.cc
+++ b/nix/libutil/serialise.cc
@@ -54,8 +54,24 @@ FdSink::~FdSink()
 }
 
 
+size_t threshold = 256 * 1024 * 1024;
+
+static void warnLargeDump()
+{
+    printMsg(lvlError, "warning: dumping very large path (> 256 MiB); this may run out of memory");
+}
+
+
 void FdSink::write(const unsigned char * data, size_t len)
 {
+    static bool warned = false;
+    if (warn && !warned) {
+        written += len;
+        if (written > threshold) {
+            warnLargeDump();
+            warned = true;
+        }
+    }
     writeFull(fd, data, len);
 }
 
@@ -256,4 +272,15 @@ template Paths readStrings(Source & source);
 template PathSet readStrings(Source & source);
 
 
+void StringSink::operator () (const unsigned char * data, size_t len)
+{
+    static bool warned = false;
+    if (!warned && s.size() > threshold) {
+        warnLargeDump();
+        warned = true;
+    }
+    s.append((const char *) data, len);
+}
+
+
 }
diff --git a/nix/libutil/serialise.hh b/nix/libutil/serialise.hh
index e5a9df1d05..6a6f028aa6 100644
--- a/nix/libutil/serialise.hh
+++ b/nix/libutil/serialise.hh
@@ -72,9 +72,11 @@ struct BufferedSource : Source
 struct FdSink : BufferedSink
 {
     int fd;
+    bool warn;
+    size_t written;
 
-    FdSink() : fd(-1) { }
-    FdSink(int fd) : fd(fd) { }
+    FdSink() : fd(-1), warn(false), written(0) { }
+    FdSink(int fd) : fd(fd), warn(false), written(0) { }
     ~FdSink();
     
     void write(const unsigned char * data, size_t len);
@@ -95,10 +97,7 @@ struct FdSource : BufferedSource
 struct StringSink : Sink
 {
     string s;
-    void operator () (const unsigned char * data, size_t len)
-    {
-        s.append((const char *) data, len);
-    }
+    void operator () (const unsigned char * data, size_t len);
 };
 
 
diff --git a/nix/libutil/types.hh b/nix/libutil/types.hh
index 4b5ce9a78c..160884ee1a 100644
--- a/nix/libutil/types.hh
+++ b/nix/libutil/types.hh
@@ -8,6 +8,15 @@
 
 #include <boost/format.hpp>
 
+/* Before 4.7, gcc's std::exception uses empty throw() specifiers for
+ * its (virtual) destructor and what() in c++11 mode, in violation of spec
+ */
+#ifdef __GNUC__
+#if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 7)
+#define EXCEPTION_NEEDS_THROW_SPEC
+#endif
+#endif
+
 
 namespace nix {
 
@@ -39,10 +48,14 @@ protected:
 public:
     unsigned int status; // exit status
     BaseError(const FormatOrString & fs, unsigned int status = 1);
+#ifdef EXCEPTION_NEEDS_THROW_SPEC
     ~BaseError() throw () { };
     const char * what() const throw () { return err.c_str(); }
-    const string & msg() const throw () { return err; }
-    const string & prefix() const throw () { return prefix_; }
+#else
+    const char * what() const noexcept { return err.c_str(); }
+#endif
+    const string & msg() const { return err; }
+    const string & prefix() const { return prefix_; }
     BaseError & addPrefix(const FormatOrString & fs);
 };
 
diff --git a/nix/libutil/util.cc b/nix/libutil/util.cc
index 846674a29d..a4a1ddb12a 100644
--- a/nix/libutil/util.cc
+++ b/nix/libutil/util.cc
@@ -1,5 +1,8 @@
 #include "config.h"
 
+#include "util.hh"
+#include "affinity.hh"
+
 #include <iostream>
 #include <cerrno>
 #include <cstdio>
@@ -16,8 +19,6 @@
 #include <sys/syscall.h>
 #endif
 
-#include "util.hh"
-
 
 extern char * * environ;
 
@@ -125,7 +126,6 @@ Path canonPath(const Path & path, bool resolveSymlinks)
                 i = temp.begin(); /* restart */
                 end = temp.end();
                 s = "";
-                /* !!! potential for infinite loop */
             }
         }
     }
@@ -202,9 +202,10 @@ bool isLink(const Path & path)
 }
 
 
-Strings readDirectory(const Path & path)
+DirEntries readDirectory(const Path & path)
 {
-    Strings names;
+    DirEntries entries;
+    entries.reserve(64);
 
     AutoCloseDir dir = opendir(path.c_str());
     if (!dir) throw SysError(format("opening directory `%1%'") % path);
@@ -214,11 +215,21 @@ Strings readDirectory(const Path & path)
         checkInterrupt();
         string name = dirent->d_name;
         if (name == "." || name == "..") continue;
-        names.push_back(name);
+        entries.emplace_back(name, dirent->d_ino, dirent->d_type);
     }
     if (errno) throw SysError(format("reading directory `%1%'") % path);
 
-    return names;
+    return entries;
+}
+
+
+unsigned char getFileType(const Path & path)
+{
+    struct stat st = lstat(path);
+    if (S_ISDIR(st.st_mode)) return DT_DIR;
+    if (S_ISLNK(st.st_mode)) return DT_LNK;
+    if (S_ISREG(st.st_mode)) return DT_REG;
+    return DT_UNKNOWN;
 }
 
 
@@ -293,16 +304,14 @@ static void _deletePath(const Path & path, unsigned long long & bytesFreed)
         bytesFreed += st.st_blocks * 512;
 
     if (S_ISDIR(st.st_mode)) {
-        Strings names = readDirectory(path);
-
         /* Make the directory writable. */
         if (!(st.st_mode & S_IWUSR)) {
             if (chmod(path.c_str(), st.st_mode | S_IWUSR) == -1)
                 throw SysError(format("making `%1%' writable") % path);
         }
 
-        for (Strings::iterator i = names.begin(); i != names.end(); ++i)
-            _deletePath(path + "/" + *i, bytesFreed);
+        for (auto & i : readDirectory(path))
+            _deletePath(path + "/" + i.name, bytesFreed);
     }
 
     if (remove(path.c_str()) == -1)
@@ -380,6 +389,9 @@ Paths createDirs(const Path & path)
         created.push_back(path);
     }
 
+    if (S_ISLNK(st.st_mode) && stat(path.c_str(), &st) == -1)
+        throw SysError(format("statting symlink `%1%'") % path);
+
     if (!S_ISDIR(st.st_mode)) throw Error(format("`%1%' is not a directory") % path);
 
     return created;
@@ -466,10 +478,18 @@ void warnOnce(bool & haveWarned, const FormatOrString & fs)
 }
 
 
+static void defaultWriteToStderr(const unsigned char * buf, size_t count)
+{
+    writeFull(STDERR_FILENO, buf, count);
+}
+
+
 void writeToStderr(const string & s)
 {
     try {
-        _writeToStderr((const unsigned char *) s.data(), s.size());
+        auto p = _writeToStderr;
+        if (!p) p = defaultWriteToStderr;
+        p((const unsigned char *) s.data(), s.size());
     } catch (SysError & e) {
         /* Ignore failing writes to stderr if we're in an exception
            handler, otherwise throw an exception.  We need to ignore
@@ -481,12 +501,6 @@ void writeToStderr(const string & s)
 }
 
 
-static void defaultWriteToStderr(const unsigned char * buf, size_t count)
-{
-    writeFull(STDERR_FILENO, buf, count);
-}
-
-
 void (*_writeToStderr) (const unsigned char * buf, size_t count) = defaultWriteToStderr;
 
 
@@ -707,10 +721,14 @@ void AutoCloseDir::close()
 
 
 Pid::Pid()
+    : pid(-1), separatePG(false), killSignal(SIGKILL)
+{
+}
+
+
+Pid::Pid(pid_t pid)
+    : pid(pid), separatePG(false), killSignal(SIGKILL)
 {
-    pid = -1;
-    separatePG = false;
-    killSignal = SIGKILL;
 }
 
 
@@ -734,11 +752,12 @@ Pid::operator pid_t()
 }
 
 
-void Pid::kill()
+void Pid::kill(bool quiet)
 {
     if (pid == -1 || pid == 0) return;
 
-    printMsg(lvlError, format("killing process %1%") % pid);
+    if (!quiet)
+        printMsg(lvlError, format("killing process %1%") % pid);
 
     /* Send the requested signal to the child.  If it has its own
        process group, send the signal to every process in the child
@@ -801,43 +820,30 @@ void killUser(uid_t uid)
        users to which the current process can send signals.  So we
        fork a process, switch to uid, and send a mass kill. */
 
-    Pid pid;
-    pid = fork();
-    switch (pid) {
+    Pid pid = startProcess([&]() {
 
-    case -1:
-        throw SysError("unable to fork");
+        if (setuid(uid) == -1)
+            throw SysError("setting uid");
 
-    case 0:
-        try { /* child */
-
-            if (setuid(uid) == -1)
-                throw SysError("setting uid");
-
-            while (true) {
+        while (true) {
 #ifdef __APPLE__
-                /* OSX's kill syscall takes a third parameter that, among other
-                   things, determines if kill(-1, signo) affects the calling
-                   process. In the OSX libc, it's set to true, which means
-                   "follow POSIX", which we don't want here
+            /* OSX's kill syscall takes a third parameter that, among
+               other things, determines if kill(-1, signo) affects the
+               calling process. In the OSX libc, it's set to true,
+               which means "follow POSIX", which we don't want here
                  */
-                if (syscall(SYS_kill, -1, SIGKILL, false) == 0) break;
+            if (syscall(SYS_kill, -1, SIGKILL, false) == 0) break;
 #else
-                if (kill(-1, SIGKILL) == 0) break;
+            if (kill(-1, SIGKILL) == 0) break;
 #endif
-                if (errno == ESRCH) break; /* no more processes */
-                if (errno != EINTR)
-                    throw SysError(format("cannot kill processes for uid `%1%'") % uid);
-            }
-
-        } catch (std::exception & e) {
-            writeToStderr((format("killing processes belonging to uid `%1%': %2%\n") % uid % e.what()).str());
-            _exit(1);
+            if (errno == ESRCH) break; /* no more processes */
+            if (errno != EINTR)
+                throw SysError(format("cannot kill processes for uid `%1%'") % uid);
         }
+
         _exit(0);
-    }
+    });
 
-    /* parent */
     int status = pid.wait(true);
     if (status != 0)
         throw Error(format("cannot kill processes for uid `%1%': %2%") % uid % statusToString(status));
@@ -852,6 +858,32 @@ void killUser(uid_t uid)
 //////////////////////////////////////////////////////////////////////
 
 
+pid_t startProcess(std::function<void()> fun,
+    bool dieWithParent, const string & errorPrefix, bool runExitHandlers)
+{
+    pid_t pid = fork();
+    if (pid == -1) throw SysError("unable to fork");
+
+    if (pid == 0) {
+        _writeToStderr = 0;
+        try {
+            restoreAffinity();
+            fun();
+        } catch (std::exception & e) {
+            try {
+                std::cerr << errorPrefix << e.what() << "\n";
+            } catch (...) { }
+        } catch (...) { }
+        if (runExitHandlers)
+            exit(1);
+        else
+            _exit(1);
+    }
+
+    return pid;
+}
+
+
 string runProgram(Path program, bool searchPath, const Strings & args)
 {
     checkInterrupt();
@@ -867,32 +899,17 @@ string runProgram(Path program, bool searchPath, const Strings & args)
     pipe.create();
 
     /* Fork. */
-    Pid pid;
-    pid = maybeVfork();
-
-    switch (pid) {
-
-    case -1:
-        throw SysError("unable to fork");
+    Pid pid = startProcess([&]() {
+        if (dup2(pipe.writeSide, STDOUT_FILENO) == -1)
+            throw SysError("dupping stdout");
 
-    case 0: /* child */
-        try {
-            if (dup2(pipe.writeSide, STDOUT_FILENO) == -1)
-                throw SysError("dupping stdout");
-
-            if (searchPath)
-                execvp(program.c_str(), (char * *) &cargs[0]);
-            else
-                execv(program.c_str(), (char * *) &cargs[0]);
-            throw SysError(format("executing `%1%'") % program);
-
-        } catch (std::exception & e) {
-            writeToStderr("error: " + string(e.what()) + "\n");
-        }
-        _exit(1);
-    }
+        if (searchPath)
+            execvp(program.c_str(), (char * *) &cargs[0]);
+        else
+            execv(program.c_str(), (char * *) &cargs[0]);
 
-    /* Parent. */
+        throw SysError(format("executing `%1%'") % program);
+    });
 
     pipe.writeSide.close();
 
@@ -901,7 +918,7 @@ string runProgram(Path program, bool searchPath, const Strings & args)
     /* Wait for the child to finish. */
     int status = pid.wait(true);
     if (!statusOk(status))
-        throw Error(format("program `%1%' %2%")
+        throw ExecError(format("program `%1%' %2%")
             % program % statusToString(status));
 
     return result;
@@ -928,13 +945,6 @@ void closeOnExec(int fd)
 }
 
 
-#if HAVE_VFORK
-pid_t (*maybeVfork)() = vfork;
-#else
-pid_t (*maybeVfork)() = fork;
-#endif
-
-
 //////////////////////////////////////////////////////////////////////
 
 
diff --git a/nix/libutil/util.hh b/nix/libutil/util.hh
index ce2d77c19a..0ad0026711 100644
--- a/nix/libutil/util.hh
+++ b/nix/libutil/util.hh
@@ -7,6 +7,7 @@
 #include <dirent.h>
 #include <unistd.h>
 #include <signal.h>
+#include <functional>
 
 #include <cstdio>
 
@@ -63,7 +64,20 @@ bool isLink(const Path & path);
 
 /* Read the contents of a directory.  The entries `.' and `..' are
    removed. */
-Strings readDirectory(const Path & path);
+struct DirEntry
+{
+    string name;
+    ino_t ino;
+    unsigned char type; // one of DT_*
+    DirEntry(const string & name, ino_t ino, unsigned char type)
+        : name(name), ino(ino), type(type) { }
+};
+
+typedef vector<DirEntry> DirEntries;
+
+DirEntries readDirectory(const Path & path);
+
+unsigned char getFileType(const Path & path);
 
 /* Read the contents of a file into a string. */
 string readFile(int fd);
@@ -237,10 +251,11 @@ class Pid
     int killSignal;
 public:
     Pid();
+    Pid(pid_t pid);
     ~Pid();
     void operator =(pid_t pid);
     operator pid_t();
-    void kill();
+    void kill(bool quiet = false);
     int wait(bool block);
     void setSeparatePG(bool separatePG);
     void setKillSignal(int signal);
@@ -252,11 +267,19 @@ public:
 void killUser(uid_t uid);
 
 
+/* Fork a process that runs the given function, and return the child
+   pid to the caller. */
+pid_t startProcess(std::function<void()> fun, bool dieWithParent = true,
+    const string & errorPrefix = "error: ", bool runExitHandlers = false);
+
+
 /* Run a program and return its stdout in a string (i.e., like the
    shell backtick operator). */
 string runProgram(Path program, bool searchPath = false,
     const Strings & args = Strings());
 
+MakeError(ExecError, Error)
+
 /* Close all file descriptors except stdin, stdout, stderr, and those
    listed in the given set.  Good practice in child processes. */
 void closeMostFDs(const set<int> & exceptions);
@@ -264,9 +287,6 @@ void closeMostFDs(const set<int> & exceptions);
 /* Set the close-on-exec flag for the given file descriptor. */
 void closeOnExec(int fd);
 
-/* Call vfork() if available, otherwise fork(). */
-extern pid_t (*maybeVfork)();
-
 
 /* User interruption. */
 
diff --git a/nix/nix-daemon/guix-daemon.cc b/nix/nix-daemon/guix-daemon.cc
index ca39c2f497..315744394f 100644
--- a/nix/nix-daemon/guix-daemon.cc
+++ b/nix/nix-daemon/guix-daemon.cc
@@ -154,8 +154,18 @@ parse_opt (int key, char *arg, struct argp_state *state)
       settings.useChroot = false;
       break;
     case GUIX_OPT_CHROOT_DIR:
-      settings.dirsInChroot.insert (arg);
-      break;
+      {
+	std::string chroot_dirs;
+
+	chroot_dirs = settings.get ("build-extra-chroot-dirs",
+				    (std::string) "");
+	if (chroot_dirs == "")
+	  chroot_dirs = arg;
+	else
+	  chroot_dirs = chroot_dirs + " " + arg;
+	settings.set("build-extra-chroot-dirs", chroot_dirs);
+	break;
+      }
     case GUIX_OPT_DISABLE_LOG_COMPRESSION:
       settings.compressLog = false;
       break;
@@ -310,7 +320,7 @@ main (int argc, char *argv[])
 	  if (subs == "default")
 	    {
 	      string subst =
-		settings.nixLibexecDir + "/guix/substitute-binary";
+		settings.nixLibexecDir + "/guix/substitute";
 	      setenv ("NIX_SUBSTITUTERS", subst.c_str (), 1);
 	    }
 	}
@@ -328,11 +338,12 @@ main (int argc, char *argv[])
 
       if (settings.useChroot)
 	{
-	  foreach (PathSet::iterator, i, settings.dirsInChroot)
-	    {
-	      printMsg (lvlDebug,
-			format ("directory `%1%' added to the chroot") % *i);
-	    }
+	  std::string chroot_dirs;
+
+	  chroot_dirs = settings.get ("build-extra-chroot-dirs",
+				      (std::string) "");
+	  printMsg (lvlDebug,
+		    format ("extra chroot directories: '%1%'") % chroot_dirs);
 	}
 
       printMsg (lvlDebug,
diff --git a/nix/nix-daemon/nix-daemon.cc b/nix/nix-daemon/nix-daemon.cc
index 8814fe3155..e42d602a3a 100644
--- a/nix/nix-daemon/nix-daemon.cc
+++ b/nix/nix-daemon/nix-daemon.cc
@@ -7,6 +7,8 @@
 #include "affinity.hh"
 #include "globals.hh"
 
+#include <algorithm>
+
 #include <cstring>
 #include <unistd.h>
 #include <signal.h>
@@ -17,6 +19,8 @@
 #include <sys/un.h>
 #include <fcntl.h>
 #include <errno.h>
+#include <pwd.h>
+#include <grp.h>
 
 using namespace nix;
 
@@ -44,7 +48,6 @@ static FdSource from(STDIN_FILENO);
 static FdSink to(STDOUT_FILENO);
 
 bool canSendStderr;
-pid_t myPid;
 
 
 
@@ -54,11 +57,7 @@ pid_t myPid;
    socket. */
 static void tunnelStderr(const unsigned char * buf, size_t count)
 {
-    /* Don't send the message to the client if we're a child of the
-       process handling the connection.  Otherwise we could screw up
-       the protocol.  It's up to the parent to redirect stderr and
-       send it to the client somehow (e.g., as in build.cc). */
-    if (canSendStderr && myPid == getpid()) {
+    if (canSendStderr) {
         try {
             writeInt(STDERR_NEXT, to);
             writeString(buf, count, to);
@@ -284,15 +283,6 @@ static void performOp(bool trusted, unsigned int clientVersion,
 {
     switch (op) {
 
-#if 0
-    case wopQuit: {
-        /* Close the database. */
-        store.reset((StoreAPI *) 0);
-        writeInt(1, to);
-        break;
-    }
-#endif
-
     case wopIsValidPath: {
         /* 'readStorePath' could raise an error leading to the connection
            being closed.  To be able to recover from an invalid path error,
@@ -450,7 +440,7 @@ static void performOp(bool trusted, unsigned int clientVersion,
     case wopImportPaths: {
         startWork();
         TunnelSource source(from);
-        Paths paths = store->importPaths(true, source);
+        Paths paths = store->importPaths(!trusted, source);
         stopWork();
         writeStrings(paths, to);
         break;
@@ -650,6 +640,13 @@ static void performOp(bool trusted, unsigned int clientVersion,
         break;
     }
 
+    case wopOptimiseStore:
+	startWork();
+	store->optimiseStore();
+	stopWork();
+	writeInt(1, to);
+	break;
+
     default:
         throw Error(format("invalid operation %1%") % op);
     }
@@ -659,7 +656,6 @@ static void performOp(bool trusted, unsigned int clientVersion,
 static void processConnection(bool trusted)
 {
     canSendStderr = false;
-    myPid = getpid();
     _writeToStderr = tunnelStderr;
 
 #ifdef HAVE_HUP_NOTIFICATION
@@ -708,7 +704,7 @@ static void processConnection(bool trusted)
         to.flush();
 
     } catch (Error & e) {
-        stopWork(false, e.msg());
+        stopWork(false, e.msg(), GET_PROTOCOL_MINOR(clientVersion) >= 8 ? 1 : 0);
         to.flush();
         return;
     }
@@ -735,12 +731,10 @@ static void processConnection(bool trusted)
                during addTextToStore() / importPath().  If that
                happens, just send the error message and exit. */
             bool errorAllowed = canSendStderr;
-            if (!errorAllowed) printMsg(lvlError, format("error processing client input: %1%") % e.msg());
             stopWork(false, e.msg(), GET_PROTOCOL_MINOR(clientVersion) >= 8 ? e.status : 0);
-            if (!errorAllowed) break;
+            if (!errorAllowed) throw;
         } catch (std::bad_alloc & e) {
-            if (canSendStderr)
-                stopWork(false, "Nix daemon out of memory", GET_PROTOCOL_MINOR(clientVersion) >= 8 ? 1 : 0);
+            stopWork(false, "Nix daemon out of memory", GET_PROTOCOL_MINOR(clientVersion) >= 8 ? 1 : 0);
             throw;
         }
 
@@ -749,7 +743,7 @@ static void processConnection(bool trusted)
         assert(!canSendStderr);
     };
 
-    printMsg(lvlError, format("%1% operations") % opCount);
+    printMsg(lvlDebug, format("%1% operations") % opCount);
 }
 
 
@@ -771,6 +765,27 @@ static void setSigChldAction(bool autoReap)
 }
 
 
+bool matchUser(const string & user, const string & group, const Strings & users)
+{
+    if (find(users.begin(), users.end(), "*") != users.end())
+        return true;
+
+    if (find(users.begin(), users.end(), user) != users.end())
+        return true;
+
+    for (auto & i : users)
+        if (string(i, 0, 1) == "@") {
+            if (group == string(i, 1)) return true;
+            struct group * gr = getgrnam(i.c_str() + 1);
+            if (!gr) continue;
+            for (char * * mem = gr->gr_mem; *mem; mem++)
+                if (user == string(*mem)) return true;
+        }
+
+    return false;
+}
+
+
 #define SD_LISTEN_FDS_START 3
 
 
@@ -856,58 +871,61 @@ static void daemonLoop()
 
             closeOnExec(remote);
 
-            /* Get the identity of the caller, if possible. */
-            uid_t clientUid = -1;
-            pid_t clientPid = -1;
             bool trusted = false;
+            pid_t clientPid = -1;
 
 #if defined(SO_PEERCRED)
+            /* Get the identity of the caller, if possible. */
             ucred cred;
             socklen_t credLen = sizeof(cred);
-            if (getsockopt(remote, SOL_SOCKET, SO_PEERCRED, &cred, &credLen) != -1) {
-                clientPid = cred.pid;
-                clientUid = cred.uid;
-                if (clientUid == 0) trusted = true;
-            }
-#endif
+            if (getsockopt(remote, SOL_SOCKET, SO_PEERCRED, &cred, &credLen) == -1)
+                throw SysError("getting peer credentials");
 
-            printMsg(lvlInfo, format("accepted connection from pid %1%, uid %2%") % clientPid % clientUid);
+            clientPid = cred.pid;
 
-            /* Fork a child to handle the connection. */
-            pid_t child;
-            child = fork();
+            struct passwd * pw = getpwuid(cred.uid);
+            string user = pw ? pw->pw_name : int2String(cred.uid);
 
-            switch (child) {
+            struct group * gr = getgrgid(cred.gid);
+            string group = gr ? gr->gr_name : int2String(cred.gid);
 
-            case -1:
-                throw SysError("unable to fork");
+            Strings trustedUsers = settings.get("trusted-users", Strings({"root"}));
+            Strings allowedUsers = settings.get("allowed-users", Strings({"*"}));
 
-            case 0:
-                try { /* child */
+            if (matchUser(user, group, trustedUsers))
+                trusted = true;
 
-                    /* Background the daemon. */
-                    if (setsid() == -1)
-                        throw SysError(format("creating a new session"));
+            if (!trusted && !matchUser(user, group, allowedUsers))
+                throw Error(format("user `%1%' is not allowed to connect to the Nix daemon") % user);
 
-                    /* Restore normal handling of SIGCHLD. */
-                    setSigChldAction(false);
+            printMsg(lvlInfo, format((string) "accepted connection from pid %1%, user %2%"
+                    + (trusted ? " (trusted)" : "")) % clientPid % user);
+#endif
+
+            /* Fork a child to handle the connection. */
+            startProcess([&]() {
+                fdSocket.close();
 
-                    /* For debugging, stuff the pid into argv[1]. */
-                    if (clientPid != -1 && argvSaved[1]) {
-                        string processName = int2String(clientPid);
-                        strncpy(argvSaved[1], processName.c_str(), strlen(argvSaved[1]));
-                    }
+                /* Background the daemon. */
+                if (setsid() == -1)
+                    throw SysError(format("creating a new session"));
 
-                    /* Handle the connection. */
-                    from.fd = remote;
-                    to.fd = remote;
-                    processConnection(trusted);
+                /* Restore normal handling of SIGCHLD. */
+                setSigChldAction(false);
 
-                } catch (std::exception & e) {
-                    writeToStderr("unexpected Nix daemon error: " + string(e.what()) + "\n");
+                /* For debugging, stuff the pid into argv[1]. */
+                if (clientPid != -1 && argvSaved[1]) {
+                    string processName = int2String(clientPid);
+                    strncpy(argvSaved[1], processName.c_str(), strlen(argvSaved[1]));
                 }
+
+                /* Handle the connection. */
+                from.fd = remote;
+                to.fd = remote;
+                processConnection(trusted);
+
                 exit(0);
-            }
+            }, false, "unexpected Nix daemon error: ", true);
 
         } catch (Interrupted & e) {
             throw;
diff --git a/nix/scripts/substitute-binary.in b/nix/scripts/substitute-binary.in
deleted file mode 100644
index 48d7bb8ff1..0000000000
--- a/nix/scripts/substitute-binary.in
+++ /dev/null
@@ -1,11 +0,0 @@
-#!@SHELL@
-# A shorthand for "guix substitute-binary", for use by the daemon.
-
-if test "x$GUIX_UNINSTALLED" = "x"
-then
-    prefix="@prefix@"
-    exec_prefix="@exec_prefix@"
-    exec "@bindir@/guix" substitute-binary "$@"
-else
-    exec guix substitute-binary "$@"
-fi
diff --git a/nix/scripts/substitute.in b/nix/scripts/substitute.in
new file mode 100644
index 0000000000..5a2eeb7259
--- /dev/null
+++ b/nix/scripts/substitute.in
@@ -0,0 +1,11 @@
+#!@SHELL@
+# A shorthand for "guix substitute", for use by the daemon.
+
+if test "x$GUIX_UNINSTALLED" = "x"
+then
+    prefix="@prefix@"
+    exec_prefix="@exec_prefix@"
+    exec "@bindir@/guix" substitute "$@"
+else
+    exec guix substitute "$@"
+fi