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.cc51
-rw-r--r--third_party/nix/src/nix/build.cc68
-rw-r--r--third_party/nix/src/nix/cat.cc56
-rw-r--r--third_party/nix/src/nix/command.cc156
-rw-r--r--third_party/nix/src/nix/command.hh194
-rw-r--r--third_party/nix/src/nix/copy.cc86
-rw-r--r--third_party/nix/src/nix/doctor.cc142
-rw-r--r--third_party/nix/src/nix/dump-path.cc28
-rw-r--r--third_party/nix/src/nix/edit.cc75
-rw-r--r--third_party/nix/src/nix/eval.cc56
-rw-r--r--third_party/nix/src/nix/hash.cc152
-rw-r--r--third_party/nix/src/nix/installables.cc349
-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/log.cc63
-rw-r--r--third_party/nix/src/nix/ls.cc137
-rw-r--r--third_party/nix/src/nix/main.cc185
-rw-r--r--third_party/nix/src/nix/optimise-store.cc27
-rw-r--r--third_party/nix/src/nix/path-info.cc133
-rw-r--r--third_party/nix/src/nix/ping-store.cc25
-rw-r--r--third_party/nix/src/nix/repl.cc819
-rw-r--r--third_party/nix/src/nix/run.cc283
-rw-r--r--third_party/nix/src/nix/search.cc276
-rw-r--r--third_party/nix/src/nix/show-config.cc31
-rw-r--r--third_party/nix/src/nix/show-derivation.cc113
-rw-r--r--third_party/nix/src/nix/sigs.cc146
-rw-r--r--third_party/nix/src/nix/upgrade-nix.cc167
-rw-r--r--third_party/nix/src/nix/verify.cc171
-rw-r--r--third_party/nix/src/nix/why-depends.cc269
29 files changed, 4288 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..53641f120e
--- /dev/null
+++ b/third_party/nix/src/nix/add-to-store.cc
@@ -0,0 +1,51 @@
+#include "libmain/common-args.hh"
+#include "libstore/store-api.hh"
+#include "libutil/archive.hh"
+#include "nix/command.hh"
+
+namespace nix {
+struct CmdAddToStore final : 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);
+  }
+};
+}  // namespace nix
+
+static nix::RegisterCommand r1(nix::make_ref<nix::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..3fe74b7ffd
--- /dev/null
+++ b/third_party/nix/src/nix/build.cc
@@ -0,0 +1,68 @@
+#include "libmain/common-args.hh"
+#include "libmain/shared.hh"
+#include "libstore/store-api.hh"
+#include "nix/command.hh"
+
+namespace nix {
+struct CmdBuild final : 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.empty()) {
+        for (auto& output : b.outputs) {
+          if (auto store2 = store.dynamic_pointer_cast<LocalFSStore>()) {
+            std::string symlink = outLink;
+            if (i != 0u) {
+              symlink += fmt("-%d", i);
+            }
+            if (output.first != "out") {
+              symlink += fmt("-%s", output.first);
+            }
+            store2->addPermRoot(output.second, absPath(symlink), true);
+          }
+        }
+      }
+    }
+  }
+};
+}  // namespace nix
+
+static nix::RegisterCommand r1(nix::make_ref<nix::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..7788707eae
--- /dev/null
+++ b/third_party/nix/src/nix/cat.cc
@@ -0,0 +1,56 @@
+#include "libstore/fs-accessor.hh"
+#include "libstore/nar-accessor.hh"
+#include "libstore/store-api.hh"
+#include "nix/command.hh"
+
+namespace nix {
+struct MixCat : virtual Args {
+  std::string path;
+
+  void cat(const 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 final : 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 final : 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))));
+  }
+};
+}  // namespace nix
+
+static nix::RegisterCommand r1(nix::make_ref<nix::CmdCatStore>());
+static nix::RegisterCommand r2(nix::make_ref<nix::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..f7f183ab0a
--- /dev/null
+++ b/third_party/nix/src/nix/command.cc
@@ -0,0 +1,156 @@
+#include "nix/command.hh"
+
+#include <utility>
+
+#include "libstore/derivations.hh"
+#include "libstore/store-api.hh"
+
+namespace nix {
+
+Commands* RegisterCommand::commands = nullptr;
+
+void Command::printHelp(const std::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(Commands _commands)
+    : commands(std::move(_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 std::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);
+  }
+  return Args::processArgs(args, finish);
+}
+
+StoreCommand::StoreCommand() = default;
+
+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.empty() != 0u) {
+      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());
+}
+
+}  // namespace nix
diff --git a/third_party/nix/src/nix/command.hh b/third_party/nix/src/nix/command.hh
new file mode 100644
index 0000000000..87e2fbe9d2
--- /dev/null
+++ b/third_party/nix/src/nix/command.hh
@@ -0,0 +1,194 @@
+#pragma once
+
+#include <memory>
+
+#include "libexpr/common-eval-args.hh"
+#include "libutil/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 std::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;
+};
+
+using Buildables = std::vector<Buildable>;
+
+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;
+  std::shared_ptr<Value*> vSourceExpr;
+};
+
+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;
+};
+
+using Commands = std::map<std::string, ref<Command>>;
+
+/* An argument parser that supports multiple subcommands,
+   i.e. ‘<command> <subcommand>’. */
+class MultiCommand : virtual Args {
+ public:
+  Commands commands;
+
+  std::shared_ptr<Command> command;
+
+  MultiCommand(Commands commands);
+
+  void printHelp(const std::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,
+                                              const ref<Store>& store,
+                                              const std::string& installable,
+                                              bool useDefaultInstallables);
+
+Buildables build(const ref<Store>& store, RealiseMode mode,
+                 const std::vector<std::shared_ptr<Installable>>& installables);
+
+PathSet toStorePaths(
+    const ref<Store>& store, RealiseMode mode,
+    const std::vector<std::shared_ptr<Installable>>& installables);
+
+Path toStorePath(const ref<Store>& store, RealiseMode mode,
+                 const std::shared_ptr<Installable>& installable);
+
+PathSet toDerivations(
+    const ref<Store>& store,
+    const std::vector<std::shared_ptr<Installable>>& installables,
+    bool useDeriver = false);
+
+}  // namespace nix
diff --git a/third_party/nix/src/nix/copy.cc b/third_party/nix/src/nix/copy.cc
new file mode 100644
index 0000000000..75c85698d1
--- /dev/null
+++ b/third_party/nix/src/nix/copy.cc
@@ -0,0 +1,86 @@
+#include <atomic>
+
+#include "libmain/shared.hh"
+#include "libstore/store-api.hh"
+#include "libutil/sync.hh"
+#include "libutil/thread-pool.hh"
+#include "nix/command.hh"
+
+namespace nix {
+struct CmdCopy final : 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);
+  }
+};
+}  // namespace nix
+
+static nix::RegisterCommand r1(nix::make_ref<nix::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..d0b4c2b588
--- /dev/null
+++ b/third_party/nix/src/nix/doctor.cc
@@ -0,0 +1,142 @@
+#include <absl/strings/match.h>
+#include <absl/strings/str_cat.h>
+#include <absl/strings/str_split.h>
+
+#include "libmain/shared.hh"
+#include "libstore/serve-protocol.hh"
+#include "libstore/store-api.hh"
+#include "libstore/worker-protocol.hh"
+#include "nix/command.hh"
+
+namespace nix {
+static std::string formatProtocol(unsigned int proto) {
+  if (proto != 0u) {
+    auto major = GET_PROTOCOL_MAJOR(proto) >> 8;
+    auto minor = GET_PROTOCOL_MINOR(proto);
+    return (format("%1%.%2%") % major % minor).str();
+  }
+  return "unknown";
+}
+
+struct CmdDoctor final : 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);
+    }
+  }
+
+  static bool checkNixInPath() {
+    PathSet dirs;
+
+    for (auto& dir : absl::StrSplit(getEnv("PATH").value_or(""),
+                                    absl::ByChar(':'), absl::SkipEmpty())) {
+      if (pathExists(absl::StrCat(dir, "/nix-env"))) {
+        dirs.insert(dirOf(canonPath(absl::StrCat(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;
+  }
+
+  static bool checkProfileRoots(const ref<Store>& store) {
+    PathSet dirs;
+
+    for (auto dir : absl::StrSplit(getEnv("PATH").value_or(""),
+                                   absl::ByChar(':'), absl::SkipEmpty())) {
+      Path profileDir = dirOf(dir);
+      try {
+        Path userEnv = canonPath(profileDir, true);
+
+        if (store->isStorePath(userEnv) &&
+            absl::EndsWith(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(std::string(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;
+  }
+
+  static 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;
+  }
+};
+}  // namespace nix
+
+static nix::RegisterCommand r1(nix::make_ref<nix::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..1d0a996e56
--- /dev/null
+++ b/third_party/nix/src/nix/dump-path.cc
@@ -0,0 +1,28 @@
+#include "libstore/store-api.hh"
+#include "nix/command.hh"
+
+namespace nix {
+struct CmdDumpPath final : 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();
+  }
+};
+}  // namespace nix
+
+static nix::RegisterCommand r1(nix::make_ref<nix::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..04c67acb94
--- /dev/null
+++ b/third_party/nix/src/nix/edit.cc
@@ -0,0 +1,75 @@
+#include <absl/strings/str_split.h>
+#include <glog/logging.h>
+#include <unistd.h>
+
+#include "libexpr/attr-path.hh"
+#include "libexpr/eval.hh"
+#include "libmain/shared.hh"
+#include "nix/command.hh"
+
+namespace nix {
+struct CmdEdit final : 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 = Bindings::New();
+      v2 = findAlongAttrPath(*state, "meta.position", dummyArgs.get(), *v);
+    } catch (Error&) {
+      throw Error("package '%s' has no source location information",
+                  installable->what());
+    }
+
+    auto pos = state->forceString(*v2);
+    DLOG(INFO) << "position is " << 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").value_or("cat");
+
+    Strings args =
+        absl::StrSplit(editor, absl::ByAnyChar(" \t\n\r"), absl::SkipEmpty());
+
+    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);
+
+    execvp(args.front().c_str(), stringsToCharPtrs(args).data());
+
+    throw SysError("cannot run editor '%s'", editor);
+  }
+};
+}  // namespace nix
+
+static nix::RegisterCommand r1(nix::make_ref<nix::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..72fcbd8271
--- /dev/null
+++ b/third_party/nix/src/nix/eval.cc
@@ -0,0 +1,56 @@
+#include "libexpr/eval.hh"
+
+#include "libexpr/value-to-json.hh"
+#include "libmain/common-args.hh"
+#include "libmain/shared.hh"
+#include "libstore/store-api.hh"
+#include "libutil/json.hh"
+#include "nix/command.hh"
+
+namespace nix {
+struct CmdEval final : 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;
+
+    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";
+    }
+  }
+};
+}  // namespace nix
+
+static nix::RegisterCommand r1(nix::make_ref<nix::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..4fb262f1a8
--- /dev/null
+++ b/third_party/nix/src/nix/hash.cc
@@ -0,0 +1,152 @@
+#include "libutil/hash.hh"
+
+#include "libmain/shared.hh"
+#include "nix/command.hh"
+#include "nix/legacy.hh"
+
+namespace nix {
+struct CmdHash final : Command {
+  enum Mode { mFile, mPath };
+  Mode mode;
+  Base base = SRI;
+  bool truncate = false;
+  HashType ht = htSHA256;
+  std::vector<std::string> paths;
+
+  explicit 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 (const auto& path : paths) {
+      Hash h = mode == mFile ? hashFile(ht, path) : hashPath(ht, path).first;
+      if (truncate && h.hashSize > nix::kStorePathHashSize) {
+        h = compressHash(h, nix::kStorePathHashSize);
+      }
+      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 final : Command {
+  Base base;
+  HashType ht = htUnknown;
+  std::vector<std::string> args;
+
+  explicit 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 (const auto& s : args) {
+      auto hash_ = Hash::deserialize(s, ht);
+      if (hash_.ok()) {
+        std::cout << hash_->to_string(base, base == SRI) << "\n";
+      } else {
+        std::cerr << "failed to parse: " << hash_.status().ToString() << "\n";
+        // create a matching blank line, for scripting
+        std::cout << "\n";
+      }
+    }
+  }
+};
+
+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") {
+                   std::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);
+}  // namespace nix
diff --git a/third_party/nix/src/nix/installables.cc b/third_party/nix/src/nix/installables.cc
new file mode 100644
index 0000000000..7aa26b0dee
--- /dev/null
+++ b/third_party/nix/src/nix/installables.cc
@@ -0,0 +1,349 @@
+#include <iostream>
+#include <regex>
+#include <utility>
+
+#include "libexpr/attr-path.hh"
+#include "libexpr/common-eval-args.hh"
+#include "libexpr/eval-inline.hh"
+#include "libexpr/eval.hh"
+#include "libexpr/get-drvs.hh"
+#include "libmain/shared.hh"
+#include "libstore/derivations.hh"
+#include "libstore/store-api.hh"
+#include "libutil/status.hh"
+#include "nix/command.hh"
+
+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 != nullptr) {
+    return *vSourceExpr;
+  }
+
+  auto sToplevel = state.symbols.Create("_toplevel");
+
+  // Allocate the vSourceExpr Value as uncollectable. Boehm GC doesn't
+  // consider the member variable "alive" during execution causing it to be
+  // GC'ed in the middle of evaluation.
+  vSourceExpr = allocRootValue(state.allocValue());
+
+  if (!file.empty()) {
+    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.empty()) {
+        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);
+      }
+    }
+  }
+
+  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 final : Installable {
+  Path storePath;
+
+  explicit InstallableStorePath(Path storePath)
+      : storePath(std::move(storePath)) {}
+
+  std::string what() override { return storePath; }
+
+  Buildables toBuildables() override {
+    return {{isDerivation(storePath) ? storePath : "", {{"out", storePath}}}};
+  }
+};
+
+struct InstallableValue : Installable {
+  SourceExprCommand& cmd;
+
+  explicit InstallableValue(SourceExprCommand& cmd) : cmd(cmd) {}
+
+  Buildables toBuildables() override {
+    auto state = cmd.getEvalState();
+
+    auto v = toValue(*state);
+
+    std::unique_ptr<Bindings> autoArgs = cmd.getAutoArgs(*state);
+
+    DrvInfos drvs;
+    getDerivations(*state, *v, "", autoArgs.get(), drvs, false);
+
+    Buildables res;
+
+    PathSet drvPaths;
+
+    for (auto& drv : drvs) {
+      Buildable b{drv.queryDrvPath()};
+      drvPaths.insert(b.drvPath);
+
+      auto outputName = drv.queryOutputName();
+      if (outputName.empty()) {
+        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};
+    }
+    return res;
+  }
+};
+
+struct InstallableExpr final : InstallableValue {
+  std::string text;
+
+  InstallableExpr(SourceExprCommand& cmd, std::string text)
+      : InstallableValue(cmd), text(std::move(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 final : InstallableValue {
+  std::string attrPath;
+
+  InstallableAttrPath(SourceExprCommand& cmd, std::string attrPath)
+      : InstallableValue(cmd), attrPath(std::move(attrPath)) {}
+
+  std::string what() override { return attrPath; }
+
+  Value* toValue(EvalState& state) override {
+    auto source = cmd.getSourceExpr(state);
+
+    std::unique_ptr<Bindings> autoArgs = cmd.getAutoArgs(state);
+
+    Value* v = findAlongAttrPath(state, attrPath, autoArgs.get(), *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, const ref<Store>& store,
+    std::vector<std::string> ss, bool useDefaultInstallables) {
+  std::vector<std::shared_ptr<Installable>> result;
+
+  if (ss.empty() && useDefaultInstallables) {
+    if (cmd.file.empty()) {
+      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.empty() || 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,
+                                              const 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(
+    const ref<Store>& store, RealiseMode mode,
+    const 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.empty()) {
+        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);
+  } else if (mode == Build) {
+    util::OkOrThrow(store->buildPaths(std::cerr, pathsToBuild));
+  }
+
+  return buildables;
+}
+
+PathSet toStorePaths(
+    const ref<Store>& store, RealiseMode mode,
+    const 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(const ref<Store>& store, RealiseMode mode,
+                 const 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(
+    const ref<Store>& store,
+    const 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);
+}
+
+}  // namespace nix
diff --git a/third_party/nix/src/nix/legacy.cc b/third_party/nix/src/nix/legacy.cc
new file mode 100644
index 0000000000..a0f9fc65b3
--- /dev/null
+++ b/third_party/nix/src/nix/legacy.cc
@@ -0,0 +1,7 @@
+#include "nix/legacy.hh"
+
+namespace nix {
+
+RegisterLegacyCommand::Commands* RegisterLegacyCommand::commands = nullptr;
+
+}
diff --git a/third_party/nix/src/nix/legacy.hh b/third_party/nix/src/nix/legacy.hh
new file mode 100644
index 0000000000..a0fc88da24
--- /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 {
+  using Commands = std::map<std::string, MainFunction>;
+  static Commands* commands;
+
+  RegisterLegacyCommand(const std::string& name, MainFunction fun) {
+    if (!commands) {
+      commands = new Commands;
+    }
+    (*commands)[name] = fun;
+  }
+};
+
+}  // namespace nix
diff --git a/third_party/nix/src/nix/log.cc b/third_party/nix/src/nix/log.cc
new file mode 100644
index 0000000000..84207d8576
--- /dev/null
+++ b/third_party/nix/src/nix/log.cc
@@ -0,0 +1,63 @@
+#include <glog/logging.h>
+
+#include "libmain/common-args.hh"
+#include "libmain/shared.hh"
+#include "libstore/store-api.hh"
+#include "nix/command.hh"
+
+namespace nix {
+struct CmdLog final : InstallableCommand {
+  CmdLog() = default;
+
+  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.empty() ? sub->getBuildLog(b.drvPath) : nullptr;
+      for (auto& output : b.outputs) {
+        if (log) {
+          break;
+        }
+        log = sub->getBuildLog(output.second);
+      }
+      if (!log) {
+        continue;
+      }
+      LOG(INFO) << "got build log for '" << installable->what() << "' from '"
+                << sub->getUri() << "'";
+      std::cout << *log;
+      return;
+    }
+
+    throw Error("build log of '%s' is not available", installable->what());
+  }
+};
+}  // namespace nix
+
+static nix::RegisterCommand r1(nix::make_ref<nix::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..1da722babb
--- /dev/null
+++ b/third_party/nix/src/nix/ls.cc
@@ -0,0 +1,137 @@
+#include "libmain/common-args.hh"
+#include "libstore/fs-accessor.hh"
+#include "libstore/nar-accessor.hh"
+#include "libstore/store-api.hh"
+#include "libutil/json.hh"
+#include "nix/command.hh"
+
+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(const ref<FSAccessor>& accessor) {
+    if (path == "/") {
+      path = "";
+    }
+
+    if (json) {
+      JSONPlaceholder jsonRoot(std::cout);
+      listNar(jsonRoot, accessor, path, recursive);
+    } else {
+      listText(accessor);
+    }
+  }
+};
+
+struct CmdLsStore final : 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 final : 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))));
+  }
+};
+}  // namespace nix
+
+static nix::RegisterCommand r1(nix::make_ref<nix::CmdLsStore>());
+static nix::RegisterCommand r2(nix::make_ref<nix::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..08390fd24b
--- /dev/null
+++ b/third_party/nix/src/nix/main.cc
@@ -0,0 +1,185 @@
+#include <algorithm>
+
+#include <glog/logging.h>
+#include <ifaddrs.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include "libexpr/eval.hh"
+#include "libmain/common-args.hh"
+#include "libmain/shared.hh"
+#include "libstore/download.hh"
+#include "libstore/globals.hh"
+#include "libstore/store-api.hh"
+#include "libutil/finally.hh"
+#include "nix/command.hh"
+#include "nix/legacy.hh"
+
+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) != 0) {
+    return true;
+  }
+
+  Finally free([&]() { freeifaddrs(addrs); });
+
+  for (auto i = addrs; i != nullptr; i = i->ifa_next) {
+    if (i->ifa_addr == nullptr) {
+      continue;
+    }
+    if (i->ifa_addr->sa_family == AF_INET) {
+      if (ntohl(
+              (reinterpret_cast<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();
+
+  programPath = argv[0];
+  std::string programName = baseNameOf(programPath);
+
+  {
+    auto legacy = (*RegisterLegacyCommand::commands)[programName];
+    if (legacy) {
+      return legacy(argc, argv);
+    }
+  }
+
+  settings.verboseBuild = false;
+
+  NixArgs args;
+
+  args.parseCmdline(argvToStrings(argc, argv));
+
+  if (!args.command) {
+    args.showHelpAndExit();
+  }
+
+  if (args.useNet && !haveInternet()) {
+    LOG(WARNING) << "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();
+}
+
+}  // namespace nix
+
+int main(int argc, char* argv[]) {
+  FLAGS_logtostderr = true;
+  google::InitGoogleLogging(argv[0]);
+
+  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..ceb53aa77b
--- /dev/null
+++ b/third_party/nix/src/nix/optimise-store.cc
@@ -0,0 +1,27 @@
+#include <atomic>
+
+#include "libmain/shared.hh"
+#include "libstore/store-api.hh"
+#include "nix/command.hh"
+
+namespace nix {
+struct CmdOptimiseStore final : StoreCommand {
+  CmdOptimiseStore() = default;
+
+  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(); }
+};
+}  // namespace nix
+
+static nix::RegisterCommand r1(nix::make_ref<nix::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..fcf060d50d
--- /dev/null
+++ b/third_party/nix/src/nix/path-info.cc
@@ -0,0 +1,133 @@
+#include <algorithm>
+#include <array>
+
+#include "libmain/common-args.hh"
+#include "libmain/shared.hh"
+#include "libstore/store-api.hh"
+#include "libutil/json.hh"
+#include "nix/command.hh"
+
+namespace nix {
+struct CmdPathInfo final : 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, static_cast<int>(pathLen) -
+                              static_cast<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.empty()) {
+            ss.push_back("ca:" + info->ca);
+          }
+          for (auto& sig : info->sigs) {
+            ss.push_back(sig);
+          }
+          std::cout << concatStringsSep(" ", ss);
+        }
+
+        std::cout << std::endl;
+      }
+    }
+  }
+};
+}  // namespace nix
+
+static nix::RegisterCommand r1(nix::make_ref<nix::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..4a33486bf8
--- /dev/null
+++ b/third_party/nix/src/nix/ping-store.cc
@@ -0,0 +1,25 @@
+#include "libmain/shared.hh"
+#include "libstore/store-api.hh"
+#include "nix/command.hh"
+
+namespace nix {
+struct CmdPingStore final : 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(); }
+};
+}  // namespace nix
+
+static nix::RegisterCommand r1(nix::make_ref<nix::CmdPingStore>());
diff --git a/third_party/nix/src/nix/repl.cc b/third_party/nix/src/nix/repl.cc
new file mode 100644
index 0000000000..b926d195ae
--- /dev/null
+++ b/third_party/nix/src/nix/repl.cc
@@ -0,0 +1,819 @@
+#include <climits>
+#include <csetjmp>
+#include <cstdlib>
+#include <cstring>
+#include <iostream>
+#include <utility>
+
+#include <absl/strings/ascii.h>
+#include <absl/strings/match.h>
+#include <editline.h>
+#include <glog/logging.h>
+
+#include "libexpr/common-eval-args.hh"
+#include "libexpr/eval-inline.hh"
+#include "libexpr/eval.hh"
+#include "libexpr/get-drvs.hh"
+#include "libmain/shared.hh"
+#include "libstore/derivations.hh"
+#include "libstore/globals.hh"
+#include "libstore/store-api.hh"
+#include "libutil/affinity.hh"
+#include "libutil/finally.hh"
+#include "nix/command.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 {
+  std::string curDir;
+  EvalState state;
+  std::unique_ptr<Bindings> autoArgs;
+
+  Strings loadedFiles;
+
+  const static int envSize = 32768;
+  StaticEnv staticEnv;
+  Env* env;
+  int displ;
+  StringSet varNames;
+
+  const Path historyFile;
+
+  NixRepl(const Strings& searchPath, const nix::ref<Store>& store);
+  ~NixRepl();
+  void mainLoop(const std::vector<std::string>& files);
+  StringSet completePrefix(const std::string& prefix);
+  static bool getLine(std::string& input, const std::string& prompt);
+  Path getDerivationPath(Value& v);
+  bool processLine(std::string line);
+  void loadFile(const Path& path);
+  void initEnv();
+  void reloadFiles();
+  void addAttrsToScope(Value& attrs);
+  void addVarToScope(const Symbol& name, Value& v);
+  Expr* parseString(const std::string& s);
+  void evalString(std::string s, Value& v);
+
+  using ValuesSeen = std::set<Value*>;
+  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";
+}
+
+std::string removeWhitespace(std::string s) {
+  s = absl::StripTrailingAsciiWhitespace(s);
+  size_t n = s.find_first_not_of(" \n\r\t");
+  if (n != std::string::npos) {
+    s = std::string(s, n);
+  }
+  return s;
+}
+
+NixRepl::NixRepl(const Strings& searchPath, const 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 == nullptr) {
+      throw Error("allocation failure");
+    }
+    return res;
+  }
+  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 == nullptr) {
+        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(static_cast<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; }
+}  // namespace
+
+void NixRepl::mainLoop(const std::vector<std::string>& files) {
+  std::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));
+  el_hist_size = 1000;
+  read_history(historyFile.c_str());
+  curRepl = this;
+  rl_set_complete_func(completionCallback);
+  rl_set_list_possib_func(listPossibleCallback);
+
+  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;
+      }
+      LOG(ERROR) << error << (settings.showTrace ? e.prefix() : "") << e.msg();
+
+    } catch (Error& e) {
+      LOG(ERROR) << error << (settings.showTrace ? e.prefix() : "") << e.msg();
+    } catch (Interrupted& e) {
+      LOG(ERROR) << error << (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(std::string& input, const std::string& prompt) {
+  struct sigaction act;
+  struct sigaction old;
+  sigset_t savedSignalMask;
+  sigset_t set;
+
+  auto setupSignals = [&]() {
+    act.sa_handler = sigintHandler;
+    sigfillset(&act.sa_mask);
+    act.sa_flags = 0;
+    if (sigaction(SIGINT, &act, &old) != 0) {
+      throw SysError("installing handler for SIGINT");
+    }
+
+    sigemptyset(&set);
+    sigaddset(&set, SIGINT);
+    if (sigprocmask(SIG_UNBLOCK, &set, &savedSignalMask) != 0) {
+      throw SysError("unblocking SIGINT");
+    }
+  };
+  auto restoreSignals = [&]() {
+    if (sigprocmask(SIG_SETMASK, &savedSignalMask, nullptr) != 0) {
+      throw SysError("restoring signals");
+    }
+
+    if (sigaction(SIGINT, &old, nullptr) != 0) {
+      throw SysError("restoring handler for SIGINT");
+    }
+  };
+
+  setupSignals();
+  char* s = readline(prompt.c_str());
+  Finally doFree([&]() { free(s); });
+  restoreSignals();
+
+  if (g_signal_received != 0) {
+    g_signal_received = 0;
+    input.clear();
+    return true;
+  }
+
+  if (s == nullptr) {
+    return false;
+  }
+  input += s;
+  input += '\n';
+  return true;
+}
+
+StringSet NixRepl::completePrefix(const std::string& prefix) {
+  StringSet completions;
+
+  size_t start = prefix.find_last_of(" \n\r\t(){}[]");
+  std::string prev;
+  std::string 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;
+  size_t dot;
+
+  if ((slash = cur.rfind('/')) != std::string::npos) {
+    try {
+      auto dir = std::string(cur, 0, slash);
+      auto prefix2 = std::string(cur, slash + 1);
+      for (auto& entry : readDirectory(dir.empty() ? "/" : dir)) {
+        if (entry.name[0] != '.' && absl::StartsWith(entry.name, prefix2)) {
+          completions.insert(prev + dir + "/" + entry.name);
+        }
+      }
+    } catch (Error&) {
+    }
+  } else if ((dot = cur.rfind('.')) == std::string::npos) {
+    /* This is a variable name; look it up in the current scope. */
+    auto i = varNames.lower_bound(cur);
+    while (i != varNames.end()) {
+      if (std::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. */
+      std::string expr(cur, 0, dot);
+      std::string cur2 = std::string(cur, dot + 1);
+
+      Expr* e = parseString(expr);
+      Value v;
+      e->eval(state, *env, v);
+      state.forceAttrs(v);
+
+      for (auto& i : *v.attrs) {
+        std::string name = i.second.name;
+        if (std::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 std::string& program, const Strings& args) {
+  Strings args2(args);
+  args2.push_front(program);
+
+  Pid pid;
+  pid = fork();
+  if (pid == Pid(-1)) {
+    throw SysError("forking");
+  }
+  if (pid == Pid(0)) {
+    restoreAffinity();
+    execvp(program.c_str(), stringsToCharPtrs(args2).data());
+    _exit(1);
+  }
+
+  return pid.wait();
+}
+
+bool isVarName(const std::string& s) {
+  if (s.empty()) {
+    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.empty() || !state.store->isValidPath(drvPath)) {
+    throw Error("expression did not evaluate to a valid derivation");
+  }
+  return drvPath;
+}
+
+bool NixRepl::processLine(std::string line) {
+  if (line.empty()) {
+    return true;
+  }
+
+  std::string command;
+  std::string arg;
+
+  if (line[0] == ':') {
+    size_t p = line.find_first_of(" \n\r\t");
+    command = std::string(line, 0, p);
+    if (p != std::string::npos) {
+      arg = removeWhitespace(std::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;
+    Value f;
+    Value 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.empty()) {
+    throw Error(format("unknown command '%1%'") % command);
+
+  } else {
+    size_t p = line.find('=');
+    std::string name;
+    if (p != std::string::npos && p < line.size() && line[p + 1] != '=' &&
+        isVarName(name = removeWhitespace(std::string(line, 0, p)))) {
+      Expr* e = parseString(std::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;
+  Value v2;
+  state.evalFile(lookupFileArg(state, path), v);
+  state.autoCallFunction(autoArgs.get(), 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.second.name, *i.second.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(std::string(name));
+}
+
+Expr* NixRepl::parseString(const std::string& s) {
+  Expr* e = state.parseExprFromString(s, curDir, staticEnv);
+  return e;
+}
+
+void NixRepl::evalString(std::string s, Value& v) {
+  Expr* e = parseString(std::move(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 != 0; 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->second.pos, *i->second.value, context)
+                : "???";
+        str << drvPath << "»";
+      }
+
+      else if (maxDepth > 0) {
+        str << "{ ";
+
+        typedef std::map<std::string, Value*> Sorted;
+        Sorted sorted;
+        for (auto& i : *v.attrs) {
+          sorted[i.second.name] = i.second.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 tList:
+      seen.insert(&v);
+
+      str << "[ ";
+      if (maxDepth > 0) {
+        for (unsigned int n = 0; n < v.listSize(); ++n) {
+          if (seen.find((*v.list)[n]) != seen.end()) {
+            str << "«repeated»";
+          } else {
+            try {
+              printValue(str, *(*v.list)[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 final : 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>());
+
+}  // namespace nix
diff --git a/third_party/nix/src/nix/run.cc b/third_party/nix/src/nix/run.cc
new file mode 100644
index 0000000000..b3b54f300b
--- /dev/null
+++ b/third_party/nix/src/nix/run.cc
@@ -0,0 +1,283 @@
+#include <queue>
+
+#include <absl/strings/str_split.h>
+#include <sys/mount.h>
+
+#include "libmain/common-args.hh"
+#include "libmain/shared.hh"
+#include "libstore/derivations.hh"
+#include "libstore/fs-accessor.hh"
+#include "libstore/local-store.hh"
+#include "libstore/store-api.hh"
+#include "libutil/affinity.hh"
+#include "libutil/finally.hh"
+#include "nix/command.hh"
+
+// note: exported in header file
+std::string chrootHelperName = "__run_in_chroot";
+
+namespace nix {
+struct CmdRun final : 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([&](const 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 != nullptr) {
+          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);
+    }
+
+    Strings unixPath = absl::StrSplit(getEnv("PATH").value_or(""),
+                                      absl::ByChar(':'), absl::SkipEmpty());
+
+    while (!todo.empty()) {
+      Path path = todo.front();
+      todo.pop();
+      if (!done.insert(path).second) {
+        continue;
+      }
+
+      { unixPath.push_front(path + "/bin"); }
+
+      auto propPath = path + "/nix-support/propagated-user-env-packages";
+      if (accessor->stat(propPath).type == FSAccessor::tRegular) {
+        for (auto p :
+             absl::StrSplit(readFile(propPath), absl::ByAnyChar(" \t\n\r"),
+                            absl::SkipEmpty())) {
+          todo.push(std::string(p));
+        }
+      }
+    }
+
+    setenv("PATH", concatStringsSep(":", unixPath).c_str(), 1);
+
+    std::string cmd = *command.begin();
+    Strings args;
+    for (auto& arg : command) {
+      args.push_back(arg);
+    }
+
+    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>());
+}  // namespace nix
+
+void chrootHelper(int argc, char** argv) {
+  int p = 1;
+  std::string storeDir = argv[p++];
+  std::string realStoreDir = argv[p++];
+  std::string cmd = argv[p++];
+  nix::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 nix::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 (!nix::pathExists(storeDir)) {
+    // FIXME: Use overlayfs?
+
+    nix::Path tmpDir = nix::createTempDir();
+
+    nix::createDirs(tmpDir + storeDir);
+
+    if (mount(realStoreDir.c_str(), (tmpDir + storeDir).c_str(), "", MS_BIND,
+              nullptr) == -1) {
+      throw nix::SysError("mounting '%s' on '%s'", realStoreDir, storeDir);
+    }
+
+    for (const auto& entry : nix::readDirectory("/")) {
+      auto src = "/" + entry.name;
+      auto st = nix::lstat(src);
+      if (!S_ISDIR(st.st_mode)) {
+        continue;
+      }
+      nix::Path dst = tmpDir + "/" + entry.name;
+      if (nix::pathExists(dst)) {
+        continue;
+      }
+      if (mkdir(dst.c_str(), 0700) == -1) {
+        throw nix::SysError("creating directory '%s'", dst);
+      }
+      if (mount(src.c_str(), dst.c_str(), "", MS_BIND | MS_REC, nullptr) ==
+          -1) {
+        throw nix::SysError("mounting '%s' on '%s'", src, dst);
+      }
+    }
+
+    char* cwd = getcwd(nullptr, 0);
+    if (cwd == nullptr) {
+      throw nix::SysError("getting current directory");
+    }
+    ::Finally freeCwd([&]() { free(cwd); });
+
+    if (chroot(tmpDir.c_str()) == -1) {
+      throw nix::SysError(nix::format("chrooting into '%s'") % tmpDir);
+    }
+
+    if (chdir(cwd) == -1) {
+      throw nix::SysError(nix::format("chdir to '%s' in chroot") % cwd);
+    }
+  } else if (mount(realStoreDir.c_str(), storeDir.c_str(), "", MS_BIND,
+                   nullptr) == -1) {
+    throw nix::SysError("mounting '%s' on '%s'", realStoreDir, storeDir);
+  }
+
+  nix::writeFile("/proc/self/setgroups", "deny");
+  nix::writeFile("/proc/self/uid_map", nix::fmt("%d %d %d", uid, uid, 1));
+  nix::writeFile("/proc/self/gid_map", nix::fmt("%d %d %d", gid, gid, 1));
+
+  execvp(cmd.c_str(), nix::stringsToCharPtrs(args).data());
+
+  throw nix::SysError("unable to exec '%s'", cmd);
+
+#else
+  throw nix::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..5a6bae6a11
--- /dev/null
+++ b/third_party/nix/src/nix/search.cc
@@ -0,0 +1,276 @@
+#include <fstream>
+#include <regex>
+
+#include <glog/logging.h>
+
+#include "libexpr/eval-inline.hh"
+#include "libexpr/eval.hh"
+#include "libexpr/get-drvs.hh"
+#include "libexpr/json-to-value.hh"
+#include "libexpr/names.hh"
+#include "libmain/common-args.hh"
+#include "libmain/shared.hh"
+#include "libstore/globals.hh"
+#include "libutil/json.hh"
+#include "nix/command.hh"
+
+namespace {
+std::string wrap(const std::string& prefix, const std::string& s) {
+  return prefix + s + ANSI_NORMAL;
+}
+
+std::string hilite(const std::string& s, const std::smatch& m,
+                   const std::string& postfix) {
+  return m.empty() ? s
+                   : std::string(m.prefix()) + ANSI_RED + std::string(m.str()) +
+                         postfix + std::string(m.suffix());
+}
+}  // namespace
+
+namespace nix {
+struct CmdSearch final : 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.emplace_back("^");
+    }
+
+    std::vector<std::regex> regexes;
+    regexes.reserve(res.size());
+
+    for (auto& re : res) {
+      regexes.emplace_back(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, const std::string& attrPath, bool toplevel,
+                 JSONObject* cache) {
+      DLOG(INFO) << "at attribute '" << attrPath << "'";
+
+      try {
+        uint found = 0;
+
+        state->forceValue(*v);
+
+        if (v->type == tLambda && toplevel) {
+          Value* v2 = state->allocValue();
+          auto dummyArgs = Bindings::New();
+          state->autoCallFunction(dummyArgs.get(), *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 != nullptr) {
+            cache->attr("type", "derivation");
+            cache->attr("name", drv.queryName());
+            cache->attr("system", drv.querySystem());
+            if (!description.empty()) {
+              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->second.value, *j->second.pos)) {
+              DLOG(INFO) << "skip attribute '" << attrPath << "'";
+              return;
+            }
+          }
+
+          bool toplevel2 = false;
+          if (!fromCache) {
+            Bindings::iterator j = v->attrs->find(sToplevel);
+            toplevel2 = j != v->attrs->end() &&
+                        state->forceBool(*j->second.value, *j->second.pos);
+          }
+
+          for (auto& i : *v->attrs) {
+            auto cache2 =
+                cache != nullptr
+                    ? std::make_unique<JSONObject>(cache->object(i.second.name))
+                    : nullptr;
+            doExpr(i.second.value,
+                   attrPath.empty()
+                       ? std::string(i.second.name)
+                       : attrPath + "." + std::string(i.second.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)) {
+      LOG(WARNING) << "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.empty()) {
+      throw Error("no results for the given search term(s)!");
+    }
+
+    RunPager pager;
+    for (const auto& el : results) {
+      std::cout << el.second << "\n";
+    }
+  }
+};
+}  // namespace nix
+
+static nix::RegisterCommand r1(nix::make_ref<nix::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..fd92e481e8
--- /dev/null
+++ b/third_party/nix/src/nix/show-config.cc
@@ -0,0 +1,31 @@
+#include "libmain/common-args.hh"
+#include "libmain/shared.hh"
+#include "libstore/store-api.hh"
+#include "libutil/json.hh"
+#include "nix/command.hh"
+
+namespace nix {
+struct CmdShowConfig final : Command, MixJSON {
+  CmdShowConfig() = default;
+
+  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";
+      }
+    }
+  }
+};
+}  // namespace nix
+
+static nix::RegisterCommand r1(nix::make_ref<nix::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..efe554710f
--- /dev/null
+++ b/third_party/nix/src/nix/show-derivation.cc
@@ -0,0 +1,113 @@
+// FIXME: integrate this with nix path-info?
+
+#include "libmain/common-args.hh"
+#include "libstore/derivations.hh"
+#include "libstore/store-api.hh"
+#include "libutil/archive.hh"
+#include "libutil/json.hh"
+#include "nix/command.hh"
+
+namespace nix {
+struct CmdShowDerivation final : 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.empty()) {
+              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";
+  }
+};
+}  // namespace nix
+
+static nix::RegisterCommand r1(nix::make_ref<nix::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..cc42613d07
--- /dev/null
+++ b/third_party/nix/src/nix/sigs.cc
@@ -0,0 +1,146 @@
+#include <atomic>
+
+#include <glog/logging.h>
+
+#include "libmain/shared.hh"
+#include "libstore/store-api.hh"
+#include "libutil/thread-pool.hh"
+#include "nix/command.hh"
+
+namespace nix {
+struct CmdCopySigs final : 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) == 0u) {
+              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();
+
+    LOG(INFO) << "imported " << added << " signatures";
+  }
+};
+
+static nix::RegisterCommand r1(make_ref<CmdCopySigs>());
+
+struct CmdSignPaths final : 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()) == 0u) {
+        store->addSignatures(storePath, info2.sigs);
+        added++;
+      }
+    }
+
+    LOG(INFO) << "added " << added << " signatures";
+  }
+};
+
+static RegisterCommand r3(make_ref<CmdSignPaths>());
+
+}  // namespace nix
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..c7f654d648
--- /dev/null
+++ b/third_party/nix/src/nix/upgrade-nix.cc
@@ -0,0 +1,167 @@
+#include <absl/strings/match.h>
+#include <absl/strings/str_cat.h>
+#include <absl/strings/str_split.h>
+#include <glog/logging.h>
+
+#include "libexpr/attr-path.hh"
+#include "libexpr/eval.hh"
+#include "libexpr/names.hh"
+#include "libmain/common-args.hh"
+#include "libstore/download.hh"
+#include "libstore/store-api.hh"
+#include "nix/command.hh"
+
+namespace nix {
+struct CmdUpgradeNix final : 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.empty()) {
+      profileDir = getProfileDir(store);
+    }
+
+    LOG(INFO) << "upgrading Nix in profile '" << profileDir << "'";
+
+    Path storePath;
+    {
+      LOG(INFO) << "querying latest Nix version";
+      storePath = getLatestNix(store);
+    }
+
+    auto version = DrvName(storePathToName(storePath)).version;
+
+    if (dryRun) {
+      LOG(ERROR) << "would upgrade to version " << version;
+      return;
+    }
+
+    {
+      LOG(INFO) << "downloading '" << storePath << "'...";
+      store->ensurePath(storePath);
+    }
+
+    {
+      LOG(INFO) << "verifying that '" << storePath << "' works...";
+      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);
+      }
+    }
+
+    {
+      LOG(INFO) << "installing '" << storePath << "' into profile '"
+                << profileDir << "'...";
+      runProgram(settings.nixBinDir + "/nix-env", false,
+                 {"--profile", profileDir, "-i", storePath, "--no-sandbox"});
+    }
+
+    LOG(INFO) << ANSI_GREEN << "upgrade to version " << version << " done"
+              << ANSI_NORMAL;
+  }
+
+  /* Return the profile in which Nix is installed. */
+  static Path getProfileDir(const ref<Store>& store) {
+    Path where;
+
+    for (auto& dir : absl::StrSplit(getEnv("PATH").value_or(""),
+                                    absl::ByChar(':'), absl::SkipEmpty())) {
+      if (pathExists(absl::StrCat(dir, "/nix-env"))) {
+        where = dir;
+        break;
+      }
+    }
+
+    if (where.empty()) {
+      throw Error(
+          "couldn't figure out how Nix is installed, so I can't upgrade it");
+    }
+
+    LOG(INFO) << "found Nix in '" << where << "'";
+
+    if (absl::StartsWith(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);
+    }
+
+    LOG(INFO) << "found profile '" << profileDir << "'";
+
+    Path userEnv = canonPath(profileDir, true);
+
+    if (baseNameOf(where) != "bin" ||
+        !absl::EndsWith(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(const 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);
+    std::unique_ptr<Bindings> bindings(Bindings::New());
+    auto v2 =
+        findAlongAttrPath(*state, settings.thisSystem, bindings.get(), *v);
+
+    return state->forceString(*v2);
+  }
+};
+}  // namespace nix
+
+static nix::RegisterCommand r1(nix::make_ref<nix::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..7de46f2a9c
--- /dev/null
+++ b/third_party/nix/src/nix/verify.cc
@@ -0,0 +1,171 @@
+#include <atomic>
+
+#include <glog/logging.h>
+
+#include "libmain/shared.hh"
+#include "libstore/store-api.hh"
+#include "libutil/sync.hh"
+#include "libutil/thread-pool.hh"
+#include "nix/command.hh"
+
+namespace nix {
+struct CmdVerify final : 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();
+
+    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};
+
+    ThreadPool pool;
+
+    auto doPath = [&](const Path& storePath) {
+      try {
+        checkInterrupt();
+
+        LOG(INFO) << "checking '" << storePath << "'";
+
+        MaintainCount<std::atomic<size_t>> mcActive(active);
+
+        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++;
+            LOG(WARNING) << "path '" << info->path
+                         << "' was modified! expected hash '"
+                         << info->narHash.to_string() << "', got '"
+                         << hash.first.to_string() << "'";
+          }
+        }
+
+        if (!noTrust) {
+          bool good = false;
+
+          if (info->ultimate && (sigsNeeded == 0u)) {
+            good = true;
+
+          } else {
+            StringSet sigsSeen;
+            size_t actualSigsNeeded =
+                std::max(sigsNeeded, static_cast<size_t>(1));
+            size_t validSigs = 0;
+
+            auto doSigs = [&](const StringSet& sigs) {
+              for (const auto& sig : sigs) {
+                if (sigsSeen.count(sig) != 0u) {
+                  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) {
+                LOG(ERROR) << e.what();
+              }
+            }
+
+            if (validSigs >= actualSigsNeeded) {
+              good = true;
+            }
+          }
+
+          if (!good) {
+            untrusted++;
+            LOG(WARNING) << "path '" << info->path << "' is untrusted";
+          }
+        }
+
+        done++;
+
+      } catch (Error& e) {
+        LOG(ERROR) << e.what();
+        failed++;
+      }
+    };
+
+    for (auto& storePath : storePaths) {
+      pool.enqueue(std::bind(doPath, storePath));
+    }
+
+    pool.process();
+
+    throw Exit((corrupted != 0u ? 1 : 0) | (untrusted != 0u ? 2 : 0) |
+               (failed != 0u ? 4 : 0));
+  }
+};
+}  // namespace nix
+
+static nix::RegisterCommand r1(nix::make_ref<nix::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..954d619ef3
--- /dev/null
+++ b/third_party/nix/src/nix/why-depends.cc
@@ -0,0 +1,269 @@
+#include <queue>
+
+#include <glog/logging.h>
+
+#include "libmain/shared.hh"
+#include "libstore/fs-accessor.hh"
+#include "libstore/store-api.hh"
+#include "nix/command.hh"
+
+namespace {
+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) != 0 ? c : '.';
+  }
+  return res;
+}
+}  // namespace
+
+namespace nix {
+struct CmdWhyDepends final : 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) == 0u) {
+      LOG(WARNING) << "'" << package->what() << "' does not depend on '"
+                   << dependency->what() << "'";
+      return;
+    }
+
+    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 std::string&, const std::string&)>
+        printNode;
+
+    const std::string treeConn = "╠═══";
+    const std::string treeLast = "╚═══";
+    const std::string treeLine = "║   ";
+    const std::string treeNull = "    ";
+
+    struct BailOut {};
+
+    printNode = [&](Node& node, const std::string& firstPad,
+                    const std::string& tailPad) {
+      assert(node.dist != inf);
+      std::cout << fmt("%s%s%s%s" ANSI_NORMAL "\n", firstPad,
+                       node.visited ? "\e[38;5;244m" : "",
+                       !firstPad.empty() ? "=> " : "", 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&) {
+    }
+  }
+};
+}  // namespace nix
+
+static nix::RegisterCommand r1(nix::make_ref<nix::CmdWhyDepends>());