// 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);
Messung V0.5 in Prozent C=93 H=98 G=95
¤ Dauer der Verarbeitung: 0.12 Sekunden
(vorverarbeitet am 2026-09-29)
¤
*© Formatika GbR, Deutschland