1cec8643Michal Nowak/*	$Id: main.c,v 1.322 2019/03/06 10:18:58 schwarze Exp $ */
295c635eGarrett D'Amore/*
3260e9a8Yuri Pankov * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
4cec8643Michal Nowak * Copyright (c) 2010-2012, 2014-2019 Ingo Schwarze <schwarze@openbsd.org>
5260e9a8Yuri Pankov * Copyright (c) 2010 Joerg Sonnenberger <joerg@netbsd.org>
695c635eGarrett D'Amore *
795c635eGarrett D'Amore * Permission to use, copy, modify, and distribute this software for any
895c635eGarrett D'Amore * purpose with or without fee is hereby granted, provided that the above
995c635eGarrett D'Amore * copyright notice and this permission notice appear in all copies.
1095c635eGarrett D'Amore *
11371584cYuri Pankov * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
1295c635eGarrett D'Amore * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13371584cYuri Pankov * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
1495c635eGarrett D'Amore * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1595c635eGarrett D'Amore * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1695c635eGarrett D'Amore * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1795c635eGarrett D'Amore * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1895c635eGarrett D'Amore */
1995c635eGarrett D'Amore#include "config.h"
20260e9a8Yuri Pankov
21260e9a8Yuri Pankov#include <sys/types.h>
226640c13Yuri Pankov#include <sys/ioctl.h>
23260e9a8Yuri Pankov#include <sys/param.h>	/* MACHINE */
24260e9a8Yuri Pankov#include <sys/wait.h>
2595c635eGarrett D'Amore
2695c635eGarrett D'Amore#include <assert.h>
27260e9a8Yuri Pankov#include <ctype.h>
28371584cYuri Pankov#if HAVE_ERR
29371584cYuri Pankov#include <err.h>
30371584cYuri Pankov#endif
31260e9a8Yuri Pankov#include <errno.h>
32260e9a8Yuri Pankov#include <fcntl.h>
33260e9a8Yuri Pankov#include <glob.h>
34371584cYuri Pankov#if HAVE_SANDBOX_INIT
35371584cYuri Pankov#include <sandbox.h>
36371584cYuri Pankov#endif
37371584cYuri Pankov#include <signal.h>
3895c635eGarrett D'Amore#include <stdio.h>
3995c635eGarrett D'Amore#include <stdint.h>
4095c635eGarrett D'Amore#include <stdlib.h>
4195c635eGarrett D'Amore#include <string.h>
42cec8643Michal Nowak#include <termios.h>
43371584cYuri Pankov#include <time.h>
4495c635eGarrett D'Amore#include <unistd.h>
4595c635eGarrett D'Amore
46260e9a8Yuri Pankov#include "mandoc_aux.h"
47371584cYuri Pankov#include "mandoc.h"
48c66b804Yuri Pankov#include "mandoc_xr.h"
49371584cYuri Pankov#include "roff.h"
5095c635eGarrett D'Amore#include "mdoc.h"
5195c635eGarrett D'Amore#include "man.h"
52cec8643Michal Nowak#include "mandoc_parse.h"
53371584cYuri Pankov#include "tag.h"
54371584cYuri Pankov#include "main.h"
55371584cYuri Pankov#include "manconf.h"
56260e9a8Yuri Pankov#include "mansearch.h"
5795c635eGarrett D'Amore
58260e9a8Yuri Pankovenum	outmode {
59260e9a8Yuri Pankov	OUTMODE_DEF = 0,
60260e9a8Yuri Pankov	OUTMODE_FLN,
61260e9a8Yuri Pankov	OUTMODE_LST,
62260e9a8Yuri Pankov	OUTMODE_ALL,
63260e9a8Yuri Pankov	OUTMODE_ONE
64260e9a8Yuri Pankov};
65260e9a8Yuri Pankov
6695c635eGarrett D'Amoreenum	outt {
6795c635eGarrett D'Amore	OUTT_ASCII = 0,	/* -Tascii */
6895c635eGarrett D'Amore	OUTT_LOCALE,	/* -Tlocale */
6995c635eGarrett D'Amore	OUTT_UTF8,	/* -Tutf8 */
7095c635eGarrett D'Amore	OUTT_TREE,	/* -Ttree */
7195c635eGarrett D'Amore	OUTT_MAN,	/* -Tman */
7295c635eGarrett D'Amore	OUTT_HTML,	/* -Thtml */
73c66b804Yuri Pankov	OUTT_MARKDOWN,	/* -Tmarkdown */
7495c635eGarrett D'Amore	OUTT_LINT,	/* -Tlint */
7595c635eGarrett D'Amore	OUTT_PS,	/* -Tps */
7695c635eGarrett D'Amore	OUTT_PDF	/* -Tpdf */
7795c635eGarrett D'Amore};
7895c635eGarrett D'Amore
7995c635eGarrett D'Amorestruct	curparse {
8095c635eGarrett D'Amore	struct mparse	 *mp;
81c66b804Yuri Pankov	struct manoutput *outopts;	/* output options */
82c66b804Yuri Pankov	void		 *outdata;	/* data for output */
83c66b804Yuri Pankov	char		 *os_s;		/* operating system for display */
8495c635eGarrett D'Amore	int		  wstop;	/* stop after a file with a warning */
85c66b804Yuri Pankov	enum mandoc_os	  os_e;		/* check base system conventions */
86260e9a8Yuri Pankov	enum outt	  outtype;	/* which output to use */
8795c635eGarrett D'Amore};
8895c635eGarrett D'Amore
89a593473Yuri Pankov
90a593473Yuri Pankovint			  mandocdb(int, char *[]);
91a593473Yuri Pankov
92cec8643Michal Nowakstatic	void		  check_xr(void);
93260e9a8Yuri Pankovstatic	int		  fs_lookup(const struct manpaths *,
94260e9a8Yuri Pankov				size_t ipath, const char *,
95260e9a8Yuri Pankov				const char *, const char *,
96260e9a8Yuri Pankov				struct manpage **, size_t *);
97c66b804Yuri Pankovstatic	int		  fs_search(const struct mansearch *,
98260e9a8Yuri Pankov				const struct manpaths *, int, char**,
99260e9a8Yuri Pankov				struct manpage **, size_t *);
100260e9a8Yuri Pankovstatic	int		  koptions(int *, char *);
101c66b804Yuri Pankovstatic	void		  moptions(int *, char *);
102a593473Yuri Pankovstatic	void		  outdata_alloc(struct curparse *);
103371584cYuri Pankovstatic	void		  parse(struct curparse *, int, const char *);
104371584cYuri Pankovstatic	void		  passthrough(const char *, int, int);
105371584cYuri Pankovstatic	pid_t		  spawn_pager(struct tag_files *);
10695c635eGarrett D'Amorestatic	int		  toptions(struct curparse *, char *);
107a40ea1aYuri Pankovstatic	void		  usage(enum argmode) __attribute__((__noreturn__));
10895c635eGarrett D'Amorestatic	int		  woptions(struct curparse *, char *);
10995c635eGarrett D'Amore
110260e9a8Yuri Pankovstatic	const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
111260e9a8Yuri Pankovstatic	char		  help_arg[] = "help";
112260e9a8Yuri Pankovstatic	char		 *help_argv[] = {help_arg, NULL};
11395c635eGarrett D'Amore
114260e9a8Yuri Pankov
11595c635eGarrett D'Amoreint
11695c635eGarrett D'Amoremain(int argc, char *argv[])
11795c635eGarrett D'Amore{
118371584cYuri Pankov	struct manconf	 conf;
119260e9a8Yuri Pankov	struct mansearch search;
120a40ea1aYuri Pankov	struct curparse	 curp;
1216640c13Yuri Pankov	struct winsize	 ws;
122371584cYuri Pankov	struct tag_files *tag_files;
123260e9a8Yuri Pankov	struct manpage	*res, *resp;
124a40ea1aYuri Pankov	const char	*progname, *sec, *thisarg;
125a40ea1aYuri Pankov	char		*conf_file, *defpaths, *auxpaths;
126cec8643Michal Nowak	char		*oarg, *tagarg;
127a40ea1aYuri Pankov	unsigned char	*uc;
128371584cYuri Pankov	size_t		 i, sz;
129371584cYuri Pankov	int		 prio, best_prio;
130260e9a8Yuri Pankov	enum outmode	 outmode;
1316640c13Yuri Pankov	int		 fd, startdir;
132260e9a8Yuri Pankov	int		 show_usage;
133260e9a8Yuri Pankov	int		 options;
134371584cYuri Pankov	int		 use_pager;
135371584cYuri Pankov	int		 status, signum;
136260e9a8Yuri Pankov	int		 c;
137371584cYuri Pankov	pid_t		 pager_pid, tc_pgid, man_pgid, pid;
13895c635eGarrett D'Amore
139371584cYuri Pankov#if HAVE_PROGNAME
140371584cYuri Pankov	progname = getprogname();
141371584cYuri Pankov#else
142260e9a8Yuri Pankov	if (argc < 1)
143371584cYuri Pankov		progname = mandoc_strdup("mandoc");
144260e9a8Yuri Pankov	else if ((progname = strrchr(argv[0], '/')) == NULL)
14595c635eGarrett D'Amore		progname = argv[0];
14695c635eGarrett D'Amore	else
14795c635eGarrett D'Amore		++progname;
148371584cYuri Pankov	setprogname(progname);
149371584cYuri Pankov#endif
15095c635eGarrett D'Amore
151cec8643Michal Nowak	mandoc_msg_setoutfile(stderr);
152371584cYuri Pankov	if (strncmp(progname, "mandocdb", 8) == 0 ||
153371584cYuri Pankov	    strcmp(progname, BINM_MAKEWHATIS) == 0)
154371584cYuri Pankov		return mandocdb(argc, argv);
155371584cYuri Pankov
156371584cYuri Pankov#if HAVE_PLEDGE
157c66b804Yuri Pankov	if (pledge("stdio rpath tmppath tty proc exec", NULL) == -1)
158371584cYuri Pankov		err((int)MANDOCLEVEL_SYSERR, "pledge");
159371584cYuri Pankov#endif
160371584cYuri Pankov
161371584cYuri Pankov#if HAVE_SANDBOX_INIT
162371584cYuri Pankov	if (sandbox_init(kSBXProfileNoInternet, SANDBOX_NAMED, NULL) == -1)
163371584cYuri Pankov		errx((int)MANDOCLEVEL_SYSERR, "sandbox_init");
164260e9a8Yuri Pankov#endif
165260e9a8Yuri Pankov
166260e9a8Yuri Pankov	/* Search options. */
16795c635eGarrett D'Amore
168371584cYuri Pankov	memset(&conf, 0, sizeof(conf));
169260e9a8Yuri Pankov	conf_file = defpaths = NULL;
170260e9a8Yuri Pankov	auxpaths = NULL;
171260e9a8Yuri Pankov
172260e9a8Yuri Pankov	memset(&search, 0, sizeof(struct mansearch));
173260e9a8Yuri Pankov	search.outkey = "Nd";
174a40ea1aYuri Pankov	oarg = NULL;
175260e9a8Yuri Pankov
176260e9a8Yuri Pankov	if (strcmp(progname, BINM_MAN) == 0)
177260e9a8Yuri Pankov		search.argmode = ARG_NAME;
178260e9a8Yuri Pankov	else if (strcmp(progname, BINM_APROPOS) == 0)
179260e9a8Yuri Pankov		search.argmode = ARG_EXPR;
180260e9a8Yuri Pankov	else if (strcmp(progname, BINM_WHATIS) == 0)
181260e9a8Yuri Pankov		search.argmode = ARG_WORD;
182260e9a8Yuri Pankov	else if (strncmp(progname, "help", 4) == 0)
183260e9a8Yuri Pankov		search.argmode = ARG_NAME;
184260e9a8Yuri Pankov	else
185260e9a8Yuri Pankov		search.argmode = ARG_FILE;
186260e9a8Yuri Pankov
187260e9a8Yuri Pankov	/* Parser and formatter options. */
188260e9a8Yuri Pankov
189260e9a8Yuri Pankov	memset(&curp, 0, sizeof(struct curparse));
190260e9a8Yuri Pankov	curp.outtype = OUTT_LOCALE;
191371584cYuri Pankov	curp.outopts = &conf.output;
192260e9a8Yuri Pankov	options = MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1;
19395c635eGarrett D'Amore
194371584cYuri Pankov	use_pager = 1;
195371584cYuri Pankov	tag_files = NULL;
196260e9a8Yuri Pankov	show_usage = 0;
197260e9a8Yuri Pankov	outmode = OUTMODE_DEF;
198260e9a8Yuri Pankov
199c66b804Yuri Pankov	while ((c = getopt(argc, argv,
200c66b804Yuri Pankov	    "aC:cfhI:iK:klM:m:O:S:s:T:VW:w")) != -1) {
201c66b804Yuri Pankov		if (c == 'i' && search.argmode == ARG_EXPR) {
202c66b804Yuri Pankov			optind--;
203c66b804Yuri Pankov			break;
204c66b804Yuri Pankov		}
20595c635eGarrett D'Amore		switch (c) {
206260e9a8Yuri Pankov		case 'a':
207260e9a8Yuri Pankov			outmode = OUTMODE_ALL;
208260e9a8Yuri Pankov			break;
209260e9a8Yuri Pankov		case 'C':
210260e9a8Yuri Pankov			conf_file = optarg;
211260e9a8Yuri Pankov			break;
212260e9a8Yuri Pankov		case 'c':
213371584cYuri Pankov			use_pager = 0;
214260e9a8Yuri Pankov			break;
215260e9a8Yuri Pankov		case 'f':
216260e9a8Yuri Pankov			search.argmode = ARG_WORD;
217260e9a8Yuri Pankov			break;
218260e9a8Yuri Pankov		case 'h':
219371584cYuri Pankov			conf.output.synopsisonly = 1;
220371584cYuri Pankov			use_pager = 0;
221260e9a8Yuri Pankov			outmode = OUTMODE_ALL;
222260e9a8Yuri Pankov			break;
223260e9a8Yuri Pankov		case 'I':
224698f87aGarrett D'Amore			if (strncmp(optarg, "os=", 3)) {
225371584cYuri Pankov				warnx("-I %s: Bad argument", optarg);
226371584cYuri Pankov				return (int)MANDOCLEVEL_BADARG;
227698f87aGarrett D'Amore			}
228c66b804Yuri Pankov			if (curp.os_s != NULL) {
229371584cYuri Pankov				warnx("-I %s: Duplicate argument", optarg);
230371584cYuri Pankov				return (int)MANDOCLEVEL_BADARG;
231698f87aGarrett D'Amore			}
232c66b804Yuri Pankov			curp.os_s = mandoc_strdup(optarg + 3);
233260e9a8Yuri Pankov			break;
234260e9a8Yuri Pankov		case 'K':
235260e9a8Yuri Pankov			if ( ! koptions(&options, optarg))
236371584cYuri Pankov				return (int)MANDOCLEVEL_BADARG;
23795c635eGarrett D'Amore			break;
238260e9a8Yuri Pankov		case 'k':
239260e9a8Yuri Pankov			search.argmode = ARG_EXPR;
240260e9a8Yuri Pankov			break;
241260e9a8Yuri Pankov		case 'l':
242260e9a8Yuri Pankov			search.argmode = ARG_FILE;
243260e9a8Yuri Pankov			outmode = OUTMODE_ALL;
244260e9a8Yuri Pankov			break;
245260e9a8Yuri Pankov		case 'M':
246260e9a8Yuri Pankov			defpaths = optarg;
247260e9a8Yuri Pankov			break;
248260e9a8Yuri Pankov		case 'm':
249260e9a8Yuri Pankov			auxpaths = optarg;
250260e9a8Yuri Pankov			break;
251260e9a8Yuri Pankov		case 'O':
252a40ea1aYuri Pankov			oarg = optarg;
25395c635eGarrett D'Amore			break;
254260e9a8Yuri Pankov		case 'S':
255260e9a8Yuri Pankov			search.arch = optarg;
256260e9a8Yuri Pankov			break;
257260e9a8Yuri Pankov		case 's':
258260e9a8Yuri Pankov			search.sec = optarg;
259260e9a8Yuri Pankov			break;
260260e9a8Yuri Pankov		case 'T':
26195c635eGarrett D'Amore			if ( ! toptions(&curp, optarg))
262371584cYuri Pankov				return (int)MANDOCLEVEL_BADARG;
26395c635eGarrett D'Amore			break;
264260e9a8Yuri Pankov		case 'W':
26595c635eGarrett D'Amore			if ( ! woptions(&curp, optarg))
266371584cYuri Pankov				return (int)MANDOCLEVEL_BADARG;
26795c635eGarrett D'Amore			break;
268260e9a8Yuri Pankov		case 'w':
269260e9a8Yuri Pankov			outmode = OUTMODE_FLN;
270260e9a8Yuri Pankov			break;
27195c635eGarrett D'Amore		default:
272260e9a8Yuri Pankov			show_usage = 1;
273260e9a8Yuri Pankov			break;
27495c635eGarrett D'Amore		}
275260e9a8Yuri Pankov	}
276260e9a8Yuri Pankov
277260e9a8Yuri Pankov	if (show_usage)
278260e9a8Yuri Pankov		usage(search.argmode);
27995c635eGarrett D'Amore
280260e9a8Yuri Pankov	/* Postprocess options. */
281260e9a8Yuri Pankov
282260e9a8Yuri Pankov	if (outmode == OUTMODE_DEF) {
283260e9a8Yuri Pankov		switch (search.argmode) {
284260e9a8Yuri Pankov		case ARG_FILE:
285260e9a8Yuri Pankov			outmode = OUTMODE_ALL;
286371584cYuri Pankov			use_pager = 0;
287260e9a8Yuri Pankov			break;
288260e9a8Yuri Pankov		case ARG_NAME:
289260e9a8Yuri Pankov			outmode = OUTMODE_ONE;
290260e9a8Yuri Pankov			break;
291260e9a8Yuri Pankov		default:
292260e9a8Yuri Pankov			outmode = OUTMODE_LST;
293260e9a8Yuri Pankov			break;
294260e9a8Yuri Pankov		}
295260e9a8Yuri Pankov	}
296260e9a8Yuri Pankov
297a40ea1aYuri Pankov	if (oarg != NULL) {
298a40ea1aYuri Pankov		if (outmode == OUTMODE_LST)
299a40ea1aYuri Pankov			search.outkey = oarg;
300a40ea1aYuri Pankov		else {
301a40ea1aYuri Pankov			while (oarg != NULL) {
302a40ea1aYuri Pankov				thisarg = oarg;
303a40ea1aYuri Pankov				if (manconf_output(&conf.output,
304a40ea1aYuri Pankov				    strsep(&oarg, ","), 0) == 0)
305a40ea1aYuri Pankov					continue;
306a40ea1aYuri Pankov				warnx("-O %s: Bad argument", thisarg);
307a40ea1aYuri Pankov				return (int)MANDOCLEVEL_BADARG;
308a40ea1aYuri Pankov			}
309a40ea1aYuri Pankov		}
310a40ea1aYuri Pankov	}
311a40ea1aYuri Pankov
312cec8643Michal Nowak	if (curp.outtype != OUTT_TREE || !curp.outopts->noval)
313cec8643Michal Nowak		options |= MPARSE_VALIDATE;
314cec8643Michal Nowak
315371584cYuri Pankov	if (outmode == OUTMODE_FLN ||
316371584cYuri Pankov	    outmode == OUTMODE_LST ||
317371584cYuri Pankov	    !isatty(STDOUT_FILENO))
318371584cYuri Pankov		use_pager = 0;
319371584cYuri Pankov
3206640c13Yuri Pankov	if (use_pager &&
3216640c13Yuri Pankov	    (conf.output.width == 0 || conf.output.indent == 0) &&
3226640c13Yuri Pankov	    ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) != -1 &&
3236640c13Yuri Pankov	    ws.ws_col > 1) {
3246640c13Yuri Pankov		if (conf.output.width == 0 && ws.ws_col < 79)
3256640c13Yuri Pankov			conf.output.width = ws.ws_col - 1;
3266640c13Yuri Pankov		if (conf.output.indent == 0 && ws.ws_col < 66)
3276640c13Yuri Pankov			conf.output.indent = 3;
3286640c13Yuri Pankov	}
3296640c13Yuri Pankov
330371584cYuri Pankov#if HAVE_PLEDGE
331371584cYuri Pankov	if (!use_pager)
332c66b804Yuri Pankov		if (pledge("stdio rpath", NULL) == -1)
333371584cYuri Pankov			err((int)MANDOCLEVEL_SYSERR, "pledge");
334371584cYuri Pankov#endif
335371584cYuri Pankov
336260e9a8Yuri Pankov	/* Parse arguments. */
337260e9a8Yuri Pankov
338260e9a8Yuri Pankov	if (argc > 0) {
339260e9a8Yuri Pankov		argc -= optind;
340260e9a8Yuri Pankov		argv += optind;
341260e9a8Yuri Pankov	}
342260e9a8Yuri Pankov	resp = NULL;
343260e9a8Yuri Pankov
344260e9a8Yuri Pankov	/*
345260e9a8Yuri Pankov	 * Quirks for help(1)
346260e9a8Yuri Pankov	 * and for a man(1) section argument without -s.
347260e9a8Yuri Pankov	 */
348260e9a8Yuri Pankov
349260e9a8Yuri Pankov	if (search.argmode == ARG_NAME) {
350260e9a8Yuri Pankov		if (*progname == 'h') {
351260e9a8Yuri Pankov			if (argc == 0) {
352260e9a8Yuri Pankov				argv = help_argv;
353260e9a8Yuri Pankov				argc = 1;
354260e9a8Yuri Pankov			}
355260e9a8Yuri Pankov		} else if (argc > 1 &&
356260e9a8Yuri Pankov		    ((uc = (unsigned char *)argv[0]) != NULL) &&
357260e9a8Yuri Pankov		    ((isdigit(uc[0]) && (uc[1] == '\0' ||
358260e9a8Yuri Pankov		      (isalpha(uc[1]) && uc[2] == '\0'))) ||
359260e9a8Yuri Pankov		     (uc[0] == 'n' && uc[1] == '\0'))) {
360260e9a8Yuri Pankov			search.sec = (char *)uc;
361260e9a8Yuri Pankov			argv++;
362260e9a8Yuri Pankov			argc--;
363260e9a8Yuri Pankov		}
364260e9a8Yuri Pankov		if (search.arch == NULL)
365260e9a8Yuri Pankov			search.arch = getenv("MACHINE");
366260e9a8Yuri Pankov#ifdef MACHINE
367260e9a8Yuri Pankov		if (search.arch == NULL)
368260e9a8Yuri Pankov			search.arch = MACHINE;
369260e9a8Yuri Pankov#endif
370260e9a8Yuri Pankov	}
371260e9a8Yuri Pankov
372cec8643Michal Nowak	/*
373cec8643Michal Nowak	 * Use the first argument for -O tag in addition to
374cec8643Michal Nowak	 * using it as a search term for man(1) or apropos(1).
375cec8643Michal Nowak	 */
376cec8643Michal Nowak
377cec8643Michal Nowak	if (conf.output.tag != NULL && *conf.output.tag == '\0') {
378cec8643Michal Nowak		tagarg = argc > 0 && search.argmode == ARG_EXPR ?
379cec8643Michal Nowak		    strchr(*argv, '=') : NULL;
380cec8643Michal Nowak		conf.output.tag = tagarg == NULL ? *argv : tagarg + 1;
381cec8643Michal Nowak	}
382260e9a8Yuri Pankov
383260e9a8Yuri Pankov	/* man(1), whatis(1), apropos(1) */
384260e9a8Yuri Pankov
385260e9a8Yuri Pankov	if (search.argmode != ARG_FILE) {
386260e9a8Yuri Pankov		if (search.argmode == ARG_NAME &&
387260e9a8Yuri Pankov		    outmode == OUTMODE_ONE)
388260e9a8Yuri Pankov			search.firstmatch = 1;
389260e9a8Yuri Pankov
390260e9a8Yuri Pankov		/* Access the mandoc database. */
391260e9a8Yuri Pankov
392371584cYuri Pankov		manconf_parse(&conf, conf_file, defpaths, auxpaths);
393371584cYuri Pankov		if ( ! mansearch(&search, &conf.manpath,
394371584cYuri Pankov		    argc, argv, &res, &sz))
395260e9a8Yuri Pankov			usage(search.argmode);
396260e9a8Yuri Pankov
3976640c13Yuri Pankov		if (sz == 0 && search.argmode == ARG_NAME)
3986640c13Yuri Pankov			fs_search(&search, &conf.manpath,
3996640c13Yuri Pankov			    argc, argv, &res, &sz);
4006640c13Yuri Pankov
4016640c13Yuri Pankov		if (search.argmode == ARG_NAME) {
4026640c13Yuri Pankov			for (c = 0; c < argc; c++) {
4036640c13Yuri Pankov				if (strchr(argv[c], '/') == NULL)
4046640c13Yuri Pankov					continue;
4056640c13Yuri Pankov				if (access(argv[c], R_OK) == -1) {
4066640c13Yuri Pankov					warn("%s", argv[c]);
4076640c13Yuri Pankov					continue;
4086640c13Yuri Pankov				}
4096640c13Yuri Pankov				res = mandoc_reallocarray(res,
4106640c13Yuri Pankov				    sz + 1, sizeof(*res));
4116640c13Yuri Pankov				res[sz].file = mandoc_strdup(argv[c]);
4126640c13Yuri Pankov				res[sz].names = NULL;
4136640c13Yuri Pankov				res[sz].output = NULL;
4146640c13Yuri Pankov				res[sz].ipath = SIZE_MAX;
4156640c13Yuri Pankov				res[sz].sec = 10;
4166640c13Yuri Pankov				res[sz].form = FORM_SRC;
4176640c13Yuri Pankov				sz++;
4186640c13Yuri Pankov			}
419371584cYuri Pankov		}
420260e9a8Yuri Pankov
421260e9a8Yuri Pankov		if (sz == 0) {
4226640c13Yuri Pankov			if (search.argmode != ARG_NAME)
4236640c13Yuri Pankov				warnx("nothing appropriate");
424cec8643Michal Nowak			mandoc_msg_setrc(MANDOCLEVEL_BADARG);
425260e9a8Yuri Pankov			goto out;
426260e9a8Yuri Pankov		}
427260e9a8Yuri Pankov
428260e9a8Yuri Pankov		/*
429260e9a8Yuri Pankov		 * For standard man(1) and -a output mode,
430260e9a8Yuri Pankov		 * prepare for copying filename pointers
431260e9a8Yuri Pankov		 * into the program parameter array.
432260e9a8Yuri Pankov		 */
433260e9a8Yuri Pankov
434260e9a8Yuri Pankov		if (outmode == OUTMODE_ONE) {
435260e9a8Yuri Pankov			argc = 1;
436371584cYuri Pankov			best_prio = 20;
437260e9a8Yuri Pankov		} else if (outmode == OUTMODE_ALL)
438260e9a8Yuri Pankov			argc = (int)sz;
439260e9a8Yuri Pankov
440260e9a8Yuri Pankov		/* Iterate all matching manuals. */
441260e9a8Yuri Pankov
442260e9a8Yuri Pankov		resp = res;
443260e9a8Yuri Pankov		for (i = 0; i < sz; i++) {
444260e9a8Yuri Pankov			if (outmode == OUTMODE_FLN)
445260e9a8Yuri Pankov				puts(res[i].file);
446260e9a8Yuri Pankov			else if (outmode == OUTMODE_LST)
447260e9a8Yuri Pankov				printf("%s - %s\n", res[i].names,
448260e9a8Yuri Pankov				    res[i].output == NULL ? "" :
449260e9a8Yuri Pankov				    res[i].output);
450260e9a8Yuri Pankov			else if (outmode == OUTMODE_ONE) {
451260e9a8Yuri Pankov				/* Search for the best section. */
452371584cYuri Pankov				sec = res[i].file;
453371584cYuri Pankov				sec += strcspn(sec, "123456789");
454371584cYuri Pankov				if (sec[0] == '\0')
455260e9a8Yuri Pankov					continue;
456371584cYuri Pankov				prio = sec_prios[sec[0] - '1'];
457371584cYuri Pankov				if (sec[1] != '/')
458371584cYuri Pankov					prio += 10;
459260e9a8Yuri Pankov				if (prio >= best_prio)
460260e9a8Yuri Pankov					continue;
461260e9a8Yuri Pankov				best_prio = prio;
462260e9a8Yuri Pankov				resp = res + i;
463260e9a8Yuri Pankov			}
464260e9a8Yuri Pankov		}
465260e9a8Yuri Pankov
466260e9a8Yuri Pankov		/*
467260e9a8Yuri Pankov		 * For man(1), -a and -i output mode, fall through
468260e9a8Yuri Pankov		 * to the main mandoc(1) code iterating files
469260e9a8Yuri Pankov		 * and running the parsers on each of them.
470260e9a8Yuri Pankov		 */
471260e9a8Yuri Pankov
472260e9a8Yuri Pankov		if (outmode == OUTMODE_FLN || outmode == OUTMODE_LST)
473260e9a8Yuri Pankov			goto out;
474260e9a8Yuri Pankov	}
475260e9a8Yuri Pankov
476260e9a8Yuri Pankov	/* mandoc(1) */
477260e9a8Yuri Pankov
478371584cYuri Pankov#if HAVE_PLEDGE
479371584cYuri Pankov	if (use_pager) {
480371584cYuri Pankov		if (pledge("stdio rpath tmppath tty proc exec", NULL) == -1)
481371584cYuri Pankov			err((int)MANDOCLEVEL_SYSERR, "pledge");
482371584cYuri Pankov	} else {
483371584cYuri Pankov		if (pledge("stdio rpath", NULL) == -1)
484371584cYuri Pankov			err((int)MANDOCLEVEL_SYSERR, "pledge");
485371584cYuri Pankov	}
486371584cYuri Pankov#endif
487371584cYuri Pankov
488c66b804Yuri Pankov	if (search.argmode == ARG_FILE)
489c66b804Yuri Pankov		moptions(&options, auxpaths);
490260e9a8Yuri Pankov
491371584cYuri Pankov	mchars_alloc();
492cec8643Michal Nowak	curp.mp = mparse_alloc(options, curp.os_e, curp.os_s);
49395c635eGarrett D'Amore
494260e9a8Yuri Pankov	if (argc < 1) {
495cec8643Michal Nowak		if (use_pager) {
496371584cYuri Pankov			tag_files = tag_init();
497cec8643Michal Nowak			tag_files->tagname = conf.output.tag;
498cec8643Michal Nowak		}
499cec8643Michal Nowak		thisarg = "<stdin>";
500cec8643Michal Nowak		mandoc_msg_setinfilename(thisarg);
501cec8643Michal Nowak		parse(&curp, STDIN_FILENO, thisarg);
502cec8643Michal Nowak		mandoc_msg_setinfilename(NULL);
503260e9a8Yuri Pankov	}
50495c635eGarrett D'Amore
5056640c13Yuri Pankov	/*
5066640c13Yuri Pankov	 * Remember the original working directory, if possible.
5076640c13Yuri Pankov	 * This will be needed if some names on the command line
5086640c13Yuri Pankov	 * are page names and some are relative file names.
5096640c13Yuri Pankov	 * Do not error out if the current directory is not
5106640c13Yuri Pankov	 * readable: Maybe it won't be needed after all.
5116640c13Yuri Pankov	 */
5126640c13Yuri Pankov	startdir = open(".", O_RDONLY | O_DIRECTORY);
5136640c13Yuri Pankov
514260e9a8Yuri Pankov	while (argc > 0) {
5156640c13Yuri Pankov
5166640c13Yuri Pankov		/*
5176640c13Yuri Pankov		 * Changing directories is not needed in ARG_FILE mode.
5186640c13Yuri Pankov		 * Do it on a best-effort basis.  Even in case of
5196640c13Yuri Pankov		 * failure, some functionality may still work.
5206640c13Yuri Pankov		 */
5216640c13Yuri Pankov		if (resp != NULL) {
5226640c13Yuri Pankov			if (resp->ipath != SIZE_MAX)
5236640c13Yuri Pankov				(void)chdir(conf.manpath.paths[resp->ipath]);
5246640c13Yuri Pankov			else if (startdir != -1)
5256640c13Yuri Pankov				(void)fchdir(startdir);
526cec8643Michal Nowak			thisarg = resp->file;
527cec8643Michal Nowak		} else
528cec8643Michal Nowak			thisarg = *argv;
5296640c13Yuri Pankov
530cec8643Michal Nowak		fd = mparse_open(curp.mp, thisarg);
531260e9a8Yuri Pankov		if (fd != -1) {
532371584cYuri Pankov			if (use_pager) {
533371584cYuri Pankov				use_pager = 0;
534cec8643Michal Nowak				tag_files = tag_init();
535cec8643Michal Nowak				tag_files->tagname = conf.output.tag;
536371584cYuri Pankov			}
537260e9a8Yuri Pankov
538cec8643Michal Nowak			mandoc_msg_setinfilename(thisarg);
539cec8643Michal Nowak			if (resp == NULL || resp->form == FORM_SRC)
540cec8643Michal Nowak				parse(&curp, fd, thisarg);
5416640c13Yuri Pankov			else
542371584cYuri Pankov				passthrough(resp->file, fd,
543371584cYuri Pankov				    conf.output.synopsisonly);
544cec8643Michal Nowak			mandoc_msg_setinfilename(NULL);
545260e9a8Yuri Pankov
5466640c13Yuri Pankov			if (ferror(stdout)) {
5476640c13Yuri Pankov				if (tag_files != NULL) {
5486640c13Yuri Pankov					warn("%s", tag_files->ofn);
5496640c13Yuri Pankov					tag_unlink();
5506640c13Yuri Pankov					tag_files = NULL;
5516640c13Yuri Pankov				} else
5526640c13Yuri Pankov					warn("stdout");
553cec8643Michal Nowak				mandoc_msg_setrc(MANDOCLEVEL_SYSERR);
5546640c13Yuri Pankov				break;
5556640c13Yuri Pankov			}
5566640c13Yuri Pankov
557a593473Yuri Pankov			if (argc > 1 && curp.outtype <= OUTT_UTF8) {
558a593473Yuri Pankov				if (curp.outdata == NULL)
559a593473Yuri Pankov					outdata_alloc(&curp);
560371584cYuri Pankov				terminal_sepline(curp.outdata);
561a593473Yuri Pankov			}
562cec8643Michal Nowak		} else
563cec8643Michal Nowak			mandoc_msg(MANDOCERR_FILE, 0, 0,
564cec8643Michal Nowak			    "%s: %s", thisarg, strerror(errno));
56595c635eGarrett D'Amore
566cec8643Michal Nowak		if (curp.wstop && mandoc_msg_getrc() != MANDOCLEVEL_OK)
56795c635eGarrett D'Amore			break;
568260e9a8Yuri Pankov
569260e9a8Yuri Pankov		if (resp != NULL)
570260e9a8Yuri Pankov			resp++;
571260e9a8Yuri Pankov		else
572260e9a8Yuri Pankov			argv++;
573260e9a8Yuri Pankov		if (--argc)
574260e9a8Yuri Pankov			mparse_reset(curp.mp);
57595c635eGarrett D'Amore	}
5766640c13Yuri Pankov	if (startdir != -1) {
5776640c13Yuri Pankov		(void)fchdir(startdir);
5786640c13Yuri Pankov		close(startdir);
5796640c13Yuri Pankov	}
58095c635eGarrett D'Amore
581371584cYuri Pankov	if (curp.outdata != NULL) {
582371584cYuri Pankov		switch (curp.outtype) {
583371584cYuri Pankov		case OUTT_HTML:
584371584cYuri Pankov			html_free(curp.outdata);
585371584cYuri Pankov			break;
586371584cYuri Pankov		case OUTT_UTF8:
587371584cYuri Pankov		case OUTT_LOCALE:
588371584cYuri Pankov		case OUTT_ASCII:
589371584cYuri Pankov			ascii_free(curp.outdata);
590371584cYuri Pankov			break;
591371584cYuri Pankov		case OUTT_PDF:
592371584cYuri Pankov		case OUTT_PS:
593371584cYuri Pankov			pspdf_free(curp.outdata);
594371584cYuri Pankov			break;
595371584cYuri Pankov		default:
596371584cYuri Pankov			break;
597371584cYuri Pankov		}
598371584cYuri Pankov	}
599c66b804Yuri Pankov	mandoc_xr_free();
600260e9a8Yuri Pankov	mparse_free(curp.mp);
601371584cYuri Pankov	mchars_free();
602260e9a8Yuri Pankov
603260e9a8Yuri Pankovout:
604260e9a8Yuri Pankov	if (search.argmode != ARG_FILE) {
605371584cYuri Pankov		manconf_free(&conf);
606260e9a8Yuri Pankov		mansearch_free(res, sz);
607260e9a8Yuri Pankov	}
608260e9a8Yuri Pankov
609c66b804Yuri Pankov	free(curp.os_s);
61095c635eGarrett D'Amore
611260e9a8Yuri Pankov	/*
612371584cYuri Pankov	 * When using a pager, finish writing both temporary files,
613371584cYuri Pankov	 * fork it, wait for the user to close it, and clean up.
614260e9a8Yuri Pankov	 */
615260e9a8Yuri Pankov
616371584cYuri Pankov	if (tag_files != NULL) {
617260e9a8Yuri Pankov		fclose(stdout);
618371584cYuri Pankov		tag_write();
619371584cYuri Pankov		man_pgid = getpgid(0);
620371584cYuri Pankov		tag_files->tcpgid = man_pgid == getpid() ?
621371584cYuri Pankov		    getpgid(getppid()) : man_pgid;
622371584cYuri Pankov		pager_pid = 0;
623371584cYuri Pankov		signum = SIGSTOP;
624371584cYuri Pankov		for (;;) {
625371584cYuri Pankov
626371584cYuri Pankov			/* Stop here until moved to the foreground. */
627371584cYuri Pankov
628a40ea1aYuri Pankov			tc_pgid = tcgetpgrp(tag_files->ofd);
629371584cYuri Pankov			if (tc_pgid != man_pgid) {
630371584cYuri Pankov				if (tc_pgid == pager_pid) {
631a40ea1aYuri Pankov					(void)tcsetpgrp(tag_files->ofd,
632371584cYuri Pankov					    man_pgid);
633371584cYuri Pankov					if (signum == SIGTTIN)
634371584cYuri Pankov						continue;
635371584cYuri Pankov				} else
636371584cYuri Pankov					tag_files->tcpgid = tc_pgid;
637371584cYuri Pankov				kill(0, signum);
638371584cYuri Pankov				continue;
639371584cYuri Pankov			}
640371584cYuri Pankov
641371584cYuri Pankov			/* Once in the foreground, activate the pager. */
642371584cYuri Pankov
643371584cYuri Pankov			if (pager_pid) {
644a40ea1aYuri Pankov				(void)tcsetpgrp(tag_files->ofd, pager_pid);
645371584cYuri Pankov				kill(pager_pid, SIGCONT);
646371584cYuri Pankov			} else
647371584cYuri Pankov				pager_pid = spawn_pager(tag_files);
648371584cYuri Pankov
649371584cYuri Pankov			/* Wait for the pager to stop or exit. */
650371584cYuri Pankov
651371584cYuri Pankov			while ((pid = waitpid(pager_pid, &status,
652371584cYuri Pankov			    WUNTRACED)) == -1 && errno == EINTR)
653371584cYuri Pankov				continue;
654371584cYuri Pankov
655371584cYuri Pankov			if (pid == -1) {
656371584cYuri Pankov				warn("wait");
657cec8643Michal Nowak				mandoc_msg_setrc(MANDOCLEVEL_SYSERR);
658371584cYuri Pankov				break;
659371584cYuri Pankov			}
660371584cYuri Pankov			if (!WIFSTOPPED(status))
661371584cYuri Pankov				break;
662371584cYuri Pankov
663371584cYuri Pankov			signum = WSTOPSIG(status);
664371584cYuri Pankov		}
665371584cYuri Pankov		tag_unlink();
666260e9a8Yuri Pankov	}
667cec8643Michal Nowak	return (int)mandoc_msg_getrc();
66895c635eGarrett D'Amore}
66995c635eGarrett D'Amore
67095c635eGarrett D'Amorestatic void
671260e9a8Yuri Pankovusage(enum argmode argmode)
67295c635eGarrett D'Amore{
67395c635eGarrett D'Amore
674260e9a8Yuri Pankov	switch (argmode) {
675260e9a8Yuri Pankov	case ARG_FILE:
676c66b804Yuri Pankov		fputs("usage: mandoc [-ac] [-I os=name] "
677c66b804Yuri Pankov		    "[-K encoding] [-mdoc | -man] [-O options]\n"
678371584cYuri Pankov		    "\t      [-T output] [-W level] [file ...]\n", stderr);
679260e9a8Yuri Pankov		break;
680260e9a8Yuri Pankov	case ARG_NAME:
681c66b804Yuri Pankov		fputs("usage: man [-acfhklw] [-C file] [-M path] "
682c66b804Yuri Pankov		    "[-m path] [-S subsection]\n"
683c66b804Yuri Pankov		    "\t   [[-s] section] name ...\n", stderr);
684260e9a8Yuri Pankov		break;
685260e9a8Yuri Pankov	case ARG_WORD:
686c66b804Yuri Pankov		fputs("usage: whatis [-afk] [-C file] "
687260e9a8Yuri Pankov		    "[-M path] [-m path] [-O outkey] [-S arch]\n"
688260e9a8Yuri Pankov		    "\t      [-s section] name ...\n", stderr);
689260e9a8Yuri Pankov		break;
690260e9a8Yuri Pankov	case ARG_EXPR:
691c66b804Yuri Pankov		fputs("usage: apropos [-afk] [-C file] "
692260e9a8Yuri Pankov		    "[-M path] [-m path] [-O outkey] [-S arch]\n"
693260e9a8Yuri Pankov		    "\t       [-s section] expression ...\n", stderr);
694260e9a8Yuri Pankov		break;
695260e9a8Yuri Pankov	}
696260e9a8Yuri Pankov	exit((int)MANDOCLEVEL_BADARG);
69795c635eGarrett D'Amore}
69895c635eGarrett D'Amore
699260e9a8Yuri Pankovstatic int
700260e9a8Yuri Pankovfs_lookup(const struct manpaths *paths, size_t ipath,
701260e9a8Yuri Pankov	const char *sec, const char *arch, const char *name,
702260e9a8Yuri Pankov	struct manpage **res, size_t *ressz)
70395c635eGarrett D'Amore{
704260e9a8Yuri Pankov	glob_t		 globinfo;
705260e9a8Yuri Pankov	struct manpage	*page;
706260e9a8Yuri Pankov	char		*file;
707a40ea1aYuri Pankov	int		 globres;
708a40ea1aYuri Pankov	enum form	 form;
709260e9a8Yuri Pankov
710260e9a8Yuri Pankov	form = FORM_SRC;
711260e9a8Yuri Pankov	mandoc_asprintf(&file, "%s/man%s/%s.%s",
712260e9a8Yuri Pankov	    paths->paths[ipath], sec, name, sec);
713260e9a8Yuri Pankov	if (access(file, R_OK) != -1)
714260e9a8Yuri Pankov		goto found;
715260e9a8Yuri Pankov	free(file);
716260e9a8Yuri Pankov
717260e9a8Yuri Pankov	mandoc_asprintf(&file, "%s/cat%s/%s.0",
718260e9a8Yuri Pankov	    paths->paths[ipath], sec, name);
719260e9a8Yuri Pankov	if (access(file, R_OK) != -1) {
720260e9a8Yuri Pankov		form = FORM_CAT;
721260e9a8Yuri Pankov		goto found;
722260e9a8Yuri Pankov	}
723260e9a8Yuri Pankov	free(file);
724260e9a8Yuri Pankov
725260e9a8Yuri Pankov	if (arch != NULL) {
726260e9a8Yuri Pankov		mandoc_asprintf(&file, "%s/man%s/%s/%s.%s",
727260e9a8Yuri Pankov		    paths->paths[ipath], sec, arch, name, sec);
728260e9a8Yuri Pankov		if (access(file, R_OK) != -1)
729260e9a8Yuri Pankov			goto found;
730260e9a8Yuri Pankov		free(file);
731260e9a8Yuri Pankov	}
732260e9a8Yuri Pankov
733371584cYuri Pankov	mandoc_asprintf(&file, "%s/man%s/%s.[01-9]*",
734260e9a8Yuri Pankov	    paths->paths[ipath], sec, name);
735260e9a8Yuri Pankov	globres = glob(file, 0, NULL, &globinfo);
736260e9a8Yuri Pankov	if (globres != 0 && globres != GLOB_NOMATCH)
737371584cYuri Pankov		warn("%s: glob", file);
738260e9a8Yuri Pankov	free(file);
739260e9a8Yuri Pankov	if (globres == 0)
740260e9a8Yuri Pankov		file = mandoc_strdup(*globinfo.gl_pathv);
741260e9a8Yuri Pankov	globfree(&globinfo);
742c66b804Yuri Pankov	if (globres == 0)
743c66b804Yuri Pankov		goto found;
744c66b804Yuri Pankov	if (res != NULL || ipath + 1 != paths->sz)
745371584cYuri Pankov		return 0;
74695c635eGarrett D'Amore
747c66b804Yuri Pankov	mandoc_asprintf(&file, "%s.%s", name, sec);
748c66b804Yuri Pankov	globres = access(file, R_OK);
749c66b804Yuri Pankov	free(file);
750c66b804Yuri Pankov	return globres != -1;
751c66b804Yuri Pankov
752260e9a8Yuri Pankovfound:
753371584cYuri Pankov	warnx("outdated mandoc.db lacks %s(%s) entry, run %s %s",
754371584cYuri Pankov	    name, sec, BINM_MAKEWHATIS, paths->paths[ipath]);
755c66b804Yuri Pankov	if (res == NULL) {
756c66b804Yuri Pankov		free(file);
757c66b804Yuri Pankov		return 1;
758c66b804Yuri Pankov	}
759260e9a8Yuri Pankov	*res = mandoc_reallocarray(*res, ++*ressz, sizeof(struct manpage));
760260e9a8Yuri Pankov	page = *res + (*ressz - 1);
761260e9a8Yuri Pankov	page->file = file;
762260e9a8Yuri Pankov	page->names = NULL;
763260e9a8Yuri Pankov	page->output = NULL;
764260e9a8Yuri Pankov	page->ipath = ipath;
765260e9a8Yuri Pankov	page->sec = (*sec >= '1' && *sec <= '9') ? *sec - '1' + 1 : 10;
766260e9a8Yuri Pankov	page->form = form;
767371584cYuri Pankov	return 1;
768260e9a8Yuri Pankov}
769260e9a8Yuri Pankov
770c66b804Yuri Pankovstatic int
771260e9a8Yuri Pankovfs_search(const struct mansearch *cfg, const struct manpaths *paths,
772260e9a8Yuri Pankov	int argc, char **argv, struct manpage **res, size_t *ressz)
773260e9a8Yuri Pankov{
774260e9a8Yuri Pankov	const char *const sections[] =
775371584cYuri Pankov	    {"1", "8", "6", "2", "3", "5", "7", "4", "9", "3p"};
776260e9a8Yuri Pankov	const size_t nsec = sizeof(sections)/sizeof(sections[0]);
777260e9a8Yuri Pankov
778260e9a8Yuri Pankov	size_t		 ipath, isec, lastsz;
779260e9a8Yuri Pankov
780260e9a8Yuri Pankov	assert(cfg->argmode == ARG_NAME);
781260e9a8Yuri Pankov
782c66b804Yuri Pankov	if (res != NULL)
783c66b804Yuri Pankov		*res = NULL;
784260e9a8Yuri Pankov	*ressz = lastsz = 0;
785260e9a8Yuri Pankov	while (argc) {
786260e9a8Yuri Pankov		for (ipath = 0; ipath < paths->sz; ipath++) {
787260e9a8Yuri Pankov			if (cfg->sec != NULL) {
788260e9a8Yuri Pankov				if (fs_lookup(paths, ipath, cfg->sec,
789260e9a8Yuri Pankov				    cfg->arch, *argv, res, ressz) &&
790260e9a8Yuri Pankov				    cfg->firstmatch)
791c66b804Yuri Pankov					return 1;
792260e9a8Yuri Pankov			} else for (isec = 0; isec < nsec; isec++)
793260e9a8Yuri Pankov				if (fs_lookup(paths, ipath, sections[isec],
794260e9a8Yuri Pankov				    cfg->arch, *argv, res, ressz) &&
795260e9a8Yuri Pankov				    cfg->firstmatch)
796c66b804Yuri Pankov					return 1;
797260e9a8Yuri Pankov		}
7986640c13Yuri Pankov		if (res != NULL && *ressz == lastsz &&
799cec8643Michal Nowak		    strchr(*argv, '/') == NULL) {
800cec8643Michal Nowak			if (cfg->arch != NULL &&
801cec8643Michal Nowak			    arch_valid(cfg->arch, OSENUM) == 0)
802cec8643Michal Nowak				warnx("Unknown architecture \"%s\".",
803cec8643Michal Nowak				    cfg->arch);
804cec8643Michal Nowak			else if (cfg->sec == NULL)
805cec8643Michal Nowak				warnx("No entry for %s in the manual.",
806cec8643Michal Nowak				    *argv);
807cec8643Michal Nowak			else
808cec8643Michal Nowak				warnx("No entry for %s in section %s "
809cec8643Michal Nowak				    "of the manual.", *argv, cfg->sec);
810cec8643Michal Nowak		}
811260e9a8Yuri Pankov		lastsz = *ressz;
812260e9a8Yuri Pankov		argv++;
813260e9a8Yuri Pankov		argc--;
814260e9a8Yuri Pankov	}
815c66b804Yuri Pankov	return 0;
81695c635eGarrett D'Amore}
81795c635eGarrett D'Amore
81895c635eGarrett D'Amorestatic void
819371584cYuri Pankovparse(struct curparse *curp, int fd, const char *file)
82095c635eGarrett D'Amore{
821cec8643Michal Nowak	struct roff_meta *meta;
82295c635eGarrett D'Amore
82395c635eGarrett D'Amore	/* Begin by parsing the file itself. */
82495c635eGarrett D'Amore
82595c635eGarrett D'Amore	assert(file);
826371584cYuri Pankov	assert(fd >= 0);
82795c635eGarrett D'Amore
828cec8643Michal Nowak	mparse_readfd(curp->mp, fd, file);
829371584cYuri Pankov	if (fd != STDIN_FILENO)
830371584cYuri Pankov		close(fd);
83195c635eGarrett D'Amore
83295c635eGarrett D'Amore	/*
83395c635eGarrett D'Amore	 * With -Wstop and warnings or errors of at least the requested
83495c635eGarrett D'Amore	 * level, do not produce output.
83595c635eGarrett D'Amore	 */
83695c635eGarrett D'Amore
837cec8643Michal Nowak	if (curp->wstop && mandoc_msg_getrc() != MANDOCLEVEL_OK)
838371584cYuri Pankov		return;
83995c635eGarrett D'Amore
840a593473Yuri Pankov	if (curp->outdata == NULL)
841a593473Yuri Pankov		outdata_alloc(curp);
842cec8643Michal Nowak	else if (curp->outtype == OUTT_HTML)
843cec8643Michal Nowak		html_reset(curp);
844371584cYuri Pankov
845cec8643Michal Nowak	mandoc_xr_reset();
846cec8643Michal Nowak	meta = mparse_result(curp->mp);
84795c635eGarrett D'Amore
848371584cYuri Pankov	/* Execute the out device, if it exists. */
849371584cYuri Pankov
850cec8643Michal Nowak	if (meta->macroset == MACROSET_MDOC) {
85195c635eGarrett D'Amore		switch (curp->outtype) {
852260e9a8Yuri Pankov		case OUTT_HTML:
853cec8643Michal Nowak			html_mdoc(curp->outdata, meta);
85495c635eGarrett D'Amore			break;
855260e9a8Yuri Pankov		case OUTT_TREE:
856cec8643Michal Nowak			tree_mdoc(curp->outdata, meta);
85795c635eGarrett D'Amore			break;
858260e9a8Yuri Pankov		case OUTT_MAN:
859cec8643Michal Nowak			man_mdoc(curp->outdata, meta);
86095c635eGarrett D'Amore			break;
861260e9a8Yuri Pankov		case OUTT_PDF:
862260e9a8Yuri Pankov		case OUTT_ASCII:
863260e9a8Yuri Pankov		case OUTT_UTF8:
864260e9a8Yuri Pankov		case OUTT_LOCALE:
865260e9a8Yuri Pankov		case OUTT_PS:
866cec8643Michal Nowak			terminal_mdoc(curp->outdata, meta);
867371584cYuri Pankov			break;
868c66b804Yuri Pankov		case OUTT_MARKDOWN:
869cec8643Michal Nowak			markdown_mdoc(curp->outdata, meta);
870c66b804Yuri Pankov			break;
871371584cYuri Pankov		default:
872371584cYuri Pankov			break;
873371584cYuri Pankov		}
874371584cYuri Pankov	}
875cec8643Michal Nowak	if (meta->macroset == MACROSET_MAN) {
876371584cYuri Pankov		switch (curp->outtype) {
877371584cYuri Pankov		case OUTT_HTML:
878cec8643Michal Nowak			html_man(curp->outdata, meta);
879371584cYuri Pankov			break;
880371584cYuri Pankov		case OUTT_TREE:
881cec8643Michal Nowak			tree_man(curp->outdata, meta);
882371584cYuri Pankov			break;
883371584cYuri Pankov		case OUTT_MAN:
884cec8643Michal Nowak			mparse_copy(curp->mp);
885371584cYuri Pankov			break;
886371584cYuri Pankov		case OUTT_PDF:
887371584cYuri Pankov		case OUTT_ASCII:
888371584cYuri Pankov		case OUTT_UTF8:
889371584cYuri Pankov		case OUTT_LOCALE:
890371584cYuri Pankov		case OUTT_PS:
891cec8643Michal Nowak			terminal_man(curp->outdata, meta);
89295c635eGarrett D'Amore			break;
89395c635eGarrett D'Amore		default:
89495c635eGarrett D'Amore			break;
89595c635eGarrett D'Amore		}
89695c635eGarrett D'Amore	}
897cec8643Michal Nowak	if (mandoc_msg_getmin() < MANDOCERR_STYLE)
898cec8643Michal Nowak		check_xr();
899a593473Yuri Pankov}
900a593473Yuri Pankov
901a593473Yuri Pankovstatic void
902cec8643Michal Nowakcheck_xr(void)
903c66b804Yuri Pankov{
904c66b804Yuri Pankov	static struct manpaths	 paths;
905c66b804Yuri Pankov	struct mansearch	 search;
906c66b804Yuri Pankov	struct mandoc_xr	*xr;
907c66b804Yuri Pankov	size_t			 sz;
908c66b804Yuri Pankov
909c66b804Yuri Pankov	if (paths.sz == 0)
910c66b804Yuri Pankov		manpath_base(&paths);
911c66b804Yuri Pankov
912c66b804Yuri Pankov	for (xr = mandoc_xr_get(); xr != NULL; xr = xr->next) {
913c66b804Yuri Pankov		if (xr->line == -1)
914c66b804Yuri Pankov			continue;
915c66b804Yuri Pankov		search.arch = NULL;
916c66b804Yuri Pankov		search.sec = xr->sec;
917c66b804Yuri Pankov		search.outkey = NULL;
918c66b804Yuri Pankov		search.argmode = ARG_NAME;
919c66b804Yuri Pankov		search.firstmatch = 1;
920c66b804Yuri Pankov		if (mansearch(&search, &paths, 1, &xr->name, NULL, &sz))
921c66b804Yuri Pankov			continue;
922c66b804Yuri Pankov		if (fs_search(&search, &paths, 1, &xr->name, NULL, &sz))
923c66b804Yuri Pankov			continue;
924c66b804Yuri Pankov		if (xr->count == 1)
925cec8643Michal Nowak			mandoc_msg(MANDOCERR_XR_BAD, xr->line,
926cec8643Michal Nowak			    xr->pos + 1, "Xr %s %s", xr->name, xr->sec);
927c66b804Yuri Pankov		else
928cec8643Michal Nowak			mandoc_msg(MANDOCERR_XR_BAD, xr->line,
929cec8643Michal Nowak			    xr->pos + 1, "Xr %s %s (%d times)",
930c66b804Yuri Pankov			    xr->name, xr->sec, xr->count);
931c66b804Yuri Pankov	}
932c66b804Yuri Pankov}
933c66b804Yuri Pankov
934c66b804Yuri Pankovstatic void
935a593473Yuri Pankovoutdata_alloc(struct curparse *curp)
936a593473Yuri Pankov{
937a593473Yuri Pankov	switch (curp->outtype) {
938a593473Yuri Pankov	case OUTT_HTML:
939a593473Yuri Pankov		curp->outdata = html_alloc(curp->outopts);
940a593473Yuri Pankov		break;
941a593473Yuri Pankov	case OUTT_UTF8:
942a593473Yuri Pankov		curp->outdata = utf8_alloc(curp->outopts);
943a593473Yuri Pankov		break;
944a593473Yuri Pankov	case OUTT_LOCALE:
945a593473Yuri Pankov		curp->outdata = locale_alloc(curp->outopts);
946a593473Yuri Pankov		break;
947a593473Yuri Pankov	case OUTT_ASCII:
948a593473Yuri Pankov		curp->outdata = ascii_alloc(curp->outopts);
949a593473Yuri Pankov		break;
950a593473Yuri Pankov	case OUTT_PDF:
951a593473Yuri Pankov		curp->outdata = pdf_alloc(curp->outopts);
952a593473Yuri Pankov		break;
953a593473Yuri Pankov	case OUTT_PS:
954a593473Yuri Pankov		curp->outdata = ps_alloc(curp->outopts);
955a593473Yuri Pankov		break;
956a593473Yuri Pankov	default:
957a593473Yuri Pankov		break;
958a593473Yuri Pankov	}
95995c635eGarrett D'Amore}
96095c635eGarrett D'Amore
961371584cYuri Pankovstatic void
962260e9a8Yuri Pankovpassthrough(const char *file, int fd, int synopsis_only)
963260e9a8Yuri Pankov{
964260e9a8Yuri Pankov	const char	 synb[] = "S\bSY\bYN\bNO\bOP\bPS\bSI\bIS\bS";
965260e9a8Yuri Pankov	const char	 synr[] = "SYNOPSIS";
966260e9a8Yuri Pankov
967260e9a8Yuri Pankov	FILE		*stream;
968260e9a8Yuri Pankov	const char	*syscall;
969371584cYuri Pankov	char		*line, *cp;
970371584cYuri Pankov	size_t		 linesz;
971a593473Yuri Pankov	ssize_t		 len, written;
972260e9a8Yuri Pankov	int		 print;
973260e9a8Yuri Pankov
974371584cYuri Pankov	line = NULL;
975371584cYuri Pankov	linesz = 0;
976260e9a8Yuri Pankov
977a593473Yuri Pankov	if (fflush(stdout) == EOF) {
978a593473Yuri Pankov		syscall = "fflush";
979a593473Yuri Pankov		goto fail;
980a593473Yuri Pankov	}
981a593473Yuri Pankov
982260e9a8Yuri Pankov	if ((stream = fdopen(fd, "r")) == NULL) {
983260e9a8Yuri Pankov		close(fd);
984260e9a8Yuri Pankov		syscall = "fdopen";
985260e9a8Yuri Pankov		goto fail;
986260e9a8Yuri Pankov	}
987260e9a8Yuri Pankov
988260e9a8Yuri Pankov	print = 0;
989a593473Yuri Pankov	while ((len = getline(&line, &linesz, stream)) != -1) {
990371584cYuri Pankov		cp = line;
991260e9a8Yuri Pankov		if (synopsis_only) {
992260e9a8Yuri Pankov			if (print) {
993371584cYuri Pankov				if ( ! isspace((unsigned char)*cp))
994260e9a8Yuri Pankov					goto done;
995a593473Yuri Pankov				while (isspace((unsigned char)*cp)) {
996371584cYuri Pankov					cp++;
997a593473Yuri Pankov					len--;
998a593473Yuri Pankov				}
999260e9a8Yuri Pankov			} else {
1000371584cYuri Pankov				if (strcmp(cp, synb) == 0 ||
1001371584cYuri Pankov				    strcmp(cp, synr) == 0)
1002260e9a8Yuri Pankov					print = 1;
1003260e9a8Yuri Pankov				continue;
1004260e9a8Yuri Pankov			}
1005260e9a8Yuri Pankov		}
1006a593473Yuri Pankov		for (; len > 0; len -= written) {
1007a593473Yuri Pankov			if ((written = write(STDOUT_FILENO, cp, len)) != -1)
1008a593473Yuri Pankov				continue;
1009371584cYuri Pankov			fclose(stream);
1010a593473Yuri Pankov			syscall = "write";
1011371584cYuri Pankov			goto fail;
1012371584cYuri Pankov		}
1013260e9a8Yuri Pankov	}
1014260e9a8Yuri Pankov
1015260e9a8Yuri Pankov	if (ferror(stream)) {
1016260e9a8Yuri Pankov		fclose(stream);
1017371584cYuri Pankov		syscall = "getline";
1018260e9a8Yuri Pankov		goto fail;
1019260e9a8Yuri Pankov	}
1020260e9a8Yuri Pankov
1021260e9a8Yuri Pankovdone:
1022371584cYuri Pankov	free(line);
1023260e9a8Yuri Pankov	fclose(stream);
1024371584cYuri Pankov	return;
1025260e9a8Yuri Pankov
1026260e9a8Yuri Pankovfail:
1027371584cYuri Pankov	free(line);
1028371584cYuri Pankov	warn("%s: SYSERR: %s", file, syscall);
1029cec8643Michal Nowak	mandoc_msg_setrc(MANDOCLEVEL_SYSERR);
1030260e9a8Yuri Pankov}
1031260e9a8Yuri Pankov
1032260e9a8Yuri Pankovstatic int
1033260e9a8Yuri Pankovkoptions(int *options, char *arg)
1034260e9a8Yuri Pankov{
1035260e9a8Yuri Pankov
1036260e9a8Yuri Pankov	if ( ! strcmp(arg, "utf-8")) {
1037260e9a8Yuri Pankov		*options |=  MPARSE_UTF8;
1038260e9a8Yuri Pankov		*options &= ~MPARSE_LATIN1;
1039260e9a8Yuri Pankov	} else if ( ! strcmp(arg, "iso-8859-1")) {
1040260e9a8Yuri Pankov		*options |=  MPARSE_LATIN1;
1041260e9a8Yuri Pankov		*options &= ~MPARSE_UTF8;
1042260e9a8Yuri Pankov	} else if ( ! strcmp(arg, "us-ascii")) {
1043260e9a8Yuri Pankov		*options &= ~(MPARSE_UTF8 | MPARSE_LATIN1);
1044260e9a8Yuri Pankov	} else {
1045371584cYuri Pankov		warnx("-K %s: Bad argument", arg);
1046371584cYuri Pankov		return 0;
1047260e9a8Yuri Pankov	}
1048371584cYuri Pankov	return 1;
1049260e9a8Yuri Pankov}
1050260e9a8Yuri Pankov
1051c66b804Yuri Pankovstatic void
1052260e9a8Yuri Pankovmoptions(int *options, char *arg)
105395c635eGarrett D'Amore{
105495c635eGarrett D'Amore
1055260e9a8Yuri Pankov	if (arg == NULL)
1056c66b804Yuri Pankov		return;
1057c66b804Yuri Pankov	if (strcmp(arg, "doc") == 0)
1058260e9a8Yuri Pankov		*options |= MPARSE_MDOC;
1059c66b804Yuri Pankov	else if (strcmp(arg, "an") == 0)
1060260e9a8Yuri Pankov		*options |= MPARSE_MAN;
106195c635eGarrett D'Amore}
106295c635eGarrett D'Amore
106395c635eGarrett D'Amorestatic int
106495c635eGarrett D'Amoretoptions(struct curparse *curp, char *arg)
106595c635eGarrett D'Amore{
106695c635eGarrett D'Amore
106795c635eGarrett D'Amore	if (0 == strcmp(arg, "ascii"))
106895c635eGarrett D'Amore		curp->outtype = OUTT_ASCII;
106995c635eGarrett D'Amore	else if (0 == strcmp(arg, "lint")) {
107095c635eGarrett D'Amore		curp->outtype = OUTT_LINT;
1071cec8643Michal Nowak		mandoc_msg_setoutfile(stdout);
1072