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 * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
24 */
25
26
27 /*
28 * System message redirection driver for Sun.
29 *
30 * Redirects system message output to the device designated as the underlying
31 * "hardware" console, as given by the value of sysmvp. The implementation
32 * assumes that sysmvp denotes a STREAMS device; the assumption is justified
33 * since consoles must be capable of effecting tty semantics.
34 */
35
36 #include <sys/types.h>
37 #include <sys/kmem.h>
38 #include <sys/open.h>
39 #include <sys/param.h>
40 #include <sys/systm.h>
41 #include <sys/signal.h>
42 #include <sys/cred.h>
43 #include <sys/user.h>
44 #include <sys/proc.h>
45 #include <sys/vnode.h>
46 #include <sys/uio.h>
47 #include <sys/stat.h>
48 #include <sys/file.h>
49 #include <sys/session.h>
50 #include <sys/stream.h>
51 #include <sys/strsubr.h>
52 #include <sys/poll.h>
53 #include <sys/debug.h>
54 #include <sys/sysmsg_impl.h>
55 #include <sys/conf.h>
56 #include <sys/termios.h>
57 #include <sys/errno.h>
58 #include <sys/modctl.h>
59 #include <sys/pathname.h>
60 #include <sys/ddi.h>
61 #include <sys/sunddi.h>
62 #include <sys/consdev.h>
63 #include <sys/policy.h>
64
65 /*
66 * internal functions
67 */
68 static int sysmopen(dev_t *, int, int, cred_t *);
69 static int sysmclose(dev_t, int, int, cred_t *);
70 static int sysmread(dev_t, struct uio *, cred_t *);
71 static int sysmwrite(dev_t, struct uio *, cred_t *);
72 static int sysmioctl(dev_t, int, intptr_t, int, cred_t *, int *);
73 static int sysmpoll(dev_t, short, int, short *, struct pollhead **);
74 static int sysm_info(dev_info_t *, ddi_info_cmd_t, void *, void **);
75 static int sysm_attach(dev_info_t *, ddi_attach_cmd_t);
76 static int sysm_detach(dev_info_t *, ddi_detach_cmd_t);
77 static void bind_consadm_conf(char *);
78 static int checkarg(dev_t);
79
80 static dev_info_t *sysm_dip; /* private copy of devinfo pointer */
81
82 static struct cb_ops sysm_cb_ops = {
83
84 sysmopen, /* open */
85 sysmclose, /* close */
86 nodev, /* strategy */
87 nodev, /* print */
88 nodev, /* dump */
89 sysmread, /* read */
90 sysmwrite, /* write */
91 sysmioctl, /* ioctl */
92 nodev, /* devmap */
93 nodev, /* mmap */
94 nodev, /* segmap */
95 sysmpoll, /* poll */
96 ddi_prop_op, /* cb_prop_op */
97 NULL, /* streamtab */
98 D_NEW | D_MP, /* Driver compatibility flag */
99 CB_REV, /* cb_rev */
100 nodev, /* aread */
101 nodev /* awrite */
102 };
103
104 static struct dev_ops sysm_ops = {
105
106 DEVO_REV, /* devo_rev, */
107 0, /* refcnt */
108 sysm_info, /* info */
109 nulldev, /* identify */
110 nulldev, /* probe */
111 sysm_attach, /* attach */
112 sysm_detach, /* detach */
113 nodev, /* reset */
114 &sysm_cb_ops, /* driver operations */
115 (struct bus_ops *)0, /* bus operations */
116 nulldev, /* power */
117 ddi_quiesce_not_needed, /* quiesce */
118
119 };
120
121 /*
122 * Global variables associated with the console device:
123 */
124
125 #define SYS_SYSMIN 0 /* sysmsg minor number */
126 #define SYS_MSGMIN 1 /* msglog minor number */
127 #define SYSPATHLEN 255 /* length of device path */
128
129 /*
130 * Private driver state:
131 */
132
133 #define MAXDEVS 5
134
135 typedef struct {
136 dev_t dca_devt;
137 int dca_flags;
138 vnode_t *dca_vp;
139 krwlock_t dca_lock;
140 char dca_name[SYSPATHLEN];
141 } devicecache_t;
142
143 /* list of dyn. + persist. config'ed dev's */
144 static devicecache_t sysmcache[MAXDEVS];
145 static kmutex_t dcvp_mutex;
146 static vnode_t *dcvp = NULL;
147 static boolean_t sysmsg_opened;
148 static boolean_t msglog_opened;
149
150 /* flags for device cache */
151 #define SYSM_DISABLED 0x0
152 #define SYSM_ENABLED 0x1
153
154 /*
155 * Module linkage information for the kernel.
156 */
157
158 static struct modldrv modldrv = {
159 &mod_driverops, /* Type of module. This one is a pseudo driver */
160 "System message redirection (fanout) driver",
161 &sysm_ops, /* driver ops */
162 };
163
164 static struct modlinkage modlinkage = {
165 MODREV_1,
166 { &modldrv, 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, NULL)) != 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, NULL);
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;
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
384 if (instance == SYS_SYSMIN)
385 sysmsg_opened = B_TRUE;
386 else
387 msglog_opened = B_TRUE;
388
389 if (!initialized) {
390 bind_consadm_conf("/etc/consadm.conf");
391 initialized = B_TRUE;
392 }
393 mutex_exit(&dcvp_mutex);
394
395 for (i = 0; i < MAXDEVS; i++) {
396 rw_enter(&sysmcache[i].dca_lock, RW_WRITER);
397 if ((sysmcache[i].dca_flags & SYSM_ENABLED) &&
398 sysmcache[i].dca_vp == NULL) {
399 /*
400 * 4196476 - FTRUNC was causing E10K to return EINVAL
401 * on open
402 */
403 flag = flag & ~FTRUNC;
404 /*
405 * Open failures on the auxiliary consoles are
406 * not returned because we don't care if some
407 * subset get an error. We know the default console
408 * is okay, and preserve the semantics of the
409 * open for the default console.
410 * Set NONBLOCK|NDELAY in case there's no carrier.
411 */
412 if (vn_open(sysmcache[i].dca_name, UIO_SYSSPACE,
413 flag | FNONBLOCK | FNDELAY, 0, &vp, 0, 0) == 0)
414 sysmcache[i].dca_vp = vp;
415 }
416 rw_exit(&sysmcache[i].dca_lock);
417 }
418
419 return (0);
420 }
421
422 /* ARGSUSED */
423 static int
424 sysmclose(dev_t dev, int flag, int state, cred_t *cred)
425 {
426 int i;
427 minor_t instance;
428
429 ASSERT(dcvp != NULL);
430
431 if (state != OTYP_CHR)
432 return (ENXIO);
433
434 instance = getminor(dev);
435
436 mutex_enter(&dcvp_mutex);
437 if (instance == SYS_SYSMIN)
438 sysmsg_opened = B_FALSE;
439 else
440 msglog_opened = B_FALSE;
441
442 if (sysmsg_opened || msglog_opened) {
443 mutex_exit(&dcvp_mutex);
444 return (0);
445 }
446
447 (void) VOP_CLOSE(dcvp, FWRITE, 1, (offset_t)0, kcred, NULL);
448 VN_RELE(dcvp);
449 dcvp = NULL;
450 mutex_exit(&dcvp_mutex);
451
452 /*
453 * Close the auxiliary consoles, we're not concerned with
454 * passing up the errors.
455 */
456 for (i = 0; i < MAXDEVS; i++) {
457 rw_enter(&sysmcache[i].dca_lock, RW_WRITER);
458 if (sysmcache[i].dca_vp != NULL) {
459 (void) VOP_CLOSE(sysmcache[i].dca_vp, flag,
460 1, (offset_t)0, cred, NULL);
461 VN_RELE(sysmcache[i].dca_vp);
462 sysmcache[i].dca_vp = NULL;
463 }
464 rw_exit(&sysmcache[i].dca_lock);
465 }
466
467 return (0);
468 }
469
470 /* Reads occur only on the default console */
471
472 /* ARGSUSED */
473 static int
474 sysmread(dev_t dev, struct uio *uio, cred_t *cred)
475 {
476 ASSERT(dcvp != NULL);
477 return (VOP_READ(dcvp, uio, 0, cred, NULL));
478 }
479
480 /* ARGSUSED */
481 static int
482 sysmwrite(dev_t dev, struct uio *uio, cred_t *cred)
483 {
484 int i = 0;
485 iovec_t uio_iov;
486 struct uio tuio;
487
488 ASSERT(dcvp != NULL);
489 ASSERT(uio != NULL);
490
491 for (i = 0; i < MAXDEVS; i++) {
492 rw_enter(&sysmcache[i].dca_lock, RW_READER);
493 if (sysmcache[i].dca_vp != NULL &&
494 (sysmcache[i].dca_flags & SYSM_ENABLED)) {
495 tuio = *uio;
496 uio_iov = *(uio->uio_iov);
497 tuio.uio_iov = &uio_iov;
498 (void) VOP_WRITE(sysmcache[i].dca_vp, &tuio, 0, cred,
499 NULL);
500 }
501 rw_exit(&sysmcache[i].dca_lock);
502 }
503 return (VOP_WRITE(dcvp, uio, 0, cred, NULL));
504 }
505
506 /* ARGSUSED */
507 static int
508 sysmioctl(dev_t dev, int cmd, intptr_t arg, int flag, cred_t *cred, int *rvalp)
509 {
510 int rval = 0;
511 int error = 0;
512 size_t size = 0;
513 int i;
514 char *infop;
515 char found = 0;
516 dev_t newdevt = (dev_t)NODEV; /* because 0 == /dev/console */
517 vnode_t *vp;
518
519 switch (cmd) {
520 case CIOCGETCONSOLE:
521 /* Sum over the number of enabled devices */
522 for (i = 0; i < MAXDEVS; i++) {
523 if (sysmcache[i].dca_flags & SYSM_ENABLED)
524 /* list is space separated, followed by NULL */
525 size += strlen(sysmcache[i].dca_name) + 1;
526 }
527 if (size == 0)
528 return (0);
529 break;
530 case CIOCSETCONSOLE:
531 case CIOCRMCONSOLE:
532 size = sizeof (sysmcache[0].dca_name);
533 break;
534 case CIOCTTYCONSOLE:
535 {
536 dev_t d;
537 dev32_t d32;
538 extern dev_t rwsconsdev, rconsdev, uconsdev;
539 proc_t *p;
540
541 if (drv_getparm(UPROCP, &p) != 0)
542 return (ENODEV);
543 else
544 d = cttydev(p);
545 /*
546 * If the controlling terminal is the real
547 * or workstation console device, map to what the
548 * user thinks is the console device.
549 */
550 if (d == rwsconsdev || d == rconsdev)
551 d = uconsdev;
552 if ((flag & FMODELS) != FNATIVE) {
553 if (!cmpldev(&d32, d))
554 return (EOVERFLOW);
555 if (ddi_copyout(&d32, (caddr_t)arg, sizeof (d32),
556 flag))
557 return (EFAULT);
558 } else {
559 if (ddi_copyout(&d, (caddr_t)arg, sizeof (d), flag))
560 return (EFAULT);
561 }
562 return (0);
563 }
564 default:
565 /* everything else is sent to the console device */
566 return (VOP_IOCTL(dcvp, cmd, arg, flag, cred, rvalp, NULL));
567 }
568
569 if ((rval = secpolicy_console(cred)) != 0)
570 return (EPERM);
571
572 infop = kmem_alloc(size, KM_SLEEP);
573 if (flag & FKIOCTL)
574 error = copystr((caddr_t)arg, infop, size, NULL);
575 else
576 error = copyinstr((caddr_t)arg, infop, size, NULL);
577
578 if (error) {
579 switch (cmd) {
580 case CIOCGETCONSOLE:
581 /*
582 * If the buffer is null, then return a byte count
583 * to user land.
584 */
585 *rvalp = size;
586 goto err_exit;
587 default:
588 rval = EFAULT;
589 goto err_exit;
590 }
591 }
592
593 if (infop[0] != NULL) {
594 if ((rval = lookupname(infop, UIO_SYSSPACE, FOLLOW,
595 NULLVPP, &vp)) == 0) {
596 if (vp->v_type != VCHR) {
597 VN_RELE(vp);
598 rval = EINVAL;
599 goto err_exit;
600 }
601 newdevt = vp->v_rdev;
602 VN_RELE(vp);
603 } else
604 goto err_exit;
605 }
606
607 switch (cmd) {
608 case CIOCGETCONSOLE:
609 /*
610 * Return the list of device names that are enabled.
611 */
612 for (i = 0; i < MAXDEVS; i++) {
613 rw_enter(&sysmcache[i].dca_lock, RW_READER);
614 if (sysmcache[i].dca_flags & SYSM_ENABLED) {
615 if (infop[0] != NULL)
616 (void) strcat(infop, " ");
617 (void) strcat(infop, sysmcache[i].dca_name);
618 }
619 rw_exit(&sysmcache[i].dca_lock);
620 }
621 if (rval == 0 && copyoutstr(infop, (void *)arg, size, NULL))
622 rval = EFAULT;
623 break;
624
625 case CIOCSETCONSOLE:
626 if ((rval = checkarg(newdevt)) != 0)
627 break;
628 /*
629 * The device does not have to be open or disabled to
630 * perform the set console.
631 */
632 for (i = 0; i < MAXDEVS; i++) {
633 rw_enter(&sysmcache[i].dca_lock, RW_WRITER);
634 if (sysmcache[i].dca_devt == newdevt &&
635 (sysmcache[i].dca_flags & SYSM_ENABLED)) {
636 (void) strcpy(sysmcache[i].dca_name, infop);
637 rval = EEXIST;
638 rw_exit(&sysmcache[i].dca_lock);
639 break;
640 } else if (sysmcache[i].dca_devt == newdevt &&
641 sysmcache[i].dca_flags == SYSM_DISABLED) {
642 sysmcache[i].dca_flags |= SYSM_ENABLED;
643 (void) strcpy(sysmcache[i].dca_name, infop);
644 rw_exit(&sysmcache[i].dca_lock);
645 found = 1;
646 break;
647 } else if (sysmcache[i].dca_devt == 0) {
648 ASSERT(sysmcache[i].dca_vp == NULL &&
649 sysmcache[i].dca_flags == SYSM_DISABLED);
650 (void) strcpy(sysmcache[i].dca_name, infop);
651 sysmcache[i].dca_flags = SYSM_ENABLED;
652 sysmcache[i].dca_devt = newdevt;
653 rw_exit(&sysmcache[i].dca_lock);
654 found = 1;
655 break;
656 }
657 rw_exit(&sysmcache[i].dca_lock);
658 }
659 if (found == 0 && rval == 0)
660 rval = ENOENT;
661 break;
662
663 case CIOCRMCONSOLE:
664 for (i = 0; i < MAXDEVS; i++) {
665 rw_enter(&sysmcache[i].dca_lock, RW_WRITER);
666 if (sysmcache[i].dca_devt == newdevt) {
667 sysmcache[i].dca_flags = SYSM_DISABLED;
668 sysmcache[i].dca_name[0] = '\0';
669 rw_exit(&sysmcache[i].dca_lock);
670 found = 1;
671 break;
672 }
673 rw_exit(&sysmcache[i].dca_lock);
674 }
675 if (found == 0)
676 rval = ENOENT;
677 break;
678
679 default:
680 break;
681 }
682
683 err_exit:
684 kmem_free(infop, size);
685 return (rval);
686 }
687
688 /* As with the read, we poll only the default console */
689
690 /* ARGSUSED */
691 static int
692 sysmpoll(dev_t dev, short events, int anyyet, short *reventsp,
693 struct pollhead **phpp)
694 {
695 return (VOP_POLL(dcvp, events, anyyet, reventsp, phpp, NULL));
696 }
697
698 /* Sanity check that the device is good */
699 static int
700 checkarg(dev_t devt)
701 {
702 int rval = 0;
703 dev_t sysmsg_dev, msglog_dev;
704 extern dev_t rwsconsdev, rconsdev, uconsdev;
705
706 if (devt == rconsdev || devt == rwsconsdev || devt == uconsdev) {
707 rval = EBUSY;
708 } else {
709 sysmsg_dev = makedevice(ddi_driver_major(sysm_dip), SYS_SYSMIN);
710 msglog_dev = makedevice(ddi_driver_major(sysm_dip), SYS_MSGMIN);
711 if (devt == sysmsg_dev || devt == msglog_dev)
712 rval = EINVAL;
713 }
714
715 return (rval);
716 }