mirror of
https://github.com/restic/restic.git
synced 2025-08-26 09:28:01 +00:00
fs: inline ExtendedStat
This commit is contained in:
@@ -25,7 +25,7 @@ type SelectByNameFunc func(item string) bool
|
||||
|
||||
// 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.
|
||||
type SelectFunc func(item string, fi os.FileInfo, fs fs.FS) bool
|
||||
type SelectFunc func(item string, fi *fs.ExtendedFileInfo, fs fs.FS) bool
|
||||
|
||||
// ErrorFunc is called when an error during archiving occurs. When nil is
|
||||
// returned, the archiver continues, otherwise it aborts and passes the error
|
||||
@@ -189,7 +189,7 @@ func New(repo archiverRepo, filesystem fs.FS, opts Options) *Archiver {
|
||||
arch := &Archiver{
|
||||
Repo: repo,
|
||||
SelectByName: func(_ string) bool { return true },
|
||||
Select: func(_ string, _ os.FileInfo, _ fs.FS) bool { return true },
|
||||
Select: func(_ string, _ *fs.ExtendedFileInfo, _ fs.FS) bool { return true },
|
||||
FS: filesystem,
|
||||
Options: opts.ApplyDefaults(),
|
||||
|
||||
@@ -618,27 +618,26 @@ func (arch *Archiver) save(ctx context.Context, snPath, target string, previous
|
||||
// fileChanged tries to detect whether a file's content has changed compared
|
||||
// to the contents of node, which describes the same path in the parent backup.
|
||||
// It should only be run for regular files.
|
||||
func fileChanged(fs fs.FS, fi os.FileInfo, node *restic.Node, ignoreFlags uint) bool {
|
||||
func fileChanged(fs fs.FS, fi *fs.ExtendedFileInfo, node *restic.Node, ignoreFlags uint) bool {
|
||||
switch {
|
||||
case node == nil:
|
||||
return true
|
||||
case node.Type != restic.NodeTypeFile:
|
||||
// We're only called for regular files, so this is a type change.
|
||||
return true
|
||||
case uint64(fi.Size()) != node.Size:
|
||||
case uint64(fi.Size) != node.Size:
|
||||
return true
|
||||
case !fi.ModTime().Equal(node.ModTime):
|
||||
case !fi.ModTime.Equal(node.ModTime):
|
||||
return true
|
||||
}
|
||||
|
||||
checkCtime := ignoreFlags&ChangeIgnoreCtime == 0
|
||||
checkInode := ignoreFlags&ChangeIgnoreInode == 0
|
||||
|
||||
extFI := fs.ExtendedStat(fi)
|
||||
switch {
|
||||
case checkCtime && !extFI.ChangeTime.Equal(node.ChangeTime):
|
||||
case checkCtime && !fi.ChangeTime.Equal(node.ChangeTime):
|
||||
return true
|
||||
case checkInode && node.Inode != extFI.Inode:
|
||||
case checkInode && node.Inode != fi.Inode:
|
||||
return true
|
||||
}
|
||||
|
||||
|
@@ -516,13 +516,13 @@ func chmodTwice(t testing.TB, name string) {
|
||||
rtest.OK(t, err)
|
||||
}
|
||||
|
||||
func lstat(t testing.TB, name string) os.FileInfo {
|
||||
func lstat(t testing.TB, name string) *fs.ExtendedFileInfo {
|
||||
fi, err := os.Lstat(name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return fi
|
||||
return fs.ExtendedStat(fi)
|
||||
}
|
||||
|
||||
func setTimestamp(t testing.TB, filename string, atime, mtime time.Time) {
|
||||
@@ -660,7 +660,7 @@ func TestFileChanged(t *testing.T) {
|
||||
rename(t, filename, tempname)
|
||||
save(t, filename, defaultContent)
|
||||
remove(t, tempname)
|
||||
setTimestamp(t, filename, fi.ModTime(), fi.ModTime())
|
||||
setTimestamp(t, filename, fi.ModTime, fi.ModTime)
|
||||
},
|
||||
ChangeIgnore: ChangeIgnoreCtime | ChangeIgnoreInode,
|
||||
SameFile: true,
|
||||
@@ -1520,7 +1520,7 @@ func TestArchiverSnapshotSelect(t *testing.T) {
|
||||
},
|
||||
"other": TestFile{Content: "another file"},
|
||||
},
|
||||
selFn: func(item string, fi os.FileInfo, _ fs.FS) bool {
|
||||
selFn: func(item string, fi *fs.ExtendedFileInfo, _ fs.FS) bool {
|
||||
return true
|
||||
},
|
||||
},
|
||||
@@ -1537,7 +1537,7 @@ func TestArchiverSnapshotSelect(t *testing.T) {
|
||||
},
|
||||
"other": TestFile{Content: "another file"},
|
||||
},
|
||||
selFn: func(item string, fi os.FileInfo, _ fs.FS) bool {
|
||||
selFn: func(item string, fi *fs.ExtendedFileInfo, _ fs.FS) bool {
|
||||
return false
|
||||
},
|
||||
err: "snapshot is empty",
|
||||
@@ -1564,7 +1564,7 @@ func TestArchiverSnapshotSelect(t *testing.T) {
|
||||
},
|
||||
"other": TestFile{Content: "another file"},
|
||||
},
|
||||
selFn: func(item string, fi os.FileInfo, _ fs.FS) bool {
|
||||
selFn: func(item string, fi *fs.ExtendedFileInfo, _ fs.FS) bool {
|
||||
return filepath.Ext(item) != ".txt"
|
||||
},
|
||||
},
|
||||
@@ -1588,7 +1588,7 @@ func TestArchiverSnapshotSelect(t *testing.T) {
|
||||
},
|
||||
"other": TestFile{Content: "another file"},
|
||||
},
|
||||
selFn: func(item string, fi os.FileInfo, fs fs.FS) bool {
|
||||
selFn: func(item string, fi *fs.ExtendedFileInfo, fs fs.FS) bool {
|
||||
return fs.Base(item) != "subdir"
|
||||
},
|
||||
},
|
||||
@@ -1597,7 +1597,7 @@ func TestArchiverSnapshotSelect(t *testing.T) {
|
||||
src: TestDir{
|
||||
"foo": TestFile{Content: "foo"},
|
||||
},
|
||||
selFn: func(item string, fi os.FileInfo, fs fs.FS) bool {
|
||||
selFn: func(item string, fi *fs.ExtendedFileInfo, fs fs.FS) bool {
|
||||
return fs.IsAbs(item)
|
||||
},
|
||||
},
|
||||
@@ -2202,7 +2202,7 @@ func snapshot(t testing.TB, repo archiverRepo, fs fs.FS, parent *restic.Snapshot
|
||||
|
||||
type overrideFS struct {
|
||||
fs.FS
|
||||
overrideFI os.FileInfo
|
||||
overrideFI *fs.ExtendedFileInfo
|
||||
resetFIOnRead bool
|
||||
overrideNode *restic.Node
|
||||
overrideErr error
|
||||
@@ -2225,7 +2225,7 @@ type overrideFile struct {
|
||||
ofs *overrideFS
|
||||
}
|
||||
|
||||
func (f overrideFile) Stat() (os.FileInfo, error) {
|
||||
func (f overrideFile) Stat() (*fs.ExtendedFileInfo, error) {
|
||||
if f.ofs.overrideFI == nil {
|
||||
return f.File.Stat()
|
||||
}
|
||||
@@ -2497,7 +2497,7 @@ type missingFile struct {
|
||||
fs.File
|
||||
}
|
||||
|
||||
func (f *missingFile) Stat() (os.FileInfo, error) {
|
||||
func (f *missingFile) Stat() (*fs.ExtendedFileInfo, error) {
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
|
||||
|
@@ -29,7 +29,7 @@ func (fi wrappedFileInfo) Mode() os.FileMode {
|
||||
}
|
||||
|
||||
// wrapFileInfo returns a new os.FileInfo with the mode, owner, and group fields changed.
|
||||
func wrapFileInfo(fi os.FileInfo) os.FileInfo {
|
||||
func wrapFileInfo(fi *fs.ExtendedFileInfo) *fs.ExtendedFileInfo {
|
||||
// get the underlying stat_t and modify the values
|
||||
stat := fi.Sys().(*syscall.Stat_t)
|
||||
stat.Mode = mockFileInfoMode
|
||||
@@ -37,22 +37,22 @@ func wrapFileInfo(fi os.FileInfo) os.FileInfo {
|
||||
stat.Gid = mockFileInfoGID
|
||||
|
||||
// wrap the os.FileInfo so we can return a modified stat_t
|
||||
res := wrappedFileInfo{
|
||||
FileInfo: fi,
|
||||
return fs.ExtendedStat(wrappedFileInfo{
|
||||
FileInfo: fi.FileInfo,
|
||||
sys: stat,
|
||||
mode: mockFileInfoMode,
|
||||
}
|
||||
|
||||
return res
|
||||
})
|
||||
}
|
||||
|
||||
// wrapIrregularFileInfo returns a new os.FileInfo with the mode changed to irregular file
|
||||
func wrapIrregularFileInfo(fi os.FileInfo) os.FileInfo {
|
||||
func wrapIrregularFileInfo(fi *fs.ExtendedFileInfo) *fs.ExtendedFileInfo {
|
||||
// wrap the os.FileInfo so we can return a modified stat_t
|
||||
return wrappedFileInfo{
|
||||
FileInfo: fi,
|
||||
sys: fi.Sys().(*syscall.Stat_t),
|
||||
mode: (fi.Mode() &^ os.ModeType) | os.ModeIrregular,
|
||||
return &fs.ExtendedFileInfo{
|
||||
FileInfo: wrappedFileInfo{
|
||||
FileInfo: fi.FileInfo,
|
||||
sys: fi.Sys(),
|
||||
mode: (fi.Mode() &^ os.ModeType) | os.ModeIrregular,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -5,6 +5,8 @@ package archiver
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/restic/restic/internal/fs"
|
||||
)
|
||||
|
||||
type wrappedFileInfo struct {
|
||||
@@ -17,20 +19,20 @@ func (fi wrappedFileInfo) Mode() os.FileMode {
|
||||
}
|
||||
|
||||
// wrapFileInfo returns a new os.FileInfo with the mode, owner, and group fields changed.
|
||||
func wrapFileInfo(fi os.FileInfo) os.FileInfo {
|
||||
func wrapFileInfo(fi *fs.ExtendedFileInfo) *fs.ExtendedFileInfo {
|
||||
// wrap the os.FileInfo and return the modified mode, uid and gid are ignored on Windows
|
||||
res := wrappedFileInfo{
|
||||
FileInfo: fi,
|
||||
return fs.ExtendedStat(wrappedFileInfo{
|
||||
FileInfo: fi.FileInfo,
|
||||
mode: mockFileInfoMode,
|
||||
}
|
||||
|
||||
return res
|
||||
})
|
||||
}
|
||||
|
||||
// wrapIrregularFileInfo returns a new os.FileInfo with the mode changed to irregular file
|
||||
func wrapIrregularFileInfo(fi os.FileInfo) os.FileInfo {
|
||||
return wrappedFileInfo{
|
||||
FileInfo: fi,
|
||||
mode: (fi.Mode() &^ os.ModeType) | os.ModeIrregular,
|
||||
func wrapIrregularFileInfo(fi *fs.ExtendedFileInfo) *fs.ExtendedFileInfo {
|
||||
return &fs.ExtendedFileInfo{
|
||||
FileInfo: wrappedFileInfo{
|
||||
FileInfo: fi.FileInfo,
|
||||
mode: (fi.Mode() &^ os.ModeType) | os.ModeIrregular,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
@@ -21,7 +22,7 @@ type RejectByNameFunc func(path string) bool
|
||||
// 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
|
||||
// should be excluded (rejected) from the backup.
|
||||
type RejectFunc func(path string, fi os.FileInfo, fs fs.FS) bool
|
||||
type RejectFunc func(path string, fi *fs.ExtendedFileInfo, fs fs.FS) bool
|
||||
|
||||
func CombineRejectByNames(funcs []RejectByNameFunc) SelectByNameFunc {
|
||||
return func(item string) bool {
|
||||
@@ -35,7 +36,7 @@ func CombineRejectByNames(funcs []RejectByNameFunc) SelectByNameFunc {
|
||||
}
|
||||
|
||||
func CombineRejects(funcs []RejectFunc) SelectFunc {
|
||||
return func(item string, fi os.FileInfo, fs fs.FS) bool {
|
||||
return func(item string, fi *fs.ExtendedFileInfo, fs fs.FS) bool {
|
||||
for _, reject := range funcs {
|
||||
if reject(item, fi, fs) {
|
||||
return false
|
||||
@@ -104,7 +105,7 @@ func RejectIfPresent(excludeFileSpec string, warnf func(msg string, args ...inte
|
||||
}
|
||||
debug.Log("using %q as exclusion tagfile", tf)
|
||||
rc := newRejectionCache()
|
||||
return func(filename string, _ os.FileInfo, fs fs.FS) bool {
|
||||
return func(filename string, _ *fs.ExtendedFileInfo, fs fs.FS) bool {
|
||||
return isExcludedByFile(filename, tf, tc, rc, fs, warnf)
|
||||
}, nil
|
||||
}
|
||||
@@ -186,6 +187,10 @@ type deviceMap map[string]uint64
|
||||
|
||||
// newDeviceMap creates a new device map from the list of source paths.
|
||||
func newDeviceMap(allowedSourcePaths []string, fs fs.FS) (deviceMap, error) {
|
||||
if runtime.GOOS == "windows" {
|
||||
return nil, errors.New("Device IDs are not supported on Windows")
|
||||
}
|
||||
|
||||
deviceMap := make(map[string]uint64)
|
||||
|
||||
for _, item := range allowedSourcePaths {
|
||||
@@ -199,12 +204,7 @@ func newDeviceMap(allowedSourcePaths []string, fs fs.FS) (deviceMap, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
id, err := fs.DeviceID(fi)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
deviceMap[item] = id
|
||||
deviceMap[item] = fi.DeviceID
|
||||
}
|
||||
|
||||
if len(deviceMap) == 0 {
|
||||
@@ -254,15 +254,8 @@ func RejectByDevice(samples []string, filesystem fs.FS) (RejectFunc, error) {
|
||||
}
|
||||
debug.Log("allowed devices: %v\n", deviceMap)
|
||||
|
||||
return func(item string, fi os.FileInfo, fs fs.FS) bool {
|
||||
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)
|
||||
}
|
||||
|
||||
allowed, err := deviceMap.IsAllowed(fs.Clean(item), id, fs)
|
||||
return func(item string, fi *fs.ExtendedFileInfo, fs fs.FS) bool {
|
||||
allowed, err := deviceMap.IsAllowed(fs.Clean(item), fi.DeviceID, fs)
|
||||
if err != nil {
|
||||
// this should not happen
|
||||
panic(fmt.Sprintf("error checking device ID of %v: %v", item, err))
|
||||
@@ -290,14 +283,7 @@ func RejectByDevice(samples []string, filesystem fs.FS) (RejectFunc, error) {
|
||||
return true
|
||||
}
|
||||
|
||||
parentDeviceID, err := fs.DeviceID(parentFI)
|
||||
if err != nil {
|
||||
debug.Log("item %v: getting device ID of parent directory: %v", item, err)
|
||||
// if in doubt, reject
|
||||
return true
|
||||
}
|
||||
|
||||
parentAllowed, err := deviceMap.IsAllowed(parentDir, parentDeviceID, fs)
|
||||
parentAllowed, err := deviceMap.IsAllowed(parentDir, parentFI.DeviceID, fs)
|
||||
if err != nil {
|
||||
debug.Log("item %v: error checking parent directory: %v", item, err)
|
||||
// if in doubt, reject
|
||||
@@ -315,13 +301,13 @@ func RejectByDevice(samples []string, filesystem fs.FS) (RejectFunc, error) {
|
||||
}
|
||||
|
||||
func RejectBySize(maxSize int64) (RejectFunc, error) {
|
||||
return func(item string, fi os.FileInfo, _ fs.FS) bool {
|
||||
return func(item string, fi *fs.ExtendedFileInfo, _ fs.FS) bool {
|
||||
// directory will be ignored
|
||||
if fi.IsDir() {
|
||||
return false
|
||||
}
|
||||
|
||||
filesize := fi.Size()
|
||||
filesize := fi.Size
|
||||
if filesize > maxSize {
|
||||
debug.Log("file %s is oversize: %d", item, filesize)
|
||||
return true
|
||||
|
@@ -193,7 +193,7 @@ func TestIsExcludedByFileSize(t *testing.T) {
|
||||
return err
|
||||
}
|
||||
|
||||
excluded := sizeExclude(p, fi, nil)
|
||||
excluded := sizeExclude(p, fs.ExtendedStat(fi), nil)
|
||||
// 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)
|
||||
m[p] = !excluded
|
||||
|
@@ -2,7 +2,6 @@ package archiver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"sort"
|
||||
|
||||
"github.com/restic/restic/internal/debug"
|
||||
@@ -25,7 +24,7 @@ func NewScanner(filesystem fs.FS) *Scanner {
|
||||
return &Scanner{
|
||||
FS: filesystem,
|
||||
SelectByName: func(_ string) bool { return true },
|
||||
Select: func(_ string, _ os.FileInfo, _ fs.FS) bool { return true },
|
||||
Select: func(_ string, _ *fs.ExtendedFileInfo, _ fs.FS) bool { return true },
|
||||
Error: func(_ string, err error) error { return err },
|
||||
Result: func(_ string, _ ScanStats) {},
|
||||
}
|
||||
@@ -121,7 +120,7 @@ func (s *Scanner) scan(ctx context.Context, stats ScanStats, target string) (Sca
|
||||
switch {
|
||||
case fi.Mode().IsRegular():
|
||||
stats.Files++
|
||||
stats.Bytes += uint64(fi.Size())
|
||||
stats.Bytes += uint64(fi.Size)
|
||||
case fi.Mode().IsDir():
|
||||
names, err := fs.Readdirnames(s.FS, target, fs.O_NOFOLLOW)
|
||||
if err != nil {
|
||||
|
@@ -56,7 +56,7 @@ func TestScanner(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
selFn: func(item string, fi os.FileInfo, fs fs.FS) bool {
|
||||
selFn: func(item string, fi *fs.ExtendedFileInfo, fs fs.FS) bool {
|
||||
if fi.IsDir() {
|
||||
return true
|
||||
}
|
||||
|
Reference in New Issue
Block a user