chunker,compress,crypt,hasher,union: fix rclone move a file over itself deleting the file

This fixes the Root() returned by the backend when it has returned
fs.ErrorIsFile.

Before this change it returned a root which included the file path.

Because Root() was wrong this caused the detection of the file being
moved over itself check to fail.

This adds an integration test to check it for all backends.

See: https://forum.rclone.org/t/rclone-move-chunker-dir-file-chunker-dir-deletes-all-file-chunks/43333/
This commit is contained in:
Nick Craig-Wood 2023-12-08 14:00:22 +00:00
parent f98e672f37
commit c69eb84573
6 changed files with 55 additions and 0 deletions

View File

@ -325,6 +325,14 @@ func NewFs(ctx context.Context, name, rpath string, m configmap.Mapper) (fs.Fs,
} }
} }
// Correct root if definitely pointing to a file
if err == fs.ErrorIsFile {
f.root = path.Dir(f.root)
if f.root == "." || f.root == "/" {
f.root = ""
}
}
// Note 1: the features here are ones we could support, and they are // Note 1: the features here are ones we could support, and they are
// ANDed with the ones from wrappedFs. // ANDed with the ones from wrappedFs.
// Note 2: features.Fill() points features.PutStream to our PutStream, // Note 2: features.Fill() points features.PutStream to our PutStream,

View File

@ -14,6 +14,7 @@ import (
"fmt" "fmt"
"io" "io"
"os" "os"
"path"
"regexp" "regexp"
"strings" "strings"
"time" "time"
@ -172,6 +173,13 @@ func NewFs(ctx context.Context, name, rpath string, m configmap.Mapper) (fs.Fs,
opt: *opt, opt: *opt,
mode: compressionModeFromName(opt.CompressionMode), mode: compressionModeFromName(opt.CompressionMode),
} }
// Correct root if definitely pointing to a file
if err == fs.ErrorIsFile {
f.root = path.Dir(f.root)
if f.root == "." || f.root == "/" {
f.root = ""
}
}
// the features here are ones we could support, and they are // the features here are ones we could support, and they are
// ANDed with the ones from wrappedFs // ANDed with the ones from wrappedFs
f.features = (&fs.Features{ f.features = (&fs.Features{

View File

@ -253,6 +253,13 @@ func NewFs(ctx context.Context, name, rpath string, m configmap.Mapper) (fs.Fs,
cipher: cipher, cipher: cipher,
} }
cache.PinUntilFinalized(f.Fs, f) cache.PinUntilFinalized(f.Fs, f)
// Correct root if definitely pointing to a file
if err == fs.ErrorIsFile {
f.root = path.Dir(f.root)
if f.root == "." || f.root == "/" {
f.root = ""
}
}
// the features here are ones we could support, and they are // the features here are ones we could support, and they are
// ANDed with the ones from wrappedFs // ANDed with the ones from wrappedFs
f.features = (&fs.Features{ f.features = (&fs.Features{

View File

@ -114,6 +114,13 @@ func NewFs(ctx context.Context, fsname, rpath string, cmap configmap.Mapper) (fs
root: rpath, root: rpath,
opt: opt, opt: opt,
} }
// Correct root if definitely pointing to a file
if err == fs.ErrorIsFile {
f.root = path.Dir(f.root)
if f.root == "." || f.root == "/" {
f.root = ""
}
}
baseFeatures := baseFs.Features() baseFeatures := baseFs.Features()
f.fpTime = baseFs.Precision() != fs.ModTimeNotSupported f.fpTime = baseFs.Precision() != fs.ModTimeNotSupported

View File

@ -877,6 +877,13 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
opt: *opt, opt: *opt,
upstreams: usedUpstreams, upstreams: usedUpstreams,
} }
// Correct root if definitely pointing to a file
if fserr == fs.ErrorIsFile {
f.root = path.Dir(f.root)
if f.root == "." || f.root == "/" {
f.root = ""
}
}
err = upstream.Prepare(f.upstreams) err = upstream.Prepare(f.upstreams)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -1675,6 +1675,24 @@ func Run(t *testing.T, opt *Opt) {
require.NotNil(t, fileRemote) require.NotNil(t, fileRemote)
assert.Equal(t, fs.ErrorIsFile, err) assert.Equal(t, fs.ErrorIsFile, err)
// Check Fs.Root returns the right thing
t.Run("FsRoot", func(t *testing.T) {
skipIfNotOk(t)
got := fileRemote.Root()
remoteDir := path.Dir(remoteName)
want := remoteDir
colon := strings.LastIndex(want, ":")
if colon >= 0 {
want = want[colon+1:]
}
if isLocalRemote {
// only check last path element on local
require.Equal(t, filepath.Base(remoteDir), filepath.Base(got))
} else {
require.Equal(t, want, got)
}
})
if strings.HasPrefix(remoteName, "TestChunker") && strings.Contains(remoteName, "Nometa") { if strings.HasPrefix(remoteName, "TestChunker") && strings.Contains(remoteName, "Nometa") {
// TODO fix chunker and remove this bypass // TODO fix chunker and remove this bypass
t.Logf("Skip listing check -- chunker can't yet handle this tricky case") t.Logf("Skip listing check -- chunker can't yet handle this tricky case")