diff --git a/changelog/unreleased/issue-4678 b/changelog/unreleased/issue-4678 new file mode 100644 index 000000000..da669fa82 --- /dev/null +++ b/changelog/unreleased/issue-4678 @@ -0,0 +1,7 @@ +Enhancement: Add --target flag to the dump command + +Restic `dump` always printed to the standard output. It now permits to select a +`--target` file to print the output. + +https://github.com/restic/restic/issues/4678 +https://github.com/restic/restic/pull/4682 diff --git a/cmd/restic/cmd_dump.go b/cmd/restic/cmd_dump.go index e72b51fa2..cf2f305c5 100644 --- a/cmd/restic/cmd_dump.go +++ b/cmd/restic/cmd_dump.go @@ -46,6 +46,7 @@ Exit status is 0 if the command was successful, and non-zero if there was any er type DumpOptions struct { restic.SnapshotFilter Archive string + Target string } var dumpOptions DumpOptions @@ -56,6 +57,7 @@ func init() { flags := cmdDump.Flags() initSingleSnapshotFilter(flags, &dumpOptions.SnapshotFilter) flags.StringVarP(&dumpOptions.Archive, "archive", "a", "tar", "set archive `format` as \"tar\" or \"zip\"") + flags.StringVarP(&dumpOptions.Target, "target", "t", "", "set the target path to dump the archive file") } func splitPath(p string) []string { @@ -67,11 +69,11 @@ func splitPath(p string) []string { return append(s, f) } -func printFromTree(ctx context.Context, tree *restic.Tree, repo restic.BlobLoader, prefix string, pathComponents []string, d *dump.Dumper) error { +func printFromTree(ctx context.Context, tree *restic.Tree, repo restic.BlobLoader, prefix string, pathComponents []string, d *dump.Dumper, checkStdoutArchiveFunc func() error) error { // If we print / we need to assume that there are multiple nodes at that // level in the tree. if pathComponents[0] == "" { - if err := checkStdoutArchive(); err != nil { + if err := checkStdoutArchiveFunc(); err != nil { return err } return d.DumpTree(ctx, tree, "/") @@ -91,9 +93,9 @@ func printFromTree(ctx context.Context, tree *restic.Tree, repo restic.BlobLoade if err != nil { return errors.Wrapf(err, "cannot load subtree for %q", item) } - return printFromTree(ctx, subtree, repo, item, pathComponents[1:], d) + return printFromTree(ctx, subtree, repo, item, pathComponents[1:], d, checkStdoutArchiveFunc) case dump.IsDir(node): - if err := checkStdoutArchive(); err != nil { + if err := checkStdoutArchiveFunc(); err != nil { return err } subtree, err := restic.LoadTree(ctx, repo, *node.Subtree) @@ -168,8 +170,23 @@ func runDump(ctx context.Context, opts DumpOptions, gopts GlobalOptions, args [] return errors.Fatalf("loading tree for snapshot %q failed: %v", snapshotIDString, err) } - d := dump.New(opts.Archive, repo, os.Stdout) - err = printFromTree(ctx, tree, repo, "/", splittedPath, d) + var outputFileWriter = os.Stdout + checkStdoutArchiveFunc := checkStdoutArchive + if opts.Target != "" { + file, err := os.OpenFile(opts.Target, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0o666) + if err != nil { + return fmt.Errorf("cannot dump to file: %w", err) + } + defer func() { + _ = file.Close() + }() + + outputFileWriter = file + checkStdoutArchiveFunc = func() error { return nil } + } + + d := dump.New(opts.Archive, repo, outputFileWriter) + err = printFromTree(ctx, tree, repo, "/", splittedPath, d, checkStdoutArchiveFunc) if err != nil { return errors.Fatalf("cannot dump file: %v", err) } diff --git a/doc/050_restore.rst b/doc/050_restore.rst index 56f6458ed..7ff7f2706 100644 --- a/doc/050_restore.rst +++ b/doc/050_restore.rst @@ -174,3 +174,10 @@ To include the folder content at the root of the archive, you can use the `` restore.tar + +It is also possible to ``dump`` the contents of a selected snapshot and folder +structure to a file using the ``--target`` flag. The ``dump`` will fail if the +file exists. + +.. code-block:: console + $ restic -r /srv/restic-repo dump latest / --target /home/linux.user/output.tar -a tar \ No newline at end of file