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
48MODULE_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
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 void ExitProgram(int code) GCC_NORETURN;
68static void
69ExitProgram(int code)
70{
71    _nc_free_entries(_nc_head);
72    _nc_free_tic(code);
73}
74#endif
75
76static void failed(const char *) GCC_NORETURN;
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    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
190static void
191free_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
206static char **
207allocArgv(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
217static void
218freeArgv(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
230static bool
231make_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
253typedef void (DescHook) (int /* db_index */ ,
254			 int /* db_limit */ ,
255			 const char * /* term_name */ ,
256			 TERMTYPE2 * /* term */ );
257
258static const char *
259term_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 */
273static void
274deschook(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
281static unsigned long
282string_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
297static unsigned long
298checksum_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 */
316static void
317sorthook(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
328static void
329show_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
355static char *
356copy_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
369static int
370typelist(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, &lterm);
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, &lterm);
453		    }
454		    _nc_free_termtype2(&lterm);
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(&lterm, 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, &lterm);
490				_nc_free_termtype2(&lterm);
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
558static void
559usage(void)
560{
561    (void) fprintf(stderr, "usage: %s [-ahsuUV] [-v n] [file...]\n", _nc_progname);
562    ExitProgram(EXIT_FAILURE);
563}
564
565int
566main(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