From f3f743c3f98317b9958c4b051d39a2cc9c466d55 Mon Sep 17 00:00:00 2001 From: Nick Craig-Wood Date: Mon, 4 Mar 2024 09:47:48 +0000 Subject: [PATCH] vfs: fix download loop when file size shrunk Before this change, if a file shrunk in size on the remote then rclone could get into an loop trying to download the file forever. The symptom was repeating errors like this: vfs cache: restart download failed: failed to start downloader: failed to open downloader: vfs reader: failed to open source file: invalid seek position The fix was to check that file size in various places and makes sure that we weren't trying to download too much data. This was a problems with backends (like s3) which update the size of the object on Open to the actual size of the object. --- vfs/vfscache/downloaders/downloaders.go | 9 ++++++++- vfs/vfscache/item.go | 9 +++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/vfs/vfscache/downloaders/downloaders.go b/vfs/vfscache/downloaders/downloaders.go index b0dd6b508..2546afdd9 100644 --- a/vfs/vfscache/downloaders/downloaders.go +++ b/vfs/vfscache/downloaders/downloaders.go @@ -359,6 +359,10 @@ func (dls *Downloaders) _ensureDownloader(r ranges.Range) (err error) { if !startNew { return nil } + // Size can be 0 here if file shrinks - no need to download + if r.Size == 0 { + return nil + } // Downloader not found so start a new one _, err = dls._newDownloader(r) if err != nil { @@ -389,7 +393,10 @@ func (dls *Downloaders) _dispatchWaiters() { newWaiters := dls.waiters[:0] for _, waiter := range dls.waiters { - if dls.item.HasRange(waiter.r) { + // Clip the size against the actual size in case it has shrunk + r := waiter.r + r.Clip(dls.src.Size()) + if dls.item.HasRange(r) { waiter.errChan <- nil } else { newWaiters = append(newWaiters, waiter) diff --git a/vfs/vfscache/item.go b/vfs/vfscache/item.go index 34962a503..6cc68d1e5 100644 --- a/vfs/vfscache/item.go +++ b/vfs/vfscache/item.go @@ -1279,6 +1279,15 @@ func (item *Item) readAt(b []byte, off int64) (n int, err error) { return 0, err } + // Check to see if object has shrunk - if so don't read too much. + if item.o != nil && !item.info.Dirty && item.o.Size() != item.info.Size { + fs.Debugf(item.o, "Size has changed from %d to %d", item.info.Size, item.o.Size()) + err = item._truncate(item.o.Size()) + if err != nil { + return 0, err + } + } + item.info.ATime = time.Now() // Do the reading with Item.mu unlocked and cache protected by preAccess n, err = item.fd.ReadAt(b, off)