root/usr/src/uts/common/klm/klmmod.c
/*
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may only use this file in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy is of the CDDL is also available via the Internet
 * at http://www.illumos.org/license/CDDL.
 */

/*
 * Copyright 2011 Nexenta Systems, Inc.  All rights reserved.
 * Copyright (c) 2012 by Delphix. All rights reserved.
 */

/*
 * NFS Lock Manager, server-side and common.
 *
 * This file contains all the external entry points of klmmod.
 * Basically, this is the "glue" to the BSD nlm code.
 */

#include <sys/types.h>
#include <sys/errno.h>
#include <sys/modctl.h>
#include <sys/flock.h>

#include <nfs/nfs.h>
#include <nfs/nfssys.h>
#include <nfs/lm.h>
#include <rpcsvc/nlm_prot.h>
#include "nlm_impl.h"

static struct modlmisc modlmisc = {
        &mod_miscops, "lock mgr common module"
};

static struct modlinkage modlinkage = {
        MODREV_1, &modlmisc, NULL
};

/*
 * Cluster node ID.  Zero unless we're part of a cluster.
 * Set by lm_set_nlmid_flk.  Pass to lm_set_nlm_status.
 * We're not yet doing "clustered" NLM stuff.
 */
int lm_global_nlmid = 0;

/*
 * Call-back hook for clusters: Set lock manager status.
 * If this hook is set, call this instead of the ususal
 * flk_set_lockmgr_status(FLK_LOCKMGR_UP / DOWN);
 */
void (*lm_set_nlm_status)(int nlm_id, flk_nlm_status_t) = NULL;

/*
 * Call-back hook for clusters: Delete all locks held by sysid.
 * Call from code that drops all client locks (for which we're
 * the server) i.e. after the SM tells us a client has crashed.
 */
void (*lm_remove_file_locks)(int) = NULL;

zone_key_t              nlm_zone_key;

/*
 * Init/fini per-zone stuff for klm
 */
/* ARGSUSED */
void *
lm_zone_init(zoneid_t zoneid)
{
        struct nlm_globals *g;

        g = kmem_zalloc(sizeof (*g), KM_SLEEP);

        avl_create(&g->nlm_hosts_tree, nlm_host_cmp,
            sizeof (struct nlm_host),
            offsetof(struct nlm_host, nh_by_addr));

        g->nlm_hosts_hash = mod_hash_create_idhash("nlm_host_by_sysid",
            64, mod_hash_null_valdtor);

        TAILQ_INIT(&g->nlm_idle_hosts);
        TAILQ_INIT(&g->nlm_slocks);

        mutex_init(&g->lock, NULL, MUTEX_DEFAULT, NULL);
        cv_init(&g->nlm_gc_sched_cv, NULL, CV_DEFAULT, NULL);
        cv_init(&g->nlm_gc_finish_cv, NULL, CV_DEFAULT, NULL);
        mutex_init(&g->clean_lock, NULL, MUTEX_DEFAULT, NULL);

        g->lockd_pid = 0;
        g->run_status = NLM_ST_DOWN;
        g->nlm_zoneid = zoneid;

        nlm_globals_register(g);
        return (g);
}

/* ARGSUSED */
void
lm_zone_fini(zoneid_t zoneid, void *data)
{
        struct nlm_globals *g = data;

        nlm_globals_unregister(g);

        ASSERT(avl_is_empty(&g->nlm_hosts_tree));
        avl_destroy(&g->nlm_hosts_tree);
        mod_hash_destroy_idhash(g->nlm_hosts_hash);

        ASSERT(g->nlm_gc_thread == NULL);
        mutex_destroy(&g->lock);
        cv_destroy(&g->nlm_gc_sched_cv);
        cv_destroy(&g->nlm_gc_finish_cv);
        mutex_destroy(&g->clean_lock);

        kmem_free(g, sizeof (*g));
}



/*
 * ****************************************************************
 * module init, fini, info
 */
int
_init()
{
        int retval;

        rw_init(&lm_lck, NULL, RW_DEFAULT, NULL);
        nlm_init();

        zone_key_create(&nlm_zone_key, lm_zone_init, NULL, lm_zone_fini);
        /* Per-zone lockmgr data.  See: os/flock.c */
        zone_key_create(&flock_zone_key, flk_zone_init, NULL, flk_zone_fini);

        retval = mod_install(&modlinkage);
        if (retval == 0)
                return (0);

        /*
         * mod_install failed! undo above, reverse order
         */

        (void) zone_key_delete(flock_zone_key);
        flock_zone_key = ZONE_KEY_UNINITIALIZED;
        (void) zone_key_delete(nlm_zone_key);
        rw_destroy(&lm_lck);

        return (retval);
}

int
_fini()
{
        /* Don't unload. */
        return (EBUSY);
}

int
_info(struct modinfo *modinfop)
{
        return (mod_info(&modlinkage, modinfop));
}



/*
 * ****************************************************************
 * Stubs listed in modstubs.S
 */

/*
 * klm system calls.  Start service on some endpoint.
 * Called by nfssys() LM_SVC, from lockd.
 */
int
lm_svc(struct lm_svc_args *args)
{
        struct knetconfig knc;
        const char *netid;
        struct nlm_globals *g;
        struct file *fp = NULL;
        int err = 0;

        /* Get our "globals" */
        g = zone_getspecific(nlm_zone_key, curzone);

        /*
         * Check version of lockd calling.
         */
        if (args->version != LM_SVC_CUR_VERS) {
                NLM_ERR("lm_svc: Version mismatch "
                    "(given 0x%x, expected 0x%x)\n",
                    args->version, LM_SVC_CUR_VERS);
                return (EINVAL);
        }

        /*
         * Build knetconfig, checking arg values.
         * Also come up with the "netid" string.
         * (With some knowledge of /etc/netconfig)
         */
        bzero(&knc, sizeof (knc));
        switch (args->n_proto) {
        case LM_TCP:
                knc.knc_semantics = NC_TPI_COTS_ORD;
                knc.knc_proto = NC_TCP;
                break;
        case LM_UDP:
                knc.knc_semantics = NC_TPI_CLTS;
                knc.knc_proto = NC_UDP;
                break;
        default:
                NLM_ERR("nlm_build_knetconfig: Unknown "
                    "lm_proto=0x%x\n", args->n_proto);
                return (EINVAL);
        }

        switch (args->n_fmly) {
        case LM_INET:
                knc.knc_protofmly = NC_INET;
                break;
        case LM_INET6:
                knc.knc_protofmly = NC_INET6;
                break;
        case LM_LOOPBACK:
                knc.knc_protofmly = NC_LOOPBACK;
                /* Override what we set above. */
                knc.knc_proto = NC_NOPROTO;
                break;
        default:
                NLM_ERR("nlm_build_knetconfig: Unknown "
                    "lm_fmly=0x%x\n", args->n_fmly);
                return (EINVAL);
        }

        knc.knc_rdev = args->n_rdev;
        netid = nlm_knc_to_netid(&knc);
        if (!netid)
                return (EINVAL);

        /*
         * Setup service on the passed transport.
         * NB: must releasef(fp) after this.
         */
        if ((fp = getf(args->fd)) == NULL)
                return (EBADF);

        mutex_enter(&g->lock);
        /*
         * Don't try to start while still shutting down,
         * or lots of things will fail...
         */
        if (g->run_status == NLM_ST_STOPPING) {
                err = EAGAIN;
                goto out;
        }

        /*
         * There is no separate "initialize" sub-call for nfssys,
         * and we want to do some one-time work when the first
         * binding comes in from lockd.
         */
        if (g->run_status == NLM_ST_DOWN) {
                g->run_status = NLM_ST_STARTING;
                g->lockd_pid = curproc->p_pid;

                /* Save the options. */
                g->cn_idle_tmo = args->timout;
                g->grace_period = args->grace;
                g->retrans_tmo = args->retransmittimeout;

                /* See nfs_sys.c (not yet per-zone) */
                if (INGLOBALZONE(curproc)) {
                        rfs4_grace_period = args->grace;
                        rfs4_lease_time   = args->grace;
                }

                mutex_exit(&g->lock);
                err = nlm_svc_starting(g, fp, netid, &knc);
                mutex_enter(&g->lock);
        } else {
                /*
                 * If KLM is not started and the very first endpoint lockd
                 * tries to add is not a loopback device, report an error.
                 */
                if (g->run_status != NLM_ST_UP) {
                        err = ENOTACTIVE;
                        goto out;
                }
                if (g->lockd_pid != curproc->p_pid) {
                        /* Check if caller has the same PID lockd does */
                        err = EPERM;
                        goto out;
                }

                err = nlm_svc_add_ep(fp, netid, &knc);
        }

out:
        mutex_exit(&g->lock);
        if (fp != NULL)
                releasef(args->fd);

        return (err);
}

/*
 * klm system calls.  Kill the lock manager.
 * Called by nfssys() KILL_LOCKMGR,
 * liblm:lm_shutdown() <- unused?
 */
int
lm_shutdown(void)
{
        struct nlm_globals *g;
        proc_t *p;
        pid_t pid;

        /* Get our "globals" */
        g = zone_getspecific(nlm_zone_key, curzone);

        mutex_enter(&g->lock);
        if (g->run_status != NLM_ST_UP) {
                mutex_exit(&g->lock);
                return (EBUSY);
        }

        g->run_status = NLM_ST_STOPPING;
        pid = g->lockd_pid;
        mutex_exit(&g->lock);
        nlm_svc_stopping(g);

        mutex_enter(&pidlock);
        p = prfind(pid);
        if (p != NULL)
                psignal(p, SIGTERM);

        mutex_exit(&pidlock);
        return (0);
}

/*
 * Cleanup remote locks on FS un-export.
 *
 * NOTE: called from nfs_export.c:unexport()
 * right before the share is going to
 * be unexported.
 */
void
lm_unexport(struct exportinfo *exi)
{
        nlm_unexport(exi);
}

/*
 * CPR suspend/resume hooks.
 * See:cpr_suspend, cpr_resume
 *
 * Before suspend, get current state from "statd" on
 * all remote systems for which we have locks.
 *
 * After resume, check with those systems again,
 * and either reclaim locks, or do SIGLOST.
 */
void
lm_cprsuspend(void)
{
        nlm_cprsuspend();
}

void
lm_cprresume(void)
{
        nlm_cprresume();
}

/*
 * Add the nlm_id bits to the sysid (by ref).
 */
void
lm_set_nlmid_flk(int *new_sysid)
{
        if (lm_global_nlmid != 0)
                *new_sysid |= (lm_global_nlmid << BITS_IN_SYSID);
}

/*
 * It seems that closed source klmmod used
 * this function to release knetconfig stored
 * in mntinfo structure (see mntinfo's mi_klmconfig
 * field).
 * We store knetconfigs differently, thus we don't
 * need this function.
 */
void
lm_free_config(struct knetconfig *knc)
{
        _NOTE(ARGUNUSED(knc));
}

/*
 * Called by NFS4 delegation code to check if there are any
 * NFSv2/v3 locks for the file, so it should not delegate.
 *
 * NOTE: called from NFSv4 code
 * (see nfs4_srv_deleg.c:rfs4_bgrant_delegation())
 */
int
lm_vp_active(const vnode_t *vp)
{
        return (nlm_vp_active(vp));
}

/*
 * Find or create a "sysid" for given knc+addr.
 * name is optional.  Sets nc_changed if the
 * found knc_proto is different from passed.
 * Increments the reference count.
 *
 * Called internally, and in nfs4_find_sysid()
 */
struct lm_sysid *
lm_get_sysid(struct knetconfig *knc, struct netbuf *addr,
    char *name, bool_t *nc_changed)
{
        struct nlm_globals *g;
        const char *netid;
        struct nlm_host *hostp;

        _NOTE(ARGUNUSED(nc_changed));
        netid = nlm_knc_to_netid(knc);
        if (netid == NULL)
                return (NULL);

        g = zone_getspecific(nlm_zone_key, curzone);

        hostp = nlm_host_findcreate(g, name, netid, addr, NULL);
        if (hostp == NULL)
                return (NULL);

        return ((struct lm_sysid *)hostp);
}

/*
 * Release a reference on a "sysid".
 */
void
lm_rel_sysid(struct lm_sysid *sysid)
{
        struct nlm_globals *g;

        g = zone_getspecific(nlm_zone_key, curzone);
        nlm_host_release(g, (struct nlm_host *)sysid);
}

/*
 * Alloc/free a sysid_t (a unique number between
 * LM_SYSID and LM_SYSID_MAX).
 *
 * Used by NFSv4 rfs4_op_lockt and smbsrv/smb_fsop_frlock,
 * both to represent non-local locks outside of klm.
 *
 * NOTE: called from NFSv4 and SMBFS to allocate unique
 * sysid.
 */
sysid_t
lm_alloc_sysidt(void)
{
        return (nlm_sysid_alloc());
}

void
lm_free_sysidt(sysid_t sysid)
{
        nlm_sysid_free(sysid);
}

/* Access private member lms->sysid */
sysid_t
lm_sysidt(struct lm_sysid *lms)
{
        return (((struct nlm_host *)lms)->nh_sysid);
}

/*
 * Called by nfs_frlock to check lock constraints.
 * Return non-zero if the lock request is "safe", i.e.
 * the range is not mapped, not MANDLOCK, etc.
 *
 * NOTE: callde from NFSv3/NFSv2 frlock() functions to
 * determine whether it's safe to add new lock.
 */
int
lm_safelock(vnode_t *vp, const struct flock64 *fl, cred_t *cr)
{
        return (nlm_safelock(vp, fl, cr));
}

/*
 * Called by nfs_lockcompletion to check whether it's "safe"
 * to map the file (and cache it's data).  Walks the list of
 * file locks looking for any that are not "whole file".
 *
 * NOTE: called from nfs_client.c:nfs_lockcompletion()
 */
int
lm_safemap(const vnode_t *vp)
{
        return (nlm_safemap(vp));
}

/*
 * Called by nfs_map() for the MANDLOCK case.
 * Return non-zero if the file has any locks with a
 * blocked request (sleep).
 *
 * NOTE: called from NFSv3/NFSv2 map() functions in
 * order to determine whether it's safe to add new
 * mapping.
 */
int
lm_has_sleep(const vnode_t *vp)
{
        return (nlm_has_sleep(vp));
}

/*
 * ****************************************************************
 * Stuff needed by klmops?
 */