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 23a3cce0ad
commit 228ca7005c
9 changed files with 382 additions and 284 deletions

4
go.mod
View File

@@ -1,6 +1,6 @@
module github.com/gilbertchen/duplicacy
go 1.19
go 1.20
require (
cloud.google.com/go v0.38.0
@@ -22,7 +22,7 @@ require (
github.com/minio/highwayhash v1.0.2
github.com/ncw/swift/v2 v2.0.1
github.com/pkg/sftp v1.11.0
github.com/pkg/xattr v0.4.1
github.com/pkg/xattr v0.4.9
github.com/vmihailenco/msgpack v4.0.4+incompatible
golang.org/x/crypto v0.12.0
golang.org/x/net v0.10.0

3
go.sum
View File

@@ -128,6 +128,8 @@ github.com/pkg/sftp v1.11.0 h1:4Zv0OGbpkg4yNuUtH0s8rvoYxRCNyT29NVUo6pgPmxI=
github.com/pkg/sftp v1.11.0/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
github.com/pkg/xattr v0.4.1 h1:dhclzL6EqOXNaPDWqoeb9tIxATfBSmjqL0b4DpSjwRw=
github.com/pkg/xattr v0.4.1/go.mod h1:W2cGD0TBEus7MkUgv0tNZ9JutLtVO3cXu+IBRuHqnFs=
github.com/pkg/xattr v0.4.9 h1:5883YPCtkSd8LFbs13nXplj9g9tlrwoJRjgpgMu1/fE=
github.com/pkg/xattr v0.4.9/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
@@ -225,6 +227,7 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=

View File

@@ -826,7 +826,10 @@ func (manager *BackupManager) Restore(top string, revision int, inPlace bool, qu
return 0
}
}
remoteEntry.RestoreEarlyDirFlags(fullPath)
err = remoteEntry.RestoreEarlyDirFlags(fullPath, 0) // TODO: mask
if err != nil {
LOG_WARN("DOWNLOAD_FLAGS", "Failed to set early file flags on %s: %v", fullPath, err)
}
directoryEntries = append(directoryEntries, remoteEntry)
} else if remoteEntry.IsSpecial() {
if stat, _ := os.Lstat(fullPath); stat != nil {
@@ -1297,7 +1300,10 @@ func (manager *BackupManager) RestoreFile(chunkDownloader *ChunkDownloader, chun
LOG_ERROR("DOWNLOAD_CREATE", "Failed to create the file %s for in-place writing: %v", fullPath, err)
return false, nil
}
entry.RestoreEarlyFileFlags(existingFile)
err = entry.RestoreEarlyFileFlags(existingFile, 0) // TODO: implement mask
if err != nil {
LOG_WARN("DOWNLOAD_FLAGS", "Failed to set early file flags on %s: %v", fullPath, err)
}
n := int64(1)
// There is a go bug on Windows (https://github.com/golang/go/issues/21681) that causes Seek to fail
@@ -1481,7 +1487,10 @@ func (manager *BackupManager) RestoreFile(chunkDownloader *ChunkDownloader, chun
return false, nil
}
}
entry.RestoreEarlyFileFlags(existingFile)
err = entry.RestoreEarlyFileFlags(existingFile, 0) // TODO: implement mask
if err != nil {
LOG_WARN("DOWNLOAD_FLAGS", "Failed to set early file flags on %s: %v", fullPath, err)
}
existingFile.Seek(0, 0)
@@ -1564,7 +1573,10 @@ func (manager *BackupManager) RestoreFile(chunkDownloader *ChunkDownloader, chun
LOG_ERROR("DOWNLOAD_OPEN", "Failed to open file for writing: %v", err)
return false, nil
}
entry.RestoreEarlyFileFlags(newFile)
err = entry.RestoreEarlyFileFlags(newFile, 0) // TODO: implement mask
if err != nil {
LOG_WARN("DOWNLOAD_FLAGS", "Failed to set early file flags on %s: %v", fullPath, err)
}
hasher := manager.config.NewFileHasher()

View File

@@ -591,8 +591,9 @@ func (entry *Entry) RestoreMetadata(fullPath string, fileInfo os.FileInfo, setOw
}
}
if entry.Attributes != nil && len(*entry.Attributes) > 0 {
entry.SetAttributesToFile(fullPath)
err := entry.SetAttributesToFile(fullPath)
if err != nil {
LOG_WARN("RESTORE_ATTR", "Failed to set extended attributes on %s: %v", entry.Path, err)
}
// Note that chown can remove setuid/setgid bits so should be called before chmod
@@ -621,10 +622,10 @@ func (entry *Entry) RestoreMetadata(fullPath string, fileInfo os.FileInfo, setOw
}
}
// TODO Set flags last
// if entry.Attributes != nil && len(*entry.Attributes) > 0 {
// entry.SetFlagsToFile(fullPath)
// }
err = entry.RestoreLateFileFlags(fullPath, fileInfo, 0) // TODO: implement mask
if err != nil {
LOG_WARN("RESTORE_FLAGS", "Failed to set file flags on %s: %v", entry.Path, err)
}
return true
}
@@ -842,9 +843,11 @@ func ListEntries(top string, path string, patterns []string, nobackupFile string
}
}
fullPath := joinPath(top, entry.Path)
if entry.IsLink() {
isRegular := false
isRegular, entry.Link, err = Readlink(joinPath(top, entry.Path))
isRegular, entry.Link, err = Readlink(fullPath)
if err != nil {
LOG_WARN("LIST_LINK", "Failed to read the symlink %s: %v", entry.Path, err)
skippedFiles = append(skippedFiles, entry.Path)
@@ -854,7 +857,7 @@ func ListEntries(top string, path string, patterns []string, nobackupFile string
if isRegular {
entry.Mode ^= uint32(os.ModeSymlink)
} else if path == "" && (filepath.IsAbs(entry.Link) || filepath.HasPrefix(entry.Link, `\\`)) && !strings.HasPrefix(entry.Link, normalizedTop) {
stat, err := os.Stat(joinPath(top, entry.Path))
stat, err := os.Stat(fullPath)
if err != nil {
LOG_WARN("LIST_LINK", "Failed to read the symlink: %v", err)
skippedFiles = append(skippedFiles, entry.Path)
@@ -873,14 +876,20 @@ func ListEntries(top string, path string, patterns []string, nobackupFile string
entry = newEntry
}
} else if entry.IsSpecial() {
if !entry.ReadSpecial(f) {
LOG_WARN("LIST_DEV", "Failed to save device node %s", entry.Path)
if err := entry.ReadSpecial(fullPath, f); err != nil {
LOG_WARN("LIST_DEV", "Failed to save device node %s: %v", entry.Path, err)
skippedFiles = append(skippedFiles, entry.Path)
continue
}
}
entry.ReadAttributes(top)
if err := entry.ReadAttributes(fullPath, f); err != nil {
LOG_WARN("LIST_ATTR", "Failed to read xattrs on %s: %v", entry.Path, err)
}
if err := entry.ReadFileFlags(fullPath, f); err != nil {
LOG_WARN("LIST_ATTR", "Failed to read file flags on %s: %v", entry.Path, err)
}
if excludeByAttribute && entry.Attributes != nil && excludedByAttribute(*entry.Attributes) {
LOG_DEBUG("LIST_EXCLUDE", "%s is excluded by attribute", entry.Path)

View File

@@ -7,112 +7,147 @@ package duplicacy
import (
"bytes"
"encoding/binary"
"errors"
"os"
"path/filepath"
"strings"
"syscall"
"github.com/pkg/xattr"
"golang.org/x/sys/unix"
)
const darwin_UF_NODUMP = 0x1
const (
darwinFileFlagsKey = "\x00bf"
)
const darwinFileFlagsKey = "\x00bf"
var darwinIsSuperUser bool
func init() {
darwinIsSuperUser = syscall.Geteuid() == 0
}
func excludedByAttribute(attributes map[string][]byte) bool {
value, ok := attributes["com.apple.metadata:com_apple_backup_excludeItem"]
excluded := ok && strings.Contains(string(value), "com.apple.backupd")
if !excluded {
value, ok := attributes[darwinFileFlagsKey]
excluded = ok && (binary.LittleEndian.Uint32(value) & darwin_UF_NODUMP) != 0
flags, ok := attributes[darwinFileFlagsKey]
excluded = ok && (binary.LittleEndian.Uint32(flags)&unix.UF_NODUMP) != 0
}
return excluded
}
func (entry *Entry) ReadAttributes(top string) {
fullPath := filepath.Join(top, entry.Path)
fileInfo, err := os.Lstat(fullPath)
if err != nil {
return
func (entry *Entry) ReadAttributes(fullPath string, fi os.FileInfo) error {
if entry.IsSpecial() {
return nil
}
attributes, err := xattr.LList(fullPath)
if err != nil {
return err
}
if !entry.IsSpecial() {
attributes, _ := xattr.LList(fullPath)
if len(attributes) > 0 {
entry.Attributes = &map[string][]byte{}
}
var allErrors error
for _, name := range attributes {
attribute, err := xattr.LGet(fullPath, name)
if err == nil {
(*entry.Attributes)[name] = attribute
}
}
}
}
if err := entry.readFileFlags(fileInfo); err != nil {
LOG_INFO("ATTR_BACKUP", "Could not backup flags for file %s: %v", fullPath, err)
}
}
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)
value, err := xattr.LGet(fullPath, name)
if err != nil {
allErrors = errors.Join(allErrors, err)
} else {
xattr.LRemove(fullPath, name)
(*entry.Attributes)[name] = value
}
}
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)
}
return allErrors
}
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)[darwinFileFlagsKey] = v
LOG_DEBUG("ATTR_READ", "Read flags 0x%x for %s", stat.Flags, entry.Path)
}
return nil
}
func (entry *Entry) restoreLateFileFlags(path string) error {
if entry.Attributes == nil {
func (entry *Entry) SetAttributesToFile(fullPath string) error {
if entry.Attributes == nil || len(*entry.Attributes) == 0 || entry.IsSpecial() {
return nil
}
if v, have := (*entry.Attributes)[darwinFileFlagsKey]; have {
f, err := os.OpenFile(path, os.O_RDONLY|syscall.O_SYMLINK, 0)
attributes := *entry.Attributes
if _, haveFlags := attributes[darwinFileFlagsKey]; haveFlags && len(attributes) <= 1 {
return nil
}
names, err := xattr.LList(fullPath)
if err != nil {
return err
}
err = syscall.Fchflags(int(f.Fd()), int(binary.LittleEndian.Uint32(v)))
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 darwinIsSuperUser {
mask |= ^uint32(unix.UF_SETTABLE | unix.SF_SETTABLE)
} else {
mask |= ^uint32(unix.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)[darwinFileFlagsKey]; have {
flags = binary.LittleEndian.Uint32(v)
}
flags = (flags & ^mask) | (stat.Flags & mask)
if flags != stat.Flags {
f, err := os.OpenFile(fullPath, os.O_RDONLY|syscall.O_SYMLINK, 0)
if err != nil {
return err
}
err = syscall.Fchflags(int(f.Fd()), int(flags))
f.Close()
return err
}

View File

@@ -7,8 +7,9 @@ package duplicacy
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"os"
"path/filepath"
"syscall"
"unsafe"
@@ -51,125 +52,55 @@ func ioctl(f *os.File, request uintptr, attrp *uint32) error {
return nil
}
type xattrHandle struct {
f *os.File
fullPath string
func excludedByAttribute(attributes map[string][]byte) bool {
_, excluded := attributes["user.duplicacy_exclude"]
if !excluded {
flags, ok := attributes[linuxFileFlagsKey]
excluded = ok && (binary.LittleEndian.Uint32(flags)&linux_FS_NODUMP_FL) != 0
}
return excluded
}
func (x xattrHandle) list() ([]string, error) {
if x.f != nil {
return xattr.FList(x.f)
} else {
return xattr.LList(x.fullPath)
}
}
func (x xattrHandle) get(name string) ([]byte, error) {
if x.f != nil {
return xattr.FGet(x.f, name)
} else {
return xattr.LGet(x.fullPath, name)
}
}
func (x xattrHandle) set(name string, value []byte) error {
if x.f != nil {
return xattr.FSet(x.f, name, value)
} else {
return xattr.LSet(x.fullPath, name, value)
}
}
func (x xattrHandle) remove(name string) error {
if x.f != nil {
return xattr.FRemove(x.f, name)
} else {
return xattr.LRemove(x.fullPath, name)
}
}
func (entry *Entry) ReadAttributes(top string) {
fullPath := filepath.Join(top, entry.Path)
x := xattrHandle{nil, fullPath}
if !entry.IsLink() {
var err error
x.f, err = os.OpenFile(fullPath, os.O_RDONLY|syscall.O_NOFOLLOW|syscall.O_NONBLOCK, 0)
func (entry *Entry) ReadAttributes(fullPath string, fi os.FileInfo) error {
attributes, err := xattr.LList(fullPath)
if err != nil {
// FIXME: We really should return errors for failure to read
return
return err
}
}
attributes, _ := x.list()
if len(attributes) > 0 {
entry.Attributes = &map[string][]byte{}
}
var allErrors error
for _, name := range attributes {
attribute, err := x.get(name)
if err == nil {
(*entry.Attributes)[name] = attribute
}
}
if entry.IsFile() || entry.IsDir() {
if err := entry.readFileFlags(x.f); err != nil {
LOG_INFO("ATTR_BACKUP", "Could not backup flags for file %s: %v", fullPath, err)
}
}
x.f.Close()
}
func excludedByAttribute(attributes map[string][]byte) bool {
_, ok := attributes["user.duplicacy_exclude"]
return ok
}
func (entry *Entry) SetAttributesToFile(fullPath string) {
x := xattrHandle{nil, fullPath}
if !entry.IsLink() {
var err error
x.f, err = os.OpenFile(fullPath, os.O_RDONLY|syscall.O_NOFOLLOW, 0)
value, err := xattr.LGet(fullPath, name)
if err != nil {
return
}
}
names, _ := x.list()
for _, name := range names {
newAttribute, found := (*entry.Attributes)[name]
if found {
oldAttribute, _ := x.get(name)
if !bytes.Equal(oldAttribute, newAttribute) {
x.set(name, newAttribute)
}
delete(*entry.Attributes, name)
allErrors = errors.Join(allErrors, err)
} else {
x.remove(name)
(*entry.Attributes)[name] = value
}
}
for name, attribute := range *entry.Attributes {
if len(name) > 0 && name[0] == '\x00' {
continue
}
x.set(name, attribute)
}
if entry.IsFile() || entry.IsDir() {
if err := entry.restoreLateFileFlags(x.f); err != nil {
LOG_DEBUG("ATTR_RESTORE", "Could not restore flags for file %s: %v", fullPath, err)
}
}
x.f.Close()
return allErrors
}
func (entry *Entry) readFileFlags(f *os.File) error {
var flags uint32
if err := ioctl(f, linux_FS_IOC_GETFLAGS, &flags); err != nil {
func (entry *Entry) ReadFileFlags(fullPath string, fileInfo os.FileInfo) error {
if !(entry.IsFile() || entry.IsDir()) {
return nil
}
f, err := os.OpenFile(fullPath, os.O_RDONLY|syscall.O_NOFOLLOW, 0)
if err != nil {
return err
}
var flags uint32
err = ioctl(f, linux_FS_IOC_GETFLAGS, &flags)
f.Close()
if err != nil {
return err
}
if flags != 0 {
if entry.Attributes == nil {
entry.Attributes = &map[string][]byte{}
@@ -177,49 +108,109 @@ func (entry *Entry) readFileFlags(f *os.File) error {
v := make([]byte, 4)
binary.LittleEndian.PutUint32(v, flags)
(*entry.Attributes)[linuxFileFlagsKey] = v
LOG_DEBUG("ATTR_READ", "Read flags 0x%x for %s", flags, entry.Path)
}
return nil
}
func (entry *Entry) RestoreEarlyDirFlags(path string) error {
if entry.Attributes == nil {
func (entry *Entry) SetAttributesToFile(fullPath string) error {
if entry.Attributes == nil || len(*entry.Attributes) == 0 {
return nil
}
if v, have := (*entry.Attributes)[linuxFileFlagsKey]; have {
flags := binary.LittleEndian.Uint32(v) & linuxIocFlagsDirEarly
f, err := os.OpenFile(path, os.O_RDONLY|syscall.O_DIRECTORY, 0)
attributes := *entry.Attributes
if _, haveFlags := attributes[linuxFileFlagsKey]; haveFlags && len(attributes) <= 1 {
return nil
}
names, err := xattr.LList(fullPath)
if err != nil {
return err
}
LOG_DEBUG("ATTR_RESTORE", "Restore dir flags (early) 0x%x for %s", flags, entry.Path)
err = ioctl(f, linux_FS_IOC_SETFLAGS, &flags)
f.Close()
for _, name := range names {
newAttribute, found := (*entry.Attributes)[name]
if found {
oldAttribute, _ := xattr.LGet(fullPath, name)
if !bytes.Equal(oldAttribute, newAttribute) {
err = errors.Join(err, xattr.LSet(fullPath, name, newAttribute))
}
delete(*entry.Attributes, name)
} else {
err = errors.Join(err, xattr.LRemove(fullPath, name))
}
}
for name, attribute := range *entry.Attributes {
if len(name) > 0 && name[0] == '\x00' {
continue
}
err = errors.Join(err, xattr.LSet(fullPath, name, attribute))
}
return err
}
return nil
}
func (entry *Entry) RestoreEarlyFileFlags(f *os.File) error {
func (entry *Entry) RestoreEarlyDirFlags(fullPath string, mask uint32) error {
if entry.Attributes == nil {
return nil
}
var flags uint32
if v, have := (*entry.Attributes)[linuxFileFlagsKey]; have {
flags := binary.LittleEndian.Uint32(v) & linuxIocFlagsFileEarly
LOG_DEBUG("ATTR_RESTORE", "Restore flags (early) 0x%x for %s", flags, entry.Path)
return ioctl(f, linux_FS_IOC_SETFLAGS, &flags)
flags = binary.LittleEndian.Uint32(v) & linuxIocFlagsDirEarly & ^mask
}
if flags != 0 {
f, err := os.OpenFile(fullPath, os.O_RDONLY|syscall.O_DIRECTORY, 0)
if err != nil {
return err
}
err = ioctl(f, linux_FS_IOC_SETFLAGS, &flags)
f.Close()
if err != nil {
return fmt.Errorf("Set flags 0x%.8x failed: %w", flags, err)
}
}
return nil
}
func (entry *Entry) restoreLateFileFlags(f *os.File) error {
func (entry *Entry) RestoreEarlyFileFlags(f *os.File, mask uint32) error {
if entry.Attributes == nil {
return nil
}
var flags uint32
if v, have := (*entry.Attributes)[linuxFileFlagsKey]; have {
flags := binary.LittleEndian.Uint32(v) & (linuxIocFlagsFileEarly | linuxIocFlagsDirEarly | linuxIocFlagsLate)
LOG_DEBUG("ATTR_RESTORE", "Restore flags (late) 0x%x for %s", flags, entry.Path)
return ioctl(f, linux_FS_IOC_SETFLAGS, &flags)
flags = binary.LittleEndian.Uint32(v) & linuxIocFlagsFileEarly & ^mask
}
if flags != 0 {
err := ioctl(f, linux_FS_IOC_SETFLAGS, &flags)
if err != nil {
return fmt.Errorf("Set flags 0x%.8x failed: %w", flags, err)
}
}
return nil
}
func (entry *Entry) RestoreLateFileFlags(fullPath string, fileInfo os.FileInfo, mask uint32) error {
if entry.IsLink() || entry.Attributes == nil {
return nil
}
var flags uint32
if v, have := (*entry.Attributes)[linuxFileFlagsKey]; have {
flags = binary.LittleEndian.Uint32(v) & (linuxIocFlagsFileEarly | linuxIocFlagsDirEarly | linuxIocFlagsLate) & ^mask
}
if flags != 0 {
f, err := os.OpenFile(fullPath, os.O_RDONLY|syscall.O_NOFOLLOW, 0)
if err != nil {
return err
}
err = ioctl(f, linux_FS_IOC_SETFLAGS, &flags)
f.Close()
if err != nil {
return fmt.Errorf("Set flags 0x%.8x failed: %w", flags, err)
}
}
return nil
}

View File

@@ -8,6 +8,7 @@
package duplicacy
import (
"errors"
"fmt"
"os"
"path"
@@ -57,7 +58,7 @@ func (entry *Entry) getHardLinkKey(f os.FileInfo) (key listEntryLinkKey, linked
return
}
stat := f.Sys().(*syscall.Stat_t)
if stat == nil || stat.Nlink < 2 {
if stat == nil || stat.Nlink <= 1 {
return
}
key.dev = uint64(stat.Dev)
@@ -66,19 +67,19 @@ func (entry *Entry) getHardLinkKey(f os.FileInfo) (key listEntryLinkKey, linked
return
}
func (entry *Entry) ReadSpecial(fileInfo os.FileInfo) bool {
func (entry *Entry) ReadSpecial(fullPath string, fileInfo os.FileInfo) error {
if fileInfo.Mode()&(os.ModeDevice|os.ModeCharDevice) == 0 {
return true
return nil
}
stat := fileInfo.Sys().(*syscall.Stat_t)
if stat == nil {
return false
return errors.New("file stat info missing")
}
entry.Size = 0
rdev := uint64(stat.Rdev)
entry.StartChunk = int(rdev & 0xFFFFFFFF)
entry.StartOffset = int(rdev >> 32)
return true
return nil
}
func (entry *Entry) GetRdev() uint64 {

View File

@@ -116,15 +116,32 @@ func (entry *Entry) getHardLinkKey(f os.FileInfo) (key listEntryLinkKey, linked
return
}
func (entry *Entry) ReadAttributes(top string) {
func (entry *Entry) ReadAttributes(fullPath string, fi os.FileInfo) error {
return nil
}
func (entry *Entry) SetAttributesToFile(fullPath string) {
func (entry *Entry) ReadFileFlags(fullPath string, fileInfo os.FileInfo) error {
return nil
}
func (entry *Entry) ReadSpecial(fileInfo os.FileInfo) bool {
return true
func (entry *Entry) SetAttributesToFile(fullPath string) error {
return nil
}
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 {
return nil
}
func (entry *Entry) ReadSpecial(fullPath string, fileInfo os.FileInfo) error {
return nil
}
func (entry *Entry) IsSameSpecial(fileInfo os.FileInfo) bool {
@@ -161,11 +178,3 @@ func SplitDir(fullPath string) (dir string, file string) {
func excludedByAttribute(attributes map[string][]byte) bool {
return false
}
func (entry *Entry) RestoreEarlyDirFlags(path string) error {
return nil
}
func (entry *Entry) RestoreEarlyFileFlags(f *os.File) error {
return nil
}

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)
if err != nil {
return
func (entry *Entry) ReadAttributes(fullPath string, fi os.FileInfo) error {
if entry.IsSpecial() {
return nil
}
if !entry.IsSpecial() {
attributes, _ := xattr.LList(fullPath)
attributes, err := xattr.LList(fullPath)
if err != nil {
return err
}
if len(attributes) > 0 {
entry.Attributes = &map[string][]byte{}
}
var allErrors error
for _, name := range attributes {
attribute, err := xattr.LGet(fullPath, name)
if err == nil {
(*entry.Attributes)[name] = attribute
}
}
}
}
if err := entry.readFileFlags(fileInfo); err != nil {
LOG_INFO("ATTR_BACKUP", "Could not backup flags for file %s: %v", fullPath, err)
}
}
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)
value, err := xattr.LGet(fullPath, name)
if err != nil {
allErrors = errors.Join(allErrors, err)
} else {
xattr.LRemove(fullPath, name)
(*entry.Attributes)[name] = value
}
}
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)
}
return allErrors
}
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)
}
}