2015-06-29 00:22:25 +02:00
package main
import (
2015-07-11 16:00:49 +02:00
"fmt"
2018-03-31 10:23:55 +02:00
"io/ioutil"
2015-07-11 16:00:49 +02:00
"os"
2018-01-02 00:38:14 -05:00
"strconv"
"strings"
2015-12-06 17:29:31 +01:00
"time"
2015-06-29 00:22:25 +02:00
2016-09-17 12:36:05 +02:00
"github.com/spf13/cobra"
2017-07-23 14:21:03 +02:00
"github.com/restic/restic/internal/checker"
"github.com/restic/restic/internal/errors"
2018-03-31 10:23:55 +02:00
"github.com/restic/restic/internal/fs"
2017-07-24 17:42:25 +02:00
"github.com/restic/restic/internal/restic"
2015-06-29 00:22:25 +02:00
)
2016-09-17 12:36:05 +02:00
var cmdCheck = & cobra . Command {
Use : "check [flags]" ,
2017-09-11 09:32:44 -07:00
Short : "Check the repository for errors" ,
2016-09-17 12:36:05 +02:00
Long : `
The "check" command tests the repository for errors and reports any errors it
finds . It can also be used to read all data and therefore simulate a restore .
2017-07-18 22:15:18 +02:00
By default , the "check" command will always load all data directly from the
repository and not use a local cache .
2016-09-17 12:36:05 +02:00
` ,
2017-08-06 21:02:16 +02:00
DisableAutoGenTag : true ,
2016-09-17 12:36:05 +02:00
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
return runCheck ( checkOptions , globalOptions , args )
} ,
2018-01-02 00:38:14 -05:00
PreRunE : func ( cmd * cobra . Command , args [ ] string ) error {
return checkFlags ( checkOptions )
} ,
2016-09-17 12:36:05 +02:00
}
2015-06-29 00:22:25 +02:00
2017-03-08 20:09:24 +01:00
// CheckOptions bundles all options for the 'check' command.
2016-09-17 12:36:05 +02:00
type CheckOptions struct {
2018-01-02 00:38:14 -05:00
ReadData bool
ReadDataSubset string
CheckUnused bool
WithCache bool
2015-06-29 00:22:25 +02:00
}
2016-09-17 12:36:05 +02:00
var checkOptions CheckOptions
2015-06-29 00:22:25 +02:00
func init ( ) {
2016-09-17 12:36:05 +02:00
cmdRoot . AddCommand ( cmdCheck )
2015-06-29 00:22:25 +02:00
2016-09-17 12:36:05 +02:00
f := cmdCheck . Flags ( )
2017-02-13 16:02:47 +01:00
f . BoolVar ( & checkOptions . ReadData , "read-data" , false , "read all data blobs" )
2018-07-05 22:19:08 +02:00
f . StringVar ( & checkOptions . ReadDataSubset , "read-data-subset" , "" , "read subset n of m data packs (format: `n/m`)" )
2017-02-13 16:02:47 +01:00
f . BoolVar ( & checkOptions . CheckUnused , "check-unused" , false , "find unused blobs" )
2017-07-18 22:15:18 +02:00
f . BoolVar ( & checkOptions . WithCache , "with-cache" , false , "use the cache" )
2015-06-29 00:22:25 +02:00
}
2018-01-02 00:38:14 -05:00
func checkFlags ( opts CheckOptions ) error {
if opts . ReadData && opts . ReadDataSubset != "" {
return errors . Fatalf ( "check flags --read-data and --read-data-subset cannot be used together" )
}
if opts . ReadDataSubset != "" {
dataSubset , err := stringToIntSlice ( opts . ReadDataSubset )
if err != nil || len ( dataSubset ) != 2 {
return errors . Fatalf ( "check flag --read-data-subset must have two positive integer values, e.g. --read-data-subset=1/2" )
}
if dataSubset [ 0 ] == 0 || dataSubset [ 1 ] == 0 || dataSubset [ 0 ] > dataSubset [ 1 ] {
return errors . Fatalf ( "check flag --read-data-subset=n/t values must be positive integers, and n <= t, e.g. --read-data-subset=1/2" )
}
}
return nil
}
// stringToIntSlice converts string to []uint, using '/' as element separator
func stringToIntSlice ( param string ) ( split [ ] uint , err error ) {
if param == "" {
return nil , nil
}
parts := strings . Split ( param , "/" )
result := make ( [ ] uint , len ( parts ) )
for idx , part := range parts {
uintval , err := strconv . ParseUint ( part , 10 , 0 )
if err != nil {
return nil , err
}
result [ idx ] = uint ( uintval )
}
return result , nil
}
2016-09-17 12:36:05 +02:00
func newReadProgress ( gopts GlobalOptions , todo restic . Stat ) * restic . Progress {
if gopts . Quiet {
2015-12-06 17:29:31 +01:00
return nil
}
2016-08-25 22:13:47 +02:00
readProgress := restic . NewProgress ( )
2015-12-06 17:29:31 +01:00
readProgress . OnUpdate = func ( s restic . Stat , d time . Duration , ticker bool ) {
status := fmt . Sprintf ( "[%s] %s %d / %d items" ,
formatDuration ( d ) ,
formatPercent ( s . Blobs , todo . Blobs ) ,
s . Blobs , todo . Blobs )
2017-03-06 11:23:00 +01:00
if w := stdoutTerminalWidth ( ) ; w > 0 {
2015-12-06 17:29:31 +01:00
if len ( status ) > w {
max := w - len ( status ) - 4
status = status [ : max ] + "... "
}
}
2016-08-25 22:13:47 +02:00
PrintProgress ( "%s" , status )
2015-12-06 17:29:31 +01:00
}
readProgress . OnDone = func ( s restic . Stat , d time . Duration , ticker bool ) {
fmt . Printf ( "\nduration: %s\n" , formatDuration ( d ) )
}
return readProgress
}
2018-03-31 10:23:55 +02:00
// prepareCheckCache configures a special cache directory for check.
//
// * if --with-cache is specified, the default cache is used
2018-05-11 10:55:12 -04:00
// * if the user explicitly requested --no-cache, we don't use any cache
2018-07-22 18:24:11 +01:00
// * if the user provides --cache-dir, we use a cache in a temporary sub-directory of the specified directory and the sub-directory is deleted after the check
2018-03-31 10:23:55 +02:00
// * by default, we use a cache in a temporary directory that is deleted after the check
func prepareCheckCache ( opts CheckOptions , gopts * GlobalOptions ) ( cleanup func ( ) ) {
cleanup = func ( ) { }
if opts . WithCache {
// use the default cache, no setup needed
return cleanup
}
if gopts . NoCache {
// don't use any cache, no setup needed
return cleanup
}
2018-07-22 18:24:11 +01:00
cachedir := gopts . CacheDir
2018-03-31 10:23:55 +02:00
// use a cache in a temporary directory
2018-07-22 18:24:11 +01:00
tempdir , err := ioutil . TempDir ( cachedir , "restic-check-cache-" )
2018-03-31 10:23:55 +02:00
if err != nil {
// if an error occurs, don't use any cache
Warnf ( "unable to create temporary directory for cache during check, disabling cache: %v\n" , err )
gopts . NoCache = true
return cleanup
}
gopts . CacheDir = tempdir
Verbosef ( "using temporary cache in %v\n" , tempdir )
cleanup = func ( ) {
err := fs . RemoveAll ( tempdir )
if err != nil {
Warnf ( "error removing temporary cache directory: %v\n" , err )
}
}
return cleanup
}
2016-09-17 12:36:05 +02:00
func runCheck ( opts CheckOptions , gopts GlobalOptions , args [ ] string ) error {
2015-06-29 00:22:25 +02:00
if len ( args ) != 0 {
2016-09-01 22:17:37 +02:00
return errors . Fatal ( "check has no arguments" )
2015-06-29 00:22:25 +02:00
}
2018-03-31 10:23:55 +02:00
cleanup := prepareCheckCache ( opts , & gopts )
2018-04-01 18:09:53 +02:00
AddCleanupHandler ( func ( ) error {
cleanup ( )
return nil
} )
2017-07-18 22:15:18 +02:00
2016-09-17 12:36:05 +02:00
repo , err := OpenRepository ( gopts )
2015-06-29 00:22:25 +02:00
if err != nil {
return err
}
2016-09-17 12:36:05 +02:00
if ! gopts . NoLock {
2017-10-27 21:06:34 +02:00
Verbosef ( "create exclusive lock for repository\n" )
2015-11-10 21:41:22 +01:00
lock , err := lockRepoExclusive ( repo )
defer unlockRepo ( lock )
if err != nil {
return err
}
2015-06-29 00:22:25 +02:00
}
2015-07-12 17:09:48 +02:00
chkr := checker . New ( repo )
2015-06-29 00:22:25 +02:00
2017-10-27 21:06:34 +02:00
Verbosef ( "load indexes\n" )
use global context for check, debug, dump, find, forget, init, key,
list, mount, tag, unlock commands
gh-1434
2017-12-06 07:02:55 -05:00
hints , errs := chkr . LoadIndex ( gopts . ctx )
2015-10-25 16:26:50 +01:00
2015-10-25 17:24:52 +01:00
dupFound := false
2015-10-25 16:26:50 +01:00
for _ , hint := range hints {
2016-09-17 12:36:05 +02:00
Printf ( "%v\n" , hint )
2015-10-25 17:24:52 +01:00
if _ , ok := hint . ( checker . ErrDuplicatePacks ) ; ok {
dupFound = true
}
}
if dupFound {
2018-04-07 10:07:54 +02:00
Printf ( "This is non-critical, you can run `restic rebuild-index' to correct this\n" )
2015-10-25 16:26:50 +01:00
}
if len ( errs ) > 0 {
for _ , err := range errs {
2016-09-17 12:36:05 +02:00
Warnf ( "error: %v\n" , err )
2015-10-25 16:26:50 +01:00
}
2016-09-01 22:17:37 +02:00
return errors . Fatal ( "LoadIndex returned errors" )
2015-06-29 00:22:25 +02:00
}
2015-07-11 16:00:49 +02:00
errorsFound := false
2018-04-07 10:07:54 +02:00
orphanedPacks := 0
2015-07-12 01:44:19 +02:00
errChan := make ( chan error )
2017-10-27 21:06:34 +02:00
Verbosef ( "check all packs\n" )
use global context for check, debug, dump, find, forget, init, key,
list, mount, tag, unlock commands
gh-1434
2017-12-06 07:02:55 -05:00
go chkr . Packs ( gopts . ctx , errChan )
2015-07-12 01:44:19 +02:00
for err := range errChan {
2018-04-07 10:07:54 +02:00
if checker . IsOrphanedPack ( err ) {
orphanedPacks ++
Verbosef ( "%v\n" , err )
continue
}
2015-07-11 16:00:49 +02:00
errorsFound = true
2015-07-12 17:09:48 +02:00
fmt . Fprintf ( os . Stderr , "%v\n" , err )
2015-07-11 16:00:49 +02:00
}
2018-04-07 10:07:54 +02:00
if orphanedPacks > 0 {
Verbosef ( "%d additional files were found in the repo, which likely contain duplicate data.\nYou can run `restic prune` to correct this.\n" , orphanedPacks )
}
2017-10-27 21:06:34 +02:00
Verbosef ( "check snapshots, trees and blobs\n" )
2015-07-12 01:44:19 +02:00
errChan = make ( chan error )
use global context for check, debug, dump, find, forget, init, key,
list, mount, tag, unlock commands
gh-1434
2017-12-06 07:02:55 -05:00
go chkr . Structure ( gopts . ctx , errChan )
2015-07-12 01:44:19 +02:00
for err := range errChan {
2015-07-11 16:00:49 +02:00
errorsFound = true
2015-10-11 19:13:45 +02:00
if e , ok := err . ( checker . TreeError ) ; ok {
fmt . Fprintf ( os . Stderr , "error for tree %v:\n" , e . ID . Str ( ) )
for _ , treeErr := range e . Errors {
fmt . Fprintf ( os . Stderr , " %v\n" , treeErr )
}
} else {
fmt . Fprintf ( os . Stderr , "error: %v\n" , err )
}
2015-07-11 16:00:49 +02:00
}
2016-09-17 12:36:05 +02:00
if opts . CheckUnused {
2015-11-08 20:46:52 +01:00
for _ , id := range chkr . UnusedBlobs ( ) {
2016-09-17 12:36:05 +02:00
Verbosef ( "unused blob %v\n" , id . Str ( ) )
2015-11-08 20:46:52 +01:00
errorsFound = true
}
2015-07-12 17:09:48 +02:00
}
2018-01-02 00:38:14 -05:00
doReadData := func ( bucket , totalBuckets uint ) {
packs := restic . IDSet { }
for pack := range chkr . GetPacks ( ) {
if ( uint ( pack [ 0 ] ) % totalBuckets ) == ( bucket - 1 ) {
packs . Insert ( pack )
}
}
packCount := uint64 ( len ( packs ) )
2015-12-06 17:09:06 +01:00
2018-01-02 00:38:14 -05:00
if packCount < chkr . CountPacks ( ) {
Verbosef ( fmt . Sprintf ( "read group #%d of %d data packs (out of total %d packs in %d groups)\n" , bucket , packCount , chkr . CountPacks ( ) , totalBuckets ) )
} else {
Verbosef ( "read all data\n" )
}
p := newReadProgress ( gopts , restic . Stat { Blobs : packCount } )
2015-12-06 17:09:06 +01:00
errChan := make ( chan error )
2018-01-02 00:38:14 -05:00
go chkr . ReadPacks ( gopts . ctx , packs , p , errChan )
2015-12-06 17:09:06 +01:00
for err := range errChan {
errorsFound = true
fmt . Fprintf ( os . Stderr , "%v\n" , err )
}
}
2018-01-02 00:38:14 -05:00
switch {
case opts . ReadData :
doReadData ( 1 , 1 )
case opts . ReadDataSubset != "" :
dataSubset , _ := stringToIntSlice ( opts . ReadDataSubset )
doReadData ( dataSubset [ 0 ] , dataSubset [ 1 ] )
}
2015-07-11 16:00:49 +02:00
if errorsFound {
2016-09-01 22:17:37 +02:00
return errors . Fatal ( "repository contains errors" )
2015-07-11 16:00:49 +02:00
}
2017-10-03 08:29:19 +02:00
2017-10-27 21:06:34 +02:00
Verbosef ( "no errors were found\n" )
2017-10-03 08:29:19 +02:00
2015-06-29 00:22:25 +02:00
return nil
}