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 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373
|
// 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 <linux/nfs3.h>
#include <linux/nfs4.h>
#include <linux/nfs_fs.h>
#include <net/netns/generic.h>
#include "localio_trace.h"
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("NFS localio protocol bypass support");
static DEFINE_SPINLOCK(nfs_uuids_lock);
/*
* Global list of nfs_uuid_t instances
* that is protected by nfs_uuids_lock.
*/
static LIST_HEAD(nfs_uuids);
/*
* Lock ordering:
* 1: nfs_uuid->lock
* 2: nfs_uuids_lock
* 3: nfs_uuid->list_lock (aka nn->local_clients_lock)
*
* May skip locks in select cases, but never hold multiple
* locks out of order.
*/
void nfs_uuid_init(nfs_uuid_t *nfs_uuid)
{
RCU_INIT_POINTER(nfs_uuid->net, NULL);
nfs_uuid->dom = NULL;
nfs_uuid->list_lock = NULL;
INIT_LIST_HEAD(&nfs_uuid->list);
INIT_LIST_HEAD(&nfs_uuid->files);
spin_lock_init(&nfs_uuid->lock);
nfs_uuid->nfs3_localio_probe_count = 0;
}
EXPORT_SYMBOL_GPL(nfs_uuid_init);
bool nfs_uuid_begin(nfs_uuid_t *nfs_uuid)
{
spin_lock(&nfs_uuid->lock);
if (rcu_access_pointer(nfs_uuid->net)) {
/* This nfs_uuid is already in use */
spin_unlock(&nfs_uuid->lock);
return false;
}
spin_lock(&nfs_uuids_lock);
if (!list_empty(&nfs_uuid->list)) {
/* This nfs_uuid is already in use */
spin_unlock(&nfs_uuids_lock);
spin_unlock(&nfs_uuid->lock);
return false;
}
list_add_tail(&nfs_uuid->list, &nfs_uuids);
spin_unlock(&nfs_uuids_lock);
uuid_gen(&nfs_uuid->uuid);
spin_unlock(&nfs_uuid->lock);
return true;
}
EXPORT_SYMBOL_GPL(nfs_uuid_begin);
void nfs_uuid_end(nfs_uuid_t *nfs_uuid)
{
if (!rcu_access_pointer(nfs_uuid->net)) {
spin_lock(&nfs_uuid->lock);
if (!rcu_access_pointer(nfs_uuid->net)) {
/* Not local, remove from nfs_uuids */
spin_lock(&nfs_uuids_lock);
list_del_init(&nfs_uuid->list);
spin_unlock(&nfs_uuids_lock);
}
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,
spinlock_t *list_lock, struct net *net,
struct auth_domain *dom, struct module *mod)
{
nfs_uuid_t *nfs_uuid;
spin_lock(&nfs_uuids_lock);
nfs_uuid = nfs_uuid_lookup_locked(uuid);
if (!nfs_uuid) {
spin_unlock(&nfs_uuids_lock);
return;
}
/*
* We don't hold a ref on the net, but instead put
* ourselves on @list (nn->local_clients) so the net
* pointer can be invalidated.
*/
spin_lock(list_lock); /* list_lock is nn->local_clients_lock */
list_move(&nfs_uuid->list, list);
spin_unlock(list_lock);
spin_unlock(&nfs_uuids_lock);
/* Once nfs_uuid is parented to @list, avoid global nfs_uuids_lock */
spin_lock(&nfs_uuid->lock);
__module_get(mod);
nfsd_mod = mod;
nfs_uuid->list_lock = list_lock;
kref_get(&dom->ref);
nfs_uuid->dom = dom;
rcu_assign_pointer(nfs_uuid->net, net);
spin_unlock(&nfs_uuid->lock);
}
EXPORT_SYMBOL_GPL(nfs_uuid_is_local);
void nfs_localio_enable_client(struct nfs_client *clp)
{
/* nfs_uuid_is_local() does the actual enablement */
trace_nfs_localio_enable_client(clp);
}
EXPORT_SYMBOL_GPL(nfs_localio_enable_client);
/*
* Cleanup the nfs_uuid_t embedded in an nfs_client.
* This is the long-form of nfs_uuid_init().
*/
static bool nfs_uuid_put(nfs_uuid_t *nfs_uuid)
{
struct nfs_file_localio *nfl;
spin_lock(&nfs_uuid->lock);
if (unlikely(!rcu_access_pointer(nfs_uuid->net))) {
spin_unlock(&nfs_uuid->lock);
return false;
}
RCU_INIT_POINTER(nfs_uuid->net, NULL);
if (nfs_uuid->dom) {
auth_domain_put(nfs_uuid->dom);
nfs_uuid->dom = NULL;
}
/* Walk list of files and ensure their last references dropped */
while ((nfl = list_first_entry_or_null(&nfs_uuid->files,
struct nfs_file_localio,
list)) != NULL) {
/* If nfs_uuid is already NULL, nfs_close_local_fh is
* closing and we must wait, else we unlink and close.
*/
if (rcu_access_pointer(nfl->nfs_uuid) == NULL) {
/* nfs_close_local_fh() is doing the
* close and we must wait. until it unlinks
*/
wait_var_event_spinlock(nfs_uuid,
list_first_entry_or_null(
&nfs_uuid->files,
struct nfs_file_localio,
list) != nfl,
&nfs_uuid->lock);
continue;
}
/* Remove nfl from nfs_uuid->files list */
list_del_init(&nfl->list);
spin_unlock(&nfs_uuid->lock);
nfs_to_nfsd_file_put_local(&nfl->ro_file);
nfs_to_nfsd_file_put_local(&nfl->rw_file);
cond_resched();
spin_lock(&nfs_uuid->lock);
/* Now we can allow racing nfs_close_local_fh() to
* skip the locking.
*/
store_release_wake_up(&nfl->nfs_uuid, RCU_INITIALIZER(NULL));
}
/* Remove client from nn->local_clients */
if (nfs_uuid->list_lock) {
spin_lock(nfs_uuid->list_lock);
BUG_ON(list_empty(&nfs_uuid->list));
list_del_init(&nfs_uuid->list);
spin_unlock(nfs_uuid->list_lock);
nfs_uuid->list_lock = NULL;
}
module_put(nfsd_mod);
spin_unlock(&nfs_uuid->lock);
return true;
}
void nfs_localio_disable_client(struct nfs_client *clp)
{
if (nfs_uuid_put(&clp->cl_uuid))
trace_nfs_localio_disable_client(clp);
}
EXPORT_SYMBOL_GPL(nfs_localio_disable_client);
void nfs_localio_invalidate_clients(struct list_head *nn_local_clients,
spinlock_t *nn_local_clients_lock)
{
LIST_HEAD(local_clients);
nfs_uuid_t *nfs_uuid, *tmp;
struct nfs_client *clp;
spin_lock(nn_local_clients_lock);
list_splice_init(nn_local_clients, &local_clients);
spin_unlock(nn_local_clients_lock);
list_for_each_entry_safe(nfs_uuid, tmp, &local_clients, list) {
if (WARN_ON(nfs_uuid->list_lock != nn_local_clients_lock))
break;
clp = container_of(nfs_uuid, struct nfs_client, cl_uuid);
nfs_localio_disable_client(clp);
}
}
EXPORT_SYMBOL_GPL(nfs_localio_invalidate_clients);
static int nfs_uuid_add_file(nfs_uuid_t *nfs_uuid, struct nfs_file_localio *nfl)
{
int ret = 0;
/* Add nfl to nfs_uuid->files if it isn't already */
spin_lock(&nfs_uuid->lock);
if (rcu_access_pointer(nfs_uuid->net) == NULL) {
ret = -ENXIO;
} else if (list_empty(&nfl->list)) {
rcu_assign_pointer(nfl->nfs_uuid, nfs_uuid);
list_add_tail(&nfl->list, &nfs_uuid->files);
}
spin_unlock(&nfs_uuid->lock);
return ret;
}
/*
* Caller is responsible for calling nfsd_net_put and
* nfsd_file_put (via nfs_to_nfsd_file_put_local).
*/
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, struct nfs_file_localio *nfl,
struct nfsd_file __rcu **pnf,
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_net_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_net_try_get(net)) {
rcu_read_unlock();
return ERR_PTR(-ENXIO);
}
rcu_read_unlock();
/* We have an implied reference to net thanks to nfsd_net_try_get */
localio = nfs_to->nfsd_open_local_fh(net, uuid->dom, rpc_clnt, cred,
nfs_fh, pnf, fmode);
if (!IS_ERR(localio) && nfs_uuid_add_file(uuid, nfl) < 0) {
/* Delete the cached file when racing with nfs_uuid_put() */
nfs_to_nfsd_file_put_local(pnf);
}
nfs_to_nfsd_net_put(net);
return localio;
}
EXPORT_SYMBOL_GPL(nfs_open_local_fh);
void nfs_close_local_fh(struct nfs_file_localio *nfl)
{
nfs_uuid_t *nfs_uuid;
rcu_read_lock();
nfs_uuid = rcu_dereference(nfl->nfs_uuid);
if (!nfs_uuid) {
/* regular (non-LOCALIO) NFS will hammer this */
rcu_read_unlock();
return;
}
spin_lock(&nfs_uuid->lock);
if (!rcu_access_pointer(nfl->nfs_uuid)) {
/* nfs_uuid_put has finished here */
spin_unlock(&nfs_uuid->lock);
rcu_read_unlock();
return;
}
if (list_empty(&nfl->list)) {
/* nfs_uuid_put() has started closing files, wait for it
* to finished
*/
spin_unlock(&nfs_uuid->lock);
rcu_read_unlock();
wait_var_event(&nfl->nfs_uuid,
rcu_access_pointer(nfl->nfs_uuid) == NULL);
return;
}
/* tell nfs_uuid_put() to wait for us */
RCU_INIT_POINTER(nfl->nfs_uuid, NULL);
spin_unlock(&nfs_uuid->lock);
rcu_read_unlock();
nfs_to_nfsd_file_put_local(&nfl->ro_file);
nfs_to_nfsd_file_put_local(&nfl->rw_file);
/* Remove nfl from nfs_uuid->files list and signal nfs_uuid_put()
* that we are done. The moment we drop the spinlock the
* nfs_uuid could be freed.
*/
spin_lock(&nfs_uuid->lock);
list_del_init(&nfl->list);
wake_up_var_locked(nfs_uuid, &nfs_uuid->lock);
spin_unlock(&nfs_uuid->lock);
}
EXPORT_SYMBOL_GPL(nfs_close_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);
|