diff options
-rw-r--r-- | Makefile | 33 | ||||
-rw-r--r-- | catman.c | 213 | ||||
-rwxr-xr-x | configure | 9 | ||||
-rw-r--r-- | configure.local.example | 9 | ||||
-rw-r--r-- | mandocd.c | 270 |
5 files changed, 522 insertions, 12 deletions
@@ -1,4 +1,4 @@ -# $Id: Makefile,v 1.493 2016/11/19 15:24:51 schwarze Exp $ +# $Id: Makefile,v 1.494 2017/02/04 12:03:07 schwarze Exp $ # # Copyright (c) 2010, 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv> # Copyright (c) 2011, 2013-2016 Ingo Schwarze <schwarze@openbsd.org> @@ -49,6 +49,7 @@ TESTSRCS = test-be32toh.c \ test-wchar.c SRCS = att.c \ + catman.c \ cgi.c \ chars.c \ compat_err.c \ @@ -89,6 +90,7 @@ SRCS = att.c \ mandoc.c \ mandoc_aux.c \ mandoc_ohash.c \ + mandocd.c \ mandocdb.c \ manpage.c \ manpath.c \ @@ -280,6 +282,12 @@ CGI_OBJS = $(MANDOC_HTML_OBJS) \ cgi.o \ out.o +MANDOCD_OBJS = $(MANDOC_HTML_OBJS) \ + $(MANDOC_TERM_OBJS) \ + mandocd.o \ + out.o \ + tag.o + MANPAGE_OBJS = $(DBM_OBJS) \ manpage.o \ manpath.o @@ -334,11 +342,7 @@ include Makefile.local # === DEPENDENCY HANDLING ============================================== -all: base-build $(BUILD_TARGETS) Makefile.local - -base-build: mandoc demandoc soelim - -cgi-build: man.cgi +all: mandoc demandoc soelim $(BUILD_TARGETS) Makefile.local install: base-install $(INSTALL_TARGETS) @@ -360,13 +364,14 @@ clean: rm -f libmandoc.a $(LIBMANDOC_OBJS) $(COMPAT_OBJS) rm -f mandoc $(MAIN_OBJS) rm -f man.cgi $(CGI_OBJS) + rm -f mandocd catman $(MANDOCD_OBJS) rm -f manpage $(MANPAGE_OBJS) rm -f demandoc $(DEMANDOC_OBJS) rm -f soelim $(SOELIM_OBJS) rm -f $(WWW_MANS) $(WWW_OBJS) rm -rf *.dSYM -base-install: base-build +base-install: mandoc demandoc soelim mkdir -p $(DESTDIR)$(BINDIR) mkdir -p $(DESTDIR)$(SBINDIR) mkdir -p $(DESTDIR)$(MANDIR)/man1 @@ -397,7 +402,7 @@ base-install: base-build $(INSTALL_MAN) makewhatis.8 \ $(DESTDIR)$(MANDIR)/man8/$(BINM_MAKEWHATIS).8 -lib-install: base-build +lib-install: libmandoc.a mkdir -p $(DESTDIR)$(LIBDIR) mkdir -p $(DESTDIR)$(INCLUDEDIR) mkdir -p $(DESTDIR)$(MANDIR)/man3 @@ -407,12 +412,16 @@ lib-install: base-build $(INSTALL_MAN) mandoc.3 mandoc_escape.3 mandoc_malloc.3 \ mansearch.3 mchars_alloc.3 tbl.3 $(DESTDIR)$(MANDIR)/man3 -cgi-install: cgi-build +cgi-install: man.cgi mkdir -p $(DESTDIR)$(CGIBINDIR) mkdir -p $(DESTDIR)$(HTDOCDIR) $(INSTALL_PROGRAM) man.cgi $(DESTDIR)$(CGIBINDIR) $(INSTALL_DATA) mandoc.css $(DESTDIR)$(HTDOCDIR) +catman-install: mandocd catman + mkdir -p $(DESTDIR)$(SBINDIR) + $(INSTALL_PROGRAM) mandocd catman $(DESTDIR)$(SBINDIR) + Makefile.local config.h: configure ${TESTSRCS} @echo "$@ is out of date; please run ./configure" @exit 1 @@ -429,6 +438,12 @@ manpage: $(MANPAGE_OBJS) libmandoc.a man.cgi: $(CGI_OBJS) libmandoc.a $(CC) $(STATIC) -o $@ $(LDFLAGS) $(CGI_OBJS) libmandoc.a $(LDADD) +mandocd: $(MANDOCD_OBJS) libmandoc.a + $(CC) -o $@ $(LDFLAGS) $(MANDOCD_OBJS) libmandoc.a $(LDADD) + +catman: catman.o + $(CC) -o $@ $(LDFLAGS) catman.o + demandoc: $(DEMANDOC_OBJS) libmandoc.a $(CC) -o $@ $(LDFLAGS) $(DEMANDOC_OBJS) libmandoc.a $(LDADD) diff --git a/catman.c b/catman.c new file mode 100644 index 00000000..4574a6e4 --- /dev/null +++ b/catman.c @@ -0,0 +1,213 @@ +/* $Id: catman.c,v 1.13 2017/02/04 12:03:07 schwarze Exp $ */ +/* + * Copyright (c) 2017 Michael Stapelberg <stapelberg@debian.org> + * Copyright (c) 2017 Ingo Schwarze <schwarze@openbsd.org> + * + * 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 <sys/types.h> +#include <sys/socket.h> +#include <sys/stat.h> + +#if HAVE_ERR +#include <err.h> +#endif +#include <fcntl.h> +#if HAVE_FTS +#include <fts.h> +#else +#include "compat_fts.h" +#endif +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +int process_manpage(int, int, const char *); +int process_tree(int, int); +void run_mandocd(int, const char *) __attribute__((noreturn)); +ssize_t sock_fd_write(int, int, int, int); +void usage(void) __attribute__((noreturn)); + + +void +run_mandocd(int sockfd, const char *outtype) +{ + char sockfdstr[10]; + + if (snprintf(sockfdstr, sizeof(sockfdstr), "%d", sockfd) == -1) + err(1, "snprintf"); + execlp("mandocd", "mandocd", "-T", outtype, sockfdstr, NULL); + err(1, "exec"); +} + +ssize_t +sock_fd_write(int fd, int fd0, int fd1, int fd2) +{ + struct msghdr msg; + struct iovec iov; + union { + struct cmsghdr cmsghdr; + char control[CMSG_SPACE(3 * sizeof(int))]; + } cmsgu; + struct cmsghdr *cmsg; + int *walk; + unsigned char dummy[1] = {'\0'}; + + iov.iov_base = dummy; + iov.iov_len = sizeof(dummy); + + msg.msg_name = NULL; + msg.msg_namelen = 0; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + msg.msg_control = cmsgu.control; + msg.msg_controllen = sizeof(cmsgu.control); + + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_len = CMSG_LEN(3 * sizeof(int)); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + + walk = (int *)CMSG_DATA(cmsg); + *(walk++) = fd0; + *(walk++) = fd1; + *(walk++) = fd2; + + return sendmsg(fd, &msg, 0); +} + +int +process_manpage(int srv_fd, int dstdir_fd, const char *path) +{ + int in_fd, out_fd; + + if ((in_fd = open(path, O_RDONLY)) == -1) { + warn("open(%s)", path); + return -1; + } + + if ((out_fd = openat(dstdir_fd, path, + O_WRONLY | O_NOFOLLOW | O_CREAT | O_TRUNC, + S_IRUSR | S_IWUSR | S_IRGRP | S_IRWXO)) == -1) { + warn("openat(%s)", path); + close(in_fd); + return -1; + } + + if (sock_fd_write(srv_fd, in_fd, out_fd, STDERR_FILENO) < 0) { + warn("sendmsg"); + return -1; + } + + close(in_fd); + close(out_fd); + return 0; +} + +int +process_tree(int srv_fd, int dstdir_fd) +{ + FTS *ftsp; + FTSENT *entry; + const char *argv[2]; + const char *path; + + argv[0] = "."; + argv[1] = (char *)NULL; + + if ((ftsp = fts_open((char * const *)argv, + FTS_PHYSICAL | FTS_NOCHDIR, NULL)) == NULL) { + warn("fts_open"); + return -1; + } + + while ((entry = fts_read(ftsp)) != NULL) { + path = entry->fts_path + 2; + switch (entry->fts_info) { + case FTS_F: + process_manpage(srv_fd, dstdir_fd, path); + break; + case FTS_D: + case FTS_DP: + break; + default: + warnx("%s: not a regular file", path); + break; + } + } + + fts_close(ftsp); + return 0; +} + +int +main(int argc, char **argv) +{ + const char *outtype; + int srv_fds[2]; + int dstdir_fd; + int opt; + pid_t pid; + + outtype = "ascii"; + while ((opt = getopt(argc, argv, "T:")) != -1) { + switch (opt) { + case 'T': + outtype = optarg; + break; + default: + usage(); + } + } + + if (argc > 0) { + argc -= optind; + argv += optind; + } + if (argc != 2) + usage(); + + if (socketpair(AF_LOCAL, SOCK_STREAM, AF_UNSPEC, srv_fds) == -1) + err(1, "socketpair"); + + pid = fork(); + switch (pid) { + case -1: + err(1, "fork"); + case 0: + close(srv_fds[0]); + run_mandocd(srv_fds[1], outtype); + default: + break; + } + close(srv_fds[1]); + + if ((dstdir_fd = open(argv[1], O_RDONLY | O_DIRECTORY)) == -1) + err(1, "open(%s)", argv[1]); + + if (chdir(argv[0]) == -1) + err(1, "chdir(%s)", argv[0]); + + return process_tree(srv_fds[0], dstdir_fd) == -1 ? 1 : 0; +} + +void +usage(void) +{ + fprintf(stderr, "usage: catman [-T output] srcdir dstdir\n"); + exit(1); +} @@ -1,6 +1,6 @@ #!/bin/sh # -# $Id: configure,v 1.55 2017/01/12 15:45:05 schwarze Exp $ +# $Id: configure,v 1.56 2017/02/04 12:03:07 schwarze Exp $ # # Copyright (c) 2014, 2015, 2016 Ingo Schwarze <schwarze@openbsd.org> # @@ -47,6 +47,7 @@ LD_OHASH= STATIC="-static" BUILD_CGI=0 +BUILD_CATMAN=0 INSTALL_LIBMANDOC=0 HAVE_DIRENT_NAMLEN= @@ -428,10 +429,14 @@ exec > Makefile.local [ -z "${INSTALL_DATA}" ] && INSTALL_DATA="${INSTALL} -m 0444" BUILD_TARGETS= -[ ${BUILD_CGI} -gt 0 ] && BUILD_TARGETS="cgi-build" +[ ${BUILD_CGI} -gt 0 ] && BUILD_TARGETS="man.cgi" +[ ${BUILD_CATMAN} -gt 0 ] && \ + BUILD_TARGETS="${BUILD_TARGETS} mandocd catman" INSTALL_TARGETS= [ ${INSTALL_LIBMANDOC} -gt 0 ] && INSTALL_TARGETS="lib-install" [ ${BUILD_CGI} -gt 0 ] && INSTALL_TARGETS="${INSTALL_TARGETS} cgi-install" +[ ${BUILD_CATMAN} -gt 0 ] && \ + INSTALL_TARGETS="${INSTALL_TARGETS} catman-install" cat << __HEREDOC__ BUILD_TARGETS = ${BUILD_TARGETS} diff --git a/configure.local.example b/configure.local.example index d5799a5a..6ac2310a 100644 --- a/configure.local.example +++ b/configure.local.example @@ -1,4 +1,4 @@ -# $Id: configure.local.example,v 1.22 2016/11/19 15:24:51 schwarze Exp $ +# $Id: configure.local.example,v 1.23 2017/02/04 12:03:07 schwarze Exp $ # # Copyright (c) 2014, 2015, 2016 Ingo Schwarze <schwarze@openbsd.org> # @@ -212,6 +212,13 @@ WWWPREFIX="/var/www" HTDOCDIR="${WWWPREFIX}/htdocs" CGIBINDIR="${WWWPREFIX}/cgi-bin" +# --- user settings related to catman ---------------------------------- + +# By default, building mandocd(8) and catman(8) is disabled. +# To enable it, use the following line. + +BUILD_CATMAN=1 + # --- settings that rarely need to be touched -------------------------- # Do not set these variables unless you really need to. diff --git a/mandocd.c b/mandocd.c new file mode 100644 index 00000000..fce28124 --- /dev/null +++ b/mandocd.c @@ -0,0 +1,270 @@ +/* $Id: mandocd.c,v 1.1 2017/02/04 12:03:07 schwarze Exp $ */ +/* + * Copyright (c) 2017 Michael Stapelberg <stapelberg@debian.org> + * Copyright (c) 2017 Ingo Schwarze <schwarze@openbsd.org> + * + * 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 <sys/types.h> +#include <sys/socket.h> + +#if HAVE_ERR +#include <err.h> +#endif +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#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, "<unixfd>"); + 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); +} |