From: NeilBrown <neilb@suse.de>
Subject: NFS: Allow NFSv4 mounts to not share transports.
Patch-mainline: Never, upstream not interested
References: FATE#315593

The "nfs-no-share-transport" patch only supports NFSv2 and NFSv3.

Making the 'nosharetransport' option work for NFSv4 turns out to be
prohibitively difficult, and the design is not ideal.
A consequence of the design is that if

   mount -o nosharetransport server:/path /mountpoint

is run repeatedly, you get multiple entries in the mounts table.

So this patch add "-o sharetransport=N" where N is some unique number
greater than 0.
This syntax:
 - can be and is supported by NFSv4
 - prevents multiple attempts of the same mount from creating multiple
   mount table entries.

It does require entering a number each time, but that should just
happen once in /etc/fstab or similar.

Acked-by: NeilBrown <neilb@suse.de>
Signed-off-by: Neil Brown <neilb@suse.de>

---
 fs/nfs/client.c           |    3 +++
 fs/nfs/internal.h         |    2 ++
 fs/nfs/nfs4client.c       |   18 ++++++++++++++----
 fs/nfs/super.c            |   22 ++++++++++++++++++++++
 include/linux/nfs_fs_sb.h |    1 +
 5 files changed, 42 insertions(+), 4 deletions(-)

--- a/fs/nfs/client.c
+++ b/fs/nfs/client.c
@@ -181,6 +181,7 @@ struct nfs_client *nfs_alloc_client(cons
 
 	clp->cl_proto = cl_init->proto;
 	clp->cl_net = get_net(cl_init->net);
+	clp->cl_xprt_id = cl_init->xprt_id;
 
 	cred = rpc_lookup_machine_cred("*");
 	if (!IS_ERR(cred))
@@ -303,6 +304,8 @@ static struct nfs_client *nfs_match_clie
 	        const struct sockaddr *clap = (struct sockaddr *)&clp->cl_addr;
 		if (test_bit(NFS_CS_NO_SHARE,&clp->cl_flags))
 			continue;
+		if (clp->cl_xprt_id != data->xprt_id)
+			continue;
 		/* Don't match clients that failed to initialise properly */
 		if (clp->cl_cons_state < 0)
 			continue;
--- a/fs/nfs/internal.h
+++ b/fs/nfs/internal.h
@@ -73,6 +73,7 @@ struct nfs_client_initdata {
 	int proto;
 	u32 minorversion;
 	struct net *net;
+	unsigned int xprt_id;
 };
 
 /*
@@ -94,6 +95,7 @@ struct nfs_parsed_mount_data {
 	unsigned int		minorversion;
 	char			*fscache_uniq;
 	bool			need_mount;
+	unsigned int		xprt_id;
 
 	struct {
 		struct sockaddr_storage	address;
--- a/fs/nfs/nfs4client.c
+++ b/fs/nfs/nfs4client.c
@@ -519,6 +519,9 @@ int nfs40_walk_client_list(struct nfs_cl
 
 		if (!nfs4_match_client_owner_id(pos, new))
 			continue;
+
+		if (pos->cl_xprt_id != new->cl_xprt_id)
+			continue;
 found:
 		atomic_inc(&pos->cl_count);
 		spin_unlock(&nn->nfs_client_lock);
@@ -650,6 +653,9 @@ int nfs41_walk_client_list(struct nfs_cl
 		if (pos->cl_cons_state != NFS_CS_READY)
 			continue;
 
+		if (pos->cl_xprt_id != new->cl_xprt_id)
+			continue;
+
 		if (!nfs4_match_clientids(pos, new))
 			continue;
 
@@ -787,7 +793,7 @@ static int nfs4_set_client(struct nfs_se
 		const char *ip_addr,
 		rpc_authflavor_t authflavour,
 		int proto, const struct rpc_timeout *timeparms,
-		u32 minorversion, struct net *net)
+		u32 minorversion, struct net *net, unsigned int xprt_id)
 {
 	struct nfs_client_initdata cl_init = {
 		.hostname = hostname,
@@ -797,6 +803,7 @@ static int nfs4_set_client(struct nfs_se
 		.proto = proto,
 		.minorversion = minorversion,
 		.net = net,
+		.xprt_id = xprt_id,
 	};
 	struct nfs_client *clp;
 	int error;
@@ -854,6 +861,7 @@ struct nfs_client *nfs4_set_ds_client(st
 		.proto = ds_proto,
 		.minorversion = minor_version,
 		.net = mds_clp->cl_net,
+		.xprt_id = mds_clp->cl_xprt_id,
 	};
 	struct rpc_timeout ds_timeout;
 	struct nfs_client *clp;
@@ -998,7 +1006,8 @@ static int nfs4_init_server(struct nfs_s
 			data->nfs_server.protocol,
 			&timeparms,
 			data->minorversion,
-			data->net);
+			data->net,
+			data->xprt_id);
 	if (error < 0)
 		goto error;
 
@@ -1095,7 +1104,8 @@ struct nfs_server *nfs4_create_referral_
 				rpc_protocol(parent_server->client),
 				parent_server->client->cl_timeout,
 				parent_client->cl_mvops->minor_version,
-				parent_client->cl_net);
+				parent_client->cl_net,
+				parent_client->cl_xprt_id);
 	if (error < 0)
 		goto error;
 
@@ -1204,7 +1214,7 @@ int nfs4_update_server(struct nfs_server
 	error = nfs4_set_client(server, hostname, sap, salen, buf,
 				clp->cl_rpcclient->cl_auth->au_flavor,
 				clp->cl_proto, clnt->cl_timeout,
-				clp->cl_minorversion, net);
+				clp->cl_minorversion, net, clp->cl_xprt_id);
 	nfs_put_client(clp);
 	if (error != 0) {
 		nfs_server_insert_lists(server);
--- a/fs/nfs/super.c
+++ b/fs/nfs/super.c
@@ -88,6 +88,7 @@ enum {
 	Opt_rdirplus, Opt_nordirplus,
 	Opt_sharecache, Opt_nosharecache,
 	Opt_sharetransport, Opt_nosharetransport,
+	Opt_sharetransportid,
 	Opt_resvport, Opt_noresvport,
 	Opt_fscache, Opt_nofscache,
 	Opt_migration, Opt_nomigration,
@@ -148,6 +149,7 @@ static const match_table_t nfs_mount_opt
 	{ Opt_nosharecache, "nosharecache" },
 	{ Opt_sharetransport, "sharetransport"},
 	{ Opt_nosharetransport, "nosharetransport"},
+	{ Opt_sharetransportid, "sharetransport=%s"},
 	{ Opt_resvport, "resvport" },
 	{ Opt_noresvport, "noresvport" },
 	{ Opt_fscache, "fsc" },
@@ -673,6 +675,8 @@ static void nfs_show_mount_options(struc
 		else
 			seq_puts(m, nfs_infop->nostr);
 	}
+	if (clp->cl_xprt_id)
+		seq_printf(m, ",sharetransport=%u", clp->cl_xprt_id);
 	rcu_read_lock();
 	seq_printf(m, ",proto=%s",
 		   rpc_peeraddr2str(nfss->client, RPC_DISPLAY_NETID));
@@ -1420,6 +1424,12 @@ static int nfs_parse_mount_options(char
 				goto out_invalid_value;
 			mnt->minorversion = option;
 			break;
+		case Opt_sharetransportid:
+			if (nfs_get_option_ul(args, &option) ||
+			    option <= 0)
+				goto out_invalid_value;
+			mnt->xprt_id = option;
+			break;
 
 		/*
 		 * options that take text values
@@ -1630,6 +1640,10 @@ static int nfs_parse_mount_options(char
 	    (mnt->version != 4 || mnt->minorversion != 0))
 		goto out_migration_misuse;
 
+	if (mnt->flags & NFS_MOUNT_NOSHARE_XPRT &&
+	    mnt->version == 4)
+		goto out_noshare_misuse;
+
 	/*
 	 * verify that any proto=/mountproto= options match the address
 	 * families in the addr=/mountaddr= options.
@@ -1650,6 +1664,10 @@ static int nfs_parse_mount_options(char
 
 	return 1;
 
+out_noshare_misuse:
+	printk(KERN_INFO "NFS: nosharetransport is not compatible with vers=4\n");
+	printk(KERN_INFO "NFS: use sharetransport=N for some unique N\n");
+	return 0;
 out_mountproto_mismatch:
 	printk(KERN_INFO "NFS: mount server address does not match mountproto= "
 			 "option\n");
@@ -2424,6 +2442,10 @@ static int nfs_compare_super_address(str
 {
 	struct sockaddr *sap1, *sap2;
 
+	if (server1->nfs_client->cl_xprt_id !=
+	    server2->nfs_client->cl_xprt_id)
+		return 0;
+
 	sap1 = (struct sockaddr *)&server1->nfs_client->cl_addr;
 	sap2 = (struct sockaddr *)&server2->nfs_client->cl_addr;
 
--- a/include/linux/nfs_fs_sb.h
+++ b/include/linux/nfs_fs_sb.h
@@ -58,6 +58,7 @@ struct nfs_client {
 	u32			cl_minorversion;/* NFSv4 minorversion */
 	struct rpc_cred		*cl_machine_cred;
 
+	unsigned int		cl_xprt_id;
 #if IS_ENABLED(CONFIG_NFS_V4)
 	struct list_head	cl_ds_clients; /* auth flavor data servers */
 	u64			cl_clientid;	/* constant */
