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