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 * This code implements the Starcat Virtual Console host daemon (see cvcd(1M)). 31 * It accepts one TCP connection at a time on a well-known port. Once a 32 * connection is accepted, the console redirection driver (cvcdredir(7D)) is 33 * opened, and console I/O is routed back and forth between the two file 34 * descriptors (network and redirection driver). No security is provided or 35 * enforced within the daemon, as the Starcat platform uses a network security 36 * solution that is transparent to domain-side daemons. 37 */ 38 39 #include <stdio.h> 40 #include <stdarg.h> 41 #include <stdlib.h> 42 #include <string.h> 43 #include <strings.h> 44 45 #include <fcntl.h> 46 #include <sys/filio.h> /* Just to get FIONBIO... */ 47 #include <unistd.h> 48 #include <errno.h> 49 #include <stropts.h> 50 #include <signal.h> 51 #include <syslog.h> 52 #include <sys/utsname.h> 53 #include <sys/stat.h> 54 #include <locale.h> 55 #include <limits.h> 56 57 #include <sys/priocntl.h> 58 #include <sys/tspriocntl.h> 59 #include <sys/rtpriocntl.h> 60 61 #include <netdb.h> 62 #include <sys/socket.h> 63 #include <tiuser.h> 64 65 #include <sys/sc_cvcio.h> 66 67 68 /* 69 * Misc. defines. 70 */ 71 #define NODENAME "/etc/nodename" 72 #define NETWORK_PFD 0 73 #define REDIR_PFD 1 74 #define LISTEN_PFD 2 75 #define NUM_PFDS 3 76 77 /* 78 * Function prototypes 79 */ 80 static void cvcd_set_priority(void); 81 static int cvcd_init_host_socket(int port); 82 static void cvcd_do_network_console(void); 83 static void cvcd_err(int code, char *format, ...); 84 static void cvcd_usage(void); 85 86 /* 87 * Globals 88 */ 89 static struct pollfd pfds[NUM_PFDS]; 90 static char progname[MAXPATHLEN]; 91 static int debug = 0; 92 93 main(int argc, char **argv) 94 { 95 int err; 96 int opt; 97 int tport = 0; 98 char *hostname; 99 struct utsname utsname; 100 int fd; 101 int i; 102 struct servent *se; 103 char prefix[256]; 104 105 (void) setlocale(LC_ALL, ""); 106 (void) strcpy(progname, argv[0]); 107 108 #ifdef DEBUG 109 while ((opt = getopt(argc, argv, "dp:")) != EOF) { 110 #else 111 while ((opt = getopt(argc, argv, "")) != EOF) { 112 #endif 113 switch (opt) { 114 #ifdef DEBUG 115 case 'd' : debug = 1; 116 break; 117 118 case 'p' : tport = atoi(optarg); 119 break; 120 #endif /* DEBUG */ 121 122 default : cvcd_usage(); 123 exit(1); 124 } 125 } 126 127 if (uname(&utsname) == -1) { 128 perror("HOSTNAME not defined"); 129 exit(1); 130 } 131 hostname = utsname.nodename; 132 133 /* 134 * hostname may still be NULL, depends on when cvcd was started 135 * in the boot sequence. If it is NULL, try one more time 136 * to get a hostname -> look in the /etc/nodename file. 137 */ 138 if (!strlen(hostname)) { 139 /* 140 * try to get the hostname from the /etc/nodename file 141 * we reuse the utsname.nodename buffer here! hostname 142 * already points to it. 143 */ 144 if ((fd = open(NODENAME, O_RDONLY)) > 0) { 145 if ((i = read(fd, utsname.nodename, SYS_NMLN)) <= 0) { 146 cvcd_err(LOG_WARNING, 147 "failed to acquire hostname"); 148 } else { 149 utsname.nodename[i-1] = '\0'; 150 } 151 (void) close(fd); 152 } 153 } 154 /* 155 * If all attempts to get the hostname have failed, put something 156 * meaningful in the buffer. 157 */ 158 if (!strlen(hostname)) { 159 (void) strcpy(utsname.nodename, "(unknown)"); 160 } 161 162 /* 163 * Must be root. 164 */ 165 if (debug == 0 && geteuid() != 0) { 166 fprintf(stderr, "cvcd: Must be root"); 167 exit(1); 168 } 169 170 /* 171 * Daemonize... 172 */ 173 if (debug == 0) { 174 for (i = 0; i < OPEN_MAX; i++) { 175 if (debug && (i == STDERR_FILENO)) { 176 /* Don't close stderr in debug mode! */ 177 continue; 178 } 179 (void) close(i); 180 } 181 (void) chdir("/"); 182 (void) umask(0); 183 if (fork() != 0) { 184 exit(0); 185 } 186 (void) setpgrp(); 187 (void) sprintf(prefix, "%s-(HOSTNAME:%s)", progname, hostname); 188 openlog(prefix, LOG_CONS | LOG_NDELAY, LOG_LOCAL0); 189 } 190 191 /* 192 * Initialize the array of pollfds used to track the listening socket, 193 * the connection to the console redirection driver, and the network 194 * connection. 195 */ 196 (void) memset((void *)pfds, 0, NUM_PFDS * sizeof (struct pollfd)); 197 for (i = 0; i < NUM_PFDS; i++) { 198 pfds[i].fd = -1; 199 } 200 201 /* SPR 94004 */ 202 (void) sigignore(SIGTERM); 203 204 /* 205 * SPR 83644: cvc and kadb are not compatible under heavy loads. 206 * Fix: will give cvcd highest TS priority at execution time. 207 */ 208 cvcd_set_priority(); 209 210 /* 211 * If not already determined by a command-line flag, figure out which 212 * port we're supposed to be listening on. 213 */ 214 if (tport == 0) { 215 if ((se = getservbyname(CVCD_SERVICE, "tcp")) == NULL) { 216 cvcd_err(LOG_ERR, "getservbyname(%s) not found", 217 CVCD_SERVICE); 218 exit(1); 219 } 220 tport = se->s_port; 221 } 222 223 if (debug == 1) { 224 cvcd_err(LOG_DEBUG, "tport = %d, debug = %d", tport, debug); 225 } 226 227 /* 228 * Attempt to initialize the socket we'll use to listen for incoming 229 * connections. No need to check the return value, as the call will 230 * exit if it fails. 231 */ 232 pfds[LISTEN_PFD].fd = cvcd_init_host_socket(tport); 233 234 /* 235 * Now that we're all set up, we loop forever waiting for connections 236 * (one at a time) and then driving network console activity over them. 237 */ 238 for (;;) { 239 /* 240 * Start by waiting for an incoming connection. 241 */ 242 do { 243 pfds[LISTEN_PFD].events = POLLIN; 244 err = poll(&(pfds[LISTEN_PFD]), 1, -1); 245 if (err == -1) { 246 cvcd_err(LOG_ERR, "poll: %s", strerror(errno)); 247 exit(1); 248 } 249 if ((err > 0) && 250 (pfds[LISTEN_PFD].revents & POLLIN)) { 251 fd = accept(pfds[LISTEN_PFD].fd, NULL, NULL); 252 if ((fd == -1) && (errno != EWOULDBLOCK)) { 253 cvcd_err(LOG_ERR, "accept: %s", 254 strerror(errno)); 255 exit(1); 256 } 257 } 258 } while (fd == -1); 259 260 /* 261 * We have a connection. Set the new socket nonblocking, and 262 * initialize the appropriate pollfd. In theory, the new socket 263 * is _already_ non-blocking because accept() is supposed to 264 * hand us a socket with the same properties as the socket we're 265 * listening on, but it won't hurt to make sure. 266 */ 267 opt = 1; 268 err = ioctl(fd, FIONBIO, &opt); 269 if (err == -1) { 270 cvcd_err(LOG_ERR, "ioctl: %s", strerror(errno)); 271 (void) close(fd); 272 continue; 273 } 274 pfds[NETWORK_PFD].fd = fd; 275 276 /* 277 * Since we're ready to do network console stuff, go ahead and 278 * open the Network Console redirection driver, which will 279 * switch traffic from the IOSRAM path to the network path if 280 * the network path has been selected in cvc. 281 */ 282 fd = open(CVCREDIR_DEV, O_RDWR|O_NDELAY); 283 if (fd == -1) { 284 cvcd_err(LOG_ERR, "open(redir): %s", strerror(errno)); 285 exit(1); 286 } 287 pfds[REDIR_PFD].fd = fd; 288 289 /* 290 * We have a network connection and we have the redirection 291 * driver open, so drive the network console until something 292 * changes. 293 */ 294 cvcd_do_network_console(); 295 296 /* 297 * cvcd_do_network_console doesn't return until there's a 298 * problem, so we need to close the network connection and the 299 * redirection driver and start the whole loop over again. 300 */ 301 (void) close(pfds[NETWORK_PFD].fd); 302 pfds[NETWORK_PFD].fd = -1; 303 (void) close(pfds[REDIR_PFD].fd); 304 pfds[REDIR_PFD].fd = -1; 305 } 306 307 /* NOTREACHED */ 308 return (1); 309 } 310 311 312 /* 313 * cvcd_set_priority 314 * 315 * DESCRIBE 316 * SPR 83644: cvc and kadb are not compatible under heavy loads. 317 * Fix: will give cvcd highest TS priority at execution time. 318 */ 319 static void 320 cvcd_set_priority(void) 321 { 322 id_t pid, tsID; 323 pcparms_t pcparms; 324 tsparms_t *tsparmsp; 325 short tsmaxpri; 326 pcinfo_t info; 327 328 pid = getpid(); 329 pcparms.pc_cid = PC_CLNULL; 330 tsparmsp = (tsparms_t *)pcparms.pc_clparms; 331 332 /* Get scheduler properties for this PID */ 333 if (priocntl(P_PID, pid, PC_GETPARMS, (caddr_t)&pcparms) == -1L) { 334 cvcd_err(LOG_ERR, "Warning: can't set priority."); 335 cvcd_err(LOG_ERR, "priocntl(GETPARMS): %s", strerror(errno)); 336 return; 337 } 338 339 /* Get class ID and maximum priority for TS process class */ 340 (void) strcpy(info.pc_clname, "TS"); 341 if (priocntl(0L, 0L, PC_GETCID, (caddr_t)&info) == -1L) { 342 cvcd_err(LOG_ERR, "Warning: can't set priority."); 343 cvcd_err(LOG_ERR, "priocntl(GETCID): %s", strerror(errno)); 344 return; 345 } 346 tsmaxpri = ((struct tsinfo *)info.pc_clinfo)->ts_maxupri; 347 tsID = info.pc_cid; 348 349 /* Print priority info in debug mode */ 350 if (debug) { 351 if (pcparms.pc_cid == tsID) { 352 cvcd_err(LOG_DEBUG, 353 "PID: %d, current priority: %d, Max priority: %d.", 354 pid, tsparmsp->ts_upri, tsmaxpri); 355 } 356 } 357 /* Change proc's priority to maxtspri */ 358 pcparms.pc_cid = tsID; 359 tsparmsp->ts_upri = tsmaxpri; 360 tsparmsp->ts_uprilim = tsmaxpri; 361 362 if (priocntl(P_PID, pid, PC_SETPARMS, (caddr_t)&pcparms) == -1L) { 363 cvcd_err(LOG_ERR, "Warning: can't set priority."); 364 cvcd_err(LOG_ERR, "priocntl(SETPARMS): %s", strerror(errno)); 365 } 366 367 /* Print new priority info in debug mode */ 368 if (debug) { 369 if (priocntl(P_PID, pid, PC_GETPARMS, (caddr_t)&pcparms) == 370 -1L) { 371 cvcd_err(LOG_ERR, "priocntl(GETPARMS): %s", 372 strerror(errno)); 373 } else { 374 cvcd_err(LOG_DEBUG, "PID: %d, new priority: %d.", pid, 375 tsparmsp->ts_upri); 376 } 377 } 378 } 379 380 381 /* 382 * cvcd_init_host_socket 383 * 384 * Given a TCP port number, create and initialize a socket appropriate for 385 * accepting incoming connections to that port. 386 */ 387 static int 388 cvcd_init_host_socket(int port) 389 { 390 int err; 391 int fd; 392 int optval; 393 int optlen = sizeof (optval); 394 struct sockaddr_in6 sin6; 395 396 /* 397 * Start by creating the socket, which needs to support IPv6. 398 */ 399 fd = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); 400 if (fd == -1) { 401 cvcd_err(LOG_ERR, "socket: %s", strerror(errno)); 402 exit(1); 403 } 404 405 /* 406 * Set the SO_REUSEADDR option, and make the socket non-blocking. 407 */ 408 optval = 1; 409 err = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &optval, optlen); 410 if (err == -1) { 411 cvcd_err(LOG_ERR, "setsockopt: %s", strerror(errno)); 412 exit(1); 413 } 414 415 err = ioctl(fd, FIONBIO, &optval); 416 if (err == -1) { 417 cvcd_err(LOG_ERR, "ioctl: %s", strerror(errno)); 418 exit(1); 419 } 420 421 /* 422 * Bind the socket to our local address and port. 423 */ 424 bzero(&sin6, sizeof (sin6)); 425 sin6.sin6_family = AF_INET6; 426 sin6.sin6_port = htons(port); 427 sin6.sin6_addr = in6addr_any; 428 err = bind(fd, (struct sockaddr *)&sin6, sizeof (sin6)); 429 if (err == -1) { 430 cvcd_err(LOG_ERR, "bind: %s", strerror(errno)); 431 exit(1); 432 } 433 434 /* 435 * Indicate that we want to accept connections on this socket. Since we 436 * only allow one connection at a time anyway, specify a maximum backlog 437 * of 1. 438 */ 439 err = listen(fd, 1); 440 if (err == -1) { 441 cvcd_err(LOG_ERR, "listen: %s", strerror(errno)); 442 exit(1); 443 } 444 445 return (fd); 446 } 447 448 449 /* 450 * cvcd_do_network_console 451 * 452 * With established connections to the network and the redirection driver, 453 * shuttle data between the two until something goes wrong. 454 */ 455 static void 456 cvcd_do_network_console(void) 457 { 458 int i; 459 int err; 460 int count; 461 short revents; 462 int input_len = 0; 463 int output_len = 0; 464 int input_off = 0; 465 int output_off = 0; 466 char input_buf[MAXPKTSZ]; 467 char output_buf[MAXPKTSZ]; 468 469 for (;;) { 470 /* 471 * Wait for activity on any of the open file descriptors, which 472 * includes the ability to write data if we have any to write. 473 * If poll() fails, break out of the network console processing 474 * loop. 475 */ 476 pfds[LISTEN_PFD].events = POLLIN; 477 pfds[NETWORK_PFD].events = POLLIN; 478 if (output_len != 0) { 479 pfds[NETWORK_PFD].events |= POLLOUT; 480 } 481 pfds[REDIR_PFD].events = POLLIN; 482 if (input_len != 0) { 483 pfds[REDIR_PFD].events |= POLLOUT; 484 } 485 err = poll(pfds, NUM_PFDS, -1); 486 if (err == -1) { 487 cvcd_err(LOG_ERR, "poll: %s", strerror(errno)); 488 break; 489 } 490 491 /* 492 * If any errors or hangups were detected, or one of our file 493 * descriptors is bad, bail out of the network console 494 * processing loop. 495 */ 496 for (i = 0; i < NUM_PFDS; i++) { 497 revents = pfds[i].revents; 498 if (revents & (POLLERR | POLLHUP | POLLNVAL)) { 499 cvcd_err(LOG_NOTICE, 500 "poll: status on %s fd:%s%s%s", 501 ((i == LISTEN_PFD) ? "listen" : 502 ((i == NETWORK_PFD) ? "network" : "redir")), 503 (revents & POLLERR) ? " error" : "", 504 (revents & POLLHUP) ? " hangup" : "", 505 (revents & POLLNVAL) ? " bad fd" : ""); 506 goto fail; /* 'break' wouldn't work here */ 507 } 508 } 509 510 /* 511 * Start by rejecting any connection attempts, since we only 512 * allow one network connection at a time. 513 */ 514 if (pfds[LISTEN_PFD].revents & POLLIN) { 515 int fd; 516 517 fd = accept(pfds[LISTEN_PFD].fd, NULL, NULL); 518 if (fd > 0) { 519 (void) close(fd); 520 } 521 } 522 523 /* 524 * If we have data waiting to be written in one direction or the 525 * other, go ahead and try to send the data on its way. We're 526 * going to attempt the writes regardless of whether the poll 527 * indicated that the destinations are ready, because we want to 528 * find out if either descriptor has a problem (e.g. broken 529 * network link). 530 * If an "unexpected" error is detected, give up and break out 531 * of the network console processing loop. 532 */ 533 if (output_len != 0) { 534 count = write(pfds[NETWORK_PFD].fd, 535 &(output_buf[output_off]), output_len); 536 if ((count == -1) && (errno != EAGAIN)) { 537 cvcd_err(LOG_ERR, "write(network): %s", 538 strerror(errno)); 539 break; 540 } else if (count > 0) { 541 output_len -= count; 542 if (output_len == 0) { 543 output_off = 0; 544 } else { 545 output_off += count; 546 } 547 } 548 } 549 550 if (input_len != 0) { 551 count = write(pfds[REDIR_PFD].fd, 552 &(input_buf[input_off]), input_len); 553 if ((count == -1) && (errno != EAGAIN)) { 554 cvcd_err(LOG_ERR, "write(redir): %s", 555 strerror(errno)); 556 break; 557 } else if (count > 0) { 558 input_len -= count; 559 if (input_len == 0) { 560 input_off = 0; 561 } else { 562 input_off += count; 563 } 564 } 565 } 566 567 /* 568 * Finally, take a look at each data source and, if there isn't 569 * any residual data from that source still waiting to be 570 * processed, see if more data can be read. We don't want to 571 * read more data from a source if we haven't finished 572 * processing the last data we read from it because doing so 573 * would maximize the amount of data lost if the network console 574 * failed or was closed. 575 * If an "unexpected" error is detected, give up and break out 576 * of the network console processing loop. 577 * The call to read() appears to be in the habit of returning 0 578 * when you've read all of the data from a stream that has been 579 * hung up, and poll apparently feels that that condition 580 * justifies setting POLLIN, so we're going to treat 0 as an 581 * error return from read(). 582 */ 583 if ((output_len == 0) && (pfds[REDIR_PFD].revents & POLLIN)) { 584 count = read(pfds[REDIR_PFD].fd, output_buf, MAXPKTSZ); 585 if (count <= 0) { 586 /* 587 * Reading 0 simply means there is no data 588 * available, since this is a terminal. 589 */ 590 if ((count < 0) && (errno != EAGAIN)) { 591 cvcd_err(LOG_ERR, "read(redir): %s", 592 strerror(errno)); 593 break; 594 } 595 } else { 596 output_len = count; 597 output_off = 0; 598 } 599 } 600 601 if ((input_len == 0) && (pfds[NETWORK_PFD].revents & POLLIN)) { 602 count = read(pfds[NETWORK_PFD].fd, input_buf, MAXPKTSZ); 603 if (count <= 0) { 604 /* 605 * Reading 0 here implies a hangup, since this 606 * is a non-blocking socket that poll() reported 607 * as having data available. This will 608 * typically occur when the console user drops 609 * to OBP or intentially switches to IOSRAM 610 * mode. 611 */ 612 if (count == 0) { 613 cvcd_err(LOG_NOTICE, 614 "read(network): hangup detected"); 615 break; 616 } else if (errno != EAGAIN) { 617 cvcd_err(LOG_ERR, "read(network): %s", 618 strerror(errno)); 619 break; 620 } 621 } else { 622 input_len = count; 623 input_off = 0; 624 } 625 } 626 } /* End forever loop */ 627 628 /* 629 * If we get here, something bad happened during an attempt to access 630 * either the redirection driver or the network connection. There 631 * doesn't appear to be any way to avoid the possibility of losing 632 * console input and/or input in that case, so we should at least report 633 * the loss if it happens. 634 * XXX - We could do more, but is it worth the effort? Logging the 635 * lost data would be pretty easy... actually preserving it 636 * in the console flow would be a lot harder. We're more robust 637 * than the previous generation at this point, at least, so 638 * perhaps that's enough for now? 639 */ 640 fail: 641 if (input_len != 0) { 642 cvcd_err(LOG_ERR, "console input lost"); 643 } 644 if (output_len != 0) { 645 cvcd_err(LOG_ERR, "console output lost"); 646 } 647 } 648 649 650 static void 651 cvcd_usage() 652 { 653 #if defined(DEBUG) 654 (void) printf("%s [-d] [-p port]\n", progname); 655 #else 656 (void) printf("%s\n", progname); 657 #endif /* DEBUG */ 658 } 659 660 /* 661 * cvcd_err () 662 * 663 * Description: 664 * Log messages via syslog daemon. 665 * 666 * Input: 667 * code - logging code 668 * format - messages to log 669 * 670 * Output: 671 * void 672 * 673 */ 674 static void 675 cvcd_err(int code, char *format, ...) 676 { 677 va_list varg_ptr; 678 char buf[MAXPKTSZ]; 679 680 va_start(varg_ptr, format); 681 (void) vsnprintf(buf, MAXPKTSZ, format, varg_ptr); 682 va_end(varg_ptr); 683 684 if (debug == 0) { 685 syslog(code, buf); 686 } else { 687 (void) fprintf(stderr, "%s: %s\n", progname, buf); 688 } 689 } 690