Home | History | Annotate | Line # | Download | only in dist
      1 /* Id: main.c,v 1.358 2021/09/04 22:38:46 schwarze Exp  */
      2 /*
      3  * Copyright (c) 2010-2012, 2014-2021 Ingo Schwarze <schwarze (at) openbsd.org>
      4  * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps (at) bsd.lv>
      5  * Copyright (c) 2010 Joerg Sonnenberger <joerg (at) netbsd.org>
      6  *
      7  * Permission to use, copy, modify, and distribute this software for any
      8  * purpose with or without fee is hereby granted, provided that the above
      9  * copyright notice and this permission notice appear in all copies.
     10  *
     11  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
     12  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     13  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
     14  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     15  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     16  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     17  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     18  *
     19  * Main program for mandoc(1), man(1), apropos(1), whatis(1), and help(1).
     20  */
     21 #include "config.h"
     22 
     23 #include <sys/types.h>
     24 #include <sys/ioctl.h>
     25 #include <sys/param.h>	/* MACHINE */
     26 #include <sys/stat.h>
     27 #include <sys/wait.h>
     28 
     29 #include <assert.h>
     30 #include <ctype.h>
     31 #if HAVE_ERR
     32 #include <err.h>
     33 #endif
     34 #include <errno.h>
     35 #include <fcntl.h>
     36 #include <glob.h>
     37 #include <limits.h>
     38 #if HAVE_SANDBOX_INIT
     39 #include <sandbox.h>
     40 #endif
     41 #include <signal.h>
     42 #include <stdio.h>
     43 #include <stdint.h>
     44 #include <stdlib.h>
     45 #include <string.h>
     46 #include <termios.h>
     47 #include <time.h>
     48 #include <unistd.h>
     49 
     50 #include "mandoc_aux.h"
     51 #include "mandoc.h"
     52 #include "mandoc_xr.h"
     53 #include "roff.h"
     54 #include "mdoc.h"
     55 #include "man.h"
     56 #include "mandoc_parse.h"
     57 #include "tag.h"
     58 #include "term_tag.h"
     59 #include "main.h"
     60 #include "manconf.h"
     61 #include "mansearch.h"
     62 
     63 enum	outmode {
     64 	OUTMODE_DEF = 0,
     65 	OUTMODE_FLN,
     66 	OUTMODE_LST,
     67 	OUTMODE_ALL,
     68 	OUTMODE_ONE
     69 };
     70 
     71 enum	outt {
     72 	OUTT_ASCII = 0,	/* -Tascii */
     73 	OUTT_LOCALE,	/* -Tlocale */
     74 	OUTT_UTF8,	/* -Tutf8 */
     75 	OUTT_TREE,	/* -Ttree */
     76 	OUTT_MAN,	/* -Tman */
     77 	OUTT_HTML,	/* -Thtml */
     78 	OUTT_MARKDOWN,	/* -Tmarkdown */
     79 	OUTT_LINT,	/* -Tlint */
     80 	OUTT_PS,	/* -Tps */
     81 	OUTT_PDF	/* -Tpdf */
     82 };
     83 
     84 struct	outstate {
     85 	struct tag_files *tag_files;	/* Tagging state variables. */
     86 	void		 *outdata;	/* data for output */
     87 	int		  use_pager;
     88 	int		  wstop;	/* stop after a file with a warning */
     89 	int		  had_output;	/* Some output was generated. */
     90 	enum outt	  outtype;	/* which output to use */
     91 };
     92 
     93 
     94 int			  mandocdb(int, char *[]);
     95 
     96 static	void		  check_xr(struct manpaths *);
     97 static	void		  fs_append(char **, size_t, int,
     98 				size_t, const char *, enum form,
     99 				struct manpage **, size_t *);
    100 static	int		  fs_lookup(const struct manpaths *, size_t,
    101 				const char *, const char *, const char *,
    102 				struct manpage **, size_t *);
    103 static	int		  fs_search(const struct mansearch *,
    104 				const struct manpaths *, const char *,
    105 				struct manpage **, size_t *);
    106 static	void		  glob_esc(char **, const char *, const char *);
    107 static	void		  outdata_alloc(struct outstate *, struct manoutput *);
    108 static	void		  parse(struct mparse *, int, const char *,
    109 				struct outstate *, struct manconf *);
    110 static	void		  passthrough(int, int);
    111 static	void		  process_onefile(struct mparse *, struct manpage *,
    112 				int, struct outstate *, struct manconf *);
    113 static	void		  run_pager(struct outstate *, char *);
    114 static	pid_t		  spawn_pager(struct outstate *, char *);
    115 static	void		  usage(enum argmode) __attribute__((__noreturn__));
    116 static	int		  woptions(char *, enum mandoc_os *, int *);
    117 
    118 static	const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
    119 static	char		  help_arg[] = "help";
    120 static	char		 *help_argv[] = {help_arg, NULL};
    121 
    122 
    123 int
    124 main(int argc, char *argv[])
    125 {
    126 	struct manconf	 conf;		/* Manpaths and output options. */
    127 	struct outstate	 outst;		/* Output state. */
    128 	struct winsize	 ws;		/* Result of ioctl(TIOCGWINSZ). */
    129 	struct mansearch search;	/* Search options. */
    130 	struct manpage	*res;		/* Complete list of search results. */
    131 	struct manpage	*resn;		/* Search results for one name. */
    132 	struct mparse	*mp;		/* Opaque parser object. */
    133 	const char	*conf_file;	/* -C: alternate config file. */
    134 	const char	*os_s;		/* -I: Operating system for display. */
    135 	const char	*progname, *sec, *ep;
    136 	char		*defpaths;	/* -M: override manpaths. */
    137 	char		*auxpaths;	/* -m: additional manpaths. */
    138 	char		*oarg;		/* -O: output option string. */
    139 	char		*tagarg;	/* -O tag: default value. */
    140 	unsigned char	*uc;
    141 	size_t		 ressz;		/* Number of elements in res[]. */
    142 	size_t		 resnsz;	/* Number of elements in resn[]. */
    143 	size_t		 i, ib, ssz;
    144 	int		 options;	/* Parser options. */
    145 	int		 show_usage;	/* Invalid argument: give up. */
    146 	int		 prio, best_prio;
    147 	int		 startdir;
    148 	int		 c;
    149 	enum mandoc_os	 os_e;		/* Check base system conventions. */
    150 	enum outmode	 outmode;	/* According to command line. */
    151 
    152 #if HAVE_PROGNAME
    153 	progname = getprogname();
    154 #else
    155 	if (argc < 1)
    156 		progname = mandoc_strdup("mandoc");
    157 	else if ((progname = strrchr(argv[0], '/')) == NULL)
    158 		progname = argv[0];
    159 	else
    160 		++progname;
    161 	setprogname(progname);
    162 #endif
    163 
    164 	mandoc_msg_setoutfile(stderr);
    165 	if (strncmp(progname, "mandocdb", 8) == 0 ||
    166 	    strcmp(progname, BINM_MAKEWHATIS) == 0)
    167 		return mandocdb(argc, argv);
    168 
    169 #if HAVE_PLEDGE
    170 	if (pledge("stdio rpath wpath cpath tmppath tty proc exec", NULL) == -1) {
    171 		mandoc_msg(MANDOCERR_PLEDGE, 0, 0, "%s", strerror(errno));
    172 		return mandoc_msg_getrc();
    173 	}
    174 #endif
    175 #if HAVE_SANDBOX_INIT
    176 	if (sandbox_init(kSBXProfileNoInternet, SANDBOX_NAMED, NULL) == -1)
    177 		errx((int)MANDOCLEVEL_SYSERR, "sandbox_init");
    178 #endif
    179 
    180 	/* Search options. */
    181 
    182 	memset(&conf, 0, sizeof(conf));
    183 	conf_file = NULL;
    184 	defpaths = auxpaths = NULL;
    185 
    186 	memset(&search, 0, sizeof(struct mansearch));
    187 	search.outkey = "Nd";
    188 	oarg = NULL;
    189 
    190 	if (strcmp(progname, BINM_MAN) == 0)
    191 		search.argmode = ARG_NAME;
    192 	else if (strcmp(progname, BINM_APROPOS) == 0)
    193 		search.argmode = ARG_EXPR;
    194 	else if (strcmp(progname, BINM_WHATIS) == 0)
    195 		search.argmode = ARG_WORD;
    196 	else if (strncmp(progname, "help", 4) == 0)
    197 		search.argmode = ARG_NAME;
    198 	else
    199 		search.argmode = ARG_FILE;
    200 
    201 	/* Parser options. */
    202 
    203 	options = MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1;
    204 	os_e = MANDOC_OS_OTHER;
    205 	os_s = NULL;
    206 
    207 	/* Formatter options. */
    208 
    209 	memset(&outst, 0, sizeof(outst));
    210 	outst.tag_files = NULL;
    211 	outst.outtype = OUTT_LOCALE;
    212 	outst.use_pager = 1;
    213 
    214 	show_usage = 0;
    215 	outmode = OUTMODE_DEF;
    216 
    217 	while ((c = getopt(argc, argv,
    218 	    "aC:cfhI:iK:klM:m:O:S:s:T:VW:w")) != -1) {
    219 		if (c == 'i' && search.argmode == ARG_EXPR) {
    220 			optind--;
    221 			break;
    222 		}
    223 		switch (c) {
    224 		case 'a':
    225 			outmode = OUTMODE_ALL;
    226 			break;
    227 		case 'C':
    228 			conf_file = optarg;
    229 			break;
    230 		case 'c':
    231 			outst.use_pager = 0;
    232 			break;
    233 		case 'f':
    234 			search.argmode = ARG_WORD;
    235 			break;
    236 		case 'h':
    237 			conf.output.synopsisonly = 1;
    238 			outst.use_pager = 0;
    239 			outmode = OUTMODE_ALL;
    240 			break;
    241 		case 'I':
    242 			if (strncmp(optarg, "os=", 3) != 0) {
    243 				mandoc_msg(MANDOCERR_BADARG_BAD, 0, 0,
    244 				    "-I %s", optarg);
    245 				return mandoc_msg_getrc();
    246 			}
    247 			if (os_s != NULL) {
    248 				mandoc_msg(MANDOCERR_BADARG_DUPE, 0, 0,
    249 				    "-I %s", optarg);
    250 				return mandoc_msg_getrc();
    251 			}
    252 			os_s = optarg + 3;
    253 			break;
    254 		case 'K':
    255 			options &= ~(MPARSE_UTF8 | MPARSE_LATIN1);
    256 			if (strcmp(optarg, "utf-8") == 0)
    257 				options |=  MPARSE_UTF8;
    258 			else if (strcmp(optarg, "iso-8859-1") == 0)
    259 				options |=  MPARSE_LATIN1;
    260 			else if (strcmp(optarg, "us-ascii") != 0) {
    261 				mandoc_msg(MANDOCERR_BADARG_BAD, 0, 0,
    262 				    "-K %s", optarg);
    263 				return mandoc_msg_getrc();
    264 			}
    265 			break;
    266 		case 'k':
    267 			search.argmode = ARG_EXPR;
    268 			break;
    269 		case 'l':
    270 			search.argmode = ARG_FILE;
    271 			outmode = OUTMODE_ALL;
    272 			break;
    273 		case 'M':
    274 			defpaths = optarg;
    275 			break;
    276 		case 'm':
    277 			auxpaths = optarg;
    278 			break;
    279 		case 'O':
    280 			oarg = optarg;
    281 			break;
    282 		case 'S':
    283 			search.arch = optarg;
    284 			break;
    285 		case 's':
    286 			search.sec = optarg;
    287 			break;
    288 		case 'T':
    289 			if (strcmp(optarg, "ascii") == 0)
    290 				outst.outtype = OUTT_ASCII;
    291 			else if (strcmp(optarg, "lint") == 0) {
    292 				outst.outtype = OUTT_LINT;
    293 				mandoc_msg_setoutfile(stdout);
    294 				mandoc_msg_setmin(MANDOCERR_BASE);
    295 			} else if (strcmp(optarg, "tree") == 0)
    296 				outst.outtype = OUTT_TREE;
    297 			else if (strcmp(optarg, "man") == 0)
    298 				outst.outtype = OUTT_MAN;
    299 			else if (strcmp(optarg, "html") == 0)
    300 				outst.outtype = OUTT_HTML;
    301 			else if (strcmp(optarg, "markdown") == 0)
    302 				outst.outtype = OUTT_MARKDOWN;
    303 			else if (strcmp(optarg, "utf8") == 0)
    304 				outst.outtype = OUTT_UTF8;
    305 			else if (strcmp(optarg, "locale") == 0)
    306 				outst.outtype = OUTT_LOCALE;
    307 			else if (strcmp(optarg, "ps") == 0)
    308 				outst.outtype = OUTT_PS;
    309 			else if (strcmp(optarg, "pdf") == 0)
    310 				outst.outtype = OUTT_PDF;
    311 			else {
    312 				mandoc_msg(MANDOCERR_BADARG_BAD, 0, 0,
    313 				    "-T %s", optarg);
    314 				return mandoc_msg_getrc();
    315 			}
    316 			break;
    317 		case 'W':
    318 			if (woptions(optarg, &os_e, &outst.wstop) == -1)
    319 				return mandoc_msg_getrc();
    320 			break;
    321 		case 'w':
    322 			outmode = OUTMODE_FLN;
    323 			break;
    324 		default:
    325 			show_usage = 1;
    326 			break;
    327 		}
    328 	}
    329 
    330 	if (show_usage)
    331 		usage(search.argmode);
    332 
    333 	/* Postprocess options. */
    334 
    335 	switch (outmode) {
    336 	case OUTMODE_DEF:
    337 		switch (search.argmode) {
    338 		case ARG_FILE:
    339 			outmode = OUTMODE_ALL;
    340 			outst.use_pager = 0;
    341 			break;
    342 		case ARG_NAME:
    343 			outmode = OUTMODE_ONE;
    344 			break;
    345 		default:
    346 			outmode = OUTMODE_LST;
    347 			break;
    348 		}
    349 		break;
    350 	case OUTMODE_FLN:
    351 		if (search.argmode == ARG_FILE)
    352 			outmode = OUTMODE_ALL;
    353 		break;
    354 	case OUTMODE_ALL:
    355 		break;
    356 	case OUTMODE_LST:
    357 	case OUTMODE_ONE:
    358 		abort();
    359 	}
    360 
    361 	if (oarg != NULL) {
    362 		if (outmode == OUTMODE_LST)
    363 			search.outkey = oarg;
    364 		else {
    365 			while (oarg != NULL) {
    366 				if (manconf_output(&conf.output,
    367 				    strsep(&oarg, ","), 0) == -1)
    368 					return mandoc_msg_getrc();
    369 			}
    370 		}
    371 	}
    372 
    373 	if (outst.outtype != OUTT_TREE || conf.output.noval == 0)
    374 		options |= MPARSE_VALIDATE;
    375 
    376 	if (outmode == OUTMODE_FLN ||
    377 	    outmode == OUTMODE_LST ||
    378 	    (conf.output.outfilename == NULL &&
    379 	     conf.output.tagfilename == NULL &&
    380 	     isatty(STDOUT_FILENO) == 0))
    381 		outst.use_pager = 0;
    382 
    383 	if (outst.use_pager &&
    384 	    (conf.output.width == 0 || conf.output.indent == 0) &&
    385 	    ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) != -1 &&
    386 	    ws.ws_col > 1) {
    387 		if (conf.output.width == 0 && ws.ws_col < 79)
    388 			conf.output.width = ws.ws_col - 1;
    389 		if (conf.output.indent == 0 && ws.ws_col < 66)
    390 			conf.output.indent = 3;
    391 	}
    392 
    393 #if HAVE_PLEDGE
    394 	if (outst.use_pager == 0)
    395 		c = pledge("stdio rpath", NULL);
    396 	else if (conf.output.outfilename != NULL ||
    397 	    conf.output.tagfilename != NULL)
    398 		c = pledge("stdio rpath wpath cpath", NULL);
    399 	else
    400 		c = pledge("stdio rpath tmppath tty proc exec", NULL);
    401 	if (c == -1) {
    402 		mandoc_msg(MANDOCERR_PLEDGE, 0, 0, "%s", strerror(errno));
    403 		return mandoc_msg_getrc();
    404 	}
    405 #endif
    406 
    407 	/* Parse arguments. */
    408 
    409 	if (argc > 0) {
    410 		argc -= optind;
    411 		argv += optind;
    412 	}
    413 
    414 	/*
    415 	 * Quirks for help(1) and man(1),
    416 	 * in particular for a section argument without -s.
    417 	 */
    418 
    419 	if (search.argmode == ARG_NAME) {
    420 		if (*progname == 'h') {
    421 			if (argc == 0) {
    422 				argv = help_argv;
    423 				argc = 1;
    424 			}
    425 		} else if (argc > 1 &&
    426 		    ((uc = (unsigned char *)argv[0]) != NULL) &&
    427 		    ((isdigit(uc[0]) && (uc[1] == '\0' ||
    428 		      isalpha(uc[1]))) ||
    429 		     (uc[0] == 'n' && uc[1] == '\0'))) {
    430 			search.sec = (char *)uc;
    431 			argv++;
    432 			argc--;
    433 		}
    434 		if (search.arch == NULL)
    435 			search.arch = getenv("MACHINE");
    436 #ifdef MACHINE
    437 		if (search.arch == NULL)
    438 			search.arch = MACHINE;
    439 #endif
    440 		if (outmode == OUTMODE_ONE)
    441 			search.firstmatch = 1;
    442 	}
    443 
    444 	/*
    445 	 * Use the first argument for -O tag in addition to
    446 	 * using it as a search term for man(1) or apropos(1).
    447 	 */
    448 
    449 	if (conf.output.tag != NULL && *conf.output.tag == '\0') {
    450 		tagarg = argc > 0 && search.argmode == ARG_EXPR ?
    451 		    strchr(*argv, '=') : NULL;
    452 		conf.output.tag = tagarg == NULL ? *argv : tagarg + 1;
    453 	}
    454 
    455 	/* Read the configuration file. */
    456 
    457 	if (search.argmode != ARG_FILE ||
    458 	    mandoc_msg_getmin() == MANDOCERR_STYLE)
    459 		manconf_parse(&conf, conf_file, defpaths, auxpaths);
    460 
    461 	/* man(1): Resolve each name individually. */
    462 
    463 	if (search.argmode == ARG_NAME) {
    464 		if (argc < 1) {
    465 			if (outmode != OUTMODE_FLN)
    466 				usage(ARG_NAME);
    467 			if (conf.manpath.sz == 0) {
    468 				warnx("The manpath is empty.");
    469 				mandoc_msg_setrc(MANDOCLEVEL_BADARG);
    470 			} else {
    471 				for (i = 0; i + 1 < conf.manpath.sz; i++)
    472 					printf("%s:", conf.manpath.paths[i]);
    473 				printf("%s\n", conf.manpath.paths[i]);
    474 			}
    475 			manconf_free(&conf);
    476 			return (int)mandoc_msg_getrc();
    477 		}
    478 		for (res = NULL, ressz = 0; argc > 0; argc--, argv++) {
    479 			(void)mansearch(&search, &conf.manpath,
    480 			    1, argv, &resn, &resnsz);
    481 			if (resnsz == 0)
    482 				(void)fs_search(&search, &conf.manpath,
    483 				    *argv, &resn, &resnsz);
    484 			if (resnsz == 0 && strchr(*argv, '/') == NULL) {
    485 				if (search.arch != NULL &&
    486 				    arch_valid(search.arch, OSENUM) == 0)
    487 					warnx("Unknown architecture \"%s\".",
    488 					    search.arch);
    489 				else if (search.sec != NULL)
    490 					warnx("No entry for %s in "
    491 					    "section %s of the manual.",
    492 					    *argv, search.sec);
    493 				else
    494 					warnx("No entry for %s in "
    495 					    "the manual.", *argv);
    496 				mandoc_msg_setrc(MANDOCLEVEL_BADARG);
    497 				continue;
    498 			}
    499 			if (resnsz == 0) {
    500 				if (access(*argv, R_OK) == -1) {
    501 					mandoc_msg_setinfilename(*argv);
    502 					mandoc_msg(MANDOCERR_BADARG_BAD,
    503 					    0, 0, "%s", strerror(errno));
    504 					mandoc_msg_setinfilename(NULL);
    505 					continue;
    506 				}
    507 				resnsz = 1;
    508 				resn = mandoc_calloc(resnsz, sizeof(*res));
    509 				resn->file = mandoc_strdup(*argv);
    510 				resn->ipath = SIZE_MAX;
    511 				resn->form = FORM_SRC;
    512 			}
    513 			if (outmode != OUTMODE_ONE || resnsz == 1) {
    514 				res = mandoc_reallocarray(res,
    515 				    ressz + resnsz, sizeof(*res));
    516 				memcpy(res + ressz, resn,
    517 				    sizeof(*resn) * resnsz);
    518 				ressz += resnsz;
    519 				continue;
    520 			}
    521 
    522 			/* Search for the best section. */
    523 
    524 			best_prio = 40;
    525 			for (ib = i = 0; i < resnsz; i++) {
    526 				sec = resn[i].file;
    527 				sec += strcspn(sec, "123456789");
    528 				if (sec[0] == '\0')
    529 					continue; /* No section at all. */
    530 				prio = sec_prios[sec[0] - '1'];
    531 				if (search.sec != NULL) {
    532 					ssz = strlen(search.sec);
    533 					if (strncmp(sec, search.sec, ssz) == 0)
    534 						sec += ssz;
    535 				} else
    536 					sec++; /* Prefer without suffix. */
    537 				if (*sec != '/')
    538 					prio += 10; /* Wrong dir name. */
    539 				if (search.sec != NULL) {
    540 					ep = strchr(sec, '\0');
    541 					if (ep - sec > 3 &&
    542 					    strncmp(ep - 3, ".gz", 3) == 0)
    543 						ep -= 3;
    544 					if ((size_t)(ep - sec) < ssz + 3 ||
    545 					    strncmp(ep - ssz, search.sec,
    546 					     ssz) != 0)      /* Wrong file */
    547 						prio += 20;  /* extension. */
    548 				}
    549 				if (prio >= best_prio)
    550 					continue;
    551 				best_prio = prio;
    552 				ib = i;
    553 			}
    554 			res = mandoc_reallocarray(res, ressz + 1,
    555 			    sizeof(*res));
    556 			memcpy(res + ressz++, resn + ib, sizeof(*resn));
    557 		}
    558 
    559 	/* apropos(1), whatis(1): Process the full search expression. */
    560 
    561 	} else if (search.argmode != ARG_FILE) {
    562 		if (mansearch(&search, &conf.manpath,
    563 		    argc, argv, &res, &ressz) == 0)
    564 			usage(search.argmode);
    565 
    566 		if (ressz == 0) {
    567 			warnx("nothing appropriate");
    568 			mandoc_msg_setrc(MANDOCLEVEL_BADARG);
    569 			goto out;
    570 		}
    571 
    572 	/* mandoc(1): Take command line arguments as file names. */
    573 
    574 	} else {
    575 		ressz = argc > 0 ? argc : 1;
    576 		res = mandoc_calloc(ressz, sizeof(*res));
    577 		for (i = 0; i < ressz; i++) {
    578 			if (argc > 0)
    579 				res[i].file = mandoc_strdup(argv[i]);
    580 			res[i].ipath = SIZE_MAX;
    581 			res[i].form = FORM_SRC;
    582 		}
    583 	}
    584 
    585 	switch (outmode) {
    586 	case OUTMODE_FLN:
    587 		for (i = 0; i < ressz; i++)
    588 			puts(res[i].file);
    589 		goto out;
    590 	case OUTMODE_LST:
    591 		for (i = 0; i < ressz; i++)
    592 			printf("%s - %s\n", res[i].names,
    593 			    res[i].output == NULL ? "" :
    594 			    res[i].output);
    595 		goto out;
    596 	default:
    597 		break;
    598 	}
    599 
    600 	if (search.argmode == ARG_FILE && auxpaths != NULL) {
    601 		if (strcmp(auxpaths, "doc") == 0)
    602 			options |= MPARSE_MDOC;
    603 		else if (strcmp(auxpaths, "an") == 0)
    604 			options |= MPARSE_MAN;
    605 	}
    606 
    607 	mchars_alloc();
    608 	mp = mparse_alloc(options, os_e, os_s);
    609 
    610 	/*
    611 	 * Remember the original working directory, if possible.
    612 	 * This will be needed if some names on the command line
    613 	 * are page names and some are relative file names.
    614 	 * Do not error out if the current directory is not
    615 	 * readable: Maybe it won't be needed after all.
    616 	 */
    617 	startdir = open(".", O_RDONLY | O_DIRECTORY);
    618 	for (i = 0; i < ressz; i++) {
    619 		process_onefile(mp, res + i, startdir, &outst, &conf);
    620 		if (outst.wstop && mandoc_msg_getrc() != MANDOCLEVEL_OK)
    621 			break;
    622 	}
    623 	if (startdir != -1) {
    624 		(void)fchdir(startdir);
    625 		close(startdir);
    626 	}
    627 	if (conf.output.tag != NULL && conf.output.tag_found == 0) {
    628 		mandoc_msg(MANDOCERR_TAG, 0, 0, "%s", conf.output.tag);
    629 		conf.output.tag = NULL;
    630 	}
    631 	if (outst.outdata != NULL) {
    632 		switch (outst.outtype) {
    633 		case OUTT_HTML:
    634 			html_free(outst.outdata);
    635 			break;
    636 		case OUTT_UTF8:
    637 		case OUTT_LOCALE:
    638 		case OUTT_ASCII:
    639 			ascii_free(outst.outdata);
    640 			break;
    641 		case OUTT_PDF:
    642 		case OUTT_PS:
    643 			pspdf_free(outst.outdata);
    644 			break;
    645 		default:
    646 			break;
    647 		}
    648 	}
    649 	mandoc_xr_free();
    650 	mparse_free(mp);
    651 	mchars_free();
    652 
    653 out:
    654 	mansearch_free(res, ressz);
    655 	if (search.argmode != ARG_FILE)
    656 		manconf_free(&conf);
    657 
    658 	if (outst.tag_files != NULL) {
    659 		if (term_tag_close() != -1 &&
    660 		    conf.output.outfilename == NULL &&
    661 		    conf.output.tagfilename == NULL)
    662 			run_pager(&outst, conf.output.tag);
    663 		term_tag_unlink();
    664 	} else if (outst.had_output && outst.outtype != OUTT_LINT)
    665 		mandoc_msg_summary();
    666 
    667 	return (int)mandoc_msg_getrc();
    668 }
    669 
    670 static void
    671 usage(enum argmode argmode)
    672 {
    673 	switch (argmode) {
    674 	case ARG_FILE:
    675 		fputs("usage: mandoc [-ac] [-I os=name] "
    676 		    "[-K encoding] [-mdoc | -man] [-O options]\n"
    677 		    "\t      [-T output] [-W level] [file ...]\n", stderr);
    678 		break;
    679 	case ARG_NAME:
    680 		fputs("usage: man [-acfhklw] [-C file] [-M path] "
    681 		    "[-m path] [-S subsection]\n"
    682 		    "\t   [[-s] section] name ...\n", stderr);
    683 		break;
    684 	case ARG_WORD:
    685 		fputs("usage: whatis [-afk] [-C file] "
    686 		    "[-M path] [-m path] [-O outkey] [-S arch]\n"
    687 		    "\t      [-s section] name ...\n", stderr);
    688 		break;
    689 	case ARG_EXPR:
    690 		fputs("usage: apropos [-afk] [-C file] "
    691 		    "[-M path] [-m path] [-O outkey] [-S arch]\n"
    692 		    "\t       [-s section] expression ...\n", stderr);
    693 		break;
    694 	}
    695 	exit((int)MANDOCLEVEL_BADARG);
    696 }
    697 
    698 static void
    699 glob_esc(char **dst, const char *src, const char *suffix)
    700 {
    701 	while (*src != '\0') {
    702 		if (strchr("*?[", *src) != NULL)
    703 			*(*dst)++ = '\\';
    704 		*(*dst)++ = *src++;
    705 	}
    706 	while (*suffix != '\0')
    707 		*(*dst)++ = *suffix++;
    708 }
    709 
    710 static void
    711 fs_append(char **file, size_t filesz, int copy, size_t ipath,
    712     const char *sec, enum form form, struct manpage **res, size_t *ressz)
    713 {
    714 	struct manpage	*page;
    715 
    716 	*res = mandoc_reallocarray(*res, *ressz + filesz, sizeof(**res));
    717 	page = *res + *ressz;
    718 	*ressz += filesz;
    719 	for (;;) {
    720 		page->file = copy ? mandoc_strdup(*file) : *file;
    721 		page->names = NULL;
    722 		page->output = NULL;
    723 		page->bits = NAME_FILE & NAME_MASK;
    724 		page->ipath = ipath;
    725 		page->sec = (*sec >= '1' && *sec <= '9') ? *sec - '1' + 1 : 10;
    726 		page->form = form;
    727 		if (--filesz == 0)
    728 			break;
    729 		file++;
    730 		page++;
    731 	}
    732 }
    733 
    734 static int
    735 fs_lookup(const struct manpaths *paths, size_t ipath,
    736 	const char *sec, const char *arch, const char *name,
    737 	struct manpage **res, size_t *ressz)
    738 {
    739 	struct stat	 sb;
    740 	glob_t		 globinfo;
    741 	char		*file, *cp, secnum[2];
    742 	int		 globres;
    743 	enum form	 form;
    744 
    745 	const char *const slman = "/man";
    746 	const char *const slash = "/";
    747 	const char *const sglob = ".[01-9]*";
    748 	const char *const dot   = ".";
    749 	const char *const aster = "*";
    750 
    751 	memset(&globinfo, 0, sizeof(globinfo));
    752 	form = FORM_SRC;
    753 
    754 	mandoc_asprintf(&file, "%s/man%s/%s.%s",
    755 	    paths->paths[ipath], sec, name, sec);
    756 	if (stat(file, &sb) != -1)
    757 		goto found;
    758 	free(file);
    759 
    760 	mandoc_asprintf(&file, "%s/cat%s/%s.0",
    761 	    paths->paths[ipath], sec, name);
    762 	if (stat(file, &sb) != -1) {
    763 		form = FORM_CAT;
    764 		goto found;
    765 	}
    766 	free(file);
    767 
    768 	if (arch != NULL) {
    769 		mandoc_asprintf(&file, "%s/man%s/%s/%s.%s",
    770 		    paths->paths[ipath], sec, arch, name, sec);
    771 		if (stat(file, &sb) != -1)
    772 			goto found;
    773 		free(file);
    774 	}
    775 
    776 	cp = file = mandoc_malloc(strlen(paths->paths[ipath]) * 2 +
    777 	    strlen(slman) + strlen(sec) * 2 + strlen(slash) +
    778 	    strlen(name) * 2 + strlen(sglob) + 1);
    779 	glob_esc(&cp, paths->paths[ipath], slman);
    780 	glob_esc(&cp, sec, slash);
    781 	glob_esc(&cp, name, sglob);
    782 	*cp = '\0';
    783 	globres = glob(file, 0, NULL, &globinfo);
    784 	if (globres != 0 && globres != GLOB_NOMATCH)
    785 		mandoc_msg(MANDOCERR_GLOB, 0, 0,
    786 		    "%s: %s", file, strerror(errno));
    787 	free(file);
    788 	file = NULL;
    789 	if (globres == 0)
    790 		goto found;
    791 	globfree(&globinfo);
    792 
    793 	if (sec[1] != '\0' && *ressz == 0) {
    794 		secnum[0] = sec[0];
    795 		secnum[1] = '\0';
    796 		cp = file = mandoc_malloc(strlen(paths->paths[ipath]) * 2 +
    797 		    strlen(slman) + strlen(secnum) * 2 + strlen(slash) +
    798 		    strlen(name) * 2 + strlen(dot) +
    799 		    strlen(sec) * 2 + strlen(aster) + 1);
    800 		glob_esc(&cp, paths->paths[ipath], slman);
    801 		glob_esc(&cp, secnum, slash);
    802 		glob_esc(&cp, name, dot);
    803 		glob_esc(&cp, sec, aster);
    804 		*cp = '\0';
    805 		globres = glob(file, 0, NULL, &globinfo);
    806 		if (globres != 0 && globres != GLOB_NOMATCH)
    807 			mandoc_msg(MANDOCERR_GLOB, 0, 0,
    808 			    "%s: %s", file, strerror(errno));
    809 		free(file);
    810 		file = NULL;
    811 		if (globres == 0)
    812 			goto found;
    813 		globfree(&globinfo);
    814 	}
    815 
    816 	if (res != NULL || ipath + 1 != paths->sz)
    817 		return -1;
    818 
    819 	mandoc_asprintf(&file, "%s.%s", name, sec);
    820 	globres = stat(file, &sb);
    821 	free(file);
    822 	return globres;
    823 
    824 found:
    825 #if !defined(__NetBSD__)
    826 	warnx("outdated mandoc.db lacks %s(%s) entry, run %s %s",
    827 	    name, sec, BINM_MAKEWHATIS, paths->paths[ipath]);
    828 #endif
    829 	if (res == NULL)
    830 		free(file);
    831 	else if (file == NULL)
    832 		fs_append(globinfo.gl_pathv, globinfo.gl_pathc, 1,
    833 		    ipath, sec, form, res, ressz);
    834 	else
    835 		fs_append(&file, 1, 0, ipath, sec, form, res, ressz);
    836 	globfree(&globinfo);
    837 	return 0;
    838 }
    839 
    840 static int
    841 fs_search(const struct mansearch *cfg, const struct manpaths *paths,
    842 	const char *name, struct manpage **res, size_t *ressz)
    843 {
    844 	const char *const sections[] =
    845 	    {"1", "8", "6", "2", "3", "5", "7", "4", "9", "3p"};
    846 	const size_t nsec = sizeof(sections)/sizeof(sections[0]);
    847 
    848 	size_t		 ipath, isec;
    849 
    850 	assert(cfg->argmode == ARG_NAME);
    851 	if (res != NULL)
    852 		*res = NULL;
    853 	*ressz = 0;
    854 	for (ipath = 0; ipath < paths->sz; ipath++) {
    855 		if (cfg->sec != NULL) {
    856 			if (fs_lookup(paths, ipath, cfg->sec, cfg->arch,
    857 			    name, res, ressz) != -1 && cfg->firstmatch)
    858 				return 0;
    859 		} else {
    860 			for (isec = 0; isec < nsec; isec++)
    861 				if (fs_lookup(paths, ipath, sections[isec],
    862 				    cfg->arch, name, res, ressz) != -1 &&
    863 				    cfg->firstmatch)
    864 					return 0;
    865 		}
    866 	}
    867 	return -1;
    868 }
    869 
    870 static void
    871 process_onefile(struct mparse *mp, struct manpage *resp, int startdir,
    872     struct outstate *outst, struct manconf *conf)
    873 {
    874 	int	 fd;
    875 
    876 	/*
    877 	 * Changing directories is not needed in ARG_FILE mode.
    878 	 * Do it on a best-effort basis.  Even in case of
    879 	 * failure, some functionality may still work.
    880 	 */
    881 	if (resp->ipath != SIZE_MAX)
    882 		(void)chdir(conf->manpath.paths[resp->ipath]);
    883 	else if (startdir != -1)
    884 		(void)fchdir(startdir);
    885 
    886 	mandoc_msg_setinfilename(resp->file);
    887 	if (resp->file != NULL) {
    888 		if ((fd = mparse_open(mp, resp->file)) == -1) {
    889 			mandoc_msg(resp->ipath == SIZE_MAX ?
    890 			    MANDOCERR_BADARG_BAD : MANDOCERR_OPEN,
    891 			    0, 0, "%s", strerror(errno));
    892 			mandoc_msg_setinfilename(NULL);
    893 			return;
    894 		}
    895 	} else
    896 		fd = STDIN_FILENO;
    897 
    898 	if (outst->use_pager) {
    899 		outst->use_pager = 0;
    900 		outst->tag_files = term_tag_init(conf->output.outfilename,
    901 		    outst->outtype == OUTT_HTML ? ".html" : "",
    902 		    conf->output.tagfilename);
    903 #if HAVE_PLEDGE
    904 		if ((conf->output.outfilename != NULL ||
    905 		     conf->output.tagfilename != NULL) &&
    906 		    pledge("stdio rpath cpath", NULL) == -1) {
    907 			mandoc_msg(MANDOCERR_PLEDGE, 0, 0,
    908 			    "%s", strerror(errno));
    909 			exit(mandoc_msg_getrc());
    910 		}
    911 #endif
    912 	}
    913 	if (outst->had_output && outst->outtype <= OUTT_UTF8) {
    914 		if (outst->outdata == NULL)
    915 			outdata_alloc(outst, &conf->output);
    916 		terminal_sepline(outst->outdata);
    917 	}
    918 
    919 	if (resp->form == FORM_SRC)
    920 		parse(mp, fd, resp->file, outst, conf);
    921 	else {
    922 		passthrough(fd, conf->output.synopsisonly);
    923 		outst->had_output = 1;
    924 	}
    925 
    926 	if (ferror(stdout)) {
    927 		if (outst->tag_files != NULL) {
    928 			mandoc_msg(MANDOCERR_WRITE, 0, 0, "%s: %s",
    929 			    outst->tag_files->ofn, strerror(errno));
    930 			term_tag_unlink();
    931 			outst->tag_files = NULL;
    932 		} else
    933 			mandoc_msg(MANDOCERR_WRITE, 0, 0, "%s",
    934 			    strerror(errno));
    935 	}
    936 	mandoc_msg_setinfilename(NULL);
    937 }
    938 
    939 static void
    940 parse(struct mparse *mp, int fd, const char *file,
    941     struct outstate *outst, struct manconf *conf)
    942 {
    943 	static struct manpaths	 basepaths;
    944 	static int		 previous;
    945 	struct roff_meta	*meta;
    946 
    947 	assert(fd >= 0);
    948 	if (file == NULL)
    949 		file = "<stdin>";
    950 
    951 	if (previous)
    952 		mparse_reset(mp);
    953 	else
    954 		previous = 1;
    955 
    956 	mparse_readfd(mp, fd, file);
    957 	if (fd != STDIN_FILENO)
    958 		close(fd);
    959 
    960 	/*
    961 	 * With -Wstop and warnings or errors of at least the requested
    962 	 * level, do not produce output.
    963 	 */
    964 
    965 	if (outst->wstop && mandoc_msg_getrc() != MANDOCLEVEL_OK)
    966 		return;
    967 
    968 	if (outst->outdata == NULL)
    969 		outdata_alloc(outst, &conf->output);
    970 	else if (outst->outtype == OUTT_HTML)
    971 		html_reset(outst->outdata);
    972 
    973 	mandoc_xr_reset();
    974 	meta = mparse_result(mp);
    975 
    976 	/* Execute the out device, if it exists. */
    977 
    978 	outst->had_output = 1;
    979 	if (meta->macroset == MACROSET_MDOC) {
    980 		switch (outst->outtype) {
    981 		case OUTT_HTML:
    982 			html_mdoc(outst->outdata, meta);
    983 			break;
    984 		case OUTT_TREE:
    985 			tree_mdoc(outst->outdata, meta);
    986 			break;
    987 		case OUTT_MAN:
    988 			man_mdoc(outst->outdata, meta);
    989 			break;
    990 		case OUTT_PDF:
    991 		case OUTT_ASCII:
    992 		case OUTT_UTF8:
    993 		case OUTT_LOCALE:
    994 		case OUTT_PS:
    995 			terminal_mdoc(outst->outdata, meta);
    996 			break;
    997 		case OUTT_MARKDOWN:
    998 			markdown_mdoc(outst->outdata, meta);
    999 			break;
   1000 		default:
   1001 			break;
   1002 		}
   1003 	}
   1004 	if (meta->macroset == MACROSET_MAN) {
   1005 		switch (outst->outtype) {
   1006 		case OUTT_HTML:
   1007 			html_man(outst->outdata, meta);
   1008 			break;
   1009 		case OUTT_TREE:
   1010 			tree_man(outst->outdata, meta);
   1011 			break;
   1012 		case OUTT_MAN:
   1013 			mparse_copy(mp);
   1014 			break;
   1015 		case OUTT_PDF:
   1016 		case OUTT_ASCII:
   1017 		case OUTT_UTF8:
   1018 		case OUTT_LOCALE:
   1019 		case OUTT_PS:
   1020 			terminal_man(outst->outdata, meta);
   1021 			break;
   1022 		case OUTT_MARKDOWN:
   1023 			mandoc_msg(MANDOCERR_MAN_TMARKDOWN, 0, 0, NULL);
   1024 			break;
   1025 		default:
   1026 			break;
   1027 		}
   1028 	}
   1029 	if (conf->output.tag != NULL && conf->output.tag_found == 0 &&
   1030 	    tag_exists(conf->output.tag))
   1031 		conf->output.tag_found = 1;
   1032 
   1033 	if (mandoc_msg_getmin() < MANDOCERR_STYLE) {
   1034 		if (basepaths.sz == 0)
   1035 			manpath_base(&basepaths);
   1036 		check_xr(&basepaths);
   1037 	} else if (mandoc_msg_getmin() < MANDOCERR_WARNING)
   1038 		check_xr(&conf->manpath);
   1039 }
   1040 
   1041 static void
   1042 check_xr(struct manpaths *paths)
   1043 {
   1044 	struct mansearch	 search;
   1045 	struct mandoc_xr	*xr;
   1046 	size_t			 sz;
   1047 
   1048 	for (xr = mandoc_xr_get(); xr != NULL; xr = xr->next) {
   1049 		if (xr->line == -1)
   1050 			continue;
   1051 		search.arch = NULL;
   1052 		search.sec = xr->sec;
   1053 		search.outkey = NULL;
   1054 		search.argmode = ARG_NAME;
   1055 		search.firstmatch = 1;
   1056 		if (mansearch(&search, paths, 1, &xr->name, NULL, &sz))
   1057 			continue;
   1058 		if (fs_search(&search, paths, xr->name, NULL, &sz) != -1)
   1059 			continue;
   1060 		if (xr->count == 1)
   1061 			mandoc_msg(MANDOCERR_XR_BAD, xr->line,
   1062 			    xr->pos + 1, "Xr %s %s", xr->name, xr->sec);
   1063 		else
   1064 			mandoc_msg(MANDOCERR_XR_BAD, xr->line,
   1065 			    xr->pos + 1, "Xr %s %s (%d times)",
   1066 			    xr->name, xr->sec, xr->count);
   1067 	}
   1068 }
   1069 
   1070 static void
   1071 outdata_alloc(struct outstate *outst, struct manoutput *outconf)
   1072 {
   1073 	switch (outst->outtype) {
   1074 	case OUTT_HTML:
   1075 		outst->outdata = html_alloc(outconf);
   1076 		break;
   1077 	case OUTT_UTF8:
   1078 		outst->outdata = utf8_alloc(outconf);
   1079 		break;
   1080 	case OUTT_LOCALE:
   1081 		outst->outdata = locale_alloc(outconf);
   1082 		break;
   1083 	case OUTT_ASCII:
   1084 		outst->outdata = ascii_alloc(outconf);
   1085 		break;
   1086 	case OUTT_PDF:
   1087 		outst->outdata = pdf_alloc(outconf);
   1088 		break;
   1089 	case OUTT_PS:
   1090 		outst->outdata = ps_alloc(outconf);
   1091 		break;
   1092 	default:
   1093 		break;
   1094 	}
   1095 }
   1096 
   1097 static void
   1098 passthrough(int fd, int synopsis_only)
   1099 {
   1100 	const char	 synb[] = "S\bSY\bYN\bNO\bOP\bPS\bSI\bIS\bS";
   1101 	const char	 synr[] = "SYNOPSIS";
   1102 
   1103 	FILE		*stream;
   1104 	char		*line, *cp;
   1105 	size_t		 linesz;
   1106 	ssize_t		 len, written;
   1107 	int		 lno, print;
   1108 
   1109 	stream = NULL;
   1110 	line = NULL;
   1111 	linesz = 0;
   1112 
   1113 	if (fflush(stdout) == EOF) {
   1114 		mandoc_msg(MANDOCERR_FFLUSH, 0, 0, "%s", strerror(errno));
   1115 		goto done;
   1116 	}
   1117 	if ((stream = fdopen(fd, "r")) == NULL) {
   1118 		close(fd);
   1119 		mandoc_msg(MANDOCERR_FDOPEN, 0, 0, "%s", strerror(errno));
   1120 		goto done;
   1121 	}
   1122 
   1123 	lno = print = 0;
   1124 	while ((len = getline(&line, &linesz, stream)) != -1) {
   1125 		lno++;
   1126 		cp = line;
   1127 		if (synopsis_only) {
   1128 			if (print) {
   1129 				if ( ! isspace((unsigned char)*cp))
   1130 					goto done;
   1131 				while (isspace((unsigned char)*cp)) {
   1132 					cp++;
   1133 					len--;
   1134 				}
   1135 			} else {
   1136 				if (strcmp(cp, synb) == 0 ||
   1137 				    strcmp(cp, synr) == 0)
   1138 					print = 1;
   1139 				continue;
   1140 			}
   1141 		}
   1142 		for (; len > 0; len -= written) {
   1143 			if ((written = write(STDOUT_FILENO, cp, len)) == -1) {
   1144 				mandoc_msg(MANDOCERR_WRITE, 0, 0,
   1145 				    "%s", strerror(errno));
   1146 				goto done;
   1147 			}
   1148 		}
   1149 	}
   1150 	if (ferror(stream))
   1151 		mandoc_msg(MANDOCERR_GETLINE, lno, 0, "%s", strerror(errno));
   1152 
   1153 done:
   1154 	free(line);
   1155 	if (stream != NULL)
   1156 		fclose(stream);
   1157 }
   1158 
   1159 static int
   1160 woptions(char *arg, enum mandoc_os *os_e, int *wstop)
   1161 {
   1162 	char		*v, *o;
   1163 	const char	*toks[11];
   1164 
   1165 	toks[0] = "stop";
   1166 	toks[1] = "all";
   1167 	toks[2] = "base";
   1168 	toks[3] = "style";
   1169 	toks[4] = "warning";
   1170 	toks[5] = "error";
   1171 	toks[6] = "unsupp";
   1172 	toks[7] = "fatal";
   1173 	toks[8] = "openbsd";
   1174 	toks[9] = "netbsd";
   1175 	toks[10] = NULL;
   1176 
   1177 	while (*arg) {
   1178 		o = arg;
   1179 		switch (getsubopt(&arg, __UNCONST(toks), &v)) {
   1180 		case 0:
   1181 			*wstop = 1;
   1182 			break;
   1183 		case 1:
   1184 		case 2:
   1185 			mandoc_msg_setmin(MANDOCERR_BASE);
   1186 			break;
   1187 		case 3:
   1188 			mandoc_msg_setmin(MANDOCERR_STYLE);
   1189 			break;
   1190 		case 4:
   1191 			mandoc_msg_setmin(MANDOCERR_WARNING);
   1192 			break;
   1193 		case 5:
   1194 			mandoc_msg_setmin(MANDOCERR_ERROR);
   1195 			break;
   1196 		case 6:
   1197 			mandoc_msg_setmin(MANDOCERR_UNSUPP);
   1198 			break;
   1199 		case 7:
   1200 			mandoc_msg_setmin(MANDOCERR_BADARG);
   1201 			break;
   1202 		case 8:
   1203 			mandoc_msg_setmin(MANDOCERR_BASE);
   1204 			*os_e = MANDOC_OS_OPENBSD;
   1205 			break;
   1206 		case 9:
   1207 			mandoc_msg_setmin(MANDOCERR_BASE);
   1208 			*os_e = MANDOC_OS_NETBSD;
   1209 			break;
   1210 		default:
   1211 			mandoc_msg(MANDOCERR_BADARG_BAD, 0, 0, "-W %s", o);
   1212 			return -1;
   1213 		}
   1214 	}
   1215 	return 0;
   1216 }
   1217 
   1218 /*
   1219  * Wait until moved to the foreground,
   1220  * then fork the pager and wait for the user to close it.
   1221  */
   1222 static void
   1223 run_pager(struct outstate *outst, char *tag_target)
   1224 {
   1225 	int	 signum, status;
   1226 	pid_t	 man_pgid, tc_pgid;
   1227 	pid_t	 pager_pid, wait_pid;
   1228 
   1229 	man_pgid = getpgid(0);
   1230 	outst->tag_files->tcpgid =
   1231 	    man_pgid == getpid() ? getpgid(getppid()) : man_pgid;
   1232 	pager_pid = 0;
   1233 	signum = SIGSTOP;
   1234 
   1235 	for (;;) {
   1236 		/* Stop here until moved to the foreground. */
   1237 
   1238 		tc_pgid = tcgetpgrp(STDOUT_FILENO);
   1239 		if (tc_pgid != man_pgid) {
   1240 			if (tc_pgid == pager_pid) {
   1241 				(void)tcsetpgrp(STDOUT_FILENO, man_pgid);
   1242 				if (signum == SIGTTIN)
   1243 					continue;
   1244 			} else
   1245 				outst->tag_files->tcpgid = tc_pgid;
   1246 			kill(0, signum);
   1247 			continue;
   1248 		}
   1249 
   1250 		/* Once in the foreground, activate the pager. */
   1251 
   1252 		if (pager_pid) {
   1253 			(void)tcsetpgrp(STDOUT_FILENO, pager_pid);
   1254 			kill(pager_pid, SIGCONT);
   1255 		} else
   1256 			pager_pid = spawn_pager(outst, tag_target);
   1257 
   1258 		/* Wait for the pager to stop or exit. */
   1259 
   1260 		while ((wait_pid = waitpid(pager_pid, &status,
   1261 		    WUNTRACED)) == -1 && errno == EINTR)
   1262 			continue;
   1263 
   1264 		if (wait_pid == -1) {
   1265 			mandoc_msg(MANDOCERR_WAIT, 0, 0,
   1266 			    "%s", strerror(errno));
   1267 			break;
   1268 		}
   1269 		if (!WIFSTOPPED(status))
   1270 			break;
   1271 
   1272 		signum = WSTOPSIG(status);
   1273 	}
   1274 }
   1275 
   1276 static pid_t
   1277 spawn_pager(struct outstate *outst, char *tag_target)
   1278 {
   1279 	const struct timespec timeout = { 0, 100000000 };  /* 0.1s */
   1280 #define MAX_PAGER_ARGS 16
   1281 	char		*argv[MAX_PAGER_ARGS];
   1282 	const char	*pager;
   1283 	char		*cp;
   1284 #if HAVE_LESS_T
   1285 	size_t		 cmdlen;
   1286 #endif
   1287 	int		 argc, use_ofn;
   1288 	pid_t		 pager_pid;
   1289 
   1290 	assert(outst->tag_files->ofd == -1);
   1291 	assert(outst->tag_files->tfs == NULL);
   1292 
   1293 	pager = getenv("MANPAGER");
   1294 	if (pager == NULL || *pager == '\0')
   1295 		pager = getenv("PAGER");
   1296 	if (pager == NULL || *pager == '\0')
   1297 		pager = BINM_PAGER;
   1298 	cp = mandoc_strdup(pager);
   1299 
   1300 	/*
   1301 	 * Parse the pager command into words.
   1302 	 * Intentionally do not do anything fancy here.
   1303 	 */
   1304 
   1305 	argc = 0;
   1306 	while (argc + 5 < MAX_PAGER_ARGS) {
   1307 		argv[argc++] = cp;
   1308 		cp = strchr(cp, ' ');
   1309 		if (cp == NULL)
   1310 			break;
   1311 		*cp++ = '\0';
   1312 		while (*cp == ' ')
   1313 			cp++;
   1314 		if (*cp == '\0')
   1315 			break;
   1316 	}
   1317 
   1318 	/* For less(1), use the tag file. */
   1319 
   1320 	use_ofn = 1;
   1321 #if HAVE_LESS_T
   1322 	if (*outst->tag_files->tfn != '\0' &&
   1323 	    (cmdlen = strlen(argv[0])) >= 4) {
   1324 		cp = argv[0] + cmdlen - 4;
   1325 		if (strcmp(cp, "less") == 0) {
   1326 			argv[argc++] = mandoc_strdup("-T");
   1327 			argv[argc++] = outst->tag_files->tfn;
   1328 			if (tag_target != NULL) {
   1329 				argv[argc++] = mandoc_strdup("-t");
   1330 				argv[argc++] = tag_target;
   1331 				use_ofn = 0;
   1332 			}
   1333 		}
   1334 	}
   1335 #endif
   1336 	if (use_ofn) {
   1337 		if (outst->outtype == OUTT_HTML && tag_target != NULL)
   1338 			mandoc_asprintf(&argv[argc], "file://%s#%s",
   1339 			    outst->tag_files->ofn, tag_target);
   1340 		else
   1341 			argv[argc] = outst->tag_files->ofn;
   1342 		argc++;
   1343 	}
   1344 	argv[argc] = NULL;
   1345 
   1346 	switch (pager_pid = fork()) {
   1347 	case -1:
   1348 		mandoc_msg(MANDOCERR_FORK, 0, 0, "%s", strerror(errno));
   1349 		exit(mandoc_msg_getrc());
   1350 	case 0:
   1351 		break;
   1352 	default:
   1353 		(void)setpgid(pager_pid, 0);
   1354 		(void)tcsetpgrp(STDOUT_FILENO, pager_pid);
   1355 #if HAVE_PLEDGE
   1356 		if (pledge("stdio rpath tmppath tty proc", NULL) == -1) {
   1357 			mandoc_msg(MANDOCERR_PLEDGE, 0, 0,
   1358 			    "%s", strerror(errno));
   1359 			exit(mandoc_msg_getrc());
   1360 		}
   1361 #endif
   1362 		outst->tag_files->pager_pid = pager_pid;
   1363 		return pager_pid;
   1364 	}
   1365 
   1366 	/*
   1367 	 * The child process becomes the pager.
   1368 	 * Do not start it before controlling the terminal.
   1369 	 */
   1370 
   1371 	while (tcgetpgrp(STDOUT_FILENO) != getpid())
   1372 		nanosleep(&timeout, NULL);
   1373 
   1374 	/*coverity[TAINTED_STRING]*/
   1375 	execvp(argv[0], argv);
   1376 	mandoc_msg(MANDOCERR_EXEC, 0, 0, "%s: %s", argv[0], strerror(errno));
   1377 	_exit(mandoc_msg_getrc());
   1378 }
   1379