]> git.cameronkatri.com Git - mandoc.git/blob - main.c
integrate preconv(1) into mandoc(1);
[mandoc.git] / main.c
1 /* $Id: main.c,v 1.194 2014/10/25 01:03:52 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_XHTML, /* -Txhtml */
68 OUTT_LINT, /* -Tlint */
69 OUTT_PS, /* -Tps */
70 OUTT_PDF /* -Tpdf */
71 };
72
73 struct curparse {
74 struct mparse *mp;
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.mp = mparse_alloc(options, curp.wlevel, mmsg, defos);
368
369 /*
370 * Conditionally start up the lookaside buffer before parsing.
371 */
372 if (OUTT_MAN == curp.outtype)
373 mparse_keep(curp.mp);
374
375 if (argc == 0)
376 parse(&curp, STDIN_FILENO, "<stdin>", &rc);
377
378 while (argc) {
379 #if HAVE_SQLITE3
380 if (resp != NULL) {
381 rc = mparse_open(curp.mp, &fd, resp->file,
382 &child_pid);
383 if (fd == -1)
384 /* nothing */;
385 else if (resp->form & FORM_SRC) {
386 /* For .so only; ignore failure. */
387 chdir(paths.paths[resp->ipath]);
388 parse(&curp, fd, resp->file, &rc);
389 } else
390 rc = passthrough(resp->file, fd);
391 resp++;
392 } else
393 #endif
394 {
395 rc = mparse_open(curp.mp, &fd, *argv++,
396 &child_pid);
397 if (fd != -1)
398 parse(&curp, fd, argv[-1], &rc);
399 }
400
401 if (child_pid &&
402 mparse_wait(curp.mp, child_pid) != MANDOCLEVEL_OK)
403 rc = MANDOCLEVEL_SYSERR;
404
405 if (MANDOCLEVEL_OK != rc && curp.wstop)
406 break;
407 argc--;
408 }
409
410 if (curp.outfree)
411 (*curp.outfree)(curp.outdata);
412 if (curp.mp)
413 mparse_free(curp.mp);
414
415 #if HAVE_SQLITE3
416 out:
417 if (search.argmode != ARG_FILE) {
418 manpath_free(&paths);
419 mansearch_free(res, sz);
420 mansearch_setup(0);
421 }
422 #endif
423
424 free(defos);
425
426 return((int)rc);
427 }
428
429 static void
430 version(void)
431 {
432
433 printf("mandoc %s\n", VERSION);
434 exit((int)MANDOCLEVEL_OK);
435 }
436
437 static void
438 usage(enum argmode argmode)
439 {
440
441 switch (argmode) {
442 case ARG_FILE:
443 fputs("usage: mandoc [-acfhklV] [-Ios=name] "
444 "[-mformat] [-Ooption] [-Toutput] [-Wlevel]\n"
445 "\t [file ...]\n", stderr);
446 break;
447 case ARG_NAME:
448 fputs("usage: man [-acfhklVw] [-C file] "
449 "[-M path] [-m path] [-S arch] [-s section]\n"
450 "\t [section] name ...\n", stderr);
451 break;
452 case ARG_WORD:
453 fputs("usage: whatis [-acfhklVw] [-C file] "
454 "[-M path] [-m path] [-O outkey] [-S arch]\n"
455 "\t [-s section] name ...\n", stderr);
456 break;
457 case ARG_EXPR:
458 fputs("usage: apropos [-acfhklVw] [-C file] "
459 "[-M path] [-m path] [-O outkey] [-S arch]\n"
460 "\t [-s section] expression ...\n", stderr);
461 break;
462 }
463 exit((int)MANDOCLEVEL_BADARG);
464 }
465
466 static void
467 parse(struct curparse *curp, int fd, const char *file,
468 enum mandoclevel *level)
469 {
470 enum mandoclevel rc;
471 struct mdoc *mdoc;
472 struct man *man;
473
474 /* Begin by parsing the file itself. */
475
476 assert(file);
477 assert(fd >= -1);
478
479 rc = mparse_readfd(curp->mp, fd, file);
480
481 /* Stop immediately if the parse has failed. */
482
483 if (MANDOCLEVEL_FATAL <= rc)
484 goto cleanup;
485
486 /*
487 * With -Wstop and warnings or errors of at least the requested
488 * level, do not produce output.
489 */
490
491 if (MANDOCLEVEL_OK != rc && curp->wstop)
492 goto cleanup;
493
494 /* If unset, allocate output dev now (if applicable). */
495
496 if ( ! (curp->outman && curp->outmdoc)) {
497 switch (curp->outtype) {
498 case OUTT_XHTML:
499 curp->outdata = xhtml_alloc(curp->outopts);
500 curp->outfree = html_free;
501 break;
502 case OUTT_HTML:
503 curp->outdata = html_alloc(curp->outopts);
504 curp->outfree = html_free;
505 break;
506 case OUTT_UTF8:
507 curp->outdata = utf8_alloc(curp->outopts);
508 curp->outfree = ascii_free;
509 break;
510 case OUTT_LOCALE:
511 curp->outdata = locale_alloc(curp->outopts);
512 curp->outfree = ascii_free;
513 break;
514 case OUTT_ASCII:
515 curp->outdata = ascii_alloc(curp->outopts);
516 curp->outfree = ascii_free;
517 break;
518 case OUTT_PDF:
519 curp->outdata = pdf_alloc(curp->outopts);
520 curp->outfree = pspdf_free;
521 break;
522 case OUTT_PS:
523 curp->outdata = ps_alloc(curp->outopts);
524 curp->outfree = pspdf_free;
525 break;
526 default:
527 break;
528 }
529
530 switch (curp->outtype) {
531 case OUTT_HTML:
532 /* FALLTHROUGH */
533 case OUTT_XHTML:
534 curp->outman = html_man;
535 curp->outmdoc = html_mdoc;
536 break;
537 case OUTT_TREE:
538 curp->outman = tree_man;
539 curp->outmdoc = tree_mdoc;
540 break;
541 case OUTT_MAN:
542 curp->outmdoc = man_mdoc;
543 curp->outman = man_man;
544 break;
545 case OUTT_PDF:
546 /* FALLTHROUGH */
547 case OUTT_ASCII:
548 /* FALLTHROUGH */
549 case OUTT_UTF8:
550 /* FALLTHROUGH */
551 case OUTT_LOCALE:
552 /* FALLTHROUGH */
553 case OUTT_PS:
554 curp->outman = terminal_man;
555 curp->outmdoc = terminal_mdoc;
556 break;
557 default:
558 break;
559 }
560 }
561
562 mparse_result(curp->mp, &mdoc, &man, NULL);
563
564 /* Execute the out device, if it exists. */
565
566 if (man && curp->outman)
567 (*curp->outman)(curp->outdata, man);
568 if (mdoc && curp->outmdoc)
569 (*curp->outmdoc)(curp->outdata, mdoc);
570
571 cleanup:
572
573 mparse_reset(curp->mp);
574
575 if (*level < rc)
576 *level = rc;
577 }
578
579 static enum mandoclevel
580 passthrough(const char *file, int fd)
581 {
582 char buf[BUFSIZ];
583 const char *syscall;
584 ssize_t nr, nw, off;
585
586 while ((nr = read(fd, buf, BUFSIZ)) != -1 && nr != 0)
587 for (off = 0; off < nr; off += nw)
588 if ((nw = write(STDOUT_FILENO, buf + off,
589 (size_t)(nr - off))) == -1 || nw == 0) {
590 close(fd);
591 syscall = "write";
592 goto fail;
593 }
594
595 close(fd);
596
597 if (nr == 0)
598 return(MANDOCLEVEL_OK);
599
600 syscall = "read";
601 fail:
602 fprintf(stderr, "%s: %s: SYSERR: %s: %s",
603 progname, file, syscall, strerror(errno));
604 return(MANDOCLEVEL_SYSERR);
605 }
606
607 static int
608 koptions(int *options, char *arg)
609 {
610
611 if ( ! strcmp(arg, "utf-8")) {
612 *options |= MPARSE_UTF8;
613 *options &= ~MPARSE_LATIN1;
614 } else if ( ! strcmp(arg, "iso-8859-1")) {
615 *options |= MPARSE_LATIN1;
616 *options &= ~MPARSE_UTF8;
617 } else if ( ! strcmp(arg, "us-ascii")) {
618 *options &= ~(MPARSE_UTF8 | MPARSE_LATIN1);
619 } else {
620 fprintf(stderr, "%s: -K%s: Bad argument\n",
621 progname, arg);
622 return(0);
623 }
624 return(1);
625 }
626
627 static int
628 moptions(int *options, char *arg)
629 {
630
631 if (arg == NULL)
632 /* nothing to do */;
633 else if (0 == strcmp(arg, "doc"))
634 *options |= MPARSE_MDOC;
635 else if (0 == strcmp(arg, "andoc"))
636 /* nothing to do */;
637 else if (0 == strcmp(arg, "an"))
638 *options |= MPARSE_MAN;
639 else {
640 fprintf(stderr, "%s: -m%s: Bad argument\n",
641 progname, arg);
642 return(0);
643 }
644
645 return(1);
646 }
647
648 static int
649 toptions(struct curparse *curp, char *arg)
650 {
651
652 if (0 == strcmp(arg, "ascii"))
653 curp->outtype = OUTT_ASCII;
654 else if (0 == strcmp(arg, "lint")) {
655 curp->outtype = OUTT_LINT;
656 curp->wlevel = MANDOCLEVEL_WARNING;
657 } else if (0 == strcmp(arg, "tree"))
658 curp->outtype = OUTT_TREE;
659 else if (0 == strcmp(arg, "man"))
660 curp->outtype = OUTT_MAN;
661 else if (0 == strcmp(arg, "html"))
662 curp->outtype = OUTT_HTML;
663 else if (0 == strcmp(arg, "utf8"))
664 curp->outtype = OUTT_UTF8;
665 else if (0 == strcmp(arg, "locale"))
666 curp->outtype = OUTT_LOCALE;
667 else if (0 == strcmp(arg, "xhtml"))
668 curp->outtype = OUTT_XHTML;
669 else if (0 == strcmp(arg, "ps"))
670 curp->outtype = OUTT_PS;
671 else if (0 == strcmp(arg, "pdf"))
672 curp->outtype = OUTT_PDF;
673 else {
674 fprintf(stderr, "%s: -T%s: Bad argument\n",
675 progname, arg);
676 return(0);
677 }
678
679 return(1);
680 }
681
682 static int
683 woptions(struct curparse *curp, char *arg)
684 {
685 char *v, *o;
686 const char *toks[6];
687
688 toks[0] = "stop";
689 toks[1] = "all";
690 toks[2] = "warning";
691 toks[3] = "error";
692 toks[4] = "fatal";
693 toks[5] = NULL;
694
695 while (*arg) {
696 o = arg;
697 switch (getsubopt(&arg, UNCONST(toks), &v)) {
698 case 0:
699 curp->wstop = 1;
700 break;
701 case 1:
702 /* FALLTHROUGH */
703 case 2:
704 curp->wlevel = MANDOCLEVEL_WARNING;
705 break;
706 case 3:
707 curp->wlevel = MANDOCLEVEL_ERROR;
708 break;
709 case 4:
710 curp->wlevel = MANDOCLEVEL_FATAL;
711 break;
712 default:
713 fprintf(stderr, "%s: -W%s: Bad argument\n",
714 progname, o);
715 return(0);
716 }
717 }
718
719 return(1);
720 }
721
722 static void
723 mmsg(enum mandocerr t, enum mandoclevel lvl,
724 const char *file, int line, int col, const char *msg)
725 {
726 const char *mparse_msg;
727
728 fprintf(stderr, "%s: %s:", progname, file);
729
730 if (line)
731 fprintf(stderr, "%d:%d:", line, col + 1);
732
733 fprintf(stderr, " %s", mparse_strlevel(lvl));
734
735 if (NULL != (mparse_msg = mparse_strerror(t)))
736 fprintf(stderr, ": %s", mparse_msg);
737
738 if (msg)
739 fprintf(stderr, ": %s", msg);
740
741 fputc('\n', stderr);
742 }
743
744 static void
745 spawn_pager(void)
746 {
747 #define MAX_PAGER_ARGS 16
748 char *argv[MAX_PAGER_ARGS];
749 const char *pager;
750 char *cp;
751 int fildes[2];
752 int argc;
753
754 if (pipe(fildes) == -1) {
755 fprintf(stderr, "%s: pipe: %s\n",
756 progname, strerror(errno));
757 return;
758 }
759
760 switch (fork()) {
761 case -1:
762 fprintf(stderr, "%s: fork: %s\n",
763 progname, strerror(errno));
764 exit((int)MANDOCLEVEL_SYSERR);
765 case 0:
766 close(fildes[0]);
767 if (dup2(fildes[1], STDOUT_FILENO) == -1) {
768 fprintf(stderr, "%s: dup output: %s\n",
769 progname, strerror(errno));
770 exit((int)MANDOCLEVEL_SYSERR);
771 }
772 return;
773 default:
774 break;
775 }
776
777 /* The original process becomes the pager. */
778
779 close(fildes[1]);
780 if (dup2(fildes[0], STDIN_FILENO) == -1) {
781 fprintf(stderr, "%s: dup input: %s\n",
782 progname, strerror(errno));
783 exit((int)MANDOCLEVEL_SYSERR);
784 }
785
786 pager = getenv("MANPAGER");
787 if (pager == NULL || *pager == '\0')
788 pager = getenv("PAGER");
789 if (pager == NULL || *pager == '\0')
790 pager = "/usr/bin/more -s";
791 cp = mandoc_strdup(pager);
792
793 /*
794 * Parse the pager command into words.
795 * Intentionally do not do anything fancy here.
796 */
797
798 argc = 0;
799 while (argc + 1 < MAX_PAGER_ARGS) {
800 argv[argc++] = cp;
801 cp = strchr(cp, ' ');
802 if (cp == NULL)
803 break;
804 *cp++ = '\0';
805 while (*cp == ' ')
806 cp++;
807 if (*cp == '\0')
808 break;
809 }
810 argv[argc] = NULL;
811
812 /* Hand over to the pager. */
813
814 execvp(argv[0], argv);
815 fprintf(stderr, "%s: exec: %s\n",
816 progname, strerror(errno));
817 exit((int)MANDOCLEVEL_SYSERR);
818 }