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