/* $Id$ */ /* * Copyright (c) 2017 Michael Stapelberg * Copyright (c) 2017 Ingo Schwarze * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "config.h" #include #include #if HAVE_ERR #include #endif #include #include #include #include #include #include #include "mandoc.h" #include "roff.h" #include "mdoc.h" #include "man.h" #include "main.h" #include "manconf.h" enum outt { OUTT_ASCII = 0, OUTT_UTF8, OUTT_HTML }; static void process(struct mparse *, enum outt, void *); static int read_fds(int, int *); static void usage(void) __attribute__((noreturn)); #define NUM_FDS 3 static int read_fds(int clientfd, int *fds) { struct msghdr msg; struct iovec iov[1]; unsigned char dummy[1]; struct cmsghdr *cmsg; int *walk; int cnt; /* Union used for alignment. */ union { uint8_t controlbuf[CMSG_SPACE(NUM_FDS * sizeof(int))]; struct cmsghdr align; } u; memset(&msg, '\0', sizeof(msg)); msg.msg_control = u.controlbuf; msg.msg_controllen = sizeof(u.controlbuf); /* * Read a dummy byte - sendmsg cannot send an empty message, * even if we are only interested in the OOB data. */ iov[0].iov_base = dummy; iov[0].iov_len = sizeof(dummy); msg.msg_iov = iov; msg.msg_iovlen = 1; switch (recvmsg(clientfd, &msg, 0)) { case -1: warn("recvmsg"); return -1; case 0: return 0; default: break; } if ((cmsg = CMSG_FIRSTHDR(&msg)) == NULL) { warnx("CMSG_FIRSTHDR: missing control message"); return -1; } if (cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_RIGHTS || cmsg->cmsg_len != CMSG_LEN(NUM_FDS * sizeof(int))) { warnx("CMSG_FIRSTHDR: invalid control message"); return -1; } walk = (int *)CMSG_DATA(cmsg); for (cnt = 0; cnt < NUM_FDS; cnt++) fds[cnt] = *walk++; return 1; } int main(int argc, char *argv[]) { struct manoutput options; struct mparse *parser; void *formatter; const char *errstr; int clientfd; int old_stdin; int old_stdout; int old_stderr; int fds[3]; int state, opt; enum outt outtype; outtype = OUTT_ASCII; while ((opt = getopt(argc, argv, "T:")) != -1) { switch (opt) { case 'T': if (strcmp(optarg, "ascii") == 0) outtype = OUTT_ASCII; else if (strcmp(optarg, "utf8") == 0) outtype = OUTT_UTF8; else if (strcmp(optarg, "html") == 0) outtype = OUTT_HTML; else { warnx("-T %s: Bad argument", optarg); usage(); } break; default: usage(); } } if (argc > 0) { argc -= optind; argv += optind; } if (argc != 1) usage(); errstr = NULL; clientfd = strtonum(argv[0], 3, INT_MAX, &errstr); if (errstr) errx(1, "file descriptor %s %s", argv[1], errstr); mchars_alloc(); parser = mparse_alloc(MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1, MANDOCLEVEL_BADARG, NULL, NULL); memset(&options, 0, sizeof(options)); switch (outtype) { case OUTT_ASCII: formatter = ascii_alloc(&options); break; case OUTT_UTF8: formatter = utf8_alloc(&options); break; case OUTT_HTML: options.fragment = 1; formatter = html_alloc(&options); break; } state = 1; /* work to do */ fflush(stdout); fflush(stderr); if ((old_stdin = dup(STDIN_FILENO)) == -1 || (old_stdout = dup(STDOUT_FILENO)) == -1 || (old_stderr = dup(STDERR_FILENO)) == -1) { warn("dup"); state = -1; /* error */ } while (state == 1 && (state = read_fds(clientfd, fds)) == 1) { if (dup2(fds[0], STDIN_FILENO) == -1 || dup2(fds[1], STDOUT_FILENO) == -1 || dup2(fds[2], STDERR_FILENO) == -1) { warn("dup2"); state = -1; break; } close(fds[0]); close(fds[1]); close(fds[2]); process(parser, outtype, formatter); mparse_reset(parser); fflush(stdout); fflush(stderr); /* Close file descriptors by restoring the old ones. */ if (dup2(old_stderr, STDERR_FILENO) == -1 || dup2(old_stdout, STDOUT_FILENO) == -1 || dup2(old_stdin, STDIN_FILENO) == -1) { warn("dup2"); state = -1; break; } } close(clientfd); switch (outtype) { case OUTT_ASCII: case OUTT_UTF8: ascii_free(formatter); break; case OUTT_HTML: html_free(formatter); break; } mparse_free(parser); mchars_free(); return state == -1 ? 1 : 0; } static void process(struct mparse *parser, enum outt outtype, void *formatter) { struct roff_man *man; mparse_readfd(parser, STDIN_FILENO, ""); mparse_result(parser, &man, NULL); if (man == NULL) return; if (man->macroset == MACROSET_MDOC) { mdoc_validate(man); switch (outtype) { case OUTT_ASCII: case OUTT_UTF8: terminal_mdoc(formatter, man); break; case OUTT_HTML: html_mdoc(formatter, man); break; } } if (man->macroset == MACROSET_MAN) { man_validate(man); switch (outtype) { case OUTT_ASCII: case OUTT_UTF8: terminal_man(formatter, man); break; case OUTT_HTML: html_man(formatter, man); break; } } } void usage(void) { fprintf(stderr, "usage: mandocd [-T output] socket_fd\n"); exit(1); }