mirror of
https://github.com/kopia/kopia.git
synced 2026-01-25 06:48:48 -05:00
- `repo.Repository` is now read-only and only has methods that can be supported over kopia server
- `repo.RepositoryWriter` has read-write methods that can be supported over kopia server
- `repo.DirectRepository` is read-only and contains all methods of `repo.Repository` plus some low-level methods for data inspection
- `repo.DirectRepositoryWriter` contains write methods for `repo.DirectRepository`
- `repo.Reader` removed and merged with `repo.Repository`
- `repo.Writer` became `repo.RepositoryWriter`
- `*repo.DirectRepository` struct became `repo.DirectRepository`
interface
Getting `{Direct}RepositoryWriter` requires using `NewWriter()` or `NewDirectWriter()` on a read-only repository and multiple simultaneous writers are supported at the same time, each writing to their own indexes and pack blobs.
`repo.Open` returns `repo.Repository` (which is also `repo.RepositoryWriter`).
* content: removed implicit flush on content manager close
* repo: added tests for WriteSession() and implicit flush behavior
* invalidate manifest manager after write session
* cli: disable maintenance in 'kopia server start'
Server will close the repository before completing.
* repo: unconditionally close RepositoryWriter in {Direct,}WriteSession
* repo: added panic in case somebody tries to create RepositoryWriter after closing repository
- used atomic to manage SharedManager.closed
* removed stale example
* linter: fixed spurious failures
Co-authored-by: Julio López <julio+gh@kasten.io>
114 lines
3.0 KiB
Go
114 lines
3.0 KiB
Go
package cli
|
|
|
|
import (
|
|
"context"
|
|
"sync/atomic"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/kopia/kopia/repo"
|
|
"github.com/kopia/kopia/repo/blob"
|
|
"github.com/kopia/kopia/repo/content"
|
|
)
|
|
|
|
var (
|
|
contentVerifyCommand = contentCommands.Command("verify", "Verify that each content is backed by a valid blob")
|
|
|
|
contentVerifyParallel = contentVerifyCommand.Flag("parallel", "Parallelism").Default("16").Int()
|
|
contentVerifyFull = contentVerifyCommand.Flag("full", "Full verification (including download)").Bool()
|
|
contentVerifyIncludeDeleted = contentVerifyCommand.Flag("include-deleted", "Include deleted contents").Bool()
|
|
)
|
|
|
|
func readBlobMap(ctx context.Context, br blob.Reader) (map[blob.ID]blob.Metadata, error) {
|
|
blobMap := map[blob.ID]blob.Metadata{}
|
|
|
|
log(ctx).Infof("Listing blobs...")
|
|
|
|
if err := br.ListBlobs(ctx, "", func(bm blob.Metadata) error {
|
|
blobMap[bm.BlobID] = bm
|
|
if len(blobMap)%10000 == 0 {
|
|
log(ctx).Infof(" %v blobs...", len(blobMap))
|
|
}
|
|
return nil
|
|
}); err != nil {
|
|
return nil, errors.Wrap(err, "unable to list blobs")
|
|
}
|
|
|
|
log(ctx).Infof("Listed %v blobs.", len(blobMap))
|
|
|
|
return blobMap, nil
|
|
}
|
|
|
|
func runContentVerifyCommand(ctx context.Context, rep repo.DirectRepository) error {
|
|
blobMap := map[blob.ID]blob.Metadata{}
|
|
|
|
if !*contentVerifyFull {
|
|
m, err := readBlobMap(ctx, rep.BlobReader())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
blobMap = m
|
|
}
|
|
|
|
var totalCount, successCount, errorCount int32
|
|
|
|
log(ctx).Infof("Verifying all contents...")
|
|
|
|
err := rep.ContentReader().IterateContents(ctx, content.IterateOptions{
|
|
Range: contentIDRange(),
|
|
Parallel: *contentVerifyParallel,
|
|
IncludeDeleted: *contentVerifyIncludeDeleted,
|
|
}, func(ci content.Info) error {
|
|
if err := contentVerify(ctx, rep.ContentReader(), &ci, blobMap); err != nil {
|
|
log(ctx).Errorf("error %v", err)
|
|
atomic.AddInt32(&errorCount, 1)
|
|
} else {
|
|
atomic.AddInt32(&successCount, 1)
|
|
}
|
|
|
|
if t := atomic.AddInt32(&totalCount, 1); t%100000 == 0 {
|
|
log(ctx).Infof(" %v contents, %v errors...", t, atomic.LoadInt32(&errorCount))
|
|
}
|
|
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return errors.Wrap(err, "iterate contents")
|
|
}
|
|
|
|
log(ctx).Infof("Finished verifying %v contents, found %v errors.", totalCount, errorCount)
|
|
|
|
if errorCount == 0 {
|
|
return nil
|
|
}
|
|
|
|
return errors.Errorf("encountered %v errors", errorCount)
|
|
}
|
|
|
|
func contentVerify(ctx context.Context, r content.Reader, ci *content.Info, blobMap map[blob.ID]blob.Metadata) error {
|
|
if *contentVerifyFull {
|
|
if _, err := r.GetContent(ctx, ci.ID); err != nil {
|
|
return errors.Wrapf(err, "content %v is invalid", ci.ID)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
bi, ok := blobMap[ci.PackBlobID]
|
|
if !ok {
|
|
return errors.Errorf("content %v depends on missing blob %v", ci.ID, ci.PackBlobID)
|
|
}
|
|
|
|
if int64(ci.PackOffset+ci.Length) > bi.Length {
|
|
return errors.Errorf("content %v out of bounds of its pack blob %v", ci.ID, ci.PackBlobID)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func init() {
|
|
contentVerifyCommand.Action(directRepositoryReadAction(runContentVerifyCommand))
|
|
setupContentIDRangeFlags(contentVerifyCommand)
|
|
}
|