iwscons.c revision da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0
/*
* 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 <sys/types.h>
#include <sys/sysmacros.h>
#include <sys/open.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/signal.h>
#include <sys/cred.h>
#include <sys/user.h>
#include <sys/proc.h>
#include <sys/vnode.h>
#include <sys/uio.h>
#include <sys/file.h>
#include <sys/kmem.h>
#include <sys/stat.h>
#include <sys/stream.h>
#include <sys/stropts.h>
#include <sys/strsubr.h>
#include <sys/poll.h>
#include <sys/debug.h>
#include <sys/strredir.h>
#include <sys/conf.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/errno.h>
#include <sys/modctl.h>
#include <sys/sunldi.h>
#include <sys/consdev.h>
#include <sys/fs/snode.h>
/*
* 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));
}