1/* $NetBSD$ */ 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$"); 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/* 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. */ 65typedef struct term { 66 STAILQ_ENTRY(term) next; 67 char *name; 68 TIC *tic; 69 uint32_t id; 70 struct term *base_term; 71} TERM; 72static STAILQ_HEAD(, term) terms = STAILQ_HEAD_INITIALIZER(terms); 73 74static int error_exit; 75static int Sflag; 76static size_t nterm, nalias; 77 78static void __printflike(1, 2) 79dowarn(const char *fmt, ...) 80{ 81 va_list va; 82 83 error_exit = 1; 84 va_start(va, fmt); 85 vwarnx(fmt, va); 86 va_end(va); 87} 88 89static char * 90grow_tbuf(TBUF *tbuf, size_t len) 91{ 92 char *buf; 93 94 buf = _ti_grow_tbuf(tbuf, len); 95 if (buf == NULL) 96 err(1, "_ti_grow_tbuf"); 97 return buf; 98} 99 100static int 101save_term(struct cdbw *db, TERM *term) 102{ 103 uint8_t *buf; 104 ssize_t len; 105 size_t slen = strlen(term->name) + 1; 106 107 if (term->base_term != NULL) { 108 len = (ssize_t)slen + 7; 109 buf = emalloc(len); 110 buf[0] = 2; 111 le32enc(buf + 1, term->base_term->id); 112 le16enc(buf + 5, slen); 113 memcpy(buf + 7, term->name, slen); 114 if (cdbw_put(db, term->name, slen, buf, len)) 115 err(1, "cdbw_put"); 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 131static TERM * 132find_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 142static TERM * 143store_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 164static int 165process_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 if (find_term(p) != NULL) { 201 dowarn("%s: has alias for already assigned" 202 " term %s", tic->name, p); 203 } else { 204 store_term(p, term); 205 } 206 p = e; 207 } 208 free(alias); 209 } 210 211 return 0; 212} 213 214static void 215merge(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 312static size_t 313merge_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 STAILQ_FOREACH(term, &terms, next) { 323 if (term->base_term != NULL) 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->base_term != NULL) 338 uterm = uterm->base_term; 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 382static int 383print_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->base_term != NULL) { 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 447static void 448write_database(const char *dbname) 449{ 450 struct cdbw *db; 451 char *tmp_dbname; 452 TERM *term; 453 int fd; 454 455 db = cdbw_open(); 456 if (db == NULL) 457 err(1, "cdbw_open failed"); 458 /* Save the terms */ 459 STAILQ_FOREACH(term, &terms, next) 460 save_term(db, term); 461 462 easprintf(&tmp_dbname, "%s.XXXXXX", dbname); 463 fd = mkstemp(tmp_dbname); 464 if (fd == -1) 465 err(1, "creating temporary database %s failed", tmp_dbname); 466 if (cdbw_output(db, fd, "NetBSD terminfo", cdbw_stable_seeder)) 467 err(1, "writing temporary database %s failed", tmp_dbname); 468 if (fchmod(fd, DEFFILEMODE)) 469 err(1, "fchmod failed"); 470 if (close(fd)) 471 err(1, "writing temporary database %s failed", tmp_dbname); 472 if (rename(tmp_dbname, dbname)) 473 err(1, "renaming %s to %s failed", tmp_dbname, dbname); 474 free(tmp_dbname); 475 cdbw_close(db); 476} 477 478int 479main(int argc, char **argv) 480{ 481 int ch, cflag, sflag, flags; 482 char *source, *dbname, *buf, *ofile; 483 FILE *f; 484 size_t buflen; 485 ssize_t len; 486 TBUF tbuf; 487 488 cflag = sflag = 0; 489 ofile = NULL; 490 flags = TIC_ALIAS | TIC_DESCRIPTION | TIC_WARNING; 491 while ((ch = getopt(argc, argv, "Saco:sx")) != -1) 492 switch (ch) { 493 case 'S': 494 Sflag = 1; 495 /* We still compile aliases so that use= works. 496 * However, it's removed before we flatten to save space. */ 497 flags &= ~TIC_DESCRIPTION; 498 break; 499 case 'a': 500 flags |= TIC_COMMENT; 501 break; 502 case 'c': 503 cflag = 1; 504 break; 505 case 'o': 506 ofile = optarg; 507 break; 508 case 's': 509 sflag = 1; 510 break; 511 case 'x': 512 flags |= TIC_EXTRA; 513 break; 514 case '?': /* FALLTHROUGH */ 515 default: 516 fprintf(stderr, "usage: %s [-acSsx] [-o file] source\n", 517 getprogname()); 518 return EXIT_FAILURE; 519 } 520 521 if (optind == argc) 522 errx(1, "No source file given"); 523 source = argv[optind++]; 524 f = fopen(source, "r"); 525 if (f == NULL) 526 err(1, "fopen: %s", source); 527 528 hcreate(HASH_SIZE); 529 530 buf = tbuf.buf = NULL; 531 buflen = tbuf.buflen = tbuf.bufpos = 0; 532 while ((len = getline(&buf, &buflen, f)) != -1) { 533 /* Skip comments */ 534 if (*buf == '#') 535 continue; 536 if (buf[len - 1] != '\n') { 537 process_entry(&tbuf, flags); 538 dowarn("last line is not a comment" 539 " and does not end with a newline"); 540 continue; 541 } 542 /* 543 If the first char is space not a space then we have a 544 new entry, so process it. 545 */ 546 if (!isspace((unsigned char)*buf) && tbuf.bufpos != 0) 547 process_entry(&tbuf, flags); 548 549 /* Grow the buffer if needed */ 550 grow_tbuf(&tbuf, len); 551 /* Append the string */ 552 memcpy(tbuf.buf + tbuf.bufpos, buf, len); 553 tbuf.bufpos += len; 554 } 555 free(buf); 556 /* Process the last entry if not done already */ 557 process_entry(&tbuf, flags); 558 free(tbuf.buf); 559 560 /* Merge use entries until we have merged all we can */ 561 while (merge_use(flags) != 0) 562 ; 563 564 if (Sflag) { 565 print_dump(argc - optind, argv + optind); 566 return error_exit; 567 } 568 569 if (cflag) 570 return error_exit; 571 572 if (ofile == NULL) 573 easprintf(&dbname, "%s.cdb", source); 574 else 575 dbname = ofile; 576 write_database(dbname); 577 578 if (sflag != 0) 579 fprintf(stderr, "%zu entries and %zu aliases written to %s\n", 580 nterm, nalias, dbname); 581 582#ifdef __VALGRIND__ 583 if (ofile == NULL) 584 free(dbname); 585 while ((term = STAILQ_FIRST(&terms)) != NULL) { 586 STAILQ_REMOVE_HEAD(&terms, next); 587 _ti_freetic(term->tic); 588 free(term->name); 589 free(term); 590 } 591 hdestroy(); 592#endif 593 594 595 return EXIT_SUCCESS; 596} 597