From 2c7557481b4c9d5113a65cc7a75c8acc18031f4e Mon Sep 17 00:00:00 2001
From: John Ericson <John.Ericson@Obsidian.Systems>
Date: Fri, 24 Jul 2020 21:02:51 +0000
Subject: [PATCH 01/18] `queryDerivationOutputMap` no longer assumes all
 outputs have a mapping

This assumption is broken by CA derivations. Making a PR now to do the
breaking daemon change as soon as possible (if it is already too late,
we can bump protocol intead).
---
 src/libstore/build.cc           | 10 +++++--
 src/libstore/daemon.cc          |  4 +--
 src/libstore/local-store.cc     | 12 +++++---
 src/libstore/local-store.hh     |  2 +-
 src/libstore/remote-store.cc    | 31 +++++++-------------
 src/libstore/remote-store.hh    |  2 +-
 src/libstore/store-api.cc       | 13 ++++++++-
 src/libstore/store-api.hh       | 10 +++++--
 src/libstore/worker-protocol.hh | 52 ++++++++++++++++++++++++++++++++-
 src/nix-env/nix-env.cc          |  2 +-
 10 files changed, 102 insertions(+), 36 deletions(-)

diff --git a/src/libstore/build.cc b/src/libstore/build.cc
index 62294a08c..3a55f24cb 100644
--- a/src/libstore/build.cc
+++ b/src/libstore/build.cc
@@ -2756,8 +2756,12 @@ struct RestrictedStore : public LocalFSStore
     void queryReferrers(const StorePath & path, StorePathSet & referrers) override
     { }
 
-    OutputPathMap queryDerivationOutputMap(const StorePath & path) override
-    { throw Error("queryDerivationOutputMap"); }
+    std::map<std::string, std::optional<StorePath>> queryDerivationOutputMap(const StorePath & path) override
+    {
+        if (!goal.isAllowed(path))
+            throw InvalidPath("cannot query output map for unknown path '%s' in recursive Nix", printStorePath(path));
+        return next->queryDerivationOutputMap(path);
+    }
 
     std::optional<StorePath> queryPathFromHashPart(const std::string & hashPart) override
     { throw Error("queryPathFromHashPart"); }
@@ -4918,7 +4922,7 @@ void Worker::waitForInput()
         std::vector<unsigned char> buffer(4096);
         for (auto & k : fds2) {
             if (pollStatus.at(fdToPollStatus.at(k)).revents) {
-                ssize_t rd = read(k, buffer.data(), buffer.size());
+                ssize_t rd = ::read(k, buffer.data(), buffer.size());
                 // FIXME: is there a cleaner way to handle pt close
                 // than EIO? Is this even standard?
                 if (rd == 0 || (rd == -1 && errno == EIO)) {
diff --git a/src/libstore/daemon.cc b/src/libstore/daemon.cc
index 7e16529a5..82833f5c3 100644
--- a/src/libstore/daemon.cc
+++ b/src/libstore/daemon.cc
@@ -325,9 +325,9 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
     case wopQueryDerivationOutputMap: {
         auto path = store->parseStorePath(readString(from));
         logger->startWork();
-        OutputPathMap outputs = store->queryDerivationOutputMap(path);
+        auto outputs = store->queryDerivationOutputMap(path);
         logger->stopWork();
-        writeOutputPathMap(*store, to, outputs);
+        write(*store, to, outputs);
         break;
     }
 
diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc
index 9ea71170f..4db4f7d6a 100644
--- a/src/libstore/local-store.cc
+++ b/src/libstore/local-store.cc
@@ -774,17 +774,21 @@ StorePathSet LocalStore::queryValidDerivers(const StorePath & path)
 }
 
 
-OutputPathMap LocalStore::queryDerivationOutputMap(const StorePath & path)
+std::map<std::string, std::optional<StorePath>> LocalStore::queryDerivationOutputMap(const StorePath & path)
 {
-    return retrySQLite<OutputPathMap>([&]() {
+    std::map<std::string, std::optional<StorePath>> outputs;
+    BasicDerivation drv = readDerivation(path);
+    for (auto & [outName, _] : drv.outputs) {
+        outputs.insert_or_assign(outName, std::nullopt);
+    }
+    return retrySQLite<std::map<std::string, std::optional<StorePath>>>([&]() {
         auto state(_state.lock());
 
         auto useQueryDerivationOutputs(state->stmtQueryDerivationOutputs.use()
             (queryValidPathId(*state, path)));
 
-        OutputPathMap outputs;
         while (useQueryDerivationOutputs.next())
-            outputs.emplace(
+            outputs.insert_or_assign(
                 useQueryDerivationOutputs.getStr(0),
                 parseStorePath(useQueryDerivationOutputs.getStr(1))
             );
diff --git a/src/libstore/local-store.hh b/src/libstore/local-store.hh
index 355c2814f..56ef1d251 100644
--- a/src/libstore/local-store.hh
+++ b/src/libstore/local-store.hh
@@ -133,7 +133,7 @@ public:
 
     StorePathSet queryValidDerivers(const StorePath & path) override;
 
-    OutputPathMap queryDerivationOutputMap(const StorePath & path) override;
+    std::map<std::string, std::optional<StorePath>> queryDerivationOutputMap(const StorePath & path) override;
 
     std::optional<StorePath> queryPathFromHashPart(const std::string & hashPart) override;
 
diff --git a/src/libstore/remote-store.cc b/src/libstore/remote-store.cc
index 9af4364b7..3b3cdbc02 100644
--- a/src/libstore/remote-store.cc
+++ b/src/libstore/remote-store.cc
@@ -39,30 +39,21 @@ void writeStorePaths(const Store & store, Sink & out, const StorePathSet & paths
         out << store.printStorePath(i);
 }
 
-std::map<string, StorePath> readOutputPathMap(const Store & store, Source & from)
+
+StorePath read(const Store & store, Source & from, Proxy<StorePath> _)
 {
-    std::map<string, StorePath> pathMap;
-    auto rawInput = readStrings<Strings>(from);
-    if (rawInput.size() % 2)
-        throw Error("got an odd number of elements from the daemon when trying to read a output path map");
-    auto curInput = rawInput.begin();
-    while (curInput != rawInput.end()) {
-        auto thisKey = *curInput++;
-        auto thisValue = *curInput++;
-        pathMap.emplace(thisKey, store.parseStorePath(thisValue));
-    }
-    return pathMap;
+	auto path = readString(from);
+	return store.parseStorePath(path);
 }
 
-void writeOutputPathMap(const Store & store, Sink & out, const std::map<string, StorePath> & pathMap)
+
+void write(const Store & store, Sink & out, const StorePath & storePath)
 {
-    out << 2*pathMap.size();
-    for (auto & i : pathMap) {
-        out << i.first;
-        out << store.printStorePath(i.second);
-    }
+	auto path = store.printStorePath(storePath);
+	out << path;
 }
 
+
 /* TODO: Separate these store impls into different files, give them better names */
 RemoteStore::RemoteStore(const Params & params)
     : Store(params)
@@ -445,12 +436,12 @@ StorePathSet RemoteStore::queryDerivationOutputs(const StorePath & path)
 }
 
 
-OutputPathMap RemoteStore::queryDerivationOutputMap(const StorePath & path)
+std::map<std::string, std::optional<StorePath>> RemoteStore::queryDerivationOutputMap(const StorePath & path)
 {
     auto conn(getConnection());
     conn->to << wopQueryDerivationOutputMap << printStorePath(path);
     conn.processStderr();
-    return readOutputPathMap(*this, conn->from);
+    return read(*this, conn->from, Proxy<std::map<std::string, std::optional<StorePath>>> {});
 
 }
 
diff --git a/src/libstore/remote-store.hh b/src/libstore/remote-store.hh
index 3c1b78b6a..b39a8de48 100644
--- a/src/libstore/remote-store.hh
+++ b/src/libstore/remote-store.hh
@@ -51,7 +51,7 @@ public:
 
     StorePathSet queryDerivationOutputs(const StorePath & path) override;
 
-    OutputPathMap queryDerivationOutputMap(const StorePath & path) override;
+    std::map<std::string, std::optional<StorePath>> queryDerivationOutputMap(const StorePath & path) override;
     std::optional<StorePath> queryPathFromHashPart(const std::string & hashPart) override;
 
     StorePathSet querySubstitutablePaths(const StorePathSet & paths) override;
diff --git a/src/libstore/store-api.cc b/src/libstore/store-api.cc
index ab21b0bd5..a624fafdd 100644
--- a/src/libstore/store-api.cc
+++ b/src/libstore/store-api.cc
@@ -330,9 +330,20 @@ bool Store::PathInfoCacheValue::isKnownNow()
     return std::chrono::steady_clock::now() < time_point + ttl;
 }
 
+OutputPathMap Store::queryDerivationOutputMapAssumeTotal(const StorePath & path) {
+	auto resp = queryDerivationOutputMap(path);
+	OutputPathMap result;
+	for (auto & [outName, optOutPath] : resp) {
+		if (!optOutPath)
+			throw Error("output '%s' has no store path mapped to it", outName);
+		result.insert_or_assign(outName, *optOutPath);
+	}
+	return result;
+}
+
 StorePathSet Store::queryDerivationOutputs(const StorePath & path)
 {
-    auto outputMap = this->queryDerivationOutputMap(path);
+    auto outputMap = this->queryDerivationOutputMapAssumeTotal(path);
     StorePathSet outputPaths;
     for (auto & i: outputMap) {
         outputPaths.emplace(std::move(i.second));
diff --git a/src/libstore/store-api.hh b/src/libstore/store-api.hh
index d1cb2035f..a83a2ff10 100644
--- a/src/libstore/store-api.hh
+++ b/src/libstore/store-api.hh
@@ -423,10 +423,16 @@ public:
     /* Query the outputs of the derivation denoted by `path'. */
     virtual StorePathSet queryDerivationOutputs(const StorePath & path);
 
-    /* Query the mapping outputName=>outputPath for the given derivation */
-    virtual OutputPathMap queryDerivationOutputMap(const StorePath & path)
+    /* Query the mapping outputName => outputPath for the given derivation. All
+       outputs are mentioned so ones mising the mapping are mapped to
+       `std::nullopt`.  */
+    virtual std::map<std::string, std::optional<StorePath>> queryDerivationOutputMap(const StorePath & path)
     { unsupported("queryDerivationOutputMap"); }
 
+    /* Query the mapping outputName=>outputPath for the given derivation.
+       Assume every output has a mapping and throw an exception otherwise. */
+    OutputPathMap queryDerivationOutputMapAssumeTotal(const StorePath & path);
+
     /* Query the full store path given the hash part of a valid store
        path, or empty if the path doesn't exist. */
     virtual std::optional<StorePath> queryPathFromHashPart(const std::string & hashPart) = 0;
diff --git a/src/libstore/worker-protocol.hh b/src/libstore/worker-protocol.hh
index 8b538f6da..6a6e29640 100644
--- a/src/libstore/worker-protocol.hh
+++ b/src/libstore/worker-protocol.hh
@@ -70,6 +70,56 @@ template<class T> T readStorePaths(const Store & store, Source & from);
 
 void writeStorePaths(const Store & store, Sink & out, const StorePathSet & paths);
 
-void writeOutputPathMap(const Store & store, Sink & out, const OutputPathMap & paths);
+/* To guide overloading */
+template<typename T>
+struct Proxy {};
+
+template<typename T>
+std::map<std::string, T> read(const Store & store, Source & from, Proxy<std::map<std::string, T>> _)
+{
+    std::map<string, T> resMap;
+    auto size = (size_t)readInt(from);
+    while (size--) {
+        auto thisKey = readString(from);
+        resMap.insert_or_assign(std::move(thisKey), read(store, from, Proxy<T> {}));
+    }
+    return resMap;
+}
+
+template<typename T>
+void write(const Store & store, Sink & out, const std::map<string, T> & resMap)
+{
+    out << resMap.size();
+    for (auto & i : resMap) {
+        out << i.first;
+        write(store, out, i.second);
+    }
+}
+
+template<typename T>
+std::optional<T> read(const Store & store, Source & from, Proxy<std::optional<T>> _)
+{
+    auto tag = readNum<uint8_t>(from);
+    switch (tag) {
+    case 0:
+        return std::nullopt;
+    case 1:
+        return read(store, from, Proxy<T> {});
+    default:
+        throw Error("got an invalid tag bit for std::optional: %#04x", tag);
+    }
+}
+
+template<typename T>
+void write(const Store & store, Sink & out, const std::optional<T> & optVal)
+{
+    out << (optVal ? 1 : 0);
+    if (optVal)
+        write(store, out, *optVal);
+}
+
+StorePath read(const Store & store, Source & from, Proxy<StorePath> _);
+
+void write(const Store & store, Sink & out, const StorePath & storePath);
 
 }
diff --git a/src/nix-env/nix-env.cc b/src/nix-env/nix-env.cc
index ddd036070..d36804658 100644
--- a/src/nix-env/nix-env.cc
+++ b/src/nix-env/nix-env.cc
@@ -381,7 +381,7 @@ static void queryInstSources(EvalState & state,
 
                 if (path.isDerivation()) {
                     elem.setDrvPath(state.store->printStorePath(path));
-                    auto outputs = state.store->queryDerivationOutputMap(path);
+                    auto outputs = state.store->queryDerivationOutputMapAssumeTotal(path);
                     elem.setOutPath(state.store->printStorePath(outputs.at("out")));
                     if (name.size() >= drvExtension.size() &&
                         string(name, name.size() - drvExtension.size()) == drvExtension)

From 45b6fdb22b05352108b6470ae16d611431c06666 Mon Sep 17 00:00:00 2001
From: John Ericson <John.Ericson@Obsidian.Systems>
Date: Tue, 4 Aug 2020 22:10:13 +0000
Subject: [PATCH 02/18] Remove unused functions

---
 src/libstore/remote-store.cc    | 16 ----------------
 src/libstore/worker-protocol.hh |  2 --
 2 files changed, 18 deletions(-)

diff --git a/src/libstore/remote-store.cc b/src/libstore/remote-store.cc
index 1200ab200..dc2efefeb 100644
--- a/src/libstore/remote-store.cc
+++ b/src/libstore/remote-store.cc
@@ -63,22 +63,6 @@ void writeStorePathCAMap(const Store & store, Sink & out, const StorePathCAMap &
     }
 }
 
-std::map<string, StorePath> readOutputPathMap(const Store & store, Source & from)
-{
-    std::map<string, StorePath> pathMap;
-    auto rawInput = readStrings<Strings>(from);
-    if (rawInput.size() % 2)
-        throw Error("got an odd number of elements from the daemon when trying to read a output path map");
-    auto curInput = rawInput.begin();
-    while (curInput != rawInput.end()) {
-        auto thisKey = *curInput++;
-        auto thisValue = *curInput++;
-        pathMap.emplace(thisKey, store.parseStorePath(thisValue));
-    }
-    return pathMap;
-}
-
-
 void write(const Store & store, Sink & out, const StorePath & storePath)
 {
 	auto path = store.printStorePath(storePath);
diff --git a/src/libstore/worker-protocol.hh b/src/libstore/worker-protocol.hh
index ad5854c85..117d3e1a4 100644
--- a/src/libstore/worker-protocol.hh
+++ b/src/libstore/worker-protocol.hh
@@ -126,6 +126,4 @@ StorePathCAMap readStorePathCAMap(const Store & store, Source & from);
 
 void writeStorePathCAMap(const Store & store, Sink & out, const StorePathCAMap & paths);
 
-void writeOutputPathMap(const Store & store, Sink & out, const OutputPathMap & paths);
-
 }

From 1dfcbebc95f1c74763770bb2bd7afdebe8804845 Mon Sep 17 00:00:00 2001
From: John Ericson <John.Ericson@Obsidian.Systems>
Date: Tue, 4 Aug 2020 22:28:10 +0000
Subject: [PATCH 03/18] Organize and format code a bit

---
 src/libstore/remote-store.cc | 17 +++++++++--------
 src/libstore/store-api.cc    | 16 ++++++++--------
 2 files changed, 17 insertions(+), 16 deletions(-)

diff --git a/src/libstore/remote-store.cc b/src/libstore/remote-store.cc
index dc2efefeb..772979066 100644
--- a/src/libstore/remote-store.cc
+++ b/src/libstore/remote-store.cc
@@ -31,7 +31,6 @@ template<> StorePathSet readStorePaths(const Store & store, Source & from)
     return paths;
 }
 
-
 void writeStorePaths(const Store & store, Sink & out, const StorePathSet & paths)
 {
     out << paths.size();
@@ -39,11 +38,6 @@ void writeStorePaths(const Store & store, Sink & out, const StorePathSet & paths
         out << store.printStorePath(i);
 }
 
-StorePath read(const Store & store, Source & from, Proxy<StorePath> _)
-{
-	auto path = readString(from);
-	return store.parseStorePath(path);
-}
 
 StorePathCAMap readStorePathCAMap(const Store & store, Source & from)
 {
@@ -63,10 +57,17 @@ void writeStorePathCAMap(const Store & store, Sink & out, const StorePathCAMap &
     }
 }
 
+
+StorePath read(const Store & store, Source & from, Proxy<StorePath> _)
+{
+    auto path = readString(from);
+    return store.parseStorePath(path);
+}
+
 void write(const Store & store, Sink & out, const StorePath & storePath)
 {
-	auto path = store.printStorePath(storePath);
-	out << path;
+    auto path = store.printStorePath(storePath);
+    out << path;
 }
 
 
diff --git a/src/libstore/store-api.cc b/src/libstore/store-api.cc
index 4c68709ef..e894d2b85 100644
--- a/src/libstore/store-api.cc
+++ b/src/libstore/store-api.cc
@@ -362,14 +362,14 @@ bool Store::PathInfoCacheValue::isKnownNow()
 }
 
 OutputPathMap Store::queryDerivationOutputMapAssumeTotal(const StorePath & path) {
-	auto resp = queryDerivationOutputMap(path);
-	OutputPathMap result;
-	for (auto & [outName, optOutPath] : resp) {
-		if (!optOutPath)
-			throw Error("output '%s' has no store path mapped to it", outName);
-		result.insert_or_assign(outName, *optOutPath);
-	}
-	return result;
+    auto resp = queryDerivationOutputMap(path);
+    OutputPathMap result;
+    for (auto & [outName, optOutPath] : resp) {
+        if (!optOutPath)
+            throw Error("output '%s' has no store path mapped to it", outName);
+        result.insert_or_assign(outName, *optOutPath);
+    }
+    return result;
 }
 
 StorePathSet Store::queryDerivationOutputs(const StorePath & path)

From 16c98bf57c52dee59c06e0e8911800943a468962 Mon Sep 17 00:00:00 2001
From: John Ericson <John.Ericson@Obsidian.Systems>
Date: Tue, 4 Aug 2020 22:36:31 +0000
Subject: [PATCH 04/18] Get rid of some unneeded temporaries

---
 src/libstore/remote-store.cc | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/src/libstore/remote-store.cc b/src/libstore/remote-store.cc
index 772979066..63c19a450 100644
--- a/src/libstore/remote-store.cc
+++ b/src/libstore/remote-store.cc
@@ -60,14 +60,12 @@ void writeStorePathCAMap(const Store & store, Sink & out, const StorePathCAMap &
 
 StorePath read(const Store & store, Source & from, Proxy<StorePath> _)
 {
-    auto path = readString(from);
-    return store.parseStorePath(path);
+    return store.parseStorePath(readString(from));
 }
 
 void write(const Store & store, Sink & out, const StorePath & storePath)
 {
-    auto path = store.printStorePath(storePath);
-    out << path;
+    out << store.printStorePath(storePath);
 }
 
 

From ed96e603e116123c1e44f5108b67b472b2c96538 Mon Sep 17 00:00:00 2001
From: John Ericson <John.Ericson@Obsidian.Systems>
Date: Wed, 5 Aug 2020 19:44:08 +0000
Subject: [PATCH 05/18] Proxy -> Phantom to match Rust

Sorry, Haskell.
---
 src/libstore/remote-store.cc    |  4 ++--
 src/libstore/worker-protocol.hh | 12 ++++++------
 2 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/src/libstore/remote-store.cc b/src/libstore/remote-store.cc
index 377c81ff4..2c0857466 100644
--- a/src/libstore/remote-store.cc
+++ b/src/libstore/remote-store.cc
@@ -58,7 +58,7 @@ void writeStorePathCAMap(const Store & store, Sink & out, const StorePathCAMap &
 }
 
 
-StorePath read(const Store & store, Source & from, Proxy<StorePath> _)
+StorePath read(const Store & store, Source & from, Phantom<StorePath> _)
 {
     return store.parseStorePath(readString(from));
 }
@@ -461,7 +461,7 @@ std::map<std::string, std::optional<StorePath>> RemoteStore::queryDerivationOutp
     auto conn(getConnection());
     conn->to << wopQueryDerivationOutputMap << printStorePath(path);
     conn.processStderr();
-    return read(*this, conn->from, Proxy<std::map<std::string, std::optional<StorePath>>> {});
+    return read(*this, conn->from, Phantom<std::map<std::string, std::optional<StorePath>>> {});
 
 }
 
diff --git a/src/libstore/worker-protocol.hh b/src/libstore/worker-protocol.hh
index 117d3e1a4..384b81f08 100644
--- a/src/libstore/worker-protocol.hh
+++ b/src/libstore/worker-protocol.hh
@@ -72,16 +72,16 @@ void writeStorePaths(const Store & store, Sink & out, const StorePathSet & paths
 
 /* To guide overloading */
 template<typename T>
-struct Proxy {};
+struct Phantom {};
 
 template<typename T>
-std::map<std::string, T> read(const Store & store, Source & from, Proxy<std::map<std::string, T>> _)
+std::map<std::string, T> read(const Store & store, Source & from, Phantom<std::map<std::string, T>> _)
 {
     std::map<string, T> resMap;
     auto size = (size_t)readInt(from);
     while (size--) {
         auto thisKey = readString(from);
-        resMap.insert_or_assign(std::move(thisKey), read(store, from, Proxy<T> {}));
+        resMap.insert_or_assign(std::move(thisKey), read(store, from, Phantom<T> {}));
     }
     return resMap;
 }
@@ -97,14 +97,14 @@ void write(const Store & store, Sink & out, const std::map<string, T> & resMap)
 }
 
 template<typename T>
-std::optional<T> read(const Store & store, Source & from, Proxy<std::optional<T>> _)
+std::optional<T> read(const Store & store, Source & from, Phantom<std::optional<T>> _)
 {
     auto tag = readNum<uint8_t>(from);
     switch (tag) {
     case 0:
         return std::nullopt;
     case 1:
-        return read(store, from, Proxy<T> {});
+        return read(store, from, Phantom<T> {});
     default:
         throw Error("got an invalid tag bit for std::optional: %#04x", tag);
     }
@@ -118,7 +118,7 @@ void write(const Store & store, Sink & out, const std::optional<T> & optVal)
         write(store, out, *optVal);
 }
 
-StorePath read(const Store & store, Source & from, Proxy<StorePath> _);
+StorePath read(const Store & store, Source & from, Phantom<StorePath> _);
 
 void write(const Store & store, Sink & out, const StorePath & storePath);
 

From 6c66331d5c34d97092a9b5e2832fed73e0da3c87 Mon Sep 17 00:00:00 2001
From: John Ericson <John.Ericson@Obsidian.Systems>
Date: Wed, 5 Aug 2020 20:37:48 +0000
Subject: [PATCH 06/18] WIP: Put the worker protocol `read` and `write` in a
 namespace to disambig

---
 src/libstore/remote-store.cc    |  6 +++++-
 src/libstore/worker-protocol.hh | 15 +++++++++++----
 2 files changed, 16 insertions(+), 5 deletions(-)

diff --git a/src/libstore/remote-store.cc b/src/libstore/remote-store.cc
index 2c0857466..96e0aabba 100644
--- a/src/libstore/remote-store.cc
+++ b/src/libstore/remote-store.cc
@@ -58,6 +58,8 @@ void writeStorePathCAMap(const Store & store, Sink & out, const StorePathCAMap &
 }
 
 
+namespace worker_proto {
+
 StorePath read(const Store & store, Source & from, Phantom<StorePath> _)
 {
     return store.parseStorePath(readString(from));
@@ -68,6 +70,8 @@ void write(const Store & store, Sink & out, const StorePath & storePath)
     out << store.printStorePath(storePath);
 }
 
+}
+
 
 /* TODO: Separate these store impls into different files, give them better names */
 RemoteStore::RemoteStore(const Params & params)
@@ -461,7 +465,7 @@ std::map<std::string, std::optional<StorePath>> RemoteStore::queryDerivationOutp
     auto conn(getConnection());
     conn->to << wopQueryDerivationOutputMap << printStorePath(path);
     conn.processStderr();
-    return read(*this, conn->from, Phantom<std::map<std::string, std::optional<StorePath>>> {});
+    return worker_proto::read(*this, conn->from, Phantom<std::map<std::string, std::optional<StorePath>>> {});
 
 }
 
diff --git a/src/libstore/worker-protocol.hh b/src/libstore/worker-protocol.hh
index 384b81f08..fdd700668 100644
--- a/src/libstore/worker-protocol.hh
+++ b/src/libstore/worker-protocol.hh
@@ -74,6 +74,10 @@ void writeStorePaths(const Store & store, Sink & out, const StorePathSet & paths
 template<typename T>
 struct Phantom {};
 
+
+namespace worker_proto {
+/* FIXME maybe move more stuff inside here */
+
 template<typename T>
 std::map<std::string, T> read(const Store & store, Source & from, Phantom<std::map<std::string, T>> _)
 {
@@ -81,7 +85,7 @@ std::map<std::string, T> read(const Store & store, Source & from, Phantom<std::m
     auto size = (size_t)readInt(from);
     while (size--) {
         auto thisKey = readString(from);
-        resMap.insert_or_assign(std::move(thisKey), read(store, from, Phantom<T> {}));
+        resMap.insert_or_assign(std::move(thisKey), nix::worker_proto::read(store, from, Phantom<T> {}));
     }
     return resMap;
 }
@@ -92,7 +96,7 @@ void write(const Store & store, Sink & out, const std::map<string, T> & resMap)
     out << resMap.size();
     for (auto & i : resMap) {
         out << i.first;
-        write(store, out, i.second);
+        nix::worker_proto::write(store, out, i.second);
     }
 }
 
@@ -104,7 +108,7 @@ std::optional<T> read(const Store & store, Source & from, Phantom<std::optional<
     case 0:
         return std::nullopt;
     case 1:
-        return read(store, from, Phantom<T> {});
+        return nix::worker_proto::read(store, from, Phantom<T> {});
     default:
         throw Error("got an invalid tag bit for std::optional: %#04x", tag);
     }
@@ -115,13 +119,16 @@ void write(const Store & store, Sink & out, const std::optional<T> & optVal)
 {
     out << (optVal ? 1 : 0);
     if (optVal)
-        write(store, out, *optVal);
+        nix::worker_proto::write(store, out, *optVal);
 }
 
 StorePath read(const Store & store, Source & from, Phantom<StorePath> _);
 
 void write(const Store & store, Sink & out, const StorePath & storePath);
 
+}
+
+
 StorePathCAMap readStorePathCAMap(const Store & store, Source & from);
 
 void writeStorePathCAMap(const Store & store, Sink & out, const StorePathCAMap & paths);

From 0739d428e07b59b7d44bdbf7354dafaae09dba7c Mon Sep 17 00:00:00 2001
From: Carlo Nucera <carlo.nucera@protonmail.com>
Date: Wed, 5 Aug 2020 17:49:45 -0400
Subject: [PATCH 07/18] Solve template deduction problem

We had to predeclare our template functions
---
 src/libstore/daemon.cc          |  2 +-
 src/libstore/worker-protocol.hh | 16 +++++++++++++---
 2 files changed, 14 insertions(+), 4 deletions(-)

diff --git a/src/libstore/daemon.cc b/src/libstore/daemon.cc
index e18f793c8..a420dcab0 100644
--- a/src/libstore/daemon.cc
+++ b/src/libstore/daemon.cc
@@ -327,7 +327,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
         logger->startWork();
         auto outputs = store->queryDerivationOutputMap(path);
         logger->stopWork();
-        write(*store, to, outputs);
+        nix::worker_proto::write(*store, to, outputs);
         break;
     }
 
diff --git a/src/libstore/worker-protocol.hh b/src/libstore/worker-protocol.hh
index fdd700668..c50995d7c 100644
--- a/src/libstore/worker-protocol.hh
+++ b/src/libstore/worker-protocol.hh
@@ -78,6 +78,19 @@ struct Phantom {};
 namespace worker_proto {
 /* FIXME maybe move more stuff inside here */
 
+StorePath read(const Store & store, Source & from, Phantom<StorePath> _);
+void write(const Store & store, Sink & out, const StorePath & storePath);
+
+template<typename T>
+std::map<std::string, T> read(const Store & store, Source & from, Phantom<std::map<std::string, T>> _);
+template<typename T>
+void write(const Store & store, Sink & out, const std::map<string, T> & resMap);
+template<typename T>
+std::optional<T> read(const Store & store, Source & from, Phantom<std::optional<T>> _);
+template<typename T>
+void write(const Store & store, Sink & out, const std::optional<T> & optVal);
+
+
 template<typename T>
 std::map<std::string, T> read(const Store & store, Source & from, Phantom<std::map<std::string, T>> _)
 {
@@ -122,9 +135,6 @@ void write(const Store & store, Sink & out, const std::optional<T> & optVal)
         nix::worker_proto::write(store, out, *optVal);
 }
 
-StorePath read(const Store & store, Source & from, Phantom<StorePath> _);
-
-void write(const Store & store, Sink & out, const StorePath & storePath);
 
 }
 

From 8b175f58d1a25dd904fb0ffdf5b2b9501983dba0 Mon Sep 17 00:00:00 2001
From: Carlo Nucera <carlo.nucera@protonmail.com>
Date: Wed, 5 Aug 2020 17:57:07 -0400
Subject: [PATCH 08/18] Simplify the namespace

---
 src/libstore/daemon.cc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/libstore/daemon.cc b/src/libstore/daemon.cc
index a420dcab0..9503915eb 100644
--- a/src/libstore/daemon.cc
+++ b/src/libstore/daemon.cc
@@ -327,7 +327,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
         logger->startWork();
         auto outputs = store->queryDerivationOutputMap(path);
         logger->stopWork();
-        nix::worker_proto::write(*store, to, outputs);
+        worker_proto::write(*store, to, outputs);
         break;
     }
 

From 47644e49ca252441fcc3ae57f5a01e7fc579ba8c Mon Sep 17 00:00:00 2001
From: John Ericson <John.Ericson@Obsidian.Systems>
Date: Fri, 7 Aug 2020 17:05:14 +0000
Subject: [PATCH 09/18] Specialize `std::optional<StorePath>` so this is
 backwards compatible

While I am cautious to break parametricity, I think it's OK in this
cases---we're not about to try to do some crazy polymorphic protocol
anytime soon.
---
 src/libstore/remote-store.cc    | 14 ++++++++++++++
 src/libstore/worker-protocol.hh |  7 +++++++
 2 files changed, 21 insertions(+)

diff --git a/src/libstore/remote-store.cc b/src/libstore/remote-store.cc
index 96e0aabba..005415666 100644
--- a/src/libstore/remote-store.cc
+++ b/src/libstore/remote-store.cc
@@ -70,6 +70,20 @@ void write(const Store & store, Sink & out, const StorePath & storePath)
     out << store.printStorePath(storePath);
 }
 
+
+template<>
+std::optional<StorePath> read(const Store & store, Source & from, Phantom<std::optional<StorePath>> _)
+{
+	auto s = readString(from);
+    return s == "" ? std::optional<StorePath> {} : store.parseStorePath(s);
+}
+
+template<>
+void write(const Store & store, Sink & out, const std::optional<StorePath> & storePathOpt)
+{
+    out << (storePathOpt ? store.printStorePath(*storePathOpt) : "");
+}
+
 }
 
 
diff --git a/src/libstore/worker-protocol.hh b/src/libstore/worker-protocol.hh
index c50995d7c..b3576fbeb 100644
--- a/src/libstore/worker-protocol.hh
+++ b/src/libstore/worker-protocol.hh
@@ -90,6 +90,13 @@ std::optional<T> read(const Store & store, Source & from, Phantom<std::optional<
 template<typename T>
 void write(const Store & store, Sink & out, const std::optional<T> & optVal);
 
+/* Specialization which uses and empty string for the empty case, taking
+   advantage of the fact StorePaths always serialize to a non-empty string.
+   This is done primarily for backwards compatability, so that StorePath <=
+   std::optional<StorePath>, where <= is the compatability partial order.
+ */
+template<>
+void write(const Store & store, Sink & out, const std::optional<StorePath> & optVal);
 
 template<typename T>
 std::map<std::string, T> read(const Store & store, Source & from, Phantom<std::map<std::string, T>> _)

From d3fa8c04c68a9532f85a18271c35457981a840ec Mon Sep 17 00:00:00 2001
From: John Ericson <John.Ericson@Obsidian.Systems>
Date: Tue, 11 Aug 2020 01:13:26 +0000
Subject: [PATCH 10/18] Simplify code as output env vars are unconditional

Since the jsonObject unique ptr is reset to flush the string to make
`__json`, all these `!jsonObject` conditions will always be true.
---
 src/libexpr/primops.cc | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc
index 65d36ca0e..af751a496 100644
--- a/src/libexpr/primops.cc
+++ b/src/libexpr/primops.cc
@@ -781,7 +781,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
         Hash h = newHashAllowEmpty(*outputHash, ht);
 
         auto outPath = state.store->makeFixedOutputPath(ingestionMethod, h, drvName);
-        if (!jsonObject) drv.env["out"] = state.store->printStorePath(outPath);
+        drv.env["out"] = state.store->printStorePath(outPath);
         drv.outputs.insert_or_assign("out", DerivationOutput {
                 .output = DerivationOutputCAFixed {
                     .hash = FixedOutputHash {
@@ -795,7 +795,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
     else if (contentAddressed) {
         HashType ht = parseHashType(outputHashAlgo);
         for (auto & i : outputs) {
-            if (!jsonObject) drv.env[i] = hashPlaceholder(i);
+            drv.env[i] = hashPlaceholder(i);
             drv.outputs.insert_or_assign(i, DerivationOutput {
                 .output = DerivationOutputCAFloating {
                     .method = ingestionMethod,
@@ -813,7 +813,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
            that changes in the set of output names do get reflected in
            the hash. */
         for (auto & i : outputs) {
-            if (!jsonObject) drv.env[i] = "";
+            drv.env[i] = "";
             drv.outputs.insert_or_assign(i,
                 DerivationOutput {
                     .output = DerivationOutputInputAddressed {
@@ -828,7 +828,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
 
         for (auto & i : outputs) {
             auto outPath = state.store->makeOutputPath(i, h, drvName);
-            if (!jsonObject) drv.env[i] = state.store->printStorePath(outPath);
+            drv.env[i] = state.store->printStorePath(outPath);
             drv.outputs.insert_or_assign(i,
                 DerivationOutput {
                     .output = DerivationOutputInputAddressed {

From e1308b121169ea8327c95556668ad4f7f4815402 Mon Sep 17 00:00:00 2001
From: John Ericson <John.Ericson@Obsidian.Systems>
Date: Wed, 12 Aug 2020 03:13:17 +0000
Subject: [PATCH 11/18] Define `LegacySSHStore::buildPaths` using
 `cmdBuildPaths`

Evidentally this was never implemented because Nix switched to using
`buildDerivation` exclusively before `build-remote.pl` was rewritten.

The `nix-copy-ssh` test (already) tests this.
---
 src/libstore/legacy-ssh-store.cc | 53 ++++++++++++++++++++++++++------
 1 file changed, 43 insertions(+), 10 deletions(-)

diff --git a/src/libstore/legacy-ssh-store.cc b/src/libstore/legacy-ssh-store.cc
index c6eeab548..b5ece22f4 100644
--- a/src/libstore/legacy-ssh-store.cc
+++ b/src/libstore/legacy-ssh-store.cc
@@ -202,6 +202,24 @@ struct LegacySSHStore : public Store
         const StorePathSet & references, RepairFlag repair) override
     { unsupported("addTextToStore"); }
 
+private:
+
+    void putBuildSettings(Connection & conn)
+    {
+        conn.to
+            << settings.maxSilentTime
+            << settings.buildTimeout;
+        if (GET_PROTOCOL_MINOR(conn.remoteVersion) >= 2)
+            conn.to
+                << settings.maxLogSize;
+        if (GET_PROTOCOL_MINOR(conn.remoteVersion) >= 3)
+            conn.to
+                << settings.buildRepeat
+                << settings.enforceDeterminism;
+    }
+
+public:
+
     BuildResult buildDerivation(const StorePath & drvPath, const BasicDerivation & drv,
         BuildMode buildMode) override
     {
@@ -211,16 +229,8 @@ struct LegacySSHStore : public Store
             << cmdBuildDerivation
             << printStorePath(drvPath);
         writeDerivation(conn->to, *this, drv);
-        conn->to
-            << settings.maxSilentTime
-            << settings.buildTimeout;
-        if (GET_PROTOCOL_MINOR(conn->remoteVersion) >= 2)
-            conn->to
-                << settings.maxLogSize;
-        if (GET_PROTOCOL_MINOR(conn->remoteVersion) >= 3)
-            conn->to
-                << settings.buildRepeat
-                << settings.enforceDeterminism;
+
+        putBuildSettings(*conn);
 
         conn->to.flush();
 
@@ -234,6 +244,29 @@ struct LegacySSHStore : public Store
         return status;
     }
 
+    void buildPaths(const std::vector<StorePathWithOutputs> & drvPaths, BuildMode buildMode) override
+    {
+        auto conn(connections->get());
+
+        conn->to << cmdBuildPaths;
+        Strings ss;
+        for (auto & p : drvPaths)
+            ss.push_back(p.to_string(*this));
+        conn->to << ss;
+
+        putBuildSettings(*conn);
+
+        conn->to.flush();
+
+        BuildResult result;
+        result.status = (BuildResult::Status) readInt(conn->from);
+
+        if (!result.success()) {
+            conn->from >> result.errorMsg;
+            throw Error(result.status, result.errorMsg);
+        }
+    }
+
     void ensurePath(const StorePath & path) override
     { unsupported("ensurePath"); }
 

From dbf96e10ecc75410c9db798f208f8a8310842a4f Mon Sep 17 00:00:00 2001
From: John Ericson <John.Ericson@Obsidian.Systems>
Date: Sun, 16 Aug 2020 17:38:12 +0000
Subject: [PATCH 12/18] Test remote building with fixed output derivations

---
 tests/build-hook-ca.nix                       | 45 +++++++++++++++++++
 tests/build-remote-content-addressed-fixed.sh |  5 +++
 tests/build-remote-input-addressed.sh         |  5 +++
 tests/build-remote.sh                         |  4 +-
 tests/local.mk                                |  3 +-
 5 files changed, 58 insertions(+), 4 deletions(-)
 create mode 100644 tests/build-hook-ca.nix
 create mode 100644 tests/build-remote-content-addressed-fixed.sh
 create mode 100644 tests/build-remote-input-addressed.sh

diff --git a/tests/build-hook-ca.nix b/tests/build-hook-ca.nix
new file mode 100644
index 000000000..98db473fc
--- /dev/null
+++ b/tests/build-hook-ca.nix
@@ -0,0 +1,45 @@
+{ busybox }:
+
+with import ./config.nix;
+
+let
+
+  mkDerivation = args:
+    derivation ({
+      inherit system;
+      builder = busybox;
+      args = ["sh" "-e" args.builder or (builtins.toFile "builder-${args.name}.sh" "if [ -e .attrs.sh ]; then source .attrs.sh; fi; eval \"$buildCommand\"")];
+      outputHashMode = "recursive";
+      outputHashAlgo = "sha256";
+    } // removeAttrs args ["builder" "meta"])
+    // { meta = args.meta or {}; };
+
+  input1 = mkDerivation {
+    shell = busybox;
+    name = "build-remote-input-1";
+    buildCommand = "echo FOO > $out";
+    requiredSystemFeatures = ["foo"];
+    outputHash = "sha256-FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc=";
+  };
+
+  input2 = mkDerivation {
+    shell = busybox;
+    name = "build-remote-input-2";
+    buildCommand = "echo BAR > $out";
+    requiredSystemFeatures = ["bar"];
+    outputHash = "sha256-XArauVH91AVwP9hBBQNlkX9ccuPpSYx9o0zeIHb6e+Q=";
+  };
+
+in
+
+  mkDerivation {
+    shell = busybox;
+    name = "build-remote";
+    buildCommand =
+      ''
+        read x < ${input1}
+        read y < ${input2}
+        echo "$x $y" > $out
+      '';
+    outputHash = "sha256-3YGhlOfbGUm9hiPn2teXXTT8M1NEpDFvfXkxMaJRld0=";
+  }
diff --git a/tests/build-remote-content-addressed-fixed.sh b/tests/build-remote-content-addressed-fixed.sh
new file mode 100644
index 000000000..1408a19d5
--- /dev/null
+++ b/tests/build-remote-content-addressed-fixed.sh
@@ -0,0 +1,5 @@
+source common.sh
+
+file=build-hook-ca.nix
+
+source build-remote.sh
diff --git a/tests/build-remote-input-addressed.sh b/tests/build-remote-input-addressed.sh
new file mode 100644
index 000000000..b34caa061
--- /dev/null
+++ b/tests/build-remote-input-addressed.sh
@@ -0,0 +1,5 @@
+source common.sh
+
+file=build-hook.nix
+
+source build-remote.sh
diff --git a/tests/build-remote.sh b/tests/build-remote.sh
index 7638f536f..d9048583f 100644
--- a/tests/build-remote.sh
+++ b/tests/build-remote.sh
@@ -1,5 +1,3 @@
-source common.sh
-
 if ! canUseSandbox; then exit; fi
 if ! [[ $busybox =~ busybox ]]; then exit; fi
 
@@ -18,7 +16,7 @@ builders=(
 # Note: ssh://localhost bypasses ssh, directly invoking nix-store as a
 # child process. This allows us to test LegacySSHStore::buildDerivation().
 # ssh-ng://... likewise allows us to test RemoteStore::buildDerivation().
-nix build -L -v -f build-hook.nix -o $TEST_ROOT/result --max-jobs 0 \
+nix build -L -v -f $file -o $TEST_ROOT/result --max-jobs 0 \
   --arg busybox $busybox \
   --store $TEST_ROOT/machine0 \
   --builders "$(join_by '; ' "${builders[@]}")"
diff --git a/tests/local.mk b/tests/local.mk
index 5c77b9bb7..492d6a0fd 100644
--- a/tests/local.mk
+++ b/tests/local.mk
@@ -14,7 +14,8 @@ nix_tests = \
   placeholders.sh nix-shell.sh \
   linux-sandbox.sh \
   build-dry.sh \
-  build-remote.sh \
+  build-remote-input-addressed.sh \
+  build-remote-content-addressed-fixed.sh \
   nar-access.sh \
   structured-attrs.sh \
   fetchGit.sh \

From 07975979aae4e7729ae13ffeb7390d07d71ad4bd Mon Sep 17 00:00:00 2001
From: Carlo Nucera <carlo.nucera@protonmail.com>
Date: Mon, 17 Aug 2020 15:04:54 -0400
Subject: [PATCH 13/18] Comment out fixed content address test

---
 tests/local.mk | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tests/local.mk b/tests/local.mk
index 492d6a0fd..53035da41 100644
--- a/tests/local.mk
+++ b/tests/local.mk
@@ -15,7 +15,6 @@ nix_tests = \
   linux-sandbox.sh \
   build-dry.sh \
   build-remote-input-addressed.sh \
-  build-remote-content-addressed-fixed.sh \
   nar-access.sh \
   structured-attrs.sh \
   fetchGit.sh \
@@ -35,6 +34,7 @@ nix_tests = \
   recursive.sh \
   flakes.sh
   # parallel.sh
+  # build-remote-content-addressed-fixed.sh \
 
 install-tests += $(foreach x, $(nix_tests), tests/$(x))
 

From f36793c7b950001f80291e162b34f1f9f2152fa0 Mon Sep 17 00:00:00 2001
From: Ryan Mulligan <ryan@ryantm.com>
Date: Wed, 19 Aug 2020 20:31:01 -0700
Subject: [PATCH 14/18] fix spelling

---
 src/nix/develop.cc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/nix/develop.cc b/src/nix/develop.cc
index 434088da7..9aaa80822 100644
--- a/src/nix/develop.cc
+++ b/src/nix/develop.cc
@@ -246,7 +246,7 @@ struct CmdDevelop : Common, MixEnvironment
         addFlag({
             .longName = "command",
             .shortName = 'c',
-            .description = "command and arguments to be executed insted of an interactive shell",
+            .description = "command and arguments to be executed instead of an interactive shell",
             .labels = {"command", "args"},
             .handler = {[&](std::vector<std::string> ss) {
                 if (ss.empty()) throw UsageError("--command requires at least one argument");

From 8ce88adad9728142e1f11f7e00d2f31925f8fdd1 Mon Sep 17 00:00:00 2001
From: Rok Garbas <rok@garbas.si>
Date: Thu, 20 Aug 2020 13:21:22 +0200
Subject: [PATCH 15/18] set Content-Type to "text/plain" for install script

fixes #3947
---
 maintainers/upload-release.pl | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/maintainers/upload-release.pl b/maintainers/upload-release.pl
index 91ae896d6..6f3882a12 100755
--- a/maintainers/upload-release.pl
+++ b/maintainers/upload-release.pl
@@ -117,7 +117,16 @@ for my $fn (glob "$tmpDir/*") {
     my $dstKey = "$releaseDir/" . $name;
     unless (defined $releasesBucket->head_key($dstKey)) {
         print STDERR "uploading $fn to s3://$releasesBucketName/$dstKey...\n";
-        $releasesBucket->add_key_filename($dstKey, $fn)
+
+        my $configuration = ();
+        $configuration->{content_type} = "application/octet-stream";
+
+        if ($fn =~ /.sha256|.asc|install/) {
+            # Text files
+            $configuration->{content_type} = "text/plain";
+        }
+
+        $releasesBucket->add_key_filename($dstKey, $fn, $configuration)
             or die $releasesBucket->err . ": " . $releasesBucket->errstr;
     }
 }

From 9a9d834dc7bde0a4eafa2f7412e7a2f0df8c3262 Mon Sep 17 00:00:00 2001
From: John Ericson <John.Ericson@Obsidian.Systems>
Date: Thu, 20 Aug 2020 14:12:51 +0000
Subject: [PATCH 16/18] Rename drv output querying functions

 - `queryDerivationOutputMapAssumeTotal` -> `queryPartialDerivationOutputMap`
 - `queryDerivationOutputMapAssumeTotal` -> `queryDerivationOutputMap`
---
 src/libstore/build.cc        | 4 ++--
 src/libstore/daemon.cc       | 2 +-
 src/libstore/local-store.cc  | 2 +-
 src/libstore/local-store.hh  | 2 +-
 src/libstore/remote-store.cc | 2 +-
 src/libstore/remote-store.hh | 2 +-
 src/libstore/store-api.cc    | 6 +++---
 src/libstore/store-api.hh    | 6 +++---
 src/nix-env/nix-env.cc       | 2 +-
 9 files changed, 14 insertions(+), 14 deletions(-)

diff --git a/src/libstore/build.cc b/src/libstore/build.cc
index b47aeff3b..5592b32eb 100644
--- a/src/libstore/build.cc
+++ b/src/libstore/build.cc
@@ -2756,11 +2756,11 @@ struct RestrictedStore : public LocalFSStore
     void queryReferrers(const StorePath & path, StorePathSet & referrers) override
     { }
 
-    std::map<std::string, std::optional<StorePath>> queryDerivationOutputMap(const StorePath & path) override
+    std::map<std::string, std::optional<StorePath>> queryPartialDerivationOutputMap(const StorePath & path) override
     {
         if (!goal.isAllowed(path))
             throw InvalidPath("cannot query output map for unknown path '%s' in recursive Nix", printStorePath(path));
-        return next->queryDerivationOutputMap(path);
+        return next->queryPartialDerivationOutputMap(path);
     }
 
     std::optional<StorePath> queryPathFromHashPart(const std::string & hashPart) override
diff --git a/src/libstore/daemon.cc b/src/libstore/daemon.cc
index 9503915eb..0580101a2 100644
--- a/src/libstore/daemon.cc
+++ b/src/libstore/daemon.cc
@@ -325,7 +325,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
     case wopQueryDerivationOutputMap: {
         auto path = store->parseStorePath(readString(from));
         logger->startWork();
-        auto outputs = store->queryDerivationOutputMap(path);
+        auto outputs = store->queryPartialDerivationOutputMap(path);
         logger->stopWork();
         worker_proto::write(*store, to, outputs);
         break;
diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc
index e96091aae..218b56861 100644
--- a/src/libstore/local-store.cc
+++ b/src/libstore/local-store.cc
@@ -782,7 +782,7 @@ StorePathSet LocalStore::queryValidDerivers(const StorePath & path)
 }
 
 
-std::map<std::string, std::optional<StorePath>> LocalStore::queryDerivationOutputMap(const StorePath & path)
+std::map<std::string, std::optional<StorePath>> LocalStore::queryPartialDerivationOutputMap(const StorePath & path)
 {
     std::map<std::string, std::optional<StorePath>> outputs;
     BasicDerivation drv = readDerivation(path);
diff --git a/src/libstore/local-store.hh b/src/libstore/local-store.hh
index 627b1f557..5af12c2b2 100644
--- a/src/libstore/local-store.hh
+++ b/src/libstore/local-store.hh
@@ -133,7 +133,7 @@ public:
 
     StorePathSet queryValidDerivers(const StorePath & path) override;
 
-    std::map<std::string, std::optional<StorePath>> queryDerivationOutputMap(const StorePath & path) override;
+    std::map<std::string, std::optional<StorePath>> queryPartialDerivationOutputMap(const StorePath & path) override;
 
     std::optional<StorePath> queryPathFromHashPart(const std::string & hashPart) override;
 
diff --git a/src/libstore/remote-store.cc b/src/libstore/remote-store.cc
index 005415666..3ee907d1a 100644
--- a/src/libstore/remote-store.cc
+++ b/src/libstore/remote-store.cc
@@ -474,7 +474,7 @@ StorePathSet RemoteStore::queryDerivationOutputs(const StorePath & path)
 }
 
 
-std::map<std::string, std::optional<StorePath>> RemoteStore::queryDerivationOutputMap(const StorePath & path)
+std::map<std::string, std::optional<StorePath>> RemoteStore::queryPartialDerivationOutputMap(const StorePath & path)
 {
     auto conn(getConnection());
     conn->to << wopQueryDerivationOutputMap << printStorePath(path);
diff --git a/src/libstore/remote-store.hh b/src/libstore/remote-store.hh
index 4b093ad3b..b319e774b 100644
--- a/src/libstore/remote-store.hh
+++ b/src/libstore/remote-store.hh
@@ -51,7 +51,7 @@ public:
 
     StorePathSet queryDerivationOutputs(const StorePath & path) override;
 
-    std::map<std::string, std::optional<StorePath>> queryDerivationOutputMap(const StorePath & path) override;
+    std::map<std::string, std::optional<StorePath>> queryPartialDerivationOutputMap(const StorePath & path) override;
     std::optional<StorePath> queryPathFromHashPart(const std::string & hashPart) override;
 
     StorePathSet querySubstitutablePaths(const StorePathSet & paths) override;
diff --git a/src/libstore/store-api.cc b/src/libstore/store-api.cc
index e66c04df4..7e016ac2e 100644
--- a/src/libstore/store-api.cc
+++ b/src/libstore/store-api.cc
@@ -357,8 +357,8 @@ bool Store::PathInfoCacheValue::isKnownNow()
     return std::chrono::steady_clock::now() < time_point + ttl;
 }
 
-OutputPathMap Store::queryDerivationOutputMapAssumeTotal(const StorePath & path) {
-    auto resp = queryDerivationOutputMap(path);
+OutputPathMap Store::queryDerivationOutputMap(const StorePath & path) {
+    auto resp = queryPartialDerivationOutputMap(path);
     OutputPathMap result;
     for (auto & [outName, optOutPath] : resp) {
         if (!optOutPath)
@@ -370,7 +370,7 @@ OutputPathMap Store::queryDerivationOutputMapAssumeTotal(const StorePath & path)
 
 StorePathSet Store::queryDerivationOutputs(const StorePath & path)
 {
-    auto outputMap = this->queryDerivationOutputMapAssumeTotal(path);
+    auto outputMap = this->queryDerivationOutputMap(path);
     StorePathSet outputPaths;
     for (auto & i: outputMap) {
         outputPaths.emplace(std::move(i.second));
diff --git a/src/libstore/store-api.hh b/src/libstore/store-api.hh
index 9003ab541..68d66be7c 100644
--- a/src/libstore/store-api.hh
+++ b/src/libstore/store-api.hh
@@ -343,12 +343,12 @@ public:
     /* Query the mapping outputName => outputPath for the given derivation. All
        outputs are mentioned so ones mising the mapping are mapped to
        `std::nullopt`.  */
-    virtual std::map<std::string, std::optional<StorePath>> queryDerivationOutputMap(const StorePath & path)
-    { unsupported("queryDerivationOutputMap"); }
+    virtual std::map<std::string, std::optional<StorePath>> queryPartialDerivationOutputMap(const StorePath & path)
+    { unsupported("queryPartialDerivationOutputMap"); }
 
     /* Query the mapping outputName=>outputPath for the given derivation.
        Assume every output has a mapping and throw an exception otherwise. */
-    OutputPathMap queryDerivationOutputMapAssumeTotal(const StorePath & path);
+    OutputPathMap queryDerivationOutputMap(const StorePath & path);
 
     /* Query the full store path given the hash part of a valid store
        path, or empty if the path doesn't exist. */
diff --git a/src/nix-env/nix-env.cc b/src/nix-env/nix-env.cc
index d36804658..ddd036070 100644
--- a/src/nix-env/nix-env.cc
+++ b/src/nix-env/nix-env.cc
@@ -381,7 +381,7 @@ static void queryInstSources(EvalState & state,
 
                 if (path.isDerivation()) {
                     elem.setDrvPath(state.store->printStorePath(path));
-                    auto outputs = state.store->queryDerivationOutputMapAssumeTotal(path);
+                    auto outputs = state.store->queryDerivationOutputMap(path);
                     elem.setOutPath(state.store->printStorePath(outputs.at("out")));
                     if (name.size() >= drvExtension.size() &&
                         string(name, name.size() - drvExtension.size()) == drvExtension)

From 422affe1027058c7807adec0dac1aee09b65ec4c Mon Sep 17 00:00:00 2001
From: John Ericson <John.Ericson@Obsidian.Systems>
Date: Fri, 21 Aug 2020 19:19:14 +0000
Subject: [PATCH 17/18] tabs -> spaces

Sorry I let the tab sneak in there in the first place.
---
 src/libstore/remote-store.cc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/libstore/remote-store.cc b/src/libstore/remote-store.cc
index c2c6eff66..adba17c5e 100644
--- a/src/libstore/remote-store.cc
+++ b/src/libstore/remote-store.cc
@@ -74,7 +74,7 @@ void write(const Store & store, Sink & out, const StorePath & storePath)
 template<>
 std::optional<StorePath> read(const Store & store, Source & from, Phantom<std::optional<StorePath>> _)
 {
-	auto s = readString(from);
+    auto s = readString(from);
     return s == "" ? std::optional<StorePath> {} : store.parseStorePath(s);
 }
 

From 35e6288be1a2384a29caccd64d88a6b623e6c033 Mon Sep 17 00:00:00 2001
From: John Ericson <John.Ericson@Obsidian.Systems>
Date: Sun, 23 Aug 2020 15:00:25 +0000
Subject: [PATCH 18/18] `writeDerivation` just needs a plain store reference

---
 src/libexpr/primops.cc      | 2 +-
 src/libstore/derivations.cc | 8 ++++----
 src/libstore/derivations.hh | 2 +-
 src/nix/develop.cc          | 2 +-
 4 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc
index 30f4c3529..dcc34407b 100644
--- a/src/libexpr/primops.cc
+++ b/src/libexpr/primops.cc
@@ -839,7 +839,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
     }
 
     /* Write the resulting term into the Nix store directory. */
-    auto drvPath = writeDerivation(state.store, drv, state.repair);
+    auto drvPath = writeDerivation(*state.store, drv, state.repair);
     auto drvPathS = state.store->printStorePath(drvPath);
 
     printMsg(lvlChatty, "instantiated '%1%' -> '%2%'", drvName, drvPathS);
diff --git a/src/libstore/derivations.cc b/src/libstore/derivations.cc
index a9fed2564..43bc61e55 100644
--- a/src/libstore/derivations.cc
+++ b/src/libstore/derivations.cc
@@ -61,7 +61,7 @@ bool BasicDerivation::isBuiltin() const
 }
 
 
-StorePath writeDerivation(ref<Store> store,
+StorePath writeDerivation(Store & store,
     const Derivation & drv, RepairFlag repair)
 {
     auto references = drv.inputSrcs;
@@ -71,10 +71,10 @@ StorePath writeDerivation(ref<Store> store,
        (that can be missing (of course) and should not necessarily be
        held during a garbage collection). */
     auto suffix = std::string(drv.name) + drvExtension;
-    auto contents = drv.unparse(*store, false);
+    auto contents = drv.unparse(store, false);
     return settings.readOnlyMode
-        ? store->computeStorePathForText(suffix, contents, references)
-        : store->addTextToStore(suffix, contents, references, repair);
+        ? store.computeStorePathForText(suffix, contents, references)
+        : store.addTextToStore(suffix, contents, references, repair);
 }
 
 
diff --git a/src/libstore/derivations.hh b/src/libstore/derivations.hh
index 3aae30ab2..9656a32f2 100644
--- a/src/libstore/derivations.hh
+++ b/src/libstore/derivations.hh
@@ -146,7 +146,7 @@ class Store;
 enum RepairFlag : bool { NoRepair = false, Repair = true };
 
 /* Write a derivation to the Nix store, and return its path. */
-StorePath writeDerivation(ref<Store> store,
+StorePath writeDerivation(Store & store,
     const Derivation & drv, RepairFlag repair = NoRepair);
 
 /* Read a derivation from a file. */
diff --git a/src/nix/develop.cc b/src/nix/develop.cc
index 9aaa80822..8a14e45c9 100644
--- a/src/nix/develop.cc
+++ b/src/nix/develop.cc
@@ -138,7 +138,7 @@ StorePath getDerivationEnvironment(ref<Store> store, const StorePath & drvPath)
                 .path = shellOutPath
             } });
     drv.env["out"] = store->printStorePath(shellOutPath);
-    auto shellDrvPath2 = writeDerivation(store, drv);
+    auto shellDrvPath2 = writeDerivation(*store, drv);
 
     /* Build the derivation. */
     store->buildPaths({{shellDrvPath2}});