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