mirror of
https://github.com/octoleo/syncthing.git
synced 2024-12-23 03:18:59 +00:00
6cac308bcd
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>
102 lines
2.8 KiB
Go
102 lines
2.8 KiB
Go
// Copyright (C) 2022 The Syncthing Authors.
|
|
//
|
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
|
|
//go:build freebsd || netbsd
|
|
// +build freebsd netbsd
|
|
|
|
package fs
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"sort"
|
|
"unsafe"
|
|
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
var (
|
|
namespaces = [...]int{unix.EXTATTR_NAMESPACE_USER, unix.EXTATTR_NAMESPACE_SYSTEM}
|
|
namespacePrefixes = [...]string{unix.EXTATTR_NAMESPACE_USER: "user.", unix.EXTATTR_NAMESPACE_SYSTEM: "system."}
|
|
)
|
|
|
|
func listXattr(path string) ([]string, error) {
|
|
var attrs []string
|
|
|
|
// List the two namespaces explicitly and prefix any results with the
|
|
// namespace name.
|
|
for _, nsid := range namespaces {
|
|
buf := make([]byte, 1024)
|
|
size, err := unixLlistxattr(path, buf, nsid)
|
|
if errors.Is(err, unix.ERANGE) || size == len(buf) {
|
|
// Buffer is too small. Try again with a zero sized buffer to
|
|
// get the size, then allocate a buffer of the correct size. We
|
|
// inlude the size == len(buf) because apparently macOS doesn't
|
|
// return ERANGE as it should -- no harm done, just an extra
|
|
// read if we happened to need precisely 1024 bytes on the first
|
|
// pass.
|
|
size, err = unixLlistxattr(path, nil, nsid)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Listxattr %s: %w", path, err)
|
|
}
|
|
buf = make([]byte, size)
|
|
size, err = unixLlistxattr(path, buf, nsid)
|
|
}
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Listxattr %s: %w", path, err)
|
|
}
|
|
|
|
buf = buf[:size]
|
|
|
|
// "Each list entry consists of a single byte containing the length
|
|
// of the attribute name, followed by the attribute name. The
|
|
// attribute name is not terminated by ASCII 0 (nul)."
|
|
i := 0
|
|
for i < len(buf) {
|
|
l := int(buf[i])
|
|
i++
|
|
if i+l > len(buf) {
|
|
// uh-oh
|
|
return nil, fmt.Errorf("get xattr %s: attribute length %d at offset %d exceeds buffer length %d", path, l, i, len(buf))
|
|
}
|
|
if l > 0 {
|
|
attrs = append(attrs, namespacePrefixes[nsid]+string(buf[i:i+l]))
|
|
i += l
|
|
}
|
|
}
|
|
}
|
|
|
|
sort.Strings(attrs)
|
|
return attrs, nil
|
|
}
|
|
|
|
// This is unix.Llistxattr except taking a namespace parameter to dodge
|
|
// https://github.com/golang/go/issues/54357 ("Listxattr on FreeBSD loses
|
|
// namespace info")
|
|
func unixLlistxattr(link string, dest []byte, nsid int) (sz int, err error) {
|
|
d := initxattrdest(dest, 0)
|
|
destsiz := len(dest)
|
|
|
|
s, e := unix.ExtattrListLink(link, nsid, uintptr(d), destsiz)
|
|
if e != nil && e == unix.EPERM && nsid != unix.EXTATTR_NAMESPACE_USER {
|
|
return 0, nil
|
|
} else if e != nil {
|
|
return s, e
|
|
}
|
|
|
|
return s, nil
|
|
}
|
|
|
|
var _zero uintptr
|
|
|
|
func initxattrdest(dest []byte, idx int) (d unsafe.Pointer) {
|
|
if len(dest) > idx {
|
|
return unsafe.Pointer(&dest[idx])
|
|
} else {
|
|
return unsafe.Pointer(_zero)
|
|
}
|
|
}
|