bisync/dropbox: fail closed on listing collapse and cursor races
build / lint (push) Canceled after 0s
build / android-all (push) Canceled after 0s
Build & Push Docker Images / Build Docker Image for linux/386 (push) Canceled after 0s
Build & Push Docker Images / Build Docker Image for linux/amd64 (push) Canceled after 0s
Build & Push Docker Images / Build Docker Image for linux/arm/v6 (push) Canceled after 0s
Build & Push Docker Images / Build Docker Image for linux/arm/v7 (push) Canceled after 0s
Build & Push Docker Images / Build Docker Image for linux/arm64 (push) Canceled after 0s
build / windows (push) Canceled after 0s
build / other_os (push) Canceled after 0s
build / mac_amd64 (push) Canceled after 0s
build / mac_arm64 (push) Canceled after 0s
build / linux (push) Canceled after 0s
build / go1.26 (push) Canceled after 0s
build / linux_386 (push) Canceled after 0s
Build & Push Docker Images / Merge & Push Final Docker Image (push) Canceled after 0s
build / lint (push) Canceled after 0s
build / android-all (push) Canceled after 0s
Build & Push Docker Images / Build Docker Image for linux/386 (push) Canceled after 0s
Build & Push Docker Images / Build Docker Image for linux/amd64 (push) Canceled after 0s
Build & Push Docker Images / Build Docker Image for linux/arm/v6 (push) Canceled after 0s
Build & Push Docker Images / Build Docker Image for linux/arm/v7 (push) Canceled after 0s
Build & Push Docker Images / Build Docker Image for linux/arm64 (push) Canceled after 0s
build / windows (push) Canceled after 0s
build / other_os (push) Canceled after 0s
build / mac_amd64 (push) Canceled after 0s
build / mac_arm64 (push) Canceled after 0s
build / linux (push) Canceled after 0s
build / go1.26 (push) Canceled after 0s
build / linux_386 (push) Canceled after 0s
Build & Push Docker Images / Merge & Push Final Docker Image (push) Canceled after 0s
Refuse --delta-list without --check-access. Abort if a listing goes empty or shrinks past --max-delete versus the prior snapshot (full relist and local walk included). Persist cursor before listing so a crash cannot pair a new listing with a stale cursor. Reconstruct delta remotes from path_lower plus leaf casing; ListR probes a non-recursive list when the recursive result is empty.
This commit is contained in:
@@ -1382,6 +1382,26 @@ func (f *Fs) ListR(ctx context.Context, dir string, callback fs.ListRCallback) (
|
||||
}
|
||||
}
|
||||
|
||||
if len(items) == 0 {
|
||||
arg := files.NewListFolderArg(f.opt.Enc.FromStandardPath(root))
|
||||
arg.Recursive = false
|
||||
arg.Limit = 1
|
||||
if root == "/" {
|
||||
arg.Path = ""
|
||||
}
|
||||
var probe *files.ListFolderResult
|
||||
err = f.pacer.Call(func() (bool, error) {
|
||||
probe, err = f.srv.ListFolderContext(ctx, arg)
|
||||
return shouldRetry(ctx, err)
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("dropbox ListR: empty recursive listing and probe failed: %w", err)
|
||||
}
|
||||
if probe != nil && len(probe.Entries) > 0 {
|
||||
return errors.New("dropbox ListR: recursive listing empty but directory has children; refusing incomplete listing")
|
||||
}
|
||||
}
|
||||
|
||||
skipPrefixes := make([]string, 0)
|
||||
for _, it := range items {
|
||||
if it.folder == nil || it.folder.SharingInfo == nil || it.folder.SharingInfo.SharedFolderId == "" {
|
||||
@@ -2630,30 +2650,45 @@ func (f *Fs) ListFolderDeltas(ctx context.Context, cursor string) (newCursor str
|
||||
|
||||
func (f *Fs) listingChangeFromEntry(entry files.IsMetadata) (ListingChange, bool) {
|
||||
var (
|
||||
ch ListingChange
|
||||
entryPath string
|
||||
ch ListingChange
|
||||
md *files.Metadata
|
||||
lower string
|
||||
)
|
||||
switch info := entry.(type) {
|
||||
case *files.FolderMetadata:
|
||||
ch.IsDir = true
|
||||
entryPath = info.PathDisplay
|
||||
md = &info.Metadata
|
||||
lower = info.PathLower
|
||||
case *files.FileMetadata:
|
||||
entryPath = info.PathDisplay
|
||||
md = &info.Metadata
|
||||
lower = info.PathLower
|
||||
ch.Size = int64(info.Size)
|
||||
ch.ModTime = time.Time(info.ClientModified)
|
||||
ch.Hash = info.ContentHash
|
||||
case *files.DeletedMetadata:
|
||||
ch.Deleted = true
|
||||
ch.IsDir = true
|
||||
entryPath = info.PathDisplay
|
||||
md = &info.Metadata
|
||||
lower = info.PathLower
|
||||
default:
|
||||
return ListingChange{}, false
|
||||
}
|
||||
entryPath = trimPrefixFold(entryPath, f.slashRootSlash)
|
||||
if entryPath == "" {
|
||||
if lower == "" {
|
||||
return ListingChange{}, false
|
||||
}
|
||||
ch.Remote = f.opt.Enc.ToStandardPath(entryPath)
|
||||
leaf := listRLeaf(md)
|
||||
rel := trimPrefixFold(lower, f.slashRootSlash)
|
||||
if rel == "" {
|
||||
return ListingChange{}, false
|
||||
}
|
||||
parent := path.Dir(rel)
|
||||
if parent == "." || parent == "/" {
|
||||
parent = ""
|
||||
}
|
||||
if parent == "" {
|
||||
ch.Remote = f.opt.Enc.ToStandardName(leaf)
|
||||
} else {
|
||||
ch.Remote = path.Join(f.opt.Enc.ToStandardPath(parent), f.opt.Enc.ToStandardName(leaf))
|
||||
}
|
||||
return ch, true
|
||||
}
|
||||
|
||||
|
||||
@@ -545,6 +545,7 @@ func TestListingChangeFromEntry(t *testing.T) {
|
||||
Metadata: files.Metadata{
|
||||
Name: "a.txt",
|
||||
PathDisplay: "/Dropbox/sub/a.txt",
|
||||
PathLower: "/dropbox/sub/a.txt",
|
||||
},
|
||||
Size: 42,
|
||||
ContentHash: "abc",
|
||||
@@ -561,6 +562,7 @@ func TestListingChangeFromEntry(t *testing.T) {
|
||||
Metadata: files.Metadata{
|
||||
Name: "sub",
|
||||
PathDisplay: "/Dropbox/sub",
|
||||
PathLower: "/dropbox/sub",
|
||||
},
|
||||
}
|
||||
ch, ok = f.listingChangeFromEntry(del)
|
||||
|
||||
Reference in New Issue
Block a user