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