nrelease - fix/improve livecd
[dragonfly.git] / contrib / mdocml / main.c
1 /* $Id: main.c,v 1.358 2021/09/04 22:38:46 schwarze Exp $ */
2 /*
3  * Copyright (c) 2010-2012, 2014-2021 Ingo Schwarze <schwarze@openbsd.org>
4  * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
5  * Copyright (c) 2010 Joerg Sonnenberger <joerg@netbsd.org>
6  *
7  * Permission to use, copy, modify, and distribute this software for any
8  * purpose with or without fee is hereby granted, provided that the above
9  * copyright notice and this permission notice appear in all copies.
10  *
11  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
12  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
14  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18  *
19  * Main program for mandoc(1), man(1), apropos(1), whatis(1), and help(1).
20  */
21 #include "config.h"
22
23 #include <sys/types.h>
24 #include <sys/ioctl.h>
25 #include <sys/param.h>  /* MACHINE */
26 #include <sys/stat.h>
27 #include <sys/wait.h>
28
29 #include <assert.h>
30 #include <ctype.h>
31 #if HAVE_ERR
32 #include <err.h>
33 #endif
34 #include <errno.h>
35 #include <fcntl.h>
36 #include <glob.h>
37 #include <limits.h>
38 #if HAVE_SANDBOX_INIT
39 #include <sandbox.h>
40 #endif
41 #include <signal.h>
42 #include <stdio.h>
43 #include <stdint.h>
44 #include <stdlib.h>
45 #include <string.h>
46 #include <termios.h>
47 #include <time.h>
48 #include <unistd.h>
49
50 #include "mandoc_aux.h"
51 #include "mandoc.h"
52 #include "mandoc_xr.h"
53 #include "roff.h"
54 #include "mdoc.h"
55 #include "man.h"
56 #include "mandoc_parse.h"
57 #include "tag.h"
58 #include "term_tag.h"
59 #include "main.h"
60 #include "manconf.h"
61 #include "mansearch.h"
62
63 enum    outmode {
64         OUTMODE_DEF = 0,
65         OUTMODE_FLN,
66         OUTMODE_LST,
67         OUTMODE_ALL,
68         OUTMODE_ONE
69 };
70
71 enum    outt {
72         OUTT_ASCII = 0, /* -Tascii */
73         OUTT_LOCALE,    /* -Tlocale */
74         OUTT_UTF8,      /* -Tutf8 */
75         OUTT_TREE,      /* -Ttree */
76         OUTT_MAN,       /* -Tman */
77         OUTT_HTML,      /* -Thtml */
78         OUTT_MARKDOWN,  /* -Tmarkdown */
79         OUTT_LINT,      /* -Tlint */
80         OUTT_PS,        /* -Tps */
81         OUTT_PDF        /* -Tpdf */
82 };
83
84 struct  outstate {
85         struct tag_files *tag_files;    /* Tagging state variables. */
86         void             *outdata;      /* data for output */
87         int               use_pager;
88         int               wstop;        /* stop after a file with a warning */
89         int               had_output;   /* Some output was generated. */
90         enum outt         outtype;      /* which output to use */
91 };
92
93
94 int                       mandocdb(int, char *[]);
95
96 static  void              check_xr(struct manpaths *);
97 static  void              fs_append(char **, size_t, int,
98                                 size_t, const char *, enum form,
99                                 struct manpage **, size_t *);
100 static  int               fs_lookup(const struct manpaths *, size_t,
101                                 const char *, const char *, const char *,
102                                 struct manpage **, size_t *);
103 static  int               fs_search(const struct mansearch *,
104                                 const struct manpaths *, const char *,
105                                 struct manpage **, size_t *);
106 static  void              glob_esc(char **, const char *, const char *);
107 static  void              outdata_alloc(struct outstate *, struct manoutput *);
108 static  void              parse(struct mparse *, int, const char *,
109                                 struct outstate *, struct manconf *);
110 static  void              passthrough(int, int);
111 static  void              process_onefile(struct mparse *, struct manpage *,
112                                 int, struct outstate *, struct manconf *);
113 static  void              run_pager(struct outstate *, char *);
114 static  pid_t             spawn_pager(struct outstate *, char *);
115 static  void              usage(enum argmode) __attribute__((__noreturn__));
116 static  int               woptions(char *, enum mandoc_os *, int *);
117
118 static  const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
119 static  char              help_arg[] = "help";
120 static  char             *help_argv[] = {help_arg, NULL};
121
122
123 int
124 main(int argc, char *argv[])
125 {
126         struct manconf   conf;          /* Manpaths and output options. */
127         struct outstate  outst;         /* Output state. */
128         struct winsize   ws;            /* Result of ioctl(TIOCGWINSZ). */
129         struct mansearch search;        /* Search options. */
130         struct manpage  *res;           /* Complete list of search results. */
131         struct manpage  *resn;          /* Search results for one name. */
132         struct mparse   *mp;            /* Opaque parser object. */
133         const char      *conf_file;     /* -C: alternate config file. */
134         const char      *os_s;          /* -I: Operating system for display. */
135         const char      *progname, *sec, *ep;
136         char            *defpaths;      /* -M: override manpaths. */
137         char            *auxpaths;      /* -m: additional manpaths. */
138         char            *oarg;          /* -O: output option string. */
139         char            *tagarg;        /* -O tag: default value. */
140         unsigned char   *uc;
141         size_t           ressz;         /* Number of elements in res[]. */
142         size_t           resnsz;        /* Number of elements in resn[]. */
143         size_t           i, ib, ssz;
144         int              options;       /* Parser options. */
145         int              show_usage;    /* Invalid argument: give up. */
146         int              prio, best_prio;
147         int              startdir;
148         int              c;
149         enum mandoc_os   os_e;          /* Check base system conventions. */
150         enum outmode     outmode;       /* According to command line. */
151
152 #if HAVE_PROGNAME
153         progname = getprogname();
154 #else
155         if (argc < 1)
156                 progname = mandoc_strdup("mandoc");
157         else if ((progname = strrchr(argv[0], '/')) == NULL)
158                 progname = argv[0];
159         else
160                 ++progname;
161         setprogname(progname);
162 #endif
163
164         mandoc_msg_setoutfile(stderr);
165         if (strncmp(progname, "mandocdb", 8) == 0 ||
166             strcmp(progname, BINM_MAKEWHATIS) == 0)
167                 return mandocdb(argc, argv);
168
169 #if HAVE_PLEDGE
170         if (pledge("stdio rpath wpath cpath tmppath tty proc exec", NULL) == -1) {
171                 mandoc_msg(MANDOCERR_PLEDGE, 0, 0, "%s", strerror(errno));
172                 return mandoc_msg_getrc();
173         }
174 #endif
175 #if HAVE_SANDBOX_INIT
176         if (sandbox_init(kSBXProfileNoInternet, SANDBOX_NAMED, NULL) == -1)
177                 errx((int)MANDOCLEVEL_SYSERR, "sandbox_init");
178 #endif
179
180         /* Search options. */
181
182         memset(&conf, 0, sizeof(conf));
183         conf_file = NULL;
184         defpaths = auxpaths = NULL;
185
186         memset(&search, 0, sizeof(struct mansearch));
187         search.outkey = "Nd";
188         oarg = NULL;
189
190         if (strcmp(progname, BINM_MAN) == 0)
191                 search.argmode = ARG_NAME;
192         else if (strcmp(progname, BINM_APROPOS) == 0)
193                 search.argmode = ARG_EXPR;
194         else if (strcmp(progname, BINM_WHATIS) == 0)
195                 search.argmode = ARG_WORD;
196         else if (strncmp(progname, "help", 4) == 0)
197                 search.argmode = ARG_NAME;
198         else
199                 search.argmode = ARG_FILE;
200
201         /* Parser options. */
202
203         options = MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1;
204         os_e = MANDOC_OS_OTHER;
205         os_s = NULL;
206
207         /* Formatter options. */
208
209         memset(&outst, 0, sizeof(outst));
210         outst.tag_files = NULL;
211         outst.outtype = OUTT_LOCALE;
212         outst.use_pager = 1;
213
214         show_usage = 0;
215         outmode = OUTMODE_DEF;
216
217         while ((c = getopt(argc, argv,
218             "aC:cfhI:iK:klM:m:O:S:s:T:VW:w")) != -1) {
219                 if (c == 'i' && search.argmode == ARG_EXPR) {
220                         optind--;
221                         break;
222                 }
223                 switch (c) {
224                 case 'a':
225                         outmode = OUTMODE_ALL;
226                         break;
227                 case 'C':
228                         conf_file = optarg;
229                         break;
230                 case 'c':
231                         outst.use_pager = 0;
232                         break;
233                 case 'f':
234                         search.argmode = ARG_WORD;
235                         break;
236                 case 'h':
237                         conf.output.synopsisonly = 1;
238                         outst.use_pager = 0;
239                         outmode = OUTMODE_ALL;
240                         break;
241                 case 'I':
242                         if (strncmp(optarg, "os=", 3) != 0) {
243                                 mandoc_msg(MANDOCERR_BADARG_BAD, 0, 0,
244                                     "-I %s", optarg);
245                                 return mandoc_msg_getrc();
246                         }
247                         if (os_s != NULL) {
248                                 mandoc_msg(MANDOCERR_BADARG_DUPE, 0, 0,
249                                     "-I %s", optarg);
250                                 return mandoc_msg_getrc();
251                         }
252                         os_s = optarg + 3;
253                         break;
254                 case 'K':
255                         options &= ~(MPARSE_UTF8 | MPARSE_LATIN1);
256                         if (strcmp(optarg, "utf-8") == 0)
257                                 options |=  MPARSE_UTF8;
258                         else if (strcmp(optarg, "iso-8859-1") == 0)
259                                 options |=  MPARSE_LATIN1;
260                         else if (strcmp(optarg, "us-ascii") != 0) {
261                                 mandoc_msg(MANDOCERR_BADARG_BAD, 0, 0,
262                                     "-K %s", optarg);
263                                 return mandoc_msg_getrc();
264                         }
265                         break;
266                 case 'k':
267                         search.argmode = ARG_EXPR;
268                         break;
269                 case 'l':
270                         search.argmode = ARG_FILE;
271                         outmode = OUTMODE_ALL;
272                         break;
273                 case 'M':
274                         defpaths = optarg;
275                         break;
276                 case 'm':
277                         auxpaths = optarg;
278                         break;
279                 case 'O':
280                         oarg = optarg;
281                         break;
282                 case 'S':
283                         search.arch = optarg;
284                         break;
285                 case 's':
286                         search.sec = optarg;
287                         break;
288                 case 'T':
289                         if (strcmp(optarg, "ascii") == 0)
290                                 outst.outtype = OUTT_ASCII;
291                         else if (strcmp(optarg, "lint") == 0) {
292                                 outst.outtype = OUTT_LINT;
293                                 mandoc_msg_setoutfile(stdout);
294                                 mandoc_msg_setmin(MANDOCERR_BASE);
295                         } else if (strcmp(optarg, "tree") == 0)
296                                 outst.outtype = OUTT_TREE;
297                         else if (strcmp(optarg, "man") == 0)
298                                 outst.outtype = OUTT_MAN;
299                         else if (strcmp(optarg, "html") == 0)
300                                 outst.outtype = OUTT_HTML;
301                         else if (strcmp(optarg, "markdown") == 0)
302                                 outst.outtype = OUTT_MARKDOWN;
303                         else if (strcmp(optarg, "utf8") == 0)
304                                 outst.outtype = OUTT_UTF8;
305                         else if (strcmp(optarg, "locale") == 0)
306                                 outst.outtype = OUTT_LOCALE;
307                         else if (strcmp(optarg, "ps") == 0)
308                                 outst.outtype = OUTT_PS;
309                         else if (strcmp(optarg, "pdf") == 0)
310                                 outst.outtype = OUTT_PDF;
311                         else {
312                                 mandoc_msg(MANDOCERR_BADARG_BAD, 0, 0,
313                                     "-T %s", optarg);
314                                 return mandoc_msg_getrc();
315                         }
316                         break;
317                 case 'W':
318                         if (woptions(optarg, &os_e, &outst.wstop) == -1)
319                                 return mandoc_msg_getrc();
320                         break;
321                 case 'w':
322                         outmode = OUTMODE_FLN;
323                         break;
324                 default:
325                         show_usage = 1;
326                         break;
327                 }
328         }
329
330         if (show_usage)
331                 usage(search.argmode);
332
333         /* Postprocess options. */
334
335         switch (outmode) {
336         case OUTMODE_DEF:
337                 switch (search.argmode) {
338                 case ARG_FILE:
339                         outmode = OUTMODE_ALL;
340                         outst.use_pager = 0;
341                         break;
342                 case ARG_NAME:
343                         outmode = OUTMODE_ONE;
344                         break;
345                 default:
346                         outmode = OUTMODE_LST;
347                         break;
348                 }
349                 break;
350         case OUTMODE_FLN:
351                 if (search.argmode == ARG_FILE)
352                         outmode = OUTMODE_ALL;
353                 break;
354         case OUTMODE_ALL:
355                 break;
356         case OUTMODE_LST:
357         case OUTMODE_ONE:
358                 abort();
359         }
360
361         if (oarg != NULL) {
362                 if (outmode == OUTMODE_LST)
363                         search.outkey = oarg;
364                 else {
365                         while (oarg != NULL) {
366                                 if (manconf_output(&conf.output,
367                                     strsep(&oarg, ","), 0) == -1)
368                                         return mandoc_msg_getrc();
369                         }
370                 }
371         }
372
373         if (outst.outtype != OUTT_TREE || conf.output.noval == 0)
374                 options |= MPARSE_VALIDATE;
375
376         if (outmode == OUTMODE_FLN ||
377             outmode == OUTMODE_LST ||
378             (conf.output.outfilename == NULL &&
379              conf.output.tagfilename == NULL &&
380              isatty(STDOUT_FILENO) == 0))
381                 outst.use_pager = 0;
382
383         if (outst.use_pager &&
384             (conf.output.width == 0 || conf.output.indent == 0) &&
385             ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) != -1 &&
386             ws.ws_col > 1) {
387                 if (conf.output.width == 0 && ws.ws_col < 79)
388                         conf.output.width = ws.ws_col - 1;
389                 if (conf.output.indent == 0 && ws.ws_col < 66)
390                         conf.output.indent = 3;
391         }
392
393 #if HAVE_PLEDGE
394         if (outst.use_pager == 0)
395                 c = pledge("stdio rpath", NULL);
396         else if (conf.output.outfilename != NULL ||
397             conf.output.tagfilename != NULL)
398                 c = pledge("stdio rpath wpath cpath", NULL);
399         else
400                 c = pledge("stdio rpath tmppath tty proc exec", NULL);
401         if (c == -1) {
402                 mandoc_msg(MANDOCERR_PLEDGE, 0, 0, "%s", strerror(errno));
403                 return mandoc_msg_getrc();
404         }
405 #endif
406
407         /* Parse arguments. */
408
409         if (argc > 0) {
410                 argc -= optind;
411                 argv += optind;
412         }
413
414         /*
415          * Quirks for help(1) and man(1),
416          * in particular for a section argument without -s.
417          */
418
419         if (search.argmode == ARG_NAME) {
420                 if (*progname == 'h') {
421                         if (argc == 0) {
422                                 argv = help_argv;
423                                 argc = 1;
424                         }
425                 } else if (argc > 1 &&
426                     ((uc = (unsigned char *)argv[0]) != NULL) &&
427                     ((isdigit(uc[0]) && (uc[1] == '\0' ||
428                       isalpha(uc[1]))) ||
429                      (uc[0] == 'n' && uc[1] == '\0'))) {
430                         search.sec = (char *)uc;
431                         argv++;
432                         argc--;
433                 }
434                 if (search.arch == NULL)
435                         search.arch = getenv("MACHINE");
436 #ifdef MACHINE
437                 if (search.arch == NULL)
438                         search.arch = MACHINE;
439 #endif
440                 if (outmode == OUTMODE_ONE)
441                         search.firstmatch = 1;
442         }
443
444         /*
445          * Use the first argument for -O tag in addition to
446          * using it as a search term for man(1) or apropos(1).
447          */
448
449         if (conf.output.tag != NULL && *conf.output.tag == '\0') {
450                 tagarg = argc > 0 && search.argmode == ARG_EXPR ?
451                     strchr(*argv, '=') : NULL;
452                 conf.output.tag = tagarg == NULL ? *argv : tagarg + 1;
453         }
454
455         /* Read the configuration file. */
456
457         if (search.argmode != ARG_FILE ||
458             mandoc_msg_getmin() == MANDOCERR_STYLE)
459                 manconf_parse(&conf, conf_file, defpaths, auxpaths);
460
461         /* man(1): Resolve each name individually. */
462
463         if (search.argmode == ARG_NAME) {
464                 if (argc < 1) {
465                         if (outmode != OUTMODE_FLN)
466                                 usage(ARG_NAME);
467                         if (conf.manpath.sz == 0) {
468                                 warnx("The manpath is empty.");
469                                 mandoc_msg_setrc(MANDOCLEVEL_BADARG);
470                         } else {
471                                 for (i = 0; i + 1 < conf.manpath.sz; i++)
472                                         printf("%s:", conf.manpath.paths[i]);
473                                 printf("%s\n", conf.manpath.paths[i]);
474                         }
475                         manconf_free(&conf);
476                         return (int)mandoc_msg_getrc();
477                 }
478                 for (res = NULL, ressz = 0; argc > 0; argc--, argv++) {
479                         (void)mansearch(&search, &conf.manpath,
480                             1, argv, &resn, &resnsz);
481                         if (resnsz == 0)
482                                 (void)fs_search(&search, &conf.manpath,
483                                     *argv, &resn, &resnsz);
484                         if (resnsz == 0 && strchr(*argv, '/') == NULL) {
485                                 if (search.arch != NULL &&
486                                     arch_valid(search.arch, OSENUM) == 0)
487                                         warnx("Unknown architecture \"%s\".",
488                                             search.arch);
489                                 else if (search.sec != NULL)
490                                         warnx("No entry for %s in "
491                                             "section %s of the manual.",
492                                             *argv, search.sec);
493                                 else
494                                         warnx("No entry for %s in "
495                                             "the manual.", *argv);
496                                 mandoc_msg_setrc(MANDOCLEVEL_BADARG);
497                                 continue;
498                         }
499                         if (resnsz == 0) {
500                                 if (access(*argv, R_OK) == -1) {
501                                         mandoc_msg_setinfilename(*argv);
502                                         mandoc_msg(MANDOCERR_BADARG_BAD,
503                                             0, 0, "%s", strerror(errno));
504                                         mandoc_msg_setinfilename(NULL);
505                                         continue;
506                                 }
507                                 resnsz = 1;
508                                 resn = mandoc_calloc(resnsz, sizeof(*res));
509                                 resn->file = mandoc_strdup(*argv);
510                                 resn->ipath = SIZE_MAX;
511                                 resn->form = FORM_SRC;
512                         }
513                         if (outmode != OUTMODE_ONE || resnsz == 1) {
514                                 res = mandoc_reallocarray(res,
515                                     ressz + resnsz, sizeof(*res));
516                                 memcpy(res + ressz, resn,
517                                     sizeof(*resn) * resnsz);
518                                 ressz += resnsz;
519                                 continue;
520                         }
521
522                         /* Search for the best section. */
523
524                         best_prio = 40;
525                         for (ib = i = 0; i < resnsz; i++) {
526                                 sec = resn[i].file;
527                                 sec += strcspn(sec, "123456789");
528                                 if (sec[0] == '\0')
529                                         continue; /* No section at all. */
530                                 prio = sec_prios[sec[0] - '1'];
531                                 if (search.sec != NULL) {
532                                         ssz = strlen(search.sec);
533                                         if (strncmp(sec, search.sec, ssz) == 0)
534                                                 sec += ssz;
535                                 } else
536                                         sec++; /* Prefer without suffix. */
537                                 if (*sec != '/')
538                                         prio += 10; /* Wrong dir name. */
539                                 if (search.sec != NULL) {
540                                         ep = strchr(sec, '\0');
541                                         if (ep - sec > 3 &&
542                                             strncmp(ep - 3, ".gz", 3) == 0)
543                                                 ep -= 3;
544                                         if ((size_t)(ep - sec) < ssz + 3 ||
545                                             strncmp(ep - ssz, search.sec,
546                                              ssz) != 0)      /* Wrong file */
547                                                 prio += 20;  /* extension. */
548                                 }
549                                 if (prio >= best_prio)
550                                         continue;
551                                 best_prio = prio;
552                                 ib = i;
553                         }
554                         res = mandoc_reallocarray(res, ressz + 1,
555                             sizeof(*res));
556                         memcpy(res + ressz++, resn + ib, sizeof(*resn));
557                 }
558
559         /* apropos(1), whatis(1): Process the full search expression. */
560
561         } else if (search.argmode != ARG_FILE) {
562                 if (mansearch(&search, &conf.manpath,
563                     argc, argv, &res, &ressz) == 0)
564                         usage(search.argmode);
565
566                 if (ressz == 0) {
567                         warnx("nothing appropriate");
568                         mandoc_msg_setrc(MANDOCLEVEL_BADARG);
569                         goto out;
570                 }
571
572         /* mandoc(1): Take command line arguments as file names. */
573
574         } else {
575                 ressz = argc > 0 ? argc : 1;
576                 res = mandoc_calloc(ressz, sizeof(*res));
577                 for (i = 0; i < ressz; i++) {
578                         if (argc > 0)
579                                 res[i].file = mandoc_strdup(argv[i]);
580                         res[i].ipath = SIZE_MAX;
581                         res[i].form = FORM_SRC;
582                 }
583         }
584
585         switch (outmode) {
586         case OUTMODE_FLN:
587                 for (i = 0; i < ressz; i++)
588                         puts(res[i].file);
589                 goto out;
590         case OUTMODE_LST:
591                 for (i = 0; i < ressz; i++)
592                         printf("%s - %s\n", res[i].names,
593                             res[i].output == NULL ? "" :
594                             res[i].output);
595                 goto out;
596         default:
597                 break;
598         }
599
600         if (search.argmode == ARG_FILE && auxpaths != NULL) {
601                 if (strcmp(auxpaths, "doc") == 0)
602                         options |= MPARSE_MDOC;
603                 else if (strcmp(auxpaths, "an") == 0)
604                         options |= MPARSE_MAN;
605         }
606
607         mchars_alloc();
608         mp = mparse_alloc(options, os_e, os_s);
609
610         /*
611          * Remember the original working directory, if possible.
612          * This will be needed if some names on the command line
613          * are page names and some are relative file names.
614          * Do not error out if the current directory is not
615          * readable: Maybe it won't be needed after all.
616          */
617         startdir = open(".", O_RDONLY | O_DIRECTORY);
618         for (i = 0; i < ressz; i++) {
619                 process_onefile(mp, res + i, startdir, &outst, &conf);
620                 if (outst.wstop && mandoc_msg_getrc() != MANDOCLEVEL_OK)
621                         break;
622         }
623         if (startdir != -1) {
624                 (void)fchdir(startdir);
625                 close(startdir);
626         }
627         if (conf.output.tag != NULL && conf.output.tag_found == 0) {
628                 mandoc_msg(MANDOCERR_TAG, 0, 0, "%s", conf.output.tag);
629                 conf.output.tag = NULL;
630         }
631         if (outst.outdata != NULL) {
632                 switch (outst.outtype) {
633                 case OUTT_HTML:
634                         html_free(outst.outdata);
635                         break;
636                 case OUTT_UTF8:
637                 case OUTT_LOCALE:
638                 case OUTT_ASCII:
639                         ascii_free(outst.outdata);
640                         break;
641                 case OUTT_PDF:
642                 case OUTT_PS:
643                         pspdf_free(outst.outdata);
644                         break;
645                 default:
646                         break;
647                 }
648         }
649         mandoc_xr_free();
650         mparse_free(mp);
651         mchars_free();
652
653 out:
654         mansearch_free(res, ressz);
655         if (search.argmode != ARG_FILE)
656                 manconf_free(&conf);
657
658         if (outst.tag_files != NULL) {
659                 if (term_tag_close() != -1 &&
660                     conf.output.outfilename == NULL &&
661                     conf.output.tagfilename == NULL)
662                         run_pager(&outst, conf.output.tag);
663                 term_tag_unlink();
664         } else if (outst.had_output && outst.outtype != OUTT_LINT)
665                 mandoc_msg_summary();
666
667         return (int)mandoc_msg_getrc();
668 }
669
670 static void
671 usage(enum argmode argmode)
672 {
673         switch (argmode) {
674         case ARG_FILE:
675                 fputs("usage: mandoc [-ac] [-I os=name] "
676                     "[-K encoding] [-mdoc | -man] [-O options]\n"
677                     "\t      [-T output] [-W level] [file ...]\n", stderr);
678                 break;
679         case ARG_NAME:
680                 fputs("usage: man [-acfhklw] [-C file] [-M path] "
681                     "[-m path] [-S subsection]\n"
682                     "\t   [[-s] section] name ...\n", stderr);
683                 break;
684         case ARG_WORD:
685                 fputs("usage: whatis [-afk] [-C file] "
686                     "[-M path] [-m path] [-O outkey] [-S arch]\n"
687                     "\t      [-s section] name ...\n", stderr);
688                 break;
689         case ARG_EXPR:
690                 fputs("usage: apropos [-afk] [-C file] "
691                     "[-M path] [-m path] [-O outkey] [-S arch]\n"
692                     "\t       [-s section] expression ...\n", stderr);
693                 break;
694         }
695         exit((int)MANDOCLEVEL_BADARG);
696 }
697
698 static void
699 glob_esc(char **dst, const char *src, const char *suffix)
700 {
701         while (*src != '\0') {
702                 if (strchr("*?[", *src) != NULL)
703                         *(*dst)++ = '\\';
704                 *(*dst)++ = *src++;
705         }
706         while (*suffix != '\0')
707                 *(*dst)++ = *suffix++;
708 }
709
710 static void
711 fs_append(char **file, size_t filesz, int copy, size_t ipath,
712     const char *sec, enum form form, struct manpage **res, size_t *ressz)
713 {
714         struct manpage  *page;
715
716         *res = mandoc_reallocarray(*res, *ressz + filesz, sizeof(**res));
717         page = *res + *ressz;
718         *ressz += filesz;
719         for (;;) {
720                 page->file = copy ? mandoc_strdup(*file) : *file;
721                 page->names = NULL;
722                 page->output = NULL;
723                 page->bits = NAME_FILE & NAME_MASK;
724                 page->ipath = ipath;
725                 page->sec = (*sec >= '1' && *sec <= '9') ? *sec - '1' + 1 : 10;
726                 page->form = form;
727                 if (--filesz == 0)
728                         break;
729                 file++;
730                 page++;
731         }
732 }
733
734 static int
735 fs_lookup(const struct manpaths *paths, size_t ipath,
736         const char *sec, const char *arch, const char *name,
737         struct manpage **res, size_t *ressz)
738 {
739         struct stat      sb;
740         glob_t           globinfo;
741         char            *file, *cp, secnum[2];
742         int              globres;
743         enum form        form;
744
745         const char *const slman = "/man";
746         const char *const slash = "/";
747         const char *const sglob = ".[01-9]*";
748         const char *const dot   = ".";
749         const char *const aster = "*";
750
751         memset(&globinfo, 0, sizeof(globinfo));
752         form = FORM_SRC;
753
754         mandoc_asprintf(&file, "%s/man%s/%s.%s",
755             paths->paths[ipath], sec, name, sec);
756         if (stat(file, &sb) != -1)
757                 goto found;
758         free(file);
759
760         mandoc_asprintf(&file, "%s/cat%s/%s.0",
761             paths->paths[ipath], sec, name);
762         if (stat(file, &sb) != -1) {
763                 form = FORM_CAT;
764                 goto found;
765         }
766         free(file);
767
768         if (arch != NULL) {
769                 mandoc_asprintf(&file, "%s/man%s/%s/%s.%s",
770                     paths->paths[ipath], sec, arch, name, sec);
771                 if (stat(file, &sb) != -1)
772                         goto found;
773                 free(file);
774         }
775
776         cp = file = mandoc_malloc(strlen(paths->paths[ipath]) * 2 +
777             strlen(slman) + strlen(sec) * 2 + strlen(slash) +
778             strlen(name) * 2 + strlen(sglob) + 1);
779         glob_esc(&cp, paths->paths[ipath], slman);
780         glob_esc(&cp, sec, slash);
781         glob_esc(&cp, name, sglob);
782         *cp = '\0';
783         globres = glob(file, 0, NULL, &globinfo);
784         if (globres != 0 && globres != GLOB_NOMATCH)
785                 mandoc_msg(MANDOCERR_GLOB, 0, 0,
786                     "%s: %s", file, strerror(errno));
787         free(file);
788         file = NULL;
789         if (globres == 0)
790                 goto found;
791         globfree(&globinfo);
792
793         if (sec[1] != '\0' && *ressz == 0) {
794                 secnum[0] = sec[0];
795                 secnum[1] = '\0';
796                 cp = file = mandoc_malloc(strlen(paths->paths[ipath]) * 2 +
797                     strlen(slman) + strlen(secnum) * 2 + strlen(slash) +
798                     strlen(name) * 2 + strlen(dot) +
799                     strlen(sec) * 2 + strlen(aster) + 1);
800                 glob_esc(&cp, paths->paths[ipath], slman);
801                 glob_esc(&cp, secnum, slash);
802                 glob_esc(&cp, name, dot);
803                 glob_esc(&cp, sec, aster);
804                 *cp = '\0';
805                 globres = glob(file, 0, NULL, &globinfo);
806                 if (globres != 0 && globres != GLOB_NOMATCH)
807                         mandoc_msg(MANDOCERR_GLOB, 0, 0,
808                             "%s: %s", file, strerror(errno));
809                 free(file);
810                 file = NULL;
811                 if (globres == 0)
812                         goto found;
813                 globfree(&globinfo);
814         }
815
816         if (res != NULL || ipath + 1 != paths->sz)
817                 return -1;
818
819         mandoc_asprintf(&file, "%s.%s", name, sec);
820         globres = stat(file, &sb);
821         free(file);
822         return globres;
823
824 found:
825         warnx("outdated mandoc.db lacks %s(%s) entry, run %s %s",
826             name, sec, BINM_MAKEWHATIS, paths->paths[ipath]);
827         if (res == NULL)
828                 free(file);
829         else if (file == NULL)
830                 fs_append(globinfo.gl_pathv, globinfo.gl_pathc, 1,
831                     ipath, sec, form, res, ressz);
832         else
833                 fs_append(&file, 1, 0, ipath, sec, form, res, ressz);
834         globfree(&globinfo);
835         return 0;
836 }
837
838 static int
839 fs_search(const struct mansearch *cfg, const struct manpaths *paths,
840         const char *name, struct manpage **res, size_t *ressz)
841 {
842         const char *const sections[] =
843             {"1", "8", "6", "2", "3", "5", "7", "4", "9", "3p"};
844         const size_t nsec = sizeof(sections)/sizeof(sections[0]);
845
846         size_t           ipath, isec;
847
848         assert(cfg->argmode == ARG_NAME);
849         if (res != NULL)
850                 *res = NULL;
851         *ressz = 0;
852         for (ipath = 0; ipath < paths->sz; ipath++) {
853                 if (cfg->sec != NULL) {
854                         if (fs_lookup(paths, ipath, cfg->sec, cfg->arch,
855                             name, res, ressz) != -1 && cfg->firstmatch)
856                                 return 0;
857                 } else {
858                         for (isec = 0; isec < nsec; isec++)
859                                 if (fs_lookup(paths, ipath, sections[isec],
860                                     cfg->arch, name, res, ressz) != -1 &&
861                                     cfg->firstmatch)
862                                         return 0;
863                 }
864         }
865         return -1;
866 }
867
868 static void
869 process_onefile(struct mparse *mp, struct manpage *resp, int startdir,
870     struct outstate *outst, struct manconf *conf)
871 {
872         int      fd;
873
874         /*
875          * Changing directories is not needed in ARG_FILE mode.
876          * Do it on a best-effort basis.  Even in case of
877          * failure, some functionality may still work.
878          */
879         if (resp->ipath != SIZE_MAX)
880                 (void)chdir(conf->manpath.paths[resp->ipath]);
881         else if (startdir != -1)
882                 (void)fchdir(startdir);
883
884         mandoc_msg_setinfilename(resp->file);
885         if (resp->file != NULL) {
886                 if ((fd = mparse_open(mp, resp->file)) == -1) {
887                         mandoc_msg(resp->ipath == SIZE_MAX ?
888                             MANDOCERR_BADARG_BAD : MANDOCERR_OPEN,
889                             0, 0, "%s", strerror(errno));
890                         mandoc_msg_setinfilename(NULL);
891                         return;
892                 }
893         } else
894                 fd = STDIN_FILENO;
895
896         if (outst->use_pager) {
897                 outst->use_pager = 0;
898                 outst->tag_files = term_tag_init(conf->output.outfilename,
899                     outst->outtype == OUTT_HTML ? ".html" : "",
900                     conf->output.tagfilename);
901 #if HAVE_PLEDGE
902                 if ((conf->output.outfilename != NULL ||
903                      conf->output.tagfilename != NULL) &&
904                     pledge("stdio rpath cpath", NULL) == -1) {
905                         mandoc_msg(MANDOCERR_PLEDGE, 0, 0,
906                             "%s", strerror(errno));
907                         exit(mandoc_msg_getrc());
908                 }
909 #endif
910         }
911         if (outst->had_output && outst->outtype <= OUTT_UTF8) {
912                 if (outst->outdata == NULL)
913                         outdata_alloc(outst, &conf->output);
914                 terminal_sepline(outst->outdata);
915         }
916
917         if (resp->form == FORM_SRC)
918                 parse(mp, fd, resp->file, outst, conf);
919         else {
920                 passthrough(fd, conf->output.synopsisonly);
921                 outst->had_output = 1;
922         }
923
924         if (ferror(stdout)) {
925                 if (outst->tag_files != NULL) {
926                         mandoc_msg(MANDOCERR_WRITE, 0, 0, "%s: %s",
927                             outst->tag_files->ofn, strerror(errno));
928                         term_tag_unlink();
929                         outst->tag_files = NULL;
930                 } else
931                         mandoc_msg(MANDOCERR_WRITE, 0, 0, "%s",
932                             strerror(errno));
933         }
934         mandoc_msg_setinfilename(NULL);
935 }
936
937 static void
938 parse(struct mparse *mp, int fd, const char *file,
939     struct outstate *outst, struct manconf *conf)
940 {
941         static struct manpaths   basepaths;
942         static int               previous;
943         struct roff_meta        *meta;
944
945         assert(fd >= 0);
946         if (file == NULL)
947                 file = "<stdin>";
948
949         if (previous)
950                 mparse_reset(mp);
951         else
952                 previous = 1;
953
954         mparse_readfd(mp, fd, file);
955         if (fd != STDIN_FILENO)
956                 close(fd);
957
958         /*
959          * With -Wstop and warnings or errors of at least the requested
960          * level, do not produce output.
961          */
962
963         if (outst->wstop && mandoc_msg_getrc() != MANDOCLEVEL_OK)
964                 return;
965
966         if (outst->outdata == NULL)
967                 outdata_alloc(outst, &conf->output);
968         else if (outst->outtype == OUTT_HTML)
969                 html_reset(outst->outdata);
970
971         mandoc_xr_reset();
972         meta = mparse_result(mp);
973
974         /* Execute the out device, if it exists. */
975
976         outst->had_output = 1;
977         if (meta->macroset == MACROSET_MDOC) {
978                 switch (outst->outtype) {
979                 case OUTT_HTML:
980                         html_mdoc(outst->outdata, meta);
981                         break;
982                 case OUTT_TREE:
983                         tree_mdoc(outst->outdata, meta);
984                         break;
985                 case OUTT_MAN:
986                         man_mdoc(outst->outdata, meta);
987                         break;
988                 case OUTT_PDF:
989                 case OUTT_ASCII:
990                 case OUTT_UTF8:
991                 case OUTT_LOCALE:
992                 case OUTT_PS:
993                         terminal_mdoc(outst->outdata, meta);
994                         break;
995                 case OUTT_MARKDOWN:
996                         markdown_mdoc(outst->outdata, meta);
997                         break;
998                 default:
999                         break;
1000                 }
1001         }
1002         if (meta->macroset == MACROSET_MAN) {
1003                 switch (outst->outtype) {
1004                 case OUTT_HTML:
1005                         html_man(outst->outdata, meta);
1006                         break;
1007                 case OUTT_TREE:
1008                         tree_man(outst->outdata, meta);
1009                         break;
1010                 case OUTT_MAN:
1011                         mparse_copy(mp);
1012                         break;
1013                 case OUTT_PDF:
1014                 case OUTT_ASCII:
1015                 case OUTT_UTF8:
1016                 case OUTT_LOCALE:
1017                 case OUTT_PS:
1018                         terminal_man(outst->outdata, meta);
1019                         break;
1020                 case OUTT_MARKDOWN:
1021                         mandoc_msg(MANDOCERR_MAN_TMARKDOWN, 0, 0, NULL);
1022                         break;
1023                 default:
1024                         break;
1025                 }
1026         }
1027         if (conf->output.tag != NULL && conf->output.tag_found == 0 &&
1028             tag_exists(conf->output.tag))
1029                 conf->output.tag_found = 1;
1030
1031         if (mandoc_msg_getmin() < MANDOCERR_STYLE) {
1032                 if (basepaths.sz == 0)
1033                         manpath_base(&basepaths);
1034                 check_xr(&basepaths);
1035         } else if (mandoc_msg_getmin() < MANDOCERR_WARNING)
1036                 check_xr(&conf->manpath);
1037 }
1038
1039 static void
1040 check_xr(struct manpaths *paths)
1041 {
1042         struct mansearch         search;
1043         struct mandoc_xr        *xr;
1044         size_t                   sz;
1045
1046         for (xr = mandoc_xr_get(); xr != NULL; xr = xr->next) {
1047                 if (xr->line == -1)
1048                         continue;
1049                 search.arch = NULL;
1050                 search.sec = xr->sec;
1051                 search.outkey = NULL;
1052                 search.argmode = ARG_NAME;
1053                 search.firstmatch = 1;
1054                 if (mansearch(&search, paths, 1, &xr->name, NULL, &sz))
1055                         continue;
1056                 if (fs_search(&search, paths, xr->name, NULL, &sz) != -1)
1057                         continue;
1058                 if (xr->count == 1)
1059                         mandoc_msg(MANDOCERR_XR_BAD, xr->line,
1060                             xr->pos + 1, "Xr %s %s", xr->name, xr->sec);
1061                 else
1062                         mandoc_msg(MANDOCERR_XR_BAD, xr->line,
1063                             xr->pos + 1, "Xr %s %s (%d times)",
1064                             xr->name, xr->sec, xr->count);
1065         }
1066 }
1067
1068 static void
1069 outdata_alloc(struct outstate *outst, struct manoutput *outconf)
1070 {
1071         switch (outst->outtype) {
1072         case OUTT_HTML:
1073                 outst->outdata = html_alloc(outconf);
1074                 break;
1075         case OUTT_UTF8:
1076                 outst->outdata = utf8_alloc(outconf);
1077                 break;
1078         case OUTT_LOCALE:
1079                 outst->outdata = locale_alloc(outconf);
1080                 break;
1081         case OUTT_ASCII:
1082                 outst->outdata = ascii_alloc(outconf);
1083                 break;
1084         case OUTT_PDF:
1085                 outst->outdata = pdf_alloc(outconf);
1086                 break;
1087         case OUTT_PS:
1088                 outst->outdata = ps_alloc(outconf);
1089                 break;
1090         default:
1091                 break;
1092         }
1093 }
1094
1095 static void
1096 passthrough(int fd, int synopsis_only)
1097 {
1098         const char       synb[] = "S\bSY\bYN\bNO\bOP\bPS\bSI\bIS\bS";
1099         const char       synr[] = "SYNOPSIS";
1100
1101         FILE            *stream;
1102         char            *line, *cp;
1103         size_t           linesz;
1104         ssize_t          len, written;
1105         int              lno, print;
1106
1107         stream = NULL;
1108         line = NULL;
1109         linesz = 0;
1110
1111         if (fflush(stdout) == EOF) {
1112                 mandoc_msg(MANDOCERR_FFLUSH, 0, 0, "%s", strerror(errno));
1113                 goto done;
1114         }
1115         if ((stream = fdopen(fd, "r")) == NULL) {
1116                 close(fd);
1117                 mandoc_msg(MANDOCERR_FDOPEN, 0, 0, "%s", strerror(errno));
1118                 goto done;
1119         }
1120
1121         lno = print = 0;
1122         while ((len = getline(&line, &linesz, stream)) != -1) {
1123                 lno++;
1124                 cp = line;
1125                 if (synopsis_only) {
1126                         if (print) {
1127                                 if ( ! isspace((unsigned char)*cp))
1128                                         goto done;
1129                                 while (isspace((unsigned char)*cp)) {
1130                                         cp++;
1131                                         len--;
1132                                 }
1133                         } else {
1134                                 if (strcmp(cp, synb) == 0 ||
1135                                     strcmp(cp, synr) == 0)
1136                                         print = 1;
1137                                 continue;
1138                         }
1139                 }
1140                 for (; len > 0; len -= written) {
1141                         if ((written = write(STDOUT_FILENO, cp, len)) == -1) {
1142                                 mandoc_msg(MANDOCERR_WRITE, 0, 0,
1143                                     "%s", strerror(errno));
1144                                 goto done;
1145                         }
1146                 }
1147         }
1148         if (ferror(stream))
1149                 mandoc_msg(MANDOCERR_GETLINE, lno, 0, "%s", strerror(errno));
1150
1151 done:
1152         free(line);
1153         if (stream != NULL)
1154                 fclose(stream);
1155 }
1156
1157 static int
1158 woptions(char *arg, enum mandoc_os *os_e, int *wstop)
1159 {
1160         char            *v, *o;
1161         const char      *toks[11];
1162
1163         toks[0] = "stop";
1164         toks[1] = "all";
1165         toks[2] = "base";
1166         toks[3] = "style";
1167         toks[4] = "warning";
1168         toks[5] = "error";
1169         toks[6] = "unsupp";
1170         toks[7] = "fatal";
1171         toks[8] = "openbsd";
1172         toks[9] = "netbsd";
1173         toks[10] = NULL;
1174
1175         while (*arg) {
1176                 o = arg;
1177                 switch (getsubopt(&arg, (char * const *)toks, &v)) {
1178                 case 0:
1179                         *wstop = 1;
1180                         break;
1181                 case 1:
1182                 case 2:
1183                         mandoc_msg_setmin(MANDOCERR_BASE);
1184                         break;
1185                 case 3:
1186                         mandoc_msg_setmin(MANDOCERR_STYLE);
1187                         break;
1188                 case 4:
1189                         mandoc_msg_setmin(MANDOCERR_WARNING);
1190                         break;
1191                 case 5:
1192                         mandoc_msg_setmin(MANDOCERR_ERROR);
1193                         break;
1194                 case 6:
1195                         mandoc_msg_setmin(MANDOCERR_UNSUPP);
1196                         break;
1197                 case 7:
1198                         mandoc_msg_setmin(MANDOCERR_BADARG);
1199                         break;
1200                 case 8:
1201                         mandoc_msg_setmin(MANDOCERR_BASE);
1202                         *os_e = MANDOC_OS_OPENBSD;
1203                         break;
1204                 case 9:
1205                         mandoc_msg_setmin(MANDOCERR_BASE);
1206                         *os_e = MANDOC_OS_NETBSD;
1207                         break;
1208                 default:
1209                         mandoc_msg(MANDOCERR_BADARG_BAD, 0, 0, "-W %s", o);
1210                         return -1;
1211                 }
1212         }
1213         return 0;
1214 }
1215
1216 /*
1217  * Wait until moved to the foreground,
1218  * then fork the pager and wait for the user to close it.
1219  */
1220 static void
1221 run_pager(struct outstate *outst, char *tag_target)
1222 {
1223         int      signum, status;
1224         pid_t    man_pgid, tc_pgid;
1225         pid_t    pager_pid, wait_pid;
1226
1227         man_pgid = getpgid(0);
1228         outst->tag_files->tcpgid =
1229             man_pgid == getpid() ? getpgid(getppid()) : man_pgid;
1230         pager_pid = 0;
1231         signum = SIGSTOP;
1232
1233         for (;;) {
1234                 /* Stop here until moved to the foreground. */
1235
1236                 tc_pgid = tcgetpgrp(STDOUT_FILENO);
1237                 if (tc_pgid != man_pgid) {
1238                         if (tc_pgid == pager_pid) {
1239                                 (void)tcsetpgrp(STDOUT_FILENO, man_pgid);
1240                                 if (signum == SIGTTIN)
1241                                         continue;
1242                         } else
1243                                 outst->tag_files->tcpgid = tc_pgid;
1244                         kill(0, signum);
1245                         continue;
1246                 }
1247
1248                 /* Once in the foreground, activate the pager. */
1249
1250                 if (pager_pid) {
1251                         (void)tcsetpgrp(STDOUT_FILENO, pager_pid);
1252                         kill(pager_pid, SIGCONT);
1253                 } else
1254                         pager_pid = spawn_pager(outst, tag_target);
1255
1256                 /* Wait for the pager to stop or exit. */
1257
1258                 while ((wait_pid = waitpid(pager_pid, &status,
1259                     WUNTRACED)) == -1 && errno == EINTR)
1260                         continue;
1261
1262                 if (wait_pid == -1) {
1263                         mandoc_msg(MANDOCERR_WAIT, 0, 0,
1264                             "%s", strerror(errno));
1265                         break;
1266                 }
1267                 if (!WIFSTOPPED(status))
1268                         break;
1269
1270                 signum = WSTOPSIG(status);
1271         }
1272 }
1273
1274 static pid_t
1275 spawn_pager(struct outstate *outst, char *tag_target)
1276 {
1277         const struct timespec timeout = { 0, 100000000 };  /* 0.1s */
1278 #define MAX_PAGER_ARGS 16
1279         char            *argv[MAX_PAGER_ARGS];
1280         const char      *pager;
1281         char            *cp;
1282 #if HAVE_LESS_T
1283         size_t           cmdlen;
1284 #endif
1285         int              argc, use_ofn;
1286         pid_t            pager_pid;
1287
1288         assert(outst->tag_files->ofd == -1);
1289         assert(outst->tag_files->tfs == NULL);
1290
1291         pager = getenv("MANPAGER");
1292         if (pager == NULL || *pager == '\0')
1293                 pager = getenv("PAGER");
1294         if (pager == NULL || *pager == '\0')
1295                 pager = BINM_PAGER;
1296         cp = mandoc_strdup(pager);
1297
1298         /*
1299          * Parse the pager command into words.
1300          * Intentionally do not do anything fancy here.
1301          */
1302
1303         argc = 0;
1304         while (argc + 5 < MAX_PAGER_ARGS) {
1305                 argv[argc++] = cp;
1306                 cp = strchr(cp, ' ');
1307                 if (cp == NULL)
1308                         break;
1309                 *cp++ = '\0';
1310                 while (*cp == ' ')
1311                         cp++;
1312                 if (*cp == '\0')
1313                         break;
1314         }
1315
1316         /* For less(1), use the tag file. */
1317
1318         use_ofn = 1;
1319 #if HAVE_LESS_T
1320         if (*outst->tag_files->tfn != '\0' &&
1321             (cmdlen = strlen(argv[0])) >= 4) {
1322                 cp = argv[0] + cmdlen - 4;
1323                 if (strcmp(cp, "less") == 0) {
1324                         argv[argc++] = mandoc_strdup("-T");
1325                         argv[argc++] = outst->tag_files->tfn;
1326                         if (tag_target != NULL) {
1327                                 argv[argc++] = mandoc_strdup("-t");
1328                                 argv[argc++] = tag_target;
1329                                 use_ofn = 0;
1330                         }
1331                 }
1332         }
1333 #endif
1334         if (use_ofn) {
1335                 if (outst->outtype == OUTT_HTML && tag_target != NULL)
1336                         mandoc_asprintf(&argv[argc], "file://%s#%s",
1337                             outst->tag_files->ofn, tag_target);
1338                 else
1339                         argv[argc] = outst->tag_files->ofn;
1340                 argc++;
1341         }
1342         argv[argc] = NULL;
1343
1344         switch (pager_pid = fork()) {
1345         case -1:
1346                 mandoc_msg(MANDOCERR_FORK, 0, 0, "%s", strerror(errno));
1347                 exit(mandoc_msg_getrc());
1348         case 0:
1349                 break;
1350         default:
1351                 (void)setpgid(pager_pid, 0);
1352                 (void)tcsetpgrp(STDOUT_FILENO, pager_pid);
1353 #if HAVE_PLEDGE
1354                 if (pledge("stdio rpath tmppath tty proc", NULL) == -1) {
1355                         mandoc_msg(MANDOCERR_PLEDGE, 0, 0,
1356                             "%s", strerror(errno));
1357                         exit(mandoc_msg_getrc());
1358                 }
1359 #endif
1360                 outst->tag_files->pager_pid = pager_pid;
1361                 return pager_pid;
1362         }
1363
1364         /*
1365          * The child process becomes the pager.
1366          * Do not start it before controlling the terminal.
1367          */
1368
1369         while (tcgetpgrp(STDOUT_FILENO) != getpid())
1370                 nanosleep(&timeout, NULL);
1371
1372         execvp(argv[0], argv);
1373         mandoc_msg(MANDOCERR_EXEC, 0, 0, "%s: %s", argv[0], strerror(errno));
1374         _exit(mandoc_msg_getrc());
1375 }