package main import ( "errors" "io/fs" "os" "syscall" "time" ) const maxRetries = 3 const retryDelay = 100 * time.Millisecond type SyncError struct { Path string Op string Err error } func (e *SyncError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() } func (e *SyncError) Unwrap() error { return e.Err } func retry(name string, fn func() error) error { var lastErr error for i := 0; i < maxRetries; i++ { lastErr = fn() if lastErr == nil { return nil } if !isRetryable(lastErr) { return lastErr } time.Sleep(retryDelay * time.Duration(i+1)) } return lastErr } func isRetryable(err error) bool { if errors.Is(err, fs.ErrNotExist) { return true } if errors.Is(err, syscall.ENOENT) { return true } return false } func logSyncError(op, relPath string, err error) { if err == nil { return } if errors.Is(err, fs.ErrNotExist) { return } var pathErr *fs.PathError if errors.As(err, &pathErr) { err = pathErr.Err } switch { case errors.Is(err, syscall.ENOSPC): logErrorf("DISK FULL: %s %s failed - no space left on device", op, relPath) case errors.Is(err, syscall.EACCES), errors.Is(err, syscall.EPERM): logErrorf("PERMISSION DENIED: %s %s - check volume mount permissions", op, relPath) case errors.Is(err, syscall.ELOOP): logErrorf("SYMLINK LOOP: %s %s - too many symbolic links", op, relPath) default: logErrorf("%s %s: %v", op, relPath, err) } } func copyFileWithRetry(src, dst string, mode os.FileMode) error { return retry("copy", func() error { return copyFile(src, dst, mode) }) }