github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/cmd/check/check.go (about)

     1  // Package check provides the check command.
     2  package check
     3  
     4  import (
     5  	"context"
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	"strings"
    10  
    11  	"github.com/rclone/rclone/cmd"
    12  	"github.com/rclone/rclone/fs"
    13  	"github.com/rclone/rclone/fs/config/flags"
    14  	"github.com/rclone/rclone/fs/hash"
    15  	"github.com/rclone/rclone/fs/operations"
    16  	"github.com/spf13/cobra"
    17  	"github.com/spf13/pflag"
    18  )
    19  
    20  // Globals
    21  var (
    22  	download          = false
    23  	oneway            = false
    24  	combined          = ""
    25  	missingOnSrc      = ""
    26  	missingOnDst      = ""
    27  	match             = ""
    28  	differ            = ""
    29  	errFile           = ""
    30  	checkFileHashType = ""
    31  )
    32  
    33  func init() {
    34  	cmd.Root.AddCommand(commandDefinition)
    35  	cmdFlags := commandDefinition.Flags()
    36  	flags.BoolVarP(cmdFlags, &download, "download", "", download, "Check by downloading rather than with hash", "")
    37  	flags.StringVarP(cmdFlags, &checkFileHashType, "checkfile", "C", checkFileHashType, "Treat source:path as a SUM file with hashes of given type", "")
    38  	AddFlags(cmdFlags)
    39  }
    40  
    41  // AddFlags adds the check flags to the cmdFlags command
    42  func AddFlags(cmdFlags *pflag.FlagSet) {
    43  	flags.BoolVarP(cmdFlags, &oneway, "one-way", "", oneway, "Check one way only, source files must exist on remote", "")
    44  	flags.StringVarP(cmdFlags, &combined, "combined", "", combined, "Make a combined report of changes to this file", "")
    45  	flags.StringVarP(cmdFlags, &missingOnSrc, "missing-on-src", "", missingOnSrc, "Report all files missing from the source to this file", "")
    46  	flags.StringVarP(cmdFlags, &missingOnDst, "missing-on-dst", "", missingOnDst, "Report all files missing from the destination to this file", "")
    47  	flags.StringVarP(cmdFlags, &match, "match", "", match, "Report all matching files to this file", "")
    48  	flags.StringVarP(cmdFlags, &differ, "differ", "", differ, "Report all non-matching files to this file", "")
    49  	flags.StringVarP(cmdFlags, &errFile, "error", "", errFile, "Report all files with errors (hashing or reading) to this file", "")
    50  }
    51  
    52  // FlagsHelp describes the flags for the help
    53  // Warning! "|" will be replaced by backticks below
    54  var FlagsHelp = strings.ReplaceAll(`
    55  If you supply the |--one-way| flag, it will only check that files in
    56  the source match the files in the destination, not the other way
    57  around. This means that extra files in the destination that are not in
    58  the source will not be detected.
    59  
    60  The |--differ|, |--missing-on-dst|, |--missing-on-src|, |--match|
    61  and |--error| flags write paths, one per line, to the file name (or
    62  stdout if it is |-|) supplied. What they write is described in the
    63  help below. For example |--differ| will write all paths which are
    64  present on both the source and destination but different.
    65  
    66  The |--combined| flag will write a file (or stdout) which contains all
    67  file paths with a symbol and then a space and then the path to tell
    68  you what happened to it. These are reminiscent of diff files.
    69  
    70  - |= path| means path was found in source and destination and was identical
    71  - |- path| means path was missing on the source, so only in the destination
    72  - |+ path| means path was missing on the destination, so only in the source
    73  - |* path| means path was present in source and destination but different.
    74  - |! path| means there was an error reading or hashing the source or dest.
    75  
    76  The default number of parallel checks is 8. See the [--checkers=N](/docs/#checkers-n)
    77  option for more information.
    78  `, "|", "`")
    79  
    80  // GetCheckOpt gets the options corresponding to the check flags
    81  func GetCheckOpt(fsrc, fdst fs.Fs) (opt *operations.CheckOpt, close func(), err error) {
    82  	closers := []io.Closer{}
    83  
    84  	opt = &operations.CheckOpt{
    85  		Fsrc:   fsrc,
    86  		Fdst:   fdst,
    87  		OneWay: oneway,
    88  	}
    89  
    90  	open := func(name string, pout *io.Writer) error {
    91  		if name == "" {
    92  			return nil
    93  		}
    94  		if name == "-" {
    95  			*pout = os.Stdout
    96  			return nil
    97  		}
    98  		out, err := os.Create(name)
    99  		if err != nil {
   100  			return err
   101  		}
   102  		*pout = out
   103  		closers = append(closers, out)
   104  		return nil
   105  	}
   106  
   107  	if err = open(combined, &opt.Combined); err != nil {
   108  		return nil, nil, err
   109  	}
   110  	if err = open(missingOnSrc, &opt.MissingOnSrc); err != nil {
   111  		return nil, nil, err
   112  	}
   113  	if err = open(missingOnDst, &opt.MissingOnDst); err != nil {
   114  		return nil, nil, err
   115  	}
   116  	if err = open(match, &opt.Match); err != nil {
   117  		return nil, nil, err
   118  	}
   119  	if err = open(differ, &opt.Differ); err != nil {
   120  		return nil, nil, err
   121  	}
   122  	if err = open(errFile, &opt.Error); err != nil {
   123  		return nil, nil, err
   124  	}
   125  
   126  	close = func() {
   127  		for _, closer := range closers {
   128  			err := closer.Close()
   129  			if err != nil {
   130  				fs.Errorf(nil, "Failed to close report output: %v", err)
   131  			}
   132  		}
   133  	}
   134  
   135  	return opt, close, nil
   136  }
   137  
   138  var commandDefinition = &cobra.Command{
   139  	Use:   "check source:path dest:path",
   140  	Short: `Checks the files in the source and destination match.`,
   141  	Long: strings.ReplaceAll(`
   142  Checks the files in the source and destination match.  It compares
   143  sizes and hashes (MD5 or SHA1) and logs a report of files that don't
   144  match.  It doesn't alter the source or destination.
   145  
   146  For the [crypt](/crypt/) remote there is a dedicated command,
   147  [cryptcheck](/commands/rclone_cryptcheck/), that are able to check
   148  the checksums of the encrypted files.
   149  
   150  If you supply the |--size-only| flag, it will only compare the sizes not
   151  the hashes as well.  Use this for a quick check.
   152  
   153  If you supply the |--download| flag, it will download the data from
   154  both remotes and check them against each other on the fly.  This can
   155  be useful for remotes that don't support hashes or if you really want
   156  to check all the data.
   157  
   158  If you supply the |--checkfile HASH| flag with a valid hash name,
   159  the |source:path| must point to a text file in the SUM format.
   160  `, "|", "`") + FlagsHelp,
   161  	Annotations: map[string]string{
   162  		"groups": "Filter,Listing,Check",
   163  	},
   164  	RunE: func(command *cobra.Command, args []string) error {
   165  		cmd.CheckArgs(2, 2, command, args)
   166  		var (
   167  			fsrc, fdst fs.Fs
   168  			hashType   hash.Type
   169  			fsum       fs.Fs
   170  			sumFile    string
   171  		)
   172  		if checkFileHashType != "" {
   173  			if err := hashType.Set(checkFileHashType); err != nil {
   174  				fmt.Println(hash.HelpString(0))
   175  				return err
   176  			}
   177  			fsum, sumFile, fsrc = cmd.NewFsSrcFileDst(args)
   178  		} else {
   179  			fsrc, fdst = cmd.NewFsSrcDst(args)
   180  		}
   181  
   182  		cmd.Run(false, true, command, func() error {
   183  			opt, close, err := GetCheckOpt(fsrc, fdst)
   184  			if err != nil {
   185  				return err
   186  			}
   187  			defer close()
   188  
   189  			if checkFileHashType != "" {
   190  				return operations.CheckSum(context.Background(), fsrc, fsum, sumFile, hashType, opt, download)
   191  			}
   192  
   193  			if download {
   194  				return operations.CheckDownload(context.Background(), opt)
   195  			}
   196  			hashType := fsrc.Hashes().Overlap(fdst.Hashes()).GetOne()
   197  			if hashType == hash.None {
   198  				fs.Errorf(nil, "No common hash found - not using a hash for checks")
   199  			} else {
   200  				fs.Infof(nil, "Using %v for hash comparisons", hashType)
   201  			}
   202  			return operations.Check(context.Background(), opt)
   203  		})
   204  		return nil
   205  	},
   206  }