]> ncurses.scripts.mit.edu Git - ncurses.git/blob - progs/dump_entry.c
ncurses 6.2 - patch 20210418
[ncurses.git] / progs / dump_entry.c
1 /****************************************************************************
2  * Copyright 2018-2020,2021 Thomas E. Dickey                                *
3  * Copyright 1998-2016,2017 Free Software Foundation, Inc.                  *
4  *                                                                          *
5  * Permission is hereby granted, free of charge, to any person obtaining a  *
6  * copy of this software and associated documentation files (the            *
7  * "Software"), to deal in the Software without restriction, including      *
8  * without limitation the rights to use, copy, modify, merge, publish,      *
9  * distribute, distribute with modifications, sublicense, and/or sell       *
10  * copies of the Software, and to permit persons to whom the Software is    *
11  * furnished to do so, subject to the following conditions:                 *
12  *                                                                          *
13  * The above copyright notice and this permission notice shall be included  *
14  * in all copies or substantial portions of the Software.                   *
15  *                                                                          *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
17  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
18  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
19  * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
20  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
21  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
22  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
23  *                                                                          *
24  * Except as contained in this notice, the name(s) of the above copyright   *
25  * holders shall not be used in advertising or otherwise to promote the     *
26  * sale, use or other dealings in this Software without prior written       *
27  * authorization.                                                           *
28  ****************************************************************************/
29
30 /****************************************************************************
31  *  Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995               *
32  *     and: Eric S. Raymond <esr@snark.thyrsus.com>                         *
33  *     and: Thomas E. Dickey                        1996 on                 *
34  ****************************************************************************/
35
36 #define __INTERNAL_CAPS_VISIBLE
37 #include <progs.priv.h>
38
39 #include <dump_entry.h>
40 #include <termsort.h>           /* this C file is generated */
41 #include <parametrized.h>       /* so is this */
42
43 MODULE_ID("$Id: dump_entry.c,v 1.186 2021/04/18 11:33:08 tom Exp $")
44
45 #define DISCARD(string) string = ABSENT_STRING
46 #define PRINTF (void) printf
47 #define WRAPPED 32
48
49 #define OkIndex(index,array) ((int)(index) >= 0 && (int)(index) < (int) SIZEOF(array))
50 #define TcOutput() (outform == F_TERMCAP || outform == F_TCONVERR)
51
52 typedef struct {
53     char *text;
54     size_t used;
55     size_t size;
56 } DYNBUF;
57
58 static int tversion;            /* terminfo version */
59 static int outform;             /* output format to use */
60 static int sortmode;            /* sort mode to use */
61 static int width = 60;          /* max line width for listings */
62 static int height = 65535;      /* max number of lines for listings */
63 static int column;              /* current column, limited by 'width' */
64 static int oldcol;              /* last value of column before wrap */
65 static bool pretty;             /* true if we format if-then-else strings */
66 static bool wrapped;            /* true if we wrap too-long strings */
67 static bool did_wrap;           /* true if last wrap_concat did wrapping */
68 static bool checking;           /* true if we are checking for tic */
69 static int quickdump;           /* true if we are dumping compiled data */
70
71 static char *save_sgr;
72
73 static DYNBUF outbuf;
74 static DYNBUF tmpbuf;
75
76 /* indirection pointers for implementing sort and display modes */
77 static const PredIdx *bool_indirect, *num_indirect, *str_indirect;
78 static NCURSES_CONST char *const *bool_names;
79 static NCURSES_CONST char *const *num_names;
80 static NCURSES_CONST char *const *str_names;
81
82 static const char *separator = "", *trailer = "";
83 static int indent = 8;
84
85 /* cover various ports and variants of terminfo */
86 #define V_ALLCAPS       0       /* all capabilities (SVr4, XSI, ncurses) */
87 #define V_SVR1          1       /* SVR1, Ultrix */
88 #define V_HPUX          2       /* HP/UX */
89 #define V_AIX           3       /* AIX */
90 #define V_BSD           4       /* BSD */
91
92 #if NCURSES_XNAMES
93 #define OBSOLETE(n) (!_nc_user_definable && (n[0] == 'O' && n[1] == 'T'))
94 #else
95 #define OBSOLETE(n) (n[0] == 'O' && n[1] == 'T')
96 #endif
97
98 #define isObsolete(f,n) ((f == F_TERMINFO || f == F_VARIABLE) && (sortmode != S_VARIABLE) && OBSOLETE(n))
99
100 #if NCURSES_XNAMES
101 #define BoolIndirect(j) ((j >= BOOLCOUNT) ? (j) : ((sortmode == S_NOSORT) ? j : bool_indirect[j]))
102 #define NumIndirect(j)  ((j >= NUMCOUNT)  ? (j) : ((sortmode == S_NOSORT) ? j : num_indirect[j]))
103 #define StrIndirect(j)  ((j >= STRCOUNT)  ? (j) : ((sortmode == S_NOSORT) ? j : str_indirect[j]))
104 #else
105 #define BoolIndirect(j) ((sortmode == S_NOSORT) ? (j) : bool_indirect[j])
106 #define NumIndirect(j)  ((sortmode == S_NOSORT) ? (j) : num_indirect[j])
107 #define StrIndirect(j)  ((sortmode == S_NOSORT) ? (j) : str_indirect[j])
108 #endif
109
110 static GCC_NORETURN void
111 failed(const char *s)
112 {
113     perror(s);
114     ExitProgram(EXIT_FAILURE);
115 }
116
117 static void
118 strncpy_DYN(DYNBUF * dst, const char *src, size_t need)
119 {
120     size_t want = need + dst->used + 1;
121     if (want > dst->size) {
122         dst->size += (want + 1024);     /* be generous */
123         dst->text = typeRealloc(char, dst->size, dst->text);
124         if (dst->text == 0)
125             failed("strncpy_DYN");
126     }
127     _nc_STRNCPY(dst->text + dst->used, src, need + 1);
128     dst->used += need;
129     dst->text[dst->used] = 0;
130 }
131
132 static void
133 strcpy_DYN(DYNBUF * dst, const char *src)
134 {
135     if (src == 0) {
136         dst->used = 0;
137         strcpy_DYN(dst, "");
138     } else {
139         strncpy_DYN(dst, src, strlen(src));
140     }
141 }
142
143 #if NO_LEAKS
144 static void
145 free_DYN(DYNBUF * p)
146 {
147     if (p->text != 0)
148         free(p->text);
149     p->text = 0;
150     p->size = 0;
151     p->used = 0;
152 }
153
154 void
155 _nc_leaks_dump_entry(void)
156 {
157     free_DYN(&outbuf);
158     free_DYN(&tmpbuf);
159 }
160 #endif
161
162 #define NameTrans(check,result) \
163             if ((np->nte_index <= OK_ ## check) \
164                 && check[np->nte_index]) \
165                 return (result[np->nte_index])
166
167 NCURSES_CONST char *
168 nametrans(const char *name)
169 /* translate a capability name to termcap from terminfo */
170 {
171     const struct name_table_entry *np;
172
173     if ((np = _nc_find_entry(name, _nc_get_hash_table(0))) != 0) {
174         switch (np->nte_type) {
175         case BOOLEAN:
176             NameTrans(bool_from_termcap, boolcodes);
177             break;
178
179         case NUMBER:
180             NameTrans(num_from_termcap, numcodes);
181             break;
182
183         case STRING:
184             NameTrans(str_from_termcap, strcodes);
185             break;
186         }
187     }
188
189     return (0);
190 }
191
192 void
193 dump_init(const char *version,
194           int mode,
195           int sort,
196           bool wrap_strings,
197           int twidth,
198           int theight,
199           unsigned traceval,
200           bool formatted,
201           bool check,
202           int quick)
203 /* set up for entry display */
204 {
205     width = twidth;
206     height = theight;
207     pretty = formatted;
208     wrapped = wrap_strings;
209     checking = check;
210     quickdump = (quick & 3);
211
212     did_wrap = (width <= 0);
213
214     /* versions */
215     if (version == 0)
216         tversion = V_ALLCAPS;
217     else if (!strcmp(version, "SVr1") || !strcmp(version, "SVR1")
218              || !strcmp(version, "Ultrix"))
219         tversion = V_SVR1;
220     else if (!strcmp(version, "HP"))
221         tversion = V_HPUX;
222     else if (!strcmp(version, "AIX"))
223         tversion = V_AIX;
224     else if (!strcmp(version, "BSD"))
225         tversion = V_BSD;
226     else
227         tversion = V_ALLCAPS;
228
229     /* implement display modes */
230     switch (outform = mode) {
231     case F_LITERAL:
232     case F_TERMINFO:
233         bool_names = boolnames;
234         num_names = numnames;
235         str_names = strnames;
236         separator = (twidth > 0 && theight > 1) ? ", " : ",";
237         trailer = "\n\t";
238         break;
239
240     case F_VARIABLE:
241         bool_names = boolfnames;
242         num_names = numfnames;
243         str_names = strfnames;
244         separator = (twidth > 0 && theight > 1) ? ", " : ",";
245         trailer = "\n\t";
246         break;
247
248     case F_TERMCAP:
249     case F_TCONVERR:
250         bool_names = boolcodes;
251         num_names = numcodes;
252         str_names = strcodes;
253         separator = ":";
254         trailer = "\\\n\t:";
255         break;
256     }
257     indent = 8;
258
259     /* implement sort modes */
260     switch (sortmode = sort) {
261     case S_NOSORT:
262         if (traceval)
263             (void) fprintf(stderr,
264                            "%s: sorting by term structure order\n", _nc_progname);
265         break;
266
267     case S_TERMINFO:
268         if (traceval)
269             (void) fprintf(stderr,
270                            "%s: sorting by terminfo name order\n", _nc_progname);
271         bool_indirect = bool_terminfo_sort;
272         num_indirect = num_terminfo_sort;
273         str_indirect = str_terminfo_sort;
274         break;
275
276     case S_VARIABLE:
277         if (traceval)
278             (void) fprintf(stderr,
279                            "%s: sorting by C variable order\n", _nc_progname);
280         bool_indirect = bool_variable_sort;
281         num_indirect = num_variable_sort;
282         str_indirect = str_variable_sort;
283         break;
284
285     case S_TERMCAP:
286         if (traceval)
287             (void) fprintf(stderr,
288                            "%s: sorting by termcap name order\n", _nc_progname);
289         bool_indirect = bool_termcap_sort;
290         num_indirect = num_termcap_sort;
291         str_indirect = str_termcap_sort;
292         break;
293     }
294
295     if (traceval)
296         (void) fprintf(stderr,
297                        "%s: width = %d, tversion = %d, outform = %d\n",
298                        _nc_progname, width, tversion, outform);
299 }
300
301 static TERMTYPE2 *cur_type;
302
303 static int
304 dump_predicate(PredType type, PredIdx idx)
305 /* predicate function to use for ordinary decompilation */
306 {
307     switch (type) {
308     case BOOLEAN:
309         return (cur_type->Booleans[idx] == FALSE)
310             ? FAIL : cur_type->Booleans[idx];
311
312     case NUMBER:
313         return (cur_type->Numbers[idx] == ABSENT_NUMERIC)
314             ? FAIL : cur_type->Numbers[idx];
315
316     case STRING:
317         return (cur_type->Strings[idx] != ABSENT_STRING)
318             ? (int) TRUE : FAIL;
319     }
320
321     return (FALSE);             /* pacify compiler */
322 }
323
324 static void set_obsolete_termcaps(TERMTYPE2 *tp);
325
326 /* is this the index of a function key string? */
327 #define FNKEY(i) \
328     (((i) >= STR_IDX(key_f0) && \
329       (i) <= STR_IDX(key_f9)) || \
330      ((i) >= STR_IDX(key_f11) && \
331       (i) <= STR_IDX(key_f63)))
332
333 /*
334  * If we configure with a different Caps file, the offsets into the arrays
335  * will change.  So we use an address expression.
336  */
337 #define BOOL_IDX(name) (PredType) (&(name) - &(CUR Booleans[0]))
338 #define NUM_IDX(name)  (PredType) (&(name) - &(CUR Numbers[0]))
339 #define STR_IDX(name)  (PredType) (&(name) - &(CUR Strings[0]))
340
341 static bool
342 version_filter(PredType type, PredIdx idx)
343 /* filter out capabilities we may want to suppress */
344 {
345     switch (tversion) {
346     case V_ALLCAPS:             /* SVr4, XSI Curses */
347         return (TRUE);
348
349     case V_SVR1:                /* System V Release 1, Ultrix */
350         switch (type) {
351         case BOOLEAN:
352             return ((idx <= BOOL_IDX(xon_xoff)) ? TRUE : FALSE);
353         case NUMBER:
354             return ((idx <= NUM_IDX(width_status_line)) ? TRUE : FALSE);
355         case STRING:
356             return ((idx <= STR_IDX(prtr_non)) ? TRUE : FALSE);
357         }
358         break;
359
360     case V_HPUX:                /* Hewlett-Packard */
361         switch (type) {
362         case BOOLEAN:
363             return ((idx <= BOOL_IDX(xon_xoff)) ? TRUE : FALSE);
364         case NUMBER:
365             return ((idx <= NUM_IDX(label_width)) ? TRUE : FALSE);
366         case STRING:
367             if (idx <= STR_IDX(prtr_non))
368                 return (TRUE);
369             else if (FNKEY(idx))        /* function keys */
370                 return (TRUE);
371             else if (idx == STR_IDX(plab_norm)
372                      || idx == STR_IDX(label_on)
373                      || idx == STR_IDX(label_off))
374                 return (TRUE);
375             else
376                 return (FALSE);
377         }
378         break;
379
380     case V_AIX:         /* AIX */
381         switch (type) {
382         case BOOLEAN:
383             return ((idx <= BOOL_IDX(xon_xoff)) ? TRUE : FALSE);
384         case NUMBER:
385             return ((idx <= NUM_IDX(width_status_line)) ? TRUE : FALSE);
386         case STRING:
387             if (idx <= STR_IDX(prtr_non))
388                 return (TRUE);
389             else if (FNKEY(idx))        /* function keys */
390                 return (TRUE);
391             else
392                 return (FALSE);
393         }
394         break;
395
396 #define is_termcap(type) (OkIndex(idx, type##_from_termcap) && \
397                           type##_from_termcap[idx])
398
399     case V_BSD:         /* BSD */
400         switch (type) {
401         case BOOLEAN:
402             return is_termcap(bool);
403         case NUMBER:
404             return is_termcap(num);
405         case STRING:
406             return is_termcap(str);
407         }
408         break;
409     }
410
411     return (FALSE);             /* pacify the compiler */
412 }
413
414 static void
415 trim_trailing(void)
416 {
417     while (outbuf.used > 0 && outbuf.text[outbuf.used - 1] == ' ')
418         outbuf.text[--outbuf.used] = '\0';
419 }
420
421 static void
422 force_wrap(void)
423 {
424     oldcol = column;
425     trim_trailing();
426     strcpy_DYN(&outbuf, trailer);
427     column = indent;
428 }
429
430 static int
431 op_length(const char *src, int offset)
432 {
433     int result = 0;
434
435     if (offset > 0 && src[offset - 1] == '\\') {
436         result = 0;
437     } else {
438         int ch;
439
440         result++;               /* for '%' mark */
441         ch = src[offset + result];
442         if (TcOutput()) {
443             if (ch == '>') {
444                 result += 3;
445             } else if (ch == '+') {
446                 result += 2;
447             } else {
448                 result++;
449             }
450         } else if (ch == '\'') {
451             result += 3;
452         } else if (ch == L_CURL[0]) {
453             int n = result;
454             while ((ch = src[offset + n]) != '\0') {
455                 if (ch == R_CURL[0]) {
456                     result = ++n;
457                     break;
458                 }
459                 n++;
460             }
461         } else if (strchr("pPg", ch) != 0) {
462             result += 2;
463         } else {
464             result++;           /* ordinary operator */
465         }
466     }
467     return result;
468 }
469
470 /*
471  * When wrapping too-long strings, avoid splitting a backslash sequence, or
472  * a terminfo '%' operator.  That will leave things a little ragged, but avoids
473  * a stray backslash at the end of the line, as well as making the result a
474  * little more readable.
475  */
476 static int
477 find_split(const char *src, int step, int size)
478 {
479     int result = size;
480
481     if (size > 0) {
482         /* check if that would split a backslash-sequence */
483         int mark = size;
484         int n;
485
486         for (n = size - 1; n > 0; --n) {
487             int ch = UChar(src[step + n]);
488             if (ch == '\\') {
489                 if (n > 0 && src[step + n - 1] == ch)
490                     --n;
491                 mark = n;
492                 break;
493             } else if (!isalnum(ch)) {
494                 break;
495             }
496         }
497         if (mark < size) {
498             result = mark;
499         } else {
500             /* check if that would split a backslash-sequence */
501             for (n = size - 1; n > 0; --n) {
502                 int ch = UChar(src[step + n]);
503                 if (ch == '%') {
504                     int need = op_length(src, step + n);
505                     if ((n + need) > size) {
506                         mark = n;
507                     }
508                     break;
509                 }
510             }
511             if (mark < size) {
512                 result = mark;
513             }
514         }
515     }
516     return result;
517 }
518
519 /*
520  * If we are going to wrap lines, we cannot leave literal spaces because that
521  * would be ambiguous if we split on that space.
522  */
523 static char *
524 fill_spaces(const char *src)
525 {
526     const char *fill = "\\s";
527     size_t need = strlen(src);
528     size_t size = strlen(fill);
529     char *result = 0;
530     int pass;
531     size_t s, d;
532     for (pass = 0; pass < 2; ++pass) {
533         for (s = d = 0; src[s] != '\0'; ++s) {
534             if (src[s] == ' ') {
535                 if (pass) {
536                     _nc_STRCPY(&result[d], fill, need + 1 - d);
537                     d += size;
538                 } else {
539                     need += size;
540                 }
541             } else {
542                 if (pass) {
543                     result[d++] = src[s];
544                 } else {
545                     ++d;
546                 }
547             }
548         }
549         if (pass) {
550             result[d] = '\0';
551         } else {
552             result = malloc(need + 1);
553             if (result == 0)
554                 failed("fill_spaces");
555         }
556     }
557     return result;
558 }
559
560 typedef enum {
561     wOFF = 0
562     ,w1ST = 1
563     ,w2ND = 2
564     ,wEND = 4
565     ,wERR = 8
566 } WRAPMODE;
567
568 #define wrap_1ST(mode) ((mode)&w1ST)
569 #define wrap_END(mode) ((mode)&wEND)
570 #define wrap_ERR(mode) ((mode)&wERR)
571
572 static void
573 wrap_concat(const char *src, int need, unsigned mode)
574 {
575     int gaps = (int) strlen(separator);
576     int want = gaps + need;
577
578     did_wrap = (width <= 0);
579     if (wrap_1ST(mode)
580         && column > indent
581         && column + want > width) {
582         force_wrap();
583     }
584     if ((wrap_END(mode) && !wrap_ERR(mode)) &&
585         wrapped &&
586         (width >= 0) &&
587         (column + want) > width) {
588         int step = 0;
589         int used = width > WRAPPED ? width : WRAPPED;
590         int base = 0;
591         char *p, align[9];
592         const char *my_t = trailer;
593         char *fill = fill_spaces(src);
594         int last = (int) strlen(fill);
595
596         need = last;
597
598         if (TcOutput())
599             trailer = "\\\n\t ";
600
601         if (!TcOutput() && (p = strchr(fill, '=')) != 0) {
602             base = (int) (p + 1 - fill);
603             if (base > 8)
604                 base = 8;
605             _nc_SPRINTF(align, _nc_SLIMIT(align) "%*s", base, " ");
606         } else if (column > 8) {
607             base = column - 8;
608             if (base > 8)
609                 base = 8;
610             _nc_SPRINTF(align, _nc_SLIMIT(align) "%*s", base, " ");
611         } else {
612             align[base] = '\0';
613         }
614         /* "pretty" overrides wrapping if it already split the line */
615         if (!pretty || strchr(fill, '\n') == 0) {
616             int tag = 0;
617
618             if (TcOutput() && outbuf.used && !wrap_1ST(mode)) {
619                 tag = 3;
620             }
621
622             while ((column + (need + gaps)) > used) {
623                 int size = used - tag;
624                 if (step) {
625                     strcpy_DYN(&outbuf, align);
626                     size -= base;
627                 }
628                 if (size > (last - step)) {
629                     size = (last - step);
630                 }
631                 size = find_split(fill, step, size);
632                 strncpy_DYN(&outbuf, fill + step, (size_t) size);
633                 step += size;
634                 need -= size;
635                 if (need > 0) {
636                     force_wrap();
637                     did_wrap = TRUE;
638                     tag = 0;
639                 }
640             }
641         }
642         if (need > 0) {
643             if (step)
644                 strcpy_DYN(&outbuf, align);
645             strcpy_DYN(&outbuf, fill + step);
646         }
647         if (wrap_END(mode))
648             strcpy_DYN(&outbuf, separator);
649         trailer = my_t;
650         force_wrap();
651
652         free(fill);
653     } else {
654         strcpy_DYN(&outbuf, src);
655         if (wrap_END(mode))
656             strcpy_DYN(&outbuf, separator);
657         column += (int) strlen(src);
658     }
659 }
660
661 static void
662 wrap_concat1(const char *src)
663 {
664     int need = (int) strlen(src);
665     wrap_concat(src, need, w1ST | wEND);
666 }
667
668 static void
669 wrap_concat3(const char *name, const char *eqls, const char *value)
670 {
671     int nlen = (int) strlen(name);
672     int elen = (int) strlen(eqls);
673     int vlen = (int) strlen(value);
674
675     wrap_concat(name, nlen + elen + vlen, w1ST);
676     wrap_concat(eqls, elen + vlen, w2ND);
677     wrap_concat(value, vlen, wEND);
678 }
679
680 #define IGNORE_SEP_TRAIL(first,last,sep_trail) \
681         if ((size_t)(last - first) > sizeof(sep_trail)-1 \
682          && !strncmp(first, sep_trail, sizeof(sep_trail)-1)) \
683                 first += sizeof(sep_trail)-2
684
685 /* Returns the nominal length of the buffer assuming it is termcap format,
686  * i.e., the continuation sequence is treated as a single character ":".
687  *
688  * There are several implementations of termcap which read the text into a
689  * fixed-size buffer.  Generally they strip the newlines from the text, but may
690  * not do it until after the buffer is read.  Also, "tc=" resolution may be
691  * expanded in the same buffer.  This function is useful for measuring the size
692  * of the best fixed-buffer implementation; the worst case may be much worse.
693  */
694 #ifdef TEST_TERMCAP_LENGTH
695 static int
696 termcap_length(const char *src)
697 {
698     static const char pattern[] = ":\\\n\t:";
699
700     int len = 0;
701     const char *const t = src + strlen(src);
702
703     while (*src != '\0') {
704         IGNORE_SEP_TRAIL(src, t, pattern);
705         src++;
706         len++;
707     }
708     return len;
709 }
710 #else
711 #define termcap_length(src) strlen(src)
712 #endif
713
714 static void
715 indent_DYN(DYNBUF * buffer, int level)
716 {
717     int n;
718
719     for (n = 0; n < level; n++)
720         strncpy_DYN(buffer, "\t", (size_t) 1);
721 }
722
723 /*
724  * Check if the current line which was begun consists only of a tab and the
725  * given leading text.
726  */
727 static bool
728 leading_DYN(DYNBUF * buffer, const char *leading)
729 {
730     bool result = FALSE;
731     size_t need = strlen(leading);
732     if (buffer->used > need) {
733         need = buffer->used - need;
734         if (!strcmp(buffer->text + need, leading)) {
735             result = TRUE;
736             while (--need != 0) {
737                 if (buffer->text[need] == '\n') {
738                     break;
739                 }
740                 if (buffer->text[need] != '\t') {
741                     result = FALSE;
742                     break;
743                 }
744             }
745         }
746     }
747     return result;
748 }
749
750 bool
751 has_params(const char *src, bool formatting)
752 {
753     bool result = FALSE;
754     int len = (int) strlen(src);
755     int n;
756     bool ifthen = FALSE;
757     bool params = FALSE;
758
759     for (n = 0; n < len - 1; ++n) {
760         if (!strncmp(src + n, "%p", (size_t) 2)) {
761             params = TRUE;
762         } else if (!strncmp(src + n, "%;", (size_t) 2)) {
763             ifthen = TRUE;
764             result = params;
765             break;
766         }
767     }
768     if (!ifthen) {
769         if (formatting) {
770             result = ((len > 50) && params);
771         } else {
772             result = params;
773         }
774     }
775     return result;
776 }
777
778 static char *
779 fmt_complex(TERMTYPE2 *tterm, const char *capability, char *src, int level)
780 {
781     bool percent = FALSE;
782     bool params = has_params(src, TRUE);
783
784     while (*src != '\0') {
785         switch (*src) {
786         case '^':
787             percent = FALSE;
788             strncpy_DYN(&tmpbuf, src++, (size_t) 1);
789             break;
790         case '\\':
791             percent = FALSE;
792             strncpy_DYN(&tmpbuf, src++, (size_t) 1);
793             break;
794         case '%':
795             percent = TRUE;
796             break;
797         case '?':               /* "if" */
798         case 't':               /* "then" */
799         case 'e':               /* "else" */
800             if (percent) {
801                 percent = FALSE;
802                 tmpbuf.text[tmpbuf.used - 1] = '\n';
803                 /* treat a "%e" as else-if, on the same level */
804                 if (*src == 'e') {
805                     indent_DYN(&tmpbuf, level);
806                     strncpy_DYN(&tmpbuf, "%", (size_t) 1);
807                     strncpy_DYN(&tmpbuf, src, (size_t) 1);
808                     src++;
809                     params = has_params(src, TRUE);
810                     if (!params && *src != '\0' && *src != '%') {
811                         strncpy_DYN(&tmpbuf, "\n", (size_t) 1);
812                         indent_DYN(&tmpbuf, level + 1);
813                     }
814                 } else {
815                     indent_DYN(&tmpbuf, level + 1);
816                     strncpy_DYN(&tmpbuf, "%", (size_t) 1);
817                     strncpy_DYN(&tmpbuf, src, (size_t) 1);
818                     if (*src++ == '?') {
819                         src = fmt_complex(tterm, capability, src, level + 1);
820                         if (*src != '\0' && *src != '%') {
821                             strncpy_DYN(&tmpbuf, "\n", (size_t) 1);
822                             indent_DYN(&tmpbuf, level + 1);
823                         }
824                     } else if (level == 1) {
825                         if (checking)
826                             _nc_warning("%s: %%%c without %%? in %s",
827                                         _nc_first_name(tterm->term_names),
828                                         *src, capability);
829                     }
830                 }
831                 continue;
832             }
833             break;
834         case ';':               /* "endif" */
835             if (percent) {
836                 percent = FALSE;
837                 if (level > 1) {
838                     tmpbuf.text[tmpbuf.used - 1] = '\n';
839                     indent_DYN(&tmpbuf, level);
840                     strncpy_DYN(&tmpbuf, "%", (size_t) 1);
841                     strncpy_DYN(&tmpbuf, src++, (size_t) 1);
842                     if (src[0] == '%'
843                         && src[1] != '\0'
844                         && (strchr("?e;", src[1])) == 0) {
845                         tmpbuf.text[tmpbuf.used++] = '\n';
846                         indent_DYN(&tmpbuf, level);
847                     }
848                     return src;
849                 }
850                 if (checking)
851                     _nc_warning("%s: %%; without %%? in %s",
852                                 _nc_first_name(tterm->term_names),
853                                 capability);
854             }
855             break;
856         case 'p':
857             if (percent && params && !leading_DYN(&tmpbuf, "%")) {
858                 tmpbuf.text[tmpbuf.used - 1] = '\n';
859                 indent_DYN(&tmpbuf, level + 1);
860                 strncpy_DYN(&tmpbuf, "%", (size_t) 1);
861             }
862             params = FALSE;
863             percent = FALSE;
864             break;
865         case ' ':
866             strncpy_DYN(&tmpbuf, "\\s", (size_t) 2);
867             ++src;
868             continue;
869         default:
870             percent = FALSE;
871             break;
872         }
873         strncpy_DYN(&tmpbuf, src++, (size_t) 1);
874     }
875     return src;
876 }
877
878 /*
879  * Make "large" numbers a little easier to read by showing them in hexadecimal
880  * if they are "close" to a power of two.
881  */
882 static const char *
883 number_format(int value)
884 {
885     const char *result = "%d";
886
887     if ((outform != F_TERMCAP) && (value > 255)) {
888         unsigned long lv = (unsigned long) value;
889         int bits = sizeof(unsigned long) * 8;
890         int nn;
891
892         for (nn = 8; nn < bits; ++nn) {
893             unsigned long mm;
894
895             mm = 1UL << nn;
896             if ((mm - 16) <= lv && (mm + 16) > lv) {
897                 result = "%#x";
898                 break;
899             }
900         }
901     }
902     return result;
903 }
904
905 #define SAME_CAP(n,cap) (&tterm->Strings[n] == &cap)
906 #define EXTRA_CAP 20
907
908 int
909 fmt_entry(TERMTYPE2 *tterm,
910           PredFunc pred,
911           int content_only,
912           int suppress_untranslatable,
913           int infodump,
914           int numbers)
915 {
916     PredIdx i, j;
917     char buffer[MAX_TERMINFO_LENGTH + EXTRA_CAP];
918     NCURSES_CONST char *name;
919     int predval, len;
920     PredIdx num_bools = 0;
921     PredIdx num_values = 0;
922     PredIdx num_strings = 0;
923     bool outcount = 0;
924
925 #define WRAP_CONCAT1(s)         wrap_concat1(s); outcount = TRUE
926 #define WRAP_CONCAT             WRAP_CONCAT1(buffer)
927
928     len = 12;                   /* terminfo file-header */
929
930     if (pred == 0) {
931         cur_type = tterm;
932         pred = dump_predicate;
933     }
934
935     strcpy_DYN(&outbuf, 0);
936     if (content_only) {
937         column = indent;        /* FIXME: workaround to prevent empty lines */
938     } else {
939         strcpy_DYN(&outbuf, tterm->term_names);
940
941         /*
942          * Colon is legal in terminfo descriptions, but not in termcap.
943          */
944         if (!infodump) {
945             char *p = outbuf.text;
946             while (*p) {
947                 if (*p == ':') {
948                     *p = '=';
949                 }
950                 ++p;
951             }
952         }
953         strcpy_DYN(&outbuf, separator);
954         column = (int) outbuf.used;
955         if (height > 1)
956             force_wrap();
957     }
958
959     for_each_boolean(j, tterm) {
960         i = BoolIndirect(j);
961         name = ExtBoolname(tterm, (int) i, bool_names);
962         assert(strlen(name) < sizeof(buffer) - EXTRA_CAP);
963
964         if (!version_filter(BOOLEAN, i))
965             continue;
966         else if (isObsolete(outform, name))
967             continue;
968
969         predval = pred(BOOLEAN, i);
970         if (predval != FAIL) {
971             _nc_STRCPY(buffer, name, sizeof(buffer));
972             if (predval <= 0)
973                 _nc_STRCAT(buffer, "@", sizeof(buffer));
974             else if (i + 1 > num_bools)
975                 num_bools = i + 1;
976             WRAP_CONCAT;
977         }
978     }
979
980     if (column != indent && height > 1)
981         force_wrap();
982
983     for_each_number(j, tterm) {
984         i = NumIndirect(j);
985         name = ExtNumname(tterm, (int) i, num_names);
986         assert(strlen(name) < sizeof(buffer) - EXTRA_CAP);
987
988         if (!version_filter(NUMBER, i))
989             continue;
990         else if (isObsolete(outform, name))
991             continue;
992
993         predval = pred(NUMBER, i);
994         if (predval != FAIL) {
995             if (tterm->Numbers[i] < 0) {
996                 _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer))
997                             "%s@", name);
998             } else {
999                 size_t nn;
1000                 _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer))
1001                             "%s#", name);
1002                 nn = strlen(buffer);
1003                 _nc_SPRINTF(buffer + nn, _nc_SLIMIT(sizeof(buffer) - nn)
1004                             number_format(tterm->Numbers[i]),
1005                             tterm->Numbers[i]);
1006                 if (i + 1 > num_values)
1007                     num_values = i + 1;
1008             }
1009             WRAP_CONCAT;
1010         }
1011     }
1012
1013     if (column != indent && height > 1)
1014         force_wrap();
1015
1016     len += (int) (num_bools
1017                   + num_values * 2
1018                   + strlen(tterm->term_names) + 1);
1019     if (len & 1)
1020         len++;
1021
1022 #undef CUR
1023 #define CUR tterm->
1024     if (outform == F_TERMCAP) {
1025         if (VALID_STRING(termcap_reset)) {
1026             if (VALID_STRING(init_3string)
1027                 && !strcmp(init_3string, termcap_reset))
1028                 DISCARD(init_3string);
1029
1030             if (VALID_STRING(reset_2string)
1031                 && !strcmp(reset_2string, termcap_reset))
1032                 DISCARD(reset_2string);
1033         }
1034     }
1035
1036     for_each_string(j, tterm) {
1037         char *capability;
1038         i = StrIndirect(j);
1039         name = ExtStrname(tterm, (int) i, str_names);
1040         assert(strlen(name) < sizeof(buffer) - EXTRA_CAP);
1041
1042         capability = tterm->Strings[i];
1043
1044         if (!version_filter(STRING, i))
1045             continue;
1046         else if (isObsolete(outform, name))
1047             continue;
1048
1049 #if NCURSES_XNAMES
1050         /*
1051          * Extended names can be longer than 2 characters, but termcap programs
1052          * cannot read those (filter them out).
1053          */
1054         if (outform == F_TERMCAP && (strlen(name) > 2))
1055             continue;
1056 #endif
1057
1058         if (outform == F_TERMCAP) {
1059             /*
1060              * Some older versions of vi want rmir/smir to be defined
1061              * for ich/ich1 to work.  If they're not defined, force
1062              * them to be output as defined and empty.
1063              */
1064             if (PRESENT(insert_character) || PRESENT(parm_ich)) {
1065                 if (SAME_CAP(i, enter_insert_mode)
1066                     && enter_insert_mode == ABSENT_STRING) {
1067                     _nc_STRCPY(buffer, "im=", sizeof(buffer));
1068                     WRAP_CONCAT;
1069                     continue;
1070                 }
1071
1072                 if (SAME_CAP(i, exit_insert_mode)
1073                     && exit_insert_mode == ABSENT_STRING) {
1074                     _nc_STRCPY(buffer, "ei=", sizeof(buffer));
1075                     WRAP_CONCAT;
1076                     continue;
1077                 }
1078             }
1079             /*
1080              * termcap applications such as screen will be confused if sgr0
1081              * is translated to a string containing rmacs.  Filter that out.
1082              */
1083             if (PRESENT(exit_attribute_mode)) {
1084                 if (SAME_CAP(i, exit_attribute_mode)) {
1085                     char *trimmed_sgr0;
1086                     char *my_sgr = set_attributes;
1087
1088                     set_attributes = save_sgr;
1089
1090                     trimmed_sgr0 = _nc_trim_sgr0(tterm);
1091                     if (strcmp(capability, trimmed_sgr0)) {
1092                         capability = trimmed_sgr0;
1093                     } else {
1094                         if (trimmed_sgr0 != exit_attribute_mode)
1095                             free(trimmed_sgr0);
1096                     }
1097
1098                     set_attributes = my_sgr;
1099                 }
1100             }
1101         }
1102
1103         predval = pred(STRING, i);
1104         buffer[0] = '\0';
1105
1106         if (predval != FAIL) {
1107             if (VALID_STRING(capability)
1108                 && i + 1 > num_strings)
1109                 num_strings = i + 1;
1110
1111             if (!VALID_STRING(capability)) {
1112                 _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer))
1113                             "%s@", name);
1114                 WRAP_CONCAT;
1115             } else if (TcOutput()) {
1116                 char *srccap = _nc_tic_expand(capability, TRUE, numbers);
1117                 int params = ((i < (int) SIZEOF(parametrized))
1118                               ? parametrized[i]
1119                               : ((*srccap == 'k')
1120                                  ? 0
1121                                  : has_params(srccap, FALSE)));
1122                 char *cv = _nc_infotocap(name, srccap, params);
1123
1124                 if (cv == 0) {
1125                     if (outform == F_TCONVERR) {
1126                         _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer))
1127                                     "%s=!!! %s WILL NOT CONVERT !!!",
1128                                     name, srccap);
1129                         WRAP_CONCAT;
1130                     } else if (suppress_untranslatable) {
1131                         continue;
1132                     } else {
1133                         char *s = srccap, *d = buffer;
1134                         int need = 3 + (int) strlen(name);
1135                         while ((*d = *s++) != 0) {
1136                             if ((d - buffer + 2) >= (int) sizeof(buffer)) {
1137                                 fprintf(stderr,
1138                                         "%s: value for %s is too long\n",
1139                                         _nc_progname,
1140                                         name);
1141                                 *d = '\0';
1142                                 break;
1143                             }
1144                             if (*d == ':') {
1145                                 *d++ = '\\';
1146                                 *d = ':';
1147                             } else if (*d == '\\') {
1148                                 if ((*++d = *s++) == '\0')
1149                                     break;
1150                             }
1151                             d++;
1152                             *d = '\0';
1153                         }
1154                         need += (int) (d - buffer);
1155                         wrap_concat("..", need, w1ST | wERR);
1156                         need -= 2;
1157                         wrap_concat(name, need, wOFF | wERR);
1158                         need -= (int) strlen(name);
1159                         wrap_concat("=", need, w2ND | wERR);
1160                         need -= 1;
1161                         wrap_concat(buffer, need, wEND | wERR);
1162                         outcount = TRUE;
1163                     }
1164                 } else {
1165                     wrap_concat3(name, "=", cv);
1166                 }
1167                 len += (int) strlen(capability) + 1;
1168             } else {
1169                 char *src = _nc_tic_expand(capability,
1170                                            outform == F_TERMINFO, numbers);
1171
1172                 strcpy_DYN(&tmpbuf, 0);
1173                 strcpy_DYN(&tmpbuf, name);
1174                 strcpy_DYN(&tmpbuf, "=");
1175                 if (pretty
1176                     && (outform == F_TERMINFO
1177                         || outform == F_VARIABLE)) {
1178                     fmt_complex(tterm, name, src, 1);
1179                 } else {
1180                     strcpy_DYN(&tmpbuf, src);
1181                 }
1182                 len += (int) strlen(capability) + 1;
1183                 WRAP_CONCAT1(tmpbuf.text);
1184             }
1185         }
1186         /* e.g., trimmed_sgr0 */
1187         if (VALID_STRING(capability) &&
1188             capability != tterm->Strings[i])
1189             free(capability);
1190     }
1191     len += (int) (num_strings * 2);
1192
1193     /*
1194      * This piece of code should be an effective inverse of the functions
1195      * postprocess_terminfo() and postprocess_terminfo() in parse_entry.c.
1196      * Much more work should be done on this to support dumping termcaps.
1197      */
1198     if (tversion == V_HPUX) {
1199         if (VALID_STRING(memory_lock)) {
1200             _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer))
1201                         "meml=%s", memory_lock);
1202             WRAP_CONCAT;
1203         }
1204         if (VALID_STRING(memory_unlock)) {
1205             _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer))
1206                         "memu=%s", memory_unlock);
1207             WRAP_CONCAT;
1208         }
1209     } else if (tversion == V_AIX) {
1210         if (VALID_STRING(acs_chars)) {
1211             bool box_ok = TRUE;
1212             const char *acstrans = "lqkxjmwuvtn";
1213             const char *cp;
1214             char *tp, *sp, boxchars[11];
1215
1216             tp = boxchars;
1217             for (cp = acstrans; *cp; cp++) {
1218                 sp = (strchr) (acs_chars, *cp);
1219                 if (sp)
1220                     *tp++ = sp[1];
1221                 else {
1222                     box_ok = FALSE;
1223                     break;
1224                 }
1225             }
1226             tp[0] = '\0';
1227
1228             if (box_ok) {
1229                 char *tmp = _nc_tic_expand(boxchars,
1230                                            (outform == F_TERMINFO),
1231                                            numbers);
1232                 _nc_STRCPY(buffer, "box1=", sizeof(buffer));
1233                 while (*tmp != '\0') {
1234                     size_t have = strlen(buffer);
1235                     size_t next = strlen(tmp);
1236                     size_t want = have + next + 1;
1237                     size_t last = next;
1238                     char save = '\0';
1239
1240                     /*
1241                      * If the expanded string is too long for the buffer,
1242                      * chop it off and save the location where we chopped it.
1243                      */
1244                     if (want >= sizeof(buffer)) {
1245                         save = tmp[last];
1246                         tmp[last] = '\0';
1247                     }
1248                     _nc_STRCAT(buffer, tmp, sizeof(buffer));
1249
1250                     /*
1251                      * If we chopped the buffer, replace the missing piece and
1252                      * shift everything to append the remainder.
1253                      */
1254                     if (save != '\0') {
1255                         next = 0;
1256                         tmp[last] = save;
1257                         while ((tmp[next] = tmp[last + next]) != '\0') {
1258                             ++next;
1259                         }
1260                     } else {
1261                         break;
1262                     }
1263                 }
1264                 WRAP_CONCAT;
1265             }
1266         }
1267     }
1268
1269     /*
1270      * kludge: trim off trailer to avoid an extra blank line
1271      * in infocmp -u output when there are no string differences
1272      */
1273     if (outcount) {
1274         bool trimmed = FALSE;
1275         j = (PredIdx) outbuf.used;
1276         if (wrapped && did_wrap) {
1277             /* EMPTY */ ;
1278         } else if (j >= 2
1279                    && outbuf.text[j - 1] == '\t'
1280                    && outbuf.text[j - 2] == '\n') {
1281             outbuf.used -= 2;
1282             trimmed = TRUE;
1283         } else if (j >= 4
1284                    && outbuf.text[j - 1] == ':'
1285                    && outbuf.text[j - 2] == '\t'
1286                    && outbuf.text[j - 3] == '\n'
1287                    && outbuf.text[j - 4] == '\\') {
1288             outbuf.used -= 4;
1289             trimmed = TRUE;
1290         }
1291         if (trimmed) {
1292             outbuf.text[outbuf.used] = '\0';
1293             column = oldcol;
1294             strcpy_DYN(&outbuf, " ");
1295         }
1296     }
1297 #if 0
1298     fprintf(stderr, "num_bools = %d\n", num_bools);
1299     fprintf(stderr, "num_values = %d\n", num_values);
1300     fprintf(stderr, "num_strings = %d\n", num_strings);
1301     fprintf(stderr, "term_names=%s, len=%d, strlen(outbuf)=%d, outbuf=%s\n",
1302             tterm->term_names, len, outbuf.used, outbuf.text);
1303 #endif
1304     /*
1305      * Here's where we use infodump to trigger a more stringent length check
1306      * for termcap-translation purposes.
1307      * Return the length of the raw entry, without tc= expansions,
1308      * It gives an idea of which entries are deadly to even *scan past*,
1309      * as opposed to *use*.
1310      */
1311     return (infodump ? len : (int) termcap_length(outbuf.text));
1312 }
1313
1314 static bool
1315 kill_string(TERMTYPE2 *tterm, char *cap)
1316 {
1317     unsigned n;
1318     for (n = 0; n < NUM_STRINGS(tterm); ++n) {
1319         if (cap == tterm->Strings[n]) {
1320             tterm->Strings[n] = ABSENT_STRING;
1321             return TRUE;
1322         }
1323     }
1324     return FALSE;
1325 }
1326
1327 static char *
1328 find_string(TERMTYPE2 *tterm, char *name)
1329 {
1330     PredIdx n;
1331     for (n = 0; n < NUM_STRINGS(tterm); ++n) {
1332         if (version_filter(STRING, n)
1333             && !strcmp(name, strnames[n])) {
1334             char *cap = tterm->Strings[n];
1335             if (VALID_STRING(cap)) {
1336                 return cap;
1337             }
1338             break;
1339         }
1340     }
1341     return ABSENT_STRING;
1342 }
1343
1344 /*
1345  * This is used to remove function-key labels from a termcap entry to
1346  * make it smaller.
1347  */
1348 static int
1349 kill_labels(TERMTYPE2 *tterm, int target)
1350 {
1351     int n;
1352     int result = 0;
1353     char name[20];
1354
1355     for (n = 0; n <= 10; ++n) {
1356         char *cap;
1357
1358         _nc_SPRINTF(name, _nc_SLIMIT(sizeof(name)) "lf%d", n);
1359         cap = find_string(tterm, name);
1360         if (VALID_STRING(cap)
1361             && kill_string(tterm, cap)) {
1362             target -= (int) (strlen(cap) + 5);
1363             ++result;
1364             if (target < 0)
1365                 break;
1366         }
1367     }
1368     return result;
1369 }
1370
1371 /*
1372  * This is used to remove function-key definitions from a termcap entry to
1373  * make it smaller.
1374  */
1375 static int
1376 kill_fkeys(TERMTYPE2 *tterm, int target)
1377 {
1378     int n;
1379     int result = 0;
1380     char name[20];
1381
1382     for (n = 60; n >= 0; --n) {
1383         char *cap;
1384
1385         _nc_SPRINTF(name, _nc_SLIMIT(sizeof(name)) "kf%d", n);
1386         cap = find_string(tterm, name);
1387         if (VALID_STRING(cap)
1388             && kill_string(tterm, cap)) {
1389             target -= (int) (strlen(cap) + 5);
1390             ++result;
1391             if (target < 0)
1392                 break;
1393         }
1394     }
1395     return result;
1396 }
1397
1398 /*
1399  * Check if the given acsc string is a 1-1 mapping, i.e., just-like-vt100.
1400  * Also, since this is for termcap, we only care about the line-drawing map.
1401  */
1402 #define isLine(c) (strchr("lmkjtuvwqxn", c) != 0)
1403
1404 static bool
1405 one_one_mapping(const char *mapping)
1406 {
1407     bool result = TRUE;
1408
1409     if (VALID_STRING(mapping)) {
1410         int n = 0;
1411         while (mapping[n] != '\0' && mapping[n + 1] != '\0') {
1412             if (isLine(mapping[n]) &&
1413                 mapping[n] != mapping[n + 1]) {
1414                 result = FALSE;
1415                 break;
1416             }
1417             n += 2;
1418         }
1419     }
1420     return result;
1421 }
1422
1423 #define FMT_ENTRY() \
1424                 fmt_entry(tterm, pred, \
1425                         0, \
1426                         suppress_untranslatable, \
1427                         infodump, numbers)
1428
1429 #define SHOW_WHY PRINTF
1430
1431 static bool
1432 purged_acs(TERMTYPE2 *tterm)
1433 {
1434     bool result = FALSE;
1435
1436     if (VALID_STRING(acs_chars)) {
1437         if (!one_one_mapping(acs_chars)) {
1438             enter_alt_charset_mode = ABSENT_STRING;
1439             exit_alt_charset_mode = ABSENT_STRING;
1440             SHOW_WHY("# (rmacs/smacs removed for consistency)\n");
1441         }
1442         result = TRUE;
1443     }
1444     return result;
1445 }
1446
1447 static void
1448 encode_b64(char *target, char *source, unsigned state, int *saved)
1449 {
1450     /* RFC-4648 */
1451     static const char data[] =
1452     "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
1453     "abcdefghijklmnopqrstuvwxyz"
1454     "0123456789" "-_";
1455     int ch = UChar(source[state]);
1456
1457     switch (state % 3) {
1458     case 0:
1459         *target++ = data[(ch >> 2) & 077];
1460         *saved = (ch << 4);
1461         break;
1462     case 1:
1463         *target++ = data[((ch >> 4) | *saved) & 077];
1464         *saved = (ch << 2);
1465         break;
1466     case 2:
1467         *target++ = data[((ch >> 6) | *saved) & 077];
1468         *target++ = data[ch & 077];
1469         *saved = 0;
1470         break;
1471     }
1472     *target = '\0';
1473 }
1474
1475 /*
1476  * Dump a single entry.
1477  */
1478 void
1479 dump_entry(TERMTYPE2 *tterm,
1480            int suppress_untranslatable,
1481            int limited,
1482            int numbers,
1483            PredFunc pred)
1484 {
1485     TERMTYPE2 save_tterm;
1486     int critlen;
1487     const char *legend;
1488     bool infodump;
1489
1490     if (quickdump) {
1491         char bigbuf[65536];
1492         unsigned offset = 0;
1493
1494         separator = "";
1495         trailer = "\n";
1496         indent = 0;
1497
1498         if (_nc_write_object(tterm, bigbuf, &offset, sizeof(bigbuf)) == OK) {
1499             char numbuf[80];
1500             unsigned n;
1501
1502             if (quickdump & 1) {
1503                 if (outbuf.used)
1504                     wrap_concat1("\n");
1505                 wrap_concat1("hex:");
1506                 for (n = 0; n < offset; ++n) {
1507                     _nc_SPRINTF(numbuf, _nc_SLIMIT(sizeof(numbuf))
1508                                 "%02X", UChar(bigbuf[n]));
1509                     wrap_concat1(numbuf);
1510                 }
1511             }
1512             if (quickdump & 2) {
1513                 static char padding[] =
1514                 {0, 0};
1515                 int value = 0;
1516
1517                 if (outbuf.used)
1518                     wrap_concat1("\n");
1519                 wrap_concat1("b64:");
1520                 for (n = 0; n < offset; ++n) {
1521                     encode_b64(numbuf, bigbuf, n, &value);
1522                     wrap_concat1(numbuf);
1523                 }
1524                 switch (n % 3) {
1525                 case 0:
1526                     break;
1527                 case 1:
1528                     encode_b64(numbuf, padding, 1, &value);
1529                     wrap_concat1(numbuf);
1530                     wrap_concat1("==");
1531                     break;
1532                 case 2:
1533                     encode_b64(numbuf, padding, 1, &value);
1534                     wrap_concat1(numbuf);
1535                     wrap_concat1("=");
1536                     break;
1537                 }
1538             }
1539         }
1540         return;
1541     }
1542
1543     if (TcOutput()) {
1544         critlen = MAX_TERMCAP_LENGTH;
1545         legend = "older termcap";
1546         infodump = FALSE;
1547         set_obsolete_termcaps(tterm);
1548     } else {
1549         critlen = MAX_TERMINFO_LENGTH;
1550         legend = "terminfo";
1551         infodump = TRUE;
1552     }
1553
1554     save_sgr = set_attributes;
1555
1556     if ((FMT_ENTRY() > critlen)
1557         && limited) {
1558
1559         save_tterm = *tterm;
1560         if (!suppress_untranslatable) {
1561             SHOW_WHY("# (untranslatable capabilities removed to fit entry within %d bytes)\n",
1562                      critlen);
1563             suppress_untranslatable = TRUE;
1564         }
1565         if (FMT_ENTRY() > critlen) {
1566             /*
1567              * We pick on sgr because it's a nice long string capability that
1568              * is really just an optimization hack.  Another good candidate is
1569              * acsc since it is both long and unused by BSD termcap.
1570              */
1571             bool changed = FALSE;
1572
1573 #if NCURSES_XNAMES
1574             /*
1575              * Extended names are most likely function-key definitions.  Drop
1576              * those first.
1577              */
1578             unsigned n;
1579             for (n = STRCOUNT; n < NUM_STRINGS(tterm); n++) {
1580                 const char *name = ExtStrname(tterm, (int) n, strnames);
1581
1582                 if (VALID_STRING(tterm->Strings[n])) {
1583                     set_attributes = ABSENT_STRING;
1584                     /* we remove long names anyway - only report the short */
1585                     if (strlen(name) <= 2) {
1586                         SHOW_WHY("# (%s removed to fit entry within %d bytes)\n",
1587                                  name,
1588                                  critlen);
1589                     }
1590                     changed = TRUE;
1591                     if (FMT_ENTRY() <= critlen)
1592                         break;
1593                 }
1594             }
1595 #endif
1596             if (VALID_STRING(set_attributes)) {
1597                 set_attributes = ABSENT_STRING;
1598                 SHOW_WHY("# (sgr removed to fit entry within %d bytes)\n",
1599                          critlen);
1600                 changed = TRUE;
1601             }
1602             if (!changed || (FMT_ENTRY() > critlen)) {
1603                 if (purged_acs(tterm)) {
1604                     acs_chars = ABSENT_STRING;
1605                     SHOW_WHY("# (acsc removed to fit entry within %d bytes)\n",
1606                              critlen);
1607                     changed = TRUE;
1608                 }
1609             }
1610             if (!changed || (FMT_ENTRY() > critlen)) {
1611                 int oldversion = tversion;
1612                 int len;
1613
1614                 tversion = V_BSD;
1615                 SHOW_WHY("# (terminfo-only capabilities suppressed to fit entry within %d bytes)\n",
1616                          critlen);
1617
1618                 len = FMT_ENTRY();
1619                 if (len > critlen
1620                     && kill_labels(tterm, len - critlen)) {
1621                     SHOW_WHY("# (some labels capabilities suppressed to fit entry within %d bytes)\n",
1622                              critlen);
1623                     len = FMT_ENTRY();
1624                 }
1625                 if (len > critlen
1626                     && kill_fkeys(tterm, len - critlen)) {
1627                     SHOW_WHY("# (some function-key capabilities suppressed to fit entry within %d bytes)\n",
1628                              critlen);
1629                     len = FMT_ENTRY();
1630                 }
1631                 if (len > critlen) {
1632                     (void) fprintf(stderr,
1633                                    "%s: %s entry is %d bytes long\n",
1634                                    _nc_progname,
1635                                    _nc_first_name(tterm->term_names),
1636                                    len);
1637                     SHOW_WHY("# WARNING: this entry, %d bytes long, may core-dump %s libraries!\n",
1638                              len, legend);
1639                 }
1640                 tversion = oldversion;
1641             }
1642             set_attributes = save_sgr;
1643             *tterm = save_tterm;
1644         }
1645     } else if (!version_filter(STRING, STR_IDX(acs_chars))) {
1646         save_tterm = *tterm;
1647         if (purged_acs(tterm)) {
1648             (void) FMT_ENTRY();
1649         }
1650         *tterm = save_tterm;
1651     }
1652 }
1653
1654 void
1655 dump_uses(const char *name, bool infodump)
1656 /* dump "use=" clauses in the appropriate format */
1657 {
1658     char buffer[MAX_TERMINFO_LENGTH];
1659
1660     if (TcOutput())
1661         trim_trailing();
1662     _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer))
1663                 "%s%s", infodump ? "use=" : "tc=", name);
1664     wrap_concat1(buffer);
1665 }
1666
1667 int
1668 show_entry(void)
1669 {
1670     /*
1671      * Trim any remaining whitespace.
1672      */
1673     if (outbuf.used != 0) {
1674         bool infodump = !TcOutput();
1675         char delim = (char) (infodump ? ',' : ':');
1676         int j;
1677
1678         for (j = (int) outbuf.used - 1; j > 0; --j) {
1679             char ch = outbuf.text[j];
1680             if (ch == '\n') {
1681                 ;
1682             } else if (isspace(UChar(ch))) {
1683                 outbuf.used = (size_t) j;
1684             } else if (!infodump && ch == '\\') {
1685                 outbuf.used = (size_t) j;
1686             } else if (ch == delim && (j == 0 || outbuf.text[j - 1] != '\\')) {
1687                 outbuf.used = (size_t) (j + 1);
1688             } else {
1689                 break;
1690             }
1691         }
1692         outbuf.text[outbuf.used] = '\0';
1693     }
1694     if (outbuf.text != 0) {
1695         (void) fputs(outbuf.text, stdout);
1696         putchar('\n');
1697     }
1698     return (int) outbuf.used;
1699 }
1700
1701 void
1702 compare_entry(PredHook hook,
1703               TERMTYPE2 *tp GCC_UNUSED,
1704               bool quiet)
1705 /* compare two entries */
1706 {
1707     PredIdx i, j;
1708     NCURSES_CONST char *name;
1709
1710     if (!quiet)
1711         fputs("    comparing booleans.\n", stdout);
1712     for_each_boolean(j, tp) {
1713         i = BoolIndirect(j);
1714         name = ExtBoolname(tp, (int) i, bool_names);
1715
1716         if (isObsolete(outform, name))
1717             continue;
1718
1719         (*hook) (CMP_BOOLEAN, i, name);
1720     }
1721
1722     if (!quiet)
1723         fputs("    comparing numbers.\n", stdout);
1724     for_each_number(j, tp) {
1725         i = NumIndirect(j);
1726         name = ExtNumname(tp, (int) i, num_names);
1727
1728         if (isObsolete(outform, name))
1729             continue;
1730
1731         (*hook) (CMP_NUMBER, i, name);
1732     }
1733
1734     if (!quiet)
1735         fputs("    comparing strings.\n", stdout);
1736     for_each_string(j, tp) {
1737         i = StrIndirect(j);
1738         name = ExtStrname(tp, (int) i, str_names);
1739
1740         if (isObsolete(outform, name))
1741             continue;
1742
1743         (*hook) (CMP_STRING, i, name);
1744     }
1745
1746     /* (void) fputs("    comparing use entries.\n", stdout); */
1747     (*hook) (CMP_USE, 0, "use");
1748
1749 }
1750
1751 #define NOTSET(s)       ((s) == 0)
1752
1753 /*
1754  * This bit of legerdemain turns all the terminfo variable names into
1755  * references to locations in the arrays Booleans, Numbers, and Strings ---
1756  * precisely what's needed.
1757  */
1758 #undef CUR
1759 #define CUR tp->
1760
1761 static void
1762 set_obsolete_termcaps(TERMTYPE2 *tp)
1763 {
1764 #include "capdefaults.c"
1765 }
1766
1767 /*
1768  * Convert an alternate-character-set string to canonical form: sorted and
1769  * unique.
1770  */
1771 void
1772 repair_acsc(TERMTYPE2 *tp)
1773 {
1774     if (VALID_STRING(acs_chars)) {
1775         size_t n;
1776         char mapped[256];
1777         unsigned source;
1778         unsigned target;
1779         bool fix_needed = FALSE;
1780
1781         for (n = 0, source = 0; acs_chars[n] != 0; n++) {
1782             target = UChar(acs_chars[n]);
1783             if (source >= target) {
1784                 fix_needed = TRUE;
1785                 break;
1786             }
1787             source = target;
1788             if (acs_chars[n + 1])
1789                 n++;
1790         }
1791
1792         if (fix_needed) {
1793             size_t m;
1794             char extra = 0;
1795
1796             memset(mapped, 0, sizeof(mapped));
1797             for (n = 0; acs_chars[n] != 0; n++) {
1798                 source = UChar(acs_chars[n]);
1799                 if ((target = (unsigned char) acs_chars[n + 1]) != 0) {
1800                     mapped[source] = (char) target;
1801                     n++;
1802                 } else {
1803                     extra = (char) source;
1804                 }
1805             }
1806             for (n = m = 0; n < sizeof(mapped); n++) {
1807                 if (mapped[n]) {
1808                     acs_chars[m++] = (char) n;
1809                     acs_chars[m++] = mapped[n];
1810                 }
1811             }
1812             if (extra)
1813                 acs_chars[m++] = extra;         /* garbage in, garbage out */
1814             acs_chars[m] = 0;
1815         }
1816     }
1817 }