16167eca2Schristos /* Id: main.c,v 1.322 2019/03/06 10:18:58 schwarze Exp */
24154958bSjoerg /*
3fec65c98Schristos * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
46167eca2Schristos * Copyright (c) 2010-2012, 2014-2019 Ingo Schwarze <schwarze@openbsd.org>
5fec65c98Schristos * Copyright (c) 2010 Joerg Sonnenberger <joerg@netbsd.org>
64154958bSjoerg *
74154958bSjoerg * Permission to use, copy, modify, and distribute this software for any
84154958bSjoerg * purpose with or without fee is hereby granted, provided that the above
94154958bSjoerg * copyright notice and this permission notice appear in all copies.
104154958bSjoerg *
11f47368cfSchristos * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
124154958bSjoerg * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13f47368cfSchristos * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
144154958bSjoerg * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
154154958bSjoerg * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
164154958bSjoerg * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
174154958bSjoerg * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
184154958bSjoerg */
19d5e63c8dSjoerg #include "config.h"
20fec65c98Schristos
21fec65c98Schristos #include <sys/types.h>
2214e7489eSchristos #include <sys/ioctl.h>
23fec65c98Schristos #include <sys/param.h> /* MACHINE */
24fec65c98Schristos #include <sys/wait.h>
25d5e63c8dSjoerg
264154958bSjoerg #include <assert.h>
27fec65c98Schristos #include <ctype.h>
28f47368cfSchristos #if HAVE_ERR
29f47368cfSchristos #include <err.h>
30f47368cfSchristos #endif
31fec65c98Schristos #include <errno.h>
32fec65c98Schristos #include <fcntl.h>
33fec65c98Schristos #include <glob.h>
34f47368cfSchristos #if HAVE_SANDBOX_INIT
35f47368cfSchristos #include <sandbox.h>
36f47368cfSchristos #endif
37f47368cfSchristos #include <signal.h>
384154958bSjoerg #include <stdio.h>
394154958bSjoerg #include <stdint.h>
404154958bSjoerg #include <stdlib.h>
414154958bSjoerg #include <string.h>
426167eca2Schristos #include <termios.h>
43f47368cfSchristos #include <time.h>
444154958bSjoerg #include <unistd.h>
454154958bSjoerg
46fec65c98Schristos #include "mandoc_aux.h"
47f47368cfSchristos #include "mandoc.h"
4814e7489eSchristos #include "mandoc_xr.h"
49f47368cfSchristos #include "roff.h"
504154958bSjoerg #include "mdoc.h"
514154958bSjoerg #include "man.h"
526167eca2Schristos #include "mandoc_parse.h"
53f47368cfSchristos #include "tag.h"
54f47368cfSchristos #include "main.h"
55f47368cfSchristos #include "manconf.h"
56fec65c98Schristos #include "mansearch.h"
573514411fSjoerg
58fec65c98Schristos enum outmode {
59fec65c98Schristos OUTMODE_DEF = 0,
60fec65c98Schristos OUTMODE_FLN,
61fec65c98Schristos OUTMODE_LST,
62fec65c98Schristos OUTMODE_ALL,
63fec65c98Schristos OUTMODE_ONE
64fec65c98Schristos };
65fec65c98Schristos
664154958bSjoerg enum outt {
6748741257Sjoerg OUTT_ASCII = 0, /* -Tascii */
68c5f73b34Sjoerg OUTT_LOCALE, /* -Tlocale */
69c5f73b34Sjoerg OUTT_UTF8, /* -Tutf8 */
7048741257Sjoerg OUTT_TREE, /* -Ttree */
71c5f73b34Sjoerg OUTT_MAN, /* -Tman */
7248741257Sjoerg OUTT_HTML, /* -Thtml */
7314e7489eSchristos OUTT_MARKDOWN, /* -Tmarkdown */
7448741257Sjoerg OUTT_LINT, /* -Tlint */
7548741257Sjoerg OUTT_PS, /* -Tps */
7648741257Sjoerg OUTT_PDF /* -Tpdf */
774154958bSjoerg };
784154958bSjoerg
794154958bSjoerg struct curparse {
8048741257Sjoerg struct mparse *mp;
81f47368cfSchristos struct manoutput *outopts; /* output options */
8214e7489eSchristos void *outdata; /* data for output */
8314e7489eSchristos char *os_s; /* operating system for display */
8414e7489eSchristos int wstop; /* stop after a file with a warning */
8514e7489eSchristos enum mandoc_os os_e; /* check base system conventions */
8614e7489eSchristos enum outt outtype; /* which output to use */
874154958bSjoerg };
884154958bSjoerg
8937ef69edSchristos
9037ef69edSchristos int mandocdb(int, char *[]);
9137ef69edSchristos
926167eca2Schristos static void check_xr(void);
93fec65c98Schristos static int fs_lookup(const struct manpaths *,
94fec65c98Schristos size_t ipath, const char *,
95fec65c98Schristos const char *, const char *,
96fec65c98Schristos struct manpage **, size_t *);
9714e7489eSchristos static int fs_search(const struct mansearch *,
98fec65c98Schristos const struct manpaths *, int, char**,
99fec65c98Schristos struct manpage **, size_t *);
100fec65c98Schristos static int koptions(int *, char *);
10114e7489eSchristos static void moptions(int *, char *);
10237ef69edSchristos static void outdata_alloc(struct curparse *);
103f47368cfSchristos static void parse(struct curparse *, int, const char *);
104f47368cfSchristos static void passthrough(const char *, int, int);
105f47368cfSchristos static pid_t spawn_pager(struct tag_files *);
1060a84adc5Sjoerg static int toptions(struct curparse *, char *);
10737ef69edSchristos static void usage(enum argmode) __attribute__((__noreturn__));
108c0d9444aSjoerg static int woptions(struct curparse *, char *);
1094154958bSjoerg
110fec65c98Schristos static const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
111fec65c98Schristos static char help_arg[] = "help";
112fec65c98Schristos static char *help_argv[] = {help_arg, NULL};
1134154958bSjoerg
114fec65c98Schristos
1154154958bSjoerg int
main(int argc,char * argv[])1164154958bSjoerg main(int argc, char *argv[])
1174154958bSjoerg {
118f47368cfSchristos struct manconf conf;
119fec65c98Schristos struct mansearch search;
12037ef69edSchristos struct curparse curp;
12114e7489eSchristos struct winsize ws;
122f47368cfSchristos struct tag_files *tag_files;
123fec65c98Schristos struct manpage *res, *resp;
12437ef69edSchristos const char *progname, *sec, *thisarg;
12537ef69edSchristos char *conf_file, *defpaths, *auxpaths;
1266167eca2Schristos char *oarg, *tagarg;
12737ef69edSchristos unsigned char *uc;
128f47368cfSchristos size_t i, sz;
129f47368cfSchristos int prio, best_prio;
130fec65c98Schristos enum outmode outmode;
13114e7489eSchristos int fd, startdir;
132fec65c98Schristos int show_usage;
133fec65c98Schristos int options;
134f47368cfSchristos int use_pager;
135f47368cfSchristos int status, signum;
136fec65c98Schristos int c;
137f47368cfSchristos pid_t pager_pid, tc_pgid, man_pgid, pid;
1384154958bSjoerg
139f47368cfSchristos #if HAVE_PROGNAME
140f47368cfSchristos progname = getprogname();
141f47368cfSchristos #else
142fec65c98Schristos if (argc < 1)
143f47368cfSchristos progname = mandoc_strdup("mandoc");
144fec65c98Schristos else if ((progname = strrchr(argv[0], '/')) == NULL)
1453514411fSjoerg progname = argv[0];
1463514411fSjoerg else
1473514411fSjoerg ++progname;
148f47368cfSchristos setprogname(progname);
149f47368cfSchristos #endif
1503514411fSjoerg
1516167eca2Schristos mandoc_msg_setoutfile(stderr);
152f47368cfSchristos if (strncmp(progname, "mandocdb", 8) == 0 ||
153f47368cfSchristos strcmp(progname, BINM_MAKEWHATIS) == 0)
154f47368cfSchristos return mandocdb(argc, argv);
155f47368cfSchristos
156f47368cfSchristos #if HAVE_PLEDGE
15714e7489eSchristos if (pledge("stdio rpath tmppath tty proc exec", NULL) == -1)
158f47368cfSchristos err((int)MANDOCLEVEL_SYSERR, "pledge");
159f47368cfSchristos #endif
160f47368cfSchristos
161f47368cfSchristos #if HAVE_SANDBOX_INIT
162f47368cfSchristos if (sandbox_init(kSBXProfileNoInternet, SANDBOX_NAMED, NULL) == -1)
163f47368cfSchristos errx((int)MANDOCLEVEL_SYSERR, "sandbox_init");
164fec65c98Schristos #endif
1654154958bSjoerg
166fec65c98Schristos /* Search options. */
167fec65c98Schristos
168f47368cfSchristos memset(&conf, 0, sizeof(conf));
169fec65c98Schristos conf_file = defpaths = NULL;
170fec65c98Schristos auxpaths = NULL;
171fec65c98Schristos
172fec65c98Schristos memset(&search, 0, sizeof(struct mansearch));
173fec65c98Schristos search.outkey = "Nd";
17437ef69edSchristos oarg = NULL;
175fec65c98Schristos
176fec65c98Schristos if (strcmp(progname, BINM_MAN) == 0)
177fec65c98Schristos search.argmode = ARG_NAME;
178fec65c98Schristos else if (strcmp(progname, BINM_APROPOS) == 0)
179fec65c98Schristos search.argmode = ARG_EXPR;
180fec65c98Schristos else if (strcmp(progname, BINM_WHATIS) == 0)
181fec65c98Schristos search.argmode = ARG_WORD;
182fec65c98Schristos else if (strncmp(progname, "help", 4) == 0)
183fec65c98Schristos search.argmode = ARG_NAME;
184fec65c98Schristos else
185fec65c98Schristos search.argmode = ARG_FILE;
186fec65c98Schristos
187fec65c98Schristos /* Parser and formatter options. */
188fec65c98Schristos
189fec65c98Schristos memset(&curp, 0, sizeof(struct curparse));
190fec65c98Schristos curp.outtype = OUTT_LOCALE;
191f47368cfSchristos curp.outopts = &conf.output;
192fec65c98Schristos options = MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1;
1934154958bSjoerg
194f47368cfSchristos use_pager = 1;
195f47368cfSchristos tag_files = NULL;
196fec65c98Schristos show_usage = 0;
197fec65c98Schristos outmode = OUTMODE_DEF;
198fec65c98Schristos
19914e7489eSchristos while ((c = getopt(argc, argv,
20014e7489eSchristos "aC:cfhI:iK:klM:m:O:S:s:T:VW:w")) != -1) {
20114e7489eSchristos if (c == 'i' && search.argmode == ARG_EXPR) {
20214e7489eSchristos optind--;
20314e7489eSchristos break;
20414e7489eSchristos }
2054154958bSjoerg switch (c) {
206fec65c98Schristos case 'a':
207fec65c98Schristos outmode = OUTMODE_ALL;
208fec65c98Schristos break;
209fec65c98Schristos case 'C':
210fec65c98Schristos conf_file = optarg;
211fec65c98Schristos break;
212fec65c98Schristos case 'c':
213f47368cfSchristos use_pager = 0;
214fec65c98Schristos break;
215fec65c98Schristos case 'f':
216fec65c98Schristos search.argmode = ARG_WORD;
217fec65c98Schristos break;
218fec65c98Schristos case 'h':
219f47368cfSchristos conf.output.synopsisonly = 1;
220f47368cfSchristos use_pager = 0;
221fec65c98Schristos outmode = OUTMODE_ALL;
222fec65c98Schristos break;
223fec65c98Schristos case 'I':
22470f041f9Sjoerg if (strncmp(optarg, "os=", 3)) {
225f47368cfSchristos warnx("-I %s: Bad argument", optarg);
226f47368cfSchristos return (int)MANDOCLEVEL_BADARG;
22770f041f9Sjoerg }
22814e7489eSchristos if (curp.os_s != NULL) {
229f47368cfSchristos warnx("-I %s: Duplicate argument", optarg);
230f47368cfSchristos return (int)MANDOCLEVEL_BADARG;
23170f041f9Sjoerg }
23214e7489eSchristos curp.os_s = mandoc_strdup(optarg + 3);
233fec65c98Schristos break;
234fec65c98Schristos case 'K':
235fec65c98Schristos if ( ! koptions(&options, optarg))
236f47368cfSchristos return (int)MANDOCLEVEL_BADARG;
2374154958bSjoerg break;
238fec65c98Schristos case 'k':
239fec65c98Schristos search.argmode = ARG_EXPR;
240fec65c98Schristos break;
241fec65c98Schristos case 'l':
242fec65c98Schristos search.argmode = ARG_FILE;
243fec65c98Schristos outmode = OUTMODE_ALL;
244fec65c98Schristos break;
245fec65c98Schristos case 'M':
246fec65c98Schristos defpaths = optarg;
247fec65c98Schristos break;
248fec65c98Schristos case 'm':
249fec65c98Schristos auxpaths = optarg;
250fec65c98Schristos break;
251fec65c98Schristos case 'O':
25237ef69edSchristos oarg = optarg;
2534154958bSjoerg break;
254fec65c98Schristos case 'S':
255fec65c98Schristos search.arch = optarg;
256fec65c98Schristos break;
257fec65c98Schristos case 's':
258fec65c98Schristos search.sec = optarg;
259fec65c98Schristos break;
260fec65c98Schristos case 'T':
26131e1f4e3Sjoerg if ( ! toptions(&curp, optarg))
262f47368cfSchristos return (int)MANDOCLEVEL_BADARG;
2634154958bSjoerg break;
264fec65c98Schristos case 'W':
265c0d9444aSjoerg if ( ! woptions(&curp, optarg))
266f47368cfSchristos return (int)MANDOCLEVEL_BADARG;
2674154958bSjoerg break;
268fec65c98Schristos case 'w':
269fec65c98Schristos outmode = OUTMODE_FLN;
270fec65c98Schristos break;
2714154958bSjoerg default:
272fec65c98Schristos show_usage = 1;
273fec65c98Schristos break;
274fec65c98Schristos }
2754154958bSjoerg }
2764154958bSjoerg
277fec65c98Schristos if (show_usage)
278fec65c98Schristos usage(search.argmode);
279fec65c98Schristos
280fec65c98Schristos /* Postprocess options. */
281fec65c98Schristos
282fec65c98Schristos if (outmode == OUTMODE_DEF) {
283fec65c98Schristos switch (search.argmode) {
284fec65c98Schristos case ARG_FILE:
285fec65c98Schristos outmode = OUTMODE_ALL;
286f47368cfSchristos use_pager = 0;
287fec65c98Schristos break;
288fec65c98Schristos case ARG_NAME:
289fec65c98Schristos outmode = OUTMODE_ONE;
290fec65c98Schristos break;
291fec65c98Schristos default:
292fec65c98Schristos outmode = OUTMODE_LST;
293fec65c98Schristos break;
294fec65c98Schristos }
295fec65c98Schristos }
296fec65c98Schristos
29737ef69edSchristos if (oarg != NULL) {
29837ef69edSchristos if (outmode == OUTMODE_LST)
29937ef69edSchristos search.outkey = oarg;
30037ef69edSchristos else {
30137ef69edSchristos while (oarg != NULL) {
30237ef69edSchristos thisarg = oarg;
30337ef69edSchristos if (manconf_output(&conf.output,
30437ef69edSchristos strsep(&oarg, ","), 0) == 0)
30537ef69edSchristos continue;
30637ef69edSchristos warnx("-O %s: Bad argument", thisarg);
30737ef69edSchristos return (int)MANDOCLEVEL_BADARG;
30837ef69edSchristos }
30937ef69edSchristos }
31037ef69edSchristos }
31137ef69edSchristos
3126167eca2Schristos if (curp.outtype != OUTT_TREE || !curp.outopts->noval)
3136167eca2Schristos options |= MPARSE_VALIDATE;
3146167eca2Schristos
315f47368cfSchristos if (outmode == OUTMODE_FLN ||
316f47368cfSchristos outmode == OUTMODE_LST ||
317f47368cfSchristos !isatty(STDOUT_FILENO))
318f47368cfSchristos use_pager = 0;
319f47368cfSchristos
32014e7489eSchristos if (use_pager &&
32114e7489eSchristos (conf.output.width == 0 || conf.output.indent == 0) &&
32214e7489eSchristos ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) != -1 &&
32314e7489eSchristos ws.ws_col > 1) {
32414e7489eSchristos if (conf.output.width == 0 && ws.ws_col < 79)
32514e7489eSchristos conf.output.width = ws.ws_col - 1;
32614e7489eSchristos if (conf.output.indent == 0 && ws.ws_col < 66)
32714e7489eSchristos conf.output.indent = 3;
32814e7489eSchristos }
32914e7489eSchristos
330f47368cfSchristos #if HAVE_PLEDGE
331f47368cfSchristos if (!use_pager)
33214e7489eSchristos if (pledge("stdio rpath", NULL) == -1)
333f47368cfSchristos err((int)MANDOCLEVEL_SYSERR, "pledge");
334f47368cfSchristos #endif
335f47368cfSchristos
336fec65c98Schristos /* Parse arguments. */
337fec65c98Schristos
338fec65c98Schristos if (argc > 0) {
339fec65c98Schristos argc -= optind;
340fec65c98Schristos argv += optind;
341fec65c98Schristos }
342fec65c98Schristos resp = NULL;
343fec65c98Schristos
344fec65c98Schristos /*
345fec65c98Schristos * Quirks for help(1)
346fec65c98Schristos * and for a man(1) section argument without -s.
347fec65c98Schristos */
348fec65c98Schristos
349fec65c98Schristos if (search.argmode == ARG_NAME) {
350fec65c98Schristos if (*progname == 'h') {
351fec65c98Schristos if (argc == 0) {
352fec65c98Schristos argv = help_argv;
353fec65c98Schristos argc = 1;
354fec65c98Schristos }
355fec65c98Schristos } else if (argc > 1 &&
356fec65c98Schristos ((uc = (unsigned char *)argv[0]) != NULL) &&
357fec65c98Schristos ((isdigit(uc[0]) && (uc[1] == '\0' ||
358fec65c98Schristos (isalpha(uc[1]) && uc[2] == '\0'))) ||
359fec65c98Schristos (uc[0] == 'n' && uc[1] == '\0'))) {
360fec65c98Schristos search.sec = (char *)uc;
361fec65c98Schristos argv++;
362fec65c98Schristos argc--;
363fec65c98Schristos }
364fec65c98Schristos if (search.arch == NULL)
365fec65c98Schristos search.arch = getenv("MACHINE");
366fec65c98Schristos #ifdef MACHINE
367fec65c98Schristos if (search.arch == NULL)
368fec65c98Schristos search.arch = MACHINE;
369fec65c98Schristos #endif
370fec65c98Schristos }
371fec65c98Schristos
3726167eca2Schristos /*
3736167eca2Schristos * Use the first argument for -O tag in addition to
3746167eca2Schristos * using it as a search term for man(1) or apropos(1).
3756167eca2Schristos */
3766167eca2Schristos
3776167eca2Schristos if (conf.output.tag != NULL && *conf.output.tag == '\0') {
3786167eca2Schristos tagarg = argc > 0 && search.argmode == ARG_EXPR ?
3796167eca2Schristos strchr(*argv, '=') : NULL;
3806167eca2Schristos conf.output.tag = tagarg == NULL ? *argv : tagarg + 1;
3816167eca2Schristos }
382fec65c98Schristos
383fec65c98Schristos /* man(1), whatis(1), apropos(1) */
384fec65c98Schristos
385fec65c98Schristos if (search.argmode != ARG_FILE) {
386fec65c98Schristos if (search.argmode == ARG_NAME &&
387fec65c98Schristos outmode == OUTMODE_ONE)
388fec65c98Schristos search.firstmatch = 1;
389fec65c98Schristos
390fec65c98Schristos /* Access the mandoc database. */
391fec65c98Schristos
392f47368cfSchristos manconf_parse(&conf, conf_file, defpaths, auxpaths);
393f47368cfSchristos if ( ! mansearch(&search, &conf.manpath,
394f47368cfSchristos argc, argv, &res, &sz))
395fec65c98Schristos usage(search.argmode);
396fec65c98Schristos
39714e7489eSchristos if (sz == 0 && search.argmode == ARG_NAME)
398f47368cfSchristos fs_search(&search, &conf.manpath,
399f47368cfSchristos argc, argv, &res, &sz);
40014e7489eSchristos
40114e7489eSchristos if (search.argmode == ARG_NAME) {
40214e7489eSchristos for (c = 0; c < argc; c++) {
40314e7489eSchristos if (strchr(argv[c], '/') == NULL)
40414e7489eSchristos continue;
40514e7489eSchristos if (access(argv[c], R_OK) == -1) {
40614e7489eSchristos warn("%s", argv[c]);
40714e7489eSchristos continue;
40814e7489eSchristos }
40914e7489eSchristos res = mandoc_reallocarray(res,
41014e7489eSchristos sz + 1, sizeof(*res));
41114e7489eSchristos res[sz].file = mandoc_strdup(argv[c]);
41214e7489eSchristos res[sz].names = NULL;
41314e7489eSchristos res[sz].output = NULL;
41414e7489eSchristos res[sz].ipath = SIZE_MAX;
41514e7489eSchristos res[sz].sec = 10;
41614e7489eSchristos res[sz].form = FORM_SRC;
41714e7489eSchristos sz++;
41814e7489eSchristos }
419f47368cfSchristos }
420fec65c98Schristos
421fec65c98Schristos if (sz == 0) {
42214e7489eSchristos if (search.argmode != ARG_NAME)
42314e7489eSchristos warnx("nothing appropriate");
4246167eca2Schristos mandoc_msg_setrc(MANDOCLEVEL_BADARG);
425fec65c98Schristos goto out;
426fec65c98Schristos }
427fec65c98Schristos
428fec65c98Schristos /*
429fec65c98Schristos * For standard man(1) and -a output mode,
430fec65c98Schristos * prepare for copying filename pointers
431fec65c98Schristos * into the program parameter array.
432fec65c98Schristos */
433fec65c98Schristos
434fec65c98Schristos if (outmode == OUTMODE_ONE) {
435fec65c98Schristos argc = 1;
436f47368cfSchristos best_prio = 20;
437fec65c98Schristos } else if (outmode == OUTMODE_ALL)
438fec65c98Schristos argc = (int)sz;
439fec65c98Schristos
440fec65c98Schristos /* Iterate all matching manuals. */
441fec65c98Schristos
442fec65c98Schristos resp = res;
443fec65c98Schristos for (i = 0; i < sz; i++) {
444fec65c98Schristos if (outmode == OUTMODE_FLN)
445fec65c98Schristos puts(res[i].file);
446fec65c98Schristos else if (outmode == OUTMODE_LST)
447fec65c98Schristos printf("%s - %s\n", res[i].names,
448fec65c98Schristos res[i].output == NULL ? "" :
449fec65c98Schristos res[i].output);
450fec65c98Schristos else if (outmode == OUTMODE_ONE) {
451fec65c98Schristos /* Search for the best section. */
452f47368cfSchristos sec = res[i].file;
453f47368cfSchristos sec += strcspn(sec, "123456789");
454f47368cfSchristos if (sec[0] == '\0')
455fec65c98Schristos continue;
456f47368cfSchristos prio = sec_prios[sec[0] - '1'];
457f47368cfSchristos if (sec[1] != '/')
458f47368cfSchristos prio += 10;
459fec65c98Schristos if (prio >= best_prio)
460fec65c98Schristos continue;
461fec65c98Schristos best_prio = prio;
462fec65c98Schristos resp = res + i;
463fec65c98Schristos }
464fec65c98Schristos }
465fec65c98Schristos
466fec65c98Schristos /*
467fec65c98Schristos * For man(1), -a and -i output mode, fall through
468fec65c98Schristos * to the main mandoc(1) code iterating files
469fec65c98Schristos * and running the parsers on each of them.
470fec65c98Schristos */
471fec65c98Schristos
472fec65c98Schristos if (outmode == OUTMODE_FLN || outmode == OUTMODE_LST)
473fec65c98Schristos goto out;
474fec65c98Schristos }
475fec65c98Schristos
476fec65c98Schristos /* mandoc(1) */
477fec65c98Schristos
478f47368cfSchristos #if HAVE_PLEDGE
479f47368cfSchristos if (use_pager) {
480f47368cfSchristos if (pledge("stdio rpath tmppath tty proc exec", NULL) == -1)
481f47368cfSchristos err((int)MANDOCLEVEL_SYSERR, "pledge");
482f47368cfSchristos } else {
483f47368cfSchristos if (pledge("stdio rpath", NULL) == -1)
484f47368cfSchristos err((int)MANDOCLEVEL_SYSERR, "pledge");
485f47368cfSchristos }
486f47368cfSchristos #endif
487fec65c98Schristos
48814e7489eSchristos if (search.argmode == ARG_FILE)
48914e7489eSchristos moptions(&options, auxpaths);
490f47368cfSchristos
491f47368cfSchristos mchars_alloc();
4926167eca2Schristos curp.mp = mparse_alloc(options, curp.os_e, curp.os_s);
493c5f73b34Sjoerg
494fec65c98Schristos if (argc < 1) {
4956167eca2Schristos if (use_pager) {
496f47368cfSchristos tag_files = tag_init();
4976167eca2Schristos tag_files->tagname = conf.output.tag;
4986167eca2Schristos }
4996167eca2Schristos thisarg = "<stdin>";
5006167eca2Schristos mandoc_msg_setinfilename(thisarg);
5016167eca2Schristos parse(&curp, STDIN_FILENO, thisarg);
5026167eca2Schristos mandoc_msg_setinfilename(NULL);
503fec65c98Schristos }
5044154958bSjoerg
50514e7489eSchristos /*
50614e7489eSchristos * Remember the original working directory, if possible.
50714e7489eSchristos * This will be needed if some names on the command line
50814e7489eSchristos * are page names and some are relative file names.
50914e7489eSchristos * Do not error out if the current directory is not
51014e7489eSchristos * readable: Maybe it won't be needed after all.
51114e7489eSchristos */
51214e7489eSchristos startdir = open(".", O_RDONLY | O_DIRECTORY);
51314e7489eSchristos
514fec65c98Schristos while (argc > 0) {
51514e7489eSchristos
51614e7489eSchristos /*
51714e7489eSchristos * Changing directories is not needed in ARG_FILE mode.
51814e7489eSchristos * Do it on a best-effort basis. Even in case of
51914e7489eSchristos * failure, some functionality may still work.
52014e7489eSchristos */
52114e7489eSchristos if (resp != NULL) {
52214e7489eSchristos if (resp->ipath != SIZE_MAX)
52314e7489eSchristos (void)chdir(conf.manpath.paths[resp->ipath]);
52414e7489eSchristos else if (startdir != -1)
52514e7489eSchristos (void)fchdir(startdir);
5266167eca2Schristos thisarg = resp->file;
5276167eca2Schristos } else
5286167eca2Schristos thisarg = *argv;
52914e7489eSchristos
5306167eca2Schristos fd = mparse_open(curp.mp, thisarg);
531fec65c98Schristos if (fd != -1) {
532f47368cfSchristos if (use_pager) {
533f47368cfSchristos use_pager = 0;
5346167eca2Schristos tag_files = tag_init();
5356167eca2Schristos tag_files->tagname = conf.output.tag;
536f47368cfSchristos }
537fec65c98Schristos
5386167eca2Schristos mandoc_msg_setinfilename(thisarg);
5396167eca2Schristos if (resp == NULL || resp->form == FORM_SRC)
5406167eca2Schristos parse(&curp, fd, thisarg);
54114e7489eSchristos else
542f47368cfSchristos passthrough(resp->file, fd,
543f47368cfSchristos conf.output.synopsisonly);
5446167eca2Schristos mandoc_msg_setinfilename(NULL);
545fec65c98Schristos
54614e7489eSchristos if (ferror(stdout)) {
54714e7489eSchristos if (tag_files != NULL) {
54814e7489eSchristos warn("%s", tag_files->ofn);
54914e7489eSchristos tag_unlink();
55014e7489eSchristos tag_files = NULL;
55114e7489eSchristos } else
55214e7489eSchristos warn("stdout");
5536167eca2Schristos mandoc_msg_setrc(MANDOCLEVEL_SYSERR);
55414e7489eSchristos break;
55514e7489eSchristos }
55614e7489eSchristos
55737ef69edSchristos if (argc > 1 && curp.outtype <= OUTT_UTF8) {
55837ef69edSchristos if (curp.outdata == NULL)
55937ef69edSchristos outdata_alloc(&curp);
560f47368cfSchristos terminal_sepline(curp.outdata);
56137ef69edSchristos }
5626167eca2Schristos } else
5636167eca2Schristos mandoc_msg(MANDOCERR_FILE, 0, 0,
5646167eca2Schristos "%s: %s", thisarg, strerror(errno));
565fec65c98Schristos
5666167eca2Schristos if (curp.wstop && mandoc_msg_getrc() != MANDOCLEVEL_OK)
5670a84adc5Sjoerg break;
568fec65c98Schristos
569fec65c98Schristos if (resp != NULL)
570fec65c98Schristos resp++;
571fec65c98Schristos else
572fec65c98Schristos argv++;
573fec65c98Schristos if (--argc)
574fec65c98Schristos mparse_reset(curp.mp);
5754154958bSjoerg }
57614e7489eSchristos if (startdir != -1) {
57714e7489eSchristos (void)fchdir(startdir);
57814e7489eSchristos close(startdir);
57914e7489eSchristos }
5804154958bSjoerg
581f47368cfSchristos if (curp.outdata != NULL) {
582f47368cfSchristos switch (curp.outtype) {
583f47368cfSchristos case OUTT_HTML:
584f47368cfSchristos html_free(curp.outdata);
585f47368cfSchristos break;
586f47368cfSchristos case OUTT_UTF8:
587f47368cfSchristos case OUTT_LOCALE:
588f47368cfSchristos case OUTT_ASCII:
589f47368cfSchristos ascii_free(curp.outdata);
590f47368cfSchristos break;
591f47368cfSchristos case OUTT_PDF:
592f47368cfSchristos case OUTT_PS:
593f47368cfSchristos pspdf_free(curp.outdata);
594f47368cfSchristos break;
595f47368cfSchristos default:
596f47368cfSchristos break;
597f47368cfSchristos }
598f47368cfSchristos }
59914e7489eSchristos mandoc_xr_free();
60048741257Sjoerg mparse_free(curp.mp);
601f47368cfSchristos mchars_free();
602fec65c98Schristos
603fec65c98Schristos out:
604fec65c98Schristos if (search.argmode != ARG_FILE) {
605f47368cfSchristos manconf_free(&conf);
606fec65c98Schristos mansearch_free(res, sz);
607fec65c98Schristos }
608fec65c98Schristos
60914e7489eSchristos free(curp.os_s);
6104154958bSjoerg
611fec65c98Schristos /*
612f47368cfSchristos * When using a pager, finish writing both temporary files,
613f47368cfSchristos * fork it, wait for the user to close it, and clean up.
614fec65c98Schristos */
615fec65c98Schristos
616f47368cfSchristos if (tag_files != NULL) {
617fec65c98Schristos fclose(stdout);
618f47368cfSchristos tag_write();
619f47368cfSchristos man_pgid = getpgid(0);
620f47368cfSchristos tag_files->tcpgid = man_pgid == getpid() ?
621f47368cfSchristos getpgid(getppid()) : man_pgid;
622f47368cfSchristos pager_pid = 0;
623f47368cfSchristos signum = SIGSTOP;
624f47368cfSchristos for (;;) {
625f47368cfSchristos
626f47368cfSchristos /* Stop here until moved to the foreground. */
627f47368cfSchristos
62837ef69edSchristos tc_pgid = tcgetpgrp(tag_files->ofd);
629f47368cfSchristos if (tc_pgid != man_pgid) {
630f47368cfSchristos if (tc_pgid == pager_pid) {
63137ef69edSchristos (void)tcsetpgrp(tag_files->ofd,
632f47368cfSchristos man_pgid);
633f47368cfSchristos if (signum == SIGTTIN)
634f47368cfSchristos continue;
635f47368cfSchristos } else
636f47368cfSchristos tag_files->tcpgid = tc_pgid;
637f47368cfSchristos kill(0, signum);
638f47368cfSchristos continue;
639fec65c98Schristos }
640fec65c98Schristos
641f47368cfSchristos /* Once in the foreground, activate the pager. */
642f47368cfSchristos
643f47368cfSchristos if (pager_pid) {
64437ef69edSchristos (void)tcsetpgrp(tag_files->ofd, pager_pid);
645f47368cfSchristos kill(pager_pid, SIGCONT);
646f47368cfSchristos } else
647f47368cfSchristos pager_pid = spawn_pager(tag_files);
648f47368cfSchristos
649f47368cfSchristos /* Wait for the pager to stop or exit. */
650f47368cfSchristos
651f47368cfSchristos while ((pid = waitpid(pager_pid, &status,
652f47368cfSchristos WUNTRACED)) == -1 && errno == EINTR)
653f47368cfSchristos continue;
654f47368cfSchristos
655f47368cfSchristos if (pid == -1) {
656f47368cfSchristos warn("wait");
6576167eca2Schristos mandoc_msg_setrc(MANDOCLEVEL_SYSERR);
658f47368cfSchristos break;
659f47368cfSchristos }
660f47368cfSchristos if (!WIFSTOPPED(status))
661f47368cfSchristos break;
662f47368cfSchristos
663f47368cfSchristos signum = WSTOPSIG(status);
664f47368cfSchristos }
665f47368cfSchristos tag_unlink();
666f47368cfSchristos }
6676167eca2Schristos return (int)mandoc_msg_getrc();
6684154958bSjoerg }
6694154958bSjoerg
6703514411fSjoerg static void
usage(enum argmode argmode)671fec65c98Schristos usage(enum argmode argmode)
6724154958bSjoerg {
6734154958bSjoerg
674fec65c98Schristos switch (argmode) {
675fec65c98Schristos case ARG_FILE:
67614e7489eSchristos fputs("usage: mandoc [-ac] [-I os=name] "
67714e7489eSchristos "[-K encoding] [-mdoc | -man] [-O options]\n"
678fec65c98Schristos "\t [-T output] [-W level] [file ...]\n", stderr);
679fec65c98Schristos break;
680fec65c98Schristos case ARG_NAME:
68114e7489eSchristos fputs("usage: man [-acfhklw] [-C file] [-M path] "
68214e7489eSchristos "[-m path] [-S subsection]\n"
68314e7489eSchristos "\t [[-s] section] name ...\n", stderr);
684fec65c98Schristos break;
685fec65c98Schristos case ARG_WORD:
68614e7489eSchristos fputs("usage: whatis [-afk] [-C file] "
687fec65c98Schristos "[-M path] [-m path] [-O outkey] [-S arch]\n"
688fec65c98Schristos "\t [-s section] name ...\n", stderr);
689fec65c98Schristos break;
690fec65c98Schristos case ARG_EXPR:
69114e7489eSchristos fputs("usage: apropos [-afk] [-C file] "
692fec65c98Schristos "[-M path] [-m path] [-O outkey] [-S arch]\n"
693fec65c98Schristos "\t [-s section] expression ...\n", stderr);
694fec65c98Schristos break;
6954154958bSjoerg }
696c0d9444aSjoerg exit((int)MANDOCLEVEL_BADARG);
6974154958bSjoerg }
6984154958bSjoerg
699fec65c98Schristos 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)700fec65c98Schristos fs_lookup(const struct manpaths *paths, size_t ipath,
701fec65c98Schristos const char *sec, const char *arch, const char *name,
702fec65c98Schristos struct manpage **res, size_t *ressz)
703fec65c98Schristos {
704fec65c98Schristos glob_t globinfo;
705fec65c98Schristos struct manpage *page;
706fec65c98Schristos char *file;
70737ef69edSchristos int globres;
70837ef69edSchristos enum form form;
709fec65c98Schristos
710fec65c98Schristos form = FORM_SRC;
711fec65c98Schristos mandoc_asprintf(&file, "%s/man%s/%s.%s",
712fec65c98Schristos paths->paths[ipath], sec, name, sec);
713fec65c98Schristos if (access(file, R_OK) != -1)
714fec65c98Schristos goto found;
715fec65c98Schristos free(file);
716fec65c98Schristos
717fec65c98Schristos mandoc_asprintf(&file, "%s/cat%s/%s.0",
718fec65c98Schristos paths->paths[ipath], sec, name);
719fec65c98Schristos if (access(file, R_OK) != -1) {
720fec65c98Schristos form = FORM_CAT;
721fec65c98Schristos goto found;
722fec65c98Schristos }
723fec65c98Schristos free(file);
724fec65c98Schristos
725fec65c98Schristos if (arch != NULL) {
726fec65c98Schristos mandoc_asprintf(&file, "%s/man%s/%s/%s.%s",
727fec65c98Schristos paths->paths[ipath], sec, arch, name, sec);
728fec65c98Schristos if (access(file, R_OK) != -1)
729fec65c98Schristos goto found;
730fec65c98Schristos free(file);
731fec65c98Schristos }
732fec65c98Schristos
733f47368cfSchristos mandoc_asprintf(&file, "%s/man%s/%s.[01-9]*",
734fec65c98Schristos paths->paths[ipath], sec, name);
735fec65c98Schristos globres = glob(file, 0, NULL, &globinfo);
736fec65c98Schristos if (globres != 0 && globres != GLOB_NOMATCH)
737f47368cfSchristos warn("%s: glob", file);
738fec65c98Schristos free(file);
739fec65c98Schristos if (globres == 0)
740fec65c98Schristos file = mandoc_strdup(*globinfo.gl_pathv);
741fec65c98Schristos globfree(&globinfo);
74214e7489eSchristos if (globres == 0)
74314e7489eSchristos goto found;
74414e7489eSchristos if (res != NULL || ipath + 1 != paths->sz)
745f47368cfSchristos return 0;
746fec65c98Schristos
74714e7489eSchristos mandoc_asprintf(&file, "%s.%s", name, sec);
74814e7489eSchristos globres = access(file, R_OK);
74914e7489eSchristos free(file);
75014e7489eSchristos return globres != -1;
75114e7489eSchristos
752fec65c98Schristos found:
753*2fd1c0acSwiz #if !defined(__NetBSD__)
754f47368cfSchristos warnx("outdated mandoc.db lacks %s(%s) entry, run %s %s",
755f47368cfSchristos name, sec, BINM_MAKEWHATIS, paths->paths[ipath]);
756*2fd1c0acSwiz #endif
75714e7489eSchristos if (res == NULL) {
75814e7489eSchristos free(file);
75914e7489eSchristos return 1;
76014e7489eSchristos }
761fec65c98Schristos *res = mandoc_reallocarray(*res, ++*ressz, sizeof(struct manpage));
762fec65c98Schristos page = *res + (*ressz - 1);
763fec65c98Schristos page->file = file;
764fec65c98Schristos page->names = NULL;
765fec65c98Schristos page->output = NULL;
766fec65c98Schristos page->ipath = ipath;
767fec65c98Schristos page->sec = (*sec >= '1' && *sec <= '9') ? *sec - '1' + 1 : 10;
768fec65c98Schristos page->form = form;
769f47368cfSchristos return 1;
770fec65c98Schristos }
771fec65c98Schristos
77214e7489eSchristos static int
fs_search(const struct mansearch * cfg,const struct manpaths * paths,int argc,char ** argv,struct manpage ** res,size_t * ressz)773fec65c98Schristos fs_search(const struct mansearch *cfg, const struct manpaths *paths,
774fec65c98Schristos int argc, char **argv, struct manpage **res, size_t *ressz)
775fec65c98Schristos {
776fec65c98Schristos const char *const sections[] =
777f47368cfSchristos {"1", "8", "6", "2", "3", "5", "7", "4", "9", "3p"};
778fec65c98Schristos const size_t nsec = sizeof(sections)/sizeof(sections[0]);
779fec65c98Schristos
780fec65c98Schristos size_t ipath, isec, lastsz;
781fec65c98Schristos
782fec65c98Schristos assert(cfg->argmode == ARG_NAME);
783fec65c98Schristos
78414e7489eSchristos if (res != NULL)
785fec65c98Schristos *res = NULL;
786fec65c98Schristos *ressz = lastsz = 0;
787fec65c98Schristos while (argc) {
788fec65c98Schristos for (ipath = 0; ipath < paths->sz; ipath++) {
789fec65c98Schristos if (cfg->sec != NULL) {
790fec65c98Schristos if (fs_lookup(paths, ipath, cfg->sec,
791fec65c98Schristos cfg->arch, *argv, res, ressz) &&
792fec65c98Schristos cfg->firstmatch)
79314e7489eSchristos return 1;
794fec65c98Schristos } else for (isec = 0; isec < nsec; isec++)
795fec65c98Schristos if (fs_lookup(paths, ipath, sections[isec],
796fec65c98Schristos cfg->arch, *argv, res, ressz) &&
797fec65c98Schristos cfg->firstmatch)
79814e7489eSchristos return 1;
799fec65c98Schristos }
80014e7489eSchristos if (res != NULL && *ressz == lastsz &&
8016167eca2Schristos strchr(*argv, '/') == NULL) {
8026167eca2Schristos if (cfg->arch != NULL &&
8036167eca2Schristos arch_valid(cfg->arch, OSENUM) == 0)
8046167eca2Schristos warnx("Unknown architecture \"%s\".",
8056167eca2Schristos cfg->arch);
8066167eca2Schristos else if (cfg->sec == NULL)
8076167eca2Schristos warnx("No entry for %s in the manual.",
8086167eca2Schristos *argv);
8096167eca2Schristos else
8106167eca2Schristos warnx("No entry for %s in section %s "
8116167eca2Schristos "of the manual.", *argv, cfg->sec);
8126167eca2Schristos }
813fec65c98Schristos lastsz = *ressz;
814fec65c98Schristos argv++;
815fec65c98Schristos argc--;
816fec65c98Schristos }
81714e7489eSchristos return 0;
818fec65c98Schristos }
819fec65c98Schristos
820fec65c98Schristos static void
parse(struct curparse * curp,int fd,const char * file)821f47368cfSchristos parse(struct curparse *curp, int fd, const char *file)
8224154958bSjoerg {
8236167eca2Schristos struct roff_meta *meta;
8244154958bSjoerg
82548741257Sjoerg /* Begin by parsing the file itself. */
826c0d9444aSjoerg
82748741257Sjoerg assert(file);
828f47368cfSchristos assert(fd >= 0);
829c0d9444aSjoerg
8306167eca2Schristos mparse_readfd(curp->mp, fd, file);
831f47368cfSchristos if (fd != STDIN_FILENO)
832f47368cfSchristos close(fd);
8334154958bSjoerg
834c0d9444aSjoerg /*
83548741257Sjoerg * With -Wstop and warnings or errors of at least the requested
83648741257Sjoerg * level, do not produce output.
837c0d9444aSjoerg */
838c0d9444aSjoerg
8396167eca2Schristos if (curp->wstop && mandoc_msg_getrc() != MANDOCLEVEL_OK)
840f47368cfSchristos return;
8414154958bSjoerg
84237ef69edSchristos if (curp->outdata == NULL)
84337ef69edSchristos outdata_alloc(curp);
8446167eca2Schristos else if (curp->outtype == OUTT_HTML)
8456167eca2Schristos html_reset(curp);
8464154958bSjoerg
8476167eca2Schristos mandoc_xr_reset();
8486167eca2Schristos meta = mparse_result(curp->mp);
84937ef69edSchristos
85037ef69edSchristos /* Execute the out device, if it exists. */
85137ef69edSchristos
8526167eca2Schristos if (meta->macroset == MACROSET_MDOC) {
85337ef69edSchristos switch (curp->outtype) {
85437ef69edSchristos case OUTT_HTML:
8556167eca2Schristos html_mdoc(curp->outdata, meta);
85637ef69edSchristos break;
85737ef69edSchristos case OUTT_TREE:
8586167eca2Schristos tree_mdoc(curp->outdata, meta);
85937ef69edSchristos break;
86037ef69edSchristos case OUTT_MAN:
8616167eca2Schristos man_mdoc(curp->outdata, meta);
86237ef69edSchristos break;
86337ef69edSchristos case OUTT_PDF:
86437ef69edSchristos case OUTT_ASCII:
86537ef69edSchristos case OUTT_UTF8:
86637ef69edSchristos case OUTT_LOCALE:
86737ef69edSchristos case OUTT_PS:
8686167eca2Schristos terminal_mdoc(curp->outdata, meta);
86937ef69edSchristos break;
87014e7489eSchristos case OUTT_MARKDOWN:
8716167eca2Schristos markdown_mdoc(curp->outdata, meta);
87214e7489eSchristos break;
87337ef69edSchristos default:
87437ef69edSchristos break;
87537ef69edSchristos }
87637ef69edSchristos }
8776167eca2Schristos if (meta->macroset == MACROSET_MAN) {
87837ef69edSchristos switch (curp->outtype) {
87937ef69edSchristos case OUTT_HTML:
8806167eca2Schristos html_man(curp->outdata, meta);
88137ef69edSchristos break;
88237ef69edSchristos case OUTT_TREE:
8836167eca2Schristos tree_man(curp->outdata, meta);
88437ef69edSchristos break;
88537ef69edSchristos case OUTT_MAN:
8866167eca2Schristos mparse_copy(curp->mp);
88737ef69edSchristos break;
88837ef69edSchristos case OUTT_PDF:
88937ef69edSchristos case OUTT_ASCII:
89037ef69edSchristos case OUTT_UTF8:
89137ef69edSchristos case OUTT_LOCALE:
89237ef69edSchristos case OUTT_PS:
8936167eca2Schristos terminal_man(curp->outdata, meta);
89437ef69edSchristos break;
89537ef69edSchristos default:
89637ef69edSchristos break;
89737ef69edSchristos }
89837ef69edSchristos }
8996167eca2Schristos if (mandoc_msg_getmin() < MANDOCERR_STYLE)
9006167eca2Schristos check_xr();
90137ef69edSchristos }
90237ef69edSchristos
90337ef69edSchristos static void
check_xr(void)9046167eca2Schristos check_xr(void)
90514e7489eSchristos {
90614e7489eSchristos static struct manpaths paths;
90714e7489eSchristos struct mansearch search;
90814e7489eSchristos struct mandoc_xr *xr;
90914e7489eSchristos size_t sz;
91014e7489eSchristos
91114e7489eSchristos if (paths.sz == 0)
91214e7489eSchristos manpath_base(&paths);
91314e7489eSchristos
91414e7489eSchristos for (xr = mandoc_xr_get(); xr != NULL; xr = xr->next) {
91514e7489eSchristos if (xr->line == -1)
91614e7489eSchristos continue;
91714e7489eSchristos search.arch = NULL;
91814e7489eSchristos search.sec = xr->sec;
91914e7489eSchristos search.outkey = NULL;
92014e7489eSchristos search.argmode = ARG_NAME;
92114e7489eSchristos search.firstmatch = 1;
92214e7489eSchristos if (mansearch(&search, &paths, 1, &xr->name, NULL, &sz))
92314e7489eSchristos continue;
92414e7489eSchristos if (fs_search(&search, &paths, 1, &xr->name, NULL, &sz))
92514e7489eSchristos continue;
92614e7489eSchristos if (xr->count == 1)
9276167eca2Schristos mandoc_msg(MANDOCERR_XR_BAD, xr->line,
9286167eca2Schristos xr->pos + 1, "Xr %s %s", xr->name, xr->sec);
92914e7489eSchristos else
9306167eca2Schristos mandoc_msg(MANDOCERR_XR_BAD, xr->line,
9316167eca2Schristos xr->pos + 1, "Xr %s %s (%d times)",
93214e7489eSchristos xr->name, xr->sec, xr->count);
93314e7489eSchristos }
93414e7489eSchristos }
93514e7489eSchristos
93614e7489eSchristos static void
outdata_alloc(struct curparse * curp)93737ef69edSchristos outdata_alloc(struct curparse *curp)
93837ef69edSchristos {
9394154958bSjoerg switch (curp->outtype) {
940fec65c98Schristos case OUTT_HTML:
941f47368cfSchristos curp->outdata = html_alloc(curp->outopts);
942d5e63c8dSjoerg break;
943fec65c98Schristos case OUTT_UTF8:
944f47368cfSchristos curp->outdata = utf8_alloc(curp->outopts);
945c5f73b34Sjoerg break;
946fec65c98Schristos case OUTT_LOCALE:
947f47368cfSchristos curp->outdata = locale_alloc(curp->outopts);
9487574e07eSjoerg break;
949fec65c98Schristos case OUTT_ASCII:
950f47368cfSchristos curp->outdata = ascii_alloc(curp->outopts);
9517574e07eSjoerg break;
952fec65c98Schristos case OUTT_PDF:
953f47368cfSchristos curp->outdata = pdf_alloc(curp->outopts);
9547da9b934Sjoerg break;
955fec65c98Schristos case OUTT_PS:
956f47368cfSchristos curp->outdata = ps_alloc(curp->outopts);
9574154958bSjoerg break;
9587574e07eSjoerg default:
9597574e07eSjoerg break;
9604154958bSjoerg }
9614154958bSjoerg }
9624154958bSjoerg
963f47368cfSchristos static void
passthrough(const char * file,int fd,int synopsis_only)964fec65c98Schristos passthrough(const char *file, int fd, int synopsis_only)
965fec65c98Schristos {
966fec65c98Schristos const char synb[] = "S\bSY\bYN\bNO\bOP\bPS\bSI\bIS\bS";
967fec65c98Schristos const char synr[] = "SYNOPSIS";
968fec65c98Schristos
969fec65c98Schristos FILE *stream;
970fec65c98Schristos const char *syscall;
971f47368cfSchristos char *line, *cp;
972f47368cfSchristos size_t linesz;
97337ef69edSchristos ssize_t len, written;
974fec65c98Schristos int print;
975fec65c98Schristos
976f47368cfSchristos line = NULL;
977f47368cfSchristos linesz = 0;
978fec65c98Schristos
97937ef69edSchristos if (fflush(stdout) == EOF) {
98037ef69edSchristos syscall = "fflush";
98137ef69edSchristos goto fail;
98237ef69edSchristos }
98337ef69edSchristos
984fec65c98Schristos if ((stream = fdopen(fd, "r")) == NULL) {
985fec65c98Schristos close(fd);
986fec65c98Schristos syscall = "fdopen";
987fec65c98Schristos goto fail;
988fec65c98Schristos }
989fec65c98Schristos
990fec65c98Schristos print = 0;
99137ef69edSchristos while ((len = getline(&line, &linesz, stream)) != -1) {
992f47368cfSchristos cp = line;
993fec65c98Schristos if (synopsis_only) {
994fec65c98Schristos if (print) {
995f47368cfSchristos if ( ! isspace((unsigned char)*cp))
996fec65c98Schristos goto done;
99737ef69edSchristos while (isspace((unsigned char)*cp)) {
998f47368cfSchristos cp++;
99937ef69edSchristos len--;
100037ef69edSchristos }
1001fec65c98Schristos } else {
1002f47368cfSchristos if (strcmp(cp, synb) == 0 ||
1003f47368cfSchristos strcmp(cp, synr) == 0)
1004fec65c98Schristos print = 1;
1005fec65c98Schristos continue;
1006fec65c98Schristos }
1007fec65c98Schristos }
100837ef69edSchristos for (; len > 0; len -= written) {
100937ef69edSchristos if ((written = write(STDOUT_FILENO, cp, len)) != -1)
101037ef69edSchristos continue;
1011fec65c98Schristos fclose(stream);
101237ef69edSchristos syscall = "write";
1013fec65c98Schristos goto fail;
1014fec65c98Schristos }
1015fec65c98Schristos }
1016fec65c98Schristos
1017fec65c98Schristos if (ferror(stream)) {
1018fec65c98Schristos fclose(stream);
1019f47368cfSchristos syscall = "getline";
1020fec65c98Schristos goto fail;
1021fec65c98Schristos }
1022fec65c98Schristos
1023fec65c98Schristos done:
1024f47368cfSchristos free(line);
1025fec65c98Schristos fclose(stream);
1026f47368cfSchristos return;
1027fec65c98Schristos
1028fec65c98Schristos fail:
1029f47368cfSchristos free(line);
1030f47368cfSchristos warn("%s: SYSERR: %s", file, syscall);
10316167eca2Schristos mandoc_msg_setrc(MANDOCLEVEL_SYSERR);
1032fec65c98Schristos }
1033fec65c98Schristos
10344154958bSjoerg static int
koptions(int * options,char * arg)1035fec65c98Schristos koptions(int *options, char *arg)
10364154958bSjoerg {
10374154958bSjoerg
1038fec65c98Schristos if ( ! strcmp(arg, "utf-8")) {
1039fec65c98Schristos *options |= MPARSE_UTF8;
1040fec65c98Schristos *options &= ~MPARSE_LATIN1;
1041fec65c98Schristos } else if ( ! strcmp(arg, "iso-8859-1")) {
1042fec65c98Schristos *options |= MPARSE_LATIN1;
1043fec65c98Schristos *options &= ~MPARSE_UTF8;
1044fec65c98Schristos } else if ( ! strcmp(arg, "us-ascii")) {
1045fec65c98Schristos *options &= ~(MPARSE_UTF8 | MPARSE_LATIN1);
1046fec65c98Schristos } else {
1047f47368cfSchristos warnx("-K %s: Bad argument", arg);
1048f47368cfSchristos return 0;
1049fec65c98Schristos }
1050f47368cfSchristos return 1;
1051fec65c98Schristos }
1052fec65c98Schristos
105314e7489eSchristos static void
moptions(int * options,char * arg)1054fec65c98Schristos moptions(int *options, char *arg)
1055fec65c98Schristos {
1056fec65c98Schristos
1057fec65c98Schristos if (arg == NULL)
105814e7489eSchristos return;
105914e7489eSchristos if (strcmp(arg, "doc") == 0)
1060fec65c98Schristos *options |= MPARSE_MDOC;
106114e7489eSchristos else if (strcmp(arg, "an") == 0)
1062fec65c98Schristos *options |= MPARSE_MAN;
10634154958bSjoerg }
10644154958bSjoerg
10654154958bSjoerg static int
toptions(struct curparse * curp,char * arg)106631e1f4e3Sjoerg toptions(struct curparse *curp, char *arg)
10674154958bSjoerg {
10684154958bSjoerg
10694154958bSjoerg if (0 == strcmp(arg, "ascii"))
107031e1f4e3Sjoerg curp->outtype = OUTT_ASCII;
107131e1f4e3Sjoerg else if (0 == strcmp(arg, "lint")) {
107231e1f4e3Sjoerg curp->outtype = OUTT_LINT;
10736167eca2Schristos mandoc_msg_setoutfile(stdout);
10746167eca2Schristos mandoc_msg_setmin(MANDOCERR_BASE);
107548741257Sjoerg } else if (0 == strcmp(arg, "tree"))
107631e1f4e3Sjoerg curp->outtype = OUTT_TREE;
1077c5f73b34Sjoerg else if (0 == strcmp(arg, "man"))
1078c5f73b34Sjoerg curp->outtype = OUTT_MAN;
10794154958bSjoerg else if (0 == strcmp(arg, "html"))
108031e1f4e3Sjoerg curp->outtype = OUTT_HTML;
108114e7489eSchristos else if (0 == strcmp(arg, "markdown"))
108214e7489eSchristos curp->outtype = OUTT_MARKDOWN;
1083c5f73b34Sjoerg else if (0 == strcmp(arg, "utf8"))
1084c5f73b34Sjoerg curp->outtype = OUTT_UTF8;
1085c5f73b34Sjoerg else if (0 == strcmp(arg, "locale"))
1086c5f73b34Sjoerg curp->outtype = OUTT_LOCALE;
10877574e07eSjoerg else if (0 == strcmp(arg, "ps"))
10887574e07eSjoerg curp->outtype = OUTT_PS;
10897da9b934Sjoerg else if (0 == strcmp(arg, "pdf"))
10907da9b934Sjoerg curp->outtype = OUTT_PDF;
10914154958bSjoerg else {
1092f47368cfSchristos warnx("-T %s: Bad argument", arg);
1093f47368cfSchristos return 0;
10944154958bSjoerg }
10954154958bSjoerg
1096f47368cfSchristos return 1;
10974154958bSjoerg }
10984154958bSjoerg
10994154958bSjoerg static int
woptions(struct curparse * curp,char * arg)1100c0d9444aSjoerg woptions(struct curparse *curp, char *arg)
11014154958bSjoerg {
11024154958bSjoerg char *v, *o;
110314e7489eSchristos const char *toks[11];
11044154958bSjoerg
1105c0d9444aSjoerg toks[0] = "stop";
1106c0d9444aSjoerg toks[1] = "all";
110714e7489eSchristos toks[2] = "base";
110814e7489eSchristos toks[3] = "style";
110914e7489eSchristos toks[4] = "warning";
111014e7489eSchristos toks[5] = "error";
111114e7489eSchristos toks[6] = "unsupp";
111214e7489eSchristos toks[7] = "fatal";
111314e7489eSchristos toks[8] = "openbsd";
111414e7489eSchristos toks[9] = "netbsd";
111514e7489eSchristos toks[10] = NULL;
11164154958bSjoerg
11174154958bSjoerg while (*arg) {
11184154958bSjoerg o = arg;
111937ef69edSchristos switch (getsubopt(&arg, __UNCONST(toks), &v)) {
1120fec65c98Schristos case 0:
1121c0d9444aSjoerg curp->wstop = 1;
11224154958bSjoerg break;
1123fec65c98Schristos case 1:
1124fec65c98Schristos case 2:
11256167eca2Schristos mandoc_msg_setmin(MANDOCERR_BASE);
11264154958bSjoerg break;
1127fec65c98Schristos case 3:
11286167eca2Schristos mandoc_msg_setmin(MANDOCERR_STYLE);
11294154958bSjoerg break;
1130fec65c98Schristos case 4:
11316167eca2Schristos mandoc_msg_setmin(MANDOCERR_WARNING);
1132fec65c98Schristos break;
1133fec65c98Schristos case 5:
11346167eca2Schristos mandoc_msg_setmin(MANDOCERR_ERROR);
113514e7489eSchristos break;
113614e7489eSchristos case 6:
11376167eca2Schristos mandoc_msg_setmin(MANDOCERR_UNSUPP);
113814e7489eSchristos break;
113914e7489eSchristos case 7:
11406167eca2Schristos mandoc_msg_setmin(MANDOCERR_MAX);
114114e7489eSchristos break;
114214e7489eSchristos case 8:
11436167eca2Schristos mandoc_msg_setmin(MANDOCERR_BASE);
114414e7489eSchristos curp->os_e = MANDOC_OS_OPENBSD;
114514e7489eSchristos break;
114614e7489eSchristos case 9:
11476167eca2Schristos mandoc_msg_setmin(MANDOCERR_BASE);
114814e7489eSchristos curp->os_e = MANDOC_OS_NETBSD;
11493514411fSjoerg break;
11504154958bSjoerg default:
1151f47368cfSchristos warnx("-W %s: Bad argument", o);
1152f47368cfSchristos return 0;
11534154958bSjoerg }
11544154958bSjoerg }
1155f47368cfSchristos return 1;
11564154958bSjoerg }
11574154958bSjoerg
1158fec65c98Schristos static pid_t
spawn_pager(struct tag_files * tag_files)1159f47368cfSchristos spawn_pager(struct tag_files *tag_files)
1160fec65c98Schristos {
1161f47368cfSchristos const struct timespec timeout = { 0, 100000000 }; /* 0.1s */
1162fec65c98Schristos #define MAX_PAGER_ARGS 16
1163fec65c98Schristos char *argv[MAX_PAGER_ARGS];
1164fec65c98Schristos const char *pager;
1165fec65c98Schristos char *cp;
11666167eca2Schristos #if HAVE_LESS_T
1167f47368cfSchristos size_t cmdlen;
11686167eca2Schristos #endif
11696167eca2Schristos int argc, use_ofn;
1170fec65c98Schristos pid_t pager_pid;
1171fec65c98Schristos
1172fec65c98Schristos pager = getenv("MANPAGER");
1173fec65c98Schristos if (pager == NULL || *pager == '\0')
1174fec65c98Schristos pager = getenv("PAGER");
1175fec65c98Schristos if (pager == NULL || *pager == '\0')
1176f47368cfSchristos pager = "more -s";
1177fec65c98Schristos cp = mandoc_strdup(pager);
1178fec65c98Schristos
1179fec65c98Schristos /*
1180fec65c98Schristos * Parse the pager command into words.
1181fec65c98Schristos * Intentionally do not do anything fancy here.
1182fec65c98Schristos */
1183fec65c98Schristos
1184fec65c98Schristos argc = 0;
11856167eca2Schristos while (argc + 5 < MAX_PAGER_ARGS) {
1186fec65c98Schristos argv[argc++] = cp;
1187fec65c98Schristos cp = strchr(cp, ' ');
1188fec65c98Schristos if (cp == NULL)
1189fec65c98Schristos break;
1190fec65c98Schristos *cp++ = '\0';
1191fec65c98Schristos while (*cp == ' ')
1192fec65c98Schristos cp++;
1193fec65c98Schristos if (*cp == '\0')
1194fec65c98Schristos break;
1195fec65c98Schristos }
1196f47368cfSchristos
1197f47368cfSchristos /* For less(1), use the tag file. */
1198f47368cfSchristos
11996167eca2Schristos use_ofn = 1;
12006167eca2Schristos #if HAVE_LESS_T
1201f47368cfSchristos if ((cmdlen = strlen(argv[0])) >= 4) {
1202f47368cfSchristos cp = argv[0] + cmdlen - 4;
1203f47368cfSchristos if (strcmp(cp, "less") == 0) {
1204f47368cfSchristos argv[argc++] = mandoc_strdup("-T");
1205f47368cfSchristos argv[argc++] = tag_files->tfn;
12066167eca2Schristos if (tag_files->tagname != NULL) {
12076167eca2Schristos argv[argc++] = mandoc_strdup("-t");
12086167eca2Schristos argv[argc++] = tag_files->tagname;
12096167eca2Schristos use_ofn = 0;
1210f47368cfSchristos }
1211f47368cfSchristos }
12126167eca2Schristos }
12136167eca2Schristos #endif
12146167eca2Schristos if (use_ofn)
1215f47368cfSchristos argv[argc++] = tag_files->ofn;
1216fec65c98Schristos argv[argc] = NULL;
1217fec65c98Schristos
1218f47368cfSchristos switch (pager_pid = fork()) {
1219f47368cfSchristos case -1:
1220f47368cfSchristos err((int)MANDOCLEVEL_SYSERR, "fork");
1221f47368cfSchristos case 0:
1222f47368cfSchristos break;
1223f47368cfSchristos default:
1224f47368cfSchristos (void)setpgid(pager_pid, 0);
122537ef69edSchristos (void)tcsetpgrp(tag_files->ofd, pager_pid);
1226f47368cfSchristos #if HAVE_PLEDGE
1227f47368cfSchristos if (pledge("stdio rpath tmppath tty proc", NULL) == -1)
1228f47368cfSchristos err((int)MANDOCLEVEL_SYSERR, "pledge");
1229f47368cfSchristos #endif
1230f47368cfSchristos tag_files->pager_pid = pager_pid;
1231f47368cfSchristos return pager_pid;
1232f47368cfSchristos }
1233f47368cfSchristos
1234f47368cfSchristos /* The child process becomes the pager. */
1235f47368cfSchristos
1236f47368cfSchristos if (dup2(tag_files->ofd, STDOUT_FILENO) == -1)
1237f47368cfSchristos err((int)MANDOCLEVEL_SYSERR, "pager stdout");
1238f47368cfSchristos close(tag_files->ofd);
123914e7489eSchristos assert(tag_files->tfd == -1);
1240f47368cfSchristos
1241f47368cfSchristos /* Do not start the pager before controlling the terminal. */
1242f47368cfSchristos
124337ef69edSchristos while (tcgetpgrp(STDOUT_FILENO) != getpid())
1244f47368cfSchristos nanosleep(&timeout, NULL);
1245fec65c98Schristos
124680a820b0Schristos /*coverity[TAINTED_STRING]*/
1247fec65c98Schristos execvp(argv[0], argv);
1248f47368cfSchristos err((int)MANDOCLEVEL_SYSERR, "exec %s", argv[0]);
1249fec65c98Schristos }
1250