diff --git a/backend/crypt/crypt.go b/backend/crypt/crypt.go index 002fbc8cc..a28114293 100644 --- a/backend/crypt/crypt.go +++ b/backend/crypt/crypt.go @@ -443,7 +443,7 @@ func (f *Fs) put(ctx context.Context, in io.Reader, src fs.ObjectInfo, options [ if err != nil { fs.Errorf(o, "Failed to remove corrupted object: %v", err) } - return nil, fmt.Errorf("corrupted on transfer: %v crypted hash differ %q vs %q", ht, srcHash, dstHash) + return nil, fmt.Errorf("corrupted on transfer: %v crypted hash differ src %q vs dst %q", ht, srcHash, dstHash) } fs.Debugf(src, "%v = %s OK", ht, srcHash) } diff --git a/backend/local/local.go b/backend/local/local.go index 5dc65ec6a..3c00d5389 100644 --- a/backend/local/local.go +++ b/backend/local/local.go @@ -1133,6 +1133,9 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op return err } + // Wipe hashes before update + o.clearHashCache() + var symlinkData bytes.Buffer // If the object is a regular file, create it. // If it is a translated link, just read in the contents, and @@ -1295,6 +1298,13 @@ func (o *Object) setMetadata(info os.FileInfo) { } } +// clearHashCache wipes any cached hashes for the object +func (o *Object) clearHashCache() { + o.fs.objectMetaMu.Lock() + o.hashes = nil + o.fs.objectMetaMu.Unlock() +} + // Stat an Object into info func (o *Object) lstat() error { info, err := o.fs.lstat(o.path) @@ -1306,6 +1316,7 @@ func (o *Object) lstat() error { // Remove an object func (o *Object) Remove(ctx context.Context) error { + o.clearHashCache() return remove(o.path) } diff --git a/backend/local/local_internal_test.go b/backend/local/local_internal_test.go index ecbc29b08..2957dcd1c 100644 --- a/backend/local/local_internal_test.go +++ b/backend/local/local_internal_test.go @@ -1,6 +1,7 @@ package local import ( + "bytes" "context" "io/ioutil" "os" @@ -12,6 +13,7 @@ import ( "github.com/rclone/rclone/fs" "github.com/rclone/rclone/fs/config/configmap" "github.com/rclone/rclone/fs/hash" + "github.com/rclone/rclone/fs/object" "github.com/rclone/rclone/fstest" "github.com/rclone/rclone/lib/file" "github.com/rclone/rclone/lib/readers" @@ -166,3 +168,64 @@ func TestSymlinkError(t *testing.T) { _, err := NewFs(context.Background(), "local", "/", m) assert.Equal(t, errLinksAndCopyLinks, err) } + +// Test hashes on updating an object +func TestHashOnUpdate(t *testing.T) { + ctx := context.Background() + r := fstest.NewRun(t) + defer r.Finalise() + const filePath = "file.txt" + when := time.Now() + r.WriteFile(filePath, "content", when) + f := r.Flocal.(*Fs) + + // Get the object + o, err := f.NewObject(ctx, filePath) + require.NoError(t, err) + + // Test the hash is as we expect + md5, err := o.Hash(ctx, hash.MD5) + require.NoError(t, err) + assert.Equal(t, "9a0364b9e99bb480dd25e1f0284c8555", md5) + + // Reupload it with diferent contents but same size and timestamp + var b = bytes.NewBufferString("CONTENT") + src := object.NewStaticObjectInfo(filePath, when, int64(b.Len()), true, nil, f) + err = o.Update(ctx, b, src) + require.NoError(t, err) + + // Check the hash is as expected + md5, err = o.Hash(ctx, hash.MD5) + require.NoError(t, err) + assert.Equal(t, "45685e95985e20822fb2538a522a5ccf", md5) +} + +// Test hashes on deleting an object +func TestHashOnDelete(t *testing.T) { + ctx := context.Background() + r := fstest.NewRun(t) + defer r.Finalise() + const filePath = "file.txt" + when := time.Now() + r.WriteFile(filePath, "content", when) + f := r.Flocal.(*Fs) + + // Get the object + o, err := f.NewObject(ctx, filePath) + require.NoError(t, err) + + // Test the hash is as we expect + md5, err := o.Hash(ctx, hash.MD5) + require.NoError(t, err) + assert.Equal(t, "9a0364b9e99bb480dd25e1f0284c8555", md5) + + // Delete the object + require.NoError(t, o.Remove(ctx)) + + // Test the hash cache is empty + require.Nil(t, o.(*Object).hashes) + + // Test the hash returns an error + _, err = o.Hash(ctx, hash.MD5) + require.Error(t, err) +}