local: disable xattr support if the filesystems indicates it is not supported

Before this change, if rclone was run with `-M` on a filesystem
without xattr support, it would error out.

This patch makes rclone detect the not supported errors and disable
xattrs from then on. It prints one ERROR level message about this.

See: https://forum.rclone.org/t/metadata-update-local-s3/32277/7
This commit is contained in:
Nick Craig-Wood 2022-08-08 10:03:25 +01:00
parent 9f33eb2e65
commit d347ac0154
2 changed files with 44 additions and 11 deletions

View File

@ -234,15 +234,16 @@ type Options struct {
// Fs represents a local filesystem rooted at root // Fs represents a local filesystem rooted at root
type Fs struct { type Fs struct {
name string // the name of the remote name string // the name of the remote
root string // The root directory (OS path) root string // The root directory (OS path)
opt Options // parsed config options opt Options // parsed config options
features *fs.Features // optional features features *fs.Features // optional features
dev uint64 // device number of root node dev uint64 // device number of root node
precisionOk sync.Once // Whether we need to read the precision precisionOk sync.Once // Whether we need to read the precision
precision time.Duration // precision of local filesystem precision time.Duration // precision of local filesystem
warnedMu sync.Mutex // used for locking access to 'warned'. warnedMu sync.Mutex // used for locking access to 'warned'.
warned map[string]struct{} // whether we have warned about this string warned map[string]struct{} // whether we have warned about this string
xattrSupported int32 // whether xattrs are supported (atomic access)
// do os.Lstat or os.Stat // do os.Lstat or os.Stat
lstat func(name string) (os.FileInfo, error) lstat func(name string) (os.FileInfo, error)
@ -286,6 +287,9 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
dev: devUnset, dev: devUnset,
lstat: os.Lstat, lstat: os.Lstat,
} }
if xattrSupported {
f.xattrSupported = 1
}
f.root = cleanRootPath(root, f.opt.NoUNC, f.opt.Enc) f.root = cleanRootPath(root, f.opt.NoUNC, f.opt.Enc)
f.features = (&fs.Features{ f.features = (&fs.Features{
CaseInsensitive: f.caseInsensitive(), CaseInsensitive: f.caseInsensitive(),

View File

@ -6,6 +6,8 @@ package local
import ( import (
"fmt" "fmt"
"strings" "strings"
"sync/atomic"
"syscall"
"github.com/pkg/xattr" "github.com/pkg/xattr"
"github.com/rclone/rclone/fs" "github.com/rclone/rclone/fs"
@ -16,12 +18,30 @@ const (
xattrSupported = xattr.XATTR_SUPPORTED xattrSupported = xattr.XATTR_SUPPORTED
) )
// Check to see if the error supplied is a not supported error, and if
// so, disable xattrs
func (f *Fs) xattrIsNotSupported(err error) bool {
xattrErr, ok := err.(*xattr.Error)
if !ok {
return false
}
// Xattrs not supported can be ENOTSUP or ENOATTR or EINVAL (on Solaris)
if xattrErr.Err == syscall.EINVAL || xattrErr.Err == syscall.ENOTSUP || xattrErr.Err == xattr.ENOATTR {
// Show xattrs not supported
if atomic.CompareAndSwapInt32(&f.xattrSupported, 1, 0) {
fs.Errorf(f, "xattrs not supported - disabling: %v", err)
}
return true
}
return false
}
// getXattr returns the extended attributes for an object // getXattr returns the extended attributes for an object
// //
// It doesn't return any attributes owned by this backend in // It doesn't return any attributes owned by this backend in
// metadataKeys // metadataKeys
func (o *Object) getXattr() (metadata fs.Metadata, err error) { func (o *Object) getXattr() (metadata fs.Metadata, err error) {
if !xattrSupported { if !xattrSupported || atomic.LoadInt32(&o.fs.xattrSupported) == 0 {
return nil, nil return nil, nil
} }
var list []string var list []string
@ -31,6 +51,9 @@ func (o *Object) getXattr() (metadata fs.Metadata, err error) {
list, err = xattr.LList(o.path) list, err = xattr.LList(o.path)
} }
if err != nil { if err != nil {
if o.fs.xattrIsNotSupported(err) {
return nil, nil
}
return nil, fmt.Errorf("failed to read xattr: %w", err) return nil, fmt.Errorf("failed to read xattr: %w", err)
} }
if len(list) == 0 { if len(list) == 0 {
@ -45,6 +68,9 @@ func (o *Object) getXattr() (metadata fs.Metadata, err error) {
v, err = xattr.LGet(o.path, k) v, err = xattr.LGet(o.path, k)
} }
if err != nil { if err != nil {
if o.fs.xattrIsNotSupported(err) {
return nil, nil
}
return nil, fmt.Errorf("failed to read xattr key %q: %w", k, err) return nil, fmt.Errorf("failed to read xattr key %q: %w", k, err)
} }
k = strings.ToLower(k) k = strings.ToLower(k)
@ -64,7 +90,7 @@ func (o *Object) getXattr() (metadata fs.Metadata, err error) {
// //
// It doesn't set any attributes owned by this backend in metadataKeys // It doesn't set any attributes owned by this backend in metadataKeys
func (o *Object) setXattr(metadata fs.Metadata) (err error) { func (o *Object) setXattr(metadata fs.Metadata) (err error) {
if !xattrSupported { if !xattrSupported || atomic.LoadInt32(&o.fs.xattrSupported) == 0 {
return nil return nil
} }
for k, value := range metadata { for k, value := range metadata {
@ -80,6 +106,9 @@ func (o *Object) setXattr(metadata fs.Metadata) (err error) {
err = xattr.LSet(o.path, k, v) err = xattr.LSet(o.path, k, v)
} }
if err != nil { if err != nil {
if o.fs.xattrIsNotSupported(err) {
return nil
}
return fmt.Errorf("failed to set xattr key %q: %w", k, err) return fmt.Errorf("failed to set xattr key %q: %w", k, err)
} }
} }