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