]> ncurses.scripts.mit.edu Git - ncurses.git/blob - progs/tic.c
cc73a4db220cca19d333182f84e3929b8b199787
[ncurses.git] / progs / tic.c
1 /****************************************************************************
2  * Copyright (c) 1998,1999,2000 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  ****************************************************************************/
33
34 /*
35  *      tic.c --- Main program for terminfo compiler
36  *                      by Eric S. Raymond
37  *
38  */
39
40 #include <progs.priv.h>
41
42 #include <dump_entry.h>
43 #include <term_entry.h>
44
45 MODULE_ID("$Id: tic.c,v 1.69 2000/04/08 23:53:49 tom Exp $")
46
47 const char *_nc_progname = "tic";
48
49 static FILE *log_fp;
50 static FILE *tmp_fp;
51 static bool showsummary = FALSE;
52 static const char *to_remove;
53
54 static void (*save_check_termtype) (TERMTYPE *);
55 static void check_termtype(TERMTYPE * tt);
56
57 static const char usage_string[] = "[-h] [-v[n]] [-e names] [-CILNRTcfrswx1] source-file\n";
58
59 static void
60 cleanup(void)
61 {
62     if (tmp_fp != 0)
63         fclose(tmp_fp);
64     if (to_remove != 0) {
65 #if HAVE_REMOVE
66         remove(to_remove);
67 #else
68         unlink(to_remove);
69 #endif
70     }
71 }
72
73 static void
74 failed(const char *msg)
75 {
76     perror(msg);
77     cleanup();
78     exit(EXIT_FAILURE);
79 }
80
81 static void
82 usage(void)
83 {
84     static const char *const tbl[] =
85     {
86         "Options:",
87         "  -1         format translation output one capability per line",
88         "  -C         translate entries to termcap source form",
89         "  -I         translate entries to terminfo source form",
90         "  -L         translate entries to full terminfo source form",
91         "  -N         disable smart defaults for source translation",
92         "  -R         restrict translation to given terminfo/termcap version",
93         "  -T         remove size-restrictions on compiled description",
94 #if NCURSES_XNAMES
95         "  -a         retain commented-out capabilities (sets -x also)",
96 #endif
97         "  -c         check only, validate input without compiling or translating",
98         "  -f         format complex strings for readability",
99         "  -G         format %{number} to %'char'",
100         "  -g         format %'char' to %{number}",
101         "  -e<names>  translate/compile only entries named by comma-separated list",
102         "  -o<dir>    set output directory for compiled entry writes",
103         "  -r         force resolution of all use entries in source translation",
104         "  -s         print summary statistics",
105         "  -v[n]      set verbosity level",
106         "  -w[n]      set format width for translation output",
107 #if NCURSES_XNAMES
108         "  -x         treat unknown capabilities as user-defined",
109 #endif
110         "",
111         "Parameters:",
112         "  <file>     file to translate or compile"
113     };
114     size_t j;
115
116     fprintf(stderr, "Usage: %s %s\n", _nc_progname, usage_string);
117     for (j = 0; j < sizeof(tbl) / sizeof(tbl[0]); j++) {
118         fputs(tbl[j], stderr);
119         putc('\n', stderr);
120     }
121     exit(EXIT_FAILURE);
122 }
123
124 #define L_BRACE '{'
125 #define R_BRACE '}'
126 #define S_QUOTE '\'';
127
128 static void
129 write_it(ENTRY * ep)
130 {
131     unsigned n;
132     int ch;
133     char *s, *d, *t;
134     char result[MAX_ENTRY_SIZE];
135
136     /*
137      * Look for strings that contain %{number}, convert them to %'char',
138      * which is shorter and runs a little faster.
139      */
140     for (n = 0; n < STRCOUNT; n++) {
141         s = ep->tterm.Strings[n];
142         if (VALID_STRING(s)
143             && strchr(s, L_BRACE) != 0) {
144             d = result;
145             t = s;
146             while ((ch = *t++) != 0) {
147                 *d++ = ch;
148                 if (ch == '\\') {
149                     *d++ = *t++;
150                 } else if ((ch == '%')
151                     && (*t == L_BRACE)) {
152                     char *v = 0;
153                     long value = strtol(t + 1, &v, 0);
154                     if (v != 0
155                         && *v == R_BRACE
156                         && value > 0
157                         && value != '\\'        /* FIXME */
158                         && value < 127
159                         && isprint((int) value)) {
160                         *d++ = S_QUOTE;
161                         *d++ = (int) value;
162                         *d++ = S_QUOTE;
163                         t = (v + 1);
164                     }
165                 }
166             }
167             *d = 0;
168             if (strlen(result) < strlen(s))
169                 strcpy(s, result);
170         }
171     }
172
173     _nc_set_type(_nc_first_name(ep->tterm.term_names));
174     _nc_curr_line = ep->startline;
175     _nc_write_entry(&ep->tterm);
176 }
177
178 static bool
179 immedhook(ENTRY * ep GCC_UNUSED)
180 /* write out entries with no use capabilities immediately to save storage */
181 {
182 #ifndef HAVE_BIG_CORE
183     /*
184      * This is strictly a core-economy kluge.  The really clean way to handle
185      * compilation is to slurp the whole file into core and then do all the
186      * name-collision checks and entry writes in one swell foop.  But the
187      * terminfo master file is large enough that some core-poor systems swap
188      * like crazy when you compile it this way...there have been reports of
189      * this process taking *three hours*, rather than the twenty seconds or
190      * less typical on my development box.
191      *
192      * So.  This hook *immediately* writes out the referenced entry if it
193      * has no use capabilities.  The compiler main loop refrains from
194      * adding the entry to the in-core list when this hook fires.  If some
195      * other entry later needs to reference an entry that got written
196      * immediately, that's OK; the resolution code will fetch it off disk
197      * when it can't find it in core.
198      *
199      * Name collisions will still be detected, just not as cleanly.  The
200      * write_entry() code complains before overwriting an entry that
201      * postdates the time of tic's first call to write_entry().  Thus
202      * it will complain about overwriting entries newly made during the
203      * tic run, but not about overwriting ones that predate it.
204      *
205      * The reason this is a hook, and not in line with the rest of the
206      * compiler code, is that the support for termcap fallback cannot assume
207      * it has anywhere to spool out these entries!
208      *
209      * The _nc_set_type() call here requires a compensating one in
210      * _nc_parse_entry().
211      *
212      * If you define HAVE_BIG_CORE, you'll disable this kluge.  This will
213      * make tic a bit faster (because the resolution code won't have to do
214      * disk I/O nearly as often).
215      */
216     if (ep->nuses == 0) {
217         int oldline = _nc_curr_line;
218
219         write_it(ep);
220         _nc_curr_line = oldline;
221         free(ep->tterm.str_table);
222         return (TRUE);
223     }
224 #endif /* HAVE_BIG_CORE */
225     return (FALSE);
226 }
227
228 static void
229 put_translate(int c)
230 /* emit a comment char, translating terminfo names to termcap names */
231 {
232     static bool in_name = FALSE;
233     static size_t have, used;
234     static char *namebuf, *suffix;
235
236     if (in_name) {
237         if (used + 1 >= have) {
238             have += 132;
239             namebuf = typeRealloc(char, have, namebuf);
240             suffix = typeRealloc(char, have, suffix);
241         }
242         if (c == '\n' || c == '@') {
243             namebuf[used++] = '\0';
244             (void) putchar('<');
245             (void) fputs(namebuf, stdout);
246             putchar(c);
247             in_name = FALSE;
248         } else if (c != '>') {
249             namebuf[used++] = c;
250         } else {                /* ah! candidate name! */
251             char *up;
252             NCURSES_CONST char *tp;
253
254             namebuf[used++] = '\0';
255             in_name = FALSE;
256
257             suffix[0] = '\0';
258             if ((up = strchr(namebuf, '#')) != 0
259                 || (up = strchr(namebuf, '=')) != 0
260                 || ((up = strchr(namebuf, '@')) != 0 && up[1] == '>')) {
261                 (void) strcpy(suffix, up);
262                 *up = '\0';
263             }
264
265             if ((tp = nametrans(namebuf)) != 0) {
266                 (void) putchar(':');
267                 (void) fputs(tp, stdout);
268                 (void) fputs(suffix, stdout);
269                 (void) putchar(':');
270             } else {
271                 /* couldn't find a translation, just dump the name */
272                 (void) putchar('<');
273                 (void) fputs(namebuf, stdout);
274                 (void) fputs(suffix, stdout);
275                 (void) putchar('>');
276             }
277         }
278     } else {
279         used = 0;
280         if (c == '<') {
281             in_name = TRUE;
282         } else {
283             putchar(c);
284         }
285     }
286 }
287
288 /* Returns a string, stripped of leading/trailing whitespace */
289 static char *
290 stripped(char *src)
291 {
292     while (isspace(*src))
293         src++;
294     if (*src != '\0') {
295         char *dst = strcpy(malloc(strlen(src) + 1), src);
296         size_t len = strlen(dst);
297         while (--len != 0 && isspace(dst[len]))
298             dst[len] = '\0';
299         return dst;
300     }
301     return 0;
302 }
303
304 /* Parse the "-e" option-value into a list of names */
305 static const char **
306 make_namelist(char *src)
307 {
308     const char **dst = 0;
309
310     char *s, *base;
311     unsigned pass, n, nn;
312     char buffer[BUFSIZ];
313
314     if (src == 0) {
315         /* EMPTY */ ;
316     } else if (strchr(src, '/') != 0) {         /* a filename */
317         FILE *fp = fopen(src, "r");
318         if (fp == 0)
319             failed(src);
320
321         for (pass = 1; pass <= 2; pass++) {
322             nn = 0;
323             while (fgets(buffer, sizeof(buffer), fp) != 0) {
324                 if ((s = stripped(buffer)) != 0) {
325                     if (dst != 0)
326                         dst[nn] = s;
327                     nn++;
328                 }
329             }
330             if (pass == 1) {
331                 dst = typeCalloc(const char *, nn + 1);
332                 rewind(fp);
333             }
334         }
335         fclose(fp);
336     } else {                    /* literal list of names */
337         for (pass = 1; pass <= 2; pass++) {
338             for (n = nn = 0, base = src;; n++) {
339                 int mark = src[n];
340                 if (mark == ',' || mark == '\0') {
341                     if (pass == 1) {
342                         nn++;
343                     } else {
344                         src[n] = '\0';
345                         if ((s = stripped(base)) != 0)
346                             dst[nn++] = s;
347                         base = &src[n + 1];
348                     }
349                 }
350                 if (mark == '\0')
351                     break;
352             }
353             if (pass == 1)
354                 dst = typeCalloc(const char *, nn + 1);
355         }
356     }
357     if (showsummary) {
358         fprintf(log_fp, "Entries that will be compiled:\n");
359         for (n = 0; dst[n] != 0; n++)
360             fprintf(log_fp, "%d:%s\n", n + 1, dst[n]);
361     }
362     return dst;
363 }
364
365 static bool
366 matches(const char **needle, const char *haystack)
367 /* does entry in needle list match |-separated field in haystack? */
368 {
369     bool code = FALSE;
370     size_t n;
371
372     if (needle != 0) {
373         for (n = 0; needle[n] != 0; n++) {
374             if (_nc_name_match(haystack, needle[n], "|")) {
375                 code = TRUE;
376                 break;
377             }
378         }
379     } else
380         code = TRUE;
381     return (code);
382 }
383
384 static FILE *
385 open_tempfile(char *name)
386 {
387     FILE *result = 0;
388 #if HAVE_MKSTEMP
389     int fd = mkstemp(name);
390     if (fd >= 0)
391         result = fdopen(fd, "w");
392 #else
393     if (tmpnam(name) != 0)
394         result = fopen(name, "w");
395 #endif
396     return result;
397 }
398
399 int
400 main(int argc, char *argv[])
401 {
402     char my_tmpname[PATH_MAX];
403     int v_opt = -1, debug_level;
404     int smart_defaults = TRUE;
405     char *termcap;
406     ENTRY *qp;
407
408     int this_opt, last_opt = '?';
409
410     int outform = F_TERMINFO;   /* output format */
411     int sortmode = S_TERMINFO;  /* sort_mode */
412
413     int width = 60;
414     bool formatted = FALSE;     /* reformat complex strings? */
415     int numbers = 0;            /* format "%'char'" to/from "%{number}" */
416     bool infodump = FALSE;      /* running as captoinfo? */
417     bool capdump = FALSE;       /* running as infotocap? */
418     bool forceresolve = FALSE;  /* force resolution */
419     bool limited = TRUE;
420     char *tversion = (char *) NULL;
421     const char *source_file = "terminfo";
422     const char **namelst = 0;
423     char *outdir = (char *) NULL;
424     bool check_only = FALSE;
425
426     log_fp = stderr;
427
428     if ((_nc_progname = strrchr(argv[0], '/')) == NULL)
429         _nc_progname = argv[0];
430     else
431         _nc_progname++;
432
433     if ((infodump = (strcmp(_nc_progname, "captoinfo") == 0)) != FALSE) {
434         outform = F_TERMINFO;
435         sortmode = S_TERMINFO;
436     }
437     if ((capdump = (strcmp(_nc_progname, "infotocap") == 0)) != FALSE) {
438         outform = F_TERMCAP;
439         sortmode = S_TERMCAP;
440     }
441 #if NCURSES_XNAMES
442     use_extended_names(FALSE);
443 #endif
444
445     /*
446      * Processing arguments is a little complicated, since someone made a
447      * design decision to allow the numeric values for -w, -v options to
448      * be optional.
449      */
450     while ((this_opt = getopt(argc, argv,
451                 "0123456789CILNR:TVace:fGgo:rsvwx")) != EOF) {
452         if (isdigit(this_opt)) {
453             switch (last_opt) {
454             case 'v':
455                 v_opt = (v_opt * 10) + (this_opt - '0');
456                 break;
457             case 'w':
458                 width = (width * 10) + (this_opt - '0');
459                 break;
460             default:
461                 if (this_opt != '1')
462                     usage();
463                 last_opt = this_opt;
464                 width = 0;
465             }
466             continue;
467         }
468         switch (this_opt) {
469         case 'C':
470             capdump = TRUE;
471             outform = F_TERMCAP;
472             sortmode = S_TERMCAP;
473             break;
474         case 'I':
475             infodump = TRUE;
476             outform = F_TERMINFO;
477             sortmode = S_TERMINFO;
478             break;
479         case 'L':
480             infodump = TRUE;
481             outform = F_VARIABLE;
482             sortmode = S_VARIABLE;
483             break;
484         case 'N':
485             smart_defaults = FALSE;
486             break;
487         case 'R':
488             tversion = optarg;
489             break;
490         case 'T':
491             limited = FALSE;
492             break;
493         case 'V':
494             puts(NCURSES_VERSION);
495             return EXIT_SUCCESS;
496         case 'c':
497             check_only = TRUE;
498             break;
499         case 'e':
500             namelst = make_namelist(optarg);
501             break;
502         case 'f':
503             formatted = TRUE;
504             break;
505         case 'G':
506             numbers = 1;
507             break;
508         case 'g':
509             numbers = -1;
510             break;
511         case 'o':
512             outdir = optarg;
513             break;
514         case 'r':
515             forceresolve = TRUE;
516             break;
517         case 's':
518             showsummary = TRUE;
519             break;
520         case 'v':
521             v_opt = 0;
522             break;
523         case 'w':
524             width = 0;
525             break;
526 #if NCURSES_XNAMES
527         case 'a':
528             _nc_disable_period = TRUE;
529             /* FALLTHRU */
530         case 'x':
531             use_extended_names(TRUE);
532             break;
533 #endif
534         default:
535             usage();
536         }
537         last_opt = this_opt;
538     }
539
540     debug_level = (v_opt > 0) ? v_opt : (v_opt == 0);
541     set_trace_level(debug_level);
542
543     if (_nc_tracing) {
544         save_check_termtype = _nc_check_termtype;
545         _nc_check_termtype = check_termtype;
546     }
547 #ifndef HAVE_BIG_CORE
548     /*
549      * Aaargh! immedhook seriously hoses us!
550      *
551      * One problem with immedhook is it means we can't do -e.  Problem
552      * is that we can't guarantee that for each terminal listed, all the
553      * terminals it depends on will have been kept in core for reference
554      * resolution -- in fact it's certain the primitive types at the end
555      * of reference chains *won't* be in core unless they were explicitly
556      * in the select list themselves.
557      */
558     if (namelst && (!infodump && !capdump)) {
559         (void) fprintf(stderr,
560             "Sorry, -e can't be used without -I or -C\n");
561         cleanup();
562         return EXIT_FAILURE;
563     }
564 #endif /* HAVE_BIG_CORE */
565
566     if (optind < argc) {
567         source_file = argv[optind++];
568         if (optind < argc) {
569             fprintf(stderr,
570                 "%s: Too many file names.  Usage:\n\t%s %s",
571                 _nc_progname,
572                 _nc_progname,
573                 usage_string);
574             return EXIT_FAILURE;
575         }
576     } else {
577         if (infodump == TRUE) {
578             /* captoinfo's no-argument case */
579             source_file = "/etc/termcap";
580             if ((termcap = getenv("TERMCAP")) != 0
581                 && (namelst = make_namelist(getenv("TERM"))) != 0) {
582                 if (access(termcap, F_OK) == 0) {
583                     /* file exists */
584                     source_file = termcap;
585                 } else if ((tmp_fp = open_tempfile(my_tmpname)) != 0) {
586                     source_file = my_tmpname;
587                     fprintf(tmp_fp, "%s\n", termcap);
588                     fclose(tmp_fp);
589                     tmp_fp = fopen(source_file, "r");
590                     to_remove = source_file;
591                 } else {
592                     failed("tmpnam");
593                 }
594             }
595         } else {
596             /* tic */
597             fprintf(stderr,
598                 "%s: File name needed.  Usage:\n\t%s %s",
599                 _nc_progname,
600                 _nc_progname,
601                 usage_string);
602             cleanup();
603             return EXIT_FAILURE;
604         }
605     }
606
607     if (tmp_fp == 0
608         && (tmp_fp = fopen(source_file, "r")) == 0) {
609         fprintf(stderr, "%s: Can't open %s\n", _nc_progname, source_file);
610         return EXIT_FAILURE;
611     }
612
613     if (infodump)
614         dump_init(tversion,
615             smart_defaults
616             ? outform
617             : F_LITERAL,
618             sortmode, width, debug_level, formatted);
619     else if (capdump)
620         dump_init(tversion,
621             outform,
622             sortmode, width, debug_level, FALSE);
623
624     /* parse entries out of the source file */
625     _nc_set_source(source_file);
626 #ifndef HAVE_BIG_CORE
627     if (!(check_only || infodump || capdump))
628         _nc_set_writedir(outdir);
629 #endif /* HAVE_BIG_CORE */
630     _nc_read_entry_source(tmp_fp, (char *) NULL,
631         !smart_defaults, FALSE,
632         (check_only || infodump || capdump) ? NULLHOOK : immedhook);
633
634     /* do use resolution */
635     if (check_only || (!infodump && !capdump) || forceresolve) {
636         if (!_nc_resolve_uses(TRUE) && !check_only) {
637             cleanup();
638             return EXIT_FAILURE;
639         }
640     }
641
642     /* length check */
643     if (check_only && (capdump || infodump)) {
644         for_entry_list(qp) {
645             if (matches(namelst, qp->tterm.term_names)) {
646                 int len = fmt_entry(&qp->tterm, NULL, TRUE, infodump, numbers);
647
648                 if (len > (infodump ? MAX_TERMINFO_LENGTH : MAX_TERMCAP_LENGTH))
649                     (void) fprintf(stderr,
650                         "warning: resolved %s entry is %d bytes long\n",
651                         _nc_first_name(qp->tterm.term_names),
652                         len);
653             }
654         }
655     }
656
657     /* write or dump all entries */
658     if (!check_only) {
659         if (!infodump && !capdump) {
660             _nc_set_writedir(outdir);
661             for_entry_list(qp) {
662                 if (matches(namelst, qp->tterm.term_names))
663                     write_it(qp);
664             }
665         } else {
666             /* this is in case infotocap() generates warnings */
667             _nc_curr_col = _nc_curr_line = -1;
668
669             for_entry_list(qp) {
670                 if (matches(namelst, qp->tterm.term_names)) {
671                     int j = qp->cend - qp->cstart;
672                     int len = 0;
673
674                     /* this is in case infotocap() generates warnings */
675                     _nc_set_type(_nc_first_name(qp->tterm.term_names));
676
677                     (void) fseek(tmp_fp, qp->cstart, SEEK_SET);
678                     while (j--) {
679                         if (infodump)
680                             (void) putchar(fgetc(tmp_fp));
681                         else
682                             put_translate(fgetc(tmp_fp));
683                     }
684
685                     len = dump_entry(&qp->tterm, limited, numbers, NULL);
686                     for (j = 0; j < qp->nuses; j++)
687                         len += dump_uses(qp->uses[j].name, !capdump);
688                     (void) putchar('\n');
689                     if (debug_level != 0 && !limited)
690                         printf("# length=%d\n", len);
691                 }
692             }
693             if (!namelst) {
694                 int c, oldc = '\0';
695                 bool in_comment = FALSE;
696                 bool trailing_comment = FALSE;
697
698                 (void) fseek(tmp_fp, _nc_tail->cend, SEEK_SET);
699                 while ((c = fgetc(tmp_fp)) != EOF) {
700                     if (oldc == '\n') {
701                         if (c == '#') {
702                             trailing_comment = TRUE;
703                             in_comment = TRUE;
704                         } else {
705                             in_comment = FALSE;
706                         }
707                     }
708                     if (trailing_comment
709                         && (in_comment || (oldc == '\n' && c == '\n')))
710                         putchar(c);
711                     oldc = c;
712                 }
713             }
714         }
715     }
716
717     /* Show the directory into which entries were written, and the total
718      * number of entries
719      */
720     if (showsummary
721         && (!(check_only || infodump || capdump))) {
722         int total = _nc_tic_written();
723         if (total != 0)
724             fprintf(log_fp, "%d entries written to %s\n",
725                 total,
726                 _nc_tic_dir((char *) 0));
727         else
728             fprintf(log_fp, "No entries written\n");
729     }
730     cleanup();
731     return (EXIT_SUCCESS);
732 }
733
734 /*
735  * This bit of legerdemain turns all the terminfo variable names into
736  * references to locations in the arrays Booleans, Numbers, and Strings ---
737  * precisely what's needed (see comp_parse.c).
738  */
739
740 TERMINAL *cur_term;             /* tweak to avoid linking lib_cur_term.c */
741
742 #undef CUR
743 #define CUR tp->
744
745 /*
746  * An sgr string may contain several settings other than the one we're
747  * interested in, essentially sgr0 + rmacs + whatever.  As long as the
748  * "whatever" is contained in the sgr string, that is close enough for our
749  * sanity check.
750  */
751 static bool
752 similar_sgr(char *a, char *b)
753 {
754     while (*b != 0) {
755         while (*a != *b) {
756             if (*a == 0)
757                 return FALSE;
758             a++;
759         }
760         a++;
761         b++;
762     }
763     return TRUE;
764 }
765
766 static void
767 check_sgr(TERMTYPE * tp, char *zero, int num, char *cap, const char *name)
768 {
769     char *test = tparm(set_attributes,
770         num == 1,
771         num == 2,
772         num == 3,
773         num == 4,
774         num == 5,
775         num == 6,
776         num == 7,
777         num == 8,
778         num == 9);
779     if (test != 0) {
780         if (PRESENT(cap)) {
781             if (!similar_sgr(test, cap)) {
782                 _nc_warning("%s differs from sgr(%d): %s", name, num,
783                     _nc_visbuf(test));
784             }
785         } else if (strcmp(test, zero)) {
786             _nc_warning("sgr(%d) present, but not %s", num, name);
787         }
788     } else if (PRESENT(cap)) {
789         _nc_warning("sgr(%d) missing, but %s present", num, name);
790     }
791 }
792
793 #define CHECK_SGR(num,name) check_sgr(tp, zero, num, name, #name)
794
795 /* other sanity-checks (things that we don't want in the normal
796  * logic that reads a terminfo entry)
797  */
798 static void
799 check_termtype(TERMTYPE * tp)
800 {
801     bool conflict = FALSE;
802     unsigned j, k;
803     char fkeys[STRCOUNT];
804
805     /*
806      * A terminal entry may contain more than one keycode assigned to
807      * a given string (e.g., KEY_END and KEY_LL).  But curses will only
808      * return one (the last one assigned).
809      */
810     memset(fkeys, 0, sizeof(fkeys));
811     for (j = 0; _nc_tinfo_fkeys[j].code; j++) {
812         char *a = tp->Strings[_nc_tinfo_fkeys[j].offset];
813         bool first = TRUE;
814         if (!VALID_STRING(a))
815             continue;
816         for (k = j + 1; _nc_tinfo_fkeys[k].code; k++) {
817             char *b = tp->Strings[_nc_tinfo_fkeys[k].offset];
818             if (!VALID_STRING(b)
819                 || fkeys[k])
820                 continue;
821             if (!strcmp(a, b)) {
822                 fkeys[j] = 1;
823                 fkeys[k] = 1;
824                 if (first) {
825                     if (!conflict) {
826                         _nc_warning("Conflicting key definitions (using the last)");
827                         conflict = TRUE;
828                     }
829                     fprintf(stderr, "... %s is the same as %s",
830                         keyname(_nc_tinfo_fkeys[j].code),
831                         keyname(_nc_tinfo_fkeys[k].code));
832                     first = FALSE;
833                 } else {
834                     fprintf(stderr, ", %s",
835                         keyname(_nc_tinfo_fkeys[k].code));
836                 }
837             }
838         }
839         if (!first)
840             fprintf(stderr, "\n");
841     }
842
843     /*
844      * Quick check for color.  We could also check if the ANSI versus
845      * non-ANSI strings are misused.
846      */
847     if ((max_colors > 0) != (max_pairs > 0)
848         || (max_colors > max_pairs))
849         _nc_warning("inconsistent values for max_colors and max_pairs");
850
851     PAIRED(set_foreground, set_background);
852     PAIRED(set_a_foreground, set_a_background);
853
854     /*
855      * These may be mismatched because the terminal description relies on
856      * restoring the cursor visibility by resetting it.
857      */
858     ANDMISSING(cursor_invisible, cursor_normal);
859     ANDMISSING(cursor_visible, cursor_normal);
860
861     if (PRESENT(cursor_visible) && PRESENT(cursor_normal)
862         && !strcmp(cursor_visible, cursor_normal))
863         _nc_warning("cursor_visible is same as cursor_normal");
864
865     /*
866      * From XSI & O'Reilly, we gather that sc/rc are required if csr is
867      * given, because the cursor position after the scrolling operation is
868      * performed is undefined.
869      */
870     ANDMISSING(change_scroll_region, save_cursor);
871     ANDMISSING(change_scroll_region, restore_cursor);
872
873     if (PRESENT(set_attributes)) {
874         char *zero = tparm(set_attributes, 0, 0, 0, 0, 0, 0, 0, 0, 0);
875
876         zero = strdup(zero);
877         CHECK_SGR(1, enter_standout_mode);
878         CHECK_SGR(2, enter_underline_mode);
879         CHECK_SGR(3, enter_reverse_mode);
880         CHECK_SGR(4, enter_blink_mode);
881         CHECK_SGR(5, enter_dim_mode);
882         CHECK_SGR(6, enter_bold_mode);
883         CHECK_SGR(7, enter_secure_mode);
884         CHECK_SGR(8, enter_protected_mode);
885         CHECK_SGR(9, enter_alt_charset_mode);
886         free(zero);
887     }
888
889     /*
890      * Some standard applications (e.g., vi) and some non-curses
891      * applications (e.g., jove) get confused if we have both ich/ich1 and
892      * smir/rmir.  Let's be nice and warn about that, too, even though
893      * ncurses handles it.
894      */
895     if ((PRESENT(enter_insert_mode) || PRESENT(exit_insert_mode))
896         && (PRESENT(insert_character) || PRESENT(parm_ich))) {
897         _nc_warning("non-curses applications may be confused by ich/ich1 with smir/rmir");
898     }
899
900     /*
901      * Finally, do the non-verbose checks
902      */
903     if (save_check_termtype != 0)
904         save_check_termtype(tp);
905 }