tic.c revision 1.41 1 /* $NetBSD: tic.c,v 1.41 2024/05/11 22:06:57 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.41 2024/05/11 22:06:57 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 <stdbool.h>
55 #include <stdlib.h>
56 #include <stdio.h>
57 #include <string.h>
58 #include <term_private.h>
59 #include <term.h>
60 #include <unistd.h>
61 #include <util.h>
62
63 #define HASH_SIZE 16384 /* 2012-06-01: 3600 entries */
64
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(EXIT_FAILURE, "_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 char *cap;
109 len = (ssize_t)(1 + sizeof(uint32_t) + sizeof(uint16_t) + slen);
110 buf = emalloc(len);
111 cap = (char *)buf;
112 *cap++ = TERMINFO_ALIAS;
113 _ti_encode_32(&cap, term->base_term->id);
114 _ti_encode_count_str(&cap, term->name, slen);
115 if (cdbw_put(db, term->name, slen, buf, len))
116 err(EXIT_FAILURE, "cdbw_put");
117 free(buf);
118 return 0;
119 }
120
121 len = _ti_flatten(&buf, term->tic);
122 if (len == -1)
123 return -1;
124
125 if (cdbw_put_data(db, buf, len, &term->id))
126 err(EXIT_FAILURE, "cdbw_put_data");
127 if (cdbw_put_key(db, term->name, slen, term->id))
128 err(EXIT_FAILURE, "cdbw_put_key");
129 free(buf);
130 return 0;
131 }
132
133 static TERM *
134 find_term(const char *name)
135 {
136 ENTRY elem, *elemp;
137
138 elem.key = __UNCONST(name);
139 elem.data = NULL;
140 elemp = hsearch(elem, FIND);
141 return elemp ? (TERM *)elemp->data : NULL;
142 }
143
144 static TERM *
145 find_newest_term(const char *name)
146 {
147 char *lname;
148 TERM *term;
149
150 lname = _ti_getname(TERMINFO_RTYPE, name);
151 if (lname == NULL)
152 return NULL;
153 term = find_term(lname);
154 free(lname);
155 if (term == NULL)
156 term = find_term(name);
157 return term;
158 }
159
160 static TERM *
161 store_term(const char *name, TERM *base_term)
162 {
163 TERM *term;
164 ENTRY elem;
165
166 term = ecalloc(1, sizeof(*term));
167 term->name = estrdup(name);
168 STAILQ_INSERT_TAIL(&terms, term, next);
169 elem.key = estrdup(name);
170 elem.data = term;
171 hsearch(elem, ENTER);
172
173 term->base_term = base_term;
174 if (base_term != NULL)
175 nalias++;
176 else
177 nterm++;
178
179 return term;
180 }
181
182 static void
183 alias_terms(TERM *term)
184 {
185 char *p, *e, *alias;
186
187 /* Create aliased terms */
188 if (term->tic->alias == NULL)
189 return;
190
191 alias = p = estrdup(term->tic->alias);
192 while (p != NULL && *p != '\0') {
193 e = strchr(p, '|');
194 if (e != NULL)
195 *e++ = '\0';
196 /* No need to lengthcheck the alias because the main
197 * terminfo description already stores all the aliases
198 * in the same length field as the alias. */
199 if (find_term(p) != NULL) {
200 dowarn("%s: has alias for already assigned"
201 " term %s", term->tic->name, p);
202 } else {
203 store_term(p, term);
204 }
205 p = e;
206 }
207 free(alias);
208 }
209
210 static int
211 process_entry(TBUF *buf, int flags)
212 {
213 TERM *term;
214 TIC *tic;
215 TBUF sbuf = *buf;
216
217 if (buf->bufpos == 0)
218 return 0;
219 /* Terminate the string */
220 buf->buf[buf->bufpos - 1] = '\0';
221 /* First rewind the buffer for new entries */
222 buf->bufpos = 0;
223
224 if (isspace((unsigned char)*buf->buf))
225 return 0;
226
227 tic = _ti_compile(buf->buf, flags);
228 if (tic == NULL)
229 return 0;
230
231 if (find_term(tic->name) != NULL) {
232 dowarn("%s: duplicate entry", tic->name);
233 _ti_freetic(tic);
234 return 0;
235 }
236 term = store_term(tic->name, NULL);
237 term->tic = tic;
238 alias_terms(term);
239
240 if (tic->rtype == TERMINFO_RTYPE)
241 return process_entry(&sbuf, flags | TIC_COMPAT_V1);
242
243 return 0;
244 }
245
246 static void
247 merge(TIC *rtic, TIC *utic, int flags)
248 {
249 char flag, type;
250 const char *cap, *code, *str;
251 short ind, len;
252 int num;
253 size_t n;
254
255 if (rtic->rtype < utic->rtype)
256 errx(EXIT_FAILURE, "merge rtype diff (%s:%d into %s:%d)",
257 utic->name, utic->rtype, rtic->name, rtic->rtype);
258
259 cap = utic->flags.buf;
260 for (n = utic->flags.entries; n > 0; n--) {
261 ind = _ti_decode_16(&cap);
262 flag = *cap++;
263 if (VALID_BOOLEAN(flag) &&
264 _ti_find_cap(rtic, &rtic->flags, 'f', ind) == NULL)
265 {
266 if (!_ti_encode_buf_id_flags(&rtic->flags, ind, flag))
267 err(EXIT_FAILURE, "encode flag");
268 }
269 }
270
271 cap = utic->nums.buf;
272 for (n = utic->nums.entries; n > 0; n--) {
273 ind = _ti_decode_16(&cap);
274 num = _ti_decode_num(&cap, utic->rtype);
275 if (VALID_NUMERIC(num) &&
276 _ti_find_cap(rtic, &rtic->nums, 'n', ind) == NULL)
277 {
278 if (!_ti_encode_buf_id_num(&rtic->nums, ind, num,
279 _ti_numsize(rtic)))
280 err(EXIT_FAILURE, "encode num");
281 }
282 }
283
284 cap = utic->strs.buf;
285 for (n = utic->strs.entries; n > 0; n--) {
286 ind = _ti_decode_16(&cap);
287 len = _ti_decode_16(&cap);
288 if (len > 0 &&
289 _ti_find_cap(rtic, &rtic->strs, 's', ind) == NULL)
290 {
291 if (!_ti_encode_buf_id_count_str(&rtic->strs, ind, cap,
292 len))
293 err(EXIT_FAILURE, "encode str");
294 }
295 cap += len;
296 }
297
298 cap = utic->extras.buf;
299 for (n = utic->extras.entries; n > 0; n--) {
300 num = _ti_decode_16(&cap);
301 code = cap;
302 cap += num;
303 type = *cap++;
304 flag = 0;
305 str = NULL;
306 switch (type) {
307 case 'f':
308 flag = *cap++;
309 if (!VALID_BOOLEAN(flag))
310 continue;
311 break;
312 case 'n':
313 num = _ti_decode_num(&cap, utic->rtype);
314 if (!VALID_NUMERIC(num))
315 continue;
316 break;
317 case 's':
318 num = _ti_decode_16(&cap);
319 str = cap;
320 cap += num;
321 if (num == 0)
322 continue;
323 break;
324 }
325 _ti_store_extra(rtic, 0, code, type, flag, num, str, num,
326 flags);
327 }
328 }
329
330 static int
331 dup_tbuf(TBUF *dst, const TBUF *src)
332 {
333
334 if (src->buflen == 0)
335 return 0;
336 dst->buf = malloc(src->buflen);
337 if (dst->buf == NULL)
338 return -1;
339 dst->buflen = src->buflen;
340 memcpy(dst->buf, src->buf, dst->buflen);
341 dst->bufpos = src->bufpos;
342 dst->entries = src->entries;
343 return 0;
344 }
345
346 static int
347 promote(TIC *rtic, TIC *utic)
348 {
349 TERM *nrterm = find_newest_term(rtic->name);
350 TERM *nuterm = find_newest_term(utic->name);
351 TERM *term;
352 TIC *tic;
353
354 if (nrterm == NULL || nuterm == NULL)
355 return -1;
356 if (nrterm->tic->rtype >= nuterm->tic->rtype)
357 return 0;
358
359 tic = calloc(1, sizeof(*tic));
360 if (tic == NULL)
361 return -1;
362
363 tic->name = _ti_getname(TERMINFO_RTYPE, rtic->name);
364 if (tic->name == NULL)
365 goto err;
366 if (rtic->alias != NULL) {
367 tic->alias = strdup(rtic->alias);
368 if (tic->alias == NULL)
369 goto err;
370 }
371 if (rtic->desc != NULL) {
372 tic->desc = strdup(rtic->desc);
373 if (tic->desc == NULL)
374 goto err;
375 }
376
377 tic->rtype = rtic->rtype;
378 if (dup_tbuf(&tic->flags, &rtic->flags) == -1)
379 goto err;
380 if (dup_tbuf(&tic->nums, &rtic->nums) == -1)
381 goto err;
382 if (dup_tbuf(&tic->strs, &rtic->strs) == -1)
383 goto err;
384 if (dup_tbuf(&tic->extras, &rtic->extras) == -1)
385 goto err;
386 if (_ti_promote(tic) == -1)
387 goto err;
388
389 term = store_term(tic->name, NULL);
390 if (term == NULL)
391 goto err;
392
393 term->tic = tic;
394 alias_terms(term);
395 return 0;
396
397 err:
398 free(tic->flags.buf);
399 free(tic->nums.buf);
400 free(tic->strs.buf);
401 free(tic->extras.buf);
402 free(tic->desc);
403 free(tic->alias);
404 free(tic->name);
405 free(tic);
406 return -1;
407 }
408
409 static size_t
410 merge_use(int flags)
411 {
412 size_t skipped, merged, memn;
413 const char *cap;
414 char *name, *basename;
415 uint16_t num;
416 TIC *rtic, *utic;
417 TERM *term, *uterm;
418 bool promoted;
419
420 skipped = merged = 0;
421 STAILQ_FOREACH(term, &terms, next) {
422 if (term->base_term != NULL)
423 continue;
424 rtic = term->tic;
425 basename = _ti_getname(TERMINFO_RTYPE_O1, rtic->name);
426 promoted = false;
427 while ((cap = _ti_find_extra(rtic, &rtic->extras, "use"))
428 != NULL) {
429 if (*cap++ != 's') {
430 dowarn("%s: use is not string", rtic->name);
431 break;
432 }
433 cap += sizeof(uint16_t);
434 if (strcmp(basename, cap) == 0) {
435 dowarn("%s: uses itself", rtic->name);
436 goto remove;
437 }
438 name = _ti_getname(rtic->rtype, cap);
439 if (name == NULL) {
440 dowarn("%s: ???: %s", rtic->name, cap);
441 goto remove;
442 }
443 uterm = find_term(name);
444 free(name);
445 if (uterm == NULL)
446 uterm = find_term(cap);
447 if (uterm != NULL && uterm->base_term != NULL)
448 uterm = uterm->base_term;
449 if (uterm == NULL) {
450 dowarn("%s: no use record for %s",
451 rtic->name, cap);
452 goto remove;
453 }
454 utic = uterm->tic;
455 if (strcmp(utic->name, rtic->name) == 0) {
456 dowarn("%s: uses itself", rtic->name);
457 goto remove;
458 }
459 if (_ti_find_extra(utic, &utic->extras, "use")
460 != NULL) {
461 skipped++;
462 break;
463 }
464
465 /* If we need to merge in a term that requires
466 * this term to be promoted, we need to duplicate
467 * this term, promote it and append it to our list. */
468 if (!promoted && rtic->rtype != TERMINFO_RTYPE) {
469 if (promote(rtic, utic) == -1)
470 err(EXIT_FAILURE, "promote");
471 promoted = rtic->rtype == TERMINFO_RTYPE;
472 }
473
474 merge(rtic, utic, flags);
475 remove:
476 /* The pointers may have changed, find the use again */
477 cap = _ti_find_extra(rtic, &rtic->extras, "use");
478 if (cap == NULL)
479 dowarn("%s: use no longer exists - impossible",
480 rtic->name);
481 else {
482 char *scap = __UNCONST(
483 cap - (4 + sizeof(uint16_t)));
484 cap++;
485 num = _ti_decode_16(&cap);
486 cap += num;
487 memn = rtic->extras.bufpos -
488 (cap - rtic->extras.buf);
489 memmove(scap, cap, memn);
490 rtic->extras.bufpos -= cap - scap;
491 cap = scap;
492 rtic->extras.entries--;
493 merged++;
494 }
495 }
496 free(basename);
497 }
498
499 if (merged == 0 && skipped != 0)
500 dowarn("circular use detected");
501 return merged;
502 }
503
504 static int
505 print_dump(int argc, char **argv)
506 {
507 TERM *term;
508 uint8_t *buf;
509 int i, n;
510 size_t j, col;
511 ssize_t len;
512
513 printf("struct compiled_term {\n");
514 printf("\tconst char *name;\n");
515 printf("\tconst char *cap;\n");
516 printf("\tsize_t caplen;\n");
517 printf("};\n\n");
518
519 printf("const struct compiled_term compiled_terms[] = {\n");
520
521 n = 0;
522 for (i = 0; i < argc; i++) {
523 term = find_newest_term(argv[i]);
524 if (term == NULL) {
525 warnx("%s: no description for terminal", argv[i]);
526 continue;
527 }
528 if (term->base_term != NULL) {
529 warnx("%s: cannot dump alias", argv[i]);
530 continue;
531 }
532 /* Don't compile the aliases in, save space */
533 free(term->tic->alias);
534 term->tic->alias = NULL;
535 len = _ti_flatten(&buf, term->tic);
536 if (len == 0 || len == -1)
537 continue;
538
539 printf("\t{\n");
540 printf("\t\t\"%s\",\n", argv[i]);
541 n++;
542 for (j = 0, col = 0; j < (size_t)len; j++) {
543 if (col == 0) {
544 printf("\t\t\"");
545 col = 16;
546 }
547
548 col += printf("\\%03o", (uint8_t)buf[j]);
549 if (col > 75) {
550 printf("\"%s\n",
551 j + 1 == (size_t)len ? "," : "");
552 col = 0;
553 }
554 }
555 if (col != 0)
556 printf("\",\n");
557 printf("\t\t%zu\n", len);
558 printf("\t}");
559 if (i + 1 < argc)
560 printf(",");
561 printf("\n");
562 free(buf);
563 }
564 printf("};\n");
565
566 return n;
567 }
568
569 static void
570 write_database(const char *dbname)
571 {
572 struct cdbw *db;
573 char *tmp_dbname;
574 TERM *term;
575 int fd;
576
577 db = cdbw_open();
578 if (db == NULL)
579 err(EXIT_FAILURE, "cdbw_open failed");
580 /* Save the terms */
581 STAILQ_FOREACH(term, &terms, next)
582 save_term(db, term);
583
584 easprintf(&tmp_dbname, "%s.XXXXXX", dbname);
585 fd = mkstemp(tmp_dbname);
586 if (fd == -1)
587 err(EXIT_FAILURE,
588 "creating temporary database %s failed", tmp_dbname);
589 if (cdbw_output(db, fd, "NetBSD terminfo", cdbw_stable_seeder))
590 err(EXIT_FAILURE,
591 "writing temporary database %s failed", tmp_dbname);
592 if (fchmod(fd, DEFFILEMODE))
593 err(EXIT_FAILURE, "fchmod failed");
594 if (close(fd))
595 err(EXIT_FAILURE,
596 "writing temporary database %s failed", tmp_dbname);
597 if (rename(tmp_dbname, dbname))
598 err(EXIT_FAILURE, "renaming %s to %s failed", tmp_dbname, dbname);
599 free(tmp_dbname);
600 cdbw_close(db);
601 }
602
603 int
604 main(int argc, char **argv)
605 {
606 int ch, cflag, sflag, flags;
607 char *source, *dbname, *buf, *ofile;
608 FILE *f;
609 size_t buflen;
610 ssize_t len;
611 TBUF tbuf;
612 struct term *term;
613
614 cflag = sflag = 0;
615 ofile = NULL;
616 flags = TIC_ALIAS | TIC_DESCRIPTION | TIC_WARNING;
617 while ((ch = getopt(argc, argv, "Saco:sx")) != -1)
618 switch (ch) {
619 case 'S':
620 Sflag = 1;
621 /* We still compile aliases so that use= works.
622 * However, it's removed before we flatten to save space. */
623 flags &= ~TIC_DESCRIPTION;
624 break;
625 case 'a':
626 flags |= TIC_COMMENT;
627 break;
628 case 'c':
629 cflag = 1;
630 break;
631 case 'o':
632 ofile = optarg;
633 break;
634 case 's':
635 sflag = 1;
636 break;
637 case 'x':
638 flags |= TIC_EXTRA;
639 break;
640 case '?': /* FALLTHROUGH */
641 default:
642 fprintf(stderr, "usage: %s [-acSsx] [-o file] source\n",
643 getprogname());
644 return EXIT_FAILURE;
645 }
646
647 if (optind == argc)
648 errx(1, "No source file given");
649 source = argv[optind++];
650 f = fopen(source, "r");
651 if (f == NULL)
652 err(EXIT_FAILURE, "fopen: %s", source);
653
654 hcreate(HASH_SIZE);
655
656 buf = tbuf.buf = NULL;
657 buflen = tbuf.buflen = tbuf.bufpos = 0;
658 while ((len = getline(&buf, &buflen, f)) != -1) {
659 /* Skip comments */
660 if (*buf == '#')
661 continue;
662 if (buf[len - 1] != '\n') {
663 process_entry(&tbuf, flags);
664 dowarn("last line is not a comment"
665 " and does not end with a newline");
666 continue;
667 }
668 /*
669 * If the first char is space not a space then we have a
670 * new entry, so process it.
671 */
672 if (!isspace((unsigned char)*buf) && tbuf.bufpos != 0)
673 process_entry(&tbuf, flags);
674
675 /* Grow the buffer if needed */
676 grow_tbuf(&tbuf, len);
677 /* Append the string */
678 memcpy(tbuf.buf + tbuf.bufpos, buf, len);
679 tbuf.bufpos += len;
680 }
681 free(buf);
682 /* Process the last entry if not done already */
683 process_entry(&tbuf, flags);
684 free(tbuf.buf);
685
686 /* Merge use entries until we have merged all we can */
687 while (merge_use(flags) != 0)
688 ;
689
690 if (Sflag) {
691 print_dump(argc - optind, argv + optind);
692 return error_exit;
693 }
694
695 if (cflag)
696 return error_exit;
697
698 if (ofile == NULL)
699 easprintf(&dbname, "%s.cdb", source);
700 else
701 dbname = ofile;
702 write_database(dbname);
703
704 if (sflag != 0)
705 fprintf(stderr, "%zu entries and %zu aliases written to %s\n",
706 nterm, nalias, dbname);
707
708 if (ofile == NULL)
709 free(dbname);
710 while ((term = STAILQ_FIRST(&terms)) != NULL) {
711 STAILQ_REMOVE_HEAD(&terms, next);
712 _ti_freetic(term->tic);
713 free(term->name);
714 free(term);
715 }
716 #ifndef HAVE_NBTOOL_CONFIG_H
717 /*
718 * hdestroy1 is not standard but we don't really care if we
719 * leak in the tools version
720 */
721 hdestroy1(free, NULL);
722 #endif
723
724 return EXIT_SUCCESS;
725 }
726