From 07c796a46beeac25096141f2bfecabfc741b6a28 Mon Sep 17 00:00:00 2001 From: "John K. Luebs" Date: Wed, 4 Oct 2023 23:01:19 -0500 Subject: [PATCH] Fix and reorganize for build for FreeBSD - the xattr handling of namespaces is different on BSD with the xattr package, the user namespace is implicit - Don't backup/exclude files with flag nodump on BSD/Darwin --- src/duplicacy_entry.go | 8 +-- src/duplicacy_entry_test.go | 6 +- src/duplicacy_utils_bsd_common.go | 109 ------------------------------ src/duplicacy_utils_darwin.go | 106 ++++++++++++++++++++++++++++- src/duplicacy_utils_linux.go | 10 +-- src/duplicacy_utils_posix.go | 13 ---- src/duplicacy_utils_windows.go | 16 ++--- src/duplicacy_utils_xbsd.go | 108 ++++++++++++++++++++++++++++- 8 files changed, 232 insertions(+), 144 deletions(-) delete mode 100644 src/duplicacy_utils_bsd_common.go delete mode 100644 src/duplicacy_utils_posix.go diff --git a/src/duplicacy_entry.go b/src/duplicacy_entry.go index 1d1d58f..39fe09a 100644 --- a/src/duplicacy_entry.go +++ b/src/duplicacy_entry.go @@ -608,10 +608,6 @@ func (entry *Entry) RestoreMetadata(fullPath string, fileInfo *os.FileInfo, setO } } - if entry.Attributes != nil && len(*entry.Attributes) > 0 { - entry.SetAttributesToFile(fullPath) - } - // Only set the time if the file is not a symlink if !entry.IsLink() && (*fileInfo).ModTime().Unix() != entry.Time { modifiedTime := time.Unix(entry.Time, 0) @@ -622,6 +618,10 @@ func (entry *Entry) RestoreMetadata(fullPath string, fileInfo *os.FileInfo, setO } } + if entry.Attributes != nil && len(*entry.Attributes) > 0 { + entry.SetAttributesToFile(fullPath) + } + return true } diff --git a/src/duplicacy_entry_test.go b/src/duplicacy_entry_test.go index 6eeb94d..a42b312 100644 --- a/src/duplicacy_entry_test.go +++ b/src/duplicacy_entry_test.go @@ -242,10 +242,12 @@ func TestEntryExcludeByAttribute(t *testing.T) { if runtime.GOOS == "darwin" { excludeAttrName = "com.apple.metadata:com_apple_backup_excludeItem" excludeAttrValue = []byte("com.apple.backupd") - } else if runtime.GOOS == "linux" || runtime.GOOS == "freebsd" || runtime.GOOS == "netbsd" || runtime.GOOS == "solaris" { + } else if runtime.GOOS == "linux" { excludeAttrName = "user.duplicacy_exclude" + } else if runtime.GOOS == "freebsd" || runtime.GOOS == "netbsd" { + excludeAttrName = "duplicacy_exclude" } else { - t.Skip("skipping test, not darwin, linux, freebsd, netbsd, or solaris") + t.Skip("skipping test, not darwin, linux, freebsd, or netbsd") } testDir := filepath.Join(os.TempDir(), "duplicacy_test") diff --git a/src/duplicacy_utils_bsd_common.go b/src/duplicacy_utils_bsd_common.go deleted file mode 100644 index 63b187f..0000000 --- a/src/duplicacy_utils_bsd_common.go +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright (c) Acrosync LLC. All rights reserved. -// Free for personal use and commercial trial -// Commercial use requires per-user licenses available from https://duplicacy.com - -//go:build freebsd || netbsd || darwin -// +build freebsd netbsd darwin - -package duplicacy - -import ( - "bytes" - "encoding/binary" - "os" - "path/filepath" - "syscall" - - "github.com/pkg/xattr" -) - -const bsdFileFlagsKey = "\x00bf" - -func (entry *Entry) ReadAttributes(top string) { - fullPath := filepath.Join(top, entry.Path) - fileInfo, err := os.Lstat(fullPath) - if err != nil { - return - } - - 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 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 { - 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) readFileFlags(fileInfo os.FileInfo) error { - stat, ok := fileInfo.Sys().(*syscall.Stat_t) - if ok && 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) RestoreEarlyDirFlags(path string) error { - return nil -} - -func (entry *Entry) RestoreEarlyFileFlags(f *os.File) error { - return nil -} - -func (entry *Entry) RestoreSpecial(fullPath string) error { - mode := entry.Mode & uint32(fileModeMask) - - if entry.Mode&uint32(os.ModeNamedPipe) != 0 { - mode |= syscall.S_IFIFO - } else if entry.Mode&uint32(os.ModeCharDevice) != 0 { - mode |= syscall.S_IFCHR - } else if entry.Mode&uint32(os.ModeDevice) != 0 { - mode |= syscall.S_IFBLK - } else { - return nil - } - return syscall.Mknod(fullPath, mode, int(entry.GetRdev())) -} diff --git a/src/duplicacy_utils_darwin.go b/src/duplicacy_utils_darwin.go index e5be817..9649be0 100644 --- a/src/duplicacy_utils_darwin.go +++ b/src/duplicacy_utils_darwin.go @@ -5,22 +5,109 @@ package duplicacy import ( + "bytes" "encoding/binary" "os" + "path/filepath" "strings" "syscall" + + "github.com/pkg/xattr" ) +const darwin_UF_NODUMP = 0x1 + +const darwinFileFlagsKey = "\x00bf" + func excludedByAttribute(attributes map[string][]byte) bool { value, ok := attributes["com.apple.metadata:com_apple_backup_excludeItem"] - return ok && strings.Contains(string(value), "com.apple.backupd") + 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 + } + return excluded +} + +func (entry *Entry) ReadAttributes(top string) { + fullPath := filepath.Join(top, entry.Path) + fileInfo, err := os.Lstat(fullPath) + if err != nil { + return + } + + 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 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 { + 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 { + 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 { return nil } - if v, have := (*entry.Attributes)[bsdFileFlagsKey]; have { + if v, have := (*entry.Attributes)[darwinFileFlagsKey]; have { f, err := os.OpenFile(path, os.O_RDONLY|syscall.O_SYMLINK, 0) if err != nil { return err @@ -31,3 +118,18 @@ func (entry *Entry) restoreLateFileFlags(path string) error { } return nil } + +func (entry *Entry) RestoreSpecial(fullPath string) error { + mode := entry.Mode & uint32(fileModeMask) + + if entry.Mode&uint32(os.ModeNamedPipe) != 0 { + mode |= syscall.S_IFIFO + } else if entry.Mode&uint32(os.ModeCharDevice) != 0 { + mode |= syscall.S_IFCHR + } else if entry.Mode&uint32(os.ModeDevice) != 0 { + mode |= syscall.S_IFBLK + } else { + return nil + } + return syscall.Mknod(fullPath, mode, int(entry.GetRdev())) +} diff --git a/src/duplicacy_utils_linux.go b/src/duplicacy_utils_linux.go index 8beeb07..7dfd291 100644 --- a/src/duplicacy_utils_linux.go +++ b/src/duplicacy_utils_linux.go @@ -121,6 +121,11 @@ func (entry *Entry) ReadAttributes(top string) { 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() { @@ -235,8 +240,3 @@ func (entry *Entry) RestoreSpecial(fullPath string) error { } return syscall.Mknod(fullPath, mode, int(entry.GetRdev())) } - -func excludedByAttribute(attributes map[string][]byte) bool { - _, ok := attributes["user.duplicacy_exclude"] - return ok -} diff --git a/src/duplicacy_utils_posix.go b/src/duplicacy_utils_posix.go deleted file mode 100644 index a37f2c9..0000000 --- a/src/duplicacy_utils_posix.go +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Acrosync LLC. All rights reserved. -// Free for personal use and commercial trial -// Commercial use requires per-user licenses available from https://duplicacy.com - -//go:build freebsd || netbsd || solaris -// +build freebsd netbsd solaris - -package duplicacy - -func excludedByAttribute(attributes map[string][]byte) bool { - _, ok := attributes["user.duplicacy_exclude"] - return ok -} diff --git a/src/duplicacy_utils_windows.go b/src/duplicacy_utils_windows.go index 57443a7..990c257 100644 --- a/src/duplicacy_utils_windows.go +++ b/src/duplicacy_utils_windows.go @@ -117,8 +117,12 @@ func (entry *Entry) SetAttributesToFile(fullPath string) { } -func (entry *Entry) ReadDeviceNode(fileInfo os.FileInfo) bool { - return nil +func (entry *Entry) ReadSpecial(fileInfo os.FileInfo) bool { + return true +} + +func (entry *Entry) IsSameSpecial(fileInfo os.FileInfo) bool { + return false } func (entry *Entry) RestoreSpecial(fullPath string) error { @@ -148,8 +152,8 @@ func SplitDir(fullPath string) (dir string, file string) { return fullPath[:i+1], fullPath[i+1:] } -func (entry *Entry) ReadFileFlags(f *os.File) error { - return nil +func excludedByAttribute(attributes map[string][]byte) bool { + return false } func (entry *Entry) RestoreEarlyDirFlags(path string) error { @@ -159,7 +163,3 @@ func (entry *Entry) RestoreEarlyDirFlags(path string) error { func (entry *Entry) RestoreEarlyFileFlags(f *os.File) error { return nil } - -func (entry *Entry) RestoreLateFileFlags(f *os.File) error { - return nil -} diff --git a/src/duplicacy_utils_xbsd.go b/src/duplicacy_utils_xbsd.go index 2112d28..96ea167 100644 --- a/src/duplicacy_utils_xbsd.go +++ b/src/duplicacy_utils_xbsd.go @@ -13,18 +13,124 @@ import ( "os" "path/filepath" "syscall" + "unsafe" "github.com/pkg/xattr" ) +const bsd_UF_NODUMP = 0x1 + +const bsdFileFlagsKey = "\x00bf" + +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 + } + return excluded +} + +func (entry *Entry) ReadAttributes(top string) { + fullPath := filepath.Join(top, entry.Path) + fileInfo, err := os.Lstat(fullPath) + if err != nil { + return + } + + 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 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 { + 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 { + 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 { if entry.Attributes == nil { return nil } if v, have := (*entry.Attributes)[bsdFileFlagsKey]; have { - if _, _, errno := syscall.Syscall(syscall.SYS_LCHFLAGS, uintptr(unsafe.Pointer(syscall.StringBytePtr(path))), uintptr(v), 0); errno != 0 { + if _, _, errno := syscall.Syscall(syscall.SYS_LCHFLAGS, + uintptr(unsafe.Pointer(syscall.StringBytePtr(path))), + uintptr(binary.LittleEndian.Uint32((v))), 0); errno != 0 { return os.NewSyscallError("lchflags", errno) } } return nil } + +func (entry *Entry) RestoreSpecial(fullPath string) error { + mode := entry.Mode & uint32(fileModeMask) + + if entry.Mode&uint32(os.ModeNamedPipe) != 0 { + mode |= syscall.S_IFIFO + } else if entry.Mode&uint32(os.ModeCharDevice) != 0 { + mode |= syscall.S_IFCHR + } else if entry.Mode&uint32(os.ModeDevice) != 0 { + mode |= syscall.S_IFBLK + } else { + return nil + } + return syscall.Mknod(fullPath, mode, entry.GetRdev()) +}