xref: /netbsd-src/usr.bin/tic/tic.c (revision e89934bbf778a6d6d6894877c4da59d0c7835b0f)
1 /* $NetBSD: tic.c,v 1.27 2017/01/10 21:15: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.27 2017/01/10 21:15:23 christos 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