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