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