/* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" /* * workstation console redirecting driver * * Redirects all I/O through a given device instance to the device designated * as the current target, as given by the vnode associated with the first * entry in the list of redirections for the given device instance. The * implementation assumes that this vnode denotes a STREAMS device; this is * perhaps a bug. * * Supports the SRIOCSREDIR ioctl for designating a new redirection target. * The new target is added to the front of a list of potentially active * designees. Should the device at the front of this list be closed, the new * front entry assumes active duty. (Stated differently, redirection targets * stack, except that it's possible for entries in the interior of the stack * to go away.) * * Supports the SRIOCISREDIR ioctl for inquiring whether the descriptor given * as argument is the current front of the redirection list associated with * the descriptor on which the ioctl was issued. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Global data */ static dev_info_t *iwscn_dip; /* * We record the list of redirections as a linked list of iwscn_list_t * structures. We need to keep track of the target's vp, so that * we can vector reads, writes, etc. off to the current designee. */ typedef struct _iwscn_list { struct _iwscn_list *wl_next; /* next entry */ vnode_t *wl_vp; /* target's vnode */ int wl_ref_cnt; /* operation in progress */ boolean_t wl_is_console; /* is the real console */ } iwscn_list_t; static iwscn_list_t *iwscn_list; /* * iwscn_list_lock serializes modifications to the global iwscn_list list. * * iwscn_list_cv is used when freeing an entry from iwscn_list to allow * the caller to wait till the wl_ref_cnt field is zero. * * iwscn_redirect_lock is used to serialize redirection requests. This * is required to ensure that all active redirection streams have * the redirection streams module (redirmod) pushed on them. * * If both iwscn_redirect_lock and iwscn_list_lock must be held then * iwscn_redirect_lock must be acquired first. */ static kcondvar_t iwscn_list_cv; static kmutex_t iwscn_list_lock; static kmutex_t iwscn_redirect_lock; /* * Routines for managing iwscn_list */ static vnode_t * str_vp(vnode_t *vp) { /* * Here we switch to using the vnode that is linked * to from the stream queue. (In the case of device * streams this will correspond to the common vnode * for the device.) The reason we use this vnode * is that when wcmclose() calls srpop(), this is the * only vnode that it has access to. */ ASSERT(vp->v_stream != NULL); return (vp->v_stream->sd_vnode); } /* * Interrupt any operations that may be outstanding against this vnode. * optionally, wait for them to complete. */ static void srinterrupt(iwscn_list_t *lp, boolean_t wait) { ASSERT(MUTEX_HELD(&iwscn_list_lock)); while (lp->wl_ref_cnt != 0) { strsetrerror(lp->wl_vp, EINTR, 0, NULL); strsetwerror(lp->wl_vp, EINTR, 0, NULL); if (!wait) break; cv_wait(&iwscn_list_cv, &iwscn_list_lock); } } /* * Remove vp from the redirection list rooted at iwscn_list, should it * be there. Return a pointer to the removed entry. */ static iwscn_list_t * srrm(vnode_t *vp) { iwscn_list_t *lp, **lpp; ASSERT(MUTEX_HELD(&iwscn_list_lock)); /* Get the stream vnode */ vp = str_vp(vp); ASSERT(vp); /* Look for this vnode on the redirection list */ for (lpp = &iwscn_list; (lp = *lpp) != NULL; lpp = &lp->wl_next) { if (lp->wl_vp == vp) break; } if (lp != NULL) /* Found it, remove this entry from the redirection list */ *lpp = lp->wl_next; return (lp); } /* * Push vp onto the redirection list. * If it's already there move it to the front position. */ static void srpush(vnode_t *vp, boolean_t is_console) { iwscn_list_t *lp; ASSERT(MUTEX_HELD(&iwscn_list_lock)); /* Get the stream vnode */ vp = str_vp(vp); ASSERT(vp); /* Check if it's already on the redirection list */ if ((lp = srrm(vp)) == NULL) { lp = kmem_zalloc(sizeof (*lp), KM_SLEEP); lp->wl_vp = vp; lp->wl_is_console = is_console; } /* * Note that if this vnode was already somewhere on the redirection * list then we removed it above and are now bumping it up to the * front of the redirection list. */ lp->wl_next = iwscn_list; iwscn_list = lp; } /* * This vnode is no longer a valid redirection target. Terminate any current * operations. If closing, wait for them to complete, then free the entry. * If called because a hangup has occurred, just deprecate the entry to ensure * it won't become the target again. */ void srpop(vnode_t *vp, boolean_t close) { iwscn_list_t *tlp; /* This target's entry */ iwscn_list_t *lp, **lpp; mutex_enter(&iwscn_list_lock); /* * Ensure no further operations are directed at the target * by removing it from the redirection list. */ if ((tlp = srrm(vp)) == NULL) { /* vnode wasn't in the list */ mutex_exit(&iwscn_list_lock); return; } /* * Terminate any current operations. * If we're closing, wait until they complete. */ srinterrupt(tlp, close); if (close) { /* We're finished with this target */ kmem_free(tlp, sizeof (*tlp)); } else { /* * Deprecate the entry. There's no need for a flag to indicate * this state, it just needs to be moved to the back of the list * behind the underlying console device. Since the underlying * device anchors the list and is never removed, this entry can * never return to the front again to become the target. */ for (lpp = &iwscn_list; (lp = *lpp) != NULL; ) lpp = &lp->wl_next; tlp->wl_next = NULL; *lpp = tlp; } mutex_exit(&iwscn_list_lock); } /* Get a hold on the current target */ static iwscn_list_t * srhold() { iwscn_list_t *lp; mutex_enter(&iwscn_list_lock); ASSERT(iwscn_list != NULL); lp = iwscn_list; ASSERT(lp->wl_ref_cnt >= 0); lp->wl_ref_cnt++; mutex_exit(&iwscn_list_lock); return (lp); } /* Release a hold on an entry from the redirection list */ static void srrele(iwscn_list_t *lp) { ASSERT(lp != NULL); mutex_enter(&iwscn_list_lock); ASSERT(lp->wl_ref_cnt > 0); lp->wl_ref_cnt--; cv_broadcast(&iwscn_list_cv); mutex_exit(&iwscn_list_lock); } static int iwscnread(dev_t dev, uio_t *uio, cred_t *cred) { iwscn_list_t *lp; int error; ASSERT(getminor(dev) == 0); lp = srhold(); error = strread(lp->wl_vp, uio, cred); srrele(lp); return (error); } static int iwscnwrite(dev_t dev, uio_t *uio, cred_t *cred) { iwscn_list_t *lp; int error; ASSERT(getminor(dev) == 0); lp = srhold(); error = strwrite(lp->wl_vp, uio, cred); srrele(lp); return (error); } static int iwscnpoll(dev_t dev, short events, int anyyet, short *reventsp, struct pollhead **phpp) { iwscn_list_t *lp; int error; ASSERT(getminor(dev) == 0); lp = srhold(); error = VOP_POLL(lp->wl_vp, events, anyyet, reventsp, phpp, NULL); srrele(lp); return (error); } static int iwscnioctl(dev_t dev, int cmd, intptr_t arg, int flag, cred_t *cred, int *rvalp) { iwscn_list_t *lp; file_t *f; char modname[FMNAMESZ + 1] = " "; int error = 0; ASSERT(getminor(dev) == 0); switch (cmd) { case SRIOCSREDIR: /* Serialize all pushes of the redirection module */ mutex_enter(&iwscn_redirect_lock); /* * Find the vnode corresponding to the file descriptor * argument and verify that it names a stream. */ if ((f = getf((int)arg)) == NULL) { mutex_exit(&iwscn_redirect_lock); return (EBADF); } if (f->f_vnode->v_stream == NULL) { releasef((int)arg); mutex_exit(&iwscn_redirect_lock); return (ENOSTR); } /* * If the user is trying to redirect console output * back to the underlying console via SRIOCSREDIR * then they are evil and we'll stop them here. */ if (str_vp(f->f_vnode) == str_vp(rwsconsvp)) { releasef((int)arg); mutex_exit(&iwscn_redirect_lock); return (EINVAL); } /* * Check if this stream already has the redirection * module pushed onto it. I_LOOK returns an error * if there are no modules pushed onto the stream. */ (void) strioctl(f->f_vnode, I_LOOK, (intptr_t)modname, FKIOCTL, K_TO_K, cred, rvalp); if (strcmp(modname, STRREDIR_MOD) != 0) { /* * Push a new instance of the redirecting module onto * the stream, so that its close routine can notify * us when the overall stream is closed. (In turn, * we'll then remove it from the redirection list.) */ error = strioctl(f->f_vnode, I_PUSH, (intptr_t)STRREDIR_MOD, FKIOCTL, K_TO_K, cred, rvalp); if (error != 0) { releasef((int)arg); mutex_exit(&iwscn_redirect_lock); return (error); } } /* Push it onto the redirection stack */ mutex_enter(&iwscn_list_lock); srpush(f->f_vnode, B_FALSE); mutex_exit(&iwscn_list_lock); releasef((int)arg); mutex_exit(&iwscn_redirect_lock); return (0); case SRIOCISREDIR: /* * Find the vnode corresponding to the file descriptor * argument and verify that it names a stream. */ if ((f = getf((int)arg)) == NULL) { return (EBADF); } if (f->f_vnode->v_stream == NULL) { releasef((int)arg); return (ENOSTR); } lp = srhold(); *rvalp = (str_vp(f->f_vnode) == lp->wl_vp); srrele(lp); releasef((int)arg); return (0); case I_POP: /* * We need to serialize I_POP operations with * SRIOCSREDIR operations so we don't accidently * remove the redirection module from a stream. */ mutex_enter(&iwscn_redirect_lock); lp = srhold(); /* * Here we need to protect against process that might * try to pop off the redirection module from the * redirected stream. Popping other modules is allowed. * * It's ok to hold iwscn_list_lock while doing the * I_LOOK since it's such a simple operation. */ (void) strioctl(lp->wl_vp, I_LOOK, (intptr_t)modname, FKIOCTL, K_TO_K, cred, rvalp); if (strcmp(STRREDIR_MOD, modname) == 0) { srrele(lp); mutex_exit(&iwscn_redirect_lock); return (EINVAL); } /* Process the ioctl normally */ error = VOP_IOCTL(lp->wl_vp, cmd, arg, flag, cred, rvalp, NULL); srrele(lp); mutex_exit(&iwscn_redirect_lock); return (error); } /* Process the ioctl normally */ lp = srhold(); error = VOP_IOCTL(lp->wl_vp, cmd, arg, flag, cred, rvalp, NULL); srrele(lp); return (error); } /* ARGSUSED */ static int iwscnopen(dev_t *devp, int flag, int state, cred_t *cred) { iwscn_list_t *lp; vnode_t *vp = rwsconsvp; if (state != OTYP_CHR) return (ENXIO); if (getminor(*devp) != 0) return (ENXIO); /* * You can't really open us until the console subsystem * has been configured. */ if (rwsconsvp == NULL) return (ENXIO); /* * Check if this is the first open of this device or if * there is currently no redirection going on. (Ie, we're * sending output to underlying console device.) */ mutex_enter(&iwscn_list_lock); if ((iwscn_list == NULL) || (iwscn_list->wl_vp == str_vp(vp))) { int error = 0; /* Don't hold the list lock across an VOP_OPEN */ mutex_exit(&iwscn_list_lock); /* * There is currently no redirection going on. * pass this open request onto the console driver */ error = VOP_OPEN(&vp, flag, cred, NULL); if (error != 0) return (error); /* Re-acquire the list lock */ mutex_enter(&iwscn_list_lock); if (iwscn_list == NULL) { /* Save this vnode on the redirection list */ srpush(vp, B_TRUE); } else { /* * In this case there must already be a copy of * this vnode on the list, so we can free up this one. */ (void) VOP_CLOSE(vp, flag, 1, (offset_t)0, cred, NULL); } } /* * XXX This is an ugly legacy hack that has been around * forever. This code is here because this driver (the * iwscn driver) is a character driver layered over a * streams driver. * * Normally streams recieve notification whenever a process * closes its last reference to that stream so that it can * clean up any signal handling related configuration. (Ie, * when a stream is configured to deliver a signal to a * process upon certain events.) This is a feature supported * by the streams framework. * * But character/block drivers don't recieve this type * of notification. A character/block driver's close routine * is only invoked upon the last close of the device. This * is an artifact of the multiple open/single close driver * model currently supported by solaris. * * So a problem occurs when a character driver layers itself * on top of a streams driver. Since this driver doesn't always * receive a close notification when a process closes its * last reference to it, this driver can't tell the stream * it's layered upon to clean up any signal handling * configuration for that process. * * So here we hack around that by manually cleaning up the * signal handling list upon each open. It doesn't guarantee * that the signaling handling data stored in the stream will * always be up to date, but it'll be more up to date than * it would be if we didn't do this. * * The real way to solve this problem would be to change * the device framework from an multiple open/single close * model to a multiple open/multiple close model. Then * character/block drivers could pass on close requests * to streams layered underneath. */ str_cn_clean(VTOS(rwsconsvp)->s_commonvp); for (lp = iwscn_list; lp != NULL; lp = lp->wl_next) { ASSERT(lp->wl_vp->v_stream != NULL); str_cn_clean(lp->wl_vp); } mutex_exit(&iwscn_list_lock); return (0); } /* ARGSUSED */ static int iwscnclose(dev_t dev, int flag, int state, cred_t *cred) { iwscn_list_t *lp; ASSERT(getminor(dev) == 0); if (state != OTYP_CHR) return (ENXIO); mutex_enter(&iwscn_list_lock); /* * Remove each entry from the redirection list, terminate any * current operations, wait for them to finish, then free the entry. */ while (iwscn_list != NULL) { lp = srrm(iwscn_list->wl_vp); ASSERT(lp != NULL); srinterrupt(lp, B_TRUE); if (lp->wl_is_console == B_TRUE) /* Close the underlying console device. */ (void) VOP_CLOSE(lp->wl_vp, 0, 1, (offset_t)0, kcred, NULL); kmem_free(lp, sizeof (*lp)); } mutex_exit(&iwscn_list_lock); return (0); } /*ARGSUSED*/ static int iwscnattach(dev_info_t *devi, ddi_attach_cmd_t cmd) { /* * This is a pseudo device so there will never be more than * one instance attached at a time */ ASSERT(iwscn_dip == NULL); if (ddi_create_minor_node(devi, "iwscn", S_IFCHR, 0, DDI_PSEUDO, NULL) == DDI_FAILURE) { return (DDI_FAILURE); } iwscn_dip = devi; mutex_init(&iwscn_list_lock, NULL, MUTEX_DRIVER, NULL); mutex_init(&iwscn_redirect_lock, NULL, MUTEX_DRIVER, NULL); cv_init(&iwscn_list_cv, NULL, CV_DRIVER, NULL); return (DDI_SUCCESS); } /* ARGSUSED */ static int iwscninfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result) { int error; switch (infocmd) { case DDI_INFO_DEVT2DEVINFO: if (iwscn_dip == NULL) { error = DDI_FAILURE; } else { *result = (void *)iwscn_dip; error = DDI_SUCCESS; } break; case DDI_INFO_DEVT2INSTANCE: *result = (void *)0; error = DDI_SUCCESS; break; default: error = DDI_FAILURE; } return (error); } struct cb_ops iwscn_cb_ops = { iwscnopen, /* open */ iwscnclose, /* close */ nodev, /* strategy */ nodev, /* print */ nodev, /* dump */ iwscnread, /* read */ iwscnwrite, /* write */ iwscnioctl, /* ioctl */ nodev, /* devmap */ nodev, /* mmap */ nodev, /* segmap */ iwscnpoll, /* poll */ ddi_prop_op, /* cb_prop_op */ NULL, /* streamtab */ D_MP /* Driver compatibility flag */ }; struct dev_ops iwscn_ops = { DEVO_REV, /* devo_rev, */ 0, /* refcnt */ iwscninfo, /* info */ nulldev, /* identify */ nulldev, /* probe */ iwscnattach, /* attach */ nodev, /* detach */ nodev, /* reset */ &iwscn_cb_ops, /* driver operations */ NULL /* bus operations */ }; /* * Module linkage information for the kernel. */ static struct modldrv modldrv = { &mod_driverops, /* Type of module. This one is a pseudo driver */ "Workstation Redirection driver %I%", &iwscn_ops, /* driver ops */ }; static struct modlinkage modlinkage = { MODREV_1, &modldrv, NULL }; int _init(void) { return (mod_install(&modlinkage)); } int _fini(void) { return (EBUSY); } int _info(struct modinfo *modinfop) { return (mod_info(&modlinkage, modinfop)); }