From 84191ac6dc600987686de342315b435bfdd45007 Mon Sep 17 00:00:00 2001 From: Nick Craig-Wood Date: Sun, 19 Jan 2020 12:52:48 +0000 Subject: [PATCH] vfs: fix incorrect modtime for mv into mount with --vfs-cache-modes write When a file has its modtime set while it is open we delay setting the modtime until the file is closed. The file is then uploaded in Flush. In Release we check the cached file has been uploaded by comparing modtimes and or hashes and upload it again if it has changed. Before this change we forgot to change the time on the cached file when we updated the time file on the object, so this mean that Release reset the time to the wrong time and uploaded the file again on remotes which don't support hashes (eg crypt). The fix was to set the modtime of the cached file at the same time we set the modtime of the remote object. This means that the files check as identical in Release so it doesn't try to upload the file. This means that we avoid a double upload and the modtime is correct. See: https://forum.rclone.org/t/modification-time-with-vfs-cache/13906/8 --- vfs/cache.go | 9 +++++++++ vfs/file.go | 14 +++++++++++++- vfs/read_write_test.go | 13 +++++++++++-- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/vfs/cache.go b/vfs/cache.go index 8a9ade07a..18f85a8cf 100644 --- a/vfs/cache.go +++ b/vfs/cache.go @@ -381,6 +381,15 @@ func (c *cache) removeDir(dir string) bool { return false } +// setModTime should be called to set the modification time of the cache file +func (c *cache) setModTime(name string, modTime time.Time) { + osPath := c.toOSPath(name) + err := os.Chtimes(osPath, modTime, modTime) + if err != nil { + fs.Errorf(name, "Failed to set modification time of cached file: %v", err) + } +} + // cleanUp empties the cache of everything func (c *cache) cleanUp() error { return os.RemoveAll(c.root) diff --git a/vfs/file.go b/vfs/file.go index c3a227b74..16f0581ff 100644 --- a/vfs/file.go +++ b/vfs/file.go @@ -88,11 +88,17 @@ func (f *File) Name() (name string) { return f.leaf } +// _path returns the full path of the file +// use when lock is held +func (f *File) _path() string { + return path.Join(f.d.path, f.leaf) +} + // Path returns the full path of the file func (f *File) Path() string { f.mu.RLock() defer f.mu.RUnlock() - return path.Join(f.d.path, f.leaf) + return f._path() } // osPath returns the full path of the file in the cache in OS format @@ -372,6 +378,12 @@ func (f *File) _applyPendingModTime() error { return errors.New("Cannot apply ModTime, file object is not available") } + // set the time of the file in the cache + if f.d.vfs.Opt.CacheMode != CacheModeOff { + f.d.vfs.cache.setModTime(f._path(), f.pendingModTime) + } + + // set the time of the object err := f.o.SetModTime(context.TODO(), f.pendingModTime) switch err { case nil: diff --git a/vfs/read_write_test.go b/vfs/read_write_test.go index ef4a6ec48..13555db1b 100644 --- a/vfs/read_write_test.go +++ b/vfs/read_write_test.go @@ -2,6 +2,7 @@ package vfs import ( "context" + "fmt" "io" "io/ioutil" "os" @@ -687,7 +688,12 @@ func TestRWFileModTimeWithOpenWriters(t *testing.T) { err = fh.Node().SetModTime(mtime) require.NoError(t, err) - err = fh.Close() + // Using Flush/Release to mimic mount instead of Close + + err = fh.Flush() + require.NoError(t, err) + + err = fh.Release() require.NoError(t, err) info, err := vfs.Stat("file1") @@ -695,8 +701,11 @@ func TestRWFileModTimeWithOpenWriters(t *testing.T) { if r.Fremote.Precision() != fs.ModTimeNotSupported { // avoid errors because of timezone differences - assert.Equal(t, info.ModTime().Unix(), mtime.Unix()) + assert.Equal(t, info.ModTime().Unix(), mtime.Unix(), fmt.Sprintf("Time mismatch: %v != %v", info.ModTime(), mtime)) } + + file1 := fstest.NewItem("file1", "hi", mtime) + fstest.CheckItems(t, r.Fremote, file1) } func TestRWCacheRename(t *testing.T) {