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