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