/**************************************************************************** * Copyright 2018-2019,2020 Thomas E. Dickey * * Copyright 1998-2016,2017 Free Software Foundation, Inc. * * * * Permission is hereby granted, free of charge, to any person obtaining a * * copy of this software and associated documentation files (the * * "Software"), to deal in the Software without restriction, including * * without limitation the rights to use, copy, modify, merge, publish, * * distribute, distribute with modifications, sublicense, and/or sell * * copies of the Software, and to permit persons to whom the Software is * * furnished to do so, subject to the following conditions: * * * * The above copyright notice and this permission notice shall be included * * in all copies or substantial portions of the Software. * * * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR * * THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * * * Except as contained in this notice, the name(s) of the above copyright * * holders shall not be used in advertising or otherwise to promote the * * sale, use or other dealings in this Software without prior written * * authorization. * ****************************************************************************/ /**************************************************************************** * Author: Zeyd M. Ben-Halim 1992,1995 * * and: Eric S. Raymond * * and: Thomas E. Dickey 1996 on * ****************************************************************************/ #define __INTERNAL_CAPS_VISIBLE #include #include "dump_entry.h" #include "termsort.c" /* this C file is generated */ #include /* so is this */ MODULE_ID("$Id: dump_entry.c,v 1.180 2020/11/14 18:18:13 tom Exp $") #define DISCARD(string) string = ABSENT_STRING #define PRINTF (void) printf #define WRAPPED 32 #define OkIndex(index,array) ((int)(index) >= 0 && (int)(index) < (int) SIZEOF(array)) #define TcOutput() (outform == F_TERMCAP || outform == F_TCONVERR) typedef struct { char *text; size_t used; size_t size; } DYNBUF; static int tversion; /* terminfo version */ static int outform; /* output format to use */ static int sortmode; /* sort mode to use */ static int width = 60; /* max line width for listings */ static int height = 65535; /* max number of lines for listings */ static int column; /* current column, limited by 'width' */ static int oldcol; /* last value of column before wrap */ static bool pretty; /* true if we format if-then-else strings */ static bool wrapped; /* true if we wrap too-long strings */ static bool did_wrap; /* true if last wrap_concat did wrapping */ static bool checking; /* true if we are checking for tic */ static int quickdump; /* true if we are dumping compiled data */ static char *save_sgr; static DYNBUF outbuf; static DYNBUF tmpbuf; /* indirection pointers for implementing sort and display modes */ static const PredIdx *bool_indirect, *num_indirect, *str_indirect; static NCURSES_CONST char *const *bool_names; static NCURSES_CONST char *const *num_names; static NCURSES_CONST char *const *str_names; static const char *separator = "", *trailer = ""; static int indent = 8; /* cover various ports and variants of terminfo */ #define V_ALLCAPS 0 /* all capabilities (SVr4, XSI, ncurses) */ #define V_SVR1 1 /* SVR1, Ultrix */ #define V_HPUX 2 /* HP/UX */ #define V_AIX 3 /* AIX */ #define V_BSD 4 /* BSD */ #if NCURSES_XNAMES #define OBSOLETE(n) (!_nc_user_definable && (n[0] == 'O' && n[1] == 'T')) #else #define OBSOLETE(n) (n[0] == 'O' && n[1] == 'T') #endif #define isObsolete(f,n) ((f == F_TERMINFO || f == F_VARIABLE) && (sortmode != S_VARIABLE) && OBSOLETE(n)) #if NCURSES_XNAMES #define BoolIndirect(j) ((j >= BOOLCOUNT) ? (j) : ((sortmode == S_NOSORT) ? j : bool_indirect[j])) #define NumIndirect(j) ((j >= NUMCOUNT) ? (j) : ((sortmode == S_NOSORT) ? j : num_indirect[j])) #define StrIndirect(j) ((j >= STRCOUNT) ? (j) : ((sortmode == S_NOSORT) ? j : str_indirect[j])) #else #define BoolIndirect(j) ((sortmode == S_NOSORT) ? (j) : bool_indirect[j]) #define NumIndirect(j) ((sortmode == S_NOSORT) ? (j) : num_indirect[j]) #define StrIndirect(j) ((sortmode == S_NOSORT) ? (j) : str_indirect[j]) #endif static void failed(const char *) GCC_NORETURN; static void failed(const char *s) { perror(s); ExitProgram(EXIT_FAILURE); } static void strncpy_DYN(DYNBUF * dst, const char *src, size_t need) { size_t want = need + dst->used + 1; if (want > dst->size) { dst->size += (want + 1024); /* be generous */ dst->text = typeRealloc(char, dst->size, dst->text); if (dst->text == 0) failed("strncpy_DYN"); } _nc_STRNCPY(dst->text + dst->used, src, need + 1); dst->used += need; dst->text[dst->used] = 0; } static void strcpy_DYN(DYNBUF * dst, const char *src) { if (src == 0) { dst->used = 0; strcpy_DYN(dst, ""); } else { strncpy_DYN(dst, src, strlen(src)); } } #if NO_LEAKS static void free_DYN(DYNBUF * p) { if (p->text != 0) free(p->text); p->text = 0; p->size = 0; p->used = 0; } void _nc_leaks_dump_entry(void) { free_DYN(&outbuf); free_DYN(&tmpbuf); } #endif #define NameTrans(check,result) \ if ((np->nte_index <= OK_ ## check) \ && check[np->nte_index]) \ return (result[np->nte_index]) NCURSES_CONST char * nametrans(const char *name) /* translate a capability name to termcap from terminfo */ { const struct name_table_entry *np; if ((np = _nc_find_entry(name, _nc_get_hash_table(0))) != 0) { switch (np->nte_type) { case BOOLEAN: NameTrans(bool_from_termcap, boolcodes); break; case NUMBER: NameTrans(num_from_termcap, numcodes); break; case STRING: NameTrans(str_from_termcap, strcodes); break; } } return (0); } void dump_init(const char *version, int mode, int sort, bool wrap_strings, int twidth, int theight, unsigned traceval, bool formatted, bool check, int quick) /* set up for entry display */ { width = twidth; height = theight; pretty = formatted; wrapped = wrap_strings; checking = check; quickdump = (quick & 3); did_wrap = (width <= 0); /* versions */ if (version == 0) tversion = V_ALLCAPS; else if (!strcmp(version, "SVr1") || !strcmp(version, "SVR1") || !strcmp(version, "Ultrix")) tversion = V_SVR1; else if (!strcmp(version, "HP")) tversion = V_HPUX; else if (!strcmp(version, "AIX")) tversion = V_AIX; else if (!strcmp(version, "BSD")) tversion = V_BSD; else tversion = V_ALLCAPS; /* implement display modes */ switch (outform = mode) { case F_LITERAL: case F_TERMINFO: bool_names = boolnames; num_names = numnames; str_names = strnames; separator = (twidth > 0 && theight > 1) ? ", " : ","; trailer = "\n\t"; break; case F_VARIABLE: bool_names = boolfnames; num_names = numfnames; str_names = strfnames; separator = (twidth > 0 && theight > 1) ? ", " : ","; trailer = "\n\t"; break; case F_TERMCAP: case F_TCONVERR: bool_names = boolcodes; num_names = numcodes; str_names = strcodes; separator = ":"; trailer = "\\\n\t:"; break; } indent = 8; /* implement sort modes */ switch (sortmode = sort) { case S_NOSORT: if (traceval) (void) fprintf(stderr, "%s: sorting by term structure order\n", _nc_progname); break; case S_TERMINFO: if (traceval) (void) fprintf(stderr, "%s: sorting by terminfo name order\n", _nc_progname); bool_indirect = bool_terminfo_sort; num_indirect = num_terminfo_sort; str_indirect = str_terminfo_sort; break; case S_VARIABLE: if (traceval) (void) fprintf(stderr, "%s: sorting by C variable order\n", _nc_progname); bool_indirect = bool_variable_sort; num_indirect = num_variable_sort; str_indirect = str_variable_sort; break; case S_TERMCAP: if (traceval) (void) fprintf(stderr, "%s: sorting by termcap name order\n", _nc_progname); bool_indirect = bool_termcap_sort; num_indirect = num_termcap_sort; str_indirect = str_termcap_sort; break; } if (traceval) (void) fprintf(stderr, "%s: width = %d, tversion = %d, outform = %d\n", _nc_progname, width, tversion, outform); } static TERMTYPE2 *cur_type; static int dump_predicate(PredType type, PredIdx idx) /* predicate function to use for ordinary decompilation */ { switch (type) { case BOOLEAN: return (cur_type->Booleans[idx] == FALSE) ? FAIL : cur_type->Booleans[idx]; case NUMBER: return (cur_type->Numbers[idx] == ABSENT_NUMERIC) ? FAIL : cur_type->Numbers[idx]; case STRING: return (cur_type->Strings[idx] != ABSENT_STRING) ? (int) TRUE : FAIL; } return (FALSE); /* pacify compiler */ } static void set_obsolete_termcaps(TERMTYPE2 *tp); /* is this the index of a function key string? */ #define FNKEY(i) \ (((i) >= STR_IDX(key_f0) && \ (i) <= STR_IDX(key_f9)) || \ ((i) >= STR_IDX(key_f11) && \ (i) <= STR_IDX(key_f63))) /* * If we configure with a different Caps file, the offsets into the arrays * will change. So we use an address expression. */ #define BOOL_IDX(name) (PredType) (&(name) - &(CUR Booleans[0])) #define NUM_IDX(name) (PredType) (&(name) - &(CUR Numbers[0])) #define STR_IDX(name) (PredType) (&(name) - &(CUR Strings[0])) static bool version_filter(PredType type, PredIdx idx) /* filter out capabilities we may want to suppress */ { switch (tversion) { case V_ALLCAPS: /* SVr4, XSI Curses */ return (TRUE); case V_SVR1: /* System V Release 1, Ultrix */ switch (type) { case BOOLEAN: return ((idx <= BOOL_IDX(xon_xoff)) ? TRUE : FALSE); case NUMBER: return ((idx <= NUM_IDX(width_status_line)) ? TRUE : FALSE); case STRING: return ((idx <= STR_IDX(prtr_non)) ? TRUE : FALSE); } break; case V_HPUX: /* Hewlett-Packard */ switch (type) { case BOOLEAN: return ((idx <= BOOL_IDX(xon_xoff)) ? TRUE : FALSE); case NUMBER: return ((idx <= NUM_IDX(label_width)) ? TRUE : FALSE); case STRING: if (idx <= STR_IDX(prtr_non)) return (TRUE); else if (FNKEY(idx)) /* function keys */ return (TRUE); else if (idx == STR_IDX(plab_norm) || idx == STR_IDX(label_on) || idx == STR_IDX(label_off)) return (TRUE); else return (FALSE); } break; case V_AIX: /* AIX */ switch (type) { case BOOLEAN: return ((idx <= BOOL_IDX(xon_xoff)) ? TRUE : FALSE); case NUMBER: return ((idx <= NUM_IDX(width_status_line)) ? TRUE : FALSE); case STRING: if (idx <= STR_IDX(prtr_non)) return (TRUE); else if (FNKEY(idx)) /* function keys */ return (TRUE); else return (FALSE); } break; #define is_termcap(type) (OkIndex(idx, type##_from_termcap) && \ type##_from_termcap[idx]) case V_BSD: /* BSD */ switch (type) { case BOOLEAN: return is_termcap(bool); case NUMBER: return is_termcap(num); case STRING: return is_termcap(str); } break; } return (FALSE); /* pacify the compiler */ } static void trim_trailing(void) { while (outbuf.used > 0 && outbuf.text[outbuf.used - 1] == ' ') outbuf.text[--outbuf.used] = '\0'; } static void force_wrap(void) { oldcol = column; trim_trailing(); strcpy_DYN(&outbuf, trailer); column = indent; } static int op_length(const char *src, int offset) { int result = 0; int ch; if (offset > 0 && src[offset - 1] == '\\') { result = 0; } else { result++; /* for '%' mark */ ch = src[offset + result]; if (TcOutput()) { if (ch == '>') { result += 3; } else if (ch == '+') { result += 2; } else { result++; } } else if (ch == '\'') { result += 3; } else if (ch == L_CURL[0]) { int n = result; while ((ch = src[offset + n]) != '\0') { if (ch == R_CURL[0]) { result = ++n; break; } n++; } } else if (strchr("pPg", ch) != 0) { result += 2; } else { result++; /* ordinary operator */ } } return result; } /* * When wrapping too-long strings, avoid splitting a backslash sequence, or * a terminfo '%' operator. That will leave things a little ragged, but avoids * a stray backslash at the end of the line, as well as making the result a * little more readable. */ static int find_split(const char *src, int step, int size) { int result = size; int n; if (size > 0) { /* check if that would split a backslash-sequence */ int mark = size; for (n = size - 1; n > 0; --n) { int ch = UChar(src[step + n]); if (ch == '\\') { if (n > 0 && src[step + n - 1] == ch) --n; mark = n; break; } else if (!isalnum(ch)) { break; } } if (mark < size) { result = mark; } else { /* check if that would split a backslash-sequence */ for (n = size - 1; n > 0; --n) { int ch = UChar(src[step + n]); if (ch == '%') { int need = op_length(src, step + n); if ((n + need) > size) { mark = n; } break; } } if (mark < size) { result = mark; } } } return result; } /* * If we are going to wrap lines, we cannot leave literal spaces because that * would be ambiguous if we split on that space. */ static char * fill_spaces(const char *src) { const char *fill = "\\s"; size_t need = strlen(src); size_t size = strlen(fill); char *result = 0; int pass; int s, d; for (pass = 0; pass < 2; ++pass) { for (s = d = 0; src[s] != '\0'; ++s) { if (src[s] == ' ') { if (pass) { _nc_STRCPY(&result[d], fill, need + 1 - d); d += (int) size; } else { need += size; } } else { if (pass) { result[d++] = src[s]; } else { ++d; } } } if (pass) { result[d] = '\0'; } else { result = malloc(need + 1); if (result == 0) failed("fill_spaces"); } } return result; } typedef enum { wOFF = 0 ,w1ST = 1 ,w2ND = 2 ,wEND = 4 ,wERR = 8 } WRAPMODE; #define wrap_1ST(mode) ((mode)&w1ST) #define wrap_END(mode) ((mode)&wEND) #define wrap_ERR(mode) ((mode)&wERR) static void wrap_concat(const char *src, int need, unsigned mode) { int gaps = (int) strlen(separator); int want = gaps + need; did_wrap = (width <= 0); if (wrap_1ST(mode) && column > indent && column + want > width) { force_wrap(); } if ((wrap_END(mode) && !wrap_ERR(mode)) && wrapped && (width >= 0) && (column + want) > width) { int step = 0; int used = width > WRAPPED ? width : WRAPPED; int size; int base = 0; char *p, align[9]; const char *my_t = trailer; char *fill = fill_spaces(src); int last = (int) strlen(fill); need = last; if (TcOutput()) trailer = "\\\n\t "; if (!TcOutput() && (p = strchr(fill, '=')) != 0) { base = (int) (p + 1 - fill); if (base > 8) base = 8; _nc_SPRINTF(align, _nc_SLIMIT(align) "%*s", base, " "); } else if (column > 8) { base = column - 8; if (base > 8) base = 8; _nc_SPRINTF(align, _nc_SLIMIT(align) "%*s", base, " "); } else { align[base] = '\0'; } /* "pretty" overrides wrapping if it already split the line */ if (!pretty || strchr(fill, '\n') == 0) { int tag = 0; if (TcOutput() && outbuf.used && !wrap_1ST(mode)) { tag = 3; } while ((column + (need + gaps)) > used) { size = used - tag; if (step) { strcpy_DYN(&outbuf, align); size -= base; } if (size > (last - step)) { size = (last - step); } size = find_split(fill, step, size); strncpy_DYN(&outbuf, fill + step, (size_t) size); step += size; need -= size; if (need > 0) { force_wrap(); did_wrap = TRUE; tag = 0; } } } if (need > 0) { if (step) strcpy_DYN(&outbuf, align); strcpy_DYN(&outbuf, fill + step); } if (wrap_END(mode)) strcpy_DYN(&outbuf, separator); trailer = my_t; force_wrap(); free(fill); } else { strcpy_DYN(&outbuf, src); if (wrap_END(mode)) strcpy_DYN(&outbuf, separator); column += (int) strlen(src); } } static void wrap_concat1(const char *src) { int need = (int) strlen(src); wrap_concat(src, need, w1ST | wEND); } static void wrap_concat3(const char *name, const char *eqls, const char *value) { int nlen = (int) strlen(name); int elen = (int) strlen(eqls); int vlen = (int) strlen(value); wrap_concat(name, nlen + elen + vlen, w1ST); wrap_concat(eqls, elen + vlen, w2ND); wrap_concat(value, vlen, wEND); } #define IGNORE_SEP_TRAIL(first,last,sep_trail) \ if ((size_t)(last - first) > sizeof(sep_trail)-1 \ && !strncmp(first, sep_trail, sizeof(sep_trail)-1)) \ first += sizeof(sep_trail)-2 /* Returns the nominal length of the buffer assuming it is termcap format, * i.e., the continuation sequence is treated as a single character ":". * * There are several implementations of termcap which read the text into a * fixed-size buffer. Generally they strip the newlines from the text, but may * not do it until after the buffer is read. Also, "tc=" resolution may be * expanded in the same buffer. This function is useful for measuring the size * of the best fixed-buffer implementation; the worst case may be much worse. */ #ifdef TEST_TERMCAP_LENGTH static int termcap_length(const char *src) { static const char pattern[] = ":\\\n\t:"; int len = 0; const char *const t = src + strlen(src); while (*src != '\0') { IGNORE_SEP_TRAIL(src, t, pattern); src++; len++; } return len; } #else #define termcap_length(src) strlen(src) #endif static void indent_DYN(DYNBUF * buffer, int level) { int n; for (n = 0; n < level; n++) strncpy_DYN(buffer, "\t", (size_t) 1); } /* * Check if the current line which was begun consists only of a tab and the * given leading text. */ static bool leading_DYN(DYNBUF * buffer, const char *leading) { bool result = FALSE; size_t need = strlen(leading); if (buffer->used > need) { need = buffer->used - need; if (!strcmp(buffer->text + need, leading)) { result = TRUE; while (--need != 0) { if (buffer->text[need] == '\n') { break; } if (buffer->text[need] != '\t') { result = FALSE; break; } } } } return result; } bool has_params(const char *src, bool formatting) { bool result = FALSE; int len = (int) strlen(src); int n; bool ifthen = FALSE; bool params = FALSE; for (n = 0; n < len - 1; ++n) { if (!strncmp(src + n, "%p", (size_t) 2)) { params = TRUE; } else if (!strncmp(src + n, "%;", (size_t) 2)) { ifthen = TRUE; result = params; break; } } if (!ifthen) { if (formatting) { result = ((len > 50) && params); } else { result = params; } } return result; } static char * fmt_complex(TERMTYPE2 *tterm, const char *capability, char *src, int level) { bool percent = FALSE; bool params = has_params(src, TRUE); while (*src != '\0') { switch (*src) { case '^': percent = FALSE; strncpy_DYN(&tmpbuf, src++, (size_t) 1); break; case '\\': percent = FALSE; strncpy_DYN(&tmpbuf, src++, (size_t) 1); break; case '%': percent = TRUE; break; case '?': /* "if" */ case 't': /* "then" */ case 'e': /* "else" */ if (percent) { percent = FALSE; tmpbuf.text[tmpbuf.used - 1] = '\n'; /* treat a "%e" as else-if, on the same level */ if (*src == 'e') { indent_DYN(&tmpbuf, level); strncpy_DYN(&tmpbuf, "%", (size_t) 1); strncpy_DYN(&tmpbuf, src, (size_t) 1); src++; params = has_params(src, TRUE); if (!params && *src != '\0' && *src != '%') { strncpy_DYN(&tmpbuf, "\n", (size_t) 1); indent_DYN(&tmpbuf, level + 1); } } else { indent_DYN(&tmpbuf, level + 1); strncpy_DYN(&tmpbuf, "%", (size_t) 1); strncpy_DYN(&tmpbuf, src, (size_t) 1); if (*src++ == '?') { src = fmt_complex(tterm, capability, src, level + 1); if (*src != '\0' && *src != '%') { strncpy_DYN(&tmpbuf, "\n", (size_t) 1); indent_DYN(&tmpbuf, level + 1); } } else if (level == 1) { if (checking) _nc_warning("%s: %%%c without %%? in %s", _nc_first_name(tterm->term_names), *src, capability); } } continue; } break; case ';': /* "endif" */ if (percent) { percent = FALSE; if (level > 1) { tmpbuf.text[tmpbuf.used - 1] = '\n'; indent_DYN(&tmpbuf, level); strncpy_DYN(&tmpbuf, "%", (size_t) 1); strncpy_DYN(&tmpbuf, src++, (size_t) 1); if (src[0] == '%' && src[1] != '\0' && (strchr("?e;", src[1])) == 0) { tmpbuf.text[tmpbuf.used++] = '\n'; indent_DYN(&tmpbuf, level); } return src; } if (checking) _nc_warning("%s: %%; without %%? in %s", _nc_first_name(tterm->term_names), capability); } break; case 'p': if (percent && params && !leading_DYN(&tmpbuf, "%")) { tmpbuf.text[tmpbuf.used - 1] = '\n'; indent_DYN(&tmpbuf, level + 1); strncpy_DYN(&tmpbuf, "%", (size_t) 1); } params = FALSE; percent = FALSE; break; case ' ': strncpy_DYN(&tmpbuf, "\\s", (size_t) 2); ++src; continue; default: percent = FALSE; break; } strncpy_DYN(&tmpbuf, src++, (size_t) 1); } return src; } /* * Make "large" numbers a little easier to read by showing them in hexadecimal * if they are "close" to a power of two. */ static const char * number_format(int value) { const char *result = "%d"; if ((outform != F_TERMCAP) && (value > 255)) { unsigned long lv = (unsigned long) value; unsigned long mm; int bits = sizeof(unsigned long) * 8; int nn; for (nn = 8; nn < bits; ++nn) { mm = 1UL << nn; if ((mm - 16) <= lv && (mm + 16) > lv) { result = "%#x"; break; } } } return result; } #define SAME_CAP(n,cap) (&tterm->Strings[n] == &cap) #define EXTRA_CAP 20 int fmt_entry(TERMTYPE2 *tterm, PredFunc pred, int content_only, int suppress_untranslatable, int infodump, int numbers) { PredIdx i, j; char buffer[MAX_TERMINFO_LENGTH + EXTRA_CAP]; char *capability; NCURSES_CONST char *name; int predval, len; PredIdx num_bools = 0; PredIdx num_values = 0; PredIdx num_strings = 0; bool outcount = 0; #define WRAP_CONCAT1(s) wrap_concat1(s); outcount = TRUE #define WRAP_CONCAT WRAP_CONCAT1(buffer) len = 12; /* terminfo file-header */ if (pred == 0) { cur_type = tterm; pred = dump_predicate; } strcpy_DYN(&outbuf, 0); if (content_only) { column = indent; /* FIXME: workaround to prevent empty lines */ } else { strcpy_DYN(&outbuf, tterm->term_names); /* * Colon is legal in terminfo descriptions, but not in termcap. */ if (!infodump) { char *p = outbuf.text; while (*p) { if (*p == ':') { *p = '='; } ++p; } } strcpy_DYN(&outbuf, separator); column = (int) outbuf.used; if (height > 1) force_wrap(); } for_each_boolean(j, tterm) { i = BoolIndirect(j); name = ExtBoolname(tterm, (int) i, bool_names); assert(strlen(name) < sizeof(buffer) - EXTRA_CAP); if (!version_filter(BOOLEAN, i)) continue; else if (isObsolete(outform, name)) continue; predval = pred(BOOLEAN, i); if (predval != FAIL) { _nc_STRCPY(buffer, name, sizeof(buffer)); if (predval <= 0) _nc_STRCAT(buffer, "@", sizeof(buffer)); else if (i + 1 > num_bools) num_bools = i + 1; WRAP_CONCAT; } } if (column != indent && height > 1) force_wrap(); for_each_number(j, tterm) { i = NumIndirect(j); name = ExtNumname(tterm, (int) i, num_names); assert(strlen(name) < sizeof(buffer) - EXTRA_CAP); if (!version_filter(NUMBER, i)) continue; else if (isObsolete(outform, name)) continue; predval = pred(NUMBER, i); if (predval != FAIL) { if (tterm->Numbers[i] < 0) { _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer)) "%s@", name); } else { size_t nn; _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer)) "%s#", name); nn = strlen(buffer); _nc_SPRINTF(buffer + nn, _nc_SLIMIT(sizeof(buffer) - nn) number_format(tterm->Numbers[i]), tterm->Numbers[i]); if (i + 1 > num_values) num_values = i + 1; } WRAP_CONCAT; } } if (column != indent && height > 1) force_wrap(); len += (int) (num_bools + num_values * 2 + strlen(tterm->term_names) + 1); if (len & 1) len++; #undef CUR #define CUR tterm-> if (outform == F_TERMCAP) { if (VALID_STRING(termcap_reset)) { if (VALID_STRING(init_3string) && !strcmp(init_3string, termcap_reset)) DISCARD(init_3string); if (VALID_STRING(reset_2string) && !strcmp(reset_2string, termcap_reset)) DISCARD(reset_2string); } } for_each_string(j, tterm) { i = StrIndirect(j); name = ExtStrname(tterm, (int) i, str_names); assert(strlen(name) < sizeof(buffer) - EXTRA_CAP); capability = tterm->Strings[i]; if (!version_filter(STRING, i)) continue; else if (isObsolete(outform, name)) continue; #if NCURSES_XNAMES /* * Extended names can be longer than 2 characters, but termcap programs * cannot read those (filter them out). */ if (outform == F_TERMCAP && (strlen(name) > 2)) continue; #endif if (outform == F_TERMCAP) { /* * Some older versions of vi want rmir/smir to be defined * for ich/ich1 to work. If they're not defined, force * them to be output as defined and empty. */ if (PRESENT(insert_character) || PRESENT(parm_ich)) { if (SAME_CAP(i, enter_insert_mode) && enter_insert_mode == ABSENT_STRING) { _nc_STRCPY(buffer, "im=", sizeof(buffer)); WRAP_CONCAT; continue; } if (SAME_CAP(i, exit_insert_mode) && exit_insert_mode == ABSENT_STRING) { _nc_STRCPY(buffer, "ei=", sizeof(buffer)); WRAP_CONCAT; continue; } } /* * termcap applications such as screen will be confused if sgr0 * is translated to a string containing rmacs. Filter that out. */ if (PRESENT(exit_attribute_mode)) { if (SAME_CAP(i, exit_attribute_mode)) { char *trimmed_sgr0; char *my_sgr = set_attributes; set_attributes = save_sgr; trimmed_sgr0 = _nc_trim_sgr0(tterm); if (strcmp(capability, trimmed_sgr0)) { capability = trimmed_sgr0; } else { if (trimmed_sgr0 != exit_attribute_mode) free(trimmed_sgr0); } set_attributes = my_sgr; } } } predval = pred(STRING, i); buffer[0] = '\0'; if (predval != FAIL) { if (VALID_STRING(capability) && i + 1 > num_strings) num_strings = i + 1; if (!VALID_STRING(capability)) { _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer)) "%s@", name); WRAP_CONCAT; } else if (TcOutput()) { char *srccap = _nc_tic_expand(capability, TRUE, numbers); int params = ((i < (int) SIZEOF(parametrized)) ? parametrized[i] : ((*srccap == 'k') ? 0 : has_params(srccap, FALSE))); char *cv = _nc_infotocap(name, srccap, params); if (cv == 0) { if (outform == F_TCONVERR) { _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer)) "%s=!!! %s WILL NOT CONVERT !!!", name, srccap); WRAP_CONCAT; } else if (suppress_untranslatable) { continue; } else { char *s = srccap, *d = buffer; int need = 3 + (int) strlen(name); while ((*d = *s++) != 0) { if ((d - buffer + 2) >= (int) sizeof(buffer)) { fprintf(stderr, "%s: value for %s is too long\n", _nc_progname, name); *d = '\0'; break; } if (*d == ':') { *d++ = '\\'; *d = ':'; } else if (*d == '\\') { if ((*++d = *s++) == '\0') break; } d++; *d = '\0'; } need += (int) (d - buffer); wrap_concat("..", need, w1ST | wERR); need -= 2; wrap_concat(name, need, wOFF | wERR); need -= (int) strlen(name); wrap_concat("=", need, w2ND | wERR); need -= 1; wrap_concat(buffer, need, wEND | wERR); outcount = TRUE; } } else { wrap_concat3(name, "=", cv); } len += (int) strlen(capability) + 1; } else { char *src = _nc_tic_expand(capability, outform == F_TERMINFO, numbers); strcpy_DYN(&tmpbuf, 0); strcpy_DYN(&tmpbuf, name); strcpy_DYN(&tmpbuf, "="); if (pretty && (outform == F_TERMINFO || outform == F_VARIABLE)) { fmt_complex(tterm, name, src, 1); } else { strcpy_DYN(&tmpbuf, src); } len += (int) strlen(capability) + 1; WRAP_CONCAT1(tmpbuf.text); } } /* e.g., trimmed_sgr0 */ if (VALID_STRING(capability) && capability != tterm->Strings[i]) free(capability); } len += (int) (num_strings * 2); /* * This piece of code should be an effective inverse of the functions * postprocess_terminfo() and postprocess_terminfo() in parse_entry.c. * Much more work should be done on this to support dumping termcaps. */ if (tversion == V_HPUX) { if (VALID_STRING(memory_lock)) { _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer)) "meml=%s", memory_lock); WRAP_CONCAT; } if (VALID_STRING(memory_unlock)) { _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer)) "memu=%s", memory_unlock); WRAP_CONCAT; } } else if (tversion == V_AIX) { if (VALID_STRING(acs_chars)) { bool box_ok = TRUE; const char *acstrans = "lqkxjmwuvtn"; const char *cp; char *tp, *sp, boxchars[11]; tp = boxchars; for (cp = acstrans; *cp; cp++) { sp = (strchr) (acs_chars, *cp); if (sp) *tp++ = sp[1]; else { box_ok = FALSE; break; } } tp[0] = '\0'; if (box_ok) { char *tmp = _nc_tic_expand(boxchars, (outform == F_TERMINFO), numbers); _nc_STRCPY(buffer, "box1=", sizeof(buffer)); while (*tmp != '\0') { size_t have = strlen(buffer); size_t next = strlen(tmp); size_t want = have + next + 1; size_t last = next; char save = '\0'; /* * If the expanded string is too long for the buffer, * chop it off and save the location where we chopped it. */ if (want >= sizeof(buffer)) { save = tmp[last]; tmp[last] = '\0'; } _nc_STRCAT(buffer, tmp, sizeof(buffer)); /* * If we chopped the buffer, replace the missing piece and * shift everything to append the remainder. */ if (save != '\0') { next = 0; tmp[last] = save; while ((tmp[next] = tmp[last + next]) != '\0') { ++next; } } else { break; } } WRAP_CONCAT; } } } /* * kludge: trim off trailer to avoid an extra blank line * in infocmp -u output when there are no string differences */ if (outcount) { bool trimmed = FALSE; j = (PredIdx) outbuf.used; if (wrapped && did_wrap) { /* EMPTY */ ; } else if (j >= 2 && outbuf.text[j - 1] == '\t' && outbuf.text[j - 2] == '\n') { outbuf.used -= 2; trimmed = TRUE; } else if (j >= 4 && outbuf.text[j - 1] == ':' && outbuf.text[j - 2] == '\t' && outbuf.text[j - 3] == '\n' && outbuf.text[j - 4] == '\\') { outbuf.used -= 4; trimmed = TRUE; } if (trimmed) { outbuf.text[outbuf.used] = '\0'; column = oldcol; strcpy_DYN(&outbuf, " "); } } #if 0 fprintf(stderr, "num_bools = %d\n", num_bools); fprintf(stderr, "num_values = %d\n", num_values); fprintf(stderr, "num_strings = %d\n", num_strings); fprintf(stderr, "term_names=%s, len=%d, strlen(outbuf)=%d, outbuf=%s\n", tterm->term_names, len, outbuf.used, outbuf.text); #endif /* * Here's where we use infodump to trigger a more stringent length check * for termcap-translation purposes. * Return the length of the raw entry, without tc= expansions, * It gives an idea of which entries are deadly to even *scan past*, * as opposed to *use*. */ return (infodump ? len : (int) termcap_length(outbuf.text)); } static bool kill_string(TERMTYPE2 *tterm, char *cap) { unsigned n; for (n = 0; n < NUM_STRINGS(tterm); ++n) { if (cap == tterm->Strings[n]) { tterm->Strings[n] = ABSENT_STRING; return TRUE; } } return FALSE; } static char * find_string(TERMTYPE2 *tterm, char *name) { PredIdx n; for (n = 0; n < NUM_STRINGS(tterm); ++n) { if (version_filter(STRING, n) && !strcmp(name, strnames[n])) { char *cap = tterm->Strings[n]; if (VALID_STRING(cap)) { return cap; } break; } } return ABSENT_STRING; } /* * This is used to remove function-key labels from a termcap entry to * make it smaller. */ static int kill_labels(TERMTYPE2 *tterm, int target) { int n; int result = 0; char *cap; char name[10]; for (n = 0; n <= 10; ++n) { _nc_SPRINTF(name, _nc_SLIMIT(sizeof(name)) "lf%d", n); cap = find_string(tterm, name); if (VALID_STRING(cap) && kill_string(tterm, cap)) { target -= (int) (strlen(cap) + 5); ++result; if (target < 0) break; } } return result; } /* * This is used to remove function-key definitions from a termcap entry to * make it smaller. */ static int kill_fkeys(TERMTYPE2 *tterm, int target) { int n; int result = 0; char *cap; char name[10]; for (n = 60; n >= 0; --n) { _nc_SPRINTF(name, _nc_SLIMIT(sizeof(name)) "kf%d", n); cap = find_string(tterm, name); if (VALID_STRING(cap) && kill_string(tterm, cap)) { target -= (int) (strlen(cap) + 5); ++result; if (target < 0) break; } } return result; } /* * Check if the given acsc string is a 1-1 mapping, i.e., just-like-vt100. * Also, since this is for termcap, we only care about the line-drawing map. */ #define isLine(c) (strchr("lmkjtuvwqxn", c) != 0) static bool one_one_mapping(const char *mapping) { bool result = TRUE; if (VALID_STRING(mapping)) { int n = 0; while (mapping[n] != '\0' && mapping[n + 1] != '\0') { if (isLine(mapping[n]) && mapping[n] != mapping[n + 1]) { result = FALSE; break; } n += 2; } } return result; } #define FMT_ENTRY() \ fmt_entry(tterm, pred, \ 0, \ suppress_untranslatable, \ infodump, numbers) #define SHOW_WHY PRINTF static bool purged_acs(TERMTYPE2 *tterm) { bool result = FALSE; if (VALID_STRING(acs_chars)) { if (!one_one_mapping(acs_chars)) { enter_alt_charset_mode = ABSENT_STRING; exit_alt_charset_mode = ABSENT_STRING; SHOW_WHY("# (rmacs/smacs removed for consistency)\n"); } result = TRUE; } return result; } static void encode_b64(char *target, char *source, unsigned state, int *saved) { /* RFC-4648 */ static const char data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789" "-_"; int ch = UChar(source[state]); switch (state % 3) { case 0: *target++ = data[(ch >> 2) & 077]; *saved = (ch << 4); break; case 1: *target++ = data[((ch >> 4) | *saved) & 077]; *saved = (ch << 2); break; case 2: *target++ = data[((ch >> 6) | *saved) & 077]; *target++ = data[ch & 077]; *saved = 0; break; } *target = '\0'; } /* * Dump a single entry. */ void dump_entry(TERMTYPE2 *tterm, int suppress_untranslatable, int limited, int numbers, PredFunc pred) { TERMTYPE2 save_tterm; int len, critlen; const char *legend; bool infodump; if (quickdump) { char bigbuf[65536]; unsigned n; unsigned offset = 0; separator = ""; trailer = "\n"; indent = 0; if (_nc_write_object(tterm, bigbuf, &offset, sizeof(bigbuf)) == OK) { char numbuf[80]; if (quickdump & 1) { if (outbuf.used) wrap_concat1("\n"); wrap_concat1("hex:"); for (n = 0; n < offset; ++n) { _nc_SPRINTF(numbuf, _nc_SLIMIT(sizeof(numbuf)) "%02X", UChar(bigbuf[n])); wrap_concat1(numbuf); } } if (quickdump & 2) { static char padding[] = {0, 0}; int value = 0; if (outbuf.used) wrap_concat1("\n"); wrap_concat1("b64:"); for (n = 0; n < offset; ++n) { encode_b64(numbuf, bigbuf, n, &value); wrap_concat1(numbuf); } switch (n % 3) { case 0: break; case 1: encode_b64(numbuf, padding, 1, &value); wrap_concat1(numbuf); wrap_concat1("=="); break; case 2: encode_b64(numbuf, padding, 1, &value); wrap_concat1(numbuf); wrap_concat1("="); break; } } } return; } if (TcOutput()) { critlen = MAX_TERMCAP_LENGTH; legend = "older termcap"; infodump = FALSE; set_obsolete_termcaps(tterm); } else { critlen = MAX_TERMINFO_LENGTH; legend = "terminfo"; infodump = TRUE; } save_sgr = set_attributes; if ((FMT_ENTRY() > critlen) && limited) { save_tterm = *tterm; if (!suppress_untranslatable) { SHOW_WHY("# (untranslatable capabilities removed to fit entry within %d bytes)\n", critlen); suppress_untranslatable = TRUE; } if (FMT_ENTRY() > critlen) { /* * We pick on sgr because it's a nice long string capability that * is really just an optimization hack. Another good candidate is * acsc since it is both long and unused by BSD termcap. */ bool changed = FALSE; #if NCURSES_XNAMES /* * Extended names are most likely function-key definitions. Drop * those first. */ unsigned n; for (n = STRCOUNT; n < NUM_STRINGS(tterm); n++) { const char *name = ExtStrname(tterm, (int) n, strnames); if (VALID_STRING(tterm->Strings[n])) { set_attributes = ABSENT_STRING; /* we remove long names anyway - only report the short */ if (strlen(name) <= 2) { SHOW_WHY("# (%s removed to fit entry within %d bytes)\n", name, critlen); } changed = TRUE; if (FMT_ENTRY() <= critlen) break; } } #endif if (VALID_STRING(set_attributes)) { set_attributes = ABSENT_STRING; SHOW_WHY("# (sgr removed to fit entry within %d bytes)\n", critlen); changed = TRUE; } if (!changed || (FMT_ENTRY() > critlen)) { if (purged_acs(tterm)) { acs_chars = ABSENT_STRING; SHOW_WHY("# (acsc removed to fit entry within %d bytes)\n", critlen); changed = TRUE; } } if (!changed || (FMT_ENTRY() > critlen)) { int oldversion = tversion; tversion = V_BSD; SHOW_WHY("# (terminfo-only capabilities suppressed to fit entry within %d bytes)\n", critlen); len = FMT_ENTRY(); if (len > critlen && kill_labels(tterm, len - critlen)) { SHOW_WHY("# (some labels capabilities suppressed to fit entry within %d bytes)\n", critlen); len = FMT_ENTRY(); } if (len > critlen && kill_fkeys(tterm, len - critlen)) { SHOW_WHY("# (some function-key capabilities suppressed to fit entry within %d bytes)\n", critlen); len = FMT_ENTRY(); } if (len > critlen) { (void) fprintf(stderr, "%s: %s entry is %d bytes long\n", _nc_progname, _nc_first_name(tterm->term_names), len); SHOW_WHY("# WARNING: this entry, %d bytes long, may core-dump %s libraries!\n", len, legend); } tversion = oldversion; } set_attributes = save_sgr; *tterm = save_tterm; } } else if (!version_filter(STRING, STR_IDX(acs_chars))) { save_tterm = *tterm; if (purged_acs(tterm)) { (void) FMT_ENTRY(); } *tterm = save_tterm; } } void dump_uses(const char *name, bool infodump) /* dump "use=" clauses in the appropriate format */ { char buffer[MAX_TERMINFO_LENGTH]; if (TcOutput()) trim_trailing(); _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer)) "%s%s", infodump ? "use=" : "tc=", name); wrap_concat1(buffer); } int show_entry(void) { /* * Trim any remaining whitespace. */ if (outbuf.used != 0) { bool infodump = !TcOutput(); char delim = (char) (infodump ? ',' : ':'); int j; for (j = (int) outbuf.used - 1; j > 0; --j) { char ch = outbuf.text[j]; if (ch == '\n') { ; } else if (isspace(UChar(ch))) { outbuf.used = (size_t) j; } else if (!infodump && ch == '\\') { outbuf.used = (size_t) j; } else if (ch == delim && (j == 0 || outbuf.text[j - 1] != '\\')) { outbuf.used = (size_t) (j + 1); } else { break; } } outbuf.text[outbuf.used] = '\0'; } if (outbuf.text != 0) { (void) fputs(outbuf.text, stdout); putchar('\n'); } return (int) outbuf.used; } void compare_entry(PredHook hook, TERMTYPE2 *tp GCC_UNUSED, bool quiet) /* compare two entries */ { PredIdx i, j; NCURSES_CONST char *name; if (!quiet) fputs(" comparing booleans.\n", stdout); for_each_boolean(j, tp) { i = BoolIndirect(j); name = ExtBoolname(tp, (int) i, bool_names); if (isObsolete(outform, name)) continue; (*hook) (CMP_BOOLEAN, i, name); } if (!quiet) fputs(" comparing numbers.\n", stdout); for_each_number(j, tp) { i = NumIndirect(j); name = ExtNumname(tp, (int) i, num_names); if (isObsolete(outform, name)) continue; (*hook) (CMP_NUMBER, i, name); } if (!quiet) fputs(" comparing strings.\n", stdout); for_each_string(j, tp) { i = StrIndirect(j); name = ExtStrname(tp, (int) i, str_names); if (isObsolete(outform, name)) continue; (*hook) (CMP_STRING, i, name); } /* (void) fputs(" comparing use entries.\n", stdout); */ (*hook) (CMP_USE, 0, "use"); } #define NOTSET(s) ((s) == 0) /* * This bit of legerdemain turns all the terminfo variable names into * references to locations in the arrays Booleans, Numbers, and Strings --- * precisely what's needed. */ #undef CUR #define CUR tp-> static void set_obsolete_termcaps(TERMTYPE2 *tp) { #include "capdefaults.c" } /* * Convert an alternate-character-set string to canonical form: sorted and * unique. */ void repair_acsc(TERMTYPE2 *tp) { if (VALID_STRING(acs_chars)) { size_t n, m; char mapped[256]; char extra = 0; unsigned source; unsigned target; bool fix_needed = FALSE; for (n = 0, source = 0; acs_chars[n] != 0; n++) { target = UChar(acs_chars[n]); if (source >= target) { fix_needed = TRUE; break; } source = target; if (acs_chars[n + 1]) n++; } if (fix_needed) { memset(mapped, 0, sizeof(mapped)); for (n = 0; acs_chars[n] != 0; n++) { source = UChar(acs_chars[n]); if ((target = (unsigned char) acs_chars[n + 1]) != 0) { mapped[source] = (char) target; n++; } else { extra = (char) source; } } for (n = m = 0; n < sizeof(mapped); n++) { if (mapped[n]) { acs_chars[m++] = (char) n; acs_chars[m++] = mapped[n]; } } if (extra) acs_chars[m++] = extra; /* garbage in, garbage out */ acs_chars[m] = 0; } } }