xref: /openbsd-src/usr.bin/tic/tic.c (revision db3296cf5c1dd9058ceecc3a29fe4aaa0bd26000)
1 /*	$OpenBSD: tic.c,v 1.27 2003/07/02 00:21:16 avsm Exp $	*/
2 
3 /****************************************************************************
4  * Copyright (c) 1998,1999,2000,2001 Free Software Foundation, Inc.         *
5  *                                                                          *
6  * Permission is hereby granted, free of charge, to any person obtaining a  *
7  * copy of this software and associated documentation files (the            *
8  * "Software"), to deal in the Software without restriction, including      *
9  * without limitation the rights to use, copy, modify, merge, publish,      *
10  * distribute, distribute with modifications, sublicense, and/or sell       *
11  * copies of the Software, and to permit persons to whom the Software is    *
12  * furnished to do so, subject to the following conditions:                 *
13  *                                                                          *
14  * The above copyright notice and this permission notice shall be included  *
15  * in all copies or substantial portions of the Software.                   *
16  *                                                                          *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
18  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
19  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
20  * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
21  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
22  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
23  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
24  *                                                                          *
25  * Except as contained in this notice, the name(s) of the above copyright   *
26  * holders shall not be used in advertising or otherwise to promote the     *
27  * sale, use or other dealings in this Software without prior written       *
28  * authorization.                                                           *
29  ****************************************************************************/
30 
31 /****************************************************************************
32  *  Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995               *
33  *     and: Eric S. Raymond <esr@snark.thyrsus.com>                         *
34  ****************************************************************************/
35 
36 /*
37  *	tic.c --- Main program for terminfo compiler
38  *			by Eric S. Raymond
39  *
40  */
41 
42 #include <progs.priv.h>
43 #include <sys/stat.h>
44 
45 #include <dump_entry.h>
46 #include <term_entry.h>
47 #include <transform.h>
48 
49 MODULE_ID("$From: tic.c,v 1.85 2001/02/03 23:31:45 tom Exp $")
50 
51 const char *_nc_progname = "tic";
52 
53 static FILE *log_fp;
54 static FILE *tmp_fp;
55 static bool showsummary = FALSE;
56 static const char *to_remove;
57 
58 static void (*save_check_termtype) (TERMTYPE *);
59 static void check_termtype(TERMTYPE * tt);
60 
61 static const char usage_string[] = "[-V] [-v[n]] [-e names] [-CILNRTcfrswx1] source-file\n";
62 
63 static void
64 cleanup(void)
65 {
66     if (tmp_fp != 0)
67 	fclose(tmp_fp);
68     if (to_remove != 0) {
69 #if HAVE_REMOVE
70 	remove(to_remove);
71 #else
72 	unlink(to_remove);
73 #endif
74     }
75 }
76 
77 static void
78 failed(const char *msg)
79 {
80     perror(msg);
81     cleanup();
82     exit(EXIT_FAILURE);
83 }
84 
85 static void
86 usage(void)
87 {
88     static const char *const tbl[] =
89     {
90 	"Options:",
91 	"  -1         format translation output one capability per line",
92 	"  -C         translate entries to termcap source form",
93 	"  -I         translate entries to terminfo source form",
94 	"  -L         translate entries to full terminfo source form",
95 	"  -N         disable smart defaults for source translation",
96 	"  -R         restrict translation to given terminfo/termcap version",
97 	"  -T         remove size-restrictions on compiled description",
98 	"  -V         print version",
99 #if NCURSES_XNAMES
100 	"  -a         retain commented-out capabilities (sets -x also)",
101 #endif
102 	"  -c         check only, validate input without compiling or translating",
103 	"  -f         format complex strings for readability",
104 	"  -G         format %{number} to %'char'",
105 	"  -g         format %'char' to %{number}",
106 	"  -e<names>  translate/compile only entries named by comma-separated list",
107 	"  -o<dir>    set output directory for compiled entry writes",
108 	"  -r         force resolution of all use entries in source translation",
109 	"  -s         print summary statistics",
110 	"  -v[n]      set verbosity level",
111 	"  -w[n]      set format width for translation output",
112 #if NCURSES_XNAMES
113 	"  -x         treat unknown capabilities as user-defined",
114 #endif
115 	"",
116 	"Parameters:",
117 	"  <file>     file to translate or compile"
118     };
119     size_t j;
120 
121     fprintf(stderr, "Usage: %s %s\n", _nc_progname, usage_string);
122     for (j = 0; j < SIZEOF(tbl); j++) {
123 	fputs(tbl[j], stderr);
124 	putc('\n', stderr);
125     }
126     exit(EXIT_FAILURE);
127 }
128 
129 #define L_BRACE '{'
130 #define R_BRACE '}'
131 #define S_QUOTE '\'';
132 
133 static void
134 write_it(ENTRY * ep)
135 {
136     unsigned n;
137     int ch;
138     char *s, *d, *t;
139     char result[MAX_ENTRY_SIZE];
140 
141     /*
142      * Look for strings that contain %{number}, convert them to %'char',
143      * which is shorter and runs a little faster.
144      */
145     for (n = 0; n < STRCOUNT; n++) {
146 	s = ep->tterm.Strings[n];
147 	if (VALID_STRING(s)
148 	    && strchr(s, L_BRACE) != 0) {
149 	    d = result;
150 	    t = s;
151 	    while ((ch = *t++) != 0) {
152 		*d++ = ch;
153 		if (ch == '\\') {
154 		    *d++ = *t++;
155 		} else if ((ch == '%')
156 			   && (*t == L_BRACE)) {
157 		    char *v = 0;
158 		    long value = strtol(t + 1, &v, 0);
159 		    if (v != 0
160 			&& *v == R_BRACE
161 			&& value > 0
162 			&& value != '\\'	/* FIXME */
163 			&& value < 127
164 			&& isprint((int) value)) {
165 			*d++ = S_QUOTE;
166 			*d++ = (int) value;
167 			*d++ = S_QUOTE;
168 			t = (v + 1);
169 		    }
170 		}
171 	    }
172 	    *d = 0;
173 	    if (strlen(result) < strlen(s)) {
174 		/* new string is same length as what is there, or shorter */
175 		strlcpy(s, result, strlen(s));
176 	    }
177 	}
178     }
179 
180     _nc_set_type(_nc_first_name(ep->tterm.term_names));
181     _nc_curr_line = ep->startline;
182     _nc_write_entry(&ep->tterm);
183 }
184 
185 static bool
186 immedhook(ENTRY * ep GCC_UNUSED)
187 /* write out entries with no use capabilities immediately to save storage */
188 {
189 #if !HAVE_BIG_CORE
190     /*
191      * This is strictly a core-economy kluge.  The really clean way to handle
192      * compilation is to slurp the whole file into core and then do all the
193      * name-collision checks and entry writes in one swell foop.  But the
194      * terminfo master file is large enough that some core-poor systems swap
195      * like crazy when you compile it this way...there have been reports of
196      * this process taking *three hours*, rather than the twenty seconds or
197      * less typical on my development box.
198      *
199      * So.  This hook *immediately* writes out the referenced entry if it
200      * has no use capabilities.  The compiler main loop refrains from
201      * adding the entry to the in-core list when this hook fires.  If some
202      * other entry later needs to reference an entry that got written
203      * immediately, that's OK; the resolution code will fetch it off disk
204      * when it can't find it in core.
205      *
206      * Name collisions will still be detected, just not as cleanly.  The
207      * write_entry() code complains before overwriting an entry that
208      * postdates the time of tic's first call to write_entry().  Thus
209      * it will complain about overwriting entries newly made during the
210      * tic run, but not about overwriting ones that predate it.
211      *
212      * The reason this is a hook, and not in line with the rest of the
213      * compiler code, is that the support for termcap fallback cannot assume
214      * it has anywhere to spool out these entries!
215      *
216      * The _nc_set_type() call here requires a compensating one in
217      * _nc_parse_entry().
218      *
219      * If you define HAVE_BIG_CORE, you'll disable this kluge.  This will
220      * make tic a bit faster (because the resolution code won't have to do
221      * disk I/O nearly as often).
222      */
223     if (ep->nuses == 0) {
224 	int oldline = _nc_curr_line;
225 
226 	write_it(ep);
227 	_nc_curr_line = oldline;
228 	free(ep->tterm.str_table);
229 	return (TRUE);
230     }
231 #endif /* HAVE_BIG_CORE */
232     return (FALSE);
233 }
234 
235 static void
236 put_translate(int c)
237 /* emit a comment char, translating terminfo names to termcap names */
238 {
239     static bool in_name = FALSE;
240     static size_t have, used;
241     static char *namebuf, *suffix;
242 
243     if (in_name) {
244 	if (used + 1 >= have) {
245 	    have += 132;
246 	    namebuf = typeRealloc(char, have, namebuf);
247 	    suffix = typeRealloc(char, have, suffix);
248 	}
249 	if (c == '\n' || c == '@') {
250 	    namebuf[used++] = '\0';
251 	    (void) putchar('<');
252 	    (void) fputs(namebuf, stdout);
253 	    putchar(c);
254 	    in_name = FALSE;
255 	} else if (c != '>') {
256 	    namebuf[used++] = c;
257 	} else {		/* ah! candidate name! */
258 	    char *up;
259 	    NCURSES_CONST char *tp;
260 
261 	    namebuf[used++] = '\0';
262 	    in_name = FALSE;
263 
264 	    suffix[0] = '\0';
265 	    if ((up = strchr(namebuf, '#')) != 0
266 		|| (up = strchr(namebuf, '=')) != 0
267 		|| ((up = strchr(namebuf, '@')) != 0 && up[1] == '>')) {
268 		(void) strlcpy(suffix, up, have);
269 		*up = '\0';
270 	    }
271 
272 	    if ((tp = nametrans(namebuf)) != 0) {
273 		(void) putchar(':');
274 		(void) fputs(tp, stdout);
275 		(void) fputs(suffix, stdout);
276 		(void) putchar(':');
277 	    } else {
278 		/* couldn't find a translation, just dump the name */
279 		(void) putchar('<');
280 		(void) fputs(namebuf, stdout);
281 		(void) fputs(suffix, stdout);
282 		(void) putchar('>');
283 	    }
284 	}
285     } else {
286 	used = 0;
287 	if (c == '<') {
288 	    in_name = TRUE;
289 	} else {
290 	    putchar(c);
291 	}
292     }
293 }
294 
295 /* Returns a string, stripped of leading/trailing whitespace */
296 static char *
297 stripped(char *src)
298 {
299     while (isspace(CharOf(*src)))
300 	src++;
301     if (*src != '\0') {
302 	char *dst = strdup(src);
303 	size_t len = strlen(dst);
304 	while (--len != 0 && isspace(CharOf(dst[len])))
305 	    dst[len] = '\0';
306 	return dst;
307     }
308     return 0;
309 }
310 
311 static FILE *
312 open_input(const char *filename)
313 {
314     FILE *fp = fopen(filename, "r");
315     struct stat sb;
316 
317     if (fp == 0) {
318 	fprintf(stderr, "%s: Can't open %s\n", _nc_progname, filename);
319 	exit(EXIT_FAILURE);
320     }
321     if (fstat(fileno(fp), &sb) < 0
322 	|| (sb.st_mode & S_IFMT) != S_IFREG) {
323 	fprintf(stderr, "%s: %s is not a file\n", _nc_progname, filename);
324 	exit(EXIT_FAILURE);
325     }
326     return fp;
327 }
328 
329 /* Parse the "-e" option-value into a list of names */
330 static const char **
331 make_namelist(char *src)
332 {
333     const char **dst = 0;
334 
335     char *s, *base;
336     unsigned pass, n, nn;
337     char buffer[BUFSIZ];
338 
339     if (src == 0) {
340 	/* EMPTY */ ;
341     } else if (strchr(src, '/') != 0) {		/* a filename */
342 	FILE *fp = open_input(src);
343 
344 	for (pass = 1; pass <= 2; pass++) {
345 	    nn = 0;
346 	    while (fgets(buffer, sizeof(buffer), fp) != 0) {
347 		if ((s = stripped(buffer)) != 0) {
348 		    if (dst != 0)
349 			dst[nn] = s;
350 		    nn++;
351 		}
352 	    }
353 	    if (pass == 1) {
354 		dst = typeCalloc(const char *, nn + 1);
355 		rewind(fp);
356 	    }
357 	}
358 	fclose(fp);
359     } else {			/* literal list of names */
360 	for (pass = 1; pass <= 2; pass++) {
361 	    for (n = nn = 0, base = src;; n++) {
362 		int mark = src[n];
363 		if (mark == ',' || mark == '\0') {
364 		    if (pass == 1) {
365 			nn++;
366 		    } else {
367 			src[n] = '\0';
368 			if ((s = stripped(base)) != 0)
369 			    dst[nn++] = s;
370 			base = &src[n + 1];
371 		    }
372 		}
373 		if (mark == '\0')
374 		    break;
375 	    }
376 	    if (pass == 1)
377 		dst = typeCalloc(const char *, nn + 1);
378 	}
379     }
380     if (showsummary) {
381 	fprintf(log_fp, "Entries that will be compiled:\n");
382 	for (n = 0; dst[n] != 0; n++)
383 	    fprintf(log_fp, "%d:%s\n", n + 1, dst[n]);
384     }
385     return dst;
386 }
387 
388 static bool
389 matches(const char **needle, const char *haystack)
390 /* does entry in needle list match |-separated field in haystack? */
391 {
392     bool code = FALSE;
393     size_t n;
394 
395     if (needle != 0) {
396 	for (n = 0; needle[n] != 0; n++) {
397 	    if (_nc_name_match(haystack, needle[n], "|")) {
398 		code = TRUE;
399 		break;
400 	    }
401 	}
402     } else
403 	code = TRUE;
404     return (code);
405 }
406 
407 static FILE *
408 open_tempfile(char *name)
409 {
410     FILE *result = 0;
411 #if HAVE_MKSTEMP
412     int fd = mkstemp(name);
413     if (fd >= 0)
414 	result = fdopen(fd, "w");
415 #else
416     if (tmpnam(name) != 0)
417 	result = fopen(name, "w");
418 #endif
419     return result;
420 }
421 
422 int
423 main(int argc, char *argv[])
424 {
425     char my_tmpname[PATH_MAX];
426     int v_opt = -1, debug_level;
427     int smart_defaults = TRUE;
428     char *termcap;
429     ENTRY *qp;
430 
431     int this_opt, last_opt = '?';
432 
433     int outform = F_TERMINFO;	/* output format */
434     int sortmode = S_TERMINFO;	/* sort_mode */
435 
436     int width = 60;
437     bool formatted = FALSE;	/* reformat complex strings? */
438     int numbers = 0;		/* format "%'char'" to/from "%{number}" */
439     bool infodump = FALSE;	/* running as captoinfo? */
440     bool capdump = FALSE;	/* running as infotocap? */
441     bool forceresolve = FALSE;	/* force resolution */
442     bool limited = TRUE;
443     char *tversion = (char *) NULL;
444     const char *source_file = "terminfo";
445     const char **namelst = 0;
446     char *outdir = (char *) NULL;
447     bool check_only = FALSE;
448 
449     log_fp = stderr;
450 
451     _nc_progname = _nc_basename(argv[0]);
452 
453     if ((infodump = (strcmp(_nc_progname, PROG_CAPTOINFO) == 0)) != FALSE) {
454 	outform = F_TERMINFO;
455 	sortmode = S_TERMINFO;
456     }
457     if ((capdump = (strcmp(_nc_progname, PROG_INFOTOCAP) == 0)) != FALSE) {
458 	outform = F_TERMCAP;
459 	sortmode = S_TERMCAP;
460     }
461 #if NCURSES_XNAMES
462     use_extended_names(FALSE);
463 #endif
464 
465     /*
466      * Processing arguments is a little complicated, since someone made a
467      * design decision to allow the numeric values for -w, -v options to
468      * be optional.
469      */
470     while ((this_opt = getopt(argc, argv,
471 			      "0123456789CILNR:TVace:fGgo:rsvwx")) != -1) {
472 	if (isdigit(this_opt)) {
473 	    switch (last_opt) {
474 	    case 'v':
475 		v_opt = (v_opt * 10) + (this_opt - '0');
476 		break;
477 	    case 'w':
478 		width = (width * 10) + (this_opt - '0');
479 		break;
480 	    default:
481 		if (this_opt != '1')
482 		    usage();
483 		last_opt = this_opt;
484 		width = 0;
485 	    }
486 	    continue;
487 	}
488 	switch (this_opt) {
489 	case 'C':
490 	    capdump = TRUE;
491 	    outform = F_TERMCAP;
492 	    sortmode = S_TERMCAP;
493 	    break;
494 	case 'I':
495 	    infodump = TRUE;
496 	    outform = F_TERMINFO;
497 	    sortmode = S_TERMINFO;
498 	    break;
499 	case 'L':
500 	    infodump = TRUE;
501 	    outform = F_VARIABLE;
502 	    sortmode = S_VARIABLE;
503 	    break;
504 	case 'N':
505 	    smart_defaults = FALSE;
506 	    break;
507 	case 'R':
508 	    tversion = optarg;
509 	    break;
510 	case 'T':
511 	    limited = FALSE;
512 	    break;
513 	case 'V':
514 	    puts(curses_version());
515 	    return EXIT_SUCCESS;
516 	case 'c':
517 	    check_only = TRUE;
518 	    break;
519 	case 'e':
520 	    namelst = make_namelist(optarg);
521 	    break;
522 	case 'f':
523 	    formatted = TRUE;
524 	    break;
525 	case 'G':
526 	    numbers = 1;
527 	    break;
528 	case 'g':
529 	    numbers = -1;
530 	    break;
531 	case 'o':
532 	    outdir = optarg;
533 	    break;
534 	case 'r':
535 	    forceresolve = TRUE;
536 	    break;
537 	case 's':
538 	    showsummary = TRUE;
539 	    break;
540 	case 'v':
541 	    v_opt = 0;
542 	    break;
543 	case 'w':
544 	    width = 0;
545 	    break;
546 #if NCURSES_XNAMES
547 	case 'a':
548 	    _nc_disable_period = TRUE;
549 	    /* FALLTHRU */
550 	case 'x':
551 	    use_extended_names(TRUE);
552 	    break;
553 #endif
554 	default:
555 	    usage();
556 	}
557 	last_opt = this_opt;
558     }
559 
560     debug_level = (v_opt > 0) ? v_opt : (v_opt == 0);
561     set_trace_level(debug_level);
562 
563     if (_nc_tracing) {
564 	save_check_termtype = _nc_check_termtype;
565 	_nc_check_termtype = check_termtype;
566     }
567 #if !HAVE_BIG_CORE
568     /*
569      * Aaargh! immedhook seriously hoses us!
570      *
571      * One problem with immedhook is it means we can't do -e.  Problem
572      * is that we can't guarantee that for each terminal listed, all the
573      * terminals it depends on will have been kept in core for reference
574      * resolution -- in fact it's certain the primitive types at the end
575      * of reference chains *won't* be in core unless they were explicitly
576      * in the select list themselves.
577      */
578     if (namelst && (!infodump && !capdump)) {
579 	(void) fprintf(stderr,
580 		       "Sorry, -e can't be used without -I or -C\n");
581 	cleanup();
582 	return EXIT_FAILURE;
583     }
584 #endif /* HAVE_BIG_CORE */
585 
586     if (optind < argc) {
587 	source_file = argv[optind++];
588 	if (optind < argc) {
589 	    fprintf(stderr,
590 		    "%s: Too many file names.  Usage:\n\t%s %s",
591 		    _nc_progname,
592 		    _nc_progname,
593 		    usage_string);
594 	    return EXIT_FAILURE;
595 	}
596     } else {
597 	if (infodump == TRUE) {
598 	    /* captoinfo's no-argument case */
599 	    source_file = "/usr/share/misc/termcap";
600 	    if ((termcap = getenv("TERMCAP")) != 0
601 		&& (namelst = make_namelist(getenv("TERM"))) != 0) {
602 		strlcpy(my_tmpname, "/tmp/XXXXXXXXXX", sizeof my_tmpname);
603 		if (access(termcap, F_OK) == 0) {
604 		    /* file exists */
605 		    source_file = termcap;
606 		} else if ((tmp_fp = open_tempfile(my_tmpname)) != 0) {
607 		    source_file = my_tmpname;
608 		    fprintf(tmp_fp, "%s\n", termcap);
609 		    fclose(tmp_fp);
610 		    tmp_fp = open_input(source_file);
611 		    to_remove = source_file;
612 		} else {
613 		    failed("mkstemp");
614 		}
615 	    }
616 	} else {
617 	    /* tic */
618 	    fprintf(stderr,
619 		    "%s: File name needed.  Usage:\n\t%s %s",
620 		    _nc_progname,
621 		    _nc_progname,
622 		    usage_string);
623 	    cleanup();
624 	    return EXIT_FAILURE;
625 	}
626     }
627 
628     if (tmp_fp == 0)
629 	tmp_fp = open_input(source_file);
630 
631     if (infodump)
632 	dump_init(tversion,
633 		  smart_defaults
634 		  ? outform
635 		  : F_LITERAL,
636 		  sortmode, width, debug_level, formatted);
637     else if (capdump)
638 	dump_init(tversion,
639 		  outform,
640 		  sortmode, width, debug_level, FALSE);
641 
642     /* parse entries out of the source file */
643     _nc_set_source(source_file);
644 #if !HAVE_BIG_CORE
645     if (!(check_only || infodump || capdump))
646 	_nc_set_writedir(outdir);
647 #endif /* HAVE_BIG_CORE */
648     _nc_read_entry_source(tmp_fp, (char *) NULL,
649 			  !smart_defaults, FALSE,
650 			  (check_only || infodump || capdump) ? NULLHOOK : immedhook);
651 
652     /* do use resolution */
653     if (check_only || (!infodump && !capdump) || forceresolve) {
654 	if (!_nc_resolve_uses(TRUE) && !check_only) {
655 	    cleanup();
656 	    return EXIT_FAILURE;
657 	}
658     }
659 
660     /* length check */
661     if (check_only && (capdump || infodump)) {
662 	for_entry_list(qp) {
663 	    if (matches(namelst, qp->tterm.term_names)) {
664 		int len = fmt_entry(&qp->tterm, NULL, TRUE, infodump, numbers);
665 
666 		if (len > (infodump ? MAX_TERMINFO_LENGTH : MAX_TERMCAP_LENGTH))
667 		    (void) fprintf(stderr,
668 				   "warning: resolved %s entry is %d bytes long\n",
669 				   _nc_first_name(qp->tterm.term_names),
670 				   len);
671 	    }
672 	}
673     }
674 
675     /* write or dump all entries */
676     if (!check_only) {
677 	if (!infodump && !capdump) {
678 	    _nc_set_writedir(outdir);
679 	    for_entry_list(qp) {
680 		if (matches(namelst, qp->tterm.term_names))
681 		    write_it(qp);
682 	    }
683 	} else {
684 	    /* this is in case infotocap() generates warnings */
685 	    _nc_curr_col = _nc_curr_line = -1;
686 
687 	    for_entry_list(qp) {
688 		if (matches(namelst, qp->tterm.term_names)) {
689 		    int j = qp->cend - qp->cstart;
690 		    int len = 0;
691 
692 		    /* this is in case infotocap() generates warnings */
693 		    _nc_set_type(_nc_first_name(qp->tterm.term_names));
694 
695 		    (void) fseek(tmp_fp, qp->cstart, SEEK_SET);
696 		    while (j--) {
697 			if (infodump)
698 			    (void) putchar(fgetc(tmp_fp));
699 			else
700 			    put_translate(fgetc(tmp_fp));
701 		    }
702 
703 		    len = dump_entry(&qp->tterm, limited, numbers, NULL);
704 		    for (j = 0; j < qp->nuses; j++)
705 			len += dump_uses(qp->uses[j].name, !capdump);
706 		    (void) putchar('\n');
707 		    if (debug_level != 0 && !limited)
708 			printf("# length=%d\n", len);
709 		}
710 	    }
711 	    if (!namelst && _nc_tail) {
712 		int c, oldc = '\0';
713 		bool in_comment = FALSE;
714 		bool trailing_comment = FALSE;
715 
716 		(void) fseek(tmp_fp, _nc_tail->cend, SEEK_SET);
717 		while ((c = fgetc(tmp_fp)) != EOF) {
718 		    if (oldc == '\n') {
719 			if (c == '#') {
720 			    trailing_comment = TRUE;
721 			    in_comment = TRUE;
722 			} else {
723 			    in_comment = FALSE;
724 			}
725 		    }
726 		    if (trailing_comment
727 			&& (in_comment || (oldc == '\n' && c == '\n')))
728 			putchar(c);
729 		    oldc = c;
730 		}
731 	    }
732 	}
733     }
734 
735     /* Show the directory into which entries were written, and the total
736      * number of entries
737      */
738     if (showsummary
739 	&& (!(check_only || infodump || capdump))) {
740 	int total = _nc_tic_written();
741 	if (total != 0)
742 	    fprintf(log_fp, "%d entries written to %s\n",
743 		    total,
744 		    _nc_tic_dir((char *) 0));
745 	else
746 	    fprintf(log_fp, "No entries written\n");
747     }
748     cleanup();
749     return (EXIT_SUCCESS);
750 }
751 
752 /*
753  * This bit of legerdemain turns all the terminfo variable names into
754  * references to locations in the arrays Booleans, Numbers, and Strings ---
755  * precisely what's needed (see comp_parse.c).
756  */
757 
758 TERMINAL *cur_term;		/* tweak to avoid linking lib_cur_term.c */
759 
760 #undef CUR
761 #define CUR tp->
762 
763 /*
764  * Returns the expected number of parameters for the given capability.
765  */
766 static int
767 expected_params(char *name)
768 {
769     /* *INDENT-OFF* */
770     static const struct {
771 	const char *name;
772 	int count;
773     } table[] = {
774 	{ "birep",		2 },
775 	{ "chr",		1 },
776 	{ "colornm",		1 },
777 	{ "cpi",		1 },
778 	{ "csnm",		1 },
779 	{ "csr",		2 },
780 	{ "cub",		1 },
781 	{ "cud",		1 },
782 	{ "cuf",		1 },
783 	{ "cup",		2 },
784 	{ "cuu",		1 },
785 	{ "cvr",		1 },
786 	{ "cwin",		5 },
787 	{ "dch",		1 },
788 	{ "defc",		3 },
789 	{ "dial",		1 },
790 	{ "dispc",		1 },
791 	{ "dl",			1 },
792 	{ "ech",		1 },
793 	{ "getm",		1 },
794 	{ "hpa",		1 },
795 	{ "ich",		1 },
796 	{ "il",			1 },
797 	{ "indn",		1 },
798 	{ "initc",		4 },
799 	{ "initp",		7 },
800 	{ "lpi",		1 },
801 	{ "mc5p",		1 },
802 	{ "mrcup",		2 },
803 	{ "mvpa",		1 },
804 	{ "pfkey",		2 },
805 	{ "pfloc",		2 },
806 	{ "pfx",		2 },
807 	{ "pfxl",		3 },
808 	{ "pln",		2 },
809 	{ "qdial",		1 },
810 	{ "rcsd",		1 },
811 	{ "rep",		2 },
812 	{ "rin",		1 },
813 	{ "sclk",		3 },
814 	{ "scp",		1 },
815 	{ "scs",		1 },
816 	{ "scsd",		2 },
817 	{ "setab",		1 },
818 	{ "setaf",		1 },
819 	{ "setb",		1 },
820 	{ "setcolor",		1 },
821 	{ "setf",		1 },
822 	{ "sgr",		9 },
823 	{ "sgr1",		6 },
824 	{ "slength",		1 },
825 	{ "slines",		1 },
826 	{ "smgbp",		2 },
827 	{ "smglp",		2 },
828 	{ "smglr",		2 },
829 	{ "smgrp",		1 },
830 	{ "smgtb",		2 },
831 	{ "smgtp",		1 },
832 	{ "tsl",		1 },
833 	{ "u6",			-1 },
834 	{ "vpa",		1 },
835 	{ "wind",		4 },
836 	{ "wingo",		1 },
837     };
838     /* *INDENT-ON* */
839 
840     unsigned n;
841     int result = 0;		/* function-keys, etc., use none */
842 
843     for (n = 0; n < SIZEOF(table); n++) {
844 	if (!strcmp(name, table[n].name)) {
845 	    result = table[n].count;
846 	    break;
847 	}
848     }
849 
850     return result;
851 }
852 
853 /*
854  * Make a quick sanity check for the parameters which are used in the given
855  * strings.  If there are no "%p" tokens, then there should be no other "%"
856  * markers.
857  */
858 static void
859 check_params(TERMTYPE * tp, char *name, char *value)
860 {
861     int expected = expected_params(name);
862     int actual = 0;
863     int n;
864     bool params[10];
865     char *s = value;
866 
867     for (n = 0; n < 10; n++)
868 	params[n] = FALSE;
869 
870     while (*s != 0) {
871 	if (*s == '%') {
872 	    if (*++s == '\0') {
873 		_nc_warning("expected character after %% in %s", name);
874 		break;
875 	    } else if (*s == 'p') {
876 		if (*++s == '\0' || !isdigit((int) *s)) {
877 		    _nc_warning("expected digit after %%p in %s", name);
878 		    return;
879 		} else {
880 		    n = (*s - '0');
881 		    if (n > actual)
882 			actual = n;
883 		    params[n] = TRUE;
884 		}
885 	    }
886 	}
887 	s++;
888     }
889 
890     if (params[0]) {
891 	_nc_warning("%s refers to parameter 0 (%%p0), which is not allowed", name);
892     }
893     if (value == set_attributes || expected < 0) {
894 	;
895     } else if (expected != actual) {
896 	_nc_warning("%s uses %d parameters, expected %d", name,
897 		    actual, expected);
898 	for (n = 1; n < actual; n++) {
899 	    if (!params[n])
900 		_nc_warning("%s omits parameter %d", name, n);
901 	}
902     }
903 }
904 
905 /*
906  * An sgr string may contain several settings other than the one we're
907  * interested in, essentially sgr0 + rmacs + whatever.  As long as the
908  * "whatever" is contained in the sgr string, that is close enough for our
909  * sanity check.
910  */
911 static bool
912 similar_sgr(char *a, char *b)
913 {
914     while (*b != 0) {
915 	while (*a != *b) {
916 	    if (*a == 0)
917 		return FALSE;
918 	    a++;
919 	}
920 	a++;
921 	b++;
922     }
923     return TRUE;
924 }
925 
926 static void
927 check_sgr(TERMTYPE * tp, char *zero, int num, char *cap, const char *name)
928 {
929     char *test = tparm(set_attributes,
930 		       num == 1,
931 		       num == 2,
932 		       num == 3,
933 		       num == 4,
934 		       num == 5,
935 		       num == 6,
936 		       num == 7,
937 		       num == 8,
938 		       num == 9);
939     if (test != 0) {
940 	if (PRESENT(cap)) {
941 	    if (!similar_sgr(test, cap)) {
942 		_nc_warning("%s differs from sgr(%d): %s", name, num,
943 			    _nc_visbuf(test));
944 	    }
945 	} else if (strcmp(test, zero)) {
946 	    _nc_warning("sgr(%d) present, but not %s", num, name);
947 	}
948     } else if (PRESENT(cap)) {
949 	_nc_warning("sgr(%d) missing, but %s present", num, name);
950     }
951 }
952 
953 #define CHECK_SGR(num,name) check_sgr(tp, zero, num, name, #name)
954 
955 /* other sanity-checks (things that we don't want in the normal
956  * logic that reads a terminfo entry)
957  */
958 static void
959 check_termtype(TERMTYPE * tp)
960 {
961     bool conflict = FALSE;
962     unsigned j, k;
963     char fkeys[STRCOUNT];
964 
965     /*
966      * A terminal entry may contain more than one keycode assigned to
967      * a given string (e.g., KEY_END and KEY_LL).  But curses will only
968      * return one (the last one assigned).
969      */
970     memset(fkeys, 0, sizeof(fkeys));
971     for (j = 0; _nc_tinfo_fkeys[j].code; j++) {
972 	char *a = tp->Strings[_nc_tinfo_fkeys[j].offset];
973 	bool first = TRUE;
974 	if (!VALID_STRING(a))
975 	    continue;
976 	for (k = j + 1; _nc_tinfo_fkeys[k].code; k++) {
977 	    char *b = tp->Strings[_nc_tinfo_fkeys[k].offset];
978 	    if (!VALID_STRING(b)
979 		|| fkeys[k])
980 		continue;
981 	    if (!strcmp(a, b)) {
982 		fkeys[j] = 1;
983 		fkeys[k] = 1;
984 		if (first) {
985 		    if (!conflict) {
986 			_nc_warning("Conflicting key definitions (using the last)");
987 			conflict = TRUE;
988 		    }
989 		    fprintf(stderr, "... %s is the same as %s",
990 			    keyname(_nc_tinfo_fkeys[j].code),
991 			    keyname(_nc_tinfo_fkeys[k].code));
992 		    first = FALSE;
993 		} else {
994 		    fprintf(stderr, ", %s",
995 			    keyname(_nc_tinfo_fkeys[k].code));
996 		}
997 	    }
998 	}
999 	if (!first)
1000 	    fprintf(stderr, "\n");
1001     }
1002 
1003     for (j = 0; j < NUM_STRINGS(tp); j++) {
1004 	char *a = tp->Strings[j];
1005 	if (VALID_STRING(a))
1006 	    check_params(tp, ExtStrname(tp, j, strnames), a);
1007     }
1008 
1009     /*
1010      * Quick check for color.  We could also check if the ANSI versus
1011      * non-ANSI strings are misused.
1012      */
1013     if ((max_colors > 0) != (max_pairs > 0)
1014 	|| (max_colors > max_pairs))
1015 	_nc_warning("inconsistent values for max_colors and max_pairs");
1016 
1017     PAIRED(set_foreground, set_background);
1018     PAIRED(set_a_foreground, set_a_background);
1019 
1020     /*
1021      * These may be mismatched because the terminal description relies on
1022      * restoring the cursor visibility by resetting it.
1023      */
1024     ANDMISSING(cursor_invisible, cursor_normal);
1025     ANDMISSING(cursor_visible, cursor_normal);
1026 
1027     if (PRESENT(cursor_visible) && PRESENT(cursor_normal)
1028 	&& !strcmp(cursor_visible, cursor_normal))
1029 	_nc_warning("cursor_visible is same as cursor_normal");
1030 
1031     /*
1032      * From XSI & O'Reilly, we gather that sc/rc are required if csr is
1033      * given, because the cursor position after the scrolling operation is
1034      * performed is undefined.
1035      */
1036     ANDMISSING(change_scroll_region, save_cursor);
1037     ANDMISSING(change_scroll_region, restore_cursor);
1038 
1039     if (PRESENT(set_attributes)) {
1040 	char *zero = tparm(set_attributes, 0, 0, 0, 0, 0, 0, 0, 0, 0);
1041 
1042 	zero = strdup(zero);
1043 	CHECK_SGR(1, enter_standout_mode);
1044 	CHECK_SGR(2, enter_underline_mode);
1045 	CHECK_SGR(3, enter_reverse_mode);
1046 	CHECK_SGR(4, enter_blink_mode);
1047 	CHECK_SGR(5, enter_dim_mode);
1048 	CHECK_SGR(6, enter_bold_mode);
1049 	CHECK_SGR(7, enter_secure_mode);
1050 	CHECK_SGR(8, enter_protected_mode);
1051 	CHECK_SGR(9, enter_alt_charset_mode);
1052 	free(zero);
1053     }
1054 
1055     /*
1056      * Some standard applications (e.g., vi) and some non-curses
1057      * applications (e.g., jove) get confused if we have both ich/ich1 and
1058      * smir/rmir.  Let's be nice and warn about that, too, even though
1059      * ncurses handles it.
1060      */
1061     if ((PRESENT(enter_insert_mode) || PRESENT(exit_insert_mode))
1062 	&& (PRESENT(insert_character) || PRESENT(parm_ich))) {
1063 	_nc_warning("non-curses applications may be confused by ich/ich1 with smir/rmir");
1064     }
1065 
1066     /*
1067      * Finally, do the non-verbose checks
1068      */
1069     if (save_check_termtype != 0)
1070 	save_check_termtype(tp);
1071 }
1072