mirror of
https://github.com/restic/restic.git
synced 2025-12-11 18:47:50 +00:00
debug: convert to termstatus
This commit is contained in:
@@ -27,6 +27,8 @@ import (
|
||||
"github.com/restic/restic/internal/repository/index"
|
||||
"github.com/restic/restic/internal/repository/pack"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
"github.com/restic/restic/internal/ui/termstatus"
|
||||
)
|
||||
|
||||
func registerDebugCommand(cmd *cobra.Command) {
|
||||
@@ -66,7 +68,9 @@ Exit status is 12 if the password is incorrect.
|
||||
`,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runDebugDump(cmd.Context(), globalOptions, args)
|
||||
term, cancel := setupTermstatus()
|
||||
defer cancel()
|
||||
return runDebugDump(cmd.Context(), globalOptions, args, term)
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
@@ -80,7 +84,9 @@ func newDebugExamineCommand() *cobra.Command {
|
||||
Short: "Examine a pack file",
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runDebugExamine(cmd.Context(), globalOptions, opts, args)
|
||||
term, cancel := setupTermstatus()
|
||||
defer cancel()
|
||||
return runDebugExamine(cmd.Context(), globalOptions, opts, args, term)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -141,13 +147,13 @@ type Blob struct {
|
||||
Offset uint `json:"offset"`
|
||||
}
|
||||
|
||||
func printPacks(ctx context.Context, repo *repository.Repository, wr io.Writer) error {
|
||||
func printPacks(ctx context.Context, repo *repository.Repository, wr io.Writer, printer progress.Printer) error {
|
||||
|
||||
var m sync.Mutex
|
||||
return restic.ParallelList(ctx, repo, restic.PackFile, repo.Connections(), func(ctx context.Context, id restic.ID, size int64) error {
|
||||
blobs, _, err := repo.ListPack(ctx, id, size)
|
||||
if err != nil {
|
||||
Warnf("error for pack %v: %v\n", id.Str(), err)
|
||||
printer.E("error for pack %v: %v", id.Str(), err)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -170,9 +176,9 @@ func printPacks(ctx context.Context, repo *repository.Repository, wr io.Writer)
|
||||
})
|
||||
}
|
||||
|
||||
func dumpIndexes(ctx context.Context, repo restic.ListerLoaderUnpacked, wr io.Writer) error {
|
||||
func dumpIndexes(ctx context.Context, repo restic.ListerLoaderUnpacked, wr io.Writer, printer progress.Printer) error {
|
||||
return index.ForAllIndexes(ctx, repo, repo, func(id restic.ID, idx *index.Index, err error) error {
|
||||
Printf("index_id: %v\n", id)
|
||||
printer.S("index_id: %v", id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -181,7 +187,9 @@ func dumpIndexes(ctx context.Context, repo restic.ListerLoaderUnpacked, wr io.Wr
|
||||
})
|
||||
}
|
||||
|
||||
func runDebugDump(ctx context.Context, gopts GlobalOptions, args []string) error {
|
||||
func runDebugDump(ctx context.Context, gopts GlobalOptions, args []string, term *termstatus.Terminal) error {
|
||||
printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
||||
|
||||
if len(args) != 1 {
|
||||
return errors.Fatal("type not specified")
|
||||
}
|
||||
@@ -196,20 +204,20 @@ func runDebugDump(ctx context.Context, gopts GlobalOptions, args []string) error
|
||||
|
||||
switch tpe {
|
||||
case "indexes":
|
||||
return dumpIndexes(ctx, repo, globalOptions.stdout)
|
||||
return dumpIndexes(ctx, repo, globalOptions.stdout, printer)
|
||||
case "snapshots":
|
||||
return debugPrintSnapshots(ctx, repo, globalOptions.stdout)
|
||||
case "packs":
|
||||
return printPacks(ctx, repo, globalOptions.stdout)
|
||||
return printPacks(ctx, repo, globalOptions.stdout, printer)
|
||||
case "all":
|
||||
Printf("snapshots:\n")
|
||||
printer.S("snapshots:")
|
||||
err := debugPrintSnapshots(ctx, repo, globalOptions.stdout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Printf("\nindexes:\n")
|
||||
err = dumpIndexes(ctx, repo, globalOptions.stdout)
|
||||
printer.S("indexes:")
|
||||
err = dumpIndexes(ctx, repo, globalOptions.stdout, printer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -220,11 +228,11 @@ func runDebugDump(ctx context.Context, gopts GlobalOptions, args []string) error
|
||||
}
|
||||
}
|
||||
|
||||
func tryRepairWithBitflip(key *crypto.Key, input []byte, bytewise bool) []byte {
|
||||
func tryRepairWithBitflip(key *crypto.Key, input []byte, bytewise bool, printer progress.Printer) []byte {
|
||||
if bytewise {
|
||||
Printf(" trying to repair blob by finding a broken byte\n")
|
||||
printer.S(" trying to repair blob by finding a broken byte")
|
||||
} else {
|
||||
Printf(" trying to repair blob with single bit flip\n")
|
||||
printer.S(" trying to repair blob with single bit flip")
|
||||
}
|
||||
|
||||
ch := make(chan int)
|
||||
@@ -234,7 +242,7 @@ func tryRepairWithBitflip(key *crypto.Key, input []byte, bytewise bool) []byte {
|
||||
var found bool
|
||||
|
||||
workers := runtime.GOMAXPROCS(0)
|
||||
Printf(" spinning up %d worker functions\n", runtime.GOMAXPROCS(0))
|
||||
printer.S(" spinning up %d worker functions", runtime.GOMAXPROCS(0))
|
||||
for i := 0; i < workers; i++ {
|
||||
wg.Go(func() error {
|
||||
// make a local copy of the buffer
|
||||
@@ -248,9 +256,9 @@ func tryRepairWithBitflip(key *crypto.Key, input []byte, bytewise bool) []byte {
|
||||
nonce, plaintext := buf[:key.NonceSize()], buf[key.NonceSize():]
|
||||
plaintext, err := key.Open(plaintext[:0], nonce, plaintext, nil)
|
||||
if err == nil {
|
||||
Printf("\n")
|
||||
Printf(" blob could be repaired by XORing byte %v with 0x%02x\n", idx, pattern)
|
||||
Printf(" hash is %v\n", restic.Hash(plaintext))
|
||||
printer.S("")
|
||||
printer.S(" blob could be repaired by XORing byte %v with 0x%02x", idx, pattern)
|
||||
printer.S(" hash is %v", restic.Hash(plaintext))
|
||||
close(done)
|
||||
found = true
|
||||
fixed = plaintext
|
||||
@@ -291,7 +299,7 @@ func tryRepairWithBitflip(key *crypto.Key, input []byte, bytewise bool) []byte {
|
||||
select {
|
||||
case ch <- i:
|
||||
case <-done:
|
||||
Printf(" done after %v\n", time.Since(start))
|
||||
printer.S(" done after %v", time.Since(start))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -301,7 +309,7 @@ func tryRepairWithBitflip(key *crypto.Key, input []byte, bytewise bool) []byte {
|
||||
remaining := len(input) - i
|
||||
eta := time.Duration(float64(remaining)/gps) * time.Second
|
||||
|
||||
Printf("\r%d byte of %d done (%.2f%%), %.0f byte per second, ETA %v",
|
||||
printer.S("\r%d byte of %d done (%.2f%%), %.0f byte per second, ETA %v",
|
||||
i, len(input), float32(i)/float32(len(input))*100, gps, eta)
|
||||
info = time.Now()
|
||||
}
|
||||
@@ -314,7 +322,7 @@ func tryRepairWithBitflip(key *crypto.Key, input []byte, bytewise bool) []byte {
|
||||
}
|
||||
|
||||
if !found {
|
||||
Printf("\n blob could not be repaired\n")
|
||||
printer.S("\n blob could not be repaired")
|
||||
}
|
||||
return fixed
|
||||
}
|
||||
@@ -335,7 +343,7 @@ func decryptUnsigned(k *crypto.Key, buf []byte) []byte {
|
||||
return out
|
||||
}
|
||||
|
||||
func loadBlobs(ctx context.Context, opts DebugExamineOptions, repo restic.Repository, packID restic.ID, list []restic.Blob) error {
|
||||
func loadBlobs(ctx context.Context, opts DebugExamineOptions, repo restic.Repository, packID restic.ID, list []restic.Blob, printer progress.Printer) error {
|
||||
dec, err := zstd.NewReader(nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -355,9 +363,9 @@ func loadBlobs(ctx context.Context, opts DebugExamineOptions, repo restic.Reposi
|
||||
|
||||
wg.Go(func() error {
|
||||
for _, blob := range list {
|
||||
Printf(" loading blob %v at %v (length %v)\n", blob.ID, blob.Offset, blob.Length)
|
||||
printer.S(" loading blob %v at %v (length %v)", blob.ID, blob.Offset, blob.Length)
|
||||
if int(blob.Offset+blob.Length) > len(pack) {
|
||||
Warnf("skipping truncated blob\n")
|
||||
printer.E("skipping truncated blob")
|
||||
continue
|
||||
}
|
||||
buf := pack[blob.Offset : blob.Offset+blob.Length]
|
||||
@@ -368,16 +376,16 @@ func loadBlobs(ctx context.Context, opts DebugExamineOptions, repo restic.Reposi
|
||||
outputPrefix := ""
|
||||
filePrefix := ""
|
||||
if err != nil {
|
||||
Warnf("error decrypting blob: %v\n", err)
|
||||
printer.E("error decrypting blob: %v", err)
|
||||
if opts.TryRepair || opts.RepairByte {
|
||||
plaintext = tryRepairWithBitflip(key, buf, opts.RepairByte)
|
||||
plaintext = tryRepairWithBitflip(key, buf, opts.RepairByte, printer)
|
||||
}
|
||||
if plaintext != nil {
|
||||
outputPrefix = "repaired "
|
||||
filePrefix = "repaired-"
|
||||
} else {
|
||||
plaintext = decryptUnsigned(key, buf)
|
||||
err = storePlainBlob(blob.ID, "damaged-", plaintext)
|
||||
err = storePlainBlob(blob.ID, "damaged-", plaintext, printer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -388,7 +396,7 @@ func loadBlobs(ctx context.Context, opts DebugExamineOptions, repo restic.Reposi
|
||||
if blob.IsCompressed() {
|
||||
decompressed, err := dec.DecodeAll(plaintext, nil)
|
||||
if err != nil {
|
||||
Printf(" failed to decompress blob %v\n", blob.ID)
|
||||
printer.S(" failed to decompress blob %v", blob.ID)
|
||||
}
|
||||
if decompressed != nil {
|
||||
plaintext = decompressed
|
||||
@@ -398,14 +406,14 @@ func loadBlobs(ctx context.Context, opts DebugExamineOptions, repo restic.Reposi
|
||||
id := restic.Hash(plaintext)
|
||||
var prefix string
|
||||
if !id.Equal(blob.ID) {
|
||||
Printf(" successfully %vdecrypted blob (length %v), hash is %v, ID does not match, wanted %v\n", outputPrefix, len(plaintext), id, blob.ID)
|
||||
printer.S(" successfully %vdecrypted blob (length %v), hash is %v, ID does not match, wanted %v", outputPrefix, len(plaintext), id, blob.ID)
|
||||
prefix = "wrong-hash-"
|
||||
} else {
|
||||
Printf(" successfully %vdecrypted blob (length %v), hash is %v, ID matches\n", outputPrefix, len(plaintext), id)
|
||||
printer.S(" successfully %vdecrypted blob (length %v), hash is %v, ID matches", outputPrefix, len(plaintext), id)
|
||||
prefix = "correct-"
|
||||
}
|
||||
if opts.ExtractPack {
|
||||
err = storePlainBlob(id, filePrefix+prefix, plaintext)
|
||||
err = storePlainBlob(id, filePrefix+prefix, plaintext, printer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -415,7 +423,7 @@ func loadBlobs(ctx context.Context, opts DebugExamineOptions, repo restic.Reposi
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
Printf(" uploaded %v %v\n", blob.Type, id)
|
||||
printer.S(" uploaded %v %v", blob.Type, id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -428,7 +436,7 @@ func loadBlobs(ctx context.Context, opts DebugExamineOptions, repo restic.Reposi
|
||||
return wg.Wait()
|
||||
}
|
||||
|
||||
func storePlainBlob(id restic.ID, prefix string, plain []byte) error {
|
||||
func storePlainBlob(id restic.ID, prefix string, plain []byte, printer progress.Printer) error {
|
||||
filename := fmt.Sprintf("%s%s.bin", prefix, id)
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
@@ -446,11 +454,13 @@ func storePlainBlob(id restic.ID, prefix string, plain []byte) error {
|
||||
return err
|
||||
}
|
||||
|
||||
Printf("decrypt of blob %v stored at %v\n", id, filename)
|
||||
printer.S("decrypt of blob %v stored at %v", id, filename)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runDebugExamine(ctx context.Context, gopts GlobalOptions, opts DebugExamineOptions, args []string) error {
|
||||
func runDebugExamine(ctx context.Context, gopts GlobalOptions, opts DebugExamineOptions, args []string, term *termstatus.Terminal) error {
|
||||
printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
||||
|
||||
if opts.ExtractPack && gopts.NoLock {
|
||||
return fmt.Errorf("--extract-pack and --no-lock are mutually exclusive")
|
||||
}
|
||||
@@ -467,7 +477,7 @@ func runDebugExamine(ctx context.Context, gopts GlobalOptions, opts DebugExamine
|
||||
if err != nil {
|
||||
id, err = restic.Find(ctx, repo, restic.PackFile, name)
|
||||
if err != nil {
|
||||
Warnf("error: %v\n", err)
|
||||
printer.E("error: %v", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -478,16 +488,16 @@ func runDebugExamine(ctx context.Context, gopts GlobalOptions, opts DebugExamine
|
||||
return errors.Fatal("no pack files to examine")
|
||||
}
|
||||
|
||||
bar := newIndexProgress(gopts.Quiet, gopts.JSON)
|
||||
bar := newIndexTerminalProgress(printer)
|
||||
err = repo.LoadIndex(ctx, bar)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, id := range ids {
|
||||
err := examinePack(ctx, opts, repo, id)
|
||||
err := examinePack(ctx, opts, repo, id, printer)
|
||||
if err != nil {
|
||||
Warnf("error: %v\n", err)
|
||||
printer.E("error: %v", err)
|
||||
}
|
||||
if err == context.Canceled {
|
||||
break
|
||||
@@ -496,24 +506,24 @@ func runDebugExamine(ctx context.Context, gopts GlobalOptions, opts DebugExamine
|
||||
return nil
|
||||
}
|
||||
|
||||
func examinePack(ctx context.Context, opts DebugExamineOptions, repo restic.Repository, id restic.ID) error {
|
||||
Printf("examine %v\n", id)
|
||||
func examinePack(ctx context.Context, opts DebugExamineOptions, repo restic.Repository, id restic.ID, printer progress.Printer) error {
|
||||
printer.S("examine %v", id)
|
||||
|
||||
buf, err := repo.LoadRaw(ctx, restic.PackFile, id)
|
||||
// also process damaged pack files
|
||||
if buf == nil {
|
||||
return err
|
||||
}
|
||||
Printf(" file size is %v\n", len(buf))
|
||||
printer.S(" file size is %v", len(buf))
|
||||
gotID := restic.Hash(buf)
|
||||
if !id.Equal(gotID) {
|
||||
Printf(" wanted hash %v, got %v\n", id, gotID)
|
||||
printer.S(" wanted hash %v, got %v", id, gotID)
|
||||
} else {
|
||||
Printf(" hash for file content matches\n")
|
||||
printer.S(" hash for file content matches")
|
||||
}
|
||||
|
||||
Printf(" ========================================\n")
|
||||
Printf(" looking for info in the indexes\n")
|
||||
printer.S(" ========================================")
|
||||
printer.S(" looking for info in the indexes")
|
||||
|
||||
blobsLoaded := false
|
||||
// examine all data the indexes have for the pack file
|
||||
@@ -523,32 +533,32 @@ func examinePack(ctx context.Context, opts DebugExamineOptions, repo restic.Repo
|
||||
continue
|
||||
}
|
||||
|
||||
checkPackSize(blobs, len(buf))
|
||||
checkPackSize(blobs, len(buf), printer)
|
||||
|
||||
err = loadBlobs(ctx, opts, repo, id, blobs)
|
||||
err = loadBlobs(ctx, opts, repo, id, blobs, printer)
|
||||
if err != nil {
|
||||
Warnf("error: %v\n", err)
|
||||
printer.E("error: %v", err)
|
||||
} else {
|
||||
blobsLoaded = true
|
||||
}
|
||||
}
|
||||
|
||||
Printf(" ========================================\n")
|
||||
Printf(" inspect the pack itself\n")
|
||||
printer.S(" ========================================")
|
||||
printer.S(" inspect the pack itself")
|
||||
|
||||
blobs, _, err := repo.ListPack(ctx, id, int64(len(buf)))
|
||||
if err != nil {
|
||||
return fmt.Errorf("pack %v: %v", id.Str(), err)
|
||||
}
|
||||
checkPackSize(blobs, len(buf))
|
||||
checkPackSize(blobs, len(buf), printer)
|
||||
|
||||
if !blobsLoaded {
|
||||
return loadBlobs(ctx, opts, repo, id, blobs)
|
||||
return loadBlobs(ctx, opts, repo, id, blobs, printer)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkPackSize(blobs []restic.Blob, fileSize int) {
|
||||
func checkPackSize(blobs []restic.Blob, fileSize int, printer progress.Printer) {
|
||||
// track current size and offset
|
||||
var size, offset uint64
|
||||
|
||||
@@ -557,9 +567,9 @@ func checkPackSize(blobs []restic.Blob, fileSize int) {
|
||||
})
|
||||
|
||||
for _, pb := range blobs {
|
||||
Printf(" %v blob %v, offset %-6d, raw length %-6d\n", pb.Type, pb.ID, pb.Offset, pb.Length)
|
||||
printer.S(" %v blob %v, offset %-6d, raw length %-6d", pb.Type, pb.ID, pb.Offset, pb.Length)
|
||||
if offset != uint64(pb.Offset) {
|
||||
Printf(" hole in file, want offset %v, got %v\n", offset, pb.Offset)
|
||||
printer.S(" hole in file, want offset %v, got %v", offset, pb.Offset)
|
||||
}
|
||||
offset = uint64(pb.Offset + pb.Length)
|
||||
size += uint64(pb.Length)
|
||||
@@ -567,8 +577,8 @@ func checkPackSize(blobs []restic.Blob, fileSize int) {
|
||||
size += uint64(pack.CalculateHeaderSize(blobs))
|
||||
|
||||
if uint64(fileSize) != size {
|
||||
Printf(" file sizes do not match: computed %v, file size is %v\n", size, fileSize)
|
||||
printer.S(" file sizes do not match: computed %v, file size is %v", size, fileSize)
|
||||
} else {
|
||||
Printf(" file sizes match\n")
|
||||
printer.S(" file sizes match")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user