Files
restic/internal/repository/repair_pack.go
T
Michael Eischer c062a78dcd repository: move Blob, Blobs and PackedBlob to pack package
This removes them from the public interface. The latter now only
provides the PackBlob interface, without being bound to the type used
internally by the pack package.
2026-06-13 18:58:37 +02:00

112 lines
3.1 KiB
Go

package repository
import (
"context"
"errors"
"io"
"slices"
"github.com/restic/restic/internal/repository/pack"
"github.com/restic/restic/internal/restic"
"github.com/restic/restic/internal/ui/progress"
)
func RepairPacks(ctx context.Context, repo *Repository, ids restic.IDSet, printer progress.Printer) error {
printer.P("salvaging intact data from specified pack files")
bar := printer.NewCounter("pack files")
bar.SetMax(uint64(len(ids)))
defer bar.Done()
packToBlobs, err := resolveBlobsForPacks(ctx, repo, ids)
if err != nil {
return err
}
err = repo.WithBlobUploader(ctx, func(ctx context.Context, uploader restic.BlobSaverWithAsync) error {
// examine all data the indexes have for the pack file
for b := range repo.listPacksFromIndex(ctx, ids) {
indexBlobs := b.Blobs
err := reuploadBlobsFromPack(ctx, repo, b.PackID, indexBlobs, printer, uploader)
if err != nil {
return err
}
indexBlobs.Sort()
packBlobs := packToBlobs[b.PackID]
packBlobs.Sort()
if packBlobs != nil && !slices.Equal(indexBlobs, packBlobs) {
// handle case where the index entry is broken or incomplete.
// this can result in duplicate blobs, which can be cleaned up by running prune.
printer.E("repairing incomplete index entry for pack %v", b.PackID)
err := reuploadBlobsFromPack(ctx, repo, b.PackID, packBlobs, printer, uploader)
if err != nil {
return err
}
}
if len(indexBlobs) == 0 && len(packBlobs) == 0 {
printer.E("no blobs found for pack %v", b.PackID)
}
bar.Add(1)
}
return nil
})
if err != nil {
return err
}
bar.Done()
// remove salvaged packs from index
err = rewriteIndexFiles(ctx, repo, ids, nil, nil, printer)
if err != nil {
return err
}
// cleanup
printer.P("removing salvaged pack files")
// if we fail to delete the damaged pack files, then prune will remove them later on
bar = printer.NewCounter("files deleted")
_ = restic.ParallelRemove(ctx, &internalRepository{repo}, ids, restic.PackFile, nil, bar)
bar.Done()
return nil
}
func resolveBlobsForPacks(ctx context.Context, repo *Repository, ids restic.IDSet) (map[restic.ID]pack.Blobs, error) {
packToBlobs := make(map[restic.ID]pack.Blobs)
err := repo.List(ctx, restic.PackFile, func(id restic.ID, size int64) error {
if ids.Has(id) {
blobs, err := repo.listPack(ctx, id, size)
if err != nil {
return nil
}
packToBlobs[id] = blobs
}
return nil
})
if err != nil {
return nil, err
}
return packToBlobs, nil
}
func reuploadBlobsFromPack(ctx context.Context, repo *Repository, packID restic.ID, blobs pack.Blobs, printer progress.Printer, uploader restic.BlobSaverWithAsync) error {
err := repo.loadBlobsFromPack(ctx, packID, blobs, func(blob restic.BlobHandle, buf []byte, err error) error {
if err != nil {
printer.E("failed to load blob %v: %v", blob.ID, err)
return nil
}
id, _, _, err := uploader.SaveBlob(ctx, blob.Type, buf, restic.ID{}, true)
if err == nil && !id.Equal(blob.ID) {
panic("pack id mismatch during upload")
}
return err
})
// ignore truncated file parts
if err != nil && !errors.Is(err, io.ErrUnexpectedEOF) {
return err
}
return nil
}