| 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(error: -EINVAL); |
| 60 | |
| 61 | if (!nfsd_net_try_get(net)) |
| 62 | return ERR_PTR(error: -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(fhp: &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(clnt: rpc_clnt, cred, &rq_cred); |
| 81 | |
| 82 | beres = nfsd_file_acquire_local(net, cred: &rq_cred, client: dom, |
| 83 | fhp: &fh, may_flags: mayflags, pnf: &localio); |
| 84 | if (beres) |
| 85 | localio = ERR_PTR(error: 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(ptr: localio)) { |
| 92 | struct nfsd_file *new; |
| 93 | if (!nfsd_net_try_get(net)) { |
| 94 | nfsd_file_put(nf: localio); |
| 95 | nfsd_net_put(net); |
| 96 | return ERR_PTR(error: -ENXIO); |
| 97 | } |
| 98 | nfsd_file_get(nf: 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(nf: 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(nf: localio); |
| 110 | nfsd_net_put(net); |
| 111 | nfsd_file_put(nf: 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_file = nfsd_file_file, |
| 136 | .nfsd_file_dio_alignment = nfsd_file_dio_alignment, |
| 137 | }; |
| 138 | |
| 139 | void nfsd_localio_ops_init(void) |
| 140 | { |
| 141 | nfs_to = &nfsd_localio_ops; |
| 142 | } |
| 143 | |
| 144 | /* |
| 145 | * UUID_IS_LOCAL XDR functions |
| 146 | */ |
| 147 | |
| 148 | static __be32 localio_proc_null(struct svc_rqst *rqstp) |
| 149 | { |
| 150 | return rpc_success; |
| 151 | } |
| 152 | |
| 153 | struct localio_uuidarg { |
| 154 | uuid_t uuid; |
| 155 | }; |
| 156 | |
| 157 | static __be32 localio_proc_uuid_is_local(struct svc_rqst *rqstp) |
| 158 | { |
| 159 | struct localio_uuidarg *argp = rqstp->rq_argp; |
| 160 | struct net *net = SVC_NET(rqstp); |
| 161 | struct nfsd_net *nn = net_generic(net, id: nfsd_net_id); |
| 162 | |
| 163 | nfs_uuid_is_local(&argp->uuid, &nn->local_clients, |
| 164 | &nn->local_clients_lock, |
| 165 | net, rqstp->rq_client, THIS_MODULE); |
| 166 | |
| 167 | return rpc_success; |
| 168 | } |
| 169 | |
| 170 | static bool localio_decode_uuidarg(struct svc_rqst *rqstp, |
| 171 | struct xdr_stream *xdr) |
| 172 | { |
| 173 | struct localio_uuidarg *argp = rqstp->rq_argp; |
| 174 | u8 uuid[UUID_SIZE]; |
| 175 | |
| 176 | if (decode_opaque_fixed(xdr, buf: uuid, UUID_SIZE)) |
| 177 | return false; |
| 178 | import_uuid(dst: &argp->uuid, src: uuid); |
| 179 | |
| 180 | return true; |
| 181 | } |
| 182 | |
| 183 | static const struct svc_procedure localio_procedures1[] = { |
| 184 | [LOCALIOPROC_NULL] = { |
| 185 | .pc_func = localio_proc_null, |
| 186 | .pc_decode = nfssvc_decode_voidarg, |
| 187 | .pc_encode = nfssvc_encode_voidres, |
| 188 | .pc_argsize = sizeof(struct nfsd_voidargs), |
| 189 | .pc_ressize = sizeof(struct nfsd_voidres), |
| 190 | .pc_cachetype = RC_NOCACHE, |
| 191 | .pc_xdrressize = 0, |
| 192 | .pc_name = "NULL" , |
| 193 | }, |
| 194 | [LOCALIOPROC_UUID_IS_LOCAL] = { |
| 195 | .pc_func = localio_proc_uuid_is_local, |
| 196 | .pc_decode = localio_decode_uuidarg, |
| 197 | .pc_encode = nfssvc_encode_voidres, |
| 198 | .pc_argsize = sizeof(struct localio_uuidarg), |
| 199 | .pc_argzero = sizeof(struct localio_uuidarg), |
| 200 | .pc_ressize = sizeof(struct nfsd_voidres), |
| 201 | .pc_cachetype = RC_NOCACHE, |
| 202 | .pc_name = "UUID_IS_LOCAL" , |
| 203 | }, |
| 204 | }; |
| 205 | |
| 206 | #define LOCALIO_NR_PROCEDURES ARRAY_SIZE(localio_procedures1) |
| 207 | static DEFINE_PER_CPU_ALIGNED(unsigned long, |
| 208 | localio_count[LOCALIO_NR_PROCEDURES]); |
| 209 | const struct svc_version localio_version1 = { |
| 210 | .vs_vers = 1, |
| 211 | .vs_nproc = LOCALIO_NR_PROCEDURES, |
| 212 | .vs_proc = localio_procedures1, |
| 213 | .vs_dispatch = nfsd_dispatch, |
| 214 | .vs_count = localio_count, |
| 215 | .vs_xdrsize = XDR_QUADLEN(UUID_SIZE), |
| 216 | .vs_hidden = true, |
| 217 | }; |
| 218 | |