diff --git a/internal/restic/node_xattr.go b/internal/restic/node_xattr.go index a55fcb2db..5a5a253d9 100644 --- a/internal/restic/node_xattr.go +++ b/internal/restic/node_xattr.go @@ -40,6 +40,11 @@ func setxattr(path, name string, data []byte) error { return handleXattrErr(xattr.LSet(path, name, data)) } +// removexattr removes the attribute name from path. +func removexattr(path, name string) error { + return handleXattrErr(xattr.LRemove(path, name)) +} + func handleXattrErr(err error) error { switch e := err.(type) { case nil: @@ -70,12 +75,29 @@ func (node *Node) fillGenericAttributes(_ string, _ os.FileInfo, _ *statT) (allo } func (node Node) restoreExtendedAttributes(path string) error { + expectedAttrs := map[string]struct{}{} for _, attr := range node.ExtendedAttributes { err := setxattr(path, attr.Name, attr.Value) if err != nil { return err } + expectedAttrs[attr.Name] = struct{}{} } + + // remove unexpected xattrs + xattrs, err := listxattr(path) + if err != nil { + return err + } + for _, name := range xattrs { + if _, ok := expectedAttrs[name]; ok { + continue + } + if err := removexattr(path, name); err != nil { + return err + } + } + return nil } diff --git a/internal/restic/node_xattr_all_test.go b/internal/restic/node_xattr_all_test.go new file mode 100644 index 000000000..4e93330bc --- /dev/null +++ b/internal/restic/node_xattr_all_test.go @@ -0,0 +1,44 @@ +//go:build darwin || freebsd || linux || solaris || windows +// +build darwin freebsd linux solaris windows + +package restic + +import ( + "os" + "path/filepath" + "testing" + + rtest "github.com/restic/restic/internal/test" +) + +func setAndVerifyXattr(t *testing.T, file string, attrs []ExtendedAttribute) { + node := Node{ + ExtendedAttributes: attrs, + } + rtest.OK(t, node.restoreExtendedAttributes(file)) + + nodeActual := Node{} + rtest.OK(t, nodeActual.fillExtendedAttributes(file, false)) + + rtest.Assert(t, nodeActual.sameExtendedAttributes(node), "xattr mismatch got %v expected %v", nodeActual.ExtendedAttributes, node.ExtendedAttributes) +} + +func TestOverwriteXattr(t *testing.T) { + dir := t.TempDir() + file := filepath.Join(dir, "file") + rtest.OK(t, os.WriteFile(file, []byte("hello world"), 0o600)) + + setAndVerifyXattr(t, file, []ExtendedAttribute{ + { + Name: "user.foo", + Value: []byte("bar"), + }, + }) + + setAndVerifyXattr(t, file, []ExtendedAttribute{ + { + Name: "user.other", + Value: []byte("some"), + }, + }) +}