1 /* $OpenBSD: tic.c,v 1.29 2006/10/10 21:38:16 cloder 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; 303 size_t len; 304 305 if ((dst = strdup(src)) == NULL) 306 failed("strdup"); 307 len = strlen(dst); 308 while (--len != 0 && isspace(CharOf(dst[len]))) 309 dst[len] = '\0'; 310 return dst; 311 } 312 return 0; 313 } 314 315 static FILE * 316 open_input(const char *filename) 317 { 318 FILE *fp = fopen(filename, "r"); 319 struct stat sb; 320 321 if (fp == 0) { 322 fprintf(stderr, "%s: Can't open %s\n", _nc_progname, filename); 323 exit(EXIT_FAILURE); 324 } 325 if (fstat(fileno(fp), &sb) < 0 326 || (sb.st_mode & S_IFMT) != S_IFREG) { 327 fprintf(stderr, "%s: %s is not a file\n", _nc_progname, filename); 328 exit(EXIT_FAILURE); 329 } 330 return fp; 331 } 332 333 /* Parse the "-e" option-value into a list of names */ 334 static const char ** 335 make_namelist(char *src) 336 { 337 const char **dst = 0; 338 339 char *s, *base; 340 unsigned pass, n, nn; 341 char buffer[BUFSIZ]; 342 343 if (src == 0) { 344 /* EMPTY */ ; 345 } else if (strchr(src, '/') != 0) { /* a filename */ 346 FILE *fp = open_input(src); 347 348 for (pass = 1; pass <= 2; pass++) { 349 nn = 0; 350 while (fgets(buffer, sizeof(buffer), fp) != NULL) { 351 if ((s = stripped(buffer)) != 0) { 352 if (dst != 0) 353 dst[nn] = s; 354 nn++; 355 } 356 } 357 if (pass == 1) { 358 dst = typeCalloc(const char *, nn + 1); 359 rewind(fp); 360 } 361 } 362 fclose(fp); 363 } else { /* literal list of names */ 364 for (pass = 1; pass <= 2; pass++) { 365 for (n = nn = 0, base = src;; n++) { 366 int mark = src[n]; 367 if (mark == ',' || mark == '\0') { 368 if (pass == 1) { 369 nn++; 370 } else { 371 src[n] = '\0'; 372 if ((s = stripped(base)) != 0) 373 dst[nn++] = s; 374 base = &src[n + 1]; 375 } 376 } 377 if (mark == '\0') 378 break; 379 } 380 if (pass == 1) 381 dst = typeCalloc(const char *, nn + 1); 382 } 383 } 384 if (showsummary) { 385 fprintf(log_fp, "Entries that will be compiled:\n"); 386 for (n = 0; dst[n] != 0; n++) 387 fprintf(log_fp, "%d:%s\n", n + 1, dst[n]); 388 } 389 return dst; 390 } 391 392 static bool 393 matches(const char **needle, const char *haystack) 394 /* does entry in needle list match |-separated field in haystack? */ 395 { 396 bool code = FALSE; 397 size_t n; 398 399 if (needle != 0) { 400 for (n = 0; needle[n] != 0; n++) { 401 if (_nc_name_match(haystack, needle[n], "|")) { 402 code = TRUE; 403 break; 404 } 405 } 406 } else 407 code = TRUE; 408 return (code); 409 } 410 411 static FILE * 412 open_tempfile(char *name) 413 { 414 FILE *result = 0; 415 #if HAVE_MKSTEMP 416 int fd = mkstemp(name); 417 if (fd >= 0) 418 result = fdopen(fd, "w"); 419 #else 420 if (tmpnam(name) != 0) 421 result = fopen(name, "w"); 422 #endif 423 return result; 424 } 425 426 int 427 main(int argc, char *argv[]) 428 { 429 char my_tmpname[PATH_MAX]; 430 int v_opt = -1, debug_level; 431 int smart_defaults = TRUE; 432 char *termcap; 433 ENTRY *qp; 434 435 int this_opt, last_opt = '?'; 436 437 int outform = F_TERMINFO; /* output format */ 438 int sortmode = S_TERMINFO; /* sort_mode */ 439 440 int width = 60; 441 bool formatted = FALSE; /* reformat complex strings? */ 442 int numbers = 0; /* format "%'char'" to/from "%{number}" */ 443 bool infodump = FALSE; /* running as captoinfo? */ 444 bool capdump = FALSE; /* running as infotocap? */ 445 bool forceresolve = FALSE; /* force resolution */ 446 bool limited = TRUE; 447 char *tversion = (char *) NULL; 448 const char *source_file = "terminfo"; 449 const char **namelst = 0; 450 char *outdir = (char *) NULL; 451 bool check_only = FALSE; 452 453 log_fp = stderr; 454 455 _nc_progname = _nc_basename(argv[0]); 456 457 if ((infodump = (strcmp(_nc_progname, PROG_CAPTOINFO) == 0)) != FALSE) { 458 outform = F_TERMINFO; 459 sortmode = S_TERMINFO; 460 } 461 if ((capdump = (strcmp(_nc_progname, PROG_INFOTOCAP) == 0)) != FALSE) { 462 outform = F_TERMCAP; 463 sortmode = S_TERMCAP; 464 } 465 #if NCURSES_XNAMES 466 use_extended_names(FALSE); 467 #endif 468 469 /* 470 * Processing arguments is a little complicated, since someone made a 471 * design decision to allow the numeric values for -w, -v options to 472 * be optional. 473 */ 474 while ((this_opt = getopt(argc, argv, 475 "0123456789CILNR:TVace:fGgo:rsvwx")) != -1) { 476 if (isdigit(this_opt)) { 477 switch (last_opt) { 478 case 'v': 479 v_opt = (v_opt * 10) + (this_opt - '0'); 480 break; 481 case 'w': 482 width = (width * 10) + (this_opt - '0'); 483 break; 484 default: 485 if (this_opt != '1') 486 usage(); 487 last_opt = this_opt; 488 width = 0; 489 } 490 continue; 491 } 492 switch (this_opt) { 493 case 'C': 494 capdump = TRUE; 495 outform = F_TERMCAP; 496 sortmode = S_TERMCAP; 497 break; 498 case 'I': 499 infodump = TRUE; 500 outform = F_TERMINFO; 501 sortmode = S_TERMINFO; 502 break; 503 case 'L': 504 infodump = TRUE; 505 outform = F_VARIABLE; 506 sortmode = S_VARIABLE; 507 break; 508 case 'N': 509 smart_defaults = FALSE; 510 break; 511 case 'R': 512 tversion = optarg; 513 break; 514 case 'T': 515 limited = FALSE; 516 break; 517 case 'V': 518 puts(curses_version()); 519 return EXIT_SUCCESS; 520 case 'c': 521 check_only = TRUE; 522 break; 523 case 'e': 524 namelst = make_namelist(optarg); 525 break; 526 case 'f': 527 formatted = TRUE; 528 break; 529 case 'G': 530 numbers = 1; 531 break; 532 case 'g': 533 numbers = -1; 534 break; 535 case 'o': 536 outdir = optarg; 537 break; 538 case 'r': 539 forceresolve = TRUE; 540 break; 541 case 's': 542 showsummary = TRUE; 543 break; 544 case 'v': 545 v_opt = 0; 546 break; 547 case 'w': 548 width = 0; 549 break; 550 #if NCURSES_XNAMES 551 case 'a': 552 _nc_disable_period = TRUE; 553 /* FALLTHRU */ 554 case 'x': 555 use_extended_names(TRUE); 556 break; 557 #endif 558 default: 559 usage(); 560 } 561 last_opt = this_opt; 562 } 563 564 debug_level = (v_opt > 0) ? v_opt : (v_opt == 0); 565 set_trace_level(debug_level); 566 567 if (_nc_tracing) { 568 save_check_termtype = _nc_check_termtype; 569 _nc_check_termtype = check_termtype; 570 } 571 #if !HAVE_BIG_CORE 572 /* 573 * Aaargh! immedhook seriously hoses us! 574 * 575 * One problem with immedhook is it means we can't do -e. Problem 576 * is that we can't guarantee that for each terminal listed, all the 577 * terminals it depends on will have been kept in core for reference 578 * resolution -- in fact it's certain the primitive types at the end 579 * of reference chains *won't* be in core unless they were explicitly 580 * in the select list themselves. 581 */ 582 if (namelst && (!infodump && !capdump)) { 583 (void) fprintf(stderr, 584 "Sorry, -e can't be used without -I or -C\n"); 585 cleanup(); 586 return EXIT_FAILURE; 587 } 588 #endif /* HAVE_BIG_CORE */ 589 590 if (optind < argc) { 591 source_file = argv[optind++]; 592 if (optind < argc) { 593 fprintf(stderr, 594 "%s: Too many file names. Usage:\n\t%s %s", 595 _nc_progname, 596 _nc_progname, 597 usage_string); 598 return EXIT_FAILURE; 599 } 600 } else { 601 if (infodump == TRUE) { 602 /* captoinfo's no-argument case */ 603 source_file = "/usr/share/misc/termcap"; 604 if ((termcap = getenv("TERMCAP")) != 0 605 && (namelst = make_namelist(getenv("TERM"))) != 0) { 606 strlcpy(my_tmpname, "/tmp/XXXXXXXXXX", sizeof my_tmpname); 607 if (access(termcap, F_OK) == 0) { 608 /* file exists */ 609 source_file = termcap; 610 } else if ((tmp_fp = open_tempfile(my_tmpname)) != 0) { 611 source_file = my_tmpname; 612 fprintf(tmp_fp, "%s\n", termcap); 613 fclose(tmp_fp); 614 tmp_fp = open_input(source_file); 615 to_remove = source_file; 616 } else { 617 failed("mkstemp"); 618 } 619 } 620 } else { 621 /* tic */ 622 fprintf(stderr, 623 "%s: File name needed. Usage:\n\t%s %s", 624 _nc_progname, 625 _nc_progname, 626 usage_string); 627 cleanup(); 628 return EXIT_FAILURE; 629 } 630 } 631 632 if (tmp_fp == 0) 633 tmp_fp = open_input(source_file); 634 635 if (infodump) 636 dump_init(tversion, 637 smart_defaults 638 ? outform 639 : F_LITERAL, 640 sortmode, width, debug_level, formatted); 641 else if (capdump) 642 dump_init(tversion, 643 outform, 644 sortmode, width, debug_level, FALSE); 645 646 /* parse entries out of the source file */ 647 _nc_set_source(source_file); 648 #if !HAVE_BIG_CORE 649 if (!(check_only || infodump || capdump)) 650 _nc_set_writedir(outdir); 651 #endif /* HAVE_BIG_CORE */ 652 _nc_read_entry_source(tmp_fp, (char *) NULL, 653 !smart_defaults, FALSE, 654 (check_only || infodump || capdump) ? NULLHOOK : immedhook); 655 656 /* do use resolution */ 657 if (check_only || (!infodump && !capdump) || forceresolve) { 658 if (!_nc_resolve_uses(TRUE) && !check_only) { 659 cleanup(); 660 return EXIT_FAILURE; 661 } 662 } 663 664 /* length check */ 665 if (check_only && (capdump || infodump)) { 666 for_entry_list(qp) { 667 if (matches(namelst, qp->tterm.term_names)) { 668 int len = fmt_entry(&qp->tterm, NULL, TRUE, infodump, numbers); 669 670 if (len > (infodump ? MAX_TERMINFO_LENGTH : MAX_TERMCAP_LENGTH)) 671 (void) fprintf(stderr, 672 "warning: resolved %s entry is %d bytes long\n", 673 _nc_first_name(qp->tterm.term_names), 674 len); 675 } 676 } 677 } 678 679 /* write or dump all entries */ 680 if (!check_only) { 681 if (!infodump && !capdump) { 682 _nc_set_writedir(outdir); 683 for_entry_list(qp) { 684 if (matches(namelst, qp->tterm.term_names)) 685 write_it(qp); 686 } 687 } else { 688 /* this is in case infotocap() generates warnings */ 689 _nc_curr_col = _nc_curr_line = -1; 690 691 for_entry_list(qp) { 692 if (matches(namelst, qp->tterm.term_names)) { 693 int j = qp->cend - qp->cstart; 694 int len = 0; 695 696 /* this is in case infotocap() generates warnings */ 697 _nc_set_type(_nc_first_name(qp->tterm.term_names)); 698 699 (void) fseek(tmp_fp, qp->cstart, SEEK_SET); 700 while (j--) { 701 if (infodump) 702 (void) putchar(fgetc(tmp_fp)); 703 else 704 put_translate(fgetc(tmp_fp)); 705 } 706 707 len = dump_entry(&qp->tterm, limited, numbers, NULL); 708 for (j = 0; j < qp->nuses; j++) 709 len += dump_uses(qp->uses[j].name, !capdump); 710 (void) putchar('\n'); 711 if (debug_level != 0 && !limited) 712 printf("# length=%d\n", len); 713 } 714 } 715 if (!namelst && _nc_tail) { 716 int c, oldc = '\0'; 717 bool in_comment = FALSE; 718 bool trailing_comment = FALSE; 719 720 (void) fseek(tmp_fp, _nc_tail->cend, SEEK_SET); 721 while ((c = fgetc(tmp_fp)) != EOF) { 722 if (oldc == '\n') { 723 if (c == '#') { 724 trailing_comment = TRUE; 725 in_comment = TRUE; 726 } else { 727 in_comment = FALSE; 728 } 729 } 730 if (trailing_comment 731 && (in_comment || (oldc == '\n' && c == '\n'))) 732 putchar(c); 733 oldc = c; 734 } 735 } 736 } 737 } 738 739 /* Show the directory into which entries were written, and the total 740 * number of entries 741 */ 742 if (showsummary 743 && (!(check_only || infodump || capdump))) { 744 int total = _nc_tic_written(); 745 if (total != 0) 746 fprintf(log_fp, "%d entries written to %s\n", 747 total, 748 _nc_tic_dir((char *) 0)); 749 else 750 fprintf(log_fp, "No entries written\n"); 751 } 752 cleanup(); 753 return (EXIT_SUCCESS); 754 } 755 756 /* 757 * This bit of legerdemain turns all the terminfo variable names into 758 * references to locations in the arrays Booleans, Numbers, and Strings --- 759 * precisely what's needed (see comp_parse.c). 760 */ 761 762 TERMINAL *cur_term; /* tweak to avoid linking lib_cur_term.c */ 763 764 #undef CUR 765 #define CUR tp-> 766 767 /* 768 * Returns the expected number of parameters for the given capability. 769 */ 770 static int 771 expected_params(char *name) 772 { 773 /* *INDENT-OFF* */ 774 static const struct { 775 const char *name; 776 int count; 777 } table[] = { 778 { "birep", 2 }, 779 { "chr", 1 }, 780 { "colornm", 1 }, 781 { "cpi", 1 }, 782 { "csnm", 1 }, 783 { "csr", 2 }, 784 { "cub", 1 }, 785 { "cud", 1 }, 786 { "cuf", 1 }, 787 { "cup", 2 }, 788 { "cuu", 1 }, 789 { "cvr", 1 }, 790 { "cwin", 5 }, 791 { "dch", 1 }, 792 { "defc", 3 }, 793 { "dial", 1 }, 794 { "dispc", 1 }, 795 { "dl", 1 }, 796 { "ech", 1 }, 797 { "getm", 1 }, 798 { "hpa", 1 }, 799 { "ich", 1 }, 800 { "il", 1 }, 801 { "indn", 1 }, 802 { "initc", 4 }, 803 { "initp", 7 }, 804 { "lpi", 1 }, 805 { "mc5p", 1 }, 806 { "mrcup", 2 }, 807 { "mvpa", 1 }, 808 { "pfkey", 2 }, 809 { "pfloc", 2 }, 810 { "pfx", 2 }, 811 { "pfxl", 3 }, 812 { "pln", 2 }, 813 { "qdial", 1 }, 814 { "rcsd", 1 }, 815 { "rep", 2 }, 816 { "rin", 1 }, 817 { "sclk", 3 }, 818 { "scp", 1 }, 819 { "scs", 1 }, 820 { "scsd", 2 }, 821 { "setab", 1 }, 822 { "setaf", 1 }, 823 { "setb", 1 }, 824 { "setcolor", 1 }, 825 { "setf", 1 }, 826 { "sgr", 9 }, 827 { "sgr1", 6 }, 828 { "slength", 1 }, 829 { "slines", 1 }, 830 { "smgbp", 2 }, 831 { "smglp", 2 }, 832 { "smglr", 2 }, 833 { "smgrp", 1 }, 834 { "smgtb", 2 }, 835 { "smgtp", 1 }, 836 { "tsl", 1 }, 837 { "u6", -1 }, 838 { "vpa", 1 }, 839 { "wind", 4 }, 840 { "wingo", 1 }, 841 }; 842 /* *INDENT-ON* */ 843 844 unsigned n; 845 int result = 0; /* function-keys, etc., use none */ 846 847 for (n = 0; n < SIZEOF(table); n++) { 848 if (!strcmp(name, table[n].name)) { 849 result = table[n].count; 850 break; 851 } 852 } 853 854 return result; 855 } 856 857 /* 858 * Make a quick sanity check for the parameters which are used in the given 859 * strings. If there are no "%p" tokens, then there should be no other "%" 860 * markers. 861 */ 862 static void 863 check_params(TERMTYPE * tp, char *name, char *value) 864 { 865 int expected = expected_params(name); 866 int actual = 0; 867 int n; 868 bool params[10]; 869 char *s = value; 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 /* 910 * An sgr string may contain several settings other than the one we're 911 * interested in, essentially sgr0 + rmacs + whatever. As long as the 912 * "whatever" is contained in the sgr string, that is close enough for our 913 * sanity check. 914 */ 915 static bool 916 similar_sgr(char *a, char *b) 917 { 918 while (*b != 0) { 919 while (*a != *b) { 920 if (*a == 0) 921 return FALSE; 922 a++; 923 } 924 a++; 925 b++; 926 } 927 return TRUE; 928 } 929 930 static void 931 check_sgr(TERMTYPE * tp, char *zero, int num, char *cap, const char *name) 932 { 933 char *test = tparm(set_attributes, 934 num == 1, 935 num == 2, 936 num == 3, 937 num == 4, 938 num == 5, 939 num == 6, 940 num == 7, 941 num == 8, 942 num == 9); 943 if (test != 0) { 944 if (PRESENT(cap)) { 945 if (!similar_sgr(test, cap)) { 946 _nc_warning("%s differs from sgr(%d): %s", name, num, 947 _nc_visbuf(test)); 948 } 949 } else if (strcmp(test, zero)) { 950 _nc_warning("sgr(%d) present, but not %s", num, name); 951 } 952 } else if (PRESENT(cap)) { 953 _nc_warning("sgr(%d) missing, but %s present", num, name); 954 } 955 } 956 957 #define CHECK_SGR(num,name) check_sgr(tp, zero, num, name, #name) 958 959 /* other sanity-checks (things that we don't want in the normal 960 * logic that reads a terminfo entry) 961 */ 962 static void 963 check_termtype(TERMTYPE * tp) 964 { 965 bool conflict = FALSE; 966 unsigned j, k; 967 char fkeys[STRCOUNT]; 968 969 /* 970 * A terminal entry may contain more than one keycode assigned to 971 * a given string (e.g., KEY_END and KEY_LL). But curses will only 972 * return one (the last one assigned). 973 */ 974 memset(fkeys, 0, sizeof(fkeys)); 975 for (j = 0; _nc_tinfo_fkeys[j].code; j++) { 976 char *a = tp->Strings[_nc_tinfo_fkeys[j].offset]; 977 bool first = TRUE; 978 if (!VALID_STRING(a)) 979 continue; 980 for (k = j + 1; _nc_tinfo_fkeys[k].code; k++) { 981 char *b = tp->Strings[_nc_tinfo_fkeys[k].offset]; 982 if (!VALID_STRING(b) 983 || fkeys[k]) 984 continue; 985 if (!strcmp(a, b)) { 986 fkeys[j] = 1; 987 fkeys[k] = 1; 988 if (first) { 989 if (!conflict) { 990 _nc_warning("Conflicting key definitions (using the last)"); 991 conflict = TRUE; 992 } 993 fprintf(stderr, "... %s is the same as %s", 994 keyname(_nc_tinfo_fkeys[j].code), 995 keyname(_nc_tinfo_fkeys[k].code)); 996 first = FALSE; 997 } else { 998 fprintf(stderr, ", %s", 999 keyname(_nc_tinfo_fkeys[k].code)); 1000 } 1001 } 1002 } 1003 if (!first) 1004 fprintf(stderr, "\n"); 1005 } 1006 1007 for (j = 0; j < NUM_STRINGS(tp); j++) { 1008 char *a = tp->Strings[j]; 1009 if (VALID_STRING(a)) 1010 check_params(tp, ExtStrname(tp, j, strnames), a); 1011 } 1012 1013 /* 1014 * Quick check for color. We could also check if the ANSI versus 1015 * non-ANSI strings are misused. 1016 */ 1017 if ((max_colors > 0) != (max_pairs > 0) 1018 || (max_colors > max_pairs)) 1019 _nc_warning("inconsistent values for max_colors and max_pairs"); 1020 1021 PAIRED(set_foreground, set_background); 1022 PAIRED(set_a_foreground, set_a_background); 1023 1024 /* 1025 * These may be mismatched because the terminal description relies on 1026 * restoring the cursor visibility by resetting it. 1027 */ 1028 ANDMISSING(cursor_invisible, cursor_normal); 1029 ANDMISSING(cursor_visible, cursor_normal); 1030 1031 if (PRESENT(cursor_visible) && PRESENT(cursor_normal) 1032 && !strcmp(cursor_visible, cursor_normal)) 1033 _nc_warning("cursor_visible is same as cursor_normal"); 1034 1035 /* 1036 * From XSI & O'Reilly, we gather that sc/rc are required if csr is 1037 * given, because the cursor position after the scrolling operation is 1038 * performed is undefined. 1039 */ 1040 ANDMISSING(change_scroll_region, save_cursor); 1041 ANDMISSING(change_scroll_region, restore_cursor); 1042 1043 if (PRESENT(set_attributes)) { 1044 char *zero = tparm(set_attributes, 0, 0, 0, 0, 0, 0, 0, 0, 0); 1045 1046 zero = strdup(zero); 1047 CHECK_SGR(1, enter_standout_mode); 1048 CHECK_SGR(2, enter_underline_mode); 1049 CHECK_SGR(3, enter_reverse_mode); 1050 CHECK_SGR(4, enter_blink_mode); 1051 CHECK_SGR(5, enter_dim_mode); 1052 CHECK_SGR(6, enter_bold_mode); 1053 CHECK_SGR(7, enter_secure_mode); 1054 CHECK_SGR(8, enter_protected_mode); 1055 CHECK_SGR(9, enter_alt_charset_mode); 1056 free(zero); 1057 } 1058 1059 /* 1060 * Some standard applications (e.g., vi) and some non-curses 1061 * applications (e.g., jove) get confused if we have both ich/ich1 and 1062 * smir/rmir. Let's be nice and warn about that, too, even though 1063 * ncurses handles it. 1064 */ 1065 if ((PRESENT(enter_insert_mode) || PRESENT(exit_insert_mode)) 1066 && (PRESENT(insert_character) || PRESENT(parm_ich))) { 1067 _nc_warning("non-curses applications may be confused by ich/ich1 with smir/rmir"); 1068 } 1069 1070 /* 1071 * Finally, do the non-verbose checks 1072 */ 1073 if (save_check_termtype != 0) 1074 save_check_termtype(tp); 1075 } 1076