[PATCH v4 4/6] nfs4.2: add UNCACHEABLE_DIRENT_METADATA attribute support
Mike Snitzer <[email protected]> Mon, 27 Jul 2026 17:09:41 -0400
| Newsgroups | gmane.linux.nfs |
|---|---|
| Message-ID | <67fa3924952bb75d2c68af3844ee3a7e9b11a73c.1785140181.git.snitzer@kernel.org> |
Recognize the NFSv4.2 per-directory UNCACHEABLE_DIRENT_METADATA attribute (attr 88, draft-ietf-nfsv4-uncacheable-directories): decode it via GETATTR, track per-exported-filesystem support, and record on the inode whether a directory's directory-entry metadata must not be cached. Honoring the attribute (refetching directory-entry metadata from the server on each READDIR) is done by a subsequent change. If the NFSv4 server reports a directory's UNCACHEABLE_DIRENT_METADATA as true, it indicates the directory's directory-entry metadata must not be cached; the client records this in NFS_I(inode)->uncacheable_dirent_metadata for use by the readdir path. The UNCACHEABLE_DIRENT_METADATA attribute applies only to directory objects (NF4DIR) and is independent of the companion UNCACHEABLE_FILE_DATA attribute (attr 87); the two govern different aspects of client caching and may be used separately. A subsequent commit gates the client accordingly so the attribute is requested only for directories. Unlike the per-file UNCACHEABLE_FILE_DATA attribute -- which is requested lazily and kept coherent via an NFS_INO_INVALID_UNCACHEABLE_FILE_DATA cache-validity bit -- this directory attribute is deliberately not tied to the cache_validity / file-delegation machinery (nfs4_bitmap_copy_adjust()'s delegation block, nfs4_bitmask_set()) and needs no invalidation bit of its own. It is requested unconditionally for directories via the nfs4_bitmap_copy_adjust() type gate, so it is refetched on every directory GETATTR and cannot go stale. (A directory cannot hold an NFSv4 read/write delegation, and per the draft a server must recall or withhold a directory delegation while the attribute is set, so there is never an authoritative cached state to optimize against; an invalidation bit would only be useful alongside a lazy re-request path such as nfs4_bitmask_set(), which is not type-gated and would request a directory-only attribute on regular files -> NFS4ERR_INVAL.) A comment at the inode recording site documents this deliberate asymmetry. 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/inode.c | 15 +++++++++++++++ fs/nfs/nfs4proc.c | 4 +++- fs/nfs/nfs4trace.h | 3 ++- fs/nfs/nfs4xdr.c | 31 ++++++++++++++++++++++++++++++- include/linux/nfs4.h | 9 +++++++++ include/linux/nfs_fs.h | 1 + include/linux/nfs_xdr.h | 5 ++++- 7 files changed, 64 insertions(+), 4 deletions(-) diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c index bb6e58123341..215d96b3e059 100644 --- a/fs/nfs/inode.c +++ b/fs/nfs/inode.c @@ -508,6 +508,7 @@ nfs_fhget(struct super_block *sb, struct nfs_fh *fh, struct nfs_fattr *fattr) nfsi->write_io = 0; nfsi->read_io = 0; nfsi->uncacheable_file_data = false; + nfsi->uncacheable_dirent_metadata = false; nfsi->read_cache_jiffies = fattr->time_start; nfsi->attr_gencount = fattr->gencount; @@ -568,6 +569,16 @@ nfs_fhget(struct super_block *sb, struct nfs_fh *fh, struct nfs_fattr *fattr) else if (S_ISREG(inode->i_mode) && (fattr_supported & NFS_ATTR_FATTR_UNCACHEABLE_FILE_DATA)) nfs_set_cache_invalid(inode, NFS_INO_INVALID_UNCACHEABLE_FILE_DATA); + /* + * No invalidation bit for uncacheable_dirent_metadata: unlike + * uncacheable_file_data, attr 88 is requested unconditionally + * for directories via the nfs4_bitmap_copy_adjust() type gate, + * so it is refetched on every directory getattr and cannot go + * stale. + */ + if (fattr->valid & NFS_ATTR_FATTR_UNCACHEABLE_DIRENT_METADATA) + nfsi->uncacheable_dirent_metadata = + fattr->aux_flags & NFS_AUX_UNCACHEABLE_DIRENT_METADATA; nfs_setsecurity(inode, fattr); @@ -2479,6 +2490,10 @@ static int nfs_update_inode(struct inode *inode, struct nfs_fattr *fattr) nfsi->cache_validity |= save_cache_validity & NFS_INO_INVALID_UNCACHEABLE_FILE_DATA; + if (fattr->valid & NFS_ATTR_FATTR_UNCACHEABLE_DIRENT_METADATA) + nfsi->uncacheable_dirent_metadata = + fattr->aux_flags & NFS_AUX_UNCACHEABLE_DIRENT_METADATA; + /* Update attrtimeo value if we're out of the unstable period */ if (attr_changed) { nfs_inc_stats(inode, NFSIOS_ATTRINVALIDATE); diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c index 5e1dbdee65bb..35156c54dac3 100644 --- a/fs/nfs/nfs4proc.c +++ b/fs/nfs/nfs4proc.c @@ -3881,7 +3881,7 @@ static void nfs4_close_context(struct nfs_open_context *ctx, int is_sync) #define FATTR4_WORD1_NFS40_MASK (2*FATTR4_WORD1_MOUNTED_ON_FILEID - 1UL) #define FATTR4_WORD2_NFS41_MASK (2*FATTR4_WORD2_SUPPATTR_EXCLCREAT - 1UL) -#define FATTR4_WORD2_NFS42_MASK (2*FATTR4_WORD2_UNCACHEABLE_FILE_DATA - 1UL) +#define FATTR4_WORD2_NFS42_MASK (2*FATTR4_WORD2_UNCACHEABLE_DIRENT_METADATA - 1UL) #define FATTR4_WORD2_NFS42_TIME_DELEG_MASK \ (FATTR4_WORD2_TIME_DELEG_MODIFY|FATTR4_WORD2_TIME_DELEG_ACCESS) @@ -4007,6 +4007,8 @@ static int _nfs4_server_capabilities(struct nfs_server *server, struct nfs_fh *f server->attr_bitmask_nl[2] &= ~FATTR4_WORD2_SECURITY_LABEL; if (!(res.attr_bitmask[2] & FATTR4_WORD2_UNCACHEABLE_FILE_DATA)) server->fattr_valid &= ~NFS_ATTR_FATTR_UNCACHEABLE_FILE_DATA; + if (!(res.attr_bitmask[2] & FATTR4_WORD2_UNCACHEABLE_DIRENT_METADATA)) + server->fattr_valid &= ~NFS_ATTR_FATTR_UNCACHEABLE_DIRENT_METADATA; if (res.open_caps.oa_share_access_want[0] & NFS4_SHARE_WANT_OPEN_XOR_DELEGATION) diff --git a/fs/nfs/nfs4trace.h b/fs/nfs/nfs4trace.h index 3298dab34a78..868c201d024f 100644 --- a/fs/nfs/nfs4trace.h +++ b/fs/nfs/nfs4trace.h @@ -34,7 +34,8 @@ { NFS_ATTR_FATTR_OWNER_NAME, "OWNER_NAME" }, \ { NFS_ATTR_FATTR_GROUP_NAME, "GROUP_NAME" }, \ { NFS_ATTR_FATTR_BTIME, "BTIME" }, \ - { NFS_ATTR_FATTR_UNCACHEABLE_FILE_DATA, "UNCACHEABLE_FILE_DATA" }) + { NFS_ATTR_FATTR_UNCACHEABLE_FILE_DATA, "UNCACHEABLE_FILE_DATA" }, \ + { NFS_ATTR_FATTR_UNCACHEABLE_DIRENT_METADATA, "UNCACHEABLE_DIRENT_METADATA" }) DECLARE_EVENT_CLASS(nfs4_clientid_event, TP_PROTO( diff --git a/fs/nfs/nfs4xdr.c b/fs/nfs/nfs4xdr.c index fc049ce4ba8a..8329d5baf90e 100644 --- a/fs/nfs/nfs4xdr.c +++ b/fs/nfs/nfs4xdr.c @@ -121,7 +121,7 @@ static int decode_layoutget(struct xdr_stream *xdr, struct rpc_rqst *req, nfs4_owner_maxsz + \ nfs4_group_maxsz + nfs4_label_maxsz + \ decode_mdsthreshold_maxsz + \ - 1)) /* uncacheable_file_data */ + 1)) /* uncacheable_file_data / dirent_metadata */ #define nfs4_fattr_maxsz (nfs4_fattr_bitmap_maxsz + \ nfs4_fattr_value_maxsz) #define decode_getattr_maxsz (op_decode_hdr_maxsz + nfs4_fattr_maxsz) @@ -4405,6 +4405,30 @@ static int decode_attr_uncacheable_file_data(struct xdr_stream *xdr, uint32_t *b return status; } +static int decode_attr_uncacheable_dirent_metadata(struct xdr_stream *xdr, uint32_t *bitmap, + uint32_t *res, uint64_t *flags) +{ + int status = 0; + __be32 *p; + + if (unlikely(bitmap[2] & (FATTR4_WORD2_UNCACHEABLE_DIRENT_METADATA - 1U))) + return -EIO; + if (likely(bitmap[2] & FATTR4_WORD2_UNCACHEABLE_DIRENT_METADATA)) { + p = xdr_inline_decode(xdr, 4); + if (unlikely(!p)) + return -EIO; + if (be32_to_cpup(p)) + *res |= NFS_AUX_UNCACHEABLE_DIRENT_METADATA; + else + *res &= ~NFS_AUX_UNCACHEABLE_DIRENT_METADATA; + bitmap[2] &= ~FATTR4_WORD2_UNCACHEABLE_DIRENT_METADATA; + *flags |= NFS_ATTR_FATTR_UNCACHEABLE_DIRENT_METADATA; + } + dprintk("%s: uncacheable_dirent_metadata: =%s\n", __func__, + (*res & NFS_AUX_UNCACHEABLE_DIRENT_METADATA) == 0 ? "false" : "true"); + return status; +} + static int verify_attr_len(struct xdr_stream *xdr, unsigned int savep, uint32_t attrlen) { unsigned int attrwords = XDR_QUADLEN(attrlen); @@ -4875,6 +4899,11 @@ static int decode_getfattr_attrs(struct xdr_stream *xdr, uint32_t *bitmap, if (status < 0) goto xdr_error; + status = decode_attr_uncacheable_dirent_metadata(xdr, bitmap, &fattr->aux_flags, + &fattr->valid); + if (status < 0) + goto xdr_error; + status = 0; xdr_error: dprintk("%s: xdr returned %d\n", __func__, -status); diff --git a/include/linux/nfs4.h b/include/linux/nfs4.h index 1a3981c26b23..a30905cb4118 100644 --- a/include/linux/nfs4.h +++ b/include/linux/nfs4.h @@ -397,6 +397,14 @@ enum { FATTR4_UNCACHEABLE_FILE_DATA = 87, }; +/* + * Symbol name and value are from draft-ietf-nfsv4-uncacheable-directories + * Section 8. "XDR for Uncacheable Dirents Attribute" + */ +enum { + FATTR4_UNCACHEABLE_DIRENT_METADATA = 88, +}; + /* * The following internal definitions enable processing the above * attribute bits within 32-bit word boundaries. @@ -484,6 +492,7 @@ enum { #define FATTR4_WORD2_POSIX_DEFAULT_ACL BIT(FATTR4_POSIX_DEFAULT_ACL - 64) #define FATTR4_WORD2_POSIX_ACCESS_ACL BIT(FATTR4_POSIX_ACCESS_ACL - 64) #define FATTR4_WORD2_UNCACHEABLE_FILE_DATA BIT(FATTR4_UNCACHEABLE_FILE_DATA - 64) +#define FATTR4_WORD2_UNCACHEABLE_DIRENT_METADATA BIT(FATTR4_UNCACHEABLE_DIRENT_METADATA - 64) /* MDS threshold bitmap bits */ #define THRESHOLD_RD (1UL << 0) diff --git a/include/linux/nfs_fs.h b/include/linux/nfs_fs.h index 48b806aa3a2f..887b76c2a5dd 100644 --- a/include/linux/nfs_fs.h +++ b/include/linux/nfs_fs.h @@ -164,6 +164,7 @@ struct nfs_inode { struct timespec64 btime; bool uncacheable_file_data : 1; + bool uncacheable_dirent_metadata : 1; /* * read_cache_jiffies is when we started read-caching this inode. diff --git a/include/linux/nfs_xdr.h b/include/linux/nfs_xdr.h index 2e1987ac403d..2018cc3c9c31 100644 --- a/include/linux/nfs_xdr.h +++ b/include/linux/nfs_xdr.h @@ -19,6 +19,7 @@ /* aux_flags in nfs_fattr */ #define NFS_AUX_UNCACHEABLE_FILE_DATA BIT(0) +#define NFS_AUX_UNCACHEABLE_DIRENT_METADATA BIT(1) struct nfs4_string { unsigned int len; @@ -113,6 +114,7 @@ struct nfs_fattr { #define NFS_ATTR_FATTR_V4_SECURITY_LABEL BIT_ULL(25) #define NFS_ATTR_FATTR_BTIME BIT_ULL(26) #define NFS_ATTR_FATTR_UNCACHEABLE_FILE_DATA BIT_ULL(27) +#define NFS_ATTR_FATTR_UNCACHEABLE_DIRENT_METADATA BIT_ULL(28) #define NFS_ATTR_FATTR (NFS_ATTR_FATTR_TYPE \ | NFS_ATTR_FATTR_MODE \ @@ -135,7 +137,8 @@ struct nfs_fattr { | NFS_ATTR_FATTR_SPACE_USED \ | NFS_ATTR_FATTR_BTIME \ | NFS_ATTR_FATTR_V4_SECURITY_LABEL \ - | NFS_ATTR_FATTR_UNCACHEABLE_FILE_DATA) + | NFS_ATTR_FATTR_UNCACHEABLE_FILE_DATA \ + | NFS_ATTR_FATTR_UNCACHEABLE_DIRENT_METADATA) /* * Maximal number of supported layout drivers. -- 2.47.3