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