Imported Upstream version 2.8.4
[platform/upstream/man-db.git] / src / whatis.c
1 /*
2  * whatis.c: search the index or whatis database(s) for words.
3  *  
4  * Copyright (C) 1994, 1995 Graeme W. Wilford. (Wilf.)
5  * Copyright (C) 2001, 2002, 2003, 2004, 2006, 2007, 2008, 2009, 2010, 2011,
6  *               2012 Colin Watson.
7  *
8  * This file is part of man-db.
9  *
10  * man-db is free software; you can redistribute it and/or modify it
11  * under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * man-db is distributed in the hope that it will be useful, but
16  * WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with man-db; if not, write to the Free Software Foundation,
22  * Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
23  *
24  * routines for whatis and apropos programs. Whatis looks up the 
25  * keyword for the description, apropos searches the entire database 
26  * for word matches.
27  *
28  * Mon Aug  8 20:35:30 BST 1994  Wilf. (G.Wilford@ee.surrey.ac.uk) 
29  *
30  * CJW: Add more safety in the face of corrupted databases.
31  */
32
33 #ifdef HAVE_CONFIG_H
34 #  include "config.h"
35 #endif /* HAVE_CONFIG_H */
36
37 #include <stdlib.h>
38 #include <string.h>
39 #include <stdio.h>
40 #include <ctype.h>
41 #include <errno.h>
42 #include <unistd.h>
43
44 #include "gettext.h"
45 #include <locale.h>
46 #define _(String) gettext (String)
47 #define N_(String) gettext_noop (String)
48
49 #ifdef HAVE_ICONV
50 #  include <iconv.h>
51 #endif /* HAVE_ICONV */
52
53 #include <sys/types.h>
54 #include <sys/stat.h>
55 #include "regex.h"
56
57 #include "argp.h"
58 #include "dirname.h"
59 #include "fnmatch.h"
60 #include "progname.h"
61 #include "xvasprintf.h"
62
63 #include "manconfig.h"
64
65 #include "cleanup.h"
66 #include "error.h"
67 #include "pipeline.h"
68 #include "pathsearch.h"
69 #include "linelength.h"
70 #include "hashtable.h"
71 #include "lower.h"
72 #include "wordfnmatch.h"
73 #include "xregcomp.h"
74 #include "encodings.h"
75 #include "sandbox.h"
76
77 #include "mydbm.h"
78 #include "db_storage.h"
79
80 #include "manp.h"
81
82 static char *manpathlist[MAXDIRS];
83
84 extern char *user_config_file;
85 static char **keywords;
86 static int num_keywords;
87
88 int am_apropos;
89 char *database;
90 int quiet = 1;
91 man_sandbox *sandbox;
92
93 #ifdef HAVE_ICONV
94 iconv_t conv_to_locale;
95 #endif /* HAVE_ICONV */
96
97 static regex_t *preg;  
98 static int regex_opt;
99 static int exact;
100
101 static int wildcard;
102
103 static int require_all;
104
105 static int long_output;
106
107 static char **sections;
108
109 static char *manp = NULL;
110 static const char *alt_systems = "";
111 static const char *locale = NULL;
112 static char *multiple_locale = NULL, *internal_locale;
113
114 static struct hashtable *display_seen = NULL;
115
116 const char *argp_program_version; /* initialised in main */
117 const char *argp_program_bug_address = PACKAGE_BUGREPORT;
118 error_t argp_err_exit_status = FAIL;
119
120 static const char args_doc[] = N_("KEYWORD...");
121 static const char apropos_doc[] = "\v" N_("The --regex option is enabled by default.");
122
123 static struct argp_option options[] = {
124         { "debug",              'd',    0,              0,      N_("emit debugging messages") },
125         { "verbose",            'v',    0,              0,      N_("print verbose warning messages") },
126         { "regex",              'r',    0,              0,      N_("interpret each keyword as a regex"),        10 },
127         { "exact",              'e',    0,              0,      N_("search each keyword for exact match") }, /* apropos only */
128         { "wildcard",           'w',    0,              0,      N_("the keyword(s) contain wildcards") },
129         { "and",                'a',    0,              0,      N_("require all keywords to match"),                    20 }, /* apropos only */
130         { "long",               'l',    0,              0,      N_("do not trim output to terminal width"),             30 },
131         { "sections",           's',    N_("LIST"),     0,      N_("search only these sections (colon-separated)"),     40 },
132         { "section",            0,      0,              OPTION_ALIAS },
133         { "systems",            'm',    N_("SYSTEM"),   0,      N_("use manual pages from other systems") },
134         { "manpath",            'M',    N_("PATH"),     0,      N_("set search path for manual pages to PATH") },
135         { "locale",             'L',    N_("LOCALE"),   0,      N_("define the locale for this search") },
136         { "config-file",        'C',    N_("FILE"),     0,      N_("use this user configuration file") },
137         { "whatis",             'f',    0,              OPTION_HIDDEN,  0 },
138         { "apropos",            'k',    0,              OPTION_HIDDEN,  0 },
139         { 0, 'h', 0, OPTION_HIDDEN, 0 }, /* compatibility for --help */
140         { 0 }
141 };
142
143 static char **split_sections (const char *sections_str)
144 {
145         int i = 0;
146         char *str = xstrdup (sections_str);
147         const char *section;
148         char **out = NULL;
149
150         /* Although this is documented as colon-separated, at least Solaris
151          * man's -s option takes a comma-separated list, so we accept that
152          * too for compatibility.
153          */
154         for (section = strtok (str, ":,"); section;
155              section = strtok (NULL, ":,")) {
156                 out = xnrealloc (out, i + 2, sizeof *out);
157                 out[i++] = xstrdup (section);
158         }
159         if (i)
160                 out[i] = NULL;
161
162         free (str);
163         return out;
164 }
165
166 static error_t parse_opt (int key, char *arg, struct argp_state *state)
167 {
168         switch (key) {
169                 case 'd':
170                         debug_level = 1;
171                         return 0;
172                 case 'v':
173                         quiet = 0;
174                         return 0;
175                 case 'r':
176                         regex_opt = 1;
177                         return 0;
178                 case 'e':
179                         /* Only makes sense for apropos, but has
180                          * historically been accepted by whatis anyway.
181                          */
182                         regex_opt = 0;
183                         exact = 1;
184                         return 0;
185                 case 'w':
186                         regex_opt = 0;
187                         wildcard = 1;
188                         return 0;
189                 case 'a':
190                         if (am_apropos)
191                                 require_all = 1;
192                         else
193                                 argp_usage (state);
194                         return 0;
195                 case 'l':
196                         long_output = 1;
197                         return 0;
198                 case 's':
199                         sections = split_sections (arg);
200                         return 0;
201                 case 'm':
202                         alt_systems = arg;
203                         return 0;
204                 case 'M':
205                         manp = xstrdup (arg);
206                         return 0;
207                 case 'L':
208                         locale = arg;
209                         return 0;
210                 case 'C':
211                         user_config_file = arg;
212                         return 0;
213                 case 'f':
214                         /* helpful override if program name detection fails */
215                         am_apropos = 0;
216                         return 0;
217                 case 'k':
218                         /* helpful override if program name detection fails */
219                         am_apropos = 1;
220                         return 0;
221                 case 'h':
222                         argp_state_help (state, state->out_stream,
223                                          ARGP_HELP_STD_HELP &
224                                          ~ARGP_HELP_PRE_DOC);
225                         break;
226                 case ARGP_KEY_ARGS:
227                         keywords = state->argv + state->next;
228                         num_keywords = state->argc - state->next;
229                         return 0;
230                 case ARGP_KEY_NO_ARGS:
231                         /* Make sure that we have a keyword! */
232                         printf (_("%s what?\n"), program_name);
233                         exit (FAIL);
234                 case ARGP_KEY_SUCCESS:
235                         if (am_apropos && !exact && !wildcard)
236                                 regex_opt = 1;
237                         return 0;
238         }
239         return ARGP_ERR_UNKNOWN;
240 }
241
242 static char *help_filter (int key, const char *text,
243                           void *input ATTRIBUTE_UNUSED)
244 {
245         switch (key) {
246                 case ARGP_KEY_HELP_PRE_DOC:
247                         /* We have no pre-options help text, but the input
248                          * text may contain header junk due to gettext ("").
249                          */
250                         return NULL;
251                 default:
252                         return (char *) text;
253         }
254 }
255
256 static struct argp apropos_argp = { options, parse_opt, args_doc, apropos_doc,
257                                     0, help_filter };
258 static struct argp whatis_argp = { options, parse_opt, args_doc };
259
260 static char *locale_manpath (const char *manpath)
261 {
262         char *all_locales;
263         char *new_manpath;
264
265         if (multiple_locale && *multiple_locale) {
266                 if (internal_locale && *internal_locale)
267                         all_locales = xasprintf ("%s:%s", multiple_locale,
268                                                  internal_locale);
269                 else
270                         all_locales = xstrdup (multiple_locale);
271         } else {
272                 if (internal_locale && *internal_locale)
273                         all_locales = xstrdup (internal_locale);
274                 else
275                         all_locales = NULL;
276         }
277
278         new_manpath = add_nls_manpaths (manpath, all_locales);
279         free (all_locales);
280
281         return new_manpath;
282 }
283
284 #ifdef HAVE_ICONV
285 static char *simple_convert (iconv_t conv, char *string)
286 {
287         if (conv != (iconv_t) -1) {
288                 size_t string_conv_alloc = strlen (string) + 1;
289                 char *string_conv = xmalloc (string_conv_alloc);
290                 for (;;) {
291                         char *inptr = string, *outptr = string_conv;
292                         size_t inleft = strlen (string);
293                         size_t outleft = string_conv_alloc - 1;
294                         if (iconv (conv, (ICONV_CONST char **) &inptr, &inleft,
295                                    &outptr, &outleft) == (size_t) -1 &&
296                             errno == E2BIG) {
297                                 string_conv_alloc <<= 1;
298                                 string_conv = xrealloc (string_conv,
299                                                         string_conv_alloc);
300                         } else {
301                                 /* Either we succeeded, or we've done our
302                                  * best; go ahead and print what we've got.
303                                  */
304                                 string_conv[string_conv_alloc - 1 - outleft] =
305                                         '\0';
306                                 break;
307                         }
308                 }
309                 return string_conv;
310         } else
311                 return xstrdup (string);
312 }
313 #else /* !HAVE_ICONV */
314 #  define simple_convert(conv, string) xstrdup (string)
315 #endif /* HAVE_ICONV */
316
317 /* Do the old thing, if we cannot find the relevant database.
318  * This invokes grep once per argument; we can't do much about this because
319  * we need to know which arguments failed.  The only way to speed this up
320  * would be to implement grep internally, but it hardly seems worth it for a
321  * legacy mechanism.
322  */
323 static void use_grep (const char * const *pages, int num_pages, char *manpath,
324                       int *found)
325 {
326         char *whatis_file = xasprintf ("%s/whatis", manpath);
327
328         if (CAN_ACCESS (whatis_file, R_OK)) {
329                 const char *flags;
330                 int i;
331
332                 if (am_apropos) {
333                         if (regex_opt)
334                                 flags = get_def_user (
335                                         "apropos_regex_grep_flags",
336                                         APROPOS_REGEX_GREP_FLAGS);
337                         else
338                                 flags = get_def_user ("apropos_grep_flags",
339                                                       APROPOS_GREP_FLAGS);
340                 } else
341                         flags = get_def_user ("whatis_grep_flags",
342                                               WHATIS_GREP_FLAGS);
343
344                 for (i = 0; i < num_pages; ++i) {
345                         pipeline *grep_pl;
346                         pipecmd *grep_cmd;
347                         char *anchored_page;
348
349                         if (am_apropos)
350                                 anchored_page = xstrdup (pages[i]);
351                         else
352                                 anchored_page = xasprintf ("^%s", pages[i]);
353
354                         grep_cmd = pipecmd_new_argstr (get_def_user ("grep",
355                                                                      GREP));
356                         pipecmd_argstr (grep_cmd, flags);
357                         pipecmd_args (grep_cmd, anchored_page, whatis_file,
358                                       NULL);
359                         pipecmd_pre_exec (grep_cmd, sandbox_load, sandbox_free,
360                                           sandbox);
361                         grep_pl = pipeline_new_commands (grep_cmd, NULL);
362
363                         if (pipeline_run (grep_pl) == 0)
364                                 found[i] = 1;
365
366                         free (anchored_page);
367                 }
368         } else
369                 debug ("warning: can't read the fallback whatis text database "
370                        "%s/whatis\n", manpath);
371
372         free (whatis_file);
373 }
374
375 static struct mandata *resolve_pointers (MYDBM_FILE dbf, struct mandata *info,
376                                          const char *page)
377 {
378         int rounds;
379         const char *newpage;
380
381         if (*(info->pointer) == '-' ||
382             ((!info->name || STREQ (info->name, page)) &&
383              STREQ (info->pointer, page)))
384                 return info;
385
386         /* Now we have to work through pointers. The limit of 10 is fairly
387          * arbitrary: it's just there to avoid an infinite loop.
388          */
389         newpage = info->pointer;
390         info = dblookup_exact (dbf, newpage, info->ext, 1);
391         for (rounds = 0; rounds < 10; rounds++) {
392                 struct mandata *newinfo;
393
394                 /* If the pointer lookup fails, do nothing. */
395                 if (!info)
396                         return NULL;
397
398                 if (*(info->pointer) == '-' ||
399                     ((!info->name || STREQ (info->name, newpage)) &&
400                      STREQ (info->pointer, newpage)))
401                         return info;
402
403                 newinfo = dblookup_exact (dbf, info->pointer, info->ext, 1);
404                 free_mandata_struct (info);
405                 info = newinfo;
406         }
407
408         if (!quiet)
409                 error (0, 0, _("warning: %s contains a pointer loop"), page);
410         return NULL;
411 }
412
413 /* fill_in_whatis() is really a ../libdb/db_lookup.c routine but whatis.c
414    is the only file that actually requires access to the whatis text... */
415    
416 /* Take mandata struct (earlier returned from a dblookup()) and return 
417    the relative whatis */
418 static char *get_whatis (struct mandata *info, const char *page)
419 {
420         if (!info)
421                 return xstrdup (_("(unknown subject)"));
422
423         /* See if we need to fill in the whatis here. */
424         if (info->whatis != NULL && *(info->whatis))
425                 return xstrdup (info->whatis);
426         if (!quiet && *(info->pointer) != '-')
427                 error (0, 0, _("warning: %s contains a pointer loop"),
428                        page);
429         return xstrdup (_("(unknown subject)"));
430 }
431
432 /* print out any matches found */
433 static void display (MYDBM_FILE dbf, struct mandata *info, const char *page)
434 {
435         struct mandata *newinfo;
436         char *string, *whatis, *string_conv;
437         const char *page_name;
438         char *key;
439         int line_len, rest;
440
441         newinfo = resolve_pointers (dbf, info, page);
442         whatis = get_whatis (newinfo, page);
443         if (newinfo == NULL)
444                 newinfo = info;
445
446         dbprintf (newinfo);
447
448         if (newinfo->name)
449                 page_name = newinfo->name;
450         else
451                 page_name = page;
452
453         key = xasprintf ("%s (%s)", page_name, newinfo->ext);
454         if (hashtable_lookup_structure (display_seen, key, strlen (key)))
455                 goto out;
456         hashtable_install (display_seen, key, strlen (key), NULL);
457
458         line_len = get_line_length ();
459
460         if (!long_output && strlen (page_name) > (size_t) (line_len / 2))
461                 string = xasprintf ("%.*s...", line_len / 2 - 3, page_name);
462         else
463                 string = xstrdup (page_name);
464         string = appendstr (string, " (", newinfo->ext, ")", NULL);
465         if (!STREQ (newinfo->pointer, "-") && !STREQ (newinfo->pointer, page))
466                 string = appendstr (string, " [", newinfo->pointer, "]", NULL);
467
468         if (strlen (string) < (size_t) 20) {
469                 int i;
470                 string = xrealloc (string, 21);
471                 for (i = strlen (string); i < 20; ++i)
472                         string[i] = ' ';
473                 string[i] = '\0';
474         }
475         string = appendstr (string, " - ", NULL);
476
477         rest = line_len - strlen (string);
478         if (!long_output && strlen (whatis) > (size_t) rest) {
479                 whatis[rest - 3] = '\0';
480                 string = appendstr (string, whatis, "...\n", NULL);
481         } else
482                 string = appendstr (string, whatis, "\n", NULL);
483
484         string_conv = simple_convert (conv_to_locale, string);
485         fputs (string_conv, stdout);
486
487         free (string_conv);
488         free (string);
489
490 out:
491         free (key);
492         free (whatis);
493         if (newinfo != info)
494                 free_mandata_struct (newinfo);
495 }
496
497 /* lookup the page and display the results */
498 static int do_whatis_section (MYDBM_FILE dbf,
499                               const char *page, const char *section)
500 {
501         struct mandata *info;
502         int count = 0;
503
504         info = dblookup_all (dbf, page, section, 0);
505         while (info) {
506                 struct mandata *pinfo;
507                         
508                 display (dbf, info, page);
509                 count++;
510                 pinfo = info->next;     /* go on to next structure */
511                 free_mandata_elements (info);
512                 free (info);
513                 info = pinfo;
514         }
515         return count;
516 }
517
518 static int suitable_manpath (const char *manpath, const char *page_dir)
519 {
520         char *page_manp, *pm;
521         char *page_manpathlist[MAXDIRS], **mp;
522         int ret;
523
524         page_manp = get_manpath_from_path (page_dir, 0);
525         if (!page_manp || !*page_manp) {
526                 free (page_manp);
527                 return 0;
528         }
529         pm = locale_manpath (page_manp);
530         free (page_manp);
531         page_manp = pm;
532         create_pathlist (page_manp, page_manpathlist);
533
534         ret = 0;
535         for (mp = page_manpathlist; *mp; ++mp) {
536                 if (STREQ (*mp, manpath)) {
537                         ret = 1;
538                         break;
539                 }
540         }
541
542         for (mp = page_manpathlist; *mp; ++mp)
543                 free (*mp);
544         free (page_manp);
545         return ret;
546 }
547
548 static void do_whatis (MYDBM_FILE dbf,
549                        const char * const *pages, int num_pages,
550                        const char *manpath, int *found)
551 {
552         int i;
553
554         for (i = 0; i < num_pages; ++i) {
555                 char *page = xstrdup (pages[i]);
556                 struct stat st;
557
558                 if (strchr (page, '/') && stat (page, &st) == 0 &&
559                     !S_ISDIR (st.st_mode) &&
560                     st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) {
561                         /* Perhaps an executable.  If its directory is on
562                          * $PATH, then we only want to process this page for
563                          * matching manual hierarchies.
564                          */
565                         char *page_dir = dir_name (page);
566
567                         if (directory_on_path (page_dir)) {
568                                 if (suitable_manpath (manpath, page_dir)) {
569                                         char *old_page = page;
570                                         page = base_name (old_page);
571                                         free (old_page);
572                                 } else {
573                                         debug ("%s not on manpath for %s\n",
574                                                manpath, page);
575                                         free (page_dir);
576                                         free (page);
577                                         continue;
578                                 }
579                         }
580                         free (page_dir);
581                 }
582
583                 if (sections) {
584                         char * const *section;
585
586                         for (section = sections; *section; ++section) {
587                                 if (do_whatis_section (dbf, page, *section))
588                                         found[i] = 1;
589                         }
590                 } else {
591                         if (do_whatis_section (dbf, page, NULL))
592                                 found[i] = 1;
593                 }
594
595                 free (page);
596         }
597 }
598
599 static int any_set (int num_pages, int *found_here)
600 {
601         int i;
602
603         for (i = 0; i < num_pages; ++i)
604                 if (found_here[i])
605                         return 1;
606         return 0;
607 }
608
609 static int all_set (int num_pages, int *found_here)
610 {
611         int i;
612
613         for (i = 0; i < num_pages; ++i)
614                 if (!found_here[i])
615                         return 0;
616         return 1;
617 }
618
619 static void parse_name (const char * const *pages, int num_pages,
620                         const char *dbname, int *found, int *found_here)
621
622         int i;
623
624         if (regex_opt) {
625                 for (i = 0; i < num_pages; ++i) {
626                         if (regexec (&preg[i], dbname, 0,
627                                      (regmatch_t *) 0, 0) == 0)
628                                 found[i] = found_here[i] = 1;
629                 }
630                 return;
631         }
632
633         if (am_apropos && !wildcard) {
634                 char *lowdbname = lower (dbname);
635
636                 for (i = 0; i < num_pages; ++i) {
637                         if (STREQ (lowdbname, pages[i]))
638                                 found[i] = found_here[i] = 1;
639                 }
640                 free (lowdbname);
641                 return;
642         }
643
644         for (i = 0; i < num_pages; ++i) {
645                 if (fnmatch (pages[i], dbname, 0) == 0)
646                         found[i] = found_here[i] = 1;
647         }
648 }
649
650 /* return 1 on word match */
651 static int match (const char *lowpage, const char *whatis)
652 {
653         char *lowwhatis = lower (whatis);
654         size_t len = strlen (lowpage);
655         char *p, *begin;
656
657         begin = lowwhatis;
658         
659         /* check for string match, then see if it is a _word_ */
660         while (lowwhatis && (p = strstr (lowwhatis, lowpage))) {
661                 char *left = p - 1; 
662                 char *right = p + len;
663
664                 if ((p == begin || (!CTYPE (islower, *left) && *left != '_')) &&
665                     (!*right || (!CTYPE (islower, *right) && *right != '_'))) {
666                         free (begin);
667                         return 1;
668                 }
669                 lowwhatis = p + 1;
670         }
671
672         free (begin);
673         return 0;
674 }
675
676 static void parse_whatis (const char * const *pages, char * const *lowpages,
677                           int num_pages, const char *whatis,
678                           int *found, int *found_here)
679
680         int i;
681
682         if (regex_opt) {
683                 for (i = 0; i < num_pages; ++i) {
684                         if (regexec (&preg[i], whatis, 0,
685                                      (regmatch_t *) 0, 0) == 0)
686                                 found[i] = found_here[i] = 1;
687                 }
688                 return;
689         }
690
691         if (wildcard) {
692                 for (i = 0; i < num_pages; ++i) {
693                         if (exact) {
694                                 if (fnmatch (pages[i], whatis, 0) == 0)
695                                         found[i] = found_here[i] = 1;
696                         } else {
697                                 if (word_fnmatch (pages[i], whatis))
698                                         found[i] = found_here[i] = 1;
699                         }
700                 }
701                 return;
702         }
703
704         for (i = 0; i < num_pages; ++i) {
705                 if (match (lowpages[i], whatis))
706                         found[i] = found_here[i] = 1;
707         }
708 }
709
710 /* cjwatson: Optimized functions don't seem to be correct in some
711  * circumstances; disabled for now.
712  */
713 #undef BTREE
714
715 /* scan for the page, print any matches */
716 static void do_apropos (MYDBM_FILE dbf,
717                         const char * const *pages, int num_pages, int *found)
718 {
719         datum key, cont;
720         char **lowpages;
721         int *found_here;
722         int (*combine) (int, int *);
723         int i;
724 #ifndef BTREE
725         datum nextkey;
726 #else /* BTREE */
727         int end;
728 #endif /* !BTREE */
729
730         lowpages = XNMALLOC (num_pages, char *);
731         for (i = 0; i < num_pages; ++i) {
732                 lowpages[i] = lower (pages[i]);
733                 debug ("lower(%s) = \"%s\"\n", pages[i], lowpages[i]);
734         }
735         found_here = XNMALLOC (num_pages, int);
736         combine = require_all ? all_set : any_set;
737
738 #ifndef BTREE
739         key = MYDBM_FIRSTKEY (dbf);
740         while (MYDBM_DPTR (key)) {
741                 cont = MYDBM_FETCH (dbf, key);
742 #else /* BTREE */
743         end = btree_nextkeydata (dbf, &key, &cont);
744         while (!end) {
745 #endif /* !BTREE */
746                 char *tab;
747                 struct mandata info;
748
749                 memset (&info, 0, sizeof (info));
750
751                 /* bug#4372, NULL pointer dereference in MYDBM_DPTR (cont),
752                  * fix by dassen@wi.leidenuniv.nl (J.H.M.Dassen), thanx Ray.
753                  * cjwatson: In that case, complain and exit, otherwise we
754                  * might loop (bug #95052).
755                  */
756                 if (!MYDBM_DPTR (cont))
757                 {
758                         debug ("key was %s\n", MYDBM_DPTR (key));
759                         error (FATAL, 0,
760                                _("Database %s corrupted; rebuild with "
761                                  "mandb --create"),
762                                database);
763                 }
764
765                 if (*MYDBM_DPTR (key) == '$')
766                         goto nextpage;
767
768                 if (*MYDBM_DPTR (cont) == '\t')
769                         goto nextpage;
770
771                 /* a real page */
772
773                 split_content (MYDBM_DPTR (cont), &info);
774
775                 /* If there are sections given, does any of them match
776                  * either the section or extension of this page?
777                  */
778                 if (sections) {
779                         char * const *section;
780                         int matched = 0;
781
782                         for (section = sections; *section; ++section) {
783                                 if (STREQ (*section, info.sec) ||
784                                     STREQ (*section, info.ext)) {
785                                         matched = 1;
786                                         break;
787                                 }
788                         }
789
790                         if (!matched)
791                                 goto nextpage;
792                 }
793
794                 tab = strrchr (MYDBM_DPTR (key), '\t');
795                 if (tab) 
796                          *tab = '\0';
797
798                 memset (found_here, 0, num_pages * sizeof (*found_here));
799                 if (am_apropos) {
800                         char *whatis;
801
802                         parse_name ((const char **) lowpages, num_pages,
803                                     MYDBM_DPTR (key), found, found_here);
804                         whatis = info.whatis ? xstrdup (info.whatis) : NULL;
805                         if (!combine (num_pages, found_here) && whatis)
806                                 parse_whatis (pages, lowpages, num_pages,
807                                               whatis, found, found_here);
808                         free (whatis);
809                 } else
810                         parse_name (pages, num_pages,
811                                     MYDBM_DPTR (key), found, found_here);
812                 if (combine (num_pages, found_here))
813                         display (dbf, &info, MYDBM_DPTR (key));
814
815                 if (tab)
816                         *tab = '\t';
817 nextpage:
818 #ifndef BTREE
819                 nextkey = MYDBM_NEXTKEY (dbf, key);
820                 MYDBM_FREE_DPTR (cont);
821                 MYDBM_FREE_DPTR (key);
822                 key = nextkey; 
823 #else /* BTREE */
824                 MYDBM_FREE_DPTR (cont);
825                 MYDBM_FREE_DPTR (key);
826                 end = btree_nextkeydata (dbf, &key, &cont);
827 #endif /* !BTREE */
828                 info.addr = NULL; /* == MYDBM_DPTR (cont), freed above */
829                 free_mandata_elements (&info);
830         }
831
832         for (i = 0; i < num_pages; ++i)
833                 free (lowpages[i]);
834         free (lowpages);
835 }
836
837 /* loop through the man paths, searching for a match */
838 static int search (const char * const *pages, int num_pages)
839 {
840         int *found = XCALLOC (num_pages, int);
841         char *catpath, **mp;
842         int any_found, i;
843
844         for (mp = manpathlist; *mp; mp++) {
845                 MYDBM_FILE dbf;
846
847                 catpath = get_catpath (*mp, SYSTEM_CAT | USER_CAT);
848                 
849                 if (catpath) {
850                         database = mkdbname (catpath);
851                         free (catpath);
852                 } else
853                         database = mkdbname (*mp);
854
855                 debug ("path=%s\n", *mp);
856
857                 dbf = MYDBM_RDOPEN (database);
858                 if (dbf && dbver_rd (dbf)) {
859                         MYDBM_CLOSE (dbf);
860                         dbf = NULL;
861                 }
862                 if (!dbf) {
863                         use_grep (pages, num_pages, *mp, found);
864                         continue;
865                 }
866
867                 if (am_apropos)
868                         do_apropos (dbf, pages, num_pages, found);
869                 else {
870                         if (regex_opt || wildcard)
871                                 do_apropos (dbf, pages, num_pages, found);
872                         else
873                                 do_whatis (dbf, pages, num_pages, *mp, found);
874                 }
875                 free (database);
876                 database = NULL;
877                 MYDBM_CLOSE (dbf);
878         }
879
880         chkr_garbage_detector ();
881
882         any_found = 0;
883         for (i = 0; i < num_pages; ++i) {
884                 if (found[i])
885                         any_found = 1;
886                 else
887                         fprintf (stderr, _("%s: nothing appropriate.\n"),
888                                  pages[i]);
889         }
890
891         free (found);
892         return any_found;
893 }
894
895 int main (int argc, char *argv[])
896 {
897         char *program_base_name;
898 #ifdef HAVE_ICONV
899         char *locale_charset;
900 #endif
901         int status = OK;
902
903         set_program_name (argv[0]);
904         program_base_name = base_name (program_name);
905         if (STREQ (program_base_name, APROPOS_NAME)) {
906                 am_apropos = 1;
907                 argp_program_version = "apropos " PACKAGE_VERSION;
908         } else {
909                 struct argp_option *optionp;
910                 am_apropos = 0;
911                 argp_program_version = "whatis " PACKAGE_VERSION;
912                 for (optionp = (struct argp_option *) whatis_argp.options;
913                      optionp->name || optionp->key || optionp->arg ||
914                      optionp->flags || optionp->doc || optionp->group;
915                      ++optionp) {
916                         if (!optionp->name)
917                                 continue;
918                         if (STREQ (optionp->name, "exact") ||
919                             STREQ (optionp->name, "and"))
920                                 optionp->flags |= OPTION_HIDDEN;
921                 }
922         }
923         free (program_base_name);
924
925         init_debug ();
926         pipeline_install_post_fork (pop_all_cleanups);
927         sandbox = sandbox_init ();
928         init_locale ();
929
930         internal_locale = setlocale (LC_MESSAGES, NULL);
931         /* Use LANGUAGE only when LC_MESSAGES locale category is
932          * neither "C" nor "POSIX". */
933         if (internal_locale && strcmp (internal_locale, "C") &&
934             strcmp (internal_locale, "POSIX"))
935                 multiple_locale = getenv ("LANGUAGE");
936         internal_locale = xstrdup (internal_locale ? internal_locale : "C");
937
938         if (argp_parse (am_apropos ? &apropos_argp : &whatis_argp, argc, argv,
939                         0, 0, 0))
940                 exit (FAIL);
941
942         read_config_file (user_config_file != NULL);
943
944         /* close this locale and reinitialise if a new locale was 
945            issued as an argument or in $MANOPT */
946         if (locale) {
947                 free (internal_locale);
948                 internal_locale = setlocale (LC_ALL, locale);
949                 if (internal_locale)
950                         internal_locale = xstrdup (internal_locale);
951                 else
952                         internal_locale = xstrdup (locale);
953
954                 debug ("main(): locale = %s, internal_locale = %s\n",
955                        locale, internal_locale);
956                 if (internal_locale) {
957                         setenv ("LANGUAGE", internal_locale, 1);
958                         locale_changed ();
959                         multiple_locale = NULL;
960                 }
961         }
962
963         /* sort out the internal manpath */
964         if (manp == NULL)
965                 manp = locale_manpath (get_manpath (alt_systems));
966         else
967                 free (get_manpath (NULL));
968
969         create_pathlist (manp, manpathlist);
970
971         display_seen = hashtable_create (&null_hashtable_free);
972
973 #ifdef HAVE_ICONV
974         locale_charset = xasprintf ("%s//IGNORE", get_locale_charset ());
975         conv_to_locale = iconv_open (locale_charset, "UTF-8");
976         free (locale_charset);
977 #endif /* HAVE_ICONV */
978
979         if (regex_opt) {
980                 int i;
981                 preg = XNMALLOC (num_keywords, regex_t);
982                 for (i = 0; i < num_keywords; ++i)
983                         xregcomp (&preg[i], keywords[i],
984                                   REG_EXTENDED | REG_NOSUB | REG_ICASE);
985         }
986
987         if (!search ((const char **) keywords, num_keywords))
988                 status = NOT_FOUND;
989
990         if (regex_opt) {
991                 int i;
992                 for (i = 0; i < num_keywords; ++i)
993                         regfree (&preg[i]);
994                 free (preg);
995         }
996
997 #ifdef HAVE_ICONV
998         if (conv_to_locale != (iconv_t) -1)
999                 iconv_close (conv_to_locale);
1000 #endif /* HAVE_ICONV */
1001         hashtable_free (display_seen);
1002         free_pathlist (manpathlist);
1003         free (manp);
1004         free (internal_locale);
1005         exit (status);
1006 }