mirror of
https://github.com/jkl1337/duplicacy.git
synced 2026-01-02 03:34:39 -06:00
Add ignoring EINTR to syscall, set O_NOATIME on open, ignore ENOTTY error (unsupported ioctl) to avoid massive spam for non-exceptional failure on nonsupporting file systems
229 lines
5.9 KiB
Go
229 lines
5.9 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 */
|
|
|
|
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"
|
|
)
|
|
|
|
var (
|
|
errENOTTY error = unix.ENOTTY
|
|
)
|
|
|
|
func ignoringEINTR(fn func() error) (err error) {
|
|
for {
|
|
err = fn()
|
|
if err != unix.EINTR {
|
|
break
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
func ioctl(f *os.File, request uintptr, attrp *uint32) error {
|
|
return ignoringEINTR(func() error {
|
|
argp := uintptr(unsafe.Pointer(attrp))
|
|
|
|
_, _, errno := unix.Syscall(unix.SYS_IOCTL, f.Fd(), request, argp)
|
|
if errno == 0 {
|
|
return nil
|
|
} else if errno == unix.ENOTTY {
|
|
return errENOTTY
|
|
}
|
|
return errno
|
|
})
|
|
}
|
|
|
|
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 {
|
|
// the linux file flags interface is quite depressing. The half assed attempt at statx
|
|
// doesn't even cover the flags we're interested in
|
|
if !(entry.IsFile() || entry.IsDir()) {
|
|
return nil
|
|
}
|
|
|
|
f, err := os.OpenFile(fullPath, os.O_RDONLY|unix.O_NOATIME|unix.O_NOFOLLOW, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var flags uint32
|
|
|
|
err = ioctl(f, unix.FS_IOC_GETFLAGS, &flags)
|
|
f.Close()
|
|
if err != nil {
|
|
if err == unix.ENOTTY {
|
|
return 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, unix.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, unix.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
|
|
}
|