xref: /dragonfly/contrib/ncurses/progs/tic.c (revision bb8c85ff)
1 /****************************************************************************
2  * Copyright (c) 1998-2015,2016 Free Software Foundation, Inc.              *
3  *                                                                          *
4  * Permission is hereby granted, free of charge, to any person obtaining a  *
5  * copy of this software and associated documentation files (the            *
6  * "Software"), to deal in the Software without restriction, including      *
7  * without limitation the rights to use, copy, modify, merge, publish,      *
8  * distribute, distribute with modifications, sublicense, and/or sell       *
9  * copies of the Software, and to permit persons to whom the Software is    *
10  * furnished to do so, subject to the following conditions:                 *
11  *                                                                          *
12  * The above copyright notice and this permission notice shall be included  *
13  * in all copies or substantial portions of the Software.                   *
14  *                                                                          *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
16  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
17  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
18  * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
19  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
20  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
21  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
22  *                                                                          *
23  * Except as contained in this notice, the name(s) of the above copyright   *
24  * holders shall not be used in advertising or otherwise to promote the     *
25  * sale, use or other dealings in this Software without prior written       *
26  * authorization.                                                           *
27  ****************************************************************************/
28 
29 /****************************************************************************
30  *  Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995               *
31  *     and: Eric S. Raymond <esr@snark.thyrsus.com>                         *
32  *     and: Thomas E. Dickey                        1996 on                 *
33  ****************************************************************************/
34 
35 /*
36  *	tic.c --- Main program for terminfo compiler
37  *			by Eric S. Raymond
38  *			and Thomas E Dickey
39  *
40  */
41 
42 #include <progs.priv.h>
43 #include <sys/stat.h>
44 
45 #include <dump_entry.h>
46 #include <tparm_type.h>
47 #include <hashed_db.h>
48 #include <parametrized.h>
49 #include <transform.h>
50 
51 MODULE_ID("$Id: tic.c,v 1.221 2016/01/02 20:04:37 tom Exp $")
52 
53 #define STDIN_NAME "<stdin>"
54 
55 const char *_nc_progname = "tic";
56 
57 static FILE *log_fp;
58 static FILE *tmp_fp;
59 static bool capdump = FALSE;	/* running as infotocap? */
60 static bool infodump = FALSE;	/* running as captoinfo? */
61 static bool showsummary = FALSE;
62 static char **namelst = 0;
63 static const char *to_remove;
64 
65 static void (*save_check_termtype) (TERMTYPE *, bool);
66 static void check_termtype(TERMTYPE *tt, bool);
67 
68 static const char usage_string[] = "\
69 [-e names] \
70 [-o dir] \
71 [-R name] \
72 [-v[n]] \
73 [-V] \
74 [-w[n]] \
75 [-\
76 1\
77 a\
78 C\
79 D\
80 c\
81 f\
82 G\
83 g\
84 I\
85 K\
86 L\
87 N\
88 r\
89 s\
90 T\
91 t\
92 U\
93 x\
94 ] \
95 source-file\n";
96 
97 #if NO_LEAKS
98 static void
99 free_namelist(char **src)
100 {
101     if (src != 0) {
102 	int n;
103 	for (n = 0; src[n] != 0; ++n)
104 	    free(src[n]);
105 	free(src);
106     }
107 }
108 #endif
109 
110 static void
111 cleanup(void)
112 {
113     int rc;
114 
115 #if NO_LEAKS
116     free_namelist(namelst);
117 #endif
118     if (tmp_fp != 0)
119 	fclose(tmp_fp);
120     if (to_remove != 0) {
121 #if HAVE_REMOVE
122 	rc = remove(to_remove);
123 #else
124 	rc = unlink(to_remove);
125 #endif
126 	if (rc != 0)
127 	    perror(to_remove);
128     }
129 }
130 
131 static void
132 failed(const char *msg)
133 {
134     perror(msg);
135     ExitProgram(EXIT_FAILURE);
136 }
137 
138 static void
139 usage(void)
140 {
141 #define DATA(s) s "\n"
142     static const char options_string[] =
143     {
144 	DATA("Options:")
145 	DATA("  -0         format translation output all capabilities on one line")
146 	DATA("  -1         format translation output one capability per line")
147 #if NCURSES_XNAMES
148 	DATA("  -a         retain commented-out capabilities (sets -x also)")
149 #endif
150 	DATA("  -C         translate entries to termcap source form")
151 	DATA("  -D         print list of tic's database locations (first must be writable)")
152 	DATA("  -c         check only, validate input without compiling or translating")
153 	DATA("  -e<names>  translate/compile only entries named by comma-separated list")
154 	DATA("  -f         format complex strings for readability")
155 	DATA("  -G         format %{number} to %'char'")
156 	DATA("  -g         format %'char' to %{number}")
157 	DATA("  -I         translate entries to terminfo source form")
158 	DATA("  -K         translate entries to termcap source form with BSD syntax")
159 	DATA("  -L         translate entries to full terminfo source form")
160 	DATA("  -N         disable smart defaults for source translation")
161 	DATA("  -o<dir>    set output directory for compiled entry writes")
162 	DATA("  -Q[n]      dump compiled description")
163 	DATA("  -q    brief listing, removes headers")
164 	DATA("  -R<name>   restrict translation to given terminfo/termcap version")
165 	DATA("  -r         force resolution of all use entries in source translation")
166 	DATA("  -s         print summary statistics")
167 	DATA("  -T         remove size-restrictions on compiled description")
168 #if NCURSES_XNAMES
169 	DATA("  -t         suppress commented-out capabilities")
170 #endif
171 	DATA("  -U         suppress post-processing of entries")
172 	DATA("  -V         print version")
173 	DATA("  -v[n]      set verbosity level")
174 	DATA("  -w[n]      set format width for translation output")
175 #if NCURSES_XNAMES
176 	DATA("  -x         treat unknown capabilities as user-defined")
177 #endif
178 	DATA("")
179 	DATA("Parameters:")
180 	DATA("  <file>     file to translate or compile")
181     };
182 #undef DATA
183 
184     fprintf(stderr, "Usage: %s %s\n", _nc_progname, usage_string);
185     fputs(options_string, stderr);
186     ExitProgram(EXIT_FAILURE);
187 }
188 
189 #define L_BRACE '{'
190 #define R_BRACE '}'
191 #define S_QUOTE '\''
192 
193 static void
194 write_it(ENTRY * ep)
195 {
196     unsigned n;
197     int ch;
198     char *s, *d, *t;
199     char result[MAX_ENTRY_SIZE];
200 
201     /*
202      * Look for strings that contain %{number}, convert them to %'char',
203      * which is shorter and runs a little faster.
204      */
205     for (n = 0; n < STRCOUNT; n++) {
206 	s = ep->tterm.Strings[n];
207 	if (VALID_STRING(s)
208 	    && strchr(s, L_BRACE) != 0) {
209 	    d = result;
210 	    t = s;
211 	    while ((ch = *t++) != 0) {
212 		*d++ = (char) ch;
213 		if (ch == '\\') {
214 		    *d++ = *t++;
215 		} else if ((ch == '%')
216 			   && (*t == L_BRACE)) {
217 		    char *v = 0;
218 		    long value = strtol(t + 1, &v, 0);
219 		    if (v != 0
220 			&& *v == R_BRACE
221 			&& value > 0
222 			&& value != '\\'	/* FIXME */
223 			&& value < 127
224 			&& isprint((int) value)) {
225 			*d++ = S_QUOTE;
226 			*d++ = (char) value;
227 			*d++ = S_QUOTE;
228 			t = (v + 1);
229 		    }
230 		}
231 	    }
232 	    *d = 0;
233 	    if (strlen(result) < strlen(s))
234 		_nc_STRCPY(s, result, strlen(s) + 1);
235 	}
236     }
237 
238     _nc_set_type(_nc_first_name(ep->tterm.term_names));
239     _nc_curr_line = (int) ep->startline;
240     _nc_write_entry(&ep->tterm);
241 }
242 
243 static bool
244 immedhook(ENTRY * ep GCC_UNUSED)
245 /* write out entries with no use capabilities immediately to save storage */
246 {
247 #if !HAVE_BIG_CORE
248     /*
249      * This is strictly a core-economy kluge.  The really clean way to handle
250      * compilation is to slurp the whole file into core and then do all the
251      * name-collision checks and entry writes in one swell foop.  But the
252      * terminfo master file is large enough that some core-poor systems swap
253      * like crazy when you compile it this way...there have been reports of
254      * this process taking *three hours*, rather than the twenty seconds or
255      * less typical on my development box.
256      *
257      * So.  This hook *immediately* writes out the referenced entry if it
258      * has no use capabilities.  The compiler main loop refrains from
259      * adding the entry to the in-core list when this hook fires.  If some
260      * other entry later needs to reference an entry that got written
261      * immediately, that's OK; the resolution code will fetch it off disk
262      * when it can't find it in core.
263      *
264      * Name collisions will still be detected, just not as cleanly.  The
265      * write_entry() code complains before overwriting an entry that
266      * postdates the time of tic's first call to write_entry().  Thus
267      * it will complain about overwriting entries newly made during the
268      * tic run, but not about overwriting ones that predate it.
269      *
270      * The reason this is a hook, and not in line with the rest of the
271      * compiler code, is that the support for termcap fallback cannot assume
272      * it has anywhere to spool out these entries!
273      *
274      * The _nc_set_type() call here requires a compensating one in
275      * _nc_parse_entry().
276      *
277      * If you define HAVE_BIG_CORE, you'll disable this kluge.  This will
278      * make tic a bit faster (because the resolution code won't have to do
279      * disk I/O nearly as often).
280      */
281     if (ep->nuses == 0) {
282 	int oldline = _nc_curr_line;
283 
284 	write_it(ep);
285 	_nc_curr_line = oldline;
286 	free(ep->tterm.str_table);
287 	return (TRUE);
288     }
289 #endif /* HAVE_BIG_CORE */
290     return (FALSE);
291 }
292 
293 static void
294 put_translate(int c)
295 /* emit a comment char, translating terminfo names to termcap names */
296 {
297     static bool in_name = FALSE;
298     static size_t have, used;
299     static char *namebuf, *suffix;
300 
301     if (in_name) {
302 	if (used + 1 >= have) {
303 	    have += 132;
304 	    if ((namebuf = typeRealloc(char, have, namebuf)) == 0)
305 		  failed("put_translate namebuf");
306 	    if ((suffix = typeRealloc(char, have, suffix)) == 0)
307 		  failed("put_translate suffix");
308 	}
309 	if (c == '\n' || c == '@') {
310 	    namebuf[used++] = '\0';
311 	    (void) putchar('<');
312 	    (void) fputs(namebuf, stdout);
313 	    putchar(c);
314 	    in_name = FALSE;
315 	} else if (c != '>') {
316 	    namebuf[used++] = (char) c;
317 	} else {		/* ah! candidate name! */
318 	    char *up;
319 	    NCURSES_CONST char *tp;
320 
321 	    namebuf[used++] = '\0';
322 	    in_name = FALSE;
323 
324 	    suffix[0] = '\0';
325 	    if ((up = strchr(namebuf, '#')) != 0
326 		|| (up = strchr(namebuf, '=')) != 0
327 		|| ((up = strchr(namebuf, '@')) != 0 && up[1] == '>')) {
328 		_nc_STRCPY(suffix, up, have);
329 		*up = '\0';
330 	    }
331 
332 	    if ((tp = nametrans(namebuf)) != 0) {
333 		(void) putchar(':');
334 		(void) fputs(tp, stdout);
335 		(void) fputs(suffix, stdout);
336 		(void) putchar(':');
337 	    } else {
338 		/* couldn't find a translation, just dump the name */
339 		(void) putchar('<');
340 		(void) fputs(namebuf, stdout);
341 		(void) fputs(suffix, stdout);
342 		(void) putchar('>');
343 	    }
344 	}
345     } else {
346 	used = 0;
347 	if (c == '<') {
348 	    in_name = TRUE;
349 	} else {
350 	    putchar(c);
351 	}
352     }
353 }
354 
355 /* Returns a string, stripped of leading/trailing whitespace */
356 static char *
357 stripped(char *src)
358 {
359     char *dst = 0;
360 
361     while (isspace(UChar(*src)))
362 	src++;
363 
364     if (*src != '\0') {
365 	size_t len;
366 
367 	if ((dst = strdup(src)) == NULL) {
368 	    failed("strdup");
369 	} else {
370 	    len = strlen(dst);
371 	    while (--len != 0 && isspace(UChar(dst[len])))
372 		dst[len] = '\0';
373 	}
374     }
375     return dst;
376 }
377 
378 static FILE *
379 open_tempfile(char *filename)
380 {
381     FILE *result = 0;
382 
383     _nc_STRCPY(filename, "/tmp/XXXXXX", PATH_MAX);
384 #if HAVE_MKSTEMP
385     {
386 	int oldmask = (int) umask(077);
387 	int fd = mkstemp(filename);
388 	if (fd >= 0)
389 	    result = fdopen(fd, "w");
390 	umask((mode_t) oldmask);
391     }
392 #else
393     if (tmpnam(filename) != 0)
394 	result = fopen(filename, "w");
395 #endif
396     return result;
397 }
398 
399 static FILE *
400 copy_input(FILE *source, const char *filename, char *alt_file)
401 {
402     char my_altfile[PATH_MAX];
403     FILE *result = 0;
404     FILE *target = 0;
405     int ch;
406 
407     if (alt_file == 0)
408 	alt_file = my_altfile;
409 
410     if (source == 0) {
411 	failed("copy_input (source)");
412     } else if ((target = open_tempfile(alt_file)) == 0) {
413 	failed("copy_input (target)");
414     } else {
415 	clearerr(source);
416 	for (;;) {
417 	    ch = fgetc(source);
418 	    if (feof(source)) {
419 		break;
420 	    } else if (ferror(source)) {
421 		failed(filename);
422 	    } else if (ch == 0) {
423 		/* don't loop in case someone wants to convert /dev/zero */
424 		fprintf(stderr, "%s: %s is not a text-file\n", _nc_progname, filename);
425 		ExitProgram(EXIT_FAILURE);
426 	    }
427 	    fputc(ch, target);
428 	}
429 	fclose(source);
430 	/*
431 	 * rewind() does not force the target file's data to disk (not does
432 	 * fflush()...).  So open a second stream on the data and then close
433 	 * the one that we were writing on before starting to read from the
434 	 * second stream.
435 	 */
436 	result = fopen(alt_file, "r+");
437 	fclose(target);
438 	to_remove = strdup(alt_file);
439     }
440     return result;
441 }
442 
443 static FILE *
444 open_input(const char *filename, char *alt_file)
445 {
446     FILE *fp;
447     struct stat sb;
448     int mode;
449 
450     if (!strcmp(filename, "-")) {
451 	fp = copy_input(stdin, STDIN_NAME, alt_file);
452     } else if (stat(filename, &sb) < 0) {
453 	fprintf(stderr, "%s: %s %s\n", _nc_progname, filename, strerror(errno));
454 	ExitProgram(EXIT_FAILURE);
455     } else if ((mode = (sb.st_mode & S_IFMT)) == S_IFDIR
456 	       || (mode != S_IFREG && mode != S_IFCHR && mode != S_IFIFO)) {
457 	fprintf(stderr, "%s: %s is not a file\n", _nc_progname, filename);
458 	ExitProgram(EXIT_FAILURE);
459     } else {
460 	fp = fopen(filename, "r");
461 
462 	if (fp == 0) {
463 	    fprintf(stderr, "%s: Can't open %s\n", _nc_progname, filename);
464 	    ExitProgram(EXIT_FAILURE);
465 	}
466 	if (mode != S_IFREG) {
467 	    if (alt_file != 0) {
468 		FILE *fp2 = copy_input(fp, filename, alt_file);
469 		fp = fp2;
470 	    } else {
471 		fprintf(stderr, "%s: %s is not a file\n", _nc_progname, filename);
472 		ExitProgram(EXIT_FAILURE);
473 	    }
474 	}
475     }
476     return fp;
477 }
478 
479 /* Parse the "-e" option-value into a list of names */
480 static char **
481 make_namelist(char *src)
482 {
483     char **dst = 0;
484 
485     char *s, *base;
486     unsigned pass, n, nn;
487     char buffer[BUFSIZ];
488 
489     if (src == 0) {
490 	/* EMPTY */ ;
491     } else if (strchr(src, '/') != 0) {		/* a filename */
492 	FILE *fp = open_input(src, (char *) 0);
493 
494 	for (pass = 1; pass <= 2; pass++) {
495 	    nn = 0;
496 	    while (fgets(buffer, sizeof(buffer), fp) != 0) {
497 		if ((s = stripped(buffer)) != 0) {
498 		    if (dst != 0)
499 			dst[nn] = s;
500 		    else
501 			free(s);
502 		    nn++;
503 		}
504 	    }
505 	    if (pass == 1) {
506 		if ((dst = typeCalloc(char *, nn + 1)) == 0)
507 		      failed("make_namelist");
508 		rewind(fp);
509 	    }
510 	}
511 	fclose(fp);
512     } else {			/* literal list of names */
513 	for (pass = 1; pass <= 2; pass++) {
514 	    for (n = nn = 0, base = src;; n++) {
515 		int mark = src[n];
516 		if (mark == ',' || mark == '\0') {
517 		    if (pass == 1) {
518 			nn++;
519 		    } else {
520 			src[n] = '\0';
521 			if ((s = stripped(base)) != 0)
522 			    dst[nn++] = s;
523 			base = &src[n + 1];
524 		    }
525 		}
526 		if (mark == '\0')
527 		    break;
528 	    }
529 	    if (pass == 1) {
530 		if ((dst = typeCalloc(char *, nn + 1)) == 0)
531 		      failed("make_namelist");
532 	    }
533 	}
534     }
535     if (showsummary && (dst != 0)) {
536 	fprintf(log_fp, "Entries that will be compiled:\n");
537 	for (n = 0; dst[n] != 0; n++)
538 	    fprintf(log_fp, "%u:%s\n", n + 1, dst[n]);
539     }
540     return dst;
541 }
542 
543 static bool
544 matches(char **needle, const char *haystack)
545 /* does entry in needle list match |-separated field in haystack? */
546 {
547     bool code = FALSE;
548     size_t n;
549 
550     if (needle != 0) {
551 	for (n = 0; needle[n] != 0; n++) {
552 	    if (_nc_name_match(haystack, needle[n], "|")) {
553 		code = TRUE;
554 		break;
555 	    }
556 	}
557     } else
558 	code = TRUE;
559     return (code);
560 }
561 
562 static char *
563 valid_db_path(const char *nominal)
564 {
565     struct stat sb;
566 #if USE_HASHED_DB
567     char suffix[] = DBM_SUFFIX;
568     size_t need = strlen(nominal) + sizeof(suffix);
569     char *result = malloc(need);
570 
571     if (result == 0)
572 	failed("valid_db_path");
573     _nc_STRCPY(result, nominal, need);
574     if (strcmp(result + need - sizeof(suffix), suffix)) {
575 	_nc_STRCAT(result, suffix, need);
576     }
577 #else
578     char *result = strdup(nominal);
579 #endif
580 
581     DEBUG(1, ("** stat(%s)", result));
582     if (stat(result, &sb) >= 0) {
583 #if USE_HASHED_DB
584 	if (!S_ISREG(sb.st_mode)
585 	    || access(result, R_OK | W_OK) != 0) {
586 	    DEBUG(1, ("...not a writable file"));
587 	    free(result);
588 	    result = 0;
589 	}
590 #else
591 	if (!S_ISDIR(sb.st_mode)
592 	    || access(result, R_OK | W_OK | X_OK) != 0) {
593 	    DEBUG(1, ("...not a writable directory"));
594 	    free(result);
595 	    result = 0;
596 	}
597 #endif
598     } else {
599 	/* check if parent is directory and is writable */
600 	unsigned leaf = _nc_pathlast(result);
601 
602 	DEBUG(1, ("...not found"));
603 	if (leaf) {
604 	    char save = result[leaf];
605 	    result[leaf] = 0;
606 	    if (stat(result, &sb) >= 0
607 		&& S_ISDIR(sb.st_mode)
608 		&& access(result, R_OK | W_OK | X_OK) == 0) {
609 		result[leaf] = save;
610 	    } else {
611 		DEBUG(1, ("...parent directory %s is not writable", result));
612 		free(result);
613 		result = 0;
614 	    }
615 	} else {
616 	    DEBUG(1, ("... no parent directory"));
617 	    free(result);
618 	    result = 0;
619 	}
620     }
621     return result;
622 }
623 
624 /*
625  * Show the databases to which tic could write.  The location to which it
626  * writes is always the first one.  If none are writable, print an error
627  * message.
628  */
629 static void
630 show_databases(const char *outdir)
631 {
632     bool specific = (outdir != 0) || getenv("TERMINFO") != 0;
633     char *result;
634     const char *tried = 0;
635 
636     if (outdir == 0) {
637 	outdir = _nc_tic_dir(0);
638     }
639     if ((result = valid_db_path(outdir)) != 0) {
640 	printf("%s\n", result);
641 	free(result);
642     } else {
643 	tried = outdir;
644     }
645 
646     if ((outdir = _nc_home_terminfo())) {
647 	if ((result = valid_db_path(outdir)) != 0) {
648 	    printf("%s\n", result);
649 	    free(result);
650 	} else if (!specific) {
651 	    tried = outdir;
652 	}
653     }
654 
655     /*
656      * If we can write in neither location, give an error message.
657      */
658     if (tried) {
659 	fflush(stdout);
660 	fprintf(stderr, "%s: %s (no permission)\n", _nc_progname, tried);
661 	ExitProgram(EXIT_FAILURE);
662     }
663 }
664 
665 static void
666 add_digit(int *target, int source)
667 {
668     *target = (*target * 10) + (source - '0');
669 }
670 
671 #define VtoTrace(opt) (unsigned) ((opt > 0) ? opt : (opt == 0))
672 
673 int
674 main(int argc, char *argv[])
675 {
676     char my_tmpname[PATH_MAX];
677     char my_altfile[PATH_MAX];
678     int v_opt = -1;
679     unsigned debug_level;
680     int smart_defaults = TRUE;
681     char *termcap;
682     ENTRY *qp;
683 
684     int this_opt, last_opt = '?';
685 
686     int outform = F_TERMINFO;	/* output format */
687     int sortmode = S_TERMINFO;	/* sort_mode */
688 
689     int width = 60;
690     int height = 65535;
691     bool formatted = FALSE;	/* reformat complex strings? */
692     bool literal = FALSE;	/* suppress post-processing? */
693     int numbers = 0;		/* format "%'char'" to/from "%{number}" */
694     bool forceresolve = FALSE;	/* force resolution */
695     bool limited = TRUE;
696     char *tversion = (char *) NULL;
697     const char *source_file = "terminfo";
698     char *outdir = (char *) NULL;
699     bool check_only = FALSE;
700     bool suppress_untranslatable = FALSE;
701     int quickdump = 0;
702     bool quiet = FALSE;
703 
704     log_fp = stderr;
705 
706     _nc_progname = _nc_rootname(argv[0]);
707     atexit(cleanup);
708 
709     if ((infodump = same_program(_nc_progname, PROG_CAPTOINFO)) != FALSE) {
710 	outform = F_TERMINFO;
711 	sortmode = S_TERMINFO;
712     }
713     if ((capdump = same_program(_nc_progname, PROG_INFOTOCAP)) != FALSE) {
714 	outform = F_TERMCAP;
715 	sortmode = S_TERMCAP;
716     }
717 #if NCURSES_XNAMES
718     use_extended_names(FALSE);
719 #endif
720     _nc_strict_bsd = 0;
721 
722     /*
723      * Processing arguments is a little complicated, since someone made a
724      * design decision to allow the numeric values for -w, -v options to
725      * be optional.
726      */
727     while ((this_opt = getopt(argc, argv,
728 			      "0123456789CDIKLNQR:TUVace:fGgo:qrstvwx")) != -1) {
729 	if (isdigit(this_opt)) {
730 	    switch (last_opt) {
731 	    case 'Q':
732 		add_digit(&quickdump, this_opt);
733 		break;
734 	    case 'v':
735 		add_digit(&v_opt, this_opt);
736 		break;
737 	    case 'w':
738 		add_digit(&width, this_opt);
739 		break;
740 	    default:
741 		switch (this_opt) {
742 		case '0':
743 		    last_opt = this_opt;
744 		    width = 65535;
745 		    height = 1;
746 		    break;
747 		case '1':
748 		    last_opt = this_opt;
749 		    width = 0;
750 		    break;
751 		default:
752 		    usage();
753 		}
754 	    }
755 	    continue;
756 	}
757 	switch (this_opt) {
758 	case 'K':
759 	    _nc_strict_bsd = 1;
760 	    /* the initial version of -K in 20110730 fell-thru here, but the
761 	     * same flag is useful when reading sources -TD
762 	     */
763 	    break;
764 	case 'C':
765 	    capdump = TRUE;
766 	    outform = F_TERMCAP;
767 	    sortmode = S_TERMCAP;
768 	    break;
769 	case 'D':
770 	    debug_level = VtoTrace(v_opt);
771 	    set_trace_level(debug_level);
772 	    show_databases(outdir);
773 	    ExitProgram(EXIT_SUCCESS);
774 	    break;
775 	case 'I':
776 	    infodump = TRUE;
777 	    outform = F_TERMINFO;
778 	    sortmode = S_TERMINFO;
779 	    break;
780 	case 'L':
781 	    infodump = TRUE;
782 	    outform = F_VARIABLE;
783 	    sortmode = S_VARIABLE;
784 	    break;
785 	case 'N':
786 	    smart_defaults = FALSE;
787 	    literal = TRUE;
788 	    break;
789 	case 'Q':
790 	    quickdump = 0;
791 	    break;
792 	case 'R':
793 	    tversion = optarg;
794 	    break;
795 	case 'T':
796 	    limited = FALSE;
797 	    break;
798 	case 'U':
799 	    literal = TRUE;
800 	    break;
801 	case 'V':
802 	    puts(curses_version());
803 	    ExitProgram(EXIT_SUCCESS);
804 	case 'c':
805 	    check_only = TRUE;
806 	    break;
807 	case 'e':
808 	    namelst = make_namelist(optarg);
809 	    break;
810 	case 'f':
811 	    formatted = TRUE;
812 	    break;
813 	case 'G':
814 	    numbers = 1;
815 	    break;
816 	case 'g':
817 	    numbers = -1;
818 	    break;
819 	case 'o':
820 	    outdir = optarg;
821 	    break;
822 	case 'q':
823 	    quiet = TRUE;
824 	    break;
825 	case 'r':
826 	    forceresolve = TRUE;
827 	    break;
828 	case 's':
829 	    showsummary = TRUE;
830 	    break;
831 	case 'v':
832 	    v_opt = 0;
833 	    break;
834 	case 'w':
835 	    width = 0;
836 	    break;
837 #if NCURSES_XNAMES
838 	case 't':
839 	    _nc_disable_period = FALSE;
840 	    suppress_untranslatable = TRUE;
841 	    break;
842 	case 'a':
843 	    _nc_disable_period = TRUE;
844 	    /* FALLTHRU */
845 	case 'x':
846 	    use_extended_names(TRUE);
847 	    break;
848 #endif
849 	default:
850 	    usage();
851 	}
852 	last_opt = this_opt;
853     }
854 
855     debug_level = VtoTrace(v_opt);
856     set_trace_level(debug_level);
857 
858     if (_nc_tracing) {
859 	save_check_termtype = _nc_check_termtype2;
860 	_nc_check_termtype2 = check_termtype;
861     }
862 #if !HAVE_BIG_CORE
863     /*
864      * Aaargh! immedhook seriously hoses us!
865      *
866      * One problem with immedhook is it means we can't do -e.  Problem
867      * is that we can't guarantee that for each terminal listed, all the
868      * terminals it depends on will have been kept in core for reference
869      * resolution -- in fact it's certain the primitive types at the end
870      * of reference chains *won't* be in core unless they were explicitly
871      * in the select list themselves.
872      */
873     if (namelst && (!infodump && !capdump)) {
874 	(void) fprintf(stderr,
875 		       "%s: Sorry, -e can't be used without -I or -C\n",
876 		       _nc_progname);
877 	ExitProgram(EXIT_FAILURE);
878     }
879 #endif /* HAVE_BIG_CORE */
880 
881     if (optind < argc) {
882 	source_file = argv[optind++];
883 	if (optind < argc) {
884 	    fprintf(stderr,
885 		    "%s: Too many file names.  Usage:\n\t%s %s",
886 		    _nc_progname,
887 		    _nc_progname,
888 		    usage_string);
889 	    ExitProgram(EXIT_FAILURE);
890 	}
891     } else {
892 	if (infodump == TRUE) {
893 	    /* captoinfo's no-argument case */
894 	    source_file = "/etc/termcap";
895 	    if ((termcap = getenv("TERMCAP")) != 0
896 		&& (namelst = make_namelist(getenv("TERM"))) != 0) {
897 		if (access(termcap, F_OK) == 0) {
898 		    /* file exists */
899 		    source_file = termcap;
900 		} else {
901 		    if ((tmp_fp = open_tempfile(my_tmpname)) != 0) {
902 			source_file = my_tmpname;
903 			fprintf(tmp_fp, "%s\n", termcap);
904 			fclose(tmp_fp);
905 			tmp_fp = open_input(source_file, (char *) 0);
906 			to_remove = source_file;
907 		    } else {
908 			failed("tmpnam");
909 		    }
910 		}
911 	    }
912 	} else {
913 	    /* tic */
914 	    fprintf(stderr,
915 		    "%s: File name needed.  Usage:\n\t%s %s",
916 		    _nc_progname,
917 		    _nc_progname,
918 		    usage_string);
919 	    ExitProgram(EXIT_FAILURE);
920 	}
921     }
922 
923     if (tmp_fp == 0) {
924 	tmp_fp = open_input(source_file, my_altfile);
925 	if (!strcmp(source_file, "-")) {
926 	    source_file = STDIN_NAME;
927 	}
928     }
929 
930     if (infodump || check_only) {
931 	dump_init(tversion,
932 		  smart_defaults
933 		  ? outform
934 		  : F_LITERAL,
935 		  sortmode, width, height, debug_level, formatted ||
936 		  check_only, check_only, quickdump);
937     } else if (capdump) {
938 	dump_init(tversion,
939 		  outform,
940 		  sortmode, width, height, debug_level, FALSE, FALSE, FALSE);
941     }
942 
943     /* parse entries out of the source file */
944     _nc_set_source(source_file);
945 #if !HAVE_BIG_CORE
946     if (!(check_only || infodump || capdump))
947 	_nc_set_writedir(outdir);
948 #endif /* HAVE_BIG_CORE */
949     _nc_read_entry_source(tmp_fp, (char *) NULL,
950 			  !smart_defaults || literal, FALSE,
951 			  ((check_only || infodump || capdump)
952 			   ? NULLHOOK
953 			   : immedhook));
954 
955     /* do use resolution */
956     if (check_only || (!infodump && !capdump) || forceresolve) {
957 	if (!_nc_resolve_uses2(TRUE, literal) && !check_only) {
958 	    ExitProgram(EXIT_FAILURE);
959 	}
960     }
961 
962     /* length check */
963     if (check_only && limited && (capdump || infodump)) {
964 	for_entry_list(qp) {
965 	    if (matches(namelst, qp->tterm.term_names)) {
966 		int len = fmt_entry(&qp->tterm, NULL, FALSE, TRUE, infodump, numbers);
967 
968 		if (len > (infodump ? MAX_TERMINFO_LENGTH : MAX_TERMCAP_LENGTH))
969 		    (void) fprintf(stderr,
970 				   "warning: resolved %s entry is %d bytes long\n",
971 				   _nc_first_name(qp->tterm.term_names),
972 				   len);
973 	    }
974 	}
975     }
976 
977     /* write or dump all entries */
978     if (check_only) {
979 	/* this is in case infotocap() generates warnings */
980 	_nc_curr_col = _nc_curr_line = -1;
981 
982 	for_entry_list(qp) {
983 	    if (matches(namelst, qp->tterm.term_names)) {
984 		/* this is in case infotocap() generates warnings */
985 		_nc_set_type(_nc_first_name(qp->tterm.term_names));
986 		_nc_curr_line = (int) qp->startline;
987 		repair_acsc(&qp->tterm);
988 		dump_entry(&qp->tterm, suppress_untranslatable,
989 			   limited, numbers, NULL);
990 	    }
991 	}
992     } else {
993 	if (!infodump && !capdump) {
994 	    _nc_set_writedir(outdir);
995 	    for_entry_list(qp) {
996 		if (matches(namelst, qp->tterm.term_names))
997 		    write_it(qp);
998 	    }
999 	} else {
1000 	    /* this is in case infotocap() generates warnings */
1001 	    _nc_curr_col = _nc_curr_line = -1;
1002 
1003 	    for_entry_list(qp) {
1004 		if (matches(namelst, qp->tterm.term_names)) {
1005 		    long j = qp->cend - qp->cstart;
1006 		    int len = 0;
1007 
1008 		    /* this is in case infotocap() generates warnings */
1009 		    _nc_set_type(_nc_first_name(qp->tterm.term_names));
1010 
1011 		    if (!quiet) {
1012 			(void) fseek(tmp_fp, qp->cstart, SEEK_SET);
1013 			while (j-- > 0) {
1014 			    if (infodump)
1015 				(void) putchar(fgetc(tmp_fp));
1016 			    else
1017 				put_translate(fgetc(tmp_fp));
1018 			}
1019 		    }
1020 
1021 		    repair_acsc(&qp->tterm);
1022 		    dump_entry(&qp->tterm, suppress_untranslatable,
1023 			       limited, numbers, NULL);
1024 		    for (j = 0; j < (long) qp->nuses; j++)
1025 			dump_uses(qp->uses[j].name, !capdump);
1026 		    len = show_entry();
1027 		    if (debug_level != 0 && !limited)
1028 			printf("# length=%d\n", len);
1029 		}
1030 	    }
1031 	    if (!namelst && _nc_tail && !quiet) {
1032 		int c, oldc = '\0';
1033 		bool in_comment = FALSE;
1034 		bool trailing_comment = FALSE;
1035 
1036 		(void) fseek(tmp_fp, _nc_tail->cend, SEEK_SET);
1037 		while ((c = fgetc(tmp_fp)) != EOF) {
1038 		    if (oldc == '\n') {
1039 			if (c == '#') {
1040 			    trailing_comment = TRUE;
1041 			    in_comment = TRUE;
1042 			} else {
1043 			    in_comment = FALSE;
1044 			}
1045 		    }
1046 		    if (trailing_comment
1047 			&& (in_comment || (oldc == '\n' && c == '\n')))
1048 			putchar(c);
1049 		    oldc = c;
1050 		}
1051 	    }
1052 	}
1053     }
1054 
1055     /* Show the directory into which entries were written, and the total
1056      * number of entries
1057      */
1058     if (showsummary
1059 	&& (!(check_only || infodump || capdump))) {
1060 	int total = _nc_tic_written();
1061 	if (total != 0)
1062 	    fprintf(log_fp, "%d entries written to %s\n",
1063 		    total,
1064 		    _nc_tic_dir((char *) 0));
1065 	else
1066 	    fprintf(log_fp, "No entries written\n");
1067     }
1068     ExitProgram(EXIT_SUCCESS);
1069 }
1070 
1071 /*
1072  * This bit of legerdemain turns all the terminfo variable names into
1073  * references to locations in the arrays Booleans, Numbers, and Strings ---
1074  * precisely what's needed (see comp_parse.c).
1075  */
1076 #undef CUR
1077 #define CUR tp->
1078 
1079 /*
1080  * Check if the alternate character-set capabilities are consistent.
1081  */
1082 static void
1083 check_acs(TERMTYPE *tp)
1084 {
1085     if (VALID_STRING(acs_chars)) {
1086 	const char *boxes = "lmkjtuvwqxn";
1087 	char mapped[256];
1088 	char missing[256];
1089 	const char *p;
1090 	char *q;
1091 
1092 	memset(mapped, 0, sizeof(mapped));
1093 	for (p = acs_chars; *p != '\0'; p += 2) {
1094 	    if (p[1] == '\0') {
1095 		_nc_warning("acsc has odd number of characters");
1096 		break;
1097 	    }
1098 	    mapped[UChar(p[0])] = p[1];
1099 	}
1100 
1101 	if (mapped[UChar('I')] && !mapped[UChar('i')]) {
1102 	    _nc_warning("acsc refers to 'I', which is probably an error");
1103 	}
1104 
1105 	for (p = boxes, q = missing; *p != '\0'; ++p) {
1106 	    if (!mapped[UChar(p[0])]) {
1107 		*q++ = p[0];
1108 	    }
1109 	}
1110 	*q = '\0';
1111 
1112 	assert(strlen(missing) <= strlen(boxes));
1113 	if (*missing != '\0' && strcmp(missing, boxes)) {
1114 	    _nc_warning("acsc is missing some line-drawing mapping: %s", missing);
1115 	}
1116     }
1117 }
1118 
1119 /*
1120  * Check if the color capabilities are consistent
1121  */
1122 static void
1123 check_colors(TERMTYPE *tp)
1124 {
1125     if ((max_colors > 0) != (max_pairs > 0)
1126 	|| ((max_colors > max_pairs) && (initialize_pair == 0)))
1127 	_nc_warning("inconsistent values for max_colors (%d) and max_pairs (%d)",
1128 		    max_colors, max_pairs);
1129 
1130     PAIRED(set_foreground, set_background);
1131     PAIRED(set_a_foreground, set_a_background);
1132     PAIRED(set_color_pair, initialize_pair);
1133 
1134     if (VALID_STRING(set_foreground)
1135 	&& VALID_STRING(set_a_foreground)
1136 	&& !_nc_capcmp(set_foreground, set_a_foreground))
1137 	_nc_warning("expected setf/setaf to be different");
1138 
1139     if (VALID_STRING(set_background)
1140 	&& VALID_STRING(set_a_background)
1141 	&& !_nc_capcmp(set_background, set_a_background))
1142 	_nc_warning("expected setb/setab to be different");
1143 
1144     /* see: has_colors() */
1145     if (VALID_NUMERIC(max_colors) && VALID_NUMERIC(max_pairs)
1146 	&& (((set_foreground != NULL)
1147 	     && (set_background != NULL))
1148 	    || ((set_a_foreground != NULL)
1149 		&& (set_a_background != NULL))
1150 	    || set_color_pair)) {
1151 	if (!VALID_STRING(orig_pair) && !VALID_STRING(orig_colors))
1152 	    _nc_warning("expected either op/oc string for resetting colors");
1153     }
1154     if (can_change) {
1155 	if (!VALID_STRING(initialize_pair) &&
1156 	    !VALID_STRING(initialize_color)) {
1157 	    _nc_warning("expected initc or initp because ccc is given");
1158 	}
1159     } else {
1160 	if (VALID_STRING(initialize_pair) ||
1161 	    VALID_STRING(initialize_color)) {
1162 	    _nc_warning("expected ccc because initc is given");
1163 	}
1164     }
1165 }
1166 
1167 static char
1168 keypad_final(const char *string)
1169 {
1170     char result = '\0';
1171 
1172     if (VALID_STRING(string)
1173 	&& *string++ == '\033'
1174 	&& *string++ == 'O'
1175 	&& strlen(string) == 1) {
1176 	result = *string;
1177     }
1178 
1179     return result;
1180 }
1181 
1182 static long
1183 keypad_index(const char *string)
1184 {
1185     char *test;
1186     const char *list = "PQRSwxymtuvlqrsPpn";	/* app-keypad except "Enter" */
1187     int ch;
1188     long result = -1;
1189 
1190     if ((ch = keypad_final(string)) != '\0') {
1191 	test = (strchr) (list, ch);
1192 	if (test != 0)
1193 	    result = (long) (test - list);
1194     }
1195     return result;
1196 }
1197 
1198 /*
1199  * list[] is down, up, left, right
1200  * "left" may be ^H rather than \E[D
1201  * "down" may be ^J rather than \E[B
1202  * But up/right are generally consistently escape sequences for ANSI terminals.
1203  */
1204 static void
1205 check_ansi_cursor(char *list[4])
1206 {
1207     int j, k;
1208     int want;
1209     size_t prefix = 0;
1210     size_t suffix;
1211     bool skip[4];
1212     bool repeated = FALSE;
1213 
1214     for (j = 0; j < 4; ++j) {
1215 	skip[j] = FALSE;
1216 	for (k = 0; k < j; ++k) {
1217 	    if (j != k
1218 		&& !strcmp(list[j], list[k])) {
1219 		char *value = _nc_tic_expand(list[k], TRUE, 0);
1220 		_nc_warning("repeated cursor control %s\n", value);
1221 		repeated = TRUE;
1222 	    }
1223 	}
1224     }
1225     if (!repeated) {
1226 	char *up = list[1];
1227 
1228 	if (UChar(up[0]) == '\033') {
1229 	    if (up[1] == '[') {
1230 		prefix = 2;
1231 	    } else {
1232 		prefix = 1;
1233 	    }
1234 	} else if (UChar(up[0]) == UChar('\233')) {
1235 	    prefix = 1;
1236 	}
1237 	if (prefix) {
1238 	    suffix = prefix;
1239 	    while (up[suffix] && isdigit(UChar(up[suffix])))
1240 		++suffix;
1241 	}
1242 	if (prefix && up[suffix] == 'A') {
1243 	    skip[1] = TRUE;
1244 	    if (!strcmp(list[0], "\n"))
1245 		skip[0] = TRUE;
1246 	    if (!strcmp(list[2], "\b"))
1247 		skip[2] = TRUE;
1248 
1249 	    for (j = 0; j < 4; ++j) {
1250 		if (skip[j] || strlen(list[j]) == 1)
1251 		    continue;
1252 		if (memcmp(list[j], up, prefix)) {
1253 		    char *value = _nc_tic_expand(list[j], TRUE, 0);
1254 		    _nc_warning("inconsistent prefix for %s\n", value);
1255 		    continue;
1256 		}
1257 		if (strlen(list[j]) < suffix) {
1258 		    char *value = _nc_tic_expand(list[j], TRUE, 0);
1259 		    _nc_warning("inconsistent length for %s, expected %d\n",
1260 				value, (int) suffix + 1);
1261 		    continue;
1262 		}
1263 		want = "BADC"[j];
1264 		if (list[j][suffix] != want) {
1265 		    char *value = _nc_tic_expand(list[j], TRUE, 0);
1266 		    _nc_warning("inconsistent suffix for %s, expected %c, have %c\n",
1267 				value, want, list[j][suffix]);
1268 		}
1269 	    }
1270 	}
1271     }
1272 }
1273 
1274 #define EXPECTED(name) if (!PRESENT(name)) _nc_warning("expected " #name)
1275 #define UNEXPECTED(name) if (PRESENT(name)) _nc_warning("unexpected " #name ", for %s", why)
1276 
1277 static void
1278 check_noaddress(TERMTYPE *tp, const char *why)
1279 {
1280     UNEXPECTED(column_address);
1281     UNEXPECTED(cursor_address);
1282     UNEXPECTED(cursor_home);
1283     UNEXPECTED(cursor_mem_address);
1284     UNEXPECTED(cursor_to_ll);
1285     UNEXPECTED(row_address);
1286     UNEXPECTED(row_address);
1287 }
1288 
1289 static void
1290 check_cursor(TERMTYPE *tp)
1291 {
1292     int count;
1293     char *list[4];
1294 
1295     if (hard_copy) {
1296 	check_noaddress(tp, "hard_copy");
1297     } else if (generic_type) {
1298 	check_noaddress(tp, "generic_type");
1299     } else if (strchr(tp->term_names, '+') == 0) {
1300 	int y = 0;
1301 	int x = 0;
1302 	if (PRESENT(column_address))
1303 	    ++y;
1304 	if (PRESENT(cursor_address))
1305 	    y = x = 10;
1306 	if (PRESENT(cursor_home))
1307 	    ++y, ++x;
1308 	if (PRESENT(cursor_mem_address))
1309 	    y = x = 10;
1310 	if (PRESENT(cursor_to_ll))
1311 	    ++y, ++x;
1312 	if (PRESENT(row_address))
1313 	    ++x;
1314 	if (PRESENT(cursor_down))
1315 	    ++y;
1316 	if (PRESENT(cursor_up))
1317 	    ++y;
1318 	if (PRESENT(cursor_left))
1319 	    ++x;
1320 	if (PRESENT(cursor_right))
1321 	    ++x;
1322 	if (x < 2 && y < 2) {
1323 	    _nc_warning("terminal lacks cursor addressing");
1324 	} else {
1325 	    if (x < 2)
1326 		_nc_warning("terminal lacks cursor column-addressing");
1327 	    if (y < 2)
1328 		_nc_warning("terminal lacks cursor row-addressing");
1329 	}
1330     }
1331 
1332     /* it is rare to have an insert-line feature without a matching delete */
1333     ANDMISSING(parm_insert_line, insert_line);
1334     ANDMISSING(parm_delete_line, delete_line);
1335     ANDMISSING(parm_insert_line, parm_delete_line);
1336 
1337     /* if we have a parameterized form, then the non-parameterized is easy */
1338     ANDMISSING(parm_down_cursor, cursor_down);
1339     ANDMISSING(parm_up_cursor, cursor_up);
1340     ANDMISSING(parm_left_cursor, cursor_left);
1341     ANDMISSING(parm_right_cursor, cursor_right);
1342 
1343     /* Given any of a set of cursor movement, the whole set should be present.
1344      * Technically this is not true (we could use cursor_address to fill in
1345      * unsupported controls), but it is likely.
1346      */
1347     count = 0;
1348     if (PRESENT(parm_down_cursor)) {
1349 	list[count++] = parm_down_cursor;
1350     }
1351     if (PRESENT(parm_up_cursor)) {
1352 	list[count++] = parm_up_cursor;
1353     }
1354     if (PRESENT(parm_left_cursor)) {
1355 	list[count++] = parm_left_cursor;
1356     }
1357     if (PRESENT(parm_right_cursor)) {
1358 	list[count++] = parm_right_cursor;
1359     }
1360     if (count == 4) {
1361 	check_ansi_cursor(list);
1362     } else if (count != 0) {
1363 	EXPECTED(parm_down_cursor);
1364 	EXPECTED(parm_up_cursor);
1365 	EXPECTED(parm_left_cursor);
1366 	EXPECTED(parm_right_cursor);
1367     }
1368 
1369     count = 0;
1370     if (PRESENT(cursor_down)) {
1371 	list[count++] = cursor_down;
1372     }
1373     if (PRESENT(cursor_up)) {
1374 	list[count++] = cursor_up;
1375     }
1376     if (PRESENT(cursor_left)) {
1377 	list[count++] = cursor_left;
1378     }
1379     if (PRESENT(cursor_right)) {
1380 	list[count++] = cursor_right;
1381     }
1382     if (count == 4) {
1383 	check_ansi_cursor(list);
1384     } else if (count != 0) {
1385 	count = 0;
1386 	if (PRESENT(cursor_down) && strcmp(cursor_down, "\n"))
1387 	    ++count;
1388 	if (PRESENT(cursor_left) && strcmp(cursor_left, "\b"))
1389 	    ++count;
1390 	if (PRESENT(cursor_up) && strlen(cursor_up) > 1)
1391 	    ++count;
1392 	if (PRESENT(cursor_right) && strlen(cursor_right) > 1)
1393 	    ++count;
1394 	if (count) {
1395 	    EXPECTED(cursor_down);
1396 	    EXPECTED(cursor_up);
1397 	    EXPECTED(cursor_left);
1398 	    EXPECTED(cursor_right);
1399 	}
1400     }
1401 }
1402 
1403 #define MAX_KP 5
1404 /*
1405  * Do a quick sanity-check for vt100-style keypads to see if the 5-key keypad
1406  * is mapped inconsistently.
1407  */
1408 static void
1409 check_keypad(TERMTYPE *tp)
1410 {
1411     char show[80];
1412 
1413     if (VALID_STRING(key_a1) &&
1414 	VALID_STRING(key_a3) &&
1415 	VALID_STRING(key_b2) &&
1416 	VALID_STRING(key_c1) &&
1417 	VALID_STRING(key_c3)) {
1418 	char final[MAX_KP + 1];
1419 	long list[MAX_KP];
1420 	int increase = 0;
1421 	int j, k, kk;
1422 	long last;
1423 	long test;
1424 
1425 	final[0] = keypad_final(key_a1);
1426 	final[1] = keypad_final(key_a3);
1427 	final[2] = keypad_final(key_b2);
1428 	final[3] = keypad_final(key_c1);
1429 	final[4] = keypad_final(key_c3);
1430 	final[5] = '\0';
1431 
1432 	/* special case: legacy coding using 1,2,3,0,. on the bottom */
1433 	assert(strlen(final) <= MAX_KP);
1434 	if (!strcmp(final, "qsrpn"))
1435 	    return;
1436 
1437 	list[0] = keypad_index(key_a1);
1438 	list[1] = keypad_index(key_a3);
1439 	list[2] = keypad_index(key_b2);
1440 	list[3] = keypad_index(key_c1);
1441 	list[4] = keypad_index(key_c3);
1442 
1443 	/* check that they're all vt100 keys */
1444 	for (j = 0; j < MAX_KP; ++j) {
1445 	    if (list[j] < 0) {
1446 		return;
1447 	    }
1448 	}
1449 
1450 	/* check if they're all in increasing order */
1451 	for (j = 1; j < MAX_KP; ++j) {
1452 	    if (list[j] > list[j - 1]) {
1453 		++increase;
1454 	    }
1455 	}
1456 	if (increase != (MAX_KP - 1)) {
1457 	    show[0] = '\0';
1458 
1459 	    for (j = 0, last = -1; j < MAX_KP; ++j) {
1460 		for (k = 0, kk = -1, test = 100; k < 5; ++k) {
1461 		    if (list[k] > last &&
1462 			list[k] < test) {
1463 			test = list[k];
1464 			kk = k;
1465 		    }
1466 		}
1467 		last = test;
1468 		assert(strlen(show) < (MAX_KP * 4));
1469 		switch (kk) {
1470 		case 0:
1471 		    _nc_STRCAT(show, " ka1", sizeof(show));
1472 		    break;
1473 		case 1:
1474 		    _nc_STRCAT(show, " ka3", sizeof(show));
1475 		    break;
1476 		case 2:
1477 		    _nc_STRCAT(show, " kb2", sizeof(show));
1478 		    break;
1479 		case 3:
1480 		    _nc_STRCAT(show, " kc1", sizeof(show));
1481 		    break;
1482 		case 4:
1483 		    _nc_STRCAT(show, " kc3", sizeof(show));
1484 		    break;
1485 		}
1486 	    }
1487 
1488 	    _nc_warning("vt100 keypad order inconsistent: %s", show);
1489 	}
1490 
1491     } else if (VALID_STRING(key_a1) ||
1492 	       VALID_STRING(key_a3) ||
1493 	       VALID_STRING(key_b2) ||
1494 	       VALID_STRING(key_c1) ||
1495 	       VALID_STRING(key_c3)) {
1496 	show[0] = '\0';
1497 	if (keypad_index(key_a1) >= 0)
1498 	    _nc_STRCAT(show, " ka1", sizeof(show));
1499 	if (keypad_index(key_a3) >= 0)
1500 	    _nc_STRCAT(show, " ka3", sizeof(show));
1501 	if (keypad_index(key_b2) >= 0)
1502 	    _nc_STRCAT(show, " kb2", sizeof(show));
1503 	if (keypad_index(key_c1) >= 0)
1504 	    _nc_STRCAT(show, " kc1", sizeof(show));
1505 	if (keypad_index(key_c3) >= 0)
1506 	    _nc_STRCAT(show, " kc3", sizeof(show));
1507 	if (*show != '\0')
1508 	    _nc_warning("vt100 keypad map incomplete:%s", show);
1509     }
1510 
1511     /*
1512      * These warnings are useful for consistency checks - it is possible that
1513      * there are real terminals with mismatches in these
1514      */
1515     ANDMISSING(key_ic, key_dc);
1516 }
1517 
1518 static void
1519 check_printer(TERMTYPE *tp)
1520 {
1521     PAIRED(enter_doublewide_mode, exit_doublewide_mode);
1522     PAIRED(enter_italics_mode, exit_italics_mode);
1523     PAIRED(enter_leftward_mode, exit_leftward_mode);
1524     PAIRED(enter_micro_mode, exit_micro_mode);
1525     PAIRED(enter_shadow_mode, exit_shadow_mode);
1526     PAIRED(enter_subscript_mode, exit_subscript_mode);
1527     PAIRED(enter_superscript_mode, exit_superscript_mode);
1528     PAIRED(enter_upward_mode, exit_upward_mode);
1529 
1530     ANDMISSING(start_char_set_def, stop_char_set_def);
1531 
1532     /* if we have a parameterized form, then the non-parameterized is easy */
1533     ANDMISSING(set_bottom_margin_parm, set_bottom_margin);
1534     ANDMISSING(set_left_margin_parm, set_left_margin);
1535     ANDMISSING(set_right_margin_parm, set_right_margin);
1536     ANDMISSING(set_top_margin_parm, set_top_margin);
1537 
1538     ANDMISSING(parm_down_micro, micro_down);
1539     ANDMISSING(parm_left_micro, micro_left);
1540     ANDMISSING(parm_right_micro, micro_right);
1541     ANDMISSING(parm_up_micro, micro_up);
1542 }
1543 
1544 static bool
1545 uses_SGR_39_49(const char *value)
1546 {
1547     return (strstr(value, "39;49") != 0
1548 	    || strstr(value, "49;39") != 0);
1549 }
1550 
1551 /*
1552  * Check consistency of termcap extensions related to "screen".
1553  */
1554 static void
1555 check_screen(TERMTYPE *tp)
1556 {
1557 #if NCURSES_XNAMES
1558     if (_nc_user_definable) {
1559 	int have_XT = tigetflag("XT");
1560 	int have_XM = tigetflag("XM");
1561 	int have_bce = back_color_erase;
1562 	bool have_kmouse = FALSE;
1563 	bool use_sgr_39_49 = FALSE;
1564 	char *name = _nc_first_name(tp->term_names);
1565 
1566 	if (!VALID_BOOLEAN(have_bce)) {
1567 	    have_bce = FALSE;
1568 	}
1569 	if (!VALID_BOOLEAN(have_XM)) {
1570 	    have_XM = FALSE;
1571 	}
1572 	if (!VALID_BOOLEAN(have_XT)) {
1573 	    have_XT = FALSE;
1574 	}
1575 	if (VALID_STRING(key_mouse)) {
1576 	    have_kmouse = !strcmp("\033[M", key_mouse);
1577 	}
1578 	if (VALID_STRING(orig_colors)) {
1579 	    use_sgr_39_49 = uses_SGR_39_49(orig_colors);
1580 	} else if (VALID_STRING(orig_pair)) {
1581 	    use_sgr_39_49 = uses_SGR_39_49(orig_pair);
1582 	}
1583 
1584 	if (have_XM && have_XT) {
1585 	    _nc_warning("Screen's XT capability conflicts with XM");
1586 	} else if (have_XT
1587 		   && strstr(name, "screen") != 0
1588 		   && strchr(name, '.') != 0) {
1589 	    _nc_warning("Screen's \"screen\" entries should not have XT set");
1590 	} else if (have_XT) {
1591 	    if (!have_kmouse && have_bce) {
1592 		if (VALID_STRING(key_mouse)) {
1593 		    _nc_warning("Value of kmous inconsistent with screen's usage");
1594 		} else {
1595 		    _nc_warning("Expected kmous capability with XT");
1596 		}
1597 	    }
1598 	    if (!have_bce && max_colors > 0)
1599 		_nc_warning("Expected bce capability with XT");
1600 	    if (!use_sgr_39_49 && have_bce && max_colors > 0)
1601 		_nc_warning("Expected orig_colors capability with XT to have 39/49 parameters");
1602 	    if (VALID_STRING(to_status_line))
1603 		_nc_warning("\"tsl\" capability is redundant, given XT");
1604 	} else {
1605 	    if (have_kmouse && !have_XM)
1606 		_nc_warning("Expected XT to be set, given kmous");
1607 	}
1608     }
1609 #endif
1610 }
1611 
1612 /*
1613  * Returns the expected number of parameters for the given capability.
1614  */
1615 static int
1616 expected_params(const char *name)
1617 {
1618 #define DATA(name,count) { { name }, count }
1619     /* *INDENT-OFF* */
1620     static const struct {
1621 	const char name[9];
1622 	int count;
1623     } table[] = {
1624 	DATA( "S0",		1 ),	/* 'screen' extension */
1625 	DATA( "birep",		2 ),
1626 	DATA( "chr",		1 ),
1627 	DATA( "colornm",	1 ),
1628 	DATA( "cpi",		1 ),
1629 	DATA( "csnm",		1 ),
1630 	DATA( "csr",		2 ),
1631 	DATA( "cub",		1 ),
1632 	DATA( "cud",		1 ),
1633 	DATA( "cuf",		1 ),
1634 	DATA( "cup",		2 ),
1635 	DATA( "cuu",		1 ),
1636 	DATA( "cvr",		1 ),
1637 	DATA( "cwin",		5 ),
1638 	DATA( "dch",		1 ),
1639 	DATA( "defc",		3 ),
1640 	DATA( "dial",		1 ),
1641 	DATA( "dispc",		1 ),
1642 	DATA( "dl",		1 ),
1643 	DATA( "ech",		1 ),
1644 	DATA( "getm",		1 ),
1645 	DATA( "hpa",		1 ),
1646 	DATA( "ich",		1 ),
1647 	DATA( "il",		1 ),
1648 	DATA( "indn",		1 ),
1649 	DATA( "initc",		4 ),
1650 	DATA( "initp",		7 ),
1651 	DATA( "lpi",		1 ),
1652 	DATA( "mc5p",		1 ),
1653 	DATA( "mrcup",		2 ),
1654 	DATA( "mvpa",		1 ),
1655 	DATA( "pfkey",		2 ),
1656 	DATA( "pfloc",		2 ),
1657 	DATA( "pfx",		2 ),
1658 	DATA( "pfxl",		3 ),
1659 	DATA( "pln",		2 ),
1660 	DATA( "qdial",		1 ),
1661 	DATA( "rcsd",		1 ),
1662 	DATA( "rep",		2 ),
1663 	DATA( "rin",		1 ),
1664 	DATA( "sclk",		3 ),
1665 	DATA( "scp",		1 ),
1666 	DATA( "scs",		1 ),
1667 	DATA( "scsd",		2 ),
1668 	DATA( "setab",		1 ),
1669 	DATA( "setaf",		1 ),
1670 	DATA( "setb",		1 ),
1671 	DATA( "setcolor",	1 ),
1672 	DATA( "setf",		1 ),
1673 	DATA( "sgr",		9 ),
1674 	DATA( "sgr1",		6 ),
1675 	DATA( "slength",	1 ),
1676 	DATA( "slines",		1 ),
1677 	DATA( "smgbp",		1 ),	/* 2 if smgtp is not given */
1678 	DATA( "smglp",		1 ),
1679 	DATA( "smglr",		2 ),
1680 	DATA( "smgrp",		1 ),
1681 	DATA( "smgtb",		2 ),
1682 	DATA( "smgtp",		1 ),
1683 	DATA( "tsl",		1 ),
1684 	DATA( "u6",		-1 ),
1685 	DATA( "vpa",		1 ),
1686 	DATA( "wind",		4 ),
1687 	DATA( "wingo",		1 ),
1688     };
1689     /* *INDENT-ON* */
1690 
1691 #undef DATA
1692 
1693     unsigned n;
1694     int result = 0;		/* function-keys, etc., use none */
1695 
1696     for (n = 0; n < SIZEOF(table); n++) {
1697 	if (!strcmp(name, table[n].name)) {
1698 	    result = table[n].count;
1699 	    break;
1700 	}
1701     }
1702 
1703     return result;
1704 }
1705 
1706 /*
1707  * Make a quick sanity check for the parameters which are used in the given
1708  * strings.  If there are no "%p" tokens, then there should be no other "%"
1709  * markers.
1710  */
1711 static void
1712 check_params(TERMTYPE *tp, const char *name, char *value)
1713 {
1714     int expected = expected_params(name);
1715     int actual = 0;
1716     int n;
1717     bool params[NUM_PARM];
1718     char *s = value;
1719 
1720 #ifdef set_top_margin_parm
1721     if (!strcmp(name, "smgbp")
1722 	&& set_top_margin_parm == 0)
1723 	expected = 2;
1724 #endif
1725 
1726     for (n = 0; n < NUM_PARM; n++)
1727 	params[n] = FALSE;
1728 
1729     while (*s != 0) {
1730 	if (*s == '%') {
1731 	    if (*++s == '\0') {
1732 		_nc_warning("expected character after %% in %s", name);
1733 		break;
1734 	    } else if (*s == 'p') {
1735 		if (*++s == '\0' || !isdigit((int) *s)) {
1736 		    _nc_warning("expected digit after %%p in %s", name);
1737 		    return;
1738 		} else {
1739 		    n = (*s - '0');
1740 		    if (n > actual)
1741 			actual = n;
1742 		    params[n] = TRUE;
1743 		}
1744 	    }
1745 	}
1746 	s++;
1747     }
1748 
1749     if (params[0]) {
1750 	_nc_warning("%s refers to parameter 0 (%%p0), which is not allowed", name);
1751     }
1752     if (value == set_attributes || expected < 0) {
1753 	;
1754     } else if (expected != actual) {
1755 	_nc_warning("%s uses %d parameters, expected %d", name,
1756 		    actual, expected);
1757 	for (n = 1; n < actual; n++) {
1758 	    if (!params[n])
1759 		_nc_warning("%s omits parameter %d", name, n);
1760 	}
1761     }
1762 }
1763 
1764 static char *
1765 check_1_infotocap(const char *name, NCURSES_CONST char *value, int count)
1766 {
1767     int k;
1768     int ignored;
1769     long numbers[1 + NUM_PARM];
1770     char *strings[1 + NUM_PARM];
1771     char *p_is_s[NUM_PARM];
1772     char *result;
1773     char blob[NUM_PARM * 10];
1774     char *next = blob;
1775 
1776     *next++ = '\0';
1777     for (k = 1; k <= NUM_PARM; k++) {
1778 	numbers[k] = count;
1779 	sprintf(next, "XYZ%d", count);
1780 	strings[k] = next;
1781 	next += strlen(next) + 1;
1782     }
1783 
1784     switch (tparm_type(name)) {
1785     case Num_Str:
1786 	result = TPARM_2(value, numbers[1], strings[2]);
1787 	break;
1788     case Num_Str_Str:
1789 	result = TPARM_3(value, numbers[1], strings[2], strings[3]);
1790 	break;
1791     case Numbers:
1792     default:
1793 	(void) _nc_tparm_analyze(value, p_is_s, &ignored);
1794 #define myParam(n) (p_is_s[n - 1] != 0 ? ((TPARM_ARG) strings[n]) : numbers[n])
1795 	result = TPARM_9(value,
1796 			 myParam(1),
1797 			 myParam(2),
1798 			 myParam(3),
1799 			 myParam(4),
1800 			 myParam(5),
1801 			 myParam(6),
1802 			 myParam(7),
1803 			 myParam(8),
1804 			 myParam(9));
1805 	break;
1806     }
1807     return result;
1808 }
1809 
1810 #define IsDelay(ch) ((ch) == '.' || isdigit(UChar(ch)))
1811 
1812 static const char *
1813 parse_delay_value(const char *src, double *delays, int *always)
1814 {
1815     int star = 0;
1816 
1817     *delays = 0.0;
1818     if (always)
1819 	*always = 0;
1820 
1821     while (isdigit(UChar(*src))) {
1822 	(*delays) = (*delays) * 10 + (*src++ - '0');
1823     }
1824     if (*src == '.') {
1825 	int gotdot = 1;
1826 
1827 	++src;
1828 	while (isdigit(UChar(*src))) {
1829 	    gotdot *= 10;
1830 	    (*delays) += (*src++ - '0') / gotdot;
1831 	}
1832     }
1833     while (*src == '*' || *src == '/') {
1834 	if (always == 0 && *src == '/')
1835 	    break;
1836 	if (*src++ == '*') {
1837 	    star = 1;
1838 	} else {
1839 	    *always = 1;
1840 	}
1841     }
1842     if (star)
1843 	*delays = -(*delays);
1844     return src;
1845 }
1846 
1847 static const char *
1848 parse_ti_delay(const char *ti, double *delays)
1849 {
1850     *delays = 0.0;
1851     while (*ti != '\0') {
1852 	if (*ti == '\\') {
1853 	    ++ti;
1854 	}
1855 	if (ti[0] == '$'
1856 	    && ti[1] == '<'
1857 	    && IsDelay(UChar(ti[2]))) {
1858 	    int ignored;
1859 	    const char *last = parse_delay_value(ti + 2, delays, &ignored);
1860 	    if (*last == '>') {
1861 		ti = last;
1862 	    }
1863 	} else {
1864 	    ++ti;
1865 	}
1866     }
1867     return ti;
1868 }
1869 
1870 static const char *
1871 parse_tc_delay(const char *tc, double *delays)
1872 {
1873     return parse_delay_value(tc, delays, (int *) 0);
1874 }
1875 
1876 /*
1877  * Compare terminfo- and termcap-strings, factoring out delays.
1878  */
1879 static bool
1880 same_ti_tc(const char *ti, const char *tc, bool * embedded)
1881 {
1882     bool same = TRUE;
1883     double ti_delay = 0.0;
1884     double tc_delay = 0.0;
1885     const char *ti_last;
1886 
1887     *embedded = FALSE;
1888     ti_last = parse_ti_delay(ti, &ti_delay);
1889     tc = parse_tc_delay(tc, &tc_delay);
1890 
1891     while ((ti < ti_last) && *tc) {
1892 	if (*ti == '\\' && ispunct(UChar(ti[1]))) {
1893 	    ++ti;
1894 	    if ((*ti == '^') && !strncmp(tc, "\\136", 4)) {
1895 		ti += 1;
1896 		tc += 4;
1897 		continue;
1898 	    }
1899 	} else if (ti[0] == '$' && ti[1] == '<') {
1900 	    double no_delay;
1901 	    const char *ss = parse_ti_delay(ti, &no_delay);
1902 	    if (ss != ti) {
1903 		*embedded = TRUE;
1904 		ti = ss;
1905 		continue;
1906 	    }
1907 	}
1908 	if (*tc == '\\' && ispunct(UChar(tc[1]))) {
1909 	    ++tc;
1910 	}
1911 	if (*ti++ != *tc++) {
1912 	    same = FALSE;
1913 	    break;
1914 	}
1915     }
1916 
1917     if (*embedded) {
1918 	if (same) {
1919 	    same = FALSE;
1920 	} else {
1921 	    *embedded = FALSE;	/* report only one problem */
1922 	}
1923     }
1924 
1925     return same;
1926 }
1927 
1928 /*
1929  * Check terminfo to termcap translation.
1930  */
1931 static void
1932 check_infotocap(TERMTYPE *tp, int i, const char *value)
1933 {
1934     const char *name = ExtStrname(tp, i, strnames);
1935     int params = (((i < (int) SIZEOF(parametrized)) &&
1936 		   (i < STRCOUNT))
1937 		  ? parametrized[i]
1938 		  : ((*value == 'k')
1939 		     ? 0
1940 		     : has_params(value)));
1941     int to_char = 0;
1942     char *ti_value;
1943     char *tc_value;
1944     bool embedded;
1945 
1946     if ((ti_value = _nc_tic_expand(value, TRUE, to_char)) == ABSENT_STRING) {
1947 	_nc_warning("tic-expansion of %s failed", name);
1948     } else if ((tc_value = _nc_infotocap(name, ti_value, params)) == ABSENT_STRING) {
1949 	_nc_warning("tic-conversion of %s failed", name);
1950     } else if (params > 0) {
1951 	int limit = 5;
1952 	int count;
1953 	bool first = TRUE;
1954 
1955 	if (!strcmp(name, "setf")
1956 	    || !strcmp(name, "setb")
1957 	    || !strcmp(name, "setaf")
1958 	    || !strcmp(name, "setab")) {
1959 	    limit = max_colors;
1960 	}
1961 	for (count = 0; count < limit; ++count) {
1962 	    char *ti_check = check_1_infotocap(name, ti_value, count);
1963 	    char *tc_check = check_1_infotocap(name, tc_value, count);
1964 
1965 	    if (strcmp(ti_check, tc_check)) {
1966 		if (first) {
1967 		    fprintf(stderr, "check_infotocap(%s)\n", name);
1968 		    fprintf(stderr, "...ti '%s'\n", ti_value);
1969 		    fprintf(stderr, "...tc '%s'\n", tc_value);
1970 		    first = FALSE;
1971 		}
1972 		_nc_warning("tparm-conversion of %s(%d) differs between\n\tterminfo %s\n\ttermcap  %s",
1973 			    name, count, ti_check, tc_check);
1974 	    }
1975 	}
1976     } else if (params == 0 && !same_ti_tc(ti_value, tc_value, &embedded)) {
1977 	if (embedded) {
1978 	    _nc_warning("termcap equivalent of %s cannot use embedded delay", name);
1979 	} else {
1980 	    _nc_warning("tic-conversion of %s changed value\n\tfrom %s\n\tto   %s",
1981 			name, ti_value, tc_value);
1982 	}
1983     }
1984 }
1985 
1986 static char *
1987 skip_delay(char *s)
1988 {
1989     while (*s == '/' || isdigit(UChar(*s)))
1990 	++s;
1991     return s;
1992 }
1993 
1994 /*
1995  * Skip a delay altogether, e.g., when comparing a simple string to sgr,
1996  * the latter may have a worst-case delay on the end.
1997  */
1998 static char *
1999 ignore_delays(char *s)
2000 {
2001     int delaying = 0;
2002 
2003     do {
2004 	switch (*s) {
2005 	case '$':
2006 	    if (delaying == 0)
2007 		delaying = 1;
2008 	    break;
2009 	case '<':
2010 	    if (delaying == 1)
2011 		delaying = 2;
2012 	    break;
2013 	case '\0':
2014 	    delaying = 0;
2015 	    break;
2016 	default:
2017 	    if (delaying) {
2018 		s = skip_delay(s);
2019 		if (*s == '>')
2020 		    ++s;
2021 		delaying = 0;
2022 	    }
2023 	    break;
2024 	}
2025 	if (delaying)
2026 	    ++s;
2027     } while (delaying);
2028     return s;
2029 }
2030 
2031 #define DATA(name) { #name }
2032 static const char sgr_names[][11] =
2033 {
2034     DATA(none),
2035     DATA(standout),
2036     DATA(underline),
2037     DATA(reverse),
2038     DATA(blink),
2039     DATA(dim),
2040     DATA(bold),
2041     DATA(invis),
2042     DATA(protect),
2043     DATA(altcharset),
2044     ""
2045 };
2046 #undef DATA
2047 
2048 /*
2049  * An sgr string may contain several settings other than the one we're
2050  * interested in, essentially sgr0 + rmacs + whatever.  As long as the
2051  * "whatever" is contained in the sgr string, that is close enough for our
2052  * sanity check.
2053  */
2054 static bool
2055 similar_sgr(int num, char *a, char *b)
2056 {
2057     char *base_a = a;
2058     char *base_b = b;
2059     int delaying = 0;
2060 
2061     while (*b != 0) {
2062 	while (*a != *b) {
2063 	    if (*a == 0) {
2064 		if (num < 0) {
2065 		    ;
2066 		} else if (b[0] == '$'
2067 			   && b[1] == '<') {
2068 		    _nc_warning("Did not find delay %s", _nc_visbuf(b));
2069 		} else {
2070 		    _nc_warning("checking sgr(%s) %s\n\tcompare to %s\n\tunmatched %s",
2071 				sgr_names[num], _nc_visbuf2(1, base_a),
2072 				_nc_visbuf2(2, base_b),
2073 				_nc_visbuf2(3, b));
2074 		}
2075 		return FALSE;
2076 	    } else if (delaying) {
2077 		a = skip_delay(a);
2078 		b = skip_delay(b);
2079 	    } else if ((*b == '0' || (*b == ';')) && *a == 'm') {
2080 		b++;
2081 	    } else {
2082 		a++;
2083 	    }
2084 	}
2085 	switch (*a) {
2086 	case '$':
2087 	    if (delaying == 0)
2088 		delaying = 1;
2089 	    break;
2090 	case '<':
2091 	    if (delaying == 1)
2092 		delaying = 2;
2093 	    break;
2094 	default:
2095 	    delaying = 0;
2096 	    break;
2097 	}
2098 	a++;
2099 	b++;
2100     }
2101     /* ignore delays on the end of the string */
2102     a = ignore_delays(a);
2103     return ((num != 0) || (*a == 0));
2104 }
2105 
2106 static char *
2107 check_sgr(TERMTYPE *tp, char *zero, int num, char *cap, const char *name)
2108 {
2109     char *test;
2110 
2111     _nc_tparm_err = 0;
2112     test = TPARM_9(set_attributes,
2113 		   num == 1,
2114 		   num == 2,
2115 		   num == 3,
2116 		   num == 4,
2117 		   num == 5,
2118 		   num == 6,
2119 		   num == 7,
2120 		   num == 8,
2121 		   num == 9);
2122     if (test != 0) {
2123 	if (PRESENT(cap)) {
2124 	    if (!similar_sgr(num, test, cap)) {
2125 		_nc_warning("%s differs from sgr(%d)\n\t%s=%s\n\tsgr(%d)=%s",
2126 			    name, num,
2127 			    name, _nc_visbuf2(1, cap),
2128 			    num, _nc_visbuf2(2, test));
2129 	    }
2130 	} else if (_nc_capcmp(test, zero)) {
2131 	    _nc_warning("sgr(%d) present, but not %s", num, name);
2132 	}
2133     } else if (PRESENT(cap)) {
2134 	_nc_warning("sgr(%d) missing, but %s present", num, name);
2135     }
2136     if (_nc_tparm_err)
2137 	_nc_warning("stack error in sgr(%d) string", num);
2138     return test;
2139 }
2140 
2141 #define CHECK_SGR(num,name) check_sgr(tp, zero, num, name, #name)
2142 
2143 #ifdef TRACE
2144 /*
2145  * If tic is compiled with TRACE, we'll be able to see the output from the
2146  * DEBUG() macro.  But since it doesn't use traceon(), it always goes to
2147  * the standard error.  Use this function to make it simpler to follow the
2148  * resulting debug traces.
2149  */
2150 static void
2151 show_where(unsigned level)
2152 {
2153     if (_nc_tracing >= DEBUG_LEVEL(level)) {
2154 	char my_name[MAX_NAME_SIZE];
2155 	_nc_get_type(my_name);
2156 	_tracef("\"%s\", line %d, '%s'",
2157 		_nc_get_source(),
2158 		_nc_curr_line, my_name);
2159     }
2160 }
2161 
2162 #else
2163 #define show_where(level)	/* nothing */
2164 #endif
2165 
2166 typedef struct {
2167     int keycode;
2168     const char *name;
2169     const char *value;
2170 } NAME_VALUE;
2171 
2172 static NAME_VALUE *
2173 get_fkey_list(TERMTYPE *tp)
2174 {
2175     NAME_VALUE *result = typeMalloc(NAME_VALUE, NUM_STRINGS(tp) + 1);
2176     const struct tinfo_fkeys *all_fkeys = _nc_tinfo_fkeys;
2177     int used = 0;
2178     unsigned j;
2179 
2180     if (result == 0)
2181 	failed("get_fkey_list");
2182 
2183     for (j = 0; all_fkeys[j].code; j++) {
2184 	char *a = tp->Strings[all_fkeys[j].offset];
2185 	if (VALID_STRING(a)) {
2186 	    result[used].keycode = (int) all_fkeys[j].code;
2187 	    result[used].name = strnames[all_fkeys[j].offset];
2188 	    result[used].value = a;
2189 	    ++used;
2190 	}
2191     }
2192 #if NCURSES_XNAMES
2193     for (j = STRCOUNT; j < NUM_STRINGS(tp); ++j) {
2194 	const char *name = ExtStrname(tp, (int) j, strnames);
2195 	if (*name == 'k') {
2196 	    result[used].keycode = -1;
2197 	    result[used].name = name;
2198 	    result[used].value = tp->Strings[j];
2199 	    ++used;
2200 	}
2201     }
2202 #endif
2203     result[used].keycode = 0;
2204     return result;
2205 }
2206 
2207 static void
2208 show_fkey_name(NAME_VALUE * data)
2209 {
2210     if (data->keycode > 0) {
2211 	fprintf(stderr, " %s", keyname(data->keycode));
2212 	fprintf(stderr, " (capability \"%s\")", data->name);
2213     } else {
2214 	fprintf(stderr, " capability \"%s\"", data->name);
2215     }
2216 }
2217 
2218 /*
2219  * A terminal entry may contain more than one keycode assigned to a given
2220  * string (e.g., KEY_END and KEY_LL).  But curses will only return one (the
2221  * last one assigned).
2222  */
2223 static void
2224 check_conflict(TERMTYPE *tp)
2225 {
2226     bool conflict = FALSE;
2227     unsigned j, k;
2228 
2229     if (!(_nc_syntax == SYN_TERMCAP && capdump)) {
2230 	char *check = calloc((size_t) (NUM_STRINGS(tp) + 1), sizeof(char));
2231 	NAME_VALUE *given = get_fkey_list(tp);
2232 
2233 	if (check == 0)
2234 	    failed("check_termtype");
2235 
2236 	for (j = 0; given[j].keycode; ++j) {
2237 	    const char *a = given[j].value;
2238 	    bool first = TRUE;
2239 
2240 	    for (k = j + 1; given[k].keycode; k++) {
2241 		const char *b = given[k].value;
2242 		if (check[k])
2243 		    continue;
2244 		if (!_nc_capcmp(a, b)) {
2245 		    check[j] = 1;
2246 		    check[k] = 1;
2247 		    if (first) {
2248 			if (!conflict) {
2249 			    _nc_warning("Conflicting key definitions (using the last)");
2250 			    conflict = TRUE;
2251 			}
2252 			fprintf(stderr, "...");
2253 			show_fkey_name(given + j);
2254 			fprintf(stderr, " is the same as");
2255 			show_fkey_name(given + k);
2256 			first = FALSE;
2257 		    } else {
2258 			fprintf(stderr, ", ");
2259 			show_fkey_name(given + k);
2260 		    }
2261 		}
2262 	    }
2263 	    if (!first)
2264 		fprintf(stderr, "\n");
2265 	}
2266 	free(given);
2267 	free(check);
2268     }
2269 }
2270 
2271 /*
2272  * Exiting a video mode should not duplicate sgr0
2273  */
2274 static void
2275 check_exit_attribute(const char *name, char *test, char *trimmed, char *untrimmed)
2276 {
2277     if (VALID_STRING(test)) {
2278 	if (similar_sgr(-1, trimmed, test) ||
2279 	    similar_sgr(-1, untrimmed, test)) {
2280 	    _nc_warning("%s matches exit_attribute_mode", name);
2281 	}
2282     }
2283 }
2284 
2285 /*
2286  * Returns true if the string looks like a standard SGR string.
2287  */
2288 static bool
2289 is_sgr_string(char *value)
2290 {
2291     bool result = FALSE;
2292 
2293     if (VALID_STRING(value)) {
2294 	if (value[0] == '\033' && value[1] == '[') {
2295 	    result = TRUE;
2296 	    value += 2;
2297 	} else if (UChar(value[0]) == 0x9a) {
2298 	    result = TRUE;
2299 	    value += 1;
2300 	}
2301 	if (result) {
2302 	    int ch;
2303 	    while ((ch = UChar(*value++)) != '\0') {
2304 		if (isdigit(ch) || ch == ';') {
2305 		    ;
2306 		} else if (ch == 'm' && *value == '\0') {
2307 		    ;
2308 		} else {
2309 		    result = FALSE;
2310 		    break;
2311 		}
2312 	    }
2313 	}
2314     }
2315     return result;
2316 }
2317 
2318 /*
2319  * Check if the given capability contains a given SGR attribute.
2320  */
2321 static void
2322 check_sgr_param(TERMTYPE *tp, int code, const char *name, char *value)
2323 {
2324     if (VALID_STRING(value)) {
2325 	int ncv = ((code != 0) ? (1 << (code - 1)) : 0);
2326 	char *test = tgoto(value, 0, 0);
2327 	if (is_sgr_string(test)) {
2328 	    int param = 0;
2329 	    int count = 0;
2330 	    int skips = 0;
2331 	    int color = (value == set_a_foreground ||
2332 			 value == set_a_background ||
2333 			 value == set_foreground ||
2334 			 value == set_background);
2335 	    while (*test != 0) {
2336 		if (isdigit(UChar(*test))) {
2337 		    param = 10 * param + (*test - '0');
2338 		    ++count;
2339 		} else {
2340 		    if (count) {
2341 			/*
2342 			 * Avoid unnecessary warning for xterm 256color codes.
2343 			 */
2344 			if (color && (param == 38 || param == 48))
2345 			    skips = 3;
2346 			if ((skips-- <= 0) && (param == code))
2347 			    break;
2348 		    }
2349 		    count = 0;
2350 		    param = 0;
2351 		}
2352 		++test;
2353 	    }
2354 	    if (count != 0 && param == code) {
2355 		if (code == 0 ||
2356 		    no_color_video < 0 ||
2357 		    !(no_color_video & ncv)) {
2358 		    _nc_warning("\"%s\" SGR-attribute used in %s",
2359 				sgr_names[code],
2360 				name);
2361 		}
2362 	    }
2363 	}
2364     }
2365 }
2366 
2367 /* other sanity-checks (things that we don't want in the normal
2368  * logic that reads a terminfo entry)
2369  */
2370 static void
2371 check_termtype(TERMTYPE *tp, bool literal)
2372 {
2373     unsigned j;
2374 
2375     check_conflict(tp);
2376 
2377     for_each_string(j, tp) {
2378 	char *a = tp->Strings[j];
2379 	if (VALID_STRING(a)) {
2380 	    check_params(tp, ExtStrname(tp, (int) j, strnames), a);
2381 	    if (capdump) {
2382 		check_infotocap(tp, (int) j, a);
2383 	    }
2384 	}
2385     }
2386 
2387     check_acs(tp);
2388     check_colors(tp);
2389     check_cursor(tp);
2390     check_keypad(tp);
2391     check_printer(tp);
2392     check_screen(tp);
2393 
2394     /*
2395      * These may be mismatched because the terminal description relies on
2396      * restoring the cursor visibility by resetting it.
2397      */
2398     ANDMISSING(cursor_invisible, cursor_normal);
2399     ANDMISSING(cursor_visible, cursor_normal);
2400 
2401     if (PRESENT(cursor_visible) && PRESENT(cursor_normal)
2402 	&& !_nc_capcmp(cursor_visible, cursor_normal))
2403 	_nc_warning("cursor_visible is same as cursor_normal");
2404 
2405     /*
2406      * From XSI & O'Reilly, we gather that sc/rc are required if csr is
2407      * given, because the cursor position after the scrolling operation is
2408      * performed is undefined.
2409      */
2410     ANDMISSING(change_scroll_region, save_cursor);
2411     ANDMISSING(change_scroll_region, restore_cursor);
2412 
2413     /*
2414      * If we can clear tabs, we should be able to initialize them.
2415      */
2416     ANDMISSING(clear_all_tabs, set_tab);
2417 
2418     if (PRESENT(set_attributes)) {
2419 	char *zero = 0;
2420 
2421 	_nc_tparm_err = 0;
2422 	if (PRESENT(exit_attribute_mode)) {
2423 	    zero = strdup(CHECK_SGR(0, exit_attribute_mode));
2424 	} else {
2425 	    zero = strdup(TPARM_9(set_attributes, 0, 0, 0, 0, 0, 0, 0, 0, 0));
2426 	}
2427 	if (_nc_tparm_err)
2428 	    _nc_warning("stack error in sgr(0) string");
2429 
2430 	if (zero != 0) {
2431 	    CHECK_SGR(1, enter_standout_mode);
2432 	    CHECK_SGR(2, enter_underline_mode);
2433 	    CHECK_SGR(3, enter_reverse_mode);
2434 	    CHECK_SGR(4, enter_blink_mode);
2435 	    CHECK_SGR(5, enter_dim_mode);
2436 	    CHECK_SGR(6, enter_bold_mode);
2437 	    CHECK_SGR(7, enter_secure_mode);
2438 	    CHECK_SGR(8, enter_protected_mode);
2439 	    CHECK_SGR(9, enter_alt_charset_mode);
2440 	    free(zero);
2441 	} else {
2442 	    _nc_warning("sgr(0) did not return a value");
2443 	}
2444     } else if (PRESENT(exit_attribute_mode) &&
2445 	       set_attributes != CANCELLED_STRING) {
2446 	if (_nc_syntax == SYN_TERMINFO)
2447 	    _nc_warning("missing sgr string");
2448     }
2449 #define CHECK_SGR0(name) check_exit_attribute(#name, name, check_sgr0, exit_attribute_mode)
2450     if (PRESENT(exit_attribute_mode)) {
2451 	char *check_sgr0 = _nc_trim_sgr0(tp);
2452 
2453 	if (check_sgr0 == 0 || *check_sgr0 == '\0') {
2454 	    _nc_warning("trimmed sgr0 is empty");
2455 	} else {
2456 	    show_where(2);
2457 	    if (check_sgr0 != exit_attribute_mode) {
2458 		DEBUG(2,
2459 		      ("will trim sgr0\n\toriginal sgr0=%s\n\ttrimmed  sgr0=%s",
2460 		       _nc_visbuf2(1, exit_attribute_mode),
2461 		       _nc_visbuf2(2, check_sgr0)));
2462 	    } else {
2463 		DEBUG(2,
2464 		      ("will not trim sgr0\n\toriginal sgr0=%s",
2465 		       _nc_visbuf(exit_attribute_mode)));
2466 	    }
2467 	}
2468 	CHECK_SGR0(exit_italics_mode);
2469 	CHECK_SGR0(exit_standout_mode);
2470 	CHECK_SGR0(exit_underline_mode);
2471 	if (check_sgr0 != exit_attribute_mode) {
2472 	    free(check_sgr0);
2473 	}
2474     }
2475 #define CHECK_SGR_PARAM(code, name) check_sgr_param(tp, (int)code, #name, name)
2476     for (j = 0; *sgr_names[j] != '\0'; ++j) {
2477 	CHECK_SGR_PARAM(j, set_a_foreground);
2478 	CHECK_SGR_PARAM(j, set_a_background);
2479 	CHECK_SGR_PARAM(j, set_foreground);
2480 	CHECK_SGR_PARAM(j, set_background);
2481     }
2482 #ifdef TRACE
2483     show_where(2);
2484     if (!auto_right_margin) {
2485 	DEBUG(2,
2486 	      ("can write to lower-right directly"));
2487     } else if (PRESENT(enter_am_mode) && PRESENT(exit_am_mode)) {
2488 	DEBUG(2,
2489 	      ("can write to lower-right by suppressing automargin"));
2490     } else if ((PRESENT(enter_insert_mode) && PRESENT(exit_insert_mode))
2491 	       || PRESENT(insert_character) || PRESENT(parm_ich)) {
2492 	DEBUG(2,
2493 	      ("can write to lower-right by using inserts"));
2494     } else {
2495 	DEBUG(2,
2496 	      ("cannot write to lower-right"));
2497     }
2498 #endif
2499 
2500     /*
2501      * Some standard applications (e.g., vi) and some non-curses
2502      * applications (e.g., jove) get confused if we have both ich1 and
2503      * smir/rmir.  Let's be nice and warn about that, too, even though
2504      * ncurses handles it.
2505      */
2506     if ((PRESENT(enter_insert_mode) || PRESENT(exit_insert_mode))
2507 	&& PRESENT(parm_ich)) {
2508 	_nc_warning("non-curses applications may be confused by ich1 with smir/rmir");
2509     }
2510 
2511     /*
2512      * Finally, do the non-verbose checks
2513      */
2514     if (save_check_termtype != 0)
2515 	save_check_termtype(tp, literal);
2516 }
2517