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