xref: /netbsd-src/usr.bin/tic/tic.c (revision 53b02e147d4ed531c0d2a5ca9b3e8026ba3e99b5)
1 /* $NetBSD: tic.c,v 1.40 2020/03/30 00:09:06 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 #if HAVE_NBTOOL_CONFIG_H
31 #include "nbtool_config.h"
32 #endif
33 
34 #include <sys/cdefs.h>
35 __RCSID("$NetBSD: tic.c,v 1.40 2020/03/30 00:09:06 roy Exp $");
36 
37 #include <sys/types.h>
38 #include <sys/queue.h>
39 #include <sys/stat.h>
40 
41 #if !HAVE_NBTOOL_CONFIG_H || HAVE_SYS_ENDIAN_H
42 #include <sys/endian.h>
43 #endif
44 
45 #include <cdbw.h>
46 #include <ctype.h>
47 #include <err.h>
48 #include <errno.h>
49 #include <getopt.h>
50 #include <limits.h>
51 #include <fcntl.h>
52 #include <search.h>
53 #include <stdarg.h>
54 #include <stdbool.h>
55 #include <stdlib.h>
56 #include <stdio.h>
57 #include <string.h>
58 #include <term_private.h>
59 #include <term.h>
60 #include <unistd.h>
61 #include <util.h>
62 
63 #define	HASH_SIZE	16384	/* 2012-06-01: 3600 entries */
64 
65 typedef struct term {
66 	STAILQ_ENTRY(term) next;
67 	char *name;
68 	TIC *tic;
69 	uint32_t id;
70 	struct term *base_term;
71 } TERM;
72 static STAILQ_HEAD(, term) terms = STAILQ_HEAD_INITIALIZER(terms);
73 
74 static int error_exit;
75 static int Sflag;
76 static size_t nterm, nalias;
77 
78 static void __printflike(1, 2)
79 dowarn(const char *fmt, ...)
80 {
81 	va_list va;
82 
83 	error_exit = 1;
84 	va_start(va, fmt);
85 	vwarnx(fmt, va);
86 	va_end(va);
87 }
88 
89 static char *
90 grow_tbuf(TBUF *tbuf, size_t len)
91 {
92 	char *buf;
93 
94 	buf = _ti_grow_tbuf(tbuf, len);
95 	if (buf == NULL)
96 		err(EXIT_FAILURE, "_ti_grow_tbuf");
97 	return buf;
98 }
99 
100 static int
101 save_term(struct cdbw *db, TERM *term)
102 {
103 	uint8_t *buf;
104 	ssize_t len;
105 	size_t slen = strlen(term->name) + 1;
106 
107 	if (term->base_term != NULL) {
108 		char *cap;
109 		len = (ssize_t)(1 + sizeof(uint32_t) + sizeof(uint16_t) + slen);
110 		buf = emalloc(len);
111 		cap = (char *)buf;
112 		*cap++ = TERMINFO_ALIAS;
113 		_ti_encode_32(&cap, term->base_term->id);
114 		_ti_encode_count_str(&cap, term->name, slen);
115 		if (cdbw_put(db, term->name, slen, buf, len))
116 			err(EXIT_FAILURE, "cdbw_put");
117 		free(buf);
118 		return 0;
119 	}
120 
121 	len = _ti_flatten(&buf, term->tic);
122 	if (len == -1)
123 		return -1;
124 
125 	if (cdbw_put_data(db, buf, len, &term->id))
126 		err(EXIT_FAILURE, "cdbw_put_data");
127 	if (cdbw_put_key(db, term->name, slen, term->id))
128 		err(EXIT_FAILURE, "cdbw_put_key");
129 	free(buf);
130 	return 0;
131 }
132 
133 static TERM *
134 find_term(const char *name)
135 {
136 	ENTRY elem, *elemp;
137 
138 	elem.key = __UNCONST(name);
139 	elem.data = NULL;
140 	elemp = hsearch(elem, FIND);
141 	return elemp ? (TERM *)elemp->data : NULL;
142 }
143 
144 static TERM *
145 find_newest_term(const char *name)
146 {
147 	char *lname;
148 	TERM *term;
149 
150 	lname = _ti_getname(TERMINFO_RTYPE, name);
151 	if (lname == NULL)
152 		return NULL;
153 	term = find_term(lname);
154 	free(lname);
155 	if (term == NULL)
156 		term = find_term(name);
157 	return term;
158 }
159 
160 static TERM *
161 store_term(const char *name, TERM *base_term)
162 {
163 	TERM *term;
164 	ENTRY elem;
165 
166 	term = ecalloc(1, sizeof(*term));
167 	term->name = estrdup(name);
168 	STAILQ_INSERT_TAIL(&terms, term, next);
169 	elem.key = estrdup(name);
170 	elem.data = term;
171 	hsearch(elem, ENTER);
172 
173 	term->base_term = base_term;
174 	if (base_term != NULL)
175 		nalias++;
176 	else
177 		nterm++;
178 
179 	return term;
180 }
181 
182 static void
183 alias_terms(TERM *term)
184 {
185 	char *p, *e, *alias;
186 
187 	/* Create aliased terms */
188 	if (term->tic->alias == NULL)
189 		return;
190 
191 	alias = p = estrdup(term->tic->alias);
192 	while (p != NULL && *p != '\0') {
193 		e = strchr(p, '|');
194 		if (e != NULL)
195 			*e++ = '\0';
196 		/* No need to lengthcheck the alias because the main
197 		 * terminfo description already stores all the aliases
198 		 * in the same length field as the alias. */
199 		if (find_term(p) != NULL) {
200 			dowarn("%s: has alias for already assigned"
201 			    " term %s", term->tic->name, p);
202 		} else {
203 			store_term(p, term);
204 		}
205 		p = e;
206 	}
207 	free(alias);
208 }
209 
210 static int
211 process_entry(TBUF *buf, int flags)
212 {
213 	TERM *term;
214 	TIC *tic;
215 	TBUF sbuf = *buf;
216 
217 	if (buf->bufpos == 0)
218 		return 0;
219 	/* Terminate the string */
220 	buf->buf[buf->bufpos - 1] = '\0';
221 	/* First rewind the buffer for new entries */
222 	buf->bufpos = 0;
223 
224 	if (isspace((unsigned char)*buf->buf))
225 		return 0;
226 
227 	tic = _ti_compile(buf->buf, flags);
228 	if (tic == NULL)
229 		return 0;
230 
231 	if (find_term(tic->name) != NULL) {
232 		dowarn("%s: duplicate entry", tic->name);
233 		_ti_freetic(tic);
234 		return 0;
235 	}
236 	term = store_term(tic->name, NULL);
237 	term->tic = tic;
238 	alias_terms(term);
239 
240 	if (tic->rtype == TERMINFO_RTYPE)
241 		return process_entry(&sbuf, flags | TIC_COMPAT_V1);
242 
243 	return 0;
244 }
245 
246 static void
247 merge(TIC *rtic, TIC *utic, int flags)
248 {
249 	char flag, type;
250 	const char *cap, *code, *str;
251 	short ind, len;
252 	int num;
253 	size_t n;
254 
255 	if (rtic->rtype < utic->rtype)
256 		errx(EXIT_FAILURE, "merge rtype diff (%s:%d into %s:%d)",
257 		    utic->name, utic->rtype, rtic->name, rtic->rtype);
258 
259 	cap = utic->flags.buf;
260 	for (n = utic->flags.entries; n > 0; n--) {
261 		ind = _ti_decode_16(&cap);
262 		flag = *cap++;
263 		if (VALID_BOOLEAN(flag) &&
264 		    _ti_find_cap(rtic, &rtic->flags, 'f', ind) == NULL)
265 		{
266 			if (!_ti_encode_buf_id_flags(&rtic->flags, ind, flag))
267 				err(EXIT_FAILURE, "encode flag");
268 		}
269 	}
270 
271 	cap = utic->nums.buf;
272 	for (n = utic->nums.entries; n > 0; n--) {
273 		ind = _ti_decode_16(&cap);
274 		num = _ti_decode_num(&cap, utic->rtype);
275 		if (VALID_NUMERIC(num) &&
276 		    _ti_find_cap(rtic, &rtic->nums, 'n', ind) == NULL)
277 		{
278 			if (!_ti_encode_buf_id_num(&rtic->nums, ind, num,
279 			    _ti_numsize(rtic)))
280 				err(EXIT_FAILURE, "encode num");
281 		}
282 	}
283 
284 	cap = utic->strs.buf;
285 	for (n = utic->strs.entries; n > 0; n--) {
286 		ind = _ti_decode_16(&cap);
287 		len = _ti_decode_16(&cap);
288 		if (len > 0 &&
289 		    _ti_find_cap(rtic, &rtic->strs, 's', ind) == NULL)
290 		{
291 			if (!_ti_encode_buf_id_count_str(&rtic->strs, ind, cap,
292 			    len))
293 				err(EXIT_FAILURE, "encode str");
294 		}
295 		cap += len;
296 	}
297 
298 	cap = utic->extras.buf;
299 	for (n = utic->extras.entries; n > 0; n--) {
300 		num = _ti_decode_16(&cap);
301 		code = cap;
302 		cap += num;
303 		type = *cap++;
304 		flag = 0;
305 		str = NULL;
306 		switch (type) {
307 		case 'f':
308 			flag = *cap++;
309 			if (!VALID_BOOLEAN(flag))
310 				continue;
311 			break;
312 		case 'n':
313 			num = _ti_decode_num(&cap, utic->rtype);
314 			if (!VALID_NUMERIC(num))
315 				continue;
316 			break;
317 		case 's':
318 			num = _ti_decode_16(&cap);
319 			str = cap;
320 			cap += num;
321 			if (num == 0)
322 				continue;
323 			break;
324 		}
325 		_ti_store_extra(rtic, 0, code, type, flag, num, str, num,
326 		    flags);
327 	}
328 }
329 
330 static int
331 dup_tbuf(TBUF *dst, const TBUF *src)
332 {
333 
334 	if (src->buflen == 0)
335 		return 0;
336 	dst->buf = malloc(src->buflen);
337 	if (dst->buf == NULL)
338 		return -1;
339 	dst->buflen = src->buflen;
340 	memcpy(dst->buf, src->buf, dst->buflen);
341 	dst->bufpos = src->bufpos;
342 	dst->entries = src->entries;
343 	return 0;
344 }
345 
346 static int
347 promote(TIC *rtic, TIC *utic)
348 {
349 	TERM *nrterm = find_newest_term(rtic->name);
350 	TERM *nuterm = find_newest_term(utic->name);
351 	TERM *term;
352 	TIC *tic;
353 
354 	if (nrterm == NULL || nuterm == NULL)
355 		return -1;
356 	if (nrterm->tic->rtype >= nuterm->tic->rtype)
357 		return 0;
358 
359 	tic = calloc(1, sizeof(*tic));
360 	if (tic == NULL)
361 		return -1;
362 
363 	tic->name = _ti_getname(TERMINFO_RTYPE, rtic->name);
364 	if (tic->name == NULL)
365 		goto err;
366 	if (rtic->alias != NULL) {
367 		tic->alias = strdup(rtic->alias);
368 		if (tic->alias == NULL)
369 			goto err;
370 	}
371 	if (rtic->desc != NULL) {
372 		tic->desc = strdup(rtic->desc);
373 		if (tic->desc == NULL)
374 			goto err;
375 	}
376 
377 	tic->rtype = rtic->rtype;
378 	if (dup_tbuf(&tic->flags, &rtic->flags) == -1)
379 		goto err;
380 	if (dup_tbuf(&tic->nums, &rtic->nums) == -1)
381 		goto err;
382 	if (dup_tbuf(&tic->strs, &rtic->strs) == -1)
383 		goto err;
384 	if (dup_tbuf(&tic->extras, &rtic->extras) == -1)
385 		goto err;
386 	if (_ti_promote(tic) == -1)
387 		goto err;
388 
389 	term = store_term(tic->name, NULL);
390 	if (term == NULL)
391 		goto err;
392 
393 	term->tic = tic;
394 	alias_terms(term);
395 	return 0;
396 
397 err:
398 	free(tic->flags.buf);
399 	free(tic->nums.buf);
400 	free(tic->strs.buf);
401 	free(tic->extras.buf);
402 	free(tic->desc);
403 	free(tic->alias);
404 	free(tic->name);
405 	free(tic);
406 	return -1;
407 }
408 
409 static size_t
410 merge_use(int flags)
411 {
412 	size_t skipped, merged, memn;
413 	const char *cap;
414 	char *name, *basename;
415 	uint16_t num;
416 	TIC *rtic, *utic;
417 	TERM *term, *uterm;
418 	bool promoted;
419 
420 	skipped = merged = 0;
421 	STAILQ_FOREACH(term, &terms, next) {
422 		if (term->base_term != NULL)
423 			continue;
424 		rtic = term->tic;
425 		basename = _ti_getname(TERMINFO_RTYPE_O1, rtic->name);
426 		promoted = false;
427 		while ((cap = _ti_find_extra(rtic, &rtic->extras, "use"))
428 		    != NULL) {
429 			if (*cap++ != 's') {
430 				dowarn("%s: use is not string", rtic->name);
431 				break;
432 			}
433 			cap += sizeof(uint16_t);
434 			if (strcmp(basename, cap) == 0) {
435 				dowarn("%s: uses itself", rtic->name);
436 				goto remove;
437 			}
438 			name = _ti_getname(rtic->rtype, cap);
439 			if (name == NULL) {
440 				dowarn("%s: ???: %s", rtic->name, cap);
441 				goto remove;
442 			}
443 			uterm = find_term(name);
444 			free(name);
445 			if (uterm == NULL)
446 				uterm = find_term(cap);
447 			if (uterm != NULL && uterm->base_term != NULL)
448 				uterm = uterm->base_term;
449 			if (uterm == NULL) {
450 				dowarn("%s: no use record for %s",
451 				    rtic->name, cap);
452 				goto remove;
453 			}
454 			utic = uterm->tic;
455 			if (strcmp(utic->name, rtic->name) == 0) {
456 				dowarn("%s: uses itself", rtic->name);
457 				goto remove;
458 			}
459 			if (_ti_find_extra(utic, &utic->extras, "use")
460 			    != NULL) {
461 				skipped++;
462 				break;
463 			}
464 
465 			/* If we need to merge in a term that requires
466 			 * this term to be promoted, we need to duplicate
467 			 * this term, promote it and append it to our list. */
468 			if (!promoted && rtic->rtype != TERMINFO_RTYPE) {
469 				if (promote(rtic, utic) == -1)
470 					err(EXIT_FAILURE, "promote");
471 				promoted = true;
472 			}
473 
474 			merge(rtic, utic, flags);
475 	remove:
476 			/* The pointers may have changed, find the use again */
477 			cap = _ti_find_extra(rtic, &rtic->extras, "use");
478 			if (cap == NULL)
479 				dowarn("%s: use no longer exists - impossible",
480 					rtic->name);
481 			else {
482 				char *scap = __UNCONST(
483 				    cap - (4 + sizeof(uint16_t)));
484 				cap++;
485 				num = _ti_decode_16(&cap);
486 				cap += num;
487 				memn = rtic->extras.bufpos -
488 				    (cap - rtic->extras.buf);
489 				memmove(scap, cap, memn);
490 				rtic->extras.bufpos -= cap - scap;
491 				cap = scap;
492 				rtic->extras.entries--;
493 				merged++;
494 			}
495 		}
496 		free(basename);
497 	}
498 
499 	if (merged == 0 && skipped != 0)
500 		dowarn("circular use detected");
501 	return merged;
502 }
503 
504 static int
505 print_dump(int argc, char **argv)
506 {
507 	TERM *term;
508 	uint8_t *buf;
509 	int i, n;
510 	size_t j, col;
511 	ssize_t len;
512 
513 	printf("struct compiled_term {\n");
514 	printf("\tconst char *name;\n");
515 	printf("\tconst char *cap;\n");
516 	printf("\tsize_t caplen;\n");
517 	printf("};\n\n");
518 
519 	printf("const struct compiled_term compiled_terms[] = {\n");
520 
521 	n = 0;
522 	for (i = 0; i < argc; i++) {
523 		term = find_newest_term(argv[i]);
524 		if (term == NULL) {
525 			warnx("%s: no description for terminal", argv[i]);
526 			continue;
527 		}
528 		if (term->base_term != NULL) {
529 			warnx("%s: cannot dump alias", argv[i]);
530 			continue;
531 		}
532 		/* Don't compile the aliases in, save space */
533 		free(term->tic->alias);
534 		term->tic->alias = NULL;
535 		len = _ti_flatten(&buf, term->tic);
536 		if (len == 0 || len == -1)
537 			continue;
538 
539 		printf("\t{\n");
540 		printf("\t\t\"%s\",\n", argv[i]);
541 		n++;
542 		for (j = 0, col = 0; j < (size_t)len; j++) {
543 			if (col == 0) {
544 				printf("\t\t\"");
545 				col = 16;
546 			}
547 
548 			col += printf("\\%03o", (uint8_t)buf[j]);
549 			if (col > 75) {
550 				printf("\"%s\n",
551 				    j + 1 == (size_t)len ? "," : "");
552 				col = 0;
553 			}
554 		}
555 		if (col != 0)
556 			printf("\",\n");
557 		printf("\t\t%zu\n", len);
558 		printf("\t}");
559 		if (i + 1 < argc)
560 			printf(",");
561 		printf("\n");
562 		free(buf);
563 	}
564 	printf("};\n");
565 
566 	return n;
567 }
568 
569 static void
570 write_database(const char *dbname)
571 {
572 	struct cdbw *db;
573 	char *tmp_dbname;
574 	TERM *term;
575 	int fd;
576 
577 	db = cdbw_open();
578 	if (db == NULL)
579 		err(EXIT_FAILURE, "cdbw_open failed");
580 	/* Save the terms */
581 	STAILQ_FOREACH(term, &terms, next)
582 		save_term(db, term);
583 
584 	easprintf(&tmp_dbname, "%s.XXXXXX", dbname);
585 	fd = mkstemp(tmp_dbname);
586 	if (fd == -1)
587 		err(EXIT_FAILURE,
588 		    "creating temporary database %s failed", tmp_dbname);
589 	if (cdbw_output(db, fd, "NetBSD terminfo", cdbw_stable_seeder))
590 		err(EXIT_FAILURE,
591 		    "writing temporary database %s failed", tmp_dbname);
592 	if (fchmod(fd, DEFFILEMODE))
593 		err(EXIT_FAILURE, "fchmod failed");
594 	if (close(fd))
595 		err(EXIT_FAILURE,
596 		    "writing temporary database %s failed", tmp_dbname);
597 	if (rename(tmp_dbname, dbname))
598 		err(EXIT_FAILURE, "renaming %s to %s failed", tmp_dbname, dbname);
599 	free(tmp_dbname);
600 	cdbw_close(db);
601 }
602 
603 int
604 main(int argc, char **argv)
605 {
606 	int ch, cflag, sflag, flags;
607 	char *source, *dbname, *buf, *ofile;
608 	FILE *f;
609 	size_t buflen;
610 	ssize_t len;
611 	TBUF tbuf;
612 	struct term *term;
613 
614 	cflag = sflag = 0;
615 	ofile = NULL;
616 	flags = TIC_ALIAS | TIC_DESCRIPTION | TIC_WARNING;
617 	while ((ch = getopt(argc, argv, "Saco:sx")) != -1)
618 	    switch (ch) {
619 	    case 'S':
620 		    Sflag = 1;
621 		    /* We still compile aliases so that use= works.
622 		     * However, it's removed before we flatten to save space. */
623 		    flags &= ~TIC_DESCRIPTION;
624 		    break;
625 	    case 'a':
626 		    flags |= TIC_COMMENT;
627 		    break;
628 	    case 'c':
629 		    cflag = 1;
630 		    break;
631 	    case 'o':
632 		    ofile = optarg;
633 		    break;
634 	    case 's':
635 		    sflag = 1;
636 		    break;
637 	    case 'x':
638 		    flags |= TIC_EXTRA;
639 		    break;
640 	    case '?': /* FALLTHROUGH */
641 	    default:
642 		    fprintf(stderr, "usage: %s [-acSsx] [-o file] source\n",
643 			getprogname());
644 		    return EXIT_FAILURE;
645 	    }
646 
647 	if (optind == argc)
648 		errx(1, "No source file given");
649 	source = argv[optind++];
650 	f = fopen(source, "r");
651 	if (f == NULL)
652 		err(EXIT_FAILURE, "fopen: %s", source);
653 
654 	hcreate(HASH_SIZE);
655 
656 	buf = tbuf.buf = NULL;
657 	buflen = tbuf.buflen = tbuf.bufpos = 0;
658 	while ((len = getline(&buf, &buflen, f)) != -1) {
659 		/* Skip comments */
660 		if (*buf == '#')
661 			continue;
662 		if (buf[len - 1] != '\n') {
663 			process_entry(&tbuf, flags);
664 			dowarn("last line is not a comment"
665 			    " and does not end with a newline");
666 			continue;
667 		}
668 		/*
669 		 * If the first char is space not a space then we have a
670 		 * new entry, so process it.
671 		 */
672 		if (!isspace((unsigned char)*buf) && tbuf.bufpos != 0)
673 			process_entry(&tbuf, flags);
674 
675 		/* Grow the buffer if needed */
676 		grow_tbuf(&tbuf, len);
677 		/* Append the string */
678 		memcpy(tbuf.buf + tbuf.bufpos, buf, len);
679 		tbuf.bufpos += len;
680 	}
681 	free(buf);
682 	/* Process the last entry if not done already */
683 	process_entry(&tbuf, flags);
684 	free(tbuf.buf);
685 
686 	/* Merge use entries until we have merged all we can */
687 	while (merge_use(flags) != 0)
688 		;
689 
690 	if (Sflag) {
691 		print_dump(argc - optind, argv + optind);
692 		return error_exit;
693 	}
694 
695 	if (cflag)
696 		return error_exit;
697 
698 	if (ofile == NULL)
699 		easprintf(&dbname, "%s.cdb", source);
700 	else
701 		dbname = ofile;
702 	write_database(dbname);
703 
704 	if (sflag != 0)
705 		fprintf(stderr, "%zu entries and %zu aliases written to %s\n",
706 		    nterm, nalias, dbname);
707 
708 	if (ofile == NULL)
709 		free(dbname);
710 	while ((term = STAILQ_FIRST(&terms)) != NULL) {
711 		STAILQ_REMOVE_HEAD(&terms, next);
712 		_ti_freetic(term->tic);
713 		free(term->name);
714 		free(term);
715 	}
716 #ifndef HAVE_NBTOOL_CONFIG_H
717 	/*
718 	 * hdestroy1 is not standard but we don't really care if we
719 	 * leak in the tools version
720 	 */
721 	hdestroy1(free, NULL);
722 #endif
723 
724 	return EXIT_SUCCESS;
725 }
726