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