xref: /openbsd-src/usr.bin/tic/tic.c (revision 898184e3e61f9129feb5978fad5a8c6865f00b92)
1 /*	$OpenBSD: tic.c,v 1.30 2010/01/12 23:22:14 nicm 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.30 2010/01/12 23:22:14 nicm 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) < 0
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     log_fp = stderr;
503 
504     _nc_progname = _nc_rootname(argv[0]);
505 
506     if ((infodump = (strcmp(_nc_progname, PROG_CAPTOINFO) == 0)) != FALSE) {
507 	outform = F_TERMINFO;
508 	sortmode = S_TERMINFO;
509     }
510     if ((capdump = (strcmp(_nc_progname, PROG_INFOTOCAP) == 0)) != FALSE) {
511 	outform = F_TERMCAP;
512 	sortmode = S_TERMCAP;
513     }
514 #if NCURSES_XNAMES
515     use_extended_names(FALSE);
516 #endif
517 
518     /*
519      * Processing arguments is a little complicated, since someone made a
520      * design decision to allow the numeric values for -w, -v options to
521      * be optional.
522      */
523     while ((this_opt = getopt(argc, argv,
524 			      "0123456789CILNR:TUVace:fGgo:rstvwx")) != -1) {
525 	if (isdigit(this_opt)) {
526 	    switch (last_opt) {
527 	    case 'v':
528 		v_opt = (v_opt * 10) + (this_opt - '0');
529 		break;
530 	    case 'w':
531 		width = (width * 10) + (this_opt - '0');
532 		break;
533 	    default:
534 		if (this_opt != '1')
535 		    usage();
536 		last_opt = this_opt;
537 		width = 0;
538 	    }
539 	    continue;
540 	}
541 	switch (this_opt) {
542 	case 'C':
543 	    capdump = TRUE;
544 	    outform = F_TERMCAP;
545 	    sortmode = S_TERMCAP;
546 	    break;
547 	case 'I':
548 	    infodump = TRUE;
549 	    outform = F_TERMINFO;
550 	    sortmode = S_TERMINFO;
551 	    break;
552 	case 'L':
553 	    infodump = TRUE;
554 	    outform = F_VARIABLE;
555 	    sortmode = S_VARIABLE;
556 	    break;
557 	case 'N':
558 	    smart_defaults = FALSE;
559 	    literal = TRUE;
560 	    break;
561 	case 'R':
562 	    tversion = optarg;
563 	    break;
564 	case 'T':
565 	    limited = FALSE;
566 	    break;
567 	case 'U':
568 	    literal = TRUE;
569 	    break;
570 	case 'V':
571 	    puts(curses_version());
572 	    cleanup(namelst);
573 	    ExitProgram(EXIT_SUCCESS);
574 	case 'c':
575 	    check_only = TRUE;
576 	    break;
577 	case 'e':
578 	    namelst = make_namelist(optarg);
579 	    break;
580 	case 'f':
581 	    formatted = TRUE;
582 	    break;
583 	case 'G':
584 	    numbers = 1;
585 	    break;
586 	case 'g':
587 	    numbers = -1;
588 	    break;
589 	case 'o':
590 	    outdir = optarg;
591 	    break;
592 	case 'r':
593 	    forceresolve = TRUE;
594 	    break;
595 	case 's':
596 	    showsummary = TRUE;
597 	    break;
598 	case 'v':
599 	    v_opt = 0;
600 	    break;
601 	case 'w':
602 	    width = 0;
603 	    break;
604 #if NCURSES_XNAMES
605 	case 't':
606 	    _nc_disable_period = FALSE;
607 	    suppress_untranslatable = TRUE;
608 	    break;
609 	case 'a':
610 	    _nc_disable_period = TRUE;
611 	    /* FALLTHRU */
612 	case 'x':
613 	    use_extended_names(TRUE);
614 	    break;
615 #endif
616 	default:
617 	    usage();
618 	}
619 	last_opt = this_opt;
620     }
621 
622     debug_level = (v_opt > 0) ? v_opt : (v_opt == 0);
623     set_trace_level(debug_level);
624 
625     if (_nc_tracing) {
626 	save_check_termtype = _nc_check_termtype2;
627 	_nc_check_termtype2 = check_termtype;
628     }
629 #if !HAVE_BIG_CORE
630     /*
631      * Aaargh! immedhook seriously hoses us!
632      *
633      * One problem with immedhook is it means we can't do -e.  Problem
634      * is that we can't guarantee that for each terminal listed, all the
635      * terminals it depends on will have been kept in core for reference
636      * resolution -- in fact it's certain the primitive types at the end
637      * of reference chains *won't* be in core unless they were explicitly
638      * in the select list themselves.
639      */
640     if (namelst && (!infodump && !capdump)) {
641 	(void) fprintf(stderr,
642 		       "Sorry, -e can't be used without -I or -C\n");
643 	cleanup(namelst);
644 	ExitProgram(EXIT_FAILURE);
645     }
646 #endif /* HAVE_BIG_CORE */
647 
648     if (optind < argc) {
649 	source_file = argv[optind++];
650 	if (optind < argc) {
651 	    fprintf(stderr,
652 		    "%s: Too many file names.  Usage:\n\t%s %s",
653 		    _nc_progname,
654 		    _nc_progname,
655 		    usage_string);
656 	    ExitProgram(EXIT_FAILURE);
657 	}
658     } else {
659 	if (infodump == TRUE) {
660 	    /* captoinfo's no-argument case */
661 	    source_file = "/etc/termcap";
662 	    if ((termcap = getenv("TERMCAP")) != 0
663 		&& (namelst = make_namelist(getenv("TERM"))) != 0) {
664 		strlcpy(my_tmpname, "/tmp/XXXXXXXXXX", sizeof my_tmpname);
665 		if (access(termcap, F_OK) == 0) {
666 		    /* file exists */
667 		    source_file = termcap;
668 		} else if ((tmp_fp = open_tempfile(my_tmpname)) != 0) {
669 		    source_file = my_tmpname;
670 		    fprintf(tmp_fp, "%s\n", termcap);
671 		    fclose(tmp_fp);
672 		    tmp_fp = open_input(source_file);
673 		    to_remove = source_file;
674 		} else {
675 		    failed("tmpnam");
676 		}
677 	    }
678 	} else {
679 	    /* tic */
680 	    fprintf(stderr,
681 		    "%s: File name needed.  Usage:\n\t%s %s",
682 		    _nc_progname,
683 		    _nc_progname,
684 		    usage_string);
685 	    cleanup(namelst);
686 	    ExitProgram(EXIT_FAILURE);
687 	}
688     }
689 
690     if (tmp_fp == 0)
691 	tmp_fp = open_input(source_file);
692 
693     if (infodump)
694 	dump_init(tversion,
695 		  smart_defaults
696 		  ? outform
697 		  : F_LITERAL,
698 		  sortmode, width, debug_level, formatted);
699     else if (capdump)
700 	dump_init(tversion,
701 		  outform,
702 		  sortmode, width, debug_level, FALSE);
703 
704     /* parse entries out of the source file */
705     _nc_set_source(source_file);
706 #if !HAVE_BIG_CORE
707     if (!(check_only || infodump || capdump))
708 	_nc_set_writedir(outdir);
709 #endif /* HAVE_BIG_CORE */
710     _nc_read_entry_source(tmp_fp, (char *) NULL,
711 			  !smart_defaults || literal, FALSE,
712 			  ((check_only || infodump || capdump)
713 			   ? NULLHOOK
714 			   : immedhook));
715 
716     /* do use resolution */
717     if (check_only || (!infodump && !capdump) || forceresolve) {
718 	if (!_nc_resolve_uses2(TRUE, literal) && !check_only) {
719 	    cleanup(namelst);
720 	    ExitProgram(EXIT_FAILURE);
721 	}
722     }
723 
724     /* length check */
725     if (check_only && (capdump || infodump)) {
726 	for_entry_list(qp) {
727 	    if (matches(namelst, qp->tterm.term_names)) {
728 		int len = fmt_entry(&qp->tterm, NULL, FALSE, TRUE, infodump, numbers);
729 
730 		if (len > (infodump ? MAX_TERMINFO_LENGTH : MAX_TERMCAP_LENGTH))
731 		    (void) fprintf(stderr,
732 				   "warning: resolved %s entry is %d bytes long\n",
733 				   _nc_first_name(qp->tterm.term_names),
734 				   len);
735 	    }
736 	}
737     }
738 
739     /* write or dump all entries */
740     if (!check_only) {
741 	if (!infodump && !capdump) {
742 	    _nc_set_writedir(outdir);
743 	    for_entry_list(qp) {
744 		if (matches(namelst, qp->tterm.term_names))
745 		    write_it(qp);
746 	    }
747 	} else {
748 	    /* this is in case infotocap() generates warnings */
749 	    _nc_curr_col = _nc_curr_line = -1;
750 
751 	    for_entry_list(qp) {
752 		if (matches(namelst, qp->tterm.term_names)) {
753 		    int j = qp->cend - qp->cstart;
754 		    int len = 0;
755 
756 		    /* this is in case infotocap() generates warnings */
757 		    _nc_set_type(_nc_first_name(qp->tterm.term_names));
758 
759 		    (void) fseek(tmp_fp, qp->cstart, SEEK_SET);
760 		    while (j-- > 0) {
761 			if (infodump)
762 			    (void) putchar(fgetc(tmp_fp));
763 			else
764 			    put_translate(fgetc(tmp_fp));
765 		    }
766 
767 		    dump_entry(&qp->tterm, suppress_untranslatable,
768 			       limited, numbers, NULL);
769 		    for (j = 0; j < (int) qp->nuses; j++)
770 			dump_uses(qp->uses[j].name, !capdump);
771 		    len = show_entry();
772 		    if (debug_level != 0 && !limited)
773 			printf("# length=%d\n", len);
774 		}
775 	    }
776 	    if (!namelst && _nc_tail) {
777 		int c, oldc = '\0';
778 		bool in_comment = FALSE;
779 		bool trailing_comment = FALSE;
780 
781 		(void) fseek(tmp_fp, _nc_tail->cend, SEEK_SET);
782 		while ((c = fgetc(tmp_fp)) != EOF) {
783 		    if (oldc == '\n') {
784 			if (c == '#') {
785 			    trailing_comment = TRUE;
786 			    in_comment = TRUE;
787 			} else {
788 			    in_comment = FALSE;
789 			}
790 		    }
791 		    if (trailing_comment
792 			&& (in_comment || (oldc == '\n' && c == '\n')))
793 			putchar(c);
794 		    oldc = c;
795 		}
796 	    }
797 	}
798     }
799 
800     /* Show the directory into which entries were written, and the total
801      * number of entries
802      */
803     if (showsummary
804 	&& (!(check_only || infodump || capdump))) {
805 	int total = _nc_tic_written();
806 	if (total != 0)
807 	    fprintf(log_fp, "%d entries written to %s\n",
808 		    total,
809 		    _nc_tic_dir((char *) 0));
810 	else
811 	    fprintf(log_fp, "No entries written\n");
812     }
813     cleanup(namelst);
814     ExitProgram(EXIT_SUCCESS);
815 }
816 
817 /*
818  * This bit of legerdemain turns all the terminfo variable names into
819  * references to locations in the arrays Booleans, Numbers, and Strings ---
820  * precisely what's needed (see comp_parse.c).
821  */
822 #undef CUR
823 #define CUR tp->
824 
825 /*
826  * Check if the alternate character-set capabilities are consistent.
827  */
828 static void
829 check_acs(TERMTYPE *tp)
830 {
831     if (VALID_STRING(acs_chars)) {
832 	const char *boxes = "lmkjtuvwqxn";
833 	char mapped[256];
834 	char missing[256];
835 	const char *p;
836 	char *q;
837 
838 	memset(mapped, 0, sizeof(mapped));
839 	for (p = acs_chars; *p != '\0'; p += 2) {
840 	    if (p[1] == '\0') {
841 		_nc_warning("acsc has odd number of characters");
842 		break;
843 	    }
844 	    mapped[UChar(p[0])] = p[1];
845 	}
846 
847 	if (mapped[UChar('I')] && !mapped[UChar('i')]) {
848 	    _nc_warning("acsc refers to 'I', which is probably an error");
849 	}
850 
851 	for (p = boxes, q = missing; *p != '\0'; ++p) {
852 	    if (!mapped[UChar(p[0])]) {
853 		*q++ = p[0];
854 	    }
855 	}
856 	*q = '\0';
857 
858 	assert(strlen(missing) <= strlen(boxes));
859 	if (*missing != '\0' && strcmp(missing, boxes)) {
860 	    _nc_warning("acsc is missing some line-drawing mapping: %s", missing);
861 	}
862     }
863 }
864 
865 /*
866  * Check if the color capabilities are consistent
867  */
868 static void
869 check_colors(TERMTYPE *tp)
870 {
871     if ((max_colors > 0) != (max_pairs > 0)
872 	|| ((max_colors > max_pairs) && (initialize_pair == 0)))
873 	_nc_warning("inconsistent values for max_colors (%d) and max_pairs (%d)",
874 		    max_colors, max_pairs);
875 
876     PAIRED(set_foreground, set_background);
877     PAIRED(set_a_foreground, set_a_background);
878     PAIRED(set_color_pair, initialize_pair);
879 
880     if (VALID_STRING(set_foreground)
881 	&& VALID_STRING(set_a_foreground)
882 	&& !_nc_capcmp(set_foreground, set_a_foreground))
883 	_nc_warning("expected setf/setaf to be different");
884 
885     if (VALID_STRING(set_background)
886 	&& VALID_STRING(set_a_background)
887 	&& !_nc_capcmp(set_background, set_a_background))
888 	_nc_warning("expected setb/setab to be different");
889 
890     /* see: has_colors() */
891     if (VALID_NUMERIC(max_colors) && VALID_NUMERIC(max_pairs)
892 	&& (((set_foreground != NULL)
893 	     && (set_background != NULL))
894 	    || ((set_a_foreground != NULL)
895 		&& (set_a_background != NULL))
896 	    || set_color_pair)) {
897 	if (!VALID_STRING(orig_pair) && !VALID_STRING(orig_colors))
898 	    _nc_warning("expected either op/oc string for resetting colors");
899     }
900 }
901 
902 static char
903 keypad_final(const char *string)
904 {
905     char result = '\0';
906 
907     if (VALID_STRING(string)
908 	&& *string++ == '\033'
909 	&& *string++ == 'O'
910 	&& strlen(string) == 1) {
911 	result = *string;
912     }
913 
914     return result;
915 }
916 
917 static int
918 keypad_index(const char *string)
919 {
920     char *test;
921     const char *list = "PQRSwxymtuvlqrsPpn";	/* app-keypad except "Enter" */
922     int ch;
923     int result = -1;
924 
925     if ((ch = keypad_final(string)) != '\0') {
926 	test = strchr(list, ch);
927 	if (test != 0)
928 	    result = (test - list);
929     }
930     return result;
931 }
932 
933 #define MAX_KP 5
934 /*
935  * Do a quick sanity-check for vt100-style keypads to see if the 5-key keypad
936  * is mapped inconsistently.
937  */
938 static void
939 check_keypad(TERMTYPE *tp)
940 {
941     char show[80];
942 
943     if (VALID_STRING(key_a1) &&
944 	VALID_STRING(key_a3) &&
945 	VALID_STRING(key_b2) &&
946 	VALID_STRING(key_c1) &&
947 	VALID_STRING(key_c3)) {
948 	char final[MAX_KP + 1];
949 	int list[MAX_KP];
950 	int increase = 0;
951 	int j, k, kk;
952 	int last;
953 	int test;
954 
955 	final[0] = keypad_final(key_a1);
956 	final[1] = keypad_final(key_a3);
957 	final[2] = keypad_final(key_b2);
958 	final[3] = keypad_final(key_c1);
959 	final[4] = keypad_final(key_c3);
960 	final[5] = '\0';
961 
962 	/* special case: legacy coding using 1,2,3,0,. on the bottom */
963 	assert(strlen(final) <= MAX_KP);
964 	if (!strcmp(final, "qsrpn"))
965 	    return;
966 
967 	list[0] = keypad_index(key_a1);
968 	list[1] = keypad_index(key_a3);
969 	list[2] = keypad_index(key_b2);
970 	list[3] = keypad_index(key_c1);
971 	list[4] = keypad_index(key_c3);
972 
973 	/* check that they're all vt100 keys */
974 	for (j = 0; j < MAX_KP; ++j) {
975 	    if (list[j] < 0) {
976 		return;
977 	    }
978 	}
979 
980 	/* check if they're all in increasing order */
981 	for (j = 1; j < MAX_KP; ++j) {
982 	    if (list[j] > list[j - 1]) {
983 		++increase;
984 	    }
985 	}
986 	if (increase != (MAX_KP - 1)) {
987 	    show[0] = '\0';
988 
989 	    for (j = 0, last = -1; j < MAX_KP; ++j) {
990 		for (k = 0, kk = -1, test = 100; k < 5; ++k) {
991 		    if (list[k] > last &&
992 			list[k] < test) {
993 			test = list[k];
994 			kk = k;
995 		    }
996 		}
997 		last = test;
998 		assert(strlen(show) < (MAX_KP * 4));
999 		switch (kk) {
1000 		case 0:
1001 		    strlcat(show, " ka1", sizeof(show));
1002 		    break;
1003 		case 1:
1004 		    strlcat(show, " ka3", sizeof(show));
1005 		    break;
1006 		case 2:
1007 		    strlcat(show, " kb2", sizeof(show));
1008 		    break;
1009 		case 3:
1010 		    strlcat(show, " kc1", sizeof(show));
1011 		    break;
1012 		case 4:
1013 		    strlcat(show, " kc3", sizeof(show));
1014 		    break;
1015 		}
1016 	    }
1017 
1018 	    _nc_warning("vt100 keypad order inconsistent: %s", show);
1019 	}
1020 
1021     } else if (VALID_STRING(key_a1) ||
1022 	       VALID_STRING(key_a3) ||
1023 	       VALID_STRING(key_b2) ||
1024 	       VALID_STRING(key_c1) ||
1025 	       VALID_STRING(key_c3)) {
1026 	show[0] = '\0';
1027 	if (keypad_index(key_a1) >= 0)
1028 	    strlcat(show, " ka1", sizeof(show));
1029 	if (keypad_index(key_a3) >= 0)
1030 	    strlcat(show, " ka3", sizeof(show));
1031 	if (keypad_index(key_b2) >= 0)
1032 	    strlcat(show, " kb2", sizeof(show));
1033 	if (keypad_index(key_c1) >= 0)
1034 	    strlcat(show, " kc1", sizeof(show));
1035 	if (keypad_index(key_c3) >= 0)
1036 	    strlcat(show, " kc3", sizeof(show));
1037 	if (*show != '\0')
1038 	    _nc_warning("vt100 keypad map incomplete:%s", show);
1039     }
1040 }
1041 
1042 /*
1043  * Returns the expected number of parameters for the given capability.
1044  */
1045 static int
1046 expected_params(const char *name)
1047 {
1048     /* *INDENT-OFF* */
1049     static const struct {
1050 	const char *name;
1051 	int count;
1052     } table[] = {
1053 	{ "S0",			1 },	/* 'screen' extension */
1054 	{ "birep",		2 },
1055 	{ "chr",		1 },
1056 	{ "colornm",		1 },
1057 	{ "cpi",		1 },
1058 	{ "csnm",		1 },
1059 	{ "csr",		2 },
1060 	{ "cub",		1 },
1061 	{ "cud",		1 },
1062 	{ "cuf",		1 },
1063 	{ "cup",		2 },
1064 	{ "cuu",		1 },
1065 	{ "cvr",		1 },
1066 	{ "cwin",		5 },
1067 	{ "dch",		1 },
1068 	{ "defc",		3 },
1069 	{ "dial",		1 },
1070 	{ "dispc",		1 },
1071 	{ "dl",			1 },
1072 	{ "ech",		1 },
1073 	{ "getm",		1 },
1074 	{ "hpa",		1 },
1075 	{ "ich",		1 },
1076 	{ "il",			1 },
1077 	{ "indn",		1 },
1078 	{ "initc",		4 },
1079 	{ "initp",		7 },
1080 	{ "lpi",		1 },
1081 	{ "mc5p",		1 },
1082 	{ "mrcup",		2 },
1083 	{ "mvpa",		1 },
1084 	{ "pfkey",		2 },
1085 	{ "pfloc",		2 },
1086 	{ "pfx",		2 },
1087 	{ "pfxl",		3 },
1088 	{ "pln",		2 },
1089 	{ "qdial",		1 },
1090 	{ "rcsd",		1 },
1091 	{ "rep",		2 },
1092 	{ "rin",		1 },
1093 	{ "sclk",		3 },
1094 	{ "scp",		1 },
1095 	{ "scs",		1 },
1096 	{ "scsd",		2 },
1097 	{ "setab",		1 },
1098 	{ "setaf",		1 },
1099 	{ "setb",		1 },
1100 	{ "setcolor",		1 },
1101 	{ "setf",		1 },
1102 	{ "sgr",		9 },
1103 	{ "sgr1",		6 },
1104 	{ "slength",		1 },
1105 	{ "slines",		1 },
1106 	{ "smgbp",		1 },	/* 2 if smgtp is not given */
1107 	{ "smglp",		1 },
1108 	{ "smglr",		2 },
1109 	{ "smgrp",		1 },
1110 	{ "smgtb",		2 },
1111 	{ "smgtp",		1 },
1112 	{ "tsl",		1 },
1113 	{ "u6",			-1 },
1114 	{ "vpa",		1 },
1115 	{ "wind",		4 },
1116 	{ "wingo",		1 },
1117     };
1118     /* *INDENT-ON* */
1119 
1120     unsigned n;
1121     int result = 0;		/* function-keys, etc., use none */
1122 
1123     for (n = 0; n < SIZEOF(table); n++) {
1124 	if (!strcmp(name, table[n].name)) {
1125 	    result = table[n].count;
1126 	    break;
1127 	}
1128     }
1129 
1130     return result;
1131 }
1132 
1133 /*
1134  * Make a quick sanity check for the parameters which are used in the given
1135  * strings.  If there are no "%p" tokens, then there should be no other "%"
1136  * markers.
1137  */
1138 static void
1139 check_params(TERMTYPE *tp, const char *name, char *value)
1140 {
1141     int expected = expected_params(name);
1142     int actual = 0;
1143     int n;
1144     bool params[10];
1145     char *s = value;
1146 
1147 #ifdef set_top_margin_parm
1148     if (!strcmp(name, "smgbp")
1149 	&& set_top_margin_parm == 0)
1150 	expected = 2;
1151 #endif
1152 
1153     for (n = 0; n < 10; n++)
1154 	params[n] = FALSE;
1155 
1156     while (*s != 0) {
1157 	if (*s == '%') {
1158 	    if (*++s == '\0') {
1159 		_nc_warning("expected character after %% in %s", name);
1160 		break;
1161 	    } else if (*s == 'p') {
1162 		if (*++s == '\0' || !isdigit((int) *s)) {
1163 		    _nc_warning("expected digit after %%p in %s", name);
1164 		    return;
1165 		} else {
1166 		    n = (*s - '0');
1167 		    if (n > actual)
1168 			actual = n;
1169 		    params[n] = TRUE;
1170 		}
1171 	    }
1172 	}
1173 	s++;
1174     }
1175 
1176     if (params[0]) {
1177 	_nc_warning("%s refers to parameter 0 (%%p0), which is not allowed", name);
1178     }
1179     if (value == set_attributes || expected < 0) {
1180 	;
1181     } else if (expected != actual) {
1182 	_nc_warning("%s uses %d parameters, expected %d", name,
1183 		    actual, expected);
1184 	for (n = 1; n < actual; n++) {
1185 	    if (!params[n])
1186 		_nc_warning("%s omits parameter %d", name, n);
1187 	}
1188     }
1189 }
1190 
1191 static char *
1192 skip_delay(char *s)
1193 {
1194     while (*s == '/' || isdigit(UChar(*s)))
1195 	++s;
1196     return s;
1197 }
1198 
1199 /*
1200  * Skip a delay altogether, e.g., when comparing a simple string to sgr,
1201  * the latter may have a worst-case delay on the end.
1202  */
1203 static char *
1204 ignore_delays(char *s)
1205 {
1206     int delaying = 0;
1207 
1208     do {
1209 	switch (*s) {
1210 	case '$':
1211 	    if (delaying == 0)
1212 		delaying = 1;
1213 	    break;
1214 	case '<':
1215 	    if (delaying == 1)
1216 		delaying = 2;
1217 	    break;
1218 	case '\0':
1219 	    delaying = 0;
1220 	    break;
1221 	default:
1222 	    if (delaying) {
1223 		s = skip_delay(s);
1224 		if (*s == '>')
1225 		    ++s;
1226 		delaying = 0;
1227 	    }
1228 	    break;
1229 	}
1230 	if (delaying)
1231 	    ++s;
1232     } while (delaying);
1233     return s;
1234 }
1235 
1236 /*
1237  * An sgr string may contain several settings other than the one we're
1238  * interested in, essentially sgr0 + rmacs + whatever.  As long as the
1239  * "whatever" is contained in the sgr string, that is close enough for our
1240  * sanity check.
1241  */
1242 static bool
1243 similar_sgr(int num, char *a, char *b)
1244 {
1245     static const char *names[] =
1246     {
1247 	"none"
1248 	,"standout"
1249 	,"underline"
1250 	,"reverse"
1251 	,"blink"
1252 	,"dim"
1253 	,"bold"
1254 	,"invis"
1255 	,"protect"
1256 	,"altcharset"
1257     };
1258     char *base_a = a;
1259     char *base_b = b;
1260     int delaying = 0;
1261 
1262     while (*b != 0) {
1263 	while (*a != *b) {
1264 	    if (*a == 0) {
1265 		if (b[0] == '$'
1266 		    && b[1] == '<') {
1267 		    _nc_warning("Did not find delay %s", _nc_visbuf(b));
1268 		} else {
1269 		    _nc_warning("checking sgr(%s) %s\n\tcompare to %s\n\tunmatched %s",
1270 				names[num], _nc_visbuf2(1, base_a),
1271 				_nc_visbuf2(2, base_b),
1272 				_nc_visbuf2(3, b));
1273 		}
1274 		return FALSE;
1275 	    } else if (delaying) {
1276 		a = skip_delay(a);
1277 		b = skip_delay(b);
1278 	    } else {
1279 		a++;
1280 	    }
1281 	}
1282 	switch (*a) {
1283 	case '$':
1284 	    if (delaying == 0)
1285 		delaying = 1;
1286 	    break;
1287 	case '<':
1288 	    if (delaying == 1)
1289 		delaying = 2;
1290 	    break;
1291 	default:
1292 	    delaying = 0;
1293 	    break;
1294 	}
1295 	a++;
1296 	b++;
1297     }
1298     /* ignore delays on the end of the string */
1299     a = ignore_delays(a);
1300     return ((num != 0) || (*a == 0));
1301 }
1302 
1303 static char *
1304 check_sgr(TERMTYPE *tp, char *zero, int num, char *cap, const char *name)
1305 {
1306     char *test;
1307 
1308     _nc_tparm_err = 0;
1309     test = TPARM_9(set_attributes,
1310 		   num == 1,
1311 		   num == 2,
1312 		   num == 3,
1313 		   num == 4,
1314 		   num == 5,
1315 		   num == 6,
1316 		   num == 7,
1317 		   num == 8,
1318 		   num == 9);
1319     if (test != 0) {
1320 	if (PRESENT(cap)) {
1321 	    if (!similar_sgr(num, test, cap)) {
1322 		_nc_warning("%s differs from sgr(%d)\n\t%s=%s\n\tsgr(%d)=%s",
1323 			    name, num,
1324 			    name, _nc_visbuf2(1, cap),
1325 			    num, _nc_visbuf2(2, test));
1326 	    }
1327 	} else if (_nc_capcmp(test, zero)) {
1328 	    _nc_warning("sgr(%d) present, but not %s", num, name);
1329 	}
1330     } else if (PRESENT(cap)) {
1331 	_nc_warning("sgr(%d) missing, but %s present", num, name);
1332     }
1333     if (_nc_tparm_err)
1334 	_nc_warning("stack error in sgr(%d) string", num);
1335     return test;
1336 }
1337 
1338 #define CHECK_SGR(num,name) check_sgr(tp, zero, num, name, #name)
1339 
1340 #ifdef TRACE
1341 /*
1342  * If tic is compiled with TRACE, we'll be able to see the output from the
1343  * DEBUG() macro.  But since it doesn't use traceon(), it always goes to
1344  * the standard error.  Use this function to make it simpler to follow the
1345  * resulting debug traces.
1346  */
1347 static void
1348 show_where(unsigned level)
1349 {
1350     if (_nc_tracing >= DEBUG_LEVEL(level)) {
1351 	char my_name[256];
1352 	_nc_get_type(my_name);
1353 	fprintf(stderr, "\"%s\", line %d, '%s' ",
1354 		_nc_get_source(),
1355 		_nc_curr_line, my_name);
1356     }
1357 }
1358 
1359 #else
1360 #define show_where(level)	/* nothing */
1361 #endif
1362 
1363 /* other sanity-checks (things that we don't want in the normal
1364  * logic that reads a terminfo entry)
1365  */
1366 static void
1367 check_termtype(TERMTYPE *tp, bool literal)
1368 {
1369     bool conflict = FALSE;
1370     unsigned j, k;
1371     char fkeys[STRCOUNT];
1372 
1373     /*
1374      * A terminal entry may contain more than one keycode assigned to
1375      * a given string (e.g., KEY_END and KEY_LL).  But curses will only
1376      * return one (the last one assigned).
1377      */
1378     if (!(_nc_syntax == SYN_TERMCAP && capdump)) {
1379 	memset(fkeys, 0, sizeof(fkeys));
1380 	for (j = 0; _nc_tinfo_fkeys[j].code; j++) {
1381 	    char *a = tp->Strings[_nc_tinfo_fkeys[j].offset];
1382 	    bool first = TRUE;
1383 	    if (!VALID_STRING(a))
1384 		continue;
1385 	    for (k = j + 1; _nc_tinfo_fkeys[k].code; k++) {
1386 		char *b = tp->Strings[_nc_tinfo_fkeys[k].offset];
1387 		if (!VALID_STRING(b)
1388 		    || fkeys[k])
1389 		    continue;
1390 		if (!_nc_capcmp(a, b)) {
1391 		    fkeys[j] = 1;
1392 		    fkeys[k] = 1;
1393 		    if (first) {
1394 			if (!conflict) {
1395 			    _nc_warning("Conflicting key definitions (using the last)");
1396 			    conflict = TRUE;
1397 			}
1398 			fprintf(stderr, "... %s is the same as %s",
1399 				keyname((int) _nc_tinfo_fkeys[j].code),
1400 				keyname((int) _nc_tinfo_fkeys[k].code));
1401 			first = FALSE;
1402 		    } else {
1403 			fprintf(stderr, ", %s",
1404 				keyname((int) _nc_tinfo_fkeys[k].code));
1405 		    }
1406 		}
1407 	    }
1408 	    if (!first)
1409 		fprintf(stderr, "\n");
1410 	}
1411     }
1412 
1413     for (j = 0; j < NUM_STRINGS(tp); j++) {
1414 	char *a = tp->Strings[j];
1415 	if (VALID_STRING(a))
1416 	    check_params(tp, ExtStrname(tp, j, strnames), a);
1417     }
1418 
1419     check_acs(tp);
1420     check_colors(tp);
1421     check_keypad(tp);
1422 
1423     /*
1424      * These may be mismatched because the terminal description relies on
1425      * restoring the cursor visibility by resetting it.
1426      */
1427     ANDMISSING(cursor_invisible, cursor_normal);
1428     ANDMISSING(cursor_visible, cursor_normal);
1429 
1430     if (PRESENT(cursor_visible) && PRESENT(cursor_normal)
1431 	&& !_nc_capcmp(cursor_visible, cursor_normal))
1432 	_nc_warning("cursor_visible is same as cursor_normal");
1433 
1434     /*
1435      * From XSI & O'Reilly, we gather that sc/rc are required if csr is
1436      * given, because the cursor position after the scrolling operation is
1437      * performed is undefined.
1438      */
1439     ANDMISSING(change_scroll_region, save_cursor);
1440     ANDMISSING(change_scroll_region, restore_cursor);
1441 
1442     if (PRESENT(set_attributes)) {
1443 	char *zero = 0;
1444 
1445 	_nc_tparm_err = 0;
1446 	if (PRESENT(exit_attribute_mode)) {
1447 	    zero = strdup(CHECK_SGR(0, exit_attribute_mode));
1448 	} else {
1449 	    zero = strdup(TPARM_9(set_attributes, 0, 0, 0, 0, 0, 0, 0, 0, 0));
1450 	}
1451 	if (_nc_tparm_err)
1452 	    _nc_warning("stack error in sgr(0) string");
1453 
1454 	if (zero != 0) {
1455 	    CHECK_SGR(1, enter_standout_mode);
1456 	    CHECK_SGR(2, enter_underline_mode);
1457 	    CHECK_SGR(3, enter_reverse_mode);
1458 	    CHECK_SGR(4, enter_blink_mode);
1459 	    CHECK_SGR(5, enter_dim_mode);
1460 	    CHECK_SGR(6, enter_bold_mode);
1461 	    CHECK_SGR(7, enter_secure_mode);
1462 	    CHECK_SGR(8, enter_protected_mode);
1463 	    CHECK_SGR(9, enter_alt_charset_mode);
1464 	    free(zero);
1465 	} else {
1466 	    _nc_warning("sgr(0) did not return a value");
1467 	}
1468     } else if (PRESENT(exit_attribute_mode) &&
1469 	       set_attributes != CANCELLED_STRING) {
1470 	if (_nc_syntax == SYN_TERMINFO)
1471 	    _nc_warning("missing sgr string");
1472     }
1473 
1474     if (PRESENT(exit_attribute_mode)) {
1475 	char *check_sgr0 = _nc_trim_sgr0(tp);
1476 
1477 	if (check_sgr0 == 0 || *check_sgr0 == '\0') {
1478 	    _nc_warning("trimmed sgr0 is empty");
1479 	} else {
1480 	    show_where(2);
1481 	    if (check_sgr0 != exit_attribute_mode) {
1482 		DEBUG(2,
1483 		      ("will trim sgr0\n\toriginal sgr0=%s\n\ttrimmed  sgr0=%s",
1484 		       _nc_visbuf2(1, exit_attribute_mode),
1485 		       _nc_visbuf2(2, check_sgr0)));
1486 		free(check_sgr0);
1487 	    } else {
1488 		DEBUG(2,
1489 		      ("will not trim sgr0\n\toriginal sgr0=%s",
1490 		       _nc_visbuf(exit_attribute_mode)));
1491 	    }
1492 	}
1493     }
1494 #ifdef TRACE
1495     show_where(2);
1496     if (!auto_right_margin) {
1497 	DEBUG(2,
1498 	      ("can write to lower-right directly"));
1499     } else if (PRESENT(enter_am_mode) && PRESENT(exit_am_mode)) {
1500 	DEBUG(2,
1501 	      ("can write to lower-right by suppressing automargin"));
1502     } else if ((PRESENT(enter_insert_mode) && PRESENT(exit_insert_mode))
1503 	       || PRESENT(insert_character) || PRESENT(parm_ich)) {
1504 	DEBUG(2,
1505 	      ("can write to lower-right by using inserts"));
1506     } else {
1507 	DEBUG(2,
1508 	      ("cannot write to lower-right"));
1509     }
1510 #endif
1511 
1512     /*
1513      * Some standard applications (e.g., vi) and some non-curses
1514      * applications (e.g., jove) get confused if we have both ich1 and
1515      * smir/rmir.  Let's be nice and warn about that, too, even though
1516      * ncurses handles it.
1517      */
1518     if ((PRESENT(enter_insert_mode) || PRESENT(exit_insert_mode))
1519 	&& PRESENT(parm_ich)) {
1520 	_nc_warning("non-curses applications may be confused by ich1 with smir/rmir");
1521     }
1522 
1523     /*
1524      * Finally, do the non-verbose checks
1525      */
1526     if (save_check_termtype != 0)
1527 	save_check_termtype(tp, literal);
1528 }
1529