about summary refs log tree commit diff
path: root/src/libstore
diff options
context:
space:
mode:
Diffstat (limited to 'src/libstore')
-rw-r--r--src/libstore/build.cc235
-rw-r--r--src/libstore/derivations.cc83
-rw-r--r--src/libstore/derivations.hh40
-rw-r--r--src/libstore/download.cc2
-rw-r--r--src/libstore/download.hh5
-rw-r--r--src/libstore/gc.cc49
-rw-r--r--src/libstore/local-store.cc16
-rw-r--r--src/libstore/local-store.hh12
-rw-r--r--src/libstore/misc.cc108
-rw-r--r--src/libstore/misc.hh40
-rw-r--r--src/libstore/pathlocks.cc6
-rw-r--r--src/libstore/profiles.cc4
-rw-r--r--src/libstore/profiles.hh4
-rw-r--r--src/libstore/remote-store.cc10
-rw-r--r--src/libstore/remote-store.hh2
-rw-r--r--src/libstore/store-api.cc29
-rw-r--r--src/libstore/store-api.hh67
17 files changed, 405 insertions, 307 deletions
diff --git a/src/libstore/build.cc b/src/libstore/build.cc
index e00a07d3b1a0..180a558dc2e9 100644
--- a/src/libstore/build.cc
+++ b/src/libstore/build.cc
@@ -2,7 +2,6 @@
 
 #include "references.hh"
 #include "pathlocks.hh"
-#include "misc.hh"
 #include "globals.hh"
 #include "local-store.hh"
 #include "util.hh"
@@ -621,11 +620,15 @@ HookInstance::HookInstance()
         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);
+        Strings args = {
+            baseNameOf(buildHook),
+            settings.thisSystem,
+            (format("%1%") % settings.maxSilentTime).str(),
+            (format("%1%") % settings.printBuildTrace).str(),
+            (format("%1%") % settings.buildTimeout).str()
+        };
+
+        execv(buildHook.c_str(), stringsToCharPtrs(args).data());
 
         throw SysError(format("executing ‘%1%’") % buildHook);
     });
@@ -902,7 +905,7 @@ DerivationGoal::DerivationGoal(const Path & drvPath, const BasicDerivation & drv
 {
     this->drv = std::unique_ptr<BasicDerivation>(new BasicDerivation(drv));
     state = &DerivationGoal::haveDerivation;
-    name = (format("building of %1%") % showPaths(outputPaths(drv))).str();
+    name = (format("building of %1%") % showPaths(drv.outputPaths())).str();
     trace("created");
 
     /* Prevent the .chroot directory from being
@@ -1014,7 +1017,7 @@ void DerivationGoal::loadDerivation()
     assert(worker.store.isValidPath(drvPath));
 
     /* Get the derivation. */
-    drv = std::unique_ptr<BasicDerivation>(new Derivation(derivationFromPath(worker.store, drvPath)));
+    drv = std::unique_ptr<BasicDerivation>(new Derivation(worker.store.derivationFromPath(drvPath)));
 
     haveDerivation();
 }
@@ -1041,10 +1044,19 @@ void DerivationGoal::haveDerivation()
     for (auto & i : invalidOutputs)
         if (pathFailed(i)) return;
 
+    /* Reject doing a hash build of anything other than a fixed-output
+       derivation. */
+    if (buildMode == bmHash) {
+        if (drv->outputs.size() != 1 ||
+            drv->outputs.find("out") == drv->outputs.end() ||
+            drv->outputs["out"].hashAlgo == "")
+            throw Error(format("cannot do a hash build of non-fixed-output derivation ‘%1%’") % drvPath);
+    }
+
     /* We are first going to try to create the invalid output paths
        through substitutes.  If that doesn't work, we'll build
        them. */
-    if (settings.useSubstitutes && substitutesAllowed(*drv))
+    if (settings.useSubstitutes && drv->substitutesAllowed())
         for (auto & i : invalidOutputs)
             addWaitee(worker.makeSubstitutionGoal(i, buildMode == bmRepair));
 
@@ -1123,8 +1135,10 @@ void DerivationGoal::repairClosure()
 
     /* Get the output closure. */
     PathSet outputClosure;
-    for (auto & i : drv->outputs)
-        computeFSClosure(worker.store, i.second.path, outputClosure);
+    for (auto & i : drv->outputs) {
+        if (!wantOutput(i.first, wantedOutputs)) continue;
+        worker.store.computeFSClosure(i.second.path, outputClosure);
+    }
 
     /* Filter out our own outputs (which we have already checked). */
     for (auto & i : drv->outputs)
@@ -1134,11 +1148,11 @@ void DerivationGoal::repairClosure()
        derivation is responsible for which path in the output
        closure. */
     PathSet inputClosure;
-    if (useDerivation) computeFSClosure(worker.store, drvPath, inputClosure);
+    if (useDerivation) worker.store.computeFSClosure(drvPath, inputClosure);
     std::map<Path, Path> outputsToDrv;
     for (auto & i : inputClosure)
         if (isDerivation(i)) {
-            Derivation drv = derivationFromPath(worker.store, i);
+            Derivation drv = worker.store.derivationFromPath(i);
             for (auto & j : drv.outputs)
                 outputsToDrv[j.second.path] = i;
         }
@@ -1210,10 +1224,10 @@ void DerivationGoal::inputsRealised()
                `i' as input paths.  Only add the closures of output paths
                that are specified as inputs. */
             assert(worker.store.isValidPath(i.first));
-            Derivation inDrv = derivationFromPath(worker.store, i.first);
+            Derivation inDrv = worker.store.derivationFromPath(i.first);
             for (auto & j : i.second)
                 if (inDrv.outputs.find(j) != inDrv.outputs.end())
-                    computeFSClosure(worker.store, inDrv.outputs[j].path, inputPaths);
+                    worker.store.computeFSClosure(inDrv.outputs[j].path, inputPaths);
                 else
                     throw Error(
                         format("derivation ‘%1%’ requires non-existent output ‘%2%’ from input derivation ‘%3%’")
@@ -1222,7 +1236,7 @@ void DerivationGoal::inputsRealised()
 
     /* Second, the input sources. */
     for (auto & i : drv->inputSrcs)
-        computeFSClosure(worker.store, i, inputPaths);
+        worker.store.computeFSClosure(i, inputPaths);
 
     debug(format("added input paths %1%") % showPaths(inputPaths));
 
@@ -1245,46 +1259,6 @@ void DerivationGoal::inputsRealised()
 }
 
 
-static bool isBuiltin(const BasicDerivation & drv)
-{
-    return string(drv.builder, 0, 8) == "builtin:";
-}
-
-
-static bool canBuildLocally(const BasicDerivation & drv)
-{
-    return drv.platform == settings.thisSystem
-        || isBuiltin(drv)
-#if __linux__
-        || (drv.platform == "i686-linux" && settings.thisSystem == "x86_64-linux")
-        || (drv.platform == "armv6l-linux" && settings.thisSystem == "armv7l-linux")
-#elif __FreeBSD__
-        || (drv.platform == "i686-linux" && settings.thisSystem == "x86_64-freebsd")
-        || (drv.platform == "i686-linux" && settings.thisSystem == "i686-freebsd")
-#endif
-        ;
-}
-
-
-static string get(const StringPairs & map, const string & key, const string & def = "")
-{
-    StringPairs::const_iterator i = map.find(key);
-    return i == map.end() ? def : i->second;
-}
-
-
-bool willBuildLocally(const BasicDerivation & drv)
-{
-    return get(drv.env, "preferLocalBuild") == "1" && canBuildLocally(drv);
-}
-
-
-bool substitutesAllowed(const BasicDerivation & drv)
-{
-    return get(drv.env, "allowSubstitutes", "1") == "1";
-}
-
-
 void DerivationGoal::tryToBuild()
 {
     trace("trying to build");
@@ -1307,7 +1281,7 @@ void DerivationGoal::tryToBuild()
        can't acquire the lock, then continue; hopefully some other
        goal can start a build, and if not, the main loop will sleep a
        few seconds and then retry this goal. */
-    if (!outputLocks.lockPaths(outputPaths(*drv), "", false)) {
+    if (!outputLocks.lockPaths(drv->outputPaths(), "", false)) {
         worker.waitForAWhile(shared_from_this());
         return;
     }
@@ -1320,7 +1294,6 @@ void DerivationGoal::tryToBuild()
        now hold the locks on the output paths, no other process can
        build this derivation, so no further checks are necessary. */
     validPaths = checkPathValidity(true, buildMode == bmRepair);
-    assert(buildMode != bmCheck || validPaths.size() == drv->outputs.size());
     if (buildMode != bmCheck && validPaths.size() == drv->outputs.size()) {
         debug(format("skipping build of derivation ‘%1%’, someone beat us to it") % drvPath);
         outputLocks.setDeletion(true);
@@ -1328,7 +1301,7 @@ void DerivationGoal::tryToBuild()
         return;
     }
 
-    missingPaths = outputPaths(*drv);
+    missingPaths = drv->outputPaths();
     if (buildMode != bmCheck)
         for (auto & i : validPaths) missingPaths.erase(i);
 
@@ -1351,7 +1324,7 @@ void DerivationGoal::tryToBuild()
     /* Don't do a remote build if the derivation has the attribute
        `preferLocalBuild' set.  Also, check and repair modes are only
        supported for local builds. */
-    bool buildLocally = buildMode != bmNormal || willBuildLocally(*drv);
+    bool buildLocally = buildMode != bmNormal || drv->willBuildLocally();
 
     /* Is the build hook willing to accept this job? */
     if (!buildLocally) {
@@ -1647,7 +1620,7 @@ HookReply DerivationGoal::tryBuildHook()
        list it since the remote system *probably* already has it.) */
     PathSet allInputs;
     allInputs.insert(inputPaths.begin(), inputPaths.end());
-    computeFSClosure(worker.store, drvPath, allInputs);
+    worker.store.computeFSClosure(drvPath, allInputs);
 
     string s;
     for (auto & i : allInputs) { s += i; s += ' '; }
@@ -1702,7 +1675,7 @@ void DerivationGoal::startBuilder()
     startNest(nest, lvlInfo, f % showPaths(missingPaths) % curRound % nrRounds);
 
     /* Right platform? */
-    if (!canBuildLocally(*drv)) {
+    if (!drv->canBuildLocally()) {
         if (settings.printBuildTrace)
             printMsg(lvlError, format("@ unsupported-platform %1% %2%") % drvPath % drv->platform);
         throw Error(
@@ -1710,6 +1683,10 @@ void DerivationGoal::startBuilder()
             % drv->platform % settings.thisSystem % drvPath);
     }
 
+#if __APPLE__
+    additionalSandboxProfile = get(drv->env, "__sandboxProfile");
+#endif
+
     /* Are we doing a chroot build?  Note that fixed-output
        derivations are never done in a chroot, mainly so that
        functions like fetchurl (which needs a proper /etc/resolv.conf)
@@ -1723,7 +1700,13 @@ void DerivationGoal::startBuilder()
             throw Error("option ‘build-use-sandbox’ must be set to one of ‘true’, ‘false’ or ‘relaxed’");
         if (x == "true") {
             if (get(drv->env, "__noChroot") == "1")
-                throw Error(format("derivation ‘%1%’ has ‘__noChroot’ set, but that's not allowed when ‘build-use-sandbox’ is ‘true’") % drvPath);
+                throw Error(format("derivation ‘%1%’ has ‘__noChroot’ set, "
+                    "but that's not allowed when ‘build-use-sandbox’ is ‘true’") % drvPath);
+#if __APPLE__
+            if (additionalSandboxProfile != "")
+                throw Error(format("derivation ‘%1%’ specifies a sandbox profile, "
+                    "but this is only allowed when ‘build-use-sandbox’ is ‘relaxed’") % drvPath);
+#endif
             useChroot = true;
         }
         else if (x == "false")
@@ -1849,14 +1832,14 @@ void DerivationGoal::startBuilder()
            like passing all build-time dependencies of some path to a
            derivation that builds a NixOS DVD image. */
         PathSet paths, paths2;
-        computeFSClosure(worker.store, storePath, paths);
+        worker.store.computeFSClosure(storePath, paths);
         paths2 = paths;
 
         for (auto & j : paths2) {
             if (isDerivation(j)) {
-                Derivation drv = derivationFromPath(worker.store, j);
+                Derivation drv = worker.store.derivationFromPath(j);
                 for (auto & k : drv.outputs)
-                    computeFSClosure(worker.store, k.second.path, paths);
+                    worker.store.computeFSClosure(k.second.path, paths);
             }
         }
 
@@ -1920,13 +1903,10 @@ void DerivationGoal::startBuilder()
         PathSet closure;
         for (auto & i : dirsInChroot)
             if (isInStore(i.second))
-                computeFSClosure(worker.store, toStorePath(i.second), closure);
+                worker.store.computeFSClosure(toStorePath(i.second), closure);
         for (auto & i : closure)
             dirsInChroot[i] = i;
 
-#if __APPLE__
-        additionalSandboxProfile = get(drv->env, "__sandboxProfile");
-#endif
         string allowed = settings.get("allowed-impure-host-deps", string(DEFAULT_ALLOWED_IMPURE_PREFIXES));
         PathSet allowedPaths = tokenizeString<StringSet>(allowed);
 
@@ -1948,7 +1928,7 @@ void DerivationGoal::startBuilder()
                 }
             }
             if (!found)
-                throw Error(format("derivation '%1%' requested impure path ‘%2%’, but it was not in allowed-impure-host-deps (‘%3%’)") % drvPath % i % allowed);
+                throw Error(format("derivation ‘%1%’ requested impure path ‘%2%’, but it was not in allowed-impure-host-deps (‘%3%’)") % drvPath % i % allowed);
 
             dirsInChroot[i] = i;
         }
@@ -2170,7 +2150,7 @@ void DerivationGoal::startBuilder()
             size_t stackSize = 1 * 1024 * 1024;
             char * stack = (char *) mmap(0, stackSize,
                 PROT_WRITE | PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
-            if (!stack) throw SysError("allocating stack");
+            if (stack == MAP_FAILED) throw SysError("allocating stack");
             int flags = CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWIPC | CLONE_NEWUTS | CLONE_PARENT | SIGCHLD;
             if (!fixedOutput) flags |= CLONE_NEWNET;
             pid_t child = clone(childEntry, stack + stackSize, flags, this);
@@ -2191,7 +2171,7 @@ void DerivationGoal::startBuilder()
 #endif
     {
         ProcessOptions options;
-        options.allowVfork = !buildUser.enabled() && !isBuiltin(*drv);
+        options.allowVfork = !buildUser.enabled() && !drv->isBuiltin();
         pid = startProcess([&]() {
             runChild();
         }, options);
@@ -2445,7 +2425,7 @@ void DerivationGoal::runChild()
         const char *builder = "invalid";
 
         string sandboxProfile;
-        if (isBuiltin(*drv)) {
+        if (drv->isBuiltin()) {
             ;
 #if __APPLE__
         } else if (useChroot) {
@@ -2562,7 +2542,7 @@ void DerivationGoal::runChild()
         writeFull(STDERR_FILENO, string("\1\n"));
 
         /* Execute the program.  This should not return. */
-        if (isBuiltin(*drv)) {
+        if (drv->isBuiltin()) {
             try {
                 logType = ltFlat;
                 if (drv->builder == "builtin:fetchurl")
@@ -2625,6 +2605,8 @@ void DerivationGoal::registerOutputs()
        outputs to allow hard links between outputs. */
     InodesSeen inodesSeen;
 
+    Path checkSuffix = "-check";
+
     /* Check whether the output paths were created, and grep each
        output path to determine what other paths it references.  Also make all
        output paths read-only. */
@@ -2650,7 +2632,7 @@ void DerivationGoal::registerOutputs()
                 && redirectedBadOutputs.find(path) != redirectedBadOutputs.end()
                 && pathExists(redirected))
                 replaceValidPath(path, redirected);
-            if (buildMode == bmCheck)
+            if (buildMode == bmCheck && redirected != "")
                 actualPath = redirected;
         }
 
@@ -2713,12 +2695,29 @@ void DerivationGoal::registerOutputs()
                         format("output path ‘%1%’ should be a non-executable regular file") % path);
             }
 
-            /* Check the hash. */
+            /* Check the hash. In hash mode, move the path produced by
+               the derivation to its content-addressed location. */
             Hash h2 = recursive ? hashPath(ht, actualPath).first : hashFile(ht, actualPath);
-            if (h != h2)
-                throw BuildError(
-                    format("output path ‘%1%’ has %2% hash ‘%3%’ when ‘%4%’ was expected")
-                    % path % i.second.hashAlgo % printHash16or32(h2) % printHash16or32(h));
+            if (buildMode == bmHash) {
+                Path dest = makeFixedOutputPath(recursive, ht, h2, drv->env["name"]);
+                printMsg(lvlError, format("build produced path ‘%1%’ with %2% hash ‘%3%’")
+                    % dest % printHashType(ht) % printHash16or32(h2));
+                if (worker.store.isValidPath(dest))
+                    return;
+                if (actualPath != dest) {
+                    PathLocks outputLocks({dest});
+                    if (pathExists(dest))
+                        deletePath(dest);
+                    if (rename(actualPath.c_str(), dest.c_str()) == -1)
+                        throw SysError(format("moving ‘%1%’ to ‘%2%’") % actualPath % dest);
+                }
+                path = actualPath = dest;
+            } else {
+                if (h != h2)
+                    throw BuildError(
+                        format("output path ‘%1%’ has %2% hash ‘%3%’ when ‘%4%’ was expected")
+                        % path % i.second.hashAlgo % printHash16or32(h2) % printHash16or32(h));
+            }
         }
 
         /* Get rid of all weird permissions.  This also checks that
@@ -2734,9 +2733,20 @@ void DerivationGoal::registerOutputs()
         PathSet references = scanForReferences(actualPath, allPaths, hash);
 
         if (buildMode == bmCheck) {
+            if (!worker.store.isValidPath(path)) continue;
             ValidPathInfo info = worker.store.queryPathInfo(path);
-            if (hash.first != info.hash)
-                throw Error(format("derivation ‘%1%’ may not be deterministic: hash mismatch in output ‘%2%’") % drvPath % path);
+            if (hash.first != info.hash) {
+                if (settings.keepFailed) {
+                    Path dst = path + checkSuffix;
+                    if (pathExists(dst)) deletePath(dst);
+                    if (rename(actualPath.c_str(), dst.c_str()))
+                        throw SysError(format("renaming ‘%1%’ to ‘%2%’") % actualPath % dst);
+                    throw Error(format("derivation ‘%1%’ may not be deterministic: output ‘%2%’ differs from ‘%3%’")
+                        % drvPath % path % dst);
+                } else
+                    throw Error(format("derivation ‘%1%’ may not be deterministic: output ‘%2%’ differs")
+                        % drvPath % path);
+            }
             continue;
         }
 
@@ -2762,7 +2772,7 @@ void DerivationGoal::registerOutputs()
                 for (auto & i : references)
                     /* Don't call computeFSClosure on ourselves. */
                     if (actualPath != i)
-                        computeFSClosure(worker.store, i, used);
+                        worker.store.computeFSClosure(i, used);
             } else
                 used = references;
 
@@ -2781,9 +2791,11 @@ void DerivationGoal::registerOutputs()
         checkRefs("disallowedReferences", false, false);
         checkRefs("disallowedRequisites", false, true);
 
-        worker.store.optimisePath(path); // FIXME: combine with scanForReferences()
+        if (curRound == nrRounds) {
+            worker.store.optimisePath(path); // FIXME: combine with scanForReferences()
 
-        worker.store.markContentsGood(path);
+            worker.store.markContentsGood(path);
+        }
 
         ValidPathInfo info;
         info.path = path;
@@ -2796,10 +2808,37 @@ void DerivationGoal::registerOutputs()
 
     if (buildMode == bmCheck) return;
 
-    if (curRound > 1 && prevInfos != infos)
-        throw NotDeterministic(
-            format("result of ‘%1%’ differs from previous round; rejecting as non-deterministic")
-            % drvPath);
+    /* Compare the result with the previous round, and report which
+       path is different, if any.*/
+    if (curRound > 1 && prevInfos != infos) {
+        assert(prevInfos.size() == infos.size());
+        for (auto i = prevInfos.begin(), j = infos.begin(); i != prevInfos.end(); ++i, ++j)
+            if (!(*i == *j)) {
+                Path prev = i->path + checkSuffix;
+                if (pathExists(prev))
+                    throw NotDeterministic(
+                        format("output ‘%1%’ of ‘%2%’ differs from ‘%3%’ from previous round")
+                        % i->path % drvPath % prev);
+                else
+                    throw NotDeterministic(
+                        format("output ‘%1%’ of ‘%2%’ differs from previous round")
+                        % i->path % drvPath);
+            }
+        assert(false); // shouldn't happen
+    }
+
+    if (settings.keepFailed) {
+        for (auto & i : drv->outputs) {
+            Path prev = i.second.path + checkSuffix;
+            if (pathExists(prev)) deletePath(prev);
+            if (curRound < nrRounds) {
+                Path dst = i.second.path + checkSuffix;
+                if (rename(i.second.path.c_str(), dst.c_str()))
+                    throw SysError(format("renaming ‘%1%’ to ‘%2%’") % i.second.path % dst);
+            }
+        }
+
+    }
 
     if (curRound < nrRounds) {
         prevInfos = infos;
@@ -3122,6 +3161,7 @@ void SubstitutionGoal::tryNext()
         /* None left.  Terminate this goal and let someone else deal
            with it. */
         debug(format("path ‘%1%’ is required, but there is no substituter that can build it") % storePath);
+
         /* Hack: don't indicate failure if there were no substituters.
            In that case the calling derivation should just do a
            build. */
@@ -3721,7 +3761,7 @@ void Worker::waitForInput()
         }
     }
 
-    if (!waitingForAWhile.empty() && lastWokenUp + settings.pollInterval <= after) {
+    if (!waitingForAWhile.empty() && lastWokenUp + (time_t) settings.pollInterval <= after) {
         lastWokenUp = after;
         for (auto & i : waitingForAWhile) {
             GoalPtr goal = i.lock();
@@ -3817,8 +3857,17 @@ void LocalStore::repairPath(const Path & path)
 
     worker.run(goals);
 
-    if (goal->getExitCode() != Goal::ecSuccess)
-        throw Error(format("cannot repair path ‘%1%’") % path, worker.exitStatus());
+    if (goal->getExitCode() != Goal::ecSuccess) {
+        /* Since substituting the path didn't work, if we have a valid
+           deriver, then rebuild the deriver. */
+        Path deriver = queryDeriver(path);
+        if (deriver != "" && isValidPath(deriver)) {
+            goals.clear();
+            goals.insert(worker.makeDerivationGoal(deriver, StringSet(), bmRepair));
+            worker.run(goals);
+        } else
+            throw Error(format("cannot repair path ‘%1%’") % path, worker.exitStatus());
+    }
 }
 
 
diff --git a/src/libstore/derivations.cc b/src/libstore/derivations.cc
index 7959d5bfc3d5..d9b009d40322 100644
--- a/src/libstore/derivations.cc
+++ b/src/libstore/derivations.cc
@@ -2,7 +2,6 @@
 #include "store-api.hh"
 #include "globals.hh"
 #include "util.hh"
-#include "misc.hh"
 #include "worker-protocol.hh"
 
 
@@ -27,7 +26,49 @@ void DerivationOutput::parseHashInfo(bool & recursive, HashType & hashType, Hash
 }
 
 
-Path writeDerivation(StoreAPI & store,
+Path BasicDerivation::findOutput(const string & id) const
+{
+    auto i = outputs.find(id);
+    if (i == outputs.end())
+        throw Error(format("derivation has no output ‘%1%’") % id);
+    return i->second.path;
+}
+
+
+bool BasicDerivation::willBuildLocally() const
+{
+    return get(env, "preferLocalBuild") == "1" && canBuildLocally();
+}
+
+
+bool BasicDerivation::substitutesAllowed() const
+{
+    return get(env, "allowSubstitutes", "1") == "1";
+}
+
+
+bool BasicDerivation::isBuiltin() const
+{
+    return string(builder, 0, 8) == "builtin:";
+}
+
+
+bool BasicDerivation::canBuildLocally() const
+{
+    return platform == settings.thisSystem
+        || isBuiltin()
+#if __linux__
+        || (platform == "i686-linux" && settings.thisSystem == "x86_64-linux")
+        || (platform == "armv6l-linux" && settings.thisSystem == "armv7l-linux")
+#elif __FreeBSD__
+        || (platform == "i686-linux" && settings.thisSystem == "x86_64-freebsd")
+        || (platform == "i686-linux" && settings.thisSystem == "i686-freebsd")
+#endif
+        ;
+}
+
+
+Path writeDerivation(ref<Store> store,
     const Derivation & drv, const string & name, bool repair)
 {
     PathSet references;
@@ -38,10 +79,10 @@ Path writeDerivation(StoreAPI & store,
        (that can be missing (of course) and should not necessarily be
        held during a garbage collection). */
     string suffix = name + drvExtension;
-    string contents = unparseDerivation(drv);
+    string contents = drv.unparse();
     return settings.readOnlyMode
         ? computeStorePathForText(suffix, contents, references)
-        : store.addTextToStore(suffix, contents, references, repair);
+        : store->addTextToStore(suffix, contents, references, repair);
 }
 
 
@@ -149,14 +190,14 @@ static void printStrings(string & res, ForwardIterator i, ForwardIterator j)
 }
 
 
-string unparseDerivation(const Derivation & drv)
+string Derivation::unparse() const
 {
     string s;
     s.reserve(65536);
     s += "Derive([";
 
     bool first = true;
-    for (auto & i : drv.outputs) {
+    for (auto & i : outputs) {
         if (first) first = false; else s += ',';
         s += '('; printString(s, i.first);
         s += ','; printString(s, i.second.path);
@@ -167,7 +208,7 @@ string unparseDerivation(const Derivation & drv)
 
     s += "],[";
     first = true;
-    for (auto & i : drv.inputDrvs) {
+    for (auto & i : inputDrvs) {
         if (first) first = false; else s += ',';
         s += '('; printString(s, i.first);
         s += ','; printStrings(s, i.second.begin(), i.second.end());
@@ -175,15 +216,15 @@ string unparseDerivation(const Derivation & drv)
     }
 
     s += "],";
-    printStrings(s, drv.inputSrcs.begin(), drv.inputSrcs.end());
+    printStrings(s, inputSrcs.begin(), inputSrcs.end());
 
-    s += ','; printString(s, drv.platform);
-    s += ','; printString(s, drv.builder);
-    s += ','; printStrings(s, drv.args.begin(), drv.args.end());
+    s += ','; printString(s, platform);
+    s += ','; printString(s, builder);
+    s += ','; printStrings(s, args.begin(), args.end());
 
     s += ",[";
     first = true;
-    for (auto & i : drv.env) {
+    for (auto & i : env) {
         if (first) first = false; else s += ',';
         s += '('; printString(s, i.first);
         s += ','; printString(s, i.second);
@@ -202,11 +243,11 @@ bool isDerivation(const string & fileName)
 }
 
 
-bool isFixedOutputDrv(const Derivation & drv)
+bool BasicDerivation::isFixedOutput() const
 {
-    return drv.outputs.size() == 1 &&
-        drv.outputs.begin()->first == "out" &&
-        drv.outputs.begin()->second.hash != "";
+    return outputs.size() == 1 &&
+        outputs.begin()->first == "out" &&
+        outputs.begin()->second.hash != "";
 }
 
 
@@ -233,10 +274,10 @@ DrvHashes drvHashes;
    paths have been replaced by the result of a recursive call to this
    function, and that for fixed-output derivations we return a hash of
    its output path. */
-Hash hashDerivationModulo(StoreAPI & store, Derivation drv)
+Hash hashDerivationModulo(Store & store, Derivation drv)
 {
     /* Return a fixed hash for fixed-output derivations. */
-    if (isFixedOutputDrv(drv)) {
+    if (drv.isFixedOutput()) {
         DerivationOutputs::const_iterator i = drv.outputs.begin();
         return hashString(htSHA256, "fixed:out:"
             + i->second.hashAlgo + ":"
@@ -259,7 +300,7 @@ Hash hashDerivationModulo(StoreAPI & store, Derivation drv)
     }
     drv.inputDrvs = inputs2;
 
-    return hashString(htSHA256, unparseDerivation(drv));
+    return hashString(htSHA256, drv.unparse());
 }
 
 
@@ -286,10 +327,10 @@ bool wantOutput(const string & output, const std::set<string> & wanted)
 }
 
 
-PathSet outputPaths(const BasicDerivation & drv)
+PathSet BasicDerivation::outputPaths() const
 {
     PathSet paths;
-    for (auto & i : drv.outputs)
+    for (auto & i : outputs)
         paths.insert(i.second.path);
     return paths;
 }
diff --git a/src/libstore/derivations.hh b/src/libstore/derivations.hh
index f0842045f86b..6f98869b0fe0 100644
--- a/src/libstore/derivations.hh
+++ b/src/libstore/derivations.hh
@@ -50,40 +50,56 @@ struct BasicDerivation
     StringPairs env;
 
     virtual ~BasicDerivation() { };
+
+    /* Return the path corresponding to the output identifier `id' in
+       the given derivation. */
+    Path findOutput(const string & id) const;
+
+    bool willBuildLocally() const;
+
+    bool substitutesAllowed() const;
+
+    bool isBuiltin() const;
+
+    bool canBuildLocally() const;
+
+    /* Return true iff this is a fixed-output derivation. */
+    bool isFixedOutput() const;
+
+    /* Return the output paths of a derivation. */
+    PathSet outputPaths() const;
+
 };
 
 struct Derivation : BasicDerivation
 {
     DerivationInputs inputDrvs; /* inputs that are sub-derivations */
+
+    /* Print a derivation. */
+    std::string unparse() const;
 };
 
 
-class StoreAPI;
+class Store;
 
 
 /* Write a derivation to the Nix store, and return its path. */
-Path writeDerivation(StoreAPI & store,
+Path writeDerivation(ref<Store> store,
     const Derivation & drv, const string & name, bool repair = false);
 
 /* Read a derivation from a file. */
 Derivation readDerivation(const Path & drvPath);
 
-/* Print a derivation. */
-string unparseDerivation(const Derivation & drv);
-
-/* Check whether a file name ends with the extensions for
+/* Check whether a file name ends with the extension for
    derivations. */
 bool isDerivation(const string & fileName);
 
-/* Return true iff this is a fixed-output derivation. */
-bool isFixedOutputDrv(const Derivation & drv);
-
-Hash hashDerivationModulo(StoreAPI & store, Derivation drv);
+Hash hashDerivationModulo(Store & store, Derivation drv);
 
 /* Memoisation of hashDerivationModulo(). */
 typedef std::map<Path, Hash> DrvHashes;
 
-extern DrvHashes drvHashes;
+extern DrvHashes drvHashes; // FIXME: global, not thread-safe
 
 /* Split a string specifying a derivation and a set of outputs
    (/nix/store/hash-foo!out1,out2,...) into the derivation path and
@@ -95,8 +111,6 @@ Path makeDrvPathWithOutputs(const Path & drvPath, const std::set<string> & outpu
 
 bool wantOutput(const string & output, const std::set<string> & wanted);
 
-PathSet outputPaths(const BasicDerivation & drv);
-
 struct Source;
 struct Sink;
 
diff --git a/src/libstore/download.cc b/src/libstore/download.cc
index 822e9a8db867..01ce1ea2fd4c 100644
--- a/src/libstore/download.cc
+++ b/src/libstore/download.cc
@@ -188,7 +188,7 @@ DownloadResult downloadFile(string url, const DownloadOptions & options)
 }
 
 
-Path downloadFileCached(const string & url, bool unpack)
+Path downloadFileCached(ref<Store> store, const string & url, bool unpack)
 {
     Path cacheDir = getEnv("XDG_CACHE_HOME", getEnv("HOME", "") + "/.cache") + "/nix/tarballs";
     createDirs(cacheDir);
diff --git a/src/libstore/download.hh b/src/libstore/download.hh
index c1cb25b90c32..7aec8de73e48 100644
--- a/src/libstore/download.hh
+++ b/src/libstore/download.hh
@@ -1,6 +1,7 @@
 #pragma once
 
 #include "types.hh"
+
 #include <string>
 
 namespace nix {
@@ -18,9 +19,11 @@ struct DownloadResult
     string data, etag;
 };
 
+class Store;
+
 DownloadResult downloadFile(string url, const DownloadOptions & options);
 
-Path downloadFileCached(const string & url, bool unpack);
+Path downloadFileCached(ref<Store> store, const string & url, bool unpack);
 
 MakeError(DownloadError, Error)
 
diff --git a/src/libstore/gc.cc b/src/libstore/gc.cc
index 998a7516a13d..d19af1cefaf2 100644
--- a/src/libstore/gc.cc
+++ b/src/libstore/gc.cc
@@ -1,5 +1,5 @@
+#include "derivations.hh"
 #include "globals.hh"
-#include "misc.hh"
 #include "local-store.hh"
 
 #include <functional>
@@ -83,7 +83,7 @@ void LocalStore::addIndirectRoot(const Path & path)
 }
 
 
-Path addPermRoot(StoreAPI & store, const Path & _storePath,
+Path Store::addPermRoot(const Path & _storePath,
     const Path & _gcRoot, bool indirect, bool allowOutsideRootsDir)
 {
     Path storePath(canonPath(_storePath));
@@ -101,7 +101,7 @@ Path addPermRoot(StoreAPI & store, const Path & _storePath,
         if (pathExists(gcRoot) && (!isLink(gcRoot) || !isInStore(readLink(gcRoot))))
             throw Error(format("cannot create symlink ‘%1%’; already exists") % gcRoot);
         makeSymlink(gcRoot, storePath);
-        store.addIndirectRoot(gcRoot);
+        addIndirectRoot(gcRoot);
     }
 
     else {
@@ -127,7 +127,7 @@ Path addPermRoot(StoreAPI & store, const Path & _storePath,
        check if the root is in a directory in or linked from the
        gcroots directory. */
     if (settings.checkRootReachability) {
-        Roots roots = store.findRoots();
+        Roots roots = findRoots();
         if (roots.find(gcRoot) == roots.end())
             printMsg(lvlError,
                 format(
@@ -139,7 +139,7 @@ Path addPermRoot(StoreAPI & store, const Path & _storePath,
     /* Grab the global GC root, causing us to block while a GC is in
        progress.  This prevents the set of permanent roots from
        increasing while a GC is in progress. */
-    store.syncWithGC();
+    syncWithGC();
 
     return gcRoot;
 }
@@ -260,19 +260,16 @@ static void readTempRoots(PathSet & tempRoots, FDs & fds)
 }
 
 
-static void foundRoot(StoreAPI & store,
-    const Path & path, const Path & target, Roots & roots)
+void LocalStore::findRoots(const Path & path, unsigned char type, Roots & roots)
 {
-    Path storePath = toStorePath(target);
-    if (store.isValidPath(storePath))
-        roots[path] = storePath;
-    else
-        printMsg(lvlInfo, format("skipping invalid root from ‘%1%’ to ‘%2%’") % path % storePath);
-}
-
+    auto foundRoot = [&](const Path & path, const Path & target) {
+        Path storePath = toStorePath(target);
+        if (isValidPath(storePath))
+            roots[path] = storePath;
+        else
+            printMsg(lvlInfo, format("skipping invalid root from ‘%1%’ to ‘%2%’") % path % storePath);
+    };
 
-static void findRoots(StoreAPI & store, const Path & path, unsigned char type, Roots & roots)
-{
     try {
 
         if (type == DT_UNKNOWN)
@@ -280,13 +277,13 @@ static void findRoots(StoreAPI & store, const Path & path, unsigned char type, R
 
         if (type == DT_DIR) {
             for (auto & i : readDirectory(path))
-                findRoots(store, path + "/" + i.name, i.type, roots);
+                findRoots(path + "/" + i.name, i.type, roots);
         }
 
         else if (type == DT_LNK) {
             Path target = readLink(path);
             if (isInStore(target))
-                foundRoot(store, path, target, roots);
+                foundRoot(path, target);
 
             /* Handle indirect roots. */
             else {
@@ -300,14 +297,14 @@ static void findRoots(StoreAPI & store, const Path & path, unsigned char type, R
                     struct stat st2 = lstat(target);
                     if (!S_ISLNK(st2.st_mode)) return;
                     Path target2 = readLink(target);
-                    if (isInStore(target2)) foundRoot(store, target, target2, roots);
+                    if (isInStore(target2)) foundRoot(target, target2);
                 }
             }
         }
 
         else if (type == DT_REG) {
             Path storePath = settings.nixStore + "/" + baseNameOf(path);
-            if (store.isValidPath(storePath))
+            if (isValidPath(storePath))
                 roots[path] = storePath;
         }
 
@@ -328,16 +325,16 @@ Roots LocalStore::findRoots()
     Roots roots;
 
     /* Process direct roots in {gcroots,manifests,profiles}. */
-    nix::findRoots(*this, settings.nixStateDir + "/" + gcRootsDir, DT_UNKNOWN, roots);
+    findRoots(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);
+        findRoots(settings.nixStateDir + "/manifests", DT_UNKNOWN, roots);
+    findRoots(settings.nixStateDir + "/profiles", DT_UNKNOWN, roots);
 
     return roots;
 }
 
 
-static void addAdditionalRoots(StoreAPI & store, PathSet & roots)
+void LocalStore::findRuntimeRoots(PathSet & roots)
 {
     Path rootFinder = getEnv("NIX_ROOT_FINDER",
         settings.nixLibexecDir + "/nix/find-runtime-roots.pl");
@@ -353,7 +350,7 @@ static void addAdditionalRoots(StoreAPI & store, PathSet & roots)
     for (auto & i : paths)
         if (isInStore(i)) {
             Path path = toStorePath(i);
-            if (roots.find(path) == roots.end() && store.isValidPath(path)) {
+            if (roots.find(path) == roots.end() && isValidPath(path)) {
                 debug(format("got additional root ‘%1%’") % path);
                 roots.insert(path);
             }
@@ -628,7 +625,7 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
        to add running programs to the set of roots (to prevent them
        from being garbage collected). */
     if (!options.ignoreLiveness)
-        addAdditionalRoots(*this, state.roots);
+        findRuntimeRoots(state.roots);
 
     /* Read the temporary roots.  This acquires read locks on all
        per-process temporary root files.  So after this point no paths
diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc
index d7cd0b088d98..13179459f1c1 100644
--- a/src/libstore/local-store.cc
+++ b/src/libstore/local-store.cc
@@ -397,9 +397,15 @@ int LocalStore::getSchema()
 }
 
 
+bool LocalStore::haveWriteAccess()
+{
+    return access(settings.nixDBPath.c_str(), R_OK | W_OK) == 0;
+}
+
+
 void LocalStore::openDB(bool create)
 {
-    if (access(settings.nixDBPath.c_str(), R_OK | W_OK))
+    if (!haveWriteAccess())
         throw SysError(format("Nix database directory ‘%1%’ is not writable") % settings.nixDBPath);
 
     /* Open the Nix database. */
@@ -602,10 +608,10 @@ static void canonicalisePathMetaData_(const Path & path, uid_t fromUid, InodesSe
        users group); we check for this case below. */
     if (st.st_uid != geteuid()) {
 #if HAVE_LCHOWN
-        if (lchown(path.c_str(), geteuid(), (gid_t) -1) == -1)
+        if (lchown(path.c_str(), geteuid(), getegid()) == -1)
 #else
         if (!S_ISLNK(st.st_mode) &&
-            chown(path.c_str(), geteuid(), (gid_t) -1) == -1)
+            chown(path.c_str(), geteuid(), getegid()) == -1)
 #endif
             throw SysError(format("changing owner of ‘%1%’ to %2%")
                 % path % geteuid());
@@ -649,7 +655,7 @@ void LocalStore::checkDerivationOutputs(const Path & drvPath, const Derivation &
     assert(isDerivation(drvName));
     drvName = string(drvName, 0, drvName.size() - drvExtension.size());
 
-    if (isFixedOutputDrv(drv)) {
+    if (drv.isFixedOutput()) {
         DerivationOutputs::const_iterator out = drv.outputs.find("out");
         if (out == drv.outputs.end())
             throw Error(format("derivation ‘%1%’ does not have an output named ‘out’") % drvPath);
@@ -1329,7 +1335,7 @@ void LocalStore::registerValidPaths(const ValidPathInfos & infos)
            error if a cycle is detected and roll back the
            transaction.  Cycles can only occur when a derivation
            has multiple outputs. */
-        topoSortPaths(*this, paths);
+        topoSortPaths(paths);
 
         txn.commit();
     } end_retry_sqlite;
diff --git a/src/libstore/local-store.hh b/src/libstore/local-store.hh
index ebdf19bf1359..b6d39d345c94 100644
--- a/src/libstore/local-store.hh
+++ b/src/libstore/local-store.hh
@@ -80,7 +80,7 @@ struct SQLiteStmt
 };
 
 
-class LocalStore : public StoreAPI
+class LocalStore : public Store
 {
 private:
     typedef std::map<Path, RunningSubstituter> RunningSubstituters;
@@ -253,6 +253,12 @@ private:
 
     int getSchema();
 
+public:
+
+    static bool haveWriteAccess();
+
+private:
+
     void openDB(bool create);
 
     void makeStoreWritable();
@@ -297,6 +303,10 @@ private:
 
     int openGCLock(LockType lockType);
 
+    void findRoots(const Path & path, unsigned char type, Roots & roots);
+
+    void findRuntimeRoots(PathSet & roots);
+
     void removeUnusedLinks(const GCState & state);
 
     void startSubstituter(const Path & substituter,
diff --git a/src/libstore/misc.cc b/src/libstore/misc.cc
index 61a976c02f5c..12472f017ce4 100644
--- a/src/libstore/misc.cc
+++ b/src/libstore/misc.cc
@@ -1,21 +1,21 @@
-#include "misc.hh"
-#include "store-api.hh"
-#include "local-store.hh"
+#include "derivations.hh"
 #include "globals.hh"
+#include "local-store.hh"
+#include "store-api.hh"
 
 
 namespace nix {
 
 
-Derivation derivationFromPath(StoreAPI & store, const Path & drvPath)
+Derivation Store::derivationFromPath(const Path & drvPath)
 {
     assertStorePath(drvPath);
-    store.ensurePath(drvPath);
+    ensurePath(drvPath);
     return readDerivation(drvPath);
 }
 
 
-void computeFSClosure(StoreAPI & store, const Path & path,
+void Store::computeFSClosure(const Path & path,
     PathSet & paths, bool flipDirection, bool includeOutputs, bool includeDerivers)
 {
     if (paths.find(path) != paths.end()) return;
@@ -24,50 +24,42 @@ void computeFSClosure(StoreAPI & store, const Path & path,
     PathSet edges;
 
     if (flipDirection) {
-        store.queryReferrers(path, edges);
+        queryReferrers(path, edges);
 
         if (includeOutputs) {
-            PathSet derivers = store.queryValidDerivers(path);
+            PathSet derivers = queryValidDerivers(path);
             for (auto & i : derivers)
                 edges.insert(i);
         }
 
         if (includeDerivers && isDerivation(path)) {
-            PathSet outputs = store.queryDerivationOutputs(path);
+            PathSet outputs = queryDerivationOutputs(path);
             for (auto & i : outputs)
-                if (store.isValidPath(i) && store.queryDeriver(i) == path)
+                if (isValidPath(i) && queryDeriver(i) == path)
                     edges.insert(i);
         }
 
     } else {
-        store.queryReferences(path, edges);
+        queryReferences(path, edges);
 
         if (includeOutputs && isDerivation(path)) {
-            PathSet outputs = store.queryDerivationOutputs(path);
+            PathSet outputs = queryDerivationOutputs(path);
             for (auto & i : outputs)
-                if (store.isValidPath(i)) edges.insert(i);
+                if (isValidPath(i)) edges.insert(i);
         }
 
         if (includeDerivers) {
-            Path deriver = store.queryDeriver(path);
-            if (store.isValidPath(deriver)) edges.insert(deriver);
+            Path deriver = queryDeriver(path);
+            if (isValidPath(deriver)) edges.insert(deriver);
         }
     }
 
     for (auto & i : edges)
-        computeFSClosure(store, i, paths, flipDirection, includeOutputs, includeDerivers);
-}
-
-
-Path findOutput(const Derivation & drv, string id)
-{
-    for (auto & i : drv.outputs)
-        if (i.first == id) return i.second.path;
-    throw Error(format("derivation has no output ‘%1%’") % id);
+        computeFSClosure(i, paths, flipDirection, includeOutputs, includeDerivers);
 }
 
 
-void queryMissing(StoreAPI & store, const PathSet & targets,
+void Store::queryMissing(const PathSet & targets,
     PathSet & willBuild, PathSet & willSubstitute, PathSet & unknown,
     unsigned long long & downloadSize, unsigned long long & narSize)
 {
@@ -105,27 +97,27 @@ void queryMissing(StoreAPI & store, const PathSet & targets,
             DrvPathWithOutputs i2 = parseDrvPathWithOutputs(i);
 
             if (isDerivation(i2.first)) {
-                if (!store.isValidPath(i2.first)) {
+                if (!isValidPath(i2.first)) {
                     // FIXME: we could try to substitute p.
                     unknown.insert(i);
                     continue;
                 }
-                Derivation drv = derivationFromPath(store, i2.first);
+                Derivation drv = derivationFromPath(i2.first);
 
                 PathSet invalid;
                 for (auto & j : drv.outputs)
                     if (wantOutput(j.first, i2.second)
-                        && !store.isValidPath(j.second.path))
+                        && !isValidPath(j.second.path))
                         invalid.insert(j.second.path);
                 if (invalid.empty()) continue;
 
                 todoDrv.insert(i);
-                if (settings.useSubstitutes && substitutesAllowed(drv))
+                if (settings.useSubstitutes && drv.substitutesAllowed())
                     query.insert(invalid.begin(), invalid.end());
             }
 
             else {
-                if (store.isValidPath(i)) continue;
+                if (isValidPath(i)) continue;
                 query.insert(i);
                 todoNonDrv.insert(i);
             }
@@ -134,20 +126,20 @@ void queryMissing(StoreAPI & store, const PathSet & targets,
         todo.clear();
 
         SubstitutablePathInfos infos;
-        store.querySubstitutablePathInfos(query, infos);
+        querySubstitutablePathInfos(query, infos);
 
         for (auto & i : todoDrv) {
             DrvPathWithOutputs i2 = parseDrvPathWithOutputs(i);
 
             // FIXME: cache this
-            Derivation drv = derivationFromPath(store, i2.first);
+            Derivation drv = derivationFromPath(i2.first);
 
             PathSet outputs;
             bool mustBuild = false;
-            if (settings.useSubstitutes && substitutesAllowed(drv)) {
+            if (settings.useSubstitutes && drv.substitutesAllowed()) {
                 for (auto & j : drv.outputs) {
                     if (!wantOutput(j.first, i2.second)) continue;
-                    if (!store.isValidPath(j.second.path)) {
+                    if (!isValidPath(j.second.path)) {
                         if (infos.find(j.second.path) == infos.end())
                             mustBuild = true;
                         else
@@ -181,38 +173,38 @@ void queryMissing(StoreAPI & store, const PathSet & targets,
 }
 
 
-static void dfsVisit(StoreAPI & store, const PathSet & paths,
-    const Path & path, PathSet & visited, Paths & sorted,
-    PathSet & parents)
+Paths Store::topoSortPaths(const PathSet & paths)
 {
-    if (parents.find(path) != parents.end())
-        throw BuildError(format("cycle detected in the references of ‘%1%’") % path);
+    Paths sorted;
+    PathSet visited, parents;
 
-    if (visited.find(path) != visited.end()) return;
-    visited.insert(path);
-    parents.insert(path);
+    std::function<void(const Path & path)> dfsVisit;
 
-    PathSet references;
-    if (store.isValidPath(path))
-        store.queryReferences(path, references);
+    dfsVisit = [&](const Path & path) {
+        if (parents.find(path) != parents.end())
+            throw BuildError(format("cycle detected in the references of ‘%1%’") % path);
 
-    for (auto & i : references)
-        /* Don't traverse into paths that don't exist.  That can
-           happen due to substitutes for non-existent paths. */
-        if (i != path && paths.find(i) != paths.end())
-            dfsVisit(store, paths, i, visited, sorted, parents);
+        if (visited.find(path) != visited.end()) return;
+        visited.insert(path);
+        parents.insert(path);
 
-    sorted.push_front(path);
-    parents.erase(path);
-}
+        PathSet references;
+        if (isValidPath(path))
+            queryReferences(path, references);
 
+        for (auto & i : references)
+            /* Don't traverse into paths that don't exist.  That can
+               happen due to substitutes for non-existent paths. */
+            if (i != path && paths.find(i) != paths.end())
+                dfsVisit(i);
+
+        sorted.push_front(path);
+        parents.erase(path);
+    };
 
-Paths topoSortPaths(StoreAPI & store, const PathSet & paths)
-{
-    Paths sorted;
-    PathSet visited, parents;
     for (auto & i : paths)
-        dfsVisit(store, paths, i, visited, sorted, parents);
+        dfsVisit(i);
+
     return sorted;
 }
 
diff --git a/src/libstore/misc.hh b/src/libstore/misc.hh
deleted file mode 100644
index 495c528750fb..000000000000
--- a/src/libstore/misc.hh
+++ /dev/null
@@ -1,40 +0,0 @@
-#pragma once
-
-#include "derivations.hh"
-
-
-namespace nix {
-
-
-/* Read a derivation, after ensuring its existence through
-   ensurePath(). */
-Derivation derivationFromPath(StoreAPI & store, const Path & drvPath);
-
-/* Place in `paths' the set of all store paths in the file system
-   closure of `storePath'; that is, all paths than can be directly or
-   indirectly reached from it.  `paths' is not cleared.  If
-   `flipDirection' is true, the set of paths that can reach
-   `storePath' is returned; that is, the closures under the
-   `referrers' relation instead of the `references' relation is
-   returned. */
-void computeFSClosure(StoreAPI & store, const Path & path,
-    PathSet & paths, bool flipDirection = false,
-    bool includeOutputs = false, bool includeDerivers = false);
-
-/* Return the path corresponding to the output identifier `id' in the
-   given derivation. */
-Path findOutput(const Derivation & drv, string id);
-
-/* Given a set of paths that are to be built, return the set of
-   derivations that will be built, and the set of output paths that
-   will be substituted. */
-void queryMissing(StoreAPI & store, const PathSet & targets,
-    PathSet & willBuild, PathSet & willSubstitute, PathSet & unknown,
-    unsigned long long & downloadSize, unsigned long long & narSize);
-
-bool willBuildLocally(const BasicDerivation & drv);
-
-bool substitutesAllowed(const BasicDerivation & drv);
-
-
-}
diff --git a/src/libstore/pathlocks.cc b/src/libstore/pathlocks.cc
index 1c87034f8e1a..eddf5bcbda65 100644
--- a/src/libstore/pathlocks.cc
+++ b/src/libstore/pathlocks.cc
@@ -161,7 +161,11 @@ bool PathLocks::lockPaths(const PathSet & _paths,
 
 PathLocks::~PathLocks()
 {
-    unlock();
+    try {
+        unlock();
+    } catch (...) {
+        ignoreException();
+    }
 }
 
 
diff --git a/src/libstore/profiles.cc b/src/libstore/profiles.cc
index da3f7da9d19d..cc83a838eddc 100644
--- a/src/libstore/profiles.cc
+++ b/src/libstore/profiles.cc
@@ -74,7 +74,7 @@ static void makeName(const Path & profile, unsigned int num,
 }
 
 
-Path createGeneration(Path profile, Path outPath)
+Path createGeneration(ref<Store> store, Path profile, Path outPath)
 {
     /* The new generation number should be higher than old the
        previous ones. */
@@ -108,7 +108,7 @@ Path createGeneration(Path profile, Path outPath)
        user environment etc. we've just built. */
     Path generation;
     makeName(profile, num + 1, generation);
-    addPermRoot(*store, outPath, generation, false, true);
+    store->addPermRoot(outPath, generation, false, true);
 
     return generation;
 }
diff --git a/src/libstore/profiles.hh b/src/libstore/profiles.hh
index e99bbf398a86..d758d94b603c 100644
--- a/src/libstore/profiles.hh
+++ b/src/libstore/profiles.hh
@@ -31,7 +31,9 @@ typedef list<Generation> Generations;
    profile, sorted by generation number. */
 Generations findGenerations(Path profile, int & curGen);
 
-Path createGeneration(Path profile, Path outPath);
+class Store;
+
+Path createGeneration(ref<Store> store, Path profile, Path outPath);
 
 void deleteGeneration(const Path & profile, unsigned int gen);
 
diff --git a/src/libstore/remote-store.cc b/src/libstore/remote-store.cc
index 262e4650bfb5..679210d4cb16 100644
--- a/src/libstore/remote-store.cc
+++ b/src/libstore/remote-store.cc
@@ -50,14 +50,8 @@ void RemoteStore::openConnection(bool reserveSpace)
     if (initialised) return;
     initialised = true;
 
-    string remoteMode = getEnv("NIX_REMOTE");
-
-    if (remoteMode == "daemon")
-        /* Connect to a daemon that does the privileged work for
-           us. */
-        connectToDaemon();
-    else
-        throw Error(format("invalid setting for NIX_REMOTE, ‘%1%’") % remoteMode);
+    /* Connect to a daemon that does the privileged work for us. */
+    connectToDaemon();
 
     from.fd = fdSocket;
     to.fd = fdSocket;
diff --git a/src/libstore/remote-store.hh b/src/libstore/remote-store.hh
index b0787c072904..f15182285e8c 100644
--- a/src/libstore/remote-store.hh
+++ b/src/libstore/remote-store.hh
@@ -14,7 +14,7 @@ struct FdSink;
 struct FdSource;
 
 
-class RemoteStore : public StoreAPI
+class RemoteStore : public Store
 {
 public:
 
diff --git a/src/libstore/store-api.cc b/src/libstore/store-api.cc
index a73ebd824264..039d07e29b3e 100644
--- a/src/libstore/store-api.cc
+++ b/src/libstore/store-api.cc
@@ -220,7 +220,7 @@ Path computeStorePathForText(const string & name, const string & s,
 /* Return a string accepted by decodeValidPathInfo() that
    registers the specified paths as valid.  Note: it's the
    responsibility of the caller to provide a closure. */
-string StoreAPI::makeValidityRegistration(const PathSet & paths,
+string Store::makeValidityRegistration(const PathSet & paths,
     bool showDerivers, bool showHash)
 {
     string s = "";
@@ -284,12 +284,12 @@ string showPaths(const PathSet & paths)
 }
 
 
-void exportPaths(StoreAPI & store, const Paths & paths,
+void Store::exportPaths(const Paths & paths,
     bool sign, Sink & sink)
 {
     for (auto & i : paths) {
         sink << 1;
-        store.exportPath(i, sign, sink);
+        exportPath(i, sign, sink);
     }
     sink << 0;
 }
@@ -306,15 +306,24 @@ void exportPaths(StoreAPI & store, const Paths & paths,
 namespace nix {
 
 
-std::shared_ptr<StoreAPI> store;
+ref<Store> openStore(bool reserveSpace)
+{
+    enum { mDaemon, mLocal, mAuto } mode;
 
+    mode = getEnv("NIX_REMOTE") == "daemon" ? mDaemon : mAuto;
 
-std::shared_ptr<StoreAPI> openStore(bool reserveSpace)
-{
-    if (getEnv("NIX_REMOTE") == "")
-        return std::shared_ptr<StoreAPI>(new LocalStore(reserveSpace));
-    else
-        return std::shared_ptr<StoreAPI>(new RemoteStore());
+    if (mode == mAuto) {
+        if (LocalStore::haveWriteAccess())
+            mode = mLocal;
+        else if (pathExists(settings.nixDaemonSocketFile))
+            mode = mDaemon;
+        else
+            mode = mLocal;
+    }
+
+    return mode == mDaemon
+        ? (ref<Store>) make_ref<RemoteStore>()
+        : (ref<Store>) make_ref<LocalStore>(reserveSpace);
 }
 
 
diff --git a/src/libstore/store-api.hh b/src/libstore/store-api.hh
index 9cc5fd45b7c4..888ef3e2a083 100644
--- a/src/libstore/store-api.hh
+++ b/src/libstore/store-api.hh
@@ -103,7 +103,7 @@ struct ValidPathInfo
 typedef list<ValidPathInfo> ValidPathInfos;
 
 
-enum BuildMode { bmNormal, bmRepair, bmCheck };
+enum BuildMode { bmNormal, bmRepair, bmCheck, bmHash };
 
 
 struct BuildResult
@@ -132,13 +132,14 @@ struct BuildResult
 
 
 struct BasicDerivation;
+struct Derivation;
 
 
-class StoreAPI
+class Store
 {
 public:
 
-    virtual ~StoreAPI() { }
+    virtual ~Store() { }
 
     /* Check whether a path is valid. */
     virtual bool isValidPath(const Path & path) = 0;
@@ -214,6 +215,10 @@ public:
     virtual void exportPath(const Path & path, bool sign,
         Sink & sink) = 0;
 
+    /* Export multiple paths in the format expected by ‘nix-store
+       --import’. */
+    void exportPaths(const Paths & paths, bool sign, Sink & sink);
+
     /* Import a sequence of NAR dumps created by exportPaths() into
        the Nix store. */
     virtual Paths importPaths(bool requireSignature, Source & source) = 0;
@@ -250,6 +255,10 @@ public:
        `path' has disappeared. */
     virtual void addIndirectRoot(const Path & path) = 0;
 
+    /* Register a permanent GC root. */
+    Path addPermRoot(const Path & storePath,
+        const Path & gcRoot, bool indirect, bool allowOutsideRootsDir = false);
+
     /* Acquire the global GC lock, then immediately release it.  This
        function must be called after registering a new permanent root,
        but before exiting.  Otherwise, it is possible that a running
@@ -298,6 +307,35 @@ public:
     /* Check the integrity of the Nix store.  Returns true if errors
        remain. */
     virtual bool verifyStore(bool checkContents, bool repair) = 0;
+
+    /* Utility functions. */
+
+    /* Read a derivation, after ensuring its existence through
+       ensurePath(). */
+    Derivation derivationFromPath(const Path & drvPath);
+
+    /* Place in `paths' the set of all store paths in the file system
+       closure of `storePath'; that is, all paths than can be directly
+       or indirectly reached from it.  `paths' is not cleared.  If
+       `flipDirection' is true, the set of paths that can reach
+       `storePath' is returned; that is, the closures under the
+       `referrers' relation instead of the `references' relation is
+       returned. */
+    void computeFSClosure(const Path & path,
+        PathSet & paths, bool flipDirection = false,
+        bool includeOutputs = false, bool includeDerivers = false);
+
+    /* Given a set of paths that are to be built, return the set of
+       derivations that will be built, and the set of output paths
+       that will be substituted. */
+    void queryMissing(const PathSet & targets,
+        PathSet & willBuild, PathSet & willSubstitute, PathSet & unknown,
+        unsigned long long & downloadSize, unsigned long long & narSize);
+
+    /* Sort a set of paths topologically under the references
+       relation.  If p refers to q, then p preceeds q in this list. */
+    Paths topoSortPaths(const PathSet & paths);
+
 };
 
 
@@ -372,24 +410,9 @@ Path computeStorePathForText(const string & name, const string & s,
 void removeTempRoots();
 
 
-/* Register a permanent GC root. */
-Path addPermRoot(StoreAPI & store, const Path & storePath,
-    const Path & gcRoot, bool indirect, bool allowOutsideRootsDir = false);
-
-
-/* Sort a set of paths topologically under the references relation.
-   If p refers to q, then p preceeds q in this list. */
-Paths topoSortPaths(StoreAPI & store, const PathSet & paths);
-
-
-/* For now, there is a single global store API object, but we'll
-   purify that in the future. */
-extern std::shared_ptr<StoreAPI> store;
-
-
 /* Factory method: open the Nix database, either through the local or
    remote implementation. */
-std::shared_ptr<StoreAPI> openStore(bool reserveSpace = true);
+ref<Store> openStore(bool reserveSpace = true);
 
 
 /* Display a set of paths in human-readable form (i.e., between quotes
@@ -401,12 +424,6 @@ ValidPathInfo decodeValidPathInfo(std::istream & str,
     bool hashGiven = false);
 
 
-/* Export multiple paths in the format expected by ‘nix-store
-   --import’. */
-void exportPaths(StoreAPI & store, const Paths & paths,
-    bool sign, Sink & sink);
-
-
 MakeError(SubstError, Error)
 MakeError(BuildError, Error) /* denotes a permanent build failure */