diff options
Diffstat (limited to 'third_party/nix/src/nix')
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 000000000000..53641f120e8f --- /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 000000000000..3fe74b7ffdba --- /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 000000000000..7788707eae56 --- /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 000000000000..f7f183ab0ab7 --- /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 000000000000..87e2fbe9d28d --- /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 000000000000..75c85698d11e --- /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 000000000000..d0b4c2b58873 --- /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 000000000000..1d0a996e561c --- /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 000000000000..04c67acb94c6 --- /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 000000000000..72fcbd82717d --- /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 000000000000..4fb262f1a8f0 --- /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 000000000000..7aa26b0dee8c --- /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 000000000000..a0f9fc65b311 --- /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 000000000000..a0fc88da244c --- /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 000000000000..84207d8576ea --- /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 000000000000..1da722babbd3 --- /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 000000000000..08390fd24b9f --- /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 000000000000..ceb53aa77b17 --- /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 000000000000..fcf060d50d08 --- /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 000000000000..4a33486bf8c6 --- /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 000000000000..b926d195aec1 --- /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 000000000000..b3b54f300b4a --- /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 000000000000..5a6bae6a1161 --- /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 000000000000..fd92e481e89a --- /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 000000000000..efe554710ff8 --- /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 000000000000..cc42613d0713 --- /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 000000000000..c7f654d64884 --- /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 000000000000..7de46f2a9c01 --- /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 000000000000..954d619ef3ce --- /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>()); |