1*885a00aeSmillert /* $OpenBSD: main.c,v 1.265 2024/03/29 01:16:30 millert Exp $ */
2f73abda9Skristaps /*
328ca454fSschwarze * Copyright (c) 2010-2012, 2014-2021 Ingo Schwarze <schwarze@openbsd.org>
40ac7e6ecSschwarze * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
539c2a57eSschwarze * Copyright (c) 2010 Joerg Sonnenberger <joerg@netbsd.org>
6f73abda9Skristaps *
7f73abda9Skristaps * Permission to use, copy, modify, and distribute this software for any
8a6464425Sschwarze * purpose with or without fee is hereby granted, provided that the above
9a6464425Sschwarze * copyright notice and this permission notice appear in all copies.
10f73abda9Skristaps *
114de77decSschwarze * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
12a6464425Sschwarze * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
134de77decSschwarze * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
14a6464425Sschwarze * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15a6464425Sschwarze * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16a6464425Sschwarze * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17a6464425Sschwarze * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
180ac7e6ecSschwarze *
190ac7e6ecSschwarze * Main program for mandoc(1), man(1), apropos(1), whatis(1), and help(1).
20f73abda9Skristaps */
210f10154cSschwarze #include <sys/types.h>
22cbdb6ce4Sschwarze #include <sys/ioctl.h>
23c68846c0Sschwarze #include <sys/param.h> /* MACHINE */
24a231508dSschwarze #include <sys/stat.h>
25bb1dcb77Sschwarze #include <sys/wait.h>
260f10154cSschwarze
27f73abda9Skristaps #include <assert.h>
280f10154cSschwarze #include <ctype.h>
29eba1598bSschwarze #include <err.h>
30d5c4dcfeSschwarze #include <errno.h>
310f10154cSschwarze #include <fcntl.h>
32e8e3ce36Sschwarze #include <glob.h>
337d109111Sschwarze #include <limits.h>
3477f78096Sschwarze #include <signal.h>
35f73abda9Skristaps #include <stdio.h>
364175bdabSschwarze #include <stdint.h>
37f73abda9Skristaps #include <stdlib.h>
38f73abda9Skristaps #include <string.h>
39c6bb096dSschwarze #include <termios.h>
40d82a663dSschwarze #include <time.h>
41f73abda9Skristaps #include <unistd.h>
42f73abda9Skristaps
434f4f7972Sschwarze #include "mandoc_aux.h"
44d1982c71Sschwarze #include "mandoc.h"
4519b6bef7Sschwarze #include "mandoc_xr.h"
46d1982c71Sschwarze #include "roff.h"
47f73abda9Skristaps #include "mdoc.h"
48f73abda9Skristaps #include "man.h"
4999acaf1eSschwarze #include "mandoc_parse.h"
50beabc24cSschwarze #include "tag.h"
510ac7e6ecSschwarze #include "term_tag.h"
52d1982c71Sschwarze #include "main.h"
534de77decSschwarze #include "manconf.h"
540f10154cSschwarze #include "mansearch.h"
550f10154cSschwarze
56ecf32ec4Sschwarze #define BINM_APROPOS "apropos"
57ecf32ec4Sschwarze #define BINM_MAN "man"
58ecf32ec4Sschwarze #define BINM_MAKEWHATIS "makewhatis"
59ecf32ec4Sschwarze #define BINM_WHATIS "whatis"
60ecf32ec4Sschwarze #define OSENUM MANDOC_OS_OPENBSD
61ecf32ec4Sschwarze
620f10154cSschwarze enum outmode {
630f10154cSschwarze OUTMODE_DEF = 0,
640f10154cSschwarze OUTMODE_FLN,
650f10154cSschwarze OUTMODE_LST,
660f10154cSschwarze OUTMODE_ALL,
670f10154cSschwarze OUTMODE_ONE
680f10154cSschwarze };
69f73abda9Skristaps
70f73abda9Skristaps enum outt {
71a35fc07aSschwarze OUTT_ASCII = 0, /* -Tascii */
72a5e11edeSschwarze OUTT_LOCALE, /* -Tlocale */
73a5e11edeSschwarze OUTT_UTF8, /* -Tutf8 */
74a35fc07aSschwarze OUTT_TREE, /* -Ttree */
7575d4d0e5Sschwarze OUTT_MAN, /* -Tman */
76a35fc07aSschwarze OUTT_HTML, /* -Thtml */
77b3257404Sschwarze OUTT_MARKDOWN, /* -Tmarkdown */
78a35fc07aSschwarze OUTT_LINT, /* -Tlint */
79a35fc07aSschwarze OUTT_PS, /* -Tps */
80a35fc07aSschwarze OUTT_PDF /* -Tpdf */
81f73abda9Skristaps };
82f73abda9Skristaps
833f735e33Sschwarze struct outstate {
846181cd34Sschwarze struct tag_files *tag_files; /* Tagging state variables. */
85f3476b07Sschwarze void *outdata; /* data for output */
866181cd34Sschwarze int use_pager;
87f3476b07Sschwarze int wstop; /* stop after a file with a warning */
8873f693efSschwarze int had_output; /* Some output was generated. */
89f3476b07Sschwarze enum outt outtype; /* which output to use */
906e03d529Sschwarze };
916e03d529Sschwarze
929398f94cSschwarze
939398f94cSschwarze int mandocdb(int, char *[]);
949398f94cSschwarze
9528ca454fSschwarze static void check_xr(struct manpaths *);
967db69220Sschwarze static void fs_append(char **, size_t, int,
977db69220Sschwarze size_t, const char *, enum form,
987db69220Sschwarze struct manpage **, size_t *);
997db69220Sschwarze static int fs_lookup(const struct manpaths *, size_t,
1007db69220Sschwarze const char *, const char *, const char *,
101c5921aadSschwarze struct manpage **, size_t *);
10210e17f8fSschwarze static int fs_search(const struct mansearch *,
103bc6b7f6fSschwarze const struct manpaths *, const char *,
104c5921aadSschwarze struct manpage **, size_t *);
105e8a031dcSschwarze static void glob_esc(char **, const char *, const char *);
1063f735e33Sschwarze static void outdata_alloc(struct outstate *, struct manoutput *);
107d8e30e1dSschwarze static void parse(struct mparse *, int, const char *,
10828ca454fSschwarze struct outstate *, struct manconf *);
109ecd1ed85Sschwarze static void passthrough(int, int);
11073f693efSschwarze static void process_onefile(struct mparse *, struct manpage *,
11173f693efSschwarze int, struct outstate *, struct manconf *);
112cdecd8e7Sschwarze static void run_pager(struct outstate *, char *);
113cdecd8e7Sschwarze static pid_t spawn_pager(struct outstate *, char *);
114e6187497Sschwarze static void usage(enum argmode) __attribute__((__noreturn__));
115d8e30e1dSschwarze static int woptions(char *, enum mandoc_os *, int *);
116f73abda9Skristaps
1170f10154cSschwarze static const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
11874e8e07bSschwarze static char help_arg[] = "help";
11974e8e07bSschwarze static char *help_argv[] = {help_arg, NULL};
120f73abda9Skristaps
12149aff9f8Sschwarze
122f73abda9Skristaps int
main(int argc,char * argv[])123f73abda9Skristaps main(int argc, char *argv[])
124f73abda9Skristaps {
125d8e30e1dSschwarze struct manconf conf; /* Manpaths and output options. */
1263f735e33Sschwarze struct outstate outst; /* Output state. */
127d8e30e1dSschwarze struct winsize ws; /* Result of ioctl(TIOCGWINSZ). */
128d8e30e1dSschwarze struct mansearch search; /* Search options. */
1299540818fSschwarze struct manpage *res; /* Complete list of search results. */
1309540818fSschwarze struct manpage *resn; /* Search results for one name. */
131d8e30e1dSschwarze struct mparse *mp; /* Opaque parser object. */
132d8e30e1dSschwarze const char *conf_file; /* -C: alternate config file. */
133d8e30e1dSschwarze const char *os_s; /* -I: Operating system for display. */
1341c80596aSschwarze const char *progname, *sec, *ep;
135d8e30e1dSschwarze char *defpaths; /* -M: override manpaths. */
136d8e30e1dSschwarze char *auxpaths; /* -m: additional manpaths. */
137d8e30e1dSschwarze char *oarg; /* -O: output option string. */
138d8e30e1dSschwarze char *tagarg; /* -O tag: default value. */
139c93abf39Sschwarze unsigned char *uc;
1409540818fSschwarze size_t ressz; /* Number of elements in res[]. */
1419540818fSschwarze size_t resnsz; /* Number of elements in resn[]. */
1429540818fSschwarze size_t i, ib, ssz;
143d8e30e1dSschwarze int options; /* Parser options. */
144d8e30e1dSschwarze int show_usage; /* Invalid argument: give up. */
1452ccd0917Sschwarze int prio, best_prio;
14673f693efSschwarze int startdir;
1470f10154cSschwarze int c;
148d8e30e1dSschwarze enum mandoc_os os_e; /* Check base system conventions. */
149d8e30e1dSschwarze enum outmode outmode; /* According to command line. */
150f73abda9Skristaps
1510aad8377Sschwarze progname = getprogname();
152eb170d6cSschwarze mandoc_msg_setoutfile(stderr);
1530aad8377Sschwarze if (strncmp(progname, "mandocdb", 8) == 0 ||
154ecf32ec4Sschwarze strcmp(progname, BINM_MAKEWHATIS) == 0)
155526e306bSschwarze return mandocdb(argc, argv);
1568dbd610cSschwarze
15790f584c6Sschwarze if (pledge("stdio rpath wpath cpath tmppath tty proc exec", NULL) == -1) {
158ecd1ed85Sschwarze mandoc_msg(MANDOCERR_PLEDGE, 0, 0, "%s", strerror(errno));
159ecd1ed85Sschwarze return mandoc_msg_getrc();
160ecd1ed85Sschwarze }
1615879777fSschwarze
1620f10154cSschwarze /* Search options. */
163f73abda9Skristaps
1644de77decSschwarze memset(&conf, 0, sizeof(conf));
165d8e30e1dSschwarze conf_file = NULL;
166d8e30e1dSschwarze defpaths = auxpaths = NULL;
1670f10154cSschwarze
1680f10154cSschwarze memset(&search, 0, sizeof(struct mansearch));
1690f10154cSschwarze search.outkey = "Nd";
170c93abf39Sschwarze oarg = NULL;
1710f10154cSschwarze
172ecf32ec4Sschwarze if (strcmp(progname, BINM_MAN) == 0)
1730f10154cSschwarze search.argmode = ARG_NAME;
174ecf32ec4Sschwarze else if (strcmp(progname, BINM_APROPOS) == 0)
1750f10154cSschwarze search.argmode = ARG_EXPR;
176ecf32ec4Sschwarze else if (strcmp(progname, BINM_WHATIS) == 0)
1770f10154cSschwarze search.argmode = ARG_WORD;
1780aad8377Sschwarze else if (strncmp(progname, "help", 4) == 0)
17974e8e07bSschwarze search.argmode = ARG_NAME;
1800f10154cSschwarze else
1810f10154cSschwarze search.argmode = ARG_FILE;
1820f10154cSschwarze
1833f735e33Sschwarze /* Parser options. */
1840f10154cSschwarze
1853f735e33Sschwarze options = MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1;
186d8e30e1dSschwarze os_e = MANDOC_OS_OTHER;
187d8e30e1dSschwarze os_s = NULL;
1883f735e33Sschwarze
1893f735e33Sschwarze /* Formatter options. */
1903f735e33Sschwarze
1913f735e33Sschwarze memset(&outst, 0, sizeof(outst));
1926181cd34Sschwarze outst.tag_files = NULL;
1933f735e33Sschwarze outst.outtype = OUTT_LOCALE;
1946181cd34Sschwarze outst.use_pager = 1;
195f73abda9Skristaps
1960f10154cSschwarze show_usage = 0;
1970f10154cSschwarze outmode = OUTMODE_DEF;
1980f10154cSschwarze
1997d0afed8Sschwarze while ((c = getopt(argc, argv,
2007d0afed8Sschwarze "aC:cfhI:iK:klM:m:O:S:s:T:VW:w")) != -1) {
2017d0afed8Sschwarze if (c == 'i' && search.argmode == ARG_EXPR) {
2027d0afed8Sschwarze optind--;
2037d0afed8Sschwarze break;
2047d0afed8Sschwarze }
205f73abda9Skristaps switch (c) {
2060f10154cSschwarze case 'a':
2070f10154cSschwarze outmode = OUTMODE_ALL;
2080f10154cSschwarze break;
2090f10154cSschwarze case 'C':
2100f10154cSschwarze conf_file = optarg;
2110f10154cSschwarze break;
2120f10154cSschwarze case 'c':
2136181cd34Sschwarze outst.use_pager = 0;
2140f10154cSschwarze break;
2150f10154cSschwarze case 'f':
2160f10154cSschwarze search.argmode = ARG_WORD;
2170f10154cSschwarze break;
2180a0199c7Sschwarze case 'h':
2192ccd0917Sschwarze conf.output.synopsisonly = 1;
2206181cd34Sschwarze outst.use_pager = 0;
2210a0199c7Sschwarze outmode = OUTMODE_ALL;
2220a0199c7Sschwarze break;
22349aff9f8Sschwarze case 'I':
224ecd1ed85Sschwarze if (strncmp(optarg, "os=", 3) != 0) {
225ecd1ed85Sschwarze mandoc_msg(MANDOCERR_BADARG_BAD, 0, 0,
226ecd1ed85Sschwarze "-I %s", optarg);
227ecd1ed85Sschwarze return mandoc_msg_getrc();
228353fa9ecSschwarze }
229d8e30e1dSschwarze if (os_s != NULL) {
230ecd1ed85Sschwarze mandoc_msg(MANDOCERR_BADARG_DUPE, 0, 0,
231ecd1ed85Sschwarze "-I %s", optarg);
232ecd1ed85Sschwarze return mandoc_msg_getrc();
233353fa9ecSschwarze }
234d8e30e1dSschwarze os_s = optarg + 3;
235353fa9ecSschwarze break;
2367232fc26Sschwarze case 'K':
237ecd1ed85Sschwarze options &= ~(MPARSE_UTF8 | MPARSE_LATIN1);
238ecd1ed85Sschwarze if (strcmp(optarg, "utf-8") == 0)
239ecd1ed85Sschwarze options |= MPARSE_UTF8;
240ecd1ed85Sschwarze else if (strcmp(optarg, "iso-8859-1") == 0)
241ecd1ed85Sschwarze options |= MPARSE_LATIN1;
242ecd1ed85Sschwarze else if (strcmp(optarg, "us-ascii") != 0) {
243ecd1ed85Sschwarze mandoc_msg(MANDOCERR_BADARG_BAD, 0, 0,
244ecd1ed85Sschwarze "-K %s", optarg);
245ecd1ed85Sschwarze return mandoc_msg_getrc();
246ecd1ed85Sschwarze }
2477232fc26Sschwarze break;
2480f10154cSschwarze case 'k':
2490f10154cSschwarze search.argmode = ARG_EXPR;
2500f10154cSschwarze break;
251db8b2ff0Sschwarze case 'l':
252db8b2ff0Sschwarze search.argmode = ARG_FILE;
253db8b2ff0Sschwarze outmode = OUTMODE_ALL;
254db8b2ff0Sschwarze break;
2550f10154cSschwarze case 'M':
2560f10154cSschwarze defpaths = optarg;
2570f10154cSschwarze break;
25849aff9f8Sschwarze case 'm':
2590f10154cSschwarze auxpaths = optarg;
260f73abda9Skristaps break;
26149aff9f8Sschwarze case 'O':
262c93abf39Sschwarze oarg = optarg;
2634175bdabSschwarze break;
2640f10154cSschwarze case 'S':
2650f10154cSschwarze search.arch = optarg;
2660f10154cSschwarze break;
2670f10154cSschwarze case 's':
2680f10154cSschwarze search.sec = optarg;
2690f10154cSschwarze break;
27049aff9f8Sschwarze case 'T':
271ecd1ed85Sschwarze if (strcmp(optarg, "ascii") == 0)
2723f735e33Sschwarze outst.outtype = OUTT_ASCII;
273ecd1ed85Sschwarze else if (strcmp(optarg, "lint") == 0) {
2743f735e33Sschwarze outst.outtype = OUTT_LINT;
275ecd1ed85Sschwarze mandoc_msg_setoutfile(stdout);
276ecd1ed85Sschwarze mandoc_msg_setmin(MANDOCERR_BASE);
277ecd1ed85Sschwarze } else if (strcmp(optarg, "tree") == 0)
2783f735e33Sschwarze outst.outtype = OUTT_TREE;
279ecd1ed85Sschwarze else if (strcmp(optarg, "man") == 0)
2803f735e33Sschwarze outst.outtype = OUTT_MAN;
281ecd1ed85Sschwarze else if (strcmp(optarg, "html") == 0)
2823f735e33Sschwarze outst.outtype = OUTT_HTML;
283ecd1ed85Sschwarze else if (strcmp(optarg, "markdown") == 0)
2843f735e33Sschwarze outst.outtype = OUTT_MARKDOWN;
285ecd1ed85Sschwarze else if (strcmp(optarg, "utf8") == 0)
2863f735e33Sschwarze outst.outtype = OUTT_UTF8;
287ecd1ed85Sschwarze else if (strcmp(optarg, "locale") == 0)
2883f735e33Sschwarze outst.outtype = OUTT_LOCALE;
289ecd1ed85Sschwarze else if (strcmp(optarg, "ps") == 0)
2903f735e33Sschwarze outst.outtype = OUTT_PS;
291ecd1ed85Sschwarze else if (strcmp(optarg, "pdf") == 0)
2923f735e33Sschwarze outst.outtype = OUTT_PDF;
293ecd1ed85Sschwarze else {
294ecd1ed85Sschwarze mandoc_msg(MANDOCERR_BADARG_BAD, 0, 0,
295ecd1ed85Sschwarze "-T %s", optarg);
296ecd1ed85Sschwarze return mandoc_msg_getrc();
297ecd1ed85Sschwarze }
298f73abda9Skristaps break;
29949aff9f8Sschwarze case 'W':
3003f735e33Sschwarze if (woptions(optarg, &os_e, &outst.wstop) == -1)
301ecd1ed85Sschwarze return mandoc_msg_getrc();
302f73abda9Skristaps break;
3030f10154cSschwarze case 'w':
3040f10154cSschwarze outmode = OUTMODE_FLN;
3050f10154cSschwarze break;
306f73abda9Skristaps default:
3070f10154cSschwarze show_usage = 1;
3080f10154cSschwarze break;
309f73abda9Skristaps }
3100f10154cSschwarze }
3110f10154cSschwarze
3120f10154cSschwarze if (show_usage)
3130f10154cSschwarze usage(search.argmode);
3140f10154cSschwarze
3150f10154cSschwarze /* Postprocess options. */
3160f10154cSschwarze
317b3eeebc7Sschwarze switch (outmode) {
318b3eeebc7Sschwarze case OUTMODE_DEF:
3190f10154cSschwarze switch (search.argmode) {
3200f10154cSschwarze case ARG_FILE:
3210f10154cSschwarze outmode = OUTMODE_ALL;
3226181cd34Sschwarze outst.use_pager = 0;
3230f10154cSschwarze break;
3240f10154cSschwarze case ARG_NAME:
3250f10154cSschwarze outmode = OUTMODE_ONE;
3260f10154cSschwarze break;
3270f10154cSschwarze default:
3280f10154cSschwarze outmode = OUTMODE_LST;
3290f10154cSschwarze break;
3300f10154cSschwarze }
331b3eeebc7Sschwarze break;
332b3eeebc7Sschwarze case OUTMODE_FLN:
333b3eeebc7Sschwarze if (search.argmode == ARG_FILE)
334b3eeebc7Sschwarze outmode = OUTMODE_ALL;
335b3eeebc7Sschwarze break;
336b3eeebc7Sschwarze case OUTMODE_ALL:
337b3eeebc7Sschwarze break;
338b3eeebc7Sschwarze case OUTMODE_LST:
339b3eeebc7Sschwarze case OUTMODE_ONE:
340b3eeebc7Sschwarze abort();
3410f10154cSschwarze }
3420f10154cSschwarze
343c93abf39Sschwarze if (oarg != NULL) {
344c93abf39Sschwarze if (outmode == OUTMODE_LST)
345c93abf39Sschwarze search.outkey = oarg;
346c93abf39Sschwarze else {
347c93abf39Sschwarze while (oarg != NULL) {
348c93abf39Sschwarze if (manconf_output(&conf.output,
349bbfeca9fSschwarze strsep(&oarg, ","), 0) == -1)
350ecd1ed85Sschwarze return mandoc_msg_getrc();
351c93abf39Sschwarze }
352c93abf39Sschwarze }
353c93abf39Sschwarze }
354c93abf39Sschwarze
3553f735e33Sschwarze if (outst.outtype != OUTT_TREE || conf.output.noval == 0)
3566b86842eSschwarze options |= MPARSE_VALIDATE;
3576b86842eSschwarze
3584a5b30c7Sschwarze if (outmode == OUTMODE_FLN ||
3594a5b30c7Sschwarze outmode == OUTMODE_LST ||
36090f584c6Sschwarze (conf.output.outfilename == NULL &&
36190f584c6Sschwarze conf.output.tagfilename == NULL &&
36290f584c6Sschwarze isatty(STDOUT_FILENO) == 0))
3636181cd34Sschwarze outst.use_pager = 0;
3644a5b30c7Sschwarze
3656181cd34Sschwarze if (outst.use_pager &&
366cbdb6ce4Sschwarze (conf.output.width == 0 || conf.output.indent == 0) &&
36724292e85Sschwarze ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) != -1 &&
36824292e85Sschwarze ws.ws_col > 1) {
369cbdb6ce4Sschwarze if (conf.output.width == 0 && ws.ws_col < 79)
370cbdb6ce4Sschwarze conf.output.width = ws.ws_col - 1;
371cbdb6ce4Sschwarze if (conf.output.indent == 0 && ws.ws_col < 66)
372cbdb6ce4Sschwarze conf.output.indent = 3;
373cbdb6ce4Sschwarze }
374cbdb6ce4Sschwarze
37590f584c6Sschwarze if (outst.use_pager == 0)
37690f584c6Sschwarze c = pledge("stdio rpath", NULL);
37790f584c6Sschwarze else if (conf.output.outfilename != NULL ||
37890f584c6Sschwarze conf.output.tagfilename != NULL)
37990f584c6Sschwarze c = pledge("stdio rpath wpath cpath", NULL);
38090f584c6Sschwarze else
38190f584c6Sschwarze c = pledge("stdio rpath tmppath tty proc exec", NULL);
38290f584c6Sschwarze if (c == -1) {
38390f584c6Sschwarze mandoc_msg(MANDOCERR_PLEDGE, 0, 0, "%s", strerror(errno));
384ecd1ed85Sschwarze return mandoc_msg_getrc();
385ecd1ed85Sschwarze }
3865879777fSschwarze
3870f10154cSschwarze /* Parse arguments. */
3880f10154cSschwarze
389a42747d6Sschwarze if (argc > 0) {
3900f10154cSschwarze argc -= optind;
3910f10154cSschwarze argv += optind;
392a42747d6Sschwarze }
3930f10154cSschwarze
39474e8e07bSschwarze /*
3959540818fSschwarze * Quirks for help(1) and man(1),
3969540818fSschwarze * in particular for a section argument without -s.
39774e8e07bSschwarze */
3980f10154cSschwarze
39974e8e07bSschwarze if (search.argmode == ARG_NAME) {
4000aad8377Sschwarze if (*progname == 'h') {
40174e8e07bSschwarze if (argc == 0) {
40274e8e07bSschwarze argv = help_argv;
40374e8e07bSschwarze argc = 1;
40474e8e07bSschwarze }
405f9282106Sschwarze } else if (argc > 1 &&
4061918841eSschwarze ((uc = (unsigned char *)argv[0]) != NULL) &&
407afe74b66Sschwarze ((isdigit(uc[0]) && (uc[1] == '\0' ||
40874c6ffceSschwarze isalpha(uc[1]))) ||
409afe74b66Sschwarze (uc[0] == 'n' && uc[1] == '\0'))) {
4101918841eSschwarze search.sec = (char *)uc;
4110f10154cSschwarze argv++;
4120f10154cSschwarze argc--;
4130f10154cSschwarze }
414c68846c0Sschwarze if (search.arch == NULL)
415c68846c0Sschwarze search.arch = getenv("MACHINE");
416c68846c0Sschwarze if (search.arch == NULL)
417c68846c0Sschwarze search.arch = MACHINE;
4189540818fSschwarze if (outmode == OUTMODE_ONE)
4199540818fSschwarze search.firstmatch = 1;
42074e8e07bSschwarze }
4210f10154cSschwarze
422bbeffd04Sschwarze /*
423bbeffd04Sschwarze * Use the first argument for -O tag in addition to
424bbeffd04Sschwarze * using it as a search term for man(1) or apropos(1).
425bbeffd04Sschwarze */
426bbeffd04Sschwarze
427bbeffd04Sschwarze if (conf.output.tag != NULL && *conf.output.tag == '\0') {
428bbeffd04Sschwarze tagarg = argc > 0 && search.argmode == ARG_EXPR ?
429bbeffd04Sschwarze strchr(*argv, '=') : NULL;
430bbeffd04Sschwarze conf.output.tag = tagarg == NULL ? *argv : tagarg + 1;
431bbeffd04Sschwarze }
432bbeffd04Sschwarze
4339540818fSschwarze /* Read the configuration file. */
4340f10154cSschwarze
43528ca454fSschwarze if (search.argmode != ARG_FILE ||
43628ca454fSschwarze mandoc_msg_getmin() == MANDOCERR_STYLE)
4374de77decSschwarze manconf_parse(&conf, conf_file, defpaths, auxpaths);
438c5921aadSschwarze
4399540818fSschwarze /* man(1): Resolve each name individually. */
4402acc4fe7Sschwarze
4412acc4fe7Sschwarze if (search.argmode == ARG_NAME) {
442db5b0f16Sschwarze if (argc < 1) {
443db5b0f16Sschwarze if (outmode != OUTMODE_FLN)
4449540818fSschwarze usage(ARG_NAME);
445db5b0f16Sschwarze if (conf.manpath.sz == 0) {
446db5b0f16Sschwarze warnx("The manpath is empty.");
447db5b0f16Sschwarze mandoc_msg_setrc(MANDOCLEVEL_BADARG);
448db5b0f16Sschwarze } else {
449db5b0f16Sschwarze for (i = 0; i + 1 < conf.manpath.sz; i++)
450db5b0f16Sschwarze printf("%s:", conf.manpath.paths[i]);
451db5b0f16Sschwarze printf("%s\n", conf.manpath.paths[i]);
452db5b0f16Sschwarze }
453db5b0f16Sschwarze manconf_free(&conf);
454db5b0f16Sschwarze return (int)mandoc_msg_getrc();
455db5b0f16Sschwarze }
4569540818fSschwarze for (res = NULL, ressz = 0; argc > 0; argc--, argv++) {
4579540818fSschwarze (void)mansearch(&search, &conf.manpath,
4589540818fSschwarze 1, argv, &resn, &resnsz);
4599540818fSschwarze if (resnsz == 0)
4609540818fSschwarze (void)fs_search(&search, &conf.manpath,
461bc6b7f6fSschwarze *argv, &resn, &resnsz);
462bc6b7f6fSschwarze if (resnsz == 0 && strchr(*argv, '/') == NULL) {
463bc6b7f6fSschwarze if (search.arch != NULL &&
464bc6b7f6fSschwarze arch_valid(search.arch, OSENUM) == 0)
465bc6b7f6fSschwarze warnx("Unknown architecture \"%s\".",
466bc6b7f6fSschwarze search.arch);
467bc6b7f6fSschwarze else if (search.sec != NULL)
468bc6b7f6fSschwarze warnx("No entry for %s in "
469bc6b7f6fSschwarze "section %s of the manual.",
470bc6b7f6fSschwarze *argv, search.sec);
471bc6b7f6fSschwarze else
472bc6b7f6fSschwarze warnx("No entry for %s in "
473bc6b7f6fSschwarze "the manual.", *argv);
4749540818fSschwarze mandoc_msg_setrc(MANDOCLEVEL_BADARG);
4752acc4fe7Sschwarze continue;
4769540818fSschwarze }
477bc6b7f6fSschwarze if (resnsz == 0) {
4789540818fSschwarze if (access(*argv, R_OK) == -1) {
4799540818fSschwarze mandoc_msg_setinfilename(*argv);
480ecd1ed85Sschwarze mandoc_msg(MANDOCERR_BADARG_BAD,
481ecd1ed85Sschwarze 0, 0, "%s", strerror(errno));
482ecd1ed85Sschwarze mandoc_msg_setinfilename(NULL);
4832acc4fe7Sschwarze continue;
4842acc4fe7Sschwarze }
4859540818fSschwarze resnsz = 1;
4869540818fSschwarze resn = mandoc_calloc(resnsz, sizeof(*res));
4879540818fSschwarze resn->file = mandoc_strdup(*argv);
4889540818fSschwarze resn->ipath = SIZE_MAX;
4899540818fSschwarze resn->form = FORM_SRC;
4909540818fSschwarze }
4919540818fSschwarze if (outmode != OUTMODE_ONE || resnsz == 1) {
4922acc4fe7Sschwarze res = mandoc_reallocarray(res,
4939540818fSschwarze ressz + resnsz, sizeof(*res));
4949540818fSschwarze memcpy(res + ressz, resn,
4959540818fSschwarze sizeof(*resn) * resnsz);
4969540818fSschwarze ressz += resnsz;
4970883e5d9Sschwarze free(resn);
4980883e5d9Sschwarze resn = NULL;
4990883e5d9Sschwarze resnsz = 0;
5009540818fSschwarze continue;
50160453e88Sschwarze }
5020f10154cSschwarze
5030f10154cSschwarze /* Search for the best section. */
5049540818fSschwarze
5059540818fSschwarze best_prio = 40;
5069540818fSschwarze for (ib = i = 0; i < resnsz; i++) {
507c02a7420Sschwarze sec = resn[i].file +
508c02a7420Sschwarze strlen(conf.manpath.paths[resn[i].ipath]);
509598b66e2Sschwarze sec += strcspn(sec, "123456789");
510598b66e2Sschwarze if (sec[0] == '\0')
51188412cd2Sschwarze continue; /* No section at all. */
512598b66e2Sschwarze prio = sec_prios[sec[0] - '1'];
51388412cd2Sschwarze if (search.sec != NULL) {
51488412cd2Sschwarze ssz = strlen(search.sec);
51588412cd2Sschwarze if (strncmp(sec, search.sec, ssz) == 0)
51688412cd2Sschwarze sec += ssz;
51788412cd2Sschwarze } else
51888412cd2Sschwarze sec++; /* Prefer without suffix. */
51988412cd2Sschwarze if (*sec != '/')
52088412cd2Sschwarze prio += 10; /* Wrong dir name. */
5211c80596aSschwarze if (search.sec != NULL) {
5221c80596aSschwarze ep = strchr(sec, '\0');
5231c80596aSschwarze if (ep - sec > 3 &&
5241c80596aSschwarze strncmp(ep - 3, ".gz", 3) == 0)
5251c80596aSschwarze ep -= 3;
5261c80596aSschwarze if ((size_t)(ep - sec) < ssz + 3 ||
5271c80596aSschwarze strncmp(ep - ssz, search.sec,
5281c80596aSschwarze ssz) != 0) /* Wrong file */
5291c80596aSschwarze prio += 20; /* extension. */
5301c80596aSschwarze }
5310f10154cSschwarze if (prio >= best_prio)
5320f10154cSschwarze continue;
5330f10154cSschwarze best_prio = prio;
5349540818fSschwarze ib = i;
5350f10154cSschwarze }
5369540818fSschwarze res = mandoc_reallocarray(res, ressz + 1,
5379540818fSschwarze sizeof(*res));
5389540818fSschwarze memcpy(res + ressz++, resn + ib, sizeof(*resn));
5390883e5d9Sschwarze memset(resn + ib, 0, sizeof(*resn));
5400883e5d9Sschwarze mansearch_free(resn, resnsz);
5410883e5d9Sschwarze resn = NULL;
5420883e5d9Sschwarze resnsz = 0;
5430f10154cSschwarze }
5440f10154cSschwarze
5459540818fSschwarze /* apropos(1), whatis(1): Process the full search expression. */
5460f10154cSschwarze
5479540818fSschwarze } else if (search.argmode != ARG_FILE) {
5489540818fSschwarze if (mansearch(&search, &conf.manpath,
5499540818fSschwarze argc, argv, &res, &ressz) == 0)
5509540818fSschwarze usage(search.argmode);
5519540818fSschwarze
5529540818fSschwarze if (ressz == 0) {
5539540818fSschwarze warnx("nothing appropriate");
5549540818fSschwarze mandoc_msg_setrc(MANDOCLEVEL_BADARG);
5550f10154cSschwarze goto out;
5569540818fSschwarze }
5579540818fSschwarze
5589540818fSschwarze /* mandoc(1): Take command line arguments as file names. */
5599540818fSschwarze
56073f693efSschwarze } else {
5619540818fSschwarze ressz = argc > 0 ? argc : 1;
5629540818fSschwarze res = mandoc_calloc(ressz, sizeof(*res));
5639540818fSschwarze for (i = 0; i < ressz; i++) {
56473f693efSschwarze if (argc > 0)
56573f693efSschwarze res[i].file = mandoc_strdup(argv[i]);
56673f693efSschwarze res[i].ipath = SIZE_MAX;
56773f693efSschwarze res[i].form = FORM_SRC;
56873f693efSschwarze }
5690f10154cSschwarze }
5700f10154cSschwarze
5719540818fSschwarze switch (outmode) {
5729540818fSschwarze case OUTMODE_FLN:
5739540818fSschwarze for (i = 0; i < ressz; i++)
5749540818fSschwarze puts(res[i].file);
5759540818fSschwarze goto out;
5769540818fSschwarze case OUTMODE_LST:
5779540818fSschwarze for (i = 0; i < ressz; i++)
5789540818fSschwarze printf("%s - %s\n", res[i].names,
5799540818fSschwarze res[i].output == NULL ? "" :
5809540818fSschwarze res[i].output);
5819540818fSschwarze goto out;
5829540818fSschwarze default:
5839540818fSschwarze break;
5849540818fSschwarze }
5850f10154cSschwarze
586ecd1ed85Sschwarze if (search.argmode == ARG_FILE && auxpaths != NULL) {
587ecd1ed85Sschwarze if (strcmp(auxpaths, "doc") == 0)
588ecd1ed85Sschwarze options |= MPARSE_MDOC;
589ecd1ed85Sschwarze else if (strcmp(auxpaths, "an") == 0)
590ecd1ed85Sschwarze options |= MPARSE_MAN;
591ecd1ed85Sschwarze }
5920f10154cSschwarze
59316536faaSschwarze mchars_alloc();
594d8e30e1dSschwarze mp = mparse_alloc(options, os_e, os_s);
595a35fc07aSschwarze
5962acc4fe7Sschwarze /*
5972acc4fe7Sschwarze * Remember the original working directory, if possible.
5982acc4fe7Sschwarze * This will be needed if some names on the command line
5992acc4fe7Sschwarze * are page names and some are relative file names.
6002acc4fe7Sschwarze * Do not error out if the current directory is not
6012acc4fe7Sschwarze * readable: Maybe it won't be needed after all.
6022acc4fe7Sschwarze */
6032acc4fe7Sschwarze startdir = open(".", O_RDONLY | O_DIRECTORY);
6049540818fSschwarze for (i = 0; i < ressz; i++) {
6059540818fSschwarze process_onefile(mp, res + i, startdir, &outst, &conf);
6063f735e33Sschwarze if (outst.wstop && mandoc_msg_getrc() != MANDOCLEVEL_OK)
6079f39971fSschwarze break;
608f73abda9Skristaps }
6092acc4fe7Sschwarze if (startdir != -1) {
6102acc4fe7Sschwarze (void)fchdir(startdir);
6112acc4fe7Sschwarze close(startdir);
6122acc4fe7Sschwarze }
613beabc24cSschwarze if (conf.output.tag != NULL && conf.output.tag_found == 0) {
614beabc24cSschwarze mandoc_msg(MANDOCERR_TAG, 0, 0, "%s", conf.output.tag);
615beabc24cSschwarze conf.output.tag = NULL;
616beabc24cSschwarze }
6173f735e33Sschwarze if (outst.outdata != NULL) {
6183f735e33Sschwarze switch (outst.outtype) {
619b68ff58bSschwarze case OUTT_HTML:
6203f735e33Sschwarze html_free(outst.outdata);
621b68ff58bSschwarze break;
622b68ff58bSschwarze case OUTT_UTF8:
623b68ff58bSschwarze case OUTT_LOCALE:
624b68ff58bSschwarze case OUTT_ASCII:
6253f735e33Sschwarze ascii_free(outst.outdata);
626b68ff58bSschwarze break;
627b68ff58bSschwarze case OUTT_PDF:
628b68ff58bSschwarze case OUTT_PS:
6293f735e33Sschwarze pspdf_free(outst.outdata);
630b68ff58bSschwarze break;
631b68ff58bSschwarze default:
632b68ff58bSschwarze break;
633b68ff58bSschwarze }
634da67bd1bSschwarze }
63519b6bef7Sschwarze mandoc_xr_free();
636d8e30e1dSschwarze mparse_free(mp);
63716536faaSschwarze mchars_free();
6380f10154cSschwarze
6390f10154cSschwarze out:
6409540818fSschwarze mansearch_free(res, ressz);
64173f693efSschwarze if (search.argmode != ARG_FILE)
64273f693efSschwarze manconf_free(&conf);
643f73abda9Skristaps
6446181cd34Sschwarze if (outst.tag_files != NULL) {
64590f584c6Sschwarze if (term_tag_close() != -1 &&
64690f584c6Sschwarze conf.output.outfilename == NULL &&
64790f584c6Sschwarze conf.output.tagfilename == NULL)
648cdecd8e7Sschwarze run_pager(&outst, conf.output.tag);
6490ac7e6ecSschwarze term_tag_unlink();
65073f693efSschwarze } else if (outst.had_output && outst.outtype != OUTT_LINT)
65116c326ecSschwarze mandoc_msg_summary();
65216c326ecSschwarze
653e501e731Sschwarze return (int)mandoc_msg_getrc();
654f73abda9Skristaps }
655f73abda9Skristaps
656aa2d850aSschwarze static void
usage(enum argmode argmode)6570f10154cSschwarze usage(enum argmode argmode)
658f73abda9Skristaps {
6590f10154cSschwarze switch (argmode) {
6600f10154cSschwarze case ARG_FILE:
661dc1f54e8Sschwarze fputs("usage: mandoc [-ac] [-I os=name] "
662dc1f54e8Sschwarze "[-K encoding] [-mdoc | -man] [-O options]\n"
6635f08183eSjmc "\t [-T output] [-W level] [file ...]\n", stderr);
6640f10154cSschwarze break;
6650f10154cSschwarze case ARG_NAME:
666dc1f54e8Sschwarze fputs("usage: man [-acfhklw] [-C file] [-M path] "
667dc1f54e8Sschwarze "[-m path] [-S subsection]\n"
668dc1f54e8Sschwarze "\t [[-s] section] name ...\n", stderr);
6690f10154cSschwarze break;
6700f10154cSschwarze case ARG_WORD:
671dc1f54e8Sschwarze fputs("usage: whatis [-afk] [-C file] "
672db8b2ff0Sschwarze "[-M path] [-m path] [-O outkey] [-S arch]\n"
673db8b2ff0Sschwarze "\t [-s section] name ...\n", stderr);
6740f10154cSschwarze break;
6750f10154cSschwarze case ARG_EXPR:
676dc1f54e8Sschwarze fputs("usage: apropos [-afk] [-C file] "
677db8b2ff0Sschwarze "[-M path] [-m path] [-O outkey] [-S arch]\n"
6780f10154cSschwarze "\t [-s section] expression ...\n", stderr);
6790f10154cSschwarze break;
6800f10154cSschwarze }
681b5fbc6a9Sschwarze exit((int)MANDOCLEVEL_BADARG);
682f73abda9Skristaps }
683f73abda9Skristaps
684e8a031dcSschwarze static void
glob_esc(char ** dst,const char * src,const char * suffix)685e8a031dcSschwarze glob_esc(char **dst, const char *src, const char *suffix)
686e8a031dcSschwarze {
687e8a031dcSschwarze while (*src != '\0') {
688e8a031dcSschwarze if (strchr("*?[", *src) != NULL)
689e8a031dcSschwarze *(*dst)++ = '\\';
690e8a031dcSschwarze *(*dst)++ = *src++;
691e8a031dcSschwarze }
692e8a031dcSschwarze while (*suffix != '\0')
693e8a031dcSschwarze *(*dst)++ = *suffix++;
694e8a031dcSschwarze }
695e8a031dcSschwarze
6967db69220Sschwarze static void
fs_append(char ** file,size_t filesz,int copy,size_t ipath,const char * sec,enum form form,struct manpage ** res,size_t * ressz)6977db69220Sschwarze fs_append(char **file, size_t filesz, int copy, size_t ipath,
6987db69220Sschwarze const char *sec, enum form form, struct manpage **res, size_t *ressz)
6997db69220Sschwarze {
7007db69220Sschwarze struct manpage *page;
7017db69220Sschwarze
7027db69220Sschwarze *res = mandoc_reallocarray(*res, *ressz + filesz, sizeof(**res));
7037db69220Sschwarze page = *res + *ressz;
7047db69220Sschwarze *ressz += filesz;
7057db69220Sschwarze for (;;) {
7067db69220Sschwarze page->file = copy ? mandoc_strdup(*file) : *file;
7077db69220Sschwarze page->names = NULL;
7087db69220Sschwarze page->output = NULL;
7097db69220Sschwarze page->bits = NAME_FILE & NAME_MASK;
7107db69220Sschwarze page->ipath = ipath;
7117db69220Sschwarze page->sec = (*sec >= '1' && *sec <= '9') ? *sec - '1' + 1 : 10;
7127db69220Sschwarze page->form = form;
7137db69220Sschwarze if (--filesz == 0)
7147db69220Sschwarze break;
7157db69220Sschwarze file++;
7167db69220Sschwarze page++;
7177db69220Sschwarze }
7187db69220Sschwarze }
7197db69220Sschwarze
720c5921aadSschwarze static int
fs_lookup(const struct manpaths * paths,size_t ipath,const char * sec,const char * arch,const char * name,struct manpage ** res,size_t * ressz)721c5921aadSschwarze fs_lookup(const struct manpaths *paths, size_t ipath,
722c5921aadSschwarze const char *sec, const char *arch, const char *name,
723c5921aadSschwarze struct manpage **res, size_t *ressz)
724c5921aadSschwarze {
725a231508dSschwarze struct stat sb;
726e8e3ce36Sschwarze glob_t globinfo;
7277db69220Sschwarze char *file, *cp, secnum[2];
728ff2dbb0fSschwarze int globres;
729ff2dbb0fSschwarze enum form form;
730c5921aadSschwarze
731e8a031dcSschwarze const char *const slman = "/man";
732e8a031dcSschwarze const char *const slash = "/";
733e8a031dcSschwarze const char *const sglob = ".[01-9]*";
7347db69220Sschwarze const char *const dot = ".";
7357db69220Sschwarze const char *const aster = "*";
736e8a031dcSschwarze
7377db69220Sschwarze memset(&globinfo, 0, sizeof(globinfo));
738e8e3ce36Sschwarze form = FORM_SRC;
7397db69220Sschwarze
740c5921aadSschwarze mandoc_asprintf(&file, "%s/man%s/%s.%s",
741c5921aadSschwarze paths->paths[ipath], sec, name, sec);
742a231508dSschwarze if (stat(file, &sb) != -1)
743c5921aadSschwarze goto found;
744c5921aadSschwarze free(file);
745c5921aadSschwarze
746c5921aadSschwarze mandoc_asprintf(&file, "%s/cat%s/%s.0",
747c5921aadSschwarze paths->paths[ipath], sec, name);
748a231508dSschwarze if (stat(file, &sb) != -1) {
749c5921aadSschwarze form = FORM_CAT;
750c5921aadSschwarze goto found;
751c5921aadSschwarze }
752c5921aadSschwarze free(file);
753c5921aadSschwarze
754c5921aadSschwarze if (arch != NULL) {
755c5921aadSschwarze mandoc_asprintf(&file, "%s/man%s/%s/%s.%s",
756c5921aadSschwarze paths->paths[ipath], sec, arch, name, sec);
757a231508dSschwarze if (stat(file, &sb) != -1)
758c5921aadSschwarze goto found;
759c5921aadSschwarze free(file);
760c5921aadSschwarze }
761e8e3ce36Sschwarze
762e8a031dcSschwarze cp = file = mandoc_malloc(strlen(paths->paths[ipath]) * 2 +
763e8a031dcSschwarze strlen(slman) + strlen(sec) * 2 + strlen(slash) +
764e8a031dcSschwarze strlen(name) * 2 + strlen(sglob) + 1);
765e8a031dcSschwarze glob_esc(&cp, paths->paths[ipath], slman);
766e8a031dcSschwarze glob_esc(&cp, sec, slash);
767e8a031dcSschwarze glob_esc(&cp, name, sglob);
768e8a031dcSschwarze *cp = '\0';
769e8e3ce36Sschwarze globres = glob(file, 0, NULL, &globinfo);
770e8e3ce36Sschwarze if (globres != 0 && globres != GLOB_NOMATCH)
771ecd1ed85Sschwarze mandoc_msg(MANDOCERR_GLOB, 0, 0,
772ecd1ed85Sschwarze "%s: %s", file, strerror(errno));
773e8e3ce36Sschwarze free(file);
7747db69220Sschwarze file = NULL;
775e8e3ce36Sschwarze if (globres == 0)
776ad164ce6Sschwarze goto found;
7777db69220Sschwarze globfree(&globinfo);
7787db69220Sschwarze
7797db69220Sschwarze if (sec[1] != '\0' && *ressz == 0) {
7807db69220Sschwarze secnum[0] = sec[0];
7817db69220Sschwarze secnum[1] = '\0';
7827db69220Sschwarze cp = file = mandoc_malloc(strlen(paths->paths[ipath]) * 2 +
7837db69220Sschwarze strlen(slman) + strlen(secnum) * 2 + strlen(slash) +
7847db69220Sschwarze strlen(name) * 2 + strlen(dot) +
7857db69220Sschwarze strlen(sec) * 2 + strlen(aster) + 1);
7867db69220Sschwarze glob_esc(&cp, paths->paths[ipath], slman);
7877db69220Sschwarze glob_esc(&cp, secnum, slash);
7887db69220Sschwarze glob_esc(&cp, name, dot);
7897db69220Sschwarze glob_esc(&cp, sec, aster);
7907db69220Sschwarze *cp = '\0';
7917db69220Sschwarze globres = glob(file, 0, NULL, &globinfo);
7927db69220Sschwarze if (globres != 0 && globres != GLOB_NOMATCH)
7937db69220Sschwarze mandoc_msg(MANDOCERR_GLOB, 0, 0,
7947db69220Sschwarze "%s: %s", file, strerror(errno));
795a231508dSschwarze free(file);
7967db69220Sschwarze file = NULL;
7977db69220Sschwarze if (globres == 0)
7987db69220Sschwarze goto found;
7997db69220Sschwarze globfree(&globinfo);
800a231508dSschwarze }
8017db69220Sschwarze
802ad164ce6Sschwarze if (res != NULL || ipath + 1 != paths->sz)
803ecd1ed85Sschwarze return -1;
804c5921aadSschwarze
805ad164ce6Sschwarze mandoc_asprintf(&file, "%s.%s", name, sec);
806a231508dSschwarze globres = stat(file, &sb);
807ad164ce6Sschwarze free(file);
808ecd1ed85Sschwarze return globres;
809ad164ce6Sschwarze
810c5921aadSschwarze found:
811ecf32ec4Sschwarze warnx("outdated mandoc.db lacks %s(%s) entry, run %s %s",
812ecf32ec4Sschwarze name, sec, BINM_MAKEWHATIS, paths->paths[ipath]);
8137db69220Sschwarze if (res == NULL)
814ad164ce6Sschwarze free(file);
8157db69220Sschwarze else if (file == NULL)
8167db69220Sschwarze fs_append(globinfo.gl_pathv, globinfo.gl_pathc, 1,
8177db69220Sschwarze ipath, sec, form, res, ressz);
8187db69220Sschwarze else
8197db69220Sschwarze fs_append(&file, 1, 0, ipath, sec, form, res, ressz);
8207db69220Sschwarze globfree(&globinfo);
821ecd1ed85Sschwarze return 0;
822c5921aadSschwarze }
823c5921aadSschwarze
82410e17f8fSschwarze static int
fs_search(const struct mansearch * cfg,const struct manpaths * paths,const char * name,struct manpage ** res,size_t * ressz)825c5921aadSschwarze fs_search(const struct mansearch *cfg, const struct manpaths *paths,
826bc6b7f6fSschwarze const char *name, struct manpage **res, size_t *ressz)
827c5921aadSschwarze {
828c5921aadSschwarze const char *const sections[] =
829598b66e2Sschwarze {"1", "8", "6", "2", "3", "5", "7", "4", "9", "3p"};
830c5921aadSschwarze const size_t nsec = sizeof(sections)/sizeof(sections[0]);
831c5921aadSschwarze
832bc6b7f6fSschwarze size_t ipath, isec;
833c5921aadSschwarze
834c5921aadSschwarze assert(cfg->argmode == ARG_NAME);
83510e17f8fSschwarze if (res != NULL)
836c5921aadSschwarze *res = NULL;
837bc6b7f6fSschwarze *ressz = 0;
838c5921aadSschwarze for (ipath = 0; ipath < paths->sz; ipath++) {
839c5921aadSschwarze if (cfg->sec != NULL) {
840bc6b7f6fSschwarze if (fs_lookup(paths, ipath, cfg->sec, cfg->arch,
841bc6b7f6fSschwarze name, res, ressz) != -1 && cfg->firstmatch)
842ecd1ed85Sschwarze return 0;
843bc6b7f6fSschwarze } else {
844bc6b7f6fSschwarze for (isec = 0; isec < nsec; isec++)
845c5921aadSschwarze if (fs_lookup(paths, ipath, sections[isec],
846bc6b7f6fSschwarze cfg->arch, name, res, ressz) != -1 &&
847c5921aadSschwarze cfg->firstmatch)
848ecd1ed85Sschwarze return 0;
849c5921aadSschwarze }
850c5921aadSschwarze }
851ecd1ed85Sschwarze return -1;
852c5921aadSschwarze }
853c5921aadSschwarze
8549f39971fSschwarze static void
process_onefile(struct mparse * mp,struct manpage * resp,int startdir,struct outstate * outst,struct manconf * conf)85573f693efSschwarze process_onefile(struct mparse *mp, struct manpage *resp, int startdir,
85673f693efSschwarze struct outstate *outst, struct manconf *conf)
85773f693efSschwarze {
85873f693efSschwarze int fd;
85973f693efSschwarze
86073f693efSschwarze /*
86173f693efSschwarze * Changing directories is not needed in ARG_FILE mode.
86273f693efSschwarze * Do it on a best-effort basis. Even in case of
86373f693efSschwarze * failure, some functionality may still work.
86473f693efSschwarze */
86573f693efSschwarze if (resp->ipath != SIZE_MAX)
86673f693efSschwarze (void)chdir(conf->manpath.paths[resp->ipath]);
86773f693efSschwarze else if (startdir != -1)
86873f693efSschwarze (void)fchdir(startdir);
86973f693efSschwarze
87073f693efSschwarze mandoc_msg_setinfilename(resp->file);
87173f693efSschwarze if (resp->file != NULL) {
87273f693efSschwarze if ((fd = mparse_open(mp, resp->file)) == -1) {
87373f693efSschwarze mandoc_msg(resp->ipath == SIZE_MAX ?
87473f693efSschwarze MANDOCERR_BADARG_BAD : MANDOCERR_OPEN,
87573f693efSschwarze 0, 0, "%s", strerror(errno));
87673f693efSschwarze mandoc_msg_setinfilename(NULL);
87773f693efSschwarze return;
87873f693efSschwarze }
87973f693efSschwarze } else
88073f693efSschwarze fd = STDIN_FILENO;
88173f693efSschwarze
88273f693efSschwarze if (outst->use_pager) {
88373f693efSschwarze outst->use_pager = 0;
88490f584c6Sschwarze outst->tag_files = term_tag_init(conf->output.outfilename,
885695084adSkn outst->outtype == OUTT_HTML ? ".html" : "",
88690f584c6Sschwarze conf->output.tagfilename);
88790f584c6Sschwarze if ((conf->output.outfilename != NULL ||
88890f584c6Sschwarze conf->output.tagfilename != NULL) &&
88990f584c6Sschwarze pledge("stdio rpath cpath", NULL) == -1) {
89090f584c6Sschwarze mandoc_msg(MANDOCERR_PLEDGE, 0, 0,
89190f584c6Sschwarze "%s", strerror(errno));
89290f584c6Sschwarze exit(mandoc_msg_getrc());
89390f584c6Sschwarze }
89473f693efSschwarze }
8954826a235Sschwarze if (outst->had_output && outst->outtype <= OUTT_UTF8) {
89673f693efSschwarze if (outst->outdata == NULL)
89773f693efSschwarze outdata_alloc(outst, &conf->output);
89873f693efSschwarze terminal_sepline(outst->outdata);
89973f693efSschwarze }
90073f693efSschwarze
90173f693efSschwarze if (resp->form == FORM_SRC)
90228ca454fSschwarze parse(mp, fd, resp->file, outst, conf);
90373f693efSschwarze else {
90473f693efSschwarze passthrough(fd, conf->output.synopsisonly);
90573f693efSschwarze outst->had_output = 1;
90673f693efSschwarze }
90773f693efSschwarze
90873f693efSschwarze if (ferror(stdout)) {
90973f693efSschwarze if (outst->tag_files != NULL) {
91073f693efSschwarze mandoc_msg(MANDOCERR_WRITE, 0, 0, "%s: %s",
91173f693efSschwarze outst->tag_files->ofn, strerror(errno));
9120ac7e6ecSschwarze term_tag_unlink();
91373f693efSschwarze outst->tag_files = NULL;
91473f693efSschwarze } else
91573f693efSschwarze mandoc_msg(MANDOCERR_WRITE, 0, 0, "%s",
91673f693efSschwarze strerror(errno));
91773f693efSschwarze }
91873f693efSschwarze mandoc_msg_setinfilename(NULL);
91973f693efSschwarze }
92073f693efSschwarze
92173f693efSschwarze static void
parse(struct mparse * mp,int fd,const char * file,struct outstate * outst,struct manconf * conf)9223f735e33Sschwarze parse(struct mparse *mp, int fd, const char *file,
92328ca454fSschwarze struct outstate *outst, struct manconf *conf)
924f73abda9Skristaps {
92528ca454fSschwarze static struct manpaths basepaths;
92673f693efSschwarze static int previous;
9276b86842eSschwarze struct roff_meta *meta;
928f73abda9Skristaps
9298dec91daSflorian assert(fd >= 0);
93073f693efSschwarze if (file == NULL)
93173f693efSschwarze file = "<stdin>";
93273f693efSschwarze
93373f693efSschwarze if (previous)
93473f693efSschwarze mparse_reset(mp);
93573f693efSschwarze else
93673f693efSschwarze previous = 1;
937e7b389f9Sschwarze
938d8e30e1dSschwarze mparse_readfd(mp, fd, file);
9397a6e7816Sschwarze if (fd != STDIN_FILENO)
9407a6e7816Sschwarze close(fd);
941f73abda9Skristaps
94287eef1c7Sschwarze /*
943a35fc07aSschwarze * With -Wstop and warnings or errors of at least the requested
944a35fc07aSschwarze * level, do not produce output.
94587eef1c7Sschwarze */
94687eef1c7Sschwarze
9473f735e33Sschwarze if (outst->wstop && mandoc_msg_getrc() != MANDOCLEVEL_OK)
94824ec91ddSschwarze return;
94987eef1c7Sschwarze
9503f735e33Sschwarze if (outst->outdata == NULL)
95128ca454fSschwarze outdata_alloc(outst, &conf->output);
9523f735e33Sschwarze else if (outst->outtype == OUTT_HTML)
953e1292dcdSotto html_reset(outst->outdata);
954f73abda9Skristaps
9556b86842eSschwarze mandoc_xr_reset();
956d8e30e1dSschwarze meta = mparse_result(mp);
957a35fc07aSschwarze
958f73abda9Skristaps /* Execute the out device, if it exists. */
959f73abda9Skristaps
96073f693efSschwarze outst->had_output = 1;
9616b86842eSschwarze if (meta->macroset == MACROSET_MDOC) {
9623f735e33Sschwarze switch (outst->outtype) {
963b68ff58bSschwarze case OUTT_HTML:
9643f735e33Sschwarze html_mdoc(outst->outdata, meta);
965b68ff58bSschwarze break;
966b68ff58bSschwarze case OUTT_TREE:
9673f735e33Sschwarze tree_mdoc(outst->outdata, meta);
968b68ff58bSschwarze break;
969b68ff58bSschwarze case OUTT_MAN:
9703f735e33Sschwarze man_mdoc(outst->outdata, meta);
971b68ff58bSschwarze break;
972b68ff58bSschwarze case OUTT_PDF:
973b68ff58bSschwarze case OUTT_ASCII:
974b68ff58bSschwarze case OUTT_UTF8:
975b68ff58bSschwarze case OUTT_LOCALE:
976b68ff58bSschwarze case OUTT_PS:
9773f735e33Sschwarze terminal_mdoc(outst->outdata, meta);
978b68ff58bSschwarze break;
979b3257404Sschwarze case OUTT_MARKDOWN:
9803f735e33Sschwarze markdown_mdoc(outst->outdata, meta);
981b3257404Sschwarze break;
982b68ff58bSschwarze default:
983b68ff58bSschwarze break;
984b68ff58bSschwarze }
985b68ff58bSschwarze }
9866b86842eSschwarze if (meta->macroset == MACROSET_MAN) {
9873f735e33Sschwarze switch (outst->outtype) {
988b68ff58bSschwarze case OUTT_HTML:
9893f735e33Sschwarze html_man(outst->outdata, meta);
990b68ff58bSschwarze break;
991b68ff58bSschwarze case OUTT_TREE:
9923f735e33Sschwarze tree_man(outst->outdata, meta);
993b68ff58bSschwarze break;
994b68ff58bSschwarze case OUTT_MAN:
995d8e30e1dSschwarze mparse_copy(mp);
996b68ff58bSschwarze break;
997b68ff58bSschwarze case OUTT_PDF:
998b68ff58bSschwarze case OUTT_ASCII:
999b68ff58bSschwarze case OUTT_UTF8:
1000b68ff58bSschwarze case OUTT_LOCALE:
1001b68ff58bSschwarze case OUTT_PS:
10023f735e33Sschwarze terminal_man(outst->outdata, meta);
1003b68ff58bSschwarze break;
1004a28ef4e8Sschwarze case OUTT_MARKDOWN:
1005a28ef4e8Sschwarze mandoc_msg(MANDOCERR_MAN_TMARKDOWN, 0, 0, NULL);
1006a28ef4e8Sschwarze break;
1007b68ff58bSschwarze default:
1008b68ff58bSschwarze break;
1009b68ff58bSschwarze }
1010b68ff58bSschwarze }
101128ca454fSschwarze if (conf->output.tag != NULL && conf->output.tag_found == 0 &&
101228ca454fSschwarze tag_exists(conf->output.tag))
101328ca454fSschwarze conf->output.tag_found = 1;
101428ca454fSschwarze
101528ca454fSschwarze if (mandoc_msg_getmin() < MANDOCERR_STYLE) {
101628ca454fSschwarze if (basepaths.sz == 0)
101728ca454fSschwarze manpath_base(&basepaths);
101828ca454fSschwarze check_xr(&basepaths);
101928ca454fSschwarze } else if (mandoc_msg_getmin() < MANDOCERR_WARNING)
102028ca454fSschwarze check_xr(&conf->manpath);
1021f73abda9Skristaps }
1022f73abda9Skristaps
102324ec91ddSschwarze static void
check_xr(struct manpaths * paths)102428ca454fSschwarze check_xr(struct manpaths *paths)
102519b6bef7Sschwarze {
102619b6bef7Sschwarze struct mansearch search;
102719b6bef7Sschwarze struct mandoc_xr *xr;
102819b6bef7Sschwarze size_t sz;
102919b6bef7Sschwarze
103019b6bef7Sschwarze for (xr = mandoc_xr_get(); xr != NULL; xr = xr->next) {
103152d11c96Sschwarze if (xr->line == -1)
103252d11c96Sschwarze continue;
103319b6bef7Sschwarze search.arch = NULL;
103419b6bef7Sschwarze search.sec = xr->sec;
103519b6bef7Sschwarze search.outkey = NULL;
103619b6bef7Sschwarze search.argmode = ARG_NAME;
103719b6bef7Sschwarze search.firstmatch = 1;
103828ca454fSschwarze if (mansearch(&search, paths, 1, &xr->name, NULL, &sz))
103919b6bef7Sschwarze continue;
104028ca454fSschwarze if (fs_search(&search, paths, xr->name, NULL, &sz) != -1)
104110e17f8fSschwarze continue;
1042bff2a0c3Sschwarze if (xr->count == 1)
1043a5a5f808Sschwarze mandoc_msg(MANDOCERR_XR_BAD, xr->line,
1044a5a5f808Sschwarze xr->pos + 1, "Xr %s %s", xr->name, xr->sec);
1045bff2a0c3Sschwarze else
1046a5a5f808Sschwarze mandoc_msg(MANDOCERR_XR_BAD, xr->line,
1047a5a5f808Sschwarze xr->pos + 1, "Xr %s %s (%d times)",
1048bff2a0c3Sschwarze xr->name, xr->sec, xr->count);
104919b6bef7Sschwarze }
105019b6bef7Sschwarze }
105119b6bef7Sschwarze
105219b6bef7Sschwarze static void
outdata_alloc(struct outstate * outst,struct manoutput * outconf)10533f735e33Sschwarze outdata_alloc(struct outstate *outst, struct manoutput *outconf)
1054fa64596dSschwarze {
10553f735e33Sschwarze switch (outst->outtype) {
1056fa64596dSschwarze case OUTT_HTML:
10573f735e33Sschwarze outst->outdata = html_alloc(outconf);
1058fa64596dSschwarze break;
1059fa64596dSschwarze case OUTT_UTF8:
10603f735e33Sschwarze outst->outdata = utf8_alloc(outconf);
1061fa64596dSschwarze break;
1062fa64596dSschwarze case OUTT_LOCALE:
10633f735e33Sschwarze outst->outdata = locale_alloc(outconf);
1064fa64596dSschwarze break;
1065fa64596dSschwarze case OUTT_ASCII:
10663f735e33Sschwarze outst->outdata = ascii_alloc(outconf);
1067fa64596dSschwarze break;
1068fa64596dSschwarze case OUTT_PDF:
10693f735e33Sschwarze outst->outdata = pdf_alloc(outconf);
1070fa64596dSschwarze break;
1071fa64596dSschwarze case OUTT_PS:
10723f735e33Sschwarze outst->outdata = ps_alloc(outconf);
1073fa64596dSschwarze break;
1074fa64596dSschwarze default:
1075fa64596dSschwarze break;
1076fa64596dSschwarze }
1077fa64596dSschwarze }
1078fa64596dSschwarze
1079fa64596dSschwarze static void
passthrough(int fd,int synopsis_only)1080ecd1ed85Sschwarze passthrough(int fd, int synopsis_only)
10810f10154cSschwarze {
10820ac900dfSschwarze const char synb[] = "S\bSY\bYN\bNO\bOP\bPS\bSI\bIS\bS";
10830ac900dfSschwarze const char synr[] = "SYNOPSIS";
10840f10154cSschwarze
10850ac900dfSschwarze FILE *stream;
108631f93c25Sschwarze char *line, *cp;
108731f93c25Sschwarze size_t linesz;
108834b4fe63Sschwarze ssize_t len, written;
1089ecd1ed85Sschwarze int lno, print;
10900ac900dfSschwarze
1091ecd1ed85Sschwarze stream = NULL;
109231f93c25Sschwarze line = NULL;
109331f93c25Sschwarze linesz = 0;
10942b036407Sschwarze
109534b4fe63Sschwarze if (fflush(stdout) == EOF) {
1096ecd1ed85Sschwarze mandoc_msg(MANDOCERR_FFLUSH, 0, 0, "%s", strerror(errno));
1097ecd1ed85Sschwarze goto done;
109834b4fe63Sschwarze }
10990ac900dfSschwarze if ((stream = fdopen(fd, "r")) == NULL) {
1100a53fa1e1Sschwarze close(fd);
1101ecd1ed85Sschwarze mandoc_msg(MANDOCERR_FDOPEN, 0, 0, "%s", strerror(errno));
1102ecd1ed85Sschwarze goto done;
11030f10154cSschwarze }
11040f10154cSschwarze
1105ecd1ed85Sschwarze lno = print = 0;
110634b4fe63Sschwarze while ((len = getline(&line, &linesz, stream)) != -1) {
1107ecd1ed85Sschwarze lno++;
110831f93c25Sschwarze cp = line;
11090ac900dfSschwarze if (synopsis_only) {
11100ac900dfSschwarze if (print) {
111131f93c25Sschwarze if ( ! isspace((unsigned char)*cp))
11120ac900dfSschwarze goto done;
111334b4fe63Sschwarze while (isspace((unsigned char)*cp)) {
111431f93c25Sschwarze cp++;
111534b4fe63Sschwarze len--;
111634b4fe63Sschwarze }
11170ac900dfSschwarze } else {
111831f93c25Sschwarze if (strcmp(cp, synb) == 0 ||
111931f93c25Sschwarze strcmp(cp, synr) == 0)
11200ac900dfSschwarze print = 1;
11210ac900dfSschwarze continue;
11220ac900dfSschwarze }
11230ac900dfSschwarze }
112434b4fe63Sschwarze for (; len > 0; len -= written) {
1125ecd1ed85Sschwarze if ((written = write(STDOUT_FILENO, cp, len)) == -1) {
1126ecd1ed85Sschwarze mandoc_msg(MANDOCERR_WRITE, 0, 0,
1127ecd1ed85Sschwarze "%s", strerror(errno));
1128ecd1ed85Sschwarze goto done;
11290ac900dfSschwarze }
11300ac900dfSschwarze }
11310ac900dfSschwarze }
1132ecd1ed85Sschwarze if (ferror(stream))
1133ecd1ed85Sschwarze mandoc_msg(MANDOCERR_GETLINE, lno, 0, "%s", strerror(errno));
11340ac900dfSschwarze
11350ac900dfSschwarze done:
113631f93c25Sschwarze free(line);
1137ecd1ed85Sschwarze if (stream != NULL)
11380ac900dfSschwarze fclose(stream);
1139f73abda9Skristaps }
1140f73abda9Skristaps
1141f73abda9Skristaps static int
woptions(char * arg,enum mandoc_os * os_e,int * wstop)1142d8e30e1dSschwarze woptions(char *arg, enum mandoc_os *os_e, int *wstop)
1143f73abda9Skristaps {
1144e6b67f92Sschwarze char *v, *o;
1145f3476b07Sschwarze const char *toks[11];
1146f73abda9Skristaps
1147cfd2bfaaSschwarze toks[0] = "stop";
1148cfd2bfaaSschwarze toks[1] = "all";
1149f3476b07Sschwarze toks[2] = "base";
1150f3476b07Sschwarze toks[3] = "style";
1151f3476b07Sschwarze toks[4] = "warning";
1152f3476b07Sschwarze toks[5] = "error";
1153f3476b07Sschwarze toks[6] = "unsupp";
1154f3476b07Sschwarze toks[7] = "fatal";
1155f3476b07Sschwarze toks[8] = "openbsd";
1156f3476b07Sschwarze toks[9] = "netbsd";
1157f3476b07Sschwarze toks[10] = NULL;
1158f73abda9Skristaps
1159e6b67f92Sschwarze while (*arg) {
1160e6b67f92Sschwarze o = arg;
11615f1e3782Sschwarze switch (getsubopt(&arg, (char * const *)toks, &v)) {
116249aff9f8Sschwarze case 0:
1163d8e30e1dSschwarze *wstop = 1;
1164f73abda9Skristaps break;
116549aff9f8Sschwarze case 1:
116649aff9f8Sschwarze case 2:
1167e501e731Sschwarze mandoc_msg_setmin(MANDOCERR_BASE);
1168f73abda9Skristaps break;
116949aff9f8Sschwarze case 3:
1170e501e731Sschwarze mandoc_msg_setmin(MANDOCERR_STYLE);
1171888b3a74Sschwarze break;
117249aff9f8Sschwarze case 4:
1173e501e731Sschwarze mandoc_msg_setmin(MANDOCERR_WARNING);
1174d04ca39fSschwarze break;
1175d04ca39fSschwarze case 5:
1176e501e731Sschwarze mandoc_msg_setmin(MANDOCERR_ERROR);
11770077f574Sschwarze break;
11780077f574Sschwarze case 6:
1179e501e731Sschwarze mandoc_msg_setmin(MANDOCERR_UNSUPP);
1180f3476b07Sschwarze break;
1181f3476b07Sschwarze case 7:
1182ecd1ed85Sschwarze mandoc_msg_setmin(MANDOCERR_BADARG);
1183f3476b07Sschwarze break;
1184f3476b07Sschwarze case 8:
1185e501e731Sschwarze mandoc_msg_setmin(MANDOCERR_BASE);
1186d8e30e1dSschwarze *os_e = MANDOC_OS_OPENBSD;
1187f3476b07Sschwarze break;
1188f3476b07Sschwarze case 9:
1189e501e731Sschwarze mandoc_msg_setmin(MANDOCERR_BASE);
1190d8e30e1dSschwarze *os_e = MANDOC_OS_NETBSD;
1191a66b65d0Sschwarze break;
1192f73abda9Skristaps default:
1193ecd1ed85Sschwarze mandoc_msg(MANDOCERR_BADARG_BAD, 0, 0, "-W %s", o);
1194ecd1ed85Sschwarze return -1;
1195ecd1ed85Sschwarze }
1196ecd1ed85Sschwarze }
1197526e306bSschwarze return 0;
1198f73abda9Skristaps }
1199f73abda9Skristaps
12003cd418b7Sschwarze /*
12013cd418b7Sschwarze * Wait until moved to the foreground,
12023cd418b7Sschwarze * then fork the pager and wait for the user to close it.
12033cd418b7Sschwarze */
12043cd418b7Sschwarze static void
run_pager(struct outstate * outst,char * tag_target)1205cdecd8e7Sschwarze run_pager(struct outstate *outst, char *tag_target)
12063cd418b7Sschwarze {
12073cd418b7Sschwarze int signum, status;
12083cd418b7Sschwarze pid_t man_pgid, tc_pgid;
12093cd418b7Sschwarze pid_t pager_pid, wait_pid;
12103cd418b7Sschwarze
12113cd418b7Sschwarze man_pgid = getpgid(0);
1212cdecd8e7Sschwarze outst->tag_files->tcpgid =
1213cdecd8e7Sschwarze man_pgid == getpid() ? getpgid(getppid()) : man_pgid;
12143cd418b7Sschwarze pager_pid = 0;
12153cd418b7Sschwarze signum = SIGSTOP;
12163cd418b7Sschwarze
12173cd418b7Sschwarze for (;;) {
12183cd418b7Sschwarze /* Stop here until moved to the foreground. */
12193cd418b7Sschwarze
1220beabc24cSschwarze tc_pgid = tcgetpgrp(STDOUT_FILENO);
12213cd418b7Sschwarze if (tc_pgid != man_pgid) {
12223cd418b7Sschwarze if (tc_pgid == pager_pid) {
1223beabc24cSschwarze (void)tcsetpgrp(STDOUT_FILENO, man_pgid);
12243cd418b7Sschwarze if (signum == SIGTTIN)
12253cd418b7Sschwarze continue;
12263cd418b7Sschwarze } else
1227cdecd8e7Sschwarze outst->tag_files->tcpgid = tc_pgid;
12283cd418b7Sschwarze kill(0, signum);
12293cd418b7Sschwarze continue;
12303cd418b7Sschwarze }
12313cd418b7Sschwarze
12323cd418b7Sschwarze /* Once in the foreground, activate the pager. */
12333cd418b7Sschwarze
12343cd418b7Sschwarze if (pager_pid) {
1235beabc24cSschwarze (void)tcsetpgrp(STDOUT_FILENO, pager_pid);
12363cd418b7Sschwarze kill(pager_pid, SIGCONT);
12373cd418b7Sschwarze } else
1238cdecd8e7Sschwarze pager_pid = spawn_pager(outst, tag_target);
12393cd418b7Sschwarze
12403cd418b7Sschwarze /* Wait for the pager to stop or exit. */
12413cd418b7Sschwarze
12423cd418b7Sschwarze while ((wait_pid = waitpid(pager_pid, &status,
12433cd418b7Sschwarze WUNTRACED)) == -1 && errno == EINTR)
12443cd418b7Sschwarze continue;
12453cd418b7Sschwarze
12463cd418b7Sschwarze if (wait_pid == -1) {
12473cd418b7Sschwarze mandoc_msg(MANDOCERR_WAIT, 0, 0,
12483cd418b7Sschwarze "%s", strerror(errno));
12493cd418b7Sschwarze break;
12503cd418b7Sschwarze }
12513cd418b7Sschwarze if (!WIFSTOPPED(status))
12523cd418b7Sschwarze break;
12533cd418b7Sschwarze
12543cd418b7Sschwarze signum = WSTOPSIG(status);
12553cd418b7Sschwarze }
12563cd418b7Sschwarze }
12573cd418b7Sschwarze
1258cad82a00Sschwarze static pid_t
spawn_pager(struct outstate * outst,char * tag_target)1259cdecd8e7Sschwarze spawn_pager(struct outstate *outst, char *tag_target)
12600f10154cSschwarze {
1261d82a663dSschwarze const struct timespec timeout = { 0, 100000000 }; /* 0.1s */
12620f10154cSschwarze #define MAX_PAGER_ARGS 16
12630f10154cSschwarze char *argv[MAX_PAGER_ARGS];
12640f10154cSschwarze const char *pager;
12650f10154cSschwarze char *cp;
12660ed293fbSschwarze size_t wordlen;
1267c0a657b3Sschwarze size_t cmdlen;
1268d17f6067Sschwarze int argc, use_ofn;
1269cad82a00Sschwarze pid_t pager_pid;
12700f10154cSschwarze
1271cdecd8e7Sschwarze assert(outst->tag_files->ofd == -1);
1272cdecd8e7Sschwarze assert(outst->tag_files->tfs == NULL);
1273beabc24cSschwarze
1274c0a657b3Sschwarze pager = getenv("MANPAGER");
1275c0a657b3Sschwarze if (pager == NULL || *pager == '\0')
1276c0a657b3Sschwarze pager = getenv("PAGER");
1277c0a657b3Sschwarze if (pager == NULL || *pager == '\0')
1278c9771691Sschwarze pager = "less";
1279c0a657b3Sschwarze
1280c0a657b3Sschwarze /*
1281c0a657b3Sschwarze * Parse the pager command into words.
1282c0a657b3Sschwarze * Intentionally do not do anything fancy here.
1283c0a657b3Sschwarze */
1284c0a657b3Sschwarze
1285c0a657b3Sschwarze argc = 0;
12860ed293fbSschwarze while (*pager != '\0' && argc + 5 < MAX_PAGER_ARGS) {
12870ed293fbSschwarze wordlen = strcspn(pager, " ");
12880ed293fbSschwarze argv[argc++] = mandoc_strndup(pager, wordlen);
12890ed293fbSschwarze pager += wordlen;
12900ed293fbSschwarze while (*pager == ' ')
12910ed293fbSschwarze pager++;
1292c0a657b3Sschwarze }
1293c0a657b3Sschwarze
12942ae7e873Sschwarze /* For more(1) and less(1), use the tag file. */
1295c0a657b3Sschwarze
1296d17f6067Sschwarze use_ofn = 1;
1297cdecd8e7Sschwarze if (*outst->tag_files->tfn != '\0' &&
1298cdecd8e7Sschwarze (cmdlen = strlen(argv[0])) >= 4) {
1299c0a657b3Sschwarze cp = argv[0] + cmdlen - 4;
13002ae7e873Sschwarze if (strcmp(cp, "less") == 0 || strcmp(cp, "more") == 0) {
1301c0a657b3Sschwarze argv[argc++] = mandoc_strdup("-T");
13020ed293fbSschwarze argv[argc++] = mandoc_strdup(outst->tag_files->tfn);
1303beabc24cSschwarze if (tag_target != NULL) {
1304d17f6067Sschwarze argv[argc++] = mandoc_strdup("-t");
13050ed293fbSschwarze argv[argc++] = mandoc_strdup(tag_target);
1306d17f6067Sschwarze use_ofn = 0;
1307c0a657b3Sschwarze }
13082ae7e873Sschwarze }
1309d17f6067Sschwarze }
1310cdecd8e7Sschwarze if (use_ofn) {
1311cdecd8e7Sschwarze if (outst->outtype == OUTT_HTML && tag_target != NULL)
1312cdecd8e7Sschwarze mandoc_asprintf(&argv[argc], "file://%s#%s",
1313cdecd8e7Sschwarze outst->tag_files->ofn, tag_target);
1314cdecd8e7Sschwarze else
13150ed293fbSschwarze argv[argc] = mandoc_strdup(outst->tag_files->ofn);
1316cdecd8e7Sschwarze argc++;
1317cdecd8e7Sschwarze }
1318c0a657b3Sschwarze argv[argc] = NULL;
1319c0a657b3Sschwarze
1320cad82a00Sschwarze switch (pager_pid = fork()) {
13210f10154cSschwarze case -1:
1322ecd1ed85Sschwarze mandoc_msg(MANDOCERR_FORK, 0, 0, "%s", strerror(errno));
1323ecd1ed85Sschwarze exit(mandoc_msg_getrc());
13240f10154cSschwarze case 0:
1325bb1dcb77Sschwarze break;
1326bb1dcb77Sschwarze default:
13270ed293fbSschwarze while (argc > 0)
13280ed293fbSschwarze free(argv[--argc]);
1329d5c4dcfeSschwarze (void)setpgid(pager_pid, 0);
1330beabc24cSschwarze (void)tcsetpgrp(STDOUT_FILENO, pager_pid);
1331ecd1ed85Sschwarze if (pledge("stdio rpath tmppath tty proc", NULL) == -1) {
1332ecd1ed85Sschwarze mandoc_msg(MANDOCERR_PLEDGE, 0, 0,
1333ecd1ed85Sschwarze "%s", strerror(errno));
1334ecd1ed85Sschwarze exit(mandoc_msg_getrc());
1335ecd1ed85Sschwarze }
1336cdecd8e7Sschwarze outst->tag_files->pager_pid = pager_pid;
1337526e306bSschwarze return pager_pid;
13380f10154cSschwarze }
13390f10154cSschwarze
1340beabc24cSschwarze /*
1341beabc24cSschwarze * The child process becomes the pager.
1342beabc24cSschwarze * Do not start it before controlling the terminal.
1343beabc24cSschwarze */
1344d82a663dSschwarze
1345bbdc5336Sschwarze while (tcgetpgrp(STDOUT_FILENO) != getpid())
1346d82a663dSschwarze nanosleep(&timeout, NULL);
1347d82a663dSschwarze
13480f10154cSschwarze execvp(argv[0], argv);
1349ecd1ed85Sschwarze mandoc_msg(MANDOCERR_EXEC, 0, 0, "%s: %s", argv[0], strerror(errno));
1350ecd1ed85Sschwarze _exit(mandoc_msg_getrc());
13510f10154cSschwarze }
1352