tic.c revision 1.13 1 /* $NetBSD: tic.c,v 1.13 2012/05/31 19:00:41 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.13 2012/05/31 19:00:41 joerg 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 __printflike(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 memmove(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 buflen, nterm, nalias;
447 ssize_t len;
448 TBUF tbuf;
449 TERM *term;
450
451 cflag = sflag = 0;
452 ofile = NULL;
453 flags = TIC_ALIAS | TIC_DESCRIPTION | TIC_WARNING;
454 while ((ch = getopt(argc, argv, "Saco:sx")) != -1)
455 switch (ch) {
456 case 'S':
457 Sflag = 1;
458 /* We still compile aliases so that use= works.
459 * However, it's removed before we flatten to save space. */
460 flags &= ~TIC_DESCRIPTION;
461 break;
462 case 'a':
463 flags |= TIC_COMMENT;
464 break;
465 case 'c':
466 cflag = 1;
467 break;
468 case 'o':
469 ofile = optarg;
470 break;
471 case 's':
472 sflag = 1;
473 break;
474 case 'x':
475 flags |= TIC_EXTRA;
476 break;
477 case '?': /* FALLTHROUGH */
478 default:
479 fprintf(stderr, "usage: %s [-acSsx] [-o file] source\n",
480 getprogname());
481 return EXIT_FAILURE;
482 }
483
484 if (optind == argc)
485 errx(1, "No source file given");
486 source = argv[optind++];
487 f = fopen(source, "r");
488 if (f == NULL)
489 err(1, "fopen: %s", source);
490 if (!cflag && !Sflag) {
491 if (ofile == NULL)
492 ofile = source;
493 len = strlen(ofile) + 9;
494 dbname = malloc(len + 4); /* For adding .db after open */
495 if (dbname == NULL)
496 err(1, "malloc");
497 snprintf(dbname, len, "%s.tmp", ofile);
498 db = dbm_open(dbname, O_CREAT | O_RDWR | O_TRUNC, DEFFILEMODE);
499 if (db == NULL)
500 err(1, "dbopen: %s", source);
501 p = dbname + strlen(dbname);
502 *p++ = '.';
503 *p++ = 'd';
504 *p++ = 'b';
505 *p++ = '\0';
506 atexit(do_unlink);
507 } else
508 db = NULL; /* satisfy gcc warning */
509
510 buf = NULL;
511 buflen = tbuf.buflen = tbuf.bufpos = 0;
512 while ((len = getline(&buf, &buflen, f)) != -1) {
513 /* Skip comments */
514 if (*buf == '#')
515 continue;
516 if (buf[len - 1] != '\n') {
517 process_entry(&tbuf, flags);
518 dowarn("last line is not a comment"
519 " and does not end with a newline");
520 continue;
521 }
522 /*
523 If the first char is space not a space then we have a
524 new entry, so process it.
525 */
526 if (!isspace((unsigned char)*buf) && tbuf.bufpos != 0)
527 process_entry(&tbuf, flags);
528
529 /* Grow the buffer if needed */
530 grow_tbuf(&tbuf, len);
531 /* Append the string */
532 memcpy(tbuf.buf + tbuf.bufpos, buf, len);
533 tbuf.bufpos += len;
534 }
535 /* Process the last entry if not done already */
536 process_entry(&tbuf, flags);
537
538 /* Merge use entries until we have merged all we can */
539 while (merge_use(flags) != 0)
540 ;
541
542 if (Sflag) {
543 print_dump(argc - optind, argv + optind);
544 return error_exit;
545 }
546
547 if (cflag)
548 return error_exit;
549
550 /* Save the terms */
551 nterm = nalias = 0;
552 for (term = terms; term != NULL; term = term->next) {
553 save_term(db, term);
554 if (term->type == 'a')
555 nalias++;
556 else
557 nterm++;
558 }
559
560 /* done! */
561 dbm_close(db);
562
563 /* Rename the tmp db to the real one now */
564 len = strlen(ofile) + 4;
565 p = malloc(len);
566 if (p == NULL)
567 err(1, "malloc");
568 snprintf(p, len, "%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 return EXIT_SUCCESS;
579 }
580