1/* 2Copyright (c) 2001 by Juliusz Chroboczek 3 4Permission is hereby granted, free of charge, to any person obtaining a copy 5of this software and associated documentation files (the "Software"), to deal 6in the Software without restriction, including without limitation the rights 7to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8copies of the Software, and to permit persons to whom the Software is 9furnished to do so, subject to the following conditions: 10 11The above copyright notice and this permission notice shall be included in 12all copies or substantial portions of the Software. 13 14THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20THE SOFTWARE. 21*/ 22 23#ifdef HAVE_CONFIG_H 24# include "config.h" 25#endif 26 27#include <stdio.h> 28#include <stdlib.h> 29#include <string.h> 30#include <locale.h> 31#include <sys/types.h> 32#include <fcntl.h> 33#include <unistd.h> 34#include <errno.h> 35#include <assert.h> 36#include <stdarg.h> 37#include <sys/ioctl.h> 38#include <signal.h> 39 40#include "luit.h" 41#include "sys.h" 42#include "other.h" 43#include "parser.h" 44#include "iso2022.h" 45 46static int pipe_option = 0; 47static int p2c_waitpipe[2]; 48static int c2p_waitpipe[2]; 49 50static Iso2022Ptr inputState = NULL, outputState = NULL; 51 52static char *child_argv0 = NULL; 53static const char *locale_name = NULL; 54static int exitOnChild = 0; 55static int converter = 0; 56 57const char *locale_alias = LOCALE_ALIAS_FILE; 58 59int ilog = -1; 60int olog = -1; 61int verbose = 0; 62 63static volatile int sigwinch_queued = 0; 64static volatile int sigchld_queued = 0; 65 66static int convert(int, int); 67static int condom(int, char **); 68 69void 70ErrorF(const char *f,...) 71{ 72 va_list args; 73 va_start(args, f); 74 vfprintf(stderr, f, args); 75 va_end(args); 76} 77 78void 79FatalError(const char *f,...) 80{ 81 va_list args; 82 va_start(args, f); 83 vfprintf(stderr, f, args); 84 va_end(args); 85 ExitProgram(1); 86} 87 88static void 89help(void) 90{ 91 fprintf(stderr, 92 "luit\n" 93 " [ -V ] [ -h ] [ -list ] [ -v ] [ -argv0 name ]\n" 94 " [ -gl gn ] [-gr gk] " 95 "[ -g0 set ] [ -g1 set ] " 96 "[ -g2 set ] [ -g3 set ]\n" 97 " [ -encoding encoding ] " 98 "[ +oss ] [ +ols ] [ +osl ] [ +ot ]\n" 99 " [ -kgl gn ] [-kgr gk] " 100 "[ -kg0 set ] [ -kg1 set ] " 101 "[ -kg2 set ] [ -kg3 set ]\n" 102 " [ -k7 ] [ +kss ] [ +kssgr ] [ -kls ]\n" 103 " [ -c ] " 104 "[ -p ] " 105 "[ -x ] " 106 "[ -ilog filename ] " 107 "[ -olog filename ] " 108 "[ -alias filename ] " 109 "[ -- ]\n" 110 " [ program [ args ] ]\n"); 111} 112 113static int 114parseOptions(int argc, char **argv) 115{ 116 int i = 1; 117 while (i < argc) { 118 if (argv[i][0] != '-' && argv[i][0] != '+') { 119 break; 120 } else if (!strcmp(argv[i], "--")) { 121 i++; 122 break; 123 } else if (!strcmp(argv[i], "-v")) { 124 verbose++; 125 i++; 126 } else if (!strcmp(argv[i], "-V")) { 127 printf("%s - %s\n", argv[0], VERSION); 128 ExitProgram(0); 129 } else if (!strcmp(argv[i], "-h")) { 130 help(); 131 ExitProgram(0); 132 } else if (!strcmp(argv[i], "-list")) { 133 reportCharsets(); 134 ExitProgram(0); 135 } else if (!strcmp(argv[i], "+oss")) { 136 outputState->outputFlags &= ~OF_SS; 137 i++; 138 } else if (!strcmp(argv[i], "+ols")) { 139 outputState->outputFlags &= ~OF_LS; 140 i++; 141 } else if (!strcmp(argv[i], "+osl")) { 142 outputState->outputFlags &= ~OF_SELECT; 143 i++; 144 } else if (!strcmp(argv[i], "+ot")) { 145 outputState->outputFlags = OF_PASSTHRU; 146 i++; 147 } else if (!strcmp(argv[i], "-k7")) { 148 inputState->inputFlags &= ~IF_EIGHTBIT; 149 i++; 150 } else if (!strcmp(argv[i], "+kss")) { 151 inputState->inputFlags &= ~IF_SS; 152 i++; 153 } else if (!strcmp(argv[1], "+kssgr")) { 154 inputState->inputFlags &= ~IF_SSGR; 155 i++; 156 } else if (!strcmp(argv[i], "-kls")) { 157 inputState->inputFlags |= IF_LS; 158 i++; 159 } else if (!strcmp(argv[i], "-g0")) { 160 if (i + 1 >= argc) 161 FatalError("-g0 requires an argument\n"); 162 G0(outputState) = getCharsetByName(argv[i + 1]); 163 i += 2; 164 } else if (!strcmp(argv[i], "-g1")) { 165 if (i + 1 >= argc) 166 FatalError("-g1 requires an argument\n"); 167 G1(outputState) = getCharsetByName(argv[i + 1]); 168 i += 2; 169 } else if (!strcmp(argv[i], "-g2")) { 170 if (i + 1 >= argc) 171 FatalError("-g2 requires an argument\n"); 172 G2(outputState) = getCharsetByName(argv[i + 1]); 173 i += 2; 174 } else if (!strcmp(argv[i], "-g3")) { 175 if (i + 1 >= argc) 176 FatalError("-g3 requires an argument\n"); 177 G3(outputState) = getCharsetByName(argv[i + 1]); 178 179 i += 2; 180 } else if (!strcmp(argv[i], "-gl")) { 181 int j; 182 if (i + 1 >= argc) 183 FatalError("-gl requires an argument\n"); 184 if (strlen(argv[i + 1]) != 2 || 185 argv[i + 1][0] != 'g') 186 j = -1; 187 else 188 j = argv[i + 1][1] - '0'; 189 if (j < 0 || j > 3) 190 FatalError("The argument of -gl " 191 "should be one of g0 through g3,\n" 192 "not %s\n", argv[i + 1]); 193 else 194 outputState->glp = &outputState->g[j]; 195 i += 2; 196 } else if (!strcmp(argv[i], "-gr")) { 197 int j; 198 if (i + 1 >= argc) 199 FatalError("-gr requires an argument\n"); 200 if (strlen(argv[i + 1]) != 2 || 201 argv[i + 1][0] != 'g') 202 j = -1; 203 else 204 j = argv[i + 1][1] - '0'; 205 if (j < 0 || j > 3) 206 FatalError("The argument of -gl " 207 "should be one of g0 through g3,\n" 208 "not %s\n", argv[i + 1]); 209 else 210 outputState->grp = &outputState->g[j]; 211 i += 2; 212 } else if (!strcmp(argv[i], "-kg0")) { 213 if (i + 1 >= argc) 214 FatalError("-kg0 requires an argument\n"); 215 G0(inputState) = getCharsetByName(argv[i + 1]); 216 i += 2; 217 } else if (!strcmp(argv[i], "-kg1")) { 218 if (i + 1 >= argc) 219 FatalError("-kg1 requires an argument\n"); 220 G1(inputState) = getCharsetByName(argv[i + 1]); 221 i += 2; 222 } else if (!strcmp(argv[i], "-kg2")) { 223 if (i + 1 >= argc) 224 FatalError("-kg2 requires an argument\n"); 225 G2(inputState) = getCharsetByName(argv[i + 1]); 226 i += 2; 227 } else if (!strcmp(argv[i], "-kg3")) { 228 if (i + 1 >= argc) 229 FatalError("-kg3 requires an argument\n"); 230 G3(inputState) = getCharsetByName(argv[i + 1]); 231 232 i += 2; 233 } else if (!strcmp(argv[i], "-kgl")) { 234 int j; 235 if (i + 1 >= argc) 236 FatalError("-kgl requires an argument\n"); 237 if (strlen(argv[i + 1]) != 2 || 238 argv[i + 1][0] != 'g') 239 j = -1; 240 else 241 j = argv[i + 1][1] - '0'; 242 if (j < 0 || j > 3) 243 FatalError("The argument of -kgl " 244 "should be one of g0 through g3,\n" 245 "not %s\n", argv[i + 1]); 246 else 247 inputState->glp = &inputState->g[j]; 248 i += 2; 249 } else if (!strcmp(argv[i], "-kgr")) { 250 int j; 251 if (i + 1 >= argc) 252 FatalError("-kgl requires an argument\n"); 253 if (strlen(argv[i + 1]) != 2 || 254 argv[i + 1][0] != 'g') 255 j = -1; 256 else 257 j = argv[i + 1][1] - '0'; 258 if (j < 0 || j > 3) 259 FatalError("The argument of -kgl " 260 "should be one of g0 through g3,\n" 261 "not %s\n", argv[i + 1]); 262 else 263 inputState->grp = &inputState->g[j]; 264 i += 2; 265 } else if (!strcmp(argv[i], "-argv0")) { 266 if (i + 1 >= argc) 267 FatalError("-argv0 requires an argument\n"); 268 child_argv0 = argv[i + 1]; 269 i += 2; 270 } else if (!strcmp(argv[i], "-x")) { 271 exitOnChild = 1; 272 i++; 273 } else if (!strcmp(argv[i], "-c")) { 274 converter = 1; 275 i++; 276 } else if (!strcmp(argv[i], "-ilog")) { 277 if (i + 1 >= argc) 278 FatalError("-ilog requires an argument\n"); 279 ilog = open(argv[i + 1], O_WRONLY | O_CREAT | O_TRUNC, 0777); 280 if (ilog < 0) { 281 perror("Couldn't open input log"); 282 ExitProgram(1); 283 } 284 i += 2; 285 } else if (!strcmp(argv[i], "-olog")) { 286 if (i + 1 >= argc) 287 FatalError("-olog requires an argument\n"); 288 olog = open(argv[i + 1], O_WRONLY | O_CREAT | O_TRUNC, 0777); 289 if (olog < 0) { 290 perror("Couldn't open output log"); 291 ExitProgram(1); 292 } 293 i += 2; 294 } else if (!strcmp(argv[i], "-alias")) { 295 if (i + 1 >= argc) 296 FatalError("-alias requires an argument\n"); 297 locale_alias = argv[i + 1]; 298 i += 2; 299 } else if (!strcmp(argv[i], "-encoding")) { 300 if (i + 1 >= argc) 301 FatalError("-encoding requires an argument\n"); 302 locale_name = argv[i + 1]; 303 i += 2; 304 } else if (!strcmp(argv[i], "-p")) { 305 pipe_option = 1; 306 i += 1; 307 } else { 308 FatalError("Unknown option %s\n", argv[i]); 309 } 310 } 311 return i; 312} 313 314static int 315parseArgs(int argc, char **argv, 316 char *argv0, 317 char **path_return, 318 char ***argv_return) 319{ 320 char *path = NULL; 321 char **child_argv = NULL; 322 323 if (argc <= 0) { 324 char *shell; 325 shell = getenv("SHELL"); 326 if (shell) { 327 path = strmalloc(shell); 328 if (!path) 329 goto bail; 330 } else { 331 path = strmalloc("/bin/sh"); 332 if (!path) 333 goto bail; 334 } 335 child_argv = malloc(2 * sizeof(char *)); 336 if (!child_argv) 337 goto bail; 338 if (argv0) 339 child_argv[0] = argv0; 340 else 341 child_argv[0] = my_basename(path); 342 child_argv[1] = NULL; 343 } else { 344 path = strmalloc(argv[0]); 345 if (!path) 346 goto bail; 347 child_argv = malloc((unsigned) (argc + 1) * sizeof(char *)); 348 if (!child_argv) { 349 goto bail; 350 } 351 if (child_argv0) 352 child_argv[0] = argv0; 353 else 354 child_argv[0] = my_basename(argv[0]); 355 memcpy(child_argv + 1, argv + 1, (unsigned) (argc - 1) * sizeof(char *)); 356 child_argv[argc] = NULL; 357 } 358 359 *path_return = path; 360 *argv_return = child_argv; 361 return 0; 362 363 bail: 364 if (path) 365 free(path); 366 if (child_argv) 367 free(child_argv); 368 return -1; 369} 370 371int 372main(int argc, char **argv) 373{ 374 int rc; 375 int i; 376 char *l; 377 378#ifdef HAVE_PUTENV 379 if ((l = strmalloc("NCURSES_NO_UTF8_ACS=1")) != 0) 380 putenv(l); 381#endif 382 383 l = setlocale(LC_ALL, ""); 384 if (!l) 385 ErrorF("Warning: couldn't set locale.\n"); 386 387 inputState = allocIso2022(); 388 if (!inputState) 389 FatalError("Couldn't create input state\n"); 390 391 outputState = allocIso2022(); 392 if (!outputState) 393 FatalError("Couldn't create output state\n"); 394 395 if (l) { 396 locale_name = setlocale(LC_CTYPE, NULL); 397 } else { 398 locale_name = getenv("LC_ALL"); 399 if (locale_name == NULL) { 400 locale_name = getenv("LC_CTYPE"); 401 if (locale_name == NULL) { 402 locale_name = getenv("LANG"); 403 } 404 } 405 } 406 407 if (locale_name == NULL) { 408 ErrorF("Couldn't get locale name -- using C\n"); 409 locale_name = "C"; 410 } 411 412 i = parseOptions(argc, argv); 413 if (i < 0) 414 FatalError("Couldn't parse options\n"); 415 416 rc = initIso2022(locale_name, NULL, outputState); 417 if (rc < 0) 418 FatalError("Couldn't init output state\n"); 419 420 rc = mergeIso2022(inputState, outputState); 421 if (rc < 0) 422 FatalError("Couldn't init input state\n"); 423 424 if (converter) 425 rc = convert(0, 1); 426 else 427 rc = condom(argc - i, argv + i); 428 429#ifdef NO_LEAKS 430 ExitProgram(rc); 431#endif 432 return rc; 433} 434 435static int 436convert(int ifd, int ofd) 437{ 438 int rc, i; 439 unsigned char buf[BUFFER_SIZE]; 440 441 rc = droppriv(); 442 if (rc < 0) { 443 perror("Couldn't drop privileges"); 444 ExitProgram(1); 445 } 446 447 while (1) { 448 i = (int) read(ifd, buf, (size_t) BUFFER_SIZE); 449 if (i <= 0) { 450 if (i < 0) { 451 perror("Read error"); 452 ExitProgram(1); 453 } 454 break; 455 } 456 copyOut(outputState, ofd, buf, (unsigned) i); 457 } 458 return 0; 459} 460 461#ifdef SIGWINCH 462static void 463sigwinchHandler(int sig GCC_UNUSED) 464{ 465 sigwinch_queued = 1; 466} 467#endif 468 469static void 470sigchldHandler(int sig GCC_UNUSED) 471{ 472 sigchld_queued = 1; 473} 474 475static int 476setup_io(int pty) 477{ 478 int rc; 479 int val; 480 481#ifdef SIGWINCH 482 installHandler(SIGWINCH, sigwinchHandler); 483#endif 484 installHandler(SIGCHLD, sigchldHandler); 485 486 rc = copyTermios(0, pty); 487 if (rc < 0) 488 FatalError("Couldn't copy terminal settings\n"); 489 490 rc = setRawTermios(); 491 if (rc < 0) 492 FatalError("Couldn't set terminal to raw\n"); 493 494 val = fcntl(0, F_GETFL, 0); 495 if (val >= 0) { 496 fcntl(0, F_SETFL, val | O_NONBLOCK); 497 } 498 val = fcntl(pty, F_GETFL, 0); 499 if (val >= 0) { 500 fcntl(pty, F_SETFL, val | O_NONBLOCK); 501 } 502 503 setWindowSize(0, pty); 504 505 return rc; 506} 507 508static void 509cleanup_io(int pty) 510{ 511 int val; 512 513#ifdef SIGWINCH 514 installHandler(SIGWINCH, SIG_DFL); 515#endif 516 installHandler(SIGCHLD, SIG_DFL); 517 518 val = fcntl(0, F_GETFL, 0); 519 if (val >= 0) { 520 fcntl(0, F_SETFL, val & ~O_NONBLOCK); 521 } 522 val = fcntl(pty, F_GETFL, 0); 523 if (val >= 0) { 524 fcntl(pty, F_SETFL, val & ~O_NONBLOCK); 525 } 526} 527 528static void 529close_waitpipe(int which) 530{ 531 close(p2c_waitpipe[which]); 532 close(c2p_waitpipe[!which]); 533} 534 535static void 536write_waitpipe(int fds[2]) 537{ 538 IGNORE_RC(write(fds[1], "1", (size_t) 1)); 539} 540 541static void 542read_waitpipe(int fds[2]) 543{ 544 char tmp[10]; 545 IGNORE_RC(read(fds[0], tmp, (size_t) 1)); 546} 547 548static int 549condom(int argc, char **argv) 550{ 551 int pty; 552 int pid; 553 char *line; 554 char *path = 0; 555 char **child_argv = 0; 556 int rc; 557 558 rc = parseArgs(argc, argv, child_argv0, 559 &path, &child_argv); 560 if (rc < 0) 561 FatalError("Couldn't parse arguments\n"); 562 563 rc = allocatePty(&pty, &line); 564 if (rc < 0) { 565 perror("Couldn't allocate pty"); 566 ExitProgram(1); 567 } 568 569 rc = droppriv(); 570 if (rc < 0) { 571 perror("Couldn't drop privileges"); 572 ExitProgram(1); 573 } 574 575 if (pipe_option) { 576 IGNORE_RC(pipe(p2c_waitpipe)); 577 IGNORE_RC(pipe(c2p_waitpipe)); 578 } 579 580 pid = fork(); 581 if (pid < 0) { 582 perror("Couldn't fork"); 583 ExitProgram(1); 584 } 585 586 if (pid == 0) { 587 close(pty); 588 if (pipe_option) { 589 close_waitpipe(1); 590 } 591 child(line, path, child_argv); 592 } else { 593 if (pipe_option) { 594 close_waitpipe(0); 595 } 596 free(child_argv); 597 free(path); 598 free(line); 599 parent(pid, pty); 600 } 601 602 return 0; 603} 604 605void 606child(char *line, char *path, char *const argv[]) 607{ 608 int tty; 609 int pgrp; 610 611 close(0); 612 close(1); 613 close(2); 614 615 pgrp = setsid(); 616 if (pgrp < 0) { 617 kill(getppid(), SIGABRT); 618 ExitProgram(1); 619 } 620 621 tty = openTty(line); 622 if (tty < 0) { 623 kill(getppid(), SIGABRT); 624 ExitProgram(1); 625 } 626 if (pipe_option) { 627 write_waitpipe(c2p_waitpipe); 628 } 629 630 if (tty != 0) 631 dup2(tty, 0); 632 if (tty != 1) 633 dup2(tty, 1); 634 if (tty != 2) 635 dup2(tty, 2); 636 637 if (tty > 2) 638 close(tty); 639 640 if (pipe_option) { 641 read_waitpipe(p2c_waitpipe); 642 close_waitpipe(0); 643 } 644 645 execvp(path, argv); 646 perror("Couldn't exec"); 647 ExitProgram(1); 648} 649 650void 651parent(int pid GCC_UNUSED, int pty) 652{ 653 unsigned char buf[BUFFER_SIZE]; 654 int i; 655 int rc; 656 657 if (pipe_option) { 658 read_waitpipe(c2p_waitpipe); 659 } 660 661 if (verbose) { 662 reportIso2022(outputState); 663 } 664 setup_io(pty); 665 666 if (pipe_option) { 667 write_waitpipe(p2c_waitpipe); 668 close_waitpipe(1); 669 } 670 671 for (;;) { 672 rc = waitForInput(0, pty); 673 674 if (sigwinch_queued) { 675 sigwinch_queued = 0; 676 setWindowSize(0, pty); 677 } 678 679 if (sigchld_queued && exitOnChild) 680 break; 681 682 if (rc > 0) { 683 if (rc & 2) { 684 i = (int) read(pty, buf, (size_t) BUFFER_SIZE); 685 if ((i == 0) || ((i < 0) && (errno != EAGAIN))) 686 break; 687 if (i > 0) 688 copyOut(outputState, 0, buf, (unsigned) i); 689 } 690 if (rc & 1) { 691 i = (int) read(0, buf, (size_t) BUFFER_SIZE); 692 if ((i == 0) || ((i < 0) && (errno != EAGAIN))) 693 break; 694 if (i > 0) 695 copyIn(inputState, pty, buf, i); 696 } 697 } 698 } 699 700 restoreTermios(); 701 cleanup_io(pty); 702} 703 704#ifdef NO_LEAKS 705void 706luit_leaks(void) 707{ 708 destroyIso2022(inputState); 709 destroyIso2022(outputState); 710} 711#endif 712