2024-01-20 20:54:47 +00:00
|
|
|
package repository_test
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"math/rand"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/restic/restic/internal/backend"
|
|
|
|
"github.com/restic/restic/internal/index"
|
|
|
|
"github.com/restic/restic/internal/repository"
|
|
|
|
"github.com/restic/restic/internal/restic"
|
|
|
|
"github.com/restic/restic/internal/test"
|
|
|
|
rtest "github.com/restic/restic/internal/test"
|
|
|
|
"github.com/restic/restic/internal/ui/progress"
|
|
|
|
)
|
|
|
|
|
|
|
|
func listBlobs(repo restic.Repository) restic.BlobSet {
|
|
|
|
blobs := restic.NewBlobSet()
|
|
|
|
repo.Index().Each(context.TODO(), func(pb restic.PackedBlob) {
|
|
|
|
blobs.Insert(pb.BlobHandle)
|
|
|
|
})
|
|
|
|
return blobs
|
|
|
|
}
|
|
|
|
|
|
|
|
func replaceFile(t *testing.T, repo restic.Repository, h backend.Handle, damage func([]byte) []byte) {
|
|
|
|
buf, err := backend.LoadAll(context.TODO(), nil, repo.Backend(), h)
|
|
|
|
test.OK(t, err)
|
|
|
|
buf = damage(buf)
|
|
|
|
test.OK(t, repo.Backend().Remove(context.TODO(), h))
|
|
|
|
test.OK(t, repo.Backend().Save(context.TODO(), h, backend.NewByteReader(buf, repo.Backend().Hasher())))
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRepairBrokenPack(t *testing.T) {
|
|
|
|
repository.TestAllVersions(t, testRepairBrokenPack)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testRepairBrokenPack(t *testing.T, version uint) {
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
2024-02-03 16:35:46 +00:00
|
|
|
damage func(t *testing.T, repo restic.Repository, packsBefore restic.IDSet) (restic.IDSet, restic.BlobSet)
|
2024-01-20 20:54:47 +00:00
|
|
|
}{
|
|
|
|
{
|
|
|
|
"valid pack",
|
2024-02-03 16:35:46 +00:00
|
|
|
func(t *testing.T, repo restic.Repository, packsBefore restic.IDSet) (restic.IDSet, restic.BlobSet) {
|
2024-01-20 20:54:47 +00:00
|
|
|
return packsBefore, restic.NewBlobSet()
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"broken pack",
|
2024-02-03 16:35:46 +00:00
|
|
|
func(t *testing.T, repo restic.Repository, packsBefore restic.IDSet) (restic.IDSet, restic.BlobSet) {
|
2024-01-20 20:54:47 +00:00
|
|
|
wrongBlob := createRandomWrongBlob(t, repo)
|
|
|
|
damagedPacks := findPacksForBlobs(t, repo, restic.NewBlobSet(wrongBlob))
|
|
|
|
return damagedPacks, restic.NewBlobSet(wrongBlob)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"partially broken pack",
|
2024-02-03 16:35:46 +00:00
|
|
|
func(t *testing.T, repo restic.Repository, packsBefore restic.IDSet) (restic.IDSet, restic.BlobSet) {
|
2024-01-20 20:54:47 +00:00
|
|
|
// damage one of the pack files
|
|
|
|
damagedID := packsBefore.List()[0]
|
|
|
|
replaceFile(t, repo, backend.Handle{Type: backend.PackFile, Name: damagedID.String()},
|
|
|
|
func(buf []byte) []byte {
|
|
|
|
buf[0] ^= 0xff
|
|
|
|
return buf
|
|
|
|
})
|
|
|
|
|
|
|
|
// find blob that starts at offset 0
|
|
|
|
var damagedBlob restic.BlobHandle
|
|
|
|
for blobs := range repo.Index().ListPacks(context.TODO(), restic.NewIDSet(damagedID)) {
|
|
|
|
for _, blob := range blobs.Blobs {
|
|
|
|
if blob.Offset == 0 {
|
|
|
|
damagedBlob = blob.BlobHandle
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return restic.NewIDSet(damagedID), restic.NewBlobSet(damagedBlob)
|
|
|
|
},
|
|
|
|
}, {
|
|
|
|
"truncated pack",
|
2024-02-03 16:35:46 +00:00
|
|
|
func(t *testing.T, repo restic.Repository, packsBefore restic.IDSet) (restic.IDSet, restic.BlobSet) {
|
2024-01-20 20:54:47 +00:00
|
|
|
// damage one of the pack files
|
|
|
|
damagedID := packsBefore.List()[0]
|
|
|
|
replaceFile(t, repo, backend.Handle{Type: backend.PackFile, Name: damagedID.String()},
|
|
|
|
func(buf []byte) []byte {
|
|
|
|
buf = buf[0:10]
|
|
|
|
return buf
|
|
|
|
})
|
|
|
|
|
|
|
|
// all blobs in the file are broken
|
|
|
|
damagedBlobs := restic.NewBlobSet()
|
|
|
|
for blobs := range repo.Index().ListPacks(context.TODO(), restic.NewIDSet(damagedID)) {
|
|
|
|
for _, blob := range blobs.Blobs {
|
|
|
|
damagedBlobs.Insert(blob.BlobHandle)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return restic.NewIDSet(damagedID), damagedBlobs
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, test := range tests {
|
|
|
|
t.Run(test.name, func(t *testing.T) {
|
2024-02-03 16:47:48 +00:00
|
|
|
// disable verification to allow adding corrupted blobs to the repository
|
2024-02-04 15:50:50 +00:00
|
|
|
repo := repository.TestRepositoryWithBackend(t, nil, version, repository.Options{NoExtraVerify: true})
|
2024-01-20 20:54:47 +00:00
|
|
|
|
|
|
|
seed := time.Now().UnixNano()
|
|
|
|
rand.Seed(seed)
|
|
|
|
t.Logf("rand seed is %v", seed)
|
|
|
|
|
|
|
|
createRandomBlobs(t, repo, 5, 0.7)
|
|
|
|
packsBefore := listPacks(t, repo)
|
|
|
|
blobsBefore := listBlobs(repo)
|
|
|
|
|
2024-02-03 16:35:46 +00:00
|
|
|
toRepair, damagedBlobs := test.damage(t, repo, packsBefore)
|
2024-01-20 20:54:47 +00:00
|
|
|
|
|
|
|
rtest.OK(t, repository.RepairPacks(context.TODO(), repo, toRepair, &progress.NoopPrinter{}))
|
|
|
|
// reload index
|
|
|
|
rtest.OK(t, repo.SetIndex(index.NewMasterIndex()))
|
2024-01-20 20:58:28 +00:00
|
|
|
rtest.OK(t, repo.LoadIndex(context.TODO(), nil))
|
2024-01-20 20:54:47 +00:00
|
|
|
|
|
|
|
packsAfter := listPacks(t, repo)
|
|
|
|
blobsAfter := listBlobs(repo)
|
|
|
|
|
|
|
|
rtest.Assert(t, len(packsAfter.Intersect(toRepair)) == 0, "some damaged packs were not removed")
|
|
|
|
rtest.Assert(t, len(packsBefore.Sub(toRepair).Sub(packsAfter)) == 0, "not-damaged packs were removed")
|
|
|
|
rtest.Assert(t, blobsBefore.Sub(damagedBlobs).Equals(blobsAfter), "diverging blob lists")
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|