diff --git a/client/internal/statemanager/manager.go b/client/internal/statemanager/manager.go index 9a99c76f1..29f962ad2 100644 --- a/client/internal/statemanager/manager.go +++ b/client/internal/statemanager/manager.go @@ -303,20 +303,29 @@ func (m *Manager) loadStateFile(deleteCorrupt bool) (map[string]json.RawMessage, var rawStates map[string]json.RawMessage if err := json.Unmarshal(data, &rawStates); err != nil { - if deleteCorrupt { - log.Warn("State file appears to be corrupted, attempting to delete it", err) - if err := os.Remove(m.filePath); err != nil { - log.Errorf("Failed to delete corrupted state file: %v", err) - } else { - log.Info("State file deleted") - } - } + m.handleCorruptedState(deleteCorrupt) return nil, fmt.Errorf("unmarshal states: %w", err) } return rawStates, nil } +// handleCorruptedState creates a backup of a corrupted state file by moving it +func (m *Manager) handleCorruptedState(deleteCorrupt bool) { + if !deleteCorrupt { + return + } + log.Warn("State file appears to be corrupted, attempting to back it up") + + backupPath := fmt.Sprintf("%s.corrupted.%d", m.filePath, time.Now().UnixNano()) + if err := os.Rename(m.filePath, backupPath); err != nil { + log.Errorf("Failed to backup corrupted state file: %v", err) + return + } + + log.Infof("Created backup of corrupted state file at: %s", backupPath) +} + // loadSingleRawState unmarshals a raw state into a concrete state object func (m *Manager) loadSingleRawState(name string, rawState json.RawMessage) (State, error) { stateType, ok := m.stateTypes[name] diff --git a/client/server/debug.go b/client/server/debug.go index a35a77a00..a37195b29 100644 --- a/client/server/debug.go +++ b/client/server/debug.go @@ -196,6 +196,10 @@ func (s *Server) createArchive(bundlePath *os.File, req *proto.DebugBundleReques log.Errorf("Failed to add state file to debug bundle: %v", err) } + if err := s.addCorruptedStateFiles(archive); err != nil { + log.Errorf("Failed to add corrupted state files to debug bundle: %v", err) + } + if s.logFile != "console" { if err := s.addLogfile(req, anonymizer, archive); err != nil { return fmt.Errorf("add log file: %w", err) @@ -407,6 +411,36 @@ func (s *Server) addStateFile(req *proto.DebugBundleRequest, anonymizer *anonymi return nil } +func (s *Server) addCorruptedStateFiles(archive *zip.Writer) error { + pattern := statemanager.GetDefaultStatePath() + if pattern == "" { + return nil + } + pattern += "*.corrupted.*" + matches, err := filepath.Glob(pattern) + if err != nil { + return fmt.Errorf("find corrupted state files: %w", err) + } + + for _, match := range matches { + data, err := os.ReadFile(match) + if err != nil { + log.Warnf("Failed to read corrupted state file %s: %v", match, err) + continue + } + + fileName := filepath.Base(match) + if err := addFileToZip(archive, bytes.NewReader(data), "corrupted_states/"+fileName); err != nil { + log.Warnf("Failed to add corrupted state file %s to zip: %v", fileName, err) + continue + } + + log.Debugf("Added corrupted state file to debug bundle: %s", fileName) + } + + return nil +} + func (s *Server) addLogfile(req *proto.DebugBundleRequest, anonymizer *anonymize.Anonymizer, archive *zip.Writer) error { logDir := filepath.Dir(s.logFile)