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