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