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