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