diff --git a/src/duplicacy_backupmanager.go b/src/duplicacy_backupmanager.go index cde8957..8d57065 100644 --- a/src/duplicacy_backupmanager.go +++ b/src/duplicacy_backupmanager.go @@ -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,25 +798,8 @@ 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 - } - continue - } + if restoreHardlink(remoteEntry, fullPath) { + continue } if err := os.Symlink(remoteEntry.Link, fullPath); err != nil { @@ -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 diff --git a/src/duplicacy_entry.go b/src/duplicacy_entry.go index 095bf43..67634d0 100644 --- a/src/duplicacy_entry.go +++ b/src/duplicacy_entry.go @@ -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) diff --git a/src/duplicacy_utils_bsd_common.go b/src/duplicacy_utils_bsd_common.go index 8a2dbc4..947f118 100644 --- a/src/duplicacy_utils_bsd_common.go +++ b/src/duplicacy_utils_bsd_common.go @@ -25,13 +25,16 @@ func (entry *Entry) ReadAttributes(top string) { 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 !entry.IsSpecial() { + 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 + } } } } @@ -41,25 +44,27 @@ func (entry *Entry) ReadAttributes(top string) { } 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) + if !entry.IsSpecial() { + 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) } - delete(*entry.Attributes, name) - } else { - xattr.LRemove(fullPath, name) } - } - for name, attribute := range *entry.Attributes { - if len(name) > 0 && name[0] == '\x00' { - continue + for name, attribute := range *entry.Attributes { + if len(name) > 0 && name[0] == '\x00' { + continue + } + xattr.LSet(fullPath, name, attribute) } - 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) diff --git a/src/duplicacy_utils_others.go b/src/duplicacy_utils_others.go index 9767516..c05904d 100644 --- a/src/duplicacy_utils_others.go +++ b/src/duplicacy_utils_others.go @@ -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 {