From 53701891a168cbf8dfd62ede988b0e5504459c10 Mon Sep 17 00:00:00 2001 From: Jakob Unterwurzacher Date: Sun, 18 Sep 2016 17:10:33 +0200 Subject: [PATCH] Add "-x", "--one-file-system" option Equivalent to rsync's "-x" option. Notes to the naming: "--exclude-other-filesystems" is used by Duplicity, "--one-file-system" is used rsync and tar. This latter should be more familiar to the user. --- src/cmds/restic/cmd_backup.go | 65 +++++++++++++++++++++++++++---- src/restic/fs/deviceid_unix.go | 21 ++++++++++ src/restic/fs/deviceid_windows.go | 15 +++++++ 3 files changed, 93 insertions(+), 8 deletions(-) create mode 100644 src/restic/fs/deviceid_unix.go create mode 100644 src/restic/fs/deviceid_windows.go diff --git a/src/cmds/restic/cmd_backup.go b/src/cmds/restic/cmd_backup.go index 50f307e15..4f360c9c3 100644 --- a/src/cmds/restic/cmd_backup.go +++ b/src/cmds/restic/cmd_backup.go @@ -19,13 +19,14 @@ import ( ) type CmdBackup struct { - Parent string `short:"p" long:"parent" description:"use this parent snapshot (default: last snapshot in repo that has the same target)"` - Force bool `short:"f" long:"force" description:"Force re-reading the target. Overrides the \"parent\" flag"` - Excludes []string `short:"e" long:"exclude" description:"Exclude a pattern (can be specified multiple times)"` - ExcludeFile string `long:"exclude-file" description:"Read exclude-patterns from file"` - Stdin bool `long:"stdin" description:"read backup data from stdin"` - StdinFilename string `long:"stdin-filename" default:"stdin" description:"file name to use when reading from stdin"` - Tags []string `long:"tag" description:"Add a tag (can be specified multiple times)"` + Parent string `short:"p" long:"parent" description:"use this parent snapshot (default: last snapshot in repo that has the same target)"` + Force bool `short:"f" long:"force" description:"Force re-reading the target. Overrides the \"parent\" flag"` + Excludes []string `short:"e" long:"exclude" description:"Exclude a pattern (can be specified multiple times)"` + ExcludeOtherFS bool `short:"x" long:"one-file-system" description:"Exclude other file systems"` + ExcludeFile string `long:"exclude-file" description:"Read exclude-patterns from file"` + Stdin bool `long:"stdin" description:"read backup data from stdin"` + StdinFilename string `long:"stdin-filename" default:"stdin" description:"file name to use when reading from stdin"` + Tags []string `long:"tag" description:"Add a tag (can be specified multiple times)"` global *GlobalOptions } @@ -239,6 +240,27 @@ func filterExisting(items []string) (result []string, err error) { return } +// gatherDevices returns the set of unique device ids of the files and/or +// directory paths listed in "items". +func gatherDevices(items []string) (deviceMap map[uint64]struct{}, err error) { + deviceMap = make(map[uint64]struct{}) + 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 + } + deviceMap[id] = struct{}{} + } + if len(deviceMap) == 0 { + return nil, errors.New("zero allowed devices") + } + return deviceMap, nil +} + func (cmd CmdBackup) readFromStdin(args []string) error { if len(args) != 0 { return errors.Fatalf("when reading from stdin, no additional files can be specified") @@ -291,6 +313,16 @@ func (cmd CmdBackup) Execute(args []string) error { return err } + // allowed devices + var allowedDevs map[uint64]struct{} + if cmd.ExcludeOtherFS { + allowedDevs, err = gatherDevices(target) + if err != nil { + return err + } + debug.Log("backup.Execute", "allowed devices: %v\n", allowedDevs) + } + repo, err := cmd.global.OpenRepository() if err != nil { return err @@ -361,9 +393,26 @@ func (cmd CmdBackup) Execute(args []string) error { if matched { debug.Log("backup.Execute", "path %q excluded by a filter", item) + return false } - return !matched + if !cmd.ExcludeOtherFS { + 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) + } + _, found := allowedDevs[id] + if !found { + debug.Log("backup.Execute", "path %q on disallowed device %d", item, id) + return false + } + + return true } stat, err := archiver.Scan(target, selectFilter, cmd.newScanProgress()) diff --git a/src/restic/fs/deviceid_unix.go b/src/restic/fs/deviceid_unix.go new file mode 100644 index 000000000..684c3cc7d --- /dev/null +++ b/src/restic/fs/deviceid_unix.go @@ -0,0 +1,21 @@ +// +build !windows + +package fs + +import ( + "os" + "syscall" + + "restic/errors" +) + +// DeviceID extracts the device ID from an os.FileInfo object by casting it +// to syscall.Stat_t +func DeviceID(fi os.FileInfo) (deviceID uint64, err error) { + if st, ok := fi.Sys().(*syscall.Stat_t); ok { + // st.Dev is uint32 on Darwin and uint64 on Linux. Just cast + // everything to uint64. + return uint64(st.Dev), nil + } + return 0, errors.New("Could not cast to syscall.Stat_t") +} diff --git a/src/restic/fs/deviceid_windows.go b/src/restic/fs/deviceid_windows.go new file mode 100644 index 000000000..fd86f5daf --- /dev/null +++ b/src/restic/fs/deviceid_windows.go @@ -0,0 +1,15 @@ +// +build windows + +package fs + +import ( + "os" + + "restic/errors" +) + +// DeviceID extracts the device ID from an os.FileInfo object by casting it +// to syscall.Stat_t +func DeviceID(fi os.FileInfo) (deviceID uint64, err error) { + return 0, errors.New("Device IDs are not supported on Windows") +}