xref: /netbsd/usr.bin/tic/tic.c (revision 6550d01e)
1 /* $NetBSD: tic.c,v 1.10 2010/02/22 23:05:39 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.10 2010/02/22 23:05:39 roy 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 __attribute__((__format__(__printf__, 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 				memcpy(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 len, buflen, nterm, nalias;
447 	TBUF tbuf;
448 	TERM *term;
449 
450 	cflag = sflag = 0;
451 	ofile = NULL;
452 	flags = TIC_ALIAS | TIC_DESCRIPTION | TIC_WARNING;
453 	while ((ch = getopt(argc, argv, "Saco:sx")) != -1)
454 	    switch (ch) {
455 	    case 'S':
456 		    Sflag = 1;
457 		    /* We still compile aliases so that use= works.
458 		     * However, it's removed before we flatten to save space. */
459 		    flags &= ~TIC_DESCRIPTION;
460 		    break;
461 	    case 'a':
462 		    flags |= TIC_COMMENT;
463 		    break;
464 	    case 'c':
465 		    cflag = 1;
466 		    break;
467 	    case 'o':
468 		    ofile = optarg;
469 		    break;
470 	    case 's':
471 		    sflag = 1;
472 		    break;
473 	    case 'x':
474 		    flags |= TIC_EXTRA;
475 		    break;
476 	    case '?': /* FALLTHROUGH */
477 	    default:
478 		    fprintf(stderr, "usage: %s [-acSsx] [-o file] source\n",
479 			getprogname());
480 		    return EXIT_FAILURE;
481 	    }
482 
483 	if (optind == argc)
484 		errx(1, "No source file given");
485 	source = argv[optind++];
486 	f = fopen(source, "r");
487 	if (f == NULL)
488 		err(1, "fopen: %s", source);
489 	if (!cflag && !Sflag) {
490 		if (ofile == NULL)
491 			ofile = source;
492 		len = strlen(ofile) + 9;
493 		dbname = malloc(len + 4); /* For adding .db after open */
494 		if (dbname == NULL)
495 			err(1, "malloc");
496 		snprintf(dbname, len, "%s.tmp", ofile);
497 		db = dbm_open(dbname, O_CREAT | O_RDWR | O_TRUNC, DEFFILEMODE);
498 		if (db == NULL)
499 			err(1, "dbopen: %s", source);
500 		p = dbname + strlen(dbname);
501 		*p++ = '.';
502 		*p++ = 'd';
503 		*p++ = 'b';
504 		*p++ = '\0';
505 		atexit(do_unlink);
506 	} else
507 		db = NULL; /* satisfy gcc warning */
508 
509 	tbuf.buflen = tbuf.bufpos = 0;
510 	while ((buf = fgetln(f, &buflen)) != NULL) {
511 		/* Skip comments */
512 		if (*buf == '#')
513 			continue;
514 		if (buf[buflen - 1] != '\n') {
515 			process_entry(&tbuf, flags);
516 			dowarn("last line is not a comment"
517 			    " and does not end with a newline");
518 			continue;
519 		}
520 		/*
521 		  If the first char is space not a space then we have a
522 		  new entry, so process it.
523 		*/
524 		if (!isspace((unsigned char)*buf) && tbuf.bufpos != 0)
525 			process_entry(&tbuf, flags);
526 
527 		/* Grow the buffer if needed */
528 		grow_tbuf(&tbuf, buflen);
529 		/* Append the string */
530 		memcpy(tbuf.buf + tbuf.bufpos, buf, buflen);
531 		tbuf.bufpos += buflen;
532 	}
533 	/* Process the last entry if not done already */
534 	process_entry(&tbuf, flags);
535 
536 	/* Merge use entries until we have merged all we can */
537 	while (merge_use(flags) != 0)
538 		;
539 
540 	if (Sflag) {
541 		print_dump(argc - optind, argv + optind);
542 		return error_exit;
543 	}
544 
545 	if (cflag)
546 		return error_exit;
547 
548 	/* Save the terms */
549 	nterm = nalias = 0;
550 	for (term = terms; term != NULL; term = term->next) {
551 		save_term(db, term);
552 		if (term->type == 'a')
553 			nalias++;
554 		else
555 			nterm++;
556 	}
557 
558 	/* done! */
559 	dbm_close(db);
560 
561 	/* Rename the tmp db to the real one now */
562 	len = strlen(ofile) + 4;
563 	p = malloc(len);
564 	if (p == NULL)
565 		err(1, "malloc");
566 	snprintf(p, len, "%s.db", ofile);
567 	if (rename(dbname, p) == -1)
568 		err(1, "rename");
569 	free(dbname);
570 	dbname = NULL;
571 
572 	if (sflag != 0)
573 		fprintf(stderr, "%zu entries and %zu aliases written to %s\n",
574 		    nterm, nalias, p);
575 
576 	return EXIT_SUCCESS;
577 }
578