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/binary-cache-store.cc12
-rw-r--r--src/libstore/binary-cache-store.hh8
-rw-r--r--src/libstore/build.cc27
-rw-r--r--src/libstore/derivations.cc8
-rw-r--r--src/libstore/derivations.hh3
-rw-r--r--src/libstore/download.cc6
-rw-r--r--src/libstore/export-import.cc6
-rw-r--r--src/libstore/gc.cc2
-rw-r--r--src/libstore/globals.hh6
-rw-r--r--src/libstore/legacy-ssh-store.cc9
-rw-r--r--src/libstore/local-store.cc56
-rw-r--r--src/libstore/local-store.hh15
-rw-r--r--src/libstore/nar-info-disk-cache.cc4
-rw-r--r--src/libstore/nar-info.cc6
-rw-r--r--src/libstore/optimise-store.cc18
-rw-r--r--src/libstore/remote-store.cc16
-rw-r--r--src/libstore/remote-store.hh11
-rw-r--r--src/libstore/store-api.cc144
-rw-r--r--src/libstore/store-api.hh43
19 files changed, 214 insertions, 186 deletions
diff --git a/src/libstore/binary-cache-store.cc b/src/libstore/binary-cache-store.cc
index 46c5aa21b2eb..8147345c2e1c 100644
--- a/src/libstore/binary-cache-store.cc
+++ b/src/libstore/binary-cache-store.cc
@@ -134,7 +134,7 @@ Path BinaryCacheStore::narInfoFileFor(const Path & storePath)
 }
 
 void BinaryCacheStore::addToStore(const ValidPathInfo & info, const ref<std::string> & nar,
-    bool repair, bool dontCheckSigs, std::shared_ptr<FSAccessor> accessor)
+    RepairFlag repair, CheckSigsFlag checkSigs, std::shared_ptr<FSAccessor> accessor)
 {
     if (!repair && isValidPath(info.path)) return;
 
@@ -239,7 +239,7 @@ void BinaryCacheStore::addToStore(const ValidPathInfo & info, const ref<std::str
         % duration);
 
     /* Atomically write the NAR file. */
-    narInfo->url = "nar/" + printHash32(narInfo->fileHash) + ".nar"
+    narInfo->url = "nar/" + narInfo->fileHash.to_string(Base32, false) + ".nar"
         + (compression == "xz" ? ".xz" :
            compression == "bzip2" ? ".bz2" :
            compression == "br" ? ".br" :
@@ -328,7 +328,7 @@ void BinaryCacheStore::queryPathInfoUncached(const Path & storePath,
 }
 
 Path BinaryCacheStore::addToStore(const string & name, const Path & srcPath,
-    bool recursive, HashType hashAlgo, PathFilter & filter, bool repair)
+    bool recursive, HashType hashAlgo, PathFilter & filter, RepairFlag repair)
 {
     // FIXME: some cut&paste from LocalStore::addToStore().
 
@@ -349,13 +349,13 @@ Path BinaryCacheStore::addToStore(const string & name, const Path & srcPath,
     ValidPathInfo info;
     info.path = makeFixedOutputPath(recursive, h, name);
 
-    addToStore(info, sink.s, repair, false, 0);
+    addToStore(info, sink.s, repair, CheckSigs, nullptr);
 
     return info.path;
 }
 
 Path BinaryCacheStore::addTextToStore(const string & name, const string & s,
-    const PathSet & references, bool repair)
+    const PathSet & references, RepairFlag repair)
 {
     ValidPathInfo info;
     info.path = computeStorePathForText(name, s, references);
@@ -364,7 +364,7 @@ Path BinaryCacheStore::addTextToStore(const string & name, const string & s,
     if (repair || !isValidPath(info.path)) {
         StringSink sink;
         dumpString(s, sink);
-        addToStore(info, sink.s, repair, false, 0);
+        addToStore(info, sink.s, repair, CheckSigs, nullptr);
     }
 
     return info.path;
diff --git a/src/libstore/binary-cache-store.hh b/src/libstore/binary-cache-store.hh
index 87d4aa43838e..f9c1c2cbe8a8 100644
--- a/src/libstore/binary-cache-store.hh
+++ b/src/libstore/binary-cache-store.hh
@@ -85,15 +85,15 @@ public:
     bool wantMassQuery() override { return wantMassQuery_; }
 
     void addToStore(const ValidPathInfo & info, const ref<std::string> & nar,
-        bool repair, bool dontCheckSigs,
+        RepairFlag repair, CheckSigsFlag checkSigs,
         std::shared_ptr<FSAccessor> accessor) override;
 
     Path addToStore(const string & name, const Path & srcPath,
         bool recursive, HashType hashAlgo,
-        PathFilter & filter, bool repair) override;
+        PathFilter & filter, RepairFlag repair) override;
 
     Path addTextToStore(const string & name, const string & s,
-        const PathSet & references, bool repair) override;
+        const PathSet & references, RepairFlag repair) override;
 
     void narFromPath(const Path & path, Sink & sink) override;
 
@@ -123,6 +123,8 @@ public:
 
     std::shared_ptr<std::string> getBuildLog(const Path & path) override;
 
+    int getPriority() override { return priority; }
+
 };
 
 }
diff --git a/src/libstore/build.cc b/src/libstore/build.cc
index c34083d2e2fc..60b0a531f423 100644
--- a/src/libstore/build.cc
+++ b/src/libstore/build.cc
@@ -262,7 +262,7 @@ public:
     GoalPtr makeDerivationGoal(const Path & drvPath, const StringSet & wantedOutputs, BuildMode buildMode = bmNormal);
     std::shared_ptr<DerivationGoal> makeBasicDerivationGoal(const Path & drvPath,
         const BasicDerivation & drv, BuildMode buildMode = bmNormal);
-    GoalPtr makeSubstitutionGoal(const Path & storePath, bool repair = false);
+    GoalPtr makeSubstitutionGoal(const Path & storePath, RepairFlag repair = NoRepair);
 
     /* Remove a dead goal. */
     void removeGoal(GoalPtr goal);
@@ -1087,7 +1087,7 @@ void DerivationGoal::haveDerivation()
        them. */
     if (settings.useSubstitutes && drv->substitutesAllowed())
         for (auto & i : invalidOutputs)
-            addWaitee(worker.makeSubstitutionGoal(i, buildMode == bmRepair));
+            addWaitee(worker.makeSubstitutionGoal(i, buildMode == bmRepair ? Repair : NoRepair));
 
     if (waitees.empty()) /* to prevent hang (no wake-up event) */
         outputsSubstituted();
@@ -1195,7 +1195,7 @@ void DerivationGoal::repairClosure()
         printError(format("found corrupted or missing path ‘%1%’ in the output closure of ‘%2%’") % i % drvPath);
         Path drvPath2 = outputsToDrv[i];
         if (drvPath2 == "")
-            addWaitee(worker.makeSubstitutionGoal(i, true));
+            addWaitee(worker.makeSubstitutionGoal(i, Repair));
         else
             addWaitee(worker.makeDerivationGoal(drvPath2, PathSet(), bmRepair));
     }
@@ -2317,6 +2317,10 @@ void setupSeccomp()
         seccomp_arch_add(ctx, SCMP_ARCH_X86) != 0)
         throw SysError("unable to add 32-bit seccomp architecture");
 
+    if (settings.thisSystem == "x86_64-linux" &&
+        seccomp_arch_add(ctx, SCMP_ARCH_X32) != 0)
+        throw SysError("unable to add X32 seccomp architecture");
+
     /* Prevent builders from creating setuid/setgid binaries. */
     for (int perm : { S_ISUID, S_ISGID }) {
         if (seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(chmod), 1,
@@ -2340,6 +2344,9 @@ void setupSeccomp()
         seccomp_rule_add(ctx, SCMP_ACT_ERRNO(ENOTSUP), SCMP_SYS(fsetxattr), 0) != 0)
         throw SysError("unable to add seccomp rule");
 
+    if (seccomp_attr_set(ctx, SCMP_FLTATR_CTL_NNP, settings.allowNewPrivileges ? 0 : 1) != 0)
+        throw SysError("unable to set 'no new privileges' seccomp attribute");
+
     if (seccomp_load(ctx) != 0)
         throw SysError("unable to load seccomp BPF program");
 #endif
@@ -3236,7 +3243,7 @@ PathSet DerivationGoal::checkPathValidity(bool returnValid, bool checkHash)
 Path DerivationGoal::addHashRewrite(const Path & path)
 {
     string h1 = string(path, worker.store.storeDir.size() + 1, 32);
-    string h2 = string(printHash32(hashString(htSHA256, "rewrite:" + drvPath + ":" + path)), 0, 32);
+    string h2 = string(hashString(htSHA256, "rewrite:" + drvPath + ":" + path).to_string(Base32, false), 0, 32);
     Path p = worker.store.storeDir + "/" + h2 + string(path, worker.store.storeDir.size() + 33);
     deletePath(p);
     assert(path.size() == p.size());
@@ -3291,7 +3298,7 @@ private:
     std::promise<void> promise;
 
     /* Whether to try to repair a valid path. */
-    bool repair;
+    RepairFlag repair;
 
     /* Location where we're downloading the substitute.  Differs from
        storePath when doing a repair. */
@@ -3301,7 +3308,7 @@ private:
     GoalState state;
 
 public:
-    SubstitutionGoal(const Path & storePath, Worker & worker, bool repair = false);
+    SubstitutionGoal(const Path & storePath, Worker & worker, RepairFlag repair = NoRepair);
     ~SubstitutionGoal();
 
     void timedOut() override { abort(); };
@@ -3337,7 +3344,7 @@ public:
 };
 
 
-SubstitutionGoal::SubstitutionGoal(const Path & storePath, Worker & worker, bool repair)
+SubstitutionGoal::SubstitutionGoal(const Path & storePath, Worker & worker, RepairFlag repair)
     : Goal(worker)
     , hasSubstitute(false)
     , repair(repair)
@@ -3600,7 +3607,7 @@ std::shared_ptr<DerivationGoal> Worker::makeBasicDerivationGoal(const Path & drv
 }
 
 
-GoalPtr Worker::makeSubstitutionGoal(const Path & path, bool repair)
+GoalPtr Worker::makeSubstitutionGoal(const Path & path, RepairFlag repair)
 {
     GoalPtr goal = substitutionGoals[path].lock();
     if (!goal) {
@@ -3953,7 +3960,7 @@ void LocalStore::buildPaths(const PathSet & drvPaths, BuildMode buildMode)
         if (isDerivation(i2.first))
             goals.insert(worker.makeDerivationGoal(i2.first, i2.second, buildMode));
         else
-            goals.insert(worker.makeSubstitutionGoal(i, buildMode));
+            goals.insert(worker.makeSubstitutionGoal(i, buildMode == bmRepair ? Repair : NoRepair));
     }
 
     worker.run(goals);
@@ -4011,7 +4018,7 @@ void LocalStore::ensurePath(const Path & path)
 void LocalStore::repairPath(const Path & path)
 {
     Worker worker(*this);
-    GoalPtr goal = worker.makeSubstitutionGoal(path, true);
+    GoalPtr goal = worker.makeSubstitutionGoal(path, Repair);
     Goals goals = {goal};
 
     worker.run(goals);
diff --git a/src/libstore/derivations.cc b/src/libstore/derivations.cc
index 0c6ceb9f6741..48c0837ffaaa 100644
--- a/src/libstore/derivations.cc
+++ b/src/libstore/derivations.cc
@@ -23,7 +23,7 @@ void DerivationOutput::parseHashInfo(bool & recursive, Hash & hash) const
     if (hashType == htUnknown)
         throw Error(format("unknown hash algorithm ‘%1%’") % algo);
 
-    hash = parseHash(hashType, this->hash);
+    hash = Hash(this->hash, hashType);
 }
 
 
@@ -71,7 +71,7 @@ bool BasicDerivation::canBuildLocally() const
 
 
 Path writeDerivation(ref<Store> store,
-    const Derivation & drv, const string & name, bool repair)
+    const Derivation & drv, const string & name, RepairFlag repair)
 {
     PathSet references;
     references.insert(drv.inputSrcs.begin(), drv.inputSrcs.end());
@@ -354,7 +354,7 @@ Hash hashDerivationModulo(Store & store, Derivation drv)
             h = hashDerivationModulo(store, drv2);
             drvHashes[i.first] = h;
         }
-        inputs2[printHash(h)] = i.second;
+        inputs2[h.to_string(Base16, false)] = i.second;
     }
     drv.inputDrvs = inputs2;
 
@@ -437,7 +437,7 @@ Sink & operator << (Sink & out, const BasicDerivation & drv)
 std::string hashPlaceholder(const std::string & outputName)
 {
     // FIXME: memoize?
-    return "/" + printHash32(hashString(htSHA256, "nix-output:" + outputName));
+    return "/" + hashString(htSHA256, "nix-output:" + outputName).to_string(Base32, false);
 }
 
 
diff --git a/src/libstore/derivations.hh b/src/libstore/derivations.hh
index 9717a81e469c..7b97730d3bf2 100644
--- a/src/libstore/derivations.hh
+++ b/src/libstore/derivations.hh
@@ -2,6 +2,7 @@
 
 #include "types.hh"
 #include "hash.hh"
+#include "store-api.hh"
 
 #include <map>
 
@@ -85,7 +86,7 @@ class Store;
 
 /* Write a derivation to the Nix store, and return its path. */
 Path writeDerivation(ref<Store> store,
-    const Derivation & drv, const string & name, bool repair = false);
+    const Derivation & drv, const string & name, RepairFlag repair = NoRepair);
 
 /* Read a derivation from a file. */
 Derivation readDerivation(const Path & drvPath);
diff --git a/src/libstore/download.cc b/src/libstore/download.cc
index 33ab1f027829..15eb68c69ea4 100644
--- a/src/libstore/download.cc
+++ b/src/libstore/download.cc
@@ -581,7 +581,7 @@ Path Downloader::downloadCached(ref<Store> store, const string & url_, bool unpa
     Path cacheDir = getCacheDir() + "/nix/tarballs";
     createDirs(cacheDir);
 
-    string urlHash = printHash32(hashString(htSHA256, url));
+    string urlHash = hashString(htSHA256, url).to_string(Base32, false);
 
     Path dataFile = cacheDir + "/" + urlHash + ".info";
     Path fileLink = cacheDir + "/" + urlHash + "-file";
@@ -631,7 +631,7 @@ Path Downloader::downloadCached(ref<Store> store, const string & url_, bool unpa
                 info.narHash = hashString(htSHA256, *sink.s);
                 info.narSize = sink.s->size();
                 info.ca = makeFixedOutputCA(false, hash);
-                store->addToStore(info, sink.s, false, true);
+                store->addToStore(info, sink.s, NoRepair, NoCheckSigs);
                 storePath = info.path;
             }
 
@@ -660,7 +660,7 @@ Path Downloader::downloadCached(ref<Store> store, const string & url_, bool unpa
             AutoDelete autoDelete(tmpDir, true);
             // FIXME: this requires GNU tar for decompression.
             runProgram("tar", true, {"xf", storePath, "-C", tmpDir, "--strip-components", "1"});
-            unpackedStorePath = store->addToStore(name, tmpDir, true, htSHA256, defaultPathFilter, false);
+            unpackedStorePath = store->addToStore(name, tmpDir, true, htSHA256, defaultPathFilter, NoRepair);
         }
         replaceSymlink(unpackedStorePath, unpackedLink);
         storePath = unpackedStorePath;
diff --git a/src/libstore/export-import.cc b/src/libstore/export-import.cc
index 6e8bc692cdff..2cbcedc6fb00 100644
--- a/src/libstore/export-import.cc
+++ b/src/libstore/export-import.cc
@@ -56,12 +56,12 @@ void Store::exportPath(const Path & path, Sink & sink)
     Hash hash = hashAndWriteSink.currentHash();
     if (hash != info->narHash && info->narHash != Hash(info->narHash.type))
         throw Error(format("hash of path ‘%1%’ has changed from ‘%2%’ to ‘%3%’!") % path
-            % printHash(info->narHash) % printHash(hash));
+            % info->narHash.to_string() % hash.to_string());
 
     hashAndWriteSink << exportMagic << path << info->references << info->deriver << 0;
 }
 
-Paths Store::importPaths(Source & source, std::shared_ptr<FSAccessor> accessor, bool dontCheckSigs)
+Paths Store::importPaths(Source & source, std::shared_ptr<FSAccessor> accessor, CheckSigsFlag checkSigs)
 {
     Paths res;
     while (true) {
@@ -95,7 +95,7 @@ Paths Store::importPaths(Source & source, std::shared_ptr<FSAccessor> accessor,
         if (readInt(source) == 1)
             readString(source);
 
-        addToStore(info, tee.source.data, false, dontCheckSigs, accessor);
+        addToStore(info, tee.source.data, NoRepair, checkSigs, accessor);
 
         res.push_back(info.path);
     }
diff --git a/src/libstore/gc.cc b/src/libstore/gc.cc
index 3cdbb114a79d..0cf9f87cac32 100644
--- a/src/libstore/gc.cc
+++ b/src/libstore/gc.cc
@@ -76,7 +76,7 @@ void LocalStore::syncWithGC()
 
 void LocalStore::addIndirectRoot(const Path & path)
 {
-    string hash = printHash32(hashString(htSHA1, path));
+    string hash = hashString(htSHA1, path).to_string(Base32, false);
     Path realRoot = canonPath((format("%1%/%2%/auto/%3%")
         % stateDir % gcRootsDir % hash).str());
     makeSymlink(realRoot, path);
diff --git a/src/libstore/globals.hh b/src/libstore/globals.hh
index af37ec61d7a1..c8d67b07110b 100644
--- a/src/libstore/globals.hh
+++ b/src/libstore/globals.hh
@@ -321,6 +321,12 @@ public:
 
     Setting<std::string> userAgentSuffix{this, "", "user-agent-suffix",
         "String appended to the user agent in HTTP requests."};
+
+#if __linux__
+    Setting<bool> allowNewPrivileges{this, false, "allow-new-privileges",
+        "Whether builders can acquire new privileges by calling programs with "
+        "setuid/setgid bits or with file capabilities."};
+#endif
 };
 
 
diff --git a/src/libstore/legacy-ssh-store.cc b/src/libstore/legacy-ssh-store.cc
index e09932e3d182..a84f85c1b95a 100644
--- a/src/libstore/legacy-ssh-store.cc
+++ b/src/libstore/legacy-ssh-store.cc
@@ -113,7 +113,7 @@ struct LegacySSHStore : public Store
     }
 
     void addToStore(const ValidPathInfo & info, const ref<std::string> & nar,
-        bool repair, bool dontCheckSigs,
+        RepairFlag repair, CheckSigsFlag checkSigs,
         std::shared_ptr<FSAccessor> accessor) override
     {
         debug("adding path ‘%s’ to remote host ‘%s’", info.path, host);
@@ -168,11 +168,11 @@ struct LegacySSHStore : public Store
 
     Path addToStore(const string & name, const Path & srcPath,
         bool recursive, HashType hashAlgo,
-        PathFilter & filter, bool repair) override
+        PathFilter & filter, RepairFlag repair) override
     { unsupported(); }
 
     Path addTextToStore(const string & name, const string & s,
-        const PathSet & references, bool repair) override
+        const PathSet & references, RepairFlag repair) override
     { unsupported(); }
 
     BuildResult buildDerivation(const Path & drvPath, const BasicDerivation & drv,
@@ -249,7 +249,8 @@ struct LegacySSHStore : public Store
         out.insert(res.begin(), res.end());
     }
 
-    PathSet queryValidPaths(const PathSet & paths, bool maybeSubstitute = false) override
+    PathSet queryValidPaths(const PathSet & paths,
+        SubstituteFlag maybeSubstitute = NoSubstitute) override
     {
         auto conn(connections->get());
 
diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc
index 74c74e672e25..7c41dfca7f31 100644
--- a/src/libstore/local-store.cc
+++ b/src/libstore/local-store.cc
@@ -30,6 +30,10 @@
 #include <sys/xattr.h>
 #endif
 
+#ifdef __CYGWIN__
+#include <windows.h>
+#endif
+
 #include <sqlite3.h>
 
 
@@ -281,6 +285,16 @@ void LocalStore::openDB(State & state, bool create)
             SQLITE_OPEN_READWRITE | (create ? SQLITE_OPEN_CREATE : 0), 0) != SQLITE_OK)
         throw Error(format("cannot open Nix database ‘%1%’") % dbPath);
 
+#ifdef __CYGWIN__
+    /* The cygwin version of sqlite3 has a patch which calls
+       SetDllDirectory("/usr/bin") on init. It was intended to fix extension
+       loading, which we don't use, and the effect of SetDllDirectory is
+       inherited by child processes, and causes libraries to be loaded from
+       /usr/bin instead of $PATH. This breaks quite a few things (e.g.
+       checkPhase on openssh), so we set it back to default behaviour. */
+    SetDllDirectoryW(L"");
+#endif
+
     if (sqlite3_busy_timeout(db, 60 * 60 * 1000) != SQLITE_OK)
         throwSQLiteError(db, "setting timeout");
 
@@ -558,7 +572,7 @@ uint64_t LocalStore::addValidPath(State & state,
 
     state.stmtRegisterValidPath.use()
         (info.path)
-        ("sha256:" + printHash(info.narHash))
+        (info.narHash.to_string(Base16))
         (info.registrationTime == 0 ? time(0) : info.registrationTime)
         (info.deriver, info.deriver != "")
         (info.narSize, info.narSize != 0)
@@ -600,20 +614,6 @@ uint64_t LocalStore::addValidPath(State & state,
 }
 
 
-Hash parseHashField(const Path & path, const string & s)
-{
-    string::size_type colon = s.find(':');
-    if (colon == string::npos)
-        throw Error(format("corrupt hash ‘%1%’ in valid-path entry for ‘%2%’")
-            % s % path);
-    HashType ht = parseHashType(string(s, 0, colon));
-    if (ht == htUnknown)
-        throw Error(format("unknown hash type ‘%1%’ in valid-path entry for ‘%2%’")
-            % string(s, 0, colon) % path);
-    return parseHash(ht, string(s, colon + 1));
-}
-
-
 void LocalStore::queryPathInfoUncached(const Path & path,
     std::function<void(std::shared_ptr<ValidPathInfo>)> success,
     std::function<void(std::exception_ptr exc)> failure)
@@ -636,7 +636,11 @@ void LocalStore::queryPathInfoUncached(const Path & path,
 
             info->id = useQueryPathInfo.getInt(0);
 
-            info->narHash = parseHashField(path, useQueryPathInfo.getStr(1));
+            try {
+                info->narHash = Hash(useQueryPathInfo.getStr(1));
+            } catch (BadHash & e) {
+                throw Error("in valid-path entry for ‘%s’: %s", path, e.what());
+            }
 
             info->registrationTime = useQueryPathInfo.getInt(2);
 
@@ -671,7 +675,7 @@ void LocalStore::updatePathInfo(State & state, const ValidPathInfo & info)
 {
     state.stmtUpdatePathInfo.use()
         (info.narSize, info.narSize != 0)
-        ("sha256:" + printHash(info.narHash))
+        (info.narHash.to_string(Base16))
         (info.ultimate ? 1 : 0, info.ultimate)
         (concatStringsSep(" ", info.sigs), !info.sigs.empty())
         (info.ca, !info.ca.empty())
@@ -704,7 +708,7 @@ bool LocalStore::isValidPathUncached(const Path & path)
 }
 
 
-PathSet LocalStore::queryValidPaths(const PathSet & paths, bool maybeSubstitute)
+PathSet LocalStore::queryValidPaths(const PathSet & paths, SubstituteFlag maybeSubstitute)
 {
     PathSet res;
     for (auto & i : paths)
@@ -947,7 +951,7 @@ void LocalStore::invalidatePath(State & state, const Path & path)
 
 
 void LocalStore::addToStore(const ValidPathInfo & info, const ref<std::string> & nar,
-    bool repair, bool dontCheckSigs, std::shared_ptr<FSAccessor> accessor)
+    RepairFlag repair, CheckSigsFlag checkSigs, std::shared_ptr<FSAccessor> accessor)
 {
     assert(info.narHash);
 
@@ -960,7 +964,7 @@ void LocalStore::addToStore(const ValidPathInfo & info, const ref<std::string> &
         throw Error("size mismatch importing path ‘%s’; expected %s, got %s",
             info.path, info.narSize, nar->size());
 
-    if (requireSigs && !dontCheckSigs && !info.checkSignatures(*this, publicKeys))
+    if (requireSigs && checkSigs && !info.checkSignatures(*this, publicKeys))
         throw Error("cannot add path ‘%s’ because it lacks a valid signature", info.path);
 
     addTempRoot(info.path);
@@ -998,7 +1002,7 @@ void LocalStore::addToStore(const ValidPathInfo & info, const ref<std::string> &
 
 
 Path LocalStore::addToStoreFromDump(const string & dump, const string & name,
-    bool recursive, HashType hashAlgo, bool repair)
+    bool recursive, HashType hashAlgo, RepairFlag repair)
 {
     Hash h = hashString(hashAlgo, dump);
 
@@ -1056,7 +1060,7 @@ Path LocalStore::addToStoreFromDump(const string & dump, const string & name,
 
 
 Path LocalStore::addToStore(const string & name, const Path & _srcPath,
-    bool recursive, HashType hashAlgo, PathFilter & filter, bool repair)
+    bool recursive, HashType hashAlgo, PathFilter & filter, RepairFlag repair)
 {
     Path srcPath(absPath(_srcPath));
 
@@ -1074,7 +1078,7 @@ Path LocalStore::addToStore(const string & name, const Path & _srcPath,
 
 
 Path LocalStore::addTextToStore(const string & name, const string & s,
-    const PathSet & references, bool repair)
+    const PathSet & references, RepairFlag repair)
 {
     auto hash = hashString(htSHA256, s);
     auto dstPath = makeTextPath(name, hash, references);
@@ -1156,7 +1160,7 @@ void LocalStore::invalidatePathChecked(const Path & path)
 }
 
 
-bool LocalStore::verifyStore(bool checkContents, bool repair)
+bool LocalStore::verifyStore(bool checkContents, RepairFlag repair)
 {
     printError(format("reading the Nix store..."));
 
@@ -1197,7 +1201,7 @@ bool LocalStore::verifyStore(bool checkContents, bool repair)
                 if (info->narHash != nullHash && info->narHash != current.first) {
                     printError(format("path ‘%1%’ was modified! "
                             "expected hash ‘%2%’, got ‘%3%’")
-                        % i % printHash(info->narHash) % printHash(current.first));
+                        % i % info->narHash.to_string() % current.first.to_string());
                     if (repair) repairPath(i); else errors = true;
                 } else {
 
@@ -1241,7 +1245,7 @@ bool LocalStore::verifyStore(bool checkContents, bool repair)
 
 
 void LocalStore::verifyPath(const Path & path, const PathSet & store,
-    PathSet & done, PathSet & validPaths, bool repair, bool & errors)
+    PathSet & done, PathSet & validPaths, RepairFlag repair, bool & errors)
 {
     checkInterrupt();
 
diff --git a/src/libstore/local-store.hh b/src/libstore/local-store.hh
index f2c40e96464b..551c6b506fb1 100644
--- a/src/libstore/local-store.hh
+++ b/src/libstore/local-store.hh
@@ -98,7 +98,8 @@ public:
 
     bool isValidPathUncached(const Path & path) override;
 
-    PathSet queryValidPaths(const PathSet & paths, bool maybeSubstitute = false) override;
+    PathSet queryValidPaths(const PathSet & paths,
+        SubstituteFlag maybeSubstitute = NoSubstitute) override;
 
     PathSet queryAllValidPaths() override;
 
@@ -122,22 +123,22 @@ public:
         SubstitutablePathInfos & infos) override;
 
     void addToStore(const ValidPathInfo & info, const ref<std::string> & nar,
-        bool repair, bool dontCheckSigs,
+        RepairFlag repair, CheckSigsFlag checkSigs,
         std::shared_ptr<FSAccessor> accessor) override;
 
     Path addToStore(const string & name, const Path & srcPath,
         bool recursive, HashType hashAlgo,
-        PathFilter & filter, bool repair) override;
+        PathFilter & filter, RepairFlag repair) override;
 
     /* Like addToStore(), but the contents of the path are contained
        in `dump', which is either a NAR serialisation (if recursive ==
        true) or simply the contents of a regular file (if recursive ==
        false). */
     Path addToStoreFromDump(const string & dump, const string & name,
-        bool recursive = true, HashType hashAlgo = htSHA256, bool repair = false);
+        bool recursive = true, HashType hashAlgo = htSHA256, RepairFlag repair = NoRepair);
 
     Path addTextToStore(const string & name, const string & s,
-        const PathSet & references, bool repair) override;
+        const PathSet & references, RepairFlag repair) override;
 
     void buildPaths(const PathSet & paths, BuildMode buildMode) override;
 
@@ -174,7 +175,7 @@ public:
     /* Optimise a single store path. */
     void optimisePath(const Path & path);
 
-    bool verifyStore(bool checkContents, bool repair) override;
+    bool verifyStore(bool checkContents, RepairFlag repair) override;
 
     /* Register the validity of a path, i.e., that `path' exists, that
        the paths referenced by it exists, and in the case of an output
@@ -212,7 +213,7 @@ private:
     void invalidatePathChecked(const Path & path);
 
     void verifyPath(const Path & path, const PathSet & store,
-        PathSet & done, PathSet & validPaths, bool repair, bool & errors);
+        PathSet & done, PathSet & validPaths, RepairFlag repair, bool & errors);
 
     void updatePathInfo(State & state, const ValidPathInfo & info);
 
diff --git a/src/libstore/nar-info-disk-cache.cc b/src/libstore/nar-info-disk-cache.cc
index 180a936edb85..6e155e877803 100644
--- a/src/libstore/nar-info-disk-cache.cc
+++ b/src/libstore/nar-info-disk-cache.cc
@@ -203,9 +203,9 @@ public:
             narInfo->url = queryNAR.getStr(3);
             narInfo->compression = queryNAR.getStr(4);
             if (!queryNAR.isNull(5))
-                narInfo->fileHash = parseHash(queryNAR.getStr(5));
+                narInfo->fileHash = Hash(queryNAR.getStr(5));
             narInfo->fileSize = queryNAR.getInt(6);
-            narInfo->narHash = parseHash(queryNAR.getStr(7));
+            narInfo->narHash = Hash(queryNAR.getStr(7));
             narInfo->narSize = queryNAR.getInt(8);
             for (auto & r : tokenizeString<Strings>(queryNAR.getStr(9), " "))
                 narInfo->references.insert(cache.storeDir + "/" + r);
diff --git a/src/libstore/nar-info.cc b/src/libstore/nar-info.cc
index d1042c6de25e..660f6a42a19d 100644
--- a/src/libstore/nar-info.cc
+++ b/src/libstore/nar-info.cc
@@ -11,7 +11,7 @@ NarInfo::NarInfo(const Store & store, const std::string & s, const std::string &
 
     auto parseHashField = [&](const string & s) {
         try {
-            return parseHash(s);
+            return Hash(s);
         } catch (BadHash &) {
             corrupt();
             return Hash(); // never reached
@@ -90,10 +90,10 @@ std::string NarInfo::to_string() const
     assert(compression != "");
     res += "Compression: " + compression + "\n";
     assert(fileHash.type == htSHA256);
-    res += "FileHash: sha256:" + printHash32(fileHash) + "\n";
+    res += "FileHash: " + fileHash.to_string(Base32) + "\n";
     res += "FileSize: " + std::to_string(fileSize) + "\n";
     assert(narHash.type == htSHA256);
-    res += "NarHash: sha256:" + printHash32(narHash) + "\n";
+    res += "NarHash: " + narHash.to_string(Base32) + "\n";
     res += "NarSize: " + std::to_string(narSize) + "\n";
 
     res += "References: " + concatStringsSep(" ", shortRefs()) + "\n";
diff --git a/src/libstore/optimise-store.cc b/src/libstore/optimise-store.cc
index 56167c4dfae8..8e8002a30db5 100644
--- a/src/libstore/optimise-store.cc
+++ b/src/libstore/optimise-store.cc
@@ -9,6 +9,7 @@
 #include <unistd.h>
 #include <errno.h>
 #include <stdio.h>
+#include <regex>
 
 
 namespace nix {
@@ -96,6 +97,19 @@ void LocalStore::optimisePath_(OptimiseStats & stats, const Path & path, InodeHa
     if (lstat(path.c_str(), &st))
         throw SysError(format("getting attributes of path ‘%1%’") % path);
 
+#if __APPLE__
+    /* HFS/OS X has some undocumented security feature disabling hardlinking for
+       special files within .app dirs. *.app/Contents/PkgInfo and
+       *.app/Contents/Resources/\*.lproj seem to be the only paths affected. See
+       https://github.com/NixOS/nix/issues/1443 for more discussion. */
+
+    if (std::regex_search(path, std::regex("\\.app/Contents/PkgInfo$")) ||
+        std::regex_search(path, std::regex("\\.app/Contents/Resources/.+\\.lproj$"))) {
+        debug(format("‘%1%’ is not allowed to be linked in OS X") % path);
+        return;
+    }
+#endif
+
     if (S_ISDIR(st.st_mode)) {
         Strings names = readDirectoryIgnoringInodes(path, inodeHash);
         for (auto & i : names)
@@ -135,10 +149,10 @@ void LocalStore::optimisePath_(OptimiseStats & stats, const Path & path, InodeHa
        contents of the symlink (i.e. the result of readlink()), not
        the contents of the target (which may not even exist). */
     Hash hash = hashPath(htSHA256, path).first;
-    debug(format("‘%1%’ has hash ‘%2%’") % path % printHash(hash));
+    debug(format("‘%1%’ has hash ‘%2%’") % path % hash.to_string());
 
     /* Check if this is a known hash. */
-    Path linkPath = linksDir + "/" + printHash32(hash);
+    Path linkPath = linksDir + "/" + hash.to_string(Base32, false);
 
  retry:
     if (!pathExists(linkPath)) {
diff --git a/src/libstore/remote-store.cc b/src/libstore/remote-store.cc
index be8819bbc004..ab726e79534a 100644
--- a/src/libstore/remote-store.cc
+++ b/src/libstore/remote-store.cc
@@ -185,7 +185,7 @@ bool RemoteStore::isValidPathUncached(const Path & path)
 }
 
 
-PathSet RemoteStore::queryValidPaths(const PathSet & paths, bool maybeSubstitute)
+PathSet RemoteStore::queryValidPaths(const PathSet & paths, SubstituteFlag maybeSubstitute)
 {
     auto conn(connections->get());
     if (GET_PROTOCOL_MINOR(conn->daemonVersion) < 12) {
@@ -294,7 +294,7 @@ void RemoteStore::queryPathInfoUncached(const Path & path,
         info->path = path;
         info->deriver = readString(conn->from);
         if (info->deriver != "") assertStorePath(info->deriver);
-        info->narHash = parseHash(htSHA256, readString(conn->from));
+        info->narHash = Hash(readString(conn->from), htSHA256);
         info->references = readStorePaths<PathSet>(*this, conn->from);
         conn->from >> info->registrationTime >> info->narSize;
         if (GET_PROTOCOL_MINOR(conn->daemonVersion) >= 16) {
@@ -357,7 +357,7 @@ Path RemoteStore::queryPathFromHashPart(const string & hashPart)
 
 
 void RemoteStore::addToStore(const ValidPathInfo & info, const ref<std::string> & nar,
-    bool repair, bool dontCheckSigs, std::shared_ptr<FSAccessor> accessor)
+    RepairFlag repair, CheckSigsFlag checkSigs, std::shared_ptr<FSAccessor> accessor)
 {
     auto conn(connections->get());
 
@@ -387,10 +387,10 @@ void RemoteStore::addToStore(const ValidPathInfo & info, const ref<std::string>
 
     else {
         conn->to << wopAddToStoreNar
-                 << info.path << info.deriver << printHash(info.narHash)
+                 << info.path << info.deriver << info.narHash.to_string(Base16, false)
                  << info.references << info.registrationTime << info.narSize
                  << info.ultimate << info.sigs << info.ca
-                 << repair << dontCheckSigs;
+                 << repair << !checkSigs;
         conn->to(*nar);
         conn->processStderr();
     }
@@ -398,7 +398,7 @@ void RemoteStore::addToStore(const ValidPathInfo & info, const ref<std::string>
 
 
 Path RemoteStore::addToStore(const string & name, const Path & _srcPath,
-    bool recursive, HashType hashAlgo, PathFilter & filter, bool repair)
+    bool recursive, HashType hashAlgo, PathFilter & filter, RepairFlag repair)
 {
     if (repair) throw Error("repairing is not supported when building through the Nix daemon");
 
@@ -434,7 +434,7 @@ Path RemoteStore::addToStore(const string & name, const Path & _srcPath,
 
 
 Path RemoteStore::addTextToStore(const string & name, const string & s,
-    const PathSet & references, bool repair)
+    const PathSet & references, RepairFlag repair)
 {
     if (repair) throw Error("repairing is not supported when building through the Nix daemon");
 
@@ -570,7 +570,7 @@ void RemoteStore::optimiseStore()
 }
 
 
-bool RemoteStore::verifyStore(bool checkContents, bool repair)
+bool RemoteStore::verifyStore(bool checkContents, RepairFlag repair)
 {
     auto conn(connections->get());
     conn->to << wopVerifyStore << checkContents << repair;
diff --git a/src/libstore/remote-store.hh b/src/libstore/remote-store.hh
index ed430e4cabb6..e370e4797d24 100644
--- a/src/libstore/remote-store.hh
+++ b/src/libstore/remote-store.hh
@@ -31,7 +31,8 @@ public:
 
     bool isValidPathUncached(const Path & path) override;
 
-    PathSet queryValidPaths(const PathSet & paths, bool maybeSubstitute = false) override;
+    PathSet queryValidPaths(const PathSet & paths,
+        SubstituteFlag maybeSubstitute = NoSubstitute) override;
 
     PathSet queryAllValidPaths() override;
 
@@ -55,15 +56,15 @@ public:
         SubstitutablePathInfos & infos) override;
 
     void addToStore(const ValidPathInfo & info, const ref<std::string> & nar,
-        bool repair, bool dontCheckSigs,
+        RepairFlag repair, CheckSigsFlag checkSigs,
         std::shared_ptr<FSAccessor> accessor) override;
 
     Path addToStore(const string & name, const Path & srcPath,
         bool recursive = true, HashType hashAlgo = htSHA256,
-        PathFilter & filter = defaultPathFilter, bool repair = false) override;
+        PathFilter & filter = defaultPathFilter, RepairFlag repair = NoRepair) override;
 
     Path addTextToStore(const string & name, const string & s,
-        const PathSet & references, bool repair = false) override;
+        const PathSet & references, RepairFlag repair) override;
 
     void buildPaths(const PathSet & paths, BuildMode buildMode) override;
 
@@ -84,7 +85,7 @@ public:
 
     void optimiseStore() override;
 
-    bool verifyStore(bool checkContents, bool repair) override;
+    bool verifyStore(bool checkContents, RepairFlag repair) override;
 
     void addSignatures(const Path & storePath, const StringSet & sigs) override;
 
diff --git a/src/libstore/store-api.cc b/src/libstore/store-api.cc
index 76ed9942256b..108e2d4ce9b0 100644
--- a/src/libstore/store-api.cc
+++ b/src/libstore/store-api.cc
@@ -176,13 +176,12 @@ Path Store::makeStorePath(const string & type,
     const Hash & hash, const string & name) const
 {
     /* e.g., "source:sha256:1abc...:/nix/store:foo.tar.gz" */
-    string s = type + ":sha256:" + printHash(hash) + ":"
-        + storeDir + ":" + name;
+    string s = type + ":" + hash.to_string(Base16) + ":" + storeDir + ":" + name;
 
     checkStoreName(name);
 
     return storeDir + "/"
-        + printHash32(compressHash(hashString(htSHA256, s), 20))
+        + compressHash(hashString(htSHA256, s), 20).to_string(Base32, false)
         + "-" + name;
 }
 
@@ -202,7 +201,7 @@ Path Store::makeFixedOutputPath(bool recursive,
         ? makeStorePath("source", hash, name)
         : makeStorePath("output:out", hashString(htSHA256,
                 "fixed:out:" + (recursive ? (string) "r:" : "") +
-                printHashType(hash.type) + ":" + printHash(hash) + ":"),
+                hash.to_string(Base16) + ":"),
             name);
 }
 
@@ -378,7 +377,7 @@ void Store::queryPathInfo(const Path & storePath,
 }
 
 
-PathSet Store::queryValidPaths(const PathSet & paths, bool maybeSubstitute)
+PathSet Store::queryValidPaths(const PathSet & paths, SubstituteFlag maybeSubstitute)
 {
     struct State
     {
@@ -438,7 +437,7 @@ string Store::makeValidityRegistration(const PathSet & paths,
         auto info = queryPathInfo(i);
 
         if (showHash) {
-            s += printHash(info->narHash) + "\n";
+            s += info->narHash.to_string(Base16, false) + "\n";
             s += (format("%1%\n") % info->narSize).str();
         }
 
@@ -537,14 +536,14 @@ void Store::buildPaths(const PathSet & paths, BuildMode buildMode)
 
 
 void copyStorePath(ref<Store> srcStore, ref<Store> dstStore,
-    const Path & storePath, bool repair, bool dontCheckSigs)
+    const Path & storePath, RepairFlag repair, CheckSigsFlag checkSigs)
 {
     auto info = srcStore->queryPathInfo(storePath);
 
     StringSink sink;
     srcStore->narFromPath({storePath}, sink);
 
-    if (!info->narHash && dontCheckSigs) {
+    if (!info->narHash && !checkSigs) {
         auto info2 = make_ref<ValidPathInfo>(*info);
         info2->narHash = hashString(htSHA256, *sink.s);
         if (!info->narSize) info2->narSize = sink.s->size();
@@ -561,33 +560,47 @@ void copyStorePath(ref<Store> srcStore, ref<Store> dstStore,
 
     assert(info->narHash);
 
-    dstStore->addToStore(*info, sink.s, repair, dontCheckSigs);
+    dstStore->addToStore(*info, sink.s, repair, checkSigs);
 }
 
 
-void copyClosure(ref<Store> srcStore, ref<Store> dstStore,
-    const PathSet & storePaths, bool repair, bool dontCheckSigs)
+void copyPaths(ref<Store> srcStore, ref<Store> dstStore, const PathSet & storePaths,
+    RepairFlag repair, CheckSigsFlag checkSigs, SubstituteFlag substitute)
 {
-    PathSet closure;
+    PathSet valid = dstStore->queryValidPaths(storePaths, substitute);
+
+    PathSet missing;
     for (auto & path : storePaths)
-        srcStore->computeFSClosure(path, closure);
+        if (!valid.count(path)) missing.insert(path);
 
-    // FIXME: use copyStorePaths()
+    ThreadPool pool;
 
-    PathSet valid = dstStore->queryValidPaths(closure);
+    processGraph<Path>(pool,
+        PathSet(missing.begin(), missing.end()),
 
-    if (valid.size() == closure.size()) return;
+        [&](const Path & storePath) {
+            if (dstStore->isValidPath(storePath)) return PathSet();
+            return srcStore->queryPathInfo(storePath)->references;
+        },
 
-    Paths sorted = srcStore->topoSortPaths(closure);
+        [&](const Path & storePath) {
+            checkInterrupt();
 
-    Paths missing;
-    for (auto i = sorted.rbegin(); i != sorted.rend(); ++i)
-        if (!valid.count(*i)) missing.push_back(*i);
+            if (!dstStore->isValidPath(storePath)) {
+                printError("copying ‘%s’...", storePath);
+                copyStorePath(srcStore, dstStore, storePath, repair, checkSigs);
+            }
+        });
+}
 
-    printMsg(lvlDebug, format("copying %1% missing paths") % missing.size());
 
-    for (auto & i : missing)
-        copyStorePath(srcStore, dstStore, i, repair, dontCheckSigs);
+void copyClosure(ref<Store> srcStore, ref<Store> dstStore,
+    const PathSet & storePaths, RepairFlag repair, CheckSigsFlag checkSigs,
+    SubstituteFlag substitute)
+{
+    PathSet closure;
+    srcStore->computeFSClosure({storePaths}, closure);
+    copyPaths(srcStore, dstStore, closure, repair, checkSigs, substitute);
 }
 
 
@@ -599,7 +612,7 @@ ValidPathInfo decodeValidPathInfo(std::istream & str, bool hashGiven)
     if (hashGiven) {
         string s;
         getline(str, s);
-        info.narHash = parseHash(htSHA256, s);
+        info.narHash = Hash(s, htSHA256);
         getline(str, s);
         if (!string2Int(s, info.narSize)) throw Error("number expected");
     }
@@ -634,7 +647,7 @@ std::string ValidPathInfo::fingerprint() const
             % path);
     return
         "1;" + path + ";"
-        + printHashType(narHash.type) + ":" + printHash32(narHash) + ";"
+        + narHash.to_string(Base32) + ";"
         + std::to_string(narSize) + ";"
         + concatStringsSep(",", references);
 }
@@ -653,7 +666,7 @@ bool ValidPathInfo::isContentAddressed(const Store & store) const
     };
 
     if (hasPrefix(ca, "text:")) {
-        auto hash = parseHash(std::string(ca, 5));
+        Hash hash(std::string(ca, 5));
         if (store.makeTextPath(storePathToName(path), hash, references) == path)
             return true;
         else
@@ -662,7 +675,7 @@ bool ValidPathInfo::isContentAddressed(const Store & store) const
 
     else if (hasPrefix(ca, "fixed:")) {
         bool recursive = ca.compare(6, 2, "r:") == 0;
-        auto hash = parseHash(std::string(ca, recursive ? 8 : 6));
+        Hash hash(std::string(ca, recursive ? 8 : 6));
         if (store.makeFixedOutputPath(recursive, hash, storePathToName(path)) == path)
             return true;
         else
@@ -782,74 +795,31 @@ static RegisterStoreImplementation regStore([](
 
 std::list<ref<Store>> getDefaultSubstituters()
 {
-    struct State {
-        bool done = false;
+    static auto stores([]() {
         std::list<ref<Store>> stores;
-    };
-    static Sync<State> state_;
-
-    auto state(state_.lock());
-
-    if (state->done) return state->stores;
-
-    StringSet done;
-
-    auto addStore = [&](const std::string & uri) {
-        if (done.count(uri)) return;
-        done.insert(uri);
-        state->stores.push_back(openStore(uri));
-    };
 
-    for (auto uri : settings.substituters.get())
-        addStore(uri);
+        StringSet done;
 
-    for (auto uri : settings.extraSubstituters.get())
-        addStore(uri);
+        auto addStore = [&](const std::string & uri) {
+            if (done.count(uri)) return;
+            done.insert(uri);
+            stores.push_back(openStore(uri));
+        };
 
-    state->done = true;
+        for (auto uri : settings.substituters.get())
+            addStore(uri);
 
-    return state->stores;
-}
-
-
-void copyPaths(ref<Store> from, ref<Store> to, const PathSet & storePaths,
-    bool substitute, bool dontCheckSigs)
-{
-    PathSet valid = to->queryValidPaths(storePaths, substitute);
-
-    PathSet missing;
-    for (auto & path : storePaths)
-        if (!valid.count(path)) missing.insert(path);
-
-    std::string copiedLabel = "copied";
-
-    //logger->setExpected(copiedLabel, missing.size());
-
-    ThreadPool pool;
+        for (auto uri : settings.extraSubstituters.get())
+            addStore(uri);
 
-    processGraph<Path>(pool,
-        PathSet(missing.begin(), missing.end()),
-
-        [&](const Path & storePath) {
-            if (to->isValidPath(storePath)) return PathSet();
-            return from->queryPathInfo(storePath)->references;
-        },
-
-        [&](const Path & storePath) {
-            checkInterrupt();
-
-            if (!to->isValidPath(storePath)) {
-                //Activity act(*logger, lvlInfo, format("copying ‘%s’...") % storePath);
-
-                copyStorePath(from, to, storePath, false, dontCheckSigs);
-
-                //logger->incProgress(copiedLabel);
-            } else
-                ;
-                //logger->incExpected(copiedLabel, -1);
+        stores.sort([](ref<Store> & a, ref<Store> & b) {
+            return a->getPriority() < b->getPriority();
         });
 
-    pool.process();
+        return stores;
+    } ());
+
+    return stores;
 }
 
 
diff --git a/src/libstore/store-api.hh b/src/libstore/store-api.hh
index 929c95a0f2f8..cada37653e6f 100644
--- a/src/libstore/store-api.hh
+++ b/src/libstore/store-api.hh
@@ -32,6 +32,11 @@ class Store;
 class JSONPlaceholder;
 
 
+enum RepairFlag : bool { NoRepair = false, Repair = true };
+enum CheckSigsFlag : bool { NoCheckSigs = false, CheckSigs = true };
+enum SubstituteFlag : bool { NoSubstitute = false, Substitute = true };
+
+
 /* Size of the hash part of store paths, in base-32 characters. */
 const size_t storePathHashLen = 32; // i.e. 160 bits
 
@@ -332,7 +337,7 @@ public:
     /* Query which of the given paths is valid. Optionally, try to
        substitute missing paths. */
     virtual PathSet queryValidPaths(const PathSet & paths,
-        bool maybeSubstitute = false);
+        SubstituteFlag maybeSubstitute = NoSubstitute);
 
     /* Query the set of all valid paths. Note that for some store
        backends, the name part of store paths may be omitted
@@ -392,7 +397,7 @@ public:
 
     /* Import a path into the store. */
     virtual void addToStore(const ValidPathInfo & info, const ref<std::string> & nar,
-        bool repair = false, bool dontCheckSigs = false,
+        RepairFlag repair = NoRepair, CheckSigsFlag checkSigs = CheckSigs,
         std::shared_ptr<FSAccessor> accessor = 0) = 0;
 
     /* Copy the contents of a path to the store and register the
@@ -401,12 +406,12 @@ public:
        libutil/archive.hh). */
     virtual Path addToStore(const string & name, const Path & srcPath,
         bool recursive = true, HashType hashAlgo = htSHA256,
-        PathFilter & filter = defaultPathFilter, bool repair = false) = 0;
+        PathFilter & filter = defaultPathFilter, RepairFlag repair = NoRepair) = 0;
 
     /* Like addToStore, but the contents written to the output path is
        a regular file containing the given string. */
     virtual Path addTextToStore(const string & name, const string & s,
-        const PathSet & references, bool repair = false) = 0;
+        const PathSet & references, RepairFlag repair = NoRepair) = 0;
 
     /* Write a NAR dump of a store path. */
     virtual void narFromPath(const Path & path, Sink & sink) = 0;
@@ -496,7 +501,7 @@ public:
 
     /* Check the integrity of the Nix store.  Returns true if errors
        remain. */
-    virtual bool verifyStore(bool checkContents, bool repair) { return false; };
+    virtual bool verifyStore(bool checkContents, RepairFlag repair = NoRepair) { return false; };
 
     /* Return an object to access files in the Nix store. */
     virtual ref<FSAccessor> getFSAccessor() = 0;
@@ -548,7 +553,7 @@ public:
        preloaded into the specified FS accessor to speed up subsequent
        access. */
     Paths importPaths(Source & source, std::shared_ptr<FSAccessor> accessor,
-        bool dontCheckSigs = false);
+        CheckSigsFlag checkSigs = CheckSigs);
 
     struct Stats
     {
@@ -585,6 +590,11 @@ public:
        a notion of connection. Otherwise this is a no-op. */
     virtual void connect() { };
 
+    /* Get the priority of the store, used to order substituters. In
+       particular, binary caches can specify a priority field in their
+       "nix-cache-info" file. Lower value means higher priority. */
+    virtual int getPriority() { return 0; }
+
 protected:
 
     Stats stats;
@@ -650,12 +660,26 @@ void checkStoreName(const string & name);
 
 /* Copy a path from one store to another. */
 void copyStorePath(ref<Store> srcStore, ref<Store> dstStore,
-    const Path & storePath, bool repair = false, bool dontCheckSigs = false);
+    const Path & storePath, RepairFlag repair = NoRepair, CheckSigsFlag checkSigs = CheckSigs);
+
+
+/* Copy store paths from one store to another. The paths may be copied
+   in parallel. They are copied in a topologically sorted order
+   (i.e. if A is a reference of B, then A is copied before B), but
+   the set of store paths is not automatically closed; use
+   copyClosure() for that. */
+void copyPaths(ref<Store> srcStore, ref<Store> dstStore, const PathSet & storePaths,
+    RepairFlag repair = NoRepair,
+    CheckSigsFlag checkSigs = CheckSigs,
+    SubstituteFlag substitute = NoSubstitute);
 
 
 /* Copy the closure of the specified paths from one store to another. */
 void copyClosure(ref<Store> srcStore, ref<Store> dstStore,
-    const PathSet & storePaths, bool repair = false, bool dontCheckSigs = false);
+    const PathSet & storePaths,
+    RepairFlag repair = NoRepair,
+    CheckSigsFlag checkSigs = CheckSigs,
+    SubstituteFlag substitute = NoSubstitute);
 
 
 /* Remove the temporary roots file for this process.  Any temporary
@@ -694,9 +718,6 @@ ref<Store> openStore(const std::string & uri = getEnv("NIX_REMOTE"),
     const Store::Params & extraParams = Store::Params());
 
 
-void copyPaths(ref<Store> from, ref<Store> to, const PathSet & storePaths,
-    bool substitute = false, bool dontCheckSigs = false);
-
 enum StoreType {
     tDaemon,
     tLocal,