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