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