summaryrefslogtreecommitdiff
path: root/usr/src/uts/common/io/iwscons.c
blob: aeb6660a2a48d404510cbc229523565593948756 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
/*
 * 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 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */


/*
 * 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, 0) == 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 */
	NULL,			/* power */
	ddi_quiesce_not_needed,		/* quiesce */
};

/*
 * Module linkage information for the kernel.
 */
static struct modldrv modldrv = {
	&mod_driverops, /* Type of module.  This one is a pseudo driver */
	"Workstation Redirection driver",
	&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));
}