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