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