compile.c revision 1.25
1/* $NetBSD: compile.c,v 1.25 2020/04/05 12:31:02 roy Exp $ */ 2 3/* 4 * Copyright (c) 2009, 2010, 2011, 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: compile.c,v 1.25 2020/04/05 12:31:02 roy Exp $"); 36 37#if !HAVE_NBTOOL_CONFIG_H || HAVE_SYS_ENDIAN_H 38#include <sys/endian.h> 39#endif 40 41#include <assert.h> 42#include <ctype.h> 43#include <err.h> 44#include <errno.h> 45#include <limits.h> 46#include <stdarg.h> 47#include <stdlib.h> 48#include <stdint.h> 49#include <stdio.h> 50#include <string.h> 51#include <term_private.h> 52#include <term.h> 53 54static void __printflike(2, 3) 55dowarn(int flags, const char *fmt, ...) 56{ 57 va_list va; 58 59 errno = EINVAL; 60 if (flags & TIC_WARNING) { 61 va_start(va, fmt); 62 vwarnx(fmt, va); 63 va_end(va); 64 } 65} 66 67#ifdef TERMINFO_COMPAT 68int 69_ti_promote(TIC *tic) 70{ 71 char *obuf, type, flag, *buf, *delim, *name, *nbuf; 72 const char *cap, *code, *str; 73 size_t n, entries, strl; 74 uint16_t ind; 75 int num, ortype, error = 0; 76 77 ortype = tic->rtype; 78 tic->rtype = TERMINFO_RTYPE; 79 obuf = tic->name; 80 tic->name = _ti_getname(tic->rtype, tic->name); 81 if (tic->name == NULL) { 82 warn("_ti_getname"); 83 tic->name = obuf; 84 return -1; 85 } 86 free(obuf); 87 88 n = 0; 89 obuf = buf = tic->alias; 90 tic->alias = NULL; 91 while (buf != NULL) { 92 delim = strchr(buf, '|'); 93 if (delim != NULL) 94 *delim++ = '\0'; 95 name = _ti_getname(tic->rtype, buf); 96 strl = strlen(name) + 1; 97 nbuf = realloc(tic->alias, n + strl); 98 if (nbuf == NULL) { 99 free(name); 100 return -1; 101 } 102 tic->alias = nbuf; 103 memcpy(tic->alias + n, name, strl); 104 n += strl; 105 free(name); 106 buf = delim; 107 } 108 free(obuf); 109 110 obuf = tic->nums.buf; 111 cap = obuf; 112 entries = tic->nums.entries; 113 tic->nums.buf = NULL; 114 tic->nums.entries = tic->nums.buflen = tic->nums.bufpos = 0; 115 for (n = entries; n > 0; n--) { 116 ind = _ti_decode_16(&cap); 117 num = _ti_decode_num(&cap, ortype); 118 if (VALID_NUMERIC(num) && 119 !_ti_encode_buf_id_num(&tic->nums, ind, num, 120 _ti_numsize(tic))) 121 { 122 warn("promote num"); 123 error = -1; 124 break; 125 } 126 } 127 free(obuf); 128 129 obuf = tic->extras.buf; 130 cap = obuf; 131 entries = tic->extras.entries; 132 tic->extras.buf = NULL; 133 tic->extras.entries = tic->extras.buflen = tic->extras.bufpos = 0; 134 for (n = entries; n > 0; n--) { 135 num = _ti_decode_16(&cap); 136 flag = 0; /* satisfy gcc, won't be used for non flag types */ 137 str = NULL; /* satisfy gcc, won't be used as strl is 0 */ 138 strl = 0; 139 code = cap; 140 cap += num; 141 type = *cap++; 142 switch (type) { 143 case 'f': 144 flag = *cap++; 145 break; 146 case 'n': 147 num = _ti_decode_num(&cap, ortype); 148 break; 149 case 's': 150 strl = _ti_decode_16(&cap); 151 str = cap; 152 cap += strl; 153 break; 154 default: 155 errno = EINVAL; 156 break; 157 } 158 if (!_ti_store_extra(tic, 0, code, type, flag, num, 159 str, strl, TIC_EXTRA)) 160 { 161 error = -1; 162 break; 163 } 164 } 165 free(obuf); 166 167 return error; 168} 169#endif 170 171char * 172_ti_grow_tbuf(TBUF *tbuf, size_t len) 173{ 174 char *buf; 175 size_t l; 176 177 _DIAGASSERT(tbuf != NULL); 178 179 l = tbuf->bufpos + len; 180 if (l > tbuf->buflen) { 181 if (tbuf->buflen == 0) 182 buf = malloc(l); 183 else 184 buf = realloc(tbuf->buf, l); 185 if (buf == NULL) 186 return NULL; 187 tbuf->buf = buf; 188 tbuf->buflen = l; 189 } 190 return tbuf->buf; 191} 192 193const char * 194_ti_find_cap(TIC *tic, TBUF *tbuf, char type, short ind) 195{ 196 size_t n; 197 uint16_t num; 198 const char *cap; 199 200 _DIAGASSERT(tbuf != NULL); 201 202 cap = tbuf->buf; 203 for (n = tbuf->entries; n > 0; n--) { 204 num = _ti_decode_16(&cap); 205 if ((short)num == ind) 206 return cap; 207 switch (type) { 208 case 'f': 209 cap++; 210 break; 211 case 'n': 212 cap += _ti_numsize(tic); 213 break; 214 case 's': 215 num = _ti_decode_16(&cap); 216 cap += num; 217 break; 218 } 219 } 220 221 errno = ESRCH; 222 return NULL; 223} 224 225const char * 226_ti_find_extra(TIC *tic, TBUF *tbuf, const char *code) 227{ 228 size_t n; 229 uint16_t num; 230 const char *cap; 231 232 _DIAGASSERT(tbuf != NULL); 233 _DIAGASSERT(code != NULL); 234 235 cap = tbuf->buf; 236 for (n = tbuf->entries; n > 0; n--) { 237 num = _ti_decode_16(&cap); 238 if (strcmp(cap, code) == 0) 239 return cap + num; 240 cap += num; 241 switch (*cap++) { 242 case 'f': 243 cap++; 244 break; 245 case 'n': 246 cap += _ti_numsize(tic); 247 break; 248 case 's': 249 num = _ti_decode_16(&cap); 250 cap += num; 251 break; 252 } 253 } 254 255 errno = ESRCH; 256 return NULL; 257} 258 259char * 260_ti_getname(int rtype, const char *orig) 261{ 262#ifdef TERMINFO_COMPAT 263 const char *delim; 264 char *name; 265 const char *verstr; 266 size_t diff, vlen; 267 268 switch (rtype) { 269 case TERMINFO_RTYPE: 270 verstr = TERMINFO_VDELIMSTR "v3"; 271 break; 272 case TERMINFO_RTYPE_O1: 273 verstr = ""; 274 break; 275 default: 276 errno = EINVAL; 277 return NULL; 278 } 279 280 delim = orig; 281 while (*delim != '\0' && *delim != TERMINFO_VDELIM) 282 delim++; 283 diff = delim - orig; 284 vlen = strlen(verstr); 285 name = malloc(diff + vlen + 1); 286 if (name == NULL) 287 return NULL; 288 289 memcpy(name, orig, diff); 290 memcpy(name + diff, verstr, vlen + 1); 291 return name; 292#else 293 return strdup(orig); 294#endif 295} 296 297size_t 298_ti_store_extra(TIC *tic, int wrn, const char *id, char type, char flag, 299 int num, const char *str, size_t strl, int flags) 300{ 301 size_t l, capl; 302 303 _DIAGASSERT(tic != NULL); 304 305 if (strcmp(id, "use") != 0) { 306 if (_ti_find_extra(tic, &tic->extras, id) != NULL) 307 return 0; 308 if (!(flags & TIC_EXTRA)) { 309 if (wrn != 0) 310 dowarn(flags, "%s: %s: unknown capability", 311 tic->name, id); 312 return 0; 313 } 314 } 315 316 l = strlen(id) + 1; 317 if (l > UINT16_MAX) { 318 dowarn(flags, "%s: %s: cap name is too long", tic->name, id); 319 return 0; 320 } 321 322 capl = sizeof(uint16_t) + l + 1; 323 switch (type) { 324 case 'f': 325 capl++; 326 break; 327 case 'n': 328 capl += _ti_numsize(tic); 329 break; 330 case 's': 331 capl += sizeof(uint16_t) + strl; 332 break; 333 } 334 335 if (!_ti_grow_tbuf(&tic->extras, capl)) 336 return 0; 337 _ti_encode_buf_count_str(&tic->extras, id, l); 338 tic->extras.buf[tic->extras.bufpos++] = type; 339 switch (type) { 340 case 'f': 341 tic->extras.buf[tic->extras.bufpos++] = flag; 342 break; 343 case 'n': 344 _ti_encode_buf_num(&tic->extras, num, tic->rtype); 345 break; 346 case 's': 347 _ti_encode_buf_count_str(&tic->extras, str, strl); 348 break; 349 } 350 tic->extras.entries++; 351 return 1; 352} 353 354static void 355_ti_encode_buf(char **cap, const TBUF *buf) 356{ 357 if (buf->entries == 0) { 358 _ti_encode_16(cap, 0); 359 } else { 360 _ti_encode_16(cap, buf->bufpos + sizeof(uint16_t)); 361 _ti_encode_16(cap, buf->entries); 362 _ti_encode_str(cap, buf->buf, buf->bufpos); 363 } 364} 365 366ssize_t 367_ti_flatten(uint8_t **buf, const TIC *tic) 368{ 369 size_t buflen, len, alen, dlen; 370 char *cap; 371 372 _DIAGASSERT(buf != NULL); 373 _DIAGASSERT(tic != NULL); 374 375 len = strlen(tic->name) + 1; 376 if (tic->alias == NULL) 377 alen = 0; 378 else 379 alen = strlen(tic->alias) + 1; 380 if (tic->desc == NULL) 381 dlen = 0; 382 else 383 dlen = strlen(tic->desc) + 1; 384 385 buflen = sizeof(char) + 386 sizeof(uint16_t) + len + 387 sizeof(uint16_t) + alen + 388 sizeof(uint16_t) + dlen + 389 (sizeof(uint16_t) * 2) + tic->flags.bufpos + 390 (sizeof(uint16_t) * 2) + tic->nums.bufpos + 391 (sizeof(uint16_t) * 2) + tic->strs.bufpos + 392 (sizeof(uint16_t) * 2) + tic->extras.bufpos; 393 394 *buf = malloc(buflen); 395 if (*buf == NULL) 396 return -1; 397 398 cap = (char *)*buf; 399 *cap++ = tic->rtype; 400 401 _ti_encode_count_str(&cap, tic->name, len); 402 _ti_encode_count_str(&cap, tic->alias, alen); 403 _ti_encode_count_str(&cap, tic->desc, dlen); 404 405 _ti_encode_buf(&cap, &tic->flags); 406 407 _ti_encode_buf(&cap, &tic->nums); 408 _ti_encode_buf(&cap, &tic->strs); 409 _ti_encode_buf(&cap, &tic->extras); 410 411 return (uint8_t *)cap - *buf; 412} 413 414static int 415encode_string(const char *term, const char *cap, TBUF *tbuf, const char *str, 416 int flags) 417{ 418 int slash, i, num; 419 char ch, *p, *s, last; 420 421 if (_ti_grow_tbuf(tbuf, strlen(str) + 1) == NULL) 422 return -1; 423 p = s = tbuf->buf + tbuf->bufpos; 424 slash = 0; 425 last = '\0'; 426 /* Convert escape codes */ 427 while ((ch = *str++) != '\0') { 428 if (ch == '\n') { 429 /* Following a newline, strip leading whitespace from 430 * capability strings. */ 431 while (isspace((unsigned char)*str)) 432 str++; 433 continue; 434 } 435 if (slash == 0 && ch == '\\') { 436 slash = 1; 437 continue; 438 } 439 if (slash == 0) { 440 if (last != '%' && ch == '^') { 441 ch = *str++; 442 if (((unsigned char)ch) >= 128) 443 dowarn(flags, 444 "%s: %s: illegal ^ character", 445 term, cap); 446 if (ch == '\0') 447 break; 448 if (ch == '?') 449 ch = '\177'; 450 else if ((ch &= 037) == 0) 451 ch = (char)128; 452 } else if (!isprint((unsigned char)ch)) 453 dowarn(flags, 454 "%s: %s: unprintable character", 455 term, cap); 456 *p++ = ch; 457 last = ch; 458 continue; 459 } 460 slash = 0; 461 if (ch >= '0' && ch <= '7') { 462 num = ch - '0'; 463 for (i = 0; i < 2; i++) { 464 if (*str < '0' || *str > '7') { 465 if (isdigit((unsigned char)*str)) 466 dowarn(flags, 467 "%s: %s: non octal" 468 " digit", term, cap); 469 else 470 break; 471 } 472 num = num * 8 + *str++ - '0'; 473 } 474 if (num == 0) 475 num = 0200; 476 *p++ = (char)num; 477 continue; 478 } 479 switch (ch) { 480 case 'a': 481 *p++ = '\a'; 482 break; 483 case 'b': 484 *p++ = '\b'; 485 break; 486 case 'e': /* FALLTHROUGH */ 487 case 'E': 488 *p++ = '\033'; 489 break; 490 case 'f': 491 *p++ = '\014'; 492 break; 493 case 'l': /* FALLTHROUGH */ 494 case 'n': 495 *p++ = '\n'; 496 break; 497 case 'r': 498 *p++ = '\r'; 499 break; 500 case 's': 501 *p++ = ' '; 502 break; 503 case 't': 504 *p++ = '\t'; 505 break; 506 default: 507 /* We should warn here */ 508 case '^': 509 case ',': 510 case ':': 511 case '|': 512 *p++ = ch; 513 break; 514 } 515 last = ch; 516 } 517 *p++ = '\0'; 518 tbuf->bufpos += (size_t)(p - s); 519 return 0; 520} 521 522char * 523_ti_get_token(char **cap, char sep) 524{ 525 char esc, *token; 526 527 while (isspace((unsigned char)**cap)) 528 (*cap)++; 529 if (**cap == '\0') 530 return NULL; 531 532 /* We can't use stresep(3) as ^ we need two escape chars */ 533 esc = '\0'; 534 for (token = *cap; 535 **cap != '\0' && (esc != '\0' || **cap != sep); 536 (*cap)++) 537 { 538 if (esc == '\0') { 539 if (**cap == '\\' || **cap == '^') 540 esc = **cap; 541 } else { 542 /* termcap /E/ is valid */ 543 if (sep == ':' && esc == '\\' && **cap == 'E') 544 esc = 'x'; 545 else 546 esc = '\0'; 547 } 548 } 549 550 if (**cap != '\0') 551 *(*cap)++ = '\0'; 552 553 return token; 554} 555 556int 557_ti_encode_buf_id_num(TBUF *tbuf, int ind, int num, size_t len) 558{ 559 if (!_ti_grow_tbuf(tbuf, sizeof(uint16_t) + len)) 560 return 0; 561 _ti_encode_buf_16(tbuf, ind); 562 if (len == sizeof(uint32_t)) 563 _ti_encode_buf_32(tbuf, num); 564 else 565 _ti_encode_buf_16(tbuf, num); 566 tbuf->entries++; 567 return 1; 568} 569 570int 571_ti_encode_buf_id_count_str(TBUF *tbuf, int ind, const void *buf, size_t len) 572{ 573 if (!_ti_grow_tbuf(tbuf, 2 * sizeof(uint16_t) + len)) 574 return 0; 575 _ti_encode_buf_16(tbuf, ind); 576 _ti_encode_buf_count_str(tbuf, buf, len); 577 tbuf->entries++; 578 return 1; 579} 580 581int 582_ti_encode_buf_id_flags(TBUF *tbuf, int ind, int flag) 583{ 584 if (!_ti_grow_tbuf(tbuf, sizeof(uint16_t) + 1)) 585 return 0; 586 _ti_encode_buf_16(tbuf, ind); 587 tbuf->buf[tbuf->bufpos++] = flag; 588 tbuf->entries++; 589 return 1; 590} 591 592TIC * 593_ti_compile(char *cap, int flags) 594{ 595 char *token, *p, *e, *name, *desc, *alias; 596 signed char flag; 597 long cnum; 598 short ind; 599 int num; 600 size_t len; 601 TBUF buf; 602 TIC *tic; 603 604 _DIAGASSERT(cap != NULL); 605 606 name = _ti_get_token(&cap, ','); 607 if (name == NULL) { 608 dowarn(flags, "no separator found: %s", cap); 609 return NULL; 610 } 611 desc = strrchr(name, '|'); 612 if (desc != NULL) 613 *desc++ = '\0'; 614 alias = strchr(name, '|'); 615 if (alias != NULL) 616 *alias++ = '\0'; 617 618 if (strlen(name) > UINT16_MAX - 1) { 619 dowarn(flags, "%s: name too long", name); 620 return NULL; 621 } 622 if (desc != NULL && strlen(desc) > UINT16_MAX - 1) { 623 dowarn(flags, "%s: description too long: %s", name, desc); 624 return NULL; 625 } 626 if (alias != NULL && strlen(alias) > UINT16_MAX - 1) { 627 dowarn(flags, "%s: alias too long: %s", name, alias); 628 return NULL; 629 } 630 631 tic = calloc(sizeof(*tic), 1); 632 if (tic == NULL) 633 return NULL; 634 635#ifdef TERMINFO_COMPAT 636 tic->rtype = TERMINFO_RTYPE_O1; /* will promote if needed */ 637#else 638 tic->rtype = TERMINFO_RTYPE; 639#endif 640 buf.buf = NULL; 641 buf.buflen = 0; 642 643 tic->name = _ti_getname(tic->rtype, name); 644 if (tic->name == NULL) 645 goto error; 646 if (alias != NULL && flags & TIC_ALIAS) { 647 tic->alias = _ti_getname(tic->rtype, alias); 648 if (tic->alias == NULL) 649 goto error; 650 } 651 if (desc != NULL && flags & TIC_DESCRIPTION) { 652 tic->desc = strdup(desc); 653 if (tic->desc == NULL) 654 goto error; 655 } 656 657 for (token = _ti_get_token(&cap, ','); 658 token != NULL && *token != '\0'; 659 token = _ti_get_token(&cap, ',')) 660 { 661 /* Skip commented caps */ 662 if (!(flags & TIC_COMMENT) && token[0] == '.') 663 continue; 664 665 /* Obsolete entries */ 666 if (token[0] == 'O' && token[1] == 'T') { 667 if (!(flags & TIC_EXTRA)) 668 continue; 669 token += 2; 670 } 671 672 /* str cap */ 673 p = strchr(token, '='); 674 if (p != NULL) { 675 *p++ = '\0'; 676 /* Don't use the string if we already have it */ 677 ind = (short)_ti_strindex(token); 678 if (ind != -1 && 679 _ti_find_cap(tic, &tic->strs, 's', ind) != NULL) 680 continue; 681 682 /* Encode the string to our scratch buffer */ 683 buf.bufpos = 0; 684 if (encode_string(tic->name, token, 685 &buf, p, flags) == -1) 686 goto error; 687 if (buf.bufpos > UINT16_MAX - 1) { 688 dowarn(flags, "%s: %s: string is too long", 689 tic->name, token); 690 continue; 691 } 692 if (!VALID_STRING(buf.buf)) { 693 dowarn(flags, "%s: %s: invalid string", 694 tic->name, token); 695 continue; 696 } 697 698 if (ind == -1) { 699 if (!_ti_store_extra(tic, 1, token, 's', -1, -2, 700 buf.buf, buf.bufpos, flags)) 701 goto error; 702 } else { 703 if (!_ti_encode_buf_id_count_str(&tic->strs, 704 ind, buf.buf, buf.bufpos)) 705 goto error; 706 } 707 continue; 708 } 709 710 /* num cap */ 711 p = strchr(token, '#'); 712 if (p != NULL) { 713 *p++ = '\0'; 714 /* Don't use the number if we already have it */ 715 ind = (short)_ti_numindex(token); 716 if (ind != -1 && 717 _ti_find_cap(tic, &tic->nums, 'n', ind) != NULL) 718 continue; 719 720 cnum = strtol(p, &e, 0); 721 if (*e != '\0') { 722 dowarn(flags, "%s: %s: not a number", 723 tic->name, token); 724 continue; 725 } 726 if (!VALID_NUMERIC(cnum) || cnum > INT32_MAX) { 727 dowarn(flags, "%s: %s: number %ld out of range", 728 tic->name, token, cnum); 729 continue; 730 } 731 if (cnum > INT16_MAX) { 732 if (flags & TIC_COMPAT_V1) 733 cnum = INT16_MAX; 734 else if (tic->rtype == TERMINFO_RTYPE_O1) 735 if (_ti_promote(tic) == -1) 736 goto error; 737 } 738 739 num = (int)cnum; 740 if (ind == -1) { 741 if (!_ti_store_extra(tic, 1, token, 'n', -1, 742 num, NULL, 0, flags)) 743 goto error; 744 } else { 745 if (!_ti_encode_buf_id_num(&tic->nums, 746 ind, num, _ti_numsize(tic))) 747 goto error; 748 } 749 continue; 750 } 751 752 flag = 1; 753 len = strlen(token) - 1; 754 if (token[len] == '@') { 755 flag = CANCELLED_BOOLEAN; 756 token[len] = '\0'; 757 } 758 ind = (short)_ti_flagindex(token); 759 if (ind == -1 && flag == CANCELLED_BOOLEAN) { 760 if ((ind = (short)_ti_numindex(token)) != -1) { 761 if (_ti_find_cap(tic, &tic->nums, 'n', ind) 762 != NULL) 763 continue; 764 if (!_ti_encode_buf_id_num(&tic->nums, ind, 765 CANCELLED_NUMERIC, _ti_numsize(tic))) 766 goto error; 767 continue; 768 } else if ((ind = (short)_ti_strindex(token)) != -1) { 769 if (_ti_find_cap(tic, &tic->strs, 's', ind) 770 != NULL) 771 continue; 772 if (!_ti_encode_buf_id_num( 773 &tic->strs, ind, 0, sizeof(uint16_t))) 774 goto error; 775 continue; 776 } 777 } 778 if (ind == -1) { 779 if (!_ti_store_extra(tic, 1, token, 'f', flag, 0, NULL, 780 0, flags)) 781 goto error; 782 } else if (_ti_find_cap(tic, &tic->flags, 'f', ind) == NULL) { 783 if (!_ti_encode_buf_id_flags(&tic->flags, ind, flag)) 784 goto error; 785 } 786 } 787 788 free(buf.buf); 789 return tic; 790 791error: 792 free(buf.buf); 793 _ti_freetic(tic); 794 return NULL; 795} 796 797void 798_ti_freetic(TIC *tic) 799{ 800 801 if (tic != NULL) { 802 free(tic->name); 803 free(tic->alias); 804 free(tic->desc); 805 free(tic->extras.buf); 806 free(tic->flags.buf); 807 free(tic->nums.buf); 808 free(tic->strs.buf); 809 free(tic); 810 } 811} 812