From 2756900749a37d450a1c31fab6386c027333844e Mon Sep 17 00:00:00 2001 From: Nick Craig-Wood Date: Mon, 28 Nov 2016 17:08:15 +0000 Subject: [PATCH] Fix not transferring files that don't differ in size - fixes #911 Due to a logic error files stored on remotes which support modtime but not hashes weren't being transferred when updating with a file of the same size but different modtime. Instead the modtime of the remote file was being set to that of the local file. In practice this affected crypt with all remotes except Amazon Drive and Dropbox. --- fs/operations.go | 66 +++++++++++++++++++++++++++++------------------- fs/sync_test.go | 24 ++++++++++++++++++ 2 files changed, 64 insertions(+), 26 deletions(-) diff --git a/fs/operations.go b/fs/operations.go index 5284b01ff..a57ee8aab 100644 --- a/fs/operations.go +++ b/fs/operations.go @@ -12,7 +12,6 @@ import ( "strings" "sync" "sync/atomic" - "time" "github.com/pkg/errors" "github.com/spf13/pflag" @@ -121,34 +120,53 @@ func Equal(src, dst Object) bool { return true } - var srcModTime time.Time - if !Config.CheckSum { - if Config.ModifyWindow == ModTimeNotSupported { - Debug(src, "Sizes identical") - return true + // Assert: Size is equal or being ignored + + // If checking checksum and not modtime + if Config.CheckSum { + // Check the hash + same, hash, _ := CheckHashes(src, dst) + if !same { + Debug(src, "%v differ", hash) + return false } - // Size the same so check the mtime - srcModTime = src.ModTime() - dstModTime := dst.ModTime() - dt := dstModTime.Sub(srcModTime) - ModifyWindow := Config.ModifyWindow - if dt >= ModifyWindow || dt <= -ModifyWindow { - Debug(src, "Modification times differ by %s: %v, %v", dt, srcModTime, dstModTime) + if hash == HashNone { + Debug(src, "Size of src and dst objects identical") } else { - Debug(src, "Size and modification time the same (differ by %s, within tolerance %s)", dt, ModifyWindow) - return true + Debug(src, "Size and %v of src and dst objects identical", hash) } + return true } - // mtime is unreadable or different but size is the same so - // check the hash + // Sizes the same so check the mtime + if Config.ModifyWindow == ModTimeNotSupported { + Debug(src, "Sizes identical") + return true + } + srcModTime := src.ModTime() + dstModTime := dst.ModTime() + dt := dstModTime.Sub(srcModTime) + ModifyWindow := Config.ModifyWindow + if dt < ModifyWindow && dt > -ModifyWindow { + Debug(src, "Size and modification time the same (differ by %s, within tolerance %s)", dt, ModifyWindow) + return true + } + + Debug(src, "Modification times differ by %s: %v, %v", dt, srcModTime, dstModTime) + + // Check if the hashes are the same same, hash, _ := CheckHashes(src, dst) if !same { - Debug(src, "Hash differ") + Debug(src, "%v differ", hash) + return false + } + if hash == HashNone { + // if couldn't check hash, return that they differ return false } - if !(Config.CheckSum || Config.NoUpdateModTime) { + // mod time differs but hash is the same to reset mod time if required + if !Config.NoUpdateModTime { // Size and hash the same but mtime different so update the // mtime of the dst object here err := dst.SetModTime(srcModTime) @@ -157,15 +175,11 @@ func Equal(src, dst Object) bool { return false } else if err != nil { Stats.Error() - ErrorLog(dst, "Failed to read set modification time: %v", err) + ErrorLog(dst, "Failed to set modification time: %v", err) + } else { + Debug(src, "Updated modification time in destination") } } - - if hash == HashNone { - Debug(src, "Size of src and dst objects identical") - } else { - Debug(src, "Size and %v of src and dst objects identical", hash) - } return true } diff --git a/fs/sync_test.go b/fs/sync_test.go index 84fbfc70c..df2227176 100644 --- a/fs/sync_test.go +++ b/fs/sync_test.go @@ -336,6 +336,30 @@ func TestSyncAfterChangingModtimeOnlyWithNoUpdateModTime(t *testing.T) { fstest.CheckItems(t, r.fremote, file2) } +func TestSyncDoesntUpdateModtime(t *testing.T) { + if fs.Config.ModifyWindow == fs.ModTimeNotSupported { + t.Skip("Can't run this test on fs which doesn't support mod time") + } + r := NewRun(t) + defer r.Finalise() + + file1 := r.WriteFile("foo", "foo", t2) + file2 := r.WriteObject("foo", "bar", t1) + + fstest.CheckItems(t, r.flocal, file1) + fstest.CheckItems(t, r.fremote, file2) + + fs.Stats.ResetCounters() + err := fs.Sync(r.fremote, r.flocal) + require.NoError(t, err) + + fstest.CheckItems(t, r.flocal, file1) + fstest.CheckItems(t, r.fremote, file1) + + // We should have transferred exactly one file, not set the mod time + assert.Equal(t, int64(1), fs.Stats.GetTransfers()) +} + func TestSyncAfterAddingAFile(t *testing.T) { r := NewRun(t) defer r.Finalise()