diff --git a/src/duplicacy_backupmanager.go b/src/duplicacy_backupmanager.go index 69fc59d..c979030 100644 --- a/src/duplicacy_backupmanager.go +++ b/src/duplicacy_backupmanager.go @@ -780,6 +780,7 @@ func (manager *BackupManager) Restore(top string, revision int, inPlace bool, qu return 0 } } + remoteEntry.RestoreEarlyDirFlags(fullPath) directoryEntries = append(directoryEntries, remoteEntry) } else { // We can't download files here since fileEntries needs to be sorted @@ -1194,6 +1195,7 @@ func (manager *BackupManager) RestoreFile(chunkDownloader *ChunkDownloader, chun LOG_ERROR("DOWNLOAD_CREATE", "Failed to create the file %s for in-place writing: %v", fullPath, err) return false, nil } + entry.RestoreEarlyFileFlags(existingFile) n := int64(1) // There is a go bug on Windows (https://github.com/golang/go/issues/21681) that causes Seek to fail @@ -1377,6 +1379,7 @@ func (manager *BackupManager) RestoreFile(chunkDownloader *ChunkDownloader, chun return false, nil } } + entry.RestoreEarlyFileFlags(existingFile) existingFile.Seek(0, 0) @@ -1459,6 +1462,7 @@ func (manager *BackupManager) RestoreFile(chunkDownloader *ChunkDownloader, chun LOG_ERROR("DOWNLOAD_OPEN", "Failed to open file for writing: %v", err) return false, nil } + entry.RestoreEarlyFileFlags(newFile) hasher := manager.config.NewFileHasher() diff --git a/src/duplicacy_entry.go b/src/duplicacy_entry.go index fa56b86..31cbad1 100644 --- a/src/duplicacy_entry.go +++ b/src/duplicacy_entry.go @@ -771,6 +771,12 @@ func ListEntries(top string, path string, patterns []string, nobackupFile string } } + if f.Mode()&(os.ModeNamedPipe|os.ModeSocket|os.ModeDevice) != 0 { + LOG_WARN("LIST_SKIP", "Skipped non-regular file %s", entry.Path) + skippedFiles = append(skippedFiles, entry.Path) + continue + } + entry.ReadAttributes(top) if excludeByAttribute && entry.Attributes != nil && excludedByAttribute(*entry.Attributes) { @@ -778,12 +784,6 @@ func ListEntries(top string, path string, patterns []string, nobackupFile string continue } - if f.Mode()&(os.ModeNamedPipe|os.ModeSocket|os.ModeDevice) != 0 { - LOG_WARN("LIST_SKIP", "Skipped non-regular file %s", entry.Path) - skippedFiles = append(skippedFiles, entry.Path) - continue - } - if entry.IsDir() { directoryList = append(directoryList, entry) } else { diff --git a/src/duplicacy_utils_bsd.go b/src/duplicacy_utils_bsd.go new file mode 100644 index 0000000..79b6bd0 --- /dev/null +++ b/src/duplicacy_utils_bsd.go @@ -0,0 +1,53 @@ +// 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 || darwin +// +build freebsd netbsd darwin + +package duplicacy + +import ( + "encoding/binary" + "os" + "syscall" +) + +const bsdFileFlagsKey = "\x00bf" + +func (entry *Entry) ReadFileFlags(f *os.File) error { + fileInfo, err := f.Stat() + if err != nil { + return err + } + stat, ok := fileInfo.Sys().(*syscall.Stat_t) + if ok && 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 + LOG_DEBUG("ATTR_READ", "Read flags 0x%x for %s", stat.Flags, entry.Path) + } + return nil +} + +func (entry *Entry) RestoreEarlyDirFlags(path string) error { + return nil +} + +func (entry *Entry) RestoreEarlyFileFlags(f *os.File) error { + return nil +} + +func (entry *Entry) RestoreLateFileFlags(f *os.File) error { + if entry.Attributes == nil { + return nil + } + if v, have := (*entry.Attributes)[bsdFileFlagsKey]; have { + LOG_DEBUG("ATTR_RESTORE", "Restore flags 0x%x for %s", binary.LittleEndian.Uint32(v), entry.Path) + return syscall.Fchflags(int(f.Fd()), int(binary.LittleEndian.Uint32(v))) + } + return nil +} diff --git a/src/duplicacy_utils_linux.go b/src/duplicacy_utils_linux.go new file mode 100644 index 0000000..ec46fe8 --- /dev/null +++ b/src/duplicacy_utils_linux.go @@ -0,0 +1,112 @@ +// 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 ( + "encoding/binary" + "os" + "syscall" + "unsafe" +) + +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 +} + +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 excludedByAttribute(attributes map[string][]byte) bool { + _, ok := attributes["user.duplicacy_exclude"] + return ok +} diff --git a/src/duplicacy_utils_others.go b/src/duplicacy_utils_others.go index 71f7a1e..ebb5ff2 100644 --- a/src/duplicacy_utils_others.go +++ b/src/duplicacy_utils_others.go @@ -48,9 +48,12 @@ func SetOwner(fullPath string, entry *Entry, fileInfo *os.FileInfo) bool { } func (entry *Entry) ReadAttributes(top string) { - fullPath := filepath.Join(top, entry.Path) - attributes, _ := xattr.List(fullPath) + f, err := os.OpenFile(fullPath, os.O_RDONLY|syscall.O_NOFOLLOW, 0) + if err != nil { + return + } + attributes, _ := xattr.FList(f) if len(attributes) > 0 { entry.Attributes = &map[string][]byte{} for _, name := range attributes { @@ -60,30 +63,42 @@ func (entry *Entry) ReadAttributes(top string) { } } } + if err := entry.ReadFileFlags(f); err != nil { + LOG_INFO("ATTR_BACKUP", "Could not backup flags for file %s: %v", fullPath, err) + } + f.Close() } func (entry *Entry) SetAttributesToFile(fullPath string) { - names, _ := xattr.List(fullPath) + f, err := os.OpenFile(fullPath, os.O_RDONLY|syscall.O_NOFOLLOW, 0) + if err != nil { + return + } + names, _ := xattr.FList(f) for _, name := range names { - - newAttribute, found := (*entry.Attributes)[name] if found { - oldAttribute, _ := xattr.Get(fullPath, name) + oldAttribute, _ := xattr.FGet(f, name) if !bytes.Equal(oldAttribute, newAttribute) { - xattr.Set(fullPath, name, newAttribute) + xattr.FSet(f, name, newAttribute) } delete(*entry.Attributes, name) } else { - xattr.Remove(fullPath, name) + xattr.FRemove(f, name) } } for name, attribute := range *entry.Attributes { - xattr.Set(fullPath, name, attribute) + if len(name) > 0 && name[0] == '\x00' { + continue + } + xattr.FSet(f, name, attribute) } - + if err := entry.RestoreLateFileFlags(f); err != nil { + LOG_DEBUG("ATTR_RESTORE", "Could not restore flags for file %s: %v", fullPath, err) + } + f.Close() } func joinPath(components ...string) string { diff --git a/src/duplicacy_utils_posix.go b/src/duplicacy_utils_posix.go index 37b0386..a37f2c9 100644 --- a/src/duplicacy_utils_posix.go +++ b/src/duplicacy_utils_posix.go @@ -2,8 +2,8 @@ // Free for personal use and commercial trial // Commercial use requires per-user licenses available from https://duplicacy.com -//go:build freebsd || netbsd || linux || solaris -// +build freebsd netbsd linux solaris +//go:build freebsd || netbsd || solaris +// +build freebsd netbsd solaris package duplicacy diff --git a/src/duplicacy_utils_windows.go b/src/duplicacy_utils_windows.go index 5a4d8d1..f394d2e 100644 --- a/src/duplicacy_utils_windows.go +++ b/src/duplicacy_utils_windows.go @@ -132,6 +132,18 @@ func SplitDir(fullPath string) (dir string, file string) { return fullPath[:i+1], fullPath[i+1:] } -func excludedByAttribute(attributes map[string][]byte) bool { - return false +func (entry *Entry) ReadFileFlags(f *os.File) error { + return nil +} + +func (entry *Entry) RestoreEarlyDirFlags(path string) error { + return nil +} + +func (entry *Entry) RestoreEarlyFileFlags(f *os.File) error { + return nil +} + +func (entry *Entry) RestoreLateFileFlags(f *os.File) error { + return nil }