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