1/****************************************************************************
2 * Copyright (c) 1998-2013,2014 Free Software Foundation, Inc.              *
3 *                                                                          *
4 * Permission is hereby granted, free of charge, to any person obtaining a  *
5 * copy of this software and associated documentation files (the            *
6 * "Software"), to deal in the Software without restriction, including      *
7 * without limitation the rights to use, copy, modify, merge, publish,      *
8 * distribute, distribute with modifications, sublicense, and/or sell       *
9 * copies of the Software, and to permit persons to whom the Software is    *
10 * furnished to do so, subject to the following conditions:                 *
11 *                                                                          *
12 * The above copyright notice and this permission notice shall be included  *
13 * in all copies or substantial portions of the Software.                   *
14 *                                                                          *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
16 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
18 * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
19 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
20 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
21 * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
22 *                                                                          *
23 * Except as contained in this notice, the name(s) of the above copyright   *
24 * holders shall not be used in advertising or otherwise to promote the     *
25 * sale, use or other dealings in this Software without prior written       *
26 * authorization.                                                           *
27 ****************************************************************************/
28
29/****************************************************************************
30 *  Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995               *
31 *     and: Eric S. Raymond <esr@snark.thyrsus.com>                         *
32 *     and: Thomas E. Dickey                        1996-on                 *
33 ****************************************************************************/
34
35/*
36 *	infocmp.c -- decompile an entry, or compare two entries
37 *		written by Eric S. Raymond
38 *		and Thomas E Dickey
39 */
40
41#include <progs.priv.h>
42
43#include <dump_entry.h>
44
45MODULE_ID("$Id: infocmp.c,v 1.129 2014/02/01 22:11:03 tom Exp $")
46
47#define L_CURL "{"
48#define R_CURL "}"
49
50#define MAX_STRING	1024	/* maximum formatted string */
51
52const char *_nc_progname = "infocmp";
53
54typedef char path[PATH_MAX];
55
56/***************************************************************************
57 *
58 * The following control variables, together with the contents of the
59 * terminfo entries, completely determine the actions of the program.
60 *
61 ***************************************************************************/
62
63static ENTRY *entries;		/* terminfo entries */
64static int termcount;		/* count of terminal entries */
65
66static bool limited = TRUE;	/* "-r" option is not set */
67static bool quiet = FALSE;
68static bool literal = FALSE;
69static const char *bool_sep = ":";
70static const char *s_absent = "NULL";
71static const char *s_cancel = "NULL";
72static const char *tversion;	/* terminfo version selected */
73static unsigned itrace;		/* trace flag for debugging */
74static int mwidth = 60;
75static int mheight = 65535;
76static int numbers = 0;		/* format "%'char'" to/from "%{number}" */
77static int outform = F_TERMINFO;	/* output format */
78static int sortmode;		/* sort_mode */
79
80/* main comparison mode */
81static int compare;
82#define C_DEFAULT	0	/* don't force comparison mode */
83#define C_DIFFERENCE	1	/* list differences between two terminals */
84#define C_COMMON	2	/* list common capabilities */
85#define C_NAND		3	/* list capabilities in neither terminal */
86#define C_USEALL	4	/* generate relative use-form entry */
87static bool ignorepads;		/* ignore pad prefixes when diffing */
88
89#if NO_LEAKS
90
91typedef struct {
92    ENTRY *head;
93    ENTRY *tail;
94} ENTERED;
95
96static ENTERED *entered;
97
98#undef ExitProgram
99static void ExitProgram(int code) GCC_NORETURN;
100/* prototype is to get gcc to accept the noreturn attribute */
101static void
102ExitProgram(int code)
103{
104    int n;
105
106    for (n = 0; n < termcount; ++n) {
107	ENTRY *new_head = _nc_head;
108	ENTRY *new_tail = _nc_tail;
109	_nc_head = entered[n].head;
110	_nc_tail = entered[n].tail;
111	_nc_free_entries(entered[n].head);
112	_nc_head = new_head;
113	_nc_tail = new_tail;
114    }
115    _nc_leaks_dump_entry();
116    free(entries);
117    free(entered);
118    _nc_free_tic(code);
119}
120#endif
121
122static void
123failed(const char *s)
124{
125    perror(s);
126    ExitProgram(EXIT_FAILURE);
127}
128
129static char *
130canonical_name(char *ptr, char *buf)
131/* extract the terminal type's primary name */
132{
133    char *bp;
134
135    _nc_STRCPY(buf, ptr, NAMESIZE);
136    if ((bp = strchr(buf, '|')) != 0)
137	*bp = '\0';
138
139    return (buf);
140}
141
142/***************************************************************************
143 *
144 * Predicates for dump function
145 *
146 ***************************************************************************/
147
148static int
149capcmp(PredIdx idx, const char *s, const char *t)
150/* capability comparison function */
151{
152    if (!VALID_STRING(s) && !VALID_STRING(t))
153	return (s != t);
154    else if (!VALID_STRING(s) || !VALID_STRING(t))
155	return (1);
156
157    if ((idx == acs_chars_index) || !ignorepads)
158	return (strcmp(s, t));
159    else
160	return (_nc_capcmp(s, t));
161}
162
163static int
164use_predicate(unsigned type, PredIdx idx)
165/* predicate function to use for use decompilation */
166{
167    ENTRY *ep;
168
169    switch (type) {
170    case BOOLEAN:
171	{
172	    int is_set = FALSE;
173
174	    /*
175	     * This assumes that multiple use entries are supposed
176	     * to contribute the logical or of their boolean capabilities.
177	     * This is true if we take the semantics of multiple uses to
178	     * be 'each capability gets the first non-default value found
179	     * in the sequence of use entries'.
180	     *
181	     * Note that cancelled or absent booleans are stored as FALSE,
182	     * unlike numbers and strings, whose cancelled/absent state is
183	     * recorded in the terminfo database.
184	     */
185	    for (ep = &entries[1]; ep < entries + termcount; ep++)
186		if (ep->tterm.Booleans[idx] == TRUE) {
187		    is_set = entries[0].tterm.Booleans[idx];
188		    break;
189		}
190	    if (is_set != entries[0].tterm.Booleans[idx])
191		return (!is_set);
192	    else
193		return (FAIL);
194	}
195
196    case NUMBER:
197	{
198	    int value = ABSENT_NUMERIC;
199
200	    /*
201	     * We take the semantics of multiple uses to be 'each
202	     * capability gets the first non-default value found
203	     * in the sequence of use entries'.
204	     */
205	    for (ep = &entries[1]; ep < entries + termcount; ep++)
206		if (VALID_NUMERIC(ep->tterm.Numbers[idx])) {
207		    value = ep->tterm.Numbers[idx];
208		    break;
209		}
210
211	    if (value != entries[0].tterm.Numbers[idx])
212		return (value != ABSENT_NUMERIC);
213	    else
214		return (FAIL);
215	}
216
217    case STRING:
218	{
219	    char *termstr, *usestr = ABSENT_STRING;
220
221	    termstr = entries[0].tterm.Strings[idx];
222
223	    /*
224	     * We take the semantics of multiple uses to be 'each
225	     * capability gets the first non-default value found
226	     * in the sequence of use entries'.
227	     */
228	    for (ep = &entries[1]; ep < entries + termcount; ep++)
229		if (ep->tterm.Strings[idx]) {
230		    usestr = ep->tterm.Strings[idx];
231		    break;
232		}
233
234	    if (usestr == ABSENT_STRING && termstr == ABSENT_STRING)
235		return (FAIL);
236	    else if (!usestr || !termstr || capcmp(idx, usestr, termstr))
237		return (TRUE);
238	    else
239		return (FAIL);
240	}
241    }
242
243    return (FALSE);		/* pacify compiler */
244}
245
246static bool
247useeq(ENTRY * e1, ENTRY * e2)
248/* are the use references in two entries equivalent? */
249{
250    unsigned i, j;
251
252    if (e1->nuses != e2->nuses)
253	return (FALSE);
254
255    /* Ugh...this is quadratic again */
256    for (i = 0; i < e1->nuses; i++) {
257	bool foundmatch = FALSE;
258
259	/* search second entry for given use reference */
260	for (j = 0; j < e2->nuses; j++)
261	    if (!strcmp(e1->uses[i].name, e2->uses[j].name)) {
262		foundmatch = TRUE;
263		break;
264	    }
265
266	if (!foundmatch)
267	    return (FALSE);
268    }
269
270    return (TRUE);
271}
272
273static bool
274entryeq(TERMTYPE *t1, TERMTYPE *t2)
275/* are two entries equivalent? */
276{
277    unsigned i;
278
279    for (i = 0; i < NUM_BOOLEANS(t1); i++)
280	if (t1->Booleans[i] != t2->Booleans[i])
281	    return (FALSE);
282
283    for (i = 0; i < NUM_NUMBERS(t1); i++)
284	if (t1->Numbers[i] != t2->Numbers[i])
285	    return (FALSE);
286
287    for (i = 0; i < NUM_STRINGS(t1); i++)
288	if (capcmp((PredIdx) i, t1->Strings[i], t2->Strings[i]))
289	    return (FALSE);
290
291    return (TRUE);
292}
293
294#define TIC_EXPAND(result) _nc_tic_expand(result, outform==F_TERMINFO, numbers)
295
296static void
297print_uses(ENTRY * ep, FILE *fp)
298/* print an entry's use references */
299{
300    unsigned i;
301
302    if (!ep->nuses)
303	fputs("NULL", fp);
304    else
305	for (i = 0; i < ep->nuses; i++) {
306	    fputs(ep->uses[i].name, fp);
307	    if (i < ep->nuses - 1)
308		fputs(" ", fp);
309	}
310}
311
312static const char *
313dump_boolean(int val)
314/* display the value of a boolean capability */
315{
316    switch (val) {
317    case ABSENT_BOOLEAN:
318	return (s_absent);
319    case CANCELLED_BOOLEAN:
320	return (s_cancel);
321    case FALSE:
322	return ("F");
323    case TRUE:
324	return ("T");
325    default:
326	return ("?");
327    }
328}
329
330static void
331dump_numeric(int val, char *buf)
332/* display the value of a boolean capability */
333{
334    switch (val) {
335    case ABSENT_NUMERIC:
336	_nc_STRCPY(buf, s_absent, MAX_STRING);
337	break;
338    case CANCELLED_NUMERIC:
339	_nc_STRCPY(buf, s_cancel, MAX_STRING);
340	break;
341    default:
342	_nc_SPRINTF(buf, _nc_SLIMIT(MAX_STRING) "%d", val);
343	break;
344    }
345}
346
347static void
348dump_string(char *val, char *buf)
349/* display the value of a string capability */
350{
351    if (val == ABSENT_STRING)
352	_nc_STRCPY(buf, s_absent, MAX_STRING);
353    else if (val == CANCELLED_STRING)
354	_nc_STRCPY(buf, s_cancel, MAX_STRING);
355    else {
356	_nc_SPRINTF(buf, _nc_SLIMIT(MAX_STRING)
357		    "'%.*s'", MAX_STRING - 3, TIC_EXPAND(val));
358    }
359}
360
361/*
362 * Show "comparing..." message for the given terminal names.
363 */
364static void
365show_comparing(char **names)
366{
367    if (itrace) {
368	switch (compare) {
369	case C_DIFFERENCE:
370	    (void) fprintf(stderr, "%s: dumping differences\n", _nc_progname);
371	    break;
372
373	case C_COMMON:
374	    (void) fprintf(stderr, "%s: dumping common capabilities\n", _nc_progname);
375	    break;
376
377	case C_NAND:
378	    (void) fprintf(stderr, "%s: dumping differences\n", _nc_progname);
379	    break;
380	}
381    }
382    if (*names) {
383	printf("comparing %s", *names++);
384	if (*names) {
385	    printf(" to %s", *names++);
386	    while (*names) {
387		printf(", %s", *names++);
388	    }
389	}
390	printf(".\n");
391    }
392}
393
394/*
395 * ncurses stores two types of non-standard capabilities:
396 * a) capabilities listed past the "STOP-HERE" comment in the Caps file.
397 *    These are used in the terminfo source file to provide data for termcaps,
398 *    e.g., when there is no equivalent capability in terminfo, as well as for
399 *    widely-used non-standard capabilities.
400 * b) user-definable capabilities, via "tic -x".
401 *
402 * However, if "-x" is omitted from the tic command, both types of
403 * non-standard capability are not loaded into the terminfo database.  This
404 * macro is used for limit-checks against the symbols that tic uses to omit
405 * the two types of non-standard entry.
406 */
407#if NCURSES_XNAMES
408#define check_user_definable(n,limit) if (!_nc_user_definable && (n) > (limit)) break
409#else
410#define check_user_definable(n,limit) if ((n) > (limit)) break
411#endif
412
413/*
414 * Use these macros to simplify loops on C_COMMON and C_NAND:
415 */
416#define for_each_entry() while (entries[extra].tterm.term_names)
417#define next_entry           (&(entries[extra++].tterm))
418
419static void
420compare_predicate(PredType type, PredIdx idx, const char *name)
421/* predicate function to use for entry difference reports */
422{
423    ENTRY *e1 = &entries[0];
424    ENTRY *e2 = &entries[1];
425    char buf1[MAX_STRING];
426    char buf2[MAX_STRING];
427    int b1, b2;
428    int n1, n2;
429    char *s1, *s2;
430    bool found;
431    int extra = 1;
432
433    switch (type) {
434    case CMP_BOOLEAN:
435	check_user_definable(idx, BOOLWRITE);
436	b1 = e1->tterm.Booleans[idx];
437	switch (compare) {
438	case C_DIFFERENCE:
439	    b2 = next_entry->Booleans[idx];
440	    if (!(b1 == ABSENT_BOOLEAN && b2 == ABSENT_BOOLEAN) && b1 != b2)
441		(void) printf("\t%s: %s%s%s.\n",
442			      name,
443			      dump_boolean(b1),
444			      bool_sep,
445			      dump_boolean(b2));
446	    break;
447
448	case C_COMMON:
449	    if (b1 != ABSENT_BOOLEAN) {
450		found = TRUE;
451		for_each_entry() {
452		    b2 = next_entry->Booleans[idx];
453		    if (b1 != b2) {
454			found = FALSE;
455			break;
456		    }
457		}
458		if (found) {
459		    (void) printf("\t%s= %s.\n", name, dump_boolean(b1));
460		}
461	    }
462	    break;
463
464	case C_NAND:
465	    if (b1 == ABSENT_BOOLEAN) {
466		found = TRUE;
467		for_each_entry() {
468		    b2 = next_entry->Booleans[idx];
469		    if (b1 != b2) {
470			found = FALSE;
471			break;
472		    }
473		}
474		if (found) {
475		    (void) printf("\t!%s.\n", name);
476		}
477	    }
478	    break;
479	}
480	break;
481
482    case CMP_NUMBER:
483	check_user_definable(idx, NUMWRITE);
484	n1 = e1->tterm.Numbers[idx];
485	switch (compare) {
486	case C_DIFFERENCE:
487	    n2 = next_entry->Numbers[idx];
488	    if (!((n1 == ABSENT_NUMERIC && n2 == ABSENT_NUMERIC)) && n1 != n2) {
489		dump_numeric(n1, buf1);
490		dump_numeric(n2, buf2);
491		(void) printf("\t%s: %s, %s.\n", name, buf1, buf2);
492	    }
493	    break;
494
495	case C_COMMON:
496	    if (n1 != ABSENT_NUMERIC) {
497		found = TRUE;
498		for_each_entry() {
499		    n2 = next_entry->Numbers[idx];
500		    if (n1 != n2) {
501			found = FALSE;
502			break;
503		    }
504		}
505		if (found) {
506		    dump_numeric(n1, buf1);
507		    (void) printf("\t%s= %s.\n", name, buf1);
508		}
509	    }
510	    break;
511
512	case C_NAND:
513	    if (n1 == ABSENT_NUMERIC) {
514		found = TRUE;
515		for_each_entry() {
516		    n2 = next_entry->Numbers[idx];
517		    if (n1 != n2) {
518			found = FALSE;
519			break;
520		    }
521		}
522		if (found) {
523		    (void) printf("\t!%s.\n", name);
524		}
525	    }
526	    break;
527	}
528	break;
529
530    case CMP_STRING:
531	check_user_definable(idx, STRWRITE);
532	s1 = e1->tterm.Strings[idx];
533	switch (compare) {
534	case C_DIFFERENCE:
535	    s2 = next_entry->Strings[idx];
536	    if (capcmp(idx, s1, s2)) {
537		dump_string(s1, buf1);
538		dump_string(s2, buf2);
539		if (strcmp(buf1, buf2))
540		    (void) printf("\t%s: %s, %s.\n", name, buf1, buf2);
541	    }
542	    break;
543
544	case C_COMMON:
545	    if (s1 != ABSENT_STRING) {
546		found = TRUE;
547		for_each_entry() {
548		    s2 = next_entry->Strings[idx];
549		    if (capcmp(idx, s1, s2) != 0) {
550			found = FALSE;
551			break;
552		    }
553		}
554		if (found) {
555		    (void) printf("\t%s= '%s'.\n", name, TIC_EXPAND(s1));
556		}
557	    }
558	    break;
559
560	case C_NAND:
561	    if (s1 == ABSENT_STRING) {
562		found = TRUE;
563		for_each_entry() {
564		    s2 = next_entry->Strings[idx];
565		    if (s2 != s1) {
566			found = FALSE;
567			break;
568		    }
569		}
570		if (found) {
571		    (void) printf("\t!%s.\n", name);
572		}
573	    }
574	    break;
575	}
576	break;
577
578    case CMP_USE:
579	/* unlike the other modes, this compares *all* use entries */
580	switch (compare) {
581	case C_DIFFERENCE:
582	    if (!useeq(e1, e2)) {
583		(void) fputs("\tuse: ", stdout);
584		print_uses(e1, stdout);
585		fputs(", ", stdout);
586		print_uses(e2, stdout);
587		fputs(".\n", stdout);
588	    }
589	    break;
590
591	case C_COMMON:
592	    if (e1->nuses) {
593		found = TRUE;
594		for_each_entry() {
595		    e2 = &entries[extra++];
596		    if (e2->nuses != e1->nuses || !useeq(e1, e2)) {
597			found = FALSE;
598			break;
599		    }
600		}
601		if (found) {
602		    (void) fputs("\tuse: ", stdout);
603		    print_uses(e1, stdout);
604		    fputs(".\n", stdout);
605		}
606	    }
607	    break;
608
609	case C_NAND:
610	    if (!e1->nuses) {
611		found = TRUE;
612		for_each_entry() {
613		    e2 = &entries[extra++];
614		    if (e2->nuses != e1->nuses) {
615			found = FALSE;
616			break;
617		    }
618		}
619		if (found) {
620		    (void) printf("\t!use.\n");
621		}
622	    }
623	    break;
624	}
625    }
626}
627
628/***************************************************************************
629 *
630 * Init string analysis
631 *
632 ***************************************************************************/
633
634typedef struct {
635    const char *from;
636    const char *to;
637} assoc;
638
639static const assoc std_caps[] =
640{
641    /* these are specified by X.364 and iBCS2 */
642    {"\033c", "RIS"},		/* full reset */
643    {"\0337", "SC"},		/* save cursor */
644    {"\0338", "RC"},		/* restore cursor */
645    {"\033[r", "RSR"},		/* not an X.364 mnemonic */
646    {"\033[m", "SGR0"},		/* not an X.364 mnemonic */
647    {"\033[2J", "ED2"},		/* clear page */
648
649    /* this group is specified by ISO 2022 */
650    {"\033(0", "ISO DEC G0"},	/* enable DEC graphics for G0 */
651    {"\033(A", "ISO UK G0"},	/* enable UK chars for G0 */
652    {"\033(B", "ISO US G0"},	/* enable US chars for G0 */
653    {"\033)0", "ISO DEC G1"},	/* enable DEC graphics for G1 */
654    {"\033)A", "ISO UK G1"},	/* enable UK chars for G1 */
655    {"\033)B", "ISO US G1"},	/* enable US chars for G1 */
656
657    /* these are DEC private controls widely supported by emulators */
658    {"\033=", "DECPAM"},	/* application keypad mode */
659    {"\033>", "DECPNM"},	/* normal keypad mode */
660    {"\033<", "DECANSI"},	/* enter ANSI mode */
661    {"\033[!p", "DECSTR"},	/* soft reset */
662    {"\033 F", "S7C1T"},	/* 7-bit controls */
663
664    {(char *) 0, (char *) 0}
665};
666
667static const assoc std_modes[] =
668/* ECMA \E[ ... [hl] modes recognized by many emulators */
669{
670    {"2", "AM"},		/* keyboard action mode */
671    {"4", "IRM"},		/* insert/replace mode */
672    {"12", "SRM"},		/* send/receive mode */
673    {"20", "LNM"},		/* linefeed mode */
674    {(char *) 0, (char *) 0}
675};
676
677static const assoc private_modes[] =
678/* DEC \E[ ... [hl] modes recognized by many emulators */
679{
680    {"1", "CKM"},		/* application cursor keys */
681    {"2", "ANM"},		/* set VT52 mode */
682    {"3", "COLM"},		/* 132-column mode */
683    {"4", "SCLM"},		/* smooth scroll */
684    {"5", "SCNM"},		/* reverse video mode */
685    {"6", "OM"},		/* origin mode */
686    {"7", "AWM"},		/* wraparound mode */
687    {"8", "ARM"},		/* auto-repeat mode */
688    {(char *) 0, (char *) 0}
689};
690
691static const assoc ecma_highlights[] =
692/* recognize ECMA attribute sequences */
693{
694    {"0", "NORMAL"},		/* normal */
695    {"1", "+BOLD"},		/* bold on */
696    {"2", "+DIM"},		/* dim on */
697    {"3", "+ITALIC"},		/* italic on */
698    {"4", "+UNDERLINE"},	/* underline on */
699    {"5", "+BLINK"},		/* blink on */
700    {"6", "+FASTBLINK"},	/* fastblink on */
701    {"7", "+REVERSE"},		/* reverse on */
702    {"8", "+INVISIBLE"},	/* invisible on */
703    {"9", "+DELETED"},		/* deleted on */
704    {"10", "MAIN-FONT"},	/* select primary font */
705    {"11", "ALT-FONT-1"},	/* select alternate font 1 */
706    {"12", "ALT-FONT-2"},	/* select alternate font 2 */
707    {"13", "ALT-FONT-3"},	/* select alternate font 3 */
708    {"14", "ALT-FONT-4"},	/* select alternate font 4 */
709    {"15", "ALT-FONT-5"},	/* select alternate font 5 */
710    {"16", "ALT-FONT-6"},	/* select alternate font 6 */
711    {"17", "ALT-FONT-7"},	/* select alternate font 7 */
712    {"18", "ALT-FONT-1"},	/* select alternate font 1 */
713    {"19", "ALT-FONT-1"},	/* select alternate font 1 */
714    {"20", "FRAKTUR"},		/* Fraktur font */
715    {"21", "DOUBLEUNDER"},	/* double underline */
716    {"22", "-DIM"},		/* dim off */
717    {"23", "-ITALIC"},		/* italic off */
718    {"24", "-UNDERLINE"},	/* underline off */
719    {"25", "-BLINK"},		/* blink off */
720    {"26", "-FASTBLINK"},	/* fastblink off */
721    {"27", "-REVERSE"},		/* reverse off */
722    {"28", "-INVISIBLE"},	/* invisible off */
723    {"29", "-DELETED"},		/* deleted off */
724    {(char *) 0, (char *) 0}
725};
726
727static int
728skip_csi(const char *cap)
729{
730    int result = 0;
731    if (cap[0] == '\033' && cap[1] == '[')
732	result = 2;
733    else if (UChar(cap[0]) == 0233)
734	result = 1;
735    return result;
736}
737
738static bool
739same_param(const char *table, const char *param, size_t length)
740{
741    bool result = FALSE;
742    if (strncmp(table, param, length) == 0) {
743	result = !isdigit(UChar(param[length]));
744    }
745    return result;
746}
747
748static char *
749lookup_params(const assoc * table, char *dst, char *src)
750{
751    char *result = 0;
752    const char *ep = strtok(src, ";");
753
754    if (ep != 0) {
755	const assoc *ap;
756
757	do {
758	    bool found = FALSE;
759
760	    for (ap = table; ap->from; ap++) {
761		size_t tlen = strlen(ap->from);
762
763		if (same_param(ap->from, ep, tlen)) {
764		    _nc_STRCAT(dst, ap->to, MAX_TERMINFO_LENGTH);
765		    found = TRUE;
766		    break;
767		}
768	    }
769
770	    if (!found)
771		_nc_STRCAT(dst, ep, MAX_TERMINFO_LENGTH);
772	    _nc_STRCAT(dst, ";", MAX_TERMINFO_LENGTH);
773	} while
774	    ((ep = strtok((char *) 0, ";")));
775
776	dst[strlen(dst) - 1] = '\0';
777
778	result = dst;
779    }
780    return result;
781}
782
783static void
784analyze_string(const char *name, const char *cap, TERMTYPE *tp)
785{
786    char buf2[MAX_TERMINFO_LENGTH];
787    const char *sp;
788    const assoc *ap;
789    int tp_lines = tp->Numbers[2];
790
791    if (!VALID_STRING(cap))
792	return;
793    (void) printf("%s: ", name);
794
795    for (sp = cap; *sp; sp++) {
796	int i;
797	int csi;
798	size_t len = 0;
799	size_t next;
800	const char *expansion = 0;
801	char buf3[MAX_TERMINFO_LENGTH];
802
803	/* first, check other capabilities in this entry */
804	for (i = 0; i < STRCOUNT; i++) {
805	    char *cp = tp->Strings[i];
806
807	    /* don't use function-key capabilities */
808	    if (strnames[i][0] == 'k' && strnames[i][1] == 'f')
809		continue;
810
811	    if (VALID_STRING(cp) &&
812		cp[0] != '\0' &&
813		cp != cap) {
814		len = strlen(cp);
815		(void) strncpy(buf2, sp, len);
816		buf2[len] = '\0';
817
818		if (_nc_capcmp(cp, buf2))
819		    continue;
820
821#define ISRS(s)	(!strncmp((s), "is", (size_t) 2) || !strncmp((s), "rs", (size_t) 2))
822		/*
823		 * Theoretically we just passed the test for translation
824		 * (equality once the padding is stripped).  However, there
825		 * are a few more hoops that need to be jumped so that
826		 * identical pairs of initialization and reset strings
827		 * don't just refer to each other.
828		 */
829		if (ISRS(name) || ISRS(strnames[i]))
830		    if (cap < cp)
831			continue;
832#undef ISRS
833
834		expansion = strnames[i];
835		break;
836	    }
837	}
838
839	/* now check the standard capabilities */
840	if (!expansion) {
841	    csi = skip_csi(sp);
842	    for (ap = std_caps; ap->from; ap++) {
843		size_t adj = (size_t) (csi ? 2 : 0);
844
845		len = strlen(ap->from);
846		if (csi && skip_csi(ap->from) != csi)
847		    continue;
848		if (len > adj
849		    && strncmp(ap->from + adj, sp + csi, len - adj) == 0) {
850		    expansion = ap->to;
851		    len -= adj;
852		    len += (size_t) csi;
853		    break;
854		}
855	    }
856	}
857
858	/* now check for standard-mode sequences */
859	if (!expansion
860	    && (csi = skip_csi(sp)) != 0
861	    && (len = (strspn) (sp + csi, "0123456789;"))
862	    && (len < sizeof(buf3))
863	    && (next = (size_t) csi + len)
864	    && ((sp[next] == 'h') || (sp[next] == 'l'))) {
865
866	    _nc_STRCPY(buf2,
867		       ((sp[next] == 'h')
868			? "ECMA+"
869			: "ECMA-"),
870		       sizeof(buf2));
871	    (void) strncpy(buf3, sp + csi, len);
872	    buf3[len] = '\0';
873	    len += (size_t) csi + 1;
874
875	    expansion = lookup_params(std_modes, buf2, buf3);
876	}
877
878	/* now check for private-mode sequences */
879	if (!expansion
880	    && (csi = skip_csi(sp)) != 0
881	    && sp[csi] == '?'
882	    && (len = (strspn) (sp + csi + 1, "0123456789;"))
883	    && (len < sizeof(buf3))
884	    && (next = (size_t) csi + 1 + len)
885	    && ((sp[next] == 'h') || (sp[next] == 'l'))) {
886
887	    _nc_STRCPY(buf2,
888		       ((sp[next] == 'h')
889			? "DEC+"
890			: "DEC-"),
891		       sizeof(buf2));
892	    (void) strncpy(buf3, sp + csi + 1, len);
893	    buf3[len] = '\0';
894	    len += (size_t) csi + 2;
895
896	    expansion = lookup_params(private_modes, buf2, buf3);
897	}
898
899	/* now check for ECMA highlight sequences */
900	if (!expansion
901	    && (csi = skip_csi(sp)) != 0
902	    && (len = (strspn) (sp + csi, "0123456789;")) != 0
903	    && (len < sizeof(buf3))
904	    && (next = (size_t) csi + len)
905	    && sp[next] == 'm') {
906
907	    _nc_STRCPY(buf2, "SGR:", sizeof(buf2));
908	    (void) strncpy(buf3, sp + csi, len);
909	    buf3[len] = '\0';
910	    len += (size_t) csi + 1;
911
912	    expansion = lookup_params(ecma_highlights, buf2, buf3);
913	}
914
915	if (!expansion
916	    && (csi = skip_csi(sp)) != 0
917	    && sp[csi] == 'm') {
918	    len = (size_t) csi + 1;
919	    _nc_STRCPY(buf2, "SGR:", sizeof(buf2));
920	    _nc_STRCAT(buf2, ecma_highlights[0].to, sizeof(buf2));
921	    expansion = buf2;
922	}
923
924	/* now check for scroll region reset */
925	if (!expansion
926	    && (csi = skip_csi(sp)) != 0) {
927	    if (sp[csi] == 'r') {
928		expansion = "RSR";
929		len = 1;
930	    } else {
931		_nc_SPRINTF(buf2, _nc_SLIMIT(sizeof(buf2)) "1;%dr", tp_lines);
932		len = strlen(buf2);
933		if (strncmp(buf2, sp + csi, len) == 0)
934		    expansion = "RSR";
935	    }
936	    len += (size_t) csi;
937	}
938
939	/* now check for home-down */
940	if (!expansion
941	    && (csi = skip_csi(sp)) != 0) {
942	    _nc_SPRINTF(buf2, _nc_SLIMIT(sizeof(buf2)) "%d;1H", tp_lines);
943	    len = strlen(buf2);
944	    if (strncmp(buf2, sp + csi, len) == 0) {
945		expansion = "LL";
946	    } else {
947		_nc_SPRINTF(buf2, _nc_SLIMIT(sizeof(buf2)) "%dH", tp_lines);
948		len = strlen(buf2);
949		if (strncmp(buf2, sp + csi, len) == 0) {
950		    expansion = "LL";
951		}
952	    }
953	    len += (size_t) csi;
954	}
955
956	/* now look at the expansion we got, if any */
957	if (expansion) {
958	    printf("{%s}", expansion);
959	    sp += len - 1;
960	} else {
961	    /* couldn't match anything */
962	    buf2[0] = *sp;
963	    buf2[1] = '\0';
964	    fputs(TIC_EXPAND(buf2), stdout);
965	}
966    }
967    putchar('\n');
968}
969
970/***************************************************************************
971 *
972 * File comparison
973 *
974 ***************************************************************************/
975
976static void
977file_comparison(int argc, char *argv[])
978{
979#define MAXCOMPARE	2
980    /* someday we may allow comparisons on more files */
981    int filecount = 0;
982    ENTRY *heads[MAXCOMPARE];
983    ENTRY *qp, *rp;
984    int i, n;
985
986    memset(heads, 0, sizeof(heads));
987    dump_init((char *) 0, F_LITERAL, S_TERMINFO, 0, 65535, itrace, FALSE);
988
989    for (n = 0; n < argc && n < MAXCOMPARE; n++) {
990	if (freopen(argv[n], "r", stdin) == 0)
991	    _nc_err_abort("Can't open %s", argv[n]);
992
993#if NO_LEAKS
994	entered[n].head = _nc_head;
995	entered[n].tail = _nc_tail;
996#endif
997	_nc_head = _nc_tail = 0;
998
999	/* parse entries out of the source file */
1000	_nc_set_source(argv[n]);
1001	_nc_read_entry_source(stdin, NULL, TRUE, literal, NULLHOOK);
1002
1003	if (itrace)
1004	    (void) fprintf(stderr, "Resolving file %d...\n", n - 0);
1005
1006	/* maybe do use resolution */
1007	if (!_nc_resolve_uses2(!limited, literal)) {
1008	    (void) fprintf(stderr,
1009			   "There are unresolved use entries in %s:\n",
1010			   argv[n]);
1011	    for_entry_list(qp) {
1012		if (qp->nuses) {
1013		    (void) fputs(qp->tterm.term_names, stderr);
1014		    (void) fputc('\n', stderr);
1015		}
1016	    }
1017	    ExitProgram(EXIT_FAILURE);
1018	}
1019
1020	heads[filecount] = _nc_head;
1021	filecount++;
1022    }
1023
1024    /* OK, all entries are in core.  Ready to do the comparison */
1025    if (itrace)
1026	(void) fprintf(stderr, "Entries are now in core...\n");
1027
1028    /* The entry-matching loop. Sigh, this is intrinsically quadratic. */
1029    for (qp = heads[0]; qp; qp = qp->next) {
1030	for (rp = heads[1]; rp; rp = rp->next)
1031	    if (_nc_entry_match(qp->tterm.term_names, rp->tterm.term_names)) {
1032		if (qp->ncrosslinks < MAX_CROSSLINKS)
1033		    qp->crosslinks[qp->ncrosslinks] = rp;
1034		qp->ncrosslinks++;
1035
1036		if (rp->ncrosslinks < MAX_CROSSLINKS)
1037		    rp->crosslinks[rp->ncrosslinks] = qp;
1038		rp->ncrosslinks++;
1039	    }
1040    }
1041
1042    /* now we have two circular lists with crosslinks */
1043    if (itrace)
1044	(void) fprintf(stderr, "Name matches are done...\n");
1045
1046    for (qp = heads[0]; qp; qp = qp->next) {
1047	if (qp->ncrosslinks > 1) {
1048	    (void) fprintf(stderr,
1049			   "%s in file 1 (%s) has %d matches in file 2 (%s):\n",
1050			   _nc_first_name(qp->tterm.term_names),
1051			   argv[0],
1052			   qp->ncrosslinks,
1053			   argv[1]);
1054	    for (i = 0; i < qp->ncrosslinks; i++)
1055		(void) fprintf(stderr,
1056			       "\t%s\n",
1057			       _nc_first_name((qp->crosslinks[i])->tterm.term_names));
1058	}
1059    }
1060
1061    for (rp = heads[1]; rp; rp = rp->next) {
1062	if (rp->ncrosslinks > 1) {
1063	    (void) fprintf(stderr,
1064			   "%s in file 2 (%s) has %d matches in file 1 (%s):\n",
1065			   _nc_first_name(rp->tterm.term_names),
1066			   argv[1],
1067			   rp->ncrosslinks,
1068			   argv[0]);
1069	    for (i = 0; i < rp->ncrosslinks; i++)
1070		(void) fprintf(stderr,
1071			       "\t%s\n",
1072			       _nc_first_name((rp->crosslinks[i])->tterm.term_names));
1073	}
1074    }
1075
1076    (void) printf("In file 1 (%s) only:\n", argv[0]);
1077    for (qp = heads[0]; qp; qp = qp->next)
1078	if (qp->ncrosslinks == 0)
1079	    (void) printf("\t%s\n",
1080			  _nc_first_name(qp->tterm.term_names));
1081
1082    (void) printf("In file 2 (%s) only:\n", argv[1]);
1083    for (rp = heads[1]; rp; rp = rp->next)
1084	if (rp->ncrosslinks == 0)
1085	    (void) printf("\t%s\n",
1086			  _nc_first_name(rp->tterm.term_names));
1087
1088    (void) printf("The following entries are equivalent:\n");
1089    for (qp = heads[0]; qp; qp = qp->next) {
1090	if (qp->ncrosslinks == 1) {
1091	    rp = qp->crosslinks[0];
1092
1093	    repair_acsc(&qp->tterm);
1094	    repair_acsc(&rp->tterm);
1095#if NCURSES_XNAMES
1096	    _nc_align_termtype(&qp->tterm, &rp->tterm);
1097#endif
1098	    if (entryeq(&qp->tterm, &rp->tterm) && useeq(qp, rp)) {
1099		char name1[NAMESIZE], name2[NAMESIZE];
1100
1101		(void) canonical_name(qp->tterm.term_names, name1);
1102		(void) canonical_name(rp->tterm.term_names, name2);
1103
1104		(void) printf("%s = %s\n", name1, name2);
1105	    }
1106	}
1107    }
1108
1109    (void) printf("Differing entries:\n");
1110    termcount = 2;
1111    for (qp = heads[0]; qp; qp = qp->next) {
1112
1113	if (qp->ncrosslinks == 1) {
1114	    rp = qp->crosslinks[0];
1115#if NCURSES_XNAMES
1116	    /* sorry - we have to do this on each pass */
1117	    _nc_align_termtype(&qp->tterm, &rp->tterm);
1118#endif
1119	    if (!(entryeq(&qp->tterm, &rp->tterm) && useeq(qp, rp))) {
1120		char name1[NAMESIZE], name2[NAMESIZE];
1121		char *names[3];
1122
1123		names[0] = name1;
1124		names[1] = name2;
1125		names[2] = 0;
1126
1127		entries[0] = *qp;
1128		entries[1] = *rp;
1129
1130		(void) canonical_name(qp->tterm.term_names, name1);
1131		(void) canonical_name(rp->tterm.term_names, name2);
1132
1133		switch (compare) {
1134		case C_DIFFERENCE:
1135		    show_comparing(names);
1136		    compare_entry(compare_predicate, &entries->tterm, quiet);
1137		    break;
1138
1139		case C_COMMON:
1140		    show_comparing(names);
1141		    compare_entry(compare_predicate, &entries->tterm, quiet);
1142		    break;
1143
1144		case C_NAND:
1145		    show_comparing(names);
1146		    compare_entry(compare_predicate, &entries->tterm, quiet);
1147		    break;
1148
1149		}
1150	    }
1151	}
1152    }
1153}
1154
1155static void
1156usage(void)
1157{
1158    static const char *tbl[] =
1159    {
1160	"Usage: infocmp [options] [-A directory] [-B directory] [termname...]"
1161	,""
1162	,"Options:"
1163	,"  -0    print single-row"
1164	,"  -1    print single-column"
1165	,"  -K    use termcap-names and BSD syntax"
1166	,"  -C    use termcap-names"
1167	,"  -F    compare terminfo-files"
1168	,"  -I    use terminfo-names"
1169	,"  -L    use long names"
1170	,"  -R subset (see manpage)"
1171	,"  -T    eliminate size limits (test)"
1172	,"  -U    eliminate post-processing of entries"
1173	,"  -D    print database locations"
1174	,"  -V    print version"
1175#if NCURSES_XNAMES
1176	,"  -a    with -F, list commented-out caps"
1177#endif
1178	,"  -c    list common capabilities"
1179	,"  -d    list different capabilities"
1180	,"  -e    format output for C initializer"
1181	,"  -E    format output as C tables"
1182	,"  -f    with -1, format complex strings"
1183	,"  -G    format %{number} to %'char'"
1184	,"  -g    format %'char' to %{number}"
1185	,"  -i    analyze initialization/reset"
1186	,"  -l    output terminfo names"
1187	,"  -n    list capabilities in neither"
1188	,"  -p    ignore padding specifiers"
1189	,"  -q    brief listing, removes headers"
1190	,"  -r    with -C, output in termcap form"
1191	,"  -r    with -F, resolve use-references"
1192	,"  -s [d|i|l|c] sort fields"
1193#if NCURSES_XNAMES
1194	,"  -t    suppress commented-out capabilities"
1195#endif
1196	,"  -u    produce source with 'use='"
1197	,"  -v number  (verbose)"
1198	,"  -w number  (width)"
1199#if NCURSES_XNAMES
1200	,"  -x    treat unknown capabilities as user-defined"
1201#endif
1202    };
1203    const size_t first = 3;
1204    const size_t last = SIZEOF(tbl);
1205    const size_t left = (last - first + 1) / 2 + first;
1206    size_t n;
1207
1208    for (n = 0; n < left; n++) {
1209	size_t m = (n < first) ? last : n + left - first;
1210	if (m < last)
1211	    fprintf(stderr, "%-40.40s%s\n", tbl[n], tbl[m]);
1212	else
1213	    fprintf(stderr, "%s\n", tbl[n]);
1214    }
1215    ExitProgram(EXIT_FAILURE);
1216}
1217
1218static char *
1219any_initializer(const char *fmt, const char *type)
1220{
1221    static char *initializer;
1222    static size_t need;
1223    char *s;
1224
1225    if (initializer == 0) {
1226	need = (strlen(entries->tterm.term_names)
1227		+ strlen(type)
1228		+ strlen(fmt));
1229	initializer = (char *) malloc(need + 1);
1230	if (initializer == 0)
1231	    failed("any_initializer");
1232    }
1233
1234    _nc_STRCPY(initializer, entries->tterm.term_names, need);
1235    for (s = initializer; *s != 0 && *s != '|'; s++) {
1236	if (!isalnum(UChar(*s)))
1237	    *s = '_';
1238    }
1239    *s = 0;
1240    _nc_SPRINTF(s, _nc_SLIMIT(need) fmt, type);
1241    return initializer;
1242}
1243
1244static char *
1245name_initializer(const char *type)
1246{
1247    return any_initializer("_%s_data", type);
1248}
1249
1250static char *
1251string_variable(const char *type)
1252{
1253    return any_initializer("_s_%s", type);
1254}
1255
1256/* dump C initializers for the terminal type */
1257static void
1258dump_initializers(TERMTYPE *term)
1259{
1260    unsigned n;
1261    const char *str = 0;
1262
1263    printf("\nstatic char %s[] = \"%s\";\n\n",
1264	   name_initializer("alias"), entries->tterm.term_names);
1265
1266    for_each_string(n, term) {
1267	char buf[MAX_STRING], *sp, *tp;
1268
1269	if (VALID_STRING(term->Strings[n])) {
1270	    tp = buf;
1271#define TP_LIMIT	((MAX_STRING - 5) - (size_t)(tp - buf))
1272	    *tp++ = '"';
1273	    for (sp = term->Strings[n];
1274		 *sp != 0 && TP_LIMIT > 2;
1275		 sp++) {
1276		if (isascii(UChar(*sp))
1277		    && isprint(UChar(*sp))
1278		    && *sp != '\\'
1279		    && *sp != '"')
1280		    *tp++ = *sp;
1281		else {
1282		    _nc_SPRINTF(tp, _nc_SLIMIT(TP_LIMIT) "\\%03o", UChar(*sp));
1283		    tp += 4;
1284		}
1285	    }
1286	    *tp++ = '"';
1287	    *tp = '\0';
1288	    (void) printf("static char %-20s[] = %s;\n",
1289			  string_variable(ExtStrname(term, (int) n, strnames)),
1290			  buf);
1291	}
1292    }
1293    printf("\n");
1294
1295    (void) printf("static char %s[] = %s\n", name_initializer("bool"), L_CURL);
1296
1297    for_each_boolean(n, term) {
1298	switch ((int) (term->Booleans[n])) {
1299	case TRUE:
1300	    str = "TRUE";
1301	    break;
1302
1303	case FALSE:
1304	    str = "FALSE";
1305	    break;
1306
1307	case ABSENT_BOOLEAN:
1308	    str = "ABSENT_BOOLEAN";
1309	    break;
1310
1311	case CANCELLED_BOOLEAN:
1312	    str = "CANCELLED_BOOLEAN";
1313	    break;
1314	}
1315	(void) printf("\t/* %3u: %-8s */\t%s,\n",
1316		      n, ExtBoolname(term, (int) n, boolnames), str);
1317    }
1318    (void) printf("%s;\n", R_CURL);
1319
1320    (void) printf("static short %s[] = %s\n", name_initializer("number"), L_CURL);
1321
1322    for_each_number(n, term) {
1323	char buf[BUFSIZ];
1324	switch (term->Numbers[n]) {
1325	case ABSENT_NUMERIC:
1326	    str = "ABSENT_NUMERIC";
1327	    break;
1328	case CANCELLED_NUMERIC:
1329	    str = "CANCELLED_NUMERIC";
1330	    break;
1331	default:
1332	    _nc_SPRINTF(buf, _nc_SLIMIT(sizeof(buf)) "%d", term->Numbers[n]);
1333	    str = buf;
1334	    break;
1335	}
1336	(void) printf("\t/* %3u: %-8s */\t%s,\n", n,
1337		      ExtNumname(term, (int) n, numnames), str);
1338    }
1339    (void) printf("%s;\n", R_CURL);
1340
1341    (void) printf("static char * %s[] = %s\n", name_initializer("string"), L_CURL);
1342
1343    for_each_string(n, term) {
1344
1345	if (term->Strings[n] == ABSENT_STRING)
1346	    str = "ABSENT_STRING";
1347	else if (term->Strings[n] == CANCELLED_STRING)
1348	    str = "CANCELLED_STRING";
1349	else {
1350	    str = string_variable(ExtStrname(term, (int) n, strnames));
1351	}
1352	(void) printf("\t/* %3u: %-8s */\t%s,\n", n,
1353		      ExtStrname(term, (int) n, strnames), str);
1354    }
1355    (void) printf("%s;\n", R_CURL);
1356
1357#if NCURSES_XNAMES
1358    if ((NUM_BOOLEANS(term) != BOOLCOUNT)
1359	|| (NUM_NUMBERS(term) != NUMCOUNT)
1360	|| (NUM_STRINGS(term) != STRCOUNT)) {
1361	(void) printf("static char * %s[] = %s\n",
1362		      name_initializer("string_ext"), L_CURL);
1363	for (n = BOOLCOUNT; n < NUM_BOOLEANS(term); ++n) {
1364	    (void) printf("\t/* %3u: bool */\t\"%s\",\n",
1365			  n, ExtBoolname(term, (int) n, boolnames));
1366	}
1367	for (n = NUMCOUNT; n < NUM_NUMBERS(term); ++n) {
1368	    (void) printf("\t/* %3u: num */\t\"%s\",\n",
1369			  n, ExtNumname(term, (int) n, numnames));
1370	}
1371	for (n = STRCOUNT; n < NUM_STRINGS(term); ++n) {
1372	    (void) printf("\t/* %3u: str */\t\"%s\",\n",
1373			  n, ExtStrname(term, (int) n, strnames));
1374	}
1375	(void) printf("%s;\n", R_CURL);
1376    }
1377#endif
1378}
1379
1380/* dump C initializers for the terminal type */
1381static void
1382dump_termtype(TERMTYPE *term)
1383{
1384    (void) printf("\t%s\n\t\t%s,\n", L_CURL, name_initializer("alias"));
1385    (void) printf("\t\t(char *)0,\t/* pointer to string table */\n");
1386
1387    (void) printf("\t\t%s,\n", name_initializer("bool"));
1388    (void) printf("\t\t%s,\n", name_initializer("number"));
1389
1390    (void) printf("\t\t%s,\n", name_initializer("string"));
1391
1392#if NCURSES_XNAMES
1393    (void) printf("#if NCURSES_XNAMES\n");
1394    (void) printf("\t\t(char *)0,\t/* pointer to extended string table */\n");
1395    (void) printf("\t\t%s,\t/* ...corresponding names */\n",
1396		  ((NUM_BOOLEANS(term) != BOOLCOUNT)
1397		   || (NUM_NUMBERS(term) != NUMCOUNT)
1398		   || (NUM_STRINGS(term) != STRCOUNT))
1399		  ? name_initializer("string_ext")
1400		  : "(char **)0");
1401
1402    (void) printf("\t\t%d,\t\t/* count total Booleans */\n", NUM_BOOLEANS(term));
1403    (void) printf("\t\t%d,\t\t/* count total Numbers */\n", NUM_NUMBERS(term));
1404    (void) printf("\t\t%d,\t\t/* count total Strings */\n", NUM_STRINGS(term));
1405
1406    (void) printf("\t\t%d,\t\t/* count extensions to Booleans */\n",
1407		  NUM_BOOLEANS(term) - BOOLCOUNT);
1408    (void) printf("\t\t%d,\t\t/* count extensions to Numbers */\n",
1409		  NUM_NUMBERS(term) - NUMCOUNT);
1410    (void) printf("\t\t%d,\t\t/* count extensions to Strings */\n",
1411		  NUM_STRINGS(term) - STRCOUNT);
1412
1413    (void) printf("#endif /* NCURSES_XNAMES */\n");
1414#else
1415    (void) term;
1416#endif /* NCURSES_XNAMES */
1417    (void) printf("\t%s\n", R_CURL);
1418}
1419
1420static int
1421optarg_to_number(void)
1422{
1423    char *temp = 0;
1424    long value = strtol(optarg, &temp, 0);
1425
1426    if (temp == 0 || temp == optarg || *temp != 0) {
1427	fprintf(stderr, "Expected a number, not \"%s\"\n", optarg);
1428	ExitProgram(EXIT_FAILURE);
1429    }
1430    return (int) value;
1431}
1432
1433static char *
1434terminal_env(void)
1435{
1436    char *terminal;
1437
1438    if ((terminal = getenv("TERM")) == 0) {
1439	(void) fprintf(stderr,
1440		       "%s: environment variable TERM not set\n",
1441		       _nc_progname);
1442	exit(EXIT_FAILURE);
1443    }
1444    return terminal;
1445}
1446
1447/*
1448 * Show the databases that infocmp knows about.  The location to which it writes is
1449 */
1450static void
1451show_databases(void)
1452{
1453    DBDIRS state;
1454    int offset;
1455    const char *path2;
1456
1457    _nc_first_db(&state, &offset);
1458    while ((path2 = _nc_next_db(&state, &offset)) != 0) {
1459	printf("%s\n", path2);
1460    }
1461    _nc_last_db();
1462}
1463
1464/***************************************************************************
1465 *
1466 * Main sequence
1467 *
1468 ***************************************************************************/
1469
1470#if NO_LEAKS
1471#define MAIN_LEAKS() \
1472    free(myargv); \
1473    free(tfile); \
1474    free(tname)
1475#else
1476#define MAIN_LEAKS()		/* nothing */
1477#endif
1478
1479int
1480main(int argc, char *argv[])
1481{
1482    /* Avoid "local data >32k" error with mwcc */
1483    /* Also avoid overflowing smaller stacks on systems like AmigaOS */
1484    path *tfile = 0;
1485    char **tname = 0;
1486    size_t maxterms;
1487
1488    char **myargv;
1489
1490    char *firstdir, *restdir;
1491    int c, i, len;
1492    bool formatted = FALSE;
1493    bool filecompare = FALSE;
1494    int initdump = 0;
1495    bool init_analyze = FALSE;
1496    bool suppress_untranslatable = FALSE;
1497
1498    /* where is the terminfo database location going to default to? */
1499    restdir = firstdir = 0;
1500
1501#if NCURSES_XNAMES
1502    use_extended_names(FALSE);
1503#endif
1504    _nc_strict_bsd = 0;
1505
1506    _nc_progname = _nc_rootname(argv[0]);
1507
1508    /* make sure we have enough space to add two terminal entries */
1509    myargv = typeCalloc(char *, (size_t) (argc + 3));
1510    if (myargv == 0)
1511	failed("myargv");
1512
1513    memcpy(myargv, argv, (sizeof(char *) * (size_t) argc));
1514    argv = myargv;
1515
1516    while ((c = getopt(argc,
1517		       argv,
1518		       "01A:aB:CcDdEeFfGgIiKLlnpqR:rs:TtUuVv:w:x")) != -1) {
1519	switch (c) {
1520	case '0':
1521	    mwidth = 65535;
1522	    mheight = 1;
1523	    break;
1524
1525	case '1':
1526	    mwidth = 0;
1527	    break;
1528
1529	case 'A':
1530	    firstdir = optarg;
1531	    break;
1532
1533#if NCURSES_XNAMES
1534	case 'a':
1535	    _nc_disable_period = TRUE;
1536	    use_extended_names(TRUE);
1537	    break;
1538#endif
1539	case 'B':
1540	    restdir = optarg;
1541	    break;
1542
1543	case 'K':
1544	    _nc_strict_bsd = 1;
1545	    /* FALLTHRU */
1546	case 'C':
1547	    outform = F_TERMCAP;
1548	    tversion = "BSD";
1549	    if (sortmode == S_DEFAULT)
1550		sortmode = S_TERMCAP;
1551	    break;
1552
1553	case 'D':
1554	    show_databases();
1555	    ExitProgram(EXIT_SUCCESS);
1556	    break;
1557
1558	case 'c':
1559	    compare = C_COMMON;
1560	    break;
1561
1562	case 'd':
1563	    compare = C_DIFFERENCE;
1564	    break;
1565
1566	case 'E':
1567	    initdump |= 2;
1568	    break;
1569
1570	case 'e':
1571	    initdump |= 1;
1572	    break;
1573
1574	case 'F':
1575	    filecompare = TRUE;
1576	    break;
1577
1578	case 'f':
1579	    formatted = TRUE;
1580	    break;
1581
1582	case 'G':
1583	    numbers = 1;
1584	    break;
1585
1586	case 'g':
1587	    numbers = -1;
1588	    break;
1589
1590	case 'I':
1591	    outform = F_TERMINFO;
1592	    if (sortmode == S_DEFAULT)
1593		sortmode = S_VARIABLE;
1594	    tversion = 0;
1595	    break;
1596
1597	case 'i':
1598	    init_analyze = TRUE;
1599	    break;
1600
1601	case 'L':
1602	    outform = F_VARIABLE;
1603	    if (sortmode == S_DEFAULT)
1604		sortmode = S_VARIABLE;
1605	    break;
1606
1607	case 'l':
1608	    outform = F_TERMINFO;
1609	    break;
1610
1611	case 'n':
1612	    compare = C_NAND;
1613	    break;
1614
1615	case 'p':
1616	    ignorepads = TRUE;
1617	    break;
1618
1619	case 'q':
1620	    quiet = TRUE;
1621	    s_absent = "-";
1622	    s_cancel = "@";
1623	    bool_sep = ", ";
1624	    break;
1625
1626	case 'R':
1627	    tversion = optarg;
1628	    break;
1629
1630	case 'r':
1631	    tversion = 0;
1632	    break;
1633
1634	case 's':
1635	    if (*optarg == 'd')
1636		sortmode = S_NOSORT;
1637	    else if (*optarg == 'i')
1638		sortmode = S_TERMINFO;
1639	    else if (*optarg == 'l')
1640		sortmode = S_VARIABLE;
1641	    else if (*optarg == 'c')
1642		sortmode = S_TERMCAP;
1643	    else {
1644		(void) fprintf(stderr,
1645			       "%s: unknown sort mode\n",
1646			       _nc_progname);
1647		ExitProgram(EXIT_FAILURE);
1648	    }
1649	    break;
1650
1651	case 'T':
1652	    limited = FALSE;
1653	    break;
1654
1655#if NCURSES_XNAMES
1656	case 't':
1657	    _nc_disable_period = FALSE;
1658	    suppress_untranslatable = TRUE;
1659	    break;
1660#endif
1661
1662	case 'U':
1663	    literal = TRUE;
1664	    break;
1665
1666	case 'u':
1667	    compare = C_USEALL;
1668	    break;
1669
1670	case 'V':
1671	    puts(curses_version());
1672	    ExitProgram(EXIT_SUCCESS);
1673
1674	case 'v':
1675	    itrace = (unsigned) optarg_to_number();
1676	    set_trace_level(itrace);
1677	    break;
1678
1679	case 'w':
1680	    mwidth = optarg_to_number();
1681	    break;
1682
1683#if NCURSES_XNAMES
1684	case 'x':
1685	    use_extended_names(TRUE);
1686	    break;
1687#endif
1688
1689	default:
1690	    usage();
1691	}
1692    }
1693
1694    maxterms = (size_t) (argc + 2 - optind);
1695    if ((tfile = typeMalloc(path, maxterms)) == 0)
1696	failed("tfile");
1697    if ((tname = typeCalloc(char *, maxterms)) == 0)
1698	  failed("tname");
1699    if ((entries = typeCalloc(ENTRY, maxterms)) == 0)
1700	failed("entries");
1701#if NO_LEAKS
1702    if ((entered = typeCalloc(ENTERED, maxterms)) == 0)
1703	failed("entered");
1704#endif
1705
1706    if (tfile == 0
1707	|| tname == 0
1708	|| entries == 0) {
1709	fprintf(stderr, "%s: not enough memory\n", _nc_progname);
1710	ExitProgram(EXIT_FAILURE);
1711    }
1712
1713    /* by default, sort by terminfo name */
1714    if (sortmode == S_DEFAULT)
1715	sortmode = S_TERMINFO;
1716
1717    /* make sure we have at least one terminal name to work with */
1718    if (optind >= argc)
1719	argv[argc++] = terminal_env();
1720
1721    /* if user is after a comparison, make sure we have two entries */
1722    if (compare != C_DEFAULT && optind >= argc - 1)
1723	argv[argc++] = terminal_env();
1724
1725    /* exactly one terminal name with no options means display it */
1726    /* exactly two terminal names with no options means do -d */
1727    if (compare == C_DEFAULT) {
1728	switch (argc - optind) {
1729	default:
1730	    fprintf(stderr, "%s: too many names to compare\n", _nc_progname);
1731	    ExitProgram(EXIT_FAILURE);
1732	case 1:
1733	    break;
1734	case 2:
1735	    compare = C_DIFFERENCE;
1736	    break;
1737	}
1738    }
1739
1740    /* set up for display */
1741    dump_init(tversion, outform, sortmode, mwidth, mheight, itrace, formatted);
1742
1743    if (!filecompare) {
1744	/* grab the entries */
1745	termcount = 0;
1746	for (; optind < argc; optind++) {
1747	    const char *directory = termcount ? restdir : firstdir;
1748	    int status;
1749
1750	    tname[termcount] = argv[optind];
1751
1752	    if (directory) {
1753#if NCURSES_USE_DATABASE
1754#if MIXEDCASE_FILENAMES
1755#define LEAF_FMT "%c"
1756#else
1757#define LEAF_FMT "%02x"
1758#endif
1759		_nc_SPRINTF(tfile[termcount],
1760			    _nc_SLIMIT(sizeof(path))
1761			    "%s/" LEAF_FMT "/%s",
1762			    directory,
1763			    UChar(*argv[optind]), argv[optind]);
1764		if (itrace)
1765		    (void) fprintf(stderr,
1766				   "%s: reading entry %s from file %s\n",
1767				   _nc_progname,
1768				   argv[optind], tfile[termcount]);
1769
1770		status = _nc_read_file_entry(tfile[termcount],
1771					     &entries[termcount].tterm);
1772#else
1773		(void) fprintf(stderr, "%s: terminfo files not supported\n",
1774			       _nc_progname);
1775		MAIN_LEAKS();
1776		ExitProgram(EXIT_FAILURE);
1777#endif
1778	    } else {
1779		if (itrace)
1780		    (void) fprintf(stderr,
1781				   "%s: reading entry %s from database\n",
1782				   _nc_progname,
1783				   tname[termcount]);
1784
1785		status = _nc_read_entry(tname[termcount],
1786					tfile[termcount],
1787					&entries[termcount].tterm);
1788	    }
1789
1790	    if (status <= 0) {
1791		(void) fprintf(stderr,
1792			       "%s: couldn't open terminfo file %s.\n",
1793			       _nc_progname,
1794			       tfile[termcount]);
1795		MAIN_LEAKS();
1796		ExitProgram(EXIT_FAILURE);
1797	    }
1798	    repair_acsc(&entries[termcount].tterm);
1799	    termcount++;
1800	}
1801
1802#if NCURSES_XNAMES
1803	if (termcount > 1)
1804	    _nc_align_termtype(&entries[0].tterm, &entries[1].tterm);
1805#endif
1806
1807	/* dump as C initializer for the terminal type */
1808	if (initdump) {
1809	    if (initdump & 1)
1810		dump_termtype(&entries[0].tterm);
1811	    if (initdump & 2)
1812		dump_initializers(&entries[0].tterm);
1813	}
1814
1815	/* analyze the init strings */
1816	else if (init_analyze) {
1817#undef CUR
1818#define CUR	entries[0].tterm.
1819	    analyze_string("is1", init_1string, &entries[0].tterm);
1820	    analyze_string("is2", init_2string, &entries[0].tterm);
1821	    analyze_string("is3", init_3string, &entries[0].tterm);
1822	    analyze_string("rs1", reset_1string, &entries[0].tterm);
1823	    analyze_string("rs2", reset_2string, &entries[0].tterm);
1824	    analyze_string("rs3", reset_3string, &entries[0].tterm);
1825	    analyze_string("smcup", enter_ca_mode, &entries[0].tterm);
1826	    analyze_string("rmcup", exit_ca_mode, &entries[0].tterm);
1827#undef CUR
1828	} else {
1829
1830	    /*
1831	     * Here's where the real work gets done
1832	     */
1833	    switch (compare) {
1834	    case C_DEFAULT:
1835		if (itrace)
1836		    (void) fprintf(stderr,
1837				   "%s: about to dump %s\n",
1838				   _nc_progname,
1839				   tname[0]);
1840		(void) printf("#\tReconstructed via infocmp from file: %s\n",
1841			      tfile[0]);
1842		dump_entry(&entries[0].tterm,
1843			   suppress_untranslatable,
1844			   limited,
1845			   numbers,
1846			   NULL);
1847		len = show_entry();
1848		if (itrace)
1849		    (void) fprintf(stderr, "%s: length %d\n", _nc_progname, len);
1850		break;
1851
1852	    case C_DIFFERENCE:
1853		show_comparing(tname);
1854		compare_entry(compare_predicate, &entries->tterm, quiet);
1855		break;
1856
1857	    case C_COMMON:
1858		show_comparing(tname);
1859		compare_entry(compare_predicate, &entries->tterm, quiet);
1860		break;
1861
1862	    case C_NAND:
1863		show_comparing(tname);
1864		compare_entry(compare_predicate, &entries->tterm, quiet);
1865		break;
1866
1867	    case C_USEALL:
1868		if (itrace)
1869		    (void) fprintf(stderr, "%s: dumping use entry\n", _nc_progname);
1870		dump_entry(&entries[0].tterm,
1871			   suppress_untranslatable,
1872			   limited,
1873			   numbers,
1874			   use_predicate);
1875		for (i = 1; i < termcount; i++)
1876		    dump_uses(tname[i], !(outform == F_TERMCAP
1877					  || outform == F_TCONVERR));
1878		len = show_entry();
1879		if (itrace)
1880		    (void) fprintf(stderr, "%s: length %d\n", _nc_progname, len);
1881		break;
1882	    }
1883	}
1884    } else if (compare == C_USEALL) {
1885	(void) fprintf(stderr, "Sorry, -u doesn't work with -F\n");
1886    } else if (compare == C_DEFAULT) {
1887	(void) fprintf(stderr, "Use `tic -[CI] <file>' for this.\n");
1888    } else if (argc - optind != 2) {
1889	(void) fprintf(stderr,
1890		       "File comparison needs exactly two file arguments.\n");
1891    } else {
1892	file_comparison(argc - optind, argv + optind);
1893    }
1894
1895    MAIN_LEAKS();
1896    ExitProgram(EXIT_SUCCESS);
1897}
1898
1899/* infocmp.c ends here */
1900