Split out xattr and flags routines to new files

This commit is contained in:
2023-10-06 02:16:43 -05:00
parent fd0f544c04
commit e791bc1f9b
6 changed files with 497 additions and 459 deletions

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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
}

148
src/duplicacy_xattr_xbsd.go Normal file
View File

@@ -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
}