1 /**************************************************************************** 2 * Copyright 2018,2020 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.79 2020/02/02 23:34:34 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 void ExitProgram(int code) GCC_NORETURN; 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 void failed(const char *) GCC_NORETURN; 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 int j, k; 131 size_t n; 132 133 if (use_termdata) { 134 if (eargc > 1) { 135 for (j = 0; j < eargc; ++j) { 136 for (k = 0; k <= j; ++k) { 137 printf("--"); 138 } 139 printf("> "); 140 printf("%s\n", eargv[j]); 141 } 142 } 143 if (use_termdata > 1) 144 qsort(ptr_termdata, use_termdata, sizeof(TERMDATA), compare_termdata); 145 for (n = 0; n < use_termdata; ++n) { 146 147 /* 148 * If there is more than one database, show how they differ. 149 */ 150 if (eargc > 1) { 151 unsigned long check = 0; 152 k = 0; 153 for (;;) { 154 for (; k < ptr_termdata[n].db_index; ++k) { 155 printf("--"); 156 } 157 158 /* 159 * If this is the first entry, or its checksum differs 160 * from the first entry's checksum, print "*". Otherwise 161 * it looks enough like a duplicate to print "+". 162 */ 163 printf("%c-", ((check == 0 164 || (check != ptr_termdata[n].checksum)) 165 ? '*' 166 : '+')); 167 check = ptr_termdata[n].checksum; 168 169 ++k; 170 if ((n + 1) >= use_termdata 171 || strcmp(ptr_termdata[n].term_name, 172 ptr_termdata[n + 1].term_name)) { 173 break; 174 } 175 ++n; 176 } 177 for (; k < eargc; ++k) { 178 printf("--"); 179 } 180 printf(":\t"); 181 } 182 183 (void) printf("%-10s\t%s\n", 184 ptr_termdata[n].term_name, 185 ptr_termdata[n].description); 186 } 187 } 188 } 189 190 static void 191 free_termdata(void) 192 { 193 if (ptr_termdata != 0) { 194 while (use_termdata != 0) { 195 --use_termdata; 196 free(ptr_termdata[use_termdata].term_name); 197 free(ptr_termdata[use_termdata].description); 198 } 199 free(ptr_termdata); 200 ptr_termdata = 0; 201 } 202 use_termdata = 0; 203 len_termdata = 0; 204 } 205 206 static char ** 207 allocArgv(size_t count) 208 { 209 char **result = typeCalloc(char *, count + 1); 210 if (result == 0) 211 failed("realloc eargv"); 212 213 assert(result != 0); 214 return result; 215 } 216 217 static void 218 freeArgv(char **argv) 219 { 220 if (argv) { 221 int count = 0; 222 while (argv[count]) { 223 free(argv[count++]); 224 } 225 free(argv); 226 } 227 } 228 229 #if USE_HASHED_DB 230 static bool 231 make_db_name(char *dst, const char *src, unsigned limit) 232 { 233 static const char suffix[] = DBM_SUFFIX; 234 235 bool result = FALSE; 236 size_t lens = sizeof(suffix) - 1; 237 size_t size = strlen(src); 238 size_t need = lens + size; 239 240 if (need <= limit) { 241 if (size >= lens 242 && !strcmp(src + size - lens, suffix)) { 243 _nc_STRCPY(dst, src, PATH_MAX); 244 } else { 245 _nc_SPRINTF(dst, _nc_SLIMIT(PATH_MAX) "%s%s", src, suffix); 246 } 247 result = TRUE; 248 } 249 return result; 250 } 251 #endif 252 253 typedef void (DescHook) (int /* db_index */ , 254 int /* db_limit */ , 255 const char * /* term_name */ , 256 TERMTYPE2 * /* term */ ); 257 258 static const char * 259 term_description(TERMTYPE2 *tp) 260 { 261 const char *desc; 262 263 if (tp->term_names == 0 264 || (desc = strrchr(tp->term_names, '|')) == 0 265 || (*++desc == '\0')) { 266 desc = "(No description)"; 267 } 268 269 return desc; 270 } 271 272 /* display a description for the type */ 273 static void 274 deschook(int db_index, int db_limit, const char *term_name, TERMTYPE2 *tp) 275 { 276 (void) db_index; 277 (void) db_limit; 278 (void) printf("%-10s\t%s\n", term_name, term_description(tp)); 279 } 280 281 static unsigned long 282 string_sum(const char *value) 283 { 284 unsigned long result = 0; 285 286 if ((intptr_t) value == (intptr_t) (-1)) { 287 result = ~result; 288 } else if (value) { 289 while (*value) { 290 result += UChar(*value); 291 ++value; 292 } 293 } 294 return result; 295 } 296 297 static unsigned long 298 checksum_of(TERMTYPE2 *tp) 299 { 300 unsigned long result = string_sum(tp->term_names); 301 unsigned i; 302 303 for (i = 0; i < NUM_BOOLEANS(tp); i++) { 304 result += (unsigned long) (tp->Booleans[i]); 305 } 306 for (i = 0; i < NUM_NUMBERS(tp); i++) { 307 result += (unsigned long) (tp->Numbers[i]); 308 } 309 for (i = 0; i < NUM_STRINGS(tp); i++) { 310 result += string_sum(tp->Strings[i]); 311 } 312 return result; 313 } 314 315 /* collect data, to sort before display */ 316 static void 317 sorthook(int db_index, int db_limit, const char *term_name, TERMTYPE2 *tp) 318 { 319 TERMDATA *data = new_termdata(); 320 321 data->db_index = db_index; 322 data->checksum = ((db_limit > 1) ? checksum_of(tp) : 0); 323 data->term_name = strmalloc(term_name); 324 data->description = strmalloc(term_description(tp)); 325 } 326 327 #if NCURSES_USE_TERMCAP 328 static void 329 show_termcap(int db_index, int db_limit, char *buffer, DescHook hook) 330 { 331 TERMTYPE2 data; 332 char *next = strchr(buffer, ':'); 333 char *last; 334 char *list = buffer; 335 336 if (next) 337 *next = '\0'; 338 339 last = strrchr(buffer, '|'); 340 if (last) 341 ++last; 342 343 memset(&data, 0, sizeof(data)); 344 data.term_names = strmalloc(buffer); 345 while ((next = strtok(list, "|")) != 0) { 346 if (next != last) 347 hook(db_index, db_limit, next, &data); 348 list = 0; 349 } 350 free(data.term_names); 351 } 352 #endif 353 354 #if NCURSES_USE_DATABASE 355 static char * 356 copy_entryname(DIRENT * src) 357 { 358 size_t len = NAMLEN(src); 359 char *result = malloc(len + 1); 360 if (result == 0) 361 failed("copy entryname"); 362 memcpy(result, src->d_name, len); 363 result[len] = '\0'; 364 365 return result; 366 } 367 #endif 368 369 static int 370 typelist(int eargc, char *eargv[], 371 int verbosity, 372 DescHook hook) 373 /* apply a function to each entry in given terminfo directories */ 374 { 375 int i; 376 377 for (i = 0; i < eargc; i++) { 378 #if NCURSES_USE_DATABASE 379 if (_nc_is_dir_path(eargv[i])) { 380 char *cwd_buf = 0; 381 DIR *termdir; 382 DIRENT *subdir; 383 384 if ((termdir = opendir(eargv[i])) == 0) { 385 (void) fflush(stdout); 386 (void) fprintf(stderr, 387 "%s: can't open terminfo directory %s\n", 388 _nc_progname, eargv[i]); 389 continue; 390 } 391 392 if (verbosity) 393 (void) printf("#\n#%s:\n#\n", eargv[i]); 394 395 while ((subdir = readdir(termdir)) != 0) { 396 size_t cwd_len; 397 char *name_1; 398 DIR *entrydir; 399 DIRENT *entry; 400 401 name_1 = copy_entryname(subdir); 402 if (isDotname(name_1)) { 403 free(name_1); 404 continue; 405 } 406 407 cwd_len = NAMLEN(subdir) + strlen(eargv[i]) + 3; 408 cwd_buf = typeRealloc(char, cwd_len, cwd_buf); 409 if (cwd_buf == 0) 410 failed("realloc cwd_buf"); 411 412 assert(cwd_buf != 0); 413 414 _nc_SPRINTF(cwd_buf, _nc_SLIMIT(cwd_len) 415 "%s/%s/", eargv[i], name_1); 416 free(name_1); 417 418 if (chdir(cwd_buf) != 0) 419 continue; 420 421 entrydir = opendir("."); 422 if (entrydir == 0) { 423 perror(cwd_buf); 424 continue; 425 } 426 while ((entry = readdir(entrydir)) != 0) { 427 char *name_2; 428 TERMTYPE2 lterm; 429 char *cn; 430 int status; 431 432 name_2 = copy_entryname(entry); 433 if (isDotname(name_2) || !_nc_is_file_path(name_2)) { 434 free(name_2); 435 continue; 436 } 437 438 status = _nc_read_file_entry(name_2, <erm); 439 if (status <= 0) { 440 (void) fflush(stdout); 441 (void) fprintf(stderr, 442 "%s: couldn't open terminfo file %s.\n", 443 _nc_progname, name_2); 444 free(name_2); 445 continue; 446 } 447 448 /* only visit things once, by primary name */ 449 cn = _nc_first_name(lterm.term_names); 450 if (!strcmp(cn, name_2)) { 451 /* apply the selected hook function */ 452 hook(i, eargc, cn, <erm); 453 } 454 _nc_free_termtype2(<erm); 455 free(name_2); 456 } 457 closedir(entrydir); 458 } 459 closedir(termdir); 460 if (cwd_buf != 0) 461 free(cwd_buf); 462 continue; 463 } 464 #if USE_HASHED_DB 465 else { 466 DB *capdbp; 467 char filename[PATH_MAX]; 468 469 if (verbosity) 470 (void) printf("#\n#%s:\n#\n", eargv[i]); 471 472 if (make_db_name(filename, eargv[i], sizeof(filename))) { 473 if ((capdbp = _nc_db_open(filename, FALSE)) != 0) { 474 DBT key, data; 475 int code; 476 477 code = _nc_db_first(capdbp, &key, &data); 478 while (code == 0) { 479 TERMTYPE2 lterm; 480 int used; 481 char *have; 482 char *cn; 483 484 if (_nc_db_have_data(&key, &data, &have, &used)) { 485 if (_nc_read_termtype(<erm, have, used) > 0) { 486 /* only visit things once, by primary name */ 487 cn = _nc_first_name(lterm.term_names); 488 /* apply the selected hook function */ 489 hook(i, eargc, cn, <erm); 490 _nc_free_termtype2(<erm); 491 } 492 } 493 code = _nc_db_next(capdbp, &key, &data); 494 } 495 496 _nc_db_close(capdbp); 497 continue; 498 } 499 } 500 } 501 #endif /* USE_HASHED_DB */ 502 #endif /* NCURSES_USE_DATABASE */ 503 #if NCURSES_USE_TERMCAP 504 #if HAVE_BSD_CGETENT 505 { 506 CGETENT_CONST char *db_array[2]; 507 char *buffer = 0; 508 509 if (verbosity) 510 (void) printf("#\n#%s:\n#\n", eargv[i]); 511 512 db_array[0] = eargv[i]; 513 db_array[1] = 0; 514 515 if (cgetfirst(&buffer, db_array) > 0) { 516 show_termcap(i, eargc, buffer, hook); 517 free(buffer); 518 while (cgetnext(&buffer, db_array) > 0) { 519 show_termcap(i, eargc, buffer, hook); 520 free(buffer); 521 } 522 cgetclose(); 523 continue; 524 } 525 } 526 #else 527 /* scan termcap text-file only */ 528 if (_nc_is_file_path(eargv[i])) { 529 char buffer[2048]; 530 FILE *fp; 531 532 if (verbosity) 533 (void) printf("#\n#%s:\n#\n", eargv[i]); 534 535 if ((fp = fopen(eargv[i], "r")) != 0) { 536 while (fgets(buffer, sizeof(buffer), fp) != 0) { 537 if (*buffer == '#') 538 continue; 539 if (isspace(*buffer)) 540 continue; 541 show_termcap(i, eargc, buffer, hook); 542 } 543 fclose(fp); 544 } 545 } 546 #endif 547 #endif 548 } 549 550 if (hook == sorthook) { 551 show_termdata(eargc, eargv); 552 free_termdata(); 553 } 554 555 return (EXIT_SUCCESS); 556 } 557 558 static void 559 usage(void) 560 { 561 (void) fprintf(stderr, "usage: %s [-ahsuUV] [-v n] [file...]\n", _nc_progname); 562 ExitProgram(EXIT_FAILURE); 563 } 564 565 int 566 main(int argc, char *argv[]) 567 { 568 bool all_dirs = FALSE; 569 bool direct_dependencies = FALSE; 570 bool invert_dependencies = FALSE; 571 bool header = FALSE; 572 char *report_file = 0; 573 unsigned i; 574 int code; 575 int this_opt, last_opt = '?'; 576 unsigned v_opt = 0; 577 DescHook *hook = deschook; 578 579 _nc_progname = _nc_rootname(argv[0]); 580 581 while ((this_opt = getopt(argc, argv, "0123456789ahsu:vU:V")) != -1) { 582 /* handle optional parameter */ 583 if (isdigit(this_opt)) { 584 switch (last_opt) { 585 case 'v': 586 v_opt = (unsigned) (this_opt - '0'); 587 break; 588 default: 589 if (isdigit(last_opt)) 590 v_opt *= 10; 591 else 592 v_opt = 0; 593 v_opt += (unsigned) (this_opt - '0'); 594 last_opt = this_opt; 595 } 596 continue; 597 } 598 switch (this_opt) { 599 case 'a': 600 all_dirs = TRUE; 601 break; 602 case 'h': 603 header = TRUE; 604 break; 605 case 's': 606 hook = sorthook; 607 break; 608 case 'u': 609 direct_dependencies = TRUE; 610 report_file = optarg; 611 break; 612 case 'v': 613 v_opt = 1; 614 break; 615 case 'U': 616 invert_dependencies = TRUE; 617 report_file = optarg; 618 break; 619 case 'V': 620 puts(curses_version()); 621 ExitProgram(EXIT_SUCCESS); 622 default: 623 usage(); 624 } 625 } 626 set_trace_level(v_opt); 627 628 if (report_file != 0) { 629 if (freopen(report_file, "r", stdin) == 0) { 630 (void) fflush(stdout); 631 fprintf(stderr, "%s: can't open %s\n", _nc_progname, report_file); 632 ExitProgram(EXIT_FAILURE); 633 } 634 635 /* parse entries out of the source file */ 636 _nc_set_source(report_file); 637 _nc_read_entry_source(stdin, 0, FALSE, FALSE, NULLHOOK); 638 } 639 640 /* maybe we want a direct-dependency listing? */ 641 if (direct_dependencies) { 642 ENTRY *qp; 643 644 for_entry_list(qp) { 645 if (qp->nuses) { 646 unsigned j; 647 648 (void) printf("%s:", _nc_first_name(qp->tterm.term_names)); 649 for (j = 0; j < qp->nuses; j++) 650 (void) printf(" %s", qp->uses[j].name); 651 putchar('\n'); 652 } 653 } 654 655 ExitProgram(EXIT_SUCCESS); 656 } 657 658 /* maybe we want a reverse-dependency listing? */ 659 if (invert_dependencies) { 660 ENTRY *qp, *rp; 661 int matchcount; 662 663 for_entry_list(qp) { 664 matchcount = 0; 665 for_entry_list(rp) { 666 if (rp->nuses == 0) 667 continue; 668 669 for (i = 0; i < rp->nuses; i++) 670 if (_nc_name_match(qp->tterm.term_names, 671 rp->uses[i].name, "|")) { 672 if (matchcount++ == 0) 673 (void) printf("%s:", 674 _nc_first_name(qp->tterm.term_names)); 675 (void) printf(" %s", 676 _nc_first_name(rp->tterm.term_names)); 677 } 678 } 679 if (matchcount) 680 putchar('\n'); 681 } 682 683 ExitProgram(EXIT_SUCCESS); 684 } 685 686 /* 687 * If we get this far, user wants a simple terminal type listing. 688 */ 689 if (optind < argc) { 690 code = typelist(argc - optind, argv + optind, header, hook); 691 } else if (all_dirs) { 692 DBDIRS state; 693 int offset; 694 int pass; 695 const char *path; 696 char **eargv = 0; 697 698 code = EXIT_FAILURE; 699 for (pass = 0; pass < 2; ++pass) { 700 size_t count = 0; 701 702 _nc_first_db(&state, &offset); 703 while ((path = _nc_next_db(&state, &offset)) != 0) { 704 if (quick_prefix(path)) 705 continue; 706 if (pass) { 707 eargv[count] = strmalloc(path); 708 } 709 ++count; 710 } 711 if (!pass) { 712 eargv = allocArgv(count); 713 if (eargv == 0) 714 failed("eargv"); 715 } else { 716 code = typelist((int) count, eargv, header, hook); 717 freeArgv(eargv); 718 } 719 } 720 } else { 721 DBDIRS state; 722 int offset; 723 const char *path; 724 char **eargv = allocArgv((size_t) 2); 725 size_t count = 0; 726 727 if (eargv == 0) 728 failed("eargv"); 729 _nc_first_db(&state, &offset); 730 if ((path = _nc_next_db(&state, &offset)) != 0) { 731 if (!quick_prefix(path)) 732 eargv[count++] = strmalloc(path); 733 } 734 735 code = typelist((int) count, eargv, header, hook); 736 737 freeArgv(eargv); 738 } 739 _nc_last_db(); 740 741 ExitProgram(code); 742 } 743