xref: /linux/fs/nfs_common/nfslocalio.c (revision 6254d537277947fc086324954ddfba1188ba8212)
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