tic.c revision 1.20 1 /* $NetBSD: tic.c,v 1.20 2012/06/03 23:19:11 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.20 2012/06/03 23:19:11 joerg Exp $");
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. */
65 typedef struct term {
66 STAILQ_ENTRY(term) next;
67 char *name;
68 TIC *tic;
69 uint32_t id;
70 struct term *base_term;
71 } TERM;
72 static STAILQ_HEAD(, term) terms = STAILQ_HEAD_INITIALIZER(terms);
73
74 static int error_exit;
75 static int Sflag;
76 static size_t nterm, nalias;
77
78 static void __printflike(1, 2)
79 dowarn(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
89 static char *
90 grow_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
100 static int
101 save_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
131 static TERM *
132 find_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
142 static TERM *
143 store_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
164 static int
165 process_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
214 static void
215 merge(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
312 static size_t
313 merge_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
382 static int
383 print_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
447 static void
448 write_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
478 int
479 main(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