Home | History | Annotate | Line # | Download | only in tic
      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 
     65 typedef struct term {
     66 	STAILQ_ENTRY(term) next;
     67 	char *name;
     68 	TIC *tic;
     69 	uint32_t id;
     70 	struct term *base_term;
     71 } TERM;
     72 static STAILQ_HEAD(, term) terms = STAILQ_HEAD_INITIALIZER(terms);
     73 
     74 static int error_exit;
     75 static int Sflag;
     76 static size_t nterm, nalias;
     77 
     78 static void __printflike(1, 2)
     79 dowarn(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 
     89 static char *
     90 grow_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 
    100 static int
    101 save_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 
    133 static TERM *
    134 find_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 
    144 static TERM *
    145 find_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 
    160 static TERM *
    161 store_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 
    182 static void
    183 alias_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 
    210 static int
    211 process_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 
    246 static void
    247 merge(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 
    330 static int
    331 dup_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 
    346 static int
    347 promote(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 
    397 err:
    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 
    409 static size_t
    410 merge_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 
    504 static int
    505 print_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 
    569 static void
    570 write_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 
    606 int
    607 main(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