/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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); }