1 /* $NetBSD: tic.c,v 1.42 2024/05/20 14:41:37 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.42 2024/05/20 14:41:37 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)
dowarn(const char * fmt,...)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 *
grow_tbuf(TBUF * tbuf,size_t len)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
save_term(struct cdbw * db,TERM * term)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 *
find_term(const char * name)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 *
find_newest_term(const char * name)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 *
store_term(const char * name,TERM * base_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
alias_terms(TERM * term)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
process_entry(TBUF * buf,int flags)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
merge(TIC * rtic,TIC * utic,int flags)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
dup_tbuf(TBUF * dst,const TBUF * src)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
promote(TIC * rtic,TIC * utic)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
merge_use(int flags)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
print_dump(int argc,char ** argv)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
write_database(const char * dbname)570 write_database(const char *dbname)
571 {
572 struct cdbw *db;
573 char *tmp_dbname;
574 TERM *term;
575 int fd;
576 mode_t m;
577
578 db = cdbw_open();
579 if (db == NULL)
580 err(EXIT_FAILURE, "cdbw_open failed");
581 /* Save the terms */
582 STAILQ_FOREACH(term, &terms, next)
583 save_term(db, term);
584
585 easprintf(&tmp_dbname, "%s.XXXXXX", dbname);
586 fd = mkstemp(tmp_dbname);
587 if (fd == -1)
588 err(EXIT_FAILURE,
589 "creating temporary database %s failed", tmp_dbname);
590 if (cdbw_output(db, fd, "NetBSD terminfo", cdbw_stable_seeder))
591 err(EXIT_FAILURE,
592 "writing temporary database %s failed", tmp_dbname);
593 m = umask(0);
594 (void)umask(m);
595 if (fchmod(fd, DEFFILEMODE & ~m))
596 err(EXIT_FAILURE, "fchmod failed");
597 if (close(fd))
598 err(EXIT_FAILURE,
599 "writing temporary database %s failed", tmp_dbname);
600 if (rename(tmp_dbname, dbname))
601 err(EXIT_FAILURE, "renaming %s to %s failed", tmp_dbname, dbname);
602 free(tmp_dbname);
603 cdbw_close(db);
604 }
605
606 int
main(int argc,char ** argv)607 main(int argc, char **argv)
608 {
609 int ch, cflag, sflag, flags;
610 char *source, *dbname, *buf, *ofile;
611 FILE *f;
612 size_t buflen;
613 ssize_t len;
614 TBUF tbuf;
615 struct term *term;
616
617 cflag = sflag = 0;
618 ofile = NULL;
619 flags = TIC_ALIAS | TIC_DESCRIPTION | TIC_WARNING;
620 while ((ch = getopt(argc, argv, "Saco:sx")) != -1)
621 switch (ch) {
622 case 'S':
623 Sflag = 1;
624 /* We still compile aliases so that use= works.
625 * However, it's removed before we flatten to save space. */
626 flags &= ~TIC_DESCRIPTION;
627 break;
628 case 'a':
629 flags |= TIC_COMMENT;
630 break;
631 case 'c':
632 cflag = 1;
633 break;
634 case 'o':
635 ofile = optarg;
636 break;
637 case 's':
638 sflag = 1;
639 break;
640 case 'x':
641 flags |= TIC_EXTRA;
642 break;
643 case '?': /* FALLTHROUGH */
644 default:
645 fprintf(stderr, "usage: %s [-acSsx] [-o file] source\n",
646 getprogname());
647 return EXIT_FAILURE;
648 }
649
650 if (optind == argc)
651 errx(1, "No source file given");
652 source = argv[optind++];
653 f = fopen(source, "r");
654 if (f == NULL)
655 err(EXIT_FAILURE, "fopen: %s", source);
656
657 hcreate(HASH_SIZE);
658
659 buf = tbuf.buf = NULL;
660 buflen = tbuf.buflen = tbuf.bufpos = 0;
661 while ((len = getline(&buf, &buflen, f)) != -1) {
662 /* Skip comments */
663 if (*buf == '#')
664 continue;
665 if (buf[len - 1] != '\n') {
666 process_entry(&tbuf, flags);
667 dowarn("last line is not a comment"
668 " and does not end with a newline");
669 continue;
670 }
671 /*
672 * If the first char is space not a space then we have a
673 * new entry, so process it.
674 */
675 if (!isspace((unsigned char)*buf) && tbuf.bufpos != 0)
676 process_entry(&tbuf, flags);
677
678 /* Grow the buffer if needed */
679 grow_tbuf(&tbuf, len);
680 /* Append the string */
681 memcpy(tbuf.buf + tbuf.bufpos, buf, len);
682 tbuf.bufpos += len;
683 }
684 free(buf);
685 /* Process the last entry if not done already */
686 process_entry(&tbuf, flags);
687 free(tbuf.buf);
688
689 /* Merge use entries until we have merged all we can */
690 while (merge_use(flags) != 0)
691 ;
692
693 if (Sflag) {
694 print_dump(argc - optind, argv + optind);
695 return error_exit;
696 }
697
698 if (cflag)
699 return error_exit;
700
701 if (ofile == NULL)
702 easprintf(&dbname, "%s.cdb", source);
703 else
704 dbname = ofile;
705 write_database(dbname);
706
707 if (sflag != 0)
708 fprintf(stderr, "%zu entries and %zu aliases written to %s\n",
709 nterm, nalias, dbname);
710
711 if (ofile == NULL)
712 free(dbname);
713 while ((term = STAILQ_FIRST(&terms)) != NULL) {
714 STAILQ_REMOVE_HEAD(&terms, next);
715 _ti_freetic(term->tic);
716 free(term->name);
717 free(term);
718 }
719 #ifndef HAVE_NBTOOL_CONFIG_H
720 /*
721 * hdestroy1 is not standard but we don't really care if we
722 * leak in the tools version
723 */
724 hdestroy1(free, NULL);
725 #endif
726
727 return EXIT_SUCCESS;
728 }
729