xref: /illumos-gate/usr/src/cmd/mandoc/main.c (revision c66b8046543352459a11a51501b628d1c98a8c44)
1*c66b8046SYuri Pankov /*	$Id: main.c,v 1.301 2017/07/26 10:21:55 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"
46*c66b8046SYuri Pankov #include "mandoc_xr.h"
47371584c2SYuri Pankov #include "roff.h"
4895c635efSGarrett D'Amore #include "mdoc.h"
4995c635efSGarrett D'Amore #include "man.h"
50371584c2SYuri Pankov #include "tag.h"
51371584c2SYuri Pankov #include "main.h"
52371584c2SYuri Pankov #include "manconf.h"
53260e9a87SYuri Pankov #include "mansearch.h"
5495c635efSGarrett D'Amore 
55260e9a87SYuri Pankov enum	outmode {
56260e9a87SYuri Pankov 	OUTMODE_DEF = 0,
57260e9a87SYuri Pankov 	OUTMODE_FLN,
58260e9a87SYuri Pankov 	OUTMODE_LST,
59260e9a87SYuri Pankov 	OUTMODE_ALL,
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 */
70*c66b8046SYuri Pankov 	OUTT_MARKDOWN,	/* -Tmarkdown */
7195c635efSGarrett D'Amore 	OUTT_LINT,	/* -Tlint */
7295c635efSGarrett D'Amore 	OUTT_PS,	/* -Tps */
7395c635efSGarrett D'Amore 	OUTT_PDF	/* -Tpdf */
7495c635efSGarrett D'Amore };
7595c635efSGarrett D'Amore 
7695c635efSGarrett D'Amore struct	curparse {
7795c635efSGarrett D'Amore 	struct mparse	 *mp;
78*c66b8046SYuri Pankov 	struct manoutput *outopts;	/* output options */
79*c66b8046SYuri Pankov 	void		 *outdata;	/* data for output */
80*c66b8046SYuri Pankov 	char		 *os_s;		/* operating system for display */
8195c635efSGarrett D'Amore 	int		  wstop;	/* stop after a file with a warning */
82*c66b8046SYuri Pankov 	enum mandocerr	  mmin;		/* ignore messages below this */
83*c66b8046SYuri Pankov 	enum mandoc_os	  os_e;		/* check base system conventions */
84260e9a87SYuri Pankov 	enum outt	  outtype;	/* which output to use */
8595c635efSGarrett D'Amore };
8695c635efSGarrett D'Amore 
87a5934736SYuri Pankov 
88a5934736SYuri Pankov int			  mandocdb(int, char *[]);
89a5934736SYuri Pankov 
90*c66b8046SYuri Pankov static	void		  check_xr(const char *);
91260e9a87SYuri Pankov static	int		  fs_lookup(const struct manpaths *,
92260e9a87SYuri Pankov 				size_t ipath, const char *,
93260e9a87SYuri Pankov 				const char *, const char *,
94260e9a87SYuri Pankov 				struct manpage **, size_t *);
95*c66b8046SYuri Pankov static	int		  fs_search(const struct mansearch *,
96260e9a87SYuri Pankov 				const struct manpaths *, int, char**,
97260e9a87SYuri Pankov 				struct manpage **, size_t *);
98260e9a87SYuri Pankov static	int		  koptions(int *, char *);
99*c66b8046SYuri Pankov static	void		  moptions(int *, char *);
10095c635efSGarrett D'Amore static	void		  mmsg(enum mandocerr, enum mandoclevel,
10195c635efSGarrett D'Amore 				const char *, int, int, const char *);
102a5934736SYuri Pankov static	void		  outdata_alloc(struct curparse *);
103371584c2SYuri Pankov static	void		  parse(struct curparse *, int, const char *);
104371584c2SYuri Pankov static	void		  passthrough(const char *, int, int);
105371584c2SYuri Pankov static	pid_t		  spawn_pager(struct tag_files *);
10695c635efSGarrett D'Amore static	int		  toptions(struct curparse *, char *);
107a40ea1a7SYuri Pankov static	void		  usage(enum argmode) __attribute__((__noreturn__));
10895c635efSGarrett D'Amore static	int		  woptions(struct curparse *, char *);
10995c635efSGarrett D'Amore 
110260e9a87SYuri Pankov static	const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
111260e9a87SYuri Pankov static	char		  help_arg[] = "help";
112260e9a87SYuri Pankov static	char		 *help_argv[] = {help_arg, NULL};
113371584c2SYuri Pankov static	enum mandoclevel  rc;
114*c66b8046SYuri Pankov static	FILE		 *mmsg_stream;
11595c635efSGarrett D'Amore 
116260e9a87SYuri Pankov 
11795c635efSGarrett D'Amore int
11895c635efSGarrett D'Amore main(int argc, char *argv[])
11995c635efSGarrett D'Amore {
120371584c2SYuri Pankov 	struct manconf	 conf;
121260e9a87SYuri Pankov 	struct mansearch search;
122a40ea1a7SYuri Pankov 	struct curparse	 curp;
123371584c2SYuri Pankov 	struct tag_files *tag_files;
124260e9a87SYuri Pankov 	struct manpage	*res, *resp;
125a40ea1a7SYuri Pankov 	const char	*progname, *sec, *thisarg;
126a40ea1a7SYuri Pankov 	char		*conf_file, *defpaths, *auxpaths;
127*c66b8046SYuri Pankov 	char		*oarg;
128a40ea1a7SYuri Pankov 	unsigned char	*uc;
129371584c2SYuri Pankov 	size_t		 i, sz;
130371584c2SYuri Pankov 	int		 prio, best_prio;
131260e9a87SYuri Pankov 	enum outmode	 outmode;
132260e9a87SYuri Pankov 	int		 fd;
133260e9a87SYuri Pankov 	int		 show_usage;
134260e9a87SYuri Pankov 	int		 options;
135371584c2SYuri Pankov 	int		 use_pager;
136371584c2SYuri Pankov 	int		 status, signum;
137260e9a87SYuri Pankov 	int		 c;
138371584c2SYuri Pankov 	pid_t		 pager_pid, tc_pgid, man_pgid, pid;
13995c635efSGarrett D'Amore 
140371584c2SYuri Pankov #if HAVE_PROGNAME
141371584c2SYuri Pankov 	progname = getprogname();
142371584c2SYuri Pankov #else
143260e9a87SYuri Pankov 	if (argc < 1)
144371584c2SYuri Pankov 		progname = mandoc_strdup("mandoc");
145260e9a87SYuri Pankov 	else if ((progname = strrchr(argv[0], '/')) == NULL)
14695c635efSGarrett D'Amore 		progname = argv[0];
14795c635efSGarrett D'Amore 	else
14895c635efSGarrett D'Amore 		++progname;
149371584c2SYuri Pankov 	setprogname(progname);
150371584c2SYuri Pankov #endif
15195c635efSGarrett D'Amore 
152371584c2SYuri Pankov 	if (strncmp(progname, "mandocdb", 8) == 0 ||
153371584c2SYuri Pankov 	    strcmp(progname, BINM_MAKEWHATIS) == 0)
154371584c2SYuri Pankov 		return mandocdb(argc, argv);
155371584c2SYuri Pankov 
156371584c2SYuri Pankov #if HAVE_PLEDGE
157*c66b8046SYuri Pankov 	if (pledge("stdio rpath tmppath tty proc exec", NULL) == -1)
158371584c2SYuri Pankov 		err((int)MANDOCLEVEL_SYSERR, "pledge");
159371584c2SYuri Pankov #endif
160371584c2SYuri Pankov 
161371584c2SYuri Pankov #if HAVE_SANDBOX_INIT
162371584c2SYuri Pankov 	if (sandbox_init(kSBXProfileNoInternet, SANDBOX_NAMED, NULL) == -1)
163371584c2SYuri Pankov 		errx((int)MANDOCLEVEL_SYSERR, "sandbox_init");
164260e9a87SYuri Pankov #endif
165260e9a87SYuri Pankov 
166260e9a87SYuri Pankov 	/* Search options. */
16795c635efSGarrett D'Amore 
168371584c2SYuri Pankov 	memset(&conf, 0, sizeof(conf));
169260e9a87SYuri Pankov 	conf_file = defpaths = NULL;
170260e9a87SYuri Pankov 	auxpaths = NULL;
171260e9a87SYuri Pankov 
172260e9a87SYuri Pankov 	memset(&search, 0, sizeof(struct mansearch));
173260e9a87SYuri Pankov 	search.outkey = "Nd";
174a40ea1a7SYuri Pankov 	oarg = NULL;
175260e9a87SYuri Pankov 
176260e9a87SYuri Pankov 	if (strcmp(progname, BINM_MAN) == 0)
177260e9a87SYuri Pankov 		search.argmode = ARG_NAME;
178260e9a87SYuri Pankov 	else if (strcmp(progname, BINM_APROPOS) == 0)
179260e9a87SYuri Pankov 		search.argmode = ARG_EXPR;
180260e9a87SYuri Pankov 	else if (strcmp(progname, BINM_WHATIS) == 0)
181260e9a87SYuri Pankov 		search.argmode = ARG_WORD;
182260e9a87SYuri Pankov 	else if (strncmp(progname, "help", 4) == 0)
183260e9a87SYuri Pankov 		search.argmode = ARG_NAME;
184260e9a87SYuri Pankov 	else
185260e9a87SYuri Pankov 		search.argmode = ARG_FILE;
186260e9a87SYuri Pankov 
187260e9a87SYuri Pankov 	/* Parser and formatter options. */
188260e9a87SYuri Pankov 
189260e9a87SYuri Pankov 	memset(&curp, 0, sizeof(struct curparse));
190260e9a87SYuri Pankov 	curp.outtype = OUTT_LOCALE;
191*c66b8046SYuri Pankov 	curp.mmin = MANDOCERR_MAX;
192371584c2SYuri Pankov 	curp.outopts = &conf.output;
193260e9a87SYuri Pankov 	options = MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1;
194*c66b8046SYuri Pankov 	mmsg_stream = stderr;
19595c635efSGarrett D'Amore 
196371584c2SYuri Pankov 	use_pager = 1;
197371584c2SYuri Pankov 	tag_files = NULL;
198260e9a87SYuri Pankov 	show_usage = 0;
199260e9a87SYuri Pankov 	outmode = OUTMODE_DEF;
200260e9a87SYuri Pankov 
201*c66b8046SYuri Pankov 	while ((c = getopt(argc, argv,
202*c66b8046SYuri Pankov 	    "aC:cfhI:iK:klM:m:O:S:s:T:VW:w")) != -1) {
203*c66b8046SYuri Pankov 		if (c == 'i' && search.argmode == ARG_EXPR) {
204*c66b8046SYuri Pankov 			optind--;
205*c66b8046SYuri Pankov 			break;
206*c66b8046SYuri Pankov 		}
20795c635efSGarrett D'Amore 		switch (c) {
208260e9a87SYuri Pankov 		case 'a':
209260e9a87SYuri Pankov 			outmode = OUTMODE_ALL;
210260e9a87SYuri Pankov 			break;
211260e9a87SYuri Pankov 		case 'C':
212260e9a87SYuri Pankov 			conf_file = optarg;
213260e9a87SYuri Pankov 			break;
214260e9a87SYuri Pankov 		case 'c':
215371584c2SYuri Pankov 			use_pager = 0;
216260e9a87SYuri Pankov 			break;
217260e9a87SYuri Pankov 		case 'f':
218260e9a87SYuri Pankov 			search.argmode = ARG_WORD;
219260e9a87SYuri Pankov 			break;
220260e9a87SYuri Pankov 		case 'h':
221371584c2SYuri Pankov 			conf.output.synopsisonly = 1;
222371584c2SYuri Pankov 			use_pager = 0;
223260e9a87SYuri Pankov 			outmode = OUTMODE_ALL;
224260e9a87SYuri Pankov 			break;
225260e9a87SYuri Pankov 		case 'I':
226698f87a4SGarrett D'Amore 			if (strncmp(optarg, "os=", 3)) {
227371584c2SYuri Pankov 				warnx("-I %s: Bad argument", optarg);
228371584c2SYuri Pankov 				return (int)MANDOCLEVEL_BADARG;
229698f87a4SGarrett D'Amore 			}
230*c66b8046SYuri Pankov 			if (curp.os_s != NULL) {
231371584c2SYuri Pankov 				warnx("-I %s: Duplicate argument", optarg);
232371584c2SYuri Pankov 				return (int)MANDOCLEVEL_BADARG;
233698f87a4SGarrett D'Amore 			}
234*c66b8046SYuri Pankov 			curp.os_s = mandoc_strdup(optarg + 3);
235260e9a87SYuri Pankov 			break;
236260e9a87SYuri Pankov 		case 'K':
237260e9a87SYuri Pankov 			if ( ! koptions(&options, optarg))
238371584c2SYuri Pankov 				return (int)MANDOCLEVEL_BADARG;
23995c635efSGarrett D'Amore 			break;
240260e9a87SYuri Pankov 		case 'k':
241260e9a87SYuri Pankov 			search.argmode = ARG_EXPR;
242260e9a87SYuri Pankov 			break;
243260e9a87SYuri Pankov 		case 'l':
244260e9a87SYuri Pankov 			search.argmode = ARG_FILE;
245260e9a87SYuri Pankov 			outmode = OUTMODE_ALL;
246260e9a87SYuri Pankov 			break;
247260e9a87SYuri Pankov 		case 'M':
248260e9a87SYuri Pankov 			defpaths = optarg;
249260e9a87SYuri Pankov 			break;
250260e9a87SYuri Pankov 		case 'm':
251260e9a87SYuri Pankov 			auxpaths = optarg;
252260e9a87SYuri Pankov 			break;
253260e9a87SYuri Pankov 		case 'O':
254a40ea1a7SYuri Pankov 			oarg = optarg;
25595c635efSGarrett D'Amore 			break;
256260e9a87SYuri Pankov 		case 'S':
257260e9a87SYuri Pankov 			search.arch = optarg;
258260e9a87SYuri Pankov 			break;
259260e9a87SYuri Pankov 		case 's':
260260e9a87SYuri Pankov 			search.sec = optarg;
261260e9a87SYuri Pankov 			break;
262260e9a87SYuri Pankov 		case 'T':
26395c635efSGarrett D'Amore 			if ( ! toptions(&curp, optarg))
264371584c2SYuri Pankov 				return (int)MANDOCLEVEL_BADARG;
26595c635efSGarrett D'Amore 			break;
266260e9a87SYuri Pankov 		case 'W':
26795c635efSGarrett D'Amore 			if ( ! woptions(&curp, optarg))
268371584c2SYuri Pankov 				return (int)MANDOCLEVEL_BADARG;
26995c635efSGarrett D'Amore 			break;
270260e9a87SYuri Pankov 		case 'w':
271260e9a87SYuri Pankov 			outmode = OUTMODE_FLN;
272260e9a87SYuri Pankov 			break;
27395c635efSGarrett D'Amore 		default:
274260e9a87SYuri Pankov 			show_usage = 1;
275260e9a87SYuri Pankov 			break;
27695c635efSGarrett D'Amore 		}
277260e9a87SYuri Pankov 	}
278260e9a87SYuri Pankov 
279260e9a87SYuri Pankov 	if (show_usage)
280260e9a87SYuri Pankov 		usage(search.argmode);
28195c635efSGarrett D'Amore 
282260e9a87SYuri Pankov 	/* Postprocess options. */
283260e9a87SYuri Pankov 
284260e9a87SYuri Pankov 	if (outmode == OUTMODE_DEF) {
285260e9a87SYuri Pankov 		switch (search.argmode) {
286260e9a87SYuri Pankov 		case ARG_FILE:
287260e9a87SYuri Pankov 			outmode = OUTMODE_ALL;
288371584c2SYuri Pankov 			use_pager = 0;
289260e9a87SYuri Pankov 			break;
290260e9a87SYuri Pankov 		case ARG_NAME:
291260e9a87SYuri Pankov 			outmode = OUTMODE_ONE;
292260e9a87SYuri Pankov 			break;
293260e9a87SYuri Pankov 		default:
294260e9a87SYuri Pankov 			outmode = OUTMODE_LST;
295260e9a87SYuri Pankov 			break;
296260e9a87SYuri Pankov 		}
297260e9a87SYuri Pankov 	}
298260e9a87SYuri Pankov 
299a40ea1a7SYuri Pankov 	if (oarg != NULL) {
300a40ea1a7SYuri Pankov 		if (outmode == OUTMODE_LST)
301a40ea1a7SYuri Pankov 			search.outkey = oarg;
302a40ea1a7SYuri Pankov 		else {
303a40ea1a7SYuri Pankov 			while (oarg != NULL) {
304a40ea1a7SYuri Pankov 				thisarg = oarg;
305a40ea1a7SYuri Pankov 				if (manconf_output(&conf.output,
306a40ea1a7SYuri Pankov 				    strsep(&oarg, ","), 0) == 0)
307a40ea1a7SYuri Pankov 					continue;
308a40ea1a7SYuri Pankov 				warnx("-O %s: Bad argument", thisarg);
309a40ea1a7SYuri Pankov 				return (int)MANDOCLEVEL_BADARG;
310a40ea1a7SYuri Pankov 			}
311a40ea1a7SYuri Pankov 		}
312a40ea1a7SYuri Pankov 	}
313a40ea1a7SYuri Pankov 
314371584c2SYuri Pankov 	if (outmode == OUTMODE_FLN ||
315371584c2SYuri Pankov 	    outmode == OUTMODE_LST ||
316371584c2SYuri Pankov 	    !isatty(STDOUT_FILENO))
317371584c2SYuri Pankov 		use_pager = 0;
318371584c2SYuri Pankov 
319371584c2SYuri Pankov #if HAVE_PLEDGE
320371584c2SYuri Pankov 	if (!use_pager)
321*c66b8046SYuri Pankov 		if (pledge("stdio rpath", NULL) == -1)
322371584c2SYuri Pankov 			err((int)MANDOCLEVEL_SYSERR, "pledge");
323371584c2SYuri Pankov #endif
324371584c2SYuri Pankov 
325260e9a87SYuri Pankov 	/* Parse arguments. */
326260e9a87SYuri Pankov 
327260e9a87SYuri Pankov 	if (argc > 0) {
328260e9a87SYuri Pankov 		argc -= optind;
329260e9a87SYuri Pankov 		argv += optind;
330260e9a87SYuri Pankov 	}
331260e9a87SYuri Pankov 	resp = NULL;
332260e9a87SYuri Pankov 
333260e9a87SYuri Pankov 	/*
334260e9a87SYuri Pankov 	 * Quirks for help(1)
335260e9a87SYuri Pankov 	 * and for a man(1) section argument without -s.
336260e9a87SYuri Pankov 	 */
337260e9a87SYuri Pankov 
338260e9a87SYuri Pankov 	if (search.argmode == ARG_NAME) {
339260e9a87SYuri Pankov 		if (*progname == 'h') {
340260e9a87SYuri Pankov 			if (argc == 0) {
341260e9a87SYuri Pankov 				argv = help_argv;
342260e9a87SYuri Pankov 				argc = 1;
343260e9a87SYuri Pankov 			}
344260e9a87SYuri Pankov 		} else if (argc > 1 &&
345260e9a87SYuri Pankov 		    ((uc = (unsigned char *)argv[0]) != NULL) &&
346260e9a87SYuri Pankov 		    ((isdigit(uc[0]) && (uc[1] == '\0' ||
347260e9a87SYuri Pankov 		      (isalpha(uc[1]) && uc[2] == '\0'))) ||
348260e9a87SYuri Pankov 		     (uc[0] == 'n' && uc[1] == '\0'))) {
349260e9a87SYuri Pankov 			search.sec = (char *)uc;
350260e9a87SYuri Pankov 			argv++;
351260e9a87SYuri Pankov 			argc--;
352260e9a87SYuri Pankov 		}
353260e9a87SYuri Pankov 		if (search.arch == NULL)
354260e9a87SYuri Pankov 			search.arch = getenv("MACHINE");
355260e9a87SYuri Pankov #ifdef MACHINE
356260e9a87SYuri Pankov 		if (search.arch == NULL)
357260e9a87SYuri Pankov 			search.arch = MACHINE;
358260e9a87SYuri Pankov #endif
359260e9a87SYuri Pankov 	}
360260e9a87SYuri Pankov 
361260e9a87SYuri Pankov 	rc = MANDOCLEVEL_OK;
362260e9a87SYuri Pankov 
363260e9a87SYuri Pankov 	/* man(1), whatis(1), apropos(1) */
364260e9a87SYuri Pankov 
365260e9a87SYuri Pankov 	if (search.argmode != ARG_FILE) {
366260e9a87SYuri Pankov 		if (search.argmode == ARG_NAME &&
367260e9a87SYuri Pankov 		    outmode == OUTMODE_ONE)
368260e9a87SYuri Pankov 			search.firstmatch = 1;
369260e9a87SYuri Pankov 
370260e9a87SYuri Pankov 		/* Access the mandoc database. */
371260e9a87SYuri Pankov 
372371584c2SYuri Pankov 		manconf_parse(&conf, conf_file, defpaths, auxpaths);
373371584c2SYuri Pankov 		if ( ! mansearch(&search, &conf.manpath,
374371584c2SYuri Pankov 		    argc, argv, &res, &sz))
375260e9a87SYuri Pankov 			usage(search.argmode);
376260e9a87SYuri Pankov 
377371584c2SYuri Pankov 		if (sz == 0) {
378371584c2SYuri Pankov 			if (search.argmode == ARG_NAME)
379371584c2SYuri Pankov 				fs_search(&search, &conf.manpath,
380371584c2SYuri Pankov 				    argc, argv, &res, &sz);
381371584c2SYuri Pankov 			else
382371584c2SYuri Pankov 				warnx("nothing appropriate");
383371584c2SYuri Pankov 		}
384260e9a87SYuri Pankov 
385260e9a87SYuri Pankov 		if (sz == 0) {
386260e9a87SYuri Pankov 			rc = MANDOCLEVEL_BADARG;
387260e9a87SYuri Pankov 			goto out;
388260e9a87SYuri Pankov 		}
389260e9a87SYuri Pankov 
390260e9a87SYuri Pankov 		/*
391260e9a87SYuri Pankov 		 * For standard man(1) and -a output mode,
392260e9a87SYuri Pankov 		 * prepare for copying filename pointers
393260e9a87SYuri Pankov 		 * into the program parameter array.
394260e9a87SYuri Pankov 		 */
395260e9a87SYuri Pankov 
396260e9a87SYuri Pankov 		if (outmode == OUTMODE_ONE) {
397260e9a87SYuri Pankov 			argc = 1;
398371584c2SYuri Pankov 			best_prio = 20;
399260e9a87SYuri Pankov 		} else if (outmode == OUTMODE_ALL)
400260e9a87SYuri Pankov 			argc = (int)sz;
401260e9a87SYuri Pankov 
402260e9a87SYuri Pankov 		/* Iterate all matching manuals. */
403260e9a87SYuri Pankov 
404260e9a87SYuri Pankov 		resp = res;
405260e9a87SYuri Pankov 		for (i = 0; i < sz; i++) {
406260e9a87SYuri Pankov 			if (outmode == OUTMODE_FLN)
407260e9a87SYuri Pankov 				puts(res[i].file);
408260e9a87SYuri Pankov 			else if (outmode == OUTMODE_LST)
409260e9a87SYuri Pankov 				printf("%s - %s\n", res[i].names,
410260e9a87SYuri Pankov 				    res[i].output == NULL ? "" :
411260e9a87SYuri Pankov 				    res[i].output);
412260e9a87SYuri Pankov 			else if (outmode == OUTMODE_ONE) {
413260e9a87SYuri Pankov 				/* Search for the best section. */
414371584c2SYuri Pankov 				sec = res[i].file;
415371584c2SYuri Pankov 				sec += strcspn(sec, "123456789");
416371584c2SYuri Pankov 				if (sec[0] == '\0')
417260e9a87SYuri Pankov 					continue;
418371584c2SYuri Pankov 				prio = sec_prios[sec[0] - '1'];
419371584c2SYuri Pankov 				if (sec[1] != '/')
420371584c2SYuri Pankov 					prio += 10;
421260e9a87SYuri Pankov 				if (prio >= best_prio)
422260e9a87SYuri Pankov 					continue;
423260e9a87SYuri Pankov 				best_prio = prio;
424260e9a87SYuri Pankov 				resp = res + i;
425260e9a87SYuri Pankov 			}
426260e9a87SYuri Pankov 		}
427260e9a87SYuri Pankov 
428260e9a87SYuri Pankov 		/*
429260e9a87SYuri Pankov 		 * For man(1), -a and -i output mode, fall through
430260e9a87SYuri Pankov 		 * to the main mandoc(1) code iterating files
431260e9a87SYuri Pankov 		 * and running the parsers on each of them.
432260e9a87SYuri Pankov 		 */
433260e9a87SYuri Pankov 
434260e9a87SYuri Pankov 		if (outmode == OUTMODE_FLN || outmode == OUTMODE_LST)
435260e9a87SYuri Pankov 			goto out;
436260e9a87SYuri Pankov 	}
437260e9a87SYuri Pankov 
438260e9a87SYuri Pankov 	/* mandoc(1) */
439260e9a87SYuri Pankov 
440371584c2SYuri Pankov #if HAVE_PLEDGE
441371584c2SYuri Pankov 	if (use_pager) {
442371584c2SYuri Pankov 		if (pledge("stdio rpath tmppath tty proc exec", NULL) == -1)
443371584c2SYuri Pankov 			err((int)MANDOCLEVEL_SYSERR, "pledge");
444371584c2SYuri Pankov 	} else {
445371584c2SYuri Pankov 		if (pledge("stdio rpath", NULL) == -1)
446371584c2SYuri Pankov 			err((int)MANDOCLEVEL_SYSERR, "pledge");
447371584c2SYuri Pankov 	}
448371584c2SYuri Pankov #endif
449371584c2SYuri Pankov 
450*c66b8046SYuri Pankov 	if (search.argmode == ARG_FILE)
451*c66b8046SYuri Pankov 		moptions(&options, auxpaths);
452260e9a87SYuri Pankov 
453371584c2SYuri Pankov 	mchars_alloc();
454*c66b8046SYuri Pankov 	curp.mp = mparse_alloc(options, curp.mmin, mmsg,
455*c66b8046SYuri Pankov 	    curp.os_e, curp.os_s);
45695c635efSGarrett D'Amore 
45795c635efSGarrett D'Amore 	/*
45895c635efSGarrett D'Amore 	 * Conditionally start up the lookaside buffer before parsing.
45995c635efSGarrett D'Amore 	 */
46095c635efSGarrett D'Amore 	if (OUTT_MAN == curp.outtype)
46195c635efSGarrett D'Amore 		mparse_keep(curp.mp);
46295c635efSGarrett D'Amore 
463260e9a87SYuri Pankov 	if (argc < 1) {
464371584c2SYuri Pankov 		if (use_pager)
465371584c2SYuri Pankov 			tag_files = tag_init();
466371584c2SYuri Pankov 		parse(&curp, STDIN_FILENO, "<stdin>");
467260e9a87SYuri Pankov 	}
46895c635efSGarrett D'Amore 
469260e9a87SYuri Pankov 	while (argc > 0) {
470371584c2SYuri Pankov 		fd = mparse_open(curp.mp, resp != NULL ? resp->file : *argv);
471260e9a87SYuri Pankov 		if (fd != -1) {
472371584c2SYuri Pankov 			if (use_pager) {
473371584c2SYuri Pankov 				tag_files = tag_init();
474371584c2SYuri Pankov 				use_pager = 0;
475371584c2SYuri Pankov 			}
476260e9a87SYuri Pankov 
477260e9a87SYuri Pankov 			if (resp == NULL)
478371584c2SYuri Pankov 				parse(&curp, fd, *argv);
479a40ea1a7SYuri Pankov 			else if (resp->form == FORM_SRC) {
480260e9a87SYuri Pankov 				/* For .so only; ignore failure. */
481*c66b8046SYuri Pankov 				(void)chdir(conf.manpath.paths[resp->ipath]);
482371584c2SYuri Pankov 				parse(&curp, fd, resp->file);
483371584c2SYuri Pankov 			} else
484371584c2SYuri Pankov 				passthrough(resp->file, fd,
485371584c2SYuri Pankov 				    conf.output.synopsisonly);
486260e9a87SYuri Pankov 
487a5934736SYuri Pankov 			if (argc > 1 && curp.outtype <= OUTT_UTF8) {
488a5934736SYuri Pankov 				if (curp.outdata == NULL)
489a5934736SYuri Pankov 					outdata_alloc(&curp);
490371584c2SYuri Pankov 				terminal_sepline(curp.outdata);
491a5934736SYuri Pankov 			}
492371584c2SYuri Pankov 		} else if (rc < MANDOCLEVEL_ERROR)
493371584c2SYuri Pankov 			rc = MANDOCLEVEL_ERROR;
49495c635efSGarrett D'Amore 
49595c635efSGarrett D'Amore 		if (MANDOCLEVEL_OK != rc && curp.wstop)
49695c635efSGarrett D'Amore 			break;
497260e9a87SYuri Pankov 
498260e9a87SYuri Pankov 		if (resp != NULL)
499260e9a87SYuri Pankov 			resp++;
500260e9a87SYuri Pankov 		else
501260e9a87SYuri Pankov 			argv++;
502260e9a87SYuri Pankov 		if (--argc)
503260e9a87SYuri Pankov 			mparse_reset(curp.mp);
50495c635efSGarrett D'Amore 	}
50595c635efSGarrett D'Amore 
506371584c2SYuri Pankov 	if (curp.outdata != NULL) {
507371584c2SYuri Pankov 		switch (curp.outtype) {
508371584c2SYuri Pankov 		case OUTT_HTML:
509371584c2SYuri Pankov 			html_free(curp.outdata);
510371584c2SYuri Pankov 			break;
511371584c2SYuri Pankov 		case OUTT_UTF8:
512371584c2SYuri Pankov 		case OUTT_LOCALE:
513371584c2SYuri Pankov 		case OUTT_ASCII:
514371584c2SYuri Pankov 			ascii_free(curp.outdata);
515371584c2SYuri Pankov 			break;
516371584c2SYuri Pankov 		case OUTT_PDF:
517371584c2SYuri Pankov 		case OUTT_PS:
518371584c2SYuri Pankov 			pspdf_free(curp.outdata);
519371584c2SYuri Pankov 			break;
520371584c2SYuri Pankov 		default:
521371584c2SYuri Pankov 			break;
522371584c2SYuri Pankov 		}
523371584c2SYuri Pankov 	}
524*c66b8046SYuri Pankov 	mandoc_xr_free();
525260e9a87SYuri Pankov 	mparse_free(curp.mp);
526371584c2SYuri Pankov 	mchars_free();
527260e9a87SYuri Pankov 
528260e9a87SYuri Pankov out:
529260e9a87SYuri Pankov 	if (search.argmode != ARG_FILE) {
530371584c2SYuri Pankov 		manconf_free(&conf);
531260e9a87SYuri Pankov 		mansearch_free(res, sz);
532260e9a87SYuri Pankov 	}
533260e9a87SYuri Pankov 
534*c66b8046SYuri Pankov 	free(curp.os_s);
53595c635efSGarrett D'Amore 
536260e9a87SYuri Pankov 	/*
537371584c2SYuri Pankov 	 * When using a pager, finish writing both temporary files,
538371584c2SYuri Pankov 	 * fork it, wait for the user to close it, and clean up.
539260e9a87SYuri Pankov 	 */
540260e9a87SYuri Pankov 
541371584c2SYuri Pankov 	if (tag_files != NULL) {
542260e9a87SYuri Pankov 		fclose(stdout);
543371584c2SYuri Pankov 		tag_write();
544371584c2SYuri Pankov 		man_pgid = getpgid(0);
545371584c2SYuri Pankov 		tag_files->tcpgid = man_pgid == getpid() ?
546371584c2SYuri Pankov 		    getpgid(getppid()) : man_pgid;
547371584c2SYuri Pankov 		pager_pid = 0;
548371584c2SYuri Pankov 		signum = SIGSTOP;
549371584c2SYuri Pankov 		for (;;) {
550371584c2SYuri Pankov 
551371584c2SYuri Pankov 			/* Stop here until moved to the foreground. */
552371584c2SYuri Pankov 
553a40ea1a7SYuri Pankov 			tc_pgid = tcgetpgrp(tag_files->ofd);
554371584c2SYuri Pankov 			if (tc_pgid != man_pgid) {
555371584c2SYuri Pankov 				if (tc_pgid == pager_pid) {
556a40ea1a7SYuri Pankov 					(void)tcsetpgrp(tag_files->ofd,
557371584c2SYuri Pankov 					    man_pgid);
558371584c2SYuri Pankov 					if (signum == SIGTTIN)
559371584c2SYuri Pankov 						continue;
560371584c2SYuri Pankov 				} else
561371584c2SYuri Pankov 					tag_files->tcpgid = tc_pgid;
562371584c2SYuri Pankov 				kill(0, signum);
563371584c2SYuri Pankov 				continue;
564371584c2SYuri Pankov 			}
565371584c2SYuri Pankov 
566371584c2SYuri Pankov 			/* Once in the foreground, activate the pager. */
567371584c2SYuri Pankov 
568371584c2SYuri Pankov 			if (pager_pid) {
569a40ea1a7SYuri Pankov 				(void)tcsetpgrp(tag_files->ofd, pager_pid);
570371584c2SYuri Pankov 				kill(pager_pid, SIGCONT);
571371584c2SYuri Pankov 			} else
572371584c2SYuri Pankov 				pager_pid = spawn_pager(tag_files);
573371584c2SYuri Pankov 
574371584c2SYuri Pankov 			/* Wait for the pager to stop or exit. */
575371584c2SYuri Pankov 
576371584c2SYuri Pankov 			while ((pid = waitpid(pager_pid, &status,
577371584c2SYuri Pankov 			    WUNTRACED)) == -1 && errno == EINTR)
578371584c2SYuri Pankov 				continue;
579371584c2SYuri Pankov 
580371584c2SYuri Pankov 			if (pid == -1) {
581371584c2SYuri Pankov 				warn("wait");
582371584c2SYuri Pankov 				rc = MANDOCLEVEL_SYSERR;
583371584c2SYuri Pankov 				break;
584371584c2SYuri Pankov 			}
585371584c2SYuri Pankov 			if (!WIFSTOPPED(status))
586371584c2SYuri Pankov 				break;
587371584c2SYuri Pankov 
588371584c2SYuri Pankov 			signum = WSTOPSIG(status);
589371584c2SYuri Pankov 		}
590371584c2SYuri Pankov 		tag_unlink();
591260e9a87SYuri Pankov 	}
592260e9a87SYuri Pankov 
593371584c2SYuri Pankov 	return (int)rc;
59495c635efSGarrett D'Amore }
59595c635efSGarrett D'Amore 
59695c635efSGarrett D'Amore static void
597260e9a87SYuri Pankov usage(enum argmode argmode)
59895c635efSGarrett D'Amore {
59995c635efSGarrett D'Amore 
600260e9a87SYuri Pankov 	switch (argmode) {
601260e9a87SYuri Pankov 	case ARG_FILE:
602*c66b8046SYuri Pankov 		fputs("usage: mandoc [-ac] [-I os=name] "
603*c66b8046SYuri Pankov 		    "[-K encoding] [-mdoc | -man] [-O options]\n"
604371584c2SYuri Pankov 		    "\t      [-T output] [-W level] [file ...]\n", stderr);
605260e9a87SYuri Pankov 		break;
606260e9a87SYuri Pankov 	case ARG_NAME:
607*c66b8046SYuri Pankov 		fputs("usage: man [-acfhklw] [-C file] [-M path] "
608*c66b8046SYuri Pankov 		    "[-m path] [-S subsection]\n"
609*c66b8046SYuri Pankov 		    "\t   [[-s] section] name ...\n", stderr);
610260e9a87SYuri Pankov 		break;
611260e9a87SYuri Pankov 	case ARG_WORD:
612*c66b8046SYuri Pankov 		fputs("usage: whatis [-afk] [-C file] "
613260e9a87SYuri Pankov 		    "[-M path] [-m path] [-O outkey] [-S arch]\n"
614260e9a87SYuri Pankov 		    "\t      [-s section] name ...\n", stderr);
615260e9a87SYuri Pankov 		break;
616260e9a87SYuri Pankov 	case ARG_EXPR:
617*c66b8046SYuri Pankov 		fputs("usage: apropos [-afk] [-C file] "
618260e9a87SYuri Pankov 		    "[-M path] [-m path] [-O outkey] [-S arch]\n"
619260e9a87SYuri Pankov 		    "\t       [-s section] expression ...\n", stderr);
620260e9a87SYuri Pankov 		break;
621260e9a87SYuri Pankov 	}
622260e9a87SYuri Pankov 	exit((int)MANDOCLEVEL_BADARG);
62395c635efSGarrett D'Amore }
62495c635efSGarrett D'Amore 
625260e9a87SYuri Pankov static int
626260e9a87SYuri Pankov fs_lookup(const struct manpaths *paths, size_t ipath,
627260e9a87SYuri Pankov 	const char *sec, const char *arch, const char *name,
628260e9a87SYuri Pankov 	struct manpage **res, size_t *ressz)
62995c635efSGarrett D'Amore {
630260e9a87SYuri Pankov 	glob_t		 globinfo;
631260e9a87SYuri Pankov 	struct manpage	*page;
632260e9a87SYuri Pankov 	char		*file;
633a40ea1a7SYuri Pankov 	int		 globres;
634a40ea1a7SYuri Pankov 	enum form	 form;
635260e9a87SYuri Pankov 
636260e9a87SYuri Pankov 	form = FORM_SRC;
637260e9a87SYuri Pankov 	mandoc_asprintf(&file, "%s/man%s/%s.%s",
638260e9a87SYuri Pankov 	    paths->paths[ipath], sec, name, sec);
639260e9a87SYuri Pankov 	if (access(file, R_OK) != -1)
640260e9a87SYuri Pankov 		goto found;
641260e9a87SYuri Pankov 	free(file);
642260e9a87SYuri Pankov 
643260e9a87SYuri Pankov 	mandoc_asprintf(&file, "%s/cat%s/%s.0",
644260e9a87SYuri Pankov 	    paths->paths[ipath], sec, name);
645260e9a87SYuri Pankov 	if (access(file, R_OK) != -1) {
646260e9a87SYuri Pankov 		form = FORM_CAT;
647260e9a87SYuri Pankov 		goto found;
648260e9a87SYuri Pankov 	}
649260e9a87SYuri Pankov 	free(file);
650260e9a87SYuri Pankov 
651260e9a87SYuri Pankov 	if (arch != NULL) {
652260e9a87SYuri Pankov 		mandoc_asprintf(&file, "%s/man%s/%s/%s.%s",
653260e9a87SYuri Pankov 		    paths->paths[ipath], sec, arch, name, sec);
654260e9a87SYuri Pankov 		if (access(file, R_OK) != -1)
655260e9a87SYuri Pankov 			goto found;
656260e9a87SYuri Pankov 		free(file);
657260e9a87SYuri Pankov 	}
658260e9a87SYuri Pankov 
659371584c2SYuri Pankov 	mandoc_asprintf(&file, "%s/man%s/%s.[01-9]*",
660260e9a87SYuri Pankov 	    paths->paths[ipath], sec, name);
661260e9a87SYuri Pankov 	globres = glob(file, 0, NULL, &globinfo);
662260e9a87SYuri Pankov 	if (globres != 0 && globres != GLOB_NOMATCH)
663371584c2SYuri Pankov 		warn("%s: glob", file);
664260e9a87SYuri Pankov 	free(file);
665260e9a87SYuri Pankov 	if (globres == 0)
666260e9a87SYuri Pankov 		file = mandoc_strdup(*globinfo.gl_pathv);
667260e9a87SYuri Pankov 	globfree(&globinfo);
668*c66b8046SYuri Pankov 	if (globres == 0)
669*c66b8046SYuri Pankov 		goto found;
670*c66b8046SYuri Pankov 	if (res != NULL || ipath + 1 != paths->sz)
671371584c2SYuri Pankov 		return 0;
67295c635efSGarrett D'Amore 
673*c66b8046SYuri Pankov 	mandoc_asprintf(&file, "%s.%s", name, sec);
674*c66b8046SYuri Pankov 	globres = access(file, R_OK);
675*c66b8046SYuri Pankov 	free(file);
676*c66b8046SYuri Pankov 	return globres != -1;
677*c66b8046SYuri Pankov 
678260e9a87SYuri Pankov found:
679371584c2SYuri Pankov 	warnx("outdated mandoc.db lacks %s(%s) entry, run %s %s",
680371584c2SYuri Pankov 	    name, sec, BINM_MAKEWHATIS, paths->paths[ipath]);
681*c66b8046SYuri Pankov 	if (res == NULL) {
682*c66b8046SYuri Pankov 		free(file);
683*c66b8046SYuri Pankov 		return 1;
684*c66b8046SYuri Pankov 	}
685260e9a87SYuri Pankov 	*res = mandoc_reallocarray(*res, ++*ressz, sizeof(struct manpage));
686260e9a87SYuri Pankov 	page = *res + (*ressz - 1);
687260e9a87SYuri Pankov 	page->file = file;
688260e9a87SYuri Pankov 	page->names = NULL;
689260e9a87SYuri Pankov 	page->output = NULL;
690260e9a87SYuri Pankov 	page->ipath = ipath;
691260e9a87SYuri Pankov 	page->bits = NAME_FILE & NAME_MASK;
692260e9a87SYuri Pankov 	page->sec = (*sec >= '1' && *sec <= '9') ? *sec - '1' + 1 : 10;
693260e9a87SYuri Pankov 	page->form = form;
694371584c2SYuri Pankov 	return 1;
695260e9a87SYuri Pankov }
696260e9a87SYuri Pankov 
697*c66b8046SYuri Pankov static int
698260e9a87SYuri Pankov fs_search(const struct mansearch *cfg, const struct manpaths *paths,
699260e9a87SYuri Pankov 	int argc, char **argv, struct manpage **res, size_t *ressz)
700260e9a87SYuri Pankov {
701260e9a87SYuri Pankov 	const char *const sections[] =
702371584c2SYuri Pankov 	    {"1", "8", "6", "2", "3", "5", "7", "4", "9", "3p"};
703260e9a87SYuri Pankov 	const size_t nsec = sizeof(sections)/sizeof(sections[0]);
704260e9a87SYuri Pankov 
705260e9a87SYuri Pankov 	size_t		 ipath, isec, lastsz;
706260e9a87SYuri Pankov 
707260e9a87SYuri Pankov 	assert(cfg->argmode == ARG_NAME);
708260e9a87SYuri Pankov 
709*c66b8046SYuri Pankov 	if (res != NULL)
710*c66b8046SYuri Pankov 		*res = NULL;
711260e9a87SYuri Pankov 	*ressz = lastsz = 0;
712260e9a87SYuri Pankov 	while (argc) {
713260e9a87SYuri Pankov 		for (ipath = 0; ipath < paths->sz; ipath++) {
714260e9a87SYuri Pankov 			if (cfg->sec != NULL) {
715260e9a87SYuri Pankov 				if (fs_lookup(paths, ipath, cfg->sec,
716260e9a87SYuri Pankov 				    cfg->arch, *argv, res, ressz) &&
717260e9a87SYuri Pankov 				    cfg->firstmatch)
718*c66b8046SYuri Pankov 					return 1;
719260e9a87SYuri Pankov 			} else for (isec = 0; isec < nsec; isec++)
720260e9a87SYuri Pankov 				if (fs_lookup(paths, ipath, sections[isec],
721260e9a87SYuri Pankov 				    cfg->arch, *argv, res, ressz) &&
722260e9a87SYuri Pankov 				    cfg->firstmatch)
723*c66b8046SYuri Pankov 					return 1;
724260e9a87SYuri Pankov 		}
725*c66b8046SYuri Pankov 		if (res != NULL && *ressz == lastsz)
726371584c2SYuri Pankov 			warnx("No entry for %s in the manual.", *argv);
727260e9a87SYuri Pankov 		lastsz = *ressz;
728260e9a87SYuri Pankov 		argv++;
729260e9a87SYuri Pankov 		argc--;
730260e9a87SYuri Pankov 	}
731*c66b8046SYuri Pankov 	return 0;
73295c635efSGarrett D'Amore }
73395c635efSGarrett D'Amore 
73495c635efSGarrett D'Amore static void
735371584c2SYuri Pankov parse(struct curparse *curp, int fd, const char *file)
73695c635efSGarrett D'Amore {
737371584c2SYuri Pankov 	enum mandoclevel  rctmp;
738371584c2SYuri Pankov 	struct roff_man	 *man;
73995c635efSGarrett D'Amore 
74095c635efSGarrett D'Amore 	/* Begin by parsing the file itself. */
74195c635efSGarrett D'Amore 
74295c635efSGarrett D'Amore 	assert(file);
743371584c2SYuri Pankov 	assert(fd >= 0);
74495c635efSGarrett D'Amore 
745371584c2SYuri Pankov 	rctmp = mparse_readfd(curp->mp, fd, file);
746371584c2SYuri Pankov 	if (fd != STDIN_FILENO)
747371584c2SYuri Pankov 		close(fd);
748371584c2SYuri Pankov 	if (rc < rctmp)
749371584c2SYuri Pankov 		rc = rctmp;
75095c635efSGarrett D'Amore 
75195c635efSGarrett D'Amore 	/*
75295c635efSGarrett D'Amore 	 * With -Wstop and warnings or errors of at least the requested
75395c635efSGarrett D'Amore 	 * level, do not produce output.
75495c635efSGarrett D'Amore 	 */
75595c635efSGarrett D'Amore 
756371584c2SYuri Pankov 	if (rctmp != MANDOCLEVEL_OK && curp->wstop)
757371584c2SYuri Pankov 		return;
75895c635efSGarrett D'Amore 
759a5934736SYuri Pankov 	if (curp->outdata == NULL)
760a5934736SYuri Pankov 		outdata_alloc(curp);
761371584c2SYuri Pankov 
762371584c2SYuri Pankov 	mparse_result(curp->mp, &man, NULL);
76395c635efSGarrett D'Amore 
764371584c2SYuri Pankov 	/* Execute the out device, if it exists. */
765371584c2SYuri Pankov 
766371584c2SYuri Pankov 	if (man == NULL)
767371584c2SYuri Pankov 		return;
768*c66b8046SYuri Pankov 	mandoc_xr_reset();
769371584c2SYuri Pankov 	if (man->macroset == MACROSET_MDOC) {
770a40ea1a7SYuri Pankov 		if (curp->outtype != OUTT_TREE || !curp->outopts->noval)
771a40ea1a7SYuri Pankov 			mdoc_validate(man);
77295c635efSGarrett D'Amore 		switch (curp->outtype) {
773260e9a87SYuri Pankov 		case OUTT_HTML:
774371584c2SYuri Pankov 			html_mdoc(curp->outdata, man);
77595c635efSGarrett D'Amore 			break;
776260e9a87SYuri Pankov 		case OUTT_TREE:
777371584c2SYuri Pankov 			tree_mdoc(curp->outdata, man);
77895c635efSGarrett D'Amore 			break;
779260e9a87SYuri Pankov 		case OUTT_MAN:
780371584c2SYuri Pankov 			man_mdoc(curp->outdata, man);
78195c635efSGarrett D'Amore 			break;
782260e9a87SYuri Pankov 		case OUTT_PDF:
783260e9a87SYuri Pankov 		case OUTT_ASCII:
784260e9a87SYuri Pankov 		case OUTT_UTF8:
785260e9a87SYuri Pankov 		case OUTT_LOCALE:
786260e9a87SYuri Pankov 		case OUTT_PS:
787371584c2SYuri Pankov 			terminal_mdoc(curp->outdata, man);
788371584c2SYuri Pankov 			break;
789*c66b8046SYuri Pankov 		case OUTT_MARKDOWN:
790*c66b8046SYuri Pankov 			markdown_mdoc(curp->outdata, man);
791*c66b8046SYuri Pankov 			break;
792371584c2SYuri Pankov 		default:
793371584c2SYuri Pankov 			break;
794371584c2SYuri Pankov 		}
795371584c2SYuri Pankov 	}
796371584c2SYuri Pankov 	if (man->macroset == MACROSET_MAN) {
797a40ea1a7SYuri Pankov 		if (curp->outtype != OUTT_TREE || !curp->outopts->noval)
798a40ea1a7SYuri Pankov 			man_validate(man);
799371584c2SYuri Pankov 		switch (curp->outtype) {
800371584c2SYuri Pankov 		case OUTT_HTML:
801371584c2SYuri Pankov 			html_man(curp->outdata, man);
802371584c2SYuri Pankov 			break;
803371584c2SYuri Pankov 		case OUTT_TREE:
804371584c2SYuri Pankov 			tree_man(curp->outdata, man);
805371584c2SYuri Pankov 			break;
806371584c2SYuri Pankov 		case OUTT_MAN:
807371584c2SYuri Pankov 			man_man(curp->outdata, man);
808371584c2SYuri Pankov 			break;
809371584c2SYuri Pankov 		case OUTT_PDF:
810371584c2SYuri Pankov 		case OUTT_ASCII:
811371584c2SYuri Pankov 		case OUTT_UTF8:
812371584c2SYuri Pankov 		case OUTT_LOCALE:
813371584c2SYuri Pankov 		case OUTT_PS:
814371584c2SYuri Pankov 			terminal_man(curp->outdata, man);
81595c635efSGarrett D'Amore 			break;
81695c635efSGarrett D'Amore 		default:
81795c635efSGarrett D'Amore 			break;
81895c635efSGarrett D'Amore 		}
81995c635efSGarrett D'Amore 	}
820*c66b8046SYuri Pankov 	if (curp->mmin < MANDOCERR_STYLE)
821*c66b8046SYuri Pankov 		check_xr(file);
822a5934736SYuri Pankov 	mparse_updaterc(curp->mp, &rc);
823a5934736SYuri Pankov }
824a5934736SYuri Pankov 
825*c66b8046SYuri Pankov static void
826*c66b8046SYuri Pankov check_xr(const char *file)
827*c66b8046SYuri Pankov {
828*c66b8046SYuri Pankov 	static struct manpaths	 paths;
829*c66b8046SYuri Pankov 	struct mansearch	 search;
830*c66b8046SYuri Pankov 	struct mandoc_xr	*xr;
831*c66b8046SYuri Pankov 	char			*cp;
832*c66b8046SYuri Pankov 	size_t			 sz;
833*c66b8046SYuri Pankov 
834*c66b8046SYuri Pankov 	if (paths.sz == 0)
835*c66b8046SYuri Pankov 		manpath_base(&paths);
836*c66b8046SYuri Pankov 
837*c66b8046SYuri Pankov 	for (xr = mandoc_xr_get(); xr != NULL; xr = xr->next) {
838*c66b8046SYuri Pankov 		if (xr->line == -1)
839*c66b8046SYuri Pankov 			continue;
840*c66b8046SYuri Pankov 		search.arch = NULL;
841*c66b8046SYuri Pankov 		search.sec = xr->sec;
842*c66b8046SYuri Pankov 		search.outkey = NULL;
843*c66b8046SYuri Pankov 		search.argmode = ARG_NAME;
844*c66b8046SYuri Pankov 		search.firstmatch = 1;
845*c66b8046SYuri Pankov 		if (mansearch(&search, &paths, 1, &xr->name, NULL, &sz))
846*c66b8046SYuri Pankov 			continue;
847*c66b8046SYuri Pankov 		if (fs_search(&search, &paths, 1, &xr->name, NULL, &sz))
848*c66b8046SYuri Pankov 			continue;
849*c66b8046SYuri Pankov 		if (xr->count == 1)
850*c66b8046SYuri Pankov 			mandoc_asprintf(&cp, "Xr %s %s", xr->name, xr->sec);
851*c66b8046SYuri Pankov 		else
852*c66b8046SYuri Pankov 			mandoc_asprintf(&cp, "Xr %s %s (%d times)",
853*c66b8046SYuri Pankov 			    xr->name, xr->sec, xr->count);
854*c66b8046SYuri Pankov 		mmsg(MANDOCERR_XR_BAD, MANDOCLEVEL_STYLE,
855*c66b8046SYuri Pankov 		    file, xr->line, xr->pos + 1, cp);
856*c66b8046SYuri Pankov 		free(cp);
857*c66b8046SYuri Pankov 	}
858*c66b8046SYuri Pankov }
859*c66b8046SYuri Pankov 
860a5934736SYuri Pankov static void
861a5934736SYuri Pankov outdata_alloc(struct curparse *curp)
862a5934736SYuri Pankov {
863a5934736SYuri Pankov 	switch (curp->outtype) {
864a5934736SYuri Pankov 	case OUTT_HTML:
865a5934736SYuri Pankov 		curp->outdata = html_alloc(curp->outopts);
866a5934736SYuri Pankov 		break;
867a5934736SYuri Pankov 	case OUTT_UTF8:
868a5934736SYuri Pankov 		curp->outdata = utf8_alloc(curp->outopts);
869a5934736SYuri Pankov 		break;
870a5934736SYuri Pankov 	case OUTT_LOCALE:
871a5934736SYuri Pankov 		curp->outdata = locale_alloc(curp->outopts);
872a5934736SYuri Pankov 		break;
873a5934736SYuri Pankov 	case OUTT_ASCII:
874a5934736SYuri Pankov 		curp->outdata = ascii_alloc(curp->outopts);
875a5934736SYuri Pankov 		break;
876a5934736SYuri Pankov 	case OUTT_PDF:
877a5934736SYuri Pankov 		curp->outdata = pdf_alloc(curp->outopts);
878a5934736SYuri Pankov 		break;
879a5934736SYuri Pankov 	case OUTT_PS:
880a5934736SYuri Pankov 		curp->outdata = ps_alloc(curp->outopts);
881a5934736SYuri Pankov 		break;
882a5934736SYuri Pankov 	default:
883a5934736SYuri Pankov 		break;
884a5934736SYuri Pankov 	}
88595c635efSGarrett D'Amore }
88695c635efSGarrett D'Amore 
887371584c2SYuri Pankov static void
888260e9a87SYuri Pankov passthrough(const char *file, int fd, int synopsis_only)
889260e9a87SYuri Pankov {
890260e9a87SYuri Pankov 	const char	 synb[] = "S\bSY\bYN\bNO\bOP\bPS\bSI\bIS\bS";
891260e9a87SYuri Pankov 	const char	 synr[] = "SYNOPSIS";
892260e9a87SYuri Pankov 
893260e9a87SYuri Pankov 	FILE		*stream;
894260e9a87SYuri Pankov 	const char	*syscall;
895371584c2SYuri Pankov 	char		*line, *cp;
896371584c2SYuri Pankov 	size_t		 linesz;
897a5934736SYuri Pankov 	ssize_t		 len, written;
898260e9a87SYuri Pankov 	int		 print;
899260e9a87SYuri Pankov 
900371584c2SYuri Pankov 	line = NULL;
901371584c2SYuri Pankov 	linesz = 0;
902260e9a87SYuri Pankov 
903a5934736SYuri Pankov 	if (fflush(stdout) == EOF) {
904a5934736SYuri Pankov 		syscall = "fflush";
905a5934736SYuri Pankov 		goto fail;
906a5934736SYuri Pankov 	}
907a5934736SYuri Pankov 
908260e9a87SYuri Pankov 	if ((stream = fdopen(fd, "r")) == NULL) {
909260e9a87SYuri Pankov 		close(fd);
910260e9a87SYuri Pankov 		syscall = "fdopen";
911260e9a87SYuri Pankov 		goto fail;
912260e9a87SYuri Pankov 	}
913260e9a87SYuri Pankov 
914260e9a87SYuri Pankov 	print = 0;
915a5934736SYuri Pankov 	while ((len = getline(&line, &linesz, stream)) != -1) {
916371584c2SYuri Pankov 		cp = line;
917260e9a87SYuri Pankov 		if (synopsis_only) {
918260e9a87SYuri Pankov 			if (print) {
919371584c2SYuri Pankov 				if ( ! isspace((unsigned char)*cp))
920260e9a87SYuri Pankov 					goto done;
921a5934736SYuri Pankov 				while (isspace((unsigned char)*cp)) {
922371584c2SYuri Pankov 					cp++;
923a5934736SYuri Pankov 					len--;
924a5934736SYuri Pankov 				}
925260e9a87SYuri Pankov 			} else {
926371584c2SYuri Pankov 				if (strcmp(cp, synb) == 0 ||
927371584c2SYuri Pankov 				    strcmp(cp, synr) == 0)
928260e9a87SYuri Pankov 					print = 1;
929260e9a87SYuri Pankov 				continue;
930260e9a87SYuri Pankov 			}
931260e9a87SYuri Pankov 		}
932a5934736SYuri Pankov 		for (; len > 0; len -= written) {
933a5934736SYuri Pankov 			if ((written = write(STDOUT_FILENO, cp, len)) != -1)
934a5934736SYuri Pankov 				continue;
935371584c2SYuri Pankov 			fclose(stream);
936a5934736SYuri Pankov 			syscall = "write";
937371584c2SYuri Pankov 			goto fail;
938371584c2SYuri Pankov 		}
939260e9a87SYuri Pankov 	}
940260e9a87SYuri Pankov 
941260e9a87SYuri Pankov 	if (ferror(stream)) {
942260e9a87SYuri Pankov 		fclose(stream);
943371584c2SYuri Pankov 		syscall = "getline";
944260e9a87SYuri Pankov 		goto fail;
945260e9a87SYuri Pankov 	}
946260e9a87SYuri Pankov 
947260e9a87SYuri Pankov done:
948371584c2SYuri Pankov 	free(line);
949260e9a87SYuri Pankov 	fclose(stream);
950371584c2SYuri Pankov 	return;
951260e9a87SYuri Pankov 
952260e9a87SYuri Pankov fail:
953371584c2SYuri Pankov 	free(line);
954371584c2SYuri Pankov 	warn("%s: SYSERR: %s", file, syscall);
955371584c2SYuri Pankov 	if (rc < MANDOCLEVEL_SYSERR)
956371584c2SYuri Pankov 		rc = MANDOCLEVEL_SYSERR;
957260e9a87SYuri Pankov }
958260e9a87SYuri Pankov 
959260e9a87SYuri Pankov static int
960260e9a87SYuri Pankov koptions(int *options, char *arg)
961260e9a87SYuri Pankov {
962260e9a87SYuri Pankov 
963260e9a87SYuri Pankov 	if ( ! strcmp(arg, "utf-8")) {
964260e9a87SYuri Pankov 		*options |=  MPARSE_UTF8;
965260e9a87SYuri Pankov 		*options &= ~MPARSE_LATIN1;
966260e9a87SYuri Pankov 	} else if ( ! strcmp(arg, "iso-8859-1")) {
967260e9a87SYuri Pankov 		*options |=  MPARSE_LATIN1;
968260e9a87SYuri Pankov 		*options &= ~MPARSE_UTF8;
969260e9a87SYuri Pankov 	} else if ( ! strcmp(arg, "us-ascii")) {
970260e9a87SYuri Pankov 		*options &= ~(MPARSE_UTF8 | MPARSE_LATIN1);
971260e9a87SYuri Pankov 	} else {
972371584c2SYuri Pankov 		warnx("-K %s: Bad argument", arg);
973371584c2SYuri Pankov 		return 0;
974260e9a87SYuri Pankov 	}
975371584c2SYuri Pankov 	return 1;
976260e9a87SYuri Pankov }
977260e9a87SYuri Pankov 
978*c66b8046SYuri Pankov static void
979260e9a87SYuri Pankov moptions(int *options, char *arg)
98095c635efSGarrett D'Amore {
98195c635efSGarrett D'Amore 
982260e9a87SYuri Pankov 	if (arg == NULL)
983*c66b8046SYuri Pankov 		return;
984*c66b8046SYuri Pankov 	if (strcmp(arg, "doc") == 0)
985260e9a87SYuri Pankov 		*options |= MPARSE_MDOC;
986*c66b8046SYuri Pankov 	else if (strcmp(arg, "an") == 0)
987260e9a87SYuri Pankov 		*options |= MPARSE_MAN;
98895c635efSGarrett D'Amore }
98995c635efSGarrett D'Amore 
99095c635efSGarrett D'Amore static int
99195c635efSGarrett D'Amore toptions(struct curparse *curp, char *arg)
99295c635efSGarrett D'Amore {
99395c635efSGarrett D'Amore 
99495c635efSGarrett D'Amore 	if (0 == strcmp(arg, "ascii"))
99595c635efSGarrett D'Amore 		curp->outtype = OUTT_ASCII;
99695c635efSGarrett D'Amore 	else if (0 == strcmp(arg, "lint")) {
99795c635efSGarrett D'Amore 		curp->outtype = OUTT_LINT;
998*c66b8046SYuri Pankov 		curp->mmin = MANDOCERR_BASE;
999*c66b8046SYuri Pankov 		mmsg_stream = stdout;
100095c635efSGarrett D'Amore 	} else if (0 == strcmp(arg, "tree"))
100195c635efSGarrett D'Amore 		curp->outtype = OUTT_TREE;
100295c635efSGarrett D'Amore 	else if (0 == strcmp(arg, "man"))
100395c635efSGarrett D'Amore 		curp->outtype = OUTT_MAN;
100495c635efSGarrett D'Amore 	else if (0 == strcmp(arg, "html"))
100595c635efSGarrett D'Amore 		curp->outtype = OUTT_HTML;
1006*c66b8046SYuri Pankov 	else if (0 == strcmp(arg, "markdown"))
1007*c66b8046SYuri Pankov 		curp->outtype = OUTT_MARKDOWN;
100895c635efSGarrett D'Amore 	else if (0 == strcmp(arg, "utf8"))
100995c635efSGarrett D'Amore 		curp->outtype = OUTT_UTF8;
101095c635efSGarrett D'Amore 	else if (0 == strcmp(arg, "locale"))
101195c635efSGarrett D'Amore 		curp->outtype = OUTT_LOCALE;
101295c635efSGarrett D'Amore 	else if (0 == strcmp(arg, "ps"))
101395c635efSGarrett D'Amore 		curp->outtype = OUTT_PS;
101495c635efSGarrett D'Amore 	else if (0 == strcmp(arg, "pdf"))
101595c635efSGarrett D'Amore 		curp->outtype = OUTT_PDF;
101695c635efSGarrett D'Amore 	else {
1017371584c2SYuri Pankov 		warnx("-T %s: Bad argument", arg);
1018371584c2SYuri Pankov 		return 0;
101995c635efSGarrett D'Amore 	}
102095c635efSGarrett D'Amore 
1021371584c2SYuri Pankov 	return 1;
102295c635efSGarrett D'Amore }
102395c635efSGarrett D'Amore 
102495c635efSGarrett D'Amore static int
102595c635efSGarrett D'Amore woptions(struct curparse *curp, char *arg)
102695c635efSGarrett D'Amore {
102795c635efSGarrett D'Amore 	char		*v, *o;
1028*c66b8046SYuri Pankov 	const char	*toks[11];
102995c635efSGarrett D'Amore 
103095c635efSGarrett D'Amore 	toks[0] = "stop";
103195c635efSGarrett D'Amore 	toks[1] = "all";
1032*c66b8046SYuri Pankov 	toks[2] = "base";
1033*c66b8046SYuri Pankov 	toks[3] = "style";
1034*c66b8046SYuri Pankov 	toks[4] = "warning";
1035*c66b8046SYuri Pankov 	toks[5] = "error";
1036*c66b8046SYuri Pankov 	toks[6] = "unsupp";
1037*c66b8046SYuri Pankov 	toks[7] = "fatal";
1038*c66b8046SYuri Pankov 	toks[8] = "openbsd";
1039*c66b8046SYuri Pankov 	toks[9] = "netbsd";
1040*c66b8046SYuri Pankov 	toks[10] = NULL;
104195c635efSGarrett D'Amore 
104295c635efSGarrett D'Amore 	while (*arg) {
104395c635efSGarrett D'Amore 		o = arg;
1044a5934736SYuri Pankov 		switch (getsubopt(&arg, (char * const *)toks, &v)) {
1045260e9a87SYuri Pankov 		case 0:
104695c635efSGarrett D'Amore 			curp->wstop = 1;
104795c635efSGarrett D'Amore 			break;
1048260e9a87SYuri Pankov 		case 1:
1049260e9a87SYuri Pankov 		case 2:
1050*c66b8046SYuri Pankov 			curp->mmin = MANDOCERR_BASE;
105195c635efSGarrett D'Amore 			break;
1052260e9a87SYuri Pankov 		case 3:
1053*c66b8046SYuri Pankov 			curp->mmin = MANDOCERR_STYLE;
105495c635efSGarrett D'Amore 			break;
1055260e9a87SYuri Pankov 		case 4:
1056*c66b8046SYuri Pankov 			curp->mmin = MANDOCERR_WARNING;
1057260e9a87SYuri Pankov 			break;
1058260e9a87SYuri Pankov 		case 5:
1059*c66b8046SYuri Pankov 			curp->mmin = MANDOCERR_ERROR;
1060*c66b8046SYuri Pankov 			break;
1061*c66b8046SYuri Pankov 		case 6:
1062*c66b8046SYuri Pankov 			curp->mmin = MANDOCERR_UNSUPP;
1063*c66b8046SYuri Pankov 			break;
1064*c66b8046SYuri Pankov 		case 7:
1065*c66b8046SYuri Pankov 			curp->mmin = MANDOCERR_MAX;
1066*c66b8046SYuri Pankov 			break;
1067*c66b8046SYuri Pankov 		case 8:
1068*c66b8046SYuri Pankov 			curp->mmin = MANDOCERR_BASE;
1069*c66b8046SYuri Pankov 			curp->os_e = MANDOC_OS_OPENBSD;
1070*c66b8046SYuri Pankov 			break;
1071*c66b8046SYuri Pankov 		case 9:
1072*c66b8046SYuri Pankov 			curp->mmin = MANDOCERR_BASE;
1073*c66b8046SYuri Pankov 			curp->os_e = MANDOC_OS_NETBSD;
107495c635efSGarrett D'Amore 			break;
107595c635efSGarrett D'Amore 		default:
1076371584c2SYuri Pankov 			warnx("-W %s: Bad argument", o);
1077371584c2SYuri Pankov 			return 0;
107895c635efSGarrett D'Amore 		}
107995c635efSGarrett D'Amore 	}
1080371584c2SYuri Pankov 	return 1;
108195c635efSGarrett D'Amore }
108295c635efSGarrett D'Amore 
108395c635efSGarrett D'Amore static void
1084260e9a87SYuri Pankov mmsg(enum mandocerr t, enum mandoclevel lvl,
108595c635efSGarrett D'Amore 		const char *file, int line, int col, const char *msg)
108695c635efSGarrett D'Amore {
1087260e9a87SYuri Pankov 	const char	*mparse_msg;
1088260e9a87SYuri Pankov 
1089*c66b8046SYuri Pankov 	fprintf(mmsg_stream, "%s: %s:", getprogname(),
1090371584c2SYuri Pankov 	    file == NULL ? "<stdin>" : file);
1091260e9a87SYuri Pankov 
1092260e9a87SYuri Pankov 	if (line)
1093*c66b8046SYuri Pankov 		fprintf(mmsg_stream, "%d:%d:", line, col + 1);
109495c635efSGarrett D'Amore 
1095*c66b8046SYuri Pankov 	fprintf(mmsg_stream, " %s", mparse_strlevel(lvl));
1096260e9a87SYuri Pankov 
1097*c66b8046SYuri Pankov 	if ((mparse_msg = mparse_strerror(t)) != NULL)
1098*c66b8046SYuri Pankov 		fprintf(mmsg_stream, ": %s", mparse_msg);
109995c635efSGarrett D'Amore 
110095c635efSGarrett D'Amore 	if (msg)
1101*c66b8046SYuri Pankov 		fprintf(mmsg_stream, ": %s", msg);
110295c635efSGarrett D'Amore 
1103*c66b8046SYuri Pankov 	fputc('\n', mmsg_stream);
110495c635efSGarrett D'Amore }
1105260e9a87SYuri Pankov 
1106260e9a87SYuri Pankov static pid_t
1107371584c2SYuri Pankov spawn_pager(struct tag_files *tag_files)
1108260e9a87SYuri Pankov {
1109371584c2SYuri Pankov 	const struct timespec timeout = { 0, 100000000 };  /* 0.1s */
1110260e9a87SYuri Pankov #define MAX_PAGER_ARGS 16
1111260e9a87SYuri Pankov 	char		*argv[MAX_PAGER_ARGS];
1112260e9a87SYuri Pankov 	const char	*pager;
1113260e9a87SYuri Pankov 	char		*cp;
1114371584c2SYuri Pankov 	size_t		 cmdlen;
1115260e9a87SYuri Pankov 	int		 argc;
1116260e9a87SYuri Pankov 	pid_t		 pager_pid;
1117260e9a87SYuri Pankov 
1118260e9a87SYuri Pankov 	pager = getenv("MANPAGER");
1119260e9a87SYuri Pankov 	if (pager == NULL || *pager == '\0')
1120260e9a87SYuri Pankov 		pager = getenv("PAGER");
1121260e9a87SYuri Pankov 	if (pager == NULL || *pager == '\0')
1122371584c2SYuri Pankov 		pager = "more -s";
1123260e9a87SYuri Pankov 	cp = mandoc_strdup(pager);
1124260e9a87SYuri Pankov 
1125260e9a87SYuri Pankov 	/*
1126260e9a87SYuri Pankov 	 * Parse the pager command into words.
1127260e9a87SYuri Pankov 	 * Intentionally do not do anything fancy here.
1128260e9a87SYuri Pankov 	 */
1129260e9a87SYuri Pankov 
1130260e9a87SYuri Pankov 	argc = 0;
1131371584c2SYuri Pankov 	while (argc + 4 < MAX_PAGER_ARGS) {
1132260e9a87SYuri Pankov 		argv[argc++] = cp;
1133260e9a87SYuri Pankov 		cp = strchr(cp, ' ');
1134260e9a87SYuri Pankov 		if (cp == NULL)
1135260e9a87SYuri Pankov 			break;
1136260e9a87SYuri Pankov 		*cp++ = '\0';
1137260e9a87SYuri Pankov 		while (*cp == ' ')
1138260e9a87SYuri Pankov 			cp++;
1139260e9a87SYuri Pankov 		if (*cp == '\0')
1140260e9a87SYuri Pankov 			break;
1141260e9a87SYuri Pankov 	}
1142371584c2SYuri Pankov 
1143371584c2SYuri Pankov 	/* For less(1), use the tag file. */
1144371584c2SYuri Pankov 
1145371584c2SYuri Pankov 	if ((cmdlen = strlen(argv[0])) >= 4) {
1146371584c2SYuri Pankov 		cp = argv[0] + cmdlen - 4;
1147371584c2SYuri Pankov 		if (strcmp(cp, "less") == 0) {
1148371584c2SYuri Pankov 			argv[argc++] = mandoc_strdup("-T");
1149371584c2SYuri Pankov 			argv[argc++] = tag_files->tfn;
1150371584c2SYuri Pankov 		}
1151371584c2SYuri Pankov 	}
1152371584c2SYuri Pankov 	argv[argc++] = tag_files->ofn;
1153260e9a87SYuri Pankov 	argv[argc] = NULL;
1154260e9a87SYuri Pankov 
1155371584c2SYuri Pankov 	switch (pager_pid = fork()) {
1156371584c2SYuri Pankov 	case -1:
1157371584c2SYuri Pankov 		err((int)MANDOCLEVEL_SYSERR, "fork");
1158371584c2SYuri Pankov 	case 0:
1159371584c2SYuri Pankov 		break;
1160371584c2SYuri Pankov 	default:
1161371584c2SYuri Pankov 		(void)setpgid(pager_pid, 0);
1162a40ea1a7SYuri Pankov 		(void)tcsetpgrp(tag_files->ofd, pager_pid);
1163371584c2SYuri Pankov #if HAVE_PLEDGE
1164371584c2SYuri Pankov 		if (pledge("stdio rpath tmppath tty proc", NULL) == -1)
1165371584c2SYuri Pankov 			err((int)MANDOCLEVEL_SYSERR, "pledge");
1166371584c2SYuri Pankov #endif
1167371584c2SYuri Pankov 		tag_files->pager_pid = pager_pid;
1168371584c2SYuri Pankov 		return pager_pid;
1169371584c2SYuri Pankov 	}
1170371584c2SYuri Pankov 
1171371584c2SYuri Pankov 	/* The child process becomes the pager. */
1172371584c2SYuri Pankov 
1173371584c2SYuri Pankov 	if (dup2(tag_files->ofd, STDOUT_FILENO) == -1)
1174371584c2SYuri Pankov 		err((int)MANDOCLEVEL_SYSERR, "pager stdout");
1175371584c2SYuri Pankov 	close(tag_files->ofd);
1176371584c2SYuri Pankov 	close(tag_files->tfd);
1177371584c2SYuri Pankov 
1178371584c2SYuri Pankov 	/* Do not start the pager before controlling the terminal. */
1179371584c2SYuri Pankov 
1180a40ea1a7SYuri Pankov 	while (tcgetpgrp(STDOUT_FILENO) != getpid())
1181371584c2SYuri Pankov 		nanosleep(&timeout, NULL);
1182260e9a87SYuri Pankov 
1183260e9a87SYuri Pankov 	execvp(argv[0], argv);
1184371584c2SYuri Pankov 	err((int)MANDOCLEVEL_SYSERR, "exec %s", argv[0]);
1185260e9a87SYuri Pankov }
1186