1 /* $NetBSD: infocmp.c,v 1.17 2020/03/31 12:44:15 roy Exp $ */
2
3 /*
4 * Copyright (c) 2009, 2010, 2020 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.17 2020/03/31 12:44:15 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 int 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
outstr(FILE * f,const char * str)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
ent_compare(const void * a,const void * b)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
setdb(char * db)129 setdb(char *db)
130 {
131 static const char *ext[] = { ".cdb", ".db" };
132
133 for (size_t i = 0; i < __arraycount(ext); i++) {
134 char *ptr = strstr(db, ext[i]);
135 if (ptr == NULL || ptr[strlen(ext[i])] != '\0')
136 continue;
137 *ptr = '\0';
138 break;
139 }
140 setenv("TERMINFO", db, 1);
141 }
142
143 static void
print_ent(const TIENT * ents,size_t nents)144 print_ent(const TIENT *ents, size_t nents)
145 {
146 size_t col, i, l;
147 char nbuf[64];
148
149 if (nents == 0)
150 return;
151
152 col = SW;
153 printf("\t");
154 for (i = 0; i < nents; i++) {
155 if (*ents[i].id == '.' && aflag == 0)
156 continue;
157 switch (ents[i].type) {
158 case 'f':
159 if (ents[i].flag == ABSENT_BOOLEAN)
160 continue;
161 l = strlen(ents[i].id) + 2;
162 if (ents[i].flag == CANCELLED_BOOLEAN)
163 l++;
164 break;
165 case 'n':
166 if (ents[i].num == ABSENT_NUMERIC)
167 continue;
168 if (VALID_NUMERIC(ents[i].num))
169 l = snprintf(nbuf, sizeof(nbuf), "%s#%d,",
170 ents[i].id, ents[i].num);
171 else
172 l = snprintf(nbuf, sizeof(nbuf), "%s@,",
173 ents[i].id);
174 break;
175 case 's':
176 if (ents[i].str == ABSENT_STRING)
177 continue;
178 if (VALID_STRING(ents[i].str))
179 l = strlen(ents[i].id) +
180 outstr(NULL, ents[i].str) + 7;
181 else
182 l = strlen(ents[i].id) + 3;
183 break;
184 default:
185 errx(EXIT_FAILURE, "invalid type");
186 }
187 if (col != SW) {
188 if (col + l > cols) {
189 printf("\n\t");
190 col = SW;
191 } else
192 col += printf(" ");
193 }
194 switch (ents[i].type) {
195 case 'f':
196 col += printf("%s", ents[i].id);
197 if (ents[i].flag == ABSENT_BOOLEAN ||
198 ents[i].flag == CANCELLED_BOOLEAN)
199 col += printf("@");
200 col += printf(",");
201 break;
202 case 'n':
203 col += printf("%s", nbuf);
204 break;
205 case 's':
206 col += printf("%s", ents[i].id);
207 if (VALID_STRING(ents[i].str)) {
208 col += printf("=");
209 col += outstr(stdout, ents[i].str);
210 } else
211 col += printf("@");
212 col += printf(",");
213 break;
214 }
215 }
216 printf("\n");
217 }
218
219 static size_t
load_ents(TIENT * ents,TERMINAL * t,char type)220 load_ents(TIENT *ents, TERMINAL *t, char type)
221 {
222 size_t i, n, max;
223 TERMUSERDEF *ud;
224
225 switch (type) {
226 case 'f':
227 max = TIFLAGMAX;
228 break;
229 case 'n':
230 max = TINUMMAX;
231 break;
232 default:
233 max = TISTRMAX;
234 }
235
236 n = 0;
237 for (i = 0; i <= max; i++) {
238 switch (type) {
239 case 'f':
240 if (t->flags[i] == 1 ||
241 (aflag && t->flags[i] == CANCELLED_BOOLEAN))
242 {
243 ents[n].id = _ti_flagid(i);
244 ents[n].type = 'f';
245 ents[n++].flag = t->flags[i];
246 }
247 break;
248 case 'n':
249 if (VALID_NUMERIC(t->nums[i]) ||
250 (aflag && t->nums[i] == CANCELLED_NUMERIC))
251 {
252 ents[n].id = _ti_numid(i);
253 ents[n].type = 'n';
254 ents[n++].num = t->nums[i];
255 }
256 break;
257 default:
258 if (VALID_STRING(t->strs[i]) ||
259 (aflag && t->strs[i] == CANCELLED_STRING))
260 {
261 ents[n].id = _ti_strid(i);
262 ents[n].type = 's';
263 ents[n++].str = t->strs[i];
264 }
265 break;
266 }
267 }
268
269 if (xflag != 0 && t->_nuserdefs != 0) {
270 for (i = 0; i < t->_nuserdefs; i++) {
271 ud = &t->_userdefs[i];
272 if (ud->type == type) {
273 switch (type) {
274 case 'f':
275 if (!aflag &&
276 !VALID_BOOLEAN(ud->flag))
277 continue;
278 break;
279 case 'n':
280 if (!aflag &&
281 !VALID_NUMERIC(ud->num))
282 continue;
283 break;
284 case 's':
285 if (!aflag &&
286 !VALID_STRING(ud->str))
287 continue;
288 break;
289 }
290 ents[n].id = ud->id;
291 ents[n].type = ud->type;
292 ents[n].flag = ud->flag;
293 ents[n].num = ud->num;
294 ents[n++].str = ud->str;
295 }
296 }
297 }
298
299 qsort(ents, n, sizeof(TIENT), ent_compare);
300 return n;
301 }
302
303 static void
cprint_ent(TIENT * ent)304 cprint_ent(TIENT *ent)
305 {
306
307 if (ent == NULL) {
308 if (qflag == 0)
309 printf("NULL");
310 else
311 printf("-");
312 }
313
314 switch (ent->type) {
315 case 'f':
316 if (VALID_BOOLEAN(ent->flag))
317 printf(ent->flag == 1 ? "T" : "F");
318 else if (qflag == 0)
319 printf("F");
320 else if (ent->flag == CANCELLED_BOOLEAN)
321 printf("@");
322 else
323 printf("-");
324 break;
325 case 'n':
326 if (VALID_NUMERIC(ent->num))
327 printf("%d", ent->num);
328 else if (qflag == 0)
329 printf("NULL");
330 else if (ent->num == CANCELLED_NUMERIC)
331 printf("@");
332 else
333 printf("-");
334 break;
335 case 's':
336 if (VALID_STRING(ent->str)) {
337 printf("'");
338 outstr(stdout, ent->str);
339 printf("'");
340 } else if (qflag == 0)
341 printf("NULL");
342 else if (ent->str == CANCELLED_STRING)
343 printf("@");
344 else
345 printf("-");
346 break;
347 }
348 }
349
350 static void
compare_ents(TIENT * ents1,size_t n1,TIENT * ents2,size_t n2)351 compare_ents(TIENT *ents1, size_t n1, TIENT *ents2, size_t n2)
352 {
353 size_t i1, i2;
354 TIENT *e1, *e2, ee;
355 int c;
356
357 i1 = i2 = 0;
358 ee.type = 'f';
359 ee.flag = ABSENT_BOOLEAN;
360 ee.num = ABSENT_NUMERIC;
361 ee.str = ABSENT_STRING;
362 while (i1 != n1 || i2 != n2) {
363 if (i1 == n1)
364 c = 1;
365 else if (i2 == n2)
366 c = -1;
367 else
368 c = strcmp(ents1[i1].id, ents2[i2].id);
369 if (c == 0) {
370 e1 = &ents1[i1++];
371 e2 = &ents2[i2++];
372 } else if (c < 0) {
373 e1 = &ents1[i1++];
374 e2 = ⅇ
375 ee.id = e1->id;
376 ee.type = e1->type;
377 } else {
378 e1 = ⅇ
379 e2 = &ents2[i2++];
380 ee.id = e2->id;
381 ee.type = e2->type;
382 }
383 switch (e1->type) {
384 case 'f':
385 if (cflag != 0) {
386 if (e1->flag == e2->flag)
387 printf("\t%s\n", ents1[i1].id);
388 continue;
389 }
390 if (e1->flag == e2->flag)
391 continue;
392 break;
393 case 'n':
394 if (cflag != 0) {
395 if (e1->num == e2->num)
396 printf("\t%s#%d\n",
397 ents1[i1].id, ents1[i1].num);
398 continue;
399 }
400 if (e1->num == e2->num)
401 continue;
402 break;
403 case 's':
404 if (cflag != 0) {
405 if (VALID_STRING(e1->str) &&
406 VALID_STRING(e2->str) &&
407 strcmp(e1->str, e2->str) == 0) {
408 printf("\t%s=", ents1[i1].id);
409 outstr(stdout, ents1[i1].str);
410 printf("\n");
411 }
412 continue;
413 }
414 if (VALID_STRING(e1->str) &&
415 VALID_STRING(e2->str) &&
416 strcmp(e1->str, e2->str) == 0)
417 continue;
418 break;
419 }
420 printf("\t%s: ", e1->id);
421 cprint_ent(e1);
422 if (e1->type == 'f')
423 printf(":");
424 else
425 printf(", ");
426 cprint_ent(e2);
427 printf(".\n");
428 }
429 }
430
431 static TERMINAL *
load_term(const char * name)432 load_term(const char *name)
433 {
434 TERMINAL *t;
435
436 t = ecalloc(1, sizeof(*t));
437 if (name == NULL)
438 name = getenv("TERM");
439 if (name == NULL)
440 name = "dumb";
441 if (_ti_getterm(t, name, 1) == 1)
442 return t;
443
444 if (_ti_database == NULL)
445 errx(EXIT_FAILURE,
446 "no terminal definition found in internal database");
447 else
448 errx(EXIT_FAILURE,
449 "no terminal definition found in %s.db", _ti_database);
450 }
451
452 static void
show_missing(TERMINAL * t1,TERMINAL * t2,char type)453 show_missing(TERMINAL *t1, TERMINAL *t2, char type)
454 {
455 ssize_t i, max;
456 const char *id;
457
458 switch (type) {
459 case 'f':
460 max = TIFLAGMAX;
461 break;
462 case 'n':
463 max = TINUMMAX;
464 break;
465 default:
466 max = TISTRMAX;
467 }
468
469 for (i = 0; i <= max; i++) {
470 switch (type) {
471 case 'f':
472 if (t1->flags[i] != ABSENT_BOOLEAN ||
473 t2->flags[i] != ABSENT_BOOLEAN)
474 continue;
475 id = _ti_flagid(i);
476 break;
477 case 'n':
478 if (t1->nums[i] != ABSENT_NUMERIC ||
479 t2->nums[i] != ABSENT_NUMERIC)
480 continue;
481 id = _ti_numid(i);
482 break;
483 default:
484 if (t1->strs[i] != ABSENT_STRING ||
485 t2->strs[i] != ABSENT_STRING)
486 continue;
487 id = _ti_strid(i);
488 break;
489 }
490 printf("\t!%s.\n", id);
491 }
492 }
493
494 static TERMUSERDEF *
find_userdef(TERMINAL * term,const char * id)495 find_userdef(TERMINAL *term, const char *id)
496 {
497 size_t i;
498
499 for (i = 0; i < term->_nuserdefs; i++)
500 if (strcmp(term->_userdefs[i].id, id) == 0)
501 return &term->_userdefs[i];
502 return NULL;
503 }
504
505 static void
use_terms(TERMINAL * term,size_t nuse,char ** uterms)506 use_terms(TERMINAL *term, size_t nuse, char **uterms)
507 {
508 TERMINAL **terms;
509 TERMUSERDEF *ud, *tud;
510 size_t i, j, agree, absent, data;
511
512 terms = ecalloc(nuse, sizeof(*terms));
513 for (i = 0; i < nuse; i++) {
514 if (strcmp(term->name, *uterms) == 0)
515 errx(EXIT_FAILURE, "cannot use same terminal");
516 for (j = 0; j < i; j++)
517 if (strcmp(terms[j]->name, *uterms) == 0)
518 errx(EXIT_FAILURE, "cannot use same terminal");
519 terms[i] = load_term(*uterms++);
520 }
521
522 for (i = 0; i < TIFLAGMAX + 1; i++) {
523 agree = absent = data = 0;
524 for (j = 0; j < nuse; j++) {
525 if (terms[j]->flags[i] == ABSENT_BOOLEAN ||
526 terms[j]->flags[i] == CANCELLED_BOOLEAN)
527 absent++;
528 else {
529 data++;
530 if (term->flags[i] == terms[j]->flags[i])
531 agree++;
532 }
533 }
534 if (data == 0)
535 continue;
536 if (agree > 0 && agree + absent == nuse)
537 term->flags[i] = ABSENT_BOOLEAN;
538 else if (term->flags[i] == ABSENT_BOOLEAN)
539 term->flags[i] = CANCELLED_BOOLEAN;
540 }
541
542 for (i = 0; i < TINUMMAX + 1; i++) {
543 agree = absent = data = 0;
544 for (j = 0; j < nuse; j++) {
545 if (terms[j]->nums[i] == ABSENT_NUMERIC ||
546 terms[j]->nums[i] == CANCELLED_NUMERIC)
547 absent++;
548 else {
549 data++;
550 if (term->nums[i] == terms[j]->nums[i])
551 agree++;
552 }
553 }
554 if (data == 0)
555 continue;
556 if (agree > 0 && agree + absent == nuse)
557 term->nums[i] = ABSENT_NUMERIC;
558 else if (term->nums[i] == ABSENT_NUMERIC)
559 term->nums[i] = CANCELLED_NUMERIC;
560 }
561
562 for (i = 0; i < TISTRMAX + 1; i++) {
563 agree = absent = data = 0;
564 for (j = 0; j < nuse; j++) {
565 if (terms[j]->strs[i] == ABSENT_STRING ||
566 terms[j]->strs[i] == CANCELLED_STRING)
567 absent++;
568 else {
569 data++;
570 if (VALID_STRING(term->strs[i]) &&
571 strcmp(term->strs[i],
572 terms[j]->strs[i]) == 0)
573 agree++;
574 }
575 }
576 if (data == 0)
577 continue;
578 if (agree > 0 && agree + absent == nuse)
579 term->strs[i] = ABSENT_STRING;
580 else if (term->strs[i] == ABSENT_STRING)
581 term->strs[i] = CANCELLED_STRING;
582 }
583
584 /* User defined caps are more tricky.
585 First we set any to absent that agree. */
586 for (i = 0; i < term->_nuserdefs; i++) {
587 agree = absent = data = 0;
588 ud = &term->_userdefs[i];
589 for (j = 0; j < nuse; j++) {
590 tud = find_userdef(terms[j], ud->id);
591 if (tud == NULL)
592 absent++;
593 else {
594 data++;
595 switch (ud->type) {
596 case 'f':
597 if (tud->type == 'f' &&
598 tud->flag == ud->flag)
599 agree++;
600 break;
601 case 'n':
602 if (tud->type == 'n' &&
603 tud->num == ud->num)
604 agree++;
605 break;
606 case 's':
607 if (tud->type == 's' &&
608 VALID_STRING(tud->str) &&
609 VALID_STRING(ud->str) &&
610 strcmp(ud->str, tud->str) == 0)
611 agree++;
612 break;
613 }
614 }
615 }
616 if (data == 0)
617 continue;
618 if (agree > 0 && agree + absent == nuse) {
619 ud->flag = ABSENT_BOOLEAN;
620 ud->num = ABSENT_NUMERIC;
621 ud->str = ABSENT_STRING;
622 }
623 }
624
625 /* Now add any that we don't have as cancelled */
626 for (i = 0; i < nuse; i++) {
627 for (j = 0; j < terms[i]->_nuserdefs; j++) {
628 ud = find_userdef(term, terms[i]->_userdefs[j].id);
629 if (ud != NULL)
630 continue; /* We have handled this */
631 term->_userdefs = erealloc(term->_userdefs,
632 sizeof(*term->_userdefs) * (term->_nuserdefs + 1));
633 tud = &term->_userdefs[term->_nuserdefs++];
634 tud->id = terms[i]->_userdefs[j].id;
635 tud->type = terms[i]->_userdefs[j].flag;
636 tud->flag = CANCELLED_BOOLEAN;
637 tud->num = CANCELLED_NUMERIC;
638 tud->str = CANCELLED_STRING;
639 }
640 }
641 }
642
643 int
main(int argc,char ** argv)644 main(int argc, char **argv)
645 {
646 char *term, *Barg;
647 int ch, uflag;
648 TERMINAL *t, *t2;
649 size_t n, n2;
650 struct winsize ws;
651 TIENT ents[TISTRMAX + 1], ents2[TISTRMAX + 1];
652
653 cols = 80; /* default */
654 term = getenv("COLUMNS");
655 if (term != NULL)
656 cols = strtoul(term, NULL, 10);
657 else if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0)
658 cols = ws.ws_col;
659
660 uflag = xflag = 0;
661 Barg = NULL;
662 while ((ch = getopt(argc, argv, "1A:B:acnquw:x")) != -1)
663 switch (ch) {
664 case '1':
665 cols = 1;
666 break;
667 case 'A':
668 setdb(optarg);
669 break;
670 case 'B':
671 Barg = optarg;
672 break;
673 case 'a':
674 aflag = 1;
675 break;
676 case 'c':
677 cflag = 1;
678 break;
679 case 'n':
680 nflag = 1;
681 break;
682 case 'q':
683 qflag = 1;
684 break;
685 case 'u':
686 uflag = 1;
687 aflag = 1;
688 break;
689 case 'w':
690 cols = strtoul(optarg, NULL, 10);
691 break;
692 case 'x':
693 xflag = 1;
694 break;
695 case '?':
696 default:
697 fprintf(stderr,
698 "usage: %s [-1acnqux] [-A database] [-B database] "
699 "[-w cols] [term]\n",
700 getprogname());
701 return EXIT_FAILURE;
702 }
703 cols--;
704
705 if (optind + 1 < argc)
706 aflag = 1;
707
708 if (optind < argc)
709 term = argv[optind++];
710 else
711 term = NULL;
712 t = load_term(term);
713
714 if (uflag != 0)
715 use_terms(t, argc - optind, argv + optind);
716
717 if ((optind + 1 != argc && nflag == 0) || uflag != 0) {
718 if (uflag == 0)
719 printf("# Reconstructed from %s\n",
720 _ti_database == NULL ?
721 "internal database" : _ti_database);
722 /* Strip internal versioning */
723 term = strchr(t->name, TERMINFO_VDELIM);
724 if (term != NULL)
725 *term = '\0';
726 printf("%s", t->name);
727 if (t->_alias != NULL) {
728 char *alias, *aliascpy, *delim;
729
730 alias = aliascpy = estrdup(t->_alias);
731 while (alias != NULL && *alias != '\0') {
732 putchar('|');
733 delim = strchr(alias, TERMINFO_VDELIM);
734 if (delim != NULL)
735 *delim++ = '\0';
736 printf("%s", alias);
737 if (delim != NULL) {
738 while (*delim != '\0' && *delim != '|')
739 delim++;
740 if (*delim == '\0')
741 alias = NULL;
742 else
743 alias = delim + 1;
744 } else
745 alias = NULL;
746 }
747 free(aliascpy);
748 }
749 if (t->desc != NULL && *t->desc != '\0')
750 printf("|%s", t->desc);
751 printf(",\n");
752
753 n = load_ents(ents, t, 'f');
754 print_ent(ents, n);
755 n = load_ents(ents, t, 'n');
756 print_ent(ents, n);
757 n = load_ents(ents, t, 's');
758 print_ent(ents, n);
759
760 if (uflag != 0) {
761 printf("\t");
762 n = SW;
763 for (; optind < argc; optind++) {
764 n2 = 5 + strlen(argv[optind]);
765 if (n != SW) {
766 if (n + n2 > cols) {
767 printf("\n\t");
768 n = SW;
769 } else
770 n += printf(" ");
771 }
772 n += printf("use=%s,", argv[optind]);
773 }
774 printf("\n");
775 }
776 return EXIT_SUCCESS;
777 }
778
779 if (Barg == NULL)
780 unsetenv("TERMINFO");
781 else
782 setdb(Barg);
783 t2 = load_term(argv[optind++]);
784 printf("comparing %s to %s.\n", t->name, t2->name);
785 if (qflag == 0)
786 printf(" comparing booleans.\n");
787 if (nflag == 0) {
788 n = load_ents(ents, t, 'f');
789 n2 = load_ents(ents2, t2, 'f');
790 compare_ents(ents, n, ents2, n2);
791 } else
792 show_missing(t, t2, 'f');
793 if (qflag == 0)
794 printf(" comparing numbers.\n");
795 if (nflag == 0) {
796 n = load_ents(ents, t, 'n');
797 n2 = load_ents(ents2, t2, 'n');
798 compare_ents(ents, n, ents2, n2);
799 } else
800 show_missing(t, t2, 'n');
801 if (qflag == 0)
802 printf(" comparing strings.\n");
803 if (nflag == 0) {
804 n = load_ents(ents, t, 's');
805 n2 = load_ents(ents2, t2, 's');
806 compare_ents(ents, n, ents2, n2);
807 } else
808 show_missing(t, t2, 's');
809 return EXIT_SUCCESS;
810 }
811