main.c revision 1.3
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
60enum	outmode {
61	OUTMODE_DEF = 0,
62	OUTMODE_FLN,
63	OUTMODE_LST,
64	OUTMODE_ALL,
65	OUTMODE_INT,
66	OUTMODE_ONE
67};
68
69enum	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
81struct	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
90static	int		  fs_lookup(const struct manpaths *,
91				size_t ipath, const char *,
92				const char *, const char *,
93				struct manpage **, size_t *);
94static	void		  fs_search(const struct mansearch *,
95				const struct manpaths *, int, char**,
96				struct manpage **, size_t *);
97static	int		  koptions(int *, char *);
98#if HAVE_SQLITE3
99int			  mandocdb(int, char**);
100#endif
101static	int		  moptions(int *, char *);
102static	void		  mmsg(enum mandocerr, enum mandoclevel,
103				const char *, int, int, const char *);
104static	void		  parse(struct curparse *, int, const char *);
105static	void		  passthrough(const char *, int, int);
106static	pid_t		  spawn_pager(struct tag_files *);
107static	int		  toptions(struct curparse *, char *);
108static	void		  usage(enum argmode) __attribute__((noreturn));
109static	int		  woptions(struct curparse *, char *);
110
111static	const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
112static	char		  help_arg[] = "help";
113static	char		 *help_argv[] = {help_arg, NULL};
114static	enum mandoclevel  rc;
115
116
117int
118main(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
526out:
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
597static void
598usage(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
628static int
629fs_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
673found:
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
690static void
691fs_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
725static void
726parse(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
831static void
832passthrough(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
881done:
882	free(line);
883	fclose(stream);
884	return;
885
886fail:
887	free(line);
888	warn("%s: SYSERR: %s", file, syscall);
889	if (rc < MANDOCLEVEL_SYSERR)
890		rc = MANDOCLEVEL_SYSERR;
891}
892
893static int
894koptions(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
912static int
913moptions(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
932static int
933toptions(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
965static int
966woptions(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
1007static void
1008mmsg(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
1029static pid_t
1030spawn_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	/*coverity[TAINTED_STRING]*/
1107	execvp(argv[0], argv);
1108	err((int)MANDOCLEVEL_SYSERR, "exec %s", argv[0]);
1109}
1110