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