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