tic.c revision 1.27.4.1 1 /* $NetBSD: tic.c,v 1.27.4.1 2017/05/11 02:58:43 pgoyette 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.27.4.1 2017/05/11 02:58:43 pgoyette Exp $");
36
37 #include <sys/types.h>
38 #include <sys/queue.h>
39 #include <sys/stat.h>
40
41 #if !HAVE_NBTOOL_CONFIG_H || HAVE_SYS_ENDIAN_H
42 #include <sys/endian.h>
43 #endif
44
45 #include <cdbw.h>
46 #include <ctype.h>
47 #include <err.h>
48 #include <errno.h>
49 #include <getopt.h>
50 #include <limits.h>
51 #include <fcntl.h>
52 #include <search.h>
53 #include <stdarg.h>
54 #include <stdlib.h>
55 #include <stdio.h>
56 #include <string.h>
57 #include <term_private.h>
58 #include <term.h>
59 #include <util.h>
60
61 #define HASH_SIZE 16384 /* 2012-06-01: 3600 entries */
62
63 typedef struct term {
64 STAILQ_ENTRY(term) next;
65 char *name;
66 TIC *tic;
67 uint32_t id;
68 struct term *base_term;
69 } TERM;
70 static STAILQ_HEAD(, term) terms = STAILQ_HEAD_INITIALIZER(terms);
71
72 static int error_exit;
73 static int Sflag;
74 static size_t nterm, nalias;
75
76 static void __printflike(1, 2)
77 dowarn(const char *fmt, ...)
78 {
79 va_list va;
80
81 error_exit = 1;
82 va_start(va, fmt);
83 vwarnx(fmt, va);
84 va_end(va);
85 }
86
87 static char *
88 grow_tbuf(TBUF *tbuf, size_t len)
89 {
90 char *buf;
91
92 buf = _ti_grow_tbuf(tbuf, len);
93 if (buf == NULL)
94 err(1, "_ti_grow_tbuf");
95 return buf;
96 }
97
98 static int
99 save_term(struct cdbw *db, TERM *term)
100 {
101 uint8_t *buf;
102 ssize_t len;
103 size_t slen = strlen(term->name) + 1;
104
105 if (term->base_term != NULL) {
106 len = (ssize_t)slen + 7;
107 buf = emalloc(len);
108 buf[0] = 2;
109 le32enc(buf + 1, term->base_term->id);
110 le16enc(buf + 5, slen);
111 memcpy(buf + 7, term->name, slen);
112 if (cdbw_put(db, term->name, slen, buf, len))
113 err(1, "cdbw_put");
114 free(buf);
115 return 0;
116 }
117
118 len = _ti_flatten(&buf, term->tic);
119 if (len == -1)
120 return -1;
121
122 if (cdbw_put_data(db, buf, len, &term->id))
123 err(1, "cdbw_put_data");
124 if (cdbw_put_key(db, term->name, slen, term->id))
125 err(1, "cdbw_put_key");
126 free(buf);
127 return 0;
128 }
129
130 static TERM *
131 find_term(const char *name)
132 {
133 ENTRY elem, *elemp;
134
135 elem.key = __UNCONST(name);
136 elem.data = NULL;
137 elemp = hsearch(elem, FIND);
138 return elemp ? (TERM *)elemp->data : NULL;
139 }
140
141 static TERM *
142 store_term(const char *name, TERM *base_term)
143 {
144 TERM *term;
145 ENTRY elem;
146
147 term = ecalloc(1, sizeof(*term));
148 term->name = estrdup(name);
149 STAILQ_INSERT_TAIL(&terms, term, next);
150 elem.key = estrdup(name);
151 elem.data = term;
152 hsearch(elem, ENTER);
153
154 term->base_term = base_term;
155 if (base_term != NULL)
156 nalias++;
157 else
158 nterm++;
159
160 return term;
161 }
162
163 static int
164 process_entry(TBUF *buf, int flags)
165 {
166 char *p, *e, *alias;
167 TERM *term;
168 TIC *tic;
169
170 if (buf->bufpos == 0)
171 return 0;
172 /* Terminate the string */
173 buf->buf[buf->bufpos - 1] = '\0';
174 /* First rewind the buffer for new entries */
175 buf->bufpos = 0;
176
177 if (isspace((unsigned char)*buf->buf))
178 return 0;
179
180 tic = _ti_compile(buf->buf, flags);
181 if (tic == NULL)
182 return 0;
183
184 if (find_term(tic->name) != NULL) {
185 dowarn("%s: duplicate entry", tic->name);
186 _ti_freetic(tic);
187 return 0;
188 }
189 term = store_term(tic->name, NULL);
190 term->tic = tic;
191
192 /* Create aliased terms */
193 if (tic->alias != NULL) {
194 alias = p = estrdup(tic->alias);
195 while (p != NULL && *p != '\0') {
196 e = strchr(p, '|');
197 if (e != NULL)
198 *e++ = '\0';
199 if (find_term(p) != NULL) {
200 dowarn("%s: has alias for already assigned"
201 " term %s", tic->name, p);
202 } else {
203 store_term(p, term);
204 }
205 p = e;
206 }
207 free(alias);
208 }
209
210 return 0;
211 }
212
213 static void
214 merge(TIC *rtic, TIC *utic, int flags)
215 {
216 char *cap, flag, *code, type, *str;
217 short ind, num;
218 size_t n;
219
220 cap = utic->flags.buf;
221 for (n = utic->flags.entries; n > 0; n--) {
222 ind = le16dec(cap);
223 cap += sizeof(uint16_t);
224 flag = *cap++;
225 if (VALID_BOOLEAN(flag) &&
226 _ti_find_cap(&rtic->flags, 'f', ind) == NULL)
227 {
228 _ti_grow_tbuf(&rtic->flags, sizeof(uint16_t) + 1);
229 le16enc(rtic->flags.buf + rtic->flags.bufpos, ind);
230 rtic->flags.bufpos += sizeof(uint16_t);
231 rtic->flags.buf[rtic->flags.bufpos++] = flag;
232 rtic->flags.entries++;
233 }
234 }
235
236 cap = utic->nums.buf;
237 for (n = utic->nums.entries; n > 0; n--) {
238 ind = le16dec(cap);
239 cap += sizeof(uint16_t);
240 num = le16dec(cap);
241 cap += sizeof(uint16_t);
242 if (VALID_NUMERIC(num) &&
243 _ti_find_cap(&rtic->nums, 'n', ind) == NULL)
244 {
245 grow_tbuf(&rtic->nums, sizeof(uint16_t) * 2);
246 le16enc(rtic->nums.buf + rtic->nums.bufpos, ind);
247 rtic->nums.bufpos += sizeof(uint16_t);
248 le16enc(rtic->nums.buf + rtic->nums.bufpos, num);
249 rtic->nums.bufpos += sizeof(uint16_t);
250 rtic->nums.entries++;
251 }
252 }
253
254 cap = utic->strs.buf;
255 for (n = utic->strs.entries; n > 0; n--) {
256 ind = le16dec(cap);
257 cap += sizeof(uint16_t);
258 num = le16dec(cap);
259 cap += sizeof(uint16_t);
260 if (num > 0 &&
261 _ti_find_cap(&rtic->strs, 's', ind) == NULL)
262 {
263 grow_tbuf(&rtic->strs, (sizeof(uint16_t) * 2) + num);
264 le16enc(rtic->strs.buf + rtic->strs.bufpos, ind);
265 rtic->strs.bufpos += sizeof(uint16_t);
266 le16enc(rtic->strs.buf + rtic->strs.bufpos, num);
267 rtic->strs.bufpos += sizeof(uint16_t);
268 memcpy(rtic->strs.buf + rtic->strs.bufpos,
269 cap, num);
270 rtic->strs.bufpos += num;
271 rtic->strs.entries++;
272 }
273 cap += num;
274 }
275
276 cap = utic->extras.buf;
277 for (n = utic->extras.entries; n > 0; n--) {
278 num = le16dec(cap);
279 cap += sizeof(uint16_t);
280 code = cap;
281 cap += num;
282 type = *cap++;
283 flag = 0;
284 str = NULL;
285 switch (type) {
286 case 'f':
287 flag = *cap++;
288 if (!VALID_BOOLEAN(flag))
289 continue;
290 break;
291 case 'n':
292 num = le16dec(cap);
293 cap += sizeof(uint16_t);
294 if (!VALID_NUMERIC(num))
295 continue;
296 break;
297 case 's':
298 num = le16dec(cap);
299 cap += sizeof(uint16_t);
300 str = cap;
301 cap += num;
302 if (num == 0)
303 continue;
304 break;
305 }
306 _ti_store_extra(rtic, 0, code, type, flag, num, str, num,
307 flags);
308 }
309 }
310
311 static size_t
312 merge_use(int flags)
313 {
314 size_t skipped, merged, memn;
315 char *cap, *scap;
316 uint16_t num;
317 TIC *rtic, *utic;
318 TERM *term, *uterm;;
319
320 skipped = merged = 0;
321 STAILQ_FOREACH(term, &terms, next) {
322 if (term->base_term != NULL)
323 continue;
324 rtic = term->tic;
325 while ((cap = _ti_find_extra(&rtic->extras, "use")) != NULL) {
326 if (*cap++ != 's') {
327 dowarn("%s: use is not string", rtic->name);
328 break;
329 }
330 cap += sizeof(uint16_t);
331 if (strcmp(rtic->name, cap) == 0) {
332 dowarn("%s: uses itself", rtic->name);
333 goto remove;
334 }
335 uterm = find_term(cap);
336 if (uterm != NULL && uterm->base_term != NULL)
337 uterm = uterm->base_term;
338 if (uterm == NULL) {
339 dowarn("%s: no use record for %s",
340 rtic->name, cap);
341 goto remove;
342 }
343 utic = uterm->tic;
344 if (strcmp(utic->name, rtic->name) == 0) {
345 dowarn("%s: uses itself", rtic->name);
346 goto remove;
347 }
348 if (_ti_find_extra(&utic->extras, "use") != NULL) {
349 skipped++;
350 break;
351 }
352 cap = _ti_find_extra(&rtic->extras, "use");
353 merge(rtic, utic, flags);
354 remove:
355 /* The pointers may have changed, find the use again */
356 cap = _ti_find_extra(&rtic->extras, "use");
357 if (cap == NULL)
358 dowarn("%s: use no longer exists - impossible",
359 rtic->name);
360 else {
361 scap = cap - (4 + sizeof(uint16_t));
362 cap++;
363 num = le16dec(cap);
364 cap += sizeof(uint16_t) + num;
365 memn = rtic->extras.bufpos -
366 (cap - rtic->extras.buf);
367 memmove(scap, cap, memn);
368 rtic->extras.bufpos -= cap - scap;
369 cap = scap;
370 rtic->extras.entries--;
371 merged++;
372 }
373 }
374 }
375
376 if (merged == 0 && skipped != 0)
377 dowarn("circular use detected");
378 return merged;
379 }
380
381 static int
382 print_dump(int argc, char **argv)
383 {
384 TERM *term;
385 uint8_t *buf;
386 int i, n;
387 size_t j, col;
388 ssize_t len;
389
390 printf("struct compiled_term {\n");
391 printf("\tconst char *name;\n");
392 printf("\tconst char *cap;\n");
393 printf("\tsize_t caplen;\n");
394 printf("};\n\n");
395
396 printf("const struct compiled_term compiled_terms[] = {\n");
397
398 n = 0;
399 for (i = 0; i < argc; i++) {
400 term = find_term(argv[i]);
401 if (term == NULL) {
402 warnx("%s: no description for terminal", argv[i]);
403 continue;
404 }
405 if (term->base_term != NULL) {
406 warnx("%s: cannot dump alias", argv[i]);
407 continue;
408 }
409 /* Don't compile the aliases in, save space */
410 free(term->tic->alias);
411 term->tic->alias = NULL;
412 len = _ti_flatten(&buf, term->tic);
413 if (len == 0 || len == -1)
414 continue;
415
416 printf("\t{\n");
417 printf("\t\t\"%s\",\n", argv[i]);
418 n++;
419 for (j = 0, col = 0; j < (size_t)len; j++) {
420 if (col == 0) {
421 printf("\t\t\"");
422 col = 16;
423 }
424
425 col += printf("\\%03o", (uint8_t)buf[j]);
426 if (col > 75) {
427 printf("\"%s\n",
428 j + 1 == (size_t)len ? "," : "");
429 col = 0;
430 }
431 }
432 if (col != 0)
433 printf("\",\n");
434 printf("\t\t%zu\n", len);
435 printf("\t}");
436 if (i + 1 < argc)
437 printf(",");
438 printf("\n");
439 free(buf);
440 }
441 printf("};\n");
442
443 return n;
444 }
445
446 static void
447 write_database(const char *dbname)
448 {
449 struct cdbw *db;
450 char *tmp_dbname;
451 TERM *term;
452 int fd;
453
454 db = cdbw_open();
455 if (db == NULL)
456 err(1, "cdbw_open failed");
457 /* Save the terms */
458 STAILQ_FOREACH(term, &terms, next)
459 save_term(db, term);
460
461 easprintf(&tmp_dbname, "%s.XXXXXX", dbname);
462 fd = mkstemp(tmp_dbname);
463 if (fd == -1)
464 err(1, "creating temporary database %s failed", tmp_dbname);
465 if (cdbw_output(db, fd, "NetBSD terminfo", cdbw_stable_seeder))
466 err(1, "writing temporary database %s failed", tmp_dbname);
467 if (fchmod(fd, DEFFILEMODE))
468 err(1, "fchmod failed");
469 if (close(fd))
470 err(1, "writing temporary database %s failed", tmp_dbname);
471 if (rename(tmp_dbname, dbname))
472 err(1, "renaming %s to %s failed", tmp_dbname, dbname);
473 free(tmp_dbname);
474 cdbw_close(db);
475 }
476
477 int
478 main(int argc, char **argv)
479 {
480 int ch, cflag, sflag, flags;
481 char *source, *dbname, *buf, *ofile;
482 FILE *f;
483 size_t buflen;
484 ssize_t len;
485 TBUF tbuf;
486 struct term *term;
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 if (ofile == NULL)
583 free(dbname);
584 while ((term = STAILQ_FIRST(&terms)) != NULL) {
585 STAILQ_REMOVE_HEAD(&terms, next);
586 _ti_freetic(term->tic);
587 free(term->name);
588 free(term);
589 }
590 #ifndef HAVE_NBTOOL_CONFIG_H
591 /*
592 * hdestroy1 is not standard but we don't really care if we
593 * leak in the tools version
594 */
595 hdestroy1(free, NULL);
596 #endif
597
598 return EXIT_SUCCESS;
599 }
600