xref: /netbsd-src/usr.bin/tic/tic.c (revision 98b3419359483c2c49c815cae40f57a582deb119)
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)
dowarn(const char * fmt,...)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 *
grow_tbuf(TBUF * tbuf,size_t len)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
save_term(struct cdbw * db,TERM * term)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 *
find_term(const char * name)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 *
find_newest_term(const char * name)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 *
store_term(const char * name,TERM * base_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
alias_terms(TERM * term)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
process_entry(TBUF * buf,int flags)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
merge(TIC * rtic,TIC * utic,int flags)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
dup_tbuf(TBUF * dst,const TBUF * src)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
promote(TIC * rtic,TIC * utic)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
merge_use(int flags)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
print_dump(int argc,char ** argv)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
write_database(const char * dbname)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
main(int argc,char ** argv)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