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