]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/mdocml/main.c
Update mandoc to 20160116
[FreeBSD/FreeBSD.git] / contrib / mdocml / main.c
1 /*      $Id: main.c,v 1.262 2016/01/08 02:53:13 schwarze Exp $ */
2 /*
3  * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
4  * Copyright (c) 2010-2012, 2014-2016 Ingo Schwarze <schwarze@openbsd.org>
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 #include "config.h"
20
21 #include <sys/types.h>
22 #include <sys/param.h>  /* MACHINE */
23 #include <sys/wait.h>
24
25 #include <assert.h>
26 #include <ctype.h>
27 #if HAVE_ERR
28 #include <err.h>
29 #endif
30 #include <errno.h>
31 #include <fcntl.h>
32 #include <glob.h>
33 #include <signal.h>
34 #include <stdio.h>
35 #include <stdint.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <unistd.h>
39
40 #include "mandoc_aux.h"
41 #include "mandoc.h"
42 #include "roff.h"
43 #include "mdoc.h"
44 #include "man.h"
45 #include "tag.h"
46 #include "main.h"
47 #include "manconf.h"
48 #include "mansearch.h"
49
50 #if !defined(__GNUC__) || (__GNUC__ < 2)
51 # if !defined(lint)
52 #  define __attribute__(x)
53 # endif
54 #endif /* !defined(__GNUC__) || (__GNUC__ < 2) */
55
56 enum    outmode {
57         OUTMODE_DEF = 0,
58         OUTMODE_FLN,
59         OUTMODE_LST,
60         OUTMODE_ALL,
61         OUTMODE_INT,
62         OUTMODE_ONE
63 };
64
65 enum    outt {
66         OUTT_ASCII = 0, /* -Tascii */
67         OUTT_LOCALE,    /* -Tlocale */
68         OUTT_UTF8,      /* -Tutf8 */
69         OUTT_TREE,      /* -Ttree */
70         OUTT_MAN,       /* -Tman */
71         OUTT_HTML,      /* -Thtml */
72         OUTT_LINT,      /* -Tlint */
73         OUTT_PS,        /* -Tps */
74         OUTT_PDF        /* -Tpdf */
75 };
76
77 struct  curparse {
78         struct mparse    *mp;
79         enum mandoclevel  wlevel;       /* ignore messages below this */
80         int               wstop;        /* stop after a file with a warning */
81         enum outt         outtype;      /* which output to use */
82         void             *outdata;      /* data for output */
83         struct manoutput *outopts;      /* output options */
84 };
85
86 static  int               fs_lookup(const struct manpaths *,
87                                 size_t ipath, const char *,
88                                 const char *, const char *,
89                                 struct manpage **, size_t *);
90 static  void              fs_search(const struct mansearch *,
91                                 const struct manpaths *, int, char**,
92                                 struct manpage **, size_t *);
93 static  int               koptions(int *, char *);
94 #if HAVE_SQLITE3
95 int                       mandocdb(int, char**);
96 #endif
97 static  int               moptions(int *, char *);
98 static  void              mmsg(enum mandocerr, enum mandoclevel,
99                                 const char *, int, int, const char *);
100 static  void              parse(struct curparse *, int, const char *);
101 static  void              passthrough(const char *, int, int);
102 static  pid_t             spawn_pager(struct tag_files *);
103 static  int               toptions(struct curparse *, char *);
104 static  void              usage(enum argmode) __attribute__((noreturn));
105 static  int               woptions(struct curparse *, char *);
106
107 static  const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
108 static  char              help_arg[] = "help";
109 static  char             *help_argv[] = {help_arg, NULL};
110 static  enum mandoclevel  rc;
111
112
113 int
114 main(int argc, char *argv[])
115 {
116         struct manconf   conf;
117         struct curparse  curp;
118         struct mansearch search;
119         struct tag_files *tag_files;
120         const char      *progname;
121         char            *auxpaths;
122         char            *defos;
123         unsigned char   *uc;
124         struct manpage  *res, *resp;
125         char            *conf_file, *defpaths;
126         size_t           isec, i, sz;
127         int              prio, best_prio;
128         char             sec;
129         enum outmode     outmode;
130         int              fd;
131         int              show_usage;
132         int              options;
133         int              use_pager;
134         int              status, signum;
135         int              c;
136         pid_t            pager_pid, tc_pgid, man_pgid, pid;
137
138 #if HAVE_PROGNAME
139         progname = getprogname();
140 #else
141         if (argc < 1)
142                 progname = mandoc_strdup("mandoc");
143         else if ((progname = strrchr(argv[0], '/')) == NULL)
144                 progname = argv[0];
145         else
146                 ++progname;
147         setprogname(progname);
148 #endif
149
150 #if HAVE_SQLITE3
151         if (strncmp(progname, "mandocdb", 8) == 0 ||
152             strcmp(progname, BINM_MAKEWHATIS) == 0)
153                 return mandocdb(argc, argv);
154 #endif
155
156 #if HAVE_PLEDGE
157         if (pledge("stdio rpath tmppath tty proc exec flock", NULL) == -1)
158                 err((int)MANDOCLEVEL_SYSERR, "pledge");
159 #endif
160
161         /* Search options. */
162
163         memset(&conf, 0, sizeof(conf));
164         conf_file = defpaths = NULL;
165         auxpaths = NULL;
166
167         memset(&search, 0, sizeof(struct mansearch));
168         search.outkey = "Nd";
169
170         if (strcmp(progname, BINM_MAN) == 0)
171                 search.argmode = ARG_NAME;
172         else if (strcmp(progname, BINM_APROPOS) == 0)
173                 search.argmode = ARG_EXPR;
174         else if (strcmp(progname, BINM_WHATIS) == 0)
175                 search.argmode = ARG_WORD;
176         else if (strncmp(progname, "help", 4) == 0)
177                 search.argmode = ARG_NAME;
178         else
179                 search.argmode = ARG_FILE;
180
181         /* Parser and formatter options. */
182
183         memset(&curp, 0, sizeof(struct curparse));
184         curp.outtype = OUTT_LOCALE;
185         curp.wlevel  = MANDOCLEVEL_BADARG;
186         curp.outopts = &conf.output;
187         options = MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1;
188         defos = NULL;
189
190         use_pager = 1;
191         tag_files = NULL;
192         show_usage = 0;
193         outmode = OUTMODE_DEF;
194
195         while (-1 != (c = getopt(argc, argv,
196                         "aC:cfhI:iK:klM:m:O:S:s:T:VW:w"))) {
197                 switch (c) {
198                 case 'a':
199                         outmode = OUTMODE_ALL;
200                         break;
201                 case 'C':
202                         conf_file = optarg;
203                         break;
204                 case 'c':
205                         use_pager = 0;
206                         break;
207                 case 'f':
208                         search.argmode = ARG_WORD;
209                         break;
210                 case 'h':
211                         conf.output.synopsisonly = 1;
212                         use_pager = 0;
213                         outmode = OUTMODE_ALL;
214                         break;
215                 case 'I':
216                         if (strncmp(optarg, "os=", 3)) {
217                                 warnx("-I %s: Bad argument", optarg);
218                                 return (int)MANDOCLEVEL_BADARG;
219                         }
220                         if (defos) {
221                                 warnx("-I %s: Duplicate argument", optarg);
222                                 return (int)MANDOCLEVEL_BADARG;
223                         }
224                         defos = mandoc_strdup(optarg + 3);
225                         break;
226                 case 'i':
227                         outmode = OUTMODE_INT;
228                         break;
229                 case 'K':
230                         if ( ! koptions(&options, optarg))
231                                 return (int)MANDOCLEVEL_BADARG;
232                         break;
233                 case 'k':
234                         search.argmode = ARG_EXPR;
235                         break;
236                 case 'l':
237                         search.argmode = ARG_FILE;
238                         outmode = OUTMODE_ALL;
239                         break;
240                 case 'M':
241                         defpaths = optarg;
242                         break;
243                 case 'm':
244                         auxpaths = optarg;
245                         break;
246                 case 'O':
247                         search.outkey = optarg;
248                         while (optarg != NULL)
249                                 manconf_output(&conf.output,
250                                     strsep(&optarg, ","));
251                         break;
252                 case 'S':
253                         search.arch = optarg;
254                         break;
255                 case 's':
256                         search.sec = optarg;
257                         break;
258                 case 'T':
259                         if ( ! toptions(&curp, optarg))
260                                 return (int)MANDOCLEVEL_BADARG;
261                         break;
262                 case 'W':
263                         if ( ! woptions(&curp, optarg))
264                                 return (int)MANDOCLEVEL_BADARG;
265                         break;
266                 case 'w':
267                         outmode = OUTMODE_FLN;
268                         break;
269                 default:
270                         show_usage = 1;
271                         break;
272                 }
273         }
274
275         if (show_usage)
276                 usage(search.argmode);
277
278         /* Postprocess options. */
279
280         if (outmode == OUTMODE_DEF) {
281                 switch (search.argmode) {
282                 case ARG_FILE:
283                         outmode = OUTMODE_ALL;
284                         use_pager = 0;
285                         break;
286                 case ARG_NAME:
287                         outmode = OUTMODE_ONE;
288                         break;
289                 default:
290                         outmode = OUTMODE_LST;
291                         break;
292                 }
293         }
294
295         if (outmode == OUTMODE_FLN ||
296             outmode == OUTMODE_LST ||
297             !isatty(STDOUT_FILENO))
298                 use_pager = 0;
299
300 #if HAVE_PLEDGE
301         if (!use_pager)
302                 if (pledge("stdio rpath flock", NULL) == -1)
303                         err((int)MANDOCLEVEL_SYSERR, "pledge");
304 #endif
305
306         /* Parse arguments. */
307
308         if (argc > 0) {
309                 argc -= optind;
310                 argv += optind;
311         }
312         resp = NULL;
313
314         /*
315          * Quirks for help(1)
316          * and for a man(1) section argument without -s.
317          */
318
319         if (search.argmode == ARG_NAME) {
320                 if (*progname == 'h') {
321                         if (argc == 0) {
322                                 argv = help_argv;
323                                 argc = 1;
324                         }
325                 } else if (argc > 1 &&
326                     ((uc = (unsigned char *)argv[0]) != NULL) &&
327                     ((isdigit(uc[0]) && (uc[1] == '\0' ||
328                       (isalpha(uc[1]) && uc[2] == '\0'))) ||
329                      (uc[0] == 'n' && uc[1] == '\0'))) {
330                         search.sec = (char *)uc;
331                         argv++;
332                         argc--;
333                 }
334                 if (search.arch == NULL)
335                         search.arch = getenv("MACHINE");
336 #ifdef MACHINE
337                 if (search.arch == NULL)
338                         search.arch = MACHINE;
339 #endif
340         }
341
342         rc = MANDOCLEVEL_OK;
343
344         /* man(1), whatis(1), apropos(1) */
345
346         if (search.argmode != ARG_FILE) {
347                 if (argc == 0)
348                         usage(search.argmode);
349
350                 if (search.argmode == ARG_NAME &&
351                     outmode == OUTMODE_ONE)
352                         search.firstmatch = 1;
353
354                 /* Access the mandoc database. */
355
356                 manconf_parse(&conf, conf_file, defpaths, auxpaths);
357 #if HAVE_SQLITE3
358                 mansearch_setup(1);
359                 if ( ! mansearch(&search, &conf.manpath,
360                     argc, argv, &res, &sz))
361                         usage(search.argmode);
362 #else
363                 if (search.argmode != ARG_NAME) {
364                         fputs("mandoc: database support not compiled in\n",
365                             stderr);
366                         return (int)MANDOCLEVEL_BADARG;
367                 }
368                 sz = 0;
369 #endif
370
371                 if (sz == 0) {
372                         if (search.argmode == ARG_NAME)
373                                 fs_search(&search, &conf.manpath,
374                                     argc, argv, &res, &sz);
375                         else
376                                 warnx("nothing appropriate");
377                 }
378
379                 if (sz == 0) {
380                         rc = MANDOCLEVEL_BADARG;
381                         goto out;
382                 }
383
384                 /*
385                  * For standard man(1) and -a output mode,
386                  * prepare for copying filename pointers
387                  * into the program parameter array.
388                  */
389
390                 if (outmode == OUTMODE_ONE) {
391                         argc = 1;
392                         best_prio = 10;
393                 } else if (outmode == OUTMODE_ALL)
394                         argc = (int)sz;
395
396                 /* Iterate all matching manuals. */
397
398                 resp = res;
399                 for (i = 0; i < sz; i++) {
400                         if (outmode == OUTMODE_FLN)
401                                 puts(res[i].file);
402                         else if (outmode == OUTMODE_LST)
403                                 printf("%s - %s\n", res[i].names,
404                                     res[i].output == NULL ? "" :
405                                     res[i].output);
406                         else if (outmode == OUTMODE_ONE) {
407                                 /* Search for the best section. */
408                                 isec = strcspn(res[i].file, "123456789");
409                                 sec = res[i].file[isec];
410                                 if ('\0' == sec)
411                                         continue;
412                                 prio = sec_prios[sec - '1'];
413                                 if (prio >= best_prio)
414                                         continue;
415                                 best_prio = prio;
416                                 resp = res + i;
417                         }
418                 }
419
420                 /*
421                  * For man(1), -a and -i output mode, fall through
422                  * to the main mandoc(1) code iterating files
423                  * and running the parsers on each of them.
424                  */
425
426                 if (outmode == OUTMODE_FLN || outmode == OUTMODE_LST)
427                         goto out;
428         }
429
430         /* mandoc(1) */
431
432 #if HAVE_PLEDGE
433         if (use_pager) {
434                 if (pledge("stdio rpath tmppath tty proc exec", NULL) == -1)
435                         err((int)MANDOCLEVEL_SYSERR, "pledge");
436         } else {
437                 if (pledge("stdio rpath", NULL) == -1)
438                         err((int)MANDOCLEVEL_SYSERR, "pledge");
439         }
440 #endif
441
442         if (search.argmode == ARG_FILE && ! moptions(&options, auxpaths))
443                 return (int)MANDOCLEVEL_BADARG;
444
445         mchars_alloc();
446         curp.mp = mparse_alloc(options, curp.wlevel, mmsg, defos);
447
448         /*
449          * Conditionally start up the lookaside buffer before parsing.
450          */
451         if (OUTT_MAN == curp.outtype)
452                 mparse_keep(curp.mp);
453
454         if (argc < 1) {
455                 if (use_pager)
456                         tag_files = tag_init();
457                 parse(&curp, STDIN_FILENO, "<stdin>");
458         }
459
460         while (argc > 0) {
461                 fd = mparse_open(curp.mp, resp != NULL ? resp->file : *argv);
462                 if (fd != -1) {
463                         if (use_pager) {
464                                 tag_files = tag_init();
465                                 use_pager = 0;
466                         }
467
468                         if (resp == NULL)
469                                 parse(&curp, fd, *argv);
470                         else if (resp->form & FORM_SRC) {
471                                 /* For .so only; ignore failure. */
472                                 chdir(conf.manpath.paths[resp->ipath]);
473                                 parse(&curp, fd, resp->file);
474                         } else
475                                 passthrough(resp->file, fd,
476                                     conf.output.synopsisonly);
477
478                         if (argc > 1 && curp.outtype <= OUTT_UTF8)
479                                 ascii_sepline(curp.outdata);
480                 } else if (rc < MANDOCLEVEL_ERROR)
481                         rc = MANDOCLEVEL_ERROR;
482
483                 if (MANDOCLEVEL_OK != rc && curp.wstop)
484                         break;
485
486                 if (resp != NULL)
487                         resp++;
488                 else
489                         argv++;
490                 if (--argc)
491                         mparse_reset(curp.mp);
492         }
493
494         if (curp.outdata != NULL) {
495                 switch (curp.outtype) {
496                 case OUTT_HTML:
497                         html_free(curp.outdata);
498                         break;
499                 case OUTT_UTF8:
500                 case OUTT_LOCALE:
501                 case OUTT_ASCII:
502                         ascii_free(curp.outdata);
503                         break;
504                 case OUTT_PDF:
505                 case OUTT_PS:
506                         pspdf_free(curp.outdata);
507                         break;
508                 default:
509                         break;
510                 }
511         }
512         mparse_free(curp.mp);
513         mchars_free();
514
515 out:
516         if (search.argmode != ARG_FILE) {
517                 manconf_free(&conf);
518 #if HAVE_SQLITE3
519                 mansearch_free(res, sz);
520                 mansearch_setup(0);
521 #endif
522         }
523
524         free(defos);
525
526         /*
527          * When using a pager, finish writing both temporary files,
528          * fork it, wait for the user to close it, and clean up.
529          */
530
531         if (tag_files != NULL) {
532                 fclose(stdout);
533                 tag_write();
534                 man_pgid = getpgid(0);
535                 tag_files->tcpgid = man_pgid == getpid() ?
536                     getpgid(getppid()) : man_pgid;
537                 pager_pid = 0;
538                 signum = SIGSTOP;
539                 for (;;) {
540
541                         /* Stop here until moved to the foreground. */
542
543                         tc_pgid = tcgetpgrp(STDIN_FILENO);
544                         if (tc_pgid != man_pgid) {
545                                 if (tc_pgid == pager_pid) {
546                                         (void)tcsetpgrp(STDIN_FILENO,
547                                             man_pgid);
548                                         if (signum == SIGTTIN)
549                                                 continue;
550                                 } else
551                                         tag_files->tcpgid = tc_pgid;
552                                 kill(0, signum);
553                                 continue;
554                         }
555
556                         /* Once in the foreground, activate the pager. */
557
558                         if (pager_pid) {
559                                 (void)tcsetpgrp(STDIN_FILENO, pager_pid);
560                                 kill(pager_pid, SIGCONT);
561                         } else
562                                 pager_pid = spawn_pager(tag_files);
563
564                         /* Wait for the pager to stop or exit. */
565
566                         while ((pid = waitpid(pager_pid, &status,
567                             WUNTRACED)) == -1 && errno == EINTR)
568                                 continue;
569
570                         if (pid == -1) {
571                                 warn("wait");
572                                 rc = MANDOCLEVEL_SYSERR;
573                                 break;
574                         }
575                         if (!WIFSTOPPED(status))
576                                 break;
577
578                         signum = WSTOPSIG(status);
579                 }
580                 tag_unlink();
581         }
582
583         return (int)rc;
584 }
585
586 static void
587 usage(enum argmode argmode)
588 {
589
590         switch (argmode) {
591         case ARG_FILE:
592                 fputs("usage: mandoc [-acfhkl] [-I os=name] "
593                     "[-K encoding] [-mformat] [-O option]\n"
594                     "\t      [-T output] [-W level] [file ...]\n", stderr);
595                 break;
596         case ARG_NAME:
597                 fputs("usage: man [-acfhklw] [-C file] [-I os=name] "
598                     "[-K encoding] [-M path] [-m path]\n"
599                     "\t   [-O option=value] [-S subsection] [-s section] "
600                     "[-T output] [-W level]\n"
601                     "\t   [section] name ...\n", stderr);
602                 break;
603         case ARG_WORD:
604                 fputs("usage: whatis [-acfhklw] [-C file] "
605                     "[-M path] [-m path] [-O outkey] [-S arch]\n"
606                     "\t      [-s section] name ...\n", stderr);
607                 break;
608         case ARG_EXPR:
609                 fputs("usage: apropos [-acfhklw] [-C file] "
610                     "[-M path] [-m path] [-O outkey] [-S arch]\n"
611                     "\t       [-s section] expression ...\n", stderr);
612                 break;
613         }
614         exit((int)MANDOCLEVEL_BADARG);
615 }
616
617 static int
618 fs_lookup(const struct manpaths *paths, size_t ipath,
619         const char *sec, const char *arch, const char *name,
620         struct manpage **res, size_t *ressz)
621 {
622         glob_t           globinfo;
623         struct manpage  *page;
624         char            *file;
625         int              form, globres;
626
627         form = FORM_SRC;
628         mandoc_asprintf(&file, "%s/man%s/%s.%s",
629             paths->paths[ipath], sec, name, sec);
630         if (access(file, R_OK) != -1)
631                 goto found;
632         free(file);
633
634         mandoc_asprintf(&file, "%s/cat%s/%s.0",
635             paths->paths[ipath], sec, name);
636         if (access(file, R_OK) != -1) {
637                 form = FORM_CAT;
638                 goto found;
639         }
640         free(file);
641
642         if (arch != NULL) {
643                 mandoc_asprintf(&file, "%s/man%s/%s/%s.%s",
644                     paths->paths[ipath], sec, arch, name, sec);
645                 if (access(file, R_OK) != -1)
646                         goto found;
647                 free(file);
648         }
649
650         mandoc_asprintf(&file, "%s/man%s/%s.[01-9]*",
651             paths->paths[ipath], sec, name);
652         globres = glob(file, 0, NULL, &globinfo);
653         if (globres != 0 && globres != GLOB_NOMATCH)
654                 warn("%s: glob", file);
655         free(file);
656         if (globres == 0)
657                 file = mandoc_strdup(*globinfo.gl_pathv);
658         globfree(&globinfo);
659         if (globres != 0)
660                 return 0;
661
662 found:
663 #if HAVE_SQLITE3
664         warnx("outdated mandoc.db lacks %s(%s) entry, run makewhatis %s",
665             name, sec, paths->paths[ipath]);
666 #endif
667         *res = mandoc_reallocarray(*res, ++*ressz, sizeof(struct manpage));
668         page = *res + (*ressz - 1);
669         page->file = file;
670         page->names = NULL;
671         page->output = NULL;
672         page->ipath = ipath;
673         page->bits = NAME_FILE & NAME_MASK;
674         page->sec = (*sec >= '1' && *sec <= '9') ? *sec - '1' + 1 : 10;
675         page->form = form;
676         return 1;
677 }
678
679 static void
680 fs_search(const struct mansearch *cfg, const struct manpaths *paths,
681         int argc, char **argv, struct manpage **res, size_t *ressz)
682 {
683         const char *const sections[] =
684             {"1", "8", "6", "2", "3", "3p", "5", "7", "4", "9"};
685         const size_t nsec = sizeof(sections)/sizeof(sections[0]);
686
687         size_t           ipath, isec, lastsz;
688
689         assert(cfg->argmode == ARG_NAME);
690
691         *res = NULL;
692         *ressz = lastsz = 0;
693         while (argc) {
694                 for (ipath = 0; ipath < paths->sz; ipath++) {
695                         if (cfg->sec != NULL) {
696                                 if (fs_lookup(paths, ipath, cfg->sec,
697                                     cfg->arch, *argv, res, ressz) &&
698                                     cfg->firstmatch)
699                                         return;
700                         } else for (isec = 0; isec < nsec; isec++)
701                                 if (fs_lookup(paths, ipath, sections[isec],
702                                     cfg->arch, *argv, res, ressz) &&
703                                     cfg->firstmatch)
704                                         return;
705                 }
706                 if (*ressz == lastsz)
707                         warnx("No entry for %s in the manual.", *argv);
708                 lastsz = *ressz;
709                 argv++;
710                 argc--;
711         }
712 }
713
714 static void
715 parse(struct curparse *curp, int fd, const char *file)
716 {
717         enum mandoclevel  rctmp;
718         struct roff_man  *man;
719
720         /* Begin by parsing the file itself. */
721
722         assert(file);
723         assert(fd > 0);
724
725         rctmp = mparse_readfd(curp->mp, fd, file);
726         if (fd != STDIN_FILENO)
727                 close(fd);
728         if (rc < rctmp)
729                 rc = rctmp;
730
731         /*
732          * With -Wstop and warnings or errors of at least the requested
733          * level, do not produce output.
734          */
735
736         if (rctmp != MANDOCLEVEL_OK && curp->wstop)
737                 return;
738
739         /* If unset, allocate output dev now (if applicable). */
740
741         if (curp->outdata == NULL) {
742                 switch (curp->outtype) {
743                 case OUTT_HTML:
744                         curp->outdata = html_alloc(curp->outopts);
745                         break;
746                 case OUTT_UTF8:
747                         curp->outdata = utf8_alloc(curp->outopts);
748                         break;
749                 case OUTT_LOCALE:
750                         curp->outdata = locale_alloc(curp->outopts);
751                         break;
752                 case OUTT_ASCII:
753                         curp->outdata = ascii_alloc(curp->outopts);
754                         break;
755                 case OUTT_PDF:
756                         curp->outdata = pdf_alloc(curp->outopts);
757                         break;
758                 case OUTT_PS:
759                         curp->outdata = ps_alloc(curp->outopts);
760                         break;
761                 default:
762                         break;
763                 }
764         }
765
766         mparse_result(curp->mp, &man, NULL);
767
768         /* Execute the out device, if it exists. */
769
770         if (man == NULL)
771                 return;
772         if (man->macroset == MACROSET_MDOC) {
773                 mdoc_validate(man);
774                 switch (curp->outtype) {
775                 case OUTT_HTML:
776                         html_mdoc(curp->outdata, man);
777                         break;
778                 case OUTT_TREE:
779                         tree_mdoc(curp->outdata, man);
780                         break;
781                 case OUTT_MAN:
782                         man_mdoc(curp->outdata, man);
783                         break;
784                 case OUTT_PDF:
785                 case OUTT_ASCII:
786                 case OUTT_UTF8:
787                 case OUTT_LOCALE:
788                 case OUTT_PS:
789                         terminal_mdoc(curp->outdata, man);
790                         break;
791                 default:
792                         break;
793                 }
794         }
795         if (man->macroset == MACROSET_MAN) {
796                 man_validate(man);
797                 switch (curp->outtype) {
798                 case OUTT_HTML:
799                         html_man(curp->outdata, man);
800                         break;
801                 case OUTT_TREE:
802                         tree_man(curp->outdata, man);
803                         break;
804                 case OUTT_MAN:
805                         man_man(curp->outdata, man);
806                         break;
807                 case OUTT_PDF:
808                 case OUTT_ASCII:
809                 case OUTT_UTF8:
810                 case OUTT_LOCALE:
811                 case OUTT_PS:
812                         terminal_man(curp->outdata, man);
813                         break;
814                 default:
815                         break;
816                 }
817         }
818 }
819
820 static void
821 passthrough(const char *file, int fd, int synopsis_only)
822 {
823         const char       synb[] = "S\bSY\bYN\bNO\bOP\bPS\bSI\bIS\bS";
824         const char       synr[] = "SYNOPSIS";
825
826         FILE            *stream;
827         const char      *syscall;
828         char            *line, *cp;
829         size_t           linesz;
830         int              print;
831
832         line = NULL;
833         linesz = 0;
834
835         if ((stream = fdopen(fd, "r")) == NULL) {
836                 close(fd);
837                 syscall = "fdopen";
838                 goto fail;
839         }
840
841         print = 0;
842         while (getline(&line, &linesz, stream) != -1) {
843                 cp = line;
844                 if (synopsis_only) {
845                         if (print) {
846                                 if ( ! isspace((unsigned char)*cp))
847                                         goto done;
848                                 while (isspace((unsigned char)*cp))
849                                         cp++;
850                         } else {
851                                 if (strcmp(cp, synb) == 0 ||
852                                     strcmp(cp, synr) == 0)
853                                         print = 1;
854                                 continue;
855                         }
856                 }
857                 if (fputs(cp, stdout)) {
858                         fclose(stream);
859                         syscall = "fputs";
860                         goto fail;
861                 }
862         }
863
864         if (ferror(stream)) {
865                 fclose(stream);
866                 syscall = "getline";
867                 goto fail;
868         }
869
870 done:
871         free(line);
872         fclose(stream);
873         return;
874
875 fail:
876         free(line);
877         warn("%s: SYSERR: %s", file, syscall);
878         if (rc < MANDOCLEVEL_SYSERR)
879                 rc = MANDOCLEVEL_SYSERR;
880 }
881
882 static int
883 koptions(int *options, char *arg)
884 {
885
886         if ( ! strcmp(arg, "utf-8")) {
887                 *options |=  MPARSE_UTF8;
888                 *options &= ~MPARSE_LATIN1;
889         } else if ( ! strcmp(arg, "iso-8859-1")) {
890                 *options |=  MPARSE_LATIN1;
891                 *options &= ~MPARSE_UTF8;
892         } else if ( ! strcmp(arg, "us-ascii")) {
893                 *options &= ~(MPARSE_UTF8 | MPARSE_LATIN1);
894         } else {
895                 warnx("-K %s: Bad argument", arg);
896                 return 0;
897         }
898         return 1;
899 }
900
901 static int
902 moptions(int *options, char *arg)
903 {
904
905         if (arg == NULL)
906                 /* nothing to do */;
907         else if (0 == strcmp(arg, "doc"))
908                 *options |= MPARSE_MDOC;
909         else if (0 == strcmp(arg, "andoc"))
910                 /* nothing to do */;
911         else if (0 == strcmp(arg, "an"))
912                 *options |= MPARSE_MAN;
913         else {
914                 warnx("-m %s: Bad argument", arg);
915                 return 0;
916         }
917
918         return 1;
919 }
920
921 static int
922 toptions(struct curparse *curp, char *arg)
923 {
924
925         if (0 == strcmp(arg, "ascii"))
926                 curp->outtype = OUTT_ASCII;
927         else if (0 == strcmp(arg, "lint")) {
928                 curp->outtype = OUTT_LINT;
929                 curp->wlevel  = MANDOCLEVEL_WARNING;
930         } else if (0 == strcmp(arg, "tree"))
931                 curp->outtype = OUTT_TREE;
932         else if (0 == strcmp(arg, "man"))
933                 curp->outtype = OUTT_MAN;
934         else if (0 == strcmp(arg, "html"))
935                 curp->outtype = OUTT_HTML;
936         else if (0 == strcmp(arg, "utf8"))
937                 curp->outtype = OUTT_UTF8;
938         else if (0 == strcmp(arg, "locale"))
939                 curp->outtype = OUTT_LOCALE;
940         else if (0 == strcmp(arg, "xhtml"))
941                 curp->outtype = OUTT_HTML;
942         else if (0 == strcmp(arg, "ps"))
943                 curp->outtype = OUTT_PS;
944         else if (0 == strcmp(arg, "pdf"))
945                 curp->outtype = OUTT_PDF;
946         else {
947                 warnx("-T %s: Bad argument", arg);
948                 return 0;
949         }
950
951         return 1;
952 }
953
954 static int
955 woptions(struct curparse *curp, char *arg)
956 {
957         char            *v, *o;
958         const char      *toks[7];
959
960         toks[0] = "stop";
961         toks[1] = "all";
962         toks[2] = "warning";
963         toks[3] = "error";
964         toks[4] = "unsupp";
965         toks[5] = "fatal";
966         toks[6] = NULL;
967
968         while (*arg) {
969                 o = arg;
970                 switch (getsubopt(&arg, UNCONST(toks), &v)) {
971                 case 0:
972                         curp->wstop = 1;
973                         break;
974                 case 1:
975                 case 2:
976                         curp->wlevel = MANDOCLEVEL_WARNING;
977                         break;
978                 case 3:
979                         curp->wlevel = MANDOCLEVEL_ERROR;
980                         break;
981                 case 4:
982                         curp->wlevel = MANDOCLEVEL_UNSUPP;
983                         break;
984                 case 5:
985                         curp->wlevel = MANDOCLEVEL_BADARG;
986                         break;
987                 default:
988                         warnx("-W %s: Bad argument", o);
989                         return 0;
990                 }
991         }
992
993         return 1;
994 }
995
996 static void
997 mmsg(enum mandocerr t, enum mandoclevel lvl,
998                 const char *file, int line, int col, const char *msg)
999 {
1000         const char      *mparse_msg;
1001
1002         fprintf(stderr, "%s: %s:", getprogname(), file);
1003
1004         if (line)
1005                 fprintf(stderr, "%d:%d:", line, col + 1);
1006
1007         fprintf(stderr, " %s", mparse_strlevel(lvl));
1008
1009         if (NULL != (mparse_msg = mparse_strerror(t)))
1010                 fprintf(stderr, ": %s", mparse_msg);
1011
1012         if (msg)
1013                 fprintf(stderr, ": %s", msg);
1014
1015         fputc('\n', stderr);
1016 }
1017
1018 static pid_t
1019 spawn_pager(struct tag_files *tag_files)
1020 {
1021 #define MAX_PAGER_ARGS 16
1022         char            *argv[MAX_PAGER_ARGS];
1023         const char      *pager;
1024         char            *cp;
1025         size_t           cmdlen;
1026         int              argc;
1027         pid_t            pager_pid;
1028
1029         pager = getenv("MANPAGER");
1030         if (pager == NULL || *pager == '\0')
1031                 pager = getenv("PAGER");
1032         if (pager == NULL || *pager == '\0')
1033                 pager = "more -s";
1034         cp = mandoc_strdup(pager);
1035
1036         /*
1037          * Parse the pager command into words.
1038          * Intentionally do not do anything fancy here.
1039          */
1040
1041         argc = 0;
1042         while (argc + 4 < MAX_PAGER_ARGS) {
1043                 argv[argc++] = cp;
1044                 cp = strchr(cp, ' ');
1045                 if (cp == NULL)
1046                         break;
1047                 *cp++ = '\0';
1048                 while (*cp == ' ')
1049                         cp++;
1050                 if (*cp == '\0')
1051                         break;
1052         }
1053
1054         /* For more(1) and less(1), use the tag file. */
1055
1056         if ((cmdlen = strlen(argv[0])) >= 4) {
1057                 cp = argv[0] + cmdlen - 4;
1058                 if (strcmp(cp, "less") == 0 || strcmp(cp, "more") == 0) {
1059                         argv[argc++] = mandoc_strdup("-T");
1060                         argv[argc++] = tag_files->tfn;
1061                 }
1062         }
1063         argv[argc++] = tag_files->ofn;
1064         argv[argc] = NULL;
1065
1066         switch (pager_pid = fork()) {
1067         case -1:
1068                 err((int)MANDOCLEVEL_SYSERR, "fork");
1069         case 0:
1070                 /* Set pgrp in both parent and child to avoid racing exec. */
1071                 (void)setpgid(0, 0);
1072                 break;
1073         default:
1074                 (void)setpgid(pager_pid, 0);
1075                 (void)tcsetpgrp(STDIN_FILENO, pager_pid);
1076 #if HAVE_PLEDGE
1077                 if (pledge("stdio rpath tmppath tty proc", NULL) == -1)
1078                         err((int)MANDOCLEVEL_SYSERR, "pledge");
1079 #endif
1080                 tag_files->pager_pid = pager_pid;
1081                 return pager_pid;
1082         }
1083
1084         /* The child process becomes the pager. */
1085
1086         if (dup2(tag_files->ofd, STDOUT_FILENO) == -1)
1087                 err((int)MANDOCLEVEL_SYSERR, "pager stdout");
1088         close(tag_files->ofd);
1089         close(tag_files->tfd);
1090         execvp(argv[0], argv);
1091         err((int)MANDOCLEVEL_SYSERR, "exec %s", argv[0]);
1092 }