diff --git a/src/duplicacy_utils_darwin.go b/src/duplicacy_utils_darwin.go index c820f69..f930903 100644 --- a/src/duplicacy_utils_darwin.go +++ b/src/duplicacy_utils_darwin.go @@ -5,27 +5,13 @@ package duplicacy import ( - "bytes" "encoding/binary" - "errors" "os" "strings" - "syscall" - "github.com/pkg/xattr" "golang.org/x/sys/unix" ) -const ( - darwinFileFlagsKey = "\x00bf" -) - -var darwinIsSuperUser bool - -func init() { - darwinIsSuperUser = unix.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") @@ -36,122 +22,6 @@ func excludedByAttribute(attributes map[string][]byte) bool { return excluded } -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 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 - } - } - - return allErrors -} - -func (entry *Entry) ReadFileFlags(fullPath string, fileInfo os.FileInfo) error { - stat := fileInfo.Sys().(*syscall.Stat_t) - if 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 - } - return nil -} - -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[darwinFileFlagsKey]; 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 darwinIsSuperUser { - mask |= ^uint32(unix.UF_SETTABLE | unix.SF_SETTABLE) - } else { - mask |= ^uint32(unix.UF_SETTABLE) - } - - var flags uint32 - - if v, have := (*entry.Attributes)[darwinFileFlagsKey]; have { - flags = binary.LittleEndian.Uint32(v) - } - - stat := fileInfo.Sys().(*syscall.Stat_t) - - flags = (flags & ^mask) | (stat.Flags & mask) - - if flags != stat.Flags { - f, err := os.OpenFile(fullPath, os.O_RDONLY|unix.O_SYMLINK, 0) - if err != nil { - return err - } - err = unix.Fchflags(int(f.Fd()), int(flags)) - f.Close() - return err - } - return nil -} - func (entry *Entry) RestoreSpecial(fullPath string) error { mode := entry.Mode & uint32(fileModeMask) diff --git a/src/duplicacy_utils_linux.go b/src/duplicacy_utils_linux.go index 57042d0..bb252c3 100644 --- a/src/duplicacy_utils_linux.go +++ b/src/duplicacy_utils_linux.go @@ -5,53 +5,12 @@ package duplicacy import ( - "bytes" "encoding/binary" - "errors" - "fmt" "os" - "unsafe" - "github.com/pkg/xattr" "golang.org/x/sys/unix" ) -const ( - linux_FS_SECRM_FL = 0x00000001 /* Secure deletion */ - linux_FS_UNRM_FL = 0x00000002 /* Undelete */ - linux_FS_COMPR_FL = 0x00000004 /* Compress file */ - linux_FS_SYNC_FL = 0x00000008 /* Synchronous updates */ - linux_FS_IMMUTABLE_FL = 0x00000010 /* Immutable file */ - linux_FS_APPEND_FL = 0x00000020 /* writes to file may only append */ - linux_FS_NODUMP_FL = 0x00000040 /* do not dump file */ - linux_FS_NOATIME_FL = 0x00000080 /* do not update atime */ - linux_FS_NOCOMP_FL = 0x00000400 /* Don't compress */ - linux_FS_JOURNAL_DATA_FL = 0x00004000 /* Reserved for ext3 */ - linux_FS_NOTAIL_FL = 0x00008000 /* file tail should not be merged */ - linux_FS_DIRSYNC_FL = 0x00010000 /* dirsync behaviour (directories only) */ - linux_FS_TOPDIR_FL = 0x00020000 /* Top of directory hierarchies*/ - linux_FS_NOCOW_FL = 0x00800000 /* Do not cow file */ - linux_FS_PROJINHERIT_FL = 0x20000000 /* Create with parents projid */ - - linux_FS_IOC_GETFLAGS uintptr = 0x80086601 - linux_FS_IOC_SETFLAGS uintptr = 0x40086602 - - linuxIocFlagsFileEarly = linux_FS_SECRM_FL | linux_FS_UNRM_FL | linux_FS_COMPR_FL | linux_FS_NODUMP_FL | linux_FS_NOATIME_FL | linux_FS_NOCOMP_FL | linux_FS_JOURNAL_DATA_FL | linux_FS_NOTAIL_FL | linux_FS_NOCOW_FL - linuxIocFlagsDirEarly = linux_FS_TOPDIR_FL | linux_FS_PROJINHERIT_FL - linuxIocFlagsLate = linux_FS_SYNC_FL | linux_FS_IMMUTABLE_FL | linux_FS_APPEND_FL | linux_FS_DIRSYNC_FL - - linuxFileFlagsKey = "\x00lf" -) - -func ioctl(f *os.File, request uintptr, attrp *uint32) error { - argp := uintptr(unsafe.Pointer(attrp)) - - if _, _, errno := unix.Syscall(unix.SYS_IOCTL, f.Fd(), request, argp); errno != 0 { - return os.NewSyscallError("ioctl", errno) - } - return nil -} - func excludedByAttribute(attributes map[string][]byte) bool { _, excluded := attributes["user.duplicacy_exclude"] if !excluded { @@ -61,160 +20,6 @@ func excludedByAttribute(attributes map[string][]byte) bool { return excluded } -func (entry *Entry) ReadAttributes(fullPath string, fi os.FileInfo) error { - 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 { - value, err := xattr.LGet(fullPath, name) - if err != nil { - allErrors = errors.Join(allErrors, err) - } else { - (*entry.Attributes)[name] = value - } - } - - return allErrors -} - -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|unix.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{} - } - v := make([]byte, 4) - binary.LittleEndian.PutUint32(v, flags) - (*entry.Attributes)[linuxFileFlagsKey] = v - } - return nil -} - -func (entry *Entry) SetAttributesToFile(fullPath string) error { - if entry.Attributes == nil || len(*entry.Attributes) == 0 { - return nil - } - attributes := *entry.Attributes - - if _, haveFlags := attributes[linuxFileFlagsKey]; haveFlags && len(attributes) <= 1 { - return nil - } - - names, err := xattr.LList(fullPath) - if err != nil { - return err - } - 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 -} - -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) & linuxIocFlagsDirEarly & ^mask - } - - if flags != 0 { - f, err := os.OpenFile(fullPath, os.O_RDONLY|unix.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) 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 & ^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|unix.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 -} - func (entry *Entry) RestoreSpecial(fullPath string) error { mode := entry.Mode & uint32(fileModeMask) diff --git a/src/duplicacy_utils_xbsd.go b/src/duplicacy_utils_xbsd.go index 3182173..5fc8cf6 100644 --- a/src/duplicacy_utils_xbsd.go +++ b/src/duplicacy_utils_xbsd.go @@ -8,30 +8,11 @@ package duplicacy import ( - "bytes" "encoding/binary" - "errors" "os" "syscall" - "unsafe" - - "github.com/pkg/xattr" ) -const ( - bsd_UF_NODUMP = 0x1 - bsd_SF_SETTABLE = 0xffff0000 - bsd_UF_SETTABLE = 0x0000ffff - - bsdFileFlagsKey = "\x00bf" -) - -var bsdIsSuperUser bool - -func init() { - bsdIsSuperUser = syscall.Geteuid() == 0 -} - func excludedByAttribute(attributes map[string][]byte) bool { _, excluded := attributes["duplicacy_exclude"] if !excluded { @@ -41,121 +22,6 @@ func excludedByAttribute(attributes map[string][]byte) bool { return excluded } -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 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 - } - } - - return allErrors -} - -func (entry *Entry) ReadFileFlags(fullPath string, fileInfo os.FileInfo) error { - stat := fileInfo.Sys().(*syscall.Stat_t) - if 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 - } - return nil -} - -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 - - if v, have := (*entry.Attributes)[bsdFileFlagsKey]; have { - flags = binary.LittleEndian.Uint32(v) - } - - stat := fileInfo.Sys().(*syscall.Stat_t) - - flags = (flags & ^mask) | (stat.Flags & mask) - - if flags != stat.Flags { - pPath, _ := syscall.BytePtrFromString(fullPath) - if _, _, errno := syscall.Syscall(syscall.SYS_LCHFLAGS, - uintptr(unsafe.Pointer(pPath)), - uintptr(flags), 0); errno != 0 { - return os.NewSyscallError("lchflags", errno) - } - } - return nil -} - func (entry *Entry) RestoreSpecial(fullPath string) error { mode := entry.Mode & uint32(fileModeMask) diff --git a/src/duplicacy_xattr_darwin.go b/src/duplicacy_xattr_darwin.go new file mode 100644 index 0000000..418174d --- /dev/null +++ b/src/duplicacy_xattr_darwin.go @@ -0,0 +1,142 @@ +// 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 + +package duplicacy + +import ( + "bytes" + "encoding/binary" + "errors" + "os" + "syscall" + + "github.com/pkg/xattr" + "golang.org/x/sys/unix" +) + +const ( + darwinFileFlagsKey = "\x00bf" +) + +var darwinIsSuperUser bool + +func init() { + darwinIsSuperUser = unix.Geteuid() == 0 +} + +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 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 + } + } + + return allErrors +} + +func (entry *Entry) ReadFileFlags(fullPath string, fileInfo os.FileInfo) error { + stat := fileInfo.Sys().(*syscall.Stat_t) + if 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 + } + return nil +} + +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[darwinFileFlagsKey]; 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 darwinIsSuperUser { + mask |= ^uint32(unix.UF_SETTABLE | unix.SF_SETTABLE) + } else { + mask |= ^uint32(unix.UF_SETTABLE) + } + + var flags uint32 + + if v, have := (*entry.Attributes)[darwinFileFlagsKey]; have { + flags = binary.LittleEndian.Uint32(v) + } + + stat := fileInfo.Sys().(*syscall.Stat_t) + + flags = (flags & ^mask) | (stat.Flags & mask) + + if flags != stat.Flags { + f, err := os.OpenFile(fullPath, os.O_RDONLY|unix.O_SYMLINK, 0) + if err != nil { + return err + } + err = unix.Fchflags(int(f.Fd()), int(flags)) + f.Close() + return err + } + return nil +} diff --git a/src/duplicacy_xattr_linux.go b/src/duplicacy_xattr_linux.go new file mode 100644 index 0000000..dff0814 --- /dev/null +++ b/src/duplicacy_xattr_linux.go @@ -0,0 +1,207 @@ +// 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 + +package duplicacy + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "os" + "unsafe" + + "github.com/pkg/xattr" + "golang.org/x/sys/unix" +) + +const ( + linux_FS_SECRM_FL = 0x00000001 /* Secure deletion */ + linux_FS_UNRM_FL = 0x00000002 /* Undelete */ + linux_FS_COMPR_FL = 0x00000004 /* Compress file */ + linux_FS_SYNC_FL = 0x00000008 /* Synchronous updates */ + linux_FS_IMMUTABLE_FL = 0x00000010 /* Immutable file */ + linux_FS_APPEND_FL = 0x00000020 /* writes to file may only append */ + linux_FS_NODUMP_FL = 0x00000040 /* do not dump file */ + linux_FS_NOATIME_FL = 0x00000080 /* do not update atime */ + linux_FS_NOCOMP_FL = 0x00000400 /* Don't compress */ + linux_FS_JOURNAL_DATA_FL = 0x00004000 /* Reserved for ext3 */ + linux_FS_NOTAIL_FL = 0x00008000 /* file tail should not be merged */ + linux_FS_DIRSYNC_FL = 0x00010000 /* dirsync behaviour (directories only) */ + linux_FS_TOPDIR_FL = 0x00020000 /* Top of directory hierarchies*/ + linux_FS_NOCOW_FL = 0x00800000 /* Do not cow file */ + linux_FS_PROJINHERIT_FL = 0x20000000 /* Create with parents projid */ + + linux_FS_IOC_GETFLAGS uintptr = 0x80086601 + linux_FS_IOC_SETFLAGS uintptr = 0x40086602 + + linuxIocFlagsFileEarly = linux_FS_SECRM_FL | linux_FS_UNRM_FL | linux_FS_COMPR_FL | linux_FS_NODUMP_FL | linux_FS_NOATIME_FL | linux_FS_NOCOMP_FL | linux_FS_JOURNAL_DATA_FL | linux_FS_NOTAIL_FL | linux_FS_NOCOW_FL + linuxIocFlagsDirEarly = linux_FS_TOPDIR_FL | linux_FS_PROJINHERIT_FL + linuxIocFlagsLate = linux_FS_SYNC_FL | linux_FS_IMMUTABLE_FL | linux_FS_APPEND_FL | linux_FS_DIRSYNC_FL + + linuxFileFlagsKey = "\x00lf" +) + +func ioctl(f *os.File, request uintptr, attrp *uint32) error { + argp := uintptr(unsafe.Pointer(attrp)) + + if _, _, errno := unix.Syscall(unix.SYS_IOCTL, f.Fd(), request, argp); errno != 0 { + return os.NewSyscallError("ioctl", errno) + } + return nil +} + +func (entry *Entry) ReadAttributes(fullPath string, fi os.FileInfo) error { + 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 { + value, err := xattr.LGet(fullPath, name) + if err != nil { + allErrors = errors.Join(allErrors, err) + } else { + (*entry.Attributes)[name] = value + } + } + + return allErrors +} + +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|unix.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{} + } + v := make([]byte, 4) + binary.LittleEndian.PutUint32(v, flags) + (*entry.Attributes)[linuxFileFlagsKey] = v + } + return nil +} + +func (entry *Entry) SetAttributesToFile(fullPath string) error { + if entry.Attributes == nil || len(*entry.Attributes) == 0 { + return nil + } + attributes := *entry.Attributes + + if _, haveFlags := attributes[linuxFileFlagsKey]; haveFlags && len(attributes) <= 1 { + return nil + } + + names, err := xattr.LList(fullPath) + if err != nil { + return err + } + 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 +} + +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) & linuxIocFlagsDirEarly & ^mask + } + + if flags != 0 { + f, err := os.OpenFile(fullPath, os.O_RDONLY|unix.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) 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 & ^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|unix.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 +} diff --git a/src/duplicacy_xattr_xbsd.go b/src/duplicacy_xattr_xbsd.go new file mode 100644 index 0000000..14948e3 --- /dev/null +++ b/src/duplicacy_xattr_xbsd.go @@ -0,0 +1,148 @@ +// 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 +// +build freebsd netbsd + +package duplicacy + +import ( + "bytes" + "encoding/binary" + "errors" + "os" + "syscall" + "unsafe" + + "github.com/pkg/xattr" +) + +const ( + bsd_UF_NODUMP = 0x1 + bsd_SF_SETTABLE = 0xffff0000 + bsd_UF_SETTABLE = 0x0000ffff + + bsdFileFlagsKey = "\x00bf" +) + +var bsdIsSuperUser bool + +func init() { + bsdIsSuperUser = syscall.Geteuid() == 0 +} + +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 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 + } + } + + return allErrors +} + +func (entry *Entry) ReadFileFlags(fullPath string, fileInfo os.FileInfo) error { + stat := fileInfo.Sys().(*syscall.Stat_t) + if 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 + } + return nil +} + +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 + + if v, have := (*entry.Attributes)[bsdFileFlagsKey]; have { + flags = binary.LittleEndian.Uint32(v) + } + + stat := fileInfo.Sys().(*syscall.Stat_t) + + flags = (flags & ^mask) | (stat.Flags & mask) + + if flags != stat.Flags { + pPath, _ := syscall.BytePtrFromString(fullPath) + if _, _, errno := syscall.Syscall(syscall.SYS_LCHFLAGS, + uintptr(unsafe.Pointer(pPath)), + uintptr(flags), 0); errno != 0 { + return os.NewSyscallError("lchflags", errno) + } + } + return nil +}