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