From 96e7c93a2c69ae7a7c5df32e5d53fb5dcc885000 Mon Sep 17 00:00:00 2001 From: "John K. Luebs" Date: Tue, 3 Oct 2023 15:08:34 -0500 Subject: [PATCH] Support backup and restore of special files on POSIX style systems Special files are device nodes and named pipes. The necessity of the former is clear, the latter is debatable. In order to preserve backward compatibility, the device number is encoded in the StartChunk/StartOffset fields of the entry. --- src/duplicacy_backupmanager.go | 15 +++++++++++++++ src/duplicacy_entry.go | 17 ++++++++++++----- src/duplicacy_entrylist.go | 4 ++-- src/duplicacy_utils_linux.go | 3 +++ src/duplicacy_utils_others.go | 33 ++++++++++++++++++++++++++++++++- src/duplicacy_utils_windows.go | 8 ++++++++ 6 files changed, 72 insertions(+), 8 deletions(-) diff --git a/src/duplicacy_backupmanager.go b/src/duplicacy_backupmanager.go index 8d8b38d..e44550d 100644 --- a/src/duplicacy_backupmanager.go +++ b/src/duplicacy_backupmanager.go @@ -797,6 +797,21 @@ func (manager *BackupManager) Restore(top string, revision int, inPlace bool, qu } remoteEntry.RestoreEarlyDirFlags(fullPath) directoryEntries = append(directoryEntries, remoteEntry) + } else if remoteEntry.IsSpecial() { + if stat, _ := os.Lstat(fullPath); stat != nil { + if !overwrite { + LOG_WERROR(allowFailures, "DOWNLOAD_OVERWRITE", + "File %s already exists. Please specify the -overwrite option to overwrite", remoteEntry.Path) + continue + } + os.Remove(fullPath) + } + + if err := remoteEntry.RestoreSpecial(fullPath); err != nil { + LOG_ERROR("RESTORE_SPECIAL", "Unable to restore special file %s: %v", remoteEntry.Path, err) + return 0 + } + remoteEntry.RestoreMetadata(fullPath, nil, setOwner) } else { if remoteEntry.IsHardlinkRoot() { hardLinkTable[len(hardLinkTable)-1] = hardLinkEntry{remoteEntry, true} diff --git a/src/duplicacy_entry.go b/src/duplicacy_entry.go index b33b53b..6e00ba1 100644 --- a/src/duplicacy_entry.go +++ b/src/duplicacy_entry.go @@ -509,6 +509,10 @@ func (entry *Entry) IsLink() bool { return entry.Mode&uint32(os.ModeSymlink) != 0 } +func (entry *Entry) IsSpecial() bool { + return entry.Mode&uint32(os.ModeNamedPipe|os.ModeDevice|os.ModeCharDevice) != 0 +} + func (entry *Entry) IsComplete() bool { return entry.Size >= 0 } @@ -815,12 +819,15 @@ func ListEntries(top string, path string, patterns []string, nobackupFile string } entry = newEntry } - } - - 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) + } 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) + skippedFiles = append(skippedFiles, entry.Path) + continue + } } var linkKey *listEntryLinkKey diff --git a/src/duplicacy_entrylist.go b/src/duplicacy_entrylist.go index 6f21a5f..0e5ef97 100644 --- a/src/duplicacy_entrylist.go +++ b/src/duplicacy_entrylist.go @@ -111,12 +111,12 @@ func (entryList *EntryList)createOnDiskFile() error { // Add an entry to the entry list func (entryList *EntryList)AddEntry(entry *Entry) error { - if !entry.IsDir() && !entry.IsLink() { + if entry.IsFile() { entryList.NumberOfEntries++ } if !entry.IsComplete() { - if entry.IsDir() || entry.IsLink() { + if !entry.IsFile() { entry.Size = 0 } else { modifiedEntry := ModifiedEntry { diff --git a/src/duplicacy_utils_linux.go b/src/duplicacy_utils_linux.go index ec46fe8..ac6dff1 100644 --- a/src/duplicacy_utils_linux.go +++ b/src/duplicacy_utils_linux.go @@ -48,6 +48,9 @@ func ioctl(f *os.File, request uintptr, attrp *uint32) error { } func (entry *Entry) ReadFileFlags(f *os.File) error { + if entry.IsSpecial() { + return nil + } var flags uint32 if err := ioctl(f, linux_FS_IOC_GETFLAGS, &flags); err != nil { return err diff --git a/src/duplicacy_utils_others.go b/src/duplicacy_utils_others.go index ebb5ff2..c202e65 100644 --- a/src/duplicacy_utils_others.go +++ b/src/duplicacy_utils_others.go @@ -49,7 +49,7 @@ func SetOwner(fullPath string, entry *Entry, fileInfo *os.FileInfo) bool { func (entry *Entry) ReadAttributes(top string) { fullPath := filepath.Join(top, entry.Path) - f, err := os.OpenFile(fullPath, os.O_RDONLY|syscall.O_NOFOLLOW, 0) + f, err := os.OpenFile(fullPath, os.O_RDONLY|syscall.O_NOFOLLOW|syscall.O_NONBLOCK, 0) if err != nil { return } @@ -101,6 +101,37 @@ func (entry *Entry) SetAttributesToFile(fullPath string) { f.Close() } +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 { + return false + } + entry.Size = 0 + rdev := uint64(stat.Rdev) + entry.StartChunk = int(rdev & 0xFFFFFFFF) + entry.StartOffset = int(rdev >> 32) + return true +} + +func (entry *Entry) RestoreSpecial(fullPath string) error { + if entry.Mode & uint32(os.ModeDevice | os.ModeCharDevice) != 0 { + mode := entry.Mode & uint32(fileModeMask) + if entry.Mode & uint32(os.ModeCharDevice) != 0 { + mode |= syscall.S_IFCHR + } else { + mode |= syscall.S_IFBLK + } + rdev := uint64(entry.StartChunk) | uint64(entry.StartOffset) << 32 + return syscall.Mknod(fullPath, mode, int(rdev)) + } else if entry.Mode & uint32(os.ModeNamedPipe) != 0 { + return syscall.Mkfifo(fullPath, uint32(entry.Mode)) + } + return nil +} + func joinPath(components ...string) string { return path.Join(components...) } diff --git a/src/duplicacy_utils_windows.go b/src/duplicacy_utils_windows.go index f394d2e..3846ca6 100644 --- a/src/duplicacy_utils_windows.go +++ b/src/duplicacy_utils_windows.go @@ -117,6 +117,14 @@ func (entry *Entry) SetAttributesToFile(fullPath string) { } +func (entry *Entry) ReadDeviceNode(fileInfo os.FileInfo) bool { + return nil +} + +func (entry *Entry) RestoreSpecial(fullPath string) error { + return nil +} + func joinPath(components ...string) string { combinedPath := `\\?\` + filepath.Join(components...)