1 /* $Id: main.c,v 1.269 2016/07/12 05:18:38 kristaps Exp $ */
3 * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
4 * Copyright (c) 2010-2012, 2014-2016 Ingo Schwarze <schwarze@openbsd.org>
5 * Copyright (c) 2010 Joerg Sonnenberger <joerg@netbsd.org>
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.
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.
21 #include <sys/types.h>
22 #include <sys/param.h> /* MACHINE */
44 #include "mandoc_aux.h"
52 #include "mansearch.h"
54 #if !defined(__GNUC__) || (__GNUC__ < 2)
56 # define __attribute__(x)
58 #endif /* !defined(__GNUC__) || (__GNUC__ < 2) */
70 OUTT_ASCII = 0, /* -Tascii */
71 OUTT_LOCALE, /* -Tlocale */
72 OUTT_UTF8, /* -Tutf8 */
73 OUTT_TREE, /* -Ttree */
75 OUTT_HTML, /* -Thtml */
76 OUTT_LINT, /* -Tlint */
83 enum mandoclevel wlevel; /* ignore messages below this */
84 int wstop; /* stop after a file with a warning */
85 enum outt outtype; /* which output to use */
86 void *outdata; /* data for output */
87 struct manoutput *outopts; /* output options */
90 static int fs_lookup(const struct manpaths *,
91 size_t ipath, const char *,
92 const char *, const char *,
93 struct manpage **, size_t *);
94 static void fs_search(const struct mansearch *,
95 const struct manpaths *, int, char**,
96 struct manpage **, size_t *);
97 static int koptions(int *, char *);
99 int mandocdb(int, char**);
101 static int moptions(int *, char *);
102 static void mmsg(enum mandocerr, enum mandoclevel,
103 const char *, int, int, const char *);
104 static void parse(struct curparse *, int, const char *);
105 static void passthrough(const char *, int, int);
106 static pid_t spawn_pager(struct tag_files *);
107 static int toptions(struct curparse *, char *);
108 static void usage(enum argmode) __attribute__((noreturn));
109 static int woptions(struct curparse *, char *);
111 static const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
112 static char help_arg[] = "help";
113 static char *help_argv[] = {help_arg, NULL};
114 static enum mandoclevel rc;
118 main(int argc, char *argv[])
121 struct curparse curp;
122 struct mansearch search;
123 struct tag_files *tag_files;
124 const char *progname;
128 struct manpage *res, *resp;
129 char *conf_file, *defpaths;
133 enum outmode outmode;
140 pid_t pager_pid, tc_pgid, man_pgid, pid;
143 progname = getprogname();
146 progname = mandoc_strdup("mandoc");
147 else if ((progname = strrchr(argv[0], '/')) == NULL)
151 setprogname(progname);
155 if (strncmp(progname, "mandocdb", 8) == 0 ||
156 strcmp(progname, BINM_MAKEWHATIS) == 0)
157 return mandocdb(argc, argv);
161 if (pledge("stdio rpath tmppath tty proc exec flock", NULL) == -1)
162 err((int)MANDOCLEVEL_SYSERR, "pledge");
165 #if HAVE_SANDBOX_INIT
166 if (sandbox_init(kSBXProfileNoInternet, SANDBOX_NAMED, NULL) == -1)
167 errx((int)MANDOCLEVEL_SYSERR, "sandbox_init");
170 /* Search options. */
172 memset(&conf, 0, sizeof(conf));
173 conf_file = defpaths = NULL;
176 memset(&search, 0, sizeof(struct mansearch));
177 search.outkey = "Nd";
179 if (strcmp(progname, BINM_MAN) == 0)
180 search.argmode = ARG_NAME;
181 else if (strcmp(progname, BINM_APROPOS) == 0)
182 search.argmode = ARG_EXPR;
183 else if (strcmp(progname, BINM_WHATIS) == 0)
184 search.argmode = ARG_WORD;
185 else if (strncmp(progname, "help", 4) == 0)
186 search.argmode = ARG_NAME;
188 search.argmode = ARG_FILE;
190 /* Parser and formatter options. */
192 memset(&curp, 0, sizeof(struct curparse));
193 curp.outtype = OUTT_LOCALE;
194 curp.wlevel = MANDOCLEVEL_BADARG;
195 curp.outopts = &conf.output;
196 options = MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1;
202 outmode = OUTMODE_DEF;
204 while (-1 != (c = getopt(argc, argv,
205 "aC:cfhI:iK:klM:m:O:S:s:T:VW:w"))) {
208 outmode = OUTMODE_ALL;
217 search.argmode = ARG_WORD;
220 conf.output.synopsisonly = 1;
222 outmode = OUTMODE_ALL;
225 if (strncmp(optarg, "os=", 3)) {
226 warnx("-I %s: Bad argument", optarg);
227 return (int)MANDOCLEVEL_BADARG;
230 warnx("-I %s: Duplicate argument", optarg);
231 return (int)MANDOCLEVEL_BADARG;
233 defos = mandoc_strdup(optarg + 3);
236 outmode = OUTMODE_INT;
239 if ( ! koptions(&options, optarg))
240 return (int)MANDOCLEVEL_BADARG;
243 search.argmode = ARG_EXPR;
246 search.argmode = ARG_FILE;
247 outmode = OUTMODE_ALL;
256 search.outkey = optarg;
257 while (optarg != NULL)
258 manconf_output(&conf.output,
259 strsep(&optarg, ","));
262 search.arch = optarg;
268 if ( ! toptions(&curp, optarg))
269 return (int)MANDOCLEVEL_BADARG;
272 if ( ! woptions(&curp, optarg))
273 return (int)MANDOCLEVEL_BADARG;
276 outmode = OUTMODE_FLN;
285 usage(search.argmode);
287 /* Postprocess options. */
289 if (outmode == OUTMODE_DEF) {
290 switch (search.argmode) {
292 outmode = OUTMODE_ALL;
296 outmode = OUTMODE_ONE;
299 outmode = OUTMODE_LST;
304 if (outmode == OUTMODE_FLN ||
305 outmode == OUTMODE_LST ||
306 !isatty(STDOUT_FILENO))
311 if (pledge("stdio rpath flock", NULL) == -1)
312 err((int)MANDOCLEVEL_SYSERR, "pledge");
315 /* Parse arguments. */
325 * and for a man(1) section argument without -s.
328 if (search.argmode == ARG_NAME) {
329 if (*progname == 'h') {
334 } else if (argc > 1 &&
335 ((uc = (unsigned char *)argv[0]) != NULL) &&
336 ((isdigit(uc[0]) && (uc[1] == '\0' ||
337 (isalpha(uc[1]) && uc[2] == '\0'))) ||
338 (uc[0] == 'n' && uc[1] == '\0'))) {
339 search.sec = (char *)uc;
343 if (search.arch == NULL)
344 search.arch = getenv("MACHINE");
346 if (search.arch == NULL)
347 search.arch = MACHINE;
353 /* man(1), whatis(1), apropos(1) */
355 if (search.argmode != ARG_FILE) {
357 usage(search.argmode);
359 if (search.argmode == ARG_NAME &&
360 outmode == OUTMODE_ONE)
361 search.firstmatch = 1;
363 /* Access the mandoc database. */
365 manconf_parse(&conf, conf_file, defpaths, auxpaths);
368 if ( ! mansearch(&search, &conf.manpath,
369 argc, argv, &res, &sz))
370 usage(search.argmode);
372 if (search.argmode != ARG_NAME) {
373 fputs("mandoc: database support not compiled in\n",
375 return (int)MANDOCLEVEL_BADARG;
381 if (search.argmode == ARG_NAME)
382 fs_search(&search, &conf.manpath,
383 argc, argv, &res, &sz);
385 warnx("nothing appropriate");
389 rc = MANDOCLEVEL_BADARG;
394 * For standard man(1) and -a output mode,
395 * prepare for copying filename pointers
396 * into the program parameter array.
399 if (outmode == OUTMODE_ONE) {
402 } else if (outmode == OUTMODE_ALL)
405 /* Iterate all matching manuals. */
408 for (i = 0; i < sz; i++) {
409 if (outmode == OUTMODE_FLN)
411 else if (outmode == OUTMODE_LST)
412 printf("%s - %s\n", res[i].names,
413 res[i].output == NULL ? "" :
415 else if (outmode == OUTMODE_ONE) {
416 /* Search for the best section. */
418 sec += strcspn(sec, "123456789");
421 prio = sec_prios[sec[0] - '1'];
424 if (prio >= best_prio)
432 * For man(1), -a and -i output mode, fall through
433 * to the main mandoc(1) code iterating files
434 * and running the parsers on each of them.
437 if (outmode == OUTMODE_FLN || outmode == OUTMODE_LST)
445 if (pledge("stdio rpath tmppath tty proc exec", NULL) == -1)
446 err((int)MANDOCLEVEL_SYSERR, "pledge");
448 if (pledge("stdio rpath", NULL) == -1)
449 err((int)MANDOCLEVEL_SYSERR, "pledge");
453 if (search.argmode == ARG_FILE && ! moptions(&options, auxpaths))
454 return (int)MANDOCLEVEL_BADARG;
457 curp.mp = mparse_alloc(options, curp.wlevel, mmsg, defos);
460 * Conditionally start up the lookaside buffer before parsing.
462 if (OUTT_MAN == curp.outtype)
463 mparse_keep(curp.mp);
467 tag_files = tag_init();
468 parse(&curp, STDIN_FILENO, "<stdin>");
472 fd = mparse_open(curp.mp, resp != NULL ? resp->file : *argv);
475 tag_files = tag_init();
480 parse(&curp, fd, *argv);
481 else if (resp->form & FORM_SRC) {
482 /* For .so only; ignore failure. */
483 chdir(conf.manpath.paths[resp->ipath]);
484 parse(&curp, fd, resp->file);
486 passthrough(resp->file, fd,
487 conf.output.synopsisonly);
489 if (argc > 1 && curp.outtype <= OUTT_UTF8)
490 terminal_sepline(curp.outdata);
491 } else if (rc < MANDOCLEVEL_ERROR)
492 rc = MANDOCLEVEL_ERROR;
494 if (MANDOCLEVEL_OK != rc && curp.wstop)
502 mparse_reset(curp.mp);
505 if (curp.outdata != NULL) {
506 switch (curp.outtype) {
508 html_free(curp.outdata);
513 ascii_free(curp.outdata);
517 pspdf_free(curp.outdata);
523 mparse_free(curp.mp);
527 if (search.argmode != ARG_FILE) {
530 mansearch_free(res, sz);
538 * When using a pager, finish writing both temporary files,
539 * fork it, wait for the user to close it, and clean up.
542 if (tag_files != NULL) {
545 man_pgid = getpgid(0);
546 tag_files->tcpgid = man_pgid == getpid() ?
547 getpgid(getppid()) : man_pgid;
552 /* Stop here until moved to the foreground. */
554 tc_pgid = tcgetpgrp(STDIN_FILENO);
555 if (tc_pgid != man_pgid) {
556 if (tc_pgid == pager_pid) {
557 (void)tcsetpgrp(STDIN_FILENO,
559 if (signum == SIGTTIN)
562 tag_files->tcpgid = tc_pgid;
567 /* Once in the foreground, activate the pager. */
570 (void)tcsetpgrp(STDIN_FILENO, pager_pid);
571 kill(pager_pid, SIGCONT);
573 pager_pid = spawn_pager(tag_files);
575 /* Wait for the pager to stop or exit. */
577 while ((pid = waitpid(pager_pid, &status,
578 WUNTRACED)) == -1 && errno == EINTR)
583 rc = MANDOCLEVEL_SYSERR;
586 if (!WIFSTOPPED(status))
589 signum = WSTOPSIG(status);
598 usage(enum argmode argmode)
603 fputs("usage: mandoc [-acfhkl] [-I os=name] "
604 "[-K encoding] [-mformat] [-O option]\n"
605 "\t [-T output] [-W level] [file ...]\n", stderr);
608 fputs("usage: man [-acfhklw] [-C file] [-I os=name] "
609 "[-K encoding] [-M path] [-m path]\n"
610 "\t [-O option=value] [-S subsection] [-s section] "
611 "[-T output] [-W level]\n"
612 "\t [section] name ...\n", stderr);
615 fputs("usage: whatis [-acfhklw] [-C file] "
616 "[-M path] [-m path] [-O outkey] [-S arch]\n"
617 "\t [-s section] name ...\n", stderr);
620 fputs("usage: apropos [-acfhklw] [-C file] "
621 "[-M path] [-m path] [-O outkey] [-S arch]\n"
622 "\t [-s section] expression ...\n", stderr);
625 exit((int)MANDOCLEVEL_BADARG);
629 fs_lookup(const struct manpaths *paths, size_t ipath,
630 const char *sec, const char *arch, const char *name,
631 struct manpage **res, size_t *ressz)
634 struct manpage *page;
639 mandoc_asprintf(&file, "%s/man%s/%s.%s",
640 paths->paths[ipath], sec, name, sec);
641 if (access(file, R_OK) != -1)
645 mandoc_asprintf(&file, "%s/cat%s/%s.0",
646 paths->paths[ipath], sec, name);
647 if (access(file, R_OK) != -1) {
654 mandoc_asprintf(&file, "%s/man%s/%s/%s.%s",
655 paths->paths[ipath], sec, arch, name, sec);
656 if (access(file, R_OK) != -1)
661 mandoc_asprintf(&file, "%s/man%s/%s.[01-9]*",
662 paths->paths[ipath], sec, name);
663 globres = glob(file, 0, NULL, &globinfo);
664 if (globres != 0 && globres != GLOB_NOMATCH)
665 warn("%s: glob", file);
668 file = mandoc_strdup(*globinfo.gl_pathv);
675 warnx("outdated mandoc.db lacks %s(%s) entry, run %s %s",
676 name, sec, BINM_MAKEWHATIS, paths->paths[ipath]);
678 *res = mandoc_reallocarray(*res, ++*ressz, sizeof(struct manpage));
679 page = *res + (*ressz - 1);
684 page->bits = NAME_FILE & NAME_MASK;
685 page->sec = (*sec >= '1' && *sec <= '9') ? *sec - '1' + 1 : 10;
691 fs_search(const struct mansearch *cfg, const struct manpaths *paths,
692 int argc, char **argv, struct manpage **res, size_t *ressz)
694 const char *const sections[] =
695 {"1", "8", "6", "2", "3", "5", "7", "4", "9", "3p"};
696 const size_t nsec = sizeof(sections)/sizeof(sections[0]);
698 size_t ipath, isec, lastsz;
700 assert(cfg->argmode == ARG_NAME);
705 for (ipath = 0; ipath < paths->sz; ipath++) {
706 if (cfg->sec != NULL) {
707 if (fs_lookup(paths, ipath, cfg->sec,
708 cfg->arch, *argv, res, ressz) &&
711 } else for (isec = 0; isec < nsec; isec++)
712 if (fs_lookup(paths, ipath, sections[isec],
713 cfg->arch, *argv, res, ressz) &&
717 if (*ressz == lastsz)
718 warnx("No entry for %s in the manual.", *argv);
726 parse(struct curparse *curp, int fd, const char *file)
728 enum mandoclevel rctmp;
729 struct roff_man *man;
731 /* Begin by parsing the file itself. */
736 rctmp = mparse_readfd(curp->mp, fd, file);
737 if (fd != STDIN_FILENO)
743 * With -Wstop and warnings or errors of at least the requested
744 * level, do not produce output.
747 if (rctmp != MANDOCLEVEL_OK && curp->wstop)
750 /* If unset, allocate output dev now (if applicable). */
752 if (curp->outdata == NULL) {
753 switch (curp->outtype) {
755 curp->outdata = html_alloc(curp->outopts);
758 curp->outdata = utf8_alloc(curp->outopts);
761 curp->outdata = locale_alloc(curp->outopts);
764 curp->outdata = ascii_alloc(curp->outopts);
767 curp->outdata = pdf_alloc(curp->outopts);
770 curp->outdata = ps_alloc(curp->outopts);
777 mparse_result(curp->mp, &man, NULL);
779 /* Execute the out device, if it exists. */
783 if (man->macroset == MACROSET_MDOC) {
785 switch (curp->outtype) {
787 html_mdoc(curp->outdata, man);
790 tree_mdoc(curp->outdata, man);
793 man_mdoc(curp->outdata, man);
800 terminal_mdoc(curp->outdata, man);
806 if (man->macroset == MACROSET_MAN) {
808 switch (curp->outtype) {
810 html_man(curp->outdata, man);
813 tree_man(curp->outdata, man);
816 man_man(curp->outdata, man);
823 terminal_man(curp->outdata, man);
832 passthrough(const char *file, int fd, int synopsis_only)
834 const char synb[] = "S\bSY\bYN\bNO\bOP\bPS\bSI\bIS\bS";
835 const char synr[] = "SYNOPSIS";
846 if ((stream = fdopen(fd, "r")) == NULL) {
853 while (getline(&line, &linesz, stream) != -1) {
857 if ( ! isspace((unsigned char)*cp))
859 while (isspace((unsigned char)*cp))
862 if (strcmp(cp, synb) == 0 ||
863 strcmp(cp, synr) == 0)
868 if (fputs(cp, stdout)) {
875 if (ferror(stream)) {
888 warn("%s: SYSERR: %s", file, syscall);
889 if (rc < MANDOCLEVEL_SYSERR)
890 rc = MANDOCLEVEL_SYSERR;
894 koptions(int *options, char *arg)
897 if ( ! strcmp(arg, "utf-8")) {
898 *options |= MPARSE_UTF8;
899 *options &= ~MPARSE_LATIN1;
900 } else if ( ! strcmp(arg, "iso-8859-1")) {
901 *options |= MPARSE_LATIN1;
902 *options &= ~MPARSE_UTF8;
903 } else if ( ! strcmp(arg, "us-ascii")) {
904 *options &= ~(MPARSE_UTF8 | MPARSE_LATIN1);
906 warnx("-K %s: Bad argument", arg);
913 moptions(int *options, char *arg)
918 else if (0 == strcmp(arg, "doc"))
919 *options |= MPARSE_MDOC;
920 else if (0 == strcmp(arg, "andoc"))
922 else if (0 == strcmp(arg, "an"))
923 *options |= MPARSE_MAN;
925 warnx("-m %s: Bad argument", arg);
933 toptions(struct curparse *curp, char *arg)
936 if (0 == strcmp(arg, "ascii"))
937 curp->outtype = OUTT_ASCII;
938 else if (0 == strcmp(arg, "lint")) {
939 curp->outtype = OUTT_LINT;
940 curp->wlevel = MANDOCLEVEL_WARNING;
941 } else if (0 == strcmp(arg, "tree"))
942 curp->outtype = OUTT_TREE;
943 else if (0 == strcmp(arg, "man"))
944 curp->outtype = OUTT_MAN;
945 else if (0 == strcmp(arg, "html"))
946 curp->outtype = OUTT_HTML;
947 else if (0 == strcmp(arg, "utf8"))
948 curp->outtype = OUTT_UTF8;
949 else if (0 == strcmp(arg, "locale"))
950 curp->outtype = OUTT_LOCALE;
951 else if (0 == strcmp(arg, "xhtml"))
952 curp->outtype = OUTT_HTML;
953 else if (0 == strcmp(arg, "ps"))
954 curp->outtype = OUTT_PS;
955 else if (0 == strcmp(arg, "pdf"))
956 curp->outtype = OUTT_PDF;
958 warnx("-T %s: Bad argument", arg);
966 woptions(struct curparse *curp, char *arg)
981 switch (getsubopt(&arg, UNCONST(toks), &v)) {
987 curp->wlevel = MANDOCLEVEL_WARNING;
990 curp->wlevel = MANDOCLEVEL_ERROR;
993 curp->wlevel = MANDOCLEVEL_UNSUPP;
996 curp->wlevel = MANDOCLEVEL_BADARG;
999 warnx("-W %s: Bad argument", o);
1008 mmsg(enum mandocerr t, enum mandoclevel lvl,
1009 const char *file, int line, int col, const char *msg)
1011 const char *mparse_msg;
1013 fprintf(stderr, "%s: %s:", getprogname(), file);
1016 fprintf(stderr, "%d:%d:", line, col + 1);
1018 fprintf(stderr, " %s", mparse_strlevel(lvl));
1020 if (NULL != (mparse_msg = mparse_strerror(t)))
1021 fprintf(stderr, ": %s", mparse_msg);
1024 fprintf(stderr, ": %s", msg);
1026 fputc('\n', stderr);
1030 spawn_pager(struct tag_files *tag_files)
1032 const struct timespec timeout = { 0, 100000000 }; /* 0.1s */
1033 #define MAX_PAGER_ARGS 16
1034 char *argv[MAX_PAGER_ARGS];
1041 pager = getenv("MANPAGER");
1042 if (pager == NULL || *pager == '\0')
1043 pager = getenv("PAGER");
1044 if (pager == NULL || *pager == '\0')
1046 cp = mandoc_strdup(pager);
1049 * Parse the pager command into words.
1050 * Intentionally do not do anything fancy here.
1054 while (argc + 4 < MAX_PAGER_ARGS) {
1056 cp = strchr(cp, ' ');
1066 /* For less(1), use the tag file. */
1068 if ((cmdlen = strlen(argv[0])) >= 4) {
1069 cp = argv[0] + cmdlen - 4;
1070 if (strcmp(cp, "less") == 0) {
1071 argv[argc++] = mandoc_strdup("-T");
1072 argv[argc++] = tag_files->tfn;
1075 argv[argc++] = tag_files->ofn;
1078 switch (pager_pid = fork()) {
1080 err((int)MANDOCLEVEL_SYSERR, "fork");
1084 (void)setpgid(pager_pid, 0);
1085 (void)tcsetpgrp(STDIN_FILENO, pager_pid);
1087 if (pledge("stdio rpath tmppath tty proc", NULL) == -1)
1088 err((int)MANDOCLEVEL_SYSERR, "pledge");
1090 tag_files->pager_pid = pager_pid;
1094 /* The child process becomes the pager. */
1096 if (dup2(tag_files->ofd, STDOUT_FILENO) == -1)
1097 err((int)MANDOCLEVEL_SYSERR, "pager stdout");
1098 close(tag_files->ofd);
1099 close(tag_files->tfd);
1101 /* Do not start the pager before controlling the terminal. */
1103 while (tcgetpgrp(STDIN_FILENO) != getpid())
1104 nanosleep(&timeout, NULL);
1106 execvp(argv[0], argv);
1107 err((int)MANDOCLEVEL_SYSERR, "exec %s", argv[0]);