1 /****************************************************************************
2 * Copyright 2020-2024,2025 Thomas E. Dickey *
3 * Copyright 1998-2016,2017 Free Software Foundation, Inc. *
4 * *
5 * Permission is hereby granted, free of charge, to any person obtaining a *
6 * copy of this software and associated documentation files (the *
7 * "Software"), to deal in the Software without restriction, including *
8 * without limitation the rights to use, copy, modify, merge, publish, *
9 * distribute, distribute with modifications, sublicense, and/or sell *
10 * copies of the Software, and to permit persons to whom the Software is *
11 * furnished to do so, subject to the following conditions: *
12 * *
13 * The above copyright notice and this permission notice shall be included *
14 * in all copies or substantial portions of the Software. *
15 * *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS *
17 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF *
18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. *
19 * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, *
20 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR *
21 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR *
22 * THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
23 * *
24 * Except as contained in this notice, the name(s) of the above copyright *
25 * holders shall not be used in advertising or otherwise to promote the *
26 * sale, use or other dealings in this Software without prior written *
27 * authorization. *
28 ****************************************************************************/
29
30 /****************************************************************************
31 * Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995 *
32 * and: Eric S. Raymond <esr@snark.thyrsus.com> *
33 * and: Thomas E. Dickey 1996-on *
34 ****************************************************************************/
35
36 /*
37 * infocmp.c -- decompile an entry, or compare two entries
38 * written by Eric S. Raymond
39 * and Thomas E Dickey
40 */
41
42 #include <progs.priv.h>
43
44 #include <dump_entry.h>
45
46 MODULE_ID("$Id: infocmp.c,v 1.177 2025/12/27 22:18:10 tom Exp $")
47
48 #ifndef ACTUAL_TIC
49 #define ACTUAL_TIC "tic"
50 #endif
51
52 #define MAX_STRING 1024 /* maximum formatted string */
53
54 const char *_nc_progname = "infocmp";
55
56 typedef char path[PATH_MAX];
57
58 /***************************************************************************
59 *
60 * The following control variables, together with the contents of the
61 * terminfo entries, completely determine the actions of the program.
62 *
63 ***************************************************************************/
64
65 static ENTRY *entries; /* terminfo entries */
66 static int termcount; /* count of terminal entries */
67
68 static const char usage_string[] = \
69 "Usage: %s [options] [-A directory] [-B directory] [termname...]";
70
71 static bool limited = TRUE; /* "-r" option is not set */
72 static bool quiet = FALSE;
73 static bool literal = FALSE;
74 static const char *bool_sep = ":";
75 static const char *s_absent = "NULL";
76 static const char *s_cancel = "NULL";
77 static const char *tversion; /* terminfo version selected */
78 static unsigned itrace; /* trace flag for debugging */
79 static int mwidth = 60;
80 static int mheight = 65535;
81 static int numbers = 0; /* format "%'char'" to/from "%{number}" */
82 static int outform = F_TERMINFO; /* output format */
83 static int sortmode; /* sort_mode */
84
85 /* main comparison mode */
86 static int compare;
87 #define C_DEFAULT 0 /* don't force comparison mode */
88 #define C_DIFFERENCE 1 /* list differences between two terminals */
89 #define C_COMMON 2 /* list common capabilities */
90 #define C_NAND 3 /* list capabilities in neither terminal */
91 #define C_USEALL 4 /* generate relative use-form entry */
92 static bool ignorepads; /* ignore pad prefixes when diffing */
93
94 #if NO_LEAKS
95
96 typedef struct {
97 ENTRY *head;
98 ENTRY *tail;
99 } ENTERED;
100
101 static ENTERED *entered;
102
103 #undef ExitProgram
104 static GCC_NORETURN void ExitProgram(int code);
105 /* prototype is to get gcc to accept the noreturn attribute */
106 static void
ExitProgram(int code)107 ExitProgram(int code)
108 {
109 int n;
110
111 for (n = 0; n < termcount; ++n) {
112 ENTRY *new_head = _nc_head;
113 ENTRY *new_tail = _nc_tail;
114 _nc_head = entered[n].head;
115 _nc_tail = entered[n].tail;
116 _nc_free_entries(entered[n].head);
117 _nc_head = new_head;
118 _nc_tail = new_tail;
119 }
120 _nc_leaks_dump_entry();
121 free(entries);
122 free(entered);
123 _nc_free_tic(code);
124 }
125 #endif
126
127 static void
failed(const char * s)128 failed(const char *s)
129 {
130 perror(s);
131 ExitProgram(EXIT_FAILURE);
132 }
133
134 static void
canonical_name(const char * source,char * target)135 canonical_name(const char *source, char *target)
136 /* extract the terminal type's primary name */
137 {
138 int limit = NAMESIZE;
139
140 while (--limit > 0) {
141 char ch = *source++;
142 if (ch == '|')
143 break;
144 *target++ = ch;
145 }
146 *target = '\0';
147 }
148
149 static bool
no_boolean(int value)150 no_boolean(int value)
151 {
152 bool result = (value == ABSENT_BOOLEAN);
153 if (!strcmp(s_absent, s_cancel))
154 result = !VALID_BOOLEAN(value);
155 return result;
156 }
157
158 static bool
no_numeric(int value)159 no_numeric(int value)
160 {
161 bool result = (value == ABSENT_NUMERIC);
162 if (!strcmp(s_absent, s_cancel))
163 result = !VALID_NUMERIC(value);
164 return result;
165 }
166
167 static bool
no_string(const char * const value)168 no_string(const char *const value)
169 {
170 bool result = (value == ABSENT_STRING);
171 if (!strcmp(s_absent, s_cancel))
172 result = !VALID_STRING(value);
173 return result;
174 }
175
176 /***************************************************************************
177 *
178 * Predicates for dump function
179 *
180 ***************************************************************************/
181
182 static int
capcmp(PredIdx idx,const char * s,const char * t)183 capcmp(PredIdx idx, const char *s, const char *t)
184 /* capability comparison function */
185 {
186 if (!VALID_STRING(s) && !VALID_STRING(t))
187 return (s != t);
188 else if (!VALID_STRING(s) || !VALID_STRING(t))
189 return (1);
190
191 if ((idx == acs_chars_index) || !ignorepads)
192 return (strcmp(s, t));
193 else
194 return (_nc_capcmp(s, t));
195 }
196
197 /*
198 * Predicate function to use for "use=" decompilation.
199 *
200 * Return value is used in fmt_entry:
201 * FAIL show nothing for this capability.
202 * FALSE show cancel for booleans (a compromise)
203 * TRUE show capability
204 *
205 * The only difference between FALSE/TRUE returns is in the treatment of
206 * booleans.
207 */
208 static int
use_predicate(unsigned type,PredIdx idx)209 use_predicate(unsigned type, PredIdx idx)
210 {
211 int result = FAIL;
212 ENTRY *ep;
213
214 switch (type) {
215 case BOOLEAN:
216 {
217 /*
218 * This assumes that multiple use entries are supposed
219 * to contribute the logical or of their boolean capabilities.
220 * This is true if we take the semantics of multiple uses to
221 * be 'each capability gets the first non-default value found
222 * in the sequence of use entries'.
223 *
224 * Note that cancelled or absent booleans are stored as FALSE,
225 * unlike numbers and strings, whose cancelled/absent state is
226 * recorded in the terminfo database.
227 */
228 if (idx < NUM_BOOLEANS(&(entries[0].tterm))) {
229 int is_set = FALSE;
230
231 for (ep = &entries[1]; ep < entries + termcount; ep++) {
232 if (idx < NUM_BOOLEANS(&(ep->tterm))
233 && (is_set = ep->tterm.Booleans[idx])) {
234 break;
235 }
236 }
237 if (is_set != entries[0].tterm.Booleans[idx])
238 result = (!is_set);
239 }
240 }
241 break;
242
243 case NUMBER:
244 {
245 /*
246 * We take the semantics of multiple uses to be 'each
247 * capability gets the first non-default value found
248 * in the sequence of use entries'.
249 */
250 if (idx < NUM_NUMBERS(&(entries[0].tterm))) {
251 int value = ABSENT_NUMERIC;
252
253 for (ep = &entries[1]; ep < entries + termcount; ep++)
254 if (idx < NUM_NUMBERS(&(ep->tterm))
255 && VALID_NUMERIC(ep->tterm.Numbers[idx])) {
256 value = ep->tterm.Numbers[idx];
257 break;
258 }
259
260 if (value != entries[0].tterm.Numbers[idx])
261 result = (value != ABSENT_NUMERIC);
262 }
263 }
264 break;
265
266 case STRING:
267 {
268 const char *termstr = entries[0].tterm.Strings[idx];
269 const char *usestr = ABSENT_STRING;
270
271 /*
272 * We take the semantics of multiple uses to be 'each
273 * capability gets the first non-default value found
274 * in the sequence of use entries'.
275 */
276 if (idx < NUM_STRINGS(&(entries[0].tterm))) {
277 for (ep = &entries[1]; ep < entries + termcount; ep++)
278 if (idx < NUM_STRINGS(&(ep->tterm))
279 && ep->tterm.Strings[idx]) {
280 usestr = ep->tterm.Strings[idx];
281 break;
282 }
283
284 if (usestr == CANCELLED_STRING && termstr == ABSENT_STRING)
285 result = (FAIL);
286 else if (usestr == CANCELLED_STRING && termstr == CANCELLED_STRING)
287 result = (TRUE);
288 else if (usestr == ABSENT_STRING && termstr == ABSENT_STRING)
289 result = (FAIL);
290 else if (!usestr || !termstr || capcmp(idx, usestr, termstr))
291 result = (TRUE);
292 }
293 }
294 break;
295
296 default:
297 result = FALSE;
298 break;
299 }
300
301 return (result);
302 }
303
304 static bool
useeq(const ENTRY * e1,const ENTRY * e2)305 useeq(const ENTRY * e1, const ENTRY * e2)
306 /* are the use references in two entries equivalent? */
307 {
308 unsigned i, j;
309
310 if (e1->nuses != e2->nuses)
311 return (FALSE);
312
313 /* Ugh...this is quadratic again */
314 for (i = 0; i < e1->nuses; i++) {
315 bool foundmatch = FALSE;
316
317 /* search second entry for given use reference */
318 for (j = 0; j < e2->nuses; j++)
319 if (!strcmp(e1->uses[i].name, e2->uses[j].name)) {
320 foundmatch = TRUE;
321 break;
322 }
323
324 if (!foundmatch)
325 return (FALSE);
326 }
327
328 return (TRUE);
329 }
330
331 static bool
entryeq(const TERMTYPE2 * t1,const TERMTYPE2 * t2)332 entryeq(const TERMTYPE2 *t1, const TERMTYPE2 *t2)
333 /* are two entries equivalent? */
334 {
335 unsigned i;
336
337 for (i = 0; i < NUM_BOOLEANS(t1); i++)
338 if (t1->Booleans[i] != t2->Booleans[i])
339 return (FALSE);
340
341 for (i = 0; i < NUM_NUMBERS(t1); i++)
342 if (t1->Numbers[i] != t2->Numbers[i])
343 return (FALSE);
344
345 for (i = 0; i < NUM_STRINGS(t1); i++)
346 if (capcmp((PredIdx) i, t1->Strings[i], t2->Strings[i]))
347 return (FALSE);
348
349 return (TRUE);
350 }
351
352 #define TIC_EXPAND(result) _nc_tic_expand(result, outform==F_TERMINFO, numbers)
353
354 static void
print_uses(const ENTRY * ep,FILE * fp)355 print_uses(const ENTRY * ep, FILE *fp)
356 /* print an entry's use references */
357 {
358 if (!ep->nuses) {
359 fputs("NULL", fp);
360 } else {
361 unsigned i;
362
363 for (i = 0; i < ep->nuses; i++) {
364 fputs(ep->uses[i].name, fp);
365 if (i < ep->nuses - 1)
366 fputs(" ", fp);
367 }
368 }
369 }
370
371 static const char *
dump_boolean(int val)372 dump_boolean(int val)
373 /* display the value of a boolean capability */
374 {
375 switch (val) {
376 case ABSENT_BOOLEAN:
377 return (s_absent);
378 case CANCELLED_BOOLEAN:
379 return (s_cancel);
380 case FALSE:
381 return ("F");
382 case TRUE:
383 return ("T");
384 default:
385 return ("?");
386 }
387 }
388
389 static void
dump_numeric(int val,char * buf)390 dump_numeric(int val, char *buf)
391 /* display the value of a numeric capability */
392 {
393 switch (val) {
394 case ABSENT_NUMERIC:
395 _nc_STRCPY(buf, s_absent, MAX_STRING);
396 break;
397 case CANCELLED_NUMERIC:
398 _nc_STRCPY(buf, s_cancel, MAX_STRING);
399 break;
400 default:
401 _nc_SPRINTF(buf, _nc_SLIMIT(MAX_STRING) "%d", val);
402 break;
403 }
404 }
405
406 static void
dump_string(const char * val,char * buf)407 dump_string(const char *val, char *buf)
408 /* display the value of a string capability */
409 {
410 if (val == ABSENT_STRING)
411 _nc_STRCPY(buf, s_absent, MAX_STRING);
412 else if (val == CANCELLED_STRING)
413 _nc_STRCPY(buf, s_cancel, MAX_STRING);
414 else {
415 _nc_SPRINTF(buf, _nc_SLIMIT(MAX_STRING)
416 "'%.*s'", MAX_STRING - 3, TIC_EXPAND(val));
417 }
418 }
419
420 /*
421 * Show "comparing..." message for the given terminal names.
422 */
423 static void
show_comparing(char ** names)424 show_comparing(char **names)
425 {
426 if (itrace) {
427 switch (compare) {
428 case C_DIFFERENCE:
429 (void) fprintf(stderr, "%s: dumping differences\n", _nc_progname);
430 break;
431
432 case C_COMMON:
433 (void) fprintf(stderr, "%s: dumping common capabilities\n", _nc_progname);
434 break;
435
436 case C_NAND:
437 (void) fprintf(stderr, "%s: dumping differences\n", _nc_progname);
438 break;
439 }
440 }
441 if (*names) {
442 printf("comparing %s", *names++);
443 if (*names) {
444 printf(" to %s", *names++);
445 while (*names) {
446 printf(", %s", *names++);
447 }
448 }
449 printf(".\n");
450 }
451 }
452
453 /*
454 * ncurses stores two types of non-standard capabilities:
455 * a) capabilities listed past the "STOP-HERE" comment in the Caps file.
456 * These are used in the terminfo source file to provide data for termcaps,
457 * e.g., when there is no equivalent capability in terminfo, as well as for
458 * widely-used non-standard capabilities.
459 * b) user-definable capabilities, via "tic -x".
460 *
461 * However, if "-x" is omitted from the tic command, both types of
462 * non-standard capability are not loaded into the terminfo database. This
463 * macro is used for limit-checks against the symbols that tic uses to omit
464 * the two types of non-standard entry.
465 */
466 #if NCURSES_XNAMES
467 #define check_user_definable(n,limit) if (!_nc_user_definable && (n) > (limit)) break
468 #else
469 #define check_user_definable(n,limit) if ((n) > (limit)) break
470 #endif
471
472 /*
473 * Use these macros to simplify loops on C_COMMON and C_NAND:
474 */
475 #define for_each_entry() while (entries[extra].tterm.term_names)
476 #define next_entry (&(entries[extra++].tterm))
477
478 static void
compare_predicate(PredType type,PredIdx idx,const char * name)479 compare_predicate(PredType type, PredIdx idx, const char *name)
480 /* predicate function to use for entry difference reports */
481 {
482 const ENTRY *e1 = &entries[0];
483 const ENTRY *e2 = &entries[1];
484 char buf1[MAX_STRING];
485 char buf2[MAX_STRING];
486 int b1, b2;
487 int n1, n2;
488 const char *s1, *s2;
489 bool found;
490 int extra = 1;
491
492 switch (type) {
493 case CMP_BOOLEAN:
494 check_user_definable(idx, BOOLWRITE);
495 b1 = e1->tterm.Booleans[idx];
496 switch (compare) {
497 case C_DIFFERENCE:
498 b2 = next_entry->Booleans[idx];
499 if (!(no_boolean(b1) && no_boolean(b2)) && (b1 != b2))
500 (void) printf("\t%s: %s%s%s.\n",
501 name,
502 dump_boolean(b1),
503 bool_sep,
504 dump_boolean(b2));
505 break;
506
507 case C_COMMON:
508 if (b1 != ABSENT_BOOLEAN) {
509 found = TRUE;
510 for_each_entry() {
511 b2 = next_entry->Booleans[idx];
512 if (b1 != b2) {
513 found = FALSE;
514 break;
515 }
516 }
517 if (found) {
518 (void) printf("\t%s= %s.\n", name, dump_boolean(b1));
519 }
520 }
521 break;
522
523 case C_NAND:
524 if (b1 == ABSENT_BOOLEAN) {
525 found = TRUE;
526 for_each_entry() {
527 b2 = next_entry->Booleans[idx];
528 if (b1 != b2) {
529 found = FALSE;
530 break;
531 }
532 }
533 if (found) {
534 (void) printf("\t!%s.\n", name);
535 }
536 }
537 break;
538 }
539 break;
540
541 case CMP_NUMBER:
542 check_user_definable(idx, NUMWRITE);
543 n1 = e1->tterm.Numbers[idx];
544 switch (compare) {
545 case C_DIFFERENCE:
546 n2 = next_entry->Numbers[idx];
547 if (!(no_numeric(n1) && no_numeric(n2)) && n1 != n2) {
548 dump_numeric(n1, buf1);
549 dump_numeric(n2, buf2);
550 (void) printf("\t%s: %s, %s.\n", name, buf1, buf2);
551 }
552 break;
553
554 case C_COMMON:
555 if (n1 != ABSENT_NUMERIC) {
556 found = TRUE;
557 for_each_entry() {
558 n2 = next_entry->Numbers[idx];
559 if (n1 != n2) {
560 found = FALSE;
561 break;
562 }
563 }
564 if (found) {
565 dump_numeric(n1, buf1);
566 (void) printf("\t%s= %s.\n", name, buf1);
567 }
568 }
569 break;
570
571 case C_NAND:
572 if (n1 == ABSENT_NUMERIC) {
573 found = TRUE;
574 for_each_entry() {
575 n2 = next_entry->Numbers[idx];
576 if (n1 != n2) {
577 found = FALSE;
578 break;
579 }
580 }
581 if (found) {
582 (void) printf("\t!%s.\n", name);
583 }
584 }
585 break;
586 }
587 break;
588
589 case CMP_STRING:
590 check_user_definable(idx, STRWRITE);
591 s1 = e1->tterm.Strings[idx];
592 switch (compare) {
593 case C_DIFFERENCE:
594 s2 = next_entry->Strings[idx];
595 if (!(no_string(s1) && no_string(s2)) && capcmp(idx, s1, s2)) {
596 dump_string(s1, buf1);
597 dump_string(s2, buf2);
598 if (strcmp(buf1, buf2))
599 (void) printf("\t%s: %s, %s.\n", name, buf1, buf2);
600 }
601 break;
602
603 case C_COMMON:
604 if (s1 != ABSENT_STRING) {
605 found = TRUE;
606 for_each_entry() {
607 s2 = next_entry->Strings[idx];
608 if (capcmp(idx, s1, s2) != 0) {
609 found = FALSE;
610 break;
611 }
612 }
613 if (found) {
614 (void) printf("\t%s= '%s'.\n", name, TIC_EXPAND(s1));
615 }
616 }
617 break;
618
619 case C_NAND:
620 if (s1 == ABSENT_STRING) {
621 found = TRUE;
622 for_each_entry() {
623 s2 = next_entry->Strings[idx];
624 if (s2 != s1) {
625 found = FALSE;
626 break;
627 }
628 }
629 if (found) {
630 (void) printf("\t!%s.\n", name);
631 }
632 }
633 break;
634 }
635 break;
636
637 case CMP_USE:
638 /* unlike the other modes, this compares *all* use entries */
639 switch (compare) {
640 case C_DIFFERENCE:
641 if (!useeq(e1, e2)) {
642 (void) fputs("\tuse: ", stdout);
643 print_uses(e1, stdout);
644 fputs(", ", stdout);
645 print_uses(e2, stdout);
646 fputs(".\n", stdout);
647 }
648 break;
649
650 case C_COMMON:
651 if (e1->nuses) {
652 found = TRUE;
653 for_each_entry() {
654 e2 = &entries[extra++];
655 if (e2->nuses != e1->nuses || !useeq(e1, e2)) {
656 found = FALSE;
657 break;
658 }
659 }
660 if (found) {
661 (void) fputs("\tuse: ", stdout);
662 print_uses(e1, stdout);
663 fputs(".\n", stdout);
664 }
665 }
666 break;
667
668 case C_NAND:
669 if (!e1->nuses) {
670 found = TRUE;
671 for_each_entry() {
672 e2 = &entries[extra++];
673 if (e2->nuses != e1->nuses) {
674 found = FALSE;
675 break;
676 }
677 }
678 if (found) {
679 (void) printf("\t!use.\n");
680 }
681 }
682 break;
683 }
684 }
685 }
686
687 /***************************************************************************
688 *
689 * Init string analysis
690 *
691 ***************************************************************************/
692
693 #define DATA(from, to) { { from }, { to } }
694 #define DATAX() DATA("", "")
695
696 typedef struct {
697 const char from[8];
698 const char to[12];
699 } assoc;
700
701 static const assoc std_caps[] =
702 {
703 /* these are specified by X.364 and iBCS2 */
704 DATA("\033c", "RIS"), /* full reset */
705 DATA("\0337", "SC"), /* save cursor */
706 DATA("\0338", "RC"), /* restore cursor */
707 DATA("\033[r", "RSR"), /* not an X.364 mnemonic */
708 DATA("\033[m", "SGR0"), /* not an X.364 mnemonic */
709 DATA("\033[2J", "ED2"), /* clear page */
710
711 /* this group is specified by ISO 2022 */
712 DATA("\033(0", "ISO DEC G0"), /* enable DEC graphics for G0 */
713 DATA("\033(A", "ISO UK G0"), /* enable UK chars for G0 */
714 DATA("\033(B", "ISO US G0"), /* enable US chars for G0 */
715 DATA("\033)0", "ISO DEC G1"), /* enable DEC graphics for G1 */
716 DATA("\033)A", "ISO UK G1"), /* enable UK chars for G1 */
717 DATA("\033)B", "ISO US G1"), /* enable US chars for G1 */
718
719 /* these are DEC private controls widely supported by emulators */
720 DATA("\033=", "DECPAM"), /* application keypad mode */
721 DATA("\033>", "DECPNM"), /* normal keypad mode */
722 DATA("\033<", "DECANSI"), /* enter ANSI mode */
723 DATA("\033[!p", "DECSTR"), /* soft reset */
724 DATA("\033 F", "S7C1T"), /* 7-bit controls */
725
726 DATAX()
727 };
728
729 static const assoc std_modes[] =
730 /* ECMA \E[ ... [hl] modes recognized by many emulators */
731 {
732 DATA("2", "AM"), /* keyboard action mode */
733 DATA("4", "IRM"), /* insert/replace mode */
734 DATA("12", "SRM"), /* send/receive mode */
735 DATA("20", "LNM"), /* linefeed mode */
736 DATAX()
737 };
738
739 static const assoc private_modes[] =
740 /* DEC \E[ ... [hl] modes recognized by many emulators */
741 {
742 DATA("1", "CKM"), /* application cursor keys */
743 DATA("2", "ANM"), /* set VT52 mode */
744 DATA("3", "COLM"), /* 132-column mode */
745 DATA("4", "SCLM"), /* smooth scroll */
746 DATA("5", "SCNM"), /* reverse video mode */
747 DATA("6", "OM"), /* origin mode */
748 DATA("7", "AWM"), /* wraparound mode */
749 DATA("8", "ARM"), /* auto-repeat mode */
750 DATAX()
751 };
752
753 static const assoc ecma_highlights[] =
754 /* recognize ECMA attribute sequences */
755 {
756 DATA("0", "NORMAL"), /* normal */
757 DATA("1", "+BOLD"), /* bold on */
758 DATA("2", "+DIM"), /* dim on */
759 DATA("3", "+ITALIC"), /* italic on */
760 DATA("4", "+UNDERLINE"), /* underline on */
761 DATA("5", "+BLINK"), /* blink on */
762 DATA("6", "+FASTBLINK"), /* fastblink on */
763 DATA("7", "+REVERSE"), /* reverse on */
764 DATA("8", "+INVISIBLE"), /* invisible on */
765 DATA("9", "+DELETED"), /* deleted on */
766 DATA("10", "MAIN-FONT"), /* select primary font */
767 DATA("11", "ALT-FONT-1"), /* select alternate font 1 */
768 DATA("12", "ALT-FONT-2"), /* select alternate font 2 */
769 DATA("13", "ALT-FONT-3"), /* select alternate font 3 */
770 DATA("14", "ALT-FONT-4"), /* select alternate font 4 */
771 DATA("15", "ALT-FONT-5"), /* select alternate font 5 */
772 DATA("16", "ALT-FONT-6"), /* select alternate font 6 */
773 DATA("17", "ALT-FONT-7"), /* select alternate font 7 */
774 DATA("18", "ALT-FONT-1"), /* select alternate font 1 */
775 DATA("19", "ALT-FONT-1"), /* select alternate font 1 */
776 DATA("20", "FRAKTUR"), /* Fraktur font */
777 DATA("21", "DOUBLEUNDER"), /* double underline */
778 DATA("22", "-DIM"), /* dim off */
779 DATA("23", "-ITALIC"), /* italic off */
780 DATA("24", "-UNDERLINE"), /* underline off */
781 DATA("25", "-BLINK"), /* blink off */
782 DATA("26", "-FASTBLINK"), /* fastblink off */
783 DATA("27", "-REVERSE"), /* reverse off */
784 DATA("28", "-INVISIBLE"), /* invisible off */
785 DATA("29", "-DELETED"), /* deleted off */
786 DATAX()
787 };
788
789 #undef DATA
790
791 static int
skip_csi(const char * cap)792 skip_csi(const char *cap)
793 {
794 int result = 0;
795 if (cap[0] == '\033' && cap[1] == '[')
796 result = 2;
797 else if (UChar(cap[0]) == 0233)
798 result = 1;
799 return result;
800 }
801
802 static bool
same_param(const char * table,const char * param,size_t length)803 same_param(const char *table, const char *param, size_t length)
804 {
805 bool result = FALSE;
806 if (strncmp(table, param, length) == 0) {
807 result = !isdigit(UChar(param[length]));
808 }
809 return result;
810 }
811
812 static char *
lookup_params(const assoc * table,char * dst,char * src)813 lookup_params(const assoc * table, char *dst, char *src)
814 {
815 char *result = NULL;
816 const char *ep = strtok(src, ";");
817
818 if (ep != NULL) {
819 const assoc *ap;
820
821 do {
822 bool found = FALSE;
823
824 for (ap = table; ap->from[0]; ap++) {
825 size_t tlen = strlen(ap->from);
826
827 if (same_param(ap->from, ep, tlen)) {
828 _nc_STRCAT(dst, ap->to, MAX_TERMINFO_LENGTH);
829 found = TRUE;
830 break;
831 }
832 }
833
834 if (!found)
835 _nc_STRCAT(dst, ep, MAX_TERMINFO_LENGTH);
836 _nc_STRCAT(dst, ";", MAX_TERMINFO_LENGTH);
837 } while
838 ((ep = strtok((char *) 0, ";")) != NULL);
839
840 dst[strlen(dst) - 1] = '\0';
841
842 result = dst;
843 }
844 return result;
845 }
846
847 static void
analyze_string(const char * name,const char * cap,TERMTYPE2 * tp)848 analyze_string(const char *name, const char *cap, TERMTYPE2 *tp)
849 {
850 char buf2[MAX_TERMINFO_LENGTH + 1];
851 const char *sp;
852 const assoc *ap;
853 int tp_lines = tp->Numbers[2];
854
855 if (!VALID_STRING(cap))
856 return;
857 (void) printf("%s: ", name);
858
859 for (sp = cap; *sp; sp++) {
860 int i;
861 int csi;
862 size_t len = 0;
863 size_t next;
864 const char *expansion = NULL;
865 char buf3[MAX_TERMINFO_LENGTH];
866
867 /* first, check other capabilities in this entry */
868 for (i = 0; i < STRCOUNT; i++) {
869 const char *cp = tp->Strings[i];
870
871 /* don't use function-key capabilities */
872 if (strnames[i] == NULL)
873 continue;
874 if (strnames[i][0] == 'k' && strnames[i][1] == 'f')
875 continue;
876
877 if (VALID_STRING(cp) &&
878 cp[0] != '\0' &&
879 cp != cap) {
880 if ((len = strlen(cp)) > MAX_TERMINFO_LENGTH)
881 len = MAX_TERMINFO_LENGTH;
882 _nc_STRNCPY(buf2, sp, len);
883 buf2[len] = '\0';
884
885 if (_nc_capcmp(cp, buf2))
886 continue;
887
888 #define ISRS(s) (!strncmp((s), "is", (size_t) 2) || !strncmp((s), "rs", (size_t) 2))
889 /*
890 * Theoretically we just passed the test for translation
891 * (equality once the padding is stripped). However, there
892 * are a few more hoops that need to be jumped so that
893 * identical pairs of initialization and reset strings
894 * don't just refer to each other.
895 */
896 if (ISRS(name) || ISRS(strnames[i]))
897 if (cap < cp)
898 continue;
899 #undef ISRS
900
901 expansion = strnames[i];
902 break;
903 }
904 }
905
906 /* now check the standard capabilities */
907 if (!expansion) {
908 csi = skip_csi(sp);
909 for (ap = std_caps; ap->from[0]; ap++) {
910 size_t adj = (size_t) (csi ? 2 : 0);
911
912 len = strlen(ap->from);
913 if (csi && skip_csi(ap->from) != csi)
914 continue;
915 if (len > adj
916 && strncmp(ap->from + adj, sp + csi, len - adj) == 0) {
917 expansion = ap->to;
918 len -= adj;
919 len += (size_t) csi;
920 break;
921 }
922 }
923 }
924
925 /* now check for standard-mode sequences */
926 if (!expansion
927 && (csi = skip_csi(sp)) != 0
928 && (len = (strspn) (sp + csi, "0123456789;"))
929 && (len < sizeof(buf3))
930 && (next = (size_t) csi + len)
931 && ((sp[next] == 'h') || (sp[next] == 'l'))) {
932
933 _nc_STRCPY(buf2,
934 ((sp[next] == 'h')
935 ? "ECMA+"
936 : "ECMA-"),
937 sizeof(buf2));
938 _nc_STRNCPY(buf3, sp + csi, len);
939 buf3[len] = '\0';
940
941 expansion = lookup_params(std_modes, buf2, buf3);
942 }
943
944 /* now check for private-mode sequences */
945 if (!expansion
946 && (csi = skip_csi(sp)) != 0
947 && sp[csi] == '?'
948 && (len = (strspn) (sp + csi + 1, "0123456789;"))
949 && (len < sizeof(buf3))
950 && (next = (size_t) csi + 1 + len)
951 && ((sp[next] == 'h') || (sp[next] == 'l'))) {
952
953 _nc_STRCPY(buf2,
954 ((sp[next] == 'h')
955 ? "DEC+"
956 : "DEC-"),
957 sizeof(buf2));
958 _nc_STRNCPY(buf3, sp + csi + 1, len);
959 buf3[len] = '\0';
960
961 expansion = lookup_params(private_modes, buf2, buf3);
962 }
963
964 /* now check for ECMA highlight sequences */
965 if (!expansion
966 && (csi = skip_csi(sp)) != 0
967 && (len = (strspn) (sp + csi, "0123456789;")) != 0
968 && (len < sizeof(buf3))
969 && (next = (size_t) csi + len)
970 && sp[next] == 'm') {
971
972 _nc_STRCPY(buf2, "SGR:", sizeof(buf2));
973 _nc_STRNCPY(buf3, sp + csi, len);
974 buf3[len] = '\0';
975 len += (size_t) csi + 1;
976
977 expansion = lookup_params(ecma_highlights, buf2, buf3);
978 }
979
980 if (!expansion
981 && (csi = skip_csi(sp)) != 0
982 && sp[csi] == 'm') {
983 len = (size_t) csi + 1;
984 _nc_STRCPY(buf2, "SGR:", sizeof(buf2));
985 _nc_STRCAT(buf2, ecma_highlights[0].to, sizeof(buf2));
986 expansion = buf2;
987 }
988
989 /* now check for scroll region reset */
990 if (!expansion
991 && (csi = skip_csi(sp)) != 0) {
992 if (sp[csi] == 'r') {
993 expansion = "RSR";
994 len = 1;
995 } else {
996 _nc_SPRINTF(buf2, _nc_SLIMIT(sizeof(buf2)) "1;%dr", tp_lines);
997 len = strlen(buf2);
998 if (strncmp(buf2, sp + csi, len) == 0)
999 expansion = "RSR";
1000 }
1001 len += (size_t) csi;
1002 }
1003
1004 /* now check for home-down */
1005 if (!expansion
1006 && (csi = skip_csi(sp)) != 0) {
1007 _nc_SPRINTF(buf2, _nc_SLIMIT(sizeof(buf2)) "%d;1H", tp_lines);
1008 len = strlen(buf2);
1009 if (strncmp(buf2, sp + csi, len) == 0) {
1010 expansion = "LL";
1011 } else {
1012 _nc_SPRINTF(buf2, _nc_SLIMIT(sizeof(buf2)) "%dH", tp_lines);
1013 len = strlen(buf2);
1014 if (strncmp(buf2, sp + csi, len) == 0) {
1015 expansion = "LL";
1016 }
1017 }
1018 len += (size_t) csi;
1019 }
1020
1021 /* now look at the expansion we got, if any */
1022 if (expansion) {
1023 printf("{%s}", expansion);
1024 sp += len - 1;
1025 } else {
1026 /* couldn't match anything */
1027 buf2[0] = *sp;
1028 buf2[1] = '\0';
1029 fputs(TIC_EXPAND(buf2), stdout);
1030 }
1031 }
1032 putchar('\n');
1033 }
1034
1035 /***************************************************************************
1036 *
1037 * File comparison
1038 *
1039 ***************************************************************************/
1040
1041 static void
file_comparison(int argc,char * argv[])1042 file_comparison(int argc, char *argv[])
1043 {
1044 #define MAXCOMPARE 2
1045 /* someday we may allow comparisons on more files */
1046 int filecount = 0;
1047 ENTRY *heads[MAXCOMPARE];
1048 ENTRY *qp, *rp;
1049 int i, n;
1050
1051 memset(heads, 0, sizeof(heads));
1052 dump_init((char *) 0, F_LITERAL, S_TERMINFO,
1053 FALSE, 0, 65535, itrace, FALSE, FALSE, FALSE);
1054
1055 for (n = 0; n < argc && n < MAXCOMPARE; n++) {
1056 if (freopen(argv[n], "r", stdin) == NULL)
1057 _nc_err_abort("Can't open %s: %s", argv[n], strerror(errno));
1058
1059 #if NO_LEAKS
1060 entered[n].head = _nc_head;
1061 entered[n].tail = _nc_tail;
1062 #endif
1063 _nc_head = _nc_tail = NULL;
1064
1065 /* parse entries out of the source file */
1066 _nc_set_source(argv[n]);
1067 _nc_read_entry_source(stdin, NULL, TRUE, literal, NULLHOOK);
1068
1069 if (itrace)
1070 (void) fprintf(stderr, "Resolving file %d...\n", n - 0);
1071
1072 /* maybe do use resolution */
1073 if (!_nc_resolve_uses2(!limited, literal)) {
1074 (void) fprintf(stderr,
1075 "There are unresolved use entries in %s:\n",
1076 argv[n]);
1077 for_entry_list(qp) {
1078 if (qp->nuses) {
1079 (void) fputs(qp->tterm.term_names, stderr);
1080 (void) fputc('\n', stderr);
1081 }
1082 }
1083 ExitProgram(EXIT_FAILURE);
1084 }
1085
1086 heads[filecount] = _nc_head;
1087 filecount++;
1088 }
1089
1090 /* OK, all entries are in core. Ready to do the comparison */
1091 if (itrace)
1092 (void) fprintf(stderr, "Entries are now in core...\n");
1093
1094 /* The entry-matching loop. Sigh, this is intrinsically quadratic. */
1095 for (qp = heads[0]; qp; qp = qp->next) {
1096 for (rp = heads[1]; rp; rp = rp->next)
1097 if (_nc_entry_match(qp->tterm.term_names, rp->tterm.term_names)) {
1098 if (qp->ncrosslinks < MAX_CROSSLINKS)
1099 qp->crosslinks[qp->ncrosslinks] = rp;
1100 qp->ncrosslinks++;
1101
1102 if (rp->ncrosslinks < MAX_CROSSLINKS)
1103 rp->crosslinks[rp->ncrosslinks] = qp;
1104 rp->ncrosslinks++;
1105 }
1106 }
1107
1108 /* now we have two circular lists with crosslinks */
1109 if (itrace)
1110 (void) fprintf(stderr, "Name matches are done...\n");
1111
1112 for (qp = heads[0]; qp; qp = qp->next) {
1113 if (qp->ncrosslinks > 1) {
1114 (void) fprintf(stderr,
1115 "%s in file 1 (%s) has %d matches in file 2 (%s):\n",
1116 _nc_first_name(qp->tterm.term_names),
1117 argv[0],
1118 qp->ncrosslinks,
1119 argv[1]);
1120 for (i = 0; i < qp->ncrosslinks; i++)
1121 (void) fprintf(stderr,
1122 "\t%s\n",
1123 _nc_first_name((qp->crosslinks[i])->tterm.term_names));
1124 }
1125 }
1126
1127 for (rp = heads[1]; rp; rp = rp->next) {
1128 if (rp->ncrosslinks > 1) {
1129 (void) fprintf(stderr,
1130 "%s in file 2 (%s) has %d matches in file 1 (%s):\n",
1131 _nc_first_name(rp->tterm.term_names),
1132 argv[1],
1133 rp->ncrosslinks,
1134 argv[0]);
1135 for (i = 0; i < rp->ncrosslinks; i++)
1136 (void) fprintf(stderr,
1137 "\t%s\n",
1138 _nc_first_name((rp->crosslinks[i])->tterm.term_names));
1139 }
1140 }
1141
1142 (void) printf("In file 1 (%s) only:\n", argv[0]);
1143 for (qp = heads[0]; qp; qp = qp->next)
1144 if (qp->ncrosslinks == 0)
1145 (void) printf("\t%s\n",
1146 _nc_first_name(qp->tterm.term_names));
1147
1148 (void) printf("In file 2 (%s) only:\n", argv[1]);
1149 for (rp = heads[1]; rp; rp = rp->next)
1150 if (rp->ncrosslinks == 0)
1151 (void) printf("\t%s\n",
1152 _nc_first_name(rp->tterm.term_names));
1153
1154 (void) printf("The following entries are equivalent:\n");
1155 for (qp = heads[0]; qp; qp = qp->next) {
1156 if (qp->ncrosslinks == 1) {
1157 rp = qp->crosslinks[0];
1158
1159 repair_acsc(&qp->tterm);
1160 repair_acsc(&rp->tterm);
1161 #if NCURSES_XNAMES
1162 _nc_align_termtype(&qp->tterm, &rp->tterm);
1163 #endif
1164 if (entryeq(&qp->tterm, &rp->tterm) && useeq(qp, rp)) {
1165 char name1[NAMESIZE], name2[NAMESIZE];
1166
1167 canonical_name(qp->tterm.term_names, name1);
1168 canonical_name(rp->tterm.term_names, name2);
1169
1170 (void) printf("%s = %s\n", name1, name2);
1171 }
1172 }
1173 }
1174
1175 (void) printf("Differing entries:\n");
1176 termcount = 2;
1177 for (qp = heads[0]; qp; qp = qp->next) {
1178
1179 if (qp->ncrosslinks == 1) {
1180 rp = qp->crosslinks[0];
1181 #if NCURSES_XNAMES
1182 /* sorry - we have to do this on each pass */
1183 _nc_align_termtype(&qp->tterm, &rp->tterm);
1184 #endif
1185 if (!(entryeq(&qp->tterm, &rp->tterm) && useeq(qp, rp))) {
1186 char name1[NAMESIZE], name2[NAMESIZE];
1187 char *names[3];
1188
1189 names[0] = name1;
1190 names[1] = name2;
1191 names[2] = NULL;
1192
1193 entries[0] = *qp;
1194 entries[1] = *rp;
1195
1196 canonical_name(qp->tterm.term_names, name1);
1197 canonical_name(rp->tterm.term_names, name2);
1198
1199 switch (compare) {
1200 case C_DIFFERENCE:
1201 show_comparing(names);
1202 compare_entry(compare_predicate, &entries->tterm, quiet);
1203 break;
1204
1205 case C_COMMON:
1206 show_comparing(names);
1207 compare_entry(compare_predicate, &entries->tterm, quiet);
1208 break;
1209
1210 case C_NAND:
1211 show_comparing(names);
1212 compare_entry(compare_predicate, &entries->tterm, quiet);
1213 break;
1214
1215 }
1216 }
1217 }
1218 }
1219 }
1220
1221 static void
usage(void)1222 usage(void)
1223 {
1224 #define DATA(s) s "\n"
1225 static const char head[] =
1226 {
1227 DATA("")
1228 DATA("Options:")
1229 };
1230 #undef DATA
1231 /* length is given here so the compiler can make everything readonly */
1232 #define DATA(s) s
1233 static const char options[][46] =
1234 {
1235 " -0 print single-row"
1236 ," -1 print single-column"
1237 ," -C use termcap-names"
1238 ," -D print database locations"
1239 ," -E format output as C tables"
1240 ," -F compare terminfo-files"
1241 ," -G format %{number} to %'char'"
1242 ," -I use terminfo-names"
1243 ," -K use termcap-names and BSD syntax"
1244 ," -L use long names"
1245 ," -R subset (see man page)"
1246 ," -T eliminate size limits (test)"
1247 ," -U do not post-process entries"
1248 ," -V print version"
1249 ," -W wrap long strings per -w[n]"
1250 #if NCURSES_XNAMES
1251 ," -a with -F, list commented-out caps"
1252 #endif
1253 ," -c list common capabilities"
1254 ," -d list different capabilities"
1255 ," -e format output for C initializer"
1256 ," -f with -1, format complex strings"
1257 ," -g format %'char' to %{number}"
1258 ," -i analyze initialization/reset"
1259 ," -l output terminfo names"
1260 ," -n list capabilities in neither"
1261 ," -p ignore padding specifiers"
1262 ," -Q number dump compiled description"
1263 ," -q brief listing, removes headers"
1264 ," -r with -C, output in termcap form"
1265 ," -r with -F, resolve use-references"
1266 ," -s [d|i|l|c] sort fields"
1267 #if NCURSES_XNAMES
1268 ," -t suppress commented-out capabilities"
1269 #endif
1270 ," -u produce source with 'use='"
1271 ," -v number (verbose)"
1272 ," -w number (width)"
1273 #if NCURSES_XNAMES
1274 ," -x unknown capabilities are user-defined"
1275 #endif
1276 };
1277 #undef DATA
1278 const size_t last = SIZEOF(options);
1279 const size_t left = (last + 1) / 2;
1280 size_t n;
1281 FILE *fp = stderr;
1282
1283 fprintf(fp, usage_string, _nc_progname);
1284 fputs(head, fp);
1285 for (n = 0; n < left; n++) {
1286 size_t m = n + left;
1287 if (m < last)
1288 fprintf(fp, "%-40.40s%s\n", options[n], options[m]);
1289 else
1290 fprintf(fp, "%s\n", options[n]);
1291 }
1292 ExitProgram(EXIT_FAILURE);
1293 }
1294
1295 #define isName(c) ((c) == '_' || isalnum(UChar(c)))
1296
1297 static char *
safe_name(const char * format,const char * prefix,const char * name)1298 safe_name(const char *format, const char *prefix, const char *name)
1299 {
1300 static char *result;
1301 static size_t need;
1302 char *s;
1303
1304 if (result == NULL) {
1305 need = (strlen(prefix)
1306 + strlen(name)
1307 + strlen(format));
1308 result = (char *) malloc(need + 1);
1309 if (result == NULL)
1310 failed("safe_name");
1311 }
1312
1313 _nc_STRCPY(result, "", need);
1314 if (isdigit(UChar(*prefix)))
1315 _nc_STRCAT(result, "ti_", need);
1316 _nc_STRCAT(result, prefix, need);
1317 for (s = result; *s != 0 && *s != '|'; s++) {
1318 if (!isName(*s))
1319 *s = '_';
1320 }
1321 *s = 0;
1322 if (isdigit(UChar(*name)) && !*prefix)
1323 *s++ = '_';
1324 _nc_SPRINTF(s, _nc_SLIMIT(need) format, name);
1325 return result;
1326 }
1327
1328 /*
1329 * escape contents of a double-quoted string.
1330 */
1331 static char *
safe_string(const char * source)1332 safe_string(const char *source)
1333 {
1334 static char *result;
1335 static size_t need;
1336 char *d;
1337 if (result == NULL) {
1338 need = 2 * strlen(source) + 1;
1339 result = (char *) malloc(need + 1);
1340 }
1341 for (d = result; *source != '\0'; ++source) {
1342 char ch = *source;
1343 if (ch == '"' || ch == '\\') {
1344 *d++ = '\\';
1345 }
1346 *d++ = ch;
1347 }
1348 *d = '\0';
1349 return result;
1350 }
1351
1352 static char *
any_initializer(const char * fmt,const char * type)1353 any_initializer(const char *fmt, const char *type)
1354 {
1355 return safe_name(fmt, entries->tterm.term_names, type);
1356 }
1357
1358 static char *
name_initializer(const char * type)1359 name_initializer(const char *type)
1360 {
1361 return any_initializer("_%s_data", type);
1362 }
1363
1364 static char *
string_variable(const char * type)1365 string_variable(const char *type)
1366 {
1367 return any_initializer("_s_%s", type);
1368 }
1369
1370 #if NCURSES_XNAMES
1371 static char *
name_of(const char * name)1372 name_of(const char *name)
1373 {
1374 return safe_name("name_of_%s", "", name);
1375 }
1376
1377 static void
dump_extended_name(const char * name)1378 dump_extended_name(const char *name)
1379 {
1380 static char **known;
1381 static size_t dumped;
1382 static size_t length;
1383
1384 if (name != NULL) {
1385 bool found = FALSE;
1386 if (length != 0) {
1387 size_t check;
1388 for (check = 0; check < dumped; ++check) {
1389 if (!strcmp(name, known[check])) {
1390 found = TRUE;
1391 break;
1392 }
1393 }
1394 }
1395 if (!found) {
1396 if (dumped + 2 > length) {
1397 length += 100;
1398 known = realloc(known, length * sizeof(*known));
1399 }
1400 printf("\n");
1401 printf("#ifndef %s\n", safe_name("extension_%s", "", name));
1402 printf("static char %s[] = \"%s\";\n", name_of(name), name);
1403 printf("#define %s 1\n", safe_name("extension_%s", "", name));
1404 printf("#endif\n");
1405 known[dumped] = strdup(name);
1406 }
1407 } else {
1408 while (dumped != 0) {
1409 free(known[--dumped]);
1410 }
1411 free(known);
1412 length = 0;
1413 }
1414 }
1415 #endif
1416
1417 /* dump C initializers for the terminal type */
1418 static void
dump_initializers(const TERMTYPE2 * term)1419 dump_initializers(const TERMTYPE2 *term)
1420 {
1421 unsigned n;
1422 const char *str = NULL;
1423
1424 printf("\nstatic char %s[] = \"%s\";\n\n",
1425 name_initializer("alias"), safe_string(entries->tterm.term_names));
1426
1427 for_each_string(n, term) {
1428 if (VALID_STRING(term->Strings[n])) {
1429 char buf[MAX_STRING], *sp, *tp;
1430
1431 tp = buf;
1432 #define TP_LIMIT ((MAX_STRING - 6) - (size_t)(tp - buf))
1433 *tp++ = '"';
1434 for (sp = term->Strings[n];
1435 *sp != 0 && TP_LIMIT > 5;
1436 sp++) {
1437 if (isascii(UChar(*sp))
1438 && isprint(UChar(*sp))
1439 && *sp != '\\'
1440 && *sp != '"')
1441 *tp++ = *sp;
1442 else {
1443 _nc_SPRINTF(tp, _nc_SLIMIT(TP_LIMIT) "\\%03o", UChar(*sp));
1444 tp += 4;
1445 }
1446 }
1447 *tp++ = '"';
1448 *tp = '\0';
1449 (void) printf("static char %-20s[] = %s;\n",
1450 string_variable(ExtStrname(term, (int) n, strnames)),
1451 buf);
1452 }
1453 }
1454 printf("\n");
1455
1456 (void) printf("static char %s[] = %s\n", name_initializer("bool"), L_CURL);
1457
1458 for_each_boolean(n, term) {
1459 switch ((int) (term->Booleans[n])) {
1460 case TRUE:
1461 str = "TRUE";
1462 break;
1463
1464 case FALSE:
1465 str = "FALSE";
1466 break;
1467
1468 case ABSENT_BOOLEAN:
1469 str = "ABSENT_BOOLEAN";
1470 break;
1471
1472 case CANCELLED_BOOLEAN:
1473 str = "CANCELLED_BOOLEAN";
1474 break;
1475 }
1476 (void) printf("\t/* %3u: %-8s */\t%s,\n",
1477 n, ExtBoolname(term, (int) n, boolnames), str);
1478 }
1479 (void) printf("%s;\n", R_CURL);
1480
1481 (void) printf("static short %s[] = %s\n", name_initializer("number"), L_CURL);
1482
1483 for_each_number(n, term) {
1484 char buf[BUFSIZ];
1485 switch (term->Numbers[n]) {
1486 case ABSENT_NUMERIC:
1487 str = "ABSENT_NUMERIC";
1488 break;
1489 case CANCELLED_NUMERIC:
1490 str = "CANCELLED_NUMERIC";
1491 break;
1492 default:
1493 _nc_SPRINTF(buf, _nc_SLIMIT(sizeof(buf)) "%d", term->Numbers[n]);
1494 str = buf;
1495 break;
1496 }
1497 (void) printf("\t/* %3u: %-8s */\t%s,\n", n,
1498 ExtNumname(term, (int) n, numnames), str);
1499 }
1500 (void) printf("%s;\n", R_CURL);
1501
1502 (void) printf("static char * %s[] = %s\n", name_initializer("string"), L_CURL);
1503
1504 for_each_string(n, term) {
1505
1506 if (term->Strings[n] == ABSENT_STRING)
1507 str = "ABSENT_STRING";
1508 else if (term->Strings[n] == CANCELLED_STRING)
1509 str = "CANCELLED_STRING";
1510 else {
1511 str = string_variable(ExtStrname(term, (int) n, strnames));
1512 }
1513 (void) printf("\t/* %3u: %-8s */\t%s,\n", n,
1514 ExtStrname(term, (int) n, strnames), str);
1515 }
1516 (void) printf("%s;\n", R_CURL);
1517
1518 #if NCURSES_XNAMES
1519 if ((NUM_BOOLEANS(term) != BOOLCOUNT)
1520 || (NUM_NUMBERS(term) != NUMCOUNT)
1521 || (NUM_STRINGS(term) != STRCOUNT)) {
1522 for (n = BOOLCOUNT; n < NUM_BOOLEANS(term); ++n) {
1523 dump_extended_name(ExtBoolname(term, (int) n, boolnames));
1524 }
1525 for (n = NUMCOUNT; n < NUM_NUMBERS(term); ++n) {
1526 dump_extended_name(ExtNumname(term, (int) n, numnames));
1527 }
1528 for (n = STRCOUNT; n < NUM_STRINGS(term); ++n) {
1529 dump_extended_name(ExtStrname(term, (int) n, strnames));
1530 }
1531 printf("\n");
1532 (void) printf("static char * %s[] = %s\n",
1533 name_initializer("string_ext"), L_CURL);
1534 for (n = BOOLCOUNT; n < NUM_BOOLEANS(term); ++n) {
1535 (void) printf("\t/* %3u: bool */\t%s,\n",
1536 n, name_of(ExtBoolname(term, (int) n, boolnames)));
1537 }
1538 for (n = NUMCOUNT; n < NUM_NUMBERS(term); ++n) {
1539 (void) printf("\t/* %3u: num */\t%s,\n",
1540 n, name_of(ExtNumname(term, (int) n, numnames)));
1541 }
1542 for (n = STRCOUNT; n < NUM_STRINGS(term); ++n) {
1543 (void) printf("\t/* %3u: str */\t%s,\n",
1544 n, name_of(ExtStrname(term, (int) n, strnames)));
1545 }
1546 (void) printf("%s;\n", R_CURL);
1547 }
1548 #endif
1549 }
1550
1551 /* dump C initializers for the terminal type */
1552 static void
dump_termtype(const TERMTYPE2 * term)1553 dump_termtype(const TERMTYPE2 *term)
1554 {
1555 (void) printf("\t%s\n\t\t%s,\n", L_CURL, name_initializer("alias"));
1556 (void) printf("\t\t(char *)0,\t/* pointer to string table */\n");
1557
1558 (void) printf("\t\t%s,\n", name_initializer("bool"));
1559 (void) printf("\t\t%s,\n", name_initializer("number"));
1560
1561 (void) printf("\t\t%s,\n", name_initializer("string"));
1562
1563 #if NCURSES_XNAMES
1564 (void) printf("#if NCURSES_XNAMES\n");
1565 (void) printf("\t\t(char *)0,\t/* pointer to extended string table */\n");
1566 (void) printf("\t\t%s,\t/* ...corresponding names */\n",
1567 ((NUM_BOOLEANS(term) != BOOLCOUNT)
1568 || (NUM_NUMBERS(term) != NUMCOUNT)
1569 || (NUM_STRINGS(term) != STRCOUNT))
1570 ? name_initializer("string_ext")
1571 : "(char **)0");
1572
1573 (void) printf("\t\t%d,\t\t/* count total Booleans */\n", NUM_BOOLEANS(term));
1574 (void) printf("\t\t%d,\t\t/* count total Numbers */\n", NUM_NUMBERS(term));
1575 (void) printf("\t\t%d,\t\t/* count total Strings */\n", NUM_STRINGS(term));
1576
1577 (void) printf("\t\t%d,\t\t/* count extensions to Booleans */\n",
1578 NUM_BOOLEANS(term) - BOOLCOUNT);
1579 (void) printf("\t\t%d,\t\t/* count extensions to Numbers */\n",
1580 NUM_NUMBERS(term) - NUMCOUNT);
1581 (void) printf("\t\t%d,\t\t/* count extensions to Strings */\n",
1582 NUM_STRINGS(term) - STRCOUNT);
1583
1584 (void) printf("#endif /* NCURSES_XNAMES */\n");
1585 #else
1586 (void) term;
1587 #endif /* NCURSES_XNAMES */
1588 (void) printf("\t%s\n", R_CURL);
1589 }
1590
1591 static int
optarg_to_number(void)1592 optarg_to_number(void)
1593 {
1594 char *temp = NULL;
1595 long value = strtol(optarg, &temp, 0);
1596
1597 if (temp == NULL || temp == optarg || *temp != 0) {
1598 fprintf(stderr, "Expected a number, not \"%s\"\n", optarg);
1599 ExitProgram(EXIT_FAILURE);
1600 }
1601 return (int) value;
1602 }
1603
1604 static char *
terminal_env(void)1605 terminal_env(void)
1606 {
1607 char *terminal;
1608
1609 if ((terminal = getenv("TERM")) == NULL) {
1610 (void) fprintf(stderr,
1611 "%s: environment variable TERM not set\n",
1612 _nc_progname);
1613 exit(EXIT_FAILURE);
1614 }
1615 return terminal;
1616 }
1617
1618 /*
1619 * Show the databases that infocmp knows about. The location to which it writes is
1620 */
1621 static void
show_databases(void)1622 show_databases(void)
1623 {
1624 DBDIRS state;
1625 int offset;
1626 const char *path2;
1627
1628 _nc_first_db(&state, &offset);
1629 while ((path2 = _nc_next_db(&state, &offset)) != NULL) {
1630 printf("%s\n", path2);
1631 }
1632 _nc_last_db();
1633 }
1634
1635 /***************************************************************************
1636 *
1637 * Main sequence
1638 *
1639 ***************************************************************************/
1640
1641 #if NO_LEAKS
1642 #define MAIN_LEAKS() \
1643 _nc_free_termtype2(&entries[0].tterm); \
1644 _nc_free_termtype2(&entries[1].tterm); \
1645 free(myargv); \
1646 free(tfile); \
1647 free(tname)
1648 #else
1649 #define MAIN_LEAKS() /* nothing */
1650 #endif
1651
1652 int
main(int argc,char * argv[])1653 main(int argc, char *argv[])
1654 {
1655 /* Avoid "local data >32k" error with mwcc */
1656 /* Also avoid overflowing smaller stacks on systems like AmigaOS */
1657 path *tfile = NULL;
1658 char **tname = NULL;
1659 size_t maxterms;
1660
1661 char **myargv;
1662
1663 char *firstdir, *restdir;
1664 int c;
1665 bool formatted = FALSE;
1666 bool filecompare = FALSE;
1667 int initdump = 0;
1668 bool init_analyze = FALSE;
1669 bool suppress_untranslatable = FALSE;
1670 int quickdump = 0;
1671 bool wrap_strings = FALSE;
1672
1673 /* where is the terminfo database location going to default to? */
1674 restdir = firstdir = NULL;
1675
1676 #if NCURSES_XNAMES
1677 use_extended_names(FALSE);
1678 #endif
1679 _nc_strict_bsd = 0;
1680
1681 _nc_progname = _nc_rootname(argv[0]);
1682
1683 /* make sure we have enough space to add two terminal entries */
1684 myargv = typeCalloc(char *, (size_t) (argc + 3));
1685 if (myargv == NULL)
1686 failed("myargv");
1687
1688 memcpy(myargv, argv, (sizeof(char *) * (size_t) argc));
1689 argv = myargv;
1690
1691 while ((c = getopt(argc,
1692 argv,
1693 "01A:aB:CcDdEeFfGgIiKLlnpQ:qR:rs:TtUuVv:Ww:x")) != -1) {
1694 switch (c) {
1695 case '0':
1696 mwidth = 65535;
1697 mheight = 1;
1698 break;
1699
1700 case '1':
1701 mwidth = 0;
1702 break;
1703
1704 case 'A':
1705 firstdir = optarg;
1706 break;
1707
1708 #if NCURSES_XNAMES
1709 case 'a':
1710 _nc_disable_period = TRUE;
1711 use_extended_names(TRUE);
1712 break;
1713 #endif
1714 case 'B':
1715 restdir = optarg;
1716 break;
1717
1718 case 'K':
1719 _nc_strict_bsd = 1;
1720 /* FALLTHRU */
1721 case 'C':
1722 outform = F_TERMCAP;
1723 tversion = "BSD";
1724 if (sortmode == S_DEFAULT)
1725 sortmode = S_TERMCAP;
1726 break;
1727
1728 case 'D':
1729 show_databases();
1730 ExitProgram(EXIT_SUCCESS);
1731 break;
1732
1733 case 'c':
1734 compare = C_COMMON;
1735 break;
1736
1737 case 'd':
1738 compare = C_DIFFERENCE;
1739 break;
1740
1741 case 'E':
1742 initdump |= 2;
1743 break;
1744
1745 case 'e':
1746 initdump |= 1;
1747 break;
1748
1749 case 'F':
1750 filecompare = TRUE;
1751 break;
1752
1753 case 'f':
1754 formatted = TRUE;
1755 break;
1756
1757 case 'G':
1758 numbers = 1;
1759 break;
1760
1761 case 'g':
1762 numbers = -1;
1763 break;
1764
1765 case 'I':
1766 outform = F_TERMINFO;
1767 if (sortmode == S_DEFAULT)
1768 sortmode = S_VARIABLE;
1769 tversion = NULL;
1770 break;
1771
1772 case 'i':
1773 init_analyze = TRUE;
1774 break;
1775
1776 case 'L':
1777 outform = F_VARIABLE;
1778 if (sortmode == S_DEFAULT)
1779 sortmode = S_VARIABLE;
1780 break;
1781
1782 case 'l':
1783 outform = F_TERMINFO;
1784 break;
1785
1786 case 'n':
1787 compare = C_NAND;
1788 break;
1789
1790 case 'p':
1791 ignorepads = TRUE;
1792 break;
1793
1794 case 'Q':
1795 quickdump = optarg_to_number();
1796 break;
1797
1798 case 'q':
1799 quiet = TRUE;
1800 s_absent = "-";
1801 s_cancel = "@";
1802 bool_sep = ", ";
1803 break;
1804
1805 case 'R':
1806 tversion = optarg;
1807 break;
1808
1809 case 'r':
1810 tversion = NULL;
1811 break;
1812
1813 case 's':
1814 if (*optarg == 'd')
1815 sortmode = S_NOSORT;
1816 else if (*optarg == 'i')
1817 sortmode = S_TERMINFO;
1818 else if (*optarg == 'l')
1819 sortmode = S_VARIABLE;
1820 else if (*optarg == 'c')
1821 sortmode = S_TERMCAP;
1822 else {
1823 (void) fprintf(stderr,
1824 "%s: unknown sort mode\n",
1825 _nc_progname);
1826 ExitProgram(EXIT_FAILURE);
1827 }
1828 break;
1829
1830 case 'T':
1831 limited = FALSE;
1832 break;
1833
1834 #if NCURSES_XNAMES
1835 case 't':
1836 _nc_disable_period = FALSE;
1837 suppress_untranslatable = TRUE;
1838 break;
1839 #endif
1840
1841 case 'U':
1842 literal = TRUE;
1843 break;
1844
1845 case 'u':
1846 compare = C_USEALL;
1847 break;
1848
1849 case 'V':
1850 puts(curses_version());
1851 ExitProgram(EXIT_SUCCESS);
1852
1853 case 'v':
1854 itrace = (unsigned) optarg_to_number();
1855 use_verbosity(itrace);
1856 break;
1857
1858 case 'W':
1859 wrap_strings = TRUE;
1860 break;
1861
1862 case 'w':
1863 mwidth = optarg_to_number();
1864 break;
1865
1866 #if NCURSES_XNAMES
1867 case 'x':
1868 use_extended_names(TRUE);
1869 break;
1870 #endif
1871
1872 default:
1873 usage();
1874 }
1875 }
1876
1877 maxterms = (size_t) (argc + 2 - optind);
1878 if ((tfile = typeMalloc(path, maxterms)) == NULL)
1879 failed("tfile");
1880 if ((tname = typeCalloc(char *, maxterms)) == NULL)
1881 failed("tname");
1882 if ((entries = typeCalloc(ENTRY, maxterms)) == NULL)
1883 failed("entries");
1884 #if NO_LEAKS
1885 if ((entered = typeCalloc(ENTERED, maxterms)) == NULL)
1886 failed("entered");
1887 #endif
1888
1889 if (tfile == NULL
1890 || tname == NULL
1891 || entries == NULL) {
1892 fprintf(stderr, "%s: not enough memory\n", _nc_progname);
1893 ExitProgram(EXIT_FAILURE);
1894 }
1895
1896 /* by default, sort by terminfo name */
1897 if (sortmode == S_DEFAULT)
1898 sortmode = S_TERMINFO;
1899
1900 /* make sure we have at least one terminal name to work with */
1901 if (optind >= argc)
1902 argv[argc++] = terminal_env();
1903
1904 /* if user is after a comparison, make sure we have two entries */
1905 if (compare != C_DEFAULT && optind >= argc - 1)
1906 argv[argc++] = terminal_env();
1907
1908 /* exactly one terminal name with no options means display it */
1909 /* exactly two terminal names with no options means do -d */
1910 if (compare == C_DEFAULT) {
1911 switch (argc - optind) {
1912 default:
1913 fprintf(stderr, "%s: too many names to compare\n", _nc_progname);
1914 ExitProgram(EXIT_FAILURE);
1915 case 1:
1916 break;
1917 case 2:
1918 compare = C_DIFFERENCE;
1919 break;
1920 }
1921 }
1922
1923 /* set up for display */
1924 dump_init(tversion, outform, sortmode,
1925 wrap_strings, mwidth, mheight, itrace,
1926 formatted, FALSE, quickdump);
1927
1928 if (!filecompare) {
1929 /* grab the entries */
1930 termcount = 0;
1931 for (; optind < argc; optind++) {
1932 const char *directory = termcount ? restdir : firstdir;
1933 int status;
1934
1935 tname[termcount] = argv[optind];
1936
1937 if (directory) {
1938 #if NCURSES_USE_DATABASE
1939 _nc_SPRINTF(tfile[termcount],
1940 _nc_SLIMIT(sizeof(path))
1941 "%s/" LEAF_FMT "/%s",
1942 directory,
1943 UChar(*argv[optind]), argv[optind]);
1944 if (itrace)
1945 (void) fprintf(stderr,
1946 "%s: reading entry %s from file %s\n",
1947 _nc_progname,
1948 argv[optind], tfile[termcount]);
1949
1950 status = _nc_read_file_entry(tfile[termcount],
1951 &entries[termcount].tterm);
1952 #else
1953 (void) fprintf(stderr, "%s: terminfo files not supported\n",
1954 _nc_progname);
1955 MAIN_LEAKS();
1956 ExitProgram(EXIT_FAILURE);
1957 #endif
1958 } else {
1959 if (itrace)
1960 (void) fprintf(stderr,
1961 "%s: reading entry %s from database\n",
1962 _nc_progname,
1963 tname[termcount]);
1964
1965 status = _nc_read_entry2(tname[termcount],
1966 tfile[termcount],
1967 &entries[termcount].tterm);
1968 }
1969
1970 if (status <= 0) {
1971 switch (status) {
1972 case TGETENT_NO:
1973 (void) fprintf(stderr,
1974 "%s: error: no match in terminfo"
1975 " database for terminal type"
1976 " \"%s\"\n",
1977 _nc_progname,
1978 tname[termcount]);
1979 break;
1980 case TGETENT_ERR:
1981 /*
1982 * Several database files might be checked; their
1983 * file names are not exposed via this API. The
1984 * best we can do is report how the final one
1985 * attempted failed. Also, tfile[termcount] is
1986 * deeply misleading when a hashed database is used.
1987 */
1988 (void) fprintf(stderr,
1989 "%s: error: unable to open terminfo"
1990 " database: %s\n",
1991 _nc_progname,
1992 strerror(errno));
1993 break;
1994 default:
1995 assert(0 == "unhandled _nc_read_entry2 return");
1996 }
1997 MAIN_LEAKS();
1998 ExitProgram(EXIT_FAILURE);
1999 }
2000 repair_acsc(&entries[termcount].tterm);
2001 termcount++;
2002 }
2003
2004 #if NCURSES_XNAMES
2005 if (termcount > 1) {
2006 /*
2007 * User-defined capabilities in different terminal descriptions
2008 * may have the same name/type but different indices. Line up
2009 * the names to use comparable indices. We may have more than two
2010 * entries to compare when processing the "-u" option.
2011 */
2012 for (c = 1; c < termcount; ++c)
2013 _nc_align_termtype(&entries[c].tterm, &entries[0].tterm);
2014 }
2015 #endif
2016
2017 /* dump as C initializer for the terminal type */
2018 if (initdump) {
2019 if (initdump & 1)
2020 dump_termtype(&entries[0].tterm);
2021 if (initdump & 2)
2022 dump_initializers(&entries[0].tterm);
2023 }
2024
2025 /* analyze the init strings */
2026 else if (init_analyze) {
2027 #undef CUR
2028 #define CUR entries[0].tterm.
2029 analyze_string("is1", init_1string, &entries[0].tterm);
2030 analyze_string("is2", init_2string, &entries[0].tterm);
2031 analyze_string("is3", init_3string, &entries[0].tterm);
2032 analyze_string("rs1", reset_1string, &entries[0].tterm);
2033 analyze_string("rs2", reset_2string, &entries[0].tterm);
2034 analyze_string("rs3", reset_3string, &entries[0].tterm);
2035 analyze_string("smcup", enter_ca_mode, &entries[0].tterm);
2036 analyze_string("rmcup", exit_ca_mode, &entries[0].tterm);
2037 analyze_string("smkx", keypad_xmit, &entries[0].tterm);
2038 analyze_string("rmkx", keypad_local, &entries[0].tterm);
2039 #undef CUR
2040 } else {
2041 int i;
2042 int len;
2043
2044 /*
2045 * Here's where the real work gets done
2046 */
2047 switch (compare) {
2048 case C_DEFAULT:
2049 if (itrace)
2050 (void) fprintf(stderr,
2051 "%s: about to dump %s\n",
2052 _nc_progname,
2053 tname[0]);
2054 if (!quiet)
2055 (void)
2056 printf("#\tReconstructed via %s from file: %s\n",
2057 _nc_progname, tfile[0]);
2058 dump_entry(&entries[0].tterm,
2059 suppress_untranslatable,
2060 limited,
2061 numbers,
2062 NULL);
2063 len = show_entry();
2064 if (itrace)
2065 (void) fprintf(stderr, "%s: length %d\n", _nc_progname, len);
2066 break;
2067
2068 case C_DIFFERENCE:
2069 show_comparing(tname);
2070 compare_entry(compare_predicate, &entries->tterm, quiet);
2071 break;
2072
2073 case C_COMMON:
2074 show_comparing(tname);
2075 compare_entry(compare_predicate, &entries->tterm, quiet);
2076 break;
2077
2078 case C_NAND:
2079 show_comparing(tname);
2080 compare_entry(compare_predicate, &entries->tterm, quiet);
2081 break;
2082
2083 case C_USEALL:
2084 if (itrace)
2085 (void) fprintf(stderr, "%s: dumping use entry\n", _nc_progname);
2086 dump_entry(&entries[0].tterm,
2087 suppress_untranslatable,
2088 limited,
2089 numbers,
2090 use_predicate);
2091 for (i = 1; i < termcount; i++)
2092 dump_uses(tname[i], !(outform == F_TERMCAP
2093 || outform == F_TCONVERR));
2094 len = show_entry();
2095 if (itrace)
2096 (void) fprintf(stderr, "%s: length %d\n", _nc_progname, len);
2097 break;
2098 }
2099 }
2100 } else if (compare == C_USEALL) {
2101 (void) fprintf(stderr, "Sorry, -u doesn't work with -F\n");
2102 } else if (compare == C_DEFAULT) {
2103 (void) fprintf(stderr,
2104 "Use `" ACTUAL_TIC " -[CI] <file>' for this.\n");
2105 } else if (argc - optind != 2) {
2106 (void) fprintf(stderr,
2107 "File comparison needs exactly two file arguments.\n");
2108 } else {
2109 file_comparison(argc - optind, argv + optind);
2110 }
2111
2112 MAIN_LEAKS();
2113 ExitProgram(EXIT_SUCCESS);
2114 }
2115
2116 /* infocmp.c ends here */
2117