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