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