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