1 /* $NetBSD: infocmp.c,v 1.4 2010/02/05 16:43:46 roy Exp $ */ 2 3 /* 4 * Copyright (c) 2009 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.4 2010/02/05 16:43:46 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) /* load the raw data */ 442 errx(1, "no terminal definition found in %s.db", _ti_database); 443 return t; 444 } 445 446 static void 447 show_missing(TERMINAL *t1, TERMINAL *t2, char type) 448 { 449 ssize_t i, max; 450 const char *id; 451 452 switch (type) { 453 case 'f': 454 max = TIFLAGMAX; 455 break; 456 case 'n': 457 max = TINUMMAX; 458 break; 459 default: 460 max = TISTRMAX; 461 } 462 463 for (i = 0; i <= max; i++) { 464 switch (type) { 465 case 'f': 466 if (t1->flags[i] != ABSENT_BOOLEAN || 467 t2->flags[i] != ABSENT_BOOLEAN) 468 continue; 469 id = _ti_flagid(i); 470 break; 471 case 'n': 472 if (t1->nums[i] != ABSENT_NUMERIC || 473 t2->nums[i] != ABSENT_NUMERIC) 474 continue; 475 id = _ti_numid(i); 476 break; 477 default: 478 if (t1->strs[i] != ABSENT_STRING || 479 t2->strs[i] != ABSENT_STRING) 480 continue; 481 id = _ti_strid(i); 482 break; 483 } 484 printf("\t!%s.\n", id); 485 } 486 } 487 488 static TERMUSERDEF * 489 find_userdef(TERMINAL *term, const char *id) 490 { 491 size_t i; 492 493 for (i = 0; i < term->_nuserdefs; i++) 494 if (strcmp(term->_userdefs[i].id, id) == 0) 495 return &term->_userdefs[i]; 496 return NULL; 497 } 498 499 static void 500 use_terms(TERMINAL *term, size_t nuse, char **uterms) 501 { 502 TERMINAL **terms; 503 TERMUSERDEF *ud, *tud; 504 size_t i, j, agree, absent, data; 505 506 terms = malloc(sizeof(**terms) * nuse); 507 if (terms == NULL) 508 err(1, "malloc"); 509 for (i = 0; i < nuse; i++) { 510 if (strcmp(term->name, *uterms) == 0) 511 errx(1, "cannot use same terminal"); 512 for (j = 0; j < i; j++) 513 if (strcmp(terms[j]->name, *uterms) == 0) 514 errx(1, "cannot use same terminal"); 515 terms[i] = load_term(*uterms++); 516 } 517 518 for (i = 0; i < TIFLAGMAX + 1; i++) { 519 agree = absent = data = 0; 520 for (j = 0; j < nuse; j++) { 521 if (terms[j]->flags[i] == ABSENT_BOOLEAN || 522 terms[j]->flags[i] == CANCELLED_BOOLEAN) 523 absent++; 524 else { 525 data++; 526 if (term->flags[i] == terms[j]->flags[i]) 527 agree++; 528 } 529 } 530 if (data == 0) 531 continue; 532 if (agree > 0 && agree + absent == nuse) 533 term->flags[i] = ABSENT_BOOLEAN; 534 else if (term->flags[i] == ABSENT_BOOLEAN) 535 term->flags[i] = CANCELLED_BOOLEAN; 536 } 537 538 for (i = 0; i < TINUMMAX + 1; i++) { 539 agree = absent = data = 0; 540 for (j = 0; j < nuse; j++) { 541 if (terms[j]->nums[i] == ABSENT_NUMERIC || 542 terms[j]->nums[i] == CANCELLED_NUMERIC) 543 absent++; 544 else { 545 data++; 546 if (term->nums[i] == terms[j]->nums[i]) 547 agree++; 548 } 549 } 550 if (data == 0) 551 continue; 552 if (agree > 0 && agree + absent == nuse) 553 term->nums[i] = ABSENT_NUMERIC; 554 else if (term->nums[i] == ABSENT_NUMERIC) 555 term->nums[i] = CANCELLED_NUMERIC; 556 } 557 558 for (i = 0; i < TISTRMAX + 1; i++) { 559 agree = absent = data = 0; 560 for (j = 0; j < nuse; j++) { 561 if (terms[j]->strs[i] == ABSENT_STRING || 562 terms[j]->strs[i] == CANCELLED_STRING) 563 absent++; 564 else { 565 data++; 566 if (VALID_STRING(term->strs[i]) && 567 strcmp(term->strs[i], 568 terms[j]->strs[i]) == 0) 569 agree++; 570 } 571 } 572 if (data == 0) 573 continue; 574 if (agree > 0 && agree + absent == nuse) 575 term->strs[i] = ABSENT_STRING; 576 else if (term->strs[i] == ABSENT_STRING) 577 term->strs[i] = CANCELLED_STRING; 578 } 579 580 /* User defined caps are more tricky. 581 First we set any to absent that agree. */ 582 for (i = 0; i < term->_nuserdefs; i++) { 583 agree = absent = data = 0; 584 ud = &term->_userdefs[i]; 585 for (j = 0; j < nuse; j++) { 586 tud = find_userdef(terms[j], ud->id); 587 if (tud == NULL) 588 absent++; 589 else { 590 data++; 591 switch (ud->type) { 592 case 'f': 593 if (tud->type == 'f' && 594 tud->flag == ud->flag) 595 agree++; 596 break; 597 case 'n': 598 if (tud->type == 'n' && 599 tud->num == ud->num) 600 agree++; 601 break; 602 case 's': 603 if (tud->type == 's' && 604 VALID_STRING(tud->str) && 605 VALID_STRING(ud->str) && 606 strcmp(ud->str, tud->str) == 0) 607 agree++; 608 break; 609 } 610 } 611 } 612 if (data == 0) 613 continue; 614 if (agree > 0 && agree + absent == nuse) { 615 ud->flag = ABSENT_BOOLEAN; 616 ud->num = ABSENT_NUMERIC; 617 ud->str = ABSENT_STRING; 618 } 619 } 620 621 /* Now add any that we don't have as cancelled */ 622 for (i = 0; i < nuse; i++) { 623 for (j = 0; j < terms[i]->_nuserdefs; j++) { 624 ud = find_userdef(term, terms[i]->_userdefs[j].id); 625 if (ud != NULL) 626 continue; /* We have handled this */ 627 term->_userdefs = realloc(term->_userdefs, 628 sizeof(*term->_userdefs) * (term->_nuserdefs + 1)); 629 if (term->_userdefs == NULL) 630 err(1, "malloc"); 631 tud = &term->_userdefs[term->_nuserdefs++]; 632 tud->id = terms[i]->_userdefs[j].id; 633 tud->type = terms[i]->_userdefs[j].flag; 634 tud->flag = CANCELLED_BOOLEAN; 635 tud->num = CANCELLED_NUMERIC; 636 tud->str = CANCELLED_STRING; 637 } 638 } 639 } 640 641 int 642 main(int argc, char **argv) 643 { 644 char *term, *Barg; 645 int ch, uflag; 646 TERMINAL *t, *t2; 647 size_t n, n2; 648 struct winsize ws; 649 TIENT ents[TISTRMAX + 1], ents2[TISTRMAX + 1]; 650 651 cols = 80; /* default */ 652 term = getenv("COLUMNS"); 653 if (term != NULL) 654 cols = strtoul(term, NULL, 10); 655 else if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0) 656 cols = ws.ws_col; 657 658 uflag = xflag = 0; 659 Barg = NULL; 660 while ((ch = getopt(argc, argv, "1A:B:acnquw:x")) != -1) 661 switch (ch) { 662 case '1': 663 cols = 1; 664 break; 665 case 'A': 666 setdb(optarg); 667 break; 668 case 'B': 669 Barg = optarg; 670 break; 671 case 'a': 672 aflag = 1; 673 break; 674 case 'c': 675 cflag = 1; 676 break; 677 case 'n': 678 nflag = 1; 679 break; 680 case 'q': 681 qflag = 1; 682 break; 683 case 'u': 684 uflag = 1; 685 aflag = 1; 686 break; 687 case 'w': 688 cols = strtoul(optarg, NULL, 10); 689 break; 690 case 'x': 691 xflag = 1; 692 break; 693 case '?': 694 default: 695 fprintf(stderr, 696 "usage: %s [-1acnqux] [-A database] [-B database] " 697 "[-w cols] [term]\n", 698 getprogname()); 699 return EXIT_FAILURE; 700 } 701 cols--; 702 703 if (optind + 1 < argc) 704 aflag = 1; 705 706 if (optind < argc) 707 term = argv[optind++]; 708 else 709 term = NULL; 710 t = load_term(term); 711 712 if (uflag != 0) 713 use_terms(t, argc - optind, argv + optind); 714 715 if ((optind + 1 != argc && nflag == 0) || uflag != 0) { 716 if (uflag == 0) 717 printf("# Reconstructed from %s.db\n", _ti_database); 718 printf("%s", t->name); 719 if (t->_alias != NULL && *t->_alias != '\0') 720 printf("|%s", t->_alias); 721 if (t->desc != NULL && *t->desc != '\0') 722 printf("|%s", t->desc); 723 printf(",\n"); 724 725 n = load_ents(ents, t, 'f'); 726 print_ent(ents, n); 727 n = load_ents(ents, t, 'n'); 728 print_ent(ents, n); 729 n = load_ents(ents, t, 's'); 730 print_ent(ents, n); 731 732 if (uflag != 0) { 733 printf("\t"); 734 n = SW; 735 for (; optind < argc; optind++) { 736 n2 = 5 + strlen(argv[optind]); 737 if (n != SW) { 738 if (n + n2 > cols) { 739 printf("\n\t"); 740 n = SW; 741 } else 742 n += printf(" "); 743 } 744 n += printf("use=%s,", argv[optind]); 745 } 746 printf("\n"); 747 } 748 return EXIT_SUCCESS; 749 } 750 751 if (Barg == NULL) 752 unsetenv("TERMINFO"); 753 else 754 setdb(Barg); 755 t2 = load_term(argv[optind++]); 756 printf("comparing %s to %s.\n", t->name, t2->name); 757 if (qflag == 0) 758 printf(" comparing booleans.\n"); 759 if (nflag == 0) { 760 n = load_ents(ents, t, 'f'); 761 n2 = load_ents(ents2, t2, 'f'); 762 compare_ents(ents, n, ents2, n2); 763 } else 764 show_missing(t, t2, 'f'); 765 if (qflag == 0) 766 printf(" comparing numbers.\n"); 767 if (nflag == 0) { 768 n = load_ents(ents, t, 'n'); 769 n2 = load_ents(ents2, t2, 'n'); 770 compare_ents(ents, n, ents2, n2); 771 } else 772 show_missing(t, t2, 'n'); 773 if (qflag == 0) 774 printf(" comparing strings.\n"); 775 if (nflag == 0) { 776 n = load_ents(ents, t, 's'); 777 n2 = load_ents(ents2, t2, 's'); 778 compare_ents(ents, n, ents2, n2); 779 } else 780 show_missing(t, t2, 's'); 781 return EXIT_SUCCESS; 782 } 783