From 0cad7630425f4c9ee0dfa376ff8bf60c88ff2566 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Tue, 12 May 2026 12:12:42 -0400 Subject: [PATCH 01/42] nfs: store the full NFS fileid in inode->i_ino Now that inode->i_ino is a 64-bit value, store the full NFS fileid in it directly instead of an XOR-folded hash. This makes NFS_FILEID() and set_nfs_fileid() operate on inode->i_ino rather than the separate nfsi->fileid field. Since iget5_locked() and ilookup5() now accept a u64 hashval, pass the full fileid as the hash parameter directly. Convert direct nfsi->fileid accesses in nfs_check_inode_attributes(), nfs_update_inode(), and nfs_same_file() to use inode->i_ino. Assisted-by: Claude:claude-opus-4-6 Signed-off-by: Jeff Layton Signed-off-by: Anna Schumaker --- fs/nfs/dir.c | 2 +- fs/nfs/inode.c | 25 ++++++++++--------------- include/linux/nfs_fs.h | 4 ++-- 3 files changed, 13 insertions(+), 18 deletions(-) diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c index e9ce1883288c..5f8c3ea0bce3 100644 --- a/fs/nfs/dir.c +++ b/fs/nfs/dir.c @@ -650,7 +650,7 @@ int nfs_same_file(struct dentry *dentry, struct nfs_entry *entry) return 0; nfsi = NFS_I(inode); - if (entry->fattr->fileid != nfsi->fileid) + if (entry->fattr->fileid != inode->i_ino) return 0; if (entry->fh->size && nfs_compare_fh(entry->fh, &nfsi->fh) != 0) return 0; diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c index e26030e73696..dd9e378c36fb 100644 --- a/fs/nfs/inode.c +++ b/fs/nfs/inode.c @@ -66,10 +66,10 @@ static int nfs_update_inode(struct inode *, struct nfs_fattr *); static struct kmem_cache * nfs_inode_cachep; -static inline unsigned long +static inline u64 nfs_fattr_to_ino_t(struct nfs_fattr *fattr) { - return nfs_fileid_to_ino_t(fattr->fileid); + return fattr->fileid; } int nfs_wait_bit_killable(struct wait_bit_key *key, int mode) @@ -313,8 +313,7 @@ struct nfs_find_desc { }; /* - * In NFSv3 we can have 64bit inode numbers. In order to support - * this, and re-exported directories (also seen in NFSv2) + * For re-exported directories (also seen in NFSv2) * we are forced to allow 2 different inodes to have the same * i_ino. */ @@ -413,7 +412,7 @@ nfs_ilookup(struct super_block *sb, struct nfs_fattr *fattr, struct nfs_fh *fh) .fattr = fattr, }; struct inode *inode; - unsigned long hash; + u64 hash; if (!(fattr->valid & NFS_ATTR_FATTR_FILEID) || !(fattr->valid & NFS_ATTR_FATTR_TYPE)) @@ -456,7 +455,7 @@ nfs_fhget(struct super_block *sb, struct nfs_fh *fh, struct nfs_fattr *fattr) }; struct inode *inode = ERR_PTR(-ENOENT); u64 fattr_supported = NFS_SB(sb)->fattr_valid; - unsigned long hash; + u64 hash; nfs_attr_check_mountpoint(sb, fattr); @@ -479,10 +478,6 @@ nfs_fhget(struct super_block *sb, struct nfs_fh *fh, struct nfs_fattr *fattr) struct nfs_inode *nfsi = NFS_I(inode); unsigned long now = jiffies; - /* We set i_ino for the few things that still rely on it, - * such as stat(2) */ - inode->i_ino = hash; - /* We can't support update_atime(), since the server will reset it */ inode->i_flags |= S_NOATIME|S_NOCMTIME; inode->i_mode = fattr->mode; @@ -1672,10 +1667,10 @@ static int nfs_check_inode_attributes(struct inode *inode, struct nfs_fattr *fat if (fattr->valid & NFS_ATTR_FATTR_MOUNTED_ON_FILEID) return 0; /* Has the inode gone and changed behind our back? */ - } else if (nfsi->fileid != fattr->fileid) { + } else if (inode->i_ino != fattr->fileid) { /* Is this perhaps the mounted-on fileid? */ if ((fattr->valid & NFS_ATTR_FATTR_MOUNTED_ON_FILEID) && - nfsi->fileid == fattr->mounted_on_fileid) + inode->i_ino == fattr->mounted_on_fileid) return 0; return -ESTALE; } @@ -2262,15 +2257,15 @@ static int nfs_update_inode(struct inode *inode, struct nfs_fattr *fattr) if (fattr->valid & NFS_ATTR_FATTR_MOUNTED_ON_FILEID) return 0; /* Has the inode gone and changed behind our back? */ - } else if (nfsi->fileid != fattr->fileid) { + } else if (inode->i_ino != fattr->fileid) { /* Is this perhaps the mounted-on fileid? */ if ((fattr->valid & NFS_ATTR_FATTR_MOUNTED_ON_FILEID) && - nfsi->fileid == fattr->mounted_on_fileid) + inode->i_ino == fattr->mounted_on_fileid) return 0; printk(KERN_ERR "NFS: server %s error: fileid changed\n" "fsid %s: expected fileid 0x%Lx, got 0x%Lx\n", NFS_SERVER(inode)->nfs_client->cl_hostname, - inode->i_sb->s_id, (long long)nfsi->fileid, + inode->i_sb->s_id, (long long)inode->i_ino, (long long)fattr->fileid); goto out_err; } diff --git a/include/linux/nfs_fs.h b/include/linux/nfs_fs.h index 4623262da3c0..8e48053b3069 100644 --- a/include/linux/nfs_fs.h +++ b/include/linux/nfs_fs.h @@ -396,12 +396,12 @@ static inline int NFS_STALE(const struct inode *inode) static inline __u64 NFS_FILEID(const struct inode *inode) { - return NFS_I(inode)->fileid; + return inode->i_ino; } static inline void set_nfs_fileid(struct inode *inode, __u64 fileid) { - NFS_I(inode)->fileid = fileid; + inode->i_ino = fileid; } static inline void nfs_mark_for_revalidate(struct inode *inode) From 0e06a884f5ba6226829441bfc656ff9f5e9e90ac Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Tue, 12 May 2026 12:12:43 -0400 Subject: [PATCH 02/42] nfs: remove nfs_compat_user_ino64() and deprecate enable_ino64 Now that inode->i_ino stores the full 64-bit NFS fileid, the nfs_compat_user_ino64() function is no longer needed. generic_fillattr() already copies inode->i_ino into stat->ino, so the explicit override in nfs_getattr() is also redundant. Also remove the now-unused nfs_fileid_to_ino_t() and nfs_fattr_to_ino_t() helper functions that were used to XOR-fold 64-bit fileids into the old unsigned long i_ino. Keep the enable_ino64 module parameter as a deprecated stub that accepts but ignores the value, logging a notice when set. This avoids breaking existing configurations that pass nfs.enable_ino64 on the kernel command line or in modprobe.d. Assisted-by: Claude:claude-opus-4-6 Signed-off-by: Jeff Layton Signed-off-by: Anna Schumaker --- .../admin-guide/kernel-parameters.txt | 7 --- fs/nfs/dir.c | 2 +- fs/nfs/inode.c | 50 ++++++------------- include/linux/nfs_fs.h | 10 ---- 4 files changed, 15 insertions(+), 54 deletions(-) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 97007f4f69d4..2e6a6b8f1d8d 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -4290,13 +4290,6 @@ Kernel parameters Only applies if the softerr mount option is enabled, and the specified value is >= 0. - nfs.enable_ino64= - [NFS] enable 64-bit inode numbers. - If zero, the NFS client will fake up a 32-bit inode - number for the readdir() and stat() syscalls instead - of returning the full 64-bit number. - The default is to return 64-bit inode numbers. - nfs.idmap_cache_timeout= [NFS] set the maximum lifetime for idmapper cache entries. diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c index 5f8c3ea0bce3..659e39b1562b 100644 --- a/fs/nfs/dir.c +++ b/fs/nfs/dir.c @@ -1106,7 +1106,7 @@ static void nfs_do_filldir(struct nfs_readdir_descriptor *desc, ent = &array->array[i]; if (!dir_emit(desc->ctx, ent->name, ent->name_len, - nfs_compat_user_ino64(ent->ino), ent->d_type)) { + ent->ino, ent->d_type)) { desc->eob = true; break; } diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c index dd9e378c36fb..a21ed1c7f89d 100644 --- a/fs/nfs/inode.c +++ b/fs/nfs/inode.c @@ -57,21 +57,23 @@ #define NFSDBG_FACILITY NFSDBG_VFS -#define NFS_64_BIT_INODE_NUMBERS_ENABLED 1 +static bool enable_ino64; -/* Default is to see 64-bit inode numbers */ -static bool enable_ino64 = NFS_64_BIT_INODE_NUMBERS_ENABLED; +static int param_set_enable_ino64(const char *val, const struct kernel_param *kp) +{ + pr_notice("enable_ino64 is deprecated and has no effect\n"); + return 0; +} + +static const struct kernel_param_ops param_ops_enable_ino64 = { + .set = param_set_enable_ino64, + .get = param_get_bool, +}; static int nfs_update_inode(struct inode *, struct nfs_fattr *); static struct kmem_cache * nfs_inode_cachep; -static inline u64 -nfs_fattr_to_ino_t(struct nfs_fattr *fattr) -{ - return fattr->fileid; -} - int nfs_wait_bit_killable(struct wait_bit_key *key, int mode) { if (unlikely(nfs_current_task_exiting())) @@ -83,29 +85,6 @@ int nfs_wait_bit_killable(struct wait_bit_key *key, int mode) } EXPORT_SYMBOL_GPL(nfs_wait_bit_killable); -/** - * nfs_compat_user_ino64 - returns the user-visible inode number - * @fileid: 64-bit fileid - * - * This function returns a 32-bit inode number if the boot parameter - * nfs.enable_ino64 is zero. - */ -u64 nfs_compat_user_ino64(u64 fileid) -{ -#ifdef CONFIG_COMPAT - compat_ulong_t ino; -#else - unsigned long ino; -#endif - - if (enable_ino64) - return fileid; - ino = fileid; - if (sizeof(ino) < sizeof(fileid)) - ino ^= fileid >> (sizeof(fileid)-sizeof(ino)) * 8; - return ino; -} - int nfs_drop_inode(struct inode *inode) { return NFS_STALE(inode) || inode_generic_drop(inode); @@ -418,7 +397,7 @@ nfs_ilookup(struct super_block *sb, struct nfs_fattr *fattr, struct nfs_fh *fh) !(fattr->valid & NFS_ATTR_FATTR_TYPE)) return NULL; - hash = nfs_fattr_to_ino_t(fattr); + hash = fattr->fileid; inode = ilookup5(sb, hash, nfs_find_actor, &desc); dprintk("%s: returning %p\n", __func__, inode); @@ -466,7 +445,7 @@ nfs_fhget(struct super_block *sb, struct nfs_fh *fh, struct nfs_fattr *fattr) if ((fattr->valid & NFS_ATTR_FATTR_TYPE) == 0) goto out_no_inode; - hash = nfs_fattr_to_ino_t(fattr); + hash = fattr->fileid; inode = iget5_locked(sb, hash, nfs_find_actor, nfs_init_locked, &desc); if (inode == NULL) { @@ -1061,7 +1040,6 @@ int nfs_getattr(struct mnt_idmap *idmap, const struct path *path, stat->result_mask = nfs_get_valid_attrmask(inode) | request_mask; generic_fillattr(&nop_mnt_idmap, request_mask, inode, stat); - stat->ino = nfs_compat_user_ino64(NFS_FILEID(inode)); stat->change_cookie = inode_peek_iversion_raw(inode); stat->attributes_mask |= STATX_ATTR_CHANGE_MONOTONIC; if (server->change_attr_type != NFS4_CHANGE_TYPE_IS_UNDEFINED) @@ -2793,7 +2771,7 @@ static void __exit exit_nfs_fs(void) MODULE_AUTHOR("Olaf Kirch "); MODULE_DESCRIPTION("NFS client support"); MODULE_LICENSE("GPL"); -module_param(enable_ino64, bool, 0644); +module_param_cb(enable_ino64, ¶m_ops_enable_ino64, &enable_ino64, 0644); module_init(init_nfs_fs) module_exit(exit_nfs_fs) diff --git a/include/linux/nfs_fs.h b/include/linux/nfs_fs.h index 8e48053b3069..6d6fa62ede10 100644 --- a/include/linux/nfs_fs.h +++ b/include/linux/nfs_fs.h @@ -473,7 +473,6 @@ extern void nfs_file_set_open_context(struct file *filp, struct nfs_open_context extern void nfs_file_clear_open_context(struct file *flip); extern struct nfs_lock_context *nfs_get_lock_context(struct nfs_open_context *ctx); extern void nfs_put_lock_context(struct nfs_lock_context *l_ctx); -extern u64 nfs_compat_user_ino64(u64 fileid); extern void nfs_fattr_init(struct nfs_fattr *fattr); extern void nfs_fattr_set_barrier(struct nfs_fattr *fattr); extern unsigned long nfs_inc_attr_generation_counter(void); @@ -668,15 +667,6 @@ static inline loff_t nfs_size_to_loff_t(__u64 size) return min_t(u64, size, OFFSET_MAX); } -static inline ino_t -nfs_fileid_to_ino_t(u64 fileid) -{ - ino_t ino = (ino_t) fileid; - if (sizeof(ino_t) < sizeof(u64)) - ino ^= fileid >> (sizeof(u64)-sizeof(ino_t)) * 8; - return ino; -} - static inline void nfs_ooo_clear(struct nfs_inode *nfsi) { nfsi->cache_validity &= ~NFS_INO_DATA_INVAL_DEFER; From 2026c8a96afe6e5783497860c4af0a0e1a53991b Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Tue, 12 May 2026 12:12:44 -0400 Subject: [PATCH 03/42] nfs: replace NFS_FILEID() and nfsi->fileid with inode->i_ino Now that inode->i_ino stores the full 64-bit NFS fileid, replace all uses of NFS_FILEID(), set_nfs_fileid(), and direct nfsi->fileid accesses with inode->i_ino throughout the NFS client. Remove the NFS_FILEID() and set_nfs_fileid() helper functions from include/linux/nfs_fs.h since they are no longer needed. Also fix two pre-existing truncation bugs in nfs4trace.h where fileid trace fields were declared as u32 instead of u64. Assisted-by: Claude:claude-opus-4-6 Signed-off-by: Jeff Layton Signed-off-by: Anna Schumaker --- fs/nfs/export.c | 6 +- fs/nfs/filelayout/filelayout.c | 4 +- fs/nfs/flexfilelayout/flexfilelayout.c | 6 +- fs/nfs/inode.c | 16 ++--- fs/nfs/nfs4proc.c | 4 +- fs/nfs/nfs4trace.h | 79 +++++++++++------------- fs/nfs/nfstrace.h | 84 +++++++++++++------------- fs/nfs/pagelist.c | 2 +- fs/nfs/pnfs.c | 2 +- fs/nfs/unlink.c | 2 +- fs/nfs/write.c | 2 +- include/linux/nfs_fs.h | 10 --- 12 files changed, 99 insertions(+), 118 deletions(-) diff --git a/fs/nfs/export.c b/fs/nfs/export.c index a10dd5f9d078..8fb08bce0623 100644 --- a/fs/nfs/export.c +++ b/fs/nfs/export.c @@ -49,14 +49,14 @@ nfs_encode_fh(struct inode *inode, __u32 *p, int *max_len, struct inode *parent) return FILEID_INVALID; } - p[FILEID_HIGH_OFF] = NFS_FILEID(inode) >> 32; - p[FILEID_LOW_OFF] = NFS_FILEID(inode); + p[FILEID_HIGH_OFF] = inode->i_ino >> 32; + p[FILEID_LOW_OFF] = inode->i_ino; p[FILE_I_TYPE_OFF] = inode->i_mode & S_IFMT; p[len - 1] = 0; /* Padding */ nfs_copy_fh(clnt_fh, server_fh); *max_len = len; dprintk("%s: result fh fileid %llu mode %u size %d\n", - __func__, NFS_FILEID(inode), inode->i_mode, *max_len); + __func__, inode->i_ino, inode->i_mode, *max_len); return *max_len; } diff --git a/fs/nfs/filelayout/filelayout.c b/fs/nfs/filelayout/filelayout.c index e85380e3b11d..f0f53f5dc871 100644 --- a/fs/nfs/filelayout/filelayout.c +++ b/fs/nfs/filelayout/filelayout.c @@ -95,7 +95,7 @@ static void filelayout_reset_write(struct nfs_pgio_header *hdr) "(req %s/%llu, %u bytes @ offset %llu)\n", __func__, hdr->task.tk_pid, hdr->inode->i_sb->s_id, - (unsigned long long)NFS_FILEID(hdr->inode), + (unsigned long long)hdr->inode->i_ino, hdr->args.count, (unsigned long long)hdr->args.offset); @@ -112,7 +112,7 @@ static void filelayout_reset_read(struct nfs_pgio_header *hdr) "(req %s/%llu, %u bytes @ offset %llu)\n", __func__, hdr->task.tk_pid, hdr->inode->i_sb->s_id, - (unsigned long long)NFS_FILEID(hdr->inode), + (unsigned long long)hdr->inode->i_ino, hdr->args.count, (unsigned long long)hdr->args.offset); diff --git a/fs/nfs/flexfilelayout/flexfilelayout.c b/fs/nfs/flexfilelayout/flexfilelayout.c index 8b1559171fe3..6a84d85e0651 100644 --- a/fs/nfs/flexfilelayout/flexfilelayout.c +++ b/fs/nfs/flexfilelayout/flexfilelayout.c @@ -1230,7 +1230,7 @@ static void ff_layout_reset_write(struct nfs_pgio_header *hdr, bool retry_pnfs) "(req %s/%llu, %u bytes @ offset %llu)\n", __func__, hdr->task.tk_pid, hdr->inode->i_sb->s_id, - (unsigned long long)NFS_FILEID(hdr->inode), + (unsigned long long)hdr->inode->i_ino, hdr->args.count, (unsigned long long)hdr->args.offset); @@ -1243,7 +1243,7 @@ static void ff_layout_reset_write(struct nfs_pgio_header *hdr, bool retry_pnfs) "(req %s/%llu, %u bytes @ offset %llu)\n", __func__, hdr->task.tk_pid, hdr->inode->i_sb->s_id, - (unsigned long long)NFS_FILEID(hdr->inode), + (unsigned long long)hdr->inode->i_ino, hdr->args.count, (unsigned long long)hdr->args.offset); @@ -1283,7 +1283,7 @@ static void ff_layout_reset_read(struct nfs_pgio_header *hdr) "(req %s/%llu, %u bytes @ offset %llu)\n", __func__, hdr->task.tk_pid, hdr->inode->i_sb->s_id, - (unsigned long long)NFS_FILEID(hdr->inode), + (unsigned long long)hdr->inode->i_ino, hdr->args.count, (unsigned long long)hdr->args.offset); diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c index a21ed1c7f89d..0d9451a2ad8e 100644 --- a/fs/nfs/inode.c +++ b/fs/nfs/inode.c @@ -303,7 +303,7 @@ nfs_find_actor(struct inode *inode, void *opaque) struct nfs_fh *fh = desc->fh; struct nfs_fattr *fattr = desc->fattr; - if (NFS_FILEID(inode) != fattr->fileid) + if (inode->i_ino != fattr->fileid) return 0; if (inode_wrong_type(inode, fattr->mode)) return 0; @@ -320,7 +320,7 @@ nfs_init_locked(struct inode *inode, void *opaque) struct nfs_find_desc *desc = opaque; struct nfs_fattr *fattr = desc->fattr; - set_nfs_fileid(inode, fattr->fileid); + inode->i_ino = fattr->fileid; inode->i_mode = fattr->mode; nfs_copy_fh(NFS_FH(inode), desc->fh); return 0; @@ -580,7 +580,7 @@ nfs_fhget(struct super_block *sb, struct nfs_fh *fh, struct nfs_fattr *fattr) } dprintk("NFS: nfs_fhget(%s/%Lu fh_crc=0x%08x ct=%d)\n", inode->i_sb->s_id, - (unsigned long long)NFS_FILEID(inode), + (unsigned long long)inode->i_ino, nfs_display_fhandle_hash(fh), icount_read(inode)); @@ -1343,7 +1343,7 @@ __nfs_revalidate_inode(struct nfs_server *server, struct inode *inode) struct nfs_inode *nfsi = NFS_I(inode); dfprintk(PAGECACHE, "NFS: revalidating (%s/%Lu)\n", - inode->i_sb->s_id, (unsigned long long)NFS_FILEID(inode)); + inode->i_sb->s_id, (unsigned long long)inode->i_ino); trace_nfs_revalidate_inode_enter(inode); @@ -1373,7 +1373,7 @@ __nfs_revalidate_inode(struct nfs_server *server, struct inode *inode) if (status != 0) { dfprintk(PAGECACHE, "nfs_revalidate_inode: (%s/%Lu) getattr failed, error=%d\n", inode->i_sb->s_id, - (unsigned long long)NFS_FILEID(inode), status); + (unsigned long long)inode->i_ino, status); switch (status) { case -ETIMEDOUT: /* A soft timeout occurred. Use cached information? */ @@ -1393,7 +1393,7 @@ __nfs_revalidate_inode(struct nfs_server *server, struct inode *inode) if (status) { dfprintk(PAGECACHE, "nfs_revalidate_inode: (%s/%Lu) refresh failed, error=%d\n", inode->i_sb->s_id, - (unsigned long long)NFS_FILEID(inode), status); + (unsigned long long)inode->i_ino, status); goto out; } @@ -1404,7 +1404,7 @@ __nfs_revalidate_inode(struct nfs_server *server, struct inode *inode) dfprintk(PAGECACHE, "NFS: (%s/%Lu) revalidation complete\n", inode->i_sb->s_id, - (unsigned long long)NFS_FILEID(inode)); + (unsigned long long)inode->i_ino); out: nfs_free_fattr(fattr); @@ -1453,7 +1453,7 @@ static int nfs_invalidate_mapping(struct inode *inode, struct address_space *map dfprintk(PAGECACHE, "NFS: (%s/%Lu) data cache invalidated\n", inode->i_sb->s_id, - (unsigned long long)NFS_FILEID(inode)); + (unsigned long long)inode->i_ino); return 0; } diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c index a9b8d482d289..60024b978ee0 100644 --- a/fs/nfs/nfs4proc.c +++ b/fs/nfs/nfs4proc.c @@ -377,7 +377,7 @@ static void nfs4_setup_readdir(u64 cookie, __be32 *verifier, struct dentry *dent *p++ = htonl(attrs); /* bitmap */ *p++ = htonl(12); /* attribute buffer length */ *p++ = htonl(NF4DIR); - p = xdr_encode_hyper(p, NFS_FILEID(d_inode(dentry))); + p = xdr_encode_hyper(p, d_inode(dentry)->i_ino); } *p++ = xdr_one; /* next */ @@ -391,7 +391,7 @@ static void nfs4_setup_readdir(u64 cookie, __be32 *verifier, struct dentry *dent *p++ = htonl(12); /* attribute buffer length */ *p++ = htonl(NF4DIR); spin_lock(&dentry->d_lock); - p = xdr_encode_hyper(p, NFS_FILEID(d_inode(dentry->d_parent))); + p = xdr_encode_hyper(p, d_inode(dentry->d_parent)->i_ino); spin_unlock(&dentry->d_lock); readdir->pgbase = (char *)p - (char *)start; diff --git a/fs/nfs/nfs4trace.h b/fs/nfs/nfs4trace.h index c939533b9881..1ed677810d9d 100644 --- a/fs/nfs/nfs4trace.h +++ b/fs/nfs/nfs4trace.h @@ -597,13 +597,13 @@ DECLARE_EVENT_CLASS(nfs4_open_event, __entry->openstateid_hash = 0; } if (inode != NULL) { - __entry->fileid = NFS_FILEID(inode); + __entry->fileid = inode->i_ino; __entry->fhandle = nfs_fhandle_hash(NFS_FH(inode)); } else { __entry->fileid = 0; __entry->fhandle = 0; } - __entry->dir = NFS_FILEID(d_inode(ctx->dentry->d_parent)); + __entry->dir = d_inode(ctx->dentry->d_parent)->i_ino; __assign_str(name); ), @@ -658,7 +658,7 @@ TRACE_EVENT(nfs4_cached_open, const struct inode *inode = state->inode; __entry->dev = inode->i_sb->s_dev; - __entry->fileid = NFS_FILEID(inode); + __entry->fileid = inode->i_ino; __entry->fhandle = nfs_fhandle_hash(NFS_FH(inode)); __entry->fmode = (__force unsigned int)state->state; __entry->stateid_seq = @@ -703,7 +703,7 @@ TRACE_EVENT(nfs4_close, const struct inode *inode = state->inode; __entry->dev = inode->i_sb->s_dev; - __entry->fileid = NFS_FILEID(inode); + __entry->fileid = inode->i_ino; __entry->fhandle = nfs_fhandle_hash(NFS_FH(inode)); __entry->fmode = (__force unsigned int)state->state; __entry->error = error < 0 ? -error : 0; @@ -759,7 +759,7 @@ DECLARE_EVENT_CLASS(nfs4_lock_event, __entry->start = request->fl_start; __entry->end = request->fl_end; __entry->dev = inode->i_sb->s_dev; - __entry->fileid = NFS_FILEID(inode); + __entry->fileid = inode->i_ino; __entry->fhandle = nfs_fhandle_hash(NFS_FH(inode)); __entry->stateid_seq = be32_to_cpu(state->stateid.seqid); @@ -831,7 +831,7 @@ TRACE_EVENT(nfs4_set_lock, __entry->start = request->fl_start; __entry->end = request->fl_end; __entry->dev = inode->i_sb->s_dev; - __entry->fileid = NFS_FILEID(inode); + __entry->fileid = inode->i_ino; __entry->fhandle = nfs_fhandle_hash(NFS_FH(inode)); __entry->stateid_seq = be32_to_cpu(state->stateid.seqid); @@ -922,7 +922,7 @@ TRACE_EVENT(nfs4_state_lock_reclaim, const struct inode *inode = state->inode; __entry->dev = inode->i_sb->s_dev; - __entry->fileid = NFS_FILEID(inode); + __entry->fileid = inode->i_ino; __entry->fhandle = nfs_fhandle_hash(NFS_FH(inode)); __entry->state_flags = state->flags; __entry->lock_flags = lock->ls_flags; @@ -960,7 +960,7 @@ DECLARE_EVENT_CLASS(nfs4_set_delegation_event, TP_fast_assign( __entry->dev = inode->i_sb->s_dev; - __entry->fileid = NFS_FILEID(inode); + __entry->fileid = inode->i_ino; __entry->fhandle = nfs_fhandle_hash(NFS_FH(inode)); __entry->fmode = (__force unsigned int)fmode; ), @@ -1087,7 +1087,7 @@ DECLARE_EVENT_CLASS(nfs4_test_stateid_event, __entry->error = error < 0 ? -error : 0; __entry->dev = inode->i_sb->s_dev; - __entry->fileid = NFS_FILEID(inode); + __entry->fileid = inode->i_ino; __entry->fhandle = nfs_fhandle_hash(NFS_FH(inode)); __entry->stateid_seq = be32_to_cpu(state->stateid.seqid); @@ -1137,7 +1137,7 @@ DECLARE_EVENT_CLASS(nfs4_lookup_event, TP_fast_assign( __entry->dev = dir->i_sb->s_dev; - __entry->dir = NFS_FILEID(dir); + __entry->dir = dir->i_ino; __entry->error = -error; __assign_str(name); ), @@ -1185,7 +1185,7 @@ TRACE_EVENT(nfs4_lookupp, TP_fast_assign( __entry->dev = inode->i_sb->s_dev; - __entry->ino = NFS_FILEID(inode); + __entry->ino = inode->i_ino; __entry->error = error < 0 ? -error : 0; ), @@ -1220,8 +1220,8 @@ TRACE_EVENT(nfs4_rename, TP_fast_assign( __entry->dev = olddir->i_sb->s_dev; - __entry->olddir = NFS_FILEID(olddir); - __entry->newdir = NFS_FILEID(newdir); + __entry->olddir = olddir->i_ino; + __entry->newdir = newdir->i_ino; __entry->error = error < 0 ? -error : 0; __assign_str(oldname); __assign_str(newname); @@ -1258,7 +1258,7 @@ DECLARE_EVENT_CLASS(nfs4_inode_event, TP_fast_assign( __entry->dev = inode->i_sb->s_dev; - __entry->fileid = NFS_FILEID(inode); + __entry->fileid = inode->i_ino; __entry->fhandle = nfs_fhandle_hash(NFS_FH(inode)); __entry->error = error < 0 ? -error : 0; ), @@ -1311,7 +1311,7 @@ DECLARE_EVENT_CLASS(nfs4_inode_stateid_event, TP_fast_assign( __entry->dev = inode->i_sb->s_dev; - __entry->fileid = NFS_FILEID(inode); + __entry->fileid = inode->i_ino; __entry->fhandle = nfs_fhandle_hash(NFS_FH(inode)); __entry->error = error < 0 ? -error : 0; __entry->stateid_seq = @@ -1421,7 +1421,7 @@ DECLARE_EVENT_CLASS(nfs4_inode_callback_event, __entry->error = error < 0 ? -error : 0; __entry->fhandle = nfs_fhandle_hash(fhandle); if (!IS_ERR_OR_NULL(inode)) { - __entry->fileid = NFS_FILEID(inode); + __entry->fileid = inode->i_ino; __entry->dev = inode->i_sb->s_dev; } else { __entry->fileid = 0; @@ -1478,7 +1478,7 @@ DECLARE_EVENT_CLASS(nfs4_inode_stateid_callback_event, __entry->error = error < 0 ? -error : 0; __entry->fhandle = nfs_fhandle_hash(fhandle); if (!IS_ERR_OR_NULL(inode)) { - __entry->fileid = NFS_FILEID(inode); + __entry->fileid = inode->i_ino; __entry->dev = inode->i_sb->s_dev; } else { __entry->fileid = 0; @@ -1655,7 +1655,7 @@ DECLARE_EVENT_CLASS(nfs4_read_event, const struct pnfs_layout_segment *lseg = hdr->lseg; __entry->dev = inode->i_sb->s_dev; - __entry->fileid = nfsi->fileid; + __entry->fileid = inode->i_ino; __entry->fhandle = nfs_fhandle_hash(fh); __entry->offset = hdr->args.offset; __entry->arg_count = hdr->args.count; @@ -1727,7 +1727,7 @@ DECLARE_EVENT_CLASS(nfs4_write_event, const struct pnfs_layout_segment *lseg = hdr->lseg; __entry->dev = inode->i_sb->s_dev; - __entry->fileid = nfsi->fileid; + __entry->fileid = inode->i_ino; __entry->fhandle = nfs_fhandle_hash(fh); __entry->offset = hdr->args.offset; __entry->arg_count = hdr->args.count; @@ -1795,7 +1795,7 @@ DECLARE_EVENT_CLASS(nfs4_commit_event, const struct pnfs_layout_segment *lseg = data->lseg; __entry->dev = inode->i_sb->s_dev; - __entry->fileid = nfsi->fileid; + __entry->fileid = inode->i_ino; __entry->fhandle = nfs_fhandle_hash(fh); __entry->offset = data->args.offset; __entry->count = data->args.count; @@ -1857,7 +1857,7 @@ TRACE_EVENT(nfs4_layoutget, const struct inode *inode = d_inode(ctx->dentry); const struct nfs4_state *state = ctx->state; __entry->dev = inode->i_sb->s_dev; - __entry->fileid = NFS_FILEID(inode); + __entry->fileid = inode->i_ino; __entry->fhandle = nfs_fhandle_hash(NFS_FH(inode)); __entry->iomode = args->iomode; __entry->offset = args->offset; @@ -1957,7 +1957,7 @@ TRACE_EVENT(pnfs_update_layout, ), TP_fast_assign( __entry->dev = inode->i_sb->s_dev; - __entry->fileid = NFS_FILEID(inode); + __entry->fileid = inode->i_ino; __entry->fhandle = nfs_fhandle_hash(NFS_FH(inode)); __entry->pos = pos; __entry->count = count; @@ -2012,7 +2012,7 @@ DECLARE_EVENT_CLASS(pnfs_layout_event, ), TP_fast_assign( __entry->dev = inode->i_sb->s_dev; - __entry->fileid = NFS_FILEID(inode); + __entry->fileid = inode->i_ino; __entry->fhandle = nfs_fhandle_hash(NFS_FH(inode)); __entry->pos = pos; __entry->count = count; @@ -2194,7 +2194,7 @@ DECLARE_EVENT_CLASS(nfs4_flexfiles_io_event, __entry->error = -error; __entry->nfs_error = hdr->res.op_status; __entry->fhandle = nfs_fhandle_hash(hdr->args.fh); - __entry->fileid = NFS_FILEID(inode); + __entry->fileid = inode->i_ino; __entry->dev = inode->i_sb->s_dev; __entry->offset = hdr->args.offset; __entry->count = hdr->args.count; @@ -2258,7 +2258,7 @@ TRACE_EVENT(ff_layout_commit_error, __entry->error = -error; __entry->nfs_error = data->res.op_status; __entry->fhandle = nfs_fhandle_hash(data->args.fh); - __entry->fileid = NFS_FILEID(inode); + __entry->fileid = inode->i_ino; __entry->dev = inode->i_sb->s_dev; __entry->offset = data->args.offset; __entry->count = data->args.count; @@ -2423,7 +2423,7 @@ TRACE_EVENT(nfs4_llseek, TP_STRUCT__entry( __field(unsigned long, error) __field(u32, fhandle) - __field(u32, fileid) + __field(u64, fileid) __field(dev_t, dev) __field(int, stateid_seq) __field(u32, stateid_hash) @@ -2434,10 +2434,9 @@ TRACE_EVENT(nfs4_llseek, ), TP_fast_assign( - const struct nfs_inode *nfsi = NFS_I(inode); const struct nfs_fh *fh = args->sa_fh; - __entry->fileid = nfsi->fileid; + __entry->fileid = inode->i_ino; __entry->dev = inode->i_sb->s_dev; __entry->fhandle = nfs_fhandle_hash(fh); __entry->offset_s = args->sa_offset; @@ -2499,7 +2498,7 @@ DECLARE_EVENT_CLASS(nfs4_sparse_event, __entry->offset = args->falloc_offset; __entry->len = args->falloc_length; __entry->dev = inode->i_sb->s_dev; - __entry->fileid = NFS_FILEID(inode); + __entry->fileid = inode->i_ino; __entry->fhandle = nfs_fhandle_hash(NFS_FH(inode)); __entry->stateid_seq = be32_to_cpu(args->falloc_stateid.seqid); @@ -2568,14 +2567,11 @@ TRACE_EVENT(nfs4_copy, ), TP_fast_assign( - const struct nfs_inode *src_nfsi = NFS_I(src_inode); - const struct nfs_inode *dst_nfsi = NFS_I(dst_inode); - - __entry->src_fileid = src_nfsi->fileid; + __entry->src_fileid = src_inode->i_ino; __entry->src_dev = src_inode->i_sb->s_dev; __entry->src_fhandle = nfs_fhandle_hash(args->src_fh); __entry->src_offset = args->src_pos; - __entry->dst_fileid = dst_nfsi->fileid; + __entry->dst_fileid = dst_inode->i_ino; __entry->dst_dev = dst_inode->i_sb->s_dev; __entry->dst_fhandle = nfs_fhandle_hash(args->dst_fh); __entry->dst_offset = args->dst_pos; @@ -2666,14 +2662,11 @@ TRACE_EVENT(nfs4_clone, ), TP_fast_assign( - const struct nfs_inode *src_nfsi = NFS_I(src_inode); - const struct nfs_inode *dst_nfsi = NFS_I(dst_inode); - - __entry->src_fileid = src_nfsi->fileid; + __entry->src_fileid = src_inode->i_ino; __entry->src_dev = src_inode->i_sb->s_dev; __entry->src_fhandle = nfs_fhandle_hash(args->src_fh); __entry->src_offset = args->src_offset; - __entry->dst_fileid = dst_nfsi->fileid; + __entry->dst_fileid = dst_inode->i_ino; __entry->dst_dev = dst_inode->i_sb->s_dev; __entry->dst_fhandle = nfs_fhandle_hash(args->dst_fh); __entry->dst_offset = args->dst_offset; @@ -2724,7 +2717,7 @@ TRACE_EVENT(nfs4_copy_notify, TP_STRUCT__entry( __field(unsigned long, error) __field(u32, fhandle) - __field(u32, fileid) + __field(u64, fileid) __field(dev_t, dev) __field(int, stateid_seq) __field(u32, stateid_hash) @@ -2733,9 +2726,7 @@ TRACE_EVENT(nfs4_copy_notify, ), TP_fast_assign( - const struct nfs_inode *nfsi = NFS_I(inode); - - __entry->fileid = nfsi->fileid; + __entry->fileid = inode->i_ino; __entry->dev = inode->i_sb->s_dev; __entry->fhandle = nfs_fhandle_hash(args->cna_src_fh); __entry->stateid_seq = @@ -2830,7 +2821,7 @@ DECLARE_EVENT_CLASS(nfs4_xattr_event, TP_fast_assign( __entry->error = error < 0 ? -error : 0; __entry->dev = inode->i_sb->s_dev; - __entry->fileid = NFS_FILEID(inode); + __entry->fileid = inode->i_ino; __entry->fhandle = nfs_fhandle_hash(NFS_FH(inode)); __assign_str(name); ), diff --git a/fs/nfs/nfstrace.h b/fs/nfs/nfstrace.h index ff467959f733..4ada21f4eebd 100644 --- a/fs/nfs/nfstrace.h +++ b/fs/nfs/nfstrace.h @@ -80,7 +80,7 @@ DECLARE_EVENT_CLASS(nfs_inode_event, TP_fast_assign( const struct nfs_inode *nfsi = NFS_I(inode); __entry->dev = inode->i_sb->s_dev; - __entry->fileid = nfsi->fileid; + __entry->fileid = inode->i_ino; __entry->fhandle = nfs_fhandle_hash(&nfsi->fh); __entry->version = inode_peek_iversion_raw(inode); __entry->cache_validity = nfsi->cache_validity; @@ -121,7 +121,7 @@ DECLARE_EVENT_CLASS(nfs_inode_event_done, const struct nfs_inode *nfsi = NFS_I(inode); __entry->error = error < 0 ? -error : 0; __entry->dev = inode->i_sb->s_dev; - __entry->fileid = nfsi->fileid; + __entry->fileid = inode->i_ino; __entry->fhandle = nfs_fhandle_hash(&nfsi->fh); __entry->type = nfs_umode_to_dtype(inode->i_mode); __entry->version = inode_peek_iversion_raw(inode); @@ -211,7 +211,7 @@ TRACE_EVENT(nfs_access_exit, const struct nfs_inode *nfsi = NFS_I(inode); __entry->error = error < 0 ? -error : 0; __entry->dev = inode->i_sb->s_dev; - __entry->fileid = nfsi->fileid; + __entry->fileid = inode->i_ino; __entry->fhandle = nfs_fhandle_hash(&nfsi->fh); __entry->type = nfs_umode_to_dtype(inode->i_mode); __entry->version = inode_peek_iversion_raw(inode); @@ -265,7 +265,7 @@ DECLARE_EVENT_CLASS(nfs_update_size_class, __entry->dev = inode->i_sb->s_dev; __entry->fhandle = nfs_fhandle_hash(&nfsi->fh); - __entry->fileid = nfsi->fileid; + __entry->fileid = inode->i_ino; __entry->version = inode_peek_iversion_raw(inode); __entry->cur_size = i_size_read(inode); __entry->new_size = new_size; @@ -317,7 +317,7 @@ DECLARE_EVENT_CLASS(nfs_inode_range_event, __entry->dev = inode->i_sb->s_dev; __entry->fhandle = nfs_fhandle_hash(&nfsi->fh); - __entry->fileid = nfsi->fileid; + __entry->fileid = inode->i_ino; __entry->version = inode_peek_iversion_raw(inode); __entry->range_start = range_start; __entry->range_end = range_end; @@ -371,7 +371,7 @@ DECLARE_EVENT_CLASS(nfs_readdir_event, const struct nfs_inode *nfsi = NFS_I(dir); __entry->dev = dir->i_sb->s_dev; - __entry->fileid = nfsi->fileid; + __entry->fileid = dir->i_ino; __entry->fhandle = nfs_fhandle_hash(&nfsi->fh); __entry->version = inode_peek_iversion_raw(dir); if (cookie != 0) @@ -429,9 +429,9 @@ DECLARE_EVENT_CLASS(nfs_lookup_event, TP_fast_assign( __entry->dev = dir->i_sb->s_dev; - __entry->dir = NFS_FILEID(dir); + __entry->dir = dir->i_ino; __entry->flags = flags; - __entry->fileid = d_is_negative(dentry) ? 0 : NFS_FILEID(d_inode(dentry)); + __entry->fileid = d_is_negative(dentry) ? 0 : d_inode(dentry)->i_ino; __assign_str(name); ), @@ -476,10 +476,10 @@ DECLARE_EVENT_CLASS(nfs_lookup_event_done, TP_fast_assign( __entry->dev = dir->i_sb->s_dev; - __entry->dir = NFS_FILEID(dir); + __entry->dir = dir->i_ino; __entry->error = error < 0 ? -error : 0; __entry->flags = flags; - __entry->fileid = d_is_negative(dentry) ? 0 : NFS_FILEID(d_inode(dentry)); + __entry->fileid = d_is_negative(dentry) ? 0 : d_inode(dentry)->i_ino; __assign_str(name); ), @@ -532,7 +532,7 @@ TRACE_EVENT(nfs_atomic_open_enter, TP_fast_assign( __entry->dev = dir->i_sb->s_dev; - __entry->dir = NFS_FILEID(dir); + __entry->dir = dir->i_ino; __entry->flags = flags; __entry->fmode = (__force unsigned long)ctx->mode; __assign_str(name); @@ -571,7 +571,7 @@ TRACE_EVENT(nfs_atomic_open_exit, TP_fast_assign( __entry->error = -error; __entry->dev = dir->i_sb->s_dev; - __entry->dir = NFS_FILEID(dir); + __entry->dir = dir->i_ino; __entry->flags = flags; __entry->fmode = (__force unsigned long)ctx->mode; __assign_str(name); @@ -608,7 +608,7 @@ TRACE_EVENT(nfs_create_enter, TP_fast_assign( __entry->dev = dir->i_sb->s_dev; - __entry->dir = NFS_FILEID(dir); + __entry->dir = dir->i_ino; __entry->flags = flags; __assign_str(name); ), @@ -644,7 +644,7 @@ TRACE_EVENT(nfs_create_exit, TP_fast_assign( __entry->error = -error; __entry->dev = dir->i_sb->s_dev; - __entry->dir = NFS_FILEID(dir); + __entry->dir = dir->i_ino; __entry->flags = flags; __assign_str(name); ), @@ -676,7 +676,7 @@ DECLARE_EVENT_CLASS(nfs_directory_event, TP_fast_assign( __entry->dev = dir->i_sb->s_dev; - __entry->dir = NFS_FILEID(dir); + __entry->dir = dir->i_ino; __assign_str(name); ), @@ -714,7 +714,7 @@ DECLARE_EVENT_CLASS(nfs_directory_event_done, TP_fast_assign( __entry->dev = dir->i_sb->s_dev; - __entry->dir = NFS_FILEID(dir); + __entry->dir = dir->i_ino; __entry->error = error < 0 ? -error : 0; __assign_str(name); ), @@ -768,8 +768,8 @@ TRACE_EVENT(nfs_link_enter, TP_fast_assign( __entry->dev = inode->i_sb->s_dev; - __entry->fileid = NFS_FILEID(inode); - __entry->dir = NFS_FILEID(dir); + __entry->fileid = inode->i_ino; + __entry->dir = dir->i_ino; __assign_str(name); ), @@ -803,8 +803,8 @@ TRACE_EVENT(nfs_link_exit, TP_fast_assign( __entry->dev = inode->i_sb->s_dev; - __entry->fileid = NFS_FILEID(inode); - __entry->dir = NFS_FILEID(dir); + __entry->fileid = inode->i_ino; + __entry->dir = dir->i_ino; __entry->error = error < 0 ? -error : 0; __assign_str(name); ), @@ -840,8 +840,8 @@ DECLARE_EVENT_CLASS(nfs_rename_event, TP_fast_assign( __entry->dev = old_dir->i_sb->s_dev; - __entry->old_dir = NFS_FILEID(old_dir); - __entry->new_dir = NFS_FILEID(new_dir); + __entry->old_dir = old_dir->i_ino; + __entry->new_dir = new_dir->i_ino; __assign_str(old_name); __assign_str(new_name); ), @@ -889,8 +889,8 @@ DECLARE_EVENT_CLASS(nfs_rename_event_done, TP_fast_assign( __entry->dev = old_dir->i_sb->s_dev; __entry->error = -error; - __entry->old_dir = NFS_FILEID(old_dir); - __entry->new_dir = NFS_FILEID(new_dir); + __entry->old_dir = old_dir->i_ino; + __entry->new_dir = new_dir->i_ino; __assign_str(old_name); __assign_str(new_name); ), @@ -943,7 +943,7 @@ TRACE_EVENT(nfs_sillyrename_unlink, struct inode *dir = d_inode(data->dentry->d_parent); size_t len = data->args.name.len; __entry->dev = dir->i_sb->s_dev; - __entry->dir = NFS_FILEID(dir); + __entry->dir = dir->i_ino; __entry->error = -error; memcpy(__get_str(name), data->args.name.name, len); @@ -981,7 +981,7 @@ DECLARE_EVENT_CLASS(nfs_folio_event, const struct nfs_inode *nfsi = NFS_I(inode); __entry->dev = inode->i_sb->s_dev; - __entry->fileid = nfsi->fileid; + __entry->fileid = inode->i_ino; __entry->fhandle = nfs_fhandle_hash(&nfsi->fh); __entry->version = inode_peek_iversion_raw(inode); __entry->offset = offset; @@ -1031,7 +1031,7 @@ DECLARE_EVENT_CLASS(nfs_folio_event_done, const struct nfs_inode *nfsi = NFS_I(inode); __entry->dev = inode->i_sb->s_dev; - __entry->fileid = nfsi->fileid; + __entry->fileid = inode->i_ino; __entry->fhandle = nfs_fhandle_hash(&nfsi->fh); __entry->version = inode_peek_iversion_raw(inode); __entry->offset = offset; @@ -1109,7 +1109,7 @@ DECLARE_EVENT_CLASS(nfs_kiocb_event, const struct nfs_inode *nfsi = NFS_I(inode); __entry->dev = inode->i_sb->s_dev; - __entry->fileid = nfsi->fileid; + __entry->fileid = inode->i_ino; __entry->fhandle = nfs_fhandle_hash(&nfsi->fh); __entry->version = inode_peek_iversion_raw(inode); __entry->offset = iocb->ki_pos; @@ -1160,7 +1160,7 @@ TRACE_EVENT(nfs_aop_readahead, const struct nfs_inode *nfsi = NFS_I(inode); __entry->dev = inode->i_sb->s_dev; - __entry->fileid = nfsi->fileid; + __entry->fileid = inode->i_ino; __entry->fhandle = nfs_fhandle_hash(&nfsi->fh); __entry->version = inode_peek_iversion_raw(inode); __entry->offset = pos; @@ -1199,7 +1199,7 @@ TRACE_EVENT(nfs_aop_readahead_done, const struct nfs_inode *nfsi = NFS_I(inode); __entry->dev = inode->i_sb->s_dev; - __entry->fileid = nfsi->fileid; + __entry->fileid = inode->i_ino; __entry->fhandle = nfs_fhandle_hash(&nfsi->fh); __entry->version = inode_peek_iversion_raw(inode); __entry->nr_pages = nr_pages; @@ -1239,7 +1239,7 @@ TRACE_EVENT(nfs_initiate_read, __entry->offset = hdr->args.offset; __entry->count = hdr->args.count; __entry->dev = inode->i_sb->s_dev; - __entry->fileid = nfsi->fileid; + __entry->fileid = inode->i_ino; __entry->fhandle = nfs_fhandle_hash(fh); ), @@ -1284,7 +1284,7 @@ TRACE_EVENT(nfs_readpage_done, __entry->res_count = hdr->res.count; __entry->eof = hdr->res.eof; __entry->dev = inode->i_sb->s_dev; - __entry->fileid = nfsi->fileid; + __entry->fileid = inode->i_ino; __entry->fhandle = nfs_fhandle_hash(fh); ), @@ -1330,7 +1330,7 @@ TRACE_EVENT(nfs_readpage_short, __entry->res_count = hdr->res.count; __entry->eof = hdr->res.eof; __entry->dev = inode->i_sb->s_dev; - __entry->fileid = nfsi->fileid; + __entry->fileid = inode->i_ino; __entry->fhandle = nfs_fhandle_hash(fh); ), @@ -1377,7 +1377,7 @@ TRACE_EVENT(nfs_pgio_error, __entry->arg_count = hdr->args.count; __entry->res_count = hdr->res.count; __entry->dev = inode->i_sb->s_dev; - __entry->fileid = nfsi->fileid; + __entry->fileid = inode->i_ino; __entry->fhandle = nfs_fhandle_hash(fh); ), @@ -1416,7 +1416,7 @@ TRACE_EVENT(nfs_initiate_write, __entry->count = hdr->args.count; __entry->stable = hdr->args.stable; __entry->dev = inode->i_sb->s_dev; - __entry->fileid = nfsi->fileid; + __entry->fileid = inode->i_ino; __entry->fhandle = nfs_fhandle_hash(fh); ), @@ -1467,7 +1467,7 @@ TRACE_EVENT(nfs_writeback_done, &verf->verifier, NFS4_VERIFIER_SIZE); __entry->dev = inode->i_sb->s_dev; - __entry->fileid = nfsi->fileid; + __entry->fileid = inode->i_ino; __entry->fhandle = nfs_fhandle_hash(fh); ), @@ -1507,7 +1507,7 @@ DECLARE_EVENT_CLASS(nfs_page_class, const struct nfs_inode *nfsi = NFS_I(inode); __entry->dev = inode->i_sb->s_dev; - __entry->fileid = nfsi->fileid; + __entry->fileid = inode->i_ino; __entry->fhandle = nfs_fhandle_hash(&nfsi->fh); __entry->req = req; __entry->offset = req_offset(req); @@ -1555,7 +1555,7 @@ DECLARE_EVENT_CLASS(nfs_page_error_class, TP_fast_assign( const struct nfs_inode *nfsi = NFS_I(inode); __entry->dev = inode->i_sb->s_dev; - __entry->fileid = nfsi->fileid; + __entry->fileid = inode->i_ino; __entry->fhandle = nfs_fhandle_hash(&nfsi->fh); __entry->offset = req_offset(req); __entry->count = req->wb_bytes; @@ -1609,7 +1609,7 @@ TRACE_EVENT(nfs_initiate_commit, __entry->offset = data->args.offset; __entry->count = data->args.count; __entry->dev = inode->i_sb->s_dev; - __entry->fileid = nfsi->fileid; + __entry->fileid = inode->i_ino; __entry->fhandle = nfs_fhandle_hash(fh); ), @@ -1655,7 +1655,7 @@ TRACE_EVENT(nfs_commit_done, &verf->verifier, NFS4_VERIFIER_SIZE); __entry->dev = inode->i_sb->s_dev; - __entry->fileid = nfsi->fileid; + __entry->fileid = inode->i_ino; __entry->fhandle = nfs_fhandle_hash(fh); ), @@ -1701,7 +1701,7 @@ DECLARE_EVENT_CLASS(nfs_direct_req_class, const struct nfs_fh *fh = &nfsi->fh; __entry->dev = inode->i_sb->s_dev; - __entry->fileid = nfsi->fileid; + __entry->fileid = inode->i_ino; __entry->fhandle = nfs_fhandle_hash(fh); __entry->offset = dreq->io_start; __entry->count = dreq->count; @@ -1765,7 +1765,7 @@ DECLARE_EVENT_CLASS(nfs_local_dio_class, const struct nfs_fh *fh = &nfsi->fh; __entry->dev = inode->i_sb->s_dev; - __entry->fileid = nfsi->fileid; + __entry->fileid = inode->i_ino; __entry->fhandle = nfs_fhandle_hash(fh); __entry->offset = offset; __entry->count = count; diff --git a/fs/nfs/pagelist.c b/fs/nfs/pagelist.c index 4a87b2fdb2e6..7dd478ffc2fa 100644 --- a/fs/nfs/pagelist.c +++ b/fs/nfs/pagelist.c @@ -759,7 +759,7 @@ int nfs_initiate_pgio(struct rpc_clnt *clnt, struct nfs_pgio_header *hdr, dprintk("NFS: initiated pgio call " "(req %s/%llu, %u bytes @ offset %llu)\n", hdr->inode->i_sb->s_id, - (unsigned long long)NFS_FILEID(hdr->inode), + (unsigned long long)hdr->inode->i_ino, hdr->args.count, (unsigned long long)hdr->args.offset); diff --git a/fs/nfs/pnfs.c b/fs/nfs/pnfs.c index 743467e9ba20..fdedeff5f6cc 100644 --- a/fs/nfs/pnfs.c +++ b/fs/nfs/pnfs.c @@ -2373,7 +2373,7 @@ pnfs_update_layout(struct inode *ino, dprintk("%s: inode %s/%llu pNFS layout segment %s for " "(%s, offset: %llu, length: %llu)\n", __func__, ino->i_sb->s_id, - (unsigned long long)NFS_FILEID(ino), + (unsigned long long)ino->i_ino, IS_ERR_OR_NULL(lseg) ? "not found" : "found", iomode==IOMODE_RW ? "read/write" : "read-only", (unsigned long long)pos, diff --git a/fs/nfs/unlink.c b/fs/nfs/unlink.c index df3ca4669df6..7f2e84eaaa9f 100644 --- a/fs/nfs/unlink.c +++ b/fs/nfs/unlink.c @@ -461,7 +461,7 @@ nfs_sillyrename(struct inode *dir, struct dentry *dentry) if (dentry->d_flags & DCACHE_NFSFS_RENAMED) goto out; - fileid = NFS_FILEID(d_inode(dentry)); + fileid = d_inode(dentry)->i_ino; sdentry = NULL; do { diff --git a/fs/nfs/write.c b/fs/nfs/write.c index d7c399763ad9..fcffb8c9e9df 100644 --- a/fs/nfs/write.c +++ b/fs/nfs/write.c @@ -1817,7 +1817,7 @@ static void nfs_commit_release_pages(struct nfs_commit_data *data) dprintk("NFS: commit (%s/%llu %d@%lld)", nfs_req_openctx(req)->dentry->d_sb->s_id, - (unsigned long long)NFS_FILEID(d_inode(nfs_req_openctx(req)->dentry)), + (unsigned long long)d_inode(nfs_req_openctx(req)->dentry)->i_ino, req->wb_bytes, (long long)req_offset(req)); if (status < 0) { diff --git a/include/linux/nfs_fs.h b/include/linux/nfs_fs.h index 6d6fa62ede10..83063f4ab488 100644 --- a/include/linux/nfs_fs.h +++ b/include/linux/nfs_fs.h @@ -394,16 +394,6 @@ static inline int NFS_STALE(const struct inode *inode) return test_bit(NFS_INO_STALE, &NFS_I(inode)->flags); } -static inline __u64 NFS_FILEID(const struct inode *inode) -{ - return inode->i_ino; -} - -static inline void set_nfs_fileid(struct inode *inode, __u64 fileid) -{ - inode->i_ino = fileid; -} - static inline void nfs_mark_for_revalidate(struct inode *inode) { struct nfs_inode *nfsi = NFS_I(inode); From bd5e6b056bd0900d8efde90da7a5877dc50842c7 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Tue, 12 May 2026 12:12:45 -0400 Subject: [PATCH 04/42] nfs: remove fileid field from struct nfs_inode Now that all NFS client code uses inode->i_ino directly to store and access the 64-bit NFS fileid, the separate fileid field in struct nfs_inode is unused. Remove it to save 8 bytes per NFS inode. Assisted-by: Claude:claude-opus-4-6 Signed-off-by: Jeff Layton Signed-off-by: Anna Schumaker --- include/linux/nfs_fs.h | 5 ----- 1 file changed, 5 deletions(-) diff --git a/include/linux/nfs_fs.h b/include/linux/nfs_fs.h index 83063f4ab488..ec17e602c979 100644 --- a/include/linux/nfs_fs.h +++ b/include/linux/nfs_fs.h @@ -145,11 +145,6 @@ struct nfs4_xattr_cache; * nfs fs inode data in memory */ struct nfs_inode { - /* - * The 64bit 'inode number' - */ - __u64 fileid; - /* * NFS file handle */ From 37957478be021b92981aa4c99b69f308d3b784d0 Mon Sep 17 00:00:00 2001 From: Hongling Zeng Date: Wed, 13 May 2026 17:28:59 +0800 Subject: [PATCH 05/42] sunrpc: Fix error handling in rpc_sysfs_xprt_switch_add_xprt_store() xprt_create_transport() never returns NULL, only valid pointers or error pointers. Using IS_ERR_OR_NULL() is incorrect, and PTR_ERR(NULL) would return 0, which indicates EOF in a sysfs store function. Fix this by using IS_ERR() instead of IS_ERR_OR_NULL(). Fixes: df210d9b0951 ("sunrpc: Add a sysfs file for adding a new xprt") Signed-off-by: Hongling Zeng Signed-off-by: Anna Schumaker --- net/sunrpc/sysfs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/sunrpc/sysfs.c b/net/sunrpc/sysfs.c index a90480f80154..49686bf740e6 100644 --- a/net/sunrpc/sysfs.c +++ b/net/sunrpc/sysfs.c @@ -348,7 +348,7 @@ static ssize_t rpc_sysfs_xprt_switch_add_xprt_store(struct kobject *kobj, xprt_create_args.reconnect_timeout = xprt->max_reconnect_timeout; new = xprt_create_transport(&xprt_create_args); - if (IS_ERR_OR_NULL(new)) { + if (IS_ERR(new)) { count = PTR_ERR(new); goto out_put_xprt; } From 2c6bb3c40bc24f6aa8dfbe6fe98c3ad6389203f2 Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Wed, 13 May 2026 12:26:56 -0400 Subject: [PATCH 06/42] NFSv4/flexfiles: reject zero filehandle version count ff_layout_alloc_lseg() decodes the filehandle-version array count from the flexfiles layout body. The value is used as the count for kzalloc_objs(), and the current code only rejects NULL. A zero count yields ZERO_SIZE_PTR, which can be stored in dss_info->fh_versions even though later flexfiles paths assume that at least one filehandle version exists. Reject fh_count == 0 before the allocation, matching the existing zero version_count validation in the flexfiles GETDEVICEINFO parser. A QEMU/KASAN run with a malformed flexfiles layout hit: KASAN: null-ptr-deref in range [0x0000000000000010-0x0000000000000017] RIP: 0010:ff_layout_encode_ff_layoutupdate.isra.0+0x15f/0x750 ff_layout_encode_layoutreturn+0x683/0x970 nfs4_xdr_enc_layoutreturn+0x278/0x3a0 Kernel panic - not syncing: Fatal exception The patched kernel rejects the malformed layout without KASAN/oops/panic, and a valid fh_count=1 regression still opens, reads, and unmounts cleanly. Cc: stable@vger.kernel.org Fixes: d67ae825a59d ("pnfs/flexfiles: Add the FlexFile Layout Driver") Assisted-by: Claude:claude-opus-4-7 Signed-off-by: Michael Bommarito Signed-off-by: Anna Schumaker --- fs/nfs/flexfilelayout/flexfilelayout.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fs/nfs/flexfilelayout/flexfilelayout.c b/fs/nfs/flexfilelayout/flexfilelayout.c index 6a84d85e0651..99caa8d28c25 100644 --- a/fs/nfs/flexfilelayout/flexfilelayout.c +++ b/fs/nfs/flexfilelayout/flexfilelayout.c @@ -551,6 +551,10 @@ ff_layout_alloc_lseg(struct pnfs_layout_hdr *lh, if (!p) goto out_err_free; fh_count = be32_to_cpup(p); + if (fh_count == 0) { + rc = -EINVAL; + goto out_err_free; + } dss_info->fh_versions = kzalloc_objs(struct nfs_fh, fh_count, gfp_flags); From 238e9b51aa29f48b6243212a3b75c8e48d6b96fd Mon Sep 17 00:00:00 2001 From: Igor Raits Date: Wed, 29 Apr 2026 12:49:38 +0200 Subject: [PATCH 07/42] NFSv4: clear exception state on successful mkdir retry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After a server returns NFS4ERR_DELAY for an NFSv4 CREATE issued by mkdir(2), the client correctly waits and retries. When the retry succeeds, however, mkdir(2) can still surface -EEXIST to userspace even though the directory was just created on the server. Reproducer (random 16-hex names so collisions are not the cause) against an in-kernel Linux nfsd; reproduces under both NFSv4.0 and NFSv4.2: N=2000000; base=/var/gdc/export for ((i=1; i<=N; i++)); do d=$base/$(openssl rand -hex 8) mkdir "$d" 2>/dev/null || echo "$(date +%T) failed loop=$i $d" rmdir "$d" 2>/dev/null done Failures cluster at the cadence at which the server-side auth/export cache refresh path causes nfsd to return NFS4ERR_DELAY for CREATE. A wire trace of one failure (the three CREATE RPCs all come from a single mkdir(2), generated by the do-while in nfs4_proc_mkdir()): client -> server CREATE name=... -> NFS4ERR_DELAY ~100 ms later client -> server CREATE name=... -> NFS4_OK (dir created) ~80 us later client -> server CREATE name=... -> NFS4ERR_EXIST (correct) Since commit dd862da61e91 ("nfs: fix incorrect handling of large-number NFS errors in nfs4_do_mkdir()"), nfs4_handle_exception() is called only when _nfs4_proc_mkdir() returned an error. That gate breaks retry-state hygiene: nfs4_do_handle_exception() resets exception.{delay,recovering, retry} to 0 on entry, so calling it on success is what previously cleared the retry flag set by the preceding NFS4ERR_DELAY iteration. With the gate in place, exception.retry stays at 1 after the successful retry, the loop runs once more, and the resulting CREATE for an already-created name yields NFS4ERR_EXIST -> -EEXIST to userspace. Drop the conditional and call nfs4_handle_exception() unconditionally, matching every other do-while in fs/nfs/nfs4proc.c (nfs4_proc_symlink(), nfs4_proc_link(), etc.). The dentry/status separation introduced by that commit is preserved. Fixes: dd862da61e91 ("nfs: fix incorrect handling of large-number NFS errors in nfs4_do_mkdir()") Reported-and-tested-by: Jan Čípa Closes: https://lore.kernel.org/linux-nfs/CA+9S74hSp_tJu2Ffe2BPNC2T25gfkhgjjDkdgSsF5c2rnJq_wA@mail.gmail.com/ Reviewed-by: NeilBrown Cc: stable@vger.kernel.org Signed-off-by: Igor Raits Signed-off-by: Anna Schumaker --- fs/nfs/nfs4proc.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c index 60024b978ee0..0d77d50f4de7 100644 --- a/fs/nfs/nfs4proc.c +++ b/fs/nfs/nfs4proc.c @@ -5302,10 +5302,9 @@ static struct dentry *nfs4_proc_mkdir(struct inode *dir, struct dentry *dentry, do { alias = _nfs4_proc_mkdir(dir, dentry, sattr, label, &err); trace_nfs4_mkdir(dir, &dentry->d_name, err); + err = nfs4_handle_exception(NFS_SERVER(dir), err, &exception); if (err) - alias = ERR_PTR(nfs4_handle_exception(NFS_SERVER(dir), - err, - &exception)); + alias = ERR_PTR(err); } while (exception.retry); nfs4_label_release_security(label); From ea7338471620030582e6a8d3f0fda767fdaee768 Mon Sep 17 00:00:00 2001 From: Marco Crivellari Date: Thu, 7 May 2026 15:01:17 +0200 Subject: [PATCH 08/42] xprtrdma: Move long delayed work on system_dfl_long_wq MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently the code enqueue work items using {queue|mod}_delayed_work(), using system_long_wq. This workqueue should be used when long works are expected and it is a per-cpu workqueue. The function(s) end up calling __queue_delayed_work(), which set a global timer that could fire anywhere, enqueuing the work where the timer fired. Unbound works could benefit from scheduler task placement, to optimize performance and power consumption. Long work shouldn't stick to a single CPU. Recently, a new unbound workqueue specific for long running work has been added:     c116737e972e ("workqueue: Add system_dfl_long_wq for long unbound works") Since the workqueue work doesn't rely on per-cpu variables, there is no obvious reason that justify the use of a per-cpu workqueue. So change system_long_wq with system_dfl_long_wq so that the work may benefit from scheduler task placement. Signed-off-by: Marco Crivellari Reviewed-by: Chuck Lever Signed-off-by: Anna Schumaker --- net/sunrpc/xprtrdma/transport.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/sunrpc/xprtrdma/transport.c b/net/sunrpc/xprtrdma/transport.c index 61706df5e485..1a54993f7ffb 100644 --- a/net/sunrpc/xprtrdma/transport.c +++ b/net/sunrpc/xprtrdma/transport.c @@ -484,7 +484,8 @@ xprt_rdma_connect(struct rpc_xprt *xprt, struct rpc_task *task) xprt_reconnect_backoff(xprt, RPCRDMA_INIT_REEST_TO); } trace_xprtrdma_op_connect(r_xprt, delay); - queue_delayed_work(system_long_wq, &r_xprt->rx_connect_worker, delay); + queue_delayed_work(system_dfl_long_wq, &r_xprt->rx_connect_worker, + delay); } /** From 91668417d4e925c98cae4a55b1b9860380ddbf16 Mon Sep 17 00:00:00 2001 From: Sagi Grimberg Date: Wed, 13 May 2026 09:58:24 +0300 Subject: [PATCH 09/42] pNFS/filelayout: fix cheking if a layout is striped A layout can still be striped with num_fh = 1 as it is perfectly possible that both MDS and DSs can handle the same filehandle. Hence check according to stripe_count > 1, which is the correct check to begin with. We should not be called with flseg->dsaddr = NULL, but if for some reason we do, return our best guess with is flseg->num_fh > 1. Fixes: a6b9d2fa0024 ("pNFS/filelayout: Fix coalescing test for single DS") Signed-off-by: Sagi Grimberg Signed-off-by: Anna Schumaker --- fs/nfs/filelayout/filelayout.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/nfs/filelayout/filelayout.c b/fs/nfs/filelayout/filelayout.c index f0f53f5dc871..72e20b56fbc7 100644 --- a/fs/nfs/filelayout/filelayout.c +++ b/fs/nfs/filelayout/filelayout.c @@ -778,6 +778,8 @@ filelayout_alloc_lseg(struct pnfs_layout_hdr *layoutid, static bool filelayout_lseg_is_striped(const struct nfs4_filelayout_segment *flseg) { + if (flseg->dsaddr) + return flseg->dsaddr->stripe_count > 1; return flseg->num_fh > 1; } From f74ad9eee7221ca67fa489b44c3d065c54e65d4d Mon Sep 17 00:00:00 2001 From: Sagi Grimberg Date: Tue, 28 Apr 2026 11:15:14 +0300 Subject: [PATCH 10/42] NFS: show redacted cert_serial and privkey_serial in mount options mount output should not reveal the contents of the serials, but indicate they were provided. Signed-off-by: Sagi Grimberg Signed-off-by: Anna Schumaker --- fs/nfs/super.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fs/nfs/super.c b/fs/nfs/super.c index 4cd420b14ce3..ff798b6685f8 100644 --- a/fs/nfs/super.c +++ b/fs/nfs/super.c @@ -509,6 +509,10 @@ static void nfs_show_mount_options(struct seq_file *m, struct nfs_server *nfss, default: break; } + if (clp->cl_xprtsec.cert_serial) + seq_puts(m, ",cert_serial="); + if (clp->cl_xprtsec.privkey_serial) + seq_puts(m, ",privkey_serial="); if (version != 4) nfs_show_mountd_options(m, nfss, showdefaults); From 35168eb947f230aaa35fd8416a30563ef89f5421 Mon Sep 17 00:00:00 2001 From: Dai Ngo Date: Thu, 23 Apr 2026 10:52:10 -0700 Subject: [PATCH 11/42] NFS: fix eof updates after NFSv4.2 fallocate/zero-range Generic/075 reliably exposes a regression when the client holds an NFSv4 write delegation: ZERO_RANGE/ALLOCATE extends the file on the server, but the local inode keeps the old i_size. The test then fails with 'Size error' because the post-op attribute refresh refuses to touch i_size while a delegation is outstanding, and the cached EOF was never marked stale. Update _nfs42_proc_fallocate() so that on success it: - bumps i_size when the operation extends the file, and - marks NFS_INO_INVALID_BLOCKS since the block count can also change Tested with xfstests generic/075 over NFSv4.2. Signed-off-by: Dai Ngo Signed-off-by: Anna Schumaker --- fs/nfs/nfs42proc.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/fs/nfs/nfs42proc.c b/fs/nfs/nfs42proc.c index 7602ede6f75f..ab86246fc364 100644 --- a/fs/nfs/nfs42proc.c +++ b/fs/nfs/nfs42proc.c @@ -81,12 +81,17 @@ static int _nfs42_proc_fallocate(struct rpc_message *msg, struct file *filep, status = nfs4_call_sync(server->client, server, msg, &args.seq_args, &res.seq_res, 0); if (status == 0) { - if (nfs_should_remove_suid(inode)) { - spin_lock(&inode->i_lock); + loff_t newsize = offset + len; + + spin_lock(&inode->i_lock); + if (newsize > i_size_read(inode)) + i_size_write(inode, newsize); + nfs_set_cache_invalid(inode, NFS_INO_INVALID_BLOCKS); + if (nfs_should_remove_suid(inode)) nfs_set_cache_invalid(inode, - NFS_INO_REVAL_FORCED | NFS_INO_INVALID_MODE); - spin_unlock(&inode->i_lock); - } + NFS_INO_REVAL_FORCED | + NFS_INO_INVALID_MODE); + spin_unlock(&inode->i_lock); status = nfs_post_op_update_inode_force_wcc(inode, res.falloc_fattr); } From 13e198a90ca4050f4bee8a3f23680389a6563ccc Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Mon, 18 May 2026 13:10:36 +0000 Subject: [PATCH 12/42] pNFS: Fix use-after-free in pnfs_update_layout() When hitting the NFS_LAYOUT_RETURN branch in pnfs_update_layout(), the code calls pnfs_prepare_to_retry_layoutget(lo). If it succeeds, pnfs_put_layout_hdr(lo) is called before trace_pnfs_update_layout(), which still references 'lo'. This results in a use-after-free when the tracepoint accesses lo's fields. Fix this by moving the tracepoint call before pnfs_put_layout_hdr(lo). Fixes: 2c8d5fc37fe2 ("pNFS: Stricter ordering of layoutget and layoutreturn") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Signed-off-by: Anna Schumaker --- fs/nfs/pnfs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/nfs/pnfs.c b/fs/nfs/pnfs.c index fdedeff5f6cc..cb203821a397 100644 --- a/fs/nfs/pnfs.c +++ b/fs/nfs/pnfs.c @@ -2229,11 +2229,11 @@ pnfs_update_layout(struct inode *ino, dprintk("%s wait for layoutreturn\n", __func__); lseg = ERR_PTR(pnfs_prepare_to_retry_layoutget(lo)); if (!IS_ERR(lseg)) { - pnfs_put_layout_hdr(lo); dprintk("%s retrying\n", __func__); trace_pnfs_update_layout(ino, pos, count, iomode, lo, lseg, PNFS_UPDATE_LAYOUT_RETRY); + pnfs_put_layout_hdr(lo); goto lookup_again; } trace_pnfs_update_layout(ino, pos, count, iomode, lo, lseg, From 2797ae7c929610fb2d2303a996a08173fa096730 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 26 May 2026 10:14:01 -0400 Subject: [PATCH 13/42] xprtrdma: Use sendctx DMA state for Send signaling Send signaling matters only when the prepared Send has page mappings to unmap. Today that test is expressed indirectly with rl_kref, because the Send-side reference is taken only for Sends with mapped SGEs. Split the SGE DMA unmap loop into its own helper and use sc_unmap_count directly for the signaling decision. This keeps the current behavior but removes one dependency on the old rl_kref semantics before the request lifetime rules are changed. Signed-off-by: Chuck Lever Signed-off-by: Anna Schumaker --- net/sunrpc/xprtrdma/frwr_ops.c | 2 +- net/sunrpc/xprtrdma/rpc_rdma.c | 22 +++++++++++++--------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/net/sunrpc/xprtrdma/frwr_ops.c b/net/sunrpc/xprtrdma/frwr_ops.c index 7f79a0a2601e..e5c71cf705a3 100644 --- a/net/sunrpc/xprtrdma/frwr_ops.c +++ b/net/sunrpc/xprtrdma/frwr_ops.c @@ -474,7 +474,7 @@ int frwr_send(struct rpcrdma_xprt *r_xprt, struct rpcrdma_req *req) ++num_wrs; } - if ((kref_read(&req->rl_kref) > 1) || num_wrs > ep->re_send_count) { + if (req->rl_sendctx->sc_unmap_count || num_wrs > ep->re_send_count) { send_wr->send_flags |= IB_SEND_SIGNALED; ep->re_send_count = min_t(unsigned int, ep->re_send_batch, num_wrs - ep->re_send_count); diff --git a/net/sunrpc/xprtrdma/rpc_rdma.c b/net/sunrpc/xprtrdma/rpc_rdma.c index 0e0f21974710..16b9987858d6 100644 --- a/net/sunrpc/xprtrdma/rpc_rdma.c +++ b/net/sunrpc/xprtrdma/rpc_rdma.c @@ -477,19 +477,11 @@ static void rpcrdma_sendctx_done(struct kref *kref) rep->rr_rxprt->rx_stats.reply_waits_for_send++; } -/** - * rpcrdma_sendctx_unmap - DMA-unmap Send buffer - * @sc: sendctx containing SGEs to unmap - * - */ -void rpcrdma_sendctx_unmap(struct rpcrdma_sendctx *sc) +static void rpcrdma_sendctx_dma_unmap(struct rpcrdma_sendctx *sc) { struct rpcrdma_regbuf *rb = sc->sc_req->rl_sendbuf; struct ib_sge *sge; - if (!sc->sc_unmap_count) - return; - /* The first two SGEs contain the transport header and * the inline buffer. These are always left mapped so * they can be cheaply re-used. @@ -498,7 +490,19 @@ void rpcrdma_sendctx_unmap(struct rpcrdma_sendctx *sc) ++sge, --sc->sc_unmap_count) ib_dma_unmap_page(rdmab_device(rb), sge->addr, sge->length, DMA_TO_DEVICE); +} +/** + * rpcrdma_sendctx_unmap - DMA-unmap Send buffer + * @sc: sendctx containing SGEs to unmap + * + */ +void rpcrdma_sendctx_unmap(struct rpcrdma_sendctx *sc) +{ + if (!sc->sc_unmap_count) + return; + + rpcrdma_sendctx_dma_unmap(sc); kref_put(&sc->sc_req->rl_kref, rpcrdma_sendctx_done); } From e786233d2e0bbff9a82e43f02ae3a46ab4b08ec3 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 26 May 2026 10:14:02 -0400 Subject: [PATCH 14/42] xprtrdma: Decouple req recycling from RPC completion rl_kref formerly served two distinct lifetimes through a single refcount: it gated when a Reply could wake its RPC task, and it gated when an rpcrdma_req could return to its free pool. The marshal path took the Send-side reference only when SGEs needed DMA-unmap (sc_unmap_count > 0), which made a Send carrying only pre-registered buffers an exception: the Reply handler dropped rl_kref from 1 to 0 and freed the req while the HCA might still be DMA-reading from its send buffer. Give rl_kref a narrower job. The RPC layer takes one reference when slot allocation hands a req out. rpcrdma_prepare_send_sges() takes a Send-side reference unconditionally after WR preparation succeeds. xprt_rdma_free_slot() and xprt_rdma_bc_free_rqst() drop the RPC-layer reference; rpcrdma_sendctx_unmap() drops the Send-side reference. The req returns to its free pool only after both owners have signed off. The existing kref_init(&req->rl_kref) call in rpcrdma_prepare_send_sges() is removed. Initialization moves to the slot-allocation paths (xprt_rdma_alloc_slot and rpcrdma_bc_rqst_get), and the release callback re-arms rl_kref before the req returns to a free pool. A re-init in the marshal path would discard the RPC-layer reference that already exists on entry. Three invariants follow: - Any rpcrdma_req held by an rpc_rqst has rl_kref >= 1. xprt_rdma_alloc_slot(), rpcrdma_bc_rqst_get(), and the backlog-wake branch in xprt_rdma_alloc_slot() each kref_init rl_kref before publishing the req. Without this invariant, an RPC task that aborts between slot allocation and marshal (gss_refresh failure or signal during call_connect, for example) would drive xprt_release() -> xprt_rdma_free_slot() -> kref_put against a refcount of zero, saturating refcount_t and stranding the slot. - The Send-side reference is taken only after WR prep succeeds. A mapping failure in rpcrdma_prepare_send_sges() runs rpcrdma_sendctx_cancel(), which DMA-unmaps the sendctx and clears sc_req without touching rl_kref. The sendctx ring walks in rpcrdma_sendctx_put_locked() and rpcrdma_sendctxs_destroy() skip entries with sc_req == NULL, so a burst of -EIO marshal failures cannot hold reqs off rb_send_bufs. - The release callback re-arms rl_kref so the next consumer enters with the invariant satisfied. Replies now complete the RPC directly. rpcrdma_reply_handler() calls rpcrdma_complete_rqst() in place of kref_put on the non-LocalInv branch. The LocalInv branch already completes the RPC from frwr_unmap_async() and is unaffected. Because Send-side references can now outlive RPC completion, connection teardown drains sendctx entries whose unsignaled Sends never had a later signaled completion to walk the ring. rpcrdma_sendctxs_destroy() walks the active range and runs rpcrdma_sendctx_unmap() on each entry with a non-NULL sc_req before the request buffers are reset, and is moved ahead of rpcrdma_reqs_reset() in rpcrdma_xprt_disconnect() so the reqs are still in their pre-reset state when the Send-side refs are released. The drain creates a teardown-ordering hazard on the backchannel path. With the new lifetime, releasing a bc_prealloc req from rpcrdma_req_release() re-adds it to bc_pa_list. The disconnect in xprt_rdma_destroy() runs after xprt_destroy_backchannel() has already emptied bc_pa_list, so the drained reqs would otherwise leak. xprt_rdma_destroy() now runs xprt_rdma_bc_destroy(xprt, 0) a second time after the disconnect to reclaim them. Fixes: 0ab115237025 ("xprtrdma: Wake RPCs directly in rpcrdma_wc_send path") Signed-off-by: Chuck Lever Signed-off-by: Anna Schumaker --- net/sunrpc/xprtrdma/backchannel.c | 5 ++- net/sunrpc/xprtrdma/rpc_rdma.c | 47 +++++++++++--------------- net/sunrpc/xprtrdma/transport.c | 55 ++++++++++++++++++++++++++++--- net/sunrpc/xprtrdma/verbs.c | 29 +++++++++++++--- net/sunrpc/xprtrdma/xprt_rdma.h | 2 +- 5 files changed, 97 insertions(+), 41 deletions(-) diff --git a/net/sunrpc/xprtrdma/backchannel.c b/net/sunrpc/xprtrdma/backchannel.c index 2f0f9618dd05..e5b3463da25f 100644 --- a/net/sunrpc/xprtrdma/backchannel.c +++ b/net/sunrpc/xprtrdma/backchannel.c @@ -159,9 +159,7 @@ void xprt_rdma_bc_free_rqst(struct rpc_rqst *rqst) rpcrdma_rep_put(&r_xprt->rx_buf, rep); req->rl_reply = NULL; - spin_lock(&xprt->bc_pa_lock); - list_add_tail(&rqst->rq_bc_pa_list, &xprt->bc_pa_list); - spin_unlock(&xprt->bc_pa_lock); + rpcrdma_req_put(req); xprt_put(xprt); } @@ -203,6 +201,7 @@ static struct rpc_rqst *rpcrdma_bc_rqst_get(struct rpcrdma_xprt *r_xprt) rqst->rq_xprt = xprt; __set_bit(RPC_BC_PA_IN_USE, &rqst->rq_bc_pa_state); xdr_buf_init(&rqst->rq_snd_buf, rdmab_data(req->rl_sendbuf), size); + kref_init(&req->rl_kref); return rqst; } diff --git a/net/sunrpc/xprtrdma/rpc_rdma.c b/net/sunrpc/xprtrdma/rpc_rdma.c index 16b9987858d6..69380f9dfa49 100644 --- a/net/sunrpc/xprtrdma/rpc_rdma.c +++ b/net/sunrpc/xprtrdma/rpc_rdma.c @@ -467,16 +467,6 @@ static int rpcrdma_encode_reply_chunk(struct rpcrdma_xprt *r_xprt, return 0; } -static void rpcrdma_sendctx_done(struct kref *kref) -{ - struct rpcrdma_req *req = - container_of(kref, struct rpcrdma_req, rl_kref); - struct rpcrdma_rep *rep = req->rl_reply; - - rpcrdma_complete_rqst(rep); - rep->rr_rxprt->rx_stats.reply_waits_for_send++; -} - static void rpcrdma_sendctx_dma_unmap(struct rpcrdma_sendctx *sc) { struct rpcrdma_regbuf *rb = sc->sc_req->rl_sendbuf; @@ -493,17 +483,26 @@ static void rpcrdma_sendctx_dma_unmap(struct rpcrdma_sendctx *sc) } /** - * rpcrdma_sendctx_unmap - DMA-unmap Send buffer + * rpcrdma_sendctx_unmap - DMA-unmap Send buffer and release Send owner * @sc: sendctx containing SGEs to unmap * */ void rpcrdma_sendctx_unmap(struct rpcrdma_sendctx *sc) { - if (!sc->sc_unmap_count) - return; + struct rpcrdma_req *req = sc->sc_req; rpcrdma_sendctx_dma_unmap(sc); - kref_put(&sc->sc_req->rl_kref, rpcrdma_sendctx_done); + sc->sc_req = NULL; + rpcrdma_req_put(req); +} + +/* No Send was posted. Release DMA mappings prepared for this + * sendctx, but leave the request reference count alone. + */ +static void rpcrdma_sendctx_cancel(struct rpcrdma_sendctx *sc) +{ + rpcrdma_sendctx_dma_unmap(sc); + sc->sc_req = NULL; } /* Prepare an SGE for the RPC-over-RDMA transport header. @@ -695,8 +694,6 @@ static bool rpcrdma_prepare_noch_mapped(struct rpcrdma_xprt *r_xprt, tail->iov_len)) return false; - if (req->rl_sendctx->sc_unmap_count) - kref_get(&req->rl_kref); return true; } @@ -726,7 +723,6 @@ static bool rpcrdma_prepare_readch(struct rpcrdma_xprt *r_xprt, len -= len & 3; if (!rpcrdma_prepare_tail_iov(req, xdr, page_base, len)) return false; - kref_get(&req->rl_kref); } return true; @@ -755,7 +751,6 @@ inline int rpcrdma_prepare_send_sges(struct rpcrdma_xprt *r_xprt, goto out_nosc; req->rl_sendctx->sc_unmap_count = 0; req->rl_sendctx->sc_req = req; - kref_init(&req->rl_kref); req->rl_wr.wr_cqe = &req->rl_sendctx->sc_cqe; req->rl_wr.sg_list = req->rl_sendctx->sc_sges; req->rl_wr.num_sge = 0; @@ -783,10 +778,14 @@ inline int rpcrdma_prepare_send_sges(struct rpcrdma_xprt *r_xprt, goto out_unmap; } + /* The Send-side owner releases this reference when the + * Send has completed. + */ + kref_get(&req->rl_kref); return 0; out_unmap: - rpcrdma_sendctx_unmap(req->rl_sendctx); + rpcrdma_sendctx_cancel(req->rl_sendctx); out_nosc: trace_xprtrdma_prepsend_failed(&req->rl_slot, ret); return ret; @@ -1364,14 +1363,6 @@ void rpcrdma_complete_rqst(struct rpcrdma_rep *rep) goto out; } -static void rpcrdma_reply_done(struct kref *kref) -{ - struct rpcrdma_req *req = - container_of(kref, struct rpcrdma_req, rl_kref); - - rpcrdma_complete_rqst(req->rl_reply); -} - /** * rpcrdma_reply_handler - Process received RPC/RDMA messages * @rep: Incoming rpcrdma_rep object to process @@ -1443,7 +1434,7 @@ void rpcrdma_reply_handler(struct rpcrdma_rep *rep) frwr_unmap_async(r_xprt, req); /* LocalInv completion will complete the RPC */ else - kref_put(&req->rl_kref, rpcrdma_reply_done); + rpcrdma_complete_rqst(rep); out_post: rpcrdma_post_recvs(r_xprt, diff --git a/net/sunrpc/xprtrdma/transport.c b/net/sunrpc/xprtrdma/transport.c index 1a54993f7ffb..875ad852a11d 100644 --- a/net/sunrpc/xprtrdma/transport.c +++ b/net/sunrpc/xprtrdma/transport.c @@ -279,6 +279,13 @@ xprt_rdma_destroy(struct rpc_xprt *xprt) cancel_delayed_work_sync(&r_xprt->rx_connect_worker); rpcrdma_xprt_disconnect(r_xprt); + + /* The disconnect's sendctx drain can return bc_prealloc reqs + * to bc_pa_list after xprt_destroy_backchannel() emptied it. + */ +#if defined(CONFIG_SUNRPC_BACKCHANNEL) + xprt_rdma_bc_destroy(xprt, 0); +#endif rpcrdma_buffer_destroy(&r_xprt->rx_buf); xprt_rdma_free_addresses(xprt); @@ -488,6 +495,45 @@ xprt_rdma_connect(struct rpc_xprt *xprt, struct rpc_task *task) delay); } +/* rl_kref has two owners while a Send is outstanding: the rpc_rqst + * owner and the sendctx. Replies complete the RPC but do not drop + * either reference. The req returns to its free pool only after + * xprt_rdma_free_slot() or xprt_rdma_bc_free_rqst() has dropped the + * RPC-layer reference and rpcrdma_sendctx_unmap() has dropped the + * Send-side reference. + */ +static void rpcrdma_req_release(struct kref *kref) +{ + struct rpcrdma_req *req = + container_of(kref, struct rpcrdma_req, rl_kref); + struct rpc_rqst *rqst = &req->rl_slot; + struct rpc_xprt *xprt = rqst->rq_xprt; + struct rpcrdma_xprt *r_xprt; + + kref_init(&req->rl_kref); + +#if defined(CONFIG_SUNRPC_BACKCHANNEL) + if (bc_prealloc(rqst)) { + spin_lock(&xprt->bc_pa_lock); + list_add_tail(&rqst->rq_bc_pa_list, &xprt->bc_pa_list); + spin_unlock(&xprt->bc_pa_lock); + return; + } +#endif + + if (xprt_wake_up_backlog(xprt, rqst)) + return; + + r_xprt = rpcx_to_rdmax(xprt); + memset(rqst, 0, sizeof(*rqst)); + rpcrdma_buffer_put(&r_xprt->rx_buf, req); +} + +void rpcrdma_req_put(struct rpcrdma_req *req) +{ + kref_put(&req->rl_kref, rpcrdma_req_release); +} + /** * xprt_rdma_alloc_slot - allocate an rpc_rqst * @xprt: controlling RPC transport @@ -506,6 +552,7 @@ xprt_rdma_alloc_slot(struct rpc_xprt *xprt, struct rpc_task *task) req = rpcrdma_buffer_get(&r_xprt->rx_buf); if (!req) goto out_sleep; + kref_init(&req->rl_kref); task->tk_rqstp = &req->rl_slot; task->tk_status = 0; return; @@ -521,6 +568,7 @@ xprt_rdma_alloc_slot(struct rpc_xprt *xprt, struct rpc_task *task) if (req) { struct rpc_rqst *rqst = &req->rl_slot; + kref_init(&req->rl_kref); if (!xprt_wake_up_backlog(xprt, rqst)) { memset(rqst, 0, sizeof(*rqst)); rpcrdma_buffer_put(&r_xprt->rx_buf, req); @@ -541,10 +589,7 @@ xprt_rdma_free_slot(struct rpc_xprt *xprt, struct rpc_rqst *rqst) container_of(xprt, struct rpcrdma_xprt, rx_xprt); rpcrdma_reply_put(&r_xprt->rx_buf, rpcr_to_rdmar(rqst)); - if (!xprt_wake_up_backlog(xprt, rqst)) { - memset(rqst, 0, sizeof(*rqst)); - rpcrdma_buffer_put(&r_xprt->rx_buf, rpcr_to_rdmar(rqst)); - } + rpcrdma_req_put(rpcr_to_rdmar(rqst)); } static bool rpcrdma_check_regbuf(struct rpcrdma_xprt *r_xprt, @@ -717,7 +762,7 @@ void xprt_rdma_print_stats(struct rpc_xprt *xprt, struct seq_file *seq) r_xprt->rx_stats.mrs_allocated, r_xprt->rx_stats.local_inv_needed, r_xprt->rx_stats.empty_sendctx_q, - r_xprt->rx_stats.reply_waits_for_send); + 0LU); /* was reply_waits_for_send; column preserved */ } static int diff --git a/net/sunrpc/xprtrdma/verbs.c b/net/sunrpc/xprtrdma/verbs.c index aecf9c0a153f..97b8b2376602 100644 --- a/net/sunrpc/xprtrdma/verbs.c +++ b/net/sunrpc/xprtrdma/verbs.c @@ -65,6 +65,8 @@ static int rpcrdma_sendctxs_create(struct rpcrdma_xprt *r_xprt); static void rpcrdma_sendctxs_destroy(struct rpcrdma_xprt *r_xprt); +static unsigned long rpcrdma_sendctx_next(struct rpcrdma_buffer *buf, + unsigned long item); static void rpcrdma_sendctx_put_locked(struct rpcrdma_xprt *r_xprt, struct rpcrdma_sendctx *sc); static int rpcrdma_reqs_setup(struct rpcrdma_xprt *r_xprt); @@ -571,9 +573,9 @@ void rpcrdma_xprt_disconnect(struct rpcrdma_xprt *r_xprt) rpcrdma_xprt_drain(r_xprt); rpcrdma_reps_unmap(r_xprt); + rpcrdma_sendctxs_destroy(r_xprt); rpcrdma_reqs_reset(r_xprt); rpcrdma_mrs_destroy(r_xprt); - rpcrdma_sendctxs_destroy(r_xprt); if (rpcrdma_ep_put(ep)) rdma_destroy_id(id); @@ -605,6 +607,20 @@ static void rpcrdma_sendctxs_destroy(struct rpcrdma_xprt *r_xprt) if (!buf->rb_sc_ctxs) return; + + /* The QP is drained, but the final unsignaled Sends might not + * have been walked by a signaled Send completion. Release those + * Send owners before request buffers are reset. + */ + for (i = rpcrdma_sendctx_next(buf, buf->rb_sc_tail); + i != rpcrdma_sendctx_next(buf, buf->rb_sc_head); + i = rpcrdma_sendctx_next(buf, i)) { + struct rpcrdma_sendctx *sc = buf->rb_sc_ctxs[i]; + + if (sc && sc->sc_req) + rpcrdma_sendctx_unmap(sc); + } + for (i = 0; i <= buf->rb_sc_last; i++) kfree(buf->rb_sc_ctxs[i]); kfree(buf->rb_sc_ctxs); @@ -739,15 +755,20 @@ static void rpcrdma_sendctx_put_locked(struct rpcrdma_xprt *r_xprt, struct rpcrdma_buffer *buf = &r_xprt->rx_buf; unsigned long next_tail; - /* Unmap SGEs of previously completed but unsignaled - * Sends by walking up the queue until @sc is found. + /* Release previously completed but unsignaled Sends by walking + * up the queue until @sc is found. Entries left behind by a + * failed rpcrdma_prepare_send_sges() have sc_req cleared. */ next_tail = buf->rb_sc_tail; do { + struct rpcrdma_sendctx *cur; + next_tail = rpcrdma_sendctx_next(buf, next_tail); /* ORDER: item must be accessed _before_ tail is updated */ - rpcrdma_sendctx_unmap(buf->rb_sc_ctxs[next_tail]); + cur = buf->rb_sc_ctxs[next_tail]; + if (cur->sc_req) + rpcrdma_sendctx_unmap(cur); } while (buf->rb_sc_ctxs[next_tail] != sc); diff --git a/net/sunrpc/xprtrdma/xprt_rdma.h b/net/sunrpc/xprtrdma/xprt_rdma.h index f53a77472724..f879d9b9f57e 100644 --- a/net/sunrpc/xprtrdma/xprt_rdma.h +++ b/net/sunrpc/xprtrdma/xprt_rdma.h @@ -427,7 +427,6 @@ struct rpcrdma_stats { /* accessed when receiving a reply */ unsigned long long total_rdma_reply; unsigned long long fixup_copy_count; - unsigned long reply_waits_for_send; unsigned long local_inv_needed; unsigned long nomsg_call_count; unsigned long bcall_count; @@ -505,6 +504,7 @@ void rpcrdma_buffer_put(struct rpcrdma_buffer *buffers, struct rpcrdma_req *req); void rpcrdma_rep_put(struct rpcrdma_buffer *buf, struct rpcrdma_rep *rep); void rpcrdma_reply_put(struct rpcrdma_buffer *buffers, struct rpcrdma_req *req); +void rpcrdma_req_put(struct rpcrdma_req *req); bool rpcrdma_regbuf_realloc(struct rpcrdma_regbuf *rb, size_t size, gfp_t flags); From 64bf6892057b746c55bcc045b9492741b72d8d27 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 26 May 2026 10:14:03 -0400 Subject: [PATCH 15/42] xprtrdma: Add request-pool slack for delayed recycling After the previous patch gates req recycling on Send completion, a completed RPC's rpcrdma_req can remain pinned by the sendctx ring until the next signaled Send completion releases it. The transmitted-RPC ceiling is unchanged: xprt_request_get_cong() gates Sends against xprt->cwnd, the RPC/RDMA credit window fed by server-granted credits and capped at re_max_requests. The req pool, however, must exceed max_reqs by enough that this recycle delay does not stall a slot allocation that the credit window would admit. The headroom is bounded. frwr_open() sets re_send_batch to re_max_requests >> 3 -- one in every eight Sends is signaled -- so at most re_send_batch unsignaled Sends can be outstanding before the next signaled completion releases them. That equals max_reqs / 8 reqs in the worst case, with a one-slot floor for small max_reqs values where the right-shift rounds to zero. The sendctx ring and the hardware Send Queue are not enlarged to match. Both are sized in rpcrdma_sendctxs_create() and frwr_query_device() for re_max_requests in-flight Sends, which is the ceiling the credit window enforces. The pool slack does not raise that ceiling -- it only lets allocation keep pace with the credit window during the brief interval in which earlier reqs are pinned waiting for the next signaled completion. At any moment, at most re_send_batch sendctxes are held by unswept unsignaled Sends, leaving the rest of the ring available for newly admitted Sends. Allocate max_reqs + DIV_ROUND_UP(max_reqs, 8) request objects and name the slack calculation at the allocation site so the 1/8 bound stays tied to the Send-signaling batch size. Signed-off-by: Chuck Lever Signed-off-by: Anna Schumaker --- net/sunrpc/xprtrdma/verbs.c | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/net/sunrpc/xprtrdma/verbs.c b/net/sunrpc/xprtrdma/verbs.c index 97b8b2376602..98bd965787e6 100644 --- a/net/sunrpc/xprtrdma/verbs.c +++ b/net/sunrpc/xprtrdma/verbs.c @@ -1080,6 +1080,22 @@ static void rpcrdma_reps_destroy(struct rpcrdma_buffer *buf) spin_unlock(&buf->rb_lock); } +static unsigned int rpcrdma_req_pool_slack(unsigned int max_reqs) +{ + /* The sendctx ring can hold up to one Send-signaling batch + * (re_send_batch, set by frwr_open() to re_max_requests >> 3) + * of unfinished Sends. Each pins its req until a signaled Send + * completion releases the sendctx. Size the pool above max_reqs + * by that batch so the recycle delay does not stall a slot + * allocation that the RPC/RDMA credit window would admit. + * + * Round up: re_max_requests >> 3 is zero when max_reqs < 8, but + * a single unsignaled Send is still enough to pin one req. One + * slack slot covers that case. + */ + return DIV_ROUND_UP(max_reqs, 8); +} + /** * rpcrdma_buffer_create - Create initial set of req/rep objects * @r_xprt: transport instance to (re)initialize @@ -1089,6 +1105,7 @@ static void rpcrdma_reps_destroy(struct rpcrdma_buffer *buf) int rpcrdma_buffer_create(struct rpcrdma_xprt *r_xprt) { struct rpcrdma_buffer *buf = &r_xprt->rx_buf; + unsigned int max_reqs; int i, rc; buf->rb_bc_srv_max_requests = 0; @@ -1102,7 +1119,9 @@ int rpcrdma_buffer_create(struct rpcrdma_xprt *r_xprt) INIT_LIST_HEAD(&buf->rb_all_reps); rc = -ENOMEM; - for (i = 0; i < r_xprt->rx_xprt.max_reqs; i++) { + max_reqs = r_xprt->rx_xprt.max_reqs; + max_reqs += rpcrdma_req_pool_slack(max_reqs); + for (i = 0; i < max_reqs; i++) { struct rpcrdma_req *req; req = rpcrdma_req_create(r_xprt, From 2ae8e7afbc63bf84243367f89eb43571f0345a74 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 26 May 2026 10:14:04 -0400 Subject: [PATCH 16/42] xprtrdma: Clear receive-side ownership pointers on release Three small ownership-state cleanups land the transport in a state that lets future reviewers reason about each pointer locally rather than tracing the whole reply path: rpcrdma_rep_put() clears rep->rr_rqst before the rep enters rb_free_reps so that no rep on the free list still carries a stale rqst pointer. rpcrdma_reply_handler() and rpcrdma_unpin_rqst() are the only sites that set rr_rqst; rpcrdma_reply_handler() hands the rep through rpcrdma_rep_put(), and rpcrdma_unpin_rqst() NULLs rr_rqst directly because its error path abandons the rep for teardown cleanup rather than returning it to rb_free_reps. rpcrdma_reply_put() NULLs req->rl_reply before calling rpcrdma_rep_put(). The previous order placed the rep on rb_free_reps while req->rl_reply still pointed at it; the window was harmless because xprt_rdma_free_slot() holds the req exclusively across the pair, but closing it makes the invariant 'rep on rb_free_reps implies no req references it' strictly checkable. rpcrdma_sendctx_unmap() and rpcrdma_sendctx_cancel() clear req->rl_sendctx after dropping the sendctx pointer in the sendctx ring. Without this, req->rl_sendctx survives across Send completion and points at a sendctx that may already have been reassigned by rpcrdma_sendctx_get_locked() to a different req. No caller dereferences the stale pointer today -- rpcrdma_prepare_send_sges() overwrites it before the next Send -- but a NULL is a more honest representation of 'the Send is no longer outstanding' and lets the assertion patch that follows trip on any future regression. Signed-off-by: Chuck Lever Signed-off-by: Anna Schumaker --- net/sunrpc/xprtrdma/rpc_rdma.c | 4 ++++ net/sunrpc/xprtrdma/verbs.c | 12 ++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/net/sunrpc/xprtrdma/rpc_rdma.c b/net/sunrpc/xprtrdma/rpc_rdma.c index 69380f9dfa49..f4b4abefc4e0 100644 --- a/net/sunrpc/xprtrdma/rpc_rdma.c +++ b/net/sunrpc/xprtrdma/rpc_rdma.c @@ -493,6 +493,7 @@ void rpcrdma_sendctx_unmap(struct rpcrdma_sendctx *sc) rpcrdma_sendctx_dma_unmap(sc); sc->sc_req = NULL; + req->rl_sendctx = NULL; rpcrdma_req_put(req); } @@ -501,8 +502,11 @@ void rpcrdma_sendctx_unmap(struct rpcrdma_sendctx *sc) */ static void rpcrdma_sendctx_cancel(struct rpcrdma_sendctx *sc) { + struct rpcrdma_req *req = sc->sc_req; + rpcrdma_sendctx_dma_unmap(sc); sc->sc_req = NULL; + req->rl_sendctx = NULL; } /* Prepare an SGE for the RPC-over-RDMA transport header. diff --git a/net/sunrpc/xprtrdma/verbs.c b/net/sunrpc/xprtrdma/verbs.c index 98bd965787e6..60cbc14c5299 100644 --- a/net/sunrpc/xprtrdma/verbs.c +++ b/net/sunrpc/xprtrdma/verbs.c @@ -1043,9 +1043,15 @@ static struct rpcrdma_rep *rpcrdma_rep_get_locked(struct rpcrdma_buffer *buf) * @buf: buffer pool * @rep: rep to release * + * The rep's transient association with an rpc_rqst, established + * by rpcrdma_reply_handler() and torn down here, must not survive + * onto rb_free_reps: rpcrdma_post_recvs() pulls reps from the free + * list to re-post them, and a non-NULL rr_rqst on a free-listed rep + * would imply the rep is still referenced by a req. */ void rpcrdma_rep_put(struct rpcrdma_buffer *buf, struct rpcrdma_rep *rep) { + rep->rr_rqst = NULL; llist_add(&rep->rr_node, &buf->rb_free_reps); } @@ -1247,9 +1253,11 @@ rpcrdma_mr_get(struct rpcrdma_xprt *r_xprt) */ void rpcrdma_reply_put(struct rpcrdma_buffer *buffers, struct rpcrdma_req *req) { - if (req->rl_reply) { - rpcrdma_rep_put(buffers, req->rl_reply); + struct rpcrdma_rep *rep = req->rl_reply; + + if (rep) { req->rl_reply = NULL; + rpcrdma_rep_put(buffers, rep); } } From 797943e8bd1ffcc63bfe79d24faad9a77054ec40 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 26 May 2026 10:14:05 -0400 Subject: [PATCH 17/42] xprtrdma: Document and assert reply-handler invariants The xprtrdma reply path has been the subject of recurring LLM-driven review claims that 'an RPC can complete while receive buffers are still DMA-mapped' or that 'the req can be freed while the HCA still owns the send buffer.' No runtime reproducer has surfaced, but the absence of a written-down invariant set lets each pass of automated review reach the same hypothetical conclusion. Subsequent fixes against ce2f9a4d9ccc ('xprtrdma: Decouple req recycling from RPC completion') closed the underlying races but did not document the closure where future readers will look for it. State the invariants explicitly in a comment above rpcrdma_reply_handler() and back four of them with WARN_ON_ONCE() probes positioned where each invariant is locally checkable on the previous patch's cleaned-up ownership state: - I1 (Receive WR ownership): WARN at rpcrdma_post_recvs() that a rep pulled from rb_free_reps carries rr_rqst == NULL. - I2 (rep attachment): WARN at rpcrdma_reply_put() that req->rl_reply was NULLed before the matching rep_put. - I3 (Registered-MR fence): WARN at rpcrdma_complete_rqst() that req->rl_registered is empty. Strong send-queue ordering of the LocalInv WR chain makes the last completion observe the ib_dma_unmap_sg() of every earlier MR, so 'list empty' implies 'all MRs unmapped'. - I4 (Send-buffer release): WARN at rpcrdma_req_release() that req->rl_sendctx is NULL. Reaching the kref release callback requires both the RPC-layer and Send-side references to have dropped; the Send-side drop runs in rpcrdma_sendctx_unmap(), which clears rl_sendctx (previous patch). A non-NULL rl_sendctx here would mean the Send-side owner had not run -- a contradiction. The XXX comment in xprt_rdma_free() about signal-driven release racing the Send completion described the pre-decouple state. Replace it with a one-line note pointing at the invariant set, since the kref scheme now holds the req across the in-flight Send regardless of which path released the rpc_task. I5 (req lifecycle) is stated in the comment but not probed: making it locally assertible would require moving kref_init out of rpcrdma_req_release(), which in turn requires adding kref_init to the bc_pa_list and backlog-wake reuse paths. That restructuring is deferred -- the invariant is unchanged either way. Signed-off-by: Chuck Lever Signed-off-by: Anna Schumaker --- net/sunrpc/xprtrdma/rpc_rdma.c | 69 +++++++++++++++++++++++++++++++++ net/sunrpc/xprtrdma/transport.c | 13 +++++-- net/sunrpc/xprtrdma/verbs.c | 6 +++ 3 files changed, 84 insertions(+), 4 deletions(-) diff --git a/net/sunrpc/xprtrdma/rpc_rdma.c b/net/sunrpc/xprtrdma/rpc_rdma.c index f4b4abefc4e0..626cadec4555 100644 --- a/net/sunrpc/xprtrdma/rpc_rdma.c +++ b/net/sunrpc/xprtrdma/rpc_rdma.c @@ -1336,6 +1336,11 @@ void rpcrdma_complete_rqst(struct rpcrdma_rep *rep) struct rpc_rqst *rqst = rep->rr_rqst; int status; + /* I3: every registered MR has been invalidated and + * ib_dma_unmap_sg()'d before complete_rqst runs. + */ + WARN_ON_ONCE(!list_empty(&rpcr_to_rdmar(rqst)->rl_registered)); + switch (rep->rr_proc) { case rdma_msg: status = rpcrdma_decode_msg(r_xprt, rep, rqst); @@ -1367,6 +1372,70 @@ void rpcrdma_complete_rqst(struct rpcrdma_rep *rep) goto out; } +/* Reply-side ownership invariants + * + * I1 (Receive WR ownership). A struct rpcrdma_rep is owned by the + * HCA between ib_post_recv() and the matching Receive completion. + * After ib_dma_sync_single_for_cpu() in rpcrdma_wc_receive() it is + * owned by the CPU until rpcrdma_rep_put() returns it to + * rb_free_reps; a rep on rb_free_reps is not re-posted until + * rpcrdma_post_recvs() pulls it off. Asserted: rpcrdma_post_recvs() + * WARNs that a pulled rep has rr_rqst == NULL. + * + * I2 (rep attachment). While req->rl_reply == rep, the rep cannot be + * re-posted. rpcrdma_reply_put() NULLs req->rl_reply before handing + * the rep to rpcrdma_rep_put(). Asserted: rpcrdma_reply_put() WARNs + * that rl_reply is NULL after the put. + * + * I3 (Registered-MR fence). On entry to rpcrdma_complete_rqst() every + * MR that was on req->rl_registered has had its rkey invalidated + * (remotely via IB_WC_WITH_INVALIDATE or locally via IB_WR_LOCAL_INV) + * and its pages ib_dma_unmap_sg()'d. The LocalInv chain is posted + * on a single QP; strong send-queue ordering makes the last + * completion (frwr_wc_localinv_done) observe the + * ib_dma_unmap_sg() that ran from each earlier completion's + * frwr_mr_put() before complete_rqst is called. The inline + * frwr_reminv() path unmaps its one MR synchronously before + * rpcrdma_reply_handler() reaches complete_rqst. Asserted: + * rpcrdma_complete_rqst() WARNs that rl_registered is empty. + * + * I4 (Send-buffer release). req->rl_kref carries two unconditional + * owners while a Send is outstanding: the RPC-layer reference (set + * at xprt_rdma_alloc_slot / xprt_rdma_bc_rqst_get / rpcrdma_req_release + * pool-entry) and the Send-side reference (kref_get() in + * rpcrdma_prepare_send_sges()). rpcrdma_req_release() runs only + * after both have dropped, so the req does not return to its free + * pool until rpcrdma_sendctx_unmap() has fired -- the HCA has + * released the send buffer before the req can be reused. Asserted: + * rpcrdma_req_release() WARNs that rl_sendctx is NULL. + * + * I5 (req lifecycle). A req is owned by the RPC layer between slot + * acquisition and the matching xprt_rdma_free_slot() (or, for the + * backchannel, xprt_rdma_bc_free_rqst()). While owned, rl_kref >= 1. + * The pools (rb_send_bufs, bc_pa_list, backlog wake target) never + * contain a req with outstanding Send-side or Reply-side work. + * + * Non-hazards. The following claims have been raised by adversarial + * review and are each closed by the invariants above: + * + * * "Reply completes the RPC while the HCA still holds the send + * buffer" -- excluded by I4. The Send-side kref reference is held + * until rpcrdma_sendctx_unmap() runs from Send completion. + * + * * "Signal-driven release races the in-flight Send" -- same + * resolution. xprt_rdma_free() does not touch rl_kref; the + * Send-side reference keeps the req out of its pool until Send + * completion fires. + * + * * "Receive completion races rep reuse" -- excluded by I1. A rep + * is on rb_free_reps only after rpcrdma_rep_put() has been called + * and rpcrdma_post_recvs() owns the next transition back to the HCA. + * + * * "Pages still DMA-mapped when call_decode reads them" -- excluded + * by I3. The matching ib_dma_unmap_sg() for every MR has run on + * the same CPU thread that calls rpcrdma_complete_rqst(). + */ + /** * rpcrdma_reply_handler - Process received RPC/RDMA messages * @rep: Incoming rpcrdma_rep object to process diff --git a/net/sunrpc/xprtrdma/transport.c b/net/sunrpc/xprtrdma/transport.c index 875ad852a11d..d4e6746d8ecd 100644 --- a/net/sunrpc/xprtrdma/transport.c +++ b/net/sunrpc/xprtrdma/transport.c @@ -510,6 +510,11 @@ static void rpcrdma_req_release(struct kref *kref) struct rpc_xprt *xprt = rqst->rq_xprt; struct rpcrdma_xprt *r_xprt; + /* I4: both the RPC-layer and Send-side owners have dropped, + * so rpcrdma_sendctx_unmap() has cleared rl_sendctx. + */ + WARN_ON_ONCE(req->rl_sendctx); + kref_init(&req->rl_kref); #if defined(CONFIG_SUNRPC_BACKCHANNEL) @@ -653,10 +658,10 @@ xprt_rdma_free(struct rpc_task *task) frwr_unmap_sync(rpcx_to_rdmax(rqst->rq_xprt), req); } - /* XXX: If the RPC is completing because of a signal and - * not because a reply was received, we ought to ensure - * that the Send completion has fired, so that memory - * involved with the Send is not still visible to the NIC. + /* The Send-side rl_kref owner keeps req out of its free pool + * until rpcrdma_sendctx_unmap() has fired -- see I4 above + * rpcrdma_reply_handler() -- so signal-driven release here + * does not let the HCA touch a recycled send buffer. */ } diff --git a/net/sunrpc/xprtrdma/verbs.c b/net/sunrpc/xprtrdma/verbs.c index 60cbc14c5299..da2c6fa44154 100644 --- a/net/sunrpc/xprtrdma/verbs.c +++ b/net/sunrpc/xprtrdma/verbs.c @@ -1259,6 +1259,10 @@ void rpcrdma_reply_put(struct rpcrdma_buffer *buffers, struct rpcrdma_req *req) req->rl_reply = NULL; rpcrdma_rep_put(buffers, rep); } + /* I2: rl_reply NULL after the put closes the + * 'rep on rb_free_reps still referenced by req' window. + */ + WARN_ON_ONCE(req->rl_reply); } /** @@ -1435,6 +1439,8 @@ void rpcrdma_post_recvs(struct rpcrdma_xprt *r_xprt, int needed) rep = rpcrdma_rep_create(r_xprt); if (!rep) break; + /* I1: a rep on rb_free_reps must carry no rqst pointer. */ + WARN_ON_ONCE(rep->rr_rqst); if (!rpcrdma_regbuf_dma_map(r_xprt, rep->rr_rdmabuf)) { rpcrdma_rep_put(buf, rep); break; From 04dd9cdbe59a9056b4cc8de07e1585db77661cbc Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Mon, 1 Jun 2026 13:54:12 -0400 Subject: [PATCH 18/42] xprtrdma: Fix I3 invariant comment in rpcrdma_complete_rqst frwr_unmap_sync() and frwr_unmap_async() drain rl_registered via rpcrdma_mr_pop() before posting invalidation Work Requests to hardware. The WARN_ON_ONCE verifies that the list-drain step has occurred, not that hardware unmapping has completed. Reword the comment to match what the assertion actually checks. Signed-off-by: Chuck Lever Signed-off-by: Anna Schumaker --- net/sunrpc/xprtrdma/rpc_rdma.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/sunrpc/xprtrdma/rpc_rdma.c b/net/sunrpc/xprtrdma/rpc_rdma.c index 626cadec4555..f115baba6d56 100644 --- a/net/sunrpc/xprtrdma/rpc_rdma.c +++ b/net/sunrpc/xprtrdma/rpc_rdma.c @@ -1336,8 +1336,8 @@ void rpcrdma_complete_rqst(struct rpcrdma_rep *rep) struct rpc_rqst *rqst = rep->rr_rqst; int status; - /* I3: every registered MR has been invalidated and - * ib_dma_unmap_sg()'d before complete_rqst runs. + /* I3: rl_registered has been drained by frwr_unmap before + * complete_rqst runs. */ WARN_ON_ONCE(!list_empty(&rpcr_to_rdmar(rqst)->rl_registered)); From bf822d717edecfbf2e6b3eec1bef3813cbeacdd4 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Mon, 1 Jun 2026 13:54:13 -0400 Subject: [PATCH 19/42] xprtrdma: Remove tautological I2 assertion in rpcrdma_reply_put rpcrdma_reply_put() sets req->rl_reply to NULL when it is non-NULL, and skips the block when it is already NULL. The WARN_ON_ONCE(req->rl_reply) that follows can never fire because both paths leave rl_reply NULL. Remove the dead assertion and its comment. Signed-off-by: Chuck Lever Signed-off-by: Anna Schumaker --- net/sunrpc/xprtrdma/verbs.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/net/sunrpc/xprtrdma/verbs.c b/net/sunrpc/xprtrdma/verbs.c index da2c6fa44154..92c691d2521f 100644 --- a/net/sunrpc/xprtrdma/verbs.c +++ b/net/sunrpc/xprtrdma/verbs.c @@ -1259,10 +1259,6 @@ void rpcrdma_reply_put(struct rpcrdma_buffer *buffers, struct rpcrdma_req *req) req->rl_reply = NULL; rpcrdma_rep_put(buffers, rep); } - /* I2: rl_reply NULL after the put closes the - * 'rep on rb_free_reps still referenced by req' window. - */ - WARN_ON_ONCE(req->rl_reply); } /** From 77b160b2d863d37f36b6c38e80a7d259ce939e69 Mon Sep 17 00:00:00 2001 From: Dai Ngo Date: Tue, 26 May 2026 16:29:53 -0700 Subject: [PATCH 20/42] NFSv4/pnfs: defer return_range callbacks until after inode unlock Sometimes unmounting an NFS filesystem mounted with pNFS SCSI layouts triggers the following warning: BUG: scheduling while atomic: umount.nfs4/... __schedule_bug+0xbd/0x100 schedule_debug.constprop.0+0x19f/0x220 __schedule+0x10d/0x10a0 schedule+0x74/0x190 schedule_timeout+0xf5/0x220 io_schedule_timeout+0xd5/0x160 __wait_for_common+0x186/0x4b0 blk_execute_rq+0x2ef/0x3a0 scsi_execute_cmd+0x1ff/0x700 sd_pr_out_command.isra.0+0x242/0x380 [sd_mod] bl_unregister_scsi.constprop.0+0x109/0x3c0 [blocklayoutdriver] bl_unregister_dev+0x175/0x1c0 [blocklayoutdriver] bl_free_device+0x1f/0x1b0 [blocklayoutdriver] bl_free_deviceid_node+0x12/0x30 [blocklayoutdriver] nfs4_put_deviceid_node+0x171/0x360 [nfsv4] ext_tree_remove+0x11c/0x1d0 [blocklayoutdriver] _pnfs_return_layout+0x416/0x900 [nfsv4] nfs4_evict_inode+0x108/0x130 [nfsv4] evict+0x316/0x750 dispose_list+0xf1/0x1a0 evict_inodes+0x33f/0x440 generic_shutdown_super+0xc9/0x4e0 kill_anon_super+0x3a/0x90 nfs_kill_super+0x44/0x60 [nfs] deactivate_locked_super+0xb8/0x1b0 cleanup_mnt+0x25a/0x380 task_work_run+0x13e/0x210 exit_to_user_mode_loop+0x169/0x400 do_syscall_64+0x467/0x1550 entry_SYSCALL_64_after_hwframe+0x76/0x7e The warning occurs because the block layout driver unregisters the SCSI device while the inode lock is still held. Device unregistration issues a SCSI PR command, which may sleep, resulting in a "scheduling while atomic" warning. During layout return, ext_tree_remove() invokes the layout driver's return_range callback while holding the inode lock. For block layouts, this callback eventually calls bl_unregister_scsi(), which may block in scsi_execute_cmd() while issuing PR commands to the device. Fix this by deferring the return_range callbacks until after the inode lock has been released. The layout header reference count is incremented before invoking return_range(), ensuring that the layout header remains valid while the layout driver removes extents from the extent tree. Fixes: c88953d87f5c8 ("pnfs: add return_range method") Signed-off-by: Dai Ngo Signed-off-by: Anna Schumaker --- fs/nfs/callback_proc.c | 9 +++++---- fs/nfs/pnfs.c | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/fs/nfs/callback_proc.c b/fs/nfs/callback_proc.c index 4ea9221ded42..10f2354ba304 100644 --- a/fs/nfs/callback_proc.c +++ b/fs/nfs/callback_proc.c @@ -257,6 +257,7 @@ static u32 initiate_file_draining(struct nfs_client *clp, struct pnfs_layout_hdr *lo; u32 rv = NFS4ERR_NOMATCHING_LAYOUT; LIST_HEAD(free_me_list); + bool return_range = false; ino = nfs_layout_find_inode(clp, &args->cbl_fh, &args->cbl_stateid); if (IS_ERR(ino)) { @@ -301,13 +302,13 @@ static u32 initiate_file_draining(struct nfs_client *clp, /* Embrace your forgetfulness! */ rv = NFS4ERR_NOMATCHING_LAYOUT; - if (NFS_SERVER(ino)->pnfs_curr_ld->return_range) { - NFS_SERVER(ino)->pnfs_curr_ld->return_range(lo, - &args->cbl_range); - } + return_range = true; } unlock: spin_unlock(&ino->i_lock); + if (return_range && NFS_SERVER(ino)->pnfs_curr_ld->return_range) + NFS_SERVER(ino)->pnfs_curr_ld->return_range(lo, + &args->cbl_range); pnfs_free_lseg_list(&free_me_list); /* Free all lsegs that are attached to commit buckets */ nfs_commit_inode(ino, 0); diff --git a/fs/nfs/pnfs.c b/fs/nfs/pnfs.c index cb203821a397..7715e2bd5871 100644 --- a/fs/nfs/pnfs.c +++ b/fs/nfs/pnfs.c @@ -1463,8 +1463,6 @@ _pnfs_return_layout(struct inode *ino) pnfs_clear_layoutcommit(ino, &tmp_list); pnfs_mark_matching_lsegs_return(lo, &tmp_list, &range, 0); - if (NFS_SERVER(ino)->pnfs_curr_ld->return_range) - NFS_SERVER(ino)->pnfs_curr_ld->return_range(lo, &range); /* Don't send a LAYOUTRETURN if list was initially empty */ if (!test_bit(NFS_LAYOUT_RETURN_REQUESTED, &lo->plh_flags) || @@ -1476,6 +1474,8 @@ _pnfs_return_layout(struct inode *ino) send = pnfs_prepare_layoutreturn(lo, &stateid, &cred, NULL); spin_unlock(&ino->i_lock); + if (NFS_SERVER(ino)->pnfs_curr_ld->return_range) + NFS_SERVER(ino)->pnfs_curr_ld->return_range(lo, &range); if (send) status = pnfs_send_layoutreturn(lo, &stateid, &cred, IOMODE_ANY, 0); From 3ff72e1cdf5c337b6acfcf3fcef748c5b9a5316b Mon Sep 17 00:00:00 2001 From: Clark Wang Date: Tue, 2 Jun 2026 19:04:38 +0800 Subject: [PATCH 21/42] nfs: keep PG_UPTODATE clear after read errors in page groups When a read request is split into multiple subrequests, earlier completions may advance PG_UPTODATE state for the page group once their bytes fall within hdr->good_bytes. If a later subrequest in the same group then completes with NFS_IOHDR_ERROR, the read path needs to clear any accumulated PG_UPTODATE state and keep later completions from rebuilding it. Otherwise, a subsequent successful subrequest can re-enter nfs_page_group_set_uptodate(), restore the page-group sync state, and leave stale PG_UPTODATE behind for nfs_page_group_destroy() to trip over in nfs_free_request(). Add a sticky page-group read-failed flag. Once any subrequest in the group is known to be bad, mark the group failed, clear any accumulated PG_UPTODATE state, and refuse further PG_UPTODATE synchronization for the rest of the completion walk. Fixes: 67d0338edd71 ("nfs: page group syncing in read path") Signed-off-by: Clark Wang Signed-off-by: Anna Schumaker --- fs/nfs/read.c | 25 ++++++++++++++++++++++++- include/linux/nfs_page.h | 1 + 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/fs/nfs/read.c b/fs/nfs/read.c index e1fe78d7b8d0..2b70bd2b934b 100644 --- a/fs/nfs/read.c +++ b/fs/nfs/read.c @@ -132,10 +132,32 @@ static void nfs_readpage_release(struct nfs_page *req, int error) static void nfs_page_group_set_uptodate(struct nfs_page *req) { - if (nfs_page_group_sync_on_bit(req, PG_UPTODATE)) + bool uptodate = false; + + nfs_page_group_lock(req); + if (!test_bit(PG_READ_FAILED, &req->wb_head->wb_flags) && + nfs_page_group_sync_on_bit_locked(req, PG_UPTODATE)) + uptodate = true; + nfs_page_group_unlock(req); + + if (uptodate) folio_mark_uptodate(nfs_page_to_folio(req)); } +static void nfs_page_group_mark_read_failed(struct nfs_page *req) +{ + struct nfs_page *tmp; + + nfs_page_group_lock(req); + set_bit(PG_READ_FAILED, &req->wb_head->wb_flags); + tmp = req; + do { + clear_bit(PG_UPTODATE, &tmp->wb_flags); + tmp = tmp->wb_this_page; + } while (tmp != req); + nfs_page_group_unlock(req); +} + static void nfs_read_completion(struct nfs_pgio_header *hdr) { unsigned long bytes = 0; @@ -172,6 +194,7 @@ static void nfs_read_completion(struct nfs_pgio_header *hdr) if (bytes <= hdr->good_bytes) nfs_page_group_set_uptodate(req); else { + nfs_page_group_mark_read_failed(req); error = hdr->error; xchg(&nfs_req_openctx(req)->error, error); } diff --git a/include/linux/nfs_page.h b/include/linux/nfs_page.h index afe1d8f09d89..4b9a35dbc062 100644 --- a/include/linux/nfs_page.h +++ b/include/linux/nfs_page.h @@ -33,6 +33,7 @@ enum { PG_TEARDOWN, /* page group sync for destroy */ PG_UNLOCKPAGE, /* page group sync bit in read path */ PG_UPTODATE, /* page group sync bit in read path */ + PG_READ_FAILED, /* page group saw a read error */ PG_WB_END, /* page group sync bit in write path */ PG_REMOVE, /* page group sync bit in write path */ PG_CONTENDED1, /* Is someone waiting for a lock? */ From 17d90b68c3a3d7d7e95b49e1fe9381a723f637a8 Mon Sep 17 00:00:00 2001 From: Hongling Zeng Date: Wed, 3 Jun 2026 09:36:52 +0800 Subject: [PATCH 22/42] sunrpc: fix uninitialized xprt_create_args structure The xprt_create_args structure is allocated on the stack without initialization in rpc_sysfs_xprt_switch_add_xprt_store(). While some fields are manually populated, critical fields like srcaddr, bc_xps, and flags contain uninitialized stack garbage. This can lead to: 1. Kernel panic when xs_setup_xprt() dereferences garbage srcaddr 2. Information leak if srcaddr points to sensitive stack data 3. Unpredictable behavior if flags has random bits set The fix is to zero-initialize the structure to ensure all unused fields are NULL/0, preventing the transport setup code from acting on garbage data. Cc: stable@vger.kernel.org Suggested-by: Jeff Layton Reviewed-by: Jeff Layton Signed-off-by: Hongling Zeng Signed-off-by: Anna Schumaker --- net/sunrpc/sysfs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/sunrpc/sysfs.c b/net/sunrpc/sysfs.c index 49686bf740e6..e638b92b7ad1 100644 --- a/net/sunrpc/sysfs.c +++ b/net/sunrpc/sysfs.c @@ -327,7 +327,7 @@ static ssize_t rpc_sysfs_xprt_switch_add_xprt_store(struct kobject *kobj, { struct rpc_xprt_switch *xprt_switch = rpc_sysfs_xprt_switch_kobj_get_xprt(kobj); - struct xprt_create xprt_create_args; + struct xprt_create xprt_create_args = {}; struct rpc_xprt *xprt, *new; if (!xprt_switch) From 7a375cafc14ed151508f908ea5681caf0a9cc1d6 Mon Sep 17 00:00:00 2001 From: Mike Snitzer Date: Thu, 4 Jun 2026 16:24:02 -0400 Subject: [PATCH 23/42] NFSv4/flexfiles: honor FF_FLAGS_NO_IO_THRU_MDS on fatal DS connect errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit f06bedfa62d5 ("pNFS/flexfiles: don't attempt pnfs on fatal DS errors") teaches ff_layout_{read,write}_pagelist() to return PNFS_NOT_ATTEMPTED when nfs4_ff_layout_prepare_ds() fails with a nfs_error_is_fatal() errno (e.g. -ETIMEDOUT from a SOFTCONN connect deadline, -ENOMEM, -ERESTARTSYS), so that the client gives up instead of spinning. pnfs_do_{read,write}() then dispatches the I/O through pnfs_{read,write}_through_mds() → nfs_pageio_reset_{read,write}_mds(). That fallback is unconditional and silently violates FF_FLAGS_NO_IO_THRU_MDS: when the layout segment carries the flag (typically single-mirror appliance layouts where MDS I/O is explicitly forbidden), the out_failed: path's \`&& !ds_fatal_error\` clause overrides the flag's short-circuit through ff_layout_avoid_mds_available_ds() and routes the I/O to the MDS file handle anyway. This is reachable in practice during a data-server restart: SOFTCONN exhaustion produces -ETIMEDOUT, which is fatal per nfs_error_is_fatal(), which triggers PNFS_NOT_ATTEMPTED, which silently goes to MDS. Preserve the upstream "don't spin on fatal errors" intent for layouts that permit MDS fallback. For layouts with FF_FLAGS_NO_IO_THRU_MDS set, mark the layout for return and request PNFS_TRY_AGAIN instead; if the server cannot supply a usable layout the failure now surfaces cleanly via pnfs_update_layout(), rather than via silent MDS I/O that contradicts the flag. Fixes: f06bedfa62d5 ("pNFS/flexfiles: don't attempt pnfs on fatal DS errors") Assisted-by: Claude:claude-opus-4-7 Signed-off-by: Mike Snitzer Signed-off-by: Anna Schumaker --- fs/nfs/flexfilelayout/flexfilelayout.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/fs/nfs/flexfilelayout/flexfilelayout.c b/fs/nfs/flexfilelayout/flexfilelayout.c index 99caa8d28c25..0a4a06d93e6a 100644 --- a/fs/nfs/flexfilelayout/flexfilelayout.c +++ b/fs/nfs/flexfilelayout/flexfilelayout.c @@ -2204,6 +2204,14 @@ ff_layout_read_pagelist(struct nfs_pgio_header *hdr) out_failed: if (ff_layout_avoid_mds_available_ds(lseg) && !ds_fatal_error) return PNFS_TRY_AGAIN; + if (ff_layout_no_fallback_to_mds(lseg)) { + /* + * FF_FLAGS_NO_IO_THRU_MDS: force fresh LAYOUTGET, + * never fall through to MDS I/O. + */ + pnfs_error_mark_layout_for_return(hdr->inode, lseg); + return PNFS_TRY_AGAIN; + } trace_pnfs_mds_fallback_read_pagelist(hdr->inode, hdr->args.offset, hdr->args.count, IOMODE_READ, NFS_I(hdr->inode)->layout, lseg); @@ -2289,6 +2297,14 @@ ff_layout_write_pagelist(struct nfs_pgio_header *hdr, int sync) out_failed: if (ff_layout_avoid_mds_available_ds(lseg) && !ds_fatal_error) return PNFS_TRY_AGAIN; + if (ff_layout_no_fallback_to_mds(lseg)) { + /* + * FF_FLAGS_NO_IO_THRU_MDS: force fresh LAYOUTGET, + * never fall through to MDS I/O. + */ + pnfs_error_mark_layout_for_return(hdr->inode, lseg); + return PNFS_TRY_AGAIN; + } trace_pnfs_mds_fallback_write_pagelist(hdr->inode, hdr->args.offset, hdr->args.count, IOMODE_RW, NFS_I(hdr->inode)->layout, lseg); From 1d62e659c0bf11649cf48e002c2a55d148f2610a Mon Sep 17 00:00:00 2001 From: Mike Snitzer Date: Thu, 4 Jun 2026 16:24:03 -0400 Subject: [PATCH 24/42] NFSv4/flexfiles: honor FF_FLAGS_NO_IO_THRU_MDS in pg_get_mirror_count_write The FF_FLAGS_NO_IO_THRU_MDS flag lives on each lseg, so any fallback decision made when there is no current lseg (e.g. between LAYOUTRETURN and the next LAYOUTGET) cannot run the per-lseg check. Introduce a sticky hdr-level ditto for FF_FLAGS_NO_IO_THRU_MDS in struct nfs4_flexfile_layout::flags (NFS4_FF_HDR_NO_IO_THRU_MDS bit), set whenever ff_layout_alloc_lseg() parses an lseg with the flag. The bit is never cleared for the lifetime of the layout hdr; the server is assumed to be consistent in its no-fallback policy per file. kzalloc() in ff_layout_alloc_layout_hdr() zero-initializes the field. Use the new ff_layout_hdr_no_fallback_to_mds() helper to gate ff_layout_pg_get_mirror_count_write(): when pnfs_update_layout() returns NULL (e.g. NFS_LAYOUT_BULK_RECALL, pnfs_layout_io_test_failed, pnfs_layoutgets_blocked) the existing code unconditionally calls nfs_pageio_reset_write_mds(). This is a source of unwanted WRITE to MDS. Fix it by checking NFS4_FF_HDR_NO_IO_THRU_MDS bit, and if set surface -EAGAIN instead; the writepage-side caller (nfs_do_writepage() for buffered, nfs_direct_write_reschedule() for O_DIRECT) then redirties the request so writeback retries via pNFS. Fixes: 260074cd8413 ("pNFS/flexfiles: Add support for FF_FLAGS_NO_IO_THRU_MDS") Assisted-by: Claude:claude-opus-4-7 Signed-off-by: Mike Snitzer Signed-off-by: Anna Schumaker --- fs/nfs/flexfilelayout/flexfilelayout.c | 13 +++++++++++++ fs/nfs/flexfilelayout/flexfilelayout.h | 16 ++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/fs/nfs/flexfilelayout/flexfilelayout.c b/fs/nfs/flexfilelayout/flexfilelayout.c index 0a4a06d93e6a..c4aa995026f6 100644 --- a/fs/nfs/flexfilelayout/flexfilelayout.c +++ b/fs/nfs/flexfilelayout/flexfilelayout.c @@ -636,6 +636,9 @@ ff_layout_alloc_lseg(struct pnfs_layout_hdr *lh, if (!p) goto out_sort_mirrors; fls->flags = be32_to_cpup(p); + if (fls->flags & FF_FLAGS_NO_IO_THRU_MDS) + set_bit(NFS4_FF_HDR_NO_IO_THRU_MDS, + &FF_LAYOUT_FROM_HDR(lh)->flags); p = xdr_inline_decode(&stream, 4); if (!p) @@ -1185,6 +1188,16 @@ ff_layout_pg_get_mirror_count_write(struct nfs_pageio_descriptor *pgio, 0, NFS4_MAX_UINT64, IOMODE_RW, NFS_I(pgio->pg_inode)->layout, pgio->pg_lseg); + if (NFS_I(pgio->pg_inode)->layout && + ff_layout_hdr_no_fallback_to_mds(NFS_I(pgio->pg_inode)->layout)) { + /* + * FF_FLAGS_NO_IO_THRU_MDS: no current lseg but the server's + * policy forbids MDS fallback. Surface -EAGAIN so writeback + * retries rather than silently issuing the WRITE via MDS. + */ + pgio->pg_error = -EAGAIN; + goto out; + } /* no lseg means that pnfs is not in use, so no mirroring here */ nfs_pageio_reset_write_mds(pgio); out: diff --git a/fs/nfs/flexfilelayout/flexfilelayout.h b/fs/nfs/flexfilelayout/flexfilelayout.h index 17a008c8e97c..a5bd00f69e82 100644 --- a/fs/nfs/flexfilelayout/flexfilelayout.h +++ b/fs/nfs/flexfilelayout/flexfilelayout.h @@ -112,12 +112,16 @@ struct nfs4_ff_layout_segment { struct nfs4_ff_layout_mirror *mirror_array[] __counted_by(mirror_array_cnt); }; +/* nfs4_flexfile_layout::flags bit indices */ +#define NFS4_FF_HDR_NO_IO_THRU_MDS 0 /* any lseg has had FF_FLAGS_NO_IO_THRU_MDS */ + struct nfs4_flexfile_layout { struct pnfs_layout_hdr generic_hdr; struct pnfs_ds_commit_info commit_info; struct list_head mirrors; struct list_head error_list; /* nfs4_ff_layout_ds_err */ ktime_t last_report_time; /* Layoutstat report times */ + unsigned long flags; }; struct nfs4_flexfile_layoutreturn_args { @@ -184,6 +188,18 @@ ff_layout_no_fallback_to_mds(struct pnfs_layout_segment *lseg) return FF_LAYOUT_LSEG(lseg)->flags & FF_FLAGS_NO_IO_THRU_MDS; } +/* + * Sticky hdr-level mirror of FF_FLAGS_NO_IO_THRU_MDS so callers that have + * no current lseg (e.g. between LAYOUTRETURN and the next LAYOUTGET) can + * still honor the no-MDS-fallback policy. + */ +static inline bool +ff_layout_hdr_no_fallback_to_mds(struct pnfs_layout_hdr *lo) +{ + return test_bit(NFS4_FF_HDR_NO_IO_THRU_MDS, + &FF_LAYOUT_FROM_HDR(lo)->flags); +} + static inline bool ff_layout_no_read_on_rw(struct pnfs_layout_segment *lseg) { From e3a78029444777b7bb75693cfa8090b189e47cdc Mon Sep 17 00:00:00 2001 From: Dylan Yudaken Date: Sun, 7 Jun 2026 08:31:54 +0100 Subject: [PATCH 25/42] nfs: add nowait version of nfs_start_io_direct nfs_start_io_direct might block on existing operations to the same inode. In order to support NOWAIT O_DIRECT reads, add a non-blocking version of this nfs_start_io_direct that just returns -EAGAIN if locks could not be taken. Signed-off-by: Dylan Yudaken Signed-off-by: Anna Schumaker --- fs/nfs/internal.h | 1 + fs/nfs/io.c | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h index 18d46b0e71dd..0c9aca624353 100644 --- a/fs/nfs/internal.h +++ b/fs/nfs/internal.h @@ -532,6 +532,7 @@ extern void nfs_end_io_read(struct inode *inode); extern __must_check int nfs_start_io_write(struct inode *inode); extern void nfs_end_io_write(struct inode *inode); extern __must_check int nfs_start_io_direct(struct inode *inode); +extern __must_check int nfs_start_io_direct_nowait(struct inode *inode); extern void nfs_end_io_direct(struct inode *inode); static inline bool nfs_file_io_is_buffered(struct nfs_inode *nfsi) diff --git a/fs/nfs/io.c b/fs/nfs/io.c index 8337f0ae852d..2faf2003faf6 100644 --- a/fs/nfs/io.c +++ b/fs/nfs/io.c @@ -109,6 +109,16 @@ static void nfs_block_buffered(struct nfs_inode *nfsi, struct inode *inode) } } +static int nfs_block_buffered_nowait(struct nfs_inode *nfsi, struct inode *inode) +{ + if (!test_bit(NFS_INO_ODIRECT, &nfsi->flags)) { + if (inode->i_mapping->nrpages != 0) + return 1; + set_bit(NFS_INO_ODIRECT, &nfsi->flags); + } + return 0; +} + /** * nfs_start_io_direct - declare the file is being used for direct i/o * @inode: file inode @@ -149,6 +159,37 @@ nfs_start_io_direct(struct inode *inode) return 0; } +/** + * nfs_start_io_direct_nowait - non-blocking variant of nfs_start_io_direct() + * @inode: file inode + * + * Try to declare that a direct I/O operation is about to start without + * blocking. + * Ensure all buffered I/O is blocked. + * If this could not be done without blocking then returns -EAGAIN. + */ +int +nfs_start_io_direct_nowait(struct inode *inode) +{ + struct nfs_inode *nfsi = NFS_I(inode); + + if (!down_read_trylock(&inode->i_rwsem)) + return -EAGAIN; + if (test_bit(NFS_INO_ODIRECT, &nfsi->flags)) + return 0; + up_read(&inode->i_rwsem); + + /* Slow path: try to flip NFS_INO_ODIRECT without blocking. */ + if (!down_write_trylock(&inode->i_rwsem)) + return -EAGAIN; + if (nfs_block_buffered_nowait(nfsi, inode)) { + up_write(&inode->i_rwsem); + return -EAGAIN; + } + downgrade_write(&inode->i_rwsem); + return 0; +} + /** * nfs_end_io_direct - declare that the direct i/o operation is done * @inode: file inode From ef74e4453856716dbdaba06eaee5251e37e6882e Mon Sep 17 00:00:00 2001 From: Dylan Yudaken Date: Sun, 7 Jun 2026 08:31:55 +0100 Subject: [PATCH 26/42] nfs: expose FMODE_NOWAIT for read-only files NFS O_DIRECT reads already (mostly) handle async requests, with the exception of locking the inode for direct. Handle async requests properly by using nfs_start_io_direct_nowait, and then expose FMODE_NOWAIT since it's now supported for direct reads. Signed-off-by: Dylan Yudaken Signed-off-by: Anna Schumaker --- fs/nfs/direct.c | 12 ++++++++++-- fs/nfs/file.c | 16 +++++++++++++++- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/fs/nfs/direct.c b/fs/nfs/direct.c index 48d89716193a..e626c72495e6 100644 --- a/fs/nfs/direct.c +++ b/fs/nfs/direct.c @@ -466,14 +466,22 @@ ssize_t nfs_file_direct_read(struct kiocb *iocb, struct iov_iter *iter, goto out_release; } dreq->l_ctx = l_ctx; - if (!is_sync_kiocb(iocb)) + if (!is_sync_kiocb(iocb)) { dreq->iocb = iocb; + } else if (iocb->ki_flags & IOCB_NOWAIT) { + result = -EAGAIN; + nfs_direct_req_release(dreq); + goto out_release; + } if (user_backed_iter(iter)) dreq->flags = NFS_ODIRECT_SHOULD_DIRTY; if (!swap) { - result = nfs_start_io_direct(inode); + if (iocb->ki_flags & IOCB_NOWAIT) + result = nfs_start_io_direct_nowait(inode); + else + result = nfs_start_io_direct(inode); if (result) { /* release the reference that would usually be * consumed by nfs_direct_read_schedule_iovec() diff --git a/fs/nfs/file.c b/fs/nfs/file.c index 25048a3c2364..a0d8f1c1cf10 100644 --- a/fs/nfs/file.c +++ b/fs/nfs/file.c @@ -72,8 +72,12 @@ nfs_file_open(struct inode *inode, struct file *filp) return res; res = nfs_open(inode, filp); - if (res == 0) + if (res == 0) { filp->f_mode |= FMODE_CAN_ODIRECT; + /* flag NOWAIT on read-only files only */ + if (!(filp->f_mode & FMODE_WRITE)) + filp->f_mode |= FMODE_NOWAIT; + } return res; } @@ -166,6 +170,10 @@ nfs_file_read(struct kiocb *iocb, struct iov_iter *to) if (iocb->ki_flags & IOCB_DIRECT) return nfs_file_direct_read(iocb, to, false); + /* NOWAIT only supported on direct reads */ + if (iocb->ki_flags & IOCB_NOWAIT) + return -EAGAIN; + dprintk("NFS: read(%pD2, %zu@%lu)\n", iocb->ki_filp, iov_iter_count(to), (unsigned long) iocb->ki_pos); @@ -705,6 +713,12 @@ ssize_t nfs_file_write(struct kiocb *iocb, struct iov_iter *from) trace_nfs_file_write(iocb, from); + /* + * FMODE_NOWAIT is not set for writable files + */ + if (WARN_ON_ONCE(iocb->ki_flags & IOCB_NOWAIT)) + return -EAGAIN; + result = nfs_key_timeout_notify(file, inode); if (result) return result; From d616d8bec3b11962735c9c9ff53fb4972162b324 Mon Sep 17 00:00:00 2001 From: Lei Yin Date: Fri, 24 Apr 2026 09:26:41 +0000 Subject: [PATCH 27/42] NFSv4.1/pNFS: fix LAYOUTCOMMIT retry loop on OLD_STATEID Handle -NFS4ERR_OLD_STATEID in nfs4_layoutcommit_done(). This issue was reproduced on NFSv4.2. Without refreshing data->args.stateid, LAYOUTCOMMIT can keep retrying with the same stale stateid after OLD_STATEID, resulting in an unbounded retry loop. Refresh the layout stateid with nfs4_layout_refresh_old_stateid() and restart the RPC only after a successful refresh. Changes since v1: update refreshed stateid in inode layout header. Signed-off-by: Lei Yin [Anna: Fix up dprintk() format specifier] Signed-off-by: Anna Schumaker --- fs/nfs/nfs4proc.c | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c index 0d77d50f4de7..393ce50274d2 100644 --- a/fs/nfs/nfs4proc.c +++ b/fs/nfs/nfs4proc.c @@ -9988,6 +9988,38 @@ nfs4_layoutcommit_done(struct rpc_task *task, void *calldata) case -NFS4ERR_GRACE: /* loca_recalim always false */ task->tk_status = 0; break; + case -NFS4ERR_OLD_STATEID: { + u32 old_seqid = be32_to_cpu(data->args.stateid.seqid); + struct pnfs_layout_range range = { + .iomode = IOMODE_ANY, + .offset = 0, + .length = NFS4_MAX_UINT64, + }; + + if (nfs4_layout_refresh_old_stateid(&data->args.stateid, + &range, + data->args.inode)) { + struct pnfs_layout_hdr *lo; + + spin_lock(&data->args.inode->i_lock); + lo = NFS_I(data->args.inode)->layout; + if (lo && pnfs_layout_is_valid(lo) && + nfs4_stateid_match_other(&data->args.stateid, + &lo->plh_stateid)) + pnfs_set_layout_stateid(lo, &data->args.stateid, + NULL, false); + spin_unlock(&data->args.inode->i_lock); + + dprintk("%s: refreshed OLD_STATEID inode %llu seq %u->%u\n", + __func__, data->args.inode->i_ino, + old_seqid, + be32_to_cpu(data->args.stateid.seqid)); + + rpc_restart_call_prepare(task); + return; + } + fallthrough; + } case 0: break; default: From 4837fb36219e6c08b666bc31a86841bad8526358 Mon Sep 17 00:00:00 2001 From: Yang Erkun Date: Thu, 26 Feb 2026 09:22:03 +0800 Subject: [PATCH 28/42] nfs: use nfsi->rwsem to protect traversal of the file lock list Lingfeng identified a bug and suggested two solutions, but both appear to have issues. Generally, we cannot release flc_lock while iterating over the file lock list to avoid use-after-free (UAF) problems with file locks. However, functions like nfs_delegation_claim_locks and nfs4_reclaim_locks cannot adhere to this rule because recover_lock or nfs4_lock_delegation_recall may take a long time. To resolve this, NFS switches to using nfsi->rwsem for the same protection, and nfs_reclaim_locks follows this approach. Although nfs_delegation_claim_locks uses so_delegreturn_mutex instead, this is inadequate since a single inode can have multiple nfs4_state instances. Therefore, the fix is to also use nfsi->rwsem in this case. Furthermore, after commit c69899a17ca4 ("NFSv4: Update of VFS byte range lock must be atomic with the stateid update"), the functions nfs4_locku_done and nfs4_lock_done also break this rule because they call locks_lock_inode_wait without holding nfsi->rwsem. Simply adding this protection could cause many deadlocks, so instead, the call to locks_lock_inode_wait is moved into _nfs4_proc_setlk. Regarding the bug fixed by commit c69899a17ca4 ("NFSv4: Update of VFS byte range lock must be atomic with the stateid update"), it has been resolved after commit 0460253913e5 ("NFSv4: nfs4_do_open() is incorrectly triggering state recovery") because all slots are drained before calling nfs4_do_reclaim, which prevents concurrent stateid changes along this path. Also, nfs_delegation_claim_locks does not cause this concurrency either since when _nfs4_proc_setlk is called with NFS_DELEGATED_STATE, no RPC is sent, so nfs4_lock_done is not called. Therefore, nfs4_lock_delegation_recall from nfs_delegation_claim_locks is the first time the stateid is set. Reported-by: Li Lingfeng Closes: https://lore.kernel.org/all/20250419085709.1452492-1-lilingfeng3@huawei.com/ Closes: https://lore.kernel.org/all/20250715030559.2906634-1-lilingfeng3@huawei.com/ Fixes: c69899a17ca4 ("NFSv4: Update of VFS byte range lock must be atomic with the stateid update") Signed-off-by: Yang Erkun Reviewed-by: Jeff Layton Signed-off-by: Anna Schumaker --- fs/nfs/delegation.c | 9 ++++++++- fs/nfs/nfs4proc.c | 22 +++++++++++----------- include/linux/nfs_xdr.h | 1 - 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/fs/nfs/delegation.c b/fs/nfs/delegation.c index 122fb3f14ffb..9546d2195c25 100644 --- a/fs/nfs/delegation.c +++ b/fs/nfs/delegation.c @@ -173,6 +173,7 @@ int nfs4_check_delegation(struct inode *inode, fmode_t type) static int nfs_delegation_claim_locks(struct nfs4_state *state, const nfs4_stateid *stateid) { struct inode *inode = state->inode; + struct nfs_inode *nfsi = NFS_I(inode); struct file_lock *fl; struct file_lock_context *flctx = locks_inode_context(inode); struct list_head *list; @@ -182,6 +183,9 @@ static int nfs_delegation_claim_locks(struct nfs4_state *state, const nfs4_state goto out; list = &flctx->flc_posix; + + /* Guard against reclaim and new lock/unlock calls */ + down_write(&nfsi->rwsem); spin_lock(&flctx->flc_lock); restart: for_each_file_lock(fl, list) { @@ -189,8 +193,10 @@ static int nfs_delegation_claim_locks(struct nfs4_state *state, const nfs4_state continue; spin_unlock(&flctx->flc_lock); status = nfs4_lock_delegation_recall(fl, state, stateid); - if (status < 0) + if (status < 0) { + up_write(&nfsi->rwsem); goto out; + } spin_lock(&flctx->flc_lock); } if (list == &flctx->flc_posix) { @@ -198,6 +204,7 @@ static int nfs_delegation_claim_locks(struct nfs4_state *state, const nfs4_state goto restart; } spin_unlock(&flctx->flc_lock); + up_write(&nfsi->rwsem); out: return status; } diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c index 393ce50274d2..d089ac262df3 100644 --- a/fs/nfs/nfs4proc.c +++ b/fs/nfs/nfs4proc.c @@ -7084,7 +7084,6 @@ static void nfs4_locku_done(struct rpc_task *task, void *data) switch (task->tk_status) { case 0: renew_lease(calldata->server, calldata->timestamp); - locks_lock_inode_wait(calldata->lsp->ls_state->inode, &calldata->fl); if (nfs4_update_lock_stateid(calldata->lsp, &calldata->res.stateid)) break; @@ -7352,11 +7351,6 @@ static void nfs4_lock_done(struct rpc_task *task, void *calldata) case 0: renew_lease(NFS_SERVER(d_inode(data->ctx->dentry)), data->timestamp); - if (data->arg.new_lock && !data->cancelled) { - data->fl.c.flc_flags &= ~(FL_SLEEP | FL_ACCESS); - if (locks_lock_inode_wait(lsp->ls_state->inode, &data->fl) < 0) - goto out_restart; - } if (data->arg.new_lock_owner != 0) { nfs_confirm_seqid(&lsp->ls_seqid, 0); nfs4_stateid_copy(&lsp->ls_stateid, &data->res.stateid); @@ -7467,11 +7461,10 @@ static int _nfs4_do_setlk(struct nfs4_state *state, int cmd, struct file_lock *f msg.rpc_argp = &data->arg; msg.rpc_resp = &data->res; task_setup_data.callback_data = data; - if (recovery_type > NFS_LOCK_NEW) { - if (recovery_type == NFS_LOCK_RECLAIM) - data->arg.reclaim = NFS_LOCK_RECLAIM; - } else - data->arg.new_lock = 1; + + if (recovery_type == NFS_LOCK_RECLAIM) + data->arg.reclaim = NFS_LOCK_RECLAIM; + task = rpc_run_task(&task_setup_data); if (IS_ERR(task)) return PTR_ERR(task); @@ -7581,6 +7574,13 @@ static int _nfs4_proc_setlk(struct nfs4_state *state, int cmd, struct file_lock up_read(&nfsi->rwsem); mutex_unlock(&sp->so_delegreturn_mutex); status = _nfs4_do_setlk(state, cmd, request, NFS_LOCK_NEW); + if (status) + goto out; + + down_read(&nfsi->rwsem); + request->c.flc_flags &= ~(FL_SLEEP | FL_ACCESS); + status = locks_lock_inode_wait(state->inode, request); + up_read(&nfsi->rwsem); out: request->c.flc_flags = flags; return status; diff --git a/include/linux/nfs_xdr.h b/include/linux/nfs_xdr.h index fcbd21b5685f..40417e3a7f85 100644 --- a/include/linux/nfs_xdr.h +++ b/include/linux/nfs_xdr.h @@ -580,7 +580,6 @@ struct nfs_lock_args { struct nfs_lowner lock_owner; unsigned char block : 1; unsigned char reclaim : 1; - unsigned char new_lock : 1; unsigned char new_lock_owner : 1; }; From 7cf5e4fc36d3a77467dd0d02a45371ea4350410b Mon Sep 17 00:00:00 2001 From: Ethan Nelson-Moore Date: Mon, 8 Jun 2026 19:56:04 -0700 Subject: [PATCH 29/42] NFS: correct CONFIG_NFS_V4 macro name in #endif comment A comment in fs/nfs/dir.c incorrectly refers to CONFIG_NFSV4 instead of CONFIG_NFS_V4. Correct it. Discovered while searching for CONFIG_* symbols referenced in code but not defined in any Kconfig file. Signed-off-by: Ethan Nelson-Moore Signed-off-by: Anna Schumaker --- fs/nfs/dir.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c index 659e39b1562b..fb8633fa2ac4 100644 --- a/fs/nfs/dir.c +++ b/fs/nfs/dir.c @@ -2299,7 +2299,7 @@ nfs4_lookup_revalidate(struct inode *dir, const struct qstr *name, return nfs_do_lookup_revalidate(dir, name, dentry, flags); } -#endif /* CONFIG_NFSV4 */ +#endif /* CONFIG_NFS_V4 */ int nfs_atomic_open_v23(struct inode *dir, struct dentry *dentry, struct file *file, unsigned int open_flags, From 417efc520207615df1083567f674085be697b845 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Fri, 5 Jun 2026 16:40:33 -0400 Subject: [PATCH 30/42] xprtrdma: Convert send buffer free list to llist rpcrdma_buffer_get() and rpcrdma_buffer_put() both take rb_lock to pop/push from the rb_send_bufs free list. Under high I/O concurrency (e.g., nconnect=N with small random writes), this spinlock is contended between the request submission path and the transport completion path. Replace the list_head with an llist_head. The put side uses lockless llist_add(), which is safe for concurrent producers. The get side retains the spinlock to satisfy the llist single-consumer contract portably; submitters continue to serialize there. Completion handlers returning buffers no longer contend on rb_lock, eliminating contention on the return path. rb_lock remains for the MR free list and the tracking lists used during setup and teardown. rb_free_reps already uses llist_head, so the llist idiom is established in this structure. The precedent is the data structure, not the locking: rb_free_reps serializes its single consumer through the re_receiving gate in rpcrdma_post_recvs, whereas rb_send_bufs serializes its consumer with rb_lock. Both satisfy the llist single-consumer contract. Signed-off-by: Chuck Lever Signed-off-by: Anna Schumaker --- net/sunrpc/xprtrdma/verbs.c | 32 ++++++++++++++------------------ net/sunrpc/xprtrdma/xprt_rdma.h | 8 ++++---- 2 files changed, 18 insertions(+), 22 deletions(-) diff --git a/net/sunrpc/xprtrdma/verbs.c b/net/sunrpc/xprtrdma/verbs.c index 92c691d2521f..993bc5c444a4 100644 --- a/net/sunrpc/xprtrdma/verbs.c +++ b/net/sunrpc/xprtrdma/verbs.c @@ -1120,7 +1120,7 @@ int rpcrdma_buffer_create(struct rpcrdma_xprt *r_xprt) INIT_LIST_HEAD(&buf->rb_all_mrs); INIT_WORK(&buf->rb_refresh_worker, rpcrdma_mr_refresh_worker); - INIT_LIST_HEAD(&buf->rb_send_bufs); + init_llist_head(&buf->rb_send_bufs); INIT_LIST_HEAD(&buf->rb_allreqs); INIT_LIST_HEAD(&buf->rb_all_reps); @@ -1134,7 +1134,7 @@ int rpcrdma_buffer_create(struct rpcrdma_xprt *r_xprt) RPCRDMA_V1_DEF_INLINE_SIZE * 2); if (!req) goto out; - list_add(&req->rl_list, &buf->rb_send_bufs); + llist_add(&req->rl_node, &buf->rb_send_bufs); } init_llist_head(&buf->rb_free_reps); @@ -1214,16 +1214,14 @@ static void rpcrdma_mrs_destroy(struct rpcrdma_xprt *r_xprt) void rpcrdma_buffer_destroy(struct rpcrdma_buffer *buf) { + struct rpcrdma_req *req, *next; + struct llist_node *node; + rpcrdma_reps_destroy(buf); - while (!list_empty(&buf->rb_send_bufs)) { - struct rpcrdma_req *req; - - req = list_first_entry(&buf->rb_send_bufs, - struct rpcrdma_req, rl_list); - list_del(&req->rl_list); + node = llist_del_all(&buf->rb_send_bufs); + llist_for_each_entry_safe(req, next, node, rl_node) rpcrdma_req_destroy(req); - } } /** @@ -1270,15 +1268,15 @@ void rpcrdma_reply_put(struct rpcrdma_buffer *buffers, struct rpcrdma_req *req) struct rpcrdma_req * rpcrdma_buffer_get(struct rpcrdma_buffer *buffers) { - struct rpcrdma_req *req; + struct llist_node *node; + /* Calls to llist_del_first are required to be serialized */ spin_lock(&buffers->rb_lock); - req = list_first_entry_or_null(&buffers->rb_send_bufs, - struct rpcrdma_req, rl_list); - if (req) - list_del_init(&req->rl_list); + node = llist_del_first(&buffers->rb_send_bufs); spin_unlock(&buffers->rb_lock); - return req; + if (!node) + return NULL; + return llist_entry(node, struct rpcrdma_req, rl_node); } /** @@ -1291,9 +1289,7 @@ void rpcrdma_buffer_put(struct rpcrdma_buffer *buffers, struct rpcrdma_req *req) { rpcrdma_reply_put(buffers, req); - spin_lock(&buffers->rb_lock); - list_add(&req->rl_list, &buffers->rb_send_bufs); - spin_unlock(&buffers->rb_lock); + llist_add(&req->rl_node, &buffers->rb_send_bufs); } /* Returns a pointer to a rpcrdma_regbuf object, or NULL. diff --git a/net/sunrpc/xprtrdma/xprt_rdma.h b/net/sunrpc/xprtrdma/xprt_rdma.h index f879d9b9f57e..ae036719f840 100644 --- a/net/sunrpc/xprtrdma/xprt_rdma.h +++ b/net/sunrpc/xprtrdma/xprt_rdma.h @@ -332,7 +332,7 @@ enum { struct rpcrdma_buffer; struct rpcrdma_req { - struct list_head rl_list; + struct llist_node rl_node; struct rpc_rqst rl_slot; struct rpcrdma_rep *rl_reply; struct xdr_stream rl_stream; @@ -374,14 +374,14 @@ rpcrdma_mr_pop(struct list_head *list) } /* - * struct rpcrdma_buffer -- holds list/queue of pre-registered memory for - * inline requests/replies, and client/server credits. + * struct rpcrdma_buffer -- holds pre-registered memory for inline + * requests/replies, and client/server credits. * * One of these is associated with a transport instance */ struct rpcrdma_buffer { spinlock_t rb_lock; - struct list_head rb_send_bufs; + struct llist_head rb_send_bufs; struct list_head rb_mrs; unsigned long rb_sc_head; From af9b65b29af341932625c4283dc7a23cdb62688a Mon Sep 17 00:00:00 2001 From: Chris Mason Date: Thu, 4 Jun 2026 13:06:33 -0400 Subject: [PATCH 31/42] xprtrdma: Fix ep kref imbalance on ADDR_CHANGE rpcrdma_cm_event_handler() falls through to the disconnected: label on RDMA_CM_EVENT_ADDR_CHANGE and calls rpcrdma_ep_put() with no matching get when the event arrives before RDMA_CM_EVENT_ESTABLISHED. The kref then underflows during connect teardown and rpcrdma_xprt_disconnect() operates on a freed ep. Reference counts across a normal connection lifecycle: rpcrdma_ep_create() kref_init ->1 rpcrdma_xprt_connect() ep_get ->2 (before post_recvs) RDMA_CM_EVENT_ESTABLISHED ep_get ->3 RDMA_CM_EVENT_DISCONNECTED ep_put ->2 rpcrdma_xprt_drain() ep_put ->1 rpcrdma_xprt_disconnect() tail ep_put ->0 (ep_destroy) The connect-time get in rpcrdma_xprt_connect(), taken just before rpcrdma_post_recvs() "while there are outstanding Receives," is balanced by rpcrdma_xprt_drain. ADDR_CHANGE before ESTABLISHED has no get to consume, so its put drops the count to 1 and the drain put then frees the ep while rpcrdma_xprt_disconnect() still holds a pointer to it. Fix by dispatching on the prior re_connect_status via xchg(): for prev == 0 (pre-ESTABLISHED) wake the connect waiter and return with no put; for prev == 1 call rpcrdma_force_disconnect() and return. The case-1 arm relies on the subsequent RDMA_CM_EVENT_DISCONNECTED event -- reliably delivered when rdma_disconnect() is called on a still-connected cm_id -- to balance the ESTABLISHED get; rpcrdma_xprt_drain() continues to balance only that connect-time get. Any other prior value means teardown is already in flight. Fixes: 2acc5cae2923 ("xprtrdma: Prevent dereferencing r_xprt->rx_ep after it is freed") Assisted-by: kres:claude-opus-4-7 Signed-off-by: Chris Mason Signed-off-by: Chuck Lever Signed-off-by: Anna Schumaker --- net/sunrpc/xprtrdma/verbs.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/net/sunrpc/xprtrdma/verbs.c b/net/sunrpc/xprtrdma/verbs.c index 993bc5c444a4..7ddab4ed5d03 100644 --- a/net/sunrpc/xprtrdma/verbs.c +++ b/net/sunrpc/xprtrdma/verbs.c @@ -245,8 +245,17 @@ rpcrdma_cm_event_handler(struct rdma_cm_id *id, struct rdma_cm_event *event) complete(&ep->re_done); return 0; case RDMA_CM_EVENT_ADDR_CHANGE: - ep->re_connect_status = -ENODEV; - goto disconnected; + switch (xchg(&ep->re_connect_status, -ENODEV)) { + case 0: + goto wake_connect_worker; + case 1: + /* The later DISCONNECTED event balances the + * ESTABLISHED get; do not put here. + */ + rpcrdma_force_disconnect(ep); + return 0; + } + return 0; case RDMA_CM_EVENT_ESTABLISHED: rpcrdma_ep_get(ep); ep->re_connect_status = 1; @@ -269,7 +278,6 @@ rpcrdma_cm_event_handler(struct rdma_cm_id *id, struct rdma_cm_event *event) return 0; case RDMA_CM_EVENT_DISCONNECTED: ep->re_connect_status = -ECONNABORTED; -disconnected: rpcrdma_force_disconnect(ep); return rpcrdma_ep_put(ep); default: From bb7caa63e1db22fd03e8dc591b12169e99169dff Mon Sep 17 00:00:00 2001 From: Chris Mason Date: Thu, 4 Jun 2026 13:06:34 -0400 Subject: [PATCH 32/42] xprtrdma: Initialize re_id before removal registration rpcrdma_create_id() registers ep->re_rn with the rpcrdma ib_client before returning the new rdma_cm_id to rpcrdma_ep_create(). However rpcrdma_ep_create() currently stores that pointer in ep->re_id only after rpcrdma_create_id() returns. A local administrator can race an NFS/RDMA mount against RDMA device removal. If rpcrdma_remove_one() observes the just-registered notification before rpcrdma_ep_create() assigns ep->re_id, rpcrdma_ep_removal_done() calls trace_xprtrdma_device_removal(NULL). The tracepoint dereferences id->device->name and copies id->route.addr.dst_addr, so the callback can crash the kernel with a NULL pointer dereference. Store the rdma_cm_id in ep->re_id immediately before publishing ep->re_rn. The existing error path still destroys the id directly if registration fails; ep is then freed by the caller without using ep->re_id. Remove the later duplicate assignment in rpcrdma_ep_create(). Fixes: 3f4eb9ff9234 ("xprtrdma: Handle device removal outside of the CM event handler") Assisted-by: kres:openai-gpt-5 Signed-off-by: Chris Mason Signed-off-by: Chuck Lever Signed-off-by: Anna Schumaker --- net/sunrpc/xprtrdma/verbs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/sunrpc/xprtrdma/verbs.c b/net/sunrpc/xprtrdma/verbs.c index 7ddab4ed5d03..a192b77ce739 100644 --- a/net/sunrpc/xprtrdma/verbs.c +++ b/net/sunrpc/xprtrdma/verbs.c @@ -334,6 +334,7 @@ static struct rdma_cm_id *rpcrdma_create_id(struct rpcrdma_xprt *r_xprt, if (rc) goto out; + ep->re_id = id; rc = rpcrdma_rn_register(id->device, &ep->re_rn, rpcrdma_ep_removal_done); if (rc) goto out; @@ -406,7 +407,6 @@ static int rpcrdma_ep_create(struct rpcrdma_xprt *r_xprt) } __module_get(THIS_MODULE); device = id->device; - ep->re_id = id; reinit_completion(&ep->re_done); ep->re_max_requests = r_xprt->rx_xprt.max_reqs; From 0f13fc7c7d2e0427517e63c739277a4cd338b0c5 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Thu, 4 Jun 2026 13:06:35 -0400 Subject: [PATCH 33/42] xprtrdma: Check frwr_wp_create() during connect frwr_wp_create() creates the singleton Memory Region used to encode padding for Write chunks whose payload length is not XDR-aligned. Its failure paths return a negative errno and leave ep->re_write_pad_mr set to NULL. rpcrdma_xprt_connect() currently ignores that return value. If frwr_wp_create() fails after the rest of the connection setup succeeds, xprt_rdma_connect_worker() treats the connection attempt as successful and sets XPRT_CONNECTED. A later NFS/RDMA read with a non-4-byte-aligned receive page length reaches rpcrdma_encode_write_list(), passes the NULL write-pad MR to encode_rdma_segment(), and dereferences it. This is locally triggerable on an NFS/RDMA client after a connect or reconnect hits a local MR allocation, DMA-map, MR-map, or post-send failure; a remote peer alone cannot force the local MR setup failure. Check the return value and fail the connect as -ENOTCONN, matching the adjacent setup failures. This keeps XPRT_CONNECTED clear and lets the normal reconnect path retry. Fixes: 21037b8c2258 ("xprtrdma: Provide a buffer to pad Write chunks of unaligned length") Signed-off-by: Chuck Lever Signed-off-by: Anna Schumaker --- net/sunrpc/xprtrdma/verbs.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/net/sunrpc/xprtrdma/verbs.c b/net/sunrpc/xprtrdma/verbs.c index a192b77ce739..74d173e5681d 100644 --- a/net/sunrpc/xprtrdma/verbs.c +++ b/net/sunrpc/xprtrdma/verbs.c @@ -549,7 +549,17 @@ int rpcrdma_xprt_connect(struct rpcrdma_xprt *r_xprt) goto out; } rpcrdma_mrs_create(r_xprt); - frwr_wp_create(r_xprt); + + /* + * rpcrdma_encode_write_list() dereferences the write-pad + * MR with no NULL check, so fail the connect rather than + * publish a transport whose write-pad MR is NULL. + */ + rc = frwr_wp_create(r_xprt); + if (rc) { + rc = -ENOTCONN; + goto out; + } out: trace_xprtrdma_connect(r_xprt, rc); From 234c0ff695ef3ffb656931000e6b823d0c2f30fd Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Thu, 4 Jun 2026 13:06:36 -0400 Subject: [PATCH 34/42] xprtrdma: Resize reply buffers before reposting receives Commit 0e13dd9ea8be ("xprtrdma: Remove temp allocation of rpcrdma_rep objects") made rpcrdma_rep objects survive disconnects. That is normally fine, but it also means their receive regbufs keep the size they had when they were first allocated. Each rep's receive buffer is sized to ep->re_inline_recv when the rep is created. rpcrdma_ep_create() resets that threshold to the rdma_max_inline_read ceiling for every new endpoint, and the connect handshake then shrinks it to the peer's advertised inline send size. A rep allocated under a smaller negotiated threshold keeps that size: on disconnect, rpcrdma_xprt_disconnect() drains and DMA-unmaps the surviving reps but does not free or resize them. The threshold can come back larger on the next connection. The first peer may supply no RPC-over-RDMA CM private data, defaulting its send size to 1024, while the reconnect target is an ordinary server offering 4096; or, with rdma_max_inline_read raised above its default, the reconnect target may advertise a larger svcrdma_max_req_size than the first. rpcrdma_post_recvs() then reposts a surviving rep whose SGE length is still the old, smaller value, and a larger inline Reply hits a receive length error and forces another disconnect. The undersized rep returns to the free list when its failed Receive flushes, so the following reconnect reposts the same rep and fails the same way. The transport flaps without making forward progress for as long as the peer keeps advertising the larger inline size. This is local/admin-triggerable rather than remote-triggerable: a local administrator must create and maintain the NFS/RDMA mount, while the server or reconnect target has to advertise a larger inline send size and return a reply that uses it. Fix this by checking each rep before it is reposted. If the receive regbuf is smaller than the current endpoint's inline receive size, reallocate it on the current RDMA device's NUMA node and reinitialize the rep's xdr_buf before DMA-mapping and posting the Receive WR. Fixes: 0e13dd9ea8be ("xprtrdma: Remove temp allocation of rpcrdma_rep objects") Signed-off-by: Chuck Lever Signed-off-by: Anna Schumaker --- net/sunrpc/xprtrdma/verbs.c | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/net/sunrpc/xprtrdma/verbs.c b/net/sunrpc/xprtrdma/verbs.c index 74d173e5681d..8392ba4bcdca 100644 --- a/net/sunrpc/xprtrdma/verbs.c +++ b/net/sunrpc/xprtrdma/verbs.c @@ -81,6 +81,8 @@ rpcrdma_regbuf_alloc_node(size_t size, enum dma_data_direction direction, int node); static struct rpcrdma_regbuf * rpcrdma_regbuf_alloc(size_t size, enum dma_data_direction direction); +static bool rpcrdma_regbuf_realloc_node(struct rpcrdma_regbuf *rb, + size_t size, gfp_t flags, int node); static void rpcrdma_regbuf_dma_unmap(struct rpcrdma_regbuf *rb); static void rpcrdma_regbuf_free(struct rpcrdma_regbuf *rb); @@ -1353,10 +1355,16 @@ rpcrdma_regbuf_alloc(size_t size, enum dma_data_direction direction) * returned, @rb is left untouched. */ bool rpcrdma_regbuf_realloc(struct rpcrdma_regbuf *rb, size_t size, gfp_t flags) +{ + return rpcrdma_regbuf_realloc_node(rb, size, flags, NUMA_NO_NODE); +} + +static bool rpcrdma_regbuf_realloc_node(struct rpcrdma_regbuf *rb, + size_t size, gfp_t flags, int node) { void *buf; - buf = kmalloc(size, flags); + buf = kmalloc_node(size, flags, node); if (!buf) return false; @@ -1368,6 +1376,23 @@ bool rpcrdma_regbuf_realloc(struct rpcrdma_regbuf *rb, size_t size, gfp_t flags) return true; } +static bool rpcrdma_rep_resize(struct rpcrdma_xprt *r_xprt, + struct rpcrdma_rep *rep) +{ + struct rpcrdma_regbuf *rb = rep->rr_rdmabuf; + struct rpcrdma_ep *ep = r_xprt->rx_ep; + size_t size = ep->re_inline_recv; + + if (likely(rdmab_length(rb) >= size)) + return true; + if (!rpcrdma_regbuf_realloc_node(rb, size, XPRTRDMA_GFP_FLAGS, + ibdev_to_node(ep->re_id->device))) + return false; + + xdr_buf_init(&rep->rr_hdrbuf, rdmab_data(rb), rdmab_length(rb)); + return true; +} + /** * __rpcrdma_regbuf_dma_map - DMA-map a regbuf * @r_xprt: controlling transport instance @@ -1451,6 +1476,10 @@ void rpcrdma_post_recvs(struct rpcrdma_xprt *r_xprt, int needed) break; /* I1: a rep on rb_free_reps must carry no rqst pointer. */ WARN_ON_ONCE(rep->rr_rqst); + if (!rpcrdma_rep_resize(r_xprt, rep)) { + rpcrdma_rep_put(buf, rep); + break; + } if (!rpcrdma_regbuf_dma_map(r_xprt, rep->rr_rdmabuf)) { rpcrdma_rep_put(buf, rep); break; From c7653d5cebc8492c77ec0415b5e9c0fb3e644bc6 Mon Sep 17 00:00:00 2001 From: Chris Mason Date: Thu, 4 Jun 2026 13:06:37 -0400 Subject: [PATCH 35/42] xprtrdma: Fix bcall rep leak and unbounded peek rpcrdma_is_bcall() decodes a reply's first words to decide whether the frame is a backchannel call. Two issues in that decode path let a short or malformed reply leak the receive buffer and drain the Receive queue. First, the speculative peek p = xdr_inline_decode(xdr, 0); /* five p++ reads follow */ asks xdr_inline_decode() for zero bytes, which returns xdr->p without consulting xdr->end. The five subsequent __be32 reads can then walk up to 20 bytes past the wire payload into stale regbuf contents and misclassify the reply as a backchannel call. Second, after the post-peek p = xdr_inline_decode(xdr, 3 * sizeof(*p)); if (unlikely(!p)) return true; the short-header arm returns true without calling rpcrdma_bc_receive_call(). The contract with the caller is that a true return transfers ownership of rep to the backchannel path: rpcrdma_reply_handler() if (rpcrdma_is_bcall(r_xprt, rep)) return; /* bare return, skips out_post */ ... out_post: rpcrdma_post_recvs(r_xprt, credits + ...); Because rpcrdma_bc_receive_call() never ran, no one took rep, but rpcrdma_reply_handler still bare-returns past rpcrdma_rep_put() and rpcrdma_post_recvs(). The rep, with its persistently DMA-mapped receive buffer, is orphaned on rb_all_reps and freed only at transport teardown. This completion reposts nothing, so its slot is reclaimed only when a later forward-channel reply reaches out_post and rpcrdma_post_recvs() allocates a fresh rep to backfill; absent that traffic the Receive queue drains and the peer's Sends draw RNR NAKs. Fix by consulting xdr->end after the zero-length peek so the five __be32 reads cannot run unless 20 bytes of wire payload remain. A byte-precise comparison against xdr->end is required because a non-4-aligned receive rounds the stream's word count up past the true payload. Also return false from the short-header arm so the reply falls through the normal out_norqst cleanup chain (rpcrdma_rep_put() plus rpcrdma_post_recvs()). Fixes: 41c8f70f5a3d ("xprtrdma: Harden backchannel call decoding") Assisted-by: kres:claude-opus-4-7 Signed-off-by: Chris Mason Signed-off-by: Chuck Lever Signed-off-by: Anna Schumaker --- net/sunrpc/xprtrdma/rpc_rdma.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/net/sunrpc/xprtrdma/rpc_rdma.c b/net/sunrpc/xprtrdma/rpc_rdma.c index f115baba6d56..63e64d53e289 100644 --- a/net/sunrpc/xprtrdma/rpc_rdma.c +++ b/net/sunrpc/xprtrdma/rpc_rdma.c @@ -1088,6 +1088,8 @@ rpcrdma_is_bcall(struct rpcrdma_xprt *r_xprt, struct rpcrdma_rep *rep) /* Peek at stream contents without advancing. */ p = xdr_inline_decode(xdr, 0); + if ((char *)xdr->end - (char *)p < 5 * XDR_UNIT) + return false; /* Chunk lists */ if (xdr_item_is_present(p++)) @@ -1112,7 +1114,7 @@ rpcrdma_is_bcall(struct rpcrdma_xprt *r_xprt, struct rpcrdma_rep *rep) */ p = xdr_inline_decode(xdr, 3 * sizeof(*p)); if (unlikely(!p)) - return true; + return false; rpcrdma_bc_receive_call(r_xprt, rep); return true; From c3a628aab2dc8f5fd7bff86ceaeae64de590e60a Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Thu, 4 Jun 2026 13:06:38 -0400 Subject: [PATCH 36/42] xprtrdma: Sanitize the reply credit grant after parsing The out_norqst exit in rpcrdma_reply_handler() branches away before the credit clamp, so a reply that matches no pending request reaches out_post carrying the raw credit value parsed from the wire. rpcrdma_post_recvs() does not bound its @needed argument: the refill loop allocates and chains Receive WRs until the count is satisfied or allocation fails. A peer that sends a well-formed reply carrying an unknown XID and an inflated credit grant therefore drives rep allocation and Receive posting past re_max_requests on every such reply. Move the clamp to immediately after the credit field is parsed, ahead of the first branch that can reach out_post, so every later consumer sees a sanitized value. The cwnd update stays on the matched-request path. Fixes: 704f3f640f72 ("xprtrdma: Post receive buffers after RPC completion") Signed-off-by: Chuck Lever Signed-off-by: Anna Schumaker --- net/sunrpc/xprtrdma/rpc_rdma.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/net/sunrpc/xprtrdma/rpc_rdma.c b/net/sunrpc/xprtrdma/rpc_rdma.c index 63e64d53e289..5ff086ccd259 100644 --- a/net/sunrpc/xprtrdma/rpc_rdma.c +++ b/net/sunrpc/xprtrdma/rpc_rdma.c @@ -1472,6 +1472,14 @@ void rpcrdma_reply_handler(struct rpcrdma_rep *rep) credits = be32_to_cpu(*p++); rep->rr_proc = *p++; + /* The credit grant from the wire is not trustworthy; + * sanitize it before any code path consumes it. + */ + if (credits == 0) + credits = 1; /* don't deadlock */ + else if (credits > r_xprt->rx_ep->re_max_requests) + credits = r_xprt->rx_ep->re_max_requests; + if (rep->rr_vers != rpcrdma_version) goto out_badversion; @@ -1488,10 +1496,6 @@ void rpcrdma_reply_handler(struct rpcrdma_rep *rep) xprt_pin_rqst(rqst); spin_unlock(&xprt->queue_lock); - if (credits == 0) - credits = 1; /* don't deadlock */ - else if (credits > r_xprt->rx_ep->re_max_requests) - credits = r_xprt->rx_ep->re_max_requests; if (buf->rb_credits != credits) rpcrdma_update_cwnd(r_xprt, credits); From abc011ddaf1617e3e82d8a1e87daa7ddbfb9bac5 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Thu, 4 Jun 2026 13:06:39 -0400 Subject: [PATCH 37/42] xprtrdma: Repost Receive buffers for malformed replies rpcrdma_wc_receive() decrements the transport's Receive count for every completion before it dispatches a successful Receive to rpcrdma_reply_handler(). The handler must post a replacement Receive WR before returning unless ownership of the rep has moved elsewhere, as on the backchannel path. Commit 2ae50ad68cd7 ("xprtrdma: Close window between waking RPC senders and posting Receives") moved the Receive refill out of rpcrdma_wc_receive(), where it had run ahead of every reply, into rpcrdma_reply_handler() so that the responder's credit grant could be parsed before reposting. The bad-version and short-reply exits never reach that refill: they recycle the rep and return without calling rpcrdma_post_recvs(). A remote peer can therefore drain the client's posted Receive queue by sending a sustained stream of replies that are shorter than the fixed transport header or that carry an unrecognized RPC/RDMA version. Each such reply consumes one posted Receive without replacing it. Once the queue empties, the peer's next Send finds no posted Receive and the transport stalls until reconnect. Route both malformed-reply exits through the shared repost tail after recycling the rep, refilling against buf->rb_credits, the most recent accepted credit grant. Neither exit updates the congestion window, so RPCs admitted under the previous grant remain in flight awaiting replies. A smaller refill target would let a stream of malformed replies ratchet the posted Receive count down to the batch floor while the congestion window still admits rb_credits RPCs; a burst of valid replies to those RPCs could then overrun the posted Receives, and because the client connects with rnr_retry_count of zero, a single RNR NAK terminates the connection. Refilling against rb_credits also restores the target that applied to malformed replies before commit 2ae50ad68cd7 ("xprtrdma: Close window between waking RPC senders and posting Receives") when rpcrdma_post_recvs() computed it from rb_credits internally. rb_credits is at least one from connection establishment onward, so the repost path always keeps Receives posted. Fixes: 2ae50ad68cd7 ("xprtrdma: Close window between waking RPC senders and posting Receives") Signed-off-by: Chuck Lever Signed-off-by: Anna Schumaker --- net/sunrpc/xprtrdma/rpc_rdma.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/net/sunrpc/xprtrdma/rpc_rdma.c b/net/sunrpc/xprtrdma/rpc_rdma.c index 5ff086ccd259..3f50828802de 100644 --- a/net/sunrpc/xprtrdma/rpc_rdma.c +++ b/net/sunrpc/xprtrdma/rpc_rdma.c @@ -1528,11 +1528,13 @@ void rpcrdma_reply_handler(struct rpcrdma_rep *rep) out_badversion: trace_xprtrdma_reply_vers_err(rep); - goto out; + rpcrdma_rep_put(buf, rep); + credits = buf->rb_credits; + goto out_post; out_shortreply: trace_xprtrdma_reply_short_err(rep); - -out: rpcrdma_rep_put(buf, rep); + credits = buf->rb_credits; + goto out_post; } From 60e7870052f417d83965db144f70ae21fcfcf37f Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Thu, 4 Jun 2026 13:06:40 -0400 Subject: [PATCH 38/42] xprtrdma: Return sendctx slot after Send preparation failure rpcrdma_prepare_send_sges() gets a sendctx before it maps the SGEs for the Send WR. If one of the mapping helpers fails, no Send WR is posted, so no Send completion is guaranteed to advance rb_sc_tail. Current cleanup clears sc_req so a later completion can sweep over that slot, but a consecutive run of preparation failures can still advance rb_sc_head until the ring appears full. At that point rpcrdma_sendctx_get_locked() returns NULL and no Send can be posted to produce the completion needed to recover the ring. The trigger requires CONFIG_SUNRPC_XPRT_RDMA and an NFS/RDMA mount. Mount setup and reliable DMA-map fault injection require local admin authority. Unprivileged I/O on an existing mount can exercise the send path, but a remote peer alone cannot force this local DMA-map failure. Add rpcrdma_sendctx_unget_locked() for the single-consumer send path to rewind rb_sc_head when the just-acquired sendctx is canceled before ib_post_send(). Wake waiters after making the slot available again. After the rewind, every slot the completion sweep visits belongs to a posted Send, so rpcrdma_sendctx_put_locked() no longer needs to test sc_req before unmapping. Fixes: ae72950abf99 ("xprtrdma: Add data structure to manage RDMA Send arguments") Signed-off-by: Chuck Lever Signed-off-by: Anna Schumaker --- net/sunrpc/xprtrdma/rpc_rdma.c | 5 ++++- net/sunrpc/xprtrdma/verbs.c | 40 +++++++++++++++++++++++++++++---- net/sunrpc/xprtrdma/xprt_rdma.h | 2 ++ 3 files changed, 42 insertions(+), 5 deletions(-) diff --git a/net/sunrpc/xprtrdma/rpc_rdma.c b/net/sunrpc/xprtrdma/rpc_rdma.c index 3f50828802de..1285f04cdac1 100644 --- a/net/sunrpc/xprtrdma/rpc_rdma.c +++ b/net/sunrpc/xprtrdma/rpc_rdma.c @@ -747,6 +747,7 @@ inline int rpcrdma_prepare_send_sges(struct rpcrdma_xprt *r_xprt, struct xdr_buf *xdr, enum rpcrdma_chunktype rtype) { + struct rpcrdma_sendctx *sc; int ret; ret = -EAGAIN; @@ -789,7 +790,9 @@ inline int rpcrdma_prepare_send_sges(struct rpcrdma_xprt *r_xprt, return 0; out_unmap: - rpcrdma_sendctx_cancel(req->rl_sendctx); + sc = req->rl_sendctx; + rpcrdma_sendctx_cancel(sc); + rpcrdma_sendctx_unget_locked(r_xprt, sc); out_nosc: trace_xprtrdma_prepsend_failed(&req->rl_slot, ret); return ret; diff --git a/net/sunrpc/xprtrdma/verbs.c b/net/sunrpc/xprtrdma/verbs.c index 8392ba4bcdca..04b286223b24 100644 --- a/net/sunrpc/xprtrdma/verbs.c +++ b/net/sunrpc/xprtrdma/verbs.c @@ -631,6 +631,11 @@ static void rpcrdma_sendctxs_destroy(struct rpcrdma_xprt *r_xprt) /* The QP is drained, but the final unsignaled Sends might not * have been walked by a signaled Send completion. Release those * Send owners before request buffers are reset. + * + * Unlike the completion sweep, this walk can visit slots with + * no Send posted: after a partial rpcrdma_sendctxs_create() + * failure on reconnect, rb_sc_head and rb_sc_tail are stale, + * and slots between them can be NULL or have sc_req clear. */ for (i = rpcrdma_sendctx_next(buf, buf->rb_sc_tail); i != rpcrdma_sendctx_next(buf, buf->rb_sc_head); @@ -703,6 +708,12 @@ static unsigned long rpcrdma_sendctx_next(struct rpcrdma_buffer *buf, return likely(item < buf->rb_sc_last) ? item + 1 : 0; } +static unsigned long rpcrdma_sendctx_prev(struct rpcrdma_buffer *buf, + unsigned long item) +{ + return item > 0 ? item - 1 : buf->rb_sc_last; +} + /** * rpcrdma_sendctx_get_locked - Acquire a send context * @r_xprt: controlling transport instance @@ -759,6 +770,29 @@ struct rpcrdma_sendctx *rpcrdma_sendctx_get_locked(struct rpcrdma_xprt *r_xprt) return NULL; } +/** + * rpcrdma_sendctx_unget_locked - Release an unposted send context + * @r_xprt: controlling transport instance + * @sc: send context to release + * + * Usage: Called when no Send is posted for the sendctx most + * recently returned by rpcrdma_sendctx_get_locked(). + * + * The caller serializes calls to this function and to + * rpcrdma_sendctx_get_locked() (per transport). + */ +void rpcrdma_sendctx_unget_locked(struct rpcrdma_xprt *r_xprt, + struct rpcrdma_sendctx *sc) +{ + struct rpcrdma_buffer *buf = &r_xprt->rx_buf; + + if (WARN_ON_ONCE(buf->rb_sc_ctxs[buf->rb_sc_head] != sc)) + return; + + buf->rb_sc_head = rpcrdma_sendctx_prev(buf, buf->rb_sc_head); + xprt_write_space(&r_xprt->rx_xprt); +} + /** * rpcrdma_sendctx_put_locked - Release a send context * @r_xprt: controlling transport instance @@ -776,8 +810,7 @@ static void rpcrdma_sendctx_put_locked(struct rpcrdma_xprt *r_xprt, unsigned long next_tail; /* Release previously completed but unsignaled Sends by walking - * up the queue until @sc is found. Entries left behind by a - * failed rpcrdma_prepare_send_sges() have sc_req cleared. + * up the queue until @sc is found. */ next_tail = buf->rb_sc_tail; do { @@ -787,8 +820,7 @@ static void rpcrdma_sendctx_put_locked(struct rpcrdma_xprt *r_xprt, /* ORDER: item must be accessed _before_ tail is updated */ cur = buf->rb_sc_ctxs[next_tail]; - if (cur->sc_req) - rpcrdma_sendctx_unmap(cur); + rpcrdma_sendctx_unmap(cur); } while (buf->rb_sc_ctxs[next_tail] != sc); diff --git a/net/sunrpc/xprtrdma/xprt_rdma.h b/net/sunrpc/xprtrdma/xprt_rdma.h index ae036719f840..4cbc941e4a3e 100644 --- a/net/sunrpc/xprtrdma/xprt_rdma.h +++ b/net/sunrpc/xprtrdma/xprt_rdma.h @@ -495,6 +495,8 @@ void rpcrdma_req_destroy(struct rpcrdma_req *req); int rpcrdma_buffer_create(struct rpcrdma_xprt *); void rpcrdma_buffer_destroy(struct rpcrdma_buffer *); struct rpcrdma_sendctx *rpcrdma_sendctx_get_locked(struct rpcrdma_xprt *r_xprt); +void rpcrdma_sendctx_unget_locked(struct rpcrdma_xprt *r_xprt, + struct rpcrdma_sendctx *sc); struct rpcrdma_mr *rpcrdma_mr_get(struct rpcrdma_xprt *r_xprt); void rpcrdma_mrs_refresh(struct rpcrdma_xprt *r_xprt); From 84800a8d1d22c475b3c35bdbcf8ed64a55f3b66f Mon Sep 17 00:00:00 2001 From: Tom Haynes Date: Sat, 18 Apr 2026 12:03:01 -0700 Subject: [PATCH 39/42] nfs: don't skip revalidate on directory delegation when attrs flagged stale On a local directory mutation (rename/create/unlink) the client marks CHANGE / MTIME / CTIME as invalid in NFS_I(dir)->cache_validity. When a subsequent stat(2) enters __nfs_revalidate_inode() and finds a directory delegation held, the function currently early-exits and returns the cached (now stale) mtime to userspace without sending a GETATTR RPC. Keep the early-exit for the fast path, but take the RPC when CHANGE, MTIME, or CTIME are already marked invalid. The delegation alone is not a guarantee of cached-attr freshness once the code itself has flagged the cache as stale. Assisted-by: Claude:claude-opus-4-7 [bpftrace] [tshark] Signed-off-by: Tom Haynes [Anna: Use NFS_INO_INVALID_ATTR insteado of individual NFS_INO_INVALID_* flags] Signed-off-by: Anna Schumaker --- fs/nfs/inode.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c index 0d9451a2ad8e..c6c939a76722 100644 --- a/fs/nfs/inode.c +++ b/fs/nfs/inode.c @@ -1357,7 +1357,8 @@ __nfs_revalidate_inode(struct nfs_server *server, struct inode *inode) status = pnfs_sync_inode(inode, false); if (status) goto out; - } else if (nfs_have_directory_delegation(inode)) { + } else if (nfs_have_directory_delegation(inode) && + !(NFS_I(inode)->cache_validity & NFS_INO_INVALID_ATTR)) { status = 0; goto out; } From 41fe0f7b84f0cb822ae10ab08592996a592b2a25 Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Wed, 27 May 2026 12:30:35 -0400 Subject: [PATCH 40/42] NFSv4/pNFS: reject zero-length r_addr in nfs4_decode_mp_ds_addr nfs4_decode_mp_ds_addr() decodes the r_netid and r_addr opaques of a netaddr4 from a GETDEVICEINFO multipath-DS body, then immediately calls strrchr(buf, '.') to locate the port separator. Both decodes use xdr_stream_decode_string_dup(), and the current code checks only "nlen < 0" / "rlen < 0" before dereferencing the returned string. When the on-wire opaque has length zero, xdr_stream_decode_opaque_inline() returns 0 and xdr_stream_decode_string_dup() falls through to its "*str = NULL; return ret" tail, leaving buf NULL with a return value of 0. The "< 0" check does not catch this, and the next line is strrchr(NULL, '.'), a kernel NULL pointer dereference reachable from any pNFS-flexfile client mounted against a malicious or compromised metadata server. Reject the zero-length cases explicitly so the decoder fails with -EBADMSG (treated as a malformed GETDEVICEINFO body) instead of panicking the client. Cc: stable@vger.kernel.org Fixes: 6b7f3cf96364 ("nfs41: pull decode_ds_addr from file layout to generic pnfs") Assisted-by: Claude:claude-opus-4-7 Signed-off-by: Michael Bommarito Signed-off-by: Anna Schumaker --- fs/nfs/pnfs_nfs.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/nfs/pnfs_nfs.c b/fs/nfs/pnfs_nfs.c index 12632a706da8..0ff43dbcb7cd 100644 --- a/fs/nfs/pnfs_nfs.c +++ b/fs/nfs/pnfs_nfs.c @@ -1075,14 +1075,14 @@ nfs4_decode_mp_ds_addr(struct net *net, struct xdr_stream *xdr, gfp_t gfp_flags) /* r_netid */ nlen = xdr_stream_decode_string_dup(xdr, &netid, XDR_MAX_NETOBJ, gfp_flags); - if (unlikely(nlen < 0)) + if (unlikely(nlen <= 0)) goto out_err; /* r_addr: ip/ip6addr with port in dec octets - see RFC 5665 */ /* port is ".ABC.DEF", 8 chars max */ rlen = xdr_stream_decode_string_dup(xdr, &buf, INET6_ADDRSTRLEN + IPV6_SCOPE_ID_LEN + 8, gfp_flags); - if (unlikely(rlen < 0)) + if (unlikely(rlen <= 0)) goto out_free_netid; /* replace port '.' with '-' */ From d189f224308c8ac3feeea8e442c99922bd18f1b2 Mon Sep 17 00:00:00 2001 From: Markus Elfring Date: Sun, 14 Jun 2026 09:56:35 +0200 Subject: [PATCH 41/42] NFS: Prevent resource leak in nfs_alloc_server() It was overlooked to call ida_free() after a failed nfs_alloc_iostats() call. Thus add the missed function call in an if branch. Fixes: 1c7251187dc067a6d460cf33ca67da9c1dd87807 ("NFS: add superblock sysfs entries") Cc: stable@vger.kernel.org Reported-by: Christophe Jaillet Closes: https://lore.kernel.org/linux-nfs/1c8e10c9-def7-4f0d-8aa1-23c8035a38c8@wanadoo.fr/ Signed-off-by: Markus Elfring Signed-off-by: Anna Schumaker --- fs/nfs/client.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/nfs/client.c b/fs/nfs/client.c index be02bb227741..0781b15e7e05 100644 --- a/fs/nfs/client.c +++ b/fs/nfs/client.c @@ -1074,6 +1074,7 @@ struct nfs_server *nfs_alloc_server(void) server->io_stats = nfs_alloc_iostats(); if (!server->io_stats) { + ida_free(&s_sysfs_ids, server->s_sysfs_id); kfree(server); return NULL; } From 284ea3fb4f6715201e1d9ef3474c25e817ad70e9 Mon Sep 17 00:00:00 2001 From: Markus Elfring Date: Sun, 14 Jun 2026 10:06:11 +0200 Subject: [PATCH 42/42] NFS: Use common error handling code in nfs_alloc_server() Use an additional label so that a bit of exception handling can be better reused at the end of this function implementation. This issue was detected by using the Coccinelle software. Signed-off-by: Markus Elfring Signed-off-by: Anna Schumaker --- fs/nfs/client.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/fs/nfs/client.c b/fs/nfs/client.c index 0781b15e7e05..48ce013b30b0 100644 --- a/fs/nfs/client.c +++ b/fs/nfs/client.c @@ -1049,10 +1049,8 @@ struct nfs_server *nfs_alloc_server(void) return NULL; server->s_sysfs_id = ida_alloc(&s_sysfs_ids, GFP_KERNEL); - if (server->s_sysfs_id < 0) { - kfree(server); - return NULL; - } + if (server->s_sysfs_id < 0) + goto free_server; server->client = server->client_acl = ERR_PTR(-EINVAL); @@ -1075,8 +1073,7 @@ struct nfs_server *nfs_alloc_server(void) server->io_stats = nfs_alloc_iostats(); if (!server->io_stats) { ida_free(&s_sysfs_ids, server->s_sysfs_id); - kfree(server); - return NULL; + goto free_server; } server->change_attr_type = NFS4_CHANGE_TYPE_IS_UNDEFINED; @@ -1090,6 +1087,10 @@ struct nfs_server *nfs_alloc_server(void) rpc_init_wait_queue(&server->uoc_rpcwaitq, "NFS UOC"); return server; + +free_server: + kfree(server); + return NULL; } EXPORT_SYMBOL_GPL(nfs_alloc_server);