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 *
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)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 const struct nfsd_localio_operations nfsd_localio_ops = {
121 .nfsd_net_try_get = nfsd_net_try_get,
122 .nfsd_net_put = nfsd_net_put,
123 .nfsd_open_local_fh = nfsd_open_local_fh,
124 .nfsd_file_put_local = nfsd_file_put_local,
125 .nfsd_file_get_local = nfsd_file_get_local,
126 .nfsd_file_file = nfsd_file_file,
127 };
128
nfsd_localio_ops_init(void)129 void nfsd_localio_ops_init(void)
130 {
131 nfs_to = &nfsd_localio_ops;
132 }
133
134 /*
135 * UUID_IS_LOCAL XDR functions
136 */
137
localio_proc_null(struct svc_rqst * rqstp)138 static __be32 localio_proc_null(struct svc_rqst *rqstp)
139 {
140 return rpc_success;
141 }
142
143 struct localio_uuidarg {
144 uuid_t uuid;
145 };
146
localio_proc_uuid_is_local(struct svc_rqst * rqstp)147 static __be32 localio_proc_uuid_is_local(struct svc_rqst *rqstp)
148 {
149 struct localio_uuidarg *argp = rqstp->rq_argp;
150 struct net *net = SVC_NET(rqstp);
151 struct nfsd_net *nn = net_generic(net, nfsd_net_id);
152
153 nfs_uuid_is_local(&argp->uuid, &nn->local_clients,
154 &nn->local_clients_lock,
155 net, rqstp->rq_client, THIS_MODULE);
156
157 return rpc_success;
158 }
159
localio_decode_uuidarg(struct svc_rqst * rqstp,struct xdr_stream * xdr)160 static bool localio_decode_uuidarg(struct svc_rqst *rqstp,
161 struct xdr_stream *xdr)
162 {
163 struct localio_uuidarg *argp = rqstp->rq_argp;
164 u8 uuid[UUID_SIZE];
165
166 if (decode_opaque_fixed(xdr, uuid, UUID_SIZE))
167 return false;
168 import_uuid(&argp->uuid, uuid);
169
170 return true;
171 }
172
173 static const struct svc_procedure localio_procedures1[] = {
174 [LOCALIOPROC_NULL] = {
175 .pc_func = localio_proc_null,
176 .pc_decode = nfssvc_decode_voidarg,
177 .pc_encode = nfssvc_encode_voidres,
178 .pc_argsize = sizeof(struct nfsd_voidargs),
179 .pc_ressize = sizeof(struct nfsd_voidres),
180 .pc_cachetype = RC_NOCACHE,
181 .pc_xdrressize = 0,
182 .pc_name = "NULL",
183 },
184 [LOCALIOPROC_UUID_IS_LOCAL] = {
185 .pc_func = localio_proc_uuid_is_local,
186 .pc_decode = localio_decode_uuidarg,
187 .pc_encode = nfssvc_encode_voidres,
188 .pc_argsize = sizeof(struct localio_uuidarg),
189 .pc_argzero = sizeof(struct localio_uuidarg),
190 .pc_ressize = sizeof(struct nfsd_voidres),
191 .pc_cachetype = RC_NOCACHE,
192 .pc_name = "UUID_IS_LOCAL",
193 },
194 };
195
196 #define LOCALIO_NR_PROCEDURES ARRAY_SIZE(localio_procedures1)
197 static DEFINE_PER_CPU_ALIGNED(unsigned long,
198 localio_count[LOCALIO_NR_PROCEDURES]);
199 const struct svc_version localio_version1 = {
200 .vs_vers = 1,
201 .vs_nproc = LOCALIO_NR_PROCEDURES,
202 .vs_proc = localio_procedures1,
203 .vs_dispatch = nfsd_dispatch,
204 .vs_count = localio_count,
205 .vs_xdrsize = XDR_QUADLEN(UUID_SIZE),
206 .vs_hidden = true,
207 };
208