diff --git a/src/nix/store-import.cc b/src/nix/store-import.cc index 707b9f792..f7719d904 100644 --- a/src/nix/store-import.cc +++ b/src/nix/store-import.cc @@ -44,6 +44,19 @@ struct CmdStoreExport : StorePathsCommand, MixImportExport ; } + std::optional outputFile = std::nullopt; + + CmdStoreExport() + { + addFlag({ + .longName = "output-file", + .description = "Write the archive to the given file instead of stdout.", + .labels = {"file"}, + .handler = {&outputFile}, + }); + + } + void run(ref store, StorePaths && storePaths) override { @@ -54,7 +67,22 @@ struct CmdStoreExport : StorePathsCommand, MixImportExport StorePathSet pathsAsSet; pathsAsSet.insert(storePaths.begin(), storePaths.end()); - FdSink sink(STDOUT_FILENO); + + auto sink = [&]() -> FdSink { + if (outputFile) { + if (*outputFile == "-") { + return FdSink(STDOUT_FILENO); + } else { + auto fd = open(outputFile->c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0644); + return FdSink(fd); + } + } else if (isatty(STDOUT_FILENO)) { + throw Error("Refusing to write binary data to a terminal. Use `--output-file` to specify a file to write to."); + } else { + return FdSink(STDOUT_FILENO); + } + }(); + store->exportPaths(pathsAsSet, sink); sink.flush(); } @@ -73,9 +101,27 @@ struct CmdStoreImport : StoreCommand, MixImportExport ; } + std::optional inputFile = std::nullopt; + + CmdStoreImport() { + addFlag({ + .longName = "input-file", + .description = "Read the archive from the given file instead of stdin.", + .labels = {"file"}, + .handler = {&inputFile}, + }); + } + void run(ref store) override { - FdSource source(STDIN_FILENO); + FdSource source = [&]() -> FdSource { + if (inputFile && *inputFile != "-") { + auto fd = open(inputFile->c_str(), O_RDONLY); + return FdSource(fd); + } else { + return FdSource(STDIN_FILENO); + } + }(); auto paths = store->importPaths(source, NoCheckSigs); for (auto & path : paths) diff --git a/tests/functional/store-import-export.sh b/tests/functional/store-import-export.sh index 325f0ae3f..d2afdbc92 100644 --- a/tests/functional/store-import-export.sh +++ b/tests/functional/store-import-export.sh @@ -22,3 +22,17 @@ for IMPORTED_STORE_PATH in $IMPORTED_STORE_PATHS; do nix path-info "$IMPORTED_STORE_PATH" || fail "path $BUILT_STORE_PATH should have been imported but isn't valid" done + +faketty () { + # Run a command in a pseudo-terminal. + script -qefc "$(printf "%q " "$@")" /dev/null +} + +! faketty nix store export --format binary --recursive $BUILT_STORE_PATHS > /dev/null || \ + fail "nix store export should refuse to write in a tty by default" +faketty nix store export --format binary --recursive $BUILT_STORE_PATHS --output-file - > /dev/null || \ + fail "nix store export should accept to write in a tty if explicitly asked to" + +nix store export --format binary --recursive $BUILT_STORE_PATHS --output-file "$TEST_ROOT/store-export2" +diff "$TEST_ROOT/store-export" "$TEST_ROOT/store-export2" || \ + fail '`nix store export --output-file blah` should be equivalent to `nix store export > blah`'