mirror of
https://github.com/jkl1337/duplicacy.git
synced 2026-01-02 03:34:39 -06:00
Fix handling of hardlinks and special files
Also, don't attempt xattr ops on special files on the BSD likes. TODO: Should have a way to allow restore without special files, otherwise very cumbersome for a regular user.
This commit is contained in:
@@ -710,6 +710,29 @@ func (manager *BackupManager) Restore(top string, revision int, inPlace bool, qu
|
||||
var hardLinkTable []hardLinkEntry
|
||||
var hardLinks []*Entry
|
||||
|
||||
restoreHardlink := func(entry *Entry, fullPath string) bool {
|
||||
if entry.IsHardlinkRoot() {
|
||||
hardLinkTable[len(hardLinkTable)-1].willExist = true
|
||||
} else if entry.IsHardlinkedFrom() {
|
||||
i, err := entry.GetHardlinkId()
|
||||
if err != nil {
|
||||
LOG_ERROR("RESTORE_HARDLINK", "Decode error for hardlinked entry %s, %v", entry.Path, err)
|
||||
return false
|
||||
}
|
||||
if !hardLinkTable[i].willExist {
|
||||
hardLinkTable[i] = hardLinkEntry{entry, true}
|
||||
} else {
|
||||
sourcePath := joinPath(top, hardLinkTable[i].entry.Path)
|
||||
LOG_INFO("RESTORE_HARDLINK", "Hard linking %s to %s", fullPath, sourcePath)
|
||||
if err := MakeHardlink(sourcePath, fullPath); err != nil {
|
||||
LOG_ERROR("RESTORE_HARDLINK", "Failed to create hard link %s to %s %v", fullPath, sourcePath, err)
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
for remoteEntry := range remoteListingChannel {
|
||||
|
||||
if remoteEntry.IsHardlinkRoot() {
|
||||
@@ -775,26 +798,9 @@ func (manager *BackupManager) Restore(top string, revision int, inPlace bool, qu
|
||||
os.Remove(fullPath)
|
||||
}
|
||||
|
||||
if remoteEntry.IsHardlinkRoot() {
|
||||
hardLinkTable[len(hardLinkTable)-1].willExist = true
|
||||
} else if remoteEntry.IsHardlinkedFrom() {
|
||||
i, err := remoteEntry.GetHardlinkId()
|
||||
if err != nil {
|
||||
LOG_ERROR("RESTORE_HARDLINK", "Decode error for hardlinked entry %s, %v", remoteEntry.Path, err)
|
||||
return 0
|
||||
}
|
||||
if !hardLinkTable[i].willExist {
|
||||
hardLinkTable[i] = hardLinkEntry{remoteEntry, true}
|
||||
} else {
|
||||
sourcePath := joinPath(top, hardLinkTable[i].entry.Path)
|
||||
LOG_INFO("RESTORE_HARDLINK", "Hard linking %s to %s", fullPath, sourcePath)
|
||||
if err := MakeHardlink(sourcePath, fullPath); err != nil {
|
||||
LOG_ERROR("RESTORE_HARDLINK", "Failed to create hard link %s to %s %v", fullPath, sourcePath, err)
|
||||
return 0
|
||||
}
|
||||
if restoreHardlink(remoteEntry, fullPath) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if err := os.Symlink(remoteEntry.Link, fullPath); err != nil {
|
||||
LOG_ERROR("RESTORE_SYMLINK", "Can't create symlink %s: %v", remoteEntry.Path, err)
|
||||
@@ -825,6 +831,12 @@ func (manager *BackupManager) Restore(top string, revision int, inPlace bool, qu
|
||||
directoryEntries = append(directoryEntries, remoteEntry)
|
||||
} else if remoteEntry.IsSpecial() {
|
||||
if stat, _ := os.Lstat(fullPath); stat != nil {
|
||||
if remoteEntry.IsSameSpecial(stat) {
|
||||
remoteEntry.RestoreMetadata(fullPath, nil, setOwner)
|
||||
if remoteEntry.IsHardlinkRoot() {
|
||||
hardLinkTable[len(hardLinkTable)-1].willExist = true
|
||||
}
|
||||
}
|
||||
if !overwrite {
|
||||
LOG_WERROR(allowFailures, "DOWNLOAD_OVERWRITE",
|
||||
"File %s already exists. Please specify the -overwrite option to overwrite", remoteEntry.Path)
|
||||
@@ -833,6 +845,10 @@ func (manager *BackupManager) Restore(top string, revision int, inPlace bool, qu
|
||||
os.Remove(fullPath)
|
||||
}
|
||||
|
||||
if restoreHardlink(remoteEntry, fullPath) {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := remoteEntry.RestoreSpecial(fullPath); err != nil {
|
||||
LOG_ERROR("RESTORE_SPECIAL", "Unable to restore special file %s: %v", remoteEntry.Path, err)
|
||||
return 0
|
||||
|
||||
@@ -519,16 +519,20 @@ func (entry *Entry) IsSpecial() bool {
|
||||
return entry.Mode&uint32(os.ModeNamedPipe|os.ModeDevice|os.ModeCharDevice) != 0
|
||||
}
|
||||
|
||||
func (entry *Entry) IsFileOrSpecial() bool {
|
||||
return entry.Mode&uint32(os.ModeDir|os.ModeSymlink|os.ModeIrregular) == 0
|
||||
}
|
||||
|
||||
func (entry *Entry) IsComplete() bool {
|
||||
return entry.Size >= 0
|
||||
}
|
||||
|
||||
func (entry *Entry) IsHardlinkedFrom() bool {
|
||||
return (entry.IsFile() && len(entry.Link) > 0 && entry.Link != "/") || (entry.IsLink() && entry.StartChunk == entrySymHardLinkTargetChunkMarker)
|
||||
return (entry.IsFileOrSpecial() && len(entry.Link) > 0 && entry.Link != "/") || (entry.IsLink() && entry.StartChunk == entrySymHardLinkTargetChunkMarker)
|
||||
}
|
||||
|
||||
func (entry *Entry) IsHardlinkRoot() bool {
|
||||
return (entry.IsFile() && entry.Link == "/") || (entry.IsLink() && entry.StartChunk == entrySymHardLinkRootChunkMarker)
|
||||
return (entry.IsFileOrSpecial() && entry.Link == "/") || (entry.IsLink() && entry.StartChunk == entrySymHardLinkRootChunkMarker)
|
||||
}
|
||||
|
||||
func (entry *Entry) GetHardlinkId() (int, error) {
|
||||
@@ -601,6 +605,11 @@ func (entry *Entry) RestoreMetadata(fullPath string, fileInfo *os.FileInfo, setO
|
||||
}
|
||||
}
|
||||
|
||||
if entry.Attributes != nil && len(*entry.Attributes) > 0 {
|
||||
entry.SetAttributesToFile(fullPath)
|
||||
}
|
||||
|
||||
|
||||
// Only set the time if the file is not a symlink
|
||||
if !entry.IsLink() && (*fileInfo).ModTime().Unix() != entry.Time {
|
||||
modifiedTime := time.Unix(entry.Time, 0)
|
||||
@@ -611,10 +620,6 @@ func (entry *Entry) RestoreMetadata(fullPath string, fileInfo *os.FileInfo, setO
|
||||
}
|
||||
}
|
||||
|
||||
if entry.Attributes != nil && len(*entry.Attributes) > 0 {
|
||||
entry.SetAttributesToFile(fullPath)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -803,14 +808,12 @@ func ListEntries(top string, path string, patterns []string, nobackupFile string
|
||||
if f.Name() == DUPLICACY_DIRECTORY {
|
||||
continue
|
||||
}
|
||||
entry := CreateEntryFromFileInfo(f, normalizedPath)
|
||||
if len(patterns) > 0 && !MatchPath(entry.Path, patterns) {
|
||||
if f.Mode() & os.ModeSocket != 0 {
|
||||
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)
|
||||
entry := CreateEntryFromFileInfo(f, normalizedPath)
|
||||
if len(patterns) > 0 && !MatchPath(entry.Path, patterns) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -873,9 +876,6 @@ func ListEntries(top string, path string, patterns []string, nobackupFile string
|
||||
}
|
||||
entry = newEntry
|
||||
}
|
||||
} else if entry.Mode & uint32(os.ModeSocket) != 0 {
|
||||
// no reason to issue a warning for what should always be a transient file anyways
|
||||
continue
|
||||
} else if entry.IsSpecial() {
|
||||
if !entry.ReadSpecial(f) {
|
||||
LOG_WARN("LIST_DEV", "Failed to save device node %s", entry.Path)
|
||||
|
||||
@@ -25,6 +25,8 @@ func (entry *Entry) ReadAttributes(top string) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !entry.IsSpecial() {
|
||||
attributes, _ := xattr.LList(fullPath)
|
||||
if len(attributes) > 0 {
|
||||
entry.Attributes = &map[string][]byte{}
|
||||
@@ -35,12 +37,14 @@ func (entry *Entry) ReadAttributes(top string) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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) {
|
||||
if !entry.IsSpecial() {
|
||||
names, _ := xattr.LList(fullPath)
|
||||
for _, name := range names {
|
||||
newAttribute, found := (*entry.Attributes)[name]
|
||||
@@ -61,6 +65,7 @@ func (entry *Entry) SetAttributesToFile(fullPath string) {
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -50,8 +50,8 @@ func (entry *Entry) ReadSpecial(fileInfo os.FileInfo) bool {
|
||||
if fileInfo.Mode()&(os.ModeDevice|os.ModeCharDevice) == 0 {
|
||||
return true
|
||||
}
|
||||
stat, ok := fileInfo.Sys().(*syscall.Stat_t)
|
||||
if !ok || stat == nil {
|
||||
stat := fileInfo.Sys().(*syscall.Stat_t)
|
||||
if stat == nil {
|
||||
return false
|
||||
}
|
||||
entry.Size = 0
|
||||
@@ -61,6 +61,10 @@ func (entry *Entry) ReadSpecial(fileInfo os.FileInfo) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (entry *Entry) GetRdev() uint64 {
|
||||
return uint64(entry.StartChunk)|uint64(entry.StartOffset)<<32
|
||||
}
|
||||
|
||||
func (entry *Entry) RestoreSpecial(fullPath string) error {
|
||||
mode := entry.Mode & uint32(fileModeMask)
|
||||
|
||||
@@ -73,7 +77,15 @@ func (entry *Entry) RestoreSpecial(fullPath string) error {
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
return syscall.Mknod(fullPath, mode, int(uint64(entry.StartChunk)|uint64(entry.StartOffset)<<32))
|
||||
return syscall.Mknod(fullPath, mode, int(entry.GetRdev()))
|
||||
}
|
||||
|
||||
func (entry *Entry) IsSameSpecial(fileInfo os.FileInfo) bool {
|
||||
stat := fileInfo.Sys().(*syscall.Stat_t)
|
||||
if stat == nil {
|
||||
return false
|
||||
}
|
||||
return (uint32(fileInfo.Mode()) == entry.Mode) && (uint64(stat.Rdev) == entry.GetRdev())
|
||||
}
|
||||
|
||||
func MakeHardlink(source string, target string) error {
|
||||
|
||||
Reference in New Issue
Block a user