diff --git a/internal/fs/node_xattr_all_test.go b/internal/fs/node_xattr_all_test.go index 7cb8758a3..470b69430 100644 --- a/internal/fs/node_xattr_all_test.go +++ b/internal/fs/node_xattr_all_test.go @@ -4,12 +4,14 @@ package fs import ( + "bytes" "os" "path/filepath" "runtime" "strings" "testing" + "github.com/restic/restic/internal/filter" "github.com/restic/restic/internal/restic" rtest "github.com/restic/restic/internal/test" ) @@ -37,6 +39,56 @@ func setAndVerifyXattr(t *testing.T, file string, attrs []restic.ExtendedAttribu rtest.Assert(t, nodeActual.Equals(*node), "xattr mismatch got %v expected %v", nodeActual.ExtendedAttributes, node.ExtendedAttributes) } +func setAndVerifyXattrWithSelectFilter(t *testing.T, file string, testAttr []testXattrToRestore, xattrSelectFilter func(_ string) bool) { + attrs := make([]restic.ExtendedAttribute, len(testAttr)) + for i := range testAttr { + attrs[i] = testAttr[i].xattr + } + + if runtime.GOOS == "windows" { + // windows seems to convert the xattr name to upper case + for i := range attrs { + attrs[i].Name = strings.ToUpper(attrs[i].Name) + } + } + + node := &restic.Node{ + Type: restic.NodeTypeFile, + ExtendedAttributes: attrs, + } + + rtest.OK(t, nodeRestoreExtendedAttributes(node, file, xattrSelectFilter)) + + nodeActual := &restic.Node{ + Type: restic.NodeTypeFile, + } + rtest.OK(t, nodeFillExtendedAttributes(nodeActual, file, false)) + + // Check nodeActual to make sure only xattrs we expect are there + for _, testAttr := range testAttr { + xattrFound := false + xattrRestored := false + for _, restoredAttr := range nodeActual.ExtendedAttributes { + if restoredAttr.Name == testAttr.xattr.Name { + xattrFound = true + xattrRestored = bytes.Equal(restoredAttr.Value, testAttr.xattr.Value) + break + } + } + if testAttr.shouldRestore { + rtest.Assert(t, xattrFound, "xattr %s not restored", testAttr.xattr.Name) + rtest.Assert(t, xattrRestored, "xattr %v value not restored", testAttr.xattr) + } else { + rtest.Assert(t, !xattrFound, "xattr %v should not have been restored", testAttr.xattr) + } + } +} + +type testXattrToRestore struct { + xattr restic.ExtendedAttribute + shouldRestore bool +} + func TestOverwriteXattr(t *testing.T) { dir := t.TempDir() file := filepath.Join(dir, "file") @@ -47,6 +99,10 @@ func TestOverwriteXattr(t *testing.T) { Name: "user.foo", Value: []byte("bar"), }, + { + Name: "abc.test", + Value: []byte("testxattr"), + }, }) setAndVerifyXattr(t, file, []restic.ExtendedAttribute{ @@ -56,3 +112,85 @@ func TestOverwriteXattr(t *testing.T) { }, }) } + +func TestOverwriteXattrWithSelectFilter(t *testing.T) { + dir := t.TempDir() + file := filepath.Join(dir, "file2") + rtest.OK(t, os.WriteFile(file, []byte("hello world"), 0o600)) + + noopWarnf := func(_ string, _ ...interface{}) {} + + // Set a filter as if the user passed in --include-xattr user.* + xattrSelectFilter1 := func(xattrName string) bool { + shouldInclude, _ := filter.IncludeByPattern([]string{"user.*"}, noopWarnf)(xattrName) + return shouldInclude + } + + setAndVerifyXattrWithSelectFilter(t, file, []testXattrToRestore{ + { + xattr: restic.ExtendedAttribute{ + Name: "user.foo", + Value: []byte("bar"), + }, + shouldRestore: true, + }, + { + xattr: restic.ExtendedAttribute{ + Name: "user.test", + Value: []byte("testxattr"), + }, + shouldRestore: true, + }, + { + xattr: restic.ExtendedAttribute{ + Name: "security.other", + Value: []byte("testing"), + }, + shouldRestore: false, + }, + }, xattrSelectFilter1) + + // Set a filter as if the user passed in --include-xattr user.* + xattrSelectFilter2 := func(xattrName string) bool { + shouldInclude, _ := filter.IncludeByPattern([]string{"user.o*", "user.comm*"}, noopWarnf)(xattrName) + return shouldInclude + } + + setAndVerifyXattrWithSelectFilter(t, file, []testXattrToRestore{ + { + xattr: restic.ExtendedAttribute{ + Name: "user.other", + Value: []byte("some"), + }, + shouldRestore: true, + }, + { + xattr: restic.ExtendedAttribute{ + Name: "security.other", + Value: []byte("testing"), + }, + shouldRestore: false, + }, + { + xattr: restic.ExtendedAttribute{ + Name: "user.open", + Value: []byte("door"), + }, + shouldRestore: true, + }, + { + xattr: restic.ExtendedAttribute{ + Name: "user.common", + Value: []byte("testing"), + }, + shouldRestore: true, + }, + { + xattr: restic.ExtendedAttribute{ + Name: "user.bad", + Value: []byte("dontincludeme"), + }, + shouldRestore: false, + }, + }, xattrSelectFilter2) +}