From: Jun Wu Date: Fri, 15 May 2026 00:14:12 +0000 (-0700) Subject: fuse: invalidate readdir cache on epoch bump X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0fa8346099b5794b9909989e3f1cb617e9d8d3fa;p=thirdparty%2Fkernel%2Flinux.git fuse: invalidate readdir cache on epoch bump FUSE_NOTIFY_INC_EPOCH invalidates dentries, but does not invalidate cached readdir results. A process with cwd inside a FUSE mount can therefore observe stale readdir(".") output after an epoch bump. Fix this by recording epoch in the readdir cache and checking it on reuse. Minimal reproducer: - mount a tiny FUSE fs with an empty root directory - on opendir, enable fi->cache_readdir and fi->keep_cache - chdir into the mount and call readdir(".") to populate readdir cache - make the FUSE server report one file in the root directory - send only FUSE_NOTIFY_INC_EPOCH - call readdir(".") again; before this change it stays stale, after this change it sees the new file Fixes: 2396356a945b ("fuse: add more control over cache invalidation behaviour") Signed-off-by: Jun Wu Reviewed-by: Joanne Koong Reviewed-by: Luis Henriques Signed-off-by: Miklos Szeredi --- diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 6455617b74a0c..85f738c531225 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -190,6 +190,11 @@ struct fuse_inode { */ struct timespec64 mtime; + /** + * @epoch: epoch of fc when cache was started + */ + int epoch; + /** * @iversion: iversion of directory when cache was * started diff --git a/fs/fuse/notify.c b/fs/fuse/notify.c index 7cd63502e124b..f93d6fa05bf72 100644 --- a/fs/fuse/notify.c +++ b/fs/fuse/notify.c @@ -348,9 +348,9 @@ static int fuse_notify_resend(struct fuse_conn *fc) } /* - * Increments the fuse connection epoch. This will result of dentries from - * previous epochs to be invalidated. Additionally, if inval_wq is set, a work - * queue is scheduled to trigger the invalidation. + * Increments the fuse connection epoch. This will cause dentries and + * readdir caches from previous epochs to be invalidated. Additionally, + * if inval_wq is set, a work queue is scheduled to trigger the invalidation. */ static int fuse_notify_inc_epoch(struct fuse_conn *fc) { diff --git a/fs/fuse/readdir.c b/fs/fuse/readdir.c index e993255584dfd..a94b00e13f6ee 100644 --- a/fs/fuse/readdir.c +++ b/fs/fuse/readdir.c @@ -489,6 +489,7 @@ static void fuse_rdc_reset(struct inode *inode) fi->rdc.version++; fi->rdc.size = 0; fi->rdc.pos = 0; + fi->rdc.epoch = 0; } #define UNCACHED 1 @@ -530,6 +531,7 @@ retry_locked: if (!ctx->pos && !fi->rdc.size) { fi->rdc.mtime = inode_get_mtime(inode); fi->rdc.iversion = inode_query_iversion(inode); + fi->rdc.epoch = atomic_read(&fc->epoch); } spin_unlock(&fi->rdc.lock); return UNCACHED; @@ -543,7 +545,8 @@ retry_locked: struct timespec64 mtime = inode_get_mtime(inode); if (inode_peek_iversion(inode) != fi->rdc.iversion || - !timespec64_equal(&fi->rdc.mtime, &mtime)) { + !timespec64_equal(&fi->rdc.mtime, &mtime) || + fi->rdc.epoch != atomic_read(&fc->epoch)) { fuse_rdc_reset(inode); goto retry_locked; }