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