1 /**************************************************************************** 2 * Copyright (c) 1998,1999 Free Software Foundation, Inc. * 3 * * 4 * Permission is hereby granted, free of charge, to any person obtaining a * 5 * copy of this software and associated documentation files (the * 6 * "Software"), to deal in the Software without restriction, including * 7 * without limitation the rights to use, copy, modify, merge, publish, * 8 * distribute, distribute with modifications, sublicense, and/or sell * 9 * copies of the Software, and to permit persons to whom the Software is * 10 * furnished to do so, subject to the following conditions: * 11 * * 12 * The above copyright notice and this permission notice shall be included * 13 * in all copies or substantial portions of the Software. * 14 * * 15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * 16 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * 17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * 18 * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * 19 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * 20 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR * 21 * THE USE OR OTHER DEALINGS IN THE SOFTWARE. * 22 * * 23 * Except as contained in this notice, the name(s) of the above copyright * 24 * holders shall not be used in advertising or otherwise to promote the * 25 * sale, use or other dealings in this Software without prior written * 26 * authorization. * 27 ****************************************************************************/ 28 29 /**************************************************************************** 30 * Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995 * 31 * and: Eric S. Raymond <esr@snark.thyrsus.com> * 32 ****************************************************************************/ 33 34 35 /* 36 * infocmp.c -- decompile an entry, or compare two entries 37 * written by Eric S. Raymond 38 */ 39 40 #include <progs.priv.h> 41 42 #include <term_entry.h> 43 #include <dump_entry.h> 44 45 MODULE_ID("$Id: infocmp.c,v 1.44 1999/06/16 00:39:48 tom Exp $") 46 47 #define L_CURL "{" 48 #define R_CURL "}" 49 50 #define MAXTERMS 32 /* max # terminal arguments we can handle */ 51 52 const char *_nc_progname = "infocmp"; 53 54 typedef char path[PATH_MAX]; 55 56 /*************************************************************************** 57 * 58 * The following control variables, together with the contents of the 59 * terminfo entries, completely determine the actions of the program. 60 * 61 ***************************************************************************/ 62 63 static char *tname[MAXTERMS]; /* terminal type names */ 64 static TERMTYPE term[MAXTERMS]; /* terminfo entries */ 65 static int termcount; /* count of terminal entries */ 66 67 static const char *tversion; /* terminfo version selected */ 68 static int numbers = 0; /* format "%'char'" to/from "%{number}" */ 69 static int outform; /* output format */ 70 static int sortmode; /* sort_mode */ 71 static int itrace; /* trace flag for debugging */ 72 static int mwidth = 60; 73 74 /* main comparison mode */ 75 static int compare; 76 #define C_DEFAULT 0 /* don't force comparison mode */ 77 #define C_DIFFERENCE 1 /* list differences between two terminals */ 78 #define C_COMMON 2 /* list common capabilities */ 79 #define C_NAND 3 /* list capabilities in neither terminal */ 80 #define C_USEALL 4 /* generate relative use-form entry */ 81 static bool ignorepads; /* ignore pad prefixes when diffing */ 82 83 #if NO_LEAKS 84 #undef ExitProgram 85 static void ExitProgram(int code) GCC_NORETURN; 86 static void ExitProgram(int code) 87 { 88 while (termcount-- > 0) 89 _nc_free_termtype(&term[termcount]); 90 _nc_leaks_dump_entry(); 91 _nc_free_and_exit(code); 92 } 93 #endif 94 95 static char *canonical_name(char *ptr, char *buf) 96 /* extract the terminal type's primary name */ 97 { 98 char *bp; 99 100 (void) strcpy(buf, ptr); 101 if ((bp = strchr(buf, '|')) != (char *)NULL) 102 *bp = '\0'; 103 104 return(buf); 105 } 106 107 /*************************************************************************** 108 * 109 * Predicates for dump function 110 * 111 ***************************************************************************/ 112 113 static int capcmp(const char *s, const char *t) 114 /* capability comparison function */ 115 { 116 if (!VALID_STRING(s) && !VALID_STRING(t)) 117 return(0); 118 else if (!VALID_STRING(s) || !VALID_STRING(t)) 119 return(1); 120 121 if (ignorepads) 122 return(_nc_capcmp(s, t)); 123 else 124 return(strcmp(s, t)); 125 } 126 127 static int use_predicate(int type, int idx) 128 /* predicate function to use for use decompilation */ 129 { 130 TERMTYPE *tp; 131 132 switch(type) 133 { 134 case BOOLEAN: { 135 int is_set = FALSE; 136 137 /* 138 * This assumes that multiple use entries are supposed 139 * to contribute the logical or of their boolean capabilities. 140 * This is true if we take the semantics of multiple uses to 141 * be 'each capability gets the first non-default value found 142 * in the sequence of use entries'. 143 */ 144 for (tp = &term[1]; tp < term + termcount; tp++) 145 if (tp->Booleans[idx]) { 146 is_set = TRUE; 147 break; 148 } 149 if (is_set != term->Booleans[idx]) 150 return(!is_set); 151 else 152 return(FAIL); 153 } 154 155 case NUMBER: { 156 int value = ABSENT_NUMERIC; 157 158 /* 159 * We take the semantics of multiple uses to be 'each 160 * capability gets the first non-default value found 161 * in the sequence of use entries'. 162 */ 163 for (tp = &term[1]; tp < term + termcount; tp++) 164 if (tp->Numbers[idx] >= 0) { 165 value = tp->Numbers[idx]; 166 break; 167 } 168 169 if (value != term->Numbers[idx]) 170 return(value != ABSENT_NUMERIC); 171 else 172 return(FAIL); 173 } 174 175 case STRING: { 176 char *termstr, *usestr = ABSENT_STRING; 177 178 termstr = term->Strings[idx]; 179 180 /* 181 * We take the semantics of multiple uses to be 'each 182 * capability gets the first non-default value found 183 * in the sequence of use entries'. 184 */ 185 for (tp = &term[1]; tp < term + termcount; tp++) 186 if (tp->Strings[idx]) 187 { 188 usestr = tp->Strings[idx]; 189 break; 190 } 191 192 if (usestr == ABSENT_STRING && termstr == ABSENT_STRING) 193 return(FAIL); 194 else if (!usestr || !termstr || capcmp(usestr, termstr)) 195 return(TRUE); 196 else 197 return(FAIL); 198 } 199 } 200 201 return(FALSE); /* pacify compiler */ 202 } 203 204 static bool entryeq(TERMTYPE *t1, TERMTYPE *t2) 205 /* are two terminal types equal */ 206 { 207 int i; 208 209 for (i = 0; i < NUM_BOOLEANS(t1); i++) 210 if (t1->Booleans[i] != t2->Booleans[i]) 211 return(FALSE); 212 213 for (i = 0; i < NUM_NUMBERS(t1); i++) 214 if (t1->Numbers[i] != t2->Numbers[i]) 215 return(FALSE); 216 217 for (i = 0; i < NUM_STRINGS(t1); i++) 218 if (capcmp(t1->Strings[i], t2->Strings[i])) 219 return(FALSE); 220 221 return(TRUE); 222 } 223 224 #define TIC_EXPAND(result) _nc_tic_expand(result, outform==F_TERMINFO, numbers) 225 226 static void compare_predicate(int type, int idx, const char *name) 227 /* predicate function to use for entry difference reports */ 228 { 229 register TERMTYPE *t1 = &term[0]; 230 register TERMTYPE *t2 = &term[1]; 231 char *s1, *s2; 232 233 switch(type) 234 { 235 case BOOLEAN: 236 switch(compare) 237 { 238 case C_DIFFERENCE: 239 if (t1->Booleans[idx] != t2->Booleans[idx]) 240 (void) printf("\t%s: %c:%c.\n", 241 name, 242 t1->Booleans[idx] ? 'T' : 'F', 243 t2->Booleans[idx] ? 'T' : 'F'); 244 break; 245 246 case C_COMMON: 247 if (t1->Booleans[idx] && t2->Booleans[idx]) 248 (void) printf("\t%s= T.\n", name); 249 break; 250 251 case C_NAND: 252 if (!t1->Booleans[idx] && !t2->Booleans[idx]) 253 (void) printf("\t!%s.\n", name); 254 break; 255 } 256 break; 257 258 case NUMBER: 259 switch(compare) 260 { 261 case C_DIFFERENCE: 262 if (t1->Numbers[idx] != t2->Numbers[idx]) 263 (void) printf("\t%s: %d:%d.\n", 264 name, t1->Numbers[idx], t2->Numbers[idx]); 265 break; 266 267 case C_COMMON: 268 if (t1->Numbers[idx]!=-1 && t2->Numbers[idx]!=-1 269 && t1->Numbers[idx] == t2->Numbers[idx]) 270 (void) printf("\t%s= %d.\n", name, t1->Numbers[idx]); 271 break; 272 273 case C_NAND: 274 if (t1->Numbers[idx]==-1 && t2->Numbers[idx] == -1) 275 (void) printf("\t!%s.\n", name); 276 break; 277 } 278 break; 279 280 case STRING: 281 s1 = t1->Strings[idx]; 282 s2 = t2->Strings[idx]; 283 switch(compare) 284 { 285 case C_DIFFERENCE: 286 if (capcmp(s1, s2)) 287 { 288 char buf1[BUFSIZ], buf2[BUFSIZ]; 289 290 if (s1 == (char *)NULL) 291 (void) strcpy(buf1, "NULL"); 292 else 293 { 294 (void) strcpy(buf1, "'"); 295 (void) strcat(buf1, TIC_EXPAND(s1)); 296 (void) strcat(buf1, "'"); 297 } 298 299 if (s2 == (char *)NULL) 300 (void) strcpy(buf2, "NULL"); 301 else 302 { 303 (void) strcpy(buf2, "'"); 304 (void) strcat(buf2, TIC_EXPAND(s2)); 305 (void) strcat(buf2, "'"); 306 } 307 308 if (strcmp(buf1, buf2)) 309 (void) printf("\t%s: %s, %s.\n", 310 name, buf1, buf2); 311 } 312 break; 313 314 case C_COMMON: 315 if (s1 && s2 && !capcmp(s1, s2)) 316 (void) printf("\t%s= '%s'.\n", name, TIC_EXPAND(s1)); 317 break; 318 319 case C_NAND: 320 if (!s1 && !s2) 321 (void) printf("\t!%s.\n", name); 322 break; 323 } 324 break; 325 } 326 327 } 328 329 /*************************************************************************** 330 * 331 * Init string analysis 332 * 333 ***************************************************************************/ 334 335 typedef struct {const char *from; const char *to;} assoc; 336 337 static const assoc std_caps[] = 338 { 339 /* these are specified by X.364 and iBCS2 */ 340 {"\033c", "RIS"}, /* full reset */ 341 {"\0337", "SC"}, /* save cursor */ 342 {"\0338", "RC"}, /* restore cursor */ 343 {"\033[r", "RSR"}, /* not an X.364 mnemonic */ 344 {"\033[m", "SGR0"}, /* not an X.364 mnemonic */ 345 {"\033[2J", "ED2"}, /* clear page */ 346 347 /* this group is specified by ISO 2022 */ 348 {"\033(0", "ISO DEC G0"}, /* enable DEC graphics for G0 */ 349 {"\033(A", "ISO UK G0"}, /* enable UK chars for G0 */ 350 {"\033(B", "ISO US G0"}, /* enable US chars for G0 */ 351 {"\033)0", "ISO DEC G1"}, /* enable DEC graphics for G1 */ 352 {"\033)A", "ISO UK G1"}, /* enable UK chars for G1 */ 353 {"\033)B", "ISO US G1"}, /* enable US chars for G1 */ 354 355 /* these are DEC private modes widely supported by emulators */ 356 {"\033=", "DECPAM"}, /* application keypad mode */ 357 {"\033>", "DECPNM"}, /* normal keypad mode */ 358 {"\033<", "DECANSI"}, /* enter ANSI mode */ 359 360 { (char *)0, (char *)0} 361 }; 362 363 static const assoc private_modes[] = 364 /* DEC \E[ ... [hl] modes recognized by many emulators */ 365 { 366 {"1", "CKM"}, /* application cursor keys */ 367 {"2", "ANM"}, /* set VT52 mode */ 368 {"3", "COLM"}, /* 132-column mode */ 369 {"4", "SCLM"}, /* smooth scroll */ 370 {"5", "SCNM"}, /* reverse video mode */ 371 {"6", "OM"}, /* origin mode */ 372 {"7", "AWM"}, /* wraparound mode */ 373 {"8", "ARM"}, /* auto-repeat mode */ 374 {(char *)0, (char *)0} 375 }; 376 377 static const assoc ecma_highlights[] = 378 /* recognize ECMA attribute sequences */ 379 { 380 {"0", "NORMAL"}, /* normal */ 381 {"1", "+BOLD"}, /* bold on */ 382 {"2", "+DIM"}, /* dim on */ 383 {"3", "+ITALIC"}, /* italic on */ 384 {"4", "+UNDERLINE"}, /* underline on */ 385 {"5", "+BLINK"}, /* blink on */ 386 {"6", "+FASTBLINK"}, /* fastblink on */ 387 {"7", "+REVERSE"}, /* reverse on */ 388 {"8", "+INVISIBLE"}, /* invisible on */ 389 {"9", "+DELETED"}, /* deleted on */ 390 {"10", "MAIN-FONT"}, /* select primary font */ 391 {"11", "ALT-FONT-1"}, /* select alternate font 1 */ 392 {"12", "ALT-FONT-2"}, /* select alternate font 2 */ 393 {"13", "ALT-FONT-3"}, /* select alternate font 3 */ 394 {"14", "ALT-FONT-4"}, /* select alternate font 4 */ 395 {"15", "ALT-FONT-5"}, /* select alternate font 5 */ 396 {"16", "ALT-FONT-6"}, /* select alternate font 6 */ 397 {"17", "ALT-FONT-7"}, /* select alternate font 7 */ 398 {"18", "ALT-FONT-1"}, /* select alternate font 1 */ 399 {"19", "ALT-FONT-1"}, /* select alternate font 1 */ 400 {"20", "FRAKTUR"}, /* Fraktur font */ 401 {"21", "DOUBLEUNDER"}, /* double underline */ 402 {"22", "-DIM"}, /* dim off */ 403 {"23", "-ITALIC"}, /* italic off */ 404 {"24", "-UNDERLINE"}, /* underline off */ 405 {"25", "-BLINK"}, /* blink off */ 406 {"26", "-FASTBLINK"}, /* fastblink off */ 407 {"27", "-REVERSE"}, /* reverse off */ 408 {"28", "-INVISIBLE"}, /* invisible off */ 409 {"29", "-DELETED"}, /* deleted off */ 410 {(char *)0, (char *)0} 411 }; 412 413 static void analyze_string(const char *name, const char *cap, TERMTYPE *tp) 414 { 415 char buf[MAX_TERMINFO_LENGTH]; 416 char buf2[MAX_TERMINFO_LENGTH]; 417 const char *sp, *ep; 418 const assoc *ap; 419 420 if (cap == ABSENT_STRING || cap == CANCELLED_STRING) 421 return; 422 (void) printf("%s: ", name); 423 424 buf[0] = '\0'; 425 for (sp = cap; *sp; sp++) 426 { 427 int i; 428 size_t len = 0; 429 const char *expansion = 0; 430 431 /* first, check other capabilities in this entry */ 432 for (i = 0; i < STRCOUNT; i++) 433 { 434 char *cp = tp->Strings[i]; 435 436 /* don't use soft-key capabilities */ 437 if (strnames[i][0] == 'k' && strnames[i][0] == 'f') 438 continue; 439 440 441 if (cp != ABSENT_STRING && cp != CANCELLED_STRING && cp[0] && cp != cap) 442 { 443 len = strlen(cp); 444 (void) strncpy(buf2, sp, len); 445 buf2[len] = '\0'; 446 447 if (_nc_capcmp(cp, buf2)) 448 continue; 449 450 #define ISRS(s) (!strncmp((s), "is", 2) || !strncmp((s), "rs", 2)) 451 /* 452 * Theoretically we just passed the test for translation 453 * (equality once the padding is stripped). However, there 454 * are a few more hoops that need to be jumped so that 455 * identical pairs of initialization and reset strings 456 * don't just refer to each other. 457 */ 458 if (ISRS(name) || ISRS(strnames[i])) 459 if (cap < cp) 460 continue; 461 #undef ISRS 462 463 expansion = strnames[i]; 464 break; 465 } 466 } 467 468 /* now check the standard capabilities */ 469 if (!expansion) 470 for (ap = std_caps; ap->from; ap++) 471 { 472 len = strlen(ap->from); 473 474 if (strncmp(ap->from, sp, len) == 0) 475 { 476 expansion = ap->to; 477 break; 478 } 479 } 480 481 /* now check for private-mode sequences */ 482 if (!expansion 483 && sp[0] == '\033' && sp[1] == '[' && sp[2] == '?' 484 && (len = strspn(sp + 3, "0123456789;")) 485 && ((sp[3 + len] == 'h') || (sp[3 + len] == 'l'))) 486 { 487 char buf3[MAX_TERMINFO_LENGTH]; 488 489 (void) strcpy(buf2, (sp[3 + len] == 'h') ? "DEC+" : "DEC-"); 490 (void) strncpy(buf3, sp + 3, len); 491 len += 4; 492 buf3[len] = '\0'; 493 494 ep = strtok(buf3, ";"); 495 do { 496 bool found = FALSE; 497 498 for (ap = private_modes; ap->from; ap++) 499 { 500 size_t tlen = strlen(ap->from); 501 502 if (strncmp(ap->from, ep, tlen) == 0) 503 { 504 (void) strcat(buf2, ap->to); 505 found = TRUE; 506 break; 507 } 508 } 509 510 if (!found) 511 (void) strcat(buf2, ep); 512 (void) strcat(buf2, ";"); 513 } while 514 ((ep = strtok((char *)NULL, ";"))); 515 buf2[strlen(buf2) - 1] = '\0'; 516 expansion = buf2; 517 } 518 519 /* now check for ECMA highlight sequences */ 520 if (!expansion 521 && sp[0] == '\033' && sp[1] == '[' 522 && (len = strspn(sp + 2, "0123456789;")) 523 && sp[2 + len] == 'm') 524 { 525 char buf3[MAX_TERMINFO_LENGTH]; 526 527 (void) strcpy(buf2, "SGR:"); 528 (void) strncpy(buf3, sp + 2, len); 529 len += 3; 530 buf3[len] = '\0'; 531 532 ep = strtok(buf3, ";"); 533 do { 534 bool found = FALSE; 535 536 for (ap = ecma_highlights; ap->from; ap++) 537 { 538 size_t tlen = strlen(ap->from); 539 540 if (strncmp(ap->from, ep, tlen) == 0) 541 { 542 (void) strcat(buf2, ap->to); 543 found = TRUE; 544 break; 545 } 546 } 547 548 if (!found) 549 (void) strcat(buf2, ep); 550 (void) strcat(buf2, ";"); 551 } while 552 ((ep = strtok((char *)NULL, ";"))); 553 554 buf2[strlen(buf2) - 1] = '\0'; 555 expansion = buf2; 556 } 557 /* now check for scroll region reset */ 558 if (!expansion) 559 { 560 (void) sprintf(buf2, "\033[1;%dr", tp->Numbers[2]); 561 len = strlen(buf2); 562 if (strncmp(buf2, sp, len) == 0) 563 expansion = "RSR"; 564 } 565 566 /* now check for home-down */ 567 if (!expansion) 568 { 569 (void) sprintf(buf2, "\033[%d;1H", tp->Numbers[2]); 570 len = strlen(buf2); 571 if (strncmp(buf2, sp, len) == 0) 572 expansion = "LL"; 573 } 574 575 /* now look at the expansion we got, if any */ 576 if (expansion) 577 { 578 (void) sprintf(buf + strlen(buf), "{%s}", expansion); 579 sp += len - 1; 580 continue; 581 } 582 else 583 { 584 /* couldn't match anything */ 585 buf2[0] = *sp; 586 buf2[1] = '\0'; 587 (void) strcat(buf, TIC_EXPAND(buf2)); 588 } 589 } 590 (void) printf("%s\n", buf); 591 } 592 593 /*************************************************************************** 594 * 595 * File comparison 596 * 597 ***************************************************************************/ 598 599 static void file_comparison(int argc, char *argv[]) 600 { 601 #define MAXCOMPARE 2 602 /* someday we may allow comparisons on more files */ 603 int filecount = 0; 604 ENTRY *heads[MAXCOMPARE]; 605 ENTRY *tails[MAXCOMPARE]; 606 ENTRY *qp, *rp; 607 int i, n; 608 609 dump_init((char *)NULL, F_LITERAL, S_TERMINFO, 0, itrace, FALSE); 610 611 for (n = 0; n < argc && n < MAXCOMPARE; n++) 612 { 613 if (freopen(argv[n], "r", stdin) == NULL) 614 _nc_err_abort("Can't open %s", argv[n]); 615 616 _nc_head = _nc_tail = (ENTRY *)NULL; 617 618 /* parse entries out of the source file */ 619 _nc_set_source(argv[n]); 620 _nc_read_entry_source(stdin, NULL, TRUE, FALSE, NULLHOOK); 621 622 if (itrace) 623 (void) fprintf(stderr, "Resolving file %d...\n", n-0); 624 625 /* do use resolution */ 626 if (!_nc_resolve_uses()) 627 { 628 (void) fprintf(stderr, 629 "There are unresolved use entries in %s:\n", 630 argv[n]); 631 for_entry_list(qp) 632 if (qp->nuses) 633 { 634 (void) fputs(qp->tterm.term_names, stderr); 635 (void) fputc('\n', stderr); 636 } 637 exit(EXIT_FAILURE); 638 } 639 640 heads[filecount] = _nc_head; 641 tails[filecount] = _nc_tail; 642 filecount++; 643 } 644 645 /* OK, all entries are in core. Ready to do the comparison */ 646 if (itrace) 647 (void) fprintf(stderr, "Entries are now in core...\n"); 648 649 /* 650 * The entry-matching loop. We're not using the use[] 651 * slots any more (they got zeroed out by resolve_uses) so 652 * we stash each entry's matches in the other file there. 653 * Sigh, this is intrinsically quadratic. 654 */ 655 for (qp = heads[0]; qp; qp = qp->next) 656 { 657 for (rp = heads[1]; rp; rp = rp->next) 658 if (_nc_entry_match(qp->tterm.term_names, rp->tterm.term_names)) 659 { 660 /* 661 * This is why the uses structure parent element is 662 * (void *) -- so we can have either (char *) for 663 * names or entry structure pointers in them and still 664 * be type-safe. 665 */ 666 if (qp->nuses < MAX_USES) 667 qp->uses[qp->nuses].parent = (void *)rp; 668 qp->nuses++; 669 670 if (rp->nuses < MAX_USES) 671 rp->uses[rp->nuses].parent = (void *)qp; 672 rp->nuses++; 673 } 674 } 675 676 /* now we have two circular lists with crosslinks */ 677 if (itrace) 678 (void) fprintf(stderr, "Name matches are done...\n"); 679 680 for (qp = heads[0]; qp; qp = qp->next) 681 if (qp->nuses > 1) 682 { 683 (void) fprintf(stderr, 684 "%s in file 1 (%s) has %d matches in file 2 (%s):\n", 685 _nc_first_name(qp->tterm.term_names), 686 argv[0], 687 qp->nuses, 688 argv[1]); 689 for (i = 0; i < qp->nuses; i++) 690 (void) fprintf(stderr, 691 "\t%s\n", 692 _nc_first_name(((ENTRY *)qp->uses[i].parent)->tterm.term_names)); 693 } 694 for (rp = heads[1]; rp; rp = rp->next) 695 if (rp->nuses > 1) 696 { 697 (void) fprintf(stderr, 698 "%s in file 2 (%s) has %d matches in file 1 (%s):\n", 699 _nc_first_name(rp->tterm.term_names), 700 argv[1], 701 rp->nuses, 702 argv[0]); 703 for (i = 0; i < rp->nuses; i++) 704 (void) fprintf(stderr, 705 "\t%s\n", 706 _nc_first_name(((ENTRY *)rp->uses[i].parent)->tterm.term_names)); 707 } 708 709 (void) printf("In file 1 (%s) only:\n", argv[0]); 710 for (qp = heads[0]; qp; qp = qp->next) 711 if (qp->nuses == 0) 712 (void) printf("\t%s\n", 713 _nc_first_name(qp->tterm.term_names)); 714 715 (void) printf("In file 2 (%s) only:\n", argv[1]); 716 for (rp = heads[1]; rp; rp = rp->next) 717 if (rp->nuses == 0) 718 (void) printf("\t%s\n", 719 _nc_first_name(rp->tterm.term_names)); 720 721 (void) printf("The following entries are equivalent:\n"); 722 for (qp = heads[0]; qp; qp = qp->next) 723 { 724 rp = (ENTRY *)qp->uses[0].parent; 725 726 if (qp->nuses == 1 && entryeq(&qp->tterm, &rp->tterm)) 727 { 728 char name1[NAMESIZE], name2[NAMESIZE]; 729 730 (void) canonical_name(qp->tterm.term_names, name1); 731 (void) canonical_name(rp->tterm.term_names, name2); 732 733 (void) printf("%s = %s\n", name1, name2); 734 } 735 } 736 737 (void) printf("Differing entries:\n"); 738 termcount = 2; 739 for (qp = heads[0]; qp; qp = qp->next) 740 { 741 rp = (ENTRY *)qp->uses[0].parent; 742 743 #if NCURSES_XNAMES 744 if (termcount > 1) 745 _nc_align_termtype(&qp->tterm, &rp->tterm); 746 #endif 747 if (qp->nuses == 1 && !entryeq(&qp->tterm, &rp->tterm)) 748 { 749 char name1[NAMESIZE], name2[NAMESIZE]; 750 751 term[0] = qp->tterm; 752 term[1] = rp->tterm; 753 754 (void) canonical_name(qp->tterm.term_names, name1); 755 (void) canonical_name(rp->tterm.term_names, name2); 756 757 switch (compare) 758 { 759 case C_DIFFERENCE: 760 if (itrace) 761 (void)fprintf(stderr, "infocmp: dumping differences\n"); 762 (void) printf("comparing %s to %s.\n", name1, name2); 763 compare_entry(compare_predicate, term); 764 break; 765 766 case C_COMMON: 767 if (itrace) 768 (void) fprintf(stderr, 769 "infocmp: dumping common capabilities\n"); 770 (void) printf("comparing %s to %s.\n", name1, name2); 771 compare_entry(compare_predicate, term); 772 break; 773 774 case C_NAND: 775 if (itrace) 776 (void) fprintf(stderr, 777 "infocmp: dumping differences\n"); 778 (void) printf("comparing %s to %s.\n", name1, name2); 779 compare_entry(compare_predicate, term); 780 break; 781 782 } 783 } 784 } 785 } 786 787 static void usage(void) 788 { 789 static const char *tbl[] = { 790 "Usage: infocmp [options] [-A directory] [-B directory] [termname...]" 791 ,"" 792 ,"Options:" 793 ," -1 print single-column" 794 ," -C use termcap-names" 795 ," -F compare terminfo-files" 796 ," -I use terminfo-names" 797 ," -L use long names" 798 ," -R subset (see manpage)" 799 ," -T eliminate size limits (test)" 800 ," -V print version" 801 ," -c list common capabilities" 802 ," -d list different capabilities" 803 ," -e format output for C initializer" 804 ," -E format output as C tables" 805 ," -f with -1, format complex strings" 806 ," -G format %{number} to %'char'" 807 ," -g format %'char' to %{number}" 808 ," -i analyze initialization/reset" 809 ," -l output terminfo names" 810 ," -n list capabilities in neither" 811 ," -p ignore padding specifiers" 812 ," -r with -C, output in termcap form" 813 ," -s [d|i|l|c] sort fields" 814 ," -u produce source with 'use='" 815 ," -v number (verbose)" 816 ," -w number (width)" 817 }; 818 const size_t first = 3; 819 const size_t last = sizeof(tbl)/sizeof(tbl[0]); 820 const size_t left = (last - first + 1) / 2 + first; 821 size_t n; 822 823 for (n = 0; n < left; n++) { 824 size_t m = (n < first) ? last : n + left - first; 825 if (m < last) 826 fprintf(stderr, "%-40.40s%s\n", tbl[n], tbl[m]); 827 else 828 fprintf(stderr, "%s\n", tbl[n]); 829 } 830 exit(EXIT_FAILURE); 831 } 832 833 static char * name_initializer(const char *type) 834 { 835 static char *initializer; 836 char *s; 837 838 if (initializer == 0) 839 initializer = malloc(strlen(term->term_names) + 20); 840 841 (void) sprintf(initializer, "%s_data_%s", type, term->term_names); 842 for (s = initializer; *s != 0 && *s != '|'; s++) 843 { 844 if (!isalnum(*s)) 845 *s = '_'; 846 } 847 *s = 0; 848 return initializer; 849 } 850 851 /* dump C initializers for the terminal type */ 852 static void dump_initializers(void) 853 { 854 int n; 855 const char *str = 0; 856 int size; 857 858 (void) printf("static bool %s[] = %s\n", name_initializer("bool"), L_CURL); 859 860 for_each_boolean(n,term) 861 { 862 switch((int)(term->Booleans[n])) 863 { 864 case TRUE: 865 str = "TRUE"; 866 break; 867 868 case FALSE: 869 str = "FALSE"; 870 break; 871 872 case ABSENT_BOOLEAN: 873 str = "ABSENT_BOOLEAN"; 874 break; 875 876 case CANCELLED_BOOLEAN: 877 str = "CANCELLED_BOOLEAN"; 878 break; 879 } 880 (void) printf("\t/* %3d: %-8s */\t%s,\n", 881 n, ExtBoolname(term,n,boolnames), str); 882 } 883 (void) printf("%s;\n", R_CURL); 884 885 (void) printf("static short %s[] = %s\n", name_initializer("number"), L_CURL); 886 887 for_each_number(n,term) 888 { 889 char buf[BUFSIZ]; 890 switch (term->Numbers[n]) 891 { 892 case ABSENT_NUMERIC: 893 str = "ABSENT_NUMERIC"; 894 break; 895 case CANCELLED_NUMERIC: 896 str = "CANCELLED_NUMERIC"; 897 break; 898 default: 899 sprintf(buf, "%d", term->Numbers[n]); 900 str = buf; 901 break; 902 } 903 (void) printf("\t/* %3d: %-8s */\t%s,\n", n, ExtNumname(term,n,numnames), str); 904 } 905 (void) printf("%s;\n", R_CURL); 906 907 size = sizeof(TERMTYPE) 908 + (NUM_BOOLEANS(term) * sizeof(term->Booleans[0])) 909 + (NUM_NUMBERS(term) * sizeof(term->Numbers[0])); 910 911 (void) printf("static char * %s[] = %s\n", name_initializer("string"), L_CURL); 912 913 for_each_string(n,term) 914 { 915 char buf[BUFSIZ], *sp, *tp; 916 917 if (term->Strings[n] == ABSENT_STRING) 918 str = "ABSENT_STRING"; 919 else if (term->Strings[n] == CANCELLED_STRING) 920 str = "CANCELLED_STRING"; 921 else 922 { 923 tp = buf; 924 *tp++ = '"'; 925 for (sp = term->Strings[n]; *sp; sp++) 926 { 927 if (isascii(*sp) && isprint(*sp) && *sp !='\\' && *sp != '"') 928 *tp++ = *sp; 929 else 930 { 931 (void) sprintf(tp, "\\%03o", *sp & 0xff); 932 tp += 4; 933 } 934 } 935 *tp++ = '"'; 936 *tp = '\0'; 937 size += (strlen(term->Strings[n]) + 1); 938 str = buf; 939 } 940 #if NCURSES_XNAMES 941 if (n == STRCOUNT) 942 { 943 (void) printf("%s;\n", R_CURL); 944 945 (void) printf("static char * %s[] = %s\n", name_initializer("string_ext"), L_CURL); 946 } 947 #endif 948 (void) printf("\t/* %3d: %-8s */\t%s,\n", n, ExtStrname(term,n,strnames), str); 949 } 950 (void) printf("%s;\n", R_CURL); 951 } 952 953 /* dump C initializers for the terminal type */ 954 static void dump_termtype(void) 955 { 956 (void) printf("\t%s\n\t\t\"%s\",\n", L_CURL, term->term_names); 957 (void) printf("\t\t(char *)0,\t/* pointer to string table */\n"); 958 959 (void) printf("\t\t%s,\n", name_initializer("bool")); 960 (void) printf("\t\t%s,\n", name_initializer("number")); 961 962 (void) printf("\t\t%s,\n", name_initializer("string")); 963 964 #if NCURSES_XNAMES 965 (void) printf("#if NCURSES_XNAMES\n"); 966 (void) printf("\t\t(char *)0,\t/* pointer to extended string table */\n"); 967 (void) printf("\t\t%s,\t/* ...corresponding names */\n", 968 (NUM_STRINGS(term) != STRCOUNT) 969 ? name_initializer("string_ext") 970 : "(char **)0"); 971 972 (void) printf("\t\t%d,\t\t/* count total Booleans */\n", NUM_BOOLEANS(term)); 973 (void) printf("\t\t%d,\t\t/* count total Numbers */\n", NUM_NUMBERS(term)); 974 (void) printf("\t\t%d,\t\t/* count total Strings */\n", NUM_STRINGS(term)); 975 976 (void) printf("\t\t%d,\t\t/* count extensions to Booleans */\n", NUM_BOOLEANS(term) - BOOLCOUNT); 977 (void) printf("\t\t%d,\t\t/* count extensions to Numbers */\n", NUM_NUMBERS(term) - NUMCOUNT); 978 (void) printf("\t\t%d,\t\t/* count extensions to Strings */\n", NUM_STRINGS(term) - STRCOUNT); 979 980 (void) printf("#endif /* NCURSES_XNAMES */\n"); 981 #endif /* NCURSES_XNAMES */ 982 (void) printf("\t%s\n", R_CURL); 983 } 984 985 /*************************************************************************** 986 * 987 * Main sequence 988 * 989 ***************************************************************************/ 990 991 int main(int argc, char *argv[]) 992 { 993 char *terminal, *firstdir, *restdir; 994 /* Avoid "local data >32k" error with mwcc */ 995 /* Also avoid overflowing smaller stacks on systems like AmigaOS */ 996 path *tfile = malloc(sizeof(path)*MAXTERMS); 997 int c, i, len; 998 bool formatted = FALSE; 999 bool filecompare = FALSE; 1000 int initdump = 0; 1001 bool init_analyze = FALSE; 1002 bool limited = TRUE; 1003 1004 if ((terminal = getenv("TERM")) == NULL) 1005 { 1006 (void) fprintf(stderr, 1007 "infocmp: environment variable TERM not set\n"); 1008 return EXIT_FAILURE; 1009 } 1010 1011 /* where is the terminfo database location going to default to? */ 1012 restdir = firstdir = 0; 1013 1014 while ((c = getopt(argc, argv, "deEcCfFGgIinlLprR:s:uv:Vw:A:B:1T")) != EOF) 1015 switch (c) 1016 { 1017 case 'd': 1018 compare = C_DIFFERENCE; 1019 break; 1020 1021 case 'e': 1022 initdump |= 1; 1023 break; 1024 1025 case 'E': 1026 initdump |= 2; 1027 break; 1028 1029 case 'c': 1030 compare = C_COMMON; 1031 break; 1032 1033 case 'C': 1034 outform = F_TERMCAP; 1035 tversion = "BSD"; 1036 if (sortmode == S_DEFAULT) 1037 sortmode = S_TERMCAP; 1038 break; 1039 1040 case 'f': 1041 formatted = TRUE; 1042 break; 1043 1044 case 'G': 1045 numbers = 1; 1046 break; 1047 1048 case 'g': 1049 numbers = -1; 1050 break; 1051 1052 case 'F': 1053 filecompare = TRUE; 1054 break; 1055 1056 case 'I': 1057 outform = F_TERMINFO; 1058 if (sortmode == S_DEFAULT) 1059 sortmode = S_VARIABLE; 1060 tversion = 0; 1061 break; 1062 1063 case 'i': 1064 init_analyze = TRUE; 1065 break; 1066 1067 case 'l': 1068 outform = F_TERMINFO; 1069 break; 1070 1071 case 'L': 1072 outform = F_VARIABLE; 1073 if (sortmode == S_DEFAULT) 1074 sortmode = S_VARIABLE; 1075 break; 1076 1077 case 'n': 1078 compare = C_NAND; 1079 break; 1080 1081 case 'p': 1082 ignorepads = TRUE; 1083 break; 1084 1085 case 'r': 1086 tversion = 0; 1087 limited = FALSE; 1088 break; 1089 1090 case 'R': 1091 tversion = optarg; 1092 break; 1093 1094 case 's': 1095 if (*optarg == 'd') 1096 sortmode = S_NOSORT; 1097 else if (*optarg == 'i') 1098 sortmode = S_TERMINFO; 1099 else if (*optarg == 'l') 1100 sortmode = S_VARIABLE; 1101 else if (*optarg == 'c') 1102 sortmode = S_TERMCAP; 1103 else 1104 { 1105 (void) fprintf(stderr, 1106 "infocmp: unknown sort mode\n"); 1107 return EXIT_FAILURE; 1108 } 1109 break; 1110 1111 case 'u': 1112 compare = C_USEALL; 1113 break; 1114 1115 case 'v': 1116 itrace = atoi(optarg); 1117 _nc_tracing = (1 << itrace) - 1; 1118 break; 1119 1120 case 'V': 1121 (void) fputs(NCURSES_VERSION, stdout); 1122 putchar('\n'); 1123 ExitProgram(EXIT_SUCCESS); 1124 1125 case 'w': 1126 mwidth = atoi(optarg); 1127 break; 1128 1129 case 'A': 1130 firstdir = optarg; 1131 break; 1132 1133 case 'B': 1134 restdir = optarg; 1135 break; 1136 1137 case '1': 1138 mwidth = 0; 1139 break; 1140 1141 case 'T': 1142 limited = FALSE; 1143 break; 1144 default: 1145 usage(); 1146 } 1147 1148 /* by default, sort by terminfo name */ 1149 if (sortmode == S_DEFAULT) 1150 sortmode = S_TERMINFO; 1151 1152 /* set up for display */ 1153 dump_init(tversion, outform, sortmode, mwidth, itrace, formatted); 1154 1155 /* make sure we have at least one terminal name to work with */ 1156 if (optind >= argc) 1157 argv[argc++] = terminal; 1158 1159 /* if user is after a comparison, make sure we have two entries */ 1160 if (compare != C_DEFAULT && optind >= argc - 1) 1161 argv[argc++] = terminal; 1162 1163 /* exactly two terminal names with no options means do -d */ 1164 if (argc - optind == 2 && compare == C_DEFAULT) 1165 compare = C_DIFFERENCE; 1166 1167 if (!filecompare) 1168 { 1169 /* grab the entries */ 1170 termcount = 0; 1171 for (; optind < argc; optind++) 1172 { 1173 if (termcount >= MAXTERMS) 1174 { 1175 (void) fprintf(stderr, 1176 "infocmp: too many terminal type arguments\n"); 1177 return EXIT_FAILURE; 1178 } 1179 else 1180 { 1181 const char *directory = termcount ? restdir : firstdir; 1182 int status; 1183 1184 tname[termcount] = argv[optind]; 1185 1186 if (directory) 1187 { 1188 (void) sprintf(tfile[termcount], "%s/%c/%s", 1189 directory, 1190 *argv[optind], argv[optind]); 1191 if (itrace) 1192 (void) fprintf(stderr, 1193 "infocmp: reading entry %s from file %s\n", 1194 argv[optind], tfile[termcount]); 1195 1196 status = _nc_read_file_entry(tfile[termcount], 1197 &term[termcount]); 1198 } 1199 else 1200 { 1201 if (itrace) 1202 (void) fprintf(stderr, 1203 "infocmp: reading entry %s from system directories %s\n", 1204 argv[optind], tname[termcount]); 1205 1206 status = _nc_read_entry(tname[termcount], 1207 tfile[termcount], 1208 &term[termcount]); 1209 directory = TERMINFO; /* for error message */ 1210 } 1211 1212 if (status <= 0) 1213 { 1214 (void) fprintf(stderr, 1215 "infocmp: couldn't open terminfo file %s.\n", 1216 tfile[termcount]); 1217 return EXIT_FAILURE; 1218 } 1219 termcount++; 1220 } 1221 } 1222 1223 #if NCURSES_XNAMES 1224 if (termcount > 1) 1225 _nc_align_termtype(&term[0], &term[1]); 1226 #endif 1227 1228 /* dump as C initializer for the terminal type */ 1229 if (initdump) 1230 { 1231 if (initdump & 1) 1232 dump_termtype(); 1233 if (initdump & 2) 1234 dump_initializers(); 1235 ExitProgram(EXIT_SUCCESS); 1236 } 1237 1238 /* analyze the init strings */ 1239 if (init_analyze) 1240 { 1241 #undef CUR 1242 #define CUR term[0]. 1243 analyze_string("is1", init_1string, &term[0]); 1244 analyze_string("is2", init_2string, &term[0]); 1245 analyze_string("is3", init_3string, &term[0]); 1246 analyze_string("rs1", reset_1string, &term[0]); 1247 analyze_string("rs2", reset_2string, &term[0]); 1248 analyze_string("rs3", reset_3string, &term[0]); 1249 analyze_string("smcup", enter_ca_mode, &term[0]); 1250 analyze_string("rmcup", exit_ca_mode, &term[0]); 1251 #undef CUR 1252 ExitProgram(EXIT_SUCCESS); 1253 } 1254 1255 /* 1256 * Here's where the real work gets done 1257 */ 1258 switch (compare) 1259 { 1260 case C_DEFAULT: 1261 if (itrace) 1262 (void) fprintf(stderr, 1263 "infocmp: about to dump %s\n", 1264 tname[0]); 1265 (void) printf("#\tReconstructed via infocmp from file: %s\n", 1266 tfile[0]); 1267 len = dump_entry(&term[0], limited, numbers, NULL); 1268 putchar('\n'); 1269 if (itrace) 1270 (void)fprintf(stderr, "infocmp: length %d\n", len); 1271 break; 1272 1273 case C_DIFFERENCE: 1274 if (itrace) 1275 (void)fprintf(stderr, "infocmp: dumping differences\n"); 1276 (void) printf("comparing %s to %s.\n", tname[0], tname[1]); 1277 compare_entry(compare_predicate, term); 1278 break; 1279 1280 case C_COMMON: 1281 if (itrace) 1282 (void) fprintf(stderr, 1283 "infocmp: dumping common capabilities\n"); 1284 (void) printf("comparing %s to %s.\n", tname[0], tname[1]); 1285 compare_entry(compare_predicate, term); 1286 break; 1287 1288 case C_NAND: 1289 if (itrace) 1290 (void) fprintf(stderr, 1291 "infocmp: dumping differences\n"); 1292 (void) printf("comparing %s to %s.\n", tname[0], tname[1]); 1293 compare_entry(compare_predicate, term); 1294 break; 1295 1296 case C_USEALL: 1297 if (itrace) 1298 (void) fprintf(stderr, "infocmp: dumping use entry\n"); 1299 len = dump_entry(&term[0], limited, numbers, use_predicate); 1300 for (i = 1; i < termcount; i++) 1301 len += dump_uses(tname[i], !(outform==F_TERMCAP || outform==F_TCONVERR)); 1302 putchar('\n'); 1303 if (itrace) 1304 (void)fprintf(stderr, "infocmp: length %d\n", len); 1305 break; 1306 } 1307 } 1308 else if (compare == C_USEALL) 1309 (void) fprintf(stderr, "Sorry, -u doesn't work with -F\n"); 1310 else if (compare == C_DEFAULT) 1311 (void) fprintf(stderr, "Use `tic -[CI] <file>' for this.\n"); 1312 else if (argc - optind != 2) 1313 (void) fprintf(stderr, 1314 "File comparison needs exactly two file arguments.\n"); 1315 else 1316 file_comparison(argc-optind, argv+optind); 1317 1318 ExitProgram(EXIT_SUCCESS); 1319 } 1320 1321 /* infocmp.c ends here */ 1322