From a28940ea2970d504e2f083b0c827ef917a3bbf2b Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Sun, 22 Oct 2023 16:28:48 +0200 Subject: [PATCH] check: Suggest usage of `restic repair packs` for corrupted blobs For now, the guide is only shown if the blob content does not match its hash. The main intended usage is to handle data corruption errors when using maximum compression in restic 0.16.0 --- cmd/restic/cmd_check.go | 17 +++++++++++++++++ internal/checker/checker.go | 12 +++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/cmd/restic/cmd_check.go b/cmd/restic/cmd_check.go index 5f03c446b..c637ce89c 100644 --- a/cmd/restic/cmd_check.go +++ b/cmd/restic/cmd_check.go @@ -330,11 +330,28 @@ func runCheck(ctx context.Context, opts CheckOptions, gopts GlobalOptions, args go chkr.ReadPacks(ctx, packs, p, errChan) + var salvagePacks restic.IDs + for err := range errChan { errorsFound = true Warnf("%v\n", err) + if err, ok := err.(*checker.ErrPackData); ok { + if strings.Contains(err.Error(), "wrong data returned, hash is") { + salvagePacks = append(salvagePacks, err.PackID) + } + } } p.Done() + + if len(salvagePacks) > 0 { + Warnf("\nThe repository contains pack files with damaged blobs. These blobs must be removed to repair the repository. This can be done using the following commands:\n\n") + var strIds []string + for _, id := range salvagePacks { + strIds = append(strIds, id.String()) + } + Warnf("restic repair packs %v\nrestic repair snapshots --forget\n\n", strings.Join(strIds, " ")) + Warnf("Corrupted blobs are either caused by hardware problems or bugs in restic. Please open an issue at https://github.com/restic/restic/issues/new/choose for further troubleshooting!\n") + } } switch { diff --git a/internal/checker/checker.go b/internal/checker/checker.go index a5bb43731..59bc20daf 100644 --- a/internal/checker/checker.go +++ b/internal/checker/checker.go @@ -90,6 +90,16 @@ func (err *ErrOldIndexFormat) Error() string { return fmt.Sprintf("index %v has old format", err.ID) } +// ErrPackData is returned if errors are discovered while verifying a packfile +type ErrPackData struct { + PackID restic.ID + errs []error +} + +func (e *ErrPackData) Error() string { + return fmt.Sprintf("pack %v contains %v errors: %v", e.PackID, len(e.errs), e.errs) +} + func (c *Checker) LoadSnapshots(ctx context.Context) error { var err error c.snapshots, err = backend.MemorizeList(ctx, c.repo.Backend(), restic.SnapshotFile) @@ -635,7 +645,7 @@ func checkPack(ctx context.Context, r restic.Repository, id restic.ID, blobs []r } if len(errs) > 0 { - return errors.Errorf("pack %v contains %v errors: %v", id, len(errs), errs) + return &ErrPackData{PackID: id, errs: errs} } return nil