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