xref: /freebsd/contrib/ncurses/progs/tic.c (revision b601c69bdbe8755d26570261d7fd4c02ee4eff74)
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 }
906