about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/build-remote/build-remote.cc19
-rw-r--r--src/build-remote/local.mk9
-rw-r--r--src/libexpr/lexer.l33
-rw-r--r--src/libstore/build.cc224
-rw-r--r--src/libstore/globals.hh6
-rw-r--r--src/libstore/legacy-ssh-store.cc6
-rw-r--r--src/libstore/local-store.cc6
-rw-r--r--src/libstore/local-store.hh2
-rw-r--r--src/libstore/remote-store.cc7
-rw-r--r--src/libstore/remote-store.hh4
-rw-r--r--src/libstore/s3-binary-cache-store.cc88
-rw-r--r--src/libstore/store-api.cc10
-rw-r--r--src/libstore/store-api.hh6
-rw-r--r--src/nix-build/local.mk9
-rwxr-xr-xsrc/nix-build/nix-build.cc14
-rw-r--r--src/nix-channel/local.mk7
-rwxr-xr-xsrc/nix-channel/nix-channel.cc16
-rw-r--r--src/nix-collect-garbage/local.mk7
-rw-r--r--src/nix-collect-garbage/nix-collect-garbage.cc15
-rw-r--r--src/nix-copy-closure/local.mk7
-rwxr-xr-xsrc/nix-copy-closure/nix-copy-closure.cc13
-rw-r--r--src/nix-daemon/local.mk13
-rw-r--r--src/nix-daemon/nix-daemon.cc15
-rw-r--r--src/nix-env/local.mk7
-rw-r--r--src/nix-env/nix-env.cc14
-rw-r--r--src/nix-instantiate/local.mk7
-rw-r--r--src/nix-instantiate/nix-instantiate.cc16
-rw-r--r--src/nix-prefetch-url/local.mk7
-rw-r--r--src/nix-prefetch-url/nix-prefetch-url.cc14
-rw-r--r--src/nix-store/graphml.cc90
-rw-r--r--src/nix-store/graphml.hh (renamed from src/nix-store/xmlgraph.hh)2
-rw-r--r--src/nix-store/local.mk9
-rw-r--r--src/nix-store/nix-store.cc23
-rw-r--r--src/nix-store/xmlgraph.cc66
-rw-r--r--src/nix/doctor.cc124
-rw-r--r--src/nix/local.mk21
-rw-r--r--src/nix/main.cc6
37 files changed, 608 insertions, 334 deletions
diff --git a/src/build-remote/build-remote.cc b/src/build-remote/build-remote.cc
index 38dbe3e58b26..abf3669b5b35 100644
--- a/src/build-remote/build-remote.cc
+++ b/src/build-remote/build-remote.cc
@@ -17,6 +17,7 @@
 #include "store-api.hh"
 #include "derivations.hh"
 #include "local-store.hh"
+#include "legacy.hh"
 
 using namespace nix;
 using std::cin;
@@ -37,11 +38,9 @@ static AutoCloseFD openSlotLock(const Machine & m, unsigned long long slot)
     return openLockFile(fmt("%s/%s-%d", currentLoad, escapeUri(m.storeUri), slot), true);
 }
 
-int main (int argc, char * * argv)
+static int _main(int argc, char * * argv)
 {
-    return handleExceptions(argv[0], [&]() {
-        initNix();
-
+    {
         logger = makeJSONLogger(*logger);
 
         /* Ensure we don't get any SSH passphrase or host key popups. */
@@ -80,7 +79,7 @@ int main (int argc, char * * argv)
 
         if (machines.empty()) {
             std::cerr << "# decline-permanently\n";
-            return;
+            return 0;
         }
 
         string drvPath;
@@ -90,8 +89,8 @@ int main (int argc, char * * argv)
 
             try {
                 auto s = readString(source);
-                if (s != "try") return;
-            } catch (EndOfFile &) { return; }
+                if (s != "try") return 0;
+            } catch (EndOfFile &) { return 0; }
 
             auto amWilling = readInt(source);
             auto neededSystem = readString(source);
@@ -253,6 +252,8 @@ connected:
             copyPaths(ref<Store>(sshStore), store, missing, NoRepair, NoCheckSigs, NoSubstitute);
         }
 
-        return;
-    });
+        return 0;
+    }
 }
+
+static RegisterLegacyCommand s1("build-remote", _main);
diff --git a/src/build-remote/local.mk b/src/build-remote/local.mk
deleted file mode 100644
index 50b0409d1886..000000000000
--- a/src/build-remote/local.mk
+++ /dev/null
@@ -1,9 +0,0 @@
-programs += build-remote
-
-build-remote_DIR := $(d)
-
-build-remote_INSTALL_DIR := $(libexecdir)/nix
-
-build-remote_LIBS = libmain libformat libstore libutil
-
-build-remote_SOURCES := $(d)/build-remote.cc
diff --git a/src/libexpr/lexer.l b/src/libexpr/lexer.l
index a052447d3dce..c34e5c383923 100644
--- a/src/libexpr/lexer.l
+++ b/src/libexpr/lexer.l
@@ -6,9 +6,9 @@
 %option nounput noyy_top_state
 
 
+%s DEFAULT
 %x STRING
 %x IND_STRING
-%x INSIDE_DOLLAR_CURLY
 
 
 %{
@@ -99,8 +99,6 @@ URI         [a-zA-Z][a-zA-Z0-9\+\-\.]*\:[a-zA-Z0-9\%\/\?\:\@\&\=\+\$\,\-\_\.\!\~
 
 %%
 
-<INITIAL,INSIDE_DOLLAR_CURLY>{
-
 
 if          { return IF; }
 then        { return THEN; }
@@ -140,17 +138,19 @@ or          { return OR_KW; }
               return FLOAT;
             }
 
-\$\{        { PUSH_STATE(INSIDE_DOLLAR_CURLY); return DOLLAR_CURLY; }
-}
+\$\{        { PUSH_STATE(DEFAULT); return DOLLAR_CURLY; }
 
-\}                           { return '}'; }
-<INSIDE_DOLLAR_CURLY>\}      { POP_STATE(); return '}'; }
-\{                           { return '{'; }
-<INSIDE_DOLLAR_CURLY>\{      { PUSH_STATE(INSIDE_DOLLAR_CURLY); return '{'; }
+\}          { /* State INITIAL only exists at the bottom of the stack and is
+                 used as a marker. DEFAULT replaces it everywhere else.
+                 Popping when in INITIAL state causes an empty stack exception,
+                 so don't */
+              if (YYSTATE != INITIAL)
+                POP_STATE();
+              return '}';
+            }
+\{          { PUSH_STATE(DEFAULT); return '{'; }
 
-<INITIAL,INSIDE_DOLLAR_CURLY>\" {
-                PUSH_STATE(STRING); return '"';
-              }
+\"          { PUSH_STATE(STRING); return '"'; }
 <STRING>([^\$\"\\]|\$[^\{\"\\]|\\{ANY}|\$\\{ANY})*\$/\" |
 <STRING>([^\$\"\\]|\$[^\{\"\\]|\\{ANY}|\$\\{ANY})+ {
                 /* It is impossible to match strings ending with '$' with one
@@ -159,7 +159,7 @@ or          { return OR_KW; }
                 yylval->e = unescapeStr(data->symbols, yytext, yyleng);
                 return STR;
               }
-<STRING>\$\{  { PUSH_STATE(INSIDE_DOLLAR_CURLY); return DOLLAR_CURLY; }
+<STRING>\$\{  { PUSH_STATE(DEFAULT); return DOLLAR_CURLY; }
 <STRING>\"    { POP_STATE(); return '"'; }
 <STRING>\$|\\|\$\\ {
                 /* This can only occur when we reach EOF, otherwise the above
@@ -169,7 +169,7 @@ or          { return OR_KW; }
                 return STR;
               }
 
-<INITIAL,INSIDE_DOLLAR_CURLY>\'\'(\ *\n)?     { PUSH_STATE(IND_STRING); return IND_STRING_OPEN; }
+\'\'(\ *\n)?     { PUSH_STATE(IND_STRING); return IND_STRING_OPEN; }
 <IND_STRING>([^\$\']|\$[^\{\']|\'[^\'\$])+ {
                    yylval->e = new ExprIndStr(yytext);
                    return IND_STR;
@@ -187,14 +187,13 @@ or          { return OR_KW; }
                    yylval->e = unescapeStr(data->symbols, yytext + 2, yyleng - 2);
                    return IND_STR;
                  }
-<IND_STRING>\$\{ { PUSH_STATE(INSIDE_DOLLAR_CURLY); return DOLLAR_CURLY; }
+<IND_STRING>\$\{ { PUSH_STATE(DEFAULT); return DOLLAR_CURLY; }
 <IND_STRING>\'\' { POP_STATE(); return IND_STRING_CLOSE; }
 <IND_STRING>\'   {
                    yylval->e = new ExprIndStr("'");
                    return IND_STR;
                  }
 
-<INITIAL,INSIDE_DOLLAR_CURLY>{
 
 {PATH}      { if (yytext[yyleng-1] == '/')
                   throw ParseError("path '%s' has a trailing slash", yytext);
@@ -219,7 +218,5 @@ or          { return OR_KW; }
               return (unsigned char) yytext[0];
             }
 
-}
-
 %%
 
diff --git a/src/libstore/build.cc b/src/libstore/build.cc
index 0073b9b727ec..676ad5856b13 100644
--- a/src/libstore/build.cc
+++ b/src/libstore/build.cc
@@ -21,6 +21,7 @@
 #include <future>
 #include <chrono>
 #include <regex>
+#include <queue>
 
 #include <limits.h>
 #include <sys/time.h>
@@ -857,7 +858,7 @@ private:
        building multiple times. Since this contains the hash, it
        allows us to compare whether two rounds produced the same
        result. */
-    ValidPathInfos prevInfos;
+    std::map<Path, ValidPathInfo> prevInfos;
 
     const uid_t sandboxUid = 1000;
     const gid_t sandboxGid = 100;
@@ -938,6 +939,11 @@ private:
        as valid. */
     void registerOutputs();
 
+    /* Check that an output meets the requirements specified by the
+       'outputChecks' attribute (or the legacy
+       '{allowed,disallowed}{References,Requisites}' attributes). */
+    void checkOutputs(const std::map<std::string, ValidPathInfo> & outputs);
+
     /* Open a log file and a pipe to it. */
     Path openLogFile();
 
@@ -3010,7 +3016,7 @@ void DerivationGoal::registerOutputs()
         if (allValid) return;
     }
 
-    ValidPathInfos infos;
+    std::map<std::string, ValidPathInfo> infos;
 
     /* Set of inodes seen during calls to canonicalisePathMetaData()
        for this build's outputs.  This needs to be shared between
@@ -3195,49 +3201,6 @@ void DerivationGoal::registerOutputs()
                 debug(format("referenced input: '%1%'") % i);
         }
 
-        /* Enforce `allowedReferences' and friends. */
-        auto checkRefs = [&](const string & attrName, bool allowed, bool recursive) {
-            auto value = parsedDrv->getStringsAttr(attrName);
-            if (!value) return;
-
-            PathSet spec = parseReferenceSpecifiers(worker.store, *drv, *value);
-
-            PathSet used;
-            if (recursive) {
-                /* Our requisites are the union of the closures of our references. */
-                for (auto & i : references)
-                    /* Don't call computeFSClosure on ourselves. */
-                    if (path != i)
-                        worker.store.computeFSClosure(i, used);
-            } else
-                used = references;
-
-            PathSet badPaths;
-
-            for (auto & i : used)
-                if (allowed) {
-                    if (spec.find(i) == spec.end())
-                        badPaths.insert(i);
-                } else {
-                    if (spec.find(i) != spec.end())
-                        badPaths.insert(i);
-                }
-
-            if (!badPaths.empty()) {
-                string badPathsStr;
-                for (auto & i : badPaths) {
-                    badPathsStr += "\n\t";
-                    badPathsStr += i;
-                }
-                throw BuildError(format("output '%1%' is not allowed to refer to the following paths:%2%") % actualPath % badPathsStr);
-            }
-        };
-
-        checkRefs("allowedReferences", true, false);
-        checkRefs("allowedRequisites", true, true);
-        checkRefs("disallowedReferences", false, false);
-        checkRefs("disallowedRequisites", false, true);
-
         if (curRound == nrRounds) {
             worker.store.optimisePath(actualPath); // FIXME: combine with scanForReferences()
             worker.markContentsGood(path);
@@ -3253,11 +3216,14 @@ void DerivationGoal::registerOutputs()
 
         if (!info.references.empty()) info.ca.clear();
 
-        infos.push_back(info);
+        infos[i.first] = info;
     }
 
     if (buildMode == bmCheck) return;
 
+    /* Apply output checks. */
+    checkOutputs(infos);
+
     /* Compare the result with the previous round, and report which
        path is different, if any.*/
     if (curRound > 1 && prevInfos != infos) {
@@ -3265,16 +3231,16 @@ void DerivationGoal::registerOutputs()
         for (auto i = prevInfos.begin(), j = infos.begin(); i != prevInfos.end(); ++i, ++j)
             if (!(*i == *j)) {
                 result.isNonDeterministic = true;
-                Path prev = i->path + checkSuffix;
+                Path prev = i->second.path + checkSuffix;
                 bool prevExists = keepPreviousRound && pathExists(prev);
                 auto msg = prevExists
-                    ? fmt("output '%1%' of '%2%' differs from '%3%' from previous round", i->path, drvPath, prev)
-                    : fmt("output '%1%' of '%2%' differs from previous round", i->path, drvPath);
+                    ? fmt("output '%1%' of '%2%' differs from '%3%' from previous round", i->second.path, drvPath, prev)
+                    : fmt("output '%1%' of '%2%' differs from previous round", i->second.path, drvPath);
 
                 auto diffHook = settings.diffHook;
                 if (prevExists && diffHook != "" && runDiffHook) {
                     try {
-                        auto diff = runProgram(diffHook, true, {prev, i->path});
+                        auto diff = runProgram(diffHook, true, {prev, i->second.path});
                         if (diff != "")
                             printError(chomp(diff));
                     } catch (Error & error) {
@@ -3319,7 +3285,11 @@ void DerivationGoal::registerOutputs()
     /* Register each output path as valid, and register the sets of
        paths referenced by each of them.  If there are cycles in the
        outputs, this will fail. */
-    worker.store.registerValidPaths(infos);
+    {
+        ValidPathInfos infos2;
+        for (auto & i : infos) infos2.push_back(i.second);
+        worker.store.registerValidPaths(infos2);
+    }
 
     /* In case of a fixed-output derivation hash mismatch, throw an
        exception now that we have registered the output as valid. */
@@ -3328,6 +3298,158 @@ void DerivationGoal::registerOutputs()
 }
 
 
+void DerivationGoal::checkOutputs(const std::map<Path, ValidPathInfo> & outputs)
+{
+    std::map<Path, const ValidPathInfo &> outputsByPath;
+    for (auto & output : outputs)
+        outputsByPath.emplace(output.second.path, output.second);
+
+    for (auto & output : outputs) {
+        auto & outputName = output.first;
+        auto & info = output.second;
+
+        struct Checks
+        {
+            bool ignoreSelfRefs = false;
+            std::experimental::optional<uint64_t> maxSize, maxClosureSize;
+            std::experimental::optional<Strings> allowedReferences, allowedRequisites, disallowedReferences, disallowedRequisites;
+        };
+
+        /* Compute the closure and closure size of some output. This
+           is slightly tricky because some of its references (namely
+           other outputs) may not be valid yet. */
+        auto getClosure = [&](const Path & path)
+        {
+            uint64_t closureSize = 0;
+            PathSet pathsDone;
+            std::queue<Path> pathsLeft;
+            pathsLeft.push(path);
+
+            while (!pathsLeft.empty()) {
+                auto path = pathsLeft.front();
+                pathsLeft.pop();
+                if (!pathsDone.insert(path).second) continue;
+
+                auto i = outputsByPath.find(path);
+                if (i != outputsByPath.end()) {
+                    closureSize += i->second.narSize;
+                    for (auto & ref : i->second.references)
+                        pathsLeft.push(ref);
+                } else {
+                    auto info = worker.store.queryPathInfo(path);
+                    closureSize += info->narSize;
+                    for (auto & ref : info->references)
+                        pathsLeft.push(ref);
+                }
+            }
+
+            return std::make_pair(pathsDone, closureSize);
+        };
+
+        auto applyChecks = [&](const Checks & checks)
+        {
+            if (checks.maxSize && info.narSize > *checks.maxSize)
+                throw BuildError("path '%s' is too large at %d bytes; limit is %d bytes",
+                    info.path, info.narSize, *checks.maxSize);
+
+            if (checks.maxClosureSize) {
+                uint64_t closureSize = getClosure(info.path).second;
+                if (closureSize > *checks.maxClosureSize)
+                    throw BuildError("closure of path '%s' is too large at %d bytes; limit is %d bytes",
+                        info.path, closureSize, *checks.maxClosureSize);
+            }
+
+            auto checkRefs = [&](const std::experimental::optional<Strings> & value, bool allowed, bool recursive)
+            {
+                if (!value) return;
+
+                PathSet spec = parseReferenceSpecifiers(worker.store, *drv, *value);
+
+                PathSet used = recursive ? getClosure(info.path).first : info.references;
+
+                if (recursive && checks.ignoreSelfRefs)
+                    used.erase(info.path);
+
+                PathSet badPaths;
+
+                for (auto & i : used)
+                    if (allowed) {
+                        if (!spec.count(i))
+                            badPaths.insert(i);
+                    } else {
+                        if (spec.count(i))
+                            badPaths.insert(i);
+                    }
+
+                if (!badPaths.empty()) {
+                    string badPathsStr;
+                    for (auto & i : badPaths) {
+                        badPathsStr += "\n  ";
+                        badPathsStr += i;
+                    }
+                    throw BuildError("output '%s' is not allowed to refer to the following paths:%s", info.path, badPathsStr);
+                }
+            };
+
+            checkRefs(checks.allowedReferences, true, false);
+            checkRefs(checks.allowedRequisites, true, true);
+            checkRefs(checks.disallowedReferences, false, false);
+            checkRefs(checks.disallowedRequisites, false, true);
+        };
+
+        if (auto structuredAttrs = parsedDrv->getStructuredAttrs()) {
+            auto outputChecks = structuredAttrs->find("outputChecks");
+            if (outputChecks != structuredAttrs->end()) {
+                auto output = outputChecks->find(outputName);
+
+                if (output != outputChecks->end()) {
+                    Checks checks;
+
+                    auto maxSize = output->find("maxSize");
+                    if (maxSize != output->end())
+                        checks.maxSize = maxSize->get<uint64_t>();
+
+                    auto maxClosureSize = output->find("maxClosureSize");
+                    if (maxClosureSize != output->end())
+                        checks.maxClosureSize = maxClosureSize->get<uint64_t>();
+
+                    auto get = [&](const std::string & name) -> std::experimental::optional<Strings> {
+                        auto i = output->find(name);
+                        if (i != output->end()) {
+                            Strings res;
+                            for (auto j = i->begin(); j != i->end(); ++j) {
+                                if (!j->is_string())
+                                    throw Error("attribute '%s' of derivation '%s' must be a list of strings", name, drvPath);
+                                res.push_back(j->get<std::string>());
+                            }
+                            checks.disallowedRequisites = res;
+                            return res;
+                        }
+                        return {};
+                    };
+
+                    checks.allowedReferences = get("allowedReferences");
+                    checks.allowedRequisites = get("allowedRequisites");
+                    checks.disallowedReferences = get("disallowedReferences");
+                    checks.disallowedRequisites = get("disallowedRequisites");
+
+                    applyChecks(checks);
+                }
+            }
+        } else {
+            // legacy non-structured-attributes case
+            Checks checks;
+            checks.ignoreSelfRefs = true;
+            checks.allowedReferences = parsedDrv->getStringsAttr("allowedReferences");
+            checks.allowedRequisites = parsedDrv->getStringsAttr("allowedRequisites");
+            checks.disallowedReferences = parsedDrv->getStringsAttr("disallowedReferences");
+            checks.disallowedRequisites = parsedDrv->getStringsAttr("disallowedRequisites");
+            applyChecks(checks);
+        }
+    }
+}
+
+
 Path DerivationGoal::openLogFile()
 {
     logSize = 0;
diff --git a/src/libstore/globals.hh b/src/libstore/globals.hh
index cf4ae63cdc2f..6b3e204536f1 100644
--- a/src/libstore/globals.hh
+++ b/src/libstore/globals.hh
@@ -82,9 +82,9 @@ public:
     /* Whether to show build log output in real time. */
     bool verboseBuild = true;
 
-    /* If verboseBuild is false, the number of lines of the tail of
-       the log to show if a build fails. */
-    size_t logLines = 10;
+    Setting<size_t> logLines{this, 10, "log-lines",
+        "If verbose-build is false, the number of lines of the tail of "
+        "the log to show if a build fails."};
 
     MaxBuildJobsSetting maxBuildJobs{this, 1, "max-jobs",
         "Maximum number of parallel build jobs. \"auto\" means use number of cores.",
diff --git a/src/libstore/legacy-ssh-store.cc b/src/libstore/legacy-ssh-store.cc
index 88d2574e86ef..26e1851981db 100644
--- a/src/libstore/legacy-ssh-store.cc
+++ b/src/libstore/legacy-ssh-store.cc
@@ -303,6 +303,12 @@ struct LegacySSHStore : public Store
     {
         auto conn(connections->get());
     }
+
+    unsigned int getProtocol() override
+    {
+        auto conn(connections->get());
+        return conn->remoteVersion;
+    }
 };
 
 static RegisterStoreImplementation regStore([](
diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc
index 197b9d78995b..216f3417c4a8 100644
--- a/src/libstore/local-store.cc
+++ b/src/libstore/local-store.cc
@@ -1338,6 +1338,12 @@ void LocalStore::verifyPath(const Path & path, const PathSet & store,
 }
 
 
+unsigned int LocalStore::getProtocol()
+{
+    return PROTOCOL_VERSION;
+}
+
+
 #if defined(FS_IOC_SETFLAGS) && defined(FS_IOC_GETFLAGS) && defined(FS_IMMUTABLE_FL)
 
 static void makeMutable(const Path & path)
diff --git a/src/libstore/local-store.hh b/src/libstore/local-store.hh
index 746bdbeed793..fce963433a5e 100644
--- a/src/libstore/local-store.hh
+++ b/src/libstore/local-store.hh
@@ -209,6 +209,8 @@ public:
 
     void registerValidPaths(const ValidPathInfos & infos);
 
+    unsigned int getProtocol() override;
+
     void vacuumDB();
 
     /* Repair the contents of the given path by redownloading it using
diff --git a/src/libstore/remote-store.cc b/src/libstore/remote-store.cc
index ef8b0e53b808..def140cfbe18 100644
--- a/src/libstore/remote-store.cc
+++ b/src/libstore/remote-store.cc
@@ -693,6 +693,13 @@ void RemoteStore::connect()
 }
 
 
+unsigned int RemoteStore::getProtocol()
+{
+    auto conn(connections->get());
+    return conn->daemonVersion;
+}
+
+
 void RemoteStore::flushBadConnections()
 {
     connections->flushBad();
diff --git a/src/libstore/remote-store.hh b/src/libstore/remote-store.hh
index 7f9d7d1f56d6..4f554b5980e8 100644
--- a/src/libstore/remote-store.hh
+++ b/src/libstore/remote-store.hh
@@ -98,6 +98,8 @@ public:
 
     void connect() override;
 
+    unsigned int getProtocol() override;
+
     void flushBadConnections();
 
 protected:
@@ -127,7 +129,7 @@ protected:
 
     ConnectionHandle getConnection();
 
-    friend class ConnectionHandle;
+    friend struct ConnectionHandle;
 
 private:
 
diff --git a/src/libstore/s3-binary-cache-store.cc b/src/libstore/s3-binary-cache-store.cc
index ba11ce6bb6de..1f755ba9eee7 100644
--- a/src/libstore/s3-binary-cache-store.cc
+++ b/src/libstore/s3-binary-cache-store.cc
@@ -173,6 +173,8 @@ struct S3BinaryCacheStoreImpl : public S3BinaryCacheStore
     const Setting<std::string> narinfoCompression{this, "", "narinfo-compression", "compression method for .narinfo files"};
     const Setting<std::string> lsCompression{this, "", "ls-compression", "compression method for .ls files"};
     const Setting<std::string> logCompression{this, "", "log-compression", "compression method for log/* files"};
+    const Setting<bool> multipartUpload{
+        this, false, "multipart-upload", "whether to use multi-part uploads"};
     const Setting<uint64_t> bufferSize{
         this, 5 * 1024 * 1024, "buffer-size", "size (in bytes) of each part in multi-part uploads"};
 
@@ -261,48 +263,70 @@ struct S3BinaryCacheStoreImpl : public S3BinaryCacheStore
         static std::shared_ptr<Aws::Utils::Threading::PooledThreadExecutor>
             executor = std::make_shared<Aws::Utils::Threading::PooledThreadExecutor>(maxThreads);
 
-        std::call_once(transferManagerCreated, [&]() {
+        std::call_once(transferManagerCreated, [&]()
+        {
+            if (multipartUpload) {
+                TransferManagerConfiguration transferConfig(executor.get());
+
+                transferConfig.s3Client = s3Helper.client;
+                transferConfig.bufferSize = bufferSize;
+
+                transferConfig.uploadProgressCallback =
+                    [](const TransferManager *transferManager,
+                        const std::shared_ptr<const TransferHandle>
+                        &transferHandle)
+                    {
+                        //FIXME: find a way to properly abort the multipart upload.
+                        //checkInterrupt();
+                        debug("upload progress ('%s'): '%d' of '%d' bytes",
+                            transferHandle->GetKey(),
+                            transferHandle->GetBytesTransferred(),
+                            transferHandle->GetBytesTotalSize());
+                    };
+
+                transferManager = TransferManager::Create(transferConfig);
+            }
+        });
 
-            TransferManagerConfiguration transferConfig(executor.get());
+        auto now1 = std::chrono::steady_clock::now();
 
-            transferConfig.s3Client = s3Helper.client;
-            transferConfig.bufferSize = bufferSize;
+        if (transferManager) {
 
-            transferConfig.uploadProgressCallback =
-                [&](const TransferManager *transferManager,
-                    const std::shared_ptr<const TransferHandle>
-                    &transferHandle)
-                {
-                    //FIXME: find a way to properly abort the multipart upload.
-                    //checkInterrupt();
-                    debug("upload progress ('%s'): '%d' of '%d' bytes",
-                        path,
-                        transferHandle->GetBytesTransferred(),
-                        transferHandle->GetBytesTotalSize());
-                };
+            std::shared_ptr<TransferHandle> transferHandle =
+                transferManager->UploadFile(
+                    stream, bucketName, path, mimeType,
+                    Aws::Map<Aws::String, Aws::String>(),
+                    nullptr, contentEncoding);
 
-            transferManager = TransferManager::Create(transferConfig);
-        });
+            transferHandle->WaitUntilFinished();
 
-        auto now1 = std::chrono::steady_clock::now();
+            if (transferHandle->GetStatus() == TransferStatus::FAILED)
+                throw Error("AWS error: failed to upload 's3://%s/%s': %s",
+                    bucketName, path, transferHandle->GetLastError().GetMessage());
 
-        std::shared_ptr<TransferHandle> transferHandle =
-            transferManager->UploadFile(
-                stream, bucketName, path, mimeType,
-                Aws::Map<Aws::String, Aws::String>(),
-                nullptr, contentEncoding);
+            if (transferHandle->GetStatus() != TransferStatus::COMPLETED)
+                throw Error("AWS error: transfer status of 's3://%s/%s' in unexpected state",
+                    bucketName, path);
 
-        transferHandle->WaitUntilFinished();
+        } else {
 
-        if (transferHandle->GetStatus() == TransferStatus::FAILED)
-            throw Error("AWS error: failed to upload 's3://%s/%s': %s",
-                bucketName, path, transferHandle->GetLastError().GetMessage());
+            auto request =
+                Aws::S3::Model::PutObjectRequest()
+                .WithBucket(bucketName)
+                .WithKey(path);
 
-        if (transferHandle->GetStatus() != TransferStatus::COMPLETED)
-            throw Error("AWS error: transfer status of 's3://%s/%s' in unexpected state",
-                bucketName, path);
+            request.SetContentType(mimeType);
 
-        printTalkative("upload of '%s' completed", path);
+            if (contentEncoding != "")
+                request.SetContentEncoding(contentEncoding);
+
+            auto stream = std::make_shared<istringstream_nocopy>(data);
+
+            request.SetBody(stream);
+
+            auto result = checkAws(fmt("AWS error uploading '%s'", path),
+                s3Helper.client->PutObject(request));
+        }
 
         auto now2 = std::chrono::steady_clock::now();
 
diff --git a/src/libstore/store-api.cc b/src/libstore/store-api.cc
index 92e2685f7f66..dc54c735fdb1 100644
--- a/src/libstore/store-api.cc
+++ b/src/libstore/store-api.cc
@@ -588,15 +588,19 @@ void copyStorePath(ref<Store> srcStore, ref<Store> dstStore,
 
     uint64_t total = 0;
 
-    // FIXME
-#if 0
     if (!info->narHash) {
+        StringSink sink;
+        srcStore->narFromPath({storePath}, sink);
         auto info2 = make_ref<ValidPathInfo>(*info);
         info2->narHash = hashString(htSHA256, *sink.s);
         if (!info->narSize) info2->narSize = sink.s->size();
+        if (info->ultimate) info2->ultimate = false;
         info = info2;
+
+        StringSource source(*sink.s);
+        dstStore->addToStore(*info, source, repair, checkSigs);
+        return;
     }
-#endif
 
     if (info->ultimate) {
         auto info2 = make_ref<ValidPathInfo>(*info);
diff --git a/src/libstore/store-api.hh b/src/libstore/store-api.hh
index 099818ed6f69..106b2be5e6b2 100644
--- a/src/libstore/store-api.hh
+++ b/src/libstore/store-api.hh
@@ -599,6 +599,12 @@ public:
        a notion of connection. Otherwise this is a no-op. */
     virtual void connect() { };
 
+    /* Get the protocol version of this store or it's connection. */
+    virtual unsigned int getProtocol()
+    {
+        return 0;
+    };
+
     /* 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. */
diff --git a/src/nix-build/local.mk b/src/nix-build/local.mk
deleted file mode 100644
index a2d1c91dfd9d..000000000000
--- a/src/nix-build/local.mk
+++ /dev/null
@@ -1,9 +0,0 @@
-programs += nix-build
-
-nix-build_DIR := $(d)
-
-nix-build_SOURCES := $(d)/nix-build.cc
-
-nix-build_LIBS = libmain libexpr libstore libutil libformat
-
-$(eval $(call install-symlink, nix-build, $(bindir)/nix-shell))
diff --git a/src/nix-build/nix-build.cc b/src/nix-build/nix-build.cc
index b78f3d9e424e..618895d387d4 100755
--- a/src/nix-build/nix-build.cc
+++ b/src/nix-build/nix-build.cc
@@ -16,6 +16,7 @@
 #include "get-drvs.hh"
 #include "common-eval-args.hh"
 #include "attr-path.hh"
+#include "legacy.hh"
 
 using namespace nix;
 using namespace std::string_literals;
@@ -66,11 +67,8 @@ std::vector<string> shellwords(const string & s)
     return res;
 }
 
-void mainWrapped(int argc, char * * argv)
+static void _main(int argc, char * * argv)
 {
-    initNix();
-    initGC();
-
     auto dryRun = false;
     auto runEnv = std::regex_search(argv[0], std::regex("nix-shell$"));
     auto pure = false;
@@ -504,9 +502,5 @@ void mainWrapped(int argc, char * * argv)
     }
 }
 
-int main(int argc, char * * argv)
-{
-    return handleExceptions(argv[0], [&]() {
-        return mainWrapped(argc, argv);
-    });
-}
+static RegisterLegacyCommand s1("nix-build", _main);
+static RegisterLegacyCommand s2("nix-shell", _main);
diff --git a/src/nix-channel/local.mk b/src/nix-channel/local.mk
deleted file mode 100644
index c14e8c359ca0..000000000000
--- a/src/nix-channel/local.mk
+++ /dev/null
@@ -1,7 +0,0 @@
-programs += nix-channel
-
-nix-channel_DIR := $(d)
-
-nix-channel_LIBS = libmain libformat libstore libutil
-
-nix-channel_SOURCES := $(d)/nix-channel.cc
diff --git a/src/nix-channel/nix-channel.cc b/src/nix-channel/nix-channel.cc
index 2083d3df5cab..8b66cc7e314e 100755
--- a/src/nix-channel/nix-channel.cc
+++ b/src/nix-channel/nix-channel.cc
@@ -1,9 +1,11 @@
 #include "shared.hh"
 #include "globals.hh"
 #include "download.hh"
+#include "store-api.hh"
+#include "legacy.hh"
+
 #include <fcntl.h>
 #include <regex>
-#include "store-api.hh"
 #include <pwd.h>
 
 using namespace nix;
@@ -157,11 +159,9 @@ static void update(const StringSet & channelNames)
     replaceSymlink(profile, channelLink);
 }
 
-int main(int argc, char ** argv)
+static int _main(int argc, char ** argv)
 {
-    return handleExceptions(argv[0], [&]() {
-        initNix();
-
+    {
         // Figure out the name of the `.nix-channels' file to use
         auto home = getHome();
         channelsList = home + "/.nix-channels";
@@ -255,5 +255,9 @@ int main(int argc, char ** argv)
                 runProgram(settings.nixBinDir + "/nix-env", false, envArgs);
                 break;
         }
-    });
+
+        return 0;
+    }
 }
+
+static RegisterLegacyCommand s1("nix-channel", _main);
diff --git a/src/nix-collect-garbage/local.mk b/src/nix-collect-garbage/local.mk
deleted file mode 100644
index 02d14cf62199..000000000000
--- a/src/nix-collect-garbage/local.mk
+++ /dev/null
@@ -1,7 +0,0 @@
-programs += nix-collect-garbage
-
-nix-collect-garbage_DIR := $(d)
-
-nix-collect-garbage_SOURCES := $(d)/nix-collect-garbage.cc
-
-nix-collect-garbage_LIBS = libmain libstore libutil libformat
diff --git a/src/nix-collect-garbage/nix-collect-garbage.cc b/src/nix-collect-garbage/nix-collect-garbage.cc
index 37fe22f48134..d4060ac937fc 100644
--- a/src/nix-collect-garbage/nix-collect-garbage.cc
+++ b/src/nix-collect-garbage/nix-collect-garbage.cc
@@ -2,6 +2,7 @@
 #include "profiles.hh"
 #include "shared.hh"
 #include "globals.hh"
+#include "legacy.hh"
 
 #include <iostream>
 #include <cerrno>
@@ -48,12 +49,10 @@ void removeOldGenerations(std::string dir)
     }
 }
 
-int main(int argc, char * * argv)
+static int _main(int argc, char * * argv)
 {
-    bool removeOld = false;
-
-    return handleExceptions(argv[0], [&]() {
-        initNix();
+    {
+        bool removeOld = false;
 
         GCOptions options;
 
@@ -90,5 +89,9 @@ int main(int argc, char * * argv)
             PrintFreed freed(true, results);
             store->collectGarbage(options, results);
         }
-    });
+
+        return 0;
+    }
 }
+
+static RegisterLegacyCommand s1("nix-collect-garbage", _main);
diff --git a/src/nix-copy-closure/local.mk b/src/nix-copy-closure/local.mk
deleted file mode 100644
index 5018ab975b44..000000000000
--- a/src/nix-copy-closure/local.mk
+++ /dev/null
@@ -1,7 +0,0 @@
-programs += nix-copy-closure
-
-nix-copy-closure_DIR := $(d)
-
-nix-copy-closure_LIBS = libmain libformat libstore libutil
-
-nix-copy-closure_SOURCES := $(d)/nix-copy-closure.cc
diff --git a/src/nix-copy-closure/nix-copy-closure.cc b/src/nix-copy-closure/nix-copy-closure.cc
index dfb1b8fc5dc4..fdcde8b076b5 100755
--- a/src/nix-copy-closure/nix-copy-closure.cc
+++ b/src/nix-copy-closure/nix-copy-closure.cc
@@ -1,13 +1,12 @@
 #include "shared.hh"
 #include "store-api.hh"
+#include "legacy.hh"
 
 using namespace nix;
 
-int main(int argc, char ** argv)
+static int _main(int argc, char ** argv)
 {
-    return handleExceptions(argv[0], [&]() {
-        initNix();
-
+    {
         auto gzip = false;
         auto toMode = true;
         auto includeOutputs = false;
@@ -61,5 +60,9 @@ int main(int argc, char ** argv)
         from->computeFSClosure(storePaths2, closure, false, includeOutputs);
 
         copyPaths(from, to, closure, NoRepair, NoCheckSigs, useSubstitutes);
-    });
+
+        return 0;
+    }
 }
+
+static RegisterLegacyCommand s1("nix-copy-closure", _main);
diff --git a/src/nix-daemon/local.mk b/src/nix-daemon/local.mk
deleted file mode 100644
index 5a4474465b3c..000000000000
--- a/src/nix-daemon/local.mk
+++ /dev/null
@@ -1,13 +0,0 @@
-programs += nix-daemon
-
-nix-daemon_DIR := $(d)
-
-nix-daemon_SOURCES := $(d)/nix-daemon.cc
-
-nix-daemon_LIBS = libmain libstore libutil libformat
-
-nix-daemon_LDFLAGS = -pthread
-
-ifeq ($(OS), SunOS)
-        nix-daemon_LDFLAGS += -lsocket
-endif
diff --git a/src/nix-daemon/nix-daemon.cc b/src/nix-daemon/nix-daemon.cc
index 423ba15e2ad0..8368c3266142 100644
--- a/src/nix-daemon/nix-daemon.cc
+++ b/src/nix-daemon/nix-daemon.cc
@@ -9,6 +9,7 @@
 #include "monitor-fd.hh"
 #include "derivations.hh"
 #include "finally.hh"
+#include "legacy.hh"
 
 #include <algorithm>
 
@@ -1058,11 +1059,9 @@ static void daemonLoop(char * * argv)
 }
 
 
-int main(int argc, char * * argv)
+static int _main(int argc, char * * argv)
 {
-    return handleExceptions(argv[0], [&]() {
-        initNix();
-
+    {
         auto stdio = false;
 
         parseCmdLine(argc, argv, [&](Strings::iterator & arg, const Strings::iterator & end) {
@@ -1122,7 +1121,7 @@ int main(int argc, char * * argv)
                         if (res == -1)
                             throw SysError("splicing data from stdin to daemon socket");
                         else if (res == 0)
-                            return;
+                            return 0;
                     }
                 }
             } else {
@@ -1131,5 +1130,9 @@ int main(int argc, char * * argv)
         } else {
             daemonLoop(argv);
         }
-    });
+
+        return 0;
+    }
 }
+
+static RegisterLegacyCommand s1("nix-daemon", _main);
diff --git a/src/nix-env/local.mk b/src/nix-env/local.mk
deleted file mode 100644
index e80719cd76f7..000000000000
--- a/src/nix-env/local.mk
+++ /dev/null
@@ -1,7 +0,0 @@
-programs += nix-env
-
-nix-env_DIR := $(d)
-
-nix-env_SOURCES := $(wildcard $(d)/*.cc)
-
-nix-env_LIBS = libexpr libmain libstore libutil libformat
diff --git a/src/nix-env/nix-env.cc b/src/nix-env/nix-env.cc
index f9c8a8d313e7..56ed75daee44 100644
--- a/src/nix-env/nix-env.cc
+++ b/src/nix-env/nix-env.cc
@@ -13,6 +13,7 @@
 #include "json.hh"
 #include "value-to-json.hh"
 #include "xml-writer.hh"
+#include "legacy.hh"
 
 #include <cerrno>
 #include <ctime>
@@ -1311,12 +1312,9 @@ static void opVersion(Globals & globals, Strings opFlags, Strings opArgs)
 }
 
 
-int main(int argc, char * * argv)
+static int _main(int argc, char * * argv)
 {
-    return handleExceptions(argv[0], [&]() {
-        initNix();
-        initGC();
-
+    {
         Strings opFlags, opArgs;
         Operation op = 0;
         RepairFlag repair = NoRepair;
@@ -1428,5 +1426,9 @@ int main(int argc, char * * argv)
         op(globals, opFlags, opArgs);
 
         globals.state->printStats();
-    });
+
+        return 0;
+    }
 }
+
+static RegisterLegacyCommand s1("nix-env", _main);
diff --git a/src/nix-instantiate/local.mk b/src/nix-instantiate/local.mk
deleted file mode 100644
index 7d1bc5ec9dfb..000000000000
--- a/src/nix-instantiate/local.mk
+++ /dev/null
@@ -1,7 +0,0 @@
-programs += nix-instantiate
-
-nix-instantiate_DIR := $(d)
-
-nix-instantiate_SOURCES := $(d)/nix-instantiate.cc
-
-nix-instantiate_LIBS = libexpr libmain libstore libutil libformat
diff --git a/src/nix-instantiate/nix-instantiate.cc b/src/nix-instantiate/nix-instantiate.cc
index eb6d34dd8219..a736caa8f056 100644
--- a/src/nix-instantiate/nix-instantiate.cc
+++ b/src/nix-instantiate/nix-instantiate.cc
@@ -9,6 +9,7 @@
 #include "util.hh"
 #include "store-api.hh"
 #include "common-eval-args.hh"
+#include "legacy.hh"
 
 #include <map>
 #include <iostream>
@@ -83,12 +84,9 @@ void processExpr(EvalState & state, const Strings & attrPaths,
 }
 
 
-int main(int argc, char * * argv)
+static int _main(int argc, char * * argv)
 {
-    return handleExceptions(argv[0], [&]() {
-        initNix();
-        initGC();
-
+    {
         Strings files;
         bool readStdin = false;
         bool fromArgs = false;
@@ -171,7 +169,7 @@ int main(int argc, char * * argv)
                 if (p == "") throw Error(format("unable to find '%1%'") % i);
                 std::cout << p << std::endl;
             }
-            return;
+            return 0;
         }
 
         if (readStdin) {
@@ -190,5 +188,9 @@ int main(int argc, char * * argv)
         }
 
         state->printStats();
-    });
+
+        return 0;
+    }
 }
+
+static RegisterLegacyCommand s1("nix-instantiate", _main);
diff --git a/src/nix-prefetch-url/local.mk b/src/nix-prefetch-url/local.mk
deleted file mode 100644
index 3e7735406af0..000000000000
--- a/src/nix-prefetch-url/local.mk
+++ /dev/null
@@ -1,7 +0,0 @@
-programs += nix-prefetch-url
-
-nix-prefetch-url_DIR := $(d)
-
-nix-prefetch-url_SOURCES := $(d)/nix-prefetch-url.cc
-
-nix-prefetch-url_LIBS = libmain libexpr libstore libutil libformat
diff --git a/src/nix-prefetch-url/nix-prefetch-url.cc b/src/nix-prefetch-url/nix-prefetch-url.cc
index a3b025723cf1..ddb724913214 100644
--- a/src/nix-prefetch-url/nix-prefetch-url.cc
+++ b/src/nix-prefetch-url/nix-prefetch-url.cc
@@ -6,6 +6,7 @@
 #include "eval-inline.hh"
 #include "common-eval-args.hh"
 #include "attr-path.hh"
+#include "legacy.hh"
 
 #include <iostream>
 
@@ -44,12 +45,9 @@ string resolveMirrorUri(EvalState & state, string uri)
 }
 
 
-int main(int argc, char * * argv)
+static int _main(int argc, char * * argv)
 {
-    return handleExceptions(argv[0], [&]() {
-        initNix();
-        initGC();
-
+    {
         HashType ht = htSHA256;
         std::vector<string> args;
         bool printPath = getEnv("PRINT_PATH") != "";
@@ -221,5 +219,9 @@ int main(int argc, char * * argv)
         std::cout << printHash16or32(hash) << std::endl;
         if (printPath)
             std::cout << storePath << std::endl;
-    });
+
+        return 0;
+    }
 }
+
+static RegisterLegacyCommand s1("nix-prefetch-url", _main);
diff --git a/src/nix-store/graphml.cc b/src/nix-store/graphml.cc
new file mode 100644
index 000000000000..670fbe227a4c
--- /dev/null
+++ b/src/nix-store/graphml.cc
@@ -0,0 +1,90 @@
+#include "graphml.hh"
+#include "util.hh"
+#include "store-api.hh"
+#include "derivations.hh"
+
+#include <iostream>
+
+
+using std::cout;
+
+namespace nix {
+
+
+static inline const string & xmlQuote(const string & s)
+{
+    // Luckily, store paths shouldn't contain any character that needs to be
+    // quoted.
+    return s;
+}
+
+
+static string symbolicName(const string & path)
+{
+    string p = baseNameOf(path);
+    return string(p, p.find('-') + 1);
+}
+
+
+static string makeEdge(const string & src, const string & dst)
+{
+    return fmt("  <edge source=\"%1%\" target=\"%2%\"/>\n",
+        xmlQuote(src), xmlQuote(dst));
+}
+
+
+static string makeNode(const ValidPathInfo & info)
+{
+    return fmt(
+        "  <node id=\"%1%\">\n"
+        "    <data key=\"narSize\">%2%</data>\n"
+        "    <data key=\"name\">%3%</data>\n"
+        "    <data key=\"type\">%4%</data>\n"
+        "  </node>\n",
+        info.path,
+        info.narSize,
+        symbolicName(info.path),
+        (isDerivation(info.path) ? "derivation" : "output-path"));
+}
+
+
+void printGraphML(ref<Store> store, const PathSet & roots)
+{
+    PathSet workList(roots);
+    PathSet doneSet;
+    std::pair<PathSet::iterator,bool> ret;
+
+    cout << "<?xml version='1.0' encoding='utf-8'?>\n"
+         << "<graphml xmlns='http://graphml.graphdrawing.org/xmlns'\n"
+         << "    xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'\n"
+         << "    xsi:schemaLocation='http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd'>\n"
+         << "<key id='narSize' for='node' attr.name='narSize' attr.type='int'/>"
+         << "<key id='name' for='node' attr.name='name' attr.type='string'/>"
+         << "<key id='type' for='node' attr.name='type' attr.type='string'/>"
+         << "<graph id='G' edgedefault='directed'>\n";
+
+    while (!workList.empty()) {
+        Path path = *(workList.begin());
+        workList.erase(path);
+
+        ret = doneSet.insert(path);
+        if (ret.second == false) continue;
+
+        ValidPathInfo info = *(store->queryPathInfo(path));
+        cout << makeNode(info);
+
+        for (auto & p : store->queryPathInfo(path)->references) {
+            if (p != path) {
+                workList.insert(p);
+                cout << makeEdge(path, p);
+            }
+        }
+
+    }
+
+    cout << "</graph>\n";
+    cout << "</graphml>\n";
+}
+
+
+}
diff --git a/src/nix-store/xmlgraph.hh b/src/nix-store/graphml.hh
index a6e7d4e2805a..b78df1e49a67 100644
--- a/src/nix-store/xmlgraph.hh
+++ b/src/nix-store/graphml.hh
@@ -6,6 +6,6 @@ namespace nix {
 
 class Store;
 
-void printXmlGraph(ref<Store> store, const PathSet & roots);
+void printGraphML(ref<Store> store, const PathSet & roots);
 
 }
diff --git a/src/nix-store/local.mk b/src/nix-store/local.mk
deleted file mode 100644
index ade0b233adf3..000000000000
--- a/src/nix-store/local.mk
+++ /dev/null
@@ -1,9 +0,0 @@
-programs += nix-store
-
-nix-store_DIR := $(d)
-
-nix-store_SOURCES := $(wildcard $(d)/*.cc)
-
-nix-store_LIBS = libmain libstore libutil libformat
-
-nix-store_LDFLAGS = -lbz2 -pthread $(SODIUM_LIBS)
diff --git a/src/nix-store/nix-store.cc b/src/nix-store/nix-store.cc
index fe68f681ae28..a9ad14762e62 100644
--- a/src/nix-store/nix-store.cc
+++ b/src/nix-store/nix-store.cc
@@ -8,7 +8,8 @@
 #include "shared.hh"
 #include "util.hh"
 #include "worker-protocol.hh"
-#include "xmlgraph.hh"
+#include "graphml.hh"
+#include "legacy.hh"
 
 #include <iostream>
 #include <algorithm>
@@ -273,7 +274,7 @@ static void opQuery(Strings opFlags, Strings opArgs)
     enum QueryType
         { qDefault, qOutputs, qRequisites, qReferences, qReferrers
         , qReferrersClosure, qDeriver, qBinding, qHash, qSize
-        , qTree, qGraph, qXml, qResolve, qRoots };
+        , qTree, qGraph, qGraphML, qResolve, qRoots };
     QueryType query = qDefault;
     bool useOutput = false;
     bool includeOutputs = false;
@@ -299,7 +300,7 @@ static void opQuery(Strings opFlags, Strings opArgs)
         else if (i == "--size") query = qSize;
         else if (i == "--tree") query = qTree;
         else if (i == "--graph") query = qGraph;
-        else if (i == "--xml") query = qXml;
+        else if (i == "--graphml") query = qGraphML;
         else if (i == "--resolve") query = qResolve;
         else if (i == "--roots") query = qRoots;
         else if (i == "--use-output" || i == "-u") useOutput = true;
@@ -403,13 +404,13 @@ static void opQuery(Strings opFlags, Strings opArgs)
             break;
         }
 
-        case qXml: {
+        case qGraphML: {
             PathSet roots;
             for (auto & i : opArgs) {
                 PathSet paths = maybeUseOutputs(store->followLinksToStorePath(i), useOutput, forceRealise);
                 roots.insert(paths.begin(), paths.end());
             }
-            printXmlGraph(ref<Store>(store), roots);
+            printGraphML(ref<Store>(store), roots);
             break;
         }
 
@@ -993,11 +994,9 @@ static void opVersion(Strings opFlags, Strings opArgs)
 /* Scan the arguments; find the operation, set global flags, put all
    other flags in a list, and put all other arguments in another
    list. */
-int main(int argc, char * * argv)
+static int _main(int argc, char * * argv)
 {
-    return handleExceptions(argv[0], [&]() {
-        initNix();
-
+    {
         Strings opFlags, opArgs;
         Operation op = 0;
 
@@ -1084,5 +1083,9 @@ int main(int argc, char * * argv)
             store = openStore();
 
         op(opFlags, opArgs);
-    });
+
+        return 0;
+    }
 }
+
+static RegisterLegacyCommand s1("nix-store", _main);
diff --git a/src/nix-store/xmlgraph.cc b/src/nix-store/xmlgraph.cc
deleted file mode 100644
index 0f7be7f7a02d..000000000000
--- a/src/nix-store/xmlgraph.cc
+++ /dev/null
@@ -1,66 +0,0 @@
-#include "xmlgraph.hh"
-#include "util.hh"
-#include "store-api.hh"
-
-#include <iostream>
-
-
-using std::cout;
-
-namespace nix {
-
-
-static inline const string & xmlQuote(const string & s)
-{
-    // Luckily, store paths shouldn't contain any character that needs to be
-    // quoted.
-    return s;
-}
-
-
-static string makeEdge(const string & src, const string & dst)
-{
-    format f = format("  <edge src=\"%1%\" dst=\"%2%\"/>\n")
-      % xmlQuote(src) % xmlQuote(dst);
-    return f.str();
-}
-
-
-static string makeNode(const string & id)
-{
-    format f = format("  <node name=\"%1%\"/>\n") % xmlQuote(id);
-    return f.str();
-}
-
-
-void printXmlGraph(ref<Store> store, const PathSet & roots)
-{
-    PathSet workList(roots);
-    PathSet doneSet;
-
-    cout << "<?xml version='1.0' encoding='utf-8'?>\n"
-         << "<nix>\n";
-
-    while (!workList.empty()) {
-        Path path = *(workList.begin());
-        workList.erase(path);
-
-        if (doneSet.find(path) != doneSet.end()) continue;
-        doneSet.insert(path);
-
-        cout << makeNode(path);
-
-        for (auto & p : store->queryPathInfo(path)->references) {
-            if (p != path) {
-                workList.insert(p);
-                cout << makeEdge(p, path);
-            }
-        }
-
-    }
-
-    cout << "</nix>\n";
-}
-
-
-}
diff --git a/src/nix/doctor.cc b/src/nix/doctor.cc
new file mode 100644
index 000000000000..7b5444619470
--- /dev/null
+++ b/src/nix/doctor.cc
@@ -0,0 +1,124 @@
+#include "command.hh"
+#include "serve-protocol.hh"
+#include "shared.hh"
+#include "store-api.hh"
+#include "worker-protocol.hh"
+
+using namespace nix;
+
+std::string formatProtocol(unsigned int proto)
+{
+    if (proto) {
+        auto major = GET_PROTOCOL_MAJOR(proto) >> 8;
+        auto minor = GET_PROTOCOL_MINOR(proto);
+        return (format("%1%.%2%") % major % minor).str();
+    }
+    return "unknown";
+}
+
+struct CmdDoctor : StoreCommand
+{
+    bool success = true;
+
+    std::string name() override
+    {
+        return "doctor";
+    }
+
+    std::string description() override
+    {
+        return "check your system for potential problems";
+    }
+
+    void run(ref<Store> store) override
+    {
+        std::cout << "Store uri: " << store->getUri() << std::endl;
+        std::cout << std::endl;
+
+        auto type = getStoreType();
+
+        if (type < tOther) {
+            success &= checkNixInPath();
+            success &= checkProfileRoots(store);
+        }
+        success &= checkStoreProtocol(store->getProtocol());
+
+        if (!success)
+            throw Exit(2);
+    }
+
+    bool checkNixInPath()
+    {
+        PathSet dirs;
+
+        for (auto & dir : tokenizeString<Strings>(getEnv("PATH"), ":"))
+            if (pathExists(dir + "/nix-env"))
+                dirs.insert(dirOf(canonPath(dir + "/nix-env", true)));
+
+        if (dirs.size() != 1) {
+            std::cout << "Warning: multiple versions of nix found in PATH." << std::endl;
+            std::cout << std::endl;
+            for (auto & dir : dirs)
+                std::cout << "  " << dir << std::endl;
+            std::cout << std::endl;
+            return false;
+        }
+
+        return true;
+    }
+
+    bool checkProfileRoots(ref<Store> store)
+    {
+        PathSet dirs;
+
+        for (auto & dir : tokenizeString<Strings>(getEnv("PATH"), ":")) {
+            Path profileDir = dirOf(dir);
+            try {
+                Path userEnv = canonPath(profileDir, true);
+
+                if (store->isStorePath(userEnv) && hasSuffix(userEnv, "user-environment")) {
+                    while (profileDir.find("/profiles/") == std::string::npos && isLink(profileDir))
+                        profileDir = absPath(readLink(profileDir), dirOf(profileDir));
+
+                    if (profileDir.find("/profiles/") == std::string::npos)
+                        dirs.insert(dir);
+                }
+            } catch (SysError &) {}
+        }
+
+        if (!dirs.empty()) {
+            std::cout << "Warning: found profiles outside of " << settings.nixStateDir << "/profiles." << std::endl;
+            std::cout << "The generation this profile points to might not have a gcroot and could be" << std::endl;
+            std::cout << "garbage collected, resulting in broken symlinks." << std::endl;
+            std::cout << std::endl;
+            for (auto & dir : dirs)
+                std::cout << "  " << dir << std::endl;
+            std::cout << std::endl;
+            return false;
+        }
+
+        return true;
+    }
+
+    bool checkStoreProtocol(unsigned int storeProto)
+    {
+        unsigned int clientProto = GET_PROTOCOL_MAJOR(SERVE_PROTOCOL_VERSION) == GET_PROTOCOL_MAJOR(storeProto)
+            ? SERVE_PROTOCOL_VERSION
+            : PROTOCOL_VERSION;
+
+        if (clientProto != storeProto) {
+            std::cout << "Warning: protocol version of this client does not match the store." << std::endl;
+            std::cout << "While this is not necessarily a problem it's recommended to keep the client in" << std::endl;
+            std::cout << "sync with the daemon." << std::endl;
+            std::cout << std::endl;
+            std::cout << "Client protocol: " << formatProtocol(clientProto) << std::endl;
+            std::cout << "Store protocol: " << formatProtocol(storeProto) << std::endl;
+            std::cout << std::endl;
+            return false;
+        }
+
+        return true;
+    }
+};
+
+static RegisterCommand r1(make_ref<CmdDoctor>());
diff --git a/src/nix/local.mk b/src/nix/local.mk
index f76da194467c..bdcca33d2a6f 100644
--- a/src/nix/local.mk
+++ b/src/nix/local.mk
@@ -2,10 +2,25 @@ programs += nix
 
 nix_DIR := $(d)
 
-nix_SOURCES := $(wildcard $(d)/*.cc) $(wildcard src/linenoise/*.cpp)
+nix_SOURCES := \
+  $(wildcard $(d)/*.cc) \
+  $(wildcard src/linenoise/*.cpp) \
+  $(wildcard src/build-remote/*.cc) \
+  $(wildcard src/nix-build/*.cc) \
+  $(wildcard src/nix-channel/*.cc) \
+  $(wildcard src/nix-collect-garbage/*.cc) \
+  $(wildcard src/nix-copy-closure/*.cc) \
+  $(wildcard src/nix-daemon/*.cc) \
+  $(wildcard src/nix-env/*.cc) \
+  $(wildcard src/nix-instantiate/*.cc) \
+  $(wildcard src/nix-prefetch-url/*.cc) \
+  $(wildcard src/nix-store/*.cc) \
 
 nix_LIBS = libexpr libmain libstore libutil libformat
 
-nix_LDFLAGS = -pthread
+nix_LDFLAGS = -pthread $(SODIUM_LIBS)
 
-$(eval $(call install-symlink, nix, $(bindir)/nix-hash))
+$(foreach name, \
+  nix-build nix-channel nix-collect-garbage nix-copy-closure nix-daemon nix-env nix-hash nix-instantiate nix-prefetch-url nix-shell nix-store, \
+  $(eval $(call install-symlink, nix, $(bindir)/$(name))))
+$(eval $(call install-symlink, $(bindir)/nix, $(libexecdir)/nix/build-remote))
diff --git a/src/nix/main.cc b/src/nix/main.cc
index 69791e223c22..64c1dc35787c 100644
--- a/src/nix/main.cc
+++ b/src/nix/main.cc
@@ -67,9 +67,6 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
 
 void mainWrapped(int argc, char * * argv)
 {
-    verbosity = lvlError;
-    settings.verboseBuild = false;
-
     /* The chroot helper needs to be run before any threads have been
        started. */
     if (argc > 0 && argv[0] == chrootHelperName) {
@@ -88,6 +85,9 @@ void mainWrapped(int argc, char * * argv)
         if (legacy) return legacy(argc, argv);
     }
 
+    verbosity = lvlError;
+    settings.verboseBuild = false;
+
     NixArgs args;
 
     args.parseCmdline(argvToStrings(argc, argv));