mirror of
https://github.com/jkl1337/duplicacy.git
synced 2026-01-02 11:44:45 -06:00
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:
4
go.mod
4
go.mod
@@ -1,6 +1,6 @@
|
|||||||
module github.com/gilbertchen/duplicacy
|
module github.com/gilbertchen/duplicacy
|
||||||
|
|
||||||
go 1.19
|
go 1.20
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go v0.38.0
|
cloud.google.com/go v0.38.0
|
||||||
@@ -22,7 +22,7 @@ require (
|
|||||||
github.com/minio/highwayhash v1.0.2
|
github.com/minio/highwayhash v1.0.2
|
||||||
github.com/ncw/swift/v2 v2.0.1
|
github.com/ncw/swift/v2 v2.0.1
|
||||||
github.com/pkg/sftp v1.11.0
|
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
|
github.com/vmihailenco/msgpack v4.0.4+incompatible
|
||||||
golang.org/x/crypto v0.12.0
|
golang.org/x/crypto v0.12.0
|
||||||
golang.org/x/net v0.10.0
|
golang.org/x/net v0.10.0
|
||||||
|
|||||||
3
go.sum
3
go.sum
@@ -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/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 h1:dhclzL6EqOXNaPDWqoeb9tIxATfBSmjqL0b4DpSjwRw=
|
||||||
github.com/pkg/xattr v0.4.1/go.mod h1:W2cGD0TBEus7MkUgv0tNZ9JutLtVO3cXu+IBRuHqnFs=
|
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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
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=
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
@@ -226,6 +228,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-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-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-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 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
|
||||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
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=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
|||||||
@@ -826,7 +826,10 @@ func (manager *BackupManager) Restore(top string, revision int, inPlace bool, qu
|
|||||||
return 0
|
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)
|
directoryEntries = append(directoryEntries, remoteEntry)
|
||||||
} else if remoteEntry.IsSpecial() {
|
} else if remoteEntry.IsSpecial() {
|
||||||
if stat, _ := os.Lstat(fullPath); stat != nil {
|
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)
|
LOG_ERROR("DOWNLOAD_CREATE", "Failed to create the file %s for in-place writing: %v", fullPath, err)
|
||||||
return false, nil
|
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)
|
n := int64(1)
|
||||||
// There is a go bug on Windows (https://github.com/golang/go/issues/21681) that causes Seek to fail
|
// 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
|
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)
|
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)
|
LOG_ERROR("DOWNLOAD_OPEN", "Failed to open file for writing: %v", err)
|
||||||
return false, nil
|
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()
|
hasher := manager.config.NewFileHasher()
|
||||||
|
|
||||||
|
|||||||
@@ -591,8 +591,9 @@ func (entry *Entry) RestoreMetadata(fullPath string, fileInfo os.FileInfo, setOw
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if entry.Attributes != nil && len(*entry.Attributes) > 0 {
|
err := entry.SetAttributesToFile(fullPath)
|
||||||
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
|
// 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
|
err = entry.RestoreLateFileFlags(fullPath, fileInfo, 0) // TODO: implement mask
|
||||||
// if entry.Attributes != nil && len(*entry.Attributes) > 0 {
|
if err != nil {
|
||||||
// entry.SetFlagsToFile(fullPath)
|
LOG_WARN("RESTORE_FLAGS", "Failed to set file flags on %s: %v", entry.Path, err)
|
||||||
// }
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -842,9 +843,11 @@ func ListEntries(top string, path string, patterns []string, nobackupFile string
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fullPath := joinPath(top, entry.Path)
|
||||||
|
|
||||||
if entry.IsLink() {
|
if entry.IsLink() {
|
||||||
isRegular := false
|
isRegular := false
|
||||||
isRegular, entry.Link, err = Readlink(joinPath(top, entry.Path))
|
isRegular, entry.Link, err = Readlink(fullPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
LOG_WARN("LIST_LINK", "Failed to read the symlink %s: %v", entry.Path, err)
|
LOG_WARN("LIST_LINK", "Failed to read the symlink %s: %v", entry.Path, err)
|
||||||
skippedFiles = append(skippedFiles, entry.Path)
|
skippedFiles = append(skippedFiles, entry.Path)
|
||||||
@@ -854,7 +857,7 @@ func ListEntries(top string, path string, patterns []string, nobackupFile string
|
|||||||
if isRegular {
|
if isRegular {
|
||||||
entry.Mode ^= uint32(os.ModeSymlink)
|
entry.Mode ^= uint32(os.ModeSymlink)
|
||||||
} else if path == "" && (filepath.IsAbs(entry.Link) || filepath.HasPrefix(entry.Link, `\\`)) && !strings.HasPrefix(entry.Link, normalizedTop) {
|
} 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 {
|
if err != nil {
|
||||||
LOG_WARN("LIST_LINK", "Failed to read the symlink: %v", err)
|
LOG_WARN("LIST_LINK", "Failed to read the symlink: %v", err)
|
||||||
skippedFiles = append(skippedFiles, entry.Path)
|
skippedFiles = append(skippedFiles, entry.Path)
|
||||||
@@ -873,14 +876,20 @@ func ListEntries(top string, path string, patterns []string, nobackupFile string
|
|||||||
entry = newEntry
|
entry = newEntry
|
||||||
}
|
}
|
||||||
} else if entry.IsSpecial() {
|
} else if entry.IsSpecial() {
|
||||||
if !entry.ReadSpecial(f) {
|
if err := entry.ReadSpecial(fullPath, f); err != nil {
|
||||||
LOG_WARN("LIST_DEV", "Failed to save device node %s", entry.Path)
|
LOG_WARN("LIST_DEV", "Failed to save device node %s: %v", entry.Path, err)
|
||||||
skippedFiles = append(skippedFiles, entry.Path)
|
skippedFiles = append(skippedFiles, entry.Path)
|
||||||
continue
|
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) {
|
if excludeByAttribute && entry.Attributes != nil && excludedByAttribute(*entry.Attributes) {
|
||||||
LOG_DEBUG("LIST_EXCLUDE", "%s is excluded by attribute", entry.Path)
|
LOG_DEBUG("LIST_EXCLUDE", "%s is excluded by attribute", entry.Path)
|
||||||
|
|||||||
@@ -7,112 +7,147 @@ package duplicacy
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/pkg/xattr"
|
"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 {
|
func excludedByAttribute(attributes map[string][]byte) bool {
|
||||||
value, ok := attributes["com.apple.metadata:com_apple_backup_excludeItem"]
|
value, ok := attributes["com.apple.metadata:com_apple_backup_excludeItem"]
|
||||||
excluded := ok && strings.Contains(string(value), "com.apple.backupd")
|
excluded := ok && strings.Contains(string(value), "com.apple.backupd")
|
||||||
if !excluded {
|
if !excluded {
|
||||||
value, ok := attributes[darwinFileFlagsKey]
|
flags, ok := attributes[darwinFileFlagsKey]
|
||||||
excluded = ok && (binary.LittleEndian.Uint32(value) & darwin_UF_NODUMP) != 0
|
excluded = ok && (binary.LittleEndian.Uint32(flags)&unix.UF_NODUMP) != 0
|
||||||
}
|
}
|
||||||
return excluded
|
return excluded
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) ReadAttributes(top string) {
|
func (entry *Entry) ReadAttributes(fullPath string, fi os.FileInfo) error {
|
||||||
fullPath := filepath.Join(top, entry.Path)
|
if entry.IsSpecial() {
|
||||||
fileInfo, err := os.Lstat(fullPath)
|
return nil
|
||||||
if err != nil {
|
}
|
||||||
return
|
|
||||||
|
attributes, err := xattr.LList(fullPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !entry.IsSpecial() {
|
|
||||||
attributes, _ := xattr.LList(fullPath)
|
|
||||||
if len(attributes) > 0 {
|
if len(attributes) > 0 {
|
||||||
entry.Attributes = &map[string][]byte{}
|
entry.Attributes = &map[string][]byte{}
|
||||||
|
}
|
||||||
|
var allErrors error
|
||||||
for _, name := range attributes {
|
for _, name := range attributes {
|
||||||
attribute, err := xattr.LGet(fullPath, name)
|
value, err := xattr.LGet(fullPath, name)
|
||||||
if err == nil {
|
if err != nil {
|
||||||
(*entry.Attributes)[name] = attribute
|
allErrors = errors.Join(allErrors, err)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
} else {
|
} else {
|
||||||
xattr.LRemove(fullPath, name)
|
(*entry.Attributes)[name] = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, attribute := range *entry.Attributes {
|
return allErrors
|
||||||
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 {
|
func (entry *Entry) ReadFileFlags(fullPath string, fileInfo os.FileInfo) error {
|
||||||
return nil
|
stat, _ := fileInfo.Sys().(*syscall.Stat_t)
|
||||||
}
|
if stat != nil && stat.Flags != 0 {
|
||||||
|
|
||||||
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 {
|
|
||||||
if entry.Attributes == nil {
|
if entry.Attributes == nil {
|
||||||
entry.Attributes = &map[string][]byte{}
|
entry.Attributes = &map[string][]byte{}
|
||||||
}
|
}
|
||||||
v := make([]byte, 4)
|
v := make([]byte, 4)
|
||||||
binary.LittleEndian.PutUint32(v, stat.Flags)
|
binary.LittleEndian.PutUint32(v, stat.Flags)
|
||||||
(*entry.Attributes)[darwinFileFlagsKey] = v
|
(*entry.Attributes)[darwinFileFlagsKey] = v
|
||||||
LOG_DEBUG("ATTR_READ", "Read flags 0x%x for %s", stat.Flags, entry.Path)
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) restoreLateFileFlags(path string) error {
|
func (entry *Entry) SetAttributesToFile(fullPath string) error {
|
||||||
if entry.Attributes == nil {
|
if entry.Attributes == nil || len(*entry.Attributes) == 0 || entry.IsSpecial() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if v, have := (*entry.Attributes)[darwinFileFlagsKey]; have {
|
attributes := *entry.Attributes
|
||||||
f, err := os.OpenFile(path, os.O_RDONLY|syscall.O_SYMLINK, 0)
|
|
||||||
|
if _, haveFlags := attributes[darwinFileFlagsKey]; haveFlags && len(attributes) <= 1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
names, err := xattr.LList(fullPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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()
|
f.Close()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ package duplicacy
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
@@ -51,125 +52,55 @@ func ioctl(f *os.File, request uintptr, attrp *uint32) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type xattrHandle struct {
|
func excludedByAttribute(attributes map[string][]byte) bool {
|
||||||
f *os.File
|
_, excluded := attributes["user.duplicacy_exclude"]
|
||||||
fullPath string
|
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) {
|
func (entry *Entry) ReadAttributes(fullPath string, fi os.FileInfo) error {
|
||||||
if x.f != nil {
|
attributes, err := xattr.LList(fullPath)
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// FIXME: We really should return errors for failure to read
|
return err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
attributes, _ := x.list()
|
|
||||||
|
|
||||||
if len(attributes) > 0 {
|
if len(attributes) > 0 {
|
||||||
entry.Attributes = &map[string][]byte{}
|
entry.Attributes = &map[string][]byte{}
|
||||||
}
|
}
|
||||||
|
var allErrors error
|
||||||
for _, name := range attributes {
|
for _, name := range attributes {
|
||||||
attribute, err := x.get(name)
|
value, err := xattr.LGet(fullPath, 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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
allErrors = errors.Join(allErrors, err)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
} else {
|
} else {
|
||||||
x.remove(name)
|
(*entry.Attributes)[name] = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, attribute := range *entry.Attributes {
|
return allErrors
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) readFileFlags(f *os.File) error {
|
func (entry *Entry) ReadFileFlags(fullPath string, fileInfo os.FileInfo) error {
|
||||||
var flags uint32
|
if !(entry.IsFile() || entry.IsDir()) {
|
||||||
if err := ioctl(f, linux_FS_IOC_GETFLAGS, &flags); err != nil {
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.OpenFile(fullPath, os.O_RDONLY|syscall.O_NOFOLLOW, 0)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var flags uint32
|
||||||
|
|
||||||
|
err = ioctl(f, linux_FS_IOC_GETFLAGS, &flags)
|
||||||
|
f.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if flags != 0 {
|
if flags != 0 {
|
||||||
if entry.Attributes == nil {
|
if entry.Attributes == nil {
|
||||||
entry.Attributes = &map[string][]byte{}
|
entry.Attributes = &map[string][]byte{}
|
||||||
@@ -177,49 +108,109 @@ func (entry *Entry) readFileFlags(f *os.File) error {
|
|||||||
v := make([]byte, 4)
|
v := make([]byte, 4)
|
||||||
binary.LittleEndian.PutUint32(v, flags)
|
binary.LittleEndian.PutUint32(v, flags)
|
||||||
(*entry.Attributes)[linuxFileFlagsKey] = v
|
(*entry.Attributes)[linuxFileFlagsKey] = v
|
||||||
LOG_DEBUG("ATTR_READ", "Read flags 0x%x for %s", flags, entry.Path)
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) RestoreEarlyDirFlags(path string) error {
|
func (entry *Entry) SetAttributesToFile(fullPath string) error {
|
||||||
if entry.Attributes == nil {
|
if entry.Attributes == nil || len(*entry.Attributes) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if v, have := (*entry.Attributes)[linuxFileFlagsKey]; have {
|
attributes := *entry.Attributes
|
||||||
flags := binary.LittleEndian.Uint32(v) & linuxIocFlagsDirEarly
|
|
||||||
f, err := os.OpenFile(path, os.O_RDONLY|syscall.O_DIRECTORY, 0)
|
if _, haveFlags := attributes[linuxFileFlagsKey]; haveFlags && len(attributes) <= 1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
names, err := xattr.LList(fullPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
LOG_DEBUG("ATTR_RESTORE", "Restore dir flags (early) 0x%x for %s", flags, entry.Path)
|
for _, name := range names {
|
||||||
err = ioctl(f, linux_FS_IOC_SETFLAGS, &flags)
|
newAttribute, found := (*entry.Attributes)[name]
|
||||||
f.Close()
|
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 err
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) RestoreEarlyFileFlags(f *os.File) error {
|
func (entry *Entry) RestoreEarlyDirFlags(fullPath string, mask uint32) error {
|
||||||
if entry.Attributes == nil {
|
if entry.Attributes == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
var flags uint32
|
||||||
|
|
||||||
if v, have := (*entry.Attributes)[linuxFileFlagsKey]; have {
|
if v, have := (*entry.Attributes)[linuxFileFlagsKey]; have {
|
||||||
flags := binary.LittleEndian.Uint32(v) & linuxIocFlagsFileEarly
|
flags = binary.LittleEndian.Uint32(v) & linuxIocFlagsDirEarly & ^mask
|
||||||
LOG_DEBUG("ATTR_RESTORE", "Restore flags (early) 0x%x for %s", flags, entry.Path)
|
}
|
||||||
return ioctl(f, linux_FS_IOC_SETFLAGS, &flags)
|
|
||||||
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) restoreLateFileFlags(f *os.File) error {
|
func (entry *Entry) RestoreEarlyFileFlags(f *os.File, mask uint32) error {
|
||||||
if entry.Attributes == nil {
|
if entry.Attributes == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
var flags uint32
|
||||||
|
|
||||||
if v, have := (*entry.Attributes)[linuxFileFlagsKey]; have {
|
if v, have := (*entry.Attributes)[linuxFileFlagsKey]; have {
|
||||||
flags := binary.LittleEndian.Uint32(v) & (linuxIocFlagsFileEarly | linuxIocFlagsDirEarly | linuxIocFlagsLate)
|
flags = binary.LittleEndian.Uint32(v) & linuxIocFlagsFileEarly & ^mask
|
||||||
LOG_DEBUG("ATTR_RESTORE", "Restore flags (late) 0x%x for %s", flags, entry.Path)
|
}
|
||||||
return ioctl(f, linux_FS_IOC_SETFLAGS, &flags)
|
|
||||||
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
package duplicacy
|
package duplicacy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
@@ -57,7 +58,7 @@ func (entry *Entry) getHardLinkKey(f os.FileInfo) (key listEntryLinkKey, linked
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
stat := f.Sys().(*syscall.Stat_t)
|
stat := f.Sys().(*syscall.Stat_t)
|
||||||
if stat == nil || stat.Nlink < 2 {
|
if stat == nil || stat.Nlink <= 1 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
key.dev = uint64(stat.Dev)
|
key.dev = uint64(stat.Dev)
|
||||||
@@ -66,19 +67,19 @@ func (entry *Entry) getHardLinkKey(f os.FileInfo) (key listEntryLinkKey, linked
|
|||||||
return
|
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 {
|
if fileInfo.Mode()&(os.ModeDevice|os.ModeCharDevice) == 0 {
|
||||||
return true
|
return nil
|
||||||
}
|
}
|
||||||
stat := fileInfo.Sys().(*syscall.Stat_t)
|
stat := fileInfo.Sys().(*syscall.Stat_t)
|
||||||
if stat == nil {
|
if stat == nil {
|
||||||
return false
|
return errors.New("file stat info missing")
|
||||||
}
|
}
|
||||||
entry.Size = 0
|
entry.Size = 0
|
||||||
rdev := uint64(stat.Rdev)
|
rdev := uint64(stat.Rdev)
|
||||||
entry.StartChunk = int(rdev & 0xFFFFFFFF)
|
entry.StartChunk = int(rdev & 0xFFFFFFFF)
|
||||||
entry.StartOffset = int(rdev >> 32)
|
entry.StartOffset = int(rdev >> 32)
|
||||||
return true
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) GetRdev() uint64 {
|
func (entry *Entry) GetRdev() uint64 {
|
||||||
|
|||||||
@@ -116,15 +116,32 @@ func (entry *Entry) getHardLinkKey(f os.FileInfo) (key listEntryLinkKey, linked
|
|||||||
return
|
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 {
|
func (entry *Entry) SetAttributesToFile(fullPath string) error {
|
||||||
return true
|
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 {
|
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 {
|
func excludedByAttribute(attributes map[string][]byte) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) RestoreEarlyDirFlags(path string) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) RestoreEarlyFileFlags(f *os.File) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -10,110 +10,148 @@ package duplicacy
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/pkg/xattr"
|
"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 {
|
func excludedByAttribute(attributes map[string][]byte) bool {
|
||||||
_, excluded := attributes["duplicacy_exclude"]
|
_, excluded := attributes["duplicacy_exclude"]
|
||||||
if !excluded {
|
if !excluded {
|
||||||
value, ok := attributes[bsdFileFlagsKey]
|
flags, ok := attributes[bsdFileFlagsKey]
|
||||||
excluded = ok && (binary.LittleEndian.Uint32(value) & bsd_UF_NODUMP) != 0
|
excluded = ok && (binary.LittleEndian.Uint32(flags)&bsd_UF_NODUMP) != 0
|
||||||
}
|
}
|
||||||
return excluded
|
return excluded
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) ReadAttributes(top string) {
|
func (entry *Entry) ReadAttributes(fullPath string, fi os.FileInfo) error {
|
||||||
fullPath := filepath.Join(top, entry.Path)
|
if entry.IsSpecial() {
|
||||||
fileInfo, err := os.Lstat(fullPath)
|
return nil
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !entry.IsSpecial() {
|
attributes, err := xattr.LList(fullPath)
|
||||||
attributes, _ := xattr.LList(fullPath)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if len(attributes) > 0 {
|
if len(attributes) > 0 {
|
||||||
entry.Attributes = &map[string][]byte{}
|
entry.Attributes = &map[string][]byte{}
|
||||||
|
}
|
||||||
|
var allErrors error
|
||||||
for _, name := range attributes {
|
for _, name := range attributes {
|
||||||
attribute, err := xattr.LGet(fullPath, name)
|
value, err := xattr.LGet(fullPath, name)
|
||||||
if err == nil {
|
if err != nil {
|
||||||
(*entry.Attributes)[name] = attribute
|
allErrors = errors.Join(allErrors, err)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
} else {
|
} else {
|
||||||
xattr.LRemove(fullPath, name)
|
(*entry.Attributes)[name] = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, attribute := range *entry.Attributes {
|
return allErrors
|
||||||
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 {
|
func (entry *Entry) ReadFileFlags(fullPath string, fileInfo os.FileInfo) error {
|
||||||
return nil
|
stat, _ := fileInfo.Sys().(*syscall.Stat_t)
|
||||||
}
|
if stat != nil && stat.Flags != 0 {
|
||||||
|
|
||||||
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 {
|
|
||||||
if entry.Attributes == nil {
|
if entry.Attributes == nil {
|
||||||
entry.Attributes = &map[string][]byte{}
|
entry.Attributes = &map[string][]byte{}
|
||||||
}
|
}
|
||||||
v := make([]byte, 4)
|
v := make([]byte, 4)
|
||||||
binary.LittleEndian.PutUint32(v, stat.Flags)
|
binary.LittleEndian.PutUint32(v, stat.Flags)
|
||||||
(*entry.Attributes)[bsdFileFlagsKey] = v
|
(*entry.Attributes)[bsdFileFlagsKey] = v
|
||||||
LOG_DEBUG("ATTR_READ", "Read flags 0x%x for %s", stat.Flags, entry.Path)
|
|
||||||
}
|
}
|
||||||
return nil
|
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 {
|
if entry.Attributes == nil {
|
||||||
return 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 {
|
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,
|
if _, _, errno := syscall.Syscall(syscall.SYS_LCHFLAGS,
|
||||||
uintptr(unsafe.Pointer(syscall.StringBytePtr(path))),
|
uintptr(unsafe.Pointer(pPath)),
|
||||||
uintptr(binary.LittleEndian.Uint32((v))), 0); errno != 0 {
|
uintptr(flags), 0); errno != 0 {
|
||||||
return os.NewSyscallError("lchflags", errno)
|
return os.NewSyscallError("lchflags", errno)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user