forked from TrueCloudLab/restic
backup: convert reject funcs to use FS interface
Depending on parameters the paths in a snapshot do not directly correspond to real paths on the filesystem. Therefore, reject funcs must use the FS interface to work correctly.
This commit is contained in:
parent
c6fae0320e
commit
f9dbcd2531
10 changed files with 98 additions and 88 deletions
|
@ -314,6 +314,29 @@ func collectRejectByNameFuncs(opts BackupOptions, repo *repository.Repository) (
|
||||||
}
|
}
|
||||||
fs = append(fs, fsPatterns...)
|
fs = append(fs, fsPatterns...)
|
||||||
|
|
||||||
|
return fs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// collectRejectFuncs returns a list of all functions which may reject data
|
||||||
|
// from being saved in a snapshot based on path and file info
|
||||||
|
func collectRejectFuncs(opts BackupOptions, targets []string, fs fs.FS) (funcs []RejectFunc, err error) {
|
||||||
|
// allowed devices
|
||||||
|
if opts.ExcludeOtherFS && !opts.Stdin {
|
||||||
|
f, err := rejectByDevice(targets, fs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
funcs = append(funcs, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(opts.ExcludeLargerThan) != 0 && !opts.Stdin {
|
||||||
|
f, err := rejectBySize(opts.ExcludeLargerThan)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
funcs = append(funcs, f)
|
||||||
|
}
|
||||||
|
|
||||||
if opts.ExcludeCaches {
|
if opts.ExcludeCaches {
|
||||||
opts.ExcludeIfPresent = append(opts.ExcludeIfPresent, "CACHEDIR.TAG:Signature: 8a477f597d28d172789f06886806bc55")
|
opts.ExcludeIfPresent = append(opts.ExcludeIfPresent, "CACHEDIR.TAG:Signature: 8a477f597d28d172789f06886806bc55")
|
||||||
}
|
}
|
||||||
|
@ -324,33 +347,10 @@ func collectRejectByNameFuncs(opts BackupOptions, repo *repository.Repository) (
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
fs = append(fs, f)
|
funcs = append(funcs, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
return fs, nil
|
return funcs, nil
|
||||||
}
|
|
||||||
|
|
||||||
// collectRejectFuncs returns a list of all functions which may reject data
|
|
||||||
// from being saved in a snapshot based on path and file info
|
|
||||||
func collectRejectFuncs(opts BackupOptions, targets []string) (fs []RejectFunc, err error) {
|
|
||||||
// allowed devices
|
|
||||||
if opts.ExcludeOtherFS && !opts.Stdin {
|
|
||||||
f, err := rejectByDevice(targets)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
fs = append(fs, f)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(opts.ExcludeLargerThan) != 0 && !opts.Stdin {
|
|
||||||
f, err := rejectBySize(opts.ExcludeLargerThan)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
fs = append(fs, f)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fs, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// collectTargets returns a list of target files/dirs from several sources.
|
// collectTargets returns a list of target files/dirs from several sources.
|
||||||
|
@ -505,12 +505,6 @@ func runBackup(ctx context.Context, opts BackupOptions, gopts GlobalOptions, ter
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// rejectFuncs collect functions that can reject items from the backup based on path and file info
|
|
||||||
rejectFuncs, err := collectRejectFuncs(opts, targets)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var parentSnapshot *restic.Snapshot
|
var parentSnapshot *restic.Snapshot
|
||||||
if !opts.Stdin {
|
if !opts.Stdin {
|
||||||
parentSnapshot, err = findParentSnapshot(ctx, repo, opts, targets, timeStamp)
|
parentSnapshot, err = findParentSnapshot(ctx, repo, opts, targets, timeStamp)
|
||||||
|
@ -547,15 +541,6 @@ func runBackup(ctx context.Context, opts BackupOptions, gopts GlobalOptions, ter
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
selectFilter := func(item string, fi os.FileInfo) bool {
|
|
||||||
for _, reject := range rejectFuncs {
|
|
||||||
if reject(item, fi) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
var targetFS fs.FS = fs.Local{}
|
var targetFS fs.FS = fs.Local{}
|
||||||
if runtime.GOOS == "windows" && opts.UseFsSnapshot {
|
if runtime.GOOS == "windows" && opts.UseFsSnapshot {
|
||||||
if err = fs.HasSufficientPrivilegesForVSS(); err != nil {
|
if err = fs.HasSufficientPrivilegesForVSS(); err != nil {
|
||||||
|
@ -598,6 +583,21 @@ func runBackup(ctx context.Context, opts BackupOptions, gopts GlobalOptions, ter
|
||||||
targets = []string{filename}
|
targets = []string{filename}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// rejectFuncs collect functions that can reject items from the backup based on path and file info
|
||||||
|
rejectFuncs, err := collectRejectFuncs(opts, targets, targetFS)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
selectFilter := func(item string, fi os.FileInfo, fs fs.FS) bool {
|
||||||
|
for _, reject := range rejectFuncs {
|
||||||
|
if reject(item, fi, fs) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
wg, wgCtx := errgroup.WithContext(ctx)
|
wg, wgCtx := errgroup.WithContext(ctx)
|
||||||
cancelCtx, cancel := context.WithCancel(wgCtx)
|
cancelCtx, cancel := context.WithCancel(wgCtx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
@ -72,7 +71,7 @@ type RejectByNameFunc func(path string) bool
|
||||||
// RejectFunc is a function that takes a filename and os.FileInfo of a
|
// RejectFunc is a function that takes a filename and os.FileInfo of a
|
||||||
// file that would be included in the backup. The function returns true if it
|
// file that would be included in the backup. The function returns true if it
|
||||||
// should be excluded (rejected) from the backup.
|
// should be excluded (rejected) from the backup.
|
||||||
type RejectFunc func(path string, fi os.FileInfo) bool
|
type RejectFunc func(path string, fi os.FileInfo, fs fs.FS) bool
|
||||||
|
|
||||||
// rejectByPattern returns a RejectByNameFunc which rejects files that match
|
// rejectByPattern returns a RejectByNameFunc which rejects files that match
|
||||||
// one of the patterns.
|
// one of the patterns.
|
||||||
|
@ -112,7 +111,7 @@ func rejectByInsensitivePattern(patterns []string) RejectByNameFunc {
|
||||||
// non-nil if the filename component of excludeFileSpec is empty. If rc is
|
// non-nil if the filename component of excludeFileSpec is empty. If rc is
|
||||||
// non-nil, it is going to be used in the RejectByNameFunc to expedite the evaluation
|
// non-nil, it is going to be used in the RejectByNameFunc to expedite the evaluation
|
||||||
// of a directory based on previous visits.
|
// of a directory based on previous visits.
|
||||||
func rejectIfPresent(excludeFileSpec string) (RejectByNameFunc, error) {
|
func rejectIfPresent(excludeFileSpec string) (RejectFunc, error) {
|
||||||
if excludeFileSpec == "" {
|
if excludeFileSpec == "" {
|
||||||
return nil, errors.New("name for exclusion tagfile is empty")
|
return nil, errors.New("name for exclusion tagfile is empty")
|
||||||
}
|
}
|
||||||
|
@ -129,10 +128,9 @@ func rejectIfPresent(excludeFileSpec string) (RejectByNameFunc, error) {
|
||||||
}
|
}
|
||||||
debug.Log("using %q as exclusion tagfile", tf)
|
debug.Log("using %q as exclusion tagfile", tf)
|
||||||
rc := &rejectionCache{}
|
rc := &rejectionCache{}
|
||||||
fn := func(filename string) bool {
|
return func(filename string, _ os.FileInfo, fs fs.FS) bool {
|
||||||
return isExcludedByFile(filename, tf, tc, rc)
|
return isExcludedByFile(filename, tf, tc, rc, fs)
|
||||||
}
|
}, nil
|
||||||
return fn, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// isExcludedByFile interprets filename as a path and returns true if that file
|
// isExcludedByFile interprets filename as a path and returns true if that file
|
||||||
|
@ -140,28 +138,28 @@ func rejectIfPresent(excludeFileSpec string) (RejectByNameFunc, error) {
|
||||||
// tagfile which bears the name specified in tagFilename and starts with
|
// tagfile which bears the name specified in tagFilename and starts with
|
||||||
// header. If rc is non-nil, it is used to expedite the evaluation of a
|
// header. If rc is non-nil, it is used to expedite the evaluation of a
|
||||||
// directory based on previous visits.
|
// directory based on previous visits.
|
||||||
func isExcludedByFile(filename, tagFilename, header string, rc *rejectionCache) bool {
|
func isExcludedByFile(filename, tagFilename, header string, rc *rejectionCache, fs fs.FS) bool {
|
||||||
if tagFilename == "" {
|
if tagFilename == "" {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
dir, base := filepath.Split(filename)
|
if fs.Base(filename) == tagFilename {
|
||||||
if base == tagFilename {
|
|
||||||
return false // do not exclude the tagfile itself
|
return false // do not exclude the tagfile itself
|
||||||
}
|
}
|
||||||
rc.Lock()
|
rc.Lock()
|
||||||
defer rc.Unlock()
|
defer rc.Unlock()
|
||||||
|
|
||||||
|
dir := fs.Dir(filename)
|
||||||
rejected, visited := rc.Get(dir)
|
rejected, visited := rc.Get(dir)
|
||||||
if visited {
|
if visited {
|
||||||
return rejected
|
return rejected
|
||||||
}
|
}
|
||||||
rejected = isDirExcludedByFile(dir, tagFilename, header)
|
rejected = isDirExcludedByFile(dir, tagFilename, header, fs)
|
||||||
rc.Store(dir, rejected)
|
rc.Store(dir, rejected)
|
||||||
return rejected
|
return rejected
|
||||||
}
|
}
|
||||||
|
|
||||||
func isDirExcludedByFile(dir, tagFilename, header string) bool {
|
func isDirExcludedByFile(dir, tagFilename, header string, fs fs.FS) bool {
|
||||||
tf := filepath.Join(dir, tagFilename)
|
tf := fs.Join(dir, tagFilename)
|
||||||
_, err := fs.Lstat(tf)
|
_, err := fs.Lstat(tf)
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
return false
|
return false
|
||||||
|
@ -178,7 +176,7 @@ func isDirExcludedByFile(dir, tagFilename, header string) bool {
|
||||||
// From this stage, errors mean tagFilename exists but it is malformed.
|
// From this stage, errors mean tagFilename exists but it is malformed.
|
||||||
// Warnings will be generated so that the user is informed that the
|
// Warnings will be generated so that the user is informed that the
|
||||||
// indented ignore-action is not performed.
|
// indented ignore-action is not performed.
|
||||||
f, err := os.Open(tf)
|
f, err := fs.OpenFile(tf, os.O_RDONLY, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Warnf("could not open exclusion tagfile: %v", err)
|
Warnf("could not open exclusion tagfile: %v", err)
|
||||||
return false
|
return false
|
||||||
|
@ -210,11 +208,11 @@ func isDirExcludedByFile(dir, tagFilename, header string) bool {
|
||||||
type DeviceMap map[string]uint64
|
type DeviceMap map[string]uint64
|
||||||
|
|
||||||
// NewDeviceMap creates a new device map from the list of source paths.
|
// NewDeviceMap creates a new device map from the list of source paths.
|
||||||
func NewDeviceMap(allowedSourcePaths []string) (DeviceMap, error) {
|
func NewDeviceMap(allowedSourcePaths []string, fs fs.FS) (DeviceMap, error) {
|
||||||
deviceMap := make(map[string]uint64)
|
deviceMap := make(map[string]uint64)
|
||||||
|
|
||||||
for _, item := range allowedSourcePaths {
|
for _, item := range allowedSourcePaths {
|
||||||
item, err := filepath.Abs(filepath.Clean(item))
|
item, err := fs.Abs(fs.Clean(item))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -240,15 +238,15 @@ func NewDeviceMap(allowedSourcePaths []string) (DeviceMap, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsAllowed returns true if the path is located on an allowed device.
|
// IsAllowed returns true if the path is located on an allowed device.
|
||||||
func (m DeviceMap) IsAllowed(item string, deviceID uint64) (bool, error) {
|
func (m DeviceMap) IsAllowed(item string, deviceID uint64, fs fs.FS) (bool, error) {
|
||||||
for dir := item; ; dir = filepath.Dir(dir) {
|
for dir := item; ; dir = fs.Dir(dir) {
|
||||||
debug.Log("item %v, test dir %v", item, dir)
|
debug.Log("item %v, test dir %v", item, dir)
|
||||||
|
|
||||||
// find a parent directory that is on an allowed device (otherwise
|
// find a parent directory that is on an allowed device (otherwise
|
||||||
// we would not traverse the directory at all)
|
// we would not traverse the directory at all)
|
||||||
allowedID, ok := m[dir]
|
allowedID, ok := m[dir]
|
||||||
if !ok {
|
if !ok {
|
||||||
if dir == filepath.Dir(dir) {
|
if dir == fs.Dir(dir) {
|
||||||
// arrived at root, no allowed device found. this should not happen.
|
// arrived at root, no allowed device found. this should not happen.
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -272,14 +270,14 @@ func (m DeviceMap) IsAllowed(item string, deviceID uint64) (bool, error) {
|
||||||
|
|
||||||
// rejectByDevice returns a RejectFunc that rejects files which are on a
|
// rejectByDevice returns a RejectFunc that rejects files which are on a
|
||||||
// different file systems than the files/dirs in samples.
|
// different file systems than the files/dirs in samples.
|
||||||
func rejectByDevice(samples []string) (RejectFunc, error) {
|
func rejectByDevice(samples []string, filesystem fs.FS) (RejectFunc, error) {
|
||||||
deviceMap, err := NewDeviceMap(samples)
|
deviceMap, err := NewDeviceMap(samples, filesystem)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
debug.Log("allowed devices: %v\n", deviceMap)
|
debug.Log("allowed devices: %v\n", deviceMap)
|
||||||
|
|
||||||
return func(item string, fi os.FileInfo) bool {
|
return func(item string, fi os.FileInfo, fs fs.FS) bool {
|
||||||
id, err := fs.DeviceID(fi)
|
id, err := fs.DeviceID(fi)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// This should never happen because gatherDevices() would have
|
// This should never happen because gatherDevices() would have
|
||||||
|
@ -287,7 +285,7 @@ func rejectByDevice(samples []string) (RejectFunc, error) {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
allowed, err := deviceMap.IsAllowed(filepath.Clean(item), id)
|
allowed, err := deviceMap.IsAllowed(fs.Clean(item), id, fs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// this should not happen
|
// this should not happen
|
||||||
panic(fmt.Sprintf("error checking device ID of %v: %v", item, err))
|
panic(fmt.Sprintf("error checking device ID of %v: %v", item, err))
|
||||||
|
@ -306,7 +304,7 @@ func rejectByDevice(samples []string) (RejectFunc, error) {
|
||||||
// special case: make sure we keep mountpoints (directories which
|
// special case: make sure we keep mountpoints (directories which
|
||||||
// contain a mounted file system). Test this by checking if the parent
|
// contain a mounted file system). Test this by checking if the parent
|
||||||
// directory would be included.
|
// directory would be included.
|
||||||
parentDir := filepath.Dir(filepath.Clean(item))
|
parentDir := fs.Dir(fs.Clean(item))
|
||||||
|
|
||||||
parentFI, err := fs.Lstat(parentDir)
|
parentFI, err := fs.Lstat(parentDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -322,7 +320,7 @@ func rejectByDevice(samples []string) (RejectFunc, error) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
parentAllowed, err := deviceMap.IsAllowed(parentDir, parentDeviceID)
|
parentAllowed, err := deviceMap.IsAllowed(parentDir, parentDeviceID, fs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
debug.Log("item %v: error checking parent directory: %v", item, err)
|
debug.Log("item %v: error checking parent directory: %v", item, err)
|
||||||
// if in doubt, reject
|
// if in doubt, reject
|
||||||
|
@ -369,7 +367,7 @@ func rejectBySize(maxSizeStr string) (RejectFunc, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return func(item string, fi os.FileInfo) bool {
|
return func(item string, fi os.FileInfo, _ fs.FS) bool {
|
||||||
// directory will be ignored
|
// directory will be ignored
|
||||||
if fi.IsDir() {
|
if fi.IsDir() {
|
||||||
return false
|
return false
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/fs"
|
||||||
"github.com/restic/restic/internal/test"
|
"github.com/restic/restic/internal/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -102,7 +103,7 @@ func TestIsExcludedByFile(t *testing.T) {
|
||||||
if tc.content == "" {
|
if tc.content == "" {
|
||||||
h = ""
|
h = ""
|
||||||
}
|
}
|
||||||
if got := isExcludedByFile(foo, tagFilename, h, nil); tc.want != got {
|
if got := isExcludedByFile(foo, tagFilename, h, nil, &fs.Local{}); tc.want != got {
|
||||||
t.Fatalf("expected %v, got %v", tc.want, got)
|
t.Fatalf("expected %v, got %v", tc.want, got)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -164,8 +165,8 @@ func TestMultipleIsExcludedByFile(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
excludedByFoo := fooExclude(p)
|
excludedByFoo := fooExclude(p, nil, &fs.Local{})
|
||||||
excludedByBar := barExclude(p)
|
excludedByBar := barExclude(p, nil, &fs.Local{})
|
||||||
excluded := excludedByFoo || excludedByBar
|
excluded := excludedByFoo || excludedByBar
|
||||||
// the log message helps debugging in case the test fails
|
// the log message helps debugging in case the test fails
|
||||||
t.Logf("%q: %v || %v = %v", p, excludedByFoo, excludedByBar, excluded)
|
t.Logf("%q: %v || %v = %v", p, excludedByFoo, excludedByBar, excluded)
|
||||||
|
@ -249,7 +250,7 @@ func TestIsExcludedByFileSize(t *testing.T) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
excluded := sizeExclude(p, fi)
|
excluded := sizeExclude(p, fi, nil)
|
||||||
// the log message helps debugging in case the test fails
|
// the log message helps debugging in case the test fails
|
||||||
t.Logf("%q: dir:%t; size:%d; excluded:%v", p, fi.IsDir(), fi.Size(), excluded)
|
t.Logf("%q: dir:%t; size:%d; excluded:%v", p, fi.IsDir(), fi.Size(), excluded)
|
||||||
m[p] = !excluded
|
m[p] = !excluded
|
||||||
|
@ -299,7 +300,7 @@ func TestDeviceMap(t *testing.T) {
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run("", func(t *testing.T) {
|
t.Run("", func(t *testing.T) {
|
||||||
res, err := deviceMap.IsAllowed(filepath.FromSlash(test.item), test.deviceID)
|
res, err := deviceMap.IsAllowed(filepath.FromSlash(test.item), test.deviceID, &fs.Local{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ type SelectByNameFunc func(item string) bool
|
||||||
|
|
||||||
// SelectFunc returns true for all items that should be included (files and
|
// SelectFunc returns true for all items that should be included (files and
|
||||||
// dirs). If false is returned, files are ignored and dirs are not even walked.
|
// dirs). If false is returned, files are ignored and dirs are not even walked.
|
||||||
type SelectFunc func(item string, fi os.FileInfo) bool
|
type SelectFunc func(item string, fi os.FileInfo, fs fs.FS) bool
|
||||||
|
|
||||||
// ErrorFunc is called when an error during archiving occurs. When nil is
|
// ErrorFunc is called when an error during archiving occurs. When nil is
|
||||||
// returned, the archiver continues, otherwise it aborts and passes the error
|
// returned, the archiver continues, otherwise it aborts and passes the error
|
||||||
|
@ -178,12 +178,12 @@ func (o Options) ApplyDefaults() Options {
|
||||||
}
|
}
|
||||||
|
|
||||||
// New initializes a new archiver.
|
// New initializes a new archiver.
|
||||||
func New(repo archiverRepo, fs fs.FS, opts Options) *Archiver {
|
func New(repo archiverRepo, filesystem fs.FS, opts Options) *Archiver {
|
||||||
arch := &Archiver{
|
arch := &Archiver{
|
||||||
Repo: repo,
|
Repo: repo,
|
||||||
SelectByName: func(_ string) bool { return true },
|
SelectByName: func(_ string) bool { return true },
|
||||||
Select: func(_ string, _ os.FileInfo) bool { return true },
|
Select: func(_ string, _ os.FileInfo, _ fs.FS) bool { return true },
|
||||||
FS: fs,
|
FS: filesystem,
|
||||||
Options: opts.ApplyDefaults(),
|
Options: opts.ApplyDefaults(),
|
||||||
|
|
||||||
CompleteItem: func(string, *restic.Node, *restic.Node, ItemStats, time.Duration) {},
|
CompleteItem: func(string, *restic.Node, *restic.Node, ItemStats, time.Duration) {},
|
||||||
|
@ -448,7 +448,7 @@ func (arch *Archiver) save(ctx context.Context, snPath, target string, previous
|
||||||
}
|
}
|
||||||
return futureNode{}, true, nil
|
return futureNode{}, true, nil
|
||||||
}
|
}
|
||||||
if !arch.Select(abstarget, fi) {
|
if !arch.Select(abstarget, fi, arch.FS) {
|
||||||
debug.Log("%v is excluded", target)
|
debug.Log("%v is excluded", target)
|
||||||
return futureNode{}, true, nil
|
return futureNode{}, true, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1529,7 +1529,7 @@ func TestArchiverSnapshotSelect(t *testing.T) {
|
||||||
},
|
},
|
||||||
"other": TestFile{Content: "another file"},
|
"other": TestFile{Content: "another file"},
|
||||||
},
|
},
|
||||||
selFn: func(item string, fi os.FileInfo) bool {
|
selFn: func(item string, fi os.FileInfo, _ fs.FS) bool {
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1546,7 +1546,7 @@ func TestArchiverSnapshotSelect(t *testing.T) {
|
||||||
},
|
},
|
||||||
"other": TestFile{Content: "another file"},
|
"other": TestFile{Content: "another file"},
|
||||||
},
|
},
|
||||||
selFn: func(item string, fi os.FileInfo) bool {
|
selFn: func(item string, fi os.FileInfo, _ fs.FS) bool {
|
||||||
return false
|
return false
|
||||||
},
|
},
|
||||||
err: "snapshot is empty",
|
err: "snapshot is empty",
|
||||||
|
@ -1573,7 +1573,7 @@ func TestArchiverSnapshotSelect(t *testing.T) {
|
||||||
},
|
},
|
||||||
"other": TestFile{Content: "another file"},
|
"other": TestFile{Content: "another file"},
|
||||||
},
|
},
|
||||||
selFn: func(item string, fi os.FileInfo) bool {
|
selFn: func(item string, fi os.FileInfo, _ fs.FS) bool {
|
||||||
return filepath.Ext(item) != ".txt"
|
return filepath.Ext(item) != ".txt"
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1597,8 +1597,8 @@ func TestArchiverSnapshotSelect(t *testing.T) {
|
||||||
},
|
},
|
||||||
"other": TestFile{Content: "another file"},
|
"other": TestFile{Content: "another file"},
|
||||||
},
|
},
|
||||||
selFn: func(item string, fi os.FileInfo) bool {
|
selFn: func(item string, fi os.FileInfo, fs fs.FS) bool {
|
||||||
return filepath.Base(item) != "subdir"
|
return fs.Base(item) != "subdir"
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1606,8 +1606,8 @@ func TestArchiverSnapshotSelect(t *testing.T) {
|
||||||
src: TestDir{
|
src: TestDir{
|
||||||
"foo": TestFile{Content: "foo"},
|
"foo": TestFile{Content: "foo"},
|
||||||
},
|
},
|
||||||
selFn: func(item string, fi os.FileInfo) bool {
|
selFn: func(item string, fi os.FileInfo, fs fs.FS) bool {
|
||||||
return filepath.IsAbs(item)
|
return fs.IsAbs(item)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,11 +22,11 @@ type Scanner struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewScanner initializes a new Scanner.
|
// NewScanner initializes a new Scanner.
|
||||||
func NewScanner(fs fs.FS) *Scanner {
|
func NewScanner(filesystem fs.FS) *Scanner {
|
||||||
return &Scanner{
|
return &Scanner{
|
||||||
FS: fs,
|
FS: filesystem,
|
||||||
SelectByName: func(_ string) bool { return true },
|
SelectByName: func(_ string) bool { return true },
|
||||||
Select: func(_ string, _ os.FileInfo) bool { return true },
|
Select: func(_ string, _ os.FileInfo, _ fs.FS) bool { return true },
|
||||||
Error: func(_ string, err error) error { return err },
|
Error: func(_ string, err error) error { return err },
|
||||||
Result: func(_ string, _ ScanStats) {},
|
Result: func(_ string, _ ScanStats) {},
|
||||||
}
|
}
|
||||||
|
@ -115,7 +115,7 @@ func (s *Scanner) scan(ctx context.Context, stats ScanStats, target string) (Sca
|
||||||
}
|
}
|
||||||
|
|
||||||
// run remaining select functions that require file information
|
// run remaining select functions that require file information
|
||||||
if !s.Select(target, fi) {
|
if !s.Select(target, fi, s.FS) {
|
||||||
return stats, nil
|
return stats, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -56,7 +56,7 @@ func TestScanner(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
selFn: func(item string, fi os.FileInfo) bool {
|
selFn: func(item string, fi os.FileInfo, fs fs.FS) bool {
|
||||||
if fi.IsDir() {
|
if fi.IsDir() {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,12 @@ func (fs Local) Lstat(name string) (os.FileInfo, error) {
|
||||||
return os.Lstat(fixpath(name))
|
return os.Lstat(fixpath(name))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeviceID extracts the DeviceID from the given FileInfo. If the fs does
|
||||||
|
// not support a DeviceID, it returns an error instead
|
||||||
|
func (fs Local) DeviceID(fi os.FileInfo) (deviceID uint64, err error) {
|
||||||
|
return DeviceID(fi)
|
||||||
|
}
|
||||||
|
|
||||||
// Join joins any number of path elements into a single path, adding a
|
// Join joins any number of path elements into a single path, adding a
|
||||||
// Separator if necessary. Join calls Clean on the result; in particular, all
|
// Separator if necessary. Join calls Clean on the result; in particular, all
|
||||||
// empty strings are ignored. On Windows, the result is a UNC path if and only
|
// empty strings are ignored. On Windows, the result is a UNC path if and only
|
||||||
|
|
|
@ -122,6 +122,10 @@ func (fs *Reader) Lstat(name string) (os.FileInfo, error) {
|
||||||
return nil, pathError("lstat", name, os.ErrNotExist)
|
return nil, pathError("lstat", name, os.ErrNotExist)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (fs *Reader) DeviceID(_ os.FileInfo) (deviceID uint64, err error) {
|
||||||
|
return 0, errors.New("Device IDs are not supported")
|
||||||
|
}
|
||||||
|
|
||||||
// Join joins any number of path elements into a single path, adding a
|
// Join joins any number of path elements into a single path, adding a
|
||||||
// Separator if necessary. Join calls Clean on the result; in particular, all
|
// Separator if necessary. Join calls Clean on the result; in particular, all
|
||||||
// empty strings are ignored. On Windows, the result is a UNC path if and only
|
// empty strings are ignored. On Windows, the result is a UNC path if and only
|
||||||
|
|
|
@ -10,6 +10,7 @@ type FS interface {
|
||||||
OpenFile(name string, flag int, perm os.FileMode) (File, error)
|
OpenFile(name string, flag int, perm os.FileMode) (File, error)
|
||||||
Stat(name string) (os.FileInfo, error)
|
Stat(name string) (os.FileInfo, error)
|
||||||
Lstat(name string) (os.FileInfo, error)
|
Lstat(name string) (os.FileInfo, error)
|
||||||
|
DeviceID(fi os.FileInfo) (deviceID uint64, err error)
|
||||||
|
|
||||||
Join(elem ...string) string
|
Join(elem ...string) string
|
||||||
Separator() string
|
Separator() string
|
||||||
|
|
Loading…
Reference in a new issue