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