xref: /openbsd/usr.bin/tic/tic.c (revision 09467b48)
1 /*	$OpenBSD: tic.c,v 1.34 2019/06/28 13:35:04 deraadt Exp $	*/
2 
3 /****************************************************************************
4  * Copyright (c) 1998-2007,2008 Free Software Foundation, Inc.              *
5  *                                                                          *
6  * Permission is hereby granted, free of charge, to any person obtaining a  *
7  * copy of this software and associated documentation files (the            *
8  * "Software"), to deal in the Software without restriction, including      *
9  * without limitation the rights to use, copy, modify, merge, publish,      *
10  * distribute, distribute with modifications, sublicense, and/or sell       *
11  * copies of the Software, and to permit persons to whom the Software is    *
12  * furnished to do so, subject to the following conditions:                 *
13  *                                                                          *
14  * The above copyright notice and this permission notice shall be included  *
15  * in all copies or substantial portions of the Software.                   *
16  *                                                                          *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
18  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
19  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
20  * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
21  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
22  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
23  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
24  *                                                                          *
25  * Except as contained in this notice, the name(s) of the above copyright   *
26  * holders shall not be used in advertising or otherwise to promote the     *
27  * sale, use or other dealings in this Software without prior written       *
28  * authorization.                                                           *
29  ****************************************************************************/
30 
31 /****************************************************************************
32  *  Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995               *
33  *     and: Eric S. Raymond <esr@snark.thyrsus.com>                         *
34  *     and: Thomas E. Dickey                        1996 on                 *
35  ****************************************************************************/
36 
37 /*
38  *	tic.c --- Main program for terminfo compiler
39  *			by Eric S. Raymond
40  *
41  */
42 
43 #include <progs.priv.h>
44 #include <sys/stat.h>
45 
46 #include <dump_entry.h>
47 #include <transform.h>
48 
49 MODULE_ID("$Id: tic.c,v 1.34 2019/06/28 13:35:04 deraadt Exp $")
50 
51 const char *_nc_progname = "tic";
52 
53 static FILE *log_fp;
54 static FILE *tmp_fp;
55 static bool capdump = FALSE;	/* running as infotocap? */
56 static bool infodump = FALSE;	/* running as captoinfo? */
57 static bool showsummary = FALSE;
58 static const char *to_remove;
59 
60 static void (*save_check_termtype) (TERMTYPE *, bool);
61 static void check_termtype(TERMTYPE *tt, bool);
62 
63 static const char usage_string[] = "\
64 [-e names] \
65 [-o dir] \
66 [-R name] \
67 [-v[n]] \
68 [-V] \
69 [-w[n]] \
70 [-\
71 1\
72 a\
73 C\
74 c\
75 f\
76 G\
77 g\
78 I\
79 L\
80 N\
81 r\
82 s\
83 T\
84 t\
85 U\
86 x\
87 ] \
88 source-file\n";
89 
90 #if NO_LEAKS
91 static void
92 free_namelist(char **src)
93 {
94     if (src != 0) {
95 	int n;
96 	for (n = 0; src[n] != 0; ++n)
97 	    free(src[n]);
98 	free(src);
99     }
100 }
101 #endif
102 
103 static void
104 cleanup(char **namelst GCC_UNUSED)
105 {
106 #if NO_LEAKS
107     free_namelist(namelst);
108 #endif
109     if (tmp_fp != 0)
110 	fclose(tmp_fp);
111     if (to_remove != 0) {
112 #if HAVE_REMOVE
113 	remove(to_remove);
114 #else
115 	unlink(to_remove);
116 #endif
117     }
118 }
119 
120 static void
121 failed(const char *msg)
122 {
123     perror(msg);
124     cleanup((char **) 0);
125     ExitProgram(EXIT_FAILURE);
126 }
127 
128 static void
129 usage(void)
130 {
131     static const char *const tbl[] =
132     {
133 	"Options:",
134 	"  -1         format translation output one capability per line",
135 #if NCURSES_XNAMES
136 	"  -a         retain commented-out capabilities (sets -x also)",
137 #endif
138 	"  -C         translate entries to termcap source form",
139 	"  -c         check only, validate input without compiling or translating",
140 	"  -e<names>  translate/compile only entries named by comma-separated list",
141 	"  -f         format complex strings for readability",
142 	"  -G         format %{number} to %'char'",
143 	"  -g         format %'char' to %{number}",
144 	"  -I         translate entries to terminfo source form",
145 	"  -L         translate entries to full terminfo source form",
146 	"  -N         disable smart defaults for source translation",
147 	"  -o<dir>    set output directory for compiled entry writes",
148 	"  -R<name>   restrict translation to given terminfo/termcap version",
149 	"  -r         force resolution of all use entries in source translation",
150 	"  -s         print summary statistics",
151 	"  -T         remove size-restrictions on compiled description",
152 #if NCURSES_XNAMES
153 	"  -t         suppress commented-out capabilities",
154 #endif
155 	"  -U         suppress post-processing of entries",
156 	"  -V         print version",
157 	"  -v[n]      set verbosity level",
158 	"  -w[n]      set format width for translation output",
159 #if NCURSES_XNAMES
160 	"  -x         treat unknown capabilities as user-defined",
161 #endif
162 	"",
163 	"Parameters:",
164 	"  <file>     file to translate or compile"
165     };
166     size_t j;
167 
168     fprintf(stderr, "Usage: %s %s\n", _nc_progname, usage_string);
169     for (j = 0; j < SIZEOF(tbl); j++) {
170 	fputs(tbl[j], stderr);
171 	putc('\n', stderr);
172     }
173     ExitProgram(EXIT_FAILURE);
174 }
175 
176 #define L_BRACE '{'
177 #define R_BRACE '}'
178 #define S_QUOTE '\'';
179 
180 static void
181 write_it(ENTRY * ep)
182 {
183     unsigned n;
184     int ch;
185     char *s, *d, *t;
186     char result[MAX_ENTRY_SIZE];
187 
188     /*
189      * Look for strings that contain %{number}, convert them to %'char',
190      * which is shorter and runs a little faster.
191      */
192     for (n = 0; n < STRCOUNT; n++) {
193 	s = ep->tterm.Strings[n];
194 	if (VALID_STRING(s)
195 	    && strchr(s, L_BRACE) != 0) {
196 	    d = result;
197 	    t = s;
198 	    while ((ch = *t++) != 0) {
199 		*d++ = (char) ch;
200 		if (ch == '\\') {
201 		    *d++ = *t++;
202 		} else if ((ch == '%')
203 			   && (*t == L_BRACE)) {
204 		    char *v = 0;
205 		    long value = strtol(t + 1, &v, 0);
206 		    if (v != 0
207 			&& *v == R_BRACE
208 			&& value > 0
209 			&& value != '\\'	/* FIXME */
210 			&& value < 127
211 			&& isprint((int) value)) {
212 			*d++ = S_QUOTE;
213 			*d++ = (char) value;
214 			*d++ = S_QUOTE;
215 			t = (v + 1);
216 		    }
217 		}
218 	    }
219 	    *d = 0;
220             if (strlen(result) < strlen(s)) {
221 		    /* new string is same length as what is there, or shorter */
222 		    strlcpy(s, result, strlen(s));
223             }
224 	}
225     }
226 
227     _nc_set_type(_nc_first_name(ep->tterm.term_names));
228     _nc_curr_line = ep->startline;
229     _nc_write_entry(&ep->tterm);
230 }
231 
232 static bool
233 immedhook(ENTRY * ep GCC_UNUSED)
234 /* write out entries with no use capabilities immediately to save storage */
235 {
236 #if !HAVE_BIG_CORE
237     /*
238      * This is strictly a core-economy kluge.  The really clean way to handle
239      * compilation is to slurp the whole file into core and then do all the
240      * name-collision checks and entry writes in one swell foop.  But the
241      * terminfo master file is large enough that some core-poor systems swap
242      * like crazy when you compile it this way...there have been reports of
243      * this process taking *three hours*, rather than the twenty seconds or
244      * less typical on my development box.
245      *
246      * So.  This hook *immediately* writes out the referenced entry if it
247      * has no use capabilities.  The compiler main loop refrains from
248      * adding the entry to the in-core list when this hook fires.  If some
249      * other entry later needs to reference an entry that got written
250      * immediately, that's OK; the resolution code will fetch it off disk
251      * when it can't find it in core.
252      *
253      * Name collisions will still be detected, just not as cleanly.  The
254      * write_entry() code complains before overwriting an entry that
255      * postdates the time of tic's first call to write_entry().  Thus
256      * it will complain about overwriting entries newly made during the
257      * tic run, but not about overwriting ones that predate it.
258      *
259      * The reason this is a hook, and not in line with the rest of the
260      * compiler code, is that the support for termcap fallback cannot assume
261      * it has anywhere to spool out these entries!
262      *
263      * The _nc_set_type() call here requires a compensating one in
264      * _nc_parse_entry().
265      *
266      * If you define HAVE_BIG_CORE, you'll disable this kluge.  This will
267      * make tic a bit faster (because the resolution code won't have to do
268      * disk I/O nearly as often).
269      */
270     if (ep->nuses == 0) {
271 	int oldline = _nc_curr_line;
272 
273 	write_it(ep);
274 	_nc_curr_line = oldline;
275 	free(ep->tterm.str_table);
276 	return (TRUE);
277     }
278 #endif /* HAVE_BIG_CORE */
279     return (FALSE);
280 }
281 
282 static void
283 put_translate(int c)
284 /* emit a comment char, translating terminfo names to termcap names */
285 {
286     static bool in_name = FALSE;
287     static size_t have, used;
288     static char *namebuf, *suffix;
289 
290     if (in_name) {
291 	if (used + 1 >= have) {
292 	    have += 132;
293 	    namebuf = typeRealloc(char, have, namebuf);
294 	    suffix = typeRealloc(char, have, suffix);
295 	}
296 	if (c == '\n' || c == '@') {
297 	    namebuf[used++] = '\0';
298 	    (void) putchar('<');
299 	    (void) fputs(namebuf, stdout);
300 	    putchar(c);
301 	    in_name = FALSE;
302 	} else if (c != '>') {
303 	    namebuf[used++] = (char) c;
304 	} else {		/* ah! candidate name! */
305 	    char *up;
306 	    NCURSES_CONST char *tp;
307 
308 	    namebuf[used++] = '\0';
309 	    in_name = FALSE;
310 
311 	    suffix[0] = '\0';
312 	    if ((up = strchr(namebuf, '#')) != 0
313 		|| (up = strchr(namebuf, '=')) != 0
314 		|| ((up = strchr(namebuf, '@')) != 0 && up[1] == '>')) {
315 		    (void) strlcpy(suffix, up, have);
316 		*up = '\0';
317 	    }
318 
319 	    if ((tp = nametrans(namebuf)) != 0) {
320 		(void) putchar(':');
321 		(void) fputs(tp, stdout);
322 		(void) fputs(suffix, stdout);
323 		(void) putchar(':');
324 	    } else {
325 		/* couldn't find a translation, just dump the name */
326 		(void) putchar('<');
327 		(void) fputs(namebuf, stdout);
328 		(void) fputs(suffix, stdout);
329 		(void) putchar('>');
330 	    }
331 	}
332     } else {
333 	used = 0;
334 	if (c == '<') {
335 	    in_name = TRUE;
336 	} else {
337 	    putchar(c);
338 	}
339     }
340 }
341 
342 /* Returns a string, stripped of leading/trailing whitespace */
343 static char *
344 stripped(char *src)
345 {
346     while (isspace(UChar(*src)))
347 	src++;
348     if (*src != '\0') {
349 	char *dst;
350 	size_t len;
351 
352 	if ((dst = strdup(src)) == NULL)
353 	    failed("strdup");
354 	len = strlen(dst);
355 	while (--len != 0 && isspace(UChar(dst[len])))
356 	    dst[len] = '\0';
357 	return dst;
358     }
359     return 0;
360 }
361 
362 static FILE *
363 open_input(const char *filename)
364 {
365     FILE *fp = fopen(filename, "r");
366     struct stat sb;
367 
368     if (fp == 0) {
369 	fprintf(stderr, "%s: Can't open %s\n", _nc_progname, filename);
370 	ExitProgram(EXIT_FAILURE);
371     }
372     if (fstat(fileno(fp), &sb) == -1
373 	|| (sb.st_mode & S_IFMT) != S_IFREG) {
374 	fprintf(stderr, "%s: %s is not a file\n", _nc_progname, filename);
375 	ExitProgram(EXIT_FAILURE);
376     }
377     return fp;
378 }
379 
380 /* Parse the "-e" option-value into a list of names */
381 static char **
382 make_namelist(char *src)
383 {
384     char **dst = 0;
385 
386     char *s, *base;
387     unsigned pass, n, nn;
388     char buffer[BUFSIZ];
389 
390     if (src == 0) {
391 	/* EMPTY */ ;
392     } else if (strchr(src, '/') != 0) {		/* a filename */
393 	FILE *fp = open_input(src);
394 
395 	for (pass = 1; pass <= 2; pass++) {
396 	    nn = 0;
397 	    while (fgets(buffer, sizeof(buffer), fp) != NULL) {
398 		if ((s = stripped(buffer)) != 0) {
399 		    if (dst != 0)
400 			dst[nn] = s;
401 		    else
402 			free(s);
403 		    nn++;
404 		}
405 	    }
406 	    if (pass == 1) {
407 		dst = typeCalloc(char *, nn + 1);
408 		rewind(fp);
409 	    }
410 	}
411 	fclose(fp);
412     } else {			/* literal list of names */
413 	for (pass = 1; pass <= 2; pass++) {
414 	    for (n = nn = 0, base = src;; n++) {
415 		int mark = src[n];
416 		if (mark == ',' || mark == '\0') {
417 		    if (pass == 1) {
418 			nn++;
419 		    } else {
420 			src[n] = '\0';
421 			if ((s = stripped(base)) != 0)
422 			    dst[nn++] = s;
423 			base = &src[n + 1];
424 		    }
425 		}
426 		if (mark == '\0')
427 		    break;
428 	    }
429 	    if (pass == 1)
430 		dst = typeCalloc(char *, nn + 1);
431 	}
432     }
433     if (showsummary && (dst != 0)) {
434 	fprintf(log_fp, "Entries that will be compiled:\n");
435 	for (n = 0; dst[n] != 0; n++)
436 	    fprintf(log_fp, "%u:%s\n", n + 1, dst[n]);
437     }
438     return dst;
439 }
440 
441 static bool
442 matches(char **needle, const char *haystack)
443 /* does entry in needle list match |-separated field in haystack? */
444 {
445     bool code = FALSE;
446     size_t n;
447 
448     if (needle != 0) {
449 	for (n = 0; needle[n] != 0; n++) {
450 	    if (_nc_name_match(haystack, needle[n], "|")) {
451 		code = TRUE;
452 		break;
453 	    }
454 	}
455     } else
456 	code = TRUE;
457     return (code);
458 }
459 
460 static FILE *
461 open_tempfile(char *name)
462 {
463     FILE *result = 0;
464 #if HAVE_MKSTEMP
465     int fd = mkstemp(name);
466     if (fd >= 0)
467 	result = fdopen(fd, "w");
468 #else
469     if (tmpnam(name) != 0)
470 	result = fopen(name, "w");
471 #endif
472     return result;
473 }
474 
475 int
476 main(int argc, char *argv[])
477 {
478     char my_tmpname[PATH_MAX];
479     int v_opt = -1, debug_level;
480     int smart_defaults = TRUE;
481     char *termcap;
482     ENTRY *qp;
483 
484     int this_opt, last_opt = '?';
485 
486     int outform = F_TERMINFO;	/* output format */
487     int sortmode = S_TERMINFO;	/* sort_mode */
488 
489     int width = 60;
490     bool formatted = FALSE;	/* reformat complex strings? */
491     bool literal = FALSE;	/* suppress post-processing? */
492     int numbers = 0;		/* format "%'char'" to/from "%{number}" */
493     bool forceresolve = FALSE;	/* force resolution */
494     bool limited = TRUE;
495     char *tversion = (char *) NULL;
496     const char *source_file = "terminfo";
497     char **namelst = 0;
498     char *outdir = (char *) NULL;
499     bool check_only = FALSE;
500     bool suppress_untranslatable = FALSE;
501 
502     if (pledge("stdio rpath wpath cpath", NULL) == -1) {
503 	perror("pledge");
504 	exit(1);
505     }
506 
507     log_fp = stderr;
508 
509     _nc_progname = _nc_rootname(argv[0]);
510 
511     if ((infodump = (strcmp(_nc_progname, PROG_CAPTOINFO) == 0)) != FALSE) {
512 	outform = F_TERMINFO;
513 	sortmode = S_TERMINFO;
514     }
515     if ((capdump = (strcmp(_nc_progname, PROG_INFOTOCAP) == 0)) != FALSE) {
516 	outform = F_TERMCAP;
517 	sortmode = S_TERMCAP;
518     }
519 #if NCURSES_XNAMES
520     use_extended_names(FALSE);
521 #endif
522 
523     /*
524      * Processing arguments is a little complicated, since someone made a
525      * design decision to allow the numeric values for -w, -v options to
526      * be optional.
527      */
528     while ((this_opt = getopt(argc, argv,
529 			      "0123456789CILNR:TUVace:fGgo:rstvwx")) != -1) {
530 	if (isdigit(this_opt)) {
531 	    switch (last_opt) {
532 	    case 'v':
533 		v_opt = (v_opt * 10) + (this_opt - '0');
534 		break;
535 	    case 'w':
536 		width = (width * 10) + (this_opt - '0');
537 		break;
538 	    default:
539 		if (this_opt != '1')
540 		    usage();
541 		last_opt = this_opt;
542 		width = 0;
543 	    }
544 	    continue;
545 	}
546 	switch (this_opt) {
547 	case 'C':
548 	    capdump = TRUE;
549 	    outform = F_TERMCAP;
550 	    sortmode = S_TERMCAP;
551 	    break;
552 	case 'I':
553 	    infodump = TRUE;
554 	    outform = F_TERMINFO;
555 	    sortmode = S_TERMINFO;
556 	    break;
557 	case 'L':
558 	    infodump = TRUE;
559 	    outform = F_VARIABLE;
560 	    sortmode = S_VARIABLE;
561 	    break;
562 	case 'N':
563 	    smart_defaults = FALSE;
564 	    literal = TRUE;
565 	    break;
566 	case 'R':
567 	    tversion = optarg;
568 	    break;
569 	case 'T':
570 	    limited = FALSE;
571 	    break;
572 	case 'U':
573 	    literal = TRUE;
574 	    break;
575 	case 'V':
576 	    puts(curses_version());
577 	    cleanup(namelst);
578 	    ExitProgram(EXIT_SUCCESS);
579 	case 'c':
580 	    check_only = TRUE;
581 	    break;
582 	case 'e':
583 	    namelst = make_namelist(optarg);
584 	    break;
585 	case 'f':
586 	    formatted = TRUE;
587 	    break;
588 	case 'G':
589 	    numbers = 1;
590 	    break;
591 	case 'g':
592 	    numbers = -1;
593 	    break;
594 	case 'o':
595 	    outdir = optarg;
596 	    break;
597 	case 'r':
598 	    forceresolve = TRUE;
599 	    break;
600 	case 's':
601 	    showsummary = TRUE;
602 	    break;
603 	case 'v':
604 	    v_opt = 0;
605 	    break;
606 	case 'w':
607 	    width = 0;
608 	    break;
609 #if NCURSES_XNAMES
610 	case 't':
611 	    _nc_disable_period = FALSE;
612 	    suppress_untranslatable = TRUE;
613 	    break;
614 	case 'a':
615 	    _nc_disable_period = TRUE;
616 	    /* FALLTHRU */
617 	case 'x':
618 	    use_extended_names(TRUE);
619 	    break;
620 #endif
621 	default:
622 	    usage();
623 	}
624 	last_opt = this_opt;
625     }
626 
627     debug_level = (v_opt > 0) ? v_opt : (v_opt == 0);
628     set_trace_level(debug_level);
629 
630     if (_nc_tracing) {
631 	save_check_termtype = _nc_check_termtype2;
632 	_nc_check_termtype2 = check_termtype;
633     }
634 #if !HAVE_BIG_CORE
635     /*
636      * Aaargh! immedhook seriously hoses us!
637      *
638      * One problem with immedhook is it means we can't do -e.  Problem
639      * is that we can't guarantee that for each terminal listed, all the
640      * terminals it depends on will have been kept in core for reference
641      * resolution -- in fact it's certain the primitive types at the end
642      * of reference chains *won't* be in core unless they were explicitly
643      * in the select list themselves.
644      */
645     if (namelst && (!infodump && !capdump)) {
646 	(void) fprintf(stderr,
647 		       "Sorry, -e can't be used without -I or -C\n");
648 	cleanup(namelst);
649 	ExitProgram(EXIT_FAILURE);
650     }
651 #endif /* HAVE_BIG_CORE */
652 
653     if (optind < argc) {
654 	source_file = argv[optind++];
655 	if (optind < argc) {
656 	    fprintf(stderr,
657 		    "%s: Too many file names.  Usage:\n\t%s %s",
658 		    _nc_progname,
659 		    _nc_progname,
660 		    usage_string);
661 	    ExitProgram(EXIT_FAILURE);
662 	}
663     } else {
664 	if (infodump == TRUE) {
665 	    /* captoinfo's no-argument case */
666 	    source_file = "/etc/termcap";
667 	    if ((termcap = getenv("TERMCAP")) != 0
668 		&& (namelst = make_namelist(getenv("TERM"))) != 0) {
669 		strlcpy(my_tmpname, "/tmp/XXXXXXXXXX", sizeof my_tmpname);
670 		if (access(termcap, F_OK) == 0) {
671 		    /* file exists */
672 		    source_file = termcap;
673 		} else if ((tmp_fp = open_tempfile(my_tmpname)) != 0) {
674 		    source_file = my_tmpname;
675 		    fprintf(tmp_fp, "%s\n", termcap);
676 		    fclose(tmp_fp);
677 		    tmp_fp = open_input(source_file);
678 		    to_remove = source_file;
679 		} else {
680 		    failed("tmpnam");
681 		}
682 	    }
683 	} else {
684 	    /* tic */
685 	    fprintf(stderr,
686 		    "%s: File name needed.  Usage:\n\t%s %s",
687 		    _nc_progname,
688 		    _nc_progname,
689 		    usage_string);
690 	    cleanup(namelst);
691 	    ExitProgram(EXIT_FAILURE);
692 	}
693     }
694 
695     if (tmp_fp == 0)
696 	tmp_fp = open_input(source_file);
697 
698     if (infodump)
699 	dump_init(tversion,
700 		  smart_defaults
701 		  ? outform
702 		  : F_LITERAL,
703 		  sortmode, width, debug_level, formatted);
704     else if (capdump)
705 	dump_init(tversion,
706 		  outform,
707 		  sortmode, width, debug_level, FALSE);
708 
709     /* parse entries out of the source file */
710     _nc_set_source(source_file);
711 #if !HAVE_BIG_CORE
712     if (!(check_only || infodump || capdump))
713 	_nc_set_writedir(outdir);
714 #endif /* HAVE_BIG_CORE */
715     _nc_read_entry_source(tmp_fp, (char *) NULL,
716 			  !smart_defaults || literal, FALSE,
717 			  ((check_only || infodump || capdump)
718 			   ? NULLHOOK
719 			   : immedhook));
720 
721     /* do use resolution */
722     if (check_only || (!infodump && !capdump) || forceresolve) {
723 	if (!_nc_resolve_uses2(TRUE, literal) && !check_only) {
724 	    cleanup(namelst);
725 	    ExitProgram(EXIT_FAILURE);
726 	}
727     }
728 
729     /* length check */
730     if (check_only && (capdump || infodump)) {
731 	for_entry_list(qp) {
732 	    if (matches(namelst, qp->tterm.term_names)) {
733 		int len = fmt_entry(&qp->tterm, NULL, FALSE, TRUE, infodump, numbers);
734 
735 		if (len > (infodump ? MAX_TERMINFO_LENGTH : MAX_TERMCAP_LENGTH))
736 		    (void) fprintf(stderr,
737 				   "warning: resolved %s entry is %d bytes long\n",
738 				   _nc_first_name(qp->tterm.term_names),
739 				   len);
740 	    }
741 	}
742     }
743 
744     /* write or dump all entries */
745     if (!check_only) {
746 	if (!infodump && !capdump) {
747 	    _nc_set_writedir(outdir);
748 	    for_entry_list(qp) {
749 		if (matches(namelst, qp->tterm.term_names))
750 		    write_it(qp);
751 	    }
752 	} else {
753 	    /* this is in case infotocap() generates warnings */
754 	    _nc_curr_col = _nc_curr_line = -1;
755 
756 	    for_entry_list(qp) {
757 		if (matches(namelst, qp->tterm.term_names)) {
758 		    int j = qp->cend - qp->cstart;
759 		    int len = 0;
760 
761 		    /* this is in case infotocap() generates warnings */
762 		    _nc_set_type(_nc_first_name(qp->tterm.term_names));
763 
764 		    (void) fseek(tmp_fp, qp->cstart, SEEK_SET);
765 		    while (j-- > 0) {
766 			if (infodump)
767 			    (void) putchar(fgetc(tmp_fp));
768 			else
769 			    put_translate(fgetc(tmp_fp));
770 		    }
771 
772 		    dump_entry(&qp->tterm, suppress_untranslatable,
773 			       limited, numbers, NULL);
774 		    for (j = 0; j < (int) qp->nuses; j++)
775 			dump_uses(qp->uses[j].name, !capdump);
776 		    len = show_entry();
777 		    if (debug_level != 0 && !limited)
778 			printf("# length=%d\n", len);
779 		}
780 	    }
781 	    if (!namelst && _nc_tail) {
782 		int c, oldc = '\0';
783 		bool in_comment = FALSE;
784 		bool trailing_comment = FALSE;
785 
786 		(void) fseek(tmp_fp, _nc_tail->cend, SEEK_SET);
787 		while ((c = fgetc(tmp_fp)) != EOF) {
788 		    if (oldc == '\n') {
789 			if (c == '#') {
790 			    trailing_comment = TRUE;
791 			    in_comment = TRUE;
792 			} else {
793 			    in_comment = FALSE;
794 			}
795 		    }
796 		    if (trailing_comment
797 			&& (in_comment || (oldc == '\n' && c == '\n')))
798 			putchar(c);
799 		    oldc = c;
800 		}
801 	    }
802 	}
803     }
804 
805     /* Show the directory into which entries were written, and the total
806      * number of entries
807      */
808     if (showsummary
809 	&& (!(check_only || infodump || capdump))) {
810 	int total = _nc_tic_written();
811 	if (total != 0)
812 	    fprintf(log_fp, "%d entries written to %s\n",
813 		    total,
814 		    _nc_tic_dir((char *) 0));
815 	else
816 	    fprintf(log_fp, "No entries written\n");
817     }
818     cleanup(namelst);
819     ExitProgram(EXIT_SUCCESS);
820 }
821 
822 /*
823  * This bit of legerdemain turns all the terminfo variable names into
824  * references to locations in the arrays Booleans, Numbers, and Strings ---
825  * precisely what's needed (see comp_parse.c).
826  */
827 #undef CUR
828 #define CUR tp->
829 
830 /*
831  * Check if the alternate character-set capabilities are consistent.
832  */
833 static void
834 check_acs(TERMTYPE *tp)
835 {
836     if (VALID_STRING(acs_chars)) {
837 	const char *boxes = "lmkjtuvwqxn";
838 	char mapped[256];
839 	char missing[256];
840 	const char *p;
841 	char *q;
842 
843 	memset(mapped, 0, sizeof(mapped));
844 	for (p = acs_chars; *p != '\0'; p += 2) {
845 	    if (p[1] == '\0') {
846 		_nc_warning("acsc has odd number of characters");
847 		break;
848 	    }
849 	    mapped[UChar(p[0])] = p[1];
850 	}
851 
852 	if (mapped[UChar('I')] && !mapped[UChar('i')]) {
853 	    _nc_warning("acsc refers to 'I', which is probably an error");
854 	}
855 
856 	for (p = boxes, q = missing; *p != '\0'; ++p) {
857 	    if (!mapped[UChar(p[0])]) {
858 		*q++ = p[0];
859 	    }
860 	}
861 	*q = '\0';
862 
863 	assert(strlen(missing) <= strlen(boxes));
864 	if (*missing != '\0' && strcmp(missing, boxes)) {
865 	    _nc_warning("acsc is missing some line-drawing mapping: %s", missing);
866 	}
867     }
868 }
869 
870 /*
871  * Check if the color capabilities are consistent
872  */
873 static void
874 check_colors(TERMTYPE *tp)
875 {
876     if ((max_colors > 0) != (max_pairs > 0)
877 	|| ((max_colors > max_pairs) && (initialize_pair == 0)))
878 	_nc_warning("inconsistent values for max_colors (%d) and max_pairs (%d)",
879 		    max_colors, max_pairs);
880 
881     PAIRED(set_foreground, set_background);
882     PAIRED(set_a_foreground, set_a_background);
883     PAIRED(set_color_pair, initialize_pair);
884 
885     if (VALID_STRING(set_foreground)
886 	&& VALID_STRING(set_a_foreground)
887 	&& !_nc_capcmp(set_foreground, set_a_foreground))
888 	_nc_warning("expected setf/setaf to be different");
889 
890     if (VALID_STRING(set_background)
891 	&& VALID_STRING(set_a_background)
892 	&& !_nc_capcmp(set_background, set_a_background))
893 	_nc_warning("expected setb/setab to be different");
894 
895     /* see: has_colors() */
896     if (VALID_NUMERIC(max_colors) && VALID_NUMERIC(max_pairs)
897 	&& (((set_foreground != NULL)
898 	     && (set_background != NULL))
899 	    || ((set_a_foreground != NULL)
900 		&& (set_a_background != NULL))
901 	    || set_color_pair)) {
902 	if (!VALID_STRING(orig_pair) && !VALID_STRING(orig_colors))
903 	    _nc_warning("expected either op/oc string for resetting colors");
904     }
905 }
906 
907 static char
908 keypad_final(const char *string)
909 {
910     char result = '\0';
911 
912     if (VALID_STRING(string)
913 	&& *string++ == '\033'
914 	&& *string++ == 'O'
915 	&& strlen(string) == 1) {
916 	result = *string;
917     }
918 
919     return result;
920 }
921 
922 static int
923 keypad_index(const char *string)
924 {
925     char *test;
926     const char *list = "PQRSwxymtuvlqrsPpn";	/* app-keypad except "Enter" */
927     int ch;
928     int result = -1;
929 
930     if ((ch = keypad_final(string)) != '\0') {
931 	test = strchr(list, ch);
932 	if (test != 0)
933 	    result = (test - list);
934     }
935     return result;
936 }
937 
938 #define MAX_KP 5
939 /*
940  * Do a quick sanity-check for vt100-style keypads to see if the 5-key keypad
941  * is mapped inconsistently.
942  */
943 static void
944 check_keypad(TERMTYPE *tp)
945 {
946     char show[80];
947 
948     if (VALID_STRING(key_a1) &&
949 	VALID_STRING(key_a3) &&
950 	VALID_STRING(key_b2) &&
951 	VALID_STRING(key_c1) &&
952 	VALID_STRING(key_c3)) {
953 	char final[MAX_KP + 1];
954 	int list[MAX_KP];
955 	int increase = 0;
956 	int j, k, kk;
957 	int last;
958 	int test;
959 
960 	final[0] = keypad_final(key_a1);
961 	final[1] = keypad_final(key_a3);
962 	final[2] = keypad_final(key_b2);
963 	final[3] = keypad_final(key_c1);
964 	final[4] = keypad_final(key_c3);
965 	final[5] = '\0';
966 
967 	/* special case: legacy coding using 1,2,3,0,. on the bottom */
968 	assert(strlen(final) <= MAX_KP);
969 	if (!strcmp(final, "qsrpn"))
970 	    return;
971 
972 	list[0] = keypad_index(key_a1);
973 	list[1] = keypad_index(key_a3);
974 	list[2] = keypad_index(key_b2);
975 	list[3] = keypad_index(key_c1);
976 	list[4] = keypad_index(key_c3);
977 
978 	/* check that they're all vt100 keys */
979 	for (j = 0; j < MAX_KP; ++j) {
980 	    if (list[j] < 0) {
981 		return;
982 	    }
983 	}
984 
985 	/* check if they're all in increasing order */
986 	for (j = 1; j < MAX_KP; ++j) {
987 	    if (list[j] > list[j - 1]) {
988 		++increase;
989 	    }
990 	}
991 	if (increase != (MAX_KP - 1)) {
992 	    show[0] = '\0';
993 
994 	    for (j = 0, last = -1; j < MAX_KP; ++j) {
995 		for (k = 0, kk = -1, test = 100; k < 5; ++k) {
996 		    if (list[k] > last &&
997 			list[k] < test) {
998 			test = list[k];
999 			kk = k;
1000 		    }
1001 		}
1002 		last = test;
1003 		assert(strlen(show) < (MAX_KP * 4));
1004 		switch (kk) {
1005 		case 0:
1006 		    strlcat(show, " ka1", sizeof(show));
1007 		    break;
1008 		case 1:
1009 		    strlcat(show, " ka3", sizeof(show));
1010 		    break;
1011 		case 2:
1012 		    strlcat(show, " kb2", sizeof(show));
1013 		    break;
1014 		case 3:
1015 		    strlcat(show, " kc1", sizeof(show));
1016 		    break;
1017 		case 4:
1018 		    strlcat(show, " kc3", sizeof(show));
1019 		    break;
1020 		}
1021 	    }
1022 
1023 	    _nc_warning("vt100 keypad order inconsistent: %s", show);
1024 	}
1025 
1026     } else if (VALID_STRING(key_a1) ||
1027 	       VALID_STRING(key_a3) ||
1028 	       VALID_STRING(key_b2) ||
1029 	       VALID_STRING(key_c1) ||
1030 	       VALID_STRING(key_c3)) {
1031 	show[0] = '\0';
1032 	if (keypad_index(key_a1) >= 0)
1033 	    strlcat(show, " ka1", sizeof(show));
1034 	if (keypad_index(key_a3) >= 0)
1035 	    strlcat(show, " ka3", sizeof(show));
1036 	if (keypad_index(key_b2) >= 0)
1037 	    strlcat(show, " kb2", sizeof(show));
1038 	if (keypad_index(key_c1) >= 0)
1039 	    strlcat(show, " kc1", sizeof(show));
1040 	if (keypad_index(key_c3) >= 0)
1041 	    strlcat(show, " kc3", sizeof(show));
1042 	if (*show != '\0')
1043 	    _nc_warning("vt100 keypad map incomplete:%s", show);
1044     }
1045 }
1046 
1047 /*
1048  * Returns the expected number of parameters for the given capability.
1049  */
1050 static int
1051 expected_params(const char *name)
1052 {
1053     /* *INDENT-OFF* */
1054     static const struct {
1055 	const char *name;
1056 	int count;
1057     } table[] = {
1058 	{ "S0",			1 },	/* 'screen' extension */
1059 	{ "birep",		2 },
1060 	{ "chr",		1 },
1061 	{ "colornm",		1 },
1062 	{ "cpi",		1 },
1063 	{ "csnm",		1 },
1064 	{ "csr",		2 },
1065 	{ "cub",		1 },
1066 	{ "cud",		1 },
1067 	{ "cuf",		1 },
1068 	{ "cup",		2 },
1069 	{ "cuu",		1 },
1070 	{ "cvr",		1 },
1071 	{ "cwin",		5 },
1072 	{ "dch",		1 },
1073 	{ "defc",		3 },
1074 	{ "dial",		1 },
1075 	{ "dispc",		1 },
1076 	{ "dl",			1 },
1077 	{ "ech",		1 },
1078 	{ "getm",		1 },
1079 	{ "hpa",		1 },
1080 	{ "ich",		1 },
1081 	{ "il",			1 },
1082 	{ "indn",		1 },
1083 	{ "initc",		4 },
1084 	{ "initp",		7 },
1085 	{ "lpi",		1 },
1086 	{ "mc5p",		1 },
1087 	{ "mrcup",		2 },
1088 	{ "mvpa",		1 },
1089 	{ "pfkey",		2 },
1090 	{ "pfloc",		2 },
1091 	{ "pfx",		2 },
1092 	{ "pfxl",		3 },
1093 	{ "pln",		2 },
1094 	{ "qdial",		1 },
1095 	{ "rcsd",		1 },
1096 	{ "rep",		2 },
1097 	{ "rin",		1 },
1098 	{ "sclk",		3 },
1099 	{ "scp",		1 },
1100 	{ "scs",		1 },
1101 	{ "scsd",		2 },
1102 	{ "setab",		1 },
1103 	{ "setaf",		1 },
1104 	{ "setb",		1 },
1105 	{ "setcolor",		1 },
1106 	{ "setf",		1 },
1107 	{ "sgr",		9 },
1108 	{ "sgr1",		6 },
1109 	{ "slength",		1 },
1110 	{ "slines",		1 },
1111 	{ "smgbp",		1 },	/* 2 if smgtp is not given */
1112 	{ "smglp",		1 },
1113 	{ "smglr",		2 },
1114 	{ "smgrp",		1 },
1115 	{ "smgtb",		2 },
1116 	{ "smgtp",		1 },
1117 	{ "tsl",		1 },
1118 	{ "u6",			-1 },
1119 	{ "vpa",		1 },
1120 	{ "wind",		4 },
1121 	{ "wingo",		1 },
1122     };
1123     /* *INDENT-ON* */
1124 
1125     unsigned n;
1126     int result = 0;		/* function-keys, etc., use none */
1127 
1128     for (n = 0; n < SIZEOF(table); n++) {
1129 	if (!strcmp(name, table[n].name)) {
1130 	    result = table[n].count;
1131 	    break;
1132 	}
1133     }
1134 
1135     return result;
1136 }
1137 
1138 /*
1139  * Make a quick sanity check for the parameters which are used in the given
1140  * strings.  If there are no "%p" tokens, then there should be no other "%"
1141  * markers.
1142  */
1143 static void
1144 check_params(TERMTYPE *tp, const char *name, char *value)
1145 {
1146     int expected = expected_params(name);
1147     int actual = 0;
1148     int n;
1149     bool params[10];
1150     char *s = value;
1151 
1152 #ifdef set_top_margin_parm
1153     if (!strcmp(name, "smgbp")
1154 	&& set_top_margin_parm == 0)
1155 	expected = 2;
1156 #endif
1157 
1158     for (n = 0; n < 10; n++)
1159 	params[n] = FALSE;
1160 
1161     while (*s != 0) {
1162 	if (*s == '%') {
1163 	    if (*++s == '\0') {
1164 		_nc_warning("expected character after %% in %s", name);
1165 		break;
1166 	    } else if (*s == 'p') {
1167 		if (*++s == '\0' || !isdigit(UChar(*s))) {
1168 		    _nc_warning("expected digit after %%p in %s", name);
1169 		    return;
1170 		} else {
1171 		    n = (*s - '0');
1172 		    if (n > actual)
1173 			actual = n;
1174 		    params[n] = TRUE;
1175 		}
1176 	    }
1177 	}
1178 	s++;
1179     }
1180 
1181     if (params[0]) {
1182 	_nc_warning("%s refers to parameter 0 (%%p0), which is not allowed", name);
1183     }
1184     if (value == set_attributes || expected < 0) {
1185 	;
1186     } else if (expected != actual) {
1187 	_nc_warning("%s uses %d parameters, expected %d", name,
1188 		    actual, expected);
1189 	for (n = 1; n < actual; n++) {
1190 	    if (!params[n])
1191 		_nc_warning("%s omits parameter %d", name, n);
1192 	}
1193     }
1194 }
1195 
1196 static char *
1197 skip_delay(char *s)
1198 {
1199     while (*s == '/' || isdigit(UChar(*s)))
1200 	++s;
1201     return s;
1202 }
1203 
1204 /*
1205  * Skip a delay altogether, e.g., when comparing a simple string to sgr,
1206  * the latter may have a worst-case delay on the end.
1207  */
1208 static char *
1209 ignore_delays(char *s)
1210 {
1211     int delaying = 0;
1212 
1213     do {
1214 	switch (*s) {
1215 	case '$':
1216 	    if (delaying == 0)
1217 		delaying = 1;
1218 	    break;
1219 	case '<':
1220 	    if (delaying == 1)
1221 		delaying = 2;
1222 	    break;
1223 	case '\0':
1224 	    delaying = 0;
1225 	    break;
1226 	default:
1227 	    if (delaying) {
1228 		s = skip_delay(s);
1229 		if (*s == '>')
1230 		    ++s;
1231 		delaying = 0;
1232 	    }
1233 	    break;
1234 	}
1235 	if (delaying)
1236 	    ++s;
1237     } while (delaying);
1238     return s;
1239 }
1240 
1241 /*
1242  * An sgr string may contain several settings other than the one we're
1243  * interested in, essentially sgr0 + rmacs + whatever.  As long as the
1244  * "whatever" is contained in the sgr string, that is close enough for our
1245  * sanity check.
1246  */
1247 static bool
1248 similar_sgr(int num, char *a, char *b)
1249 {
1250     static const char *names[] =
1251     {
1252 	"none"
1253 	,"standout"
1254 	,"underline"
1255 	,"reverse"
1256 	,"blink"
1257 	,"dim"
1258 	,"bold"
1259 	,"invis"
1260 	,"protect"
1261 	,"altcharset"
1262     };
1263     char *base_a = a;
1264     char *base_b = b;
1265     int delaying = 0;
1266 
1267     while (*b != 0) {
1268 	while (*a != *b) {
1269 	    if (*a == 0) {
1270 		if (b[0] == '$'
1271 		    && b[1] == '<') {
1272 		    _nc_warning("Did not find delay %s", _nc_visbuf(b));
1273 		} else {
1274 		    _nc_warning("checking sgr(%s) %s\n\tcompare to %s\n\tunmatched %s",
1275 				names[num], _nc_visbuf2(1, base_a),
1276 				_nc_visbuf2(2, base_b),
1277 				_nc_visbuf2(3, b));
1278 		}
1279 		return FALSE;
1280 	    } else if (delaying) {
1281 		a = skip_delay(a);
1282 		b = skip_delay(b);
1283 	    } else {
1284 		a++;
1285 	    }
1286 	}
1287 	switch (*a) {
1288 	case '$':
1289 	    if (delaying == 0)
1290 		delaying = 1;
1291 	    break;
1292 	case '<':
1293 	    if (delaying == 1)
1294 		delaying = 2;
1295 	    break;
1296 	default:
1297 	    delaying = 0;
1298 	    break;
1299 	}
1300 	a++;
1301 	b++;
1302     }
1303     /* ignore delays on the end of the string */
1304     a = ignore_delays(a);
1305     return ((num != 0) || (*a == 0));
1306 }
1307 
1308 static char *
1309 check_sgr(TERMTYPE *tp, char *zero, int num, char *cap, const char *name)
1310 {
1311     char *test;
1312 
1313     _nc_tparm_err = 0;
1314     test = TPARM_9(set_attributes,
1315 		   num == 1,
1316 		   num == 2,
1317 		   num == 3,
1318 		   num == 4,
1319 		   num == 5,
1320 		   num == 6,
1321 		   num == 7,
1322 		   num == 8,
1323 		   num == 9);
1324     if (test != 0) {
1325 	if (PRESENT(cap)) {
1326 	    if (!similar_sgr(num, test, cap)) {
1327 		_nc_warning("%s differs from sgr(%d)\n\t%s=%s\n\tsgr(%d)=%s",
1328 			    name, num,
1329 			    name, _nc_visbuf2(1, cap),
1330 			    num, _nc_visbuf2(2, test));
1331 	    }
1332 	} else if (_nc_capcmp(test, zero)) {
1333 	    _nc_warning("sgr(%d) present, but not %s", num, name);
1334 	}
1335     } else if (PRESENT(cap)) {
1336 	_nc_warning("sgr(%d) missing, but %s present", num, name);
1337     }
1338     if (_nc_tparm_err)
1339 	_nc_warning("stack error in sgr(%d) string", num);
1340     return test;
1341 }
1342 
1343 #define CHECK_SGR(num,name) check_sgr(tp, zero, num, name, #name)
1344 
1345 #ifdef TRACE
1346 /*
1347  * If tic is compiled with TRACE, we'll be able to see the output from the
1348  * DEBUG() macro.  But since it doesn't use traceon(), it always goes to
1349  * the standard error.  Use this function to make it simpler to follow the
1350  * resulting debug traces.
1351  */
1352 static void
1353 show_where(unsigned level)
1354 {
1355     if (_nc_tracing >= DEBUG_LEVEL(level)) {
1356 	char my_name[256];
1357 	_nc_get_type(my_name);
1358 	fprintf(stderr, "\"%s\", line %d, '%s' ",
1359 		_nc_get_source(),
1360 		_nc_curr_line, my_name);
1361     }
1362 }
1363 
1364 #else
1365 #define show_where(level)	/* nothing */
1366 #endif
1367 
1368 /* other sanity-checks (things that we don't want in the normal
1369  * logic that reads a terminfo entry)
1370  */
1371 static void
1372 check_termtype(TERMTYPE *tp, bool literal)
1373 {
1374     bool conflict = FALSE;
1375     unsigned j, k;
1376     char fkeys[STRCOUNT];
1377 
1378     /*
1379      * A terminal entry may contain more than one keycode assigned to
1380      * a given string (e.g., KEY_END and KEY_LL).  But curses will only
1381      * return one (the last one assigned).
1382      */
1383     if (!(_nc_syntax == SYN_TERMCAP && capdump)) {
1384 	memset(fkeys, 0, sizeof(fkeys));
1385 	for (j = 0; _nc_tinfo_fkeys[j].code; j++) {
1386 	    char *a = tp->Strings[_nc_tinfo_fkeys[j].offset];
1387 	    bool first = TRUE;
1388 	    if (!VALID_STRING(a))
1389 		continue;
1390 	    for (k = j + 1; _nc_tinfo_fkeys[k].code; k++) {
1391 		char *b = tp->Strings[_nc_tinfo_fkeys[k].offset];
1392 		if (!VALID_STRING(b)
1393 		    || fkeys[k])
1394 		    continue;
1395 		if (!_nc_capcmp(a, b)) {
1396 		    fkeys[j] = 1;
1397 		    fkeys[k] = 1;
1398 		    if (first) {
1399 			if (!conflict) {
1400 			    _nc_warning("Conflicting key definitions (using the last)");
1401 			    conflict = TRUE;
1402 			}
1403 			fprintf(stderr, "... %s is the same as %s",
1404 				keyname((int) _nc_tinfo_fkeys[j].code),
1405 				keyname((int) _nc_tinfo_fkeys[k].code));
1406 			first = FALSE;
1407 		    } else {
1408 			fprintf(stderr, ", %s",
1409 				keyname((int) _nc_tinfo_fkeys[k].code));
1410 		    }
1411 		}
1412 	    }
1413 	    if (!first)
1414 		fprintf(stderr, "\n");
1415 	}
1416     }
1417 
1418     for (j = 0; j < NUM_STRINGS(tp); j++) {
1419 	char *a = tp->Strings[j];
1420 	if (VALID_STRING(a))
1421 	    check_params(tp, ExtStrname(tp, j, strnames), a);
1422     }
1423 
1424     check_acs(tp);
1425     check_colors(tp);
1426     check_keypad(tp);
1427 
1428     /*
1429      * These may be mismatched because the terminal description relies on
1430      * restoring the cursor visibility by resetting it.
1431      */
1432     ANDMISSING(cursor_invisible, cursor_normal);
1433     ANDMISSING(cursor_visible, cursor_normal);
1434 
1435     if (PRESENT(cursor_visible) && PRESENT(cursor_normal)
1436 	&& !_nc_capcmp(cursor_visible, cursor_normal))
1437 	_nc_warning("cursor_visible is same as cursor_normal");
1438 
1439     /*
1440      * From XSI & O'Reilly, we gather that sc/rc are required if csr is
1441      * given, because the cursor position after the scrolling operation is
1442      * performed is undefined.
1443      */
1444     ANDMISSING(change_scroll_region, save_cursor);
1445     ANDMISSING(change_scroll_region, restore_cursor);
1446 
1447     if (PRESENT(set_attributes)) {
1448 	char *zero = 0;
1449 
1450 	_nc_tparm_err = 0;
1451 	if (PRESENT(exit_attribute_mode)) {
1452 	    zero = strdup(CHECK_SGR(0, exit_attribute_mode));
1453 	} else {
1454 	    zero = strdup(TPARM_9(set_attributes, 0, 0, 0, 0, 0, 0, 0, 0, 0));
1455 	}
1456 	if (_nc_tparm_err)
1457 	    _nc_warning("stack error in sgr(0) string");
1458 
1459 	if (zero != 0) {
1460 	    CHECK_SGR(1, enter_standout_mode);
1461 	    CHECK_SGR(2, enter_underline_mode);
1462 	    CHECK_SGR(3, enter_reverse_mode);
1463 	    CHECK_SGR(4, enter_blink_mode);
1464 	    CHECK_SGR(5, enter_dim_mode);
1465 	    CHECK_SGR(6, enter_bold_mode);
1466 	    CHECK_SGR(7, enter_secure_mode);
1467 	    CHECK_SGR(8, enter_protected_mode);
1468 	    CHECK_SGR(9, enter_alt_charset_mode);
1469 	    free(zero);
1470 	} else {
1471 	    _nc_warning("sgr(0) did not return a value");
1472 	}
1473     } else if (PRESENT(exit_attribute_mode) &&
1474 	       set_attributes != CANCELLED_STRING) {
1475 	if (_nc_syntax == SYN_TERMINFO)
1476 	    _nc_warning("missing sgr string");
1477     }
1478 
1479     if (PRESENT(exit_attribute_mode)) {
1480 	char *check_sgr0 = _nc_trim_sgr0(tp);
1481 
1482 	if (check_sgr0 == 0 || *check_sgr0 == '\0') {
1483 	    _nc_warning("trimmed sgr0 is empty");
1484 	} else {
1485 	    show_where(2);
1486 	    if (check_sgr0 != exit_attribute_mode) {
1487 		DEBUG(2,
1488 		      ("will trim sgr0\n\toriginal sgr0=%s\n\ttrimmed  sgr0=%s",
1489 		       _nc_visbuf2(1, exit_attribute_mode),
1490 		       _nc_visbuf2(2, check_sgr0)));
1491 		free(check_sgr0);
1492 	    } else {
1493 		DEBUG(2,
1494 		      ("will not trim sgr0\n\toriginal sgr0=%s",
1495 		       _nc_visbuf(exit_attribute_mode)));
1496 	    }
1497 	}
1498     }
1499 #ifdef TRACE
1500     show_where(2);
1501     if (!auto_right_margin) {
1502 	DEBUG(2,
1503 	      ("can write to lower-right directly"));
1504     } else if (PRESENT(enter_am_mode) && PRESENT(exit_am_mode)) {
1505 	DEBUG(2,
1506 	      ("can write to lower-right by suppressing automargin"));
1507     } else if ((PRESENT(enter_insert_mode) && PRESENT(exit_insert_mode))
1508 	       || PRESENT(insert_character) || PRESENT(parm_ich)) {
1509 	DEBUG(2,
1510 	      ("can write to lower-right by using inserts"));
1511     } else {
1512 	DEBUG(2,
1513 	      ("cannot write to lower-right"));
1514     }
1515 #endif
1516 
1517     /*
1518      * Some standard applications (e.g., vi) and some non-curses
1519      * applications (e.g., jove) get confused if we have both ich1 and
1520      * smir/rmir.  Let's be nice and warn about that, too, even though
1521      * ncurses handles it.
1522      */
1523     if ((PRESENT(enter_insert_mode) || PRESENT(exit_insert_mode))
1524 	&& PRESENT(parm_ich)) {
1525 	_nc_warning("non-curses applications may be confused by ich1 with smir/rmir");
1526     }
1527 
1528     /*
1529      * Finally, do the non-verbose checks
1530      */
1531     if (save_check_termtype != 0)
1532 	save_check_termtype(tp, literal);
1533 }
1534