root/usr/src/uts/common/fs/nfs/nfs4x_state.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 *
 * Copyright 2011 Nexenta Systems, Inc.  All rights reserved.
 * Copyright 2017-2026 RackTop Systems.
 */

#include <sys/sdt.h>
#include <sys/atomic.h>
#include <nfs/nfs4.h>

extern uint32_t clientid_hash(void *);
extern bool_t clientid_compare(rfs4_entry_t, void *);
extern void *clientid_mkkey(rfs4_entry_t);

#ifdef DEBUG
#define RFS4_TABSIZE 17
#else
#define RFS4_TABSIZE 2047
#endif

#define RFS4_MAXTABSZ 1024*1024

slotid4 rfs4_max_slots          = MAXSLOTS;             /* fore channel */
slotid4 rfs4_back_max_slots     = MAXSLOTS_BACK;        /* back channel */

static const callback_sec_parms4 rfs4x_cbsec_none = {
        .cb_secflavor = AUTH_NONE
};

typedef union {
        /* Both members have the same size */
        struct {
                uint32_t pad0;
                uint32_t pad1;
                uint32_t start_time;    /* NFS server start time */
                uint32_t s_id;          /* unique session index */
        } impl_id;
        sessionid4 id4;
} rfs4_sid;

/*
 * --------------------------------------------------------
 * MDS - NFSv4.1  Sessions
 * --------------------------------------------------------
 */
static uint32_t
sessid_hash(void *key)
{
        rfs4_sid *idp = key;

        return (idp->impl_id.s_id);
}

static bool_t
sessid_compare(rfs4_entry_t entry, void *key)
{
        rfs4_session_t  *sp = (rfs4_session_t *)entry;
        sessionid4      *idp = (sessionid4 *)key;

        return (bcmp(idp, &sp->sn_sessid, sizeof (sessionid4)) == 0);
}

static void *
sessid_mkkey(rfs4_entry_t entry)
{
        rfs4_session_t *sp = (rfs4_session_t *)entry;

        return (&sp->sn_sessid);
}

static bool_t
sessid_clientid_compare(rfs4_entry_t entry, void *key)
{
        rfs4_session_t  *sp = (rfs4_session_t *)entry;
        clientid4       *idp = (clientid4 *)key;

        return (*idp == sp->sn_clnt->rc_clientid);
}

static void *
sessid_clientid_mkkey(rfs4_entry_t entry)
{
        return (&(((rfs4_session_t *)entry)->sn_clnt->rc_clientid));
}

void
rfs4x_session_rele(rfs4_session_t *sp)
{
        rfs4_dbe_rele(sp->sn_dbe);
}

void
rfs4x_session_hold(rfs4_session_t *sp)
{
        rfs4_dbe_hold(sp->sn_dbe);
}

rfs4_session_t *
rfs4x_findsession_by_id(sessionid4 sessid)
{
        rfs4_session_t  *sp;
        bool_t           create = FALSE;
        nfs4_srv_t *nsrv4 = nfs4_get_srv();

        sp = (rfs4_session_t *)rfs4_dbsearch(nsrv4->rfs4_session_idx,
            sessid, &create, NULL, RFS4_DBS_VALID);

        return (sp);
}

/*
 * A clientid can have multiple sessions associated with it. Hence,
 * performing a raw 'mds_findsession' (even for a create) might
 * yield a list of sessions associated with the clientid in question.
 * Call rfs4_dbseach() function with key that cannot be found
 * and create an association between the session table and both
 * primary (sessionid) index and secondary (clientid) index for the
 * newly created session.
 */

rfs4_session_t  *
rfs4x_createsession(session41_create_t *ap)
{
        static uint32_t session_id_counter;

        rfs4_session_t  *sp = NULL;
        bool_t create = TRUE;
        rfs4_sid key = {0, 0, 0, 0};
        nfs4_srv_t *nsrv4 = nfs4_get_srv();

        /*
         * Use unique counter for s_id and s_id to ensure that
         * created entry will have the same index in dbi_buckets[]
         */
        ap->cs_id = key.impl_id.s_id = atomic_inc_32_nv(&session_id_counter);

        if ((sp = (rfs4_session_t *)rfs4_dbsearch(nsrv4->rfs4_session_idx,
            &key, &create, (void *)ap, RFS4_DBS_VALID)) == NULL) {
                DTRACE_PROBE1(mds__srv__createsession__fail,
                    session41_create_t *, ap);
        }
        return (sp);
}

/* return success of operation */
static bool_t
client_insert_session(rfs4_client_t *cp, rfs4_session_t *sp)
{
        bool_t res = TRUE;

        rfs4_dbe_lock(cp->rc_dbe);
        if (cp->rc_destroying)
                res = FALSE;
        else
                list_insert_tail(&cp->rc_sessions, sp);
        rfs4_dbe_unlock(cp->rc_dbe);

        return (res);
}

static void
client_remove_session(rfs4_client_t *cp, rfs4_session_t *sp)
{
        rfs4_dbe_lock(cp->rc_dbe);
        if (list_link_active(&sp->sn_node))
                list_remove(&cp->rc_sessions, sp);
        rfs4_dbe_unlock(cp->rc_dbe);
}

/*
 * Invalidate the session in the DB (so it can't be found anymore)
 */
nfsstat4
rfs4x_destroysession(rfs4_session_t *sp, unsigned useref)
{
        nfsstat4 status = NFS4_OK;

        /*
         * RFC 7862 Section 14.1.3:
         * In hindsight, the  NFSv4.1 specification should have
         * mandated that DESTROY_SESSION either abort or complete
         * all outstanding operations.
         */
        rfs4_dbe_lock(sp->sn_dbe);
        if (rfs4_dbe_refcnt(sp->sn_dbe) > useref) {
                status = NFS4ERR_DELAY;
        } else if (SN_CB_CHAN_EST(sp)) {
                sess_channel_t  *bcp = SNTOBC(sp);
                sess_bcsd_t     *bsdp;

                rw_enter(&bcp->cn_lock, RW_READER);
                bsdp = CTOBSD(bcp);
                ASSERT(bsdp != NULL);
                status = slot_cb_status(bsdp->bsd_stok);

                mutex_enter(&bsdp->bsd_lock);
                bsdp->bsd_stat = status;
                mutex_exit(&bsdp->bsd_lock);

                rw_exit(&bcp->cn_lock);
        }
        if (status == NFS4_OK)
                rfs4_dbe_invalidate(sp->sn_dbe);
        rfs4_dbe_unlock(sp->sn_dbe);

        if (status == NFS4_OK)
                client_remove_session(sp->sn_clnt, sp);

        return (status);
}

/* Invalidate all client's sessions */
void
rfs4x_client_session_remove(rfs4_client_t *cp)
{
        rfs4_session_t *sp;

        /*
         * Client is forcibly closing so invalidate all sessions
         * without checking the refcount.
         */
        rfs4_dbe_lock(cp->rc_dbe);
        while ((sp = list_head(&cp->rc_sessions)) != NULL) {
                if (list_link_active(&sp->sn_node))
                        list_remove(&cp->rc_sessions, sp);

                rfs4_dbe_invalidate(sp->sn_dbe);
        }
        rfs4_dbe_unlock(cp->rc_dbe);
}

/*
 * RFC8881 Section 18.36.3
 * Definitions for ca_maxrequestsize:
 *   The maximum size of a COMPOUND or CB_COMPOUND request that will be sent.
 */
#define NFS4_MIN_COMPOUND_REQSZ (( \
        2 + 2 + /* credential, verifier: AUTH_NULL, Length 0 */ \
        1 +     /* zero length tag */ \
        3 +     /* minorversion, opcount, opcode */ \
        (RNDUP(NFS4_SESSIONID_SIZE) / BYTES_PER_XDR_UNIT) + \
        4) *    /* seqid, slotid, high-slotid, cachethis */ \
        BYTES_PER_XDR_UNIT)
/*
 * ca_maxresponsesize:
 *   The maximum size of a COMPOUND or CB_COMPOUND reply that the
 *   requester will accept from the replier including RPC headers
 *   (see the ca_maxrequestsize definition).
 */
#define NFS4_MIN_COMPOUND_RESPSZ ((                                     \
        2 +     /* verifier: AUTH_NULL, Length 0 */ \
        1 +     /* status */ \
        1 +     /* zero length tag */ \
        3 +     /* opcount, opcode, status */ \
        (RNDUP(NFS4_SESSIONID_SIZE) / BYTES_PER_XDR_UNIT) + \
        4) *    /* seqid, slotid, high-slotid, target-slotid, status */ \
        BYTES_PER_XDR_UNIT)

#define NFS4_MIN_CB_COMPOUND_REQSZ      NFS4_MIN_COMPOUND_REQSZ
#define NFS4_MIN_CB_COMPOUND_RESPSZ     NFS4_MIN_COMPOUND_RESPSZ

#define NFS4_SLOT_CACHED_SIZE   2048

nfsstat4
sess_chan_limits(rfs4_session_t *sp)
{
        if (sp->cn_attrs.ca_maxrequests > rfs4_max_slots) {
                sp->cn_attrs.ca_maxrequests = rfs4_max_slots;
        }
        if (sp->cn_back_attrs.ca_maxrequests < 1) {
                return (NFS4ERR_INVAL);
        }

        /*
         * RFC 5661 requires the server to preserve the client's
         * backchannel ca_maxrequests value in CREATE_SESSION.
         * However, we are not obligated to USE all the slots
         * the client offers, so limit what we will use here
         * to the lesser of our MAX or the client's.
         */
        sp->sn_bc.maxreqs = MIN(sp->cn_back_attrs.ca_maxrequests,
            rfs4_back_max_slots);

        if (sp->cn_attrs.ca_maxrequestsize < NFS4_MIN_COMPOUND_REQSZ)
                return (NFS4ERR_TOOSMALL);

        if (sp->cn_back_attrs.ca_maxrequestsize < NFS4_MIN_CB_COMPOUND_REQSZ)
                return (NFS4ERR_TOOSMALL);

        if (sp->cn_attrs.ca_maxoperations > NFS4_COMPOUND_LIMIT)
                sp->cn_attrs.ca_maxoperations = NFS4_COMPOUND_LIMIT;

        if (sp->cn_back_attrs.ca_maxoperations < 2)
                return (NFS4ERR_TOOSMALL);

        /*
         * Lower limit should be set to smallest sane COMPOUND. Even
         * though a singleton SEQUENCE op is the very smallest COMPOUND,
         * it's also quite boring. For all practical purposes, the lower
         * limit for creating a sess is limited to:
         *
         *              [SEQUENCE + PUTROOTFH + GETFH]
         *
         *       Can't limit READ's to a specific threshold, otherwise
         *       we artificially limit the clients to perform reads of
         *       AT LEAST that granularity, which is WRONG !!! Same goes
         *       for READDIR's and GETATTR's.
         */
        if (sp->cn_attrs.ca_maxresponsesize < (sizeof (SEQUENCE4res) +
            sizeof (PUTROOTFH4res) + sizeof (GETFH4res)))
                return (NFS4ERR_TOOSMALL);

        if (sp->cn_attrs.ca_maxresponsesize < NFS4_MIN_COMPOUND_RESPSZ)
                return (NFS4ERR_TOOSMALL);

        if (sp->cn_back_attrs.ca_maxresponsesize < NFS4_MIN_CB_COMPOUND_RESPSZ)
                return (NFS4ERR_TOOSMALL);

        /* maxresp_cached */
        sp->cn_attrs.ca_maxresponsesize_cached =
            MIN(sp->cn_attrs.ca_maxresponsesize_cached,
            NFS4_SLOT_CACHED_SIZE + NFS4_MIN_HDR_SEQSZ);

        sp->cn_back_attrs.ca_maxresponsesize_cached = 0;

        return (NFS4_OK);
}

/*
 * NFSv4.1 Slot replay cache
 */
static void
rfs41_cleanup_slot(rfs4_slot_t *se)
{
        rfs4_compound_free((COMPOUND4res *)&se->se_buf);
}

static rfs4_slot_t *
slots_alloc(size_t n)
{
        rfs4_slot_t *p;
        int i;

        p = kmem_zalloc(sizeof (rfs4_slot_t) * n, KM_SLEEP);
        for (i = 0; i < n; i++) {
                mutex_init(&p[i].se_lock, NULL, MUTEX_DEFAULT, NULL);
        }

        return (p);
}

static void
slots_free(rfs4_slot_t *slots, size_t n)
{
        int i;

        for (i = 0; i < n; i++) {
                rfs4_slot_t *slot = &slots[i];

                mutex_destroy(&slot->se_lock);

                if (slot->se_flags & RFS4_SLOT_CACHED) {
                        rfs41_cleanup_slot(slot);
                }
        }
        kmem_free(slots, sizeof (rfs4_slot_t) * n);
}

/* Additional functions */

/* check csa_flags for OP_CREATE_SESSION */
bool_t
nfs4x_csa_flags_valid(uint32_t flags)
{
        if (flags & ~CREATE_SESSION4_FLAG_MASK)
                return (FALSE);

        return (TRUE);
}

/*
 * Delegation CB race detection support
 */
void
rfs41_deleg_rs_hold(rfs4_deleg_state_t *dsp)
{
        atomic_inc_32(&dsp->rds_rs.refcnt);
}

void
rfs41_deleg_rs_rele(rfs4_deleg_state_t *dsp)
{
        ASSERT(dsp->rds_rs.refcnt > 0);
        if (atomic_add_32_nv(&dsp->rds_rs.refcnt, -1) == 0) {
                bzero(dsp->rds_rs.sessid, sizeof (sessionid4));
                dsp->rds_rs.seqid = dsp->rds_rs.slotno = 0;
        }
}

sess_channel_t *
rfs41_create_session_channel(channel_dir_from_server4 dir)
{
        sess_channel_t   *cp;
        sess_bcsd_t      *bp;

        cp = (sess_channel_t *)kmem_zalloc(sizeof (sess_channel_t), KM_SLEEP);
        rw_init(&cp->cn_lock, NULL, RW_DEFAULT, NULL);

        switch (dir) {
        case CDFS4_FORE:
                break;

        case CDFS4_BOTH:
        case CDFS4_BACK:
                /* BackChan Specific Data */
                bp = (sess_bcsd_t *)kmem_zalloc(sizeof (sess_bcsd_t), KM_SLEEP);
                mutex_init(&bp->bsd_lock, NULL, MUTEX_DEFAULT, NULL);
                cp->cn_csd = (sess_bcsd_t *)bp;
                break;
        }
        return (cp);
}

void
rfs41_destroy_back_channel(sess_channel_t *bcp)
{
        sess_bcsd_t     *bsdp;

        ASSERT(bcp != NULL);

        bsdp = CTOBSD(bcp);
        ASSERT(bsdp != NULL);
        mutex_destroy(&bsdp->bsd_lock);
        kmem_free(bsdp, sizeof (sess_bcsd_t));

        rw_destroy(&bcp->cn_lock);
        kmem_free(bcp, sizeof (sess_channel_t));
}

void
rfs4x_destroy_session_channel(rfs4_session_t *sp, channel_dir_from_server4 dir)
{
        sess_channel_t  *cp;

        if (sp == NULL)
                return;
        if (dir == CDFS4_FORE && sp->sn_fore == NULL)
                return;
        if (dir == CDFS4_BACK && sp->sn_back == NULL)
                return;

        if (sp->sn_bdrpc) {
                ASSERT(sp->sn_fore == sp->sn_back);
                sp->sn_fore = NULL;
                goto back;
        }

        if (dir == CDFS4_FORE || dir == CDFS4_BOTH) {
                if (sp->sn_fore == NULL)
                        return;
                cp = sp->sn_fore;

                rw_destroy(&cp->cn_lock);
                kmem_free(cp, sizeof (sess_channel_t));
                sp->sn_fore = NULL;
        }

        if (dir == CDFS4_BACK || dir == CDFS4_BOTH) {
back:
                if (sp->sn_back == NULL)
                        return;
                cp = sp->sn_back;
                rfs41_destroy_back_channel(cp);
                sp->sn_back = NULL;
        }
}

static bool_t
rfs4_session_create(rfs4_entry_t u_entry, void *arg)
{
        rfs4_session_t          *sp = (rfs4_session_t *)u_entry;
        session41_create_t      *ap = (session41_create_t *)arg;
        sess_channel_t          *ocp = NULL;
        rfs4_sid                *sidp;
        bool_t                   bdrpc = FALSE;
        channel_dir_from_server4 dir;
        nfsstat4                 sle;
        rpcprog_t                prog;
        SVCMASTERXPRT           *mxprt;
        struct svc_req          *req;
        sess_bcsd_t             *bsdp;
        callback_sec_parms4     *secp = NULL;

        nfs4_srv_t *nsrv4 = nfs4_get_srv();

        ASSERT(sp != NULL);
        if (sp == NULL)
                return (FALSE);

        /*
         * Back pointer/ref to parent data struct (rfs4_client_t)
         */
        sp->sn_clnt = (rfs4_client_t *)ap->cs_client;
        rfs4_dbe_hold(sp->sn_clnt->rc_dbe);
        req = (struct svc_req *)ap->cs_req;
        mxprt = (SVCMASTERXPRT *)req->rq_xprt->xp_master;

        /*
         * Handcrafting the session id
         */
        sidp = (rfs4_sid *)&sp->sn_sessid;
        sidp->impl_id.pad0 = 0x00000000;
        sidp->impl_id.pad1 = 0xFFFFFFFF;
        sidp->impl_id.start_time = nsrv4->rfs4_start_time;
        sidp->impl_id.s_id = ap->cs_id;

        /*
         * Process csa_flags; note that CREATE_SESSION4_FLAG_CONN_BACK_CHAN
         * is processed below since it affects direction and setup of the
         * backchannel accordingly.
         */
        if (!nfs4x_csa_flags_valid(ap->cs_aotw.csa_flags)) {
                ap->cs_error = NFS4ERR_INVAL;
                goto err;
        }

        sp->sn_csflags = ap->cs_aotw.csa_flags;
        if (ap->cs_aotw.csa_flags & CREATE_SESSION4_FLAG_PERSIST)
                /* Do not support persistent reply cache (yet). */
                sp->sn_csflags &= ~CREATE_SESSION4_FLAG_PERSIST;

        if (ap->cs_aotw.csa_flags & CREATE_SESSION4_FLAG_CONN_RDMA)
                /* No RDMA for now */
                sp->sn_csflags &= ~CREATE_SESSION4_FLAG_CONN_RDMA;

        /*
         * Initialize backchannel security
         */
        sp->sn_bc.progno = ap->cs_aotw.csa_cb_program;
        sp->sn_bc.cr = crget();

        if (ap->cs_aotw.csa_sec_parms.csa_sec_parms_len == 0) {
                secp = (callback_sec_parms4 *)&rfs4x_cbsec_none;
        } else if (ap->cs_aotw.csa_sec_parms.csa_sec_parms_val != NULL) {
                secp = ap->cs_aotw.csa_sec_parms.csa_sec_parms_val;
        } else {
                cmn_err(CE_WARN, "Invalid backchannel security.");
                ap->cs_error = NFS4ERR_INVAL;
                goto err;
        }

        if (!rfs4x_cbsec_valid(secp)) {
                cmn_err(CE_WARN, "Unsupported backchannel security.");
                ap->cs_error = NFS4ERR_INVAL;
                goto err;
        }
        sp->sn_bc.secprms.csa_sec_parms_len = 1;
        sp->sn_bc.secprms.csa_sec_parms_val = (callback_sec_parms4 *)
            kmem_zalloc(sizeof (callback_sec_parms4), KM_SLEEP);
        rfs4x_cbsec_init(sp->sn_bc.secprms.csa_sec_parms_val,
            secp);

        /*
         * Initialize some overall sessions values
         */
        sp->sn_bc.progno = ap->cs_aotw.csa_cb_program;
        sp->sn_laccess = nfs_sys_uptime();
        sp->sn_flags = 0;
        sp->sn_rcached = 0;

        /*
         * Check if client has specified that the FORE channel should
         * also be used for call back traffic (ie. bidir RPC). If so,
         * let's try to accomodate the request.
         */
        DTRACE_PROBE1(csa__flags, uint32_t, ap->cs_aotw.csa_flags);
        bdrpc = ap->cs_aotw.csa_flags & CREATE_SESSION4_FLAG_CONN_BACK_CHAN;
        if (bdrpc) {
                SVCCB_ARGS cbargs;
                prog = sp->sn_bc.progno;
                cbargs.xprt = mxprt;
                cbargs.prog = prog;
                cbargs.vers = NFS_CB;
                cbargs.family = sp->sn_clnt->rc_addr.ss_family;
                cbargs.tag = (void *)sp->sn_sessid;

                if (SVC_CTL(req->rq_xprt, SVCCTL_SET_CBCONN, (void *)&cbargs)) {
                        /*
                         * Couldn't create a bi-dir RPC connection. Reset
                         * bdrpc so that the session's channel flags are
                         * set appropriately and the client knows it needs
                         * to do the BIND_CONN_TO_SESSION dance in order
                         * to establish a callback path.
                         */
                        bdrpc = 0;
                }
        }

        /*
         * Session's channel flags depending on bdrpc
         */
        sp->sn_bdrpc = bdrpc;
        dir = bdrpc ? (CDFS4_FORE | CDFS4_BACK) : CDFS4_FORE;
        ocp = rfs41_create_session_channel(dir);
        ocp->cn_dir = dir;
        sp->sn_fore = ocp;

        /*
         * Check if channel attrs will be flexible enough for future
         * purposes. Channel attribute enforcement is done as part of
         * COMPOUND processing.
         */
        sp->cn_attrs = ap->cs_aotw.csa_fore_chan_attrs;
        sp->cn_back_attrs = ap->cs_aotw.csa_back_chan_attrs;
        sle = sess_chan_limits(sp);
        if (sle != NFS4_OK) {
                ap->cs_error = sle;
                goto err_free_chan;
        }

        /* will fail if client is going to destroy */
        if (!client_insert_session(sp->sn_clnt, sp)) {
                ap->cs_error = NFS4ERR_DELAY;
                goto err_free_chan;
        }

        /*
         * No need for locks/synchronization at this time,
         * since we're barely creating the session.
         */
        if (sp->sn_bdrpc) {
                /*
                 * bcsd got built as part of the channel's construction.
                 */
                bsdp = CTOBSD(ocp);
                ASSERT(bsdp != NULL);
                slot_table_create(&bsdp->bsd_stok, sp->sn_bc.maxreqs);
                sp->sn_csflags |= CREATE_SESSION4_FLAG_CONN_BACK_CHAN;
                sp->sn_back = ocp;
        } else {
                sp->sn_csflags &= ~CREATE_SESSION4_FLAG_CONN_BACK_CHAN;
                sp->sn_back = NULL;
        }

        /*
         * Now we allocate space for the slrc, initializing each slot's
         * sequenceid and slotid to zero and a (pre)cached result of
         * NFS4ERR_SEQ_MISORDERED. Note that we zero out the entries
         * by virtue of the z-alloc.
         */
        sp->sn_slots = slots_alloc(sp->cn_attrs.ca_maxrequests);

        return (TRUE);

err_free_chan:
        rfs4x_destroy_session_channel(sp, CDFS4_BOTH);
err:
        rfs4_dbe_rele(sp->sn_clnt->rc_dbe);
        return (FALSE);
}

static void
rfs4_session_destroy(rfs4_entry_t u_entry)
{
        rfs4_session_t  *sp = (rfs4_session_t *)u_entry;
        sess_bcsd_t     *bsdp;

        if (SN_CB_CHAN_EST(sp) && (bsdp = CTOBSD(sp->sn_back)) != NULL) {
                slot_table_destroy(bsdp->bsd_stok);
                bsdp->bsd_stok = NULL;
                rfs4x_cb_chflush(sp);
        }

        /*
         * Nuke back channel security data.
         */
        rfs4x_cbsec_fini(sp);

        /*
         * Nuke slot replay cache for this session
         */
        if (sp->sn_slots) {
                slots_free(sp->sn_slots, sp->cn_attrs.ca_maxrequests);
                sp->sn_slots = NULL;
        }

        /*
         * Remove the fore and back channels.
         */
        rfs4x_destroy_session_channel(sp, CDFS4_BOTH);

        client_remove_session(sp->sn_clnt, sp);

        rfs4_client_rele(sp->sn_clnt);
}

static bool_t
rfs4_session_expiry(rfs4_entry_t u_entry)
{
        rfs4_session_t *sp = (rfs4_session_t *)u_entry;

        if (sp == NULL || rfs4_dbe_is_invalid(sp->sn_dbe))
                return (TRUE);

        if (rfs4_lease_expired(sp->sn_clnt))
                return (TRUE);

        return (FALSE);
}

void
rfs4x_state_init_locked(nfs4_srv_t *nsrv4)
{
        nsrv4->rfs4_session_tab = rfs4_table_create(nsrv4->nfs4_server_state,
            "Session", 5 * rfs4_lease_time, 2, rfs4_session_create,
            rfs4_session_destroy, rfs4_session_expiry, sizeof (rfs4_session_t),
            RFS4_TABSIZE, RFS4_MAXTABSZ/8, -1);

        nsrv4->rfs4_session_idx = rfs4_index_create(nsrv4->rfs4_session_tab,
            "session_idx", sessid_hash, sessid_compare, sessid_mkkey, TRUE);

        nsrv4->rfs4_session_clid_idx = rfs4_index_create(
            nsrv4->rfs4_session_tab, "session_clid_idx", clientid_hash,
            sessid_clientid_compare, sessid_clientid_mkkey, FALSE);
}

void
rfs4x_state_fini(nfs4_srv_t *nsrv4)
{
        /* All tables will be destroyed by caller */
}