Home | History | Annotate | Line # | Download | only in dist
main.c revision 1.2.2.2
      1 /*	Id: main.c,v 1.283 2017/02/17 14:31:52 schwarze Exp  */
      2 /*
      3  * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps (at) bsd.lv>
      4  * Copyright (c) 2010-2012, 2014-2017 Ingo Schwarze <schwarze (at) openbsd.org>
      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 #include "config.h"
     20 
     21 #include <sys/types.h>
     22 #include <sys/param.h>	/* MACHINE */
     23 #include <sys/wait.h>
     24 
     25 #include <assert.h>
     26 #include <ctype.h>
     27 #if HAVE_ERR
     28 #include <err.h>
     29 #endif
     30 #include <errno.h>
     31 #include <fcntl.h>
     32 #include <glob.h>
     33 #if HAVE_SANDBOX_INIT
     34 #include <sandbox.h>
     35 #endif
     36 #include <signal.h>
     37 #include <stdio.h>
     38 #include <stdint.h>
     39 #include <stdlib.h>
     40 #include <string.h>
     41 #include <time.h>
     42 #include <unistd.h>
     43 
     44 #include "mandoc_aux.h"
     45 #include "mandoc.h"
     46 #include "roff.h"
     47 #include "mdoc.h"
     48 #include "man.h"
     49 #include "tag.h"
     50 #include "main.h"
     51 #include "manconf.h"
     52 #include "mansearch.h"
     53 
     54 enum	outmode {
     55 	OUTMODE_DEF = 0,
     56 	OUTMODE_FLN,
     57 	OUTMODE_LST,
     58 	OUTMODE_ALL,
     59 	OUTMODE_INT,
     60 	OUTMODE_ONE
     61 };
     62 
     63 enum	outt {
     64 	OUTT_ASCII = 0,	/* -Tascii */
     65 	OUTT_LOCALE,	/* -Tlocale */
     66 	OUTT_UTF8,	/* -Tutf8 */
     67 	OUTT_TREE,	/* -Ttree */
     68 	OUTT_MAN,	/* -Tman */
     69 	OUTT_HTML,	/* -Thtml */
     70 	OUTT_LINT,	/* -Tlint */
     71 	OUTT_PS,	/* -Tps */
     72 	OUTT_PDF	/* -Tpdf */
     73 };
     74 
     75 struct	curparse {
     76 	struct mparse	 *mp;
     77 	enum mandoclevel  wlevel;	/* ignore messages below this */
     78 	int		  wstop;	/* stop after a file with a warning */
     79 	enum outt	  outtype;	/* which output to use */
     80 	void		 *outdata;	/* data for output */
     81 	struct manoutput *outopts;	/* output options */
     82 };
     83 
     84 
     85 #ifdef HAVE_SQLITE3
     86 int			  mandocdb(int, char *[]);
     87 #endif
     88 
     89 static	int		  fs_lookup(const struct manpaths *,
     90 				size_t ipath, const char *,
     91 				const char *, const char *,
     92 				struct manpage **, size_t *);
     93 static	void		  fs_search(const struct mansearch *,
     94 				const struct manpaths *, int, char**,
     95 				struct manpage **, size_t *);
     96 static	int		  koptions(int *, char *);
     97 static	int		  moptions(int *, char *);
     98 static	void		  mmsg(enum mandocerr, enum mandoclevel,
     99 				const char *, int, int, const char *);
    100 static	void		  outdata_alloc(struct curparse *);
    101 static	void		  parse(struct curparse *, int, const char *);
    102 static	void		  passthrough(const char *, int, int);
    103 static	pid_t		  spawn_pager(struct tag_files *);
    104 static	int		  toptions(struct curparse *, char *);
    105 static	void		  usage(enum argmode) __attribute__((__noreturn__));
    106 static	int		  woptions(struct curparse *, char *);
    107 
    108 static	const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
    109 static	char		  help_arg[] = "help";
    110 static	char		 *help_argv[] = {help_arg, NULL};
    111 static	enum mandoclevel  rc;
    112 
    113 
    114 int
    115 main(int argc, char *argv[])
    116 {
    117 	struct manconf	 conf;
    118 	struct mansearch search;
    119 	struct curparse	 curp;
    120 	struct tag_files *tag_files;
    121 	struct manpage	*res, *resp;
    122 	const char	*progname, *sec, *thisarg;
    123 	char		*conf_file, *defpaths, *auxpaths;
    124 	char		*defos, *oarg;
    125 	unsigned char	*uc;
    126 	size_t		 i, sz;
    127 	int		 prio, best_prio;
    128 	enum outmode	 outmode;
    129 	int		 fd;
    130 	int		 show_usage;
    131 	int		 options;
    132 	int		 use_pager;
    133 	int		 status, signum;
    134 	int		 c;
    135 	pid_t		 pager_pid, tc_pgid, man_pgid, pid;
    136 
    137 #if HAVE_PROGNAME
    138 	progname = getprogname();
    139 #else
    140 	if (argc < 1)
    141 		progname = mandoc_strdup("mandoc");
    142 	else if ((progname = strrchr(argv[0], '/')) == NULL)
    143 		progname = argv[0];
    144 	else
    145 		++progname;
    146 	setprogname(progname);
    147 #endif
    148 
    149 #ifdef HAVE_SQLITE3
    150 	if (strncmp(progname, "mandocdb", 8) == 0 ||
    151 	    strcmp(progname, BINM_MAKEWHATIS) == 0)
    152 		return mandocdb(argc, argv);
    153 #endif
    154 
    155 #if HAVE_PLEDGE
    156 	if (pledge("stdio rpath tmppath tty proc exec flock", NULL) == -1)
    157 		err((int)MANDOCLEVEL_SYSERR, "pledge");
    158 #endif
    159 
    160 #if HAVE_SANDBOX_INIT
    161 	if (sandbox_init(kSBXProfileNoInternet, SANDBOX_NAMED, NULL) == -1)
    162 		errx((int)MANDOCLEVEL_SYSERR, "sandbox_init");
    163 #endif
    164 
    165 	/* Search options. */
    166 
    167 	memset(&conf, 0, sizeof(conf));
    168 	conf_file = defpaths = NULL;
    169 	auxpaths = NULL;
    170 
    171 	memset(&search, 0, sizeof(struct mansearch));
    172 	search.outkey = "Nd";
    173 	oarg = NULL;
    174 
    175 	if (strcmp(progname, BINM_MAN) == 0)
    176 		search.argmode = ARG_NAME;
    177 	else if (strcmp(progname, BINM_APROPOS) == 0)
    178 		search.argmode = ARG_EXPR;
    179 	else if (strcmp(progname, BINM_WHATIS) == 0)
    180 		search.argmode = ARG_WORD;
    181 	else if (strncmp(progname, "help", 4) == 0)
    182 		search.argmode = ARG_NAME;
    183 	else
    184 		search.argmode = ARG_FILE;
    185 
    186 	/* Parser and formatter options. */
    187 
    188 	memset(&curp, 0, sizeof(struct curparse));
    189 	curp.outtype = OUTT_LOCALE;
    190 	curp.wlevel  = MANDOCLEVEL_BADARG;
    191 	curp.outopts = &conf.output;
    192 	options = MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1;
    193 	defos = NULL;
    194 
    195 	use_pager = 1;
    196 	tag_files = NULL;
    197 	show_usage = 0;
    198 	outmode = OUTMODE_DEF;
    199 
    200 	while (-1 != (c = getopt(argc, argv,
    201 			"aC:cfhI:iK:klM:m:O:S:s:T:VW:w"))) {
    202 		switch (c) {
    203 		case 'a':
    204 			outmode = OUTMODE_ALL;
    205 			break;
    206 		case 'C':
    207 			conf_file = optarg;
    208 			break;
    209 		case 'c':
    210 			use_pager = 0;
    211 			break;
    212 		case 'f':
    213 			search.argmode = ARG_WORD;
    214 			break;
    215 		case 'h':
    216 			conf.output.synopsisonly = 1;
    217 			use_pager = 0;
    218 			outmode = OUTMODE_ALL;
    219 			break;
    220 		case 'I':
    221 			if (strncmp(optarg, "os=", 3)) {
    222 				warnx("-I %s: Bad argument", optarg);
    223 				return (int)MANDOCLEVEL_BADARG;
    224 			}
    225 			if (defos) {
    226 				warnx("-I %s: Duplicate argument", optarg);
    227 				return (int)MANDOCLEVEL_BADARG;
    228 			}
    229 			defos = mandoc_strdup(optarg + 3);
    230 			break;
    231 		case 'i':
    232 			outmode = OUTMODE_INT;
    233 			break;
    234 		case 'K':
    235 			if ( ! koptions(&options, optarg))
    236 				return (int)MANDOCLEVEL_BADARG;
    237 			break;
    238 		case 'k':
    239 			search.argmode = ARG_EXPR;
    240 			break;
    241 		case 'l':
    242 			search.argmode = ARG_FILE;
    243 			outmode = OUTMODE_ALL;
    244 			break;
    245 		case 'M':
    246 			defpaths = optarg;
    247 			break;
    248 		case 'm':
    249 			auxpaths = optarg;
    250 			break;
    251 		case 'O':
    252 			oarg = optarg;
    253 			break;
    254 		case 'S':
    255 			search.arch = optarg;
    256 			break;
    257 		case 's':
    258 			search.sec = optarg;
    259 			break;
    260 		case 'T':
    261 			if ( ! toptions(&curp, optarg))
    262 				return (int)MANDOCLEVEL_BADARG;
    263 			break;
    264 		case 'W':
    265 			if ( ! woptions(&curp, optarg))
    266 				return (int)MANDOCLEVEL_BADARG;
    267 			break;
    268 		case 'w':
    269 			outmode = OUTMODE_FLN;
    270 			break;
    271 		default:
    272 			show_usage = 1;
    273 			break;
    274 		}
    275 	}
    276 
    277 	if (show_usage)
    278 		usage(search.argmode);
    279 
    280 	/* Postprocess options. */
    281 
    282 	if (outmode == OUTMODE_DEF) {
    283 		switch (search.argmode) {
    284 		case ARG_FILE:
    285 			outmode = OUTMODE_ALL;
    286 			use_pager = 0;
    287 			break;
    288 		case ARG_NAME:
    289 			outmode = OUTMODE_ONE;
    290 			break;
    291 		default:
    292 			outmode = OUTMODE_LST;
    293 			break;
    294 		}
    295 	}
    296 
    297 	if (oarg != NULL) {
    298 		if (outmode == OUTMODE_LST)
    299 			search.outkey = oarg;
    300 		else {
    301 			while (oarg != NULL) {
    302 				thisarg = oarg;
    303 				if (manconf_output(&conf.output,
    304 				    strsep(&oarg, ","), 0) == 0)
    305 					continue;
    306 				warnx("-O %s: Bad argument", thisarg);
    307 				return (int)MANDOCLEVEL_BADARG;
    308 			}
    309 		}
    310 	}
    311 
    312 	if (outmode == OUTMODE_FLN ||
    313 	    outmode == OUTMODE_LST ||
    314 	    !isatty(STDOUT_FILENO))
    315 		use_pager = 0;
    316 
    317 #if HAVE_PLEDGE
    318 	if (!use_pager)
    319 		if (pledge("stdio rpath flock", NULL) == -1)
    320 			err((int)MANDOCLEVEL_SYSERR, "pledge");
    321 #endif
    322 
    323 	/* Parse arguments. */
    324 
    325 	if (argc > 0) {
    326 		argc -= optind;
    327 		argv += optind;
    328 	}
    329 	resp = NULL;
    330 
    331 	/*
    332 	 * Quirks for help(1)
    333 	 * and for a man(1) section argument without -s.
    334 	 */
    335 
    336 	if (search.argmode == ARG_NAME) {
    337 		if (*progname == 'h') {
    338 			if (argc == 0) {
    339 				argv = help_argv;
    340 				argc = 1;
    341 			}
    342 		} else if (argc > 1 &&
    343 		    ((uc = (unsigned char *)argv[0]) != NULL) &&
    344 		    ((isdigit(uc[0]) && (uc[1] == '\0' ||
    345 		      (isalpha(uc[1]) && uc[2] == '\0'))) ||
    346 		     (uc[0] == 'n' && uc[1] == '\0'))) {
    347 			search.sec = (char *)uc;
    348 			argv++;
    349 			argc--;
    350 		}
    351 		if (search.arch == NULL)
    352 			search.arch = getenv("MACHINE");
    353 #ifdef MACHINE
    354 		if (search.arch == NULL)
    355 			search.arch = MACHINE;
    356 #endif
    357 	}
    358 
    359 	rc = MANDOCLEVEL_OK;
    360 
    361 	/* man(1), whatis(1), apropos(1) */
    362 
    363 	if (search.argmode != ARG_FILE) {
    364 		if (search.argmode == ARG_NAME &&
    365 		    outmode == OUTMODE_ONE)
    366 			search.firstmatch = 1;
    367 
    368 		/* Access the mandoc database. */
    369 
    370 		manconf_parse(&conf, conf_file, defpaths, auxpaths);
    371 #if HAVE_SQLITE3
    372 		if ( ! mansearch(&search, &conf.manpath,
    373 		    argc, argv, &res, &sz))
    374 			usage(search.argmode);
    375 #else
    376 		if (search.argmode != ARG_NAME) {
    377 			fputs("mandoc: database support not compiled in\n",
    378 			    stderr);
    379 			return (int)MANDOCLEVEL_BADARG;
    380 		}
    381 		sz = 0;
    382 #endif
    383 
    384 		if (sz == 0) {
    385 			if (search.argmode == ARG_NAME)
    386 				fs_search(&search, &conf.manpath,
    387 				    argc, argv, &res, &sz);
    388 			else
    389 				warnx("nothing appropriate");
    390 		}
    391 
    392 		if (sz == 0) {
    393 			rc = MANDOCLEVEL_BADARG;
    394 			goto out;
    395 		}
    396 
    397 		/*
    398 		 * For standard man(1) and -a output mode,
    399 		 * prepare for copying filename pointers
    400 		 * into the program parameter array.
    401 		 */
    402 
    403 		if (outmode == OUTMODE_ONE) {
    404 			argc = 1;
    405 			best_prio = 20;
    406 		} else if (outmode == OUTMODE_ALL)
    407 			argc = (int)sz;
    408 
    409 		/* Iterate all matching manuals. */
    410 
    411 		resp = res;
    412 		for (i = 0; i < sz; i++) {
    413 			if (outmode == OUTMODE_FLN)
    414 				puts(res[i].file);
    415 			else if (outmode == OUTMODE_LST)
    416 				printf("%s - %s\n", res[i].names,
    417 				    res[i].output == NULL ? "" :
    418 				    res[i].output);
    419 			else if (outmode == OUTMODE_ONE) {
    420 				/* Search for the best section. */
    421 				sec = res[i].file;
    422 				sec += strcspn(sec, "123456789");
    423 				if (sec[0] == '\0')
    424 					continue;
    425 				prio = sec_prios[sec[0] - '1'];
    426 				if (sec[1] != '/')
    427 					prio += 10;
    428 				if (prio >= best_prio)
    429 					continue;
    430 				best_prio = prio;
    431 				resp = res + i;
    432 			}
    433 		}
    434 
    435 		/*
    436 		 * For man(1), -a and -i output mode, fall through
    437 		 * to the main mandoc(1) code iterating files
    438 		 * and running the parsers on each of them.
    439 		 */
    440 
    441 		if (outmode == OUTMODE_FLN || outmode == OUTMODE_LST)
    442 			goto out;
    443 	}
    444 
    445 	/* mandoc(1) */
    446 
    447 #if HAVE_PLEDGE
    448 	if (use_pager) {
    449 		if (pledge("stdio rpath tmppath tty proc exec", NULL) == -1)
    450 			err((int)MANDOCLEVEL_SYSERR, "pledge");
    451 	} else {
    452 		if (pledge("stdio rpath", NULL) == -1)
    453 			err((int)MANDOCLEVEL_SYSERR, "pledge");
    454 	}
    455 #endif
    456 
    457 	if (search.argmode == ARG_FILE && ! moptions(&options, auxpaths))
    458 		return (int)MANDOCLEVEL_BADARG;
    459 
    460 	mchars_alloc();
    461 	curp.mp = mparse_alloc(options, curp.wlevel, mmsg, defos);
    462 
    463 	/*
    464 	 * Conditionally start up the lookaside buffer before parsing.
    465 	 */
    466 	if (OUTT_MAN == curp.outtype)
    467 		mparse_keep(curp.mp);
    468 
    469 	if (argc < 1) {
    470 		if (use_pager)
    471 			tag_files = tag_init();
    472 		parse(&curp, STDIN_FILENO, "<stdin>");
    473 	}
    474 
    475 	while (argc > 0) {
    476 		fd = mparse_open(curp.mp, resp != NULL ? resp->file : *argv);
    477 		if (fd != -1) {
    478 			if (use_pager) {
    479 				tag_files = tag_init();
    480 				use_pager = 0;
    481 			}
    482 
    483 			if (resp == NULL)
    484 				parse(&curp, fd, *argv);
    485 			else if (resp->form == FORM_SRC) {
    486 				/* For .so only; ignore failure. */
    487 				chdir(conf.manpath.paths[resp->ipath]);
    488 				parse(&curp, fd, resp->file);
    489 			} else
    490 				passthrough(resp->file, fd,
    491 				    conf.output.synopsisonly);
    492 
    493 			if (argc > 1 && curp.outtype <= OUTT_UTF8) {
    494 				if (curp.outdata == NULL)
    495 					outdata_alloc(&curp);
    496 				terminal_sepline(curp.outdata);
    497 			}
    498 		} else if (rc < MANDOCLEVEL_ERROR)
    499 			rc = MANDOCLEVEL_ERROR;
    500 
    501 		if (MANDOCLEVEL_OK != rc && curp.wstop)
    502 			break;
    503 
    504 		if (resp != NULL)
    505 			resp++;
    506 		else
    507 			argv++;
    508 		if (--argc)
    509 			mparse_reset(curp.mp);
    510 	}
    511 
    512 	if (curp.outdata != NULL) {
    513 		switch (curp.outtype) {
    514 		case OUTT_HTML:
    515 			html_free(curp.outdata);
    516 			break;
    517 		case OUTT_UTF8:
    518 		case OUTT_LOCALE:
    519 		case OUTT_ASCII:
    520 			ascii_free(curp.outdata);
    521 			break;
    522 		case OUTT_PDF:
    523 		case OUTT_PS:
    524 			pspdf_free(curp.outdata);
    525 			break;
    526 		default:
    527 			break;
    528 		}
    529 	}
    530 	mparse_free(curp.mp);
    531 	mchars_free();
    532 
    533 out:
    534 	if (search.argmode != ARG_FILE) {
    535 		manconf_free(&conf);
    536 #ifdef HAVE_SQLITE3
    537 		mansearch_free(res, sz);
    538 #endif
    539 	}
    540 
    541 	free(defos);
    542 
    543 	/*
    544 	 * When using a pager, finish writing both temporary files,
    545 	 * fork it, wait for the user to close it, and clean up.
    546 	 */
    547 
    548 	if (tag_files != NULL) {
    549 		fclose(stdout);
    550 		tag_write();
    551 		man_pgid = getpgid(0);
    552 		tag_files->tcpgid = man_pgid == getpid() ?
    553 		    getpgid(getppid()) : man_pgid;
    554 		pager_pid = 0;
    555 		signum = SIGSTOP;
    556 		for (;;) {
    557 
    558 			/* Stop here until moved to the foreground. */
    559 
    560 			tc_pgid = tcgetpgrp(tag_files->ofd);
    561 			if (tc_pgid != man_pgid) {
    562 				if (tc_pgid == pager_pid) {
    563 					(void)tcsetpgrp(tag_files->ofd,
    564 					    man_pgid);
    565 					if (signum == SIGTTIN)
    566 						continue;
    567 				} else
    568 					tag_files->tcpgid = tc_pgid;
    569 				kill(0, signum);
    570 				continue;
    571 			}
    572 
    573 			/* Once in the foreground, activate the pager. */
    574 
    575 			if (pager_pid) {
    576 				(void)tcsetpgrp(tag_files->ofd, pager_pid);
    577 				kill(pager_pid, SIGCONT);
    578 			} else
    579 				pager_pid = spawn_pager(tag_files);
    580 
    581 			/* Wait for the pager to stop or exit. */
    582 
    583 			while ((pid = waitpid(pager_pid, &status,
    584 			    WUNTRACED)) == -1 && errno == EINTR)
    585 				continue;
    586 
    587 			if (pid == -1) {
    588 				warn("wait");
    589 				rc = MANDOCLEVEL_SYSERR;
    590 				break;
    591 			}
    592 			if (!WIFSTOPPED(status))
    593 				break;
    594 
    595 			signum = WSTOPSIG(status);
    596 		}
    597 		tag_unlink();
    598 	}
    599 
    600 	return (int)rc;
    601 }
    602 
    603 static void
    604 usage(enum argmode argmode)
    605 {
    606 
    607 	switch (argmode) {
    608 	case ARG_FILE:
    609 		fputs("usage: mandoc [-acfhkl] [-I os=name] "
    610 		    "[-K encoding] [-mformat] [-O option]\n"
    611 		    "\t      [-T output] [-W level] [file ...]\n", stderr);
    612 		break;
    613 	case ARG_NAME:
    614 		fputs("usage: man [-acfhklw] [-C file] [-I os=name] "
    615 		    "[-K encoding] [-M path] [-m path]\n"
    616 		    "\t   [-O option=value] [-S subsection] [-s section] "
    617 		    "[-T output] [-W level]\n"
    618 		    "\t   [section] name ...\n", stderr);
    619 		break;
    620 	case ARG_WORD:
    621 		fputs("usage: whatis [-acfhklw] [-C file] "
    622 		    "[-M path] [-m path] [-O outkey] [-S arch]\n"
    623 		    "\t      [-s section] name ...\n", stderr);
    624 		break;
    625 	case ARG_EXPR:
    626 		fputs("usage: apropos [-acfhklw] [-C file] "
    627 		    "[-M path] [-m path] [-O outkey] [-S arch]\n"
    628 		    "\t       [-s section] expression ...\n", stderr);
    629 		break;
    630 	}
    631 	exit((int)MANDOCLEVEL_BADARG);
    632 }
    633 
    634 static int
    635 fs_lookup(const struct manpaths *paths, size_t ipath,
    636 	const char *sec, const char *arch, const char *name,
    637 	struct manpage **res, size_t *ressz)
    638 {
    639 	glob_t		 globinfo;
    640 	struct manpage	*page;
    641 	char		*file;
    642 	int		 globres;
    643 	enum form	 form;
    644 
    645 	form = FORM_SRC;
    646 	mandoc_asprintf(&file, "%s/man%s/%s.%s",
    647 	    paths->paths[ipath], sec, name, sec);
    648 	if (access(file, R_OK) != -1)
    649 		goto found;
    650 	free(file);
    651 
    652 	mandoc_asprintf(&file, "%s/cat%s/%s.0",
    653 	    paths->paths[ipath], sec, name);
    654 	if (access(file, R_OK) != -1) {
    655 		form = FORM_CAT;
    656 		goto found;
    657 	}
    658 	free(file);
    659 
    660 	if (arch != NULL) {
    661 		mandoc_asprintf(&file, "%s/man%s/%s/%s.%s",
    662 		    paths->paths[ipath], sec, arch, name, sec);
    663 		if (access(file, R_OK) != -1)
    664 			goto found;
    665 		free(file);
    666 	}
    667 
    668 	mandoc_asprintf(&file, "%s/man%s/%s.[01-9]*",
    669 	    paths->paths[ipath], sec, name);
    670 	globres = glob(file, 0, NULL, &globinfo);
    671 	if (globres != 0 && globres != GLOB_NOMATCH)
    672 		warn("%s: glob", file);
    673 	free(file);
    674 	if (globres == 0)
    675 		file = mandoc_strdup(*globinfo.gl_pathv);
    676 	globfree(&globinfo);
    677 	if (globres != 0)
    678 		return 0;
    679 
    680 found:
    681 	warnx("outdated mandoc.db lacks %s(%s) entry, run %s %s",
    682 	    name, sec, BINM_MAKEWHATIS, paths->paths[ipath]);
    683 	*res = mandoc_reallocarray(*res, ++*ressz, sizeof(struct manpage));
    684 	page = *res + (*ressz - 1);
    685 	page->file = file;
    686 	page->names = NULL;
    687 	page->output = NULL;
    688 	page->ipath = ipath;
    689 	page->bits = NAME_FILE & NAME_MASK;
    690 	page->sec = (*sec >= '1' && *sec <= '9') ? *sec - '1' + 1 : 10;
    691 	page->form = form;
    692 	return 1;
    693 }
    694 
    695 static void
    696 fs_search(const struct mansearch *cfg, const struct manpaths *paths,
    697 	int argc, char **argv, struct manpage **res, size_t *ressz)
    698 {
    699 	const char *const sections[] =
    700 	    {"1", "8", "6", "2", "3", "5", "7", "4", "9", "3p"};
    701 	const size_t nsec = sizeof(sections)/sizeof(sections[0]);
    702 
    703 	size_t		 ipath, isec, lastsz;
    704 
    705 	assert(cfg->argmode == ARG_NAME);
    706 
    707 	*res = NULL;
    708 	*ressz = lastsz = 0;
    709 	while (argc) {
    710 		for (ipath = 0; ipath < paths->sz; ipath++) {
    711 			if (cfg->sec != NULL) {
    712 				if (fs_lookup(paths, ipath, cfg->sec,
    713 				    cfg->arch, *argv, res, ressz) &&
    714 				    cfg->firstmatch)
    715 					return;
    716 			} else for (isec = 0; isec < nsec; isec++)
    717 				if (fs_lookup(paths, ipath, sections[isec],
    718 				    cfg->arch, *argv, res, ressz) &&
    719 				    cfg->firstmatch)
    720 					return;
    721 		}
    722 		if (*ressz == lastsz)
    723 			warnx("No entry for %s in the manual.", *argv);
    724 		lastsz = *ressz;
    725 		argv++;
    726 		argc--;
    727 	}
    728 }
    729 
    730 static void
    731 parse(struct curparse *curp, int fd, const char *file)
    732 {
    733 	enum mandoclevel  rctmp;
    734 	struct roff_man	 *man;
    735 
    736 	/* Begin by parsing the file itself. */
    737 
    738 	assert(file);
    739 	assert(fd >= 0);
    740 
    741 	rctmp = mparse_readfd(curp->mp, fd, file);
    742 	if (fd != STDIN_FILENO)
    743 		close(fd);
    744 	if (rc < rctmp)
    745 		rc = rctmp;
    746 
    747 	/*
    748 	 * With -Wstop and warnings or errors of at least the requested
    749 	 * level, do not produce output.
    750 	 */
    751 
    752 	if (rctmp != MANDOCLEVEL_OK && curp->wstop)
    753 		return;
    754 
    755 	if (curp->outdata == NULL)
    756 		outdata_alloc(curp);
    757 
    758 	mparse_result(curp->mp, &man, NULL);
    759 
    760 	/* Execute the out device, if it exists. */
    761 
    762 	if (man == NULL)
    763 		return;
    764 	if (man->macroset == MACROSET_MDOC) {
    765 		if (curp->outtype != OUTT_TREE || !curp->outopts->noval)
    766 			mdoc_validate(man);
    767 		switch (curp->outtype) {
    768 		case OUTT_HTML:
    769 			html_mdoc(curp->outdata, man);
    770 			break;
    771 		case OUTT_TREE:
    772 			tree_mdoc(curp->outdata, man);
    773 			break;
    774 		case OUTT_MAN:
    775 			man_mdoc(curp->outdata, man);
    776 			break;
    777 		case OUTT_PDF:
    778 		case OUTT_ASCII:
    779 		case OUTT_UTF8:
    780 		case OUTT_LOCALE:
    781 		case OUTT_PS:
    782 			terminal_mdoc(curp->outdata, man);
    783 			break;
    784 		default:
    785 			break;
    786 		}
    787 	}
    788 	if (man->macroset == MACROSET_MAN) {
    789 		if (curp->outtype != OUTT_TREE || !curp->outopts->noval)
    790 			man_validate(man);
    791 		switch (curp->outtype) {
    792 		case OUTT_HTML:
    793 			html_man(curp->outdata, man);
    794 			break;
    795 		case OUTT_TREE:
    796 			tree_man(curp->outdata, man);
    797 			break;
    798 		case OUTT_MAN:
    799 			man_man(curp->outdata, man);
    800 			break;
    801 		case OUTT_PDF:
    802 		case OUTT_ASCII:
    803 		case OUTT_UTF8:
    804 		case OUTT_LOCALE:
    805 		case OUTT_PS:
    806 			terminal_man(curp->outdata, man);
    807 			break;
    808 		default:
    809 			break;
    810 		}
    811 	}
    812 	mparse_updaterc(curp->mp, &rc);
    813 }
    814 
    815 static void
    816 outdata_alloc(struct curparse *curp)
    817 {
    818 	switch (curp->outtype) {
    819 	case OUTT_HTML:
    820 		curp->outdata = html_alloc(curp->outopts);
    821 		break;
    822 	case OUTT_UTF8:
    823 		curp->outdata = utf8_alloc(curp->outopts);
    824 		break;
    825 	case OUTT_LOCALE:
    826 		curp->outdata = locale_alloc(curp->outopts);
    827 		break;
    828 	case OUTT_ASCII:
    829 		curp->outdata = ascii_alloc(curp->outopts);
    830 		break;
    831 	case OUTT_PDF:
    832 		curp->outdata = pdf_alloc(curp->outopts);
    833 		break;
    834 	case OUTT_PS:
    835 		curp->outdata = ps_alloc(curp->outopts);
    836 		break;
    837 	default:
    838 		break;
    839 	}
    840 }
    841 
    842 static void
    843 passthrough(const char *file, int fd, int synopsis_only)
    844 {
    845 	const char	 synb[] = "S\bSY\bYN\bNO\bOP\bPS\bSI\bIS\bS";
    846 	const char	 synr[] = "SYNOPSIS";
    847 
    848 	FILE		*stream;
    849 	const char	*syscall;
    850 	char		*line, *cp;
    851 	size_t		 linesz;
    852 	ssize_t		 len, written;
    853 	int		 print;
    854 
    855 	line = NULL;
    856 	linesz = 0;
    857 
    858 	if (fflush(stdout) == EOF) {
    859 		syscall = "fflush";
    860 		goto fail;
    861 	}
    862 
    863 	if ((stream = fdopen(fd, "r")) == NULL) {
    864 		close(fd);
    865 		syscall = "fdopen";
    866 		goto fail;
    867 	}
    868 
    869 	print = 0;
    870 	while ((len = getline(&line, &linesz, stream)) != -1) {
    871 		cp = line;
    872 		if (synopsis_only) {
    873 			if (print) {
    874 				if ( ! isspace((unsigned char)*cp))
    875 					goto done;
    876 				while (isspace((unsigned char)*cp)) {
    877 					cp++;
    878 					len--;
    879 				}
    880 			} else {
    881 				if (strcmp(cp, synb) == 0 ||
    882 				    strcmp(cp, synr) == 0)
    883 					print = 1;
    884 				continue;
    885 			}
    886 		}
    887 		for (; len > 0; len -= written) {
    888 			if ((written = write(STDOUT_FILENO, cp, len)) != -1)
    889 				continue;
    890 			fclose(stream);
    891 			syscall = "write";
    892 			goto fail;
    893 		}
    894 	}
    895 
    896 	if (ferror(stream)) {
    897 		fclose(stream);
    898 		syscall = "getline";
    899 		goto fail;
    900 	}
    901 
    902 done:
    903 	free(line);
    904 	fclose(stream);
    905 	return;
    906 
    907 fail:
    908 	free(line);
    909 	warn("%s: SYSERR: %s", file, syscall);
    910 	if (rc < MANDOCLEVEL_SYSERR)
    911 		rc = MANDOCLEVEL_SYSERR;
    912 }
    913 
    914 static int
    915 koptions(int *options, char *arg)
    916 {
    917 
    918 	if ( ! strcmp(arg, "utf-8")) {
    919 		*options |=  MPARSE_UTF8;
    920 		*options &= ~MPARSE_LATIN1;
    921 	} else if ( ! strcmp(arg, "iso-8859-1")) {
    922 		*options |=  MPARSE_LATIN1;
    923 		*options &= ~MPARSE_UTF8;
    924 	} else if ( ! strcmp(arg, "us-ascii")) {
    925 		*options &= ~(MPARSE_UTF8 | MPARSE_LATIN1);
    926 	} else {
    927 		warnx("-K %s: Bad argument", arg);
    928 		return 0;
    929 	}
    930 	return 1;
    931 }
    932 
    933 static int
    934 moptions(int *options, char *arg)
    935 {
    936 
    937 	if (arg == NULL)
    938 		/* nothing to do */;
    939 	else if (0 == strcmp(arg, "doc"))
    940 		*options |= MPARSE_MDOC;
    941 	else if (0 == strcmp(arg, "andoc"))
    942 		/* nothing to do */;
    943 	else if (0 == strcmp(arg, "an"))
    944 		*options |= MPARSE_MAN;
    945 	else {
    946 		warnx("-m %s: Bad argument", arg);
    947 		return 0;
    948 	}
    949 
    950 	return 1;
    951 }
    952 
    953 static int
    954 toptions(struct curparse *curp, char *arg)
    955 {
    956 
    957 	if (0 == strcmp(arg, "ascii"))
    958 		curp->outtype = OUTT_ASCII;
    959 	else if (0 == strcmp(arg, "lint")) {
    960 		curp->outtype = OUTT_LINT;
    961 		curp->wlevel  = MANDOCLEVEL_WARNING;
    962 	} else if (0 == strcmp(arg, "tree"))
    963 		curp->outtype = OUTT_TREE;
    964 	else if (0 == strcmp(arg, "man"))
    965 		curp->outtype = OUTT_MAN;
    966 	else if (0 == strcmp(arg, "html"))
    967 		curp->outtype = OUTT_HTML;
    968 	else if (0 == strcmp(arg, "utf8"))
    969 		curp->outtype = OUTT_UTF8;
    970 	else if (0 == strcmp(arg, "locale"))
    971 		curp->outtype = OUTT_LOCALE;
    972 	else if (0 == strcmp(arg, "xhtml"))
    973 		curp->outtype = OUTT_HTML;
    974 	else if (0 == strcmp(arg, "ps"))
    975 		curp->outtype = OUTT_PS;
    976 	else if (0 == strcmp(arg, "pdf"))
    977 		curp->outtype = OUTT_PDF;
    978 	else {
    979 		warnx("-T %s: Bad argument", arg);
    980 		return 0;
    981 	}
    982 
    983 	return 1;
    984 }
    985 
    986 static int
    987 woptions(struct curparse *curp, char *arg)
    988 {
    989 	char		*v, *o;
    990 	const char	*toks[7];
    991 
    992 	toks[0] = "stop";
    993 	toks[1] = "all";
    994 	toks[2] = "warning";
    995 	toks[3] = "error";
    996 	toks[4] = "unsupp";
    997 	toks[5] = "fatal";
    998 	toks[6] = NULL;
    999 
   1000 	while (*arg) {
   1001 		o = arg;
   1002 		switch (getsubopt(&arg, __UNCONST(toks), &v)) {
   1003 		case 0:
   1004 			curp->wstop = 1;
   1005 			break;
   1006 		case 1:
   1007 		case 2:
   1008 			curp->wlevel = MANDOCLEVEL_WARNING;
   1009 			break;
   1010 		case 3:
   1011 			curp->wlevel = MANDOCLEVEL_ERROR;
   1012 			break;
   1013 		case 4:
   1014 			curp->wlevel = MANDOCLEVEL_UNSUPP;
   1015 			break;
   1016 		case 5:
   1017 			curp->wlevel = MANDOCLEVEL_BADARG;
   1018 			break;
   1019 		default:
   1020 			warnx("-W %s: Bad argument", o);
   1021 			return 0;
   1022 		}
   1023 	}
   1024 
   1025 	return 1;
   1026 }
   1027 
   1028 static void
   1029 mmsg(enum mandocerr t, enum mandoclevel lvl,
   1030 		const char *file, int line, int col, const char *msg)
   1031 {
   1032 	const char	*mparse_msg;
   1033 
   1034 	fprintf(stderr, "%s: %s:", getprogname(),
   1035 	    file == NULL ? "<stdin>" : file);
   1036 
   1037 	if (line)
   1038 		fprintf(stderr, "%d:%d:", line, col + 1);
   1039 
   1040 	fprintf(stderr, " %s", mparse_strlevel(lvl));
   1041 
   1042 	if (NULL != (mparse_msg = mparse_strerror(t)))
   1043 		fprintf(stderr, ": %s", mparse_msg);
   1044 
   1045 	if (msg)
   1046 		fprintf(stderr, ": %s", msg);
   1047 
   1048 	fputc('\n', stderr);
   1049 }
   1050 
   1051 static pid_t
   1052 spawn_pager(struct tag_files *tag_files)
   1053 {
   1054 	const struct timespec timeout = { 0, 100000000 };  /* 0.1s */
   1055 #define MAX_PAGER_ARGS 16
   1056 	char		*argv[MAX_PAGER_ARGS];
   1057 	const char	*pager;
   1058 	char		*cp;
   1059 	size_t		 cmdlen;
   1060 	int		 argc;
   1061 	pid_t		 pager_pid;
   1062 
   1063 	pager = getenv("MANPAGER");
   1064 	if (pager == NULL || *pager == '\0')
   1065 		pager = getenv("PAGER");
   1066 	if (pager == NULL || *pager == '\0')
   1067 		pager = "more -s";
   1068 	cp = mandoc_strdup(pager);
   1069 
   1070 	/*
   1071 	 * Parse the pager command into words.
   1072 	 * Intentionally do not do anything fancy here.
   1073 	 */
   1074 
   1075 	argc = 0;
   1076 	while (argc + 4 < MAX_PAGER_ARGS) {
   1077 		argv[argc++] = cp;
   1078 		cp = strchr(cp, ' ');
   1079 		if (cp == NULL)
   1080 			break;
   1081 		*cp++ = '\0';
   1082 		while (*cp == ' ')
   1083 			cp++;
   1084 		if (*cp == '\0')
   1085 			break;
   1086 	}
   1087 
   1088 	/* For less(1), use the tag file. */
   1089 
   1090 	if ((cmdlen = strlen(argv[0])) >= 4) {
   1091 		cp = argv[0] + cmdlen - 4;
   1092 		if (strcmp(cp, "less") == 0) {
   1093 			argv[argc++] = mandoc_strdup("-T");
   1094 			argv[argc++] = tag_files->tfn;
   1095 		}
   1096 	}
   1097 	argv[argc++] = tag_files->ofn;
   1098 	argv[argc] = NULL;
   1099 
   1100 	switch (pager_pid = fork()) {
   1101 	case -1:
   1102 		err((int)MANDOCLEVEL_SYSERR, "fork");
   1103 	case 0:
   1104 		break;
   1105 	default:
   1106 		(void)setpgid(pager_pid, 0);
   1107 		(void)tcsetpgrp(tag_files->ofd, pager_pid);
   1108 #if HAVE_PLEDGE
   1109 		if (pledge("stdio rpath tmppath tty proc", NULL) == -1)
   1110 			err((int)MANDOCLEVEL_SYSERR, "pledge");
   1111 #endif
   1112 		tag_files->pager_pid = pager_pid;
   1113 		return pager_pid;
   1114 	}
   1115 
   1116 	/* The child process becomes the pager. */
   1117 
   1118 	if (dup2(tag_files->ofd, STDOUT_FILENO) == -1)
   1119 		err((int)MANDOCLEVEL_SYSERR, "pager stdout");
   1120 	close(tag_files->ofd);
   1121 	close(tag_files->tfd);
   1122 
   1123 	/* Do not start the pager before controlling the terminal. */
   1124 
   1125 	while (tcgetpgrp(STDOUT_FILENO) != getpid())
   1126 		nanosleep(&timeout, NULL);
   1127 
   1128 	/*coverity[TAINTED_STRING]*/
   1129 	execvp(argv[0], argv);
   1130 	err((int)MANDOCLEVEL_SYSERR, "exec %s", argv[0]);
   1131 }
   1132