[PATCH v4 6/6] nfs4.2: honor UNCACHEABLE_DIRENT_METADATA by refetching readdir
Mike Snitzer <[email protected]> Mon, 27 Jul 2026 17:09:43 -0400
| Newsgroups | org.kernel.vger.linux-nfs |
|---|---|
| Message-ID | <77b72759ad7beee334abfda36098cc310f6e96a5.1785140181.git.snitzer@kernel.org> |
Honor the per-directory UNCACHEABLE_DIRENT_METADATA attribute: when a directory is marked uncacheable, nfs_readdir() bypasses the readdir page cache and refetches directory-entry metadata from the server on every readdir, satisfying the always-refetch semantics the attribute requires (draft-ietf-nfsv4-uncacheable-directories Section 5.1). Rather than searching the cached readdir folios, nfs_readdir() forces the -EBADCOOKIE path so the request is served by uncached_readdir(); the dir_cookie == 0 (start-of-directory) case is included so the very first readdir of an uncacheable directory also goes to the server. A tracepoint records when an uncacheable directory bypasses the cache. The metadata the attribute governs is the per-entry size and timestamps, which are carried only by READDIRPLUS (a plain READDIR refreshes names but leaves the entries' attributes to the inode attribute caches). So also force READDIRPLUS for an uncacheable directory (when the server is capable): nfs_use_readdirplus() otherwise enables it only at the start of the directory or once cache usage crosses a threshold, and the cache- bypassing path above never accrues that usage -- which would leave continuation READDIRs of a large directory refreshing names but serving stale per-entry attributes. Forcing READDIRPLUS makes each READDIR refresh the entries' attribute caches (via nfs_prime_dcache() -> nfs_refresh_inode()), so a subsequent stat() observes current values. The attribute does not change NFSv4.2 change-attribute semantics: the client continues to use the directory change attribute for validation; this only suppresses serving READDIR responses from the local cache. Link: https://datatracker.ietf.org/doc/draft-ietf-nfsv4-uncacheable-directories/ Signed-off-by: Mike Snitzer <[email protected]> Assisted-by: Claude:claude-opus-4-8 --- fs/nfs/dir.c | 18 ++++++++++++++++-- fs/nfs/nfstrace.h | 1 + 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c index 6b07abf272b1..2162e93992c2 100644 --- a/fs/nfs/dir.c +++ b/fs/nfs/dir.c @@ -667,6 +667,14 @@ static bool nfs_use_readdirplus(struct inode *dir, struct dir_context *ctx, return false; if (NFS_SERVER(dir)->flags & NFS_MOUNT_FORCE_RDIRPLUS) return true; + /* + * An uncacheable directory must refetch directory-entry metadata + * (including per-entry size and timestamps) from the server on each + * READDIR; force READDIRPLUS so those attributes are refreshed on + * every call rather than left stale in the inode attribute caches. + */ + if (NFS_I(dir)->uncacheable_dirent_metadata) + return true; if (ctx->pos == 0 || cache_hits + cache_misses > NFS_READDIR_CACHE_USAGE_THRESHOLD) return true; @@ -1274,12 +1282,18 @@ static int nfs_readdir(struct file *file, struct dir_context *ctx) desc->clear_cache = force_clear; do { - res = readdir_search_pagecache(desc); + if (nfsi->uncacheable_dirent_metadata) { + res = -EBADCOOKIE; + trace_nfs_readdir_uncacheable_directory(inode); + } else { + res = readdir_search_pagecache(desc); + } if (res == -EBADCOOKIE) { res = 0; /* This means either end of directory */ - if (desc->dir_cookie && !desc->eof) { + if ((desc->dir_cookie || nfsi->uncacheable_dirent_metadata) && + !desc->eof) { /* Or that the server has 'lost' a cookie */ res = uncached_readdir(desc); if (res == 0) diff --git a/fs/nfs/nfstrace.h b/fs/nfs/nfstrace.h index b15c1732c869..a9930d59c610 100644 --- a/fs/nfs/nfstrace.h +++ b/fs/nfs/nfstrace.h @@ -181,6 +181,7 @@ DEFINE_NFS_INODE_EVENT_DONE(nfs_fsync_exit); DEFINE_NFS_INODE_EVENT(nfs_access_enter); DEFINE_NFS_INODE_EVENT_DONE(nfs_set_cache_invalid); DEFINE_NFS_INODE_EVENT(nfs_readdir_force_readdirplus); +DEFINE_NFS_INODE_EVENT(nfs_readdir_uncacheable_directory); DEFINE_NFS_INODE_EVENT_DONE(nfs_readdir_cache_fill_done); DEFINE_NFS_INODE_EVENT_DONE(nfs_readdir_uncached_done); -- 2.47.3