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

NeilBrown <[email protected]> Mon, 3 Aug 2026 11:21:19 +1000
Newsgroups gmane.comp.file-systems.ceph.devel,gmane.linux.kernel.autofs,gmane.linux.kernel,gmane.comp.file-systems.coda.general,gmane.linux.file-systems,gmane.linux.nfs
Message-ID <[email protected]>
From: NeilBrown <[email protected]>

When __d_move() moves or exchanges dentries it currently always moves
both dentries to the head of the ->d_children list of the respective
parents.

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.

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 a 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 <[email protected]>
---
 fs/dcache.c | 20 ++++++++++++++------
 1 file changed, 14 insertions(+), 6 deletions(-)

diff --git a/fs/dcache.c b/fs/dcache.c
index ae726f3ff0cb..50fbbcceca01 100644
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -3052,6 +3052,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)
@@ -3119,15 +3122,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