github.com/fawick/restic@v0.1.1-0.20171126184616-c02923fbfc79/cmd/restic/cmd_check.go (about)

     1  package main
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"time"
     8  
     9  	"github.com/spf13/cobra"
    10  
    11  	"github.com/restic/restic/internal/checker"
    12  	"github.com/restic/restic/internal/errors"
    13  	"github.com/restic/restic/internal/restic"
    14  )
    15  
    16  var cmdCheck = &cobra.Command{
    17  	Use:   "check [flags]",
    18  	Short: "Check the repository for errors",
    19  	Long: `
    20  The "check" command tests the repository for errors and reports any errors it
    21  finds. It can also be used to read all data and therefore simulate a restore.
    22  
    23  By default, the "check" command will always load all data directly from the
    24  repository and not use a local cache.
    25  `,
    26  	DisableAutoGenTag: true,
    27  	RunE: func(cmd *cobra.Command, args []string) error {
    28  		return runCheck(checkOptions, globalOptions, args)
    29  	},
    30  }
    31  
    32  // CheckOptions bundles all options for the 'check' command.
    33  type CheckOptions struct {
    34  	ReadData    bool
    35  	CheckUnused bool
    36  	WithCache   bool
    37  }
    38  
    39  var checkOptions CheckOptions
    40  
    41  func init() {
    42  	cmdRoot.AddCommand(cmdCheck)
    43  
    44  	f := cmdCheck.Flags()
    45  	f.BoolVar(&checkOptions.ReadData, "read-data", false, "read all data blobs")
    46  	f.BoolVar(&checkOptions.CheckUnused, "check-unused", false, "find unused blobs")
    47  	f.BoolVar(&checkOptions.WithCache, "with-cache", false, "use the cache")
    48  }
    49  
    50  func newReadProgress(gopts GlobalOptions, todo restic.Stat) *restic.Progress {
    51  	if gopts.Quiet {
    52  		return nil
    53  	}
    54  
    55  	readProgress := restic.NewProgress()
    56  
    57  	readProgress.OnUpdate = func(s restic.Stat, d time.Duration, ticker bool) {
    58  		status := fmt.Sprintf("[%s] %s  %d / %d items",
    59  			formatDuration(d),
    60  			formatPercent(s.Blobs, todo.Blobs),
    61  			s.Blobs, todo.Blobs)
    62  
    63  		if w := stdoutTerminalWidth(); w > 0 {
    64  			if len(status) > w {
    65  				max := w - len(status) - 4
    66  				status = status[:max] + "... "
    67  			}
    68  		}
    69  
    70  		PrintProgress("%s", status)
    71  	}
    72  
    73  	readProgress.OnDone = func(s restic.Stat, d time.Duration, ticker bool) {
    74  		fmt.Printf("\nduration: %s\n", formatDuration(d))
    75  	}
    76  
    77  	return readProgress
    78  }
    79  
    80  func runCheck(opts CheckOptions, gopts GlobalOptions, args []string) error {
    81  	if len(args) != 0 {
    82  		return errors.Fatal("check has no arguments")
    83  	}
    84  
    85  	if !opts.WithCache {
    86  		// do not use a cache for the checker
    87  		gopts.NoCache = true
    88  	}
    89  
    90  	repo, err := OpenRepository(gopts)
    91  	if err != nil {
    92  		return err
    93  	}
    94  
    95  	if !gopts.NoLock {
    96  		Verbosef("create exclusive lock for repository\n")
    97  		lock, err := lockRepoExclusive(repo)
    98  		defer unlockRepo(lock)
    99  		if err != nil {
   100  			return err
   101  		}
   102  	}
   103  
   104  	chkr := checker.New(repo)
   105  
   106  	Verbosef("load indexes\n")
   107  	hints, errs := chkr.LoadIndex(context.TODO())
   108  
   109  	dupFound := false
   110  	for _, hint := range hints {
   111  		Printf("%v\n", hint)
   112  		if _, ok := hint.(checker.ErrDuplicatePacks); ok {
   113  			dupFound = true
   114  		}
   115  	}
   116  
   117  	if dupFound {
   118  		Printf("\nrun `restic rebuild-index' to correct this\n")
   119  	}
   120  
   121  	if len(errs) > 0 {
   122  		for _, err := range errs {
   123  			Warnf("error: %v\n", err)
   124  		}
   125  		return errors.Fatal("LoadIndex returned errors")
   126  	}
   127  
   128  	errorsFound := false
   129  	errChan := make(chan error)
   130  
   131  	Verbosef("check all packs\n")
   132  	go chkr.Packs(context.TODO(), errChan)
   133  
   134  	for err := range errChan {
   135  		errorsFound = true
   136  		fmt.Fprintf(os.Stderr, "%v\n", err)
   137  	}
   138  
   139  	Verbosef("check snapshots, trees and blobs\n")
   140  	errChan = make(chan error)
   141  	go chkr.Structure(context.TODO(), errChan)
   142  
   143  	for err := range errChan {
   144  		errorsFound = true
   145  		if e, ok := err.(checker.TreeError); ok {
   146  			fmt.Fprintf(os.Stderr, "error for tree %v:\n", e.ID.Str())
   147  			for _, treeErr := range e.Errors {
   148  				fmt.Fprintf(os.Stderr, "  %v\n", treeErr)
   149  			}
   150  		} else {
   151  			fmt.Fprintf(os.Stderr, "error: %v\n", err)
   152  		}
   153  	}
   154  
   155  	if opts.CheckUnused {
   156  		for _, id := range chkr.UnusedBlobs() {
   157  			Verbosef("unused blob %v\n", id.Str())
   158  			errorsFound = true
   159  		}
   160  	}
   161  
   162  	if opts.ReadData {
   163  		Verbosef("read all data\n")
   164  
   165  		p := newReadProgress(gopts, restic.Stat{Blobs: chkr.CountPacks()})
   166  		errChan := make(chan error)
   167  
   168  		go chkr.ReadData(context.TODO(), p, errChan)
   169  
   170  		for err := range errChan {
   171  			errorsFound = true
   172  			fmt.Fprintf(os.Stderr, "%v\n", err)
   173  		}
   174  	}
   175  
   176  	if errorsFound {
   177  		return errors.Fatal("repository contains errors")
   178  	}
   179  
   180  	Verbosef("no errors were found\n")
   181  
   182  	return nil
   183  }