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 --- 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