xref: /openbsd-src/usr.bin/mandoc/main.c (revision 885a00aee8cfc20b2ab3631915e4284780d98155)
1*885a00aeSmillert /* $OpenBSD: main.c,v 1.265 2024/03/29 01:16:30 millert Exp $ */
2f73abda9Skristaps /*
328ca454fSschwarze  * Copyright (c) 2010-2012, 2014-2021 Ingo Schwarze <schwarze@openbsd.org>
40ac7e6ecSschwarze  * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
539c2a57eSschwarze  * Copyright (c) 2010 Joerg Sonnenberger <joerg@netbsd.org>
6f73abda9Skristaps  *
7f73abda9Skristaps  * Permission to use, copy, modify, and distribute this software for any
8a6464425Sschwarze  * purpose with or without fee is hereby granted, provided that the above
9a6464425Sschwarze  * copyright notice and this permission notice appear in all copies.
10f73abda9Skristaps  *
114de77decSschwarze  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
12a6464425Sschwarze  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
134de77decSschwarze  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
14a6464425Sschwarze  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15a6464425Sschwarze  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16a6464425Sschwarze  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17a6464425Sschwarze  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
180ac7e6ecSschwarze  *
190ac7e6ecSschwarze  * Main program for mandoc(1), man(1), apropos(1), whatis(1), and help(1).
20f73abda9Skristaps  */
210f10154cSschwarze #include <sys/types.h>
22cbdb6ce4Sschwarze #include <sys/ioctl.h>
23c68846c0Sschwarze #include <sys/param.h>	/* MACHINE */
24a231508dSschwarze #include <sys/stat.h>
25bb1dcb77Sschwarze #include <sys/wait.h>
260f10154cSschwarze 
27f73abda9Skristaps #include <assert.h>
280f10154cSschwarze #include <ctype.h>
29eba1598bSschwarze #include <err.h>
30d5c4dcfeSschwarze #include <errno.h>
310f10154cSschwarze #include <fcntl.h>
32e8e3ce36Sschwarze #include <glob.h>
337d109111Sschwarze #include <limits.h>
3477f78096Sschwarze #include <signal.h>
35f73abda9Skristaps #include <stdio.h>
364175bdabSschwarze #include <stdint.h>
37f73abda9Skristaps #include <stdlib.h>
38f73abda9Skristaps #include <string.h>
39c6bb096dSschwarze #include <termios.h>
40d82a663dSschwarze #include <time.h>
41f73abda9Skristaps #include <unistd.h>
42f73abda9Skristaps 
434f4f7972Sschwarze #include "mandoc_aux.h"
44d1982c71Sschwarze #include "mandoc.h"
4519b6bef7Sschwarze #include "mandoc_xr.h"
46d1982c71Sschwarze #include "roff.h"
47f73abda9Skristaps #include "mdoc.h"
48f73abda9Skristaps #include "man.h"
4999acaf1eSschwarze #include "mandoc_parse.h"
50beabc24cSschwarze #include "tag.h"
510ac7e6ecSschwarze #include "term_tag.h"
52d1982c71Sschwarze #include "main.h"
534de77decSschwarze #include "manconf.h"
540f10154cSschwarze #include "mansearch.h"
550f10154cSschwarze 
56ecf32ec4Sschwarze #define BINM_APROPOS	"apropos"
57ecf32ec4Sschwarze #define BINM_MAN	"man"
58ecf32ec4Sschwarze #define BINM_MAKEWHATIS	"makewhatis"
59ecf32ec4Sschwarze #define BINM_WHATIS	"whatis"
60ecf32ec4Sschwarze #define OSENUM		MANDOC_OS_OPENBSD
61ecf32ec4Sschwarze 
620f10154cSschwarze enum	outmode {
630f10154cSschwarze 	OUTMODE_DEF = 0,
640f10154cSschwarze 	OUTMODE_FLN,
650f10154cSschwarze 	OUTMODE_LST,
660f10154cSschwarze 	OUTMODE_ALL,
670f10154cSschwarze 	OUTMODE_ONE
680f10154cSschwarze };
69f73abda9Skristaps 
70f73abda9Skristaps enum	outt {
71a35fc07aSschwarze 	OUTT_ASCII = 0,	/* -Tascii */
72a5e11edeSschwarze 	OUTT_LOCALE,	/* -Tlocale */
73a5e11edeSschwarze 	OUTT_UTF8,	/* -Tutf8 */
74a35fc07aSschwarze 	OUTT_TREE,	/* -Ttree */
7575d4d0e5Sschwarze 	OUTT_MAN,	/* -Tman */
76a35fc07aSschwarze 	OUTT_HTML,	/* -Thtml */
77b3257404Sschwarze 	OUTT_MARKDOWN,	/* -Tmarkdown */
78a35fc07aSschwarze 	OUTT_LINT,	/* -Tlint */
79a35fc07aSschwarze 	OUTT_PS,	/* -Tps */
80a35fc07aSschwarze 	OUTT_PDF	/* -Tpdf */
81f73abda9Skristaps };
82f73abda9Skristaps 
833f735e33Sschwarze struct	outstate {
846181cd34Sschwarze 	struct tag_files *tag_files;	/* Tagging state variables. */
85f3476b07Sschwarze 	void		 *outdata;	/* data for output */
866181cd34Sschwarze 	int		  use_pager;
87f3476b07Sschwarze 	int		  wstop;	/* stop after a file with a warning */
8873f693efSschwarze 	int		  had_output;	/* Some output was generated. */
89f3476b07Sschwarze 	enum outt	  outtype;	/* which output to use */
906e03d529Sschwarze };
916e03d529Sschwarze 
929398f94cSschwarze 
939398f94cSschwarze int			  mandocdb(int, char *[]);
949398f94cSschwarze 
9528ca454fSschwarze static	void		  check_xr(struct manpaths *);
967db69220Sschwarze static	void		  fs_append(char **, size_t, int,
977db69220Sschwarze 				size_t, const char *, enum form,
987db69220Sschwarze 				struct manpage **, size_t *);
997db69220Sschwarze static	int		  fs_lookup(const struct manpaths *, size_t,
1007db69220Sschwarze 				const char *, const char *, const char *,
101c5921aadSschwarze 				struct manpage **, size_t *);
10210e17f8fSschwarze static	int		  fs_search(const struct mansearch *,
103bc6b7f6fSschwarze 				const struct manpaths *, const char *,
104c5921aadSschwarze 				struct manpage **, size_t *);
105e8a031dcSschwarze static	void		  glob_esc(char **, const char *, const char *);
1063f735e33Sschwarze static	void		  outdata_alloc(struct outstate *, struct manoutput *);
107d8e30e1dSschwarze static	void		  parse(struct mparse *, int, const char *,
10828ca454fSschwarze 				struct outstate *, struct manconf *);
109ecd1ed85Sschwarze static	void		  passthrough(int, int);
11073f693efSschwarze static	void		  process_onefile(struct mparse *, struct manpage *,
11173f693efSschwarze 				int, struct outstate *, struct manconf *);
112cdecd8e7Sschwarze static	void		  run_pager(struct outstate *, char *);
113cdecd8e7Sschwarze static	pid_t		  spawn_pager(struct outstate *, char *);
114e6187497Sschwarze static	void		  usage(enum argmode) __attribute__((__noreturn__));
115d8e30e1dSschwarze static	int		  woptions(char *, enum mandoc_os *, int *);
116f73abda9Skristaps 
1170f10154cSschwarze static	const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
11874e8e07bSschwarze static	char		  help_arg[] = "help";
11974e8e07bSschwarze static	char		 *help_argv[] = {help_arg, NULL};
120f73abda9Skristaps 
12149aff9f8Sschwarze 
122f73abda9Skristaps int
main(int argc,char * argv[])123f73abda9Skristaps main(int argc, char *argv[])
124f73abda9Skristaps {
125d8e30e1dSschwarze 	struct manconf	 conf;		/* Manpaths and output options. */
1263f735e33Sschwarze 	struct outstate	 outst;		/* Output state. */
127d8e30e1dSschwarze 	struct winsize	 ws;		/* Result of ioctl(TIOCGWINSZ). */
128d8e30e1dSschwarze 	struct mansearch search;	/* Search options. */
1299540818fSschwarze 	struct manpage	*res;		/* Complete list of search results. */
1309540818fSschwarze 	struct manpage	*resn;		/* Search results for one name. */
131d8e30e1dSschwarze 	struct mparse	*mp;		/* Opaque parser object. */
132d8e30e1dSschwarze 	const char	*conf_file;	/* -C: alternate config file. */
133d8e30e1dSschwarze 	const char	*os_s;		/* -I: Operating system for display. */
1341c80596aSschwarze 	const char	*progname, *sec, *ep;
135d8e30e1dSschwarze 	char		*defpaths;	/* -M: override manpaths. */
136d8e30e1dSschwarze 	char		*auxpaths;	/* -m: additional manpaths. */
137d8e30e1dSschwarze 	char		*oarg;		/* -O: output option string. */
138d8e30e1dSschwarze 	char		*tagarg;	/* -O tag: default value. */
139c93abf39Sschwarze 	unsigned char	*uc;
1409540818fSschwarze 	size_t		 ressz;		/* Number of elements in res[]. */
1419540818fSschwarze 	size_t		 resnsz;	/* Number of elements in resn[]. */
1429540818fSschwarze 	size_t		 i, ib, ssz;
143d8e30e1dSschwarze 	int		 options;	/* Parser options. */
144d8e30e1dSschwarze 	int		 show_usage;	/* Invalid argument: give up. */
1452ccd0917Sschwarze 	int		 prio, best_prio;
14673f693efSschwarze 	int		 startdir;
1470f10154cSschwarze 	int		 c;
148d8e30e1dSschwarze 	enum mandoc_os	 os_e;		/* Check base system conventions. */
149d8e30e1dSschwarze 	enum outmode	 outmode;	/* According to command line. */
150f73abda9Skristaps 
1510aad8377Sschwarze 	progname = getprogname();
152eb170d6cSschwarze 	mandoc_msg_setoutfile(stderr);
1530aad8377Sschwarze 	if (strncmp(progname, "mandocdb", 8) == 0 ||
154ecf32ec4Sschwarze 	    strcmp(progname, BINM_MAKEWHATIS) == 0)
155526e306bSschwarze 		return mandocdb(argc, argv);
1568dbd610cSschwarze 
15790f584c6Sschwarze 	if (pledge("stdio rpath wpath cpath tmppath tty proc exec", NULL) == -1) {
158ecd1ed85Sschwarze 		mandoc_msg(MANDOCERR_PLEDGE, 0, 0, "%s", strerror(errno));
159ecd1ed85Sschwarze 		return mandoc_msg_getrc();
160ecd1ed85Sschwarze 	}
1615879777fSschwarze 
1620f10154cSschwarze 	/* Search options. */
163f73abda9Skristaps 
1644de77decSschwarze 	memset(&conf, 0, sizeof(conf));
165d8e30e1dSschwarze 	conf_file = NULL;
166d8e30e1dSschwarze 	defpaths = auxpaths = NULL;
1670f10154cSschwarze 
1680f10154cSschwarze 	memset(&search, 0, sizeof(struct mansearch));
1690f10154cSschwarze 	search.outkey = "Nd";
170c93abf39Sschwarze 	oarg = NULL;
1710f10154cSschwarze 
172ecf32ec4Sschwarze 	if (strcmp(progname, BINM_MAN) == 0)
1730f10154cSschwarze 		search.argmode = ARG_NAME;
174ecf32ec4Sschwarze 	else if (strcmp(progname, BINM_APROPOS) == 0)
1750f10154cSschwarze 		search.argmode = ARG_EXPR;
176ecf32ec4Sschwarze 	else if (strcmp(progname, BINM_WHATIS) == 0)
1770f10154cSschwarze 		search.argmode = ARG_WORD;
1780aad8377Sschwarze 	else if (strncmp(progname, "help", 4) == 0)
17974e8e07bSschwarze 		search.argmode = ARG_NAME;
1800f10154cSschwarze 	else
1810f10154cSschwarze 		search.argmode = ARG_FILE;
1820f10154cSschwarze 
1833f735e33Sschwarze 	/* Parser options. */
1840f10154cSschwarze 
1853f735e33Sschwarze 	options = MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1;
186d8e30e1dSschwarze 	os_e = MANDOC_OS_OTHER;
187d8e30e1dSschwarze 	os_s = NULL;
1883f735e33Sschwarze 
1893f735e33Sschwarze 	/* Formatter options. */
1903f735e33Sschwarze 
1913f735e33Sschwarze 	memset(&outst, 0, sizeof(outst));
1926181cd34Sschwarze 	outst.tag_files = NULL;
1933f735e33Sschwarze 	outst.outtype = OUTT_LOCALE;
1946181cd34Sschwarze 	outst.use_pager = 1;
195f73abda9Skristaps 
1960f10154cSschwarze 	show_usage = 0;
1970f10154cSschwarze 	outmode = OUTMODE_DEF;
1980f10154cSschwarze 
1997d0afed8Sschwarze 	while ((c = getopt(argc, argv,
2007d0afed8Sschwarze 	    "aC:cfhI:iK:klM:m:O:S:s:T:VW:w")) != -1) {
2017d0afed8Sschwarze 		if (c == 'i' && search.argmode == ARG_EXPR) {
2027d0afed8Sschwarze 			optind--;
2037d0afed8Sschwarze 			break;
2047d0afed8Sschwarze 		}
205f73abda9Skristaps 		switch (c) {
2060f10154cSschwarze 		case 'a':
2070f10154cSschwarze 			outmode = OUTMODE_ALL;
2080f10154cSschwarze 			break;
2090f10154cSschwarze 		case 'C':
2100f10154cSschwarze 			conf_file = optarg;
2110f10154cSschwarze 			break;
2120f10154cSschwarze 		case 'c':
2136181cd34Sschwarze 			outst.use_pager = 0;
2140f10154cSschwarze 			break;
2150f10154cSschwarze 		case 'f':
2160f10154cSschwarze 			search.argmode = ARG_WORD;
2170f10154cSschwarze 			break;
2180a0199c7Sschwarze 		case 'h':
2192ccd0917Sschwarze 			conf.output.synopsisonly = 1;
2206181cd34Sschwarze 			outst.use_pager = 0;
2210a0199c7Sschwarze 			outmode = OUTMODE_ALL;
2220a0199c7Sschwarze 			break;
22349aff9f8Sschwarze 		case 'I':
224ecd1ed85Sschwarze 			if (strncmp(optarg, "os=", 3) != 0) {
225ecd1ed85Sschwarze 				mandoc_msg(MANDOCERR_BADARG_BAD, 0, 0,
226ecd1ed85Sschwarze 				    "-I %s", optarg);
227ecd1ed85Sschwarze 				return mandoc_msg_getrc();
228353fa9ecSschwarze 			}
229d8e30e1dSschwarze 			if (os_s != NULL) {
230ecd1ed85Sschwarze 				mandoc_msg(MANDOCERR_BADARG_DUPE, 0, 0,
231ecd1ed85Sschwarze 				    "-I %s", optarg);
232ecd1ed85Sschwarze 				return mandoc_msg_getrc();
233353fa9ecSschwarze 			}
234d8e30e1dSschwarze 			os_s = optarg + 3;
235353fa9ecSschwarze 			break;
2367232fc26Sschwarze 		case 'K':
237ecd1ed85Sschwarze 			options &= ~(MPARSE_UTF8 | MPARSE_LATIN1);
238ecd1ed85Sschwarze 			if (strcmp(optarg, "utf-8") == 0)
239ecd1ed85Sschwarze 				options |=  MPARSE_UTF8;
240ecd1ed85Sschwarze 			else if (strcmp(optarg, "iso-8859-1") == 0)
241ecd1ed85Sschwarze 				options |=  MPARSE_LATIN1;
242ecd1ed85Sschwarze 			else if (strcmp(optarg, "us-ascii") != 0) {
243ecd1ed85Sschwarze 				mandoc_msg(MANDOCERR_BADARG_BAD, 0, 0,
244ecd1ed85Sschwarze 				    "-K %s", optarg);
245ecd1ed85Sschwarze 				return mandoc_msg_getrc();
246ecd1ed85Sschwarze 			}
2477232fc26Sschwarze 			break;
2480f10154cSschwarze 		case 'k':
2490f10154cSschwarze 			search.argmode = ARG_EXPR;
2500f10154cSschwarze 			break;
251db8b2ff0Sschwarze 		case 'l':
252db8b2ff0Sschwarze 			search.argmode = ARG_FILE;
253db8b2ff0Sschwarze 			outmode = OUTMODE_ALL;
254db8b2ff0Sschwarze 			break;
2550f10154cSschwarze 		case 'M':
2560f10154cSschwarze 			defpaths = optarg;
2570f10154cSschwarze 			break;
25849aff9f8Sschwarze 		case 'm':
2590f10154cSschwarze 			auxpaths = optarg;
260f73abda9Skristaps 			break;
26149aff9f8Sschwarze 		case 'O':
262c93abf39Sschwarze 			oarg = optarg;
2634175bdabSschwarze 			break;
2640f10154cSschwarze 		case 'S':
2650f10154cSschwarze 			search.arch = optarg;
2660f10154cSschwarze 			break;
2670f10154cSschwarze 		case 's':
2680f10154cSschwarze 			search.sec = optarg;
2690f10154cSschwarze 			break;
27049aff9f8Sschwarze 		case 'T':
271ecd1ed85Sschwarze 			if (strcmp(optarg, "ascii") == 0)
2723f735e33Sschwarze 				outst.outtype = OUTT_ASCII;
273ecd1ed85Sschwarze 			else if (strcmp(optarg, "lint") == 0) {
2743f735e33Sschwarze 				outst.outtype = OUTT_LINT;
275ecd1ed85Sschwarze 				mandoc_msg_setoutfile(stdout);
276ecd1ed85Sschwarze 				mandoc_msg_setmin(MANDOCERR_BASE);
277ecd1ed85Sschwarze 			} else if (strcmp(optarg, "tree") == 0)
2783f735e33Sschwarze 				outst.outtype = OUTT_TREE;
279ecd1ed85Sschwarze 			else if (strcmp(optarg, "man") == 0)
2803f735e33Sschwarze 				outst.outtype = OUTT_MAN;
281ecd1ed85Sschwarze 			else if (strcmp(optarg, "html") == 0)
2823f735e33Sschwarze 				outst.outtype = OUTT_HTML;
283ecd1ed85Sschwarze 			else if (strcmp(optarg, "markdown") == 0)
2843f735e33Sschwarze 				outst.outtype = OUTT_MARKDOWN;
285ecd1ed85Sschwarze 			else if (strcmp(optarg, "utf8") == 0)
2863f735e33Sschwarze 				outst.outtype = OUTT_UTF8;
287ecd1ed85Sschwarze 			else if (strcmp(optarg, "locale") == 0)
2883f735e33Sschwarze 				outst.outtype = OUTT_LOCALE;
289ecd1ed85Sschwarze 			else if (strcmp(optarg, "ps") == 0)
2903f735e33Sschwarze 				outst.outtype = OUTT_PS;
291ecd1ed85Sschwarze 			else if (strcmp(optarg, "pdf") == 0)
2923f735e33Sschwarze 				outst.outtype = OUTT_PDF;
293ecd1ed85Sschwarze 			else {
294ecd1ed85Sschwarze 				mandoc_msg(MANDOCERR_BADARG_BAD, 0, 0,
295ecd1ed85Sschwarze 				    "-T %s", optarg);
296ecd1ed85Sschwarze 				return mandoc_msg_getrc();
297ecd1ed85Sschwarze 			}
298f73abda9Skristaps 			break;
29949aff9f8Sschwarze 		case 'W':
3003f735e33Sschwarze 			if (woptions(optarg, &os_e, &outst.wstop) == -1)
301ecd1ed85Sschwarze 				return mandoc_msg_getrc();
302f73abda9Skristaps 			break;
3030f10154cSschwarze 		case 'w':
3040f10154cSschwarze 			outmode = OUTMODE_FLN;
3050f10154cSschwarze 			break;
306f73abda9Skristaps 		default:
3070f10154cSschwarze 			show_usage = 1;
3080f10154cSschwarze 			break;
309f73abda9Skristaps 		}
3100f10154cSschwarze 	}
3110f10154cSschwarze 
3120f10154cSschwarze 	if (show_usage)
3130f10154cSschwarze 		usage(search.argmode);
3140f10154cSschwarze 
3150f10154cSschwarze 	/* Postprocess options. */
3160f10154cSschwarze 
317b3eeebc7Sschwarze 	switch (outmode) {
318b3eeebc7Sschwarze 	case OUTMODE_DEF:
3190f10154cSschwarze 		switch (search.argmode) {
3200f10154cSschwarze 		case ARG_FILE:
3210f10154cSschwarze 			outmode = OUTMODE_ALL;
3226181cd34Sschwarze 			outst.use_pager = 0;
3230f10154cSschwarze 			break;
3240f10154cSschwarze 		case ARG_NAME:
3250f10154cSschwarze 			outmode = OUTMODE_ONE;
3260f10154cSschwarze 			break;
3270f10154cSschwarze 		default:
3280f10154cSschwarze 			outmode = OUTMODE_LST;
3290f10154cSschwarze 			break;
3300f10154cSschwarze 		}
331b3eeebc7Sschwarze 		break;
332b3eeebc7Sschwarze 	case OUTMODE_FLN:
333b3eeebc7Sschwarze 		if (search.argmode == ARG_FILE)
334b3eeebc7Sschwarze 			outmode = OUTMODE_ALL;
335b3eeebc7Sschwarze 		break;
336b3eeebc7Sschwarze 	case OUTMODE_ALL:
337b3eeebc7Sschwarze 		break;
338b3eeebc7Sschwarze 	case OUTMODE_LST:
339b3eeebc7Sschwarze 	case OUTMODE_ONE:
340b3eeebc7Sschwarze 		abort();
3410f10154cSschwarze 	}
3420f10154cSschwarze 
343c93abf39Sschwarze 	if (oarg != NULL) {
344c93abf39Sschwarze 		if (outmode == OUTMODE_LST)
345c93abf39Sschwarze 			search.outkey = oarg;
346c93abf39Sschwarze 		else {
347c93abf39Sschwarze 			while (oarg != NULL) {
348c93abf39Sschwarze 				if (manconf_output(&conf.output,
349bbfeca9fSschwarze 				    strsep(&oarg, ","), 0) == -1)
350ecd1ed85Sschwarze 					return mandoc_msg_getrc();
351c93abf39Sschwarze 			}
352c93abf39Sschwarze 		}
353c93abf39Sschwarze 	}
354c93abf39Sschwarze 
3553f735e33Sschwarze 	if (outst.outtype != OUTT_TREE || conf.output.noval == 0)
3566b86842eSschwarze 		options |= MPARSE_VALIDATE;
3576b86842eSschwarze 
3584a5b30c7Sschwarze 	if (outmode == OUTMODE_FLN ||
3594a5b30c7Sschwarze 	    outmode == OUTMODE_LST ||
36090f584c6Sschwarze 	    (conf.output.outfilename == NULL &&
36190f584c6Sschwarze 	     conf.output.tagfilename == NULL &&
36290f584c6Sschwarze 	     isatty(STDOUT_FILENO) == 0))
3636181cd34Sschwarze 		outst.use_pager = 0;
3644a5b30c7Sschwarze 
3656181cd34Sschwarze 	if (outst.use_pager &&
366cbdb6ce4Sschwarze 	    (conf.output.width == 0 || conf.output.indent == 0) &&
36724292e85Sschwarze 	    ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) != -1 &&
36824292e85Sschwarze 	    ws.ws_col > 1) {
369cbdb6ce4Sschwarze 		if (conf.output.width == 0 && ws.ws_col < 79)
370cbdb6ce4Sschwarze 			conf.output.width = ws.ws_col - 1;
371cbdb6ce4Sschwarze 		if (conf.output.indent == 0 && ws.ws_col < 66)
372cbdb6ce4Sschwarze 			conf.output.indent = 3;
373cbdb6ce4Sschwarze 	}
374cbdb6ce4Sschwarze 
37590f584c6Sschwarze 	if (outst.use_pager == 0)
37690f584c6Sschwarze 		c = pledge("stdio rpath", NULL);
37790f584c6Sschwarze 	else if (conf.output.outfilename != NULL ||
37890f584c6Sschwarze 	    conf.output.tagfilename != NULL)
37990f584c6Sschwarze 		c = pledge("stdio rpath wpath cpath", NULL);
38090f584c6Sschwarze 	else
38190f584c6Sschwarze 		c = pledge("stdio rpath tmppath tty proc exec", NULL);
38290f584c6Sschwarze 	if (c == -1) {
38390f584c6Sschwarze 		mandoc_msg(MANDOCERR_PLEDGE, 0, 0, "%s", strerror(errno));
384ecd1ed85Sschwarze 		return mandoc_msg_getrc();
385ecd1ed85Sschwarze 	}
3865879777fSschwarze 
3870f10154cSschwarze 	/* Parse arguments. */
3880f10154cSschwarze 
389a42747d6Sschwarze 	if (argc > 0) {
3900f10154cSschwarze 		argc -= optind;
3910f10154cSschwarze 		argv += optind;
392a42747d6Sschwarze 	}
3930f10154cSschwarze 
39474e8e07bSschwarze 	/*
3959540818fSschwarze 	 * Quirks for help(1) and man(1),
3969540818fSschwarze 	 * in particular for a section argument without -s.
39774e8e07bSschwarze 	 */
3980f10154cSschwarze 
39974e8e07bSschwarze 	if (search.argmode == ARG_NAME) {
4000aad8377Sschwarze 		if (*progname == 'h') {
40174e8e07bSschwarze 			if (argc == 0) {
40274e8e07bSschwarze 				argv = help_argv;
40374e8e07bSschwarze 				argc = 1;
40474e8e07bSschwarze 			}
405f9282106Sschwarze 		} else if (argc > 1 &&
4061918841eSschwarze 		    ((uc = (unsigned char *)argv[0]) != NULL) &&
407afe74b66Sschwarze 		    ((isdigit(uc[0]) && (uc[1] == '\0' ||
40874c6ffceSschwarze 		      isalpha(uc[1]))) ||
409afe74b66Sschwarze 		     (uc[0] == 'n' && uc[1] == '\0'))) {
4101918841eSschwarze 			search.sec = (char *)uc;
4110f10154cSschwarze 			argv++;
4120f10154cSschwarze 			argc--;
4130f10154cSschwarze 		}
414c68846c0Sschwarze 		if (search.arch == NULL)
415c68846c0Sschwarze 			search.arch = getenv("MACHINE");
416c68846c0Sschwarze 		if (search.arch == NULL)
417c68846c0Sschwarze 			search.arch = MACHINE;
4189540818fSschwarze 		if (outmode == OUTMODE_ONE)
4199540818fSschwarze 			search.firstmatch = 1;
42074e8e07bSschwarze 	}
4210f10154cSschwarze 
422bbeffd04Sschwarze 	/*
423bbeffd04Sschwarze 	 * Use the first argument for -O tag in addition to
424bbeffd04Sschwarze 	 * using it as a search term for man(1) or apropos(1).
425bbeffd04Sschwarze 	 */
426bbeffd04Sschwarze 
427bbeffd04Sschwarze 	if (conf.output.tag != NULL && *conf.output.tag == '\0') {
428bbeffd04Sschwarze 		tagarg = argc > 0 && search.argmode == ARG_EXPR ?
429bbeffd04Sschwarze 		    strchr(*argv, '=') : NULL;
430bbeffd04Sschwarze 		conf.output.tag = tagarg == NULL ? *argv : tagarg + 1;
431bbeffd04Sschwarze 	}
432bbeffd04Sschwarze 
4339540818fSschwarze 	/* Read the configuration file. */
4340f10154cSschwarze 
43528ca454fSschwarze 	if (search.argmode != ARG_FILE ||
43628ca454fSschwarze 	    mandoc_msg_getmin() == MANDOCERR_STYLE)
4374de77decSschwarze 		manconf_parse(&conf, conf_file, defpaths, auxpaths);
438c5921aadSschwarze 
4399540818fSschwarze 	/* man(1): Resolve each name individually. */
4402acc4fe7Sschwarze 
4412acc4fe7Sschwarze 	if (search.argmode == ARG_NAME) {
442db5b0f16Sschwarze 		if (argc < 1) {
443db5b0f16Sschwarze 			if (outmode != OUTMODE_FLN)
4449540818fSschwarze 				usage(ARG_NAME);
445db5b0f16Sschwarze 			if (conf.manpath.sz == 0) {
446db5b0f16Sschwarze 				warnx("The manpath is empty.");
447db5b0f16Sschwarze 				mandoc_msg_setrc(MANDOCLEVEL_BADARG);
448db5b0f16Sschwarze 			} else {
449db5b0f16Sschwarze 				for (i = 0; i + 1 < conf.manpath.sz; i++)
450db5b0f16Sschwarze 					printf("%s:", conf.manpath.paths[i]);
451db5b0f16Sschwarze 				printf("%s\n", conf.manpath.paths[i]);
452db5b0f16Sschwarze 			}
453db5b0f16Sschwarze 			manconf_free(&conf);
454db5b0f16Sschwarze 			return (int)mandoc_msg_getrc();
455db5b0f16Sschwarze 		}
4569540818fSschwarze 		for (res = NULL, ressz = 0; argc > 0; argc--, argv++) {
4579540818fSschwarze 			(void)mansearch(&search, &conf.manpath,
4589540818fSschwarze 			    1, argv, &resn, &resnsz);
4599540818fSschwarze 			if (resnsz == 0)
4609540818fSschwarze 				(void)fs_search(&search, &conf.manpath,
461bc6b7f6fSschwarze 				    *argv, &resn, &resnsz);
462bc6b7f6fSschwarze 			if (resnsz == 0 && strchr(*argv, '/') == NULL) {
463bc6b7f6fSschwarze 				if (search.arch != NULL &&
464bc6b7f6fSschwarze 				    arch_valid(search.arch, OSENUM) == 0)
465bc6b7f6fSschwarze 					warnx("Unknown architecture \"%s\".",
466bc6b7f6fSschwarze 					    search.arch);
467bc6b7f6fSschwarze 				else if (search.sec != NULL)
468bc6b7f6fSschwarze 					warnx("No entry for %s in "
469bc6b7f6fSschwarze 					    "section %s of the manual.",
470bc6b7f6fSschwarze 					    *argv, search.sec);
471bc6b7f6fSschwarze 				else
472bc6b7f6fSschwarze 					warnx("No entry for %s in "
473bc6b7f6fSschwarze 					    "the manual.", *argv);
4749540818fSschwarze 				mandoc_msg_setrc(MANDOCLEVEL_BADARG);
4752acc4fe7Sschwarze 				continue;
4769540818fSschwarze 			}
477bc6b7f6fSschwarze 			if (resnsz == 0) {
4789540818fSschwarze 				if (access(*argv, R_OK) == -1) {
4799540818fSschwarze 					mandoc_msg_setinfilename(*argv);
480ecd1ed85Sschwarze 					mandoc_msg(MANDOCERR_BADARG_BAD,
481ecd1ed85Sschwarze 					    0, 0, "%s", strerror(errno));
482ecd1ed85Sschwarze 					mandoc_msg_setinfilename(NULL);
4832acc4fe7Sschwarze 					continue;
4842acc4fe7Sschwarze 				}
4859540818fSschwarze 				resnsz = 1;
4869540818fSschwarze 				resn = mandoc_calloc(resnsz, sizeof(*res));
4879540818fSschwarze 				resn->file = mandoc_strdup(*argv);
4889540818fSschwarze 				resn->ipath = SIZE_MAX;
4899540818fSschwarze 				resn->form = FORM_SRC;
4909540818fSschwarze 			}
4919540818fSschwarze 			if (outmode != OUTMODE_ONE || resnsz == 1) {
4922acc4fe7Sschwarze 				res = mandoc_reallocarray(res,
4939540818fSschwarze 				    ressz + resnsz, sizeof(*res));
4949540818fSschwarze 				memcpy(res + ressz, resn,
4959540818fSschwarze 				    sizeof(*resn) * resnsz);
4969540818fSschwarze 				ressz += resnsz;
4970883e5d9Sschwarze 				free(resn);
4980883e5d9Sschwarze 				resn = NULL;
4990883e5d9Sschwarze 				resnsz = 0;
5009540818fSschwarze 				continue;
50160453e88Sschwarze 			}
5020f10154cSschwarze 
5030f10154cSschwarze 			/* Search for the best section. */
5049540818fSschwarze 
5059540818fSschwarze 			best_prio = 40;
5069540818fSschwarze 			for (ib = i = 0; i < resnsz; i++) {
507c02a7420Sschwarze 				sec = resn[i].file +
508c02a7420Sschwarze 				    strlen(conf.manpath.paths[resn[i].ipath]);
509598b66e2Sschwarze 				sec += strcspn(sec, "123456789");
510598b66e2Sschwarze 				if (sec[0] == '\0')
51188412cd2Sschwarze 					continue; /* No section at all. */
512598b66e2Sschwarze 				prio = sec_prios[sec[0] - '1'];
51388412cd2Sschwarze 				if (search.sec != NULL) {
51488412cd2Sschwarze 					ssz = strlen(search.sec);
51588412cd2Sschwarze 					if (strncmp(sec, search.sec, ssz) == 0)
51688412cd2Sschwarze 						sec += ssz;
51788412cd2Sschwarze 				} else
51888412cd2Sschwarze 					sec++; /* Prefer without suffix. */
51988412cd2Sschwarze 				if (*sec != '/')
52088412cd2Sschwarze 					prio += 10; /* Wrong dir name. */
5211c80596aSschwarze 				if (search.sec != NULL) {
5221c80596aSschwarze 					ep = strchr(sec, '\0');
5231c80596aSschwarze 					if (ep - sec > 3 &&
5241c80596aSschwarze 					    strncmp(ep - 3, ".gz", 3) == 0)
5251c80596aSschwarze 						ep -= 3;
5261c80596aSschwarze 					if ((size_t)(ep - sec) < ssz + 3 ||
5271c80596aSschwarze 					    strncmp(ep - ssz, search.sec,
5281c80596aSschwarze 					     ssz) != 0)      /* Wrong file */
5291c80596aSschwarze 						prio += 20;  /* extension. */
5301c80596aSschwarze 				}
5310f10154cSschwarze 				if (prio >= best_prio)
5320f10154cSschwarze 					continue;
5330f10154cSschwarze 				best_prio = prio;
5349540818fSschwarze 				ib = i;
5350f10154cSschwarze 			}
5369540818fSschwarze 			res = mandoc_reallocarray(res, ressz + 1,
5379540818fSschwarze 			    sizeof(*res));
5389540818fSschwarze 			memcpy(res + ressz++, resn + ib, sizeof(*resn));
5390883e5d9Sschwarze 			memset(resn + ib, 0, sizeof(*resn));
5400883e5d9Sschwarze 			mansearch_free(resn, resnsz);
5410883e5d9Sschwarze 			resn = NULL;
5420883e5d9Sschwarze 			resnsz = 0;
5430f10154cSschwarze 		}
5440f10154cSschwarze 
5459540818fSschwarze 	/* apropos(1), whatis(1): Process the full search expression. */
5460f10154cSschwarze 
5479540818fSschwarze 	} else if (search.argmode != ARG_FILE) {
5489540818fSschwarze 		if (mansearch(&search, &conf.manpath,
5499540818fSschwarze 		    argc, argv, &res, &ressz) == 0)
5509540818fSschwarze 			usage(search.argmode);
5519540818fSschwarze 
5529540818fSschwarze 		if (ressz == 0) {
5539540818fSschwarze 			warnx("nothing appropriate");
5549540818fSschwarze 			mandoc_msg_setrc(MANDOCLEVEL_BADARG);
5550f10154cSschwarze 			goto out;
5569540818fSschwarze 		}
5579540818fSschwarze 
5589540818fSschwarze 	/* mandoc(1): Take command line arguments as file names. */
5599540818fSschwarze 
56073f693efSschwarze 	} else {
5619540818fSschwarze 		ressz = argc > 0 ? argc : 1;
5629540818fSschwarze 		res = mandoc_calloc(ressz, sizeof(*res));
5639540818fSschwarze 		for (i = 0; i < ressz; i++) {
56473f693efSschwarze 			if (argc > 0)
56573f693efSschwarze 				res[i].file = mandoc_strdup(argv[i]);
56673f693efSschwarze 			res[i].ipath = SIZE_MAX;
56773f693efSschwarze 			res[i].form = FORM_SRC;
56873f693efSschwarze 		}
5690f10154cSschwarze 	}
5700f10154cSschwarze 
5719540818fSschwarze 	switch (outmode) {
5729540818fSschwarze 	case OUTMODE_FLN:
5739540818fSschwarze 		for (i = 0; i < ressz; i++)
5749540818fSschwarze 			puts(res[i].file);
5759540818fSschwarze 		goto out;
5769540818fSschwarze 	case OUTMODE_LST:
5779540818fSschwarze 		for (i = 0; i < ressz; i++)
5789540818fSschwarze 			printf("%s - %s\n", res[i].names,
5799540818fSschwarze 			    res[i].output == NULL ? "" :
5809540818fSschwarze 			    res[i].output);
5819540818fSschwarze 		goto out;
5829540818fSschwarze 	default:
5839540818fSschwarze 		break;
5849540818fSschwarze 	}
5850f10154cSschwarze 
586ecd1ed85Sschwarze 	if (search.argmode == ARG_FILE && auxpaths != NULL) {
587ecd1ed85Sschwarze 		if (strcmp(auxpaths, "doc") == 0)
588ecd1ed85Sschwarze 			options |= MPARSE_MDOC;
589ecd1ed85Sschwarze 		else if (strcmp(auxpaths, "an") == 0)
590ecd1ed85Sschwarze 			options |= MPARSE_MAN;
591ecd1ed85Sschwarze 	}
5920f10154cSschwarze 
59316536faaSschwarze 	mchars_alloc();
594d8e30e1dSschwarze 	mp = mparse_alloc(options, os_e, os_s);
595a35fc07aSschwarze 
5962acc4fe7Sschwarze 	/*
5972acc4fe7Sschwarze 	 * Remember the original working directory, if possible.
5982acc4fe7Sschwarze 	 * This will be needed if some names on the command line
5992acc4fe7Sschwarze 	 * are page names and some are relative file names.
6002acc4fe7Sschwarze 	 * Do not error out if the current directory is not
6012acc4fe7Sschwarze 	 * readable: Maybe it won't be needed after all.
6022acc4fe7Sschwarze 	 */
6032acc4fe7Sschwarze 	startdir = open(".", O_RDONLY | O_DIRECTORY);
6049540818fSschwarze 	for (i = 0; i < ressz; i++) {
6059540818fSschwarze 		process_onefile(mp, res + i, startdir, &outst, &conf);
6063f735e33Sschwarze 		if (outst.wstop && mandoc_msg_getrc() != MANDOCLEVEL_OK)
6079f39971fSschwarze 			break;
608f73abda9Skristaps 	}
6092acc4fe7Sschwarze 	if (startdir != -1) {
6102acc4fe7Sschwarze 		(void)fchdir(startdir);
6112acc4fe7Sschwarze 		close(startdir);
6122acc4fe7Sschwarze 	}
613beabc24cSschwarze 	if (conf.output.tag != NULL && conf.output.tag_found == 0) {
614beabc24cSschwarze 		mandoc_msg(MANDOCERR_TAG, 0, 0, "%s", conf.output.tag);
615beabc24cSschwarze 		conf.output.tag = NULL;
616beabc24cSschwarze 	}
6173f735e33Sschwarze 	if (outst.outdata != NULL) {
6183f735e33Sschwarze 		switch (outst.outtype) {
619b68ff58bSschwarze 		case OUTT_HTML:
6203f735e33Sschwarze 			html_free(outst.outdata);
621b68ff58bSschwarze 			break;
622b68ff58bSschwarze 		case OUTT_UTF8:
623b68ff58bSschwarze 		case OUTT_LOCALE:
624b68ff58bSschwarze 		case OUTT_ASCII:
6253f735e33Sschwarze 			ascii_free(outst.outdata);
626b68ff58bSschwarze 			break;
627b68ff58bSschwarze 		case OUTT_PDF:
628b68ff58bSschwarze 		case OUTT_PS:
6293f735e33Sschwarze 			pspdf_free(outst.outdata);
630b68ff58bSschwarze 			break;
631b68ff58bSschwarze 		default:
632b68ff58bSschwarze 			break;
633b68ff58bSschwarze 		}
634da67bd1bSschwarze 	}
63519b6bef7Sschwarze 	mandoc_xr_free();
636d8e30e1dSschwarze 	mparse_free(mp);
63716536faaSschwarze 	mchars_free();
6380f10154cSschwarze 
6390f10154cSschwarze out:
6409540818fSschwarze 	mansearch_free(res, ressz);
64173f693efSschwarze 	if (search.argmode != ARG_FILE)
64273f693efSschwarze 		manconf_free(&conf);
643f73abda9Skristaps 
6446181cd34Sschwarze 	if (outst.tag_files != NULL) {
64590f584c6Sschwarze 		if (term_tag_close() != -1 &&
64690f584c6Sschwarze 		    conf.output.outfilename == NULL &&
64790f584c6Sschwarze 		    conf.output.tagfilename == NULL)
648cdecd8e7Sschwarze 			run_pager(&outst, conf.output.tag);
6490ac7e6ecSschwarze 		term_tag_unlink();
65073f693efSschwarze 	} else if (outst.had_output && outst.outtype != OUTT_LINT)
65116c326ecSschwarze 		mandoc_msg_summary();
65216c326ecSschwarze 
653e501e731Sschwarze 	return (int)mandoc_msg_getrc();
654f73abda9Skristaps }
655f73abda9Skristaps 
656aa2d850aSschwarze static void
usage(enum argmode argmode)6570f10154cSschwarze usage(enum argmode argmode)
658f73abda9Skristaps {
6590f10154cSschwarze 	switch (argmode) {
6600f10154cSschwarze 	case ARG_FILE:
661dc1f54e8Sschwarze 		fputs("usage: mandoc [-ac] [-I os=name] "
662dc1f54e8Sschwarze 		    "[-K encoding] [-mdoc | -man] [-O options]\n"
6635f08183eSjmc 		    "\t      [-T output] [-W level] [file ...]\n", stderr);
6640f10154cSschwarze 		break;
6650f10154cSschwarze 	case ARG_NAME:
666dc1f54e8Sschwarze 		fputs("usage: man [-acfhklw] [-C file] [-M path] "
667dc1f54e8Sschwarze 		    "[-m path] [-S subsection]\n"
668dc1f54e8Sschwarze 		    "\t   [[-s] section] name ...\n", stderr);
6690f10154cSschwarze 		break;
6700f10154cSschwarze 	case ARG_WORD:
671dc1f54e8Sschwarze 		fputs("usage: whatis [-afk] [-C file] "
672db8b2ff0Sschwarze 		    "[-M path] [-m path] [-O outkey] [-S arch]\n"
673db8b2ff0Sschwarze 		    "\t      [-s section] name ...\n", stderr);
6740f10154cSschwarze 		break;
6750f10154cSschwarze 	case ARG_EXPR:
676dc1f54e8Sschwarze 		fputs("usage: apropos [-afk] [-C file] "
677db8b2ff0Sschwarze 		    "[-M path] [-m path] [-O outkey] [-S arch]\n"
6780f10154cSschwarze 		    "\t       [-s section] expression ...\n", stderr);
6790f10154cSschwarze 		break;
6800f10154cSschwarze 	}
681b5fbc6a9Sschwarze 	exit((int)MANDOCLEVEL_BADARG);
682f73abda9Skristaps }
683f73abda9Skristaps 
684e8a031dcSschwarze static void
glob_esc(char ** dst,const char * src,const char * suffix)685e8a031dcSschwarze glob_esc(char **dst, const char *src, const char *suffix)
686e8a031dcSschwarze {
687e8a031dcSschwarze 	while (*src != '\0') {
688e8a031dcSschwarze 		if (strchr("*?[", *src) != NULL)
689e8a031dcSschwarze 			*(*dst)++ = '\\';
690e8a031dcSschwarze 		*(*dst)++ = *src++;
691e8a031dcSschwarze 	}
692e8a031dcSschwarze 	while (*suffix != '\0')
693e8a031dcSschwarze 		*(*dst)++ = *suffix++;
694e8a031dcSschwarze }
695e8a031dcSschwarze 
6967db69220Sschwarze static void
fs_append(char ** file,size_t filesz,int copy,size_t ipath,const char * sec,enum form form,struct manpage ** res,size_t * ressz)6977db69220Sschwarze fs_append(char **file, size_t filesz, int copy, size_t ipath,
6987db69220Sschwarze     const char *sec, enum form form, struct manpage **res, size_t *ressz)
6997db69220Sschwarze {
7007db69220Sschwarze 	struct manpage	*page;
7017db69220Sschwarze 
7027db69220Sschwarze 	*res = mandoc_reallocarray(*res, *ressz + filesz, sizeof(**res));
7037db69220Sschwarze 	page = *res + *ressz;
7047db69220Sschwarze 	*ressz += filesz;
7057db69220Sschwarze 	for (;;) {
7067db69220Sschwarze 		page->file = copy ? mandoc_strdup(*file) : *file;
7077db69220Sschwarze 		page->names = NULL;
7087db69220Sschwarze 		page->output = NULL;
7097db69220Sschwarze 		page->bits = NAME_FILE & NAME_MASK;
7107db69220Sschwarze 		page->ipath = ipath;
7117db69220Sschwarze 		page->sec = (*sec >= '1' && *sec <= '9') ? *sec - '1' + 1 : 10;
7127db69220Sschwarze 		page->form = form;
7137db69220Sschwarze 		if (--filesz == 0)
7147db69220Sschwarze 			break;
7157db69220Sschwarze 		file++;
7167db69220Sschwarze 		page++;
7177db69220Sschwarze 	}
7187db69220Sschwarze }
7197db69220Sschwarze 
720c5921aadSschwarze static int
fs_lookup(const struct manpaths * paths,size_t ipath,const char * sec,const char * arch,const char * name,struct manpage ** res,size_t * ressz)721c5921aadSschwarze fs_lookup(const struct manpaths *paths, size_t ipath,
722c5921aadSschwarze 	const char *sec, const char *arch, const char *name,
723c5921aadSschwarze 	struct manpage **res, size_t *ressz)
724c5921aadSschwarze {
725a231508dSschwarze 	struct stat	 sb;
726e8e3ce36Sschwarze 	glob_t		 globinfo;
7277db69220Sschwarze 	char		*file, *cp, secnum[2];
728ff2dbb0fSschwarze 	int		 globres;
729ff2dbb0fSschwarze 	enum form	 form;
730c5921aadSschwarze 
731e8a031dcSschwarze 	const char *const slman = "/man";
732e8a031dcSschwarze 	const char *const slash = "/";
733e8a031dcSschwarze 	const char *const sglob = ".[01-9]*";
7347db69220Sschwarze 	const char *const dot   = ".";
7357db69220Sschwarze 	const char *const aster = "*";
736e8a031dcSschwarze 
7377db69220Sschwarze 	memset(&globinfo, 0, sizeof(globinfo));
738e8e3ce36Sschwarze 	form = FORM_SRC;
7397db69220Sschwarze 
740c5921aadSschwarze 	mandoc_asprintf(&file, "%s/man%s/%s.%s",
741c5921aadSschwarze 	    paths->paths[ipath], sec, name, sec);
742a231508dSschwarze 	if (stat(file, &sb) != -1)
743c5921aadSschwarze 		goto found;
744c5921aadSschwarze 	free(file);
745c5921aadSschwarze 
746c5921aadSschwarze 	mandoc_asprintf(&file, "%s/cat%s/%s.0",
747c5921aadSschwarze 	    paths->paths[ipath], sec, name);
748a231508dSschwarze 	if (stat(file, &sb) != -1) {
749c5921aadSschwarze 		form = FORM_CAT;
750c5921aadSschwarze 		goto found;
751c5921aadSschwarze 	}
752c5921aadSschwarze 	free(file);
753c5921aadSschwarze 
754c5921aadSschwarze 	if (arch != NULL) {
755c5921aadSschwarze 		mandoc_asprintf(&file, "%s/man%s/%s/%s.%s",
756c5921aadSschwarze 		    paths->paths[ipath], sec, arch, name, sec);
757a231508dSschwarze 		if (stat(file, &sb) != -1)
758c5921aadSschwarze 			goto found;
759c5921aadSschwarze 		free(file);
760c5921aadSschwarze 	}
761e8e3ce36Sschwarze 
762e8a031dcSschwarze 	cp = file = mandoc_malloc(strlen(paths->paths[ipath]) * 2 +
763e8a031dcSschwarze 	    strlen(slman) + strlen(sec) * 2 + strlen(slash) +
764e8a031dcSschwarze 	    strlen(name) * 2 + strlen(sglob) + 1);
765e8a031dcSschwarze 	glob_esc(&cp, paths->paths[ipath], slman);
766e8a031dcSschwarze 	glob_esc(&cp, sec, slash);
767e8a031dcSschwarze 	glob_esc(&cp, name, sglob);
768e8a031dcSschwarze 	*cp = '\0';
769e8e3ce36Sschwarze 	globres = glob(file, 0, NULL, &globinfo);
770e8e3ce36Sschwarze 	if (globres != 0 && globres != GLOB_NOMATCH)
771ecd1ed85Sschwarze 		mandoc_msg(MANDOCERR_GLOB, 0, 0,
772ecd1ed85Sschwarze 		    "%s: %s", file, strerror(errno));
773e8e3ce36Sschwarze 	free(file);
7747db69220Sschwarze 	file = NULL;
775e8e3ce36Sschwarze 	if (globres == 0)
776ad164ce6Sschwarze 		goto found;
7777db69220Sschwarze 	globfree(&globinfo);
7787db69220Sschwarze 
7797db69220Sschwarze 	if (sec[1] != '\0' && *ressz == 0) {
7807db69220Sschwarze 		secnum[0] = sec[0];
7817db69220Sschwarze 		secnum[1] = '\0';
7827db69220Sschwarze 		cp = file = mandoc_malloc(strlen(paths->paths[ipath]) * 2 +
7837db69220Sschwarze 		    strlen(slman) + strlen(secnum) * 2 + strlen(slash) +
7847db69220Sschwarze 		    strlen(name) * 2 + strlen(dot) +
7857db69220Sschwarze 		    strlen(sec) * 2 + strlen(aster) + 1);
7867db69220Sschwarze 		glob_esc(&cp, paths->paths[ipath], slman);
7877db69220Sschwarze 		glob_esc(&cp, secnum, slash);
7887db69220Sschwarze 		glob_esc(&cp, name, dot);
7897db69220Sschwarze 		glob_esc(&cp, sec, aster);
7907db69220Sschwarze 		*cp = '\0';
7917db69220Sschwarze 		globres = glob(file, 0, NULL, &globinfo);
7927db69220Sschwarze 		if (globres != 0 && globres != GLOB_NOMATCH)
7937db69220Sschwarze 			mandoc_msg(MANDOCERR_GLOB, 0, 0,
7947db69220Sschwarze 			    "%s: %s", file, strerror(errno));
795a231508dSschwarze 		free(file);
7967db69220Sschwarze 		file = NULL;
7977db69220Sschwarze 		if (globres == 0)
7987db69220Sschwarze 			goto found;
7997db69220Sschwarze 		globfree(&globinfo);
800a231508dSschwarze 	}
8017db69220Sschwarze 
802ad164ce6Sschwarze 	if (res != NULL || ipath + 1 != paths->sz)
803ecd1ed85Sschwarze 		return -1;
804c5921aadSschwarze 
805ad164ce6Sschwarze 	mandoc_asprintf(&file, "%s.%s", name, sec);
806a231508dSschwarze 	globres = stat(file, &sb);
807ad164ce6Sschwarze 	free(file);
808ecd1ed85Sschwarze 	return globres;
809ad164ce6Sschwarze 
810c5921aadSschwarze found:
811ecf32ec4Sschwarze 	warnx("outdated mandoc.db lacks %s(%s) entry, run %s %s",
812ecf32ec4Sschwarze 	    name, sec, BINM_MAKEWHATIS, paths->paths[ipath]);
8137db69220Sschwarze 	if (res == NULL)
814ad164ce6Sschwarze 		free(file);
8157db69220Sschwarze 	else if (file == NULL)
8167db69220Sschwarze 		fs_append(globinfo.gl_pathv, globinfo.gl_pathc, 1,
8177db69220Sschwarze 		    ipath, sec, form, res, ressz);
8187db69220Sschwarze 	else
8197db69220Sschwarze 		fs_append(&file, 1, 0, ipath, sec, form, res, ressz);
8207db69220Sschwarze 	globfree(&globinfo);
821ecd1ed85Sschwarze 	return 0;
822c5921aadSschwarze }
823c5921aadSschwarze 
82410e17f8fSschwarze static int
fs_search(const struct mansearch * cfg,const struct manpaths * paths,const char * name,struct manpage ** res,size_t * ressz)825c5921aadSschwarze fs_search(const struct mansearch *cfg, const struct manpaths *paths,
826bc6b7f6fSschwarze 	const char *name, struct manpage **res, size_t *ressz)
827c5921aadSschwarze {
828c5921aadSschwarze 	const char *const sections[] =
829598b66e2Sschwarze 	    {"1", "8", "6", "2", "3", "5", "7", "4", "9", "3p"};
830c5921aadSschwarze 	const size_t nsec = sizeof(sections)/sizeof(sections[0]);
831c5921aadSschwarze 
832bc6b7f6fSschwarze 	size_t		 ipath, isec;
833c5921aadSschwarze 
834c5921aadSschwarze 	assert(cfg->argmode == ARG_NAME);
83510e17f8fSschwarze 	if (res != NULL)
836c5921aadSschwarze 		*res = NULL;
837bc6b7f6fSschwarze 	*ressz = 0;
838c5921aadSschwarze 	for (ipath = 0; ipath < paths->sz; ipath++) {
839c5921aadSschwarze 		if (cfg->sec != NULL) {
840bc6b7f6fSschwarze 			if (fs_lookup(paths, ipath, cfg->sec, cfg->arch,
841bc6b7f6fSschwarze 			    name, res, ressz) != -1 && cfg->firstmatch)
842ecd1ed85Sschwarze 				return 0;
843bc6b7f6fSschwarze 		} else {
844bc6b7f6fSschwarze 			for (isec = 0; isec < nsec; isec++)
845c5921aadSschwarze 				if (fs_lookup(paths, ipath, sections[isec],
846bc6b7f6fSschwarze 				    cfg->arch, name, res, ressz) != -1 &&
847c5921aadSschwarze 				    cfg->firstmatch)
848ecd1ed85Sschwarze 					return 0;
849c5921aadSschwarze 		}
850c5921aadSschwarze 	}
851ecd1ed85Sschwarze 	return -1;
852c5921aadSschwarze }
853c5921aadSschwarze 
8549f39971fSschwarze static void
process_onefile(struct mparse * mp,struct manpage * resp,int startdir,struct outstate * outst,struct manconf * conf)85573f693efSschwarze process_onefile(struct mparse *mp, struct manpage *resp, int startdir,
85673f693efSschwarze     struct outstate *outst, struct manconf *conf)
85773f693efSschwarze {
85873f693efSschwarze 	int	 fd;
85973f693efSschwarze 
86073f693efSschwarze 	/*
86173f693efSschwarze 	 * Changing directories is not needed in ARG_FILE mode.
86273f693efSschwarze 	 * Do it on a best-effort basis.  Even in case of
86373f693efSschwarze 	 * failure, some functionality may still work.
86473f693efSschwarze 	 */
86573f693efSschwarze 	if (resp->ipath != SIZE_MAX)
86673f693efSschwarze 		(void)chdir(conf->manpath.paths[resp->ipath]);
86773f693efSschwarze 	else if (startdir != -1)
86873f693efSschwarze 		(void)fchdir(startdir);
86973f693efSschwarze 
87073f693efSschwarze 	mandoc_msg_setinfilename(resp->file);
87173f693efSschwarze 	if (resp->file != NULL) {
87273f693efSschwarze 		if ((fd = mparse_open(mp, resp->file)) == -1) {
87373f693efSschwarze 			mandoc_msg(resp->ipath == SIZE_MAX ?
87473f693efSschwarze 			    MANDOCERR_BADARG_BAD : MANDOCERR_OPEN,
87573f693efSschwarze 			    0, 0, "%s", strerror(errno));
87673f693efSschwarze 			mandoc_msg_setinfilename(NULL);
87773f693efSschwarze 			return;
87873f693efSschwarze 		}
87973f693efSschwarze 	} else
88073f693efSschwarze 		fd = STDIN_FILENO;
88173f693efSschwarze 
88273f693efSschwarze 	if (outst->use_pager) {
88373f693efSschwarze 		outst->use_pager = 0;
88490f584c6Sschwarze 		outst->tag_files = term_tag_init(conf->output.outfilename,
885695084adSkn 		    outst->outtype == OUTT_HTML ? ".html" : "",
88690f584c6Sschwarze 		    conf->output.tagfilename);
88790f584c6Sschwarze 		if ((conf->output.outfilename != NULL ||
88890f584c6Sschwarze 		     conf->output.tagfilename != NULL) &&
88990f584c6Sschwarze 		    pledge("stdio rpath cpath", NULL) == -1) {
89090f584c6Sschwarze 			mandoc_msg(MANDOCERR_PLEDGE, 0, 0,
89190f584c6Sschwarze 			    "%s", strerror(errno));
89290f584c6Sschwarze 			exit(mandoc_msg_getrc());
89390f584c6Sschwarze 		}
89473f693efSschwarze 	}
8954826a235Sschwarze 	if (outst->had_output && outst->outtype <= OUTT_UTF8) {
89673f693efSschwarze 		if (outst->outdata == NULL)
89773f693efSschwarze 			outdata_alloc(outst, &conf->output);
89873f693efSschwarze 		terminal_sepline(outst->outdata);
89973f693efSschwarze 	}
90073f693efSschwarze 
90173f693efSschwarze 	if (resp->form == FORM_SRC)
90228ca454fSschwarze 		parse(mp, fd, resp->file, outst, conf);
90373f693efSschwarze 	else {
90473f693efSschwarze 		passthrough(fd, conf->output.synopsisonly);
90573f693efSschwarze 		outst->had_output = 1;
90673f693efSschwarze 	}
90773f693efSschwarze 
90873f693efSschwarze 	if (ferror(stdout)) {
90973f693efSschwarze 		if (outst->tag_files != NULL) {
91073f693efSschwarze 			mandoc_msg(MANDOCERR_WRITE, 0, 0, "%s: %s",
91173f693efSschwarze 			    outst->tag_files->ofn, strerror(errno));
9120ac7e6ecSschwarze 			term_tag_unlink();
91373f693efSschwarze 			outst->tag_files = NULL;
91473f693efSschwarze 		} else
91573f693efSschwarze 			mandoc_msg(MANDOCERR_WRITE, 0, 0, "%s",
91673f693efSschwarze 			    strerror(errno));
91773f693efSschwarze 	}
91873f693efSschwarze 	mandoc_msg_setinfilename(NULL);
91973f693efSschwarze }
92073f693efSschwarze 
92173f693efSschwarze static void
parse(struct mparse * mp,int fd,const char * file,struct outstate * outst,struct manconf * conf)9223f735e33Sschwarze parse(struct mparse *mp, int fd, const char *file,
92328ca454fSschwarze     struct outstate *outst, struct manconf *conf)
924f73abda9Skristaps {
92528ca454fSschwarze 	static struct manpaths	 basepaths;
92673f693efSschwarze 	static int		 previous;
9276b86842eSschwarze 	struct roff_meta	*meta;
928f73abda9Skristaps 
9298dec91daSflorian 	assert(fd >= 0);
93073f693efSschwarze 	if (file == NULL)
93173f693efSschwarze 		file = "<stdin>";
93273f693efSschwarze 
93373f693efSschwarze 	if (previous)
93473f693efSschwarze 		mparse_reset(mp);
93573f693efSschwarze 	else
93673f693efSschwarze 		previous = 1;
937e7b389f9Sschwarze 
938d8e30e1dSschwarze 	mparse_readfd(mp, fd, file);
9397a6e7816Sschwarze 	if (fd != STDIN_FILENO)
9407a6e7816Sschwarze 		close(fd);
941f73abda9Skristaps 
94287eef1c7Sschwarze 	/*
943a35fc07aSschwarze 	 * With -Wstop and warnings or errors of at least the requested
944a35fc07aSschwarze 	 * level, do not produce output.
94587eef1c7Sschwarze 	 */
94687eef1c7Sschwarze 
9473f735e33Sschwarze 	if (outst->wstop && mandoc_msg_getrc() != MANDOCLEVEL_OK)
94824ec91ddSschwarze 		return;
94987eef1c7Sschwarze 
9503f735e33Sschwarze 	if (outst->outdata == NULL)
95128ca454fSschwarze 		outdata_alloc(outst, &conf->output);
9523f735e33Sschwarze 	else if (outst->outtype == OUTT_HTML)
953e1292dcdSotto 		html_reset(outst->outdata);
954f73abda9Skristaps 
9556b86842eSschwarze 	mandoc_xr_reset();
956d8e30e1dSschwarze 	meta = mparse_result(mp);
957a35fc07aSschwarze 
958f73abda9Skristaps 	/* Execute the out device, if it exists. */
959f73abda9Skristaps 
96073f693efSschwarze 	outst->had_output = 1;
9616b86842eSschwarze 	if (meta->macroset == MACROSET_MDOC) {
9623f735e33Sschwarze 		switch (outst->outtype) {
963b68ff58bSschwarze 		case OUTT_HTML:
9643f735e33Sschwarze 			html_mdoc(outst->outdata, meta);
965b68ff58bSschwarze 			break;
966b68ff58bSschwarze 		case OUTT_TREE:
9673f735e33Sschwarze 			tree_mdoc(outst->outdata, meta);
968b68ff58bSschwarze 			break;
969b68ff58bSschwarze 		case OUTT_MAN:
9703f735e33Sschwarze 			man_mdoc(outst->outdata, meta);
971b68ff58bSschwarze 			break;
972b68ff58bSschwarze 		case OUTT_PDF:
973b68ff58bSschwarze 		case OUTT_ASCII:
974b68ff58bSschwarze 		case OUTT_UTF8:
975b68ff58bSschwarze 		case OUTT_LOCALE:
976b68ff58bSschwarze 		case OUTT_PS:
9773f735e33Sschwarze 			terminal_mdoc(outst->outdata, meta);
978b68ff58bSschwarze 			break;
979b3257404Sschwarze 		case OUTT_MARKDOWN:
9803f735e33Sschwarze 			markdown_mdoc(outst->outdata, meta);
981b3257404Sschwarze 			break;
982b68ff58bSschwarze 		default:
983b68ff58bSschwarze 			break;
984b68ff58bSschwarze 		}
985b68ff58bSschwarze 	}
9866b86842eSschwarze 	if (meta->macroset == MACROSET_MAN) {
9873f735e33Sschwarze 		switch (outst->outtype) {
988b68ff58bSschwarze 		case OUTT_HTML:
9893f735e33Sschwarze 			html_man(outst->outdata, meta);
990b68ff58bSschwarze 			break;
991b68ff58bSschwarze 		case OUTT_TREE:
9923f735e33Sschwarze 			tree_man(outst->outdata, meta);
993b68ff58bSschwarze 			break;
994b68ff58bSschwarze 		case OUTT_MAN:
995d8e30e1dSschwarze 			mparse_copy(mp);
996b68ff58bSschwarze 			break;
997b68ff58bSschwarze 		case OUTT_PDF:
998b68ff58bSschwarze 		case OUTT_ASCII:
999b68ff58bSschwarze 		case OUTT_UTF8:
1000b68ff58bSschwarze 		case OUTT_LOCALE:
1001b68ff58bSschwarze 		case OUTT_PS:
10023f735e33Sschwarze 			terminal_man(outst->outdata, meta);
1003b68ff58bSschwarze 			break;
1004a28ef4e8Sschwarze 		case OUTT_MARKDOWN:
1005a28ef4e8Sschwarze 			mandoc_msg(MANDOCERR_MAN_TMARKDOWN, 0, 0, NULL);
1006a28ef4e8Sschwarze 			break;
1007b68ff58bSschwarze 		default:
1008b68ff58bSschwarze 			break;
1009b68ff58bSschwarze 		}
1010b68ff58bSschwarze 	}
101128ca454fSschwarze 	if (conf->output.tag != NULL && conf->output.tag_found == 0 &&
101228ca454fSschwarze 	    tag_exists(conf->output.tag))
101328ca454fSschwarze 		conf->output.tag_found = 1;
101428ca454fSschwarze 
101528ca454fSschwarze 	if (mandoc_msg_getmin() < MANDOCERR_STYLE) {
101628ca454fSschwarze 		if (basepaths.sz == 0)
101728ca454fSschwarze 			manpath_base(&basepaths);
101828ca454fSschwarze 		check_xr(&basepaths);
101928ca454fSschwarze 	} else if (mandoc_msg_getmin() < MANDOCERR_WARNING)
102028ca454fSschwarze 		check_xr(&conf->manpath);
1021f73abda9Skristaps }
1022f73abda9Skristaps 
102324ec91ddSschwarze static void
check_xr(struct manpaths * paths)102428ca454fSschwarze check_xr(struct manpaths *paths)
102519b6bef7Sschwarze {
102619b6bef7Sschwarze 	struct mansearch	 search;
102719b6bef7Sschwarze 	struct mandoc_xr	*xr;
102819b6bef7Sschwarze 	size_t			 sz;
102919b6bef7Sschwarze 
103019b6bef7Sschwarze 	for (xr = mandoc_xr_get(); xr != NULL; xr = xr->next) {
103152d11c96Sschwarze 		if (xr->line == -1)
103252d11c96Sschwarze 			continue;
103319b6bef7Sschwarze 		search.arch = NULL;
103419b6bef7Sschwarze 		search.sec = xr->sec;
103519b6bef7Sschwarze 		search.outkey = NULL;
103619b6bef7Sschwarze 		search.argmode = ARG_NAME;
103719b6bef7Sschwarze 		search.firstmatch = 1;
103828ca454fSschwarze 		if (mansearch(&search, paths, 1, &xr->name, NULL, &sz))
103919b6bef7Sschwarze 			continue;
104028ca454fSschwarze 		if (fs_search(&search, paths, xr->name, NULL, &sz) != -1)
104110e17f8fSschwarze 			continue;
1042bff2a0c3Sschwarze 		if (xr->count == 1)
1043a5a5f808Sschwarze 			mandoc_msg(MANDOCERR_XR_BAD, xr->line,
1044a5a5f808Sschwarze 			    xr->pos + 1, "Xr %s %s", xr->name, xr->sec);
1045bff2a0c3Sschwarze 		else
1046a5a5f808Sschwarze 			mandoc_msg(MANDOCERR_XR_BAD, xr->line,
1047a5a5f808Sschwarze 			    xr->pos + 1, "Xr %s %s (%d times)",
1048bff2a0c3Sschwarze 			    xr->name, xr->sec, xr->count);
104919b6bef7Sschwarze 	}
105019b6bef7Sschwarze }
105119b6bef7Sschwarze 
105219b6bef7Sschwarze static void
outdata_alloc(struct outstate * outst,struct manoutput * outconf)10533f735e33Sschwarze outdata_alloc(struct outstate *outst, struct manoutput *outconf)
1054fa64596dSschwarze {
10553f735e33Sschwarze 	switch (outst->outtype) {
1056fa64596dSschwarze 	case OUTT_HTML:
10573f735e33Sschwarze 		outst->outdata = html_alloc(outconf);
1058fa64596dSschwarze 		break;
1059fa64596dSschwarze 	case OUTT_UTF8:
10603f735e33Sschwarze 		outst->outdata = utf8_alloc(outconf);
1061fa64596dSschwarze 		break;
1062fa64596dSschwarze 	case OUTT_LOCALE:
10633f735e33Sschwarze 		outst->outdata = locale_alloc(outconf);
1064fa64596dSschwarze 		break;
1065fa64596dSschwarze 	case OUTT_ASCII:
10663f735e33Sschwarze 		outst->outdata = ascii_alloc(outconf);
1067fa64596dSschwarze 		break;
1068fa64596dSschwarze 	case OUTT_PDF:
10693f735e33Sschwarze 		outst->outdata = pdf_alloc(outconf);
1070fa64596dSschwarze 		break;
1071fa64596dSschwarze 	case OUTT_PS:
10723f735e33Sschwarze 		outst->outdata = ps_alloc(outconf);
1073fa64596dSschwarze 		break;
1074fa64596dSschwarze 	default:
1075fa64596dSschwarze 		break;
1076fa64596dSschwarze 	}
1077fa64596dSschwarze }
1078fa64596dSschwarze 
1079fa64596dSschwarze static void
passthrough(int fd,int synopsis_only)1080ecd1ed85Sschwarze passthrough(int fd, int synopsis_only)
10810f10154cSschwarze {
10820ac900dfSschwarze 	const char	 synb[] = "S\bSY\bYN\bNO\bOP\bPS\bSI\bIS\bS";
10830ac900dfSschwarze 	const char	 synr[] = "SYNOPSIS";
10840f10154cSschwarze 
10850ac900dfSschwarze 	FILE		*stream;
108631f93c25Sschwarze 	char		*line, *cp;
108731f93c25Sschwarze 	size_t		 linesz;
108834b4fe63Sschwarze 	ssize_t		 len, written;
1089ecd1ed85Sschwarze 	int		 lno, print;
10900ac900dfSschwarze 
1091ecd1ed85Sschwarze 	stream = NULL;
109231f93c25Sschwarze 	line = NULL;
109331f93c25Sschwarze 	linesz = 0;
10942b036407Sschwarze 
109534b4fe63Sschwarze 	if (fflush(stdout) == EOF) {
1096ecd1ed85Sschwarze 		mandoc_msg(MANDOCERR_FFLUSH, 0, 0, "%s", strerror(errno));
1097ecd1ed85Sschwarze 		goto done;
109834b4fe63Sschwarze 	}
10990ac900dfSschwarze 	if ((stream = fdopen(fd, "r")) == NULL) {
1100a53fa1e1Sschwarze 		close(fd);
1101ecd1ed85Sschwarze 		mandoc_msg(MANDOCERR_FDOPEN, 0, 0, "%s", strerror(errno));
1102ecd1ed85Sschwarze 		goto done;
11030f10154cSschwarze 	}
11040f10154cSschwarze 
1105ecd1ed85Sschwarze 	lno = print = 0;
110634b4fe63Sschwarze 	while ((len = getline(&line, &linesz, stream)) != -1) {
1107ecd1ed85Sschwarze 		lno++;
110831f93c25Sschwarze 		cp = line;
11090ac900dfSschwarze 		if (synopsis_only) {
11100ac900dfSschwarze 			if (print) {
111131f93c25Sschwarze 				if ( ! isspace((unsigned char)*cp))
11120ac900dfSschwarze 					goto done;
111334b4fe63Sschwarze 				while (isspace((unsigned char)*cp)) {
111431f93c25Sschwarze 					cp++;
111534b4fe63Sschwarze 					len--;
111634b4fe63Sschwarze 				}
11170ac900dfSschwarze 			} else {
111831f93c25Sschwarze 				if (strcmp(cp, synb) == 0 ||
111931f93c25Sschwarze 				    strcmp(cp, synr) == 0)
11200ac900dfSschwarze 					print = 1;
11210ac900dfSschwarze 				continue;
11220ac900dfSschwarze 			}
11230ac900dfSschwarze 		}
112434b4fe63Sschwarze 		for (; len > 0; len -= written) {
1125ecd1ed85Sschwarze 			if ((written = write(STDOUT_FILENO, cp, len)) == -1) {
1126ecd1ed85Sschwarze 				mandoc_msg(MANDOCERR_WRITE, 0, 0,
1127ecd1ed85Sschwarze 				    "%s", strerror(errno));
1128ecd1ed85Sschwarze 				goto done;
11290ac900dfSschwarze 			}
11300ac900dfSschwarze 		}
11310ac900dfSschwarze 	}
1132ecd1ed85Sschwarze 	if (ferror(stream))
1133ecd1ed85Sschwarze 		mandoc_msg(MANDOCERR_GETLINE, lno, 0, "%s", strerror(errno));
11340ac900dfSschwarze 
11350ac900dfSschwarze done:
113631f93c25Sschwarze 	free(line);
1137ecd1ed85Sschwarze 	if (stream != NULL)
11380ac900dfSschwarze 		fclose(stream);
1139f73abda9Skristaps }
1140f73abda9Skristaps 
1141f73abda9Skristaps static int
woptions(char * arg,enum mandoc_os * os_e,int * wstop)1142d8e30e1dSschwarze woptions(char *arg, enum mandoc_os *os_e, int *wstop)
1143f73abda9Skristaps {
1144e6b67f92Sschwarze 	char		*v, *o;
1145f3476b07Sschwarze 	const char	*toks[11];
1146f73abda9Skristaps 
1147cfd2bfaaSschwarze 	toks[0] = "stop";
1148cfd2bfaaSschwarze 	toks[1] = "all";
1149f3476b07Sschwarze 	toks[2] = "base";
1150f3476b07Sschwarze 	toks[3] = "style";
1151f3476b07Sschwarze 	toks[4] = "warning";
1152f3476b07Sschwarze 	toks[5] = "error";
1153f3476b07Sschwarze 	toks[6] = "unsupp";
1154f3476b07Sschwarze 	toks[7] = "fatal";
1155f3476b07Sschwarze 	toks[8] = "openbsd";
1156f3476b07Sschwarze 	toks[9] = "netbsd";
1157f3476b07Sschwarze 	toks[10] = NULL;
1158f73abda9Skristaps 
1159e6b67f92Sschwarze 	while (*arg) {
1160e6b67f92Sschwarze 		o = arg;
11615f1e3782Sschwarze 		switch (getsubopt(&arg, (char * const *)toks, &v)) {
116249aff9f8Sschwarze 		case 0:
1163d8e30e1dSschwarze 			*wstop = 1;
1164f73abda9Skristaps 			break;
116549aff9f8Sschwarze 		case 1:
116649aff9f8Sschwarze 		case 2:
1167e501e731Sschwarze 			mandoc_msg_setmin(MANDOCERR_BASE);
1168f73abda9Skristaps 			break;
116949aff9f8Sschwarze 		case 3:
1170e501e731Sschwarze 			mandoc_msg_setmin(MANDOCERR_STYLE);
1171888b3a74Sschwarze 			break;
117249aff9f8Sschwarze 		case 4:
1173e501e731Sschwarze 			mandoc_msg_setmin(MANDOCERR_WARNING);
1174d04ca39fSschwarze 			break;
1175d04ca39fSschwarze 		case 5:
1176e501e731Sschwarze 			mandoc_msg_setmin(MANDOCERR_ERROR);
11770077f574Sschwarze 			break;
11780077f574Sschwarze 		case 6:
1179e501e731Sschwarze 			mandoc_msg_setmin(MANDOCERR_UNSUPP);
1180f3476b07Sschwarze 			break;
1181f3476b07Sschwarze 		case 7:
1182ecd1ed85Sschwarze 			mandoc_msg_setmin(MANDOCERR_BADARG);
1183f3476b07Sschwarze 			break;
1184f3476b07Sschwarze 		case 8:
1185e501e731Sschwarze 			mandoc_msg_setmin(MANDOCERR_BASE);
1186d8e30e1dSschwarze 			*os_e = MANDOC_OS_OPENBSD;
1187f3476b07Sschwarze 			break;
1188f3476b07Sschwarze 		case 9:
1189e501e731Sschwarze 			mandoc_msg_setmin(MANDOCERR_BASE);
1190d8e30e1dSschwarze 			*os_e = MANDOC_OS_NETBSD;
1191a66b65d0Sschwarze 			break;
1192f73abda9Skristaps 		default:
1193ecd1ed85Sschwarze 			mandoc_msg(MANDOCERR_BADARG_BAD, 0, 0, "-W %s", o);
1194ecd1ed85Sschwarze 			return -1;
1195ecd1ed85Sschwarze 		}
1196ecd1ed85Sschwarze 	}
1197526e306bSschwarze 	return 0;
1198f73abda9Skristaps }
1199f73abda9Skristaps 
12003cd418b7Sschwarze /*
12013cd418b7Sschwarze  * Wait until moved to the foreground,
12023cd418b7Sschwarze  * then fork the pager and wait for the user to close it.
12033cd418b7Sschwarze  */
12043cd418b7Sschwarze static void
run_pager(struct outstate * outst,char * tag_target)1205cdecd8e7Sschwarze run_pager(struct outstate *outst, char *tag_target)
12063cd418b7Sschwarze {
12073cd418b7Sschwarze 	int	 signum, status;
12083cd418b7Sschwarze 	pid_t	 man_pgid, tc_pgid;
12093cd418b7Sschwarze 	pid_t	 pager_pid, wait_pid;
12103cd418b7Sschwarze 
12113cd418b7Sschwarze 	man_pgid = getpgid(0);
1212cdecd8e7Sschwarze 	outst->tag_files->tcpgid =
1213cdecd8e7Sschwarze 	    man_pgid == getpid() ? getpgid(getppid()) : man_pgid;
12143cd418b7Sschwarze 	pager_pid = 0;
12153cd418b7Sschwarze 	signum = SIGSTOP;
12163cd418b7Sschwarze 
12173cd418b7Sschwarze 	for (;;) {
12183cd418b7Sschwarze 		/* Stop here until moved to the foreground. */
12193cd418b7Sschwarze 
1220beabc24cSschwarze 		tc_pgid = tcgetpgrp(STDOUT_FILENO);
12213cd418b7Sschwarze 		if (tc_pgid != man_pgid) {
12223cd418b7Sschwarze 			if (tc_pgid == pager_pid) {
1223beabc24cSschwarze 				(void)tcsetpgrp(STDOUT_FILENO, man_pgid);
12243cd418b7Sschwarze 				if (signum == SIGTTIN)
12253cd418b7Sschwarze 					continue;
12263cd418b7Sschwarze 			} else
1227cdecd8e7Sschwarze 				outst->tag_files->tcpgid = tc_pgid;
12283cd418b7Sschwarze 			kill(0, signum);
12293cd418b7Sschwarze 			continue;
12303cd418b7Sschwarze 		}
12313cd418b7Sschwarze 
12323cd418b7Sschwarze 		/* Once in the foreground, activate the pager. */
12333cd418b7Sschwarze 
12343cd418b7Sschwarze 		if (pager_pid) {
1235beabc24cSschwarze 			(void)tcsetpgrp(STDOUT_FILENO, pager_pid);
12363cd418b7Sschwarze 			kill(pager_pid, SIGCONT);
12373cd418b7Sschwarze 		} else
1238cdecd8e7Sschwarze 			pager_pid = spawn_pager(outst, tag_target);
12393cd418b7Sschwarze 
12403cd418b7Sschwarze 		/* Wait for the pager to stop or exit. */
12413cd418b7Sschwarze 
12423cd418b7Sschwarze 		while ((wait_pid = waitpid(pager_pid, &status,
12433cd418b7Sschwarze 		    WUNTRACED)) == -1 && errno == EINTR)
12443cd418b7Sschwarze 			continue;
12453cd418b7Sschwarze 
12463cd418b7Sschwarze 		if (wait_pid == -1) {
12473cd418b7Sschwarze 			mandoc_msg(MANDOCERR_WAIT, 0, 0,
12483cd418b7Sschwarze 			    "%s", strerror(errno));
12493cd418b7Sschwarze 			break;
12503cd418b7Sschwarze 		}
12513cd418b7Sschwarze 		if (!WIFSTOPPED(status))
12523cd418b7Sschwarze 			break;
12533cd418b7Sschwarze 
12543cd418b7Sschwarze 		signum = WSTOPSIG(status);
12553cd418b7Sschwarze 	}
12563cd418b7Sschwarze }
12573cd418b7Sschwarze 
1258cad82a00Sschwarze static pid_t
spawn_pager(struct outstate * outst,char * tag_target)1259cdecd8e7Sschwarze spawn_pager(struct outstate *outst, char *tag_target)
12600f10154cSschwarze {
1261d82a663dSschwarze 	const struct timespec timeout = { 0, 100000000 };  /* 0.1s */
12620f10154cSschwarze #define MAX_PAGER_ARGS 16
12630f10154cSschwarze 	char		*argv[MAX_PAGER_ARGS];
12640f10154cSschwarze 	const char	*pager;
12650f10154cSschwarze 	char		*cp;
12660ed293fbSschwarze 	size_t		 wordlen;
1267c0a657b3Sschwarze 	size_t		 cmdlen;
1268d17f6067Sschwarze 	int		 argc, use_ofn;
1269cad82a00Sschwarze 	pid_t		 pager_pid;
12700f10154cSschwarze 
1271cdecd8e7Sschwarze 	assert(outst->tag_files->ofd == -1);
1272cdecd8e7Sschwarze 	assert(outst->tag_files->tfs == NULL);
1273beabc24cSschwarze 
1274c0a657b3Sschwarze 	pager = getenv("MANPAGER");
1275c0a657b3Sschwarze 	if (pager == NULL || *pager == '\0')
1276c0a657b3Sschwarze 		pager = getenv("PAGER");
1277c0a657b3Sschwarze 	if (pager == NULL || *pager == '\0')
1278c9771691Sschwarze 		pager = "less";
1279c0a657b3Sschwarze 
1280c0a657b3Sschwarze 	/*
1281c0a657b3Sschwarze 	 * Parse the pager command into words.
1282c0a657b3Sschwarze 	 * Intentionally do not do anything fancy here.
1283c0a657b3Sschwarze 	 */
1284c0a657b3Sschwarze 
1285c0a657b3Sschwarze 	argc = 0;
12860ed293fbSschwarze 	while (*pager != '\0' && argc + 5 < MAX_PAGER_ARGS) {
12870ed293fbSschwarze 		wordlen = strcspn(pager, " ");
12880ed293fbSschwarze 		argv[argc++] = mandoc_strndup(pager, wordlen);
12890ed293fbSschwarze 		pager += wordlen;
12900ed293fbSschwarze 		while (*pager == ' ')
12910ed293fbSschwarze 			pager++;
1292c0a657b3Sschwarze 	}
1293c0a657b3Sschwarze 
12942ae7e873Sschwarze 	/* For more(1) and less(1), use the tag file. */
1295c0a657b3Sschwarze 
1296d17f6067Sschwarze 	use_ofn = 1;
1297cdecd8e7Sschwarze 	if (*outst->tag_files->tfn != '\0' &&
1298cdecd8e7Sschwarze 	    (cmdlen = strlen(argv[0])) >= 4) {
1299c0a657b3Sschwarze 		cp = argv[0] + cmdlen - 4;
13002ae7e873Sschwarze 		if (strcmp(cp, "less") == 0 || strcmp(cp, "more") == 0) {
1301c0a657b3Sschwarze 			argv[argc++] = mandoc_strdup("-T");
13020ed293fbSschwarze 			argv[argc++] = mandoc_strdup(outst->tag_files->tfn);
1303beabc24cSschwarze 			if (tag_target != NULL) {
1304d17f6067Sschwarze 				argv[argc++] = mandoc_strdup("-t");
13050ed293fbSschwarze 				argv[argc++] = mandoc_strdup(tag_target);
1306d17f6067Sschwarze 				use_ofn = 0;
1307c0a657b3Sschwarze 			}
13082ae7e873Sschwarze 		}
1309d17f6067Sschwarze 	}
1310cdecd8e7Sschwarze 	if (use_ofn) {
1311cdecd8e7Sschwarze 		if (outst->outtype == OUTT_HTML && tag_target != NULL)
1312cdecd8e7Sschwarze 			mandoc_asprintf(&argv[argc], "file://%s#%s",
1313cdecd8e7Sschwarze 			    outst->tag_files->ofn, tag_target);
1314cdecd8e7Sschwarze 		else
13150ed293fbSschwarze 			argv[argc] = mandoc_strdup(outst->tag_files->ofn);
1316cdecd8e7Sschwarze 		argc++;
1317cdecd8e7Sschwarze 	}
1318c0a657b3Sschwarze 	argv[argc] = NULL;
1319c0a657b3Sschwarze 
1320cad82a00Sschwarze 	switch (pager_pid = fork()) {
13210f10154cSschwarze 	case -1:
1322ecd1ed85Sschwarze 		mandoc_msg(MANDOCERR_FORK, 0, 0, "%s", strerror(errno));
1323ecd1ed85Sschwarze 		exit(mandoc_msg_getrc());
13240f10154cSschwarze 	case 0:
1325bb1dcb77Sschwarze 		break;
1326bb1dcb77Sschwarze 	default:
13270ed293fbSschwarze 		while (argc > 0)
13280ed293fbSschwarze 			free(argv[--argc]);
1329d5c4dcfeSschwarze 		(void)setpgid(pager_pid, 0);
1330beabc24cSschwarze 		(void)tcsetpgrp(STDOUT_FILENO, pager_pid);
1331ecd1ed85Sschwarze 		if (pledge("stdio rpath tmppath tty proc", NULL) == -1) {
1332ecd1ed85Sschwarze 			mandoc_msg(MANDOCERR_PLEDGE, 0, 0,
1333ecd1ed85Sschwarze 			    "%s", strerror(errno));
1334ecd1ed85Sschwarze 			exit(mandoc_msg_getrc());
1335ecd1ed85Sschwarze 		}
1336cdecd8e7Sschwarze 		outst->tag_files->pager_pid = pager_pid;
1337526e306bSschwarze 		return pager_pid;
13380f10154cSschwarze 	}
13390f10154cSschwarze 
1340beabc24cSschwarze 	/*
1341beabc24cSschwarze 	 * The child process becomes the pager.
1342beabc24cSschwarze 	 * Do not start it before controlling the terminal.
1343beabc24cSschwarze 	 */
1344d82a663dSschwarze 
1345bbdc5336Sschwarze 	while (tcgetpgrp(STDOUT_FILENO) != getpid())
1346d82a663dSschwarze 		nanosleep(&timeout, NULL);
1347d82a663dSschwarze 
13480f10154cSschwarze 	execvp(argv[0], argv);
1349ecd1ed85Sschwarze 	mandoc_msg(MANDOCERR_EXEC, 0, 0, "%s: %s", argv[0], strerror(errno));
1350ecd1ed85Sschwarze 	_exit(mandoc_msg_getrc());
13510f10154cSschwarze }
1352