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