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
47typedef struct tient {
48	char type;
49	const char *id;
50	signed char flag;
51	int num;
52	const char *str;
53} TIENT;
54
55static size_t cols;
56static int aflag, cflag, nflag, qflag, xflag;
57
58static size_t
59outstr(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++;
110prnt:
111		if (f != NULL)
112			fputc(ch, f);
113		r++;
114	}
115	return r;
116}
117
118static int
119ent_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
128static void
129setdb(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
143static void
144print_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
219static size_t
220load_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
303static void
304cprint_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
350static void
351compare_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 = &ee;
375			ee.id = e1->id;
376			ee.type = e1->type;
377		} else {
378			e1 = &ee;
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
431static TERMINAL *
432load_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
452static void
453show_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
494static TERMUSERDEF *
495find_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
505static void
506use_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
643int
644main(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