summaryrefslogtreecommitdiff
path: root/fs/nfs_common/nfslocalio.c
blob: 09404d142d1ae68fd08f03972ba1cc200939b610 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (C) 2024 Mike Snitzer <snitzer@hammerspace.com>
 * Copyright (C) 2024 NeilBrown <neilb@suse.de>
 */

#include <linux/module.h>
#include <linux/list.h>
#include <linux/nfslocalio.h>
#include <net/netns/generic.h>

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("NFS localio protocol bypass support");

static DEFINE_SPINLOCK(nfs_uuid_lock);

/*
 * Global list of nfs_uuid_t instances
 * that is protected by nfs_uuid_lock.
 */
static LIST_HEAD(nfs_uuids);

void nfs_uuid_init(nfs_uuid_t *nfs_uuid)
{
	nfs_uuid->net = NULL;
	nfs_uuid->dom = NULL;
	INIT_LIST_HEAD(&nfs_uuid->list);
}
EXPORT_SYMBOL_GPL(nfs_uuid_init);

bool nfs_uuid_begin(nfs_uuid_t *nfs_uuid)
{
	spin_lock(&nfs_uuid_lock);
	/* Is this nfs_uuid already in use? */
	if (!list_empty(&nfs_uuid->list)) {
		spin_unlock(&nfs_uuid_lock);
		return false;
	}
	uuid_gen(&nfs_uuid->uuid);
	list_add_tail(&nfs_uuid->list, &nfs_uuids);
	spin_unlock(&nfs_uuid_lock);

	return true;
}
EXPORT_SYMBOL_GPL(nfs_uuid_begin);

void nfs_uuid_end(nfs_uuid_t *nfs_uuid)
{
	if (nfs_uuid->net == NULL) {
		spin_lock(&nfs_uuid_lock);
		if (nfs_uuid->net == NULL)
			list_del_init(&nfs_uuid->list);
		spin_unlock(&nfs_uuid_lock);
	}
}
EXPORT_SYMBOL_GPL(nfs_uuid_end);

static nfs_uuid_t * nfs_uuid_lookup_locked(const uuid_t *uuid)
{
	nfs_uuid_t *nfs_uuid;

	list_for_each_entry(nfs_uuid, &nfs_uuids, list)
		if (uuid_equal(&nfs_uuid->uuid, uuid))
			return nfs_uuid;

	return NULL;
}

static struct module *nfsd_mod;

void nfs_uuid_is_local(const uuid_t *uuid, struct list_head *list,
		       struct net *net, struct auth_domain *dom,
		       struct module *mod)
{
	nfs_uuid_t *nfs_uuid;

	spin_lock(&nfs_uuid_lock);
	nfs_uuid = nfs_uuid_lookup_locked(uuid);
	if (nfs_uuid) {
		kref_get(&dom->ref);
		nfs_uuid->dom = dom;
		/*
		 * We don't hold a ref on the net, but instead put
		 * ourselves on a list so the net pointer can be
		 * invalidated.
		 */
		list_move(&nfs_uuid->list, list);
		rcu_assign_pointer(nfs_uuid->net, net);

		__module_get(mod);
		nfsd_mod = mod;
	}
	spin_unlock(&nfs_uuid_lock);
}
EXPORT_SYMBOL_GPL(nfs_uuid_is_local);

static void nfs_uuid_put_locked(nfs_uuid_t *nfs_uuid)
{
	if (nfs_uuid->net) {
		module_put(nfsd_mod);
		nfs_uuid->net = NULL;
	}
	if (nfs_uuid->dom) {
		auth_domain_put(nfs_uuid->dom);
		nfs_uuid->dom = NULL;
	}
	list_del_init(&nfs_uuid->list);
}

void nfs_uuid_invalidate_clients(struct list_head *list)
{
	nfs_uuid_t *nfs_uuid, *tmp;

	spin_lock(&nfs_uuid_lock);
	list_for_each_entry_safe(nfs_uuid, tmp, list, list)
		nfs_uuid_put_locked(nfs_uuid);
	spin_unlock(&nfs_uuid_lock);
}
EXPORT_SYMBOL_GPL(nfs_uuid_invalidate_clients);

void nfs_uuid_invalidate_one_client(nfs_uuid_t *nfs_uuid)
{
	if (nfs_uuid->net) {
		spin_lock(&nfs_uuid_lock);
		nfs_uuid_put_locked(nfs_uuid);
		spin_unlock(&nfs_uuid_lock);
	}
}
EXPORT_SYMBOL_GPL(nfs_uuid_invalidate_one_client);

struct nfsd_file *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)
{
	struct net *net;
	struct nfsd_file *localio;

	/*
	 * Not running in nfsd context, so must safely get reference on nfsd_serv.
	 * But the server may already be shutting down, if so disallow new localio.
	 * uuid->net is NOT a counted reference, but rcu_read_lock() ensures that
	 * if uuid->net is not NULL, then calling nfsd_serv_try_get() is safe
	 * and if it succeeds we will have an implied reference to the net.
	 *
	 * Otherwise NFS may not have ref on NFSD and therefore cannot safely
	 * make 'nfs_to' calls.
	 */
	rcu_read_lock();
	net = rcu_dereference(uuid->net);
	if (!net || !nfs_to->nfsd_serv_try_get(net)) {
		rcu_read_unlock();
		return ERR_PTR(-ENXIO);
	}
	rcu_read_unlock();
	/* We have an implied reference to net thanks to nfsd_serv_try_get */
	localio = nfs_to->nfsd_open_local_fh(net, uuid->dom, rpc_clnt,
					     cred, nfs_fh, fmode);
	if (IS_ERR(localio)) {
		rcu_read_lock();
		nfs_to->nfsd_serv_put(net);
		rcu_read_unlock();
	}
	return localio;
}
EXPORT_SYMBOL_GPL(nfs_open_local_fh);

/*
 * The NFS LOCALIO code needs to call into NFSD using various symbols,
 * but cannot be statically linked, because that will make the NFS
 * module always depend on the NFSD module.
 *
 * 'nfs_to' provides NFS access to NFSD functions needed for LOCALIO,
 * its lifetime is tightly coupled to the NFSD module and will always
 * be available to NFS LOCALIO because any successful client<->server
 * LOCALIO handshake results in a reference on the NFSD module (above),
 * so NFS implicitly holds a reference to the NFSD module and its
 * functions in the 'nfs_to' nfsd_localio_operations cannot disappear.
 *
 * If the last NFS client using LOCALIO disconnects (and its reference
 * on NFSD dropped) then NFSD could be unloaded, resulting in 'nfs_to'
 * functions being invalid pointers. But if NFSD isn't loaded then NFS
 * will not be able to handshake with NFSD and will have no cause to
 * try to call 'nfs_to' function pointers. If/when NFSD is reloaded it
 * will reinitialize the 'nfs_to' function pointers and make LOCALIO
 * possible.
 */
const struct nfsd_localio_operations *nfs_to;
EXPORT_SYMBOL_GPL(nfs_to);