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