1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3 * Copyright (C) 2024 Mike Snitzer <snitzer@hammerspace.com>
4 * Copyright (C) 2024 NeilBrown <neilb@suse.de>
5 */
6
7 #include <linux/module.h>
8 #include <linux/rculist.h>
9 #include <linux/nfslocalio.h>
10 #include <net/netns/generic.h>
11
12 MODULE_LICENSE("GPL");
13 MODULE_DESCRIPTION("NFS localio protocol bypass support");
14
15 static DEFINE_SPINLOCK(nfs_uuid_lock);
16
17 /*
18 * Global list of nfs_uuid_t instances
19 * that is protected by nfs_uuid_lock.
20 */
21 static LIST_HEAD(nfs_uuids);
22
nfs_uuid_begin(nfs_uuid_t * nfs_uuid)23 void nfs_uuid_begin(nfs_uuid_t *nfs_uuid)
24 {
25 nfs_uuid->net = NULL;
26 nfs_uuid->dom = NULL;
27 uuid_gen(&nfs_uuid->uuid);
28
29 spin_lock(&nfs_uuid_lock);
30 list_add_tail_rcu(&nfs_uuid->list, &nfs_uuids);
31 spin_unlock(&nfs_uuid_lock);
32 }
33 EXPORT_SYMBOL_GPL(nfs_uuid_begin);
34
nfs_uuid_end(nfs_uuid_t * nfs_uuid)35 void nfs_uuid_end(nfs_uuid_t *nfs_uuid)
36 {
37 if (nfs_uuid->net == NULL) {
38 spin_lock(&nfs_uuid_lock);
39 list_del_init(&nfs_uuid->list);
40 spin_unlock(&nfs_uuid_lock);
41 }
42 }
43 EXPORT_SYMBOL_GPL(nfs_uuid_end);
44
nfs_uuid_lookup_locked(const uuid_t * uuid)45 static nfs_uuid_t * nfs_uuid_lookup_locked(const uuid_t *uuid)
46 {
47 nfs_uuid_t *nfs_uuid;
48
49 list_for_each_entry(nfs_uuid, &nfs_uuids, list)
50 if (uuid_equal(&nfs_uuid->uuid, uuid))
51 return nfs_uuid;
52
53 return NULL;
54 }
55
56 static struct module *nfsd_mod;
57
nfs_uuid_is_local(const uuid_t * uuid,struct list_head * list,struct net * net,struct auth_domain * dom,struct module * mod)58 void nfs_uuid_is_local(const uuid_t *uuid, struct list_head *list,
59 struct net *net, struct auth_domain *dom,
60 struct module *mod)
61 {
62 nfs_uuid_t *nfs_uuid;
63
64 spin_lock(&nfs_uuid_lock);
65 nfs_uuid = nfs_uuid_lookup_locked(uuid);
66 if (nfs_uuid) {
67 kref_get(&dom->ref);
68 nfs_uuid->dom = dom;
69 /*
70 * We don't hold a ref on the net, but instead put
71 * ourselves on a list so the net pointer can be
72 * invalidated.
73 */
74 list_move(&nfs_uuid->list, list);
75 rcu_assign_pointer(nfs_uuid->net, net);
76
77 __module_get(mod);
78 nfsd_mod = mod;
79 }
80 spin_unlock(&nfs_uuid_lock);
81 }
82 EXPORT_SYMBOL_GPL(nfs_uuid_is_local);
83
nfs_uuid_put_locked(nfs_uuid_t * nfs_uuid)84 static void nfs_uuid_put_locked(nfs_uuid_t *nfs_uuid)
85 {
86 if (nfs_uuid->net) {
87 module_put(nfsd_mod);
88 nfs_uuid->net = NULL;
89 }
90 if (nfs_uuid->dom) {
91 auth_domain_put(nfs_uuid->dom);
92 nfs_uuid->dom = NULL;
93 }
94 list_del_init(&nfs_uuid->list);
95 }
96
nfs_uuid_invalidate_clients(struct list_head * list)97 void nfs_uuid_invalidate_clients(struct list_head *list)
98 {
99 nfs_uuid_t *nfs_uuid, *tmp;
100
101 spin_lock(&nfs_uuid_lock);
102 list_for_each_entry_safe(nfs_uuid, tmp, list, list)
103 nfs_uuid_put_locked(nfs_uuid);
104 spin_unlock(&nfs_uuid_lock);
105 }
106 EXPORT_SYMBOL_GPL(nfs_uuid_invalidate_clients);
107
nfs_uuid_invalidate_one_client(nfs_uuid_t * nfs_uuid)108 void nfs_uuid_invalidate_one_client(nfs_uuid_t *nfs_uuid)
109 {
110 if (nfs_uuid->net) {
111 spin_lock(&nfs_uuid_lock);
112 nfs_uuid_put_locked(nfs_uuid);
113 spin_unlock(&nfs_uuid_lock);
114 }
115 }
116 EXPORT_SYMBOL_GPL(nfs_uuid_invalidate_one_client);
117
nfs_open_local_fh(nfs_uuid_t * uuid,struct rpc_clnt * rpc_clnt,const struct cred * cred,const struct nfs_fh * nfs_fh,const fmode_t fmode)118 struct nfsd_file *nfs_open_local_fh(nfs_uuid_t *uuid,
119 struct rpc_clnt *rpc_clnt, const struct cred *cred,
120 const struct nfs_fh *nfs_fh, const fmode_t fmode)
121 {
122 struct net *net;
123 struct nfsd_file *localio;
124
125 /*
126 * Not running in nfsd context, so must safely get reference on nfsd_serv.
127 * But the server may already be shutting down, if so disallow new localio.
128 * uuid->net is NOT a counted reference, but rcu_read_lock() ensures that
129 * if uuid->net is not NULL, then calling nfsd_serv_try_get() is safe
130 * and if it succeeds we will have an implied reference to the net.
131 *
132 * Otherwise NFS may not have ref on NFSD and therefore cannot safely
133 * make 'nfs_to' calls.
134 */
135 rcu_read_lock();
136 net = rcu_dereference(uuid->net);
137 if (!net || !nfs_to->nfsd_serv_try_get(net)) {
138 rcu_read_unlock();
139 return ERR_PTR(-ENXIO);
140 }
141 rcu_read_unlock();
142 /* We have an implied reference to net thanks to nfsd_serv_try_get */
143 localio = nfs_to->nfsd_open_local_fh(net, uuid->dom, rpc_clnt,
144 cred, nfs_fh, fmode);
145 if (IS_ERR(localio)) {
146 rcu_read_lock();
147 nfs_to->nfsd_serv_put(net);
148 rcu_read_unlock();
149 }
150 return localio;
151 }
152 EXPORT_SYMBOL_GPL(nfs_open_local_fh);
153
154 /*
155 * The NFS LOCALIO code needs to call into NFSD using various symbols,
156 * but cannot be statically linked, because that will make the NFS
157 * module always depend on the NFSD module.
158 *
159 * 'nfs_to' provides NFS access to NFSD functions needed for LOCALIO,
160 * its lifetime is tightly coupled to the NFSD module and will always
161 * be available to NFS LOCALIO because any successful client<->server
162 * LOCALIO handshake results in a reference on the NFSD module (above),
163 * so NFS implicitly holds a reference to the NFSD module and its
164 * functions in the 'nfs_to' nfsd_localio_operations cannot disappear.
165 *
166 * If the last NFS client using LOCALIO disconnects (and its reference
167 * on NFSD dropped) then NFSD could be unloaded, resulting in 'nfs_to'
168 * functions being invalid pointers. But if NFSD isn't loaded then NFS
169 * will not be able to handshake with NFSD and will have no cause to
170 * try to call 'nfs_to' function pointers. If/when NFSD is reloaded it
171 * will reinitialize the 'nfs_to' function pointers and make LOCALIO
172 * possible.
173 */
174 const struct nfsd_localio_operations *nfs_to;
175 EXPORT_SYMBOL_GPL(nfs_to);
176