1 /* $OpenBSD: tic.c,v 1.30 2010/01/12 23:22:14 nicm 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.30 2010/01/12 23:22:14 nicm 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) < 0 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 log_fp = stderr; 503 504 _nc_progname = _nc_rootname(argv[0]); 505 506 if ((infodump = (strcmp(_nc_progname, PROG_CAPTOINFO) == 0)) != FALSE) { 507 outform = F_TERMINFO; 508 sortmode = S_TERMINFO; 509 } 510 if ((capdump = (strcmp(_nc_progname, PROG_INFOTOCAP) == 0)) != FALSE) { 511 outform = F_TERMCAP; 512 sortmode = S_TERMCAP; 513 } 514 #if NCURSES_XNAMES 515 use_extended_names(FALSE); 516 #endif 517 518 /* 519 * Processing arguments is a little complicated, since someone made a 520 * design decision to allow the numeric values for -w, -v options to 521 * be optional. 522 */ 523 while ((this_opt = getopt(argc, argv, 524 "0123456789CILNR:TUVace:fGgo:rstvwx")) != -1) { 525 if (isdigit(this_opt)) { 526 switch (last_opt) { 527 case 'v': 528 v_opt = (v_opt * 10) + (this_opt - '0'); 529 break; 530 case 'w': 531 width = (width * 10) + (this_opt - '0'); 532 break; 533 default: 534 if (this_opt != '1') 535 usage(); 536 last_opt = this_opt; 537 width = 0; 538 } 539 continue; 540 } 541 switch (this_opt) { 542 case 'C': 543 capdump = TRUE; 544 outform = F_TERMCAP; 545 sortmode = S_TERMCAP; 546 break; 547 case 'I': 548 infodump = TRUE; 549 outform = F_TERMINFO; 550 sortmode = S_TERMINFO; 551 break; 552 case 'L': 553 infodump = TRUE; 554 outform = F_VARIABLE; 555 sortmode = S_VARIABLE; 556 break; 557 case 'N': 558 smart_defaults = FALSE; 559 literal = TRUE; 560 break; 561 case 'R': 562 tversion = optarg; 563 break; 564 case 'T': 565 limited = FALSE; 566 break; 567 case 'U': 568 literal = TRUE; 569 break; 570 case 'V': 571 puts(curses_version()); 572 cleanup(namelst); 573 ExitProgram(EXIT_SUCCESS); 574 case 'c': 575 check_only = TRUE; 576 break; 577 case 'e': 578 namelst = make_namelist(optarg); 579 break; 580 case 'f': 581 formatted = TRUE; 582 break; 583 case 'G': 584 numbers = 1; 585 break; 586 case 'g': 587 numbers = -1; 588 break; 589 case 'o': 590 outdir = optarg; 591 break; 592 case 'r': 593 forceresolve = TRUE; 594 break; 595 case 's': 596 showsummary = TRUE; 597 break; 598 case 'v': 599 v_opt = 0; 600 break; 601 case 'w': 602 width = 0; 603 break; 604 #if NCURSES_XNAMES 605 case 't': 606 _nc_disable_period = FALSE; 607 suppress_untranslatable = TRUE; 608 break; 609 case 'a': 610 _nc_disable_period = TRUE; 611 /* FALLTHRU */ 612 case 'x': 613 use_extended_names(TRUE); 614 break; 615 #endif 616 default: 617 usage(); 618 } 619 last_opt = this_opt; 620 } 621 622 debug_level = (v_opt > 0) ? v_opt : (v_opt == 0); 623 set_trace_level(debug_level); 624 625 if (_nc_tracing) { 626 save_check_termtype = _nc_check_termtype2; 627 _nc_check_termtype2 = check_termtype; 628 } 629 #if !HAVE_BIG_CORE 630 /* 631 * Aaargh! immedhook seriously hoses us! 632 * 633 * One problem with immedhook is it means we can't do -e. Problem 634 * is that we can't guarantee that for each terminal listed, all the 635 * terminals it depends on will have been kept in core for reference 636 * resolution -- in fact it's certain the primitive types at the end 637 * of reference chains *won't* be in core unless they were explicitly 638 * in the select list themselves. 639 */ 640 if (namelst && (!infodump && !capdump)) { 641 (void) fprintf(stderr, 642 "Sorry, -e can't be used without -I or -C\n"); 643 cleanup(namelst); 644 ExitProgram(EXIT_FAILURE); 645 } 646 #endif /* HAVE_BIG_CORE */ 647 648 if (optind < argc) { 649 source_file = argv[optind++]; 650 if (optind < argc) { 651 fprintf(stderr, 652 "%s: Too many file names. Usage:\n\t%s %s", 653 _nc_progname, 654 _nc_progname, 655 usage_string); 656 ExitProgram(EXIT_FAILURE); 657 } 658 } else { 659 if (infodump == TRUE) { 660 /* captoinfo's no-argument case */ 661 source_file = "/etc/termcap"; 662 if ((termcap = getenv("TERMCAP")) != 0 663 && (namelst = make_namelist(getenv("TERM"))) != 0) { 664 strlcpy(my_tmpname, "/tmp/XXXXXXXXXX", sizeof my_tmpname); 665 if (access(termcap, F_OK) == 0) { 666 /* file exists */ 667 source_file = termcap; 668 } else if ((tmp_fp = open_tempfile(my_tmpname)) != 0) { 669 source_file = my_tmpname; 670 fprintf(tmp_fp, "%s\n", termcap); 671 fclose(tmp_fp); 672 tmp_fp = open_input(source_file); 673 to_remove = source_file; 674 } else { 675 failed("tmpnam"); 676 } 677 } 678 } else { 679 /* tic */ 680 fprintf(stderr, 681 "%s: File name needed. Usage:\n\t%s %s", 682 _nc_progname, 683 _nc_progname, 684 usage_string); 685 cleanup(namelst); 686 ExitProgram(EXIT_FAILURE); 687 } 688 } 689 690 if (tmp_fp == 0) 691 tmp_fp = open_input(source_file); 692 693 if (infodump) 694 dump_init(tversion, 695 smart_defaults 696 ? outform 697 : F_LITERAL, 698 sortmode, width, debug_level, formatted); 699 else if (capdump) 700 dump_init(tversion, 701 outform, 702 sortmode, width, debug_level, FALSE); 703 704 /* parse entries out of the source file */ 705 _nc_set_source(source_file); 706 #if !HAVE_BIG_CORE 707 if (!(check_only || infodump || capdump)) 708 _nc_set_writedir(outdir); 709 #endif /* HAVE_BIG_CORE */ 710 _nc_read_entry_source(tmp_fp, (char *) NULL, 711 !smart_defaults || literal, FALSE, 712 ((check_only || infodump || capdump) 713 ? NULLHOOK 714 : immedhook)); 715 716 /* do use resolution */ 717 if (check_only || (!infodump && !capdump) || forceresolve) { 718 if (!_nc_resolve_uses2(TRUE, literal) && !check_only) { 719 cleanup(namelst); 720 ExitProgram(EXIT_FAILURE); 721 } 722 } 723 724 /* length check */ 725 if (check_only && (capdump || infodump)) { 726 for_entry_list(qp) { 727 if (matches(namelst, qp->tterm.term_names)) { 728 int len = fmt_entry(&qp->tterm, NULL, FALSE, TRUE, infodump, numbers); 729 730 if (len > (infodump ? MAX_TERMINFO_LENGTH : MAX_TERMCAP_LENGTH)) 731 (void) fprintf(stderr, 732 "warning: resolved %s entry is %d bytes long\n", 733 _nc_first_name(qp->tterm.term_names), 734 len); 735 } 736 } 737 } 738 739 /* write or dump all entries */ 740 if (!check_only) { 741 if (!infodump && !capdump) { 742 _nc_set_writedir(outdir); 743 for_entry_list(qp) { 744 if (matches(namelst, qp->tterm.term_names)) 745 write_it(qp); 746 } 747 } else { 748 /* this is in case infotocap() generates warnings */ 749 _nc_curr_col = _nc_curr_line = -1; 750 751 for_entry_list(qp) { 752 if (matches(namelst, qp->tterm.term_names)) { 753 int j = qp->cend - qp->cstart; 754 int len = 0; 755 756 /* this is in case infotocap() generates warnings */ 757 _nc_set_type(_nc_first_name(qp->tterm.term_names)); 758 759 (void) fseek(tmp_fp, qp->cstart, SEEK_SET); 760 while (j-- > 0) { 761 if (infodump) 762 (void) putchar(fgetc(tmp_fp)); 763 else 764 put_translate(fgetc(tmp_fp)); 765 } 766 767 dump_entry(&qp->tterm, suppress_untranslatable, 768 limited, numbers, NULL); 769 for (j = 0; j < (int) qp->nuses; j++) 770 dump_uses(qp->uses[j].name, !capdump); 771 len = show_entry(); 772 if (debug_level != 0 && !limited) 773 printf("# length=%d\n", len); 774 } 775 } 776 if (!namelst && _nc_tail) { 777 int c, oldc = '\0'; 778 bool in_comment = FALSE; 779 bool trailing_comment = FALSE; 780 781 (void) fseek(tmp_fp, _nc_tail->cend, SEEK_SET); 782 while ((c = fgetc(tmp_fp)) != EOF) { 783 if (oldc == '\n') { 784 if (c == '#') { 785 trailing_comment = TRUE; 786 in_comment = TRUE; 787 } else { 788 in_comment = FALSE; 789 } 790 } 791 if (trailing_comment 792 && (in_comment || (oldc == '\n' && c == '\n'))) 793 putchar(c); 794 oldc = c; 795 } 796 } 797 } 798 } 799 800 /* Show the directory into which entries were written, and the total 801 * number of entries 802 */ 803 if (showsummary 804 && (!(check_only || infodump || capdump))) { 805 int total = _nc_tic_written(); 806 if (total != 0) 807 fprintf(log_fp, "%d entries written to %s\n", 808 total, 809 _nc_tic_dir((char *) 0)); 810 else 811 fprintf(log_fp, "No entries written\n"); 812 } 813 cleanup(namelst); 814 ExitProgram(EXIT_SUCCESS); 815 } 816 817 /* 818 * This bit of legerdemain turns all the terminfo variable names into 819 * references to locations in the arrays Booleans, Numbers, and Strings --- 820 * precisely what's needed (see comp_parse.c). 821 */ 822 #undef CUR 823 #define CUR tp-> 824 825 /* 826 * Check if the alternate character-set capabilities are consistent. 827 */ 828 static void 829 check_acs(TERMTYPE *tp) 830 { 831 if (VALID_STRING(acs_chars)) { 832 const char *boxes = "lmkjtuvwqxn"; 833 char mapped[256]; 834 char missing[256]; 835 const char *p; 836 char *q; 837 838 memset(mapped, 0, sizeof(mapped)); 839 for (p = acs_chars; *p != '\0'; p += 2) { 840 if (p[1] == '\0') { 841 _nc_warning("acsc has odd number of characters"); 842 break; 843 } 844 mapped[UChar(p[0])] = p[1]; 845 } 846 847 if (mapped[UChar('I')] && !mapped[UChar('i')]) { 848 _nc_warning("acsc refers to 'I', which is probably an error"); 849 } 850 851 for (p = boxes, q = missing; *p != '\0'; ++p) { 852 if (!mapped[UChar(p[0])]) { 853 *q++ = p[0]; 854 } 855 } 856 *q = '\0'; 857 858 assert(strlen(missing) <= strlen(boxes)); 859 if (*missing != '\0' && strcmp(missing, boxes)) { 860 _nc_warning("acsc is missing some line-drawing mapping: %s", missing); 861 } 862 } 863 } 864 865 /* 866 * Check if the color capabilities are consistent 867 */ 868 static void 869 check_colors(TERMTYPE *tp) 870 { 871 if ((max_colors > 0) != (max_pairs > 0) 872 || ((max_colors > max_pairs) && (initialize_pair == 0))) 873 _nc_warning("inconsistent values for max_colors (%d) and max_pairs (%d)", 874 max_colors, max_pairs); 875 876 PAIRED(set_foreground, set_background); 877 PAIRED(set_a_foreground, set_a_background); 878 PAIRED(set_color_pair, initialize_pair); 879 880 if (VALID_STRING(set_foreground) 881 && VALID_STRING(set_a_foreground) 882 && !_nc_capcmp(set_foreground, set_a_foreground)) 883 _nc_warning("expected setf/setaf to be different"); 884 885 if (VALID_STRING(set_background) 886 && VALID_STRING(set_a_background) 887 && !_nc_capcmp(set_background, set_a_background)) 888 _nc_warning("expected setb/setab to be different"); 889 890 /* see: has_colors() */ 891 if (VALID_NUMERIC(max_colors) && VALID_NUMERIC(max_pairs) 892 && (((set_foreground != NULL) 893 && (set_background != NULL)) 894 || ((set_a_foreground != NULL) 895 && (set_a_background != NULL)) 896 || set_color_pair)) { 897 if (!VALID_STRING(orig_pair) && !VALID_STRING(orig_colors)) 898 _nc_warning("expected either op/oc string for resetting colors"); 899 } 900 } 901 902 static char 903 keypad_final(const char *string) 904 { 905 char result = '\0'; 906 907 if (VALID_STRING(string) 908 && *string++ == '\033' 909 && *string++ == 'O' 910 && strlen(string) == 1) { 911 result = *string; 912 } 913 914 return result; 915 } 916 917 static int 918 keypad_index(const char *string) 919 { 920 char *test; 921 const char *list = "PQRSwxymtuvlqrsPpn"; /* app-keypad except "Enter" */ 922 int ch; 923 int result = -1; 924 925 if ((ch = keypad_final(string)) != '\0') { 926 test = strchr(list, ch); 927 if (test != 0) 928 result = (test - list); 929 } 930 return result; 931 } 932 933 #define MAX_KP 5 934 /* 935 * Do a quick sanity-check for vt100-style keypads to see if the 5-key keypad 936 * is mapped inconsistently. 937 */ 938 static void 939 check_keypad(TERMTYPE *tp) 940 { 941 char show[80]; 942 943 if (VALID_STRING(key_a1) && 944 VALID_STRING(key_a3) && 945 VALID_STRING(key_b2) && 946 VALID_STRING(key_c1) && 947 VALID_STRING(key_c3)) { 948 char final[MAX_KP + 1]; 949 int list[MAX_KP]; 950 int increase = 0; 951 int j, k, kk; 952 int last; 953 int test; 954 955 final[0] = keypad_final(key_a1); 956 final[1] = keypad_final(key_a3); 957 final[2] = keypad_final(key_b2); 958 final[3] = keypad_final(key_c1); 959 final[4] = keypad_final(key_c3); 960 final[5] = '\0'; 961 962 /* special case: legacy coding using 1,2,3,0,. on the bottom */ 963 assert(strlen(final) <= MAX_KP); 964 if (!strcmp(final, "qsrpn")) 965 return; 966 967 list[0] = keypad_index(key_a1); 968 list[1] = keypad_index(key_a3); 969 list[2] = keypad_index(key_b2); 970 list[3] = keypad_index(key_c1); 971 list[4] = keypad_index(key_c3); 972 973 /* check that they're all vt100 keys */ 974 for (j = 0; j < MAX_KP; ++j) { 975 if (list[j] < 0) { 976 return; 977 } 978 } 979 980 /* check if they're all in increasing order */ 981 for (j = 1; j < MAX_KP; ++j) { 982 if (list[j] > list[j - 1]) { 983 ++increase; 984 } 985 } 986 if (increase != (MAX_KP - 1)) { 987 show[0] = '\0'; 988 989 for (j = 0, last = -1; j < MAX_KP; ++j) { 990 for (k = 0, kk = -1, test = 100; k < 5; ++k) { 991 if (list[k] > last && 992 list[k] < test) { 993 test = list[k]; 994 kk = k; 995 } 996 } 997 last = test; 998 assert(strlen(show) < (MAX_KP * 4)); 999 switch (kk) { 1000 case 0: 1001 strlcat(show, " ka1", sizeof(show)); 1002 break; 1003 case 1: 1004 strlcat(show, " ka3", sizeof(show)); 1005 break; 1006 case 2: 1007 strlcat(show, " kb2", sizeof(show)); 1008 break; 1009 case 3: 1010 strlcat(show, " kc1", sizeof(show)); 1011 break; 1012 case 4: 1013 strlcat(show, " kc3", sizeof(show)); 1014 break; 1015 } 1016 } 1017 1018 _nc_warning("vt100 keypad order inconsistent: %s", show); 1019 } 1020 1021 } else if (VALID_STRING(key_a1) || 1022 VALID_STRING(key_a3) || 1023 VALID_STRING(key_b2) || 1024 VALID_STRING(key_c1) || 1025 VALID_STRING(key_c3)) { 1026 show[0] = '\0'; 1027 if (keypad_index(key_a1) >= 0) 1028 strlcat(show, " ka1", sizeof(show)); 1029 if (keypad_index(key_a3) >= 0) 1030 strlcat(show, " ka3", sizeof(show)); 1031 if (keypad_index(key_b2) >= 0) 1032 strlcat(show, " kb2", sizeof(show)); 1033 if (keypad_index(key_c1) >= 0) 1034 strlcat(show, " kc1", sizeof(show)); 1035 if (keypad_index(key_c3) >= 0) 1036 strlcat(show, " kc3", sizeof(show)); 1037 if (*show != '\0') 1038 _nc_warning("vt100 keypad map incomplete:%s", show); 1039 } 1040 } 1041 1042 /* 1043 * Returns the expected number of parameters for the given capability. 1044 */ 1045 static int 1046 expected_params(const char *name) 1047 { 1048 /* *INDENT-OFF* */ 1049 static const struct { 1050 const char *name; 1051 int count; 1052 } table[] = { 1053 { "S0", 1 }, /* 'screen' extension */ 1054 { "birep", 2 }, 1055 { "chr", 1 }, 1056 { "colornm", 1 }, 1057 { "cpi", 1 }, 1058 { "csnm", 1 }, 1059 { "csr", 2 }, 1060 { "cub", 1 }, 1061 { "cud", 1 }, 1062 { "cuf", 1 }, 1063 { "cup", 2 }, 1064 { "cuu", 1 }, 1065 { "cvr", 1 }, 1066 { "cwin", 5 }, 1067 { "dch", 1 }, 1068 { "defc", 3 }, 1069 { "dial", 1 }, 1070 { "dispc", 1 }, 1071 { "dl", 1 }, 1072 { "ech", 1 }, 1073 { "getm", 1 }, 1074 { "hpa", 1 }, 1075 { "ich", 1 }, 1076 { "il", 1 }, 1077 { "indn", 1 }, 1078 { "initc", 4 }, 1079 { "initp", 7 }, 1080 { "lpi", 1 }, 1081 { "mc5p", 1 }, 1082 { "mrcup", 2 }, 1083 { "mvpa", 1 }, 1084 { "pfkey", 2 }, 1085 { "pfloc", 2 }, 1086 { "pfx", 2 }, 1087 { "pfxl", 3 }, 1088 { "pln", 2 }, 1089 { "qdial", 1 }, 1090 { "rcsd", 1 }, 1091 { "rep", 2 }, 1092 { "rin", 1 }, 1093 { "sclk", 3 }, 1094 { "scp", 1 }, 1095 { "scs", 1 }, 1096 { "scsd", 2 }, 1097 { "setab", 1 }, 1098 { "setaf", 1 }, 1099 { "setb", 1 }, 1100 { "setcolor", 1 }, 1101 { "setf", 1 }, 1102 { "sgr", 9 }, 1103 { "sgr1", 6 }, 1104 { "slength", 1 }, 1105 { "slines", 1 }, 1106 { "smgbp", 1 }, /* 2 if smgtp is not given */ 1107 { "smglp", 1 }, 1108 { "smglr", 2 }, 1109 { "smgrp", 1 }, 1110 { "smgtb", 2 }, 1111 { "smgtp", 1 }, 1112 { "tsl", 1 }, 1113 { "u6", -1 }, 1114 { "vpa", 1 }, 1115 { "wind", 4 }, 1116 { "wingo", 1 }, 1117 }; 1118 /* *INDENT-ON* */ 1119 1120 unsigned n; 1121 int result = 0; /* function-keys, etc., use none */ 1122 1123 for (n = 0; n < SIZEOF(table); n++) { 1124 if (!strcmp(name, table[n].name)) { 1125 result = table[n].count; 1126 break; 1127 } 1128 } 1129 1130 return result; 1131 } 1132 1133 /* 1134 * Make a quick sanity check for the parameters which are used in the given 1135 * strings. If there are no "%p" tokens, then there should be no other "%" 1136 * markers. 1137 */ 1138 static void 1139 check_params(TERMTYPE *tp, const char *name, char *value) 1140 { 1141 int expected = expected_params(name); 1142 int actual = 0; 1143 int n; 1144 bool params[10]; 1145 char *s = value; 1146 1147 #ifdef set_top_margin_parm 1148 if (!strcmp(name, "smgbp") 1149 && set_top_margin_parm == 0) 1150 expected = 2; 1151 #endif 1152 1153 for (n = 0; n < 10; n++) 1154 params[n] = FALSE; 1155 1156 while (*s != 0) { 1157 if (*s == '%') { 1158 if (*++s == '\0') { 1159 _nc_warning("expected character after %% in %s", name); 1160 break; 1161 } else if (*s == 'p') { 1162 if (*++s == '\0' || !isdigit((int) *s)) { 1163 _nc_warning("expected digit after %%p in %s", name); 1164 return; 1165 } else { 1166 n = (*s - '0'); 1167 if (n > actual) 1168 actual = n; 1169 params[n] = TRUE; 1170 } 1171 } 1172 } 1173 s++; 1174 } 1175 1176 if (params[0]) { 1177 _nc_warning("%s refers to parameter 0 (%%p0), which is not allowed", name); 1178 } 1179 if (value == set_attributes || expected < 0) { 1180 ; 1181 } else if (expected != actual) { 1182 _nc_warning("%s uses %d parameters, expected %d", name, 1183 actual, expected); 1184 for (n = 1; n < actual; n++) { 1185 if (!params[n]) 1186 _nc_warning("%s omits parameter %d", name, n); 1187 } 1188 } 1189 } 1190 1191 static char * 1192 skip_delay(char *s) 1193 { 1194 while (*s == '/' || isdigit(UChar(*s))) 1195 ++s; 1196 return s; 1197 } 1198 1199 /* 1200 * Skip a delay altogether, e.g., when comparing a simple string to sgr, 1201 * the latter may have a worst-case delay on the end. 1202 */ 1203 static char * 1204 ignore_delays(char *s) 1205 { 1206 int delaying = 0; 1207 1208 do { 1209 switch (*s) { 1210 case '$': 1211 if (delaying == 0) 1212 delaying = 1; 1213 break; 1214 case '<': 1215 if (delaying == 1) 1216 delaying = 2; 1217 break; 1218 case '\0': 1219 delaying = 0; 1220 break; 1221 default: 1222 if (delaying) { 1223 s = skip_delay(s); 1224 if (*s == '>') 1225 ++s; 1226 delaying = 0; 1227 } 1228 break; 1229 } 1230 if (delaying) 1231 ++s; 1232 } while (delaying); 1233 return s; 1234 } 1235 1236 /* 1237 * An sgr string may contain several settings other than the one we're 1238 * interested in, essentially sgr0 + rmacs + whatever. As long as the 1239 * "whatever" is contained in the sgr string, that is close enough for our 1240 * sanity check. 1241 */ 1242 static bool 1243 similar_sgr(int num, char *a, char *b) 1244 { 1245 static const char *names[] = 1246 { 1247 "none" 1248 ,"standout" 1249 ,"underline" 1250 ,"reverse" 1251 ,"blink" 1252 ,"dim" 1253 ,"bold" 1254 ,"invis" 1255 ,"protect" 1256 ,"altcharset" 1257 }; 1258 char *base_a = a; 1259 char *base_b = b; 1260 int delaying = 0; 1261 1262 while (*b != 0) { 1263 while (*a != *b) { 1264 if (*a == 0) { 1265 if (b[0] == '$' 1266 && b[1] == '<') { 1267 _nc_warning("Did not find delay %s", _nc_visbuf(b)); 1268 } else { 1269 _nc_warning("checking sgr(%s) %s\n\tcompare to %s\n\tunmatched %s", 1270 names[num], _nc_visbuf2(1, base_a), 1271 _nc_visbuf2(2, base_b), 1272 _nc_visbuf2(3, b)); 1273 } 1274 return FALSE; 1275 } else if (delaying) { 1276 a = skip_delay(a); 1277 b = skip_delay(b); 1278 } else { 1279 a++; 1280 } 1281 } 1282 switch (*a) { 1283 case '$': 1284 if (delaying == 0) 1285 delaying = 1; 1286 break; 1287 case '<': 1288 if (delaying == 1) 1289 delaying = 2; 1290 break; 1291 default: 1292 delaying = 0; 1293 break; 1294 } 1295 a++; 1296 b++; 1297 } 1298 /* ignore delays on the end of the string */ 1299 a = ignore_delays(a); 1300 return ((num != 0) || (*a == 0)); 1301 } 1302 1303 static char * 1304 check_sgr(TERMTYPE *tp, char *zero, int num, char *cap, const char *name) 1305 { 1306 char *test; 1307 1308 _nc_tparm_err = 0; 1309 test = TPARM_9(set_attributes, 1310 num == 1, 1311 num == 2, 1312 num == 3, 1313 num == 4, 1314 num == 5, 1315 num == 6, 1316 num == 7, 1317 num == 8, 1318 num == 9); 1319 if (test != 0) { 1320 if (PRESENT(cap)) { 1321 if (!similar_sgr(num, test, cap)) { 1322 _nc_warning("%s differs from sgr(%d)\n\t%s=%s\n\tsgr(%d)=%s", 1323 name, num, 1324 name, _nc_visbuf2(1, cap), 1325 num, _nc_visbuf2(2, test)); 1326 } 1327 } else if (_nc_capcmp(test, zero)) { 1328 _nc_warning("sgr(%d) present, but not %s", num, name); 1329 } 1330 } else if (PRESENT(cap)) { 1331 _nc_warning("sgr(%d) missing, but %s present", num, name); 1332 } 1333 if (_nc_tparm_err) 1334 _nc_warning("stack error in sgr(%d) string", num); 1335 return test; 1336 } 1337 1338 #define CHECK_SGR(num,name) check_sgr(tp, zero, num, name, #name) 1339 1340 #ifdef TRACE 1341 /* 1342 * If tic is compiled with TRACE, we'll be able to see the output from the 1343 * DEBUG() macro. But since it doesn't use traceon(), it always goes to 1344 * the standard error. Use this function to make it simpler to follow the 1345 * resulting debug traces. 1346 */ 1347 static void 1348 show_where(unsigned level) 1349 { 1350 if (_nc_tracing >= DEBUG_LEVEL(level)) { 1351 char my_name[256]; 1352 _nc_get_type(my_name); 1353 fprintf(stderr, "\"%s\", line %d, '%s' ", 1354 _nc_get_source(), 1355 _nc_curr_line, my_name); 1356 } 1357 } 1358 1359 #else 1360 #define show_where(level) /* nothing */ 1361 #endif 1362 1363 /* other sanity-checks (things that we don't want in the normal 1364 * logic that reads a terminfo entry) 1365 */ 1366 static void 1367 check_termtype(TERMTYPE *tp, bool literal) 1368 { 1369 bool conflict = FALSE; 1370 unsigned j, k; 1371 char fkeys[STRCOUNT]; 1372 1373 /* 1374 * A terminal entry may contain more than one keycode assigned to 1375 * a given string (e.g., KEY_END and KEY_LL). But curses will only 1376 * return one (the last one assigned). 1377 */ 1378 if (!(_nc_syntax == SYN_TERMCAP && capdump)) { 1379 memset(fkeys, 0, sizeof(fkeys)); 1380 for (j = 0; _nc_tinfo_fkeys[j].code; j++) { 1381 char *a = tp->Strings[_nc_tinfo_fkeys[j].offset]; 1382 bool first = TRUE; 1383 if (!VALID_STRING(a)) 1384 continue; 1385 for (k = j + 1; _nc_tinfo_fkeys[k].code; k++) { 1386 char *b = tp->Strings[_nc_tinfo_fkeys[k].offset]; 1387 if (!VALID_STRING(b) 1388 || fkeys[k]) 1389 continue; 1390 if (!_nc_capcmp(a, b)) { 1391 fkeys[j] = 1; 1392 fkeys[k] = 1; 1393 if (first) { 1394 if (!conflict) { 1395 _nc_warning("Conflicting key definitions (using the last)"); 1396 conflict = TRUE; 1397 } 1398 fprintf(stderr, "... %s is the same as %s", 1399 keyname((int) _nc_tinfo_fkeys[j].code), 1400 keyname((int) _nc_tinfo_fkeys[k].code)); 1401 first = FALSE; 1402 } else { 1403 fprintf(stderr, ", %s", 1404 keyname((int) _nc_tinfo_fkeys[k].code)); 1405 } 1406 } 1407 } 1408 if (!first) 1409 fprintf(stderr, "\n"); 1410 } 1411 } 1412 1413 for (j = 0; j < NUM_STRINGS(tp); j++) { 1414 char *a = tp->Strings[j]; 1415 if (VALID_STRING(a)) 1416 check_params(tp, ExtStrname(tp, j, strnames), a); 1417 } 1418 1419 check_acs(tp); 1420 check_colors(tp); 1421 check_keypad(tp); 1422 1423 /* 1424 * These may be mismatched because the terminal description relies on 1425 * restoring the cursor visibility by resetting it. 1426 */ 1427 ANDMISSING(cursor_invisible, cursor_normal); 1428 ANDMISSING(cursor_visible, cursor_normal); 1429 1430 if (PRESENT(cursor_visible) && PRESENT(cursor_normal) 1431 && !_nc_capcmp(cursor_visible, cursor_normal)) 1432 _nc_warning("cursor_visible is same as cursor_normal"); 1433 1434 /* 1435 * From XSI & O'Reilly, we gather that sc/rc are required if csr is 1436 * given, because the cursor position after the scrolling operation is 1437 * performed is undefined. 1438 */ 1439 ANDMISSING(change_scroll_region, save_cursor); 1440 ANDMISSING(change_scroll_region, restore_cursor); 1441 1442 if (PRESENT(set_attributes)) { 1443 char *zero = 0; 1444 1445 _nc_tparm_err = 0; 1446 if (PRESENT(exit_attribute_mode)) { 1447 zero = strdup(CHECK_SGR(0, exit_attribute_mode)); 1448 } else { 1449 zero = strdup(TPARM_9(set_attributes, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 1450 } 1451 if (_nc_tparm_err) 1452 _nc_warning("stack error in sgr(0) string"); 1453 1454 if (zero != 0) { 1455 CHECK_SGR(1, enter_standout_mode); 1456 CHECK_SGR(2, enter_underline_mode); 1457 CHECK_SGR(3, enter_reverse_mode); 1458 CHECK_SGR(4, enter_blink_mode); 1459 CHECK_SGR(5, enter_dim_mode); 1460 CHECK_SGR(6, enter_bold_mode); 1461 CHECK_SGR(7, enter_secure_mode); 1462 CHECK_SGR(8, enter_protected_mode); 1463 CHECK_SGR(9, enter_alt_charset_mode); 1464 free(zero); 1465 } else { 1466 _nc_warning("sgr(0) did not return a value"); 1467 } 1468 } else if (PRESENT(exit_attribute_mode) && 1469 set_attributes != CANCELLED_STRING) { 1470 if (_nc_syntax == SYN_TERMINFO) 1471 _nc_warning("missing sgr string"); 1472 } 1473 1474 if (PRESENT(exit_attribute_mode)) { 1475 char *check_sgr0 = _nc_trim_sgr0(tp); 1476 1477 if (check_sgr0 == 0 || *check_sgr0 == '\0') { 1478 _nc_warning("trimmed sgr0 is empty"); 1479 } else { 1480 show_where(2); 1481 if (check_sgr0 != exit_attribute_mode) { 1482 DEBUG(2, 1483 ("will trim sgr0\n\toriginal sgr0=%s\n\ttrimmed sgr0=%s", 1484 _nc_visbuf2(1, exit_attribute_mode), 1485 _nc_visbuf2(2, check_sgr0))); 1486 free(check_sgr0); 1487 } else { 1488 DEBUG(2, 1489 ("will not trim sgr0\n\toriginal sgr0=%s", 1490 _nc_visbuf(exit_attribute_mode))); 1491 } 1492 } 1493 } 1494 #ifdef TRACE 1495 show_where(2); 1496 if (!auto_right_margin) { 1497 DEBUG(2, 1498 ("can write to lower-right directly")); 1499 } else if (PRESENT(enter_am_mode) && PRESENT(exit_am_mode)) { 1500 DEBUG(2, 1501 ("can write to lower-right by suppressing automargin")); 1502 } else if ((PRESENT(enter_insert_mode) && PRESENT(exit_insert_mode)) 1503 || PRESENT(insert_character) || PRESENT(parm_ich)) { 1504 DEBUG(2, 1505 ("can write to lower-right by using inserts")); 1506 } else { 1507 DEBUG(2, 1508 ("cannot write to lower-right")); 1509 } 1510 #endif 1511 1512 /* 1513 * Some standard applications (e.g., vi) and some non-curses 1514 * applications (e.g., jove) get confused if we have both ich1 and 1515 * smir/rmir. Let's be nice and warn about that, too, even though 1516 * ncurses handles it. 1517 */ 1518 if ((PRESENT(enter_insert_mode) || PRESENT(exit_insert_mode)) 1519 && PRESENT(parm_ich)) { 1520 _nc_warning("non-curses applications may be confused by ich1 with smir/rmir"); 1521 } 1522 1523 /* 1524 * Finally, do the non-verbose checks 1525 */ 1526 if (save_check_termtype != 0) 1527 save_check_termtype(tp, literal); 1528 } 1529