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