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:
2023-10-04 01:01:46 -05:00
committed by John K. Luebs
parent a1a3f3d4cb
commit 70ea4d3acf
4 changed files with 91 additions and 58 deletions

View File

@@ -710,6 +710,29 @@ func (manager *BackupManager) Restore(top string, revision int, inPlace bool, qu
var hardLinkTable []hardLinkEntry var hardLinkTable []hardLinkEntry
var hardLinks []*Entry 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 { for remoteEntry := range remoteListingChannel {
if remoteEntry.IsHardlinkRoot() { if remoteEntry.IsHardlinkRoot() {
@@ -775,26 +798,9 @@ func (manager *BackupManager) Restore(top string, revision int, inPlace bool, qu
os.Remove(fullPath) os.Remove(fullPath)
} }
if remoteEntry.IsHardlinkRoot() { if restoreHardlink(remoteEntry, fullPath) {
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 continue
} }
}
if err := os.Symlink(remoteEntry.Link, fullPath); err != nil { if err := os.Symlink(remoteEntry.Link, fullPath); err != nil {
LOG_ERROR("RESTORE_SYMLINK", "Can't create symlink %s: %v", remoteEntry.Path, err) 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) directoryEntries = append(directoryEntries, remoteEntry)
} else if remoteEntry.IsSpecial() { } else if remoteEntry.IsSpecial() {
if stat, _ := os.Lstat(fullPath); stat != nil { 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 { if !overwrite {
LOG_WERROR(allowFailures, "DOWNLOAD_OVERWRITE", LOG_WERROR(allowFailures, "DOWNLOAD_OVERWRITE",
"File %s already exists. Please specify the -overwrite option to overwrite", remoteEntry.Path) "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) os.Remove(fullPath)
} }
if restoreHardlink(remoteEntry, fullPath) {
continue
}
if err := remoteEntry.RestoreSpecial(fullPath); err != nil { if err := remoteEntry.RestoreSpecial(fullPath); err != nil {
LOG_ERROR("RESTORE_SPECIAL", "Unable to restore special file %s: %v", remoteEntry.Path, err) LOG_ERROR("RESTORE_SPECIAL", "Unable to restore special file %s: %v", remoteEntry.Path, err)
return 0 return 0

View File

@@ -519,16 +519,20 @@ func (entry *Entry) IsSpecial() bool {
return entry.Mode&uint32(os.ModeNamedPipe|os.ModeDevice|os.ModeCharDevice) != 0 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 { func (entry *Entry) IsComplete() bool {
return entry.Size >= 0 return entry.Size >= 0
} }
func (entry *Entry) IsHardlinkedFrom() bool { 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 { 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) { 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 // Only set the time if the file is not a symlink
if !entry.IsLink() && (*fileInfo).ModTime().Unix() != entry.Time { if !entry.IsLink() && (*fileInfo).ModTime().Unix() != entry.Time {
modifiedTime := time.Unix(entry.Time, 0) 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 return true
} }
@@ -803,14 +808,12 @@ func ListEntries(top string, path string, patterns []string, nobackupFile string
if f.Name() == DUPLICACY_DIRECTORY { if f.Name() == DUPLICACY_DIRECTORY {
continue continue
} }
entry := CreateEntryFromFileInfo(f, normalizedPath) if f.Mode() & os.ModeSocket != 0 {
if len(patterns) > 0 && !MatchPath(entry.Path, patterns) {
continue continue
} }
if f.Mode()&(os.ModeNamedPipe|os.ModeSocket|os.ModeDevice) != 0 { entry := CreateEntryFromFileInfo(f, normalizedPath)
LOG_WARN("LIST_SKIP", "Skipped non-regular file %s", entry.Path) if len(patterns) > 0 && !MatchPath(entry.Path, patterns) {
skippedFiles = append(skippedFiles, entry.Path)
continue continue
} }
@@ -873,9 +876,6 @@ func ListEntries(top string, path string, patterns []string, nobackupFile string
} }
entry = newEntry 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() { } else if entry.IsSpecial() {
if !entry.ReadSpecial(f) { if !entry.ReadSpecial(f) {
LOG_WARN("LIST_DEV", "Failed to save device node %s", entry.Path) LOG_WARN("LIST_DEV", "Failed to save device node %s", entry.Path)

View File

@@ -25,6 +25,8 @@ func (entry *Entry) ReadAttributes(top string) {
if err != nil { if err != nil {
return return
} }
if !entry.IsSpecial() {
attributes, _ := xattr.LList(fullPath) attributes, _ := xattr.LList(fullPath)
if len(attributes) > 0 { if len(attributes) > 0 {
entry.Attributes = &map[string][]byte{} entry.Attributes = &map[string][]byte{}
@@ -35,12 +37,14 @@ func (entry *Entry) ReadAttributes(top string) {
} }
} }
} }
}
if err := entry.readFileFlags(fileInfo); err != nil { if err := entry.readFileFlags(fileInfo); err != nil {
LOG_INFO("ATTR_BACKUP", "Could not backup flags for file %s: %v", fullPath, err) LOG_INFO("ATTR_BACKUP", "Could not backup flags for file %s: %v", fullPath, err)
} }
} }
func (entry *Entry) SetAttributesToFile(fullPath string) { func (entry *Entry) SetAttributesToFile(fullPath string) {
if !entry.IsSpecial() {
names, _ := xattr.LList(fullPath) names, _ := xattr.LList(fullPath)
for _, name := range names { for _, name := range names {
newAttribute, found := (*entry.Attributes)[name] newAttribute, found := (*entry.Attributes)[name]
@@ -61,6 +65,7 @@ func (entry *Entry) SetAttributesToFile(fullPath string) {
} }
xattr.LSet(fullPath, name, attribute) xattr.LSet(fullPath, name, attribute)
} }
}
if err := entry.restoreLateFileFlags(fullPath); err != nil { if err := entry.restoreLateFileFlags(fullPath); err != nil {
LOG_DEBUG("ATTR_RESTORE", "Could not restore flags for file %s: %v", fullPath, err) LOG_DEBUG("ATTR_RESTORE", "Could not restore flags for file %s: %v", fullPath, err)
} }

View File

@@ -50,8 +50,8 @@ func (entry *Entry) ReadSpecial(fileInfo os.FileInfo) bool {
if fileInfo.Mode()&(os.ModeDevice|os.ModeCharDevice) == 0 { if fileInfo.Mode()&(os.ModeDevice|os.ModeCharDevice) == 0 {
return true return true
} }
stat, ok := fileInfo.Sys().(*syscall.Stat_t) stat := fileInfo.Sys().(*syscall.Stat_t)
if !ok || stat == nil { if stat == nil {
return false return false
} }
entry.Size = 0 entry.Size = 0
@@ -61,6 +61,10 @@ func (entry *Entry) ReadSpecial(fileInfo os.FileInfo) bool {
return true return true
} }
func (entry *Entry) GetRdev() uint64 {
return uint64(entry.StartChunk)|uint64(entry.StartOffset)<<32
}
func (entry *Entry) RestoreSpecial(fullPath string) error { func (entry *Entry) RestoreSpecial(fullPath string) error {
mode := entry.Mode & uint32(fileModeMask) mode := entry.Mode & uint32(fileModeMask)
@@ -73,7 +77,15 @@ func (entry *Entry) RestoreSpecial(fullPath string) error {
} else { } else {
return nil 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 { func MakeHardlink(source string, target string) error {