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