restic/cmd/restic/cmd_dump.go

196 lines
5.3 KiB
Go
Raw Normal View History

2017-10-09 22:09:41 +02:00
package main
import (
"context"
"fmt"
"os"
"path"
2017-10-09 22:09:41 +02:00
"path/filepath"
"github.com/restic/restic/internal/debug"
"github.com/restic/restic/internal/dump"
2017-10-09 22:09:41 +02:00
"github.com/restic/restic/internal/errors"
"github.com/restic/restic/internal/restic"
"github.com/spf13/cobra"
)
2017-10-14 11:34:04 +02:00
var cmdDump = &cobra.Command{
Use: "dump [flags] snapshotID file",
2017-10-09 22:09:41 +02:00
Short: "Print a backed-up file to stdout",
Long: `
The "dump" command extracts files from a snapshot from the repository. If a
single file is selected, it prints its contents to stdout. Folders are output
2020-11-10 02:24:09 +01:00
as a tar (default) or zip file containing the contents of the specified folder.
Pass "/" as file name to dump the whole snapshot as an archive file.
2017-10-09 22:09:41 +02:00
The special snapshotID "latest" can be used to use the latest snapshot in the
2017-10-09 22:09:41 +02:00
repository.
To include the folder content at the root of the archive, you can use the
"<snapshotID>:<subfolder>" syntax, where "subfolder" is a path within the
snapshot.
EXIT STATUS
===========
Exit status is 0 if the command was successful, and non-zero if there was any error.
2017-10-09 22:09:41 +02:00
`,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
2022-10-02 23:24:37 +02:00
return runDump(cmd.Context(), dumpOptions, globalOptions, args)
2017-10-09 22:09:41 +02:00
},
}
2017-10-14 11:34:04 +02:00
// DumpOptions collects all options for the dump command.
type DumpOptions struct {
restic.SnapshotFilter
2020-11-10 02:24:09 +01:00
Archive string
2024-02-04 09:45:14 +01:00
Target string
2017-10-09 22:09:41 +02:00
}
2017-10-14 11:34:04 +02:00
var dumpOptions DumpOptions
2017-10-09 22:09:41 +02:00
func init() {
2017-10-14 11:34:04 +02:00
cmdRoot.AddCommand(cmdDump)
2017-10-09 22:09:41 +02:00
2017-10-14 11:34:04 +02:00
flags := cmdDump.Flags()
initSingleSnapshotFilter(flags, &dumpOptions.SnapshotFilter)
2020-11-10 02:24:09 +01:00
flags.StringVarP(&dumpOptions.Archive, "archive", "a", "tar", "set archive `format` as \"tar\" or \"zip\"")
2024-02-05 20:09:58 +01:00
flags.StringVarP(&dumpOptions.Target, "target", "t", "", "write the output to target `path`")
2017-10-09 22:09:41 +02:00
}
func splitPath(p string) []string {
d, f := path.Split(p)
if d == "" || d == "/" {
2017-10-09 22:09:41 +02:00
return []string{f}
}
2020-08-29 19:36:13 +02:00
s := splitPath(path.Join("/", d))
2017-10-09 22:09:41 +02:00
return append(s, f)
}
2024-02-05 20:09:58 +01:00
func printFromTree(ctx context.Context, tree *restic.Tree, repo restic.BlobLoader, prefix string, pathComponents []string, d *dump.Dumper, canWriteArchiveFunc func() error) error {
// If we print / we need to assume that there are multiple nodes at that
// level in the tree.
if pathComponents[0] == "" {
2024-02-05 20:09:58 +01:00
if err := canWriteArchiveFunc(); err != nil {
return err
}
return d.DumpTree(ctx, tree, "/")
}
2017-10-09 22:09:41 +02:00
item := filepath.Join(prefix, pathComponents[0])
l := len(pathComponents)
2017-10-09 22:09:41 +02:00
for _, node := range tree.Nodes {
// If dumping something in the highest level it will just take the
// first item it finds and dump that according to the switch case below.
if node.Name == pathComponents[0] {
2017-10-09 22:09:41 +02:00
switch {
case l == 1 && dump.IsFile(node):
return d.WriteNode(ctx, node)
case l > 1 && dump.IsDir(node):
subtree, err := restic.LoadTree(ctx, repo, *node.Subtree)
2017-10-09 22:09:41 +02:00
if err != nil {
return errors.Wrapf(err, "cannot load subtree for %q", item)
}
2024-02-05 20:09:58 +01:00
return printFromTree(ctx, subtree, repo, item, pathComponents[1:], d, canWriteArchiveFunc)
case dump.IsDir(node):
2024-02-05 20:09:58 +01:00
if err := canWriteArchiveFunc(); err != nil {
return err
}
subtree, err := restic.LoadTree(ctx, repo, *node.Subtree)
if err != nil {
return err
}
return d.DumpTree(ctx, subtree, item)
2017-10-09 22:09:41 +02:00
case l > 1:
2019-05-02 11:51:35 +02:00
return fmt.Errorf("%q should be a dir, but is a %q", item, node.Type)
case !dump.IsFile(node):
2017-10-09 22:09:41 +02:00
return fmt.Errorf("%q should be a file, but is a %q", item, node.Type)
}
}
}
return fmt.Errorf("path %q not found in snapshot", item)
}
func runDump(ctx context.Context, opts DumpOptions, gopts GlobalOptions, args []string) error {
2017-10-09 22:09:41 +02:00
if len(args) != 2 {
return errors.Fatal("no file and no snapshot ID specified")
}
2020-11-10 02:24:09 +01:00
switch opts.Archive {
case "tar", "zip":
2020-11-10 02:24:09 +01:00
default:
return fmt.Errorf("unknown archive format %q", opts.Archive)
}
2017-10-14 11:34:04 +02:00
snapshotIDString := args[0]
pathToPrint := args[1]
2017-10-09 22:09:41 +02:00
2017-10-14 11:34:04 +02:00
debug.Log("dump file %q from %q", pathToPrint, snapshotIDString)
2017-10-09 22:09:41 +02:00
splittedPath := splitPath(path.Clean(pathToPrint))
2017-10-09 22:09:41 +02:00
ctx, repo, unlock, err := openWithReadLock(ctx, gopts, gopts.NoLock)
2017-10-09 22:09:41 +02:00
if err != nil {
return err
}
defer unlock()
2017-10-09 22:09:41 +02:00
sn, subfolder, err := (&restic.SnapshotFilter{
Hosts: opts.Hosts,
Paths: opts.Paths,
Tags: opts.Tags,
}).FindLatest(ctx, repo, repo, snapshotIDString)
if err != nil {
return errors.Fatalf("failed to find snapshot: %v", err)
2017-10-09 22:09:41 +02:00
}
2023-10-01 19:38:09 +02:00
bar := newIndexProgress(gopts.Quiet, gopts.JSON)
err = repo.LoadIndex(ctx, bar)
if err != nil {
return err
}
sn.Tree, err = restic.FindTreeDirectory(ctx, repo, sn.Tree, subfolder)
if err != nil {
return err
}
tree, err := restic.LoadTree(ctx, repo, *sn.Tree)
2017-10-09 22:09:41 +02:00
if err != nil {
return errors.Fatalf("loading tree for snapshot %q failed: %v", snapshotIDString, err)
2017-10-09 22:09:41 +02:00
}
2024-02-05 20:09:58 +01:00
outputFileWriter := os.Stdout
canWriteArchiveFunc := checkStdoutArchive
2024-02-04 09:45:14 +01:00
if opts.Target != "" {
file, err := os.Create(opts.Target)
2024-02-04 09:45:14 +01:00
if err != nil {
return fmt.Errorf("cannot dump to file: %w", err)
}
defer func() {
_ = file.Close()
}()
outputFileWriter = file
2024-02-05 20:09:58 +01:00
canWriteArchiveFunc = func() error { return nil }
2024-02-04 09:45:14 +01:00
}
d := dump.New(opts.Archive, repo, outputFileWriter)
2024-02-05 20:09:58 +01:00
err = printFromTree(ctx, tree, repo, "/", splittedPath, d, canWriteArchiveFunc)
2017-10-09 22:09:41 +02:00
if err != nil {
return errors.Fatalf("cannot dump file: %v", err)
2017-10-09 22:09:41 +02:00
}
return nil
}
2020-11-09 23:22:27 +01:00
func checkStdoutArchive() error {
if stdoutIsTerminal() {
return fmt.Errorf("stdout is the terminal, please redirect output")
}
return nil
}