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 }