/* $Id$ */
/*
* Copyright (c) 2017 Michael Stapelberg <stapelberg@debian.org>
* Copyright (c) 2017, 2019, 2021 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"
#if NEED_XPG4_2
#define _XPG4_2
#endif
#include <sys/types.h>
#include <sys/socket.h>
#if HAVE_ERR
#include <err.h>
#endif
#include <limits.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "mandoc.h"
#if DEBUG_MEMORY
#define DEBUG_NODEF 1
#include "mandoc_dbg.h"
#endif
#include "roff.h"
#include "mdoc.h"
#include "man.h"
#include "mandoc_parse.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 *defos;
const char *errstr;
int clientfd;
int old_stdin;
int old_stdout;
int old_stderr;
int fds[3];
int state, opt;
enum outt outtype;
#if DEBUG_MEMORY
mandoc_dbg_init(argc, argv);
#endif
defos = NULL;
outtype = OUTT_ASCII;
while ((opt = getopt(argc, argv, "I:T:")) != -1) {
switch (opt) {
case 'I':
if (strncmp(optarg, "os=", 3) == 0)
defos = optarg + 3;
else {
warnx("-I %s: Bad argument", optarg);
usage();
}
break;
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 |
MPARSE_VALIDATE, MANDOC_OS_OTHER, defos);
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);
if (outtype == OUTT_HTML)
html_reset(formatter);
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();
#if DEBUG_MEMORY
mandoc_dbg_finish();
#endif
return state == -1 ? 1 : 0;
}
static void
process(struct mparse *parser, enum outt outtype, void *formatter)
{
struct roff_meta *meta;
mparse_readfd(parser, STDIN_FILENO, "<unixfd>");
meta = mparse_result(parser);
if (meta->macroset == MACROSET_MDOC) {
switch (outtype) {
case OUTT_ASCII:
case OUTT_UTF8:
terminal_mdoc(formatter, meta);
break;
case OUTT_HTML:
html_mdoc(formatter, meta);
break;
}
}
if (meta->macroset == MACROSET_MAN) {
switch (outtype) {
case OUTT_ASCII:
case OUTT_UTF8:
terminal_man(formatter, meta);
break;
case OUTT_HTML:
html_man(formatter, meta);
break;
}
}
}
void
usage(void)
{
fprintf(stderr, "usage: mandocd [-I os=name] [-T output] socket_fd\n");
exit(1);
}