xref: /netbsd-src/usr.bin/infocmp/infocmp.c (revision 7330f729ccf0bd976a06f95fad452fe774fc7fd1)
1 /* $NetBSD: infocmp.c,v 1.12 2017/05/16 09:21:54 roy Exp $ */
2 
3 /*
4  * Copyright (c) 2009, 2010 The NetBSD Foundation, Inc.
5  *
6  * This code is derived from software contributed to The NetBSD Foundation
7  * by Roy Marples.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29 
30 #include <sys/cdefs.h>
31 __RCSID("$NetBSD: infocmp.c,v 1.12 2017/05/16 09:21:54 roy Exp $");
32 
33 #include <sys/ioctl.h>
34 
35 #include <ctype.h>
36 #include <err.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <term_private.h>
41 #include <term.h>
42 #include <unistd.h>
43 #include <util.h>
44 
45 #define SW 8
46 
47 typedef struct tient {
48 	char type;
49 	const char *id;
50 	signed char flag;
51 	short num;
52 	const char *str;
53 } TIENT;
54 
55 static size_t cols;
56 static int aflag, cflag, nflag, qflag, xflag;
57 
58 static size_t
59 outstr(FILE *f, const char *str)
60 {
61 	unsigned char ch;
62 	size_t r, l;
63 
64 	r = 0;
65 	l = strlen(str);
66 	while ((ch = (unsigned char)(*str++)) != '\0') {
67 		switch (ch) {
68 		case 128:
69 			ch = '0';
70 			break;
71 		case '\033':
72 			ch = 'E';
73 			break;
74 		case '\014':
75 			ch = 'f';
76 			break;
77 		case '^': /* FALLTHROUGH */
78 		case ',': /* escape these */
79 			break;
80 		case ' ':
81 			ch = 's';
82 			break;
83 		default:
84 			if (ch == '\177') {
85 				if (f != NULL)
86 					fputc('^', f);
87 				ch = '?';
88 				r++;
89 			} else if (iscntrl(ch) &&
90 			    ch < 128 &&
91 			    ch != '\\' &&
92 			    (l < 4 || isdigit((unsigned char)*str)))
93 			{
94 				if (f != NULL)
95 					fputc('^', f);
96 				ch += '@';
97 				r++;
98 			} else if (!isprint(ch)) {
99 				if (f != NULL)
100 					fprintf(f, "\\%03o", ch);
101 				r += 4;
102 				continue;
103 			}
104 			goto prnt;
105 		}
106 
107 		if (f != NULL)
108 			fputc('\\', f);
109 		r++;
110 prnt:
111 		if (f != NULL)
112 			fputc(ch, f);
113 		r++;
114 	}
115 	return r;
116 }
117 
118 static int
119 ent_compare(const void *a, const void *b)
120 {
121 	const TIENT *ta, *tb;
122 
123 	ta = (const TIENT *)a;
124 	tb = (const TIENT *)b;
125 	return strcmp(ta->id, tb->id);
126 }
127 
128 static void
129 setdb(char *db)
130 {
131 	size_t len;
132 
133 	len = strlen(db);
134 	if (len > 3 &&
135 	    db[len - 3] == '.' &&
136 	    db[len - 2] == 'd' &&
137 	    db[len - 1] == 'b')
138 		db[len - 3] = '\0';
139 	setenv("TERMINFO", db, 1);
140 }
141 
142 static void
143 print_ent(const TIENT *ents, size_t nents)
144 {
145 	size_t col, i, l;
146 	char nbuf[64];
147 
148 	if (nents == 0)
149 		return;
150 
151 	col = SW;
152 	printf("\t");
153 	for (i = 0; i < nents; i++) {
154 		if (*ents[i].id == '.' && aflag == 0)
155 			continue;
156 		switch (ents[i].type) {
157 		case 'f':
158 			if (ents[i].flag == ABSENT_BOOLEAN)
159 				continue;
160 			l = strlen(ents[i].id) + 2;
161 			if (ents[i].flag == CANCELLED_BOOLEAN)
162 				l++;
163 			break;
164 		case 'n':
165 			if (ents[i].num == ABSENT_NUMERIC)
166 				continue;
167 			if (VALID_NUMERIC(ents[i].num))
168 				l = snprintf(nbuf, sizeof(nbuf), "%s#%d,",
169 				    ents[i].id, ents[i].num);
170 			else
171 				l = snprintf(nbuf, sizeof(nbuf), "%s@,",
172 				    ents[i].id);
173 			break;
174 		case 's':
175 			if (ents[i].str == ABSENT_STRING)
176 				continue;
177 			if (VALID_STRING(ents[i].str))
178 				l = strlen(ents[i].id) +
179 				    outstr(NULL, ents[i].str) + 7;
180 			else
181 				l = strlen(ents[i].id) + 3;
182 			break;
183 		default:
184 			errx(EXIT_FAILURE, "invalid type");
185 		}
186 		if (col != SW) {
187 			if (col + l > cols) {
188 				printf("\n\t");
189 				col = SW;
190 			} else
191 				col += printf(" ");
192 		}
193 		switch (ents[i].type) {
194 		case 'f':
195 			col += printf("%s", ents[i].id);
196 			if (ents[i].flag == ABSENT_BOOLEAN ||
197 			    ents[i].flag == CANCELLED_BOOLEAN)
198 				col += printf("@");
199 			col += printf(",");
200 			break;
201 		case 'n':
202 			col += printf("%s", nbuf);
203 			break;
204 		case 's':
205 			col += printf("%s", ents[i].id);
206 			if (VALID_STRING(ents[i].str)) {
207 				col += printf("=");
208 				col += outstr(stdout, ents[i].str);
209 			} else
210 				col += printf("@");
211 			col += printf(",");
212 			break;
213 		}
214 	}
215 	printf("\n");
216 }
217 
218 static size_t
219 load_ents(TIENT *ents, TERMINAL *t, char type)
220 {
221 	size_t i, n, max;
222 	TERMUSERDEF *ud;
223 
224 	switch (type) {
225 	case 'f':
226 		max = TIFLAGMAX;
227 		break;
228 	case 'n':
229 		max = TINUMMAX;
230 		break;
231 	default:
232 		max = TISTRMAX;
233 	}
234 
235 	n = 0;
236 	for (i = 0; i <= max; i++) {
237 		switch (type) {
238 		case 'f':
239 			if (t->flags[i] == 1 ||
240 			    (aflag && t->flags[i] == CANCELLED_BOOLEAN))
241 			{
242 				ents[n].id = _ti_flagid(i);
243 				ents[n].type = 'f';
244 				ents[n++].flag = t->flags[i];
245 			}
246 			break;
247 		case 'n':
248 			if (VALID_NUMERIC(t->nums[i]) ||
249 			    (aflag && t->nums[i] == CANCELLED_NUMERIC))
250 			{
251 				ents[n].id = _ti_numid(i);
252 				ents[n].type = 'n';
253 				ents[n++].num = t->nums[i];
254 			}
255 			break;
256 		default:
257 			if (VALID_STRING(t->strs[i]) ||
258 			    (aflag && t->strs[i] == CANCELLED_STRING))
259 			{
260 				ents[n].id = _ti_strid(i);
261 				ents[n].type = 's';
262 				ents[n++].str = t->strs[i];
263 			}
264 			break;
265 		}
266 	}
267 
268 	if (xflag != 0 && t->_nuserdefs != 0) {
269 		for (i = 0; i < t->_nuserdefs; i++) {
270 			ud = &t->_userdefs[i];
271 			if (ud->type == type) {
272 				switch (type) {
273 				case 'f':
274 					if (!aflag &&
275 					    !VALID_BOOLEAN(ud->flag))
276 						continue;
277 					break;
278 				case 'n':
279 					if (!aflag &&
280 					    !VALID_NUMERIC(ud->num))
281 						continue;
282 					break;
283 				case 's':
284 					if (!aflag &&
285 					    !VALID_STRING(ud->str))
286 						continue;
287 					break;
288 				}
289 				ents[n].id = ud->id;
290 				ents[n].type = ud->type;
291 				ents[n].flag = ud->flag;
292 				ents[n].num = ud->num;
293 				ents[n++].str = ud->str;
294 			}
295 		}
296 	}
297 
298 	qsort(ents, n, sizeof(TIENT), ent_compare);
299 	return n;
300 }
301 
302 static void
303 cprint_ent(TIENT *ent)
304 {
305 
306 	if (ent == NULL) {
307 		if (qflag == 0)
308 			printf("NULL");
309 		else
310 			printf("-");
311 	}
312 
313 	switch (ent->type) {
314 	case 'f':
315 		if (VALID_BOOLEAN(ent->flag))
316 			printf(ent->flag == 1 ? "T" : "F");
317 		else if (qflag == 0)
318 			printf("F");
319 		else if (ent->flag == CANCELLED_BOOLEAN)
320 			printf("@");
321 		else
322 			printf("-");
323 		break;
324 	case 'n':
325 		if (VALID_NUMERIC(ent->num))
326 			printf("%d", ent->num);
327 		else if (qflag == 0)
328 			printf("NULL");
329 		else if (ent->num == CANCELLED_NUMERIC)
330 			printf("@");
331 		else
332 			printf("-");
333 		break;
334 	case 's':
335 		if (VALID_STRING(ent->str)) {
336 			printf("'");
337 			outstr(stdout, ent->str);
338 			printf("'");
339 		} else if (qflag == 0)
340 			printf("NULL");
341 		else if (ent->str == CANCELLED_STRING)
342 			printf("@");
343 		else
344 			printf("-");
345 		break;
346 	}
347 }
348 
349 static void
350 compare_ents(TIENT *ents1, size_t n1, TIENT *ents2, size_t n2)
351 {
352 	size_t i1, i2;
353 	TIENT *e1, *e2, ee;
354 	int c;
355 
356 	i1 = i2 = 0;
357 	ee.type = 'f';
358 	ee.flag = ABSENT_BOOLEAN;
359 	ee.num = ABSENT_NUMERIC;
360 	ee.str = ABSENT_STRING;
361 	while (i1 != n1 || i2 != n2) {
362 		if (i1 == n1)
363 			c = 1;
364 		else if (i2 == n2)
365 			c = -1;
366 		else
367 			c = strcmp(ents1[i1].id, ents2[i2].id);
368 		if (c == 0) {
369 			e1 = &ents1[i1++];
370 			e2 = &ents2[i2++];
371 		} else if (c < 0) {
372 			e1 = &ents1[i1++];
373 			e2 = &ee;
374 			ee.id = e1->id;
375 			ee.type = e1->type;
376 		} else {
377 			e1 = &ee;
378 			e2 = &ents2[i2++];
379 			ee.id = e2->id;
380 			ee.type = e2->type;
381 		}
382 		switch (e1->type) {
383 		case 'f':
384 			if (cflag != 0) {
385 				if (e1->flag == e2->flag)
386 					printf("\t%s\n", ents1[i1].id);
387 				continue;
388 			}
389 			if (e1->flag == e2->flag)
390 				continue;
391 			break;
392 		case 'n':
393 			if (cflag != 0) {
394 				if (e1->num == e2->num)
395 					printf("\t%s#%d\n",
396 					    ents1[i1].id, ents1[i1].num);
397 				continue;
398 			}
399 			if (e1->num == e2->num)
400 				continue;
401 			break;
402 		case 's':
403 			if (cflag != 0) {
404 				if (VALID_STRING(e1->str) &&
405 				    VALID_STRING(e2->str) &&
406 				    strcmp(e1->str, e2->str) == 0) {
407 					printf("\t%s=", ents1[i1].id);
408 					outstr(stdout, ents1[i1].str);
409 					printf("\n");
410 				}
411 				continue;
412 			}
413 			if (VALID_STRING(e1->str) &&
414 			    VALID_STRING(e2->str) &&
415 			    strcmp(e1->str, e2->str) == 0)
416 				continue;
417 			break;
418 		}
419 		printf("\t%s: ", e1->id);
420 		cprint_ent(e1);
421 		if (e1->type == 'f')
422 			printf(":");
423 		else
424 			printf(", ");
425 		cprint_ent(e2);
426 		printf(".\n");
427 	}
428 }
429 
430 static TERMINAL *
431 load_term(const char *name)
432 {
433 	TERMINAL *t;
434 
435 	t = ecalloc(1, sizeof(*t));
436 	if (name == NULL)
437 		name = getenv("TERM");
438 	if (name == NULL)
439 		name = "dumb";
440 	if (_ti_getterm(t, name, 1) == 1)
441 		return t;
442 
443 	if (_ti_database == NULL)
444 		errx(EXIT_FAILURE, "no terminal definition found in internal database");
445 	else
446 		errx(EXIT_FAILURE, "no terminal definition found in %s.db", _ti_database);
447 }
448 
449 static void
450 show_missing(TERMINAL *t1, TERMINAL *t2, char type)
451 {
452 	ssize_t i, max;
453 	const char *id;
454 
455 	switch (type) {
456 	case 'f':
457 		max = TIFLAGMAX;
458 		break;
459 	case 'n':
460 		max = TINUMMAX;
461 		break;
462 	default:
463 		max = TISTRMAX;
464 	}
465 
466 	for (i = 0; i <= max; i++) {
467 		switch (type) {
468 		case 'f':
469 			if (t1->flags[i] != ABSENT_BOOLEAN ||
470 			    t2->flags[i] != ABSENT_BOOLEAN)
471 				continue;
472 			id = _ti_flagid(i);
473 			break;
474 		case 'n':
475 			if (t1->nums[i] != ABSENT_NUMERIC ||
476 			    t2->nums[i] != ABSENT_NUMERIC)
477 				continue;
478 			id = _ti_numid(i);
479 			break;
480 		default:
481 			if (t1->strs[i] != ABSENT_STRING ||
482 			    t2->strs[i] != ABSENT_STRING)
483 				continue;
484 			id = _ti_strid(i);
485 			break;
486 		}
487 		printf("\t!%s.\n", id);
488 	}
489 }
490 
491 static TERMUSERDEF *
492 find_userdef(TERMINAL *term, const char *id)
493 {
494 	size_t i;
495 
496 	for (i = 0; i < term->_nuserdefs; i++)
497 		if (strcmp(term->_userdefs[i].id, id) == 0)
498 			return &term->_userdefs[i];
499 	return NULL;
500 }
501 
502 static void
503 use_terms(TERMINAL *term, size_t nuse, char **uterms)
504 {
505 	TERMINAL **terms;
506 	TERMUSERDEF *ud, *tud;
507 	size_t i, j, agree, absent, data;
508 
509 	terms = ecalloc(nuse, sizeof(*terms));
510 	for (i = 0; i < nuse; i++) {
511 		if (strcmp(term->name, *uterms) == 0)
512 			errx(EXIT_FAILURE, "cannot use same terminal");
513 		for (j = 0; j < i; j++)
514 			if (strcmp(terms[j]->name, *uterms) == 0)
515 				errx(EXIT_FAILURE, "cannot use same terminal");
516 		terms[i] = load_term(*uterms++);
517 	}
518 
519 	for (i = 0; i < TIFLAGMAX + 1; i++) {
520 		agree = absent = data = 0;
521 		for (j = 0; j < nuse; j++) {
522 			if (terms[j]->flags[i] == ABSENT_BOOLEAN ||
523 			    terms[j]->flags[i] == CANCELLED_BOOLEAN)
524 				absent++;
525 			else {
526 				data++;
527 				if (term->flags[i] == terms[j]->flags[i])
528 					agree++;
529 			}
530 		}
531 		if (data == 0)
532 			continue;
533 		if (agree > 0 && agree + absent == nuse)
534 			term->flags[i] = ABSENT_BOOLEAN;
535 		else if (term->flags[i] == ABSENT_BOOLEAN)
536 			term->flags[i] = CANCELLED_BOOLEAN;
537 	}
538 
539 	for (i = 0; i < TINUMMAX + 1; i++) {
540 		agree = absent = data = 0;
541 		for (j = 0; j < nuse; j++) {
542 			if (terms[j]->nums[i] == ABSENT_NUMERIC ||
543 			    terms[j]->nums[i] == CANCELLED_NUMERIC)
544 				absent++;
545 			else {
546 				data++;
547 				if (term->nums[i] == terms[j]->nums[i])
548 					agree++;
549 			}
550 		}
551 		if (data == 0)
552 			continue;
553 		if (agree > 0 && agree + absent == nuse)
554 			term->nums[i] = ABSENT_NUMERIC;
555 		else if (term->nums[i] == ABSENT_NUMERIC)
556 			term->nums[i] = CANCELLED_NUMERIC;
557 	}
558 
559 	for (i = 0; i < TISTRMAX + 1; i++) {
560 		agree = absent = data = 0;
561 		for (j = 0; j < nuse; j++) {
562 			if (terms[j]->strs[i] == ABSENT_STRING ||
563 			    terms[j]->strs[i] == CANCELLED_STRING)
564 				absent++;
565 			else {
566 				data++;
567 				if (VALID_STRING(term->strs[i]) &&
568 				    strcmp(term->strs[i],
569 					terms[j]->strs[i]) == 0)
570 					agree++;
571 			}
572 		}
573 		if (data == 0)
574 			continue;
575 		if (agree > 0 && agree + absent == nuse)
576 			term->strs[i] = ABSENT_STRING;
577 		else if (term->strs[i] == ABSENT_STRING)
578 			term->strs[i] = CANCELLED_STRING;
579 	}
580 
581 	/* User defined caps are more tricky.
582 	   First we set any to absent that agree. */
583 	for (i = 0; i < term->_nuserdefs; i++) {
584 		agree = absent = data = 0;
585 		ud = &term->_userdefs[i];
586 		for (j = 0; j < nuse; j++) {
587 			tud = find_userdef(terms[j], ud->id);
588 			if (tud == NULL)
589 				absent++;
590 			else {
591 				data++;
592 				switch (ud->type) {
593 				case 'f':
594 					if (tud->type == 'f' &&
595 					    tud->flag == ud->flag)
596 						agree++;
597 					break;
598 				case 'n':
599 					if (tud->type == 'n' &&
600 					    tud->num == ud->num)
601 						agree++;
602 					break;
603 				case 's':
604 					if (tud->type == 's' &&
605 					    VALID_STRING(tud->str) &&
606 					    VALID_STRING(ud->str) &&
607 					    strcmp(ud->str, tud->str) == 0)
608 						agree++;
609 					break;
610 				}
611 			}
612 		}
613 		if (data == 0)
614 			continue;
615 		if (agree > 0 && agree + absent == nuse) {
616 			ud->flag = ABSENT_BOOLEAN;
617 			ud->num = ABSENT_NUMERIC;
618 			ud->str = ABSENT_STRING;
619 		}
620 	}
621 
622 	/* Now add any that we don't have as cancelled */
623 	for (i = 0; i < nuse; i++) {
624 		for (j = 0; j < terms[i]->_nuserdefs; j++) {
625 			ud = find_userdef(term, terms[i]->_userdefs[j].id);
626 			if (ud != NULL)
627 				continue; /* We have handled this */
628 			term->_userdefs = erealloc(term->_userdefs,
629 			    sizeof(*term->_userdefs) * (term->_nuserdefs + 1));
630 			tud = &term->_userdefs[term->_nuserdefs++];
631 			tud->id = terms[i]->_userdefs[j].id;
632 			tud->type = terms[i]->_userdefs[j].flag;
633 			tud->flag = CANCELLED_BOOLEAN;
634 			tud->num = CANCELLED_NUMERIC;
635 			tud->str = CANCELLED_STRING;
636 		}
637 	}
638 }
639 
640 int
641 main(int argc, char **argv)
642 {
643 	char *term, *Barg;
644 	int ch, uflag;
645 	TERMINAL *t, *t2;
646 	size_t n, n2;
647 	struct winsize ws;
648 	TIENT ents[TISTRMAX + 1], ents2[TISTRMAX + 1];
649 
650 	cols = 80; /* default */
651 	term = getenv("COLUMNS");
652 	if (term != NULL)
653 		cols = strtoul(term, NULL, 10);
654 	else if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0)
655 		cols = ws.ws_col;
656 
657 	uflag = xflag = 0;
658 	Barg = NULL;
659 	while ((ch = getopt(argc, argv, "1A:B:acnquw:x")) != -1)
660 		switch (ch) {
661 		case '1':
662 			cols = 1;
663 			break;
664 		case 'A':
665 			setdb(optarg);
666 			break;
667 		case 'B':
668 			Barg = optarg;
669 			break;
670 		case 'a':
671 			aflag = 1;
672 			break;
673 		case 'c':
674 			cflag = 1;
675 			break;
676 		case 'n':
677 			nflag = 1;
678 			break;
679 		case 'q':
680 			qflag = 1;
681 			break;
682 		case 'u':
683 			uflag = 1;
684 			aflag = 1;
685 			break;
686 		case 'w':
687 			cols = strtoul(optarg, NULL, 10);
688 			break;
689 		case 'x':
690 			xflag = 1;
691 			break;
692 		case '?':
693 		default:
694 			fprintf(stderr,
695 			    "usage: %s [-1acnqux] [-A database] [-B database] "
696 			    "[-w cols] [term]\n",
697 			    getprogname());
698 			return EXIT_FAILURE;
699 		}
700 	cols--;
701 
702 	if (optind + 1 < argc)
703 		aflag = 1;
704 
705 	if (optind < argc)
706 		term = argv[optind++];
707 	else
708 		term = NULL;
709 	t = load_term(term);
710 
711 	if (uflag != 0)
712 		use_terms(t, argc - optind, argv + optind);
713 
714 	if ((optind + 1 != argc && nflag == 0) || uflag != 0) {
715 		if (uflag == 0)
716 			printf("# Reconstructed from %s\n",
717 			     _ti_database == NULL ?
718 			     "internal database" : _ti_database);
719 		printf("%s", t->name);
720 		if (t->_alias != NULL && *t->_alias != '\0')
721 			printf("|%s", t->_alias);
722 		if (t->desc != NULL && *t->desc != '\0')
723 			printf("|%s", t->desc);
724 		printf(",\n");
725 
726 		n = load_ents(ents, t, 'f');
727 		print_ent(ents, n);
728 		n = load_ents(ents, t, 'n');
729 		print_ent(ents, n);
730 		n = load_ents(ents, t, 's');
731 		print_ent(ents, n);
732 
733 		if (uflag != 0) {
734 			printf("\t");
735 			n = SW;
736 			for (; optind < argc; optind++) {
737 				n2 = 5 + strlen(argv[optind]);
738 				if (n != SW) {
739 					if (n + n2 > cols) {
740 						printf("\n\t");
741 						n = SW;
742 					} else
743 						n += printf(" ");
744 				}
745 				n += printf("use=%s,", argv[optind]);
746 			}
747 			printf("\n");
748 		}
749 		return EXIT_SUCCESS;
750 	}
751 
752 	if (Barg == NULL)
753 		unsetenv("TERMINFO");
754 	else
755 		setdb(Barg);
756 	t2 = load_term(argv[optind++]);
757 	printf("comparing %s to %s.\n", t->name, t2->name);
758 	if (qflag == 0)
759 		printf("    comparing booleans.\n");
760 	if (nflag == 0) {
761 		n = load_ents(ents, t, 'f');
762 		n2 = load_ents(ents2, t2, 'f');
763 		compare_ents(ents, n, ents2, n2);
764 	} else
765 		show_missing(t, t2, 'f');
766 	if (qflag == 0)
767 		printf("    comparing numbers.\n");
768 	if (nflag == 0) {
769 		n = load_ents(ents, t, 'n');
770 		n2 = load_ents(ents2, t2, 'n');
771 		compare_ents(ents, n, ents2, n2);
772 	} else
773 		show_missing(t, t2, 'n');
774 	if (qflag == 0)
775 		printf("    comparing strings.\n");
776 	if (nflag == 0) {
777 		n = load_ents(ents, t, 's');
778 		n2 = load_ents(ents2, t2, 's');
779 		compare_ents(ents, n, ents2, n2);
780 	} else
781 		show_missing(t, t2, 's');
782 	return EXIT_SUCCESS;
783 }
784