aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
-rw-r--r--Makefile33
-rw-r--r--catman.c213
-rwxr-xr-xconfigure9
-rw-r--r--configure.local.example9
-rw-r--r--mandocd.c270
5 files changed, 522 insertions, 12 deletions
diff --git a/Makefile b/Makefile
index 5c2657c3..a0fc3d03 100644
--- a/Makefile
+++ b/Makefile
@@ -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);
+}
diff --git a/configure b/configure
index 1b810158..38a2a37f 100755
--- a/configure
+++ b/configure
@@ -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);
+}