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 }