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