mirror of
https://github.com/jkl1337/duplicacy.git
synced 2026-01-02 03:34:39 -06:00
Compare commits
2 Commits
16885eaa61
...
b2749b6e20
| Author | SHA1 | Date | |
|---|---|---|---|
| b2749b6e20 | |||
| f06779659e |
@@ -753,8 +753,7 @@ func (manager *BackupManager) Restore(top string, revision int, inPlace bool, qu
|
||||
|
||||
fullPath := joinPath(top, remoteEntry.Path)
|
||||
if remoteEntry.IsLink() {
|
||||
stat, err := os.Lstat(fullPath)
|
||||
if stat != nil {
|
||||
if stat, _ := os.Lstat(fullPath); stat != nil {
|
||||
if stat.Mode()&os.ModeSymlink != 0 {
|
||||
isRegular, link, err := Readlink(fullPath)
|
||||
if err == nil && link == remoteEntry.Link && !isRegular {
|
||||
@@ -763,11 +762,16 @@ func (manager *BackupManager) Restore(top string, revision int, inPlace bool, qu
|
||||
}
|
||||
}
|
||||
|
||||
if !overwrite {
|
||||
LOG_WERROR(allowFailures, "DOWNLOAD_OVERWRITE",
|
||||
"File %s already exists. Please specify the -overwrite option to overwrite", remoteEntry.Path)
|
||||
continue
|
||||
}
|
||||
|
||||
os.Remove(fullPath)
|
||||
}
|
||||
|
||||
err = os.Symlink(remoteEntry.Link, fullPath)
|
||||
if err != nil {
|
||||
if err := os.Symlink(remoteEntry.Link, fullPath); err != nil {
|
||||
LOG_ERROR("RESTORE_SYMLINK", "Can't create symlink %s: %v", remoteEntry.Path, err)
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
// 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
|
||||
}
|
||||
90
src/duplicacy_utils_bsd_common.go
Normal file
90
src/duplicacy_utils_bsd_common.go
Normal file
@@ -0,0 +1,90 @@
|
||||
// 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"
|
||||
"path/filepath"
|
||||
"bytes"
|
||||
"syscall"
|
||||
|
||||
"github.com/pkg/xattr"
|
||||
)
|
||||
|
||||
const bsdFileFlagsKey = "\x00bf"
|
||||
|
||||
func (entry *Entry) ReadAttributes(top string) {
|
||||
fullPath := filepath.Join(top, entry.Path)
|
||||
fileInfo, err := os.Lstat(fullPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
attributes, _ := xattr.LList(fullPath)
|
||||
if len(attributes) > 0 {
|
||||
entry.Attributes = &map[string][]byte{}
|
||||
for _, name := range attributes {
|
||||
attribute, err := xattr.LGet(fullPath, name)
|
||||
if err == nil {
|
||||
(*entry.Attributes)[name] = attribute
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := entry.readFileFlags(fileInfo); err != nil {
|
||||
LOG_INFO("ATTR_BACKUP", "Could not backup flags for file %s: %v", fullPath, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) SetAttributesToFile(fullPath string) {
|
||||
names, _ := xattr.LList(fullPath)
|
||||
for _, name := range names {
|
||||
newAttribute, found := (*entry.Attributes)[name]
|
||||
if found {
|
||||
oldAttribute, _ := xattr.LGet(fullPath, name)
|
||||
if !bytes.Equal(oldAttribute, newAttribute) {
|
||||
xattr.LSet(fullPath, name, newAttribute)
|
||||
}
|
||||
delete(*entry.Attributes, name)
|
||||
} else {
|
||||
xattr.LRemove(fullPath, name)
|
||||
}
|
||||
}
|
||||
|
||||
for name, attribute := range *entry.Attributes {
|
||||
if len(name) > 0 && name[0] == '\x00' {
|
||||
continue
|
||||
}
|
||||
xattr.LSet(fullPath, name, attribute)
|
||||
}
|
||||
if err := entry.restoreLateFileFlags(fullPath); err != nil {
|
||||
LOG_DEBUG("ATTR_RESTORE", "Could not restore flags for file %s: %v", fullPath, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) readFileFlags(fileInfo os.FileInfo) error {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -5,10 +5,29 @@
|
||||
package duplicacy
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
"strings"
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
func excludedByAttribute(attributes map[string][]byte) bool {
|
||||
value, ok := attributes["com.apple.metadata:com_apple_backup_excludeItem"]
|
||||
return ok && strings.Contains(string(value), "com.apple.backupd")
|
||||
}
|
||||
|
||||
func (entry *Entry) restoreLateFileFlags(path string) error {
|
||||
if entry.Attributes == nil {
|
||||
return nil
|
||||
}
|
||||
if v, have := (*entry.Attributes)[bsdFileFlagsKey]; have {
|
||||
f, err := os.OpenFile(path, os.O_RDONLY|syscall.O_SYMLINK, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = syscall.Fchflags(int(f.Fd()), int(binary.LittleEndian.Uint32(v)))
|
||||
f.Close()
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -5,10 +5,14 @@
|
||||
package duplicacy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/pkg/xattr"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -47,7 +51,113 @@ func ioctl(f *os.File, request uintptr, attrp *uint32) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (entry *Entry) ReadFileFlags(f *os.File) error {
|
||||
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.LSet(x.fullPath, name)
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) ReadAttributes(top string) {
|
||||
x := xattrHandle{nil, filepath.Join(top, entry.Path)}
|
||||
|
||||
if !entry.IsLink() {
|
||||
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(f, 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)
|
||||
}
|
||||
}
|
||||
f.Close()
|
||||
}
|
||||
|
||||
func (entry *Entry) SetAttributesToFile(fullPath string) {
|
||||
x := xattrHandle{nil, fullPath}
|
||||
if !entry.IsLink() {
|
||||
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(f); err != nil {
|
||||
LOG_DEBUG("ATTR_RESTORE", "Could not restore flags for file %s: %v", fullPath, err)
|
||||
}
|
||||
}
|
||||
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
|
||||
@@ -94,7 +204,7 @@ func (entry *Entry) RestoreEarlyFileFlags(f *os.File) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (entry *Entry) RestoreLateFileFlags(f *os.File) error {
|
||||
func (entry *Entry) restoreLateFileFlags(f *os.File) error {
|
||||
if entry.Attributes == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2,18 +2,15 @@
|
||||
// Free for personal use and commercial trial
|
||||
// Commercial use requires per-user licenses available from https://duplicacy.com
|
||||
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package duplicacy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"github.com/pkg/xattr"
|
||||
)
|
||||
|
||||
func Readlink(path string) (isRegular bool, s string, err error) {
|
||||
@@ -47,60 +44,6 @@ func SetOwner(fullPath string, entry *Entry, fileInfo *os.FileInfo) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (entry *Entry) ReadAttributes(top string) {
|
||||
fullPath := filepath.Join(top, entry.Path)
|
||||
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 {
|
||||
attribute, err := xattr.Get(fullPath, name)
|
||||
if err == nil {
|
||||
(*entry.Attributes)[name] = attribute
|
||||
}
|
||||
}
|
||||
}
|
||||
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) {
|
||||
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.FGet(f, name)
|
||||
if !bytes.Equal(oldAttribute, newAttribute) {
|
||||
xattr.FSet(f, name, newAttribute)
|
||||
}
|
||||
delete(*entry.Attributes, name)
|
||||
} else {
|
||||
xattr.FRemove(f, name)
|
||||
}
|
||||
}
|
||||
|
||||
for name, attribute := range *entry.Attributes {
|
||||
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 {
|
||||
return path.Join(components...)
|
||||
}
|
||||
|
||||
30
src/duplicacy_utils_xbsd.go
Normal file
30
src/duplicacy_utils_xbsd.go
Normal file
@@ -0,0 +1,30 @@
|
||||
// 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"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"github.com/pkg/xattr"
|
||||
)
|
||||
|
||||
func (entry *Entry) restoreLateFileFlags(path string) error {
|
||||
if entry.Attributes == nil {
|
||||
return nil
|
||||
}
|
||||
if v, have := (*entry.Attributes)[bsdFileFlagsKey]; have {
|
||||
if _, _, errno := syscall.Syscall(syscall.SYS_LCHFLAGS, uintptr(unsafe.Pointer(syscall.StringBytePtr(path))), uintptr(v), 0); errno != 0 {
|
||||
return os.NewSyscallError("lchflags", errno)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user