mirror of
https://github.com/rclone/rclone.git
synced 2026-07-09 15:25:14 -04:00
drive: detect shortcut loops to avoid infinite recursion
A folder shortcut pointing at one of its own ancestor folders (for example a shortcut to FolderX placed inside FolderX) made rclone recurse forever when dereferencing shortcuts, duplicating the folder contents until the disk was full. Rclone now detects when a folder shortcut targets an ancestor directory using the directory cache, leaves that shortcut out of the listing and logs an ERROR, so the rest of the drive can still be copied. Fixes #7118 Fixes #9565 Closes #9051
This commit is contained in:
@@ -2416,12 +2416,40 @@ func (f *Fs) resolveShortcut(ctx context.Context, item *drive.File) (newItem *dr
|
||||
return newItem, nil
|
||||
}
|
||||
|
||||
// shortcutLoop reports whether a folder shortcut at remote pointing to
|
||||
// the directory targetID would create a recursive loop.
|
||||
//
|
||||
// This is the case when targetID is the ID of remote's parent or any
|
||||
// other ancestor directory, as descending into it would list an
|
||||
// ancestor again forever. The ancestor IDs are looked up in the
|
||||
// directory cache which is always populated for the parents of remote
|
||||
// as they are listed before their children.
|
||||
func (f *Fs) shortcutLoop(remote, targetID string) bool {
|
||||
for dir := path.Dir(remote); ; dir = path.Dir(dir) {
|
||||
if dir == "." {
|
||||
dir = ""
|
||||
}
|
||||
if id, ok := f.dirCache.Get(dir); ok && actualID(id) == targetID {
|
||||
return true
|
||||
}
|
||||
if dir == "" {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// itemToDirEntry converts a drive.File to an fs.DirEntry.
|
||||
// When the drive.File cannot be represented as an fs.DirEntry
|
||||
// (nil, nil) is returned.
|
||||
func (f *Fs) itemToDirEntry(ctx context.Context, remote string, item *drive.File) (entry fs.DirEntry, err error) {
|
||||
switch {
|
||||
case item.MimeType == driveFolderType:
|
||||
// A folder shortcut pointing at one of its own ancestors
|
||||
// would make rclone recurse forever, so drop it.
|
||||
if isShortcutID(item.Id) && f.shortcutLoop(remote, actualID(item.Id)) {
|
||||
fs.Errorf(remote, "Ignoring folder shortcut as it points to an ancestor directory creating a loop")
|
||||
return nil, nil
|
||||
}
|
||||
// cache the directory ID for later lookups
|
||||
f.dirCache.Put(remote, item.Id)
|
||||
// cache the resource key for later lookups
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"github.com/rclone/rclone/fs/sync"
|
||||
"github.com/rclone/rclone/fstest"
|
||||
"github.com/rclone/rclone/fstest/fstests"
|
||||
"github.com/rclone/rclone/lib/dircache"
|
||||
"github.com/rclone/rclone/lib/random"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -145,6 +146,34 @@ func TestInternalFindExportFormat(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestShortcutLoop(t *testing.T) {
|
||||
f := &Fs{}
|
||||
f.dirCache = dircache.New("", "root", f)
|
||||
// root is FolderX with ID "X"
|
||||
f.dirCache.Put("", "X")
|
||||
// a real subfolder
|
||||
f.dirCache.Put("sub", "sub-id")
|
||||
// a nested subfolder reached via a shortcut, composite ID
|
||||
f.dirCache.Put("sub/nested", joinID("nested-id", "shortcut-id"))
|
||||
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
remote string
|
||||
targetID string
|
||||
want bool
|
||||
}{
|
||||
{"shortcut to the root itself", "loop", "X", true},
|
||||
{"shortcut to the parent", "sub/loop", "sub-id", true},
|
||||
{"shortcut to a grandparent", "sub/nested/loop", "X", true},
|
||||
{"shortcut to a composite ancestor", "sub/nested/loop", "nested-id", true},
|
||||
{"shortcut to an unrelated folder", "sub/loop", "other-id", false},
|
||||
{"shortcut to a sibling", "sub/loop", "sub-id2", false},
|
||||
} {
|
||||
got := f.shortcutLoop(test.remote, test.targetID)
|
||||
assert.Equal(t, test.want, got, test.name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMimeTypesToExtension(t *testing.T) {
|
||||
for mimeType, extension := range _mimeTypeToExtension {
|
||||
extensions, err := mime.ExtensionsByType(mimeType)
|
||||
|
||||
@@ -483,9 +483,10 @@ used to create shortcuts.
|
||||
Shortcuts can be completely ignored with the `--drive-skip-shortcuts` flag
|
||||
or the corresponding `skip_shortcuts` configuration setting.
|
||||
|
||||
If you have shortcuts that lead to an infinite recursion in your drive (e.g. a
|
||||
shortcut pointing to a parent folder), `skip_shortcuts` might be mandatory to
|
||||
be able to copy the drive.
|
||||
If you have a folder shortcut that points at one of its own parent folders it
|
||||
would lead to an infinite recursion. Rclone detects this, leaves the offending
|
||||
shortcut out of the listing and logs an ERROR, so the rest of the drive can
|
||||
still be copied.
|
||||
|
||||
### Emptying trash
|
||||
|
||||
|
||||
Reference in New Issue
Block a user