1 /* $OpenBSD: tic.c,v 1.27 2003/07/02 00:21:16 avsm Exp $ */ 2 3 /**************************************************************************** 4 * Copyright (c) 1998,1999,2000,2001 Free Software Foundation, Inc. * 5 * * 6 * Permission is hereby granted, free of charge, to any person obtaining a * 7 * copy of this software and associated documentation files (the * 8 * "Software"), to deal in the Software without restriction, including * 9 * without limitation the rights to use, copy, modify, merge, publish, * 10 * distribute, distribute with modifications, sublicense, and/or sell * 11 * copies of the Software, and to permit persons to whom the Software is * 12 * furnished to do so, subject to the following conditions: * 13 * * 14 * The above copyright notice and this permission notice shall be included * 15 * in all copies or substantial portions of the Software. * 16 * * 17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * 18 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * 19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * 20 * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * 21 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * 22 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR * 23 * THE USE OR OTHER DEALINGS IN THE SOFTWARE. * 24 * * 25 * Except as contained in this notice, the name(s) of the above copyright * 26 * holders shall not be used in advertising or otherwise to promote the * 27 * sale, use or other dealings in this Software without prior written * 28 * authorization. * 29 ****************************************************************************/ 30 31 /**************************************************************************** 32 * Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995 * 33 * and: Eric S. Raymond <esr@snark.thyrsus.com> * 34 ****************************************************************************/ 35 36 /* 37 * tic.c --- Main program for terminfo compiler 38 * by Eric S. Raymond 39 * 40 */ 41 42 #include <progs.priv.h> 43 #include <sys/stat.h> 44 45 #include <dump_entry.h> 46 #include <term_entry.h> 47 #include <transform.h> 48 49 MODULE_ID("$From: tic.c,v 1.85 2001/02/03 23:31:45 tom Exp $") 50 51 const char *_nc_progname = "tic"; 52 53 static FILE *log_fp; 54 static FILE *tmp_fp; 55 static bool showsummary = FALSE; 56 static const char *to_remove; 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 /* new string is same length as what is there, or shorter */ 175 strlcpy(s, result, strlen(s)); 176 } 177 } 178 } 179 180 _nc_set_type(_nc_first_name(ep->tterm.term_names)); 181 _nc_curr_line = ep->startline; 182 _nc_write_entry(&ep->tterm); 183 } 184 185 static bool 186 immedhook(ENTRY * ep GCC_UNUSED) 187 /* write out entries with no use capabilities immediately to save storage */ 188 { 189 #if !HAVE_BIG_CORE 190 /* 191 * This is strictly a core-economy kluge. The really clean way to handle 192 * compilation is to slurp the whole file into core and then do all the 193 * name-collision checks and entry writes in one swell foop. But the 194 * terminfo master file is large enough that some core-poor systems swap 195 * like crazy when you compile it this way...there have been reports of 196 * this process taking *three hours*, rather than the twenty seconds or 197 * less typical on my development box. 198 * 199 * So. This hook *immediately* writes out the referenced entry if it 200 * has no use capabilities. The compiler main loop refrains from 201 * adding the entry to the in-core list when this hook fires. If some 202 * other entry later needs to reference an entry that got written 203 * immediately, that's OK; the resolution code will fetch it off disk 204 * when it can't find it in core. 205 * 206 * Name collisions will still be detected, just not as cleanly. The 207 * write_entry() code complains before overwriting an entry that 208 * postdates the time of tic's first call to write_entry(). Thus 209 * it will complain about overwriting entries newly made during the 210 * tic run, but not about overwriting ones that predate it. 211 * 212 * The reason this is a hook, and not in line with the rest of the 213 * compiler code, is that the support for termcap fallback cannot assume 214 * it has anywhere to spool out these entries! 215 * 216 * The _nc_set_type() call here requires a compensating one in 217 * _nc_parse_entry(). 218 * 219 * If you define HAVE_BIG_CORE, you'll disable this kluge. This will 220 * make tic a bit faster (because the resolution code won't have to do 221 * disk I/O nearly as often). 222 */ 223 if (ep->nuses == 0) { 224 int oldline = _nc_curr_line; 225 226 write_it(ep); 227 _nc_curr_line = oldline; 228 free(ep->tterm.str_table); 229 return (TRUE); 230 } 231 #endif /* HAVE_BIG_CORE */ 232 return (FALSE); 233 } 234 235 static void 236 put_translate(int c) 237 /* emit a comment char, translating terminfo names to termcap names */ 238 { 239 static bool in_name = FALSE; 240 static size_t have, used; 241 static char *namebuf, *suffix; 242 243 if (in_name) { 244 if (used + 1 >= have) { 245 have += 132; 246 namebuf = typeRealloc(char, have, namebuf); 247 suffix = typeRealloc(char, have, suffix); 248 } 249 if (c == '\n' || c == '@') { 250 namebuf[used++] = '\0'; 251 (void) putchar('<'); 252 (void) fputs(namebuf, stdout); 253 putchar(c); 254 in_name = FALSE; 255 } else if (c != '>') { 256 namebuf[used++] = c; 257 } else { /* ah! candidate name! */ 258 char *up; 259 NCURSES_CONST char *tp; 260 261 namebuf[used++] = '\0'; 262 in_name = FALSE; 263 264 suffix[0] = '\0'; 265 if ((up = strchr(namebuf, '#')) != 0 266 || (up = strchr(namebuf, '=')) != 0 267 || ((up = strchr(namebuf, '@')) != 0 && up[1] == '>')) { 268 (void) strlcpy(suffix, up, have); 269 *up = '\0'; 270 } 271 272 if ((tp = nametrans(namebuf)) != 0) { 273 (void) putchar(':'); 274 (void) fputs(tp, stdout); 275 (void) fputs(suffix, stdout); 276 (void) putchar(':'); 277 } else { 278 /* couldn't find a translation, just dump the name */ 279 (void) putchar('<'); 280 (void) fputs(namebuf, stdout); 281 (void) fputs(suffix, stdout); 282 (void) putchar('>'); 283 } 284 } 285 } else { 286 used = 0; 287 if (c == '<') { 288 in_name = TRUE; 289 } else { 290 putchar(c); 291 } 292 } 293 } 294 295 /* Returns a string, stripped of leading/trailing whitespace */ 296 static char * 297 stripped(char *src) 298 { 299 while (isspace(CharOf(*src))) 300 src++; 301 if (*src != '\0') { 302 char *dst = strdup(src); 303 size_t len = strlen(dst); 304 while (--len != 0 && isspace(CharOf(dst[len]))) 305 dst[len] = '\0'; 306 return dst; 307 } 308 return 0; 309 } 310 311 static FILE * 312 open_input(const char *filename) 313 { 314 FILE *fp = fopen(filename, "r"); 315 struct stat sb; 316 317 if (fp == 0) { 318 fprintf(stderr, "%s: Can't open %s\n", _nc_progname, filename); 319 exit(EXIT_FAILURE); 320 } 321 if (fstat(fileno(fp), &sb) < 0 322 || (sb.st_mode & S_IFMT) != S_IFREG) { 323 fprintf(stderr, "%s: %s is not a file\n", _nc_progname, filename); 324 exit(EXIT_FAILURE); 325 } 326 return fp; 327 } 328 329 /* Parse the "-e" option-value into a list of names */ 330 static const char ** 331 make_namelist(char *src) 332 { 333 const char **dst = 0; 334 335 char *s, *base; 336 unsigned pass, n, nn; 337 char buffer[BUFSIZ]; 338 339 if (src == 0) { 340 /* EMPTY */ ; 341 } else if (strchr(src, '/') != 0) { /* a filename */ 342 FILE *fp = open_input(src); 343 344 for (pass = 1; pass <= 2; pass++) { 345 nn = 0; 346 while (fgets(buffer, sizeof(buffer), fp) != 0) { 347 if ((s = stripped(buffer)) != 0) { 348 if (dst != 0) 349 dst[nn] = s; 350 nn++; 351 } 352 } 353 if (pass == 1) { 354 dst = typeCalloc(const char *, nn + 1); 355 rewind(fp); 356 } 357 } 358 fclose(fp); 359 } else { /* literal list of names */ 360 for (pass = 1; pass <= 2; pass++) { 361 for (n = nn = 0, base = src;; n++) { 362 int mark = src[n]; 363 if (mark == ',' || mark == '\0') { 364 if (pass == 1) { 365 nn++; 366 } else { 367 src[n] = '\0'; 368 if ((s = stripped(base)) != 0) 369 dst[nn++] = s; 370 base = &src[n + 1]; 371 } 372 } 373 if (mark == '\0') 374 break; 375 } 376 if (pass == 1) 377 dst = typeCalloc(const char *, nn + 1); 378 } 379 } 380 if (showsummary) { 381 fprintf(log_fp, "Entries that will be compiled:\n"); 382 for (n = 0; dst[n] != 0; n++) 383 fprintf(log_fp, "%d:%s\n", n + 1, dst[n]); 384 } 385 return dst; 386 } 387 388 static bool 389 matches(const char **needle, const char *haystack) 390 /* does entry in needle list match |-separated field in haystack? */ 391 { 392 bool code = FALSE; 393 size_t n; 394 395 if (needle != 0) { 396 for (n = 0; needle[n] != 0; n++) { 397 if (_nc_name_match(haystack, needle[n], "|")) { 398 code = TRUE; 399 break; 400 } 401 } 402 } else 403 code = TRUE; 404 return (code); 405 } 406 407 static FILE * 408 open_tempfile(char *name) 409 { 410 FILE *result = 0; 411 #if HAVE_MKSTEMP 412 int fd = mkstemp(name); 413 if (fd >= 0) 414 result = fdopen(fd, "w"); 415 #else 416 if (tmpnam(name) != 0) 417 result = fopen(name, "w"); 418 #endif 419 return result; 420 } 421 422 int 423 main(int argc, char *argv[]) 424 { 425 char my_tmpname[PATH_MAX]; 426 int v_opt = -1, debug_level; 427 int smart_defaults = TRUE; 428 char *termcap; 429 ENTRY *qp; 430 431 int this_opt, last_opt = '?'; 432 433 int outform = F_TERMINFO; /* output format */ 434 int sortmode = S_TERMINFO; /* sort_mode */ 435 436 int width = 60; 437 bool formatted = FALSE; /* reformat complex strings? */ 438 int numbers = 0; /* format "%'char'" to/from "%{number}" */ 439 bool infodump = FALSE; /* running as captoinfo? */ 440 bool capdump = FALSE; /* running as infotocap? */ 441 bool forceresolve = FALSE; /* force resolution */ 442 bool limited = TRUE; 443 char *tversion = (char *) NULL; 444 const char *source_file = "terminfo"; 445 const char **namelst = 0; 446 char *outdir = (char *) NULL; 447 bool check_only = FALSE; 448 449 log_fp = stderr; 450 451 _nc_progname = _nc_basename(argv[0]); 452 453 if ((infodump = (strcmp(_nc_progname, PROG_CAPTOINFO) == 0)) != FALSE) { 454 outform = F_TERMINFO; 455 sortmode = S_TERMINFO; 456 } 457 if ((capdump = (strcmp(_nc_progname, PROG_INFOTOCAP) == 0)) != FALSE) { 458 outform = F_TERMCAP; 459 sortmode = S_TERMCAP; 460 } 461 #if NCURSES_XNAMES 462 use_extended_names(FALSE); 463 #endif 464 465 /* 466 * Processing arguments is a little complicated, since someone made a 467 * design decision to allow the numeric values for -w, -v options to 468 * be optional. 469 */ 470 while ((this_opt = getopt(argc, argv, 471 "0123456789CILNR:TVace:fGgo:rsvwx")) != -1) { 472 if (isdigit(this_opt)) { 473 switch (last_opt) { 474 case 'v': 475 v_opt = (v_opt * 10) + (this_opt - '0'); 476 break; 477 case 'w': 478 width = (width * 10) + (this_opt - '0'); 479 break; 480 default: 481 if (this_opt != '1') 482 usage(); 483 last_opt = this_opt; 484 width = 0; 485 } 486 continue; 487 } 488 switch (this_opt) { 489 case 'C': 490 capdump = TRUE; 491 outform = F_TERMCAP; 492 sortmode = S_TERMCAP; 493 break; 494 case 'I': 495 infodump = TRUE; 496 outform = F_TERMINFO; 497 sortmode = S_TERMINFO; 498 break; 499 case 'L': 500 infodump = TRUE; 501 outform = F_VARIABLE; 502 sortmode = S_VARIABLE; 503 break; 504 case 'N': 505 smart_defaults = FALSE; 506 break; 507 case 'R': 508 tversion = optarg; 509 break; 510 case 'T': 511 limited = FALSE; 512 break; 513 case 'V': 514 puts(curses_version()); 515 return EXIT_SUCCESS; 516 case 'c': 517 check_only = TRUE; 518 break; 519 case 'e': 520 namelst = make_namelist(optarg); 521 break; 522 case 'f': 523 formatted = TRUE; 524 break; 525 case 'G': 526 numbers = 1; 527 break; 528 case 'g': 529 numbers = -1; 530 break; 531 case 'o': 532 outdir = optarg; 533 break; 534 case 'r': 535 forceresolve = TRUE; 536 break; 537 case 's': 538 showsummary = TRUE; 539 break; 540 case 'v': 541 v_opt = 0; 542 break; 543 case 'w': 544 width = 0; 545 break; 546 #if NCURSES_XNAMES 547 case 'a': 548 _nc_disable_period = TRUE; 549 /* FALLTHRU */ 550 case 'x': 551 use_extended_names(TRUE); 552 break; 553 #endif 554 default: 555 usage(); 556 } 557 last_opt = this_opt; 558 } 559 560 debug_level = (v_opt > 0) ? v_opt : (v_opt == 0); 561 set_trace_level(debug_level); 562 563 if (_nc_tracing) { 564 save_check_termtype = _nc_check_termtype; 565 _nc_check_termtype = check_termtype; 566 } 567 #if !HAVE_BIG_CORE 568 /* 569 * Aaargh! immedhook seriously hoses us! 570 * 571 * One problem with immedhook is it means we can't do -e. Problem 572 * is that we can't guarantee that for each terminal listed, all the 573 * terminals it depends on will have been kept in core for reference 574 * resolution -- in fact it's certain the primitive types at the end 575 * of reference chains *won't* be in core unless they were explicitly 576 * in the select list themselves. 577 */ 578 if (namelst && (!infodump && !capdump)) { 579 (void) fprintf(stderr, 580 "Sorry, -e can't be used without -I or -C\n"); 581 cleanup(); 582 return EXIT_FAILURE; 583 } 584 #endif /* HAVE_BIG_CORE */ 585 586 if (optind < argc) { 587 source_file = argv[optind++]; 588 if (optind < argc) { 589 fprintf(stderr, 590 "%s: Too many file names. Usage:\n\t%s %s", 591 _nc_progname, 592 _nc_progname, 593 usage_string); 594 return EXIT_FAILURE; 595 } 596 } else { 597 if (infodump == TRUE) { 598 /* captoinfo's no-argument case */ 599 source_file = "/usr/share/misc/termcap"; 600 if ((termcap = getenv("TERMCAP")) != 0 601 && (namelst = make_namelist(getenv("TERM"))) != 0) { 602 strlcpy(my_tmpname, "/tmp/XXXXXXXXXX", sizeof my_tmpname); 603 if (access(termcap, F_OK) == 0) { 604 /* file exists */ 605 source_file = termcap; 606 } else if ((tmp_fp = open_tempfile(my_tmpname)) != 0) { 607 source_file = my_tmpname; 608 fprintf(tmp_fp, "%s\n", termcap); 609 fclose(tmp_fp); 610 tmp_fp = open_input(source_file); 611 to_remove = source_file; 612 } else { 613 failed("mkstemp"); 614 } 615 } 616 } else { 617 /* tic */ 618 fprintf(stderr, 619 "%s: File name needed. Usage:\n\t%s %s", 620 _nc_progname, 621 _nc_progname, 622 usage_string); 623 cleanup(); 624 return EXIT_FAILURE; 625 } 626 } 627 628 if (tmp_fp == 0) 629 tmp_fp = open_input(source_file); 630 631 if (infodump) 632 dump_init(tversion, 633 smart_defaults 634 ? outform 635 : F_LITERAL, 636 sortmode, width, debug_level, formatted); 637 else if (capdump) 638 dump_init(tversion, 639 outform, 640 sortmode, width, debug_level, FALSE); 641 642 /* parse entries out of the source file */ 643 _nc_set_source(source_file); 644 #if !HAVE_BIG_CORE 645 if (!(check_only || infodump || capdump)) 646 _nc_set_writedir(outdir); 647 #endif /* HAVE_BIG_CORE */ 648 _nc_read_entry_source(tmp_fp, (char *) NULL, 649 !smart_defaults, FALSE, 650 (check_only || infodump || capdump) ? NULLHOOK : immedhook); 651 652 /* do use resolution */ 653 if (check_only || (!infodump && !capdump) || forceresolve) { 654 if (!_nc_resolve_uses(TRUE) && !check_only) { 655 cleanup(); 656 return EXIT_FAILURE; 657 } 658 } 659 660 /* length check */ 661 if (check_only && (capdump || infodump)) { 662 for_entry_list(qp) { 663 if (matches(namelst, qp->tterm.term_names)) { 664 int len = fmt_entry(&qp->tterm, NULL, TRUE, infodump, numbers); 665 666 if (len > (infodump ? MAX_TERMINFO_LENGTH : MAX_TERMCAP_LENGTH)) 667 (void) fprintf(stderr, 668 "warning: resolved %s entry is %d bytes long\n", 669 _nc_first_name(qp->tterm.term_names), 670 len); 671 } 672 } 673 } 674 675 /* write or dump all entries */ 676 if (!check_only) { 677 if (!infodump && !capdump) { 678 _nc_set_writedir(outdir); 679 for_entry_list(qp) { 680 if (matches(namelst, qp->tterm.term_names)) 681 write_it(qp); 682 } 683 } else { 684 /* this is in case infotocap() generates warnings */ 685 _nc_curr_col = _nc_curr_line = -1; 686 687 for_entry_list(qp) { 688 if (matches(namelst, qp->tterm.term_names)) { 689 int j = qp->cend - qp->cstart; 690 int len = 0; 691 692 /* this is in case infotocap() generates warnings */ 693 _nc_set_type(_nc_first_name(qp->tterm.term_names)); 694 695 (void) fseek(tmp_fp, qp->cstart, SEEK_SET); 696 while (j--) { 697 if (infodump) 698 (void) putchar(fgetc(tmp_fp)); 699 else 700 put_translate(fgetc(tmp_fp)); 701 } 702 703 len = dump_entry(&qp->tterm, limited, numbers, NULL); 704 for (j = 0; j < qp->nuses; j++) 705 len += dump_uses(qp->uses[j].name, !capdump); 706 (void) putchar('\n'); 707 if (debug_level != 0 && !limited) 708 printf("# length=%d\n", len); 709 } 710 } 711 if (!namelst && _nc_tail) { 712 int c, oldc = '\0'; 713 bool in_comment = FALSE; 714 bool trailing_comment = FALSE; 715 716 (void) fseek(tmp_fp, _nc_tail->cend, SEEK_SET); 717 while ((c = fgetc(tmp_fp)) != EOF) { 718 if (oldc == '\n') { 719 if (c == '#') { 720 trailing_comment = TRUE; 721 in_comment = TRUE; 722 } else { 723 in_comment = FALSE; 724 } 725 } 726 if (trailing_comment 727 && (in_comment || (oldc == '\n' && c == '\n'))) 728 putchar(c); 729 oldc = c; 730 } 731 } 732 } 733 } 734 735 /* Show the directory into which entries were written, and the total 736 * number of entries 737 */ 738 if (showsummary 739 && (!(check_only || infodump || capdump))) { 740 int total = _nc_tic_written(); 741 if (total != 0) 742 fprintf(log_fp, "%d entries written to %s\n", 743 total, 744 _nc_tic_dir((char *) 0)); 745 else 746 fprintf(log_fp, "No entries written\n"); 747 } 748 cleanup(); 749 return (EXIT_SUCCESS); 750 } 751 752 /* 753 * This bit of legerdemain turns all the terminfo variable names into 754 * references to locations in the arrays Booleans, Numbers, and Strings --- 755 * precisely what's needed (see comp_parse.c). 756 */ 757 758 TERMINAL *cur_term; /* tweak to avoid linking lib_cur_term.c */ 759 760 #undef CUR 761 #define CUR tp-> 762 763 /* 764 * Returns the expected number of parameters for the given capability. 765 */ 766 static int 767 expected_params(char *name) 768 { 769 /* *INDENT-OFF* */ 770 static const struct { 771 const char *name; 772 int count; 773 } table[] = { 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", 2 }, 827 { "smglp", 2 }, 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, 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 for (n = 0; n < 10; n++) 868 params[n] = FALSE; 869 870 while (*s != 0) { 871 if (*s == '%') { 872 if (*++s == '\0') { 873 _nc_warning("expected character after %% in %s", name); 874 break; 875 } else if (*s == 'p') { 876 if (*++s == '\0' || !isdigit((int) *s)) { 877 _nc_warning("expected digit after %%p in %s", name); 878 return; 879 } else { 880 n = (*s - '0'); 881 if (n > actual) 882 actual = n; 883 params[n] = TRUE; 884 } 885 } 886 } 887 s++; 888 } 889 890 if (params[0]) { 891 _nc_warning("%s refers to parameter 0 (%%p0), which is not allowed", name); 892 } 893 if (value == set_attributes || expected < 0) { 894 ; 895 } else if (expected != actual) { 896 _nc_warning("%s uses %d parameters, expected %d", name, 897 actual, expected); 898 for (n = 1; n < actual; n++) { 899 if (!params[n]) 900 _nc_warning("%s omits parameter %d", name, n); 901 } 902 } 903 } 904 905 /* 906 * An sgr string may contain several settings other than the one we're 907 * interested in, essentially sgr0 + rmacs + whatever. As long as the 908 * "whatever" is contained in the sgr string, that is close enough for our 909 * sanity check. 910 */ 911 static bool 912 similar_sgr(char *a, char *b) 913 { 914 while (*b != 0) { 915 while (*a != *b) { 916 if (*a == 0) 917 return FALSE; 918 a++; 919 } 920 a++; 921 b++; 922 } 923 return TRUE; 924 } 925 926 static void 927 check_sgr(TERMTYPE * tp, char *zero, int num, char *cap, const char *name) 928 { 929 char *test = tparm(set_attributes, 930 num == 1, 931 num == 2, 932 num == 3, 933 num == 4, 934 num == 5, 935 num == 6, 936 num == 7, 937 num == 8, 938 num == 9); 939 if (test != 0) { 940 if (PRESENT(cap)) { 941 if (!similar_sgr(test, cap)) { 942 _nc_warning("%s differs from sgr(%d): %s", name, num, 943 _nc_visbuf(test)); 944 } 945 } else if (strcmp(test, zero)) { 946 _nc_warning("sgr(%d) present, but not %s", num, name); 947 } 948 } else if (PRESENT(cap)) { 949 _nc_warning("sgr(%d) missing, but %s present", num, name); 950 } 951 } 952 953 #define CHECK_SGR(num,name) check_sgr(tp, zero, num, name, #name) 954 955 /* other sanity-checks (things that we don't want in the normal 956 * logic that reads a terminfo entry) 957 */ 958 static void 959 check_termtype(TERMTYPE * tp) 960 { 961 bool conflict = FALSE; 962 unsigned j, k; 963 char fkeys[STRCOUNT]; 964 965 /* 966 * A terminal entry may contain more than one keycode assigned to 967 * a given string (e.g., KEY_END and KEY_LL). But curses will only 968 * return one (the last one assigned). 969 */ 970 memset(fkeys, 0, sizeof(fkeys)); 971 for (j = 0; _nc_tinfo_fkeys[j].code; j++) { 972 char *a = tp->Strings[_nc_tinfo_fkeys[j].offset]; 973 bool first = TRUE; 974 if (!VALID_STRING(a)) 975 continue; 976 for (k = j + 1; _nc_tinfo_fkeys[k].code; k++) { 977 char *b = tp->Strings[_nc_tinfo_fkeys[k].offset]; 978 if (!VALID_STRING(b) 979 || fkeys[k]) 980 continue; 981 if (!strcmp(a, b)) { 982 fkeys[j] = 1; 983 fkeys[k] = 1; 984 if (first) { 985 if (!conflict) { 986 _nc_warning("Conflicting key definitions (using the last)"); 987 conflict = TRUE; 988 } 989 fprintf(stderr, "... %s is the same as %s", 990 keyname(_nc_tinfo_fkeys[j].code), 991 keyname(_nc_tinfo_fkeys[k].code)); 992 first = FALSE; 993 } else { 994 fprintf(stderr, ", %s", 995 keyname(_nc_tinfo_fkeys[k].code)); 996 } 997 } 998 } 999 if (!first) 1000 fprintf(stderr, "\n"); 1001 } 1002 1003 for (j = 0; j < NUM_STRINGS(tp); j++) { 1004 char *a = tp->Strings[j]; 1005 if (VALID_STRING(a)) 1006 check_params(tp, ExtStrname(tp, j, strnames), a); 1007 } 1008 1009 /* 1010 * Quick check for color. We could also check if the ANSI versus 1011 * non-ANSI strings are misused. 1012 */ 1013 if ((max_colors > 0) != (max_pairs > 0) 1014 || (max_colors > max_pairs)) 1015 _nc_warning("inconsistent values for max_colors and max_pairs"); 1016 1017 PAIRED(set_foreground, set_background); 1018 PAIRED(set_a_foreground, set_a_background); 1019 1020 /* 1021 * These may be mismatched because the terminal description relies on 1022 * restoring the cursor visibility by resetting it. 1023 */ 1024 ANDMISSING(cursor_invisible, cursor_normal); 1025 ANDMISSING(cursor_visible, cursor_normal); 1026 1027 if (PRESENT(cursor_visible) && PRESENT(cursor_normal) 1028 && !strcmp(cursor_visible, cursor_normal)) 1029 _nc_warning("cursor_visible is same as cursor_normal"); 1030 1031 /* 1032 * From XSI & O'Reilly, we gather that sc/rc are required if csr is 1033 * given, because the cursor position after the scrolling operation is 1034 * performed is undefined. 1035 */ 1036 ANDMISSING(change_scroll_region, save_cursor); 1037 ANDMISSING(change_scroll_region, restore_cursor); 1038 1039 if (PRESENT(set_attributes)) { 1040 char *zero = tparm(set_attributes, 0, 0, 0, 0, 0, 0, 0, 0, 0); 1041 1042 zero = strdup(zero); 1043 CHECK_SGR(1, enter_standout_mode); 1044 CHECK_SGR(2, enter_underline_mode); 1045 CHECK_SGR(3, enter_reverse_mode); 1046 CHECK_SGR(4, enter_blink_mode); 1047 CHECK_SGR(5, enter_dim_mode); 1048 CHECK_SGR(6, enter_bold_mode); 1049 CHECK_SGR(7, enter_secure_mode); 1050 CHECK_SGR(8, enter_protected_mode); 1051 CHECK_SGR(9, enter_alt_charset_mode); 1052 free(zero); 1053 } 1054 1055 /* 1056 * Some standard applications (e.g., vi) and some non-curses 1057 * applications (e.g., jove) get confused if we have both ich/ich1 and 1058 * smir/rmir. Let's be nice and warn about that, too, even though 1059 * ncurses handles it. 1060 */ 1061 if ((PRESENT(enter_insert_mode) || PRESENT(exit_insert_mode)) 1062 && (PRESENT(insert_character) || PRESENT(parm_ich))) { 1063 _nc_warning("non-curses applications may be confused by ich/ich1 with smir/rmir"); 1064 } 1065 1066 /* 1067 * Finally, do the non-verbose checks 1068 */ 1069 if (save_check_termtype != 0) 1070 save_check_termtype(tp); 1071 } 1072