From: Shakeel Butt <shakeel.butt@linux.dev>
To: Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
Tejun Heo <tj@kernel.org>,
Christian Brauner <christian@brauner.io>
Cc: Sebastian Andrzej Siewior <bigeasy@linutronix.de>,
Meta kernel team <kernel-team@meta.com>,
linux-fsdevel@vger.kernel.org, driver-core@lists.linux.dev,
linux-kernel@vger.kernel.org
Subject: [PATCH 1/3] kernfs: don't repeat or skip an entry when readdir resumes
Date: Wed, 9 Sep 2026 17:36:48 -0700 [thread overview]
Message-ID: <20260910003650.1680854-2-shakeel.butt@linux.dev> (raw)
In-Reply-To: <20260910003650.1680854-1-shakeel.butt@linux.dev>
readdir keeps its place as a name hash in ctx->pos and pins the entry in
file->private_data. If that entry is gone when the listing comes back,
kernfs_dir_pos() searches the rbtree for the hash and keeps whatever
node the descent stopped on. That is the entry before or after the
missing one, depending on the shape of the tree.
Landing before it repeats an entry the previous getdents(2) call already
reported. Landing after it, kernfs_dir_next_pos() calls rb_next() and
steps over an unreported entry.
With children A(10), B(20), C(30):
report A, ctx->pos = 10
A removed
kernfs_dir_next_pos(10, A)
A is gone, the search for 10 stops at B
rb_next(B) -> C, so B is never reported
Only the repeat happens today, between two getdents(2) calls. The skip
needs the pinned entry to go away inside one call, which the next patch
allows when it drops kernfs_rwsem around dir_emit().
Before the commit 4e4d6d860b93 the descent kept a node only on the way
left, which is a search for the first entry at or after the hash. That
commit moved the assignment to the top of the loop, where it runs on
right turns too. Restore that search, and step forward only when the
pinned entry is still there rather than when the hash matches, since two
entries in one directory can share a hash.
While here, kernfs_dir_next_pos() called the hash @ino. It is never an
inode number, so name it @hash.
Fixes: 4e4d6d860b93 ("sysfs: Add s_hash to sysfs_dirent and order directory entries by hash")
Assisted-by: LLM
Signed-off-by: Shakeel Butt <shakeel.butt@linux.dev>
---
fs/kernfs/dir.c | 43 ++++++++++++++++++++++++++++---------------
1 file changed, 28 insertions(+), 15 deletions(-)
diff --git a/fs/kernfs/dir.c b/fs/kernfs/dir.c
index 07abf59f0264..24a927c85123 100644
--- a/fs/kernfs/dir.c
+++ b/fs/kernfs/dir.c
@@ -1903,9 +1903,16 @@ static int kernfs_dir_fop_release(struct inode *inode, struct file *filp)
return 0;
}
+/*
+ * Find where a listing left off. @resumed says whether @pos is still that
+ * entry; if not, the first entry at or after @hash is returned instead.
+ */
static struct kernfs_node *kernfs_dir_pos(const struct ns_common *ns,
- struct kernfs_node *parent, loff_t hash, struct kernfs_node *pos)
+ struct kernfs_node *parent, loff_t hash, struct kernfs_node *pos,
+ bool *resumed)
{
+ if (resumed)
+ *resumed = false;
if (pos) {
int valid = kernfs_active(pos) &&
rcu_access_pointer(pos->__parent) == parent &&
@@ -1913,23 +1920,26 @@ static struct kernfs_node *kernfs_dir_pos(const struct ns_common *ns,
kernfs_put(pos);
if (!valid)
pos = NULL;
+ else if (resumed)
+ *resumed = true;
}
if (!pos && (hash > 1) && (hash < INT_MAX)) {
struct rb_node *node = parent->dir.children.rb_node;
- u64 ns_id = kernfs_ns_id(ns);
+
+ /*
+ * Keep a node only on the way left, so the search ends on the
+ * first entry at or after @hash. The empty name sorts before
+ * every entry sharing the hash, so it lands on the first.
+ */
while (node) {
- pos = rb_to_kn(node);
+ struct kernfs_node *kn = rb_to_kn(node);
- if (hash < pos->hash)
+ if (kernfs_name_compare(hash, "", ns, kn) < 0) {
+ pos = kn;
node = node->rb_left;
- else if (hash > pos->hash)
+ } else {
node = node->rb_right;
- else if (ns_id < kernfs_ns_id(pos->ns))
- node = node->rb_left;
- else if (ns_id > kernfs_ns_id(pos->ns))
- node = node->rb_right;
- else
- break;
+ }
}
}
/* Skip over entries which are dying/dead or in the wrong namespace */
@@ -1945,10 +1955,13 @@ static struct kernfs_node *kernfs_dir_pos(const struct ns_common *ns,
}
static struct kernfs_node *kernfs_dir_next_pos(const struct ns_common *ns,
- struct kernfs_node *parent, ino_t ino, struct kernfs_node *pos)
+ struct kernfs_node *parent, loff_t hash, struct kernfs_node *pos)
{
- pos = kernfs_dir_pos(ns, parent, ino, pos);
- if (pos) {
+ bool resumed;
+
+ pos = kernfs_dir_pos(ns, parent, hash, pos, &resumed);
+ /* Step over @pos only if it survived; two entries can share a hash. */
+ if (pos && resumed) {
do {
struct rb_node *node = rb_next(&pos->rb);
if (!node)
@@ -1978,7 +1991,7 @@ static int kernfs_fop_readdir(struct file *file, struct dir_context *ctx)
if (kernfs_ns_enabled(parent))
ns = kernfs_info(dentry->d_sb)->ns;
- for (pos = kernfs_dir_pos(ns, parent, ctx->pos, pos);
+ for (pos = kernfs_dir_pos(ns, parent, ctx->pos, pos, NULL);
pos;
pos = kernfs_dir_next_pos(ns, parent, ctx->pos, pos)) {
const char *name = kernfs_rcu_name(pos);
--
2.53.0-Meta
next prev parent reply other threads:[~2026-09-10 0:37 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-10 0:36 [PATCH 0/3] kernfs: don't hold kernfs_rwsem across dir_emit() Shakeel Butt
2026-09-10 0:36 ` Shakeel Butt [this message]
2026-09-10 0:36 ` [PATCH 2/3] " Shakeel Butt
2026-09-10 0:36 ` [PATCH 3/3] selftests: cover readdir resuming at a removed entry Shakeel Butt
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260910003650.1680854-2-shakeel.butt@linux.dev \
--to=shakeel.butt@linux.dev \
--cc=bigeasy@linutronix.de \
--cc=christian@brauner.io \
--cc=driver-core@lists.linux.dev \
--cc=gregkh@linuxfoundation.org \
--cc=kernel-team@meta.com \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=tj@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®