node: report error on xattr retrieval using standard error logging

This commit is contained in:
Michael Eischer
2025-09-21 19:24:48 +02:00
parent f2b9ea6455
commit df7924f4df
17 changed files with 30 additions and 29 deletions

View File

@@ -68,7 +68,7 @@ func (s *ItemStats) Add(other ItemStats) {
// ToNoder returns a restic.Node for a File. // ToNoder returns a restic.Node for a File.
type ToNoder interface { type ToNoder interface {
ToNode(ignoreXattrListError bool) (*restic.Node, error) ToNode(ignoreXattrListError bool, warnf func(format string, args ...any)) (*restic.Node, error)
} }
type archiverRepo interface { type archiverRepo interface {
@@ -263,7 +263,9 @@ 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, meta ToNoder, ignoreXattrListError bool) (*restic.Node, error) { func (arch *Archiver) nodeFromFileInfo(snPath, filename string, meta ToNoder, ignoreXattrListError bool) (*restic.Node, error) {
node, err := meta.ToNode(ignoreXattrListError) node, err := meta.ToNode(ignoreXattrListError, func(format string, args ...any) {
_ = arch.error(filename, fmt.Errorf(format, args...))
})
// node does not exist. This prevents all further processing for this file. // node does not exist. This prevents all further processing for this file.
// If an error and a node are returned, then preserve as much data as possible (see below). // If an error and a node are returned, then preserve as much data as possible (see below).
if err != nil && node == nil { if err != nil && node == nil {

View File

@@ -551,7 +551,7 @@ func rename(t testing.TB, oldname, newname string) {
func nodeFromFile(t testing.TB, localFs fs.FS, filename string) *restic.Node { func nodeFromFile(t testing.TB, localFs fs.FS, filename string) *restic.Node {
meta, err := localFs.OpenFile(filename, fs.O_NOFOLLOW, true) meta, err := localFs.OpenFile(filename, fs.O_NOFOLLOW, true)
rtest.OK(t, err) rtest.OK(t, err)
node, err := meta.ToNode(false) node, err := meta.ToNode(false, t.Logf)
rtest.OK(t, err) rtest.OK(t, err)
rtest.OK(t, meta.Close()) rtest.OK(t, meta.Close())
@@ -2287,9 +2287,9 @@ func (f overrideFile) MakeReadable() error {
return f.File.MakeReadable() return f.File.MakeReadable()
} }
func (f overrideFile) ToNode(ignoreXattrListError bool) (*restic.Node, error) { func (f overrideFile) ToNode(ignoreXattrListError bool, warnf func(format string, args ...any)) (*restic.Node, error) {
if f.ofs.overrideNode == nil { if f.ofs.overrideNode == nil {
return f.File.ToNode(ignoreXattrListError) return f.File.ToNode(ignoreXattrListError, warnf)
} }
return f.ofs.overrideNode, f.ofs.overrideErr return f.ofs.overrideNode, f.ofs.overrideErr
} }
@@ -2321,7 +2321,7 @@ func TestMetadataChanged(t *testing.T) {
localFS := &fs.Local{} localFS := &fs.Local{}
meta, err := localFS.OpenFile("testfile", fs.O_NOFOLLOW, true) meta, err := localFS.OpenFile("testfile", fs.O_NOFOLLOW, true)
rtest.OK(t, err) rtest.OK(t, err)
want, err := meta.ToNode(false) want, err := meta.ToNode(false, t.Logf)
rtest.OK(t, err) rtest.OK(t, err)
rtest.OK(t, meta.Close()) rtest.OK(t, meta.Close())
@@ -2455,7 +2455,7 @@ type mockToNoder struct {
err error err error
} }
func (m *mockToNoder) ToNode(_ bool) (*restic.Node, error) { func (m *mockToNoder) ToNode(_ bool, _ func(format string, args ...any)) (*restic.Node, error) {
return m.node, m.err return m.node, m.err
} }

View File

@@ -50,7 +50,7 @@ func startFileSaver(ctx context.Context, t testing.TB, _ fs.FS) (*fileSaver, con
s := newFileSaver(ctx, wg, saveBlob, pol, workers, workers) s := newFileSaver(ctx, wg, saveBlob, pol, workers, workers)
s.NodeFromFileInfo = func(snPath, filename string, meta ToNoder, ignoreXattrListError bool) (*restic.Node, error) { s.NodeFromFileInfo = func(snPath, filename string, meta ToNoder, ignoreXattrListError bool) (*restic.Node, error) {
return meta.ToNode(ignoreXattrListError) return meta.ToNode(ignoreXattrListError, t.Logf)
} }
return s, ctx, wg return s, ctx, wg

View File

@@ -152,11 +152,11 @@ func (f *localFile) Stat() (*ExtendedFileInfo, error) {
return f.fi, err return f.fi, err
} }
func (f *localFile) ToNode(ignoreXattrListError bool) (*restic.Node, error) { func (f *localFile) ToNode(ignoreXattrListError bool, warnf func(format string, args ...any)) (*restic.Node, error) {
if err := f.cacheFI(); err != nil { if err := f.cacheFI(); err != nil {
return nil, err return nil, err
} }
return nodeFromFileInfo(f.name, f.fi, ignoreXattrListError) return nodeFromFileInfo(f.name, f.fi, ignoreXattrListError, warnf)
} }
func (f *localFile) Read(p []byte) (n int, err error) { func (f *localFile) Read(p []byte) (n int, err error) {

View File

@@ -86,7 +86,7 @@ func checkMetadata(t *testing.T, f File, path string, follow bool, nodeType rest
rtest.OK(t, err) rtest.OK(t, err)
assertFIEqual(t, fi2, fi) assertFIEqual(t, fi2, fi)
node, err := f.ToNode(false) node, err := f.ToNode(false, t.Logf)
rtest.OK(t, err) rtest.OK(t, err)
// ModTime is likely unique per file, thus it provides a good indication that it is from the correct file // ModTime is likely unique per file, thus it provides a good indication that it is from the correct file

View File

@@ -333,7 +333,7 @@ func TestVSSFS(t *testing.T) {
rtest.OK(t, err) rtest.OK(t, err)
rtest.Equals(t, "example", string(data), "unexpected file content") rtest.Equals(t, "example", string(data), "unexpected file content")
node, err := f.ToNode(false) node, err := f.ToNode(false, t.Logf)
rtest.OK(t, err) rtest.OK(t, err)
rtest.Equals(t, node.Mode, lstatFi.Mode) rtest.Equals(t, node.Mode, lstatFi.Mode)

View File

@@ -267,7 +267,7 @@ func (f fakeFile) Stat() (*ExtendedFileInfo, error) {
return f.fi, nil return f.fi, nil
} }
func (f fakeFile) ToNode(_ bool) (*restic.Node, error) { func (f fakeFile) ToNode(_ bool, _ func(format string, args ...any)) (*restic.Node, error) {
node := buildBasicNode(f.name, f.fi) node := buildBasicNode(f.name, f.fi)
// fill minimal info with current values for uid, gid // fill minimal info with current values for uid, gid

View File

@@ -48,5 +48,5 @@ type File interface {
// ToNode returns a restic.Node for the File. The internally used os.FileInfo // ToNode returns a restic.Node for the File. The internally used os.FileInfo
// must be consistent with that returned by Stat(). In particular, the metadata // must be consistent with that returned by Stat(). In particular, the metadata
// returned by consecutive calls to Stat() and ToNode() must match. // returned by consecutive calls to Stat() and ToNode() must match.
ToNode(ignoreXattrListError bool) (*restic.Node, error) ToNode(ignoreXattrListError bool, warnf func(format string, args ...any)) (*restic.Node, error)
} }

View File

@@ -15,7 +15,7 @@ import (
// 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 *ExtendedFileInfo, ignoreXattrListError bool) (*restic.Node, error) { func nodeFromFileInfo(path string, fi *ExtendedFileInfo, ignoreXattrListError bool, warnf func(format string, args ...any)) (*restic.Node, error) {
node := buildBasicNode(path, fi) node := buildBasicNode(path, fi)
if err := nodeFillExtendedStat(node, path, fi); err != nil { if err := nodeFillExtendedStat(node, path, fi); err != nil {
@@ -23,7 +23,7 @@ func nodeFromFileInfo(path string, fi *ExtendedFileInfo, ignoreXattrListError bo
} }
err := nodeFillGenericAttributes(node, path, fi) err := nodeFillGenericAttributes(node, path, fi)
err = errors.Join(err, nodeFillExtendedAttributes(node, path, ignoreXattrListError)) err = errors.Join(err, nodeFillExtendedAttributes(node, path, ignoreXattrListError, warnf))
return node, err return node, err
} }

View File

@@ -13,6 +13,6 @@ func nodeRestoreExtendedAttributes(_ *restic.Node, _ string, _ func(xattrName st
} }
// nodeFillExtendedAttributes is a no-op // nodeFillExtendedAttributes is a no-op
func nodeFillExtendedAttributes(_ *restic.Node, _ string, _ bool) error { func nodeFillExtendedAttributes(_ *restic.Node, _ string, _ bool, _ func(format string, args ...any)) error {
return nil return nil
} }

View File

@@ -31,7 +31,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 := f.ToNode(false) _, err := f.ToNode(false, t.Logf)
rtest.OK(t, err) rtest.OK(t, err)
} }
@@ -223,9 +223,9 @@ func TestNodeRestoreAt(t *testing.T) {
fs := &Local{} fs := &Local{}
meta, err := fs.OpenFile(nodePath, O_NOFOLLOW, true) meta, err := fs.OpenFile(nodePath, O_NOFOLLOW, true)
rtest.OK(t, err) rtest.OK(t, err)
n2, err := meta.ToNode(false) n2, err := meta.ToNode(false, t.Logf)
rtest.OK(t, err) rtest.OK(t, err)
n3, err := meta.ToNode(true) n3, err := meta.ToNode(true, t.Logf)
rtest.OK(t, err) rtest.OK(t, err)
rtest.OK(t, meta.Close()) rtest.OK(t, meta.Close())
rtest.Assert(t, n2.Equals(*n3), "unexpected node info mismatch %v", cmp.Diff(n2, n3)) rtest.Assert(t, n2.Equals(*n3), "unexpected node info mismatch %v", cmp.Diff(n2, n3))

View File

@@ -117,7 +117,7 @@ func TestNodeFromFileInfo(t *testing.T) {
fs := &Local{} fs := &Local{}
meta, err := fs.OpenFile(test.filename, O_NOFOLLOW, true) meta, err := fs.OpenFile(test.filename, O_NOFOLLOW, true)
rtest.OK(t, err) rtest.OK(t, err)
node, err := meta.ToNode(false) node, err := meta.ToNode(false, t.Logf)
rtest.OK(t, err) rtest.OK(t, err)
rtest.OK(t, meta.Close()) rtest.OK(t, meta.Close())

View File

@@ -91,7 +91,7 @@ func nodeRestoreExtendedAttributes(node *restic.Node, path string, xattrSelectFi
// fill extended attributes in the node // fill extended attributes in the node
// It also checks if the volume supports extended attributes and stores the result in a map // It also checks if the volume supports extended attributes and stores the result in a map
// so that it does not have to be checked again for subsequent calls for paths in the same volume. // so that it does not have to be checked again for subsequent calls for paths in the same volume.
func nodeFillExtendedAttributes(node *restic.Node, path string, _ bool) (err error) { func nodeFillExtendedAttributes(node *restic.Node, path string, _ bool, _ func(format string, args ...any)) (err error) {
if strings.Contains(filepath.Base(path), ":") { if strings.Contains(filepath.Base(path), ":") {
// Do not process for Alternate Data Streams in Windows // Do not process for Alternate Data Streams in Windows
return nil return nil

View File

@@ -224,7 +224,7 @@ func restoreAndGetNode(t *testing.T, tempDir string, testNode *restic.Node, warn
fs := &Local{} fs := &Local{}
meta, err := fs.OpenFile(testPath, O_NOFOLLOW, true) meta, err := fs.OpenFile(testPath, O_NOFOLLOW, true)
test.OK(t, err) test.OK(t, err)
nodeFromFileInfo, err := meta.ToNode(false) nodeFromFileInfo, err := meta.ToNode(false, t.Logf)
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))
test.OK(t, meta.Close()) test.OK(t, meta.Close())

View File

@@ -4,7 +4,6 @@
package fs package fs
import ( import (
"fmt"
"os" "os"
"syscall" "syscall"
@@ -97,7 +96,7 @@ func nodeRestoreExtendedAttributes(node *restic.Node, path string, xattrSelectFi
return nil return nil
} }
func nodeFillExtendedAttributes(node *restic.Node, path string, ignoreListError bool) error { func nodeFillExtendedAttributes(node *restic.Node, path string, ignoreListError bool, warnf func(format string, args ...any)) 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 {
@@ -111,7 +110,7 @@ func nodeFillExtendedAttributes(node *restic.Node, path string, ignoreListError
for _, attr := range xattrs { for _, attr := range xattrs {
attrVal, err := getxattr(path, attr) attrVal, err := getxattr(path, attr)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "can not obtain extended attribute %v for %v:\n", attr, path) warnf("can not obtain extended attribute %v for %v:\n", attr, path)
continue continue
} }
attr := restic.ExtendedAttribute{ attr := restic.ExtendedAttribute{

View File

@@ -34,7 +34,7 @@ func setAndVerifyXattr(t *testing.T, file string, attrs []restic.ExtendedAttribu
nodeActual := &restic.Node{ nodeActual := &restic.Node{
Type: restic.NodeTypeFile, Type: restic.NodeTypeFile,
} }
rtest.OK(t, nodeFillExtendedAttributes(nodeActual, file, false)) rtest.OK(t, nodeFillExtendedAttributes(nodeActual, file, false, t.Logf))
rtest.Assert(t, nodeActual.Equals(*node), "xattr mismatch got %v expected %v", nodeActual.ExtendedAttributes, node.ExtendedAttributes) rtest.Assert(t, nodeActual.Equals(*node), "xattr mismatch got %v expected %v", nodeActual.ExtendedAttributes, node.ExtendedAttributes)
} }
@@ -59,7 +59,7 @@ func setAndVerifyXattrWithSelectFilter(t *testing.T, file string, testAttr []tes
nodeActual := &restic.Node{ nodeActual := &restic.Node{
Type: restic.NodeTypeFile, Type: restic.NodeTypeFile,
} }
rtest.OK(t, nodeFillExtendedAttributes(nodeActual, file, false)) rtest.OK(t, nodeFillExtendedAttributes(nodeActual, file, false, t.Logf))
// Check nodeActual to make sure only xattrs we expect are there // Check nodeActual to make sure only xattrs we expect are there
for _, testAttr := range testAttr { for _, testAttr := range testAttr {

View File

@@ -86,7 +86,7 @@ func TestNodeMarshal(t *testing.T) {
func nodeForFile(t *testing.T, name string) *restic.Node { func nodeForFile(t *testing.T, name string) *restic.Node {
f, err := (&fs.Local{}).OpenFile(name, fs.O_NOFOLLOW, true) f, err := (&fs.Local{}).OpenFile(name, fs.O_NOFOLLOW, true)
rtest.OK(t, err) rtest.OK(t, err)
node, err := f.ToNode(false) node, err := f.ToNode(false, t.Logf)
rtest.OK(t, err) rtest.OK(t, err)
rtest.OK(t, f.Close()) rtest.OK(t, f.Close())
return node return node