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