2017-02-12 17:30:18 +01:00
|
|
|
package cryptcheck
|
|
|
|
|
|
|
|
import (
|
2019-06-17 10:34:30 +02:00
|
|
|
"context"
|
|
|
|
|
2017-02-12 17:30:18 +01:00
|
|
|
"github.com/pkg/errors"
|
2019-07-28 19:47:38 +02:00
|
|
|
"github.com/rclone/rclone/backend/crypt"
|
|
|
|
"github.com/rclone/rclone/cmd"
|
|
|
|
"github.com/rclone/rclone/fs"
|
2019-10-11 17:55:04 +02:00
|
|
|
"github.com/rclone/rclone/fs/config/flags"
|
2019-07-28 19:47:38 +02:00
|
|
|
"github.com/rclone/rclone/fs/hash"
|
|
|
|
"github.com/rclone/rclone/fs/operations"
|
2017-02-12 17:30:18 +01:00
|
|
|
"github.com/spf13/cobra"
|
|
|
|
)
|
|
|
|
|
2018-05-29 19:07:04 +02:00
|
|
|
// Globals
|
|
|
|
var (
|
|
|
|
oneway = false
|
|
|
|
)
|
|
|
|
|
2017-02-12 17:30:18 +01:00
|
|
|
func init() {
|
2019-10-11 17:58:11 +02:00
|
|
|
cmd.Root.AddCommand(commandDefinition)
|
2019-10-11 17:55:04 +02:00
|
|
|
cmdFlag := commandDefinition.Flags()
|
|
|
|
flags.BoolVarP(cmdFlag, &oneway, "one-way", "", oneway, "Check one way only, source files must exist on destination")
|
2017-02-12 17:30:18 +01:00
|
|
|
}
|
|
|
|
|
2019-10-11 17:58:11 +02:00
|
|
|
var commandDefinition = &cobra.Command{
|
2017-02-12 17:30:18 +01:00
|
|
|
Use: "cryptcheck remote:path cryptedremote:path",
|
2017-06-25 23:43:28 +02:00
|
|
|
Short: `Cryptcheck checks the integrity of a crypted remote.`,
|
2017-02-12 17:30:18 +01:00
|
|
|
Long: `
|
|
|
|
rclone cryptcheck checks a remote against a crypted remote. This is
|
|
|
|
the equivalent of running rclone check, but able to check the
|
|
|
|
checksums of the crypted remote.
|
|
|
|
|
|
|
|
For it to work the underlying remote of the cryptedremote must support
|
|
|
|
some kind of checksum.
|
|
|
|
|
|
|
|
It works by reading the nonce from each file on the cryptedremote: and
|
|
|
|
using that to encrypt each file on the remote:. It then checks the
|
|
|
|
checksum of the underlying file on the cryptedremote: against the
|
|
|
|
checksum of the file it has just encrypted.
|
|
|
|
|
|
|
|
Use it like this
|
|
|
|
|
|
|
|
rclone cryptcheck /path/to/files encryptedremote:path
|
|
|
|
|
|
|
|
You can use it like this also, but that will involve downloading all
|
|
|
|
the files in remote:path.
|
|
|
|
|
|
|
|
rclone cryptcheck remote:path encryptedremote:path
|
|
|
|
|
|
|
|
After it has run it will log the status of the encryptedremote:.
|
2018-05-29 19:07:04 +02:00
|
|
|
|
|
|
|
If you supply the --one-way flag, it will only check that files in source
|
|
|
|
match the files in destination, not the other way around. Meaning extra files in
|
|
|
|
destination that are not in the source will not trigger an error.
|
2017-02-12 17:30:18 +01:00
|
|
|
`,
|
|
|
|
Run: func(command *cobra.Command, args []string) {
|
|
|
|
cmd.CheckArgs(2, 2, command, args)
|
|
|
|
fsrc, fdst := cmd.NewFsSrcDst(args)
|
|
|
|
cmd.Run(false, true, command, func() error {
|
2019-06-17 10:34:30 +02:00
|
|
|
return cryptCheck(context.Background(), fdst, fsrc)
|
2017-02-12 17:30:18 +01:00
|
|
|
})
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
// cryptCheck checks the integrity of a crypted remote
|
2019-06-17 10:34:30 +02:00
|
|
|
func cryptCheck(ctx context.Context, fdst, fsrc fs.Fs) error {
|
2017-02-12 17:30:18 +01:00
|
|
|
// Check to see fcrypt is a crypt
|
|
|
|
fcrypt, ok := fdst.(*crypt.Fs)
|
|
|
|
if !ok {
|
|
|
|
return errors.Errorf("%s:%s is not a crypt remote", fdst.Name(), fdst.Root())
|
|
|
|
}
|
|
|
|
// Find a hash to use
|
|
|
|
funderlying := fcrypt.UnWrap()
|
|
|
|
hashType := funderlying.Hashes().GetOne()
|
2018-01-18 21:27:52 +01:00
|
|
|
if hashType == hash.None {
|
2017-02-12 17:30:18 +01:00
|
|
|
return errors.Errorf("%s:%s does not support any hashes", funderlying.Name(), funderlying.Root())
|
|
|
|
}
|
|
|
|
fs.Infof(nil, "Using %v for hash comparisons", hashType)
|
|
|
|
|
|
|
|
// checkIdentical checks to see if dst and src are identical
|
|
|
|
//
|
|
|
|
// it returns true if differences were found
|
|
|
|
// it also returns whether it couldn't be hashed
|
2019-06-17 10:34:30 +02:00
|
|
|
checkIdentical := func(ctx context.Context, dst, src fs.Object) (differ bool, noHash bool) {
|
2017-02-12 17:30:18 +01:00
|
|
|
cryptDst := dst.(*crypt.Object)
|
|
|
|
underlyingDst := cryptDst.UnWrap()
|
2019-06-17 10:34:30 +02:00
|
|
|
underlyingHash, err := underlyingDst.Hash(ctx, hashType)
|
2017-02-12 17:30:18 +01:00
|
|
|
if err != nil {
|
2019-11-18 15:13:02 +01:00
|
|
|
err = fs.CountError(err)
|
2017-02-12 17:30:18 +01:00
|
|
|
fs.Errorf(dst, "Error reading hash from underlying %v: %v", underlyingDst, err)
|
|
|
|
return true, false
|
|
|
|
}
|
|
|
|
if underlyingHash == "" {
|
|
|
|
return false, true
|
|
|
|
}
|
2019-06-17 10:34:30 +02:00
|
|
|
cryptHash, err := fcrypt.ComputeHash(ctx, cryptDst, src, hashType)
|
2017-02-12 17:30:18 +01:00
|
|
|
if err != nil {
|
2019-11-18 15:13:02 +01:00
|
|
|
err = fs.CountError(err)
|
2017-02-12 17:30:18 +01:00
|
|
|
fs.Errorf(dst, "Error computing hash: %v", err)
|
|
|
|
return true, false
|
|
|
|
}
|
|
|
|
if cryptHash == "" {
|
|
|
|
return false, true
|
|
|
|
}
|
|
|
|
if cryptHash != underlyingHash {
|
2017-11-15 06:32:00 +01:00
|
|
|
err = errors.Errorf("hashes differ (%s:%s) %q vs (%s:%s) %q", fdst.Name(), fdst.Root(), cryptHash, fsrc.Name(), fsrc.Root(), underlyingHash)
|
2019-11-18 15:13:02 +01:00
|
|
|
err = fs.CountError(err)
|
2017-11-15 06:32:00 +01:00
|
|
|
fs.Errorf(src, err.Error())
|
2017-02-12 17:30:18 +01:00
|
|
|
return true, false
|
|
|
|
}
|
|
|
|
fs.Debugf(src, "OK")
|
|
|
|
return false, false
|
|
|
|
}
|
|
|
|
|
2019-06-17 10:34:30 +02:00
|
|
|
return operations.CheckFn(ctx, fcrypt, fsrc, checkIdentical, oneway)
|
2017-02-12 17:30:18 +01:00
|
|
|
}
|