Compare commits

...

2 Commits

Author SHA1 Message Date
96e7c93a2c 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.
2023-10-03 16:26:19 -05:00
f06779659e Don't overwrite symlinks if file already exists 2023-10-03 15:08:47 -05:00
6 changed files with 80 additions and 12 deletions

View File

@@ -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
}
@@ -793,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}

View File

@@ -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,13 +819,16 @@ 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)
} 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

View File

@@ -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 {

View File

@@ -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

View File

@@ -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...)
}

View File

@@ -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...)