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