mirror of
https://github.com/jkl1337/duplicacy.git
synced 2026-01-02 03:34:39 -06:00
Don't bother using checked type assertion for stat since we are guarded by specific build configuration, we should know the correct type, and if not, panicing is fine. Despite syscall being deprecated a decade ago, we still need it for FileInfo and sys/windows still calls it as well.
234 lines
6.3 KiB
Go
234 lines
6.3 KiB
Go
// 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 excludedByAttribute(attributes map[string][]byte) bool {
|
|
_, excluded := attributes["user.duplicacy_exclude"]
|
|
if !excluded {
|
|
flags, ok := attributes[linuxFileFlagsKey]
|
|
excluded = ok && (binary.LittleEndian.Uint32(flags)&linux_FS_NODUMP_FL) != 0
|
|
}
|
|
return excluded
|
|
}
|
|
|
|
func (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)
|
|
|
|
if entry.Mode&uint32(os.ModeNamedPipe) != 0 {
|
|
mode |= unix.S_IFIFO
|
|
} else if entry.Mode&uint32(os.ModeCharDevice) != 0 {
|
|
mode |= unix.S_IFCHR
|
|
} else if entry.Mode&uint32(os.ModeDevice) != 0 {
|
|
mode |= unix.S_IFBLK
|
|
} else if entry.Mode&uint32(os.ModeSocket) != 0 {
|
|
mode |= unix.S_IFSOCK
|
|
} else {
|
|
return nil
|
|
}
|
|
return unix.Mknod(fullPath, mode, int(entry.GetRdev()))
|
|
}
|