1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * NFS server support for local clients to bypass network stack 4 * 5 * Copyright (C) 2014 Weston Andros Adamson <dros@primarydata.com> 6 * Copyright (C) 2019 Trond Myklebust <trond.myklebust@hammerspace.com> 7 * Copyright (C) 2024 Mike Snitzer <snitzer@hammerspace.com> 8 * Copyright (C) 2024 NeilBrown <neilb@suse.de> 9 */ 10 11 #include <linux/exportfs.h> 12 #include <linux/sunrpc/svcauth.h> 13 #include <linux/sunrpc/clnt.h> 14 #include <linux/nfs.h> 15 #include <linux/nfs_common.h> 16 #include <linux/nfslocalio.h> 17 #include <linux/nfs_fs.h> 18 #include <linux/nfs_xdr.h> 19 #include <linux/string.h> 20 21 #include "nfsd.h" 22 #include "vfs.h" 23 #include "netns.h" 24 #include "filecache.h" 25 #include "cache.h" 26 27 /** 28 * nfsd_open_local_fh - lookup a local filehandle @nfs_fh and map to nfsd_file 29 * 30 * @net: 'struct net' to get the proper nfsd_net required for LOCALIO access 31 * @dom: 'struct auth_domain' required for LOCALIO access 32 * @rpc_clnt: rpc_clnt that the client established 33 * @cred: cred that the client established 34 * @nfs_fh: filehandle to lookup 35 * @pnf: place to find the nfsd_file, or store it if it was non-NULL 36 * @fmode: fmode_t to use for open 37 * 38 * This function maps a local fh to a path on a local filesystem. 39 * This is useful when the nfs client has the local server mounted - it can 40 * avoid all the NFS overhead with reads, writes and commits. 41 * 42 * On successful return, returned nfsd_file will have its nf_net member 43 * set. Caller (NFS client) is responsible for calling nfsd_net_put and 44 * nfsd_file_put (via nfs_to_nfsd_file_put_local). 45 */ 46 static struct nfsd_file * 47 nfsd_open_local_fh(struct net *net, struct auth_domain *dom, 48 struct rpc_clnt *rpc_clnt, const struct cred *cred, 49 const struct nfs_fh *nfs_fh, struct nfsd_file __rcu **pnf, 50 const fmode_t fmode) 51 { 52 int mayflags = NFSD_MAY_LOCALIO; 53 struct svc_cred rq_cred; 54 struct svc_fh fh; 55 struct nfsd_file *localio; 56 __be32 beres; 57 58 if (nfs_fh->size > NFS4_FHSIZE) 59 return ERR_PTR(-EINVAL); 60 61 if (!nfsd_net_try_get(net)) 62 return ERR_PTR(-ENXIO); 63 64 rcu_read_lock(); 65 localio = nfsd_file_get(rcu_dereference(*pnf)); 66 rcu_read_unlock(); 67 if (localio) 68 return localio; 69 70 /* nfs_fh -> svc_fh */ 71 fh_init(&fh, NFS4_FHSIZE); 72 fh.fh_handle.fh_size = nfs_fh->size; 73 memcpy(fh.fh_handle.fh_raw, nfs_fh->data, nfs_fh->size); 74 75 if (fmode & FMODE_READ) 76 mayflags |= NFSD_MAY_READ; 77 if (fmode & FMODE_WRITE) 78 mayflags |= NFSD_MAY_WRITE; 79 80 svcauth_map_clnt_to_svc_cred_local(rpc_clnt, cred, &rq_cred); 81 82 beres = nfsd_file_acquire_local(net, &rq_cred, dom, 83 &fh, mayflags, &localio); 84 if (beres) 85 localio = ERR_PTR(nfs_stat_to_errno(be32_to_cpu(beres))); 86 87 fh_put(&fh); 88 if (rq_cred.cr_group_info) 89 put_group_info(rq_cred.cr_group_info); 90 91 if (!IS_ERR(localio)) { 92 struct nfsd_file *new; 93 if (!nfsd_net_try_get(net)) { 94 nfsd_file_put(localio); 95 nfsd_net_put(net); 96 return ERR_PTR(-ENXIO); 97 } 98 nfsd_file_get(localio); 99 again: 100 new = unrcu_pointer(cmpxchg(pnf, NULL, RCU_INITIALIZER(localio))); 101 if (new) { 102 /* Some other thread installed an nfsd_file */ 103 if (nfsd_file_get(new) == NULL) 104 goto again; 105 /* 106 * Drop the ref we were going to install (both file and 107 * net) and the one we were going to return (only file). 108 */ 109 nfsd_file_put(localio); 110 nfsd_net_put(net); 111 nfsd_file_put(localio); 112 localio = new; 113 } 114 } else 115 nfsd_net_put(net); 116 117 return localio; 118 } 119 120 static void nfsd_file_dio_alignment(struct nfsd_file *nf, 121 u32 *nf_dio_mem_align, 122 u32 *nf_dio_offset_align, 123 u32 *nf_dio_read_offset_align) 124 { 125 *nf_dio_mem_align = nf->nf_dio_mem_align; 126 *nf_dio_offset_align = nf->nf_dio_offset_align; 127 *nf_dio_read_offset_align = nf->nf_dio_read_offset_align; 128 } 129 130 static const struct nfsd_localio_operations nfsd_localio_ops = { 131 .nfsd_net_try_get = nfsd_net_try_get, 132 .nfsd_net_put = nfsd_net_put, 133 .nfsd_open_local_fh = nfsd_open_local_fh, 134 .nfsd_file_put_local = nfsd_file_put_local, 135 .nfsd_file_get_local = nfsd_file_get_local, 136 .nfsd_file_file = nfsd_file_file, 137 .nfsd_file_dio_alignment = nfsd_file_dio_alignment, 138 }; 139 140 void nfsd_localio_ops_init(void) 141 { 142 nfs_to = &nfsd_localio_ops; 143 } 144 145 /* 146 * UUID_IS_LOCAL XDR functions 147 */ 148 149 static __be32 localio_proc_null(struct svc_rqst *rqstp) 150 { 151 return rpc_success; 152 } 153 154 struct localio_uuidarg { 155 uuid_t uuid; 156 }; 157 158 static __be32 localio_proc_uuid_is_local(struct svc_rqst *rqstp) 159 { 160 struct localio_uuidarg *argp = rqstp->rq_argp; 161 struct net *net = SVC_NET(rqstp); 162 struct nfsd_net *nn = net_generic(net, nfsd_net_id); 163 164 nfs_uuid_is_local(&argp->uuid, &nn->local_clients, 165 &nn->local_clients_lock, 166 net, rqstp->rq_client, THIS_MODULE); 167 168 return rpc_success; 169 } 170 171 static bool localio_decode_uuidarg(struct svc_rqst *rqstp, 172 struct xdr_stream *xdr) 173 { 174 struct localio_uuidarg *argp = rqstp->rq_argp; 175 u8 uuid[UUID_SIZE]; 176 177 if (decode_opaque_fixed(xdr, uuid, UUID_SIZE)) 178 return false; 179 import_uuid(&argp->uuid, uuid); 180 181 return true; 182 } 183 184 static const struct svc_procedure localio_procedures1[] = { 185 [LOCALIOPROC_NULL] = { 186 .pc_func = localio_proc_null, 187 .pc_decode = nfssvc_decode_voidarg, 188 .pc_encode = nfssvc_encode_voidres, 189 .pc_argsize = sizeof(struct nfsd_voidargs), 190 .pc_ressize = sizeof(struct nfsd_voidres), 191 .pc_cachetype = RC_NOCACHE, 192 .pc_xdrressize = 0, 193 .pc_name = "NULL", 194 }, 195 [LOCALIOPROC_UUID_IS_LOCAL] = { 196 .pc_func = localio_proc_uuid_is_local, 197 .pc_decode = localio_decode_uuidarg, 198 .pc_encode = nfssvc_encode_voidres, 199 .pc_argsize = sizeof(struct localio_uuidarg), 200 .pc_argzero = sizeof(struct localio_uuidarg), 201 .pc_ressize = sizeof(struct nfsd_voidres), 202 .pc_cachetype = RC_NOCACHE, 203 .pc_name = "UUID_IS_LOCAL", 204 }, 205 }; 206 207 #define LOCALIO_NR_PROCEDURES ARRAY_SIZE(localio_procedures1) 208 static DEFINE_PER_CPU_ALIGNED(unsigned long, 209 localio_count[LOCALIO_NR_PROCEDURES]); 210 const struct svc_version localio_version1 = { 211 .vs_vers = 1, 212 .vs_nproc = LOCALIO_NR_PROCEDURES, 213 .vs_proc = localio_procedures1, 214 .vs_dispatch = nfsd_dispatch, 215 .vs_count = localio_count, 216 .vs_xdrsize = XDR_QUADLEN(UUID_SIZE), 217 .vs_hidden = true, 218 }; 219