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