#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;
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);
static struct modldrv modldrv = {
&mod_driverops,
"STREAMS Administrative Driver 'sad'",
&sad_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);
}
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);
}
static int
sadopen(
queue_t *qp,
dev_t *devp,
int flag,
int sflag,
cred_t *credp)
{
int i;
netstack_t *ns;
str_stack_t *ss;
if (sflag)
return (EINVAL);
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);
for (i = 0; i < ss->ss_sadcnt; i++)
if (ss->ss_saddev[i].sa_qp == NULL)
break;
if (i >= ss->ss_sadcnt) {
netstack_rele(ss->ss_netstack);
return (ENXIO);
}
switch (getminor(*devp)) {
case USRMIN:
ss->ss_saddev[i].sa_flags = 0;
break;
case ADMMIN:
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];
*devp = makedevice(getemajor(*devp), i + 2);
qprocson(qp);
return (0);
}
static int
sadclose(
queue_t *qp,
int flag,
cred_t *credp)
{
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);
}
static int
sadwput(
queue_t *qp,
mblk_t *mp)
{
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;
}
return (0);
}
static void
apush_ioctl(
queue_t *qp,
mblk_t *mp)
{
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;
}
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;
}
}
static void
apush_iocdata(
queue_t *qp,
mblk_t *mp)
{
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) {
freemsg(mp);
return;
}
if (mp->b_cont) {
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):
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:
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);
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;
}
mutex_enter(&ss->ss_sad_lock);
ap_tmp = sad_ap_find(&ap->ap_common, ss);
if (ap_tmp != NULL) {
mutex_exit(&ss->ss_sad_lock);
sad_ap_rele(ap_tmp, ss);
sad_ap_rele(ap, ss);
miocnak(qp, mp, 0, EEXIST);
return;
}
sad_ap_insert(ap, ss);
mutex_exit(&ss->ss_sad_lock);
miocack(qp, mp, 0, 0);
return;
case SAP_CLEAR:
if (ret = valid_major(sap->sap_major)) {
miocnak(qp, mp, 0, ret);
return;
}
if ((ap = sad_ap_find_by_dev(dev, ss)) == NULL) {
miocnak(qp, mp, 0, ENODEV);
return;
}
if ((ap->ap_type == SAP_RANGE) &&
(ap->ap_minor != sap->sap_minor)) {
sad_ap_rele(ap, ss);
miocnak(qp, mp, 0, ERANGE);
return;
}
if ((ap->ap_type == SAP_ALL) &&
(sap->sap_minor != 0)) {
sad_ap_rele(ap, ss);
miocnak(qp, mp, 0, EINVAL);
return;
}
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;
}
sad_ap_remove(ap, ss);
mutex_exit(&ss->ss_sad_lock);
sad_ap_rele(ap, ss);
sad_ap_rele(ap, ss);
sad_ap_rele(ap, ss);
miocack(qp, mp, 0, 0);
return;
}
case SAD_CMD(SAD_GAP):
switch ((long)csp->cp_private) {
case GETSTRUCT:
if (ret = valid_major(sap->sap_major)) {
miocnak(qp, mp, 0, ret);
return;
}
if ((ap = sad_ap_find_by_dev(dev, ss)) == NULL) {
miocnak(qp, mp, 0, ENODEV);
return;
}
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);
sad_ap_rele(ap, ss);
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;
}
default:
ASSERT(0);
freemsg(mp);
return;
}
}
static void
vml_ioctl(
queue_t *qp,
mblk_t *mp)
{
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);
}
static void
vml_iocdata(
queue_t *qp,
mblk_t *mp)
{
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) {
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;
}
}
static int
valid_major(major_t major)
{
int ret = 0;
if (etoimajor(major) == -1)
return (EINVAL);
if (ddi_hold_driver(major) == NULL)
return (EINVAL);
if (!STREAMSTAB(major))
ret = ENOSTR;
ddi_rele_driver(major);
return (ret);
}