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