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