[PATCH v3 10/11] VFS: don't move dentries in d_sib list when they have the same parent

From: NeilBrown

Date: Tue Aug 25 2026 - 18:27:25 EST


From: NeilBrown <neil@xxxxxxxxxx>

When __d_move() moves or exchanges dentries it currently moves whichever
of those dentries that survive to the head of the ->d_children list of
the respective parent.

When they have the same parent, this simply moves them from where they
are to the start in the same list. So it achieves nothing useful.

A future patch will allow d_for_each_positive_child() to drop and retake
the parent's d_lock during the iteration. With the current __d_move
behaviour this would allow a dentry to be moved to the front and so
missed, even though it is still in the same directory. This might be
unexpected.

With this change the only dentries that d_for_each_positive_child()
might miss are those moved out of the directory, or those moved in after
the iteration started. These are unavoidable and should not be
unexpected.

Signed-off-by: NeilBrown <neil@xxxxxxxxxx>
---
fs/dcache.c | 20 ++++++++++++++------
1 file changed, 14 insertions(+), 6 deletions(-)

diff --git a/fs/dcache.c b/fs/dcache.c
index 241a686f7c5f..16b9ea6b0dba 100644
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -3057,6 +3057,9 @@ static void copy_name(struct dentry *dentry, struct dentry *target)
* entries should not be moved in this way. Caller must hold rename_lock, the
* i_rwsem of the source and target directories (exclusively), and the sb->
* s_vfs_rename_mutex if they differ. See lock_rename().
+ *
+ * If @dentry and @target have the same parent, then neither is
+ * moved in the d_sib list.
*/
static void __d_move(struct dentry *dentry, struct dentry *target,
bool exchange)
@@ -3124,15 +3127,20 @@ static void __d_move(struct dentry *dentry, struct dentry *target,
} else {
target->d_parent = old_parent;
swap_names(dentry, target);
- if (!hlist_unhashed(&target->d_sib))
- __hlist_del(&target->d_sib);
- hlist_add_head(&target->d_sib, &target->d_parent->d_children);
+ if (target->d_parent != dentry->d_parent) {
+ if (!hlist_unhashed(&target->d_sib))
+ __hlist_del(&target->d_sib);
+ hlist_add_head(&target->d_sib,
+ &target->d_parent->d_children);
+ }
__d_rehash(target);
fsnotify_update_flags(target);
}
- if (!hlist_unhashed(&dentry->d_sib))
- __hlist_del(&dentry->d_sib);
- hlist_add_head(&dentry->d_sib, &dentry->d_parent->d_children);
+ if (dentry->d_parent != old_parent) {
+ if (!hlist_unhashed(&dentry->d_sib))
+ __hlist_del(&dentry->d_sib);
+ hlist_add_head(&dentry->d_sib, &dentry->d_parent->d_children);
+ }

/*
* Adjust parent refcounts if either d_children ended up empty.
--
2.50.0.107.gf914562f5916.dirty