[PATCH RFC 01/12] nfs: update a server's capabilities in one step

From: Jonathan Flynn

Date: Thu Oct 29 2026 - 14:11:37 EST


When the client reads a server's capabilities again, on remount, on
migration, or when a later mount reuses a filesystem that is already
mounted, it clears a group of them and then sets again, one at a time,
each one the server still offers. Anything that tests one of them on
another CPU in between sees it missing: a case-insensitive server looks
case-sensitive, a server that supports security labels looks as if it
does not.

Build the new set first and store it once, as the NFSv3 path already
does.

This was found by an AI coding assistant (Claude Code) reviewing how
the client derives these capabilities, and has not been reproduced.

Signed-off-by: Jonathan Flynn <jonathan.flynn@xxxxxxxxxxxxxxx>
---
fs/nfs/nfs4proc.c | 28 ++++++++++++++++++----------
1 file changed, 18 insertions(+), 10 deletions(-)

diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c
index 22a2a4009d..113c199a6f 100644
--- a/fs/nfs/nfs4proc.c
+++ b/fs/nfs/nfs4proc.c
@@ -3896,6 +3896,7 @@ static bool nfs4_server_delegtime_capable(struct nfs4_server_caps_res *res)

static int _nfs4_server_capabilities(struct nfs_server *server, struct nfs_fh *fhandle)
{
+ unsigned int caps;
u32 minorversion = server->nfs_client->cl_minorversion;
u32 bitmask[3] = {
[0] = FATTR4_WORD0_SUPPORTED_ATTRS,
@@ -3953,7 +3954,13 @@ static int _nfs4_server_capabilities(struct nfs_server *server, struct nfs_fh *f
res.attr_bitmask[2];
}
memcpy(server->attr_bitmask, res.attr_bitmask, sizeof(server->attr_bitmask));
- server->caps &=
+ /*
+ * Build the new set in a local and store it once: a reader of
+ * server->caps during the probe, such as a remount or migration
+ * of a mounted filesystem, must not see these capabilities
+ * cleared and then set again one by one.
+ */
+ caps = server->caps &
~(NFS_CAP_ACLS | NFS_CAP_HARDLINKS | NFS_CAP_SYMLINKS |
NFS_CAP_SECURITY_LABEL | NFS_CAP_FS_LOCATIONS |
NFS_CAP_OPEN_XOR | NFS_CAP_DELEGTIME |
@@ -3961,22 +3968,22 @@ static int _nfs4_server_capabilities(struct nfs_server *server, struct nfs_fh *f
server->fattr_valid = NFS_ATTR_FATTR_V4;
if (res.attr_bitmask[0] & FATTR4_WORD0_ACL &&
res.acl_bitmask & ACL4_SUPPORT_ALLOW_ACL)
- server->caps |= NFS_CAP_ACLS;
+ caps |= NFS_CAP_ACLS;
if (res.has_links != 0)
- server->caps |= NFS_CAP_HARDLINKS;
+ caps |= NFS_CAP_HARDLINKS;
if (res.has_symlinks != 0)
- server->caps |= NFS_CAP_SYMLINKS;
+ caps |= NFS_CAP_SYMLINKS;
if (res.case_insensitive)
- server->caps |= NFS_CAP_CASE_INSENSITIVE;
+ caps |= NFS_CAP_CASE_INSENSITIVE;
if ((res.attr_bitmask[0] & FATTR4_WORD0_CASE_PRESERVING) &&
!res.case_preserving)
- server->caps |= NFS_CAP_CASE_NONPRESERVING;
+ caps |= NFS_CAP_CASE_NONPRESERVING;
#ifdef CONFIG_NFS_V4_SECURITY_LABEL
if (res.attr_bitmask[2] & FATTR4_WORD2_SECURITY_LABEL)
- server->caps |= NFS_CAP_SECURITY_LABEL;
+ caps |= NFS_CAP_SECURITY_LABEL;
#endif
if (res.attr_bitmask[0] & FATTR4_WORD0_FS_LOCATIONS)
- server->caps |= NFS_CAP_FS_LOCATIONS;
+ caps |= NFS_CAP_FS_LOCATIONS;
if (!(res.attr_bitmask[0] & FATTR4_WORD0_FILEID))
server->fattr_valid &= ~NFS_ATTR_FATTR_FILEID;
if (!(res.attr_bitmask[1] & FATTR4_WORD1_MODE))
@@ -4009,9 +4016,10 @@ static int _nfs4_server_capabilities(struct nfs_server *server, struct nfs_fh *f

if (res.open_caps.oa_share_access_want[0] &
NFS4_SHARE_WANT_OPEN_XOR_DELEGATION)
- server->caps |= NFS_CAP_OPEN_XOR;
+ caps |= NFS_CAP_OPEN_XOR;
if (nfs4_server_delegtime_capable(&res))
- server->caps |= NFS_CAP_DELEGTIME;
+ caps |= NFS_CAP_DELEGTIME;
+ WRITE_ONCE(server->caps, caps);

memcpy(server->cache_consistency_bitmask, res.attr_bitmask, sizeof(server->cache_consistency_bitmask));
server->cache_consistency_bitmask[0] &= FATTR4_WORD0_CHANGE|FATTR4_WORD0_SIZE;
--
2.43.0