1 /**************************************************************************** 2 * Copyright (c) 1998,1999,2000,2001 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 ****************************************************************************/ 33 34 /* 35 * tic.c --- Main program for terminfo compiler 36 * by Eric S. Raymond 37 * 38 */ 39 40 #include <progs.priv.h> 41 #include <sys/stat.h> 42 43 #include <dump_entry.h> 44 #include <term_entry.h> 45 #include <transform.h> 46 47 MODULE_ID("$Id: tic.c,v 1.90 2001/04/15 00:21:31 tom Exp $") 48 49 const char *_nc_progname = "tic"; 50 51 static FILE *log_fp; 52 static FILE *tmp_fp; 53 static bool showsummary = FALSE; 54 static const char *to_remove; 55 static int tparm_errs; 56 57 static void (*save_check_termtype) (TERMTYPE *); 58 static void check_termtype(TERMTYPE * tt); 59 60 static const char usage_string[] = "[-V] [-v[n]] [-e names] [-CILNRTcfrswx1] source-file\n"; 61 62 static void 63 cleanup(void) 64 { 65 if (tmp_fp != 0) 66 fclose(tmp_fp); 67 if (to_remove != 0) { 68 #if HAVE_REMOVE 69 remove(to_remove); 70 #else 71 unlink(to_remove); 72 #endif 73 } 74 } 75 76 static void 77 failed(const char *msg) 78 { 79 perror(msg); 80 cleanup(); 81 exit(EXIT_FAILURE); 82 } 83 84 static void 85 usage(void) 86 { 87 static const char *const tbl[] = 88 { 89 "Options:", 90 " -1 format translation output one capability per line", 91 " -C translate entries to termcap source form", 92 " -I translate entries to terminfo source form", 93 " -L translate entries to full terminfo source form", 94 " -N disable smart defaults for source translation", 95 " -R restrict translation to given terminfo/termcap version", 96 " -T remove size-restrictions on compiled description", 97 " -V print version", 98 #if NCURSES_XNAMES 99 " -a retain commented-out capabilities (sets -x also)", 100 #endif 101 " -c check only, validate input without compiling or translating", 102 " -f format complex strings for readability", 103 " -G format %{number} to %'char'", 104 " -g format %'char' to %{number}", 105 " -e<names> translate/compile only entries named by comma-separated list", 106 " -o<dir> set output directory for compiled entry writes", 107 " -r force resolution of all use entries in source translation", 108 " -s print summary statistics", 109 " -v[n] set verbosity level", 110 " -w[n] set format width for translation output", 111 #if NCURSES_XNAMES 112 " -x treat unknown capabilities as user-defined", 113 #endif 114 "", 115 "Parameters:", 116 " <file> file to translate or compile" 117 }; 118 size_t j; 119 120 fprintf(stderr, "Usage: %s %s\n", _nc_progname, usage_string); 121 for (j = 0; j < SIZEOF(tbl); j++) { 122 fputs(tbl[j], stderr); 123 putc('\n', stderr); 124 } 125 exit(EXIT_FAILURE); 126 } 127 128 #define L_BRACE '{' 129 #define R_BRACE '}' 130 #define S_QUOTE '\''; 131 132 static void 133 write_it(ENTRY * ep) 134 { 135 unsigned n; 136 int ch; 137 char *s, *d, *t; 138 char result[MAX_ENTRY_SIZE]; 139 140 /* 141 * Look for strings that contain %{number}, convert them to %'char', 142 * which is shorter and runs a little faster. 143 */ 144 for (n = 0; n < STRCOUNT; n++) { 145 s = ep->tterm.Strings[n]; 146 if (VALID_STRING(s) 147 && strchr(s, L_BRACE) != 0) { 148 d = result; 149 t = s; 150 while ((ch = *t++) != 0) { 151 *d++ = ch; 152 if (ch == '\\') { 153 *d++ = *t++; 154 } else if ((ch == '%') 155 && (*t == L_BRACE)) { 156 char *v = 0; 157 long value = strtol(t + 1, &v, 0); 158 if (v != 0 159 && *v == R_BRACE 160 && value > 0 161 && value != '\\' /* FIXME */ 162 && value < 127 163 && isprint((int) value)) { 164 *d++ = S_QUOTE; 165 *d++ = (int) value; 166 *d++ = S_QUOTE; 167 t = (v + 1); 168 } 169 } 170 } 171 *d = 0; 172 if (strlen(result) < strlen(s)) 173 strcpy(s, result); 174 } 175 } 176 177 _nc_set_type(_nc_first_name(ep->tterm.term_names)); 178 _nc_curr_line = ep->startline; 179 _nc_write_entry(&ep->tterm); 180 } 181 182 static bool 183 immedhook(ENTRY * ep GCC_UNUSED) 184 /* write out entries with no use capabilities immediately to save storage */ 185 { 186 #if !HAVE_BIG_CORE 187 /* 188 * This is strictly a core-economy kluge. The really clean way to handle 189 * compilation is to slurp the whole file into core and then do all the 190 * name-collision checks and entry writes in one swell foop. But the 191 * terminfo master file is large enough that some core-poor systems swap 192 * like crazy when you compile it this way...there have been reports of 193 * this process taking *three hours*, rather than the twenty seconds or 194 * less typical on my development box. 195 * 196 * So. This hook *immediately* writes out the referenced entry if it 197 * has no use capabilities. The compiler main loop refrains from 198 * adding the entry to the in-core list when this hook fires. If some 199 * other entry later needs to reference an entry that got written 200 * immediately, that's OK; the resolution code will fetch it off disk 201 * when it can't find it in core. 202 * 203 * Name collisions will still be detected, just not as cleanly. The 204 * write_entry() code complains before overwriting an entry that 205 * postdates the time of tic's first call to write_entry(). Thus 206 * it will complain about overwriting entries newly made during the 207 * tic run, but not about overwriting ones that predate it. 208 * 209 * The reason this is a hook, and not in line with the rest of the 210 * compiler code, is that the support for termcap fallback cannot assume 211 * it has anywhere to spool out these entries! 212 * 213 * The _nc_set_type() call here requires a compensating one in 214 * _nc_parse_entry(). 215 * 216 * If you define HAVE_BIG_CORE, you'll disable this kluge. This will 217 * make tic a bit faster (because the resolution code won't have to do 218 * disk I/O nearly as often). 219 */ 220 if (ep->nuses == 0) { 221 int oldline = _nc_curr_line; 222 223 write_it(ep); 224 _nc_curr_line = oldline; 225 free(ep->tterm.str_table); 226 return (TRUE); 227 } 228 #endif /* HAVE_BIG_CORE */ 229 return (FALSE); 230 } 231 232 static void 233 put_translate(int c) 234 /* emit a comment char, translating terminfo names to termcap names */ 235 { 236 static bool in_name = FALSE; 237 static size_t have, used; 238 static char *namebuf, *suffix; 239 240 if (in_name) { 241 if (used + 1 >= have) { 242 have += 132; 243 namebuf = typeRealloc(char, have, namebuf); 244 suffix = typeRealloc(char, have, suffix); 245 } 246 if (c == '\n' || c == '@') { 247 namebuf[used++] = '\0'; 248 (void) putchar('<'); 249 (void) fputs(namebuf, stdout); 250 putchar(c); 251 in_name = FALSE; 252 } else if (c != '>') { 253 namebuf[used++] = c; 254 } else { /* ah! candidate name! */ 255 char *up; 256 NCURSES_CONST char *tp; 257 258 namebuf[used++] = '\0'; 259 in_name = FALSE; 260 261 suffix[0] = '\0'; 262 if ((up = strchr(namebuf, '#')) != 0 263 || (up = strchr(namebuf, '=')) != 0 264 || ((up = strchr(namebuf, '@')) != 0 && up[1] == '>')) { 265 (void) strcpy(suffix, up); 266 *up = '\0'; 267 } 268 269 if ((tp = nametrans(namebuf)) != 0) { 270 (void) putchar(':'); 271 (void) fputs(tp, stdout); 272 (void) fputs(suffix, stdout); 273 (void) putchar(':'); 274 } else { 275 /* couldn't find a translation, just dump the name */ 276 (void) putchar('<'); 277 (void) fputs(namebuf, stdout); 278 (void) fputs(suffix, stdout); 279 (void) putchar('>'); 280 } 281 } 282 } else { 283 used = 0; 284 if (c == '<') { 285 in_name = TRUE; 286 } else { 287 putchar(c); 288 } 289 } 290 } 291 292 /* Returns a string, stripped of leading/trailing whitespace */ 293 static char * 294 stripped(char *src) 295 { 296 while (isspace(CharOf(*src))) 297 src++; 298 if (*src != '\0') { 299 char *dst = strcpy(malloc(strlen(src) + 1), src); 300 size_t len = strlen(dst); 301 while (--len != 0 && isspace(CharOf(dst[len]))) 302 dst[len] = '\0'; 303 return dst; 304 } 305 return 0; 306 } 307 308 static FILE * 309 open_input(const char *filename) 310 { 311 FILE *fp = fopen(filename, "r"); 312 struct stat sb; 313 314 if (fp == 0) { 315 fprintf(stderr, "%s: Can't open %s\n", _nc_progname, filename); 316 exit(EXIT_FAILURE); 317 } 318 if (fstat(fileno(fp), &sb) < 0 319 || (sb.st_mode & S_IFMT) != S_IFREG) { 320 fprintf(stderr, "%s: %s is not a file\n", _nc_progname, filename); 321 exit(EXIT_FAILURE); 322 } 323 return fp; 324 } 325 326 /* Parse the "-e" option-value into a list of names */ 327 static const char ** 328 make_namelist(char *src) 329 { 330 const char **dst = 0; 331 332 char *s, *base; 333 unsigned pass, n, nn; 334 char buffer[BUFSIZ]; 335 336 if (src == 0) { 337 /* EMPTY */ ; 338 } else if (strchr(src, '/') != 0) { /* a filename */ 339 FILE *fp = open_input(src); 340 341 for (pass = 1; pass <= 2; pass++) { 342 nn = 0; 343 while (fgets(buffer, sizeof(buffer), fp) != 0) { 344 if ((s = stripped(buffer)) != 0) { 345 if (dst != 0) 346 dst[nn] = s; 347 nn++; 348 } 349 } 350 if (pass == 1) { 351 dst = typeCalloc(const char *, nn + 1); 352 rewind(fp); 353 } 354 } 355 fclose(fp); 356 } else { /* literal list of names */ 357 for (pass = 1; pass <= 2; pass++) { 358 for (n = nn = 0, base = src;; n++) { 359 int mark = src[n]; 360 if (mark == ',' || mark == '\0') { 361 if (pass == 1) { 362 nn++; 363 } else { 364 src[n] = '\0'; 365 if ((s = stripped(base)) != 0) 366 dst[nn++] = s; 367 base = &src[n + 1]; 368 } 369 } 370 if (mark == '\0') 371 break; 372 } 373 if (pass == 1) 374 dst = typeCalloc(const char *, nn + 1); 375 } 376 } 377 if (showsummary) { 378 fprintf(log_fp, "Entries that will be compiled:\n"); 379 for (n = 0; dst[n] != 0; n++) 380 fprintf(log_fp, "%d:%s\n", n + 1, dst[n]); 381 } 382 return dst; 383 } 384 385 static bool 386 matches(const char **needle, const char *haystack) 387 /* does entry in needle list match |-separated field in haystack? */ 388 { 389 bool code = FALSE; 390 size_t n; 391 392 if (needle != 0) { 393 for (n = 0; needle[n] != 0; n++) { 394 if (_nc_name_match(haystack, needle[n], "|")) { 395 code = TRUE; 396 break; 397 } 398 } 399 } else 400 code = TRUE; 401 return (code); 402 } 403 404 static FILE * 405 open_tempfile(char *name) 406 { 407 FILE *result = 0; 408 #if HAVE_MKSTEMP 409 int fd = mkstemp(name); 410 if (fd >= 0) 411 result = fdopen(fd, "w"); 412 #else 413 if (tmpnam(name) != 0) 414 result = fopen(name, "w"); 415 #endif 416 return result; 417 } 418 419 int 420 main(int argc, char *argv[]) 421 { 422 char my_tmpname[PATH_MAX]; 423 int v_opt = -1, debug_level; 424 int smart_defaults = TRUE; 425 char *termcap; 426 ENTRY *qp; 427 428 int this_opt, last_opt = '?'; 429 430 int outform = F_TERMINFO; /* output format */ 431 int sortmode = S_TERMINFO; /* sort_mode */ 432 433 int width = 60; 434 bool formatted = FALSE; /* reformat complex strings? */ 435 int numbers = 0; /* format "%'char'" to/from "%{number}" */ 436 bool infodump = FALSE; /* running as captoinfo? */ 437 bool capdump = FALSE; /* running as infotocap? */ 438 bool forceresolve = FALSE; /* force resolution */ 439 bool limited = TRUE; 440 char *tversion = (char *) NULL; 441 const char *source_file = "terminfo"; 442 const char **namelst = 0; 443 char *outdir = (char *) NULL; 444 bool check_only = FALSE; 445 446 log_fp = stderr; 447 448 _nc_progname = _nc_basename(argv[0]); 449 450 if ((infodump = (strcmp(_nc_progname, PROG_CAPTOINFO) == 0)) != FALSE) { 451 outform = F_TERMINFO; 452 sortmode = S_TERMINFO; 453 } 454 if ((capdump = (strcmp(_nc_progname, PROG_INFOTOCAP) == 0)) != FALSE) { 455 outform = F_TERMCAP; 456 sortmode = S_TERMCAP; 457 } 458 #if NCURSES_XNAMES 459 use_extended_names(FALSE); 460 #endif 461 462 /* 463 * Processing arguments is a little complicated, since someone made a 464 * design decision to allow the numeric values for -w, -v options to 465 * be optional. 466 */ 467 while ((this_opt = getopt(argc, argv, 468 "0123456789CILNR:TVace:fGgo:rsvwx")) != EOF) { 469 if (isdigit(this_opt)) { 470 switch (last_opt) { 471 case 'v': 472 v_opt = (v_opt * 10) + (this_opt - '0'); 473 break; 474 case 'w': 475 width = (width * 10) + (this_opt - '0'); 476 break; 477 default: 478 if (this_opt != '1') 479 usage(); 480 last_opt = this_opt; 481 width = 0; 482 } 483 continue; 484 } 485 switch (this_opt) { 486 case 'C': 487 capdump = TRUE; 488 outform = F_TERMCAP; 489 sortmode = S_TERMCAP; 490 break; 491 case 'I': 492 infodump = TRUE; 493 outform = F_TERMINFO; 494 sortmode = S_TERMINFO; 495 break; 496 case 'L': 497 infodump = TRUE; 498 outform = F_VARIABLE; 499 sortmode = S_VARIABLE; 500 break; 501 case 'N': 502 smart_defaults = FALSE; 503 break; 504 case 'R': 505 tversion = optarg; 506 break; 507 case 'T': 508 limited = FALSE; 509 break; 510 case 'V': 511 puts(curses_version()); 512 return EXIT_SUCCESS; 513 case 'c': 514 check_only = TRUE; 515 break; 516 case 'e': 517 namelst = make_namelist(optarg); 518 break; 519 case 'f': 520 formatted = TRUE; 521 break; 522 case 'G': 523 numbers = 1; 524 break; 525 case 'g': 526 numbers = -1; 527 break; 528 case 'o': 529 outdir = optarg; 530 break; 531 case 'r': 532 forceresolve = TRUE; 533 break; 534 case 's': 535 showsummary = TRUE; 536 break; 537 case 'v': 538 v_opt = 0; 539 break; 540 case 'w': 541 width = 0; 542 break; 543 #if NCURSES_XNAMES 544 case 'a': 545 _nc_disable_period = TRUE; 546 /* FALLTHRU */ 547 case 'x': 548 use_extended_names(TRUE); 549 break; 550 #endif 551 default: 552 usage(); 553 } 554 last_opt = this_opt; 555 } 556 557 debug_level = (v_opt > 0) ? v_opt : (v_opt == 0); 558 set_trace_level(debug_level); 559 560 if (_nc_tracing) { 561 save_check_termtype = _nc_check_termtype; 562 _nc_check_termtype = check_termtype; 563 } 564 #if !HAVE_BIG_CORE 565 /* 566 * Aaargh! immedhook seriously hoses us! 567 * 568 * One problem with immedhook is it means we can't do -e. Problem 569 * is that we can't guarantee that for each terminal listed, all the 570 * terminals it depends on will have been kept in core for reference 571 * resolution -- in fact it's certain the primitive types at the end 572 * of reference chains *won't* be in core unless they were explicitly 573 * in the select list themselves. 574 */ 575 if (namelst && (!infodump && !capdump)) { 576 (void) fprintf(stderr, 577 "Sorry, -e can't be used without -I or -C\n"); 578 cleanup(); 579 return EXIT_FAILURE; 580 } 581 #endif /* HAVE_BIG_CORE */ 582 583 if (optind < argc) { 584 source_file = argv[optind++]; 585 if (optind < argc) { 586 fprintf(stderr, 587 "%s: Too many file names. Usage:\n\t%s %s", 588 _nc_progname, 589 _nc_progname, 590 usage_string); 591 return EXIT_FAILURE; 592 } 593 } else { 594 if (infodump == TRUE) { 595 /* captoinfo's no-argument case */ 596 source_file = "/etc/termcap"; 597 if ((termcap = getenv("TERMCAP")) != 0 598 && (namelst = make_namelist(getenv("TERM"))) != 0) { 599 if (access(termcap, F_OK) == 0) { 600 /* file exists */ 601 source_file = termcap; 602 } else if ((tmp_fp = open_tempfile(strcpy(my_tmpname, 603 "/tmp/XXXXXX"))) 604 != 0) { 605 source_file = my_tmpname; 606 fprintf(tmp_fp, "%s\n", termcap); 607 fclose(tmp_fp); 608 tmp_fp = open_input(source_file); 609 to_remove = source_file; 610 } else { 611 failed("tmpnam"); 612 } 613 } 614 } else { 615 /* tic */ 616 fprintf(stderr, 617 "%s: File name needed. Usage:\n\t%s %s", 618 _nc_progname, 619 _nc_progname, 620 usage_string); 621 cleanup(); 622 return EXIT_FAILURE; 623 } 624 } 625 626 if (tmp_fp == 0) 627 tmp_fp = open_input(source_file); 628 629 if (infodump) 630 dump_init(tversion, 631 smart_defaults 632 ? outform 633 : F_LITERAL, 634 sortmode, width, debug_level, formatted); 635 else if (capdump) 636 dump_init(tversion, 637 outform, 638 sortmode, width, debug_level, FALSE); 639 640 /* parse entries out of the source file */ 641 _nc_set_source(source_file); 642 #if !HAVE_BIG_CORE 643 if (!(check_only || infodump || capdump)) 644 _nc_set_writedir(outdir); 645 #endif /* HAVE_BIG_CORE */ 646 _nc_read_entry_source(tmp_fp, (char *) NULL, 647 !smart_defaults, FALSE, 648 (check_only || infodump || capdump) ? NULLHOOK : immedhook); 649 650 /* do use resolution */ 651 if (check_only || (!infodump && !capdump) || forceresolve) { 652 if (!_nc_resolve_uses(TRUE) && !check_only) { 653 cleanup(); 654 return EXIT_FAILURE; 655 } 656 } 657 658 /* length check */ 659 if (check_only && (capdump || infodump)) { 660 for_entry_list(qp) { 661 if (matches(namelst, qp->tterm.term_names)) { 662 int len = fmt_entry(&qp->tterm, NULL, TRUE, infodump, numbers); 663 664 if (len > (infodump ? MAX_TERMINFO_LENGTH : MAX_TERMCAP_LENGTH)) 665 (void) fprintf(stderr, 666 "warning: resolved %s entry is %d bytes long\n", 667 _nc_first_name(qp->tterm.term_names), 668 len); 669 } 670 } 671 } 672 673 /* write or dump all entries */ 674 if (!check_only) { 675 if (!infodump && !capdump) { 676 _nc_set_writedir(outdir); 677 for_entry_list(qp) { 678 if (matches(namelst, qp->tterm.term_names)) 679 write_it(qp); 680 } 681 } else { 682 /* this is in case infotocap() generates warnings */ 683 _nc_curr_col = _nc_curr_line = -1; 684 685 for_entry_list(qp) { 686 if (matches(namelst, qp->tterm.term_names)) { 687 int j = qp->cend - qp->cstart; 688 int len = 0; 689 690 /* this is in case infotocap() generates warnings */ 691 _nc_set_type(_nc_first_name(qp->tterm.term_names)); 692 693 (void) fseek(tmp_fp, qp->cstart, SEEK_SET); 694 while (j--) { 695 if (infodump) 696 (void) putchar(fgetc(tmp_fp)); 697 else 698 put_translate(fgetc(tmp_fp)); 699 } 700 701 len = dump_entry(&qp->tterm, limited, numbers, NULL); 702 for (j = 0; j < qp->nuses; j++) 703 len += dump_uses(qp->uses[j].name, !capdump); 704 (void) putchar('\n'); 705 if (debug_level != 0 && !limited) 706 printf("# length=%d\n", len); 707 } 708 } 709 if (!namelst && _nc_tail) { 710 int c, oldc = '\0'; 711 bool in_comment = FALSE; 712 bool trailing_comment = FALSE; 713 714 (void) fseek(tmp_fp, _nc_tail->cend, SEEK_SET); 715 while ((c = fgetc(tmp_fp)) != EOF) { 716 if (oldc == '\n') { 717 if (c == '#') { 718 trailing_comment = TRUE; 719 in_comment = TRUE; 720 } else { 721 in_comment = FALSE; 722 } 723 } 724 if (trailing_comment 725 && (in_comment || (oldc == '\n' && c == '\n'))) 726 putchar(c); 727 oldc = c; 728 } 729 } 730 } 731 } 732 733 /* Show the directory into which entries were written, and the total 734 * number of entries 735 */ 736 if (showsummary 737 && (!(check_only || infodump || capdump))) { 738 int total = _nc_tic_written(); 739 if (total != 0) 740 fprintf(log_fp, "%d entries written to %s\n", 741 total, 742 _nc_tic_dir((char *) 0)); 743 else 744 fprintf(log_fp, "No entries written\n"); 745 } 746 cleanup(); 747 return (EXIT_SUCCESS); 748 } 749 750 /* 751 * This bit of legerdemain turns all the terminfo variable names into 752 * references to locations in the arrays Booleans, Numbers, and Strings --- 753 * precisely what's needed (see comp_parse.c). 754 */ 755 756 TERMINAL *cur_term; /* tweak to avoid linking lib_cur_term.c */ 757 758 #undef CUR 759 #define CUR tp-> 760 761 /* 762 * Returns the expected number of parameters for the given capability. 763 */ 764 static int 765 expected_params(const char *name) 766 { 767 /* *INDENT-OFF* */ 768 static const struct { 769 const char *name; 770 int count; 771 } table[] = { 772 { "S0", 1 }, /* 'screen' extension */ 773 { "birep", 2 }, 774 { "chr", 1 }, 775 { "colornm", 1 }, 776 { "cpi", 1 }, 777 { "csnm", 1 }, 778 { "csr", 2 }, 779 { "cub", 1 }, 780 { "cud", 1 }, 781 { "cuf", 1 }, 782 { "cup", 2 }, 783 { "cuu", 1 }, 784 { "cvr", 1 }, 785 { "cwin", 5 }, 786 { "dch", 1 }, 787 { "defc", 3 }, 788 { "dial", 1 }, 789 { "dispc", 1 }, 790 { "dl", 1 }, 791 { "ech", 1 }, 792 { "getm", 1 }, 793 { "hpa", 1 }, 794 { "ich", 1 }, 795 { "il", 1 }, 796 { "indn", 1 }, 797 { "initc", 4 }, 798 { "initp", 7 }, 799 { "lpi", 1 }, 800 { "mc5p", 1 }, 801 { "mrcup", 2 }, 802 { "mvpa", 1 }, 803 { "pfkey", 2 }, 804 { "pfloc", 2 }, 805 { "pfx", 2 }, 806 { "pfxl", 3 }, 807 { "pln", 2 }, 808 { "qdial", 1 }, 809 { "rcsd", 1 }, 810 { "rep", 2 }, 811 { "rin", 1 }, 812 { "sclk", 3 }, 813 { "scp", 1 }, 814 { "scs", 1 }, 815 { "scsd", 2 }, 816 { "setab", 1 }, 817 { "setaf", 1 }, 818 { "setb", 1 }, 819 { "setcolor", 1 }, 820 { "setf", 1 }, 821 { "sgr", 9 }, 822 { "sgr1", 6 }, 823 { "slength", 1 }, 824 { "slines", 1 }, 825 { "smgbp", 2 }, 826 { "smglp", 2 }, 827 { "smglr", 2 }, 828 { "smgrp", 1 }, 829 { "smgtb", 2 }, 830 { "smgtp", 1 }, 831 { "tsl", 1 }, 832 { "u6", -1 }, 833 { "vpa", 1 }, 834 { "wind", 4 }, 835 { "wingo", 1 }, 836 }; 837 /* *INDENT-ON* */ 838 839 unsigned n; 840 int result = 0; /* function-keys, etc., use none */ 841 842 for (n = 0; n < SIZEOF(table); n++) { 843 if (!strcmp(name, table[n].name)) { 844 result = table[n].count; 845 break; 846 } 847 } 848 849 return result; 850 } 851 852 /* 853 * Make a quick sanity check for the parameters which are used in the given 854 * strings. If there are no "%p" tokens, then there should be no other "%" 855 * markers. 856 */ 857 static void 858 check_params(TERMTYPE * tp, const char *name, char *value) 859 { 860 int expected = expected_params(name); 861 int actual = 0; 862 int n; 863 bool params[10]; 864 char *s = value; 865 866 for (n = 0; n < 10; n++) 867 params[n] = FALSE; 868 869 while (*s != 0) { 870 if (*s == '%') { 871 if (*++s == '\0') { 872 _nc_warning("expected character after %% in %s", name); 873 break; 874 } else if (*s == 'p') { 875 if (*++s == '\0' || !isdigit((int) *s)) { 876 _nc_warning("expected digit after %%p in %s", name); 877 return; 878 } else { 879 n = (*s - '0'); 880 if (n > actual) 881 actual = n; 882 params[n] = TRUE; 883 } 884 } 885 } 886 s++; 887 } 888 889 if (params[0]) { 890 _nc_warning("%s refers to parameter 0 (%%p0), which is not allowed", name); 891 } 892 if (value == set_attributes || expected < 0) { 893 ; 894 } else if (expected != actual) { 895 _nc_warning("%s uses %d parameters, expected %d", name, 896 actual, expected); 897 for (n = 1; n < actual; n++) { 898 if (!params[n]) 899 _nc_warning("%s omits parameter %d", name, n); 900 } 901 } 902 } 903 904 /* 905 * An sgr string may contain several settings other than the one we're 906 * interested in, essentially sgr0 + rmacs + whatever. As long as the 907 * "whatever" is contained in the sgr string, that is close enough for our 908 * sanity check. 909 */ 910 static bool 911 similar_sgr(char *a, char *b) 912 { 913 while (*b != 0) { 914 while (*a != *b) { 915 if (*a == 0) { 916 if (b[0] == '$' 917 && b[1] == '<') { 918 _nc_warning("Did not find delay %s", _nc_visbuf(b)); 919 } else { 920 _nc_warning("Unmatched portion %s", _nc_visbuf(b)); 921 } 922 return FALSE; 923 } 924 a++; 925 } 926 a++; 927 b++; 928 } 929 return TRUE; 930 } 931 932 static void 933 check_sgr(TERMTYPE * tp, char *zero, int num, char *cap, const char *name) 934 { 935 char *test = tparm(set_attributes, 936 num == 1, 937 num == 2, 938 num == 3, 939 num == 4, 940 num == 5, 941 num == 6, 942 num == 7, 943 num == 8, 944 num == 9); 945 tparm_errs += _nc_tparm_err; 946 if (test != 0) { 947 if (PRESENT(cap)) { 948 if (!similar_sgr(test, cap)) { 949 _nc_warning("%s differs from sgr(%d): %s", name, num, 950 _nc_visbuf(test)); 951 } 952 } else if (strcmp(test, zero)) { 953 _nc_warning("sgr(%d) present, but not %s", num, name); 954 } 955 } else if (PRESENT(cap)) { 956 _nc_warning("sgr(%d) missing, but %s present", num, name); 957 } 958 } 959 960 #define CHECK_SGR(num,name) check_sgr(tp, zero, num, name, #name) 961 962 /* other sanity-checks (things that we don't want in the normal 963 * logic that reads a terminfo entry) 964 */ 965 static void 966 check_termtype(TERMTYPE * tp) 967 { 968 bool conflict = FALSE; 969 unsigned j, k; 970 char fkeys[STRCOUNT]; 971 972 /* 973 * A terminal entry may contain more than one keycode assigned to 974 * a given string (e.g., KEY_END and KEY_LL). But curses will only 975 * return one (the last one assigned). 976 */ 977 memset(fkeys, 0, sizeof(fkeys)); 978 for (j = 0; _nc_tinfo_fkeys[j].code; j++) { 979 char *a = tp->Strings[_nc_tinfo_fkeys[j].offset]; 980 bool first = TRUE; 981 if (!VALID_STRING(a)) 982 continue; 983 for (k = j + 1; _nc_tinfo_fkeys[k].code; k++) { 984 char *b = tp->Strings[_nc_tinfo_fkeys[k].offset]; 985 if (!VALID_STRING(b) 986 || fkeys[k]) 987 continue; 988 if (!strcmp(a, b)) { 989 fkeys[j] = 1; 990 fkeys[k] = 1; 991 if (first) { 992 if (!conflict) { 993 _nc_warning("Conflicting key definitions (using the last)"); 994 conflict = TRUE; 995 } 996 fprintf(stderr, "... %s is the same as %s", 997 keyname(_nc_tinfo_fkeys[j].code), 998 keyname(_nc_tinfo_fkeys[k].code)); 999 first = FALSE; 1000 } else { 1001 fprintf(stderr, ", %s", 1002 keyname(_nc_tinfo_fkeys[k].code)); 1003 } 1004 } 1005 } 1006 if (!first) 1007 fprintf(stderr, "\n"); 1008 } 1009 1010 for (j = 0; j < NUM_STRINGS(tp); j++) { 1011 char *a = tp->Strings[j]; 1012 if (VALID_STRING(a)) 1013 check_params(tp, ExtStrname(tp, j, strnames), a); 1014 } 1015 1016 /* 1017 * Quick check for color. We could also check if the ANSI versus 1018 * non-ANSI strings are misused. 1019 */ 1020 if ((max_colors > 0) != (max_pairs > 0) 1021 || (max_colors > max_pairs)) 1022 _nc_warning("inconsistent values for max_colors and max_pairs"); 1023 1024 PAIRED(set_foreground, set_background); 1025 PAIRED(set_a_foreground, set_a_background); 1026 1027 /* 1028 * These may be mismatched because the terminal description relies on 1029 * restoring the cursor visibility by resetting it. 1030 */ 1031 ANDMISSING(cursor_invisible, cursor_normal); 1032 ANDMISSING(cursor_visible, cursor_normal); 1033 1034 if (PRESENT(cursor_visible) && PRESENT(cursor_normal) 1035 && !strcmp(cursor_visible, cursor_normal)) 1036 _nc_warning("cursor_visible is same as cursor_normal"); 1037 1038 /* 1039 * From XSI & O'Reilly, we gather that sc/rc are required if csr is 1040 * given, because the cursor position after the scrolling operation is 1041 * performed is undefined. 1042 */ 1043 ANDMISSING(change_scroll_region, save_cursor); 1044 ANDMISSING(change_scroll_region, restore_cursor); 1045 1046 tparm_errs = 0; 1047 if (PRESENT(set_attributes)) { 1048 char *zero = tparm(set_attributes, 0, 0, 0, 0, 0, 0, 0, 0, 0); 1049 1050 zero = strdup(zero); 1051 CHECK_SGR(1, enter_standout_mode); 1052 CHECK_SGR(2, enter_underline_mode); 1053 CHECK_SGR(3, enter_reverse_mode); 1054 CHECK_SGR(4, enter_blink_mode); 1055 CHECK_SGR(5, enter_dim_mode); 1056 CHECK_SGR(6, enter_bold_mode); 1057 CHECK_SGR(7, enter_secure_mode); 1058 CHECK_SGR(8, enter_protected_mode); 1059 CHECK_SGR(9, enter_alt_charset_mode); 1060 free(zero); 1061 if (tparm_errs) 1062 _nc_warning("stack error in sgr string"); 1063 } 1064 1065 /* 1066 * Some standard applications (e.g., vi) and some non-curses 1067 * applications (e.g., jove) get confused if we have both ich/ich1 and 1068 * smir/rmir. Let's be nice and warn about that, too, even though 1069 * ncurses handles it. 1070 */ 1071 if ((PRESENT(enter_insert_mode) || PRESENT(exit_insert_mode)) 1072 && (PRESENT(insert_character) || PRESENT(parm_ich))) { 1073 _nc_warning("non-curses applications may be confused by ich/ich1 with smir/rmir"); 1074 } 1075 1076 /* 1077 * Finally, do the non-verbose checks 1078 */ 1079 if (save_check_termtype != 0) 1080 save_check_termtype(tp); 1081 } 1082