]> git.cameronkatri.com Git - mandoc.git/blob - main.c
Introduce a man(1) -l option as an alias for mandoc -a.
[mandoc.git] / main.c
1 /* $Id: main.c,v 1.188 2014/08/30 18:08:10 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:iklM: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 'l':
193 search.argmode = ARG_FILE;
194 outmode = OUTMODE_ALL;
195 break;
196 case 'M':
197 defpaths = optarg;
198 break;
199 case 'm':
200 auxpaths = optarg;
201 break;
202 case 'O':
203 search.outkey = optarg;
204 (void)strlcat(curp.outopts, optarg, BUFSIZ);
205 (void)strlcat(curp.outopts, ",", BUFSIZ);
206 break;
207 case 'S':
208 search.arch = optarg;
209 break;
210 case 's':
211 search.sec = optarg;
212 break;
213 case 'T':
214 if ( ! toptions(&curp, optarg))
215 return((int)MANDOCLEVEL_BADARG);
216 break;
217 case 'W':
218 if ( ! woptions(&curp, optarg))
219 return((int)MANDOCLEVEL_BADARG);
220 break;
221 case 'w':
222 outmode = OUTMODE_FLN;
223 break;
224 case 'V':
225 version();
226 /* NOTREACHED */
227 default:
228 show_usage = 1;
229 break;
230 }
231 }
232
233 if (show_usage)
234 usage(search.argmode);
235
236 /* Postprocess options. */
237
238 if (outmode == OUTMODE_DEF) {
239 switch (search.argmode) {
240 case ARG_FILE:
241 outmode = OUTMODE_ALL;
242 use_pager = 0;
243 break;
244 case ARG_NAME:
245 outmode = OUTMODE_ONE;
246 break;
247 default:
248 outmode = OUTMODE_LST;
249 break;
250 }
251 }
252
253 /* Parse arguments. */
254
255 argc -= optind;
256 argv += optind;
257 #if HAVE_SQLITE3
258 resp = NULL;
259 #endif
260
261 /* Quirk for a man(1) section argument without -s. */
262
263 if (search.argmode == ARG_NAME &&
264 argv[0] != NULL &&
265 isdigit((unsigned char)argv[0][0]) &&
266 (argv[0][1] == '\0' || !strcmp(argv[0], "3p"))) {
267 search.sec = argv[0];
268 argv++;
269 argc--;
270 }
271
272 rc = MANDOCLEVEL_OK;
273
274 /* man(1), whatis(1), apropos(1) */
275
276 if (search.argmode != ARG_FILE) {
277 #if HAVE_SQLITE3
278 if (argc == 0)
279 usage(search.argmode);
280
281 /* Access the mandoc database. */
282
283 manpath_parse(&paths, conf_file, defpaths, auxpaths);
284 mansearch_setup(1);
285 if( ! mansearch(&search, &paths, argc, argv, &res, &sz))
286 usage(search.argmode);
287 manpath_free(&paths);
288 resp = res;
289
290 if (sz == 0) {
291 if (search.argmode == ARG_NAME)
292 fprintf(stderr, "%s: No entry for %s "
293 "in the manual.\n", progname, argv[0]);
294 rc = MANDOCLEVEL_BADARG;
295 goto out;
296 }
297
298 /*
299 * For standard man(1) and -a output mode,
300 * prepare for copying filename pointers
301 * into the program parameter array.
302 */
303
304 if (outmode == OUTMODE_ONE) {
305 argc = 1;
306 best_prio = 10;
307 } else if (outmode == OUTMODE_ALL)
308 argc = (int)sz;
309
310 /* Iterate all matching manuals. */
311
312 for (i = 0; i < sz; i++) {
313 if (outmode == OUTMODE_FLN)
314 puts(res[i].file);
315 else if (outmode == OUTMODE_LST)
316 printf("%s - %s\n", res[i].names,
317 res[i].output == NULL ? "" :
318 res[i].output);
319 else if (outmode == OUTMODE_ONE) {
320 /* Search for the best section. */
321 isec = strcspn(res[i].file, "123456789");
322 sec = res[i].file[isec];
323 if ('\0' == sec)
324 continue;
325 prio = sec_prios[sec - '1'];
326 if (prio >= best_prio)
327 continue;
328 best_prio = prio;
329 resp = res + i;
330 }
331 }
332
333 /*
334 * For man(1), -a and -i output mode, fall through
335 * to the main mandoc(1) code iterating files
336 * and running the parsers on each of them.
337 */
338
339 if (outmode == OUTMODE_FLN || outmode == OUTMODE_LST)
340 goto out;
341 #else
342 fputs("mandoc: database support not compiled in\n",
343 stderr);
344 return((int)MANDOCLEVEL_BADARG);
345 #endif
346 }
347
348 /* mandoc(1) */
349
350 if ( ! moptions(&options, auxpaths))
351 return((int)MANDOCLEVEL_BADARG);
352
353 if (use_pager && isatty(STDOUT_FILENO))
354 spawn_pager();
355
356 curp.mp = mparse_alloc(options, curp.wlevel, mmsg, defos);
357
358 /*
359 * Conditionally start up the lookaside buffer before parsing.
360 */
361 if (OUTT_MAN == curp.outtype)
362 mparse_keep(curp.mp);
363
364 if (argc == 0)
365 parse(&curp, STDIN_FILENO, "<stdin>", &rc);
366
367 while (argc) {
368 #if HAVE_SQLITE3
369 if (resp != NULL) {
370 if (resp->form)
371 parse(&curp, -1, resp->file, &rc);
372 else
373 rc = passthrough(resp->file);
374 resp++;
375 } else
376 #endif
377 parse(&curp, -1, *argv++, &rc);
378 if (MANDOCLEVEL_OK != rc && curp.wstop)
379 break;
380 argc--;
381 }
382
383 if (curp.outfree)
384 (*curp.outfree)(curp.outdata);
385 if (curp.mp)
386 mparse_free(curp.mp);
387
388 #if HAVE_SQLITE3
389 out:
390 if (search.argmode != ARG_FILE) {
391 mansearch_free(res, sz);
392 mansearch_setup(0);
393 }
394 #endif
395
396 free(defos);
397
398 return((int)rc);
399 }
400
401 static void
402 version(void)
403 {
404
405 printf("mandoc %s\n", VERSION);
406 exit((int)MANDOCLEVEL_OK);
407 }
408
409 static void
410 usage(enum argmode argmode)
411 {
412
413 switch (argmode) {
414 case ARG_FILE:
415 fputs("usage: mandoc [-acfklV] [-Ios=name] "
416 "[-mformat] [-Ooption] [-Toutput] [-Wlevel]\n"
417 "\t [file ...]\n", stderr);
418 break;
419 case ARG_NAME:
420 fputs("usage: man [-acfhklVw] [-C file] "
421 "[-M path] [-m path] [-S arch] [-s section]\n"
422 "\t [section] name ...\n", stderr);
423 break;
424 case ARG_WORD:
425 fputs("usage: whatis [-acfklVw] [-C file] "
426 "[-M path] [-m path] [-O outkey] [-S arch]\n"
427 "\t [-s section] name ...\n", stderr);
428 break;
429 case ARG_EXPR:
430 fputs("usage: apropos [-acfklVw] [-C file] "
431 "[-M path] [-m path] [-O outkey] [-S arch]\n"
432 "\t [-s section] expression ...\n", stderr);
433 break;
434 }
435 exit((int)MANDOCLEVEL_BADARG);
436 }
437
438 static void
439 parse(struct curparse *curp, int fd, const char *file,
440 enum mandoclevel *level)
441 {
442 enum mandoclevel rc;
443 struct mdoc *mdoc;
444 struct man *man;
445
446 /* Begin by parsing the file itself. */
447
448 assert(file);
449 assert(fd >= -1);
450
451 rc = mparse_readfd(curp->mp, fd, file);
452
453 /* Stop immediately if the parse has failed. */
454
455 if (MANDOCLEVEL_FATAL <= rc)
456 goto cleanup;
457
458 /*
459 * With -Wstop and warnings or errors of at least the requested
460 * level, do not produce output.
461 */
462
463 if (MANDOCLEVEL_OK != rc && curp->wstop)
464 goto cleanup;
465
466 /* If unset, allocate output dev now (if applicable). */
467
468 if ( ! (curp->outman && curp->outmdoc)) {
469 switch (curp->outtype) {
470 case OUTT_XHTML:
471 curp->outdata = xhtml_alloc(curp->outopts);
472 curp->outfree = html_free;
473 break;
474 case OUTT_HTML:
475 curp->outdata = html_alloc(curp->outopts);
476 curp->outfree = html_free;
477 break;
478 case OUTT_UTF8:
479 curp->outdata = utf8_alloc(curp->outopts);
480 curp->outfree = ascii_free;
481 break;
482 case OUTT_LOCALE:
483 curp->outdata = locale_alloc(curp->outopts);
484 curp->outfree = ascii_free;
485 break;
486 case OUTT_ASCII:
487 curp->outdata = ascii_alloc(curp->outopts);
488 curp->outfree = ascii_free;
489 break;
490 case OUTT_PDF:
491 curp->outdata = pdf_alloc(curp->outopts);
492 curp->outfree = pspdf_free;
493 break;
494 case OUTT_PS:
495 curp->outdata = ps_alloc(curp->outopts);
496 curp->outfree = pspdf_free;
497 break;
498 default:
499 break;
500 }
501
502 switch (curp->outtype) {
503 case OUTT_HTML:
504 /* FALLTHROUGH */
505 case OUTT_XHTML:
506 curp->outman = html_man;
507 curp->outmdoc = html_mdoc;
508 break;
509 case OUTT_TREE:
510 curp->outman = tree_man;
511 curp->outmdoc = tree_mdoc;
512 break;
513 case OUTT_MAN:
514 curp->outmdoc = man_mdoc;
515 curp->outman = man_man;
516 break;
517 case OUTT_PDF:
518 /* FALLTHROUGH */
519 case OUTT_ASCII:
520 /* FALLTHROUGH */
521 case OUTT_UTF8:
522 /* FALLTHROUGH */
523 case OUTT_LOCALE:
524 /* FALLTHROUGH */
525 case OUTT_PS:
526 curp->outman = terminal_man;
527 curp->outmdoc = terminal_mdoc;
528 break;
529 default:
530 break;
531 }
532 }
533
534 mparse_result(curp->mp, &mdoc, &man, NULL);
535
536 /* Execute the out device, if it exists. */
537
538 if (man && curp->outman)
539 (*curp->outman)(curp->outdata, man);
540 if (mdoc && curp->outmdoc)
541 (*curp->outmdoc)(curp->outdata, mdoc);
542
543 cleanup:
544
545 mparse_reset(curp->mp);
546
547 if (*level < rc)
548 *level = rc;
549 }
550
551 static enum mandoclevel
552 passthrough(const char *file)
553 {
554 char buf[BUFSIZ];
555 const char *syscall;
556 ssize_t nr, nw, off;
557 int fd;
558
559 fd = open(file, O_RDONLY);
560 if (fd == -1) {
561 syscall = "open";
562 goto fail;
563 }
564
565 while ((nr = read(fd, buf, BUFSIZ)) != -1 && nr != 0)
566 for (off = 0; off < nr; off += nw)
567 if ((nw = write(STDOUT_FILENO, buf + off,
568 (size_t)(nr - off))) == -1 || nw == 0) {
569 syscall = "write";
570 goto fail;
571 }
572
573 if (nr == 0) {
574 close(fd);
575 return(MANDOCLEVEL_OK);
576 }
577
578 syscall = "read";
579 fail:
580 fprintf(stderr, "%s: %s: SYSERR: %s: %s",
581 progname, file, syscall, strerror(errno));
582 return(MANDOCLEVEL_SYSERR);
583 }
584
585 static int
586 moptions(int *options, char *arg)
587 {
588
589 if (arg == NULL)
590 /* nothing to do */;
591 else if (0 == strcmp(arg, "doc"))
592 *options |= MPARSE_MDOC;
593 else if (0 == strcmp(arg, "andoc"))
594 /* nothing to do */;
595 else if (0 == strcmp(arg, "an"))
596 *options |= MPARSE_MAN;
597 else {
598 fprintf(stderr, "%s: -m%s: Bad argument\n",
599 progname, arg);
600 return(0);
601 }
602
603 return(1);
604 }
605
606 static int
607 toptions(struct curparse *curp, char *arg)
608 {
609
610 if (0 == strcmp(arg, "ascii"))
611 curp->outtype = OUTT_ASCII;
612 else if (0 == strcmp(arg, "lint")) {
613 curp->outtype = OUTT_LINT;
614 curp->wlevel = MANDOCLEVEL_WARNING;
615 } else if (0 == strcmp(arg, "tree"))
616 curp->outtype = OUTT_TREE;
617 else if (0 == strcmp(arg, "man"))
618 curp->outtype = OUTT_MAN;
619 else if (0 == strcmp(arg, "html"))
620 curp->outtype = OUTT_HTML;
621 else if (0 == strcmp(arg, "utf8"))
622 curp->outtype = OUTT_UTF8;
623 else if (0 == strcmp(arg, "locale"))
624 curp->outtype = OUTT_LOCALE;
625 else if (0 == strcmp(arg, "xhtml"))
626 curp->outtype = OUTT_XHTML;
627 else if (0 == strcmp(arg, "ps"))
628 curp->outtype = OUTT_PS;
629 else if (0 == strcmp(arg, "pdf"))
630 curp->outtype = OUTT_PDF;
631 else {
632 fprintf(stderr, "%s: -T%s: Bad argument\n",
633 progname, arg);
634 return(0);
635 }
636
637 return(1);
638 }
639
640 static int
641 woptions(struct curparse *curp, char *arg)
642 {
643 char *v, *o;
644 const char *toks[6];
645
646 toks[0] = "stop";
647 toks[1] = "all";
648 toks[2] = "warning";
649 toks[3] = "error";
650 toks[4] = "fatal";
651 toks[5] = NULL;
652
653 while (*arg) {
654 o = arg;
655 switch (getsubopt(&arg, UNCONST(toks), &v)) {
656 case 0:
657 curp->wstop = 1;
658 break;
659 case 1:
660 /* FALLTHROUGH */
661 case 2:
662 curp->wlevel = MANDOCLEVEL_WARNING;
663 break;
664 case 3:
665 curp->wlevel = MANDOCLEVEL_ERROR;
666 break;
667 case 4:
668 curp->wlevel = MANDOCLEVEL_FATAL;
669 break;
670 default:
671 fprintf(stderr, "%s: -W%s: Bad argument\n",
672 progname, o);
673 return(0);
674 }
675 }
676
677 return(1);
678 }
679
680 static void
681 mmsg(enum mandocerr t, enum mandoclevel lvl,
682 const char *file, int line, int col, const char *msg)
683 {
684 const char *mparse_msg;
685
686 fprintf(stderr, "%s: %s:", progname, file);
687
688 if (line)
689 fprintf(stderr, "%d:%d:", line, col + 1);
690
691 fprintf(stderr, " %s", mparse_strlevel(lvl));
692
693 if (NULL != (mparse_msg = mparse_strerror(t)))
694 fprintf(stderr, ": %s", mparse_msg);
695
696 if (msg)
697 fprintf(stderr, ": %s", msg);
698
699 fputc('\n', stderr);
700 }
701
702 static void
703 spawn_pager(void)
704 {
705 #define MAX_PAGER_ARGS 16
706 char *argv[MAX_PAGER_ARGS];
707 const char *pager;
708 char *cp;
709 int fildes[2];
710 int argc;
711
712 if (pipe(fildes) == -1) {
713 fprintf(stderr, "%s: pipe: %s\n",
714 progname, strerror(errno));
715 return;
716 }
717
718 switch (fork()) {
719 case -1:
720 fprintf(stderr, "%s: fork: %s\n",
721 progname, strerror(errno));
722 exit((int)MANDOCLEVEL_SYSERR);
723 case 0:
724 close(fildes[0]);
725 if (dup2(fildes[1], STDOUT_FILENO) == -1) {
726 fprintf(stderr, "%s: dup output: %s\n",
727 progname, strerror(errno));
728 exit((int)MANDOCLEVEL_SYSERR);
729 }
730 return;
731 default:
732 break;
733 }
734
735 /* The original process becomes the pager. */
736
737 close(fildes[1]);
738 if (dup2(fildes[0], STDIN_FILENO) == -1) {
739 fprintf(stderr, "%s: dup input: %s\n",
740 progname, strerror(errno));
741 exit((int)MANDOCLEVEL_SYSERR);
742 }
743
744 pager = getenv("MANPAGER");
745 if (pager == NULL || *pager == '\0')
746 pager = getenv("PAGER");
747 if (pager == NULL || *pager == '\0')
748 pager = "/usr/bin/more -s";
749 cp = mandoc_strdup(pager);
750
751 /*
752 * Parse the pager command into words.
753 * Intentionally do not do anything fancy here.
754 */
755
756 argc = 0;
757 while (argc + 1 < MAX_PAGER_ARGS) {
758 argv[argc++] = cp;
759 cp = strchr(cp, ' ');
760 if (cp == NULL)
761 break;
762 *cp++ = '\0';
763 while (*cp == ' ')
764 cp++;
765 if (*cp == '\0')
766 break;
767 }
768 argv[argc] = NULL;
769
770 /* Hand over to the pager. */
771
772 execvp(argv[0], argv);
773 fprintf(stderr, "%s: exec: %s\n",
774 progname, strerror(errno));
775 exit((int)MANDOCLEVEL_SYSERR);
776 }