ncurses 5.9 - patch 20150530
[ncurses.git] / progs / dump_entry.c
1 /****************************************************************************
2  * Copyright (c) 1998-2014,2015 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 #define __INTERNAL_CAPS_VISIBLE
36 #include <progs.priv.h>
37
38 #include "dump_entry.h"
39 #include "termsort.c"           /* this C file is generated */
40 #include <parametrized.h>       /* so is this */
41
42 MODULE_ID("$Id: dump_entry.c,v 1.115 2015/05/27 00:57:40 tom Exp $")
43
44 #define INDENT                  8
45 #define DISCARD(string) string = ABSENT_STRING
46 #define PRINTF (void) printf
47
48 #define OkIndex(index,array) ((int)(index) >= 0 && (int)(index) < (int) SIZEOF(array))
49
50 typedef struct {
51     char *text;
52     size_t used;
53     size_t size;
54 } DYNBUF;
55
56 static int tversion;            /* terminfo version */
57 static int outform;             /* output format to use */
58 static int sortmode;            /* sort mode to use */
59 static int width = 60;          /* max line width for listings */
60 static int height = 65535;      /* max number of lines for listings */
61 static int column;              /* current column, limited by 'width' */
62 static int oldcol;              /* last value of column before wrap */
63 static bool pretty;             /* true if we format if-then-else strings */
64 static bool checking;           /* true if we are checking for tic */
65
66 static char *save_sgr;
67
68 static DYNBUF outbuf;
69 static DYNBUF tmpbuf;
70
71 /* indirection pointers for implementing sort and display modes */
72 static const PredIdx *bool_indirect, *num_indirect, *str_indirect;
73 static NCURSES_CONST char *const *bool_names;
74 static NCURSES_CONST char *const *num_names;
75 static NCURSES_CONST char *const *str_names;
76
77 static const char *separator = "", *trailer = "";
78
79 /* cover various ports and variants of terminfo */
80 #define V_ALLCAPS       0       /* all capabilities (SVr4, XSI, ncurses) */
81 #define V_SVR1          1       /* SVR1, Ultrix */
82 #define V_HPUX          2       /* HP/UX */
83 #define V_AIX           3       /* AIX */
84 #define V_BSD           4       /* BSD */
85
86 #if NCURSES_XNAMES
87 #define OBSOLETE(n) (!_nc_user_definable && (n[0] == 'O' && n[1] == 'T'))
88 #else
89 #define OBSOLETE(n) (n[0] == 'O' && n[1] == 'T')
90 #endif
91
92 #define isObsolete(f,n) ((f == F_TERMINFO || f == F_VARIABLE) && OBSOLETE(n))
93
94 #if NCURSES_XNAMES
95 #define BoolIndirect(j) ((j >= BOOLCOUNT) ? (j) : ((sortmode == S_NOSORT) ? j : bool_indirect[j]))
96 #define NumIndirect(j)  ((j >= NUMCOUNT)  ? (j) : ((sortmode == S_NOSORT) ? j : num_indirect[j]))
97 #define StrIndirect(j)  ((j >= STRCOUNT)  ? (j) : ((sortmode == S_NOSORT) ? j : str_indirect[j]))
98 #else
99 #define BoolIndirect(j) ((sortmode == S_NOSORT) ? (j) : bool_indirect[j])
100 #define NumIndirect(j)  ((sortmode == S_NOSORT) ? (j) : num_indirect[j])
101 #define StrIndirect(j)  ((sortmode == S_NOSORT) ? (j) : str_indirect[j])
102 #endif
103
104 static void failed(const char *) GCC_NORETURN;
105
106 static void
107 failed(const char *s)
108 {
109     perror(s);
110     ExitProgram(EXIT_FAILURE);
111 }
112
113 static void
114 strncpy_DYN(DYNBUF * dst, const char *src, size_t need)
115 {
116     size_t want = need + dst->used + 1;
117     if (want > dst->size) {
118         dst->size += (want + 1024);     /* be generous */
119         dst->text = typeRealloc(char, dst->size, dst->text);
120         if (dst->text == 0)
121             failed("strncpy_DYN");
122     }
123     (void) strncpy(dst->text + dst->used, src, need);
124     dst->used += need;
125     dst->text[dst->used] = 0;
126 }
127
128 static void
129 strcpy_DYN(DYNBUF * dst, const char *src)
130 {
131     if (src == 0) {
132         dst->used = 0;
133         strcpy_DYN(dst, "");
134     } else {
135         strncpy_DYN(dst, src, strlen(src));
136     }
137 }
138
139 #if NO_LEAKS
140 static void
141 free_DYN(DYNBUF * p)
142 {
143     if (p->text != 0)
144         free(p->text);
145     p->text = 0;
146     p->size = 0;
147     p->used = 0;
148 }
149
150 void
151 _nc_leaks_dump_entry(void)
152 {
153     free_DYN(&outbuf);
154     free_DYN(&tmpbuf);
155 }
156 #endif
157
158 #define NameTrans(check,result) \
159             if (OkIndex(np->nte_index, check) \
160                 && check[np->nte_index]) \
161                 return (result[np->nte_index])
162
163 NCURSES_CONST char *
164 nametrans(const char *name)
165 /* translate a capability name from termcap to terminfo */
166 {
167     const struct name_table_entry *np;
168
169     if ((np = _nc_find_entry(name, _nc_get_hash_table(0))) != 0)
170         switch (np->nte_type) {
171         case BOOLEAN:
172             NameTrans(bool_from_termcap, boolcodes);
173             break;
174
175         case NUMBER:
176             NameTrans(num_from_termcap, numcodes);
177             break;
178
179         case STRING:
180             NameTrans(str_from_termcap, strcodes);
181             break;
182         }
183
184     return (0);
185 }
186
187 void
188 dump_init(const char *version,
189           int mode,
190           int sort,
191           int twidth,
192           int theight,
193           unsigned traceval,
194           bool formatted,
195           bool check)
196 /* set up for entry display */
197 {
198     width = twidth;
199     height = theight;
200     pretty = formatted;
201     checking = check;
202
203     /* versions */
204     if (version == 0)
205         tversion = V_ALLCAPS;
206     else if (!strcmp(version, "SVr1") || !strcmp(version, "SVR1")
207              || !strcmp(version, "Ultrix"))
208         tversion = V_SVR1;
209     else if (!strcmp(version, "HP"))
210         tversion = V_HPUX;
211     else if (!strcmp(version, "AIX"))
212         tversion = V_AIX;
213     else if (!strcmp(version, "BSD"))
214         tversion = V_BSD;
215     else
216         tversion = V_ALLCAPS;
217
218     /* implement display modes */
219     switch (outform = mode) {
220     case F_LITERAL:
221     case F_TERMINFO:
222         bool_names = boolnames;
223         num_names = numnames;
224         str_names = strnames;
225         separator = (twidth > 0 && theight > 1) ? ", " : ",";
226         trailer = "\n\t";
227         break;
228
229     case F_VARIABLE:
230         bool_names = boolfnames;
231         num_names = numfnames;
232         str_names = strfnames;
233         separator = (twidth > 0 && theight > 1) ? ", " : ",";
234         trailer = "\n\t";
235         break;
236
237     case F_TERMCAP:
238     case F_TCONVERR:
239         bool_names = boolcodes;
240         num_names = numcodes;
241         str_names = strcodes;
242         separator = ":";
243         trailer = "\\\n\t:";
244         break;
245     }
246
247     /* implement sort modes */
248     switch (sortmode = sort) {
249     case S_NOSORT:
250         if (traceval)
251             (void) fprintf(stderr,
252                            "%s: sorting by term structure order\n", _nc_progname);
253         break;
254
255     case S_TERMINFO:
256         if (traceval)
257             (void) fprintf(stderr,
258                            "%s: sorting by terminfo name order\n", _nc_progname);
259         bool_indirect = bool_terminfo_sort;
260         num_indirect = num_terminfo_sort;
261         str_indirect = str_terminfo_sort;
262         break;
263
264     case S_VARIABLE:
265         if (traceval)
266             (void) fprintf(stderr,
267                            "%s: sorting by C variable order\n", _nc_progname);
268         bool_indirect = bool_variable_sort;
269         num_indirect = num_variable_sort;
270         str_indirect = str_variable_sort;
271         break;
272
273     case S_TERMCAP:
274         if (traceval)
275             (void) fprintf(stderr,
276                            "%s: sorting by termcap name order\n", _nc_progname);
277         bool_indirect = bool_termcap_sort;
278         num_indirect = num_termcap_sort;
279         str_indirect = str_termcap_sort;
280         break;
281     }
282
283     if (traceval)
284         (void) fprintf(stderr,
285                        "%s: width = %d, tversion = %d, outform = %d\n",
286                        _nc_progname, width, tversion, outform);
287 }
288
289 static TERMTYPE *cur_type;
290
291 static int
292 dump_predicate(PredType type, PredIdx idx)
293 /* predicate function to use for ordinary decompilation */
294 {
295     switch (type) {
296     case BOOLEAN:
297         return (cur_type->Booleans[idx] == FALSE)
298             ? FAIL : cur_type->Booleans[idx];
299
300     case NUMBER:
301         return (cur_type->Numbers[idx] == ABSENT_NUMERIC)
302             ? FAIL : cur_type->Numbers[idx];
303
304     case STRING:
305         return (cur_type->Strings[idx] != ABSENT_STRING)
306             ? (int) TRUE : FAIL;
307     }
308
309     return (FALSE);             /* pacify compiler */
310 }
311
312 static void set_obsolete_termcaps(TERMTYPE *tp);
313
314 /* is this the index of a function key string? */
315 #define FNKEY(i) \
316     (((i) >= STR_IDX(key_f0) && \
317       (i) <= STR_IDX(key_f9)) || \
318      ((i) >= STR_IDX(key_f11) && \
319       (i) <= STR_IDX(key_f63)))
320
321 /*
322  * If we configure with a different Caps file, the offsets into the arrays
323  * will change.  So we use an address expression.
324  */
325 #define BOOL_IDX(name) (PredType) (&(name) - &(CUR Booleans[0]))
326 #define NUM_IDX(name)  (PredType) (&(name) - &(CUR Numbers[0]))
327 #define STR_IDX(name)  (PredType) (&(name) - &(CUR Strings[0]))
328
329 static bool
330 version_filter(PredType type, PredIdx idx)
331 /* filter out capabilities we may want to suppress */
332 {
333     switch (tversion) {
334     case V_ALLCAPS:             /* SVr4, XSI Curses */
335         return (TRUE);
336
337     case V_SVR1:                /* System V Release 1, Ultrix */
338         switch (type) {
339         case BOOLEAN:
340             return ((idx <= BOOL_IDX(xon_xoff)) ? TRUE : FALSE);
341         case NUMBER:
342             return ((idx <= NUM_IDX(width_status_line)) ? TRUE : FALSE);
343         case STRING:
344             return ((idx <= STR_IDX(prtr_non)) ? TRUE : FALSE);
345         }
346         break;
347
348     case V_HPUX:                /* Hewlett-Packard */
349         switch (type) {
350         case BOOLEAN:
351             return ((idx <= BOOL_IDX(xon_xoff)) ? TRUE : FALSE);
352         case NUMBER:
353             return ((idx <= NUM_IDX(label_width)) ? TRUE : FALSE);
354         case STRING:
355             if (idx <= STR_IDX(prtr_non))
356                 return (TRUE);
357             else if (FNKEY(idx))        /* function keys */
358                 return (TRUE);
359             else if (idx == STR_IDX(plab_norm)
360                      || idx == STR_IDX(label_on)
361                      || idx == STR_IDX(label_off))
362                 return (TRUE);
363             else
364                 return (FALSE);
365         }
366         break;
367
368     case V_AIX:         /* AIX */
369         switch (type) {
370         case BOOLEAN:
371             return ((idx <= BOOL_IDX(xon_xoff)) ? TRUE : FALSE);
372         case NUMBER:
373             return ((idx <= NUM_IDX(width_status_line)) ? TRUE : FALSE);
374         case STRING:
375             if (idx <= STR_IDX(prtr_non))
376                 return (TRUE);
377             else if (FNKEY(idx))        /* function keys */
378                 return (TRUE);
379             else
380                 return (FALSE);
381         }
382         break;
383
384 #define is_termcap(type) (OkIndex(idx, type##_from_termcap) && \
385                           type##_from_termcap[idx])
386
387     case V_BSD:         /* BSD */
388         switch (type) {
389         case BOOLEAN:
390             return is_termcap(bool);
391         case NUMBER:
392             return is_termcap(num);
393         case STRING:
394             return is_termcap(str);
395         }
396         break;
397     }
398
399     return (FALSE);             /* pacify the compiler */
400 }
401
402 static void
403 trim_trailing(void)
404 {
405     while (outbuf.used > 0 && outbuf.text[outbuf.used - 1] == ' ')
406         outbuf.text[--outbuf.used] = '\0';
407 }
408
409 static void
410 force_wrap(void)
411 {
412     oldcol = column;
413     trim_trailing();
414     strcpy_DYN(&outbuf, trailer);
415     column = INDENT;
416 }
417
418 static void
419 wrap_concat(const char *src)
420 {
421     size_t need = strlen(src);
422     size_t want = strlen(separator) + need;
423
424     if (column > INDENT
425         && column + (int) want > width) {
426         force_wrap();
427     }
428     strcpy_DYN(&outbuf, src);
429     strcpy_DYN(&outbuf, separator);
430     column += (int) need;
431 }
432
433 #define IGNORE_SEP_TRAIL(first,last,sep_trail) \
434         if ((size_t)(last - first) > sizeof(sep_trail)-1 \
435          && !strncmp(first, sep_trail, sizeof(sep_trail)-1)) \
436                 first += sizeof(sep_trail)-2
437
438 /* Returns the nominal length of the buffer assuming it is termcap format,
439  * i.e., the continuation sequence is treated as a single character ":".
440  *
441  * There are several implementations of termcap which read the text into a
442  * fixed-size buffer.  Generally they strip the newlines from the text, but may
443  * not do it until after the buffer is read.  Also, "tc=" resolution may be
444  * expanded in the same buffer.  This function is useful for measuring the size
445  * of the best fixed-buffer implementation; the worst case may be much worse.
446  */
447 #ifdef TEST_TERMCAP_LENGTH
448 static int
449 termcap_length(const char *src)
450 {
451     static const char pattern[] = ":\\\n\t:";
452
453     int len = 0;
454     const char *const t = src + strlen(src);
455
456     while (*src != '\0') {
457         IGNORE_SEP_TRAIL(src, t, pattern);
458         src++;
459         len++;
460     }
461     return len;
462 }
463 #else
464 #define termcap_length(src) strlen(src)
465 #endif
466
467 static void
468 indent_DYN(DYNBUF * buffer, int level)
469 {
470     int n;
471
472     for (n = 0; n < level; n++)
473         strncpy_DYN(buffer, "\t", (size_t) 1);
474 }
475
476 bool
477 has_params(const char *src)
478 {
479     bool result = FALSE;
480     int len = (int) strlen(src);
481     int n;
482     bool ifthen = FALSE;
483     bool params = FALSE;
484
485     for (n = 0; n < len - 1; ++n) {
486         if (!strncmp(src + n, "%p", (size_t) 2)) {
487             params = TRUE;
488         } else if (!strncmp(src + n, "%;", (size_t) 2)) {
489             ifthen = TRUE;
490             result = params;
491             break;
492         }
493     }
494     if (!ifthen) {
495         result = ((len > 50) && params);
496     }
497     return result;
498 }
499
500 static char *
501 fmt_complex(TERMTYPE *tterm, const char *capability, char *src, int level)
502 {
503     bool percent = FALSE;
504     bool params = has_params(src);
505
506     while (*src != '\0') {
507         switch (*src) {
508         case '\\':
509             percent = FALSE;
510             strncpy_DYN(&tmpbuf, src++, (size_t) 1);
511             break;
512         case '%':
513             percent = TRUE;
514             break;
515         case '?':               /* "if" */
516         case 't':               /* "then" */
517         case 'e':               /* "else" */
518             if (percent) {
519                 percent = FALSE;
520                 tmpbuf.text[tmpbuf.used - 1] = '\n';
521                 /* treat a "%e" as else-if, on the same level */
522                 if (*src == 'e') {
523                     indent_DYN(&tmpbuf, level);
524                     strncpy_DYN(&tmpbuf, "%", (size_t) 1);
525                     strncpy_DYN(&tmpbuf, src, (size_t) 1);
526                     src++;
527                     params = has_params(src);
528                     if (!params && *src != '\0' && *src != '%') {
529                         strncpy_DYN(&tmpbuf, "\n", (size_t) 1);
530                         indent_DYN(&tmpbuf, level + 1);
531                     }
532                 } else {
533                     indent_DYN(&tmpbuf, level + 1);
534                     strncpy_DYN(&tmpbuf, "%", (size_t) 1);
535                     strncpy_DYN(&tmpbuf, src, (size_t) 1);
536                     if (*src++ == '?') {
537                         src = fmt_complex(tterm, capability, src, level + 1);
538                         if (*src != '\0' && *src != '%') {
539                             strncpy_DYN(&tmpbuf, "\n", (size_t) 1);
540                             indent_DYN(&tmpbuf, level + 1);
541                         }
542                     } else if (level == 1) {
543                         if (checking)
544                             _nc_warning("%s: %%%c without %%? in %s",
545                                         _nc_first_name(tterm->term_names),
546                                         *src, capability);
547                     }
548                 }
549                 continue;
550             }
551             break;
552         case ';':               /* "endif" */
553             if (percent) {
554                 percent = FALSE;
555                 if (level > 1) {
556                     tmpbuf.text[tmpbuf.used - 1] = '\n';
557                     indent_DYN(&tmpbuf, level);
558                     strncpy_DYN(&tmpbuf, "%", (size_t) 1);
559                     strncpy_DYN(&tmpbuf, src++, (size_t) 1);
560                     if (src[0] == '%'
561                         && src[1] != '\0'
562                         && (strchr("?e;", src[1])) == 0) {
563                         tmpbuf.text[tmpbuf.used++] = '\n';
564                         indent_DYN(&tmpbuf, level);
565                     }
566                     return src;
567                 }
568                 if (checking)
569                     _nc_warning("%s: %%; without %%? in %s",
570                                 _nc_first_name(tterm->term_names),
571                                 capability);
572             }
573             break;
574         case 'p':
575             if (percent && params) {
576                 tmpbuf.text[tmpbuf.used - 1] = '\n';
577                 indent_DYN(&tmpbuf, level + 1);
578                 strncpy_DYN(&tmpbuf, "%", (size_t) 1);
579             }
580             params = FALSE;
581             percent = FALSE;
582             break;
583         case ' ':
584             strncpy_DYN(&tmpbuf, "\\s", (size_t) 2);
585             ++src;
586             continue;
587         default:
588             percent = FALSE;
589             break;
590         }
591         strncpy_DYN(&tmpbuf, src++, (size_t) 1);
592     }
593     return src;
594 }
595
596 #define SAME_CAP(n,cap) (&tterm->Strings[n] == &cap)
597 #define EXTRA_CAP 20
598
599 int
600 fmt_entry(TERMTYPE *tterm,
601           PredFunc pred,
602           int content_only,
603           int suppress_untranslatable,
604           int infodump,
605           int numbers)
606 {
607     PredIdx i, j;
608     char buffer[MAX_TERMINFO_LENGTH + EXTRA_CAP];
609     char *capability;
610     NCURSES_CONST char *name;
611     int predval, len;
612     PredIdx num_bools = 0;
613     PredIdx num_values = 0;
614     PredIdx num_strings = 0;
615     bool outcount = 0;
616
617 #define WRAP_CONCAT     \
618         wrap_concat(buffer); \
619         outcount = TRUE
620
621     len = 12;                   /* terminfo file-header */
622
623     if (pred == 0) {
624         cur_type = tterm;
625         pred = dump_predicate;
626     }
627
628     strcpy_DYN(&outbuf, 0);
629     if (content_only) {
630         column = INDENT;        /* FIXME: workaround to prevent empty lines */
631     } else {
632         strcpy_DYN(&outbuf, tterm->term_names);
633
634         /*
635          * Colon is legal in terminfo descriptions, but not in termcap.
636          */
637         if (!infodump) {
638             char *p = outbuf.text;
639             while (*p) {
640                 if (*p == ':') {
641                     *p = '=';
642                 }
643                 ++p;
644             }
645         }
646         strcpy_DYN(&outbuf, separator);
647         column = (int) outbuf.used;
648         if (height > 1)
649             force_wrap();
650     }
651
652     for_each_boolean(j, tterm) {
653         i = BoolIndirect(j);
654         name = ExtBoolname(tterm, (int) i, bool_names);
655         assert(strlen(name) < sizeof(buffer) - EXTRA_CAP);
656
657         if (!version_filter(BOOLEAN, i))
658             continue;
659         else if (isObsolete(outform, name))
660             continue;
661
662         predval = pred(BOOLEAN, i);
663         if (predval != FAIL) {
664             _nc_STRCPY(buffer, name, sizeof(buffer));
665             if (predval <= 0)
666                 _nc_STRCAT(buffer, "@", sizeof(buffer));
667             else if (i + 1 > num_bools)
668                 num_bools = i + 1;
669             WRAP_CONCAT;
670         }
671     }
672
673     if (column != INDENT && height > 1)
674         force_wrap();
675
676     for_each_number(j, tterm) {
677         i = NumIndirect(j);
678         name = ExtNumname(tterm, (int) i, num_names);
679         assert(strlen(name) < sizeof(buffer) - EXTRA_CAP);
680
681         if (!version_filter(NUMBER, i))
682             continue;
683         else if (isObsolete(outform, name))
684             continue;
685
686         predval = pred(NUMBER, i);
687         if (predval != FAIL) {
688             if (tterm->Numbers[i] < 0) {
689                 _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer))
690                             "%s@", name);
691             } else {
692                 _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer))
693                             "%s#%d", name, tterm->Numbers[i]);
694                 if (i + 1 > num_values)
695                     num_values = i + 1;
696             }
697             WRAP_CONCAT;
698         }
699     }
700
701     if (column != INDENT && height > 1)
702         force_wrap();
703
704     len += (int) (num_bools
705                   + num_values * 2
706                   + strlen(tterm->term_names) + 1);
707     if (len & 1)
708         len++;
709
710 #undef CUR
711 #define CUR tterm->
712     if (outform == F_TERMCAP) {
713         if (termcap_reset != ABSENT_STRING) {
714             if (init_3string != ABSENT_STRING
715                 && !strcmp(init_3string, termcap_reset))
716                 DISCARD(init_3string);
717
718             if (reset_2string != ABSENT_STRING
719                 && !strcmp(reset_2string, termcap_reset))
720                 DISCARD(reset_2string);
721         }
722     }
723
724     for_each_string(j, tterm) {
725         i = StrIndirect(j);
726         name = ExtStrname(tterm, (int) i, str_names);
727         assert(strlen(name) < sizeof(buffer) - EXTRA_CAP);
728
729         capability = tterm->Strings[i];
730
731         if (!version_filter(STRING, i))
732             continue;
733         else if (isObsolete(outform, name))
734             continue;
735
736 #if NCURSES_XNAMES
737         /*
738          * Extended names can be longer than 2 characters, but termcap programs
739          * cannot read those (filter them out).
740          */
741         if (outform == F_TERMCAP && (strlen(name) > 2))
742             continue;
743 #endif
744
745         if (outform == F_TERMCAP) {
746             /*
747              * Some older versions of vi want rmir/smir to be defined
748              * for ich/ich1 to work.  If they're not defined, force
749              * them to be output as defined and empty.
750              */
751             if (PRESENT(insert_character) || PRESENT(parm_ich)) {
752                 if (SAME_CAP(i, enter_insert_mode)
753                     && enter_insert_mode == ABSENT_STRING) {
754                     _nc_STRCPY(buffer, "im=", sizeof(buffer));
755                     WRAP_CONCAT;
756                     continue;
757                 }
758
759                 if (SAME_CAP(i, exit_insert_mode)
760                     && exit_insert_mode == ABSENT_STRING) {
761                     _nc_STRCPY(buffer, "ei=", sizeof(buffer));
762                     WRAP_CONCAT;
763                     continue;
764                 }
765             }
766             /*
767              * termcap applications such as screen will be confused if sgr0
768              * is translated to a string containing rmacs.  Filter that out.
769              */
770             if (PRESENT(exit_attribute_mode)) {
771                 if (SAME_CAP(i, exit_attribute_mode)) {
772                     char *trimmed_sgr0;
773                     char *my_sgr = set_attributes;
774
775                     set_attributes = save_sgr;
776
777                     trimmed_sgr0 = _nc_trim_sgr0(tterm);
778                     if (strcmp(capability, trimmed_sgr0))
779                         capability = trimmed_sgr0;
780
781                     set_attributes = my_sgr;
782                 }
783             }
784         }
785
786         predval = pred(STRING, i);
787         buffer[0] = '\0';
788
789         if (predval != FAIL) {
790             if (capability != ABSENT_STRING
791                 && i + 1 > num_strings)
792                 num_strings = i + 1;
793
794             if (!VALID_STRING(capability)) {
795                 _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer))
796                             "%s@", name);
797                 WRAP_CONCAT;
798             } else if (outform == F_TERMCAP || outform == F_TCONVERR) {
799                 char *srccap = _nc_tic_expand(capability, TRUE, numbers);
800                 int params = (((i < (int) SIZEOF(parametrized)) &&
801                                (i < STRCOUNT))
802                               ? parametrized[i]
803                               : ((*srccap == 'k')
804                                  ? 0
805                                  : has_params(srccap)));
806                 char *cv = _nc_infotocap(name, srccap, params);
807
808                 if (cv == 0) {
809                     if (outform == F_TCONVERR) {
810                         _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer))
811                                     "%s=!!! %s WILL NOT CONVERT !!!",
812                                     name, srccap);
813                     } else if (suppress_untranslatable) {
814                         continue;
815                     } else {
816                         char *s = srccap, *d = buffer;
817                         _nc_SPRINTF(d, _nc_SLIMIT(sizeof(buffer)) "..%s=", name);
818                         d += strlen(d);
819                         while ((*d = *s++) != 0) {
820                             if (*d == ':') {
821                                 *d++ = '\\';
822                                 *d = ':';
823                             } else if (*d == '\\') {
824                                 *++d = *s++;
825                             }
826                             d++;
827                         }
828                     }
829                 } else {
830                     _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer))
831                                 "%s=%s", name, cv);
832                 }
833                 len += (int) strlen(capability) + 1;
834                 WRAP_CONCAT;
835             } else {
836                 char *src = _nc_tic_expand(capability,
837                                            outform == F_TERMINFO, numbers);
838
839                 strcpy_DYN(&tmpbuf, 0);
840                 strcpy_DYN(&tmpbuf, name);
841                 strcpy_DYN(&tmpbuf, "=");
842                 if (pretty
843                     && (outform == F_TERMINFO
844                         || outform == F_VARIABLE)) {
845                     fmt_complex(tterm, name, src, 1);
846                 } else {
847                     strcpy_DYN(&tmpbuf, src);
848                 }
849                 len += (int) strlen(capability) + 1;
850                 wrap_concat(tmpbuf.text);
851                 outcount = TRUE;
852             }
853         }
854         /* e.g., trimmed_sgr0 */
855         if (capability != ABSENT_STRING &&
856             capability != CANCELLED_STRING &&
857             capability != tterm->Strings[i])
858             free(capability);
859     }
860     len += (int) (num_strings * 2);
861
862     /*
863      * This piece of code should be an effective inverse of the functions
864      * postprocess_terminfo() and postprocess_terminfo() in parse_entry.c.
865      * Much more work should be done on this to support dumping termcaps.
866      */
867     if (tversion == V_HPUX) {
868         if (VALID_STRING(memory_lock)) {
869             _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer))
870                         "meml=%s", memory_lock);
871             WRAP_CONCAT;
872         }
873         if (VALID_STRING(memory_unlock)) {
874             _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer))
875                         "memu=%s", memory_unlock);
876             WRAP_CONCAT;
877         }
878     } else if (tversion == V_AIX) {
879         if (VALID_STRING(acs_chars)) {
880             bool box_ok = TRUE;
881             const char *acstrans = "lqkxjmwuvtn";
882             const char *cp;
883             char *tp, *sp, boxchars[11];
884
885             tp = boxchars;
886             for (cp = acstrans; *cp; cp++) {
887                 sp = (strchr) (acs_chars, *cp);
888                 if (sp)
889                     *tp++ = sp[1];
890                 else {
891                     box_ok = FALSE;
892                     break;
893                 }
894             }
895             tp[0] = '\0';
896
897             if (box_ok) {
898                 char *tmp = _nc_tic_expand(boxchars,
899                                            (outform == F_TERMINFO),
900                                            numbers);
901                 _nc_STRCPY(buffer, "box1=", sizeof(buffer));
902                 while (*tmp != '\0') {
903                     size_t have = strlen(buffer);
904                     size_t next = strlen(tmp);
905                     size_t want = have + next + 1;
906                     size_t last = next;
907                     char save = '\0';
908
909                     /*
910                      * If the expanded string is too long for the buffer,
911                      * chop it off and save the location where we chopped it.
912                      */
913                     if (want >= sizeof(buffer)) {
914                         save = tmp[last];
915                         tmp[last] = '\0';
916                     }
917                     _nc_STRCAT(buffer, tmp, sizeof(buffer));
918
919                     /*
920                      * If we chopped the buffer, replace the missing piece and
921                      * shift everything to append the remainder.
922                      */
923                     if (save != '\0') {
924                         next = 0;
925                         tmp[last] = save;
926                         while ((tmp[next] = tmp[last + next]) != '\0') {
927                             ++next;
928                         }
929                     } else {
930                         break;
931                     }
932                 }
933                 WRAP_CONCAT;
934             }
935         }
936     }
937
938     /*
939      * kludge: trim off trailer to avoid an extra blank line
940      * in infocmp -u output when there are no string differences
941      */
942     if (outcount) {
943         bool trimmed = FALSE;
944         j = (PredIdx) outbuf.used;
945         if (j >= 2
946             && outbuf.text[j - 1] == '\t'
947             && outbuf.text[j - 2] == '\n') {
948             outbuf.used -= 2;
949             trimmed = TRUE;
950         } else if (j >= 4
951                    && outbuf.text[j - 1] == ':'
952                    && outbuf.text[j - 2] == '\t'
953                    && outbuf.text[j - 3] == '\n'
954                    && outbuf.text[j - 4] == '\\') {
955             outbuf.used -= 4;
956             trimmed = TRUE;
957         }
958         if (trimmed) {
959             outbuf.text[outbuf.used] = '\0';
960             column = oldcol;
961             strcpy_DYN(&outbuf, " ");
962         }
963     }
964 #if 0
965     fprintf(stderr, "num_bools = %d\n", num_bools);
966     fprintf(stderr, "num_values = %d\n", num_values);
967     fprintf(stderr, "num_strings = %d\n", num_strings);
968     fprintf(stderr, "term_names=%s, len=%d, strlen(outbuf)=%d, outbuf=%s\n",
969             tterm->term_names, len, outbuf.used, outbuf.text);
970 #endif
971     /*
972      * Here's where we use infodump to trigger a more stringent length check
973      * for termcap-translation purposes.
974      * Return the length of the raw entry, without tc= expansions,
975      * It gives an idea of which entries are deadly to even *scan past*,
976      * as opposed to *use*.
977      */
978     return (infodump ? len : (int) termcap_length(outbuf.text));
979 }
980
981 static bool
982 kill_string(TERMTYPE *tterm, char *cap)
983 {
984     unsigned n;
985     for (n = 0; n < NUM_STRINGS(tterm); ++n) {
986         if (cap == tterm->Strings[n]) {
987             tterm->Strings[n] = ABSENT_STRING;
988             return TRUE;
989         }
990     }
991     return FALSE;
992 }
993
994 static char *
995 find_string(TERMTYPE *tterm, char *name)
996 {
997     PredIdx n;
998     for (n = 0; n < NUM_STRINGS(tterm); ++n) {
999         if (version_filter(STRING, n)
1000             && !strcmp(name, strnames[n])) {
1001             char *cap = tterm->Strings[n];
1002             if (VALID_STRING(cap)) {
1003                 return cap;
1004             }
1005             break;
1006         }
1007     }
1008     return ABSENT_STRING;
1009 }
1010
1011 /*
1012  * This is used to remove function-key labels from a termcap entry to
1013  * make it smaller.
1014  */
1015 static int
1016 kill_labels(TERMTYPE *tterm, int target)
1017 {
1018     int n;
1019     int result = 0;
1020     char *cap;
1021     char name[10];
1022
1023     for (n = 0; n <= 10; ++n) {
1024         _nc_SPRINTF(name, _nc_SLIMIT(sizeof(name)) "lf%d", n);
1025         if ((cap = find_string(tterm, name)) != ABSENT_STRING
1026             && kill_string(tterm, cap)) {
1027             target -= (int) (strlen(cap) + 5);
1028             ++result;
1029             if (target < 0)
1030                 break;
1031         }
1032     }
1033     return result;
1034 }
1035
1036 /*
1037  * This is used to remove function-key definitions from a termcap entry to
1038  * make it smaller.
1039  */
1040 static int
1041 kill_fkeys(TERMTYPE *tterm, int target)
1042 {
1043     int n;
1044     int result = 0;
1045     char *cap;
1046     char name[10];
1047
1048     for (n = 60; n >= 0; --n) {
1049         _nc_SPRINTF(name, _nc_SLIMIT(sizeof(name)) "kf%d", n);
1050         if ((cap = find_string(tterm, name)) != ABSENT_STRING
1051             && kill_string(tterm, cap)) {
1052             target -= (int) (strlen(cap) + 5);
1053             ++result;
1054             if (target < 0)
1055                 break;
1056         }
1057     }
1058     return result;
1059 }
1060
1061 /*
1062  * Check if the given acsc string is a 1-1 mapping, i.e., just-like-vt100.
1063  * Also, since this is for termcap, we only care about the line-drawing map.
1064  */
1065 #define isLine(c) (strchr("lmkjtuvwqxn", c) != 0)
1066
1067 static bool
1068 one_one_mapping(const char *mapping)
1069 {
1070     bool result = TRUE;
1071
1072     if (mapping != ABSENT_STRING) {
1073         int n = 0;
1074         while (mapping[n] != '\0') {
1075             if (isLine(mapping[n]) &&
1076                 mapping[n] != mapping[n + 1]) {
1077                 result = FALSE;
1078                 break;
1079             }
1080             n += 2;
1081         }
1082     }
1083     return result;
1084 }
1085
1086 #define FMT_ENTRY() \
1087                 fmt_entry(tterm, pred, \
1088                         0, \
1089                         suppress_untranslatable, \
1090                         infodump, numbers)
1091
1092 #define SHOW_WHY PRINTF
1093
1094 static bool
1095 purged_acs(TERMTYPE *tterm)
1096 {
1097     bool result = FALSE;
1098
1099     if (VALID_STRING(acs_chars)) {
1100         if (!one_one_mapping(acs_chars)) {
1101             enter_alt_charset_mode = ABSENT_STRING;
1102             exit_alt_charset_mode = ABSENT_STRING;
1103             SHOW_WHY("# (rmacs/smacs removed for consistency)\n");
1104         }
1105         result = TRUE;
1106     }
1107     return result;
1108 }
1109
1110 /*
1111  * Dump a single entry.
1112  */
1113 void
1114 dump_entry(TERMTYPE *tterm,
1115            int suppress_untranslatable,
1116            int limited,
1117            int numbers,
1118            PredFunc pred)
1119 {
1120     TERMTYPE save_tterm;
1121     int len, critlen;
1122     const char *legend;
1123     bool infodump;
1124
1125     if (outform == F_TERMCAP || outform == F_TCONVERR) {
1126         critlen = MAX_TERMCAP_LENGTH;
1127         legend = "older termcap";
1128         infodump = FALSE;
1129         set_obsolete_termcaps(tterm);
1130     } else {
1131         critlen = MAX_TERMINFO_LENGTH;
1132         legend = "terminfo";
1133         infodump = TRUE;
1134     }
1135
1136     save_sgr = set_attributes;
1137
1138     if ((FMT_ENTRY() > critlen)
1139         && limited) {
1140
1141         save_tterm = *tterm;
1142         if (!suppress_untranslatable) {
1143             SHOW_WHY("# (untranslatable capabilities removed to fit entry within %d bytes)\n",
1144                      critlen);
1145             suppress_untranslatable = TRUE;
1146         }
1147         if (FMT_ENTRY() > critlen) {
1148             /*
1149              * We pick on sgr because it's a nice long string capability that
1150              * is really just an optimization hack.  Another good candidate is
1151              * acsc since it is both long and unused by BSD termcap.
1152              */
1153             bool changed = FALSE;
1154
1155 #if NCURSES_XNAMES
1156             /*
1157              * Extended names are most likely function-key definitions.  Drop
1158              * those first.
1159              */
1160             unsigned n;
1161             for (n = STRCOUNT; n < NUM_STRINGS(tterm); n++) {
1162                 const char *name = ExtStrname(tterm, (int) n, strnames);
1163
1164                 if (VALID_STRING(tterm->Strings[n])) {
1165                     set_attributes = ABSENT_STRING;
1166                     /* we remove long names anyway - only report the short */
1167                     if (strlen(name) <= 2) {
1168                         SHOW_WHY("# (%s removed to fit entry within %d bytes)\n",
1169                                  name,
1170                                  critlen);
1171                     }
1172                     changed = TRUE;
1173                     if (FMT_ENTRY() <= critlen)
1174                         break;
1175                 }
1176             }
1177 #endif
1178             if (VALID_STRING(set_attributes)) {
1179                 set_attributes = ABSENT_STRING;
1180                 SHOW_WHY("# (sgr removed to fit entry within %d bytes)\n",
1181                          critlen);
1182                 changed = TRUE;
1183             }
1184             if (!changed || (FMT_ENTRY() > critlen)) {
1185                 if (purged_acs(tterm)) {
1186                     acs_chars = ABSENT_STRING;
1187                     SHOW_WHY("# (acsc removed to fit entry within %d bytes)\n",
1188                              critlen);
1189                     changed = TRUE;
1190                 }
1191             }
1192             if (!changed || (FMT_ENTRY() > critlen)) {
1193                 int oldversion = tversion;
1194
1195                 tversion = V_BSD;
1196                 SHOW_WHY("# (terminfo-only capabilities suppressed to fit entry within %d bytes)\n",
1197                          critlen);
1198
1199                 len = FMT_ENTRY();
1200                 if (len > critlen
1201                     && kill_labels(tterm, len - critlen)) {
1202                     SHOW_WHY("# (some labels capabilities suppressed to fit entry within %d bytes)\n",
1203                              critlen);
1204                     len = FMT_ENTRY();
1205                 }
1206                 if (len > critlen
1207                     && kill_fkeys(tterm, len - critlen)) {
1208                     SHOW_WHY("# (some function-key capabilities suppressed to fit entry within %d bytes)\n",
1209                              critlen);
1210                     len = FMT_ENTRY();
1211                 }
1212                 if (len > critlen) {
1213                     (void) fprintf(stderr,
1214                                    "warning: %s entry is %d bytes long\n",
1215                                    _nc_first_name(tterm->term_names),
1216                                    len);
1217                     SHOW_WHY("# WARNING: this entry, %d bytes long, may core-dump %s libraries!\n",
1218                              len, legend);
1219                 }
1220                 tversion = oldversion;
1221             }
1222             set_attributes = save_sgr;
1223             *tterm = save_tterm;
1224         }
1225     } else if (!version_filter(STRING, STR_IDX(acs_chars))) {
1226         save_tterm = *tterm;
1227         if (purged_acs(tterm)) {
1228             (void) FMT_ENTRY();
1229         }
1230         *tterm = save_tterm;
1231     }
1232 }
1233
1234 void
1235 dump_uses(const char *name, bool infodump)
1236 /* dump "use=" clauses in the appropriate format */
1237 {
1238     char buffer[MAX_TERMINFO_LENGTH];
1239
1240     if (outform == F_TERMCAP || outform == F_TCONVERR)
1241         trim_trailing();
1242     _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer))
1243                 "%s%s", infodump ? "use=" : "tc=", name);
1244     wrap_concat(buffer);
1245 }
1246
1247 int
1248 show_entry(void)
1249 {
1250     /*
1251      * Trim any remaining whitespace.
1252      */
1253     if (outbuf.used != 0) {
1254         bool infodump = (outform != F_TERMCAP && outform != F_TCONVERR);
1255         char delim = (char) (infodump ? ',' : ':');
1256         int j;
1257
1258         for (j = (int) outbuf.used - 1; j > 0; --j) {
1259             char ch = outbuf.text[j];
1260             if (ch == '\n') {
1261                 ;
1262             } else if (isspace(UChar(ch))) {
1263                 outbuf.used = (size_t) j;
1264             } else if (!infodump && ch == '\\') {
1265                 outbuf.used = (size_t) j;
1266             } else if (ch == delim && (j == 0 || outbuf.text[j - 1] != '\\')) {
1267                 outbuf.used = (size_t) (j + 1);
1268             } else {
1269                 break;
1270             }
1271         }
1272         outbuf.text[outbuf.used] = '\0';
1273     }
1274     (void) fputs(outbuf.text, stdout);
1275     putchar('\n');
1276     return (int) outbuf.used;
1277 }
1278
1279 void
1280 compare_entry(PredHook hook,
1281               TERMTYPE *tp GCC_UNUSED,
1282               bool quiet)
1283 /* compare two entries */
1284 {
1285     PredIdx i, j;
1286     NCURSES_CONST char *name;
1287
1288     if (!quiet)
1289         fputs("    comparing booleans.\n", stdout);
1290     for_each_boolean(j, tp) {
1291         i = BoolIndirect(j);
1292         name = ExtBoolname(tp, (int) i, bool_names);
1293
1294         if (isObsolete(outform, name))
1295             continue;
1296
1297         (*hook) (CMP_BOOLEAN, i, name);
1298     }
1299
1300     if (!quiet)
1301         fputs("    comparing numbers.\n", stdout);
1302     for_each_number(j, tp) {
1303         i = NumIndirect(j);
1304         name = ExtNumname(tp, (int) i, num_names);
1305
1306         if (isObsolete(outform, name))
1307             continue;
1308
1309         (*hook) (CMP_NUMBER, i, name);
1310     }
1311
1312     if (!quiet)
1313         fputs("    comparing strings.\n", stdout);
1314     for_each_string(j, tp) {
1315         i = StrIndirect(j);
1316         name = ExtStrname(tp, (int) i, str_names);
1317
1318         if (isObsolete(outform, name))
1319             continue;
1320
1321         (*hook) (CMP_STRING, i, name);
1322     }
1323
1324     /* (void) fputs("    comparing use entries.\n", stdout); */
1325     (*hook) (CMP_USE, 0, "use");
1326
1327 }
1328
1329 #define NOTSET(s)       ((s) == 0)
1330
1331 /*
1332  * This bit of legerdemain turns all the terminfo variable names into
1333  * references to locations in the arrays Booleans, Numbers, and Strings ---
1334  * precisely what's needed.
1335  */
1336 #undef CUR
1337 #define CUR tp->
1338
1339 static void
1340 set_obsolete_termcaps(TERMTYPE *tp)
1341 {
1342 #include "capdefaults.c"
1343 }
1344
1345 /*
1346  * Convert an alternate-character-set string to canonical form: sorted and
1347  * unique.
1348  */
1349 void
1350 repair_acsc(TERMTYPE *tp)
1351 {
1352     if (VALID_STRING(acs_chars)) {
1353         size_t n, m;
1354         char mapped[256];
1355         char extra = 0;
1356         unsigned source;
1357         unsigned target;
1358         bool fix_needed = FALSE;
1359
1360         for (n = 0, source = 0; acs_chars[n] != 0; n++) {
1361             target = UChar(acs_chars[n]);
1362             if (source >= target) {
1363                 fix_needed = TRUE;
1364                 break;
1365             }
1366             source = target;
1367             if (acs_chars[n + 1])
1368                 n++;
1369         }
1370         if (fix_needed) {
1371             memset(mapped, 0, sizeof(mapped));
1372             for (n = 0; acs_chars[n] != 0; n++) {
1373                 source = UChar(acs_chars[n]);
1374                 if ((target = (unsigned char) acs_chars[n + 1]) != 0) {
1375                     mapped[source] = (char) target;
1376                     n++;
1377                 } else {
1378                     extra = (char) source;
1379                 }
1380             }
1381             for (n = m = 0; n < sizeof(mapped); n++) {
1382                 if (mapped[n]) {
1383                     acs_chars[m++] = (char) n;
1384                     acs_chars[m++] = mapped[n];
1385                 }
1386             }
1387             if (extra)
1388                 acs_chars[m++] = extra;         /* garbage in, garbage out */
1389             acs_chars[m] = 0;
1390         }
1391     }
1392 }