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