about summary refs log tree commit diff
path: root/third_party/nix/src/nix
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/nix/src/nix')
-rw-r--r--third_party/nix/src/nix/add-to-store.cc61
-rw-r--r--third_party/nix/src/nix/build.cc72
-rw-r--r--third_party/nix/src/nix/cat.cc74
-rw-r--r--third_party/nix/src/nix/command.cc156
-rw-r--r--third_party/nix/src/nix/command.hh214
-rw-r--r--third_party/nix/src/nix/copy.cc100
-rw-r--r--third_party/nix/src/nix/doctor.cc124
-rw-r--r--third_party/nix/src/nix/dump-path.cc36
-rw-r--r--third_party/nix/src/nix/edit.cc81
-rw-r--r--third_party/nix/src/nix/eval.cc77
-rw-r--r--third_party/nix/src/nix/hash.cc151
-rw-r--r--third_party/nix/src/nix/installables.cc327
-rw-r--r--third_party/nix/src/nix/legacy.cc7
-rw-r--r--third_party/nix/src/nix/legacy.hh23
-rw-r--r--third_party/nix/src/nix/local.mk25
-rw-r--r--third_party/nix/src/nix/log.cc71
-rw-r--r--third_party/nix/src/nix/ls.cc156
-rw-r--r--third_party/nix/src/nix/main.cc178
-rw-r--r--third_party/nix/src/nix/optimise-store.cc41
-rw-r--r--third_party/nix/src/nix/path-info.cc133
-rw-r--r--third_party/nix/src/nix/ping-store.cc35
-rw-r--r--third_party/nix/src/nix/progress-bar.cc454
-rw-r--r--third_party/nix/src/nix/progress-bar.hh11
-rw-r--r--third_party/nix/src/nix/repl.cc791
-rw-r--r--third_party/nix/src/nix/run.cc260
-rw-r--r--third_party/nix/src/nix/search.cc280
-rw-r--r--third_party/nix/src/nix/show-config.cc40
-rw-r--r--third_party/nix/src/nix/show-derivation.cc119
-rw-r--r--third_party/nix/src/nix/sigs.cc149
-rw-r--r--third_party/nix/src/nix/upgrade-nix.cc160
-rw-r--r--third_party/nix/src/nix/verify.cc178
-rw-r--r--third_party/nix/src/nix/why-depends.cc267
32 files changed, 4851 insertions, 0 deletions
diff --git a/third_party/nix/src/nix/add-to-store.cc b/third_party/nix/src/nix/add-to-store.cc
new file mode 100644
index 0000000000..e86b96e3f3
--- /dev/null
+++ b/third_party/nix/src/nix/add-to-store.cc
@@ -0,0 +1,61 @@
+#include "command.hh"
+#include "common-args.hh"
+#include "store-api.hh"
+#include "archive.hh"
+
+using namespace nix;
+
+struct CmdAddToStore : MixDryRun, StoreCommand
+{
+    Path path;
+    std::optional<std::string> namePart;
+
+    CmdAddToStore()
+    {
+        expectArg("path", &path);
+
+        mkFlag()
+            .longName("name")
+            .shortName('n')
+            .description("name component of the store path")
+            .labels({"name"})
+            .dest(&namePart);
+    }
+
+    std::string name() override
+    {
+        return "add-to-store";
+    }
+
+    std::string description() override
+    {
+        return "add a path to the Nix store";
+    }
+
+    Examples examples() override
+    {
+        return {
+        };
+    }
+
+    void run(ref<Store> store) override
+    {
+        if (!namePart) namePart = baseNameOf(path);
+
+        StringSink sink;
+        dumpPath(path, sink);
+
+        ValidPathInfo info;
+        info.narHash = hashString(htSHA256, *sink.s);
+        info.narSize = sink.s->size();
+        info.path = store->makeFixedOutputPath(true, info.narHash, *namePart);
+        info.ca = makeFixedOutputCA(true, info.narHash);
+
+        if (!dryRun)
+            store->addToStore(info, sink.s);
+
+        std::cout << fmt("%s\n", info.path);
+    }
+};
+
+static RegisterCommand r1(make_ref<CmdAddToStore>());
diff --git a/third_party/nix/src/nix/build.cc b/third_party/nix/src/nix/build.cc
new file mode 100644
index 0000000000..b329ac38ac
--- /dev/null
+++ b/third_party/nix/src/nix/build.cc
@@ -0,0 +1,72 @@
+#include "command.hh"
+#include "common-args.hh"
+#include "shared.hh"
+#include "store-api.hh"
+
+using namespace nix;
+
+struct CmdBuild : MixDryRun, InstallablesCommand
+{
+    Path outLink = "result";
+
+    CmdBuild()
+    {
+        mkFlag()
+            .longName("out-link")
+            .shortName('o')
+            .description("path of the symlink to the build result")
+            .labels({"path"})
+            .dest(&outLink);
+
+        mkFlag()
+            .longName("no-link")
+            .description("do not create a symlink to the build result")
+            .set(&outLink, Path(""));
+    }
+
+    std::string name() override
+    {
+        return "build";
+    }
+
+    std::string description() override
+    {
+        return "build a derivation or fetch a store path";
+    }
+
+    Examples examples() override
+    {
+        return {
+            Example{
+                "To build and run GNU Hello from NixOS 17.03:",
+                "nix build -f channel:nixos-17.03 hello; ./result/bin/hello"
+            },
+            Example{
+                "To build the build.x86_64-linux attribute from release.nix:",
+                "nix build -f release.nix build.x86_64-linux"
+            },
+        };
+    }
+
+    void run(ref<Store> store) override
+    {
+        auto buildables = build(store, dryRun ? DryRun : Build, installables);
+
+        if (dryRun) return;
+
+        for (size_t i = 0; i < buildables.size(); ++i) {
+            auto & b(buildables[i]);
+
+            if (outLink != "")
+                for (auto & output : b.outputs)
+                    if (auto store2 = store.dynamic_pointer_cast<LocalFSStore>()) {
+                        std::string symlink = outLink;
+                        if (i) symlink += fmt("-%d", i);
+                        if (output.first != "out") symlink += fmt("-%s", output.first);
+                        store2->addPermRoot(output.second, absPath(symlink), true);
+                    }
+        }
+    }
+};
+
+static RegisterCommand r1(make_ref<CmdBuild>());
diff --git a/third_party/nix/src/nix/cat.cc b/third_party/nix/src/nix/cat.cc
new file mode 100644
index 0000000000..a35f640d88
--- /dev/null
+++ b/third_party/nix/src/nix/cat.cc
@@ -0,0 +1,74 @@
+#include "command.hh"
+#include "store-api.hh"
+#include "fs-accessor.hh"
+#include "nar-accessor.hh"
+
+using namespace nix;
+
+struct MixCat : virtual Args
+{
+    std::string path;
+
+    void cat(ref<FSAccessor> accessor)
+    {
+        auto st = accessor->stat(path);
+        if (st.type == FSAccessor::Type::tMissing)
+            throw Error(format("path '%1%' does not exist") % path);
+        if (st.type != FSAccessor::Type::tRegular)
+            throw Error(format("path '%1%' is not a regular file") % path);
+
+        std::cout << accessor->readFile(path);
+    }
+};
+
+struct CmdCatStore : StoreCommand, MixCat
+{
+    CmdCatStore()
+    {
+        expectArg("path", &path);
+    }
+
+    std::string name() override
+    {
+        return "cat-store";
+    }
+
+    std::string description() override
+    {
+        return "print the contents of a store file on stdout";
+    }
+
+    void run(ref<Store> store) override
+    {
+        cat(store->getFSAccessor());
+    }
+};
+
+struct CmdCatNar : StoreCommand, MixCat
+{
+    Path narPath;
+
+    CmdCatNar()
+    {
+        expectArg("nar", &narPath);
+        expectArg("path", &path);
+    }
+
+    std::string name() override
+    {
+        return "cat-nar";
+    }
+
+    std::string description() override
+    {
+        return "print the contents of a file inside a NAR file";
+    }
+
+    void run(ref<Store> store) override
+    {
+        cat(makeNarAccessor(make_ref<std::string>(readFile(narPath))));
+    }
+};
+
+static RegisterCommand r1(make_ref<CmdCatStore>());
+static RegisterCommand r2(make_ref<CmdCatNar>());
diff --git a/third_party/nix/src/nix/command.cc b/third_party/nix/src/nix/command.cc
new file mode 100644
index 0000000000..3d7d582d6f
--- /dev/null
+++ b/third_party/nix/src/nix/command.cc
@@ -0,0 +1,156 @@
+#include "command.hh"
+#include "store-api.hh"
+#include "derivations.hh"
+
+namespace nix {
+
+Commands * RegisterCommand::commands = 0;
+
+void Command::printHelp(const string & programName, std::ostream & out)
+{
+    Args::printHelp(programName, out);
+
+    auto exs = examples();
+    if (!exs.empty()) {
+        out << "\n";
+        out << "Examples:\n";
+        for (auto & ex : exs)
+            out << "\n"
+                << "  " << ex.description << "\n" // FIXME: wrap
+                << "  $ " << ex.command << "\n";
+    }
+}
+
+MultiCommand::MultiCommand(const Commands & _commands)
+    : commands(_commands)
+{
+    expectedArgs.push_back(ExpectedArg{"command", 1, true, [=](std::vector<std::string> ss) {
+        assert(!command);
+        auto i = commands.find(ss[0]);
+        if (i == commands.end())
+            throw UsageError("'%s' is not a recognised command", ss[0]);
+        command = i->second;
+    }});
+}
+
+void MultiCommand::printHelp(const string & programName, std::ostream & out)
+{
+    if (command) {
+        command->printHelp(programName + " " + command->name(), out);
+        return;
+    }
+
+    out << "Usage: " << programName << " <COMMAND> <FLAGS>... <ARGS>...\n";
+
+    out << "\n";
+    out << "Common flags:\n";
+    printFlags(out);
+
+    out << "\n";
+    out << "Available commands:\n";
+
+    Table2 table;
+    for (auto & command : commands) {
+        auto descr = command.second->description();
+        if (!descr.empty())
+            table.push_back(std::make_pair(command.second->name(), descr));
+    }
+    printTable(out, table);
+
+#if 0
+    out << "\n";
+    out << "For full documentation, run 'man " << programName << "' or 'man " << programName << "-<COMMAND>'.\n";
+#endif
+}
+
+bool MultiCommand::processFlag(Strings::iterator & pos, Strings::iterator end)
+{
+    if (Args::processFlag(pos, end)) return true;
+    if (command && command->processFlag(pos, end)) return true;
+    return false;
+}
+
+bool MultiCommand::processArgs(const Strings & args, bool finish)
+{
+    if (command)
+        return command->processArgs(args, finish);
+    else
+        return Args::processArgs(args, finish);
+}
+
+StoreCommand::StoreCommand()
+{
+}
+
+ref<Store> StoreCommand::getStore()
+{
+    if (!_store)
+        _store = createStore();
+    return ref<Store>(_store);
+}
+
+ref<Store> StoreCommand::createStore()
+{
+    return openStore();
+}
+
+void StoreCommand::run()
+{
+    run(getStore());
+}
+
+StorePathsCommand::StorePathsCommand(bool recursive)
+    : recursive(recursive)
+{
+    if (recursive)
+        mkFlag()
+            .longName("no-recursive")
+            .description("apply operation to specified paths only")
+            .set(&this->recursive, false);
+    else
+        mkFlag()
+            .longName("recursive")
+            .shortName('r')
+            .description("apply operation to closure of the specified paths")
+            .set(&this->recursive, true);
+
+    mkFlag(0, "all", "apply operation to the entire store", &all);
+}
+
+void StorePathsCommand::run(ref<Store> store)
+{
+    Paths storePaths;
+
+    if (all) {
+        if (installables.size())
+            throw UsageError("'--all' does not expect arguments");
+        for (auto & p : store->queryAllValidPaths())
+            storePaths.push_back(p);
+    }
+
+    else {
+        for (auto & p : toStorePaths(store, NoBuild, installables))
+            storePaths.push_back(p);
+
+        if (recursive) {
+            PathSet closure;
+            store->computeFSClosure(PathSet(storePaths.begin(), storePaths.end()),
+                closure, false, false);
+            storePaths = Paths(closure.begin(), closure.end());
+        }
+    }
+
+    run(store, storePaths);
+}
+
+void StorePathCommand::run(ref<Store> store)
+{
+    auto storePaths = toStorePaths(store, NoBuild, installables);
+
+    if (storePaths.size() != 1)
+        throw UsageError("this command requires exactly one store path");
+
+    run(store, *storePaths.begin());
+}
+
+}
diff --git a/third_party/nix/src/nix/command.hh b/third_party/nix/src/nix/command.hh
new file mode 100644
index 0000000000..97a6fee7fd
--- /dev/null
+++ b/third_party/nix/src/nix/command.hh
@@ -0,0 +1,214 @@
+#pragma once
+
+#include "args.hh"
+#include "common-eval-args.hh"
+
+namespace nix {
+
+extern std::string programPath;
+
+struct Value;
+class Bindings;
+class EvalState;
+
+/* A command is an argument parser that can be executed by calling its
+   run() method. */
+struct Command : virtual Args
+{
+    virtual std::string name() = 0;
+    virtual void prepare() { };
+    virtual void run() = 0;
+
+    struct Example
+    {
+        std::string description;
+        std::string command;
+    };
+
+    typedef std::list<Example> Examples;
+
+    virtual Examples examples() { return Examples(); }
+
+    void printHelp(const string & programName, std::ostream & out) override;
+};
+
+class Store;
+
+/* A command that require a Nix store. */
+struct StoreCommand : virtual Command
+{
+    StoreCommand();
+    void run() override;
+    ref<Store> getStore();
+    virtual ref<Store> createStore();
+    virtual void run(ref<Store>) = 0;
+
+private:
+    std::shared_ptr<Store> _store;
+};
+
+struct Buildable
+{
+    Path drvPath; // may be empty
+    std::map<std::string, Path> outputs;
+};
+
+typedef std::vector<Buildable> Buildables;
+
+struct Installable
+{
+    virtual std::string what() = 0;
+
+    virtual Buildables toBuildables()
+    {
+        throw Error("argument '%s' cannot be built", what());
+    }
+
+    Buildable toBuildable();
+
+    virtual Value * toValue(EvalState & state)
+    {
+        throw Error("argument '%s' cannot be evaluated", what());
+    }
+};
+
+struct SourceExprCommand : virtual Args, StoreCommand, MixEvalArgs
+{
+    Path file;
+
+    SourceExprCommand();
+
+    /* Return a value representing the Nix expression from which we
+       are installing. This is either the file specified by ‘--file’,
+       or an attribute set constructed from $NIX_PATH, e.g. ‘{ nixpkgs
+       = import ...; bla = import ...; }’. */
+    Value * getSourceExpr(EvalState & state);
+
+    ref<EvalState> getEvalState();
+
+private:
+
+    std::shared_ptr<EvalState> evalState;
+
+    Value * vSourceExpr = 0;
+};
+
+enum RealiseMode { Build, NoBuild, DryRun };
+
+/* A command that operates on a list of "installables", which can be
+   store paths, attribute paths, Nix expressions, etc. */
+struct InstallablesCommand : virtual Args, SourceExprCommand
+{
+    std::vector<std::shared_ptr<Installable>> installables;
+
+    InstallablesCommand()
+    {
+        expectArgs("installables", &_installables);
+    }
+
+    void prepare() override;
+
+    virtual bool useDefaultInstallables() { return true; }
+
+private:
+
+    std::vector<std::string> _installables;
+};
+
+struct InstallableCommand : virtual Args, SourceExprCommand
+{
+    std::shared_ptr<Installable> installable;
+
+    InstallableCommand()
+    {
+        expectArg("installable", &_installable);
+    }
+
+    void prepare() override;
+
+private:
+
+    std::string _installable;
+};
+
+/* A command that operates on zero or more store paths. */
+struct StorePathsCommand : public InstallablesCommand
+{
+private:
+
+    bool recursive = false;
+    bool all = false;
+
+public:
+
+    StorePathsCommand(bool recursive = false);
+
+    using StoreCommand::run;
+
+    virtual void run(ref<Store> store, Paths storePaths) = 0;
+
+    void run(ref<Store> store) override;
+
+    bool useDefaultInstallables() override { return !all; }
+};
+
+/* A command that operates on exactly one store path. */
+struct StorePathCommand : public InstallablesCommand
+{
+    using StoreCommand::run;
+
+    virtual void run(ref<Store> store, const Path & storePath) = 0;
+
+    void run(ref<Store> store) override;
+};
+
+typedef std::map<std::string, ref<Command>> Commands;
+
+/* An argument parser that supports multiple subcommands,
+   i.e. ‘<command> <subcommand>’. */
+class MultiCommand : virtual Args
+{
+public:
+    Commands commands;
+
+    std::shared_ptr<Command> command;
+
+    MultiCommand(const Commands & commands);
+
+    void printHelp(const string & programName, std::ostream & out) override;
+
+    bool processFlag(Strings::iterator & pos, Strings::iterator end) override;
+
+    bool processArgs(const Strings & args, bool finish) override;
+};
+
+/* A helper class for registering commands globally. */
+struct RegisterCommand
+{
+    static Commands * commands;
+
+    RegisterCommand(ref<Command> command)
+    {
+        if (!commands) commands = new Commands;
+        commands->emplace(command->name(), command);
+    }
+};
+
+std::shared_ptr<Installable> parseInstallable(
+    SourceExprCommand & cmd, ref<Store> store, const std::string & installable,
+    bool useDefaultInstallables);
+
+Buildables build(ref<Store> store, RealiseMode mode,
+    std::vector<std::shared_ptr<Installable>> installables);
+
+PathSet toStorePaths(ref<Store> store, RealiseMode mode,
+    std::vector<std::shared_ptr<Installable>> installables);
+
+Path toStorePath(ref<Store> store, RealiseMode mode,
+    std::shared_ptr<Installable> installable);
+
+PathSet toDerivations(ref<Store> store,
+    std::vector<std::shared_ptr<Installable>> installables,
+    bool useDeriver = false);
+
+}
diff --git a/third_party/nix/src/nix/copy.cc b/third_party/nix/src/nix/copy.cc
new file mode 100644
index 0000000000..12a9f9cd33
--- /dev/null
+++ b/third_party/nix/src/nix/copy.cc
@@ -0,0 +1,100 @@
+#include "command.hh"
+#include "shared.hh"
+#include "store-api.hh"
+#include "sync.hh"
+#include "thread-pool.hh"
+
+#include <atomic>
+
+using namespace nix;
+
+struct CmdCopy : StorePathsCommand
+{
+    std::string srcUri, dstUri;
+
+    CheckSigsFlag checkSigs = CheckSigs;
+
+    SubstituteFlag substitute = NoSubstitute;
+
+    CmdCopy()
+        : StorePathsCommand(true)
+    {
+        mkFlag()
+            .longName("from")
+            .labels({"store-uri"})
+            .description("URI of the source Nix store")
+            .dest(&srcUri);
+        mkFlag()
+            .longName("to")
+            .labels({"store-uri"})
+            .description("URI of the destination Nix store")
+            .dest(&dstUri);
+
+        mkFlag()
+            .longName("no-check-sigs")
+            .description("do not require that paths are signed by trusted keys")
+            .set(&checkSigs, NoCheckSigs);
+
+        mkFlag()
+            .longName("substitute-on-destination")
+            .shortName('s')
+            .description("whether to try substitutes on the destination store (only supported by SSH)")
+            .set(&substitute, Substitute);
+    }
+
+    std::string name() override
+    {
+        return "copy";
+    }
+
+    std::string description() override
+    {
+        return "copy paths between Nix stores";
+    }
+
+    Examples examples() override
+    {
+        return {
+            Example{
+                "To copy Firefox from the local store to a binary cache in file:///tmp/cache:",
+                "nix copy --to file:///tmp/cache $(type -p firefox)"
+            },
+            Example{
+                "To copy the entire current NixOS system closure to another machine via SSH:",
+                "nix copy --to ssh://server /run/current-system"
+            },
+            Example{
+                "To copy a closure from another machine via SSH:",
+                "nix copy --from ssh://server /nix/store/a6cnl93nk1wxnq84brbbwr6hxw9gp2w9-blender-2.79-rc2"
+            },
+#ifdef ENABLE_S3
+            Example{
+                "To copy Hello to an S3 binary cache:",
+                "nix copy --to s3://my-bucket?region=eu-west-1 nixpkgs.hello"
+            },
+            Example{
+                "To copy Hello to an S3-compatible binary cache:",
+                "nix copy --to s3://my-bucket?region=eu-west-1&endpoint=example.com nixpkgs.hello"
+            },
+#endif
+        };
+    }
+
+    ref<Store> createStore() override
+    {
+        return srcUri.empty() ? StoreCommand::createStore() : openStore(srcUri);
+    }
+
+    void run(ref<Store> srcStore, Paths storePaths) override
+    {
+        if (srcUri.empty() && dstUri.empty())
+            throw UsageError("you must pass '--from' and/or '--to'");
+
+        ref<Store> dstStore = dstUri.empty() ? openStore() : openStore(dstUri);
+
+        copyPaths(srcStore, dstStore, PathSet(storePaths.begin(), storePaths.end()),
+            NoRepair, checkSigs, substitute);
+    }
+};
+
+static RegisterCommand r1(make_ref<CmdCopy>());
diff --git a/third_party/nix/src/nix/doctor.cc b/third_party/nix/src/nix/doctor.cc
new file mode 100644
index 0000000000..7b54446194
--- /dev/null
+++ b/third_party/nix/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/third_party/nix/src/nix/dump-path.cc b/third_party/nix/src/nix/dump-path.cc
new file mode 100644
index 0000000000..f411c0cb7c
--- /dev/null
+++ b/third_party/nix/src/nix/dump-path.cc
@@ -0,0 +1,36 @@
+#include "command.hh"
+#include "store-api.hh"
+
+using namespace nix;
+
+struct CmdDumpPath : StorePathCommand
+{
+    std::string name() override
+    {
+        return "dump-path";
+    }
+
+    std::string description() override
+    {
+        return "dump a store path to stdout (in NAR format)";
+    }
+
+    Examples examples() override
+    {
+        return {
+            Example{
+                "To get a NAR from the binary cache https://cache.nixos.org/:",
+                "nix dump-path --store https://cache.nixos.org/ /nix/store/7crrmih8c52r8fbnqb933dxrsp44md93-glibc-2.25"
+            },
+        };
+    }
+
+    void run(ref<Store> store, const Path & storePath) override
+    {
+        FdSink sink(STDOUT_FILENO);
+        store->narFromPath(storePath, sink);
+        sink.flush();
+    }
+};
+
+static RegisterCommand r1(make_ref<CmdDumpPath>());
diff --git a/third_party/nix/src/nix/edit.cc b/third_party/nix/src/nix/edit.cc
new file mode 100644
index 0000000000..a6169f1183
--- /dev/null
+++ b/third_party/nix/src/nix/edit.cc
@@ -0,0 +1,81 @@
+#include "command.hh"
+#include "shared.hh"
+#include "eval.hh"
+#include "attr-path.hh"
+#include "progress-bar.hh"
+
+#include <unistd.h>
+
+using namespace nix;
+
+struct CmdEdit : InstallableCommand
+{
+    std::string name() override
+    {
+        return "edit";
+    }
+
+    std::string description() override
+    {
+        return "open the Nix expression of a Nix package in $EDITOR";
+    }
+
+    Examples examples() override
+    {
+        return {
+            Example{
+                "To open the Nix expression of the GNU Hello package:",
+                "nix edit nixpkgs.hello"
+            },
+        };
+    }
+
+    void run(ref<Store> store) override
+    {
+        auto state = getEvalState();
+
+        auto v = installable->toValue(*state);
+
+        Value * v2;
+        try {
+            auto dummyArgs = state->allocBindings(0);
+            v2 = findAlongAttrPath(*state, "meta.position", *dummyArgs, *v);
+        } catch (Error &) {
+            throw Error("package '%s' has no source location information", installable->what());
+        }
+
+        auto pos = state->forceString(*v2);
+        debug("position is %s", pos);
+
+        auto colon = pos.rfind(':');
+        if (colon == std::string::npos)
+            throw Error("cannot parse meta.position attribute '%s'", pos);
+
+        std::string filename(pos, 0, colon);
+        int lineno;
+        try {
+            lineno = std::stoi(std::string(pos, colon + 1));
+        } catch (std::invalid_argument & e) {
+            throw Error("cannot parse line number '%s'", pos);
+        }
+
+        auto editor = getEnv("EDITOR", "cat");
+
+        auto args = tokenizeString<Strings>(editor);
+
+        if (editor.find("emacs") != std::string::npos ||
+            editor.find("nano") != std::string::npos ||
+            editor.find("vim") != std::string::npos)
+            args.push_back(fmt("+%d", lineno));
+
+        args.push_back(filename);
+
+        stopProgressBar();
+
+        execvp(args.front().c_str(), stringsToCharPtrs(args).data());
+
+        throw SysError("cannot run editor '%s'", editor);
+    }
+};
+
+static RegisterCommand r1(make_ref<CmdEdit>());
diff --git a/third_party/nix/src/nix/eval.cc b/third_party/nix/src/nix/eval.cc
new file mode 100644
index 0000000000..b7058361cb
--- /dev/null
+++ b/third_party/nix/src/nix/eval.cc
@@ -0,0 +1,77 @@
+#include "command.hh"
+#include "common-args.hh"
+#include "shared.hh"
+#include "store-api.hh"
+#include "eval.hh"
+#include "json.hh"
+#include "value-to-json.hh"
+#include "progress-bar.hh"
+
+using namespace nix;
+
+struct CmdEval : MixJSON, InstallableCommand
+{
+    bool raw = false;
+
+    CmdEval()
+    {
+        mkFlag(0, "raw", "print strings unquoted", &raw);
+    }
+
+    std::string name() override
+    {
+        return "eval";
+    }
+
+    std::string description() override
+    {
+        return "evaluate a Nix expression";
+    }
+
+    Examples examples() override
+    {
+        return {
+            Example{
+                "To evaluate a Nix expression given on the command line:",
+                "nix eval '(1 + 2)'"
+            },
+            Example{
+                "To evaluate a Nix expression from a file or URI:",
+                "nix eval -f channel:nixos-17.09 hello.name"
+            },
+            Example{
+                "To get the current version of Nixpkgs:",
+                "nix eval --raw nixpkgs.lib.nixpkgsVersion"
+            },
+            Example{
+                "To print the store path of the Hello package:",
+                "nix eval --raw nixpkgs.hello"
+            },
+        };
+    }
+
+    void run(ref<Store> store) override
+    {
+        if (raw && json)
+            throw UsageError("--raw and --json are mutually exclusive");
+
+        auto state = getEvalState();
+
+        auto v = installable->toValue(*state);
+        PathSet context;
+
+        stopProgressBar();
+
+        if (raw) {
+            std::cout << state->coerceToString(noPos, *v, context);
+        } else if (json) {
+            JSONPlaceholder jsonOut(std::cout);
+            printValueAsJSON(*state, true, *v, jsonOut, context);
+        } else {
+            state->forceValueDeep(*v);
+            std::cout << *v << "\n";
+        }
+    }
+};
+
+static RegisterCommand r1(make_ref<CmdEval>());
diff --git a/third_party/nix/src/nix/hash.cc b/third_party/nix/src/nix/hash.cc
new file mode 100644
index 0000000000..af4105e289
--- /dev/null
+++ b/third_party/nix/src/nix/hash.cc
@@ -0,0 +1,151 @@
+#include "command.hh"
+#include "hash.hh"
+#include "legacy.hh"
+#include "shared.hh"
+
+using namespace nix;
+
+struct CmdHash : Command
+{
+    enum Mode { mFile, mPath };
+    Mode mode;
+    Base base = SRI;
+    bool truncate = false;
+    HashType ht = htSHA256;
+    std::vector<std::string> paths;
+
+    CmdHash(Mode mode) : mode(mode)
+    {
+        mkFlag(0, "sri", "print hash in SRI format", &base, SRI);
+        mkFlag(0, "base64", "print hash in base-64", &base, Base64);
+        mkFlag(0, "base32", "print hash in base-32 (Nix-specific)", &base, Base32);
+        mkFlag(0, "base16", "print hash in base-16", &base, Base16);
+        mkFlag()
+            .longName("type")
+            .mkHashTypeFlag(&ht);
+        expectArgs("paths", &paths);
+    }
+
+    std::string name() override
+    {
+        return mode == mFile ? "hash-file" : "hash-path";
+    }
+
+    std::string description() override
+    {
+        return mode == mFile
+            ? "print cryptographic hash of a regular file"
+            : "print cryptographic hash of the NAR serialisation of a path";
+    }
+
+    void run() override
+    {
+        for (auto path : paths) {
+            Hash h = mode == mFile ? hashFile(ht, path) : hashPath(ht, path).first;
+            if (truncate && h.hashSize > 20) h = compressHash(h, 20);
+            std::cout << format("%1%\n") %
+                h.to_string(base, base == SRI);
+        }
+    }
+};
+
+static RegisterCommand r1(make_ref<CmdHash>(CmdHash::mFile));
+static RegisterCommand r2(make_ref<CmdHash>(CmdHash::mPath));
+
+struct CmdToBase : Command
+{
+    Base base;
+    HashType ht = htUnknown;
+    std::vector<std::string> args;
+
+    CmdToBase(Base base) : base(base)
+    {
+        mkFlag()
+            .longName("type")
+            .mkHashTypeFlag(&ht);
+        expectArgs("strings", &args);
+    }
+
+    std::string name() override
+    {
+        return
+            base == Base16 ? "to-base16" :
+            base == Base32 ? "to-base32" :
+            base == Base64 ? "to-base64" :
+            "to-sri";
+    }
+
+    std::string description() override
+    {
+        return fmt("convert a hash to %s representation",
+            base == Base16 ? "base-16" :
+            base == Base32 ? "base-32" :
+            base == Base64 ? "base-64" :
+            "SRI");
+    }
+
+    void run() override
+    {
+        for (auto s : args)
+            std::cout << fmt("%s\n", Hash(s, ht).to_string(base, base == SRI));
+    }
+};
+
+static RegisterCommand r3(make_ref<CmdToBase>(Base16));
+static RegisterCommand r4(make_ref<CmdToBase>(Base32));
+static RegisterCommand r5(make_ref<CmdToBase>(Base64));
+static RegisterCommand r6(make_ref<CmdToBase>(SRI));
+
+/* Legacy nix-hash command. */
+static int compatNixHash(int argc, char * * argv)
+{
+    HashType ht = htMD5;
+    bool flat = false;
+    bool base32 = false;
+    bool truncate = false;
+    enum { opHash, opTo32, opTo16 } op = opHash;
+    std::vector<std::string> ss;
+
+    parseCmdLine(argc, argv, [&](Strings::iterator & arg, const Strings::iterator & end) {
+        if (*arg == "--help")
+            showManPage("nix-hash");
+        else if (*arg == "--version")
+            printVersion("nix-hash");
+        else if (*arg == "--flat") flat = true;
+        else if (*arg == "--base32") base32 = true;
+        else if (*arg == "--truncate") truncate = true;
+        else if (*arg == "--type") {
+            string s = getArg(*arg, arg, end);
+            ht = parseHashType(s);
+            if (ht == htUnknown)
+                throw UsageError(format("unknown hash type '%1%'") % s);
+        }
+        else if (*arg == "--to-base16") op = opTo16;
+        else if (*arg == "--to-base32") op = opTo32;
+        else if (*arg != "" && arg->at(0) == '-')
+            return false;
+        else
+            ss.push_back(*arg);
+        return true;
+    });
+
+    if (op == opHash) {
+        CmdHash cmd(flat ? CmdHash::mFile : CmdHash::mPath);
+        cmd.ht = ht;
+        cmd.base = base32 ? Base32 : Base16;
+        cmd.truncate = truncate;
+        cmd.paths = ss;
+        cmd.run();
+    }
+
+    else {
+        CmdToBase cmd(op == opTo32 ? Base32 : Base16);
+        cmd.args = ss;
+        cmd.ht = ht;
+        cmd.run();
+    }
+
+    return 0;
+}
+
+static RegisterLegacyCommand s1("nix-hash", compatNixHash);
diff --git a/third_party/nix/src/nix/installables.cc b/third_party/nix/src/nix/installables.cc
new file mode 100644
index 0000000000..0e8bba39de
--- /dev/null
+++ b/third_party/nix/src/nix/installables.cc
@@ -0,0 +1,327 @@
+#include "command.hh"
+#include "attr-path.hh"
+#include "common-eval-args.hh"
+#include "derivations.hh"
+#include "eval-inline.hh"
+#include "eval.hh"
+#include "get-drvs.hh"
+#include "store-api.hh"
+#include "shared.hh"
+
+#include <regex>
+
+namespace nix {
+
+SourceExprCommand::SourceExprCommand()
+{
+    mkFlag()
+        .shortName('f')
+        .longName("file")
+        .label("file")
+        .description("evaluate FILE rather than the default")
+        .dest(&file);
+}
+
+Value * SourceExprCommand::getSourceExpr(EvalState & state)
+{
+    if (vSourceExpr) return vSourceExpr;
+
+    auto sToplevel = state.symbols.create("_toplevel");
+
+    vSourceExpr = state.allocValue();
+
+    if (file != "")
+        state.evalFile(lookupFileArg(state, file), *vSourceExpr);
+
+    else {
+
+        /* Construct the installation source from $NIX_PATH. */
+
+        auto searchPath = state.getSearchPath();
+
+        state.mkAttrs(*vSourceExpr, 1024);
+
+        mkBool(*state.allocAttr(*vSourceExpr, sToplevel), true);
+
+        std::unordered_set<std::string> seen;
+
+        auto addEntry = [&](const std::string & name) {
+            if (name == "") return;
+            if (!seen.insert(name).second) return;
+            Value * v1 = state.allocValue();
+            mkPrimOpApp(*v1, state.getBuiltin("findFile"), state.getBuiltin("nixPath"));
+            Value * v2 = state.allocValue();
+            mkApp(*v2, *v1, mkString(*state.allocValue(), name));
+            mkApp(*state.allocAttr(*vSourceExpr, state.symbols.create(name)),
+                state.getBuiltin("import"), *v2);
+        };
+
+        for (auto & i : searchPath)
+            /* Hack to handle channels. */
+            if (i.first.empty() && pathExists(i.second + "/manifest.nix")) {
+                for (auto & j : readDirectory(i.second))
+                    if (j.name != "manifest.nix"
+                        && pathExists(fmt("%s/%s/default.nix", i.second, j.name)))
+                        addEntry(j.name);
+            } else
+                addEntry(i.first);
+
+        vSourceExpr->attrs->sort();
+    }
+
+    return vSourceExpr;
+}
+
+ref<EvalState> SourceExprCommand::getEvalState()
+{
+    if (!evalState)
+        evalState = std::make_shared<EvalState>(searchPath, getStore());
+    return ref<EvalState>(evalState);
+}
+
+Buildable Installable::toBuildable()
+{
+    auto buildables = toBuildables();
+    if (buildables.size() != 1)
+        throw Error("installable '%s' evaluates to %d derivations, where only one is expected", what(), buildables.size());
+    return std::move(buildables[0]);
+}
+
+struct InstallableStorePath : Installable
+{
+    Path storePath;
+
+    InstallableStorePath(const Path & storePath) : storePath(storePath) { }
+
+    std::string what() override { return storePath; }
+
+    Buildables toBuildables() override
+    {
+        return {{isDerivation(storePath) ? storePath : "", {{"out", storePath}}}};
+    }
+};
+
+struct InstallableValue : Installable
+{
+    SourceExprCommand & cmd;
+
+    InstallableValue(SourceExprCommand & cmd) : cmd(cmd) { }
+
+    Buildables toBuildables() override
+    {
+        auto state = cmd.getEvalState();
+
+        auto v = toValue(*state);
+
+        Bindings & autoArgs = *cmd.getAutoArgs(*state);
+
+        DrvInfos drvs;
+        getDerivations(*state, *v, "", autoArgs, drvs, false);
+
+        Buildables res;
+
+        PathSet drvPaths;
+
+        for (auto & drv : drvs) {
+            Buildable b{drv.queryDrvPath()};
+            drvPaths.insert(b.drvPath);
+
+            auto outputName = drv.queryOutputName();
+            if (outputName == "")
+                throw Error("derivation '%s' lacks an 'outputName' attribute", b.drvPath);
+
+            b.outputs.emplace(outputName, drv.queryOutPath());
+
+            res.push_back(std::move(b));
+        }
+
+        // Hack to recognize .all: if all drvs have the same drvPath,
+        // merge the buildables.
+        if (drvPaths.size() == 1) {
+            Buildable b{*drvPaths.begin()};
+            for (auto & b2 : res)
+                b.outputs.insert(b2.outputs.begin(), b2.outputs.end());
+            return {b};
+        } else
+            return res;
+    }
+};
+
+struct InstallableExpr : InstallableValue
+{
+    std::string text;
+
+    InstallableExpr(SourceExprCommand & cmd, const std::string & text)
+         : InstallableValue(cmd), text(text) { }
+
+    std::string what() override { return text; }
+
+    Value * toValue(EvalState & state) override
+    {
+        auto v = state.allocValue();
+        state.eval(state.parseExprFromString(text, absPath(".")), *v);
+        return v;
+    }
+};
+
+struct InstallableAttrPath : InstallableValue
+{
+    std::string attrPath;
+
+    InstallableAttrPath(SourceExprCommand & cmd, const std::string & attrPath)
+        : InstallableValue(cmd), attrPath(attrPath)
+    { }
+
+    std::string what() override { return attrPath; }
+
+    Value * toValue(EvalState & state) override
+    {
+        auto source = cmd.getSourceExpr(state);
+
+        Bindings & autoArgs = *cmd.getAutoArgs(state);
+
+        Value * v = findAlongAttrPath(state, attrPath, autoArgs, *source);
+        state.forceValue(*v);
+
+        return v;
+    }
+};
+
+// FIXME: extend
+std::string attrRegex = R"([A-Za-z_][A-Za-z0-9-_+]*)";
+static std::regex attrPathRegex(fmt(R"(%1%(\.%1%)*)", attrRegex));
+
+static std::vector<std::shared_ptr<Installable>> parseInstallables(
+    SourceExprCommand & cmd, ref<Store> store, std::vector<std::string> ss, bool useDefaultInstallables)
+{
+    std::vector<std::shared_ptr<Installable>> result;
+
+    if (ss.empty() && useDefaultInstallables) {
+        if (cmd.file == "")
+            cmd.file = ".";
+        ss = {""};
+    }
+
+    for (auto & s : ss) {
+
+        if (s.compare(0, 1, "(") == 0)
+            result.push_back(std::make_shared<InstallableExpr>(cmd, s));
+
+        else if (s.find("/") != std::string::npos) {
+
+            auto path = store->toStorePath(store->followLinksToStore(s));
+
+            if (store->isStorePath(path))
+                result.push_back(std::make_shared<InstallableStorePath>(path));
+        }
+
+        else if (s == "" || std::regex_match(s, attrPathRegex))
+            result.push_back(std::make_shared<InstallableAttrPath>(cmd, s));
+
+        else
+            throw UsageError("don't know what to do with argument '%s'", s);
+    }
+
+    return result;
+}
+
+std::shared_ptr<Installable> parseInstallable(
+    SourceExprCommand & cmd, ref<Store> store, const std::string & installable,
+    bool useDefaultInstallables)
+{
+    auto installables = parseInstallables(cmd, store, {installable}, false);
+    assert(installables.size() == 1);
+    return installables.front();
+}
+
+Buildables build(ref<Store> store, RealiseMode mode,
+    std::vector<std::shared_ptr<Installable>> installables)
+{
+    if (mode != Build)
+        settings.readOnlyMode = true;
+
+    Buildables buildables;
+
+    PathSet pathsToBuild;
+
+    for (auto & i : installables) {
+        for (auto & b : i->toBuildables()) {
+            if (b.drvPath != "") {
+                StringSet outputNames;
+                for (auto & output : b.outputs)
+                    outputNames.insert(output.first);
+                pathsToBuild.insert(
+                    b.drvPath + "!" + concatStringsSep(",", outputNames));
+            } else
+                for (auto & output : b.outputs)
+                    pathsToBuild.insert(output.second);
+            buildables.push_back(std::move(b));
+        }
+    }
+
+    if (mode == DryRun)
+        printMissing(store, pathsToBuild, lvlError);
+    else if (mode == Build)
+        store->buildPaths(pathsToBuild);
+
+    return buildables;
+}
+
+PathSet toStorePaths(ref<Store> store, RealiseMode mode,
+    std::vector<std::shared_ptr<Installable>> installables)
+{
+    PathSet outPaths;
+
+    for (auto & b : build(store, mode, installables))
+        for (auto & output : b.outputs)
+            outPaths.insert(output.second);
+
+    return outPaths;
+}
+
+Path toStorePath(ref<Store> store, RealiseMode mode,
+    std::shared_ptr<Installable> installable)
+{
+    auto paths = toStorePaths(store, mode, {installable});
+
+    if (paths.size() != 1)
+            throw Error("argument '%s' should evaluate to one store path", installable->what());
+
+    return *paths.begin();
+}
+
+PathSet toDerivations(ref<Store> store,
+    std::vector<std::shared_ptr<Installable>> installables, bool useDeriver)
+{
+    PathSet drvPaths;
+
+    for (auto & i : installables)
+        for (auto & b : i->toBuildables()) {
+            if (b.drvPath.empty()) {
+                if (!useDeriver)
+                    throw Error("argument '%s' did not evaluate to a derivation", i->what());
+                for (auto & output : b.outputs) {
+                    auto derivers = store->queryValidDerivers(output.second);
+                    if (derivers.empty())
+                        throw Error("'%s' does not have a known deriver", i->what());
+                    // FIXME: use all derivers?
+                    drvPaths.insert(*derivers.begin());
+                }
+            } else
+                drvPaths.insert(b.drvPath);
+        }
+
+    return drvPaths;
+}
+
+void InstallablesCommand::prepare()
+{
+    installables = parseInstallables(*this, getStore(), _installables, useDefaultInstallables());
+}
+
+void InstallableCommand::prepare()
+{
+    installable = parseInstallable(*this, getStore(), _installable, false);
+}
+
+}
diff --git a/third_party/nix/src/nix/legacy.cc b/third_party/nix/src/nix/legacy.cc
new file mode 100644
index 0000000000..6df09ee37a
--- /dev/null
+++ b/third_party/nix/src/nix/legacy.cc
@@ -0,0 +1,7 @@
+#include "legacy.hh"
+
+namespace nix {
+
+RegisterLegacyCommand::Commands * RegisterLegacyCommand::commands = 0;
+
+}
diff --git a/third_party/nix/src/nix/legacy.hh b/third_party/nix/src/nix/legacy.hh
new file mode 100644
index 0000000000..f503b0da3e
--- /dev/null
+++ b/third_party/nix/src/nix/legacy.hh
@@ -0,0 +1,23 @@
+#pragma once
+
+#include <functional>
+#include <map>
+#include <string>
+
+namespace nix {
+
+typedef std::function<void(int, char * *)> MainFunction;
+
+struct RegisterLegacyCommand
+{
+    typedef std::map<std::string, MainFunction> Commands;
+    static Commands * commands;
+
+    RegisterLegacyCommand(const std::string & name, MainFunction fun)
+    {
+        if (!commands) commands = new Commands;
+        (*commands)[name] = fun;
+    }
+};
+
+}
diff --git a/third_party/nix/src/nix/local.mk b/third_party/nix/src/nix/local.mk
new file mode 100644
index 0000000000..c09efd1fc8
--- /dev/null
+++ b/third_party/nix/src/nix/local.mk
@@ -0,0 +1,25 @@
+programs += nix
+
+nix_DIR := $(d)
+
+nix_SOURCES := \
+  $(wildcard $(d)/*.cc) \
+  $(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
+
+nix_LDFLAGS = -pthread $(SODIUM_LIBS) $(EDITLINE_LIBS) $(BOOST_LDFLAGS) -lboost_context -lboost_thread -lboost_system
+
+$(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/third_party/nix/src/nix/log.cc b/third_party/nix/src/nix/log.cc
new file mode 100644
index 0000000000..f07ec4e93a
--- /dev/null
+++ b/third_party/nix/src/nix/log.cc
@@ -0,0 +1,71 @@
+#include "command.hh"
+#include "common-args.hh"
+#include "shared.hh"
+#include "store-api.hh"
+#include "progress-bar.hh"
+
+using namespace nix;
+
+struct CmdLog : InstallableCommand
+{
+    CmdLog()
+    {
+    }
+
+    std::string name() override
+    {
+        return "log";
+    }
+
+    std::string description() override
+    {
+        return "show the build log of the specified packages or paths, if available";
+    }
+
+    Examples examples() override
+    {
+        return {
+            Example{
+                "To get the build log of GNU Hello:",
+                "nix log nixpkgs.hello"
+            },
+            Example{
+                "To get the build log of a specific path:",
+                "nix log /nix/store/lmngj4wcm9rkv3w4dfhzhcyij3195hiq-thunderbird-52.2.1"
+            },
+            Example{
+                "To get a build log from a specific binary cache:",
+                "nix log --store https://cache.nixos.org nixpkgs.hello"
+            },
+        };
+    }
+
+    void run(ref<Store> store) override
+    {
+        settings.readOnlyMode = true;
+
+        auto subs = getDefaultSubstituters();
+
+        subs.push_front(store);
+
+        auto b = installable->toBuildable();
+
+        RunPager pager;
+        for (auto & sub : subs) {
+            auto log = b.drvPath != "" ? sub->getBuildLog(b.drvPath) : nullptr;
+            for (auto & output : b.outputs) {
+                if (log) break;
+                log = sub->getBuildLog(output.second);
+            }
+            if (!log) continue;
+            stopProgressBar();
+            printInfo("got build log for '%s' from '%s'", installable->what(), sub->getUri());
+            std::cout << *log;
+            return;
+        }
+
+        throw Error("build log of '%s' is not available", installable->what());
+    }
+};
+
+static RegisterCommand r1(make_ref<CmdLog>());
diff --git a/third_party/nix/src/nix/ls.cc b/third_party/nix/src/nix/ls.cc
new file mode 100644
index 0000000000..d089be42fb
--- /dev/null
+++ b/third_party/nix/src/nix/ls.cc
@@ -0,0 +1,156 @@
+#include "command.hh"
+#include "store-api.hh"
+#include "fs-accessor.hh"
+#include "nar-accessor.hh"
+#include "common-args.hh"
+#include "json.hh"
+
+using namespace nix;
+
+struct MixLs : virtual Args, MixJSON
+{
+    std::string path;
+
+    bool recursive = false;
+    bool verbose = false;
+    bool showDirectory = false;
+
+    MixLs()
+    {
+        mkFlag('R', "recursive", "list subdirectories recursively", &recursive);
+        mkFlag('l', "long", "show more file information", &verbose);
+        mkFlag('d', "directory", "show directories rather than their contents", &showDirectory);
+    }
+
+    void listText(ref<FSAccessor> accessor)
+    {
+        std::function<void(const FSAccessor::Stat &, const Path &, const std::string &, bool)> doPath;
+
+        auto showFile = [&](const Path & curPath, const std::string & relPath) {
+            if (verbose) {
+                auto st = accessor->stat(curPath);
+                std::string tp =
+                    st.type == FSAccessor::Type::tRegular ?
+                        (st.isExecutable ? "-r-xr-xr-x" : "-r--r--r--") :
+                    st.type == FSAccessor::Type::tSymlink ? "lrwxrwxrwx" :
+                    "dr-xr-xr-x";
+                std::cout <<
+                    (format("%s %20d %s") % tp % st.fileSize % relPath);
+                if (st.type == FSAccessor::Type::tSymlink)
+                    std::cout << " -> " << accessor->readLink(curPath)
+                    ;
+                std::cout << "\n";
+                if (recursive && st.type == FSAccessor::Type::tDirectory)
+                    doPath(st, curPath, relPath, false);
+            } else {
+                std::cout << relPath << "\n";
+                if (recursive) {
+                    auto st = accessor->stat(curPath);
+                    if (st.type == FSAccessor::Type::tDirectory)
+                        doPath(st, curPath, relPath, false);
+                }
+            }
+        };
+
+        doPath = [&](const FSAccessor::Stat & st, const Path & curPath,
+            const std::string & relPath, bool showDirectory)
+        {
+            if (st.type == FSAccessor::Type::tDirectory && !showDirectory) {
+                auto names = accessor->readDirectory(curPath);
+                for (auto & name : names)
+                    showFile(curPath + "/" + name, relPath + "/" + name);
+            } else
+                showFile(curPath, relPath);
+        };
+
+        auto st = accessor->stat(path);
+        if (st.type == FSAccessor::Type::tMissing)
+            throw Error(format("path '%1%' does not exist") % path);
+        doPath(st, path,
+            st.type == FSAccessor::Type::tDirectory ? "." : baseNameOf(path),
+            showDirectory);
+    }
+
+    void list(ref<FSAccessor> accessor)
+    {
+        if (path == "/") path = "";
+
+        if (json) {
+            JSONPlaceholder jsonRoot(std::cout);
+            listNar(jsonRoot, accessor, path, recursive);
+        } else
+            listText(accessor);
+    }
+};
+
+struct CmdLsStore : StoreCommand, MixLs
+{
+    CmdLsStore()
+    {
+        expectArg("path", &path);
+    }
+
+    Examples examples() override
+    {
+        return {
+            Example{
+                "To list the contents of a store path in a binary cache:",
+                "nix ls-store --store https://cache.nixos.org/ -lR /nix/store/0i2jd68mp5g6h2sa5k9c85rb80sn8hi9-hello-2.10"
+            },
+        };
+    }
+
+    std::string name() override
+    {
+        return "ls-store";
+    }
+
+    std::string description() override
+    {
+        return "show information about a store path";
+    }
+
+    void run(ref<Store> store) override
+    {
+        list(store->getFSAccessor());
+    }
+};
+
+struct CmdLsNar : Command, MixLs
+{
+    Path narPath;
+
+    CmdLsNar()
+    {
+        expectArg("nar", &narPath);
+        expectArg("path", &path);
+    }
+
+    Examples examples() override
+    {
+        return {
+            Example{
+                "To list a specific file in a NAR:",
+                "nix ls-nar -l hello.nar /bin/hello"
+            },
+        };
+    }
+
+    std::string name() override
+    {
+        return "ls-nar";
+    }
+
+    std::string description() override
+    {
+        return "show information about the contents of a NAR file";
+    }
+
+    void run() override
+    {
+        list(makeNarAccessor(make_ref<std::string>(readFile(narPath, true))));
+    }
+};
+
+static RegisterCommand r1(make_ref<CmdLsStore>());
+static RegisterCommand r2(make_ref<CmdLsNar>());
diff --git a/third_party/nix/src/nix/main.cc b/third_party/nix/src/nix/main.cc
new file mode 100644
index 0000000000..c683d86df4
--- /dev/null
+++ b/third_party/nix/src/nix/main.cc
@@ -0,0 +1,178 @@
+#include <algorithm>
+
+#include "command.hh"
+#include "common-args.hh"
+#include "eval.hh"
+#include "globals.hh"
+#include "legacy.hh"
+#include "shared.hh"
+#include "store-api.hh"
+#include "progress-bar.hh"
+#include "download.hh"
+#include "finally.hh"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <ifaddrs.h>
+#include <netdb.h>
+#include <netinet/in.h>
+
+extern std::string chrootHelperName;
+
+void chrootHelper(int argc, char * * argv);
+
+namespace nix {
+
+/* Check if we have a non-loopback/link-local network interface. */
+static bool haveInternet()
+{
+    struct ifaddrs * addrs;
+
+    if (getifaddrs(&addrs))
+        return true;
+
+    Finally free([&]() { freeifaddrs(addrs); });
+
+    for (auto i = addrs; i; i = i->ifa_next) {
+        if (!i->ifa_addr) continue;
+        if (i->ifa_addr->sa_family == AF_INET) {
+            if (ntohl(((sockaddr_in *) i->ifa_addr)->sin_addr.s_addr) != INADDR_LOOPBACK) {
+                return true;
+            }
+        } else if (i->ifa_addr->sa_family == AF_INET6) {
+            if (!IN6_IS_ADDR_LOOPBACK(&((sockaddr_in6 *) i->ifa_addr)->sin6_addr) &&
+                !IN6_IS_ADDR_LINKLOCAL(&((sockaddr_in6 *) i->ifa_addr)->sin6_addr))
+                return true;
+        }
+    }
+
+    return false;
+}
+
+std::string programPath;
+
+struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
+{
+    bool printBuildLogs = false;
+    bool useNet = true;
+
+    NixArgs() : MultiCommand(*RegisterCommand::commands), MixCommonArgs("nix")
+    {
+        mkFlag()
+            .longName("help")
+            .description("show usage information")
+            .handler([&]() { showHelpAndExit(); });
+
+        mkFlag()
+            .longName("help-config")
+            .description("show configuration options")
+            .handler([&]() {
+                std::cout << "The following configuration options are available:\n\n";
+                Table2 tbl;
+                std::map<std::string, Config::SettingInfo> settings;
+                globalConfig.getSettings(settings);
+                for (const auto & s : settings)
+                    tbl.emplace_back(s.first, s.second.description);
+                printTable(std::cout, tbl);
+                throw Exit();
+            });
+
+        mkFlag()
+            .longName("print-build-logs")
+            .shortName('L')
+            .description("print full build logs on stderr")
+            .set(&printBuildLogs, true);
+
+        mkFlag()
+            .longName("version")
+            .description("show version information")
+            .handler([&]() { printVersion(programName); });
+
+        mkFlag()
+            .longName("no-net")
+            .description("disable substituters and consider all previously downloaded files up-to-date")
+            .handler([&]() { useNet = false; });
+    }
+
+    void printFlags(std::ostream & out) override
+    {
+        Args::printFlags(out);
+        std::cout <<
+            "\n"
+            "In addition, most configuration settings can be overriden using '--<name> <value>'.\n"
+            "Boolean settings can be overriden using '--<name>' or '--no-<name>'. See 'nix\n"
+            "--help-config' for a list of configuration settings.\n";
+    }
+
+    void showHelpAndExit()
+    {
+        printHelp(programName, std::cout);
+        std::cout << "\nNote: this program is EXPERIMENTAL and subject to change.\n";
+        throw Exit();
+    }
+};
+
+void mainWrapped(int argc, char * * argv)
+{
+    /* The chroot helper needs to be run before any threads have been
+       started. */
+    if (argc > 0 && argv[0] == chrootHelperName) {
+        chrootHelper(argc, argv);
+        return;
+    }
+
+    initNix();
+    initGC();
+
+    programPath = argv[0];
+    string programName = baseNameOf(programPath);
+
+    {
+        auto legacy = (*RegisterLegacyCommand::commands)[programName];
+        if (legacy) return legacy(argc, argv);
+    }
+
+    verbosity = lvlWarn;
+    settings.verboseBuild = false;
+
+    NixArgs args;
+
+    args.parseCmdline(argvToStrings(argc, argv));
+
+    initPlugins();
+
+    if (!args.command) args.showHelpAndExit();
+
+    Finally f([]() { stopProgressBar(); });
+
+    startProgressBar(args.printBuildLogs);
+
+    if (args.useNet && !haveInternet()) {
+        warn("you don't have Internet access; disabling some network-dependent features");
+        args.useNet = false;
+    }
+
+    if (!args.useNet) {
+        // FIXME: should check for command line overrides only.
+        if (!settings.useSubstitutes.overriden)
+            settings.useSubstitutes = false;
+        if (!settings.tarballTtl.overriden)
+            settings.tarballTtl = std::numeric_limits<unsigned int>::max();
+        if (!downloadSettings.tries.overriden)
+            downloadSettings.tries = 0;
+        if (!downloadSettings.connectTimeout.overriden)
+            downloadSettings.connectTimeout = 1;
+    }
+
+    args.command->prepare();
+    args.command->run();
+}
+
+}
+
+int main(int argc, char * * argv)
+{
+    return nix::handleExceptions(argv[0], [&]() {
+        nix::mainWrapped(argc, argv);
+    });
+}
diff --git a/third_party/nix/src/nix/optimise-store.cc b/third_party/nix/src/nix/optimise-store.cc
new file mode 100644
index 0000000000..725fb75a19
--- /dev/null
+++ b/third_party/nix/src/nix/optimise-store.cc
@@ -0,0 +1,41 @@
+#include "command.hh"
+#include "shared.hh"
+#include "store-api.hh"
+
+#include <atomic>
+
+using namespace nix;
+
+struct CmdOptimiseStore : StoreCommand
+{
+    CmdOptimiseStore()
+    {
+    }
+
+    std::string name() override
+    {
+        return "optimise-store";
+    }
+
+    std::string description() override
+    {
+        return "replace identical files in the store by hard links";
+    }
+
+    Examples examples() override
+    {
+        return {
+            Example{
+                "To optimise the Nix store:",
+                "nix optimise-store"
+            },
+        };
+    }
+
+    void run(ref<Store> store) override
+    {
+        store->optimiseStore();
+    }
+};
+
+static RegisterCommand r1(make_ref<CmdOptimiseStore>());
diff --git a/third_party/nix/src/nix/path-info.cc b/third_party/nix/src/nix/path-info.cc
new file mode 100644
index 0000000000..dea5f0557b
--- /dev/null
+++ b/third_party/nix/src/nix/path-info.cc
@@ -0,0 +1,133 @@
+#include "command.hh"
+#include "shared.hh"
+#include "store-api.hh"
+#include "json.hh"
+#include "common-args.hh"
+
+#include <algorithm>
+#include <array>
+
+using namespace nix;
+
+struct CmdPathInfo : StorePathsCommand, MixJSON
+{
+    bool showSize = false;
+    bool showClosureSize = false;
+    bool humanReadable = false;
+    bool showSigs = false;
+
+    CmdPathInfo()
+    {
+        mkFlag('s', "size", "print size of the NAR dump of each path", &showSize);
+        mkFlag('S', "closure-size", "print sum size of the NAR dumps of the closure of each path", &showClosureSize);
+        mkFlag('h', "human-readable", "with -s and -S, print sizes like 1K 234M 5.67G etc.", &humanReadable);
+        mkFlag(0, "sigs", "show signatures", &showSigs);
+    }
+
+    std::string name() override
+    {
+        return "path-info";
+    }
+
+    std::string description() override
+    {
+        return "query information about store paths";
+    }
+
+    Examples examples() override
+    {
+        return {
+            Example{
+                "To show the closure sizes of every path in the current NixOS system closure, sorted by size:",
+                "nix path-info -rS /run/current-system | sort -nk2"
+            },
+            Example{
+                "To show a package's closure size and all its dependencies with human readable sizes:",
+                "nix path-info -rsSh nixpkgs.rust"
+            },
+            Example{
+                "To check the existence of a path in a binary cache:",
+                "nix path-info -r /nix/store/7qvk5c91...-geeqie-1.1 --store https://cache.nixos.org/"
+            },
+            Example{
+                "To print the 10 most recently added paths (using --json and the jq(1) command):",
+                "nix path-info --json --all | jq -r 'sort_by(.registrationTime)[-11:-1][].path'"
+            },
+            Example{
+                "To show the size of the entire Nix store:",
+                "nix path-info --json --all | jq 'map(.narSize) | add'"
+            },
+            Example{
+                "To show every path whose closure is bigger than 1 GB, sorted by closure size:",
+                "nix path-info --json --all -S | jq 'map(select(.closureSize > 1e9)) | sort_by(.closureSize) | map([.path, .closureSize])'"
+            },
+        };
+    }
+
+    void printSize(unsigned long long value)
+    {
+        if (!humanReadable) {
+            std::cout << fmt("\t%11d", value);
+            return;
+        }
+
+        static const std::array<char, 9> idents{{
+            ' ', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'
+        }};
+        size_t power = 0;
+        double res = value;
+        while (res > 1024 && power < idents.size()) {
+            ++power;
+            res /= 1024;
+        }
+        std::cout << fmt("\t%6.1f%c", res, idents.at(power));
+    }
+
+    void run(ref<Store> store, Paths storePaths) override
+    {
+        size_t pathLen = 0;
+        for (auto & storePath : storePaths)
+            pathLen = std::max(pathLen, storePath.size());
+
+        if (json) {
+            JSONPlaceholder jsonRoot(std::cout);
+            store->pathInfoToJSON(jsonRoot,
+                // FIXME: preserve order?
+                PathSet(storePaths.begin(), storePaths.end()),
+                true, showClosureSize, AllowInvalid);
+        }
+
+        else {
+
+            for (auto storePath : storePaths) {
+                auto info = store->queryPathInfo(storePath);
+                storePath = info->path; // FIXME: screws up padding
+
+                std::cout << storePath;
+
+                if (showSize || showClosureSize || showSigs)
+                    std::cout << std::string(std::max(0, (int) pathLen - (int) storePath.size()), ' ');
+
+                if (showSize)
+                    printSize(info->narSize);
+
+                if (showClosureSize)
+                    printSize(store->getClosureSize(storePath).first);
+
+                if (showSigs) {
+                    std::cout << '\t';
+                    Strings ss;
+                    if (info->ultimate) ss.push_back("ultimate");
+                    if (info->ca != "") ss.push_back("ca:" + info->ca);
+                    for (auto & sig : info->sigs) ss.push_back(sig);
+                    std::cout << concatStringsSep(" ", ss);
+                }
+
+                std::cout << std::endl;
+            }
+
+        }
+    }
+};
+
+static RegisterCommand r1(make_ref<CmdPathInfo>());
diff --git a/third_party/nix/src/nix/ping-store.cc b/third_party/nix/src/nix/ping-store.cc
new file mode 100644
index 0000000000..310942574a
--- /dev/null
+++ b/third_party/nix/src/nix/ping-store.cc
@@ -0,0 +1,35 @@
+#include "command.hh"
+#include "shared.hh"
+#include "store-api.hh"
+
+using namespace nix;
+
+struct CmdPingStore : StoreCommand
+{
+    std::string name() override
+    {
+        return "ping-store";
+    }
+
+    std::string description() override
+    {
+        return "test whether a store can be opened";
+    }
+
+    Examples examples() override
+    {
+        return {
+            Example{
+                "To test whether connecting to a remote Nix store via SSH works:",
+                "nix ping-store --store ssh://mac1"
+            },
+        };
+    }
+
+    void run(ref<Store> store) override
+    {
+        store->connect();
+    }
+};
+
+static RegisterCommand r1(make_ref<CmdPingStore>());
diff --git a/third_party/nix/src/nix/progress-bar.cc b/third_party/nix/src/nix/progress-bar.cc
new file mode 100644
index 0000000000..98049b5daf
--- /dev/null
+++ b/third_party/nix/src/nix/progress-bar.cc
@@ -0,0 +1,454 @@
+#include "progress-bar.hh"
+#include "util.hh"
+#include "sync.hh"
+#include "store-api.hh"
+#include "names.hh"
+
+#include <atomic>
+#include <map>
+#include <thread>
+
+namespace nix {
+
+static std::string getS(const std::vector<Logger::Field> & fields, size_t n)
+{
+    assert(n < fields.size());
+    assert(fields[n].type == Logger::Field::tString);
+    return fields[n].s;
+}
+
+static uint64_t getI(const std::vector<Logger::Field> & fields, size_t n)
+{
+    assert(n < fields.size());
+    assert(fields[n].type == Logger::Field::tInt);
+    return fields[n].i;
+}
+
+class ProgressBar : public Logger
+{
+private:
+
+    struct ActInfo
+    {
+        std::string s, lastLine, phase;
+        ActivityType type = actUnknown;
+        uint64_t done = 0;
+        uint64_t expected = 0;
+        uint64_t running = 0;
+        uint64_t failed = 0;
+        std::map<ActivityType, uint64_t> expectedByType;
+        bool visible = true;
+        ActivityId parent;
+        std::optional<std::string> name;
+    };
+
+    struct ActivitiesByType
+    {
+        std::map<ActivityId, std::list<ActInfo>::iterator> its;
+        uint64_t done = 0;
+        uint64_t expected = 0;
+        uint64_t failed = 0;
+    };
+
+    struct State
+    {
+        std::list<ActInfo> activities;
+        std::map<ActivityId, std::list<ActInfo>::iterator> its;
+
+        std::map<ActivityType, ActivitiesByType> activitiesByType;
+
+        uint64_t filesLinked = 0, bytesLinked = 0;
+
+        uint64_t corruptedPaths = 0, untrustedPaths = 0;
+
+        bool active = true;
+        bool haveUpdate = true;
+    };
+
+    Sync<State> state_;
+
+    std::thread updateThread;
+
+    std::condition_variable quitCV, updateCV;
+
+    bool printBuildLogs;
+    bool isTTY;
+
+public:
+
+    ProgressBar(bool printBuildLogs, bool isTTY)
+        : printBuildLogs(printBuildLogs)
+        , isTTY(isTTY)
+    {
+        state_.lock()->active = isTTY;
+        updateThread = std::thread([&]() {
+            auto state(state_.lock());
+            while (state->active) {
+                if (!state->haveUpdate)
+                    state.wait(updateCV);
+                draw(*state);
+                state.wait_for(quitCV, std::chrono::milliseconds(50));
+            }
+        });
+    }
+
+    ~ProgressBar()
+    {
+        stop();
+        updateThread.join();
+    }
+
+    void stop()
+    {
+        auto state(state_.lock());
+        if (!state->active) return;
+        state->active = false;
+        std::string status = getStatus(*state);
+        writeToStderr("\r\e[K");
+        if (status != "")
+            writeToStderr("[" + status + "]\n");
+        updateCV.notify_one();
+        quitCV.notify_one();
+    }
+
+    void log(Verbosity lvl, const FormatOrString & fs) override
+    {
+        auto state(state_.lock());
+        log(*state, lvl, fs.s);
+    }
+
+    void log(State & state, Verbosity lvl, const std::string & s)
+    {
+        if (state.active) {
+            writeToStderr("\r\e[K" + filterANSIEscapes(s, !isTTY) + ANSI_NORMAL "\n");
+            draw(state);
+        } else {
+            auto s2 = s + ANSI_NORMAL "\n";
+            if (!isTTY) s2 = filterANSIEscapes(s2, true);
+            writeToStderr(s2);
+        }
+    }
+
+    void startActivity(ActivityId act, Verbosity lvl, ActivityType type,
+        const std::string & s, const Fields & fields, ActivityId parent) override
+    {
+        auto state(state_.lock());
+
+        if (lvl <= verbosity && !s.empty())
+            log(*state, lvl, s + "...");
+
+        state->activities.emplace_back(ActInfo());
+        auto i = std::prev(state->activities.end());
+        i->s = s;
+        i->type = type;
+        i->parent = parent;
+        state->its.emplace(act, i);
+        state->activitiesByType[type].its.emplace(act, i);
+
+        if (type == actBuild) {
+            auto name = storePathToName(getS(fields, 0));
+            if (hasSuffix(name, ".drv"))
+                name.resize(name.size() - 4);
+            i->s = fmt("building " ANSI_BOLD "%s" ANSI_NORMAL, name);
+            auto machineName = getS(fields, 1);
+            if (machineName != "")
+                i->s += fmt(" on " ANSI_BOLD "%s" ANSI_NORMAL, machineName);
+            auto curRound = getI(fields, 2);
+            auto nrRounds = getI(fields, 3);
+            if (nrRounds != 1)
+                i->s += fmt(" (round %d/%d)", curRound, nrRounds);
+            i->name = DrvName(name).name;
+        }
+
+        if (type == actSubstitute) {
+            auto name = storePathToName(getS(fields, 0));
+            auto sub = getS(fields, 1);
+            i->s = fmt(
+                hasPrefix(sub, "local")
+                ? "copying " ANSI_BOLD "%s" ANSI_NORMAL " from %s"
+                : "fetching " ANSI_BOLD "%s" ANSI_NORMAL " from %s",
+                name, sub);
+        }
+
+        if (type == actPostBuildHook) {
+            auto name = storePathToName(getS(fields, 0));
+            if (hasSuffix(name, ".drv"))
+                name.resize(name.size() - 4);
+            i->s = fmt("post-build " ANSI_BOLD "%s" ANSI_NORMAL, name);
+            i->name = DrvName(name).name;
+        }
+
+        if (type == actQueryPathInfo) {
+            auto name = storePathToName(getS(fields, 0));
+            i->s = fmt("querying " ANSI_BOLD "%s" ANSI_NORMAL " on %s", name, getS(fields, 1));
+        }
+
+        if ((type == actDownload && hasAncestor(*state, actCopyPath, parent))
+            || (type == actDownload && hasAncestor(*state, actQueryPathInfo, parent))
+            || (type == actCopyPath && hasAncestor(*state, actSubstitute, parent)))
+            i->visible = false;
+
+        update(*state);
+    }
+
+    /* Check whether an activity has an ancestore with the specified
+       type. */
+    bool hasAncestor(State & state, ActivityType type, ActivityId act)
+    {
+        while (act != 0) {
+            auto i = state.its.find(act);
+            if (i == state.its.end()) break;
+            if (i->second->type == type) return true;
+            act = i->second->parent;
+        }
+        return false;
+    }
+
+    void stopActivity(ActivityId act) override
+    {
+        auto state(state_.lock());
+
+        auto i = state->its.find(act);
+        if (i != state->its.end()) {
+
+            auto & actByType = state->activitiesByType[i->second->type];
+            actByType.done += i->second->done;
+            actByType.failed += i->second->failed;
+
+            for (auto & j : i->second->expectedByType)
+                state->activitiesByType[j.first].expected -= j.second;
+
+            actByType.its.erase(act);
+            state->activities.erase(i->second);
+            state->its.erase(i);
+        }
+
+        update(*state);
+    }
+
+    void result(ActivityId act, ResultType type, const std::vector<Field> & fields) override
+    {
+        auto state(state_.lock());
+
+        if (type == resFileLinked) {
+            state->filesLinked++;
+            state->bytesLinked += getI(fields, 0);
+            update(*state);
+        }
+
+        else if (type == resBuildLogLine || type == resPostBuildLogLine) {
+            auto lastLine = trim(getS(fields, 0));
+            if (!lastLine.empty()) {
+                auto i = state->its.find(act);
+                assert(i != state->its.end());
+                ActInfo info = *i->second;
+                if (printBuildLogs) {
+                    auto suffix = "> ";
+                    if (type == resPostBuildLogLine) {
+                        suffix = " (post)> ";
+                    }
+                    log(*state, lvlInfo, ANSI_FAINT + info.name.value_or("unnamed") + suffix + ANSI_NORMAL + lastLine);
+                } else {
+                    state->activities.erase(i->second);
+                    info.lastLine = lastLine;
+                    state->activities.emplace_back(info);
+                    i->second = std::prev(state->activities.end());
+                    update(*state);
+                }
+            }
+        }
+
+        else if (type == resUntrustedPath) {
+            state->untrustedPaths++;
+            update(*state);
+        }
+
+        else if (type == resCorruptedPath) {
+            state->corruptedPaths++;
+            update(*state);
+        }
+
+        else if (type == resSetPhase) {
+            auto i = state->its.find(act);
+            assert(i != state->its.end());
+            i->second->phase = getS(fields, 0);
+            update(*state);
+        }
+
+        else if (type == resProgress) {
+            auto i = state->its.find(act);
+            assert(i != state->its.end());
+            ActInfo & actInfo = *i->second;
+            actInfo.done = getI(fields, 0);
+            actInfo.expected = getI(fields, 1);
+            actInfo.running = getI(fields, 2);
+            actInfo.failed = getI(fields, 3);
+            update(*state);
+        }
+
+        else if (type == resSetExpected) {
+            auto i = state->its.find(act);
+            assert(i != state->its.end());
+            ActInfo & actInfo = *i->second;
+            auto type = (ActivityType) getI(fields, 0);
+            auto & j = actInfo.expectedByType[type];
+            state->activitiesByType[type].expected -= j;
+            j = getI(fields, 1);
+            state->activitiesByType[type].expected += j;
+            update(*state);
+        }
+    }
+
+    void update(State & state)
+    {
+        state.haveUpdate = true;
+        updateCV.notify_one();
+    }
+
+    void draw(State & state)
+    {
+        state.haveUpdate = false;
+        if (!state.active) return;
+
+        std::string line;
+
+        std::string status = getStatus(state);
+        if (!status.empty()) {
+            line += '[';
+            line += status;
+            line += "]";
+        }
+
+        if (!state.activities.empty()) {
+            if (!status.empty()) line += " ";
+            auto i = state.activities.rbegin();
+
+            while (i != state.activities.rend() && (!i->visible || (i->s.empty() && i->lastLine.empty())))
+                ++i;
+
+            if (i != state.activities.rend()) {
+                line += i->s;
+                if (!i->phase.empty()) {
+                    line += " (";
+                    line += i->phase;
+                    line += ")";
+                }
+                if (!i->lastLine.empty()) {
+                    if (!i->s.empty()) line += ": ";
+                    line += i->lastLine;
+                }
+            }
+        }
+
+        auto width = getWindowSize().second;
+        if (width <= 0) width = std::numeric_limits<decltype(width)>::max();
+
+        writeToStderr("\r" + filterANSIEscapes(line, false, width) + "\e[K");
+    }
+
+    std::string getStatus(State & state)
+    {
+        auto MiB = 1024.0 * 1024.0;
+
+        std::string res;
+
+        auto renderActivity = [&](ActivityType type, const std::string & itemFmt, const std::string & numberFmt = "%d", double unit = 1) {
+            auto & act = state.activitiesByType[type];
+            uint64_t done = act.done, expected = act.done, running = 0, failed = act.failed;
+            for (auto & j : act.its) {
+                done += j.second->done;
+                expected += j.second->expected;
+                running += j.second->running;
+                failed += j.second->failed;
+            }
+
+            expected = std::max(expected, act.expected);
+
+            std::string s;
+
+            if (running || done || expected || failed) {
+                if (running)
+                    if (expected != 0)
+                        s = fmt(ANSI_BLUE + numberFmt + ANSI_NORMAL "/" ANSI_GREEN + numberFmt + ANSI_NORMAL "/" + numberFmt,
+                            running / unit, done / unit, expected / unit);
+                    else
+                        s = fmt(ANSI_BLUE + numberFmt + ANSI_NORMAL "/" ANSI_GREEN + numberFmt + ANSI_NORMAL,
+                            running / unit, done / unit);
+                else if (expected != done)
+                    if (expected != 0)
+                        s = fmt(ANSI_GREEN + numberFmt + ANSI_NORMAL "/" + numberFmt,
+                            done / unit, expected / unit);
+                    else
+                        s = fmt(ANSI_GREEN + numberFmt + ANSI_NORMAL, done / unit);
+                else
+                    s = fmt(done ? ANSI_GREEN + numberFmt + ANSI_NORMAL : numberFmt, done / unit);
+                s = fmt(itemFmt, s);
+
+                if (failed)
+                    s += fmt(" (" ANSI_RED "%d failed" ANSI_NORMAL ")", failed / unit);
+            }
+
+            return s;
+        };
+
+        auto showActivity = [&](ActivityType type, const std::string & itemFmt, const std::string & numberFmt = "%d", double unit = 1) {
+            auto s = renderActivity(type, itemFmt, numberFmt, unit);
+            if (s.empty()) return;
+            if (!res.empty()) res += ", ";
+            res += s;
+        };
+
+        showActivity(actBuilds, "%s built");
+
+        auto s1 = renderActivity(actCopyPaths, "%s copied");
+        auto s2 = renderActivity(actCopyPath, "%s MiB", "%.1f", MiB);
+
+        if (!s1.empty() || !s2.empty()) {
+            if (!res.empty()) res += ", ";
+            if (s1.empty()) res += "0 copied"; else res += s1;
+            if (!s2.empty()) { res += " ("; res += s2; res += ')'; }
+        }
+
+        showActivity(actDownload, "%s MiB DL", "%.1f", MiB);
+
+        {
+            auto s = renderActivity(actOptimiseStore, "%s paths optimised");
+            if (s != "") {
+                s += fmt(", %.1f MiB / %d inodes freed", state.bytesLinked / MiB, state.filesLinked);
+                if (!res.empty()) res += ", ";
+                res += s;
+            }
+        }
+
+        // FIXME: don't show "done" paths in green.
+        showActivity(actVerifyPaths, "%s paths verified");
+
+        if (state.corruptedPaths) {
+            if (!res.empty()) res += ", ";
+            res += fmt(ANSI_RED "%d corrupted" ANSI_NORMAL, state.corruptedPaths);
+        }
+
+        if (state.untrustedPaths) {
+            if (!res.empty()) res += ", ";
+            res += fmt(ANSI_RED "%d untrusted" ANSI_NORMAL, state.untrustedPaths);
+        }
+
+        return res;
+    }
+};
+
+void startProgressBar(bool printBuildLogs)
+{
+    logger = new ProgressBar(
+        printBuildLogs,
+        isatty(STDERR_FILENO) && getEnv("TERM", "dumb") != "dumb");
+}
+
+void stopProgressBar()
+{
+    auto progressBar = dynamic_cast<ProgressBar *>(logger);
+    if (progressBar) progressBar->stop();
+
+}
+
+}
diff --git a/third_party/nix/src/nix/progress-bar.hh b/third_party/nix/src/nix/progress-bar.hh
new file mode 100644
index 0000000000..4d61175c24
--- /dev/null
+++ b/third_party/nix/src/nix/progress-bar.hh
@@ -0,0 +1,11 @@
+#pragma once
+
+#include "logging.hh"
+
+namespace nix {
+
+void startProgressBar(bool printBuildLogs = false);
+
+void stopProgressBar();
+
+}
diff --git a/third_party/nix/src/nix/repl.cc b/third_party/nix/src/nix/repl.cc
new file mode 100644
index 0000000000..f857b2e89c
--- /dev/null
+++ b/third_party/nix/src/nix/repl.cc
@@ -0,0 +1,791 @@
+#include <iostream>
+#include <cstdlib>
+#include <cstring>
+#include <climits>
+
+#include <setjmp.h>
+
+#ifdef READLINE
+#include <readline/history.h>
+#include <readline/readline.h>
+#else
+// editline < 1.15.2 don't wrap their API for C++ usage
+// (added in https://github.com/troglobit/editline/commit/91398ceb3427b730995357e9d120539fb9bb7461).
+// This results in linker errors due to to name-mangling of editline C symbols.
+// For compatibility with these versions, we wrap the API here
+// (wrapping multiple times on newer versions is no problem).
+extern "C" {
+#include <editline.h>
+}
+#endif
+
+#include "shared.hh"
+#include "eval.hh"
+#include "eval-inline.hh"
+#include "store-api.hh"
+#include "common-eval-args.hh"
+#include "get-drvs.hh"
+#include "derivations.hh"
+#include "affinity.hh"
+#include "globals.hh"
+#include "command.hh"
+#include "finally.hh"
+
+namespace nix {
+
+#define ESC_RED "\033[31m"
+#define ESC_GRE "\033[32m"
+#define ESC_YEL "\033[33m"
+#define ESC_BLU "\033[34;1m"
+#define ESC_MAG "\033[35m"
+#define ESC_CYA "\033[36m"
+#define ESC_END "\033[0m"
+
+struct NixRepl
+{
+    string curDir;
+    EvalState state;
+    Bindings * autoArgs;
+
+    Strings loadedFiles;
+
+    const static int envSize = 32768;
+    StaticEnv staticEnv;
+    Env * env;
+    int displ;
+    StringSet varNames;
+
+    const Path historyFile;
+
+    NixRepl(const Strings & searchPath, nix::ref<Store> store);
+    ~NixRepl();
+    void mainLoop(const std::vector<std::string> & files);
+    StringSet completePrefix(string prefix);
+    bool getLine(string & input, const std::string &prompt);
+    Path getDerivationPath(Value & v);
+    bool processLine(string line);
+    void loadFile(const Path & path);
+    void initEnv();
+    void reloadFiles();
+    void addAttrsToScope(Value & attrs);
+    void addVarToScope(const Symbol & name, Value & v);
+    Expr * parseString(string s);
+    void evalString(string s, Value & v);
+
+    typedef set<Value *> ValuesSeen;
+    std::ostream &  printValue(std::ostream & str, Value & v, unsigned int maxDepth);
+    std::ostream &  printValue(std::ostream & str, Value & v, unsigned int maxDepth, ValuesSeen & seen);
+};
+
+
+void printHelp()
+{
+    std::cout
+         << "Usage: nix-repl [--help] [--version] [-I path] paths...\n"
+         << "\n"
+         << "nix-repl is a simple read-eval-print loop (REPL) for the Nix package manager.\n"
+         << "\n"
+         << "Options:\n"
+         << "    --help\n"
+         << "        Prints out a summary of the command syntax and exits.\n"
+         << "\n"
+         << "    --version\n"
+         << "        Prints out the Nix version number on standard output and exits.\n"
+         << "\n"
+         << "    -I path\n"
+         << "        Add a path to the Nix expression search path. This option may be given\n"
+         << "        multiple times. See the NIX_PATH environment variable for information on\n"
+         << "        the semantics of the Nix search path. Paths added through -I take\n"
+         << "        precedence over NIX_PATH.\n"
+         << "\n"
+         << "    paths...\n"
+         << "        A list of paths to files containing Nix expressions which nix-repl will\n"
+         << "        load and add to its scope.\n"
+         << "\n"
+         << "        A path surrounded in < and > will be looked up in the Nix expression search\n"
+         << "        path, as in the Nix language itself.\n"
+         << "\n"
+         << "        If an element of paths starts with http:// or https://, it is interpreted\n"
+         << "        as the URL of a tarball that will be downloaded and unpacked to a temporary\n"
+         << "        location. The tarball must include a single top-level directory containing\n"
+         << "        at least a file named default.nix.\n";
+}
+
+
+string removeWhitespace(string s)
+{
+    s = chomp(s);
+    size_t n = s.find_first_not_of(" \n\r\t");
+    if (n != string::npos) s = string(s, n);
+    return s;
+}
+
+
+NixRepl::NixRepl(const Strings & searchPath, nix::ref<Store> store)
+    : state(searchPath, store)
+    , staticEnv(false, &state.staticBaseEnv)
+    , historyFile(getDataDir() + "/nix/repl-history")
+{
+    curDir = absPath(".");
+}
+
+
+NixRepl::~NixRepl()
+{
+    write_history(historyFile.c_str());
+}
+
+static NixRepl * curRepl; // ugly
+
+static char * completionCallback(char * s, int *match) {
+  auto possible = curRepl->completePrefix(s);
+  if (possible.size() == 1) {
+    *match = 1;
+    auto *res = strdup(possible.begin()->c_str() + strlen(s));
+    if (!res) throw Error("allocation failure");
+    return res;
+  } else if (possible.size() > 1) {
+    auto checkAllHaveSameAt = [&](size_t pos) {
+      auto &first = *possible.begin();
+      for (auto &p : possible) {
+        if (p.size() <= pos || p[pos] != first[pos])
+          return false;
+      }
+      return true;
+    };
+    size_t start = strlen(s);
+    size_t len = 0;
+    while (checkAllHaveSameAt(start + len)) ++len;
+    if (len > 0) {
+      *match = 1;
+      auto *res = strdup(std::string(*possible.begin(), start, len).c_str());
+      if (!res) throw Error("allocation failure");
+      return res;
+    }
+  }
+
+  *match = 0;
+  return nullptr;
+}
+
+static int listPossibleCallback(char *s, char ***avp) {
+  auto possible = curRepl->completePrefix(s);
+
+  if (possible.size() > (INT_MAX / sizeof(char*)))
+    throw Error("too many completions");
+
+  int ac = 0;
+  char **vp = nullptr;
+
+  auto check = [&](auto *p) {
+    if (!p) {
+      if (vp) {
+        while (--ac >= 0)
+          free(vp[ac]);
+        free(vp);
+      }
+      throw Error("allocation failure");
+    }
+    return p;
+  };
+
+  vp = check((char **)malloc(possible.size() * sizeof(char*)));
+
+  for (auto & p : possible)
+    vp[ac++] = check(strdup(p.c_str()));
+
+  *avp = vp;
+
+  return ac;
+}
+
+namespace {
+    // Used to communicate to NixRepl::getLine whether a signal occurred in ::readline.
+    volatile sig_atomic_t g_signal_received = 0;
+
+    void sigintHandler(int signo) {
+        g_signal_received = signo;
+    }
+}
+
+void NixRepl::mainLoop(const std::vector<std::string> & files)
+{
+    string error = ANSI_RED "error:" ANSI_NORMAL " ";
+    std::cout << "Welcome to Nix version " << nixVersion << ". Type :? for help." << std::endl << std::endl;
+
+    for (auto & i : files)
+        loadedFiles.push_back(i);
+
+    reloadFiles();
+    if (!loadedFiles.empty()) std::cout << std::endl;
+
+    // Allow nix-repl specific settings in .inputrc
+    rl_readline_name = "nix-repl";
+    createDirs(dirOf(historyFile));
+#ifndef READLINE
+    el_hist_size = 1000;
+#endif
+    read_history(historyFile.c_str());
+    curRepl = this;
+#ifndef READLINE
+    rl_set_complete_func(completionCallback);
+    rl_set_list_possib_func(listPossibleCallback);
+#endif
+
+    std::string input;
+
+    while (true) {
+        // When continuing input from previous lines, don't print a prompt, just align to the same
+        // number of chars as the prompt.
+        if (!getLine(input, input.empty() ? "nix-repl> " : "          "))
+            break;
+
+        try {
+            if (!removeWhitespace(input).empty() && !processLine(input)) return;
+        } catch (ParseError & e) {
+            if (e.msg().find("unexpected $end") != std::string::npos) {
+                // For parse errors on incomplete input, we continue waiting for the next line of
+                // input without clearing the input so far.
+                continue;
+            } else {
+              printMsg(lvlError, format(error + "%1%%2%") % (settings.showTrace ? e.prefix() : "") % e.msg());
+            }
+        } catch (Error & e) {
+            printMsg(lvlError, format(error + "%1%%2%") % (settings.showTrace ? e.prefix() : "") % e.msg());
+        } catch (Interrupted & e) {
+            printMsg(lvlError, format(error + "%1%%2%") % (settings.showTrace ? e.prefix() : "") % e.msg());
+        }
+
+        // We handled the current input fully, so we should clear it
+        // and read brand new input.
+        input.clear();
+        std::cout << std::endl;
+    }
+}
+
+
+bool NixRepl::getLine(string & input, const std::string &prompt)
+{
+    struct sigaction act, old;
+    sigset_t savedSignalMask, set;
+
+    auto setupSignals = [&]() {
+        act.sa_handler = sigintHandler;
+        sigfillset(&act.sa_mask);
+        act.sa_flags = 0;
+        if (sigaction(SIGINT, &act, &old))
+            throw SysError("installing handler for SIGINT");
+
+        sigemptyset(&set);
+        sigaddset(&set, SIGINT);
+        if (sigprocmask(SIG_UNBLOCK, &set, &savedSignalMask))
+            throw SysError("unblocking SIGINT");
+    };
+    auto restoreSignals = [&]() {
+        if (sigprocmask(SIG_SETMASK, &savedSignalMask, nullptr))
+            throw SysError("restoring signals");
+
+        if (sigaction(SIGINT, &old, 0))
+            throw SysError("restoring handler for SIGINT");
+    };
+
+    setupSignals();
+    char * s = readline(prompt.c_str());
+    Finally doFree([&]() { free(s); });
+    restoreSignals();
+
+    if (g_signal_received) {
+        g_signal_received = 0;
+        input.clear();
+        return true;
+    }
+
+    if (!s)
+      return false;
+    input += s;
+    input += '\n';
+    return true;
+}
+
+
+StringSet NixRepl::completePrefix(string prefix)
+{
+    StringSet completions;
+
+    size_t start = prefix.find_last_of(" \n\r\t(){}[]");
+    std::string prev, cur;
+    if (start == std::string::npos) {
+        prev = "";
+        cur = prefix;
+    } else {
+        prev = std::string(prefix, 0, start + 1);
+        cur = std::string(prefix, start + 1);
+    }
+
+    size_t slash, dot;
+
+    if ((slash = cur.rfind('/')) != string::npos) {
+        try {
+            auto dir = std::string(cur, 0, slash);
+            auto prefix2 = std::string(cur, slash + 1);
+            for (auto & entry : readDirectory(dir == "" ? "/" : dir)) {
+                if (entry.name[0] != '.' && hasPrefix(entry.name, prefix2))
+                    completions.insert(prev + dir + "/" + entry.name);
+            }
+        } catch (Error &) {
+        }
+    } else if ((dot = cur.rfind('.')) == string::npos) {
+        /* This is a variable name; look it up in the current scope. */
+        StringSet::iterator i = varNames.lower_bound(cur);
+        while (i != varNames.end()) {
+            if (string(*i, 0, cur.size()) != cur) break;
+            completions.insert(prev + *i);
+            i++;
+        }
+    } else {
+        try {
+            /* This is an expression that should evaluate to an
+               attribute set.  Evaluate it to get the names of the
+               attributes. */
+            string expr(cur, 0, dot);
+            string cur2 = string(cur, dot + 1);
+
+            Expr * e = parseString(expr);
+            Value v;
+            e->eval(state, *env, v);
+            state.forceAttrs(v);
+
+            for (auto & i : *v.attrs) {
+                string name = i.name;
+                if (string(name, 0, cur2.size()) != cur2) continue;
+                completions.insert(prev + expr + "." + name);
+            }
+
+        } catch (ParseError & e) {
+            // Quietly ignore parse errors.
+        } catch (EvalError & e) {
+            // Quietly ignore evaluation errors.
+        } catch (UndefinedVarError & e) {
+            // Quietly ignore undefined variable errors.
+        }
+    }
+
+    return completions;
+}
+
+
+static int runProgram(const string & program, const Strings & args)
+{
+    Strings args2(args);
+    args2.push_front(program);
+
+    Pid pid;
+    pid = fork();
+    if (pid == -1) throw SysError("forking");
+    if (pid == 0) {
+        restoreAffinity();
+        execvp(program.c_str(), stringsToCharPtrs(args2).data());
+        _exit(1);
+    }
+
+    return pid.wait();
+}
+
+
+bool isVarName(const string & s)
+{
+    if (s.size() == 0) return false;
+    char c = s[0];
+    if ((c >= '0' && c <= '9') || c == '-' || c == '\'') return false;
+    for (auto & i : s)
+        if (!((i >= 'a' && i <= 'z') ||
+              (i >= 'A' && i <= 'Z') ||
+              (i >= '0' && i <= '9') ||
+              i == '_' || i == '-' || i == '\''))
+            return false;
+    return true;
+}
+
+
+Path NixRepl::getDerivationPath(Value & v) {
+    auto drvInfo = getDerivation(state, v, false);
+    if (!drvInfo)
+        throw Error("expression does not evaluate to a derivation, so I can't build it");
+    Path drvPath = drvInfo->queryDrvPath();
+    if (drvPath == "" || !state.store->isValidPath(drvPath))
+        throw Error("expression did not evaluate to a valid derivation");
+    return drvPath;
+}
+
+
+bool NixRepl::processLine(string line)
+{
+    if (line == "") return true;
+
+    string command, arg;
+
+    if (line[0] == ':') {
+        size_t p = line.find_first_of(" \n\r\t");
+        command = string(line, 0, p);
+        if (p != string::npos) arg = removeWhitespace(string(line, p));
+    } else {
+        arg = line;
+    }
+
+    if (command == ":?" || command == ":help") {
+        std::cout
+             << "The following commands are available:\n"
+             << "\n"
+             << "  <expr>        Evaluate and print expression\n"
+             << "  <x> = <expr>  Bind expression to variable\n"
+             << "  :a <expr>     Add attributes from resulting set to scope\n"
+             << "  :b <expr>     Build derivation\n"
+             << "  :i <expr>     Build derivation, then install result into current profile\n"
+             << "  :l <path>     Load Nix expression and add it to scope\n"
+             << "  :p <expr>     Evaluate and print expression recursively\n"
+             << "  :q            Exit nix-repl\n"
+             << "  :r            Reload all files\n"
+             << "  :s <expr>     Build dependencies of derivation, then start nix-shell\n"
+             << "  :t <expr>     Describe result of evaluation\n"
+             << "  :u <expr>     Build derivation, then start nix-shell\n";
+    }
+
+    else if (command == ":a" || command == ":add") {
+        Value v;
+        evalString(arg, v);
+        addAttrsToScope(v);
+    }
+
+    else if (command == ":l" || command == ":load") {
+        state.resetFileCache();
+        loadFile(arg);
+    }
+
+    else if (command == ":r" || command == ":reload") {
+        state.resetFileCache();
+        reloadFiles();
+    }
+
+    else if (command == ":t") {
+        Value v;
+        evalString(arg, v);
+        std::cout << showType(v) << std::endl;
+
+    } else if (command == ":u") {
+        Value v, f, result;
+        evalString(arg, v);
+        evalString("drv: (import <nixpkgs> {}).runCommand \"shell\" { buildInputs = [ drv ]; } \"\"", f);
+        state.callFunction(f, v, result, Pos());
+
+        Path drvPath = getDerivationPath(result);
+        runProgram(settings.nixBinDir + "/nix-shell", Strings{drvPath});
+    }
+
+    else if (command == ":b" || command == ":i" || command == ":s") {
+        Value v;
+        evalString(arg, v);
+        Path drvPath = getDerivationPath(v);
+
+        if (command == ":b") {
+            /* We could do the build in this process using buildPaths(),
+               but doing it in a child makes it easier to recover from
+               problems / SIGINT. */
+            if (runProgram(settings.nixBinDir + "/nix", Strings{"build", "--no-link", drvPath}) == 0) {
+                Derivation drv = readDerivation(drvPath);
+                std::cout << std::endl << "this derivation produced the following outputs:" << std::endl;
+                for (auto & i : drv.outputs)
+                    std::cout << format("  %1% -> %2%") % i.first % i.second.path << std::endl;
+            }
+        } else if (command == ":i") {
+            runProgram(settings.nixBinDir + "/nix-env", Strings{"-i", drvPath});
+        } else {
+            runProgram(settings.nixBinDir + "/nix-shell", Strings{drvPath});
+        }
+    }
+
+    else if (command == ":p" || command == ":print") {
+        Value v;
+        evalString(arg, v);
+        printValue(std::cout, v, 1000000000) << std::endl;
+    }
+
+    else if (command == ":q" || command == ":quit")
+        return false;
+
+    else if (command != "")
+        throw Error(format("unknown command '%1%'") % command);
+
+    else {
+        size_t p = line.find('=');
+        string name;
+        if (p != string::npos &&
+            p < line.size() &&
+            line[p + 1] != '=' &&
+            isVarName(name = removeWhitespace(string(line, 0, p))))
+        {
+            Expr * e = parseString(string(line, p + 1));
+            Value & v(*state.allocValue());
+            v.type = tThunk;
+            v.thunk.env = env;
+            v.thunk.expr = e;
+            addVarToScope(state.symbols.create(name), v);
+        } else {
+            Value v;
+            evalString(line, v);
+            printValue(std::cout, v, 1) << std::endl;
+        }
+    }
+
+    return true;
+}
+
+
+void NixRepl::loadFile(const Path & path)
+{
+    loadedFiles.remove(path);
+    loadedFiles.push_back(path);
+    Value v, v2;
+    state.evalFile(lookupFileArg(state, path), v);
+    state.autoCallFunction(*autoArgs, v, v2);
+    addAttrsToScope(v2);
+}
+
+
+void NixRepl::initEnv()
+{
+    env = &state.allocEnv(envSize);
+    env->up = &state.baseEnv;
+    displ = 0;
+    staticEnv.vars.clear();
+
+    varNames.clear();
+    for (auto & i : state.staticBaseEnv.vars)
+        varNames.insert(i.first);
+}
+
+
+void NixRepl::reloadFiles()
+{
+    initEnv();
+
+    Strings old = loadedFiles;
+    loadedFiles.clear();
+
+    bool first = true;
+    for (auto & i : old) {
+        if (!first) std::cout << std::endl;
+        first = false;
+        std::cout << format("Loading '%1%'...") % i << std::endl;
+        loadFile(i);
+    }
+}
+
+
+void NixRepl::addAttrsToScope(Value & attrs)
+{
+    state.forceAttrs(attrs);
+    for (auto & i : *attrs.attrs)
+        addVarToScope(i.name, *i.value);
+    std::cout << format("Added %1% variables.") % attrs.attrs->size() << std::endl;
+}
+
+
+void NixRepl::addVarToScope(const Symbol & name, Value & v)
+{
+    if (displ >= envSize)
+        throw Error("environment full; cannot add more variables");
+    staticEnv.vars[name] = displ;
+    env->values[displ++] = &v;
+    varNames.insert((string) name);
+}
+
+
+Expr * NixRepl::parseString(string s)
+{
+    Expr * e = state.parseExprFromString(s, curDir, staticEnv);
+    return e;
+}
+
+
+void NixRepl::evalString(string s, Value & v)
+{
+    Expr * e = parseString(s);
+    e->eval(state, *env, v);
+    state.forceValue(v);
+}
+
+
+std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int maxDepth)
+{
+    ValuesSeen seen;
+    return printValue(str, v, maxDepth, seen);
+}
+
+
+std::ostream & printStringValue(std::ostream & str, const char * string) {
+    str << "\"";
+    for (const char * i = string; *i; i++)
+        if (*i == '\"' || *i == '\\') str << "\\" << *i;
+        else if (*i == '\n') str << "\\n";
+        else if (*i == '\r') str << "\\r";
+        else if (*i == '\t') str << "\\t";
+        else str << *i;
+    str << "\"";
+    return str;
+}
+
+
+// FIXME: lot of cut&paste from Nix's eval.cc.
+std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int maxDepth, ValuesSeen & seen)
+{
+    str.flush();
+    checkInterrupt();
+
+    state.forceValue(v);
+
+    switch (v.type) {
+
+    case tInt:
+        str << ESC_CYA << v.integer << ESC_END;
+        break;
+
+    case tBool:
+        str << ESC_CYA << (v.boolean ? "true" : "false") << ESC_END;
+        break;
+
+    case tString:
+        str << ESC_YEL;
+        printStringValue(str, v.string.s);
+        str << ESC_END;
+        break;
+
+    case tPath:
+        str << ESC_GRE << v.path << ESC_END; // !!! escaping?
+        break;
+
+    case tNull:
+        str << ESC_CYA "null" ESC_END;
+        break;
+
+    case tAttrs: {
+        seen.insert(&v);
+
+        bool isDrv = state.isDerivation(v);
+
+        if (isDrv) {
+            str << "«derivation ";
+            Bindings::iterator i = v.attrs->find(state.sDrvPath);
+            PathSet context;
+            Path drvPath = i != v.attrs->end() ? state.coerceToPath(*i->pos, *i->value, context) : "???";
+            str << drvPath << "»";
+        }
+
+        else if (maxDepth > 0) {
+            str << "{ ";
+
+            typedef std::map<string, Value *> Sorted;
+            Sorted sorted;
+            for (auto & i : *v.attrs)
+                sorted[i.name] = i.value;
+
+            for (auto & i : sorted) {
+                if (isVarName(i.first))
+                    str << i.first;
+                else
+                    printStringValue(str, i.first.c_str());
+                str << " = ";
+                if (seen.find(i.second) != seen.end())
+                    str << "«repeated»";
+                else
+                    try {
+                        printValue(str, *i.second, maxDepth - 1, seen);
+                    } catch (AssertionError & e) {
+                        str << ESC_RED "«error: " << e.msg() << "»" ESC_END;
+                    }
+                str << "; ";
+            }
+
+            str << "}";
+        } else
+            str << "{ ... }";
+
+        break;
+    }
+
+    case tList1:
+    case tList2:
+    case tListN:
+        seen.insert(&v);
+
+        str << "[ ";
+        if (maxDepth > 0)
+            for (unsigned int n = 0; n < v.listSize(); ++n) {
+                if (seen.find(v.listElems()[n]) != seen.end())
+                    str << "«repeated»";
+                else
+                    try {
+                        printValue(str, *v.listElems()[n], maxDepth - 1, seen);
+                    } catch (AssertionError & e) {
+                        str << ESC_RED "«error: " << e.msg() << "»" ESC_END;
+                    }
+                str << " ";
+            }
+        else
+            str << "... ";
+        str << "]";
+        break;
+
+    case tLambda: {
+        std::ostringstream s;
+        s << v.lambda.fun->pos;
+        str << ESC_BLU "«lambda @ " << filterANSIEscapes(s.str()) << "»" ESC_END;
+        break;
+    }
+
+    case tPrimOp:
+        str << ESC_MAG "«primop»" ESC_END;
+        break;
+
+    case tPrimOpApp:
+        str << ESC_BLU "«primop-app»" ESC_END;
+        break;
+
+    case tFloat:
+        str << v.fpoint;
+        break;
+
+    default:
+        str << ESC_RED "«unknown»" ESC_END;
+        break;
+    }
+
+    return str;
+}
+
+struct CmdRepl : StoreCommand, MixEvalArgs
+{
+    std::vector<std::string> files;
+
+    CmdRepl()
+    {
+        expectArgs("files", &files);
+    }
+
+    std::string name() override { return "repl"; }
+
+    std::string description() override
+    {
+        return "start an interactive environment for evaluating Nix expressions";
+    }
+
+    void run(ref<Store> store) override
+    {
+        auto repl = std::make_unique<NixRepl>(searchPath, openStore());
+        repl->autoArgs = getAutoArgs(repl->state);
+        repl->mainLoop(files);
+    }
+};
+
+static RegisterCommand r1(make_ref<CmdRepl>());
+
+}
diff --git a/third_party/nix/src/nix/run.cc b/third_party/nix/src/nix/run.cc
new file mode 100644
index 0000000000..90b76d6663
--- /dev/null
+++ b/third_party/nix/src/nix/run.cc
@@ -0,0 +1,260 @@
+#include "command.hh"
+#include "common-args.hh"
+#include "shared.hh"
+#include "store-api.hh"
+#include "derivations.hh"
+#include "local-store.hh"
+#include "finally.hh"
+#include "fs-accessor.hh"
+#include "progress-bar.hh"
+#include "affinity.hh"
+
+#if __linux__
+#include <sys/mount.h>
+#endif
+
+#include <queue>
+
+using namespace nix;
+
+std::string chrootHelperName = "__run_in_chroot";
+
+struct CmdRun : InstallablesCommand
+{
+    std::vector<std::string> command = { "bash" };
+    StringSet keep, unset;
+    bool ignoreEnvironment = false;
+
+    CmdRun()
+    {
+        mkFlag()
+            .longName("command")
+            .shortName('c')
+            .description("command and arguments to be executed; defaults to 'bash'")
+            .labels({"command", "args"})
+            .arity(ArityAny)
+            .handler([&](std::vector<std::string> ss) {
+                if (ss.empty()) throw UsageError("--command requires at least one argument");
+                command = ss;
+            });
+
+        mkFlag()
+            .longName("ignore-environment")
+            .shortName('i')
+            .description("clear the entire environment (except those specified with --keep)")
+            .set(&ignoreEnvironment, true);
+
+        mkFlag()
+            .longName("keep")
+            .shortName('k')
+            .description("keep specified environment variable")
+            .arity(1)
+            .labels({"name"})
+            .handler([&](std::vector<std::string> ss) { keep.insert(ss.front()); });
+
+        mkFlag()
+            .longName("unset")
+            .shortName('u')
+            .description("unset specified environment variable")
+            .arity(1)
+            .labels({"name"})
+            .handler([&](std::vector<std::string> ss) { unset.insert(ss.front()); });
+    }
+
+    std::string name() override
+    {
+        return "run";
+    }
+
+    std::string description() override
+    {
+        return "run a shell in which the specified packages are available";
+    }
+
+    Examples examples() override
+    {
+        return {
+            Example{
+                "To start a shell providing GNU Hello from NixOS 17.03:",
+                "nix run -f channel:nixos-17.03 hello"
+            },
+            Example{
+                "To start a shell providing youtube-dl from your 'nixpkgs' channel:",
+                "nix run nixpkgs.youtube-dl"
+            },
+            Example{
+                "To run GNU Hello:",
+                "nix run nixpkgs.hello -c hello --greeting 'Hi everybody!'"
+            },
+            Example{
+                "To run GNU Hello in a chroot store:",
+                "nix run --store ~/my-nix nixpkgs.hello -c hello"
+            },
+        };
+    }
+
+    void run(ref<Store> store) override
+    {
+        auto outPaths = toStorePaths(store, Build, installables);
+
+        auto accessor = store->getFSAccessor();
+
+        if (ignoreEnvironment) {
+
+            if (!unset.empty())
+                throw UsageError("--unset does not make sense with --ignore-environment");
+
+            std::map<std::string, std::string> kept;
+            for (auto & var : keep) {
+                auto s = getenv(var.c_str());
+                if (s) kept[var] = s;
+            }
+
+            clearEnv();
+
+            for (auto & var : kept)
+                setenv(var.first.c_str(), var.second.c_str(), 1);
+
+        } else {
+
+            if (!keep.empty())
+                throw UsageError("--keep does not make sense without --ignore-environment");
+
+            for (auto & var : unset)
+                unsetenv(var.c_str());
+        }
+
+        std::unordered_set<Path> done;
+        std::queue<Path> todo;
+        for (auto & path : outPaths) todo.push(path);
+
+        auto unixPath = tokenizeString<Strings>(getEnv("PATH"), ":");
+
+        while (!todo.empty()) {
+            Path path = todo.front();
+            todo.pop();
+            if (!done.insert(path).second) continue;
+
+            if (true)
+                unixPath.push_front(path + "/bin");
+
+            auto propPath = path + "/nix-support/propagated-user-env-packages";
+            if (accessor->stat(propPath).type == FSAccessor::tRegular) {
+                for (auto & p : tokenizeString<Paths>(readFile(propPath)))
+                    todo.push(p);
+            }
+        }
+
+        setenv("PATH", concatStringsSep(":", unixPath).c_str(), 1);
+
+        std::string cmd = *command.begin();
+        Strings args;
+        for (auto & arg : command) args.push_back(arg);
+
+        stopProgressBar();
+
+        restoreSignals();
+
+        restoreAffinity();
+
+        /* If this is a diverted store (i.e. its "logical" location
+           (typically /nix/store) differs from its "physical" location
+           (e.g. /home/eelco/nix/store), then run the command in a
+           chroot. For non-root users, this requires running it in new
+           mount and user namespaces. Unfortunately,
+           unshare(CLONE_NEWUSER) doesn't work in a multithreaded
+           program (which "nix" is), so we exec() a single-threaded
+           helper program (chrootHelper() below) to do the work. */
+        auto store2 = store.dynamic_pointer_cast<LocalStore>();
+
+        if (store2 && store->storeDir != store2->realStoreDir) {
+            Strings helperArgs = { chrootHelperName, store->storeDir, store2->realStoreDir, cmd };
+            for (auto & arg : args) helperArgs.push_back(arg);
+
+            execv(readLink("/proc/self/exe").c_str(), stringsToCharPtrs(helperArgs).data());
+
+            throw SysError("could not execute chroot helper");
+        }
+
+        execvp(cmd.c_str(), stringsToCharPtrs(args).data());
+
+        throw SysError("unable to exec '%s'", cmd);
+    }
+};
+
+static RegisterCommand r1(make_ref<CmdRun>());
+
+void chrootHelper(int argc, char * * argv)
+{
+    int p = 1;
+    std::string storeDir = argv[p++];
+    std::string realStoreDir = argv[p++];
+    std::string cmd = argv[p++];
+    Strings args;
+    while (p < argc)
+        args.push_back(argv[p++]);
+
+#if __linux__
+    uid_t uid = getuid();
+    uid_t gid = getgid();
+
+    if (unshare(CLONE_NEWUSER | CLONE_NEWNS) == -1)
+        /* Try with just CLONE_NEWNS in case user namespaces are
+           specifically disabled. */
+        if (unshare(CLONE_NEWNS) == -1)
+            throw SysError("setting up a private mount namespace");
+
+    /* Bind-mount realStoreDir on /nix/store. If the latter mount
+       point doesn't already exists, we have to create a chroot
+       environment containing the mount point and bind mounts for the
+       children of /. Would be nice if we could use overlayfs here,
+       but that doesn't work in a user namespace yet (Ubuntu has a
+       patch for this:
+       https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1478578). */
+    if (!pathExists(storeDir)) {
+        // FIXME: Use overlayfs?
+
+        Path tmpDir = createTempDir();
+
+        createDirs(tmpDir + storeDir);
+
+        if (mount(realStoreDir.c_str(), (tmpDir + storeDir).c_str(), "", MS_BIND, 0) == -1)
+            throw SysError("mounting '%s' on '%s'", realStoreDir, storeDir);
+
+        for (auto entry : readDirectory("/")) {
+            auto src = "/" + entry.name;
+            auto st = lstat(src);
+            if (!S_ISDIR(st.st_mode)) continue;
+            Path dst = tmpDir + "/" + entry.name;
+            if (pathExists(dst)) continue;
+            if (mkdir(dst.c_str(), 0700) == -1)
+                throw SysError("creating directory '%s'", dst);
+            if (mount(src.c_str(), dst.c_str(), "", MS_BIND | MS_REC, 0) == -1)
+                throw SysError("mounting '%s' on '%s'", src, dst);
+        }
+
+        char * cwd = getcwd(0, 0);
+        if (!cwd) throw SysError("getting current directory");
+        Finally freeCwd([&]() { free(cwd); });
+
+        if (chroot(tmpDir.c_str()) == -1)
+            throw SysError(format("chrooting into '%s'") % tmpDir);
+
+        if (chdir(cwd) == -1)
+            throw SysError(format("chdir to '%s' in chroot") % cwd);
+    } else
+        if (mount(realStoreDir.c_str(), storeDir.c_str(), "", MS_BIND, 0) == -1)
+            throw SysError("mounting '%s' on '%s'", realStoreDir, storeDir);
+
+    writeFile("/proc/self/setgroups", "deny");
+    writeFile("/proc/self/uid_map", fmt("%d %d %d", uid, uid, 1));
+    writeFile("/proc/self/gid_map", fmt("%d %d %d", gid, gid, 1));
+
+    execvp(cmd.c_str(), stringsToCharPtrs(args).data());
+
+    throw SysError("unable to exec '%s'", cmd);
+
+#else
+    throw Error("mounting the Nix store on '%s' is not supported on this platform", storeDir);
+#endif
+}
diff --git a/third_party/nix/src/nix/search.cc b/third_party/nix/src/nix/search.cc
new file mode 100644
index 0000000000..eb75493e47
--- /dev/null
+++ b/third_party/nix/src/nix/search.cc
@@ -0,0 +1,280 @@
+#include "command.hh"
+#include "globals.hh"
+#include "eval.hh"
+#include "eval-inline.hh"
+#include "names.hh"
+#include "get-drvs.hh"
+#include "common-args.hh"
+#include "json.hh"
+#include "json-to-value.hh"
+#include "shared.hh"
+
+#include <regex>
+#include <fstream>
+
+using namespace nix;
+
+std::string wrap(std::string prefix, std::string s)
+{
+    return prefix + s + ANSI_NORMAL;
+}
+
+std::string hilite(const std::string & s, const std::smatch & m, std::string postfix)
+{
+    return
+        m.empty()
+        ? s
+        : std::string(m.prefix())
+          + ANSI_RED + std::string(m.str()) + postfix
+          + std::string(m.suffix());
+}
+
+struct CmdSearch : SourceExprCommand, MixJSON
+{
+    std::vector<std::string> res;
+
+    bool writeCache = true;
+    bool useCache = true;
+
+    CmdSearch()
+    {
+        expectArgs("regex", &res);
+
+        mkFlag()
+            .longName("update-cache")
+            .shortName('u')
+            .description("update the package search cache")
+            .handler([&]() { writeCache = true; useCache = false; });
+
+        mkFlag()
+            .longName("no-cache")
+            .description("do not use or update the package search cache")
+            .handler([&]() { writeCache = false; useCache = false; });
+    }
+
+    std::string name() override
+    {
+        return "search";
+    }
+
+    std::string description() override
+    {
+        return "query available packages";
+    }
+
+    Examples examples() override
+    {
+        return {
+            Example{
+                "To show all available packages:",
+                "nix search"
+            },
+            Example{
+                "To show any packages containing 'blender' in its name or description:",
+                "nix search blender"
+            },
+            Example{
+                "To search for Firefox or Chromium:",
+                "nix search 'firefox|chromium'"
+            },
+            Example{
+                "To search for git and frontend or gui:",
+                "nix search git 'frontend|gui'"
+            }
+        };
+    }
+
+    void run(ref<Store> store) override
+    {
+        settings.readOnlyMode = true;
+
+        // Empty search string should match all packages
+        // Use "^" here instead of ".*" due to differences in resulting highlighting
+        // (see #1893 -- libc++ claims empty search string is not in POSIX grammar)
+        if (res.empty()) {
+            res.push_back("^");
+        }
+
+        std::vector<std::regex> regexes;
+        regexes.reserve(res.size());
+
+        for (auto &re : res) {
+            regexes.push_back(std::regex(re, std::regex::extended | std::regex::icase));
+        }
+
+        auto state = getEvalState();
+
+        auto jsonOut = json ? std::make_unique<JSONObject>(std::cout) : nullptr;
+
+        auto sToplevel = state->symbols.create("_toplevel");
+        auto sRecurse = state->symbols.create("recurseForDerivations");
+
+        bool fromCache = false;
+
+        std::map<std::string, std::string> results;
+
+        std::function<void(Value *, std::string, bool, JSONObject *)> doExpr;
+
+        doExpr = [&](Value * v, std::string attrPath, bool toplevel, JSONObject * cache) {
+            debug("at attribute '%s'", attrPath);
+
+            try {
+                uint found = 0;
+
+                state->forceValue(*v);
+
+                if (v->type == tLambda && toplevel) {
+                    Value * v2 = state->allocValue();
+                    state->autoCallFunction(*state->allocBindings(1), *v, *v2);
+                    v = v2;
+                    state->forceValue(*v);
+                }
+
+                if (state->isDerivation(*v)) {
+
+                    DrvInfo drv(*state, attrPath, v->attrs);
+                    std::string description;
+                    std::smatch attrPathMatch;
+                    std::smatch descriptionMatch;
+                    std::smatch nameMatch;
+                    std::string name;
+
+                    DrvName parsed(drv.queryName());
+
+                    for (auto &regex : regexes) {
+                        std::regex_search(attrPath, attrPathMatch, regex);
+
+                        name = parsed.name;
+                        std::regex_search(name, nameMatch, regex);
+
+                        description = drv.queryMetaString("description");
+                        std::replace(description.begin(), description.end(), '\n', ' ');
+                        std::regex_search(description, descriptionMatch, regex);
+
+                        if (!attrPathMatch.empty()
+                            || !nameMatch.empty()
+                            || !descriptionMatch.empty())
+                        {
+                            found++;
+                        }
+                    }
+
+                    if (found == res.size()) {
+                        if (json) {
+
+                            auto jsonElem = jsonOut->object(attrPath);
+
+                            jsonElem.attr("pkgName", parsed.name);
+                            jsonElem.attr("version", parsed.version);
+                            jsonElem.attr("description", description);
+
+                        } else {
+                            auto name = hilite(parsed.name, nameMatch, "\e[0;2m")
+                                + std::string(parsed.fullName, parsed.name.length());
+                            results[attrPath] = fmt(
+                                "* %s (%s)\n  %s\n",
+                                wrap("\e[0;1m", hilite(attrPath, attrPathMatch, "\e[0;1m")),
+                                wrap("\e[0;2m", hilite(name, nameMatch, "\e[0;2m")),
+                                hilite(description, descriptionMatch, ANSI_NORMAL));
+                        }
+                    }
+
+                    if (cache) {
+                        cache->attr("type", "derivation");
+                        cache->attr("name", drv.queryName());
+                        cache->attr("system", drv.querySystem());
+                        if (description != "") {
+                            auto meta(cache->object("meta"));
+                            meta.attr("description", description);
+                        }
+                    }
+                }
+
+                else if (v->type == tAttrs) {
+
+                    if (!toplevel) {
+                        auto attrs = v->attrs;
+                        Bindings::iterator j = attrs->find(sRecurse);
+                        if (j == attrs->end() || !state->forceBool(*j->value, *j->pos)) {
+                            debug("skip attribute '%s'", attrPath);
+                            return;
+                        }
+                    }
+
+                    bool toplevel2 = false;
+                    if (!fromCache) {
+                        Bindings::iterator j = v->attrs->find(sToplevel);
+                        toplevel2 = j != v->attrs->end() && state->forceBool(*j->value, *j->pos);
+                    }
+
+                    for (auto & i : *v->attrs) {
+                        auto cache2 =
+                            cache ? std::make_unique<JSONObject>(cache->object(i.name)) : nullptr;
+                        doExpr(i.value,
+                            attrPath == "" ? (std::string) i.name : attrPath + "." + (std::string) i.name,
+                            toplevel2 || fromCache, cache2 ? cache2.get() : nullptr);
+                    }
+                }
+
+            } catch (AssertionError & e) {
+            } catch (Error & e) {
+                if (!toplevel) {
+                    e.addPrefix(fmt("While evaluating the attribute '%s':\n", attrPath));
+                    throw;
+                }
+            }
+        };
+
+        Path jsonCacheFileName = getCacheDir() + "/nix/package-search.json";
+
+        if (useCache && pathExists(jsonCacheFileName)) {
+
+            warn("using cached results; pass '-u' to update the cache");
+
+            Value vRoot;
+            parseJSON(*state, readFile(jsonCacheFileName), vRoot);
+
+            fromCache = true;
+
+            doExpr(&vRoot, "", true, nullptr);
+        }
+
+        else {
+            createDirs(dirOf(jsonCacheFileName));
+
+            Path tmpFile = fmt("%s.tmp.%d", jsonCacheFileName, getpid());
+
+            std::ofstream jsonCacheFile;
+
+            try {
+                // iostream considered harmful
+                jsonCacheFile.exceptions(std::ofstream::failbit);
+                jsonCacheFile.open(tmpFile);
+
+                auto cache = writeCache ? std::make_unique<JSONObject>(jsonCacheFile, false) : nullptr;
+
+                doExpr(getSourceExpr(*state), "", true, cache.get());
+
+            } catch (std::exception &) {
+                /* Fun fact: catching std::ios::failure does not work
+                   due to C++11 ABI shenanigans.
+                   https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66145 */
+                if (!jsonCacheFile)
+                    throw Error("error writing to %s", tmpFile);
+                throw;
+            }
+
+            if (writeCache && rename(tmpFile.c_str(), jsonCacheFileName.c_str()) == -1)
+                throw SysError("cannot rename '%s' to '%s'", tmpFile, jsonCacheFileName);
+        }
+
+        if (results.size() == 0)
+            throw Error("no results for the given search term(s)!");
+
+        RunPager pager;
+        for (auto el : results) std::cout << el.second << "\n";
+
+    }
+};
+
+static RegisterCommand r1(make_ref<CmdSearch>());
diff --git a/third_party/nix/src/nix/show-config.cc b/third_party/nix/src/nix/show-config.cc
new file mode 100644
index 0000000000..86638b50d2
--- /dev/null
+++ b/third_party/nix/src/nix/show-config.cc
@@ -0,0 +1,40 @@
+#include "command.hh"
+#include "common-args.hh"
+#include "shared.hh"
+#include "store-api.hh"
+#include "json.hh"
+
+using namespace nix;
+
+struct CmdShowConfig : Command, MixJSON
+{
+    CmdShowConfig()
+    {
+    }
+
+    std::string name() override
+    {
+        return "show-config";
+    }
+
+    std::string description() override
+    {
+        return "show the Nix configuration";
+    }
+
+    void run() override
+    {
+        if (json) {
+            // FIXME: use appropriate JSON types (bool, ints, etc).
+            JSONObject jsonObj(std::cout);
+            globalConfig.toJSON(jsonObj);
+        } else {
+            std::map<std::string, Config::SettingInfo> settings;
+            globalConfig.getSettings(settings);
+            for (auto & s : settings)
+                std::cout << s.first + " = " + s.second.value + "\n";
+        }
+    }
+};
+
+static RegisterCommand r1(make_ref<CmdShowConfig>());
diff --git a/third_party/nix/src/nix/show-derivation.cc b/third_party/nix/src/nix/show-derivation.cc
new file mode 100644
index 0000000000..ee94fded36
--- /dev/null
+++ b/third_party/nix/src/nix/show-derivation.cc
@@ -0,0 +1,119 @@
+// FIXME: integrate this with nix path-info?
+
+#include "command.hh"
+#include "common-args.hh"
+#include "store-api.hh"
+#include "archive.hh"
+#include "json.hh"
+#include "derivations.hh"
+
+using namespace nix;
+
+struct CmdShowDerivation : InstallablesCommand
+{
+    bool recursive = false;
+
+    CmdShowDerivation()
+    {
+        mkFlag()
+            .longName("recursive")
+            .shortName('r')
+            .description("include the dependencies of the specified derivations")
+            .set(&recursive, true);
+    }
+
+    std::string name() override
+    {
+        return "show-derivation";
+    }
+
+    std::string description() override
+    {
+        return "show the contents of a store derivation";
+    }
+
+    Examples examples() override
+    {
+        return {
+            Example{
+                "To show the store derivation that results from evaluating the Hello package:",
+                "nix show-derivation nixpkgs.hello"
+            },
+            Example{
+                "To show the full derivation graph (if available) that produced your NixOS system:",
+                "nix show-derivation -r /run/current-system"
+            },
+        };
+    }
+
+    void run(ref<Store> store) override
+    {
+        auto drvPaths = toDerivations(store, installables, true);
+
+        if (recursive) {
+            PathSet closure;
+            store->computeFSClosure(drvPaths, closure);
+            drvPaths = closure;
+        }
+
+        {
+
+        JSONObject jsonRoot(std::cout, true);
+
+        for (auto & drvPath : drvPaths) {
+            if (!isDerivation(drvPath)) continue;
+
+            auto drvObj(jsonRoot.object(drvPath));
+
+            auto drv = readDerivation(drvPath);
+
+            {
+                auto outputsObj(drvObj.object("outputs"));
+                for (auto & output : drv.outputs) {
+                    auto outputObj(outputsObj.object(output.first));
+                    outputObj.attr("path", output.second.path);
+                    if (output.second.hash != "") {
+                        outputObj.attr("hashAlgo", output.second.hashAlgo);
+                        outputObj.attr("hash", output.second.hash);
+                    }
+                }
+            }
+
+            {
+                auto inputsList(drvObj.list("inputSrcs"));
+                for (auto & input : drv.inputSrcs)
+                    inputsList.elem(input);
+            }
+
+            {
+                auto inputDrvsObj(drvObj.object("inputDrvs"));
+                for (auto & input : drv.inputDrvs) {
+                    auto inputList(inputDrvsObj.list(input.first));
+                    for (auto & outputId : input.second)
+                        inputList.elem(outputId);
+                }
+            }
+
+            drvObj.attr("platform", drv.platform);
+            drvObj.attr("builder", drv.builder);
+
+            {
+                auto argsList(drvObj.list("args"));
+                for (auto & arg : drv.args)
+                    argsList.elem(arg);
+            }
+
+            {
+                auto envObj(drvObj.object("env"));
+                for (auto & var : drv.env)
+                    envObj.attr(var.first, var.second);
+            }
+        }
+
+        }
+
+        std::cout << "\n";
+    }
+};
+
+static RegisterCommand r1(make_ref<CmdShowDerivation>());
diff --git a/third_party/nix/src/nix/sigs.cc b/third_party/nix/src/nix/sigs.cc
new file mode 100644
index 0000000000..b1825c412c
--- /dev/null
+++ b/third_party/nix/src/nix/sigs.cc
@@ -0,0 +1,149 @@
+#include "command.hh"
+#include "shared.hh"
+#include "store-api.hh"
+#include "thread-pool.hh"
+
+#include <atomic>
+
+using namespace nix;
+
+struct CmdCopySigs : StorePathsCommand
+{
+    Strings substituterUris;
+
+    CmdCopySigs()
+    {
+        mkFlag()
+            .longName("substituter")
+            .shortName('s')
+            .labels({"store-uri"})
+            .description("use signatures from specified store")
+            .arity(1)
+            .handler([&](std::vector<std::string> ss) { substituterUris.push_back(ss[0]); });
+    }
+
+    std::string name() override
+    {
+        return "copy-sigs";
+    }
+
+    std::string description() override
+    {
+        return "copy path signatures from substituters (like binary caches)";
+    }
+
+    void run(ref<Store> store, Paths storePaths) override
+    {
+        if (substituterUris.empty())
+            throw UsageError("you must specify at least one substituter using '-s'");
+
+        // FIXME: factor out commonality with MixVerify.
+        std::vector<ref<Store>> substituters;
+        for (auto & s : substituterUris)
+            substituters.push_back(openStore(s));
+
+        ThreadPool pool;
+
+        std::string doneLabel = "done";
+        std::atomic<size_t> added{0};
+
+        //logger->setExpected(doneLabel, storePaths.size());
+
+        auto doPath = [&](const Path & storePath) {
+            //Activity act(*logger, lvlInfo, format("getting signatures for '%s'") % storePath);
+
+            checkInterrupt();
+
+            auto info = store->queryPathInfo(storePath);
+
+            StringSet newSigs;
+
+            for (auto & store2 : substituters) {
+                try {
+                    auto info2 = store2->queryPathInfo(storePath);
+
+                    /* Don't import signatures that don't match this
+                       binary. */
+                    if (info->narHash != info2->narHash ||
+                        info->narSize != info2->narSize ||
+                        info->references != info2->references)
+                        continue;
+
+                    for (auto & sig : info2->sigs)
+                        if (!info->sigs.count(sig))
+                            newSigs.insert(sig);
+                } catch (InvalidPath &) {
+                }
+            }
+
+            if (!newSigs.empty()) {
+                store->addSignatures(storePath, newSigs);
+                added += newSigs.size();
+            }
+
+            //logger->incProgress(doneLabel);
+        };
+
+        for (auto & storePath : storePaths)
+            pool.enqueue(std::bind(doPath, storePath));
+
+        pool.process();
+
+        printInfo(format("imported %d signatures") % added);
+    }
+};
+
+static RegisterCommand r1(make_ref<CmdCopySigs>());
+
+struct CmdSignPaths : StorePathsCommand
+{
+    Path secretKeyFile;
+
+    CmdSignPaths()
+    {
+        mkFlag()
+            .shortName('k')
+            .longName("key-file")
+            .label("file")
+            .description("file containing the secret signing key")
+            .dest(&secretKeyFile);
+    }
+
+    std::string name() override
+    {
+        return "sign-paths";
+    }
+
+    std::string description() override
+    {
+        return "sign the specified paths";
+    }
+
+    void run(ref<Store> store, Paths storePaths) override
+    {
+        if (secretKeyFile.empty())
+            throw UsageError("you must specify a secret key file using '-k'");
+
+        SecretKey secretKey(readFile(secretKeyFile));
+
+        size_t added{0};
+
+        for (auto & storePath : storePaths) {
+            auto info = store->queryPathInfo(storePath);
+
+            auto info2(*info);
+            info2.sigs.clear();
+            info2.sign(secretKey);
+            assert(!info2.sigs.empty());
+
+            if (!info->sigs.count(*info2.sigs.begin())) {
+                store->addSignatures(storePath, info2.sigs);
+                added++;
+            }
+        }
+
+        printInfo(format("added %d signatures") % added);
+    }
+};
+
+static RegisterCommand r3(make_ref<CmdSignPaths>());
diff --git a/third_party/nix/src/nix/upgrade-nix.cc b/third_party/nix/src/nix/upgrade-nix.cc
new file mode 100644
index 0000000000..35c44a70cf
--- /dev/null
+++ b/third_party/nix/src/nix/upgrade-nix.cc
@@ -0,0 +1,160 @@
+#include "command.hh"
+#include "common-args.hh"
+#include "store-api.hh"
+#include "download.hh"
+#include "eval.hh"
+#include "attr-path.hh"
+#include "names.hh"
+#include "progress-bar.hh"
+
+using namespace nix;
+
+struct CmdUpgradeNix : MixDryRun, StoreCommand
+{
+    Path profileDir;
+    std::string storePathsUrl = "https://github.com/NixOS/nixpkgs/raw/master/nixos/modules/installer/tools/nix-fallback-paths.nix";
+
+    CmdUpgradeNix()
+    {
+        mkFlag()
+            .longName("profile")
+            .shortName('p')
+            .labels({"profile-dir"})
+            .description("the Nix profile to upgrade")
+            .dest(&profileDir);
+
+        mkFlag()
+            .longName("nix-store-paths-url")
+            .labels({"url"})
+            .description("URL of the file that contains the store paths of the latest Nix release")
+            .dest(&storePathsUrl);
+    }
+
+    std::string name() override
+    {
+        return "upgrade-nix";
+    }
+
+    std::string description() override
+    {
+        return "upgrade Nix to the latest stable version";
+    }
+
+    Examples examples() override
+    {
+        return {
+            Example{
+                "To upgrade Nix to the latest stable version:",
+                "nix upgrade-nix"
+            },
+            Example{
+                "To upgrade Nix in a specific profile:",
+                "nix upgrade-nix -p /nix/var/nix/profiles/per-user/alice/profile"
+            },
+        };
+    }
+
+    void run(ref<Store> store) override
+    {
+        evalSettings.pureEval = true;
+
+        if (profileDir == "")
+            profileDir = getProfileDir(store);
+
+        printInfo("upgrading Nix in profile '%s'", profileDir);
+
+        Path storePath;
+        {
+            Activity act(*logger, lvlInfo, actUnknown, "querying latest Nix version");
+            storePath = getLatestNix(store);
+        }
+
+        auto version = DrvName(storePathToName(storePath)).version;
+
+        if (dryRun) {
+            stopProgressBar();
+            printError("would upgrade to version %s", version);
+            return;
+        }
+
+        {
+            Activity act(*logger, lvlInfo, actUnknown, fmt("downloading '%s'...", storePath));
+            store->ensurePath(storePath);
+        }
+
+        {
+            Activity act(*logger, lvlInfo, actUnknown, fmt("verifying that '%s' works...", storePath));
+            auto program = storePath + "/bin/nix-env";
+            auto s = runProgram(program, false, {"--version"});
+            if (s.find("Nix") == std::string::npos)
+                throw Error("could not verify that '%s' works", program);
+        }
+
+        stopProgressBar();
+
+        {
+            Activity act(*logger, lvlInfo, actUnknown, fmt("installing '%s' into profile '%s'...", storePath, profileDir));
+            runProgram(settings.nixBinDir + "/nix-env", false,
+                {"--profile", profileDir, "-i", storePath, "--no-sandbox"});
+        }
+
+        printError(ANSI_GREEN "upgrade to version %s done" ANSI_NORMAL, version);
+    }
+
+    /* Return the profile in which Nix is installed. */
+    Path getProfileDir(ref<Store> store)
+    {
+        Path where;
+
+        for (auto & dir : tokenizeString<Strings>(getEnv("PATH"), ":"))
+            if (pathExists(dir + "/nix-env")) {
+                where = dir;
+                break;
+            }
+
+        if (where == "")
+            throw Error("couldn't figure out how Nix is installed, so I can't upgrade it");
+
+        printInfo("found Nix in '%s'", where);
+
+        if (hasPrefix(where, "/run/current-system"))
+            throw Error("Nix on NixOS must be upgraded via 'nixos-rebuild'");
+
+        Path profileDir = dirOf(where);
+
+        // Resolve profile to /nix/var/nix/profiles/<name> link.
+        while (canonPath(profileDir).find("/profiles/") == std::string::npos && isLink(profileDir))
+            profileDir = readLink(profileDir);
+
+        printInfo("found profile '%s'", profileDir);
+
+        Path userEnv = canonPath(profileDir, true);
+
+        if (baseNameOf(where) != "bin" ||
+            !hasSuffix(userEnv, "user-environment"))
+            throw Error("directory '%s' does not appear to be part of a Nix profile", where);
+
+        if (!store->isValidPath(userEnv))
+            throw Error("directory '%s' is not in the Nix store", userEnv);
+
+        return profileDir;
+    }
+
+    /* Return the store path of the latest stable Nix. */
+    Path getLatestNix(ref<Store> store)
+    {
+        // FIXME: use nixos.org?
+        auto req = DownloadRequest(storePathsUrl);
+        auto res = getDownloader()->download(req);
+
+        auto state = std::make_unique<EvalState>(Strings(), store);
+        auto v = state->allocValue();
+        state->eval(state->parseExprFromString(*res.data, "/no-such-path"), *v);
+        Bindings & bindings(*state->allocBindings(0));
+        auto v2 = findAlongAttrPath(*state, settings.thisSystem, bindings, *v);
+
+        return state->forceString(*v2);
+    }
+};
+
+static RegisterCommand r1(make_ref<CmdUpgradeNix>());
diff --git a/third_party/nix/src/nix/verify.cc b/third_party/nix/src/nix/verify.cc
new file mode 100644
index 0000000000..8893fded5e
--- /dev/null
+++ b/third_party/nix/src/nix/verify.cc
@@ -0,0 +1,178 @@
+#include "command.hh"
+#include "shared.hh"
+#include "store-api.hh"
+#include "sync.hh"
+#include "thread-pool.hh"
+
+#include <atomic>
+
+using namespace nix;
+
+struct CmdVerify : StorePathsCommand
+{
+    bool noContents = false;
+    bool noTrust = false;
+    Strings substituterUris;
+    size_t sigsNeeded = 0;
+
+    CmdVerify()
+    {
+        mkFlag(0, "no-contents", "do not verify the contents of each store path", &noContents);
+        mkFlag(0, "no-trust", "do not verify whether each store path is trusted", &noTrust);
+        mkFlag()
+            .longName("substituter")
+            .shortName('s')
+            .labels({"store-uri"})
+            .description("use signatures from specified store")
+            .arity(1)
+            .handler([&](std::vector<std::string> ss) { substituterUris.push_back(ss[0]); });
+        mkIntFlag('n', "sigs-needed", "require that each path has at least N valid signatures", &sigsNeeded);
+    }
+
+    std::string name() override
+    {
+        return "verify";
+    }
+
+    std::string description() override
+    {
+        return "verify the integrity of store paths";
+    }
+
+    Examples examples() override
+    {
+        return {
+            Example{
+                "To verify the entire Nix store:",
+                "nix verify --all"
+            },
+            Example{
+                "To check whether each path in the closure of Firefox has at least 2 signatures:",
+                "nix verify -r -n2 --no-contents $(type -p firefox)"
+            },
+        };
+    }
+
+    void run(ref<Store> store, Paths storePaths) override
+    {
+        std::vector<ref<Store>> substituters;
+        for (auto & s : substituterUris)
+            substituters.push_back(openStore(s));
+
+        auto publicKeys = getDefaultPublicKeys();
+
+        Activity act(*logger, actVerifyPaths);
+
+        std::atomic<size_t> done{0};
+        std::atomic<size_t> untrusted{0};
+        std::atomic<size_t> corrupted{0};
+        std::atomic<size_t> failed{0};
+        std::atomic<size_t> active{0};
+
+        auto update = [&]() {
+            act.progress(done, storePaths.size(), active, failed);
+        };
+
+        ThreadPool pool;
+
+        auto doPath = [&](const Path & storePath) {
+            try {
+                checkInterrupt();
+
+                Activity act2(*logger, lvlInfo, actUnknown, fmt("checking '%s'", storePath));
+
+                MaintainCount<std::atomic<size_t>> mcActive(active);
+                update();
+
+                auto info = store->queryPathInfo(storePath);
+
+                if (!noContents) {
+
+                    HashSink sink(info->narHash.type);
+                    store->narFromPath(info->path, sink);
+
+                    auto hash = sink.finish();
+
+                    if (hash.first != info->narHash) {
+                        corrupted++;
+                        act2.result(resCorruptedPath, info->path);
+                        printError(
+                            format("path '%s' was modified! expected hash '%s', got '%s'")
+                            % info->path % info->narHash.to_string() % hash.first.to_string());
+                    }
+
+                }
+
+                if (!noTrust) {
+
+                    bool good = false;
+
+                    if (info->ultimate && !sigsNeeded)
+                        good = true;
+
+                    else {
+
+                        StringSet sigsSeen;
+                        size_t actualSigsNeeded = std::max(sigsNeeded, (size_t) 1);
+                        size_t validSigs = 0;
+
+                        auto doSigs = [&](StringSet sigs) {
+                            for (auto sig : sigs) {
+                                if (sigsSeen.count(sig)) continue;
+                                sigsSeen.insert(sig);
+                                if (validSigs < ValidPathInfo::maxSigs && info->checkSignature(publicKeys, sig))
+                                    validSigs++;
+                            }
+                        };
+
+                        if (info->isContentAddressed(*store)) validSigs = ValidPathInfo::maxSigs;
+
+                        doSigs(info->sigs);
+
+                        for (auto & store2 : substituters) {
+                            if (validSigs >= actualSigsNeeded) break;
+                            try {
+                                auto info2 = store2->queryPathInfo(info->path);
+                                if (info2->isContentAddressed(*store)) validSigs = ValidPathInfo::maxSigs;
+                                doSigs(info2->sigs);
+                            } catch (InvalidPath &) {
+                            } catch (Error & e) {
+                                printError(format(ANSI_RED "error:" ANSI_NORMAL " %s") % e.what());
+                            }
+                        }
+
+                        if (validSigs >= actualSigsNeeded)
+                            good = true;
+                    }
+
+                    if (!good) {
+                        untrusted++;
+                        act2.result(resUntrustedPath, info->path);
+                        printError(format("path '%s' is untrusted") % info->path);
+                    }
+
+                }
+
+                done++;
+
+            } catch (Error & e) {
+                printError(format(ANSI_RED "error:" ANSI_NORMAL " %s") % e.what());
+                failed++;
+            }
+
+            update();
+        };
+
+        for (auto & storePath : storePaths)
+            pool.enqueue(std::bind(doPath, storePath));
+
+        pool.process();
+
+        throw Exit(
+            (corrupted ? 1 : 0) |
+            (untrusted ? 2 : 0) |
+            (failed ? 4 : 0));
+    }
+};
+
+static RegisterCommand r1(make_ref<CmdVerify>());
diff --git a/third_party/nix/src/nix/why-depends.cc b/third_party/nix/src/nix/why-depends.cc
new file mode 100644
index 0000000000..325a2be0a7
--- /dev/null
+++ b/third_party/nix/src/nix/why-depends.cc
@@ -0,0 +1,267 @@
+#include "command.hh"
+#include "store-api.hh"
+#include "progress-bar.hh"
+#include "fs-accessor.hh"
+#include "shared.hh"
+
+#include <queue>
+
+using namespace nix;
+
+static std::string hilite(const std::string & s, size_t pos, size_t len,
+    const std::string & colour = ANSI_RED)
+{
+    return
+        std::string(s, 0, pos)
+        + colour
+        + std::string(s, pos, len)
+        + ANSI_NORMAL
+        + std::string(s, pos + len);
+}
+
+static std::string filterPrintable(const std::string & s)
+{
+    std::string res;
+    for (char c : s)
+        res += isprint(c) ? c : '.';
+    return res;
+}
+
+struct CmdWhyDepends : SourceExprCommand
+{
+    std::string _package, _dependency;
+    bool all = false;
+
+    CmdWhyDepends()
+    {
+        expectArg("package", &_package);
+        expectArg("dependency", &_dependency);
+
+        mkFlag()
+            .longName("all")
+            .shortName('a')
+            .description("show all edges in the dependency graph leading from 'package' to 'dependency', rather than just a shortest path")
+            .set(&all, true);
+    }
+
+    std::string name() override
+    {
+        return "why-depends";
+    }
+
+    std::string description() override
+    {
+        return "show why a package has another package in its closure";
+    }
+
+    Examples examples() override
+    {
+        return {
+            Example{
+                "To show one path through the dependency graph leading from Hello to Glibc:",
+                "nix why-depends nixpkgs.hello nixpkgs.glibc"
+            },
+            Example{
+                "To show all files and paths in the dependency graph leading from Thunderbird to libX11:",
+                "nix why-depends --all nixpkgs.thunderbird nixpkgs.xorg.libX11"
+            },
+            Example{
+                "To show why Glibc depends on itself:",
+                "nix why-depends nixpkgs.glibc nixpkgs.glibc"
+            },
+        };
+    }
+
+    void run(ref<Store> store) override
+    {
+        auto package = parseInstallable(*this, store, _package, false);
+        auto packagePath = toStorePath(store, Build, package);
+        auto dependency = parseInstallable(*this, store, _dependency, false);
+        auto dependencyPath = toStorePath(store, NoBuild, dependency);
+        auto dependencyPathHash = storePathToHash(dependencyPath);
+
+        PathSet closure;
+        store->computeFSClosure({packagePath}, closure, false, false);
+
+        if (!closure.count(dependencyPath)) {
+            printError("'%s' does not depend on '%s'", package->what(), dependency->what());
+            return;
+        }
+
+        stopProgressBar(); // FIXME
+
+        auto accessor = store->getFSAccessor();
+
+        auto const inf = std::numeric_limits<size_t>::max();
+
+        struct Node
+        {
+            Path path;
+            PathSet refs;
+            PathSet rrefs;
+            size_t dist = inf;
+            Node * prev = nullptr;
+            bool queued = false;
+            bool visited = false;
+        };
+
+        std::map<Path, Node> graph;
+
+        for (auto & path : closure)
+            graph.emplace(path, Node{path, store->queryPathInfo(path)->references});
+
+        // Transpose the graph.
+        for (auto & node : graph)
+            for (auto & ref : node.second.refs)
+                graph[ref].rrefs.insert(node.first);
+
+        /* Run Dijkstra's shortest path algorithm to get the distance
+           of every path in the closure to 'dependency'. */
+        graph[dependencyPath].dist = 0;
+
+        std::priority_queue<Node *> queue;
+
+        queue.push(&graph.at(dependencyPath));
+
+        while (!queue.empty()) {
+            auto & node = *queue.top();
+            queue.pop();
+
+            for (auto & rref : node.rrefs) {
+                auto & node2 = graph.at(rref);
+                auto dist = node.dist + 1;
+                if (dist < node2.dist) {
+                    node2.dist = dist;
+                    node2.prev = &node;
+                    if (!node2.queued) {
+                        node2.queued = true;
+                        queue.push(&node2);
+                    }
+                }
+
+            }
+        }
+
+        /* Print the subgraph of nodes that have 'dependency' in their
+           closure (i.e., that have a non-infinite distance to
+           'dependency'). Print every edge on a path between `package`
+           and `dependency`. */
+        std::function<void(Node &, const string &, const string &)> printNode;
+
+        const string treeConn = "╠═══";
+        const string treeLast = "╚═══";
+        const string treeLine = "║   ";
+        const string treeNull = "    ";
+
+        struct BailOut { };
+
+        printNode = [&](Node & node, const string & firstPad, const string & tailPad) {
+            assert(node.dist != inf);
+            std::cout << fmt("%s%s%s%s" ANSI_NORMAL "\n",
+                firstPad,
+                node.visited ? "\e[38;5;244m" : "",
+                firstPad != "" ? "=> " : "",
+                node.path);
+
+            if (node.path == dependencyPath && !all
+                && packagePath != dependencyPath)
+                throw BailOut();
+
+            if (node.visited) return;
+            node.visited = true;
+
+            /* Sort the references by distance to `dependency` to
+               ensure that the shortest path is printed first. */
+            std::multimap<size_t, Node *> refs;
+            std::set<std::string> hashes;
+
+            for (auto & ref : node.refs) {
+                if (ref == node.path && packagePath != dependencyPath) continue;
+                auto & node2 = graph.at(ref);
+                if (node2.dist == inf) continue;
+                refs.emplace(node2.dist, &node2);
+                hashes.insert(storePathToHash(node2.path));
+            }
+
+            /* For each reference, find the files and symlinks that
+               contain the reference. */
+            std::map<std::string, Strings> hits;
+
+            std::function<void(const Path &)> visitPath;
+
+            visitPath = [&](const Path & p) {
+                auto st = accessor->stat(p);
+
+                auto p2 = p == node.path ? "/" : std::string(p, node.path.size() + 1);
+
+                auto getColour = [&](const std::string & hash) {
+                    return hash == dependencyPathHash ? ANSI_GREEN : ANSI_BLUE;
+                };
+
+                if (st.type == FSAccessor::Type::tDirectory) {
+                    auto names = accessor->readDirectory(p);
+                    for (auto & name : names)
+                        visitPath(p + "/" + name);
+                }
+
+                else if (st.type == FSAccessor::Type::tRegular) {
+                    auto contents = accessor->readFile(p);
+
+                    for (auto & hash : hashes) {
+                        auto pos = contents.find(hash);
+                        if (pos != std::string::npos) {
+                            size_t margin = 32;
+                            auto pos2 = pos >= margin ? pos - margin : 0;
+                            hits[hash].emplace_back(fmt("%s: …%s…\n",
+                                    p2,
+                                    hilite(filterPrintable(
+                                            std::string(contents, pos2, pos - pos2 + hash.size() + margin)),
+                                        pos - pos2, storePathHashLen,
+                                        getColour(hash))));
+                        }
+                    }
+                }
+
+                else if (st.type == FSAccessor::Type::tSymlink) {
+                    auto target = accessor->readLink(p);
+
+                    for (auto & hash : hashes) {
+                        auto pos = target.find(hash);
+                        if (pos != std::string::npos)
+                            hits[hash].emplace_back(fmt("%s -> %s\n", p2,
+                                    hilite(target, pos, storePathHashLen, getColour(hash))));
+                    }
+                }
+            };
+
+            // FIXME: should use scanForReferences().
+
+            visitPath(node.path);
+
+            RunPager pager;
+            for (auto & ref : refs) {
+                auto hash = storePathToHash(ref.second->path);
+
+                bool last = all ? ref == *refs.rbegin() : true;
+
+                for (auto & hit : hits[hash]) {
+                    bool first = hit == *hits[hash].begin();
+                    std::cout << tailPad
+                              << (first ? (last ? treeLast : treeConn) : (last ? treeNull : treeLine))
+                              << hit;
+                    if (!all) break;
+                }
+
+                printNode(*ref.second,
+                    tailPad + (last ? treeNull : treeLine),
+                    tailPad + (last ? treeNull : treeLine));
+            }
+        };
+
+        try {
+            printNode(graph.at(packagePath), "", "");
+        } catch (BailOut & ) { }
+    }
+};
+
+static RegisterCommand r1(make_ref<CmdWhyDepends>());