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