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