root/fs/nfsd/localio.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * NFS server support for local clients to bypass network stack
 *
 * Copyright (C) 2014 Weston Andros Adamson <dros@primarydata.com>
 * Copyright (C) 2019 Trond Myklebust <trond.myklebust@hammerspace.com>
 * Copyright (C) 2024 Mike Snitzer <snitzer@hammerspace.com>
 * Copyright (C) 2024 NeilBrown <neilb@suse.de>
 */

#include <linux/exportfs.h>
#include <linux/sunrpc/svcauth.h>
#include <linux/sunrpc/clnt.h>
#include <linux/nfs.h>
#include <linux/nfs_common.h>
#include <linux/nfslocalio.h>
#include <linux/nfs_fs.h>
#include <linux/nfs_xdr.h>
#include <linux/string.h>

#include "nfsd.h"
#include "vfs.h"
#include "netns.h"
#include "filecache.h"
#include "cache.h"

/**
 * nfsd_open_local_fh - lookup a local filehandle @nfs_fh and map to nfsd_file
 *
 * @net: 'struct net' to get the proper nfsd_net required for LOCALIO access
 * @dom: 'struct auth_domain' required for LOCALIO access
 * @rpc_clnt: rpc_clnt that the client established
 * @cred: cred that the client established
 * @nfs_fh: filehandle to lookup
 * @pnf: place to find the nfsd_file, or store it if it was non-NULL
 * @fmode: fmode_t to use for open
 *
 * This function maps a local fh to a path on a local filesystem.
 * This is useful when the nfs client has the local server mounted - it can
 * avoid all the NFS overhead with reads, writes and commits.
 *
 * On successful return, returned nfsd_file will have its nf_net member
 * set. Caller (NFS client) is responsible for calling nfsd_net_put and
 * nfsd_file_put (via nfs_to_nfsd_file_put_local).
 */
static struct nfsd_file *
nfsd_open_local_fh(struct net *net, struct auth_domain *dom,
                   struct rpc_clnt *rpc_clnt, const struct cred *cred,
                   const struct nfs_fh *nfs_fh, struct nfsd_file __rcu **pnf,
                   const fmode_t fmode)
{
        int mayflags = NFSD_MAY_LOCALIO;
        struct svc_cred rq_cred;
        struct svc_fh fh;
        struct nfsd_file *localio;
        __be32 beres;

        if (nfs_fh->size > NFS4_FHSIZE)
                return ERR_PTR(-EINVAL);

        if (!nfsd_net_try_get(net))
                return ERR_PTR(-ENXIO);

        rcu_read_lock();
        localio = nfsd_file_get(rcu_dereference(*pnf));
        rcu_read_unlock();
        if (localio)
                return localio;

        /* nfs_fh -> svc_fh */
        fh_init(&fh, NFS4_FHSIZE);
        fh.fh_handle.fh_size = nfs_fh->size;
        memcpy(fh.fh_handle.fh_raw, nfs_fh->data, nfs_fh->size);

        if (fmode & FMODE_READ)
                mayflags |= NFSD_MAY_READ;
        if (fmode & FMODE_WRITE)
                mayflags |= NFSD_MAY_WRITE;

        svcauth_map_clnt_to_svc_cred_local(rpc_clnt, cred, &rq_cred);

        beres = nfsd_file_acquire_local(net, &rq_cred, dom,
                                        &fh, mayflags, &localio);
        if (beres)
                localio = ERR_PTR(nfs_stat_to_errno(be32_to_cpu(beres)));

        fh_put(&fh);
        if (rq_cred.cr_group_info)
                put_group_info(rq_cred.cr_group_info);

        if (!IS_ERR(localio)) {
                struct nfsd_file *new;
                if (!nfsd_net_try_get(net)) {
                        nfsd_file_put(localio);
                        nfsd_net_put(net);
                        return ERR_PTR(-ENXIO);
                }
                nfsd_file_get(localio);
        again:
                new = unrcu_pointer(cmpxchg(pnf, NULL, RCU_INITIALIZER(localio)));
                if (new) {
                        /* Some other thread installed an nfsd_file */
                        if (nfsd_file_get(new) == NULL)
                                goto again;
                        /*
                         * Drop the ref we were going to install (both file and
                         * net) and the one we were going to return (only file).
                         */
                        nfsd_file_put(localio);
                        nfsd_net_put(net);
                        nfsd_file_put(localio);
                        localio = new;
                }
        } else
                nfsd_net_put(net);

        return localio;
}

static void nfsd_file_dio_alignment(struct nfsd_file *nf,
                                    u32 *nf_dio_mem_align,
                                    u32 *nf_dio_offset_align,
                                    u32 *nf_dio_read_offset_align)
{
        *nf_dio_mem_align = nf->nf_dio_mem_align;
        *nf_dio_offset_align = nf->nf_dio_offset_align;
        *nf_dio_read_offset_align = nf->nf_dio_read_offset_align;
}

static const struct nfsd_localio_operations nfsd_localio_ops = {
        .nfsd_net_try_get  = nfsd_net_try_get,
        .nfsd_net_put  = nfsd_net_put,
        .nfsd_open_local_fh = nfsd_open_local_fh,
        .nfsd_file_put_local = nfsd_file_put_local,
        .nfsd_file_file = nfsd_file_file,
        .nfsd_file_dio_alignment = nfsd_file_dio_alignment,
};

void nfsd_localio_ops_init(void)
{
        nfs_to = &nfsd_localio_ops;
}

/*
 * UUID_IS_LOCAL XDR functions
 */

static __be32 localio_proc_null(struct svc_rqst *rqstp)
{
        return rpc_success;
}

struct localio_uuidarg {
        uuid_t                  uuid;
};

static __be32 localio_proc_uuid_is_local(struct svc_rqst *rqstp)
{
        struct localio_uuidarg *argp = rqstp->rq_argp;
        struct net *net = SVC_NET(rqstp);
        struct nfsd_net *nn = net_generic(net, nfsd_net_id);

        nfs_uuid_is_local(&argp->uuid, &nn->local_clients,
                          &nn->local_clients_lock,
                          net, rqstp->rq_client, THIS_MODULE);

        return rpc_success;
}

static bool localio_decode_uuidarg(struct svc_rqst *rqstp,
                                   struct xdr_stream *xdr)
{
        struct localio_uuidarg *argp = rqstp->rq_argp;
        u8 uuid[UUID_SIZE];

        if (decode_opaque_fixed(xdr, uuid, UUID_SIZE))
                return false;
        import_uuid(&argp->uuid, uuid);

        return true;
}

static const struct svc_procedure localio_procedures1[] = {
        [LOCALIOPROC_NULL] = {
                .pc_func = localio_proc_null,
                .pc_decode = nfssvc_decode_voidarg,
                .pc_encode = nfssvc_encode_voidres,
                .pc_argsize = sizeof(struct nfsd_voidargs),
                .pc_ressize = sizeof(struct nfsd_voidres),
                .pc_cachetype = RC_NOCACHE,
                .pc_xdrressize = 0,
                .pc_name = "NULL",
        },
        [LOCALIOPROC_UUID_IS_LOCAL] = {
                .pc_func = localio_proc_uuid_is_local,
                .pc_decode = localio_decode_uuidarg,
                .pc_encode = nfssvc_encode_voidres,
                .pc_argsize = sizeof(struct localio_uuidarg),
                .pc_argzero = sizeof(struct localio_uuidarg),
                .pc_ressize = sizeof(struct nfsd_voidres),
                .pc_cachetype = RC_NOCACHE,
                .pc_name = "UUID_IS_LOCAL",
        },
};

#define LOCALIO_NR_PROCEDURES ARRAY_SIZE(localio_procedures1)
static DEFINE_PER_CPU_ALIGNED(unsigned long,
                              localio_count[LOCALIO_NR_PROCEDURES]);
const struct svc_version localio_version1 = {
        .vs_vers        = 1,
        .vs_nproc       = LOCALIO_NR_PROCEDURES,
        .vs_proc        = localio_procedures1,
        .vs_dispatch    = nfsd_dispatch,
        .vs_count       = localio_count,
        .vs_xdrsize     = XDR_QUADLEN(UUID_SIZE),
        .vs_hidden      = true,
};