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