diff options
Diffstat (limited to 'src/nix')
-rw-r--r-- | src/nix/build.cc | 46 | ||||
-rw-r--r-- | src/nix/cat.cc | 74 | ||||
-rw-r--r-- | src/nix/command.cc | 93 | ||||
-rw-r--r-- | src/nix/command.hh | 76 | ||||
-rw-r--r-- | src/nix/hash.cc | 140 | ||||
-rw-r--r-- | src/nix/installables.cc | 75 | ||||
-rw-r--r-- | src/nix/installables.hh | 38 | ||||
-rw-r--r-- | src/nix/legacy.cc | 7 | ||||
-rw-r--r-- | src/nix/legacy.hh | 23 | ||||
-rw-r--r-- | src/nix/local.mk | 9 | ||||
-rw-r--r-- | src/nix/ls.cc | 123 | ||||
-rw-r--r-- | src/nix/main.cc | 56 | ||||
-rw-r--r-- | src/nix/progress-bar.cc | 72 | ||||
-rw-r--r-- | src/nix/progress-bar.hh | 49 | ||||
-rw-r--r-- | src/nix/sigs.cc | 181 | ||||
-rw-r--r-- | src/nix/verify.cc | 211 |
16 files changed, 1273 insertions, 0 deletions
diff --git a/src/nix/build.cc b/src/nix/build.cc new file mode 100644 index 000000000000..812464d7582b --- /dev/null +++ b/src/nix/build.cc @@ -0,0 +1,46 @@ +#include "command.hh" +#include "common-args.hh" +#include "installables.hh" +#include "shared.hh" +#include "store-api.hh" + +using namespace nix; + +struct CmdBuild : StoreCommand, MixDryRun, MixInstallables +{ + 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 elems = evalInstallables(store); + + PathSet pathsToBuild; + + for (auto & elem : elems) { + if (elem.isDrv) + pathsToBuild.insert(elem.drvPath); + else + pathsToBuild.insert(elem.outPaths.begin(), elem.outPaths.end()); + } + + printMissing(store, pathsToBuild); + + if (dryRun) return; + + store->buildPaths(pathsToBuild); + } +}; + +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..a89246a937c1 --- /dev/null +++ b/src/nix/command.cc @@ -0,0 +1,93 @@ +#include "command.hh" +#include "store-api.hh" + +namespace nix { + +Commands * RegisterCommand::commands = 0; + +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); +} + +void StoreCommand::run() +{ + run(openStoreAt(storeUri)); +} + +StorePathsCommand::StorePathsCommand() +{ + expectArgs("paths", &storePaths); + mkFlag('r', "recursive", "apply operation to closure of the specified paths", &recursive); +} + +void StorePathsCommand::run(ref<Store> store) +{ + for (auto & storePath : storePaths) + storePath = followLinksToStorePath(storePath); + + if (recursive) { + PathSet closure; + for (auto & storePath : storePaths) + store->computeFSClosure(storePath, closure, false, false); + storePaths = store->topoSortPaths(closure); + } + + run(store, storePaths); +} + +} diff --git a/src/nix/command.hh b/src/nix/command.hh new file mode 100644 index 000000000000..8397244ca177 --- /dev/null +++ b/src/nix/command.hh @@ -0,0 +1,76 @@ +#pragma once + +#include "args.hh" + +namespace nix { + +/* 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; +}; + +class Store; + +/* A command that require a Nix store. */ +struct StoreCommand : virtual Command +{ + std::string storeUri; + StoreCommand(); + void run() override; + virtual void run(ref<Store>) = 0; +}; + +/* A command that operates on zero or more store paths. */ +struct StorePathsCommand : public StoreCommand +{ +private: + + Paths storePaths; + bool recursive = false; + +public: + + StorePathsCommand(); + + 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/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..fb5a515825aa --- /dev/null +++ b/src/nix/installables.cc @@ -0,0 +1,75 @@ +#include "attr-path.hh" +#include "common-opts.hh" +#include "derivations.hh" +#include "eval-inline.hh" +#include "eval.hh" +#include "get-drvs.hh" +#include "installables.hh" +#include "store-api.hh" + +namespace nix { + +UserEnvElems MixInstallables::evalInstallables(ref<Store> store) +{ + UserEnvElems res; + + for (auto & installable : installables) { + + if (std::string(installable, 0, 1) == "/") { + + if (isStorePath(installable)) { + + if (isDerivation(installable)) { + UserEnvElem elem; + // FIXME: handle empty case, drop version + elem.attrPath = {storePathToName(installable)}; + elem.isDrv = true; + elem.drvPath = installable; + res.push_back(elem); + } + + else { + UserEnvElem elem; + // FIXME: handle empty case, drop version + elem.attrPath = {storePathToName(installable)}; + elem.isDrv = false; + elem.outPaths = {installable}; + res.push_back(elem); + } + } + + else + throw UsageError(format("don't know what to do with ‘%1%’") % installable); + } + + else { + + EvalState state({}, store); + + Expr * e = state.parseExprFromFile(resolveExprPath(lookupFileArg(state, file))); + + Value vRoot; + state.eval(e, vRoot); + + std::map<string, string> autoArgs_; + Bindings & autoArgs(*evalAutoArgs(state, autoArgs_)); + + Value & v(*findAlongAttrPath(state, installable, autoArgs, vRoot)); + state.forceValue(v); + + DrvInfos drvs; + getDerivations(state, v, "", autoArgs, drvs, false); + + for (auto & i : drvs) { + UserEnvElem elem; + elem.isDrv = true; + elem.drvPath = i.queryDrvPath(); + res.push_back(elem); + } + } + } + + return res; +} + +} diff --git a/src/nix/installables.hh b/src/nix/installables.hh new file mode 100644 index 000000000000..5eb897d46148 --- /dev/null +++ b/src/nix/installables.hh @@ -0,0 +1,38 @@ +#pragma once + +#include "args.hh" + +namespace nix { + +struct UserEnvElem +{ + Strings attrPath; + + // FIXME: should use boost::variant or so. + bool isDrv; + + // Derivation case: + Path drvPath; + StringSet outputNames; + + // Non-derivation case: + PathSet outPaths; +}; + +typedef std::vector<UserEnvElem> UserEnvElems; + +struct MixInstallables : virtual Args +{ + Strings installables; + Path file = "<nixpkgs>"; + + MixInstallables() + { + mkFlag('f', "file", "file", "evaluate FILE rather than the default", &file); + expectArgs("installables", &installables); + } + + UserEnvElems evalInstallables(ref<Store> store); +}; + +} 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/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..2005ec5f9a6d --- /dev/null +++ b/src/nix/main.cc @@ -0,0 +1,56 @@ +#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" + +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) +{ + 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); + + 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/progress-bar.cc b/src/nix/progress-bar.cc new file mode 100644 index 000000000000..ed7b578e2f49 --- /dev/null +++ b/src/nix/progress-bar.cc @@ -0,0 +1,72 @@ +#include "progress-bar.hh" + +#include <iostream> + +namespace nix { + +ProgressBar::ProgressBar() +{ + _writeToStderr = [&](const unsigned char * buf, size_t count) { + auto state_(state.lock()); + assert(!state_->done); + std::cerr << "\r\e[K" << std::string((const char *) buf, count); + render(*state_); + }; +} + +ProgressBar::~ProgressBar() +{ + done(); +} + +void ProgressBar::updateStatus(const std::string & s) +{ + auto state_(state.lock()); + assert(!state_->done); + state_->status = s; + render(*state_); +} + +void ProgressBar::done() +{ + auto state_(state.lock()); + assert(state_->activities.empty()); + state_->done = true; + std::cerr << "\r\e[K"; + std::cerr.flush(); + _writeToStderr = decltype(_writeToStderr)(); +} + +void ProgressBar::render(State & state_) +{ + std::cerr << '\r' << state_.status; + if (!state_.activities.empty()) { + if (!state_.status.empty()) std::cerr << ' '; + std::cerr << *state_.activities.rbegin(); + } + std::cerr << "\e[K"; + std::cerr.flush(); +} + + +ProgressBar::Activity ProgressBar::startActivity(const FormatOrString & fs) +{ + return Activity(*this, fs); +} + +ProgressBar::Activity::Activity(ProgressBar & pb, const FormatOrString & fs) + : pb(pb) +{ + auto state_(pb.state.lock()); + state_->activities.push_back(fs.s); + it = state_->activities.end(); --it; + pb.render(*state_); +} + +ProgressBar::Activity::~Activity() +{ + auto state_(pb.state.lock()); + state_->activities.erase(it); +} + +} diff --git a/src/nix/progress-bar.hh b/src/nix/progress-bar.hh new file mode 100644 index 000000000000..2dda24346c90 --- /dev/null +++ b/src/nix/progress-bar.hh @@ -0,0 +1,49 @@ +#pragma once + +#include "sync.hh" +#include "util.hh" + +namespace nix { + +class ProgressBar +{ +private: + struct State + { + std::string status; + bool done = false; + std::list<std::string> activities; + }; + + Sync<State> state; + +public: + + ProgressBar(); + + ~ProgressBar(); + + void updateStatus(const std::string & s); + + void done(); + + class Activity + { + friend class ProgressBar; + private: + ProgressBar & pb; + std::list<std::string>::iterator it; + Activity(ProgressBar & pb, const FormatOrString & fs); + public: + ~Activity(); + }; + + Activity startActivity(const FormatOrString & fs); + +private: + + void render(State & state_); + +}; + +} diff --git a/src/nix/sigs.cc b/src/nix/sigs.cc new file mode 100644 index 000000000000..bcc46c3e7d4f --- /dev/null +++ b/src/nix/sigs.cc @@ -0,0 +1,181 @@ +#include "affinity.hh" // FIXME +#include "command.hh" +#include "progress-bar.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 + { + restoreAffinity(); // FIXME + + 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(openStoreAt(s)); + + ProgressBar progressBar; + + ThreadPool pool; + + std::atomic<size_t> done{0}; + std::atomic<size_t> added{0}; + + auto showProgress = [&]() { + return (format("[%d/%d done]") % done % storePaths.size()).str(); + }; + + progressBar.updateStatus(showProgress()); + + auto doPath = [&](const Path & storePath) { + auto activity(progressBar.startActivity(format("getting signatures for ‘%s’") % storePath)); + + checkInterrupt(); + + auto info = store->queryPathInfo(storePath); + + StringSet newSigs; + + for (auto & store2 : substituters) { + if (!store2->isValidPath(storePath)) continue; + 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); + } + + if (!newSigs.empty()) { + store->addSignatures(storePath, newSigs); + added += newSigs.size(); + } + + done++; + progressBar.updateStatus(showProgress()); + }; + + for (auto & storePath : storePaths) + pool.enqueue(std::bind(doPath, storePath)); + + pool.process(); + + progressBar.done(); + + printMsg(lvlInfo, format("imported %d signatures") % added); + } +}; + +static RegisterCommand r1(make_ref<CmdCopySigs>()); + +struct CmdQueryPathSigs : StorePathsCommand +{ + CmdQueryPathSigs() + { + } + + std::string name() override + { + return "query-path-sigs"; + } + + std::string description() override + { + return "print store path signatures"; + } + + void run(ref<Store> store, Paths storePaths) override + { + for (auto & storePath : storePaths) { + auto info = store->queryPathInfo(storePath); + std::cout << storePath << " "; + if (info.ultimate) std::cout << "ultimate "; + for (auto & sig : info.sigs) + std::cout << sig << " "; + std::cout << "\n"; + } + } +}; + +static RegisterCommand r2(make_ref<CmdQueryPathSigs>()); + +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++; + } + } + + printMsg(lvlInfo, 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..9214d3b651d1 --- /dev/null +++ b/src/nix/verify.cc @@ -0,0 +1,211 @@ +#include "affinity.hh" // FIXME +#include "command.hh" +#include "progress-bar.hh" +#include "shared.hh" +#include "store-api.hh" +#include "sync.hh" +#include "thread-pool.hh" + +#include <atomic> + +using namespace nix; + +struct MixVerify : virtual Args +{ + bool noContents = false; + bool noTrust = false; + Strings substituterUris; + size_t sigsNeeded; + + MixVerify() + { + 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); + } + + void verifyPaths(ref<Store> store, const Paths & storePaths) + { + restoreAffinity(); // FIXME + + std::vector<ref<Store>> substituters; + for (auto & s : substituterUris) + substituters.push_back(openStoreAt(s)); + + auto publicKeys = getDefaultPublicKeys(); + + std::atomic<size_t> untrusted{0}; + std::atomic<size_t> corrupted{0}; + std::atomic<size_t> done{0}; + std::atomic<size_t> failed{0}; + + ProgressBar progressBar; + + auto showProgress = [&](bool final) { + std::string s; + if (final) + s = (format("checked %d paths") % storePaths.size()).str(); + else + s = (format("[%d/%d checked") % done % storePaths.size()).str(); + if (corrupted > 0) + s += (format(", %d corrupted") % corrupted).str(); + if (untrusted > 0) + s += (format(", %d untrusted") % untrusted).str(); + if (failed > 0) + s += (format(", %d failed") % failed).str(); + if (!final) s += "]"; + return s; + }; + + progressBar.updateStatus(showProgress(false)); + + ThreadPool pool; + + auto doPath = [&](const Path & storePath) { + try { + checkInterrupt(); + + auto activity(progressBar.startActivity(format("checking ‘%s’") % storePath)); + + auto info = store->queryPathInfo(storePath); + + if (!noContents) { + + HashSink sink(info.narHash.type); + store->narFromPath(storePath, sink); + + auto hash = sink.finish(); + + if (hash.first != info.narHash) { + corrupted = 1; + printMsg(lvlError, + format("path ‘%s’ was modified! expected hash ‘%s’, got ‘%s’") + % storePath % 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++; + } + }; + + doSigs(info.sigs); + + for (auto & store2 : substituters) { + if (validSigs >= actualSigsNeeded) break; + try { + if (!store2->isValidPath(storePath)) continue; + doSigs(store2->queryPathInfo(storePath).sigs); + } catch (Error & e) { + printMsg(lvlError, format(ANSI_RED "error:" ANSI_NORMAL " %s") % e.what()); + } + } + + if (validSigs >= actualSigsNeeded) + good = true; + } + + if (!good) { + untrusted++; + printMsg(lvlError, format("path ‘%s’ is untrusted") % storePath); + } + + } + + done++; + + progressBar.updateStatus(showProgress(false)); + + } catch (Error & e) { + printMsg(lvlError, format(ANSI_RED "error:" ANSI_NORMAL " %s") % e.what()); + failed++; + } + }; + + for (auto & storePath : storePaths) + pool.enqueue(std::bind(doPath, storePath)); + + pool.process(); + + progressBar.done(); + + printMsg(lvlInfo, showProgress(true)); + + throw Exit( + (corrupted ? 1 : 0) | + (untrusted ? 2 : 0) | + (failed ? 4 : 0)); + } +}; + +struct CmdVerifyPaths : StorePathsCommand, MixVerify +{ + CmdVerifyPaths() + { + } + + std::string name() override + { + return "verify-paths"; + } + + std::string description() override + { + return "verify the integrity of store paths"; + } + + void run(ref<Store> store, Paths storePaths) override + { + verifyPaths(store, storePaths); + } +}; + +static RegisterCommand r1(make_ref<CmdVerifyPaths>()); + +struct CmdVerifyStore : StoreCommand, MixVerify +{ + CmdVerifyStore() + { + } + + std::string name() override + { + return "verify-store"; + } + + std::string description() override + { + return "verify the integrity of all paths in the Nix store"; + } + + void run(ref<Store> store) override + { + // FIXME: use store->verifyStore()? + + PathSet validPaths = store->queryAllValidPaths(); + + verifyPaths(store, Paths(validPaths.begin(), validPaths.end())); + } +}; + +static RegisterCommand r2(make_ref<CmdVerifyStore>()); |