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 2004 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/stream.h> 52 #include <sys/strsubr.h> 53 #include <sys/poll.h> 54 #include <sys/debug.h> 55 #include <sys/sysmsg_impl.h> 56 #include <sys/conf.h> 57 #include <sys/termios.h> 58 #include <sys/errno.h> 59 #include <sys/modctl.h> 60 #include <sys/pathname.h> 61 #include <sys/ddi.h> 62 #include <sys/sunddi.h> 63 #include <sys/consdev.h> 64 #include <sys/policy.h> 65 66 /* 67 * internal functions 68 */ 69 static int sysmopen(dev_t *, int, int, cred_t *); 70 static int sysmclose(dev_t, int, int, cred_t *); 71 static int sysmread(dev_t, struct uio *, cred_t *); 72 static int sysmwrite(dev_t, struct uio *, cred_t *); 73 static int sysmioctl(dev_t, int, intptr_t, int, cred_t *, int *); 74 static int sysmpoll(dev_t, short, int, short *, struct pollhead **); 75 static int sysm_info(dev_info_t *, ddi_info_cmd_t, void *, void **); 76 static int sysm_attach(dev_info_t *, ddi_attach_cmd_t); 77 static int sysm_detach(dev_info_t *, ddi_detach_cmd_t); 78 static void bind_consadm_conf(char *); 79 static int checkarg(dev_t); 80 81 static dev_info_t *sysm_dip; /* private copy of devinfo pointer */ 82 83 static struct cb_ops sysm_cb_ops = { 84 85 sysmopen, /* open */ 86 sysmclose, /* close */ 87 nodev, /* strategy */ 88 nodev, /* print */ 89 nodev, /* dump */ 90 sysmread, /* read */ 91 sysmwrite, /* write */ 92 sysmioctl, /* ioctl */ 93 nodev, /* devmap */ 94 nodev, /* mmap */ 95 nodev, /* segmap */ 96 sysmpoll, /* poll */ 97 ddi_prop_op, /* cb_prop_op */ 98 NULL, /* streamtab */ 99 D_NEW | D_MP, /* Driver compatibility flag */ 100 CB_REV, /* cb_rev */ 101 nodev, /* aread */ 102 nodev /* awrite */ 103 }; 104 105 static struct dev_ops sysm_ops = { 106 107 DEVO_REV, /* devo_rev, */ 108 0, /* refcnt */ 109 sysm_info, /* info */ 110 nulldev, /* identify */ 111 nulldev, /* probe */ 112 sysm_attach, /* attach */ 113 sysm_detach, /* detach */ 114 nodev, /* reset */ 115 &sysm_cb_ops, /* driver operations */ 116 (struct bus_ops *)0, /* bus operations */ 117 nulldev /* power */ 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 148 /* flags for device cache */ 149 #define SYSM_DISABLED 0x0 150 #define SYSM_ENABLED 0x1 151 152 /* 153 * Module linkage information for the kernel. 154 */ 155 156 static struct modldrv modldrv = { 157 &mod_driverops, /* Type of module. This one is a pseudo driver */ 158 "System message redirection (fanout) driver %I%", 159 &sysm_ops, /* driver ops */ 160 }; 161 162 static struct modlinkage modlinkage = { 163 MODREV_1, 164 &modldrv, 165 NULL 166 }; 167 168 int 169 _init(void) 170 { 171 return (mod_install(&modlinkage)); 172 } 173 174 int 175 _fini(void) 176 { 177 return (mod_remove(&modlinkage)); 178 } 179 180 int 181 _info(struct modinfo *modinfop) 182 { 183 return (mod_info(&modlinkage, modinfop)); 184 } 185 186 /* 187 * DDI glue routines 188 */ 189 static int 190 sysm_attach(dev_info_t *devi, ddi_attach_cmd_t cmd) 191 { 192 int i; 193 194 switch (cmd) { 195 case DDI_ATTACH: 196 ASSERT(sysm_dip == NULL); 197 198 if (ddi_create_minor_node(devi, "sysmsg", S_IFCHR, 199 SYS_SYSMIN, DDI_PSEUDO, NULL) == DDI_FAILURE || 200 ddi_create_minor_node(devi, "msglog", S_IFCHR, 201 SYS_MSGMIN, DDI_PSEUDO, NULL) == DDI_FAILURE) { 202 ddi_remove_minor_node(devi, NULL); 203 return (DDI_FAILURE); 204 } 205 206 for (i = 0; i < MAXDEVS; i++) { 207 rw_init(&sysmcache[i].dca_lock, NULL, RW_DRIVER, NULL); 208 } 209 210 /* set everything up .. */ 211 bind_consadm_conf("/etc/consadm.conf"); 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 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 if (dcvp) { 232 (void) VOP_CLOSE(dcvp, FWRITE, 1, (offset_t)0, kcred); 233 VN_RELE(dcvp); 234 dcvp = NULL; 235 } 236 for (i = 0; i < MAXDEVS; i++) { 237 if (sysmcache[i].dca_vp != NULL) { 238 (void) VOP_CLOSE(sysmcache[i].dca_vp, 0, 239 1, (offset_t)0, 0); 240 VN_RELE(sysmcache[i].dca_vp); 241 } 242 sysmcache[i].dca_vp = NULL; 243 rw_destroy(&sysmcache[i].dca_lock); 244 } 245 246 ddi_remove_minor_node(devi, NULL); 247 sysm_dip = NULL; 248 return (DDI_SUCCESS); 249 250 case DDI_SUSPEND: 251 case DDI_PM_SUSPEND: 252 return (DDI_SUCCESS); 253 default: 254 return (DDI_FAILURE); 255 } 256 257 } 258 259 /* ARGSUSED */ 260 static int 261 sysm_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result) 262 { 263 int rval = DDI_FAILURE; 264 minor_t instance; 265 266 instance = getminor((dev_t)arg); 267 268 switch (infocmd) { 269 case DDI_INFO_DEVT2DEVINFO: 270 if (sysm_dip != NULL && 271 (instance == SYS_SYSMIN || instance == SYS_MSGMIN)) { 272 *result = sysm_dip; 273 rval = DDI_SUCCESS; 274 } 275 break; 276 277 case DDI_INFO_DEVT2INSTANCE: 278 if (instance == SYS_SYSMIN || instance == SYS_MSGMIN) { 279 *result = NULL; 280 rval = DDI_SUCCESS; 281 } 282 break; 283 284 default: 285 break; 286 } 287 288 return (rval); 289 } 290 291 /* 292 * Parse the contents of the buffer, and bind the named 293 * devices as auxiliary consoles using our own ioctl routine. 294 * 295 * Comments begin with '#' and are terminated only by a newline 296 * Device names begin with a '/', and are terminated by a newline, 297 * space, '#' or tab. 298 */ 299 static void 300 parse_buffer(char *buf, ssize_t fsize) 301 { 302 char *ebuf = buf + fsize; 303 char *devname = NULL; 304 int eatcomments = 0; 305 306 while (buf < ebuf) { 307 if (eatcomments) { 308 if (*buf++ == '\n') 309 eatcomments = 0; 310 continue; 311 } 312 switch (*buf) { 313 case '/': 314 if (devname == NULL) 315 devname = buf; 316 break; 317 case '#': 318 eatcomments = 1; 319 /*FALLTHROUGH*/ 320 case ' ': 321 case '\t': 322 case '\n': 323 *buf = '\0'; 324 if (devname == NULL) 325 break; 326 (void) sysmioctl(NODEV, CIOCSETCONSOLE, 327 (intptr_t)devname, FNATIVE|FKIOCTL|FREAD|FWRITE, 328 kcred, NULL); 329 devname = NULL; 330 break; 331 default: 332 break; 333 } 334 buf++; 335 } 336 } 337 338 #define CNSADM_BYTES_MAX 2000 /* XXX nasty fixed size */ 339 340 static void 341 bind_consadm_conf(char *path) 342 { 343 struct vattr vattr; 344 vnode_t *vp; 345 void *buf; 346 size_t size; 347 ssize_t resid; 348 int err = 0; 349 350 if (vn_open(path, UIO_SYSSPACE, FREAD, 0, &vp, 0, 0) != 0) 351 return; 352 vattr.va_mask = AT_SIZE; 353 if ((err = VOP_GETATTR(vp, &vattr, 0, kcred)) != 0) { 354 cmn_err(CE_WARN, "sysmsg: getattr: '%s': error %d", 355 path, err); 356 goto closevp; 357 } 358 359 size = vattr.va_size > CNSADM_BYTES_MAX ? 360 CNSADM_BYTES_MAX : (ssize_t)vattr.va_size; 361 buf = kmem_alloc(size, KM_SLEEP); 362 363 if ((err = vn_rdwr(UIO_READ, vp, buf, size, (offset_t)0, 364 UIO_SYSSPACE, 0, (rlim64_t)0, kcred, &resid)) != 0) 365 cmn_err(CE_WARN, "sysmsg: vn_rdwr: '%s': error %d", 366 path, err); 367 else 368 parse_buffer(buf, size - resid); 369 370 kmem_free(buf, size); 371 closevp: 372 (void) VOP_CLOSE(vp, FREAD, 1, (offset_t)0, kcred); 373 VN_RELE(vp); 374 } 375 376 /* ARGSUSED */ 377 static int 378 sysmopen(dev_t *dev, int flag, int state, cred_t *cred) 379 { 380 int i; 381 vnode_t *vp; 382 minor_t instance; 383 384 instance = getminor(*dev); 385 386 if (state != OTYP_CHR || (instance != 0 && instance != 1)) 387 return (ENXIO); 388 389 mutex_enter(&dcvp_mutex); 390 if ((dcvp == NULL) && (vn_open("/dev/console", 391 UIO_SYSSPACE, FWRITE, 0, &dcvp, 0, 0) != 0)) { 392 mutex_exit(&dcvp_mutex); 393 return (ENXIO); 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 430 if (state != OTYP_CHR) 431 return (ENXIO); 432 433 /* 434 * Close the auxiliary consoles, we're not concerned with 435 * passing up the errors. 436 */ 437 for (i = 0; i < MAXDEVS; i++) { 438 rw_enter(&sysmcache[i].dca_lock, RW_WRITER); 439 if (sysmcache[i].dca_vp != NULL) { 440 (void) VOP_CLOSE(sysmcache[i].dca_vp, flag, 441 1, (offset_t)0, cred); 442 VN_RELE(sysmcache[i].dca_vp); 443 sysmcache[i].dca_vp = NULL; 444 } 445 rw_exit(&sysmcache[i].dca_lock); 446 } 447 448 return (0); 449 } 450 451 /* Reads occur only on the default console */ 452 453 /* ARGSUSED */ 454 static int 455 sysmread(dev_t dev, struct uio *uio, cred_t *cred) 456 { 457 ASSERT(dcvp != NULL); 458 return (VOP_READ(dcvp, uio, 0, cred, NULL)); 459 } 460 461 /* ARGSUSED */ 462 static int 463 sysmwrite(dev_t dev, struct uio *uio, cred_t *cred) 464 { 465 int i = 0; 466 iovec_t uio_iov; 467 struct uio tuio; 468 469 ASSERT(dcvp != NULL); 470 ASSERT(uio != NULL); 471 472 for (i = 0; i < MAXDEVS; i++) { 473 rw_enter(&sysmcache[i].dca_lock, RW_READER); 474 if (sysmcache[i].dca_vp != NULL && 475 (sysmcache[i].dca_flags & SYSM_ENABLED)) { 476 tuio = *uio; 477 uio_iov = *(uio->uio_iov); 478 tuio.uio_iov = &uio_iov; 479 (void) VOP_WRITE(sysmcache[i].dca_vp, &tuio, 0, cred, 480 NULL); 481 } 482 rw_exit(&sysmcache[i].dca_lock); 483 } 484 return (VOP_WRITE(dcvp, uio, 0, cred, NULL)); 485 } 486 487 /* ARGSUSED */ 488 static int 489 sysmioctl(dev_t dev, int cmd, intptr_t arg, int flag, cred_t *cred, int *rvalp) 490 { 491 int rval = 0; 492 int error = 0; 493 size_t size = 0; 494 int i; 495 char *infop; 496 char found = 0; 497 dev_t newdevt = (dev_t)NODEV; /* because 0 == /dev/console */ 498 vnode_t *vp; 499 500 switch (cmd) { 501 case CIOCGETCONSOLE: 502 /* Sum over the number of enabled devices */ 503 for (i = 0; i < MAXDEVS; i++) { 504 if (sysmcache[i].dca_flags & SYSM_ENABLED) 505 /* list is space separated, followed by NULL */ 506 size += strlen(sysmcache[i].dca_name) + 1; 507 } 508 if (size == 0) 509 return (0); 510 break; 511 case CIOCSETCONSOLE: 512 case CIOCRMCONSOLE: 513 size = sizeof (sysmcache[0].dca_name); 514 break; 515 case CIOCTTYCONSOLE: 516 { 517 dev_t d; 518 dev32_t d32; 519 extern dev_t rwsconsdev, rconsdev, uconsdev; 520 proc_t *p; 521 522 if (drv_getparm(UPROCP, &p) != 0) 523 return (ENODEV); 524 else 525 d = cttydev(p); 526 /* 527 * If the controlling terminal is the real 528 * or workstation console device, map to what the 529 * user thinks is the console device. 530 */ 531 if (d == rwsconsdev || d == rconsdev) 532 d = uconsdev; 533 if ((flag & FMODELS) != FNATIVE) { 534 if (!cmpldev(&d32, d)) 535 return (EOVERFLOW); 536 if (ddi_copyout(&d32, (caddr_t)arg, sizeof (d32), 537 flag)) 538 return (EFAULT); 539 } else { 540 if (ddi_copyout(&d, (caddr_t)arg, sizeof (d), flag)) 541 return (EFAULT); 542 } 543 return (0); 544 } 545 default: 546 /* everything else is sent to the console device */ 547 return (VOP_IOCTL(dcvp, cmd, arg, flag, cred, rvalp)); 548 } 549 550 if ((rval = secpolicy_console(cred)) != 0) 551 return (EPERM); 552 553 infop = kmem_alloc(size, KM_SLEEP); 554 if (flag & FKIOCTL) 555 error = copystr((caddr_t)arg, infop, size, NULL); 556 else 557 error = copyinstr((caddr_t)arg, infop, size, NULL); 558 559 if (error) { 560 switch (cmd) { 561 case CIOCGETCONSOLE: 562 /* 563 * If the buffer is null, then return a byte count 564 * to user land. 565 */ 566 *rvalp = size; 567 goto err_exit; 568 default: 569 rval = EFAULT; 570 goto err_exit; 571 } 572 } 573 574 if (infop[0] != NULL) { 575 if ((rval = lookupname(infop, UIO_SYSSPACE, FOLLOW, 576 NULLVPP, &vp)) == 0) { 577 if (vp->v_type != VCHR) { 578 VN_RELE(vp); 579 rval = EINVAL; 580 goto err_exit; 581 } 582 newdevt = vp->v_rdev; 583 VN_RELE(vp); 584 } else 585 goto err_exit; 586 } 587 588 switch (cmd) { 589 case CIOCGETCONSOLE: 590 /* 591 * Return the list of device names that are enabled. 592 */ 593 for (i = 0; i < MAXDEVS; i++) { 594 rw_enter(&sysmcache[i].dca_lock, RW_READER); 595 if (sysmcache[i].dca_flags & SYSM_ENABLED) { 596 if (infop[0] != NULL) 597 (void) strcat(infop, " "); 598 (void) strcat(infop, sysmcache[i].dca_name); 599 } 600 rw_exit(&sysmcache[i].dca_lock); 601 } 602 if (rval == 0 && copyoutstr(infop, (void *)arg, size, NULL)) 603 rval = EFAULT; 604 break; 605 606 case CIOCSETCONSOLE: 607 if ((rval = checkarg(newdevt)) != 0) 608 break; 609 /* 610 * The device does not have to be open or disabled to 611 * perform the set console. 612 */ 613 for (i = 0; i < MAXDEVS; i++) { 614 rw_enter(&sysmcache[i].dca_lock, RW_WRITER); 615 if (sysmcache[i].dca_devt == newdevt && 616 (sysmcache[i].dca_flags & SYSM_ENABLED)) { 617 (void) strcpy(sysmcache[i].dca_name, infop); 618 rval = EEXIST; 619 rw_exit(&sysmcache[i].dca_lock); 620 break; 621 } else if (sysmcache[i].dca_devt == newdevt && 622 sysmcache[i].dca_flags == SYSM_DISABLED) { 623 sysmcache[i].dca_flags |= SYSM_ENABLED; 624 (void) strcpy(sysmcache[i].dca_name, infop); 625 rw_exit(&sysmcache[i].dca_lock); 626 found = 1; 627 break; 628 } else if (sysmcache[i].dca_devt == 0) { 629 ASSERT(sysmcache[i].dca_vp == NULL && 630 sysmcache[i].dca_flags == SYSM_DISABLED); 631 (void) strcpy(sysmcache[i].dca_name, infop); 632 sysmcache[i].dca_flags = SYSM_ENABLED; 633 sysmcache[i].dca_devt = newdevt; 634 rw_exit(&sysmcache[i].dca_lock); 635 found = 1; 636 break; 637 } 638 rw_exit(&sysmcache[i].dca_lock); 639 } 640 if (found == 0 && rval == 0) 641 rval = ENOENT; 642 break; 643 644 case CIOCRMCONSOLE: 645 for (i = 0; i < MAXDEVS; i++) { 646 rw_enter(&sysmcache[i].dca_lock, RW_WRITER); 647 if (sysmcache[i].dca_devt == newdevt) { 648 sysmcache[i].dca_flags = SYSM_DISABLED; 649 sysmcache[i].dca_name[0] = '\0'; 650 rw_exit(&sysmcache[i].dca_lock); 651 found = 1; 652 break; 653 } 654 rw_exit(&sysmcache[i].dca_lock); 655 } 656 if (found == 0) 657 rval = ENOENT; 658 break; 659 660 default: 661 break; 662 } 663 664 err_exit: 665 kmem_free(infop, size); 666 return (rval); 667 } 668 669 /* As with the read, we poll only the default console */ 670 671 /* ARGSUSED */ 672 static int 673 sysmpoll(dev_t dev, short events, int anyyet, short *reventsp, 674 struct pollhead **phpp) 675 { 676 return (VOP_POLL(dcvp, events, anyyet, reventsp, phpp)); 677 } 678 679 /* Sanity check that the device is good */ 680 static int 681 checkarg(dev_t devt) 682 { 683 int rval = 0; 684 vnode_t *vp; 685 extern dev_t rwsconsdev, rconsdev, uconsdev; 686 687 if (devt == rconsdev || devt == rwsconsdev || devt == uconsdev) { 688 rval = EBUSY; 689 goto err_exit; 690 } 691 if ((rval = lookupname("/dev/sysmsg", UIO_SYSSPACE, FOLLOW, 692 NULLVPP, &vp)) == 0) { 693 if (devt == vp->v_rdev) { 694 VN_RELE(vp); 695 rval = EINVAL; 696 goto err_exit; 697 } 698 VN_RELE(vp); 699 } 700 if ((rval = lookupname("/dev/msglog", UIO_SYSSPACE, FOLLOW, 701 NULLVPP, &vp)) == 0) { 702 if (devt == vp->v_rdev) { 703 VN_RELE(vp); 704 rval = EINVAL; 705 goto err_exit; 706 } 707 VN_RELE(vp); 708 } 709 710 err_exit: 711 return (rval); 712 } 713