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