forked from TrueCloudLab/restic
fs: move NodeFromFileInfo into FS interface
This commit is contained in:
parent
c3b3120e10
commit
75711446e1
12 changed files with 69 additions and 41 deletions
|
@ -256,7 +256,7 @@ func (arch *Archiver) trackItem(item string, previous, current *restic.Node, s I
|
|||
|
||||
// nodeFromFileInfo returns the restic node from an os.FileInfo.
|
||||
func (arch *Archiver) nodeFromFileInfo(snPath, filename string, fi os.FileInfo, ignoreXattrListError bool) (*restic.Node, error) {
|
||||
node, err := fs.NodeFromFileInfo(filename, fi, ignoreXattrListError)
|
||||
node, err := arch.FS.NodeFromFileInfo(filename, fi, ignoreXattrListError)
|
||||
if !arch.WithAtime {
|
||||
node.AccessTime = node.ModTime
|
||||
}
|
||||
|
|
|
@ -556,7 +556,7 @@ func rename(t testing.TB, oldname, newname string) {
|
|||
}
|
||||
}
|
||||
|
||||
func nodeFromFI(t testing.TB, filename string, fi os.FileInfo) *restic.Node {
|
||||
func nodeFromFI(t testing.TB, fs fs.FS, filename string, fi os.FileInfo) *restic.Node {
|
||||
node, err := fs.NodeFromFileInfo(filename, fi, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -688,7 +688,7 @@ func TestFileChanged(t *testing.T) {
|
|||
|
||||
fs := &fs.Local{}
|
||||
fiBefore := lstat(t, filename)
|
||||
node := nodeFromFI(t, filename, fiBefore)
|
||||
node := nodeFromFI(t, fs, filename, fiBefore)
|
||||
|
||||
if fileChanged(fs, fiBefore, node, 0) {
|
||||
t.Fatalf("unchanged file detected as changed")
|
||||
|
@ -729,7 +729,7 @@ func TestFilChangedSpecialCases(t *testing.T) {
|
|||
|
||||
t.Run("type-change", func(t *testing.T) {
|
||||
fi := lstat(t, filename)
|
||||
node := nodeFromFI(t, filename, fi)
|
||||
node := nodeFromFI(t, &fs.Local{}, filename, fi)
|
||||
node.Type = "restic.NodeTypeSymlink"
|
||||
if !fileChanged(&fs.Local{}, fi, node, 0) {
|
||||
t.Fatal("node with changed type detected as unchanged")
|
||||
|
@ -2275,13 +2275,14 @@ func TestMetadataChanged(t *testing.T) {
|
|||
|
||||
// get metadata
|
||||
fi := lstat(t, "testfile")
|
||||
want, err := fs.NodeFromFileInfo("testfile", fi, false)
|
||||
localFS := &fs.Local{}
|
||||
want, err := localFS.NodeFromFileInfo("testfile", fi, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
fs := &StatFS{
|
||||
FS: fs.Local{},
|
||||
FS: localFS,
|
||||
OverrideLstat: map[string]os.FileInfo{
|
||||
"testfile": fi,
|
||||
},
|
||||
|
|
|
@ -58,10 +58,11 @@ func wrapIrregularFileInfo(fi os.FileInfo) os.FileInfo {
|
|||
|
||||
func statAndSnapshot(t *testing.T, repo archiverRepo, name string) (*restic.Node, *restic.Node) {
|
||||
fi := lstat(t, name)
|
||||
fs := &fs.Local{}
|
||||
want, err := fs.NodeFromFileInfo(name, fi, false)
|
||||
rtest.OK(t, err)
|
||||
|
||||
_, node := snapshot(t, repo, fs.Local{}, nil, name)
|
||||
_, node := snapshot(t, repo, fs, nil, name)
|
||||
return want, node
|
||||
}
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ func createTestFiles(t testing.TB, num int) (files []string) {
|
|||
return files
|
||||
}
|
||||
|
||||
func startFileSaver(ctx context.Context, t testing.TB) (*fileSaver, context.Context, *errgroup.Group) {
|
||||
func startFileSaver(ctx context.Context, t testing.TB, fs fs.FS) (*fileSaver, context.Context, *errgroup.Group) {
|
||||
wg, ctx := errgroup.WithContext(ctx)
|
||||
|
||||
saveBlob := func(ctx context.Context, tpe restic.BlobType, buf *buffer, _ string, cb func(saveBlobResponse)) {
|
||||
|
@ -67,7 +67,7 @@ func TestFileSaver(t *testing.T) {
|
|||
completeFn := func(*restic.Node, ItemStats) {}
|
||||
|
||||
testFs := fs.Local{}
|
||||
s, ctx, wg := startFileSaver(ctx, t)
|
||||
s, ctx, wg := startFileSaver(ctx, t, testFs)
|
||||
|
||||
var results []futureNode
|
||||
|
||||
|
|
|
@ -3,6 +3,8 @@ package fs
|
|||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/restic/restic/internal/restic"
|
||||
)
|
||||
|
||||
// Local is the local file system. Most methods are just passed on to the stdlib.
|
||||
|
@ -57,6 +59,10 @@ func (fs Local) ExtendedStat(fi os.FileInfo) ExtendedFileInfo {
|
|||
return ExtendedStat(fi)
|
||||
}
|
||||
|
||||
func (fs Local) NodeFromFileInfo(path string, fi os.FileInfo, ignoreXattrListError bool) (*restic.Node, error) {
|
||||
return nodeFromFileInfo(path, fi, ignoreXattrListError)
|
||||
}
|
||||
|
||||
// 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
|
||||
// empty strings are ignored. On Windows, the result is a UNC path if and only
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
)
|
||||
|
||||
// Reader is a file system which provides a directory with a single file. When
|
||||
|
@ -132,6 +133,17 @@ func (fs *Reader) ExtendedStat(fi os.FileInfo) ExtendedFileInfo {
|
|||
}
|
||||
}
|
||||
|
||||
func (fs *Reader) NodeFromFileInfo(path string, fi os.FileInfo, _ bool) (*restic.Node, error) {
|
||||
node := buildBasicNode(path, fi)
|
||||
|
||||
// fill minimal info with current values for uid, gid
|
||||
node.UID = uint32(os.Getuid())
|
||||
node.GID = uint32(os.Getgid())
|
||||
node.ChangeTime = node.ModTime
|
||||
|
||||
return node, nil
|
||||
}
|
||||
|
||||
// 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
|
||||
// empty strings are ignored. On Windows, the result is a UNC path if and only
|
||||
|
|
|
@ -3,6 +3,8 @@ package fs
|
|||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/restic/restic/internal/restic"
|
||||
)
|
||||
|
||||
// FS bundles all methods needed for a file system.
|
||||
|
@ -12,6 +14,7 @@ type FS interface {
|
|||
Lstat(name string) (os.FileInfo, error)
|
||||
DeviceID(fi os.FileInfo) (deviceID uint64, err error)
|
||||
ExtendedStat(fi os.FileInfo) ExtendedFileInfo
|
||||
NodeFromFileInfo(path string, fi os.FileInfo, ignoreXattrListError bool) (*restic.Node, error)
|
||||
|
||||
Join(elem ...string) string
|
||||
Separator() string
|
||||
|
|
|
@ -12,9 +12,25 @@ import (
|
|||
"github.com/restic/restic/internal/restic"
|
||||
)
|
||||
|
||||
// NodeFromFileInfo returns a new node from the given path and FileInfo. It
|
||||
// nodeFromFileInfo returns a new node from the given path and FileInfo. It
|
||||
// returns the first error that is encountered, together with a node.
|
||||
func NodeFromFileInfo(path string, fi os.FileInfo, ignoreXattrListError bool) (*restic.Node, error) {
|
||||
func nodeFromFileInfo(path string, fi os.FileInfo, ignoreXattrListError bool) (*restic.Node, error) {
|
||||
node := buildBasicNode(path, fi)
|
||||
|
||||
stat := ExtendedStat(fi)
|
||||
if err := nodeFillExtendedStat(node, path, &stat); err != nil {
|
||||
return node, err
|
||||
}
|
||||
|
||||
allowExtended, err := nodeFillGenericAttributes(node, path, &stat)
|
||||
if allowExtended {
|
||||
// Skip processing ExtendedAttributes if allowExtended is false.
|
||||
err = errors.Join(err, nodeFillExtendedAttributes(node, path, ignoreXattrListError))
|
||||
}
|
||||
return node, err
|
||||
}
|
||||
|
||||
func buildBasicNode(path string, fi os.FileInfo) *restic.Node {
|
||||
mask := os.ModePerm | os.ModeType | os.ModeSetuid | os.ModeSetgid | os.ModeSticky
|
||||
node := &restic.Node{
|
||||
Path: path,
|
||||
|
@ -27,9 +43,7 @@ func NodeFromFileInfo(path string, fi os.FileInfo, ignoreXattrListError bool) (*
|
|||
if node.Type == restic.NodeTypeFile {
|
||||
node.Size = uint64(fi.Size())
|
||||
}
|
||||
|
||||
err := nodeFillExtra(node, path, fi, ignoreXattrListError)
|
||||
return node, err
|
||||
return node
|
||||
}
|
||||
|
||||
func nodeTypeFromFileInfo(fi os.FileInfo) restic.NodeType {
|
||||
|
@ -55,17 +69,7 @@ func nodeTypeFromFileInfo(fi os.FileInfo) restic.NodeType {
|
|||
return restic.NodeTypeInvalid
|
||||
}
|
||||
|
||||
func nodeFillExtra(node *restic.Node, path string, fi os.FileInfo, ignoreXattrListError bool) error {
|
||||
if fi.Sys() == nil {
|
||||
// fill minimal info with current values for uid, gid
|
||||
node.UID = uint32(os.Getuid())
|
||||
node.GID = uint32(os.Getgid())
|
||||
node.ChangeTime = node.ModTime
|
||||
return nil
|
||||
}
|
||||
|
||||
stat := ExtendedStat(fi)
|
||||
|
||||
func nodeFillExtendedStat(node *restic.Node, path string, stat *ExtendedFileInfo) error {
|
||||
node.Inode = stat.Inode
|
||||
node.DeviceID = stat.DeviceID
|
||||
node.ChangeTime = stat.ChangeTime
|
||||
|
@ -99,13 +103,7 @@ func nodeFillExtra(node *restic.Node, path string, fi os.FileInfo, ignoreXattrLi
|
|||
default:
|
||||
return errors.Errorf("unsupported file type %q", node.Type)
|
||||
}
|
||||
|
||||
allowExtended, err := nodeFillGenericAttributes(node, path, &stat)
|
||||
if allowExtended {
|
||||
// Skip processing ExtendedAttributes if allowExtended is false.
|
||||
err = errors.Join(err, nodeFillExtendedAttributes(node, path, ignoreXattrListError))
|
||||
}
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
|
|
|
@ -29,11 +29,12 @@ func BenchmarkNodeFillUser(t *testing.B) {
|
|||
}
|
||||
|
||||
path := tempfile.Name()
|
||||
fs := Local{}
|
||||
|
||||
t.ResetTimer()
|
||||
|
||||
for i := 0; i < t.N; i++ {
|
||||
_, err := NodeFromFileInfo(path, fi, false)
|
||||
_, err := fs.NodeFromFileInfo(path, fi, false)
|
||||
rtest.OK(t, err)
|
||||
}
|
||||
|
||||
|
@ -53,11 +54,12 @@ func BenchmarkNodeFromFileInfo(t *testing.B) {
|
|||
}
|
||||
|
||||
path := tempfile.Name()
|
||||
fs := Local{}
|
||||
|
||||
t.ResetTimer()
|
||||
|
||||
for i := 0; i < t.N; i++ {
|
||||
_, err := NodeFromFileInfo(path, fi, false)
|
||||
_, err := fs.NodeFromFileInfo(path, fi, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -250,9 +252,10 @@ func TestNodeRestoreAt(t *testing.T) {
|
|||
fi, err := os.Lstat(nodePath)
|
||||
rtest.OK(t, err)
|
||||
|
||||
n2, err := NodeFromFileInfo(nodePath, fi, false)
|
||||
fs := &Local{}
|
||||
n2, err := fs.NodeFromFileInfo(nodePath, fi, false)
|
||||
rtest.OK(t, err)
|
||||
n3, err := NodeFromFileInfo(nodePath, fi, true)
|
||||
n3, err := fs.NodeFromFileInfo(nodePath, fi, true)
|
||||
rtest.OK(t, err)
|
||||
rtest.Assert(t, n2.Equals(*n3), "unexpected node info mismatch %v", cmp.Diff(n2, n3))
|
||||
|
||||
|
|
|
@ -119,7 +119,8 @@ func TestNodeFromFileInfo(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
node, err := NodeFromFileInfo(test.filename, fi, false)
|
||||
fs := &Local{}
|
||||
node, err := fs.NodeFromFileInfo(test.filename, fi, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
|
@ -221,10 +221,11 @@ func restoreAndGetNode(t *testing.T, tempDir string, testNode *restic.Node, warn
|
|||
})
|
||||
test.OK(t, errors.Wrapf(err, "Failed to restore metadata for: %s", testPath))
|
||||
|
||||
fi, err := os.Lstat(testPath)
|
||||
fs := &Local{}
|
||||
fi, err := fs.Lstat(testPath)
|
||||
test.OK(t, errors.Wrapf(err, "Could not Lstat for path: %s", testPath))
|
||||
|
||||
nodeFromFileInfo, err := NodeFromFileInfo(testPath, fi, false)
|
||||
nodeFromFileInfo, err := fs.NodeFromFileInfo(testPath, fi, false)
|
||||
test.OK(t, errors.Wrapf(err, "Could not get NodeFromFileInfo for path: %s", testPath))
|
||||
|
||||
return testPath, nodeFromFileInfo
|
||||
|
|
|
@ -84,7 +84,8 @@ func TestNodeMarshal(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestNodeComparison(t *testing.T) {
|
||||
fi, err := os.Lstat("tree_test.go")
|
||||
fs := &fs.Local{}
|
||||
fi, err := fs.Lstat("tree_test.go")
|
||||
rtest.OK(t, err)
|
||||
|
||||
node, err := fs.NodeFromFileInfo("tree_test.go", fi, false)
|
||||
|
@ -126,7 +127,8 @@ func TestTreeEqualSerialization(t *testing.T) {
|
|||
builder := restic.NewTreeJSONBuilder()
|
||||
|
||||
for _, fn := range files[:i] {
|
||||
fi, err := os.Lstat(fn)
|
||||
fs := &fs.Local{}
|
||||
fi, err := fs.Lstat(fn)
|
||||
rtest.OK(t, err)
|
||||
node, err := fs.NodeFromFileInfo(fn, fi, false)
|
||||
rtest.OK(t, err)
|
||||
|
|
Loading…
Reference in a new issue