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