1 /* $NetBSD: infocmp.c,v 1.8 2013/10/01 09:01:49 roy Exp $ */ 2 3 /* 4 * Copyright (c) 2009, 2010 The NetBSD Foundation, Inc. 5 * 6 * This code is derived from software contributed to The NetBSD Foundation 7 * by Roy Marples. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 21 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 23 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 */ 29 30 #include <sys/cdefs.h> 31 __RCSID("$NetBSD: infocmp.c,v 1.8 2013/10/01 09:01:49 roy Exp $"); 32 33 #include <sys/ioctl.h> 34 35 #include <ctype.h> 36 #include <err.h> 37 #include <stdio.h> 38 #include <stdlib.h> 39 #include <string.h> 40 #include <term_private.h> 41 #include <term.h> 42 #include <unistd.h> 43 44 #define SW 8 45 46 typedef struct tient { 47 char type; 48 const char *id; 49 signed char flag; 50 short num; 51 const char *str; 52 } TIENT; 53 54 static size_t cols; 55 static int aflag, cflag, nflag, qflag, xflag; 56 57 static size_t 58 outstr(FILE *f, const char *str) 59 { 60 unsigned char ch; 61 size_t r, l; 62 63 r = 0; 64 l = strlen(str); 65 while ((ch = (unsigned char)(*str++)) != '\0') { 66 switch (ch) { 67 case 128: 68 ch = '0'; 69 break; 70 case '\033': 71 ch = 'E'; 72 break; 73 case '\014': 74 ch = 'f'; 75 break; 76 case '^': /* FALLTHROUGH */ 77 case ',': /* escape these */ 78 break; 79 case ' ': 80 ch = 's'; 81 break; 82 default: 83 if (ch == '\177') { 84 if (f != NULL) 85 fputc('^', f); 86 ch = '?'; 87 r++; 88 } else if (iscntrl(ch) && 89 ch < 128 && 90 ch != '\\' && 91 (l < 4 || isdigit((unsigned char)*str))) 92 { 93 if (f != NULL) 94 fputc('^', f); 95 ch += '@'; 96 r++; 97 } else if (!isprint(ch)) { 98 if (f != NULL) 99 fprintf(f, "\\%03o", ch); 100 r += 4; 101 continue; 102 } 103 goto prnt; 104 } 105 106 if (f != NULL) 107 fputc('\\', f); 108 r++; 109 prnt: 110 if (f != NULL) 111 fputc(ch, f); 112 r++; 113 } 114 return r; 115 } 116 117 static int 118 ent_compare(const void *a, const void *b) 119 { 120 const TIENT *ta, *tb; 121 122 ta = (const TIENT *)a; 123 tb = (const TIENT *)b; 124 return strcmp(ta->id, tb->id); 125 } 126 127 static void 128 setdb(char *db) 129 { 130 size_t len; 131 132 len = strlen(db); 133 if (len > 3 && 134 db[len - 3] == '.' && 135 db[len - 2] == 'd' && 136 db[len - 1] == 'b') 137 db[len - 3] = '\0'; 138 setenv("TERMINFO", db, 1); 139 } 140 141 static void 142 print_ent(const TIENT *ents, size_t nents) 143 { 144 size_t col, i, l; 145 char nbuf[64]; 146 147 if (nents == 0) 148 return; 149 150 col = SW; 151 printf("\t"); 152 for (i = 0; i < nents; i++) { 153 if (*ents[i].id == '.' && aflag == 0) 154 continue; 155 switch (ents[i].type) { 156 case 'f': 157 if (ents[i].flag == ABSENT_BOOLEAN) 158 continue; 159 l = strlen(ents[i].id) + 2; 160 if (ents[i].flag == CANCELLED_BOOLEAN) 161 l++; 162 break; 163 case 'n': 164 if (ents[i].num == ABSENT_NUMERIC) 165 continue; 166 if (VALID_NUMERIC(ents[i].num)) 167 l = snprintf(nbuf, sizeof(nbuf), "%s#%d,", 168 ents[i].id, ents[i].num); 169 else 170 l = snprintf(nbuf, sizeof(nbuf), "%s@,", 171 ents[i].id); 172 break; 173 case 's': 174 if (ents[i].str == ABSENT_STRING) 175 continue; 176 if (VALID_STRING(ents[i].str)) 177 l = strlen(ents[i].id) + 178 outstr(NULL, ents[i].str) + 7; 179 else 180 l = strlen(ents[i].id) + 3; 181 break; 182 default: 183 errx(1, "invalid type"); 184 } 185 if (col != SW) { 186 if (col + l > cols) { 187 printf("\n\t"); 188 col = SW; 189 } else 190 col += printf(" "); 191 } 192 switch (ents[i].type) { 193 case 'f': 194 col += printf("%s", ents[i].id); 195 if (ents[i].flag == ABSENT_BOOLEAN || 196 ents[i].flag == CANCELLED_BOOLEAN) 197 col += printf("@"); 198 col += printf(","); 199 break; 200 case 'n': 201 col += printf("%s", nbuf); 202 break; 203 case 's': 204 col += printf("%s", ents[i].id); 205 if (VALID_STRING(ents[i].str)) { 206 col += printf("="); 207 col += outstr(stdout, ents[i].str); 208 } else 209 col += printf("@"); 210 col += printf(","); 211 break; 212 } 213 } 214 printf("\n"); 215 } 216 217 static size_t 218 load_ents(TIENT *ents, TERMINAL *t, char type) 219 { 220 size_t i, n, max; 221 TERMUSERDEF *ud; 222 223 switch (type) { 224 case 'f': 225 max = TIFLAGMAX; 226 break; 227 case 'n': 228 max = TINUMMAX; 229 break; 230 default: 231 max = TISTRMAX; 232 } 233 234 n = 0; 235 for (i = 0; i <= max; i++) { 236 switch (type) { 237 case 'f': 238 if (t->flags[i] == 1 || 239 (aflag && t->flags[i] == CANCELLED_BOOLEAN)) 240 { 241 ents[n].id = _ti_flagid(i); 242 ents[n].type = 'f'; 243 ents[n++].flag = t->flags[i]; 244 } 245 break; 246 case 'n': 247 if (VALID_NUMERIC(t->nums[i]) || 248 (aflag && t->nums[i] == CANCELLED_NUMERIC)) 249 { 250 ents[n].id = _ti_numid(i); 251 ents[n].type = 'n'; 252 ents[n++].num = t->nums[i]; 253 } 254 break; 255 default: 256 if (VALID_STRING(t->strs[i]) || 257 (aflag && t->strs[i] == CANCELLED_STRING)) 258 { 259 ents[n].id = _ti_strid(i); 260 ents[n].type = 's'; 261 ents[n++].str = t->strs[i]; 262 } 263 break; 264 } 265 } 266 267 if (xflag != 0 && t->_nuserdefs != 0) { 268 for (i = 0; i < t->_nuserdefs; i++) { 269 ud = &t->_userdefs[i]; 270 if (ud->type == type) { 271 switch (type) { 272 case 'f': 273 if (!aflag && 274 !VALID_BOOLEAN(ud->flag)) 275 continue; 276 break; 277 case 'n': 278 if (!aflag && 279 !VALID_NUMERIC(ud->num)) 280 continue; 281 break; 282 case 's': 283 if (!aflag && 284 !VALID_STRING(ud->str)) 285 continue; 286 break; 287 } 288 ents[n].id = ud->id; 289 ents[n].type = ud->type; 290 ents[n].flag = ud->flag; 291 ents[n].num = ud->num; 292 ents[n++].str = ud->str; 293 } 294 } 295 } 296 297 qsort(ents, n, sizeof(TIENT), ent_compare); 298 return n; 299 } 300 301 static void 302 cprint_ent(TIENT *ent) 303 { 304 305 if (ent == NULL) { 306 if (qflag == 0) 307 printf("NULL"); 308 else 309 printf("-"); 310 } 311 312 switch (ent->type) { 313 case 'f': 314 if (VALID_BOOLEAN(ent->flag)) 315 printf(ent->flag == 1 ? "T" : "F"); 316 else if (qflag == 0) 317 printf("F"); 318 else if (ent->flag == CANCELLED_BOOLEAN) 319 printf("@"); 320 else 321 printf("-"); 322 break; 323 case 'n': 324 if (VALID_NUMERIC(ent->num)) 325 printf("%d", ent->num); 326 else if (qflag == 0) 327 printf("NULL"); 328 else if (ent->num == CANCELLED_NUMERIC) 329 printf("@"); 330 else 331 printf("-"); 332 break; 333 case 's': 334 if (VALID_STRING(ent->str)) { 335 printf("'"); 336 outstr(stdout, ent->str); 337 printf("'"); 338 } else if (qflag == 0) 339 printf("NULL"); 340 else if (ent->str == CANCELLED_STRING) 341 printf("@"); 342 else 343 printf("-"); 344 break; 345 } 346 } 347 348 static void 349 compare_ents(TIENT *ents1, size_t n1, TIENT *ents2, size_t n2) 350 { 351 size_t i1, i2; 352 TIENT *e1, *e2, ee; 353 int c; 354 355 i1 = i2 = 0; 356 ee.type = 'f'; 357 ee.flag = ABSENT_BOOLEAN; 358 ee.num = ABSENT_NUMERIC; 359 ee.str = ABSENT_STRING; 360 while (i1 != n1 || i2 != n2) { 361 if (i1 == n1) 362 c = 1; 363 else if (i2 == n2) 364 c = -1; 365 else 366 c = strcmp(ents1[i1].id, ents2[i2].id); 367 if (c == 0) { 368 e1 = &ents1[i1++]; 369 e2 = &ents2[i2++]; 370 } else if (c < 0) { 371 e1 = &ents1[i1++]; 372 e2 = ⅇ 373 ee.id = e1->id; 374 ee.type = e1->type; 375 } else { 376 e1 = ⅇ 377 e2 = &ents2[i2++]; 378 ee.id = e2->id; 379 ee.type = e2->type; 380 } 381 switch (e1->type) { 382 case 'f': 383 if (cflag != 0) { 384 if (e1->flag == e2->flag) 385 printf("\t%s\n", ents1[i1].id); 386 continue; 387 } 388 if (e1->flag == e2->flag) 389 continue; 390 break; 391 case 'n': 392 if (cflag != 0) { 393 if (e1->num == e2->num) 394 printf("\t%s#%d\n", 395 ents1[i1].id, ents1[i1].num); 396 continue; 397 } 398 if (e1->num == e2->num) 399 continue; 400 break; 401 case 's': 402 if (cflag != 0) { 403 if (VALID_STRING(e1->str) && 404 VALID_STRING(e2->str) && 405 strcmp(e1->str, e2->str) == 0) { 406 printf("\t%s=", ents1[i1].id); 407 outstr(stdout, ents1[i1].str); 408 printf("\n"); 409 } 410 continue; 411 } 412 if (VALID_STRING(e1->str) && 413 VALID_STRING(e2->str) && 414 strcmp(e1->str, e2->str) == 0) 415 continue; 416 break; 417 } 418 printf("\t%s: ", e1->id); 419 cprint_ent(e1); 420 if (e1->type == 'f') 421 printf(":"); 422 else 423 printf(", "); 424 cprint_ent(e2); 425 printf(".\n"); 426 } 427 } 428 429 static TERMINAL * 430 load_term(const char *name) 431 { 432 TERMINAL *t; 433 434 t = calloc(1, sizeof(*t)); 435 if (t == NULL) 436 err(1, "calloc"); 437 if (name == NULL) 438 name = getenv("TERM"); 439 if (name == NULL) 440 name = "dumb"; 441 if (_ti_getterm(t, name, 1) == 1) 442 return t; 443 444 if (_ti_database == NULL) 445 errx(1, "no terminal definition found in internal database"); 446 else 447 errx(1, "no terminal definition found in %s.db", _ti_database); 448 } 449 450 static void 451 show_missing(TERMINAL *t1, TERMINAL *t2, char type) 452 { 453 ssize_t i, max; 454 const char *id; 455 456 switch (type) { 457 case 'f': 458 max = TIFLAGMAX; 459 break; 460 case 'n': 461 max = TINUMMAX; 462 break; 463 default: 464 max = TISTRMAX; 465 } 466 467 for (i = 0; i <= max; i++) { 468 switch (type) { 469 case 'f': 470 if (t1->flags[i] != ABSENT_BOOLEAN || 471 t2->flags[i] != ABSENT_BOOLEAN) 472 continue; 473 id = _ti_flagid(i); 474 break; 475 case 'n': 476 if (t1->nums[i] != ABSENT_NUMERIC || 477 t2->nums[i] != ABSENT_NUMERIC) 478 continue; 479 id = _ti_numid(i); 480 break; 481 default: 482 if (t1->strs[i] != ABSENT_STRING || 483 t2->strs[i] != ABSENT_STRING) 484 continue; 485 id = _ti_strid(i); 486 break; 487 } 488 printf("\t!%s.\n", id); 489 } 490 } 491 492 static TERMUSERDEF * 493 find_userdef(TERMINAL *term, const char *id) 494 { 495 size_t i; 496 497 for (i = 0; i < term->_nuserdefs; i++) 498 if (strcmp(term->_userdefs[i].id, id) == 0) 499 return &term->_userdefs[i]; 500 return NULL; 501 } 502 503 static void 504 use_terms(TERMINAL *term, size_t nuse, char **uterms) 505 { 506 TERMINAL **terms; 507 TERMUSERDEF *ud, *tud; 508 size_t i, j, agree, absent, data; 509 510 terms = malloc(sizeof(**terms) * nuse); 511 if (terms == NULL) 512 err(1, "malloc"); 513 for (i = 0; i < nuse; i++) { 514 if (strcmp(term->name, *uterms) == 0) 515 errx(1, "cannot use same terminal"); 516 for (j = 0; j < i; j++) 517 if (strcmp(terms[j]->name, *uterms) == 0) 518 errx(1, "cannot use same terminal"); 519 terms[i] = load_term(*uterms++); 520 } 521 522 for (i = 0; i < TIFLAGMAX + 1; i++) { 523 agree = absent = data = 0; 524 for (j = 0; j < nuse; j++) { 525 if (terms[j]->flags[i] == ABSENT_BOOLEAN || 526 terms[j]->flags[i] == CANCELLED_BOOLEAN) 527 absent++; 528 else { 529 data++; 530 if (term->flags[i] == terms[j]->flags[i]) 531 agree++; 532 } 533 } 534 if (data == 0) 535 continue; 536 if (agree > 0 && agree + absent == nuse) 537 term->flags[i] = ABSENT_BOOLEAN; 538 else if (term->flags[i] == ABSENT_BOOLEAN) 539 term->flags[i] = CANCELLED_BOOLEAN; 540 } 541 542 for (i = 0; i < TINUMMAX + 1; i++) { 543 agree = absent = data = 0; 544 for (j = 0; j < nuse; j++) { 545 if (terms[j]->nums[i] == ABSENT_NUMERIC || 546 terms[j]->nums[i] == CANCELLED_NUMERIC) 547 absent++; 548 else { 549 data++; 550 if (term->nums[i] == terms[j]->nums[i]) 551 agree++; 552 } 553 } 554 if (data == 0) 555 continue; 556 if (agree > 0 && agree + absent == nuse) 557 term->nums[i] = ABSENT_NUMERIC; 558 else if (term->nums[i] == ABSENT_NUMERIC) 559 term->nums[i] = CANCELLED_NUMERIC; 560 } 561 562 for (i = 0; i < TISTRMAX + 1; i++) { 563 agree = absent = data = 0; 564 for (j = 0; j < nuse; j++) { 565 if (terms[j]->strs[i] == ABSENT_STRING || 566 terms[j]->strs[i] == CANCELLED_STRING) 567 absent++; 568 else { 569 data++; 570 if (VALID_STRING(term->strs[i]) && 571 strcmp(term->strs[i], 572 terms[j]->strs[i]) == 0) 573 agree++; 574 } 575 } 576 if (data == 0) 577 continue; 578 if (agree > 0 && agree + absent == nuse) 579 term->strs[i] = ABSENT_STRING; 580 else if (term->strs[i] == ABSENT_STRING) 581 term->strs[i] = CANCELLED_STRING; 582 } 583 584 /* User defined caps are more tricky. 585 First we set any to absent that agree. */ 586 for (i = 0; i < term->_nuserdefs; i++) { 587 agree = absent = data = 0; 588 ud = &term->_userdefs[i]; 589 for (j = 0; j < nuse; j++) { 590 tud = find_userdef(terms[j], ud->id); 591 if (tud == NULL) 592 absent++; 593 else { 594 data++; 595 switch (ud->type) { 596 case 'f': 597 if (tud->type == 'f' && 598 tud->flag == ud->flag) 599 agree++; 600 break; 601 case 'n': 602 if (tud->type == 'n' && 603 tud->num == ud->num) 604 agree++; 605 break; 606 case 's': 607 if (tud->type == 's' && 608 VALID_STRING(tud->str) && 609 VALID_STRING(ud->str) && 610 strcmp(ud->str, tud->str) == 0) 611 agree++; 612 break; 613 } 614 } 615 } 616 if (data == 0) 617 continue; 618 if (agree > 0 && agree + absent == nuse) { 619 ud->flag = ABSENT_BOOLEAN; 620 ud->num = ABSENT_NUMERIC; 621 ud->str = ABSENT_STRING; 622 } 623 } 624 625 /* Now add any that we don't have as cancelled */ 626 for (i = 0; i < nuse; i++) { 627 for (j = 0; j < terms[i]->_nuserdefs; j++) { 628 ud = find_userdef(term, terms[i]->_userdefs[j].id); 629 if (ud != NULL) 630 continue; /* We have handled this */ 631 term->_userdefs = realloc(term->_userdefs, 632 sizeof(*term->_userdefs) * (term->_nuserdefs + 1)); 633 if (term->_userdefs == NULL) 634 err(1, "malloc"); 635 tud = &term->_userdefs[term->_nuserdefs++]; 636 tud->id = terms[i]->_userdefs[j].id; 637 tud->type = terms[i]->_userdefs[j].flag; 638 tud->flag = CANCELLED_BOOLEAN; 639 tud->num = CANCELLED_NUMERIC; 640 tud->str = CANCELLED_STRING; 641 } 642 } 643 } 644 645 int 646 main(int argc, char **argv) 647 { 648 char *term, *Barg; 649 int ch, uflag; 650 TERMINAL *t, *t2; 651 size_t n, n2; 652 struct winsize ws; 653 TIENT ents[TISTRMAX + 1], ents2[TISTRMAX + 1]; 654 655 cols = 80; /* default */ 656 term = getenv("COLUMNS"); 657 if (term != NULL) 658 cols = strtoul(term, NULL, 10); 659 else if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0) 660 cols = ws.ws_col; 661 662 uflag = xflag = 0; 663 Barg = NULL; 664 while ((ch = getopt(argc, argv, "1A:B:acnquw:x")) != -1) 665 switch (ch) { 666 case '1': 667 cols = 1; 668 break; 669 case 'A': 670 setdb(optarg); 671 break; 672 case 'B': 673 Barg = optarg; 674 break; 675 case 'a': 676 aflag = 1; 677 break; 678 case 'c': 679 cflag = 1; 680 break; 681 case 'n': 682 nflag = 1; 683 break; 684 case 'q': 685 qflag = 1; 686 break; 687 case 'u': 688 uflag = 1; 689 aflag = 1; 690 break; 691 case 'w': 692 cols = strtoul(optarg, NULL, 10); 693 break; 694 case 'x': 695 xflag = 1; 696 break; 697 case '?': 698 default: 699 fprintf(stderr, 700 "usage: %s [-1acnqux] [-A database] [-B database] " 701 "[-w cols] [term]\n", 702 getprogname()); 703 return EXIT_FAILURE; 704 } 705 cols--; 706 707 if (optind + 1 < argc) 708 aflag = 1; 709 710 if (optind < argc) 711 term = argv[optind++]; 712 else 713 term = NULL; 714 t = load_term(term); 715 716 if (uflag != 0) 717 use_terms(t, argc - optind, argv + optind); 718 719 if ((optind + 1 != argc && nflag == 0) || uflag != 0) { 720 if (uflag == 0) { 721 printf("# Reconstructed from "); 722 if (_ti_database == NULL) 723 printf("internal database\n"); 724 else 725 printf("%s%s\n", _ti_database, 726 *_ti_database == '/' ? ".cdb" : ""); 727 } 728 printf("%s", t->name); 729 if (t->_alias != NULL && *t->_alias != '\0') 730 printf("|%s", t->_alias); 731 if (t->desc != NULL && *t->desc != '\0') 732 printf("|%s", t->desc); 733 printf(",\n"); 734 735 n = load_ents(ents, t, 'f'); 736 print_ent(ents, n); 737 n = load_ents(ents, t, 'n'); 738 print_ent(ents, n); 739 n = load_ents(ents, t, 's'); 740 print_ent(ents, n); 741 742 if (uflag != 0) { 743 printf("\t"); 744 n = SW; 745 for (; optind < argc; optind++) { 746 n2 = 5 + strlen(argv[optind]); 747 if (n != SW) { 748 if (n + n2 > cols) { 749 printf("\n\t"); 750 n = SW; 751 } else 752 n += printf(" "); 753 } 754 n += printf("use=%s,", argv[optind]); 755 } 756 printf("\n"); 757 } 758 return EXIT_SUCCESS; 759 } 760 761 if (Barg == NULL) 762 unsetenv("TERMINFO"); 763 else 764 setdb(Barg); 765 t2 = load_term(argv[optind++]); 766 printf("comparing %s to %s.\n", t->name, t2->name); 767 if (qflag == 0) 768 printf(" comparing booleans.\n"); 769 if (nflag == 0) { 770 n = load_ents(ents, t, 'f'); 771 n2 = load_ents(ents2, t2, 'f'); 772 compare_ents(ents, n, ents2, n2); 773 } else 774 show_missing(t, t2, 'f'); 775 if (qflag == 0) 776 printf(" comparing numbers.\n"); 777 if (nflag == 0) { 778 n = load_ents(ents, t, 'n'); 779 n2 = load_ents(ents2, t2, 'n'); 780 compare_ents(ents, n, ents2, n2); 781 } else 782 show_missing(t, t2, 'n'); 783 if (qflag == 0) 784 printf(" comparing strings.\n"); 785 if (nflag == 0) { 786 n = load_ents(ents, t, 's'); 787 n2 = load_ents(ents2, t2, 's'); 788 compare_ents(ents, n, ents2, n2); 789 } else 790 show_missing(t, t2, 's'); 791 return EXIT_SUCCESS; 792 } 793