| // SPDX-License-Identifier: GPL-2.0-only | 
 | /* | 
 |  * NFS server support for local clients to bypass network stack | 
 |  * | 
 |  * Copyright (C) 2014 Weston Andros Adamson <dros@primarydata.com> | 
 |  * Copyright (C) 2019 Trond Myklebust <trond.myklebust@hammerspace.com> | 
 |  * Copyright (C) 2024 Mike Snitzer <snitzer@hammerspace.com> | 
 |  * Copyright (C) 2024 NeilBrown <neilb@suse.de> | 
 |  */ | 
 |  | 
 | #include <linux/exportfs.h> | 
 | #include <linux/sunrpc/svcauth.h> | 
 | #include <linux/sunrpc/clnt.h> | 
 | #include <linux/nfs.h> | 
 | #include <linux/nfs_common.h> | 
 | #include <linux/nfslocalio.h> | 
 | #include <linux/nfs_fs.h> | 
 | #include <linux/nfs_xdr.h> | 
 | #include <linux/string.h> | 
 |  | 
 | #include "nfsd.h" | 
 | #include "vfs.h" | 
 | #include "netns.h" | 
 | #include "filecache.h" | 
 | #include "cache.h" | 
 |  | 
 | /** | 
 |  * nfsd_open_local_fh - lookup a local filehandle @nfs_fh and map to nfsd_file | 
 |  * | 
 |  * @net: 'struct net' to get the proper nfsd_net required for LOCALIO access | 
 |  * @dom: 'struct auth_domain' required for LOCALIO access | 
 |  * @rpc_clnt: rpc_clnt that the client established | 
 |  * @cred: cred that the client established | 
 |  * @nfs_fh: filehandle to lookup | 
 |  * @pnf: place to find the nfsd_file, or store it if it was non-NULL | 
 |  * @fmode: fmode_t to use for open | 
 |  * | 
 |  * This function maps a local fh to a path on a local filesystem. | 
 |  * This is useful when the nfs client has the local server mounted - it can | 
 |  * avoid all the NFS overhead with reads, writes and commits. | 
 |  * | 
 |  * On successful return, returned nfsd_file will have its nf_net member | 
 |  * set. Caller (NFS client) is responsible for calling nfsd_net_put and | 
 |  * nfsd_file_put (via nfs_to_nfsd_file_put_local). | 
 |  */ | 
 | static struct nfsd_file * | 
 | nfsd_open_local_fh(struct net *net, struct auth_domain *dom, | 
 | 		   struct rpc_clnt *rpc_clnt, const struct cred *cred, | 
 | 		   const struct nfs_fh *nfs_fh, struct nfsd_file __rcu **pnf, | 
 | 		   const fmode_t fmode) | 
 | { | 
 | 	int mayflags = NFSD_MAY_LOCALIO; | 
 | 	struct svc_cred rq_cred; | 
 | 	struct svc_fh fh; | 
 | 	struct nfsd_file *localio; | 
 | 	__be32 beres; | 
 |  | 
 | 	if (nfs_fh->size > NFS4_FHSIZE) | 
 | 		return ERR_PTR(-EINVAL); | 
 |  | 
 | 	if (!nfsd_net_try_get(net)) | 
 | 		return ERR_PTR(-ENXIO); | 
 |  | 
 | 	rcu_read_lock(); | 
 | 	localio = nfsd_file_get(rcu_dereference(*pnf)); | 
 | 	rcu_read_unlock(); | 
 | 	if (localio) | 
 | 		return localio; | 
 |  | 
 | 	/* nfs_fh -> svc_fh */ | 
 | 	fh_init(&fh, NFS4_FHSIZE); | 
 | 	fh.fh_handle.fh_size = nfs_fh->size; | 
 | 	memcpy(fh.fh_handle.fh_raw, nfs_fh->data, nfs_fh->size); | 
 |  | 
 | 	if (fmode & FMODE_READ) | 
 | 		mayflags |= NFSD_MAY_READ; | 
 | 	if (fmode & FMODE_WRITE) | 
 | 		mayflags |= NFSD_MAY_WRITE; | 
 |  | 
 | 	svcauth_map_clnt_to_svc_cred_local(rpc_clnt, cred, &rq_cred); | 
 |  | 
 | 	beres = nfsd_file_acquire_local(net, &rq_cred, dom, | 
 | 					&fh, mayflags, &localio); | 
 | 	if (beres) | 
 | 		localio = ERR_PTR(nfs_stat_to_errno(be32_to_cpu(beres))); | 
 |  | 
 | 	fh_put(&fh); | 
 | 	if (rq_cred.cr_group_info) | 
 | 		put_group_info(rq_cred.cr_group_info); | 
 |  | 
 | 	if (!IS_ERR(localio)) { | 
 | 		struct nfsd_file *new; | 
 | 		if (!nfsd_net_try_get(net)) { | 
 | 			nfsd_file_put(localio); | 
 | 			nfsd_net_put(net); | 
 | 			return ERR_PTR(-ENXIO); | 
 | 		} | 
 | 		nfsd_file_get(localio); | 
 | 	again: | 
 | 		new = unrcu_pointer(cmpxchg(pnf, NULL, RCU_INITIALIZER(localio))); | 
 | 		if (new) { | 
 | 			/* Some other thread installed an nfsd_file */ | 
 | 			if (nfsd_file_get(new) == NULL) | 
 | 				goto again; | 
 | 			/* | 
 | 			 * Drop the ref we were going to install (both file and | 
 | 			 * net) and the one we were going to return (only file). | 
 | 			 */ | 
 | 			nfsd_file_put(localio); | 
 | 			nfsd_net_put(net); | 
 | 			nfsd_file_put(localio); | 
 | 			localio = new; | 
 | 		} | 
 | 	} else | 
 | 		nfsd_net_put(net); | 
 |  | 
 | 	return localio; | 
 | } | 
 |  | 
 | static const struct nfsd_localio_operations nfsd_localio_ops = { | 
 | 	.nfsd_net_try_get  = nfsd_net_try_get, | 
 | 	.nfsd_net_put  = nfsd_net_put, | 
 | 	.nfsd_open_local_fh = nfsd_open_local_fh, | 
 | 	.nfsd_file_put_local = nfsd_file_put_local, | 
 | 	.nfsd_file_get_local = nfsd_file_get_local, | 
 | 	.nfsd_file_file = nfsd_file_file, | 
 | }; | 
 |  | 
 | void nfsd_localio_ops_init(void) | 
 | { | 
 | 	nfs_to = &nfsd_localio_ops; | 
 | } | 
 |  | 
 | /* | 
 |  * UUID_IS_LOCAL XDR functions | 
 |  */ | 
 |  | 
 | static __be32 localio_proc_null(struct svc_rqst *rqstp) | 
 | { | 
 | 	return rpc_success; | 
 | } | 
 |  | 
 | struct localio_uuidarg { | 
 | 	uuid_t			uuid; | 
 | }; | 
 |  | 
 | static __be32 localio_proc_uuid_is_local(struct svc_rqst *rqstp) | 
 | { | 
 | 	struct localio_uuidarg *argp = rqstp->rq_argp; | 
 | 	struct net *net = SVC_NET(rqstp); | 
 | 	struct nfsd_net *nn = net_generic(net, nfsd_net_id); | 
 |  | 
 | 	nfs_uuid_is_local(&argp->uuid, &nn->local_clients, | 
 | 			  &nn->local_clients_lock, | 
 | 			  net, rqstp->rq_client, THIS_MODULE); | 
 |  | 
 | 	return rpc_success; | 
 | } | 
 |  | 
 | static bool localio_decode_uuidarg(struct svc_rqst *rqstp, | 
 | 				   struct xdr_stream *xdr) | 
 | { | 
 | 	struct localio_uuidarg *argp = rqstp->rq_argp; | 
 | 	u8 uuid[UUID_SIZE]; | 
 |  | 
 | 	if (decode_opaque_fixed(xdr, uuid, UUID_SIZE)) | 
 | 		return false; | 
 | 	import_uuid(&argp->uuid, uuid); | 
 |  | 
 | 	return true; | 
 | } | 
 |  | 
 | static const struct svc_procedure localio_procedures1[] = { | 
 | 	[LOCALIOPROC_NULL] = { | 
 | 		.pc_func = localio_proc_null, | 
 | 		.pc_decode = nfssvc_decode_voidarg, | 
 | 		.pc_encode = nfssvc_encode_voidres, | 
 | 		.pc_argsize = sizeof(struct nfsd_voidargs), | 
 | 		.pc_ressize = sizeof(struct nfsd_voidres), | 
 | 		.pc_cachetype = RC_NOCACHE, | 
 | 		.pc_xdrressize = 0, | 
 | 		.pc_name = "NULL", | 
 | 	}, | 
 | 	[LOCALIOPROC_UUID_IS_LOCAL] = { | 
 | 		.pc_func = localio_proc_uuid_is_local, | 
 | 		.pc_decode = localio_decode_uuidarg, | 
 | 		.pc_encode = nfssvc_encode_voidres, | 
 | 		.pc_argsize = sizeof(struct localio_uuidarg), | 
 | 		.pc_argzero = sizeof(struct localio_uuidarg), | 
 | 		.pc_ressize = sizeof(struct nfsd_voidres), | 
 | 		.pc_cachetype = RC_NOCACHE, | 
 | 		.pc_name = "UUID_IS_LOCAL", | 
 | 	}, | 
 | }; | 
 |  | 
 | #define LOCALIO_NR_PROCEDURES ARRAY_SIZE(localio_procedures1) | 
 | static DEFINE_PER_CPU_ALIGNED(unsigned long, | 
 | 			      localio_count[LOCALIO_NR_PROCEDURES]); | 
 | const struct svc_version localio_version1 = { | 
 | 	.vs_vers	= 1, | 
 | 	.vs_nproc	= LOCALIO_NR_PROCEDURES, | 
 | 	.vs_proc	= localio_procedures1, | 
 | 	.vs_dispatch	= nfsd_dispatch, | 
 | 	.vs_count	= localio_count, | 
 | 	.vs_xdrsize	= XDR_QUADLEN(UUID_SIZE), | 
 | 	.vs_hidden	= true, | 
 | }; |