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