1/* $NetBSD: infocmp.c,v 1.17 2020/03/31 12:44:15 roy Exp $ */ 2 3/* 4 * Copyright (c) 2009, 2010, 2020 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.17 2020/03/31 12:44:15 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 47typedef struct tient { 48 char type; 49 const char *id; 50 signed char flag; 51 int num; 52 const char *str; 53} TIENT; 54 55static size_t cols; 56static int aflag, cflag, nflag, qflag, xflag; 57 58static size_t 59outstr(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++; 110prnt: 111 if (f != NULL) 112 fputc(ch, f); 113 r++; 114 } 115 return r; 116} 117 118static int 119ent_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 128static void 129setdb(char *db) 130{ 131 static const char *ext[] = { ".cdb", ".db" }; 132 133 for (size_t i = 0; i < __arraycount(ext); i++) { 134 char *ptr = strstr(db, ext[i]); 135 if (ptr == NULL || ptr[strlen(ext[i])] != '\0') 136 continue; 137 *ptr = '\0'; 138 break; 139 } 140 setenv("TERMINFO", db, 1); 141} 142 143static void 144print_ent(const TIENT *ents, size_t nents) 145{ 146 size_t col, i, l; 147 char nbuf[64]; 148 149 if (nents == 0) 150 return; 151 152 col = SW; 153 printf("\t"); 154 for (i = 0; i < nents; i++) { 155 if (*ents[i].id == '.' && aflag == 0) 156 continue; 157 switch (ents[i].type) { 158 case 'f': 159 if (ents[i].flag == ABSENT_BOOLEAN) 160 continue; 161 l = strlen(ents[i].id) + 2; 162 if (ents[i].flag == CANCELLED_BOOLEAN) 163 l++; 164 break; 165 case 'n': 166 if (ents[i].num == ABSENT_NUMERIC) 167 continue; 168 if (VALID_NUMERIC(ents[i].num)) 169 l = snprintf(nbuf, sizeof(nbuf), "%s#%d,", 170 ents[i].id, ents[i].num); 171 else 172 l = snprintf(nbuf, sizeof(nbuf), "%s@,", 173 ents[i].id); 174 break; 175 case 's': 176 if (ents[i].str == ABSENT_STRING) 177 continue; 178 if (VALID_STRING(ents[i].str)) 179 l = strlen(ents[i].id) + 180 outstr(NULL, ents[i].str) + 7; 181 else 182 l = strlen(ents[i].id) + 3; 183 break; 184 default: 185 errx(EXIT_FAILURE, "invalid type"); 186 } 187 if (col != SW) { 188 if (col + l > cols) { 189 printf("\n\t"); 190 col = SW; 191 } else 192 col += printf(" "); 193 } 194 switch (ents[i].type) { 195 case 'f': 196 col += printf("%s", ents[i].id); 197 if (ents[i].flag == ABSENT_BOOLEAN || 198 ents[i].flag == CANCELLED_BOOLEAN) 199 col += printf("@"); 200 col += printf(","); 201 break; 202 case 'n': 203 col += printf("%s", nbuf); 204 break; 205 case 's': 206 col += printf("%s", ents[i].id); 207 if (VALID_STRING(ents[i].str)) { 208 col += printf("="); 209 col += outstr(stdout, ents[i].str); 210 } else 211 col += printf("@"); 212 col += printf(","); 213 break; 214 } 215 } 216 printf("\n"); 217} 218 219static size_t 220load_ents(TIENT *ents, TERMINAL *t, char type) 221{ 222 size_t i, n, max; 223 TERMUSERDEF *ud; 224 225 switch (type) { 226 case 'f': 227 max = TIFLAGMAX; 228 break; 229 case 'n': 230 max = TINUMMAX; 231 break; 232 default: 233 max = TISTRMAX; 234 } 235 236 n = 0; 237 for (i = 0; i <= max; i++) { 238 switch (type) { 239 case 'f': 240 if (t->flags[i] == 1 || 241 (aflag && t->flags[i] == CANCELLED_BOOLEAN)) 242 { 243 ents[n].id = _ti_flagid(i); 244 ents[n].type = 'f'; 245 ents[n++].flag = t->flags[i]; 246 } 247 break; 248 case 'n': 249 if (VALID_NUMERIC(t->nums[i]) || 250 (aflag && t->nums[i] == CANCELLED_NUMERIC)) 251 { 252 ents[n].id = _ti_numid(i); 253 ents[n].type = 'n'; 254 ents[n++].num = t->nums[i]; 255 } 256 break; 257 default: 258 if (VALID_STRING(t->strs[i]) || 259 (aflag && t->strs[i] == CANCELLED_STRING)) 260 { 261 ents[n].id = _ti_strid(i); 262 ents[n].type = 's'; 263 ents[n++].str = t->strs[i]; 264 } 265 break; 266 } 267 } 268 269 if (xflag != 0 && t->_nuserdefs != 0) { 270 for (i = 0; i < t->_nuserdefs; i++) { 271 ud = &t->_userdefs[i]; 272 if (ud->type == type) { 273 switch (type) { 274 case 'f': 275 if (!aflag && 276 !VALID_BOOLEAN(ud->flag)) 277 continue; 278 break; 279 case 'n': 280 if (!aflag && 281 !VALID_NUMERIC(ud->num)) 282 continue; 283 break; 284 case 's': 285 if (!aflag && 286 !VALID_STRING(ud->str)) 287 continue; 288 break; 289 } 290 ents[n].id = ud->id; 291 ents[n].type = ud->type; 292 ents[n].flag = ud->flag; 293 ents[n].num = ud->num; 294 ents[n++].str = ud->str; 295 } 296 } 297 } 298 299 qsort(ents, n, sizeof(TIENT), ent_compare); 300 return n; 301} 302 303static void 304cprint_ent(TIENT *ent) 305{ 306 307 if (ent == NULL) { 308 if (qflag == 0) 309 printf("NULL"); 310 else 311 printf("-"); 312 } 313 314 switch (ent->type) { 315 case 'f': 316 if (VALID_BOOLEAN(ent->flag)) 317 printf(ent->flag == 1 ? "T" : "F"); 318 else if (qflag == 0) 319 printf("F"); 320 else if (ent->flag == CANCELLED_BOOLEAN) 321 printf("@"); 322 else 323 printf("-"); 324 break; 325 case 'n': 326 if (VALID_NUMERIC(ent->num)) 327 printf("%d", ent->num); 328 else if (qflag == 0) 329 printf("NULL"); 330 else if (ent->num == CANCELLED_NUMERIC) 331 printf("@"); 332 else 333 printf("-"); 334 break; 335 case 's': 336 if (VALID_STRING(ent->str)) { 337 printf("'"); 338 outstr(stdout, ent->str); 339 printf("'"); 340 } else if (qflag == 0) 341 printf("NULL"); 342 else if (ent->str == CANCELLED_STRING) 343 printf("@"); 344 else 345 printf("-"); 346 break; 347 } 348} 349 350static void 351compare_ents(TIENT *ents1, size_t n1, TIENT *ents2, size_t n2) 352{ 353 size_t i1, i2; 354 TIENT *e1, *e2, ee; 355 int c; 356 357 i1 = i2 = 0; 358 ee.type = 'f'; 359 ee.flag = ABSENT_BOOLEAN; 360 ee.num = ABSENT_NUMERIC; 361 ee.str = ABSENT_STRING; 362 while (i1 != n1 || i2 != n2) { 363 if (i1 == n1) 364 c = 1; 365 else if (i2 == n2) 366 c = -1; 367 else 368 c = strcmp(ents1[i1].id, ents2[i2].id); 369 if (c == 0) { 370 e1 = &ents1[i1++]; 371 e2 = &ents2[i2++]; 372 } else if (c < 0) { 373 e1 = &ents1[i1++]; 374 e2 = ⅇ 375 ee.id = e1->id; 376 ee.type = e1->type; 377 } else { 378 e1 = ⅇ 379 e2 = &ents2[i2++]; 380 ee.id = e2->id; 381 ee.type = e2->type; 382 } 383 switch (e1->type) { 384 case 'f': 385 if (cflag != 0) { 386 if (e1->flag == e2->flag) 387 printf("\t%s\n", ents1[i1].id); 388 continue; 389 } 390 if (e1->flag == e2->flag) 391 continue; 392 break; 393 case 'n': 394 if (cflag != 0) { 395 if (e1->num == e2->num) 396 printf("\t%s#%d\n", 397 ents1[i1].id, ents1[i1].num); 398 continue; 399 } 400 if (e1->num == e2->num) 401 continue; 402 break; 403 case 's': 404 if (cflag != 0) { 405 if (VALID_STRING(e1->str) && 406 VALID_STRING(e2->str) && 407 strcmp(e1->str, e2->str) == 0) { 408 printf("\t%s=", ents1[i1].id); 409 outstr(stdout, ents1[i1].str); 410 printf("\n"); 411 } 412 continue; 413 } 414 if (VALID_STRING(e1->str) && 415 VALID_STRING(e2->str) && 416 strcmp(e1->str, e2->str) == 0) 417 continue; 418 break; 419 } 420 printf("\t%s: ", e1->id); 421 cprint_ent(e1); 422 if (e1->type == 'f') 423 printf(":"); 424 else 425 printf(", "); 426 cprint_ent(e2); 427 printf(".\n"); 428 } 429} 430 431static TERMINAL * 432load_term(const char *name) 433{ 434 TERMINAL *t; 435 436 t = ecalloc(1, sizeof(*t)); 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(EXIT_FAILURE, 446 "no terminal definition found in internal database"); 447 else 448 errx(EXIT_FAILURE, 449 "no terminal definition found in %s.db", _ti_database); 450} 451 452static void 453show_missing(TERMINAL *t1, TERMINAL *t2, char type) 454{ 455 ssize_t i, max; 456 const char *id; 457 458 switch (type) { 459 case 'f': 460 max = TIFLAGMAX; 461 break; 462 case 'n': 463 max = TINUMMAX; 464 break; 465 default: 466 max = TISTRMAX; 467 } 468 469 for (i = 0; i <= max; i++) { 470 switch (type) { 471 case 'f': 472 if (t1->flags[i] != ABSENT_BOOLEAN || 473 t2->flags[i] != ABSENT_BOOLEAN) 474 continue; 475 id = _ti_flagid(i); 476 break; 477 case 'n': 478 if (t1->nums[i] != ABSENT_NUMERIC || 479 t2->nums[i] != ABSENT_NUMERIC) 480 continue; 481 id = _ti_numid(i); 482 break; 483 default: 484 if (t1->strs[i] != ABSENT_STRING || 485 t2->strs[i] != ABSENT_STRING) 486 continue; 487 id = _ti_strid(i); 488 break; 489 } 490 printf("\t!%s.\n", id); 491 } 492} 493 494static TERMUSERDEF * 495find_userdef(TERMINAL *term, const char *id) 496{ 497 size_t i; 498 499 for (i = 0; i < term->_nuserdefs; i++) 500 if (strcmp(term->_userdefs[i].id, id) == 0) 501 return &term->_userdefs[i]; 502 return NULL; 503} 504 505static void 506use_terms(TERMINAL *term, size_t nuse, char **uterms) 507{ 508 TERMINAL **terms; 509 TERMUSERDEF *ud, *tud; 510 size_t i, j, agree, absent, data; 511 512 terms = ecalloc(nuse, sizeof(*terms)); 513 for (i = 0; i < nuse; i++) { 514 if (strcmp(term->name, *uterms) == 0) 515 errx(EXIT_FAILURE, "cannot use same terminal"); 516 for (j = 0; j < i; j++) 517 if (strcmp(terms[j]->name, *uterms) == 0) 518 errx(EXIT_FAILURE, "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 = erealloc(term->_userdefs, 632 sizeof(*term->_userdefs) * (term->_nuserdefs + 1)); 633 tud = &term->_userdefs[term->_nuserdefs++]; 634 tud->id = terms[i]->_userdefs[j].id; 635 tud->type = terms[i]->_userdefs[j].flag; 636 tud->flag = CANCELLED_BOOLEAN; 637 tud->num = CANCELLED_NUMERIC; 638 tud->str = CANCELLED_STRING; 639 } 640 } 641} 642 643int 644main(int argc, char **argv) 645{ 646 char *term, *Barg; 647 int ch, uflag; 648 TERMINAL *t, *t2; 649 size_t n, n2; 650 struct winsize ws; 651 TIENT ents[TISTRMAX + 1], ents2[TISTRMAX + 1]; 652 653 cols = 80; /* default */ 654 term = getenv("COLUMNS"); 655 if (term != NULL) 656 cols = strtoul(term, NULL, 10); 657 else if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0) 658 cols = ws.ws_col; 659 660 uflag = xflag = 0; 661 Barg = NULL; 662 while ((ch = getopt(argc, argv, "1A:B:acnquw:x")) != -1) 663 switch (ch) { 664 case '1': 665 cols = 1; 666 break; 667 case 'A': 668 setdb(optarg); 669 break; 670 case 'B': 671 Barg = optarg; 672 break; 673 case 'a': 674 aflag = 1; 675 break; 676 case 'c': 677 cflag = 1; 678 break; 679 case 'n': 680 nflag = 1; 681 break; 682 case 'q': 683 qflag = 1; 684 break; 685 case 'u': 686 uflag = 1; 687 aflag = 1; 688 break; 689 case 'w': 690 cols = strtoul(optarg, NULL, 10); 691 break; 692 case 'x': 693 xflag = 1; 694 break; 695 case '?': 696 default: 697 fprintf(stderr, 698 "usage: %s [-1acnqux] [-A database] [-B database] " 699 "[-w cols] [term]\n", 700 getprogname()); 701 return EXIT_FAILURE; 702 } 703 cols--; 704 705 if (optind + 1 < argc) 706 aflag = 1; 707 708 if (optind < argc) 709 term = argv[optind++]; 710 else 711 term = NULL; 712 t = load_term(term); 713 714 if (uflag != 0) 715 use_terms(t, argc - optind, argv + optind); 716 717 if ((optind + 1 != argc && nflag == 0) || uflag != 0) { 718 if (uflag == 0) 719 printf("# Reconstructed from %s\n", 720 _ti_database == NULL ? 721 "internal database" : _ti_database); 722 /* Strip internal versioning */ 723 term = strchr(t->name, TERMINFO_VDELIM); 724 if (term != NULL) 725 *term = '\0'; 726 printf("%s", t->name); 727 if (t->_alias != NULL) { 728 char *alias, *aliascpy, *delim; 729 730 alias = aliascpy = estrdup(t->_alias); 731 while (alias != NULL && *alias != '\0') { 732 putchar('|'); 733 delim = strchr(alias, TERMINFO_VDELIM); 734 if (delim != NULL) 735 *delim++ = '\0'; 736 printf("%s", alias); 737 if (delim != NULL) { 738 while (*delim != '\0' && *delim != '|') 739 delim++; 740 if (*delim == '\0') 741 alias = NULL; 742 else 743 alias = delim + 1; 744 } else 745 alias = NULL; 746 } 747 free(aliascpy); 748 } 749 if (t->desc != NULL && *t->desc != '\0') 750 printf("|%s", t->desc); 751 printf(",\n"); 752 753 n = load_ents(ents, t, 'f'); 754 print_ent(ents, n); 755 n = load_ents(ents, t, 'n'); 756 print_ent(ents, n); 757 n = load_ents(ents, t, 's'); 758 print_ent(ents, n); 759 760 if (uflag != 0) { 761 printf("\t"); 762 n = SW; 763 for (; optind < argc; optind++) { 764 n2 = 5 + strlen(argv[optind]); 765 if (n != SW) { 766 if (n + n2 > cols) { 767 printf("\n\t"); 768 n = SW; 769 } else 770 n += printf(" "); 771 } 772 n += printf("use=%s,", argv[optind]); 773 } 774 printf("\n"); 775 } 776 return EXIT_SUCCESS; 777 } 778 779 if (Barg == NULL) 780 unsetenv("TERMINFO"); 781 else 782 setdb(Barg); 783 t2 = load_term(argv[optind++]); 784 printf("comparing %s to %s.\n", t->name, t2->name); 785 if (qflag == 0) 786 printf(" comparing booleans.\n"); 787 if (nflag == 0) { 788 n = load_ents(ents, t, 'f'); 789 n2 = load_ents(ents2, t2, 'f'); 790 compare_ents(ents, n, ents2, n2); 791 } else 792 show_missing(t, t2, 'f'); 793 if (qflag == 0) 794 printf(" comparing numbers.\n"); 795 if (nflag == 0) { 796 n = load_ents(ents, t, 'n'); 797 n2 = load_ents(ents2, t2, 'n'); 798 compare_ents(ents, n, ents2, n2); 799 } else 800 show_missing(t, t2, 'n'); 801 if (qflag == 0) 802 printf(" comparing strings.\n"); 803 if (nflag == 0) { 804 n = load_ents(ents, t, 's'); 805 n2 = load_ents(ents2, t2, 's'); 806 compare_ents(ents, n, ents2, n2); 807 } else 808 show_missing(t, t2, 's'); 809 return EXIT_SUCCESS; 810} 811