[PATCH v1 02/12] Add and use d_for_each_positive_child family of iterators
NeilBrown <[email protected]> Mon, 3 Aug 2026 11:21:11 +1000
| Newsgroups | org.kernel.vger.ceph-devel,org.kernel.vger.autofs,org.kernel.vger.linux-fsdevel,org.kernel.vger.linux-kernel,org.kernel.vger.linux-nfs |
|---|---|
| Message-ID | <[email protected]> |
From: NeilBrown <[email protected]> Provide iterators for the d_children/d_sib lists. These iterators only report dentries that are positive (though they could be negative by the time they are used). As DCACHE_DENTRY_CURSOR dentries are never positive they are never reported. d_for_each_positive_child() takes the parent lock and sets the iterator to each positive child in turn. d_for_each_positive_child_continue() can continue after an existing dentry, or (when initialised to NULL) behave like d_for_each_positive_child(). This requires that something prevent the start dentry from being moved before d_for_each_positive_child_continue() can take the parent lock - typically ->i_rwsem. The use of scoped_guard() in these macros makes it safe to "break" or "goto" out of the loop - the lock will be dropped in that case. These are then used everywhere outside of dcache.c and libfs.c where d_children/d_sib iteration is needed. coda_flag_children() doesn't need rcu_read_lock() as holding any spinlock prevents an RCU critical section from finishing. nfs_clear_verifier_directory() is changed slightly so the parent lock is dropped and retaken between clearing the verifier on the parent and on all the children. I think this is safe. get_next_postive_dentry() in autofs now holds the parent lock a bit less. The only non-trivial code that is no locked any more is parent = p->d_parent This cannot need the lock as autofs doesn't support rename and a ref is held on a descendent of parent, so ->d_parent cannot change. And if it could, the lock wouldn't help. Signed-off-by: NeilBrown <[email protected]> --- fs/autofs/expire.c | 11 +------- fs/ceph/mds_client.c | 10 +++----- fs/coda/cache.c | 6 +---- fs/libfs.c | 4 +-- fs/nfs/dir.c | 7 ++--- fs/notify/fsnotify.c | 7 +---- include/linux/dcache.h | 58 ++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 67 insertions(+), 36 deletions(-) diff --git a/fs/autofs/expire.c b/fs/autofs/expire.c index 5c2d459e1e48..7b0a22629415 100644 --- a/fs/autofs/expire.c +++ b/fs/autofs/expire.c @@ -70,12 +70,9 @@ static int autofs_mount_busy(struct vfsmount *mnt, return status; } -/* p->d_lock held */ static struct dentry *positive_after(struct dentry *p, struct dentry *child) { - child = child ? d_next_sibling(child) : d_first_child(p); - - hlist_for_each_entry_from(child, d_sib) { + d_for_each_positive_child_continue(child, p) { spin_lock_nested(&child->d_lock, DENTRY_D_LOCK_NESTED); if (simple_positive(child)) { dget_dlock(child); @@ -98,9 +95,7 @@ static struct dentry *get_next_positive_subdir(struct dentry *prev, struct dentry *q; spin_lock(&sbi->lookup_lock); - spin_lock(&root->d_lock); q = positive_after(root, prev); - spin_unlock(&root->d_lock); spin_unlock(&sbi->lookup_lock); dput(prev); return q; @@ -119,7 +114,6 @@ static struct dentry *get_next_positive_dentry(struct dentry *prev, return dget(root); spin_lock(&sbi->lookup_lock); - spin_lock(&p->d_lock); while (1) { struct dentry *parent; @@ -127,12 +121,9 @@ static struct dentry *get_next_positive_dentry(struct dentry *prev, if (ret || p == root) break; parent = p->d_parent; - spin_unlock(&p->d_lock); - spin_lock(&parent->d_lock); d = p; p = parent; } - spin_unlock(&p->d_lock); spin_unlock(&sbi->lookup_lock); dput(prev); return ret; diff --git a/fs/ceph/mds_client.c b/fs/ceph/mds_client.c index 3c692ad02c85..5c4e72c3b110 100644 --- a/fs/ceph/mds_client.c +++ b/fs/ceph/mds_client.c @@ -2182,14 +2182,10 @@ static bool drop_negative_children(struct dentry *dentry) if (!d_is_dir(dentry)) goto out; - spin_lock(&dentry->d_lock); - hlist_for_each_entry(child, &dentry->d_children, d_sib) { - if (d_really_is_positive(child)) { - all_negative = false; - break; - } + d_for_each_positive_child(child, dentry) { + all_negative = false; + break; } - spin_unlock(&dentry->d_lock); if (all_negative) shrink_dcache_parent(dentry); diff --git a/fs/coda/cache.c b/fs/coda/cache.c index 245131296300..dca88d749b86 100644 --- a/fs/coda/cache.c +++ b/fs/coda/cache.c @@ -92,16 +92,12 @@ static void coda_flag_children(struct dentry *parent, int flag) { struct dentry *de; - spin_lock(&parent->d_lock); - rcu_read_lock(); - hlist_for_each_entry(de, &parent->d_children, d_sib) { + d_for_each_positive_child(de, parent) { struct inode *inode = d_inode_rcu(de); /* don't know what to do with negative dentries */ if (inode) coda_flag_inode(inode, flag); } - rcu_read_unlock(); - spin_unlock(&parent->d_lock); } void coda_flag_inode_children(struct inode *inode, int flag) diff --git a/fs/libfs.c b/fs/libfs.c index 5a0d276379d1..fc283e0a7c3a 100644 --- a/fs/libfs.c +++ b/fs/libfs.c @@ -768,8 +768,7 @@ int simple_empty(struct dentry *dentry) struct dentry *child; int ret = 0; - spin_lock(&dentry->d_lock); - hlist_for_each_entry(child, &dentry->d_children, d_sib) { + d_for_each_positive_child(child, dentry) { spin_lock_nested(&child->d_lock, DENTRY_D_LOCK_NESTED); if (simple_positive(child)) { spin_unlock(&child->d_lock); @@ -779,7 +778,6 @@ int simple_empty(struct dentry *dentry) } ret = 1; out: - spin_unlock(&dentry->d_lock); return ret; } EXPORT_SYMBOL(simple_empty); diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c index c7caffb31935..7a7419458c6a 100644 --- a/fs/nfs/dir.c +++ b/fs/nfs/dir.c @@ -1492,10 +1492,8 @@ static void nfs_clear_verifier_directory(struct inode *dir) spin_lock(&this_parent->d_lock); nfs_unset_verifier_delegated(&this_parent->d_time); - dentry = d_first_child(this_parent); - hlist_for_each_entry_from(dentry, d_sib) { - if (unlikely(dentry->d_flags & DCACHE_DENTRY_CURSOR)) - continue; + spin_unlock(&this_parent->d_lock); + d_for_each_positive_child(dentry, this_parent) { inode = d_inode_rcu(dentry); if (inode && NFS_PROTO(inode)->have_delegation(inode, FMODE_READ, 0)) @@ -1504,7 +1502,6 @@ static void nfs_clear_verifier_directory(struct inode *dir) nfs_unset_verifier_delegated(&dentry->d_time); spin_unlock(&dentry->d_lock); } - spin_unlock(&this_parent->d_lock); } /** diff --git a/fs/notify/fsnotify.c b/fs/notify/fsnotify.c index 7e2f330fd283..90a2121fc54a 100644 --- a/fs/notify/fsnotify.c +++ b/fs/notify/fsnotify.c @@ -85,16 +85,11 @@ void fsnotify_set_children_dentry_flags(struct inode *inode) /* run all of the children of the original inode and fix their * d_flags to indicate parental interest (their parent is the * original inode) */ - spin_lock(&alias->d_lock); - hlist_for_each_entry(child, &alias->d_children, d_sib) { - if (!child->d_inode) - continue; - + d_for_each_positive_child(child, alias) { spin_lock_nested(&child->d_lock, DENTRY_D_LOCK_NESTED); child->d_flags |= DCACHE_FSNOTIFY_PARENT_WATCHED; spin_unlock(&child->d_lock); } - spin_unlock(&alias->d_lock); } spin_unlock(&inode->i_lock); } diff --git a/include/linux/dcache.h b/include/linux/dcache.h index 64d64bab16fe..cabf05e74b1f 100644 --- a/include/linux/dcache.h +++ b/include/linux/dcache.h @@ -656,6 +656,64 @@ static inline struct dentry *d_next_sibling(const struct dentry *dentry) return hlist_entry_safe(dentry->d_sib.next, struct dentry, d_sib); } +static inline struct dentry *d_next_positive(struct dentry *child) +{ + do { + child = d_next_sibling(child); + } while (child && !d_really_is_positive(child)); + return child; +} + +static inline struct dentry *d_first_positive(const struct dentry *parent, + struct dentry *child) +{ + if (!child) + child = d_first_child(parent); + else + child = d_next_sibling(child); + if (child && !d_really_is_positive(child)) + child = d_next_positive(child); + return child; +} + +/** + * d_for_each_positive_child - iterate over positive children in the dcache + * @child: iterator dentry + * @parent: dentry of parent + * + * Iteratively set @child to each positive child of @parent. + * @parent->d_lock should NOT be held. + * @child may no longer be positive when the caller examines it + * so care is still needed which could involve locking the child + * or using d_inode_rcu() to access the inode. + * + * DCACHE_DENTRY_CURSOR dentries will never be returned, only true children + * which have at some point in the past been positive. + */ +#define d_for_each_positive_child(child, parent) \ + scoped_guard(spinlock, &parent->d_lock) \ + for (child = d_first_positive(parent, NULL); child; \ + child = d_next_positive(child)) + +/** + * d_for_each_positive_child_continue - iterate over remaining positive children + * @child: iterator dentry and starting point. + * @parent: dentry of parent + * + * If @child is %NULL this behaves identically to d_for_each_positive_child(). + * Otherwise @child must be an existing child of %parent and subsequent children + * in the d_children list of @parent are returned. + * + * Safely using this requires that something prevents @child from being + * renamed to a different directory before we get the lock. Holding + * i_rwsem on the @parent is sufficient. + * + */ +#define d_for_each_positive_child_continue(child, parent) \ + scoped_guard(spinlock, &parent->d_lock) \ + for (child = d_first_positive(parent, child); child; \ + child = d_next_positive(child)) + void set_default_d_op(struct super_block *, const struct dentry_operations *); struct dentry *d_make_persistent(struct dentry *, struct inode *); void d_make_discardable(struct dentry *dentry); -- 2.50.0.107.gf914562f5916.dirty