2014-04-27 22:00:15 +00:00
package main
import (
2016-04-01 11:50:45 +00:00
"bufio"
2014-04-27 22:00:15 +00:00
"fmt"
2017-02-06 11:43:44 +00:00
"io"
2014-09-23 20:39:12 +00:00
"os"
2015-03-02 13:48:47 +00:00
"path/filepath"
2016-02-14 14:29:28 +00:00
"restic"
2016-04-16 20:04:29 +00:00
"strings"
"time"
2016-09-17 10:36:05 +00:00
"github.com/spf13/cobra"
2015-04-25 17:20:41 +00:00
2016-09-17 10:36:05 +00:00
"restic/archiver"
"restic/debug"
"restic/errors"
"restic/filter"
"restic/fs"
)
2015-04-25 17:20:41 +00:00
2016-09-17 10:36:05 +00:00
var cmdBackup = & cobra . Command {
Use : "backup [flags] FILE/DIR [FILE/DIR] ..." ,
Short : "create a new backup of files and/or directories" ,
Long : `
The "backup" command creates a new snapshot and saves the files and directories
given as the arguments .
` ,
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
2017-02-06 11:43:44 +00:00
if backupOptions . Stdin && backupOptions . FilesFrom == "-" {
return errors . Fatal ( "cannot use both `--stdin` and `--files-from -`" )
}
2016-09-17 10:36:05 +00:00
if backupOptions . Stdin {
return readBackupFromStdin ( backupOptions , globalOptions , args )
}
2015-04-25 17:20:41 +00:00
2016-09-17 10:36:05 +00:00
return runBackup ( backupOptions , globalOptions , args )
} ,
2015-04-25 17:20:41 +00:00
}
2016-09-17 10:36:05 +00:00
// BackupOptions bundles all options for the backup command.
type BackupOptions struct {
Parent string
Force bool
Excludes [ ] string
2017-04-16 18:52:41 +00:00
ExcludeFiles [ ] string
2016-09-17 10:36:05 +00:00
ExcludeOtherFS bool
Stdin bool
StdinFilename string
Tags [ ] string
2017-02-10 18:37:33 +00:00
Hostname string
2016-11-03 17:48:07 +00:00
FilesFrom string
2015-04-25 17:20:41 +00:00
}
2016-09-17 10:36:05 +00:00
var backupOptions BackupOptions
2015-01-04 17:23:00 +00:00
2016-09-17 10:36:05 +00:00
func init ( ) {
cmdRoot . AddCommand ( cmdBackup )
2017-02-10 18:37:33 +00:00
hostname , err := os . Hostname ( )
if err != nil {
debug . Log ( "os.Hostname() returned err: %v" , err )
hostname = ""
}
2016-09-17 10:36:05 +00:00
f := cmdBackup . Flags ( )
f . StringVar ( & backupOptions . Parent , "parent" , "" , "use this parent snapshot (default: last snapshot in the repo that has the same target files/directories)" )
2017-02-13 15:02:47 +00:00
f . BoolVarP ( & backupOptions . Force , "force" , "f" , false , ` force re-reading the target files/directories (overrides the "parent" flag) ` )
2017-03-08 18:55:58 +00:00
f . StringSliceVarP ( & backupOptions . Excludes , "exclude" , "e" , nil , "exclude a `pattern` (can be specified multiple times)" )
2017-04-16 18:52:41 +00:00
f . StringSliceVar ( & backupOptions . ExcludeFiles , "exclude-file" , nil , "read exclude patterns from a `file` (can be specified multiple times)" )
2017-02-13 15:02:47 +00:00
f . BoolVarP ( & backupOptions . ExcludeOtherFS , "one-file-system" , "x" , false , "exclude other file systems" )
2016-09-17 10:36:05 +00:00
f . BoolVar ( & backupOptions . Stdin , "stdin" , false , "read backup from stdin" )
2017-02-08 21:37:02 +00:00
f . StringVar ( & backupOptions . StdinFilename , "stdin-filename" , "stdin" , "file name to use when reading from stdin" )
2017-03-08 18:55:58 +00:00
f . StringSliceVar ( & backupOptions . Tags , "tag" , nil , "add a `tag` for the new snapshot (can be specified multiple times)" )
2017-02-10 18:37:33 +00:00
f . StringVar ( & backupOptions . Hostname , "hostname" , hostname , "set the `hostname` for the snapshot manually" )
2016-11-03 17:48:07 +00:00
f . StringVar ( & backupOptions . FilesFrom , "files-from" , "" , "read the files to backup from file (can be combined with file args)" )
2014-12-07 15:30:52 +00:00
}
2016-09-17 10:36:05 +00:00
func newScanProgress ( gopts GlobalOptions ) * restic . Progress {
if gopts . Quiet {
2015-02-21 13:23:49 +00:00
return nil
}
2016-08-25 20:13:47 +00:00
p := restic . NewProgress ( )
2015-02-21 14:32:48 +00:00
p . OnUpdate = func ( s restic . Stat , d time . Duration , ticker bool ) {
2016-10-14 23:54:54 +00:00
if IsProcessBackground ( ) {
return
}
2016-08-25 20:13:47 +00:00
PrintProgress ( "[%s] %d directories, %d files, %s" , formatDuration ( d ) , s . Dirs , s . Files , formatBytes ( s . Bytes ) )
2015-02-21 14:32:48 +00:00
}
2016-10-14 23:54:54 +00:00
2015-02-21 14:32:48 +00:00
p . OnDone = func ( s restic . Stat , d time . Duration , ticker bool ) {
2016-08-25 20:13:47 +00:00
PrintProgress ( "scanned %d directories, %d files in %s\n" , s . Dirs , s . Files , formatDuration ( d ) )
2015-02-21 14:32:48 +00:00
}
return p
}
2016-09-17 10:36:05 +00:00
func newArchiveProgress ( gopts GlobalOptions , todo restic . Stat ) * restic . Progress {
if gopts . Quiet {
2015-02-21 13:23:49 +00:00
return nil
}
2016-08-25 20:13:47 +00:00
archiveProgress := restic . NewProgress ( )
2015-02-21 13:23:49 +00:00
var bps , eta uint64
itemsTodo := todo . Files + todo . Dirs
archiveProgress . OnUpdate = func ( s restic . Stat , d time . Duration , ticker bool ) {
2016-10-14 23:54:54 +00:00
if IsProcessBackground ( ) {
return
}
2015-02-21 13:23:49 +00:00
sec := uint64 ( d / time . Second )
if todo . Bytes > 0 && sec > 0 && ticker {
bps = s . Bytes / sec
2015-03-16 19:20:53 +00:00
if s . Bytes >= todo . Bytes {
eta = 0
} else if bps > 0 {
2015-02-21 13:23:49 +00:00
eta = ( todo . Bytes - s . Bytes ) / bps
}
}
itemsDone := s . Files + s . Dirs
2015-03-16 19:20:53 +00:00
2015-04-26 10:09:14 +00:00
status1 := fmt . Sprintf ( "[%s] %s %s/s %s / %s %d / %d items %d errors " ,
2015-04-24 23:39:32 +00:00
formatDuration ( d ) ,
2015-04-25 17:20:41 +00:00
formatPercent ( s . Bytes , todo . Bytes ) ,
2015-04-24 23:39:32 +00:00
formatBytes ( bps ) ,
formatBytes ( s . Bytes ) , formatBytes ( todo . Bytes ) ,
2015-04-26 01:54:35 +00:00
itemsDone , itemsTodo ,
s . Errors )
2015-04-24 23:39:32 +00:00
status2 := fmt . Sprintf ( "ETA %s " , formatSeconds ( eta ) )
2015-03-16 19:48:29 +00:00
2017-03-06 10:23:00 +00:00
if w := stdoutTerminalWidth ( ) ; w > 0 {
2016-08-22 20:07:10 +00:00
maxlen := w - len ( status2 ) - 1
2016-02-10 16:28:48 +00:00
if maxlen < 4 {
status1 = ""
} else if len ( status1 ) > maxlen {
status1 = status1 [ : maxlen - 4 ]
status1 += "... "
2015-03-16 19:48:29 +00:00
}
}
2016-08-25 20:13:47 +00:00
PrintProgress ( "%s%s" , status1 , status2 )
2015-02-21 13:23:49 +00:00
}
archiveProgress . OnDone = func ( s restic . Stat , d time . Duration , ticker bool ) {
2015-04-25 17:20:41 +00:00
fmt . Printf ( "\nduration: %s, %s\n" , formatDuration ( d ) , formatRate ( todo . Bytes , d ) )
2015-02-21 13:23:49 +00:00
}
return archiveProgress
2015-02-16 22:44:26 +00:00
}
2016-09-17 10:36:05 +00:00
func newArchiveStdinProgress ( gopts GlobalOptions ) * restic . Progress {
if gopts . Quiet {
2016-05-10 19:51:56 +00:00
return nil
}
2016-08-25 20:13:47 +00:00
archiveProgress := restic . NewProgress ( )
2016-05-10 19:51:56 +00:00
var bps uint64
archiveProgress . OnUpdate = func ( s restic . Stat , d time . Duration , ticker bool ) {
2016-10-14 23:54:54 +00:00
if IsProcessBackground ( ) {
return
}
2016-05-10 19:51:56 +00:00
sec := uint64 ( d / time . Second )
if s . Bytes > 0 && sec > 0 && ticker {
bps = s . Bytes / sec
}
status1 := fmt . Sprintf ( "[%s] %s %s/s" , formatDuration ( d ) ,
formatBytes ( s . Bytes ) ,
formatBytes ( bps ) )
2017-03-06 10:23:00 +00:00
if w := stdoutTerminalWidth ( ) ; w > 0 {
2016-05-10 19:51:56 +00:00
maxlen := w - len ( status1 )
if maxlen < 4 {
status1 = ""
} else if len ( status1 ) > maxlen {
status1 = status1 [ : maxlen - 4 ]
status1 += "... "
}
}
2016-09-13 19:01:29 +00:00
PrintProgress ( "%s" , status1 )
2016-05-10 19:51:56 +00:00
}
archiveProgress . OnDone = func ( s restic . Stat , d time . Duration , ticker bool ) {
fmt . Printf ( "\nduration: %s, %s\n" , formatDuration ( d ) , formatRate ( s . Bytes , d ) )
}
return archiveProgress
}
2015-07-06 21:02:16 +00:00
// filterExisting returns a slice of all existing items, or an error if no
// items exist at all.
func filterExisting ( items [ ] string ) ( result [ ] string , err error ) {
for _ , item := range items {
Fix 567 (#570)
* Patch for https://github.com/restic/restic/issues/567
Backup also files on windows with longer pathnames than 255 chars (e.g. from node).
as fd0 says "So, as far as I can see, we need to have custom methods for all functions that accept a path, so that on Windows we can substitute the normal (possibly relative) path used within restic by an (absolute) UNC path, and only then call the underlying functions like os.Stat(), os.Lstat(), os.Open() and so on.
I've already thought about adding a generic abstraction for the file system (so we can mock this easier in tests), and this looks like a good opportunity to build it."
* fixed building tests
* Restructured patches
Add Wrapper for filepath.Walk
* using \\?\ requires absolute pathes to be used.
Now all tests run
* used gofmt on the code
* Restructured Code. No patches dir, integrate the file functions into restic/fs/
There is still an issue, because restic.fs.Open has a different api the os.Open, which returns the result of OpenFile, but takes only a string
* Changed the last os.Open() calls to fs.Open() after extending the File interface
* fixed name-clash of restic.fs and fuse.fs detected by travis
* fixed fmt with gofmt
* c&p failure: removed fixpath() call.
* missing include
* fixed includes in linux variant
* Fix for Linux. Fd() is required on File interface
* done gofmt
2016-08-15 19:59:13 +00:00
_ , err := fs . Lstat ( item )
2016-08-29 17:18:57 +00:00
if err != nil && os . IsNotExist ( errors . Cause ( err ) ) {
2015-07-06 21:02:16 +00:00
continue
}
result = append ( result , item )
}
if len ( result ) == 0 {
2016-09-01 20:17:37 +00:00
return nil , errors . Fatal ( "all target directories/files do not exist" )
2015-07-06 21:02:16 +00:00
}
return
}
2016-09-18 15:10:33 +00:00
// gatherDevices returns the set of unique device ids of the files and/or
// directory paths listed in "items".
2017-04-13 19:03:05 +00:00
func gatherDevices ( items [ ] string ) ( deviceMap map [ string ] uint64 , err error ) {
deviceMap = make ( map [ string ] uint64 )
2016-09-18 15:10:33 +00:00
for _ , item := range items {
fi , err := fs . Lstat ( item )
if err != nil {
return nil , err
}
id , err := fs . DeviceID ( fi )
if err != nil {
return nil , err
}
2017-04-13 19:03:05 +00:00
deviceMap [ item ] = id
2016-09-18 15:10:33 +00:00
}
if len ( deviceMap ) == 0 {
return nil , errors . New ( "zero allowed devices" )
}
return deviceMap , nil
}
2016-09-17 10:36:05 +00:00
func readBackupFromStdin ( opts BackupOptions , gopts GlobalOptions , args [ ] string ) error {
2016-05-10 19:51:56 +00:00
if len ( args ) != 0 {
2017-02-10 18:39:49 +00:00
return errors . Fatal ( "when reading from stdin, no additional files can be specified" )
2016-05-10 19:51:56 +00:00
}
2017-02-08 21:37:02 +00:00
if opts . StdinFilename == "" {
return errors . Fatal ( "filename for backup from stdin must not be empty" )
}
2017-02-03 14:53:07 +00:00
if gopts . password == "" && gopts . PasswordFile == "" {
2017-02-03 16:06:06 +00:00
return errors . Fatal ( "unable to read password from stdin when data is to be read from stdin, use --password-file or $RESTIC_PASSWORD" )
2017-02-03 14:53:07 +00:00
}
2016-09-17 10:36:05 +00:00
repo , err := OpenRepository ( gopts )
2016-05-10 19:51:56 +00:00
if err != nil {
return err
}
lock , err := lockRepo ( repo )
defer unlockRepo ( lock )
if err != nil {
return err
}
err = repo . LoadIndex ( )
if err != nil {
return err
}
2017-03-02 14:45:35 +00:00
r := & archiver . Reader {
Repository : repo ,
Tags : opts . Tags ,
Hostname : opts . Hostname ,
}
_ , id , err := r . Archive ( opts . StdinFilename , os . Stdin , newArchiveStdinProgress ( gopts ) )
2016-05-10 19:51:56 +00:00
if err != nil {
return err
}
2016-12-03 09:25:44 +00:00
Verbosef ( "archived as %v\n" , id . Str ( ) )
2016-05-10 19:51:56 +00:00
return nil
}
2016-11-03 17:48:07 +00:00
// readFromFile will read all lines from the given filename and write them to a
// string array, if filename is empty readFromFile returns and empty string
2017-02-06 11:43:44 +00:00
// array. If filename is a dash (-), readFromFile will read the lines from
// the standard input.
2016-11-03 17:48:07 +00:00
func readLinesFromFile ( filename string ) ( [ ] string , error ) {
if filename == "" {
return nil , nil
}
2017-02-06 11:43:44 +00:00
var r io . Reader = os . Stdin
if filename != "-" {
f , err := os . Open ( filename )
if err != nil {
return nil , err
}
defer f . Close ( )
r = f
2016-11-03 17:48:07 +00:00
}
var lines [ ] string
2017-02-06 11:43:44 +00:00
scanner := bufio . NewScanner ( r )
2016-11-03 17:48:07 +00:00
for scanner . Scan ( ) {
2017-02-27 18:42:00 +00:00
line := scanner . Text ( )
if line == "" {
continue
}
lines = append ( lines , line )
2016-11-03 17:48:07 +00:00
}
if err := scanner . Err ( ) ; err != nil {
return nil , err
}
return lines , nil
}
2016-09-17 10:36:05 +00:00
func runBackup ( opts BackupOptions , gopts GlobalOptions , args [ ] string ) error {
2017-02-06 11:43:44 +00:00
if opts . FilesFrom == "-" && gopts . password == "" && gopts . PasswordFile == "" {
return errors . Fatal ( "no password; either use `--password-file` option or put the password into the RESTIC_PASSWORD environment variable" )
}
2016-12-18 22:20:08 +00:00
fromfile , err := readLinesFromFile ( opts . FilesFrom )
2016-11-03 17:48:07 +00:00
if err != nil {
return err
}
// merge files from files-from into normal args so we can reuse the normal
// args checks and have the ability to use both files-from and args at the
// same time
2016-12-18 22:20:08 +00:00
args = append ( args , fromfile ... )
2015-03-02 13:48:47 +00:00
if len ( args ) == 0 {
2017-02-10 18:39:49 +00:00
return errors . Fatal ( "wrong number of parameters" )
2014-12-07 15:30:52 +00:00
}
2016-12-18 22:20:08 +00:00
target := make ( [ ] string , 0 , len ( args ) )
2015-03-02 13:48:47 +00:00
for _ , d := range args {
if a , err := filepath . Abs ( d ) ; err == nil {
d = a
}
target = append ( target , d )
}
2016-11-03 17:48:07 +00:00
target , err = filterExisting ( target )
2015-07-06 21:02:16 +00:00
if err != nil {
return err
}
2016-09-18 15:10:33 +00:00
// allowed devices
2017-04-13 19:03:05 +00:00
var allowedDevs map [ string ] uint64
2016-09-17 10:36:05 +00:00
if opts . ExcludeOtherFS {
2016-09-18 15:10:33 +00:00
allowedDevs , err = gatherDevices ( target )
if err != nil {
return err
}
2016-09-27 20:35:08 +00:00
debug . Log ( "allowed devices: %v\n" , allowedDevs )
2016-09-18 15:10:33 +00:00
}
2016-09-17 10:36:05 +00:00
repo , err := OpenRepository ( gopts )
2014-12-07 15:30:52 +00:00
if err != nil {
return err
2014-04-27 22:00:15 +00:00
}
2015-06-27 13:05:20 +00:00
lock , err := lockRepo ( repo )
defer unlockRepo ( lock )
2015-06-27 12:40:18 +00:00
if err != nil {
return err
}
2015-06-27 12:36:46 +00:00
err = repo . LoadIndex ( )
2015-04-26 15:44:38 +00:00
if err != nil {
return err
}
2016-09-01 14:04:29 +00:00
var parentSnapshotID * restic . ID
2014-11-30 21:34:21 +00:00
2015-04-03 19:18:09 +00:00
// Force using a parent
2016-09-17 10:36:05 +00:00
if ! opts . Force && opts . Parent != "" {
id , err := restic . FindSnapshot ( repo , opts . Parent )
2014-11-30 21:34:21 +00:00
if err != nil {
2016-09-17 10:36:05 +00:00
return errors . Fatalf ( "invalid id %q: %v" , opts . Parent , err )
2014-11-30 21:34:21 +00:00
}
2015-07-25 15:05:45 +00:00
parentSnapshotID = & id
2014-11-30 21:34:21 +00:00
}
2014-04-27 22:00:15 +00:00
2015-04-03 19:18:09 +00:00
// Find last snapshot to set it as parent, if not already set
2016-09-17 10:36:05 +00:00
if ! opts . Force && parentSnapshotID == nil {
2017-03-08 18:55:58 +00:00
id , err := restic . FindLatestSnapshot ( repo , target , opts . Tags , opts . Hostname )
2015-07-25 15:05:45 +00:00
if err == nil {
parentSnapshotID = & id
2016-04-27 16:36:48 +00:00
} else if err != restic . ErrNoSnapshotFound {
2015-07-25 15:05:45 +00:00
return err
2015-04-03 19:18:09 +00:00
}
}
2015-07-25 16:09:38 +00:00
if parentSnapshotID != nil {
2016-09-17 10:36:05 +00:00
Verbosef ( "using parent snapshot %v\n" , parentSnapshotID . Str ( ) )
2015-07-25 16:09:38 +00:00
}
2016-09-17 10:36:05 +00:00
Verbosef ( "scan %v\n" , target )
2014-11-16 20:29:11 +00:00
2016-04-01 11:50:45 +00:00
// add patterns from file
2017-04-16 18:52:41 +00:00
if len ( opts . ExcludeFiles ) > 0 {
for _ , filename := range opts . ExcludeFiles {
file , err := fs . Open ( filename )
if err != nil {
Warnf ( "error reading exclude patterns: %v" , err )
return nil
}
2016-04-01 11:50:45 +00:00
2017-04-16 18:52:41 +00:00
scanner := bufio . NewScanner ( file )
for scanner . Scan ( ) {
line := strings . TrimSpace ( scanner . Text ( ) )
2017-04-16 18:46:52 +00:00
2017-04-16 18:52:41 +00:00
// ignore empty lines
if line == "" {
continue
}
2017-04-16 18:46:52 +00:00
2017-04-16 18:52:41 +00:00
// strip comments
if strings . HasPrefix ( line , "#" ) {
continue
}
2017-04-16 18:46:52 +00:00
2017-04-16 18:52:41 +00:00
line = os . ExpandEnv ( line )
opts . Excludes = append ( opts . Excludes , line )
}
2016-04-01 11:50:45 +00:00
}
}
2015-07-19 22:13:39 +00:00
selectFilter := func ( item string , fi os . FileInfo ) bool {
2016-09-17 10:36:05 +00:00
matched , err := filter . List ( opts . Excludes , item )
2015-07-19 22:13:39 +00:00
if err != nil {
2016-09-17 10:36:05 +00:00
Warnf ( "error for exclude pattern: %v" , err )
2015-07-19 22:13:39 +00:00
}
2015-10-27 21:34:30 +00:00
if matched {
2016-09-27 20:35:08 +00:00
debug . Log ( "path %q excluded by a filter" , item )
2016-09-18 15:10:33 +00:00
return false
}
2016-11-05 11:37:54 +00:00
if ! opts . ExcludeOtherFS || fi == nil {
2016-09-18 15:10:33 +00:00
return true
}
id , err := fs . DeviceID ( fi )
if err != nil {
// This should never happen because gatherDevices() would have
// errored out earlier. If it still does that's a reason to panic.
panic ( err )
}
2017-04-13 19:03:05 +00:00
for dir := item ; dir != "" ; dir = filepath . Dir ( dir ) {
debug . Log ( "item %v, test dir %v" , item , dir )
allowedID , ok := allowedDevs [ dir ]
if ! ok {
continue
}
if allowedID != id {
debug . Log ( "path %q on disallowed device %d" , item , id )
return false
}
return true
2015-10-27 21:34:30 +00:00
}
2017-04-13 19:03:05 +00:00
panic ( fmt . Sprintf ( "item %v, device id %v not found, allowedDevs: %v" , item , id , allowedDevs ) )
2015-07-19 22:13:39 +00:00
}
2014-11-16 20:29:11 +00:00
2016-09-17 10:36:05 +00:00
stat , err := archiver . Scan ( target , selectFilter , newScanProgress ( gopts ) )
2015-07-19 22:13:39 +00:00
if err != nil {
return err
}
2014-11-16 20:29:11 +00:00
2016-09-01 14:04:29 +00:00
arch := archiver . New ( repo )
2016-09-17 10:36:05 +00:00
arch . Excludes = opts . Excludes
2015-07-19 22:13:39 +00:00
arch . SelectFilter = selectFilter
2014-11-23 11:05:43 +00:00
2016-12-10 16:14:13 +00:00
arch . Warn = func ( dir string , fi os . FileInfo , err error ) {
2015-01-04 17:23:00 +00:00
// TODO: make ignoring errors configurable
2016-12-10 16:14:13 +00:00
Warnf ( "%s\rwarning for %s: %v\n" , ClearLine ( ) , dir , err )
2014-11-16 20:29:11 +00:00
}
2017-02-10 18:37:33 +00:00
_ , id , err := arch . Snapshot ( newArchiveProgress ( gopts , stat ) , target , opts . Tags , opts . Hostname , parentSnapshotID )
2014-11-16 20:29:11 +00:00
if err != nil {
2015-02-03 21:05:46 +00:00
return err
2014-11-16 20:29:11 +00:00
}
2016-09-17 10:36:05 +00:00
Verbosef ( "snapshot %s saved\n" , id . Str ( ) )
2014-04-27 22:00:15 +00:00
return nil
}