/*
* 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, NULL) == DDI_FAILURE) {
return (DDI_FAILURE);
}
if (ddi_create_minor_node(devi, "admin", S_IFCHR,
1, DDI_PSEUDO, NULL) == 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);
}