mirror of
https://github.com/restic/restic.git
synced 2026-06-21 16:14:18 +00:00
113 lines
3.2 KiB
Go
113 lines
3.2 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 {
|
|
// ignore errors for broken pack files to be able to salvage as much as possible
|
|
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
|
|
}
|