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