diff mbox series

[v3,03/21] dentry: switch the lists of children to hlist

Message ID 20231124060422.576198-3-viro@zeniv.linux.org.uk (mailing list archive)
State New, archived
Headers show
Series [v3,01/21] switch nfsd_client_rmdir() to use of simple_recursive_removal() | expand

Commit Message

Al Viro Nov. 24, 2023, 6:04 a.m. UTC
Saves a pointer per struct dentry and actually makes the things less
clumsy.  Cleaned the d_walk() and dcache_readdir() a bit by use
of hlist_for_... iterators.

A couple of new helpers - d_first_child() and d_next_sibling(),
to make the expressions less awful.

X-fuck-kABI: gladly
Reviewed-by: Christian Brauner <brauner@kernel.org>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
---
 Documentation/filesystems/porting.rst     |  9 +++
 arch/powerpc/platforms/cell/spufs/inode.c |  5 +-
 fs/afs/dynroot.c                          |  5 +-
 fs/autofs/expire.c                        |  7 +--
 fs/ceph/dir.c                             |  2 +-
 fs/ceph/mds_client.c                      |  2 +-
 fs/coda/cache.c                           |  2 +-
 fs/dcache.c                               | 76 +++++++++++------------
 fs/libfs.c                                | 45 +++++++-------
 fs/notify/fsnotify.c                      |  2 +-
 fs/tracefs/inode.c                        | 34 +++++-----
 include/linux/dcache.h                    | 20 ++++--
 12 files changed, 108 insertions(+), 101 deletions(-)

Comments

Amir Goldstein Nov. 24, 2023, 7:44 a.m. UTC | #1
On Fri, Nov 24, 2023 at 8:05 AM Al Viro <viro@zeniv.linux.org.uk> wrote:
>
> Saves a pointer per struct dentry and actually makes the things less
> clumsy.  Cleaned the d_walk() and dcache_readdir() a bit by use
> of hlist_for_... iterators.
>
> A couple of new helpers - d_first_child() and d_next_sibling(),
> to make the expressions less awful.
>
> X-fuck-kABI: gladly

???

> Reviewed-by: Christian Brauner <brauner@kernel.org>
> Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
> ---
>  Documentation/filesystems/porting.rst     |  9 +++
>  arch/powerpc/platforms/cell/spufs/inode.c |  5 +-
>  fs/afs/dynroot.c                          |  5 +-
>  fs/autofs/expire.c                        |  7 +--
>  fs/ceph/dir.c                             |  2 +-
>  fs/ceph/mds_client.c                      |  2 +-
>  fs/coda/cache.c                           |  2 +-
>  fs/dcache.c                               | 76 +++++++++++------------
>  fs/libfs.c                                | 45 +++++++-------
>  fs/notify/fsnotify.c                      |  2 +-
>  fs/tracefs/inode.c                        | 34 +++++-----
>  include/linux/dcache.h                    | 20 ++++--
>  12 files changed, 108 insertions(+), 101 deletions(-)
>
> diff --git a/Documentation/filesystems/porting.rst b/Documentation/filesystems/porting.rst
> index 878e72b2f8b7..331405f4b29f 100644
> --- a/Documentation/filesystems/porting.rst
> +++ b/Documentation/filesystems/porting.rst
> @@ -1061,3 +1061,12 @@ export_operations ->encode_fh() no longer has a default implementation to
>  encode FILEID_INO32_GEN* file handles.
>  Filesystems that used the default implementation may use the generic helper
>  generic_encode_ino32_fh() explicitly.
> +
> +---
> +
> +**mandatory**
> +
> +The list of children anchored in parent dentry got turned into hlist now.
> +Field names got changed (->d_children/->d_sib instead of ->d_subdirs/->d_child
> +for anchor/entries resp.), so any affected places will be immediately caught
> +by compiler.
> diff --git a/arch/powerpc/platforms/cell/spufs/inode.c b/arch/powerpc/platforms/cell/spufs/inode.c
> index 10c1320adfd0..030de2b8c145 100644
> --- a/arch/powerpc/platforms/cell/spufs/inode.c
> +++ b/arch/powerpc/platforms/cell/spufs/inode.c
> @@ -145,10 +145,11 @@ spufs_evict_inode(struct inode *inode)
>
>  static void spufs_prune_dir(struct dentry *dir)
>  {
> -       struct dentry *dentry, *tmp;
> +       struct dentry *dentry;
> +       struct hlist_node *n;
>
>         inode_lock(d_inode(dir));
> -       list_for_each_entry_safe(dentry, tmp, &dir->d_subdirs, d_child) {
> +       hlist_for_each_entry_safe(dentry, n, &dir->d_children, d_sib) {
>                 spin_lock(&dentry->d_lock);
>                 if (simple_positive(dentry)) {
>                         dget_dlock(dentry);
> diff --git a/fs/afs/dynroot.c b/fs/afs/dynroot.c
> index 4d04ef2d3ae7..fe45462834cc 100644
> --- a/fs/afs/dynroot.c
> +++ b/fs/afs/dynroot.c
> @@ -370,7 +370,7 @@ int afs_dynroot_populate(struct super_block *sb)
>  void afs_dynroot_depopulate(struct super_block *sb)
>  {
>         struct afs_net *net = afs_sb2net(sb);
> -       struct dentry *root = sb->s_root, *subdir, *tmp;
> +       struct dentry *root = sb->s_root, *subdir;
>
>         /* Prevent more subdirs from being created */
>         mutex_lock(&net->proc_cells_lock);
> @@ -379,10 +379,11 @@ void afs_dynroot_depopulate(struct super_block *sb)
>         mutex_unlock(&net->proc_cells_lock);
>
>         if (root) {
> +               struct hlist_node *n;
>                 inode_lock(root->d_inode);
>
>                 /* Remove all the pins for dirs created for manually added cells */
> -               list_for_each_entry_safe(subdir, tmp, &root->d_subdirs, d_child) {
> +               hlist_for_each_entry_safe(subdir, n, &root->d_children, d_sib) {
>                         if (subdir->d_fsdata) {
>                                 subdir->d_fsdata = NULL;
>                                 dput(subdir);
> diff --git a/fs/autofs/expire.c b/fs/autofs/expire.c
> index 038b3d2d9f57..39d8c84c16f4 100644
> --- a/fs/autofs/expire.c
> +++ b/fs/autofs/expire.c
> @@ -73,12 +73,9 @@ static int autofs_mount_busy(struct vfsmount *mnt,
>  /* p->d_lock held */
>  static struct dentry *positive_after(struct dentry *p, struct dentry *child)
>  {
> -       if (child)
> -               child = list_next_entry(child, d_child);
> -       else
> -               child = list_first_entry(&p->d_subdirs, struct dentry, d_child);
> +       child = child ? d_next_sibling(child) : d_first_child(p);
>
> -       list_for_each_entry_from(child, &p->d_subdirs, d_child) {
> +       hlist_for_each_entry_from(child, d_sib) {
>                 spin_lock_nested(&child->d_lock, DENTRY_D_LOCK_NESTED);
>                 if (simple_positive(child)) {
>                         dget_dlock(child);
> diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c
> index 91709934c8b1..678596684596 100644
> --- a/fs/ceph/dir.c
> +++ b/fs/ceph/dir.c
> @@ -174,7 +174,7 @@ __dcache_find_get_entry(struct dentry *parent, u64 idx,
>  /*
>   * When possible, we try to satisfy a readdir by peeking at the
>   * dcache.  We make this work by carefully ordering dentries on
> - * d_child when we initially get results back from the MDS, and
> + * d_children when we initially get results back from the MDS, and
>   * falling back to a "normal" sync readdir if any dentries in the dir
>   * are dropped.
>   *
> diff --git a/fs/ceph/mds_client.c b/fs/ceph/mds_client.c
> index d95eb525519a..02ebfabfc8ee 100644
> --- a/fs/ceph/mds_client.c
> +++ b/fs/ceph/mds_client.c
> @@ -2128,7 +2128,7 @@ static bool drop_negative_children(struct dentry *dentry)
>                 goto out;
>
>         spin_lock(&dentry->d_lock);
> -       list_for_each_entry(child, &dentry->d_subdirs, d_child) {
> +       hlist_for_each_entry(child, &dentry->d_children, d_sib) {
>                 if (d_really_is_positive(child)) {
>                         all_negative = false;
>                         break;
> diff --git a/fs/coda/cache.c b/fs/coda/cache.c
> index bfbc03c6b632..f5b71a35f9db 100644
> --- a/fs/coda/cache.c
> +++ b/fs/coda/cache.c
> @@ -94,7 +94,7 @@ static void coda_flag_children(struct dentry *parent, int flag)
>
>         rcu_read_lock();
>         spin_lock(&parent->d_lock);
> -       list_for_each_entry(de, &parent->d_subdirs, d_child) {
> +       hlist_for_each_entry(de, &parent->d_children, d_sib) {
>                 struct inode *inode = d_inode_rcu(de);
>                 /* don't know what to do with negative dentries */
>                 if (inode)
> diff --git a/fs/dcache.c b/fs/dcache.c
> index c82ae731df9a..59f76c9a15d1 100644
> --- a/fs/dcache.c
> +++ b/fs/dcache.c
> @@ -51,8 +51,8 @@
>   *   - d_lru
>   *   - d_count
>   *   - d_unhashed()
> - *   - d_parent and d_subdirs
> - *   - childrens' d_child and d_parent
> + *   - d_parent and d_chilren
> + *   - childrens' d_sib and d_parent
>   *   - d_u.d_alias, d_inode
>   *
>   * Ordering:
> @@ -537,7 +537,7 @@ void d_drop(struct dentry *dentry)
>  }
>  EXPORT_SYMBOL(d_drop);
>
> -static inline void dentry_unlist(struct dentry *dentry, struct dentry *parent)
> +static inline void dentry_unlist(struct dentry *dentry)
>  {
>         struct dentry *next;
>         /*
> @@ -545,12 +545,12 @@ static inline void dentry_unlist(struct dentry *dentry, struct dentry *parent)
>          * attached to the dentry tree
>          */
>         dentry->d_flags |= DCACHE_DENTRY_KILLED;
> -       if (unlikely(list_empty(&dentry->d_child)))
> +       if (unlikely(hlist_unhashed(&dentry->d_sib)))
>                 return;
> -       __list_del_entry(&dentry->d_child);
> +       __hlist_del(&dentry->d_sib);
>         /*
>          * Cursors can move around the list of children.  While we'd been
> -        * a normal list member, it didn't matter - ->d_child.next would've
> +        * a normal list member, it didn't matter - ->d_sib.next would've
>          * been updated.  However, from now on it won't be and for the
>          * things like d_walk() it might end up with a nasty surprise.
>          * Normally d_walk() doesn't care about cursors moving around -
> @@ -558,20 +558,20 @@ static inline void dentry_unlist(struct dentry *dentry, struct dentry *parent)
>          * of its own, we get through it without ever unlocking the parent.
>          * There is one exception, though - if we ascend from a child that
>          * gets killed as soon as we unlock it, the next sibling is found
> -        * using the value left in its ->d_child.next.  And if _that_
> +        * using the value left in its ->d_sib.next.  And if _that_
>          * pointed to a cursor, and cursor got moved (e.g. by lseek())
>          * before d_walk() regains parent->d_lock, we'll end up skipping
>          * everything the cursor had been moved past.
>          *
> -        * Solution: make sure that the pointer left behind in ->d_child.next
> +        * Solution: make sure that the pointer left behind in ->d_sib.next
>          * points to something that won't be moving around.  I.e. skip the
>          * cursors.
>          */
> -       while (dentry->d_child.next != &parent->d_subdirs) {
> -               next = list_entry(dentry->d_child.next, struct dentry, d_child);
> +       while (dentry->d_sib.next) {
> +               next = hlist_entry(dentry->d_sib.next, struct dentry, d_sib);
>                 if (likely(!(next->d_flags & DCACHE_DENTRY_CURSOR)))
>                         break;
> -               dentry->d_child.next = next->d_child.next;
> +               dentry->d_sib.next = next->d_sib.next;
>         }
>  }
>
> @@ -600,7 +600,7 @@ static void __dentry_kill(struct dentry *dentry)
>         }
>         /* if it was on the hash then remove it */
>         __d_drop(dentry);
> -       dentry_unlist(dentry, parent);
> +       dentry_unlist(dentry);
>         if (parent)
>                 spin_unlock(&parent->d_lock);
>         if (dentry->d_inode)
> @@ -1348,8 +1348,7 @@ enum d_walk_ret {
>  static void d_walk(struct dentry *parent, void *data,
>                    enum d_walk_ret (*enter)(void *, struct dentry *))
>  {
> -       struct dentry *this_parent;
> -       struct list_head *next;
> +       struct dentry *this_parent, *dentry;
>         unsigned seq = 0;
>         enum d_walk_ret ret;
>         bool retry = true;
> @@ -1371,13 +1370,9 @@ static void d_walk(struct dentry *parent, void *data,
>                 break;
>         }
>  repeat:
> -       next = this_parent->d_subdirs.next;
> +       dentry = d_first_child(this_parent);
>  resume:
> -       while (next != &this_parent->d_subdirs) {
> -               struct list_head *tmp = next;
> -               struct dentry *dentry = list_entry(tmp, struct dentry, d_child);
> -               next = tmp->next;
> -
> +       hlist_for_each_entry_from(dentry, d_sib) {
>                 if (unlikely(dentry->d_flags & DCACHE_DENTRY_CURSOR))
>                         continue;
>
> @@ -1398,7 +1393,7 @@ static void d_walk(struct dentry *parent, void *data,
>                         continue;
>                 }
>
> -               if (!list_empty(&dentry->d_subdirs)) {
> +               if (!hlist_empty(&dentry->d_children)) {
>                         spin_unlock(&this_parent->d_lock);
>                         spin_release(&dentry->d_lock.dep_map, _RET_IP_);
>                         this_parent = dentry;
> @@ -1413,24 +1408,23 @@ static void d_walk(struct dentry *parent, void *data,
>         rcu_read_lock();
>  ascend:
>         if (this_parent != parent) {
> -               struct dentry *child = this_parent;
> -               this_parent = child->d_parent;
> +               dentry = this_parent;
> +               this_parent = dentry->d_parent;
>
> -               spin_unlock(&child->d_lock);
> +               spin_unlock(&dentry->d_lock);
>                 spin_lock(&this_parent->d_lock);
>
>                 /* might go back up the wrong parent if we have had a rename. */
>                 if (need_seqretry(&rename_lock, seq))
>                         goto rename_retry;
>                 /* go into the first sibling still alive */
> -               do {
> -                       next = child->d_child.next;
> -                       if (next == &this_parent->d_subdirs)
> -                               goto ascend;
> -                       child = list_entry(next, struct dentry, d_child);
> -               } while (unlikely(child->d_flags & DCACHE_DENTRY_KILLED));
> -               rcu_read_unlock();
> -               goto resume;
> +               hlist_for_each_entry_continue(dentry, d_sib) {
> +                       if (likely(!(dentry->d_flags & DCACHE_DENTRY_KILLED))) {
> +                               rcu_read_unlock();
> +                               goto resume;
> +                       }
> +               }
> +               goto ascend;
>         }
>         if (need_seqretry(&rename_lock, seq))
>                 goto rename_retry;
> @@ -1530,7 +1524,7 @@ int d_set_mounted(struct dentry *dentry)
>   * Search the dentry child list of the specified parent,
>   * and move any unused dentries to the end of the unused
>   * list for prune_dcache(). We descend to the next level
> - * whenever the d_subdirs list is non-empty and continue
> + * whenever the d_children list is non-empty and continue
>   * searching.
>   *
>   * It returns zero iff there are no unused children,
> @@ -1657,7 +1651,7 @@ EXPORT_SYMBOL(shrink_dcache_parent);
>  static enum d_walk_ret umount_check(void *_data, struct dentry *dentry)
>  {
>         /* it has busy descendents; complain about those instead */
> -       if (!list_empty(&dentry->d_subdirs))
> +       if (!hlist_empty(&dentry->d_children))
>                 return D_WALK_CONTINUE;
>
>         /* root with refcount 1 is fine */
> @@ -1814,9 +1808,9 @@ static struct dentry *__d_alloc(struct super_block *sb, const struct qstr *name)
>         dentry->d_fsdata = NULL;
>         INIT_HLIST_BL_NODE(&dentry->d_hash);
>         INIT_LIST_HEAD(&dentry->d_lru);
> -       INIT_LIST_HEAD(&dentry->d_subdirs);
> +       INIT_HLIST_HEAD(&dentry->d_children);
>         INIT_HLIST_NODE(&dentry->d_u.d_alias);
> -       INIT_LIST_HEAD(&dentry->d_child);
> +       INIT_HLIST_NODE(&dentry->d_sib);
>         d_set_d_op(dentry, dentry->d_sb->s_d_op);
>
>         if (dentry->d_op && dentry->d_op->d_init) {
> @@ -1855,7 +1849,7 @@ struct dentry *d_alloc(struct dentry * parent, const struct qstr *name)
>          */
>         __dget_dlock(parent);
>         dentry->d_parent = parent;
> -       list_add(&dentry->d_child, &parent->d_subdirs);
> +       hlist_add_head(&dentry->d_sib, &parent->d_children);
>         spin_unlock(&parent->d_lock);
>
>         return dentry;
> @@ -2993,11 +2987,15 @@ static void __d_move(struct dentry *dentry, struct dentry *target,
>         } else {
>                 target->d_parent = old_parent;
>                 swap_names(dentry, target);
> -               list_move(&target->d_child, &target->d_parent->d_subdirs);
> +               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);
>         }
> -       list_move(&dentry->d_child, &dentry->d_parent->d_subdirs);
> +       if (!hlist_unhashed(&dentry->d_sib))
> +               __hlist_del(&dentry->d_sib);
> +       hlist_add_head(&dentry->d_sib, &dentry->d_parent->d_children);
>         __d_rehash(dentry);
>         fsnotify_update_flags(dentry);
>         fscrypt_handle_d_move(dentry);
> diff --git a/fs/libfs.c b/fs/libfs.c
> index e9440d55073c..46c9177769c1 100644
> --- a/fs/libfs.c
> +++ b/fs/libfs.c
> @@ -104,15 +104,16 @@ EXPORT_SYMBOL(dcache_dir_close);
>   * If no such element exists, NULL is returned.
>   */
>  static struct dentry *scan_positives(struct dentry *cursor,
> -                                       struct list_head *p,
> +                                       struct hlist_node **p,
>                                         loff_t count,
>                                         struct dentry *last)
>  {
>         struct dentry *dentry = cursor->d_parent, *found = NULL;
>
>         spin_lock(&dentry->d_lock);
> -       while ((p = p->next) != &dentry->d_subdirs) {
> -               struct dentry *d = list_entry(p, struct dentry, d_child);
> +       while (*p) {
> +               struct dentry *d = hlist_entry(*p, struct dentry, d_sib);
> +               p = &d->d_sib.next;
>                 // we must at least skip cursors, to avoid livelocks
>                 if (d->d_flags & DCACHE_DENTRY_CURSOR)
>                         continue;
> @@ -126,8 +127,10 @@ static struct dentry *scan_positives(struct dentry *cursor,
>                         count = 1;
>                 }
>                 if (need_resched()) {
> -                       list_move(&cursor->d_child, p);
> -                       p = &cursor->d_child;
> +                       if (!hlist_unhashed(&cursor->d_sib))
> +                               __hlist_del(&cursor->d_sib);
> +                       hlist_add_behind(&cursor->d_sib, &d->d_sib);
> +                       p = &cursor->d_sib.next;
>                         spin_unlock(&dentry->d_lock);
>                         cond_resched();
>                         spin_lock(&dentry->d_lock);
> @@ -159,13 +162,12 @@ loff_t dcache_dir_lseek(struct file *file, loff_t offset, int whence)
>                 inode_lock_shared(dentry->d_inode);
>
>                 if (offset > 2)
> -                       to = scan_positives(cursor, &dentry->d_subdirs,
> +                       to = scan_positives(cursor, &dentry->d_children.first,
>                                             offset - 2, NULL);
>                 spin_lock(&dentry->d_lock);
> +               hlist_del_init(&cursor->d_sib);
>                 if (to)
> -                       list_move(&cursor->d_child, &to->d_child);
> -               else
> -                       list_del_init(&cursor->d_child);
> +                       hlist_add_behind(&cursor->d_sib, &to->d_sib);
>                 spin_unlock(&dentry->d_lock);
>                 dput(to);
>
> @@ -187,19 +189,16 @@ int dcache_readdir(struct file *file, struct dir_context *ctx)
>  {
>         struct dentry *dentry = file->f_path.dentry;
>         struct dentry *cursor = file->private_data;
> -       struct list_head *anchor = &dentry->d_subdirs;
>         struct dentry *next = NULL;
> -       struct list_head *p;
> +       struct hlist_node **p;
>
>         if (!dir_emit_dots(file, ctx))
>                 return 0;
>
>         if (ctx->pos == 2)
> -               p = anchor;
> -       else if (!list_empty(&cursor->d_child))
> -               p = &cursor->d_child;
> +               p = &dentry->d_children.first;
>         else
> -               return 0;
> +               p = &cursor->d_sib.next;
>
>         while ((next = scan_positives(cursor, p, 1, next)) != NULL) {
>                 if (!dir_emit(ctx, next->d_name.name, next->d_name.len,
> @@ -207,13 +206,12 @@ int dcache_readdir(struct file *file, struct dir_context *ctx)
>                               fs_umode_to_dtype(d_inode(next)->i_mode)))
>                         break;
>                 ctx->pos++;
> -               p = &next->d_child;
> +               p = &next->d_sib.next;
>         }
>         spin_lock(&dentry->d_lock);
> +       hlist_del_init(&cursor->d_sib);
>         if (next)
> -               list_move_tail(&cursor->d_child, &next->d_child);
> -       else
> -               list_del_init(&cursor->d_child);
> +               hlist_add_before(&cursor->d_sib, &next->d_sib);
>         spin_unlock(&dentry->d_lock);
>         dput(next);
>
> @@ -492,12 +490,11 @@ const struct file_operations simple_offset_dir_operations = {
>
>  static struct dentry *find_next_child(struct dentry *parent, struct dentry *prev)
>  {
> -       struct dentry *child = NULL;
> -       struct list_head *p = prev ? &prev->d_child : &parent->d_subdirs;
> +       struct dentry *child = NULL, *d;
>
>         spin_lock(&parent->d_lock);
> -       while ((p = p->next) != &parent->d_subdirs) {
> -               struct dentry *d = container_of(p, struct dentry, d_child);
> +       d = prev ? d_next_sibling(prev) : d_first_child(parent);
> +       hlist_for_each_entry_from(d, d_sib) {
>                 if (simple_positive(d)) {
>                         spin_lock_nested(&d->d_lock, DENTRY_D_LOCK_NESTED);
>                         if (simple_positive(d))
> @@ -658,7 +655,7 @@ int simple_empty(struct dentry *dentry)
>         int ret = 0;
>
>         spin_lock(&dentry->d_lock);
> -       list_for_each_entry(child, &dentry->d_subdirs, d_child) {
> +       hlist_for_each_entry(child, &dentry->d_children, d_sib) {
>                 spin_lock_nested(&child->d_lock, DENTRY_D_LOCK_NESTED);
>                 if (simple_positive(child)) {
>                         spin_unlock(&child->d_lock);
> diff --git a/fs/notify/fsnotify.c b/fs/notify/fsnotify.c
> index 7974e91ffe13..8bfd690e9f10 100644
> --- a/fs/notify/fsnotify.c
> +++ b/fs/notify/fsnotify.c
> @@ -124,7 +124,7 @@ void __fsnotify_update_child_dentry_flags(struct inode *inode)
>                  * d_flags to indicate parental interest (their parent is the
>                  * original inode) */
>                 spin_lock(&alias->d_lock);
> -               list_for_each_entry(child, &alias->d_subdirs, d_child) {
> +               hlist_for_each_entry(child, &alias->d_children, d_sib) {
>                         if (!child->d_inode)
>                                 continue;
>
> diff --git a/fs/tracefs/inode.c b/fs/tracefs/inode.c
> index 5b54948514fe..61ca5fcf10f9 100644
> --- a/fs/tracefs/inode.c
> +++ b/fs/tracefs/inode.c
> @@ -199,26 +199,21 @@ static void change_gid(struct dentry *dentry, kgid_t gid)
>   */
>  static void set_gid(struct dentry *parent, kgid_t gid)
>  {
> -       struct dentry *this_parent;
> -       struct list_head *next;
> +       struct dentry *this_parent, *dentry;
>
>         this_parent = parent;
>         spin_lock(&this_parent->d_lock);
>
>         change_gid(this_parent, gid);
>  repeat:
> -       next = this_parent->d_subdirs.next;
> +       dentry = d_first_child(this_parent);
>  resume:
> -       while (next != &this_parent->d_subdirs) {
> -               struct list_head *tmp = next;
> -               struct dentry *dentry = list_entry(tmp, struct dentry, d_child);
> -               next = tmp->next;
> -
> +       hlist_for_each_entry_from(dentry, d_sib) {
>                 spin_lock_nested(&dentry->d_lock, DENTRY_D_LOCK_NESTED);
>
>                 change_gid(dentry, gid);
>
> -               if (!list_empty(&dentry->d_subdirs)) {
> +               if (!hlist_empty(&dentry->d_children)) {
>                         spin_unlock(&this_parent->d_lock);
>                         spin_release(&dentry->d_lock.dep_map, _RET_IP_);
>                         this_parent = dentry;
> @@ -233,21 +228,20 @@ static void set_gid(struct dentry *parent, kgid_t gid)
>         rcu_read_lock();
>  ascend:
>         if (this_parent != parent) {
> -               struct dentry *child = this_parent;
> -               this_parent = child->d_parent;
> +               dentry = this_parent;
> +               this_parent = dentry->d_parent;
>
> -               spin_unlock(&child->d_lock);
> +               spin_unlock(&dentry->d_lock);
>                 spin_lock(&this_parent->d_lock);
>
>                 /* go into the first sibling still alive */
> -               do {
> -                       next = child->d_child.next;
> -                       if (next == &this_parent->d_subdirs)
> -                               goto ascend;
> -                       child = list_entry(next, struct dentry, d_child);
> -               } while (unlikely(child->d_flags & DCACHE_DENTRY_KILLED));
> -               rcu_read_unlock();
> -               goto resume;
> +               hlist_for_each_entry_continue(dentry, d_sib) {
> +                       if (likely(!(dentry->d_flags & DCACHE_DENTRY_KILLED))) {
> +                               rcu_read_unlock();
> +                               goto resume;
> +                       }
> +               }
> +               goto ascend;
>         }
>         rcu_read_unlock();
>         spin_unlock(&this_parent->d_lock);
> diff --git a/include/linux/dcache.h b/include/linux/dcache.h
> index 3da2f0545d5d..0e397a0c519c 100644
> --- a/include/linux/dcache.h
> +++ b/include/linux/dcache.h
> @@ -68,12 +68,12 @@ extern const struct qstr dotdot_name;
>   * large memory footprint increase).
>   */
>  #ifdef CONFIG_64BIT
> -# define DNAME_INLINE_LEN 32 /* 192 bytes */
> +# define DNAME_INLINE_LEN 40 /* 192 bytes */
>  #else
>  # ifdef CONFIG_SMP
> -#  define DNAME_INLINE_LEN 36 /* 128 bytes */
> -# else
>  #  define DNAME_INLINE_LEN 40 /* 128 bytes */
> +# else
> +#  define DNAME_INLINE_LEN 44 /* 128 bytes */
>  # endif
>  #endif
>
> @@ -101,8 +101,8 @@ struct dentry {
>                 struct list_head d_lru;         /* LRU list */
>                 wait_queue_head_t *d_wait;      /* in-lookup ones only */
>         };
> -       struct list_head d_child;       /* child of parent list */
> -       struct list_head d_subdirs;     /* our children */
> +       struct hlist_node d_sib;        /* child of parent list */
> +       struct hlist_head d_children;   /* our children */
>         /*
>          * d_alias and d_rcu can share memory
>          */
> @@ -600,4 +600,14 @@ struct name_snapshot {
>  void take_dentry_name_snapshot(struct name_snapshot *, struct dentry *);
>  void release_dentry_name_snapshot(struct name_snapshot *);
>
> +static inline struct dentry *d_first_child(const struct dentry *dentry)
> +{
> +       return hlist_entry_safe(dentry->d_children.first, struct dentry, d_sib);
> +}
> +
> +static inline struct dentry *d_next_sibling(const struct dentry *dentry)
> +{
> +       return hlist_entry_safe(dentry->d_sib.next, struct dentry, d_sib);
> +}
> +
>  #endif /* __LINUX_DCACHE_H */
> --
> 2.39.2
>
>
Al Viro Nov. 24, 2023, 7:55 a.m. UTC | #2
On Fri, Nov 24, 2023 at 09:44:27AM +0200, Amir Goldstein wrote:
> On Fri, Nov 24, 2023 at 8:05 AM Al Viro <viro@zeniv.linux.org.uk> wrote:
> >
> > Saves a pointer per struct dentry and actually makes the things less
> > clumsy.  Cleaned the d_walk() and dcache_readdir() a bit by use
> > of hlist_for_... iterators.
> >
> > A couple of new helpers - d_first_child() and d_next_sibling(),
> > to make the expressions less awful.
> >
> > X-fuck-kABI: gladly
> 
> ???

It breaks kABI.  Hard.  For a good reason.  And if you need an elaboration
of the reasons why kABI is, er, not universally liked - let's take it
to private email.  Some rants are really too unprintable for maillists...
Amir Goldstein Nov. 24, 2023, 8:02 a.m. UTC | #3
On Fri, Nov 24, 2023 at 9:55 AM Al Viro <viro@zeniv.linux.org.uk> wrote:
>
> On Fri, Nov 24, 2023 at 09:44:27AM +0200, Amir Goldstein wrote:
> > On Fri, Nov 24, 2023 at 8:05 AM Al Viro <viro@zeniv.linux.org.uk> wrote:
> > >
> > > Saves a pointer per struct dentry and actually makes the things less
> > > clumsy.  Cleaned the d_walk() and dcache_readdir() a bit by use
> > > of hlist_for_... iterators.
> > >
> > > A couple of new helpers - d_first_child() and d_next_sibling(),
> > > to make the expressions less awful.
> > >
> > > X-fuck-kABI: gladly
> >
> > ???
>
> It breaks kABI.  Hard.  For a good reason.  And if you need an elaboration
> of the reasons why kABI is, er, not universally liked - let's take it
> to private email.  Some rants are really too unprintable for maillists...

I don't mind rants, even quite fond of yours :)

But "X-fuck-kABI: gladly" serves no real value.
You'd already documented the kAPI change in porting.rst.
If you want to add a note about kABI, I suggest that you add it there.

Thanks,
Amir.
diff mbox series

Patch

diff --git a/Documentation/filesystems/porting.rst b/Documentation/filesystems/porting.rst
index 878e72b2f8b7..331405f4b29f 100644
--- a/Documentation/filesystems/porting.rst
+++ b/Documentation/filesystems/porting.rst
@@ -1061,3 +1061,12 @@  export_operations ->encode_fh() no longer has a default implementation to
 encode FILEID_INO32_GEN* file handles.
 Filesystems that used the default implementation may use the generic helper
 generic_encode_ino32_fh() explicitly.
+
+---
+
+**mandatory**
+
+The list of children anchored in parent dentry got turned into hlist now.
+Field names got changed (->d_children/->d_sib instead of ->d_subdirs/->d_child
+for anchor/entries resp.), so any affected places will be immediately caught
+by compiler.
diff --git a/arch/powerpc/platforms/cell/spufs/inode.c b/arch/powerpc/platforms/cell/spufs/inode.c
index 10c1320adfd0..030de2b8c145 100644
--- a/arch/powerpc/platforms/cell/spufs/inode.c
+++ b/arch/powerpc/platforms/cell/spufs/inode.c
@@ -145,10 +145,11 @@  spufs_evict_inode(struct inode *inode)
 
 static void spufs_prune_dir(struct dentry *dir)
 {
-	struct dentry *dentry, *tmp;
+	struct dentry *dentry;
+	struct hlist_node *n;
 
 	inode_lock(d_inode(dir));
-	list_for_each_entry_safe(dentry, tmp, &dir->d_subdirs, d_child) {
+	hlist_for_each_entry_safe(dentry, n, &dir->d_children, d_sib) {
 		spin_lock(&dentry->d_lock);
 		if (simple_positive(dentry)) {
 			dget_dlock(dentry);
diff --git a/fs/afs/dynroot.c b/fs/afs/dynroot.c
index 4d04ef2d3ae7..fe45462834cc 100644
--- a/fs/afs/dynroot.c
+++ b/fs/afs/dynroot.c
@@ -370,7 +370,7 @@  int afs_dynroot_populate(struct super_block *sb)
 void afs_dynroot_depopulate(struct super_block *sb)
 {
 	struct afs_net *net = afs_sb2net(sb);
-	struct dentry *root = sb->s_root, *subdir, *tmp;
+	struct dentry *root = sb->s_root, *subdir;
 
 	/* Prevent more subdirs from being created */
 	mutex_lock(&net->proc_cells_lock);
@@ -379,10 +379,11 @@  void afs_dynroot_depopulate(struct super_block *sb)
 	mutex_unlock(&net->proc_cells_lock);
 
 	if (root) {
+		struct hlist_node *n;
 		inode_lock(root->d_inode);
 
 		/* Remove all the pins for dirs created for manually added cells */
-		list_for_each_entry_safe(subdir, tmp, &root->d_subdirs, d_child) {
+		hlist_for_each_entry_safe(subdir, n, &root->d_children, d_sib) {
 			if (subdir->d_fsdata) {
 				subdir->d_fsdata = NULL;
 				dput(subdir);
diff --git a/fs/autofs/expire.c b/fs/autofs/expire.c
index 038b3d2d9f57..39d8c84c16f4 100644
--- a/fs/autofs/expire.c
+++ b/fs/autofs/expire.c
@@ -73,12 +73,9 @@  static int autofs_mount_busy(struct vfsmount *mnt,
 /* p->d_lock held */
 static struct dentry *positive_after(struct dentry *p, struct dentry *child)
 {
-	if (child)
-		child = list_next_entry(child, d_child);
-	else
-		child = list_first_entry(&p->d_subdirs, struct dentry, d_child);
+	child = child ? d_next_sibling(child) : d_first_child(p);
 
-	list_for_each_entry_from(child, &p->d_subdirs, d_child) {
+	hlist_for_each_entry_from(child, d_sib) {
 		spin_lock_nested(&child->d_lock, DENTRY_D_LOCK_NESTED);
 		if (simple_positive(child)) {
 			dget_dlock(child);
diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c
index 91709934c8b1..678596684596 100644
--- a/fs/ceph/dir.c
+++ b/fs/ceph/dir.c
@@ -174,7 +174,7 @@  __dcache_find_get_entry(struct dentry *parent, u64 idx,
 /*
  * When possible, we try to satisfy a readdir by peeking at the
  * dcache.  We make this work by carefully ordering dentries on
- * d_child when we initially get results back from the MDS, and
+ * d_children when we initially get results back from the MDS, and
  * falling back to a "normal" sync readdir if any dentries in the dir
  * are dropped.
  *
diff --git a/fs/ceph/mds_client.c b/fs/ceph/mds_client.c
index d95eb525519a..02ebfabfc8ee 100644
--- a/fs/ceph/mds_client.c
+++ b/fs/ceph/mds_client.c
@@ -2128,7 +2128,7 @@  static bool drop_negative_children(struct dentry *dentry)
 		goto out;
 
 	spin_lock(&dentry->d_lock);
-	list_for_each_entry(child, &dentry->d_subdirs, d_child) {
+	hlist_for_each_entry(child, &dentry->d_children, d_sib) {
 		if (d_really_is_positive(child)) {
 			all_negative = false;
 			break;
diff --git a/fs/coda/cache.c b/fs/coda/cache.c
index bfbc03c6b632..f5b71a35f9db 100644
--- a/fs/coda/cache.c
+++ b/fs/coda/cache.c
@@ -94,7 +94,7 @@  static void coda_flag_children(struct dentry *parent, int flag)
 
 	rcu_read_lock();
 	spin_lock(&parent->d_lock);
-	list_for_each_entry(de, &parent->d_subdirs, d_child) {
+	hlist_for_each_entry(de, &parent->d_children, d_sib) {
 		struct inode *inode = d_inode_rcu(de);
 		/* don't know what to do with negative dentries */
 		if (inode)
diff --git a/fs/dcache.c b/fs/dcache.c
index c82ae731df9a..59f76c9a15d1 100644
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -51,8 +51,8 @@ 
  *   - d_lru
  *   - d_count
  *   - d_unhashed()
- *   - d_parent and d_subdirs
- *   - childrens' d_child and d_parent
+ *   - d_parent and d_chilren
+ *   - childrens' d_sib and d_parent
  *   - d_u.d_alias, d_inode
  *
  * Ordering:
@@ -537,7 +537,7 @@  void d_drop(struct dentry *dentry)
 }
 EXPORT_SYMBOL(d_drop);
 
-static inline void dentry_unlist(struct dentry *dentry, struct dentry *parent)
+static inline void dentry_unlist(struct dentry *dentry)
 {
 	struct dentry *next;
 	/*
@@ -545,12 +545,12 @@  static inline void dentry_unlist(struct dentry *dentry, struct dentry *parent)
 	 * attached to the dentry tree
 	 */
 	dentry->d_flags |= DCACHE_DENTRY_KILLED;
-	if (unlikely(list_empty(&dentry->d_child)))
+	if (unlikely(hlist_unhashed(&dentry->d_sib)))
 		return;
-	__list_del_entry(&dentry->d_child);
+	__hlist_del(&dentry->d_sib);
 	/*
 	 * Cursors can move around the list of children.  While we'd been
-	 * a normal list member, it didn't matter - ->d_child.next would've
+	 * a normal list member, it didn't matter - ->d_sib.next would've
 	 * been updated.  However, from now on it won't be and for the
 	 * things like d_walk() it might end up with a nasty surprise.
 	 * Normally d_walk() doesn't care about cursors moving around -
@@ -558,20 +558,20 @@  static inline void dentry_unlist(struct dentry *dentry, struct dentry *parent)
 	 * of its own, we get through it without ever unlocking the parent.
 	 * There is one exception, though - if we ascend from a child that
 	 * gets killed as soon as we unlock it, the next sibling is found
-	 * using the value left in its ->d_child.next.  And if _that_
+	 * using the value left in its ->d_sib.next.  And if _that_
 	 * pointed to a cursor, and cursor got moved (e.g. by lseek())
 	 * before d_walk() regains parent->d_lock, we'll end up skipping
 	 * everything the cursor had been moved past.
 	 *
-	 * Solution: make sure that the pointer left behind in ->d_child.next
+	 * Solution: make sure that the pointer left behind in ->d_sib.next
 	 * points to something that won't be moving around.  I.e. skip the
 	 * cursors.
 	 */
-	while (dentry->d_child.next != &parent->d_subdirs) {
-		next = list_entry(dentry->d_child.next, struct dentry, d_child);
+	while (dentry->d_sib.next) {
+		next = hlist_entry(dentry->d_sib.next, struct dentry, d_sib);
 		if (likely(!(next->d_flags & DCACHE_DENTRY_CURSOR)))
 			break;
-		dentry->d_child.next = next->d_child.next;
+		dentry->d_sib.next = next->d_sib.next;
 	}
 }
 
@@ -600,7 +600,7 @@  static void __dentry_kill(struct dentry *dentry)
 	}
 	/* if it was on the hash then remove it */
 	__d_drop(dentry);
-	dentry_unlist(dentry, parent);
+	dentry_unlist(dentry);
 	if (parent)
 		spin_unlock(&parent->d_lock);
 	if (dentry->d_inode)
@@ -1348,8 +1348,7 @@  enum d_walk_ret {
 static void d_walk(struct dentry *parent, void *data,
 		   enum d_walk_ret (*enter)(void *, struct dentry *))
 {
-	struct dentry *this_parent;
-	struct list_head *next;
+	struct dentry *this_parent, *dentry;
 	unsigned seq = 0;
 	enum d_walk_ret ret;
 	bool retry = true;
@@ -1371,13 +1370,9 @@  static void d_walk(struct dentry *parent, void *data,
 		break;
 	}
 repeat:
-	next = this_parent->d_subdirs.next;
+	dentry = d_first_child(this_parent);
 resume:
-	while (next != &this_parent->d_subdirs) {
-		struct list_head *tmp = next;
-		struct dentry *dentry = list_entry(tmp, struct dentry, d_child);
-		next = tmp->next;
-
+	hlist_for_each_entry_from(dentry, d_sib) {
 		if (unlikely(dentry->d_flags & DCACHE_DENTRY_CURSOR))
 			continue;
 
@@ -1398,7 +1393,7 @@  static void d_walk(struct dentry *parent, void *data,
 			continue;
 		}
 
-		if (!list_empty(&dentry->d_subdirs)) {
+		if (!hlist_empty(&dentry->d_children)) {
 			spin_unlock(&this_parent->d_lock);
 			spin_release(&dentry->d_lock.dep_map, _RET_IP_);
 			this_parent = dentry;
@@ -1413,24 +1408,23 @@  static void d_walk(struct dentry *parent, void *data,
 	rcu_read_lock();
 ascend:
 	if (this_parent != parent) {
-		struct dentry *child = this_parent;
-		this_parent = child->d_parent;
+		dentry = this_parent;
+		this_parent = dentry->d_parent;
 
-		spin_unlock(&child->d_lock);
+		spin_unlock(&dentry->d_lock);
 		spin_lock(&this_parent->d_lock);
 
 		/* might go back up the wrong parent if we have had a rename. */
 		if (need_seqretry(&rename_lock, seq))
 			goto rename_retry;
 		/* go into the first sibling still alive */
-		do {
-			next = child->d_child.next;
-			if (next == &this_parent->d_subdirs)
-				goto ascend;
-			child = list_entry(next, struct dentry, d_child);
-		} while (unlikely(child->d_flags & DCACHE_DENTRY_KILLED));
-		rcu_read_unlock();
-		goto resume;
+		hlist_for_each_entry_continue(dentry, d_sib) {
+			if (likely(!(dentry->d_flags & DCACHE_DENTRY_KILLED))) {
+				rcu_read_unlock();
+				goto resume;
+			}
+		}
+		goto ascend;
 	}
 	if (need_seqretry(&rename_lock, seq))
 		goto rename_retry;
@@ -1530,7 +1524,7 @@  int d_set_mounted(struct dentry *dentry)
  * Search the dentry child list of the specified parent,
  * and move any unused dentries to the end of the unused
  * list for prune_dcache(). We descend to the next level
- * whenever the d_subdirs list is non-empty and continue
+ * whenever the d_children list is non-empty and continue
  * searching.
  *
  * It returns zero iff there are no unused children,
@@ -1657,7 +1651,7 @@  EXPORT_SYMBOL(shrink_dcache_parent);
 static enum d_walk_ret umount_check(void *_data, struct dentry *dentry)
 {
 	/* it has busy descendents; complain about those instead */
-	if (!list_empty(&dentry->d_subdirs))
+	if (!hlist_empty(&dentry->d_children))
 		return D_WALK_CONTINUE;
 
 	/* root with refcount 1 is fine */
@@ -1814,9 +1808,9 @@  static struct dentry *__d_alloc(struct super_block *sb, const struct qstr *name)
 	dentry->d_fsdata = NULL;
 	INIT_HLIST_BL_NODE(&dentry->d_hash);
 	INIT_LIST_HEAD(&dentry->d_lru);
-	INIT_LIST_HEAD(&dentry->d_subdirs);
+	INIT_HLIST_HEAD(&dentry->d_children);
 	INIT_HLIST_NODE(&dentry->d_u.d_alias);
-	INIT_LIST_HEAD(&dentry->d_child);
+	INIT_HLIST_NODE(&dentry->d_sib);
 	d_set_d_op(dentry, dentry->d_sb->s_d_op);
 
 	if (dentry->d_op && dentry->d_op->d_init) {
@@ -1855,7 +1849,7 @@  struct dentry *d_alloc(struct dentry * parent, const struct qstr *name)
 	 */
 	__dget_dlock(parent);
 	dentry->d_parent = parent;
-	list_add(&dentry->d_child, &parent->d_subdirs);
+	hlist_add_head(&dentry->d_sib, &parent->d_children);
 	spin_unlock(&parent->d_lock);
 
 	return dentry;
@@ -2993,11 +2987,15 @@  static void __d_move(struct dentry *dentry, struct dentry *target,
 	} else {
 		target->d_parent = old_parent;
 		swap_names(dentry, target);
-		list_move(&target->d_child, &target->d_parent->d_subdirs);
+		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);
 	}
-	list_move(&dentry->d_child, &dentry->d_parent->d_subdirs);
+	if (!hlist_unhashed(&dentry->d_sib))
+		__hlist_del(&dentry->d_sib);
+	hlist_add_head(&dentry->d_sib, &dentry->d_parent->d_children);
 	__d_rehash(dentry);
 	fsnotify_update_flags(dentry);
 	fscrypt_handle_d_move(dentry);
diff --git a/fs/libfs.c b/fs/libfs.c
index e9440d55073c..46c9177769c1 100644
--- a/fs/libfs.c
+++ b/fs/libfs.c
@@ -104,15 +104,16 @@  EXPORT_SYMBOL(dcache_dir_close);
  * If no such element exists, NULL is returned.
  */
 static struct dentry *scan_positives(struct dentry *cursor,
-					struct list_head *p,
+					struct hlist_node **p,
 					loff_t count,
 					struct dentry *last)
 {
 	struct dentry *dentry = cursor->d_parent, *found = NULL;
 
 	spin_lock(&dentry->d_lock);
-	while ((p = p->next) != &dentry->d_subdirs) {
-		struct dentry *d = list_entry(p, struct dentry, d_child);
+	while (*p) {
+		struct dentry *d = hlist_entry(*p, struct dentry, d_sib);
+		p = &d->d_sib.next;
 		// we must at least skip cursors, to avoid livelocks
 		if (d->d_flags & DCACHE_DENTRY_CURSOR)
 			continue;
@@ -126,8 +127,10 @@  static struct dentry *scan_positives(struct dentry *cursor,
 			count = 1;
 		}
 		if (need_resched()) {
-			list_move(&cursor->d_child, p);
-			p = &cursor->d_child;
+			if (!hlist_unhashed(&cursor->d_sib))
+				__hlist_del(&cursor->d_sib);
+			hlist_add_behind(&cursor->d_sib, &d->d_sib);
+			p = &cursor->d_sib.next;
 			spin_unlock(&dentry->d_lock);
 			cond_resched();
 			spin_lock(&dentry->d_lock);
@@ -159,13 +162,12 @@  loff_t dcache_dir_lseek(struct file *file, loff_t offset, int whence)
 		inode_lock_shared(dentry->d_inode);
 
 		if (offset > 2)
-			to = scan_positives(cursor, &dentry->d_subdirs,
+			to = scan_positives(cursor, &dentry->d_children.first,
 					    offset - 2, NULL);
 		spin_lock(&dentry->d_lock);
+		hlist_del_init(&cursor->d_sib);
 		if (to)
-			list_move(&cursor->d_child, &to->d_child);
-		else
-			list_del_init(&cursor->d_child);
+			hlist_add_behind(&cursor->d_sib, &to->d_sib);
 		spin_unlock(&dentry->d_lock);
 		dput(to);
 
@@ -187,19 +189,16 @@  int dcache_readdir(struct file *file, struct dir_context *ctx)
 {
 	struct dentry *dentry = file->f_path.dentry;
 	struct dentry *cursor = file->private_data;
-	struct list_head *anchor = &dentry->d_subdirs;
 	struct dentry *next = NULL;
-	struct list_head *p;
+	struct hlist_node **p;
 
 	if (!dir_emit_dots(file, ctx))
 		return 0;
 
 	if (ctx->pos == 2)
-		p = anchor;
-	else if (!list_empty(&cursor->d_child))
-		p = &cursor->d_child;
+		p = &dentry->d_children.first;
 	else
-		return 0;
+		p = &cursor->d_sib.next;
 
 	while ((next = scan_positives(cursor, p, 1, next)) != NULL) {
 		if (!dir_emit(ctx, next->d_name.name, next->d_name.len,
@@ -207,13 +206,12 @@  int dcache_readdir(struct file *file, struct dir_context *ctx)
 			      fs_umode_to_dtype(d_inode(next)->i_mode)))
 			break;
 		ctx->pos++;
-		p = &next->d_child;
+		p = &next->d_sib.next;
 	}
 	spin_lock(&dentry->d_lock);
+	hlist_del_init(&cursor->d_sib);
 	if (next)
-		list_move_tail(&cursor->d_child, &next->d_child);
-	else
-		list_del_init(&cursor->d_child);
+		hlist_add_before(&cursor->d_sib, &next->d_sib);
 	spin_unlock(&dentry->d_lock);
 	dput(next);
 
@@ -492,12 +490,11 @@  const struct file_operations simple_offset_dir_operations = {
 
 static struct dentry *find_next_child(struct dentry *parent, struct dentry *prev)
 {
-	struct dentry *child = NULL;
-	struct list_head *p = prev ? &prev->d_child : &parent->d_subdirs;
+	struct dentry *child = NULL, *d;
 
 	spin_lock(&parent->d_lock);
-	while ((p = p->next) != &parent->d_subdirs) {
-		struct dentry *d = container_of(p, struct dentry, d_child);
+	d = prev ? d_next_sibling(prev) : d_first_child(parent);
+	hlist_for_each_entry_from(d, d_sib) {
 		if (simple_positive(d)) {
 			spin_lock_nested(&d->d_lock, DENTRY_D_LOCK_NESTED);
 			if (simple_positive(d))
@@ -658,7 +655,7 @@  int simple_empty(struct dentry *dentry)
 	int ret = 0;
 
 	spin_lock(&dentry->d_lock);
-	list_for_each_entry(child, &dentry->d_subdirs, d_child) {
+	hlist_for_each_entry(child, &dentry->d_children, d_sib) {
 		spin_lock_nested(&child->d_lock, DENTRY_D_LOCK_NESTED);
 		if (simple_positive(child)) {
 			spin_unlock(&child->d_lock);
diff --git a/fs/notify/fsnotify.c b/fs/notify/fsnotify.c
index 7974e91ffe13..8bfd690e9f10 100644
--- a/fs/notify/fsnotify.c
+++ b/fs/notify/fsnotify.c
@@ -124,7 +124,7 @@  void __fsnotify_update_child_dentry_flags(struct inode *inode)
 		 * d_flags to indicate parental interest (their parent is the
 		 * original inode) */
 		spin_lock(&alias->d_lock);
-		list_for_each_entry(child, &alias->d_subdirs, d_child) {
+		hlist_for_each_entry(child, &alias->d_children, d_sib) {
 			if (!child->d_inode)
 				continue;
 
diff --git a/fs/tracefs/inode.c b/fs/tracefs/inode.c
index 5b54948514fe..61ca5fcf10f9 100644
--- a/fs/tracefs/inode.c
+++ b/fs/tracefs/inode.c
@@ -199,26 +199,21 @@  static void change_gid(struct dentry *dentry, kgid_t gid)
  */
 static void set_gid(struct dentry *parent, kgid_t gid)
 {
-	struct dentry *this_parent;
-	struct list_head *next;
+	struct dentry *this_parent, *dentry;
 
 	this_parent = parent;
 	spin_lock(&this_parent->d_lock);
 
 	change_gid(this_parent, gid);
 repeat:
-	next = this_parent->d_subdirs.next;
+	dentry = d_first_child(this_parent);
 resume:
-	while (next != &this_parent->d_subdirs) {
-		struct list_head *tmp = next;
-		struct dentry *dentry = list_entry(tmp, struct dentry, d_child);
-		next = tmp->next;
-
+	hlist_for_each_entry_from(dentry, d_sib) {
 		spin_lock_nested(&dentry->d_lock, DENTRY_D_LOCK_NESTED);
 
 		change_gid(dentry, gid);
 
-		if (!list_empty(&dentry->d_subdirs)) {
+		if (!hlist_empty(&dentry->d_children)) {
 			spin_unlock(&this_parent->d_lock);
 			spin_release(&dentry->d_lock.dep_map, _RET_IP_);
 			this_parent = dentry;
@@ -233,21 +228,20 @@  static void set_gid(struct dentry *parent, kgid_t gid)
 	rcu_read_lock();
 ascend:
 	if (this_parent != parent) {
-		struct dentry *child = this_parent;
-		this_parent = child->d_parent;
+		dentry = this_parent;
+		this_parent = dentry->d_parent;
 
-		spin_unlock(&child->d_lock);
+		spin_unlock(&dentry->d_lock);
 		spin_lock(&this_parent->d_lock);
 
 		/* go into the first sibling still alive */
-		do {
-			next = child->d_child.next;
-			if (next == &this_parent->d_subdirs)
-				goto ascend;
-			child = list_entry(next, struct dentry, d_child);
-		} while (unlikely(child->d_flags & DCACHE_DENTRY_KILLED));
-		rcu_read_unlock();
-		goto resume;
+		hlist_for_each_entry_continue(dentry, d_sib) {
+			if (likely(!(dentry->d_flags & DCACHE_DENTRY_KILLED))) {
+				rcu_read_unlock();
+				goto resume;
+			}
+		}
+		goto ascend;
 	}
 	rcu_read_unlock();
 	spin_unlock(&this_parent->d_lock);
diff --git a/include/linux/dcache.h b/include/linux/dcache.h
index 3da2f0545d5d..0e397a0c519c 100644
--- a/include/linux/dcache.h
+++ b/include/linux/dcache.h
@@ -68,12 +68,12 @@  extern const struct qstr dotdot_name;
  * large memory footprint increase).
  */
 #ifdef CONFIG_64BIT
-# define DNAME_INLINE_LEN 32 /* 192 bytes */
+# define DNAME_INLINE_LEN 40 /* 192 bytes */
 #else
 # ifdef CONFIG_SMP
-#  define DNAME_INLINE_LEN 36 /* 128 bytes */
-# else
 #  define DNAME_INLINE_LEN 40 /* 128 bytes */
+# else
+#  define DNAME_INLINE_LEN 44 /* 128 bytes */
 # endif
 #endif
 
@@ -101,8 +101,8 @@  struct dentry {
 		struct list_head d_lru;		/* LRU list */
 		wait_queue_head_t *d_wait;	/* in-lookup ones only */
 	};
-	struct list_head d_child;	/* child of parent list */
-	struct list_head d_subdirs;	/* our children */
+	struct hlist_node d_sib;	/* child of parent list */
+	struct hlist_head d_children;	/* our children */
 	/*
 	 * d_alias and d_rcu can share memory
 	 */
@@ -600,4 +600,14 @@  struct name_snapshot {
 void take_dentry_name_snapshot(struct name_snapshot *, struct dentry *);
 void release_dentry_name_snapshot(struct name_snapshot *);
 
+static inline struct dentry *d_first_child(const struct dentry *dentry)
+{
+	return hlist_entry_safe(dentry->d_children.first, struct dentry, d_sib);
+}
+
+static inline struct dentry *d_next_sibling(const struct dentry *dentry)
+{
+	return hlist_entry_safe(dentry->d_sib.next, struct dentry, d_sib);
+}
+
 #endif	/* __LINUX_DCACHE_H */