Imported Upstream version 2.8.4
[platform/upstream/man-db.git] / src / man.c
1 /*
2  * man.c: The manual pager
3  *
4  * Copyright (C) 1990, 1991 John W. Eaton.
5  * Copyright (C) 1994, 1995 Graeme W. Wilford. (Wilf.)
6  * Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
7  *               2011, 2012 Colin Watson.
8  *
9  * This file is part of man-db.
10  *
11  * man-db is free software; you can redistribute it and/or modify it
12  * under the terms of the GNU General Public License as published by
13  * the Free Software Foundation; either version 2 of the License, or
14  * (at your option) any later version.
15  *
16  * man-db is distributed in the hope that it will be useful, but
17  * WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with man-db; if not, write to the Free Software Foundation,
23  * Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
24  *
25  * John W. Eaton
26  * jwe@che.utexas.edu
27  * Department of Chemical Engineering
28  * The University of Texas at Austin
29  * Austin, Texas  78712
30  *
31  * Mostly written/re-written by Wilf, some routines by Markus Armbruster.
32  *
33  * CJW: Various robustness, security, and internationalization fixes.
34  * Improved HTML support (originally written by Fabrizio Polacco).
35  * Rewrite of page location routines for improved maintainability and
36  * accuracy.
37  */
38
39 #ifdef HAVE_CONFIG_H
40 #  include "config.h"
41 #endif /* HAVE_CONFIG_H */
42
43 #include <string.h>
44 #include <stdlib.h>
45 #include <stdio.h>
46 #include <stdarg.h>
47 #include <assert.h>
48 #include <errno.h>
49 #include <termios.h>
50 #include <unistd.h>
51
52 #ifndef R_OK
53 #  define R_OK          4
54 #  define X_OK          1
55 #endif /* !R_OK */
56
57 #include <limits.h>
58
59 #if HAVE_FCNTL_H
60 #  include <fcntl.h>
61 #endif
62
63 #include <ctype.h>
64 #include <signal.h>
65 #include <time.h>
66 #include <sys/types.h>
67 #include <sys/stat.h>
68
69 #include "argp.h"
70 #include "dirname.h"
71 #include "minmax.h"
72 #include "progname.h"
73 #include "regex.h"
74 #include "stat-time.h"
75 #include "utimens.h"
76 #include "xvasprintf.h"
77 #include "xgetcwd.h"
78
79 #include "gettext.h"
80 #include <locale.h>
81 #define _(String) gettext (String)
82 #define N_(String) gettext_noop (String)
83
84 #include "manconfig.h"
85
86 #include "error.h"
87 #include "cleanup.h"
88 #include "hashtable.h"
89 #include "pipeline.h"
90 #include "pathsearch.h"
91 #include "linelength.h"
92 #include "decompress.h"
93 #include "xregcomp.h"
94 #include "security.h"
95 #include "encodings.h"
96 #include "orderfiles.h"
97 #include "sandbox.h"
98
99 #include "mydbm.h"
100 #include "db_storage.h"
101
102 #include "filenames.h"
103 #include "globbing.h"
104 #include "ult_src.h"
105 #include "manp.h"
106 #include "zsoelim.h"
107 #include "manconv_client.h"
108
109 #ifdef MAN_OWNER
110 extern uid_t ruid;
111 extern uid_t euid;
112 #endif /* MAN_OWNER */
113
114 /* the default preprocessor sequence */
115 #ifndef DEFAULT_MANROFFSEQ
116 #  define DEFAULT_MANROFFSEQ ""
117 #endif
118
119 /* placeholder for the manual page name in the less prompt string */
120 #define MAN_PN "$MAN_PN"
121
122 /* Some systems lack these */
123 #ifndef STDIN_FILENO
124 #  define STDIN_FILENO 0
125 #endif
126 #ifndef STDOUT_FILENO
127 #  define STDOUT_FILENO 1
128 #endif
129 #ifndef STDERR_FILENO
130 #  define STDERR_FILENO 2
131 #endif
132
133 char *lang;
134
135 /* external formatter programs, one for use without -t, and one with -t */
136 #define NFMT_PROG "mandb_nfmt"
137 #define TFMT_PROG "mandb_tfmt"
138 #undef ALT_EXT_FORMAT   /* allow external formatters located in cat hierarchy */
139
140 static int global_manpath = -1; /* global or user manual page hierarchy? */
141 static int skip;                /* page exists but has been skipped */
142
143 #if defined _AIX || defined __sgi
144 char **global_argv;
145 #endif
146
147 struct candidate {
148         const char *req_name;
149         char from_db;
150         char cat;
151         const char *path;
152         char *ult;
153         struct mandata *source;
154         int add_index; /* for sort stabilisation */
155         struct candidate *next;
156 };
157
158 #define CANDIDATE_FILESYSTEM 0
159 #define CANDIDATE_DATABASE   1
160
161 static void gripe_system (pipeline *p, int status)
162 {
163         error (CHILD_FAIL, 0, _("command exited with status %d: %s"),
164                status, pipeline_tostring (p));
165 }
166
167 enum opts {
168         OPT_WARNINGS = 256,
169         OPT_REGEX,
170         OPT_WILDCARD,
171         OPT_NAMES,
172         OPT_NO_HYPHENATION,
173         OPT_NO_JUSTIFICATION,
174         OPT_NO_SUBPAGES,
175         OPT_MAX
176 };
177
178 struct string_llist;
179 struct string_llist {
180         const char *name;
181         struct string_llist *next;
182 };
183
184
185 static char *manpathlist[MAXDIRS];
186
187 /* globals */
188 int quiet = 1;
189 char *database = NULL;
190 extern const char *extension; /* for globbing.c */
191 extern char *user_config_file;  /* defined in manp.c */
192 extern int disable_cache;
193 extern int min_cat_width, max_cat_width, cat_width;
194 man_sandbox *sandbox;
195
196 /* locals */
197 static const char *alt_system_name;
198 static const char **section_list;               
199 static const char *section;
200 static char *colon_sep_section_list;
201 static const char *preprocessors;
202 static const char *pager;
203 static const char *locale;
204 static char *internal_locale, *multiple_locale;
205 static const char *prompt_string;
206 static char *less;
207 static const char *std_sections[] = STD_SECTIONS;
208 static char *manp;
209 static const char *external;
210 static struct hashtable *db_hash = NULL;
211
212 static int troff;
213 static const char *roff_device = NULL;
214 static const char *want_encoding = NULL;
215 #ifdef NROFF_WARNINGS
216 static const char default_roff_warnings[] = "mac";
217 static struct string_llist *roff_warnings = NULL;
218 #endif /* NROFF_WARNINGS */
219 static int global_apropos;
220 static int print_where, print_where_cat;
221 static int catman;
222 static int local_man_file;
223 static int findall;
224 static int update;
225 static int match_case;
226 static int regex_opt;
227 static int wildcard;
228 static int names_only;
229 static int ult_flags = SO_LINK | SOFT_LINK | HARD_LINK;
230 static const char *recode = NULL;
231 static int no_hyphenation;
232 static int no_justification;
233 static int subpages = 1;
234
235 static int ascii;               /* insert tr in the output pipe */
236 static int save_cat;            /* security breach? Can we save the cat? */
237
238 static int first_arg;
239
240 static int found_a_stray;               /* found a straycat */
241
242 #ifdef MAN_CATS
243 static char *tmp_cat_file;      /* for open_cat_stream(), close_cat_stream() */
244 static int created_tmp_cat;                     /* dto. */
245 #endif
246 static int tmp_cat_fd;
247 static struct timespec man_modtime;     /* modtime of man page, for
248                                          * commit_tmp_cat() */
249
250 # ifdef TROFF_IS_GROFF
251 static int ditroff;
252 static const char *gxditview;
253 static int htmlout;
254 static const char *html_pager;
255 # endif /* TROFF_IS_GROFF */
256
257 const char *argp_program_version = "man " PACKAGE_VERSION;
258 const char *argp_program_bug_address = PACKAGE_BUGREPORT;
259 error_t argp_err_exit_status = FAIL;
260
261 static const char args_doc[] = N_("[SECTION] PAGE...");
262
263 # ifdef NROFF_WARNINGS
264 #  define ONLY_NROFF_WARNINGS 0
265 # else
266 #  define ONLY_NROFF_WARNINGS OPTION_HIDDEN
267 # endif
268
269 # ifdef TROFF_IS_GROFF
270 #  define ONLY_TROFF_IS_GROFF 0
271 # else
272 #  define ONLY_TROFF_IS_GROFF OPTION_HIDDEN
273 # endif
274
275 /* Please keep these options in the same order as in parse_opt below. */
276 static struct argp_option options[] = {
277         { "config-file",        'C',    N_("FILE"),     0,              N_("use this user configuration file") },
278         { "debug",              'd',    0,              0,              N_("emit debugging messages") },
279         { "default",            'D',    0,              0,              N_("reset all options to their default values") },
280         { "warnings",  OPT_WARNINGS,    N_("WARNINGS"), ONLY_NROFF_WARNINGS | OPTION_ARG_OPTIONAL,
281                                                                         N_("enable warnings from groff") },
282
283         { 0,                    0,      0,              0,              N_("Main modes of operation:"),                                 10 },
284         { "whatis",             'f',    0,              0,              N_("equivalent to whatis") },
285         { "apropos",            'k',    0,              0,              N_("equivalent to apropos") },
286         { "global-apropos",     'K',    0,              0,              N_("search for text in all pages") },
287         { "where",              'w',    0,              0,              N_("print physical location of man page(s)") },
288         { "path",               0,      0,              OPTION_ALIAS },
289         { "location",           0,      0,              OPTION_ALIAS },
290         { "where-cat",          'W',    0,              0,              N_("print physical location of cat file(s)") },
291         { "location-cat",       0,      0,              OPTION_ALIAS },
292         { "local-file",         'l',    0,              0,              N_("interpret PAGE argument(s) as local filename(s)") },
293         { "catman",             'c',    0,              0,              N_("used by catman to reformat out of date cat pages"),         11 },
294         { "recode",             'R',    N_("ENCODING"), 0,              N_("output source page encoded in ENCODING") },
295
296         { 0,                    0,      0,              0,              N_("Finding manual pages:"),                                    20 },
297         { "locale",             'L',    N_("LOCALE"),   0,              N_("define the locale for this particular man search") },
298         { "systems",            'm',    N_("SYSTEM"),   0,              N_("use manual pages from other systems") },
299         { "manpath",            'M',    N_("PATH"),     0,              N_("set search path for manual pages to PATH") },
300         { "sections",           'S',    N_("LIST"),     0,              N_("use colon separated section list"),                         21 },
301         { 0,                    's',    0,              OPTION_ALIAS },
302         { "extension",          'e',    N_("EXTENSION"),
303                                                         0,              N_("limit search to extension type EXTENSION"),                 22 },
304         { "ignore-case",        'i',    0,              0,              N_("look for pages case-insensitively (default)"),              23 },
305         { "match-case",         'I',    0,              0,              N_("look for pages case-sensitively") },
306         { "regex",        OPT_REGEX,    0,              0,              N_("show all pages matching regex"),                            24 },
307         { "wildcard",  OPT_WILDCARD,    0,              0,              N_("show all pages matching wildcard") },
308         { "names-only",   OPT_NAMES,    0,              0,              N_("make --regex and --wildcard match page names only, not "
309                                                                            "descriptions"),                                             25 },
310         { "all",                'a',    0,              0,              N_("find all matching manual pages"),                           26 },
311         { "update",             'u',    0,              0,              N_("force a cache consistency check") },
312         { "no-subpages",
313                     OPT_NO_SUBPAGES,    0,              0,              N_("don't try subpages, e.g. 'man foo bar' => 'man foo-bar'"),  27 },
314
315         { 0,                    0,      0,              0,              N_("Controlling formatted output:"),                            30 },
316         { "pager",              'P',    N_("PAGER"),    0,              N_("use program PAGER to display output") },
317         { "prompt",             'r',    N_("STRING"),   0,              N_("provide the `less' pager with a prompt") },
318         { "ascii",              '7',    0,              0,              N_("display ASCII translation of certain latin1 chars"),        31 },
319         { "encoding",           'E',    N_("ENCODING"), 0,              N_("use selected output encoding") },
320         { "no-hyphenation",
321                  OPT_NO_HYPHENATION,    0,              0,              N_("turn off hyphenation") },
322         { "nh",                 0,      0,              OPTION_ALIAS },
323         { "no-justification",
324                OPT_NO_JUSTIFICATION,    0,              0,              N_("turn off justification") },
325         { "nj",                 0,      0,              OPTION_ALIAS },
326         { "preprocessor",       'p',    N_("STRING"),   0,              N_("STRING indicates which preprocessors to run:\n"
327                                                                            "e - [n]eqn, p - pic, t - tbl,\n"
328                                                                            "g - grap, r - refer, v - vgrind") },
329 #ifdef HAS_TROFF
330         { "troff",              't',    0,              0,              N_("use %s to format pages"),                                   32 },
331         { "troff-device",       'T',    N_("DEVICE"),   OPTION_ARG_OPTIONAL,
332                                                                         N_("use %s with selected device") },
333         { "html",               'H',    N_("BROWSER"),  ONLY_TROFF_IS_GROFF | OPTION_ARG_OPTIONAL,
334                                                                         N_("use %s or BROWSER to display HTML output"),                 33 },
335         { "gxditview",          'X',    N_("RESOLUTION"),
336                                                         ONLY_TROFF_IS_GROFF | OPTION_ARG_OPTIONAL,
337                                                                         N_("use groff and display through gxditview (X11):\n"
338                                                                            "-X = -TX75, -X100 = -TX100, -X100-12 = -TX100-12") },
339         { "ditroff",            'Z',    0,              ONLY_TROFF_IS_GROFF,    N_("use groff and force it to produce ditroff") },
340 #endif /* HAS_TROFF */
341
342         { 0, 'h', 0, OPTION_HIDDEN, 0 }, /* compatibility for --help */
343         { 0 }
344 };
345
346 static void init_html_pager (void)
347 {
348         html_pager = getenv ("BROWSER");
349         if (!html_pager)
350                 html_pager = WEB_BROWSER;
351 }
352
353 static error_t parse_opt (int key, char *arg, struct argp_state *state)
354 {
355         static int apropos, whatis; /* retain values between calls */
356
357         /* Please keep these keys in the same order as in options above. */
358         switch (key) {
359                 case 'C':
360                         user_config_file = arg;
361                         return 0;
362                 case 'd':
363                         debug_level = 1;
364                         return 0;
365                 case 'D':
366                         /* discard all preset options */
367                         local_man_file = findall = update = catman =
368                                 debug_level = troff = global_apropos =
369                                 print_where = print_where_cat =
370                                 ascii = match_case =
371                                 regex_opt = wildcard = names_only =
372                                 no_hyphenation = no_justification = 0;
373 #ifdef TROFF_IS_GROFF
374                         ditroff = 0;
375                         gxditview = NULL;
376                         htmlout = 0;
377                         init_html_pager ();
378 #endif
379                         roff_device = want_encoding = extension = pager =
380                                 locale = alt_system_name = external =
381                                 preprocessors = NULL;
382                         colon_sep_section_list = manp = NULL;
383                         return 0;
384
385                 case OPT_WARNINGS:
386 #ifdef NROFF_WARNINGS
387                         {
388                                 char *s = xstrdup
389                                         (arg ? arg : default_roff_warnings);
390                                 const char *warning;
391
392                                 for (warning = strtok (s, ","); warning;
393                                      warning = strtok (NULL, ",")) {
394                                         struct string_llist *new;
395                                         new = xmalloc (sizeof *new);
396                                         new->name = xstrdup (warning);
397                                         new->next = roff_warnings;
398                                         roff_warnings = new;
399                                 }
400
401                                 free (s);
402                         }
403 #endif /* NROFF_WARNINGS */
404                         return 0;
405
406                 case 'f':
407                         external = WHATIS;
408                         whatis = 1;
409                         return 0;
410                 case 'k':
411                         external = APROPOS;
412                         apropos = 1;
413                         return 0;
414                 case 'K':
415                         global_apropos = 1;
416                         return 0;
417                 case 'w':
418                         print_where = 1;
419                         return 0;
420                 case 'W':
421                         print_where_cat = 1;
422                         return 0;
423                 case 'l':
424                         local_man_file = 1;
425                         return 0;
426                 case 'c':
427                         catman = 1;
428                         return 0;
429                 case 'R':
430                         recode = arg;
431                         ult_flags &= ~SO_LINK;
432                         return 0;
433
434                 case 'L':
435                         locale = arg;
436                         return 0;
437                 case 'm':
438                         alt_system_name = arg;
439                         return 0;
440                 case 'M':
441                         manp = arg;
442                         return 0;
443                 case 'S':
444                 case 's':
445                         if (*arg)
446                                 colon_sep_section_list = arg;
447                         return 0;
448                 case 'e':
449                         extension = arg;
450                         return 0;
451                 case 'i':
452                         match_case = 0;
453                         return 0;
454                 case 'I':
455                         match_case = 1;
456                         return 0;
457                 case OPT_REGEX:
458                         regex_opt = 1;
459                         findall = 1;
460                         return 0;
461                 case OPT_WILDCARD:
462                         wildcard = 1;
463                         findall = 1;
464                         return 0;
465                 case OPT_NAMES:
466                         names_only = 1;
467                         return 0;
468                 case 'a':
469                         findall = 1;
470                         return 0;
471                 case 'u':
472                         update = 1;
473                         return 0;
474                 case OPT_NO_SUBPAGES:
475                         subpages = 0;
476                         return 0;
477
478                 case 'P':
479                         pager = arg;
480                         return 0;
481                 case 'r':
482                         prompt_string = arg;
483                         return 0;
484                 case '7':
485                         ascii = 1;
486                         return 0;
487                 case 'E':
488                         want_encoding = arg;
489                         if (is_roff_device (want_encoding))
490                                 roff_device = want_encoding;
491                         return 0;
492                 case OPT_NO_HYPHENATION:
493                         no_hyphenation = 1;
494                         return 0;
495                 case OPT_NO_JUSTIFICATION:
496                         no_justification = 1;
497                         return 0;
498                 case 'p':
499                         preprocessors = arg;
500                         return 0;
501 #ifdef HAS_TROFF
502                 case 't':
503                         troff = 1;
504                         return 0;
505                 case 'T':
506                         /* Traditional nroff knows -T; troff does not (gets
507                          * ignored). All incarnations of groff know it. Why
508                          * does -T imply -t?
509                          */
510                         roff_device = (arg ? arg : "ps");
511                         troff = 1;
512                         return 0;
513                 case 'H':
514 # ifdef TROFF_IS_GROFF
515                         if (arg)
516                                 html_pager = arg;
517                         htmlout = 1;
518                         troff = 1;
519                         roff_device = "html";
520 # endif /* TROFF_IS_GROFF */
521                         return 0;
522                 case 'X':
523 # ifdef TROFF_IS_GROFF
524                         troff = 1;
525                         gxditview = (arg ? arg : "75");
526 # endif /* TROFF_IS_GROFF */
527                         return 0;
528                 case 'Z':
529 # ifdef TROFF_IS_GROFF
530                         ditroff = 1;
531                         troff = 1;
532 # endif /* TROFF_IS_GROFF */
533                         return 0;
534 #endif /* HAS_TROFF */
535
536                 case 'h':
537                         argp_state_help (state, state->out_stream,
538                                          ARGP_HELP_STD_HELP);
539                         break;
540                 case ARGP_KEY_SUCCESS:
541                         /* check for incompatible options */
542                         if (troff + whatis + apropos + catman +
543                             (print_where || print_where_cat) > 1) {
544                                 char *badopts = xasprintf
545                                         ("%s%s%s%s%s%s",
546                                          troff ? "-[tTZH] " : "",
547                                          whatis ? "-f " : "",
548                                          apropos ? "-k " : "",
549                                          catman ? "-c " : "",
550                                          print_where ? "-w " : "",
551                                          print_where_cat ? "-W " : "");
552                                 argp_error (state,
553                                             _("%s: incompatible options"),
554                                             badopts);
555                         }
556                         if (regex_opt + wildcard > 1) {
557                                 char *badopts = xasprintf
558                                         ("%s%s",
559                                          regex_opt ? "--regex " : "",
560                                          wildcard ? "--wildcard " : "");
561                                 argp_error (state,
562                                             _("%s: incompatible options"),
563                                             badopts);
564                         }
565                         return 0;
566         }
567         return ARGP_ERR_UNKNOWN;
568 }
569
570 static char *help_filter (int key, const char *text,
571                           void *input ATTRIBUTE_UNUSED)
572 {
573 #ifdef HAS_TROFF
574 # ifdef TROFF_IS_GROFF
575         static const char formatter[] = "groff";
576         const char *browser;
577 # else
578         static const char formatter[] = "troff";
579 # endif /* TROFF_IS_GROFF */
580 #endif /* HAS_TROFF */
581
582         switch (key) {
583 #ifdef HAS_TROFF
584                 case 't':
585                 case 'T':
586                         return xasprintf (text, formatter);
587 # ifdef TROFF_IS_GROFF
588                 case 'H':
589                         browser = html_pager;
590                         assert (browser);
591                         if (STRNEQ (browser, "exec ", 5))
592                                 browser += 5;
593                         return xasprintf (text, browser);
594 # endif /* TROFF_IS_GROFF */
595 #endif /* HAS_TROFF */
596                 default:
597                         return (char *) text;
598         }
599 }
600
601 static struct argp argp = { options, parse_opt, args_doc, 0, 0, help_filter };
602
603 /*
604  * changed these messages from stdout to stderr,
605  * (Fabrizio Polacco) Fri, 14 Feb 1997 01:30:07 +0200
606  */
607 static void gripe_no_name (const char *sect)
608 {
609         if (sect) {
610                 fprintf (stderr, _("No manual entry for %s\n"), sect);
611                 fprintf (stderr,
612                          _("(Alternatively, what manual page do you want from "
613                            "section %s?)\n"),
614                          sect);
615         } else
616                 fputs (_("What manual page do you want?\n"), stderr);
617
618         exit (FAIL);
619 }
620
621 /* In case we're set-id, double-check that our standard file descriptors are
622  * open in a standard way.  See:
623  *
624  *   http://austingroupbugs.net/view.php?id=173
625  */
626 static void check_standard_fds (void)
627 {
628         int flags, mode;
629
630         /* We can't even write an error message in this case, so check it
631          * first.
632          */
633         flags = fcntl (2, F_GETFL);
634         if (flags < 0)
635                 exit (FATAL);
636         mode = flags & O_ACCMODE;
637         if (mode != O_WRONLY && mode != O_RDWR)
638                 exit (FATAL);
639
640         flags = fcntl (0, F_GETFL);
641         if (flags < 0) {
642                 fprintf (stderr, "stdin not open!\n");
643                 exit (FATAL);
644         }
645         mode = flags & O_ACCMODE;
646         if (mode != O_RDONLY && mode != O_RDWR) {
647                 fprintf (stderr, "stdin not open for reading!\n");
648                 exit (FATAL);
649         }
650
651         flags = fcntl (1, F_GETFL);
652         if (flags < 0) {
653                 fprintf (stderr, "stdout not open!\n");
654                 exit (FATAL);
655         }
656         mode = flags & O_ACCMODE;
657         if (mode != O_WRONLY && mode != O_RDWR) {
658                 fprintf (stderr, "stdout not open for reading!\n");
659                 exit (FATAL);
660         }
661 }
662
663 static struct termios tms;
664 static int tms_set = 0;
665 static pid_t tms_pid = 0;
666
667 static void set_term (void)
668 {
669         if (tms_set && getpid () == tms_pid)
670                 tcsetattr (STDIN_FILENO, TCSANOW, &tms);
671 }
672
673 static void get_term (void)
674 {
675         if (isatty (STDOUT_FILENO)) {
676                 debug ("is a tty\n");
677                 tcgetattr (STDIN_FILENO, &tms);
678                 if (!tms_set++) {
679                         /* Work around pipecmd_exec calling exit(3) rather
680                          * than _exit(2), which means our atexit-registered
681                          * functions are called at the end of each child
682                          * process created using pipecmd_new_function and
683                          * friends.  It would probably be good to fix this
684                          * in libpipeline at some point, but it would
685                          * require care to avoid breaking compatibility.
686                          */
687                         tms_pid = getpid ();
688                         atexit (set_term);
689                 }
690         }
691 }
692
693 #if defined(TROFF_IS_GROFF) || defined(HEIRLOOM_NROFF)
694 static int get_roff_line_length (void)
695 {
696         int line_length = cat_width ? cat_width : get_line_length ();
697
698         /* groff >= 1.18 defaults to 78. */
699         if ((!troff || ditroff) && line_length != 80) {
700                 int length = line_length * 39 / 40;
701                 if (length > line_length - 2)
702                         return line_length - 2;
703                 else
704                         return length;
705         } else
706                 return 0;
707 }
708
709 #ifdef HEIRLOOM_NROFF
710 static void heirloom_line_length (void *data)
711 {
712         printf (".ll %sn\n", (const char *) data);
713         /* TODO: This fails to do anything useful.  Why? */
714         printf (".lt %sn\n", (const char *) data);
715         printf (".lf 1\n");
716 }
717 #endif /* HEIRLOOM_NROFF */
718
719 static pipecmd *add_roff_line_length (pipecmd *cmd, int *save_cat_p)
720 {
721         int length;
722         pipecmd *ret = NULL;
723
724         if (!catman) {
725                 int line_length = get_line_length ();
726                 debug ("Terminal width %d\n", line_length);
727                 if (line_length >= min_cat_width &&
728                     line_length <= max_cat_width)
729                         debug ("Terminal width %d within cat page range "
730                                "[%d, %d]\n",
731                                line_length, min_cat_width, max_cat_width);
732                 else {
733                         debug ("Terminal width %d not within cat page range "
734                                "[%d, %d]\n",
735                                line_length, min_cat_width, max_cat_width);
736                         *save_cat_p = 0;
737                 }
738         }
739
740         length = get_roff_line_length ();
741         if (length) {
742 #ifdef HEIRLOOM_NROFF
743                 char *name;
744                 char *lldata;
745                 pipecmd *llcmd;
746 #endif /* HEIRLOOM_NROFF */
747
748                 debug ("Using %d-character lines\n", length);
749 #if defined(TROFF_IS_GROFF)
750                 pipecmd_argf (cmd, "-rLL=%dn", length);
751                 pipecmd_argf (cmd, "-rLT=%dn", length);
752 #elif defined(HEIRLOOM_NROFF)
753                 name = xasprintf ("echo .ll %dn && echo .lt %dn",
754                                   length, length);
755                 lldata = xasprintf ("%d", length);
756                 llcmd = pipecmd_new_function (name, heirloom_line_length, free,
757                                               lldata);
758                 ret = pipecmd_new_sequence ("line-length", llcmd,
759                                             pipecmd_new_passthrough (), NULL);
760                 free (name);
761 #endif /* HEIRLOOM_NROFF */
762         }
763
764         return ret;
765 }
766 #endif /* TROFF_IS_GROFF || HEIRLOOM_NROFF */
767
768 static void gripe_no_man (const char *name, const char *sec)
769 {
770         /* On AIX and IRIX, fall back to the vendor supplied browser. */
771 #if defined _AIX || defined __sgi
772         if (!troff) {
773                 pipecmd *vendor_man;
774                 int i;
775
776                 vendor_man = pipecmd_new ("/usr/bin/man");
777                 for (i = 1; i < argc; ++i)
778                         pipecmd_arg (vendor_man, global_argv[i]);
779                 pipecmd_unsetenv (vendor_man, "MANPATH");
780                 pipecmd_exec (vendor_man);
781         }
782 #endif
783
784         if (sec)
785                 fprintf (stderr, _("No manual entry for %s in section %s\n"),
786                          name, sec);
787         else
788                 fprintf (stderr, _("No manual entry for %s\n"), name);
789
790 #ifdef UNDOC_COMMAND
791         if (getenv ("MAN_TEST_DISABLE_UNDOCUMENTED") == NULL &&
792             pathsearch_executable (name))
793                 fprintf (stderr,
794                          _("See '%s' for help when manual pages are not "
795                            "available.\n"), UNDOC_COMMAND);
796 #endif
797 }
798
799 /* fire up the appropriate external program */
800 static void do_extern (int argc, char *argv[])
801 {
802         pipeline *p;
803         pipecmd *cmd;
804
805         cmd = pipecmd_new (external);
806         /* Please keep these in the same order as they are in whatis.c. */
807         if (debug_level)
808                 pipecmd_arg (cmd, "-d");
809         if (local_man_file)  /* actually apropos/whatis --long */
810                 pipecmd_arg (cmd, "-l");
811         if (colon_sep_section_list)
812                 pipecmd_args (cmd, "-s", colon_sep_section_list, NULL);
813         if (alt_system_name)
814                 pipecmd_args (cmd, "-m", alt_system_name, NULL);
815         if (manp)
816                 pipecmd_args (cmd, "-M", manp, NULL);
817         if (locale)
818                 pipecmd_args (cmd, "-L", locale, NULL);
819         if (user_config_file)
820                 pipecmd_args (cmd, "-C", user_config_file, NULL);
821         while (first_arg < argc)
822                 pipecmd_arg (cmd, argv[first_arg++]);
823         p = pipeline_new_commands (cmd, NULL);
824
825         /* privs are already dropped */
826         exit (pipeline_run (p));
827 }
828
829 /* lookup $MANOPT and if available, put in *argv[] format for argp */
830 static char **manopt_to_env (int *argc)
831 {
832         char *manopt, *manopt_copy, *opt_start, **argv;
833
834         manopt = getenv ("MANOPT");
835         if (manopt == NULL || *manopt == '\0')
836                 return NULL;
837
838         opt_start = manopt = manopt_copy = xstrdup (manopt);
839
840         /* allocate space for the program name */
841         *argc = 0;
842         argv = XNMALLOC (*argc + 3, char *);
843         argv[(*argc)++] = base_name (program_name);
844         
845         /* for each [ \t]+ delimited string, allocate an array space and fill
846            it in. An escaped space is treated specially */      
847         while (*manopt) {
848                 switch (*manopt) {
849                         case ' ':
850                         case '\t':
851                                 if (manopt != opt_start) {
852                                         *manopt = '\0';
853                                         argv = xnrealloc (argv, *argc + 3,
854                                                           sizeof (char *));
855                                         argv[(*argc)++] = xstrdup (opt_start);
856                                 }
857                                 while (CTYPE (isspace, *(manopt + 1)))
858                                         *++manopt = '\0';
859                                 opt_start = manopt + 1;
860                                 break;
861                         case '\\':
862                                 if (*(manopt + 1) == ' ')
863                                         manopt++;
864                                 break;
865                         default:
866                                 break;
867                 }
868                 manopt++;
869         }
870
871         if (*opt_start)
872                 argv[(*argc)++] = xstrdup (opt_start);
873         argv[*argc] = NULL;                     
874
875         free (manopt_copy);
876         return argv;
877 }
878
879 /* Return char array with 'less' special chars escaped. Uses static storage. */
880 static const char *escape_less (const char *string)
881 {
882         static char *escaped_string; 
883         char *ptr;
884
885         /* 2*strlen will always be long enough to hold the escaped string */
886         ptr = escaped_string = xrealloc (escaped_string, 
887                                          2 * strlen (string) + 1);
888         
889         while (*string) {
890                 if (*string == '?' ||
891                     *string == ':' ||
892                     *string == '.' ||
893                     *string == '%' ||
894                     *string == '\\')
895                         *ptr++ = '\\';
896
897                 *ptr++ = *string++;
898         }
899
900         *ptr = *string;
901         return escaped_string;
902 }
903
904 #if defined(MAN_DB_CREATES) || defined(MAN_DB_UPDATES)
905 /* Run mandb to ensure databases are up to date. Only used with -u.
906  * Returns the exit status of mandb.
907  *
908  * If filename is non-NULL, uses mandb's -f option to update a single file.
909  */
910 static int run_mandb (int create, const char *manpath, const char *filename)
911 {
912         pipeline *mandb_pl = pipeline_new ();
913         pipecmd *mandb_cmd = pipecmd_new ("mandb");
914
915         if (debug_level)
916                 pipecmd_arg (mandb_cmd, "-d");
917         else
918                 pipecmd_arg (mandb_cmd, "-q");
919
920         if (user_config_file)
921                 pipecmd_args (mandb_cmd, "-C", user_config_file, NULL);
922
923         if (filename)
924                 pipecmd_args (mandb_cmd, "-f", filename, NULL);
925         else if (create) {
926                 pipecmd_arg (mandb_cmd, "-c");
927                 pipecmd_setenv (mandb_cmd, "MAN_MUST_CREATE", "1");
928         } else
929                 pipecmd_arg (mandb_cmd, "-p");
930
931         if (manpath)
932                 pipecmd_arg (mandb_cmd, manpath);
933
934         pipeline_command (mandb_pl, mandb_cmd);
935
936         if (debug_level) {
937                 debug ("running mandb: ");
938                 pipeline_dump (mandb_pl, stderr);
939         }
940
941         return pipeline_run (mandb_pl);
942 }
943 #endif /* MAN_DB_CREATES || MAN_DB_UPDATES */
944
945
946 static char *locale_manpath (const char *manpath)
947 {
948         char *all_locales;
949         char *new_manpath;
950
951         if (multiple_locale && *multiple_locale) {
952                 if (internal_locale && *internal_locale)
953                         all_locales = xasprintf ("%s:%s", multiple_locale,
954                                                  internal_locale);
955                 else
956                         all_locales = xstrdup (multiple_locale);
957         } else {
958                 if (internal_locale && *internal_locale)
959                         all_locales = xstrdup (internal_locale);
960                 else
961                         all_locales = NULL;
962         }
963
964         new_manpath = add_nls_manpaths (manpath, all_locales);
965         free (all_locales);
966
967         return new_manpath;
968 }
969
970 /*
971  * Check to see if the argument is a valid section number. 
972  * If the name matches one of
973  * the sections listed in section_list, we'll assume that it's a section.
974  * The list of sections in config.h simply allows us to specify oddly
975  * named directories like .../man3f.  Yuk.
976  */
977 static const char *is_section (const char *name)
978 {
979         const char **vs;
980
981         for (vs = section_list; *vs; vs++) {
982                 if (STREQ (*vs, name))
983                         return name;
984                 /* allow e.g. 3perl but disallow 8139too and libfoo */
985                 if (strlen (*vs) == 1 && CTYPE (isdigit, **vs) &&
986                     strlen (name) > 1 && !CTYPE (isdigit, name[1]) &&
987                     STRNEQ (*vs, name, 1))
988                         return name;
989         }
990         return NULL;
991 }
992
993 /* Snarf pre-processors from file, return string or NULL on failure */
994 static char *get_preprocessors_from_file (pipeline *decomp, int prefixes)
995 {
996 #ifdef PP_COOKIE
997         const size_t block = 4096;
998         int i;
999         char *line = NULL;
1000         size_t previous_len = 0;
1001
1002         if (!decomp)
1003                 return NULL;
1004
1005         /* Prefixes are inserted into the stream by man itself, and we must
1006          * skip over them to find any preprocessors line that exists.  Each
1007          * one ends with an .lf macro.
1008          */
1009         for (i = 0; ; ++i) {
1010                 size_t len = block * (i + 1);
1011                 const char *buffer, *scan, *end;
1012                 int j;
1013
1014                 scan = buffer = pipeline_peek (decomp, &len);
1015                 if (!buffer || len == 0)
1016                         return NULL;
1017
1018                 for (j = 0; j < prefixes; ++j) {
1019                         scan = memmem (scan, len - (scan - buffer),
1020                                        "\n.lf ", strlen ("\n.lf "));
1021                         if (!scan)
1022                                 break;
1023                         ++scan;
1024                         scan = memchr (scan, '\n', len - (scan - buffer));
1025                         if (!scan)
1026                                 break;
1027                         ++scan;
1028                 }
1029                 if (!scan)
1030                         continue;
1031
1032                 end = memchr (scan, '\n', len - (scan - buffer));
1033                 if (!end && len == previous_len)
1034                         /* end of file, no newline found */
1035                         end = buffer + len - 1;
1036                 if (end) {
1037                         line = xstrndup (scan, end - scan + 1);
1038                         break;
1039                 }
1040                 previous_len = len;
1041         }
1042         if (!line)
1043                 return NULL;
1044
1045         if (!strncmp (line, PP_COOKIE, 4)) {
1046                 const char *newline = strchr (line, '\n');
1047                 if (newline)
1048                         return xstrndup (line + 4, newline - (line + 4));
1049                 else
1050                         return xstrdup (line + 4);
1051         }
1052 #endif
1053         return NULL;
1054 }
1055
1056
1057 /* Determine pre-processors, set save_cat and return string */
1058 static char *get_preprocessors (pipeline *decomp, const char *dbfilters,
1059                                 int prefixes)
1060 {
1061         char *pp_string;
1062         const char *pp_source;
1063         const char *env;
1064
1065         /* try in order: database, command line, file, environment, default */
1066         /* command line overrides the database, but database empty overrides default */
1067         if (dbfilters && (dbfilters[0] != '-') && !preprocessors) {
1068                 pp_string = xstrdup (dbfilters);
1069                 pp_source = "database";
1070                 save_cat = 1;
1071         } else if (preprocessors) {
1072                 pp_string = xstrdup (preprocessors);
1073                 pp_source = "command line";
1074                 save_cat = 0;
1075         } else if ((pp_string = get_preprocessors_from_file (decomp,
1076                                                              prefixes))) {
1077                 pp_source = "file";
1078                 save_cat = 1;
1079         } else if ((env = getenv ("MANROFFSEQ"))) {
1080                 pp_string = xstrdup (env);
1081                 pp_source = "environment";
1082                 save_cat = 0;
1083         } else if (!dbfilters) {
1084                 pp_string = xstrdup (DEFAULT_MANROFFSEQ);
1085                 pp_source = "default";
1086                 save_cat = 1;
1087         } else {
1088                 pp_string = xstrdup ("");
1089                 pp_source = "no filters";
1090                 save_cat = 1;
1091         }
1092
1093         debug ("pre-processors `%s' from %s\n", pp_string, pp_source);
1094         return pp_string;
1095 }
1096
1097 static const char *my_locale_charset (void)
1098 {
1099         if (want_encoding && !is_roff_device (want_encoding))
1100                 return want_encoding;
1101         else
1102                 return get_locale_charset ();
1103 }
1104
1105 static void add_col (pipeline *p, const char *locale_charset, ...)
1106 {
1107         pipecmd *cmd;
1108         va_list argv;
1109         char *col_locale = NULL;
1110
1111         cmd = pipecmd_new (COL);
1112         va_start (argv, locale_charset);
1113         pipecmd_argv (cmd, argv);
1114         va_end (argv);
1115         pipecmd_pre_exec (cmd, sandbox_load, sandbox_free, sandbox);
1116
1117         if (locale_charset)
1118                 col_locale = find_charset_locale (locale_charset);
1119         if (col_locale) {
1120                 pipecmd_setenv (cmd, "LC_CTYPE", col_locale);
1121                 free (col_locale);
1122         }
1123
1124         pipeline_command (p, cmd);
1125 }
1126
1127 /* Return pipeline to format file to stdout. */
1128 static pipeline *make_roff_command (const char *dir, const char *file,
1129                                     pipeline *decomp, const char *pp_string,
1130                                     char **result_encoding)
1131 {
1132         const char *roff_opt;
1133         char *fmt_prog = NULL;
1134         pipeline *p = pipeline_new ();
1135         pipecmd *cmd;
1136         char *page_encoding = NULL;
1137         const char *output_encoding = NULL;
1138         const char *locale_charset = NULL;
1139
1140         *result_encoding = xstrdup ("UTF-8"); /* optimistic default */
1141
1142         roff_opt = getenv ("MANROFFOPT");
1143         if (!roff_opt)
1144                 roff_opt = "";
1145
1146         if (dir && !recode) {
1147 #ifdef ALT_EXT_FORMAT
1148                 char *catpath = get_catpath
1149                         (dir, global_manpath ? SYSTEM_CAT : USER_CAT);
1150
1151                 /* If we have an alternate catpath, look for an external
1152                  * formatter there.
1153                  */
1154                 if (catpath) {
1155                         fmt_prog = appendstr (catpath, "/",
1156                                               troff ? TFMT_PROG : NFMT_PROG, 
1157                                               NULL);
1158                         if (!CAN_ACCESS (fmt_prog, X_OK)) {
1159                                 free (fmt_prog);
1160                                 fmt_prog = NULL;
1161                         }
1162                 }
1163 #endif /* ALT_EXT_FORMAT */
1164
1165                 /* If the page is in a proper manual page hierarchy (as
1166                  * opposed to being read using --local-file or similar),
1167                  * look for an external formatter there.
1168                  */
1169                 if (!fmt_prog) {
1170                         fmt_prog = appendstr (NULL, dir, "/",
1171                                               troff ? TFMT_PROG : NFMT_PROG,
1172                                               NULL);
1173                         if (!CAN_ACCESS (fmt_prog, X_OK)) {
1174                                 free (fmt_prog);
1175                                 fmt_prog = NULL;
1176                         }
1177                 }
1178         }
1179
1180         if (fmt_prog)
1181                 debug ("External formatter %s\n", fmt_prog);
1182                                 
1183         if (!fmt_prog) {
1184                 /* we don't have an external formatter script */
1185                 const char *source_encoding, *roff_encoding;
1186                 const char *groff_preconv;
1187
1188                 if (!recode) {
1189                         struct zsoelim_stdin_data *zsoelim_data;
1190
1191                         zsoelim_data = zsoelim_stdin_data_new (dir,
1192                                                                manpathlist);
1193                         cmd = pipecmd_new_function (ZSOELIM, &zsoelim_stdin,
1194                                                     zsoelim_stdin_data_free,
1195                                                     zsoelim_data);
1196                         pipecmd_pre_exec (cmd, sandbox_load, sandbox_free,
1197                                           sandbox);
1198                         pipeline_command (p, cmd);
1199                 }
1200
1201                 page_encoding = check_preprocessor_encoding (decomp);
1202                 if (!page_encoding)
1203                         page_encoding = get_page_encoding (lang);
1204                 if (page_encoding && !STREQ (page_encoding, "UTF-8"))
1205                         source_encoding = page_encoding;
1206                 else
1207                         source_encoding = get_source_encoding (lang);
1208                 debug ("page_encoding = %s\n", page_encoding);
1209                 debug ("source_encoding = %s\n", source_encoding);
1210
1211                 /* Load the roff_device value dependent on the language dir
1212                  * in the path.
1213                  */
1214                 if (!troff) {
1215 #define STRC(s, otherwise) ((s) ? (s) : (otherwise))
1216
1217                         locale_charset = my_locale_charset ();
1218                         debug ("locale_charset = %s\n",
1219                                STRC (locale_charset, "NULL"));
1220
1221                         /* Pick the default device for this locale if there
1222                          * wasn't one selected explicitly.
1223                          */
1224                         if (!roff_device) {
1225                                 roff_device =
1226                                         get_default_device (locale_charset,
1227                                                             source_encoding);
1228 #ifdef HEIRLOOM_NROFF
1229                                 /* In Heirloom, if LC_CTYPE is a UTF-8
1230                                  * locale, then -Tlocale will be equivalent
1231                                  * to -Tutf8 except that it will do a
1232                                  * slightly better job of rendering some
1233                                  * special characters.
1234                                  */
1235                                 if (STREQ (roff_device, "utf8")) {
1236                                         const char *real_locale_charset =
1237                                                 get_locale_charset ();
1238                                         if (real_locale_charset &&
1239                                             STREQ (real_locale_charset,
1240                                                    "UTF-8"))
1241                                                 roff_device = "locale";
1242                                 }
1243 #endif /* HEIRLOOM_NROFF */
1244                                 debug ("roff_device (locale) = %s\n",
1245                                        STRC (roff_device, "NULL"));
1246                         }
1247                 }
1248
1249                 roff_encoding = get_roff_encoding (roff_device,
1250                                                    source_encoding);
1251                 debug ("roff_encoding = %s\n", roff_encoding);
1252
1253                 /* We may need to recode:
1254                  *   from page_encoding to roff_encoding on input;
1255                  *   from output_encoding to locale_charset on output
1256                  *     (if not troff).
1257                  * If we have preconv, then use it to recode the
1258                  * input to a safe escaped form.
1259                  * The --recode option overrides everything else.
1260                  */
1261                 groff_preconv = get_groff_preconv ();
1262                 if (recode)
1263                         add_manconv (p, page_encoding, recode);
1264                 else if (groff_preconv) {
1265                         pipecmd *preconv_cmd;
1266                         add_manconv (p, page_encoding, "UTF-8");
1267                         preconv_cmd = pipecmd_new_args
1268                                 (groff_preconv, "-e", "UTF-8", NULL);
1269                         pipecmd_pre_exec (preconv_cmd, sandbox_load,
1270                                           sandbox_free, sandbox);
1271                         pipeline_command (p, preconv_cmd);
1272                 } else if (roff_encoding)
1273                         add_manconv (p, page_encoding, roff_encoding);
1274                 else
1275                         add_manconv (p, page_encoding, page_encoding);
1276
1277                 if (!troff && !recode) {
1278                         output_encoding = get_output_encoding (roff_device);
1279                         if (!output_encoding)
1280                                 output_encoding = source_encoding;
1281                         debug ("output_encoding = %s\n", output_encoding);
1282                         free (*result_encoding);
1283                         *result_encoding = xstrdup (output_encoding);
1284
1285                         if (!getenv ("LESSCHARSET")) {
1286                                 const char *less_charset =
1287                                         get_less_charset (locale_charset);
1288                                 debug ("less_charset = %s\n", less_charset);
1289                                 setenv ("LESSCHARSET", less_charset, 1);
1290                         }
1291
1292                         if (!getenv ("JLESSCHARSET")) {
1293                                 const char *jless_charset =
1294                                         get_jless_charset (locale_charset);
1295                                 if (jless_charset) {
1296                                         debug ("jless_charset = %s\n",
1297                                                jless_charset);
1298                                         setenv ("JLESSCHARSET",
1299                                                 jless_charset, 1);
1300                                 }
1301                         }
1302                 }
1303         }
1304
1305         if (recode)
1306                 ;
1307         else if (!fmt_prog) {
1308 #ifndef GNU_NROFF
1309                 int using_tbl = 0;
1310 #endif /* GNU_NROFF */
1311
1312                 do {
1313 #ifdef NROFF_WARNINGS
1314                         struct string_llist *cur;
1315 #endif /* NROFF_WARNINGS */
1316                         int wants_dev = 0; /* filter wants a dev argument */
1317                         int wants_post = 0; /* postprocessor arguments */
1318
1319                         cmd = NULL;
1320                         /* set cmd according to *pp_string, on
1321                            errors leave cmd as NULL */
1322                         switch (*pp_string) {
1323                         case 'e':
1324                                 if (troff)
1325                                         cmd = pipecmd_new_argstr
1326                                                 (get_def ("eqn", EQN));
1327                                 else
1328                                         cmd = pipecmd_new_argstr
1329                                                 (get_def ("neqn", NEQN));
1330                                 wants_dev = 1;
1331                                 break;
1332                         case 'g':
1333                                 cmd = pipecmd_new_argstr
1334                                         (get_def ("grap", GRAP));
1335                                 break;
1336                         case 'p':
1337                                 cmd = pipecmd_new_argstr
1338                                         (get_def ("pic", PIC));
1339                                 break;
1340                         case 't':
1341                                 cmd = pipecmd_new_argstr
1342                                         (get_def ("tbl", TBL));
1343 #ifndef GNU_NROFF
1344                                 using_tbl = 1;
1345 #endif /* GNU_NROFF */
1346                                 break;
1347                         case 'v':
1348                                 cmd = pipecmd_new_argstr
1349                                         (get_def ("vgrind", VGRIND));
1350                                 break;
1351                         case 'r':
1352                                 cmd = pipecmd_new_argstr
1353                                         (get_def ("refer", REFER));
1354                                 break;
1355                         case ' ':
1356                         case '-':
1357                         case 0:
1358                                 /* done with preprocessors, now add roff */
1359                                 if (troff) {
1360                                         cmd = pipecmd_new_argstr
1361                                                 (get_def ("troff", TROFF));
1362                                         save_cat = 0;
1363                                 } else
1364                                         cmd = pipecmd_new_argstr
1365                                                 (get_def ("nroff", NROFF));
1366
1367 #ifdef TROFF_IS_GROFF
1368                                 if (troff && ditroff)
1369                                         pipecmd_arg (cmd, "-Z");
1370 #endif /* TROFF_IS_GROFF */
1371
1372 #if defined(TROFF_IS_GROFF) || defined(HEIRLOOM_NROFF)
1373                                 {
1374                                         pipecmd *seq = add_roff_line_length
1375                                                 (cmd, &save_cat);
1376                                         if (seq)
1377                                                 pipeline_command (p, seq);
1378                                 }
1379 #endif /* TROFF_IS_GROFF || HEIRLOOM_NROFF */
1380
1381 #ifdef NROFF_WARNINGS
1382                                 for (cur = roff_warnings; cur;
1383                                      cur = cur->next)
1384                                         pipecmd_argf (cmd, "-w%s", cur->name);
1385 #endif /* NROFF_WARNINGS */
1386
1387 #ifdef HEIRLOOM_NROFF
1388                                 if (running_setuid ())
1389                                         pipecmd_unsetenv (cmd, "TROFFMACS");
1390 #endif /* HEIRLOOM_NROFF */
1391
1392                                 pipecmd_argstr (cmd, roff_opt);
1393
1394                                 wants_dev = 1;
1395                                 wants_post = 1;
1396                                 break;
1397                         }
1398
1399                         if (!cmd) {
1400                                 assert (*pp_string); /* didn't fail on roff */
1401                                 error (0, 0,
1402                                        _("ignoring unknown preprocessor `%c'"),
1403                                        *pp_string);
1404                                 continue;
1405                         }
1406
1407                         if (wants_dev) {
1408                                 if (roff_device)
1409                                         pipecmd_argf (cmd,
1410                                                       "-T%s", roff_device);
1411 #ifdef TROFF_IS_GROFF
1412                                 else if (gxditview)
1413                                         pipecmd_argf (cmd, "-TX%s", gxditview);
1414 #endif /* TROFF_IS_GROFF */
1415                         }
1416
1417                         if (wants_post) {
1418 #ifdef TROFF_IS_GROFF
1419                                 if (gxditview)
1420                                         pipecmd_arg (cmd, "-X");
1421 #endif /* TROFF_IS_GROFF */
1422
1423                                 if (roff_device && STREQ (roff_device, "ps"))
1424                                         /* Tell grops to guess the page
1425                                          * size.
1426                                          */
1427                                         pipecmd_arg (cmd, "-P-g");
1428                         }
1429
1430                         pipecmd_pre_exec (cmd, sandbox_load_permissive,
1431                                           sandbox_free, sandbox);
1432                         pipeline_command (p, cmd);
1433
1434                         if (*pp_string == ' ' || *pp_string == '-')
1435                                 break;
1436                 } while (*pp_string++);
1437
1438                 if (!troff && *COL) {
1439                         const char *man_keep_formatting =
1440                                 getenv ("MAN_KEEP_FORMATTING");
1441                         if ((!man_keep_formatting || !*man_keep_formatting) &&
1442                             !isatty (STDOUT_FILENO))
1443                                 /* we'll run col later, but prepare for it */
1444                                 setenv ("GROFF_NO_SGR", "1", 1);
1445 #ifndef GNU_NROFF
1446                         /* tbl needs col */
1447                         else if (using_tbl && !troff && *COL)
1448                                 add_col (p, locale_charset, NULL);
1449 #endif /* GNU_NROFF */
1450                 }
1451         } else {
1452                 /* use external formatter script, it takes arguments
1453                    input file, preprocessor string, and (optional)
1454                    output device */
1455                 cmd = pipecmd_new_args (fmt_prog, file, pp_string, NULL);
1456                 if (roff_device)
1457                         pipecmd_arg (cmd, roff_device);
1458                 pipeline_command (p, cmd);
1459         }
1460
1461         free (page_encoding);
1462         return p;
1463 }
1464
1465 #ifdef TROFF_IS_GROFF
1466 /* Return pipeline to run a browser on a given file, observing
1467  * http://www.tuxedo.org/~esr/BROWSER/.
1468  *
1469  * (Actually, I really implement
1470  * https://www.dwheeler.com/browse/secure_browser.html, but it's
1471  * backward-compatible.)
1472  *
1473  * TODO: Is there any way to use the pipeline library better here?
1474  */
1475 static pipeline *make_browser (const char *pattern, const char *file)
1476 {
1477         pipeline *p;
1478         pipecmd *cmd;
1479         char *browser = xmalloc (1);
1480         int found_percent_s = 0;
1481         char *percent;
1482         char *esc_file;
1483
1484         *browser = '\0';
1485
1486         percent = strchr (pattern, '%');
1487         while (percent) {
1488                 size_t len = strlen (browser);
1489                 browser = xrealloc (browser, len + 1 + (percent - pattern));
1490                 strncat (browser, pattern, percent - pattern);
1491                 switch (*(percent + 1)) {
1492                         case '\0':
1493                         case '%':
1494                                 browser = appendstr (browser, "%", NULL);
1495                                 break;
1496                         case 'c':
1497                                 browser = appendstr (browser, ":", NULL);
1498                                 break;
1499                         case 's':
1500                                 esc_file = escape_shell (file);
1501                                 browser = appendstr (browser, esc_file, NULL);
1502                                 free (esc_file);
1503                                 found_percent_s = 1;
1504                                 break;
1505                         default:
1506                                 len = strlen (browser); /* cannot be NULL */
1507                                 browser = xrealloc (browser, len + 3);
1508                                 strncat (browser, percent, 2);
1509                                 break;
1510                 }
1511                 if (*(percent + 1))
1512                         pattern = percent + 2;
1513                 else
1514                         pattern = percent + 1;
1515                 percent = strchr (pattern, '%');
1516         }
1517         browser = appendstr (browser, pattern, NULL);
1518         if (!found_percent_s) {
1519                 esc_file = escape_shell (file);
1520                 browser = appendstr (browser, " ", esc_file, NULL);
1521                 free (esc_file);
1522         }
1523
1524         cmd = pipecmd_new_args ("/bin/sh", "-c", browser, NULL);
1525         pipecmd_pre_exec (cmd, drop_privs, NULL, NULL);
1526         p = pipeline_new_commands (cmd, NULL);
1527         pipeline_ignore_signals (p, 1);
1528         free (browser);
1529
1530         return p;
1531 }
1532 #endif /* TROFF_IS_GROFF */
1533
1534 static void setenv_less (pipecmd *cmd, const char *title)
1535 {
1536         const char *esc_title;
1537         char *less_opts, *man_pn;
1538
1539         esc_title = escape_less (title);
1540         less_opts = xasprintf (LESS_OPTS, prompt_string, prompt_string);
1541         less_opts = appendstr (less_opts, less, NULL);
1542         man_pn = strstr (less_opts, MAN_PN);
1543         while (man_pn) {
1544                 char *subst_opts =
1545                         xmalloc (strlen (less_opts) - strlen (MAN_PN) +
1546                                  strlen (esc_title) + 1);
1547                 strncpy (subst_opts, less_opts, man_pn - less_opts);
1548                 subst_opts[man_pn - less_opts] = '\0';
1549                 strcat (subst_opts, esc_title);
1550                 strcat (subst_opts, man_pn + strlen (MAN_PN));
1551                 free (less_opts);
1552                 less_opts = subst_opts;
1553                 man_pn = strstr (less_opts, MAN_PN);
1554         }
1555
1556         debug ("Setting LESS to %s\n", less_opts);
1557         pipecmd_setenv (cmd, "LESS", less_opts);
1558
1559         debug ("Setting MAN_PN to %s\n", esc_title);
1560         pipecmd_setenv (cmd, "MAN_PN", esc_title);
1561
1562         free (less_opts);
1563 }
1564
1565 static void add_output_iconv (pipeline *p,
1566                               const char *source, const char *target)
1567 {
1568         debug ("add_output_iconv: source %s, target %s\n", source, target);
1569         if (source && target && !STREQ (source, target)) {
1570                 char *target_translit = xasprintf ("%s//TRANSLIT", target);
1571                 pipecmd *iconv_cmd;
1572                 iconv_cmd = pipecmd_new_args
1573                         ("iconv", "-c", "-f", source, "-t", target_translit,
1574                          NULL);
1575                 pipecmd_pre_exec (iconv_cmd, sandbox_load, sandbox_free,
1576                                   sandbox);
1577                 pipeline_command (p, iconv_cmd);
1578                 free (target_translit);
1579         }
1580 }
1581
1582 /* Pipeline command to squeeze multiple blank lines into one.
1583  *
1584  */
1585 static void squeeze_blank_lines (void *data ATTRIBUTE_UNUSED)
1586 {
1587         char *line = NULL;
1588         size_t len = 0;
1589
1590         while (getline (&line, &len, stdin) != -1) {
1591                 int in_blank_line  = 1;
1592                 int got_blank_line = 0;
1593
1594                 while (in_blank_line) {
1595                         char *p;
1596                         for (p = line; *p; ++p) {
1597                                 if (!CTYPE (isspace, *p)) {
1598                                         in_blank_line = 0;
1599                                         break;
1600                                 }
1601                         }
1602
1603                         if (in_blank_line) {
1604                                 got_blank_line = 1;
1605                                 free (line);
1606                                 line = NULL;
1607                                 len  = 0;
1608                                 if (getline (&line, &len, stdin) == -1)
1609                                         break;
1610                         }
1611                 }
1612
1613                 if (got_blank_line && putchar ('\n') < 0)
1614                         break;
1615
1616                 if (!in_blank_line && fputs (line, stdout) < 0)
1617                         break;
1618
1619                 free (line);
1620                 line = NULL;
1621                 len  = 0;
1622         }
1623
1624         free (line);
1625         return;
1626 }
1627
1628 /* Return pipeline to display file provided on stdin.
1629  *
1630  * TODO: htmlout case is pretty weird now. I'd like the intelligence to be
1631  * somewhere other than format_display.
1632  */
1633 static pipeline *make_display_command (const char *encoding, const char *title)
1634 {
1635         pipeline *p = pipeline_new ();
1636         const char *locale_charset = NULL;
1637         pipecmd *pager_cmd = NULL;
1638
1639         locale_charset = my_locale_charset ();
1640
1641         if (!troff && (!want_encoding || !is_roff_device (want_encoding)))
1642                 add_output_iconv (p, encoding, locale_charset);
1643
1644         if (!troff && *COL) {
1645                 /* get rid of special characters if not writing to a
1646                  * terminal
1647                  */
1648                 const char *man_keep_formatting =
1649                         getenv ("MAN_KEEP_FORMATTING");
1650                 if ((!man_keep_formatting || !*man_keep_formatting) &&
1651                     !isatty (STDOUT_FILENO))
1652                         add_col (p, locale_charset, "-b", "-p", "-x", NULL);
1653         }
1654
1655         /* emulate pager -s, the sed code is just for information */
1656         {
1657                 pipecmd *cmd;
1658                 const char *name = "sed -e '/^[[:space:]]*$/{ N; /^[[:space:]]*\\n[[:space:]]*$/D; }'";
1659                 cmd = pipecmd_new_function (name, &squeeze_blank_lines, NULL, NULL);
1660                 pipeline_command (p, cmd);
1661         }
1662
1663         if (isatty (STDOUT_FILENO)) {
1664                 if (ascii) {
1665                         pipecmd *tr_cmd;
1666                         tr_cmd = pipecmd_new_argstr
1667                                 (get_def_user ("tr", TR TR_SET1 TR_SET2));
1668                         pipecmd_pre_exec (tr_cmd, sandbox_load, sandbox_free,
1669                                           sandbox);
1670                         pipeline_command (p, tr_cmd);
1671                         pager_cmd = pipecmd_new_argstr (pager);
1672                 } else
1673 #ifdef TROFF_IS_GROFF
1674                 if (!htmlout)
1675                         /* format_display deals with html_pager */
1676 #endif
1677                         pager_cmd = pipecmd_new_argstr (pager);
1678         }
1679
1680         if (pager_cmd) {
1681                 setenv_less (pager_cmd, title);
1682                 pipeline_command (p, pager_cmd);
1683         }
1684         pipeline_ignore_signals (p, 1);
1685
1686         if (!pipeline_get_ncommands (p))
1687                 /* Always return at least a dummy pipeline. */
1688                 pipeline_command (p, pipecmd_new_passthrough ());
1689         return p;
1690 }
1691
1692
1693 /* return a (malloced) temporary name in cat_file's directory */
1694 static char *tmp_cat_filename (const char *cat_file)
1695 {
1696         char *name;
1697
1698         if (debug_level) {
1699                 name = xstrdup ("/dev/null");
1700                 tmp_cat_fd = open (name, O_WRONLY);
1701         } else {
1702                 char *slash;
1703                 name = xstrdup (cat_file);
1704                 slash = strrchr (name, '/');
1705                 if (slash)
1706                         *(slash + 1) = '\0';
1707                 else
1708                         *name = '\0';
1709                 name = appendstr (name, "catXXXXXX", NULL);
1710                 tmp_cat_fd = mkstemp (name);
1711         }
1712
1713         if (tmp_cat_fd == -1) {
1714                 free (name);
1715                 return NULL;
1716         } else
1717                 return name;
1718 }
1719
1720
1721 /* If delete unlink tmp_cat, else commit tmp_cat to cat_file.
1722    Return non-zero on error.
1723  */
1724 static int commit_tmp_cat (const char *cat_file, const char *tmp_cat,
1725                            int delete)
1726 {
1727         int status = 0;
1728
1729 #ifdef MAN_OWNER
1730         if (!delete && global_manpath && euid == 0) {
1731                 if (debug_level) {
1732                         debug ("fixing temporary cat's ownership\n");
1733                         status = 0;
1734                 } else {
1735                         struct passwd *man_owner = get_man_owner ();
1736                         status = chown (tmp_cat, man_owner->pw_uid,
1737                                         man_owner->pw_gid);
1738                         if (status)
1739                                 error (0, errno, _("can't chown %s"), tmp_cat);
1740                 }
1741         }
1742 #endif /* MAN_OWNER */
1743
1744         if (!delete && !status) {
1745                 if (debug_level) {
1746                         debug ("fixing temporary cat's mode\n");
1747                         status = 0;
1748                 } else {
1749                         status = chmod (tmp_cat, CATMODE);
1750                         if (status)
1751                                 error (0, errno, _("can't chmod %s"), tmp_cat);
1752                 }
1753         }
1754
1755         if (!delete && !status) {
1756                 if (debug_level) {
1757                         debug ("renaming temporary cat to %s\n", cat_file);
1758                         status = 0;
1759                 } else {
1760                         status = rename (tmp_cat, cat_file);
1761                         if (status)
1762                                 error (0, errno, _("can't rename %s to %s"),
1763                                        tmp_cat, cat_file);
1764                 }
1765         }
1766
1767         if (!delete && !status) {
1768                 if (debug_level) {
1769                         debug ("setting modtime on cat file %s\n", cat_file);
1770                         status = 0;
1771                 } else {
1772                         struct timespec times[2];
1773
1774                         times[0].tv_sec = 0;
1775                         times[0].tv_nsec = UTIME_NOW;
1776                         times[1] = man_modtime;
1777                         status = utimens (cat_file, times);
1778                         if (status)
1779                                 error (0, errno, _("can't set times on %s"),
1780                                        cat_file);
1781                 }
1782         }
1783
1784         if (delete || status) {
1785                 if (debug_level)
1786                         debug ("unlinking temporary cat\n");
1787                 else if (unlink (tmp_cat))
1788                         error (0, errno, _("can't unlink %s"), tmp_cat);
1789         }
1790
1791         return status;
1792 }
1793
1794 /* TODO: This should all be refactored after work on the decompression
1795  * library is complete.
1796  */
1797 static void discard_stderr (pipeline *p)
1798 {
1799         int i;
1800
1801         for (i = 0; i < pipeline_get_ncommands (p); ++i)
1802                 pipecmd_discard_err (pipeline_get_command (p, i), 1);
1803 }
1804
1805 static void maybe_discard_stderr (pipeline *p)
1806 {
1807         const char *man_keep_stderr = getenv ("MAN_KEEP_STDERR");
1808         if ((!man_keep_stderr || !*man_keep_stderr) && isatty (STDOUT_FILENO))
1809                 discard_stderr (p);
1810 }
1811
1812 static void chdir_commands (pipeline *p, const char *dir)
1813 {
1814         int i;
1815
1816         for (i = 0; i < pipeline_get_ncommands (p); ++i)
1817                 pipecmd_chdir (pipeline_get_command (p, i), dir);
1818 }
1819
1820 #ifdef MAN_CATS
1821
1822 /* Return pipeline to write formatted manual page to for saving as cat file. */
1823 static pipeline *open_cat_stream (const char *cat_file, const char *encoding)
1824 {
1825         pipeline *cat_p;
1826 #  ifdef COMP_CAT
1827         pipecmd *comp_cmd;
1828 #  endif
1829
1830         created_tmp_cat = 0;
1831
1832         debug ("creating temporary cat for %s\n", cat_file);
1833
1834         tmp_cat_file = tmp_cat_filename (cat_file);
1835         if (tmp_cat_file)
1836                 created_tmp_cat = 1;
1837         else {
1838                 if (!debug_level && (errno == EACCES || errno == EROFS)) {
1839                         /* No permission to write to the cat file. Oh well,
1840                          * return NULL and let the caller sort it out.
1841                          */
1842                         debug ("can't write to temporary cat for %s\n",
1843                                cat_file);
1844                         return NULL;
1845                 } else
1846                         error (FATAL, errno,
1847                                _("can't create temporary cat for %s"),
1848                                cat_file);
1849         }
1850
1851         if (!debug_level)
1852                 push_cleanup ((cleanup_fun) unlink, tmp_cat_file, 1);
1853
1854         cat_p = pipeline_new ();
1855         add_output_iconv (cat_p, encoding, "UTF-8");
1856 #  ifdef COMP_CAT
1857         /* fork the compressor */
1858         comp_cmd = pipecmd_new_argstr (get_def ("compressor", COMPRESSOR));
1859         pipecmd_nice (comp_cmd, 10);
1860         pipecmd_pre_exec (comp_cmd, sandbox_load, sandbox_free, sandbox);
1861         pipeline_command (cat_p, comp_cmd);
1862 #  endif
1863         /* pipeline_start will close tmp_cat_fd */
1864         pipeline_want_out (cat_p, tmp_cat_fd);
1865
1866         return cat_p;
1867 }
1868
1869 /* Close the cat page stream, return non-zero on error.
1870    If delete don't update the cat file.
1871  */
1872 static int close_cat_stream (pipeline *cat_p, const char *cat_file,
1873                              int delete)
1874 {
1875         int status;
1876
1877         status = pipeline_wait (cat_p);
1878         debug ("cat-saver exited with status %d\n", status);
1879
1880         pipeline_free (cat_p);
1881
1882         if (created_tmp_cat) {
1883                 status |= commit_tmp_cat (cat_file, tmp_cat_file,
1884                                           delete || status);
1885                 if (!debug_level)
1886                         pop_cleanup ((cleanup_fun) unlink, tmp_cat_file);
1887         }
1888         free (tmp_cat_file);
1889         return status;
1890 }
1891
1892 /*
1893  * format a manual page with format_cmd, display it with disp_cmd, and
1894  * save it to cat_file
1895  */
1896 static int format_display_and_save (pipeline *decomp,
1897                                     pipeline *format_cmd,
1898                                     pipeline *disp_cmd,
1899                                     const char *cat_file, const char *encoding)
1900 {
1901         pipeline *sav_p = open_cat_stream (cat_file, encoding);
1902         int instat;
1903
1904         if (global_manpath)
1905                 drop_effective_privs ();
1906
1907         maybe_discard_stderr (format_cmd);
1908
1909         pipeline_connect (decomp, format_cmd, NULL);
1910         if (sav_p) {
1911                 pipeline_connect (format_cmd, disp_cmd, sav_p, NULL);
1912                 pipeline_pump (decomp, format_cmd, disp_cmd, sav_p, NULL);
1913         } else {
1914                 pipeline_connect (format_cmd, disp_cmd, NULL);
1915                 pipeline_pump (decomp, format_cmd, disp_cmd, NULL);
1916         }
1917
1918         if (global_manpath)
1919                 regain_effective_privs ();
1920
1921         pipeline_wait (decomp);
1922         instat = pipeline_wait (format_cmd);
1923         if (sav_p)
1924                 close_cat_stream (sav_p, cat_file, instat);
1925         pipeline_wait (disp_cmd);
1926         return instat;
1927 }
1928 #endif /* MAN_CATS */
1929
1930 /* Format a manual page with format_cmd and display it with disp_cmd.
1931  * Handle temporary file creation if necessary.
1932  * TODO: merge with format_display_and_save
1933  */
1934 static void format_display (pipeline *decomp,
1935                             pipeline *format_cmd, pipeline *disp_cmd,
1936                             const char *man_file)
1937 {
1938         int format_status = 0, disp_status = 0;
1939 #ifdef TROFF_IS_GROFF
1940         char *htmldir = NULL, *htmlfile = NULL;
1941 #endif /* TROFF_IS_GROFF */
1942
1943         if (format_cmd)
1944                 maybe_discard_stderr (format_cmd);
1945
1946         drop_effective_privs ();
1947
1948 #ifdef TROFF_IS_GROFF
1949         if (format_cmd && htmlout) {
1950                 char *man_base, *man_ext;
1951                 int htmlfd;
1952
1953                 htmldir = create_tempdir ("hman");
1954                 if (!htmldir)
1955                         error (FATAL, errno,
1956                                _("can't create temporary directory"));
1957                 chdir_commands (format_cmd, htmldir);
1958                 chdir_commands (disp_cmd, htmldir);
1959                 man_base = base_name (man_file);
1960                 man_ext = strchr (man_base, '.');
1961                 if (man_ext)
1962                         *man_ext = '\0';
1963                 htmlfile = xasprintf ("%s/%s.html", htmldir, man_base);
1964                 free (man_base);
1965                 htmlfd = open (htmlfile, O_CREAT | O_EXCL | O_WRONLY, 0644);
1966                 if (htmlfd == -1)
1967                         error (FATAL, errno, _("can't open temporary file %s"),
1968                                htmlfile);
1969                 pipeline_want_out (format_cmd, htmlfd);
1970                 pipeline_connect (decomp, format_cmd, NULL);
1971                 pipeline_pump (decomp, format_cmd, NULL);
1972                 pipeline_wait (decomp);
1973                 format_status = pipeline_wait (format_cmd);
1974         } else
1975 #endif /* TROFF_IS_GROFF */
1976             if (format_cmd) {
1977                 pipeline_connect (decomp, format_cmd, NULL);
1978                 pipeline_connect (format_cmd, disp_cmd, NULL);
1979                 pipeline_pump (decomp, format_cmd, disp_cmd, NULL);
1980                 pipeline_wait (decomp);
1981                 format_status = pipeline_wait (format_cmd);
1982                 disp_status = pipeline_wait (disp_cmd);
1983         } else {
1984                 pipeline_connect (decomp, disp_cmd, NULL);
1985                 pipeline_pump (decomp, disp_cmd, NULL);
1986                 pipeline_wait (decomp);
1987                 disp_status = pipeline_wait (disp_cmd);
1988         }
1989
1990 #ifdef TROFF_IS_GROFF
1991         if (format_cmd && htmlout) {
1992                 char *browser_list, *candidate;
1993
1994                 if (format_status) {
1995                         if (remove_directory (htmldir, 0) == -1)
1996                                 error (0, errno,
1997                                        _("can't remove directory %s"),
1998                                        htmldir);
1999                         free (htmlfile);
2000                         free (htmldir);
2001                         gripe_system (format_cmd, format_status);
2002                 }
2003
2004                 browser_list = xstrdup (html_pager);
2005                 for (candidate = strtok (browser_list, ":"); candidate;
2006                      candidate = strtok (NULL, ":")) {
2007                         pipeline *browser;
2008                         debug ("Trying browser: %s\n", candidate);
2009                         browser = make_browser (candidate, htmlfile);
2010                         disp_status = pipeline_run (browser);
2011                         if (!disp_status)
2012                                 break;
2013                 }
2014                 if (!candidate) {
2015                         if (html_pager && *html_pager)
2016                                 error (CHILD_FAIL, 0,
2017                                        "couldn't execute any browser from %s",
2018                                        html_pager);
2019                         else
2020                                 error (CHILD_FAIL, 0,
2021                                        "no browser configured, so cannot show "
2022                                        "HTML output");
2023                 }
2024                 free (browser_list);
2025                 if (remove_directory (htmldir, 0) == -1)
2026                         error (0, errno, _("can't remove directory %s"),
2027                                htmldir);
2028                 free (htmlfile);
2029                 free (htmldir);
2030         } else
2031 #endif /* TROFF_IS_GROFF */
2032         {
2033                 if (format_status && format_status != (SIGPIPE + 0x80) * 256)
2034                         gripe_system (format_cmd, format_status);
2035                 if (disp_status && disp_status != (SIGPIPE + 0x80) * 256)
2036                         gripe_system (disp_cmd, disp_status);
2037         }
2038
2039         regain_effective_privs ();
2040 }
2041
2042 /* "Display" a page in catman mode, which amounts to saving it. */
2043 /* TODO: merge with format_display_and_save? */
2044 static void display_catman (const char *cat_file, pipeline *decomp,
2045                             pipeline *format_cmd, const char *encoding)
2046 {
2047         char *tmpcat = tmp_cat_filename (cat_file);
2048 #ifdef COMP_CAT
2049         pipecmd *comp_cmd;
2050 #endif /* COMP_CAT */
2051         int status;
2052
2053         add_output_iconv (format_cmd, encoding, "UTF-8");
2054
2055 #ifdef COMP_CAT
2056         comp_cmd = pipecmd_new_argstr (get_def ("compressor", COMPRESSOR));
2057         pipecmd_pre_exec (comp_cmd, sandbox_load, sandbox_free, sandbox);
2058         pipeline_command (format_cmd, comp_cmd);
2059 #endif /* COMP_CAT */
2060
2061         maybe_discard_stderr (format_cmd);
2062         pipeline_want_out (format_cmd, tmp_cat_fd);
2063
2064         push_cleanup ((cleanup_fun) unlink, tmpcat, 1);
2065
2066         /* save the cat as real user
2067          * (1) required for user man hierarchy
2068          * (2) else depending on ruid's privs is ok, effectively disables
2069          *     catman for non-root.
2070          */
2071         drop_effective_privs ();
2072         pipeline_connect (decomp, format_cmd, NULL);
2073         pipeline_pump (decomp, format_cmd, NULL);
2074         pipeline_wait (decomp);
2075         status = pipeline_wait (format_cmd);
2076         regain_effective_privs ();
2077         if (status)
2078                 gripe_system (format_cmd, status);
2079
2080         close (tmp_cat_fd);
2081         commit_tmp_cat (cat_file, tmpcat, status);
2082         pop_cleanup ((cleanup_fun) unlink, tmpcat);
2083         free (tmpcat);
2084 }
2085
2086 static void disable_hyphenation (void *data ATTRIBUTE_UNUSED)
2087 {
2088         fputs (".nh\n"
2089                ".de hy\n"
2090                "..\n"
2091                ".lf 1\n", stdout);
2092 }
2093
2094 static void disable_justification (void *data ATTRIBUTE_UNUSED)
2095 {
2096         fputs (".na\n"
2097                ".de ad\n"
2098                "..\n"
2099                ".lf 1\n", stdout);
2100 }
2101
2102 #ifdef TROFF_IS_GROFF
2103 static void locale_macros (void *data)
2104 {
2105         const char *macro_lang = data;
2106         const char *hyphen_lang = STREQ (lang, "en") ? "us" : macro_lang;
2107
2108         debug ("Macro language %s; hyphenation language %s\n",
2109                macro_lang, hyphen_lang);
2110
2111         printf (
2112                 /* If we're using groff >= 1.20.2 (for the 'file' warning
2113                  * category):
2114                  */
2115                 ".if \\n[.g] \\{\\\n"
2116                 ".  ds Ystring \\n[.Y]\n"
2117                 ".  while (\\B'\\*[Ystring]' = 0) .chop Ystring\n"
2118                 ".  if ((\\n[.x] > 1) :"
2119                 " ((\\n[.x] == 1) & (\\n[.y] > 20)) :"
2120                 " ((\\n[.x] == 1) & (\\n[.y] == 20) & (\\*[Ystring] >= 2))) "
2121                 "\\{\\\n"
2122                 /*   disable warnings of category 'file' */
2123                 ".    warn (\\n[.warn] -"
2124                 " (\\n[.warn] / 1048576 %% 2 * 1048576))\n"
2125                 /*   and load the appropriate per-locale macros */
2126                 ".    mso %s.tmac\n"
2127                 ".  \\}\n"
2128                 ".  rm Ystring\n"
2129                 ".\\}\n"
2130                 /* set the hyphenation language anyway, to make sure groff
2131                  * only hyphenates languages it knows about
2132                  */
2133                 ".hla %s\n"
2134                 ".lf 1\n", macro_lang, hyphen_lang);
2135 }
2136 #endif /* TROFF_IS_GROFF */
2137
2138 /* allow user to skip a page or quit after viewing desired page 
2139    return 1 to skip
2140    return 0 to view
2141  */
2142 static int do_prompt (const char *name)
2143 {
2144         int ch;
2145         FILE *tty = NULL;
2146
2147         skip = 0;
2148         if (!isatty (STDOUT_FILENO) || !isatty (STDIN_FILENO))
2149                 return 0; /* noninteractive */
2150         tty = fopen ("/dev/tty", "r+");
2151         if (!tty)
2152                 return 0;
2153
2154         fprintf (tty, _( 
2155                  "--Man-- next: %s "
2156                  "[ view (return) | skip (Ctrl-D) | quit (Ctrl-C) ]\n"), 
2157                  name);
2158         fflush (tty);
2159
2160         do {
2161                 ch = getc (tty);
2162                 switch (ch) {
2163                         case '\n':
2164                                 fclose (tty);
2165                                 return 0;
2166                         case EOF:
2167                                 skip = 1;
2168                                 fclose (tty);
2169                                 return 1;
2170                         default:
2171                                 break;
2172                 }
2173         } while (1);
2174
2175         fclose (tty);
2176         return 0;
2177 }
2178
2179 /*
2180  * optionally chdir to dir, if necessary update cat_file from man_file
2181  * and display it.  if man_file is NULL cat_file is a stray cat.  If
2182  * !save_cat or cat_file is NULL we must not save the formatted cat.
2183  * If man_file is "" this is a special case -- we expect the man page
2184  * on standard input.
2185  */
2186 static int display (const char *dir, const char *man_file,
2187                     const char *cat_file, const char *title,
2188                     const char *dbfilters)
2189 {
2190         int found;
2191         static int prompt;
2192         int prefixes = 0;
2193         pipeline *format_cmd;   /* command to format man_file to stdout */
2194         char *formatted_encoding = NULL;
2195         int display_to_stdout;
2196         pipeline *decomp = NULL;
2197         int decomp_errno = 0;
2198
2199         /* define format_cmd */
2200         if (man_file) {
2201                 pipecmd *seq = pipecmd_new_sequence ("decompressor", NULL);
2202
2203                 if (*man_file)
2204                         decomp = decompress_open (man_file);
2205                 else
2206                         decomp = decompress_fdopen (dup (STDIN_FILENO));
2207
2208                 if (!recode && no_hyphenation) {
2209                         pipecmd *hcmd = pipecmd_new_function (
2210                                 "echo .nh && echo .de hy && echo ..",
2211                                 disable_hyphenation, NULL, NULL);
2212                         pipecmd_sequence_command (seq, hcmd);
2213                         ++prefixes;
2214                 }
2215
2216                 if (!recode && no_justification) {
2217                         pipecmd *jcmd = pipecmd_new_function (
2218                                 "echo .na && echo .de ad && echo ..",
2219                                 disable_justification, NULL, NULL);
2220                         pipecmd_sequence_command (seq, jcmd);
2221                         ++prefixes;
2222                 }
2223
2224 #ifdef TROFF_IS_GROFF
2225                 /* This only works with preconv, since the per-locale macros
2226                  * may change the assumed input encoding.
2227                  */
2228                 if (!recode && *man_file && get_groff_preconv ()) {
2229                         char *page_lang = lang_dir (man_file);
2230
2231                         if (page_lang && *page_lang &&
2232                             !STREQ (page_lang, "C")) {
2233                                 struct locale_bits bits;
2234                                 char *name;
2235                                 pipecmd *lcmd;
2236
2237                                 unpack_locale_bits (page_lang, &bits);
2238                                 name = xasprintf ("echo .mso %s.tmac",
2239                                                   bits.language);
2240                                 lcmd = pipecmd_new_function (
2241                                         name, locale_macros, free,
2242                                         xstrdup (bits.language));
2243                                 pipecmd_sequence_command (seq, lcmd);
2244                                 ++prefixes;
2245                                 free (name);
2246                                 free_locale_bits (&bits);
2247                         }
2248                         free (page_lang);
2249                 }
2250 #endif /* TROFF_IS_GROFF */
2251
2252                 if (prefixes) {
2253                         assert (pipeline_get_ncommands (decomp) <= 1);
2254                         if (pipeline_get_ncommands (decomp)) {
2255                                 pipecmd_sequence_command
2256                                         (seq,
2257                                          pipeline_get_command (decomp, 0));
2258                                 pipeline_set_command (decomp, 0, seq);
2259                         } else {
2260                                 pipecmd_sequence_command
2261                                         (seq, pipecmd_new_passthrough ());
2262                                 pipeline_command (decomp, seq);
2263                         }
2264                 } else
2265                         pipecmd_free (seq);
2266         }
2267
2268         if (decomp) {
2269                 char *pp_string;
2270
2271                 pipeline_start (decomp);
2272                 pp_string = get_preprocessors (decomp, dbfilters, prefixes);
2273                 format_cmd = make_roff_command (dir, man_file, decomp,
2274                                                 pp_string,
2275                                                 &formatted_encoding);
2276                 if (dir)
2277                         chdir_commands (format_cmd, dir);
2278                 debug ("formatted_encoding = %s\n", formatted_encoding);
2279                 free (pp_string);
2280         } else {
2281                 format_cmd = NULL;
2282                 decomp_errno = errno;
2283         }
2284
2285         /* Get modification time, for commit_tmp_cat(). */
2286         if (man_file && *man_file) {
2287                 struct stat stb;
2288                 if (stat (man_file, &stb)) {
2289                         man_modtime.tv_sec = 0;
2290                         man_modtime.tv_nsec = 0;
2291                 } else
2292                         man_modtime = get_stat_mtime (&stb);
2293         }
2294
2295         display_to_stdout = troff;
2296 #ifdef TROFF_IS_GROFF
2297         if (htmlout)
2298                 display_to_stdout = 0;
2299 #endif
2300         if (recode)
2301                 display_to_stdout = 1;
2302
2303         if (display_to_stdout) {
2304                 /* If we're reading stdin via '-l -', man_file is "". See
2305                  * below.
2306                  */
2307                 assert (man_file);
2308                 if (!decomp) {
2309                         assert (!format_cmd); /* no need to free it */
2310                         error (0, decomp_errno, _("can't open %s"), man_file);
2311                         return 0;
2312                 }
2313                 if (*man_file == '\0')
2314                         found = 1;
2315                 else
2316                         found = CAN_ACCESS (man_file, R_OK);
2317                 if (found) {
2318                         int status;
2319                         if (prompt && do_prompt (title)) {
2320                                 pipeline_free (format_cmd);
2321                                 pipeline_free (decomp);
2322                                 free (formatted_encoding);
2323                                 return 0;
2324                         }
2325                         drop_effective_privs ();
2326                         pipeline_connect (decomp, format_cmd, NULL);
2327                         pipeline_pump (decomp, format_cmd, NULL);
2328                         pipeline_wait (decomp);
2329                         status = pipeline_wait (format_cmd);
2330                         regain_effective_privs ();
2331                         if (status != 0)
2332                                 gripe_system (format_cmd, status);
2333                 }
2334         } else {
2335                 int format = 1;
2336                 int status;
2337
2338                 /* The caller should already have checked for any
2339                  * FSSTND-style (same hierarchy) cat page that may be
2340                  * present, and we don't expect to have to update the cat
2341                  * page in that case. If by some chance we do have to update
2342                  * it, then there's no harm trying; open_cat_stream() will
2343                  * refuse gracefully if the file isn't writeable.
2344                  */
2345
2346                 /* In theory we might be able to get away with saving cats
2347                  * for want_encoding, but it does change the roff device so
2348                  * perhaps that's best avoided.
2349                  */
2350                 if (want_encoding
2351 #ifdef TROFF_IS_GROFF
2352                     || htmlout
2353 #endif
2354                     || local_man_file
2355                     || recode
2356                     || disable_cache)
2357                         save_cat = 0;
2358
2359                 if (!man_file) {
2360                         /* Stray cat. */
2361                         assert (cat_file);
2362                         format = 0;
2363                 } else if (!cat_file) {
2364                         assert (man_file);
2365                         save_cat = 0;
2366                         format = 1;
2367                 } else if (format && save_cat) {
2368                         char *cat_dir;
2369                         char *tmp;
2370
2371                         status = is_changed (man_file, cat_file);
2372                         format = (status == -2) || ((status & 1) == 1);
2373
2374                         /* don't save if we haven't a cat directory */
2375                         cat_dir = xstrdup (cat_file);
2376                         tmp = strrchr (cat_dir, '/');
2377                         if (tmp)
2378                                 *tmp = 0;
2379                         save_cat = is_directory (cat_dir) == 1;
2380                         if (!save_cat)
2381                                 debug ("cat dir %s does not exist\n", cat_dir);
2382                         free (cat_dir);
2383                 }
2384
2385                 if (format && (!format_cmd || !decomp)) {
2386                         assert (man_file);
2387                         /* format_cmd is NULL iff decomp is NULL; no need to
2388                          * free either of them.
2389                          */
2390                         assert (!format_cmd);
2391                         assert (!decomp);
2392                         error (0, decomp_errno, _("can't open %s"), man_file);
2393                         return 0;
2394                 }
2395
2396                 /* if we're trying to read stdin via '-l -' then man_file
2397                  * will be "" which access() obviously barfs on, but all is
2398                  * well because the format_cmd will have been created to
2399                  * expect input via stdin. So we special-case this to avoid
2400                  * the bogus access() check.
2401                 */
2402                 if (format == 1 && *man_file == '\0')
2403                         found = 1;
2404                 else
2405                         found = CAN_ACCESS
2406                                 (format ? man_file : cat_file, R_OK);
2407
2408                 debug ("format: %d, save_cat: %d, found: %d\n",
2409                        format, save_cat, found);
2410
2411                 if (!found) {
2412                         pipeline_free (format_cmd);
2413                         pipeline_free (decomp);
2414                         return found;
2415                 }
2416
2417                 if (print_where || print_where_cat) {
2418                         int printed = 0;
2419                         if (print_where && man_file) {
2420                                 printf ("%s", man_file);
2421                                 printed = 1;
2422                         }
2423                         if (print_where_cat && cat_file && !format) {
2424                                 if (printed)
2425                                         putchar (' ');
2426                                 printf ("%s", cat_file);
2427                                 printed = 1;
2428                         }
2429                         if (printed)
2430                                 putchar ('\n');
2431                 } else if (catman) {
2432                         if (format) {
2433                                 if (!save_cat)
2434                                         error (0, 0,
2435                                                _("\ncannot write to "
2436                                                  "%s in catman mode"),
2437                                                cat_file);
2438                                 else
2439                                         display_catman (cat_file, decomp,
2440                                                         format_cmd,
2441                                                         formatted_encoding);
2442                         }
2443                 } else if (format) {
2444                         /* no cat or out of date */
2445                         pipeline *disp_cmd;
2446
2447                         if (prompt && do_prompt (title)) {
2448                                 pipeline_free (format_cmd);
2449                                 pipeline_free (decomp);
2450                                 free (formatted_encoding);
2451                                 if (local_man_file)
2452                                         return 1;
2453                                 else
2454                                         return 0;
2455                         }
2456
2457                         disp_cmd = make_display_command (formatted_encoding,
2458                                                          title);
2459
2460 #ifdef MAN_CATS
2461                         if (save_cat) {
2462                                 /* save cat */
2463                                 assert (disp_cmd); /* not htmlout for now */
2464                                 format_display_and_save (decomp,
2465                                                          format_cmd,
2466                                                          disp_cmd,
2467                                                          cat_file,
2468                                                          formatted_encoding);
2469                         } else 
2470 #endif /* MAN_CATS */
2471                                 /* don't save cat */
2472                                 format_display (decomp, format_cmd, disp_cmd,
2473                                                 man_file);
2474
2475                         pipeline_free (disp_cmd);
2476
2477                 } else {
2478                         /* display preformatted cat */
2479                         pipeline *disp_cmd;
2480                         pipeline *decomp_cat;
2481
2482                         if (prompt && do_prompt (title)) {
2483                                 pipeline_free (format_cmd);
2484                                 pipeline_free (decomp);
2485                                 return 0;
2486                         }
2487
2488                         decomp_cat = decompress_open (cat_file);
2489                         if (!decomp_cat) {
2490                                 error (0, errno, _("can't open %s"), cat_file);
2491                                 pipeline_free (format_cmd);
2492                                 pipeline_free (decomp);
2493                                 return 0;
2494                         }
2495                         disp_cmd = make_display_command ("UTF-8", title);
2496                         format_display (decomp_cat, NULL, disp_cmd, man_file);
2497                         pipeline_free (disp_cmd);
2498                         pipeline_free (decomp_cat);
2499                 }
2500         }
2501
2502         free (formatted_encoding);
2503
2504         pipeline_free (format_cmd);
2505         pipeline_free (decomp);
2506
2507         if (!prompt)
2508                 prompt = found;
2509
2510         return found;
2511 }
2512
2513 static void gripe_converting_name (const char *name) ATTRIBUTE_NORETURN;
2514 static void gripe_converting_name (const char *name)
2515 {
2516         error (FATAL, 0, _("Can't convert %s to cat name"), name);
2517         abort (); /* error should have exited; help compilers prove noreturn */
2518 }
2519
2520 /* Convert the trailing part of 'name' to be a cat page path by altering its
2521  * extension appropriately. If fsstnd is set, also try converting the
2522  * containing directory name from "man1" to "cat1" etc., returning NULL if
2523  * that doesn't work.
2524  *
2525  * fsstnd should only be set if name is the original path of a man page
2526  * found in a man hierarchy, not something like a symlink target or a file
2527  * named with 'man -l'. Otherwise, a symlink to "/home/manuel/foo.1.gz"
2528  * would be converted to "/home/catuel/foo.1.gz", which would be bad.
2529  */
2530 static char *convert_name (const char *name, int fsstnd)
2531 {
2532         char *to_name, *t1 = NULL;
2533         char *t2 = NULL;
2534 #ifdef COMP_SRC
2535         struct compression *comp;
2536 #endif /* COMP_SRC */
2537         char *namestem;
2538
2539 #ifdef COMP_SRC
2540         comp = comp_info (name, 1);
2541         if (comp)
2542                 namestem = comp->stem;
2543         else
2544 #endif /* COMP_SRC */
2545                 namestem = xstrdup (name);
2546
2547 #ifdef COMP_CAT
2548         /* TODO: BSD layout requires .0. */
2549         to_name = xasprintf ("%s.%s", namestem, COMPRESS_EXT);
2550 #else /* !COMP_CAT */
2551         to_name = xstrdup (namestem);
2552 #endif /* COMP_CAT */
2553         free (namestem);
2554
2555         if (fsstnd) {
2556                 t1 = strrchr (to_name, '/');
2557                 if (!t1)
2558                         gripe_converting_name (name);
2559                 *t1 = '\0';
2560
2561                 t2 = strrchr (to_name, '/');
2562                 if (!t2)
2563                         gripe_converting_name (name);
2564                 ++t2;
2565                 *t1 = '/';
2566
2567                 if (STRNEQ (t2, "man", 3)) {
2568                         /* If the second-last component starts with "man",
2569                          * replace "man" with "cat".
2570                          */
2571                         *t2 = 'c';
2572                         *(t2 + 2) = 't';
2573                 } else {
2574                         free (to_name);
2575                         debug ("couldn't convert %s to FSSTND cat file\n",
2576                                name);
2577                         return NULL;
2578                 }
2579         }
2580
2581         debug ("converted %s to %s\n", name, to_name);
2582
2583         return to_name;
2584 }
2585
2586 static char *find_cat_file (const char *path, const char *original,
2587                             const char *man_file)
2588 {
2589         size_t path_len = strlen (path);
2590         char *cat_file, *cat_path;
2591         int status;
2592
2593         /* Try the FSSTND way first, namely a cat page in the same hierarchy
2594          * as the original path to the man page. We don't create these
2595          * unless no alternate cat hierarchy is available, but will use them
2596          * if they happen to exist already and have the same timestamp as
2597          * the corresponding man page. (In practice I'm betting that this
2598          * means we'll hardly ever use them at all except for user
2599          * hierarchies; but compatibility, eh?)
2600          */
2601         cat_file = convert_name (original, 1);
2602         if (cat_file) {
2603                 status = is_changed (original, cat_file);
2604                 if (status != -2 && (!(status & 1)) == 1) {
2605                         debug ("found valid FSSTND cat file %s\n", cat_file);
2606                         return cat_file;
2607                 }
2608                 free (cat_file);
2609         }
2610
2611         /* Otherwise, find the cat page we actually want to use or create,
2612          * taking any alternate cat hierarchy into account. If the original
2613          * path and man_file differ (i.e. original was a symlink or .so
2614          * link), try the link target and then the source.
2615          */
2616         if (!STREQ (man_file, original)) {
2617                 global_manpath = is_global_mandir (man_file);
2618                 cat_path = get_catpath
2619                         (man_file, global_manpath ? SYSTEM_CAT : USER_CAT);
2620
2621                 if (cat_path) {
2622                         cat_file = convert_name (cat_path, 0);
2623                         free (cat_path);
2624                 } else if (STRNEQ (man_file, path, path_len) &&
2625                            man_file[path_len] == '/')
2626                         cat_file = convert_name (man_file, 1);
2627                 else
2628                         cat_file = NULL;
2629
2630                 if (cat_file) {
2631                         char *cat_dir = xstrdup (cat_file);
2632                         char *tmp = strrchr (cat_dir, '/');
2633                         if (tmp)
2634                                 *tmp = 0;
2635                         if (is_directory (cat_dir)) {
2636                                 debug ("will try cat file %s\n", cat_file);
2637                                 free (cat_dir);
2638                                 return cat_file;
2639                         } else
2640                                 debug ("cat dir %s does not exist\n", cat_dir);
2641                         free (cat_dir);
2642                 } else
2643                         debug ("no cat path for %s\n", man_file);
2644         }
2645
2646         global_manpath = is_global_mandir (original);
2647         cat_path = get_catpath
2648                 (original, global_manpath ? SYSTEM_CAT : USER_CAT);
2649
2650         if (cat_path) {
2651                 cat_file = convert_name (cat_path, 0);
2652                 free (cat_path);
2653         } else
2654                 cat_file = convert_name (original, 1);
2655
2656         if (cat_file)
2657                 debug ("will try cat file %s\n", cat_file);
2658         else
2659                 debug ("no cat path for %s\n", original);
2660
2661         return cat_file;
2662 }
2663
2664 static int get_ult_flags (char from_db, char id)
2665 {
2666         if (!from_db)
2667                 return ult_flags;
2668         else if (id == ULT_MAN)
2669                 /* Checking .so links is expensive, as we have to open the
2670                  * file. Therefore, if the database lists it as ULT_MAN,
2671                  * that's good enough for us and we won't recheck that. This
2672                  * does mean that if a page changes from ULT_MAN to SO_MAN
2673                  * then you might get duplicates until mandb is next run,
2674                  * but that isn't a big deal.
2675                  */
2676                 return ult_flags & ~SO_LINK;
2677         else
2678                 return ult_flags;
2679 }
2680
2681 /* Is this candidate substantially a duplicate of a previous one?
2682  * Returns non-zero if so, otherwise zero.
2683  */
2684 static int duplicate_candidates (struct candidate *left,
2685                                  struct candidate *right)
2686 {
2687         const char *slash1, *slash2;
2688         struct locale_bits bits1, bits2;
2689         int ret;
2690
2691         if (left->ult && right->ult && STREQ (left->ult, right->ult))
2692                 return 1; /* same ultimate source file */
2693
2694         if (!STREQ (left->source->name, right->source->name) ||
2695             !STREQ (left->source->sec, right->source->sec) ||
2696             !STREQ (left->source->ext, right->source->ext))
2697                 return 0; /* different name/section/extension */
2698
2699         if (STREQ (left->path, right->path))
2700                 return 1; /* same path */
2701
2702         /* Figure out if we've had a sufficiently similar candidate for this
2703          * language already.
2704          */
2705         slash1 = strrchr (left->path, '/');
2706         slash2 = strrchr (right->path, '/');
2707         if (!slash1 || !slash2 ||
2708             !STRNEQ (left->path, right->path,
2709                      MAX (slash1 - left->path, slash2 - right->path)))
2710                 return 0; /* different path base */
2711
2712         unpack_locale_bits (++slash1, &bits1);
2713         unpack_locale_bits (++slash2, &bits2);
2714
2715         if (!STREQ (bits1.language, bits2.language) ||
2716             !STREQ (bits1.territory, bits2.territory) ||
2717             !STREQ (bits1.modifier, bits2.modifier))
2718                 ret = 0; /* different language/territory/modifier */
2719         else
2720                 /* Everything seems to be the same; we can find nothing to
2721                  * choose between them.
2722                  */
2723                 ret = 1;
2724
2725         free_locale_bits (&bits1);
2726         free_locale_bits (&bits2);
2727         return ret;
2728 }
2729
2730 static int compare_candidates (const struct candidate *left,
2731                                const struct candidate *right)
2732 {
2733         const struct mandata *lsource = left->source, *rsource = right->source;
2734         int sec_left = 0, sec_right = 0;
2735         int cmp;
2736         const char *slash1, *slash2;
2737
2738         /* If one candidate matches the requested name exactly, sort it
2739          * first. This makes --ignore-case behave more sensibly.
2740          */
2741         /* name is never NULL here, see add_candidate() */
2742         if (STREQ (lsource->name, left->req_name)) {
2743                 if (!STREQ (rsource->name, right->req_name))
2744                         return -1;
2745         } else {
2746                 if (STREQ (rsource->name, right->req_name))
2747                         return 1;
2748         }
2749
2750         /* Compare pure sections first, then ids, then extensions.
2751          * Rationale: whatis refs get the same section and extension as
2752          * their source, but may be supplanted by a real page with a
2753          * slightly different extension, possibly in another hierarchy (!);
2754          * see Debian bug #204249 for the gory details.
2755          *
2756          * Any extension spelt out in full in section_list effectively
2757          * becomes a pure section; this allows extensions to be selectively
2758          * moved out of order with respect to their parent sections.
2759          */
2760         if (strcmp (lsource->ext, rsource->ext)) {
2761                 const char **sp;
2762
2763                 /* If the user asked for an explicit section, sort exact
2764                  * matches first.
2765                  */
2766                 if (section) {
2767                         if (STREQ (lsource->ext, section)) {
2768                                 if (!STREQ (rsource->ext, section))
2769                                         return -1;
2770                         } else {
2771                                 if (STREQ (rsource->ext, section))
2772                                         return 1;
2773                         }
2774                 }
2775
2776                 /* Find out whether lsource->ext is ahead of rsource->ext in
2777                  * section_list.
2778                  */
2779                 for (sp = section_list; *sp; ++sp) {
2780                         if (!*(*sp + 1)) {
2781                                 /* No extension */
2782                                 if (!sec_left  && **sp == *(lsource->ext))
2783                                         sec_left  = sp - section_list + 1;
2784                                 if (!sec_right && **sp == *(rsource->ext))
2785                                         sec_right = sp - section_list + 1;
2786                         } else if (STREQ (*sp, lsource->ext)) {
2787                                 sec_left  = sp - section_list + 1;
2788                         } else if (STREQ (*sp, rsource->ext)) {
2789                                 sec_right = sp - section_list + 1;
2790                         }
2791                         /* Keep looking for a more specific match */
2792                 }
2793                 if (sec_left != sec_right)
2794                         return sec_left - sec_right;
2795
2796                 cmp = strcmp (lsource->sec, rsource->sec);
2797                 if (cmp)
2798                         return cmp;
2799         }
2800
2801         /* ULT_MAN comes first, etc. Consider SO_MAN equivalent to ULT_MAN. */
2802         cmp = compare_ids (lsource->id, rsource->id, 1);
2803         if (cmp)
2804                 return cmp;
2805
2806         /* The order in section_list has already been compared above. For
2807          * everything not mentioned explicitly there, we just compare
2808          * lexically.
2809          */
2810         cmp = strcmp (lsource->ext, rsource->ext);
2811         if (cmp)
2812                 return cmp;
2813
2814         /* Try comparing based on language. We used to prefer to display a
2815          * page in the user's preferred language than a page from a better
2816          * section, but that attracted objections, so now we prefer to get
2817          * the section right. See Debian bug #519547.
2818          */
2819         slash1 = strrchr (left->path, '/');
2820         slash2 = strrchr (right->path, '/');
2821         if (slash1 && slash2) {
2822                 char *locale_copy, *p;
2823                 struct locale_bits bits1, bits2, lbits;
2824                 const char *codeset1, *codeset2;
2825
2826                 unpack_locale_bits (++slash1, &bits1);
2827                 unpack_locale_bits (++slash2, &bits2);
2828
2829                 /* We need the current locale as well. */
2830                 locale_copy = xstrdup (internal_locale);
2831                 p = strchr (locale_copy, ':');
2832                 if (p)
2833                         *p = '\0';
2834                 unpack_locale_bits (locale_copy, &lbits);
2835                 free (locale_copy);
2836
2837 #define COMPARE_LOCALE_ELEMENTS(elt) do { \
2838         /* For different elements, prefer one that matches the locale if
2839          * possible.
2840          */ \
2841         if (*lbits.elt) { \
2842                 if (STREQ (lbits.elt, bits1.elt)) { \
2843                         if (!STREQ (lbits.elt, bits2.elt)) { \
2844                                 cmp = -1; \
2845                                 goto out; \
2846                         } \
2847                 } else { \
2848                         if (STREQ (lbits.elt, bits2.elt)) { \
2849                                 cmp = 1; \
2850                                 goto out; \
2851                         } \
2852                 } \
2853         } \
2854         cmp = strcmp (bits1.territory, bits2.territory); \
2855         if (cmp) \
2856                 /* No help from locale; might as well sort lexically. */ \
2857                 goto out; \
2858 } while (0)
2859
2860                 COMPARE_LOCALE_ELEMENTS (language);
2861                 COMPARE_LOCALE_ELEMENTS (territory);
2862                 COMPARE_LOCALE_ELEMENTS (modifier);
2863
2864 #undef COMPARE_LOCALE_ELEMENTS
2865
2866                 /* Prefer UTF-8 if available. Otherwise, consider them
2867                  * equal.
2868                  */
2869                 codeset1 = get_canonical_charset_name (bits1.codeset);
2870                 codeset2 = get_canonical_charset_name (bits2.codeset);
2871                 if (STREQ (codeset1, "UTF-8")) {
2872                         if (!STREQ (codeset2, "UTF-8")) {
2873                                 cmp = -1;
2874                                 goto out;
2875                         }
2876                 } else {
2877                         if (STREQ (codeset2, "UTF-8")) {
2878                                 cmp = 1;
2879                                 goto out;
2880                         }
2881                 }
2882
2883 out:
2884                 free_locale_bits (&lbits);
2885                 free_locale_bits (&bits1);
2886                 free_locale_bits (&bits2);
2887                 if (cmp)
2888                         return cmp;
2889         }
2890
2891         /* Explicitly stabilise the sort as a last resort, so that manpath
2892          * ordering (e.g. language-specific hierarchies) works.
2893          */
2894         if (left->add_index < right->add_index)
2895                 return -1;
2896         else if (left->add_index > right->add_index)
2897                 return 1;
2898         else
2899                 return 0;
2900
2901         return 0;
2902 }
2903
2904 static int compare_candidates_qsort (const void *l, const void *r)
2905 {
2906         const struct candidate *left = *(const struct candidate **)l;
2907         const struct candidate *right = *(const struct candidate **)r;
2908
2909         return compare_candidates (left, right);
2910 }
2911
2912 static void free_candidate (struct candidate *candidate)
2913 {
2914         if (candidate)
2915                 free (candidate->ult);
2916         free (candidate);
2917 }
2918
2919 /* Add an entry to the list of candidates. */
2920 static int add_candidate (struct candidate **head, char from_db, char cat,
2921                           const char *req_name, const char *path,
2922                           const char *ult, struct mandata *source)
2923 {
2924         struct candidate *search, *prev, *insert, *candp;
2925         static int add_index = 0;
2926
2927         if (!ult) {
2928                 const char *name;
2929                 char *filename;
2930
2931                 if (*source->pointer != '-')
2932                         name = source->pointer;
2933                 else if (source->name)
2934                         name = source->name;
2935                 else
2936                         name = req_name;
2937
2938                 filename = make_filename (path, name, source, cat ? "cat" : "man");
2939                 if (!filename)
2940                         return 0;
2941                 ult = ult_src (filename, path, NULL,
2942                                get_ult_flags (from_db, source->id), NULL);
2943                 free (filename);
2944         }
2945
2946         debug ("candidate: %d %d %s %s %s %c %s %s %s\n",
2947                from_db, cat, req_name, path, ult,
2948                source->id, source->name ? source->name : "-",
2949                source->sec, source->ext);
2950
2951         if (!source->name)
2952                 source->name = xstrdup (req_name);
2953
2954         candp = XMALLOC (struct candidate);
2955         candp->req_name = req_name;
2956         candp->from_db = from_db;
2957         candp->cat = cat;
2958         candp->path = path;
2959         candp->ult = ult ? xstrdup (ult) : NULL;
2960         candp->source = source;
2961         candp->add_index = add_index++;
2962         candp->next = NULL;
2963
2964         /* insert will be NULL (insert at start) or a pointer to the element
2965          * after which this element should be inserted.
2966          */
2967         insert = NULL;
2968         search = *head;
2969         prev = NULL;
2970         /* This search produces quadratic-time behaviour, although in
2971          * practice it doesn't seem to be too bad at the moment since the
2972          * run-time is dominated by calls to ult_src. In future it might be
2973          * worth optimising this; the reason I haven't done this yet is that
2974          * it involves quite a bit of tedious bookkeeping. A practical
2975          * approach would be to keep two hashes, one that's just a set to
2976          * keep track of whether candp->ult has been seen already, and one
2977          * that keeps a list of candidates for each candp->name that could
2978          * then be quickly checked by brute force.
2979          */
2980         while (search) {
2981                 int dupcand = duplicate_candidates (candp, search);
2982
2983                 debug ("search: %d %d %s %s %s %c %s %s %s "
2984                        "(dup: %d)\n",
2985                        search->from_db, search->cat, search->req_name,
2986                        search->path, search->ult, search->source->id,
2987                        search->source->name ? search->source->name : "-",
2988                        search->source->sec, search->source->ext, dupcand);
2989
2990                 /* Check for duplicates. */
2991                 if (dupcand) {
2992                         int cmp = compare_candidates (candp, search);
2993
2994                         if (cmp >= 0) {
2995                                 debug ("other duplicate is at least as "
2996                                        "good\n");
2997                                 free_candidate (candp);
2998                                 return 0;
2999                         } else {
3000                                 debug ("this duplicate is better; removing "
3001                                        "old one\n");
3002                                 if (prev) {
3003                                         prev->next = search->next;
3004                                         free_candidate (search);
3005                                         search = prev->next;
3006                                 } else {
3007                                         *head = search->next;
3008                                         free_candidate (search);
3009                                         search = *head;
3010                                 }
3011                                 continue;
3012                         }
3013                 }
3014
3015                 prev = search;
3016                 if (search->next)
3017                         search = search->next;
3018                 else
3019                         break;
3020         }
3021         /* Insert the new candidate at the end of the list (having had to go
3022          * through them all looking for duplicates anyway); we'll sort it
3023          * into place later.
3024          */
3025         insert = prev;
3026
3027         candp->next = insert ? insert->next : *head;
3028         if (insert)
3029                 insert->next = candp;
3030         else
3031                 *head = candp;
3032
3033         return 1;
3034 }
3035
3036 /* Sort the entire list of candidates. */
3037 static void sort_candidates (struct candidate **candidates)
3038 {
3039         struct candidate *cand, **allcands;
3040         size_t count = 0, i;
3041
3042         for (cand = *candidates; cand; cand = cand->next)
3043                 ++count;
3044
3045         if (count == 0)
3046                 return;
3047
3048         allcands = XNMALLOC (count, struct candidate *);
3049         i = 0;
3050         for (cand = *candidates; cand; cand = cand->next) {
3051                 assert (i < count);
3052                 allcands[i++] = cand;
3053         }
3054         assert (i == count);
3055
3056         qsort (allcands, count, sizeof *allcands, compare_candidates_qsort);
3057
3058         *candidates = cand = allcands[0];
3059         for (i = 1; i < count; ++i) {
3060                 cand->next = allcands[i];
3061                 cand = cand->next;
3062         }
3063         cand->next = NULL;
3064
3065         free (allcands);
3066 }
3067
3068 /*
3069  * See if the preformatted man page or the source exists in the given
3070  * section.
3071  */
3072 static int try_section (const char *path, const char *sec, const char *name,
3073                         struct candidate **cand_head)
3074 {
3075         int found = 0;
3076         char **names = NULL, **np;
3077         size_t names_len = 0;
3078         char cat = 0;
3079         int lff_opts = (match_case ? LFF_MATCHCASE : 0) |
3080                        (regex_opt ? LFF_REGEX : 0) |
3081                        (wildcard ? LFF_WILDCARD : 0);
3082
3083         debug ("trying section %s with globbing\n", sec);
3084
3085 #ifndef NROFF_MISSING /* #ifdef NROFF */
3086         /*
3087          * Look for man page source files.
3088          */
3089
3090         names = look_for_file (path, sec, name, 0, lff_opts);
3091         if (!names)
3092                 /*
3093                  * No files match.  
3094                  * See if there's a preformatted page around that
3095                  * we can display.
3096                  */
3097 #endif /* NROFF_MISSING */
3098         {
3099                 if (catman)
3100                         return 1;
3101
3102                 if (!troff && !want_encoding && !recode) {
3103                         names = look_for_file (path, sec, name, 1, lff_opts);
3104                         cat = 1;
3105                 }
3106         }
3107
3108         for (np = names; np && *np; np++)
3109                 ++names_len;
3110         order_files (path, names, names_len);
3111
3112         for (np = names; np && *np; np++) {
3113                 struct mandata *info = infoalloc ();
3114                 char *info_buffer = filename_info (*np, info, name);
3115                 const char *ult;
3116                 int f;
3117
3118                 if (!info_buffer) {
3119                         free_mandata_struct (info);
3120                         continue;
3121                 }
3122                 info->addr = info_buffer;
3123
3124                 /* What kind of page is this? Since it's a real file, it
3125                  * must be either ULT_MAN or SO_MAN. ult_src() can tell us
3126                  * which.
3127                  */
3128                 ult = ult_src (*np, path, NULL, ult_flags, NULL);
3129                 if (!ult) {
3130                         /* already warned */
3131                         debug ("try_section(): bad link %s\n", *np);
3132                         free (info_buffer);
3133                         info->addr = NULL;
3134                         free_mandata_struct (info);
3135                         continue;
3136                 }
3137                 if (STREQ (ult, *np))
3138                         info->id = ULT_MAN;
3139                 else
3140                         info->id = SO_MAN;
3141
3142                 f = add_candidate (cand_head, CANDIDATE_FILESYSTEM,
3143                                    cat, name, path, ult, info);
3144                 found += f;
3145                 /* Free info and info_buffer if they weren't added to the
3146                  * candidates.
3147                  */
3148                 if (f == 0) {
3149                         free (info_buffer);
3150                         info->addr = NULL;
3151                         free_mandata_struct (info);
3152                 }
3153                 /* Don't free info and info_buffer here. */
3154         }
3155
3156         return found;
3157 }
3158
3159 static int display_filesystem (struct candidate *candp)
3160 {
3161         char *filename = make_filename (candp->path, NULL, candp->source,
3162                                         candp->cat ? "cat" : "man");
3163         char *title;
3164         int found = 0;
3165
3166         if (!filename)
3167                 return 0;
3168         /* source->name is never NULL thanks to add_candidate() */
3169         title = xasprintf ("%s(%s)", candp->source->name, candp->source->ext);
3170
3171         if (candp->cat) {
3172                 if (troff || want_encoding || recode)
3173                         goto out;
3174                 found = display (candp->path, NULL, filename, title, NULL);
3175         } else {
3176                 const char *man_file;
3177                 char *cat_file;
3178
3179                 man_file = ult_src (filename, candp->path, NULL, ult_flags,
3180                                     NULL);
3181                 if (man_file == NULL)
3182                         goto out;
3183
3184                 debug ("found ultimate source file %s\n", man_file);
3185                 lang = lang_dir (man_file);
3186
3187                 cat_file = find_cat_file (candp->path, filename, man_file);
3188                 found = display (candp->path, man_file, cat_file, title, NULL);
3189                 free (cat_file);
3190                 free (lang);
3191                 lang = NULL;
3192         }
3193
3194 out:
3195         free (title);
3196         free (filename);
3197         return found;
3198 }
3199
3200 #ifdef MAN_DB_UPDATES
3201 /* wrapper to dbdelete which deals with opening/closing the db */
3202 static void dbdelete_wrapper (const char *page, struct mandata *info)
3203 {
3204         if (!catman) {
3205                 MYDBM_FILE dbf;
3206
3207                 dbf = MYDBM_RWOPEN (database);
3208                 if (dbf) {
3209                         if (dbdelete (dbf, page, info) == 1)
3210                                 debug ("%s(%s) not in db!\n", page, info->ext);
3211                         MYDBM_CLOSE (dbf);
3212                 }
3213         }
3214 }
3215 #endif /* MAN_DB_UPDATES */
3216
3217 /* This started out life as try_section, but a lot of that routine is 
3218    redundant wrt the db cache. */
3219 static int display_database (struct candidate *candp)
3220 {
3221         int found = 0;
3222         char *file;
3223         const char *name;
3224         char *title;
3225         struct mandata *in = candp->source;
3226
3227         debug ("trying a db located file.\n");
3228         dbprintf (in);
3229
3230         /* if the pointer holds some data, this is a reference to the 
3231            real page, use that instead. */
3232         if (*in->pointer != '-')
3233                 name = in->pointer;
3234         else if (in->name)
3235                 name = in->name;
3236         else
3237                 name = candp->req_name;
3238
3239         if (in->id == WHATIS_MAN || in->id == WHATIS_CAT)
3240                 debug (_("%s: relying on whatis refs is deprecated\n"), name);
3241
3242         title = xasprintf ("%s(%s)",
3243                            in->name ? in->name : candp->req_name, in->ext);
3244
3245 #ifndef NROFF_MISSING /* #ifdef NROFF */
3246         /*
3247          * Look for man page source files.
3248          */
3249
3250         if (in->id < STRAY_CAT) {       /* There should be a src page */
3251                 file = make_filename (candp->path, name, in, "man");
3252                 if (file) {
3253                         const char *man_file;
3254                         char *cat_file;
3255
3256                         man_file = ult_src (file, candp->path, NULL,
3257                                             get_ult_flags (1, in->id), NULL);
3258                         if (man_file == NULL) {
3259                                 free (title);
3260                                 return found; /* zero */
3261                         }
3262
3263                         debug ("found ultimate source file %s\n", man_file);
3264                         lang = lang_dir (man_file);
3265
3266                         cat_file = find_cat_file (candp->path, file, man_file);
3267                         found += display (candp->path, man_file, cat_file,
3268                                           title, in->filter);
3269                         free (cat_file);
3270                         free (lang);
3271                         lang = NULL;
3272                         free (file);
3273                 } /* else {drop through to the bottom and return 0 anyway} */
3274         } else
3275
3276 #endif /* NROFF_MISSING */
3277
3278         if (in->id <= WHATIS_CAT) {
3279                 /* The db says we have a stray cat or whatis ref */
3280
3281                 if (catman) {
3282                         free (title);
3283                         return ++found;
3284                 }
3285
3286                 /* show this page but force an update later to make sure
3287                    we haven't just added the new page */
3288                 found_a_stray = 1;
3289
3290                 /* If explicitly asked for troff or a different encoding,
3291                  * don't show a stray cat.
3292                  */
3293                 if (troff || want_encoding || recode) {
3294                         free (title);
3295                         return found;
3296                 }
3297
3298                 file = make_filename (candp->path, name, in, "cat");
3299                 if (!file) {
3300                         char *catpath;
3301                         catpath = get_catpath (candp->path,
3302                                                global_manpath ? SYSTEM_CAT
3303                                                               : USER_CAT);
3304
3305                         if (catpath && strcmp (catpath, candp->path) != 0) {
3306                                 file = make_filename (catpath, name,
3307                                                       in, "cat");
3308                                 free (catpath);
3309                                 if (!file) {
3310                                         /* don't delete here, 
3311                                            return==0 will do that */
3312                                         free (title);
3313                                         return found; /* zero */
3314                                 }
3315                         } else {
3316                                 free (catpath);
3317                                 free (title);
3318                                 return found; /* zero */
3319                         }
3320                 }
3321
3322                 found += display (candp->path, NULL, file, title, in->filter);
3323                 free (file);
3324         }
3325         free (title);
3326         return found;
3327 }
3328
3329 /* test for existence, if fail: call dbdelete_wrapper, else return amount */
3330 static int display_database_check (struct candidate *candp)
3331 {
3332         int exists = display_database (candp);
3333
3334 #ifdef MAN_DB_UPDATES
3335         if (!exists && !skip) {
3336                 debug ("dbdelete_wrapper (%s, %p)\n",
3337                        candp->req_name, candp->source);
3338                 dbdelete_wrapper (candp->req_name, candp->source);
3339         }
3340 #endif /* MAN_DB_UPDATES */
3341
3342         return exists;
3343 }
3344
3345 static void db_hashtable_free (void *defn)
3346 {
3347         free_mandata_struct (defn);
3348 }
3349
3350 #ifdef MAN_DB_UPDATES
3351 static int maybe_update_file (const char *manpath, const char *name,
3352                               struct mandata *info)
3353 {
3354         const char *real_name;
3355         char *file;
3356         struct stat buf;
3357         struct timespec file_mtime;
3358         int status;
3359
3360         if (!update)
3361                 return 0;
3362
3363         /* If the pointer holds some data, then we need to look at that
3364          * name in the filesystem instead.
3365          */
3366         if (!STRNEQ (info->pointer, "-", 1))
3367                 real_name = info->pointer;
3368         else if (info->name)
3369                 real_name = info->name;
3370         else
3371                 real_name = name;
3372
3373         file = make_filename (manpath, real_name, info, "man");
3374         if (!file)
3375                 return 0;
3376         if (lstat (file, &buf) != 0)
3377                 return 0;
3378         file_mtime = get_stat_mtime (&buf);
3379         if (timespec_cmp (file_mtime, info->mtime) == 0)
3380                 return 0;
3381
3382         debug ("%s needs to be recached: %ld.%09ld %ld.%09ld\n",
3383                file,
3384                (long) info->mtime.tv_sec, (long) info->mtime.tv_nsec,
3385                (long) file_mtime.tv_sec, (long) file_mtime.tv_nsec);
3386         status = run_mandb (0, manpath, file);
3387         if (status)
3388                 error (0, 0, _("mandb command failed with exit status %d"),
3389                        status);
3390         free (file);
3391
3392         return 1;
3393 }
3394 #endif /* MAN_DB_UPDATES */
3395
3396 /* Special return values from try_db(). */
3397
3398 #define TRY_DATABASE_OPEN_FAILED  -1
3399
3400 #ifdef MAN_DB_CREATES
3401 #define TRY_DATABASE_CREATED      -2
3402 #endif /* MAN_DB_CREATES */
3403
3404 #ifdef MAN_DB_UPDATES
3405 #define TRY_DATABASE_UPDATED      -3
3406 #endif /* MAN_DB_UPDATES */
3407
3408 /* Look for a page in the database. If db not accessible, return -1,
3409    otherwise return number of pages found. */
3410 static int try_db (const char *manpath, const char *sec, const char *name,
3411                    struct candidate **cand_head)
3412 {
3413         struct mandata *loc, *data;
3414         char *catpath;
3415         int found = 0;
3416 #ifdef MAN_DB_UPDATES
3417         int found_stale = 0;
3418 #endif /* MAN_DB_UPDATES */
3419
3420         /* find out where our db for this manpath should be */
3421
3422         catpath = get_catpath (manpath, global_manpath ? SYSTEM_CAT : USER_CAT);
3423         free (database);
3424         if (catpath) {
3425                 database = mkdbname (catpath);
3426                 free (catpath);
3427         } else
3428                 database = mkdbname (manpath);
3429
3430         if (!db_hash)
3431                 db_hash = hashtable_create (&db_hashtable_free);
3432
3433         /* Have we looked here already? */
3434         data = hashtable_lookup (db_hash, manpath, strlen (manpath));
3435
3436         if (!data) {
3437                 MYDBM_FILE dbf;
3438
3439                 dbf = MYDBM_RDOPEN (database);
3440                 if (dbf && dbver_rd (dbf)) {
3441                         MYDBM_CLOSE (dbf);
3442                         dbf = NULL;
3443                 }
3444                 if (dbf) {
3445                         debug ("Succeeded in opening %s O_RDONLY\n", database);
3446
3447                         /* if section is set, only return those that match,
3448                            otherwise NULL retrieves all available */
3449                         if (regex_opt || wildcard)
3450                                 data = dblookup_pattern
3451                                         (dbf, name, section, match_case,
3452                                          regex_opt, !names_only);
3453                         else
3454                                 data = dblookup_all (dbf, name, section,
3455                                                      match_case);
3456                         hashtable_install (db_hash, manpath, strlen (manpath),
3457                                            data);
3458                         MYDBM_CLOSE (dbf);
3459                         dbf = NULL;
3460 #ifdef MAN_DB_CREATES
3461                 } else if (!global_manpath) {
3462                         /* create one */
3463                         debug ("Failed to open %s O_RDONLY\n", database);
3464                         if (run_mandb (1, manpath, NULL)) {
3465                                 data = infoalloc ();
3466                                 data->next = NULL;
3467                                 data->addr = NULL;
3468                                 hashtable_install (db_hash,
3469                                                    manpath, strlen (manpath),
3470                                                    data);
3471                                 return TRY_DATABASE_OPEN_FAILED;
3472                         }
3473                         return TRY_DATABASE_CREATED;
3474 #endif /* MAN_DB_CREATES */
3475                 } else {
3476                         debug ("Failed to open %s O_RDONLY\n", database);
3477                         data = infoalloc ();
3478                         data->next = (struct mandata *) NULL;
3479                         data->addr = NULL;
3480                         hashtable_install (db_hash, manpath, strlen (manpath),
3481                                            data);
3482                         return TRY_DATABASE_OPEN_FAILED;
3483                 }
3484         }
3485
3486         /* if we already know that there is nothing here, get on with it */
3487         if (!data)
3488                 return 0;
3489
3490         /* We already tried (and failed) to open this db before */
3491         if (!data->addr)
3492                 return TRY_DATABASE_OPEN_FAILED;
3493
3494 #ifdef MAN_DB_UPDATES
3495         /* Check that all the entries found are up to date. If not, the
3496          * caller should try again.
3497          */
3498         for (loc = data; loc; loc = loc->next)
3499                 if (STREQ (sec, loc->sec) &&
3500                     (!extension || STREQ (extension, loc->ext)
3501                                 || STREQ (extension, loc->ext + strlen (sec))))
3502                         if (maybe_update_file (manpath, name, loc))
3503                                 found_stale = 1;
3504
3505         if (found_stale) {
3506                 hashtable_remove (db_hash, manpath, strlen (manpath));
3507                 return TRY_DATABASE_UPDATED;
3508         }
3509 #endif /* MAN_DB_UPDATES */
3510
3511         /* cycle through the mandata structures (there's usually only 
3512            1 or 2) and see what we have w.r.t. the current section */
3513         for (loc = data; loc; loc = loc->next)
3514                 if (STREQ (sec, loc->sec) &&
3515                     (!extension || STREQ (extension, loc->ext)
3516                                 || STREQ (extension, loc->ext + strlen (sec))))
3517                         found += add_candidate (cand_head, CANDIDATE_DATABASE,
3518                                                 0, name, manpath, NULL, loc);
3519
3520         return found;
3521 }
3522
3523 /* Try to locate the page under the specified manpath, in the desired section,
3524  * with the supplied name. Glob if necessary. Initially search the filesystem;
3525  * if that fails, try finding it via a db cache access. */
3526 static int locate_page (const char *manpath, const char *sec, const char *name,
3527                         struct candidate **candidates)
3528 {
3529         int found, db_ok;
3530
3531         /* sort out whether we want to treat this hierarchy as 
3532            global or user. Differences:
3533
3534            global: if setuid, use privs; don't create db.
3535            user  : if setuid, drop privs; allow db creation. */
3536
3537         global_manpath = is_global_mandir (manpath);
3538         if (!global_manpath)
3539                 drop_effective_privs ();
3540
3541         debug ("searching in %s, section %s\n", manpath, sec);
3542
3543         found = try_section (manpath, sec, name, candidates);
3544
3545         if ((!found || findall) && !global_apropos) {
3546                 db_ok = try_db (manpath, sec, name, candidates);
3547
3548 #ifdef MAN_DB_CREATES
3549                 if (db_ok == TRY_DATABASE_CREATED)
3550                         /* we created a db in the last call */
3551                         db_ok = try_db (manpath, sec, name, candidates);
3552 #endif /* MAN_DB_CREATES */
3553
3554 #ifdef MAN_DB_UPDATES
3555                 if (db_ok == TRY_DATABASE_UPDATED)
3556                         /* We found some outdated entries and rebuilt the
3557                          * database in the last call. If this keeps
3558                          * happening, though, give up and punt to the
3559                          * filesystem.
3560                          */
3561                         db_ok = try_db (manpath, sec, name, candidates);
3562 #endif /* MAN_DB_UPDATES */
3563
3564                 if (db_ok > 0)  /* we found/opened a db and found something */
3565                         found += db_ok;
3566         }
3567
3568         if (!global_manpath)
3569                 regain_effective_privs ();
3570
3571         return found;
3572 }
3573
3574 static int display_pages (struct candidate *candidates)
3575 {
3576         struct candidate *candp;
3577         int found = 0;
3578
3579         for (candp = candidates; candp; candp = candp->next) {
3580                 global_manpath = is_global_mandir (candp->path);
3581                 if (!global_manpath)
3582                         drop_effective_privs ();
3583
3584                 switch (candp->from_db) {
3585                         case CANDIDATE_FILESYSTEM:
3586                                 found += display_filesystem (candp);
3587                                 break;
3588                         case CANDIDATE_DATABASE:
3589                                 found += display_database_check (candp);
3590                                 break;
3591                         default:
3592                                 error (0, 0,
3593                                        _("internal error: candidate type %d "
3594                                          "out of range"), candp->from_db);
3595                 }
3596
3597                 if (!global_manpath)
3598                         regain_effective_privs ();
3599
3600                 if (found && !findall)
3601                         return found;
3602         }
3603
3604         return found;
3605 }
3606
3607 /*
3608  * Search for text in all manual pages.
3609  *
3610  * This is not a real full-text search, but a brute-force on-demand search.
3611  * The idea, name, and approach originate in the 'man' package, added (I
3612  * believe) by Andries Brouwer, although the implementation is new for
3613  * man-db and much faster due to running in-process.
3614  *
3615  * Conceptually, this really belongs in whatis.c, as part of apropos.
3616  * However, the implementation in 'man' offers pages for immediate display
3617  * on request rather than simply listing them, which is currently awkward to
3618  * do in apropos. If we ever add support to apropos/whatis for either
3619  * calling back to man or displaying pages directly, we should revisit this.
3620  */
3621 static int grep (const char *file, const char *string, const regex_t *search)
3622 {
3623         struct stat st;
3624         pipeline *decomp;
3625         const char *line;
3626         int ret = 0;
3627
3628         /* pipeline_start makes file open failures unconditionally fatal.
3629          * Here, we'd rather just ignore any such files.
3630          */
3631         if (stat (file, &st) < 0)
3632                 return 0;
3633
3634         decomp = decompress_open (file);
3635         if (!decomp)
3636                 return 0;
3637         pipeline_start (decomp);
3638         while ((line = pipeline_readline (decomp)) != NULL) {
3639                 if (regex_opt) {
3640                         if (regexec (search, line,
3641                                      0, (regmatch_t *) 0, 0) == 0) {
3642                                 ret = 1;
3643                                 break;
3644                         }
3645                 } else {
3646                         if (match_case ?
3647                             strstr (line, string) :
3648                             strcasestr (line, string)) {
3649                                 ret = 1;
3650                                 break;
3651                         }
3652                 }
3653         }
3654
3655         pipeline_free (decomp);
3656         return ret;
3657 }
3658
3659 static int do_global_apropos_section (const char *path, const char *sec,
3660                                       const char *name)
3661 {
3662         int found = 0;
3663         char **names, **np;
3664         size_t names_len = 0;
3665         regex_t search;
3666
3667         global_manpath = is_global_mandir (path);
3668         if (!global_manpath)
3669                 drop_effective_privs ();
3670
3671         debug ("searching in %s, section %s\n", path, sec);
3672
3673         names = look_for_file (path, sec, "*", 0, LFF_WILDCARD);
3674         if (regex_opt)
3675                 xregcomp (&search, name,
3676                           REG_EXTENDED | REG_NOSUB |
3677                           (match_case ? 0 : REG_ICASE));
3678         else
3679                 memset (&search, 0, sizeof search);
3680
3681         for (np = names; np && *np; ++np)
3682                 ++names_len;
3683         order_files (path, names, names_len);
3684
3685         for (np = names; np && *np; ++np) {
3686                 struct mandata *info;
3687                 char *info_buffer;
3688                 char *title = NULL;
3689                 const char *man_file;
3690                 char *cat_file = NULL;
3691
3692                 if (!grep (*np, name, &search))
3693                         continue;
3694
3695                 info = infoalloc ();
3696                 info_buffer = filename_info (*np, info, NULL);
3697                 if (!info_buffer)
3698                         goto next;
3699                 info->addr = info_buffer;
3700
3701                 title = xasprintf ("%s(%s)", strchr (info_buffer, '\0') + 1,
3702                                    info->ext);
3703                 man_file = ult_src (*np, path, NULL, ult_flags, NULL);
3704                 if (!man_file)
3705                         goto next;
3706                 lang = lang_dir (man_file);
3707                 cat_file = find_cat_file (path, *np, man_file);
3708                 if (display (path, man_file, cat_file, title, NULL))
3709                         found = 1;
3710                 free (lang);
3711                 lang = NULL;
3712
3713 next:
3714                 free (cat_file);
3715                 free (title);
3716                 free_mandata_struct (info);
3717         }
3718
3719         if (regex_opt)
3720                 regfree (&search);
3721
3722         if (!global_manpath)
3723                 regain_effective_privs ();
3724
3725         return found;
3726 }
3727
3728 static int do_global_apropos (const char *name, int *found)
3729 {
3730         const char **my_section_list;
3731         const char **sp;
3732         char **mp;
3733
3734         if (section) {
3735                 my_section_list = XNMALLOC (2, const char *);
3736                 my_section_list[0] = section;
3737                 my_section_list[1] = NULL;
3738         } else
3739                 my_section_list = section_list;
3740
3741         for (sp = my_section_list; *sp; sp++)
3742                 for (mp = manpathlist; *mp; mp++)
3743                         *found += do_global_apropos_section (*mp, *sp, name);
3744
3745         if (section)
3746                 free (my_section_list);
3747
3748         return *found ? OK : NOT_FOUND;
3749 }
3750
3751 /* Each of local_man_loop and man sometimes calls the other. */
3752 static int man (const char *name, int *found);
3753
3754 /* man issued with `-l' option */
3755 static int local_man_loop (const char *argv)
3756 {
3757         int exit_status = OK;
3758         int local_mf = local_man_file;
3759
3760         drop_effective_privs ();
3761         local_man_file = 1;
3762         if (strcmp (argv, "-") == 0)
3763                 display (NULL, "", NULL, "(stdin)", NULL);
3764         else {
3765                 struct stat st;
3766
3767                 /* Check that the file exists and isn't e.g. a directory */
3768                 if (stat (argv, &st)) {
3769                         error (0, errno, "%s", argv);
3770                         return NOT_FOUND;
3771                 }
3772
3773                 if (S_ISDIR (st.st_mode)) {
3774                         error (0, EISDIR, "%s", argv);
3775                         return NOT_FOUND;
3776                 }
3777
3778                 if (S_ISCHR (st.st_mode) || S_ISBLK (st.st_mode)) {
3779                         /* EINVAL is about the best I can do. */
3780                         error (0, EINVAL, "%s", argv);
3781                         return NOT_FOUND;
3782                 }
3783
3784                 if (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) {
3785                         /* Perhaps an executable. If its directory is on
3786                          * $PATH, then we want to look up the corresponding
3787                          * manual page in the appropriate hierarchy rather
3788                          * than displaying the executable.
3789                          */
3790                         char *argv_dir = dir_name (argv);
3791                         int found = 0;
3792
3793                         if (directory_on_path (argv_dir)) {
3794                                 char *argv_base = base_name (argv);
3795                                 char *new_manp, *nm;
3796                                 char **old_manpathlist, **mp;
3797
3798                                 debug ("recalculating manpath for executable "
3799                                        "in %s\n", argv_dir);
3800
3801                                 new_manp = get_manpath_from_path (argv_dir, 0);
3802                                 if (!new_manp || !*new_manp) {
3803                                         debug ("no useful manpath for "
3804                                                "executable\n");
3805                                         goto executable_out;
3806                                 }
3807                                 nm = locale_manpath (new_manp);
3808                                 free (new_manp);
3809                                 new_manp = nm;
3810
3811                                 old_manpathlist = XNMALLOC (MAXDIRS, char *);
3812                                 memcpy (old_manpathlist, manpathlist,
3813                                         MAXDIRS * sizeof (*manpathlist));
3814                                 create_pathlist (new_manp, manpathlist);
3815
3816                                 man (argv_base, &found);
3817
3818                                 for (mp = manpathlist; *mp; ++mp)
3819                                         free (*mp);
3820                                 memcpy (manpathlist, old_manpathlist,
3821                                         MAXDIRS * sizeof (*manpathlist));
3822                                 free (old_manpathlist);
3823 executable_out:
3824                                 free (new_manp);
3825                                 free (argv_base);
3826                         }
3827                         free (argv_dir);
3828
3829                         if (found)
3830                                 return OK;
3831                 }
3832
3833                 if (exit_status == OK) {
3834                         char *argv_base = base_name (argv);
3835                         char *argv_abs;
3836                         if (argv[0] == '/')
3837                                 argv_abs = xstrdup (argv);
3838                         else {
3839                                 argv_abs = xgetcwd ();
3840                                 if (argv_abs)
3841                                         argv_abs = appendstr (argv_abs, "/",
3842                                                               argv, NULL);
3843                                 else
3844                                         argv_abs = xstrdup (argv);
3845                         }
3846                         lang = lang_dir (argv_abs);
3847                         free (argv_abs);
3848                         if (!display (NULL, argv, NULL, argv_base, NULL)) {
3849                                 if (local_mf)
3850                                         error (0, errno, "%s", argv);
3851                                 exit_status = NOT_FOUND;
3852                         }
3853                         free (lang);
3854                         lang = NULL;
3855                         free (argv_base);
3856                 }
3857         }
3858         local_man_file = local_mf;
3859         regain_effective_privs ();
3860         return exit_status;
3861 }
3862
3863 /*
3864  * Splits a "name[.section]" into { "name", "section" }.
3865  * Section would be NULL if not present.
3866  * The caller is responsible for freeing *ret_name and *ret_section.
3867  * */
3868 static void split_page_name (const char *page_name,
3869                              char **ret_name,
3870                              char **ret_section)
3871 {
3872         char *dot;
3873
3874         dot = strrchr (page_name, '.');
3875
3876         if (dot && is_section (dot + 1)) {
3877                 *ret_name = xstrndup (page_name, dot - page_name);
3878                 *ret_section = xstrdup (dot + 1);
3879         } else {
3880                 *ret_name = xstrdup (page_name);
3881                 *ret_section = NULL;
3882         }
3883 }
3884
3885 static void locate_page_in_manpath (const char *page_section,
3886                                     const char *page_name,
3887                                     struct candidate **candidates,
3888                                     int *found)
3889 {
3890         char **mp;
3891
3892         for (mp = manpathlist; *mp; mp++)
3893                 *found += locate_page (*mp, page_section, page_name, candidates);
3894 }
3895
3896 /*
3897  * Search for manual pages.
3898  *
3899  * If preformatted manual pages are supported, look for the formatted
3900  * file first, then the man page source file.  If they both exist and
3901  * the man page source file is newer, or only the source file exists,
3902  * try to reformat it and write the results in the cat directory.  If
3903  * it is not possible to write the cat file, simply format and display
3904  * the man file.
3905  *
3906  * If preformatted pages are not supported, or the troff option is
3907  * being used, only look for the man page source file.
3908  *
3909  */
3910 static int man (const char *name, int *found)
3911 {
3912         char *page_name, *page_section;
3913         struct candidate *candidates = NULL, *cand, *candnext;
3914
3915         *found = 0;
3916         fflush (stdout);
3917
3918         if (strchr (name, '/')) {
3919                 int status = local_man_loop (name);
3920                 if (status == OK)
3921                         *found = 1;
3922                 return status;
3923         }
3924
3925         if (section)
3926                 locate_page_in_manpath (section, name, &candidates, found);
3927         else {
3928                 const char **sp;
3929
3930                 for (sp = section_list; *sp; sp++) {
3931                         locate_page_in_manpath (*sp, name, &candidates, found);
3932                 }
3933         }
3934
3935         split_page_name (name, &page_name, &page_section);
3936
3937         if (!*found && page_section)
3938                 locate_page_in_manpath (page_section, page_name, &candidates,
3939                                         found);
3940
3941         free (page_name);
3942         free (page_section);
3943
3944         sort_candidates (&candidates);
3945
3946         if (*found)
3947                 *found = display_pages (candidates);
3948
3949         for (cand = candidates; cand; cand = candnext) {
3950                 candnext = cand->next;
3951                 free_candidate (cand);
3952         }
3953
3954         return *found ? OK : NOT_FOUND;
3955 }
3956
3957
3958 static const char **get_section_list (void)
3959 {
3960         int i = 0;
3961         const char **config_sections;
3962         const char **sections = NULL;
3963         const char *sec;
3964
3965         /* Section list from configuration file, or STD_SECTIONS if it's
3966          * empty.
3967          */
3968         config_sections = get_sections ();
3969         if (!*config_sections) {
3970                 free (config_sections);
3971                 config_sections = std_sections;
3972         }
3973
3974         if (colon_sep_section_list == NULL)
3975                 colon_sep_section_list = getenv ("MANSECT");
3976         if (colon_sep_section_list == NULL || *colon_sep_section_list == '\0')
3977                 return config_sections;
3978
3979         /* Although this is documented as colon-separated, at least Solaris
3980          * man's -s option takes a comma-separated list, so we accept that
3981          * too for compatibility.
3982          */
3983         for (sec = strtok (colon_sep_section_list, ":,"); sec; 
3984              sec = strtok (NULL, ":,")) {
3985                 sections = xnrealloc (sections, i + 2, sizeof *sections);
3986                 sections[i++] = sec;
3987         }
3988
3989         if (i > 0) {
3990                 sections[i] = NULL;
3991                 return sections;
3992         } else {
3993                 free (sections);
3994                 return config_sections;
3995         }
3996 }
3997
3998 /*
3999  * Returns the first token of a libpipeline/sh-style command. See SUSv4TC2:
4000  * 2.2 Shell Command Language: Quoting.
4001  *
4002  * Free the returned value.
4003  *
4004  * Examples:
4005  * sh_lang_first_word ("echo 3") returns "echo"
4006  * sh_lang_first_word ("'e ho' 3") returns "e ho"
4007  * sh_lang_first_word ("e\\cho 3") returns "echo"
4008  * sh_lang_first_word ("e\\\ncho 3") returns "echo"
4009  * sh_lang_first_word ("\"echo t\" 3") returns "echo t"
4010  * sh_lang_first_word ("\"ech\\o t\" 3") returns "ech\\o t"
4011  * sh_lang_first_word ("\"ech\\\\o t\" 3") returns "ech\\o t"
4012  * sh_lang_first_word ("\"ech\\\no t\" 3") returns "echo t"
4013  * sh_lang_first_word ("\"ech\\$ t\" 3") returns "ech$ t"
4014  * sh_lang_first_word ("\"ech\\` t\" 3") returns "ech` t"
4015  * sh_lang_first_word ("e\"ch\"o 3") returns "echo"
4016  * sh_lang_first_word ("e'ch'o 3") returns "echo"
4017  */
4018 static char *sh_lang_first_word (const char *cmd)
4019 {
4020         int i, o = 0;
4021         char *ret = xmalloc (strlen (cmd) + 1);
4022
4023         for (i = 0; cmd[i] != '\0'; i++) {
4024                 if (cmd[i] == '\\') {
4025                         /* Escape Character (Backslash) */
4026                         i++;
4027                         if (cmd[i] == '\0')
4028                                 break;
4029                         if (cmd[i] != '\n')
4030                                 ret[o++] = cmd[i];
4031                 } else if (cmd[i] == '\'') {
4032                         /* Single-Quotes */
4033                         i++;
4034                         while (cmd[i] != '\0' && cmd[i] != '\'')
4035                                 ret[o++] = cmd[i++];
4036                 } else if (cmd[i] == '"') {
4037                         /* Double-Quotes */
4038                         i++;
4039                         while (cmd[i] != '\0' && cmd[i] != '"') {
4040                                 if (cmd[i] == '\\') {
4041                                         if (cmd[i + 1] == '$' ||
4042                                             cmd[i + 1] == '`' ||
4043                                             cmd[i + 1] == '"' ||
4044                                             cmd[i + 1] == '\\')
4045                                                 ret[o++] = cmd[++i];
4046                                         else if (cmd[i + 1] == '\n')
4047                                                 i++;
4048                                         else
4049                                                 ret[o++] = cmd[i];
4050                                 } else
4051                                         ret[o++] = cmd[i];
4052
4053                                 i++;
4054                         }
4055                 } else if (cmd[i] == '\t' || cmd[i] == ' ' || cmd[i] == '\n' ||
4056                            cmd[i] == '#')
4057                         break;
4058                 else
4059                         ret[o++] = cmd[i];
4060         }
4061
4062         ret[o] = '\0';
4063
4064         return ret;
4065 }
4066
4067 int main (int argc, char *argv[])
4068 {
4069         int argc_env, exit_status = OK;
4070         char **argv_env;
4071         const char *tmp;
4072
4073         set_program_name (argv[0]);
4074
4075         check_standard_fds ();
4076
4077         init_debug ();
4078         pipeline_install_post_fork (pop_all_cleanups);
4079         sandbox = sandbox_init ();
4080
4081         umask (022);
4082         init_locale ();
4083
4084         internal_locale = setlocale (LC_MESSAGES, NULL);
4085         /* Use LANGUAGE only when LC_MESSAGES locale category is
4086          * neither "C" nor "POSIX". */
4087         if (internal_locale && strcmp (internal_locale, "C") &&
4088             strcmp (internal_locale, "POSIX"))
4089                 multiple_locale = getenv ("LANGUAGE");
4090         internal_locale = xstrdup (internal_locale ? internal_locale : "C");
4091
4092 /* export argv, it might be needed when invoking the vendor supplied browser */
4093 #if defined _AIX || defined __sgi
4094         global_argv = argv;
4095 #endif
4096
4097 #ifdef TROFF_IS_GROFF
4098         /* used in --help, so initialise early */
4099         if (!html_pager)
4100                 init_html_pager ();
4101 #endif /* TROFF_IS_GROFF */
4102
4103         /* First of all, find out if $MANOPT is set. If so, put it in 
4104            *argv[] format for argp to play with. */
4105         argv_env = manopt_to_env (&argc_env);
4106         if (argv_env)
4107                 if (argp_parse (&argp, argc_env, argv_env, ARGP_NO_ARGS, 0, 0))
4108                         exit (FAIL);
4109
4110         /* parse the actual program args */
4111         if (argp_parse (&argp, argc, argv, ARGP_NO_ARGS, &first_arg, 0))
4112                 exit (FAIL);
4113
4114         /* record who we are and drop effective privs for later use */
4115         init_security ();
4116
4117         read_config_file (local_man_file || user_config_file);
4118
4119         /* if the user wants whatis or apropos, give it to them... */
4120         if (external)
4121                 do_extern (argc, argv);
4122
4123         get_term (); /* stores terminal settings */
4124 #ifdef MAN_OWNER
4125         debug ("real user = %d; effective user = %d\n", ruid, euid);
4126 #endif /* MAN_OWNER */
4127
4128         /* close this locale and reinitialise if a new locale was 
4129            issued as an argument or in $MANOPT */
4130         if (locale) {
4131                 free (internal_locale);
4132                 internal_locale = setlocale (LC_ALL, locale);
4133                 if (internal_locale)
4134                         internal_locale = xstrdup (internal_locale);
4135                 else
4136                         internal_locale = xstrdup (locale);
4137
4138                 debug ("main(): locale = %s, internal_locale = %s\n",
4139                        locale, internal_locale);
4140                 if (internal_locale) {
4141                         setenv ("LANGUAGE", internal_locale, 1);
4142                         locale_changed ();
4143                         multiple_locale = NULL;
4144                 }
4145         }
4146
4147 #ifdef TROFF_IS_GROFF
4148         if (htmlout)
4149                 pager = html_pager;
4150 #endif /* TROFF_IS_GROFF */
4151
4152         if (pager == NULL)
4153                 pager = getenv ("MANPAGER");
4154         if (pager == NULL)
4155                 pager = getenv ("PAGER");
4156         if (pager == NULL)
4157                 pager = get_def_user ("pager", NULL);
4158         if (pager == NULL) {
4159                 char *pager_program = sh_lang_first_word (PAGER);
4160                 if (pathsearch_executable (pager_program))
4161                         pager = PAGER;
4162                 else
4163                         pager = "";
4164                 free (pager_program);
4165         }
4166         if (*pager == '\0')
4167                 pager = get_def_user ("cat", CAT);
4168
4169         if (prompt_string == NULL)
4170                 prompt_string = getenv ("MANLESS");
4171
4172         if (prompt_string == NULL)
4173 #ifdef LESS_PROMPT
4174                 prompt_string = LESS_PROMPT;
4175 #else
4176                 prompt_string = _(
4177                                 " Manual page " MAN_PN
4178                                 " ?ltline %lt?L/%L.:byte %bB?s/%s..?e (END):"
4179                                 "?pB %pB\\%.. "
4180                                 "(press h for help or q to quit)");
4181 #endif
4182
4183         /* Restore and save $LESS in $MAN_ORIG_LESS so that recursive uses
4184          * of man work as expected.
4185          */
4186         less = getenv ("MAN_ORIG_LESS");
4187         if (less == NULL)
4188                 less = getenv ("LESS");
4189         setenv ("MAN_ORIG_LESS", less ? less : "", 1);
4190
4191         debug ("\nusing %s as pager\n", pager);
4192
4193         if (first_arg == argc) {
4194                 if (print_where) {
4195                         manp = get_manpath ("");
4196                         printf ("%s\n", manp);
4197                         exit (OK);
4198                 } else {
4199                         free (internal_locale);
4200                         gripe_no_name (NULL);
4201                 }
4202         }
4203
4204         section_list = get_section_list ();
4205
4206         if (manp == NULL) {
4207                 char *mp = get_manpath (alt_system_name);
4208                 manp = locale_manpath (mp);
4209                 free (mp);
4210         } else
4211                 free (get_manpath (NULL));
4212
4213         debug ("manpath search path (with duplicates) = %s\n", manp);
4214
4215         create_pathlist (manp, manpathlist);
4216
4217         /* man issued with `-l' option */
4218         if (local_man_file) {
4219                 while (first_arg < argc) {
4220                         exit_status = local_man_loop (argv[first_arg]);
4221                         ++first_arg;
4222                 }
4223                 free (internal_locale);
4224                 exit (exit_status);
4225         }
4226
4227         /* finished manpath processing, regain privs */
4228         regain_effective_privs ();
4229
4230 #ifdef MAN_DB_UPDATES
4231         /* If `-u', do it now. */
4232         if (update) {
4233                 int status = run_mandb (0, NULL, NULL);
4234                 if (status)
4235                         error (0, 0,
4236                                _("mandb command failed with exit status %d"),
4237                                status);
4238         }
4239 #endif /* MAN_DB_UPDATES */
4240
4241         while (first_arg < argc) {
4242                 int status = OK;
4243                 int found = 0;
4244                 static int maybe_section = 0;
4245                 const char *nextarg = argv[first_arg++];
4246
4247                 /*
4248                  * See if this argument is a valid section name.  If not,
4249                  * is_section returns NULL.
4250                  */
4251                 if (!catman) {
4252                         tmp = is_section (nextarg);
4253                         if (tmp) {
4254                                 section = tmp;
4255                                 debug ("\nsection: %s\n", section);
4256                                 maybe_section = 1;
4257                         }
4258                 }
4259
4260                 if (maybe_section) {
4261                         if (first_arg < argc)
4262                                 /* e.g. 'man 3perl Shell' */
4263                                 nextarg = argv[first_arg++];
4264                         else
4265                                 /* e.g. 'man 9wm' */
4266                                 section = NULL;
4267                                 /* ... but leave maybe_section set so we can
4268                                  * tell later that this happened.
4269                                  */
4270                 }
4271
4272                 /* this is where we actually start looking for the man page */
4273                 skip = 0;
4274                 if (global_apropos)
4275                         status = do_global_apropos (nextarg, &found);
4276                 else {
4277                         int found_subpage = 0;
4278                         if (subpages && first_arg < argc) {
4279                                 char *subname = xasprintf (
4280                                         "%s-%s", nextarg, argv[first_arg]);
4281                                 status = man (subname, &found);
4282                                 free (subname);
4283                                 if (status == OK) {
4284                                         found_subpage = 1;
4285                                         ++first_arg;
4286                                 }
4287                         }
4288                         if (!found_subpage && subpages && first_arg < argc) {
4289                                 char *subname = xasprintf (
4290                                         "%s_%s", nextarg, argv[first_arg]);
4291                                 status = man (subname, &found);
4292                                 free (subname);
4293                                 if (status == OK) {
4294                                         found_subpage = 1;
4295                                         ++first_arg;
4296                                 }
4297                         }
4298                         if (!found_subpage)
4299                                 status = man (nextarg, &found);
4300                 }
4301
4302                 /* clean out the cache of database lookups for each man page */
4303                 hashtable_free (db_hash);
4304                 db_hash = NULL;
4305
4306                 if (section && maybe_section) {
4307                         if (status != OK && !catman) {
4308                                 /* Maybe the section wasn't a section after
4309                                  * all? e.g. 'man 9wm fvwm'.
4310                                  */
4311                                 int found_subpage = 0;
4312                                 debug ("\nRetrying section %s as name\n",
4313                                        section);
4314                                 tmp = section;
4315                                 section = NULL;
4316                                 if (subpages) {
4317                                         char *subname = xasprintf (
4318                                                 "%s-%s", tmp, nextarg);
4319                                         status = man (subname, &found);
4320                                         free (subname);
4321                                         if (status == OK) {
4322                                                 found_subpage = 1;
4323                                                 ++first_arg;
4324                                         }
4325                                 }
4326                                 if (!found_subpage)
4327                                         status = man (tmp, &found);
4328                                 hashtable_free (db_hash);
4329                                 db_hash = NULL;
4330                                 /* ... but don't gripe about it if it doesn't
4331                                  * work!
4332                                  */
4333                                 if (status == OK) {
4334                                         /* It was a name after all, so arrange
4335                                          * to try the next page again with a
4336                                          * null section.
4337                                          */
4338                                         nextarg = tmp;
4339                                         --first_arg;
4340                                 } else
4341                                         /* No go, it really was a section. */
4342                                         section = tmp;
4343                         }
4344                 }
4345
4346                 if (status != OK && !catman) {
4347                         if (!skip) {
4348                                 exit_status = status;
4349                                 if (exit_status == NOT_FOUND) {
4350                                         if (!section && maybe_section &&
4351                                             CTYPE (isdigit, nextarg[0]))
4352                                                 gripe_no_name (nextarg);
4353                                         else
4354                                                 gripe_no_man (nextarg, section);
4355                                 }
4356                         }
4357                 } else {
4358                         debug ("\nFound %d man pages\n", found);
4359                         if (catman) {
4360                                 printf ("%s", nextarg);
4361                                 if (section)
4362                                         printf ("(%s)", section);
4363                                 if (first_arg != argc)
4364                                         fputs (", ", stdout);
4365                                 else
4366                                         fputs (".\n", stdout);
4367                         }
4368                 }
4369
4370                 maybe_section = 0;
4371
4372                 chkr_garbage_detector ();
4373         }
4374         hashtable_free (db_hash);
4375         db_hash = NULL;
4376
4377         drop_effective_privs ();
4378
4379         free (database);
4380         free_pathlist (manpathlist);
4381         free (internal_locale);
4382         exit (exit_status);
4383 }