mirror of
https://github.com/kopia/kopia.git
synced 2026-09-13 05:37:46 -04:00
The --parallel flag was declared with kingpin .IntVar on 12 commands, which accepted negative values and led to a panic at runtime (e.g. `snapshot migrate --parallel=-1`). Declare the --parallel family as uint so kingpin rejects negative input at parse time. The uint->int conversion the callee APIs need is done through a small helper . Add CLI test that asserts the rejection message (a bare failure check passed even without the fix since the commands also fail with no repository connected). Parse via ParseFloat like kingpin's built-in int flag so the only behavior change is rejecting negatives.
127 lines
3.4 KiB
Go
127 lines
3.4 KiB
Go
package cli
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/kopia/kopia/internal/timetrack"
|
|
"github.com/kopia/kopia/repo"
|
|
"github.com/kopia/kopia/repo/content"
|
|
)
|
|
|
|
type commandContentVerify struct {
|
|
contentVerifyParallel uint
|
|
contentVerifyFull bool
|
|
contentVerifyIncludeDeleted bool
|
|
contentVerifyPercent float64
|
|
progressInterval time.Duration
|
|
|
|
contentRange contentRangeFlags
|
|
}
|
|
|
|
func (c *commandContentVerify) setup(svc appServices, parent commandParent) {
|
|
cmd := parent.Command("verify", "Verify that each content is backed by a valid blob")
|
|
|
|
cmd.Flag("parallel", "Parallelism").Default("16").UintVar(&c.contentVerifyParallel)
|
|
cmd.Flag("full", "Full verification (including download)").BoolVar(&c.contentVerifyFull)
|
|
cmd.Flag("include-deleted", "Include deleted contents").BoolVar(&c.contentVerifyIncludeDeleted)
|
|
cmd.Flag("download-percent", "Download a percentage of files [0.0 .. 100.0]").Float64Var(&c.contentVerifyPercent)
|
|
cmd.Flag("progress-interval", "Progress output interval").Default("3s").DurationVar(&c.progressInterval)
|
|
c.contentRange.setup(cmd)
|
|
cmd.Action(svc.directRepositoryReadAction(c.run))
|
|
}
|
|
|
|
func (c *commandContentVerify) run(ctx context.Context, rep repo.DirectRepository) error {
|
|
var (
|
|
totalCount atomic.Int32
|
|
|
|
wg sync.WaitGroup
|
|
)
|
|
|
|
subctx, cancel := context.WithCancel(ctx)
|
|
|
|
// ensure we cancel estimation goroutine and wait for it before returning
|
|
defer func() {
|
|
cancel()
|
|
wg.Wait()
|
|
}()
|
|
|
|
// start a goroutine that will populate totalCount
|
|
|
|
wg.Go(func() {
|
|
c.getTotalContentCount(subctx, rep, &totalCount)
|
|
})
|
|
|
|
rep.DisableIndexRefresh()
|
|
|
|
var throttle timetrack.Throttle
|
|
|
|
est := timetrack.Start()
|
|
|
|
if c.contentVerifyFull {
|
|
c.contentVerifyPercent = 100.0
|
|
}
|
|
|
|
opts := content.VerifyOptions{
|
|
ContentIDRange: c.contentRange.contentIDRange(),
|
|
ContentReadPercentage: c.contentVerifyPercent,
|
|
IncludeDeletedContents: c.contentVerifyIncludeDeleted,
|
|
ContentIterateParallelism: parallelismAsInt(c.contentVerifyParallel),
|
|
ProgressCallbackInterval: 1,
|
|
|
|
ProgressCallback: func(vps content.VerifyProgressStats) {
|
|
if !throttle.ShouldOutput(c.progressInterval) {
|
|
return
|
|
}
|
|
|
|
verifiedCount := vps.SuccessCount + vps.ErrorCount
|
|
|
|
timings, ok := est.Estimate(float64(verifiedCount), float64(totalCount.Load()))
|
|
if ok {
|
|
log(ctx).Infof(" Verified %v of %v contents (%.1f%%), %v errors, remaining %v, ETA %v",
|
|
verifiedCount,
|
|
totalCount.Load(),
|
|
timings.PercentComplete,
|
|
vps.ErrorCount,
|
|
timings.Remaining,
|
|
formatTimestamp(timings.EstimatedEndTime),
|
|
)
|
|
} else {
|
|
log(ctx).Infof(" Verified %v contents, %v errors, estimating...", verifiedCount, vps.ErrorCount)
|
|
}
|
|
},
|
|
}
|
|
|
|
if err := rep.ContentReader().VerifyContents(ctx, opts); err != nil {
|
|
return errors.Wrap(err, "verify contents")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *commandContentVerify) getTotalContentCount(ctx context.Context, rep repo.DirectRepository, totalCount *atomic.Int32) {
|
|
var tc int32
|
|
|
|
if err := rep.ContentReader().IterateContents(ctx, content.IterateOptions{
|
|
Range: c.contentRange.contentIDRange(),
|
|
IncludeDeleted: c.contentVerifyIncludeDeleted,
|
|
}, func(_ content.Info) error {
|
|
if err := ctx.Err(); err != nil {
|
|
return errors.Wrap(err, "context error")
|
|
}
|
|
|
|
tc++
|
|
|
|
return nil
|
|
}); err != nil {
|
|
log(ctx).Debugf("error estimating content count: %v", err)
|
|
return
|
|
}
|
|
|
|
totalCount.Store(tc)
|
|
}
|