mirror of
https://github.com/octoleo/restic.git
synced 2024-11-16 01:57:10 +00:00
backup: Ignore xattr.list permission error for parent directories
On FreeBSD, limited users may not be able to even list xattrs for the parent directories above the snapshot source paths. As this can cause the backup to fail, just ignore those errors.
This commit is contained in:
parent
6091029fd6
commit
bf054c09d2
11
changelog/unreleased/issue-3600
Normal file
11
changelog/unreleased/issue-3600
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
Bugfix: `backup` works if xattrs above the backup target cannot be read
|
||||||
|
|
||||||
|
When backup targets are specified using absolute paths, then `backup` also
|
||||||
|
includes information about the parent folders of the backup targets in the
|
||||||
|
snapshot. If the extended attributes for some of these folders could not be
|
||||||
|
read due to missing permissions, this caused the backup to fail. This has been
|
||||||
|
fixed.
|
||||||
|
|
||||||
|
https://github.com/restic/restic/issues/3600
|
||||||
|
https://github.com/restic/restic/pull/4668
|
||||||
|
https://forum.restic.net/t/parent-directories-above-the-snapshot-source-path-fatal-error-permission-denied/7216
|
@ -237,8 +237,8 @@ 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) (*restic.Node, error) {
|
func (arch *Archiver) nodeFromFileInfo(snPath, filename string, fi os.FileInfo, ignoreXattrListError bool) (*restic.Node, error) {
|
||||||
node, err := restic.NodeFromFileInfo(filename, fi)
|
node, err := restic.NodeFromFileInfo(filename, fi, ignoreXattrListError)
|
||||||
if !arch.WithAtime {
|
if !arch.WithAtime {
|
||||||
node.AccessTime = node.ModTime
|
node.AccessTime = node.ModTime
|
||||||
}
|
}
|
||||||
@ -289,7 +289,7 @@ func (arch *Archiver) wrapLoadTreeError(id restic.ID, err error) error {
|
|||||||
func (arch *Archiver) saveDir(ctx context.Context, snPath string, dir string, fi os.FileInfo, previous *restic.Tree, complete CompleteFunc) (d FutureNode, err error) {
|
func (arch *Archiver) saveDir(ctx context.Context, snPath string, dir string, fi os.FileInfo, previous *restic.Tree, complete CompleteFunc) (d FutureNode, err error) {
|
||||||
debug.Log("%v %v", snPath, dir)
|
debug.Log("%v %v", snPath, dir)
|
||||||
|
|
||||||
treeNode, err := arch.nodeFromFileInfo(snPath, dir, fi)
|
treeNode, err := arch.nodeFromFileInfo(snPath, dir, fi, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return FutureNode{}, err
|
return FutureNode{}, err
|
||||||
}
|
}
|
||||||
@ -444,7 +444,7 @@ func (arch *Archiver) save(ctx context.Context, snPath, target string, previous
|
|||||||
debug.Log("%v hasn't changed, using old list of blobs", target)
|
debug.Log("%v hasn't changed, using old list of blobs", target)
|
||||||
arch.trackItem(snPath, previous, previous, ItemStats{}, time.Since(start))
|
arch.trackItem(snPath, previous, previous, ItemStats{}, time.Since(start))
|
||||||
arch.CompleteBlob(previous.Size)
|
arch.CompleteBlob(previous.Size)
|
||||||
node, err := arch.nodeFromFileInfo(snPath, target, fi)
|
node, err := arch.nodeFromFileInfo(snPath, target, fi, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return FutureNode{}, false, err
|
return FutureNode{}, false, err
|
||||||
}
|
}
|
||||||
@ -540,7 +540,7 @@ func (arch *Archiver) save(ctx context.Context, snPath, target string, previous
|
|||||||
default:
|
default:
|
||||||
debug.Log(" %v other", target)
|
debug.Log(" %v other", target)
|
||||||
|
|
||||||
node, err := arch.nodeFromFileInfo(snPath, target, fi)
|
node, err := arch.nodeFromFileInfo(snPath, target, fi, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return FutureNode{}, false, err
|
return FutureNode{}, false, err
|
||||||
}
|
}
|
||||||
@ -623,7 +623,9 @@ func (arch *Archiver) saveTree(ctx context.Context, snPath string, atree *Tree,
|
|||||||
}
|
}
|
||||||
|
|
||||||
debug.Log("%v, dir node data loaded from %v", snPath, atree.FileInfoPath)
|
debug.Log("%v, dir node data loaded from %v", snPath, atree.FileInfoPath)
|
||||||
node, err = arch.nodeFromFileInfo(snPath, atree.FileInfoPath, fi)
|
// in some cases reading xattrs for directories above the backup target is not allowed
|
||||||
|
// thus ignore errors for such folders.
|
||||||
|
node, err = arch.nodeFromFileInfo(snPath, atree.FileInfoPath, fi, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return FutureNode{}, 0, err
|
return FutureNode{}, 0, err
|
||||||
}
|
}
|
||||||
|
@ -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, filename string, fi os.FileInfo) *restic.Node {
|
||||||
node, err := restic.NodeFromFileInfo(filename, fi)
|
node, err := restic.NodeFromFileInfo(filename, fi, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -2230,7 +2230,7 @@ func TestMetadataChanged(t *testing.T) {
|
|||||||
|
|
||||||
// get metadata
|
// get metadata
|
||||||
fi := lstat(t, "testfile")
|
fi := lstat(t, "testfile")
|
||||||
want, err := restic.NodeFromFileInfo("testfile", fi)
|
want, err := restic.NodeFromFileInfo("testfile", fi, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,7 @@ func wrapFileInfo(fi os.FileInfo) os.FileInfo {
|
|||||||
|
|
||||||
func statAndSnapshot(t *testing.T, repo restic.Repository, name string) (*restic.Node, *restic.Node) {
|
func statAndSnapshot(t *testing.T, repo restic.Repository, name string) (*restic.Node, *restic.Node) {
|
||||||
fi := lstat(t, name)
|
fi := lstat(t, name)
|
||||||
want, err := restic.NodeFromFileInfo(name, fi)
|
want, err := restic.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.Local{}, nil, name)
|
||||||
|
@ -29,7 +29,7 @@ type FileSaver struct {
|
|||||||
|
|
||||||
CompleteBlob func(bytes uint64)
|
CompleteBlob func(bytes uint64)
|
||||||
|
|
||||||
NodeFromFileInfo func(snPath, filename string, fi os.FileInfo) (*restic.Node, error)
|
NodeFromFileInfo func(snPath, filename string, fi os.FileInfo, ignoreXattrListError bool) (*restic.Node, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFileSaver returns a new file saver. A worker pool with fileWorkers is
|
// NewFileSaver returns a new file saver. A worker pool with fileWorkers is
|
||||||
@ -156,7 +156,7 @@ func (s *FileSaver) saveFile(ctx context.Context, chnker *chunker.Chunker, snPat
|
|||||||
|
|
||||||
debug.Log("%v", snPath)
|
debug.Log("%v", snPath)
|
||||||
|
|
||||||
node, err := s.NodeFromFileInfo(snPath, f.Name(), fi)
|
node, err := s.NodeFromFileInfo(snPath, f.Name(), fi, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = f.Close()
|
_ = f.Close()
|
||||||
completeError(err)
|
completeError(err)
|
||||||
|
@ -49,8 +49,8 @@ func startFileSaver(ctx context.Context, t testing.TB) (*FileSaver, context.Cont
|
|||||||
}
|
}
|
||||||
|
|
||||||
s := NewFileSaver(ctx, wg, saveBlob, pol, workers, workers)
|
s := NewFileSaver(ctx, wg, saveBlob, pol, workers, workers)
|
||||||
s.NodeFromFileInfo = func(snPath, filename string, fi os.FileInfo) (*restic.Node, error) {
|
s.NodeFromFileInfo = func(snPath, filename string, fi os.FileInfo, ignoreXattrListError bool) (*restic.Node, error) {
|
||||||
return restic.NodeFromFileInfo(filename, fi)
|
return restic.NodeFromFileInfo(filename, fi, ignoreXattrListError)
|
||||||
}
|
}
|
||||||
|
|
||||||
return s, ctx, wg
|
return s, ctx, wg
|
||||||
|
@ -134,7 +134,7 @@ func (node Node) String() string {
|
|||||||
|
|
||||||
// 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) (*Node, error) {
|
func NodeFromFileInfo(path string, fi os.FileInfo, ignoreXattrListError bool) (*Node, error) {
|
||||||
mask := os.ModePerm | os.ModeType | os.ModeSetuid | os.ModeSetgid | os.ModeSticky
|
mask := os.ModePerm | os.ModeType | os.ModeSetuid | os.ModeSetgid | os.ModeSticky
|
||||||
node := &Node{
|
node := &Node{
|
||||||
Path: path,
|
Path: path,
|
||||||
@ -148,7 +148,7 @@ func NodeFromFileInfo(path string, fi os.FileInfo) (*Node, error) {
|
|||||||
node.Size = uint64(fi.Size())
|
node.Size = uint64(fi.Size())
|
||||||
}
|
}
|
||||||
|
|
||||||
err := node.fillExtra(path, fi)
|
err := node.fillExtra(path, fi, ignoreXattrListError)
|
||||||
return node, err
|
return node, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -675,7 +675,7 @@ func lookupGroup(gid uint32) string {
|
|||||||
return group
|
return group
|
||||||
}
|
}
|
||||||
|
|
||||||
func (node *Node) fillExtra(path string, fi os.FileInfo) error {
|
func (node *Node) fillExtra(path string, fi os.FileInfo, ignoreXattrListError bool) error {
|
||||||
stat, ok := toStatT(fi.Sys())
|
stat, ok := toStatT(fi.Sys())
|
||||||
if !ok {
|
if !ok {
|
||||||
// fill minimal info with current values for uid, gid
|
// fill minimal info with current values for uid, gid
|
||||||
@ -719,7 +719,7 @@ func (node *Node) fillExtra(path string, fi os.FileInfo) error {
|
|||||||
allowExtended, err := node.fillGenericAttributes(path, fi, stat)
|
allowExtended, err := node.fillGenericAttributes(path, fi, stat)
|
||||||
if allowExtended {
|
if allowExtended {
|
||||||
// Skip processing ExtendedAttributes if allowExtended is false.
|
// Skip processing ExtendedAttributes if allowExtended is false.
|
||||||
errEx := node.fillExtendedAttributes(path)
|
errEx := node.fillExtendedAttributes(path, ignoreXattrListError)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = errEx
|
err = errEx
|
||||||
} else {
|
} else {
|
||||||
@ -729,10 +729,13 @@ func (node *Node) fillExtra(path string, fi os.FileInfo) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (node *Node) fillExtendedAttributes(path string) error {
|
func (node *Node) fillExtendedAttributes(path string, ignoreListError bool) error {
|
||||||
xattrs, err := Listxattr(path)
|
xattrs, err := Listxattr(path)
|
||||||
debug.Log("fillExtendedAttributes(%v) %v %v", path, xattrs, err)
|
debug.Log("fillExtendedAttributes(%v) %v %v", path, xattrs, err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if ignoreListError && IsListxattrPermissionError(err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,6 +33,10 @@ func Listxattr(path string) ([]string, error) {
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsListxattrPermissionError(_ error) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Setxattr is a no-op on AIX.
|
// Setxattr is a no-op on AIX.
|
||||||
func Setxattr(path, name string, data []byte) error {
|
func Setxattr(path, name string, data []byte) error {
|
||||||
return nil
|
return nil
|
||||||
|
@ -23,6 +23,10 @@ func Listxattr(path string) ([]string, error) {
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsListxattrPermissionError(_ error) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Setxattr is a no-op on netbsd.
|
// Setxattr is a no-op on netbsd.
|
||||||
func Setxattr(path, name string, data []byte) error {
|
func Setxattr(path, name string, data []byte) error {
|
||||||
return nil
|
return nil
|
||||||
|
@ -23,6 +23,10 @@ func Listxattr(path string) ([]string, error) {
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsListxattrPermissionError(_ error) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Setxattr is a no-op on openbsd.
|
// Setxattr is a no-op on openbsd.
|
||||||
func Setxattr(path, name string, data []byte) error {
|
func Setxattr(path, name string, data []byte) error {
|
||||||
return nil
|
return nil
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
"github.com/restic/restic/internal/test"
|
"github.com/restic/restic/internal/test"
|
||||||
rtest "github.com/restic/restic/internal/test"
|
rtest "github.com/restic/restic/internal/test"
|
||||||
)
|
)
|
||||||
@ -31,7 +32,7 @@ func BenchmarkNodeFillUser(t *testing.B) {
|
|||||||
t.ResetTimer()
|
t.ResetTimer()
|
||||||
|
|
||||||
for i := 0; i < t.N; i++ {
|
for i := 0; i < t.N; i++ {
|
||||||
_, err := NodeFromFileInfo(path, fi)
|
_, err := NodeFromFileInfo(path, fi, false)
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,7 +56,7 @@ func BenchmarkNodeFromFileInfo(t *testing.B) {
|
|||||||
t.ResetTimer()
|
t.ResetTimer()
|
||||||
|
|
||||||
for i := 0; i < t.N; i++ {
|
for i := 0; i < t.N; i++ {
|
||||||
_, err := NodeFromFileInfo(path, fi)
|
_, err := NodeFromFileInfo(path, fi, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -227,8 +228,11 @@ 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)
|
n2, err := NodeFromFileInfo(nodePath, fi, false)
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
|
n3, err := NodeFromFileInfo(nodePath, fi, true)
|
||||||
|
rtest.OK(t, err)
|
||||||
|
rtest.Assert(t, n2.Equals(*n3), "unexpected node info mismatch %v", cmp.Diff(n2, n3))
|
||||||
|
|
||||||
rtest.Assert(t, test.Name == n2.Name,
|
rtest.Assert(t, test.Name == n2.Name,
|
||||||
"%v: name doesn't match (%v != %v)", test.Type, test.Name, n2.Name)
|
"%v: name doesn't match (%v != %v)", test.Type, test.Name, n2.Name)
|
||||||
|
@ -128,7 +128,7 @@ func TestNodeFromFileInfo(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
node, err := NodeFromFileInfo(test.filename, fi)
|
node, err := NodeFromFileInfo(test.filename, fi, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -78,6 +78,10 @@ func Listxattr(path string) ([]string, error) {
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsListxattrPermissionError(_ error) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Setxattr associates name and data together as an attribute of path.
|
// Setxattr associates name and data together as an attribute of path.
|
||||||
func Setxattr(path, name string, data []byte) error {
|
func Setxattr(path, name string, data []byte) error {
|
||||||
return nil
|
return nil
|
||||||
|
@ -165,7 +165,7 @@ func restoreAndGetNode(t *testing.T, tempDir string, testNode Node, warningExpec
|
|||||||
fi, err := os.Lstat(testPath)
|
fi, err := os.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)
|
nodeFromFileInfo, err := 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
|
||||||
|
@ -25,6 +25,14 @@ func Listxattr(path string) ([]string, error) {
|
|||||||
return l, handleXattrErr(err)
|
return l, handleXattrErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsListxattrPermissionError(err error) bool {
|
||||||
|
var xerr *xattr.Error
|
||||||
|
if errors.As(err, &xerr) {
|
||||||
|
return xerr.Op == "xattr.list" && errors.Is(xerr.Err, os.ErrPermission)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Setxattr associates name and data together as an attribute of path.
|
// Setxattr associates name and data together as an attribute of path.
|
||||||
func Setxattr(path, name string, data []byte) error {
|
func Setxattr(path, name string, data []byte) error {
|
||||||
return handleXattrErr(xattr.LSet(path, name, data))
|
return handleXattrErr(xattr.LSet(path, name, data))
|
||||||
|
28
internal/restic/node_xattr_test.go
Normal file
28
internal/restic/node_xattr_test.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
//go:build darwin || freebsd || linux || solaris
|
||||||
|
// +build darwin freebsd linux solaris
|
||||||
|
|
||||||
|
package restic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/pkg/xattr"
|
||||||
|
rtest "github.com/restic/restic/internal/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIsListxattrPermissionError(t *testing.T) {
|
||||||
|
xerr := &xattr.Error{
|
||||||
|
Op: "xattr.list",
|
||||||
|
Name: "test",
|
||||||
|
Err: os.ErrPermission,
|
||||||
|
}
|
||||||
|
err := handleXattrErr(xerr)
|
||||||
|
rtest.Assert(t, err != nil, "missing error")
|
||||||
|
rtest.Assert(t, IsListxattrPermissionError(err), "expected IsListxattrPermissionError to return true for %v", err)
|
||||||
|
|
||||||
|
xerr.Err = os.ErrNotExist
|
||||||
|
err = handleXattrErr(xerr)
|
||||||
|
rtest.Assert(t, err != nil, "missing error")
|
||||||
|
rtest.Assert(t, !IsListxattrPermissionError(err), "expected IsListxattrPermissionError to return false for %v", err)
|
||||||
|
}
|
@ -86,7 +86,7 @@ func TestNodeComparison(t *testing.T) {
|
|||||||
fi, err := os.Lstat("tree_test.go")
|
fi, err := os.Lstat("tree_test.go")
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
|
|
||||||
node, err := restic.NodeFromFileInfo("tree_test.go", fi)
|
node, err := restic.NodeFromFileInfo("tree_test.go", fi, false)
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
|
|
||||||
n2 := *node
|
n2 := *node
|
||||||
@ -127,7 +127,7 @@ func TestTreeEqualSerialization(t *testing.T) {
|
|||||||
for _, fn := range files[:i] {
|
for _, fn := range files[:i] {
|
||||||
fi, err := os.Lstat(fn)
|
fi, err := os.Lstat(fn)
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
node, err := restic.NodeFromFileInfo(fn, fi)
|
node, err := restic.NodeFromFileInfo(fn, fi, false)
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
|
|
||||||
rtest.OK(t, tree.Insert(node))
|
rtest.OK(t, tree.Insert(node))
|
||||||
|
Loading…
Reference in New Issue
Block a user