]> ncurses.scripts.mit.edu Git - ncurses.git/blob - progs/tic.c
53fa7225a65285cc6979c3037b08499fa4daccd1
[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.162 2012/02/22 23:59:45 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                 _nc_STRCPY(s, result, strlen(s) + 1);
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                 _nc_STRCPY(suffix, up, have);
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     _nc_STRCPY(result, nominal, need);
490     if (strcmp(result + need - sizeof(suffix), suffix)) {
491         _nc_STRCAT(result, suffix, need);
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 {
799                     _nc_STRCPY(my_tmpname,
800                                "/tmp/XXXXXX",
801                                sizeof(my_tmpname));
802                     if ((tmp_fp = open_tempfile(my_tmpname)) != 0) {
803                         source_file = my_tmpname;
804                         fprintf(tmp_fp, "%s\n", termcap);
805                         fclose(tmp_fp);
806                         tmp_fp = open_input(source_file);
807                         to_remove = source_file;
808                     } else {
809                         failed("tmpnam");
810                     }
811                 }
812             }
813         } else {
814             /* tic */
815             fprintf(stderr,
816                     "%s: File name needed.  Usage:\n\t%s %s",
817                     _nc_progname,
818                     _nc_progname,
819                     usage_string);
820             cleanup(namelst);
821             ExitProgram(EXIT_FAILURE);
822         }
823     }
824
825     if (tmp_fp == 0)
826         tmp_fp = open_input(source_file);
827
828     if (infodump)
829         dump_init(tversion,
830                   smart_defaults
831                   ? outform
832                   : F_LITERAL,
833                   sortmode, width, height, debug_level, formatted);
834     else if (capdump)
835         dump_init(tversion,
836                   outform,
837                   sortmode, width, height, debug_level, FALSE);
838
839     /* parse entries out of the source file */
840     _nc_set_source(source_file);
841 #if !HAVE_BIG_CORE
842     if (!(check_only || infodump || capdump))
843         _nc_set_writedir(outdir);
844 #endif /* HAVE_BIG_CORE */
845     _nc_read_entry_source(tmp_fp, (char *) NULL,
846                           !smart_defaults || literal, FALSE,
847                           ((check_only || infodump || capdump)
848                            ? NULLHOOK
849                            : immedhook));
850
851     /* do use resolution */
852     if (check_only || (!infodump && !capdump) || forceresolve) {
853         if (!_nc_resolve_uses2(TRUE, literal) && !check_only) {
854             cleanup(namelst);
855             ExitProgram(EXIT_FAILURE);
856         }
857     }
858
859     /* length check */
860     if (check_only && (capdump || infodump)) {
861         for_entry_list(qp) {
862             if (matches(namelst, qp->tterm.term_names)) {
863                 int len = fmt_entry(&qp->tterm, NULL, FALSE, TRUE, infodump, numbers);
864
865                 if (len > (infodump ? MAX_TERMINFO_LENGTH : MAX_TERMCAP_LENGTH))
866                     (void) fprintf(stderr,
867                                    "warning: resolved %s entry is %d bytes long\n",
868                                    _nc_first_name(qp->tterm.term_names),
869                                    len);
870             }
871         }
872     }
873
874     /* write or dump all entries */
875     if (!check_only) {
876         if (!infodump && !capdump) {
877             _nc_set_writedir(outdir);
878             for_entry_list(qp) {
879                 if (matches(namelst, qp->tterm.term_names))
880                     write_it(qp);
881             }
882         } else {
883             /* this is in case infotocap() generates warnings */
884             _nc_curr_col = _nc_curr_line = -1;
885
886             for_entry_list(qp) {
887                 if (matches(namelst, qp->tterm.term_names)) {
888                     long j = qp->cend - qp->cstart;
889                     int len = 0;
890
891                     /* this is in case infotocap() generates warnings */
892                     _nc_set_type(_nc_first_name(qp->tterm.term_names));
893
894                     (void) fseek(tmp_fp, qp->cstart, SEEK_SET);
895                     while (j-- > 0) {
896                         if (infodump)
897                             (void) putchar(fgetc(tmp_fp));
898                         else
899                             put_translate(fgetc(tmp_fp));
900                     }
901
902                     repair_acsc(&qp->tterm);
903                     dump_entry(&qp->tterm, suppress_untranslatable,
904                                limited, numbers, NULL);
905                     for (j = 0; j < (long) qp->nuses; j++)
906                         dump_uses(qp->uses[j].name, !capdump);
907                     len = show_entry();
908                     if (debug_level != 0 && !limited)
909                         printf("# length=%d\n", len);
910                 }
911             }
912             if (!namelst && _nc_tail) {
913                 int c, oldc = '\0';
914                 bool in_comment = FALSE;
915                 bool trailing_comment = FALSE;
916
917                 (void) fseek(tmp_fp, _nc_tail->cend, SEEK_SET);
918                 while ((c = fgetc(tmp_fp)) != EOF) {
919                     if (oldc == '\n') {
920                         if (c == '#') {
921                             trailing_comment = TRUE;
922                             in_comment = TRUE;
923                         } else {
924                             in_comment = FALSE;
925                         }
926                     }
927                     if (trailing_comment
928                         && (in_comment || (oldc == '\n' && c == '\n')))
929                         putchar(c);
930                     oldc = c;
931                 }
932             }
933         }
934     }
935
936     /* Show the directory into which entries were written, and the total
937      * number of entries
938      */
939     if (showsummary
940         && (!(check_only || infodump || capdump))) {
941         int total = _nc_tic_written();
942         if (total != 0)
943             fprintf(log_fp, "%d entries written to %s\n",
944                     total,
945                     _nc_tic_dir((char *) 0));
946         else
947             fprintf(log_fp, "No entries written\n");
948     }
949     cleanup(namelst);
950     ExitProgram(EXIT_SUCCESS);
951 }
952
953 /*
954  * This bit of legerdemain turns all the terminfo variable names into
955  * references to locations in the arrays Booleans, Numbers, and Strings ---
956  * precisely what's needed (see comp_parse.c).
957  */
958 #undef CUR
959 #define CUR tp->
960
961 /*
962  * Check if the alternate character-set capabilities are consistent.
963  */
964 static void
965 check_acs(TERMTYPE *tp)
966 {
967     if (VALID_STRING(acs_chars)) {
968         const char *boxes = "lmkjtuvwqxn";
969         char mapped[256];
970         char missing[256];
971         const char *p;
972         char *q;
973
974         memset(mapped, 0, sizeof(mapped));
975         for (p = acs_chars; *p != '\0'; p += 2) {
976             if (p[1] == '\0') {
977                 _nc_warning("acsc has odd number of characters");
978                 break;
979             }
980             mapped[UChar(p[0])] = p[1];
981         }
982
983         if (mapped[UChar('I')] && !mapped[UChar('i')]) {
984             _nc_warning("acsc refers to 'I', which is probably an error");
985         }
986
987         for (p = boxes, q = missing; *p != '\0'; ++p) {
988             if (!mapped[UChar(p[0])]) {
989                 *q++ = p[0];
990             }
991         }
992         *q = '\0';
993
994         assert(strlen(missing) <= strlen(boxes));
995         if (*missing != '\0' && strcmp(missing, boxes)) {
996             _nc_warning("acsc is missing some line-drawing mapping: %s", missing);
997         }
998     }
999 }
1000
1001 /*
1002  * Check if the color capabilities are consistent
1003  */
1004 static void
1005 check_colors(TERMTYPE *tp)
1006 {
1007     if ((max_colors > 0) != (max_pairs > 0)
1008         || ((max_colors > max_pairs) && (initialize_pair == 0)))
1009         _nc_warning("inconsistent values for max_colors (%d) and max_pairs (%d)",
1010                     max_colors, max_pairs);
1011
1012     PAIRED(set_foreground, set_background);
1013     PAIRED(set_a_foreground, set_a_background);
1014     PAIRED(set_color_pair, initialize_pair);
1015
1016     if (VALID_STRING(set_foreground)
1017         && VALID_STRING(set_a_foreground)
1018         && !_nc_capcmp(set_foreground, set_a_foreground))
1019         _nc_warning("expected setf/setaf to be different");
1020
1021     if (VALID_STRING(set_background)
1022         && VALID_STRING(set_a_background)
1023         && !_nc_capcmp(set_background, set_a_background))
1024         _nc_warning("expected setb/setab to be different");
1025
1026     /* see: has_colors() */
1027     if (VALID_NUMERIC(max_colors) && VALID_NUMERIC(max_pairs)
1028         && (((set_foreground != NULL)
1029              && (set_background != NULL))
1030             || ((set_a_foreground != NULL)
1031                 && (set_a_background != NULL))
1032             || set_color_pair)) {
1033         if (!VALID_STRING(orig_pair) && !VALID_STRING(orig_colors))
1034             _nc_warning("expected either op/oc string for resetting colors");
1035     }
1036 }
1037
1038 static char
1039 keypad_final(const char *string)
1040 {
1041     char result = '\0';
1042
1043     if (VALID_STRING(string)
1044         && *string++ == '\033'
1045         && *string++ == 'O'
1046         && strlen(string) == 1) {
1047         result = *string;
1048     }
1049
1050     return result;
1051 }
1052
1053 static long
1054 keypad_index(const char *string)
1055 {
1056     char *test;
1057     const char *list = "PQRSwxymtuvlqrsPpn";    /* app-keypad except "Enter" */
1058     int ch;
1059     long result = -1;
1060
1061     if ((ch = keypad_final(string)) != '\0') {
1062         test = strchr(list, ch);
1063         if (test != 0)
1064             result = (long) (test - list);
1065     }
1066     return result;
1067 }
1068
1069 /*
1070  * list[] is down, up, left, right
1071  * "left" may be ^H rather than \E[D
1072  * "down" may be ^J rather than \E[B
1073  * But up/right are generally consistently escape sequences for ANSI terminals.
1074  */
1075 static void
1076 check_ansi_cursor(char *list[4])
1077 {
1078     int j, k;
1079     int want;
1080     size_t prefix = 0;
1081     size_t suffix;
1082     bool skip[4];
1083     bool repeated = FALSE;
1084
1085     for (j = 0; j < 4; ++j) {
1086         skip[j] = FALSE;
1087         for (k = 0; k < j; ++k) {
1088             if (j != k
1089                 && !strcmp(list[j], list[k])) {
1090                 char *value = _nc_tic_expand(list[k], TRUE, 0);
1091                 _nc_warning("repeated cursor control %s\n", value);
1092                 repeated = TRUE;
1093             }
1094         }
1095     }
1096     if (!repeated) {
1097         char *up = list[1];
1098
1099         if (UChar(up[0]) == '\033') {
1100             if (up[1] == '[') {
1101                 prefix = 2;
1102             } else {
1103                 prefix = 1;
1104             }
1105         } else if (UChar(up[0]) == UChar('\233')) {
1106             prefix = 1;
1107         }
1108         if (prefix) {
1109             suffix = prefix;
1110             while (up[suffix] && isdigit(UChar(up[suffix])))
1111                 ++suffix;
1112         }
1113         if (prefix && up[suffix] == 'A') {
1114             skip[1] = TRUE;
1115             if (!strcmp(list[0], "\n"))
1116                 skip[0] = TRUE;
1117             if (!strcmp(list[2], "\b"))
1118                 skip[2] = TRUE;
1119
1120             for (j = 0; j < 4; ++j) {
1121                 if (skip[j] || strlen(list[j]) == 1)
1122                     continue;
1123                 if (memcmp(list[j], up, prefix)) {
1124                     char *value = _nc_tic_expand(list[j], TRUE, 0);
1125                     _nc_warning("inconsistent prefix for %s\n", value);
1126                     continue;
1127                 }
1128                 if (strlen(list[j]) < suffix) {
1129                     char *value = _nc_tic_expand(list[j], TRUE, 0);
1130                     _nc_warning("inconsistent length for %s, expected %d\n",
1131                                 value, (int) suffix + 1);
1132                     continue;
1133                 }
1134                 want = "BADC"[j];
1135                 if (list[j][suffix] != want) {
1136                     char *value = _nc_tic_expand(list[j], TRUE, 0);
1137                     _nc_warning("inconsistent suffix for %s, expected %c, have %c\n",
1138                                 value, want, list[j][suffix]);
1139                 }
1140             }
1141         }
1142     }
1143 }
1144
1145 #define EXPECTED(name) if (!PRESENT(name)) _nc_warning("expected " #name)
1146
1147 static void
1148 check_cursor(TERMTYPE *tp)
1149 {
1150     int count;
1151     char *list[4];
1152
1153     /* if we have a parameterized form, then the non-parameterized is easy */
1154     ANDMISSING(parm_down_cursor, cursor_down);
1155     ANDMISSING(parm_up_cursor, cursor_up);
1156     ANDMISSING(parm_left_cursor, cursor_left);
1157     ANDMISSING(parm_right_cursor, cursor_right);
1158
1159     /* Given any of a set of cursor movement, the whole set should be present. 
1160      * Technically this is not true (we could use cursor_address to fill in
1161      * unsupported controls), but it is likely.
1162      */
1163     count = 0;
1164     if (PRESENT(parm_down_cursor)) {
1165         list[count++] = parm_down_cursor;
1166     }
1167     if (PRESENT(parm_up_cursor)) {
1168         list[count++] = parm_up_cursor;
1169     }
1170     if (PRESENT(parm_left_cursor)) {
1171         list[count++] = parm_left_cursor;
1172     }
1173     if (PRESENT(parm_right_cursor)) {
1174         list[count++] = parm_right_cursor;
1175     }
1176     if (count == 4) {
1177         check_ansi_cursor(list);
1178     } else if (count != 0) {
1179         EXPECTED(parm_down_cursor);
1180         EXPECTED(parm_up_cursor);
1181         EXPECTED(parm_left_cursor);
1182         EXPECTED(parm_right_cursor);
1183     }
1184
1185     count = 0;
1186     if (PRESENT(cursor_down)) {
1187         list[count++] = cursor_down;
1188     }
1189     if (PRESENT(cursor_up)) {
1190         list[count++] = cursor_up;
1191     }
1192     if (PRESENT(cursor_left)) {
1193         list[count++] = cursor_left;
1194     }
1195     if (PRESENT(cursor_right)) {
1196         list[count++] = cursor_right;
1197     }
1198     if (count == 4) {
1199         check_ansi_cursor(list);
1200     } else if (count != 0) {
1201         count = 0;
1202         if (PRESENT(cursor_down) && strcmp(cursor_down, "\n"))
1203             ++count;
1204         if (PRESENT(cursor_left) && strcmp(cursor_left, "\b"))
1205             ++count;
1206         if (PRESENT(cursor_up) && strlen(cursor_up) > 1)
1207             ++count;
1208         if (PRESENT(cursor_right) && strlen(cursor_right) > 1)
1209             ++count;
1210         if (count) {
1211             EXPECTED(cursor_down);
1212             EXPECTED(cursor_up);
1213             EXPECTED(cursor_left);
1214             EXPECTED(cursor_right);
1215         }
1216     }
1217 }
1218
1219 #define MAX_KP 5
1220 /*
1221  * Do a quick sanity-check for vt100-style keypads to see if the 5-key keypad
1222  * is mapped inconsistently.
1223  */
1224 static void
1225 check_keypad(TERMTYPE *tp)
1226 {
1227     char show[80];
1228
1229     if (VALID_STRING(key_a1) &&
1230         VALID_STRING(key_a3) &&
1231         VALID_STRING(key_b2) &&
1232         VALID_STRING(key_c1) &&
1233         VALID_STRING(key_c3)) {
1234         char final[MAX_KP + 1];
1235         long list[MAX_KP];
1236         int increase = 0;
1237         int j, k, kk;
1238         long last;
1239         long test;
1240
1241         final[0] = keypad_final(key_a1);
1242         final[1] = keypad_final(key_a3);
1243         final[2] = keypad_final(key_b2);
1244         final[3] = keypad_final(key_c1);
1245         final[4] = keypad_final(key_c3);
1246         final[5] = '\0';
1247
1248         /* special case: legacy coding using 1,2,3,0,. on the bottom */
1249         assert(strlen(final) <= MAX_KP);
1250         if (!strcmp(final, "qsrpn"))
1251             return;
1252
1253         list[0] = keypad_index(key_a1);
1254         list[1] = keypad_index(key_a3);
1255         list[2] = keypad_index(key_b2);
1256         list[3] = keypad_index(key_c1);
1257         list[4] = keypad_index(key_c3);
1258
1259         /* check that they're all vt100 keys */
1260         for (j = 0; j < MAX_KP; ++j) {
1261             if (list[j] < 0) {
1262                 return;
1263             }
1264         }
1265
1266         /* check if they're all in increasing order */
1267         for (j = 1; j < MAX_KP; ++j) {
1268             if (list[j] > list[j - 1]) {
1269                 ++increase;
1270             }
1271         }
1272         if (increase != (MAX_KP - 1)) {
1273             show[0] = '\0';
1274
1275             for (j = 0, last = -1; j < MAX_KP; ++j) {
1276                 for (k = 0, kk = -1, test = 100; k < 5; ++k) {
1277                     if (list[k] > last &&
1278                         list[k] < test) {
1279                         test = list[k];
1280                         kk = k;
1281                     }
1282                 }
1283                 last = test;
1284                 assert(strlen(show) < (MAX_KP * 4));
1285                 switch (kk) {
1286                 case 0:
1287                     _nc_STRCAT(show, " ka1", sizeof(show));
1288                     break;
1289                 case 1:
1290                     _nc_STRCAT(show, " ka3", sizeof(show));
1291                     break;
1292                 case 2:
1293                     _nc_STRCAT(show, " kb2", sizeof(show));
1294                     break;
1295                 case 3:
1296                     _nc_STRCAT(show, " kc1", sizeof(show));
1297                     break;
1298                 case 4:
1299                     _nc_STRCAT(show, " kc3", sizeof(show));
1300                     break;
1301                 }
1302             }
1303
1304             _nc_warning("vt100 keypad order inconsistent: %s", show);
1305         }
1306
1307     } else if (VALID_STRING(key_a1) ||
1308                VALID_STRING(key_a3) ||
1309                VALID_STRING(key_b2) ||
1310                VALID_STRING(key_c1) ||
1311                VALID_STRING(key_c3)) {
1312         show[0] = '\0';
1313         if (keypad_index(key_a1) >= 0)
1314             _nc_STRCAT(show, " ka1", sizeof(show));
1315         if (keypad_index(key_a3) >= 0)
1316             _nc_STRCAT(show, " ka3", sizeof(show));
1317         if (keypad_index(key_b2) >= 0)
1318             _nc_STRCAT(show, " kb2", sizeof(show));
1319         if (keypad_index(key_c1) >= 0)
1320             _nc_STRCAT(show, " kc1", sizeof(show));
1321         if (keypad_index(key_c3) >= 0)
1322             _nc_STRCAT(show, " kc3", sizeof(show));
1323         if (*show != '\0')
1324             _nc_warning("vt100 keypad map incomplete:%s", show);
1325     }
1326 }
1327
1328 static void
1329 check_printer(TERMTYPE *tp)
1330 {
1331     PAIRED(enter_doublewide_mode, exit_doublewide_mode);
1332     PAIRED(enter_italics_mode, exit_italics_mode);
1333     PAIRED(enter_leftward_mode, exit_leftward_mode);
1334     PAIRED(enter_micro_mode, exit_micro_mode);
1335     PAIRED(enter_shadow_mode, exit_shadow_mode);
1336     PAIRED(enter_subscript_mode, exit_subscript_mode);
1337     PAIRED(enter_superscript_mode, exit_superscript_mode);
1338     PAIRED(enter_upward_mode, exit_upward_mode);
1339
1340     ANDMISSING(start_char_set_def, stop_char_set_def);
1341
1342     /* if we have a parameterized form, then the non-parameterized is easy */
1343     ANDMISSING(set_bottom_margin_parm, set_bottom_margin);
1344     ANDMISSING(set_left_margin_parm, set_left_margin);
1345     ANDMISSING(set_right_margin_parm, set_right_margin);
1346     ANDMISSING(set_top_margin_parm, set_top_margin);
1347
1348     ANDMISSING(parm_down_micro, micro_down);
1349     ANDMISSING(parm_left_micro, micro_left);
1350     ANDMISSING(parm_right_micro, micro_right);
1351     ANDMISSING(parm_up_micro, micro_up);
1352 }
1353
1354 /*
1355  * Returns the expected number of parameters for the given capability.
1356  */
1357 static int
1358 expected_params(const char *name)
1359 {
1360     /* *INDENT-OFF* */
1361     static const struct {
1362         const char *name;
1363         int count;
1364     } table[] = {
1365         { "S0",                 1 },    /* 'screen' extension */
1366         { "birep",              2 },
1367         { "chr",                1 },
1368         { "colornm",            1 },
1369         { "cpi",                1 },
1370         { "csnm",               1 },
1371         { "csr",                2 },
1372         { "cub",                1 },
1373         { "cud",                1 },
1374         { "cuf",                1 },
1375         { "cup",                2 },
1376         { "cuu",                1 },
1377         { "cvr",                1 },
1378         { "cwin",               5 },
1379         { "dch",                1 },
1380         { "defc",               3 },
1381         { "dial",               1 },
1382         { "dispc",              1 },
1383         { "dl",                 1 },
1384         { "ech",                1 },
1385         { "getm",               1 },
1386         { "hpa",                1 },
1387         { "ich",                1 },
1388         { "il",                 1 },
1389         { "indn",               1 },
1390         { "initc",              4 },
1391         { "initp",              7 },
1392         { "lpi",                1 },
1393         { "mc5p",               1 },
1394         { "mrcup",              2 },
1395         { "mvpa",               1 },
1396         { "pfkey",              2 },
1397         { "pfloc",              2 },
1398         { "pfx",                2 },
1399         { "pfxl",               3 },
1400         { "pln",                2 },
1401         { "qdial",              1 },
1402         { "rcsd",               1 },
1403         { "rep",                2 },
1404         { "rin",                1 },
1405         { "sclk",               3 },
1406         { "scp",                1 },
1407         { "scs",                1 },
1408         { "scsd",               2 },
1409         { "setab",              1 },
1410         { "setaf",              1 },
1411         { "setb",               1 },
1412         { "setcolor",           1 },
1413         { "setf",               1 },
1414         { "sgr",                9 },
1415         { "sgr1",               6 },
1416         { "slength",            1 },
1417         { "slines",             1 },
1418         { "smgbp",              1 },    /* 2 if smgtp is not given */
1419         { "smglp",              1 },
1420         { "smglr",              2 },
1421         { "smgrp",              1 },
1422         { "smgtb",              2 },
1423         { "smgtp",              1 },
1424         { "tsl",                1 },
1425         { "u6",                 -1 },
1426         { "vpa",                1 },
1427         { "wind",               4 },
1428         { "wingo",              1 },
1429     };
1430     /* *INDENT-ON* */
1431
1432     unsigned n;
1433     int result = 0;             /* function-keys, etc., use none */
1434
1435     for (n = 0; n < SIZEOF(table); n++) {
1436         if (!strcmp(name, table[n].name)) {
1437             result = table[n].count;
1438             break;
1439         }
1440     }
1441
1442     return result;
1443 }
1444
1445 /*
1446  * Make a quick sanity check for the parameters which are used in the given
1447  * strings.  If there are no "%p" tokens, then there should be no other "%"
1448  * markers.
1449  */
1450 static void
1451 check_params(TERMTYPE *tp, const char *name, char *value)
1452 {
1453     int expected = expected_params(name);
1454     int actual = 0;
1455     int n;
1456     bool params[10];
1457     char *s = value;
1458
1459 #ifdef set_top_margin_parm
1460     if (!strcmp(name, "smgbp")
1461         && set_top_margin_parm == 0)
1462         expected = 2;
1463 #endif
1464
1465     for (n = 0; n < 10; n++)
1466         params[n] = FALSE;
1467
1468     while (*s != 0) {
1469         if (*s == '%') {
1470             if (*++s == '\0') {
1471                 _nc_warning("expected character after %% in %s", name);
1472                 break;
1473             } else if (*s == 'p') {
1474                 if (*++s == '\0' || !isdigit((int) *s)) {
1475                     _nc_warning("expected digit after %%p in %s", name);
1476                     return;
1477                 } else {
1478                     n = (*s - '0');
1479                     if (n > actual)
1480                         actual = n;
1481                     params[n] = TRUE;
1482                 }
1483             }
1484         }
1485         s++;
1486     }
1487
1488     if (params[0]) {
1489         _nc_warning("%s refers to parameter 0 (%%p0), which is not allowed", name);
1490     }
1491     if (value == set_attributes || expected < 0) {
1492         ;
1493     } else if (expected != actual) {
1494         _nc_warning("%s uses %d parameters, expected %d", name,
1495                     actual, expected);
1496         for (n = 1; n < actual; n++) {
1497             if (!params[n])
1498                 _nc_warning("%s omits parameter %d", name, n);
1499         }
1500     }
1501 }
1502
1503 static char *
1504 skip_delay(char *s)
1505 {
1506     while (*s == '/' || isdigit(UChar(*s)))
1507         ++s;
1508     return s;
1509 }
1510
1511 /*
1512  * Skip a delay altogether, e.g., when comparing a simple string to sgr,
1513  * the latter may have a worst-case delay on the end.
1514  */
1515 static char *
1516 ignore_delays(char *s)
1517 {
1518     int delaying = 0;
1519
1520     do {
1521         switch (*s) {
1522         case '$':
1523             if (delaying == 0)
1524                 delaying = 1;
1525             break;
1526         case '<':
1527             if (delaying == 1)
1528                 delaying = 2;
1529             break;
1530         case '\0':
1531             delaying = 0;
1532             break;
1533         default:
1534             if (delaying) {
1535                 s = skip_delay(s);
1536                 if (*s == '>')
1537                     ++s;
1538                 delaying = 0;
1539             }
1540             break;
1541         }
1542         if (delaying)
1543             ++s;
1544     } while (delaying);
1545     return s;
1546 }
1547
1548 /*
1549  * An sgr string may contain several settings other than the one we're
1550  * interested in, essentially sgr0 + rmacs + whatever.  As long as the
1551  * "whatever" is contained in the sgr string, that is close enough for our
1552  * sanity check.
1553  */
1554 static bool
1555 similar_sgr(int num, char *a, char *b)
1556 {
1557     static const char *names[] =
1558     {
1559         "none"
1560         ,"standout"
1561         ,"underline"
1562         ,"reverse"
1563         ,"blink"
1564         ,"dim"
1565         ,"bold"
1566         ,"invis"
1567         ,"protect"
1568         ,"altcharset"
1569     };
1570     char *base_a = a;
1571     char *base_b = b;
1572     int delaying = 0;
1573
1574     while (*b != 0) {
1575         while (*a != *b) {
1576             if (*a == 0) {
1577                 if (b[0] == '$'
1578                     && b[1] == '<') {
1579                     _nc_warning("Did not find delay %s", _nc_visbuf(b));
1580                 } else {
1581                     _nc_warning("checking sgr(%s) %s\n\tcompare to %s\n\tunmatched %s",
1582                                 names[num], _nc_visbuf2(1, base_a),
1583                                 _nc_visbuf2(2, base_b),
1584                                 _nc_visbuf2(3, b));
1585                 }
1586                 return FALSE;
1587             } else if (delaying) {
1588                 a = skip_delay(a);
1589                 b = skip_delay(b);
1590             } else if ((*b == '0' || (*b == ';')) && *a == 'm') {
1591                 b++;
1592             } else {
1593                 a++;
1594             }
1595         }
1596         switch (*a) {
1597         case '$':
1598             if (delaying == 0)
1599                 delaying = 1;
1600             break;
1601         case '<':
1602             if (delaying == 1)
1603                 delaying = 2;
1604             break;
1605         default:
1606             delaying = 0;
1607             break;
1608         }
1609         a++;
1610         b++;
1611     }
1612     /* ignore delays on the end of the string */
1613     a = ignore_delays(a);
1614     return ((num != 0) || (*a == 0));
1615 }
1616
1617 static char *
1618 check_sgr(TERMTYPE *tp, char *zero, int num, char *cap, const char *name)
1619 {
1620     char *test;
1621
1622     _nc_tparm_err = 0;
1623     test = TPARM_9(set_attributes,
1624                    num == 1,
1625                    num == 2,
1626                    num == 3,
1627                    num == 4,
1628                    num == 5,
1629                    num == 6,
1630                    num == 7,
1631                    num == 8,
1632                    num == 9);
1633     if (test != 0) {
1634         if (PRESENT(cap)) {
1635             if (!similar_sgr(num, test, cap)) {
1636                 _nc_warning("%s differs from sgr(%d)\n\t%s=%s\n\tsgr(%d)=%s",
1637                             name, num,
1638                             name, _nc_visbuf2(1, cap),
1639                             num, _nc_visbuf2(2, test));
1640             }
1641         } else if (_nc_capcmp(test, zero)) {
1642             _nc_warning("sgr(%d) present, but not %s", num, name);
1643         }
1644     } else if (PRESENT(cap)) {
1645         _nc_warning("sgr(%d) missing, but %s present", num, name);
1646     }
1647     if (_nc_tparm_err)
1648         _nc_warning("stack error in sgr(%d) string", num);
1649     return test;
1650 }
1651
1652 #define CHECK_SGR(num,name) check_sgr(tp, zero, num, name, #name)
1653
1654 #ifdef TRACE
1655 /*
1656  * If tic is compiled with TRACE, we'll be able to see the output from the
1657  * DEBUG() macro.  But since it doesn't use traceon(), it always goes to
1658  * the standard error.  Use this function to make it simpler to follow the
1659  * resulting debug traces.
1660  */
1661 static void
1662 show_where(unsigned level)
1663 {
1664     if (_nc_tracing >= DEBUG_LEVEL(level)) {
1665         char my_name[256];
1666         _nc_get_type(my_name);
1667         _tracef("\"%s\", line %d, '%s'",
1668                 _nc_get_source(),
1669                 _nc_curr_line, my_name);
1670     }
1671 }
1672
1673 #else
1674 #define show_where(level)       /* nothing */
1675 #endif
1676
1677 /* other sanity-checks (things that we don't want in the normal
1678  * logic that reads a terminfo entry)
1679  */
1680 static void
1681 check_termtype(TERMTYPE *tp, bool literal)
1682 {
1683     bool conflict = FALSE;
1684     unsigned j, k;
1685     char fkeys[STRCOUNT];
1686
1687     /*
1688      * A terminal entry may contain more than one keycode assigned to
1689      * a given string (e.g., KEY_END and KEY_LL).  But curses will only
1690      * return one (the last one assigned).
1691      */
1692     if (!(_nc_syntax == SYN_TERMCAP && capdump)) {
1693         memset(fkeys, 0, sizeof(fkeys));
1694         for (j = 0; _nc_tinfo_fkeys[j].code; j++) {
1695             char *a = tp->Strings[_nc_tinfo_fkeys[j].offset];
1696             bool first = TRUE;
1697             if (!VALID_STRING(a))
1698                 continue;
1699             for (k = j + 1; _nc_tinfo_fkeys[k].code; k++) {
1700                 char *b = tp->Strings[_nc_tinfo_fkeys[k].offset];
1701                 if (!VALID_STRING(b)
1702                     || fkeys[k])
1703                     continue;
1704                 if (!_nc_capcmp(a, b)) {
1705                     fkeys[j] = 1;
1706                     fkeys[k] = 1;
1707                     if (first) {
1708                         if (!conflict) {
1709                             _nc_warning("Conflicting key definitions (using the last)");
1710                             conflict = TRUE;
1711                         }
1712                         fprintf(stderr, "... %s is the same as %s",
1713                                 keyname((int) _nc_tinfo_fkeys[j].code),
1714                                 keyname((int) _nc_tinfo_fkeys[k].code));
1715                         first = FALSE;
1716                     } else {
1717                         fprintf(stderr, ", %s",
1718                                 keyname((int) _nc_tinfo_fkeys[k].code));
1719                     }
1720                 }
1721             }
1722             if (!first)
1723                 fprintf(stderr, "\n");
1724         }
1725     }
1726
1727     for (j = 0; j < NUM_STRINGS(tp); j++) {
1728         char *a = tp->Strings[j];
1729         if (VALID_STRING(a))
1730             check_params(tp, ExtStrname(tp, (int) j, strnames), a);
1731     }
1732
1733     check_acs(tp);
1734     check_colors(tp);
1735     check_cursor(tp);
1736     check_keypad(tp);
1737     check_printer(tp);
1738
1739     /*
1740      * These may be mismatched because the terminal description relies on
1741      * restoring the cursor visibility by resetting it.
1742      */
1743     ANDMISSING(cursor_invisible, cursor_normal);
1744     ANDMISSING(cursor_visible, cursor_normal);
1745
1746     if (PRESENT(cursor_visible) && PRESENT(cursor_normal)
1747         && !_nc_capcmp(cursor_visible, cursor_normal))
1748         _nc_warning("cursor_visible is same as cursor_normal");
1749
1750     /*
1751      * From XSI & O'Reilly, we gather that sc/rc are required if csr is
1752      * given, because the cursor position after the scrolling operation is
1753      * performed is undefined.
1754      */
1755     ANDMISSING(change_scroll_region, save_cursor);
1756     ANDMISSING(change_scroll_region, restore_cursor);
1757
1758     /*
1759      * If we can clear tabs, we should be able to initialize them.
1760      */
1761     ANDMISSING(clear_all_tabs, set_tab);
1762
1763     if (PRESENT(set_attributes)) {
1764         char *zero = 0;
1765
1766         _nc_tparm_err = 0;
1767         if (PRESENT(exit_attribute_mode)) {
1768             zero = strdup(CHECK_SGR(0, exit_attribute_mode));
1769         } else {
1770             zero = strdup(TPARM_9(set_attributes, 0, 0, 0, 0, 0, 0, 0, 0, 0));
1771         }
1772         if (_nc_tparm_err)
1773             _nc_warning("stack error in sgr(0) string");
1774
1775         if (zero != 0) {
1776             CHECK_SGR(1, enter_standout_mode);
1777             CHECK_SGR(2, enter_underline_mode);
1778             CHECK_SGR(3, enter_reverse_mode);
1779             CHECK_SGR(4, enter_blink_mode);
1780             CHECK_SGR(5, enter_dim_mode);
1781             CHECK_SGR(6, enter_bold_mode);
1782             CHECK_SGR(7, enter_secure_mode);
1783             CHECK_SGR(8, enter_protected_mode);
1784             CHECK_SGR(9, enter_alt_charset_mode);
1785             free(zero);
1786         } else {
1787             _nc_warning("sgr(0) did not return a value");
1788         }
1789     } else if (PRESENT(exit_attribute_mode) &&
1790                set_attributes != CANCELLED_STRING) {
1791         if (_nc_syntax == SYN_TERMINFO)
1792             _nc_warning("missing sgr string");
1793     }
1794
1795     if (PRESENT(exit_attribute_mode)) {
1796         char *check_sgr0 = _nc_trim_sgr0(tp);
1797
1798         if (check_sgr0 == 0 || *check_sgr0 == '\0') {
1799             _nc_warning("trimmed sgr0 is empty");
1800         } else {
1801             show_where(2);
1802             if (check_sgr0 != exit_attribute_mode) {
1803                 DEBUG(2,
1804                       ("will trim sgr0\n\toriginal sgr0=%s\n\ttrimmed  sgr0=%s",
1805                        _nc_visbuf2(1, exit_attribute_mode),
1806                        _nc_visbuf2(2, check_sgr0)));
1807                 free(check_sgr0);
1808             } else {
1809                 DEBUG(2,
1810                       ("will not trim sgr0\n\toriginal sgr0=%s",
1811                        _nc_visbuf(exit_attribute_mode)));
1812             }
1813         }
1814     }
1815 #ifdef TRACE
1816     show_where(2);
1817     if (!auto_right_margin) {
1818         DEBUG(2,
1819               ("can write to lower-right directly"));
1820     } else if (PRESENT(enter_am_mode) && PRESENT(exit_am_mode)) {
1821         DEBUG(2,
1822               ("can write to lower-right by suppressing automargin"));
1823     } else if ((PRESENT(enter_insert_mode) && PRESENT(exit_insert_mode))
1824                || PRESENT(insert_character) || PRESENT(parm_ich)) {
1825         DEBUG(2,
1826               ("can write to lower-right by using inserts"));
1827     } else {
1828         DEBUG(2,
1829               ("cannot write to lower-right"));
1830     }
1831 #endif
1832
1833     /*
1834      * Some standard applications (e.g., vi) and some non-curses
1835      * applications (e.g., jove) get confused if we have both ich1 and
1836      * smir/rmir.  Let's be nice and warn about that, too, even though
1837      * ncurses handles it.
1838      */
1839     if ((PRESENT(enter_insert_mode) || PRESENT(exit_insert_mode))
1840         && PRESENT(parm_ich)) {
1841         _nc_warning("non-curses applications may be confused by ich1 with smir/rmir");
1842     }
1843
1844     /*
1845      * Finally, do the non-verbose checks
1846      */
1847     if (save_check_termtype != 0)
1848         save_check_termtype(tp, literal);
1849 }