tic.c revision 62449
1/****************************************************************************
2 * Copyright (c) 1998,1999,2000 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 ****************************************************************************/
33
34/*
35 *	tic.c --- Main program for terminfo compiler
36 *			by Eric S. Raymond
37 *
38 */
39
40#include <progs.priv.h>
41
42#include <dump_entry.h>
43#include <term_entry.h>
44
45MODULE_ID("$Id: tic.c,v 1.69 2000/04/08 23:53:49 tom Exp $")
46
47const char *_nc_progname = "tic";
48
49static FILE *log_fp;
50static FILE *tmp_fp;
51static bool showsummary = FALSE;
52static const char *to_remove;
53
54static void (*save_check_termtype) (TERMTYPE *);
55static void check_termtype(TERMTYPE * tt);
56
57static const char usage_string[] = "[-h] [-v[n]] [-e names] [-CILNRTcfrswx1] source-file\n";
58
59static void
60cleanup(void)
61{
62    if (tmp_fp != 0)
63	fclose(tmp_fp);
64    if (to_remove != 0) {
65#if HAVE_REMOVE
66	remove(to_remove);
67#else
68	unlink(to_remove);
69#endif
70    }
71}
72
73static void
74failed(const char *msg)
75{
76    perror(msg);
77    cleanup();
78    exit(EXIT_FAILURE);
79}
80
81static void
82usage(void)
83{
84    static const char *const tbl[] =
85    {
86	"Options:",
87	"  -1         format translation output one capability per line",
88	"  -C         translate entries to termcap source form",
89	"  -I         translate entries to terminfo source form",
90	"  -L         translate entries to full terminfo source form",
91	"  -N         disable smart defaults for source translation",
92	"  -R         restrict translation to given terminfo/termcap version",
93	"  -T         remove size-restrictions on compiled description",
94#if NCURSES_XNAMES
95	"  -a         retain commented-out capabilities (sets -x also)",
96#endif
97	"  -c         check only, validate input without compiling or translating",
98	"  -f         format complex strings for readability",
99	"  -G         format %{number} to %'char'",
100	"  -g         format %'char' to %{number}",
101	"  -e<names>  translate/compile only entries named by comma-separated list",
102	"  -o<dir>    set output directory for compiled entry writes",
103	"  -r         force resolution of all use entries in source translation",
104	"  -s         print summary statistics",
105	"  -v[n]      set verbosity level",
106	"  -w[n]      set format width for translation output",
107#if NCURSES_XNAMES
108	"  -x         treat unknown capabilities as user-defined",
109#endif
110	"",
111	"Parameters:",
112	"  <file>     file to translate or compile"
113    };
114    size_t j;
115
116    fprintf(stderr, "Usage: %s %s\n", _nc_progname, usage_string);
117    for (j = 0; j < sizeof(tbl) / sizeof(tbl[0]); j++) {
118	fputs(tbl[j], stderr);
119	putc('\n', stderr);
120    }
121    exit(EXIT_FAILURE);
122}
123
124#define L_BRACE '{'
125#define R_BRACE '}'
126#define S_QUOTE '\'';
127
128static void
129write_it(ENTRY * ep)
130{
131    unsigned n;
132    int ch;
133    char *s, *d, *t;
134    char result[MAX_ENTRY_SIZE];
135
136    /*
137     * Look for strings that contain %{number}, convert them to %'char',
138     * which is shorter and runs a little faster.
139     */
140    for (n = 0; n < STRCOUNT; n++) {
141	s = ep->tterm.Strings[n];
142	if (VALID_STRING(s)
143	    && strchr(s, L_BRACE) != 0) {
144	    d = result;
145	    t = s;
146	    while ((ch = *t++) != 0) {
147		*d++ = ch;
148		if (ch == '\\') {
149		    *d++ = *t++;
150		} else if ((ch == '%')
151		    && (*t == L_BRACE)) {
152		    char *v = 0;
153		    long value = strtol(t + 1, &v, 0);
154		    if (v != 0
155			&& *v == R_BRACE
156			&& value > 0
157			&& value != '\\'	/* FIXME */
158			&& value < 127
159			&& isprint((int) value)) {
160			*d++ = S_QUOTE;
161			*d++ = (int) value;
162			*d++ = S_QUOTE;
163			t = (v + 1);
164		    }
165		}
166	    }
167	    *d = 0;
168	    if (strlen(result) < strlen(s))
169		strcpy(s, result);
170	}
171    }
172
173    _nc_set_type(_nc_first_name(ep->tterm.term_names));
174    _nc_curr_line = ep->startline;
175    _nc_write_entry(&ep->tterm);
176}
177
178static bool
179immedhook(ENTRY * ep GCC_UNUSED)
180/* write out entries with no use capabilities immediately to save storage */
181{
182#ifndef HAVE_BIG_CORE
183    /*
184     * This is strictly a core-economy kluge.  The really clean way to handle
185     * compilation is to slurp the whole file into core and then do all the
186     * name-collision checks and entry writes in one swell foop.  But the
187     * terminfo master file is large enough that some core-poor systems swap
188     * like crazy when you compile it this way...there have been reports of
189     * this process taking *three hours*, rather than the twenty seconds or
190     * less typical on my development box.
191     *
192     * So.  This hook *immediately* writes out the referenced entry if it
193     * has no use capabilities.  The compiler main loop refrains from
194     * adding the entry to the in-core list when this hook fires.  If some
195     * other entry later needs to reference an entry that got written
196     * immediately, that's OK; the resolution code will fetch it off disk
197     * when it can't find it in core.
198     *
199     * Name collisions will still be detected, just not as cleanly.  The
200     * write_entry() code complains before overwriting an entry that
201     * postdates the time of tic's first call to write_entry().  Thus
202     * it will complain about overwriting entries newly made during the
203     * tic run, but not about overwriting ones that predate it.
204     *
205     * The reason this is a hook, and not in line with the rest of the
206     * compiler code, is that the support for termcap fallback cannot assume
207     * it has anywhere to spool out these entries!
208     *
209     * The _nc_set_type() call here requires a compensating one in
210     * _nc_parse_entry().
211     *
212     * If you define HAVE_BIG_CORE, you'll disable this kluge.  This will
213     * make tic a bit faster (because the resolution code won't have to do
214     * disk I/O nearly as often).
215     */
216    if (ep->nuses == 0) {
217	int oldline = _nc_curr_line;
218
219	write_it(ep);
220	_nc_curr_line = oldline;
221	free(ep->tterm.str_table);
222	return (TRUE);
223    }
224#endif /* HAVE_BIG_CORE */
225    return (FALSE);
226}
227
228static void
229put_translate(int c)
230/* emit a comment char, translating terminfo names to termcap names */
231{
232    static bool in_name = FALSE;
233    static size_t have, used;
234    static char *namebuf, *suffix;
235
236    if (in_name) {
237	if (used + 1 >= have) {
238	    have += 132;
239	    namebuf = typeRealloc(char, have, namebuf);
240	    suffix = typeRealloc(char, have, suffix);
241	}
242	if (c == '\n' || c == '@') {
243	    namebuf[used++] = '\0';
244	    (void) putchar('<');
245	    (void) fputs(namebuf, stdout);
246	    putchar(c);
247	    in_name = FALSE;
248	} else if (c != '>') {
249	    namebuf[used++] = c;
250	} else {		/* ah! candidate name! */
251	    char *up;
252	    NCURSES_CONST char *tp;
253
254	    namebuf[used++] = '\0';
255	    in_name = FALSE;
256
257	    suffix[0] = '\0';
258	    if ((up = strchr(namebuf, '#')) != 0
259		|| (up = strchr(namebuf, '=')) != 0
260		|| ((up = strchr(namebuf, '@')) != 0 && up[1] == '>')) {
261		(void) strcpy(suffix, up);
262		*up = '\0';
263	    }
264
265	    if ((tp = nametrans(namebuf)) != 0) {
266		(void) putchar(':');
267		(void) fputs(tp, stdout);
268		(void) fputs(suffix, stdout);
269		(void) putchar(':');
270	    } else {
271		/* couldn't find a translation, just dump the name */
272		(void) putchar('<');
273		(void) fputs(namebuf, stdout);
274		(void) fputs(suffix, stdout);
275		(void) putchar('>');
276	    }
277	}
278    } else {
279	used = 0;
280	if (c == '<') {
281	    in_name = TRUE;
282	} else {
283	    putchar(c);
284	}
285    }
286}
287
288/* Returns a string, stripped of leading/trailing whitespace */
289static char *
290stripped(char *src)
291{
292    while (isspace(*src))
293	src++;
294    if (*src != '\0') {
295	char *dst = strcpy(malloc(strlen(src) + 1), src);
296	size_t len = strlen(dst);
297	while (--len != 0 && isspace(dst[len]))
298	    dst[len] = '\0';
299	return dst;
300    }
301    return 0;
302}
303
304/* Parse the "-e" option-value into a list of names */
305static const char **
306make_namelist(char *src)
307{
308    const char **dst = 0;
309
310    char *s, *base;
311    unsigned pass, n, nn;
312    char buffer[BUFSIZ];
313
314    if (src == 0) {
315	/* EMPTY */ ;
316    } else if (strchr(src, '/') != 0) {		/* a filename */
317	FILE *fp = fopen(src, "r");
318	if (fp == 0)
319	    failed(src);
320
321	for (pass = 1; pass <= 2; pass++) {
322	    nn = 0;
323	    while (fgets(buffer, sizeof(buffer), fp) != 0) {
324		if ((s = stripped(buffer)) != 0) {
325		    if (dst != 0)
326			dst[nn] = s;
327		    nn++;
328		}
329	    }
330	    if (pass == 1) {
331		dst = typeCalloc(const char *, nn + 1);
332		rewind(fp);
333	    }
334	}
335	fclose(fp);
336    } else {			/* literal list of names */
337	for (pass = 1; pass <= 2; pass++) {
338	    for (n = nn = 0, base = src;; n++) {
339		int mark = src[n];
340		if (mark == ',' || mark == '\0') {
341		    if (pass == 1) {
342			nn++;
343		    } else {
344			src[n] = '\0';
345			if ((s = stripped(base)) != 0)
346			    dst[nn++] = s;
347			base = &src[n + 1];
348		    }
349		}
350		if (mark == '\0')
351		    break;
352	    }
353	    if (pass == 1)
354		dst = typeCalloc(const char *, nn + 1);
355	}
356    }
357    if (showsummary) {
358	fprintf(log_fp, "Entries that will be compiled:\n");
359	for (n = 0; dst[n] != 0; n++)
360	    fprintf(log_fp, "%d:%s\n", n + 1, dst[n]);
361    }
362    return dst;
363}
364
365static bool
366matches(const char **needle, const char *haystack)
367/* does entry in needle list match |-separated field in haystack? */
368{
369    bool code = FALSE;
370    size_t n;
371
372    if (needle != 0) {
373	for (n = 0; needle[n] != 0; n++) {
374	    if (_nc_name_match(haystack, needle[n], "|")) {
375		code = TRUE;
376		break;
377	    }
378	}
379    } else
380	code = TRUE;
381    return (code);
382}
383
384static FILE *
385open_tempfile(char *name)
386{
387    FILE *result = 0;
388#if HAVE_MKSTEMP
389    int fd = mkstemp(name);
390    if (fd >= 0)
391	result = fdopen(fd, "w");
392#else
393    if (tmpnam(name) != 0)
394	result = fopen(name, "w");
395#endif
396    return result;
397}
398
399int
400main(int argc, char *argv[])
401{
402    char my_tmpname[PATH_MAX];
403    int v_opt = -1, debug_level;
404    int smart_defaults = TRUE;
405    char *termcap;
406    ENTRY *qp;
407
408    int this_opt, last_opt = '?';
409
410    int outform = F_TERMINFO;	/* output format */
411    int sortmode = S_TERMINFO;	/* sort_mode */
412
413    int width = 60;
414    bool formatted = FALSE;	/* reformat complex strings? */
415    int numbers = 0;		/* format "%'char'" to/from "%{number}" */
416    bool infodump = FALSE;	/* running as captoinfo? */
417    bool capdump = FALSE;	/* running as infotocap? */
418    bool forceresolve = FALSE;	/* force resolution */
419    bool limited = TRUE;
420    char *tversion = (char *) NULL;
421    const char *source_file = "terminfo";
422    const char **namelst = 0;
423    char *outdir = (char *) NULL;
424    bool check_only = FALSE;
425
426    log_fp = stderr;
427
428    if ((_nc_progname = strrchr(argv[0], '/')) == NULL)
429	_nc_progname = argv[0];
430    else
431	_nc_progname++;
432
433    if ((infodump = (strcmp(_nc_progname, "captoinfo") == 0)) != FALSE) {
434	outform = F_TERMINFO;
435	sortmode = S_TERMINFO;
436    }
437    if ((capdump = (strcmp(_nc_progname, "infotocap") == 0)) != FALSE) {
438	outform = F_TERMCAP;
439	sortmode = S_TERMCAP;
440    }
441#if NCURSES_XNAMES
442    use_extended_names(FALSE);
443#endif
444
445    /*
446     * Processing arguments is a little complicated, since someone made a
447     * design decision to allow the numeric values for -w, -v options to
448     * be optional.
449     */
450    while ((this_opt = getopt(argc, argv,
451		"0123456789CILNR:TVace:fGgo:rsvwx")) != EOF) {
452	if (isdigit(this_opt)) {
453	    switch (last_opt) {
454	    case 'v':
455		v_opt = (v_opt * 10) + (this_opt - '0');
456		break;
457	    case 'w':
458		width = (width * 10) + (this_opt - '0');
459		break;
460	    default:
461		if (this_opt != '1')
462		    usage();
463		last_opt = this_opt;
464		width = 0;
465	    }
466	    continue;
467	}
468	switch (this_opt) {
469	case 'C':
470	    capdump = TRUE;
471	    outform = F_TERMCAP;
472	    sortmode = S_TERMCAP;
473	    break;
474	case 'I':
475	    infodump = TRUE;
476	    outform = F_TERMINFO;
477	    sortmode = S_TERMINFO;
478	    break;
479	case 'L':
480	    infodump = TRUE;
481	    outform = F_VARIABLE;
482	    sortmode = S_VARIABLE;
483	    break;
484	case 'N':
485	    smart_defaults = FALSE;
486	    break;
487	case 'R':
488	    tversion = optarg;
489	    break;
490	case 'T':
491	    limited = FALSE;
492	    break;
493	case 'V':
494	    puts(NCURSES_VERSION);
495	    return EXIT_SUCCESS;
496	case 'c':
497	    check_only = TRUE;
498	    break;
499	case 'e':
500	    namelst = make_namelist(optarg);
501	    break;
502	case 'f':
503	    formatted = TRUE;
504	    break;
505	case 'G':
506	    numbers = 1;
507	    break;
508	case 'g':
509	    numbers = -1;
510	    break;
511	case 'o':
512	    outdir = optarg;
513	    break;
514	case 'r':
515	    forceresolve = TRUE;
516	    break;
517	case 's':
518	    showsummary = TRUE;
519	    break;
520	case 'v':
521	    v_opt = 0;
522	    break;
523	case 'w':
524	    width = 0;
525	    break;
526#if NCURSES_XNAMES
527	case 'a':
528	    _nc_disable_period = TRUE;
529	    /* FALLTHRU */
530	case 'x':
531	    use_extended_names(TRUE);
532	    break;
533#endif
534	default:
535	    usage();
536	}
537	last_opt = this_opt;
538    }
539
540    debug_level = (v_opt > 0) ? v_opt : (v_opt == 0);
541    set_trace_level(debug_level);
542
543    if (_nc_tracing) {
544	save_check_termtype = _nc_check_termtype;
545	_nc_check_termtype = check_termtype;
546    }
547#ifndef HAVE_BIG_CORE
548    /*
549     * Aaargh! immedhook seriously hoses us!
550     *
551     * One problem with immedhook is it means we can't do -e.  Problem
552     * is that we can't guarantee that for each terminal listed, all the
553     * terminals it depends on will have been kept in core for reference
554     * resolution -- in fact it's certain the primitive types at the end
555     * of reference chains *won't* be in core unless they were explicitly
556     * in the select list themselves.
557     */
558    if (namelst && (!infodump && !capdump)) {
559	(void) fprintf(stderr,
560	    "Sorry, -e can't be used without -I or -C\n");
561	cleanup();
562	return EXIT_FAILURE;
563    }
564#endif /* HAVE_BIG_CORE */
565
566    if (optind < argc) {
567	source_file = argv[optind++];
568	if (optind < argc) {
569	    fprintf(stderr,
570		"%s: Too many file names.  Usage:\n\t%s %s",
571		_nc_progname,
572		_nc_progname,
573		usage_string);
574	    return EXIT_FAILURE;
575	}
576    } else {
577	if (infodump == TRUE) {
578	    /* captoinfo's no-argument case */
579	    source_file = "/etc/termcap";
580	    if ((termcap = getenv("TERMCAP")) != 0
581		&& (namelst = make_namelist(getenv("TERM"))) != 0) {
582		if (access(termcap, F_OK) == 0) {
583		    /* file exists */
584		    source_file = termcap;
585		} else if ((tmp_fp = open_tempfile(my_tmpname)) != 0) {
586		    source_file = my_tmpname;
587		    fprintf(tmp_fp, "%s\n", termcap);
588		    fclose(tmp_fp);
589		    tmp_fp = fopen(source_file, "r");
590		    to_remove = source_file;
591		} else {
592		    failed("tmpnam");
593		}
594	    }
595	} else {
596	    /* tic */
597	    fprintf(stderr,
598		"%s: File name needed.  Usage:\n\t%s %s",
599		_nc_progname,
600		_nc_progname,
601		usage_string);
602	    cleanup();
603	    return EXIT_FAILURE;
604	}
605    }
606
607    if (tmp_fp == 0
608	&& (tmp_fp = fopen(source_file, "r")) == 0) {
609	fprintf(stderr, "%s: Can't open %s\n", _nc_progname, source_file);
610	return EXIT_FAILURE;
611    }
612
613    if (infodump)
614	dump_init(tversion,
615	    smart_defaults
616	    ? outform
617	    : F_LITERAL,
618	    sortmode, width, debug_level, formatted);
619    else if (capdump)
620	dump_init(tversion,
621	    outform,
622	    sortmode, width, debug_level, FALSE);
623
624    /* parse entries out of the source file */
625    _nc_set_source(source_file);
626#ifndef HAVE_BIG_CORE
627    if (!(check_only || infodump || capdump))
628	_nc_set_writedir(outdir);
629#endif /* HAVE_BIG_CORE */
630    _nc_read_entry_source(tmp_fp, (char *) NULL,
631	!smart_defaults, FALSE,
632	(check_only || infodump || capdump) ? NULLHOOK : immedhook);
633
634    /* do use resolution */
635    if (check_only || (!infodump && !capdump) || forceresolve) {
636	if (!_nc_resolve_uses(TRUE) && !check_only) {
637	    cleanup();
638	    return EXIT_FAILURE;
639	}
640    }
641
642    /* length check */
643    if (check_only && (capdump || infodump)) {
644	for_entry_list(qp) {
645	    if (matches(namelst, qp->tterm.term_names)) {
646		int len = fmt_entry(&qp->tterm, NULL, TRUE, infodump, numbers);
647
648		if (len > (infodump ? MAX_TERMINFO_LENGTH : MAX_TERMCAP_LENGTH))
649		    (void) fprintf(stderr,
650			"warning: resolved %s entry is %d bytes long\n",
651			_nc_first_name(qp->tterm.term_names),
652			len);
653	    }
654	}
655    }
656
657    /* write or dump all entries */
658    if (!check_only) {
659	if (!infodump && !capdump) {
660	    _nc_set_writedir(outdir);
661	    for_entry_list(qp) {
662		if (matches(namelst, qp->tterm.term_names))
663		    write_it(qp);
664	    }
665	} else {
666	    /* this is in case infotocap() generates warnings */
667	    _nc_curr_col = _nc_curr_line = -1;
668
669	    for_entry_list(qp) {
670		if (matches(namelst, qp->tterm.term_names)) {
671		    int j = qp->cend - qp->cstart;
672		    int len = 0;
673
674		    /* this is in case infotocap() generates warnings */
675		    _nc_set_type(_nc_first_name(qp->tterm.term_names));
676
677		    (void) fseek(tmp_fp, qp->cstart, SEEK_SET);
678		    while (j--) {
679			if (infodump)
680			    (void) putchar(fgetc(tmp_fp));
681			else
682			    put_translate(fgetc(tmp_fp));
683		    }
684
685		    len = dump_entry(&qp->tterm, limited, numbers, NULL);
686		    for (j = 0; j < qp->nuses; j++)
687			len += dump_uses(qp->uses[j].name, !capdump);
688		    (void) putchar('\n');
689		    if (debug_level != 0 && !limited)
690			printf("# length=%d\n", len);
691		}
692	    }
693	    if (!namelst) {
694		int c, oldc = '\0';
695		bool in_comment = FALSE;
696		bool trailing_comment = FALSE;
697
698		(void) fseek(tmp_fp, _nc_tail->cend, SEEK_SET);
699		while ((c = fgetc(tmp_fp)) != EOF) {
700		    if (oldc == '\n') {
701			if (c == '#') {
702			    trailing_comment = TRUE;
703			    in_comment = TRUE;
704			} else {
705			    in_comment = FALSE;
706			}
707		    }
708		    if (trailing_comment
709			&& (in_comment || (oldc == '\n' && c == '\n')))
710			putchar(c);
711		    oldc = c;
712		}
713	    }
714	}
715    }
716
717    /* Show the directory into which entries were written, and the total
718     * number of entries
719     */
720    if (showsummary
721	&& (!(check_only || infodump || capdump))) {
722	int total = _nc_tic_written();
723	if (total != 0)
724	    fprintf(log_fp, "%d entries written to %s\n",
725		total,
726		_nc_tic_dir((char *) 0));
727	else
728	    fprintf(log_fp, "No entries written\n");
729    }
730    cleanup();
731    return (EXIT_SUCCESS);
732}
733
734/*
735 * This bit of legerdemain turns all the terminfo variable names into
736 * references to locations in the arrays Booleans, Numbers, and Strings ---
737 * precisely what's needed (see comp_parse.c).
738 */
739
740TERMINAL *cur_term;		/* tweak to avoid linking lib_cur_term.c */
741
742#undef CUR
743#define CUR tp->
744
745/*
746 * An sgr string may contain several settings other than the one we're
747 * interested in, essentially sgr0 + rmacs + whatever.  As long as the
748 * "whatever" is contained in the sgr string, that is close enough for our
749 * sanity check.
750 */
751static bool
752similar_sgr(char *a, char *b)
753{
754    while (*b != 0) {
755	while (*a != *b) {
756	    if (*a == 0)
757		return FALSE;
758	    a++;
759	}
760	a++;
761	b++;
762    }
763    return TRUE;
764}
765
766static void
767check_sgr(TERMTYPE * tp, char *zero, int num, char *cap, const char *name)
768{
769    char *test = tparm(set_attributes,
770	num == 1,
771	num == 2,
772	num == 3,
773	num == 4,
774	num == 5,
775	num == 6,
776	num == 7,
777	num == 8,
778	num == 9);
779    if (test != 0) {
780	if (PRESENT(cap)) {
781	    if (!similar_sgr(test, cap)) {
782		_nc_warning("%s differs from sgr(%d): %s", name, num,
783		    _nc_visbuf(test));
784	    }
785	} else if (strcmp(test, zero)) {
786	    _nc_warning("sgr(%d) present, but not %s", num, name);
787	}
788    } else if (PRESENT(cap)) {
789	_nc_warning("sgr(%d) missing, but %s present", num, name);
790    }
791}
792
793#define CHECK_SGR(num,name) check_sgr(tp, zero, num, name, #name)
794
795/* other sanity-checks (things that we don't want in the normal
796 * logic that reads a terminfo entry)
797 */
798static void
799check_termtype(TERMTYPE * tp)
800{
801    bool conflict = FALSE;
802    unsigned j, k;
803    char fkeys[STRCOUNT];
804
805    /*
806     * A terminal entry may contain more than one keycode assigned to
807     * a given string (e.g., KEY_END and KEY_LL).  But curses will only
808     * return one (the last one assigned).
809     */
810    memset(fkeys, 0, sizeof(fkeys));
811    for (j = 0; _nc_tinfo_fkeys[j].code; j++) {
812	char *a = tp->Strings[_nc_tinfo_fkeys[j].offset];
813	bool first = TRUE;
814	if (!VALID_STRING(a))
815	    continue;
816	for (k = j + 1; _nc_tinfo_fkeys[k].code; k++) {
817	    char *b = tp->Strings[_nc_tinfo_fkeys[k].offset];
818	    if (!VALID_STRING(b)
819		|| fkeys[k])
820		continue;
821	    if (!strcmp(a, b)) {
822		fkeys[j] = 1;
823		fkeys[k] = 1;
824		if (first) {
825		    if (!conflict) {
826			_nc_warning("Conflicting key definitions (using the last)");
827			conflict = TRUE;
828		    }
829		    fprintf(stderr, "... %s is the same as %s",
830			keyname(_nc_tinfo_fkeys[j].code),
831			keyname(_nc_tinfo_fkeys[k].code));
832		    first = FALSE;
833		} else {
834		    fprintf(stderr, ", %s",
835			keyname(_nc_tinfo_fkeys[k].code));
836		}
837	    }
838	}
839	if (!first)
840	    fprintf(stderr, "\n");
841    }
842
843    /*
844     * Quick check for color.  We could also check if the ANSI versus
845     * non-ANSI strings are misused.
846     */
847    if ((max_colors > 0) != (max_pairs > 0)
848	|| (max_colors > max_pairs))
849	_nc_warning("inconsistent values for max_colors and max_pairs");
850
851    PAIRED(set_foreground, set_background);
852    PAIRED(set_a_foreground, set_a_background);
853
854    /*
855     * These may be mismatched because the terminal description relies on
856     * restoring the cursor visibility by resetting it.
857     */
858    ANDMISSING(cursor_invisible, cursor_normal);
859    ANDMISSING(cursor_visible, cursor_normal);
860
861    if (PRESENT(cursor_visible) && PRESENT(cursor_normal)
862	&& !strcmp(cursor_visible, cursor_normal))
863	_nc_warning("cursor_visible is same as cursor_normal");
864
865    /*
866     * From XSI & O'Reilly, we gather that sc/rc are required if csr is
867     * given, because the cursor position after the scrolling operation is
868     * performed is undefined.
869     */
870    ANDMISSING(change_scroll_region, save_cursor);
871    ANDMISSING(change_scroll_region, restore_cursor);
872
873    if (PRESENT(set_attributes)) {
874	char *zero = tparm(set_attributes, 0, 0, 0, 0, 0, 0, 0, 0, 0);
875
876	zero = strdup(zero);
877	CHECK_SGR(1, enter_standout_mode);
878	CHECK_SGR(2, enter_underline_mode);
879	CHECK_SGR(3, enter_reverse_mode);
880	CHECK_SGR(4, enter_blink_mode);
881	CHECK_SGR(5, enter_dim_mode);
882	CHECK_SGR(6, enter_bold_mode);
883	CHECK_SGR(7, enter_secure_mode);
884	CHECK_SGR(8, enter_protected_mode);
885	CHECK_SGR(9, enter_alt_charset_mode);
886	free(zero);
887    }
888
889    /*
890     * Some standard applications (e.g., vi) and some non-curses
891     * applications (e.g., jove) get confused if we have both ich/ich1 and
892     * smir/rmir.  Let's be nice and warn about that, too, even though
893     * ncurses handles it.
894     */
895    if ((PRESENT(enter_insert_mode) || PRESENT(exit_insert_mode))
896	&& (PRESENT(insert_character) || PRESENT(parm_ich))) {
897	_nc_warning("non-curses applications may be confused by ich/ich1 with smir/rmir");
898    }
899
900    /*
901     * Finally, do the non-verbose checks
902     */
903    if (save_check_termtype != 0)
904	save_check_termtype(tp);
905}
906