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