Improve error handling and setting of xattr and file flags

- Improve error handling of file flags and xattrs. You now get warnings
- file flags on *BSD/Darwin use a mask and combine with the user/superuser
mask to prevent spurious warnings. The mask is not yet implemented as
a preference
- error handling/report for reading special files
- bump to go 1.20 for errors.Join
This commit is contained in:
2023-10-05 21:43:02 -05:00
parent 7df1ba5d82
commit 42ee20a9e1
9 changed files with 382 additions and 284 deletions

View File

@@ -10,110 +10,148 @@ package duplicacy
import (
"bytes"
"encoding/binary"
"errors"
"os"
"path/filepath"
"syscall"
"unsafe"
"github.com/pkg/xattr"
)
const bsd_UF_NODUMP = 0x1
const (
bsd_UF_NODUMP = 0x1
bsd_SF_SETTABLE = 0xffff0000
bsd_UF_SETTABLE = 0x0000ffff
const bsdFileFlagsKey = "\x00bf"
bsdFileFlagsKey = "\x00bf"
)
var bsdIsSuperUser bool
func init() {
bsdIsSuperUser = syscall.Geteuid() == 0
}
func excludedByAttribute(attributes map[string][]byte) bool {
_, excluded := attributes["duplicacy_exclude"]
if !excluded {
value, ok := attributes[bsdFileFlagsKey]
excluded = ok && (binary.LittleEndian.Uint32(value) & bsd_UF_NODUMP) != 0
flags, ok := attributes[bsdFileFlagsKey]
excluded = ok && (binary.LittleEndian.Uint32(flags)&bsd_UF_NODUMP) != 0
}
return excluded
}
func (entry *Entry) ReadAttributes(top string) {
fullPath := filepath.Join(top, entry.Path)
fileInfo, err := os.Lstat(fullPath)
func (entry *Entry) ReadAttributes(fullPath string, fi os.FileInfo) error {
if entry.IsSpecial() {
return nil
}
attributes, err := xattr.LList(fullPath)
if err != nil {
return
return err
}
if !entry.IsSpecial() {
attributes, _ := xattr.LList(fullPath)
if len(attributes) > 0 {
entry.Attributes = &map[string][]byte{}
for _, name := range attributes {
attribute, err := xattr.LGet(fullPath, name)
if err == nil {
(*entry.Attributes)[name] = attribute
}
}
if len(attributes) > 0 {
entry.Attributes = &map[string][]byte{}
}
var allErrors error
for _, name := range attributes {
value, err := xattr.LGet(fullPath, name)
if err != nil {
allErrors = errors.Join(allErrors, err)
} else {
(*entry.Attributes)[name] = value
}
}
if err := entry.readFileFlags(fileInfo); err != nil {
LOG_INFO("ATTR_BACKUP", "Could not backup flags for file %s: %v", fullPath, err)
}
return allErrors
}
func (entry *Entry) SetAttributesToFile(fullPath string) {
if !entry.IsSpecial() {
names, _ := xattr.LList(fullPath)
for _, name := range names {
newAttribute, found := (*entry.Attributes)[name]
if found {
oldAttribute, _ := xattr.LGet(fullPath, name)
if !bytes.Equal(oldAttribute, newAttribute) {
xattr.LSet(fullPath, name, newAttribute)
}
delete(*entry.Attributes, name)
} else {
xattr.LRemove(fullPath, name)
}
}
for name, attribute := range *entry.Attributes {
if len(name) > 0 && name[0] == '\x00' {
continue
}
xattr.LSet(fullPath, name, attribute)
}
}
if err := entry.restoreLateFileFlags(fullPath); err != nil {
LOG_DEBUG("ATTR_RESTORE", "Could not restore flags for file %s: %v", fullPath, err)
}
}
func (entry *Entry) RestoreEarlyDirFlags(path string) error {
return nil
}
func (entry *Entry) RestoreEarlyFileFlags(f *os.File) error {
return nil
}
func (entry *Entry) readFileFlags(fileInfo os.FileInfo) error {
stat, ok := fileInfo.Sys().(*syscall.Stat_t)
if ok && stat.Flags != 0 {
func (entry *Entry) ReadFileFlags(fullPath string, fileInfo os.FileInfo) error {
stat, _ := fileInfo.Sys().(*syscall.Stat_t)
if stat != nil && stat.Flags != 0 {
if entry.Attributes == nil {
entry.Attributes = &map[string][]byte{}
}
v := make([]byte, 4)
binary.LittleEndian.PutUint32(v, stat.Flags)
(*entry.Attributes)[bsdFileFlagsKey] = v
LOG_DEBUG("ATTR_READ", "Read flags 0x%x for %s", stat.Flags, entry.Path)
}
return nil
}
func (entry *Entry) restoreLateFileFlags(path string) error {
func (entry *Entry) SetAttributesToFile(fullPath string) error {
if entry.Attributes == nil || len(*entry.Attributes) == 0 || entry.IsSpecial() {
return nil
}
attributes := *entry.Attributes
if _, haveFlags := attributes[bsdFileFlagsKey]; haveFlags && len(attributes) <= 1 {
return nil
}
names, err := xattr.LList(fullPath)
if err != nil {
return err
}
for _, name := range names {
newAttribute, found := attributes[name]
if found {
oldAttribute, _ := xattr.LGet(fullPath, name)
if !bytes.Equal(oldAttribute, newAttribute) {
err = errors.Join(err, xattr.LSet(fullPath, name, newAttribute))
}
delete(attributes, name)
} else {
err = errors.Join(err, xattr.LRemove(fullPath, name))
}
}
for name, attribute := range attributes {
if len(name) > 0 && name[0] == '\x00' {
continue
}
err = errors.Join(err, xattr.LSet(fullPath, name, attribute))
}
return err
}
func (entry *Entry) RestoreEarlyDirFlags(fullPath string, mask uint32) error {
return nil
}
func (entry *Entry) RestoreEarlyFileFlags(f *os.File, mask uint32) error {
return nil
}
func (entry *Entry) RestoreLateFileFlags(fullPath string, fileInfo os.FileInfo, mask uint32) error {
if entry.Attributes == nil {
return nil
}
if bsdIsSuperUser {
mask |= ^uint32(bsd_UF_SETTABLE | bsd_SF_SETTABLE)
} else {
mask |= ^uint32(bsd_UF_SETTABLE)
}
var flags uint32
stat := fileInfo.Sys().(*syscall.Stat_t)
if stat == nil {
return errors.New("file stat info missing")
}
if v, have := (*entry.Attributes)[bsdFileFlagsKey]; have {
flags = binary.LittleEndian.Uint32(v)
}
flags = (flags & ^mask) | (stat.Flags & mask)
if flags != stat.Flags {
pPath, _ := syscall.BytePtrFromString(fullPath)
if _, _, errno := syscall.Syscall(syscall.SYS_LCHFLAGS,
uintptr(unsafe.Pointer(syscall.StringBytePtr(path))),
uintptr(binary.LittleEndian.Uint32((v))), 0); errno != 0 {
uintptr(unsafe.Pointer(pPath)),
uintptr(flags), 0); errno != 0 {
return os.NewSyscallError("lchflags", errno)
}
}