xref: /illumos-gate/usr/src/uts/common/io/sysmsg.c (revision 2bc92566ea035349acb03567b0d95e399c77c0d3)
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, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2005 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  * System message redirection driver for Sun.
31  *
32  * Redirects system message output to the device designated as the underlying
33  * "hardware" console, as given by the value of sysmvp.  The implementation
34  * assumes that sysmvp denotes a STREAMS device; the assumption is justified
35  * since consoles must be capable of effecting tty semantics.
36  */
37 
38 #include <sys/types.h>
39 #include <sys/kmem.h>
40 #include <sys/open.h>
41 #include <sys/param.h>
42 #include <sys/systm.h>
43 #include <sys/signal.h>
44 #include <sys/cred.h>
45 #include <sys/user.h>
46 #include <sys/proc.h>
47 #include <sys/vnode.h>
48 #include <sys/uio.h>
49 #include <sys/stat.h>
50 #include <sys/file.h>
51 #include <sys/session.h>
52 #include <sys/stream.h>
53 #include <sys/strsubr.h>
54 #include <sys/poll.h>
55 #include <sys/debug.h>
56 #include <sys/sysmsg_impl.h>
57 #include <sys/conf.h>
58 #include <sys/termios.h>
59 #include <sys/errno.h>
60 #include <sys/modctl.h>
61 #include <sys/pathname.h>
62 #include <sys/ddi.h>
63 #include <sys/sunddi.h>
64 #include <sys/consdev.h>
65 #include <sys/policy.h>
66 
67 /*
68  * internal functions
69  */
70 static int sysmopen(dev_t *, int, int, cred_t *);
71 static int sysmclose(dev_t, int, int, cred_t *);
72 static int sysmread(dev_t, struct uio *, cred_t *);
73 static int sysmwrite(dev_t, struct uio *, cred_t *);
74 static int sysmioctl(dev_t, int, intptr_t, int, cred_t *, int *);
75 static int sysmpoll(dev_t, short, int, short *, struct pollhead **);
76 static int sysm_info(dev_info_t *, ddi_info_cmd_t, void *, void **);
77 static int sysm_attach(dev_info_t *, ddi_attach_cmd_t);
78 static int sysm_detach(dev_info_t *, ddi_detach_cmd_t);
79 static void bind_consadm_conf(char *);
80 static int checkarg(dev_t);
81 
82 static dev_info_t *sysm_dip;		/* private copy of devinfo pointer */
83 
84 static struct cb_ops sysm_cb_ops = {
85 
86 	sysmopen,		/* open */
87 	sysmclose,		/* close */
88 	nodev,			/* strategy */
89 	nodev,			/* print */
90 	nodev,			/* dump */
91 	sysmread,		/* read */
92 	sysmwrite,		/* write */
93 	sysmioctl,		/* ioctl */
94 	nodev,			/* devmap */
95 	nodev,			/* mmap */
96 	nodev, 			/* segmap */
97 	sysmpoll,		/* poll */
98 	ddi_prop_op,		/* cb_prop_op */
99 	NULL,			/* streamtab  */
100 	D_NEW | D_MP,		/* Driver compatibility flag */
101 	CB_REV,			/* cb_rev */
102 	nodev,			/* aread */
103 	nodev			/* awrite */
104 };
105 
106 static struct dev_ops sysm_ops = {
107 
108 	DEVO_REV,		/* devo_rev, */
109 	0,			/* refcnt  */
110 	sysm_info,		/* info */
111 	nulldev,		/* identify */
112 	nulldev,		/* probe */
113 	sysm_attach,		/* attach */
114 	sysm_detach,		/* detach */
115 	nodev,			/* reset */
116 	&sysm_cb_ops,		/* driver operations */
117 	(struct bus_ops *)0,	/* bus operations */
118 	nulldev			/* power */
119 
120 };
121 
122 /*
123  * Global variables associated with the console device:
124  */
125 
126 #define	SYS_SYSMIN	0	/* sysmsg minor number */
127 #define	SYS_MSGMIN	1	/* msglog minor number */
128 #define	SYSPATHLEN	255	/* length of device path */
129 
130 /*
131  * Private driver state:
132  */
133 
134 #define	MAXDEVS 5
135 
136 typedef struct {
137 	dev_t	dca_devt;
138 	int	dca_flags;
139 	vnode_t	*dca_vp;
140 	krwlock_t	dca_lock;
141 	char	dca_name[SYSPATHLEN];
142 } devicecache_t;
143 
144 /* list of dyn. + persist. config'ed dev's */
145 static devicecache_t sysmcache[MAXDEVS];
146 static kmutex_t	dcvp_mutex;
147 static vnode_t	*dcvp = NULL;
148 
149 /* flags for device cache */
150 #define	SYSM_DISABLED	0x0
151 #define	SYSM_ENABLED	0x1
152 
153 /*
154  * Module linkage information for the kernel.
155  */
156 
157 static struct modldrv modldrv = {
158 	&mod_driverops, /* Type of module.  This one is a pseudo driver */
159 	"System message redirection (fanout) driver %I%",
160 	&sysm_ops,	/* driver ops */
161 };
162 
163 static struct modlinkage modlinkage = {
164 	MODREV_1,
165 	&modldrv,
166 	NULL
167 };
168 
169 int
170 _init(void)
171 {
172 	return (mod_install(&modlinkage));
173 }
174 
175 int
176 _fini(void)
177 {
178 	return (mod_remove(&modlinkage));
179 }
180 
181 int
182 _info(struct modinfo *modinfop)
183 {
184 	return (mod_info(&modlinkage, modinfop));
185 }
186 
187 /*
188  * DDI glue routines
189  */
190 static int
191 sysm_attach(dev_info_t *devi, ddi_attach_cmd_t cmd)
192 {
193 	int i;
194 
195 	switch (cmd) {
196 	case DDI_ATTACH:
197 		ASSERT(sysm_dip == NULL);
198 
199 		if (ddi_create_minor_node(devi, "sysmsg", S_IFCHR,
200 			SYS_SYSMIN, DDI_PSEUDO, NULL) == DDI_FAILURE ||
201 			ddi_create_minor_node(devi, "msglog", S_IFCHR,
202 			SYS_MSGMIN, DDI_PSEUDO, NULL) == DDI_FAILURE) {
203 			ddi_remove_minor_node(devi, NULL);
204 			return (DDI_FAILURE);
205 		}
206 
207 		for (i = 0; i < MAXDEVS; i++) {
208 			rw_init(&sysmcache[i].dca_lock, NULL, RW_DRIVER, NULL);
209 		}
210 
211 		sysm_dip = devi;
212 		return (DDI_SUCCESS);
213 	case DDI_SUSPEND:
214 	case DDI_PM_SUSPEND:
215 		return (DDI_SUCCESS);
216 	default:
217 		return (DDI_FAILURE);
218 	}
219 }
220 
221 static int
222 sysm_detach(dev_info_t *devi, ddi_detach_cmd_t cmd)
223 {
224 	int i;
225 
226 	switch (cmd) {
227 	case DDI_DETACH:
228 		ASSERT(sysm_dip == devi);
229 
230 		for (i = 0; i < MAXDEVS; i++)
231 			rw_destroy(&sysmcache[i].dca_lock);
232 
233 		ddi_remove_minor_node(devi, NULL);
234 		sysm_dip = NULL;
235 		return (DDI_SUCCESS);
236 
237 	case DDI_SUSPEND:
238 	case DDI_PM_SUSPEND:
239 		return (DDI_SUCCESS);
240 	default:
241 		return (DDI_FAILURE);
242 	}
243 
244 }
245 
246 /* ARGSUSED */
247 static int
248 sysm_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
249 {
250 	int rval = DDI_FAILURE;
251 	minor_t instance;
252 
253 	instance = getminor((dev_t)arg);
254 
255 	switch (infocmd) {
256 	case DDI_INFO_DEVT2DEVINFO:
257 		if (sysm_dip != NULL &&
258 			(instance == SYS_SYSMIN || instance == SYS_MSGMIN)) {
259 			*result = sysm_dip;
260 			rval = DDI_SUCCESS;
261 		}
262 		break;
263 
264 	case DDI_INFO_DEVT2INSTANCE:
265 		if (instance == SYS_SYSMIN || instance == SYS_MSGMIN) {
266 			*result = NULL;
267 			rval = DDI_SUCCESS;
268 		}
269 		break;
270 
271 	default:
272 		break;
273 	}
274 
275 	return (rval);
276 }
277 
278 /*
279  * Parse the contents of the buffer, and bind the named
280  * devices as auxiliary consoles using our own ioctl routine.
281  *
282  * Comments begin with '#' and are terminated only by a newline
283  * Device names begin with a '/', and are terminated by a newline,
284  * space, '#' or tab.
285  */
286 static void
287 parse_buffer(char *buf, ssize_t fsize)
288 {
289 	char *ebuf = buf + fsize;
290 	char *devname = NULL;
291 	int eatcomments = 0;
292 
293 	while (buf < ebuf) {
294 		if (eatcomments) {
295 			if (*buf++ == '\n')
296 				eatcomments = 0;
297 			continue;
298 		}
299 		switch (*buf) {
300 		case '/':
301 			if (devname == NULL)
302 				devname = buf;
303 			break;
304 		case '#':
305 			eatcomments = 1;
306 			/*FALLTHROUGH*/
307 		case ' ':
308 		case '\t':
309 		case '\n':
310 			*buf = '\0';
311 			if (devname == NULL)
312 				break;
313 			(void) sysmioctl(NODEV, CIOCSETCONSOLE,
314 				(intptr_t)devname, FNATIVE|FKIOCTL|FREAD|FWRITE,
315 				kcred, NULL);
316 			devname = NULL;
317 			break;
318 		default:
319 			break;
320 		}
321 		buf++;
322 	}
323 }
324 
325 #define	CNSADM_BYTES_MAX	2000	/* XXX  nasty fixed size */
326 
327 static void
328 bind_consadm_conf(char *path)
329 {
330 	struct vattr vattr;
331 	vnode_t *vp;
332 	void *buf;
333 	size_t size;
334 	ssize_t resid;
335 	int err = 0;
336 
337 	if (vn_open(path, UIO_SYSSPACE, FREAD, 0, &vp, 0, 0) != 0)
338 		return;
339 	vattr.va_mask = AT_SIZE;
340 	if ((err = VOP_GETATTR(vp, &vattr, 0, kcred)) != 0) {
341 		cmn_err(CE_WARN, "sysmsg: getattr: '%s': error %d",
342 			path, err);
343 		goto closevp;
344 	}
345 
346 	size = vattr.va_size > CNSADM_BYTES_MAX ?
347 		CNSADM_BYTES_MAX : (ssize_t)vattr.va_size;
348 	buf = kmem_alloc(size, KM_SLEEP);
349 
350 	if ((err = vn_rdwr(UIO_READ, vp, buf, size, (offset_t)0,
351 		UIO_SYSSPACE, 0, (rlim64_t)0, kcred, &resid)) != 0)
352 		cmn_err(CE_WARN, "sysmsg: vn_rdwr: '%s': error %d",
353 		    path, err);
354 	else
355 		parse_buffer(buf, size - resid);
356 
357 	kmem_free(buf, size);
358 closevp:
359 	(void) VOP_CLOSE(vp, FREAD, 1, (offset_t)0, kcred);
360 	VN_RELE(vp);
361 }
362 
363 /* ARGSUSED */
364 static int
365 sysmopen(dev_t *dev, int flag, int state, cred_t *cred)
366 {
367 	int	i;
368 	vnode_t	*vp;
369 	minor_t instance;
370 	static boolean_t initialized = B_FALSE;
371 
372 	instance = getminor(*dev);
373 
374 	if (state != OTYP_CHR || (instance != 0 && instance != 1))
375 		return (ENXIO);
376 
377 	mutex_enter(&dcvp_mutex);
378 	if ((dcvp == NULL) && (vn_open("/dev/console",
379 		UIO_SYSSPACE, FWRITE, 0, &dcvp, 0, 0) != 0)) {
380 		mutex_exit(&dcvp_mutex);
381 		return (ENXIO);
382 	}
383 	mutex_exit(&dcvp_mutex);
384 
385 	if (!initialized) {
386 		bind_consadm_conf("/etc/consadm.conf");
387 		initialized = B_TRUE;
388 	}
389 
390 	for (i = 0; i < MAXDEVS; i++) {
391 		rw_enter(&sysmcache[i].dca_lock, RW_WRITER);
392 		if ((sysmcache[i].dca_flags & SYSM_ENABLED) &&
393 			sysmcache[i].dca_vp == NULL) {
394 			/*
395 			 * 4196476 - FTRUNC was causing E10K to return EINVAL
396 			 * on open
397 			 */
398 			flag = flag & ~FTRUNC;
399 			/*
400 			 * Open failures on the auxiliary consoles are
401 			 * not returned because we don't care if some
402 			 * subset get an error. We know the default console
403 			 * is okay, and preserve the semantics of the
404 			 * open for the default console.
405 			 * Set NONBLOCK|NDELAY in case there's no carrier.
406 			 */
407 			if (vn_open(sysmcache[i].dca_name, UIO_SYSSPACE,
408 				flag | FNONBLOCK | FNDELAY, 0, &vp, 0, 0) == 0)
409 				sysmcache[i].dca_vp = vp;
410 		}
411 		rw_exit(&sysmcache[i].dca_lock);
412 	}
413 
414 	return (0);
415 }
416 
417 /* ARGSUSED */
418 static int
419 sysmclose(dev_t dev, int flag, int state, cred_t *cred)
420 {
421 	int	i;
422 
423 	ASSERT(dcvp != NULL);
424 
425 	if (state != OTYP_CHR)
426 		return (ENXIO);
427 
428 	(void) VOP_CLOSE(dcvp, FWRITE, 1, (offset_t)0, kcred);
429 	VN_RELE(dcvp);
430 	dcvp = NULL;
431 
432 	/*
433 	 * Close the auxiliary consoles, we're not concerned with
434 	 * passing up the errors.
435 	 */
436 	for (i = 0; i < MAXDEVS; i++) {
437 		rw_enter(&sysmcache[i].dca_lock, RW_WRITER);
438 		if (sysmcache[i].dca_vp != NULL) {
439 			(void) VOP_CLOSE(sysmcache[i].dca_vp, flag,
440 				1, (offset_t)0, cred);
441 			VN_RELE(sysmcache[i].dca_vp);
442 			sysmcache[i].dca_vp = NULL;
443 		}
444 		rw_exit(&sysmcache[i].dca_lock);
445 	}
446 
447 	return (0);
448 }
449 
450 /* Reads occur only on the default console */
451 
452 /* ARGSUSED */
453 static int
454 sysmread(dev_t dev, struct uio *uio, cred_t *cred)
455 {
456 	ASSERT(dcvp != NULL);
457 	return (VOP_READ(dcvp, uio, 0, cred, NULL));
458 }
459 
460 /* ARGSUSED */
461 static int
462 sysmwrite(dev_t dev, struct uio *uio, cred_t *cred)
463 {
464 	int	i = 0;
465 	iovec_t	uio_iov;
466 	struct uio	tuio;
467 
468 	ASSERT(dcvp != NULL);
469 	ASSERT(uio != NULL);
470 
471 	for (i = 0; i < MAXDEVS; i++) {
472 		rw_enter(&sysmcache[i].dca_lock, RW_READER);
473 		if (sysmcache[i].dca_vp != NULL &&
474 			(sysmcache[i].dca_flags & SYSM_ENABLED)) {
475 			tuio = *uio;
476 			uio_iov = *(uio->uio_iov);
477 			tuio.uio_iov = &uio_iov;
478 			(void) VOP_WRITE(sysmcache[i].dca_vp, &tuio, 0, cred,
479 				NULL);
480 		}
481 		rw_exit(&sysmcache[i].dca_lock);
482 	}
483 	return (VOP_WRITE(dcvp, uio, 0, cred, NULL));
484 }
485 
486 /* ARGSUSED */
487 static int
488 sysmioctl(dev_t dev, int cmd, intptr_t arg, int flag, cred_t *cred, int *rvalp)
489 {
490 	int	rval = 0;
491 	int	error = 0;
492 	size_t	size = 0;
493 	int	i;
494 	char	*infop;
495 	char	found = 0;
496 	dev_t	newdevt = (dev_t)NODEV;	/* because 0 == /dev/console */
497 	vnode_t	*vp;
498 
499 	switch (cmd) {
500 	case CIOCGETCONSOLE:
501 		/* Sum over the number of enabled devices */
502 		for (i = 0; i < MAXDEVS; i++) {
503 			if (sysmcache[i].dca_flags & SYSM_ENABLED)
504 				/* list is space separated, followed by NULL */
505 				size += strlen(sysmcache[i].dca_name) + 1;
506 		}
507 		if (size == 0)
508 			return (0);
509 		break;
510 	case CIOCSETCONSOLE:
511 	case CIOCRMCONSOLE:
512 		size = sizeof (sysmcache[0].dca_name);
513 		break;
514 	case CIOCTTYCONSOLE:
515 	{
516 		dev_t	d;
517 		dev32_t	d32;
518 		extern dev_t rwsconsdev, rconsdev, uconsdev;
519 		proc_t	*p;
520 
521 		if (drv_getparm(UPROCP, &p) != 0)
522 			return (ENODEV);
523 		else
524 			d = cttydev(p);
525 		/*
526 		 * If the controlling terminal is the real
527 		 * or workstation console device, map to what the
528 		 * user thinks is the console device.
529 		 */
530 		if (d == rwsconsdev || d == rconsdev)
531 			d = uconsdev;
532 		if ((flag & FMODELS) != FNATIVE) {
533 			if (!cmpldev(&d32, d))
534 				return (EOVERFLOW);
535 			if (ddi_copyout(&d32, (caddr_t)arg, sizeof (d32),
536 			    flag))
537 				return (EFAULT);
538 		} else {
539 			if (ddi_copyout(&d, (caddr_t)arg, sizeof (d), flag))
540 				return (EFAULT);
541 		}
542 		return (0);
543 	}
544 	default:
545 		/* everything else is sent to the console device */
546 		return (VOP_IOCTL(dcvp, cmd, arg, flag, cred, rvalp));
547 	}
548 
549 	if ((rval = secpolicy_console(cred)) != 0)
550 		return (EPERM);
551 
552 	infop = kmem_alloc(size, KM_SLEEP);
553 	if (flag & FKIOCTL)
554 		error = copystr((caddr_t)arg, infop, size, NULL);
555 	else
556 		error = copyinstr((caddr_t)arg, infop, size, NULL);
557 
558 	if (error) {
559 		switch (cmd) {
560 		case CIOCGETCONSOLE:
561 			/*
562 			 * If the buffer is null, then return a byte count
563 			 * to user land.
564 			 */
565 			*rvalp = size;
566 			goto err_exit;
567 		default:
568 			rval = EFAULT;
569 			goto err_exit;
570 		}
571 	}
572 
573 	if (infop[0] != NULL) {
574 		if ((rval = lookupname(infop, UIO_SYSSPACE, FOLLOW,
575 			NULLVPP, &vp)) == 0) {
576 			if (vp->v_type != VCHR) {
577 				VN_RELE(vp);
578 				rval = EINVAL;
579 				goto err_exit;
580 			}
581 			newdevt = vp->v_rdev;
582 			VN_RELE(vp);
583 		} else
584 			goto err_exit;
585 	}
586 
587 	switch (cmd) {
588 	case CIOCGETCONSOLE:
589 		/*
590 		 * Return the list of device names that are enabled.
591 		 */
592 		for (i = 0; i < MAXDEVS; i++) {
593 			rw_enter(&sysmcache[i].dca_lock, RW_READER);
594 			if (sysmcache[i].dca_flags & SYSM_ENABLED) {
595 				if (infop[0] != NULL)
596 					(void) strcat(infop, " ");
597 				(void) strcat(infop, sysmcache[i].dca_name);
598 			}
599 			rw_exit(&sysmcache[i].dca_lock);
600 		}
601 		if (rval == 0 && copyoutstr(infop, (void *)arg, size, NULL))
602 			rval = EFAULT;
603 		break;
604 
605 	case CIOCSETCONSOLE:
606 		if ((rval = checkarg(newdevt)) != 0)
607 			break;
608 		/*
609 		 * The device does not have to be open or disabled to
610 		 * perform the set console.
611 		 */
612 		for (i = 0; i < MAXDEVS; i++) {
613 			rw_enter(&sysmcache[i].dca_lock, RW_WRITER);
614 			if (sysmcache[i].dca_devt == newdevt &&
615 				(sysmcache[i].dca_flags & SYSM_ENABLED)) {
616 				(void) strcpy(sysmcache[i].dca_name, infop);
617 				rval = EEXIST;
618 				rw_exit(&sysmcache[i].dca_lock);
619 				break;
620 			} else if (sysmcache[i].dca_devt == newdevt &&
621 				sysmcache[i].dca_flags == SYSM_DISABLED) {
622 				sysmcache[i].dca_flags |= SYSM_ENABLED;
623 				(void) strcpy(sysmcache[i].dca_name, infop);
624 				rw_exit(&sysmcache[i].dca_lock);
625 				found = 1;
626 				break;
627 			} else if (sysmcache[i].dca_devt == 0) {
628 				ASSERT(sysmcache[i].dca_vp == NULL &&
629 				sysmcache[i].dca_flags == SYSM_DISABLED);
630 				(void) strcpy(sysmcache[i].dca_name, infop);
631 				sysmcache[i].dca_flags = SYSM_ENABLED;
632 				sysmcache[i].dca_devt = newdevt;
633 				rw_exit(&sysmcache[i].dca_lock);
634 				found = 1;
635 				break;
636 			}
637 			rw_exit(&sysmcache[i].dca_lock);
638 		}
639 		if (found == 0 && rval == 0)
640 			rval = ENOENT;
641 		break;
642 
643 	case CIOCRMCONSOLE:
644 		for (i = 0; i < MAXDEVS; i++) {
645 			rw_enter(&sysmcache[i].dca_lock, RW_WRITER);
646 			if (sysmcache[i].dca_devt == newdevt) {
647 				sysmcache[i].dca_flags = SYSM_DISABLED;
648 				sysmcache[i].dca_name[0] = '\0';
649 				rw_exit(&sysmcache[i].dca_lock);
650 				found = 1;
651 				break;
652 			}
653 			rw_exit(&sysmcache[i].dca_lock);
654 		}
655 		if (found == 0)
656 			rval = ENOENT;
657 		break;
658 
659 	default:
660 		break;
661 	}
662 
663 err_exit:
664 	kmem_free(infop, size);
665 	return (rval);
666 }
667 
668 /* As with the read, we poll only the default console */
669 
670 /* ARGSUSED */
671 static int
672 sysmpoll(dev_t dev, short events, int anyyet, short *reventsp,
673 	struct pollhead **phpp)
674 {
675 	return (VOP_POLL(dcvp, events, anyyet, reventsp, phpp));
676 }
677 
678 /* Sanity check that the device is good */
679 static int
680 checkarg(dev_t devt)
681 {
682 	int rval = 0;
683 	dev_t sysmsg_dev, msglog_dev;
684 	extern dev_t rwsconsdev, rconsdev, uconsdev;
685 
686 	if (devt == rconsdev || devt == rwsconsdev || devt == uconsdev) {
687 		rval = EBUSY;
688 	} else {
689 		sysmsg_dev = makedevice(ddi_driver_major(sysm_dip), SYS_SYSMIN);
690 		msglog_dev = makedevice(ddi_driver_major(sysm_dip), SYS_MSGMIN);
691 		if (devt == sysmsg_dev || devt == msglog_dev)
692 			rval = EINVAL;
693 	}
694 
695 	return (rval);
696 }
697