infocmp.c revision 1.10 1 /* $NetBSD: infocmp.c,v 1.10 2016/03/09 20:02:33 christos 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.10 2016/03/09 20:02:33 christos 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 = ⅇ
374 ee.id = e1->id;
375 ee.type = e1->type;
376 } else {
377 e1 = ⅇ
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 ");
717 if (_ti_database == NULL)
718 printf("internal database\n");
719 else
720 printf("%s%s\n", _ti_database,
721 *_ti_database == '/' ? ".cdb" : "");
722 }
723 printf("%s", t->name);
724 if (t->_alias != NULL && *t->_alias != '\0')
725 printf("|%s", t->_alias);
726 if (t->desc != NULL && *t->desc != '\0')
727 printf("|%s", t->desc);
728 printf(",\n");
729
730 n = load_ents(ents, t, 'f');
731 print_ent(ents, n);
732 n = load_ents(ents, t, 'n');
733 print_ent(ents, n);
734 n = load_ents(ents, t, 's');
735 print_ent(ents, n);
736
737 if (uflag != 0) {
738 printf("\t");
739 n = SW;
740 for (; optind < argc; optind++) {
741 n2 = 5 + strlen(argv[optind]);
742 if (n != SW) {
743 if (n + n2 > cols) {
744 printf("\n\t");
745 n = SW;
746 } else
747 n += printf(" ");
748 }
749 n += printf("use=%s,", argv[optind]);
750 }
751 printf("\n");
752 }
753 return EXIT_SUCCESS;
754 }
755
756 if (Barg == NULL)
757 unsetenv("TERMINFO");
758 else
759 setdb(Barg);
760 t2 = load_term(argv[optind++]);
761 printf("comparing %s to %s.\n", t->name, t2->name);
762 if (qflag == 0)
763 printf(" comparing booleans.\n");
764 if (nflag == 0) {
765 n = load_ents(ents, t, 'f');
766 n2 = load_ents(ents2, t2, 'f');
767 compare_ents(ents, n, ents2, n2);
768 } else
769 show_missing(t, t2, 'f');
770 if (qflag == 0)
771 printf(" comparing numbers.\n");
772 if (nflag == 0) {
773 n = load_ents(ents, t, 'n');
774 n2 = load_ents(ents2, t2, 'n');
775 compare_ents(ents, n, ents2, n2);
776 } else
777 show_missing(t, t2, 'n');
778 if (qflag == 0)
779 printf(" comparing strings.\n");
780 if (nflag == 0) {
781 n = load_ents(ents, t, 's');
782 n2 = load_ents(ents2, t2, 's');
783 compare_ents(ents, n, ents2, n2);
784 } else
785 show_missing(t, t2, 's');
786 return EXIT_SUCCESS;
787 }
788