4924d2c1cccf32da6266f958d49c028aae7965a8
[ncurses.git] / progs / toe.c
1 /****************************************************************************
2  * Copyright (c) 1998-2011,2012 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: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995               *
31  *     and: Eric S. Raymond <esr@snark.thyrsus.com>                         *
32  *     and: Thomas E. Dickey                        1996-on                 *
33  ****************************************************************************/
34
35 /*
36  *      toe.c --- table of entries report generator
37  */
38
39 #include <progs.priv.h>
40
41 #include <sys/stat.h>
42
43 #if USE_HASHED_DB
44 #include <hashed_db.h>
45 #endif
46
47 MODULE_ID("$Id: toe.c,v 1.68 2012/07/21 22:55:59 tom Exp $")
48
49 #define isDotname(name) (!strcmp(name, ".") || !strcmp(name, ".."))
50
51 typedef struct {
52     int db_index;
53     unsigned long checksum;
54     char *term_name;
55     char *description;
56 } TERMDATA;
57
58 const char *_nc_progname;
59
60 static TERMDATA *ptr_termdata;  /* array of terminal data */
61 static size_t use_termdata;     /* actual usage in ptr_termdata[] */
62 static size_t len_termdata;     /* allocated size of ptr_termdata[] */
63
64 #if NO_LEAKS
65 #undef ExitProgram
66 static void ExitProgram(int code) GCC_NORETURN;
67 static void
68 ExitProgram(int code)
69 {
70     _nc_free_entries(_nc_head);
71     _nc_free_tic(code);
72 }
73 #endif
74
75 static void failed(const char *) GCC_NORETURN;
76
77 static void
78 failed(const char *msg)
79 {
80     perror(msg);
81     ExitProgram(EXIT_FAILURE);
82 }
83
84 static char *
85 strmalloc(const char *value)
86 {
87     char *result = strdup(value);
88     if (result == 0) {
89         failed("strmalloc");
90     }
91     return result;
92 }
93
94 static TERMDATA *
95 new_termdata(void)
96 {
97     size_t want = use_termdata + 1;
98
99     if (want >= len_termdata) {
100         len_termdata = (2 * want) + 10;
101         ptr_termdata = typeRealloc(TERMDATA, len_termdata, ptr_termdata);
102     }
103
104     return ptr_termdata + use_termdata++;
105 }
106
107 static int
108 compare_termdata(const void *a, const void *b)
109 {
110     const TERMDATA *p = (const TERMDATA *) a;
111     const TERMDATA *q = (const TERMDATA *) b;
112     int result = strcmp(p->term_name, q->term_name);
113
114     if (result == 0) {
115         result = (p->db_index - q->db_index);
116     }
117     return result;
118 }
119
120 /*
121  * Sort the array of TERMDATA and print it.  If more than one database is being
122  * reported, add a column to show which database has a given entry.
123  */
124 static void
125 show_termdata(int eargc, char **eargv)
126 {
127     int j, k;
128     size_t n;
129
130     if (use_termdata) {
131         if (eargc > 1) {
132             for (j = 0; j < eargc; ++j) {
133                 for (k = 0; k <= j; ++k) {
134                     printf("--");
135                 }
136                 printf("> ");
137                 printf("%s\n", eargv[j]);
138             }
139         }
140         if (use_termdata > 1)
141             qsort(ptr_termdata, use_termdata, sizeof(TERMDATA), compare_termdata);
142         for (n = 0; n < use_termdata; ++n) {
143
144             /*
145              * If there is more than one database, show how they differ.
146              */
147             if (eargc > 1) {
148                 unsigned long check = 0;
149                 k = 0;
150                 for (;;) {
151                     for (; k < ptr_termdata[n].db_index; ++k) {
152                         printf("--");
153                     }
154
155                     /*
156                      * If this is the first entry, or its checksum differs
157                      * from the first entry's checksum, print "*". Otherwise
158                      * it looks enough like a duplicate to print "+".
159                      */
160                     printf("%c-", ((check == 0
161                                     || (check != ptr_termdata[n].checksum))
162                                    ? '*'
163                                    : '+'));
164                     check = ptr_termdata[n].checksum;
165
166                     ++k;
167                     if ((n + 1) >= use_termdata
168                         || strcmp(ptr_termdata[n].term_name,
169                                   ptr_termdata[n + 1].term_name)) {
170                         break;
171                     }
172                     ++n;
173                 }
174                 for (; k < eargc; ++k) {
175                     printf("--");
176                 }
177                 printf(":\t");
178             }
179
180             (void) printf("%-10s\t%s\n",
181                           ptr_termdata[n].term_name,
182                           ptr_termdata[n].description);
183         }
184     }
185 }
186
187 static void
188 free_termdata(void)
189 {
190     if (ptr_termdata != 0) {
191         while (use_termdata != 0) {
192             --use_termdata;
193             free(ptr_termdata[use_termdata].term_name);
194             free(ptr_termdata[use_termdata].description);
195         }
196         free(ptr_termdata);
197         ptr_termdata = 0;
198     }
199     use_termdata = 0;
200     len_termdata = 0;
201 }
202
203 static char **
204 allocArgv(size_t count)
205 {
206     char **result = typeCalloc(char *, count + 1);
207     if (result == 0)
208         failed("realloc eargv");
209
210     assert(result != 0);
211     return result;
212 }
213
214 static void
215 freeArgv(char **argv)
216 {
217     if (argv) {
218         int count = 0;
219         while (argv[count]) {
220             free(argv[count++]);
221         }
222         free(argv);
223     }
224 }
225
226 #if USE_HASHED_DB
227 static bool
228 make_db_name(char *dst, const char *src, unsigned limit)
229 {
230     static const char suffix[] = DBM_SUFFIX;
231
232     bool result = FALSE;
233     unsigned lens = sizeof(suffix) - 1;
234     unsigned size = strlen(src);
235     unsigned need = lens + size;
236
237     if (need <= limit) {
238         if (size >= lens
239             && !strcmp(src + size - lens, suffix)) {
240             _nc_STRCPY(dst, src, PATH_MAX);
241         } else {
242             _nc_SPRINTF(dst, _nc_SLIMIT(PATH_MAX) "%s%s", src, suffix);
243         }
244         result = TRUE;
245     }
246     return result;
247 }
248 #endif
249
250 typedef void (DescHook) (int /* db_index */ ,
251                          int /* db_limit */ ,
252                          const char * /* term_name */ ,
253                          TERMTYPE * /* term */ );
254
255 static const char *
256 term_description(TERMTYPE *tp)
257 {
258     const char *desc;
259
260     if ((desc = strrchr(tp->term_names, '|')) == 0 || *++desc == '\0')
261         desc = "(No description)";
262
263     return desc;
264 }
265
266 /* display a description for the type */
267 static void
268 deschook(int db_index, int db_limit, const char *term_name, TERMTYPE *tp)
269 {
270     (void) db_index;
271     (void) db_limit;
272     (void) printf("%-10s\t%s\n", term_name, term_description(tp));
273 }
274
275 static unsigned long
276 string_sum(const char *value)
277 {
278     unsigned long result = 0;
279
280     if ((intptr_t) value == (intptr_t) (-1)) {
281         result = ~result;
282     } else if (value) {
283         while (*value) {
284             result += UChar(*value);
285             ++value;
286         }
287     }
288     return result;
289 }
290
291 static unsigned long
292 checksum_of(TERMTYPE *tp)
293 {
294     unsigned long result = string_sum(tp->term_names);
295     unsigned i;
296
297     for (i = 0; i < NUM_BOOLEANS(tp); i++) {
298         result += (unsigned long) (tp->Booleans[i]);
299     }
300     for (i = 0; i < NUM_NUMBERS(tp); i++) {
301         result += (unsigned long) (tp->Numbers[i]);
302     }
303     for (i = 0; i < NUM_STRINGS(tp); i++) {
304         result += string_sum(tp->Strings[i]);
305     }
306     return result;
307 }
308
309 /* collect data, to sort before display */
310 static void
311 sorthook(int db_index, int db_limit, const char *term_name, TERMTYPE *tp)
312 {
313     TERMDATA *data = new_termdata();
314
315     data->db_index = db_index;
316     data->checksum = ((db_limit > 1) ? checksum_of(tp) : 0);
317     data->term_name = strmalloc(term_name);
318     data->description = strmalloc(term_description(tp));
319 }
320
321 #if USE_TERMCAP
322 static void
323 show_termcap(int db_index, int db_limit, char *buffer, DescHook hook)
324 {
325     TERMTYPE data;
326     char *next = strchr(buffer, ':');
327     char *last;
328     char *list = buffer;
329
330     if (next)
331         *next = '\0';
332
333     last = strrchr(buffer, '|');
334     if (last)
335         ++last;
336
337     memset(&data, 0, sizeof(data));
338     data.term_names = strmalloc(buffer);
339     while ((next = strtok(list, "|")) != 0) {
340         if (next != last)
341             hook(db_index, db_limit, next, &data);
342         list = 0;
343     }
344     free(data.term_names);
345 }
346 #endif
347
348 #if USE_DATABASE
349 static char *
350 copy_entryname(DIRENT * src)
351 {
352     size_t len = NAMLEN(src);
353     char *result = malloc(len + 1);
354     if (result == 0)
355         failed("copy entryname");
356     memcpy(result, src->d_name, len);
357     result[len] = '\0';
358
359     return result;
360 }
361 #endif
362
363 static int
364 typelist(int eargc, char *eargv[],
365          bool verbosity,
366          DescHook hook)
367 /* apply a function to each entry in given terminfo directories */
368 {
369     int i;
370
371     for (i = 0; i < eargc; i++) {
372 #if USE_DATABASE
373         if (_nc_is_dir_path(eargv[i])) {
374             char *cwd_buf = 0;
375             DIR *termdir;
376             DIRENT *subdir;
377
378             if ((termdir = opendir(eargv[i])) == 0) {
379                 (void) fflush(stdout);
380                 (void) fprintf(stderr,
381                                "%s: can't open terminfo directory %s\n",
382                                _nc_progname, eargv[i]);
383                 continue;
384             }
385
386             if (verbosity)
387                 (void) printf("#\n#%s:\n#\n", eargv[i]);
388
389             while ((subdir = readdir(termdir)) != 0) {
390                 size_t cwd_len;
391                 char *name_1;
392                 DIR *entrydir;
393                 DIRENT *entry;
394
395                 name_1 = copy_entryname(subdir);
396                 if (isDotname(name_1)) {
397                     free(name_1);
398                     continue;
399                 }
400
401                 cwd_len = NAMLEN(subdir) + strlen(eargv[i]) + 3;
402                 cwd_buf = typeRealloc(char, cwd_len, cwd_buf);
403                 if (cwd_buf == 0)
404                     failed("realloc cwd_buf");
405
406                 assert(cwd_buf != 0);
407
408                 _nc_SPRINTF(cwd_buf, _nc_SLIMIT(cwd_len)
409                             "%s/%s/", eargv[i], name_1);
410                 free(name_1);
411
412                 if (chdir(cwd_buf) != 0)
413                     continue;
414
415                 entrydir = opendir(".");
416                 if (entrydir == 0) {
417                     perror(cwd_buf);
418                     continue;
419                 }
420                 while ((entry = readdir(entrydir)) != 0) {
421                     char *name_2;
422                     TERMTYPE lterm;
423                     char *cn;
424                     int status;
425
426                     name_2 = copy_entryname(entry);
427                     if (isDotname(name_2) || !_nc_is_file_path(name_2)) {
428                         free(name_2);
429                         continue;
430                     }
431
432                     status = _nc_read_file_entry(name_2, &lterm);
433                     if (status <= 0) {
434                         (void) fflush(stdout);
435                         (void) fprintf(stderr,
436                                        "%s: couldn't open terminfo file %s.\n",
437                                        _nc_progname, name_2);
438                         free(name_2);
439                         return (EXIT_FAILURE);
440                     }
441
442                     /* only visit things once, by primary name */
443                     cn = _nc_first_name(lterm.term_names);
444                     if (!strcmp(cn, name_2)) {
445                         /* apply the selected hook function */
446                         hook(i, eargc, cn, &lterm);
447                     }
448                     _nc_free_termtype(&lterm);
449                     free(name_2);
450                 }
451                 closedir(entrydir);
452             }
453             closedir(termdir);
454             if (cwd_buf != 0)
455                 free(cwd_buf);
456             continue;
457         }
458 #if USE_HASHED_DB
459         else {
460             DB *capdbp;
461             char filename[PATH_MAX];
462
463             if (verbosity)
464                 (void) printf("#\n#%s:\n#\n", eargv[i]);
465
466             if (make_db_name(filename, eargv[i], sizeof(filename))) {
467                 if ((capdbp = _nc_db_open(filename, FALSE)) != 0) {
468                     DBT key, data;
469                     int code;
470
471                     code = _nc_db_first(capdbp, &key, &data);
472                     while (code == 0) {
473                         TERMTYPE lterm;
474                         int used;
475                         char *have;
476                         char *cn;
477
478                         if (_nc_db_have_data(&key, &data, &have, &used)) {
479                             if (_nc_read_termtype(&lterm, have, used) > 0) {
480                                 /* only visit things once, by primary name */
481                                 cn = _nc_first_name(lterm.term_names);
482                                 /* apply the selected hook function */
483                                 hook(i, eargc, cn, &lterm);
484                                 _nc_free_termtype(&lterm);
485                             }
486                         }
487                         code = _nc_db_next(capdbp, &key, &data);
488                     }
489
490                     _nc_db_close(capdbp);
491                     continue;
492                 }
493             }
494         }
495 #endif
496 #endif
497 #if USE_TERMCAP
498 #if HAVE_BSD_CGETENT
499         {
500             CGETENT_CONST char *db_array[2];
501             char *buffer = 0;
502
503             if (verbosity)
504                 (void) printf("#\n#%s:\n#\n", eargv[i]);
505
506             db_array[0] = eargv[i];
507             db_array[1] = 0;
508
509             if (cgetfirst(&buffer, db_array) > 0) {
510                 show_termcap(i, eargc, buffer, hook);
511                 free(buffer);
512                 while (cgetnext(&buffer, db_array) > 0) {
513                     show_termcap(i, eargc, buffer, hook);
514                     free(buffer);
515                 }
516                 cgetclose();
517                 continue;
518             }
519         }
520 #else
521         /* scan termcap text-file only */
522         if (_nc_is_file_path(eargv[i])) {
523             char buffer[2048];
524             FILE *fp;
525
526             if (verbosity)
527                 (void) printf("#\n#%s:\n#\n", eargv[i]);
528
529             if ((fp = fopen(eargv[i], "r")) != 0) {
530                 while (fgets(buffer, sizeof(buffer), fp) != 0) {
531                     if (*buffer == '#')
532                         continue;
533                     if (isspace(*buffer))
534                         continue;
535                     show_termcap(i, eargc, buffer, hook);
536                 }
537                 fclose(fp);
538             }
539         }
540 #endif
541 #endif
542     }
543
544     if (hook == sorthook) {
545         show_termdata(eargc, eargv);
546         free_termdata();
547     }
548
549     return (EXIT_SUCCESS);
550 }
551
552 static void
553 usage(void)
554 {
555     (void) fprintf(stderr, "usage: %s [-ahsuUV] [-v n] [file...]\n", _nc_progname);
556     ExitProgram(EXIT_FAILURE);
557 }
558
559 int
560 main(int argc, char *argv[])
561 {
562     bool all_dirs = FALSE;
563     bool direct_dependencies = FALSE;
564     bool invert_dependencies = FALSE;
565     bool header = FALSE;
566     char *report_file = 0;
567     unsigned i;
568     int code;
569     int this_opt, last_opt = '?';
570     unsigned v_opt = 0;
571     DescHook *hook = deschook;
572
573     _nc_progname = _nc_rootname(argv[0]);
574
575     while ((this_opt = getopt(argc, argv, "0123456789ahsu:vU:V")) != -1) {
576         /* handle optional parameter */
577         if (isdigit(this_opt)) {
578             switch (last_opt) {
579             case 'v':
580                 v_opt = (unsigned) (this_opt - '0');
581                 break;
582             default:
583                 if (isdigit(last_opt))
584                     v_opt *= 10;
585                 else
586                     v_opt = 0;
587                 v_opt += (unsigned) (this_opt - '0');
588                 last_opt = this_opt;
589             }
590             continue;
591         }
592         switch (this_opt) {
593         case 'a':
594             all_dirs = TRUE;
595             break;
596         case 'h':
597             header = TRUE;
598             break;
599         case 's':
600             hook = sorthook;
601             break;
602         case 'u':
603             direct_dependencies = TRUE;
604             report_file = optarg;
605             break;
606         case 'v':
607             v_opt = 1;
608             break;
609         case 'U':
610             invert_dependencies = TRUE;
611             report_file = optarg;
612             break;
613         case 'V':
614             puts(curses_version());
615             ExitProgram(EXIT_SUCCESS);
616         default:
617             usage();
618         }
619     }
620     set_trace_level(v_opt);
621
622     if (report_file != 0) {
623         if (freopen(report_file, "r", stdin) == 0) {
624             (void) fflush(stdout);
625             fprintf(stderr, "%s: can't open %s\n", _nc_progname, report_file);
626             ExitProgram(EXIT_FAILURE);
627         }
628
629         /* parse entries out of the source file */
630         _nc_set_source(report_file);
631         _nc_read_entry_source(stdin, 0, FALSE, FALSE, NULLHOOK);
632     }
633
634     /* maybe we want a direct-dependency listing? */
635     if (direct_dependencies) {
636         ENTRY *qp;
637
638         for_entry_list(qp) {
639             if (qp->nuses) {
640                 unsigned j;
641
642                 (void) printf("%s:", _nc_first_name(qp->tterm.term_names));
643                 for (j = 0; j < qp->nuses; j++)
644                     (void) printf(" %s", qp->uses[j].name);
645                 putchar('\n');
646             }
647         }
648
649         ExitProgram(EXIT_SUCCESS);
650     }
651
652     /* maybe we want a reverse-dependency listing? */
653     if (invert_dependencies) {
654         ENTRY *qp, *rp;
655         int matchcount;
656
657         for_entry_list(qp) {
658             matchcount = 0;
659             for_entry_list(rp) {
660                 if (rp->nuses == 0)
661                     continue;
662
663                 for (i = 0; i < rp->nuses; i++)
664                     if (_nc_name_match(qp->tterm.term_names,
665                                        rp->uses[i].name, "|")) {
666                         if (matchcount++ == 0)
667                             (void) printf("%s:",
668                                           _nc_first_name(qp->tterm.term_names));
669                         (void) printf(" %s",
670                                       _nc_first_name(rp->tterm.term_names));
671                     }
672             }
673             if (matchcount)
674                 putchar('\n');
675         }
676
677         ExitProgram(EXIT_SUCCESS);
678     }
679
680     /*
681      * If we get this far, user wants a simple terminal type listing.
682      */
683     if (optind < argc) {
684         code = typelist(argc - optind, argv + optind, header, hook);
685     } else if (all_dirs) {
686         DBDIRS state;
687         int offset;
688         int pass;
689         const char *path;
690         char **eargv = 0;
691
692         code = EXIT_FAILURE;
693         for (pass = 0; pass < 2; ++pass) {
694             size_t count = 0;
695
696             _nc_first_db(&state, &offset);
697             while ((path = _nc_next_db(&state, &offset)) != 0) {
698                 if (pass) {
699                     eargv[count] = strmalloc(path);
700                 }
701                 ++count;
702             }
703             if (!pass) {
704                 eargv = allocArgv(count);
705             } else {
706                 code = typelist((int) count, eargv, header, hook);
707                 freeArgv(eargv);
708             }
709         }
710     } else {
711         DBDIRS state;
712         int offset;
713         const char *path;
714         char **eargv = allocArgv(2);
715         size_t count = 0;
716
717         _nc_first_db(&state, &offset);
718         if ((path = _nc_next_db(&state, &offset)) != 0) {
719             eargv[count++] = strmalloc(path);
720         }
721
722         code = typelist((int) count, eargv, header, hook);
723
724         freeArgv(eargv);
725     }
726     _nc_last_db();
727
728     ExitProgram(code);
729 }