xref: /illumos-gate/usr/src/cmd/mandoc/main.c (revision a40ea1a7d80eee1b409e9dcc2e48c730988147ea)
1*a40ea1a7SYuri Pankov /*	$Id: main.c,v 1.283 2017/02/17 14:31:52 schwarze Exp $ */
295c635efSGarrett D'Amore /*
3260e9a87SYuri Pankov  * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
4a5934736SYuri Pankov  * Copyright (c) 2010-2012, 2014-2017 Ingo Schwarze <schwarze@openbsd.org>
5260e9a87SYuri Pankov  * Copyright (c) 2010 Joerg Sonnenberger <joerg@netbsd.org>
695c635efSGarrett D'Amore  *
795c635efSGarrett D'Amore  * Permission to use, copy, modify, and distribute this software for any
895c635efSGarrett D'Amore  * purpose with or without fee is hereby granted, provided that the above
995c635efSGarrett D'Amore  * copyright notice and this permission notice appear in all copies.
1095c635efSGarrett D'Amore  *
11371584c2SYuri Pankov  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
1295c635efSGarrett D'Amore  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13371584c2SYuri Pankov  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
1495c635efSGarrett D'Amore  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1595c635efSGarrett D'Amore  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1695c635efSGarrett D'Amore  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1795c635efSGarrett D'Amore  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1895c635efSGarrett D'Amore  */
1995c635efSGarrett D'Amore #include "config.h"
20260e9a87SYuri Pankov 
21260e9a87SYuri Pankov #include <sys/types.h>
22260e9a87SYuri Pankov #include <sys/param.h>	/* MACHINE */
23260e9a87SYuri Pankov #include <sys/wait.h>
2495c635efSGarrett D'Amore 
2595c635efSGarrett D'Amore #include <assert.h>
26260e9a87SYuri Pankov #include <ctype.h>
27371584c2SYuri Pankov #if HAVE_ERR
28371584c2SYuri Pankov #include <err.h>
29371584c2SYuri Pankov #endif
30260e9a87SYuri Pankov #include <errno.h>
31260e9a87SYuri Pankov #include <fcntl.h>
32260e9a87SYuri Pankov #include <glob.h>
33371584c2SYuri Pankov #if HAVE_SANDBOX_INIT
34371584c2SYuri Pankov #include <sandbox.h>
35371584c2SYuri Pankov #endif
36371584c2SYuri Pankov #include <signal.h>
3795c635efSGarrett D'Amore #include <stdio.h>
3895c635efSGarrett D'Amore #include <stdint.h>
3995c635efSGarrett D'Amore #include <stdlib.h>
4095c635efSGarrett D'Amore #include <string.h>
41371584c2SYuri Pankov #include <time.h>
4295c635efSGarrett D'Amore #include <unistd.h>
4395c635efSGarrett D'Amore 
44260e9a87SYuri Pankov #include "mandoc_aux.h"
45371584c2SYuri Pankov #include "mandoc.h"
46371584c2SYuri Pankov #include "roff.h"
4795c635efSGarrett D'Amore #include "mdoc.h"
4895c635efSGarrett D'Amore #include "man.h"
49371584c2SYuri Pankov #include "tag.h"
50371584c2SYuri Pankov #include "main.h"
51371584c2SYuri Pankov #include "manconf.h"
52260e9a87SYuri Pankov #include "mansearch.h"
5395c635efSGarrett D'Amore 
54260e9a87SYuri Pankov enum	outmode {
55260e9a87SYuri Pankov 	OUTMODE_DEF = 0,
56260e9a87SYuri Pankov 	OUTMODE_FLN,
57260e9a87SYuri Pankov 	OUTMODE_LST,
58260e9a87SYuri Pankov 	OUTMODE_ALL,
59260e9a87SYuri Pankov 	OUTMODE_INT,
60260e9a87SYuri Pankov 	OUTMODE_ONE
61260e9a87SYuri Pankov };
62260e9a87SYuri Pankov 
6395c635efSGarrett D'Amore enum	outt {
6495c635efSGarrett D'Amore 	OUTT_ASCII = 0,	/* -Tascii */
6595c635efSGarrett D'Amore 	OUTT_LOCALE,	/* -Tlocale */
6695c635efSGarrett D'Amore 	OUTT_UTF8,	/* -Tutf8 */
6795c635efSGarrett D'Amore 	OUTT_TREE,	/* -Ttree */
6895c635efSGarrett D'Amore 	OUTT_MAN,	/* -Tman */
6995c635efSGarrett D'Amore 	OUTT_HTML,	/* -Thtml */
7095c635efSGarrett D'Amore 	OUTT_LINT,	/* -Tlint */
7195c635efSGarrett D'Amore 	OUTT_PS,	/* -Tps */
7295c635efSGarrett D'Amore 	OUTT_PDF	/* -Tpdf */
7395c635efSGarrett D'Amore };
7495c635efSGarrett D'Amore 
7595c635efSGarrett D'Amore struct	curparse {
7695c635efSGarrett D'Amore 	struct mparse	 *mp;
7795c635efSGarrett D'Amore 	enum mandoclevel  wlevel;	/* ignore messages below this */
7895c635efSGarrett D'Amore 	int		  wstop;	/* stop after a file with a warning */
79260e9a87SYuri Pankov 	enum outt	  outtype;	/* which output to use */
8095c635efSGarrett D'Amore 	void		 *outdata;	/* data for output */
81371584c2SYuri Pankov 	struct manoutput *outopts;	/* output options */
8295c635efSGarrett D'Amore };
8395c635efSGarrett D'Amore 
84a5934736SYuri Pankov 
85a5934736SYuri Pankov int			  mandocdb(int, char *[]);
86a5934736SYuri Pankov 
87260e9a87SYuri Pankov static	int		  fs_lookup(const struct manpaths *,
88260e9a87SYuri Pankov 				size_t ipath, const char *,
89260e9a87SYuri Pankov 				const char *, const char *,
90260e9a87SYuri Pankov 				struct manpage **, size_t *);
91260e9a87SYuri Pankov static	void		  fs_search(const struct mansearch *,
92260e9a87SYuri Pankov 				const struct manpaths *, int, char**,
93260e9a87SYuri Pankov 				struct manpage **, size_t *);
94260e9a87SYuri Pankov static	int		  koptions(int *, char *);
95260e9a87SYuri Pankov static	int		  moptions(int *, char *);
9695c635efSGarrett D'Amore static	void		  mmsg(enum mandocerr, enum mandoclevel,
9795c635efSGarrett D'Amore 				const char *, int, int, const char *);
98a5934736SYuri Pankov static	void		  outdata_alloc(struct curparse *);
99371584c2SYuri Pankov static	void		  parse(struct curparse *, int, const char *);
100371584c2SYuri Pankov static	void		  passthrough(const char *, int, int);
101371584c2SYuri Pankov static	pid_t		  spawn_pager(struct tag_files *);
10295c635efSGarrett D'Amore static	int		  toptions(struct curparse *, char *);
103*a40ea1a7SYuri Pankov static	void		  usage(enum argmode) __attribute__((__noreturn__));
10495c635efSGarrett D'Amore static	int		  woptions(struct curparse *, char *);
10595c635efSGarrett D'Amore 
106260e9a87SYuri Pankov static	const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
107260e9a87SYuri Pankov static	char		  help_arg[] = "help";
108260e9a87SYuri Pankov static	char		 *help_argv[] = {help_arg, NULL};
109371584c2SYuri Pankov static	enum mandoclevel  rc;
11095c635efSGarrett D'Amore 
111260e9a87SYuri Pankov 
11295c635efSGarrett D'Amore int
11395c635efSGarrett D'Amore main(int argc, char *argv[])
11495c635efSGarrett D'Amore {
115371584c2SYuri Pankov 	struct manconf	 conf;
116260e9a87SYuri Pankov 	struct mansearch search;
117*a40ea1a7SYuri Pankov 	struct curparse	 curp;
118371584c2SYuri Pankov 	struct tag_files *tag_files;
119260e9a87SYuri Pankov 	struct manpage	*res, *resp;
120*a40ea1a7SYuri Pankov 	const char	*progname, *sec, *thisarg;
121*a40ea1a7SYuri Pankov 	char		*conf_file, *defpaths, *auxpaths;
122*a40ea1a7SYuri Pankov 	char		*defos, *oarg;
123*a40ea1a7SYuri Pankov 	unsigned char	*uc;
124371584c2SYuri Pankov 	size_t		 i, sz;
125371584c2SYuri Pankov 	int		 prio, best_prio;
126260e9a87SYuri Pankov 	enum outmode	 outmode;
127260e9a87SYuri Pankov 	int		 fd;
128260e9a87SYuri Pankov 	int		 show_usage;
129260e9a87SYuri Pankov 	int		 options;
130371584c2SYuri Pankov 	int		 use_pager;
131371584c2SYuri Pankov 	int		 status, signum;
132260e9a87SYuri Pankov 	int		 c;
133371584c2SYuri Pankov 	pid_t		 pager_pid, tc_pgid, man_pgid, pid;
13495c635efSGarrett D'Amore 
135371584c2SYuri Pankov #if HAVE_PROGNAME
136371584c2SYuri Pankov 	progname = getprogname();
137371584c2SYuri Pankov #else
138260e9a87SYuri Pankov 	if (argc < 1)
139371584c2SYuri Pankov 		progname = mandoc_strdup("mandoc");
140260e9a87SYuri Pankov 	else if ((progname = strrchr(argv[0], '/')) == NULL)
14195c635efSGarrett D'Amore 		progname = argv[0];
14295c635efSGarrett D'Amore 	else
14395c635efSGarrett D'Amore 		++progname;
144371584c2SYuri Pankov 	setprogname(progname);
145371584c2SYuri Pankov #endif
14695c635efSGarrett D'Amore 
147371584c2SYuri Pankov 	if (strncmp(progname, "mandocdb", 8) == 0 ||
148371584c2SYuri Pankov 	    strcmp(progname, BINM_MAKEWHATIS) == 0)
149371584c2SYuri Pankov 		return mandocdb(argc, argv);
150371584c2SYuri Pankov 
151371584c2SYuri Pankov #if HAVE_PLEDGE
152371584c2SYuri Pankov 	if (pledge("stdio rpath tmppath tty proc exec flock", NULL) == -1)
153371584c2SYuri Pankov 		err((int)MANDOCLEVEL_SYSERR, "pledge");
154371584c2SYuri Pankov #endif
155371584c2SYuri Pankov 
156371584c2SYuri Pankov #if HAVE_SANDBOX_INIT
157371584c2SYuri Pankov 	if (sandbox_init(kSBXProfileNoInternet, SANDBOX_NAMED, NULL) == -1)
158371584c2SYuri Pankov 		errx((int)MANDOCLEVEL_SYSERR, "sandbox_init");
159260e9a87SYuri Pankov #endif
160260e9a87SYuri Pankov 
161260e9a87SYuri Pankov 	/* Search options. */
16295c635efSGarrett D'Amore 
163371584c2SYuri Pankov 	memset(&conf, 0, sizeof(conf));
164260e9a87SYuri Pankov 	conf_file = defpaths = NULL;
165260e9a87SYuri Pankov 	auxpaths = NULL;
166260e9a87SYuri Pankov 
167260e9a87SYuri Pankov 	memset(&search, 0, sizeof(struct mansearch));
168260e9a87SYuri Pankov 	search.outkey = "Nd";
169*a40ea1a7SYuri Pankov 	oarg = NULL;
170260e9a87SYuri Pankov 
171260e9a87SYuri Pankov 	if (strcmp(progname, BINM_MAN) == 0)
172260e9a87SYuri Pankov 		search.argmode = ARG_NAME;
173260e9a87SYuri Pankov 	else if (strcmp(progname, BINM_APROPOS) == 0)
174260e9a87SYuri Pankov 		search.argmode = ARG_EXPR;
175260e9a87SYuri Pankov 	else if (strcmp(progname, BINM_WHATIS) == 0)
176260e9a87SYuri Pankov 		search.argmode = ARG_WORD;
177260e9a87SYuri Pankov 	else if (strncmp(progname, "help", 4) == 0)
178260e9a87SYuri Pankov 		search.argmode = ARG_NAME;
179260e9a87SYuri Pankov 	else
180260e9a87SYuri Pankov 		search.argmode = ARG_FILE;
181260e9a87SYuri Pankov 
182260e9a87SYuri Pankov 	/* Parser and formatter options. */
183260e9a87SYuri Pankov 
184260e9a87SYuri Pankov 	memset(&curp, 0, sizeof(struct curparse));
185260e9a87SYuri Pankov 	curp.outtype = OUTT_LOCALE;
186260e9a87SYuri Pankov 	curp.wlevel  = MANDOCLEVEL_BADARG;
187371584c2SYuri Pankov 	curp.outopts = &conf.output;
188260e9a87SYuri Pankov 	options = MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1;
189698f87a4SGarrett D'Amore 	defos = NULL;
19095c635efSGarrett D'Amore 
191371584c2SYuri Pankov 	use_pager = 1;
192371584c2SYuri Pankov 	tag_files = NULL;
193260e9a87SYuri Pankov 	show_usage = 0;
194260e9a87SYuri Pankov 	outmode = OUTMODE_DEF;
195260e9a87SYuri Pankov 
196260e9a87SYuri Pankov 	while (-1 != (c = getopt(argc, argv,
197260e9a87SYuri Pankov 			"aC:cfhI:iK:klM:m:O:S:s:T:VW:w"))) {
19895c635efSGarrett D'Amore 		switch (c) {
199260e9a87SYuri Pankov 		case 'a':
200260e9a87SYuri Pankov 			outmode = OUTMODE_ALL;
201260e9a87SYuri Pankov 			break;
202260e9a87SYuri Pankov 		case 'C':
203260e9a87SYuri Pankov 			conf_file = optarg;
204260e9a87SYuri Pankov 			break;
205260e9a87SYuri Pankov 		case 'c':
206371584c2SYuri Pankov 			use_pager = 0;
207260e9a87SYuri Pankov 			break;
208260e9a87SYuri Pankov 		case 'f':
209260e9a87SYuri Pankov 			search.argmode = ARG_WORD;
210260e9a87SYuri Pankov 			break;
211260e9a87SYuri Pankov 		case 'h':
212371584c2SYuri Pankov 			conf.output.synopsisonly = 1;
213371584c2SYuri Pankov 			use_pager = 0;
214260e9a87SYuri Pankov 			outmode = OUTMODE_ALL;
215260e9a87SYuri Pankov 			break;
216260e9a87SYuri Pankov 		case 'I':
217698f87a4SGarrett D'Amore 			if (strncmp(optarg, "os=", 3)) {
218371584c2SYuri Pankov 				warnx("-I %s: Bad argument", optarg);
219371584c2SYuri Pankov 				return (int)MANDOCLEVEL_BADARG;
220698f87a4SGarrett D'Amore 			}
221698f87a4SGarrett D'Amore 			if (defos) {
222371584c2SYuri Pankov 				warnx("-I %s: Duplicate argument", optarg);
223371584c2SYuri Pankov 				return (int)MANDOCLEVEL_BADARG;
224698f87a4SGarrett D'Amore 			}
225698f87a4SGarrett D'Amore 			defos = mandoc_strdup(optarg + 3);
226698f87a4SGarrett D'Amore 			break;
227260e9a87SYuri Pankov 		case 'i':
228260e9a87SYuri Pankov 			outmode = OUTMODE_INT;
229260e9a87SYuri Pankov 			break;
230260e9a87SYuri Pankov 		case 'K':
231260e9a87SYuri Pankov 			if ( ! koptions(&options, optarg))
232371584c2SYuri Pankov 				return (int)MANDOCLEVEL_BADARG;
23395c635efSGarrett D'Amore 			break;
234260e9a87SYuri Pankov 		case 'k':
235260e9a87SYuri Pankov 			search.argmode = ARG_EXPR;
236260e9a87SYuri Pankov 			break;
237260e9a87SYuri Pankov 		case 'l':
238260e9a87SYuri Pankov 			search.argmode = ARG_FILE;
239260e9a87SYuri Pankov 			outmode = OUTMODE_ALL;
240260e9a87SYuri Pankov 			break;
241260e9a87SYuri Pankov 		case 'M':
242260e9a87SYuri Pankov 			defpaths = optarg;
243260e9a87SYuri Pankov 			break;
244260e9a87SYuri Pankov 		case 'm':
245260e9a87SYuri Pankov 			auxpaths = optarg;
246260e9a87SYuri Pankov 			break;
247260e9a87SYuri Pankov 		case 'O':
248*a40ea1a7SYuri Pankov 			oarg = optarg;
24995c635efSGarrett D'Amore 			break;
250260e9a87SYuri Pankov 		case 'S':
251260e9a87SYuri Pankov 			search.arch = optarg;
252260e9a87SYuri Pankov 			break;
253260e9a87SYuri Pankov 		case 's':
254260e9a87SYuri Pankov 			search.sec = optarg;
255260e9a87SYuri Pankov 			break;
256260e9a87SYuri Pankov 		case 'T':
25795c635efSGarrett D'Amore 			if ( ! toptions(&curp, optarg))
258371584c2SYuri Pankov 				return (int)MANDOCLEVEL_BADARG;
25995c635efSGarrett D'Amore 			break;
260260e9a87SYuri Pankov 		case 'W':
26195c635efSGarrett D'Amore 			if ( ! woptions(&curp, optarg))
262371584c2SYuri Pankov 				return (int)MANDOCLEVEL_BADARG;
26395c635efSGarrett D'Amore 			break;
264260e9a87SYuri Pankov 		case 'w':
265260e9a87SYuri Pankov 			outmode = OUTMODE_FLN;
266260e9a87SYuri Pankov 			break;
26795c635efSGarrett D'Amore 		default:
268260e9a87SYuri Pankov 			show_usage = 1;
269260e9a87SYuri Pankov 			break;
27095c635efSGarrett D'Amore 		}
271260e9a87SYuri Pankov 	}
272260e9a87SYuri Pankov 
273260e9a87SYuri Pankov 	if (show_usage)
274260e9a87SYuri Pankov 		usage(search.argmode);
27595c635efSGarrett D'Amore 
276260e9a87SYuri Pankov 	/* Postprocess options. */
277260e9a87SYuri Pankov 
278260e9a87SYuri Pankov 	if (outmode == OUTMODE_DEF) {
279260e9a87SYuri Pankov 		switch (search.argmode) {
280260e9a87SYuri Pankov 		case ARG_FILE:
281260e9a87SYuri Pankov 			outmode = OUTMODE_ALL;
282371584c2SYuri Pankov 			use_pager = 0;
283260e9a87SYuri Pankov 			break;
284260e9a87SYuri Pankov 		case ARG_NAME:
285260e9a87SYuri Pankov 			outmode = OUTMODE_ONE;
286260e9a87SYuri Pankov 			break;
287260e9a87SYuri Pankov 		default:
288260e9a87SYuri Pankov 			outmode = OUTMODE_LST;
289260e9a87SYuri Pankov 			break;
290260e9a87SYuri Pankov 		}
291260e9a87SYuri Pankov 	}
292260e9a87SYuri Pankov 
293*a40ea1a7SYuri Pankov 	if (oarg != NULL) {
294*a40ea1a7SYuri Pankov 		if (outmode == OUTMODE_LST)
295*a40ea1a7SYuri Pankov 			search.outkey = oarg;
296*a40ea1a7SYuri Pankov 		else {
297*a40ea1a7SYuri Pankov 			while (oarg != NULL) {
298*a40ea1a7SYuri Pankov 				thisarg = oarg;
299*a40ea1a7SYuri Pankov 				if (manconf_output(&conf.output,
300*a40ea1a7SYuri Pankov 				    strsep(&oarg, ","), 0) == 0)
301*a40ea1a7SYuri Pankov 					continue;
302*a40ea1a7SYuri Pankov 				warnx("-O %s: Bad argument", thisarg);
303*a40ea1a7SYuri Pankov 				return (int)MANDOCLEVEL_BADARG;
304*a40ea1a7SYuri Pankov 			}
305*a40ea1a7SYuri Pankov 		}
306*a40ea1a7SYuri Pankov 	}
307*a40ea1a7SYuri Pankov 
308371584c2SYuri Pankov 	if (outmode == OUTMODE_FLN ||
309371584c2SYuri Pankov 	    outmode == OUTMODE_LST ||
310371584c2SYuri Pankov 	    !isatty(STDOUT_FILENO))
311371584c2SYuri Pankov 		use_pager = 0;
312371584c2SYuri Pankov 
313371584c2SYuri Pankov #if HAVE_PLEDGE
314371584c2SYuri Pankov 	if (!use_pager)
315371584c2SYuri Pankov 		if (pledge("stdio rpath flock", NULL) == -1)
316371584c2SYuri Pankov 			err((int)MANDOCLEVEL_SYSERR, "pledge");
317371584c2SYuri Pankov #endif
318371584c2SYuri Pankov 
319260e9a87SYuri Pankov 	/* Parse arguments. */
320260e9a87SYuri Pankov 
321260e9a87SYuri Pankov 	if (argc > 0) {
322260e9a87SYuri Pankov 		argc -= optind;
323260e9a87SYuri Pankov 		argv += optind;
324260e9a87SYuri Pankov 	}
325260e9a87SYuri Pankov 	resp = NULL;
326260e9a87SYuri Pankov 
327260e9a87SYuri Pankov 	/*
328260e9a87SYuri Pankov 	 * Quirks for help(1)
329260e9a87SYuri Pankov 	 * and for a man(1) section argument without -s.
330260e9a87SYuri Pankov 	 */
331260e9a87SYuri Pankov 
332260e9a87SYuri Pankov 	if (search.argmode == ARG_NAME) {
333260e9a87SYuri Pankov 		if (*progname == 'h') {
334260e9a87SYuri Pankov 			if (argc == 0) {
335260e9a87SYuri Pankov 				argv = help_argv;
336260e9a87SYuri Pankov 				argc = 1;
337260e9a87SYuri Pankov 			}
338260e9a87SYuri Pankov 		} else if (argc > 1 &&
339260e9a87SYuri Pankov 		    ((uc = (unsigned char *)argv[0]) != NULL) &&
340260e9a87SYuri Pankov 		    ((isdigit(uc[0]) && (uc[1] == '\0' ||
341260e9a87SYuri Pankov 		      (isalpha(uc[1]) && uc[2] == '\0'))) ||
342260e9a87SYuri Pankov 		     (uc[0] == 'n' && uc[1] == '\0'))) {
343260e9a87SYuri Pankov 			search.sec = (char *)uc;
344260e9a87SYuri Pankov 			argv++;
345260e9a87SYuri Pankov 			argc--;
346260e9a87SYuri Pankov 		}
347260e9a87SYuri Pankov 		if (search.arch == NULL)
348260e9a87SYuri Pankov 			search.arch = getenv("MACHINE");
349260e9a87SYuri Pankov #ifdef MACHINE
350260e9a87SYuri Pankov 		if (search.arch == NULL)
351260e9a87SYuri Pankov 			search.arch = MACHINE;
352260e9a87SYuri Pankov #endif
353260e9a87SYuri Pankov 	}
354260e9a87SYuri Pankov 
355260e9a87SYuri Pankov 	rc = MANDOCLEVEL_OK;
356260e9a87SYuri Pankov 
357260e9a87SYuri Pankov 	/* man(1), whatis(1), apropos(1) */
358260e9a87SYuri Pankov 
359260e9a87SYuri Pankov 	if (search.argmode != ARG_FILE) {
360260e9a87SYuri Pankov 		if (search.argmode == ARG_NAME &&
361260e9a87SYuri Pankov 		    outmode == OUTMODE_ONE)
362260e9a87SYuri Pankov 			search.firstmatch = 1;
363260e9a87SYuri Pankov 
364260e9a87SYuri Pankov 		/* Access the mandoc database. */
365260e9a87SYuri Pankov 
366371584c2SYuri Pankov 		manconf_parse(&conf, conf_file, defpaths, auxpaths);
367371584c2SYuri Pankov 		if ( ! mansearch(&search, &conf.manpath,
368371584c2SYuri Pankov 		    argc, argv, &res, &sz))
369260e9a87SYuri Pankov 			usage(search.argmode);
370260e9a87SYuri Pankov 
371371584c2SYuri Pankov 		if (sz == 0) {
372371584c2SYuri Pankov 			if (search.argmode == ARG_NAME)
373371584c2SYuri Pankov 				fs_search(&search, &conf.manpath,
374371584c2SYuri Pankov 				    argc, argv, &res, &sz);
375371584c2SYuri Pankov 			else
376371584c2SYuri Pankov 				warnx("nothing appropriate");
377371584c2SYuri Pankov 		}
378260e9a87SYuri Pankov 
379260e9a87SYuri Pankov 		if (sz == 0) {
380260e9a87SYuri Pankov 			rc = MANDOCLEVEL_BADARG;
381260e9a87SYuri Pankov 			goto out;
382260e9a87SYuri Pankov 		}
383260e9a87SYuri Pankov 
384260e9a87SYuri Pankov 		/*
385260e9a87SYuri Pankov 		 * For standard man(1) and -a output mode,
386260e9a87SYuri Pankov 		 * prepare for copying filename pointers
387260e9a87SYuri Pankov 		 * into the program parameter array.
388260e9a87SYuri Pankov 		 */
389260e9a87SYuri Pankov 
390260e9a87SYuri Pankov 		if (outmode == OUTMODE_ONE) {
391260e9a87SYuri Pankov 			argc = 1;
392371584c2SYuri Pankov 			best_prio = 20;
393260e9a87SYuri Pankov 		} else if (outmode == OUTMODE_ALL)
394260e9a87SYuri Pankov 			argc = (int)sz;
395260e9a87SYuri Pankov 
396260e9a87SYuri Pankov 		/* Iterate all matching manuals. */
397260e9a87SYuri Pankov 
398260e9a87SYuri Pankov 		resp = res;
399260e9a87SYuri Pankov 		for (i = 0; i < sz; i++) {
400260e9a87SYuri Pankov 			if (outmode == OUTMODE_FLN)
401260e9a87SYuri Pankov 				puts(res[i].file);
402260e9a87SYuri Pankov 			else if (outmode == OUTMODE_LST)
403260e9a87SYuri Pankov 				printf("%s - %s\n", res[i].names,
404260e9a87SYuri Pankov 				    res[i].output == NULL ? "" :
405260e9a87SYuri Pankov 				    res[i].output);
406260e9a87SYuri Pankov 			else if (outmode == OUTMODE_ONE) {
407260e9a87SYuri Pankov 				/* Search for the best section. */
408371584c2SYuri Pankov 				sec = res[i].file;
409371584c2SYuri Pankov 				sec += strcspn(sec, "123456789");
410371584c2SYuri Pankov 				if (sec[0] == '\0')
411260e9a87SYuri Pankov 					continue;
412371584c2SYuri Pankov 				prio = sec_prios[sec[0] - '1'];
413371584c2SYuri Pankov 				if (sec[1] != '/')
414371584c2SYuri Pankov 					prio += 10;
415260e9a87SYuri Pankov 				if (prio >= best_prio)
416260e9a87SYuri Pankov 					continue;
417260e9a87SYuri Pankov 				best_prio = prio;
418260e9a87SYuri Pankov 				resp = res + i;
419260e9a87SYuri Pankov 			}
420260e9a87SYuri Pankov 		}
421260e9a87SYuri Pankov 
422260e9a87SYuri Pankov 		/*
423260e9a87SYuri Pankov 		 * For man(1), -a and -i output mode, fall through
424260e9a87SYuri Pankov 		 * to the main mandoc(1) code iterating files
425260e9a87SYuri Pankov 		 * and running the parsers on each of them.
426260e9a87SYuri Pankov 		 */
427260e9a87SYuri Pankov 
428260e9a87SYuri Pankov 		if (outmode == OUTMODE_FLN || outmode == OUTMODE_LST)
429260e9a87SYuri Pankov 			goto out;
430260e9a87SYuri Pankov 	}
431260e9a87SYuri Pankov 
432260e9a87SYuri Pankov 	/* mandoc(1) */
433260e9a87SYuri Pankov 
434371584c2SYuri Pankov #if HAVE_PLEDGE
435371584c2SYuri Pankov 	if (use_pager) {
436371584c2SYuri Pankov 		if (pledge("stdio rpath tmppath tty proc exec", NULL) == -1)
437371584c2SYuri Pankov 			err((int)MANDOCLEVEL_SYSERR, "pledge");
438371584c2SYuri Pankov 	} else {
439371584c2SYuri Pankov 		if (pledge("stdio rpath", NULL) == -1)
440371584c2SYuri Pankov 			err((int)MANDOCLEVEL_SYSERR, "pledge");
441371584c2SYuri Pankov 	}
442371584c2SYuri Pankov #endif
443371584c2SYuri Pankov 
444260e9a87SYuri Pankov 	if (search.argmode == ARG_FILE && ! moptions(&options, auxpaths))
445371584c2SYuri Pankov 		return (int)MANDOCLEVEL_BADARG;
446260e9a87SYuri Pankov 
447371584c2SYuri Pankov 	mchars_alloc();
448371584c2SYuri Pankov 	curp.mp = mparse_alloc(options, curp.wlevel, mmsg, defos);
44995c635efSGarrett D'Amore 
45095c635efSGarrett D'Amore 	/*
45195c635efSGarrett D'Amore 	 * Conditionally start up the lookaside buffer before parsing.
45295c635efSGarrett D'Amore 	 */
45395c635efSGarrett D'Amore 	if (OUTT_MAN == curp.outtype)
45495c635efSGarrett D'Amore 		mparse_keep(curp.mp);
45595c635efSGarrett D'Amore 
456260e9a87SYuri Pankov 	if (argc < 1) {
457371584c2SYuri Pankov 		if (use_pager)
458371584c2SYuri Pankov 			tag_files = tag_init();
459371584c2SYuri Pankov 		parse(&curp, STDIN_FILENO, "<stdin>");
460260e9a87SYuri Pankov 	}
46195c635efSGarrett D'Amore 
462260e9a87SYuri Pankov 	while (argc > 0) {
463371584c2SYuri Pankov 		fd = mparse_open(curp.mp, resp != NULL ? resp->file : *argv);
464260e9a87SYuri Pankov 		if (fd != -1) {
465371584c2SYuri Pankov 			if (use_pager) {
466371584c2SYuri Pankov 				tag_files = tag_init();
467371584c2SYuri Pankov 				use_pager = 0;
468371584c2SYuri Pankov 			}
469260e9a87SYuri Pankov 
470260e9a87SYuri Pankov 			if (resp == NULL)
471371584c2SYuri Pankov 				parse(&curp, fd, *argv);
472*a40ea1a7SYuri Pankov 			else if (resp->form == FORM_SRC) {
473260e9a87SYuri Pankov 				/* For .so only; ignore failure. */
474371584c2SYuri Pankov 				chdir(conf.manpath.paths[resp->ipath]);
475371584c2SYuri Pankov 				parse(&curp, fd, resp->file);
476371584c2SYuri Pankov 			} else
477371584c2SYuri Pankov 				passthrough(resp->file, fd,
478371584c2SYuri Pankov 				    conf.output.synopsisonly);
479260e9a87SYuri Pankov 
480a5934736SYuri Pankov 			if (argc > 1 && curp.outtype <= OUTT_UTF8) {
481a5934736SYuri Pankov 				if (curp.outdata == NULL)
482a5934736SYuri Pankov 					outdata_alloc(&curp);
483371584c2SYuri Pankov 				terminal_sepline(curp.outdata);
484a5934736SYuri Pankov 			}
485371584c2SYuri Pankov 		} else if (rc < MANDOCLEVEL_ERROR)
486371584c2SYuri Pankov 			rc = MANDOCLEVEL_ERROR;
48795c635efSGarrett D'Amore 
48895c635efSGarrett D'Amore 		if (MANDOCLEVEL_OK != rc && curp.wstop)
48995c635efSGarrett D'Amore 			break;
490260e9a87SYuri Pankov 
491260e9a87SYuri Pankov 		if (resp != NULL)
492260e9a87SYuri Pankov 			resp++;
493260e9a87SYuri Pankov 		else
494260e9a87SYuri Pankov 			argv++;
495260e9a87SYuri Pankov 		if (--argc)
496260e9a87SYuri Pankov 			mparse_reset(curp.mp);
49795c635efSGarrett D'Amore 	}
49895c635efSGarrett D'Amore 
499371584c2SYuri Pankov 	if (curp.outdata != NULL) {
500371584c2SYuri Pankov 		switch (curp.outtype) {
501371584c2SYuri Pankov 		case OUTT_HTML:
502371584c2SYuri Pankov 			html_free(curp.outdata);
503371584c2SYuri Pankov 			break;
504371584c2SYuri Pankov 		case OUTT_UTF8:
505371584c2SYuri Pankov 		case OUTT_LOCALE:
506371584c2SYuri Pankov 		case OUTT_ASCII:
507371584c2SYuri Pankov 			ascii_free(curp.outdata);
508371584c2SYuri Pankov 			break;
509371584c2SYuri Pankov 		case OUTT_PDF:
510371584c2SYuri Pankov 		case OUTT_PS:
511371584c2SYuri Pankov 			pspdf_free(curp.outdata);
512371584c2SYuri Pankov 			break;
513371584c2SYuri Pankov 		default:
514371584c2SYuri Pankov 			break;
515371584c2SYuri Pankov 		}
516371584c2SYuri Pankov 	}
517260e9a87SYuri Pankov 	mparse_free(curp.mp);
518371584c2SYuri Pankov 	mchars_free();
519260e9a87SYuri Pankov 
520260e9a87SYuri Pankov out:
521260e9a87SYuri Pankov 	if (search.argmode != ARG_FILE) {
522371584c2SYuri Pankov 		manconf_free(&conf);
523260e9a87SYuri Pankov 		mansearch_free(res, sz);
524260e9a87SYuri Pankov 	}
525260e9a87SYuri Pankov 
526698f87a4SGarrett D'Amore 	free(defos);
52795c635efSGarrett D'Amore 
528260e9a87SYuri Pankov 	/*
529371584c2SYuri Pankov 	 * When using a pager, finish writing both temporary files,
530371584c2SYuri Pankov 	 * fork it, wait for the user to close it, and clean up.
531260e9a87SYuri Pankov 	 */
532260e9a87SYuri Pankov 
533371584c2SYuri Pankov 	if (tag_files != NULL) {
534260e9a87SYuri Pankov 		fclose(stdout);
535371584c2SYuri Pankov 		tag_write();
536371584c2SYuri Pankov 		man_pgid = getpgid(0);
537371584c2SYuri Pankov 		tag_files->tcpgid = man_pgid == getpid() ?
538371584c2SYuri Pankov 		    getpgid(getppid()) : man_pgid;
539371584c2SYuri Pankov 		pager_pid = 0;
540371584c2SYuri Pankov 		signum = SIGSTOP;
541371584c2SYuri Pankov 		for (;;) {
542371584c2SYuri Pankov 
543371584c2SYuri Pankov 			/* Stop here until moved to the foreground. */
544371584c2SYuri Pankov 
545*a40ea1a7SYuri Pankov 			tc_pgid = tcgetpgrp(tag_files->ofd);
546371584c2SYuri Pankov 			if (tc_pgid != man_pgid) {
547371584c2SYuri Pankov 				if (tc_pgid == pager_pid) {
548*a40ea1a7SYuri Pankov 					(void)tcsetpgrp(tag_files->ofd,
549371584c2SYuri Pankov 					    man_pgid);
550371584c2SYuri Pankov 					if (signum == SIGTTIN)
551371584c2SYuri Pankov 						continue;
552371584c2SYuri Pankov 				} else
553371584c2SYuri Pankov 					tag_files->tcpgid = tc_pgid;
554371584c2SYuri Pankov 				kill(0, signum);
555371584c2SYuri Pankov 				continue;
556371584c2SYuri Pankov 			}
557371584c2SYuri Pankov 
558371584c2SYuri Pankov 			/* Once in the foreground, activate the pager. */
559371584c2SYuri Pankov 
560371584c2SYuri Pankov 			if (pager_pid) {
561*a40ea1a7SYuri Pankov 				(void)tcsetpgrp(tag_files->ofd, pager_pid);
562371584c2SYuri Pankov 				kill(pager_pid, SIGCONT);
563371584c2SYuri Pankov 			} else
564371584c2SYuri Pankov 				pager_pid = spawn_pager(tag_files);
565371584c2SYuri Pankov 
566371584c2SYuri Pankov 			/* Wait for the pager to stop or exit. */
567371584c2SYuri Pankov 
568371584c2SYuri Pankov 			while ((pid = waitpid(pager_pid, &status,
569371584c2SYuri Pankov 			    WUNTRACED)) == -1 && errno == EINTR)
570371584c2SYuri Pankov 				continue;
571371584c2SYuri Pankov 
572371584c2SYuri Pankov 			if (pid == -1) {
573371584c2SYuri Pankov 				warn("wait");
574371584c2SYuri Pankov 				rc = MANDOCLEVEL_SYSERR;
575371584c2SYuri Pankov 				break;
576371584c2SYuri Pankov 			}
577371584c2SYuri Pankov 			if (!WIFSTOPPED(status))
578371584c2SYuri Pankov 				break;
579371584c2SYuri Pankov 
580371584c2SYuri Pankov 			signum = WSTOPSIG(status);
581371584c2SYuri Pankov 		}
582371584c2SYuri Pankov 		tag_unlink();
583260e9a87SYuri Pankov 	}
584260e9a87SYuri Pankov 
585371584c2SYuri Pankov 	return (int)rc;
58695c635efSGarrett D'Amore }
58795c635efSGarrett D'Amore 
58895c635efSGarrett D'Amore static void
589260e9a87SYuri Pankov usage(enum argmode argmode)
59095c635efSGarrett D'Amore {
59195c635efSGarrett D'Amore 
592260e9a87SYuri Pankov 	switch (argmode) {
593260e9a87SYuri Pankov 	case ARG_FILE:
594371584c2SYuri Pankov 		fputs("usage: mandoc [-acfhkl] [-I os=name] "
595371584c2SYuri Pankov 		    "[-K encoding] [-mformat] [-O option]\n"
596371584c2SYuri Pankov 		    "\t      [-T output] [-W level] [file ...]\n", stderr);
597260e9a87SYuri Pankov 		break;
598260e9a87SYuri Pankov 	case ARG_NAME:
599260e9a87SYuri Pankov 		fputs("usage: man [-acfhklw] [-C file] [-I os=name] "
600260e9a87SYuri Pankov 		    "[-K encoding] [-M path] [-m path]\n"
601260e9a87SYuri Pankov 		    "\t   [-O option=value] [-S subsection] [-s section] "
602260e9a87SYuri Pankov 		    "[-T output] [-W level]\n"
603260e9a87SYuri Pankov 		    "\t   [section] name ...\n", stderr);
604260e9a87SYuri Pankov 		break;
605260e9a87SYuri Pankov 	case ARG_WORD:
606260e9a87SYuri Pankov 		fputs("usage: whatis [-acfhklw] [-C file] "
607260e9a87SYuri Pankov 		    "[-M path] [-m path] [-O outkey] [-S arch]\n"
608260e9a87SYuri Pankov 		    "\t      [-s section] name ...\n", stderr);
609260e9a87SYuri Pankov 		break;
610260e9a87SYuri Pankov 	case ARG_EXPR:
611260e9a87SYuri Pankov 		fputs("usage: apropos [-acfhklw] [-C file] "
612260e9a87SYuri Pankov 		    "[-M path] [-m path] [-O outkey] [-S arch]\n"
613260e9a87SYuri Pankov 		    "\t       [-s section] expression ...\n", stderr);
614260e9a87SYuri Pankov 		break;
615260e9a87SYuri Pankov 	}
616260e9a87SYuri Pankov 	exit((int)MANDOCLEVEL_BADARG);
61795c635efSGarrett D'Amore }
61895c635efSGarrett D'Amore 
619260e9a87SYuri Pankov static int
620260e9a87SYuri Pankov fs_lookup(const struct manpaths *paths, size_t ipath,
621260e9a87SYuri Pankov 	const char *sec, const char *arch, const char *name,
622260e9a87SYuri Pankov 	struct manpage **res, size_t *ressz)
62395c635efSGarrett D'Amore {
624260e9a87SYuri Pankov 	glob_t		 globinfo;
625260e9a87SYuri Pankov 	struct manpage	*page;
626260e9a87SYuri Pankov 	char		*file;
627*a40ea1a7SYuri Pankov 	int		 globres;
628*a40ea1a7SYuri Pankov 	enum form	 form;
629260e9a87SYuri Pankov 
630260e9a87SYuri Pankov 	form = FORM_SRC;
631260e9a87SYuri Pankov 	mandoc_asprintf(&file, "%s/man%s/%s.%s",
632260e9a87SYuri Pankov 	    paths->paths[ipath], sec, name, sec);
633260e9a87SYuri Pankov 	if (access(file, R_OK) != -1)
634260e9a87SYuri Pankov 		goto found;
635260e9a87SYuri Pankov 	free(file);
636260e9a87SYuri Pankov 
637260e9a87SYuri Pankov 	mandoc_asprintf(&file, "%s/cat%s/%s.0",
638260e9a87SYuri Pankov 	    paths->paths[ipath], sec, name);
639260e9a87SYuri Pankov 	if (access(file, R_OK) != -1) {
640260e9a87SYuri Pankov 		form = FORM_CAT;
641260e9a87SYuri Pankov 		goto found;
642260e9a87SYuri Pankov 	}
643260e9a87SYuri Pankov 	free(file);
644260e9a87SYuri Pankov 
645260e9a87SYuri Pankov 	if (arch != NULL) {
646260e9a87SYuri Pankov 		mandoc_asprintf(&file, "%s/man%s/%s/%s.%s",
647260e9a87SYuri Pankov 		    paths->paths[ipath], sec, arch, name, sec);
648260e9a87SYuri Pankov 		if (access(file, R_OK) != -1)
649260e9a87SYuri Pankov 			goto found;
650260e9a87SYuri Pankov 		free(file);
651260e9a87SYuri Pankov 	}
652260e9a87SYuri Pankov 
653371584c2SYuri Pankov 	mandoc_asprintf(&file, "%s/man%s/%s.[01-9]*",
654260e9a87SYuri Pankov 	    paths->paths[ipath], sec, name);
655260e9a87SYuri Pankov 	globres = glob(file, 0, NULL, &globinfo);
656260e9a87SYuri Pankov 	if (globres != 0 && globres != GLOB_NOMATCH)
657371584c2SYuri Pankov 		warn("%s: glob", file);
658260e9a87SYuri Pankov 	free(file);
659260e9a87SYuri Pankov 	if (globres == 0)
660260e9a87SYuri Pankov 		file = mandoc_strdup(*globinfo.gl_pathv);
661260e9a87SYuri Pankov 	globfree(&globinfo);
662260e9a87SYuri Pankov 	if (globres != 0)
663371584c2SYuri Pankov 		return 0;
66495c635efSGarrett D'Amore 
665260e9a87SYuri Pankov found:
666371584c2SYuri Pankov 	warnx("outdated mandoc.db lacks %s(%s) entry, run %s %s",
667371584c2SYuri Pankov 	    name, sec, BINM_MAKEWHATIS, paths->paths[ipath]);
668260e9a87SYuri Pankov 	*res = mandoc_reallocarray(*res, ++*ressz, sizeof(struct manpage));
669260e9a87SYuri Pankov 	page = *res + (*ressz - 1);
670260e9a87SYuri Pankov 	page->file = file;
671260e9a87SYuri Pankov 	page->names = NULL;
672260e9a87SYuri Pankov 	page->output = NULL;
673260e9a87SYuri Pankov 	page->ipath = ipath;
674260e9a87SYuri Pankov 	page->bits = NAME_FILE & NAME_MASK;
675260e9a87SYuri Pankov 	page->sec = (*sec >= '1' && *sec <= '9') ? *sec - '1' + 1 : 10;
676260e9a87SYuri Pankov 	page->form = form;
677371584c2SYuri Pankov 	return 1;
678260e9a87SYuri Pankov }
679260e9a87SYuri Pankov 
680260e9a87SYuri Pankov static void
681260e9a87SYuri Pankov fs_search(const struct mansearch *cfg, const struct manpaths *paths,
682260e9a87SYuri Pankov 	int argc, char **argv, struct manpage **res, size_t *ressz)
683260e9a87SYuri Pankov {
684260e9a87SYuri Pankov 	const char *const sections[] =
685371584c2SYuri Pankov 	    {"1", "8", "6", "2", "3", "5", "7", "4", "9", "3p"};
686260e9a87SYuri Pankov 	const size_t nsec = sizeof(sections)/sizeof(sections[0]);
687260e9a87SYuri Pankov 
688260e9a87SYuri Pankov 	size_t		 ipath, isec, lastsz;
689260e9a87SYuri Pankov 
690260e9a87SYuri Pankov 	assert(cfg->argmode == ARG_NAME);
691260e9a87SYuri Pankov 
692260e9a87SYuri Pankov 	*res = NULL;
693260e9a87SYuri Pankov 	*ressz = lastsz = 0;
694260e9a87SYuri Pankov 	while (argc) {
695260e9a87SYuri Pankov 		for (ipath = 0; ipath < paths->sz; ipath++) {
696260e9a87SYuri Pankov 			if (cfg->sec != NULL) {
697260e9a87SYuri Pankov 				if (fs_lookup(paths, ipath, cfg->sec,
698260e9a87SYuri Pankov 				    cfg->arch, *argv, res, ressz) &&
699260e9a87SYuri Pankov 				    cfg->firstmatch)
700260e9a87SYuri Pankov 					return;
701260e9a87SYuri Pankov 			} else for (isec = 0; isec < nsec; isec++)
702260e9a87SYuri Pankov 				if (fs_lookup(paths, ipath, sections[isec],
703260e9a87SYuri Pankov 				    cfg->arch, *argv, res, ressz) &&
704260e9a87SYuri Pankov 				    cfg->firstmatch)
705260e9a87SYuri Pankov 					return;
706260e9a87SYuri Pankov 		}
707260e9a87SYuri Pankov 		if (*ressz == lastsz)
708371584c2SYuri Pankov 			warnx("No entry for %s in the manual.", *argv);
709260e9a87SYuri Pankov 		lastsz = *ressz;
710260e9a87SYuri Pankov 		argv++;
711260e9a87SYuri Pankov 		argc--;
712260e9a87SYuri Pankov 	}
71395c635efSGarrett D'Amore }
71495c635efSGarrett D'Amore 
71595c635efSGarrett D'Amore static void
716371584c2SYuri Pankov parse(struct curparse *curp, int fd, const char *file)
71795c635efSGarrett D'Amore {
718371584c2SYuri Pankov 	enum mandoclevel  rctmp;
719371584c2SYuri Pankov 	struct roff_man	 *man;
72095c635efSGarrett D'Amore 
72195c635efSGarrett D'Amore 	/* Begin by parsing the file itself. */
72295c635efSGarrett D'Amore 
72395c635efSGarrett D'Amore 	assert(file);
724371584c2SYuri Pankov 	assert(fd >= 0);
72595c635efSGarrett D'Amore 
726371584c2SYuri Pankov 	rctmp = mparse_readfd(curp->mp, fd, file);
727371584c2SYuri Pankov 	if (fd != STDIN_FILENO)
728371584c2SYuri Pankov 		close(fd);
729371584c2SYuri Pankov 	if (rc < rctmp)
730371584c2SYuri Pankov 		rc = rctmp;
73195c635efSGarrett D'Amore 
73295c635efSGarrett D'Amore 	/*
73395c635efSGarrett D'Amore 	 * With -Wstop and warnings or errors of at least the requested
73495c635efSGarrett D'Amore 	 * level, do not produce output.
73595c635efSGarrett D'Amore 	 */
73695c635efSGarrett D'Amore 
737371584c2SYuri Pankov 	if (rctmp != MANDOCLEVEL_OK && curp->wstop)
738371584c2SYuri Pankov 		return;
73995c635efSGarrett D'Amore 
740a5934736SYuri Pankov 	if (curp->outdata == NULL)
741a5934736SYuri Pankov 		outdata_alloc(curp);
742371584c2SYuri Pankov 
743371584c2SYuri Pankov 	mparse_result(curp->mp, &man, NULL);
74495c635efSGarrett D'Amore 
745371584c2SYuri Pankov 	/* Execute the out device, if it exists. */
746371584c2SYuri Pankov 
747371584c2SYuri Pankov 	if (man == NULL)
748371584c2SYuri Pankov 		return;
749371584c2SYuri Pankov 	if (man->macroset == MACROSET_MDOC) {
750*a40ea1a7SYuri Pankov 		if (curp->outtype != OUTT_TREE || !curp->outopts->noval)
751*a40ea1a7SYuri Pankov 			mdoc_validate(man);
75295c635efSGarrett D'Amore 		switch (curp->outtype) {
753260e9a87SYuri Pankov 		case OUTT_HTML:
754371584c2SYuri Pankov 			html_mdoc(curp->outdata, man);
75595c635efSGarrett D'Amore 			break;
756260e9a87SYuri Pankov 		case OUTT_TREE:
757371584c2SYuri Pankov 			tree_mdoc(curp->outdata, man);
75895c635efSGarrett D'Amore 			break;
759260e9a87SYuri Pankov 		case OUTT_MAN:
760371584c2SYuri Pankov 			man_mdoc(curp->outdata, man);
76195c635efSGarrett D'Amore 			break;
762260e9a87SYuri Pankov 		case OUTT_PDF:
763260e9a87SYuri Pankov 		case OUTT_ASCII:
764260e9a87SYuri Pankov 		case OUTT_UTF8:
765260e9a87SYuri Pankov 		case OUTT_LOCALE:
766260e9a87SYuri Pankov 		case OUTT_PS:
767371584c2SYuri Pankov 			terminal_mdoc(curp->outdata, man);
768371584c2SYuri Pankov 			break;
769371584c2SYuri Pankov 		default:
770371584c2SYuri Pankov 			break;
771371584c2SYuri Pankov 		}
772371584c2SYuri Pankov 	}
773371584c2SYuri Pankov 	if (man->macroset == MACROSET_MAN) {
774*a40ea1a7SYuri Pankov 		if (curp->outtype != OUTT_TREE || !curp->outopts->noval)
775*a40ea1a7SYuri Pankov 			man_validate(man);
776371584c2SYuri Pankov 		switch (curp->outtype) {
777371584c2SYuri Pankov 		case OUTT_HTML:
778371584c2SYuri Pankov 			html_man(curp->outdata, man);
779371584c2SYuri Pankov 			break;
780371584c2SYuri Pankov 		case OUTT_TREE:
781371584c2SYuri Pankov 			tree_man(curp->outdata, man);
782371584c2SYuri Pankov 			break;
783371584c2SYuri Pankov 		case OUTT_MAN:
784371584c2SYuri Pankov 			man_man(curp->outdata, man);
785371584c2SYuri Pankov 			break;
786371584c2SYuri Pankov 		case OUTT_PDF:
787371584c2SYuri Pankov 		case OUTT_ASCII:
788371584c2SYuri Pankov 		case OUTT_UTF8:
789371584c2SYuri Pankov 		case OUTT_LOCALE:
790371584c2SYuri Pankov 		case OUTT_PS:
791371584c2SYuri Pankov 			terminal_man(curp->outdata, man);
79295c635efSGarrett D'Amore 			break;
79395c635efSGarrett D'Amore 		default:
79495c635efSGarrett D'Amore 			break;
79595c635efSGarrett D'Amore 		}
79695c635efSGarrett D'Amore 	}
797a5934736SYuri Pankov 	mparse_updaterc(curp->mp, &rc);
798a5934736SYuri Pankov }
799a5934736SYuri Pankov 
800a5934736SYuri Pankov static void
801a5934736SYuri Pankov outdata_alloc(struct curparse *curp)
802a5934736SYuri Pankov {
803a5934736SYuri Pankov 	switch (curp->outtype) {
804a5934736SYuri Pankov 	case OUTT_HTML:
805a5934736SYuri Pankov 		curp->outdata = html_alloc(curp->outopts);
806a5934736SYuri Pankov 		break;
807a5934736SYuri Pankov 	case OUTT_UTF8:
808a5934736SYuri Pankov 		curp->outdata = utf8_alloc(curp->outopts);
809a5934736SYuri Pankov 		break;
810a5934736SYuri Pankov 	case OUTT_LOCALE:
811a5934736SYuri Pankov 		curp->outdata = locale_alloc(curp->outopts);
812a5934736SYuri Pankov 		break;
813a5934736SYuri Pankov 	case OUTT_ASCII:
814a5934736SYuri Pankov 		curp->outdata = ascii_alloc(curp->outopts);
815a5934736SYuri Pankov 		break;
816a5934736SYuri Pankov 	case OUTT_PDF:
817a5934736SYuri Pankov 		curp->outdata = pdf_alloc(curp->outopts);
818a5934736SYuri Pankov 		break;
819a5934736SYuri Pankov 	case OUTT_PS:
820a5934736SYuri Pankov 		curp->outdata = ps_alloc(curp->outopts);
821a5934736SYuri Pankov 		break;
822a5934736SYuri Pankov 	default:
823a5934736SYuri Pankov 		break;
824a5934736SYuri Pankov 	}
82595c635efSGarrett D'Amore }
82695c635efSGarrett D'Amore 
827371584c2SYuri Pankov static void
828260e9a87SYuri Pankov passthrough(const char *file, int fd, int synopsis_only)
829260e9a87SYuri Pankov {
830260e9a87SYuri Pankov 	const char	 synb[] = "S\bSY\bYN\bNO\bOP\bPS\bSI\bIS\bS";
831260e9a87SYuri Pankov 	const char	 synr[] = "SYNOPSIS";
832260e9a87SYuri Pankov 
833260e9a87SYuri Pankov 	FILE		*stream;
834260e9a87SYuri Pankov 	const char	*syscall;
835371584c2SYuri Pankov 	char		*line, *cp;
836371584c2SYuri Pankov 	size_t		 linesz;
837a5934736SYuri Pankov 	ssize_t		 len, written;
838260e9a87SYuri Pankov 	int		 print;
839260e9a87SYuri Pankov 
840371584c2SYuri Pankov 	line = NULL;
841371584c2SYuri Pankov 	linesz = 0;
842260e9a87SYuri Pankov 
843a5934736SYuri Pankov 	if (fflush(stdout) == EOF) {
844a5934736SYuri Pankov 		syscall = "fflush";
845a5934736SYuri Pankov 		goto fail;
846a5934736SYuri Pankov 	}
847a5934736SYuri Pankov 
848260e9a87SYuri Pankov 	if ((stream = fdopen(fd, "r")) == NULL) {
849260e9a87SYuri Pankov 		close(fd);
850260e9a87SYuri Pankov 		syscall = "fdopen";
851260e9a87SYuri Pankov 		goto fail;
852260e9a87SYuri Pankov 	}
853260e9a87SYuri Pankov 
854260e9a87SYuri Pankov 	print = 0;
855a5934736SYuri Pankov 	while ((len = getline(&line, &linesz, stream)) != -1) {
856371584c2SYuri Pankov 		cp = line;
857260e9a87SYuri Pankov 		if (synopsis_only) {
858260e9a87SYuri Pankov 			if (print) {
859371584c2SYuri Pankov 				if ( ! isspace((unsigned char)*cp))
860260e9a87SYuri Pankov 					goto done;
861a5934736SYuri Pankov 				while (isspace((unsigned char)*cp)) {
862371584c2SYuri Pankov 					cp++;
863a5934736SYuri Pankov 					len--;
864a5934736SYuri Pankov 				}
865260e9a87SYuri Pankov 			} else {
866371584c2SYuri Pankov 				if (strcmp(cp, synb) == 0 ||
867371584c2SYuri Pankov 				    strcmp(cp, synr) == 0)
868260e9a87SYuri Pankov 					print = 1;
869260e9a87SYuri Pankov 				continue;
870260e9a87SYuri Pankov 			}
871260e9a87SYuri Pankov 		}
872a5934736SYuri Pankov 		for (; len > 0; len -= written) {
873a5934736SYuri Pankov 			if ((written = write(STDOUT_FILENO, cp, len)) != -1)
874a5934736SYuri Pankov 				continue;
875371584c2SYuri Pankov 			fclose(stream);
876a5934736SYuri Pankov 			syscall = "write";
877371584c2SYuri Pankov 			goto fail;
878371584c2SYuri Pankov 		}
879260e9a87SYuri Pankov 	}
880260e9a87SYuri Pankov 
881260e9a87SYuri Pankov 	if (ferror(stream)) {
882260e9a87SYuri Pankov 		fclose(stream);
883371584c2SYuri Pankov 		syscall = "getline";
884260e9a87SYuri Pankov 		goto fail;
885260e9a87SYuri Pankov 	}
886260e9a87SYuri Pankov 
887260e9a87SYuri Pankov done:
888371584c2SYuri Pankov 	free(line);
889260e9a87SYuri Pankov 	fclose(stream);
890371584c2SYuri Pankov 	return;
891260e9a87SYuri Pankov 
892260e9a87SYuri Pankov fail:
893371584c2SYuri Pankov 	free(line);
894371584c2SYuri Pankov 	warn("%s: SYSERR: %s", file, syscall);
895371584c2SYuri Pankov 	if (rc < MANDOCLEVEL_SYSERR)
896371584c2SYuri Pankov 		rc = MANDOCLEVEL_SYSERR;
897260e9a87SYuri Pankov }
898260e9a87SYuri Pankov 
899260e9a87SYuri Pankov static int
900260e9a87SYuri Pankov koptions(int *options, char *arg)
901260e9a87SYuri Pankov {
902260e9a87SYuri Pankov 
903260e9a87SYuri Pankov 	if ( ! strcmp(arg, "utf-8")) {
904260e9a87SYuri Pankov 		*options |=  MPARSE_UTF8;
905260e9a87SYuri Pankov 		*options &= ~MPARSE_LATIN1;
906260e9a87SYuri Pankov 	} else if ( ! strcmp(arg, "iso-8859-1")) {
907260e9a87SYuri Pankov 		*options |=  MPARSE_LATIN1;
908260e9a87SYuri Pankov 		*options &= ~MPARSE_UTF8;
909260e9a87SYuri Pankov 	} else if ( ! strcmp(arg, "us-ascii")) {
910260e9a87SYuri Pankov 		*options &= ~(MPARSE_UTF8 | MPARSE_LATIN1);
911260e9a87SYuri Pankov 	} else {
912371584c2SYuri Pankov 		warnx("-K %s: Bad argument", arg);
913371584c2SYuri Pankov 		return 0;
914260e9a87SYuri Pankov 	}
915371584c2SYuri Pankov 	return 1;
916260e9a87SYuri Pankov }
917260e9a87SYuri Pankov 
91895c635efSGarrett D'Amore static int
919260e9a87SYuri Pankov moptions(int *options, char *arg)
92095c635efSGarrett D'Amore {
92195c635efSGarrett D'Amore 
922260e9a87SYuri Pankov 	if (arg == NULL)
923260e9a87SYuri Pankov 		/* nothing to do */;
924260e9a87SYuri Pankov 	else if (0 == strcmp(arg, "doc"))
925260e9a87SYuri Pankov 		*options |= MPARSE_MDOC;
92695c635efSGarrett D'Amore 	else if (0 == strcmp(arg, "andoc"))
927260e9a87SYuri Pankov 		/* nothing to do */;
92895c635efSGarrett D'Amore 	else if (0 == strcmp(arg, "an"))
929260e9a87SYuri Pankov 		*options |= MPARSE_MAN;
93095c635efSGarrett D'Amore 	else {
931371584c2SYuri Pankov 		warnx("-m %s: Bad argument", arg);
932371584c2SYuri Pankov 		return 0;
93395c635efSGarrett D'Amore 	}
93495c635efSGarrett D'Amore 
935371584c2SYuri Pankov 	return 1;
93695c635efSGarrett D'Amore }
93795c635efSGarrett D'Amore 
93895c635efSGarrett D'Amore static int
93995c635efSGarrett D'Amore toptions(struct curparse *curp, char *arg)
94095c635efSGarrett D'Amore {
94195c635efSGarrett D'Amore 
94295c635efSGarrett D'Amore 	if (0 == strcmp(arg, "ascii"))
94395c635efSGarrett D'Amore 		curp->outtype = OUTT_ASCII;
94495c635efSGarrett D'Amore 	else if (0 == strcmp(arg, "lint")) {
94595c635efSGarrett D'Amore 		curp->outtype = OUTT_LINT;
94695c635efSGarrett D'Amore 		curp->wlevel  = MANDOCLEVEL_WARNING;
94795c635efSGarrett D'Amore 	} else if (0 == strcmp(arg, "tree"))
94895c635efSGarrett D'Amore 		curp->outtype = OUTT_TREE;
94995c635efSGarrett D'Amore 	else if (0 == strcmp(arg, "man"))
95095c635efSGarrett D'Amore 		curp->outtype = OUTT_MAN;
95195c635efSGarrett D'Amore 	else if (0 == strcmp(arg, "html"))
95295c635efSGarrett D'Amore 		curp->outtype = OUTT_HTML;
95395c635efSGarrett D'Amore 	else if (0 == strcmp(arg, "utf8"))
95495c635efSGarrett D'Amore 		curp->outtype = OUTT_UTF8;
95595c635efSGarrett D'Amore 	else if (0 == strcmp(arg, "locale"))
95695c635efSGarrett D'Amore 		curp->outtype = OUTT_LOCALE;
95795c635efSGarrett D'Amore 	else if (0 == strcmp(arg, "xhtml"))
958260e9a87SYuri Pankov 		curp->outtype = OUTT_HTML;
95995c635efSGarrett D'Amore 	else if (0 == strcmp(arg, "ps"))
96095c635efSGarrett D'Amore 		curp->outtype = OUTT_PS;
96195c635efSGarrett D'Amore 	else if (0 == strcmp(arg, "pdf"))
96295c635efSGarrett D'Amore 		curp->outtype = OUTT_PDF;
96395c635efSGarrett D'Amore 	else {
964371584c2SYuri Pankov 		warnx("-T %s: Bad argument", arg);
965371584c2SYuri Pankov 		return 0;
96695c635efSGarrett D'Amore 	}
96795c635efSGarrett D'Amore 
968371584c2SYuri Pankov 	return 1;
96995c635efSGarrett D'Amore }
97095c635efSGarrett D'Amore 
97195c635efSGarrett D'Amore static int
97295c635efSGarrett D'Amore woptions(struct curparse *curp, char *arg)
97395c635efSGarrett D'Amore {
97495c635efSGarrett D'Amore 	char		*v, *o;
975260e9a87SYuri Pankov 	const char	*toks[7];
97695c635efSGarrett D'Amore 
97795c635efSGarrett D'Amore 	toks[0] = "stop";
97895c635efSGarrett D'Amore 	toks[1] = "all";
97995c635efSGarrett D'Amore 	toks[2] = "warning";
98095c635efSGarrett D'Amore 	toks[3] = "error";
981260e9a87SYuri Pankov 	toks[4] = "unsupp";
982260e9a87SYuri Pankov 	toks[5] = "fatal";
983260e9a87SYuri Pankov 	toks[6] = NULL;
98495c635efSGarrett D'Amore 
98595c635efSGarrett D'Amore 	while (*arg) {
98695c635efSGarrett D'Amore 		o = arg;
987a5934736SYuri Pankov 		switch (getsubopt(&arg, (char * const *)toks, &v)) {
988260e9a87SYuri Pankov 		case 0:
98995c635efSGarrett D'Amore 			curp->wstop = 1;
99095c635efSGarrett D'Amore 			break;
991260e9a87SYuri Pankov 		case 1:
992260e9a87SYuri Pankov 		case 2:
99395c635efSGarrett D'Amore 			curp->wlevel = MANDOCLEVEL_WARNING;
99495c635efSGarrett D'Amore 			break;
995260e9a87SYuri Pankov 		case 3:
99695c635efSGarrett D'Amore 			curp->wlevel = MANDOCLEVEL_ERROR;
99795c635efSGarrett D'Amore 			break;
998260e9a87SYuri Pankov 		case 4:
999260e9a87SYuri Pankov 			curp->wlevel = MANDOCLEVEL_UNSUPP;
1000260e9a87SYuri Pankov 			break;
1001260e9a87SYuri Pankov 		case 5:
1002260e9a87SYuri Pankov 			curp->wlevel = MANDOCLEVEL_BADARG;
100395c635efSGarrett D'Amore 			break;
100495c635efSGarrett D'Amore 		default:
1005371584c2SYuri Pankov 			warnx("-W %s: Bad argument", o);
1006371584c2SYuri Pankov 			return 0;
100795c635efSGarrett D'Amore 		}
100895c635efSGarrett D'Amore 	}
100995c635efSGarrett D'Amore 
1010371584c2SYuri Pankov 	return 1;
101195c635efSGarrett D'Amore }
101295c635efSGarrett D'Amore 
101395c635efSGarrett D'Amore static void
1014260e9a87SYuri Pankov mmsg(enum mandocerr t, enum mandoclevel lvl,
101595c635efSGarrett D'Amore 		const char *file, int line, int col, const char *msg)
101695c635efSGarrett D'Amore {
1017260e9a87SYuri Pankov 	const char	*mparse_msg;
1018260e9a87SYuri Pankov 
1019371584c2SYuri Pankov 	fprintf(stderr, "%s: %s:", getprogname(),
1020371584c2SYuri Pankov 	    file == NULL ? "<stdin>" : file);
1021260e9a87SYuri Pankov 
1022260e9a87SYuri Pankov 	if (line)
1023260e9a87SYuri Pankov 		fprintf(stderr, "%d:%d:", line, col + 1);
102495c635efSGarrett D'Amore 
1025260e9a87SYuri Pankov 	fprintf(stderr, " %s", mparse_strlevel(lvl));
1026260e9a87SYuri Pankov 
1027260e9a87SYuri Pankov 	if (NULL != (mparse_msg = mparse_strerror(t)))
1028260e9a87SYuri Pankov 		fprintf(stderr, ": %s", mparse_msg);
102995c635efSGarrett D'Amore 
103095c635efSGarrett D'Amore 	if (msg)
103195c635efSGarrett D'Amore 		fprintf(stderr, ": %s", msg);
103295c635efSGarrett D'Amore 
103395c635efSGarrett D'Amore 	fputc('\n', stderr);
103495c635efSGarrett D'Amore }
1035260e9a87SYuri Pankov 
1036260e9a87SYuri Pankov static pid_t
1037371584c2SYuri Pankov spawn_pager(struct tag_files *tag_files)
1038260e9a87SYuri Pankov {
1039371584c2SYuri Pankov 	const struct timespec timeout = { 0, 100000000 };  /* 0.1s */
1040260e9a87SYuri Pankov #define MAX_PAGER_ARGS 16
1041260e9a87SYuri Pankov 	char		*argv[MAX_PAGER_ARGS];
1042260e9a87SYuri Pankov 	const char	*pager;
1043260e9a87SYuri Pankov 	char		*cp;
1044371584c2SYuri Pankov 	size_t		 cmdlen;
1045260e9a87SYuri Pankov 	int		 argc;
1046260e9a87SYuri Pankov 	pid_t		 pager_pid;
1047260e9a87SYuri Pankov 
1048260e9a87SYuri Pankov 	pager = getenv("MANPAGER");
1049260e9a87SYuri Pankov 	if (pager == NULL || *pager == '\0')
1050260e9a87SYuri Pankov 		pager = getenv("PAGER");
1051260e9a87SYuri Pankov 	if (pager == NULL || *pager == '\0')
1052371584c2SYuri Pankov 		pager = "more -s";
1053260e9a87SYuri Pankov 	cp = mandoc_strdup(pager);
1054260e9a87SYuri Pankov 
1055260e9a87SYuri Pankov 	/*
1056260e9a87SYuri Pankov 	 * Parse the pager command into words.
1057260e9a87SYuri Pankov 	 * Intentionally do not do anything fancy here.
1058260e9a87SYuri Pankov 	 */
1059260e9a87SYuri Pankov 
1060260e9a87SYuri Pankov 	argc = 0;
1061371584c2SYuri Pankov 	while (argc + 4 < MAX_PAGER_ARGS) {
1062260e9a87SYuri Pankov 		argv[argc++] = cp;
1063260e9a87SYuri Pankov 		cp = strchr(cp, ' ');
1064260e9a87SYuri Pankov 		if (cp == NULL)
1065260e9a87SYuri Pankov 			break;
1066260e9a87SYuri Pankov 		*cp++ = '\0';
1067260e9a87SYuri Pankov 		while (*cp == ' ')
1068260e9a87SYuri Pankov 			cp++;
1069260e9a87SYuri Pankov 		if (*cp == '\0')
1070260e9a87SYuri Pankov 			break;
1071260e9a87SYuri Pankov 	}
1072371584c2SYuri Pankov 
1073371584c2SYuri Pankov 	/* For less(1), use the tag file. */
1074371584c2SYuri Pankov 
1075371584c2SYuri Pankov 	if ((cmdlen = strlen(argv[0])) >= 4) {
1076371584c2SYuri Pankov 		cp = argv[0] + cmdlen - 4;
1077371584c2SYuri Pankov 		if (strcmp(cp, "less") == 0) {
1078371584c2SYuri Pankov 			argv[argc++] = mandoc_strdup("-T");
1079371584c2SYuri Pankov 			argv[argc++] = tag_files->tfn;
1080371584c2SYuri Pankov 		}
1081371584c2SYuri Pankov 	}
1082371584c2SYuri Pankov 	argv[argc++] = tag_files->ofn;
1083260e9a87SYuri Pankov 	argv[argc] = NULL;
1084260e9a87SYuri Pankov 
1085371584c2SYuri Pankov 	switch (pager_pid = fork()) {
1086371584c2SYuri Pankov 	case -1:
1087371584c2SYuri Pankov 		err((int)MANDOCLEVEL_SYSERR, "fork");
1088371584c2SYuri Pankov 	case 0:
1089371584c2SYuri Pankov 		break;
1090371584c2SYuri Pankov 	default:
1091371584c2SYuri Pankov 		(void)setpgid(pager_pid, 0);
1092*a40ea1a7SYuri Pankov 		(void)tcsetpgrp(tag_files->ofd, pager_pid);
1093371584c2SYuri Pankov #if HAVE_PLEDGE
1094371584c2SYuri Pankov 		if (pledge("stdio rpath tmppath tty proc", NULL) == -1)
1095371584c2SYuri Pankov 			err((int)MANDOCLEVEL_SYSERR, "pledge");
1096371584c2SYuri Pankov #endif
1097371584c2SYuri Pankov 		tag_files->pager_pid = pager_pid;
1098371584c2SYuri Pankov 		return pager_pid;
1099371584c2SYuri Pankov 	}
1100371584c2SYuri Pankov 
1101371584c2SYuri Pankov 	/* The child process becomes the pager. */
1102371584c2SYuri Pankov 
1103371584c2SYuri Pankov 	if (dup2(tag_files->ofd, STDOUT_FILENO) == -1)
1104371584c2SYuri Pankov 		err((int)MANDOCLEVEL_SYSERR, "pager stdout");
1105371584c2SYuri Pankov 	close(tag_files->ofd);
1106371584c2SYuri Pankov 	close(tag_files->tfd);
1107371584c2SYuri Pankov 
1108371584c2SYuri Pankov 	/* Do not start the pager before controlling the terminal. */
1109371584c2SYuri Pankov 
1110*a40ea1a7SYuri Pankov 	while (tcgetpgrp(STDOUT_FILENO) != getpid())
1111371584c2SYuri Pankov 		nanosleep(&timeout, NULL);
1112260e9a87SYuri Pankov 
1113260e9a87SYuri Pankov 	execvp(argv[0], argv);
1114371584c2SYuri Pankov 	err((int)MANDOCLEVEL_SYSERR, "exec %s", argv[0]);
1115260e9a87SYuri Pankov }
1116