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