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