vfs: fix virtual entries leak in removed directory

Deleting the contents of a directory and then the directory itself,
before a listing has confirmed the deletes, left the directory's
virtual entries behind. They are counted in every parent directory, and
a directory which counts virtual entries refuses to forget its cache, so
vfs/forget could not make changes made outside rclone visible.

This lasted until the removed directory's own cleanup timer purged its
entries, which happens 2 x --dir-cache-time later.

This fix drops the removed directory's virtual entries once it has been
removed.

Fixes #10083
This commit is contained in:
Biagio Peccerillo authored and Nick Craig-Wood committed 2026-10-15 17:39:54 +01:00
1 parent 36ce1a28fe
commit dcf3483513
2 files changed
+27

No files matched your search

+9
View File
@@ -1142,6 +1142,15 @@ func (d *Dir) Remove() error {
fs.Errorf(d, "Dir.Remove failed to remove directory: %v", err)
return err
}
// The directory is gone, so no listing will resolve its virtual
// entries, which can only be deletes as it was empty. They must be
// removed now, or they will be counted in every parent and
// cache forgets blocked as a consequence
d.mu.Lock()
for leaf := range d.virtual {
d._deleteVirtual(leaf)
}
d.mu.Unlock()
// Remove the item from the parent directory listing
if d.parent != nil {
d.parent.delObject(d.Name())
+18
View File
@@ -553,6 +553,24 @@ func TestDirRemove(t *testing.T) {
assert.Equal(t, EROFS, err)
}
// Removing a directory leaves no virtual entries counted in its parent, so the
// parent can still forget its cache.
func TestDirRemoveVirtuals(t *testing.T) {
_, vfs, dir, _ := dirCreate(t)
root, err := vfs.Root()
require.NoError(t, err)
// Remove the directory's contents and then the directory, before a
// listing has confirmed the contents are gone.
require.NoError(t, dir.RemoveName("file1"))
require.NoError(t, root.RemoveName("dir"))
assert.False(t, root.ForgetAll(), "root holds virtual entries after rmdir")
dir.ForgetAll()
assert.False(t, root.ForgetAll(), "root holds virtual entries after the removed dir is forgotten")
}
func TestDirRemoveAll(t *testing.T) {
r, vfs, dir, _ := dirCreate(t)