1 /* $OpenBSD: tic.c,v 1.34 2019/06/28 13:35:04 deraadt Exp $ */ 2 3 /**************************************************************************** 4 * Copyright (c) 1998-2007,2008 Free Software Foundation, Inc. * 5 * * 6 * Permission is hereby granted, free of charge, to any person obtaining a * 7 * copy of this software and associated documentation files (the * 8 * "Software"), to deal in the Software without restriction, including * 9 * without limitation the rights to use, copy, modify, merge, publish, * 10 * distribute, distribute with modifications, sublicense, and/or sell * 11 * copies of the Software, and to permit persons to whom the Software is * 12 * furnished to do so, subject to the following conditions: * 13 * * 14 * The above copyright notice and this permission notice shall be included * 15 * in all copies or substantial portions of the Software. * 16 * * 17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * 18 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * 19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * 20 * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * 21 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * 22 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR * 23 * THE USE OR OTHER DEALINGS IN THE SOFTWARE. * 24 * * 25 * Except as contained in this notice, the name(s) of the above copyright * 26 * holders shall not be used in advertising or otherwise to promote the * 27 * sale, use or other dealings in this Software without prior written * 28 * authorization. * 29 ****************************************************************************/ 30 31 /**************************************************************************** 32 * Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995 * 33 * and: Eric S. Raymond <esr@snark.thyrsus.com> * 34 * and: Thomas E. Dickey 1996 on * 35 ****************************************************************************/ 36 37 /* 38 * tic.c --- Main program for terminfo compiler 39 * by Eric S. Raymond 40 * 41 */ 42 43 #include <progs.priv.h> 44 #include <sys/stat.h> 45 46 #include <dump_entry.h> 47 #include <transform.h> 48 49 MODULE_ID("$Id: tic.c,v 1.34 2019/06/28 13:35:04 deraadt Exp $") 50 51 const char *_nc_progname = "tic"; 52 53 static FILE *log_fp; 54 static FILE *tmp_fp; 55 static bool capdump = FALSE; /* running as infotocap? */ 56 static bool infodump = FALSE; /* running as captoinfo? */ 57 static bool showsummary = FALSE; 58 static const char *to_remove; 59 60 static void (*save_check_termtype) (TERMTYPE *, bool); 61 static void check_termtype(TERMTYPE *tt, bool); 62 63 static const char usage_string[] = "\ 64 [-e names] \ 65 [-o dir] \ 66 [-R name] \ 67 [-v[n]] \ 68 [-V] \ 69 [-w[n]] \ 70 [-\ 71 1\ 72 a\ 73 C\ 74 c\ 75 f\ 76 G\ 77 g\ 78 I\ 79 L\ 80 N\ 81 r\ 82 s\ 83 T\ 84 t\ 85 U\ 86 x\ 87 ] \ 88 source-file\n"; 89 90 #if NO_LEAKS 91 static void 92 free_namelist(char **src) 93 { 94 if (src != 0) { 95 int n; 96 for (n = 0; src[n] != 0; ++n) 97 free(src[n]); 98 free(src); 99 } 100 } 101 #endif 102 103 static void 104 cleanup(char **namelst GCC_UNUSED) 105 { 106 #if NO_LEAKS 107 free_namelist(namelst); 108 #endif 109 if (tmp_fp != 0) 110 fclose(tmp_fp); 111 if (to_remove != 0) { 112 #if HAVE_REMOVE 113 remove(to_remove); 114 #else 115 unlink(to_remove); 116 #endif 117 } 118 } 119 120 static void 121 failed(const char *msg) 122 { 123 perror(msg); 124 cleanup((char **) 0); 125 ExitProgram(EXIT_FAILURE); 126 } 127 128 static void 129 usage(void) 130 { 131 static const char *const tbl[] = 132 { 133 "Options:", 134 " -1 format translation output one capability per line", 135 #if NCURSES_XNAMES 136 " -a retain commented-out capabilities (sets -x also)", 137 #endif 138 " -C translate entries to termcap source form", 139 " -c check only, validate input without compiling or translating", 140 " -e<names> translate/compile only entries named by comma-separated list", 141 " -f format complex strings for readability", 142 " -G format %{number} to %'char'", 143 " -g format %'char' to %{number}", 144 " -I translate entries to terminfo source form", 145 " -L translate entries to full terminfo source form", 146 " -N disable smart defaults for source translation", 147 " -o<dir> set output directory for compiled entry writes", 148 " -R<name> restrict translation to given terminfo/termcap version", 149 " -r force resolution of all use entries in source translation", 150 " -s print summary statistics", 151 " -T remove size-restrictions on compiled description", 152 #if NCURSES_XNAMES 153 " -t suppress commented-out capabilities", 154 #endif 155 " -U suppress post-processing of entries", 156 " -V print version", 157 " -v[n] set verbosity level", 158 " -w[n] set format width for translation output", 159 #if NCURSES_XNAMES 160 " -x treat unknown capabilities as user-defined", 161 #endif 162 "", 163 "Parameters:", 164 " <file> file to translate or compile" 165 }; 166 size_t j; 167 168 fprintf(stderr, "Usage: %s %s\n", _nc_progname, usage_string); 169 for (j = 0; j < SIZEOF(tbl); j++) { 170 fputs(tbl[j], stderr); 171 putc('\n', stderr); 172 } 173 ExitProgram(EXIT_FAILURE); 174 } 175 176 #define L_BRACE '{' 177 #define R_BRACE '}' 178 #define S_QUOTE '\''; 179 180 static void 181 write_it(ENTRY * ep) 182 { 183 unsigned n; 184 int ch; 185 char *s, *d, *t; 186 char result[MAX_ENTRY_SIZE]; 187 188 /* 189 * Look for strings that contain %{number}, convert them to %'char', 190 * which is shorter and runs a little faster. 191 */ 192 for (n = 0; n < STRCOUNT; n++) { 193 s = ep->tterm.Strings[n]; 194 if (VALID_STRING(s) 195 && strchr(s, L_BRACE) != 0) { 196 d = result; 197 t = s; 198 while ((ch = *t++) != 0) { 199 *d++ = (char) ch; 200 if (ch == '\\') { 201 *d++ = *t++; 202 } else if ((ch == '%') 203 && (*t == L_BRACE)) { 204 char *v = 0; 205 long value = strtol(t + 1, &v, 0); 206 if (v != 0 207 && *v == R_BRACE 208 && value > 0 209 && value != '\\' /* FIXME */ 210 && value < 127 211 && isprint((int) value)) { 212 *d++ = S_QUOTE; 213 *d++ = (char) value; 214 *d++ = S_QUOTE; 215 t = (v + 1); 216 } 217 } 218 } 219 *d = 0; 220 if (strlen(result) < strlen(s)) { 221 /* new string is same length as what is there, or shorter */ 222 strlcpy(s, result, strlen(s)); 223 } 224 } 225 } 226 227 _nc_set_type(_nc_first_name(ep->tterm.term_names)); 228 _nc_curr_line = ep->startline; 229 _nc_write_entry(&ep->tterm); 230 } 231 232 static bool 233 immedhook(ENTRY * ep GCC_UNUSED) 234 /* write out entries with no use capabilities immediately to save storage */ 235 { 236 #if !HAVE_BIG_CORE 237 /* 238 * This is strictly a core-economy kluge. The really clean way to handle 239 * compilation is to slurp the whole file into core and then do all the 240 * name-collision checks and entry writes in one swell foop. But the 241 * terminfo master file is large enough that some core-poor systems swap 242 * like crazy when you compile it this way...there have been reports of 243 * this process taking *three hours*, rather than the twenty seconds or 244 * less typical on my development box. 245 * 246 * So. This hook *immediately* writes out the referenced entry if it 247 * has no use capabilities. The compiler main loop refrains from 248 * adding the entry to the in-core list when this hook fires. If some 249 * other entry later needs to reference an entry that got written 250 * immediately, that's OK; the resolution code will fetch it off disk 251 * when it can't find it in core. 252 * 253 * Name collisions will still be detected, just not as cleanly. The 254 * write_entry() code complains before overwriting an entry that 255 * postdates the time of tic's first call to write_entry(). Thus 256 * it will complain about overwriting entries newly made during the 257 * tic run, but not about overwriting ones that predate it. 258 * 259 * The reason this is a hook, and not in line with the rest of the 260 * compiler code, is that the support for termcap fallback cannot assume 261 * it has anywhere to spool out these entries! 262 * 263 * The _nc_set_type() call here requires a compensating one in 264 * _nc_parse_entry(). 265 * 266 * If you define HAVE_BIG_CORE, you'll disable this kluge. This will 267 * make tic a bit faster (because the resolution code won't have to do 268 * disk I/O nearly as often). 269 */ 270 if (ep->nuses == 0) { 271 int oldline = _nc_curr_line; 272 273 write_it(ep); 274 _nc_curr_line = oldline; 275 free(ep->tterm.str_table); 276 return (TRUE); 277 } 278 #endif /* HAVE_BIG_CORE */ 279 return (FALSE); 280 } 281 282 static void 283 put_translate(int c) 284 /* emit a comment char, translating terminfo names to termcap names */ 285 { 286 static bool in_name = FALSE; 287 static size_t have, used; 288 static char *namebuf, *suffix; 289 290 if (in_name) { 291 if (used + 1 >= have) { 292 have += 132; 293 namebuf = typeRealloc(char, have, namebuf); 294 suffix = typeRealloc(char, have, suffix); 295 } 296 if (c == '\n' || c == '@') { 297 namebuf[used++] = '\0'; 298 (void) putchar('<'); 299 (void) fputs(namebuf, stdout); 300 putchar(c); 301 in_name = FALSE; 302 } else if (c != '>') { 303 namebuf[used++] = (char) c; 304 } else { /* ah! candidate name! */ 305 char *up; 306 NCURSES_CONST char *tp; 307 308 namebuf[used++] = '\0'; 309 in_name = FALSE; 310 311 suffix[0] = '\0'; 312 if ((up = strchr(namebuf, '#')) != 0 313 || (up = strchr(namebuf, '=')) != 0 314 || ((up = strchr(namebuf, '@')) != 0 && up[1] == '>')) { 315 (void) strlcpy(suffix, up, have); 316 *up = '\0'; 317 } 318 319 if ((tp = nametrans(namebuf)) != 0) { 320 (void) putchar(':'); 321 (void) fputs(tp, stdout); 322 (void) fputs(suffix, stdout); 323 (void) putchar(':'); 324 } else { 325 /* couldn't find a translation, just dump the name */ 326 (void) putchar('<'); 327 (void) fputs(namebuf, stdout); 328 (void) fputs(suffix, stdout); 329 (void) putchar('>'); 330 } 331 } 332 } else { 333 used = 0; 334 if (c == '<') { 335 in_name = TRUE; 336 } else { 337 putchar(c); 338 } 339 } 340 } 341 342 /* Returns a string, stripped of leading/trailing whitespace */ 343 static char * 344 stripped(char *src) 345 { 346 while (isspace(UChar(*src))) 347 src++; 348 if (*src != '\0') { 349 char *dst; 350 size_t len; 351 352 if ((dst = strdup(src)) == NULL) 353 failed("strdup"); 354 len = strlen(dst); 355 while (--len != 0 && isspace(UChar(dst[len]))) 356 dst[len] = '\0'; 357 return dst; 358 } 359 return 0; 360 } 361 362 static FILE * 363 open_input(const char *filename) 364 { 365 FILE *fp = fopen(filename, "r"); 366 struct stat sb; 367 368 if (fp == 0) { 369 fprintf(stderr, "%s: Can't open %s\n", _nc_progname, filename); 370 ExitProgram(EXIT_FAILURE); 371 } 372 if (fstat(fileno(fp), &sb) == -1 373 || (sb.st_mode & S_IFMT) != S_IFREG) { 374 fprintf(stderr, "%s: %s is not a file\n", _nc_progname, filename); 375 ExitProgram(EXIT_FAILURE); 376 } 377 return fp; 378 } 379 380 /* Parse the "-e" option-value into a list of names */ 381 static char ** 382 make_namelist(char *src) 383 { 384 char **dst = 0; 385 386 char *s, *base; 387 unsigned pass, n, nn; 388 char buffer[BUFSIZ]; 389 390 if (src == 0) { 391 /* EMPTY */ ; 392 } else if (strchr(src, '/') != 0) { /* a filename */ 393 FILE *fp = open_input(src); 394 395 for (pass = 1; pass <= 2; pass++) { 396 nn = 0; 397 while (fgets(buffer, sizeof(buffer), fp) != NULL) { 398 if ((s = stripped(buffer)) != 0) { 399 if (dst != 0) 400 dst[nn] = s; 401 else 402 free(s); 403 nn++; 404 } 405 } 406 if (pass == 1) { 407 dst = typeCalloc(char *, nn + 1); 408 rewind(fp); 409 } 410 } 411 fclose(fp); 412 } else { /* literal list of names */ 413 for (pass = 1; pass <= 2; pass++) { 414 for (n = nn = 0, base = src;; n++) { 415 int mark = src[n]; 416 if (mark == ',' || mark == '\0') { 417 if (pass == 1) { 418 nn++; 419 } else { 420 src[n] = '\0'; 421 if ((s = stripped(base)) != 0) 422 dst[nn++] = s; 423 base = &src[n + 1]; 424 } 425 } 426 if (mark == '\0') 427 break; 428 } 429 if (pass == 1) 430 dst = typeCalloc(char *, nn + 1); 431 } 432 } 433 if (showsummary && (dst != 0)) { 434 fprintf(log_fp, "Entries that will be compiled:\n"); 435 for (n = 0; dst[n] != 0; n++) 436 fprintf(log_fp, "%u:%s\n", n + 1, dst[n]); 437 } 438 return dst; 439 } 440 441 static bool 442 matches(char **needle, const char *haystack) 443 /* does entry in needle list match |-separated field in haystack? */ 444 { 445 bool code = FALSE; 446 size_t n; 447 448 if (needle != 0) { 449 for (n = 0; needle[n] != 0; n++) { 450 if (_nc_name_match(haystack, needle[n], "|")) { 451 code = TRUE; 452 break; 453 } 454 } 455 } else 456 code = TRUE; 457 return (code); 458 } 459 460 static FILE * 461 open_tempfile(char *name) 462 { 463 FILE *result = 0; 464 #if HAVE_MKSTEMP 465 int fd = mkstemp(name); 466 if (fd >= 0) 467 result = fdopen(fd, "w"); 468 #else 469 if (tmpnam(name) != 0) 470 result = fopen(name, "w"); 471 #endif 472 return result; 473 } 474 475 int 476 main(int argc, char *argv[]) 477 { 478 char my_tmpname[PATH_MAX]; 479 int v_opt = -1, debug_level; 480 int smart_defaults = TRUE; 481 char *termcap; 482 ENTRY *qp; 483 484 int this_opt, last_opt = '?'; 485 486 int outform = F_TERMINFO; /* output format */ 487 int sortmode = S_TERMINFO; /* sort_mode */ 488 489 int width = 60; 490 bool formatted = FALSE; /* reformat complex strings? */ 491 bool literal = FALSE; /* suppress post-processing? */ 492 int numbers = 0; /* format "%'char'" to/from "%{number}" */ 493 bool forceresolve = FALSE; /* force resolution */ 494 bool limited = TRUE; 495 char *tversion = (char *) NULL; 496 const char *source_file = "terminfo"; 497 char **namelst = 0; 498 char *outdir = (char *) NULL; 499 bool check_only = FALSE; 500 bool suppress_untranslatable = FALSE; 501 502 if (pledge("stdio rpath wpath cpath", NULL) == -1) { 503 perror("pledge"); 504 exit(1); 505 } 506 507 log_fp = stderr; 508 509 _nc_progname = _nc_rootname(argv[0]); 510 511 if ((infodump = (strcmp(_nc_progname, PROG_CAPTOINFO) == 0)) != FALSE) { 512 outform = F_TERMINFO; 513 sortmode = S_TERMINFO; 514 } 515 if ((capdump = (strcmp(_nc_progname, PROG_INFOTOCAP) == 0)) != FALSE) { 516 outform = F_TERMCAP; 517 sortmode = S_TERMCAP; 518 } 519 #if NCURSES_XNAMES 520 use_extended_names(FALSE); 521 #endif 522 523 /* 524 * Processing arguments is a little complicated, since someone made a 525 * design decision to allow the numeric values for -w, -v options to 526 * be optional. 527 */ 528 while ((this_opt = getopt(argc, argv, 529 "0123456789CILNR:TUVace:fGgo:rstvwx")) != -1) { 530 if (isdigit(this_opt)) { 531 switch (last_opt) { 532 case 'v': 533 v_opt = (v_opt * 10) + (this_opt - '0'); 534 break; 535 case 'w': 536 width = (width * 10) + (this_opt - '0'); 537 break; 538 default: 539 if (this_opt != '1') 540 usage(); 541 last_opt = this_opt; 542 width = 0; 543 } 544 continue; 545 } 546 switch (this_opt) { 547 case 'C': 548 capdump = TRUE; 549 outform = F_TERMCAP; 550 sortmode = S_TERMCAP; 551 break; 552 case 'I': 553 infodump = TRUE; 554 outform = F_TERMINFO; 555 sortmode = S_TERMINFO; 556 break; 557 case 'L': 558 infodump = TRUE; 559 outform = F_VARIABLE; 560 sortmode = S_VARIABLE; 561 break; 562 case 'N': 563 smart_defaults = FALSE; 564 literal = TRUE; 565 break; 566 case 'R': 567 tversion = optarg; 568 break; 569 case 'T': 570 limited = FALSE; 571 break; 572 case 'U': 573 literal = TRUE; 574 break; 575 case 'V': 576 puts(curses_version()); 577 cleanup(namelst); 578 ExitProgram(EXIT_SUCCESS); 579 case 'c': 580 check_only = TRUE; 581 break; 582 case 'e': 583 namelst = make_namelist(optarg); 584 break; 585 case 'f': 586 formatted = TRUE; 587 break; 588 case 'G': 589 numbers = 1; 590 break; 591 case 'g': 592 numbers = -1; 593 break; 594 case 'o': 595 outdir = optarg; 596 break; 597 case 'r': 598 forceresolve = TRUE; 599 break; 600 case 's': 601 showsummary = TRUE; 602 break; 603 case 'v': 604 v_opt = 0; 605 break; 606 case 'w': 607 width = 0; 608 break; 609 #if NCURSES_XNAMES 610 case 't': 611 _nc_disable_period = FALSE; 612 suppress_untranslatable = TRUE; 613 break; 614 case 'a': 615 _nc_disable_period = TRUE; 616 /* FALLTHRU */ 617 case 'x': 618 use_extended_names(TRUE); 619 break; 620 #endif 621 default: 622 usage(); 623 } 624 last_opt = this_opt; 625 } 626 627 debug_level = (v_opt > 0) ? v_opt : (v_opt == 0); 628 set_trace_level(debug_level); 629 630 if (_nc_tracing) { 631 save_check_termtype = _nc_check_termtype2; 632 _nc_check_termtype2 = check_termtype; 633 } 634 #if !HAVE_BIG_CORE 635 /* 636 * Aaargh! immedhook seriously hoses us! 637 * 638 * One problem with immedhook is it means we can't do -e. Problem 639 * is that we can't guarantee that for each terminal listed, all the 640 * terminals it depends on will have been kept in core for reference 641 * resolution -- in fact it's certain the primitive types at the end 642 * of reference chains *won't* be in core unless they were explicitly 643 * in the select list themselves. 644 */ 645 if (namelst && (!infodump && !capdump)) { 646 (void) fprintf(stderr, 647 "Sorry, -e can't be used without -I or -C\n"); 648 cleanup(namelst); 649 ExitProgram(EXIT_FAILURE); 650 } 651 #endif /* HAVE_BIG_CORE */ 652 653 if (optind < argc) { 654 source_file = argv[optind++]; 655 if (optind < argc) { 656 fprintf(stderr, 657 "%s: Too many file names. Usage:\n\t%s %s", 658 _nc_progname, 659 _nc_progname, 660 usage_string); 661 ExitProgram(EXIT_FAILURE); 662 } 663 } else { 664 if (infodump == TRUE) { 665 /* captoinfo's no-argument case */ 666 source_file = "/etc/termcap"; 667 if ((termcap = getenv("TERMCAP")) != 0 668 && (namelst = make_namelist(getenv("TERM"))) != 0) { 669 strlcpy(my_tmpname, "/tmp/XXXXXXXXXX", sizeof my_tmpname); 670 if (access(termcap, F_OK) == 0) { 671 /* file exists */ 672 source_file = termcap; 673 } else if ((tmp_fp = open_tempfile(my_tmpname)) != 0) { 674 source_file = my_tmpname; 675 fprintf(tmp_fp, "%s\n", termcap); 676 fclose(tmp_fp); 677 tmp_fp = open_input(source_file); 678 to_remove = source_file; 679 } else { 680 failed("tmpnam"); 681 } 682 } 683 } else { 684 /* tic */ 685 fprintf(stderr, 686 "%s: File name needed. Usage:\n\t%s %s", 687 _nc_progname, 688 _nc_progname, 689 usage_string); 690 cleanup(namelst); 691 ExitProgram(EXIT_FAILURE); 692 } 693 } 694 695 if (tmp_fp == 0) 696 tmp_fp = open_input(source_file); 697 698 if (infodump) 699 dump_init(tversion, 700 smart_defaults 701 ? outform 702 : F_LITERAL, 703 sortmode, width, debug_level, formatted); 704 else if (capdump) 705 dump_init(tversion, 706 outform, 707 sortmode, width, debug_level, FALSE); 708 709 /* parse entries out of the source file */ 710 _nc_set_source(source_file); 711 #if !HAVE_BIG_CORE 712 if (!(check_only || infodump || capdump)) 713 _nc_set_writedir(outdir); 714 #endif /* HAVE_BIG_CORE */ 715 _nc_read_entry_source(tmp_fp, (char *) NULL, 716 !smart_defaults || literal, FALSE, 717 ((check_only || infodump || capdump) 718 ? NULLHOOK 719 : immedhook)); 720 721 /* do use resolution */ 722 if (check_only || (!infodump && !capdump) || forceresolve) { 723 if (!_nc_resolve_uses2(TRUE, literal) && !check_only) { 724 cleanup(namelst); 725 ExitProgram(EXIT_FAILURE); 726 } 727 } 728 729 /* length check */ 730 if (check_only && (capdump || infodump)) { 731 for_entry_list(qp) { 732 if (matches(namelst, qp->tterm.term_names)) { 733 int len = fmt_entry(&qp->tterm, NULL, FALSE, TRUE, infodump, numbers); 734 735 if (len > (infodump ? MAX_TERMINFO_LENGTH : MAX_TERMCAP_LENGTH)) 736 (void) fprintf(stderr, 737 "warning: resolved %s entry is %d bytes long\n", 738 _nc_first_name(qp->tterm.term_names), 739 len); 740 } 741 } 742 } 743 744 /* write or dump all entries */ 745 if (!check_only) { 746 if (!infodump && !capdump) { 747 _nc_set_writedir(outdir); 748 for_entry_list(qp) { 749 if (matches(namelst, qp->tterm.term_names)) 750 write_it(qp); 751 } 752 } else { 753 /* this is in case infotocap() generates warnings */ 754 _nc_curr_col = _nc_curr_line = -1; 755 756 for_entry_list(qp) { 757 if (matches(namelst, qp->tterm.term_names)) { 758 int j = qp->cend - qp->cstart; 759 int len = 0; 760 761 /* this is in case infotocap() generates warnings */ 762 _nc_set_type(_nc_first_name(qp->tterm.term_names)); 763 764 (void) fseek(tmp_fp, qp->cstart, SEEK_SET); 765 while (j-- > 0) { 766 if (infodump) 767 (void) putchar(fgetc(tmp_fp)); 768 else 769 put_translate(fgetc(tmp_fp)); 770 } 771 772 dump_entry(&qp->tterm, suppress_untranslatable, 773 limited, numbers, NULL); 774 for (j = 0; j < (int) qp->nuses; j++) 775 dump_uses(qp->uses[j].name, !capdump); 776 len = show_entry(); 777 if (debug_level != 0 && !limited) 778 printf("# length=%d\n", len); 779 } 780 } 781 if (!namelst && _nc_tail) { 782 int c, oldc = '\0'; 783 bool in_comment = FALSE; 784 bool trailing_comment = FALSE; 785 786 (void) fseek(tmp_fp, _nc_tail->cend, SEEK_SET); 787 while ((c = fgetc(tmp_fp)) != EOF) { 788 if (oldc == '\n') { 789 if (c == '#') { 790 trailing_comment = TRUE; 791 in_comment = TRUE; 792 } else { 793 in_comment = FALSE; 794 } 795 } 796 if (trailing_comment 797 && (in_comment || (oldc == '\n' && c == '\n'))) 798 putchar(c); 799 oldc = c; 800 } 801 } 802 } 803 } 804 805 /* Show the directory into which entries were written, and the total 806 * number of entries 807 */ 808 if (showsummary 809 && (!(check_only || infodump || capdump))) { 810 int total = _nc_tic_written(); 811 if (total != 0) 812 fprintf(log_fp, "%d entries written to %s\n", 813 total, 814 _nc_tic_dir((char *) 0)); 815 else 816 fprintf(log_fp, "No entries written\n"); 817 } 818 cleanup(namelst); 819 ExitProgram(EXIT_SUCCESS); 820 } 821 822 /* 823 * This bit of legerdemain turns all the terminfo variable names into 824 * references to locations in the arrays Booleans, Numbers, and Strings --- 825 * precisely what's needed (see comp_parse.c). 826 */ 827 #undef CUR 828 #define CUR tp-> 829 830 /* 831 * Check if the alternate character-set capabilities are consistent. 832 */ 833 static void 834 check_acs(TERMTYPE *tp) 835 { 836 if (VALID_STRING(acs_chars)) { 837 const char *boxes = "lmkjtuvwqxn"; 838 char mapped[256]; 839 char missing[256]; 840 const char *p; 841 char *q; 842 843 memset(mapped, 0, sizeof(mapped)); 844 for (p = acs_chars; *p != '\0'; p += 2) { 845 if (p[1] == '\0') { 846 _nc_warning("acsc has odd number of characters"); 847 break; 848 } 849 mapped[UChar(p[0])] = p[1]; 850 } 851 852 if (mapped[UChar('I')] && !mapped[UChar('i')]) { 853 _nc_warning("acsc refers to 'I', which is probably an error"); 854 } 855 856 for (p = boxes, q = missing; *p != '\0'; ++p) { 857 if (!mapped[UChar(p[0])]) { 858 *q++ = p[0]; 859 } 860 } 861 *q = '\0'; 862 863 assert(strlen(missing) <= strlen(boxes)); 864 if (*missing != '\0' && strcmp(missing, boxes)) { 865 _nc_warning("acsc is missing some line-drawing mapping: %s", missing); 866 } 867 } 868 } 869 870 /* 871 * Check if the color capabilities are consistent 872 */ 873 static void 874 check_colors(TERMTYPE *tp) 875 { 876 if ((max_colors > 0) != (max_pairs > 0) 877 || ((max_colors > max_pairs) && (initialize_pair == 0))) 878 _nc_warning("inconsistent values for max_colors (%d) and max_pairs (%d)", 879 max_colors, max_pairs); 880 881 PAIRED(set_foreground, set_background); 882 PAIRED(set_a_foreground, set_a_background); 883 PAIRED(set_color_pair, initialize_pair); 884 885 if (VALID_STRING(set_foreground) 886 && VALID_STRING(set_a_foreground) 887 && !_nc_capcmp(set_foreground, set_a_foreground)) 888 _nc_warning("expected setf/setaf to be different"); 889 890 if (VALID_STRING(set_background) 891 && VALID_STRING(set_a_background) 892 && !_nc_capcmp(set_background, set_a_background)) 893 _nc_warning("expected setb/setab to be different"); 894 895 /* see: has_colors() */ 896 if (VALID_NUMERIC(max_colors) && VALID_NUMERIC(max_pairs) 897 && (((set_foreground != NULL) 898 && (set_background != NULL)) 899 || ((set_a_foreground != NULL) 900 && (set_a_background != NULL)) 901 || set_color_pair)) { 902 if (!VALID_STRING(orig_pair) && !VALID_STRING(orig_colors)) 903 _nc_warning("expected either op/oc string for resetting colors"); 904 } 905 } 906 907 static char 908 keypad_final(const char *string) 909 { 910 char result = '\0'; 911 912 if (VALID_STRING(string) 913 && *string++ == '\033' 914 && *string++ == 'O' 915 && strlen(string) == 1) { 916 result = *string; 917 } 918 919 return result; 920 } 921 922 static int 923 keypad_index(const char *string) 924 { 925 char *test; 926 const char *list = "PQRSwxymtuvlqrsPpn"; /* app-keypad except "Enter" */ 927 int ch; 928 int result = -1; 929 930 if ((ch = keypad_final(string)) != '\0') { 931 test = strchr(list, ch); 932 if (test != 0) 933 result = (test - list); 934 } 935 return result; 936 } 937 938 #define MAX_KP 5 939 /* 940 * Do a quick sanity-check for vt100-style keypads to see if the 5-key keypad 941 * is mapped inconsistently. 942 */ 943 static void 944 check_keypad(TERMTYPE *tp) 945 { 946 char show[80]; 947 948 if (VALID_STRING(key_a1) && 949 VALID_STRING(key_a3) && 950 VALID_STRING(key_b2) && 951 VALID_STRING(key_c1) && 952 VALID_STRING(key_c3)) { 953 char final[MAX_KP + 1]; 954 int list[MAX_KP]; 955 int increase = 0; 956 int j, k, kk; 957 int last; 958 int test; 959 960 final[0] = keypad_final(key_a1); 961 final[1] = keypad_final(key_a3); 962 final[2] = keypad_final(key_b2); 963 final[3] = keypad_final(key_c1); 964 final[4] = keypad_final(key_c3); 965 final[5] = '\0'; 966 967 /* special case: legacy coding using 1,2,3,0,. on the bottom */ 968 assert(strlen(final) <= MAX_KP); 969 if (!strcmp(final, "qsrpn")) 970 return; 971 972 list[0] = keypad_index(key_a1); 973 list[1] = keypad_index(key_a3); 974 list[2] = keypad_index(key_b2); 975 list[3] = keypad_index(key_c1); 976 list[4] = keypad_index(key_c3); 977 978 /* check that they're all vt100 keys */ 979 for (j = 0; j < MAX_KP; ++j) { 980 if (list[j] < 0) { 981 return; 982 } 983 } 984 985 /* check if they're all in increasing order */ 986 for (j = 1; j < MAX_KP; ++j) { 987 if (list[j] > list[j - 1]) { 988 ++increase; 989 } 990 } 991 if (increase != (MAX_KP - 1)) { 992 show[0] = '\0'; 993 994 for (j = 0, last = -1; j < MAX_KP; ++j) { 995 for (k = 0, kk = -1, test = 100; k < 5; ++k) { 996 if (list[k] > last && 997 list[k] < test) { 998 test = list[k]; 999 kk = k; 1000 } 1001 } 1002 last = test; 1003 assert(strlen(show) < (MAX_KP * 4)); 1004 switch (kk) { 1005 case 0: 1006 strlcat(show, " ka1", sizeof(show)); 1007 break; 1008 case 1: 1009 strlcat(show, " ka3", sizeof(show)); 1010 break; 1011 case 2: 1012 strlcat(show, " kb2", sizeof(show)); 1013 break; 1014 case 3: 1015 strlcat(show, " kc1", sizeof(show)); 1016 break; 1017 case 4: 1018 strlcat(show, " kc3", sizeof(show)); 1019 break; 1020 } 1021 } 1022 1023 _nc_warning("vt100 keypad order inconsistent: %s", show); 1024 } 1025 1026 } else if (VALID_STRING(key_a1) || 1027 VALID_STRING(key_a3) || 1028 VALID_STRING(key_b2) || 1029 VALID_STRING(key_c1) || 1030 VALID_STRING(key_c3)) { 1031 show[0] = '\0'; 1032 if (keypad_index(key_a1) >= 0) 1033 strlcat(show, " ka1", sizeof(show)); 1034 if (keypad_index(key_a3) >= 0) 1035 strlcat(show, " ka3", sizeof(show)); 1036 if (keypad_index(key_b2) >= 0) 1037 strlcat(show, " kb2", sizeof(show)); 1038 if (keypad_index(key_c1) >= 0) 1039 strlcat(show, " kc1", sizeof(show)); 1040 if (keypad_index(key_c3) >= 0) 1041 strlcat(show, " kc3", sizeof(show)); 1042 if (*show != '\0') 1043 _nc_warning("vt100 keypad map incomplete:%s", show); 1044 } 1045 } 1046 1047 /* 1048 * Returns the expected number of parameters for the given capability. 1049 */ 1050 static int 1051 expected_params(const char *name) 1052 { 1053 /* *INDENT-OFF* */ 1054 static const struct { 1055 const char *name; 1056 int count; 1057 } table[] = { 1058 { "S0", 1 }, /* 'screen' extension */ 1059 { "birep", 2 }, 1060 { "chr", 1 }, 1061 { "colornm", 1 }, 1062 { "cpi", 1 }, 1063 { "csnm", 1 }, 1064 { "csr", 2 }, 1065 { "cub", 1 }, 1066 { "cud", 1 }, 1067 { "cuf", 1 }, 1068 { "cup", 2 }, 1069 { "cuu", 1 }, 1070 { "cvr", 1 }, 1071 { "cwin", 5 }, 1072 { "dch", 1 }, 1073 { "defc", 3 }, 1074 { "dial", 1 }, 1075 { "dispc", 1 }, 1076 { "dl", 1 }, 1077 { "ech", 1 }, 1078 { "getm", 1 }, 1079 { "hpa", 1 }, 1080 { "ich", 1 }, 1081 { "il", 1 }, 1082 { "indn", 1 }, 1083 { "initc", 4 }, 1084 { "initp", 7 }, 1085 { "lpi", 1 }, 1086 { "mc5p", 1 }, 1087 { "mrcup", 2 }, 1088 { "mvpa", 1 }, 1089 { "pfkey", 2 }, 1090 { "pfloc", 2 }, 1091 { "pfx", 2 }, 1092 { "pfxl", 3 }, 1093 { "pln", 2 }, 1094 { "qdial", 1 }, 1095 { "rcsd", 1 }, 1096 { "rep", 2 }, 1097 { "rin", 1 }, 1098 { "sclk", 3 }, 1099 { "scp", 1 }, 1100 { "scs", 1 }, 1101 { "scsd", 2 }, 1102 { "setab", 1 }, 1103 { "setaf", 1 }, 1104 { "setb", 1 }, 1105 { "setcolor", 1 }, 1106 { "setf", 1 }, 1107 { "sgr", 9 }, 1108 { "sgr1", 6 }, 1109 { "slength", 1 }, 1110 { "slines", 1 }, 1111 { "smgbp", 1 }, /* 2 if smgtp is not given */ 1112 { "smglp", 1 }, 1113 { "smglr", 2 }, 1114 { "smgrp", 1 }, 1115 { "smgtb", 2 }, 1116 { "smgtp", 1 }, 1117 { "tsl", 1 }, 1118 { "u6", -1 }, 1119 { "vpa", 1 }, 1120 { "wind", 4 }, 1121 { "wingo", 1 }, 1122 }; 1123 /* *INDENT-ON* */ 1124 1125 unsigned n; 1126 int result = 0; /* function-keys, etc., use none */ 1127 1128 for (n = 0; n < SIZEOF(table); n++) { 1129 if (!strcmp(name, table[n].name)) { 1130 result = table[n].count; 1131 break; 1132 } 1133 } 1134 1135 return result; 1136 } 1137 1138 /* 1139 * Make a quick sanity check for the parameters which are used in the given 1140 * strings. If there are no "%p" tokens, then there should be no other "%" 1141 * markers. 1142 */ 1143 static void 1144 check_params(TERMTYPE *tp, const char *name, char *value) 1145 { 1146 int expected = expected_params(name); 1147 int actual = 0; 1148 int n; 1149 bool params[10]; 1150 char *s = value; 1151 1152 #ifdef set_top_margin_parm 1153 if (!strcmp(name, "smgbp") 1154 && set_top_margin_parm == 0) 1155 expected = 2; 1156 #endif 1157 1158 for (n = 0; n < 10; n++) 1159 params[n] = FALSE; 1160 1161 while (*s != 0) { 1162 if (*s == '%') { 1163 if (*++s == '\0') { 1164 _nc_warning("expected character after %% in %s", name); 1165 break; 1166 } else if (*s == 'p') { 1167 if (*++s == '\0' || !isdigit(UChar(*s))) { 1168 _nc_warning("expected digit after %%p in %s", name); 1169 return; 1170 } else { 1171 n = (*s - '0'); 1172 if (n > actual) 1173 actual = n; 1174 params[n] = TRUE; 1175 } 1176 } 1177 } 1178 s++; 1179 } 1180 1181 if (params[0]) { 1182 _nc_warning("%s refers to parameter 0 (%%p0), which is not allowed", name); 1183 } 1184 if (value == set_attributes || expected < 0) { 1185 ; 1186 } else if (expected != actual) { 1187 _nc_warning("%s uses %d parameters, expected %d", name, 1188 actual, expected); 1189 for (n = 1; n < actual; n++) { 1190 if (!params[n]) 1191 _nc_warning("%s omits parameter %d", name, n); 1192 } 1193 } 1194 } 1195 1196 static char * 1197 skip_delay(char *s) 1198 { 1199 while (*s == '/' || isdigit(UChar(*s))) 1200 ++s; 1201 return s; 1202 } 1203 1204 /* 1205 * Skip a delay altogether, e.g., when comparing a simple string to sgr, 1206 * the latter may have a worst-case delay on the end. 1207 */ 1208 static char * 1209 ignore_delays(char *s) 1210 { 1211 int delaying = 0; 1212 1213 do { 1214 switch (*s) { 1215 case '$': 1216 if (delaying == 0) 1217 delaying = 1; 1218 break; 1219 case '<': 1220 if (delaying == 1) 1221 delaying = 2; 1222 break; 1223 case '\0': 1224 delaying = 0; 1225 break; 1226 default: 1227 if (delaying) { 1228 s = skip_delay(s); 1229 if (*s == '>') 1230 ++s; 1231 delaying = 0; 1232 } 1233 break; 1234 } 1235 if (delaying) 1236 ++s; 1237 } while (delaying); 1238 return s; 1239 } 1240 1241 /* 1242 * An sgr string may contain several settings other than the one we're 1243 * interested in, essentially sgr0 + rmacs + whatever. As long as the 1244 * "whatever" is contained in the sgr string, that is close enough for our 1245 * sanity check. 1246 */ 1247 static bool 1248 similar_sgr(int num, char *a, char *b) 1249 { 1250 static const char *names[] = 1251 { 1252 "none" 1253 ,"standout" 1254 ,"underline" 1255 ,"reverse" 1256 ,"blink" 1257 ,"dim" 1258 ,"bold" 1259 ,"invis" 1260 ,"protect" 1261 ,"altcharset" 1262 }; 1263 char *base_a = a; 1264 char *base_b = b; 1265 int delaying = 0; 1266 1267 while (*b != 0) { 1268 while (*a != *b) { 1269 if (*a == 0) { 1270 if (b[0] == '$' 1271 && b[1] == '<') { 1272 _nc_warning("Did not find delay %s", _nc_visbuf(b)); 1273 } else { 1274 _nc_warning("checking sgr(%s) %s\n\tcompare to %s\n\tunmatched %s", 1275 names[num], _nc_visbuf2(1, base_a), 1276 _nc_visbuf2(2, base_b), 1277 _nc_visbuf2(3, b)); 1278 } 1279 return FALSE; 1280 } else if (delaying) { 1281 a = skip_delay(a); 1282 b = skip_delay(b); 1283 } else { 1284 a++; 1285 } 1286 } 1287 switch (*a) { 1288 case '$': 1289 if (delaying == 0) 1290 delaying = 1; 1291 break; 1292 case '<': 1293 if (delaying == 1) 1294 delaying = 2; 1295 break; 1296 default: 1297 delaying = 0; 1298 break; 1299 } 1300 a++; 1301 b++; 1302 } 1303 /* ignore delays on the end of the string */ 1304 a = ignore_delays(a); 1305 return ((num != 0) || (*a == 0)); 1306 } 1307 1308 static char * 1309 check_sgr(TERMTYPE *tp, char *zero, int num, char *cap, const char *name) 1310 { 1311 char *test; 1312 1313 _nc_tparm_err = 0; 1314 test = TPARM_9(set_attributes, 1315 num == 1, 1316 num == 2, 1317 num == 3, 1318 num == 4, 1319 num == 5, 1320 num == 6, 1321 num == 7, 1322 num == 8, 1323 num == 9); 1324 if (test != 0) { 1325 if (PRESENT(cap)) { 1326 if (!similar_sgr(num, test, cap)) { 1327 _nc_warning("%s differs from sgr(%d)\n\t%s=%s\n\tsgr(%d)=%s", 1328 name, num, 1329 name, _nc_visbuf2(1, cap), 1330 num, _nc_visbuf2(2, test)); 1331 } 1332 } else if (_nc_capcmp(test, zero)) { 1333 _nc_warning("sgr(%d) present, but not %s", num, name); 1334 } 1335 } else if (PRESENT(cap)) { 1336 _nc_warning("sgr(%d) missing, but %s present", num, name); 1337 } 1338 if (_nc_tparm_err) 1339 _nc_warning("stack error in sgr(%d) string", num); 1340 return test; 1341 } 1342 1343 #define CHECK_SGR(num,name) check_sgr(tp, zero, num, name, #name) 1344 1345 #ifdef TRACE 1346 /* 1347 * If tic is compiled with TRACE, we'll be able to see the output from the 1348 * DEBUG() macro. But since it doesn't use traceon(), it always goes to 1349 * the standard error. Use this function to make it simpler to follow the 1350 * resulting debug traces. 1351 */ 1352 static void 1353 show_where(unsigned level) 1354 { 1355 if (_nc_tracing >= DEBUG_LEVEL(level)) { 1356 char my_name[256]; 1357 _nc_get_type(my_name); 1358 fprintf(stderr, "\"%s\", line %d, '%s' ", 1359 _nc_get_source(), 1360 _nc_curr_line, my_name); 1361 } 1362 } 1363 1364 #else 1365 #define show_where(level) /* nothing */ 1366 #endif 1367 1368 /* other sanity-checks (things that we don't want in the normal 1369 * logic that reads a terminfo entry) 1370 */ 1371 static void 1372 check_termtype(TERMTYPE *tp, bool literal) 1373 { 1374 bool conflict = FALSE; 1375 unsigned j, k; 1376 char fkeys[STRCOUNT]; 1377 1378 /* 1379 * A terminal entry may contain more than one keycode assigned to 1380 * a given string (e.g., KEY_END and KEY_LL). But curses will only 1381 * return one (the last one assigned). 1382 */ 1383 if (!(_nc_syntax == SYN_TERMCAP && capdump)) { 1384 memset(fkeys, 0, sizeof(fkeys)); 1385 for (j = 0; _nc_tinfo_fkeys[j].code; j++) { 1386 char *a = tp->Strings[_nc_tinfo_fkeys[j].offset]; 1387 bool first = TRUE; 1388 if (!VALID_STRING(a)) 1389 continue; 1390 for (k = j + 1; _nc_tinfo_fkeys[k].code; k++) { 1391 char *b = tp->Strings[_nc_tinfo_fkeys[k].offset]; 1392 if (!VALID_STRING(b) 1393 || fkeys[k]) 1394 continue; 1395 if (!_nc_capcmp(a, b)) { 1396 fkeys[j] = 1; 1397 fkeys[k] = 1; 1398 if (first) { 1399 if (!conflict) { 1400 _nc_warning("Conflicting key definitions (using the last)"); 1401 conflict = TRUE; 1402 } 1403 fprintf(stderr, "... %s is the same as %s", 1404 keyname((int) _nc_tinfo_fkeys[j].code), 1405 keyname((int) _nc_tinfo_fkeys[k].code)); 1406 first = FALSE; 1407 } else { 1408 fprintf(stderr, ", %s", 1409 keyname((int) _nc_tinfo_fkeys[k].code)); 1410 } 1411 } 1412 } 1413 if (!first) 1414 fprintf(stderr, "\n"); 1415 } 1416 } 1417 1418 for (j = 0; j < NUM_STRINGS(tp); j++) { 1419 char *a = tp->Strings[j]; 1420 if (VALID_STRING(a)) 1421 check_params(tp, ExtStrname(tp, j, strnames), a); 1422 } 1423 1424 check_acs(tp); 1425 check_colors(tp); 1426 check_keypad(tp); 1427 1428 /* 1429 * These may be mismatched because the terminal description relies on 1430 * restoring the cursor visibility by resetting it. 1431 */ 1432 ANDMISSING(cursor_invisible, cursor_normal); 1433 ANDMISSING(cursor_visible, cursor_normal); 1434 1435 if (PRESENT(cursor_visible) && PRESENT(cursor_normal) 1436 && !_nc_capcmp(cursor_visible, cursor_normal)) 1437 _nc_warning("cursor_visible is same as cursor_normal"); 1438 1439 /* 1440 * From XSI & O'Reilly, we gather that sc/rc are required if csr is 1441 * given, because the cursor position after the scrolling operation is 1442 * performed is undefined. 1443 */ 1444 ANDMISSING(change_scroll_region, save_cursor); 1445 ANDMISSING(change_scroll_region, restore_cursor); 1446 1447 if (PRESENT(set_attributes)) { 1448 char *zero = 0; 1449 1450 _nc_tparm_err = 0; 1451 if (PRESENT(exit_attribute_mode)) { 1452 zero = strdup(CHECK_SGR(0, exit_attribute_mode)); 1453 } else { 1454 zero = strdup(TPARM_9(set_attributes, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 1455 } 1456 if (_nc_tparm_err) 1457 _nc_warning("stack error in sgr(0) string"); 1458 1459 if (zero != 0) { 1460 CHECK_SGR(1, enter_standout_mode); 1461 CHECK_SGR(2, enter_underline_mode); 1462 CHECK_SGR(3, enter_reverse_mode); 1463 CHECK_SGR(4, enter_blink_mode); 1464 CHECK_SGR(5, enter_dim_mode); 1465 CHECK_SGR(6, enter_bold_mode); 1466 CHECK_SGR(7, enter_secure_mode); 1467 CHECK_SGR(8, enter_protected_mode); 1468 CHECK_SGR(9, enter_alt_charset_mode); 1469 free(zero); 1470 } else { 1471 _nc_warning("sgr(0) did not return a value"); 1472 } 1473 } else if (PRESENT(exit_attribute_mode) && 1474 set_attributes != CANCELLED_STRING) { 1475 if (_nc_syntax == SYN_TERMINFO) 1476 _nc_warning("missing sgr string"); 1477 } 1478 1479 if (PRESENT(exit_attribute_mode)) { 1480 char *check_sgr0 = _nc_trim_sgr0(tp); 1481 1482 if (check_sgr0 == 0 || *check_sgr0 == '\0') { 1483 _nc_warning("trimmed sgr0 is empty"); 1484 } else { 1485 show_where(2); 1486 if (check_sgr0 != exit_attribute_mode) { 1487 DEBUG(2, 1488 ("will trim sgr0\n\toriginal sgr0=%s\n\ttrimmed sgr0=%s", 1489 _nc_visbuf2(1, exit_attribute_mode), 1490 _nc_visbuf2(2, check_sgr0))); 1491 free(check_sgr0); 1492 } else { 1493 DEBUG(2, 1494 ("will not trim sgr0\n\toriginal sgr0=%s", 1495 _nc_visbuf(exit_attribute_mode))); 1496 } 1497 } 1498 } 1499 #ifdef TRACE 1500 show_where(2); 1501 if (!auto_right_margin) { 1502 DEBUG(2, 1503 ("can write to lower-right directly")); 1504 } else if (PRESENT(enter_am_mode) && PRESENT(exit_am_mode)) { 1505 DEBUG(2, 1506 ("can write to lower-right by suppressing automargin")); 1507 } else if ((PRESENT(enter_insert_mode) && PRESENT(exit_insert_mode)) 1508 || PRESENT(insert_character) || PRESENT(parm_ich)) { 1509 DEBUG(2, 1510 ("can write to lower-right by using inserts")); 1511 } else { 1512 DEBUG(2, 1513 ("cannot write to lower-right")); 1514 } 1515 #endif 1516 1517 /* 1518 * Some standard applications (e.g., vi) and some non-curses 1519 * applications (e.g., jove) get confused if we have both ich1 and 1520 * smir/rmir. Let's be nice and warn about that, too, even though 1521 * ncurses handles it. 1522 */ 1523 if ((PRESENT(enter_insert_mode) || PRESENT(exit_insert_mode)) 1524 && PRESENT(parm_ich)) { 1525 _nc_warning("non-curses applications may be confused by ich1 with smir/rmir"); 1526 } 1527 1528 /* 1529 * Finally, do the non-verbose checks 1530 */ 1531 if (save_check_termtype != 0) 1532 save_check_termtype(tp, literal); 1533 } 1534