/* * 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 2006 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 aquired 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); } /* * Remove vp from the redirection list rooted at iwscn_list, should it * be there. If iwscn_list is non-NULL, deallocate the entry. If * the entry doesn't exist upon completion, return NULL; otherwise * return a pointer to it. */ static iwscn_list_t * srrm(vnode_t *vp, boolean_t free_entry) { 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) return (NULL); /* Found it, remove this entry from the redirection list */ *lpp = lp->wl_next; if (free_entry == B_FALSE) return (lp); /* * This entry is no longer on the global redirection list so now * we have to wait for all operations currently in progress to * finish before we can actually delete this entry. We don't * have to worry about a new operation on this vnode starting up * because we've removed it from the redirection list. */ while (lp->wl_ref_cnt != 0) { /* * Interrupt any operations that may be outstanding * against this vnode and wait for them to complete. */ strsetrerror(lp->wl_vp, EINTR, 0, NULL); strsetwerror(lp->wl_vp, EINTR, 0, NULL); cv_wait(&iwscn_list_cv, &iwscn_list_lock); } if (lp->wl_is_console == B_TRUE) { /* * Special case. If this is the underlying console device * then we opened it so we need to close it. */ (void) VOP_CLOSE(lp->wl_vp, 0, 1, (offset_t)0, kcred); } else { /* Release our hold on this vnode */ VN_RELE(lp->wl_vp); } /* Free the entry */ kmem_free(lp, sizeof (*lp)); return (NULL); } /* * 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, B_FALSE)) == NULL) { lp = kmem_zalloc(sizeof (*lp), KM_SLEEP); lp->wl_vp = vp; if (is_console == B_TRUE) { lp->wl_is_console = B_TRUE; } else { /* * Hold the vnode. Note that this hold will not * prevent the device stream associated with the * vnode from being closed. (We protect against * that by pushing our streams redirection module * onto the stream to intercept close requests.) */ VN_HOLD(lp->wl_vp); lp->wl_is_console = B_FALSE; } } /* * 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 * from of the redirection list. */ lp->wl_next = iwscn_list; iwscn_list = lp; } /* * srpop() - Remove redirection because the target stream is being closed. * Called from wcmclose(). */ void srpop(vnode_t *vp) { mutex_enter(&iwscn_list_lock); (void) srrm(vp, B_TRUE); 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); 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); 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); 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); if (error != 0) return (error); /* Re-aquire 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); } } /* * 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) { ASSERT(getminor(dev) == 0); if (state != OTYP_CHR) return (ENXIO); mutex_enter(&iwscn_list_lock); /* Remove all outstanding redirections */ while (iwscn_list != NULL) (void) srrm(iwscn_list->wl_vp, B_TRUE); iwscn_list = NULL; 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)); }