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