1 /** 2 * Console server process for OpenBMC 3 * 4 * Copyright © 2016 IBM Corporation 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 19 #include <assert.h> 20 #include <errno.h> 21 #include <signal.h> 22 #include <stdint.h> 23 #include <stdbool.h> 24 #include <stdlib.h> 25 #include <stdio.h> 26 #include <fcntl.h> 27 #include <unistd.h> 28 #include <err.h> 29 #include <string.h> 30 #include <getopt.h> 31 #include <limits.h> 32 #include <time.h> 33 #include <termios.h> 34 35 #include <sys/types.h> 36 #include <sys/time.h> 37 #include <sys/socket.h> 38 #include <poll.h> 39 40 #include "console-server.h" 41 42 #define DEV_PTS_PATH "/dev/pts" 43 44 /* size of the shared backlog ringbuffer */ 45 const size_t buffer_size = 128ul * 1024ul; 46 47 /* state shared with the signal handler */ 48 static bool sigint; 49 50 static void usage(const char *progname) 51 { 52 fprintf(stderr, 53 "usage: %s [options] <DEVICE>\n" 54 "\n" 55 "Options:\n" 56 " --config <FILE> Use FILE for configuration\n" 57 "", 58 progname); 59 } 60 61 /* populates console->tty.dev and console->tty.sysfs_devnode, using the tty kernel name */ 62 static int tty_find_device(struct console *console) 63 { 64 char *tty_class_device_link = NULL; 65 char *tty_path_input_real = NULL; 66 char *tty_device_tty_dir = NULL; 67 char *tty_vuart_lpc_addr = NULL; 68 char *tty_device_reldir = NULL; 69 char *tty_sysfs_devnode = NULL; 70 char *tty_kname_real = NULL; 71 char *tty_path_input = NULL; 72 int rc; 73 74 console->tty.type = TTY_DEVICE_UNDEFINED; 75 76 assert(console->tty.kname); 77 if (!strlen(console->tty.kname)) { 78 warnx("TTY kname must not be empty"); 79 rc = -1; 80 goto out_free; 81 } 82 83 if (console->tty.kname[0] == '/') { 84 tty_path_input = strdup(console->tty.kname); 85 if (!tty_path_input) { 86 rc = -1; 87 goto out_free; 88 } 89 } else { 90 rc = asprintf(&tty_path_input, "/dev/%s", console->tty.kname); 91 if (rc < 0) { 92 goto out_free; 93 } 94 } 95 96 /* udev may rename the tty name with a symbol link, try to resolve */ 97 tty_path_input_real = realpath(tty_path_input, NULL); 98 if (!tty_path_input_real) { 99 warn("Can't find realpath for %s", tty_path_input); 100 rc = -1; 101 goto out_free; 102 } 103 104 /* 105 * Allow hooking obmc-console-server up to PTYs for testing 106 * 107 * https://amboar.github.io/notes/2023/05/02/testing-obmc-console-with-socat.html 108 */ 109 if (!strncmp(DEV_PTS_PATH, tty_path_input_real, strlen(DEV_PTS_PATH))) { 110 console->tty.type = TTY_DEVICE_PTY; 111 console->tty.dev = strdup(console->tty.kname); 112 rc = console->tty.dev ? 0 : -1; 113 goto out_free; 114 } 115 116 tty_kname_real = basename(tty_path_input_real); 117 if (!tty_kname_real) { 118 warn("Can't find real name for %s", console->tty.kname); 119 rc = -1; 120 goto out_free; 121 } 122 123 rc = asprintf(&tty_class_device_link, "/sys/class/tty/%s", 124 tty_kname_real); 125 if (rc < 0) { 126 goto out_free; 127 } 128 129 tty_device_tty_dir = realpath(tty_class_device_link, NULL); 130 if (!tty_device_tty_dir) { 131 warn("Can't query sysfs for device %s", tty_kname_real); 132 rc = -1; 133 goto out_free; 134 } 135 136 rc = asprintf(&tty_device_reldir, "%s/../../", tty_device_tty_dir); 137 if (rc < 0) { 138 goto out_free; 139 } 140 141 tty_sysfs_devnode = realpath(tty_device_reldir, NULL); 142 if (!tty_sysfs_devnode) { 143 warn("Can't find parent device for %s", tty_kname_real); 144 } 145 146 rc = asprintf(&console->tty.dev, "/dev/%s", tty_kname_real); 147 if (rc < 0) { 148 goto out_free; 149 } 150 151 /* Arbitrarily pick an attribute to differentiate UART vs VUART */ 152 rc = asprintf(&tty_vuart_lpc_addr, "%s/lpc_addr", tty_sysfs_devnode); 153 if (rc < 0) { 154 goto out_free; 155 } 156 157 rc = access(tty_vuart_lpc_addr, F_OK); 158 console->tty.type = (!rc) ? TTY_DEVICE_VUART : TTY_DEVICE_UART; 159 160 rc = 0; 161 162 out_free: 163 free(tty_vuart_lpc_addr); 164 free(tty_class_device_link); 165 free(tty_device_tty_dir); 166 free(tty_device_reldir); 167 free(tty_path_input); 168 free(tty_path_input_real); 169 return rc; 170 } 171 172 static int tty_set_sysfs_attr(struct console *console, const char *name, 173 int value) 174 { 175 char *path; 176 FILE *fp; 177 int rc; 178 179 assert(console->tty.type == TTY_DEVICE_VUART); 180 181 if (!console->tty.vuart.sysfs_devnode) { 182 return -1; 183 } 184 185 rc = asprintf(&path, "%s/%s", console->tty.vuart.sysfs_devnode, name); 186 if (rc < 0) { 187 return -1; 188 } 189 190 fp = fopen(path, "w"); 191 if (!fp) { 192 warn("Can't access attribute %s on device %s", name, 193 console->tty.kname); 194 rc = -1; 195 goto out_free; 196 } 197 setvbuf(fp, NULL, _IONBF, 0); 198 199 rc = fprintf(fp, "0x%x", value); 200 if (rc < 0) { 201 warn("Error writing to %s attribute of device %s", name, 202 console->tty.kname); 203 } 204 fclose(fp); 205 206 out_free: 207 free(path); 208 return rc; 209 } 210 211 /** 212 * Set termios attributes on the console tty. 213 */ 214 void tty_init_termios(struct console *console) 215 { 216 struct termios termios; 217 int rc; 218 219 rc = tcgetattr(console->tty.fd, &termios); 220 if (rc) { 221 warn("Can't read tty termios"); 222 return; 223 } 224 225 if (console->tty.type == TTY_DEVICE_UART && console->tty.uart.baud) { 226 if (cfsetspeed(&termios, console->tty.uart.baud) < 0) { 227 warn("Couldn't set speeds for %s", console->tty.kname); 228 } 229 } 230 231 /* Set console to raw mode: we don't want any processing to occur on 232 * the underlying terminal input/output. 233 */ 234 cfmakeraw(&termios); 235 236 rc = tcsetattr(console->tty.fd, TCSANOW, &termios); 237 if (rc) { 238 warn("Can't set terminal options for %s", console->tty.kname); 239 } 240 } 241 242 /** 243 * Open and initialise the serial device 244 */ 245 static void tty_init_vuart_io(struct console *console) 246 { 247 assert(console->tty.type == TTY_DEVICE_VUART); 248 249 if (console->tty.vuart.sirq) { 250 tty_set_sysfs_attr(console, "sirq", console->tty.vuart.sirq); 251 } 252 253 if (console->tty.vuart.lpc_addr) { 254 tty_set_sysfs_attr(console, "lpc_address", 255 console->tty.vuart.lpc_addr); 256 } 257 } 258 259 static int tty_init_io(struct console *console) 260 { 261 console->tty.fd = open(console->tty.dev, O_RDWR); 262 if (console->tty.fd <= 0) { 263 warn("Can't open tty %s", console->tty.dev); 264 return -1; 265 } 266 267 /* Disable character delay. We may want to later enable this when 268 * we detect larger amounts of data 269 */ 270 fcntl(console->tty.fd, F_SETFL, FNDELAY); 271 272 tty_init_termios(console); 273 274 console->pollfds[console->n_pollers].fd = console->tty.fd; 275 console->pollfds[console->n_pollers].events = POLLIN; 276 277 return 0; 278 } 279 280 static int tty_init_vuart(struct console *console, struct config *config) 281 { 282 unsigned long parsed; 283 const char *val; 284 char *endp; 285 286 assert(console->tty.type == TTY_DEVICE_VUART); 287 288 val = config_get_value(config, "lpc-address"); 289 if (val) { 290 errno = 0; 291 parsed = strtoul(val, &endp, 0); 292 if (parsed == ULONG_MAX && errno == ERANGE) { 293 warn("Cannot interpret 'lpc-address' value as an unsigned long: '%s'", 294 val); 295 return -1; 296 } 297 298 if (parsed > UINT16_MAX) { 299 warn("Invalid LPC address '%s'", val); 300 return -1; 301 } 302 303 console->tty.vuart.lpc_addr = (uint16_t)parsed; 304 if (endp == optarg) { 305 warn("Invalid LPC address: '%s'", val); 306 return -1; 307 } 308 } 309 310 val = config_get_value(config, "sirq"); 311 if (val) { 312 errno = 0; 313 parsed = strtoul(val, &endp, 0); 314 if (parsed == ULONG_MAX && errno == ERANGE) { 315 warn("Cannot interpret 'sirq' value as an unsigned long: '%s'", 316 val); 317 } 318 319 if (parsed > 16) { 320 warn("Invalid LPC SERIRQ: '%s'", val); 321 } 322 323 console->tty.vuart.sirq = (int)parsed; 324 if (endp == optarg) { 325 warn("Invalid sirq: '%s'", val); 326 } 327 } 328 329 return 0; 330 } 331 332 static int tty_init(struct console *console, struct config *config, 333 const char *tty_arg) 334 { 335 const char *val; 336 int rc; 337 338 if (tty_arg) { 339 console->tty.kname = tty_arg; 340 } else if ((val = config_get_value(config, "upstream-tty"))) { 341 console->tty.kname = val; 342 } else { 343 warnx("Error: No TTY device specified"); 344 return -1; 345 } 346 347 rc = tty_find_device(console); 348 if (rc) { 349 return rc; 350 } 351 352 switch (console->tty.type) { 353 case TTY_DEVICE_VUART: 354 rc = tty_init_vuart(console, config); 355 if (rc) { 356 return rc; 357 } 358 359 tty_init_vuart_io(console); 360 break; 361 case TTY_DEVICE_UART: 362 val = config_get_value(config, "baud"); 363 if (val) { 364 if (config_parse_baud(&console->tty.uart.baud, val)) { 365 warnx("Invalid baud rate: '%s'", val); 366 } 367 } 368 break; 369 case TTY_DEVICE_PTY: 370 break; 371 case TTY_DEVICE_UNDEFINED: 372 default: 373 warnx("Cannot configure unrecognised TTY device"); 374 return -1; 375 } 376 377 return tty_init_io(console); 378 } 379 380 static void tty_fini(struct console *console) 381 { 382 if (console->tty.type == TTY_DEVICE_VUART) { 383 free(console->tty.vuart.sysfs_devnode); 384 } 385 free(console->tty.dev); 386 } 387 388 int console_data_out(struct console *console, const uint8_t *data, size_t len) 389 { 390 return write_buf_to_fd(console->tty.fd, data, len); 391 } 392 393 /* Read console if from config and prepare a socket name */ 394 static int set_socket_info(struct console *console, struct config *config) 395 { 396 ssize_t len; 397 398 console->console_id = config_get_value(config, "console-id"); 399 400 /* socket-id is deprecated */ 401 if (!console->console_id) { 402 console->console_id = config_get_value(config, "socket-id"); 403 } 404 405 if (!console->console_id) { 406 warnx("Error: The console-id is not set in the config file"); 407 return EXIT_FAILURE; 408 } 409 410 /* Get the socket name/path */ 411 len = console_socket_path(console->socket_name, console->console_id); 412 if (len < 0) { 413 warn("Failed to set socket path: %s", strerror(errno)); 414 return EXIT_FAILURE; 415 } 416 417 /* Socket name is not a null terminated string hence save the length */ 418 console->socket_name_len = len; 419 420 return 0; 421 } 422 423 static void handlers_init(struct console *console, struct config *config) 424 { 425 /* NOLINTBEGIN(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp) */ 426 extern struct handler *__start_handlers; 427 extern struct handler *__stop_handlers; 428 /* NOLINTEND(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp) */ 429 struct handler *handler; 430 int i; 431 int rc; 432 433 console->n_handlers = &__stop_handlers - &__start_handlers; 434 console->handlers = &__start_handlers; 435 436 printf("%ld handler%s\n", console->n_handlers, 437 console->n_handlers == 1 ? "" : "s"); 438 439 for (i = 0; i < console->n_handlers; i++) { 440 handler = console->handlers[i]; 441 442 rc = 0; 443 if (handler->init) { 444 rc = handler->init(handler, console, config); 445 } 446 447 handler->active = rc == 0; 448 449 printf(" %s [%sactive]\n", handler->name, 450 handler->active ? "" : "in"); 451 } 452 } 453 454 static void handlers_fini(struct console *console) 455 { 456 struct handler *handler; 457 int i; 458 459 for (i = 0; i < console->n_handlers; i++) { 460 handler = console->handlers[i]; 461 if (handler->fini && handler->active) { 462 handler->fini(handler); 463 } 464 } 465 } 466 467 static int get_current_time(struct timeval *tv) 468 { 469 struct timespec t; 470 int rc; 471 472 /* 473 * We use clock_gettime(CLOCK_MONOTONIC) so we're immune to 474 * local time changes. However, a struct timeval is more 475 * convenient for calculations, so convert to that. 476 */ 477 rc = clock_gettime(CLOCK_MONOTONIC, &t); 478 if (rc) { 479 return rc; 480 } 481 482 tv->tv_sec = t.tv_sec; 483 tv->tv_usec = t.tv_nsec / 1000; 484 485 return 0; 486 } 487 488 struct ringbuffer_consumer * 489 console_ringbuffer_consumer_register(struct console *console, 490 ringbuffer_poll_fn_t poll_fn, void *data) 491 { 492 return ringbuffer_consumer_register(console->rb, poll_fn, data); 493 } 494 495 struct poller *console_poller_register(struct console *console, 496 struct handler *handler, 497 poller_event_fn_t poller_fn, 498 poller_timeout_fn_t timeout_fn, int fd, 499 int events, void *data) 500 { 501 struct poller *poller; 502 long n; 503 504 poller = malloc(sizeof(*poller)); 505 poller->remove = false; 506 poller->handler = handler; 507 poller->event_fn = poller_fn; 508 poller->timeout_fn = timeout_fn; 509 poller->data = data; 510 511 /* add one to our pollers array */ 512 n = console->n_pollers++; 513 /* 514 * We're managing an array of pointers to aggregates, so don't warn about sizeof() on a 515 * pointer type. 516 */ 517 /* NOLINTBEGIN(bugprone-sizeof-expression) */ 518 console->pollers = reallocarray(console->pollers, console->n_pollers, 519 sizeof(*console->pollers)); 520 /* NOLINTEND(bugprone-sizeof-expression) */ 521 522 console->pollers[n] = poller; 523 524 /* increase pollfds array too */ 525 console->pollfds = 526 reallocarray(console->pollfds, 527 (MAX_INTERNAL_POLLFD + console->n_pollers), 528 sizeof(*console->pollfds)); 529 530 /* shift the end pollfds up by one */ 531 memcpy(&console->pollfds[n + 1], &console->pollfds[n], 532 sizeof(*console->pollfds) * MAX_INTERNAL_POLLFD); 533 534 console->pollfds[n].fd = fd; 535 console->pollfds[n].events = (short)(events & 0x7fff); 536 537 return poller; 538 } 539 540 void console_poller_unregister(struct console *console, struct poller *poller) 541 { 542 int i; 543 544 /* find the entry in our pollers array */ 545 for (i = 0; i < console->n_pollers; i++) { 546 if (console->pollers[i] == poller) { 547 break; 548 } 549 } 550 551 assert(i < console->n_pollers); 552 553 console->n_pollers--; 554 555 /* 556 * Remove the item from the pollers array... 557 * 558 * We're managing an array of pointers to aggregates, so don't warn about sizeof() on a 559 * pointer type. 560 */ 561 /* NOLINTBEGIN(bugprone-sizeof-expression) */ 562 memmove(&console->pollers[i], &console->pollers[i + 1], 563 sizeof(*console->pollers) * (console->n_pollers - i)); 564 565 console->pollers = reallocarray(console->pollers, console->n_pollers, 566 sizeof(*console->pollers)); 567 /* NOLINTEND(bugprone-sizeof-expression) */ 568 569 /* ... and the pollfds array */ 570 memmove(&console->pollfds[i], &console->pollfds[i + 1], 571 sizeof(*console->pollfds) * 572 (MAX_INTERNAL_POLLFD + console->n_pollers - i)); 573 574 console->pollfds = 575 reallocarray(console->pollfds, 576 (MAX_INTERNAL_POLLFD + console->n_pollers), 577 sizeof(*console->pollfds)); 578 579 free(poller); 580 } 581 582 void console_poller_set_events(struct console *console, struct poller *poller, 583 int events) 584 { 585 int i; 586 587 /* find the entry in our pollers array */ 588 for (i = 0; i < console->n_pollers; i++) { 589 if (console->pollers[i] == poller) { 590 break; 591 } 592 } 593 594 console->pollfds[i].events = (short)(events & 0x7fff); 595 } 596 597 void console_poller_set_timeout(struct console *console __attribute__((unused)), 598 struct poller *poller, const struct timeval *tv) 599 { 600 struct timeval now; 601 int rc; 602 603 rc = get_current_time(&now); 604 if (rc) { 605 return; 606 } 607 608 timeradd(&now, tv, &poller->timeout); 609 } 610 611 static long get_poll_timeout(struct console *console, struct timeval *cur_time) 612 { 613 struct timeval *earliest; 614 struct timeval interval; 615 struct poller *poller; 616 int i; 617 618 earliest = NULL; 619 620 for (i = 0; i < console->n_pollers; i++) { 621 poller = console->pollers[i]; 622 623 if (poller->timeout_fn && timerisset(&poller->timeout) && 624 (!earliest || 625 (earliest && timercmp(&poller->timeout, earliest, <)))) { 626 // poller is buffering data and needs the poll 627 // function to timeout. 628 earliest = &poller->timeout; 629 } 630 } 631 632 if (earliest) { 633 if (timercmp(earliest, cur_time, >)) { 634 /* recalculate the timeout period, time period has 635 * not elapsed */ 636 timersub(earliest, cur_time, &interval); 637 return ((interval.tv_sec * 1000) + 638 (interval.tv_usec / 1000)); 639 } /* return from poll immediately */ 640 return 0; 641 642 } /* poll indefinitely */ 643 return -1; 644 } 645 646 static int call_pollers(struct console *console, struct timeval *cur_time) 647 { 648 struct poller *poller; 649 struct pollfd *pollfd; 650 enum poller_ret prc; 651 int i; 652 int rc; 653 654 rc = 0; 655 656 /* 657 * Process poll events by iterating through the pollers and pollfds 658 * in-step, calling any pollers that we've found revents for. 659 */ 660 for (i = 0; i < console->n_pollers; i++) { 661 poller = console->pollers[i]; 662 pollfd = &console->pollfds[i]; 663 prc = POLLER_OK; 664 665 /* process pending events... */ 666 if (pollfd->revents) { 667 prc = poller->event_fn(poller->handler, pollfd->revents, 668 poller->data); 669 if (prc == POLLER_EXIT) { 670 rc = -1; 671 } else if (prc == POLLER_REMOVE) { 672 poller->remove = true; 673 } 674 } 675 676 if ((prc == POLLER_OK) && poller->timeout_fn && 677 timerisset(&poller->timeout) && 678 timercmp(&poller->timeout, cur_time, <=)) { 679 /* One of the ringbuffer consumers is buffering the 680 data stream. The amount of idle time the consumer 681 desired has expired. Process the buffered data for 682 transmission. */ 683 timerclear(&poller->timeout); 684 prc = poller->timeout_fn(poller->handler, poller->data); 685 if (prc == POLLER_EXIT) { 686 rc = -1; 687 } else if (prc == POLLER_REMOVE) { 688 poller->remove = true; 689 } 690 } 691 } 692 693 /** 694 * Process deferred removals; restarting each time we unregister, as 695 * the array will have changed 696 */ 697 for (;;) { 698 bool removed = false; 699 700 for (i = 0; i < console->n_pollers; i++) { 701 poller = console->pollers[i]; 702 if (poller->remove) { 703 console_poller_unregister(console, poller); 704 removed = true; 705 break; 706 } 707 } 708 if (!removed) { 709 break; 710 } 711 } 712 713 return rc; 714 } 715 716 static void sighandler(int signal) 717 { 718 if (signal == SIGINT) { 719 sigint = true; 720 } 721 } 722 723 int run_console(struct console *console) 724 { 725 sighandler_t sighandler_save = signal(SIGINT, sighandler); 726 struct timeval tv; 727 long timeout; 728 ssize_t rc; 729 730 rc = 0; 731 732 for (;;) { 733 uint8_t buf[4096]; 734 735 BUILD_ASSERT(sizeof(buf) <= buffer_size); 736 737 if (sigint) { 738 fprintf(stderr, "Received interrupt, exiting\n"); 739 break; 740 } 741 742 rc = get_current_time(&tv); 743 if (rc) { 744 warn("Failed to read current time"); 745 break; 746 } 747 748 timeout = get_poll_timeout(console, &tv); 749 750 rc = poll(console->pollfds, 751 console->n_pollers + MAX_INTERNAL_POLLFD, 752 (int)timeout); 753 754 if (rc < 0) { 755 if (errno == EINTR) { 756 continue; 757 } 758 warn("poll error"); 759 break; 760 } 761 762 /* process internal fd first */ 763 if (console->pollfds[console->n_pollers].revents) { 764 rc = read(console->tty.fd, buf, sizeof(buf)); 765 if (rc <= 0) { 766 warn("Error reading from tty device"); 767 rc = -1; 768 break; 769 } 770 rc = ringbuffer_queue(console->rb, buf, rc); 771 if (rc) { 772 break; 773 } 774 } 775 776 if (console->pollfds[console->n_pollers + 1].revents) { 777 sd_bus_process(console->bus, NULL); 778 } 779 780 /* ... and then the pollers */ 781 rc = call_pollers(console, &tv); 782 if (rc) { 783 break; 784 } 785 } 786 787 signal(SIGINT, sighandler_save); 788 sd_bus_unref(console->bus); 789 790 return rc ? -1 : 0; 791 } 792 static const struct option options[] = { 793 { "config", required_argument, 0, 'c' }, 794 { 0, 0, 0, 0 }, 795 }; 796 797 int main(int argc, char **argv) 798 { 799 const char *config_filename = NULL; 800 const char *config_tty_kname = NULL; 801 struct console *console; 802 struct config *config; 803 int rc; 804 805 for (;;) { 806 int c; 807 int idx; 808 809 c = getopt_long(argc, argv, "c:", options, &idx); 810 if (c == -1) { 811 break; 812 } 813 814 switch (c) { 815 case 'c': 816 config_filename = optarg; 817 break; 818 case 'h': 819 case '?': 820 usage(argv[0]); 821 return EXIT_SUCCESS; 822 } 823 } 824 825 if (optind < argc) { 826 config_tty_kname = argv[optind]; 827 } 828 829 console = malloc(sizeof(struct console)); 830 memset(console, 0, sizeof(*console)); 831 console->pollfds = 832 calloc(MAX_INTERNAL_POLLFD, sizeof(*console->pollfds)); 833 console->rb = ringbuffer_init(buffer_size); 834 835 config = config_init(config_filename); 836 if (!config) { 837 warnx("Can't read configuration, exiting."); 838 rc = -1; 839 goto out_free; 840 } 841 842 if (set_socket_info(console, config)) { 843 rc = -1; 844 goto out_config_fini; 845 } 846 847 rc = tty_init(console, config, config_tty_kname); 848 if (rc) { 849 goto out_config_fini; 850 } 851 852 dbus_init(console, config); 853 854 handlers_init(console, config); 855 856 rc = run_console(console); 857 858 handlers_fini(console); 859 860 tty_fini(console); 861 862 out_config_fini: 863 config_fini(config); 864 865 out_free: 866 free(console->pollers); 867 free(console->pollfds); 868 free(console); 869 870 return rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE; 871 } 872