2017-10-09 20:09:41 +00:00
package main
import (
"context"
"fmt"
"os"
2018-11-10 11:43:11 +00:00
"path"
2017-10-09 20:09:41 +00:00
"path/filepath"
"github.com/restic/restic/internal/debug"
2019-05-14 18:07:29 +00:00
"github.com/restic/restic/internal/dump"
2017-10-09 20:09:41 +00:00
"github.com/restic/restic/internal/errors"
"github.com/restic/restic/internal/restic"
"github.com/spf13/cobra"
)
2017-10-14 09:34:04 +00:00
var cmdDump = & cobra . Command {
Use : "dump [flags] snapshotID file" ,
2017-10-09 20:09:41 +00:00
Short : "Print a backed-up file to stdout" ,
Long : `
2017-10-14 09:34:04 +00:00
The "dump" command extracts a single file from a snapshot from the repository and
2017-10-09 20:09:41 +00:00
prints its contents to stdout .
The special snapshot "latest" can be used to use the latest snapshot in the
repository .
2019-11-05 06:03:38 +00:00
EXIT STATUS
== == == == == =
Exit status is 0 if the command was successful , and non - zero if there was any error .
2017-10-09 20:09:41 +00:00
` ,
DisableAutoGenTag : true ,
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
2017-10-14 09:34:04 +00:00
return runDump ( dumpOptions , globalOptions , args )
2017-10-09 20:09:41 +00:00
} ,
}
2017-10-14 09:34:04 +00:00
// DumpOptions collects all options for the dump command.
type DumpOptions struct {
2020-02-26 21:17:59 +00:00
Hosts [ ] string
2017-10-09 20:09:41 +00:00
Paths [ ] string
Tags restic . TagLists
}
2017-10-14 09:34:04 +00:00
var dumpOptions DumpOptions
2017-10-09 20:09:41 +00:00
func init ( ) {
2017-10-14 09:34:04 +00:00
cmdRoot . AddCommand ( cmdDump )
2017-10-09 20:09:41 +00:00
2017-10-14 09:34:04 +00:00
flags := cmdDump . Flags ( )
2020-02-26 21:17:59 +00:00
flags . StringArrayVarP ( & dumpOptions . Hosts , "host" , "H" , nil , ` only consider snapshots for this host when the snapshot ID is "latest" (can be specified multiple times) ` )
2017-10-14 09:34:04 +00:00
flags . Var ( & dumpOptions . Tags , "tag" , "only consider snapshots which include this `taglist` for snapshot ID \"latest\"" )
flags . StringArrayVar ( & dumpOptions . Paths , "path" , nil , "only consider snapshots which include this (absolute) `path` for snapshot ID \"latest\"" )
2017-10-09 20:09:41 +00:00
}
2018-11-10 11:43:11 +00:00
func splitPath ( p string ) [ ] string {
d , f := path . Split ( p )
2019-04-25 12:08:36 +00:00
if d == "" || d == "/" {
2017-10-09 20:09:41 +00:00
return [ ] string { f }
}
2020-08-29 17:36:13 +00:00
s := splitPath ( path . Join ( "/" , d ) )
2017-10-09 20:09:41 +00:00
return append ( s , f )
}
2020-08-30 16:14:54 +00:00
func printFromTree ( ctx context . Context , tree * restic . Tree , repo restic . Repository , prefix string , pathComponents [ ] string ) error {
2017-10-09 20:09:41 +00:00
if tree == nil {
return fmt . Errorf ( "called with a nil tree" )
}
if repo == nil {
return fmt . Errorf ( "called with a nil repository" )
}
l := len ( pathComponents )
if l == 0 {
return fmt . Errorf ( "empty path components" )
}
2019-04-25 12:08:36 +00:00
// If we print / we need to assume that there are multiple nodes at that
// level in the tree.
2020-08-30 16:14:54 +00:00
if pathComponents [ 0 ] == "" {
2019-05-14 18:07:29 +00:00
if err := checkStdoutTar ( ) ; err != nil {
return err
}
2020-08-30 16:14:54 +00:00
return dump . WriteTar ( ctx , repo , tree , "/" , os . Stdout )
2019-04-25 12:08:36 +00:00
}
2017-10-09 20:09:41 +00:00
item := filepath . Join ( prefix , pathComponents [ 0 ] )
for _ , node := range tree . Nodes {
2019-04-25 12:08:36 +00:00
// 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 20:09:41 +00:00
switch {
2019-05-14 18:07:29 +00:00
case l == 1 && dump . IsFile ( node ) :
return dump . GetNodeData ( ctx , os . Stdout , repo , node )
case l > 1 && dump . IsDir ( node ) :
2017-10-09 20:09:41 +00:00
subtree , err := repo . LoadTree ( ctx , * node . Subtree )
if err != nil {
return errors . Wrapf ( err , "cannot load subtree for %q" , item )
}
2020-08-30 16:14:54 +00:00
return printFromTree ( ctx , subtree , repo , item , pathComponents [ 1 : ] )
2019-05-14 18:07:29 +00:00
case dump . IsDir ( node ) :
if err := checkStdoutTar ( ) ; err != nil {
return err
}
2019-04-25 12:08:36 +00:00
subtree , err := repo . LoadTree ( ctx , * node . Subtree )
if err != nil {
return err
}
2020-08-30 16:14:54 +00:00
return dump . WriteTar ( ctx , repo , subtree , item , os . Stdout )
2017-10-09 20:09:41 +00:00
case l > 1 :
2019-05-02 09:51:35 +00:00
return fmt . Errorf ( "%q should be a dir, but is a %q" , item , node . Type )
2019-05-14 18:07:29 +00:00
case ! dump . IsFile ( node ) :
2017-10-09 20:09:41 +00: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 )
}
2017-10-14 09:34:04 +00:00
func runDump ( opts DumpOptions , gopts GlobalOptions , args [ ] string ) error {
2017-10-09 20:09:41 +00:00
ctx := gopts . ctx
if len ( args ) != 2 {
return errors . Fatal ( "no file and no snapshot ID specified" )
}
2017-10-14 09:34:04 +00:00
snapshotIDString := args [ 0 ]
pathToPrint := args [ 1 ]
2017-10-09 20:09:41 +00:00
2017-10-14 09:34:04 +00:00
debug . Log ( "dump file %q from %q" , pathToPrint , snapshotIDString )
2017-10-09 20:09:41 +00:00
2018-12-20 15:39:48 +00:00
splittedPath := splitPath ( path . Clean ( pathToPrint ) )
2017-10-09 20:09:41 +00:00
repo , err := OpenRepository ( gopts )
if err != nil {
return err
}
if ! gopts . NoLock {
lock , err := lockRepo ( repo )
defer unlockRepo ( lock )
if err != nil {
return err
}
}
err = repo . LoadIndex ( ctx )
if err != nil {
return err
}
var id restic . ID
if snapshotIDString == "latest" {
2020-02-26 21:17:59 +00:00
id , err = restic . FindLatestSnapshot ( ctx , repo , opts . Paths , opts . Tags , opts . Hosts )
2017-10-09 20:09:41 +00:00
if err != nil {
2020-02-26 21:17:59 +00:00
Exitf ( 1 , "latest snapshot for criteria not found: %v Paths:%v Hosts:%v" , err , opts . Paths , opts . Hosts )
2017-10-09 20:09:41 +00:00
}
} else {
id , err = restic . FindSnapshot ( repo , snapshotIDString )
if err != nil {
Exitf ( 1 , "invalid id %q: %v" , snapshotIDString , err )
}
}
use global context for check, debug, dump, find, forget, init, key,
list, mount, tag, unlock commands
gh-1434
2017-12-06 12:02:55 +00:00
sn , err := restic . LoadSnapshot ( gopts . ctx , repo , id )
2017-10-09 20:09:41 +00:00
if err != nil {
Exitf ( 2 , "loading snapshot %q failed: %v" , snapshotIDString , err )
}
tree , err := repo . LoadTree ( ctx , * sn . Tree )
if err != nil {
Exitf ( 2 , "loading tree for snapshot %q failed: %v" , snapshotIDString , err )
}
2020-08-30 16:14:54 +00:00
err = printFromTree ( ctx , tree , repo , "/" , splittedPath )
2017-10-09 20:09:41 +00:00
if err != nil {
2017-10-14 09:34:04 +00:00
Exitf ( 2 , "cannot dump file: %v" , err )
2017-10-09 20:09:41 +00:00
}
return nil
}
2018-12-20 15:39:48 +00:00
2019-05-14 18:07:29 +00:00
func checkStdoutTar ( ) error {
2019-04-25 12:08:36 +00:00
if stdoutIsTerminal ( ) {
return fmt . Errorf ( "stdout is the terminal, please redirect output" )
}
2018-12-20 15:39:48 +00:00
return nil
}