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,
167 NULL
168 };
169
170 int
_init(void)171 _init(void)
172 {
173 return (mod_install(&modlinkage));
174 }
175
176 int
_fini(void)177 _fini(void)
178 {
179 return (mod_remove(&modlinkage));
180 }
181
182 int
_info(struct modinfo * modinfop)183 _info(struct modinfo *modinfop)
184 {
185 return (mod_info(&modlinkage, modinfop));
186 }
187
188 /*
189 * DDI glue routines
190 */
191 static int
sysm_attach(dev_info_t * devi,ddi_attach_cmd_t cmd)192 sysm_attach(dev_info_t *devi, ddi_attach_cmd_t cmd)
193 {
194 int i;
195
196 switch (cmd) {
197 case DDI_ATTACH:
198 ASSERT(sysm_dip == NULL);
199
200 if (ddi_create_minor_node(devi, "sysmsg", S_IFCHR,
201 SYS_SYSMIN, DDI_PSEUDO, 0) == DDI_FAILURE ||
202 ddi_create_minor_node(devi, "msglog", S_IFCHR,
203 SYS_MSGMIN, DDI_PSEUDO, 0) == DDI_FAILURE) {
204 ddi_remove_minor_node(devi, NULL);
205 return (DDI_FAILURE);
206 }
207
208 for (i = 0; i < MAXDEVS; i++) {
209 rw_init(&sysmcache[i].dca_lock, NULL, RW_DRIVER, NULL);
210 }
211
212 sysm_dip = devi;
213 return (DDI_SUCCESS);
214 case DDI_SUSPEND:
215 case DDI_PM_SUSPEND:
216 return (DDI_SUCCESS);
217 default:
218 return (DDI_FAILURE);
219 }
220 }
221
222 static int
sysm_detach(dev_info_t * devi,ddi_detach_cmd_t cmd)223 sysm_detach(dev_info_t *devi, ddi_detach_cmd_t cmd)
224 {
225 int i;
226
227 switch (cmd) {
228 case DDI_DETACH:
229 ASSERT(sysm_dip == devi);
230
231 for (i = 0; i < MAXDEVS; i++)
232 rw_destroy(&sysmcache[i].dca_lock);
233
234 ddi_remove_minor_node(devi, NULL);
235 sysm_dip = NULL;
236 return (DDI_SUCCESS);
237
238 case DDI_SUSPEND:
239 case DDI_PM_SUSPEND:
240 return (DDI_SUCCESS);
241 default:
242 return (DDI_FAILURE);
243 }
244
245 }
246
247 /* ARGSUSED */
248 static int
sysm_info(dev_info_t * dip,ddi_info_cmd_t infocmd,void * arg,void ** result)249 sysm_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
250 {
251 int rval = DDI_FAILURE;
252 minor_t instance;
253
254 instance = getminor((dev_t)arg);
255
256 switch (infocmd) {
257 case DDI_INFO_DEVT2DEVINFO:
258 if (sysm_dip != NULL &&
259 (instance == SYS_SYSMIN || instance == SYS_MSGMIN)) {
260 *result = sysm_dip;
261 rval = DDI_SUCCESS;
262 }
263 break;
264
265 case DDI_INFO_DEVT2INSTANCE:
266 if (instance == SYS_SYSMIN || instance == SYS_MSGMIN) {
267 *result = NULL;
268 rval = DDI_SUCCESS;
269 }
270 break;
271
272 default:
273 break;
274 }
275
276 return (rval);
277 }
278
279 /*
280 * Parse the contents of the buffer, and bind the named
281 * devices as auxiliary consoles using our own ioctl routine.
282 *
283 * Comments begin with '#' and are terminated only by a newline
284 * Device names begin with a '/', and are terminated by a newline,
285 * space, '#' or tab.
286 */
287 static void
parse_buffer(char * buf,ssize_t fsize)288 parse_buffer(char *buf, ssize_t fsize)
289 {
290 char *ebuf = buf + fsize;
291 char *devname = NULL;
292 int eatcomments = 0;
293
294 while (buf < ebuf) {
295 if (eatcomments) {
296 if (*buf++ == '\n')
297 eatcomments = 0;
298 continue;
299 }
300 switch (*buf) {
301 case '/':
302 if (devname == NULL)
303 devname = buf;
304 break;
305 case '#':
306 eatcomments = 1;
307 /*FALLTHROUGH*/
308 case ' ':
309 case '\t':
310 case '\n':
311 *buf = '\0';
312 if (devname == NULL)
313 break;
314 (void) sysmioctl(NODEV, CIOCSETCONSOLE,
315 (intptr_t)devname, FNATIVE|FKIOCTL|FREAD|FWRITE,
316 kcred, NULL);
317 devname = NULL;
318 break;
319 default:
320 break;
321 }
322 buf++;
323 }
324 }
325
326 #define CNSADM_BYTES_MAX 2000 /* XXX nasty fixed size */
327
328 static void
bind_consadm_conf(char * path)329 bind_consadm_conf(char *path)
330 {
331 struct vattr vattr;
332 vnode_t *vp;
333 void *buf;
334 size_t size;
335 ssize_t resid;
336 int err = 0;
337
338 if (vn_open(path, UIO_SYSSPACE, FREAD, 0, &vp, 0, 0) != 0)
339 return;
340 vattr.va_mask = AT_SIZE;
341 if ((err = VOP_GETATTR(vp, &vattr, 0, kcred, NULL)) != 0) {
342 cmn_err(CE_WARN, "sysmsg: getattr: '%s': error %d",
343 path, err);
344 goto closevp;
345 }
346
347 size = vattr.va_size > CNSADM_BYTES_MAX ?
348 CNSADM_BYTES_MAX : (ssize_t)vattr.va_size;
349 buf = kmem_alloc(size, KM_SLEEP);
350
351 if ((err = vn_rdwr(UIO_READ, vp, buf, size, (offset_t)0,
352 UIO_SYSSPACE, 0, (rlim64_t)0, kcred, &resid)) != 0)
353 cmn_err(CE_WARN, "sysmsg: vn_rdwr: '%s': error %d",
354 path, err);
355 else
356 parse_buffer(buf, size - resid);
357
358 kmem_free(buf, size);
359 closevp:
360 (void) VOP_CLOSE(vp, FREAD, 1, (offset_t)0, kcred, NULL);
361 VN_RELE(vp);
362 }
363
364 /* ARGSUSED */
365 static int
sysmopen(dev_t * dev,int flag,int state,cred_t * cred)366 sysmopen(dev_t *dev, int flag, int state, cred_t *cred)
367 {
368 int i;
369 vnode_t *vp;
370 minor_t instance;
371 static boolean_t initialized;
372
373 instance = getminor(*dev);
374
375 if (state != OTYP_CHR || (instance != 0 && instance != 1))
376 return (ENXIO);
377
378 mutex_enter(&dcvp_mutex);
379 if ((dcvp == NULL) && (vn_open("/dev/console",
380 UIO_SYSSPACE, FWRITE, 0, &dcvp, 0, 0) != 0)) {
381 mutex_exit(&dcvp_mutex);
382 return (ENXIO);
383 }
384
385 if (instance == SYS_SYSMIN)
386 sysmsg_opened = B_TRUE;
387 else
388 msglog_opened = B_TRUE;
389
390 if (!initialized) {
391 bind_consadm_conf("/etc/consadm.conf");
392 initialized = B_TRUE;
393 }
394 mutex_exit(&dcvp_mutex);
395
396 for (i = 0; i < MAXDEVS; i++) {
397 rw_enter(&sysmcache[i].dca_lock, RW_WRITER);
398 if ((sysmcache[i].dca_flags & SYSM_ENABLED) &&
399 sysmcache[i].dca_vp == NULL) {
400 /*
401 * 4196476 - FTRUNC was causing E10K to return EINVAL
402 * on open
403 */
404 flag = flag & ~FTRUNC;
405 /*
406 * Open failures on the auxiliary consoles are
407 * not returned because we don't care if some
408 * subset get an error. We know the default console
409 * is okay, and preserve the semantics of the
410 * open for the default console.
411 * Set NONBLOCK|NDELAY in case there's no carrier.
412 */
413 if (vn_open(sysmcache[i].dca_name, UIO_SYSSPACE,
414 flag | FNONBLOCK | FNDELAY, 0, &vp, 0, 0) == 0)
415 sysmcache[i].dca_vp = vp;
416 }
417 rw_exit(&sysmcache[i].dca_lock);
418 }
419
420 return (0);
421 }
422
423 /* ARGSUSED */
424 static int
sysmclose(dev_t dev,int flag,int state,cred_t * cred)425 sysmclose(dev_t dev, int flag, int state, cred_t *cred)
426 {
427 int i;
428 minor_t instance;
429
430 ASSERT(dcvp != NULL);
431
432 if (state != OTYP_CHR)
433 return (ENXIO);
434
435 instance = getminor(dev);
436
437 mutex_enter(&dcvp_mutex);
438 if (instance == SYS_SYSMIN)
439 sysmsg_opened = B_FALSE;
440 else
441 msglog_opened = B_FALSE;
442
443 if (sysmsg_opened || msglog_opened) {
444 mutex_exit(&dcvp_mutex);
445 return (0);
446 }
447
448 (void) VOP_CLOSE(dcvp, FWRITE, 1, (offset_t)0, kcred, NULL);
449 VN_RELE(dcvp);
450 dcvp = NULL;
451 mutex_exit(&dcvp_mutex);
452
453 /*
454 * Close the auxiliary consoles, we're not concerned with
455 * passing up the errors.
456 */
457 for (i = 0; i < MAXDEVS; i++) {
458 rw_enter(&sysmcache[i].dca_lock, RW_WRITER);
459 if (sysmcache[i].dca_vp != NULL) {
460 (void) VOP_CLOSE(sysmcache[i].dca_vp, flag,
461 1, (offset_t)0, cred, NULL);
462 VN_RELE(sysmcache[i].dca_vp);
463 sysmcache[i].dca_vp = NULL;
464 }
465 rw_exit(&sysmcache[i].dca_lock);
466 }
467
468 return (0);
469 }
470
471 /* Reads occur only on the default console */
472
473 /* ARGSUSED */
474 static int
sysmread(dev_t dev,struct uio * uio,cred_t * cred)475 sysmread(dev_t dev, struct uio *uio, cred_t *cred)
476 {
477 ASSERT(dcvp != NULL);
478 return (VOP_READ(dcvp, uio, 0, cred, NULL));
479 }
480
481 /* ARGSUSED */
482 static int
sysmwrite(dev_t dev,struct uio * uio,cred_t * cred)483 sysmwrite(dev_t dev, struct uio *uio, cred_t *cred)
484 {
485 int i = 0;
486 iovec_t uio_iov;
487 struct uio tuio;
488
489 ASSERT(dcvp != NULL);
490 ASSERT(uio != NULL);
491
492 for (i = 0; i < MAXDEVS; i++) {
493 rw_enter(&sysmcache[i].dca_lock, RW_READER);
494 if (sysmcache[i].dca_vp != NULL &&
495 (sysmcache[i].dca_flags & SYSM_ENABLED)) {
496 tuio = *uio;
497 uio_iov = *(uio->uio_iov);
498 tuio.uio_iov = &uio_iov;
499 (void) VOP_WRITE(sysmcache[i].dca_vp, &tuio, 0, cred,
500 NULL);
501 }
502 rw_exit(&sysmcache[i].dca_lock);
503 }
504 return (VOP_WRITE(dcvp, uio, 0, cred, NULL));
505 }
506
507 /* ARGSUSED */
508 static int
sysmioctl(dev_t dev,int cmd,intptr_t arg,int flag,cred_t * cred,int * rvalp)509 sysmioctl(dev_t dev, int cmd, intptr_t arg, int flag, cred_t *cred, int *rvalp)
510 {
511 int rval = 0;
512 int error = 0;
513 size_t size = 0;
514 int i;
515 char *infop;
516 char found = 0;
517 dev_t newdevt = (dev_t)NODEV; /* because 0 == /dev/console */
518 vnode_t *vp;
519
520 switch (cmd) {
521 case CIOCGETCONSOLE:
522 /* Sum over the number of enabled devices */
523 for (i = 0; i < MAXDEVS; i++) {
524 if (sysmcache[i].dca_flags & SYSM_ENABLED)
525 /* list is space separated, followed by NULL */
526 size += strlen(sysmcache[i].dca_name) + 1;
527 }
528 if (size == 0)
529 return (0);
530 break;
531 case CIOCSETCONSOLE:
532 case CIOCRMCONSOLE:
533 size = sizeof (sysmcache[0].dca_name);
534 break;
535 case CIOCTTYCONSOLE:
536 {
537 dev_t d;
538 dev32_t d32;
539 extern dev_t rwsconsdev, rconsdev, uconsdev;
540 proc_t *p;
541
542 if (drv_getparm(UPROCP, &p) != 0)
543 return (ENODEV);
544 else
545 d = cttydev(p);
546 /*
547 * If the controlling terminal is the real
548 * or workstation console device, map to what the
549 * user thinks is the console device.
550 */
551 if (d == rwsconsdev || d == rconsdev)
552 d = uconsdev;
553 if ((flag & FMODELS) != FNATIVE) {
554 if (!cmpldev(&d32, d))
555 return (EOVERFLOW);
556 if (ddi_copyout(&d32, (caddr_t)arg, sizeof (d32),
557 flag))
558 return (EFAULT);
559 } else {
560 if (ddi_copyout(&d, (caddr_t)arg, sizeof (d), flag))
561 return (EFAULT);
562 }
563 return (0);
564 }
565 default:
566 /* everything else is sent to the console device */
567 return (VOP_IOCTL(dcvp, cmd, arg, flag, cred, rvalp, NULL));
568 }
569
570 if ((rval = secpolicy_console(cred)) != 0)
571 return (EPERM);
572
573 infop = kmem_alloc(size, KM_SLEEP);
574 if (flag & FKIOCTL)
575 error = copystr((caddr_t)arg, infop, size, NULL);
576 else
577 error = copyinstr((caddr_t)arg, infop, size, NULL);
578
579 if (error) {
580 switch (cmd) {
581 case CIOCGETCONSOLE:
582 /*
583 * If the buffer is null, then return a byte count
584 * to user land.
585 */
586 *rvalp = size;
587 goto err_exit;
588 default:
589 rval = EFAULT;
590 goto err_exit;
591 }
592 }
593
594 if (infop[0] != '\0') {
595 if ((rval = lookupname(infop, UIO_SYSSPACE, FOLLOW,
596 NULLVPP, &vp)) == 0) {
597 if (vp->v_type != VCHR) {
598 VN_RELE(vp);
599 rval = EINVAL;
600 goto err_exit;
601 }
602 newdevt = vp->v_rdev;
603 VN_RELE(vp);
604 } else
605 goto err_exit;
606 }
607
608 switch (cmd) {
609 case CIOCGETCONSOLE:
610 /*
611 * Return the list of device names that are enabled.
612 */
613 for (i = 0; i < MAXDEVS; i++) {
614 rw_enter(&sysmcache[i].dca_lock, RW_READER);
615 if (sysmcache[i].dca_flags & SYSM_ENABLED) {
616 if (infop[0] != '\0')
617 (void) strcat(infop, " ");
618 (void) strcat(infop, sysmcache[i].dca_name);
619 }
620 rw_exit(&sysmcache[i].dca_lock);
621 }
622 if (rval == 0 && copyoutstr(infop, (void *)arg, size, NULL))
623 rval = EFAULT;
624 break;
625
626 case CIOCSETCONSOLE:
627 if ((rval = checkarg(newdevt)) != 0)
628 break;
629 /*
630 * The device does not have to be open or disabled to
631 * perform the set console.
632 */
633 for (i = 0; i < MAXDEVS; i++) {
634 rw_enter(&sysmcache[i].dca_lock, RW_WRITER);
635 if (sysmcache[i].dca_devt == newdevt &&
636 (sysmcache[i].dca_flags & SYSM_ENABLED)) {
637 (void) strcpy(sysmcache[i].dca_name, infop);
638 rval = EEXIST;
639 rw_exit(&sysmcache[i].dca_lock);
640 break;
641 } else if (sysmcache[i].dca_devt == newdevt &&
642 sysmcache[i].dca_flags == SYSM_DISABLED) {
643 sysmcache[i].dca_flags |= SYSM_ENABLED;
644 (void) strcpy(sysmcache[i].dca_name, infop);
645 rw_exit(&sysmcache[i].dca_lock);
646 found = 1;
647 break;
648 } else if (sysmcache[i].dca_devt == 0) {
649 ASSERT(sysmcache[i].dca_vp == NULL &&
650 sysmcache[i].dca_flags == SYSM_DISABLED);
651 (void) strcpy(sysmcache[i].dca_name, infop);
652 sysmcache[i].dca_flags = SYSM_ENABLED;
653 sysmcache[i].dca_devt = newdevt;
654 rw_exit(&sysmcache[i].dca_lock);
655 found = 1;
656 break;
657 }
658 rw_exit(&sysmcache[i].dca_lock);
659 }
660 if (found == 0 && rval == 0)
661 rval = ENOENT;
662 break;
663
664 case CIOCRMCONSOLE:
665 for (i = 0; i < MAXDEVS; i++) {
666 rw_enter(&sysmcache[i].dca_lock, RW_WRITER);
667 if (sysmcache[i].dca_devt == newdevt) {
668 sysmcache[i].dca_flags = SYSM_DISABLED;
669 sysmcache[i].dca_name[0] = '\0';
670 rw_exit(&sysmcache[i].dca_lock);
671 found = 1;
672 break;
673 }
674 rw_exit(&sysmcache[i].dca_lock);
675 }
676 if (found == 0)
677 rval = ENOENT;
678 break;
679
680 default:
681 break;
682 }
683
684 err_exit:
685 kmem_free(infop, size);
686 return (rval);
687 }
688
689 /* As with the read, we poll only the default console */
690
691 /* ARGSUSED */
692 static int
sysmpoll(dev_t dev,short events,int anyyet,short * reventsp,struct pollhead ** phpp)693 sysmpoll(dev_t dev, short events, int anyyet, short *reventsp,
694 struct pollhead **phpp)
695 {
696 return (VOP_POLL(dcvp, events, anyyet, reventsp, phpp, NULL));
697 }
698
699 /* Sanity check that the device is good */
700 static int
checkarg(dev_t devt)701 checkarg(dev_t devt)
702 {
703 int rval = 0;
704 dev_t sysmsg_dev, msglog_dev;
705 extern dev_t rwsconsdev, rconsdev, uconsdev;
706
707 if (devt == rconsdev || devt == rwsconsdev || devt == uconsdev) {
708 rval = EBUSY;
709 } else {
710 sysmsg_dev = makedevice(ddi_driver_major(sysm_dip), SYS_SYSMIN);
711 msglog_dev = makedevice(ddi_driver_major(sysm_dip), SYS_MSGMIN);
712 if (devt == sysmsg_dev || devt == msglog_dev)
713 rval = EINVAL;
714 }
715
716 return (rval);
717 }
718