]> git.cameronkatri.com Git - mandoc.git/blob - main.c
79a2ddd6fd1dd534c9bbfe563c6fc1288299c844
[mandoc.git] / main.c
1 /* $Id: main.c,v 1.195 2014/10/28 17:36:19 schwarze Exp $ */
2 /*
3 * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
4 * Copyright (c) 2010, 2011, 2012, 2014 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 AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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
23 #include <assert.h>
24 #include <ctype.h>
25 #include <errno.h>
26 #include <fcntl.h>
27 #include <stdio.h>
28 #include <stdint.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <unistd.h>
32
33 #include "mandoc.h"
34 #include "mandoc_aux.h"
35 #include "main.h"
36 #include "mdoc.h"
37 #include "man.h"
38 #include "manpath.h"
39 #include "mansearch.h"
40
41 #if !defined(__GNUC__) || (__GNUC__ < 2)
42 # if !defined(lint)
43 # define __attribute__(x)
44 # endif
45 #endif /* !defined(__GNUC__) || (__GNUC__ < 2) */
46
47 enum outmode {
48 OUTMODE_DEF = 0,
49 OUTMODE_FLN,
50 OUTMODE_LST,
51 OUTMODE_ALL,
52 OUTMODE_INT,
53 OUTMODE_ONE
54 };
55
56 typedef void (*out_mdoc)(void *, const struct mdoc *);
57 typedef void (*out_man)(void *, const struct man *);
58 typedef void (*out_free)(void *);
59
60 enum outt {
61 OUTT_ASCII = 0, /* -Tascii */
62 OUTT_LOCALE, /* -Tlocale */
63 OUTT_UTF8, /* -Tutf8 */
64 OUTT_TREE, /* -Ttree */
65 OUTT_MAN, /* -Tman */
66 OUTT_HTML, /* -Thtml */
67 OUTT_LINT, /* -Tlint */
68 OUTT_PS, /* -Tps */
69 OUTT_PDF /* -Tpdf */
70 };
71
72 struct curparse {
73 struct mparse *mp;
74 struct mchars *mchars; /* character table */
75 enum mandoclevel wlevel; /* ignore messages below this */
76 int wstop; /* stop after a file with a warning */
77 enum outt outtype; /* which output to use */
78 out_mdoc outmdoc; /* mdoc output ptr */
79 out_man outman; /* man output ptr */
80 out_free outfree; /* free output ptr */
81 void *outdata; /* data for output */
82 char outopts[BUFSIZ]; /* buf of output opts */
83 };
84
85 static int koptions(int *, char *);
86 static int moptions(int *, char *);
87 static void mmsg(enum mandocerr, enum mandoclevel,
88 const char *, int, int, const char *);
89 static void parse(struct curparse *, int,
90 const char *, enum mandoclevel *);
91 static enum mandoclevel passthrough(const char *, int);
92 static void spawn_pager(void);
93 static int toptions(struct curparse *, char *);
94 static void usage(enum argmode) __attribute__((noreturn));
95 static void version(void) __attribute__((noreturn));
96 static int woptions(struct curparse *, char *);
97
98 static const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
99 static const char *progname;
100
101
102 int
103 main(int argc, char *argv[])
104 {
105 struct curparse curp;
106 struct mansearch search;
107 struct manpaths paths;
108 char *conf_file, *defpaths, *auxpaths;
109 char *defos;
110 #if HAVE_SQLITE3
111 struct manpage *res, *resp;
112 size_t isec, i, sz;
113 int prio, best_prio;
114 char sec;
115 #endif
116 enum mandoclevel rc;
117 enum outmode outmode;
118 pid_t child_pid;
119 int fd;
120 int show_usage;
121 int use_pager;
122 int options;
123 int c;
124
125 progname = strrchr(argv[0], '/');
126 if (progname == NULL)
127 progname = argv[0];
128 else
129 ++progname;
130
131 /* Search options. */
132
133 memset(&paths, 0, sizeof(struct manpaths));
134 conf_file = defpaths = auxpaths = NULL;
135
136 memset(&search, 0, sizeof(struct mansearch));
137 search.outkey = "Nd";
138
139 if (strcmp(progname, "man") == 0)
140 search.argmode = ARG_NAME;
141 else if (strncmp(progname, "apropos", 7) == 0)
142 search.argmode = ARG_EXPR;
143 else if (strncmp(progname, "whatis", 6) == 0)
144 search.argmode = ARG_WORD;
145 else
146 search.argmode = ARG_FILE;
147
148 /* Parser and formatter options. */
149
150 memset(&curp, 0, sizeof(struct curparse));
151 curp.outtype = OUTT_ASCII;
152 curp.wlevel = MANDOCLEVEL_FATAL;
153 options = MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1;
154 defos = NULL;
155
156 use_pager = 1;
157 show_usage = 0;
158 outmode = OUTMODE_DEF;
159
160 while (-1 != (c = getopt(argc, argv,
161 "aC:cfhI:iK:klM:m:O:S:s:T:VW:w"))) {
162 switch (c) {
163 case 'a':
164 outmode = OUTMODE_ALL;
165 break;
166 case 'C':
167 conf_file = optarg;
168 break;
169 case 'c':
170 use_pager = 0;
171 break;
172 case 'f':
173 search.argmode = ARG_WORD;
174 break;
175 case 'h':
176 (void)strlcat(curp.outopts, "synopsis,", BUFSIZ);
177 outmode = OUTMODE_ALL;
178 break;
179 case 'I':
180 if (strncmp(optarg, "os=", 3)) {
181 fprintf(stderr,
182 "%s: -I%s: Bad argument\n",
183 progname, optarg);
184 return((int)MANDOCLEVEL_BADARG);
185 }
186 if (defos) {
187 fprintf(stderr,
188 "%s: -I%s: Duplicate argument\n",
189 progname, optarg);
190 return((int)MANDOCLEVEL_BADARG);
191 }
192 defos = mandoc_strdup(optarg + 3);
193 break;
194 case 'i':
195 outmode = OUTMODE_INT;
196 break;
197 case 'K':
198 if ( ! koptions(&options, optarg))
199 return((int)MANDOCLEVEL_BADARG);
200 break;
201 case 'k':
202 search.argmode = ARG_EXPR;
203 break;
204 case 'l':
205 search.argmode = ARG_FILE;
206 outmode = OUTMODE_ALL;
207 break;
208 case 'M':
209 defpaths = optarg;
210 break;
211 case 'm':
212 auxpaths = optarg;
213 break;
214 case 'O':
215 search.outkey = optarg;
216 (void)strlcat(curp.outopts, optarg, BUFSIZ);
217 (void)strlcat(curp.outopts, ",", BUFSIZ);
218 break;
219 case 'S':
220 search.arch = optarg;
221 break;
222 case 's':
223 search.sec = optarg;
224 break;
225 case 'T':
226 if ( ! toptions(&curp, optarg))
227 return((int)MANDOCLEVEL_BADARG);
228 break;
229 case 'W':
230 if ( ! woptions(&curp, optarg))
231 return((int)MANDOCLEVEL_BADARG);
232 break;
233 case 'w':
234 outmode = OUTMODE_FLN;
235 break;
236 case 'V':
237 version();
238 /* NOTREACHED */
239 default:
240 show_usage = 1;
241 break;
242 }
243 }
244
245 if (show_usage)
246 usage(search.argmode);
247
248 /* Postprocess options. */
249
250 if (outmode == OUTMODE_DEF) {
251 switch (search.argmode) {
252 case ARG_FILE:
253 outmode = OUTMODE_ALL;
254 use_pager = 0;
255 break;
256 case ARG_NAME:
257 outmode = OUTMODE_ONE;
258 break;
259 default:
260 outmode = OUTMODE_LST;
261 break;
262 }
263 }
264
265 /* Parse arguments. */
266
267 argc -= optind;
268 argv += optind;
269 #if HAVE_SQLITE3
270 resp = NULL;
271 #endif
272
273 /* Quirk for a man(1) section argument without -s. */
274
275 if (search.argmode == ARG_NAME &&
276 argv[0] != NULL &&
277 isdigit((unsigned char)argv[0][0]) &&
278 (argv[0][1] == '\0' || !strcmp(argv[0], "3p"))) {
279 search.sec = argv[0];
280 argv++;
281 argc--;
282 }
283
284 rc = MANDOCLEVEL_OK;
285
286 /* man(1), whatis(1), apropos(1) */
287
288 if (search.argmode != ARG_FILE) {
289 #if HAVE_SQLITE3
290 if (argc == 0)
291 usage(search.argmode);
292
293 /* Access the mandoc database. */
294
295 manpath_parse(&paths, conf_file, defpaths, auxpaths);
296 mansearch_setup(1);
297 if( ! mansearch(&search, &paths, argc, argv, &res, &sz))
298 usage(search.argmode);
299 resp = res;
300
301 if (sz == 0) {
302 if (search.argmode == ARG_NAME)
303 fprintf(stderr, "%s: No entry for %s "
304 "in the manual.\n", progname, argv[0]);
305 rc = MANDOCLEVEL_BADARG;
306 goto out;
307 }
308
309 /*
310 * For standard man(1) and -a output mode,
311 * prepare for copying filename pointers
312 * into the program parameter array.
313 */
314
315 if (outmode == OUTMODE_ONE) {
316 argc = 1;
317 best_prio = 10;
318 } else if (outmode == OUTMODE_ALL)
319 argc = (int)sz;
320
321 /* Iterate all matching manuals. */
322
323 for (i = 0; i < sz; i++) {
324 if (outmode == OUTMODE_FLN)
325 puts(res[i].file);
326 else if (outmode == OUTMODE_LST)
327 printf("%s - %s\n", res[i].names,
328 res[i].output == NULL ? "" :
329 res[i].output);
330 else if (outmode == OUTMODE_ONE) {
331 /* Search for the best section. */
332 isec = strcspn(res[i].file, "123456789");
333 sec = res[i].file[isec];
334 if ('\0' == sec)
335 continue;
336 prio = sec_prios[sec - '1'];
337 if (prio >= best_prio)
338 continue;
339 best_prio = prio;
340 resp = res + i;
341 }
342 }
343
344 /*
345 * For man(1), -a and -i output mode, fall through
346 * to the main mandoc(1) code iterating files
347 * and running the parsers on each of them.
348 */
349
350 if (outmode == OUTMODE_FLN || outmode == OUTMODE_LST)
351 goto out;
352 #else
353 fputs("mandoc: database support not compiled in\n",
354 stderr);
355 return((int)MANDOCLEVEL_BADARG);
356 #endif
357 }
358
359 /* mandoc(1) */
360
361 if ( ! moptions(&options, auxpaths))
362 return((int)MANDOCLEVEL_BADARG);
363
364 if (use_pager && isatty(STDOUT_FILENO))
365 spawn_pager();
366
367 curp.mchars = mchars_alloc();
368 curp.mp = mparse_alloc(options, curp.wlevel, mmsg,
369 curp.mchars, defos);
370
371 /*
372 * Conditionally start up the lookaside buffer before parsing.
373 */
374 if (OUTT_MAN == curp.outtype)
375 mparse_keep(curp.mp);
376
377 if (argc == 0)
378 parse(&curp, STDIN_FILENO, "<stdin>", &rc);
379
380 while (argc) {
381 #if HAVE_SQLITE3
382 if (resp != NULL) {
383 rc = mparse_open(curp.mp, &fd, resp->file,
384 &child_pid);
385 if (fd == -1)
386 /* nothing */;
387 else if (resp->form & FORM_SRC) {
388 /* For .so only; ignore failure. */
389 chdir(paths.paths[resp->ipath]);
390 parse(&curp, fd, resp->file, &rc);
391 } else
392 rc = passthrough(resp->file, fd);
393 resp++;
394 } else
395 #endif
396 {
397 rc = mparse_open(curp.mp, &fd, *argv++,
398 &child_pid);
399 if (fd != -1)
400 parse(&curp, fd, argv[-1], &rc);
401 }
402
403 if (child_pid &&
404 mparse_wait(curp.mp, child_pid) != MANDOCLEVEL_OK)
405 rc = MANDOCLEVEL_SYSERR;
406
407 if (MANDOCLEVEL_OK != rc && curp.wstop)
408 break;
409 argc--;
410 }
411
412 if (curp.outfree)
413 (*curp.outfree)(curp.outdata);
414 mparse_free(curp.mp);
415 mchars_free(curp.mchars);
416
417 #if HAVE_SQLITE3
418 out:
419 if (search.argmode != ARG_FILE) {
420 manpath_free(&paths);
421 mansearch_free(res, sz);
422 mansearch_setup(0);
423 }
424 #endif
425
426 free(defos);
427
428 return((int)rc);
429 }
430
431 static void
432 version(void)
433 {
434
435 printf("mandoc %s\n", VERSION);
436 exit((int)MANDOCLEVEL_OK);
437 }
438
439 static void
440 usage(enum argmode argmode)
441 {
442
443 switch (argmode) {
444 case ARG_FILE:
445 fputs("usage: mandoc [-acfhklV] [-Ios=name] "
446 "[-mformat] [-Ooption] [-Toutput] [-Wlevel]\n"
447 "\t [file ...]\n", stderr);
448 break;
449 case ARG_NAME:
450 fputs("usage: man [-acfhklVw] [-C file] "
451 "[-M path] [-m path] [-S arch] [-s section]\n"
452 "\t [section] name ...\n", stderr);
453 break;
454 case ARG_WORD:
455 fputs("usage: whatis [-acfhklVw] [-C file] "
456 "[-M path] [-m path] [-O outkey] [-S arch]\n"
457 "\t [-s section] name ...\n", stderr);
458 break;
459 case ARG_EXPR:
460 fputs("usage: apropos [-acfhklVw] [-C file] "
461 "[-M path] [-m path] [-O outkey] [-S arch]\n"
462 "\t [-s section] expression ...\n", stderr);
463 break;
464 }
465 exit((int)MANDOCLEVEL_BADARG);
466 }
467
468 static void
469 parse(struct curparse *curp, int fd, const char *file,
470 enum mandoclevel *level)
471 {
472 enum mandoclevel rc;
473 struct mdoc *mdoc;
474 struct man *man;
475
476 /* Begin by parsing the file itself. */
477
478 assert(file);
479 assert(fd >= -1);
480
481 rc = mparse_readfd(curp->mp, fd, file);
482
483 /* Stop immediately if the parse has failed. */
484
485 if (MANDOCLEVEL_FATAL <= rc)
486 goto cleanup;
487
488 /*
489 * With -Wstop and warnings or errors of at least the requested
490 * level, do not produce output.
491 */
492
493 if (MANDOCLEVEL_OK != rc && curp->wstop)
494 goto cleanup;
495
496 /* If unset, allocate output dev now (if applicable). */
497
498 if ( ! (curp->outman && curp->outmdoc)) {
499 switch (curp->outtype) {
500 case OUTT_HTML:
501 curp->outdata = html_alloc(curp->mchars,
502 curp->outopts);
503 curp->outfree = html_free;
504 break;
505 case OUTT_UTF8:
506 curp->outdata = utf8_alloc(curp->mchars,
507 curp->outopts);
508 curp->outfree = ascii_free;
509 break;
510 case OUTT_LOCALE:
511 curp->outdata = locale_alloc(curp->mchars,
512 curp->outopts);
513 curp->outfree = ascii_free;
514 break;
515 case OUTT_ASCII:
516 curp->outdata = ascii_alloc(curp->mchars,
517 curp->outopts);
518 curp->outfree = ascii_free;
519 break;
520 case OUTT_PDF:
521 curp->outdata = pdf_alloc(curp->mchars,
522 curp->outopts);
523 curp->outfree = pspdf_free;
524 break;
525 case OUTT_PS:
526 curp->outdata = ps_alloc(curp->mchars,
527 curp->outopts);
528 curp->outfree = pspdf_free;
529 break;
530 default:
531 break;
532 }
533
534 switch (curp->outtype) {
535 case OUTT_HTML:
536 curp->outman = html_man;
537 curp->outmdoc = html_mdoc;
538 break;
539 case OUTT_TREE:
540 curp->outman = tree_man;
541 curp->outmdoc = tree_mdoc;
542 break;
543 case OUTT_MAN:
544 curp->outmdoc = man_mdoc;
545 curp->outman = man_man;
546 break;
547 case OUTT_PDF:
548 /* FALLTHROUGH */
549 case OUTT_ASCII:
550 /* FALLTHROUGH */
551 case OUTT_UTF8:
552 /* FALLTHROUGH */
553 case OUTT_LOCALE:
554 /* FALLTHROUGH */
555 case OUTT_PS:
556 curp->outman = terminal_man;
557 curp->outmdoc = terminal_mdoc;
558 break;
559 default:
560 break;
561 }
562 }
563
564 mparse_result(curp->mp, &mdoc, &man, NULL);
565
566 /* Execute the out device, if it exists. */
567
568 if (man && curp->outman)
569 (*curp->outman)(curp->outdata, man);
570 if (mdoc && curp->outmdoc)
571 (*curp->outmdoc)(curp->outdata, mdoc);
572
573 cleanup:
574
575 mparse_reset(curp->mp);
576
577 if (*level < rc)
578 *level = rc;
579 }
580
581 static enum mandoclevel
582 passthrough(const char *file, int fd)
583 {
584 char buf[BUFSIZ];
585 const char *syscall;
586 ssize_t nr, nw, off;
587
588 while ((nr = read(fd, buf, BUFSIZ)) != -1 && nr != 0)
589 for (off = 0; off < nr; off += nw)
590 if ((nw = write(STDOUT_FILENO, buf + off,
591 (size_t)(nr - off))) == -1 || nw == 0) {
592 close(fd);
593 syscall = "write";
594 goto fail;
595 }
596
597 close(fd);
598
599 if (nr == 0)
600 return(MANDOCLEVEL_OK);
601
602 syscall = "read";
603 fail:
604 fprintf(stderr, "%s: %s: SYSERR: %s: %s",
605 progname, file, syscall, strerror(errno));
606 return(MANDOCLEVEL_SYSERR);
607 }
608
609 static int
610 koptions(int *options, char *arg)
611 {
612
613 if ( ! strcmp(arg, "utf-8")) {
614 *options |= MPARSE_UTF8;
615 *options &= ~MPARSE_LATIN1;
616 } else if ( ! strcmp(arg, "iso-8859-1")) {
617 *options |= MPARSE_LATIN1;
618 *options &= ~MPARSE_UTF8;
619 } else if ( ! strcmp(arg, "us-ascii")) {
620 *options &= ~(MPARSE_UTF8 | MPARSE_LATIN1);
621 } else {
622 fprintf(stderr, "%s: -K%s: Bad argument\n",
623 progname, arg);
624 return(0);
625 }
626 return(1);
627 }
628
629 static int
630 moptions(int *options, char *arg)
631 {
632
633 if (arg == NULL)
634 /* nothing to do */;
635 else if (0 == strcmp(arg, "doc"))
636 *options |= MPARSE_MDOC;
637 else if (0 == strcmp(arg, "andoc"))
638 /* nothing to do */;
639 else if (0 == strcmp(arg, "an"))
640 *options |= MPARSE_MAN;
641 else {
642 fprintf(stderr, "%s: -m%s: Bad argument\n",
643 progname, arg);
644 return(0);
645 }
646
647 return(1);
648 }
649
650 static int
651 toptions(struct curparse *curp, char *arg)
652 {
653
654 if (0 == strcmp(arg, "ascii"))
655 curp->outtype = OUTT_ASCII;
656 else if (0 == strcmp(arg, "lint")) {
657 curp->outtype = OUTT_LINT;
658 curp->wlevel = MANDOCLEVEL_WARNING;
659 } else if (0 == strcmp(arg, "tree"))
660 curp->outtype = OUTT_TREE;
661 else if (0 == strcmp(arg, "man"))
662 curp->outtype = OUTT_MAN;
663 else if (0 == strcmp(arg, "html"))
664 curp->outtype = OUTT_HTML;
665 else if (0 == strcmp(arg, "utf8"))
666 curp->outtype = OUTT_UTF8;
667 else if (0 == strcmp(arg, "locale"))
668 curp->outtype = OUTT_LOCALE;
669 else if (0 == strcmp(arg, "xhtml"))
670 curp->outtype = OUTT_HTML;
671 else if (0 == strcmp(arg, "ps"))
672 curp->outtype = OUTT_PS;
673 else if (0 == strcmp(arg, "pdf"))
674 curp->outtype = OUTT_PDF;
675 else {
676 fprintf(stderr, "%s: -T%s: Bad argument\n",
677 progname, arg);
678 return(0);
679 }
680
681 return(1);
682 }
683
684 static int
685 woptions(struct curparse *curp, char *arg)
686 {
687 char *v, *o;
688 const char *toks[6];
689
690 toks[0] = "stop";
691 toks[1] = "all";
692 toks[2] = "warning";
693 toks[3] = "error";
694 toks[4] = "fatal";
695 toks[5] = NULL;
696
697 while (*arg) {
698 o = arg;
699 switch (getsubopt(&arg, UNCONST(toks), &v)) {
700 case 0:
701 curp->wstop = 1;
702 break;
703 case 1:
704 /* FALLTHROUGH */
705 case 2:
706 curp->wlevel = MANDOCLEVEL_WARNING;
707 break;
708 case 3:
709 curp->wlevel = MANDOCLEVEL_ERROR;
710 break;
711 case 4:
712 curp->wlevel = MANDOCLEVEL_FATAL;
713 break;
714 default:
715 fprintf(stderr, "%s: -W%s: Bad argument\n",
716 progname, o);
717 return(0);
718 }
719 }
720
721 return(1);
722 }
723
724 static void
725 mmsg(enum mandocerr t, enum mandoclevel lvl,
726 const char *file, int line, int col, const char *msg)
727 {
728 const char *mparse_msg;
729
730 fprintf(stderr, "%s: %s:", progname, file);
731
732 if (line)
733 fprintf(stderr, "%d:%d:", line, col + 1);
734
735 fprintf(stderr, " %s", mparse_strlevel(lvl));
736
737 if (NULL != (mparse_msg = mparse_strerror(t)))
738 fprintf(stderr, ": %s", mparse_msg);
739
740 if (msg)
741 fprintf(stderr, ": %s", msg);
742
743 fputc('\n', stderr);
744 }
745
746 static void
747 spawn_pager(void)
748 {
749 #define MAX_PAGER_ARGS 16
750 char *argv[MAX_PAGER_ARGS];
751 const char *pager;
752 char *cp;
753 int fildes[2];
754 int argc;
755
756 if (pipe(fildes) == -1) {
757 fprintf(stderr, "%s: pipe: %s\n",
758 progname, strerror(errno));
759 return;
760 }
761
762 switch (fork()) {
763 case -1:
764 fprintf(stderr, "%s: fork: %s\n",
765 progname, strerror(errno));
766 exit((int)MANDOCLEVEL_SYSERR);
767 case 0:
768 close(fildes[0]);
769 if (dup2(fildes[1], STDOUT_FILENO) == -1) {
770 fprintf(stderr, "%s: dup output: %s\n",
771 progname, strerror(errno));
772 exit((int)MANDOCLEVEL_SYSERR);
773 }
774 return;
775 default:
776 break;
777 }
778
779 /* The original process becomes the pager. */
780
781 close(fildes[1]);
782 if (dup2(fildes[0], STDIN_FILENO) == -1) {
783 fprintf(stderr, "%s: dup input: %s\n",
784 progname, strerror(errno));
785 exit((int)MANDOCLEVEL_SYSERR);
786 }
787
788 pager = getenv("MANPAGER");
789 if (pager == NULL || *pager == '\0')
790 pager = getenv("PAGER");
791 if (pager == NULL || *pager == '\0')
792 pager = "/usr/bin/more -s";
793 cp = mandoc_strdup(pager);
794
795 /*
796 * Parse the pager command into words.
797 * Intentionally do not do anything fancy here.
798 */
799
800 argc = 0;
801 while (argc + 1 < MAX_PAGER_ARGS) {
802 argv[argc++] = cp;
803 cp = strchr(cp, ' ');
804 if (cp == NULL)
805 break;
806 *cp++ = '\0';
807 while (*cp == ' ')
808 cp++;
809 if (*cp == '\0')
810 break;
811 }
812 argv[argc] = NULL;
813
814 /* Hand over to the pager. */
815
816 execvp(argv[0], argv);
817 fprintf(stderr, "%s: exec: %s\n",
818 progname, strerror(errno));
819 exit((int)MANDOCLEVEL_SYSERR);
820 }