Sync with manuals from netbsd-8 branch.
[minix3.git] / usr.bin / tic / tic.c
bloba6b72db56be9bcc22ae7f8f00a44e7885cc4c799
1 /* $NetBSD: tic.c,v 1.24 2014/07/20 20:20:16 christos Exp $ */
3 /*
4 * Copyright (c) 2009, 2010 The NetBSD Foundation, Inc.
6 * This code is derived from software contributed to The NetBSD Foundation
7 * by Roy Marples.
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.
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.
30 #if HAVE_NBTOOL_CONFIG_H
31 #include "nbtool_config.h"
32 #endif
34 #include <sys/cdefs.h>
35 __RCSID("$NetBSD: tic.c,v 1.24 2014/07/20 20:20:16 christos Exp $");
37 #include <sys/types.h>
38 #include <sys/queue.h>
40 #if !HAVE_NBTOOL_CONFIG_H || HAVE_SYS_ENDIAN_H
41 #include <sys/endian.h>
42 #endif
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>
60 #define HASH_SIZE 16384 /* 2012-06-01: 3600 entries */
62 typedef struct term {
63 STAILQ_ENTRY(term) next;
64 char *name;
65 TIC *tic;
66 uint32_t id;
67 struct term *base_term;
68 } TERM;
69 static STAILQ_HEAD(, term) terms = STAILQ_HEAD_INITIALIZER(terms);
71 static int error_exit;
72 static int Sflag;
73 static size_t nterm, nalias;
75 static void __printflike(1, 2)
76 dowarn(const char *fmt, ...)
78 va_list va;
80 error_exit = 1;
81 va_start(va, fmt);
82 vwarnx(fmt, va);
83 va_end(va);
86 static char *
87 grow_tbuf(TBUF *tbuf, size_t len)
89 char *buf;
91 buf = _ti_grow_tbuf(tbuf, len);
92 if (buf == NULL)
93 err(1, "_ti_grow_tbuf");
94 return buf;
97 static int
98 save_term(struct cdbw *db, TERM *term)
100 uint8_t *buf;
101 ssize_t len;
102 size_t slen = strlen(term->name) + 1;
104 if (term->base_term != NULL) {
105 len = (ssize_t)slen + 7;
106 buf = emalloc(len);
107 buf[0] = 2;
108 le32enc(buf + 1, term->base_term->id);
109 le16enc(buf + 5, slen);
110 memcpy(buf + 7, term->name, slen);
111 if (cdbw_put(db, term->name, slen, buf, len))
112 err(1, "cdbw_put");
113 return 0;
116 len = _ti_flatten(&buf, term->tic);
117 if (len == -1)
118 return -1;
120 if (cdbw_put_data(db, buf, len, &term->id))
121 err(1, "cdbw_put_data");
122 if (cdbw_put_key(db, term->name, slen, term->id))
123 err(1, "cdbw_put_key");
124 free(buf);
125 return 0;
128 static TERM *
129 find_term(const char *name)
131 ENTRY elem, *elemp;
133 elem.key = __UNCONST(name);
134 elem.data = NULL;
135 elemp = hsearch(elem, FIND);
136 return elemp ? (TERM *)elemp->data : NULL;
139 static TERM *
140 store_term(const char *name, TERM *base_term)
142 TERM *term;
143 ENTRY elem;
145 term = ecalloc(1, sizeof(*term));
146 term->name = estrdup(name);
147 STAILQ_INSERT_TAIL(&terms, term, next);
148 elem.key = estrdup(name);
149 elem.data = term;
150 hsearch(elem, ENTER);
152 term->base_term = base_term;
153 if (base_term != NULL)
154 nalias++;
155 else
156 nterm++;
158 return term;
161 static int
162 process_entry(TBUF *buf, int flags)
164 char *p, *e, *alias;
165 TERM *term;
166 TIC *tic;
168 if (buf->bufpos == 0)
169 return 0;
170 /* Terminate the string */
171 buf->buf[buf->bufpos - 1] = '\0';
172 /* First rewind the buffer for new entries */
173 buf->bufpos = 0;
175 if (isspace((unsigned char)*buf->buf))
176 return 0;
178 tic = _ti_compile(buf->buf, flags);
179 if (tic == NULL)
180 return 0;
182 if (find_term(tic->name) != NULL) {
183 dowarn("%s: duplicate entry", tic->name);
184 _ti_freetic(tic);
185 return 0;
187 term = store_term(tic->name, NULL);
188 term->tic = tic;
190 /* Create aliased terms */
191 if (tic->alias != NULL) {
192 alias = p = estrdup(tic->alias);
193 while (p != NULL && *p != '\0') {
194 e = strchr(p, '|');
195 if (e != NULL)
196 *e++ = '\0';
197 if (find_term(p) != NULL) {
198 dowarn("%s: has alias for already assigned"
199 " term %s", tic->name, p);
200 } else {
201 store_term(p, term);
203 p = e;
205 free(alias);
208 return 0;
211 static void
212 merge(TIC *rtic, TIC *utic, int flags)
214 char *cap, flag, *code, type, *str;
215 short ind, num;
216 size_t n;
218 cap = utic->flags.buf;
219 for (n = utic->flags.entries; n > 0; n--) {
220 ind = le16dec(cap);
221 cap += sizeof(uint16_t);
222 flag = *cap++;
223 if (VALID_BOOLEAN(flag) &&
224 _ti_find_cap(&rtic->flags, 'f', ind) == NULL)
226 _ti_grow_tbuf(&rtic->flags, sizeof(uint16_t) + 1);
227 le16enc(rtic->flags.buf + rtic->flags.bufpos, ind);
228 rtic->flags.bufpos += sizeof(uint16_t);
229 rtic->flags.buf[rtic->flags.bufpos++] = flag;
230 rtic->flags.entries++;
234 cap = utic->nums.buf;
235 for (n = utic->nums.entries; n > 0; n--) {
236 ind = le16dec(cap);
237 cap += sizeof(uint16_t);
238 num = le16dec(cap);
239 cap += sizeof(uint16_t);
240 if (VALID_NUMERIC(num) &&
241 _ti_find_cap(&rtic->nums, 'n', ind) == NULL)
243 grow_tbuf(&rtic->nums, sizeof(uint16_t) * 2);
244 le16enc(rtic->nums.buf + rtic->nums.bufpos, ind);
245 rtic->nums.bufpos += sizeof(uint16_t);
246 le16enc(rtic->nums.buf + rtic->nums.bufpos, num);
247 rtic->nums.bufpos += sizeof(uint16_t);
248 rtic->nums.entries++;
252 cap = utic->strs.buf;
253 for (n = utic->strs.entries; n > 0; n--) {
254 ind = le16dec(cap);
255 cap += sizeof(uint16_t);
256 num = le16dec(cap);
257 cap += sizeof(uint16_t);
258 if (num > 0 &&
259 _ti_find_cap(&rtic->strs, 's', ind) == NULL)
261 grow_tbuf(&rtic->strs, (sizeof(uint16_t) * 2) + num);
262 le16enc(rtic->strs.buf + rtic->strs.bufpos, ind);
263 rtic->strs.bufpos += sizeof(uint16_t);
264 le16enc(rtic->strs.buf + rtic->strs.bufpos, num);
265 rtic->strs.bufpos += sizeof(uint16_t);
266 memcpy(rtic->strs.buf + rtic->strs.bufpos,
267 cap, num);
268 rtic->strs.bufpos += num;
269 rtic->strs.entries++;
271 cap += num;
274 cap = utic->extras.buf;
275 for (n = utic->extras.entries; n > 0; n--) {
276 num = le16dec(cap);
277 cap += sizeof(uint16_t);
278 code = cap;
279 cap += num;
280 type = *cap++;
281 flag = 0;
282 str = NULL;
283 switch (type) {
284 case 'f':
285 flag = *cap++;
286 if (!VALID_BOOLEAN(flag))
287 continue;
288 break;
289 case 'n':
290 num = le16dec(cap);
291 cap += sizeof(uint16_t);
292 if (!VALID_NUMERIC(num))
293 continue;
294 break;
295 case 's':
296 num = le16dec(cap);
297 cap += sizeof(uint16_t);
298 str = cap;
299 cap += num;
300 if (num == 0)
301 continue;
302 break;
304 _ti_store_extra(rtic, 0, code, type, flag, num, str, num,
305 flags);
309 static size_t
310 merge_use(int flags)
312 size_t skipped, merged, memn;
313 char *cap, *scap;
314 uint16_t num;
315 TIC *rtic, *utic;
316 TERM *term, *uterm;;
318 skipped = merged = 0;
319 STAILQ_FOREACH(term, &terms, next) {
320 if (term->base_term != NULL)
321 continue;
322 rtic = term->tic;
323 while ((cap = _ti_find_extra(&rtic->extras, "use")) != NULL) {
324 if (*cap++ != 's') {
325 dowarn("%s: use is not string", rtic->name);
326 break;
328 cap += sizeof(uint16_t);
329 if (strcmp(rtic->name, cap) == 0) {
330 dowarn("%s: uses itself", rtic->name);
331 goto remove;
333 uterm = find_term(cap);
334 if (uterm != NULL && uterm->base_term != NULL)
335 uterm = uterm->base_term;
336 if (uterm == NULL) {
337 dowarn("%s: no use record for %s",
338 rtic->name, cap);
339 goto remove;
341 utic = uterm->tic;
342 if (strcmp(utic->name, rtic->name) == 0) {
343 dowarn("%s: uses itself", rtic->name);
344 goto remove;
346 if (_ti_find_extra(&utic->extras, "use") != NULL) {
347 skipped++;
348 break;
350 cap = _ti_find_extra(&rtic->extras, "use");
351 merge(rtic, utic, flags);
352 remove:
353 /* The pointers may have changed, find the use again */
354 cap = _ti_find_extra(&rtic->extras, "use");
355 if (cap == NULL)
356 dowarn("%s: use no longer exists - impossible",
357 rtic->name);
358 else {
359 scap = cap - (4 + sizeof(uint16_t));
360 cap++;
361 num = le16dec(cap);
362 cap += sizeof(uint16_t) + num;
363 memn = rtic->extras.bufpos -
364 (cap - rtic->extras.buf);
365 memmove(scap, cap, memn);
366 rtic->extras.bufpos -= cap - scap;
367 cap = scap;
368 rtic->extras.entries--;
369 merged++;
374 if (merged == 0 && skipped != 0)
375 dowarn("circular use detected");
376 return merged;
379 static int
380 print_dump(int argc, char **argv)
382 TERM *term;
383 uint8_t *buf;
384 int i, n;
385 size_t j, col;
386 ssize_t len;
388 printf("struct compiled_term {\n");
389 printf("\tconst char *name;\n");
390 printf("\tconst char *cap;\n");
391 printf("\tsize_t caplen;\n");
392 printf("};\n\n");
394 printf("const struct compiled_term compiled_terms[] = {\n");
396 n = 0;
397 for (i = 0; i < argc; i++) {
398 term = find_term(argv[i]);
399 if (term == NULL) {
400 warnx("%s: no description for terminal", argv[i]);
401 continue;
403 if (term->base_term != NULL) {
404 warnx("%s: cannot dump alias", argv[i]);
405 continue;
407 /* Don't compile the aliases in, save space */
408 free(term->tic->alias);
409 term->tic->alias = NULL;
410 len = _ti_flatten(&buf, term->tic);
411 if (len == 0 || len == -1)
412 continue;
414 printf("\t{\n");
415 printf("\t\t\"%s\",\n", argv[i]);
416 n++;
417 for (j = 0, col = 0; j < (size_t)len; j++) {
418 if (col == 0) {
419 printf("\t\t\"");
420 col = 16;
423 col += printf("\\%03o", (uint8_t)buf[j]);
424 if (col > 75) {
425 printf("\"%s\n",
426 j + 1 == (size_t)len ? "," : "");
427 col = 0;
430 if (col != 0)
431 printf("\",\n");
432 #if defined(__minix)
433 printf("\t\t%zu\n", (size_t) len);
434 #else
435 printf("\t\t%zu\n", len);
436 #endif /* defined(__minix) */
437 printf("\t}");
438 if (i + 1 < argc)
439 printf(",");
440 printf("\n");
441 free(buf);
443 printf("};\n");
445 return n;
448 static void
449 write_database(const char *dbname)
451 struct cdbw *db;
452 char *tmp_dbname;
453 TERM *term;
454 int fd;
456 db = cdbw_open();
457 if (db == NULL)
458 err(1, "cdbw_open failed");
459 /* Save the terms */
460 STAILQ_FOREACH(term, &terms, next)
461 save_term(db, term);
463 easprintf(&tmp_dbname, "%s.XXXXXX", dbname);
464 fd = mkstemp(tmp_dbname);
465 if (fd == -1)
466 err(1, "creating temporary database %s failed", tmp_dbname);
467 if (cdbw_output(db, fd, "NetBSD terminfo", cdbw_stable_seeder))
468 err(1, "writing temporary database %s failed", tmp_dbname);
469 if (fchmod(fd, DEFFILEMODE))
470 err(1, "fchmod failed");
471 if (close(fd))
472 err(1, "writing temporary database %s failed", tmp_dbname);
473 if (rename(tmp_dbname, dbname))
474 err(1, "renaming %s to %s failed", tmp_dbname, dbname);
475 free(tmp_dbname);
476 cdbw_close(db);
480 main(int argc, char **argv)
482 int ch, cflag, sflag, flags;
483 char *source, *dbname, *buf, *ofile;
484 FILE *f;
485 size_t buflen;
486 ssize_t len;
487 TBUF tbuf;
489 cflag = sflag = 0;
490 ofile = NULL;
491 flags = TIC_ALIAS | TIC_DESCRIPTION | TIC_WARNING;
492 while ((ch = getopt(argc, argv, "Saco:sx")) != -1)
493 switch (ch) {
494 case 'S':
495 Sflag = 1;
496 /* We still compile aliases so that use= works.
497 * However, it's removed before we flatten to save space. */
498 flags &= ~TIC_DESCRIPTION;
499 break;
500 case 'a':
501 flags |= TIC_COMMENT;
502 break;
503 case 'c':
504 cflag = 1;
505 break;
506 case 'o':
507 ofile = optarg;
508 break;
509 case 's':
510 sflag = 1;
511 break;
512 case 'x':
513 flags |= TIC_EXTRA;
514 break;
515 case '?': /* FALLTHROUGH */
516 default:
517 fprintf(stderr, "usage: %s [-acSsx] [-o file] source\n",
518 getprogname());
519 return EXIT_FAILURE;
522 if (optind == argc)
523 errx(1, "No source file given");
524 source = argv[optind++];
525 f = fopen(source, "r");
526 if (f == NULL)
527 err(1, "fopen: %s", source);
529 hcreate(HASH_SIZE);
531 buf = tbuf.buf = NULL;
532 buflen = tbuf.buflen = tbuf.bufpos = 0;
533 while ((len = getline(&buf, &buflen, f)) != -1) {
534 /* Skip comments */
535 if (*buf == '#')
536 continue;
537 if (buf[len - 1] != '\n') {
538 process_entry(&tbuf, flags);
539 dowarn("last line is not a comment"
540 " and does not end with a newline");
541 continue;
544 If the first char is space not a space then we have a
545 new entry, so process it.
547 if (!isspace((unsigned char)*buf) && tbuf.bufpos != 0)
548 process_entry(&tbuf, flags);
550 /* Grow the buffer if needed */
551 grow_tbuf(&tbuf, len);
552 /* Append the string */
553 memcpy(tbuf.buf + tbuf.bufpos, buf, len);
554 tbuf.bufpos += len;
556 free(buf);
557 /* Process the last entry if not done already */
558 process_entry(&tbuf, flags);
559 free(tbuf.buf);
561 /* Merge use entries until we have merged all we can */
562 while (merge_use(flags) != 0)
565 if (Sflag) {
566 print_dump(argc - optind, argv + optind);
567 return error_exit;
570 if (cflag)
571 return error_exit;
573 if (ofile == NULL)
574 easprintf(&dbname, "%s.cdb", source);
575 else
576 dbname = ofile;
577 write_database(dbname);
579 if (sflag != 0)
580 fprintf(stderr, "%zu entries and %zu aliases written to %s\n",
581 nterm, nalias, dbname);
583 #ifdef __VALGRIND__
584 if (ofile == NULL)
585 free(dbname);
586 while ((term = STAILQ_FIRST(&terms)) != NULL) {
587 STAILQ_REMOVE_HEAD(&terms, next);
588 _ti_freetic(term->tic);
589 free(term->name);
590 free(term);
592 hdestroy1(free, NULL);
593 #endif
596 return EXIT_SUCCESS;