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