This adds support for syncing extended attributes on supported filesystem on Linux, macOS, FreeBSD and NetBSD. Windows is currently excluded because the APIs seem onerous and annoying and frankly the uses cases seem few and far between. On Unixes this also covers ACLs as those are stored as extended attributes. Similar to ownership syncing this will optional & opt-in, which two settings controlling the main behavior: one to "sync" xattrs (read & write) and another one to "scan" xattrs (only read them so other devices can "sync" them, but not apply any locally). Co-authored-by: Tomasz Wilczyński <twilczynski@naver.com>
This commit is contained in:
co-authored by
Tomasz Wilczyński
parent
8065cf7e97
commit
6cac308bcd
@@ -55,7 +55,7 @@ func (i infiniteFS) Open(name string) (fs.File, error) {
|
||||
return &fakeFile{name, i.filesize, 0}, nil
|
||||
}
|
||||
|
||||
func (infiniteFS) PlatformData(_ string) (protocol.PlatformData, error) {
|
||||
func (infiniteFS) PlatformData(_ string, _, _ bool, _ fs.XattrFilter) (protocol.PlatformData, error) {
|
||||
return protocol.PlatformData{}, nil
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ func (singleFileFS) Options() []fs.Option {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (singleFileFS) PlatformData(_ string) (protocol.PlatformData, error) {
|
||||
func (singleFileFS) PlatformData(_ string, _, _ bool, _ fs.XattrFilter) (protocol.PlatformData, error) {
|
||||
return protocol.PlatformData{}, nil
|
||||
}
|
||||
|
||||
@@ -121,10 +121,12 @@ func (fakeInfo) ModTime() time.Time { return time.Unix(1234567890, 0) }
|
||||
func (f fakeInfo) IsDir() bool {
|
||||
return strings.Contains(filepath.Base(f.name), "dir") || f.name == "."
|
||||
}
|
||||
func (f fakeInfo) IsRegular() bool { return !f.IsDir() }
|
||||
func (fakeInfo) IsSymlink() bool { return false }
|
||||
func (fakeInfo) Owner() int { return 0 }
|
||||
func (fakeInfo) Group() int { return 0 }
|
||||
func (f fakeInfo) IsRegular() bool { return !f.IsDir() }
|
||||
func (fakeInfo) IsSymlink() bool { return false }
|
||||
func (fakeInfo) Owner() int { return 0 }
|
||||
func (fakeInfo) Group() int { return 0 }
|
||||
func (fakeInfo) Sys() interface{} { return nil }
|
||||
func (fakeInfo) InodeChangeTime() time.Time { return time.Time{} }
|
||||
|
||||
type fakeFile struct {
|
||||
name string
|
||||
|
||||
+29
-18
@@ -42,8 +42,6 @@ type Config struct {
|
||||
// If IgnorePerms is true, changes to permission bits will not be
|
||||
// detected.
|
||||
IgnorePerms bool
|
||||
// If IgnoreOwnership is true, changes to ownership will not be detected.
|
||||
IgnoreOwnership bool
|
||||
// When AutoNormalize is set, file names that are in UTF8 but incorrect
|
||||
// normalization form will be corrected.
|
||||
AutoNormalize bool
|
||||
@@ -62,6 +60,10 @@ type Config struct {
|
||||
EventLogger events.Logger
|
||||
// If ScanOwnership is true, we pick up ownership information on files while scanning.
|
||||
ScanOwnership bool
|
||||
// If ScanXattrs is true, we pick up extended attributes on files while scanning.
|
||||
ScanXattrs bool
|
||||
// Filter for extended attributes
|
||||
XattrFilter XattrFilter
|
||||
}
|
||||
|
||||
type CurrentFiler interface {
|
||||
@@ -69,6 +71,12 @@ type CurrentFiler interface {
|
||||
CurrentFile(name string) (protocol.FileInfo, bool)
|
||||
}
|
||||
|
||||
type XattrFilter interface {
|
||||
Permit(string) bool
|
||||
GetMaxSingleEntrySize() int
|
||||
GetMaxTotalSize() int
|
||||
}
|
||||
|
||||
type ScanResult struct {
|
||||
File protocol.FileInfo
|
||||
Err error
|
||||
@@ -384,7 +392,7 @@ func (w *walker) walkRegular(ctx context.Context, relPath string, info fs.FileIn
|
||||
}
|
||||
}
|
||||
|
||||
f, err := CreateFileInfo(info, relPath, w.Filesystem, w.ScanOwnership)
|
||||
f, err := CreateFileInfo(info, relPath, w.Filesystem, w.ScanOwnership, w.ScanXattrs, w.XattrFilter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -398,7 +406,8 @@ func (w *walker) walkRegular(ctx context.Context, relPath string, info fs.FileIn
|
||||
IgnorePerms: w.IgnorePerms,
|
||||
IgnoreBlocks: true,
|
||||
IgnoreFlags: w.LocalFlags,
|
||||
IgnoreOwnership: w.IgnoreOwnership,
|
||||
IgnoreOwnership: !w.ScanOwnership,
|
||||
IgnoreXattrs: !w.ScanXattrs,
|
||||
}) {
|
||||
l.Debugln(w, "unchanged:", curFile, info.ModTime().Unix(), info.Mode()&fs.ModePerm)
|
||||
return nil
|
||||
@@ -428,7 +437,7 @@ func (w *walker) walkRegular(ctx context.Context, relPath string, info fs.FileIn
|
||||
func (w *walker) walkDir(ctx context.Context, relPath string, info fs.FileInfo, finishedChan chan<- ScanResult) error {
|
||||
curFile, hasCurFile := w.CurrentFiler.CurrentFile(relPath)
|
||||
|
||||
f, err := CreateFileInfo(info, relPath, w.Filesystem, w.ScanOwnership)
|
||||
f, err := CreateFileInfo(info, relPath, w.Filesystem, w.ScanOwnership, w.ScanXattrs, w.XattrFilter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -441,7 +450,8 @@ func (w *walker) walkDir(ctx context.Context, relPath string, info fs.FileInfo,
|
||||
IgnorePerms: w.IgnorePerms,
|
||||
IgnoreBlocks: true,
|
||||
IgnoreFlags: w.LocalFlags,
|
||||
IgnoreOwnership: w.IgnoreOwnership,
|
||||
IgnoreOwnership: !w.ScanOwnership,
|
||||
IgnoreXattrs: !w.ScanXattrs,
|
||||
}) {
|
||||
l.Debugln(w, "unchanged:", curFile, info.ModTime().Unix(), info.Mode()&fs.ModePerm)
|
||||
return nil
|
||||
@@ -476,9 +486,9 @@ func (w *walker) walkSymlink(ctx context.Context, relPath string, info fs.FileIn
|
||||
return nil
|
||||
}
|
||||
|
||||
f, err := CreateFileInfo(info, relPath, w.Filesystem, w.ScanOwnership)
|
||||
f, err := CreateFileInfo(info, relPath, w.Filesystem, w.ScanOwnership, w.ScanXattrs, w.XattrFilter)
|
||||
if err != nil {
|
||||
handleError(ctx, "reading link:", relPath, err, finishedChan)
|
||||
handleError(ctx, "reading link", relPath, err, finishedChan)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -492,7 +502,8 @@ func (w *walker) walkSymlink(ctx context.Context, relPath string, info fs.FileIn
|
||||
IgnorePerms: w.IgnorePerms,
|
||||
IgnoreBlocks: true,
|
||||
IgnoreFlags: w.LocalFlags,
|
||||
IgnoreOwnership: w.IgnoreOwnership,
|
||||
IgnoreOwnership: !w.ScanOwnership,
|
||||
IgnoreXattrs: !w.ScanXattrs,
|
||||
}) {
|
||||
l.Debugln(w, "unchanged:", curFile, info.ModTime().Unix(), info.Mode()&fs.ModePerm)
|
||||
return nil
|
||||
@@ -591,12 +602,7 @@ func (w *walker) updateFileInfo(dst, src protocol.FileInfo) protocol.FileInfo {
|
||||
dst.LocalFlags = w.LocalFlags
|
||||
|
||||
// Copy OS data from src to dst, unless it was already set on dst.
|
||||
if dst.Platform.Unix == nil {
|
||||
dst.Platform.Unix = src.Platform.Unix
|
||||
}
|
||||
if dst.Platform.Windows == nil {
|
||||
dst.Platform.Windows = src.Platform.Windows
|
||||
}
|
||||
dst.Platform.MergeWith(&src.Platform)
|
||||
|
||||
return dst
|
||||
}
|
||||
@@ -668,10 +674,10 @@ func (noCurrentFiler) CurrentFile(_ string) (protocol.FileInfo, bool) {
|
||||
return protocol.FileInfo{}, false
|
||||
}
|
||||
|
||||
func CreateFileInfo(fi fs.FileInfo, name string, filesystem fs.Filesystem, scanOwnership bool) (protocol.FileInfo, error) {
|
||||
func CreateFileInfo(fi fs.FileInfo, name string, filesystem fs.Filesystem, scanOwnership bool, scanXattrs bool, xattrFilter XattrFilter) (protocol.FileInfo, error) {
|
||||
f := protocol.FileInfo{Name: name}
|
||||
if scanOwnership {
|
||||
if plat, err := filesystem.PlatformData(name); err == nil {
|
||||
if scanOwnership || scanXattrs {
|
||||
if plat, err := filesystem.PlatformData(name, scanOwnership, scanXattrs, xattrFilter); err == nil {
|
||||
f.Platform = plat
|
||||
} else {
|
||||
return protocol.FileInfo{}, fmt.Errorf("reading platform data: %w", err)
|
||||
@@ -696,5 +702,10 @@ func CreateFileInfo(fi fs.FileInfo, name string, filesystem fs.Filesystem, scanO
|
||||
}
|
||||
f.Size = fi.Size()
|
||||
f.Type = protocol.FileInfoTypeFile
|
||||
if ct := fi.InodeChangeTime(); !ct.IsZero() {
|
||||
f.InodeChangeNs = ct.UnixNano()
|
||||
} else {
|
||||
f.InodeChangeNs = 0
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user