1 /**************************************************************************** 2 * Copyright 2018-2023,2024 Thomas E. Dickey * 3 * Copyright 1998-2017,2018 Free Software Foundation, Inc. * 4 * * 5 * Permission is hereby granted, free of charge, to any person obtaining a * 6 * copy of this software and associated documentation files (the * 7 * "Software"), to deal in the Software without restriction, including * 8 * without limitation the rights to use, copy, modify, merge, publish, * 9 * distribute, distribute with modifications, sublicense, and/or sell * 10 * copies of the Software, and to permit persons to whom the Software is * 11 * furnished to do so, subject to the following conditions: * 12 * * 13 * The above copyright notice and this permission notice shall be included * 14 * in all copies or substantial portions of the Software. * 15 * * 16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * 17 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * 18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * 19 * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * 20 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * 21 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR * 22 * THE USE OR OTHER DEALINGS IN THE SOFTWARE. * 23 * * 24 * Except as contained in this notice, the name(s) of the above copyright * 25 * holders shall not be used in advertising or otherwise to promote the * 26 * sale, use or other dealings in this Software without prior written * 27 * authorization. * 28 ****************************************************************************/ 29 30 /**************************************************************************** 31 * Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995 * 32 * and: Eric S. Raymond <esr@snark.thyrsus.com> * 33 * and: Thomas E. Dickey 1996 on * 34 ****************************************************************************/ 35 36 /* 37 * tic.c --- Main program for terminfo compiler 38 * by Eric S. Raymond 39 * and Thomas E Dickey 40 * 41 */ 42 43 #include <progs.priv.h> 44 #include <sys/stat.h> 45 46 #include <dump_entry.h> 47 #include <tparm_type.h> 48 #include <hashed_db.h> 49 #include <parametrized.h> 50 #include <transform.h> 51 52 MODULE_ID("$Id: tic.c,v 1.325 2024/03/02 19:33:22 tom Exp $") 53 54 #define STDIN_NAME "<stdin>" 55 56 const char *_nc_progname = "tic"; 57 58 static FILE *log_fp; 59 static FILE *tmp_fp; 60 static bool capdump = FALSE; /* running as infotocap? */ 61 static bool infodump = FALSE; /* running as captoinfo? */ 62 static bool showsummary = FALSE; 63 static unsigned debug_level; 64 static char **namelst = 0; 65 static const char *to_remove; 66 67 #if NCURSES_XNAMES 68 static bool using_extensions = FALSE; 69 #endif 70 71 static void (*save_check_termtype) (TERMTYPE2 *, bool); 72 static void check_termtype(TERMTYPE2 *tt, bool); 73 74 static const char usage_string[] = "\ 75 [-e names] \ 76 [-o dir] \ 77 [-R name] \ 78 [-v[n]] \ 79 [-V] \ 80 [-w[n]] \ 81 [-\ 82 1\ 83 a\ 84 C\ 85 D\ 86 c\ 87 f\ 88 G\ 89 g\ 90 I\ 91 K\ 92 L\ 93 N\ 94 r\ 95 s\ 96 T\ 97 t\ 98 U\ 99 x\ 100 ] \ 101 source-file\n"; 102 103 #if NO_LEAKS 104 static void 105 free_namelist(char **src) 106 { 107 if (src != 0) { 108 int n; 109 for (n = 0; src[n] != 0; ++n) 110 free(src[n]); 111 free(src); 112 } 113 } 114 #endif 115 116 static void 117 cleanup(void) 118 { 119 #if NO_LEAKS 120 free_namelist(namelst); 121 _nc_leaks_dump_entry(); 122 #endif 123 if (tmp_fp != 0) 124 fclose(tmp_fp); 125 if (to_remove != 0) { 126 int rc; 127 128 #if HAVE_REMOVE 129 rc = remove(to_remove); 130 #else 131 rc = unlink(to_remove); 132 #endif 133 if (rc != 0) 134 perror(to_remove); 135 } 136 } 137 138 static void 139 failed(const char *msg) 140 { 141 perror(msg); 142 ExitProgram(EXIT_FAILURE); 143 } 144 145 static void 146 usage(void) 147 { 148 #define DATA(s) s "\n" 149 static const char options_string[] = 150 { 151 DATA("Options:") 152 DATA(" -0 format translation output all capabilities on one line") 153 DATA(" -1 format translation output one capability per line") 154 #if NCURSES_XNAMES 155 DATA(" -a retain commented-out capabilities (sets -x also)") 156 #endif 157 DATA(" -C translate entries to termcap source form") 158 DATA(" -D print list of tic's database locations (first must be writable)") 159 DATA(" -c check only, validate input without compiling or translating") 160 DATA(" -e<names> translate/compile only entries named by comma-separated list") 161 DATA(" -f format complex strings for readability") 162 DATA(" -G format %{number} to %'char'") 163 DATA(" -g format %'char' to %{number}") 164 DATA(" -I translate entries to terminfo source form") 165 DATA(" -K translate entries to termcap source form with BSD syntax") 166 DATA(" -L translate entries to full terminfo source form") 167 DATA(" -N disable smart defaults for source translation") 168 DATA(" -o<dir> set output directory for compiled entry writes") 169 DATA(" -Q[n] dump compiled description") 170 DATA(" -q brief listing, removes headers") 171 DATA(" -R<name> restrict translation to given terminfo/termcap version") 172 DATA(" -r force resolution of all use entries in source translation") 173 DATA(" -s print summary statistics") 174 DATA(" -T remove size-restrictions on compiled description") 175 #if NCURSES_XNAMES 176 DATA(" -t suppress commented-out capabilities") 177 #endif 178 DATA(" -U suppress post-processing of entries") 179 DATA(" -V print version") 180 DATA(" -W wrap long strings according to -w[n] option") 181 DATA(" -v[n] set verbosity level") 182 DATA(" -w[n] set format width for translation output") 183 #if NCURSES_XNAMES 184 DATA(" -x treat unknown capabilities as user-defined") 185 #endif 186 DATA("") 187 DATA("Parameters:") 188 DATA(" <file> file to translate or compile") 189 }; 190 #undef DATA 191 192 fprintf(stderr, "Usage: %s %s\n", _nc_progname, usage_string); 193 fputs(options_string, stderr); 194 ExitProgram(EXIT_FAILURE); 195 } 196 197 #define L_BRACE '{' 198 #define R_BRACE '}' 199 #define S_QUOTE '\'' 200 201 static void 202 write_it(ENTRY * ep) 203 { 204 unsigned n; 205 int ch; 206 char *s, *d, *t; 207 char result[MAX_ENTRY_SIZE]; 208 209 /* 210 * Look for strings that contain %{number}, convert them to %'char', 211 * which is shorter and runs a little faster. 212 */ 213 for (n = 0; n < STRCOUNT; n++) { 214 s = ep->tterm.Strings[n]; 215 if (VALID_STRING(s) 216 && strchr(s, L_BRACE) != 0) { 217 d = result; 218 t = s; 219 while ((ch = *t++) != 0) { 220 *d++ = (char) ch; 221 if (ch == '\\') { 222 if ((*d++ = *t++) == '\0') 223 break; 224 } else if ((ch == '%') 225 && (*t == L_BRACE)) { 226 char *v = 0; 227 long value = strtol(t + 1, &v, 0); 228 if (v != 0 229 && *v == R_BRACE 230 && value > 0 231 && value != '\\' /* FIXME */ 232 && value < 127 233 && isprint((int) value)) { 234 *d++ = S_QUOTE; 235 *d++ = (char) value; 236 *d++ = S_QUOTE; 237 t = (v + 1); 238 } 239 } 240 } 241 *d = 0; 242 if (strlen(result) < strlen(s)) 243 _nc_STRCPY(s, result, strlen(s) + 1); 244 } 245 } 246 247 _nc_set_type(_nc_first_name(ep->tterm.term_names)); 248 _nc_curr_line = (int) ep->startline; 249 _nc_write_entry(&ep->tterm); 250 } 251 252 static bool 253 immedhook(ENTRY * ep GCC_UNUSED) 254 /* write out entries with no use capabilities immediately to save storage */ 255 { 256 #if !HAVE_BIG_CORE 257 /* 258 * This is strictly a core-economy kluge. The really clean way to handle 259 * compilation is to slurp the whole file into core and then do all the 260 * name-collision checks and entry writes in one swell foop. But the 261 * terminfo master file is large enough that some core-poor systems swap 262 * like crazy when you compile it this way...there have been reports of 263 * this process taking *three hours*, rather than the twenty seconds or 264 * less typical on my development box. 265 * 266 * So. This hook *immediately* writes out the referenced entry if it 267 * has no use capabilities. The compiler main loop refrains from 268 * adding the entry to the in-core list when this hook fires. If some 269 * other entry later needs to reference an entry that got written 270 * immediately, that's OK; the resolution code will fetch it off disk 271 * when it can't find it in core. 272 * 273 * Name collisions will still be detected, just not as cleanly. The 274 * write_entry() code complains before overwriting an entry that 275 * postdates the time of tic's first call to write_entry(). Thus 276 * it will complain about overwriting entries newly made during the 277 * tic run, but not about overwriting ones that predate it. 278 * 279 * The reason this is a hook, and not in line with the rest of the 280 * compiler code, is that the support for termcap fallback cannot assume 281 * it has anywhere to spool out these entries! 282 * 283 * The _nc_set_type() call here requires a compensating one in 284 * _nc_parse_entry(). 285 * 286 * If you define HAVE_BIG_CORE, you'll disable this kluge. This will 287 * make tic a bit faster (because the resolution code won't have to do 288 * disk I/O nearly as often). 289 */ 290 if (ep->nuses == 0) { 291 int oldline = _nc_curr_line; 292 293 write_it(ep); 294 _nc_curr_line = oldline; 295 free(ep->tterm.str_table); 296 return (TRUE); 297 } 298 #endif /* HAVE_BIG_CORE */ 299 return (FALSE); 300 } 301 302 static void 303 put_translate(int c) 304 /* emit a comment char, translating terminfo names to termcap names */ 305 { 306 static bool in_name = FALSE; 307 static size_t used; 308 309 if (in_name) { 310 static size_t have; 311 static char *namebuf, *suffix; 312 313 if (used + 1 >= have) { 314 have += 132; 315 if ((namebuf = typeRealloc(char, have, namebuf)) == NULL) 316 failed("put_translate namebuf"); 317 if ((suffix = typeRealloc(char, have, suffix)) == NULL) 318 failed("put_translate suffix"); 319 } 320 if (c == '\n' || c == '@') { 321 namebuf[used++] = '\0'; 322 (void) putchar('<'); 323 (void) fputs(namebuf, stdout); 324 putchar(c); 325 in_name = FALSE; 326 } else if (c != '>') { 327 namebuf[used++] = (char) c; 328 } else { /* ah! candidate name! */ 329 char *up; 330 NCURSES_CONST char *tp; 331 332 namebuf[used++] = '\0'; 333 in_name = FALSE; 334 335 suffix[0] = '\0'; 336 if ((up = strchr(namebuf, '#')) != 0 337 || (up = strchr(namebuf, '=')) != 0 338 || ((up = strchr(namebuf, '@')) != 0 && up[1] == '>')) { 339 _nc_STRCPY(suffix, up, have); 340 *up = '\0'; 341 } 342 343 if ((tp = nametrans(namebuf)) != 0) { 344 (void) putchar(':'); 345 (void) fputs(tp, stdout); 346 (void) fputs(suffix, stdout); 347 (void) putchar(':'); 348 } else { 349 /* couldn't find a translation, just dump the name */ 350 (void) putchar('<'); 351 (void) fputs(namebuf, stdout); 352 (void) fputs(suffix, stdout); 353 (void) putchar('>'); 354 } 355 } 356 } else { 357 used = 0; 358 if (c == '<') { 359 in_name = TRUE; 360 } else { 361 putchar(c); 362 } 363 } 364 } 365 366 /* Returns a string, stripped of leading/trailing whitespace */ 367 static char * 368 stripped(char *src) 369 { 370 char *dst = 0; 371 372 while (isspace(UChar(*src))) 373 src++; 374 375 if (*src != '\0') { 376 if ((dst = strdup(src)) == NULL) { 377 failed("strdup"); 378 } else { 379 size_t len = strlen(dst); 380 while (--len != 0 && isspace(UChar(dst[len]))) 381 dst[len] = '\0'; 382 } 383 } 384 return dst; 385 } 386 387 static FILE * 388 open_tempfile(char *filename) 389 { 390 FILE *result = 0; 391 392 _nc_STRCPY(filename, "/tmp/XXXXXX", PATH_MAX); 393 #if HAVE_MKSTEMP 394 { 395 int oldmask = (int) umask(077); 396 int fd = mkstemp(filename); 397 if (fd >= 0) 398 result = fdopen(fd, "w"); 399 umask((mode_t) oldmask); 400 } 401 #else 402 if (tmpnam(filename) != 0) 403 result = safe_fopen(filename, "w"); 404 #endif 405 return result; 406 } 407 408 static FILE * 409 copy_input(FILE *source, const char *filename, char *alt_file) 410 { 411 char my_altfile[PATH_MAX]; 412 FILE *result = 0; 413 FILE *target; 414 int ch; 415 416 if (alt_file == NULL) 417 alt_file = my_altfile; 418 419 if (source == NULL) { 420 failed("copy_input (source)"); 421 } else if ((target = open_tempfile(alt_file)) == NULL) { 422 failed("copy_input (target)"); 423 } else { 424 clearerr(source); 425 for (;;) { 426 ch = fgetc(source); 427 if (feof(source)) { 428 break; 429 } else if (ferror(source)) { 430 failed(filename); 431 } else if (ch == 0) { 432 /* don't loop in case someone wants to convert /dev/zero */ 433 fprintf(stderr, "%s: %s is not a text-file\n", _nc_progname, filename); 434 ExitProgram(EXIT_FAILURE); 435 } 436 fputc(ch, target); 437 } 438 fclose(source); 439 /* 440 * rewind() does not force the target file's data to disk (not does 441 * fflush()...). So open a second stream on the data and then close 442 * the one that we were writing on before starting to read from the 443 * second stream. 444 */ 445 result = safe_fopen(alt_file, "r+"); 446 fclose(target); 447 to_remove = strdup(alt_file); 448 } 449 return result; 450 } 451 452 static FILE * 453 open_input(const char *filename, char *alt_file) 454 { 455 FILE *fp; 456 struct stat sb; 457 int mode; 458 459 if (!strcmp(filename, "-")) { 460 fp = copy_input(stdin, STDIN_NAME, alt_file); 461 } else if (stat(filename, &sb) == -1) { 462 fprintf(stderr, "%s: cannot open '%s': %s\n", _nc_progname, 463 filename, strerror(errno)); 464 ExitProgram(EXIT_FAILURE); 465 } else if ((mode = (sb.st_mode & S_IFMT)) == S_IFDIR 466 || (mode != S_IFREG && mode != S_IFCHR && mode != S_IFIFO)) { 467 fprintf(stderr, "%s: cannot open '%s'; it is not a file\n", 468 _nc_progname, filename); 469 ExitProgram(EXIT_FAILURE); 470 } else { 471 fp = safe_fopen(filename, "r"); 472 473 if (fp == NULL) { 474 fprintf(stderr, "%s: cannot open '%s': %s\n", _nc_progname, 475 filename, strerror(errno)); 476 ExitProgram(EXIT_FAILURE); 477 } 478 if (mode != S_IFREG) { 479 if (alt_file != 0) { 480 FILE *fp2 = copy_input(fp, filename, alt_file); 481 fp = fp2; 482 } else { 483 fprintf(stderr, "%s: cannot open '%s'; it is not a" 484 " file\n", _nc_progname, filename); 485 ExitProgram(EXIT_FAILURE); 486 } 487 } 488 } 489 return fp; 490 } 491 492 /* Parse the "-e" option-value into a list of names */ 493 static char ** 494 make_namelist(char *src) 495 { 496 char **dst = 0; 497 498 char *s, *base; 499 unsigned pass, n, nn; 500 char buffer[BUFSIZ]; 501 502 if (src == NULL) { 503 /* EMPTY */ ; 504 } else if (strchr(src, '/') != 0) { /* a filename */ 505 FILE *fp = open_input(src, (char *) 0); 506 507 for (pass = 1; pass <= 2; pass++) { 508 nn = 0; 509 while (fgets(buffer, sizeof(buffer), fp) != 0) { 510 if ((s = stripped(buffer)) != 0) { 511 if (dst != 0) 512 dst[nn] = s; 513 else 514 free(s); 515 nn++; 516 } 517 } 518 if (pass == 1) { 519 if ((dst = typeCalloc(char *, nn + 1)) == NULL) 520 failed("make_namelist"); 521 rewind(fp); 522 } 523 } 524 fclose(fp); 525 } else { /* literal list of names */ 526 for (pass = 1; pass <= 2; pass++) { 527 for (n = nn = 0, base = src;; n++) { 528 int mark = src[n]; 529 if (mark == ',' || mark == '\0') { 530 if (pass == 1) { 531 nn++; 532 } else { 533 src[n] = '\0'; 534 if ((s = stripped(base)) != 0) 535 dst[nn++] = s; 536 base = &src[n + 1]; 537 } 538 } 539 if (mark == '\0') 540 break; 541 } 542 if (pass == 1) { 543 if ((dst = typeCalloc(char *, nn + 1)) == NULL) 544 failed("make_namelist"); 545 } 546 } 547 } 548 if (showsummary && (dst != 0)) { 549 fprintf(log_fp, "Entries that will be compiled:\n"); 550 for (n = 0; dst[n] != 0; n++) 551 fprintf(log_fp, "%u:%s\n", n + 1, dst[n]); 552 } 553 return dst; 554 } 555 556 static bool 557 matches(char **needle, const char *haystack) 558 /* does entry in needle list match |-separated field in haystack? */ 559 { 560 bool code = FALSE; 561 562 if (needle != 0) { 563 size_t n; 564 565 for (n = 0; needle[n] != 0; n++) { 566 if (_nc_name_match(haystack, needle[n], "|")) { 567 code = TRUE; 568 break; 569 } 570 } 571 } else 572 code = TRUE; 573 return (code); 574 } 575 576 static char * 577 valid_db_path(const char *nominal) 578 { 579 struct stat sb; 580 #if USE_HASHED_DB 581 char suffix[] = DBM_SUFFIX; 582 size_t need = strlen(nominal) + sizeof(suffix); 583 char *result = malloc(need); 584 585 if (result == NULL) 586 failed("valid_db_path"); 587 _nc_STRCPY(result, nominal, need); 588 if (strcmp(result + need - sizeof(suffix), suffix)) { 589 _nc_STRCAT(result, suffix, need); 590 } 591 #else 592 char *result = strdup(nominal); 593 #endif 594 595 DEBUG(1, ("** stat(%s)", result)); 596 if (stat(result, &sb) >= 0) { 597 #if USE_HASHED_DB 598 if (!S_ISREG(sb.st_mode) 599 || access(result, R_OK | W_OK) != 0) { 600 DEBUG(1, ("...not a writable file")); 601 free(result); 602 result = 0; 603 } 604 #else 605 if (!S_ISDIR(sb.st_mode) 606 || access(result, R_OK | W_OK | X_OK) != 0) { 607 DEBUG(1, ("...not a writable directory")); 608 free(result); 609 result = 0; 610 } 611 #endif 612 } else { 613 /* check if parent is directory and is writable */ 614 unsigned leaf = _nc_pathlast(result); 615 616 DEBUG(1, ("...not found")); 617 if (leaf) { 618 char save = result[leaf]; 619 result[leaf] = 0; 620 if (stat(result, &sb) >= 0 621 && S_ISDIR(sb.st_mode) 622 && access(result, R_OK | W_OK | X_OK) == 0) { 623 result[leaf] = save; 624 } else { 625 DEBUG(1, ("...parent directory %s is not writable", result)); 626 free(result); 627 result = 0; 628 } 629 } else { 630 DEBUG(1, ("... no parent directory")); 631 free(result); 632 result = 0; 633 } 634 } 635 return result; 636 } 637 638 /* 639 * Show the databases to which tic could write. The location to which it 640 * writes is always the first one. If none are writable, print an error 641 * message. 642 */ 643 static void 644 show_databases(const char *outdir) 645 { 646 bool specific = (outdir != 0) || getenv("TERMINFO") != 0; 647 char *result; 648 const char *tried = 0; 649 650 if (outdir == NULL) { 651 outdir = _nc_tic_dir(NULL); 652 } 653 if ((result = valid_db_path(outdir)) != 0) { 654 printf("%s\n", result); 655 free(result); 656 } else { 657 tried = outdir; 658 } 659 660 if ((outdir = _nc_home_terminfo())) { 661 if ((result = valid_db_path(outdir)) != 0) { 662 printf("%s\n", result); 663 free(result); 664 } else if (!specific) { 665 tried = outdir; 666 } 667 } 668 669 /* 670 * If we can write in neither location, give an error message. 671 */ 672 if (tried) { 673 fflush(stdout); 674 fprintf(stderr, "%s: %s (no permission)\n", _nc_progname, tried); 675 ExitProgram(EXIT_FAILURE); 676 } 677 } 678 679 static void 680 add_digit(int *target, int source) 681 { 682 *target = (*target * 10) + (source - '0'); 683 } 684 685 int 686 main(int argc, char *argv[]) 687 { 688 char my_tmpname[PATH_MAX]; 689 int v_opt = -1; 690 int smart_defaults = TRUE; 691 char *termcap; 692 ENTRY *qp; 693 694 int this_opt, last_opt = '?'; 695 696 int outform = F_TERMINFO; /* output format */ 697 int sortmode = S_TERMINFO; /* sort_mode */ 698 699 int width = 60; 700 int height = 65535; 701 bool formatted = FALSE; /* reformat complex strings? */ 702 bool literal = FALSE; /* suppress post-processing? */ 703 int numbers = 0; /* format "%'char'" to/from "%{number}" */ 704 bool forceresolve = FALSE; /* force resolution */ 705 bool limited = TRUE; 706 char *tversion = (char *) NULL; 707 const char *source_file; 708 char *outdir = (char *) NULL; 709 bool check_only = FALSE; 710 bool suppress_untranslatable = FALSE; 711 int quickdump = 0; 712 bool quiet = FALSE; 713 bool wrap_strings = FALSE; 714 715 log_fp = stderr; 716 717 _nc_progname = _nc_rootname(argv[0]); 718 atexit(cleanup); 719 720 if ((infodump = same_program(_nc_progname, PROG_CAPTOINFO)) != FALSE) { 721 outform = F_TERMINFO; 722 sortmode = S_TERMINFO; 723 } 724 if ((capdump = same_program(_nc_progname, PROG_INFOTOCAP)) != FALSE) { 725 outform = F_TERMCAP; 726 sortmode = S_TERMCAP; 727 } 728 #if NCURSES_XNAMES 729 /* set this directly to avoid interaction with -v and -D options */ 730 _nc_user_definable = FALSE; 731 #endif 732 _nc_strict_bsd = 0; 733 734 /* 735 * Processing arguments is a little complicated, since someone made a 736 * design decision to allow the numeric values for -w, -v options to 737 * be optional. 738 */ 739 while ((this_opt = getopt(argc, argv, 740 "0123456789CDIKLNQR:TUVWace:fGgo:qrstvwx")) != -1) { 741 if (isdigit(this_opt)) { 742 switch (last_opt) { 743 case 'Q': 744 add_digit(&quickdump, this_opt); 745 break; 746 case 'v': 747 add_digit(&v_opt, this_opt); 748 break; 749 case 'w': 750 add_digit(&width, this_opt); 751 break; 752 default: 753 switch (this_opt) { 754 case '0': 755 last_opt = this_opt; 756 width = 65535; 757 height = 1; 758 break; 759 case '1': 760 last_opt = this_opt; 761 width = 0; 762 break; 763 default: 764 usage(); 765 } 766 } 767 continue; 768 } 769 switch (this_opt) { 770 case 'K': 771 _nc_strict_bsd = 1; 772 /* the initial version of -K in 20110730 fell-thru here, but the 773 * same flag is useful when reading sources -TD 774 */ 775 break; 776 case 'C': 777 capdump = TRUE; 778 outform = F_TERMCAP; 779 sortmode = S_TERMCAP; 780 break; 781 case 'D': 782 debug_level = VtoTrace(v_opt); 783 use_verbosity(debug_level); 784 show_databases(outdir); 785 ExitProgram(EXIT_SUCCESS); 786 break; 787 case 'I': 788 infodump = TRUE; 789 outform = F_TERMINFO; 790 sortmode = S_TERMINFO; 791 break; 792 case 'L': 793 infodump = TRUE; 794 outform = F_VARIABLE; 795 sortmode = S_VARIABLE; 796 break; 797 case 'N': 798 smart_defaults = FALSE; 799 literal = TRUE; 800 break; 801 case 'Q': 802 quickdump = 0; 803 break; 804 case 'R': 805 tversion = optarg; 806 break; 807 case 'T': 808 limited = FALSE; 809 break; 810 case 'U': 811 literal = TRUE; 812 break; 813 case 'V': 814 puts(curses_version()); 815 ExitProgram(EXIT_SUCCESS); 816 case 'W': 817 wrap_strings = TRUE; 818 break; 819 case 'c': 820 check_only = TRUE; 821 break; 822 case 'e': 823 namelst = make_namelist(optarg); 824 break; 825 case 'f': 826 formatted = TRUE; 827 break; 828 case 'G': 829 numbers = 1; 830 break; 831 case 'g': 832 numbers = -1; 833 break; 834 case 'o': 835 outdir = optarg; 836 break; 837 case 'q': 838 quiet = TRUE; 839 break; 840 case 'r': 841 forceresolve = TRUE; 842 break; 843 case 's': 844 showsummary = TRUE; 845 break; 846 case 'v': 847 v_opt = 0; 848 break; 849 case 'w': 850 width = 0; 851 break; 852 #if NCURSES_XNAMES 853 case 't': 854 _nc_disable_period = FALSE; 855 suppress_untranslatable = TRUE; 856 break; 857 case 'a': 858 _nc_disable_period = TRUE; 859 /* FALLTHRU */ 860 case 'x': 861 using_extensions = TRUE; 862 break; 863 #endif 864 default: 865 usage(); 866 } 867 last_opt = this_opt; 868 } 869 870 /* 871 * If the -v option is set, it may override the $NCURSES_TRACE environment 872 * variable, e.g., for -v3 and up. 873 */ 874 debug_level = VtoTrace(v_opt); 875 use_verbosity(debug_level); 876 877 /* 878 * Do this after setting debug_level, since the function calls START_TRACE, 879 * which uses the $NCURSES_TRACE environment variable if _nc_tracing bits 880 * for tracing are zero. 881 */ 882 #if NCURSES_XNAMES 883 if (using_extensions) { 884 use_extended_names(TRUE); 885 } 886 #endif 887 888 if (_nc_tracing) { 889 save_check_termtype = _nc_check_termtype2; 890 _nc_check_termtype2 = check_termtype; 891 } 892 #if !HAVE_BIG_CORE 893 /* 894 * Aaargh! immedhook seriously hoses us! 895 * 896 * One problem with immedhook is it means we can't do -e. Problem 897 * is that we can't guarantee that for each terminal listed, all the 898 * terminals it depends on will have been kept in core for reference 899 * resolution -- in fact it is certain the primitive types at the end 900 * of reference chains *won't* be in core unless they were explicitly 901 * in the select list themselves. 902 */ 903 if (namelst && (!infodump && !capdump)) { 904 (void) fprintf(stderr, 905 "%s: Sorry, -e can't be used without -I or -C\n", 906 _nc_progname); 907 ExitProgram(EXIT_FAILURE); 908 } 909 #endif /* HAVE_BIG_CORE */ 910 911 if (optind < argc) { 912 source_file = argv[optind++]; 913 if (optind < argc) { 914 fprintf(stderr, 915 "%s: Too many file names. Usage:\n\t%s %s", 916 _nc_progname, 917 _nc_progname, 918 usage_string); 919 ExitProgram(EXIT_FAILURE); 920 } 921 } else { 922 if (infodump == TRUE) { 923 /* captoinfo's no-argument case */ 924 source_file = "/etc/termcap"; 925 if ((termcap = getenv("TERMCAP")) != 0 926 && (namelst = make_namelist(getenv("TERM"))) != 0) { 927 if (access(termcap, F_OK) == 0) { 928 /* file exists */ 929 source_file = termcap; 930 } else { 931 if ((tmp_fp = open_tempfile(my_tmpname)) != 0) { 932 source_file = my_tmpname; 933 fprintf(tmp_fp, "%s\n", termcap); 934 fclose(tmp_fp); 935 tmp_fp = open_input(source_file, (char *) 0); 936 to_remove = source_file; 937 } else { 938 failed("tmpnam"); 939 } 940 } 941 } 942 } else { 943 /* tic */ 944 fprintf(stderr, 945 "%s: File name needed. Usage:\n\t%s %s", 946 _nc_progname, 947 _nc_progname, 948 usage_string); 949 ExitProgram(EXIT_FAILURE); 950 } 951 } 952 953 if (tmp_fp == NULL) { 954 char my_altfile[PATH_MAX]; 955 tmp_fp = open_input(source_file, my_altfile); 956 if (!strcmp(source_file, "-")) { 957 source_file = STDIN_NAME; 958 } 959 } 960 961 if (infodump || check_only) { 962 dump_init(tversion, 963 (smart_defaults 964 ? outform 965 : F_LITERAL), 966 sortmode, 967 wrap_strings, width, height, 968 debug_level, formatted || check_only, check_only, quickdump); 969 } else if (capdump) { 970 dump_init(tversion, 971 outform, 972 sortmode, 973 wrap_strings, width, height, 974 debug_level, FALSE, FALSE, FALSE); 975 } 976 977 /* parse entries out of the source file */ 978 _nc_set_source(source_file); 979 #if !HAVE_BIG_CORE 980 if (!(check_only || infodump || capdump)) 981 _nc_set_writedir(outdir); 982 #endif /* HAVE_BIG_CORE */ 983 _nc_read_entry_source(tmp_fp, (char *) NULL, 984 !smart_defaults || literal, FALSE, 985 ((check_only || infodump || capdump) 986 ? NULLHOOK 987 : immedhook)); 988 989 /* do use resolution */ 990 if (check_only || (!infodump && !capdump) || forceresolve) { 991 if (!_nc_resolve_uses2(TRUE, literal) && !check_only) { 992 ExitProgram(EXIT_FAILURE); 993 } 994 } 995 996 /* length check */ 997 if (check_only && limited && (capdump || infodump)) { 998 for_entry_list(qp) { 999 if (matches(namelst, qp->tterm.term_names)) { 1000 int len = fmt_entry(&qp->tterm, NULL, FALSE, TRUE, infodump, numbers); 1001 1002 if (len > (infodump ? MAX_TERMINFO_LENGTH : MAX_TERMCAP_LENGTH)) 1003 (void) fprintf(stderr, 1004 "%s: resolved %s entry is %d bytes long\n", 1005 _nc_progname, 1006 _nc_first_name(qp->tterm.term_names), 1007 len); 1008 } 1009 } 1010 } 1011 1012 /* write or dump all entries */ 1013 if (check_only) { 1014 /* this is in case infotocap() generates warnings */ 1015 _nc_curr_col = _nc_curr_line = -1; 1016 1017 for_entry_list(qp) { 1018 if (matches(namelst, qp->tterm.term_names)) { 1019 /* this is in case infotocap() generates warnings */ 1020 _nc_set_type(_nc_first_name(qp->tterm.term_names)); 1021 _nc_curr_line = (int) qp->startline; 1022 repair_acsc(&qp->tterm); 1023 dump_entry(&qp->tterm, suppress_untranslatable, 1024 limited, numbers, NULL); 1025 } 1026 } 1027 } else { 1028 if (!infodump && !capdump) { 1029 _nc_set_writedir(outdir); 1030 for_entry_list(qp) { 1031 if (matches(namelst, qp->tterm.term_names)) 1032 write_it(qp); 1033 } 1034 } else { 1035 /* this is in case infotocap() generates warnings */ 1036 _nc_curr_col = _nc_curr_line = -1; 1037 1038 for_entry_list(qp) { 1039 if (matches(namelst, qp->tterm.term_names)) { 1040 long j = qp->cend - qp->cstart; 1041 int len = 0; 1042 1043 /* this is in case infotocap() generates warnings */ 1044 _nc_set_type(_nc_first_name(qp->tterm.term_names)); 1045 1046 if (!quiet) { 1047 (void) fseek(tmp_fp, qp->cstart, SEEK_SET); 1048 while (j-- > 0) { 1049 int ch = fgetc(tmp_fp); 1050 if (ch == EOF || ferror(tmp_fp)) { 1051 break; 1052 } else if (infodump) { 1053 (void) putchar(ch); 1054 } else { 1055 put_translate(ch); 1056 } 1057 } 1058 } 1059 1060 repair_acsc(&qp->tterm); 1061 dump_entry(&qp->tterm, suppress_untranslatable, 1062 limited, numbers, NULL); 1063 for (j = 0; j < (long) qp->nuses; j++) 1064 dump_uses(qp->uses[j].name, !capdump); 1065 len = show_entry(); 1066 if (debug_level != 0 && !limited) 1067 printf("# length=%d\n", len); 1068 } 1069 } 1070 if (!namelst && _nc_tail && !quiet) { 1071 int c, oldc = '\0'; 1072 bool in_comment = FALSE; 1073 bool trailing_comment = FALSE; 1074 1075 (void) fseek(tmp_fp, _nc_tail->cend, SEEK_SET); 1076 while ((c = fgetc(tmp_fp)) != EOF) { 1077 if (oldc == '\n') { 1078 if (c == '#') { 1079 trailing_comment = TRUE; 1080 in_comment = TRUE; 1081 } else { 1082 in_comment = FALSE; 1083 } 1084 } 1085 if (trailing_comment 1086 && (in_comment || (oldc == '\n' && c == '\n'))) 1087 putchar(c); 1088 oldc = c; 1089 } 1090 } 1091 } 1092 } 1093 1094 /* Show the directory into which entries were written, and the total 1095 * number of entries 1096 */ 1097 if (showsummary 1098 && (!(check_only || infodump || capdump))) { 1099 int total = _nc_tic_written(); 1100 if (total != 0) 1101 fprintf(log_fp, "%d entries written to %s\n", 1102 total, 1103 _nc_tic_dir(NULL)); 1104 else 1105 fprintf(log_fp, "No entries written\n"); 1106 } 1107 ExitProgram(EXIT_SUCCESS); 1108 } 1109 1110 /* 1111 * This bit of legerdemain turns all the terminfo variable names into 1112 * references to locations in the arrays Booleans, Numbers, and Strings --- 1113 * precisely what's needed (see comp_parse.c). 1114 */ 1115 #undef CUR 1116 #define CUR tp-> 1117 1118 /* 1119 * Check if the alternate character-set capabilities are consistent. 1120 */ 1121 static void 1122 check_acs(TERMTYPE2 *tp) 1123 { 1124 int vt100_smacs = 0; 1125 int vt100_rmacs = 0; 1126 int vt100_enacs = 0; 1127 1128 /* 1129 * ena_acs is not always necessary, but if it is present, the enter/exit 1130 * capabilities should be. 1131 */ 1132 ANDMISSING(ena_acs, enter_alt_charset_mode); 1133 ANDMISSING(ena_acs, exit_alt_charset_mode); 1134 PAIRED(exit_alt_charset_mode, exit_alt_charset_mode); 1135 1136 /* 1137 * vt100-like is frequently used, but perhaps ena_acs is missing, etc. 1138 */ 1139 if (VALID_STRING(enter_alt_charset_mode)) { 1140 vt100_smacs = (!strcmp("\033(0", enter_alt_charset_mode) 1141 ? 2 1142 : (!strcmp("\016", enter_alt_charset_mode) 1143 ? 1 1144 : 0)); 1145 } 1146 if (VALID_STRING(exit_alt_charset_mode)) { 1147 vt100_rmacs = (!strcmp("\033(B", exit_alt_charset_mode) 1148 ? 2 1149 : (!strcmp("\017", exit_alt_charset_mode) 1150 ? 1 1151 : 0)); 1152 } 1153 if (VALID_STRING(ena_acs)) { 1154 vt100_enacs = (!strcmp("\033(B\033)0", ena_acs) 1155 ? 2 1156 : 0); 1157 } 1158 if (vt100_rmacs && vt100_smacs && (vt100_rmacs != vt100_smacs)) { 1159 _nc_warning("rmacs/smacs are inconsistent"); 1160 } 1161 if ((vt100_rmacs == 2) && (vt100_smacs == 2) && vt100_enacs) { 1162 _nc_warning("rmacs/smacs make enacs redundant"); 1163 } 1164 if ((vt100_rmacs == 1) && (vt100_smacs == 1) && !vt100_enacs) { 1165 _nc_warning("VT100-style rmacs/smacs require enacs"); 1166 } 1167 1168 if (VALID_STRING(acs_chars)) { 1169 const char *boxes = "lmkjtuvwqxn"; 1170 char mapped[256]; 1171 char missing[256]; 1172 const char *p; 1173 char *q; 1174 1175 memset(mapped, 0, sizeof(mapped)); 1176 memset(missing, 0, sizeof(missing)); 1177 for (p = acs_chars; *p != '\0'; p += 2) { 1178 if (p[1] == '\0') { 1179 _nc_warning("acsc has odd number of characters"); 1180 break; 1181 } 1182 mapped[UChar(p[0])] = p[1]; 1183 } 1184 1185 if (mapped[UChar('I')] && !mapped[UChar('i')]) { 1186 _nc_warning("acsc refers to 'I', which is probably an error"); 1187 } 1188 1189 for (p = boxes, q = missing; *p != '\0'; ++p) { 1190 if (!mapped[UChar(p[0])]) { 1191 *q++ = p[0]; 1192 } 1193 } 1194 *q = '\0'; 1195 1196 assert(strlen(missing) <= strlen(boxes)); 1197 if (*missing != '\0' && strcmp(missing, boxes)) { 1198 _nc_warning("acsc is missing some line-drawing mapping: %s", missing); 1199 } 1200 } 1201 } 1202 1203 static char * 1204 safe_strdup(const char *value) 1205 { 1206 if (value == NULL) 1207 value = ""; 1208 return strdup(value); 1209 } 1210 1211 static bool 1212 same_color(NCURSES_CONST char *oldcap, NCURSES_CONST char *newcap, int limit) 1213 { 1214 bool result = FALSE; 1215 if (limit > 16) 1216 limit = 16; 1217 if (limit >= 8) { 1218 int n; 1219 int same; 1220 for (n = same = 0; n < limit; ++n) { 1221 char *oldvalue = safe_strdup(TIPARM_1(oldcap, n)); 1222 char *newvalue = safe_strdup(TIPARM_1(newcap, n)); 1223 same += !strcmp(oldvalue, newvalue); 1224 free(oldvalue); 1225 free(newvalue); 1226 } 1227 result = (same == limit); 1228 } 1229 return result; 1230 } 1231 1232 /* 1233 * Check if the color capabilities are consistent 1234 */ 1235 static void 1236 check_colors(TERMTYPE2 *tp) 1237 { 1238 char *value; 1239 1240 if ((max_colors > 0) != (max_pairs > 0) 1241 || ((max_colors > max_pairs) && !VALID_STRING(initialize_pair))) 1242 _nc_warning("inconsistent values for max_colors (%d) and max_pairs (%d)", 1243 max_colors, max_pairs); 1244 1245 PAIRED(set_foreground, set_background); 1246 PAIRED(set_a_foreground, set_a_background); 1247 PAIRED(set_color_pair, initialize_pair); 1248 1249 if (VALID_STRING(set_foreground) 1250 && VALID_STRING(set_a_foreground)) { 1251 if (!_nc_capcmp(set_foreground, set_a_foreground)) { 1252 _nc_warning("expected setf/setaf to be different"); 1253 } else if (same_color(set_foreground, set_a_foreground, max_colors)) { 1254 _nc_warning("setf/setaf are equivalent"); 1255 } 1256 } 1257 1258 if (VALID_STRING(set_background) 1259 && VALID_STRING(set_a_background)) { 1260 if (!_nc_capcmp(set_background, set_a_background)) { 1261 _nc_warning("expected setb/setab to be different"); 1262 } else if (same_color(set_background, set_a_background, max_colors)) { 1263 _nc_warning("setb/setab are equivalent"); 1264 } 1265 } 1266 1267 /* see: has_colors() */ 1268 if (VALID_NUMERIC(max_colors) && VALID_NUMERIC(max_pairs) 1269 && ((VALID_STRING(set_foreground) 1270 && VALID_STRING(set_background)) 1271 || (VALID_STRING(set_a_foreground) 1272 && VALID_STRING(set_a_background)) 1273 || set_color_pair)) { 1274 if (!VALID_STRING(orig_pair) && !VALID_STRING(orig_colors)) 1275 _nc_warning("expected either op/oc string for resetting colors"); 1276 } 1277 if (can_change) { 1278 if (!VALID_STRING(initialize_pair) && 1279 !VALID_STRING(initialize_color)) { 1280 _nc_warning("expected initc or initp because ccc is given"); 1281 } 1282 } else { 1283 if (VALID_STRING(initialize_pair) || 1284 VALID_STRING(initialize_color)) { 1285 _nc_warning("expected ccc because initc is given"); 1286 } 1287 } 1288 value = tigetstr("RGB"); 1289 if (VALID_STRING(value)) { 1290 int r, g, b; 1291 char bad; 1292 int code = sscanf(value, "%d/%d/%d%c", &r, &g, &b, &bad); 1293 if (code != 3 || r <= 0 || g <= 0 || b <= 0) { 1294 _nc_warning("unexpected value for RGB capability: %s", value); 1295 } 1296 } 1297 } 1298 1299 static int 1300 csi_length(const char *value) 1301 { 1302 int result = 0; 1303 1304 if (value[0] == '\033' && value[1] == '[') { 1305 result = 2; 1306 } else if (UChar(value[0]) == 0x9a) { 1307 result = 1; 1308 } 1309 return result; 1310 } 1311 1312 static char 1313 keypad_final(const char *string) 1314 { 1315 char result = '\0'; 1316 1317 if (VALID_STRING(string) 1318 && *string++ == '\033' 1319 && *string++ == 'O' 1320 && strlen(string) == 1) { 1321 result = *string; 1322 } 1323 1324 return result; 1325 } 1326 1327 static long 1328 keypad_index(const char *string) 1329 { 1330 int ch; 1331 long result = -1; 1332 1333 if ((ch = keypad_final(string)) != '\0') { 1334 const char *list = "PQRSwxymtuvlqrsPpn"; /* app-keypad except "Enter" */ 1335 char *test = (strchr) (list, ch); 1336 if (test != 0) 1337 result = (long) (test - list); 1338 } 1339 return result; 1340 } 1341 1342 /* 1343 * list[] is down, up, left, right 1344 * "left" may be ^H rather than \E[D 1345 * "down" may be ^J rather than \E[B 1346 * But up/right are generally consistently escape sequences for ANSI terminals. 1347 */ 1348 static void 1349 check_ansi_cursor(char *list[4]) 1350 { 1351 int j, k; 1352 bool skip[4]; 1353 bool repeated = FALSE; 1354 1355 for (j = 0; j < 4; ++j) { 1356 skip[j] = FALSE; 1357 for (k = 0; k < j; ++k) { 1358 if (!strcmp(list[j], list[k])) { 1359 char *value = _nc_tic_expand(list[k], TRUE, 0); 1360 _nc_warning("repeated cursor control %s", value); 1361 repeated = TRUE; 1362 } 1363 } 1364 } 1365 if (!repeated) { 1366 char *up = list[1]; 1367 size_t prefix = (size_t) csi_length(up); 1368 size_t suffix; 1369 1370 if (prefix) { 1371 suffix = prefix; 1372 while (up[suffix] && isdigit(UChar(up[suffix]))) 1373 ++suffix; 1374 } 1375 if (prefix && up[suffix] == 'A') { 1376 skip[1] = TRUE; 1377 if (!strcmp(list[0], "\n")) 1378 skip[0] = TRUE; 1379 if (!strcmp(list[2], "\b")) 1380 skip[2] = TRUE; 1381 1382 for (j = 0; j < 4; ++j) { 1383 int want; 1384 1385 if (skip[j] || strlen(list[j]) == 1) 1386 continue; 1387 if (memcmp(list[j], up, prefix)) { 1388 char *value = _nc_tic_expand(list[j], TRUE, 0); 1389 _nc_warning("inconsistent prefix for %s", value); 1390 continue; 1391 } 1392 if (strlen(list[j]) < suffix) { 1393 char *value = _nc_tic_expand(list[j], TRUE, 0); 1394 _nc_warning("inconsistent length for %s, expected %d", 1395 value, (int) suffix + 1); 1396 continue; 1397 } 1398 want = "BADC"[j]; 1399 if (list[j][suffix] != want) { 1400 char *value = _nc_tic_expand(list[j], TRUE, 0); 1401 _nc_warning("inconsistent suffix for %s, expected %c, have %c", 1402 value, want, list[j][suffix]); 1403 } 1404 } 1405 } 1406 } 1407 } 1408 1409 #define EXPECTED(name) if (!PRESENT(name)) _nc_warning("expected " #name) 1410 #define UNEXPECTED(name) if (PRESENT(name)) _nc_warning("unexpected " #name ", for %s", why) 1411 1412 static void 1413 check_noaddress(TERMTYPE2 *tp, const char *why) 1414 { 1415 UNEXPECTED(column_address); 1416 UNEXPECTED(cursor_address); 1417 UNEXPECTED(cursor_home); 1418 UNEXPECTED(cursor_mem_address); 1419 UNEXPECTED(cursor_to_ll); 1420 UNEXPECTED(row_address); 1421 UNEXPECTED(row_address); 1422 } 1423 1424 static void 1425 check_cursor(TERMTYPE2 *tp) 1426 { 1427 int count; 1428 char *list[4]; 1429 1430 if (hard_copy) { 1431 check_noaddress(tp, "hard_copy"); 1432 } else if (generic_type) { 1433 check_noaddress(tp, "generic_type"); 1434 } else if (strchr(tp->term_names, '+') == NULL) { 1435 int y = 0; 1436 int x = 0; 1437 if (PRESENT(column_address)) 1438 ++y; 1439 if (PRESENT(cursor_address)) 1440 y = x = 10; 1441 if (PRESENT(cursor_home)) 1442 ++y, ++x; 1443 if (PRESENT(cursor_mem_address)) 1444 y = x = 10; 1445 if (PRESENT(cursor_to_ll)) 1446 ++y, ++x; 1447 if (PRESENT(row_address)) 1448 ++x; 1449 if (PRESENT(cursor_down)) 1450 ++y; 1451 if (PRESENT(cursor_up)) 1452 ++y; 1453 if (PRESENT(cursor_left)) 1454 ++x; 1455 if (PRESENT(cursor_right)) 1456 ++x; 1457 if (x < 2 && y < 2) { 1458 _nc_warning("terminal lacks cursor addressing"); 1459 } else { 1460 if (x < 2) 1461 _nc_warning("terminal lacks cursor column-addressing"); 1462 if (y < 2) 1463 _nc_warning("terminal lacks cursor row-addressing"); 1464 } 1465 } 1466 1467 /* it is rare to have an insert-line feature without a matching delete */ 1468 ANDMISSING(parm_insert_line, insert_line); 1469 ANDMISSING(parm_delete_line, delete_line); 1470 ANDMISSING(parm_insert_line, parm_delete_line); 1471 1472 /* if we have a parameterized form, then the non-parameterized is easy */ 1473 ANDMISSING(parm_down_cursor, cursor_down); 1474 ANDMISSING(parm_up_cursor, cursor_up); 1475 ANDMISSING(parm_left_cursor, cursor_left); 1476 ANDMISSING(parm_right_cursor, cursor_right); 1477 1478 /* Given any of a set of cursor movement, the whole set should be present. 1479 * Technically this is not true (we could use cursor_address to fill in 1480 * unsupported controls), but it is likely. 1481 */ 1482 count = 0; 1483 if (PRESENT(parm_down_cursor)) { 1484 list[count++] = parm_down_cursor; 1485 } 1486 if (PRESENT(parm_up_cursor)) { 1487 list[count++] = parm_up_cursor; 1488 } 1489 if (PRESENT(parm_left_cursor)) { 1490 list[count++] = parm_left_cursor; 1491 } 1492 if (PRESENT(parm_right_cursor)) { 1493 list[count++] = parm_right_cursor; 1494 } 1495 if (count == 4) { 1496 check_ansi_cursor(list); 1497 } else if (count != 0) { 1498 EXPECTED(parm_down_cursor); 1499 EXPECTED(parm_up_cursor); 1500 EXPECTED(parm_left_cursor); 1501 EXPECTED(parm_right_cursor); 1502 } 1503 1504 count = 0; 1505 if (PRESENT(cursor_down)) { 1506 list[count++] = cursor_down; 1507 } 1508 if (PRESENT(cursor_up)) { 1509 list[count++] = cursor_up; 1510 } 1511 if (PRESENT(cursor_left)) { 1512 list[count++] = cursor_left; 1513 } 1514 if (PRESENT(cursor_right)) { 1515 list[count++] = cursor_right; 1516 } 1517 if (count == 4) { 1518 check_ansi_cursor(list); 1519 } else if (count != 0) { 1520 count = 0; 1521 if (PRESENT(cursor_down) && strcmp(cursor_down, "\n")) 1522 ++count; 1523 if (PRESENT(cursor_left) && strcmp(cursor_left, "\b")) 1524 ++count; 1525 if (PRESENT(cursor_up) && strlen(cursor_up) > 1) 1526 ++count; 1527 if (PRESENT(cursor_right) && strlen(cursor_right) > 1) 1528 ++count; 1529 if (count) { 1530 EXPECTED(cursor_down); 1531 EXPECTED(cursor_up); 1532 EXPECTED(cursor_left); 1533 EXPECTED(cursor_right); 1534 } 1535 } 1536 } 1537 1538 #define MAX_KP 5 1539 /* 1540 * Do a quick sanity-check for vt100-style keypads to see if the 5-key keypad 1541 * is mapped inconsistently. 1542 */ 1543 static void 1544 check_keypad(TERMTYPE2 *tp) 1545 { 1546 char show[80]; 1547 1548 if (VALID_STRING(key_a1) && 1549 VALID_STRING(key_a3) && 1550 VALID_STRING(key_b2) && 1551 VALID_STRING(key_c1) && 1552 VALID_STRING(key_c3)) { 1553 char final[MAX_KP + 1]; 1554 long list[MAX_KP]; 1555 int increase = 0; 1556 int j; 1557 1558 final[0] = keypad_final(key_a1); 1559 final[1] = keypad_final(key_a3); 1560 final[2] = keypad_final(key_b2); 1561 final[3] = keypad_final(key_c1); 1562 final[4] = keypad_final(key_c3); 1563 final[5] = '\0'; 1564 1565 /* special case: legacy coding using 1,2,3,0,. on the bottom */ 1566 assert(strlen(final) <= MAX_KP); 1567 if (!strcmp(final, "qsrpn")) 1568 return; 1569 1570 list[0] = keypad_index(key_a1); 1571 list[1] = keypad_index(key_a3); 1572 list[2] = keypad_index(key_b2); 1573 list[3] = keypad_index(key_c1); 1574 list[4] = keypad_index(key_c3); 1575 1576 /* check that they're all vt100 keys */ 1577 for (j = 0; j < MAX_KP; ++j) { 1578 if (list[j] < 0) { 1579 return; 1580 } 1581 } 1582 1583 /* check if they're all in increasing order */ 1584 for (j = 1; j < MAX_KP; ++j) { 1585 if (list[j] > list[j - 1]) { 1586 ++increase; 1587 } 1588 } 1589 1590 if (increase != (MAX_KP - 1)) { 1591 long last; 1592 1593 show[0] = '\0'; 1594 1595 for (j = 0, last = -1; j < MAX_KP; ++j) { 1596 int k; 1597 int kk; 1598 long test; 1599 1600 for (k = 0, kk = -1, test = 100; k < 5; ++k) { 1601 if (list[k] > last && 1602 list[k] < test) { 1603 test = list[k]; 1604 kk = k; 1605 } 1606 } 1607 last = test; 1608 assert(strlen(show) < (MAX_KP * 4)); 1609 switch (kk) { 1610 case 0: 1611 _nc_STRCAT(show, " ka1", sizeof(show)); 1612 break; 1613 case 1: 1614 _nc_STRCAT(show, " ka3", sizeof(show)); 1615 break; 1616 case 2: 1617 _nc_STRCAT(show, " kb2", sizeof(show)); 1618 break; 1619 case 3: 1620 _nc_STRCAT(show, " kc1", sizeof(show)); 1621 break; 1622 case 4: 1623 _nc_STRCAT(show, " kc3", sizeof(show)); 1624 break; 1625 } 1626 } 1627 1628 _nc_warning("vt100 keypad order inconsistent: %s", show); 1629 } 1630 1631 } else if (VALID_STRING(key_a1) || 1632 VALID_STRING(key_a3) || 1633 VALID_STRING(key_b2) || 1634 VALID_STRING(key_c1) || 1635 VALID_STRING(key_c3)) { 1636 show[0] = '\0'; 1637 if (keypad_index(key_a1) >= 0) 1638 _nc_STRCAT(show, " ka1", sizeof(show)); 1639 if (keypad_index(key_a3) >= 0) 1640 _nc_STRCAT(show, " ka3", sizeof(show)); 1641 if (keypad_index(key_b2) >= 0) 1642 _nc_STRCAT(show, " kb2", sizeof(show)); 1643 if (keypad_index(key_c1) >= 0) 1644 _nc_STRCAT(show, " kc1", sizeof(show)); 1645 if (keypad_index(key_c3) >= 0) 1646 _nc_STRCAT(show, " kc3", sizeof(show)); 1647 if (*show != '\0') 1648 _nc_warning("vt100 keypad map incomplete:%s", show); 1649 } 1650 1651 /* 1652 * These warnings are useful for consistency checks - it is possible that 1653 * there are real terminals with mismatches in these 1654 */ 1655 ANDMISSING(key_ic, key_dc); 1656 } 1657 1658 static void 1659 check_printer(TERMTYPE2 *tp) 1660 { 1661 (void) tp; 1662 #if defined(enter_doublewide_mode) && defined(exit_doublewide_mode) 1663 PAIRED(enter_doublewide_mode, exit_doublewide_mode); 1664 #endif 1665 #if defined(enter_italics_mode) && defined(exit_italics_mode) 1666 PAIRED(enter_italics_mode, exit_italics_mode); 1667 #endif 1668 #if defined(enter_leftward_mode) && defined(exit_leftward_mode) 1669 PAIRED(enter_leftward_mode, exit_leftward_mode); 1670 #endif 1671 #if defined(enter_micro_mode) && defined(exit_micro_mode) 1672 PAIRED(enter_micro_mode, exit_micro_mode); 1673 #endif 1674 #if defined(enter_shadow_mode) && defined(exit_shadow_mode) 1675 PAIRED(enter_shadow_mode, exit_shadow_mode); 1676 #endif 1677 #if defined(enter_subscript_mode) && defined(exit_subscript_mode) 1678 PAIRED(enter_subscript_mode, exit_subscript_mode); 1679 #endif 1680 #if defined(enter_superscript_mode) && defined(exit_superscript_mode) 1681 PAIRED(enter_superscript_mode, exit_superscript_mode); 1682 #endif 1683 #if defined(enter_upward_mode) && defined(exit_upward_mode) 1684 PAIRED(enter_upward_mode, exit_upward_mode); 1685 #endif 1686 1687 #if defined(start_char_set_def) && defined(stop_char_set_def) 1688 ANDMISSING(start_char_set_def, stop_char_set_def); 1689 #endif 1690 1691 /* 1692 * If we have a parameterized form, then the non-parameterized is easy. 1693 * note: parameterized/non-parameterized margin settings are unrelated. 1694 */ 1695 #if defined(parm_down_micro) && defined(micro_down) 1696 ANDMISSING(parm_down_micro, micro_down); 1697 #endif 1698 #if defined(parm_left_micro) && defined(micro_left) 1699 ANDMISSING(parm_left_micro, micro_left); 1700 #endif 1701 #if defined(parm_right_micro) && defined(micro_right) 1702 ANDMISSING(parm_right_micro, micro_right); 1703 #endif 1704 #if defined(parm_up_micro) && defined(micro_up) 1705 ANDMISSING(parm_up_micro, micro_up); 1706 #endif 1707 } 1708 1709 #if NCURSES_XNAMES 1710 static bool 1711 uses_SGR_39_49(const char *value) 1712 { 1713 return (strstr(value, "39;49") != 0 1714 || strstr(value, "49;39") != 0); 1715 } 1716 1717 /* 1718 * Check consistency of termcap extensions related to "screen". 1719 */ 1720 static void 1721 check_screen(TERMTYPE2 *tp) 1722 { 1723 if (_nc_user_definable) { 1724 int have_XT = tigetflag("XT"); 1725 int have_XM = tigetflag("XM"); 1726 int have_bce = back_color_erase; 1727 bool have_kmouse = FALSE; 1728 bool use_sgr_39_49 = FALSE; 1729 const char *name_39_49 = "orig_pair or orig_colors"; 1730 char *name = _nc_first_name(tp->term_names); 1731 bool is_screen = !strncmp(name, "screen", 6); 1732 bool screen_base = (is_screen 1733 && strchr(name, '.') == NULL); 1734 1735 if (!VALID_BOOLEAN(have_bce)) { 1736 have_bce = FALSE; 1737 } 1738 if (!VALID_BOOLEAN(have_XM)) { 1739 have_XM = FALSE; 1740 } 1741 if (!VALID_BOOLEAN(have_XT)) { 1742 have_XT = FALSE; 1743 } 1744 if (VALID_STRING(key_mouse)) { 1745 have_kmouse = !strcmp("\033[M", key_mouse); 1746 } 1747 if (have_bce) { 1748 if (VALID_STRING(orig_pair)) { 1749 name_39_49 = "orig_pair"; 1750 use_sgr_39_49 = uses_SGR_39_49(orig_pair); 1751 } 1752 if (!use_sgr_39_49 && VALID_STRING(orig_colors)) { 1753 name_39_49 = "orig_colors"; 1754 use_sgr_39_49 = uses_SGR_39_49(orig_colors); 1755 } 1756 } 1757 1758 if (have_XM && have_XT) { 1759 _nc_warning("screen's XT capability conflicts with XM"); 1760 } else if (have_XT && screen_base) { 1761 _nc_warning("screen's \"screen\" entries should not have XT set"); 1762 } else if (have_XT) { 1763 char *s; 1764 1765 if (!have_kmouse && is_screen) { 1766 if (VALID_STRING(key_mouse)) { 1767 _nc_warning("value of kmous inconsistent with screen's usage"); 1768 } else { 1769 _nc_warning("expected kmous capability with XT"); 1770 } 1771 } 1772 if (max_colors > 0) { 1773 if (!have_bce) { 1774 _nc_warning("expected bce capability with XT"); 1775 } else if (!use_sgr_39_49) { 1776 _nc_warning("expected %s capability with XT " 1777 "to have 39/49 parameters", name_39_49); 1778 } 1779 } 1780 if (VALID_STRING(to_status_line) 1781 && (s = strchr(to_status_line, ';')) != NULL 1782 && *++s == '\0') 1783 _nc_warning("\"tsl\" capability is redundant, given XT"); 1784 } else { 1785 if (have_kmouse 1786 && !have_XM 1787 && !screen_base && strchr(name, '+') == NULL) { 1788 _nc_warning("expected XT to be set, given kmous"); 1789 } 1790 } 1791 } 1792 } 1793 #else 1794 #define check_screen(tp) /* nothing */ 1795 #endif 1796 1797 /* 1798 * Returns the expected number of parameters for the given capability. 1799 */ 1800 static int 1801 expected_params(const char *name) 1802 { 1803 #define DATA(name,count) { { name }, count } 1804 /* *INDENT-OFF* */ 1805 static const struct { 1806 const char name[9]; 1807 int count; 1808 } table[] = { 1809 DATA( "S0", 1 ), /* 'screen' extension */ 1810 DATA( "birep", 2 ), 1811 DATA( "chr", 1 ), 1812 DATA( "colornm", 1 ), 1813 DATA( "cpi", 1 ), 1814 DATA( "csnm", 1 ), 1815 DATA( "csr", 2 ), 1816 DATA( "cub", 1 ), 1817 DATA( "cud", 1 ), 1818 DATA( "cuf", 1 ), 1819 DATA( "cup", 2 ), 1820 DATA( "cuu", 1 ), 1821 DATA( "cvr", 1 ), 1822 DATA( "cwin", 5 ), 1823 DATA( "dch", 1 ), 1824 DATA( "defc", 3 ), 1825 DATA( "dial", 1 ), 1826 DATA( "dispc", 1 ), 1827 DATA( "dl", 1 ), 1828 DATA( "ech", 1 ), 1829 DATA( "getm", 1 ), 1830 DATA( "hpa", 1 ), 1831 DATA( "ich", 1 ), 1832 DATA( "il", 1 ), 1833 DATA( "indn", 1 ), 1834 DATA( "initc", 4 ), 1835 DATA( "initp", 7 ), 1836 DATA( "lpi", 1 ), 1837 DATA( "mc5p", 1 ), 1838 DATA( "mrcup", 2 ), 1839 DATA( "mvpa", 1 ), 1840 DATA( "pfkey", 2 ), 1841 DATA( "pfloc", 2 ), 1842 DATA( "pfx", 2 ), 1843 DATA( "pfxl", 3 ), 1844 DATA( "pln", 2 ), 1845 DATA( "qdial", 1 ), 1846 DATA( "rcsd", 1 ), 1847 DATA( "rep", 2 ), 1848 DATA( "rin", 1 ), 1849 DATA( "sclk", 3 ), 1850 DATA( "scp", 1 ), 1851 DATA( "scs", 1 ), 1852 DATA( "scsd", 2 ), 1853 DATA( "setab", 1 ), 1854 DATA( "setaf", 1 ), 1855 DATA( "setb", 1 ), 1856 DATA( "setcolor", 1 ), 1857 DATA( "setf", 1 ), 1858 DATA( "sgr", 9 ), 1859 DATA( "sgr1", 6 ), 1860 DATA( "slength", 1 ), 1861 DATA( "slines", 1 ), 1862 DATA( "smgbp", 1 ), /* 2 if smgtp is not given */ 1863 DATA( "smglp", 1 ), 1864 DATA( "smglr", 2 ), 1865 DATA( "smgrp", 1 ), 1866 DATA( "smgtb", 2 ), 1867 DATA( "smgtp", 1 ), 1868 DATA( "tsl", 1 ), 1869 DATA( "u6", -1 ), 1870 DATA( "vpa", 1 ), 1871 DATA( "wind", 4 ), 1872 DATA( "wingo", 1 ), 1873 }; 1874 /* *INDENT-ON* */ 1875 #undef DATA 1876 1877 unsigned n; 1878 int result = 0; /* function-keys, etc., use none */ 1879 1880 for (n = 0; n < SIZEOF(table); n++) { 1881 if (!strcmp(name, table[n].name)) { 1882 result = table[n].count; 1883 break; 1884 } 1885 } 1886 1887 return result; 1888 } 1889 1890 /* 1891 * Check for user-capabilities that happen to be used in ncurses' terminal 1892 * database. 1893 */ 1894 #if NCURSES_XNAMES 1895 static struct user_table_entry const * 1896 lookup_user_capability(const char *name) 1897 { 1898 struct user_table_entry const *result = 0; 1899 if (*name != 'k') { 1900 result = _nc_find_user_entry(name); 1901 } 1902 return result; 1903 } 1904 #endif 1905 1906 /* 1907 * If a given name is likely to be a user-capability, return the number of 1908 * parameters it would be used with. If not, return -1. 1909 * 1910 * ncurses assumes that u6 could be used for getting the cursor-position, but 1911 * that is not implemented. Make a special case for that, to quiet needless 1912 * warnings. 1913 * 1914 * The other string-capability extensions (see terminfo.src) which could have 1915 * parameters such as "Ss", "%u", are not used by ncurses. But we check those 1916 * anyway, to validate the terminfo database. 1917 */ 1918 static int 1919 is_user_capability(const char *name) 1920 { 1921 int result = -1; 1922 if (name[0] == 'u' && 1923 (name[1] >= '0' && name[1] <= '9') && 1924 name[2] == '\0') { 1925 result = (name[1] == '6') ? 2 : 0; 1926 } 1927 #if NCURSES_XNAMES 1928 else if (using_extensions) { 1929 struct user_table_entry const *p = lookup_user_capability(name); 1930 if (p != 0) { 1931 result = (int) p->ute_argc; 1932 } 1933 } 1934 #endif 1935 return result; 1936 } 1937 1938 static bool 1939 line_capability(const char *name) 1940 { 1941 bool result = FALSE; 1942 static const char *table[] = 1943 { 1944 "csr", /* change_scroll_region */ 1945 "clear", /* clear_screen */ 1946 "ed", /* clr_eos */ 1947 "cwin", /* create_window */ 1948 "cup", /* cursor_address */ 1949 "cud1", /* cursor_down */ 1950 "home", /* cursor_home */ 1951 "mrcup", /* cursor_mem_address */ 1952 "ll", /* cursor_to_ll */ 1953 "cuu1", /* cursor_up */ 1954 "dl1", /* delete_line */ 1955 "hd", /* down_half_line */ 1956 "flash", /* flash_screen */ 1957 "ff", /* form_feed */ 1958 "il1", /* insert_line */ 1959 "nel", /* newline */ 1960 "dl", /* parm_delete_line */ 1961 "cud", /* parm_down_cursor */ 1962 "indn", /* parm_index */ 1963 "il", /* parm_insert_line */ 1964 "rin", /* parm_rindex */ 1965 "cuu", /* parm_up_cursor */ 1966 "mc0", /* print_screen */ 1967 "vpa", /* row_address */ 1968 "ind", /* scroll_forward */ 1969 "ri", /* scroll_reverse */ 1970 "hu", /* up_half_line */ 1971 }; 1972 size_t n; 1973 for (n = 0; n < SIZEOF(table); ++n) { 1974 if (!strcmp(name, table[n])) { 1975 result = TRUE; 1976 break; 1977 } 1978 } 1979 return result; 1980 } 1981 1982 /* 1983 * Make a quick sanity check for the parameters which are used in the given 1984 * strings. If there are no "%p" tokens, then there should be no other "%" 1985 * markers. 1986 */ 1987 static void 1988 check_params(TERMTYPE2 *tp, const char *name, const char *value, int extended) 1989 { 1990 int expected = expected_params(name); 1991 int actual = 0; 1992 int n; 1993 bool params[1 + NUM_PARM]; 1994 const char *s = value; 1995 1996 #ifdef set_left_margin_parm 1997 if (!strcmp(name, "smgrp") 1998 && !VALID_STRING(set_left_margin_parm)) 1999 expected = 2; 2000 #endif 2001 #ifdef set_right_margin_parm 2002 if (!strcmp(name, "smglp") 2003 && !VALID_STRING(set_right_margin_parm)) 2004 expected = 2; 2005 #endif 2006 #ifdef set_top_margin_parm 2007 if (!strcmp(name, "smgbp") 2008 && !VALID_STRING(set_top_margin_parm)) 2009 expected = 2; 2010 #endif 2011 #ifdef set_bottom_margin_parm 2012 if (!strcmp(name, "smgtp") 2013 && !VALID_STRING(set_bottom_margin_parm)) 2014 expected = 2; 2015 #endif 2016 2017 for (n = 0; n <= NUM_PARM; n++) 2018 params[n] = FALSE; 2019 2020 while (*s != 0) { 2021 if (*s == '%') { 2022 if (*++s == '\0') { 2023 _nc_warning("expected character after %% in %s", name); 2024 break; 2025 } else if (*s == 'p') { 2026 if (*++s == '\0' || !isdigit((int) *s)) { 2027 _nc_warning("expected digit after %%p in %s", name); 2028 return; 2029 } else { 2030 n = (*s - '0'); 2031 if (n > actual) 2032 actual = n; 2033 params[n] = TRUE; 2034 } 2035 } 2036 } 2037 s++; 2038 } 2039 2040 #if NCURSES_XNAMES 2041 if (extended) { 2042 int check = is_user_capability(name); 2043 if (check != actual && (check >= 0 && actual >= 0)) { 2044 _nc_warning("extended %s capability has %d parameters, expected %d", 2045 name, actual, check); 2046 } else if (debug_level > 1) { 2047 _nc_warning("extended %s capability has %d parameters, as expected", 2048 name, actual); 2049 } 2050 expected = actual; 2051 } 2052 #else 2053 (void) extended; 2054 #endif 2055 2056 if (params[0]) { 2057 _nc_warning("%s refers to parameter 0 (%%p0), which is not allowed", name); 2058 } 2059 if (value == set_attributes || expected < 0) { 2060 ; 2061 } else if (expected != actual) { 2062 _nc_warning("%s uses %d parameters, expected %d", name, 2063 actual, expected); 2064 for (n = 1; n < actual; n++) { 2065 if (!params[n]) 2066 _nc_warning("%s omits parameter %d", name, n); 2067 } 2068 } 2069 2070 /* 2071 * Counting "%p" markers does not account for termcap expressions which 2072 * may not have been fully translated. Also, tparm does its own analysis. 2073 * Report differences here. 2074 */ 2075 _nc_reset_tparm(NULL); 2076 if (actual >= 0) { 2077 char *p_is_s[NUM_PARM]; 2078 int popcount; 2079 int analyzed = _nc_tparm_analyze(NULL, value, p_is_s, &popcount); 2080 if (analyzed < popcount) { 2081 analyzed = popcount; 2082 } 2083 if (actual != analyzed && expected != analyzed) { 2084 #if NCURSES_XNAMES 2085 int user_cap = is_user_capability(name); 2086 if ((user_cap == analyzed) && using_extensions) { 2087 ; /* ignore */ 2088 } else if (user_cap >= 0) { 2089 _nc_warning("tparm will use %d parameters for %s, expected %d", 2090 analyzed, name, user_cap); 2091 } else 2092 #endif 2093 { 2094 _nc_warning("tparm analyzed %d parameters for %s, expected %d", 2095 analyzed, name, actual); 2096 } 2097 } else if (expected > 0 2098 && actual == expected 2099 && guess_tparm_type(expected, p_is_s) == Numbers) { 2100 int limit = 1; 2101 2102 if (!strcmp(name, "setf") 2103 || !strcmp(name, "setb") 2104 || !strcmp(name, "setaf") 2105 || !strcmp(name, "setab")) { 2106 if ((limit = max_colors) > 256) 2107 limit = 256; 2108 } else if (line_capability(name)) { 2109 limit = 24; 2110 } else if (is_user_capability(name) < 0) { 2111 limit = 80; 2112 } 2113 for (n = 0; n < limit; ++n) { 2114 _nc_reset_tparm(NULL); 2115 (void) TPARM_9(value, n, n, n, n, n, n, n, n, n); 2116 if (_nc_tparm_err) { 2117 _nc_warning("problem%s in tparm(%s, %d, ...)", 2118 (_nc_tparm_err == 1) ? "" : "s", 2119 name, n); 2120 if (debug_level < 2) 2121 break; 2122 } 2123 } 2124 } 2125 } 2126 } 2127 2128 /* 2129 * Check for DEC VT100 private mode for reverse video. 2130 */ 2131 static const char * 2132 skip_DECSCNM(const char *value, int *flag) 2133 { 2134 *flag = -1; 2135 if (value != 0) { 2136 int skip = csi_length(value); 2137 if (skip > 0 && 2138 value[skip++] == '?' && 2139 value[skip++] == '5') { 2140 if (value[skip] == 'h') { 2141 *flag = 1; 2142 } else if (value[skip] == 'l') { 2143 *flag = 0; 2144 } 2145 value += skip + 1; 2146 } 2147 } 2148 return value; 2149 } 2150 2151 static void 2152 check_delays(TERMTYPE2 *tp, const char *name, const char *value) 2153 { 2154 const char *p, *q; 2155 const char *first = 0; 2156 const char *last = 0; 2157 2158 for (p = value; *p != '\0'; ++p) { 2159 if (p[0] == '$' && p[1] == '<') { 2160 const char *base = p + 2; 2161 const char *mark = 0; 2162 bool mixed = FALSE; 2163 int proportional = 0; 2164 int mandatory = 0; 2165 2166 first = p; 2167 2168 for (q = base; *q != '\0'; ++q) { 2169 if (*q == '>') { 2170 if (mark == NULL) 2171 mark = q; 2172 break; 2173 } else if (*q == '*' || *q == '/') { 2174 if (*q == '*') 2175 ++proportional; 2176 if (*q == '/') 2177 ++mandatory; 2178 if (mark == NULL) 2179 mark = q; 2180 } else if (!(isalnum(UChar(*q)) || strchr("+-.", *q) != 0)) { 2181 break; 2182 } else if (proportional || mandatory) { 2183 mixed = TRUE; 2184 } 2185 } 2186 last = *q ? (q + 1) : q; 2187 if (*q != '\0') { 2188 float check_f; 2189 char check_c; 2190 int rc = sscanf(base, "%f%c", &check_f, &check_c); 2191 if ((rc != 2) || (mark != NULL && (check_c != *mark)) || mixed) { 2192 _nc_warning("syntax error in %s delay '%.*s'", name, 2193 (int) (q - base), base); 2194 } else if (*name == 'k') { 2195 _nc_warning("function-key %s has delay", name); 2196 } else if (proportional && !line_capability(name)) { 2197 _nc_warning("non-line capability using proportional delay: %s", name); 2198 } else if (!xon_xoff && 2199 !mandatory && 2200 strchr(_nc_first_name(tp->term_names), '+') == NULL) { 2201 _nc_warning("%s in %s is used since no xon/xoff", 2202 (proportional 2203 ? "proportional delay" 2204 : "delay"), 2205 name); 2206 } 2207 } else { 2208 p = q - 1; /* restart scan */ 2209 } 2210 } 2211 } 2212 2213 if (!strcmp(name, "flash") || 2214 !strcmp(name, "beep")) { 2215 2216 if (first != 0) { 2217 if (first == value || *last == 0) { 2218 /* 2219 * Delay is on one end or the other. 2220 */ 2221 _nc_warning("expected delay embedded within %s", name); 2222 } 2223 } else { 2224 int flag; 2225 2226 /* 2227 * Check for missing delay when using VT100 reverse-video. 2228 * A real VT100 might not need this, but terminal emulators do. 2229 */ 2230 if ((p = skip_DECSCNM(value, &flag)) != 0 && 2231 flag > 0 && 2232 skip_DECSCNM(p, &flag) != 0 && 2233 flag == 0) { 2234 _nc_warning("expected a delay in %s", name); 2235 } 2236 } 2237 } 2238 } 2239 2240 static char * 2241 check_1_infotocap(const char *name, NCURSES_CONST char *value, int count) 2242 { 2243 int k; 2244 int ignored; 2245 long numbers[1 + NUM_PARM]; 2246 char *strings[1 + NUM_PARM]; 2247 char *p_is_s[NUM_PARM]; 2248 char *result; 2249 char blob[NUM_PARM * 10]; 2250 char *next = blob; 2251 TParams expect; 2252 TParams actual; 2253 int nparam; 2254 2255 *next++ = '\0'; 2256 for (k = 1; k <= NUM_PARM; k++) { 2257 numbers[k] = count; 2258 _nc_SPRINTF(next, 2259 _nc_SLIMIT(sizeof(blob) - (size_t) (next - blob)) 2260 "XYZ%d", count); 2261 strings[k] = next; 2262 next += strlen(next) + 1; 2263 } 2264 2265 _nc_reset_tparm(NULL); 2266 expect = tparm_type(name); 2267 nparam = _nc_tparm_analyze(NULL, value, p_is_s, &ignored); 2268 actual = guess_tparm_type(nparam, p_is_s); 2269 2270 if (expect != actual) { 2271 _nc_warning("%s has mismatched parameters", name); 2272 actual = Other; 2273 } 2274 2275 _nc_reset_tparm(NULL); 2276 switch (actual) { 2277 case Str: 2278 result = TPARM_1(value, strings[1]); 2279 break; 2280 case Num_Str: 2281 result = TPARM_2(value, numbers[1], strings[2]); 2282 break; 2283 case Str_Str: 2284 result = TPARM_2(value, strings[1], strings[2]); 2285 break; 2286 case Num_Str_Str: 2287 result = TPARM_3(value, numbers[1], strings[2], strings[3]); 2288 break; 2289 case Numbers: 2290 #define myParam(n) numbers[n] 2291 result = TIPARM_9(value, 2292 myParam(1), 2293 myParam(2), 2294 myParam(3), 2295 myParam(4), 2296 myParam(5), 2297 myParam(6), 2298 myParam(7), 2299 myParam(8), 2300 myParam(9)); 2301 #undef myParam 2302 break; 2303 case Other: 2304 default: 2305 #define myParam(n) (p_is_s[n - 1] != 0 ? ((TPARM_ARG) strings[n]) : numbers[n]) 2306 result = TPARM_9(value, 2307 myParam(1), 2308 myParam(2), 2309 myParam(3), 2310 myParam(4), 2311 myParam(5), 2312 myParam(6), 2313 myParam(7), 2314 myParam(8), 2315 myParam(9)); 2316 #undef myParam 2317 break; 2318 } 2319 return strdup(result); 2320 } 2321 2322 #define IsDelay(ch) ((ch) == '.' || isdigit(UChar(ch))) 2323 2324 static const char * 2325 parse_delay_value(const char *src, double *delays, int *always) 2326 { 2327 int star = 0; 2328 2329 *delays = 0.0; 2330 if (always) 2331 *always = 0; 2332 2333 while (isdigit(UChar(*src))) { 2334 (*delays) = (*delays) * 10 + (*src++ - '0'); 2335 } 2336 if (*src == '.') { 2337 int gotdot = 1; 2338 2339 ++src; 2340 while (isdigit(UChar(*src))) { 2341 gotdot *= 10; 2342 (*delays) += (*src++ - '0') / gotdot; 2343 } 2344 } 2345 while (*src == '*' || *src == '/') { 2346 if (always == NULL && *src == '/') 2347 break; 2348 if (*src++ == '*') { 2349 star = 1; 2350 } else { 2351 *always = 1; 2352 } 2353 } 2354 if (star) 2355 *delays = -(*delays); 2356 return src; 2357 } 2358 2359 static const char * 2360 parse_ti_delay(const char *ti, double *delays) 2361 { 2362 *delays = 0.0; 2363 while (*ti != '\0') { 2364 if (*ti == '\\') { 2365 ++ti; 2366 } 2367 if (ti[0] == '$' 2368 && ti[1] == '<' 2369 && IsDelay(UChar(ti[2]))) { 2370 int ignored; 2371 const char *last = parse_delay_value(ti + 2, delays, &ignored); 2372 if (*last == '>') { 2373 ti = last; 2374 } 2375 } else { 2376 ++ti; 2377 } 2378 } 2379 return ti; 2380 } 2381 2382 static const char * 2383 parse_tc_delay(const char *tc, double *delays) 2384 { 2385 return parse_delay_value(tc, delays, (int *) 0); 2386 } 2387 2388 /* 2389 * Compare terminfo- and termcap-strings, factoring out delays. 2390 */ 2391 static bool 2392 same_ti_tc(const char *ti, const char *tc, bool * embedded) 2393 { 2394 bool same = TRUE; 2395 double ti_delay = 0.0; 2396 double tc_delay = 0.0; 2397 const char *ti_last; 2398 2399 *embedded = FALSE; 2400 ti_last = parse_ti_delay(ti, &ti_delay); 2401 tc = parse_tc_delay(tc, &tc_delay); 2402 2403 while ((ti < ti_last) && *tc) { 2404 if (*ti == '\\' && ispunct(UChar(ti[1]))) { 2405 ++ti; 2406 if ((*ti == '^') && !strncmp(tc, "\\136", 4)) { 2407 ti += 1; 2408 tc += 4; 2409 continue; 2410 } 2411 } else if (ti[0] == '$' && ti[1] == '<') { 2412 double no_delay; 2413 const char *ss = parse_ti_delay(ti, &no_delay); 2414 if (ss != ti) { 2415 *embedded = TRUE; 2416 ti = ss; 2417 continue; 2418 } 2419 } 2420 if (*tc == '\\' && ispunct(UChar(tc[1]))) { 2421 ++tc; 2422 } 2423 if (*ti++ != *tc++) { 2424 same = FALSE; 2425 break; 2426 } 2427 } 2428 2429 if (*embedded) { 2430 if (same) { 2431 same = FALSE; 2432 } else { 2433 *embedded = FALSE; /* report only one problem */ 2434 } 2435 } 2436 2437 return same; 2438 } 2439 2440 /* 2441 * Check terminfo to termcap translation. 2442 */ 2443 static void 2444 check_infotocap(TERMTYPE2 *tp, int i, const char *value) 2445 { 2446 const char *name = ExtStrname(tp, i, strnames); 2447 char *ti_value = NULL; 2448 2449 assert(SIZEOF(parametrized) == STRCOUNT); 2450 if (!VALID_STRING(value) || (ti_value = strdup(value)) == NULL) { 2451 _nc_warning("tic-expansion of %s failed", name); 2452 } else { 2453 char *tc_value; 2454 bool embedded; 2455 int params = ((i < (int) SIZEOF(parametrized)) 2456 ? parametrized[i] 2457 : ((*value == 'k') 2458 ? 0 2459 : has_params(value, FALSE))); 2460 2461 if ((tc_value = _nc_infotocap(name, ti_value, params)) == ABSENT_STRING) { 2462 _nc_warning("tic-conversion of %s failed", name); 2463 } else if (params > 0) { 2464 int limit = 5; 2465 int count; 2466 bool first = TRUE; 2467 2468 if (!strcmp(name, "setf") 2469 || !strcmp(name, "setb") 2470 || !strcmp(name, "setaf") 2471 || !strcmp(name, "setab")) { 2472 if ((limit = max_colors) > 256) 2473 limit = 256; 2474 } 2475 for (count = 0; count < limit; ++count) { 2476 char *ti_check = check_1_infotocap(name, ti_value, count); 2477 char *tc_check = check_1_infotocap(name, tc_value, count); 2478 2479 if (strcmp(ti_check, tc_check)) { 2480 if (first) { 2481 fprintf(stderr, "check_infotocap(%s)\n", name); 2482 fprintf(stderr, "...ti '%s'\n", _nc_visbuf2(0, ti_value)); 2483 fprintf(stderr, "...tc '%s'\n", _nc_visbuf2(0, tc_value)); 2484 first = FALSE; 2485 } 2486 _nc_warning("tparm-conversion of %s(%d) differs between\n\tterminfo %s\n\ttermcap %s", 2487 name, count, 2488 _nc_visbuf2(0, ti_check), 2489 _nc_visbuf2(1, tc_check)); 2490 } 2491 free(ti_check); 2492 free(tc_check); 2493 } 2494 } else if (params == 0 && !same_ti_tc(ti_value, tc_value, &embedded)) { 2495 if (embedded) { 2496 _nc_warning("termcap equivalent of %s cannot use embedded delay", name); 2497 } else { 2498 _nc_warning("tic-conversion of %s changed value\n\tfrom %s\n\tto %s", 2499 name, ti_value, tc_value); 2500 } 2501 } 2502 free(ti_value); 2503 } 2504 } 2505 2506 static char * 2507 skip_delay(char *s) 2508 { 2509 while (*s == '/' || isdigit(UChar(*s))) 2510 ++s; 2511 return s; 2512 } 2513 2514 /* 2515 * Skip a delay altogether, e.g., when comparing a simple string to sgr, 2516 * the latter may have a worst-case delay on the end. 2517 */ 2518 static char * 2519 ignore_delays(char *s) 2520 { 2521 int delaying = 0; 2522 2523 do { 2524 switch (*s) { 2525 case '$': 2526 if (delaying == 0) 2527 delaying = 1; 2528 break; 2529 case '<': 2530 if (delaying == 1) 2531 delaying = 2; 2532 break; 2533 case '\0': 2534 delaying = 0; 2535 break; 2536 default: 2537 if (delaying) { 2538 s = skip_delay(s); 2539 if (*s == '>') 2540 ++s; 2541 delaying = 0; 2542 } 2543 break; 2544 } 2545 if (delaying) 2546 ++s; 2547 } while (delaying); 2548 return s; 2549 } 2550 2551 #define DATA(name) { #name } 2552 static const char sgr_names[][11] = 2553 { 2554 DATA(none), 2555 DATA(standout), 2556 DATA(underline), 2557 DATA(reverse), 2558 DATA(blink), 2559 DATA(dim), 2560 DATA(bold), 2561 DATA(invis), 2562 DATA(protect), 2563 DATA(altcharset), 2564 "" 2565 }; 2566 #undef DATA 2567 2568 /* 2569 * An sgr string may contain several settings other than the one we're 2570 * interested in, essentially sgr0 + rmacs + whatever. As long as the 2571 * "whatever" is contained in the sgr string, that is close enough for our 2572 * sanity check. 2573 */ 2574 static bool 2575 similar_sgr(int num, char *a, char *b) 2576 { 2577 char *base_a = a; 2578 char *base_b = b; 2579 int delaying = 0; 2580 2581 while (*b != 0) { 2582 while (*a != *b) { 2583 if (*a == 0) { 2584 if (num < 0) { 2585 ; 2586 } else if (b[0] == '$' 2587 && b[1] == '<') { 2588 _nc_warning("did not find delay %s", _nc_visbuf(b)); 2589 } else { 2590 _nc_warning("checking sgr(%s) %s\n\tcompare to %s\n\tunmatched %s", 2591 sgr_names[num], _nc_visbuf2(1, base_a), 2592 _nc_visbuf2(2, base_b), 2593 _nc_visbuf2(3, b)); 2594 } 2595 return FALSE; 2596 } else if (delaying) { 2597 a = skip_delay(a); 2598 b = skip_delay(b); 2599 } else if ((*b == '0' || (*b == ';')) && *a == 'm') { 2600 b++; 2601 } else { 2602 a++; 2603 } 2604 } 2605 switch (*a) { 2606 case '$': 2607 if (delaying == 0) 2608 delaying = 1; 2609 break; 2610 case '<': 2611 if (delaying == 1) 2612 delaying = 2; 2613 break; 2614 default: 2615 delaying = 0; 2616 break; 2617 } 2618 a++; 2619 b++; 2620 } 2621 /* ignore delays on the end of the string */ 2622 a = ignore_delays(a); 2623 return ((num != 0) || (*a == 0)); 2624 } 2625 2626 static void 2627 check_tparm_err(int num) 2628 { 2629 if (_nc_tparm_err) 2630 _nc_warning("tparam error in sgr(%d): %s", num, sgr_names[num]); 2631 } 2632 2633 static char * 2634 check_sgr(TERMTYPE2 *tp, char *zero, int num, char *cap, const char *name) 2635 { 2636 char *test; 2637 2638 _nc_tparm_err = 0; 2639 test = TIPARM_9(set_attributes, 2640 num == 1, 2641 num == 2, 2642 num == 3, 2643 num == 4, 2644 num == 5, 2645 num == 6, 2646 num == 7, 2647 num == 8, 2648 num == 9); 2649 if (test != 0) { 2650 if (PRESENT(cap)) { 2651 if (!similar_sgr(num, test, cap)) { 2652 _nc_warning("%s differs from sgr(%d)\n\t%s=%s\n\tsgr(%d)=%s", 2653 name, num, 2654 name, _nc_visbuf2(1, cap), 2655 num, _nc_visbuf2(2, test)); 2656 } 2657 } else if (_nc_capcmp(test, zero)) { 2658 _nc_warning("sgr(%d) present, but not %s", num, name); 2659 } 2660 } else if (PRESENT(cap)) { 2661 _nc_warning("sgr(%d) missing, but %s present", num, name); 2662 } 2663 check_tparm_err(num); 2664 return test; 2665 } 2666 2667 #define CHECK_SGR(num,name) check_sgr(tp, zero, num, name, #name) 2668 2669 #ifdef TRACE 2670 /* 2671 * If tic is compiled with TRACE, we'll be able to see the output from the 2672 * DEBUG() macro. But since it doesn't use traceon(), it always goes to 2673 * the standard error. Use this function to make it simpler to follow the 2674 * resulting debug traces. 2675 */ 2676 static void 2677 show_where(unsigned level) 2678 { 2679 if (_nc_tracing >= DEBUG_LEVEL(level)) { 2680 char my_name[MAX_NAME_SIZE]; 2681 _nc_get_type(my_name); 2682 _tracef("\"%s\", line %d, '%s'", 2683 _nc_get_source(), 2684 _nc_curr_line, my_name); 2685 } 2686 } 2687 2688 #else 2689 #define show_where(level) /* nothing */ 2690 #endif 2691 2692 typedef struct { 2693 int keycode; 2694 const char *name; 2695 const char *value; 2696 } NAME_VALUE; 2697 2698 static NAME_VALUE * 2699 get_fkey_list(TERMTYPE2 *tp) 2700 { 2701 NAME_VALUE *result = typeMalloc(NAME_VALUE, NUM_STRINGS(tp) + 1); 2702 const struct tinfo_fkeys *all_fkeys = _nc_tinfo_fkeys; 2703 int used = 0; 2704 unsigned j; 2705 2706 if (result == NULL) 2707 failed("get_fkey_list"); 2708 2709 for (j = 0; all_fkeys[j].code; j++) { 2710 char *a = tp->Strings[all_fkeys[j].offset]; 2711 if (VALID_STRING(a)) { 2712 result[used].keycode = (int) all_fkeys[j].code; 2713 result[used].name = strnames[all_fkeys[j].offset]; 2714 result[used].value = a; 2715 ++used; 2716 } 2717 } 2718 #if NCURSES_XNAMES 2719 for (j = STRCOUNT; j < NUM_STRINGS(tp); ++j) { 2720 const char *name = ExtStrname(tp, (int) j, strnames); 2721 if (*name == 'k') { 2722 result[used].keycode = -1; 2723 result[used].name = name; 2724 result[used].value = tp->Strings[j]; 2725 ++used; 2726 } 2727 } 2728 #endif 2729 result[used].keycode = 0; 2730 return result; 2731 } 2732 2733 static void 2734 show_fkey_name(NAME_VALUE * data) 2735 { 2736 if (data->keycode > 0) { 2737 fprintf(stderr, " %s", keyname(data->keycode)); 2738 fprintf(stderr, " (capability \"%s\")", data->name); 2739 } else { 2740 fprintf(stderr, " capability \"%s\"", data->name); 2741 } 2742 } 2743 2744 /* 2745 * A terminal entry may contain more than one keycode assigned to a given 2746 * string (e.g., KEY_END and KEY_LL). But curses will only return one (the 2747 * last one assigned). 2748 */ 2749 static void 2750 check_conflict(TERMTYPE2 *tp) 2751 { 2752 if (!(_nc_syntax == SYN_TERMCAP && capdump)) { 2753 char *check = calloc((size_t) (NUM_STRINGS(tp) + 1), sizeof(char)); 2754 NAME_VALUE *given = get_fkey_list(tp); 2755 unsigned j, k; 2756 bool conflict = FALSE; 2757 2758 if (check == NULL) 2759 failed("check_conflict"); 2760 2761 for (j = 0; given[j].keycode; ++j) { 2762 const char *a = given[j].value; 2763 bool first = TRUE; 2764 2765 if (!VALID_STRING(a)) 2766 continue; 2767 2768 for (k = j + 1; given[k].keycode; k++) { 2769 const char *b = given[k].value; 2770 2771 if (!VALID_STRING(b)) 2772 continue; 2773 if (check[k]) 2774 continue; 2775 2776 if (!_nc_capcmp(a, b)) { 2777 check[j] = 1; 2778 check[k] = 1; 2779 if (first) { 2780 if (!conflict) { 2781 _nc_warning("conflicting key definitions (using the last)"); 2782 conflict = TRUE; 2783 } 2784 fprintf(stderr, "..."); 2785 show_fkey_name(given + j); 2786 fprintf(stderr, " is the same as"); 2787 show_fkey_name(given + k); 2788 first = FALSE; 2789 } else { 2790 fprintf(stderr, ", "); 2791 show_fkey_name(given + k); 2792 } 2793 } 2794 } 2795 if (!first) 2796 fprintf(stderr, "\n"); 2797 } 2798 #if NCURSES_XNAMES 2799 if (using_extensions) { 2800 /* *INDENT-OFF* */ 2801 static struct { 2802 const char *xcurses; 2803 const char *shifted; 2804 } table[] = { 2805 { "kDC", NULL }, 2806 { "kDN", "kind" }, 2807 { "kEND", NULL }, 2808 { "kHOM", NULL }, 2809 { "kLFT", NULL }, 2810 { "kNXT", NULL }, 2811 { "kPRV", NULL }, 2812 { "kRIT", NULL }, 2813 { "kUP", "kri" }, 2814 { NULL, NULL }, 2815 }; 2816 /* *INDENT-ON* */ 2817 /* 2818 * SVr4 curses defines the "xcurses" names listed above except for 2819 * the special cases in the "shifted" column. When using these 2820 * names for xterm's extensions, that was confusing, and resulted 2821 * in adding extended capabilities with "2" (shift) suffix. This 2822 * check warns about unnecessary use of extensions for this quirk. 2823 */ 2824 for (j = 0; given[j].keycode; ++j) { 2825 const char *find = given[j].name; 2826 int value; 2827 char ch; 2828 2829 if (!VALID_STRING(given[j].value)) 2830 continue; 2831 2832 for (k = 0; table[k].xcurses; ++k) { 2833 const char *test = table[k].xcurses; 2834 size_t size = strlen(test); 2835 2836 if (!strncmp(find, test, size) && strcmp(find, test)) { 2837 switch (sscanf(find + size, "%d%c", &value, &ch)) { 2838 case 1: 2839 if (value == 2) { 2840 _nc_warning("expected '%s' rather than '%s'", 2841 (table[k].shifted 2842 ? table[k].shifted 2843 : test), find); 2844 } else if (value < 2 || value > 15) { 2845 _nc_warning("expected numeric 2..15 '%s'", find); 2846 } 2847 break; 2848 default: 2849 _nc_warning("expected numeric suffix for '%s'", find); 2850 break; 2851 } 2852 break; 2853 } 2854 } 2855 } 2856 } 2857 #endif 2858 free(given); 2859 free(check); 2860 } 2861 } 2862 2863 /* 2864 * Exiting a video mode should not duplicate sgr0 2865 */ 2866 static void 2867 check_exit_attribute(const char *name, char *test, char *trimmed, char *untrimmed) 2868 { 2869 if (VALID_STRING(test) && (trimmed != 0)) { 2870 if (similar_sgr(-1, trimmed, test) || 2871 similar_sgr(-1, untrimmed, test)) { 2872 _nc_warning("%s matches exit_attribute_mode", name); 2873 } 2874 } 2875 } 2876 2877 /* 2878 * Returns true if the string looks like a standard SGR string. 2879 */ 2880 static bool 2881 is_sgr_string(char *value) 2882 { 2883 bool result = FALSE; 2884 2885 if (VALID_STRING(value)) { 2886 int skip = csi_length(value); 2887 2888 if (skip) { 2889 int ch; 2890 2891 result = TRUE; 2892 value += skip; 2893 while ((ch = UChar(*value++)) != '\0') { 2894 if (isdigit(ch) || ch == ';') { 2895 ; 2896 } else if (ch == 'm' && *value == '\0') { 2897 ; 2898 } else { 2899 result = FALSE; 2900 break; 2901 } 2902 } 2903 } 2904 } 2905 return result; 2906 } 2907 2908 /* 2909 * Check if the given capability contains a given SGR attribute. 2910 */ 2911 static void 2912 check_sgr_param(TERMTYPE2 *tp, int code, const char *name, char *value) 2913 { 2914 if (VALID_STRING(value)) { 2915 int ncv = ((code != 0) ? (1 << (code - 1)) : 0); 2916 char *test = tgoto(value, 0, 0); 2917 if (is_sgr_string(test)) { 2918 int param = 0; 2919 int count = 0; 2920 int skips = 0; 2921 int color = (value == set_a_foreground || 2922 value == set_a_background || 2923 value == set_foreground || 2924 value == set_background); 2925 while (*test != 0) { 2926 if (isdigit(UChar(*test))) { 2927 param = 10 * param + (*test - '0'); 2928 ++count; 2929 } else { 2930 if (count) { 2931 /* 2932 * Avoid unnecessary warning for xterm 256color codes. 2933 */ 2934 if (color && (param == 38 || param == 48)) 2935 skips = 3; 2936 if ((skips-- <= 0) && (param == code)) 2937 break; 2938 } 2939 count = 0; 2940 param = 0; 2941 } 2942 ++test; 2943 } 2944 if (count != 0 && param == code) { 2945 if (code == 0 || 2946 no_color_video < 0 || 2947 !(no_color_video & ncv)) { 2948 _nc_warning("\"%s\" SGR-attribute used in %s", 2949 sgr_names[code], 2950 name); 2951 } 2952 } 2953 } 2954 } 2955 } 2956 2957 #if NCURSES_XNAMES 2958 static int 2959 standard_type(const char *name) 2960 { 2961 int result = -1; 2962 const struct name_table_entry *np; 2963 2964 if ((np = _nc_find_entry(name, _nc_get_hash_table(0))) != 0) { 2965 result = np->nte_type; 2966 } 2967 return result; 2968 } 2969 2970 static const char * 2971 name_of_type(int type) 2972 { 2973 const char *result = "unknown"; 2974 switch (type) { 2975 case BOOLEAN: 2976 result = "boolean"; 2977 break; 2978 case NUMBER: 2979 result = "number"; 2980 break; 2981 case STRING: 2982 result = "string"; 2983 break; 2984 } 2985 return result; 2986 } 2987 2988 static void 2989 check_user_capability_type(const char *name, int actual) 2990 { 2991 if (lookup_user_capability(name) == 0) { 2992 int expected = standard_type(name); 2993 if (expected >= 0) { 2994 _nc_warning("expected %s to be %s, but actually %s", 2995 name, 2996 name_of_type(actual), 2997 name_of_type(expected) 2998 ); 2999 } else if (*name != 'k') { 3000 _nc_warning("undocumented %s capability %s", 3001 name_of_type(actual), 3002 name); 3003 } 3004 } 3005 } 3006 #endif 3007 3008 #define IN_DELAY "0123456789*/." 3009 3010 static bool 3011 check_ANSI_cap(const char *value, int nparams, char final) 3012 { 3013 bool result = FALSE; 3014 if (VALID_STRING(value) && csi_length(value) > 0) { 3015 char *p_is_s[NUM_PARM]; 3016 int popcount; 3017 int analyzed = _nc_tparm_analyze(NULL, value, p_is_s, &popcount); 3018 if (analyzed < popcount) { 3019 analyzed = popcount; 3020 } 3021 if (analyzed == nparams) { 3022 bool numbers = TRUE; 3023 int p; 3024 for (p = 0; p < nparams; ++p) { 3025 if (p_is_s[p]) { 3026 numbers = FALSE; 3027 break; 3028 } 3029 } 3030 if (numbers) { 3031 int in_delay = 0; 3032 p = (int) strlen(value); 3033 while (p-- > 0) { 3034 char ch = value[p]; 3035 if (ch == final) { 3036 result = TRUE; 3037 break; 3038 } 3039 switch (in_delay) { 3040 case 0: 3041 if (ch == '>') 3042 in_delay = 1; 3043 break; 3044 case 1: 3045 if (strchr(IN_DELAY, value[p]) != NULL) 3046 break; 3047 if (ch != '<') 3048 p = 0; 3049 in_delay = 2; 3050 break; 3051 case 2: 3052 if (ch != '$') 3053 p = 0; 3054 in_delay = 0; 3055 break; 3056 } 3057 } 3058 } 3059 } 3060 } 3061 return result; 3062 } 3063 3064 static const char * 3065 skip_Delay(const char *value) 3066 { 3067 const char *result = value; 3068 3069 if (*value == '$') { 3070 ++result; 3071 if (*result++ == '<') { 3072 while (strchr(IN_DELAY, *result) != NULL) 3073 ++result; 3074 if (*result++ != '>') { 3075 result = value; 3076 } 3077 } else { 3078 result = value; 3079 } 3080 } 3081 return result; 3082 } 3083 3084 static bool 3085 isValidString(const char *value, const char *expect) 3086 { 3087 bool result = FALSE; 3088 if (VALID_STRING(value)) { 3089 if (!strcmp(value, expect)) 3090 result = TRUE; 3091 } 3092 return result; 3093 } 3094 3095 static bool 3096 isValidEscape(const char *value, const char *expect) 3097 { 3098 bool result = FALSE; 3099 if (VALID_STRING(value)) { 3100 if (*value == '\033') { 3101 size_t need = strlen(expect); 3102 size_t have = strlen(value) - 1; 3103 if (have >= need && !strncmp(value + 1, expect, need)) { 3104 if (*skip_Delay(value + need + 1) == '\0') { 3105 result = TRUE; 3106 } 3107 } 3108 } 3109 } 3110 return result; 3111 } 3112 3113 static int 3114 guess_ANSI_VTxx(TERMTYPE2 *tp) 3115 { 3116 int result = -1; 3117 int checks = 0; 3118 3119 /* VT100s have scrolling region, but ANSI (ECMA-48) does not specify */ 3120 if (check_ANSI_cap(change_scroll_region, 2, 'r') && 3121 (isValidEscape(scroll_forward, "D") || 3122 isValidString(scroll_forward, "\n") || 3123 isValidEscape(scroll_forward, "6")) && 3124 (isValidEscape(scroll_reverse, "M") || 3125 isValidEscape(scroll_reverse, "9"))) { 3126 checks |= 2; 3127 } 3128 if (check_ANSI_cap(cursor_address, 2, 'H') && 3129 check_ANSI_cap(cursor_up, 0, 'A') && 3130 (check_ANSI_cap(cursor_down, 0, 'B') || 3131 isValidString(cursor_down, "\n")) && 3132 check_ANSI_cap(cursor_right, 0, 'C') && 3133 (check_ANSI_cap(cursor_left, 0, 'D') || 3134 isValidString(cursor_left, "\b")) && 3135 check_ANSI_cap(clr_eos, 0, 'J') && 3136 check_ANSI_cap(clr_bol, 0, 'K') && 3137 check_ANSI_cap(clr_eol, 0, 'K')) { 3138 checks |= 1; 3139 } 3140 if (checks == 3) 3141 result = 1; 3142 if (checks == 1) 3143 result = 0; 3144 return result; 3145 } 3146 3147 /* 3148 * u6/u7 and u8/u9 are query/response extensions which most terminals support. 3149 * In particular, any ECMA-48 terminal should support these, though the details 3150 * for u9 are implementation dependent. 3151 */ 3152 #if defined(user6) && defined(user7) && defined(user8) && defined(user9) 3153 static void 3154 check_user_6789(TERMTYPE2 *tp) 3155 { 3156 /* 3157 * Check if the terminal is known to not 3158 */ 3159 #define NO_QUERY(longname,shortname) \ 3160 if (PRESENT(longname)) _nc_warning(#shortname " is not supported") 3161 if (tigetflag("NQ") > 0) { 3162 NO_QUERY(user6, u6); 3163 NO_QUERY(user7, u7); 3164 NO_QUERY(user8, u8); 3165 NO_QUERY(user9, u9); 3166 return; 3167 } 3168 3169 PAIRED(user6, user7); 3170 PAIRED(user8, user9); 3171 3172 if (strchr(tp->term_names, '+') != NULL) 3173 return; 3174 3175 switch (guess_ANSI_VTxx(tp)) { 3176 case 1: 3177 if (!PRESENT(user8)) { 3178 _nc_warning("expected u8/u9 for device-attributes"); 3179 } 3180 /* FALLTHRU */ 3181 case 0: 3182 if (!PRESENT(user6)) { 3183 _nc_warning("expected u6/u7 for cursor-position"); 3184 } 3185 break; 3186 } 3187 } 3188 #else 3189 #define check_user_6789(tp) /* nothing */ 3190 #endif 3191 3192 /* other sanity-checks (things that we don't want in the normal 3193 * logic that reads a terminfo entry) 3194 */ 3195 static void 3196 check_termtype(TERMTYPE2 *tp, bool literal) 3197 { 3198 unsigned j; 3199 3200 check_conflict(tp); 3201 3202 for_each_string(j, tp) { 3203 char *a = tp->Strings[j]; 3204 if (VALID_STRING(a)) { 3205 const char *name = ExtStrname(tp, (int) j, strnames); 3206 /* 3207 * If we expect parameters, or if there might be parameters, 3208 * check for consistent number of parameters. 3209 */ 3210 if (j >= SIZEOF(parametrized) || 3211 is_user_capability(name) >= 0 || 3212 parametrized[j] > 0) { 3213 check_params(tp, name, a, (j >= STRCOUNT)); 3214 } 3215 check_delays(tp, ExtStrname(tp, (int) j, strnames), a); 3216 if (capdump) { 3217 check_infotocap(tp, (int) j, a); 3218 } 3219 } 3220 } 3221 #if NCURSES_XNAMES 3222 /* in extended mode, verify that each extension is expected type */ 3223 for_each_ext_boolean(j, tp) { 3224 check_user_capability_type(ExtBoolname(tp, (int) j, strnames), BOOLEAN); 3225 } 3226 for_each_ext_number(j, tp) { 3227 check_user_capability_type(ExtNumname(tp, (int) j, strnames), NUMBER); 3228 } 3229 for_each_ext_string(j, tp) { 3230 check_user_capability_type(ExtStrname(tp, (int) j, strnames), STRING); 3231 } 3232 #endif /* NCURSES_XNAMES */ 3233 3234 check_acs(tp); 3235 check_colors(tp); 3236 check_cursor(tp); 3237 check_keypad(tp); 3238 check_printer(tp); 3239 check_screen(tp); 3240 check_user_6789(tp); 3241 3242 /* 3243 * These are probably both or none. 3244 */ 3245 PAIRED(parm_index, parm_rindex); 3246 PAIRED(parm_ich, parm_dch); 3247 3248 /* 3249 * These may be mismatched because the terminal description relies on 3250 * restoring the cursor visibility by resetting it. 3251 */ 3252 ANDMISSING(cursor_invisible, cursor_normal); 3253 ANDMISSING(cursor_visible, cursor_normal); 3254 3255 if (PRESENT(cursor_visible) && PRESENT(cursor_normal) 3256 && !_nc_capcmp(cursor_visible, cursor_normal)) 3257 _nc_warning("cursor_visible is same as cursor_normal"); 3258 3259 /* 3260 * From XSI & O'Reilly, we gather that sc/rc are required if csr is 3261 * given, because the cursor position after the scrolling operation is 3262 * performed is undefined. 3263 */ 3264 ANDMISSING(change_scroll_region, save_cursor); 3265 ANDMISSING(change_scroll_region, restore_cursor); 3266 3267 /* 3268 * If we can clear tabs, we should be able to initialize them. 3269 */ 3270 ANDMISSING(clear_all_tabs, set_tab); 3271 3272 if (PRESENT(set_attributes)) { 3273 char *zero = 0; 3274 3275 _nc_tparm_err = 0; 3276 if (PRESENT(exit_attribute_mode)) { 3277 zero = strdup(CHECK_SGR(0, exit_attribute_mode)); 3278 } else { 3279 zero = strdup(TIPARM_9(set_attributes, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 3280 } 3281 check_tparm_err(0); 3282 3283 if (zero != 0) { 3284 CHECK_SGR(1, enter_standout_mode); 3285 CHECK_SGR(2, enter_underline_mode); 3286 CHECK_SGR(3, enter_reverse_mode); 3287 CHECK_SGR(4, enter_blink_mode); 3288 CHECK_SGR(5, enter_dim_mode); 3289 CHECK_SGR(6, enter_bold_mode); 3290 CHECK_SGR(7, enter_secure_mode); 3291 CHECK_SGR(8, enter_protected_mode); 3292 CHECK_SGR(9, enter_alt_charset_mode); 3293 free(zero); 3294 } else { 3295 _nc_warning("sgr(0) did not return a value"); 3296 } 3297 } else if (PRESENT(exit_attribute_mode) && 3298 set_attributes != CANCELLED_STRING) { 3299 if (_nc_syntax == SYN_TERMINFO) 3300 _nc_warning("missing sgr string"); 3301 } 3302 #define CHECK_SGR0(name) check_exit_attribute(#name, name, check_sgr0, exit_attribute_mode) 3303 if (PRESENT(exit_attribute_mode)) { 3304 char *check_sgr0 = _nc_trim_sgr0(tp); 3305 3306 if (check_sgr0 == NULL || *check_sgr0 == '\0') { 3307 _nc_warning("trimmed sgr0 is empty"); 3308 } else { 3309 show_where(2); 3310 if (check_sgr0 != exit_attribute_mode) { 3311 DEBUG(2, 3312 ("will trim sgr0\n\toriginal sgr0=%s\n\ttrimmed sgr0=%s", 3313 _nc_visbuf2(1, exit_attribute_mode), 3314 _nc_visbuf2(2, check_sgr0))); 3315 } else { 3316 DEBUG(2, 3317 ("will not trim sgr0\n\toriginal sgr0=%s", 3318 _nc_visbuf(exit_attribute_mode))); 3319 } 3320 } 3321 #if defined(exit_italics_mode) 3322 CHECK_SGR0(exit_italics_mode); 3323 #endif 3324 CHECK_SGR0(exit_standout_mode); 3325 CHECK_SGR0(exit_underline_mode); 3326 if (check_sgr0 != exit_attribute_mode) { 3327 free(check_sgr0); 3328 } 3329 } 3330 #define CHECK_SGR_PARAM(code, name) check_sgr_param(tp, (int)code, #name, name) 3331 for (j = 0; *sgr_names[j] != '\0'; ++j) { 3332 CHECK_SGR_PARAM(j, set_a_foreground); 3333 CHECK_SGR_PARAM(j, set_a_background); 3334 CHECK_SGR_PARAM(j, set_foreground); 3335 CHECK_SGR_PARAM(j, set_background); 3336 } 3337 #ifdef TRACE 3338 show_where(2); 3339 if (!auto_right_margin) { 3340 DEBUG(2, 3341 ("can write to lower-right directly")); 3342 } else if (PRESENT(enter_am_mode) && PRESENT(exit_am_mode)) { 3343 DEBUG(2, 3344 ("can write to lower-right by suppressing automargin")); 3345 } else if ((PRESENT(enter_insert_mode) && PRESENT(exit_insert_mode)) 3346 || PRESENT(insert_character) || PRESENT(parm_ich)) { 3347 DEBUG(2, 3348 ("can write to lower-right by using inserts")); 3349 } else { 3350 DEBUG(2, 3351 ("cannot write to lower-right")); 3352 } 3353 #endif 3354 3355 /* 3356 * Some standard applications (e.g., vi) and some non-curses 3357 * applications (e.g., jove) get confused if we have both ich1 and 3358 * smir/rmir. Let's be nice and warn about that, too, even though 3359 * ncurses handles it. 3360 */ 3361 if ((PRESENT(enter_insert_mode) || PRESENT(exit_insert_mode)) 3362 && PRESENT(insert_character)) { 3363 _nc_warning("non-curses applications may be confused by ich1 with smir/rmir"); 3364 } 3365 3366 /* 3367 * Finally, do the non-verbose checks 3368 */ 3369 if (save_check_termtype != 0) 3370 save_check_termtype(tp, literal); 3371 } 3372