]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
fuse: invalidate readdir cache on epoch bump
authorJun Wu <quark@meta.com>
Fri, 15 May 2026 00:14:12 +0000 (17:14 -0700)
committerMiklos Szeredi <mszeredi@redhat.com>
Mon, 15 Jun 2026 12:06:20 +0000 (14:06 +0200)
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 <quark@meta.com>
Reviewed-by: Joanne Koong <joannelkoong@gmail.com>
Reviewed-by: Luis Henriques <luis@igalia.com>
Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
fs/fuse/fuse_i.h
fs/fuse/notify.c
fs/fuse/readdir.c

index 6455617b74a0c6741184dfb50385cbac13a67ff8..85f738c531225a08c508e29ebda005cf4bd3d60d 100644 (file)
@@ -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
index 7cd63502e124b2aa0dfdec7dbbd0d6bf53d60aad..f93d6fa05bf72d9d99c274b3524bc3e74f91cb5c 100644 (file)
@@ -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)
 {
index e993255584dfd8ab755155434bde08955f125fbc..a94b00e13f6eef59bfe7646473ea5daa771e4e30 100644 (file)
@@ -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;
                }