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