github.com/advanderveer/restic@v0.8.1-0.20171209104529-42a8c19aaea6/cmd/restic/cmd_check.go (about)

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