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