Stop a backup on a symlink whose target cannot be read (closes #269)
check / check (push) Waiting to run
check / check (push) Waiting to run
When readlink failed, the scanner logged at debug level and left the symlink out of the snapshot, and the run reported success even without --skip-errors. The error now goes through the same handling as any other entry the walk cannot read: the run aborts, or with --skip-errors the symlink is skipped with the usual "Failed to access" error line. A symlink removed between the walk's lstat and the readlink also aborts the run, as an entry that vanishes during the walk already does. Model: opus-5-5
This commit is contained in:
@@ -889,9 +889,10 @@ func (s *Scanner) scanPhase(
|
||||
}
|
||||
|
||||
// Handle symlinks and directories
|
||||
if handled := s.recordSpecialEntry(
|
||||
filePath, info, existingFiles, collector, result); handled {
|
||||
return nil
|
||||
handled, err := s.recordSpecialEntry(
|
||||
filePath, info, existingFiles, collector, result)
|
||||
if handled {
|
||||
return err
|
||||
}
|
||||
|
||||
// Skip other non-regular files (devices, sockets, etc.)
|
||||
@@ -933,22 +934,25 @@ func (s *Scanner) scanPhase(
|
||||
}
|
||||
|
||||
// recordSpecialEntry records symlinks and directories (which have no
|
||||
// data to chunk) and reports whether it handled the entry.
|
||||
// data to chunk) and reports whether it handled the entry. For a symlink
|
||||
// whose target cannot be read it returns handleWalkError's result.
|
||||
func (s *Scanner) recordSpecialEntry(
|
||||
filePath string, info os.FileInfo,
|
||||
existingFiles map[string]struct{},
|
||||
collector *scanCollector, result *ScanResult,
|
||||
) bool {
|
||||
) (bool, error) {
|
||||
// Handle symlinks
|
||||
if info.Mode()&os.ModeSymlink != 0 {
|
||||
file := s.buildSymlinkEntry(filePath, info)
|
||||
if file != nil {
|
||||
existingFiles[filePath] = struct{}{}
|
||||
collector.addToProcess(filePath, info, file)
|
||||
s.updateScanEntryStats(result, true, info)
|
||||
file, err := s.buildSymlinkEntry(filePath, info)
|
||||
if err != nil {
|
||||
return true, s.handleWalkError(filePath, err)
|
||||
}
|
||||
|
||||
return true
|
||||
existingFiles[filePath] = struct{}{}
|
||||
collector.addToProcess(filePath, info, file)
|
||||
s.updateScanEntryStats(result, true, info)
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Handle directories (record for permission/ownership preservation
|
||||
@@ -958,10 +962,10 @@ func (s *Scanner) recordSpecialEntry(
|
||||
existingFiles[filePath] = struct{}{}
|
||||
collector.addToProcess(filePath, info, file)
|
||||
|
||||
return true
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// handleWalkError deals with a filesystem error surfaced by the walk:
|
||||
@@ -1111,13 +1115,12 @@ func (s *Scanner) printScanProgressLine(
|
||||
}
|
||||
|
||||
// buildSymlinkEntry creates a File record for a symlink.
|
||||
// Returns nil if the link target cannot be read.
|
||||
func (s *Scanner) buildSymlinkEntry(path string, info os.FileInfo) *database.File {
|
||||
func (s *Scanner) buildSymlinkEntry(
|
||||
path string, info os.FileInfo,
|
||||
) (*database.File, error) {
|
||||
target, err := os.Readlink(path)
|
||||
if err != nil {
|
||||
log.Debug("Cannot read symlink target", "path", path, "error", err)
|
||||
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var uid, gid uint32
|
||||
@@ -1136,7 +1139,7 @@ func (s *Scanner) buildSymlinkEntry(path string, info os.FileInfo) *database.Fil
|
||||
UID: uid,
|
||||
GID: gid,
|
||||
LinkTarget: types.FilePath(target),
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
// buildDirectoryEntry creates a File record for a directory.
|
||||
|
||||
Reference in New Issue
Block a user