iwscons.c revision 7656:2621e50fdf4a
1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22/*
23 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
24 * Use is subject to license terms.
25 */
26
27
28/*
29 * workstation console redirecting driver
30 *
31 * Redirects all I/O through a given device instance to the device designated
32 * as the current target, as given by the vnode associated with the first
33 * entry in the list of redirections for the given device instance.  The
34 * implementation assumes that this vnode denotes a STREAMS device; this is
35 * perhaps a bug.
36 *
37 * Supports the SRIOCSREDIR ioctl for designating a new redirection target.
38 * The new target is added to the front of a list of potentially active
39 * designees.  Should the device at the front of this list be closed, the new
40 * front entry assumes active duty.  (Stated differently, redirection targets
41 * stack, except that it's possible for entries in the interior of the stack
42 * to go away.)
43 *
44 * Supports the SRIOCISREDIR ioctl for inquiring whether the descriptor given
45 * as argument is the current front of the redirection list associated with
46 * the descriptor on which the ioctl was issued.
47 */
48
49#include <sys/types.h>
50#include <sys/sysmacros.h>
51#include <sys/open.h>
52#include <sys/param.h>
53#include <sys/systm.h>
54#include <sys/signal.h>
55#include <sys/cred.h>
56#include <sys/user.h>
57#include <sys/proc.h>
58#include <sys/vnode.h>
59#include <sys/uio.h>
60#include <sys/file.h>
61#include <sys/kmem.h>
62#include <sys/stat.h>
63#include <sys/stream.h>
64#include <sys/stropts.h>
65#include <sys/strsubr.h>
66#include <sys/poll.h>
67#include <sys/debug.h>
68#include <sys/strredir.h>
69#include <sys/conf.h>
70#include <sys/ddi.h>
71#include <sys/sunddi.h>
72#include <sys/errno.h>
73#include <sys/modctl.h>
74#include <sys/sunldi.h>
75#include <sys/consdev.h>
76#include <sys/fs/snode.h>
77
78/*
79 * Global data
80 */
81static dev_info_t	*iwscn_dip;
82
83/*
84 * We record the list of redirections as a linked list of iwscn_list_t
85 * structures.  We need to keep track of the target's vp, so that
86 * we can vector reads, writes, etc. off to the current designee.
87 */
88typedef struct _iwscn_list {
89	struct _iwscn_list	*wl_next;	/* next entry */
90	vnode_t			*wl_vp;		/* target's vnode */
91	int			wl_ref_cnt;	/* operation in progress */
92	boolean_t		wl_is_console;	/* is the real console */
93} iwscn_list_t;
94static iwscn_list_t	*iwscn_list;
95
96/*
97 * iwscn_list_lock serializes modifications to the global iwscn_list list.
98 *
99 * iwscn_list_cv is used when freeing an entry from iwscn_list to allow
100 * the caller to wait till the wl_ref_cnt field is zero.
101 *
102 * iwscn_redirect_lock is used to serialize redirection requests.  This
103 * is required to ensure that all active redirection streams have
104 * the redirection streams module (redirmod) pushed on them.
105 *
106 * If both iwscn_redirect_lock and iwscn_list_lock must be held then
107 * iwscn_redirect_lock must be acquired first.
108 */
109static kcondvar_t	iwscn_list_cv;
110static kmutex_t		iwscn_list_lock;
111static kmutex_t		iwscn_redirect_lock;
112
113/*
114 * Routines for managing iwscn_list
115 */
116static vnode_t *
117str_vp(vnode_t *vp)
118{
119	/*
120	 * Here we switch to using the vnode that is linked
121	 * to from the stream queue.  (In the case of device
122	 * streams this will correspond to the common vnode
123	 * for the device.)  The reason we use this vnode
124	 * is that when wcmclose() calls srpop(), this is the
125	 * only vnode that it has access to.
126	 */
127	ASSERT(vp->v_stream != NULL);
128	return (vp->v_stream->sd_vnode);
129}
130
131/*
132 * Interrupt any operations that may be outstanding against this vnode.
133 * optionally, wait for them to complete.
134 */
135static void
136srinterrupt(iwscn_list_t *lp, boolean_t wait)
137{
138	ASSERT(MUTEX_HELD(&iwscn_list_lock));
139
140	while (lp->wl_ref_cnt != 0) {
141		strsetrerror(lp->wl_vp, EINTR, 0, NULL);
142		strsetwerror(lp->wl_vp, EINTR, 0, NULL);
143		if (!wait)
144			break;
145		cv_wait(&iwscn_list_cv, &iwscn_list_lock);
146	}
147}
148
149/*
150 * Remove vp from the redirection list rooted at iwscn_list, should it
151 * be there. Return a pointer to the removed entry.
152 */
153static iwscn_list_t *
154srrm(vnode_t *vp)
155{
156	iwscn_list_t	*lp, **lpp;
157
158	ASSERT(MUTEX_HELD(&iwscn_list_lock));
159
160	/* Get the stream vnode */
161	vp = str_vp(vp);
162	ASSERT(vp);
163
164	/* Look for this vnode on the redirection list */
165	for (lpp = &iwscn_list; (lp = *lpp) != NULL; lpp = &lp->wl_next) {
166		if (lp->wl_vp == vp)
167			break;
168	}
169	if (lp != NULL)
170		/* Found it, remove this entry from the redirection list */
171		*lpp = lp->wl_next;
172
173	return (lp);
174}
175
176/*
177 * Push vp onto the redirection list.
178 * If it's already there move it to the front position.
179 */
180static void
181srpush(vnode_t *vp, boolean_t is_console)
182{
183	iwscn_list_t	*lp;
184
185	ASSERT(MUTEX_HELD(&iwscn_list_lock));
186
187	/* Get the stream vnode */
188	vp = str_vp(vp);
189	ASSERT(vp);
190
191	/* Check if it's already on the redirection list */
192	if ((lp = srrm(vp)) == NULL) {
193		lp = kmem_zalloc(sizeof (*lp), KM_SLEEP);
194		lp->wl_vp = vp;
195		lp->wl_is_console = is_console;
196	}
197	/*
198	 * Note that if this vnode was already somewhere on the redirection
199	 * list then we removed it above and are now bumping it up to the
200	 * front of the redirection list.
201	 */
202	lp->wl_next = iwscn_list;
203	iwscn_list = lp;
204}
205
206/*
207 * This vnode is no longer a valid redirection target. Terminate any current
208 * operations. If closing, wait for them to complete, then free the entry.
209 * If called because a hangup has occurred, just deprecate the entry to ensure
210 * it won't become the target again.
211 */
212void
213srpop(vnode_t *vp, boolean_t close)
214{
215	iwscn_list_t	*tlp;		/* This target's entry */
216	iwscn_list_t	*lp, **lpp;
217
218	mutex_enter(&iwscn_list_lock);
219
220	/*
221	 * Ensure no further operations are directed at the target
222	 * by removing it from the redirection list.
223	 */
224	if ((tlp = srrm(vp)) == NULL) {
225		/* vnode wasn't in the list */
226		mutex_exit(&iwscn_list_lock);
227		return;
228	}
229	/*
230	 * Terminate any current operations.
231	 * If we're closing, wait until they complete.
232	 */
233	srinterrupt(tlp, close);
234
235	if (close) {
236		/* We're finished with this target */
237		kmem_free(tlp, sizeof (*tlp));
238	} else {
239		/*
240		 * Deprecate the entry. There's no need for a flag to indicate
241		 * this state, it just needs to be moved to the back of the list
242		 * behind the underlying console device. Since the underlying
243		 * device anchors the list and is never removed, this entry can
244		 * never return to the front again to become the target.
245		 */
246		for (lpp = &iwscn_list; (lp = *lpp) != NULL; )
247			lpp = &lp->wl_next;
248		tlp->wl_next = NULL;
249		*lpp = tlp;
250	}
251	mutex_exit(&iwscn_list_lock);
252}
253
254/* Get a hold on the current target */
255static iwscn_list_t *
256srhold()
257{
258	iwscn_list_t	*lp;
259
260	mutex_enter(&iwscn_list_lock);
261	ASSERT(iwscn_list != NULL);
262	lp = iwscn_list;
263	ASSERT(lp->wl_ref_cnt >= 0);
264	lp->wl_ref_cnt++;
265	mutex_exit(&iwscn_list_lock);
266
267	return (lp);
268}
269
270/* Release a hold on an entry from the redirection list */
271static void
272srrele(iwscn_list_t *lp)
273{
274	ASSERT(lp != NULL);
275	mutex_enter(&iwscn_list_lock);
276	ASSERT(lp->wl_ref_cnt > 0);
277	lp->wl_ref_cnt--;
278	cv_broadcast(&iwscn_list_cv);
279	mutex_exit(&iwscn_list_lock);
280}
281
282static int
283iwscnread(dev_t dev, uio_t *uio, cred_t *cred)
284{
285	iwscn_list_t	*lp;
286	int		error;
287
288	ASSERT(getminor(dev) == 0);
289
290	lp = srhold();
291	error = strread(lp->wl_vp, uio, cred);
292	srrele(lp);
293
294	return (error);
295}
296
297static int
298iwscnwrite(dev_t dev, uio_t *uio, cred_t *cred)
299{
300	iwscn_list_t	*lp;
301	int		error;
302
303	ASSERT(getminor(dev) == 0);
304
305	lp = srhold();
306	error = strwrite(lp->wl_vp, uio, cred);
307	srrele(lp);
308
309	return (error);
310}
311
312static int
313iwscnpoll(dev_t dev, short events, int anyyet, short *reventsp,
314    struct pollhead **phpp)
315{
316	iwscn_list_t	*lp;
317	int		error;
318
319	ASSERT(getminor(dev) == 0);
320
321	lp = srhold();
322	error = VOP_POLL(lp->wl_vp, events, anyyet, reventsp, phpp, NULL);
323	srrele(lp);
324
325	return (error);
326}
327
328static int
329iwscnioctl(dev_t dev, int cmd, intptr_t arg, int flag,
330    cred_t *cred, int *rvalp)
331{
332	iwscn_list_t	*lp;
333	file_t		*f;
334	char		modname[FMNAMESZ + 1] = " ";
335	int		error = 0;
336
337	ASSERT(getminor(dev) == 0);
338
339	switch (cmd) {
340	case SRIOCSREDIR:
341		/* Serialize all pushes of the redirection module */
342		mutex_enter(&iwscn_redirect_lock);
343
344		/*
345		 * Find the vnode corresponding to the file descriptor
346		 * argument and verify that it names a stream.
347		 */
348		if ((f = getf((int)arg)) == NULL) {
349			mutex_exit(&iwscn_redirect_lock);
350			return (EBADF);
351		}
352		if (f->f_vnode->v_stream == NULL) {
353			releasef((int)arg);
354			mutex_exit(&iwscn_redirect_lock);
355			return (ENOSTR);
356		}
357
358		/*
359		 * If the user is trying to redirect console output
360		 * back to the underlying console via SRIOCSREDIR
361		 * then they are evil and we'll stop them here.
362		 */
363		if (str_vp(f->f_vnode) == str_vp(rwsconsvp)) {
364			releasef((int)arg);
365			mutex_exit(&iwscn_redirect_lock);
366			return (EINVAL);
367		}
368
369		/*
370		 * Check if this stream already has the redirection
371		 * module pushed onto it.  I_LOOK returns an error
372		 * if there are no modules pushed onto the stream.
373		 */
374		(void) strioctl(f->f_vnode, I_LOOK, (intptr_t)modname,
375		    FKIOCTL, K_TO_K, cred, rvalp);
376		if (strcmp(modname, STRREDIR_MOD) != 0) {
377
378			/*
379			 * Push a new instance of the redirecting module onto
380			 * the stream, so that its close routine can notify
381			 * us when the overall stream is closed.  (In turn,
382			 * we'll then remove it from the redirection list.)
383			 */
384			error = strioctl(f->f_vnode, I_PUSH,
385			    (intptr_t)STRREDIR_MOD, FKIOCTL, K_TO_K,
386			    cred, rvalp);
387
388			if (error != 0) {
389				releasef((int)arg);
390				mutex_exit(&iwscn_redirect_lock);
391				return (error);
392			}
393		}
394
395		/* Push it onto the redirection stack */
396		mutex_enter(&iwscn_list_lock);
397		srpush(f->f_vnode, B_FALSE);
398		mutex_exit(&iwscn_list_lock);
399
400		releasef((int)arg);
401		mutex_exit(&iwscn_redirect_lock);
402		return (0);
403
404	case SRIOCISREDIR:
405		/*
406		 * Find the vnode corresponding to the file descriptor
407		 * argument and verify that it names a stream.
408		 */
409		if ((f = getf((int)arg)) == NULL) {
410			return (EBADF);
411		}
412		if (f->f_vnode->v_stream == NULL) {
413			releasef((int)arg);
414			return (ENOSTR);
415		}
416
417		lp = srhold();
418		*rvalp = (str_vp(f->f_vnode) == lp->wl_vp);
419		srrele(lp);
420		releasef((int)arg);
421		return (0);
422
423	case I_POP:
424		/*
425		 * We need to serialize I_POP operations with
426		 * SRIOCSREDIR operations so we don't accidently
427		 * remove the redirection module from a stream.
428		 */
429		mutex_enter(&iwscn_redirect_lock);
430		lp = srhold();
431
432		/*
433		 * Here we need to protect against process that might
434		 * try to pop off the redirection module from the
435		 * redirected stream.  Popping other modules is allowed.
436		 *
437		 * It's ok to hold iwscn_list_lock while doing the
438		 * I_LOOK since it's such a simple operation.
439		 */
440		(void) strioctl(lp->wl_vp, I_LOOK, (intptr_t)modname,
441		    FKIOCTL, K_TO_K, cred, rvalp);
442
443		if (strcmp(STRREDIR_MOD, modname) == 0) {
444			srrele(lp);
445			mutex_exit(&iwscn_redirect_lock);
446			return (EINVAL);
447		}
448
449		/* Process the ioctl normally */
450		error = VOP_IOCTL(lp->wl_vp, cmd, arg, flag, cred, rvalp, NULL);
451
452		srrele(lp);
453		mutex_exit(&iwscn_redirect_lock);
454		return (error);
455	}
456
457	/* Process the ioctl normally */
458	lp = srhold();
459	error = VOP_IOCTL(lp->wl_vp, cmd, arg, flag, cred, rvalp, NULL);
460	srrele(lp);
461	return (error);
462}
463
464/* ARGSUSED */
465static int
466iwscnopen(dev_t *devp, int flag, int state, cred_t *cred)
467{
468	iwscn_list_t	*lp;
469	vnode_t		*vp = rwsconsvp;
470
471	if (state != OTYP_CHR)
472		return (ENXIO);
473
474	if (getminor(*devp) != 0)
475		return (ENXIO);
476
477	/*
478	 * You can't really open us until the console subsystem
479	 * has been configured.
480	 */
481	if (rwsconsvp == NULL)
482		return (ENXIO);
483
484	/*
485	 * Check if this is the first open of this device or if
486	 * there is currently no redirection going on.  (Ie, we're
487	 * sending output to underlying console device.)
488	 */
489	mutex_enter(&iwscn_list_lock);
490	if ((iwscn_list == NULL) || (iwscn_list->wl_vp == str_vp(vp))) {
491		int		error = 0;
492
493		/* Don't hold the list lock across an VOP_OPEN */
494		mutex_exit(&iwscn_list_lock);
495
496		/*
497		 * There is currently no redirection going on.
498		 * pass this open request onto the console driver
499		 */
500		error = VOP_OPEN(&vp, flag, cred, NULL);
501		if (error != 0)
502			return (error);
503
504		/* Re-acquire the list lock */
505		mutex_enter(&iwscn_list_lock);
506
507		if (iwscn_list == NULL) {
508			/* Save this vnode on the redirection list */
509			srpush(vp, B_TRUE);
510		} else {
511			/*
512			 * In this case there must already be a copy of
513			 * this vnode on the list, so we can free up this one.
514			 */
515			(void) VOP_CLOSE(vp, flag, 1, (offset_t)0, cred, NULL);
516		}
517	}
518
519	/*
520	 * XXX This is an ugly legacy hack that has been around
521	 * forever.  This code is here because this driver (the
522	 * iwscn driver) is a character driver layered over a
523	 * streams driver.
524	 *
525	 * Normally streams recieve notification whenever a process
526	 * closes its last reference to that stream so that it can
527	 * clean up any signal handling related configuration.  (Ie,
528	 * when a stream is configured to deliver a signal to a
529	 * process upon certain events.)  This is a feature supported
530	 * by the streams framework.
531	 *
532	 * But character/block drivers don't recieve this type
533	 * of notification.  A character/block driver's close routine
534	 * is only invoked upon the last close of the device.  This
535	 * is an artifact of the multiple open/single close driver
536	 * model currently supported by solaris.
537	 *
538	 * So a problem occurs when a character driver layers itself
539	 * on top of a streams driver.  Since this driver doesn't always
540	 * receive a close notification when a process closes its
541	 * last reference to it, this driver can't tell the stream
542	 * it's layered upon to clean up any signal handling
543	 * configuration for that process.
544	 *
545	 * So here we hack around that by manually cleaning up the
546	 * signal handling list upon each open.  It doesn't guarantee
547	 * that the signaling handling data stored in the stream will
548	 * always be up to date, but it'll be more up to date than
549	 * it would be if we didn't do this.
550	 *
551	 * The real way to solve this problem would be to change
552	 * the device framework from an multiple open/single close
553	 * model to a multiple open/multiple close model.  Then
554	 * character/block drivers could pass on close requests
555	 * to streams layered underneath.
556	 */
557	str_cn_clean(VTOS(rwsconsvp)->s_commonvp);
558	for (lp = iwscn_list; lp != NULL; lp = lp->wl_next) {
559		ASSERT(lp->wl_vp->v_stream != NULL);
560		str_cn_clean(lp->wl_vp);
561	}
562
563	mutex_exit(&iwscn_list_lock);
564	return (0);
565}
566
567/* ARGSUSED */
568static int
569iwscnclose(dev_t dev, int flag, int state, cred_t *cred)
570{
571	iwscn_list_t	*lp;
572
573	ASSERT(getminor(dev) == 0);
574
575	if (state != OTYP_CHR)
576		return (ENXIO);
577
578	mutex_enter(&iwscn_list_lock);
579	/*
580	 * Remove each entry from the redirection list, terminate any
581	 * current operations, wait for them to finish, then free the entry.
582	 */
583	while (iwscn_list != NULL) {
584		lp = srrm(iwscn_list->wl_vp);
585		ASSERT(lp != NULL);
586		srinterrupt(lp, B_TRUE);
587
588		if (lp->wl_is_console == B_TRUE)
589			/* Close the underlying console device. */
590			(void) VOP_CLOSE(lp->wl_vp, 0, 1, (offset_t)0, kcred,
591			    NULL);
592
593		kmem_free(lp, sizeof (*lp));
594	}
595	mutex_exit(&iwscn_list_lock);
596	return (0);
597}
598
599/*ARGSUSED*/
600static int
601iwscnattach(dev_info_t *devi, ddi_attach_cmd_t cmd)
602{
603	/*
604	 * This is a pseudo device so there will never be more than
605	 * one instance attached at a time
606	 */
607	ASSERT(iwscn_dip == NULL);
608
609	if (ddi_create_minor_node(devi, "iwscn", S_IFCHR,
610	    0, DDI_PSEUDO, NULL) == DDI_FAILURE) {
611		return (DDI_FAILURE);
612	}
613
614	iwscn_dip = devi;
615	mutex_init(&iwscn_list_lock, NULL, MUTEX_DRIVER, NULL);
616	mutex_init(&iwscn_redirect_lock, NULL, MUTEX_DRIVER, NULL);
617	cv_init(&iwscn_list_cv, NULL, CV_DRIVER, NULL);
618
619	return (DDI_SUCCESS);
620}
621
622/* ARGSUSED */
623static int
624iwscninfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
625{
626	int error;
627
628	switch (infocmd) {
629	case DDI_INFO_DEVT2DEVINFO:
630		if (iwscn_dip == NULL) {
631			error = DDI_FAILURE;
632		} else {
633			*result = (void *)iwscn_dip;
634			error = DDI_SUCCESS;
635		}
636		break;
637	case DDI_INFO_DEVT2INSTANCE:
638		*result = (void *)0;
639		error = DDI_SUCCESS;
640		break;
641	default:
642		error = DDI_FAILURE;
643	}
644	return (error);
645}
646
647struct cb_ops	iwscn_cb_ops = {
648	iwscnopen,		/* open */
649	iwscnclose,		/* close */
650	nodev,			/* strategy */
651	nodev,			/* print */
652	nodev,			/* dump */
653	iwscnread,		/* read */
654	iwscnwrite,		/* write */
655	iwscnioctl,		/* ioctl */
656	nodev,			/* devmap */
657	nodev,			/* mmap */
658	nodev, 			/* segmap */
659	iwscnpoll,		/* poll */
660	ddi_prop_op,		/* cb_prop_op */
661	NULL,			/* streamtab  */
662	D_MP			/* Driver compatibility flag */
663};
664
665struct dev_ops	iwscn_ops = {
666	DEVO_REV,		/* devo_rev, */
667	0,			/* refcnt  */
668	iwscninfo,		/* info */
669	nulldev,		/* identify */
670	nulldev,		/* probe */
671	iwscnattach,		/* attach */
672	nodev,			/* detach */
673	nodev,			/* reset */
674	&iwscn_cb_ops,		/* driver operations */
675	NULL,			/* bus operations */
676	NULL,			/* power */
677	ddi_quiesce_not_needed,		/* quiesce */
678};
679
680/*
681 * Module linkage information for the kernel.
682 */
683static struct modldrv modldrv = {
684	&mod_driverops, /* Type of module.  This one is a pseudo driver */
685	"Workstation Redirection driver",
686	&iwscn_ops,	/* driver ops */
687};
688
689static struct modlinkage modlinkage = {
690	MODREV_1,
691	&modldrv,
692	NULL
693};
694
695int
696_init(void)
697{
698	return (mod_install(&modlinkage));
699}
700
701int
702_fini(void)
703{
704	return (EBUSY);
705}
706
707int
708_info(struct modinfo *modinfop)
709{
710	return (mod_info(&modlinkage, modinfop));
711}
712