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