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