about summary refs log tree commit diff
path: root/src/nix
diff options
context:
space:
mode:
Diffstat (limited to 'src/nix')
-rw-r--r--src/nix/build.cc32
-rw-r--r--src/nix/cat.cc74
-rw-r--r--src/nix/command.cc132
-rw-r--r--src/nix/command.hh149
-rw-r--r--src/nix/copy.cc57
-rw-r--r--src/nix/eval.cc43
-rw-r--r--src/nix/hash.cc140
-rw-r--r--src/nix/installables.cc255
-rw-r--r--src/nix/legacy.cc7
-rw-r--r--src/nix/legacy.hh23
-rw-r--r--src/nix/local.mk9
-rw-r--r--src/nix/log.cc47
-rw-r--r--src/nix/ls.cc123
-rw-r--r--src/nix/main.cc61
-rw-r--r--src/nix/path-info.cc105
-rw-r--r--src/nix/progress-bar.cc157
-rw-r--r--src/nix/progress-bar.hh15
-rw-r--r--src/nix/run.cc104
-rw-r--r--src/nix/show-config.cc38
-rw-r--r--src/nix/sigs.cc139
-rw-r--r--src/nix/verify.cc172
21 files changed, 1882 insertions, 0 deletions
diff --git a/src/nix/build.cc b/src/nix/build.cc
new file mode 100644
index 000000000000..00bda1fd1045
--- /dev/null
+++ b/src/nix/build.cc
@@ -0,0 +1,32 @@
+#include "command.hh"
+#include "common-args.hh"
+#include "shared.hh"
+#include "store-api.hh"
+
+using namespace nix;
+
+struct CmdBuild : MixDryRun, InstallablesCommand
+{
+    CmdBuild()
+    {
+    }
+
+    std::string name() override
+    {
+        return "build";
+    }
+
+    std::string description() override
+    {
+        return "build a derivation or fetch a store path";
+    }
+
+    void run(ref<Store> store) override
+    {
+        auto paths = buildInstallables(store, dryRun);
+
+        printInfo("build result: %s", showPaths(paths));
+    }
+};
+
+static RegisterCommand r1(make_ref<CmdBuild>());
diff --git a/src/nix/cat.cc b/src/nix/cat.cc
new file mode 100644
index 000000000000..2405a8cb44ef
--- /dev/null
+++ b/src/nix/cat.cc
@@ -0,0 +1,74 @@
+#include "command.hh"
+#include "store-api.hh"
+#include "fs-accessor.hh"
+#include "nar-accessor.hh"
+
+using namespace nix;
+
+struct MixCat : virtual Args
+{
+    std::string path;
+
+    void cat(ref<FSAccessor> accessor)
+    {
+        auto st = accessor->stat(path);
+        if (st.type == FSAccessor::Type::tMissing)
+            throw Error(format("path ‘%1%’ does not exist") % path);
+        if (st.type != FSAccessor::Type::tRegular)
+            throw Error(format("path ‘%1%’ is not a regular file") % path);
+
+        std::cout << accessor->readFile(path);
+    }
+};
+
+struct CmdCatStore : StoreCommand, MixCat
+{
+    CmdCatStore()
+    {
+        expectArg("path", &path);
+    }
+
+    std::string name() override
+    {
+        return "cat-store";
+    }
+
+    std::string description() override
+    {
+        return "print the contents of a store file on stdout";
+    }
+
+    void run(ref<Store> store) override
+    {
+        cat(store->getFSAccessor());
+    }
+};
+
+struct CmdCatNar : StoreCommand, MixCat
+{
+    Path narPath;
+
+    CmdCatNar()
+    {
+        expectArg("nar", &narPath);
+        expectArg("path", &path);
+    }
+
+    std::string name() override
+    {
+        return "cat-nar";
+    }
+
+    std::string description() override
+    {
+        return "print the contents of a file inside a NAR file";
+    }
+
+    void run(ref<Store> store) override
+    {
+        cat(makeNarAccessor(make_ref<std::string>(readFile(narPath))));
+    }
+};
+
+static RegisterCommand r1(make_ref<CmdCatStore>());
+static RegisterCommand r2(make_ref<CmdCatNar>());
diff --git a/src/nix/command.cc b/src/nix/command.cc
new file mode 100644
index 000000000000..a45f2888bfb5
--- /dev/null
+++ b/src/nix/command.cc
@@ -0,0 +1,132 @@
+#include "command.hh"
+#include "store-api.hh"
+#include "derivations.hh"
+
+namespace nix {
+
+Commands * RegisterCommand::commands = 0;
+
+void Command::printHelp(const string & programName, std::ostream & out)
+{
+    Args::printHelp(programName, out);
+
+    auto exs = examples();
+    if (!exs.empty()) {
+        out << "\n";
+        out << "Examples:\n";
+        for (auto & ex : exs)
+            out << "\n"
+                << "  " << ex.description << "\n" // FIXME: wrap
+                << "  $ " << ex.command << "\n";
+    }
+}
+
+MultiCommand::MultiCommand(const Commands & _commands)
+    : commands(_commands)
+{
+    expectedArgs.push_back(ExpectedArg{"command", 1, [=](Strings ss) {
+        assert(!command);
+        auto i = commands.find(ss.front());
+        if (i == commands.end())
+            throw UsageError(format("‘%1%’ is not a recognised command") % ss.front());
+        command = i->second;
+    }});
+}
+
+void MultiCommand::printHelp(const string & programName, std::ostream & out)
+{
+    if (command) {
+        command->printHelp(programName + " " + command->name(), out);
+        return;
+    }
+
+    out << "Usage: " << programName << " <COMMAND> <FLAGS>... <ARGS>...\n";
+
+    out << "\n";
+    out << "Common flags:\n";
+    printFlags(out);
+
+    out << "\n";
+    out << "Available commands:\n";
+
+    Table2 table;
+    for (auto & command : commands)
+        table.push_back(std::make_pair(command.second->name(), command.second->description()));
+    printTable(out, table);
+
+    out << "\n";
+    out << "For full documentation, run ‘man " << programName << "’ or ‘man " << programName << "-<COMMAND>’.\n";
+}
+
+bool MultiCommand::processFlag(Strings::iterator & pos, Strings::iterator end)
+{
+    if (Args::processFlag(pos, end)) return true;
+    if (command && command->processFlag(pos, end)) return true;
+    return false;
+}
+
+bool MultiCommand::processArgs(const Strings & args, bool finish)
+{
+    if (command)
+        return command->processArgs(args, finish);
+    else
+        return Args::processArgs(args, finish);
+}
+
+StoreCommand::StoreCommand()
+{
+    storeUri = getEnv("NIX_REMOTE");
+
+    mkFlag(0, "store", "store-uri", "URI of the Nix store to use", &storeUri);
+}
+
+ref<Store> StoreCommand::getStore()
+{
+    if (!_store)
+        _store = createStore();
+    return ref<Store>(_store);
+}
+
+ref<Store> StoreCommand::createStore()
+{
+    return openStore(storeUri);
+}
+
+void StoreCommand::run()
+{
+    run(createStore());
+}
+
+StorePathsCommand::StorePathsCommand()
+{
+    mkFlag('r', "recursive", "apply operation to closure of the specified paths", &recursive);
+    mkFlag(0, "all", "apply operation to the entire store", &all);
+}
+
+void StorePathsCommand::run(ref<Store> store)
+{
+    Paths storePaths;
+
+    if (all) {
+        if (installables.size())
+            throw UsageError("‘--all’ does not expect arguments");
+        for (auto & p : store->queryAllValidPaths())
+            storePaths.push_back(p);
+    }
+
+    else {
+        for (auto & p : buildInstallables(store, false))
+            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);
+}
+
+}
diff --git a/src/nix/command.hh b/src/nix/command.hh
new file mode 100644
index 000000000000..dc7b2637d66a
--- /dev/null
+++ b/src/nix/command.hh
@@ -0,0 +1,149 @@
+#pragma once
+
+#include "args.hh"
+
+namespace nix {
+
+struct Value;
+class EvalState;
+
+/* A command is an argument parser that can be executed by calling its
+   run() method. */
+struct Command : virtual Args
+{
+    virtual std::string name() = 0;
+    virtual void prepare() { };
+    virtual void run() = 0;
+
+    struct Example
+    {
+        std::string description;
+        std::string command;
+    };
+
+    typedef std::list<Example> Examples;
+
+    virtual Examples examples() { return Examples(); }
+
+    void printHelp(const string & programName, std::ostream & out) override;
+};
+
+class Store;
+
+/* A command that require a Nix store. */
+struct StoreCommand : virtual Command
+{
+    std::string storeUri;
+    StoreCommand();
+    void run() override;
+    ref<Store> getStore();
+    virtual ref<Store> createStore();
+    virtual void run(ref<Store>) = 0;
+
+private:
+    std::shared_ptr<Store> _store;
+};
+
+struct Installable
+{
+    virtual std::string what() = 0;
+
+    virtual PathSet toBuildable()
+    {
+        throw Error("argument ‘%s’ cannot be built", what());
+    }
+
+    virtual Value * toValue(EvalState & state)
+    {
+        throw Error("argument ‘%s’ cannot be evaluated", what());
+    }
+};
+
+/* A command that operates on a list of "installables", which can be
+   store paths, attribute paths, Nix expressions, etc. */
+struct InstallablesCommand : virtual Args, StoreCommand
+{
+    std::vector<std::shared_ptr<Installable>> installables;
+    Path file;
+
+    InstallablesCommand()
+    {
+        mkFlag('f', "file", "file", "evaluate FILE rather than the default", &file);
+        expectArgs("installables", &_installables);
+    }
+
+    /* 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);
+
+    std::vector<std::shared_ptr<Installable>> parseInstallables(ref<Store> store, Strings installables);
+
+    PathSet buildInstallables(ref<Store> store, bool dryRun);
+
+    ref<EvalState> getEvalState();
+
+    void prepare() override;
+
+private:
+
+    Strings _installables;
+
+    std::shared_ptr<EvalState> evalState;
+
+    Value * vSourceExpr = 0;
+};
+
+/* A command that operates on zero or more store paths. */
+struct StorePathsCommand : public InstallablesCommand
+{
+private:
+
+    bool recursive = false;
+    bool all = false;
+
+public:
+
+    StorePathsCommand();
+
+    using StoreCommand::run;
+
+    virtual void run(ref<Store> store, Paths storePaths) = 0;
+
+    void run(ref<Store> store) override;
+};
+
+typedef std::map<std::string, ref<Command>> Commands;
+
+/* An argument parser that supports multiple subcommands,
+   i.e. ‘<command> <subcommand>’. */
+class MultiCommand : virtual Args
+{
+public:
+    Commands commands;
+
+    std::shared_ptr<Command> command;
+
+    MultiCommand(const Commands & commands);
+
+    void printHelp(const string & programName, std::ostream & out) override;
+
+    bool processFlag(Strings::iterator & pos, Strings::iterator end) override;
+
+    bool processArgs(const Strings & args, bool finish) override;
+};
+
+/* A helper class for registering commands globally. */
+struct RegisterCommand
+{
+    static Commands * commands;
+
+    RegisterCommand(ref<Command> command)
+    {
+        if (!commands) commands = new Commands;
+        commands->emplace(command->name(), command);
+    }
+};
+
+}
diff --git a/src/nix/copy.cc b/src/nix/copy.cc
new file mode 100644
index 000000000000..b2165cb8f85c
--- /dev/null
+++ b/src/nix/copy.cc
@@ -0,0 +1,57 @@
+#include "command.hh"
+#include "shared.hh"
+#include "store-api.hh"
+#include "sync.hh"
+#include "thread-pool.hh"
+
+#include <atomic>
+
+using namespace nix;
+
+struct CmdCopy : StorePathsCommand
+{
+    std::string srcUri, dstUri;
+
+    CmdCopy()
+    {
+        mkFlag(0, "from", "store-uri", "URI of the source Nix store", &srcUri);
+        mkFlag(0, "to", "store-uri", "URI of the destination Nix store", &dstUri);
+    }
+
+    std::string name() override
+    {
+        return "copy";
+    }
+
+    std::string description() override
+    {
+        return "copy paths between Nix stores";
+    }
+
+    Examples examples() override
+    {
+        return {
+            Example{
+                "To copy Firefox to the local store to a binary cache in file:///tmp/cache:",
+                "nix copy --to file:///tmp/cache -r $(type -p firefox)"
+            },
+        };
+    }
+
+    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()));
+    }
+};
+
+static RegisterCommand r1(make_ref<CmdCopy>());
diff --git a/src/nix/eval.cc b/src/nix/eval.cc
new file mode 100644
index 000000000000..eb2b13a2dcd7
--- /dev/null
+++ b/src/nix/eval.cc
@@ -0,0 +1,43 @@
+#include "command.hh"
+#include "common-args.hh"
+#include "shared.hh"
+#include "store-api.hh"
+#include "eval.hh"
+#include "json.hh"
+#include "value-to-json.hh"
+
+using namespace nix;
+
+struct CmdEval : MixJSON, InstallablesCommand
+{
+    std::string name() override
+    {
+        return "eval";
+    }
+
+    std::string description() override
+    {
+        return "evaluate a Nix expression";
+    }
+
+    void run(ref<Store> store) override
+    {
+        auto state = getEvalState();
+
+        auto jsonOut = json ? std::make_unique<JSONList>(std::cout) : nullptr;
+
+        for (auto & i : installables) {
+            auto v = i->toValue(*state);
+            if (json) {
+                PathSet context;
+                auto jsonElem = jsonOut->placeholder();
+                printValueAsJSON(*state, true, *v, jsonElem, context);
+            } else {
+                state->forceValueDeep(*v);
+                std::cout << *v << "\n";
+            }
+        }
+    }
+};
+
+static RegisterCommand r1(make_ref<CmdEval>());
diff --git a/src/nix/hash.cc b/src/nix/hash.cc
new file mode 100644
index 000000000000..5dd891e8add3
--- /dev/null
+++ b/src/nix/hash.cc
@@ -0,0 +1,140 @@
+#include "command.hh"
+#include "hash.hh"
+#include "legacy.hh"
+#include "shared.hh"
+
+using namespace nix;
+
+struct CmdHash : Command
+{
+    enum Mode { mFile, mPath };
+    Mode mode;
+    bool base32 = false;
+    bool truncate = false;
+    HashType ht = htSHA512;
+    Strings paths;
+
+    CmdHash(Mode mode) : mode(mode)
+    {
+        mkFlag(0, "base32", "print hash in base-32", &base32);
+        mkFlag(0, "base16", "print hash in base-16", &base32, false);
+        mkHashTypeFlag("type", &ht);
+        expectArgs("paths", &paths);
+    }
+
+    std::string name() override
+    {
+        return mode == mFile ? "hash-file" : "hash-path";
+    }
+
+    std::string description() override
+    {
+        return mode == mFile
+            ? "print cryptographic hash of a regular file"
+            : "print cryptographic hash of the NAR serialisation of a path";
+    }
+
+    void run() override
+    {
+        for (auto path : paths) {
+            Hash h = mode == mFile ? hashFile(ht, path) : hashPath(ht, path).first;
+            if (truncate && h.hashSize > 20) h = compressHash(h, 20);
+            std::cout << format("%1%\n") %
+                (base32 ? printHash32(h) : printHash(h));
+        }
+    }
+};
+
+static RegisterCommand r1(make_ref<CmdHash>(CmdHash::mFile));
+static RegisterCommand r2(make_ref<CmdHash>(CmdHash::mPath));
+
+struct CmdToBase : Command
+{
+    bool toBase32;
+    HashType ht = htSHA512;
+    Strings args;
+
+    CmdToBase(bool toBase32) : toBase32(toBase32)
+    {
+        mkHashTypeFlag("type", &ht);
+        expectArgs("strings", &args);
+    }
+
+    std::string name() override
+    {
+        return toBase32 ? "to-base32" : "to-base16";
+    }
+
+    std::string description() override
+    {
+        return toBase32
+            ? "convert a hash to base-32 representation"
+            : "convert a hash to base-16 representation";
+    }
+
+    void run() override
+    {
+        for (auto s : args) {
+            Hash h = parseHash16or32(ht, s);
+            std::cout << format("%1%\n") %
+                (toBase32 ? printHash32(h) : printHash(h));
+        }
+    }
+};
+
+static RegisterCommand r3(make_ref<CmdToBase>(false));
+static RegisterCommand r4(make_ref<CmdToBase>(true));
+
+/* 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;
+    Strings ss;
+
+    parseCmdLine(argc, argv, [&](Strings::iterator & arg, const Strings::iterator & end) {
+        if (*arg == "--help")
+            showManPage("nix-hash");
+        else if (*arg == "--version")
+            printVersion("nix-hash");
+        else if (*arg == "--flat") flat = true;
+        else if (*arg == "--base32") base32 = true;
+        else if (*arg == "--truncate") truncate = true;
+        else if (*arg == "--type") {
+            string s = getArg(*arg, arg, end);
+            ht = parseHashType(s);
+            if (ht == htUnknown)
+                throw UsageError(format("unknown hash type ‘%1%’") % s);
+        }
+        else if (*arg == "--to-base16") op = opTo16;
+        else if (*arg == "--to-base32") op = opTo32;
+        else if (*arg != "" && arg->at(0) == '-')
+            return false;
+        else
+            ss.push_back(*arg);
+        return true;
+    });
+
+    if (op == opHash) {
+        CmdHash cmd(flat ? CmdHash::mFile : CmdHash::mPath);
+        cmd.ht = ht;
+        cmd.base32 = base32;
+        cmd.truncate = truncate;
+        cmd.paths = ss;
+        cmd.run();
+    }
+
+    else {
+        CmdToBase cmd(op == opTo32);
+        cmd.args = ss;
+        cmd.ht = ht;
+        cmd.run();
+    }
+
+    return 0;
+}
+
+static RegisterLegacyCommand s1("nix-hash", compatNixHash);
diff --git a/src/nix/installables.cc b/src/nix/installables.cc
new file mode 100644
index 000000000000..57580049f25f
--- /dev/null
+++ b/src/nix/installables.cc
@@ -0,0 +1,255 @@
+#include "command.hh"
+#include "attr-path.hh"
+#include "common-opts.hh"
+#include "derivations.hh"
+#include "eval-inline.hh"
+#include "eval.hh"
+#include "get-drvs.hh"
+#include "store-api.hh"
+#include "shared.hh"
+
+#include <regex>
+
+namespace nix {
+
+Value * InstallablesCommand::getSourceExpr(EvalState & state)
+{
+    if (vSourceExpr) return vSourceExpr;
+
+    vSourceExpr = state.allocValue();
+
+    if (file != "") {
+        Expr * e = state.parseExprFromFile(resolveExprPath(lookupFileArg(state, file)));
+        state.eval(e, *vSourceExpr);
+    }
+
+    else {
+
+        /* Construct the installation source from $NIX_PATH. */
+
+        auto searchPath = state.getSearchPath();
+
+        state.mkAttrs(*vSourceExpr, searchPath.size());
+
+        std::unordered_set<std::string> seen;
+
+        for (auto & i : searchPath) {
+            if (i.first == "") continue;
+            if (seen.count(i.first)) continue;
+            seen.insert(i.first);
+#if 0
+            auto res = state.resolveSearchPathElem(i);
+            if (!res.first) continue;
+            if (!pathExists(res.second)) continue;
+            mkApp(*state.allocAttr(*vSourceExpr, state.symbols.create(i.first)),
+                state.getBuiltin("import"),
+                mkString(*state.allocValue(), res.second));
+#endif
+            Value * v1 = state.allocValue();
+            mkPrimOpApp(*v1, state.getBuiltin("findFile"), state.getBuiltin("nixPath"));
+            Value * v2 = state.allocValue();
+            mkApp(*v2, *v1, mkString(*state.allocValue(), i.first));
+            mkApp(*state.allocAttr(*vSourceExpr, state.symbols.create(i.first)),
+                state.getBuiltin("import"), *v2);
+        }
+
+        vSourceExpr->attrs->sort();
+    }
+
+    return vSourceExpr;
+}
+
+struct InstallableStoreDrv : Installable
+{
+    Path storePath;
+
+    InstallableStoreDrv(const Path & storePath) : storePath(storePath) { }
+
+    std::string what() override { return storePath; }
+
+    PathSet toBuildable() override
+    {
+        return {storePath};
+    }
+};
+
+struct InstallableStorePath : Installable
+{
+    Path storePath;
+
+    InstallableStorePath(const Path & storePath) : storePath(storePath) { }
+
+    std::string what() override { return storePath; }
+
+    PathSet toBuildable() override
+    {
+        return {storePath};
+    }
+};
+
+struct InstallableExpr : Installable
+{
+    InstallablesCommand & installables;
+    std::string text;
+
+    InstallableExpr(InstallablesCommand & installables, const std::string & text)
+         : installables(installables), text(text) { }
+
+    std::string what() override { return text; }
+
+    PathSet toBuildable() override
+    {
+        auto state = installables.getEvalState();
+
+        auto v = toValue(*state);
+
+        // FIXME
+        std::map<string, string> autoArgs_;
+        Bindings & autoArgs(*evalAutoArgs(*state, autoArgs_));
+
+        DrvInfos drvs;
+        getDerivations(*state, *v, "", autoArgs, drvs, false);
+
+        PathSet res;
+
+        for (auto & i : drvs)
+            res.insert(i.queryDrvPath());
+
+        return res;
+    }
+
+    Value * toValue(EvalState & state) override
+    {
+        auto v = state.allocValue();
+        state.eval(state.parseExprFromString(text, absPath(".")), *v);
+        return v;
+    }
+};
+
+struct InstallableAttrPath : Installable
+{
+    InstallablesCommand & installables;
+    std::string attrPath;
+
+    InstallableAttrPath(InstallablesCommand & installables, const std::string & attrPath)
+        : installables(installables), attrPath(attrPath)
+    { }
+
+    std::string what() override { return attrPath; }
+
+    PathSet toBuildable() override
+    {
+        auto state = installables.getEvalState();
+
+        auto v = toValue(*state);
+
+        // FIXME
+        std::map<string, string> autoArgs_;
+        Bindings & autoArgs(*evalAutoArgs(*state, autoArgs_));
+
+        DrvInfos drvs;
+        getDerivations(*state, *v, "", autoArgs, drvs, false);
+
+        PathSet res;
+
+        for (auto & i : drvs)
+            res.insert(i.queryDrvPath());
+
+        return res;
+    }
+
+    Value * toValue(EvalState & state) override
+    {
+        auto source = installables.getSourceExpr(state);
+
+        // FIXME
+        std::map<string, string> autoArgs_;
+        Bindings & autoArgs(*evalAutoArgs(state, autoArgs_));
+
+        Value * v = findAlongAttrPath(state, attrPath, autoArgs, *source);
+        state.forceValue(*v);
+
+        return v;
+    }
+};
+
+// FIXME: extend
+std::string attrRegex = R"([A-Za-z_][A-Za-z0-9-_+]*)";
+static std::regex attrPathRegex(fmt(R"(%1%(\.%1%)*)", attrRegex));
+
+std::vector<std::shared_ptr<Installable>> InstallablesCommand::parseInstallables(ref<Store> store, Strings installables)
+{
+    std::vector<std::shared_ptr<Installable>> result;
+
+    if (installables.empty()) {
+        if (file == "")
+            file = ".";
+        installables = Strings{""};
+    }
+
+    for (auto & installable : installables) {
+
+        if (installable.find("/") != std::string::npos) {
+
+            auto path = store->toStorePath(store->followLinksToStore(installable));
+
+            if (store->isStorePath(path)) {
+                if (isDerivation(path))
+                    result.push_back(std::make_shared<InstallableStoreDrv>(path));
+                else
+                    result.push_back(std::make_shared<InstallableStorePath>(path));
+            }
+        }
+
+        else if (installable.compare(0, 1, "(") == 0)
+            result.push_back(std::make_shared<InstallableExpr>(*this, installable));
+
+        else if (installable == "" || std::regex_match(installable, attrPathRegex))
+            result.push_back(std::make_shared<InstallableAttrPath>(*this, installable));
+
+        else
+            throw UsageError("don't know what to do with argument ‘%s’", installable);
+    }
+
+    return result;
+}
+
+PathSet InstallablesCommand::buildInstallables(ref<Store> store, bool dryRun)
+{
+    PathSet buildables;
+
+    for (auto & i : installables) {
+        auto b = i->toBuildable();
+        buildables.insert(b.begin(), b.end());
+    }
+
+    printMissing(store, buildables);
+
+    if (!dryRun)
+        store->buildPaths(buildables);
+
+    PathSet outPaths;
+    for (auto & path : buildables)
+        if (isDerivation(path)) {
+            Derivation drv = store->derivationFromPath(path);
+            for (auto & output : drv.outputs)
+                outPaths.insert(output.second.path);
+        } else
+            outPaths.insert(path);
+
+    return outPaths;
+}
+
+ref<EvalState> InstallablesCommand::getEvalState()
+{
+    if (!evalState)
+        evalState = std::make_shared<EvalState>(Strings{}, getStore());
+    return ref<EvalState>(evalState);
+}
+
+void InstallablesCommand::prepare()
+{
+    installables = parseInstallables(getStore(), _installables);
+}
+
+}
diff --git a/src/nix/legacy.cc b/src/nix/legacy.cc
new file mode 100644
index 000000000000..6df09ee37a5e
--- /dev/null
+++ b/src/nix/legacy.cc
@@ -0,0 +1,7 @@
+#include "legacy.hh"
+
+namespace nix {
+
+RegisterLegacyCommand::Commands * RegisterLegacyCommand::commands = 0;
+
+}
diff --git a/src/nix/legacy.hh b/src/nix/legacy.hh
new file mode 100644
index 000000000000..f503b0da3e1a
--- /dev/null
+++ b/src/nix/legacy.hh
@@ -0,0 +1,23 @@
+#pragma once
+
+#include <functional>
+#include <map>
+#include <string>
+
+namespace nix {
+
+typedef std::function<void(int, char * *)> MainFunction;
+
+struct RegisterLegacyCommand
+{
+    typedef std::map<std::string, MainFunction> Commands;
+    static Commands * commands;
+
+    RegisterLegacyCommand(const std::string & name, MainFunction fun)
+    {
+        if (!commands) commands = new Commands;
+        (*commands)[name] = fun;
+    }
+};
+
+}
diff --git a/src/nix/local.mk b/src/nix/local.mk
new file mode 100644
index 000000000000..f6e7073b6e7d
--- /dev/null
+++ b/src/nix/local.mk
@@ -0,0 +1,9 @@
+programs += nix
+
+nix_DIR := $(d)
+
+nix_SOURCES := $(wildcard $(d)/*.cc)
+
+nix_LIBS = libexpr libmain libstore libutil libformat
+
+$(eval $(call install-symlink, nix, $(bindir)/nix-hash))
diff --git a/src/nix/log.cc b/src/nix/log.cc
new file mode 100644
index 000000000000..ed610261d1ca
--- /dev/null
+++ b/src/nix/log.cc
@@ -0,0 +1,47 @@
+#include "command.hh"
+#include "common-args.hh"
+#include "shared.hh"
+#include "store-api.hh"
+
+using namespace nix;
+
+struct CmdLog : InstallablesCommand
+{
+    CmdLog()
+    {
+    }
+
+    std::string name() override
+    {
+        return "log";
+    }
+
+    std::string description() override
+    {
+        return "show the build log of the specified packages or paths";
+    }
+
+    void run(ref<Store> store) override
+    {
+        auto subs = getDefaultSubstituters();
+
+        subs.push_front(store);
+
+        for (auto & inst : installables) {
+            for (auto & path : inst->toBuildable()) {
+                bool found = false;
+                for (auto & sub : subs) {
+                    auto log = sub->getBuildLog(path);
+                    if (!log) continue;
+                    std::cout << *log;
+                    found = true;
+                    break;
+                }
+                if (!found)
+                    throw Error("build log of path ‘%s’ is not available", path);
+            }
+        }
+    }
+};
+
+static RegisterCommand r1(make_ref<CmdLog>());
diff --git a/src/nix/ls.cc b/src/nix/ls.cc
new file mode 100644
index 000000000000..3476dfb05287
--- /dev/null
+++ b/src/nix/ls.cc
@@ -0,0 +1,123 @@
+#include "command.hh"
+#include "store-api.hh"
+#include "fs-accessor.hh"
+#include "nar-accessor.hh"
+
+using namespace nix;
+
+struct MixLs : virtual Args
+{
+    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 list(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);
+    }
+};
+
+struct CmdLsStore : StoreCommand, MixLs
+{
+    CmdLsStore()
+    {
+        expectArg("path", &path);
+    }
+
+    std::string name() override
+    {
+        return "ls-store";
+    }
+
+    std::string description() override
+    {
+        return "show information about a store path";
+    }
+
+    void run(ref<Store> store) override
+    {
+        list(store->getFSAccessor());
+    }
+};
+
+struct CmdLsNar : Command, MixLs
+{
+    Path narPath;
+
+    CmdLsNar()
+    {
+        expectArg("nar", &narPath);
+        expectArg("path", &path);
+    }
+
+    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))));
+    }
+};
+
+static RegisterCommand r1(make_ref<CmdLsStore>());
+static RegisterCommand r2(make_ref<CmdLsNar>());
diff --git a/src/nix/main.cc b/src/nix/main.cc
new file mode 100644
index 000000000000..440ced97dfcc
--- /dev/null
+++ b/src/nix/main.cc
@@ -0,0 +1,61 @@
+#include <algorithm>
+
+#include "command.hh"
+#include "common-args.hh"
+#include "eval.hh"
+#include "globals.hh"
+#include "legacy.hh"
+#include "shared.hh"
+#include "store-api.hh"
+#include "progress-bar.hh"
+
+namespace nix {
+
+struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
+{
+    NixArgs() : MultiCommand(*RegisterCommand::commands), MixCommonArgs("nix")
+    {
+        mkFlag('h', "help", "show usage information", [=]() {
+            printHelp(programName, std::cout);
+            std::cout << "\nNote: this program is EXPERIMENTAL and subject to change.\n";
+            throw Exit();
+        });
+
+        mkFlag(0, "version", "show version information", std::bind(printVersion, programName));
+    }
+};
+
+void mainWrapped(int argc, char * * argv)
+{
+    settings.verboseBuild = false;
+
+    initNix();
+    initGC();
+
+    string programName = baseNameOf(argv[0]);
+
+    {
+        auto legacy = (*RegisterLegacyCommand::commands)[programName];
+        if (legacy) return legacy(argc, argv);
+    }
+
+    NixArgs args;
+
+    args.parseCmdline(argvToStrings(argc, argv));
+
+    assert(args.command);
+
+    StartProgressBar bar;
+
+    args.command->prepare();
+    args.command->run();
+}
+
+}
+
+int main(int argc, char * * argv)
+{
+    return nix::handleExceptions(argv[0], [&]() {
+        nix::mainWrapped(argc, argv);
+    });
+}
diff --git a/src/nix/path-info.cc b/src/nix/path-info.cc
new file mode 100644
index 000000000000..f16209238610
--- /dev/null
+++ b/src/nix/path-info.cc
@@ -0,0 +1,105 @@
+#include "command.hh"
+#include "shared.hh"
+#include "store-api.hh"
+#include "json.hh"
+#include "common-args.hh"
+
+#include <iomanip>
+#include <algorithm>
+
+using namespace nix;
+
+struct CmdPathInfo : StorePathsCommand, MixJSON
+{
+    bool showSize = false;
+    bool showClosureSize = 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(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 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 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, true);
+            store->pathInfoToJSON(jsonRoot,
+                // FIXME: preserve order?
+                PathSet(storePaths.begin(), storePaths.end()),
+                true, showClosureSize);
+        }
+
+        else {
+
+            for (auto storePath : storePaths) {
+                auto info = store->queryPathInfo(storePath);
+                storePath = info->path; // FIXME: screws up padding
+
+                std::cout << storePath << std::string(std::max(0, (int) pathLen - (int) storePath.size()), ' ');
+
+                if (showSize)
+                    std::cout << '\t' << std::setw(11) << info->narSize;
+
+                if (showClosureSize)
+                    std::cout << '\t' << std::setw(11) << store->getClosureSize(storePath);
+
+                if (showSigs) {
+                    std::cout << '\t';
+                    Strings ss;
+                    if (info->ultimate) ss.push_back("ultimate");
+                    if (info->ca != "") ss.push_back("ca:" + info->ca);
+                    for (auto & sig : info->sigs) ss.push_back(sig);
+                    std::cout << concatStringsSep(" ", ss);
+                }
+
+                std::cout << std::endl;
+            }
+
+        }
+    }
+};
+
+static RegisterCommand r1(make_ref<CmdPathInfo>());
diff --git a/src/nix/progress-bar.cc b/src/nix/progress-bar.cc
new file mode 100644
index 000000000000..69811b282804
--- /dev/null
+++ b/src/nix/progress-bar.cc
@@ -0,0 +1,157 @@
+#include "progress-bar.hh"
+#include "util.hh"
+#include "sync.hh"
+
+#include <map>
+
+namespace nix {
+
+class ProgressBar : public Logger
+{
+private:
+
+    struct ActInfo
+    {
+        Activity * activity;
+        Verbosity lvl;
+        std::string s;
+    };
+
+    struct Progress
+    {
+        uint64_t expected = 0, progress = 0;
+    };
+
+    struct State
+    {
+        std::list<ActInfo> activities;
+        std::map<Activity *, std::list<ActInfo>::iterator> its;
+        std::map<std::string, Progress> progress;
+    };
+
+    Sync<State> state_;
+
+public:
+
+    ~ProgressBar()
+    {
+        auto state(state_.lock());
+        assert(state->activities.empty());
+        writeToStderr("\r\e[K");
+    }
+
+    void log(Verbosity lvl, const FormatOrString & fs) override
+    {
+        auto state(state_.lock());
+        log(*state, lvl, fs.s);
+    }
+
+    void log(State & state, Verbosity lvl, const std::string & s)
+    {
+        writeToStderr("\r\e[K" + s + "\n");
+        update(state);
+    }
+
+    void startActivity(Activity & activity, Verbosity lvl, const FormatOrString & fs) override
+    {
+        if (lvl > verbosity) return;
+        auto state(state_.lock());
+        state->activities.emplace_back(ActInfo{&activity, lvl, fs.s});
+        state->its.emplace(&activity, std::prev(state->activities.end()));
+        update(*state);
+    }
+
+    void stopActivity(Activity & activity) override
+    {
+        auto state(state_.lock());
+        auto i = state->its.find(&activity);
+        if (i == state->its.end()) return;
+        state->activities.erase(i->second);
+        state->its.erase(i);
+        update(*state);
+    }
+
+    void setExpected(const std::string & label, uint64_t value) override
+    {
+        auto state(state_.lock());
+        state->progress[label].expected = value;
+    }
+
+    void setProgress(const std::string & label, uint64_t value) override
+    {
+        auto state(state_.lock());
+        state->progress[label].progress = value;
+    }
+
+    void incExpected(const std::string & label, uint64_t value) override
+    {
+        auto state(state_.lock());
+        state->progress[label].expected += value;
+    }
+
+    void incProgress(const std::string & label, uint64_t value) override
+    {
+        auto state(state_.lock());
+        state->progress[label].progress += value;
+    }
+
+    void update()
+    {
+        auto state(state_.lock());
+    }
+
+    void update(State & state)
+    {
+        std::string line = "\r";
+
+        std::string status = getStatus(state);
+        if (!status.empty()) {
+            line += '[';
+            line += status;
+            line += "]";
+        }
+
+        if (!state.activities.empty()) {
+            if (!status.empty()) line += " ";
+            line += state.activities.rbegin()->s;
+        }
+
+        line += "\e[K";
+        writeToStderr(line);
+    }
+
+    std::string getStatus(State & state)
+    {
+        std::string res;
+        for (auto & p : state.progress)
+            if (p.second.expected || p.second.progress) {
+                if (!res.empty()) res += ", ";
+                res += std::to_string(p.second.progress);
+                if (p.second.expected) {
+                    res += "/";
+                    res += std::to_string(p.second.expected);
+                }
+                res += " "; res += p.first;
+            }
+        return res;
+    }
+};
+
+StartProgressBar::StartProgressBar()
+{
+    if (isatty(STDERR_FILENO)) {
+        prev = logger;
+        logger = new ProgressBar();
+    }
+}
+
+StartProgressBar::~StartProgressBar()
+{
+    if (prev) {
+        auto bar = logger;
+        logger = prev;
+        delete bar;
+    }
+}
+
+}
diff --git a/src/nix/progress-bar.hh b/src/nix/progress-bar.hh
new file mode 100644
index 000000000000..d2e44f7c4fd9
--- /dev/null
+++ b/src/nix/progress-bar.hh
@@ -0,0 +1,15 @@
+#pragma once
+
+#include "logging.hh"
+
+namespace nix {
+
+class StartProgressBar
+{
+    Logger * prev = 0;
+public:
+    StartProgressBar();
+    ~StartProgressBar();
+};
+
+}
diff --git a/src/nix/run.cc b/src/nix/run.cc
new file mode 100644
index 000000000000..bcfa74eb5f5f
--- /dev/null
+++ b/src/nix/run.cc
@@ -0,0 +1,104 @@
+#include "command.hh"
+#include "common-args.hh"
+#include "shared.hh"
+#include "store-api.hh"
+#include "derivations.hh"
+#include "local-store.hh"
+#include "finally.hh"
+
+#if __linux__
+#include <sys/mount.h>
+#endif
+
+using namespace nix;
+
+struct CmdRun : InstallablesCommand
+{
+    CmdRun()
+    {
+    }
+
+    std::string name() override
+    {
+        return "run";
+    }
+
+    std::string description() override
+    {
+        return "run a shell in which the specified packages are available";
+    }
+
+    void run(ref<Store> store) override
+    {
+        auto outPaths = buildInstallables(store, false);
+
+        auto store2 = store.dynamic_pointer_cast<LocalStore>();
+
+        if (store2 && store->storeDir != store2->realStoreDir) {
+#if __linux__
+            uid_t uid = getuid();
+            uid_t gid = getgid();
+
+            if (unshare(CLONE_NEWUSER | CLONE_NEWNS) == -1)
+                throw SysError("setting up a private mount namespace");
+
+            /* Bind-mount realStoreDir on /nix/store. If the latter
+               mount point doesn't already exists, we have to create a
+               chroot environment containing the mount point and bind
+               mounts for the children of /. Would be nice if we could
+               use overlayfs here, but that doesn't work in a user
+               namespace yet (Ubuntu has a patch for this:
+               https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1478578). */
+            if (!pathExists(store->storeDir)) {
+                // FIXME: Use overlayfs?
+
+                Path tmpDir = createTempDir();
+
+                createDirs(tmpDir + store->storeDir);
+
+                if (mount(store2->realStoreDir.c_str(), (tmpDir + store->storeDir).c_str(), "", MS_BIND, 0) == -1)
+                    throw SysError(format("mounting ‘%s’ on ‘%s’") % store2->realStoreDir % store->storeDir);
+
+                for (auto entry : readDirectory("/")) {
+                    Path dst = tmpDir + "/" + entry.name;
+                    if (pathExists(dst)) continue;
+                    if (mkdir(dst.c_str(), 0700) == -1)
+                        throw SysError(format("creating directory ‘%s’") % dst);
+                    if (mount(("/" + entry.name).c_str(), dst.c_str(), "", MS_BIND | MS_REC, 0) == -1)
+                        throw SysError(format("mounting ‘%s’ on ‘%s’") %  ("/" + entry.name) % dst);
+                }
+
+                char * cwd = getcwd(0, 0);
+                if (!cwd) throw SysError("getting current directory");
+                Finally freeCwd([&]() { free(cwd); });
+
+                if (chroot(tmpDir.c_str()) == -1)
+                    throw SysError(format("chrooting into ‘%s’") % tmpDir);
+
+                if (chdir(cwd) == -1)
+                    throw SysError(format("chdir to ‘%s’ in chroot") % cwd);
+            } else
+                if (mount(store2->realStoreDir.c_str(), store->storeDir.c_str(), "", MS_BIND, 0) == -1)
+                    throw SysError(format("mounting ‘%s’ on ‘%s’") % store2->realStoreDir % store->storeDir);
+
+            writeFile("/proc/self/setgroups", "deny");
+            writeFile("/proc/self/uid_map", (format("%d %d %d") % uid % uid % 1).str());
+            writeFile("/proc/self/gid_map", (format("%d %d %d") % gid % gid % 1).str());
+#else
+            throw Error(format("mounting the Nix store on ‘%s’ is not supported on this platform") % store->storeDir);
+#endif
+        }
+
+
+        auto unixPath = tokenizeString<Strings>(getEnv("PATH"), ":");
+        for (auto & path : outPaths)
+            if (pathExists(path + "/bin"))
+                unixPath.push_front(path + "/bin");
+        setenv("PATH", concatStringsSep(":", unixPath).c_str(), 1);
+
+        if (execlp("bash", "bash", nullptr) == -1)
+            throw SysError("unable to exec ‘bash’");
+    }
+};
+
+static RegisterCommand r1(make_ref<CmdRun>());
diff --git a/src/nix/show-config.cc b/src/nix/show-config.cc
new file mode 100644
index 000000000000..c628c2898d73
--- /dev/null
+++ b/src/nix/show-config.cc
@@ -0,0 +1,38 @@
+#include "command.hh"
+#include "common-args.hh"
+#include "shared.hh"
+#include "store-api.hh"
+#include "json.hh"
+
+using namespace nix;
+
+struct CmdShowConfig : Command, MixJSON
+{
+    CmdShowConfig()
+    {
+    }
+
+    std::string name() override
+    {
+        return "show-config";
+    }
+
+    std::string description() override
+    {
+        return "show the Nix configuration";
+    }
+
+    void run() override
+    {
+        if (json) {
+            // FIXME: use appropriate JSON types (bool, ints, etc).
+            JSONObject jsonObj(std::cout, true);
+            settings.toJSON(jsonObj);
+        } else {
+            for (auto & s : settings.getSettings())
+                std::cout << s.first + " = " + s.second + "\n";
+        }
+    }
+};
+
+static RegisterCommand r1(make_ref<CmdShowConfig>());
diff --git a/src/nix/sigs.cc b/src/nix/sigs.cc
new file mode 100644
index 000000000000..d8d8c0f53df0
--- /dev/null
+++ b/src/nix/sigs.cc
@@ -0,0 +1,139 @@
+#include "command.hh"
+#include "shared.hh"
+#include "store-api.hh"
+#include "thread-pool.hh"
+
+#include <atomic>
+
+using namespace nix;
+
+struct CmdCopySigs : StorePathsCommand
+{
+    Strings substituterUris;
+
+    CmdCopySigs()
+    {
+        mkFlag('s', "substituter", {"store-uri"}, "use signatures from specified store", 1,
+            [&](Strings ss) { substituterUris.push_back(ss.front()); });
+    }
+
+    std::string name() override
+    {
+        return "copy-sigs";
+    }
+
+    std::string description() override
+    {
+        return "copy path signatures from substituters (like binary caches)";
+    }
+
+    void run(ref<Store> store, Paths storePaths) override
+    {
+        if (substituterUris.empty())
+            throw UsageError("you must specify at least one substituter using ‘-s’");
+
+        // FIXME: factor out commonality with MixVerify.
+        std::vector<ref<Store>> substituters;
+        for (auto & s : substituterUris)
+            substituters.push_back(openStore(s));
+
+        ThreadPool pool;
+
+        std::string doneLabel = "done";
+        std::atomic<size_t> added{0};
+
+        logger->setExpected(doneLabel, storePaths.size());
+
+        auto doPath = [&](const Path & storePath) {
+            Activity act(*logger, lvlInfo, format("getting signatures for ‘%s’") % storePath);
+
+            checkInterrupt();
+
+            auto info = store->queryPathInfo(storePath);
+
+            StringSet newSigs;
+
+            for (auto & store2 : substituters) {
+                try {
+                    auto info2 = store2->queryPathInfo(storePath);
+
+                    /* Don't import signatures that don't match this
+                       binary. */
+                    if (info->narHash != info2->narHash ||
+                        info->narSize != info2->narSize ||
+                        info->references != info2->references)
+                        continue;
+
+                    for (auto & sig : info2->sigs)
+                        if (!info->sigs.count(sig))
+                            newSigs.insert(sig);
+                } catch (InvalidPath &) {
+                }
+            }
+
+            if (!newSigs.empty()) {
+                store->addSignatures(storePath, newSigs);
+                added += newSigs.size();
+            }
+
+            logger->incProgress(doneLabel);
+        };
+
+        for (auto & storePath : storePaths)
+            pool.enqueue(std::bind(doPath, storePath));
+
+        pool.process();
+
+        printInfo(format("imported %d signatures") % added);
+    }
+};
+
+static RegisterCommand r1(make_ref<CmdCopySigs>());
+
+struct CmdSignPaths : StorePathsCommand
+{
+    Path secretKeyFile;
+
+    CmdSignPaths()
+    {
+        mkFlag('k', "key-file", {"file"}, "file containing the secret signing key", &secretKeyFile);
+    }
+
+    std::string name() override
+    {
+        return "sign-paths";
+    }
+
+    std::string description() override
+    {
+        return "sign the specified paths";
+    }
+
+    void run(ref<Store> store, Paths storePaths) override
+    {
+        if (secretKeyFile.empty())
+            throw UsageError("you must specify a secret key file using ‘-k’");
+
+        SecretKey secretKey(readFile(secretKeyFile));
+
+        size_t added{0};
+
+        for (auto & storePath : storePaths) {
+            auto info = store->queryPathInfo(storePath);
+
+            auto info2(*info);
+            info2.sigs.clear();
+            info2.sign(secretKey);
+            assert(!info2.sigs.empty());
+
+            if (!info->sigs.count(*info2.sigs.begin())) {
+                store->addSignatures(storePath, info2.sigs);
+                added++;
+            }
+        }
+
+        printInfo(format("added %d signatures") % added);
+    }
+};
+
+static RegisterCommand r3(make_ref<CmdSignPaths>());
diff --git a/src/nix/verify.cc b/src/nix/verify.cc
new file mode 100644
index 000000000000..2f8d02fa060e
--- /dev/null
+++ b/src/nix/verify.cc
@@ -0,0 +1,172 @@
+#include "command.hh"
+#include "shared.hh"
+#include "store-api.hh"
+#include "sync.hh"
+#include "thread-pool.hh"
+
+#include <atomic>
+
+using namespace nix;
+
+struct CmdVerify : StorePathsCommand
+{
+    bool noContents = false;
+    bool noTrust = false;
+    Strings substituterUris;
+    size_t sigsNeeded;
+
+    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('s', "substituter", {"store-uri"}, "use signatures from specified store", 1,
+            [&](Strings ss) { substituterUris.push_back(ss.front()); });
+        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::string doneLabel("paths checked");
+        std::string untrustedLabel("untrusted");
+        std::string corruptedLabel("corrupted");
+        std::string failedLabel("failed");
+        logger->setExpected(doneLabel, storePaths.size());
+
+        ThreadPool pool;
+
+        auto doPath = [&](const Path & storePath) {
+            try {
+                checkInterrupt();
+
+                Activity act(*logger, lvlInfo, format("checking ‘%s’") % storePath);
+
+                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) {
+                        logger->incProgress(corruptedLabel);
+                        corrupted = 1;
+                        printError(
+                            format("path ‘%s’ was modified! expected hash ‘%s’, got ‘%s’")
+                            % info->path % printHash(info->narHash) % printHash(hash.first));
+                    }
+
+                }
+
+                if (!noTrust) {
+
+                    bool good = false;
+
+                    if (info->ultimate && !sigsNeeded)
+                        good = true;
+
+                    else {
+
+                        StringSet sigsSeen;
+                        size_t actualSigsNeeded = sigsNeeded ? sigsNeeded : 1;
+                        size_t validSigs = 0;
+
+                        auto doSigs = [&](StringSet sigs) {
+                            for (auto sig : sigs) {
+                                if (sigsSeen.count(sig)) continue;
+                                sigsSeen.insert(sig);
+                                if (info->checkSignature(publicKeys, sig))
+                                    validSigs++;
+                            }
+                        };
+
+                        if (info->isContentAddressed(*store)) validSigs = ValidPathInfo::maxSigs;
+
+                        doSigs(info->sigs);
+
+                        for (auto & store2 : substituters) {
+                            if (validSigs >= actualSigsNeeded) break;
+                            try {
+                                auto info2 = store2->queryPathInfo(info->path);
+                                if (info2->isContentAddressed(*store)) validSigs = ValidPathInfo::maxSigs;
+                                doSigs(info2->sigs);
+                            } catch (InvalidPath &) {
+                            } catch (Error & e) {
+                                printError(format(ANSI_RED "error:" ANSI_NORMAL " %s") % e.what());
+                            }
+                        }
+
+                        if (validSigs >= actualSigsNeeded)
+                            good = true;
+                    }
+
+                    if (!good) {
+                        logger->incProgress(untrustedLabel);
+                        untrusted++;
+                        printError(format("path ‘%s’ is untrusted") % info->path);
+                    }
+
+                }
+
+                logger->incProgress(doneLabel);
+                done++;
+
+            } catch (Error & e) {
+                printError(format(ANSI_RED "error:" ANSI_NORMAL " %s") % e.what());
+                logger->incProgress(failedLabel);
+                failed++;
+            }
+        };
+
+        for (auto & storePath : storePaths)
+            pool.enqueue(std::bind(doPath, storePath));
+
+        pool.process();
+
+        printInfo(format("%d paths checked, %d untrusted, %d corrupted, %d failed")
+            % done % untrusted % corrupted % failed);
+
+        throw Exit(
+            (corrupted ? 1 : 0) |
+            (untrusted ? 2 : 0) |
+            (failed ? 4 : 0));
+    }
+};
+
+static RegisterCommand r1(make_ref<CmdVerify>());