[PATCH 2/2] 9p: invalidate readdir buffer on seek
From: Pierre Barre
Date: Tue May 12 2026 - 09:36:06 EST
The per-fid readdir buffer (fid->rdir) is populated lazily and only
refilled when fully drained (rdir->head == rdir->tail). userspace
lseek() on a directory fd updates file->f_pos via generic_file_llseek()
but does not touch the cached buffer, so the next getdents() iterates
the stale cache and emits entries from the previous position instead
of the one the caller asked for.
Track the file position the cached data corresponds to in
struct p9_rdir, and drop the cache on entry to iterate_shared when it
no longer matches ctx->pos. The 9p protocol's Tread/Treaddir already
take an arbitrary offset on every request, so a refill at the new
position is always legal; no .llseek override or seek restriction is
needed.
Reported-by: Pierre Barre <pierre@xxxxxxxx>
Link: https://lore.kernel.org/v9fs/496d10b9-40fe-4f81-8014-37497c37ff63@xxxxxxxxxxxxxxxx/
Signed-off-by: Pierre Barre <pierre@xxxxxxxx>
---
fs/9p/vfs_dir.c | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/fs/9p/vfs_dir.c b/fs/9p/vfs_dir.c
index e82f60c1c854..323f85352f6a 100644
--- a/fs/9p/vfs_dir.c
+++ b/fs/9p/vfs_dir.c
@@ -27,6 +27,7 @@
* struct p9_rdir - readdir accounting
* @head: start offset of current dirread buffer
* @tail: end offset of current dirread buffer
+ * @offset: file position the data at @head corresponds to
* @buf: dirread buffer
*
* private structure for keeping track of readdir
@@ -36,6 +37,7 @@
struct p9_rdir {
int head;
int tail;
+ loff_t offset;
uint8_t buf[];
};
@@ -102,6 +104,9 @@ static int v9fs_dir_readdir(struct file *file, struct dir_context *ctx)
kvec.iov_base = rdir->buf;
kvec.iov_len = buflen;
+ if (rdir->head < rdir->tail && rdir->offset != ctx->pos)
+ rdir->head = rdir->tail = 0;
+
while (1) {
if (rdir->tail == rdir->head) {
struct iov_iter to;
@@ -117,6 +122,7 @@ static int v9fs_dir_readdir(struct file *file, struct dir_context *ctx)
rdir->head = 0;
rdir->tail = n;
+ rdir->offset = ctx->pos;
}
while (rdir->head < rdir->tail) {
err = p9stat_read(fid->clnt, rdir->buf + rdir->head,
@@ -134,6 +140,7 @@ static int v9fs_dir_readdir(struct file *file, struct dir_context *ctx)
rdir->head += err;
ctx->pos += err;
+ rdir->offset = ctx->pos;
}
}
}
@@ -161,6 +168,9 @@ static int v9fs_dir_readdir_dotl(struct file *file, struct dir_context *ctx)
if (!rdir)
return -ENOMEM;
+ if (rdir->head < rdir->tail && rdir->offset != ctx->pos)
+ rdir->head = rdir->tail = 0;
+
while (1) {
if (rdir->tail == rdir->head) {
err = p9_client_readdir(fid, rdir->buf, buflen,
@@ -170,6 +180,7 @@ static int v9fs_dir_readdir_dotl(struct file *file, struct dir_context *ctx)
rdir->head = 0;
rdir->tail = err;
+ rdir->offset = ctx->pos;
}
while (rdir->head < rdir->tail) {
@@ -190,6 +201,7 @@ static int v9fs_dir_readdir_dotl(struct file *file, struct dir_context *ctx)
ctx->pos = curdirent.d_off;
rdir->head += err;
+ rdir->offset = ctx->pos;
}
}
}
--
2.51.0