1 /**************************************************************************** 2 * Copyright 2020 Thomas E. Dickey * 3 * Copyright 1998-2016,2017 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 * infocmp.c -- decompile an entry, or compare two entries 38 * written by Eric S. Raymond 39 * and Thomas E Dickey 40 */ 41 42 #include <progs.priv.h> 43 44 #include <dump_entry.h> 45 46 MODULE_ID("$Id: infocmp.c,v 1.144 2020/02/02 23:34:34 tom Exp $") 47 48 #define MAX_STRING 1024 /* maximum formatted string */ 49 50 const char *_nc_progname = "infocmp"; 51 52 typedef char path[PATH_MAX]; 53 54 /*************************************************************************** 55 * 56 * The following control variables, together with the contents of the 57 * terminfo entries, completely determine the actions of the program. 58 * 59 ***************************************************************************/ 60 61 static ENTRY *entries; /* terminfo entries */ 62 static int termcount; /* count of terminal entries */ 63 64 static bool limited = TRUE; /* "-r" option is not set */ 65 static bool quiet = FALSE; 66 static bool literal = FALSE; 67 static const char *bool_sep = ":"; 68 static const char *s_absent = "NULL"; 69 static const char *s_cancel = "NULL"; 70 static const char *tversion; /* terminfo version selected */ 71 static unsigned itrace; /* trace flag for debugging */ 72 static int mwidth = 60; 73 static int mheight = 65535; 74 static int numbers = 0; /* format "%'char'" to/from "%{number}" */ 75 static int outform = F_TERMINFO; /* output format */ 76 static int sortmode; /* sort_mode */ 77 78 /* main comparison mode */ 79 static int compare; 80 #define C_DEFAULT 0 /* don't force comparison mode */ 81 #define C_DIFFERENCE 1 /* list differences between two terminals */ 82 #define C_COMMON 2 /* list common capabilities */ 83 #define C_NAND 3 /* list capabilities in neither terminal */ 84 #define C_USEALL 4 /* generate relative use-form entry */ 85 static bool ignorepads; /* ignore pad prefixes when diffing */ 86 87 #if NO_LEAKS 88 89 typedef struct { 90 ENTRY *head; 91 ENTRY *tail; 92 } ENTERED; 93 94 static ENTERED *entered; 95 96 #undef ExitProgram 97 static void ExitProgram(int code) GCC_NORETURN; 98 /* prototype is to get gcc to accept the noreturn attribute */ 99 static void 100 ExitProgram(int code) 101 { 102 int n; 103 104 for (n = 0; n < termcount; ++n) { 105 ENTRY *new_head = _nc_head; 106 ENTRY *new_tail = _nc_tail; 107 _nc_head = entered[n].head; 108 _nc_tail = entered[n].tail; 109 _nc_free_entries(entered[n].head); 110 _nc_head = new_head; 111 _nc_tail = new_tail; 112 } 113 _nc_leaks_dump_entry(); 114 free(entries); 115 free(entered); 116 _nc_free_tic(code); 117 } 118 #endif 119 120 static void 121 failed(const char *s) 122 { 123 perror(s); 124 ExitProgram(EXIT_FAILURE); 125 } 126 127 static char * 128 canonical_name(char *ptr, char *buf) 129 /* extract the terminal type's primary name */ 130 { 131 char *bp; 132 133 _nc_STRCPY(buf, ptr, NAMESIZE); 134 if ((bp = strchr(buf, '|')) != 0) 135 *bp = '\0'; 136 137 return (buf); 138 } 139 140 static bool 141 no_boolean(int value) 142 { 143 bool result = (value == ABSENT_BOOLEAN); 144 if (!strcmp(s_absent, s_cancel)) 145 result = !VALID_BOOLEAN(value); 146 return result; 147 } 148 149 static bool 150 no_numeric(int value) 151 { 152 bool result = (value == ABSENT_NUMERIC); 153 if (!strcmp(s_absent, s_cancel)) 154 result = !VALID_NUMERIC(value); 155 return result; 156 } 157 158 static bool 159 no_string(char *value) 160 { 161 bool result = (value == ABSENT_STRING); 162 if (!strcmp(s_absent, s_cancel)) 163 result = !VALID_STRING(value); 164 return result; 165 } 166 167 /*************************************************************************** 168 * 169 * Predicates for dump function 170 * 171 ***************************************************************************/ 172 173 static int 174 capcmp(PredIdx idx, const char *s, const char *t) 175 /* capability comparison function */ 176 { 177 if (!VALID_STRING(s) && !VALID_STRING(t)) 178 return (s != t); 179 else if (!VALID_STRING(s) || !VALID_STRING(t)) 180 return (1); 181 182 if ((idx == acs_chars_index) || !ignorepads) 183 return (strcmp(s, t)); 184 else 185 return (_nc_capcmp(s, t)); 186 } 187 188 static int 189 use_predicate(unsigned type, PredIdx idx) 190 /* predicate function to use for use decompilation */ 191 { 192 ENTRY *ep; 193 194 switch (type) { 195 case BOOLEAN: 196 { 197 int is_set = FALSE; 198 199 /* 200 * This assumes that multiple use entries are supposed 201 * to contribute the logical or of their boolean capabilities. 202 * This is true if we take the semantics of multiple uses to 203 * be 'each capability gets the first non-default value found 204 * in the sequence of use entries'. 205 * 206 * Note that cancelled or absent booleans are stored as FALSE, 207 * unlike numbers and strings, whose cancelled/absent state is 208 * recorded in the terminfo database. 209 */ 210 for (ep = &entries[1]; ep < entries + termcount; ep++) 211 if (ep->tterm.Booleans[idx] == TRUE) { 212 is_set = entries[0].tterm.Booleans[idx]; 213 break; 214 } 215 if (is_set != entries[0].tterm.Booleans[idx]) 216 return (!is_set); 217 else 218 return (FAIL); 219 } 220 221 case NUMBER: 222 { 223 int value = ABSENT_NUMERIC; 224 225 /* 226 * We take the semantics of multiple uses to be 'each 227 * capability gets the first non-default value found 228 * in the sequence of use entries'. 229 */ 230 for (ep = &entries[1]; ep < entries + termcount; ep++) 231 if (VALID_NUMERIC(ep->tterm.Numbers[idx])) { 232 value = ep->tterm.Numbers[idx]; 233 break; 234 } 235 236 if (value != entries[0].tterm.Numbers[idx]) 237 return (value != ABSENT_NUMERIC); 238 else 239 return (FAIL); 240 } 241 242 case STRING: 243 { 244 char *termstr, *usestr = ABSENT_STRING; 245 246 termstr = entries[0].tterm.Strings[idx]; 247 248 /* 249 * We take the semantics of multiple uses to be 'each 250 * capability gets the first non-default value found 251 * in the sequence of use entries'. 252 */ 253 for (ep = &entries[1]; ep < entries + termcount; ep++) 254 if (ep->tterm.Strings[idx]) { 255 usestr = ep->tterm.Strings[idx]; 256 break; 257 } 258 259 if (usestr == ABSENT_STRING && termstr == ABSENT_STRING) 260 return (FAIL); 261 else if (!usestr || !termstr || capcmp(idx, usestr, termstr)) 262 return (TRUE); 263 else 264 return (FAIL); 265 } 266 } 267 268 return (FALSE); /* pacify compiler */ 269 } 270 271 static bool 272 useeq(ENTRY * e1, ENTRY * e2) 273 /* are the use references in two entries equivalent? */ 274 { 275 unsigned i, j; 276 277 if (e1->nuses != e2->nuses) 278 return (FALSE); 279 280 /* Ugh...this is quadratic again */ 281 for (i = 0; i < e1->nuses; i++) { 282 bool foundmatch = FALSE; 283 284 /* search second entry for given use reference */ 285 for (j = 0; j < e2->nuses; j++) 286 if (!strcmp(e1->uses[i].name, e2->uses[j].name)) { 287 foundmatch = TRUE; 288 break; 289 } 290 291 if (!foundmatch) 292 return (FALSE); 293 } 294 295 return (TRUE); 296 } 297 298 static bool 299 entryeq(TERMTYPE2 *t1, TERMTYPE2 *t2) 300 /* are two entries equivalent? */ 301 { 302 unsigned i; 303 304 for (i = 0; i < NUM_BOOLEANS(t1); i++) 305 if (t1->Booleans[i] != t2->Booleans[i]) 306 return (FALSE); 307 308 for (i = 0; i < NUM_NUMBERS(t1); i++) 309 if (t1->Numbers[i] != t2->Numbers[i]) 310 return (FALSE); 311 312 for (i = 0; i < NUM_STRINGS(t1); i++) 313 if (capcmp((PredIdx) i, t1->Strings[i], t2->Strings[i])) 314 return (FALSE); 315 316 return (TRUE); 317 } 318 319 #define TIC_EXPAND(result) _nc_tic_expand(result, outform==F_TERMINFO, numbers) 320 321 static void 322 print_uses(ENTRY * ep, FILE *fp) 323 /* print an entry's use references */ 324 { 325 unsigned i; 326 327 if (!ep->nuses) 328 fputs("NULL", fp); 329 else 330 for (i = 0; i < ep->nuses; i++) { 331 fputs(ep->uses[i].name, fp); 332 if (i < ep->nuses - 1) 333 fputs(" ", fp); 334 } 335 } 336 337 static const char * 338 dump_boolean(int val) 339 /* display the value of a boolean capability */ 340 { 341 switch (val) { 342 case ABSENT_BOOLEAN: 343 return (s_absent); 344 case CANCELLED_BOOLEAN: 345 return (s_cancel); 346 case FALSE: 347 return ("F"); 348 case TRUE: 349 return ("T"); 350 default: 351 return ("?"); 352 } 353 } 354 355 static void 356 dump_numeric(int val, char *buf) 357 /* display the value of a numeric capability */ 358 { 359 switch (val) { 360 case ABSENT_NUMERIC: 361 _nc_STRCPY(buf, s_absent, MAX_STRING); 362 break; 363 case CANCELLED_NUMERIC: 364 _nc_STRCPY(buf, s_cancel, MAX_STRING); 365 break; 366 default: 367 _nc_SPRINTF(buf, _nc_SLIMIT(MAX_STRING) "%d", val); 368 break; 369 } 370 } 371 372 static void 373 dump_string(char *val, char *buf) 374 /* display the value of a string capability */ 375 { 376 if (val == ABSENT_STRING) 377 _nc_STRCPY(buf, s_absent, MAX_STRING); 378 else if (val == CANCELLED_STRING) 379 _nc_STRCPY(buf, s_cancel, MAX_STRING); 380 else { 381 _nc_SPRINTF(buf, _nc_SLIMIT(MAX_STRING) 382 "'%.*s'", MAX_STRING - 3, TIC_EXPAND(val)); 383 } 384 } 385 386 /* 387 * Show "comparing..." message for the given terminal names. 388 */ 389 static void 390 show_comparing(char **names) 391 { 392 if (itrace) { 393 switch (compare) { 394 case C_DIFFERENCE: 395 (void) fprintf(stderr, "%s: dumping differences\n", _nc_progname); 396 break; 397 398 case C_COMMON: 399 (void) fprintf(stderr, "%s: dumping common capabilities\n", _nc_progname); 400 break; 401 402 case C_NAND: 403 (void) fprintf(stderr, "%s: dumping differences\n", _nc_progname); 404 break; 405 } 406 } 407 if (*names) { 408 printf("comparing %s", *names++); 409 if (*names) { 410 printf(" to %s", *names++); 411 while (*names) { 412 printf(", %s", *names++); 413 } 414 } 415 printf(".\n"); 416 } 417 } 418 419 /* 420 * ncurses stores two types of non-standard capabilities: 421 * a) capabilities listed past the "STOP-HERE" comment in the Caps file. 422 * These are used in the terminfo source file to provide data for termcaps, 423 * e.g., when there is no equivalent capability in terminfo, as well as for 424 * widely-used non-standard capabilities. 425 * b) user-definable capabilities, via "tic -x". 426 * 427 * However, if "-x" is omitted from the tic command, both types of 428 * non-standard capability are not loaded into the terminfo database. This 429 * macro is used for limit-checks against the symbols that tic uses to omit 430 * the two types of non-standard entry. 431 */ 432 #if NCURSES_XNAMES 433 #define check_user_definable(n,limit) if (!_nc_user_definable && (n) > (limit)) break 434 #else 435 #define check_user_definable(n,limit) if ((n) > (limit)) break 436 #endif 437 438 /* 439 * Use these macros to simplify loops on C_COMMON and C_NAND: 440 */ 441 #define for_each_entry() while (entries[extra].tterm.term_names) 442 #define next_entry (&(entries[extra++].tterm)) 443 444 static void 445 compare_predicate(PredType type, PredIdx idx, const char *name) 446 /* predicate function to use for entry difference reports */ 447 { 448 ENTRY *e1 = &entries[0]; 449 ENTRY *e2 = &entries[1]; 450 char buf1[MAX_STRING]; 451 char buf2[MAX_STRING]; 452 int b1, b2; 453 int n1, n2; 454 char *s1, *s2; 455 bool found; 456 int extra = 1; 457 458 switch (type) { 459 case CMP_BOOLEAN: 460 check_user_definable(idx, BOOLWRITE); 461 b1 = e1->tterm.Booleans[idx]; 462 switch (compare) { 463 case C_DIFFERENCE: 464 b2 = next_entry->Booleans[idx]; 465 if (!(no_boolean(b1) && no_boolean(b2)) && (b1 != b2)) 466 (void) printf("\t%s: %s%s%s.\n", 467 name, 468 dump_boolean(b1), 469 bool_sep, 470 dump_boolean(b2)); 471 break; 472 473 case C_COMMON: 474 if (b1 != ABSENT_BOOLEAN) { 475 found = TRUE; 476 for_each_entry() { 477 b2 = next_entry->Booleans[idx]; 478 if (b1 != b2) { 479 found = FALSE; 480 break; 481 } 482 } 483 if (found) { 484 (void) printf("\t%s= %s.\n", name, dump_boolean(b1)); 485 } 486 } 487 break; 488 489 case C_NAND: 490 if (b1 == ABSENT_BOOLEAN) { 491 found = TRUE; 492 for_each_entry() { 493 b2 = next_entry->Booleans[idx]; 494 if (b1 != b2) { 495 found = FALSE; 496 break; 497 } 498 } 499 if (found) { 500 (void) printf("\t!%s.\n", name); 501 } 502 } 503 break; 504 } 505 break; 506 507 case CMP_NUMBER: 508 check_user_definable(idx, NUMWRITE); 509 n1 = e1->tterm.Numbers[idx]; 510 switch (compare) { 511 case C_DIFFERENCE: 512 n2 = next_entry->Numbers[idx]; 513 if (!(no_numeric(n1) && no_numeric(n2)) && n1 != n2) { 514 dump_numeric(n1, buf1); 515 dump_numeric(n2, buf2); 516 (void) printf("\t%s: %s, %s.\n", name, buf1, buf2); 517 } 518 break; 519 520 case C_COMMON: 521 if (n1 != ABSENT_NUMERIC) { 522 found = TRUE; 523 for_each_entry() { 524 n2 = next_entry->Numbers[idx]; 525 if (n1 != n2) { 526 found = FALSE; 527 break; 528 } 529 } 530 if (found) { 531 dump_numeric(n1, buf1); 532 (void) printf("\t%s= %s.\n", name, buf1); 533 } 534 } 535 break; 536 537 case C_NAND: 538 if (n1 == ABSENT_NUMERIC) { 539 found = TRUE; 540 for_each_entry() { 541 n2 = next_entry->Numbers[idx]; 542 if (n1 != n2) { 543 found = FALSE; 544 break; 545 } 546 } 547 if (found) { 548 (void) printf("\t!%s.\n", name); 549 } 550 } 551 break; 552 } 553 break; 554 555 case CMP_STRING: 556 check_user_definable(idx, STRWRITE); 557 s1 = e1->tterm.Strings[idx]; 558 switch (compare) { 559 case C_DIFFERENCE: 560 s2 = next_entry->Strings[idx]; 561 if (!(no_string(s1) && no_string(s2)) && capcmp(idx, s1, s2)) { 562 dump_string(s1, buf1); 563 dump_string(s2, buf2); 564 if (strcmp(buf1, buf2)) 565 (void) printf("\t%s: %s, %s.\n", name, buf1, buf2); 566 } 567 break; 568 569 case C_COMMON: 570 if (s1 != ABSENT_STRING) { 571 found = TRUE; 572 for_each_entry() { 573 s2 = next_entry->Strings[idx]; 574 if (capcmp(idx, s1, s2) != 0) { 575 found = FALSE; 576 break; 577 } 578 } 579 if (found) { 580 (void) printf("\t%s= '%s'.\n", name, TIC_EXPAND(s1)); 581 } 582 } 583 break; 584 585 case C_NAND: 586 if (s1 == ABSENT_STRING) { 587 found = TRUE; 588 for_each_entry() { 589 s2 = next_entry->Strings[idx]; 590 if (s2 != s1) { 591 found = FALSE; 592 break; 593 } 594 } 595 if (found) { 596 (void) printf("\t!%s.\n", name); 597 } 598 } 599 break; 600 } 601 break; 602 603 case CMP_USE: 604 /* unlike the other modes, this compares *all* use entries */ 605 switch (compare) { 606 case C_DIFFERENCE: 607 if (!useeq(e1, e2)) { 608 (void) fputs("\tuse: ", stdout); 609 print_uses(e1, stdout); 610 fputs(", ", stdout); 611 print_uses(e2, stdout); 612 fputs(".\n", stdout); 613 } 614 break; 615 616 case C_COMMON: 617 if (e1->nuses) { 618 found = TRUE; 619 for_each_entry() { 620 e2 = &entries[extra++]; 621 if (e2->nuses != e1->nuses || !useeq(e1, e2)) { 622 found = FALSE; 623 break; 624 } 625 } 626 if (found) { 627 (void) fputs("\tuse: ", stdout); 628 print_uses(e1, stdout); 629 fputs(".\n", stdout); 630 } 631 } 632 break; 633 634 case C_NAND: 635 if (!e1->nuses) { 636 found = TRUE; 637 for_each_entry() { 638 e2 = &entries[extra++]; 639 if (e2->nuses != e1->nuses) { 640 found = FALSE; 641 break; 642 } 643 } 644 if (found) { 645 (void) printf("\t!use.\n"); 646 } 647 } 648 break; 649 } 650 } 651 } 652 653 /*************************************************************************** 654 * 655 * Init string analysis 656 * 657 ***************************************************************************/ 658 659 #define DATA(from, to) { { from }, { to } } 660 #define DATAX() DATA("", "") 661 662 typedef struct { 663 const char from[4]; 664 const char to[12]; 665 } assoc; 666 667 static const assoc std_caps[] = 668 { 669 /* these are specified by X.364 and iBCS2 */ 670 DATA("\033c", "RIS"), /* full reset */ 671 DATA("\0337", "SC"), /* save cursor */ 672 DATA("\0338", "RC"), /* restore cursor */ 673 DATA("\033[r", "RSR"), /* not an X.364 mnemonic */ 674 DATA("\033[m", "SGR0"), /* not an X.364 mnemonic */ 675 DATA("\033[2J", "ED2"), /* clear page */ 676 677 /* this group is specified by ISO 2022 */ 678 DATA("\033(0", "ISO DEC G0"), /* enable DEC graphics for G0 */ 679 DATA("\033(A", "ISO UK G0"), /* enable UK chars for G0 */ 680 DATA("\033(B", "ISO US G0"), /* enable US chars for G0 */ 681 DATA("\033)0", "ISO DEC G1"), /* enable DEC graphics for G1 */ 682 DATA("\033)A", "ISO UK G1"), /* enable UK chars for G1 */ 683 DATA("\033)B", "ISO US G1"), /* enable US chars for G1 */ 684 685 /* these are DEC private controls widely supported by emulators */ 686 DATA("\033=", "DECPAM"), /* application keypad mode */ 687 DATA("\033>", "DECPNM"), /* normal keypad mode */ 688 DATA("\033<", "DECANSI"), /* enter ANSI mode */ 689 DATA("\033[!p", "DECSTR"), /* soft reset */ 690 DATA("\033 F", "S7C1T"), /* 7-bit controls */ 691 692 DATAX() 693 }; 694 695 static const assoc std_modes[] = 696 /* ECMA \E[ ... [hl] modes recognized by many emulators */ 697 { 698 DATA("2", "AM"), /* keyboard action mode */ 699 DATA("4", "IRM"), /* insert/replace mode */ 700 DATA("12", "SRM"), /* send/receive mode */ 701 DATA("20", "LNM"), /* linefeed mode */ 702 DATAX() 703 }; 704 705 static const assoc private_modes[] = 706 /* DEC \E[ ... [hl] modes recognized by many emulators */ 707 { 708 DATA("1", "CKM"), /* application cursor keys */ 709 DATA("2", "ANM"), /* set VT52 mode */ 710 DATA("3", "COLM"), /* 132-column mode */ 711 DATA("4", "SCLM"), /* smooth scroll */ 712 DATA("5", "SCNM"), /* reverse video mode */ 713 DATA("6", "OM"), /* origin mode */ 714 DATA("7", "AWM"), /* wraparound mode */ 715 DATA("8", "ARM"), /* auto-repeat mode */ 716 DATAX() 717 }; 718 719 static const assoc ecma_highlights[] = 720 /* recognize ECMA attribute sequences */ 721 { 722 DATA("0", "NORMAL"), /* normal */ 723 DATA("1", "+BOLD"), /* bold on */ 724 DATA("2", "+DIM"), /* dim on */ 725 DATA("3", "+ITALIC"), /* italic on */ 726 DATA("4", "+UNDERLINE"), /* underline on */ 727 DATA("5", "+BLINK"), /* blink on */ 728 DATA("6", "+FASTBLINK"), /* fastblink on */ 729 DATA("7", "+REVERSE"), /* reverse on */ 730 DATA("8", "+INVISIBLE"), /* invisible on */ 731 DATA("9", "+DELETED"), /* deleted on */ 732 DATA("10", "MAIN-FONT"), /* select primary font */ 733 DATA("11", "ALT-FONT-1"), /* select alternate font 1 */ 734 DATA("12", "ALT-FONT-2"), /* select alternate font 2 */ 735 DATA("13", "ALT-FONT-3"), /* select alternate font 3 */ 736 DATA("14", "ALT-FONT-4"), /* select alternate font 4 */ 737 DATA("15", "ALT-FONT-5"), /* select alternate font 5 */ 738 DATA("16", "ALT-FONT-6"), /* select alternate font 6 */ 739 DATA("17", "ALT-FONT-7"), /* select alternate font 7 */ 740 DATA("18", "ALT-FONT-1"), /* select alternate font 1 */ 741 DATA("19", "ALT-FONT-1"), /* select alternate font 1 */ 742 DATA("20", "FRAKTUR"), /* Fraktur font */ 743 DATA("21", "DOUBLEUNDER"), /* double underline */ 744 DATA("22", "-DIM"), /* dim off */ 745 DATA("23", "-ITALIC"), /* italic off */ 746 DATA("24", "-UNDERLINE"), /* underline off */ 747 DATA("25", "-BLINK"), /* blink off */ 748 DATA("26", "-FASTBLINK"), /* fastblink off */ 749 DATA("27", "-REVERSE"), /* reverse off */ 750 DATA("28", "-INVISIBLE"), /* invisible off */ 751 DATA("29", "-DELETED"), /* deleted off */ 752 DATAX() 753 }; 754 755 #undef DATA 756 757 static int 758 skip_csi(const char *cap) 759 { 760 int result = 0; 761 if (cap[0] == '\033' && cap[1] == '[') 762 result = 2; 763 else if (UChar(cap[0]) == 0233) 764 result = 1; 765 return result; 766 } 767 768 static bool 769 same_param(const char *table, const char *param, size_t length) 770 { 771 bool result = FALSE; 772 if (strncmp(table, param, length) == 0) { 773 result = !isdigit(UChar(param[length])); 774 } 775 return result; 776 } 777 778 static char * 779 lookup_params(const assoc * table, char *dst, char *src) 780 { 781 char *result = 0; 782 const char *ep = strtok(src, ";"); 783 784 if (ep != 0) { 785 const assoc *ap; 786 787 do { 788 bool found = FALSE; 789 790 for (ap = table; ap->from[0]; ap++) { 791 size_t tlen = strlen(ap->from); 792 793 if (same_param(ap->from, ep, tlen)) { 794 _nc_STRCAT(dst, ap->to, MAX_TERMINFO_LENGTH); 795 found = TRUE; 796 break; 797 } 798 } 799 800 if (!found) 801 _nc_STRCAT(dst, ep, MAX_TERMINFO_LENGTH); 802 _nc_STRCAT(dst, ";", MAX_TERMINFO_LENGTH); 803 } while 804 ((ep = strtok((char *) 0, ";"))); 805 806 dst[strlen(dst) - 1] = '\0'; 807 808 result = dst; 809 } 810 return result; 811 } 812 813 static void 814 analyze_string(const char *name, const char *cap, TERMTYPE2 *tp) 815 { 816 char buf2[MAX_TERMINFO_LENGTH]; 817 const char *sp; 818 const assoc *ap; 819 int tp_lines = tp->Numbers[2]; 820 821 if (!VALID_STRING(cap)) 822 return; 823 (void) printf("%s: ", name); 824 825 for (sp = cap; *sp; sp++) { 826 int i; 827 int csi; 828 size_t len = 0; 829 size_t next; 830 const char *expansion = 0; 831 char buf3[MAX_TERMINFO_LENGTH]; 832 833 /* first, check other capabilities in this entry */ 834 for (i = 0; i < STRCOUNT; i++) { 835 char *cp = tp->Strings[i]; 836 837 /* don't use function-key capabilities */ 838 if (strnames[i][0] == 'k' && strnames[i][1] == 'f') 839 continue; 840 841 if (VALID_STRING(cp) && 842 cp[0] != '\0' && 843 cp != cap) { 844 len = strlen(cp); 845 _nc_STRNCPY(buf2, sp, len); 846 buf2[len] = '\0'; 847 848 if (_nc_capcmp(cp, buf2)) 849 continue; 850 851 #define ISRS(s) (!strncmp((s), "is", (size_t) 2) || !strncmp((s), "rs", (size_t) 2)) 852 /* 853 * Theoretically we just passed the test for translation 854 * (equality once the padding is stripped). However, there 855 * are a few more hoops that need to be jumped so that 856 * identical pairs of initialization and reset strings 857 * don't just refer to each other. 858 */ 859 if (ISRS(name) || ISRS(strnames[i])) 860 if (cap < cp) 861 continue; 862 #undef ISRS 863 864 expansion = strnames[i]; 865 break; 866 } 867 } 868 869 /* now check the standard capabilities */ 870 if (!expansion) { 871 csi = skip_csi(sp); 872 for (ap = std_caps; ap->from[0]; ap++) { 873 size_t adj = (size_t) (csi ? 2 : 0); 874 875 len = strlen(ap->from); 876 if (csi && skip_csi(ap->from) != csi) 877 continue; 878 if (len > adj 879 && strncmp(ap->from + adj, sp + csi, len - adj) == 0) { 880 expansion = ap->to; 881 len -= adj; 882 len += (size_t) csi; 883 break; 884 } 885 } 886 } 887 888 /* now check for standard-mode sequences */ 889 if (!expansion 890 && (csi = skip_csi(sp)) != 0 891 && (len = (strspn) (sp + csi, "0123456789;")) 892 && (len < sizeof(buf3)) 893 && (next = (size_t) csi + len) 894 && ((sp[next] == 'h') || (sp[next] == 'l'))) { 895 896 _nc_STRCPY(buf2, 897 ((sp[next] == 'h') 898 ? "ECMA+" 899 : "ECMA-"), 900 sizeof(buf2)); 901 _nc_STRNCPY(buf3, sp + csi, len); 902 buf3[len] = '\0'; 903 len += (size_t) csi + 1; 904 905 expansion = lookup_params(std_modes, buf2, buf3); 906 } 907 908 /* now check for private-mode sequences */ 909 if (!expansion 910 && (csi = skip_csi(sp)) != 0 911 && sp[csi] == '?' 912 && (len = (strspn) (sp + csi + 1, "0123456789;")) 913 && (len < sizeof(buf3)) 914 && (next = (size_t) csi + 1 + len) 915 && ((sp[next] == 'h') || (sp[next] == 'l'))) { 916 917 _nc_STRCPY(buf2, 918 ((sp[next] == 'h') 919 ? "DEC+" 920 : "DEC-"), 921 sizeof(buf2)); 922 _nc_STRNCPY(buf3, sp + csi + 1, len); 923 buf3[len] = '\0'; 924 len += (size_t) csi + 2; 925 926 expansion = lookup_params(private_modes, buf2, buf3); 927 } 928 929 /* now check for ECMA highlight sequences */ 930 if (!expansion 931 && (csi = skip_csi(sp)) != 0 932 && (len = (strspn) (sp + csi, "0123456789;")) != 0 933 && (len < sizeof(buf3)) 934 && (next = (size_t) csi + len) 935 && sp[next] == 'm') { 936 937 _nc_STRCPY(buf2, "SGR:", sizeof(buf2)); 938 _nc_STRNCPY(buf3, sp + csi, len); 939 buf3[len] = '\0'; 940 len += (size_t) csi + 1; 941 942 expansion = lookup_params(ecma_highlights, buf2, buf3); 943 } 944 945 if (!expansion 946 && (csi = skip_csi(sp)) != 0 947 && sp[csi] == 'm') { 948 len = (size_t) csi + 1; 949 _nc_STRCPY(buf2, "SGR:", sizeof(buf2)); 950 _nc_STRCAT(buf2, ecma_highlights[0].to, sizeof(buf2)); 951 expansion = buf2; 952 } 953 954 /* now check for scroll region reset */ 955 if (!expansion 956 && (csi = skip_csi(sp)) != 0) { 957 if (sp[csi] == 'r') { 958 expansion = "RSR"; 959 len = 1; 960 } else { 961 _nc_SPRINTF(buf2, _nc_SLIMIT(sizeof(buf2)) "1;%dr", tp_lines); 962 len = strlen(buf2); 963 if (strncmp(buf2, sp + csi, len) == 0) 964 expansion = "RSR"; 965 } 966 len += (size_t) csi; 967 } 968 969 /* now check for home-down */ 970 if (!expansion 971 && (csi = skip_csi(sp)) != 0) { 972 _nc_SPRINTF(buf2, _nc_SLIMIT(sizeof(buf2)) "%d;1H", tp_lines); 973 len = strlen(buf2); 974 if (strncmp(buf2, sp + csi, len) == 0) { 975 expansion = "LL"; 976 } else { 977 _nc_SPRINTF(buf2, _nc_SLIMIT(sizeof(buf2)) "%dH", tp_lines); 978 len = strlen(buf2); 979 if (strncmp(buf2, sp + csi, len) == 0) { 980 expansion = "LL"; 981 } 982 } 983 len += (size_t) csi; 984 } 985 986 /* now look at the expansion we got, if any */ 987 if (expansion) { 988 printf("{%s}", expansion); 989 sp += len - 1; 990 } else { 991 /* couldn't match anything */ 992 buf2[0] = *sp; 993 buf2[1] = '\0'; 994 fputs(TIC_EXPAND(buf2), stdout); 995 } 996 } 997 putchar('\n'); 998 } 999 1000 /*************************************************************************** 1001 * 1002 * File comparison 1003 * 1004 ***************************************************************************/ 1005 1006 static void 1007 file_comparison(int argc, char *argv[]) 1008 { 1009 #define MAXCOMPARE 2 1010 /* someday we may allow comparisons on more files */ 1011 int filecount = 0; 1012 ENTRY *heads[MAXCOMPARE]; 1013 ENTRY *qp, *rp; 1014 int i, n; 1015 1016 memset(heads, 0, sizeof(heads)); 1017 dump_init((char *) 0, F_LITERAL, S_TERMINFO, 1018 FALSE, 0, 65535, itrace, FALSE, FALSE, FALSE); 1019 1020 for (n = 0; n < argc && n < MAXCOMPARE; n++) { 1021 if (freopen(argv[n], "r", stdin) == 0) 1022 _nc_err_abort("Can't open %s", argv[n]); 1023 1024 #if NO_LEAKS 1025 entered[n].head = _nc_head; 1026 entered[n].tail = _nc_tail; 1027 #endif 1028 _nc_head = _nc_tail = 0; 1029 1030 /* parse entries out of the source file */ 1031 _nc_set_source(argv[n]); 1032 _nc_read_entry_source(stdin, NULL, TRUE, literal, NULLHOOK); 1033 1034 if (itrace) 1035 (void) fprintf(stderr, "Resolving file %d...\n", n - 0); 1036 1037 /* maybe do use resolution */ 1038 if (!_nc_resolve_uses2(!limited, literal)) { 1039 (void) fprintf(stderr, 1040 "There are unresolved use entries in %s:\n", 1041 argv[n]); 1042 for_entry_list(qp) { 1043 if (qp->nuses) { 1044 (void) fputs(qp->tterm.term_names, stderr); 1045 (void) fputc('\n', stderr); 1046 } 1047 } 1048 ExitProgram(EXIT_FAILURE); 1049 } 1050 1051 heads[filecount] = _nc_head; 1052 filecount++; 1053 } 1054 1055 /* OK, all entries are in core. Ready to do the comparison */ 1056 if (itrace) 1057 (void) fprintf(stderr, "Entries are now in core...\n"); 1058 1059 /* The entry-matching loop. Sigh, this is intrinsically quadratic. */ 1060 for (qp = heads[0]; qp; qp = qp->next) { 1061 for (rp = heads[1]; rp; rp = rp->next) 1062 if (_nc_entry_match(qp->tterm.term_names, rp->tterm.term_names)) { 1063 if (qp->ncrosslinks < MAX_CROSSLINKS) 1064 qp->crosslinks[qp->ncrosslinks] = rp; 1065 qp->ncrosslinks++; 1066 1067 if (rp->ncrosslinks < MAX_CROSSLINKS) 1068 rp->crosslinks[rp->ncrosslinks] = qp; 1069 rp->ncrosslinks++; 1070 } 1071 } 1072 1073 /* now we have two circular lists with crosslinks */ 1074 if (itrace) 1075 (void) fprintf(stderr, "Name matches are done...\n"); 1076 1077 for (qp = heads[0]; qp; qp = qp->next) { 1078 if (qp->ncrosslinks > 1) { 1079 (void) fprintf(stderr, 1080 "%s in file 1 (%s) has %d matches in file 2 (%s):\n", 1081 _nc_first_name(qp->tterm.term_names), 1082 argv[0], 1083 qp->ncrosslinks, 1084 argv[1]); 1085 for (i = 0; i < qp->ncrosslinks; i++) 1086 (void) fprintf(stderr, 1087 "\t%s\n", 1088 _nc_first_name((qp->crosslinks[i])->tterm.term_names)); 1089 } 1090 } 1091 1092 for (rp = heads[1]; rp; rp = rp->next) { 1093 if (rp->ncrosslinks > 1) { 1094 (void) fprintf(stderr, 1095 "%s in file 2 (%s) has %d matches in file 1 (%s):\n", 1096 _nc_first_name(rp->tterm.term_names), 1097 argv[1], 1098 rp->ncrosslinks, 1099 argv[0]); 1100 for (i = 0; i < rp->ncrosslinks; i++) 1101 (void) fprintf(stderr, 1102 "\t%s\n", 1103 _nc_first_name((rp->crosslinks[i])->tterm.term_names)); 1104 } 1105 } 1106 1107 (void) printf("In file 1 (%s) only:\n", argv[0]); 1108 for (qp = heads[0]; qp; qp = qp->next) 1109 if (qp->ncrosslinks == 0) 1110 (void) printf("\t%s\n", 1111 _nc_first_name(qp->tterm.term_names)); 1112 1113 (void) printf("In file 2 (%s) only:\n", argv[1]); 1114 for (rp = heads[1]; rp; rp = rp->next) 1115 if (rp->ncrosslinks == 0) 1116 (void) printf("\t%s\n", 1117 _nc_first_name(rp->tterm.term_names)); 1118 1119 (void) printf("The following entries are equivalent:\n"); 1120 for (qp = heads[0]; qp; qp = qp->next) { 1121 if (qp->ncrosslinks == 1) { 1122 rp = qp->crosslinks[0]; 1123 1124 repair_acsc(&qp->tterm); 1125 repair_acsc(&rp->tterm); 1126 #if NCURSES_XNAMES 1127 _nc_align_termtype(&qp->tterm, &rp->tterm); 1128 #endif 1129 if (entryeq(&qp->tterm, &rp->tterm) && useeq(qp, rp)) { 1130 char name1[NAMESIZE], name2[NAMESIZE]; 1131 1132 (void) canonical_name(qp->tterm.term_names, name1); 1133 (void) canonical_name(rp->tterm.term_names, name2); 1134 1135 (void) printf("%s = %s\n", name1, name2); 1136 } 1137 } 1138 } 1139 1140 (void) printf("Differing entries:\n"); 1141 termcount = 2; 1142 for (qp = heads[0]; qp; qp = qp->next) { 1143 1144 if (qp->ncrosslinks == 1) { 1145 rp = qp->crosslinks[0]; 1146 #if NCURSES_XNAMES 1147 /* sorry - we have to do this on each pass */ 1148 _nc_align_termtype(&qp->tterm, &rp->tterm); 1149 #endif 1150 if (!(entryeq(&qp->tterm, &rp->tterm) && useeq(qp, rp))) { 1151 char name1[NAMESIZE], name2[NAMESIZE]; 1152 char *names[3]; 1153 1154 names[0] = name1; 1155 names[1] = name2; 1156 names[2] = 0; 1157 1158 entries[0] = *qp; 1159 entries[1] = *rp; 1160 1161 (void) canonical_name(qp->tterm.term_names, name1); 1162 (void) canonical_name(rp->tterm.term_names, name2); 1163 1164 switch (compare) { 1165 case C_DIFFERENCE: 1166 show_comparing(names); 1167 compare_entry(compare_predicate, &entries->tterm, quiet); 1168 break; 1169 1170 case C_COMMON: 1171 show_comparing(names); 1172 compare_entry(compare_predicate, &entries->tterm, quiet); 1173 break; 1174 1175 case C_NAND: 1176 show_comparing(names); 1177 compare_entry(compare_predicate, &entries->tterm, quiet); 1178 break; 1179 1180 } 1181 } 1182 } 1183 } 1184 } 1185 1186 static void 1187 usage(void) 1188 { 1189 #define DATA(s) s "\n" 1190 static const char head[] = 1191 { 1192 DATA("Usage: infocmp [options] [-A directory] [-B directory] [termname...]") 1193 DATA("") 1194 DATA("Options:") 1195 }; 1196 #undef DATA 1197 #define DATA(s) s 1198 static const char options[][45] = 1199 { 1200 " -0 print single-row" 1201 ," -1 print single-column" 1202 ," -C use termcap-names" 1203 ," -D print database locations" 1204 ," -E format output as C tables" 1205 ," -F compare terminfo-files" 1206 ," -G format %{number} to %'char'" 1207 ," -I use terminfo-names" 1208 ," -K use termcap-names and BSD syntax" 1209 ," -L use long names" 1210 ," -R subset (see manpage)" 1211 ," -T eliminate size limits (test)" 1212 ," -U do not post-process entries" 1213 ," -V print version" 1214 ," -W wrap long strings per -w[n]" 1215 #if NCURSES_XNAMES 1216 ," -a with -F, list commented-out caps" 1217 #endif 1218 ," -c list common capabilities" 1219 ," -d list different capabilities" 1220 ," -e format output for C initializer" 1221 ," -f with -1, format complex strings" 1222 ," -g format %'char' to %{number}" 1223 ," -i analyze initialization/reset" 1224 ," -l output terminfo names" 1225 ," -n list capabilities in neither" 1226 ," -p ignore padding specifiers" 1227 ," -Q number dump compiled description" 1228 ," -q brief listing, removes headers" 1229 ," -r with -C, output in termcap form" 1230 ," -r with -F, resolve use-references" 1231 ," -s [d|i|l|c] sort fields" 1232 #if NCURSES_XNAMES 1233 ," -t suppress commented-out capabilities" 1234 #endif 1235 ," -u produce source with 'use='" 1236 ," -v number (verbose)" 1237 ," -w number (width)" 1238 #if NCURSES_XNAMES 1239 ," -x unknown capabilities are user-defined" 1240 #endif 1241 }; 1242 #undef DATA 1243 const size_t last = SIZEOF(options); 1244 const size_t left = (last + 1) / 2; 1245 size_t n; 1246 1247 fputs(head, stderr); 1248 for (n = 0; n < left; n++) { 1249 size_t m = n + left; 1250 if (m < last) 1251 fprintf(stderr, "%-40.40s%s\n", options[n], options[m]); 1252 else 1253 fprintf(stderr, "%s\n", options[n]); 1254 } 1255 ExitProgram(EXIT_FAILURE); 1256 } 1257 1258 static char * 1259 any_initializer(const char *fmt, const char *type) 1260 { 1261 static char *initializer; 1262 static size_t need; 1263 char *s; 1264 1265 if (initializer == 0) { 1266 need = (strlen(entries->tterm.term_names) 1267 + strlen(type) 1268 + strlen(fmt)); 1269 initializer = (char *) malloc(need + 1); 1270 if (initializer == 0) 1271 failed("any_initializer"); 1272 } 1273 1274 _nc_STRCPY(initializer, entries->tterm.term_names, need); 1275 for (s = initializer; *s != 0 && *s != '|'; s++) { 1276 if (!isalnum(UChar(*s))) 1277 *s = '_'; 1278 } 1279 *s = 0; 1280 _nc_SPRINTF(s, _nc_SLIMIT(need) fmt, type); 1281 return initializer; 1282 } 1283 1284 static char * 1285 name_initializer(const char *type) 1286 { 1287 return any_initializer("_%s_data", type); 1288 } 1289 1290 static char * 1291 string_variable(const char *type) 1292 { 1293 return any_initializer("_s_%s", type); 1294 } 1295 1296 /* dump C initializers for the terminal type */ 1297 static void 1298 dump_initializers(TERMTYPE2 *term) 1299 { 1300 unsigned n; 1301 const char *str = 0; 1302 1303 printf("\nstatic char %s[] = \"%s\";\n\n", 1304 name_initializer("alias"), entries->tterm.term_names); 1305 1306 for_each_string(n, term) { 1307 char buf[MAX_STRING], *sp, *tp; 1308 1309 if (VALID_STRING(term->Strings[n])) { 1310 tp = buf; 1311 #define TP_LIMIT ((MAX_STRING - 5) - (size_t)(tp - buf)) 1312 *tp++ = '"'; 1313 for (sp = term->Strings[n]; 1314 *sp != 0 && TP_LIMIT > 2; 1315 sp++) { 1316 if (isascii(UChar(*sp)) 1317 && isprint(UChar(*sp)) 1318 && *sp != '\\' 1319 && *sp != '"') 1320 *tp++ = *sp; 1321 else { 1322 _nc_SPRINTF(tp, _nc_SLIMIT(TP_LIMIT) "\\%03o", UChar(*sp)); 1323 tp += 4; 1324 } 1325 } 1326 *tp++ = '"'; 1327 *tp = '\0'; 1328 (void) printf("static char %-20s[] = %s;\n", 1329 string_variable(ExtStrname(term, (int) n, strnames)), 1330 buf); 1331 } 1332 } 1333 printf("\n"); 1334 1335 (void) printf("static char %s[] = %s\n", name_initializer("bool"), L_CURL); 1336 1337 for_each_boolean(n, term) { 1338 switch ((int) (term->Booleans[n])) { 1339 case TRUE: 1340 str = "TRUE"; 1341 break; 1342 1343 case FALSE: 1344 str = "FALSE"; 1345 break; 1346 1347 case ABSENT_BOOLEAN: 1348 str = "ABSENT_BOOLEAN"; 1349 break; 1350 1351 case CANCELLED_BOOLEAN: 1352 str = "CANCELLED_BOOLEAN"; 1353 break; 1354 } 1355 (void) printf("\t/* %3u: %-8s */\t%s,\n", 1356 n, ExtBoolname(term, (int) n, boolnames), str); 1357 } 1358 (void) printf("%s;\n", R_CURL); 1359 1360 (void) printf("static short %s[] = %s\n", name_initializer("number"), L_CURL); 1361 1362 for_each_number(n, term) { 1363 char buf[BUFSIZ]; 1364 switch (term->Numbers[n]) { 1365 case ABSENT_NUMERIC: 1366 str = "ABSENT_NUMERIC"; 1367 break; 1368 case CANCELLED_NUMERIC: 1369 str = "CANCELLED_NUMERIC"; 1370 break; 1371 default: 1372 _nc_SPRINTF(buf, _nc_SLIMIT(sizeof(buf)) "%d", term->Numbers[n]); 1373 str = buf; 1374 break; 1375 } 1376 (void) printf("\t/* %3u: %-8s */\t%s,\n", n, 1377 ExtNumname(term, (int) n, numnames), str); 1378 } 1379 (void) printf("%s;\n", R_CURL); 1380 1381 (void) printf("static char * %s[] = %s\n", name_initializer("string"), L_CURL); 1382 1383 for_each_string(n, term) { 1384 1385 if (term->Strings[n] == ABSENT_STRING) 1386 str = "ABSENT_STRING"; 1387 else if (term->Strings[n] == CANCELLED_STRING) 1388 str = "CANCELLED_STRING"; 1389 else { 1390 str = string_variable(ExtStrname(term, (int) n, strnames)); 1391 } 1392 (void) printf("\t/* %3u: %-8s */\t%s,\n", n, 1393 ExtStrname(term, (int) n, strnames), str); 1394 } 1395 (void) printf("%s;\n", R_CURL); 1396 1397 #if NCURSES_XNAMES 1398 if ((NUM_BOOLEANS(term) != BOOLCOUNT) 1399 || (NUM_NUMBERS(term) != NUMCOUNT) 1400 || (NUM_STRINGS(term) != STRCOUNT)) { 1401 (void) printf("static char * %s[] = %s\n", 1402 name_initializer("string_ext"), L_CURL); 1403 for (n = BOOLCOUNT; n < NUM_BOOLEANS(term); ++n) { 1404 (void) printf("\t/* %3u: bool */\t\"%s\",\n", 1405 n, ExtBoolname(term, (int) n, boolnames)); 1406 } 1407 for (n = NUMCOUNT; n < NUM_NUMBERS(term); ++n) { 1408 (void) printf("\t/* %3u: num */\t\"%s\",\n", 1409 n, ExtNumname(term, (int) n, numnames)); 1410 } 1411 for (n = STRCOUNT; n < NUM_STRINGS(term); ++n) { 1412 (void) printf("\t/* %3u: str */\t\"%s\",\n", 1413 n, ExtStrname(term, (int) n, strnames)); 1414 } 1415 (void) printf("%s;\n", R_CURL); 1416 } 1417 #endif 1418 } 1419 1420 /* dump C initializers for the terminal type */ 1421 static void 1422 dump_termtype(TERMTYPE2 *term) 1423 { 1424 (void) printf("\t%s\n\t\t%s,\n", L_CURL, name_initializer("alias")); 1425 (void) printf("\t\t(char *)0,\t/* pointer to string table */\n"); 1426 1427 (void) printf("\t\t%s,\n", name_initializer("bool")); 1428 (void) printf("\t\t%s,\n", name_initializer("number")); 1429 1430 (void) printf("\t\t%s,\n", name_initializer("string")); 1431 1432 #if NCURSES_XNAMES 1433 (void) printf("#if NCURSES_XNAMES\n"); 1434 (void) printf("\t\t(char *)0,\t/* pointer to extended string table */\n"); 1435 (void) printf("\t\t%s,\t/* ...corresponding names */\n", 1436 ((NUM_BOOLEANS(term) != BOOLCOUNT) 1437 || (NUM_NUMBERS(term) != NUMCOUNT) 1438 || (NUM_STRINGS(term) != STRCOUNT)) 1439 ? name_initializer("string_ext") 1440 : "(char **)0"); 1441 1442 (void) printf("\t\t%d,\t\t/* count total Booleans */\n", NUM_BOOLEANS(term)); 1443 (void) printf("\t\t%d,\t\t/* count total Numbers */\n", NUM_NUMBERS(term)); 1444 (void) printf("\t\t%d,\t\t/* count total Strings */\n", NUM_STRINGS(term)); 1445 1446 (void) printf("\t\t%d,\t\t/* count extensions to Booleans */\n", 1447 NUM_BOOLEANS(term) - BOOLCOUNT); 1448 (void) printf("\t\t%d,\t\t/* count extensions to Numbers */\n", 1449 NUM_NUMBERS(term) - NUMCOUNT); 1450 (void) printf("\t\t%d,\t\t/* count extensions to Strings */\n", 1451 NUM_STRINGS(term) - STRCOUNT); 1452 1453 (void) printf("#endif /* NCURSES_XNAMES */\n"); 1454 #else 1455 (void) term; 1456 #endif /* NCURSES_XNAMES */ 1457 (void) printf("\t%s\n", R_CURL); 1458 } 1459 1460 static int 1461 optarg_to_number(void) 1462 { 1463 char *temp = 0; 1464 long value = strtol(optarg, &temp, 0); 1465 1466 if (temp == 0 || temp == optarg || *temp != 0) { 1467 fprintf(stderr, "Expected a number, not \"%s\"\n", optarg); 1468 ExitProgram(EXIT_FAILURE); 1469 } 1470 return (int) value; 1471 } 1472 1473 static char * 1474 terminal_env(void) 1475 { 1476 char *terminal; 1477 1478 if ((terminal = getenv("TERM")) == 0) { 1479 (void) fprintf(stderr, 1480 "%s: environment variable TERM not set\n", 1481 _nc_progname); 1482 exit(EXIT_FAILURE); 1483 } 1484 return terminal; 1485 } 1486 1487 /* 1488 * Show the databases that infocmp knows about. The location to which it writes is 1489 */ 1490 static void 1491 show_databases(void) 1492 { 1493 DBDIRS state; 1494 int offset; 1495 const char *path2; 1496 1497 _nc_first_db(&state, &offset); 1498 while ((path2 = _nc_next_db(&state, &offset)) != 0) { 1499 printf("%s\n", path2); 1500 } 1501 _nc_last_db(); 1502 } 1503 1504 /*************************************************************************** 1505 * 1506 * Main sequence 1507 * 1508 ***************************************************************************/ 1509 1510 #if NO_LEAKS 1511 #define MAIN_LEAKS() \ 1512 free(myargv); \ 1513 free(tfile); \ 1514 free(tname) 1515 #else 1516 #define MAIN_LEAKS() /* nothing */ 1517 #endif 1518 1519 int 1520 main(int argc, char *argv[]) 1521 { 1522 /* Avoid "local data >32k" error with mwcc */ 1523 /* Also avoid overflowing smaller stacks on systems like AmigaOS */ 1524 path *tfile = 0; 1525 char **tname = 0; 1526 size_t maxterms; 1527 1528 char **myargv; 1529 1530 char *firstdir, *restdir; 1531 int c, i, len; 1532 bool formatted = FALSE; 1533 bool filecompare = FALSE; 1534 int initdump = 0; 1535 bool init_analyze = FALSE; 1536 bool suppress_untranslatable = FALSE; 1537 int quickdump = 0; 1538 bool wrap_strings = FALSE; 1539 1540 /* where is the terminfo database location going to default to? */ 1541 restdir = firstdir = 0; 1542 1543 #if NCURSES_XNAMES 1544 use_extended_names(FALSE); 1545 #endif 1546 _nc_strict_bsd = 0; 1547 1548 _nc_progname = _nc_rootname(argv[0]); 1549 1550 /* make sure we have enough space to add two terminal entries */ 1551 myargv = typeCalloc(char *, (size_t) (argc + 3)); 1552 if (myargv == 0) 1553 failed("myargv"); 1554 1555 memcpy(myargv, argv, (sizeof(char *) * (size_t) argc)); 1556 argv = myargv; 1557 1558 while ((c = getopt(argc, 1559 argv, 1560 "01A:aB:CcDdEeFfGgIiKLlnpQ:qR:rs:TtUuVv:Ww:x")) != -1) { 1561 switch (c) { 1562 case '0': 1563 mwidth = 65535; 1564 mheight = 1; 1565 break; 1566 1567 case '1': 1568 mwidth = 0; 1569 break; 1570 1571 case 'A': 1572 firstdir = optarg; 1573 break; 1574 1575 #if NCURSES_XNAMES 1576 case 'a': 1577 _nc_disable_period = TRUE; 1578 use_extended_names(TRUE); 1579 break; 1580 #endif 1581 case 'B': 1582 restdir = optarg; 1583 break; 1584 1585 case 'K': 1586 _nc_strict_bsd = 1; 1587 /* FALLTHRU */ 1588 case 'C': 1589 outform = F_TERMCAP; 1590 tversion = "BSD"; 1591 if (sortmode == S_DEFAULT) 1592 sortmode = S_TERMCAP; 1593 break; 1594 1595 case 'D': 1596 show_databases(); 1597 ExitProgram(EXIT_SUCCESS); 1598 break; 1599 1600 case 'c': 1601 compare = C_COMMON; 1602 break; 1603 1604 case 'd': 1605 compare = C_DIFFERENCE; 1606 break; 1607 1608 case 'E': 1609 initdump |= 2; 1610 break; 1611 1612 case 'e': 1613 initdump |= 1; 1614 break; 1615 1616 case 'F': 1617 filecompare = TRUE; 1618 break; 1619 1620 case 'f': 1621 formatted = TRUE; 1622 break; 1623 1624 case 'G': 1625 numbers = 1; 1626 break; 1627 1628 case 'g': 1629 numbers = -1; 1630 break; 1631 1632 case 'I': 1633 outform = F_TERMINFO; 1634 if (sortmode == S_DEFAULT) 1635 sortmode = S_VARIABLE; 1636 tversion = 0; 1637 break; 1638 1639 case 'i': 1640 init_analyze = TRUE; 1641 break; 1642 1643 case 'L': 1644 outform = F_VARIABLE; 1645 if (sortmode == S_DEFAULT) 1646 sortmode = S_VARIABLE; 1647 break; 1648 1649 case 'l': 1650 outform = F_TERMINFO; 1651 break; 1652 1653 case 'n': 1654 compare = C_NAND; 1655 break; 1656 1657 case 'p': 1658 ignorepads = TRUE; 1659 break; 1660 1661 case 'Q': 1662 quickdump = optarg_to_number(); 1663 break; 1664 1665 case 'q': 1666 quiet = TRUE; 1667 s_absent = "-"; 1668 s_cancel = "@"; 1669 bool_sep = ", "; 1670 break; 1671 1672 case 'R': 1673 tversion = optarg; 1674 break; 1675 1676 case 'r': 1677 tversion = 0; 1678 break; 1679 1680 case 's': 1681 if (*optarg == 'd') 1682 sortmode = S_NOSORT; 1683 else if (*optarg == 'i') 1684 sortmode = S_TERMINFO; 1685 else if (*optarg == 'l') 1686 sortmode = S_VARIABLE; 1687 else if (*optarg == 'c') 1688 sortmode = S_TERMCAP; 1689 else { 1690 (void) fprintf(stderr, 1691 "%s: unknown sort mode\n", 1692 _nc_progname); 1693 ExitProgram(EXIT_FAILURE); 1694 } 1695 break; 1696 1697 case 'T': 1698 limited = FALSE; 1699 break; 1700 1701 #if NCURSES_XNAMES 1702 case 't': 1703 _nc_disable_period = FALSE; 1704 suppress_untranslatable = TRUE; 1705 break; 1706 #endif 1707 1708 case 'U': 1709 literal = TRUE; 1710 break; 1711 1712 case 'u': 1713 compare = C_USEALL; 1714 break; 1715 1716 case 'V': 1717 puts(curses_version()); 1718 ExitProgram(EXIT_SUCCESS); 1719 1720 case 'v': 1721 itrace = (unsigned) optarg_to_number(); 1722 set_trace_level(itrace); 1723 break; 1724 1725 case 'W': 1726 wrap_strings = TRUE; 1727 break; 1728 1729 case 'w': 1730 mwidth = optarg_to_number(); 1731 break; 1732 1733 #if NCURSES_XNAMES 1734 case 'x': 1735 use_extended_names(TRUE); 1736 break; 1737 #endif 1738 1739 default: 1740 usage(); 1741 } 1742 } 1743 1744 maxterms = (size_t) (argc + 2 - optind); 1745 if ((tfile = typeMalloc(path, maxterms)) == 0) 1746 failed("tfile"); 1747 if ((tname = typeCalloc(char *, maxterms)) == 0) 1748 failed("tname"); 1749 if ((entries = typeCalloc(ENTRY, maxterms)) == 0) 1750 failed("entries"); 1751 #if NO_LEAKS 1752 if ((entered = typeCalloc(ENTERED, maxterms)) == 0) 1753 failed("entered"); 1754 #endif 1755 1756 if (tfile == 0 1757 || tname == 0 1758 || entries == 0) { 1759 fprintf(stderr, "%s: not enough memory\n", _nc_progname); 1760 ExitProgram(EXIT_FAILURE); 1761 } 1762 1763 /* by default, sort by terminfo name */ 1764 if (sortmode == S_DEFAULT) 1765 sortmode = S_TERMINFO; 1766 1767 /* make sure we have at least one terminal name to work with */ 1768 if (optind >= argc) 1769 argv[argc++] = terminal_env(); 1770 1771 /* if user is after a comparison, make sure we have two entries */ 1772 if (compare != C_DEFAULT && optind >= argc - 1) 1773 argv[argc++] = terminal_env(); 1774 1775 /* exactly one terminal name with no options means display it */ 1776 /* exactly two terminal names with no options means do -d */ 1777 if (compare == C_DEFAULT) { 1778 switch (argc - optind) { 1779 default: 1780 fprintf(stderr, "%s: too many names to compare\n", _nc_progname); 1781 ExitProgram(EXIT_FAILURE); 1782 case 1: 1783 break; 1784 case 2: 1785 compare = C_DIFFERENCE; 1786 break; 1787 } 1788 } 1789 1790 /* set up for display */ 1791 dump_init(tversion, outform, sortmode, 1792 wrap_strings, mwidth, mheight, itrace, 1793 formatted, FALSE, quickdump); 1794 1795 if (!filecompare) { 1796 /* grab the entries */ 1797 termcount = 0; 1798 for (; optind < argc; optind++) { 1799 const char *directory = termcount ? restdir : firstdir; 1800 int status; 1801 1802 tname[termcount] = argv[optind]; 1803 1804 if (directory) { 1805 #if NCURSES_USE_DATABASE 1806 #if MIXEDCASE_FILENAMES 1807 #define LEAF_FMT "%c" 1808 #else 1809 #define LEAF_FMT "%02x" 1810 #endif 1811 _nc_SPRINTF(tfile[termcount], 1812 _nc_SLIMIT(sizeof(path)) 1813 "%s/" LEAF_FMT "/%s", 1814 directory, 1815 UChar(*argv[optind]), argv[optind]); 1816 if (itrace) 1817 (void) fprintf(stderr, 1818 "%s: reading entry %s from file %s\n", 1819 _nc_progname, 1820 argv[optind], tfile[termcount]); 1821 1822 status = _nc_read_file_entry(tfile[termcount], 1823 &entries[termcount].tterm); 1824 #else 1825 (void) fprintf(stderr, "%s: terminfo files not supported\n", 1826 _nc_progname); 1827 MAIN_LEAKS(); 1828 ExitProgram(EXIT_FAILURE); 1829 #endif 1830 } else { 1831 if (itrace) 1832 (void) fprintf(stderr, 1833 "%s: reading entry %s from database\n", 1834 _nc_progname, 1835 tname[termcount]); 1836 1837 status = _nc_read_entry2(tname[termcount], 1838 tfile[termcount], 1839 &entries[termcount].tterm); 1840 } 1841 1842 if (status <= 0) { 1843 (void) fprintf(stderr, 1844 "%s: couldn't open terminfo file %s.\n", 1845 _nc_progname, 1846 tfile[termcount]); 1847 MAIN_LEAKS(); 1848 ExitProgram(EXIT_FAILURE); 1849 } 1850 repair_acsc(&entries[termcount].tterm); 1851 termcount++; 1852 } 1853 1854 #if NCURSES_XNAMES 1855 if (termcount > 1) 1856 _nc_align_termtype(&entries[0].tterm, &entries[1].tterm); 1857 #endif 1858 1859 /* dump as C initializer for the terminal type */ 1860 if (initdump) { 1861 if (initdump & 1) 1862 dump_termtype(&entries[0].tterm); 1863 if (initdump & 2) 1864 dump_initializers(&entries[0].tterm); 1865 } 1866 1867 /* analyze the init strings */ 1868 else if (init_analyze) { 1869 #undef CUR 1870 #define CUR entries[0].tterm. 1871 analyze_string("is1", init_1string, &entries[0].tterm); 1872 analyze_string("is2", init_2string, &entries[0].tterm); 1873 analyze_string("is3", init_3string, &entries[0].tterm); 1874 analyze_string("rs1", reset_1string, &entries[0].tterm); 1875 analyze_string("rs2", reset_2string, &entries[0].tterm); 1876 analyze_string("rs3", reset_3string, &entries[0].tterm); 1877 analyze_string("smcup", enter_ca_mode, &entries[0].tterm); 1878 analyze_string("rmcup", exit_ca_mode, &entries[0].tterm); 1879 analyze_string("smkx", keypad_xmit, &entries[0].tterm); 1880 analyze_string("rmkx", keypad_local, &entries[0].tterm); 1881 #undef CUR 1882 } else { 1883 1884 /* 1885 * Here's where the real work gets done 1886 */ 1887 switch (compare) { 1888 case C_DEFAULT: 1889 if (itrace) 1890 (void) fprintf(stderr, 1891 "%s: about to dump %s\n", 1892 _nc_progname, 1893 tname[0]); 1894 if (!quiet) 1895 (void) 1896 printf("#\tReconstructed via infocmp from file: %s\n", 1897 tfile[0]); 1898 dump_entry(&entries[0].tterm, 1899 suppress_untranslatable, 1900 limited, 1901 numbers, 1902 NULL); 1903 len = show_entry(); 1904 if (itrace) 1905 (void) fprintf(stderr, "%s: length %d\n", _nc_progname, len); 1906 break; 1907 1908 case C_DIFFERENCE: 1909 show_comparing(tname); 1910 compare_entry(compare_predicate, &entries->tterm, quiet); 1911 break; 1912 1913 case C_COMMON: 1914 show_comparing(tname); 1915 compare_entry(compare_predicate, &entries->tterm, quiet); 1916 break; 1917 1918 case C_NAND: 1919 show_comparing(tname); 1920 compare_entry(compare_predicate, &entries->tterm, quiet); 1921 break; 1922 1923 case C_USEALL: 1924 if (itrace) 1925 (void) fprintf(stderr, "%s: dumping use entry\n", _nc_progname); 1926 dump_entry(&entries[0].tterm, 1927 suppress_untranslatable, 1928 limited, 1929 numbers, 1930 use_predicate); 1931 for (i = 1; i < termcount; i++) 1932 dump_uses(tname[i], !(outform == F_TERMCAP 1933 || outform == F_TCONVERR)); 1934 len = show_entry(); 1935 if (itrace) 1936 (void) fprintf(stderr, "%s: length %d\n", _nc_progname, len); 1937 break; 1938 } 1939 } 1940 } else if (compare == C_USEALL) { 1941 (void) fprintf(stderr, "Sorry, -u doesn't work with -F\n"); 1942 } else if (compare == C_DEFAULT) { 1943 (void) fprintf(stderr, "Use `tic -[CI] <file>' for this.\n"); 1944 } else if (argc - optind != 2) { 1945 (void) fprintf(stderr, 1946 "File comparison needs exactly two file arguments.\n"); 1947 } else { 1948 file_comparison(argc - optind, argv + optind); 1949 } 1950 1951 MAIN_LEAKS(); 1952 ExitProgram(EXIT_SUCCESS); 1953 } 1954 1955 /* infocmp.c ends here */ 1956