Changeset View
Changeset View
Standalone View
Standalone View
contrib/mg/tic.c
- This file was added.
| /* $NetBSD: tic.c,v 1.31 2017/10/02 21:53:55 joerg Exp $ */ | |||||
| /* | |||||
| * Copyright (c) 2009, 2010 The NetBSD Foundation, Inc. | |||||
| * | |||||
| * This code is derived from software contributed to The NetBSD Foundation | |||||
| * by Roy Marples. | |||||
| * | |||||
| * Redistribution and use in source and binary forms, with or without | |||||
| * modification, are permitted provided that the following conditions | |||||
| * are met: | |||||
| * 1. Redistributions of source code must retain the above copyright | |||||
| * notice, this list of conditions and the following disclaimer. | |||||
| * 2. Redistributions in binary form must reproduce the above copyright | |||||
| * notice, this list of conditions and the following disclaimer in the | |||||
| * documentation and/or other materials provided with the distribution. | |||||
| * | |||||
| * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR | |||||
| * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | |||||
| * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | |||||
| * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, | |||||
| * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | |||||
| * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |||||
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |||||
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |||||
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | |||||
| * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||||
| */ | |||||
| #include <sys/cdefs.h> | |||||
| #include <sys/types.h> | |||||
| #include <sys/queue.h> | |||||
| #include <sys/stat.h> | |||||
| #include <ctype.h> | |||||
| #include <err.h> | |||||
| #include <errno.h> | |||||
| #include <getopt.h> | |||||
| #include <limits.h> | |||||
| #include <fcntl.h> | |||||
| #include <search.h> | |||||
| #include <stdarg.h> | |||||
| #include <stdlib.h> | |||||
| #include <stdio.h> | |||||
| #include <string.h> | |||||
| #include <unistd.h> | |||||
| #if defined(__linux__) || defined(__CYGWIN__) | |||||
| #include "util.h" | |||||
| #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) | |||||
| #include <util.h> | |||||
| #else | |||||
| #include <libutil.h> | |||||
| #endif | |||||
| #include "cdbw.h" | |||||
| #include "term_private.h" | |||||
| #include "terminfo_term.h" | |||||
| #define HASH_SIZE 16384 /* 2012-06-01: 3600 entries */ | |||||
| #define __UNCONST(a) ((void *)(unsigned long)(const void *)(a)) | |||||
| #ifndef TAILQ_REMOVE_HEAD | |||||
| #define TAILQ_REMOVE_HEAD(head, field) do { \ | |||||
| if (((head)->tqh_first = (head)->tqh_first->field.tqe_next) == \ | |||||
| NULL) \ | |||||
| (head)->tqh_last = &(head)->tqh_first; \ | |||||
| } while (/*CONSTCOND*/0) | |||||
| #endif | |||||
| typedef struct term { | |||||
| TAILQ_ENTRY(term) next; | |||||
| char *name; | |||||
| TIC *tic; | |||||
| uint32_t id; | |||||
| struct term *base_term; | |||||
| } TERM; | |||||
| static TAILQ_HEAD(, term) terms = TAILQ_HEAD_INITIALIZER(terms); | |||||
| static int error_exit; | |||||
| static int Sflag; | |||||
| static size_t nterm, nalias; | |||||
| static void | |||||
| dowarn(const char *fmt, ...) | |||||
| { | |||||
| va_list va; | |||||
| error_exit = 1; | |||||
| va_start(va, fmt); | |||||
| vwarnx(fmt, va); | |||||
| va_end(va); | |||||
| } | |||||
| static char * | |||||
| grow_tbuf(TBUF *tbuf, size_t len) | |||||
| { | |||||
| char *buf; | |||||
| buf = _ti_grow_tbuf(tbuf, len); | |||||
| if (buf == NULL) | |||||
| err(1, "_ti_grow_tbuf"); | |||||
| return buf; | |||||
| } | |||||
| /* From NetBSD sys/arch/hpc/stand/include/machine/endian.h */ | |||||
| static uint16_t | |||||
| le16dec(const void *buf) | |||||
| { | |||||
| const uint8_t *p = (const uint8_t *)buf; | |||||
| return ((p[1] << 8) | p[0]); | |||||
| } | |||||
| static void | |||||
| le16enc(void *buf, uint32_t u) | |||||
| { | |||||
| uint8_t *p = (uint8_t *) buf; | |||||
| p[0] = u & 0xff; | |||||
| p[1] = ((unsigned)u >> 8) & 0xff; | |||||
| } | |||||
| void | |||||
| le32enc(void *buf, uint32_t u) | |||||
| { | |||||
| uint8_t *p = (uint8_t *) buf; | |||||
| p[0] = u & 0xff; | |||||
| p[1] = (u >> 8) & 0xff; | |||||
| p[2] = (u >> 16) & 0xff; | |||||
| p[3] = (u >> 24) & 0xff; | |||||
| } | |||||
| static int | |||||
| save_term(struct cdbw *db, TERM *term) | |||||
| { | |||||
| uint8_t *buf; | |||||
| ssize_t len; | |||||
| size_t slen = strlen(term->name) + 1; | |||||
| if (term->base_term != NULL) { | |||||
| len = (ssize_t)slen + 7; | |||||
| buf = malloc(len); | |||||
| buf[0] = 2; | |||||
| le32enc(buf + 1, term->base_term->id); | |||||
| le16enc(buf + 5, slen); | |||||
| memcpy(buf + 7, term->name, slen); | |||||
| if (cdbw_put(db, term->name, slen, buf, len)) | |||||
| err(1, "cdbw_put"); | |||||
| free(buf); | |||||
| return 0; | |||||
| } | |||||
| len = _ti_flatten(&buf, term->tic); | |||||
| if (len == -1) | |||||
| return -1; | |||||
| if (cdbw_put_data(db, buf, len, &term->id)) | |||||
| err(1, "cdbw_put_data"); | |||||
| if (cdbw_put_key(db, term->name, slen, term->id)) | |||||
| err(1, "cdbw_put_key"); | |||||
| free(buf); | |||||
| return 0; | |||||
| } | |||||
| static TERM * | |||||
| find_term(const char *name) | |||||
| { | |||||
| ENTRY elem, *elemp; | |||||
| elem.key = __UNCONST(name); | |||||
| elem.data = NULL; | |||||
| elemp = hsearch(elem, FIND); | |||||
| return elemp ? (TERM *)elemp->data : NULL; | |||||
| } | |||||
| static TERM * | |||||
| store_term(const char *name, TERM *base_term) | |||||
| { | |||||
| TERM *term; | |||||
| ENTRY elem; | |||||
| term = calloc(1, sizeof(*term)); | |||||
| term->name = strdup(name); | |||||
| TAILQ_INSERT_TAIL(&terms, term, next); | |||||
| elem.key = strdup(name); | |||||
| elem.data = term; | |||||
| hsearch(elem, ENTER); | |||||
| term->base_term = base_term; | |||||
| if (base_term != NULL) | |||||
| nalias++; | |||||
| else | |||||
| nterm++; | |||||
| return term; | |||||
| } | |||||
| static int | |||||
| process_entry(TBUF *buf, int flags) | |||||
| { | |||||
| char *p, *e, *alias; | |||||
| TERM *term; | |||||
| TIC *tic; | |||||
| if (buf->bufpos == 0) | |||||
| return 0; | |||||
| /* Terminate the string */ | |||||
| buf->buf[buf->bufpos - 1] = '\0'; | |||||
| /* First rewind the buffer for new entries */ | |||||
| buf->bufpos = 0; | |||||
| if (isspace((unsigned char)*buf->buf)) | |||||
| return 0; | |||||
| tic = _ti_compile(buf->buf, flags); | |||||
| if (tic == NULL) | |||||
| return 0; | |||||
| if (find_term(tic->name) != NULL) { | |||||
| dowarn("%s: duplicate entry", tic->name); | |||||
| _ti_freetic(tic); | |||||
| return 0; | |||||
| } | |||||
| term = store_term(tic->name, NULL); | |||||
| term->tic = tic; | |||||
| /* Create aliased terms */ | |||||
| if (tic->alias != NULL) { | |||||
| alias = p = strdup(tic->alias); | |||||
| while (p != NULL && *p != '\0') { | |||||
| e = strchr(p, '|'); | |||||
| if (e != NULL) | |||||
| *e++ = '\0'; | |||||
| if (find_term(p) != NULL) { | |||||
| dowarn("%s: has alias for already assigned" | |||||
| " term %s", tic->name, p); | |||||
| } else { | |||||
| store_term(p, term); | |||||
| } | |||||
| p = e; | |||||
| } | |||||
| free(alias); | |||||
| } | |||||
| return 0; | |||||
| } | |||||
| static void | |||||
| merge(TIC *rtic, TIC *utic, int flags) | |||||
| { | |||||
| char *cap, flag, *code, type, *str; | |||||
| short ind, num; | |||||
| size_t n; | |||||
| cap = utic->flags.buf; | |||||
| for (n = utic->flags.entries; n > 0; n--) { | |||||
| ind = le16dec(cap); | |||||
| cap += sizeof(uint16_t); | |||||
| flag = *cap++; | |||||
| if (VALID_BOOLEAN(flag) && | |||||
| _ti_find_cap(&rtic->flags, 'f', ind) == NULL) | |||||
| { | |||||
| _ti_grow_tbuf(&rtic->flags, sizeof(uint16_t) + 1); | |||||
| le16enc(rtic->flags.buf + rtic->flags.bufpos, ind); | |||||
| rtic->flags.bufpos += sizeof(uint16_t); | |||||
| rtic->flags.buf[rtic->flags.bufpos++] = flag; | |||||
| rtic->flags.entries++; | |||||
| } | |||||
| } | |||||
| cap = utic->nums.buf; | |||||
| for (n = utic->nums.entries; n > 0; n--) { | |||||
| ind = le16dec(cap); | |||||
| cap += sizeof(uint16_t); | |||||
| num = le16dec(cap); | |||||
| cap += sizeof(uint16_t); | |||||
| if (VALID_NUMERIC(num) && | |||||
| _ti_find_cap(&rtic->nums, 'n', ind) == NULL) | |||||
| { | |||||
| grow_tbuf(&rtic->nums, sizeof(uint16_t) * 2); | |||||
| le16enc(rtic->nums.buf + rtic->nums.bufpos, ind); | |||||
| rtic->nums.bufpos += sizeof(uint16_t); | |||||
| le16enc(rtic->nums.buf + rtic->nums.bufpos, num); | |||||
| rtic->nums.bufpos += sizeof(uint16_t); | |||||
| rtic->nums.entries++; | |||||
| } | |||||
| } | |||||
| cap = utic->strs.buf; | |||||
| for (n = utic->strs.entries; n > 0; n--) { | |||||
| ind = le16dec(cap); | |||||
| cap += sizeof(uint16_t); | |||||
| num = le16dec(cap); | |||||
| cap += sizeof(uint16_t); | |||||
| if (num > 0 && | |||||
| _ti_find_cap(&rtic->strs, 's', ind) == NULL) | |||||
| { | |||||
| grow_tbuf(&rtic->strs, (sizeof(uint16_t) * 2) + num); | |||||
| le16enc(rtic->strs.buf + rtic->strs.bufpos, ind); | |||||
| rtic->strs.bufpos += sizeof(uint16_t); | |||||
| le16enc(rtic->strs.buf + rtic->strs.bufpos, num); | |||||
| rtic->strs.bufpos += sizeof(uint16_t); | |||||
| memcpy(rtic->strs.buf + rtic->strs.bufpos, | |||||
| cap, num); | |||||
| rtic->strs.bufpos += num; | |||||
| rtic->strs.entries++; | |||||
| } | |||||
| cap += num; | |||||
| } | |||||
| cap = utic->extras.buf; | |||||
| for (n = utic->extras.entries; n > 0; n--) { | |||||
| num = le16dec(cap); | |||||
| cap += sizeof(uint16_t); | |||||
| code = cap; | |||||
| cap += num; | |||||
| type = *cap++; | |||||
| flag = 0; | |||||
| str = NULL; | |||||
| switch (type) { | |||||
| case 'f': | |||||
| flag = *cap++; | |||||
| if (!VALID_BOOLEAN(flag)) | |||||
| continue; | |||||
| break; | |||||
| case 'n': | |||||
| num = le16dec(cap); | |||||
| cap += sizeof(uint16_t); | |||||
| if (!VALID_NUMERIC(num)) | |||||
| continue; | |||||
| break; | |||||
| case 's': | |||||
| num = le16dec(cap); | |||||
| cap += sizeof(uint16_t); | |||||
| str = cap; | |||||
| cap += num; | |||||
| if (num == 0) | |||||
| continue; | |||||
| break; | |||||
| } | |||||
| _ti_store_extra(rtic, 0, code, type, flag, num, str, num, | |||||
| flags); | |||||
| } | |||||
| } | |||||
| static size_t | |||||
| merge_use(int flags) | |||||
| { | |||||
| size_t skipped, merged, memn; | |||||
| char *cap, *scap; | |||||
| uint16_t num; | |||||
| TIC *rtic, *utic; | |||||
| TERM *term, *uterm;; | |||||
| skipped = merged = 0; | |||||
| TAILQ_FOREACH(term, &terms, next) { | |||||
| if (term->base_term != NULL) | |||||
| continue; | |||||
| rtic = term->tic; | |||||
| while ((cap = _ti_find_extra(&rtic->extras, "use")) != NULL) { | |||||
| if (*cap++ != 's') { | |||||
| dowarn("%s: use is not string", rtic->name); | |||||
| break; | |||||
| } | |||||
| cap += sizeof(uint16_t); | |||||
| if (strcmp(rtic->name, cap) == 0) { | |||||
| dowarn("%s: uses itself", rtic->name); | |||||
| goto remove; | |||||
| } | |||||
| uterm = find_term(cap); | |||||
| if (uterm != NULL && uterm->base_term != NULL) | |||||
| uterm = uterm->base_term; | |||||
| if (uterm == NULL) { | |||||
| dowarn("%s: no use record for %s", | |||||
| rtic->name, cap); | |||||
| goto remove; | |||||
| } | |||||
| utic = uterm->tic; | |||||
| if (strcmp(utic->name, rtic->name) == 0) { | |||||
| dowarn("%s: uses itself", rtic->name); | |||||
| goto remove; | |||||
| } | |||||
| if (_ti_find_extra(&utic->extras, "use") != NULL) { | |||||
| skipped++; | |||||
| break; | |||||
| } | |||||
| cap = _ti_find_extra(&rtic->extras, "use"); | |||||
| merge(rtic, utic, flags); | |||||
| remove: | |||||
| /* The pointers may have changed, find the use again */ | |||||
| cap = _ti_find_extra(&rtic->extras, "use"); | |||||
| if (cap == NULL) | |||||
| dowarn("%s: use no longer exists - impossible", | |||||
| rtic->name); | |||||
| else { | |||||
| scap = cap - (4 + sizeof(uint16_t)); | |||||
| cap++; | |||||
| num = le16dec(cap); | |||||
| cap += sizeof(uint16_t) + num; | |||||
| memn = rtic->extras.bufpos - | |||||
| (cap - rtic->extras.buf); | |||||
| memmove(scap, cap, memn); | |||||
| rtic->extras.bufpos -= cap - scap; | |||||
| cap = scap; | |||||
| rtic->extras.entries--; | |||||
| merged++; | |||||
| } | |||||
| } | |||||
| } | |||||
| if (merged == 0 && skipped != 0) | |||||
| dowarn("circular use detected"); | |||||
| return merged; | |||||
| } | |||||
| static int | |||||
| print_dump(int argc, char **argv) | |||||
| { | |||||
| TERM *term; | |||||
| uint8_t *buf; | |||||
| int i, n; | |||||
| size_t j, col; | |||||
| ssize_t len; | |||||
| printf("struct compiled_term {\n"); | |||||
| printf("\tconst char *name;\n"); | |||||
| printf("\tconst char *cap;\n"); | |||||
| printf("\tsize_t caplen;\n"); | |||||
| printf("};\n\n"); | |||||
| printf("const struct compiled_term compiled_terms[] = {\n"); | |||||
| n = 0; | |||||
| for (i = 0; i < argc; i++) { | |||||
| term = find_term(argv[i]); | |||||
| if (term == NULL) { | |||||
| warnx("%s: no description for terminal", argv[i]); | |||||
| continue; | |||||
| } | |||||
| if (term->base_term != NULL) { | |||||
| warnx("%s: cannot dump alias", argv[i]); | |||||
| continue; | |||||
| } | |||||
| /* Don't compile the aliases in, save space */ | |||||
| free(term->tic->alias); | |||||
| term->tic->alias = NULL; | |||||
| len = _ti_flatten(&buf, term->tic); | |||||
| if (len == 0 || len == -1) | |||||
| continue; | |||||
| printf("\t{\n"); | |||||
| printf("\t\t\"%s\",\n", argv[i]); | |||||
| n++; | |||||
| for (j = 0, col = 0; j < (size_t)len; j++) { | |||||
| if (col == 0) { | |||||
| printf("\t\t\""); | |||||
| col = 16; | |||||
| } | |||||
| col += printf("\\%03o", (uint8_t)buf[j]); | |||||
| if (col > 75) { | |||||
| printf("\"%s\n", | |||||
| j + 1 == (size_t)len ? "," : ""); | |||||
| col = 0; | |||||
| } | |||||
| } | |||||
| if (col != 0) | |||||
| printf("\",\n"); | |||||
| printf("\t\t%zu\n", len); | |||||
| printf("\t}"); | |||||
| if (i + 1 < argc) | |||||
| printf(","); | |||||
| printf("\n"); | |||||
| free(buf); | |||||
| } | |||||
| printf("};\n"); | |||||
| return n; | |||||
| } | |||||
| static void | |||||
| write_database(const char *dbname) | |||||
| { | |||||
| struct cdbw *db; | |||||
| char *tmp_dbname; | |||||
| TERM *term; | |||||
| int fd; | |||||
| db = cdbw_open(); | |||||
| if (db == NULL) | |||||
| err(1, "cdbw_open failed"); | |||||
| /* Save the terms */ | |||||
| TAILQ_FOREACH(term, &terms, next) | |||||
| save_term(db, term); | |||||
| asprintf(&tmp_dbname, "%s.XXXXXX", dbname); | |||||
| fd = mkstemp(tmp_dbname); | |||||
| if (fd == -1) | |||||
| err(1, "creating temporary database %s failed", tmp_dbname); | |||||
| if (cdbw_output(db, fd, "NetBSD terminfo", cdbw_stable_seeder)) | |||||
| err(1, "writing temporary database %s failed", tmp_dbname); | |||||
| if (fchmod(fd, DEFFILEMODE)) | |||||
| err(1, "fchmod failed"); | |||||
| if (close(fd)) | |||||
| err(1, "writing temporary database %s failed", tmp_dbname); | |||||
| if (rename(tmp_dbname, dbname)) | |||||
| err(1, "renaming %s to %s failed", tmp_dbname, dbname); | |||||
| free(tmp_dbname); | |||||
| cdbw_close(db); | |||||
| } | |||||
| int | |||||
| main(int argc, char **argv) | |||||
| { | |||||
| int ch, cflag, sflag, flags; | |||||
| char *source, *dbname, *buf, *ofile; | |||||
| FILE *f; | |||||
| size_t buflen; | |||||
| ssize_t len; | |||||
| TBUF tbuf; | |||||
| struct term *term; | |||||
| cflag = sflag = 0; | |||||
| ofile = NULL; | |||||
| flags = TIC_ALIAS | TIC_DESCRIPTION | TIC_WARNING; | |||||
| while ((ch = getopt(argc, argv, "Saco:sx")) != -1) | |||||
| switch (ch) { | |||||
| case 'S': | |||||
| Sflag = 1; | |||||
| /* We still compile aliases so that use= works. | |||||
| * However, it's removed before we flatten to save space. */ | |||||
| flags &= ~TIC_DESCRIPTION; | |||||
| break; | |||||
| case 'a': | |||||
| flags |= TIC_COMMENT; | |||||
| break; | |||||
| case 'c': | |||||
| cflag = 1; | |||||
| break; | |||||
| case 'o': | |||||
| ofile = optarg; | |||||
| break; | |||||
| case 's': | |||||
| sflag = 1; | |||||
| break; | |||||
| case 'x': | |||||
| flags |= TIC_EXTRA; | |||||
| break; | |||||
| case '?': /* FALLTHROUGH */ | |||||
| default: | |||||
| fprintf(stderr, "usage: %s [-acSsx] [-o file] source\n", | |||||
| getprogname()); | |||||
| return EXIT_FAILURE; | |||||
| } | |||||
| if (optind == argc) | |||||
| errx(1, "No source file given"); | |||||
| source = argv[optind++]; | |||||
| f = fopen(source, "r"); | |||||
| if (f == NULL) | |||||
| err(1, "fopen: %s", source); | |||||
| hcreate(HASH_SIZE); | |||||
| buf = tbuf.buf = NULL; | |||||
| buflen = tbuf.buflen = tbuf.bufpos = 0; | |||||
| while ((len = getline(&buf, &buflen, f)) != -1) { | |||||
| /* Skip comments */ | |||||
| if (*buf == '#') | |||||
| continue; | |||||
| if (buf[len - 1] != '\n') { | |||||
| process_entry(&tbuf, flags); | |||||
| dowarn("last line is not a comment" | |||||
| " and does not end with a newline"); | |||||
| continue; | |||||
| } | |||||
| /* | |||||
| * If the first char is space not a space then we have a | |||||
| * new entry, so process it. | |||||
| */ | |||||
| if (!isspace((unsigned char)*buf) && tbuf.bufpos != 0) | |||||
| process_entry(&tbuf, flags); | |||||
| /* Grow the buffer if needed */ | |||||
| grow_tbuf(&tbuf, len); | |||||
| /* Append the string */ | |||||
| memcpy(tbuf.buf + tbuf.bufpos, buf, len); | |||||
| tbuf.bufpos += len; | |||||
| } | |||||
| free(buf); | |||||
| /* Process the last entry if not done already */ | |||||
| process_entry(&tbuf, flags); | |||||
| free(tbuf.buf); | |||||
| /* Merge use entries until we have merged all we can */ | |||||
| while (merge_use(flags) != 0) | |||||
| ; | |||||
| if (Sflag) { | |||||
| print_dump(argc - optind, argv + optind); | |||||
| return error_exit; | |||||
| } | |||||
| if (cflag) | |||||
| return error_exit; | |||||
| if (ofile == NULL) | |||||
| asprintf(&dbname, "%s.cdb", source); | |||||
| else | |||||
| dbname = ofile; | |||||
| write_database(dbname); | |||||
| if (sflag != 0) | |||||
| fprintf(stderr, "%zu entries and %zu aliases written to %s\n", | |||||
| nterm, nalias, dbname); | |||||
| if (ofile == NULL) | |||||
| free(dbname); | |||||
| while ((term = TAILQ_FIRST(&terms)) != NULL) { | |||||
| TAILQ_REMOVE_HEAD(&terms, next); | |||||
| _ti_freetic(term->tic); | |||||
| free(term->name); | |||||
| free(term); | |||||
| } | |||||
| return EXIT_SUCCESS; | |||||
| } | |||||