mirror of
https://github.com/jkl1337/duplicacy.git
synced 2026-06-14 12:18:58 -05:00
This only works on Linux. Darwin does not allow mknod of a socket. Have not tested BSD.
243 lines
6.7 KiB
Go
243 lines
6.7 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"
|
|
"os"
|
|
"path/filepath"
|
|
"syscall"
|
|
"unsafe"
|
|
|
|
"github.com/pkg/xattr"
|
|
)
|
|
|
|
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 := syscall.Syscall(syscall.SYS_IOCTL, f.Fd(), request, argp); errno != 0 {
|
|
return os.NewSyscallError("ioctl", errno)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type xattrHandle struct {
|
|
f *os.File
|
|
fullPath string
|
|
}
|
|
|
|
func (x xattrHandle) list() ([]string, error) {
|
|
if x.f != nil {
|
|
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 {
|
|
// FIXME: We really should return errors for failure to read
|
|
return
|
|
}
|
|
}
|
|
|
|
attributes, _ := x.list()
|
|
|
|
if len(attributes) > 0 {
|
|
entry.Attributes = &map[string][]byte{}
|
|
}
|
|
for _, name := range attributes {
|
|
attribute, err := x.get(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 (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 {
|
|
return
|
|
}
|
|
}
|
|
|
|
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 {
|
|
x.remove(name)
|
|
}
|
|
}
|
|
|
|
for name, attribute := range *entry.Attributes {
|
|
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 {
|
|
var flags uint32
|
|
if err := ioctl(f, linux_FS_IOC_GETFLAGS, &flags); 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
|
|
LOG_DEBUG("ATTR_READ", "Read flags 0x%x for %s", flags, entry.Path)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (entry *Entry) RestoreEarlyDirFlags(path string) error {
|
|
if entry.Attributes == nil {
|
|
return nil
|
|
}
|
|
if v, have := (*entry.Attributes)[linuxFileFlagsKey]; have {
|
|
flags := binary.LittleEndian.Uint32(v) & linuxIocFlagsDirEarly
|
|
f, err := os.OpenFile(path, os.O_RDONLY|syscall.O_DIRECTORY, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
LOG_DEBUG("ATTR_RESTORE", "Restore dir flags (early) 0x%x for %s", flags, entry.Path)
|
|
err = ioctl(f, linux_FS_IOC_SETFLAGS, &flags)
|
|
f.Close()
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (entry *Entry) RestoreEarlyFileFlags(f *os.File) error {
|
|
if entry.Attributes == nil {
|
|
return nil
|
|
}
|
|
if v, have := (*entry.Attributes)[linuxFileFlagsKey]; have {
|
|
flags := binary.LittleEndian.Uint32(v) & linuxIocFlagsFileEarly
|
|
LOG_DEBUG("ATTR_RESTORE", "Restore flags (early) 0x%x for %s", flags, entry.Path)
|
|
return ioctl(f, linux_FS_IOC_SETFLAGS, &flags)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (entry *Entry) restoreLateFileFlags(f *os.File) error {
|
|
if entry.Attributes == nil {
|
|
return nil
|
|
}
|
|
if v, have := (*entry.Attributes)[linuxFileFlagsKey]; have {
|
|
flags := binary.LittleEndian.Uint32(v) & (linuxIocFlagsFileEarly | linuxIocFlagsDirEarly | linuxIocFlagsLate)
|
|
LOG_DEBUG("ATTR_RESTORE", "Restore flags (late) 0x%x for %s", flags, entry.Path)
|
|
return ioctl(f, linux_FS_IOC_SETFLAGS, &flags)
|
|
}
|
|
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 if entry.Mode&uint32(os.ModeSocket) != 0 {
|
|
mode |= syscall.S_IFSOCK
|
|
} else {
|
|
return nil
|
|
}
|
|
return syscall.Mknod(fullPath, mode, int(entry.GetRdev()))
|
|
}
|
|
|
|
func excludedByAttribute(attributes map[string][]byte) bool {
|
|
_, ok := attributes["user.duplicacy_exclude"]
|
|
return ok
|
|
}
|