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