diff --git a/cmd/restic/cmd_restore.go b/cmd/restic/cmd_restore.go index fc3148ce1..3bc6ac5c5 100644 --- a/cmd/restic/cmd_restore.go +++ b/cmd/restic/cmd_restore.go @@ -101,9 +101,6 @@ func runRestore(ctx context.Context, opts RestoreOptions, gopts GlobalOptions, hasExcludes := len(excludePatternFns) > 0 hasIncludes := len(includePatternFns) > 0 - hasXattrExcludes := len(opts.ExcludeXattrPattern) > 0 - hasXattrIncludes := len(opts.IncludeXattrPattern) > 0 - switch { case len(args) == 0: return errors.Fatal("no snapshot ID specified") @@ -119,10 +116,6 @@ func runRestore(ctx context.Context, opts RestoreOptions, gopts GlobalOptions, return errors.Fatal("exclude and include patterns are mutually exclusive") } - if hasXattrExcludes && hasXattrIncludes { - return errors.Fatal("exclude and include xattr patterns are mutually exclusive") - } - if opts.DryRun && opts.Verify { return errors.Fatal("--dry-run and --verify are mutually exclusive") } @@ -232,29 +225,9 @@ func runRestore(ctx context.Context, opts RestoreOptions, gopts GlobalOptions, res.SelectFilter = selectIncludeFilter } - if !hasXattrExcludes && !hasXattrIncludes { - // set default of including xattrs from the 'user' namespace - opts.IncludeXattrPattern = []string{"user.*"} - } - if hasXattrExcludes { - if err := filter.ValidatePatterns(opts.ExcludeXattrPattern); err != nil { - return errors.Fatalf("--exclude-xattr: %s", err) - } - - res.XattrSelectFilter = func(xattrName string) bool { - shouldReject := filter.RejectByPattern(opts.ExcludeXattrPattern, Warnf)(xattrName) - return !shouldReject - } - } else { - // User has either input include xattr pattern(s) or we're using our default include pattern - if err := filter.ValidatePatterns(opts.IncludeXattrPattern); err != nil { - return errors.Fatalf("--include-xattr: %s", err) - } - - res.XattrSelectFilter = func(xattrName string) bool { - shouldInclude, _ := filter.IncludeByPattern(opts.IncludeXattrPattern, Warnf)(xattrName) - return shouldInclude - } + res.XattrSelectFilter, err = getXattrSelectFilter(opts) + if err != nil { + return err } if !gopts.JSON { @@ -295,3 +268,38 @@ func runRestore(ctx context.Context, opts RestoreOptions, gopts GlobalOptions, return nil } + +func getXattrSelectFilter(opts RestoreOptions) (func(xattrName string) bool, error) { + hasXattrExcludes := len(opts.ExcludeXattrPattern) > 0 + hasXattrIncludes := len(opts.IncludeXattrPattern) > 0 + + if hasXattrExcludes && hasXattrIncludes { + return nil, errors.Fatal("exclude and include xattr patterns are mutually exclusive") + } + + if hasXattrExcludes { + if err := filter.ValidatePatterns(opts.ExcludeXattrPattern); err != nil { + return nil, errors.Fatalf("--exclude-xattr: %s", err) + } + + return func(xattrName string) bool { + shouldReject := filter.RejectByPattern(opts.ExcludeXattrPattern, Warnf)(xattrName) + return !shouldReject + }, nil + } + + if hasXattrIncludes { + // User has either input include xattr pattern(s) or we're using our default include pattern + if err := filter.ValidatePatterns(opts.IncludeXattrPattern); err != nil { + return nil, errors.Fatalf("--include-xattr: %s", err) + } + + return func(xattrName string) bool { + shouldInclude, _ := filter.IncludeByPattern(opts.IncludeXattrPattern, Warnf)(xattrName) + return shouldInclude + }, nil + } + + // no includes or excludes, set default of including all xattrs + return func(_ string) bool { return true }, nil +} diff --git a/internal/fs/node_noxattr.go b/internal/fs/node_noxattr.go index 281a16dbd..0bc706321 100644 --- a/internal/fs/node_noxattr.go +++ b/internal/fs/node_noxattr.go @@ -8,7 +8,7 @@ import ( ) // nodeRestoreExtendedAttributes is a no-op -func nodeRestoreExtendedAttributes(_ *restic.Node, _ string) error { +func nodeRestoreExtendedAttributes(_ *restic.Node, _ string, _ func(xattrName string) bool) error { return nil } diff --git a/internal/fs/node_test.go b/internal/fs/node_test.go index b67295f68..490ab7e40 100644 --- a/internal/fs/node_test.go +++ b/internal/fs/node_test.go @@ -217,8 +217,9 @@ func TestNodeRestoreAt(t *testing.T) { nodePath = filepath.Join(tempdir, test.Name) } rtest.OK(t, NodeCreateAt(&test, nodePath)) + // Restore metadata, restoring all xattrs rtest.OK(t, NodeRestoreMetadata(&test, nodePath, func(msg string) { rtest.OK(t, fmt.Errorf("Warning triggered for path: %s: %s", nodePath, msg)) }, - func(_ string) bool { return true } /* restore all xattrs */)) + func(_ string) bool { return true })) fs := &Local{} meta, err := fs.OpenFile(nodePath, O_NOFOLLOW, true) diff --git a/internal/fs/node_windows.go b/internal/fs/node_windows.go index 74cf6c0e5..df0a7ea65 100644 --- a/internal/fs/node_windows.go +++ b/internal/fs/node_windows.go @@ -69,15 +69,20 @@ func utimesNano(path string, atime, mtime int64, _ restic.NodeType) error { } // restore extended attributes for windows -func nodeRestoreExtendedAttributes(node *restic.Node, path string) (err error) { +func nodeRestoreExtendedAttributes(node *restic.Node, path string, xattrSelectFilter func(xattrName string) bool) error { count := len(node.ExtendedAttributes) if count > 0 { - eas := make([]extendedAttribute, count) - for i, attr := range node.ExtendedAttributes { - eas[i] = extendedAttribute{Name: attr.Name, Value: attr.Value} + eas := []extendedAttribute{} + for _, attr := range node.ExtendedAttributes { + // Filter for xattrs we want to include/exclude + if xattrSelectFilter(attr.Name) { + eas = append(eas, extendedAttribute{Name: attr.Name, Value: attr.Value}) + } } - if errExt := restoreExtendedAttributes(node.Type, path, eas); errExt != nil { - return errExt + if len(eas) > 0 { + if errExt := restoreExtendedAttributes(node.Type, path, eas); errExt != nil { + return errExt + } } } return nil diff --git a/internal/fs/node_windows_test.go b/internal/fs/node_windows_test.go index f75df54d3..458a7bcb1 100644 --- a/internal/fs/node_windows_test.go +++ b/internal/fs/node_windows_test.go @@ -218,7 +218,7 @@ func restoreAndGetNode(t *testing.T, tempDir string, testNode *restic.Node, warn // If warning is not expected, this code should not get triggered. test.OK(t, fmt.Errorf("Warning triggered for path: %s: %s", testPath, msg)) } - }) + }, func(_ string) bool { return true }) test.OK(t, errors.Wrapf(err, "Failed to restore metadata for: %s", testPath)) fs := &Local{} diff --git a/internal/fs/node_xattr_all_test.go b/internal/fs/node_xattr_all_test.go index d71773e0a..7cb8758a3 100644 --- a/internal/fs/node_xattr_all_test.go +++ b/internal/fs/node_xattr_all_test.go @@ -26,7 +26,8 @@ func setAndVerifyXattr(t *testing.T, file string, attrs []restic.ExtendedAttribu Type: restic.NodeTypeFile, ExtendedAttributes: attrs, } - rtest.OK(t, nodeRestoreExtendedAttributes(node, file, func(_ string) bool { return true } /*restore all xattrs*/)) + /* restore all xattrs */ + rtest.OK(t, nodeRestoreExtendedAttributes(node, file, func(_ string) bool { return true })) nodeActual := &restic.Node{ Type: restic.NodeTypeFile, diff --git a/internal/restorer/restorer.go b/internal/restorer/restorer.go index 536958d4f..cce175ebc 100644 --- a/internal/restorer/restorer.go +++ b/internal/restorer/restorer.go @@ -99,12 +99,13 @@ func (c *OverwriteBehavior) Type() string { // NewRestorer creates a restorer preloaded with the content from the snapshot id. func NewRestorer(repo restic.Repository, sn *restic.Snapshot, opts Options) *Restorer { r := &Restorer{ - repo: repo, - opts: opts, - fileList: make(map[string]bool), - Error: restorerAbortOnAllErrors, - SelectFilter: func(string, bool) (bool, bool) { return true, true }, - sn: sn, + repo: repo, + opts: opts, + fileList: make(map[string]bool), + Error: restorerAbortOnAllErrors, + SelectFilter: func(string, bool) (bool, bool) { return true, true }, + XattrSelectFilter: func(string) bool { return true }, + sn: sn, } return r