1/****************************************************************************
2 * Copyright 2018-2022,2023 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
43MODULE_ID("$Id: dump_entry.c,v 1.196 2023/05/27 20:13:10 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
52typedef struct {
53    char *text;
54    size_t used;
55    size_t size;
56} DYNBUF;
57
58static int tversion;		/* terminfo version */
59static int outform;		/* output format to use */
60static int sortmode;		/* sort mode to use */
61static int width = 60;		/* max line width for listings */
62static int height = 65535;	/* max number of lines for listings */
63static int column;		/* current column, limited by 'width' */
64static int oldcol;		/* last value of column before wrap */
65static bool pretty;		/* true if we format if-then-else strings */
66static bool wrapped;		/* true if we wrap too-long strings */
67static bool did_wrap;		/* true if last wrap_concat did wrapping */
68static bool checking;		/* true if we are checking for tic */
69static int quickdump;		/* true if we are dumping compiled data */
70
71static char *save_sgr;
72
73static DYNBUF outbuf;
74static DYNBUF tmpbuf;
75
76/* indirection pointers for implementing sort and display modes */
77static const PredIdx *bool_indirect, *num_indirect, *str_indirect;
78static NCURSES_CONST char *const *bool_names;
79static NCURSES_CONST char *const *num_names;
80static NCURSES_CONST char *const *str_names;
81
82static const char *separator = "", *trailer = "";
83static 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
110static GCC_NORETURN void
111failed(const char *s)
112{
113    perror(s);
114    ExitProgram(EXIT_FAILURE);
115}
116
117static void
118strncpy_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
132static void
133strcpy_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
144static void
145free_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
154void
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
167NCURSES_CONST char *
168nametrans(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
192void
193dump_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
301static TERMTYPE2 *cur_type;
302
303static int
304dump_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
324static 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
341static bool
342version_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
414static void
415trim_trailing(void)
416{
417    while (outbuf.used > 0 && outbuf.text[outbuf.used - 1] == ' ')
418	outbuf.text[--outbuf.used] = '\0';
419}
420
421static void
422force_wrap(void)
423{
424    oldcol = column;
425    trim_trailing();
426    strcpy_DYN(&outbuf, trailer);
427    column = indent;
428}
429
430static int
431op_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 */
476static int
477find_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 */
523static char *
524fill_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 = calloc(need + 1, sizeof(char));
553	    if (result == 0)
554		failed("fill_spaces");
555	}
556    }
557    return result;
558}
559
560typedef 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
572static void
573wrap_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
661static void
662wrap_concat1(const char *src)
663{
664    int need = (int) strlen(src);
665    wrap_concat(src, need, w1ST | wEND);
666}
667
668static void
669wrap_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
695static int
696termcap_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
714static void
715indent_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 */
727static bool
728leading_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
750bool
751has_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
778static char *
779fmt_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	    percent = FALSE;
863	    break;
864	case ' ':
865	    strncpy_DYN(&tmpbuf, "\\s", (size_t) 2);
866	    ++src;
867	    continue;
868	default:
869	    percent = FALSE;
870	    break;
871	}
872	strncpy_DYN(&tmpbuf, src++, (size_t) 1);
873    }
874    return src;
875}
876
877/*
878 * Make "large" numbers a little easier to read by showing them in hexadecimal
879 * if they are "close" to a power of two.
880 */
881static const char *
882number_format(int value)
883{
884    const char *result = "%d";
885
886    if ((outform != F_TERMCAP) && (value > 255)) {
887	unsigned long lv = (unsigned long) value;
888	int bits = sizeof(unsigned long) * 8;
889	int nn;
890
891	for (nn = 8; nn < bits; ++nn) {
892	    unsigned long mm;
893
894	    mm = 1UL << nn;
895	    if ((mm - 16) <= lv && (mm + 16) > lv) {
896		result = "%#x";
897		break;
898	    }
899	}
900    }
901    return result;
902}
903
904#define SAME_CAP(n,cap) (&tterm->Strings[n] == &cap)
905#define EXTRA_CAP 20
906
907int
908fmt_entry(TERMTYPE2 *tterm,
909	  PredFunc pred,
910	  int content_only,
911	  int suppress_untranslatable,
912	  int infodump,
913	  int numbers)
914{
915    PredIdx i, j;
916    char buffer[MAX_TERMINFO_LENGTH + EXTRA_CAP];
917    NCURSES_CONST char *name;
918    int predval, len;
919    PredIdx num_bools = 0;
920    PredIdx num_values = 0;
921    PredIdx num_strings = 0;
922    bool outcount = 0;
923
924#define WRAP_CONCAT1(s)		wrap_concat1(s); outcount = TRUE
925#define WRAP_CONCAT		WRAP_CONCAT1(buffer)
926
927    len = 12;			/* terminfo file-header */
928
929    if (pred == 0) {
930	cur_type = tterm;
931	pred = dump_predicate;
932    }
933
934    strcpy_DYN(&outbuf, 0);
935    if (content_only) {
936	column = indent;	/* workaround to prevent empty lines */
937    } else {
938	strcpy_DYN(&outbuf, tterm->term_names);
939
940	/*
941	 * Colon is legal in terminfo descriptions, but not in termcap.
942	 */
943	if (!infodump) {
944	    char *p = outbuf.text;
945	    while (*p) {
946		if (*p == ':') {
947		    *p = '=';
948		}
949		++p;
950	    }
951	}
952	strcpy_DYN(&outbuf, separator);
953	column = (int) outbuf.used;
954	if (height > 1)
955	    force_wrap();
956    }
957
958    for_each_boolean(j, tterm) {
959	i = BoolIndirect(j);
960	name = ExtBoolname(tterm, (int) i, bool_names);
961	assert(strlen(name) < sizeof(buffer) - EXTRA_CAP);
962
963	if (!version_filter(BOOLEAN, i))
964	    continue;
965	else if (isObsolete(outform, name))
966	    continue;
967
968	predval = pred(BOOLEAN, i);
969	if (predval != FAIL) {
970	    _nc_STRCPY(buffer, name, sizeof(buffer));
971	    if (predval <= 0)
972		_nc_STRCAT(buffer, "@", sizeof(buffer));
973	    else if (i + 1 > num_bools)
974		num_bools = i + 1;
975	    WRAP_CONCAT;
976	}
977    }
978
979    if (column != indent && height > 1)
980	force_wrap();
981
982    for_each_number(j, tterm) {
983	i = NumIndirect(j);
984	name = ExtNumname(tterm, (int) i, num_names);
985	assert(strlen(name) < sizeof(buffer) - EXTRA_CAP);
986
987	if (!version_filter(NUMBER, i))
988	    continue;
989	else if (isObsolete(outform, name))
990	    continue;
991
992	predval = pred(NUMBER, i);
993	if (predval != FAIL) {
994	    if (tterm->Numbers[i] < 0) {
995		_nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer))
996			    "%s@", name);
997	    } else {
998		size_t nn;
999		_nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer))
1000			    "%s#", name);
1001		nn = strlen(buffer);
1002		_nc_SPRINTF(buffer + nn, _nc_SLIMIT(sizeof(buffer) - nn)
1003			    number_format(tterm->Numbers[i]),
1004			    tterm->Numbers[i]);
1005		if (i + 1 > num_values)
1006		    num_values = i + 1;
1007	    }
1008	    WRAP_CONCAT;
1009	}
1010    }
1011
1012    if (column != indent && height > 1)
1013	force_wrap();
1014
1015    len += (int) (num_bools
1016		  + num_values * 2
1017		  + strlen(tterm->term_names) + 1);
1018    if (len & 1)
1019	len++;
1020
1021#undef CUR
1022#define CUR tterm->
1023    if (outform == F_TERMCAP) {
1024	if (VALID_STRING(termcap_reset)) {
1025	    if (VALID_STRING(init_3string)
1026		&& !strcmp(init_3string, termcap_reset))
1027		DISCARD(init_3string);
1028
1029	    if (VALID_STRING(reset_2string)
1030		&& !strcmp(reset_2string, termcap_reset))
1031		DISCARD(reset_2string);
1032	}
1033    }
1034
1035    for_each_string(j, tterm) {
1036	char *capability;
1037	i = StrIndirect(j);
1038	name = ExtStrname(tterm, (int) i, str_names);
1039	assert(strlen(name) < sizeof(buffer) - EXTRA_CAP);
1040
1041	capability = tterm->Strings[i];
1042
1043	if (!version_filter(STRING, i))
1044	    continue;
1045	else if (isObsolete(outform, name))
1046	    continue;
1047
1048#if NCURSES_XNAMES
1049	/*
1050	 * Extended names can be longer than 2 characters, but termcap programs
1051	 * cannot read those (filter them out).
1052	 */
1053	if (outform == F_TERMCAP && (strlen(name) > 2))
1054	    continue;
1055#endif
1056
1057	if (outform == F_TERMCAP) {
1058	    /*
1059	     * Some older versions of vi want rmir/smir to be defined
1060	     * for ich/ich1 to work.  If they're not defined, force
1061	     * them to be output as defined and empty.
1062	     */
1063	    if (PRESENT(insert_character) || PRESENT(parm_ich)) {
1064		if (SAME_CAP(i, enter_insert_mode)
1065		    && enter_insert_mode == ABSENT_STRING) {
1066		    _nc_STRCPY(buffer, "im=", sizeof(buffer));
1067		    WRAP_CONCAT;
1068		    continue;
1069		}
1070
1071		if (SAME_CAP(i, exit_insert_mode)
1072		    && exit_insert_mode == ABSENT_STRING) {
1073		    _nc_STRCPY(buffer, "ei=", sizeof(buffer));
1074		    WRAP_CONCAT;
1075		    continue;
1076		}
1077	    }
1078	    /*
1079	     * termcap applications such as screen will be confused if sgr0
1080	     * is translated to a string containing rmacs.  Filter that out.
1081	     */
1082	    if (PRESENT(exit_attribute_mode)) {
1083		if (SAME_CAP(i, exit_attribute_mode)) {
1084		    char *trimmed_sgr0;
1085		    char *my_sgr = set_attributes;
1086
1087		    set_attributes = save_sgr;
1088
1089		    trimmed_sgr0 = _nc_trim_sgr0(tterm);
1090		    if (strcmp(capability, trimmed_sgr0)) {
1091			capability = trimmed_sgr0;
1092		    } else {
1093			if (trimmed_sgr0 != exit_attribute_mode)
1094			    free(trimmed_sgr0);
1095		    }
1096
1097		    set_attributes = my_sgr;
1098		}
1099	    }
1100	}
1101
1102	predval = pred(STRING, i);
1103	buffer[0] = '\0';
1104
1105	if (predval != FAIL) {
1106	    if (VALID_STRING(capability)
1107		&& i + 1 > num_strings)
1108		num_strings = i + 1;
1109
1110	    if (!VALID_STRING(capability)) {
1111		_nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer))
1112			    "%s@", name);
1113		WRAP_CONCAT;
1114	    } else if (TcOutput()) {
1115		char *srccap = _nc_tic_expand(capability, TRUE, numbers);
1116		int params = ((i < (int) SIZEOF(parametrized))
1117			      ? parametrized[i]
1118			      : ((*srccap == 'k')
1119				 ? 0
1120				 : has_params(srccap, FALSE)));
1121		char *cv = _nc_infotocap(name, srccap, params);
1122
1123		if (cv == 0) {
1124		    if (outform == F_TCONVERR) {
1125			_nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer))
1126				    "%s=!!! %s WILL NOT CONVERT !!!",
1127				    name, srccap);
1128			WRAP_CONCAT;
1129		    } else if (suppress_untranslatable) {
1130			continue;
1131		    } else {
1132			char *s = srccap, *d = buffer;
1133			int need = 3 + (int) strlen(name);
1134			while ((*d = *s++) != 0) {
1135			    if ((d - buffer + 2) >= (int) sizeof(buffer)) {
1136				fprintf(stderr,
1137					"%s: value for %s is too long\n",
1138					_nc_progname,
1139					name);
1140				*d = '\0';
1141				break;
1142			    }
1143			    if (*d == ':') {
1144				*d++ = '\\';
1145				*d = ':';
1146			    } else if (*d == '\\') {
1147				if ((*++d = *s++) == '\0')
1148				    break;
1149			    }
1150			    d++;
1151			    *d = '\0';
1152			}
1153			need += (int) (d - buffer);
1154			wrap_concat("..", need, w1ST | wERR);
1155			need -= 2;
1156			wrap_concat(name, need, wOFF | wERR);
1157			need -= (int) strlen(name);
1158			wrap_concat("=", need, w2ND | wERR);
1159			need -= 1;
1160			wrap_concat(buffer, need, wEND | wERR);
1161			outcount = TRUE;
1162		    }
1163		} else {
1164		    wrap_concat3(name, "=", cv);
1165		}
1166		len += (int) strlen(capability) + 1;
1167	    } else {
1168		char *src = _nc_tic_expand(capability,
1169					   outform == F_TERMINFO, numbers);
1170
1171		strcpy_DYN(&tmpbuf, 0);
1172		strcpy_DYN(&tmpbuf, name);
1173		strcpy_DYN(&tmpbuf, "=");
1174		if (pretty
1175		    && (outform == F_TERMINFO
1176			|| outform == F_VARIABLE)) {
1177		    fmt_complex(tterm, name, src, 1);
1178		} else {
1179		    strcpy_DYN(&tmpbuf, src);
1180		}
1181		len += (int) strlen(capability) + 1;
1182		WRAP_CONCAT1(tmpbuf.text);
1183	    }
1184	}
1185	/* e.g., trimmed_sgr0 */
1186	if (VALID_STRING(capability) &&
1187	    capability != tterm->Strings[i])
1188	    free(capability);
1189    }
1190    len += (int) (num_strings * 2);
1191
1192    /*
1193     * This piece of code should be an effective inverse of the functions
1194     * postprocess_terminfo() and postprocess_terminfo() in parse_entry.c.
1195     * Much more work should be done on this to support dumping termcaps.
1196     */
1197    if (tversion == V_HPUX) {
1198	if (VALID_STRING(memory_lock)) {
1199	    _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer))
1200			"meml=%s", memory_lock);
1201	    WRAP_CONCAT;
1202	}
1203	if (VALID_STRING(memory_unlock)) {
1204	    _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer))
1205			"memu=%s", memory_unlock);
1206	    WRAP_CONCAT;
1207	}
1208    } else if (tversion == V_AIX) {
1209	if (VALID_STRING(acs_chars)) {
1210	    bool box_ok = TRUE;
1211	    const char *acstrans = "lqkxjmwuvtn";
1212	    const char *cp;
1213	    char *tp, *sp, boxchars[11];
1214
1215	    tp = boxchars;
1216	    for (cp = acstrans; *cp; cp++) {
1217		sp = (strchr) (acs_chars, *cp);
1218		if (sp)
1219		    *tp++ = sp[1];
1220		else {
1221		    box_ok = FALSE;
1222		    break;
1223		}
1224	    }
1225	    tp[0] = '\0';
1226
1227	    if (box_ok) {
1228		char *tmp = _nc_tic_expand(boxchars,
1229					   (outform == F_TERMINFO),
1230					   numbers);
1231		_nc_STRCPY(buffer, "box1=", sizeof(buffer));
1232		while (*tmp != '\0') {
1233		    size_t have = strlen(buffer);
1234		    size_t next = strlen(tmp);
1235		    size_t want = have + next + 1;
1236		    size_t last = next;
1237		    char save = '\0';
1238
1239		    /*
1240		     * If the expanded string is too long for the buffer,
1241		     * chop it off and save the location where we chopped it.
1242		     */
1243		    if (want >= sizeof(buffer)) {
1244			save = tmp[last];
1245			tmp[last] = '\0';
1246		    }
1247		    _nc_STRCAT(buffer, tmp, sizeof(buffer));
1248
1249		    /*
1250		     * If we chopped the buffer, replace the missing piece and
1251		     * shift everything to append the remainder.
1252		     */
1253		    if (save != '\0') {
1254			next = 0;
1255			tmp[last] = save;
1256			while ((tmp[next] = tmp[last + next]) != '\0') {
1257			    ++next;
1258			}
1259		    } else {
1260			break;
1261		    }
1262		}
1263		WRAP_CONCAT;
1264	    }
1265	}
1266    }
1267
1268    /*
1269     * kludge: trim off trailer to avoid an extra blank line
1270     * in infocmp -u output when there are no string differences
1271     */
1272    if (outcount) {
1273	bool trimmed = FALSE;
1274	j = (PredIdx) outbuf.used;
1275	if (wrapped && did_wrap) {
1276	    /* EMPTY */ ;
1277	} else if (j >= 2
1278		   && outbuf.text[j - 1] == '\t'
1279		   && outbuf.text[j - 2] == '\n') {
1280	    outbuf.used -= 2;
1281	    trimmed = TRUE;
1282	} else if (j >= 4
1283		   && outbuf.text[j - 1] == ':'
1284		   && outbuf.text[j - 2] == '\t'
1285		   && outbuf.text[j - 3] == '\n'
1286		   && outbuf.text[j - 4] == '\\') {
1287	    outbuf.used -= 4;
1288	    trimmed = TRUE;
1289	}
1290	if (trimmed) {
1291	    outbuf.text[outbuf.used] = '\0';
1292	    column = oldcol;
1293	    strcpy_DYN(&outbuf, " ");
1294	}
1295    }
1296#if 0
1297    fprintf(stderr, "num_bools = %d\n", num_bools);
1298    fprintf(stderr, "num_values = %d\n", num_values);
1299    fprintf(stderr, "num_strings = %d\n", num_strings);
1300    fprintf(stderr, "term_names=%s, len=%d, strlen(outbuf)=%d, outbuf=%s\n",
1301	    tterm->term_names, len, outbuf.used, outbuf.text);
1302#endif
1303    /*
1304     * Here's where we use infodump to trigger a more stringent length check
1305     * for termcap-translation purposes.
1306     * Return the length of the raw entry, without tc= expansions,
1307     * It gives an idea of which entries are deadly to even *scan past*,
1308     * as opposed to *use*.
1309     */
1310    return (infodump ? len : (int) termcap_length(outbuf.text));
1311}
1312
1313static bool
1314kill_string(TERMTYPE2 *tterm, const char *const cap)
1315{
1316    unsigned n;
1317    for (n = 0; n < NUM_STRINGS(tterm); ++n) {
1318	if (cap == tterm->Strings[n]) {
1319	    tterm->Strings[n] = ABSENT_STRING;
1320	    return TRUE;
1321	}
1322    }
1323    return FALSE;
1324}
1325
1326static char *
1327find_string(TERMTYPE2 *tterm, char *name)
1328{
1329    PredIdx n;
1330    for (n = 0; n < NUM_STRINGS(tterm); ++n) {
1331	if (version_filter(STRING, n)
1332	    && !strcmp(name, strnames[n])) {
1333	    char *cap = tterm->Strings[n];
1334	    if (VALID_STRING(cap)) {
1335		return cap;
1336	    }
1337	    break;
1338	}
1339    }
1340    return ABSENT_STRING;
1341}
1342
1343/*
1344 * This is used to remove function-key labels from a termcap entry to
1345 * make it smaller.
1346 */
1347static int
1348kill_labels(TERMTYPE2 *tterm, int target)
1349{
1350    int n;
1351    int result = 0;
1352    char name[20];
1353
1354    for (n = 0; n <= 10; ++n) {
1355	char *cap;
1356
1357	_nc_SPRINTF(name, _nc_SLIMIT(sizeof(name)) "lf%d", n);
1358	cap = find_string(tterm, name);
1359	if (VALID_STRING(cap)
1360	    && kill_string(tterm, cap)) {
1361	    target -= (int) (strlen(cap) + 5);
1362	    ++result;
1363	    if (target < 0)
1364		break;
1365	}
1366    }
1367    return result;
1368}
1369
1370/*
1371 * This is used to remove function-key definitions from a termcap entry to
1372 * make it smaller.
1373 */
1374static int
1375kill_fkeys(TERMTYPE2 *tterm, int target)
1376{
1377    int n;
1378    int result = 0;
1379    char name[20];
1380
1381    for (n = 60; n >= 0; --n) {
1382	char *cap;
1383
1384	_nc_SPRINTF(name, _nc_SLIMIT(sizeof(name)) "kf%d", n);
1385	cap = find_string(tterm, name);
1386	if (VALID_STRING(cap)
1387	    && kill_string(tterm, cap)) {
1388	    target -= (int) (strlen(cap) + 5);
1389	    ++result;
1390	    if (target < 0)
1391		break;
1392	}
1393    }
1394    return result;
1395}
1396
1397/*
1398 * Check if the given acsc string is a 1-1 mapping, i.e., just-like-vt100.
1399 * Also, since this is for termcap, we only care about the line-drawing map.
1400 */
1401#define isLine(c) (strchr("lmkjtuvwqxn", c) != 0)
1402
1403static bool
1404one_one_mapping(const char *mapping)
1405{
1406    bool result = TRUE;
1407
1408    if (VALID_STRING(mapping)) {
1409	int n = 0;
1410	while (mapping[n] != '\0' && mapping[n + 1] != '\0') {
1411	    if (isLine(mapping[n]) &&
1412		mapping[n] != mapping[n + 1]) {
1413		result = FALSE;
1414		break;
1415	    }
1416	    n += 2;
1417	}
1418    }
1419    return result;
1420}
1421
1422#define FMT_ENTRY() \
1423		fmt_entry(tterm, pred, \
1424			0, \
1425			suppress_untranslatable, \
1426			infodump, numbers)
1427
1428#define SHOW_WHY PRINTF
1429
1430static bool
1431purged_acs(TERMTYPE2 *tterm)
1432{
1433    bool result = FALSE;
1434
1435    if (VALID_STRING(acs_chars)) {
1436	if (!one_one_mapping(acs_chars)) {
1437	    enter_alt_charset_mode = ABSENT_STRING;
1438	    exit_alt_charset_mode = ABSENT_STRING;
1439	    SHOW_WHY("# (rmacs/smacs removed for consistency)\n");
1440	}
1441	result = TRUE;
1442    }
1443    return result;
1444}
1445
1446static void
1447encode_b64(char *target, char *source, unsigned state, int *saved)
1448{
1449    /* RFC-4648 */
1450    static const char data[] =
1451    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
1452    "abcdefghijklmnopqrstuvwxyz"
1453    "0123456789" "-_";
1454    int ch = UChar(source[state]);
1455
1456    switch (state % 3) {
1457    case 0:
1458	*target++ = data[(ch >> 2) & 077];
1459	*saved = (ch << 4);
1460	break;
1461    case 1:
1462	*target++ = data[((ch >> 4) | *saved) & 077];
1463	*saved = (ch << 2);
1464	break;
1465    case 2:
1466	*target++ = data[((ch >> 6) | *saved) & 077];
1467	*target++ = data[ch & 077];
1468	*saved = 0;
1469	break;
1470    }
1471    *target = '\0';
1472}
1473
1474/*
1475 * Dump a single entry.
1476 */
1477void
1478dump_entry(TERMTYPE2 *tterm,
1479	   int suppress_untranslatable,
1480	   int limited,
1481	   int numbers,
1482	   PredFunc pred)
1483{
1484    TERMTYPE2 save_tterm;
1485    int critlen;
1486    const char *legend;
1487    bool infodump;
1488
1489    if (quickdump) {
1490	char bigbuf[65536];
1491	unsigned offset = 0;
1492
1493	separator = "";
1494	trailer = "\n";
1495	indent = 0;
1496
1497	if (_nc_write_object(tterm, bigbuf, &offset, sizeof(bigbuf)) == OK) {
1498	    char numbuf[80];
1499	    unsigned n;
1500
1501	    if (quickdump & 1) {
1502		if (outbuf.used)
1503		    wrap_concat1("\n");
1504		wrap_concat1("hex:");
1505		for (n = 0; n < offset; ++n) {
1506		    _nc_SPRINTF(numbuf, _nc_SLIMIT(sizeof(numbuf))
1507				"%02X", UChar(bigbuf[n]));
1508		    wrap_concat1(numbuf);
1509		}
1510	    }
1511	    if (quickdump & 2) {
1512		static char padding[] =
1513		{0, 0};
1514		int value = 0;
1515
1516		if (outbuf.used)
1517		    wrap_concat1("\n");
1518		wrap_concat1("b64:");
1519		for (n = 0; n < offset; ++n) {
1520		    encode_b64(numbuf, bigbuf, n, &value);
1521		    wrap_concat1(numbuf);
1522		}
1523		switch (n % 3) {
1524		case 0:
1525		    break;
1526		case 1:
1527		    encode_b64(numbuf, padding, 1, &value);
1528		    wrap_concat1(numbuf);
1529		    wrap_concat1("==");
1530		    break;
1531		case 2:
1532		    encode_b64(numbuf, padding, 1, &value);
1533		    wrap_concat1(numbuf);
1534		    wrap_concat1("=");
1535		    break;
1536		}
1537	    }
1538	}
1539	return;
1540    }
1541
1542    if (TcOutput()) {
1543	critlen = MAX_TERMCAP_LENGTH;
1544	legend = "older termcap";
1545	infodump = FALSE;
1546	set_obsolete_termcaps(tterm);
1547    } else {
1548	critlen = MAX_TERMINFO_LENGTH;
1549	legend = "terminfo";
1550	infodump = TRUE;
1551    }
1552
1553    save_sgr = set_attributes;
1554
1555    if ((FMT_ENTRY() > critlen)
1556	&& TcOutput()
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 is 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
1654void
1655dump_uses(const char *value, bool infodump)
1656/* dump "use=" clauses in the appropriate format */
1657{
1658    char buffer[MAX_TERMINFO_LENGTH + EXTRA_CAP];
1659    int limit = (VALID_STRING(value) ? (int) strlen(value) : 0);
1660    const char *cap = infodump ? "use" : "tc";
1661
1662    if (TcOutput())
1663	trim_trailing();
1664    if (limit == 0) {
1665	_nc_warning("empty \"%s\" field", cap);
1666	value = "";
1667    } else if (limit > MAX_ALIAS) {
1668	_nc_warning("\"%s\" field too long (%d), limit to %d",
1669		    cap, limit, MAX_ALIAS);
1670	limit = MAX_ALIAS;
1671    }
1672    _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer))
1673		"%s=%.*s", cap, limit, value);
1674    wrap_concat1(buffer);
1675}
1676
1677int
1678show_entry(void)
1679{
1680    /*
1681     * Trim any remaining whitespace.
1682     */
1683    if (outbuf.used != 0) {
1684	bool infodump = !TcOutput();
1685	char delim = (char) (infodump ? ',' : ':');
1686	int j;
1687
1688	for (j = (int) outbuf.used - 1; j > 0; --j) {
1689	    char ch = outbuf.text[j];
1690	    if (ch == '\n') {
1691		;
1692	    } else if (isspace(UChar(ch))) {
1693		outbuf.used = (size_t) j;
1694	    } else if (!infodump && ch == '\\') {
1695		outbuf.used = (size_t) j;
1696	    } else if (ch == delim && (outbuf.text[j - 1] != '\\')) {
1697		outbuf.used = (size_t) (j + 1);
1698	    } else {
1699		break;
1700	    }
1701	}
1702	outbuf.text[outbuf.used] = '\0';
1703    }
1704    if (outbuf.text != 0) {
1705	(void) fputs(outbuf.text, stdout);
1706	putchar('\n');
1707    }
1708    return (int) outbuf.used;
1709}
1710
1711void
1712compare_entry(PredHook hook,
1713	      TERMTYPE2 *tp GCC_UNUSED,
1714	      bool quiet)
1715/* compare two entries */
1716{
1717    PredIdx i, j;
1718    NCURSES_CONST char *name;
1719
1720    if (!quiet)
1721	fputs("    comparing booleans.\n", stdout);
1722    for_each_boolean(j, tp) {
1723	i = BoolIndirect(j);
1724	name = ExtBoolname(tp, (int) i, bool_names);
1725
1726	if (isObsolete(outform, name))
1727	    continue;
1728
1729	(*hook) (CMP_BOOLEAN, i, name);
1730    }
1731
1732    if (!quiet)
1733	fputs("    comparing numbers.\n", stdout);
1734    for_each_number(j, tp) {
1735	i = NumIndirect(j);
1736	name = ExtNumname(tp, (int) i, num_names);
1737
1738	if (isObsolete(outform, name))
1739	    continue;
1740
1741	(*hook) (CMP_NUMBER, i, name);
1742    }
1743
1744    if (!quiet)
1745	fputs("    comparing strings.\n", stdout);
1746    for_each_string(j, tp) {
1747	i = StrIndirect(j);
1748	name = ExtStrname(tp, (int) i, str_names);
1749
1750	if (isObsolete(outform, name))
1751	    continue;
1752
1753	(*hook) (CMP_STRING, i, name);
1754    }
1755
1756    /* (void) fputs("    comparing use entries.\n", stdout); */
1757    (*hook) (CMP_USE, 0, "use");
1758
1759}
1760
1761#define NOTSET(s)	((s) == 0)
1762
1763/*
1764 * This bit of legerdemain turns all the terminfo variable names into
1765 * references to locations in the arrays Booleans, Numbers, and Strings ---
1766 * precisely what's needed.
1767 */
1768#undef CUR
1769#define CUR tp->
1770
1771static void
1772set_obsolete_termcaps(TERMTYPE2 *tp)
1773{
1774#include "capdefaults.c"
1775}
1776
1777/*
1778 * Convert an alternate-character-set string to canonical form: sorted and
1779 * unique.
1780 */
1781void
1782repair_acsc(TERMTYPE2 *tp)
1783{
1784    if (VALID_STRING(acs_chars)) {
1785	size_t n;
1786	char mapped[256];
1787	unsigned source;
1788	unsigned target;
1789	bool fix_needed = FALSE;
1790
1791	for (n = 0, source = 0; acs_chars[n] != 0; n++) {
1792	    target = UChar(acs_chars[n]);
1793	    if (source >= target) {
1794		fix_needed = TRUE;
1795		break;
1796	    }
1797	    source = target;
1798	    if (acs_chars[n + 1])
1799		n++;
1800	}
1801
1802	if (fix_needed) {
1803	    size_t m;
1804	    char extra = 0;
1805
1806	    memset(mapped, 0, sizeof(mapped));
1807	    for (n = 0; acs_chars[n] != 0; n++) {
1808		source = UChar(acs_chars[n]);
1809		if ((target = (unsigned char) acs_chars[n + 1]) != 0) {
1810		    mapped[source] = (char) target;
1811		    n++;
1812		} else {
1813		    extra = (char) source;
1814		}
1815	    }
1816	    for (n = m = 0; n < sizeof(mapped); n++) {
1817		if (mapped[n]) {
1818		    acs_chars[m++] = (char) n;
1819		    acs_chars[m++] = mapped[n];
1820		}
1821	    }
1822	    if (extra)
1823		acs_chars[m++] = extra;		/* garbage in, garbage out */
1824	    acs_chars[m] = 0;
1825	}
1826    }
1827}
1828