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