all: Support syncing extended attributes (fixes #2698) (#8513)

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:
Jakob Borg
2022-09-14 09:50:55 +02:00
committed by GitHub
co-authored by Tomasz Wilczyński
parent 8065cf7e97
commit 6cac308bcd
37 changed files with 2720 additions and 527 deletions
+63 -5
View File
@@ -3216,10 +3216,28 @@ func TestConnCloseOnRestart(t *testing.T) {
}
func TestModTimeWindow(t *testing.T) {
// This test doesn't work any more, because changing the file like we do
// in the test below changes the inode time, which we detect
// (correctly). The test could be fixed by having a filesystem wrapper
// around fakeFs or basicFs that lies in the returned modtime (like FAT
// does...), but injecting such a wrapper isn't trivial. The filesystem
// is created by FolderConfiguration, so it would require a new
// filesystem type, which is really ugly, or creating a
// FilesystemFactory object that would create filesystems based on
// configs and would be injected into the model. But that's a major
// refactor. Adding a test-only override of the filesystem to the
// FolderConfiguration could be neat, but the FolderConfiguration is
// generated by protobuf so this is also a little tricky or at least
// ugly. I'm leaving it like this for now.
t.Skip("this test is currently broken")
w, fcfg, wCancel := tmpDefaultWrapper(t)
defer wCancel()
tfs := fcfg.Filesystem(nil)
fcfg.RawModTimeWindowS = 2
tfs := modtimeTruncatingFS{
trunc: 0,
Filesystem: fcfg.Filesystem(nil),
}
// fcfg.RawModTimeWindowS = 2
setFolder(t, w, fcfg)
m := setupModel(t, w)
defer cleanupModelAndRemoveDir(m, tfs.URI())
@@ -3243,10 +3261,12 @@ func TestModTimeWindow(t *testing.T) {
}
v := fi.Version
// Update time on disk 1s
// Change the filesystem to only return modtimes to the closest two
// seconds, like FAT.
err = tfs.Chtimes(name, time.Now(), modTime.Add(time.Second))
must(t, err)
tfs.trunc = 2 * time.Second
// Scan again
m.ScanFolders()
@@ -4305,3 +4325,41 @@ func equalStringsInAnyOrder(a, b []string) bool {
}
return true
}
// modtimeTruncatingFS is a FileSystem that returns modification times only
// to the closest two `trunc` interval.
type modtimeTruncatingFS struct {
trunc time.Duration
fs.Filesystem
}
func (f modtimeTruncatingFS) Lstat(name string) (fs.FileInfo, error) {
fmt.Println("lstat", name)
info, err := f.Filesystem.Lstat(name)
return modtimeTruncatingFileInfo{trunc: f.trunc, FileInfo: info}, err
}
func (f modtimeTruncatingFS) Stat(name string) (fs.FileInfo, error) {
fmt.Println("stat", name)
info, err := f.Filesystem.Stat(name)
return modtimeTruncatingFileInfo{trunc: f.trunc, FileInfo: info}, err
}
func (f modtimeTruncatingFS) Walk(root string, walkFn fs.WalkFunc) error {
return f.Filesystem.Walk(root, func(path string, info fs.FileInfo, err error) error {
if err != nil {
return walkFn(path, nil, err)
}
fmt.Println("walk", info.Name())
return walkFn(path, modtimeTruncatingFileInfo{trunc: f.trunc, FileInfo: info}, nil)
})
}
type modtimeTruncatingFileInfo struct {
trunc time.Duration
fs.FileInfo
}
func (fi modtimeTruncatingFileInfo) ModTime() time.Time {
return fi.FileInfo.ModTime().Truncate(fi.trunc)
}