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