Linux NFS development
 help / color / mirror / Atom feed
* [PATCH 0/4] Confine the inter-server copy interface to fs/nfs_common
@ 2026-07-21 16:23 Chuck Lever
  2026-07-21 16:23 ` [PATCH 1/4] nfs_common: Remove unused nfs_ssc_client_ops infrastructure Chuck Lever
                   ` (3 more replies)
  0 siblings, 4 replies; 5+ messages in thread
From: Chuck Lever @ 2026-07-21 16:23 UTC (permalink / raw)
  To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs

Inter-server copy has drawn a steady trail of use-after-free and
reference-leak fixes: 75333d48f922, 34e8f9ec4c9a, and d2c343c51caf
among them. They cluster on one object, the source mount that the NFS
client creates and opens while NFSD decides when it may be torn down.
The bookkeeping that tracks it, added by f4e44b393389, has needed
three follow-on repairs of its own. Splitting an object's lifetime
across two modules that are forbidden to call each other directly is
what keeps producing these bugs.

The fix in 75333d48f922 narrowed that split rather than adding
another rule to it. NFSD stopped reaching back through the broker to
deactivate the source superblock, and post-copy clean-up became the
laundromat's business alone. What the fix did not do was retract the
interface it had just stopped using, so the client-side registration
machinery has sat unused since (patch 1).

One implicit contract between the two modules is still in force.
Reading the client ops table without synchronization is safe
only for reasons that live on the caller's side: an inter-server copy
holds an active vers=4.2 mount of the source, that mount pins the
nfsv4 module, and unregister runs only at module exit. Patch 3
replaces that with synchronization the broker owns, so the argument
no longer depends on how a caller in another subsystem manages
mounts.

What remains after that is textual. The header still declares both
sides' interfaces, so each implementation parses the other's types
and inherits its includes, which is how <uapi/linux/nfs.h> reaches
nfs4proc.c. Once the knowledge only NFSD holds has moved into the
broker, splitting that header is mechanical.

None of this fixes a live bug, and the source mount's lifetime still
spans both modules. What changes is that the broker no longer relies
on rules its callers enforce. Inter-server copy behavior is meant to
be unchanged throughout.

Chuck Lever (4):
  nfs_common: Remove unused nfs_ssc_client_ops infrastructure
  NFSD: Hoist nfs42_ssc_open() into fs/nfs_common/nfs_ssc.c
  nfs_common: Synchronize access to the SSC client ops table
  NFSD: Split linux/nfs_ssc.h

 fs/nfs/nfs4file.c        |   1 +
 fs/nfs/super.c           |  25 --------
 fs/nfs_common/nfs_ssc.c  | 126 ++++++++++++++++++++++++---------------
 fs/nfsd/nfs4proc.c       |  22 ++-----
 fs/nfsd/nfs4state.c      |   2 +-
 fs/nfsd/xdr4.h           |  13 ++++
 include/linux/nfs_ssc.h  |  69 ++++-----------------
 include/linux/nfsd_ssc.h |  38 ++++++++++++
 8 files changed, 147 insertions(+), 149 deletions(-)
 create mode 100644 include/linux/nfsd_ssc.h

-- 
2.54.0


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH 1/4] nfs_common: Remove unused nfs_ssc_client_ops infrastructure
  2026-07-21 16:23 [PATCH 0/4] Confine the inter-server copy interface to fs/nfs_common Chuck Lever
@ 2026-07-21 16:23 ` Chuck Lever
  2026-07-21 16:23 ` [PATCH 2/4] NFSD: Hoist nfs42_ssc_open() into fs/nfs_common/nfs_ssc.c Chuck Lever
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 5+ messages in thread
From: Chuck Lever @ 2026-07-21 16:23 UTC (permalink / raw)
  To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs

Clean up: Commit 75333d48f922 ("NFSD: fix use-after-free in
__nfs42_ssc_open()") addressed a use-after-free bug by removing the
nfsd4_interssc_disconnect() function. Post-copy clean-up was then
delegated to NFSD's laundromat.

Since that commit, the nfs_do_sb_deactive() wrapper function and the
entire nfs_ssc_client_ops infrastructure no longer have any
consumers. This includes nfs_do_sb_deactive(), struct
nfs_ssc_client_ops, nfs_ssc_register(), nfs_ssc_unregister(), and
related registrations in the NFS client.

Cc: Olga Kornievskaia <okorniev@redhat.com>
Cc: Dai Ngo <dai.ngo@oracle.com>
Signed-off-by: Chuck Lever <cel@kernel.org>
---
 fs/nfs/super.c          | 25 ------------------------
 fs/nfs_common/nfs_ssc.c | 42 -----------------------------------------
 fs/nfsd/nfs4proc.c      |  2 --
 include/linux/nfs_ssc.h | 20 --------------------
 4 files changed, 89 deletions(-)

diff --git a/fs/nfs/super.c b/fs/nfs/super.c
index cb19f1540d98..23292680adbd 100644
--- a/fs/nfs/super.c
+++ b/fs/nfs/super.c
@@ -58,7 +58,6 @@
 #include <linux/rcupdate.h>
 
 #include <linux/uaccess.h>
-#include <linux/nfs_ssc.h>
 
 #include <uapi/linux/tls.h>
 
@@ -92,12 +91,6 @@ const struct super_operations nfs_sops = {
 };
 EXPORT_SYMBOL_GPL(nfs_sops);
 
-#ifdef CONFIG_NFS_V4_2
-static const struct nfs_ssc_client_ops nfs_ssc_clnt_ops_tbl = {
-	.sco_sb_deactive = nfs_sb_deactive,
-};
-#endif
-
 #if IS_ENABLED(CONFIG_NFS_V4)
 static int __init register_nfs4_fs(void)
 {
@@ -119,18 +112,6 @@ static void unregister_nfs4_fs(void)
 }
 #endif
 
-#ifdef CONFIG_NFS_V4_2
-static void nfs_ssc_register_ops(void)
-{
-	nfs_ssc_register(&nfs_ssc_clnt_ops_tbl);
-}
-
-static void nfs_ssc_unregister_ops(void)
-{
-	nfs_ssc_unregister(&nfs_ssc_clnt_ops_tbl);
-}
-#endif /* CONFIG_NFS_V4_2 */
-
 static struct shrinker *acl_shrinker;
 
 /*
@@ -163,9 +144,6 @@ int __init register_nfs_fs(void)
 
 	shrinker_register(acl_shrinker);
 
-#ifdef CONFIG_NFS_V4_2
-	nfs_ssc_register_ops();
-#endif
 	return 0;
 error_3:
 	nfs_unregister_sysctl();
@@ -185,9 +163,6 @@ void __exit unregister_nfs_fs(void)
 	shrinker_free(acl_shrinker);
 	nfs_unregister_sysctl();
 	unregister_nfs4_fs();
-#ifdef CONFIG_NFS_V4_2
-	nfs_ssc_unregister_ops();
-#endif
 	unregister_filesystem(&nfs_fs_type);
 }
 
diff --git a/fs/nfs_common/nfs_ssc.c b/fs/nfs_common/nfs_ssc.c
index 832246b22c51..8d8b7344ab4f 100644
--- a/fs/nfs_common/nfs_ssc.c
+++ b/fs/nfs_common/nfs_ssc.c
@@ -47,45 +47,3 @@ void nfs42_ssc_unregister(const struct nfs4_ssc_client_ops *ops)
 }
 EXPORT_SYMBOL_GPL(nfs42_ssc_unregister);
 #endif /* CONFIG_NFS_V4_2 */
-
-#ifdef CONFIG_NFS_V4_2
-/**
- * nfs_ssc_register - install the NFS_FS client ops in the nfs_ssc_client_tbl
- * @ops: NFS_FS ops to be installed
- *
- * Return values:
- *   None
- */
-void nfs_ssc_register(const struct nfs_ssc_client_ops *ops)
-{
-	nfs_ssc_client_tbl.ssc_nfs_ops = ops;
-}
-EXPORT_SYMBOL_GPL(nfs_ssc_register);
-
-/**
- * nfs_ssc_unregister - uninstall the NFS_FS client ops from
- *				the nfs_ssc_client_tbl
- * @ops: ops to be uninstalled
- *
- * Return values:
- *   None
- */
-void nfs_ssc_unregister(const struct nfs_ssc_client_ops *ops)
-{
-	if (nfs_ssc_client_tbl.ssc_nfs_ops != ops)
-		return;
-	nfs_ssc_client_tbl.ssc_nfs_ops = NULL;
-}
-EXPORT_SYMBOL_GPL(nfs_ssc_unregister);
-
-#else
-void nfs_ssc_register(const struct nfs_ssc_client_ops *ops)
-{
-}
-EXPORT_SYMBOL_GPL(nfs_ssc_register);
-
-void nfs_ssc_unregister(const struct nfs_ssc_client_ops *ops)
-{
-}
-EXPORT_SYMBOL_GPL(nfs_ssc_unregister);
-#endif /* CONFIG_NFS_V4_2 */
diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c
index b7d745020fd1..83715ba38b6a 100644
--- a/fs/nfsd/nfs4proc.c
+++ b/fs/nfsd/nfs4proc.c
@@ -1697,8 +1697,6 @@ extern struct file *nfs42_ssc_open(struct vfsmount *ss_mnt,
 				   nfs4_stateid *stateid);
 extern void nfs42_ssc_close(struct file *filep);
 
-extern void nfs_sb_deactive(struct super_block *sb);
-
 #define NFSD42_INTERSSC_MOUNTOPS "vers=4.2,addr=%s,sec=sys"
 
 /*
diff --git a/include/linux/nfs_ssc.h b/include/linux/nfs_ssc.h
index 22265b1ff080..ba236dba8975 100644
--- a/include/linux/nfs_ssc.h
+++ b/include/linux/nfs_ssc.h
@@ -21,16 +21,8 @@ struct nfs4_ssc_client_ops {
 	void (*sco_close)(struct file *filep);
 };
 
-/*
- * NFS_FS
- */
-struct nfs_ssc_client_ops {
-	void (*sco_sb_deactive)(struct super_block *sb);
-};
-
 struct nfs_ssc_client_ops_tbl {
 	const struct nfs4_ssc_client_ops *ssc_nfs4_ops;
-	const struct nfs_ssc_client_ops *ssc_nfs_ops;
 };
 
 extern void nfs42_ssc_register_ops(void);
@@ -67,15 +59,3 @@ struct nfsd4_ssc_umount_item {
 	struct vfsmount *nsui_vfsmount;
 	char nsui_ipaddr[RPC_MAX_ADDRBUFLEN + 1];
 };
-
-/*
- * NFS_FS
- */
-extern void nfs_ssc_register(const struct nfs_ssc_client_ops *ops);
-extern void nfs_ssc_unregister(const struct nfs_ssc_client_ops *ops);
-
-static inline void nfs_do_sb_deactive(struct super_block *sb)
-{
-	if (nfs_ssc_client_tbl.ssc_nfs_ops)
-		(*nfs_ssc_client_tbl.ssc_nfs_ops->sco_sb_deactive)(sb);
-}
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH 2/4] NFSD: Hoist nfs42_ssc_open() into fs/nfs_common/nfs_ssc.c
  2026-07-21 16:23 [PATCH 0/4] Confine the inter-server copy interface to fs/nfs_common Chuck Lever
  2026-07-21 16:23 ` [PATCH 1/4] nfs_common: Remove unused nfs_ssc_client_ops infrastructure Chuck Lever
@ 2026-07-21 16:23 ` Chuck Lever
  2026-07-21 16:23 ` [PATCH 3/4] nfs_common: Synchronize access to the SSC client ops table Chuck Lever
  2026-07-21 16:23 ` [PATCH 4/4] NFSD: Split linux/nfs_ssc.h Chuck Lever
  3 siblings, 0 replies; 5+ messages in thread
From: Chuck Lever @ 2026-07-21 16:23 UTC (permalink / raw)
  To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs

Refactor: The infrastructure and details for calling the client's
ssc_open method can be hidden in nfs_ssc.c. This reduces the SSC
footprint in fs/nfsd/nfs4proc.c, a step toward removing that
file's dependency on <linux/nfs_fs.h>, which indirectly includes
<uapi/linux/nfs.h>.

The open and close functions are named "nfsd42_" since they are
meant to be invoked only by NFSD.

Cc: Olga Kornievskaia <okorniev@redhat.com>
Cc: Dai Ngo <dai.ngo@oracle.com>
Signed-off-by: Chuck Lever <cel@kernel.org>
---
 fs/nfs_common/nfs_ssc.c | 56 +++++++++++++++++++++++++++++++++++++++--
 fs/nfsd/nfs4proc.c      | 17 +++----------
 include/linux/nfs_ssc.h | 23 +++++++----------
 3 files changed, 66 insertions(+), 30 deletions(-)

diff --git a/fs/nfs_common/nfs_ssc.c b/fs/nfs_common/nfs_ssc.c
index 8d8b7344ab4f..a8e79ec68701 100644
--- a/fs/nfs_common/nfs_ssc.c
+++ b/fs/nfs_common/nfs_ssc.c
@@ -12,9 +12,61 @@
 #include <linux/nfs_ssc.h>
 #include "../nfs/nfs4_fs.h"
 
+struct nfs_ssc_client_ops_tbl {
+	const struct nfs4_ssc_client_ops *ssc_nfs4_ops;
+};
 
-struct nfs_ssc_client_ops_tbl nfs_ssc_client_tbl;
-EXPORT_SYMBOL_GPL(nfs_ssc_client_tbl);
+static struct nfs_ssc_client_ops_tbl nfs_ssc_client_tbl __read_mostly;
+
+/**
+ * nfsd42_ssc_open - Open a file to be used for server-to-server copy
+ * @ss_mnt: active mount point on which the source file resides
+ * @src_fh: file handle of the source file to be copied
+ * @stateid: stateid to use for COPY operation
+ *
+ * Caller must close the returned file using nfsd42_ssc_close().
+ *
+ * Return: an open file, or an ERR_PTR on error
+ */
+struct file *nfsd42_ssc_open(struct vfsmount *ss_mnt, struct nfs_fh *src_fh,
+			     nfs4_stateid *stateid)
+{
+	/*
+	 * Built under CONFIG_NFS_V4_2_SSC_HELPER, which the NFS client
+	 * enables on its own. The dispatch below is live only when the
+	 * server also sets CONFIG_NFSD_V4_2_INTER_SSC; without it the
+	 * source file cannot be opened, so callers get -EIO.
+	 */
+#if IS_ENABLED(CONFIG_NFSD_V4_2_INTER_SSC)
+	const struct nfs4_ssc_client_ops *ops = nfs_ssc_client_tbl.ssc_nfs4_ops;
+
+	if (ops)
+		return ops->sco_open(ss_mnt, src_fh, stateid);
+#endif
+
+	return ERR_PTR(-EIO);
+}
+EXPORT_SYMBOL_GPL(nfsd42_ssc_open);
+
+/**
+ * nfsd42_ssc_close - Close a file opened with nfsd42_ssc_open()
+ * @filp: struct file to be closed
+ *
+ * The real cleanup happens unconditionally in nfsd4_cleanup_inter_ssc().
+ * The vfsmount is pinned until this function is called, preventing
+ * the client from unregistering its SSC ops.
+ */
+void nfsd42_ssc_close(struct file *filp)
+{
+	/* Live only under CONFIG_NFSD_V4_2_INTER_SSC; see nfsd42_ssc_open(). */
+#if IS_ENABLED(CONFIG_NFSD_V4_2_INTER_SSC)
+	const struct nfs4_ssc_client_ops *ops = nfs_ssc_client_tbl.ssc_nfs4_ops;
+
+	if (ops)
+		ops->sco_close(filp);
+#endif
+}
+EXPORT_SYMBOL_GPL(nfsd42_ssc_close);
 
 #ifdef CONFIG_NFS_V4_2
 /**
diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c
index 83715ba38b6a..dbea2da7b1ea 100644
--- a/fs/nfsd/nfs4proc.c
+++ b/fs/nfsd/nfs4proc.c
@@ -1692,11 +1692,6 @@ void nfsd4_cancel_copy_by_sb(struct net *net, struct super_block *sb)
 
 #ifdef CONFIG_NFSD_V4_2_INTER_SSC
 
-extern struct file *nfs42_ssc_open(struct vfsmount *ss_mnt,
-				   struct nfs_fh *src_fh,
-				   nfs4_stateid *stateid);
-extern void nfs42_ssc_close(struct file *filep);
-
 #define NFSD42_INTERSSC_MOUNTOPS "vers=4.2,addr=%s,sec=sys"
 
 /*
@@ -1919,7 +1914,7 @@ nfsd4_cleanup_inter_ssc(struct nfsd4_ssc_umount_item *nsui, struct file *filp,
 	struct nfsd_net *nn = net_generic(dst->nf_net, nfsd_net_id);
 	long timeout = msecs_to_jiffies(nfsd4_ssc_umount_timeout);
 
-	nfs42_ssc_close(filp);
+	nfsd42_ssc_close(filp);
 	fput(filp);
 
 	spin_lock(&nn->nfsd_ssc_lock);
@@ -1951,12 +1946,6 @@ nfsd4_cleanup_inter_ssc(struct nfsd4_ssc_umount_item *nsui, struct file *filp,
 {
 }
 
-static struct file *nfs42_ssc_open(struct vfsmount *ss_mnt,
-				   struct nfs_fh *src_fh,
-				   nfs4_stateid *stateid)
-{
-	return NULL;
-}
 #endif /* CONFIG_NFSD_V4_2_INTER_SSC */
 
 static __be32
@@ -2179,8 +2168,8 @@ static int nfsd4_do_async_copy(void *data)
 	if (nfsd4_ssc_is_inter(copy)) {
 		struct file *filp;
 
-		filp = nfs42_ssc_open(copy->ss_nsui->nsui_vfsmount,
-				      &copy->c_fh, &copy->stateid);
+		filp = nfsd42_ssc_open(copy->ss_nsui->nsui_vfsmount,
+				       &copy->c_fh, &copy->stateid);
 		if (IS_ERR(filp)) {
 			switch (PTR_ERR(filp)) {
 			case -EBADF:
diff --git a/include/linux/nfs_ssc.h b/include/linux/nfs_ssc.h
index ba236dba8975..fc0d5d48dec2 100644
--- a/include/linux/nfs_ssc.h
+++ b/include/linux/nfs_ssc.h
@@ -10,8 +10,6 @@
 #include <linux/nfs_fs.h>
 #include <linux/sunrpc/svc.h>
 
-extern struct nfs_ssc_client_ops_tbl nfs_ssc_client_tbl;
-
 /*
  * NFS_V4
  */
@@ -21,29 +19,26 @@ struct nfs4_ssc_client_ops {
 	void (*sco_close)(struct file *filep);
 };
 
-struct nfs_ssc_client_ops_tbl {
-	const struct nfs4_ssc_client_ops *ssc_nfs4_ops;
-};
-
 extern void nfs42_ssc_register_ops(void);
 extern void nfs42_ssc_unregister_ops(void);
 
 extern void nfs42_ssc_register(const struct nfs4_ssc_client_ops *ops);
 extern void nfs42_ssc_unregister(const struct nfs4_ssc_client_ops *ops);
 
-#ifdef CONFIG_NFSD_V4_2_INTER_SSC
-static inline struct file *nfs42_ssc_open(struct vfsmount *ss_mnt,
-		struct nfs_fh *src_fh, nfs4_stateid *stateid)
+#if IS_ENABLED(CONFIG_NFS_V4_2_SSC_HELPER)
+struct file *nfsd42_ssc_open(struct vfsmount *ss_mnt, struct nfs_fh *src_fh,
+			     nfs4_stateid *stateid);
+void nfsd42_ssc_close(struct file *filp);
+#else
+static inline struct file *nfsd42_ssc_open(struct vfsmount *ss_mnt,
+					   struct nfs_fh *src_fh,
+					   nfs4_stateid *stateid)
 {
-	if (nfs_ssc_client_tbl.ssc_nfs4_ops)
-		return (*nfs_ssc_client_tbl.ssc_nfs4_ops->sco_open)(ss_mnt, src_fh, stateid);
 	return ERR_PTR(-EIO);
 }
 
-static inline void nfs42_ssc_close(struct file *filep)
+static inline void nfsd42_ssc_close(struct file *filp)
 {
-	if (nfs_ssc_client_tbl.ssc_nfs4_ops)
-		(*nfs_ssc_client_tbl.ssc_nfs4_ops->sco_close)(filep);
 }
 #endif
 
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH 3/4] nfs_common: Synchronize access to the SSC client ops table
  2026-07-21 16:23 [PATCH 0/4] Confine the inter-server copy interface to fs/nfs_common Chuck Lever
  2026-07-21 16:23 ` [PATCH 1/4] nfs_common: Remove unused nfs_ssc_client_ops infrastructure Chuck Lever
  2026-07-21 16:23 ` [PATCH 2/4] NFSD: Hoist nfs42_ssc_open() into fs/nfs_common/nfs_ssc.c Chuck Lever
@ 2026-07-21 16:23 ` Chuck Lever
  2026-07-21 16:23 ` [PATCH 4/4] NFSD: Split linux/nfs_ssc.h Chuck Lever
  3 siblings, 0 replies; 5+ messages in thread
From: Chuck Lever @ 2026-07-21 16:23 UTC (permalink / raw)
  To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs

nfsd42_ssc_open() and nfsd42_ssc_close() load ssc_nfs4_ops without
synchronization while nfs42_ssc_register() and nfs42_ssc_unregister()
store to it. Those reads are safe today only through a non-obvious
invariant: an inter-server copy holds an active vers=4.2 mount of the
source across both calls, the mount pins the nfsv4 module through the
nfs_client's cl_nfs_mod reference, and unregister runs only at nfsv4
module exit, so it cannot run while a call is in flight. Replace that
implicit contract with synchronization local to the broker, so its
safety no longer rests on a caller in another subsystem.

Read the pointer under RCU so a reader observes it atomically as a
valid table or NULL. nfs42_ssc_unregister() stores NULL and then calls
synchronize_rcu(), so it cannot return while a reader still holds the
pointer.

The two readers need different handling because one sleeps and the
other does not. sco_close() does not sleep, so nfsd42_ssc_close() runs
it to completion inside the RCU read-side section and the
synchronize_rcu() in unregister waits for it. __nfs42_ssc_open() does
sleep -- it issues a GETATTR RPC to the source server and allocates
with GFP_KERNEL -- so it must not run inside an RCU read-side section.
Pin the provider module with try_module_get() while still under
rcu_read_lock(), drop the lock, invoke the open, then release the
module. The reference keeps the provider mapped across the sleep
without relying on the caller's mount. If the table has already been
torn down the copy gets -EIO.

Cc: Olga Kornievskaia <okorniev@redhat.com>
Cc: Dai Ngo <dai.ngo@oracle.com>
Signed-off-by: Chuck Lever <cel@kernel.org>
---
 fs/nfs/nfs4file.c       |  1 +
 fs/nfs_common/nfs_ssc.c | 40 ++++++++++++++++++++++++++++++----------
 include/linux/nfs_ssc.h |  1 +
 3 files changed, 32 insertions(+), 10 deletions(-)

diff --git a/fs/nfs/nfs4file.c b/fs/nfs/nfs4file.c
index be40e126c539..b9ffd00e467c 100644
--- a/fs/nfs/nfs4file.c
+++ b/fs/nfs/nfs4file.c
@@ -400,6 +400,7 @@ static void __nfs42_ssc_close(struct file *filep)
 }
 
 static const struct nfs4_ssc_client_ops nfs4_ssc_clnt_ops_tbl = {
+	.owner = THIS_MODULE,
 	.sco_open = __nfs42_ssc_open,
 	.sco_close = __nfs42_ssc_close,
 };
diff --git a/fs/nfs_common/nfs_ssc.c b/fs/nfs_common/nfs_ssc.c
index a8e79ec68701..ef158008b803 100644
--- a/fs/nfs_common/nfs_ssc.c
+++ b/fs/nfs_common/nfs_ssc.c
@@ -13,7 +13,7 @@
 #include "../nfs/nfs4_fs.h"
 
 struct nfs_ssc_client_ops_tbl {
-	const struct nfs4_ssc_client_ops *ssc_nfs4_ops;
+	const struct nfs4_ssc_client_ops __rcu *ssc_nfs4_ops;
 };
 
 static struct nfs_ssc_client_ops_tbl nfs_ssc_client_tbl __read_mostly;
@@ -38,10 +38,24 @@ struct file *nfsd42_ssc_open(struct vfsmount *ss_mnt, struct nfs_fh *src_fh,
 	 * source file cannot be opened, so callers get -EIO.
 	 */
 #if IS_ENABLED(CONFIG_NFSD_V4_2_INTER_SSC)
-	const struct nfs4_ssc_client_ops *ops = nfs_ssc_client_tbl.ssc_nfs4_ops;
+	const struct nfs4_ssc_client_ops *ops;
+	struct file *res;
 
-	if (ops)
-		return ops->sco_open(ss_mnt, src_fh, stateid);
+	/*
+	 * sco_open() sleeps and must not run inside an RCU read-side
+	 * section. Pin the provider module so the open runs with the
+	 * module held; try_module_get() fails once unregister begins,
+	 * and the copy then gets -EIO.
+	 */
+	rcu_read_lock();
+	ops = rcu_dereference(nfs_ssc_client_tbl.ssc_nfs4_ops);
+	if (ops && try_module_get(ops->owner)) {
+		rcu_read_unlock();
+		res = ops->sco_open(ss_mnt, src_fh, stateid);
+		module_put(ops->owner);
+		return res;
+	}
+	rcu_read_unlock();
 #endif
 
 	return ERR_PTR(-EIO);
@@ -53,17 +67,21 @@ EXPORT_SYMBOL_GPL(nfsd42_ssc_open);
  * @filp: struct file to be closed
  *
  * The real cleanup happens unconditionally in nfsd4_cleanup_inter_ssc().
- * The vfsmount is pinned until this function is called, preventing
- * the client from unregistering its SSC ops.
+ * The client ops table is read under RCU; nfs42_ssc_unregister() calls
+ * synchronize_rcu() so unregistration cannot complete while a close is
+ * in flight.
  */
 void nfsd42_ssc_close(struct file *filp)
 {
 	/* Live only under CONFIG_NFSD_V4_2_INTER_SSC; see nfsd42_ssc_open(). */
 #if IS_ENABLED(CONFIG_NFSD_V4_2_INTER_SSC)
-	const struct nfs4_ssc_client_ops *ops = nfs_ssc_client_tbl.ssc_nfs4_ops;
+	const struct nfs4_ssc_client_ops *ops;
 
+	rcu_read_lock();
+	ops = rcu_dereference(nfs_ssc_client_tbl.ssc_nfs4_ops);
 	if (ops)
 		ops->sco_close(filp);
+	rcu_read_unlock();
 #endif
 }
 EXPORT_SYMBOL_GPL(nfsd42_ssc_close);
@@ -78,7 +96,7 @@ EXPORT_SYMBOL_GPL(nfsd42_ssc_close);
  */
 void nfs42_ssc_register(const struct nfs4_ssc_client_ops *ops)
 {
-	nfs_ssc_client_tbl.ssc_nfs4_ops = ops;
+	rcu_assign_pointer(nfs_ssc_client_tbl.ssc_nfs4_ops, ops);
 }
 EXPORT_SYMBOL_GPL(nfs42_ssc_register);
 
@@ -92,10 +110,12 @@ EXPORT_SYMBOL_GPL(nfs42_ssc_register);
  */
 void nfs42_ssc_unregister(const struct nfs4_ssc_client_ops *ops)
 {
-	if (nfs_ssc_client_tbl.ssc_nfs4_ops != ops)
+	if (rcu_dereference_protected(nfs_ssc_client_tbl.ssc_nfs4_ops,
+				      true) != ops)
 		return;
 
-	nfs_ssc_client_tbl.ssc_nfs4_ops = NULL;
+	rcu_assign_pointer(nfs_ssc_client_tbl.ssc_nfs4_ops, NULL);
+	synchronize_rcu();
 }
 EXPORT_SYMBOL_GPL(nfs42_ssc_unregister);
 #endif /* CONFIG_NFS_V4_2 */
diff --git a/include/linux/nfs_ssc.h b/include/linux/nfs_ssc.h
index fc0d5d48dec2..b392d56a4dc2 100644
--- a/include/linux/nfs_ssc.h
+++ b/include/linux/nfs_ssc.h
@@ -14,6 +14,7 @@
  * NFS_V4
  */
 struct nfs4_ssc_client_ops {
+	struct module *owner;
 	struct file *(*sco_open)(struct vfsmount *ss_mnt,
 		struct nfs_fh *src_fh, nfs4_stateid *stateid);
 	void (*sco_close)(struct file *filep);
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH 4/4] NFSD: Split linux/nfs_ssc.h
  2026-07-21 16:23 [PATCH 0/4] Confine the inter-server copy interface to fs/nfs_common Chuck Lever
                   ` (2 preceding siblings ...)
  2026-07-21 16:23 ` [PATCH 3/4] nfs_common: Synchronize access to the SSC client ops table Chuck Lever
@ 2026-07-21 16:23 ` Chuck Lever
  3 siblings, 0 replies; 5+ messages in thread
From: Chuck Lever @ 2026-07-21 16:23 UTC (permalink / raw)
  To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs

The nfs_ssc.h header contains both client- and server-side data
structures, which means each of those implementations has to pull in
headers from the other.

Create a linux/nfsd_ssc.h for the server side APIs which no longer
includes uapi/linux/nfs.h either directly or indirectly. Because
nfsd_ssc.h drops the transitive include of the NFS client headers,
fs/nfsd/nfs4proc.c now includes <linux/pagemap.h> directly for
filemap_check_wb_err().

struct nfsd4_ssc_umount_item is private to nfsd. Move it into
fs/nfsd/xdr4.h alongside its only consumers rather than into the
exported nfsd_ssc.h.

As an added clean-up, add missing header guard macros and the
struct file and struct vfsmount forward declarations the server
prototypes need.

Cc: Olga Kornievskaia <okorniev@redhat.com>
Cc: Dai Ngo <dai.ngo@oracle.com>
Signed-off-by: Chuck Lever <cel@kernel.org>
---
 fs/nfs_common/nfs_ssc.c  |  2 +-
 fs/nfsd/nfs4proc.c       |  3 ++-
 fs/nfsd/nfs4state.c      |  2 +-
 fs/nfsd/xdr4.h           | 13 ++++++++++++
 include/linux/nfs_ssc.h  | 45 ++++++++++------------------------------
 include/linux/nfsd_ssc.h | 38 +++++++++++++++++++++++++++++++++
 6 files changed, 66 insertions(+), 37 deletions(-)
 create mode 100644 include/linux/nfsd_ssc.h

diff --git a/fs/nfs_common/nfs_ssc.c b/fs/nfs_common/nfs_ssc.c
index ef158008b803..e521e3c836fe 100644
--- a/fs/nfs_common/nfs_ssc.c
+++ b/fs/nfs_common/nfs_ssc.c
@@ -10,7 +10,7 @@
 #include <linux/module.h>
 #include <linux/fs.h>
 #include <linux/nfs_ssc.h>
-#include "../nfs/nfs4_fs.h"
+#include <linux/nfsd_ssc.h>
 
 struct nfs_ssc_client_ops_tbl {
 	const struct nfs4_ssc_client_ops __rcu *ssc_nfs4_ops;
diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c
index dbea2da7b1ea..a39880208761 100644
--- a/fs/nfsd/nfs4proc.c
+++ b/fs/nfsd/nfs4proc.c
@@ -38,9 +38,10 @@
 #include <linux/slab.h>
 #include <linux/kthread.h>
 #include <linux/namei.h>
+#include <linux/pagemap.h>
 
 #include <linux/sunrpc/addr.h>
-#include <linux/nfs_ssc.h>
+#include <linux/nfsd_ssc.h>
 
 #include "attr4.h"
 #include "idmap.h"
diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c
index e78d4fe5fbfb..d7731bb4959c 100644
--- a/fs/nfsd/nfs4state.c
+++ b/fs/nfsd/nfs4state.c
@@ -45,7 +45,7 @@
 #include <linux/string_helpers.h>
 #include <linux/fsnotify.h>
 #include <linux/rhashtable.h>
-#include <linux/nfs_ssc.h>
+#include <linux/nfsd_ssc.h>
 
 #include "xdr4.h"
 #include "nfs4ctl.h"
diff --git a/fs/nfsd/xdr4.h b/fs/nfsd/xdr4.h
index e833407859c8..7bbb375874ef 100644
--- a/fs/nfsd/xdr4.h
+++ b/fs/nfsd/xdr4.h
@@ -597,6 +597,19 @@ struct nfsd4_cb_offload {
 	u32			co_referring_seqno;
 };
 
+struct nfsd4_ssc_umount_item {
+	struct list_head	nsui_list;
+	bool			nsui_busy;
+	/*
+	 * nsui_refcnt inited to 2, 1 on list and 1 for consumer. Entry
+	 * is removed when refcnt drops to 1 and nsui_expire expires.
+	 */
+	refcount_t		nsui_refcnt;
+	unsigned long		nsui_expire;
+	struct vfsmount		*nsui_vfsmount;
+	char			nsui_ipaddr[RPC_MAX_ADDRBUFLEN + 1];
+};
+
 struct nfsd4_copy {
 	/* request */
 	stateid_t		cp_src_stateid;
diff --git a/include/linux/nfs_ssc.h b/include/linux/nfs_ssc.h
index b392d56a4dc2..c199ea23e7eb 100644
--- a/include/linux/nfs_ssc.h
+++ b/include/linux/nfs_ssc.h
@@ -2,17 +2,22 @@
 /*
  * include/linux/nfs_ssc.h
  *
+ * NFSv4.2 server-to-server copy, NFS client side APIs
+ *
  * Author: Dai Ngo <dai.ngo@oracle.com>
  *
  * Copyright (c) 2020, Oracle and/or its affiliates.
  */
 
-#include <linux/nfs_fs.h>
-#include <linux/sunrpc/svc.h>
+#ifndef _LINUX_NFS_SSC_H
+#define _LINUX_NFS_SSC_H
+
+#include <linux/nfs_fh.h>
+#include <linux/nfs4.h>
+
+struct file;
+struct vfsmount;
 
-/*
- * NFS_V4
- */
 struct nfs4_ssc_client_ops {
 	struct module *owner;
 	struct file *(*sco_open)(struct vfsmount *ss_mnt,
@@ -26,32 +31,4 @@ extern void nfs42_ssc_unregister_ops(void);
 extern void nfs42_ssc_register(const struct nfs4_ssc_client_ops *ops);
 extern void nfs42_ssc_unregister(const struct nfs4_ssc_client_ops *ops);
 
-#if IS_ENABLED(CONFIG_NFS_V4_2_SSC_HELPER)
-struct file *nfsd42_ssc_open(struct vfsmount *ss_mnt, struct nfs_fh *src_fh,
-			     nfs4_stateid *stateid);
-void nfsd42_ssc_close(struct file *filp);
-#else
-static inline struct file *nfsd42_ssc_open(struct vfsmount *ss_mnt,
-					   struct nfs_fh *src_fh,
-					   nfs4_stateid *stateid)
-{
-	return ERR_PTR(-EIO);
-}
-
-static inline void nfsd42_ssc_close(struct file *filp)
-{
-}
-#endif
-
-struct nfsd4_ssc_umount_item {
-	struct list_head nsui_list;
-	bool nsui_busy;
-	/*
-	 * nsui_refcnt inited to 2, 1 on list and 1 for consumer. Entry
-	 * is removed when refcnt drops to 1 and nsui_expire expires.
-	 */
-	refcount_t nsui_refcnt;
-	unsigned long nsui_expire;
-	struct vfsmount *nsui_vfsmount;
-	char nsui_ipaddr[RPC_MAX_ADDRBUFLEN + 1];
-};
+#endif /* _LINUX_NFS_SSC_H */
diff --git a/include/linux/nfsd_ssc.h b/include/linux/nfsd_ssc.h
new file mode 100644
index 000000000000..7001410f01c2
--- /dev/null
+++ b/include/linux/nfsd_ssc.h
@@ -0,0 +1,38 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * include/linux/nfsd_ssc.h
+ *
+ * NFSv4.2 server-to-server copy, NFS server side APIs
+ *
+ * Author: Dai Ngo <dai.ngo@oracle.com>
+ *
+ * Copyright (c) 2020, Oracle and/or its affiliates.
+ */
+
+#ifndef _LINUX_NFSD_SSC_H
+#define _LINUX_NFSD_SSC_H
+
+#include <linux/nfs_fh.h>
+#include <linux/nfs4.h>
+
+struct file;
+struct vfsmount;
+
+#if IS_ENABLED(CONFIG_NFS_V4_2_SSC_HELPER)
+struct file *nfsd42_ssc_open(struct vfsmount *ss_mnt, struct nfs_fh *src_fh,
+			     nfs4_stateid *stateid);
+void nfsd42_ssc_close(struct file *filp);
+#else
+static inline struct file *nfsd42_ssc_open(struct vfsmount *ss_mnt,
+					   struct nfs_fh *src_fh,
+					   nfs4_stateid *stateid)
+{
+	return ERR_PTR(-EIO);
+}
+
+static inline void nfsd42_ssc_close(struct file *filp)
+{
+}
+#endif
+
+#endif /* _LINUX_NFSD_SSC_H */
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-07-21 16:23 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-21 16:23 [PATCH 0/4] Confine the inter-server copy interface to fs/nfs_common Chuck Lever
2026-07-21 16:23 ` [PATCH 1/4] nfs_common: Remove unused nfs_ssc_client_ops infrastructure Chuck Lever
2026-07-21 16:23 ` [PATCH 2/4] NFSD: Hoist nfs42_ssc_open() into fs/nfs_common/nfs_ssc.c Chuck Lever
2026-07-21 16:23 ` [PATCH 3/4] nfs_common: Synchronize access to the SSC client ops table Chuck Lever
2026-07-21 16:23 ` [PATCH 4/4] NFSD: Split linux/nfs_ssc.h Chuck Lever

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox