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