xref: /illumos-gate/usr/src/cmd/mandoc/main.c (revision 371584c2eae4cf827fd406ba26c14f021adaaa70)
1*371584c2SYuri Pankov /*	$Id: main.c,v 1.269 2016/07/12 05:18:38 kristaps Exp $ */
295c635efSGarrett D'Amore /*
3260e9a87SYuri Pankov  * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
4*371584c2SYuri Pankov  * Copyright (c) 2010-2012, 2014-2016 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  *
11*371584c2SYuri 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
13*371584c2SYuri 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>
27*371584c2SYuri Pankov #if HAVE_ERR
28*371584c2SYuri Pankov #include <err.h>
29*371584c2SYuri Pankov #endif
30260e9a87SYuri Pankov #include <errno.h>
31260e9a87SYuri Pankov #include <fcntl.h>
32260e9a87SYuri Pankov #include <glob.h>
33*371584c2SYuri Pankov #if HAVE_SANDBOX_INIT
34*371584c2SYuri Pankov #include <sandbox.h>
35*371584c2SYuri Pankov #endif
36*371584c2SYuri 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>
41*371584c2SYuri Pankov #include <time.h>
4295c635efSGarrett D'Amore #include <unistd.h>
4395c635efSGarrett D'Amore 
44260e9a87SYuri Pankov #include "mandoc_aux.h"
45*371584c2SYuri Pankov #include "mandoc.h"
46*371584c2SYuri Pankov #include "roff.h"
4795c635efSGarrett D'Amore #include "mdoc.h"
4895c635efSGarrett D'Amore #include "man.h"
49*371584c2SYuri Pankov #include "tag.h"
50*371584c2SYuri Pankov #include "main.h"
51*371584c2SYuri Pankov #include "manconf.h"
52260e9a87SYuri Pankov #include "mansearch.h"
5395c635efSGarrett D'Amore 
5495c635efSGarrett D'Amore #if !defined(__GNUC__) || (__GNUC__ < 2)
5595c635efSGarrett D'Amore # if !defined(lint)
5695c635efSGarrett D'Amore #  define __attribute__(x)
5795c635efSGarrett D'Amore # endif
5895c635efSGarrett D'Amore #endif /* !defined(__GNUC__) || (__GNUC__ < 2) */
5995c635efSGarrett D'Amore 
60260e9a87SYuri Pankov enum	outmode {
61260e9a87SYuri Pankov 	OUTMODE_DEF = 0,
62260e9a87SYuri Pankov 	OUTMODE_FLN,
63260e9a87SYuri Pankov 	OUTMODE_LST,
64260e9a87SYuri Pankov 	OUTMODE_ALL,
65260e9a87SYuri Pankov 	OUTMODE_INT,
66260e9a87SYuri Pankov 	OUTMODE_ONE
67260e9a87SYuri Pankov };
68260e9a87SYuri Pankov 
6995c635efSGarrett D'Amore enum	outt {
7095c635efSGarrett D'Amore 	OUTT_ASCII = 0,	/* -Tascii */
7195c635efSGarrett D'Amore 	OUTT_LOCALE,	/* -Tlocale */
7295c635efSGarrett D'Amore 	OUTT_UTF8,	/* -Tutf8 */
7395c635efSGarrett D'Amore 	OUTT_TREE,	/* -Ttree */
7495c635efSGarrett D'Amore 	OUTT_MAN,	/* -Tman */
7595c635efSGarrett D'Amore 	OUTT_HTML,	/* -Thtml */
7695c635efSGarrett D'Amore 	OUTT_LINT,	/* -Tlint */
7795c635efSGarrett D'Amore 	OUTT_PS,	/* -Tps */
7895c635efSGarrett D'Amore 	OUTT_PDF	/* -Tpdf */
7995c635efSGarrett D'Amore };
8095c635efSGarrett D'Amore 
8195c635efSGarrett D'Amore struct	curparse {
8295c635efSGarrett D'Amore 	struct mparse	 *mp;
8395c635efSGarrett D'Amore 	enum mandoclevel  wlevel;	/* ignore messages below this */
8495c635efSGarrett D'Amore 	int		  wstop;	/* stop after a file with a warning */
85260e9a87SYuri Pankov 	enum outt	  outtype;	/* which output to use */
8695c635efSGarrett D'Amore 	void		 *outdata;	/* data for output */
87*371584c2SYuri Pankov 	struct manoutput *outopts;	/* output options */
8895c635efSGarrett D'Amore };
8995c635efSGarrett D'Amore 
90260e9a87SYuri Pankov static	int		  fs_lookup(const struct manpaths *,
91260e9a87SYuri Pankov 				size_t ipath, const char *,
92260e9a87SYuri Pankov 				const char *, const char *,
93260e9a87SYuri Pankov 				struct manpage **, size_t *);
94260e9a87SYuri Pankov static	void		  fs_search(const struct mansearch *,
95260e9a87SYuri Pankov 				const struct manpaths *, int, char**,
96260e9a87SYuri Pankov 				struct manpage **, size_t *);
97260e9a87SYuri Pankov static	int		  koptions(int *, char *);
98260e9a87SYuri Pankov #if HAVE_SQLITE3
99260e9a87SYuri Pankov int			  mandocdb(int, char**);
100260e9a87SYuri Pankov #endif
101260e9a87SYuri Pankov static	int		  moptions(int *, char *);
10295c635efSGarrett D'Amore static	void		  mmsg(enum mandocerr, enum mandoclevel,
10395c635efSGarrett D'Amore 				const char *, int, int, const char *);
104*371584c2SYuri Pankov static	void		  parse(struct curparse *, int, const char *);
105*371584c2SYuri Pankov static	void		  passthrough(const char *, int, int);
106*371584c2SYuri Pankov static	pid_t		  spawn_pager(struct tag_files *);
10795c635efSGarrett D'Amore static	int		  toptions(struct curparse *, char *);
108260e9a87SYuri Pankov static	void		  usage(enum argmode) __attribute__((noreturn));
10995c635efSGarrett D'Amore static	int		  woptions(struct curparse *, char *);
11095c635efSGarrett D'Amore 
111260e9a87SYuri Pankov static	const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
112260e9a87SYuri Pankov static	char		  help_arg[] = "help";
113260e9a87SYuri Pankov static	char		 *help_argv[] = {help_arg, NULL};
114*371584c2SYuri Pankov static	enum mandoclevel  rc;
11595c635efSGarrett D'Amore 
116260e9a87SYuri Pankov 
11795c635efSGarrett D'Amore int
11895c635efSGarrett D'Amore main(int argc, char *argv[])
11995c635efSGarrett D'Amore {
120*371584c2SYuri Pankov 	struct manconf	 conf;
12195c635efSGarrett D'Amore 	struct curparse	 curp;
122260e9a87SYuri Pankov 	struct mansearch search;
123*371584c2SYuri Pankov 	struct tag_files *tag_files;
124*371584c2SYuri Pankov 	const char	*progname;
125260e9a87SYuri Pankov 	char		*auxpaths;
126698f87a4SGarrett D'Amore 	char		*defos;
127260e9a87SYuri Pankov 	unsigned char	*uc;
128260e9a87SYuri Pankov 	struct manpage	*res, *resp;
129260e9a87SYuri Pankov 	char		*conf_file, *defpaths;
130*371584c2SYuri Pankov 	const char	*sec;
131*371584c2SYuri Pankov 	size_t		 i, sz;
132*371584c2SYuri Pankov 	int		 prio, best_prio;
133260e9a87SYuri Pankov 	enum outmode	 outmode;
134260e9a87SYuri Pankov 	int		 fd;
135260e9a87SYuri Pankov 	int		 show_usage;
136260e9a87SYuri Pankov 	int		 options;
137*371584c2SYuri Pankov 	int		 use_pager;
138*371584c2SYuri Pankov 	int		 status, signum;
139260e9a87SYuri Pankov 	int		 c;
140*371584c2SYuri Pankov 	pid_t		 pager_pid, tc_pgid, man_pgid, pid;
14195c635efSGarrett D'Amore 
142*371584c2SYuri Pankov #if HAVE_PROGNAME
143*371584c2SYuri Pankov 	progname = getprogname();
144*371584c2SYuri Pankov #else
145260e9a87SYuri Pankov 	if (argc < 1)
146*371584c2SYuri Pankov 		progname = mandoc_strdup("mandoc");
147260e9a87SYuri Pankov 	else if ((progname = strrchr(argv[0], '/')) == NULL)
14895c635efSGarrett D'Amore 		progname = argv[0];
14995c635efSGarrett D'Amore 	else
15095c635efSGarrett D'Amore 		++progname;
151*371584c2SYuri Pankov 	setprogname(progname);
152*371584c2SYuri Pankov #endif
15395c635efSGarrett D'Amore 
154260e9a87SYuri Pankov #if HAVE_SQLITE3
155*371584c2SYuri Pankov 	if (strncmp(progname, "mandocdb", 8) == 0 ||
156*371584c2SYuri Pankov 	    strcmp(progname, BINM_MAKEWHATIS) == 0)
157*371584c2SYuri Pankov 		return mandocdb(argc, argv);
158*371584c2SYuri Pankov #endif
159*371584c2SYuri Pankov 
160*371584c2SYuri Pankov #if HAVE_PLEDGE
161*371584c2SYuri Pankov 	if (pledge("stdio rpath tmppath tty proc exec flock", NULL) == -1)
162*371584c2SYuri Pankov 		err((int)MANDOCLEVEL_SYSERR, "pledge");
163*371584c2SYuri Pankov #endif
164*371584c2SYuri Pankov 
165*371584c2SYuri Pankov #if HAVE_SANDBOX_INIT
166*371584c2SYuri Pankov 	if (sandbox_init(kSBXProfileNoInternet, SANDBOX_NAMED, NULL) == -1)
167*371584c2SYuri Pankov 		errx((int)MANDOCLEVEL_SYSERR, "sandbox_init");
168260e9a87SYuri Pankov #endif
169260e9a87SYuri Pankov 
170260e9a87SYuri Pankov 	/* Search options. */
17195c635efSGarrett D'Amore 
172*371584c2SYuri Pankov 	memset(&conf, 0, sizeof(conf));
173260e9a87SYuri Pankov 	conf_file = defpaths = NULL;
174260e9a87SYuri Pankov 	auxpaths = NULL;
175260e9a87SYuri Pankov 
176260e9a87SYuri Pankov 	memset(&search, 0, sizeof(struct mansearch));
177260e9a87SYuri Pankov 	search.outkey = "Nd";
178260e9a87SYuri Pankov 
179260e9a87SYuri Pankov 	if (strcmp(progname, BINM_MAN) == 0)
180260e9a87SYuri Pankov 		search.argmode = ARG_NAME;
181260e9a87SYuri Pankov 	else if (strcmp(progname, BINM_APROPOS) == 0)
182260e9a87SYuri Pankov 		search.argmode = ARG_EXPR;
183260e9a87SYuri Pankov 	else if (strcmp(progname, BINM_WHATIS) == 0)
184260e9a87SYuri Pankov 		search.argmode = ARG_WORD;
185260e9a87SYuri Pankov 	else if (strncmp(progname, "help", 4) == 0)
186260e9a87SYuri Pankov 		search.argmode = ARG_NAME;
187260e9a87SYuri Pankov 	else
188260e9a87SYuri Pankov 		search.argmode = ARG_FILE;
189260e9a87SYuri Pankov 
190260e9a87SYuri Pankov 	/* Parser and formatter options. */
191260e9a87SYuri Pankov 
192260e9a87SYuri Pankov 	memset(&curp, 0, sizeof(struct curparse));
193260e9a87SYuri Pankov 	curp.outtype = OUTT_LOCALE;
194260e9a87SYuri Pankov 	curp.wlevel  = MANDOCLEVEL_BADARG;
195*371584c2SYuri Pankov 	curp.outopts = &conf.output;
196260e9a87SYuri Pankov 	options = MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1;
197698f87a4SGarrett D'Amore 	defos = NULL;
19895c635efSGarrett D'Amore 
199*371584c2SYuri Pankov 	use_pager = 1;
200*371584c2SYuri Pankov 	tag_files = NULL;
201260e9a87SYuri Pankov 	show_usage = 0;
202260e9a87SYuri Pankov 	outmode = OUTMODE_DEF;
203260e9a87SYuri Pankov 
204260e9a87SYuri Pankov 	while (-1 != (c = getopt(argc, argv,
205260e9a87SYuri Pankov 			"aC:cfhI:iK:klM:m:O:S:s:T:VW:w"))) {
20695c635efSGarrett D'Amore 		switch (c) {
207260e9a87SYuri Pankov 		case 'a':
208260e9a87SYuri Pankov 			outmode = OUTMODE_ALL;
209260e9a87SYuri Pankov 			break;
210260e9a87SYuri Pankov 		case 'C':
211260e9a87SYuri Pankov 			conf_file = optarg;
212260e9a87SYuri Pankov 			break;
213260e9a87SYuri Pankov 		case 'c':
214*371584c2SYuri Pankov 			use_pager = 0;
215260e9a87SYuri Pankov 			break;
216260e9a87SYuri Pankov 		case 'f':
217260e9a87SYuri Pankov 			search.argmode = ARG_WORD;
218260e9a87SYuri Pankov 			break;
219260e9a87SYuri Pankov 		case 'h':
220*371584c2SYuri Pankov 			conf.output.synopsisonly = 1;
221*371584c2SYuri Pankov 			use_pager = 0;
222260e9a87SYuri Pankov 			outmode = OUTMODE_ALL;
223260e9a87SYuri Pankov 			break;
224260e9a87SYuri Pankov 		case 'I':
225698f87a4SGarrett D'Amore 			if (strncmp(optarg, "os=", 3)) {
226*371584c2SYuri Pankov 				warnx("-I %s: Bad argument", optarg);
227*371584c2SYuri Pankov 				return (int)MANDOCLEVEL_BADARG;
228698f87a4SGarrett D'Amore 			}
229698f87a4SGarrett D'Amore 			if (defos) {
230*371584c2SYuri Pankov 				warnx("-I %s: Duplicate argument", optarg);
231*371584c2SYuri Pankov 				return (int)MANDOCLEVEL_BADARG;
232698f87a4SGarrett D'Amore 			}
233698f87a4SGarrett D'Amore 			defos = mandoc_strdup(optarg + 3);
234698f87a4SGarrett D'Amore 			break;
235260e9a87SYuri Pankov 		case 'i':
236260e9a87SYuri Pankov 			outmode = OUTMODE_INT;
237260e9a87SYuri Pankov 			break;
238260e9a87SYuri Pankov 		case 'K':
239260e9a87SYuri Pankov 			if ( ! koptions(&options, optarg))
240*371584c2SYuri Pankov 				return (int)MANDOCLEVEL_BADARG;
24195c635efSGarrett D'Amore 			break;
242260e9a87SYuri Pankov 		case 'k':
243260e9a87SYuri Pankov 			search.argmode = ARG_EXPR;
244260e9a87SYuri Pankov 			break;
245260e9a87SYuri Pankov 		case 'l':
246260e9a87SYuri Pankov 			search.argmode = ARG_FILE;
247260e9a87SYuri Pankov 			outmode = OUTMODE_ALL;
248260e9a87SYuri Pankov 			break;
249260e9a87SYuri Pankov 		case 'M':
250260e9a87SYuri Pankov 			defpaths = optarg;
251260e9a87SYuri Pankov 			break;
252260e9a87SYuri Pankov 		case 'm':
253260e9a87SYuri Pankov 			auxpaths = optarg;
254260e9a87SYuri Pankov 			break;
255260e9a87SYuri Pankov 		case 'O':
256260e9a87SYuri Pankov 			search.outkey = optarg;
257*371584c2SYuri Pankov 			while (optarg != NULL)
258*371584c2SYuri Pankov 				manconf_output(&conf.output,
259*371584c2SYuri Pankov 				    strsep(&optarg, ","));
26095c635efSGarrett D'Amore 			break;
261260e9a87SYuri Pankov 		case 'S':
262260e9a87SYuri Pankov 			search.arch = optarg;
263260e9a87SYuri Pankov 			break;
264260e9a87SYuri Pankov 		case 's':
265260e9a87SYuri Pankov 			search.sec = optarg;
266260e9a87SYuri Pankov 			break;
267260e9a87SYuri Pankov 		case 'T':
26895c635efSGarrett D'Amore 			if ( ! toptions(&curp, optarg))
269*371584c2SYuri Pankov 				return (int)MANDOCLEVEL_BADARG;
27095c635efSGarrett D'Amore 			break;
271260e9a87SYuri Pankov 		case 'W':
27295c635efSGarrett D'Amore 			if ( ! woptions(&curp, optarg))
273*371584c2SYuri Pankov 				return (int)MANDOCLEVEL_BADARG;
27495c635efSGarrett D'Amore 			break;
275260e9a87SYuri Pankov 		case 'w':
276260e9a87SYuri Pankov 			outmode = OUTMODE_FLN;
277260e9a87SYuri Pankov 			break;
27895c635efSGarrett D'Amore 		default:
279260e9a87SYuri Pankov 			show_usage = 1;
280260e9a87SYuri Pankov 			break;
28195c635efSGarrett D'Amore 		}
282260e9a87SYuri Pankov 	}
283260e9a87SYuri Pankov 
284260e9a87SYuri Pankov 	if (show_usage)
285260e9a87SYuri Pankov 		usage(search.argmode);
28695c635efSGarrett D'Amore 
287260e9a87SYuri Pankov 	/* Postprocess options. */
288260e9a87SYuri Pankov 
289260e9a87SYuri Pankov 	if (outmode == OUTMODE_DEF) {
290260e9a87SYuri Pankov 		switch (search.argmode) {
291260e9a87SYuri Pankov 		case ARG_FILE:
292260e9a87SYuri Pankov 			outmode = OUTMODE_ALL;
293*371584c2SYuri Pankov 			use_pager = 0;
294260e9a87SYuri Pankov 			break;
295260e9a87SYuri Pankov 		case ARG_NAME:
296260e9a87SYuri Pankov 			outmode = OUTMODE_ONE;
297260e9a87SYuri Pankov 			break;
298260e9a87SYuri Pankov 		default:
299260e9a87SYuri Pankov 			outmode = OUTMODE_LST;
300260e9a87SYuri Pankov 			break;
301260e9a87SYuri Pankov 		}
302260e9a87SYuri Pankov 	}
303260e9a87SYuri Pankov 
304*371584c2SYuri Pankov 	if (outmode == OUTMODE_FLN ||
305*371584c2SYuri Pankov 	    outmode == OUTMODE_LST ||
306*371584c2SYuri Pankov 	    !isatty(STDOUT_FILENO))
307*371584c2SYuri Pankov 		use_pager = 0;
308*371584c2SYuri Pankov 
309*371584c2SYuri Pankov #if HAVE_PLEDGE
310*371584c2SYuri Pankov 	if (!use_pager)
311*371584c2SYuri Pankov 		if (pledge("stdio rpath flock", NULL) == -1)
312*371584c2SYuri Pankov 			err((int)MANDOCLEVEL_SYSERR, "pledge");
313*371584c2SYuri Pankov #endif
314*371584c2SYuri Pankov 
315260e9a87SYuri Pankov 	/* Parse arguments. */
316260e9a87SYuri Pankov 
317260e9a87SYuri Pankov 	if (argc > 0) {
318260e9a87SYuri Pankov 		argc -= optind;
319260e9a87SYuri Pankov 		argv += optind;
320260e9a87SYuri Pankov 	}
321260e9a87SYuri Pankov 	resp = NULL;
322260e9a87SYuri Pankov 
323260e9a87SYuri Pankov 	/*
324260e9a87SYuri Pankov 	 * Quirks for help(1)
325260e9a87SYuri Pankov 	 * and for a man(1) section argument without -s.
326260e9a87SYuri Pankov 	 */
327260e9a87SYuri Pankov 
328260e9a87SYuri Pankov 	if (search.argmode == ARG_NAME) {
329260e9a87SYuri Pankov 		if (*progname == 'h') {
330260e9a87SYuri Pankov 			if (argc == 0) {
331260e9a87SYuri Pankov 				argv = help_argv;
332260e9a87SYuri Pankov 				argc = 1;
333260e9a87SYuri Pankov 			}
334260e9a87SYuri Pankov 		} else if (argc > 1 &&
335260e9a87SYuri Pankov 		    ((uc = (unsigned char *)argv[0]) != NULL) &&
336260e9a87SYuri Pankov 		    ((isdigit(uc[0]) && (uc[1] == '\0' ||
337260e9a87SYuri Pankov 		      (isalpha(uc[1]) && uc[2] == '\0'))) ||
338260e9a87SYuri Pankov 		     (uc[0] == 'n' && uc[1] == '\0'))) {
339260e9a87SYuri Pankov 			search.sec = (char *)uc;
340260e9a87SYuri Pankov 			argv++;
341260e9a87SYuri Pankov 			argc--;
342260e9a87SYuri Pankov 		}
343260e9a87SYuri Pankov 		if (search.arch == NULL)
344260e9a87SYuri Pankov 			search.arch = getenv("MACHINE");
345260e9a87SYuri Pankov #ifdef MACHINE
346260e9a87SYuri Pankov 		if (search.arch == NULL)
347260e9a87SYuri Pankov 			search.arch = MACHINE;
348260e9a87SYuri Pankov #endif
349260e9a87SYuri Pankov 	}
350260e9a87SYuri Pankov 
351260e9a87SYuri Pankov 	rc = MANDOCLEVEL_OK;
352260e9a87SYuri Pankov 
353260e9a87SYuri Pankov 	/* man(1), whatis(1), apropos(1) */
354260e9a87SYuri Pankov 
355260e9a87SYuri Pankov 	if (search.argmode != ARG_FILE) {
356260e9a87SYuri Pankov 		if (argc == 0)
357260e9a87SYuri Pankov 			usage(search.argmode);
358260e9a87SYuri Pankov 
359260e9a87SYuri Pankov 		if (search.argmode == ARG_NAME &&
360260e9a87SYuri Pankov 		    outmode == OUTMODE_ONE)
361260e9a87SYuri Pankov 			search.firstmatch = 1;
362260e9a87SYuri Pankov 
363260e9a87SYuri Pankov 		/* Access the mandoc database. */
364260e9a87SYuri Pankov 
365*371584c2SYuri Pankov 		manconf_parse(&conf, conf_file, defpaths, auxpaths);
366260e9a87SYuri Pankov #if HAVE_SQLITE3
367260e9a87SYuri Pankov 		mansearch_setup(1);
368*371584c2SYuri Pankov 		if ( ! mansearch(&search, &conf.manpath,
369*371584c2SYuri Pankov 		    argc, argv, &res, &sz))
370260e9a87SYuri Pankov 			usage(search.argmode);
371260e9a87SYuri Pankov #else
372260e9a87SYuri Pankov 		if (search.argmode != ARG_NAME) {
373260e9a87SYuri Pankov 			fputs("mandoc: database support not compiled in\n",
374260e9a87SYuri Pankov 			    stderr);
375*371584c2SYuri Pankov 			return (int)MANDOCLEVEL_BADARG;
376260e9a87SYuri Pankov 		}
377260e9a87SYuri Pankov 		sz = 0;
378260e9a87SYuri Pankov #endif
379260e9a87SYuri Pankov 
380*371584c2SYuri Pankov 		if (sz == 0) {
381*371584c2SYuri Pankov 			if (search.argmode == ARG_NAME)
382*371584c2SYuri Pankov 				fs_search(&search, &conf.manpath,
383*371584c2SYuri Pankov 				    argc, argv, &res, &sz);
384*371584c2SYuri Pankov 			else
385*371584c2SYuri Pankov 				warnx("nothing appropriate");
386*371584c2SYuri Pankov 		}
387260e9a87SYuri Pankov 
388260e9a87SYuri Pankov 		if (sz == 0) {
389260e9a87SYuri Pankov 			rc = MANDOCLEVEL_BADARG;
390260e9a87SYuri Pankov 			goto out;
391260e9a87SYuri Pankov 		}
392260e9a87SYuri Pankov 
393260e9a87SYuri Pankov 		/*
394260e9a87SYuri Pankov 		 * For standard man(1) and -a output mode,
395260e9a87SYuri Pankov 		 * prepare for copying filename pointers
396260e9a87SYuri Pankov 		 * into the program parameter array.
397260e9a87SYuri Pankov 		 */
398260e9a87SYuri Pankov 
399260e9a87SYuri Pankov 		if (outmode == OUTMODE_ONE) {
400260e9a87SYuri Pankov 			argc = 1;
401*371584c2SYuri Pankov 			best_prio = 20;
402260e9a87SYuri Pankov 		} else if (outmode == OUTMODE_ALL)
403260e9a87SYuri Pankov 			argc = (int)sz;
404260e9a87SYuri Pankov 
405260e9a87SYuri Pankov 		/* Iterate all matching manuals. */
406260e9a87SYuri Pankov 
407260e9a87SYuri Pankov 		resp = res;
408260e9a87SYuri Pankov 		for (i = 0; i < sz; i++) {
409260e9a87SYuri Pankov 			if (outmode == OUTMODE_FLN)
410260e9a87SYuri Pankov 				puts(res[i].file);
411260e9a87SYuri Pankov 			else if (outmode == OUTMODE_LST)
412260e9a87SYuri Pankov 				printf("%s - %s\n", res[i].names,
413260e9a87SYuri Pankov 				    res[i].output == NULL ? "" :
414260e9a87SYuri Pankov 				    res[i].output);
415260e9a87SYuri Pankov 			else if (outmode == OUTMODE_ONE) {
416260e9a87SYuri Pankov 				/* Search for the best section. */
417*371584c2SYuri Pankov 				sec = res[i].file;
418*371584c2SYuri Pankov 				sec += strcspn(sec, "123456789");
419*371584c2SYuri Pankov 				if (sec[0] == '\0')
420260e9a87SYuri Pankov 					continue;
421*371584c2SYuri Pankov 				prio = sec_prios[sec[0] - '1'];
422*371584c2SYuri Pankov 				if (sec[1] != '/')
423*371584c2SYuri Pankov 					prio += 10;
424260e9a87SYuri Pankov 				if (prio >= best_prio)
425260e9a87SYuri Pankov 					continue;
426260e9a87SYuri Pankov 				best_prio = prio;
427260e9a87SYuri Pankov 				resp = res + i;
428260e9a87SYuri Pankov 			}
429260e9a87SYuri Pankov 		}
430260e9a87SYuri Pankov 
431260e9a87SYuri Pankov 		/*
432260e9a87SYuri Pankov 		 * For man(1), -a and -i output mode, fall through
433260e9a87SYuri Pankov 		 * to the main mandoc(1) code iterating files
434260e9a87SYuri Pankov 		 * and running the parsers on each of them.
435260e9a87SYuri Pankov 		 */
436260e9a87SYuri Pankov 
437260e9a87SYuri Pankov 		if (outmode == OUTMODE_FLN || outmode == OUTMODE_LST)
438260e9a87SYuri Pankov 			goto out;
439260e9a87SYuri Pankov 	}
440260e9a87SYuri Pankov 
441260e9a87SYuri Pankov 	/* mandoc(1) */
442260e9a87SYuri Pankov 
443*371584c2SYuri Pankov #if HAVE_PLEDGE
444*371584c2SYuri Pankov 	if (use_pager) {
445*371584c2SYuri Pankov 		if (pledge("stdio rpath tmppath tty proc exec", NULL) == -1)
446*371584c2SYuri Pankov 			err((int)MANDOCLEVEL_SYSERR, "pledge");
447*371584c2SYuri Pankov 	} else {
448*371584c2SYuri Pankov 		if (pledge("stdio rpath", NULL) == -1)
449*371584c2SYuri Pankov 			err((int)MANDOCLEVEL_SYSERR, "pledge");
450*371584c2SYuri Pankov 	}
451*371584c2SYuri Pankov #endif
452*371584c2SYuri Pankov 
453260e9a87SYuri Pankov 	if (search.argmode == ARG_FILE && ! moptions(&options, auxpaths))
454*371584c2SYuri Pankov 		return (int)MANDOCLEVEL_BADARG;
455260e9a87SYuri Pankov 
456*371584c2SYuri Pankov 	mchars_alloc();
457*371584c2SYuri Pankov 	curp.mp = mparse_alloc(options, curp.wlevel, mmsg, defos);
45895c635efSGarrett D'Amore 
45995c635efSGarrett D'Amore 	/*
46095c635efSGarrett D'Amore 	 * Conditionally start up the lookaside buffer before parsing.
46195c635efSGarrett D'Amore 	 */
46295c635efSGarrett D'Amore 	if (OUTT_MAN == curp.outtype)
46395c635efSGarrett D'Amore 		mparse_keep(curp.mp);
46495c635efSGarrett D'Amore 
465260e9a87SYuri Pankov 	if (argc < 1) {
466*371584c2SYuri Pankov 		if (use_pager)
467*371584c2SYuri Pankov 			tag_files = tag_init();
468*371584c2SYuri Pankov 		parse(&curp, STDIN_FILENO, "<stdin>");
469260e9a87SYuri Pankov 	}
47095c635efSGarrett D'Amore 
471260e9a87SYuri Pankov 	while (argc > 0) {
472*371584c2SYuri Pankov 		fd = mparse_open(curp.mp, resp != NULL ? resp->file : *argv);
473260e9a87SYuri Pankov 		if (fd != -1) {
474*371584c2SYuri Pankov 			if (use_pager) {
475*371584c2SYuri Pankov 				tag_files = tag_init();
476*371584c2SYuri Pankov 				use_pager = 0;
477*371584c2SYuri Pankov 			}
478260e9a87SYuri Pankov 
479260e9a87SYuri Pankov 			if (resp == NULL)
480*371584c2SYuri Pankov 				parse(&curp, fd, *argv);
481260e9a87SYuri Pankov 			else if (resp->form & FORM_SRC) {
482260e9a87SYuri Pankov 				/* For .so only; ignore failure. */
483*371584c2SYuri Pankov 				chdir(conf.manpath.paths[resp->ipath]);
484*371584c2SYuri Pankov 				parse(&curp, fd, resp->file);
485*371584c2SYuri Pankov 			} else
486*371584c2SYuri Pankov 				passthrough(resp->file, fd,
487*371584c2SYuri Pankov 				    conf.output.synopsisonly);
488260e9a87SYuri Pankov 
489260e9a87SYuri Pankov 			if (argc > 1 && curp.outtype <= OUTT_UTF8)
490*371584c2SYuri Pankov 				terminal_sepline(curp.outdata);
491*371584c2SYuri Pankov 		} else if (rc < MANDOCLEVEL_ERROR)
492*371584c2SYuri Pankov 			rc = MANDOCLEVEL_ERROR;
49395c635efSGarrett D'Amore 
49495c635efSGarrett D'Amore 		if (MANDOCLEVEL_OK != rc && curp.wstop)
49595c635efSGarrett D'Amore 			break;
496260e9a87SYuri Pankov 
497260e9a87SYuri Pankov 		if (resp != NULL)
498260e9a87SYuri Pankov 			resp++;
499260e9a87SYuri Pankov 		else
500260e9a87SYuri Pankov 			argv++;
501260e9a87SYuri Pankov 		if (--argc)
502260e9a87SYuri Pankov 			mparse_reset(curp.mp);
50395c635efSGarrett D'Amore 	}
50495c635efSGarrett D'Amore 
505*371584c2SYuri Pankov 	if (curp.outdata != NULL) {
506*371584c2SYuri Pankov 		switch (curp.outtype) {
507*371584c2SYuri Pankov 		case OUTT_HTML:
508*371584c2SYuri Pankov 			html_free(curp.outdata);
509*371584c2SYuri Pankov 			break;
510*371584c2SYuri Pankov 		case OUTT_UTF8:
511*371584c2SYuri Pankov 		case OUTT_LOCALE:
512*371584c2SYuri Pankov 		case OUTT_ASCII:
513*371584c2SYuri Pankov 			ascii_free(curp.outdata);
514*371584c2SYuri Pankov 			break;
515*371584c2SYuri Pankov 		case OUTT_PDF:
516*371584c2SYuri Pankov 		case OUTT_PS:
517*371584c2SYuri Pankov 			pspdf_free(curp.outdata);
518*371584c2SYuri Pankov 			break;
519*371584c2SYuri Pankov 		default:
520*371584c2SYuri Pankov 			break;
521*371584c2SYuri Pankov 		}
522*371584c2SYuri Pankov 	}
523260e9a87SYuri Pankov 	mparse_free(curp.mp);
524*371584c2SYuri Pankov 	mchars_free();
525260e9a87SYuri Pankov 
526260e9a87SYuri Pankov out:
527260e9a87SYuri Pankov 	if (search.argmode != ARG_FILE) {
528*371584c2SYuri Pankov 		manconf_free(&conf);
529260e9a87SYuri Pankov #if HAVE_SQLITE3
530260e9a87SYuri Pankov 		mansearch_free(res, sz);
531260e9a87SYuri Pankov 		mansearch_setup(0);
532260e9a87SYuri Pankov #endif
533260e9a87SYuri Pankov 	}
534260e9a87SYuri Pankov 
535698f87a4SGarrett D'Amore 	free(defos);
53695c635efSGarrett D'Amore 
537260e9a87SYuri Pankov 	/*
538*371584c2SYuri Pankov 	 * When using a pager, finish writing both temporary files,
539*371584c2SYuri Pankov 	 * fork it, wait for the user to close it, and clean up.
540260e9a87SYuri Pankov 	 */
541260e9a87SYuri Pankov 
542*371584c2SYuri Pankov 	if (tag_files != NULL) {
543260e9a87SYuri Pankov 		fclose(stdout);
544*371584c2SYuri Pankov 		tag_write();
545*371584c2SYuri Pankov 		man_pgid = getpgid(0);
546*371584c2SYuri Pankov 		tag_files->tcpgid = man_pgid == getpid() ?
547*371584c2SYuri Pankov 		    getpgid(getppid()) : man_pgid;
548*371584c2SYuri Pankov 		pager_pid = 0;
549*371584c2SYuri Pankov 		signum = SIGSTOP;
550*371584c2SYuri Pankov 		for (;;) {
551*371584c2SYuri Pankov 
552*371584c2SYuri Pankov 			/* Stop here until moved to the foreground. */
553*371584c2SYuri Pankov 
554*371584c2SYuri Pankov 			tc_pgid = tcgetpgrp(STDIN_FILENO);
555*371584c2SYuri Pankov 			if (tc_pgid != man_pgid) {
556*371584c2SYuri Pankov 				if (tc_pgid == pager_pid) {
557*371584c2SYuri Pankov 					(void)tcsetpgrp(STDIN_FILENO,
558*371584c2SYuri Pankov 					    man_pgid);
559*371584c2SYuri Pankov 					if (signum == SIGTTIN)
560*371584c2SYuri Pankov 						continue;
561*371584c2SYuri Pankov 				} else
562*371584c2SYuri Pankov 					tag_files->tcpgid = tc_pgid;
563*371584c2SYuri Pankov 				kill(0, signum);
564*371584c2SYuri Pankov 				continue;
565*371584c2SYuri Pankov 			}
566*371584c2SYuri Pankov 
567*371584c2SYuri Pankov 			/* Once in the foreground, activate the pager. */
568*371584c2SYuri Pankov 
569*371584c2SYuri Pankov 			if (pager_pid) {
570*371584c2SYuri Pankov 				(void)tcsetpgrp(STDIN_FILENO, pager_pid);
571*371584c2SYuri Pankov 				kill(pager_pid, SIGCONT);
572*371584c2SYuri Pankov 			} else
573*371584c2SYuri Pankov 				pager_pid = spawn_pager(tag_files);
574*371584c2SYuri Pankov 
575*371584c2SYuri Pankov 			/* Wait for the pager to stop or exit. */
576*371584c2SYuri Pankov 
577*371584c2SYuri Pankov 			while ((pid = waitpid(pager_pid, &status,
578*371584c2SYuri Pankov 			    WUNTRACED)) == -1 && errno == EINTR)
579*371584c2SYuri Pankov 				continue;
580*371584c2SYuri Pankov 
581*371584c2SYuri Pankov 			if (pid == -1) {
582*371584c2SYuri Pankov 				warn("wait");
583*371584c2SYuri Pankov 				rc = MANDOCLEVEL_SYSERR;
584*371584c2SYuri Pankov 				break;
585*371584c2SYuri Pankov 			}
586*371584c2SYuri Pankov 			if (!WIFSTOPPED(status))
587*371584c2SYuri Pankov 				break;
588*371584c2SYuri Pankov 
589*371584c2SYuri Pankov 			signum = WSTOPSIG(status);
590*371584c2SYuri Pankov 		}
591*371584c2SYuri Pankov 		tag_unlink();
592260e9a87SYuri Pankov 	}
593260e9a87SYuri Pankov 
594*371584c2SYuri Pankov 	return (int)rc;
59595c635efSGarrett D'Amore }
59695c635efSGarrett D'Amore 
59795c635efSGarrett D'Amore static void
598260e9a87SYuri Pankov usage(enum argmode argmode)
59995c635efSGarrett D'Amore {
60095c635efSGarrett D'Amore 
601260e9a87SYuri Pankov 	switch (argmode) {
602260e9a87SYuri Pankov 	case ARG_FILE:
603*371584c2SYuri Pankov 		fputs("usage: mandoc [-acfhkl] [-I os=name] "
604*371584c2SYuri Pankov 		    "[-K encoding] [-mformat] [-O option]\n"
605*371584c2SYuri Pankov 		    "\t      [-T output] [-W level] [file ...]\n", stderr);
606260e9a87SYuri Pankov 		break;
607260e9a87SYuri Pankov 	case ARG_NAME:
608260e9a87SYuri Pankov 		fputs("usage: man [-acfhklw] [-C file] [-I os=name] "
609260e9a87SYuri Pankov 		    "[-K encoding] [-M path] [-m path]\n"
610260e9a87SYuri Pankov 		    "\t   [-O option=value] [-S subsection] [-s section] "
611260e9a87SYuri Pankov 		    "[-T output] [-W level]\n"
612260e9a87SYuri Pankov 		    "\t   [section] name ...\n", stderr);
613260e9a87SYuri Pankov 		break;
614260e9a87SYuri Pankov 	case ARG_WORD:
615260e9a87SYuri Pankov 		fputs("usage: whatis [-acfhklw] [-C file] "
616260e9a87SYuri Pankov 		    "[-M path] [-m path] [-O outkey] [-S arch]\n"
617260e9a87SYuri Pankov 		    "\t      [-s section] name ...\n", stderr);
618260e9a87SYuri Pankov 		break;
619260e9a87SYuri Pankov 	case ARG_EXPR:
620260e9a87SYuri Pankov 		fputs("usage: apropos [-acfhklw] [-C file] "
621260e9a87SYuri Pankov 		    "[-M path] [-m path] [-O outkey] [-S arch]\n"
622260e9a87SYuri Pankov 		    "\t       [-s section] expression ...\n", stderr);
623260e9a87SYuri Pankov 		break;
624260e9a87SYuri Pankov 	}
625260e9a87SYuri Pankov 	exit((int)MANDOCLEVEL_BADARG);
62695c635efSGarrett D'Amore }
62795c635efSGarrett D'Amore 
628260e9a87SYuri Pankov static int
629260e9a87SYuri Pankov fs_lookup(const struct manpaths *paths, size_t ipath,
630260e9a87SYuri Pankov 	const char *sec, const char *arch, const char *name,
631260e9a87SYuri Pankov 	struct manpage **res, size_t *ressz)
63295c635efSGarrett D'Amore {
633260e9a87SYuri Pankov 	glob_t		 globinfo;
634260e9a87SYuri Pankov 	struct manpage	*page;
635260e9a87SYuri Pankov 	char		*file;
636260e9a87SYuri Pankov 	int		 form, globres;
637260e9a87SYuri Pankov 
638260e9a87SYuri Pankov 	form = FORM_SRC;
639260e9a87SYuri Pankov 	mandoc_asprintf(&file, "%s/man%s/%s.%s",
640260e9a87SYuri Pankov 	    paths->paths[ipath], sec, name, sec);
641260e9a87SYuri Pankov 	if (access(file, R_OK) != -1)
642260e9a87SYuri Pankov 		goto found;
643260e9a87SYuri Pankov 	free(file);
644260e9a87SYuri Pankov 
645260e9a87SYuri Pankov 	mandoc_asprintf(&file, "%s/cat%s/%s.0",
646260e9a87SYuri Pankov 	    paths->paths[ipath], sec, name);
647260e9a87SYuri Pankov 	if (access(file, R_OK) != -1) {
648260e9a87SYuri Pankov 		form = FORM_CAT;
649260e9a87SYuri Pankov 		goto found;
650260e9a87SYuri Pankov 	}
651260e9a87SYuri Pankov 	free(file);
652260e9a87SYuri Pankov 
653260e9a87SYuri Pankov 	if (arch != NULL) {
654260e9a87SYuri Pankov 		mandoc_asprintf(&file, "%s/man%s/%s/%s.%s",
655260e9a87SYuri Pankov 		    paths->paths[ipath], sec, arch, name, sec);
656260e9a87SYuri Pankov 		if (access(file, R_OK) != -1)
657260e9a87SYuri Pankov 			goto found;
658260e9a87SYuri Pankov 		free(file);
659260e9a87SYuri Pankov 	}
660260e9a87SYuri Pankov 
661*371584c2SYuri Pankov 	mandoc_asprintf(&file, "%s/man%s/%s.[01-9]*",
662260e9a87SYuri Pankov 	    paths->paths[ipath], sec, name);
663260e9a87SYuri Pankov 	globres = glob(file, 0, NULL, &globinfo);
664260e9a87SYuri Pankov 	if (globres != 0 && globres != GLOB_NOMATCH)
665*371584c2SYuri Pankov 		warn("%s: glob", file);
666260e9a87SYuri Pankov 	free(file);
667260e9a87SYuri Pankov 	if (globres == 0)
668260e9a87SYuri Pankov 		file = mandoc_strdup(*globinfo.gl_pathv);
669260e9a87SYuri Pankov 	globfree(&globinfo);
670260e9a87SYuri Pankov 	if (globres != 0)
671*371584c2SYuri Pankov 		return 0;
67295c635efSGarrett D'Amore 
673260e9a87SYuri Pankov found:
674260e9a87SYuri Pankov #if HAVE_SQLITE3
675*371584c2SYuri Pankov 	warnx("outdated mandoc.db lacks %s(%s) entry, run %s %s",
676*371584c2SYuri Pankov 	    name, sec, BINM_MAKEWHATIS, paths->paths[ipath]);
677260e9a87SYuri Pankov #endif
678260e9a87SYuri Pankov 	*res = mandoc_reallocarray(*res, ++*ressz, sizeof(struct manpage));
679260e9a87SYuri Pankov 	page = *res + (*ressz - 1);
680260e9a87SYuri Pankov 	page->file = file;
681260e9a87SYuri Pankov 	page->names = NULL;
682260e9a87SYuri Pankov 	page->output = NULL;
683260e9a87SYuri Pankov 	page->ipath = ipath;
684260e9a87SYuri Pankov 	page->bits = NAME_FILE & NAME_MASK;
685260e9a87SYuri Pankov 	page->sec = (*sec >= '1' && *sec <= '9') ? *sec - '1' + 1 : 10;
686260e9a87SYuri Pankov 	page->form = form;
687*371584c2SYuri Pankov 	return 1;
688260e9a87SYuri Pankov }
689260e9a87SYuri Pankov 
690260e9a87SYuri Pankov static void
691260e9a87SYuri Pankov fs_search(const struct mansearch *cfg, const struct manpaths *paths,
692260e9a87SYuri Pankov 	int argc, char **argv, struct manpage **res, size_t *ressz)
693260e9a87SYuri Pankov {
694260e9a87SYuri Pankov 	const char *const sections[] =
695*371584c2SYuri Pankov 	    {"1", "8", "6", "2", "3", "5", "7", "4", "9", "3p"};
696260e9a87SYuri Pankov 	const size_t nsec = sizeof(sections)/sizeof(sections[0]);
697260e9a87SYuri Pankov 
698260e9a87SYuri Pankov 	size_t		 ipath, isec, lastsz;
699260e9a87SYuri Pankov 
700260e9a87SYuri Pankov 	assert(cfg->argmode == ARG_NAME);
701260e9a87SYuri Pankov 
702260e9a87SYuri Pankov 	*res = NULL;
703260e9a87SYuri Pankov 	*ressz = lastsz = 0;
704260e9a87SYuri Pankov 	while (argc) {
705260e9a87SYuri Pankov 		for (ipath = 0; ipath < paths->sz; ipath++) {
706260e9a87SYuri Pankov 			if (cfg->sec != NULL) {
707260e9a87SYuri Pankov 				if (fs_lookup(paths, ipath, cfg->sec,
708260e9a87SYuri Pankov 				    cfg->arch, *argv, res, ressz) &&
709260e9a87SYuri Pankov 				    cfg->firstmatch)
710260e9a87SYuri Pankov 					return;
711260e9a87SYuri Pankov 			} else for (isec = 0; isec < nsec; isec++)
712260e9a87SYuri Pankov 				if (fs_lookup(paths, ipath, sections[isec],
713260e9a87SYuri Pankov 				    cfg->arch, *argv, res, ressz) &&
714260e9a87SYuri Pankov 				    cfg->firstmatch)
715260e9a87SYuri Pankov 					return;
716260e9a87SYuri Pankov 		}
717260e9a87SYuri Pankov 		if (*ressz == lastsz)
718*371584c2SYuri Pankov 			warnx("No entry for %s in the manual.", *argv);
719260e9a87SYuri Pankov 		lastsz = *ressz;
720260e9a87SYuri Pankov 		argv++;
721260e9a87SYuri Pankov 		argc--;
722260e9a87SYuri Pankov 	}
72395c635efSGarrett D'Amore }
72495c635efSGarrett D'Amore 
72595c635efSGarrett D'Amore static void
726*371584c2SYuri Pankov parse(struct curparse *curp, int fd, const char *file)
72795c635efSGarrett D'Amore {
728*371584c2SYuri Pankov 	enum mandoclevel  rctmp;
729*371584c2SYuri Pankov 	struct roff_man	 *man;
73095c635efSGarrett D'Amore 
73195c635efSGarrett D'Amore 	/* Begin by parsing the file itself. */
73295c635efSGarrett D'Amore 
73395c635efSGarrett D'Amore 	assert(file);
734*371584c2SYuri Pankov 	assert(fd >= 0);
73595c635efSGarrett D'Amore 
736*371584c2SYuri Pankov 	rctmp = mparse_readfd(curp->mp, fd, file);
737*371584c2SYuri Pankov 	if (fd != STDIN_FILENO)
738*371584c2SYuri Pankov 		close(fd);
739*371584c2SYuri Pankov 	if (rc < rctmp)
740*371584c2SYuri Pankov 		rc = rctmp;
74195c635efSGarrett D'Amore 
74295c635efSGarrett D'Amore 	/*
74395c635efSGarrett D'Amore 	 * With -Wstop and warnings or errors of at least the requested
74495c635efSGarrett D'Amore 	 * level, do not produce output.
74595c635efSGarrett D'Amore 	 */
74695c635efSGarrett D'Amore 
747*371584c2SYuri Pankov 	if (rctmp != MANDOCLEVEL_OK && curp->wstop)
748*371584c2SYuri Pankov 		return;
74995c635efSGarrett D'Amore 
75095c635efSGarrett D'Amore 	/* If unset, allocate output dev now (if applicable). */
75195c635efSGarrett D'Amore 
752*371584c2SYuri Pankov 	if (curp->outdata == NULL) {
75395c635efSGarrett D'Amore 		switch (curp->outtype) {
754260e9a87SYuri Pankov 		case OUTT_HTML:
755*371584c2SYuri Pankov 			curp->outdata = html_alloc(curp->outopts);
75695c635efSGarrett D'Amore 			break;
757260e9a87SYuri Pankov 		case OUTT_UTF8:
758*371584c2SYuri Pankov 			curp->outdata = utf8_alloc(curp->outopts);
75995c635efSGarrett D'Amore 			break;
760260e9a87SYuri Pankov 		case OUTT_LOCALE:
761*371584c2SYuri Pankov 			curp->outdata = locale_alloc(curp->outopts);
76295c635efSGarrett D'Amore 			break;
763260e9a87SYuri Pankov 		case OUTT_ASCII:
764*371584c2SYuri Pankov 			curp->outdata = ascii_alloc(curp->outopts);
76595c635efSGarrett D'Amore 			break;
766260e9a87SYuri Pankov 		case OUTT_PDF:
767*371584c2SYuri Pankov 			curp->outdata = pdf_alloc(curp->outopts);
76895c635efSGarrett D'Amore 			break;
769260e9a87SYuri Pankov 		case OUTT_PS:
770*371584c2SYuri Pankov 			curp->outdata = ps_alloc(curp->outopts);
77195c635efSGarrett D'Amore 			break;
77295c635efSGarrett D'Amore 		default:
77395c635efSGarrett D'Amore 			break;
77495c635efSGarrett D'Amore 		}
775*371584c2SYuri Pankov 	}
776*371584c2SYuri Pankov 
777*371584c2SYuri Pankov 	mparse_result(curp->mp, &man, NULL);
77895c635efSGarrett D'Amore 
779*371584c2SYuri Pankov 	/* Execute the out device, if it exists. */
780*371584c2SYuri Pankov 
781*371584c2SYuri Pankov 	if (man == NULL)
782*371584c2SYuri Pankov 		return;
783*371584c2SYuri Pankov 	if (man->macroset == MACROSET_MDOC) {
784*371584c2SYuri Pankov 		mdoc_validate(man);
78595c635efSGarrett D'Amore 		switch (curp->outtype) {
786260e9a87SYuri Pankov 		case OUTT_HTML:
787*371584c2SYuri Pankov 			html_mdoc(curp->outdata, man);
78895c635efSGarrett D'Amore 			break;
789260e9a87SYuri Pankov 		case OUTT_TREE:
790*371584c2SYuri Pankov 			tree_mdoc(curp->outdata, man);
79195c635efSGarrett D'Amore 			break;
792260e9a87SYuri Pankov 		case OUTT_MAN:
793*371584c2SYuri Pankov 			man_mdoc(curp->outdata, man);
79495c635efSGarrett D'Amore 			break;
795260e9a87SYuri Pankov 		case OUTT_PDF:
796260e9a87SYuri Pankov 		case OUTT_ASCII:
797260e9a87SYuri Pankov 		case OUTT_UTF8:
798260e9a87SYuri Pankov 		case OUTT_LOCALE:
799260e9a87SYuri Pankov 		case OUTT_PS:
800*371584c2SYuri Pankov 			terminal_mdoc(curp->outdata, man);
801*371584c2SYuri Pankov 			break;
802*371584c2SYuri Pankov 		default:
803*371584c2SYuri Pankov 			break;
804*371584c2SYuri Pankov 		}
805*371584c2SYuri Pankov 	}
806*371584c2SYuri Pankov 	if (man->macroset == MACROSET_MAN) {
807*371584c2SYuri Pankov 		man_validate(man);
808*371584c2SYuri Pankov 		switch (curp->outtype) {
809*371584c2SYuri Pankov 		case OUTT_HTML:
810*371584c2SYuri Pankov 			html_man(curp->outdata, man);
811*371584c2SYuri Pankov 			break;
812*371584c2SYuri Pankov 		case OUTT_TREE:
813*371584c2SYuri Pankov 			tree_man(curp->outdata, man);
814*371584c2SYuri Pankov 			break;
815*371584c2SYuri Pankov 		case OUTT_MAN:
816*371584c2SYuri Pankov 			man_man(curp->outdata, man);
817*371584c2SYuri Pankov 			break;
818*371584c2SYuri Pankov 		case OUTT_PDF:
819*371584c2SYuri Pankov 		case OUTT_ASCII:
820*371584c2SYuri Pankov 		case OUTT_UTF8:
821*371584c2SYuri Pankov 		case OUTT_LOCALE:
822*371584c2SYuri Pankov 		case OUTT_PS:
823*371584c2SYuri Pankov 			terminal_man(curp->outdata, man);
82495c635efSGarrett D'Amore 			break;
82595c635efSGarrett D'Amore 		default:
82695c635efSGarrett D'Amore 			break;
82795c635efSGarrett D'Amore 		}
82895c635efSGarrett D'Amore 	}
82995c635efSGarrett D'Amore }
83095c635efSGarrett D'Amore 
831*371584c2SYuri Pankov static void
832260e9a87SYuri Pankov passthrough(const char *file, int fd, int synopsis_only)
833260e9a87SYuri Pankov {
834260e9a87SYuri Pankov 	const char	 synb[] = "S\bSY\bYN\bNO\bOP\bPS\bSI\bIS\bS";
835260e9a87SYuri Pankov 	const char	 synr[] = "SYNOPSIS";
836260e9a87SYuri Pankov 
837260e9a87SYuri Pankov 	FILE		*stream;
838260e9a87SYuri Pankov 	const char	*syscall;
839*371584c2SYuri Pankov 	char		*line, *cp;
840*371584c2SYuri Pankov 	size_t		 linesz;
841260e9a87SYuri Pankov 	int		 print;
842260e9a87SYuri Pankov 
843*371584c2SYuri Pankov 	line = NULL;
844*371584c2SYuri Pankov 	linesz = 0;
845260e9a87SYuri Pankov 
846260e9a87SYuri Pankov 	if ((stream = fdopen(fd, "r")) == NULL) {
847260e9a87SYuri Pankov 		close(fd);
848260e9a87SYuri Pankov 		syscall = "fdopen";
849260e9a87SYuri Pankov 		goto fail;
850260e9a87SYuri Pankov 	}
851260e9a87SYuri Pankov 
852260e9a87SYuri Pankov 	print = 0;
853*371584c2SYuri Pankov 	while (getline(&line, &linesz, stream) != -1) {
854*371584c2SYuri Pankov 		cp = line;
855260e9a87SYuri Pankov 		if (synopsis_only) {
856260e9a87SYuri Pankov 			if (print) {
857*371584c2SYuri Pankov 				if ( ! isspace((unsigned char)*cp))
858260e9a87SYuri Pankov 					goto done;
859*371584c2SYuri Pankov 				while (isspace((unsigned char)*cp))
860*371584c2SYuri Pankov 					cp++;
861260e9a87SYuri Pankov 			} else {
862*371584c2SYuri Pankov 				if (strcmp(cp, synb) == 0 ||
863*371584c2SYuri Pankov 				    strcmp(cp, synr) == 0)
864260e9a87SYuri Pankov 					print = 1;
865260e9a87SYuri Pankov 				continue;
866260e9a87SYuri Pankov 			}
867260e9a87SYuri Pankov 		}
868*371584c2SYuri Pankov 		if (fputs(cp, stdout)) {
869*371584c2SYuri Pankov 			fclose(stream);
870*371584c2SYuri Pankov 			syscall = "fputs";
871*371584c2SYuri Pankov 			goto fail;
872*371584c2SYuri Pankov 		}
873260e9a87SYuri Pankov 	}
874260e9a87SYuri Pankov 
875260e9a87SYuri Pankov 	if (ferror(stream)) {
876260e9a87SYuri Pankov 		fclose(stream);
877*371584c2SYuri Pankov 		syscall = "getline";
878260e9a87SYuri Pankov 		goto fail;
879260e9a87SYuri Pankov 	}
880260e9a87SYuri Pankov 
881260e9a87SYuri Pankov done:
882*371584c2SYuri Pankov 	free(line);
883260e9a87SYuri Pankov 	fclose(stream);
884*371584c2SYuri Pankov 	return;
885260e9a87SYuri Pankov 
886260e9a87SYuri Pankov fail:
887*371584c2SYuri Pankov 	free(line);
888*371584c2SYuri Pankov 	warn("%s: SYSERR: %s", file, syscall);
889*371584c2SYuri Pankov 	if (rc < MANDOCLEVEL_SYSERR)
890*371584c2SYuri Pankov 		rc = MANDOCLEVEL_SYSERR;
891260e9a87SYuri Pankov }
892260e9a87SYuri Pankov 
893260e9a87SYuri Pankov static int
894260e9a87SYuri Pankov koptions(int *options, char *arg)
895260e9a87SYuri Pankov {
896260e9a87SYuri Pankov 
897260e9a87SYuri Pankov 	if ( ! strcmp(arg, "utf-8")) {
898260e9a87SYuri Pankov 		*options |=  MPARSE_UTF8;
899260e9a87SYuri Pankov 		*options &= ~MPARSE_LATIN1;
900260e9a87SYuri Pankov 	} else if ( ! strcmp(arg, "iso-8859-1")) {
901260e9a87SYuri Pankov 		*options |=  MPARSE_LATIN1;
902260e9a87SYuri Pankov 		*options &= ~MPARSE_UTF8;
903260e9a87SYuri Pankov 	} else if ( ! strcmp(arg, "us-ascii")) {
904260e9a87SYuri Pankov 		*options &= ~(MPARSE_UTF8 | MPARSE_LATIN1);
905260e9a87SYuri Pankov 	} else {
906*371584c2SYuri Pankov 		warnx("-K %s: Bad argument", arg);
907*371584c2SYuri Pankov 		return 0;
908260e9a87SYuri Pankov 	}
909*371584c2SYuri Pankov 	return 1;
910260e9a87SYuri Pankov }
911260e9a87SYuri Pankov 
91295c635efSGarrett D'Amore static int
913260e9a87SYuri Pankov moptions(int *options, char *arg)
91495c635efSGarrett D'Amore {
91595c635efSGarrett D'Amore 
916260e9a87SYuri Pankov 	if (arg == NULL)
917260e9a87SYuri Pankov 		/* nothing to do */;
918260e9a87SYuri Pankov 	else if (0 == strcmp(arg, "doc"))
919260e9a87SYuri Pankov 		*options |= MPARSE_MDOC;
92095c635efSGarrett D'Amore 	else if (0 == strcmp(arg, "andoc"))
921260e9a87SYuri Pankov 		/* nothing to do */;
92295c635efSGarrett D'Amore 	else if (0 == strcmp(arg, "an"))
923260e9a87SYuri Pankov 		*options |= MPARSE_MAN;
92495c635efSGarrett D'Amore 	else {
925*371584c2SYuri Pankov 		warnx("-m %s: Bad argument", arg);
926*371584c2SYuri Pankov 		return 0;
92795c635efSGarrett D'Amore 	}
92895c635efSGarrett D'Amore 
929*371584c2SYuri Pankov 	return 1;
93095c635efSGarrett D'Amore }
93195c635efSGarrett D'Amore 
93295c635efSGarrett D'Amore static int
93395c635efSGarrett D'Amore toptions(struct curparse *curp, char *arg)
93495c635efSGarrett D'Amore {
93595c635efSGarrett D'Amore 
93695c635efSGarrett D'Amore 	if (0 == strcmp(arg, "ascii"))
93795c635efSGarrett D'Amore 		curp->outtype = OUTT_ASCII;
93895c635efSGarrett D'Amore 	else if (0 == strcmp(arg, "lint")) {
93995c635efSGarrett D'Amore 		curp->outtype = OUTT_LINT;
94095c635efSGarrett D'Amore 		curp->wlevel  = MANDOCLEVEL_WARNING;
94195c635efSGarrett D'Amore 	} else if (0 == strcmp(arg, "tree"))
94295c635efSGarrett D'Amore 		curp->outtype = OUTT_TREE;
94395c635efSGarrett D'Amore 	else if (0 == strcmp(arg, "man"))
94495c635efSGarrett D'Amore 		curp->outtype = OUTT_MAN;
94595c635efSGarrett D'Amore 	else if (0 == strcmp(arg, "html"))
94695c635efSGarrett D'Amore 		curp->outtype = OUTT_HTML;
94795c635efSGarrett D'Amore 	else if (0 == strcmp(arg, "utf8"))
94895c635efSGarrett D'Amore 		curp->outtype = OUTT_UTF8;
94995c635efSGarrett D'Amore 	else if (0 == strcmp(arg, "locale"))
95095c635efSGarrett D'Amore 		curp->outtype = OUTT_LOCALE;
95195c635efSGarrett D'Amore 	else if (0 == strcmp(arg, "xhtml"))
952260e9a87SYuri Pankov 		curp->outtype = OUTT_HTML;
95395c635efSGarrett D'Amore 	else if (0 == strcmp(arg, "ps"))
95495c635efSGarrett D'Amore 		curp->outtype = OUTT_PS;
95595c635efSGarrett D'Amore 	else if (0 == strcmp(arg, "pdf"))
95695c635efSGarrett D'Amore 		curp->outtype = OUTT_PDF;
95795c635efSGarrett D'Amore 	else {
958*371584c2SYuri Pankov 		warnx("-T %s: Bad argument", arg);
959*371584c2SYuri Pankov 		return 0;
96095c635efSGarrett D'Amore 	}
96195c635efSGarrett D'Amore 
962*371584c2SYuri Pankov 	return 1;
96395c635efSGarrett D'Amore }
96495c635efSGarrett D'Amore 
96595c635efSGarrett D'Amore static int
96695c635efSGarrett D'Amore woptions(struct curparse *curp, char *arg)
96795c635efSGarrett D'Amore {
96895c635efSGarrett D'Amore 	char		*v, *o;
969260e9a87SYuri Pankov 	const char	*toks[7];
97095c635efSGarrett D'Amore 
97195c635efSGarrett D'Amore 	toks[0] = "stop";
97295c635efSGarrett D'Amore 	toks[1] = "all";
97395c635efSGarrett D'Amore 	toks[2] = "warning";
97495c635efSGarrett D'Amore 	toks[3] = "error";
975260e9a87SYuri Pankov 	toks[4] = "unsupp";
976260e9a87SYuri Pankov 	toks[5] = "fatal";
977260e9a87SYuri Pankov 	toks[6] = NULL;
97895c635efSGarrett D'Amore 
97995c635efSGarrett D'Amore 	while (*arg) {
98095c635efSGarrett D'Amore 		o = arg;
98195c635efSGarrett D'Amore 		switch (getsubopt(&arg, UNCONST(toks), &v)) {
982260e9a87SYuri Pankov 		case 0:
98395c635efSGarrett D'Amore 			curp->wstop = 1;
98495c635efSGarrett D'Amore 			break;
985260e9a87SYuri Pankov 		case 1:
986260e9a87SYuri Pankov 		case 2:
98795c635efSGarrett D'Amore 			curp->wlevel = MANDOCLEVEL_WARNING;
98895c635efSGarrett D'Amore 			break;
989260e9a87SYuri Pankov 		case 3:
99095c635efSGarrett D'Amore 			curp->wlevel = MANDOCLEVEL_ERROR;
99195c635efSGarrett D'Amore 			break;
992260e9a87SYuri Pankov 		case 4:
993260e9a87SYuri Pankov 			curp->wlevel = MANDOCLEVEL_UNSUPP;
994260e9a87SYuri Pankov 			break;
995260e9a87SYuri Pankov 		case 5:
996260e9a87SYuri Pankov 			curp->wlevel = MANDOCLEVEL_BADARG;
99795c635efSGarrett D'Amore 			break;
99895c635efSGarrett D'Amore 		default:
999*371584c2SYuri Pankov 			warnx("-W %s: Bad argument", o);
1000*371584c2SYuri Pankov 			return 0;
100195c635efSGarrett D'Amore 		}
100295c635efSGarrett D'Amore 	}
100395c635efSGarrett D'Amore 
1004*371584c2SYuri Pankov 	return 1;
100595c635efSGarrett D'Amore }
100695c635efSGarrett D'Amore 
100795c635efSGarrett D'Amore static void
1008260e9a87SYuri Pankov mmsg(enum mandocerr t, enum mandoclevel lvl,
100995c635efSGarrett D'Amore 		const char *file, int line, int col, const char *msg)
101095c635efSGarrett D'Amore {
1011260e9a87SYuri Pankov 	const char	*mparse_msg;
1012260e9a87SYuri Pankov 
1013*371584c2SYuri Pankov 	fprintf(stderr, "%s: %s:", getprogname(),
1014*371584c2SYuri Pankov 	    file == NULL ? "<stdin>" : file);
1015260e9a87SYuri Pankov 
1016260e9a87SYuri Pankov 	if (line)
1017260e9a87SYuri Pankov 		fprintf(stderr, "%d:%d:", line, col + 1);
101895c635efSGarrett D'Amore 
1019260e9a87SYuri Pankov 	fprintf(stderr, " %s", mparse_strlevel(lvl));
1020260e9a87SYuri Pankov 
1021260e9a87SYuri Pankov 	if (NULL != (mparse_msg = mparse_strerror(t)))
1022260e9a87SYuri Pankov 		fprintf(stderr, ": %s", mparse_msg);
102395c635efSGarrett D'Amore 
102495c635efSGarrett D'Amore 	if (msg)
102595c635efSGarrett D'Amore 		fprintf(stderr, ": %s", msg);
102695c635efSGarrett D'Amore 
102795c635efSGarrett D'Amore 	fputc('\n', stderr);
102895c635efSGarrett D'Amore }
1029260e9a87SYuri Pankov 
1030260e9a87SYuri Pankov static pid_t
1031*371584c2SYuri Pankov spawn_pager(struct tag_files *tag_files)
1032260e9a87SYuri Pankov {
1033*371584c2SYuri Pankov 	const struct timespec timeout = { 0, 100000000 };  /* 0.1s */
1034260e9a87SYuri Pankov #define MAX_PAGER_ARGS 16
1035260e9a87SYuri Pankov 	char		*argv[MAX_PAGER_ARGS];
1036260e9a87SYuri Pankov 	const char	*pager;
1037260e9a87SYuri Pankov 	char		*cp;
1038*371584c2SYuri Pankov 	size_t		 cmdlen;
1039260e9a87SYuri Pankov 	int		 argc;
1040260e9a87SYuri Pankov 	pid_t		 pager_pid;
1041260e9a87SYuri Pankov 
1042260e9a87SYuri Pankov 	pager = getenv("MANPAGER");
1043260e9a87SYuri Pankov 	if (pager == NULL || *pager == '\0')
1044260e9a87SYuri Pankov 		pager = getenv("PAGER");
1045260e9a87SYuri Pankov 	if (pager == NULL || *pager == '\0')
1046*371584c2SYuri Pankov 		pager = "more -s";
1047260e9a87SYuri Pankov 	cp = mandoc_strdup(pager);
1048260e9a87SYuri Pankov 
1049260e9a87SYuri Pankov 	/*
1050260e9a87SYuri Pankov 	 * Parse the pager command into words.
1051260e9a87SYuri Pankov 	 * Intentionally do not do anything fancy here.
1052260e9a87SYuri Pankov 	 */
1053260e9a87SYuri Pankov 
1054260e9a87SYuri Pankov 	argc = 0;
1055*371584c2SYuri Pankov 	while (argc + 4 < MAX_PAGER_ARGS) {
1056260e9a87SYuri Pankov 		argv[argc++] = cp;
1057260e9a87SYuri Pankov 		cp = strchr(cp, ' ');
1058260e9a87SYuri Pankov 		if (cp == NULL)
1059260e9a87SYuri Pankov 			break;
1060260e9a87SYuri Pankov 		*cp++ = '\0';
1061260e9a87SYuri Pankov 		while (*cp == ' ')
1062260e9a87SYuri Pankov 			cp++;
1063260e9a87SYuri Pankov 		if (*cp == '\0')
1064260e9a87SYuri Pankov 			break;
1065260e9a87SYuri Pankov 	}
1066*371584c2SYuri Pankov 
1067*371584c2SYuri Pankov 	/* For less(1), use the tag file. */
1068*371584c2SYuri Pankov 
1069*371584c2SYuri Pankov 	if ((cmdlen = strlen(argv[0])) >= 4) {
1070*371584c2SYuri Pankov 		cp = argv[0] + cmdlen - 4;
1071*371584c2SYuri Pankov 		if (strcmp(cp, "less") == 0) {
1072*371584c2SYuri Pankov 			argv[argc++] = mandoc_strdup("-T");
1073*371584c2SYuri Pankov 			argv[argc++] = tag_files->tfn;
1074*371584c2SYuri Pankov 		}
1075*371584c2SYuri Pankov 	}
1076*371584c2SYuri Pankov 	argv[argc++] = tag_files->ofn;
1077260e9a87SYuri Pankov 	argv[argc] = NULL;
1078260e9a87SYuri Pankov 
1079*371584c2SYuri Pankov 	switch (pager_pid = fork()) {
1080*371584c2SYuri Pankov 	case -1:
1081*371584c2SYuri Pankov 		err((int)MANDOCLEVEL_SYSERR, "fork");
1082*371584c2SYuri Pankov 	case 0:
1083*371584c2SYuri Pankov 		break;
1084*371584c2SYuri Pankov 	default:
1085*371584c2SYuri Pankov 		(void)setpgid(pager_pid, 0);
1086*371584c2SYuri Pankov 		(void)tcsetpgrp(STDIN_FILENO, pager_pid);
1087*371584c2SYuri Pankov #if HAVE_PLEDGE
1088*371584c2SYuri Pankov 		if (pledge("stdio rpath tmppath tty proc", NULL) == -1)
1089*371584c2SYuri Pankov 			err((int)MANDOCLEVEL_SYSERR, "pledge");
1090*371584c2SYuri Pankov #endif
1091*371584c2SYuri Pankov 		tag_files->pager_pid = pager_pid;
1092*371584c2SYuri Pankov 		return pager_pid;
1093*371584c2SYuri Pankov 	}
1094*371584c2SYuri Pankov 
1095*371584c2SYuri Pankov 	/* The child process becomes the pager. */
1096*371584c2SYuri Pankov 
1097*371584c2SYuri Pankov 	if (dup2(tag_files->ofd, STDOUT_FILENO) == -1)
1098*371584c2SYuri Pankov 		err((int)MANDOCLEVEL_SYSERR, "pager stdout");
1099*371584c2SYuri Pankov 	close(tag_files->ofd);
1100*371584c2SYuri Pankov 	close(tag_files->tfd);
1101*371584c2SYuri Pankov 
1102*371584c2SYuri Pankov 	/* Do not start the pager before controlling the terminal. */
1103*371584c2SYuri Pankov 
1104*371584c2SYuri Pankov 	while (tcgetpgrp(STDIN_FILENO) != getpid())
1105*371584c2SYuri Pankov 		nanosleep(&timeout, NULL);
1106260e9a87SYuri Pankov 
1107260e9a87SYuri Pankov 	execvp(argv[0], argv);
1108*371584c2SYuri Pankov 	err((int)MANDOCLEVEL_SYSERR, "exec %s", argv[0]);
1109260e9a87SYuri Pankov }
1110