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