2014-04-27 22:00:15 +00:00
package main
import (
2016-04-01 11:50:45 +00:00
"bufio"
2017-08-19 20:44:18 +00:00
"bytes"
2017-06-04 09:16:55 +00:00
"context"
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-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
2017-07-23 12:21:03 +00:00
"github.com/restic/restic/internal/archiver"
"github.com/restic/restic/internal/debug"
"github.com/restic/restic/internal/errors"
"github.com/restic/restic/internal/filter"
"github.com/restic/restic/internal/fs"
2017-07-24 15:42:25 +00:00
"github.com/restic/restic/internal/restic"
2016-09-17 10:36:05 +00:00
)
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 .
` ,
2017-08-05 09:54:59 +00:00
PreRun : func ( cmd * cobra . Command , args [ ] string ) {
if backupOptions . Hostname == "" {
hostname , err := os . Hostname ( )
if err != nil {
debug . Log ( "os.Hostname() returned err: %v" , err )
return
}
backupOptions . Hostname = hostname
}
} ,
2017-08-06 19:02:16 +00:00
DisableAutoGenTag : true ,
2016-09-17 10:36:05 +00:00
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 {
2017-08-19 20:44:18 +00:00
Parent string
Force bool
Excludes [ ] string
ExcludeFiles [ ] string
ExcludeOtherFS bool
ExcludeIfPresent string
ExcludeCaches bool
Stdin bool
StdinFilename string
Tags [ ] string
Hostname string
FilesFrom string
2017-08-29 16:29:46 +00:00
TimeStamp 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 )
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-07-07 01:19:06 +00:00
f . StringArrayVarP ( & backupOptions . Excludes , "exclude" , "e" , nil , "exclude a `pattern` (can be specified multiple times)" )
f . StringArrayVar ( & 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" )
2017-08-19 20:44:18 +00:00
f . StringVar ( & backupOptions . ExcludeIfPresent , "exclude-if-present" , "" , "takes filename[:header], exclude contents of directories containing filename (except filename itself) if header of that file is as provided" )
f . BoolVar ( & backupOptions . ExcludeCaches , "exclude-caches" , false , ` excludes cache directories that are marked with a CACHEDIR.TAG file ` )
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-07-07 01:19:06 +00:00
f . StringArrayVar ( & backupOptions . Tags , "tag" , nil , "add a `tag` for the new snapshot (can be specified multiple times)" )
2017-08-05 09:54:59 +00:00
f . StringVar ( & backupOptions . 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)" )
2017-08-29 16:29:46 +00:00
f . StringVar ( & backupOptions . TimeStamp , "time" , "" , "time of the backup (ex. '2012-11-01 22:08:41') (default: now)" )
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-07-24 21:15:31 +00:00
if gopts . password == "" {
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
}
2017-06-04 09:16:55 +00:00
err = repo . LoadIndex ( context . TODO ( ) )
2016-05-10 19:51:56 +00:00
if err != nil {
return err
}
2017-03-02 14:45:35 +00:00
r := & archiver . Reader {
Repository : repo ,
Tags : opts . Tags ,
Hostname : opts . Hostname ,
}
2017-06-04 09:16:55 +00:00
_ , id , err := r . Archive ( context . TODO ( ) , 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-07-24 21:15:31 +00:00
if opts . FilesFrom == "-" && gopts . password == "" {
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-06 11:43:44 +00:00
}
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
}
2017-06-04 09:16:55 +00:00
err = repo . LoadIndex ( context . TODO ( ) )
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-07-09 07:47:41 +00:00
id , err := restic . FindLatestSnapshot ( context . TODO ( ) , repo , target , [ ] restic . TagList { 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 {
2017-08-01 17:20:09 +00:00
opts . Excludes = append ( opts . Excludes , readExcludePatternsFromFiles ( opts . ExcludeFiles ) ... )
2016-04-01 11:50:45 +00:00
}
2017-08-19 20:44:18 +00:00
if opts . ExcludeCaches {
if opts . ExcludeIfPresent != "" {
return fmt . Errorf ( "cannot have --exclude-caches defined at the same time as --exclude-if-present" )
}
opts . ExcludeIfPresent = "CACHEDIR.TAG:Signature: 8a477f597d28d172789f06886806bc55"
}
excludeByFile , err := excludeByFile ( opts . ExcludeIfPresent )
if err != nil {
return err
}
2015-07-19 22:13:39 +00:00
selectFilter := func ( item string , fi os . FileInfo ) bool {
2017-06-16 14:46:16 +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
}
2017-08-19 20:44:18 +00:00
if excludeByFile ( item ) {
debug . Log ( "path %q excluded by tagfile" , item )
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-08-29 16:29:46 +00:00
timeStamp := time . Now ( )
if opts . TimeStamp != "" {
timeStamp , err = time . Parse ( TimeFormat , opts . TimeStamp )
if err != nil {
return errors . Fatalf ( "error in time option: %v\n" , err )
}
}
_ , id , err := arch . Snapshot ( context . TODO ( ) , newArchiveProgress ( gopts , stat ) , target , opts . Tags , opts . Hostname , parentSnapshotID , timeStamp )
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
}
2017-08-01 17:20:09 +00:00
func readExcludePatternsFromFiles ( excludeFiles [ ] string ) [ ] string {
var excludes [ ] string
for _ , filename := range excludeFiles {
err := func ( ) ( err error ) {
file , err := fs . Open ( filename )
if err != nil {
return err
}
defer func ( ) {
// return pre-close error if there was one
if errClose := file . Close ( ) ; err == nil {
err = errClose
}
} ( )
scanner := bufio . NewScanner ( file )
for scanner . Scan ( ) {
line := strings . TrimSpace ( scanner . Text ( ) )
// ignore empty lines
if line == "" {
continue
}
// strip comments
if strings . HasPrefix ( line , "#" ) {
continue
}
line = os . ExpandEnv ( line )
excludes = append ( excludes , line )
}
return scanner . Err ( )
} ( )
if err != nil {
Warnf ( "error reading exclude patterns: %v:" , err )
return nil
}
}
return excludes
}
2017-08-19 20:44:18 +00:00
// FilenameCheck is a function that takes a filename and returns a boolean
// depending on arbitrary check.
type FilenameCheck func ( filename string ) bool
// excludeByFile returns a FilenameCheck which itself returns whether a path
// should be excluded. The FilenameCheck considers a file to be excluded when
// it resides in a directory with an exclusion file, that is specified by
// excludeFileSpec in the form "filename[:content]". The returned error is
// non-nil if the filename component of excludeFileSpec is empty.
func excludeByFile ( excludeFileSpec string ) ( FilenameCheck , error ) {
if excludeFileSpec == "" {
return func ( string ) bool { return false } , nil
}
colon := strings . Index ( excludeFileSpec , ":" )
if colon == 0 {
return nil , fmt . Errorf ( "no name for exclusion tagfile provided" )
}
tf , tc := "" , ""
if colon > 0 {
tf = excludeFileSpec [ : colon ]
tc = excludeFileSpec [ colon + 1 : ]
} else {
tf = excludeFileSpec
}
debug . Log ( "using %q as exclusion tagfile" , tf )
fn := func ( filename string ) bool {
return isExcludedByFile ( filename , tf , tc )
}
return fn , nil
}
// isExcludedByFile interprets filename as a path and returns true if that file
// is in a excluded directory. A directory is identified as excluded if it contains a
// tagfile which bears the name specified in tagFilename and starts with header.
func isExcludedByFile ( filename , tagFilename , header string ) bool {
if tagFilename == "" {
return false
}
dir , base := filepath . Split ( filename )
if base == tagFilename {
return false // do not exclude the tagfile itself
}
tf := filepath . Join ( dir , tagFilename )
_ , err := fs . Lstat ( tf )
if os . IsNotExist ( err ) {
return false
}
if err != nil {
Warnf ( "could not access exclusion tagfile: %v" , err )
return false
}
// when no signature is given, the mere presence of tf is enough reason
// to exclude filename
if len ( header ) == 0 {
return true
}
// From this stage, errors mean tagFilename exists but it is malformed.
// Warnings will be generated so that the user is informed that the
// indented ignore-action is not performed.
f , err := os . Open ( tf )
if err != nil {
Warnf ( "could not open exclusion tagfile: %v" , err )
return false
}
defer f . Close ( )
buf := make ( [ ] byte , len ( header ) )
_ , err = io . ReadFull ( f , buf )
// EOF is handled with a dedicated message, otherwise the warning were too cryptic
if err == io . EOF {
Warnf ( "invalid (too short) signature in exclusion tagfile %q\n" , tf )
return false
}
if err != nil {
Warnf ( "could not read signature from exclusion tagfile %q: %v\n" , tf , err )
return false
}
if bytes . Compare ( buf , [ ] byte ( header ) ) != 0 {
Warnf ( "invalid signature in exclusion tagfile %q\n" , tf )
return false
}
return true
}