]> git.cameronkatri.com Git - mandoc.git/blob - main.c
e0380e945e1dac2f8e2f411e44f352408983ee63
[mandoc.git] / main.c
1 /* $Id: main.c,v 1.187 2014/08/23 22:26:06 schwarze Exp $ */
2 /*
3 * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
4 * Copyright (c) 2010, 2011, 2012, 2014 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 AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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
23 #include <assert.h>
24 #include <ctype.h>
25 #include <errno.h>
26 #include <fcntl.h>
27 #include <stdio.h>
28 #include <stdint.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <unistd.h>
32
33 #include "mandoc.h"
34 #include "mandoc_aux.h"
35 #include "main.h"
36 #include "mdoc.h"
37 #include "man.h"
38 #include "manpath.h"
39 #include "mansearch.h"
40
41 #if !defined(__GNUC__) || (__GNUC__ < 2)
42 # if !defined(lint)
43 # define __attribute__(x)
44 # endif
45 #endif /* !defined(__GNUC__) || (__GNUC__ < 2) */
46
47 enum outmode {
48 OUTMODE_DEF = 0,
49 OUTMODE_FLN,
50 OUTMODE_LST,
51 OUTMODE_ALL,
52 OUTMODE_INT,
53 OUTMODE_ONE
54 };
55
56 typedef void (*out_mdoc)(void *, const struct mdoc *);
57 typedef void (*out_man)(void *, const struct man *);
58 typedef void (*out_free)(void *);
59
60 enum outt {
61 OUTT_ASCII = 0, /* -Tascii */
62 OUTT_LOCALE, /* -Tlocale */
63 OUTT_UTF8, /* -Tutf8 */
64 OUTT_TREE, /* -Ttree */
65 OUTT_MAN, /* -Tman */
66 OUTT_HTML, /* -Thtml */
67 OUTT_XHTML, /* -Txhtml */
68 OUTT_LINT, /* -Tlint */
69 OUTT_PS, /* -Tps */
70 OUTT_PDF /* -Tpdf */
71 };
72
73 struct curparse {
74 struct mparse *mp;
75 enum mandoclevel wlevel; /* ignore messages below this */
76 int wstop; /* stop after a file with a warning */
77 enum outt outtype; /* which output to use */
78 out_mdoc outmdoc; /* mdoc output ptr */
79 out_man outman; /* man output ptr */
80 out_free outfree; /* free output ptr */
81 void *outdata; /* data for output */
82 char outopts[BUFSIZ]; /* buf of output opts */
83 };
84
85 static int moptions(int *, char *);
86 static void mmsg(enum mandocerr, enum mandoclevel,
87 const char *, int, int, const char *);
88 static void parse(struct curparse *, int,
89 const char *, enum mandoclevel *);
90 static enum mandoclevel passthrough(const char *);
91 static void spawn_pager(void);
92 static int toptions(struct curparse *, char *);
93 static void usage(enum argmode) __attribute__((noreturn));
94 static void version(void) __attribute__((noreturn));
95 static int woptions(struct curparse *, char *);
96
97 static const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
98 static const char *progname;
99
100
101 int
102 main(int argc, char *argv[])
103 {
104 struct curparse curp;
105 struct mansearch search;
106 struct manpaths paths;
107 char *conf_file, *defpaths, *auxpaths;
108 char *defos;
109 #if HAVE_SQLITE3
110 struct manpage *res, *resp;
111 size_t isec, i, sz;
112 int prio, best_prio;
113 char sec;
114 #endif
115 enum mandoclevel rc;
116 enum outmode outmode;
117 int show_usage;
118 int use_pager;
119 int options;
120 int c;
121
122 progname = strrchr(argv[0], '/');
123 if (progname == NULL)
124 progname = argv[0];
125 else
126 ++progname;
127
128 /* Search options. */
129
130 memset(&paths, 0, sizeof(struct manpaths));
131 conf_file = defpaths = auxpaths = NULL;
132
133 memset(&search, 0, sizeof(struct mansearch));
134 search.outkey = "Nd";
135
136 if (strcmp(progname, "man") == 0)
137 search.argmode = ARG_NAME;
138 else if (strncmp(progname, "apropos", 7) == 0)
139 search.argmode = ARG_EXPR;
140 else if (strncmp(progname, "whatis", 6) == 0)
141 search.argmode = ARG_WORD;
142 else
143 search.argmode = ARG_FILE;
144
145 /* Parser and formatter options. */
146
147 memset(&curp, 0, sizeof(struct curparse));
148 curp.outtype = OUTT_ASCII;
149 curp.wlevel = MANDOCLEVEL_FATAL;
150 options = MPARSE_SO;
151 defos = NULL;
152
153 use_pager = 1;
154 show_usage = 0;
155 outmode = OUTMODE_DEF;
156
157 while (-1 != (c = getopt(argc, argv, "aC:cfI:ikM:m:O:S:s:T:VW:w"))) {
158 switch (c) {
159 case 'a':
160 outmode = OUTMODE_ALL;
161 break;
162 case 'C':
163 conf_file = optarg;
164 break;
165 case 'c':
166 use_pager = 0;
167 break;
168 case 'f':
169 search.argmode = ARG_WORD;
170 break;
171 case 'I':
172 if (strncmp(optarg, "os=", 3)) {
173 fprintf(stderr,
174 "%s: -I%s: Bad argument\n",
175 progname, optarg);
176 return((int)MANDOCLEVEL_BADARG);
177 }
178 if (defos) {
179 fprintf(stderr,
180 "%s: -I%s: Duplicate argument\n",
181 progname, optarg);
182 return((int)MANDOCLEVEL_BADARG);
183 }
184 defos = mandoc_strdup(optarg + 3);
185 break;
186 case 'i':
187 outmode = OUTMODE_INT;
188 break;
189 case 'k':
190 search.argmode = ARG_EXPR;
191 break;
192 case 'M':
193 defpaths = optarg;
194 break;
195 case 'm':
196 auxpaths = optarg;
197 break;
198 case 'O':
199 search.outkey = optarg;
200 (void)strlcat(curp.outopts, optarg, BUFSIZ);
201 (void)strlcat(curp.outopts, ",", BUFSIZ);
202 break;
203 case 'S':
204 search.arch = optarg;
205 break;
206 case 's':
207 search.sec = optarg;
208 break;
209 case 'T':
210 if ( ! toptions(&curp, optarg))
211 return((int)MANDOCLEVEL_BADARG);
212 break;
213 case 'W':
214 if ( ! woptions(&curp, optarg))
215 return((int)MANDOCLEVEL_BADARG);
216 break;
217 case 'w':
218 outmode = OUTMODE_FLN;
219 break;
220 case 'V':
221 version();
222 /* NOTREACHED */
223 default:
224 show_usage = 1;
225 break;
226 }
227 }
228
229 if (show_usage)
230 usage(search.argmode);
231
232 /* Postprocess options. */
233
234 if (outmode == OUTMODE_DEF) {
235 switch (search.argmode) {
236 case ARG_FILE:
237 outmode = OUTMODE_ALL;
238 use_pager = 0;
239 break;
240 case ARG_NAME:
241 outmode = OUTMODE_ONE;
242 break;
243 default:
244 outmode = OUTMODE_LST;
245 break;
246 }
247 }
248
249 /* Parse arguments. */
250
251 argc -= optind;
252 argv += optind;
253 #if HAVE_SQLITE3
254 resp = NULL;
255 #endif
256
257 /* Quirk for a man(1) section argument without -s. */
258
259 if (search.argmode == ARG_NAME &&
260 argv[0] != NULL &&
261 isdigit((unsigned char)argv[0][0]) &&
262 (argv[0][1] == '\0' || !strcmp(argv[0], "3p"))) {
263 search.sec = argv[0];
264 argv++;
265 argc--;
266 }
267
268 rc = MANDOCLEVEL_OK;
269
270 /* man(1), whatis(1), apropos(1) */
271
272 if (search.argmode != ARG_FILE) {
273 #if HAVE_SQLITE3
274 if (argc == 0)
275 usage(search.argmode);
276
277 /* Access the mandoc database. */
278
279 manpath_parse(&paths, conf_file, defpaths, auxpaths);
280 mansearch_setup(1);
281 if( ! mansearch(&search, &paths, argc, argv, &res, &sz))
282 usage(search.argmode);
283 manpath_free(&paths);
284 resp = res;
285
286 if (sz == 0) {
287 if (search.argmode == ARG_NAME)
288 fprintf(stderr, "%s: No entry for %s "
289 "in the manual.\n", progname, argv[0]);
290 rc = MANDOCLEVEL_BADARG;
291 goto out;
292 }
293
294 /*
295 * For standard man(1) and -a output mode,
296 * prepare for copying filename pointers
297 * into the program parameter array.
298 */
299
300 if (outmode == OUTMODE_ONE) {
301 argc = 1;
302 best_prio = 10;
303 } else if (outmode == OUTMODE_ALL)
304 argc = (int)sz;
305
306 /* Iterate all matching manuals. */
307
308 for (i = 0; i < sz; i++) {
309 if (outmode == OUTMODE_FLN)
310 puts(res[i].file);
311 else if (outmode == OUTMODE_LST)
312 printf("%s - %s\n", res[i].names,
313 res[i].output == NULL ? "" :
314 res[i].output);
315 else if (outmode == OUTMODE_ONE) {
316 /* Search for the best section. */
317 isec = strcspn(res[i].file, "123456789");
318 sec = res[i].file[isec];
319 if ('\0' == sec)
320 continue;
321 prio = sec_prios[sec - '1'];
322 if (prio >= best_prio)
323 continue;
324 best_prio = prio;
325 resp = res + i;
326 }
327 }
328
329 /*
330 * For man(1), -a and -i output mode, fall through
331 * to the main mandoc(1) code iterating files
332 * and running the parsers on each of them.
333 */
334
335 if (outmode == OUTMODE_FLN || outmode == OUTMODE_LST)
336 goto out;
337 #else
338 fputs("mandoc: database support not compiled in\n",
339 stderr);
340 return((int)MANDOCLEVEL_BADARG);
341 #endif
342 }
343
344 /* mandoc(1) */
345
346 if ( ! moptions(&options, auxpaths))
347 return((int)MANDOCLEVEL_BADARG);
348
349 if (use_pager && isatty(STDOUT_FILENO))
350 spawn_pager();
351
352 curp.mp = mparse_alloc(options, curp.wlevel, mmsg, defos);
353
354 /*
355 * Conditionally start up the lookaside buffer before parsing.
356 */
357 if (OUTT_MAN == curp.outtype)
358 mparse_keep(curp.mp);
359
360 if (argc == 0)
361 parse(&curp, STDIN_FILENO, "<stdin>", &rc);
362
363 while (argc) {
364 #if HAVE_SQLITE3
365 if (resp != NULL) {
366 if (resp->form)
367 parse(&curp, -1, resp->file, &rc);
368 else
369 rc = passthrough(resp->file);
370 resp++;
371 } else
372 #endif
373 parse(&curp, -1, *argv++, &rc);
374 if (MANDOCLEVEL_OK != rc && curp.wstop)
375 break;
376 argc--;
377 }
378
379 if (curp.outfree)
380 (*curp.outfree)(curp.outdata);
381 if (curp.mp)
382 mparse_free(curp.mp);
383
384 #if HAVE_SQLITE3
385 out:
386 if (search.argmode != ARG_FILE) {
387 mansearch_free(res, sz);
388 mansearch_setup(0);
389 }
390 #endif
391
392 free(defos);
393
394 return((int)rc);
395 }
396
397 static void
398 version(void)
399 {
400
401 printf("mandoc %s\n", VERSION);
402 exit((int)MANDOCLEVEL_OK);
403 }
404
405 static void
406 usage(enum argmode argmode)
407 {
408
409 switch (argmode) {
410 case ARG_FILE:
411 fputs("usage: mandoc [-V] [-Ios=name] [-mformat]"
412 " [-Ooption] [-Toutput] [-Wlevel]\n"
413 "\t [file ...]\n", stderr);
414 break;
415 case ARG_NAME:
416 fputs("usage: man [-acfhkVw] [-C file] "
417 "[-M path] [-m path] [-S arch] [-s section]\n"
418 "\t [section] name ...\n", stderr);
419 break;
420 case ARG_WORD:
421 fputs("usage: whatis [-V] [-C file] [-M path] [-m path] "
422 "[-S arch] [-s section] name ...\n", stderr);
423 break;
424 case ARG_EXPR:
425 fputs("usage: apropos [-V] [-C file] [-M path] [-m path] "
426 "[-O outkey] [-S arch]\n"
427 "\t [-s section] expression ...\n", stderr);
428 break;
429 }
430 exit((int)MANDOCLEVEL_BADARG);
431 }
432
433 static void
434 parse(struct curparse *curp, int fd, const char *file,
435 enum mandoclevel *level)
436 {
437 enum mandoclevel rc;
438 struct mdoc *mdoc;
439 struct man *man;
440
441 /* Begin by parsing the file itself. */
442
443 assert(file);
444 assert(fd >= -1);
445
446 rc = mparse_readfd(curp->mp, fd, file);
447
448 /* Stop immediately if the parse has failed. */
449
450 if (MANDOCLEVEL_FATAL <= rc)
451 goto cleanup;
452
453 /*
454 * With -Wstop and warnings or errors of at least the requested
455 * level, do not produce output.
456 */
457
458 if (MANDOCLEVEL_OK != rc && curp->wstop)
459 goto cleanup;
460
461 /* If unset, allocate output dev now (if applicable). */
462
463 if ( ! (curp->outman && curp->outmdoc)) {
464 switch (curp->outtype) {
465 case OUTT_XHTML:
466 curp->outdata = xhtml_alloc(curp->outopts);
467 curp->outfree = html_free;
468 break;
469 case OUTT_HTML:
470 curp->outdata = html_alloc(curp->outopts);
471 curp->outfree = html_free;
472 break;
473 case OUTT_UTF8:
474 curp->outdata = utf8_alloc(curp->outopts);
475 curp->outfree = ascii_free;
476 break;
477 case OUTT_LOCALE:
478 curp->outdata = locale_alloc(curp->outopts);
479 curp->outfree = ascii_free;
480 break;
481 case OUTT_ASCII:
482 curp->outdata = ascii_alloc(curp->outopts);
483 curp->outfree = ascii_free;
484 break;
485 case OUTT_PDF:
486 curp->outdata = pdf_alloc(curp->outopts);
487 curp->outfree = pspdf_free;
488 break;
489 case OUTT_PS:
490 curp->outdata = ps_alloc(curp->outopts);
491 curp->outfree = pspdf_free;
492 break;
493 default:
494 break;
495 }
496
497 switch (curp->outtype) {
498 case OUTT_HTML:
499 /* FALLTHROUGH */
500 case OUTT_XHTML:
501 curp->outman = html_man;
502 curp->outmdoc = html_mdoc;
503 break;
504 case OUTT_TREE:
505 curp->outman = tree_man;
506 curp->outmdoc = tree_mdoc;
507 break;
508 case OUTT_MAN:
509 curp->outmdoc = man_mdoc;
510 curp->outman = man_man;
511 break;
512 case OUTT_PDF:
513 /* FALLTHROUGH */
514 case OUTT_ASCII:
515 /* FALLTHROUGH */
516 case OUTT_UTF8:
517 /* FALLTHROUGH */
518 case OUTT_LOCALE:
519 /* FALLTHROUGH */
520 case OUTT_PS:
521 curp->outman = terminal_man;
522 curp->outmdoc = terminal_mdoc;
523 break;
524 default:
525 break;
526 }
527 }
528
529 mparse_result(curp->mp, &mdoc, &man, NULL);
530
531 /* Execute the out device, if it exists. */
532
533 if (man && curp->outman)
534 (*curp->outman)(curp->outdata, man);
535 if (mdoc && curp->outmdoc)
536 (*curp->outmdoc)(curp->outdata, mdoc);
537
538 cleanup:
539
540 mparse_reset(curp->mp);
541
542 if (*level < rc)
543 *level = rc;
544 }
545
546 static enum mandoclevel
547 passthrough(const char *file)
548 {
549 char buf[BUFSIZ];
550 const char *syscall;
551 ssize_t nr, nw, off;
552 int fd;
553
554 fd = open(file, O_RDONLY);
555 if (fd == -1) {
556 syscall = "open";
557 goto fail;
558 }
559
560 while ((nr = read(fd, buf, BUFSIZ)) != -1 && nr != 0)
561 for (off = 0; off < nr; off += nw)
562 if ((nw = write(STDOUT_FILENO, buf + off,
563 (size_t)(nr - off))) == -1 || nw == 0) {
564 syscall = "write";
565 goto fail;
566 }
567
568 if (nr == 0) {
569 close(fd);
570 return(MANDOCLEVEL_OK);
571 }
572
573 syscall = "read";
574 fail:
575 fprintf(stderr, "%s: %s: SYSERR: %s: %s",
576 progname, file, syscall, strerror(errno));
577 return(MANDOCLEVEL_SYSERR);
578 }
579
580 static int
581 moptions(int *options, char *arg)
582 {
583
584 if (arg == NULL)
585 /* nothing to do */;
586 else if (0 == strcmp(arg, "doc"))
587 *options |= MPARSE_MDOC;
588 else if (0 == strcmp(arg, "andoc"))
589 /* nothing to do */;
590 else if (0 == strcmp(arg, "an"))
591 *options |= MPARSE_MAN;
592 else {
593 fprintf(stderr, "%s: -m%s: Bad argument\n",
594 progname, arg);
595 return(0);
596 }
597
598 return(1);
599 }
600
601 static int
602 toptions(struct curparse *curp, char *arg)
603 {
604
605 if (0 == strcmp(arg, "ascii"))
606 curp->outtype = OUTT_ASCII;
607 else if (0 == strcmp(arg, "lint")) {
608 curp->outtype = OUTT_LINT;
609 curp->wlevel = MANDOCLEVEL_WARNING;
610 } else if (0 == strcmp(arg, "tree"))
611 curp->outtype = OUTT_TREE;
612 else if (0 == strcmp(arg, "man"))
613 curp->outtype = OUTT_MAN;
614 else if (0 == strcmp(arg, "html"))
615 curp->outtype = OUTT_HTML;
616 else if (0 == strcmp(arg, "utf8"))
617 curp->outtype = OUTT_UTF8;
618 else if (0 == strcmp(arg, "locale"))
619 curp->outtype = OUTT_LOCALE;
620 else if (0 == strcmp(arg, "xhtml"))
621 curp->outtype = OUTT_XHTML;
622 else if (0 == strcmp(arg, "ps"))
623 curp->outtype = OUTT_PS;
624 else if (0 == strcmp(arg, "pdf"))
625 curp->outtype = OUTT_PDF;
626 else {
627 fprintf(stderr, "%s: -T%s: Bad argument\n",
628 progname, arg);
629 return(0);
630 }
631
632 return(1);
633 }
634
635 static int
636 woptions(struct curparse *curp, char *arg)
637 {
638 char *v, *o;
639 const char *toks[6];
640
641 toks[0] = "stop";
642 toks[1] = "all";
643 toks[2] = "warning";
644 toks[3] = "error";
645 toks[4] = "fatal";
646 toks[5] = NULL;
647
648 while (*arg) {
649 o = arg;
650 switch (getsubopt(&arg, UNCONST(toks), &v)) {
651 case 0:
652 curp->wstop = 1;
653 break;
654 case 1:
655 /* FALLTHROUGH */
656 case 2:
657 curp->wlevel = MANDOCLEVEL_WARNING;
658 break;
659 case 3:
660 curp->wlevel = MANDOCLEVEL_ERROR;
661 break;
662 case 4:
663 curp->wlevel = MANDOCLEVEL_FATAL;
664 break;
665 default:
666 fprintf(stderr, "%s: -W%s: Bad argument\n",
667 progname, o);
668 return(0);
669 }
670 }
671
672 return(1);
673 }
674
675 static void
676 mmsg(enum mandocerr t, enum mandoclevel lvl,
677 const char *file, int line, int col, const char *msg)
678 {
679 const char *mparse_msg;
680
681 fprintf(stderr, "%s: %s:", progname, file);
682
683 if (line)
684 fprintf(stderr, "%d:%d:", line, col + 1);
685
686 fprintf(stderr, " %s", mparse_strlevel(lvl));
687
688 if (NULL != (mparse_msg = mparse_strerror(t)))
689 fprintf(stderr, ": %s", mparse_msg);
690
691 if (msg)
692 fprintf(stderr, ": %s", msg);
693
694 fputc('\n', stderr);
695 }
696
697 static void
698 spawn_pager(void)
699 {
700 #define MAX_PAGER_ARGS 16
701 char *argv[MAX_PAGER_ARGS];
702 const char *pager;
703 char *cp;
704 int fildes[2];
705 int argc;
706
707 if (pipe(fildes) == -1) {
708 fprintf(stderr, "%s: pipe: %s\n",
709 progname, strerror(errno));
710 return;
711 }
712
713 switch (fork()) {
714 case -1:
715 fprintf(stderr, "%s: fork: %s\n",
716 progname, strerror(errno));
717 exit((int)MANDOCLEVEL_SYSERR);
718 case 0:
719 close(fildes[0]);
720 if (dup2(fildes[1], STDOUT_FILENO) == -1) {
721 fprintf(stderr, "%s: dup output: %s\n",
722 progname, strerror(errno));
723 exit((int)MANDOCLEVEL_SYSERR);
724 }
725 return;
726 default:
727 break;
728 }
729
730 /* The original process becomes the pager. */
731
732 close(fildes[1]);
733 if (dup2(fildes[0], STDIN_FILENO) == -1) {
734 fprintf(stderr, "%s: dup input: %s\n",
735 progname, strerror(errno));
736 exit((int)MANDOCLEVEL_SYSERR);
737 }
738
739 pager = getenv("MANPAGER");
740 if (pager == NULL || *pager == '\0')
741 pager = getenv("PAGER");
742 if (pager == NULL || *pager == '\0')
743 pager = "/usr/bin/more -s";
744 cp = mandoc_strdup(pager);
745
746 /*
747 * Parse the pager command into words.
748 * Intentionally do not do anything fancy here.
749 */
750
751 argc = 0;
752 while (argc + 1 < MAX_PAGER_ARGS) {
753 argv[argc++] = cp;
754 cp = strchr(cp, ' ');
755 if (cp == NULL)
756 break;
757 *cp++ = '\0';
758 while (*cp == ' ')
759 cp++;
760 if (*cp == '\0')
761 break;
762 }
763 argv[argc] = NULL;
764
765 /* Hand over to the pager. */
766
767 execvp(argv[0], argv);
768 fprintf(stderr, "%s: exec: %s\n",
769 progname, strerror(errno));
770 exit((int)MANDOCLEVEL_SYSERR);
771 }