root/usr/src/uts/common/io/sad.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 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */


/*      Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
/*        All Rights Reserved   */


/*
 * STREAMS Administrative Driver
 *
 * Currently only handles autopush and module name verification.
 */

#include <sys/types.h>
#include <sys/param.h>
#include <sys/errno.h>
#include <sys/stream.h>
#include <sys/stropts.h>
#include <sys/strsubr.h>
#include <sys/strsun.h>
#include <sys/conf.h>
#include <sys/sad.h>
#include <sys/cred.h>
#include <sys/debug.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/stat.h>
#include <sys/cmn_err.h>
#include <sys/systm.h>
#include <sys/modctl.h>
#include <sys/sysmacros.h>
#include <sys/zone.h>
#include <sys/policy.h>

static int sadopen(queue_t *, dev_t *, int, int, cred_t *);
static int sadclose(queue_t *, int, cred_t *);
static int sadwput(queue_t *qp, mblk_t *mp);

static int sad_info(dev_info_t *, ddi_info_cmd_t, void *, void **);
static int sad_attach(dev_info_t *, ddi_attach_cmd_t);

static void apush_ioctl(), apush_iocdata();
static void vml_ioctl(), vml_iocdata();
static int valid_major(major_t);

static dev_info_t *sad_dip;             /* private copy of devinfo pointer */

static struct module_info sad_minfo = {
        0x7361, "sad", 0, INFPSZ, 0, 0
};

static struct qinit sad_rinit = {
        NULL, NULL, sadopen, sadclose, NULL, &sad_minfo, NULL
};

static struct qinit sad_winit = {
        sadwput, NULL, NULL, NULL, NULL, &sad_minfo, NULL
};

struct streamtab sadinfo = {
        &sad_rinit, &sad_winit, NULL, NULL
};

DDI_DEFINE_STREAM_OPS(sad_ops, nulldev, nulldev, sad_attach,
    nodev, nodev, sad_info,
    D_MP | D_MTPERQ | D_MTOUTPERIM | D_MTOCEXCL, &sadinfo,
    ddi_quiesce_not_supported);

/*
 * Module linkage information for the kernel.
 */

static struct modldrv modldrv = {
        &mod_driverops, /* Type of module.  This one is a pseudo driver */
        "STREAMS Administrative Driver 'sad'",
        &sad_ops,       /* driver ops */
};

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

int
_init(void)
{
        return (mod_install(&modlinkage));
}

int
_fini(void)
{
        return (mod_remove(&modlinkage));
}

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

static int
sad_attach(dev_info_t *devi, ddi_attach_cmd_t cmd)
{
        int instance = ddi_get_instance(devi);

        if (cmd != DDI_ATTACH)
                return (DDI_FAILURE);

        ASSERT(instance == 0);
        if (instance != 0)
                return (DDI_FAILURE);

        if (ddi_create_minor_node(devi, "user", S_IFCHR,
            0, DDI_PSEUDO, 0) == DDI_FAILURE) {
                return (DDI_FAILURE);
        }
        if (ddi_create_minor_node(devi, "admin", S_IFCHR,
            1, DDI_PSEUDO, 0) == DDI_FAILURE) {
                ddi_remove_minor_node(devi, NULL);
                return (DDI_FAILURE);
        }
        sad_dip = devi;
        return (DDI_SUCCESS);
}

/* ARGSUSED */
static int
sad_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
{
        int error;

        switch (infocmd) {
        case DDI_INFO_DEVT2DEVINFO:
                if (sad_dip == NULL) {
                        error = DDI_FAILURE;
                } else {
                        *result = sad_dip;
                        error = DDI_SUCCESS;
                }
                break;
        case DDI_INFO_DEVT2INSTANCE:
                *result = (void *)0;
                error = DDI_SUCCESS;
                break;
        default:
                error = DDI_FAILURE;
        }
        return (error);
}


/*
 * sadopen() -
 * Allocate a sad device.  Only one
 * open at a time allowed per device.
 */
/* ARGSUSED */
static int
sadopen(
        queue_t *qp,    /* pointer to read queue */
        dev_t *devp,    /* major/minor device of stream */
        int flag,       /* file open flags */
        int sflag,      /* stream open flags */
        cred_t *credp)  /* user credentials */
{
        int i;
        netstack_t *ns;
        str_stack_t *ss;

        if (sflag)              /* no longer called from clone driver */
                return (EINVAL);

        /* Only privileged process can access ADMINDEV */
        if (getminor(*devp) == ADMMIN) {
                int err;

                err = secpolicy_sadopen(credp);

                if (err != 0)
                        return (err);
        }

        ns = netstack_find_by_cred(credp);
        ASSERT(ns != NULL);
        ss = ns->netstack_str;
        ASSERT(ss != NULL);

        /*
         * Both USRMIN and ADMMIN are clone interfaces.
         */
        for (i = 0; i < ss->ss_sadcnt; i++)
                if (ss->ss_saddev[i].sa_qp == NULL)
                        break;
        if (i >= ss->ss_sadcnt) {               /* no such device */
                netstack_rele(ss->ss_netstack);
                return (ENXIO);
        }
        switch (getminor(*devp)) {
        case USRMIN:                    /* mere mortal */
                ss->ss_saddev[i].sa_flags = 0;
                break;

        case ADMMIN:                    /* privileged user */
                ss->ss_saddev[i].sa_flags = SADPRIV;
                break;

        default:
                netstack_rele(ss->ss_netstack);
                return (EINVAL);
        }

        ss->ss_saddev[i].sa_qp = qp;
        ss->ss_saddev[i].sa_ss = ss;
        qp->q_ptr = (caddr_t)&ss->ss_saddev[i];
        WR(qp)->q_ptr = (caddr_t)&ss->ss_saddev[i];

        /*
         * NOTE: should the ADMMIN or USRMIN minors change
         * then so should the offset of 2 below
         * Both USRMIN and ADMMIN are clone interfaces and
         * therefore their minor numbers (0 and 1) are reserved.
         */
        *devp = makedevice(getemajor(*devp), i + 2);
        qprocson(qp);
        return (0);
}

/*
 * sadclose() -
 * Clean up the data structures.
 */
/* ARGSUSED */
static int
sadclose(
        queue_t *qp,    /* pointer to read queue */
        int flag,       /* file open flags */
        cred_t *credp)  /* user credentials */
{
        struct saddev *sadp;

        qprocsoff(qp);
        sadp = (struct saddev *)qp->q_ptr;
        sadp->sa_qp = NULL;
        sadp->sa_addr = NULL;
        netstack_rele(sadp->sa_ss->ss_netstack);
        sadp->sa_ss = NULL;
        qp->q_ptr = NULL;
        WR(qp)->q_ptr = NULL;
        return (0);
}

/*
 * sadwput() -
 * Write side put procedure.
 */
static int
sadwput(
        queue_t *qp,    /* pointer to write queue */
        mblk_t *mp)     /* message pointer */
{
        struct iocblk *iocp;

        switch (mp->b_datap->db_type) {
        case M_FLUSH:
                if (*mp->b_rptr & FLUSHR) {
                        *mp->b_rptr &= ~FLUSHW;
                        qreply(qp, mp);
                } else
                        freemsg(mp);
                break;

        case M_IOCTL:
                iocp = (struct iocblk *)mp->b_rptr;
                switch (SAD_CMD(iocp->ioc_cmd)) {
                case SAD_CMD(SAD_SAP):
                case SAD_CMD(SAD_GAP):
                        apush_ioctl(qp, mp);
                        break;

                case SAD_VML:
                        vml_ioctl(qp, mp);
                        break;

                default:
                        miocnak(qp, mp, 0, EINVAL);
                        break;
                }
                break;

        case M_IOCDATA:
                iocp = (struct iocblk *)mp->b_rptr;
                switch (SAD_CMD(iocp->ioc_cmd)) {
                case SAD_CMD(SAD_SAP):
                case SAD_CMD(SAD_GAP):
                        apush_iocdata(qp, mp);
                        break;

                case SAD_VML:
                        vml_iocdata(qp, mp);
                        break;

                default:
                        cmn_err(CE_WARN,
                            "sadwput: invalid ioc_cmd in case M_IOCDATA: %d",
                            iocp->ioc_cmd);
                        freemsg(mp);
                        break;
                }
                break;

        default:
                freemsg(mp);
                break;
        } /* switch (db_type) */
        return (0);
}

/*
 * apush_ioctl() -
 * Handle the M_IOCTL messages associated with
 * the autopush feature.
 */
static void
apush_ioctl(
        queue_t *qp,    /* pointer to write queue */
        mblk_t *mp)     /* message pointer */
{
        struct iocblk   *iocp;
        struct saddev   *sadp;
        uint_t          size;

        iocp = (struct iocblk *)mp->b_rptr;
        if (iocp->ioc_count != TRANSPARENT) {
                miocnak(qp, mp, 0, EINVAL);
                return;
        }
        if (SAD_VER(iocp->ioc_cmd) > AP_VERSION) {
                miocnak(qp, mp, 0, EINVAL);
                return;
        }

        sadp = (struct saddev *)qp->q_ptr;
        switch (SAD_CMD(iocp->ioc_cmd)) {
        case SAD_CMD(SAD_SAP):
                if (!(sadp->sa_flags & SADPRIV)) {
                        miocnak(qp, mp, 0, EPERM);
                        break;
                }
                /* FALLTHRU */

        case SAD_CMD(SAD_GAP):
                sadp->sa_addr = (caddr_t)*(uintptr_t *)mp->b_cont->b_rptr;
                if (SAD_VER(iocp->ioc_cmd) == 1)
                        size = STRAPUSH_V1_LEN;
                else
                        size = STRAPUSH_V0_LEN;
                mcopyin(mp, (void *)GETSTRUCT, size, NULL);
                qreply(qp, mp);
                break;

        default:
                ASSERT(0);
                miocnak(qp, mp, 0, EINVAL);
                break;
        } /* switch (ioc_cmd) */
}

/*
 * apush_iocdata() -
 * Handle the M_IOCDATA messages associated with
 * the autopush feature.
 */
static void
apush_iocdata(
        queue_t *qp,    /* pointer to write queue */
        mblk_t *mp)     /* message pointer */
{
        int i, ret;
        struct copyresp *csp;
        struct strapush *sap = NULL;
        struct autopush *ap, *ap_tmp;
        struct saddev *sadp;
        uint_t size;
        dev_t dev;
        str_stack_t *ss;

        sadp = (struct saddev *)qp->q_ptr;
        ss = sadp->sa_ss;

        csp = (struct copyresp *)mp->b_rptr;
        if (csp->cp_rval) {     /* if there was an error */
                freemsg(mp);
                return;
        }
        if (mp->b_cont) {
                /*
                 * sap needed only if mp->b_cont is set.  figure out the
                 * size of the expected sap structure and make sure
                 * enough data was supplied.
                 */
                if (SAD_VER(csp->cp_cmd) == 1)
                        size = STRAPUSH_V1_LEN;
                else
                        size = STRAPUSH_V0_LEN;
                if (MBLKL(mp->b_cont) < size) {
                        miocnak(qp, mp, 0, EINVAL);
                        return;
                }
                sap = (struct strapush *)mp->b_cont->b_rptr;
                dev = makedevice(sap->sap_major, sap->sap_minor);
        }
        switch (SAD_CMD(csp->cp_cmd)) {
        case SAD_CMD(SAD_SAP):

                /* currently we only support one SAD_SAP command */
                if (((long)csp->cp_private) != GETSTRUCT) {
                        cmn_err(CE_WARN,
                            "apush_iocdata: cp_private bad in SAD_SAP: %p",
                            (void *)csp->cp_private);
                        miocnak(qp, mp, 0, EINVAL);
                        return;
                }

                switch (sap->sap_cmd) {
                default:
                        miocnak(qp, mp, 0, EINVAL);
                        return;
                case SAP_ONE:
                case SAP_RANGE:
                case SAP_ALL:
                        /* allocate and initialize a new config */
                        ap = sad_ap_alloc();
                        ap->ap_common = sap->sap_common;
                        if (SAD_VER(csp->cp_cmd) > 0)
                                ap->ap_anchor = sap->sap_anchor;
                        for (i = 0; i < MIN(sap->sap_npush, MAXAPUSH); i++)
                                (void) strncpy(ap->ap_list[i],
                                    sap->sap_list[i], FMNAMESZ);

                        /* sanity check the request */
                        if (((ret = sad_ap_verify(ap)) != 0) ||
                            ((ret = valid_major(ap->ap_major)) != 0)) {
                                sad_ap_rele(ap, ss);
                                miocnak(qp, mp, 0, ret);
                                return;
                        }

                        /* check for overlapping configs */
                        mutex_enter(&ss->ss_sad_lock);
                        ap_tmp = sad_ap_find(&ap->ap_common, ss);
                        if (ap_tmp != NULL) {
                                /* already configured */
                                mutex_exit(&ss->ss_sad_lock);
                                sad_ap_rele(ap_tmp, ss);
                                sad_ap_rele(ap, ss);
                                miocnak(qp, mp, 0, EEXIST);
                                return;
                        }

                        /* add the new config to our hash */
                        sad_ap_insert(ap, ss);
                        mutex_exit(&ss->ss_sad_lock);
                        miocack(qp, mp, 0, 0);
                        return;

                case SAP_CLEAR:
                        /* sanity check the request */
                        if (ret = valid_major(sap->sap_major)) {
                                miocnak(qp, mp, 0, ret);
                                return;
                        }

                        /* search for a matching config */
                        if ((ap = sad_ap_find_by_dev(dev, ss)) == NULL) {
                                /* no config found */
                                miocnak(qp, mp, 0, ENODEV);
                                return;
                        }

                        /*
                         * If we matched a SAP_RANGE config
                         * the minor passed in must match the
                         * beginning of the range exactly.
                         */
                        if ((ap->ap_type == SAP_RANGE) &&
                            (ap->ap_minor != sap->sap_minor)) {
                                sad_ap_rele(ap, ss);
                                miocnak(qp, mp, 0, ERANGE);
                                return;
                        }

                        /*
                         * If we matched a SAP_ALL config
                         * the minor passed in must be 0.
                         */
                        if ((ap->ap_type == SAP_ALL) &&
                            (sap->sap_minor != 0)) {
                                sad_ap_rele(ap, ss);
                                miocnak(qp, mp, 0, EINVAL);
                                return;
                        }

                        /*
                         * make sure someone else hasn't already
                         * removed this config from the hash.
                         */
                        mutex_enter(&ss->ss_sad_lock);
                        ap_tmp = sad_ap_find(&ap->ap_common, ss);
                        if (ap_tmp != ap) {
                                mutex_exit(&ss->ss_sad_lock);
                                sad_ap_rele(ap_tmp, ss);
                                sad_ap_rele(ap, ss);
                                miocnak(qp, mp, 0, ENODEV);
                                return;
                        }

                        /* remove the config from the hash and return */
                        sad_ap_remove(ap, ss);
                        mutex_exit(&ss->ss_sad_lock);

                        /*
                         * Release thrice, once for sad_ap_find_by_dev(),
                         * once for sad_ap_find(), and once to free.
                         */
                        sad_ap_rele(ap, ss);
                        sad_ap_rele(ap, ss);
                        sad_ap_rele(ap, ss);
                        miocack(qp, mp, 0, 0);
                        return;
                } /* switch (sap_cmd) */
                /*NOTREACHED*/

        case SAD_CMD(SAD_GAP):
                switch ((long)csp->cp_private) {

                case GETSTRUCT:
                        /* sanity check the request */
                        if (ret = valid_major(sap->sap_major)) {
                                miocnak(qp, mp, 0, ret);
                                return;
                        }

                        /* search for a matching config */
                        if ((ap = sad_ap_find_by_dev(dev, ss)) == NULL) {
                                /* no config found */
                                miocnak(qp, mp, 0, ENODEV);
                                return;
                        }

                        /* copy out the contents of the config */
                        sap->sap_common = ap->ap_common;
                        if (SAD_VER(csp->cp_cmd) > 0)
                                sap->sap_anchor = ap->ap_anchor;
                        for (i = 0; i < ap->ap_npush; i++)
                                (void) strcpy(sap->sap_list[i], ap->ap_list[i]);
                        for (; i < MAXAPUSH; i++)
                                bzero(sap->sap_list[i], FMNAMESZ + 1);

                        /* release our hold on the config */
                        sad_ap_rele(ap, ss);

                        /* copyout the results */
                        if (SAD_VER(csp->cp_cmd) == 1)
                                size = STRAPUSH_V1_LEN;
                        else
                                size = STRAPUSH_V0_LEN;

                        mcopyout(mp, (void *)GETRESULT, size, sadp->sa_addr,
                            NULL);
                        qreply(qp, mp);
                        return;
                case GETRESULT:
                        miocack(qp, mp, 0, 0);
                        return;

                default:
                        cmn_err(CE_WARN,
                            "apush_iocdata: cp_private bad case SAD_GAP: %p",
                            (void *)csp->cp_private);
                        freemsg(mp);
                        return;
                } /* switch (cp_private) */
                /*NOTREACHED*/
        default:        /* can't happen */
                ASSERT(0);
                freemsg(mp);
                return;
        } /* switch (cp_cmd) */
}

/*
 * vml_ioctl() -
 * Handle the M_IOCTL message associated with a request
 * to validate a module list.
 */
static void
vml_ioctl(
        queue_t *qp,    /* pointer to write queue */
        mblk_t *mp)     /* message pointer */
{
        struct iocblk *iocp;

        iocp = (struct iocblk *)mp->b_rptr;
        if (iocp->ioc_count != TRANSPARENT) {
                miocnak(qp, mp, 0, EINVAL);
                return;
        }
        ASSERT(SAD_CMD(iocp->ioc_cmd) == SAD_VML);
        mcopyin(mp, (void *)GETSTRUCT,
            SIZEOF_STRUCT(str_list, iocp->ioc_flag), NULL);
        qreply(qp, mp);
}

/*
 * vml_iocdata() -
 * Handle the M_IOCDATA messages associated with
 * a request to validate a module list.
 */
static void
vml_iocdata(
        queue_t *qp,    /* pointer to write queue */
        mblk_t *mp)     /* message pointer */
{
        long i;
        int     nmods;
        struct copyresp *csp;
        struct str_mlist *lp;
        STRUCT_HANDLE(str_list, slp);
        struct saddev *sadp;

        csp = (struct copyresp *)mp->b_rptr;
        if (csp->cp_rval) {     /* if there was an error */
                freemsg(mp);
                return;
        }

        ASSERT(SAD_CMD(csp->cp_cmd) == SAD_VML);
        sadp = (struct saddev *)qp->q_ptr;
        switch ((long)csp->cp_private) {
        case GETSTRUCT:
                STRUCT_SET_HANDLE(slp, csp->cp_flag,
                    (struct str_list *)mp->b_cont->b_rptr);
                nmods = STRUCT_FGET(slp, sl_nmods);
                if (nmods <= 0) {
                        miocnak(qp, mp, 0, EINVAL);
                        break;
                }
                sadp->sa_addr = (caddr_t)(uintptr_t)nmods;

                mcopyin(mp, (void *)GETLIST, nmods * sizeof (struct str_mlist),
                    STRUCT_FGETP(slp, sl_modlist));
                qreply(qp, mp);
                break;

        case GETLIST:
                lp = (struct str_mlist *)mp->b_cont->b_rptr;
                for (i = 0; i < (long)sadp->sa_addr; i++, lp++) {
                        lp->l_name[FMNAMESZ] = '\0';
                        if (fmodsw_find(lp->l_name, FMODSW_LOAD) == NULL) {
                                miocack(qp, mp, 0, 1);
                                return;
                        }
                }
                miocack(qp, mp, 0, 0);
                break;

        default:
                cmn_err(CE_WARN, "vml_iocdata: invalid cp_private value: %p",
                    (void *)csp->cp_private);
                freemsg(mp);
                break;
        } /* switch (cp_private) */
}

/*
 * Validate a major number and also verify if
 * it is a STREAMS device.
 * Return values: 0 if a valid STREAMS dev
 *                error code otherwise
 */
static int
valid_major(major_t major)
{
        int ret = 0;

        if (etoimajor(major) == -1)
                return (EINVAL);

        /*
         * attempt to load the driver 'major' and verify that
         * it is a STREAMS driver.
         */
        if (ddi_hold_driver(major) == NULL)
                return (EINVAL);

        if (!STREAMSTAB(major))
                ret = ENOSTR;

        ddi_rele_driver(major);

        return (ret);
}