xref: /netbsd-src/external/bsd/mdocml/dist/main.c (revision 2fd1c0ac7f6b5e074b39266fb1a740bd22fd697c)
16167eca2Schristos /*	Id: main.c,v 1.322 2019/03/06 10:18:58 schwarze Exp  */
24154958bSjoerg /*
3fec65c98Schristos  * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
46167eca2Schristos  * Copyright (c) 2010-2012, 2014-2019 Ingo Schwarze <schwarze@openbsd.org>
5fec65c98Schristos  * Copyright (c) 2010 Joerg Sonnenberger <joerg@netbsd.org>
64154958bSjoerg  *
74154958bSjoerg  * Permission to use, copy, modify, and distribute this software for any
84154958bSjoerg  * purpose with or without fee is hereby granted, provided that the above
94154958bSjoerg  * copyright notice and this permission notice appear in all copies.
104154958bSjoerg  *
11f47368cfSchristos  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
124154958bSjoerg  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13f47368cfSchristos  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
144154958bSjoerg  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
154154958bSjoerg  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
164154958bSjoerg  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
174154958bSjoerg  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
184154958bSjoerg  */
19d5e63c8dSjoerg #include "config.h"
20fec65c98Schristos 
21fec65c98Schristos #include <sys/types.h>
2214e7489eSchristos #include <sys/ioctl.h>
23fec65c98Schristos #include <sys/param.h>	/* MACHINE */
24fec65c98Schristos #include <sys/wait.h>
25d5e63c8dSjoerg 
264154958bSjoerg #include <assert.h>
27fec65c98Schristos #include <ctype.h>
28f47368cfSchristos #if HAVE_ERR
29f47368cfSchristos #include <err.h>
30f47368cfSchristos #endif
31fec65c98Schristos #include <errno.h>
32fec65c98Schristos #include <fcntl.h>
33fec65c98Schristos #include <glob.h>
34f47368cfSchristos #if HAVE_SANDBOX_INIT
35f47368cfSchristos #include <sandbox.h>
36f47368cfSchristos #endif
37f47368cfSchristos #include <signal.h>
384154958bSjoerg #include <stdio.h>
394154958bSjoerg #include <stdint.h>
404154958bSjoerg #include <stdlib.h>
414154958bSjoerg #include <string.h>
426167eca2Schristos #include <termios.h>
43f47368cfSchristos #include <time.h>
444154958bSjoerg #include <unistd.h>
454154958bSjoerg 
46fec65c98Schristos #include "mandoc_aux.h"
47f47368cfSchristos #include "mandoc.h"
4814e7489eSchristos #include "mandoc_xr.h"
49f47368cfSchristos #include "roff.h"
504154958bSjoerg #include "mdoc.h"
514154958bSjoerg #include "man.h"
526167eca2Schristos #include "mandoc_parse.h"
53f47368cfSchristos #include "tag.h"
54f47368cfSchristos #include "main.h"
55f47368cfSchristos #include "manconf.h"
56fec65c98Schristos #include "mansearch.h"
573514411fSjoerg 
58fec65c98Schristos enum	outmode {
59fec65c98Schristos 	OUTMODE_DEF = 0,
60fec65c98Schristos 	OUTMODE_FLN,
61fec65c98Schristos 	OUTMODE_LST,
62fec65c98Schristos 	OUTMODE_ALL,
63fec65c98Schristos 	OUTMODE_ONE
64fec65c98Schristos };
65fec65c98Schristos 
664154958bSjoerg enum	outt {
6748741257Sjoerg 	OUTT_ASCII = 0,	/* -Tascii */
68c5f73b34Sjoerg 	OUTT_LOCALE,	/* -Tlocale */
69c5f73b34Sjoerg 	OUTT_UTF8,	/* -Tutf8 */
7048741257Sjoerg 	OUTT_TREE,	/* -Ttree */
71c5f73b34Sjoerg 	OUTT_MAN,	/* -Tman */
7248741257Sjoerg 	OUTT_HTML,	/* -Thtml */
7314e7489eSchristos 	OUTT_MARKDOWN,	/* -Tmarkdown */
7448741257Sjoerg 	OUTT_LINT,	/* -Tlint */
7548741257Sjoerg 	OUTT_PS,	/* -Tps */
7648741257Sjoerg 	OUTT_PDF	/* -Tpdf */
774154958bSjoerg };
784154958bSjoerg 
794154958bSjoerg struct	curparse {
8048741257Sjoerg 	struct mparse	 *mp;
81f47368cfSchristos 	struct manoutput *outopts;	/* output options */
8214e7489eSchristos 	void		 *outdata;	/* data for output */
8314e7489eSchristos 	char		 *os_s;		/* operating system for display */
8414e7489eSchristos 	int		  wstop;	/* stop after a file with a warning */
8514e7489eSchristos 	enum mandoc_os	  os_e;		/* check base system conventions */
8614e7489eSchristos 	enum outt	  outtype;	/* which output to use */
874154958bSjoerg };
884154958bSjoerg 
8937ef69edSchristos 
9037ef69edSchristos int			  mandocdb(int, char *[]);
9137ef69edSchristos 
926167eca2Schristos static	void		  check_xr(void);
93fec65c98Schristos static	int		  fs_lookup(const struct manpaths *,
94fec65c98Schristos 				size_t ipath, const char *,
95fec65c98Schristos 				const char *, const char *,
96fec65c98Schristos 				struct manpage **, size_t *);
9714e7489eSchristos static	int		  fs_search(const struct mansearch *,
98fec65c98Schristos 				const struct manpaths *, int, char**,
99fec65c98Schristos 				struct manpage **, size_t *);
100fec65c98Schristos static	int		  koptions(int *, char *);
10114e7489eSchristos static	void		  moptions(int *, char *);
10237ef69edSchristos static	void		  outdata_alloc(struct curparse *);
103f47368cfSchristos static	void		  parse(struct curparse *, int, const char *);
104f47368cfSchristos static	void		  passthrough(const char *, int, int);
105f47368cfSchristos static	pid_t		  spawn_pager(struct tag_files *);
1060a84adc5Sjoerg static	int		  toptions(struct curparse *, char *);
10737ef69edSchristos static	void		  usage(enum argmode) __attribute__((__noreturn__));
108c0d9444aSjoerg static	int		  woptions(struct curparse *, char *);
1094154958bSjoerg 
110fec65c98Schristos static	const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
111fec65c98Schristos static	char		  help_arg[] = "help";
112fec65c98Schristos static	char		 *help_argv[] = {help_arg, NULL};
1134154958bSjoerg 
114fec65c98Schristos 
1154154958bSjoerg int
main(int argc,char * argv[])1164154958bSjoerg main(int argc, char *argv[])
1174154958bSjoerg {
118f47368cfSchristos 	struct manconf	 conf;
119fec65c98Schristos 	struct mansearch search;
12037ef69edSchristos 	struct curparse	 curp;
12114e7489eSchristos 	struct winsize	 ws;
122f47368cfSchristos 	struct tag_files *tag_files;
123fec65c98Schristos 	struct manpage	*res, *resp;
12437ef69edSchristos 	const char	*progname, *sec, *thisarg;
12537ef69edSchristos 	char		*conf_file, *defpaths, *auxpaths;
1266167eca2Schristos 	char		*oarg, *tagarg;
12737ef69edSchristos 	unsigned char	*uc;
128f47368cfSchristos 	size_t		 i, sz;
129f47368cfSchristos 	int		 prio, best_prio;
130fec65c98Schristos 	enum outmode	 outmode;
13114e7489eSchristos 	int		 fd, startdir;
132fec65c98Schristos 	int		 show_usage;
133fec65c98Schristos 	int		 options;
134f47368cfSchristos 	int		 use_pager;
135f47368cfSchristos 	int		 status, signum;
136fec65c98Schristos 	int		 c;
137f47368cfSchristos 	pid_t		 pager_pid, tc_pgid, man_pgid, pid;
1384154958bSjoerg 
139f47368cfSchristos #if HAVE_PROGNAME
140f47368cfSchristos 	progname = getprogname();
141f47368cfSchristos #else
142fec65c98Schristos 	if (argc < 1)
143f47368cfSchristos 		progname = mandoc_strdup("mandoc");
144fec65c98Schristos 	else if ((progname = strrchr(argv[0], '/')) == NULL)
1453514411fSjoerg 		progname = argv[0];
1463514411fSjoerg 	else
1473514411fSjoerg 		++progname;
148f47368cfSchristos 	setprogname(progname);
149f47368cfSchristos #endif
1503514411fSjoerg 
1516167eca2Schristos 	mandoc_msg_setoutfile(stderr);
152f47368cfSchristos 	if (strncmp(progname, "mandocdb", 8) == 0 ||
153f47368cfSchristos 	    strcmp(progname, BINM_MAKEWHATIS) == 0)
154f47368cfSchristos 		return mandocdb(argc, argv);
155f47368cfSchristos 
156f47368cfSchristos #if HAVE_PLEDGE
15714e7489eSchristos 	if (pledge("stdio rpath tmppath tty proc exec", NULL) == -1)
158f47368cfSchristos 		err((int)MANDOCLEVEL_SYSERR, "pledge");
159f47368cfSchristos #endif
160f47368cfSchristos 
161f47368cfSchristos #if HAVE_SANDBOX_INIT
162f47368cfSchristos 	if (sandbox_init(kSBXProfileNoInternet, SANDBOX_NAMED, NULL) == -1)
163f47368cfSchristos 		errx((int)MANDOCLEVEL_SYSERR, "sandbox_init");
164fec65c98Schristos #endif
1654154958bSjoerg 
166fec65c98Schristos 	/* Search options. */
167fec65c98Schristos 
168f47368cfSchristos 	memset(&conf, 0, sizeof(conf));
169fec65c98Schristos 	conf_file = defpaths = NULL;
170fec65c98Schristos 	auxpaths = NULL;
171fec65c98Schristos 
172fec65c98Schristos 	memset(&search, 0, sizeof(struct mansearch));
173fec65c98Schristos 	search.outkey = "Nd";
17437ef69edSchristos 	oarg = NULL;
175fec65c98Schristos 
176fec65c98Schristos 	if (strcmp(progname, BINM_MAN) == 0)
177fec65c98Schristos 		search.argmode = ARG_NAME;
178fec65c98Schristos 	else if (strcmp(progname, BINM_APROPOS) == 0)
179fec65c98Schristos 		search.argmode = ARG_EXPR;
180fec65c98Schristos 	else if (strcmp(progname, BINM_WHATIS) == 0)
181fec65c98Schristos 		search.argmode = ARG_WORD;
182fec65c98Schristos 	else if (strncmp(progname, "help", 4) == 0)
183fec65c98Schristos 		search.argmode = ARG_NAME;
184fec65c98Schristos 	else
185fec65c98Schristos 		search.argmode = ARG_FILE;
186fec65c98Schristos 
187fec65c98Schristos 	/* Parser and formatter options. */
188fec65c98Schristos 
189fec65c98Schristos 	memset(&curp, 0, sizeof(struct curparse));
190fec65c98Schristos 	curp.outtype = OUTT_LOCALE;
191f47368cfSchristos 	curp.outopts = &conf.output;
192fec65c98Schristos 	options = MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1;
1934154958bSjoerg 
194f47368cfSchristos 	use_pager = 1;
195f47368cfSchristos 	tag_files = NULL;
196fec65c98Schristos 	show_usage = 0;
197fec65c98Schristos 	outmode = OUTMODE_DEF;
198fec65c98Schristos 
19914e7489eSchristos 	while ((c = getopt(argc, argv,
20014e7489eSchristos 	    "aC:cfhI:iK:klM:m:O:S:s:T:VW:w")) != -1) {
20114e7489eSchristos 		if (c == 'i' && search.argmode == ARG_EXPR) {
20214e7489eSchristos 			optind--;
20314e7489eSchristos 			break;
20414e7489eSchristos 		}
2054154958bSjoerg 		switch (c) {
206fec65c98Schristos 		case 'a':
207fec65c98Schristos 			outmode = OUTMODE_ALL;
208fec65c98Schristos 			break;
209fec65c98Schristos 		case 'C':
210fec65c98Schristos 			conf_file = optarg;
211fec65c98Schristos 			break;
212fec65c98Schristos 		case 'c':
213f47368cfSchristos 			use_pager = 0;
214fec65c98Schristos 			break;
215fec65c98Schristos 		case 'f':
216fec65c98Schristos 			search.argmode = ARG_WORD;
217fec65c98Schristos 			break;
218fec65c98Schristos 		case 'h':
219f47368cfSchristos 			conf.output.synopsisonly = 1;
220f47368cfSchristos 			use_pager = 0;
221fec65c98Schristos 			outmode = OUTMODE_ALL;
222fec65c98Schristos 			break;
223fec65c98Schristos 		case 'I':
22470f041f9Sjoerg 			if (strncmp(optarg, "os=", 3)) {
225f47368cfSchristos 				warnx("-I %s: Bad argument", optarg);
226f47368cfSchristos 				return (int)MANDOCLEVEL_BADARG;
22770f041f9Sjoerg 			}
22814e7489eSchristos 			if (curp.os_s != NULL) {
229f47368cfSchristos 				warnx("-I %s: Duplicate argument", optarg);
230f47368cfSchristos 				return (int)MANDOCLEVEL_BADARG;
23170f041f9Sjoerg 			}
23214e7489eSchristos 			curp.os_s = mandoc_strdup(optarg + 3);
233fec65c98Schristos 			break;
234fec65c98Schristos 		case 'K':
235fec65c98Schristos 			if ( ! koptions(&options, optarg))
236f47368cfSchristos 				return (int)MANDOCLEVEL_BADARG;
2374154958bSjoerg 			break;
238fec65c98Schristos 		case 'k':
239fec65c98Schristos 			search.argmode = ARG_EXPR;
240fec65c98Schristos 			break;
241fec65c98Schristos 		case 'l':
242fec65c98Schristos 			search.argmode = ARG_FILE;
243fec65c98Schristos 			outmode = OUTMODE_ALL;
244fec65c98Schristos 			break;
245fec65c98Schristos 		case 'M':
246fec65c98Schristos 			defpaths = optarg;
247fec65c98Schristos 			break;
248fec65c98Schristos 		case 'm':
249fec65c98Schristos 			auxpaths = optarg;
250fec65c98Schristos 			break;
251fec65c98Schristos 		case 'O':
25237ef69edSchristos 			oarg = optarg;
2534154958bSjoerg 			break;
254fec65c98Schristos 		case 'S':
255fec65c98Schristos 			search.arch = optarg;
256fec65c98Schristos 			break;
257fec65c98Schristos 		case 's':
258fec65c98Schristos 			search.sec = optarg;
259fec65c98Schristos 			break;
260fec65c98Schristos 		case 'T':
26131e1f4e3Sjoerg 			if ( ! toptions(&curp, optarg))
262f47368cfSchristos 				return (int)MANDOCLEVEL_BADARG;
2634154958bSjoerg 			break;
264fec65c98Schristos 		case 'W':
265c0d9444aSjoerg 			if ( ! woptions(&curp, optarg))
266f47368cfSchristos 				return (int)MANDOCLEVEL_BADARG;
2674154958bSjoerg 			break;
268fec65c98Schristos 		case 'w':
269fec65c98Schristos 			outmode = OUTMODE_FLN;
270fec65c98Schristos 			break;
2714154958bSjoerg 		default:
272fec65c98Schristos 			show_usage = 1;
273fec65c98Schristos 			break;
274fec65c98Schristos 		}
2754154958bSjoerg 	}
2764154958bSjoerg 
277fec65c98Schristos 	if (show_usage)
278fec65c98Schristos 		usage(search.argmode);
279fec65c98Schristos 
280fec65c98Schristos 	/* Postprocess options. */
281fec65c98Schristos 
282fec65c98Schristos 	if (outmode == OUTMODE_DEF) {
283fec65c98Schristos 		switch (search.argmode) {
284fec65c98Schristos 		case ARG_FILE:
285fec65c98Schristos 			outmode = OUTMODE_ALL;
286f47368cfSchristos 			use_pager = 0;
287fec65c98Schristos 			break;
288fec65c98Schristos 		case ARG_NAME:
289fec65c98Schristos 			outmode = OUTMODE_ONE;
290fec65c98Schristos 			break;
291fec65c98Schristos 		default:
292fec65c98Schristos 			outmode = OUTMODE_LST;
293fec65c98Schristos 			break;
294fec65c98Schristos 		}
295fec65c98Schristos 	}
296fec65c98Schristos 
29737ef69edSchristos 	if (oarg != NULL) {
29837ef69edSchristos 		if (outmode == OUTMODE_LST)
29937ef69edSchristos 			search.outkey = oarg;
30037ef69edSchristos 		else {
30137ef69edSchristos 			while (oarg != NULL) {
30237ef69edSchristos 				thisarg = oarg;
30337ef69edSchristos 				if (manconf_output(&conf.output,
30437ef69edSchristos 				    strsep(&oarg, ","), 0) == 0)
30537ef69edSchristos 					continue;
30637ef69edSchristos 				warnx("-O %s: Bad argument", thisarg);
30737ef69edSchristos 				return (int)MANDOCLEVEL_BADARG;
30837ef69edSchristos 			}
30937ef69edSchristos 		}
31037ef69edSchristos 	}
31137ef69edSchristos 
3126167eca2Schristos 	if (curp.outtype != OUTT_TREE || !curp.outopts->noval)
3136167eca2Schristos 		options |= MPARSE_VALIDATE;
3146167eca2Schristos 
315f47368cfSchristos 	if (outmode == OUTMODE_FLN ||
316f47368cfSchristos 	    outmode == OUTMODE_LST ||
317f47368cfSchristos 	    !isatty(STDOUT_FILENO))
318f47368cfSchristos 		use_pager = 0;
319f47368cfSchristos 
32014e7489eSchristos 	if (use_pager &&
32114e7489eSchristos 	    (conf.output.width == 0 || conf.output.indent == 0) &&
32214e7489eSchristos 	    ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) != -1 &&
32314e7489eSchristos 	    ws.ws_col > 1) {
32414e7489eSchristos 		if (conf.output.width == 0 && ws.ws_col < 79)
32514e7489eSchristos 			conf.output.width = ws.ws_col - 1;
32614e7489eSchristos 		if (conf.output.indent == 0 && ws.ws_col < 66)
32714e7489eSchristos 			conf.output.indent = 3;
32814e7489eSchristos 	}
32914e7489eSchristos 
330f47368cfSchristos #if HAVE_PLEDGE
331f47368cfSchristos 	if (!use_pager)
33214e7489eSchristos 		if (pledge("stdio rpath", NULL) == -1)
333f47368cfSchristos 			err((int)MANDOCLEVEL_SYSERR, "pledge");
334f47368cfSchristos #endif
335f47368cfSchristos 
336fec65c98Schristos 	/* Parse arguments. */
337fec65c98Schristos 
338fec65c98Schristos 	if (argc > 0) {
339fec65c98Schristos 		argc -= optind;
340fec65c98Schristos 		argv += optind;
341fec65c98Schristos 	}
342fec65c98Schristos 	resp = NULL;
343fec65c98Schristos 
344fec65c98Schristos 	/*
345fec65c98Schristos 	 * Quirks for help(1)
346fec65c98Schristos 	 * and for a man(1) section argument without -s.
347fec65c98Schristos 	 */
348fec65c98Schristos 
349fec65c98Schristos 	if (search.argmode == ARG_NAME) {
350fec65c98Schristos 		if (*progname == 'h') {
351fec65c98Schristos 			if (argc == 0) {
352fec65c98Schristos 				argv = help_argv;
353fec65c98Schristos 				argc = 1;
354fec65c98Schristos 			}
355fec65c98Schristos 		} else if (argc > 1 &&
356fec65c98Schristos 		    ((uc = (unsigned char *)argv[0]) != NULL) &&
357fec65c98Schristos 		    ((isdigit(uc[0]) && (uc[1] == '\0' ||
358fec65c98Schristos 		      (isalpha(uc[1]) && uc[2] == '\0'))) ||
359fec65c98Schristos 		     (uc[0] == 'n' && uc[1] == '\0'))) {
360fec65c98Schristos 			search.sec = (char *)uc;
361fec65c98Schristos 			argv++;
362fec65c98Schristos 			argc--;
363fec65c98Schristos 		}
364fec65c98Schristos 		if (search.arch == NULL)
365fec65c98Schristos 			search.arch = getenv("MACHINE");
366fec65c98Schristos #ifdef MACHINE
367fec65c98Schristos 		if (search.arch == NULL)
368fec65c98Schristos 			search.arch = MACHINE;
369fec65c98Schristos #endif
370fec65c98Schristos 	}
371fec65c98Schristos 
3726167eca2Schristos 	/*
3736167eca2Schristos 	 * Use the first argument for -O tag in addition to
3746167eca2Schristos 	 * using it as a search term for man(1) or apropos(1).
3756167eca2Schristos 	 */
3766167eca2Schristos 
3776167eca2Schristos 	if (conf.output.tag != NULL && *conf.output.tag == '\0') {
3786167eca2Schristos 		tagarg = argc > 0 && search.argmode == ARG_EXPR ?
3796167eca2Schristos 		    strchr(*argv, '=') : NULL;
3806167eca2Schristos 		conf.output.tag = tagarg == NULL ? *argv : tagarg + 1;
3816167eca2Schristos 	}
382fec65c98Schristos 
383fec65c98Schristos 	/* man(1), whatis(1), apropos(1) */
384fec65c98Schristos 
385fec65c98Schristos 	if (search.argmode != ARG_FILE) {
386fec65c98Schristos 		if (search.argmode == ARG_NAME &&
387fec65c98Schristos 		    outmode == OUTMODE_ONE)
388fec65c98Schristos 			search.firstmatch = 1;
389fec65c98Schristos 
390fec65c98Schristos 		/* Access the mandoc database. */
391fec65c98Schristos 
392f47368cfSchristos 		manconf_parse(&conf, conf_file, defpaths, auxpaths);
393f47368cfSchristos 		if ( ! mansearch(&search, &conf.manpath,
394f47368cfSchristos 		    argc, argv, &res, &sz))
395fec65c98Schristos 			usage(search.argmode);
396fec65c98Schristos 
39714e7489eSchristos 		if (sz == 0 && search.argmode == ARG_NAME)
398f47368cfSchristos 			fs_search(&search, &conf.manpath,
399f47368cfSchristos 			    argc, argv, &res, &sz);
40014e7489eSchristos 
40114e7489eSchristos 		if (search.argmode == ARG_NAME) {
40214e7489eSchristos 			for (c = 0; c < argc; c++) {
40314e7489eSchristos 				if (strchr(argv[c], '/') == NULL)
40414e7489eSchristos 					continue;
40514e7489eSchristos 				if (access(argv[c], R_OK) == -1) {
40614e7489eSchristos 					warn("%s", argv[c]);
40714e7489eSchristos 					continue;
40814e7489eSchristos 				}
40914e7489eSchristos 				res = mandoc_reallocarray(res,
41014e7489eSchristos 				    sz + 1, sizeof(*res));
41114e7489eSchristos 				res[sz].file = mandoc_strdup(argv[c]);
41214e7489eSchristos 				res[sz].names = NULL;
41314e7489eSchristos 				res[sz].output = NULL;
41414e7489eSchristos 				res[sz].ipath = SIZE_MAX;
41514e7489eSchristos 				res[sz].sec = 10;
41614e7489eSchristos 				res[sz].form = FORM_SRC;
41714e7489eSchristos 				sz++;
41814e7489eSchristos 			}
419f47368cfSchristos 		}
420fec65c98Schristos 
421fec65c98Schristos 		if (sz == 0) {
42214e7489eSchristos 			if (search.argmode != ARG_NAME)
42314e7489eSchristos 				warnx("nothing appropriate");
4246167eca2Schristos 			mandoc_msg_setrc(MANDOCLEVEL_BADARG);
425fec65c98Schristos 			goto out;
426fec65c98Schristos 		}
427fec65c98Schristos 
428fec65c98Schristos 		/*
429fec65c98Schristos 		 * For standard man(1) and -a output mode,
430fec65c98Schristos 		 * prepare for copying filename pointers
431fec65c98Schristos 		 * into the program parameter array.
432fec65c98Schristos 		 */
433fec65c98Schristos 
434fec65c98Schristos 		if (outmode == OUTMODE_ONE) {
435fec65c98Schristos 			argc = 1;
436f47368cfSchristos 			best_prio = 20;
437fec65c98Schristos 		} else if (outmode == OUTMODE_ALL)
438fec65c98Schristos 			argc = (int)sz;
439fec65c98Schristos 
440fec65c98Schristos 		/* Iterate all matching manuals. */
441fec65c98Schristos 
442fec65c98Schristos 		resp = res;
443fec65c98Schristos 		for (i = 0; i < sz; i++) {
444fec65c98Schristos 			if (outmode == OUTMODE_FLN)
445fec65c98Schristos 				puts(res[i].file);
446fec65c98Schristos 			else if (outmode == OUTMODE_LST)
447fec65c98Schristos 				printf("%s - %s\n", res[i].names,
448fec65c98Schristos 				    res[i].output == NULL ? "" :
449fec65c98Schristos 				    res[i].output);
450fec65c98Schristos 			else if (outmode == OUTMODE_ONE) {
451fec65c98Schristos 				/* Search for the best section. */
452f47368cfSchristos 				sec = res[i].file;
453f47368cfSchristos 				sec += strcspn(sec, "123456789");
454f47368cfSchristos 				if (sec[0] == '\0')
455fec65c98Schristos 					continue;
456f47368cfSchristos 				prio = sec_prios[sec[0] - '1'];
457f47368cfSchristos 				if (sec[1] != '/')
458f47368cfSchristos 					prio += 10;
459fec65c98Schristos 				if (prio >= best_prio)
460fec65c98Schristos 					continue;
461fec65c98Schristos 				best_prio = prio;
462fec65c98Schristos 				resp = res + i;
463fec65c98Schristos 			}
464fec65c98Schristos 		}
465fec65c98Schristos 
466fec65c98Schristos 		/*
467fec65c98Schristos 		 * For man(1), -a and -i output mode, fall through
468fec65c98Schristos 		 * to the main mandoc(1) code iterating files
469fec65c98Schristos 		 * and running the parsers on each of them.
470fec65c98Schristos 		 */
471fec65c98Schristos 
472fec65c98Schristos 		if (outmode == OUTMODE_FLN || outmode == OUTMODE_LST)
473fec65c98Schristos 			goto out;
474fec65c98Schristos 	}
475fec65c98Schristos 
476fec65c98Schristos 	/* mandoc(1) */
477fec65c98Schristos 
478f47368cfSchristos #if HAVE_PLEDGE
479f47368cfSchristos 	if (use_pager) {
480f47368cfSchristos 		if (pledge("stdio rpath tmppath tty proc exec", NULL) == -1)
481f47368cfSchristos 			err((int)MANDOCLEVEL_SYSERR, "pledge");
482f47368cfSchristos 	} else {
483f47368cfSchristos 		if (pledge("stdio rpath", NULL) == -1)
484f47368cfSchristos 			err((int)MANDOCLEVEL_SYSERR, "pledge");
485f47368cfSchristos 	}
486f47368cfSchristos #endif
487fec65c98Schristos 
48814e7489eSchristos 	if (search.argmode == ARG_FILE)
48914e7489eSchristos 		moptions(&options, auxpaths);
490f47368cfSchristos 
491f47368cfSchristos 	mchars_alloc();
4926167eca2Schristos 	curp.mp = mparse_alloc(options, curp.os_e, curp.os_s);
493c5f73b34Sjoerg 
494fec65c98Schristos 	if (argc < 1) {
4956167eca2Schristos 		if (use_pager) {
496f47368cfSchristos 			tag_files = tag_init();
4976167eca2Schristos 			tag_files->tagname = conf.output.tag;
4986167eca2Schristos 		}
4996167eca2Schristos 		thisarg = "<stdin>";
5006167eca2Schristos 		mandoc_msg_setinfilename(thisarg);
5016167eca2Schristos 		parse(&curp, STDIN_FILENO, thisarg);
5026167eca2Schristos 		mandoc_msg_setinfilename(NULL);
503fec65c98Schristos 	}
5044154958bSjoerg 
50514e7489eSchristos 	/*
50614e7489eSchristos 	 * Remember the original working directory, if possible.
50714e7489eSchristos 	 * This will be needed if some names on the command line
50814e7489eSchristos 	 * are page names and some are relative file names.
50914e7489eSchristos 	 * Do not error out if the current directory is not
51014e7489eSchristos 	 * readable: Maybe it won't be needed after all.
51114e7489eSchristos 	 */
51214e7489eSchristos 	startdir = open(".", O_RDONLY | O_DIRECTORY);
51314e7489eSchristos 
514fec65c98Schristos 	while (argc > 0) {
51514e7489eSchristos 
51614e7489eSchristos 		/*
51714e7489eSchristos 		 * Changing directories is not needed in ARG_FILE mode.
51814e7489eSchristos 		 * Do it on a best-effort basis.  Even in case of
51914e7489eSchristos 		 * failure, some functionality may still work.
52014e7489eSchristos 		 */
52114e7489eSchristos 		if (resp != NULL) {
52214e7489eSchristos 			if (resp->ipath != SIZE_MAX)
52314e7489eSchristos 				(void)chdir(conf.manpath.paths[resp->ipath]);
52414e7489eSchristos 			else if (startdir != -1)
52514e7489eSchristos 				(void)fchdir(startdir);
5266167eca2Schristos 			thisarg = resp->file;
5276167eca2Schristos 		} else
5286167eca2Schristos 			thisarg = *argv;
52914e7489eSchristos 
5306167eca2Schristos 		fd = mparse_open(curp.mp, thisarg);
531fec65c98Schristos 		if (fd != -1) {
532f47368cfSchristos 			if (use_pager) {
533f47368cfSchristos 				use_pager = 0;
5346167eca2Schristos 				tag_files = tag_init();
5356167eca2Schristos 				tag_files->tagname = conf.output.tag;
536f47368cfSchristos 			}
537fec65c98Schristos 
5386167eca2Schristos 			mandoc_msg_setinfilename(thisarg);
5396167eca2Schristos 			if (resp == NULL || resp->form == FORM_SRC)
5406167eca2Schristos 				parse(&curp, fd, thisarg);
54114e7489eSchristos 			else
542f47368cfSchristos 				passthrough(resp->file, fd,
543f47368cfSchristos 				    conf.output.synopsisonly);
5446167eca2Schristos 			mandoc_msg_setinfilename(NULL);
545fec65c98Schristos 
54614e7489eSchristos 			if (ferror(stdout)) {
54714e7489eSchristos 				if (tag_files != NULL) {
54814e7489eSchristos 					warn("%s", tag_files->ofn);
54914e7489eSchristos 					tag_unlink();
55014e7489eSchristos 					tag_files = NULL;
55114e7489eSchristos 				} else
55214e7489eSchristos 					warn("stdout");
5536167eca2Schristos 				mandoc_msg_setrc(MANDOCLEVEL_SYSERR);
55414e7489eSchristos 				break;
55514e7489eSchristos 			}
55614e7489eSchristos 
55737ef69edSchristos 			if (argc > 1 && curp.outtype <= OUTT_UTF8) {
55837ef69edSchristos 				if (curp.outdata == NULL)
55937ef69edSchristos 					outdata_alloc(&curp);
560f47368cfSchristos 				terminal_sepline(curp.outdata);
56137ef69edSchristos 			}
5626167eca2Schristos 		} else
5636167eca2Schristos 			mandoc_msg(MANDOCERR_FILE, 0, 0,
5646167eca2Schristos 			    "%s: %s", thisarg, strerror(errno));
565fec65c98Schristos 
5666167eca2Schristos 		if (curp.wstop && mandoc_msg_getrc() != MANDOCLEVEL_OK)
5670a84adc5Sjoerg 			break;
568fec65c98Schristos 
569fec65c98Schristos 		if (resp != NULL)
570fec65c98Schristos 			resp++;
571fec65c98Schristos 		else
572fec65c98Schristos 			argv++;
573fec65c98Schristos 		if (--argc)
574fec65c98Schristos 			mparse_reset(curp.mp);
5754154958bSjoerg 	}
57614e7489eSchristos 	if (startdir != -1) {
57714e7489eSchristos 		(void)fchdir(startdir);
57814e7489eSchristos 		close(startdir);
57914e7489eSchristos 	}
5804154958bSjoerg 
581f47368cfSchristos 	if (curp.outdata != NULL) {
582f47368cfSchristos 		switch (curp.outtype) {
583f47368cfSchristos 		case OUTT_HTML:
584f47368cfSchristos 			html_free(curp.outdata);
585f47368cfSchristos 			break;
586f47368cfSchristos 		case OUTT_UTF8:
587f47368cfSchristos 		case OUTT_LOCALE:
588f47368cfSchristos 		case OUTT_ASCII:
589f47368cfSchristos 			ascii_free(curp.outdata);
590f47368cfSchristos 			break;
591f47368cfSchristos 		case OUTT_PDF:
592f47368cfSchristos 		case OUTT_PS:
593f47368cfSchristos 			pspdf_free(curp.outdata);
594f47368cfSchristos 			break;
595f47368cfSchristos 		default:
596f47368cfSchristos 			break;
597f47368cfSchristos 		}
598f47368cfSchristos 	}
59914e7489eSchristos 	mandoc_xr_free();
60048741257Sjoerg 	mparse_free(curp.mp);
601f47368cfSchristos 	mchars_free();
602fec65c98Schristos 
603fec65c98Schristos out:
604fec65c98Schristos 	if (search.argmode != ARG_FILE) {
605f47368cfSchristos 		manconf_free(&conf);
606fec65c98Schristos 		mansearch_free(res, sz);
607fec65c98Schristos 	}
608fec65c98Schristos 
60914e7489eSchristos 	free(curp.os_s);
6104154958bSjoerg 
611fec65c98Schristos 	/*
612f47368cfSchristos 	 * When using a pager, finish writing both temporary files,
613f47368cfSchristos 	 * fork it, wait for the user to close it, and clean up.
614fec65c98Schristos 	 */
615fec65c98Schristos 
616f47368cfSchristos 	if (tag_files != NULL) {
617fec65c98Schristos 		fclose(stdout);
618f47368cfSchristos 		tag_write();
619f47368cfSchristos 		man_pgid = getpgid(0);
620f47368cfSchristos 		tag_files->tcpgid = man_pgid == getpid() ?
621f47368cfSchristos 		    getpgid(getppid()) : man_pgid;
622f47368cfSchristos 		pager_pid = 0;
623f47368cfSchristos 		signum = SIGSTOP;
624f47368cfSchristos 		for (;;) {
625f47368cfSchristos 
626f47368cfSchristos 			/* Stop here until moved to the foreground. */
627f47368cfSchristos 
62837ef69edSchristos 			tc_pgid = tcgetpgrp(tag_files->ofd);
629f47368cfSchristos 			if (tc_pgid != man_pgid) {
630f47368cfSchristos 				if (tc_pgid == pager_pid) {
63137ef69edSchristos 					(void)tcsetpgrp(tag_files->ofd,
632f47368cfSchristos 					    man_pgid);
633f47368cfSchristos 					if (signum == SIGTTIN)
634f47368cfSchristos 						continue;
635f47368cfSchristos 				} else
636f47368cfSchristos 					tag_files->tcpgid = tc_pgid;
637f47368cfSchristos 				kill(0, signum);
638f47368cfSchristos 				continue;
639fec65c98Schristos 			}
640fec65c98Schristos 
641f47368cfSchristos 			/* Once in the foreground, activate the pager. */
642f47368cfSchristos 
643f47368cfSchristos 			if (pager_pid) {
64437ef69edSchristos 				(void)tcsetpgrp(tag_files->ofd, pager_pid);
645f47368cfSchristos 				kill(pager_pid, SIGCONT);
646f47368cfSchristos 			} else
647f47368cfSchristos 				pager_pid = spawn_pager(tag_files);
648f47368cfSchristos 
649f47368cfSchristos 			/* Wait for the pager to stop or exit. */
650f47368cfSchristos 
651f47368cfSchristos 			while ((pid = waitpid(pager_pid, &status,
652f47368cfSchristos 			    WUNTRACED)) == -1 && errno == EINTR)
653f47368cfSchristos 				continue;
654f47368cfSchristos 
655f47368cfSchristos 			if (pid == -1) {
656f47368cfSchristos 				warn("wait");
6576167eca2Schristos 				mandoc_msg_setrc(MANDOCLEVEL_SYSERR);
658f47368cfSchristos 				break;
659f47368cfSchristos 			}
660f47368cfSchristos 			if (!WIFSTOPPED(status))
661f47368cfSchristos 				break;
662f47368cfSchristos 
663f47368cfSchristos 			signum = WSTOPSIG(status);
664f47368cfSchristos 		}
665f47368cfSchristos 		tag_unlink();
666f47368cfSchristos 	}
6676167eca2Schristos 	return (int)mandoc_msg_getrc();
6684154958bSjoerg }
6694154958bSjoerg 
6703514411fSjoerg static void
usage(enum argmode argmode)671fec65c98Schristos usage(enum argmode argmode)
6724154958bSjoerg {
6734154958bSjoerg 
674fec65c98Schristos 	switch (argmode) {
675fec65c98Schristos 	case ARG_FILE:
67614e7489eSchristos 		fputs("usage: mandoc [-ac] [-I os=name] "
67714e7489eSchristos 		    "[-K encoding] [-mdoc | -man] [-O options]\n"
678fec65c98Schristos 		    "\t      [-T output] [-W level] [file ...]\n", stderr);
679fec65c98Schristos 		break;
680fec65c98Schristos 	case ARG_NAME:
68114e7489eSchristos 		fputs("usage: man [-acfhklw] [-C file] [-M path] "
68214e7489eSchristos 		    "[-m path] [-S subsection]\n"
68314e7489eSchristos 		    "\t   [[-s] section] name ...\n", stderr);
684fec65c98Schristos 		break;
685fec65c98Schristos 	case ARG_WORD:
68614e7489eSchristos 		fputs("usage: whatis [-afk] [-C file] "
687fec65c98Schristos 		    "[-M path] [-m path] [-O outkey] [-S arch]\n"
688fec65c98Schristos 		    "\t      [-s section] name ...\n", stderr);
689fec65c98Schristos 		break;
690fec65c98Schristos 	case ARG_EXPR:
69114e7489eSchristos 		fputs("usage: apropos [-afk] [-C file] "
692fec65c98Schristos 		    "[-M path] [-m path] [-O outkey] [-S arch]\n"
693fec65c98Schristos 		    "\t       [-s section] expression ...\n", stderr);
694fec65c98Schristos 		break;
6954154958bSjoerg 	}
696c0d9444aSjoerg 	exit((int)MANDOCLEVEL_BADARG);
6974154958bSjoerg }
6984154958bSjoerg 
699fec65c98Schristos static int
fs_lookup(const struct manpaths * paths,size_t ipath,const char * sec,const char * arch,const char * name,struct manpage ** res,size_t * ressz)700fec65c98Schristos fs_lookup(const struct manpaths *paths, size_t ipath,
701fec65c98Schristos 	const char *sec, const char *arch, const char *name,
702fec65c98Schristos 	struct manpage **res, size_t *ressz)
703fec65c98Schristos {
704fec65c98Schristos 	glob_t		 globinfo;
705fec65c98Schristos 	struct manpage	*page;
706fec65c98Schristos 	char		*file;
70737ef69edSchristos 	int		 globres;
70837ef69edSchristos 	enum form	 form;
709fec65c98Schristos 
710fec65c98Schristos 	form = FORM_SRC;
711fec65c98Schristos 	mandoc_asprintf(&file, "%s/man%s/%s.%s",
712fec65c98Schristos 	    paths->paths[ipath], sec, name, sec);
713fec65c98Schristos 	if (access(file, R_OK) != -1)
714fec65c98Schristos 		goto found;
715fec65c98Schristos 	free(file);
716fec65c98Schristos 
717fec65c98Schristos 	mandoc_asprintf(&file, "%s/cat%s/%s.0",
718fec65c98Schristos 	    paths->paths[ipath], sec, name);
719fec65c98Schristos 	if (access(file, R_OK) != -1) {
720fec65c98Schristos 		form = FORM_CAT;
721fec65c98Schristos 		goto found;
722fec65c98Schristos 	}
723fec65c98Schristos 	free(file);
724fec65c98Schristos 
725fec65c98Schristos 	if (arch != NULL) {
726fec65c98Schristos 		mandoc_asprintf(&file, "%s/man%s/%s/%s.%s",
727fec65c98Schristos 		    paths->paths[ipath], sec, arch, name, sec);
728fec65c98Schristos 		if (access(file, R_OK) != -1)
729fec65c98Schristos 			goto found;
730fec65c98Schristos 		free(file);
731fec65c98Schristos 	}
732fec65c98Schristos 
733f47368cfSchristos 	mandoc_asprintf(&file, "%s/man%s/%s.[01-9]*",
734fec65c98Schristos 	    paths->paths[ipath], sec, name);
735fec65c98Schristos 	globres = glob(file, 0, NULL, &globinfo);
736fec65c98Schristos 	if (globres != 0 && globres != GLOB_NOMATCH)
737f47368cfSchristos 		warn("%s: glob", file);
738fec65c98Schristos 	free(file);
739fec65c98Schristos 	if (globres == 0)
740fec65c98Schristos 		file = mandoc_strdup(*globinfo.gl_pathv);
741fec65c98Schristos 	globfree(&globinfo);
74214e7489eSchristos 	if (globres == 0)
74314e7489eSchristos 		goto found;
74414e7489eSchristos 	if (res != NULL || ipath + 1 != paths->sz)
745f47368cfSchristos 		return 0;
746fec65c98Schristos 
74714e7489eSchristos 	mandoc_asprintf(&file, "%s.%s", name, sec);
74814e7489eSchristos 	globres = access(file, R_OK);
74914e7489eSchristos 	free(file);
75014e7489eSchristos 	return globres != -1;
75114e7489eSchristos 
752fec65c98Schristos found:
753*2fd1c0acSwiz #if !defined(__NetBSD__)
754f47368cfSchristos 	warnx("outdated mandoc.db lacks %s(%s) entry, run %s %s",
755f47368cfSchristos 	    name, sec, BINM_MAKEWHATIS, paths->paths[ipath]);
756*2fd1c0acSwiz #endif
75714e7489eSchristos 	if (res == NULL) {
75814e7489eSchristos 		free(file);
75914e7489eSchristos 		return 1;
76014e7489eSchristos 	}
761fec65c98Schristos 	*res = mandoc_reallocarray(*res, ++*ressz, sizeof(struct manpage));
762fec65c98Schristos 	page = *res + (*ressz - 1);
763fec65c98Schristos 	page->file = file;
764fec65c98Schristos 	page->names = NULL;
765fec65c98Schristos 	page->output = NULL;
766fec65c98Schristos 	page->ipath = ipath;
767fec65c98Schristos 	page->sec = (*sec >= '1' && *sec <= '9') ? *sec - '1' + 1 : 10;
768fec65c98Schristos 	page->form = form;
769f47368cfSchristos 	return 1;
770fec65c98Schristos }
771fec65c98Schristos 
77214e7489eSchristos static int
fs_search(const struct mansearch * cfg,const struct manpaths * paths,int argc,char ** argv,struct manpage ** res,size_t * ressz)773fec65c98Schristos fs_search(const struct mansearch *cfg, const struct manpaths *paths,
774fec65c98Schristos 	int argc, char **argv, struct manpage **res, size_t *ressz)
775fec65c98Schristos {
776fec65c98Schristos 	const char *const sections[] =
777f47368cfSchristos 	    {"1", "8", "6", "2", "3", "5", "7", "4", "9", "3p"};
778fec65c98Schristos 	const size_t nsec = sizeof(sections)/sizeof(sections[0]);
779fec65c98Schristos 
780fec65c98Schristos 	size_t		 ipath, isec, lastsz;
781fec65c98Schristos 
782fec65c98Schristos 	assert(cfg->argmode == ARG_NAME);
783fec65c98Schristos 
78414e7489eSchristos 	if (res != NULL)
785fec65c98Schristos 		*res = NULL;
786fec65c98Schristos 	*ressz = lastsz = 0;
787fec65c98Schristos 	while (argc) {
788fec65c98Schristos 		for (ipath = 0; ipath < paths->sz; ipath++) {
789fec65c98Schristos 			if (cfg->sec != NULL) {
790fec65c98Schristos 				if (fs_lookup(paths, ipath, cfg->sec,
791fec65c98Schristos 				    cfg->arch, *argv, res, ressz) &&
792fec65c98Schristos 				    cfg->firstmatch)
79314e7489eSchristos 					return 1;
794fec65c98Schristos 			} else for (isec = 0; isec < nsec; isec++)
795fec65c98Schristos 				if (fs_lookup(paths, ipath, sections[isec],
796fec65c98Schristos 				    cfg->arch, *argv, res, ressz) &&
797fec65c98Schristos 				    cfg->firstmatch)
79814e7489eSchristos 					return 1;
799fec65c98Schristos 		}
80014e7489eSchristos 		if (res != NULL && *ressz == lastsz &&
8016167eca2Schristos 		    strchr(*argv, '/') == NULL) {
8026167eca2Schristos 			if (cfg->arch != NULL &&
8036167eca2Schristos 			    arch_valid(cfg->arch, OSENUM) == 0)
8046167eca2Schristos 				warnx("Unknown architecture \"%s\".",
8056167eca2Schristos 				    cfg->arch);
8066167eca2Schristos 			else if (cfg->sec == NULL)
8076167eca2Schristos 				warnx("No entry for %s in the manual.",
8086167eca2Schristos 				    *argv);
8096167eca2Schristos 			else
8106167eca2Schristos 				warnx("No entry for %s in section %s "
8116167eca2Schristos 				    "of the manual.", *argv, cfg->sec);
8126167eca2Schristos 		}
813fec65c98Schristos 		lastsz = *ressz;
814fec65c98Schristos 		argv++;
815fec65c98Schristos 		argc--;
816fec65c98Schristos 	}
81714e7489eSchristos 	return 0;
818fec65c98Schristos }
819fec65c98Schristos 
820fec65c98Schristos static void
parse(struct curparse * curp,int fd,const char * file)821f47368cfSchristos parse(struct curparse *curp, int fd, const char *file)
8224154958bSjoerg {
8236167eca2Schristos 	struct roff_meta *meta;
8244154958bSjoerg 
82548741257Sjoerg 	/* Begin by parsing the file itself. */
826c0d9444aSjoerg 
82748741257Sjoerg 	assert(file);
828f47368cfSchristos 	assert(fd >= 0);
829c0d9444aSjoerg 
8306167eca2Schristos 	mparse_readfd(curp->mp, fd, file);
831f47368cfSchristos 	if (fd != STDIN_FILENO)
832f47368cfSchristos 		close(fd);
8334154958bSjoerg 
834c0d9444aSjoerg 	/*
83548741257Sjoerg 	 * With -Wstop and warnings or errors of at least the requested
83648741257Sjoerg 	 * level, do not produce output.
837c0d9444aSjoerg 	 */
838c0d9444aSjoerg 
8396167eca2Schristos 	if (curp->wstop && mandoc_msg_getrc() != MANDOCLEVEL_OK)
840f47368cfSchristos 		return;
8414154958bSjoerg 
84237ef69edSchristos 	if (curp->outdata == NULL)
84337ef69edSchristos 		outdata_alloc(curp);
8446167eca2Schristos 	else if (curp->outtype == OUTT_HTML)
8456167eca2Schristos 		html_reset(curp);
8464154958bSjoerg 
8476167eca2Schristos 	mandoc_xr_reset();
8486167eca2Schristos 	meta = mparse_result(curp->mp);
84937ef69edSchristos 
85037ef69edSchristos 	/* Execute the out device, if it exists. */
85137ef69edSchristos 
8526167eca2Schristos 	if (meta->macroset == MACROSET_MDOC) {
85337ef69edSchristos 		switch (curp->outtype) {
85437ef69edSchristos 		case OUTT_HTML:
8556167eca2Schristos 			html_mdoc(curp->outdata, meta);
85637ef69edSchristos 			break;
85737ef69edSchristos 		case OUTT_TREE:
8586167eca2Schristos 			tree_mdoc(curp->outdata, meta);
85937ef69edSchristos 			break;
86037ef69edSchristos 		case OUTT_MAN:
8616167eca2Schristos 			man_mdoc(curp->outdata, meta);
86237ef69edSchristos 			break;
86337ef69edSchristos 		case OUTT_PDF:
86437ef69edSchristos 		case OUTT_ASCII:
86537ef69edSchristos 		case OUTT_UTF8:
86637ef69edSchristos 		case OUTT_LOCALE:
86737ef69edSchristos 		case OUTT_PS:
8686167eca2Schristos 			terminal_mdoc(curp->outdata, meta);
86937ef69edSchristos 			break;
87014e7489eSchristos 		case OUTT_MARKDOWN:
8716167eca2Schristos 			markdown_mdoc(curp->outdata, meta);
87214e7489eSchristos 			break;
87337ef69edSchristos 		default:
87437ef69edSchristos 			break;
87537ef69edSchristos 		}
87637ef69edSchristos 	}
8776167eca2Schristos 	if (meta->macroset == MACROSET_MAN) {
87837ef69edSchristos 		switch (curp->outtype) {
87937ef69edSchristos 		case OUTT_HTML:
8806167eca2Schristos 			html_man(curp->outdata, meta);
88137ef69edSchristos 			break;
88237ef69edSchristos 		case OUTT_TREE:
8836167eca2Schristos 			tree_man(curp->outdata, meta);
88437ef69edSchristos 			break;
88537ef69edSchristos 		case OUTT_MAN:
8866167eca2Schristos 			mparse_copy(curp->mp);
88737ef69edSchristos 			break;
88837ef69edSchristos 		case OUTT_PDF:
88937ef69edSchristos 		case OUTT_ASCII:
89037ef69edSchristos 		case OUTT_UTF8:
89137ef69edSchristos 		case OUTT_LOCALE:
89237ef69edSchristos 		case OUTT_PS:
8936167eca2Schristos 			terminal_man(curp->outdata, meta);
89437ef69edSchristos 			break;
89537ef69edSchristos 		default:
89637ef69edSchristos 			break;
89737ef69edSchristos 		}
89837ef69edSchristos 	}
8996167eca2Schristos 	if (mandoc_msg_getmin() < MANDOCERR_STYLE)
9006167eca2Schristos 		check_xr();
90137ef69edSchristos }
90237ef69edSchristos 
90337ef69edSchristos static void
check_xr(void)9046167eca2Schristos check_xr(void)
90514e7489eSchristos {
90614e7489eSchristos 	static struct manpaths	 paths;
90714e7489eSchristos 	struct mansearch	 search;
90814e7489eSchristos 	struct mandoc_xr	*xr;
90914e7489eSchristos 	size_t			 sz;
91014e7489eSchristos 
91114e7489eSchristos 	if (paths.sz == 0)
91214e7489eSchristos 		manpath_base(&paths);
91314e7489eSchristos 
91414e7489eSchristos 	for (xr = mandoc_xr_get(); xr != NULL; xr = xr->next) {
91514e7489eSchristos 		if (xr->line == -1)
91614e7489eSchristos 			continue;
91714e7489eSchristos 		search.arch = NULL;
91814e7489eSchristos 		search.sec = xr->sec;
91914e7489eSchristos 		search.outkey = NULL;
92014e7489eSchristos 		search.argmode = ARG_NAME;
92114e7489eSchristos 		search.firstmatch = 1;
92214e7489eSchristos 		if (mansearch(&search, &paths, 1, &xr->name, NULL, &sz))
92314e7489eSchristos 			continue;
92414e7489eSchristos 		if (fs_search(&search, &paths, 1, &xr->name, NULL, &sz))
92514e7489eSchristos 			continue;
92614e7489eSchristos 		if (xr->count == 1)
9276167eca2Schristos 			mandoc_msg(MANDOCERR_XR_BAD, xr->line,
9286167eca2Schristos 			    xr->pos + 1, "Xr %s %s", xr->name, xr->sec);
92914e7489eSchristos 		else
9306167eca2Schristos 			mandoc_msg(MANDOCERR_XR_BAD, xr->line,
9316167eca2Schristos 			    xr->pos + 1, "Xr %s %s (%d times)",
93214e7489eSchristos 			    xr->name, xr->sec, xr->count);
93314e7489eSchristos 	}
93414e7489eSchristos }
93514e7489eSchristos 
93614e7489eSchristos static void
outdata_alloc(struct curparse * curp)93737ef69edSchristos outdata_alloc(struct curparse *curp)
93837ef69edSchristos {
9394154958bSjoerg 	switch (curp->outtype) {
940fec65c98Schristos 	case OUTT_HTML:
941f47368cfSchristos 		curp->outdata = html_alloc(curp->outopts);
942d5e63c8dSjoerg 		break;
943fec65c98Schristos 	case OUTT_UTF8:
944f47368cfSchristos 		curp->outdata = utf8_alloc(curp->outopts);
945c5f73b34Sjoerg 		break;
946fec65c98Schristos 	case OUTT_LOCALE:
947f47368cfSchristos 		curp->outdata = locale_alloc(curp->outopts);
9487574e07eSjoerg 		break;
949fec65c98Schristos 	case OUTT_ASCII:
950f47368cfSchristos 		curp->outdata = ascii_alloc(curp->outopts);
9517574e07eSjoerg 		break;
952fec65c98Schristos 	case OUTT_PDF:
953f47368cfSchristos 		curp->outdata = pdf_alloc(curp->outopts);
9547da9b934Sjoerg 		break;
955fec65c98Schristos 	case OUTT_PS:
956f47368cfSchristos 		curp->outdata = ps_alloc(curp->outopts);
9574154958bSjoerg 		break;
9587574e07eSjoerg 	default:
9597574e07eSjoerg 		break;
9604154958bSjoerg 	}
9614154958bSjoerg }
9624154958bSjoerg 
963f47368cfSchristos static void
passthrough(const char * file,int fd,int synopsis_only)964fec65c98Schristos passthrough(const char *file, int fd, int synopsis_only)
965fec65c98Schristos {
966fec65c98Schristos 	const char	 synb[] = "S\bSY\bYN\bNO\bOP\bPS\bSI\bIS\bS";
967fec65c98Schristos 	const char	 synr[] = "SYNOPSIS";
968fec65c98Schristos 
969fec65c98Schristos 	FILE		*stream;
970fec65c98Schristos 	const char	*syscall;
971f47368cfSchristos 	char		*line, *cp;
972f47368cfSchristos 	size_t		 linesz;
97337ef69edSchristos 	ssize_t		 len, written;
974fec65c98Schristos 	int		 print;
975fec65c98Schristos 
976f47368cfSchristos 	line = NULL;
977f47368cfSchristos 	linesz = 0;
978fec65c98Schristos 
97937ef69edSchristos 	if (fflush(stdout) == EOF) {
98037ef69edSchristos 		syscall = "fflush";
98137ef69edSchristos 		goto fail;
98237ef69edSchristos 	}
98337ef69edSchristos 
984fec65c98Schristos 	if ((stream = fdopen(fd, "r")) == NULL) {
985fec65c98Schristos 		close(fd);
986fec65c98Schristos 		syscall = "fdopen";
987fec65c98Schristos 		goto fail;
988fec65c98Schristos 	}
989fec65c98Schristos 
990fec65c98Schristos 	print = 0;
99137ef69edSchristos 	while ((len = getline(&line, &linesz, stream)) != -1) {
992f47368cfSchristos 		cp = line;
993fec65c98Schristos 		if (synopsis_only) {
994fec65c98Schristos 			if (print) {
995f47368cfSchristos 				if ( ! isspace((unsigned char)*cp))
996fec65c98Schristos 					goto done;
99737ef69edSchristos 				while (isspace((unsigned char)*cp)) {
998f47368cfSchristos 					cp++;
99937ef69edSchristos 					len--;
100037ef69edSchristos 				}
1001fec65c98Schristos 			} else {
1002f47368cfSchristos 				if (strcmp(cp, synb) == 0 ||
1003f47368cfSchristos 				    strcmp(cp, synr) == 0)
1004fec65c98Schristos 					print = 1;
1005fec65c98Schristos 				continue;
1006fec65c98Schristos 			}
1007fec65c98Schristos 		}
100837ef69edSchristos 		for (; len > 0; len -= written) {
100937ef69edSchristos 			if ((written = write(STDOUT_FILENO, cp, len)) != -1)
101037ef69edSchristos 				continue;
1011fec65c98Schristos 			fclose(stream);
101237ef69edSchristos 			syscall = "write";
1013fec65c98Schristos 			goto fail;
1014fec65c98Schristos 		}
1015fec65c98Schristos 	}
1016fec65c98Schristos 
1017fec65c98Schristos 	if (ferror(stream)) {
1018fec65c98Schristos 		fclose(stream);
1019f47368cfSchristos 		syscall = "getline";
1020fec65c98Schristos 		goto fail;
1021fec65c98Schristos 	}
1022fec65c98Schristos 
1023fec65c98Schristos done:
1024f47368cfSchristos 	free(line);
1025fec65c98Schristos 	fclose(stream);
1026f47368cfSchristos 	return;
1027fec65c98Schristos 
1028fec65c98Schristos fail:
1029f47368cfSchristos 	free(line);
1030f47368cfSchristos 	warn("%s: SYSERR: %s", file, syscall);
10316167eca2Schristos 	mandoc_msg_setrc(MANDOCLEVEL_SYSERR);
1032fec65c98Schristos }
1033fec65c98Schristos 
10344154958bSjoerg static int
koptions(int * options,char * arg)1035fec65c98Schristos koptions(int *options, char *arg)
10364154958bSjoerg {
10374154958bSjoerg 
1038fec65c98Schristos 	if ( ! strcmp(arg, "utf-8")) {
1039fec65c98Schristos 		*options |=  MPARSE_UTF8;
1040fec65c98Schristos 		*options &= ~MPARSE_LATIN1;
1041fec65c98Schristos 	} else if ( ! strcmp(arg, "iso-8859-1")) {
1042fec65c98Schristos 		*options |=  MPARSE_LATIN1;
1043fec65c98Schristos 		*options &= ~MPARSE_UTF8;
1044fec65c98Schristos 	} else if ( ! strcmp(arg, "us-ascii")) {
1045fec65c98Schristos 		*options &= ~(MPARSE_UTF8 | MPARSE_LATIN1);
1046fec65c98Schristos 	} else {
1047f47368cfSchristos 		warnx("-K %s: Bad argument", arg);
1048f47368cfSchristos 		return 0;
1049fec65c98Schristos 	}
1050f47368cfSchristos 	return 1;
1051fec65c98Schristos }
1052fec65c98Schristos 
105314e7489eSchristos static void
moptions(int * options,char * arg)1054fec65c98Schristos moptions(int *options, char *arg)
1055fec65c98Schristos {
1056fec65c98Schristos 
1057fec65c98Schristos 	if (arg == NULL)
105814e7489eSchristos 		return;
105914e7489eSchristos 	if (strcmp(arg, "doc") == 0)
1060fec65c98Schristos 		*options |= MPARSE_MDOC;
106114e7489eSchristos 	else if (strcmp(arg, "an") == 0)
1062fec65c98Schristos 		*options |= MPARSE_MAN;
10634154958bSjoerg }
10644154958bSjoerg 
10654154958bSjoerg static int
toptions(struct curparse * curp,char * arg)106631e1f4e3Sjoerg toptions(struct curparse *curp, char *arg)
10674154958bSjoerg {
10684154958bSjoerg 
10694154958bSjoerg 	if (0 == strcmp(arg, "ascii"))
107031e1f4e3Sjoerg 		curp->outtype = OUTT_ASCII;
107131e1f4e3Sjoerg 	else if (0 == strcmp(arg, "lint")) {
107231e1f4e3Sjoerg 		curp->outtype = OUTT_LINT;
10736167eca2Schristos 		mandoc_msg_setoutfile(stdout);
10746167eca2Schristos 		mandoc_msg_setmin(MANDOCERR_BASE);
107548741257Sjoerg 	} else if (0 == strcmp(arg, "tree"))
107631e1f4e3Sjoerg 		curp->outtype = OUTT_TREE;
1077c5f73b34Sjoerg 	else if (0 == strcmp(arg, "man"))
1078c5f73b34Sjoerg 		curp->outtype = OUTT_MAN;
10794154958bSjoerg 	else if (0 == strcmp(arg, "html"))
108031e1f4e3Sjoerg 		curp->outtype = OUTT_HTML;
108114e7489eSchristos 	else if (0 == strcmp(arg, "markdown"))
108214e7489eSchristos 		curp->outtype = OUTT_MARKDOWN;
1083c5f73b34Sjoerg 	else if (0 == strcmp(arg, "utf8"))
1084c5f73b34Sjoerg 		curp->outtype = OUTT_UTF8;
1085c5f73b34Sjoerg 	else if (0 == strcmp(arg, "locale"))
1086c5f73b34Sjoerg 		curp->outtype = OUTT_LOCALE;
10877574e07eSjoerg 	else if (0 == strcmp(arg, "ps"))
10887574e07eSjoerg 		curp->outtype = OUTT_PS;
10897da9b934Sjoerg 	else if (0 == strcmp(arg, "pdf"))
10907da9b934Sjoerg 		curp->outtype = OUTT_PDF;
10914154958bSjoerg 	else {
1092f47368cfSchristos 		warnx("-T %s: Bad argument", arg);
1093f47368cfSchristos 		return 0;
10944154958bSjoerg 	}
10954154958bSjoerg 
1096f47368cfSchristos 	return 1;
10974154958bSjoerg }
10984154958bSjoerg 
10994154958bSjoerg static int
woptions(struct curparse * curp,char * arg)1100c0d9444aSjoerg woptions(struct curparse *curp, char *arg)
11014154958bSjoerg {
11024154958bSjoerg 	char		*v, *o;
110314e7489eSchristos 	const char	*toks[11];
11044154958bSjoerg 
1105c0d9444aSjoerg 	toks[0] = "stop";
1106c0d9444aSjoerg 	toks[1] = "all";
110714e7489eSchristos 	toks[2] = "base";
110814e7489eSchristos 	toks[3] = "style";
110914e7489eSchristos 	toks[4] = "warning";
111014e7489eSchristos 	toks[5] = "error";
111114e7489eSchristos 	toks[6] = "unsupp";
111214e7489eSchristos 	toks[7] = "fatal";
111314e7489eSchristos 	toks[8] = "openbsd";
111414e7489eSchristos 	toks[9] = "netbsd";
111514e7489eSchristos 	toks[10] = NULL;
11164154958bSjoerg 
11174154958bSjoerg 	while (*arg) {
11184154958bSjoerg 		o = arg;
111937ef69edSchristos 		switch (getsubopt(&arg, __UNCONST(toks), &v)) {
1120fec65c98Schristos 		case 0:
1121c0d9444aSjoerg 			curp->wstop = 1;
11224154958bSjoerg 			break;
1123fec65c98Schristos 		case 1:
1124fec65c98Schristos 		case 2:
11256167eca2Schristos 			mandoc_msg_setmin(MANDOCERR_BASE);
11264154958bSjoerg 			break;
1127fec65c98Schristos 		case 3:
11286167eca2Schristos 			mandoc_msg_setmin(MANDOCERR_STYLE);
11294154958bSjoerg 			break;
1130fec65c98Schristos 		case 4:
11316167eca2Schristos 			mandoc_msg_setmin(MANDOCERR_WARNING);
1132fec65c98Schristos 			break;
1133fec65c98Schristos 		case 5:
11346167eca2Schristos 			mandoc_msg_setmin(MANDOCERR_ERROR);
113514e7489eSchristos 			break;
113614e7489eSchristos 		case 6:
11376167eca2Schristos 			mandoc_msg_setmin(MANDOCERR_UNSUPP);
113814e7489eSchristos 			break;
113914e7489eSchristos 		case 7:
11406167eca2Schristos 			mandoc_msg_setmin(MANDOCERR_MAX);
114114e7489eSchristos 			break;
114214e7489eSchristos 		case 8:
11436167eca2Schristos 			mandoc_msg_setmin(MANDOCERR_BASE);
114414e7489eSchristos 			curp->os_e = MANDOC_OS_OPENBSD;
114514e7489eSchristos 			break;
114614e7489eSchristos 		case 9:
11476167eca2Schristos 			mandoc_msg_setmin(MANDOCERR_BASE);
114814e7489eSchristos 			curp->os_e = MANDOC_OS_NETBSD;
11493514411fSjoerg 			break;
11504154958bSjoerg 		default:
1151f47368cfSchristos 			warnx("-W %s: Bad argument", o);
1152f47368cfSchristos 			return 0;
11534154958bSjoerg 		}
11544154958bSjoerg 	}
1155f47368cfSchristos 	return 1;
11564154958bSjoerg }
11574154958bSjoerg 
1158fec65c98Schristos static pid_t
spawn_pager(struct tag_files * tag_files)1159f47368cfSchristos spawn_pager(struct tag_files *tag_files)
1160fec65c98Schristos {
1161f47368cfSchristos 	const struct timespec timeout = { 0, 100000000 };  /* 0.1s */
1162fec65c98Schristos #define MAX_PAGER_ARGS 16
1163fec65c98Schristos 	char		*argv[MAX_PAGER_ARGS];
1164fec65c98Schristos 	const char	*pager;
1165fec65c98Schristos 	char		*cp;
11666167eca2Schristos #if HAVE_LESS_T
1167f47368cfSchristos 	size_t		 cmdlen;
11686167eca2Schristos #endif
11696167eca2Schristos 	int		 argc, use_ofn;
1170fec65c98Schristos 	pid_t		 pager_pid;
1171fec65c98Schristos 
1172fec65c98Schristos 	pager = getenv("MANPAGER");
1173fec65c98Schristos 	if (pager == NULL || *pager == '\0')
1174fec65c98Schristos 		pager = getenv("PAGER");
1175fec65c98Schristos 	if (pager == NULL || *pager == '\0')
1176f47368cfSchristos 		pager = "more -s";
1177fec65c98Schristos 	cp = mandoc_strdup(pager);
1178fec65c98Schristos 
1179fec65c98Schristos 	/*
1180fec65c98Schristos 	 * Parse the pager command into words.
1181fec65c98Schristos 	 * Intentionally do not do anything fancy here.
1182fec65c98Schristos 	 */
1183fec65c98Schristos 
1184fec65c98Schristos 	argc = 0;
11856167eca2Schristos 	while (argc + 5 < MAX_PAGER_ARGS) {
1186fec65c98Schristos 		argv[argc++] = cp;
1187fec65c98Schristos 		cp = strchr(cp, ' ');
1188fec65c98Schristos 		if (cp == NULL)
1189fec65c98Schristos 			break;
1190fec65c98Schristos 		*cp++ = '\0';
1191fec65c98Schristos 		while (*cp == ' ')
1192fec65c98Schristos 			cp++;
1193fec65c98Schristos 		if (*cp == '\0')
1194fec65c98Schristos 			break;
1195fec65c98Schristos 	}
1196f47368cfSchristos 
1197f47368cfSchristos 	/* For less(1), use the tag file. */
1198f47368cfSchristos 
11996167eca2Schristos 	use_ofn = 1;
12006167eca2Schristos #if HAVE_LESS_T
1201f47368cfSchristos 	if ((cmdlen = strlen(argv[0])) >= 4) {
1202f47368cfSchristos 		cp = argv[0] + cmdlen - 4;
1203f47368cfSchristos 		if (strcmp(cp, "less") == 0) {
1204f47368cfSchristos 			argv[argc++] = mandoc_strdup("-T");
1205f47368cfSchristos 			argv[argc++] = tag_files->tfn;
12066167eca2Schristos 			if (tag_files->tagname != NULL) {
12076167eca2Schristos 				argv[argc++] = mandoc_strdup("-t");
12086167eca2Schristos 				argv[argc++] = tag_files->tagname;
12096167eca2Schristos 				use_ofn = 0;
1210f47368cfSchristos 			}
1211f47368cfSchristos 		}
12126167eca2Schristos 	}
12136167eca2Schristos #endif
12146167eca2Schristos 	if (use_ofn)
1215f47368cfSchristos 		argv[argc++] = tag_files->ofn;
1216fec65c98Schristos 	argv[argc] = NULL;
1217fec65c98Schristos 
1218f47368cfSchristos 	switch (pager_pid = fork()) {
1219f47368cfSchristos 	case -1:
1220f47368cfSchristos 		err((int)MANDOCLEVEL_SYSERR, "fork");
1221f47368cfSchristos 	case 0:
1222f47368cfSchristos 		break;
1223f47368cfSchristos 	default:
1224f47368cfSchristos 		(void)setpgid(pager_pid, 0);
122537ef69edSchristos 		(void)tcsetpgrp(tag_files->ofd, pager_pid);
1226f47368cfSchristos #if HAVE_PLEDGE
1227f47368cfSchristos 		if (pledge("stdio rpath tmppath tty proc", NULL) == -1)
1228f47368cfSchristos 			err((int)MANDOCLEVEL_SYSERR, "pledge");
1229f47368cfSchristos #endif
1230f47368cfSchristos 		tag_files->pager_pid = pager_pid;
1231f47368cfSchristos 		return pager_pid;
1232f47368cfSchristos 	}
1233f47368cfSchristos 
1234f47368cfSchristos 	/* The child process becomes the pager. */
1235f47368cfSchristos 
1236f47368cfSchristos 	if (dup2(tag_files->ofd, STDOUT_FILENO) == -1)
1237f47368cfSchristos 		err((int)MANDOCLEVEL_SYSERR, "pager stdout");
1238f47368cfSchristos 	close(tag_files->ofd);
123914e7489eSchristos 	assert(tag_files->tfd == -1);
1240f47368cfSchristos 
1241f47368cfSchristos 	/* Do not start the pager before controlling the terminal. */
1242f47368cfSchristos 
124337ef69edSchristos 	while (tcgetpgrp(STDOUT_FILENO) != getpid())
1244f47368cfSchristos 		nanosleep(&timeout, NULL);
1245fec65c98Schristos 
124680a820b0Schristos 	/*coverity[TAINTED_STRING]*/
1247fec65c98Schristos 	execvp(argv[0], argv);
1248f47368cfSchristos 	err((int)MANDOCLEVEL_SYSERR, "exec %s", argv[0]);
1249fec65c98Schristos }
1250