fs: move NodeFromFileInfo into FS interface

This commit is contained in:
Michael Eischer 2024-08-28 10:58:07 +02:00
parent c3b3120e10
commit 75711446e1
12 changed files with 69 additions and 41 deletions

View file

@ -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. // nodeFromFileInfo returns the restic node from an os.FileInfo.
func (arch *Archiver) nodeFromFileInfo(snPath, filename string, fi os.FileInfo, ignoreXattrListError bool) (*restic.Node, error) { 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 { if !arch.WithAtime {
node.AccessTime = node.ModTime node.AccessTime = node.ModTime
} }

View file

@ -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) node, err := fs.NodeFromFileInfo(filename, fi, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -688,7 +688,7 @@ func TestFileChanged(t *testing.T) {
fs := &fs.Local{} fs := &fs.Local{}
fiBefore := lstat(t, filename) fiBefore := lstat(t, filename)
node := nodeFromFI(t, filename, fiBefore) node := nodeFromFI(t, fs, filename, fiBefore)
if fileChanged(fs, fiBefore, node, 0) { if fileChanged(fs, fiBefore, node, 0) {
t.Fatalf("unchanged file detected as changed") t.Fatalf("unchanged file detected as changed")
@ -729,7 +729,7 @@ func TestFilChangedSpecialCases(t *testing.T) {
t.Run("type-change", func(t *testing.T) { t.Run("type-change", func(t *testing.T) {
fi := lstat(t, filename) fi := lstat(t, filename)
node := nodeFromFI(t, filename, fi) node := nodeFromFI(t, &fs.Local{}, filename, fi)
node.Type = "restic.NodeTypeSymlink" node.Type = "restic.NodeTypeSymlink"
if !fileChanged(&fs.Local{}, fi, node, 0) { if !fileChanged(&fs.Local{}, fi, node, 0) {
t.Fatal("node with changed type detected as unchanged") t.Fatal("node with changed type detected as unchanged")
@ -2275,13 +2275,14 @@ func TestMetadataChanged(t *testing.T) {
// get metadata // get metadata
fi := lstat(t, "testfile") fi := lstat(t, "testfile")
want, err := fs.NodeFromFileInfo("testfile", fi, false) localFS := &fs.Local{}
want, err := localFS.NodeFromFileInfo("testfile", fi, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
fs := &StatFS{ fs := &StatFS{
FS: fs.Local{}, FS: localFS,
OverrideLstat: map[string]os.FileInfo{ OverrideLstat: map[string]os.FileInfo{
"testfile": fi, "testfile": fi,
}, },

View file

@ -58,10 +58,11 @@ func wrapIrregularFileInfo(fi os.FileInfo) os.FileInfo {
func statAndSnapshot(t *testing.T, repo archiverRepo, name string) (*restic.Node, *restic.Node) { func statAndSnapshot(t *testing.T, repo archiverRepo, name string) (*restic.Node, *restic.Node) {
fi := lstat(t, name) fi := lstat(t, name)
fs := &fs.Local{}
want, err := fs.NodeFromFileInfo(name, fi, false) want, err := fs.NodeFromFileInfo(name, fi, false)
rtest.OK(t, err) rtest.OK(t, err)
_, node := snapshot(t, repo, fs.Local{}, nil, name) _, node := snapshot(t, repo, fs, nil, name)
return want, node return want, node
} }

View file

@ -30,7 +30,7 @@ func createTestFiles(t testing.TB, num int) (files []string) {
return files 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) wg, ctx := errgroup.WithContext(ctx)
saveBlob := func(ctx context.Context, tpe restic.BlobType, buf *buffer, _ string, cb func(saveBlobResponse)) { 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) {} completeFn := func(*restic.Node, ItemStats) {}
testFs := fs.Local{} testFs := fs.Local{}
s, ctx, wg := startFileSaver(ctx, t) s, ctx, wg := startFileSaver(ctx, t, testFs)
var results []futureNode var results []futureNode

View file

@ -3,6 +3,8 @@ package fs
import ( import (
"os" "os"
"path/filepath" "path/filepath"
"github.com/restic/restic/internal/restic"
) )
// Local is the local file system. Most methods are just passed on to the stdlib. // 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) 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 // 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

View file

@ -10,6 +10,7 @@ import (
"time" "time"
"github.com/restic/restic/internal/errors" "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 // 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 // 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

View file

@ -3,6 +3,8 @@ package fs
import ( import (
"io" "io"
"os" "os"
"github.com/restic/restic/internal/restic"
) )
// FS bundles all methods needed for a file system. // FS bundles all methods needed for a file system.
@ -12,6 +14,7 @@ type FS interface {
Lstat(name string) (os.FileInfo, error) Lstat(name string) (os.FileInfo, error)
DeviceID(fi os.FileInfo) (deviceID uint64, err error) DeviceID(fi os.FileInfo) (deviceID uint64, err error)
ExtendedStat(fi os.FileInfo) ExtendedFileInfo ExtendedStat(fi os.FileInfo) ExtendedFileInfo
NodeFromFileInfo(path string, fi os.FileInfo, ignoreXattrListError bool) (*restic.Node, error)
Join(elem ...string) string Join(elem ...string) string
Separator() string Separator() string

View file

@ -12,9 +12,25 @@ import (
"github.com/restic/restic/internal/restic" "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. // 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 mask := os.ModePerm | os.ModeType | os.ModeSetuid | os.ModeSetgid | os.ModeSticky
node := &restic.Node{ node := &restic.Node{
Path: path, Path: path,
@ -27,9 +43,7 @@ func NodeFromFileInfo(path string, fi os.FileInfo, ignoreXattrListError bool) (*
if node.Type == restic.NodeTypeFile { if node.Type == restic.NodeTypeFile {
node.Size = uint64(fi.Size()) node.Size = uint64(fi.Size())
} }
return node
err := nodeFillExtra(node, path, fi, ignoreXattrListError)
return node, err
} }
func nodeTypeFromFileInfo(fi os.FileInfo) restic.NodeType { func nodeTypeFromFileInfo(fi os.FileInfo) restic.NodeType {
@ -55,17 +69,7 @@ func nodeTypeFromFileInfo(fi os.FileInfo) restic.NodeType {
return restic.NodeTypeInvalid return restic.NodeTypeInvalid
} }
func nodeFillExtra(node *restic.Node, path string, fi os.FileInfo, ignoreXattrListError bool) error { func nodeFillExtendedStat(node *restic.Node, path string, stat *ExtendedFileInfo) 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)
node.Inode = stat.Inode node.Inode = stat.Inode
node.DeviceID = stat.DeviceID node.DeviceID = stat.DeviceID
node.ChangeTime = stat.ChangeTime node.ChangeTime = stat.ChangeTime
@ -99,13 +103,7 @@ func nodeFillExtra(node *restic.Node, path string, fi os.FileInfo, ignoreXattrLi
default: default:
return errors.Errorf("unsupported file type %q", node.Type) return errors.Errorf("unsupported file type %q", node.Type)
} }
return nil
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
} }
var ( var (

View file

@ -29,11 +29,12 @@ func BenchmarkNodeFillUser(t *testing.B) {
} }
path := tempfile.Name() path := tempfile.Name()
fs := Local{}
t.ResetTimer() t.ResetTimer()
for i := 0; i < t.N; i++ { for i := 0; i < t.N; i++ {
_, err := NodeFromFileInfo(path, fi, false) _, err := fs.NodeFromFileInfo(path, fi, false)
rtest.OK(t, err) rtest.OK(t, err)
} }
@ -53,11 +54,12 @@ func BenchmarkNodeFromFileInfo(t *testing.B) {
} }
path := tempfile.Name() path := tempfile.Name()
fs := Local{}
t.ResetTimer() t.ResetTimer()
for i := 0; i < t.N; i++ { for i := 0; i < t.N; i++ {
_, err := NodeFromFileInfo(path, fi, false) _, err := fs.NodeFromFileInfo(path, fi, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -250,9 +252,10 @@ func TestNodeRestoreAt(t *testing.T) {
fi, err := os.Lstat(nodePath) fi, err := os.Lstat(nodePath)
rtest.OK(t, err) rtest.OK(t, err)
n2, err := NodeFromFileInfo(nodePath, fi, false) fs := &Local{}
n2, err := fs.NodeFromFileInfo(nodePath, fi, false)
rtest.OK(t, err) rtest.OK(t, err)
n3, err := NodeFromFileInfo(nodePath, fi, true) n3, err := fs.NodeFromFileInfo(nodePath, fi, true)
rtest.OK(t, err) rtest.OK(t, err)
rtest.Assert(t, n2.Equals(*n3), "unexpected node info mismatch %v", cmp.Diff(n2, n3)) rtest.Assert(t, n2.Equals(*n3), "unexpected node info mismatch %v", cmp.Diff(n2, n3))

View file

@ -119,7 +119,8 @@ func TestNodeFromFileInfo(t *testing.T) {
return return
} }
node, err := NodeFromFileInfo(test.filename, fi, false) fs := &Local{}
node, err := fs.NodeFromFileInfo(test.filename, fi, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View file

@ -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)) 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)) 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)) test.OK(t, errors.Wrapf(err, "Could not get NodeFromFileInfo for path: %s", testPath))
return testPath, nodeFromFileInfo return testPath, nodeFromFileInfo

View file

@ -84,7 +84,8 @@ func TestNodeMarshal(t *testing.T) {
} }
func TestNodeComparison(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) rtest.OK(t, err)
node, err := fs.NodeFromFileInfo("tree_test.go", fi, false) node, err := fs.NodeFromFileInfo("tree_test.go", fi, false)
@ -126,7 +127,8 @@ func TestTreeEqualSerialization(t *testing.T) {
builder := restic.NewTreeJSONBuilder() builder := restic.NewTreeJSONBuilder()
for _, fn := range files[:i] { for _, fn := range files[:i] {
fi, err := os.Lstat(fn) fs := &fs.Local{}
fi, err := fs.Lstat(fn)
rtest.OK(t, err) rtest.OK(t, err)
node, err := fs.NodeFromFileInfo(fn, fi, false) node, err := fs.NodeFromFileInfo(fn, fi, false)
rtest.OK(t, err) rtest.OK(t, err)