mirror of
https://github.com/octoleo/restic.git
synced 2024-05-30 07:30:50 +00:00
c55b6ee544
This is a new error which implements the restic.Fataler interface. Errors of this type are written to stderr, the restic exits. For all other errors, restic prints the stack trace (if available).
261 lines
4.7 KiB
Go
261 lines
4.7 KiB
Go
// +build debug
|
|
|
|
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
|
|
"restic"
|
|
"restic/backend"
|
|
"restic/pack"
|
|
"restic/repository"
|
|
|
|
"restic/worker"
|
|
)
|
|
|
|
type CmdDump struct {
|
|
global *GlobalOptions
|
|
|
|
repo *repository.Repository
|
|
}
|
|
|
|
func init() {
|
|
_, err := parser.AddCommand("dump",
|
|
"dump data structures",
|
|
"The dump command dumps data structures from a repository as JSON documents",
|
|
&CmdDump{global: &globalOpts})
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func (cmd CmdDump) Usage() string {
|
|
return "[indexes|snapshots|trees|all|packs]"
|
|
}
|
|
|
|
func prettyPrintJSON(wr io.Writer, item interface{}) error {
|
|
buf, err := json.MarshalIndent(item, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = wr.Write(append(buf, '\n'))
|
|
return err
|
|
}
|
|
|
|
func debugPrintSnapshots(repo *repository.Repository, wr io.Writer) error {
|
|
done := make(chan struct{})
|
|
defer close(done)
|
|
|
|
for id := range repo.List(backend.Snapshot, done) {
|
|
snapshot, err := restic.LoadSnapshot(repo, id)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "LoadSnapshot(%v): %v", id.Str(), err)
|
|
continue
|
|
}
|
|
|
|
fmt.Fprintf(wr, "snapshot_id: %v\n", id)
|
|
|
|
err = prettyPrintJSON(wr, snapshot)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func printTrees(repo *repository.Repository, wr io.Writer) error {
|
|
done := make(chan struct{})
|
|
defer close(done)
|
|
|
|
trees := []backend.ID{}
|
|
|
|
for _, idx := range repo.Index().All() {
|
|
for blob := range idx.Each(nil) {
|
|
if blob.Type != pack.Tree {
|
|
continue
|
|
}
|
|
|
|
trees = append(trees, blob.ID)
|
|
}
|
|
}
|
|
|
|
for _, id := range trees {
|
|
tree, err := restic.LoadTree(repo, id)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "LoadTree(%v): %v", id.Str(), err)
|
|
continue
|
|
}
|
|
|
|
fmt.Fprintf(wr, "tree_id: %v\n", id)
|
|
|
|
prettyPrintJSON(wr, tree)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
const dumpPackWorkers = 10
|
|
|
|
// Pack is the struct used in printPacks.
|
|
type Pack struct {
|
|
Name string `json:"name"`
|
|
|
|
Blobs []Blob `json:"blobs"`
|
|
}
|
|
|
|
// Blob is the struct used in printPacks.
|
|
type Blob struct {
|
|
Type pack.BlobType `json:"type"`
|
|
Length uint `json:"length"`
|
|
ID backend.ID `json:"id"`
|
|
Offset uint `json:"offset"`
|
|
}
|
|
|
|
func printPacks(repo *repository.Repository, wr io.Writer) error {
|
|
done := make(chan struct{})
|
|
defer close(done)
|
|
|
|
f := func(job worker.Job, done <-chan struct{}) (interface{}, error) {
|
|
name := job.Data.(string)
|
|
|
|
h := backend.Handle{Type: backend.Data, Name: name}
|
|
|
|
blobInfo, err := repo.Backend().Stat(h)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
blobs, err := pack.List(repo.Key(), backend.ReaderAt(repo.Backend(), h), blobInfo.Size)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return blobs, nil
|
|
}
|
|
|
|
jobCh := make(chan worker.Job)
|
|
resCh := make(chan worker.Job)
|
|
wp := worker.New(dumpPackWorkers, f, jobCh, resCh)
|
|
|
|
go func() {
|
|
for name := range repo.Backend().List(backend.Data, done) {
|
|
jobCh <- worker.Job{Data: name}
|
|
}
|
|
close(jobCh)
|
|
}()
|
|
|
|
for job := range resCh {
|
|
name := job.Data.(string)
|
|
|
|
if job.Error != nil {
|
|
fmt.Fprintf(os.Stderr, "error for pack %v: %v\n", name, job.Error)
|
|
continue
|
|
}
|
|
|
|
entries := job.Result.([]pack.Blob)
|
|
p := Pack{
|
|
Name: name,
|
|
Blobs: make([]Blob, len(entries)),
|
|
}
|
|
for i, blob := range entries {
|
|
p.Blobs[i] = Blob{
|
|
Type: blob.Type,
|
|
Length: blob.Length,
|
|
ID: blob.ID,
|
|
Offset: blob.Offset,
|
|
}
|
|
}
|
|
|
|
prettyPrintJSON(os.Stdout, p)
|
|
}
|
|
|
|
wp.Wait()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (cmd CmdDump) DumpIndexes() error {
|
|
done := make(chan struct{})
|
|
defer close(done)
|
|
|
|
for id := range cmd.repo.List(backend.Index, done) {
|
|
fmt.Printf("index_id: %v\n", id)
|
|
|
|
idx, err := repository.LoadIndex(cmd.repo, id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = idx.Dump(os.Stdout)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (cmd CmdDump) Execute(args []string) error {
|
|
if len(args) != 1 {
|
|
return restic.Fatalf("type not specified, Usage: %s", cmd.Usage())
|
|
}
|
|
|
|
repo, err := cmd.global.OpenRepository()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cmd.repo = repo
|
|
|
|
lock, err := lockRepo(repo)
|
|
defer unlockRepo(lock)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = repo.LoadIndex()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
tpe := args[0]
|
|
|
|
switch tpe {
|
|
case "indexes":
|
|
return cmd.DumpIndexes()
|
|
case "snapshots":
|
|
return debugPrintSnapshots(repo, os.Stdout)
|
|
case "trees":
|
|
return printTrees(repo, os.Stdout)
|
|
case "packs":
|
|
return printPacks(repo, os.Stdout)
|
|
case "all":
|
|
fmt.Printf("snapshots:\n")
|
|
err := debugPrintSnapshots(repo, os.Stdout)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Printf("\ntrees:\n")
|
|
|
|
err = printTrees(repo, os.Stdout)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Printf("\nindexes:\n")
|
|
err = cmd.DumpIndexes()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
default:
|
|
return restic.Fatalf("no such type %q", tpe)
|
|
}
|
|
}
|