1 /**************************************************************************** 2 * Copyright 2018-2022,2023 Thomas E. Dickey * 3 * Copyright 1998-2013,2017 Free Software Foundation, Inc. * 4 * * 5 * Permission is hereby granted, free of charge, to any person obtaining a * 6 * copy of this software and associated documentation files (the * 7 * "Software"), to deal in the Software without restriction, including * 8 * without limitation the rights to use, copy, modify, merge, publish, * 9 * distribute, distribute with modifications, sublicense, and/or sell * 10 * copies of the Software, and to permit persons to whom the Software is * 11 * furnished to do so, subject to the following conditions: * 12 * * 13 * The above copyright notice and this permission notice shall be included * 14 * in all copies or substantial portions of the Software. * 15 * * 16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * 17 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * 18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * 19 * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * 20 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * 21 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR * 22 * THE USE OR OTHER DEALINGS IN THE SOFTWARE. * 23 * * 24 * Except as contained in this notice, the name(s) of the above copyright * 25 * holders shall not be used in advertising or otherwise to promote the * 26 * sale, use or other dealings in this Software without prior written * 27 * authorization. * 28 ****************************************************************************/ 29 30 /**************************************************************************** 31 * Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995 * 32 * and: Eric S. Raymond <esr@snark.thyrsus.com> * 33 * and: Thomas E. Dickey 1996-on * 34 ****************************************************************************/ 35 36 /* 37 * toe.c --- table of entries report generator 38 */ 39 40 #include <progs.priv.h> 41 42 #include <sys/stat.h> 43 44 #if USE_HASHED_DB 45 #include <hashed_db.h> 46 #endif 47 48 MODULE_ID("$Id: toe.c,v 1.89 2023/07/01 17:04:46 tom Exp $") 49 50 #define isDotname(name) (!strcmp(name, ".") || !strcmp(name, "..")) 51 52 typedef struct { 53 int db_index; 54 unsigned long checksum; 55 char *term_name; 56 char *description; 57 } TERMDATA; 58 59 const char *_nc_progname; 60 61 static TERMDATA *ptr_termdata; /* array of terminal data */ 62 static size_t use_termdata; /* actual usage in ptr_termdata[] */ 63 static size_t len_termdata; /* allocated size of ptr_termdata[] */ 64 65 #if NO_LEAKS 66 #undef ExitProgram 67 static GCC_NORETURN void ExitProgram(int code); 68 static void 69 ExitProgram(int code) 70 { 71 _nc_free_entries(_nc_head); 72 _nc_free_tic(code); 73 } 74 #endif 75 76 static GCC_NORETURN void failed(const char *); 77 78 static void 79 failed(const char *msg) 80 { 81 perror(msg); 82 ExitProgram(EXIT_FAILURE); 83 } 84 85 static char * 86 strmalloc(const char *value) 87 { 88 char *result = strdup(value); 89 if (result == 0) { 90 failed("strmalloc"); 91 } 92 return result; 93 } 94 95 static TERMDATA * 96 new_termdata(void) 97 { 98 size_t want = use_termdata + 1; 99 100 if (want >= len_termdata) { 101 len_termdata = (2 * want) + 10; 102 ptr_termdata = typeRealloc(TERMDATA, len_termdata, ptr_termdata); 103 if (ptr_termdata == 0) 104 failed("ptr_termdata"); 105 } 106 107 return ptr_termdata + use_termdata++; 108 } 109 110 static int 111 compare_termdata(const void *a, const void *b) 112 { 113 const TERMDATA *p = (const TERMDATA *) a; 114 const TERMDATA *q = (const TERMDATA *) b; 115 int result = strcmp(p->term_name, q->term_name); 116 117 if (result == 0) { 118 result = (p->db_index - q->db_index); 119 } 120 return result; 121 } 122 123 /* 124 * Sort the array of TERMDATA and print it. If more than one database is being 125 * reported, add a column to show which database has a given entry. 126 */ 127 static void 128 show_termdata(int eargc, char **eargv) 129 { 130 if (use_termdata) { 131 size_t n; 132 133 if (eargc > 1) { 134 int j; 135 136 for (j = 0; j < eargc; ++j) { 137 int k; 138 139 for (k = 0; k <= j; ++k) { 140 printf("--"); 141 } 142 printf("> "); 143 printf("%s\n", eargv[j]); 144 } 145 } 146 if (use_termdata > 1) 147 qsort(ptr_termdata, use_termdata, sizeof(TERMDATA), compare_termdata); 148 for (n = 0; n < use_termdata; ++n) { 149 int nk = -1; 150 151 /* 152 * If there is more than one database, show how they differ. 153 */ 154 if (eargc > 1) { 155 unsigned long check = 0; 156 int k = 0; 157 for (;;) { 158 char mark = ((check == 0 159 || (check != ptr_termdata[n].checksum)) 160 ? '*' 161 : '+'); 162 163 for (; k < ptr_termdata[n].db_index; ++k) { 164 printf("--"); 165 } 166 167 /* 168 * If this is the first entry, or its checksum differs 169 * from the first entry's checksum, print "*". Otherwise 170 * it looks enough like a duplicate to print "+". 171 */ 172 printf("%c-", mark); 173 check = ptr_termdata[n].checksum; 174 if (mark == '*' && nk < 0) 175 nk = (int) n; 176 177 ++k; 178 if ((n + 1) >= use_termdata 179 || strcmp(ptr_termdata[n].term_name, 180 ptr_termdata[n + 1].term_name)) { 181 break; 182 } 183 ++n; 184 } 185 for (; k < eargc; ++k) { 186 printf("--"); 187 } 188 printf(":\t"); 189 } 190 if (nk < 0) 191 nk = (int) n; 192 193 (void) printf("%-10s\t%s\n", 194 ptr_termdata[n].term_name, 195 ptr_termdata[nk].description); 196 } 197 } 198 } 199 200 static void 201 free_termdata(void) 202 { 203 if (ptr_termdata != 0) { 204 while (use_termdata != 0) { 205 --use_termdata; 206 free(ptr_termdata[use_termdata].term_name); 207 free(ptr_termdata[use_termdata].description); 208 } 209 free(ptr_termdata); 210 ptr_termdata = 0; 211 } 212 use_termdata = 0; 213 len_termdata = 0; 214 } 215 216 static char ** 217 allocArgv(size_t count) 218 { 219 char **result = typeCalloc(char *, count + 1); 220 if (result == 0) 221 failed("realloc eargv"); 222 223 assert(result != 0); 224 return result; 225 } 226 227 static void 228 freeArgv(char **argv) 229 { 230 if (argv) { 231 int count = 0; 232 while (argv[count]) { 233 free(argv[count++]); 234 } 235 free(argv); 236 } 237 } 238 239 #if USE_HASHED_DB 240 static bool 241 make_db_name(char *dst, const char *src, unsigned limit) 242 { 243 static const char suffix[] = DBM_SUFFIX; 244 245 bool result = FALSE; 246 size_t lens = sizeof(suffix) - 1; 247 size_t size = strlen(src); 248 size_t need = lens + size; 249 250 if (need <= limit) { 251 if (size >= lens 252 && !strcmp(src + size - lens, suffix)) { 253 _nc_STRCPY(dst, src, PATH_MAX); 254 } else { 255 _nc_SPRINTF(dst, _nc_SLIMIT(PATH_MAX) "%.*s%s", 256 (int) (PATH_MAX - sizeof(suffix)), 257 src, suffix); 258 } 259 result = TRUE; 260 } 261 return result; 262 } 263 #endif 264 265 typedef void (DescHook) (int /* db_index */ , 266 int /* db_limit */ , 267 const char * /* term_name */ , 268 TERMTYPE2 * /* term */ ); 269 270 static const char * 271 term_description(TERMTYPE2 *tp) 272 { 273 const char *desc; 274 275 if (tp->term_names == 0 276 || (desc = strrchr(tp->term_names, '|')) == 0 277 || (*++desc == '\0')) { 278 desc = "(No description)"; 279 } 280 281 return desc; 282 } 283 284 /* display a description for the type */ 285 static void 286 deschook(int db_index, int db_limit, const char *term_name, TERMTYPE2 *tp) 287 { 288 (void) db_index; 289 (void) db_limit; 290 (void) printf("%-10s\t%s\n", term_name, term_description(tp)); 291 } 292 293 static unsigned long 294 string_sum(const char *value) 295 { 296 unsigned long result = 0; 297 298 if ((intptr_t) value == (intptr_t) (-1)) { 299 result = ~result; 300 } else if (value) { 301 while (*value) { 302 result += UChar(*value); 303 ++value; 304 } 305 } 306 return result; 307 } 308 309 static unsigned long 310 checksum_of(TERMTYPE2 *tp) 311 { 312 unsigned long result = string_sum(tp->term_names); 313 unsigned i; 314 315 for (i = 0; i < NUM_BOOLEANS(tp); i++) { 316 result += (unsigned long) (tp->Booleans[i]); 317 } 318 for (i = 0; i < NUM_NUMBERS(tp); i++) { 319 result += (unsigned long) (tp->Numbers[i]); 320 } 321 for (i = 0; i < NUM_STRINGS(tp); i++) { 322 result += string_sum(tp->Strings[i]); 323 } 324 return result; 325 } 326 327 /* collect data, to sort before display */ 328 static void 329 sorthook(int db_index, int db_limit, const char *term_name, TERMTYPE2 *tp) 330 { 331 TERMDATA *data = new_termdata(); 332 333 data->db_index = db_index; 334 data->checksum = ((db_limit > 1) ? checksum_of(tp) : 0); 335 data->term_name = strmalloc(term_name); 336 data->description = strmalloc(term_description(tp)); 337 } 338 339 #if NCURSES_USE_TERMCAP 340 /* 341 * Check if the buffer contents are printable ASCII, ensuring that we do not 342 * accidentally pick up incompatible binary content from a hashed database. 343 */ 344 static bool 345 is_termcap(char *buffer) 346 { 347 bool result = TRUE; 348 while (*buffer != '\0') { 349 int ch = UChar(*buffer++); 350 if (ch == '\t') 351 continue; 352 if (ch < ' ' || ch > '~') { 353 result = FALSE; 354 break; 355 } 356 } 357 return result; 358 } 359 360 static void 361 show_termcap(int db_index, int db_limit, char *buffer, DescHook hook) 362 { 363 TERMTYPE2 data; 364 char *next = strchr(buffer, ':'); 365 char *last; 366 char *list = buffer; 367 368 if (next) 369 *next = '\0'; 370 371 last = strrchr(buffer, '|'); 372 if (last) 373 ++last; 374 375 memset(&data, 0, sizeof(data)); 376 data.term_names = strmalloc(buffer); 377 while ((next = strtok(list, "|")) != 0) { 378 if (next != last) 379 hook(db_index, db_limit, next, &data); 380 list = 0; 381 } 382 free(data.term_names); 383 } 384 #endif 385 386 #if NCURSES_USE_DATABASE 387 static char * 388 copy_entryname(DIRENT * src) 389 { 390 size_t len = NAMLEN(src); 391 char *result = malloc(len + 1); 392 if (result == 0) 393 failed("copy entryname"); 394 memcpy(result, src->d_name, len); 395 result[len] = '\0'; 396 397 return result; 398 } 399 #endif 400 401 static int 402 typelist(int eargc, char *eargv[], 403 int verbosity, 404 DescHook hook) 405 /* apply a function to each entry in given terminfo directories */ 406 { 407 int i; 408 409 for (i = 0; i < eargc; i++) { 410 #if NCURSES_USE_DATABASE 411 if (_nc_is_dir_path(eargv[i])) { 412 char *cwd_buf = 0; 413 DIR *termdir; 414 DIRENT *subdir; 415 416 if ((termdir = opendir(eargv[i])) == 0) { 417 (void) fflush(stdout); 418 (void) fprintf(stderr, 419 "%s: can't open terminfo directory %s\n", 420 _nc_progname, eargv[i]); 421 continue; 422 } 423 424 if (verbosity) 425 (void) printf("#\n#%s:\n#\n", eargv[i]); 426 427 while ((subdir = readdir(termdir)) != 0) { 428 size_t cwd_len; 429 char *name_1; 430 DIR *entrydir; 431 DIRENT *entry; 432 433 name_1 = copy_entryname(subdir); 434 if (isDotname(name_1)) { 435 free(name_1); 436 continue; 437 } 438 439 cwd_len = NAMLEN(subdir) + strlen(eargv[i]) + 3; 440 cwd_buf = typeRealloc(char, cwd_len, cwd_buf); 441 if (cwd_buf == 0) 442 failed("realloc cwd_buf"); 443 444 assert(cwd_buf != 0); 445 446 _nc_SPRINTF(cwd_buf, _nc_SLIMIT(cwd_len) 447 "%s/%s/", eargv[i], name_1); 448 free(name_1); 449 450 if (chdir(cwd_buf) != 0) 451 continue; 452 453 entrydir = opendir("."); 454 if (entrydir == 0) { 455 perror(cwd_buf); 456 continue; 457 } 458 while ((entry = readdir(entrydir)) != 0) { 459 char *name_2; 460 TERMTYPE2 lterm; 461 char *cn; 462 int status; 463 464 name_2 = copy_entryname(entry); 465 if (isDotname(name_2) || !_nc_is_file_path(name_2)) { 466 free(name_2); 467 continue; 468 } 469 470 status = _nc_read_file_entry(name_2, <erm); 471 if (status <= 0) { 472 (void) fflush(stdout); 473 (void) fprintf(stderr, 474 "%s: couldn't open terminfo file %s.\n", 475 _nc_progname, name_2); 476 free(name_2); 477 continue; 478 } 479 480 /* only visit things once, by primary name */ 481 cn = _nc_first_name(lterm.term_names); 482 if (!strcmp(cn, name_2)) { 483 /* apply the selected hook function */ 484 hook(i, eargc, cn, <erm); 485 } 486 _nc_free_termtype2(<erm); 487 free(name_2); 488 } 489 closedir(entrydir); 490 } 491 closedir(termdir); 492 if (cwd_buf != 0) 493 free(cwd_buf); 494 continue; 495 } 496 #if USE_HASHED_DB 497 else { 498 DB *capdbp; 499 char filename[PATH_MAX]; 500 501 if (verbosity) 502 (void) printf("#\n#%s:\n#\n", eargv[i]); 503 504 if (make_db_name(filename, eargv[i], sizeof(filename))) { 505 if ((capdbp = _nc_db_open(filename, FALSE)) != 0) { 506 DBT key, data; 507 int code; 508 509 code = _nc_db_first(capdbp, &key, &data); 510 while (code == 0) { 511 TERMTYPE2 lterm; 512 int used; 513 char *have; 514 char *cn; 515 516 if (_nc_db_have_data(&key, &data, &have, &used)) { 517 if (_nc_read_termtype(<erm, have, used) > 0) { 518 /* only visit things once, by primary name */ 519 cn = _nc_first_name(lterm.term_names); 520 /* apply the selected hook function */ 521 hook(i, eargc, cn, <erm); 522 _nc_free_termtype2(<erm); 523 } 524 } 525 code = _nc_db_next(capdbp, &key, &data); 526 } 527 528 _nc_db_close(capdbp); 529 continue; 530 } 531 } 532 } 533 #endif /* USE_HASHED_DB */ 534 #endif /* NCURSES_USE_DATABASE */ 535 #if NCURSES_USE_TERMCAP 536 #if HAVE_BSD_CGETENT 537 { 538 CGETENT_CONST char *db_array[2]; 539 char *buffer = 0; 540 541 if (verbosity) 542 (void) printf("#\n#%s:\n#\n", eargv[i]); 543 544 db_array[0] = eargv[i]; 545 db_array[1] = 0; 546 547 if (cgetfirst(&buffer, db_array) > 0) { 548 if (is_termcap(buffer)) { 549 show_termcap(i, eargc, buffer, hook); 550 free(buffer); 551 while (cgetnext(&buffer, db_array) > 0) { 552 show_termcap(i, eargc, buffer, hook); 553 free(buffer); 554 } 555 } 556 cgetclose(); 557 continue; 558 } 559 } 560 #else 561 /* scan termcap text-file only */ 562 if (_nc_is_file_path(eargv[i])) { 563 char buffer[2048]; 564 FILE *fp; 565 566 if (verbosity) 567 (void) printf("#\n#%s:\n#\n", eargv[i]); 568 569 if ((fp = safe_fopen(eargv[i], "r")) != 0) { 570 while (fgets(buffer, sizeof(buffer), fp) != 0) { 571 if (!is_termcap(buffer)) 572 break; 573 if (*buffer == '#') 574 continue; 575 if (isspace(UChar(*buffer))) 576 continue; 577 show_termcap(i, eargc, buffer, hook); 578 } 579 fclose(fp); 580 } 581 } 582 #endif 583 #endif 584 } 585 586 if (hook == sorthook) { 587 show_termdata(eargc, eargv); 588 free_termdata(); 589 } 590 591 return (EXIT_SUCCESS); 592 } 593 594 static void 595 usage(void) 596 { 597 (void) fprintf(stderr, "usage: %s [-ahsuUV] [-v n] [file...]\n", _nc_progname); 598 ExitProgram(EXIT_FAILURE); 599 } 600 601 int 602 main(int argc, char *argv[]) 603 { 604 bool all_dirs = FALSE; 605 bool direct_dependencies = FALSE; 606 bool invert_dependencies = FALSE; 607 bool header = FALSE; 608 char *report_file = 0; 609 int code; 610 int this_opt, last_opt = '?'; 611 unsigned v_opt = 0; 612 DescHook *hook = deschook; 613 614 _nc_progname = _nc_rootname(argv[0]); 615 616 while ((this_opt = getopt(argc, argv, "0123456789ahsu:vU:V")) != -1) { 617 /* handle optional parameter */ 618 if (isdigit(this_opt)) { 619 switch (last_opt) { 620 case 'v': 621 v_opt = (unsigned) (this_opt - '0'); 622 break; 623 default: 624 if (isdigit(last_opt)) 625 v_opt *= 10; 626 else 627 v_opt = 0; 628 v_opt += (unsigned) (this_opt - '0'); 629 last_opt = this_opt; 630 } 631 continue; 632 } 633 switch (this_opt) { 634 case 'a': 635 all_dirs = TRUE; 636 break; 637 case 'h': 638 header = TRUE; 639 break; 640 case 's': 641 hook = sorthook; 642 break; 643 case 'u': 644 direct_dependencies = TRUE; 645 report_file = optarg; 646 break; 647 case 'v': 648 v_opt = 1; 649 break; 650 case 'U': 651 invert_dependencies = TRUE; 652 report_file = optarg; 653 break; 654 case 'V': 655 puts(curses_version()); 656 ExitProgram(EXIT_SUCCESS); 657 default: 658 usage(); 659 } 660 } 661 use_verbosity(v_opt); 662 663 if (report_file != 0) { 664 if (freopen(report_file, "r", stdin) == 0) { 665 (void) fflush(stdout); 666 fprintf(stderr, "%s: can't open %s\n", _nc_progname, report_file); 667 ExitProgram(EXIT_FAILURE); 668 } 669 670 /* parse entries out of the source file */ 671 _nc_set_source(report_file); 672 _nc_read_entry_source(stdin, 0, FALSE, FALSE, NULLHOOK); 673 } 674 675 /* maybe we want a direct-dependency listing? */ 676 if (direct_dependencies) { 677 ENTRY *qp; 678 679 for_entry_list(qp) { 680 if (qp->nuses) { 681 unsigned j; 682 683 (void) printf("%s:", _nc_first_name(qp->tterm.term_names)); 684 for (j = 0; j < qp->nuses; j++) 685 (void) printf(" %s", qp->uses[j].name); 686 putchar('\n'); 687 } 688 } 689 690 ExitProgram(EXIT_SUCCESS); 691 } 692 693 /* maybe we want a reverse-dependency listing? */ 694 if (invert_dependencies) { 695 ENTRY *qp, *rp; 696 697 for_entry_list(qp) { 698 int matchcount = 0; 699 700 for_entry_list(rp) { 701 unsigned i; 702 703 if (rp->nuses == 0) 704 continue; 705 706 for (i = 0; i < rp->nuses; i++) 707 if (_nc_name_match(qp->tterm.term_names, 708 rp->uses[i].name, "|")) { 709 if (matchcount++ == 0) 710 (void) printf("%s:", 711 _nc_first_name(qp->tterm.term_names)); 712 (void) printf(" %s", 713 _nc_first_name(rp->tterm.term_names)); 714 } 715 } 716 if (matchcount) 717 putchar('\n'); 718 } 719 720 ExitProgram(EXIT_SUCCESS); 721 } 722 723 /* 724 * If we get this far, user wants a simple terminal type listing. 725 */ 726 if (optind < argc) { 727 code = typelist(argc - optind, argv + optind, header, hook); 728 } else if (all_dirs) { 729 DBDIRS state; 730 int offset; 731 int pass; 732 char **eargv = 0; 733 734 code = EXIT_FAILURE; 735 for (pass = 0; pass < 2; ++pass) { 736 size_t count = 0; 737 const char *path; 738 739 _nc_first_db(&state, &offset); 740 while ((path = _nc_next_db(&state, &offset)) != 0) { 741 if (quick_prefix(path)) 742 continue; 743 if (pass) { 744 eargv[count] = strmalloc(path); 745 } 746 ++count; 747 } 748 if (!pass) { 749 eargv = allocArgv(count); 750 if (eargv == 0) 751 failed("eargv"); 752 } else { 753 code = typelist((int) count, eargv, header, hook); 754 freeArgv(eargv); 755 } 756 } 757 } else { 758 DBDIRS state; 759 int offset; 760 const char *path; 761 char **eargv = allocArgv((size_t) 2); 762 size_t count = 0; 763 764 if (eargv == 0) 765 failed("eargv"); 766 _nc_first_db(&state, &offset); 767 if ((path = _nc_next_db(&state, &offset)) != 0) { 768 if (!quick_prefix(path)) 769 eargv[count++] = strmalloc(path); 770 } 771 772 code = typelist((int) count, eargv, header, hook); 773 774 freeArgv(eargv); 775 } 776 _nc_last_db(); 777 778 ExitProgram(code); 779 } 780