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