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
48MODULE_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
52typedef struct {
53    int db_index;
54    unsigned long checksum;
55    char *term_name;
56    char *description;
57} TERMDATA;
58
59const char *_nc_progname;
60
61static TERMDATA *ptr_termdata;	/* array of terminal data */
62static size_t use_termdata;	/* actual usage in ptr_termdata[] */
63static size_t len_termdata;	/* allocated size of ptr_termdata[] */
64
65#if NO_LEAKS
66#undef ExitProgram
67static GCC_NORETURN void ExitProgram(int code);
68static void
69ExitProgram(int code)
70{
71    _nc_free_entries(_nc_head);
72    _nc_free_tic(code);
73}
74#endif
75
76static GCC_NORETURN void failed(const char *);
77
78static void
79failed(const char *msg)
80{
81    perror(msg);
82    ExitProgram(EXIT_FAILURE);
83}
84
85static char *
86strmalloc(const char *value)
87{
88    char *result = strdup(value);
89    if (result == 0) {
90	failed("strmalloc");
91    }
92    return result;
93}
94
95static TERMDATA *
96new_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
110static int
111compare_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 */
127static void
128show_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
200static void
201free_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
216static char **
217allocArgv(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
227static void
228freeArgv(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
240static bool
241make_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
265typedef void (DescHook) (int /* db_index */ ,
266			 int /* db_limit */ ,
267			 const char * /* term_name */ ,
268			 TERMTYPE2 * /* term */ );
269
270static const char *
271term_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 */
285static void
286deschook(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
293static unsigned long
294string_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
309static unsigned long
310checksum_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 */
328static void
329sorthook(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 */
344static bool
345is_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
360static void
361show_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
387static char *
388copy_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
401static int
402typelist(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, &lterm);
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, &lterm);
485		    }
486		    _nc_free_termtype2(&lterm);
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(&lterm, 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, &lterm);
522				_nc_free_termtype2(&lterm);
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
594static void
595usage(void)
596{
597    (void) fprintf(stderr, "usage: %s [-ahsuUV] [-v n] [file...]\n", _nc_progname);
598    ExitProgram(EXIT_FAILURE);
599}
600
601int
602main(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