restore: exclude/include xattrs
For: https://github.com/restic/restic/issues/5089 Signed-off-by: Tesshu Flower <tflower@redhat.com>
This commit is contained in:
parent
d7d9af4c9f
commit
0d6e008114
6 changed files with 68 additions and 20 deletions
|
@ -54,11 +54,13 @@ type RestoreOptions struct {
|
||||||
filter.IncludePatternOptions
|
filter.IncludePatternOptions
|
||||||
Target string
|
Target string
|
||||||
restic.SnapshotFilter
|
restic.SnapshotFilter
|
||||||
DryRun bool
|
DryRun bool
|
||||||
Sparse bool
|
Sparse bool
|
||||||
Verify bool
|
Verify bool
|
||||||
Overwrite restorer.OverwriteBehavior
|
Overwrite restorer.OverwriteBehavior
|
||||||
Delete bool
|
Delete bool
|
||||||
|
ExcludeXattrPattern []string
|
||||||
|
IncludeXattrPattern []string
|
||||||
}
|
}
|
||||||
|
|
||||||
var restoreOptions RestoreOptions
|
var restoreOptions RestoreOptions
|
||||||
|
@ -72,6 +74,9 @@ func init() {
|
||||||
restoreOptions.ExcludePatternOptions.Add(flags)
|
restoreOptions.ExcludePatternOptions.Add(flags)
|
||||||
restoreOptions.IncludePatternOptions.Add(flags)
|
restoreOptions.IncludePatternOptions.Add(flags)
|
||||||
|
|
||||||
|
flags.StringArrayVar(&restoreOptions.ExcludeXattrPattern, "exclude-xattr", nil, "exclude xattr by `pattern` (can be specified multiple times)")
|
||||||
|
flags.StringArrayVar(&restoreOptions.IncludeXattrPattern, "include-xattr", nil, "include xattr by `pattern` (can be specified multiple times)")
|
||||||
|
|
||||||
initSingleSnapshotFilter(flags, &restoreOptions.SnapshotFilter)
|
initSingleSnapshotFilter(flags, &restoreOptions.SnapshotFilter)
|
||||||
flags.BoolVar(&restoreOptions.DryRun, "dry-run", false, "do not write any data, just show what would be done")
|
flags.BoolVar(&restoreOptions.DryRun, "dry-run", false, "do not write any data, just show what would be done")
|
||||||
flags.BoolVar(&restoreOptions.Sparse, "sparse", false, "restore files as sparse")
|
flags.BoolVar(&restoreOptions.Sparse, "sparse", false, "restore files as sparse")
|
||||||
|
@ -96,6 +101,9 @@ func runRestore(ctx context.Context, opts RestoreOptions, gopts GlobalOptions,
|
||||||
hasExcludes := len(excludePatternFns) > 0
|
hasExcludes := len(excludePatternFns) > 0
|
||||||
hasIncludes := len(includePatternFns) > 0
|
hasIncludes := len(includePatternFns) > 0
|
||||||
|
|
||||||
|
hasXattrExcludes := len(opts.ExcludeXattrPattern) > 0
|
||||||
|
hasXattrIncludes := len(opts.IncludeXattrPattern) > 0
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case len(args) == 0:
|
case len(args) == 0:
|
||||||
return errors.Fatal("no snapshot ID specified")
|
return errors.Fatal("no snapshot ID specified")
|
||||||
|
@ -110,6 +118,11 @@ func runRestore(ctx context.Context, opts RestoreOptions, gopts GlobalOptions,
|
||||||
if hasExcludes && hasIncludes {
|
if hasExcludes && hasIncludes {
|
||||||
return errors.Fatal("exclude and include patterns are mutually exclusive")
|
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 {
|
if opts.DryRun && opts.Verify {
|
||||||
return errors.Fatal("--dry-run and --verify are mutually exclusive")
|
return errors.Fatal("--dry-run and --verify are mutually exclusive")
|
||||||
}
|
}
|
||||||
|
@ -219,6 +232,31 @@ func runRestore(ctx context.Context, opts RestoreOptions, gopts GlobalOptions,
|
||||||
res.SelectFilter = selectIncludeFilter
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !gopts.JSON {
|
if !gopts.JSON {
|
||||||
msg.P("restoring %s to %s\n", res.Snapshot(), opts.Target)
|
msg.P("restoring %s to %s\n", res.Snapshot(), opts.Target)
|
||||||
}
|
}
|
||||||
|
|
|
@ -230,8 +230,8 @@ func mkfifo(path string, mode uint32) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NodeRestoreMetadata restores node metadata
|
// NodeRestoreMetadata restores node metadata
|
||||||
func NodeRestoreMetadata(node *restic.Node, path string, warn func(msg string)) error {
|
func NodeRestoreMetadata(node *restic.Node, path string, warn func(msg string), xattrSelectFilter func(xattrName string) bool) error {
|
||||||
err := nodeRestoreMetadata(node, path, warn)
|
err := nodeRestoreMetadata(node, path, warn, xattrSelectFilter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// It is common to have permission errors for folders like /home
|
// It is common to have permission errors for folders like /home
|
||||||
// unless you're running as root, so ignore those.
|
// unless you're running as root, so ignore those.
|
||||||
|
@ -246,14 +246,14 @@ func NodeRestoreMetadata(node *restic.Node, path string, warn func(msg string))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func nodeRestoreMetadata(node *restic.Node, path string, warn func(msg string)) error {
|
func nodeRestoreMetadata(node *restic.Node, path string, warn func(msg string), xattrSelectFilter func(xattrName string) bool) error {
|
||||||
var firsterr error
|
var firsterr error
|
||||||
|
|
||||||
if err := lchown(path, int(node.UID), int(node.GID)); err != nil {
|
if err := lchown(path, int(node.UID), int(node.GID)); err != nil {
|
||||||
firsterr = errors.WithStack(err)
|
firsterr = errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := nodeRestoreExtendedAttributes(node, path); err != nil {
|
if err := nodeRestoreExtendedAttributes(node, path, xattrSelectFilter); err != nil {
|
||||||
debug.Log("error restoring extended attributes for %v: %v", path, err)
|
debug.Log("error restoring extended attributes for %v: %v", path, err)
|
||||||
if firsterr == nil {
|
if firsterr == nil {
|
||||||
firsterr = err
|
firsterr = err
|
||||||
|
|
|
@ -217,7 +217,8 @@ func TestNodeRestoreAt(t *testing.T) {
|
||||||
nodePath = filepath.Join(tempdir, test.Name)
|
nodePath = filepath.Join(tempdir, test.Name)
|
||||||
}
|
}
|
||||||
rtest.OK(t, NodeCreateAt(&test, nodePath))
|
rtest.OK(t, NodeCreateAt(&test, nodePath))
|
||||||
rtest.OK(t, NodeRestoreMetadata(&test, nodePath, func(msg string) { rtest.OK(t, fmt.Errorf("Warning triggered for path: %s: %s", nodePath, msg)) }))
|
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 */))
|
||||||
|
|
||||||
fs := &Local{}
|
fs := &Local{}
|
||||||
meta, err := fs.OpenFile(nodePath, O_NOFOLLOW, true)
|
meta, err := fs.OpenFile(nodePath, O_NOFOLLOW, true)
|
||||||
|
@ -292,6 +293,7 @@ func TestNodeRestoreMetadataError(t *testing.T) {
|
||||||
nodePath := filepath.Join(tempdir, node.Name)
|
nodePath := filepath.Join(tempdir, node.Name)
|
||||||
|
|
||||||
// This will fail because the target file does not exist
|
// This will fail because the target file does not exist
|
||||||
err := NodeRestoreMetadata(node, nodePath, func(msg string) { rtest.OK(t, fmt.Errorf("Warning triggered for path: %s: %s", nodePath, msg)) })
|
err := NodeRestoreMetadata(node, nodePath, func(msg string) { rtest.OK(t, fmt.Errorf("Warning triggered for path: %s: %s", nodePath, msg)) },
|
||||||
|
func(_ string) bool { return true })
|
||||||
test.Assert(t, errors.Is(err, os.ErrNotExist), "failed for an unexpected reason")
|
test.Assert(t, errors.Is(err, os.ErrNotExist), "failed for an unexpected reason")
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,14 +65,17 @@ func handleXattrErr(err error) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func nodeRestoreExtendedAttributes(node *restic.Node, path string) error {
|
func nodeRestoreExtendedAttributes(node *restic.Node, path string, xattrSelectFilter func(xattrName string) bool) error {
|
||||||
expectedAttrs := map[string]struct{}{}
|
expectedAttrs := map[string]struct{}{}
|
||||||
for _, attr := range node.ExtendedAttributes {
|
for _, attr := range node.ExtendedAttributes {
|
||||||
err := setxattr(path, attr.Name, attr.Value)
|
// Only restore xattrs that match the filter
|
||||||
if err != nil {
|
if xattrSelectFilter(attr.Name) {
|
||||||
return err
|
err := setxattr(path, attr.Name, attr.Value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
expectedAttrs[attr.Name] = struct{}{}
|
||||||
}
|
}
|
||||||
expectedAttrs[attr.Name] = struct{}{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove unexpected xattrs
|
// remove unexpected xattrs
|
||||||
|
@ -84,8 +87,11 @@ func nodeRestoreExtendedAttributes(node *restic.Node, path string) error {
|
||||||
if _, ok := expectedAttrs[name]; ok {
|
if _, ok := expectedAttrs[name]; ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := removexattr(path, name); err != nil {
|
// Only attempt to remove xattrs that match the filter
|
||||||
return err
|
if xattrSelectFilter(name) {
|
||||||
|
if err := removexattr(path, name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ func setAndVerifyXattr(t *testing.T, file string, attrs []restic.ExtendedAttribu
|
||||||
Type: restic.NodeTypeFile,
|
Type: restic.NodeTypeFile,
|
||||||
ExtendedAttributes: attrs,
|
ExtendedAttributes: attrs,
|
||||||
}
|
}
|
||||||
rtest.OK(t, nodeRestoreExtendedAttributes(node, file))
|
rtest.OK(t, nodeRestoreExtendedAttributes(node, file, func(_ string) bool { return true } /*restore all xattrs*/))
|
||||||
|
|
||||||
nodeActual := &restic.Node{
|
nodeActual := &restic.Node{
|
||||||
Type: restic.NodeTypeFile,
|
Type: restic.NodeTypeFile,
|
||||||
|
|
|
@ -31,6 +31,8 @@ type Restorer struct {
|
||||||
// SelectFilter determines whether the item is selectedForRestore or whether a childMayBeSelected.
|
// SelectFilter determines whether the item is selectedForRestore or whether a childMayBeSelected.
|
||||||
// selectedForRestore must not depend on isDir as `removeUnexpectedFiles` always passes false to isDir.
|
// selectedForRestore must not depend on isDir as `removeUnexpectedFiles` always passes false to isDir.
|
||||||
SelectFilter func(item string, isDir bool) (selectedForRestore bool, childMayBeSelected bool)
|
SelectFilter func(item string, isDir bool) (selectedForRestore bool, childMayBeSelected bool)
|
||||||
|
|
||||||
|
XattrSelectFilter func(xattrName string) (xattrSelectedForRestore bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
var restorerAbortOnAllErrors = func(_ string, err error) error { return err }
|
var restorerAbortOnAllErrors = func(_ string, err error) error { return err }
|
||||||
|
@ -288,7 +290,7 @@ func (res *Restorer) restoreNodeMetadataTo(node *restic.Node, target, location s
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
debug.Log("restoreNodeMetadata %v %v %v", node.Name, target, location)
|
debug.Log("restoreNodeMetadata %v %v %v", node.Name, target, location)
|
||||||
err := fs.NodeRestoreMetadata(node, target, res.Warn)
|
err := fs.NodeRestoreMetadata(node, target, res.Warn, res.XattrSelectFilter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
debug.Log("node.RestoreMetadata(%s) error %v", target, err)
|
debug.Log("node.RestoreMetadata(%s) error %v", target, err)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue