xref: /freebsd/contrib/mandoc/mandocd.c (revision e8d8bef961a50d4dc22501cde4fb9fb0be1b2532)
1 /*	$Id: mandocd.c,v 1.11 2019/03/03 13:02:11 schwarze Exp $ */
2 /*
3  * Copyright (c) 2017 Michael Stapelberg <stapelberg@debian.org>
4  * Copyright (c) 2017, 2019 Ingo Schwarze <schwarze@openbsd.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 #include "config.h"
19 
20 #if HAVE_CMSG_XPG42
21 #define _XPG4_2
22 #endif
23 
24 #include <sys/types.h>
25 #include <sys/socket.h>
26 
27 #if HAVE_ERR
28 #include <err.h>
29 #endif
30 #include <limits.h>
31 #include <stdint.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <unistd.h>
36 
37 #include "mandoc.h"
38 #include "roff.h"
39 #include "mdoc.h"
40 #include "man.h"
41 #include "mandoc_parse.h"
42 #include "main.h"
43 #include "manconf.h"
44 
45 enum	outt {
46 	OUTT_ASCII = 0,
47 	OUTT_UTF8,
48 	OUTT_HTML
49 };
50 
51 static	void	  process(struct mparse *, enum outt, void *);
52 static	int	  read_fds(int, int *);
53 static	void	  usage(void) __attribute__((__noreturn__));
54 
55 
56 #define NUM_FDS 3
57 static int
58 read_fds(int clientfd, int *fds)
59 {
60 	struct msghdr	 msg;
61 	struct iovec	 iov[1];
62 	unsigned char	 dummy[1];
63 	struct cmsghdr	*cmsg;
64 	int		*walk;
65 	int		 cnt;
66 
67 	/* Union used for alignment. */
68 	union {
69 		uint8_t controlbuf[CMSG_SPACE(NUM_FDS * sizeof(int))];
70 		struct cmsghdr align;
71 	} u;
72 
73 	memset(&msg, '\0', sizeof(msg));
74 	msg.msg_control = u.controlbuf;
75 	msg.msg_controllen = sizeof(u.controlbuf);
76 
77 	/*
78 	 * Read a dummy byte - sendmsg cannot send an empty message,
79 	 * even if we are only interested in the OOB data.
80 	 */
81 
82 	iov[0].iov_base = dummy;
83 	iov[0].iov_len = sizeof(dummy);
84 	msg.msg_iov = iov;
85 	msg.msg_iovlen = 1;
86 
87 	switch (recvmsg(clientfd, &msg, 0)) {
88 	case -1:
89 		warn("recvmsg");
90 		return -1;
91 	case 0:
92 		return 0;
93 	default:
94 		break;
95 	}
96 
97 	if ((cmsg = CMSG_FIRSTHDR(&msg)) == NULL) {
98 		warnx("CMSG_FIRSTHDR: missing control message");
99 		return -1;
100 	}
101 
102 	if (cmsg->cmsg_level != SOL_SOCKET ||
103 	    cmsg->cmsg_type != SCM_RIGHTS ||
104 	    cmsg->cmsg_len != CMSG_LEN(NUM_FDS * sizeof(int))) {
105 		warnx("CMSG_FIRSTHDR: invalid control message");
106 		return -1;
107 	}
108 
109 	walk = (int *)CMSG_DATA(cmsg);
110 	for (cnt = 0; cnt < NUM_FDS; cnt++)
111 		fds[cnt] = *walk++;
112 
113 	return 1;
114 }
115 
116 int
117 main(int argc, char *argv[])
118 {
119 	struct manoutput	 options;
120 	struct mparse		*parser;
121 	void			*formatter;
122 	const char		*defos;
123 	const char		*errstr;
124 	int			 clientfd;
125 	int			 old_stdin;
126 	int			 old_stdout;
127 	int			 old_stderr;
128 	int			 fds[3];
129 	int			 state, opt;
130 	enum outt		 outtype;
131 
132 	defos = NULL;
133 	outtype = OUTT_ASCII;
134 	while ((opt = getopt(argc, argv, "I:T:")) != -1) {
135 		switch (opt) {
136 		case 'I':
137 			if (strncmp(optarg, "os=", 3) == 0)
138 				defos = optarg + 3;
139 			else {
140 				warnx("-I %s: Bad argument", optarg);
141 				usage();
142 			}
143 			break;
144 		case 'T':
145 			if (strcmp(optarg, "ascii") == 0)
146 				outtype = OUTT_ASCII;
147 			else if (strcmp(optarg, "utf8") == 0)
148 				outtype = OUTT_UTF8;
149 			else if (strcmp(optarg, "html") == 0)
150 				outtype = OUTT_HTML;
151 			else {
152 				warnx("-T %s: Bad argument", optarg);
153 				usage();
154 			}
155 			break;
156 		default:
157 			usage();
158 		}
159 	}
160 
161 	if (argc > 0) {
162 		argc -= optind;
163 		argv += optind;
164 	}
165 	if (argc != 1)
166 		usage();
167 
168 	errstr = NULL;
169 	clientfd = strtonum(argv[0], 3, INT_MAX, &errstr);
170 	if (errstr)
171 		errx(1, "file descriptor %s %s", argv[1], errstr);
172 
173 	mchars_alloc();
174 	parser = mparse_alloc(MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1 |
175 	    MPARSE_VALIDATE, MANDOC_OS_OTHER, defos);
176 
177 	memset(&options, 0, sizeof(options));
178 	switch (outtype) {
179 	case OUTT_ASCII:
180 		formatter = ascii_alloc(&options);
181 		break;
182 	case OUTT_UTF8:
183 		formatter = utf8_alloc(&options);
184 		break;
185 	case OUTT_HTML:
186 		options.fragment = 1;
187 		formatter = html_alloc(&options);
188 		break;
189 	}
190 
191 	state = 1;  /* work to do */
192 	fflush(stdout);
193 	fflush(stderr);
194 	if ((old_stdin = dup(STDIN_FILENO)) == -1 ||
195 	    (old_stdout = dup(STDOUT_FILENO)) == -1 ||
196 	    (old_stderr = dup(STDERR_FILENO)) == -1) {
197 		warn("dup");
198 		state = -1;  /* error */
199 	}
200 
201 	while (state == 1 && (state = read_fds(clientfd, fds)) == 1) {
202 		if (dup2(fds[0], STDIN_FILENO) == -1 ||
203 		    dup2(fds[1], STDOUT_FILENO) == -1 ||
204 		    dup2(fds[2], STDERR_FILENO) == -1) {
205 			warn("dup2");
206 			state = -1;
207 			break;
208 		}
209 
210 		close(fds[0]);
211 		close(fds[1]);
212 		close(fds[2]);
213 
214 		process(parser, outtype, formatter);
215 		mparse_reset(parser);
216 		if (outtype == OUTT_HTML)
217 			html_reset(formatter);
218 
219 		fflush(stdout);
220 		fflush(stderr);
221 		/* Close file descriptors by restoring the old ones. */
222 		if (dup2(old_stderr, STDERR_FILENO) == -1 ||
223 		    dup2(old_stdout, STDOUT_FILENO) == -1 ||
224 		    dup2(old_stdin, STDIN_FILENO) == -1) {
225 			warn("dup2");
226 			state = -1;
227 			break;
228 		}
229 	}
230 
231 	close(clientfd);
232 	switch (outtype) {
233 	case OUTT_ASCII:
234 	case OUTT_UTF8:
235 		ascii_free(formatter);
236 		break;
237 	case OUTT_HTML:
238 		html_free(formatter);
239 		break;
240 	}
241 	mparse_free(parser);
242 	mchars_free();
243 	return state == -1 ? 1 : 0;
244 }
245 
246 static void
247 process(struct mparse *parser, enum outt outtype, void *formatter)
248 {
249 	struct roff_meta *meta;
250 
251 	mparse_readfd(parser, STDIN_FILENO, "<unixfd>");
252 	meta = mparse_result(parser);
253 	if (meta->macroset == MACROSET_MDOC) {
254 		switch (outtype) {
255 		case OUTT_ASCII:
256 		case OUTT_UTF8:
257 			terminal_mdoc(formatter, meta);
258 			break;
259 		case OUTT_HTML:
260 			html_mdoc(formatter, meta);
261 			break;
262 		}
263 	}
264 	if (meta->macroset == MACROSET_MAN) {
265 		switch (outtype) {
266 		case OUTT_ASCII:
267 		case OUTT_UTF8:
268 			terminal_man(formatter, meta);
269 			break;
270 		case OUTT_HTML:
271 			html_man(formatter, meta);
272 			break;
273 		}
274 	}
275 }
276 
277 void
278 usage(void)
279 {
280 	fprintf(stderr, "usage: mandocd [-I os=name] [-T output] socket_fd\n");
281 	exit(1);
282 }
283