ncurses 6.0 - patch 20150808
[ncurses.git] / test / demo_termcap.c
1 /****************************************************************************
2  * Copyright (c) 2005-2014,2015 Free Software Foundation, Inc.              *
3  *                                                                          *
4  * Permission is hereby granted, free of charge, to any person obtaining a  *
5  * copy of this software and associated documentation files (the            *
6  * "Software"), to deal in the Software without restriction, including      *
7  * without limitation the rights to use, copy, modify, merge, publish,      *
8  * distribute, distribute with modifications, sublicense, and/or sell       *
9  * copies of the Software, and to permit persons to whom the Software is    *
10  * furnished to do so, subject to the following conditions:                 *
11  *                                                                          *
12  * The above copyright notice and this permission notice shall be included  *
13  * in all copies or substantial portions of the Software.                   *
14  *                                                                          *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
16  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
17  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
18  * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
19  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
20  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
21  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
22  *                                                                          *
23  * Except as contained in this notice, the name(s) of the above copyright   *
24  * holders shall not be used in advertising or otherwise to promote the     *
25  * sale, use or other dealings in this Software without prior written       *
26  * authorization.                                                           *
27  ****************************************************************************/
28
29 /*
30  * Author: Thomas E. Dickey
31  *
32  * $Id: demo_termcap.c,v 1.48 2015/08/08 20:25:39 tom Exp $
33  *
34  * A simple demo of the termcap interface.
35  */
36 #define USE_TINFO
37 #include <test.priv.h>
38 #include <sys/stat.h>
39
40 #if NCURSES_XNAMES
41 #if HAVE_TERM_ENTRY_H
42 #include <term_entry.h>
43 #else
44 #undef NCURSES_XNAMES
45 #define NCURSES_XNAMES 0
46 #endif
47 #endif
48
49 #ifdef NCURSES_VERSION
50 #include <termcap.h>
51 #endif
52
53 static void
54 failed(const char *msg)
55 {
56     fprintf(stderr, "%s\n", msg);
57     ExitProgram(EXIT_FAILURE);
58 }
59
60 #if HAVE_TGETENT
61
62 #if defined(HAVE_CURSES_DATA_BOOLNAMES) || defined(DECL_CURSES_DATA_BOOLNAMES)
63 #define USE_CODE_LISTS 1
64 #else
65 #define USE_CODE_LISTS 0
66 #endif
67
68 #define FCOLS 8
69 #define FNAME(type) "%s %-*s = ", #type, FCOLS
70
71 static bool b_opt = FALSE;
72 static bool n_opt = FALSE;
73 static bool s_opt = FALSE;
74 static bool q_opt = FALSE;
75 static bool x_opt = FALSE;
76 static bool y_opt = FALSE;
77
78 static char *d_opt;
79 static char *e_opt;
80 static char **db_list;
81 static int db_item;
82
83 static char *my_blob;
84 static char **my_boolcodes;
85 static char **my_numcodes;
86 static char **my_numvalues;
87 static char **my_strcodes;
88 static char **my_strvalues;
89
90 static long total_values;
91 static long total_b_values;
92 static long total_n_values;
93 static long total_s_values;
94
95 #define isCapName(c) (isgraph(c) && strchr("^=:\\", c) == 0)
96 #define EachCapName(n) n = 33; n < 127; ++n
97
98 static char *
99 make_dbitem(char *p, char *q)
100 {
101     char *result = malloc(strlen(e_opt) + 2 + (size_t) (p - q));
102     sprintf(result, "%s=%.*s", e_opt, (int) (p - q), q);
103     return result;
104 }
105
106 static void
107 make_dblist(void)
108 {
109     if (d_opt && e_opt) {
110         int pass;
111
112         for (pass = 0; pass < 2; ++pass) {
113             char *p, *q;
114             size_t count = 0;
115
116             for (p = q = d_opt; *p != '\0'; ++p) {
117                 if (*p == ':') {
118                     if (p != q + 1) {
119                         if (pass) {
120                             db_list[count] = make_dbitem(p, q);
121                         }
122                         count++;
123                     }
124                     q = p + 1;
125                 }
126             }
127             if (p != q + 1) {
128                 if (pass) {
129                     db_list[count] = make_dbitem(p, q);
130                 }
131                 count++;
132             }
133             if (!pass) {
134                 db_list = typeCalloc(char *, count + 1);
135             }
136         }
137     }
138 }
139
140 static char *
141 next_dbitem(void)
142 {
143     char *result = 0;
144
145     if (db_list) {
146         if ((result = db_list[db_item]) == 0) {
147             db_item = 0;
148             result = db_list[0];
149         } else {
150             db_item++;
151         }
152     }
153     printf("** %s\n", result);
154     return result;
155 }
156
157 static void
158 free_dblist(void)
159 {
160     if (db_list) {
161         int n;
162         for (n = 0; db_list[n]; ++n)
163             free(db_list[n]);
164         free(db_list);
165         db_list = 0;
166     }
167 }
168
169 static void
170 show_string(const char *name, const char *value)
171 {
172     printf(FNAME(str), name);
173     if (value == ((char *) -1)) {
174         printf("CANCELLED");
175     } else if (value == ((char *) 0)) {
176         printf("ABSENT");
177     } else {
178         while (*value != 0) {
179             int ch = UChar(*value++);
180             switch (ch) {
181             case '\177':
182                 fputs("^?", stdout);
183                 break;
184             case '\033':
185                 fputs("\\E", stdout);
186                 break;
187             case '\b':
188                 fputs("\\b", stdout);
189                 break;
190             case '\f':
191                 fputs("\\f", stdout);
192                 break;
193             case '\n':
194                 fputs("\\n", stdout);
195                 break;
196             case '\r':
197                 fputs("\\r", stdout);
198                 break;
199             case ' ':
200                 fputs("\\s", stdout);
201                 break;
202             case '\t':
203                 fputs("\\t", stdout);
204                 break;
205             case '^':
206                 fputs("\\^", stdout);
207                 break;
208             case ':':
209                 fputs("\\072", stdout);
210                 break;
211             case '\\':
212                 fputs("\\\\", stdout);
213                 break;
214             default:
215                 if (isgraph(ch))
216                     fputc(ch, stdout);
217                 else if (ch < 32)
218                     printf("^%c", ch + '@');
219                 else
220                     printf("\\%03o", ch);
221                 break;
222             }
223         }
224     }
225     printf("\n");
226 }
227
228 static void
229 show_number(const char *name, int value)
230 {
231     printf(FNAME(num), name);
232     printf(" %d\n", value);
233 }
234
235 static void
236 dumpit(NCURSES_CONST char *cap)
237 {
238     /*
239      * One of the limitations of the termcap interface is that the library
240      * cannot determine the size of the buffer passed via tgetstr(), nor the
241      * amount of space remaining.  This demo simply reuses the whole buffer
242      * for each call; a normal termcap application would try to use the buffer
243      * to hold all of the strings extracted from the terminal entry.
244      */
245     char area[1024], *ap = area;
246     char *str;
247     int num;
248
249     if ((str = tgetstr(cap, &ap)) != 0) {
250         total_values++;
251         total_s_values++;
252         if (!q_opt) {
253             /*
254              * Note that the strings returned are mostly terminfo format, since
255              * ncurses does not convert except for a handful of special cases.
256              */
257             show_string(cap, str);
258         }
259     } else if ((num = tgetnum(cap)) >= 0) {
260         total_values++;
261         total_n_values++;
262         if (!q_opt) {
263             show_number(cap, num);
264         }
265     } else if (tgetflag(cap) > 0) {
266         total_values++;
267         total_b_values++;
268         if (!q_opt) {
269             printf(FNAME(flg), cap);
270             printf("%s\n", "true");
271         }
272     }
273
274     if (!q_opt)
275         fflush(stdout);
276 }
277
278 static void
279 brute_force(const char *name)
280 {
281     char buffer[1024];
282
283     if (db_list) {
284         putenv(next_dbitem());
285     }
286     if (!q_opt)
287         printf("Terminal type \"%s\"\n", name);
288     if (tgetent(buffer, name) >= 0) {
289         char cap[3];
290         int c1, c2;
291
292         cap[2] = 0;
293         for (EachCapName(c1)) {
294             cap[0] = (char) c1;
295             if (isCapName(c1)) {
296                 for (EachCapName(c2)) {
297                     cap[1] = (char) c2;
298                     if (isCapName(c2)) {
299                         dumpit(cap);
300                     }
301                 }
302             }
303         }
304     }
305 }
306
307 #if NCURSES_XNAMES
308 static void
309 dump_xname(NCURSES_CONST char *cap)
310 {
311     if (strlen(cap) == 2)
312         dumpit(cap);
313 }
314 #endif
315
316 static void
317 demo_termcap(NCURSES_CONST char *name)
318 {
319     unsigned n;
320     NCURSES_CONST char *cap;
321     char buffer[1024];
322
323     if (db_list) {
324         putenv(next_dbitem());
325     }
326     if (!q_opt)
327         printf("Terminal type \"%s\"\n", name);
328     if (tgetent(buffer, name) >= 0) {
329
330         if (b_opt) {
331             for (n = 0;; ++n) {
332                 cap = my_boolcodes[n];
333                 if (cap == 0)
334                     break;
335                 dumpit(cap);
336             }
337         }
338
339         if (n_opt) {
340             for (n = 0;; ++n) {
341                 cap = my_numcodes[n];
342                 if (cap == 0)
343                     break;
344                 dumpit(cap);
345             }
346         }
347
348         if (s_opt) {
349             for (n = 0;; ++n) {
350                 cap = my_strcodes[n];
351                 if (cap == 0)
352                     break;
353                 dumpit(cap);
354             }
355         }
356 #ifdef NCURSES_VERSION
357         if (x_opt && (my_blob == 0) && y_opt) {
358 #if NCURSES_XNAMES
359             TERMTYPE *term = &(cur_term->type);
360             if (term != 0
361                 && ((NUM_BOOLEANS(term) != BOOLCOUNT)
362                     || (NUM_NUMBERS(term) != NUMCOUNT)
363                     || (NUM_STRINGS(term) != STRCOUNT))) {
364                 for (n = BOOLCOUNT; n < NUM_BOOLEANS(term); ++n) {
365                     dump_xname(ExtBoolname(term, (int) n, boolnames));
366                 }
367                 for (n = NUMCOUNT; n < NUM_NUMBERS(term); ++n) {
368                     dump_xname(ExtNumname(term, (int) n, numnames));
369                 }
370                 for (n = STRCOUNT; n < NUM_STRINGS(term); ++n) {
371                     dump_xname(ExtStrname(term, (int) n, strnames));
372                 }
373             }
374 #endif
375         }
376 #endif
377     }
378 }
379
380 typedef enum {
381     pDefault = 0
382     ,pComment
383     ,pDescription
384     ,pEscaped
385     ,pNewline
386     ,pName
387     ,pNumber
388     ,pString
389 } STATE;
390
391 static void
392 parse_description(const char *input_name)
393 {
394     static char empty[1];
395
396     FILE *fp;
397     struct stat sb;
398     size_t count_bools = 0;
399     size_t count_nums = 0;
400     size_t count_strs = 0;
401     size_t len;
402     size_t j, k;
403     STATE state;
404
405     if (stat(input_name, &sb) != 0
406         || (sb.st_mode & S_IFMT) != S_IFREG) {
407         failed("input is not a file");
408     }
409
410     if (sb.st_size == 0) {
411         failed("input is empty");
412     }
413
414     /*
415      * None of the arrays could be larger than the input-file, and since it
416      * is small, just allocate the maximum for simplicity.
417      */
418     if ((my_blob = malloc((size_t) sb.st_size + 1)) == 0 ||
419         (my_boolcodes = typeCalloc(char *, sb.st_size)) == 0 ||
420           (my_numcodes = typeCalloc(char *, sb.st_size)) == 0 ||
421           (my_numvalues = typeCalloc(char *, sb.st_size)) == 0 ||
422           (my_strcodes = typeCalloc(char *, sb.st_size)) == 0 ||
423           (my_strvalues = typeCalloc(char *, sb.st_size)) == 0) {
424         failed("cannot allocate memory for input-file");
425     }
426
427     if ((fp = fopen(input_name, "r")) == 0)
428         failed("cannot open input-file");
429     len = fread(my_blob, sizeof(char), (size_t) sb.st_size, fp);
430     my_blob[sb.st_size] = '\0';
431     fclose(fp);
432
433     /*
434      * First, get rid of comments and escaped newlines, as well as repeated
435      * colons to construct a canonical entry.
436      *
437      * FIXME: actually this should make an additional pass just to strip
438      * comment-lines and escaped newlines.  But it is workable for infocmp
439      * output.
440      */
441     state = pNewline;
442     for (j = k = 0; j < len; ++j) {
443         int ch = my_blob[j];
444         if (ch == '\t') {
445             ch = ' ';
446         }
447         switch (state) {
448         case pNewline:
449             if (ch == ' ') {
450                 continue;
451             }
452             if (ch == '#') {
453                 state = pComment;
454                 continue;
455             }
456             state = pDefault;
457             /* FALLTHRU */
458         case pDefault:
459             switch (ch) {
460             case '|':
461                 state = pDescription;
462                 continue;
463             case '\\':
464                 state = pEscaped;
465                 continue;
466             case '\n':
467                 state = pNewline;
468                 continue;
469             case ' ':
470             case ':':
471                 break;
472             default:
473                 state = pName;
474                 break;
475             }
476             my_blob[k++] = (char) ch;
477             break;
478         case pComment:
479             if (ch == '\n')
480                 state = pNewline;
481             break;
482         case pDescription:
483             switch (ch) {
484             case ':':
485                 state = pDefault;
486                 break;
487             case '\n':
488                 state = pNewline;
489                 break;
490             }
491             break;
492         case pEscaped:
493             if (ch != '\n') {
494                 my_blob[k++] = (char) ch;
495                 state = pDefault;
496             } else {
497                 state = pNewline;
498             }
499             break;
500         case pName:
501             switch (ch) {
502             case '\n':
503                 state = pNewline;
504                 continue;
505             case ' ':
506             case ':':
507                 state = pDefault;
508                 break;
509             case '#':
510                 state = pNumber;
511                 break;
512             case '|':
513                 state = pDescription;
514                 continue;
515             }
516             my_blob[k++] = (char) ch;
517             break;
518         case pNumber:
519             switch (ch) {
520             case '\n':
521                 state = pNewline;
522                 continue;
523             case ':':
524                 state = pDefault;
525                 break;
526             case ' ':
527                 state = pDefault;
528                 continue;
529             }
530             my_blob[k++] = (char) ch;
531             break;
532         case pString:
533             switch (ch) {
534             case '\\':
535                 if (my_blob[j + 1] == '\0') {
536                     state = pDefault;
537                     continue;
538                 }
539                 break;
540             case '\n':
541                 state = pNewline;
542                 continue;
543             case ':':
544                 state = pDefault;
545                 break;
546             }
547             my_blob[k++] = (char) ch;
548             break;
549         default:
550             /* not used */
551             break;
552         }
553     }
554     my_blob[k] = '\0';
555
556     /*
557      * Then, parse what's left, making indexes of the names and values.
558      */
559     state = pDefault;
560     for (j = 0; my_blob[j] != '\0'; ++j) {
561         switch (state) {
562         case pDefault:
563             switch (my_blob[j]) {
564             case '\\':
565                 state = pEscaped;
566                 break;
567             case ':':
568                 my_blob[j] = '\0';
569                 if (my_blob[j + 1] != '\0' && my_blob[j + 1] != ':')
570                     state = pName;
571                 break;
572             case ' ':
573                 break;
574             default:
575                 break;
576             }
577         case pEscaped:
578             break;
579         case pName:
580             state = pDefault;
581             /*
582              * Commented-out capabilities might be accessible (they are in
583              * ncurses).
584              */
585             if (my_blob[j] == '.' && my_blob[j + 1] == '.') {
586                 j += 2;
587             }
588             if (my_blob[j + 1] != '\0') {
589                 switch (my_blob[j + 2]) {
590                 case '#':
591                     my_numvalues[count_nums] = &my_blob[j + 3];
592                     my_numcodes[count_nums++] = &my_blob[j];
593                     my_blob[j + 2] = '\0';
594                     state = pNumber;
595                     j += 2;
596                     break;
597                 case '=':
598                     my_strvalues[count_strs] = &my_blob[j + 3];
599                     my_strcodes[count_strs++] = &my_blob[j];
600                     my_blob[j + 2] = '\0';
601                     state = pString;
602                     j += 2;
603                     break;
604                 default:
605                     if (my_blob[j + 2] == '@') {
606                         /*
607                          * We cannot get the type for a cancelled item
608                          * directly, but can infer it assuming the input
609                          * came from infocmp, which puts the data in a
610                          * known order.
611                          */
612                         if (count_strs) {
613                             my_strvalues[count_strs] = empty;
614                             my_strcodes[count_strs++] = &my_blob[j];
615                         } else if (count_nums) {
616                             my_numvalues[count_nums] = empty;
617                             my_numcodes[count_nums++] = &my_blob[j];
618                         } else {
619                             my_boolcodes[count_bools++] = &my_blob[j];
620                         }
621                     } else {
622                         my_boolcodes[count_bools++] = &my_blob[j];
623                     }
624                     j++;
625                     break;
626                 }
627             }
628             break;
629         case pNumber:
630             if (!isdigit(UChar(my_blob[j]))) {
631                 --j;
632                 state = pDefault;
633             }
634             break;
635         case pString:
636             switch (my_blob[j]) {
637             case '\\':
638                 if (my_blob[j + 1] == '\0') {
639                     state = pDefault;
640                     continue;
641                 } else {
642                     ++j;
643                 }
644                 break;
645             case '\n':
646                 state = pNewline;
647                 continue;
648             case ':':
649                 --j;
650                 state = pDefault;
651                 break;
652             }
653             break;
654         case pNewline:
655         case pComment:
656         case pDescription:
657         default:
658             break;
659         }
660     }
661     my_boolcodes[count_bools] = 0;
662     my_numcodes[count_nums] = 0;
663     my_numvalues[count_nums] = 0;
664     my_strcodes[count_strs] = 0;
665     my_strvalues[count_strs] = 0;
666
667 #if 0
668     printf("bools:%d\n", (int) count_bools);
669     for (j = 0; my_boolcodes[j]; ++j)
670         printf("%5d:%s\n", (int) j, my_boolcodes[j]);
671
672     printf("numbers:%d\n", (int) count_nums);
673     for (j = 0; my_numcodes[j]; ++j)
674         printf("%5d:%s(%s)\n", (int) j, my_numcodes[j], my_numvalues[j]);
675
676     printf("strings:%d\n", (int) count_strs);
677     for (j = 0; my_strcodes[j]; ++j)
678         printf("%5d:%s(%s)\n", (int) j, my_strcodes[j], my_strvalues[j]);
679 #endif
680 }
681
682 #if USE_CODE_LISTS
683 static char **
684 copy_code_list(NCURSES_CONST char *const *list)
685 {
686     int pass;
687     size_t count;
688     size_t length = 1;
689     char **result = 0;
690     char *blob = 0;
691     char *unused = 0;
692
693     for (pass = 0; pass < 2; ++pass) {
694         for (count = 0; list[count] != 0; ++count) {
695             size_t chunk = strlen(list[count]) + 1;
696             if (pass == 0) {
697                 length += chunk;
698             } else {
699                 result[count] = unused;
700                 strcpy(unused, list[count]);
701                 unused += chunk;
702             }
703         }
704         if (pass == 0) {
705             blob = malloc(length);
706             result = typeCalloc(char *, count + 1);
707             unused = blob;
708             if (blob == 0 || result == 0)
709                 failed("copy_code_list failed");
710         }
711     }
712
713     return result;
714 }
715 #endif
716
717 static void
718 usage(void)
719 {
720     static const char *msg[] =
721     {
722         "Usage: demo_termcap [options] [terminal]",
723         "",
724         "If no options are given, print all (boolean, numeric, string)",
725         "capabilities for the given terminal, using short names.",
726         "",
727         "Options:",
728         " -a       try all names, print capabilities found",
729         " -b       print boolean-capabilities",
730         " -d LIST  colon-separated list of databases to use",
731         " -e NAME  environment variable to set with -d option",
732         " -i NAME  terminal description to use as names for \"-a\" option, etc.",
733         " -n       print numeric-capabilities",
734         " -q       quiet (prints only counts)",
735         " -r COUNT repeat for given count",
736         " -s       print string-capabilities",
737         " -v       print termcap-variables",
738 #ifdef NCURSES_VERSION
739         " -x       print extended capabilities",
740 #endif
741     };
742     unsigned n;
743     for (n = 0; n < SIZEOF(msg); ++n) {
744         fprintf(stderr, "%s\n", msg[n]);
745     }
746     ExitProgram(EXIT_FAILURE);
747 }
748
749 int
750 main(int argc, char *argv[])
751 {
752     int n;
753     char *name;
754     bool a_opt = FALSE;
755     bool v_opt = FALSE;
756     char *input_name = 0;
757
758     int repeat;
759     int r_opt = 1;
760
761     while ((n = getopt(argc, argv, "abd:e:i:nqr:svxy")) != -1) {
762         switch (n) {
763         case 'a':
764             a_opt = TRUE;
765             break;
766         case 'b':
767             b_opt = TRUE;
768             break;
769         case 'd':
770             d_opt = optarg;
771             break;
772         case 'e':
773             e_opt = optarg;
774             break;
775         case 'i':
776             input_name = optarg;
777             break;
778         case 'n':
779             n_opt = TRUE;
780             break;
781         case 'q':
782             q_opt = TRUE;
783             break;
784         case 'r':
785             if ((r_opt = atoi(optarg)) <= 0)
786                 usage();
787             break;
788         case 's':
789             s_opt = TRUE;
790             break;
791         case 'v':
792             v_opt = TRUE;
793             break;
794 #if NCURSES_XNAMES
795         case 'x':
796             x_opt = TRUE;
797             break;
798         case 'y':
799             y_opt = TRUE;
800             x_opt = TRUE;
801             break;
802 #endif
803         default:
804             usage();
805             break;
806         }
807     }
808
809 #if HAVE_USE_EXTENDED_NAMES
810     use_extended_names(x_opt);
811 #endif
812
813     if (!(b_opt || n_opt || s_opt)) {
814         b_opt = TRUE;
815         n_opt = TRUE;
816         s_opt = TRUE;
817     }
818
819     make_dblist();
820
821     if (a_opt) {
822         for (repeat = 0; repeat < r_opt; ++repeat) {
823             if (optind < argc) {
824                 for (n = optind; n < argc; ++n) {
825                     brute_force(argv[n]);
826                 }
827             } else if ((name = getenv("TERM")) != 0) {
828                 brute_force(name);
829             } else {
830                 static char dumb[] = "dumb";
831                 brute_force(dumb);
832             }
833         }
834     } else {
835         if (input_name != 0) {
836             parse_description(input_name);
837         }
838 #if USE_CODE_LISTS
839         else {
840             my_boolcodes = copy_code_list(boolcodes);
841             my_numcodes = copy_code_list(numcodes);
842             my_strcodes = copy_code_list(strcodes);
843         }
844 #else
845         else {
846             failed("no capability-lists available (use -i option)");
847         }
848 #endif /* USE_CODE_LISTS */
849         for (repeat = 0; repeat < r_opt; ++repeat) {
850             if (optind < argc) {
851                 for (n = optind; n < argc; ++n) {
852                     demo_termcap(argv[n]);
853                 }
854             } else if ((name = getenv("TERM")) != 0) {
855                 demo_termcap(name);
856             } else {
857                 static char dumb[] = "dumb";
858                 demo_termcap(dumb);
859             }
860         }
861     }
862
863     printf("%ld values (%ld booleans, %ld numbers, %ld strings)\n",
864            total_values, total_b_values, total_n_values, total_s_values);
865
866 #if defined(NCURSES_VERSION) || defined(HAVE_CURSES_DATA_OSPEED)
867     if (v_opt) {
868         show_number("PC", PC);
869         show_string("UP", UP);
870         show_string("BC", BC);
871         show_number("ospeed", ospeed);
872     }
873 #endif
874
875     free_dblist();
876
877     ExitProgram(EXIT_SUCCESS);
878 }
879
880 #else
881 int
882 main(int argc GCC_UNUSED,
883      char *argv[]GCC_UNUSED)
884 {
885     failed("This program requires termcap");
886 }
887 #endif