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