diff --git a/src/libcmd/include/nix/cmd/command.hh b/src/libcmd/include/nix/cmd/command.hh index 0455a1d3c85..bd5786fcdea 100644 --- a/src/libcmd/include/nix/cmd/command.hh +++ b/src/libcmd/include/nix/cmd/command.hh @@ -352,6 +352,20 @@ struct MixEnvironment : virtual Args void setEnviron(); }; +struct MixNoCheckSigs : virtual Args +{ + CheckSigsFlag checkSigs = CheckSigs; + + MixNoCheckSigs() + { + addFlag({ + .longName = "no-check-sigs", + .description = "Do not require that paths are signed by trusted keys.", + .handler = {&checkSigs, NoCheckSigs}, + }); + } +}; + void completeFlakeInputAttrPath( AddCompletions & completions, ref evalState, diff --git a/src/libmain/shared.cc b/src/libmain/shared.cc index 6d84e0d216d..7097eef2602 100644 --- a/src/libmain/shared.cc +++ b/src/libmain/shared.cc @@ -334,7 +334,7 @@ int handleExceptions(const std::string & programName, std::function fun) return e.status; } catch (UsageError & e) { logError(e.info()); - printError("Try '%1% --help' for more information.", programName); + printError("\nTry '%1% --help' for more information.", programName); return 1; } catch (BaseError & e) { logError(e.info()); diff --git a/src/libstore/common-protocol.cc b/src/libstore/common-protocol.cc index d4f3efc9b5c..b289d4b5403 100644 --- a/src/libstore/common-protocol.cc +++ b/src/libstore/common-protocol.cc @@ -26,13 +26,13 @@ void CommonProto::Serialise::write( StorePath CommonProto::Serialise::read(const StoreDirConfig & store, CommonProto::ReadConn conn) { - return store.parseStorePath(readString(conn.from)); + return conn.shortStorePaths ? StorePath(readString(conn.from)) : store.parseStorePath(readString(conn.from)); } void CommonProto::Serialise::write( const StoreDirConfig & store, CommonProto::WriteConn conn, const StorePath & storePath) { - conn.to << store.printStorePath(storePath); + conn.to << (conn.shortStorePaths ? storePath.to_string() : store.printStorePath(storePath)); } ContentAddress CommonProto::Serialise::read(const StoreDirConfig & store, CommonProto::ReadConn conn) @@ -73,13 +73,15 @@ std::optional CommonProto::Serialise>::read(const StoreDirConfig & store, CommonProto::ReadConn conn) { auto s = readString(conn.from); - return s == "" ? std::optional{} : store.parseStorePath(s); + return s == "" ? std::optional{} : conn.shortStorePaths ? StorePath(s) : store.parseStorePath(s); } void CommonProto::Serialise>::write( const StoreDirConfig & store, CommonProto::WriteConn conn, const std::optional & storePathOpt) { - conn.to << (storePathOpt ? store.printStorePath(*storePathOpt) : ""); + conn.to + << (storePathOpt ? (conn.shortStorePaths ? storePathOpt->to_string() : store.printStorePath(*storePathOpt)) + : ""); } std::optional diff --git a/src/libstore/export-import.cc b/src/libstore/export-import.cc index a343b5837db..2ea46be4cc3 100644 --- a/src/libstore/export-import.cc +++ b/src/libstore/export-import.cc @@ -4,92 +4,152 @@ #include "nix/util/archive.hh" #include "nix/store/common-protocol.hh" #include "nix/store/common-protocol-impl.hh" - -#include +#include "nix/store/worker-protocol.hh" namespace nix { -static void exportPath(Store & store, const StorePath & path, Sink & sink) -{ - auto info = store.queryPathInfo(path); - - HashSink hashSink(HashAlgorithm::SHA256); - TeeSink teeSink(sink, hashSink); - - store.narFromPath(path, teeSink); - - /* Refuse to export paths that have changed. This prevents - filesystem corruption from spreading to other machines. - Don't complain if the stored hash is zero (unknown). */ - Hash hash = hashSink.currentHash().hash; - if (hash != info->narHash && info->narHash != Hash(info->narHash.algo)) - throw Error( - "hash of path '%s' has changed from '%s' to '%s'!", - store.printStorePath(path), - info->narHash.to_string(HashFormat::Nix32, true), - hash.to_string(HashFormat::Nix32, true)); - - teeSink << exportMagic << store.printStorePath(path); - CommonProto::write(store, CommonProto::WriteConn{.to = teeSink}, info->references); - teeSink << (info->deriver ? store.printStorePath(*info->deriver) : "") << 0; -} +static const uint32_t exportMagicV1 = 0x4558494e; +static const uint64_t exportMagicV2 = 0x324f4952414e; // = 'NARIO2' -void exportPaths(Store & store, const StorePathSet & paths, Sink & sink) +void exportPaths(Store & store, const StorePathSet & paths, Sink & sink, unsigned int version) { auto sorted = store.topoSortPaths(paths); std::reverse(sorted.begin(), sorted.end()); - for (auto & path : sorted) { - sink << 1; - exportPath(store, path, sink); + auto dumpNar = [&](const ValidPathInfo & info) { + HashSink hashSink(HashAlgorithm::SHA256); + TeeSink teeSink(sink, hashSink); + + store.narFromPath(info.path, teeSink); + + /* Refuse to export paths that have changed. This prevents + filesystem corruption from spreading to other machines. + Don't complain if the stored hash is zero (unknown). */ + Hash hash = hashSink.currentHash().hash; + if (hash != info.narHash && info.narHash != Hash(info.narHash.algo)) + throw Error( + "hash of path '%s' has changed from '%s' to '%s'!", + store.printStorePath(info.path), + info.narHash.to_string(HashFormat::Nix32, true), + hash.to_string(HashFormat::Nix32, true)); + }; + + switch (version) { + + case 1: + for (auto & path : sorted) { + sink << 1; + auto info = store.queryPathInfo(path); + dumpNar(*info); + sink << exportMagicV1 << store.printStorePath(path); + CommonProto::write(store, CommonProto::WriteConn{.to = sink}, info->references); + sink << (info->deriver ? store.printStorePath(*info->deriver) : "") << 0; + } + sink << 0; + break; + + case 2: + sink << exportMagicV2; + + for (auto & path : sorted) { + Activity act(*logger, lvlTalkative, actUnknown, fmt("exporting path '%s'", store.printStorePath(path))); + sink << 1; + auto info = store.queryPathInfo(path); + // FIXME: move to CommonProto? + WorkerProto::Serialise::write( + store, WorkerProto::WriteConn{.to = sink, .version = 16, .shortStorePaths = true}, *info); + dumpNar(*info); + } + + sink << 0; + break; + + default: + throw Error("unsupported nario version %d", version); } - - sink << 0; } StorePaths importPaths(Store & store, Source & source, CheckSigsFlag checkSigs) { StorePaths res; - while (true) { - auto n = readNum(source); - if (n == 0) - break; - if (n != 1) - throw Error("input doesn't look like something created by 'nix-store --export'"); - - /* Extract the NAR from the source. */ - StringSink saved; - TeeSource tee{source, saved}; - NullFileSystemObjectSink ether; - parseDump(ether, tee); - - uint32_t magic = readInt(source); - if (magic != exportMagic) - throw Error("Nix archive cannot be imported; wrong format"); - - auto path = store.parseStorePath(readString(source)); - - // Activity act(*logger, lvlInfo, "importing path '%s'", info.path); - - auto references = CommonProto::Serialise::read(store, CommonProto::ReadConn{.from = source}); - auto deriver = readString(source); - auto narHash = hashString(HashAlgorithm::SHA256, saved.s); - - ValidPathInfo info{path, narHash}; - if (deriver != "") - info.deriver = store.parseStorePath(deriver); - info.references = references; - info.narSize = saved.s.size(); - - // Ignore optional legacy signature. - if (readInt(source) == 1) - readString(source); - - // Can't use underlying source, which would have been exhausted - auto source = StringSource(saved.s); - store.addToStore(info, source, NoRepair, checkSigs); - - res.push_back(info.path); + + auto version = readNum(source); + + /* Note: nario version 1 lacks an explicit header. The first + integer denotes whether a store path follows or not. So look + for 0 or 1. */ + switch (version) { + + case 0: + /* Empty version 1 nario, nothing to do. */ + break; + + case 1: + /* Non-empty version 1 nario. */ + while (true) { + /* Extract the NAR from the source. */ + StringSink saved; + TeeSource tee{source, saved}; + NullFileSystemObjectSink ether; + parseDump(ether, tee); + + uint32_t magic = readInt(source); + if (magic != exportMagicV1) + throw Error("nario cannot be imported; wrong format"); + + auto path = store.parseStorePath(readString(source)); + + auto references = CommonProto::Serialise::read(store, CommonProto::ReadConn{.from = source}); + auto deriver = readString(source); + auto narHash = hashString(HashAlgorithm::SHA256, saved.s); + + ValidPathInfo info{path, narHash}; + if (deriver != "") + info.deriver = store.parseStorePath(deriver); + info.references = references; + info.narSize = saved.s.size(); + + // Ignore optional legacy signature. + if (readInt(source) == 1) + readString(source); + + // Can't use underlying source, which would have been exhausted. + auto source2 = StringSource(saved.s); + store.addToStore(info, source2, NoRepair, checkSigs); + + res.push_back(info.path); + + auto n = readNum(source); + if (n == 0) + break; + if (n != 1) + throw Error("input doesn't look like a nario"); + } + break; + + case exportMagicV2: + while (true) { + auto n = readNum(source); + if (n == 0) + break; + if (n != 1) + throw Error("input doesn't look like a nario"); + + auto info = WorkerProto::Serialise::read( + store, WorkerProto::ReadConn{.from = source, .version = 16, .shortStorePaths = true}); + + Activity act( + *logger, lvlTalkative, actUnknown, fmt("importing path '%s'", store.printStorePath(info.path))); + + store.addToStore(info, source, NoRepair, checkSigs); + + res.push_back(info.path); + } + + break; + + default: + throw Error("input doesn't look like a nario"); } return res; diff --git a/src/libstore/include/nix/store/common-protocol.hh b/src/libstore/include/nix/store/common-protocol.hh index c1d22fa6c54..6139afc5d2e 100644 --- a/src/libstore/include/nix/store/common-protocol.hh +++ b/src/libstore/include/nix/store/common-protocol.hh @@ -30,6 +30,7 @@ struct CommonProto struct ReadConn { Source & from; + bool shortStorePaths = false; }; /** @@ -39,6 +40,7 @@ struct CommonProto struct WriteConn { Sink & to; + bool shortStorePaths = false; }; template diff --git a/src/libstore/include/nix/store/export-import.hh b/src/libstore/include/nix/store/export-import.hh index 15092202f1f..4ea696f992f 100644 --- a/src/libstore/include/nix/store/export-import.hh +++ b/src/libstore/include/nix/store/export-import.hh @@ -4,16 +4,11 @@ namespace nix { -/** - * Magic header of exportPath() output (obsolete). - */ -const uint32_t exportMagic = 0x4558494e; - /** * Export multiple paths in the format expected by `nix-store * --import`. The paths will be sorted topologically. */ -void exportPaths(Store & store, const StorePathSet & paths, Sink & sink); +void exportPaths(Store & store, const StorePathSet & paths, Sink & sink, unsigned int version); /** * Import a sequence of NAR dumps created by `exportPaths()` into the diff --git a/src/libstore/include/nix/store/worker-protocol-impl.hh b/src/libstore/include/nix/store/worker-protocol-impl.hh index 26f6b9d44e4..c36145d620d 100644 --- a/src/libstore/include/nix/store/worker-protocol-impl.hh +++ b/src/libstore/include/nix/store/worker-protocol-impl.hh @@ -45,12 +45,14 @@ struct WorkerProto::Serialise { static T read(const StoreDirConfig & store, WorkerProto::ReadConn conn) { - return CommonProto::Serialise::read(store, CommonProto::ReadConn{.from = conn.from}); + return CommonProto::Serialise::read( + store, CommonProto::ReadConn{.from = conn.from, .shortStorePaths = conn.shortStorePaths}); } static void write(const StoreDirConfig & store, WorkerProto::WriteConn conn, const T & t) { - CommonProto::Serialise::write(store, CommonProto::WriteConn{.to = conn.to}, t); + CommonProto::Serialise::write( + store, CommonProto::WriteConn{.to = conn.to, .shortStorePaths = conn.shortStorePaths}, t); } }; diff --git a/src/libstore/include/nix/store/worker-protocol.hh b/src/libstore/include/nix/store/worker-protocol.hh index 6ae5fdcbc29..79d59144cdf 100644 --- a/src/libstore/include/nix/store/worker-protocol.hh +++ b/src/libstore/include/nix/store/worker-protocol.hh @@ -66,6 +66,7 @@ struct WorkerProto { Source & from; Version version; + bool shortStorePaths = false; }; /** @@ -76,6 +77,7 @@ struct WorkerProto { Sink & to; Version version; + bool shortStorePaths = false; }; /** diff --git a/src/libstore/worker-protocol.cc b/src/libstore/worker-protocol.cc index 1bbff64a25b..90de0c04df8 100644 --- a/src/libstore/worker-protocol.cc +++ b/src/libstore/worker-protocol.cc @@ -219,11 +219,10 @@ void WorkerProto::Serialise::write( UnkeyedValidPathInfo WorkerProto::Serialise::read(const StoreDirConfig & store, ReadConn conn) { - auto deriver = readString(conn.from); + auto deriver = WorkerProto::Serialise>::read(store, conn); auto narHash = Hash::parseAny(readString(conn.from), HashAlgorithm::SHA256); UnkeyedValidPathInfo info(narHash); - if (deriver != "") - info.deriver = store.parseStorePath(deriver); + info.deriver = std::move(deriver); info.references = WorkerProto::Serialise::read(store, conn); conn.from >> info.registrationTime >> info.narSize; if (GET_PROTOCOL_MINOR(conn.version) >= 16) { @@ -237,8 +236,8 @@ UnkeyedValidPathInfo WorkerProto::Serialise::read(const St void WorkerProto::Serialise::write( const StoreDirConfig & store, WriteConn conn, const UnkeyedValidPathInfo & pathInfo) { - conn.to << (pathInfo.deriver ? store.printStorePath(*pathInfo.deriver) : "") - << pathInfo.narHash.to_string(HashFormat::Base16, false); + WorkerProto::write(store, conn, pathInfo.deriver); + conn.to << pathInfo.narHash.to_string(HashFormat::Base16, false); WorkerProto::write(store, conn, pathInfo.references); conn.to << pathInfo.registrationTime << pathInfo.narSize; if (GET_PROTOCOL_MINOR(conn.version) >= 16) { diff --git a/src/libutil/args.cc b/src/libutil/args.cc index 2506c5eb3fe..5faf9dd43ea 100644 --- a/src/libutil/args.cc +++ b/src/libutil/args.cc @@ -318,6 +318,7 @@ void RootArgs::parseCmdline(const Strings & _cmdline, bool allowShebang) } catch (SystemError &) { } } + for (auto pos = cmdline.begin(); pos != cmdline.end();) { auto arg = *pos; @@ -354,6 +355,9 @@ void RootArgs::parseCmdline(const Strings & _cmdline, bool allowShebang) processArgs(pendingArgs, true); + if (!completions) + checkArgs(); + initialFlagsProcessed(); /* Now that we are done parsing, make sure that any experimental @@ -384,7 +388,7 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end) auto & rootArgs = getRoot(); - auto process = [&](const std::string & name, const Flag & flag) -> bool { + auto process = [&](const std::string & name, Flag & flag) -> bool { ++pos; if (auto & f = flag.experimentalFeature) @@ -413,6 +417,7 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end) } if (!anyCompleted) flag.handler.fun(std::move(args)); + flag.timesUsed++; return true; }; @@ -504,6 +509,14 @@ bool Args::processArgs(const Strings & args, bool finish) return res; } +void Args::checkArgs() +{ + for (auto & [name, flag] : longFlags) { + if (flag->required && flag->timesUsed == 0) + throw UsageError("required argument '%s' is missing", "--" + name); + } +} + nlohmann::json Args::toJSON() { auto flags = nlohmann::json::object(); @@ -643,6 +656,13 @@ bool MultiCommand::processArgs(const Strings & args, bool finish) return Args::processArgs(args, finish); } +void MultiCommand::checkArgs() +{ + Args::checkArgs(); + if (command) + command->second->checkArgs(); +} + nlohmann::json MultiCommand::toJSON() { auto cmds = nlohmann::json::object(); diff --git a/src/libutil/include/nix/util/args.hh b/src/libutil/include/nix/util/args.hh index 443db445f2a..99f6e23e8e9 100644 --- a/src/libutil/include/nix/util/args.hh +++ b/src/libutil/include/nix/util/args.hh @@ -202,8 +202,12 @@ public: Strings labels; Handler handler; CompleterClosure completer; + bool required = false; std::optional experimentalFeature; + + // FIXME: this should be private, but that breaks designated initializers. + size_t timesUsed = 0; }; protected: @@ -283,6 +287,8 @@ protected: StringSet hiddenCategories; + virtual void checkArgs(); + /** * Called after all command line flags before the first non-flag * argument (if any) have been processed. @@ -428,6 +434,8 @@ public: protected: std::string commandName = ""; bool aliasUsed = false; + + void checkArgs() override; }; Strings argvToStrings(int argc, char ** argv); diff --git a/src/nix/copy.cc b/src/nix/copy.cc index 62e8b64f513..706edc6c9c5 100644 --- a/src/nix/copy.cc +++ b/src/nix/copy.cc @@ -5,10 +5,9 @@ using namespace nix; -struct CmdCopy : virtual CopyCommand, virtual BuiltPathsCommand, MixProfile +struct CmdCopy : virtual CopyCommand, virtual BuiltPathsCommand, MixProfile, MixNoCheckSigs { std::optional outLink; - CheckSigsFlag checkSigs = CheckSigs; SubstituteFlag substitute = NoSubstitute; @@ -24,13 +23,6 @@ struct CmdCopy : virtual CopyCommand, virtual BuiltPathsCommand, MixProfile .handler = {&outLink}, .completer = completePath, }); - - addFlag({ - .longName = "no-check-sigs", - .description = "Do not require that paths are signed by trusted keys.", - .handler = {&checkSigs, NoCheckSigs}, - }); - addFlag({ .longName = "substitute-on-destination", .shortName = 's', diff --git a/src/nix/flake.cc b/src/nix/flake.cc index c8837f04006..40a126ea676 100644 --- a/src/nix/flake.cc +++ b/src/nix/flake.cc @@ -1058,12 +1058,10 @@ struct CmdFlakeClone : FlakeCommand } }; -struct CmdFlakeArchive : FlakeCommand, MixJSON, MixDryRun +struct CmdFlakeArchive : FlakeCommand, MixJSON, MixDryRun, MixNoCheckSigs { std::string dstUri; - CheckSigsFlag checkSigs = CheckSigs; - SubstituteFlag substitute = NoSubstitute; CmdFlakeArchive() @@ -1074,11 +1072,6 @@ struct CmdFlakeArchive : FlakeCommand, MixJSON, MixDryRun .labels = {"store-uri"}, .handler = {&dstUri}, }); - addFlag({ - .longName = "no-check-sigs", - .description = "Do not require that paths are signed by trusted keys.", - .handler = {&checkSigs, NoCheckSigs}, - }); } std::string description() override diff --git a/src/nix/meson.build b/src/nix/meson.build index 504938b91d3..c0ef03d2613 100644 --- a/src/nix/meson.build +++ b/src/nix/meson.build @@ -88,6 +88,7 @@ nix_sources = [ config_priv_h ] + files( 'make-content-addressed.cc', 'man-pages.cc', 'nar.cc', + 'nario.cc', 'optimise-store.cc', 'path-from-hash-part.cc', 'path-info.cc', diff --git a/src/nix/nario-export.md b/src/nix/nario-export.md new file mode 100644 index 00000000000..2480733c1ca --- /dev/null +++ b/src/nix/nario-export.md @@ -0,0 +1,29 @@ +R""( + +# Examples + +* Export the closure of the build of `nixpkgs#hello`: + + ```console + # nix nario export --format 2 -r nixpkgs#hello > dump.nario + ``` + + It can be imported into another store: + + ```console + # nix nario import --no-check-sigs < dump.nario + ``` + +# Description + +This command prints to standard output a serialization of the specified store paths in `nario` format. This serialization can be imported into another store using `nix nario import`. + +References of a path are not exported by default; use `-r` to export a complete closure. +Paths are exported in topologically sorted order (i.e. if path `X` refers to `Y`, then `Y` appears before `X`). +You must specify the desired `nario` version. Currently the following versions are supported: + +* `1`: This version is compatible with the legacy `nix-store --export` and `nix-store --import` commands. It should be avoided because it is not memory-efficient on import. It does not support signatures, so you have to use `--no-check-sigs` on import. + +* `2`: The latest version. Recommended. + +)"" diff --git a/src/nix/nario-import.md b/src/nix/nario-import.md new file mode 100644 index 00000000000..9cba60c6220 --- /dev/null +++ b/src/nix/nario-import.md @@ -0,0 +1,15 @@ +R""( + +# Examples + +* Import store paths from the file named `dump`: + + ```console + # nix nario import < dump.nario + ``` + +# Description + +This command reads from standard input a serialization of store paths produced by `nix nario export` and adds them to the Nix store. + +)"" diff --git a/src/nix/nario-list.md b/src/nix/nario-list.md new file mode 100644 index 00000000000..80c1f10d7a0 --- /dev/null +++ b/src/nix/nario-list.md @@ -0,0 +1,18 @@ +R""( + +# Examples + +* List the contents of a nario file: + + ```console + # nix nario list < dump + /nix/store/4y1jj6cwvslmfh1bzkhbvhx77az6yf00-xgcc-14.2.1.20250322-libgcc: 201856 bytes + /nix/store/d8hnbm5hvbg2vza50garppb63y724i94-libunistring-1.3: 2070240 bytes + … + ``` + +# Description + +This command lists the contents of a nario file read from standard input. + +)"" diff --git a/src/nix/nario.cc b/src/nix/nario.cc new file mode 100644 index 00000000000..0439c5c6809 --- /dev/null +++ b/src/nix/nario.cc @@ -0,0 +1,198 @@ +#include "nix/cmd/command.hh" +#include "nix/main/shared.hh" +#include "nix/store/store-api.hh" +#include "nix/store/export-import.hh" +#include "nix/util/callback.hh" +#include "nix/util/fs-sink.hh" +#include "nix/util/archive.hh" + +using namespace nix; + +struct CmdNario : NixMultiCommand +{ + CmdNario() + : NixMultiCommand("nario", RegisterCommand::getCommandsFor({"nario"})) + { + } + + std::string description() override + { + return "operations for manipulating nario files"; + } + + Category category() override + { + return catUtility; + } +}; + +static auto rCmdNario = registerCommand("nario"); + +struct CmdNarioExport : StorePathsCommand +{ + unsigned int version = 0; + + CmdNarioExport() + { + addFlag({ + .longName = "format", + .description = "Version of the nario format to use. Must be `1` or `2`.", + .labels = {"nario-format"}, + .handler = {&version}, + .required = true, + }); + } + + std::string description() override + { + return "serialize store paths to standard output in nario format"; + } + + std::string doc() override + { + return +#include "nario-export.md" + ; + } + + void run(ref store, StorePaths && storePaths) override + { + auto fd = getStandardOutput(); + if (isatty(fd)) + throw UsageError("refusing to write nario to standard output"); + FdSink sink(std::move(fd)); + exportPaths(*store, StorePathSet(storePaths.begin(), storePaths.end()), sink, version); + } +}; + +static auto rCmdNarioExport = registerCommand2({"nario", "export"}); + +static FdSource getNarioSource() +{ + auto fd = getStandardInput(); + if (isatty(fd)) + throw UsageError("refusing to read nario from standard input"); + return FdSource(std::move(fd)); +} + +struct CmdNarioImport : StoreCommand, MixNoCheckSigs +{ + std::string description() override + { + return "import store paths from a nario file on standard input"; + } + + std::string doc() override + { + return +#include "nario-import.md" + ; + } + + void run(ref store) override + { + auto source{getNarioSource()}; + importPaths(*store, source, checkSigs); + } +}; + +static auto rCmdNarioImport = registerCommand2({"nario", "import"}); + +struct CmdNarioList : Command +{ + std::string description() override + { + return "list the contents of a nario file"; + } + + std::string doc() override + { + return +#include "nario-list.md" + ; + } + + void run() override + { + struct Config : StoreConfig + { + Config(const Params & params) + : StoreConfig(params) + { + } + + ref openStore() const override + { + abort(); + } + }; + + struct ListingStore : Store + { + ListingStore(ref config) + : Store{*config} + { + } + + void queryPathInfoUncached( + const StorePath & path, Callback> callback) noexcept override + { + callback(nullptr); + } + + std::optional isTrustedClient() override + { + return Trusted; + } + + std::optional queryPathFromHashPart(const std::string & hashPart) override + { + return std::nullopt; + } + + void + addToStore(const ValidPathInfo & info, Source & source, RepairFlag repair, CheckSigsFlag checkSigs) override + { + logger->cout(fmt("%s: %d bytes", printStorePath(info.path), info.narSize)); + // Discard the NAR. + NullFileSystemObjectSink parseSink; + parseDump(parseSink, source); + } + + StorePath addToStoreFromDump( + Source & dump, + std::string_view name, + FileSerialisationMethod dumpMethod, + ContentAddressMethod hashMethod, + HashAlgorithm hashAlgo, + const StorePathSet & references, + RepairFlag repair) override + { + unsupported("addToStoreFromDump"); + } + + void narFromPath(const StorePath & path, Sink & sink) override + { + unsupported("narFromPath"); + } + + void queryRealisationUncached( + const DrvOutput &, Callback> callback) noexcept override + { + callback(nullptr); + } + + ref getFSAccessor(bool requireValidPath) override + { + return makeEmptySourceAccessor(); + } + }; + + auto source{getNarioSource()}; + auto config = make_ref(StoreConfig::Params()); + ListingStore lister(config); + importPaths(lister, source, NoCheckSigs); + } +}; + +static auto rCmdNarioList = registerCommand2({"nario", "list"}); diff --git a/src/nix/nix-store/nix-store.cc b/src/nix/nix-store/nix-store.cc index 5f85e06f0b2..9b149cebee8 100644 --- a/src/nix/nix-store/nix-store.cc +++ b/src/nix/nix-store/nix-store.cc @@ -775,7 +775,7 @@ static void opExport(Strings opFlags, Strings opArgs) paths.insert(store->followLinksToStorePath(i)); FdSink sink(getStandardOutput()); - exportPaths(*store, paths, sink); + exportPaths(*store, paths, sink, 1); sink.flush(); } diff --git a/src/nix/sigs.cc b/src/nix/sigs.cc index 422a4998ce4..e82f0d284b9 100644 --- a/src/nix/sigs.cc +++ b/src/nix/sigs.cc @@ -112,6 +112,7 @@ struct CmdSign : StorePathsCommand .labels = {"file"}, .handler = {&secretKeyFile}, .completer = completePath, + .required = true, }); } @@ -122,9 +123,6 @@ struct CmdSign : StorePathsCommand void run(ref store, StorePaths && storePaths) override { - if (secretKeyFile.empty()) - throw UsageError("you must specify a secret key file using '-k'"); - SecretKey secretKey(readFile(secretKeyFile)); LocalSigner signer(std::move(secretKey)); @@ -152,7 +150,7 @@ static auto rCmdSign = registerCommand2({"store", "sign"}); struct CmdKeyGenerateSecret : Command { - std::optional keyName; + std::string keyName; CmdKeyGenerateSecret() { @@ -161,6 +159,7 @@ struct CmdKeyGenerateSecret : Command .description = "Identifier of the key (e.g. `cache.example.org-1`).", .labels = {"name"}, .handler = {&keyName}, + .required = true, }); } @@ -178,11 +177,8 @@ struct CmdKeyGenerateSecret : Command void run() override { - if (!keyName) - throw UsageError("required argument '--key-name' is missing"); - logger->stop(); - writeFull(getStandardOutput(), SecretKey::generate(*keyName).to_string()); + writeFull(getStandardOutput(), SecretKey::generate(keyName).to_string()); } }; diff --git a/src/perl/lib/Nix/Store.xs b/src/perl/lib/Nix/Store.xs index 7aa918ba0c6..0d5a9f57172 100644 --- a/src/perl/lib/Nix/Store.xs +++ b/src/perl/lib/Nix/Store.xs @@ -234,7 +234,7 @@ StoreWrapper::exportPaths(int fd, ...) StorePathSet paths; for (int n = 2; n < items; ++n) paths.insert(THIS->store->parseStorePath(SvPV_nolen(ST(n)))); FdSink sink(fd); - exportPaths(*THIS->store, paths, sink); + exportPaths(*THIS->store, paths, sink, 1); } catch (Error & e) { croak("%s", e.what()); } diff --git a/tests/functional/export.sh b/tests/functional/export.sh index 3e895a5402d..83797a2a25a 100755 --- a/tests/functional/export.sh +++ b/tests/functional/export.sh @@ -9,15 +9,20 @@ clearStore outPath=$(nix-build dependencies.nix --no-out-link) nix-store --export $outPath > $TEST_ROOT/exp +expectStderr 1 nix nario export "$outPath" | grepQuiet "required argument.*missing" +nix nario export --format 1 "$outPath" > $TEST_ROOT/exp2 +cmp "$TEST_ROOT/exp" "$TEST_ROOT/exp2" nix-store --export $(nix-store -qR $outPath) > $TEST_ROOT/exp_all +nix nario export --format 1 -r "$outPath" > $TEST_ROOT/exp_all2 +cmp "$TEST_ROOT/exp_all" "$TEST_ROOT/exp_all2" + if nix-store --export $outPath >/dev/full ; then echo "exporting to a bad file descriptor should fail" exit 1 fi - clearStore if nix-store --import < $TEST_ROOT/exp; then @@ -25,16 +30,33 @@ if nix-store --import < $TEST_ROOT/exp; then exit 1 fi - clearStore nix-store --import < $TEST_ROOT/exp_all nix-store --export $(nix-store -qR $outPath) > $TEST_ROOT/exp_all2 - clearStore # Regression test: the derivers in exp_all2 are empty, which shouldn't # cause a failure. nix-store --import < $TEST_ROOT/exp_all2 + +# Test `nix nario import` on files created by `nix-store --export`. +clearStore +expectStderr 1 nix nario import < $TEST_ROOT/exp_all | grepQuiet "lacks a signature" +nix nario import --no-check-sigs < $TEST_ROOT/exp_all +nix path-info "$outPath" + +# Test `nix nario list`. +nix nario list < $TEST_ROOT/exp_all | grepQuiet "dependencies-input-0: .* bytes" + +# Test format 2 (including signatures). +nix key generate-secret --key-name my-key > $TEST_ROOT/secret +public_key=$(nix key convert-secret-to-public < $TEST_ROOT/secret) +nix store sign --key-file "$TEST_ROOT/secret" -r "$outPath" +nix nario export --format 2 -r "$outPath" > $TEST_ROOT/exp_all +clearStore +expectStderr 1 nix nario import < $TEST_ROOT/exp_all | grepQuiet "lacks a signature" +nix nario import --trusted-public-keys "$public_key" < $TEST_ROOT/exp_all +[[ $(nix path-info --json "$outPath" | jq -r .[].signatures[]) =~ my-key: ]] diff --git a/tests/functional/nix-profile.sh b/tests/functional/nix-profile.sh index a96abbbdff5..288ffa75d9e 100755 --- a/tests/functional/nix-profile.sh +++ b/tests/functional/nix-profile.sh @@ -94,6 +94,7 @@ printf 1.0 > $flake1Dir/version # Test --all exclusivity. assertStderr nix --offline profile upgrade --all foo << EOF error: --all cannot be used with package names or regular expressions. + Try 'nix --help' for more information. EOF