github.com/mckael/restic@v0.8.3/cmd/restic/cmd_dump.go (about) 1 package main 2 3 import ( 4 "context" 5 "fmt" 6 "os" 7 "path/filepath" 8 9 "github.com/restic/restic/internal/debug" 10 "github.com/restic/restic/internal/errors" 11 "github.com/restic/restic/internal/restic" 12 13 "github.com/spf13/cobra" 14 ) 15 16 var cmdDump = &cobra.Command{ 17 Use: "dump [flags] snapshotID file", 18 Short: "Print a backed-up file to stdout", 19 Long: ` 20 The "dump" command extracts a single file from a snapshot from the repository and 21 prints its contents to stdout. 22 23 The special snapshot "latest" can be used to use the latest snapshot in the 24 repository. 25 `, 26 DisableAutoGenTag: true, 27 RunE: func(cmd *cobra.Command, args []string) error { 28 return runDump(dumpOptions, globalOptions, args) 29 }, 30 } 31 32 // DumpOptions collects all options for the dump command. 33 type DumpOptions struct { 34 Host string 35 Paths []string 36 Tags restic.TagLists 37 } 38 39 var dumpOptions DumpOptions 40 41 func init() { 42 cmdRoot.AddCommand(cmdDump) 43 44 flags := cmdDump.Flags() 45 flags.StringVarP(&dumpOptions.Host, "host", "H", "", `only consider snapshots for this host when the snapshot ID is "latest"`) 46 flags.Var(&dumpOptions.Tags, "tag", "only consider snapshots which include this `taglist` for snapshot ID \"latest\"") 47 flags.StringArrayVar(&dumpOptions.Paths, "path", nil, "only consider snapshots which include this (absolute) `path` for snapshot ID \"latest\"") 48 } 49 50 func splitPath(path string) []string { 51 d, f := filepath.Split(path) 52 if d == "" || d == "/" { 53 return []string{f} 54 } 55 s := splitPath(filepath.Clean(d)) 56 return append(s, f) 57 } 58 59 func dumpNode(ctx context.Context, repo restic.Repository, node *restic.Node) error { 60 var buf []byte 61 for _, id := range node.Content { 62 size, found := repo.LookupBlobSize(id, restic.DataBlob) 63 if !found { 64 return errors.Errorf("id %v not found in repository", id) 65 } 66 67 buf = buf[:cap(buf)] 68 if len(buf) < restic.CiphertextLength(int(size)) { 69 buf = restic.NewBlobBuffer(int(size)) 70 } 71 72 n, err := repo.LoadBlob(ctx, restic.DataBlob, id, buf) 73 if err != nil { 74 return err 75 } 76 buf = buf[:n] 77 78 _, err = os.Stdout.Write(buf) 79 if err != nil { 80 return errors.Wrap(err, "Write") 81 } 82 } 83 return nil 84 } 85 86 func printFromTree(ctx context.Context, tree *restic.Tree, repo restic.Repository, prefix string, pathComponents []string) error { 87 if tree == nil { 88 return fmt.Errorf("called with a nil tree") 89 } 90 if repo == nil { 91 return fmt.Errorf("called with a nil repository") 92 } 93 l := len(pathComponents) 94 if l == 0 { 95 return fmt.Errorf("empty path components") 96 } 97 item := filepath.Join(prefix, pathComponents[0]) 98 for _, node := range tree.Nodes { 99 if node.Name == pathComponents[0] { 100 switch { 101 case l == 1 && node.Type == "file": 102 return dumpNode(ctx, repo, node) 103 case l > 1 && node.Type == "dir": 104 subtree, err := repo.LoadTree(ctx, *node.Subtree) 105 if err != nil { 106 return errors.Wrapf(err, "cannot load subtree for %q", item) 107 } 108 return printFromTree(ctx, subtree, repo, item, pathComponents[1:]) 109 case l > 1: 110 return fmt.Errorf("%q should be a dir, but s a %q", item, node.Type) 111 case node.Type != "file": 112 return fmt.Errorf("%q should be a file, but is a %q", item, node.Type) 113 } 114 } 115 } 116 return fmt.Errorf("path %q not found in snapshot", item) 117 } 118 119 func runDump(opts DumpOptions, gopts GlobalOptions, args []string) error { 120 ctx := gopts.ctx 121 122 if len(args) != 2 { 123 return errors.Fatal("no file and no snapshot ID specified") 124 } 125 126 snapshotIDString := args[0] 127 pathToPrint := args[1] 128 129 debug.Log("dump file %q from %q", pathToPrint, snapshotIDString) 130 131 splittedPath := splitPath(pathToPrint) 132 133 repo, err := OpenRepository(gopts) 134 if err != nil { 135 return err 136 } 137 138 if !gopts.NoLock { 139 lock, err := lockRepo(repo) 140 defer unlockRepo(lock) 141 if err != nil { 142 return err 143 } 144 } 145 146 err = repo.LoadIndex(ctx) 147 if err != nil { 148 return err 149 } 150 151 var id restic.ID 152 153 if snapshotIDString == "latest" { 154 id, err = restic.FindLatestSnapshot(ctx, repo, opts.Paths, opts.Tags, opts.Host) 155 if err != nil { 156 Exitf(1, "latest snapshot for criteria not found: %v Paths:%v Host:%v", err, opts.Paths, opts.Host) 157 } 158 } else { 159 id, err = restic.FindSnapshot(repo, snapshotIDString) 160 if err != nil { 161 Exitf(1, "invalid id %q: %v", snapshotIDString, err) 162 } 163 } 164 165 sn, err := restic.LoadSnapshot(gopts.ctx, repo, id) 166 if err != nil { 167 Exitf(2, "loading snapshot %q failed: %v", snapshotIDString, err) 168 } 169 170 tree, err := repo.LoadTree(ctx, *sn.Tree) 171 if err != nil { 172 Exitf(2, "loading tree for snapshot %q failed: %v", snapshotIDString, err) 173 } 174 175 err = printFromTree(ctx, tree, repo, "", splittedPath) 176 if err != nil { 177 Exitf(2, "cannot dump file: %v", err) 178 } 179 180 return nil 181 }