xref: /freebsd/contrib/mandoc/mandocd.c (revision 6829dae12bb055451fa467da4589c43bd03b1e64)
1 /*	$Id: mandocd.c,v 1.6 2017/06/24 14:38:32 schwarze Exp $ */
2 /*
3  * Copyright (c) 2017 Michael Stapelberg <stapelberg@debian.org>
4  * Copyright (c) 2017 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 "main.h"
42 #include "manconf.h"
43 
44 enum	outt {
45 	OUTT_ASCII = 0,
46 	OUTT_UTF8,
47 	OUTT_HTML
48 };
49 
50 static	void	  process(struct mparse *, enum outt, void *);
51 static	int	  read_fds(int, int *);
52 static	void	  usage(void) __attribute__((__noreturn__));
53 
54 
55 #define NUM_FDS 3
56 static int
57 read_fds(int clientfd, int *fds)
58 {
59 	struct msghdr	 msg;
60 	struct iovec	 iov[1];
61 	unsigned char	 dummy[1];
62 	struct cmsghdr	*cmsg;
63 	int		*walk;
64 	int		 cnt;
65 
66 	/* Union used for alignment. */
67 	union {
68 		uint8_t controlbuf[CMSG_SPACE(NUM_FDS * sizeof(int))];
69 		struct cmsghdr align;
70 	} u;
71 
72 	memset(&msg, '\0', sizeof(msg));
73 	msg.msg_control = u.controlbuf;
74 	msg.msg_controllen = sizeof(u.controlbuf);
75 
76 	/*
77 	 * Read a dummy byte - sendmsg cannot send an empty message,
78 	 * even if we are only interested in the OOB data.
79 	 */
80 
81 	iov[0].iov_base = dummy;
82 	iov[0].iov_len = sizeof(dummy);
83 	msg.msg_iov = iov;
84 	msg.msg_iovlen = 1;
85 
86 	switch (recvmsg(clientfd, &msg, 0)) {
87 	case -1:
88 		warn("recvmsg");
89 		return -1;
90 	case 0:
91 		return 0;
92 	default:
93 		break;
94 	}
95 
96 	if ((cmsg = CMSG_FIRSTHDR(&msg)) == NULL) {
97 		warnx("CMSG_FIRSTHDR: missing control message");
98 		return -1;
99 	}
100 
101 	if (cmsg->cmsg_level != SOL_SOCKET ||
102 	    cmsg->cmsg_type != SCM_RIGHTS ||
103 	    cmsg->cmsg_len != CMSG_LEN(NUM_FDS * sizeof(int))) {
104 		warnx("CMSG_FIRSTHDR: invalid control message");
105 		return -1;
106 	}
107 
108 	walk = (int *)CMSG_DATA(cmsg);
109 	for (cnt = 0; cnt < NUM_FDS; cnt++)
110 		fds[cnt] = *walk++;
111 
112 	return 1;
113 }
114 
115 int
116 main(int argc, char *argv[])
117 {
118 	struct manoutput	 options;
119 	struct mparse		*parser;
120 	void			*formatter;
121 	const char		*defos;
122 	const char		*errstr;
123 	int			 clientfd;
124 	int			 old_stdin;
125 	int			 old_stdout;
126 	int			 old_stderr;
127 	int			 fds[3];
128 	int			 state, opt;
129 	enum outt		 outtype;
130 
131 	defos = NULL;
132 	outtype = OUTT_ASCII;
133 	while ((opt = getopt(argc, argv, "I:T:")) != -1) {
134 		switch (opt) {
135 		case 'I':
136 			if (strncmp(optarg, "os=", 3) == 0)
137 				defos = optarg + 3;
138 			else {
139 				warnx("-I %s: Bad argument", optarg);
140 				usage();
141 			}
142 			break;
143 		case 'T':
144 			if (strcmp(optarg, "ascii") == 0)
145 				outtype = OUTT_ASCII;
146 			else if (strcmp(optarg, "utf8") == 0)
147 				outtype = OUTT_UTF8;
148 			else if (strcmp(optarg, "html") == 0)
149 				outtype = OUTT_HTML;
150 			else {
151 				warnx("-T %s: Bad argument", optarg);
152 				usage();
153 			}
154 			break;
155 		default:
156 			usage();
157 		}
158 	}
159 
160 	if (argc > 0) {
161 		argc -= optind;
162 		argv += optind;
163 	}
164 	if (argc != 1)
165 		usage();
166 
167 	errstr = NULL;
168 	clientfd = strtonum(argv[0], 3, INT_MAX, &errstr);
169 	if (errstr)
170 		errx(1, "file descriptor %s %s", argv[1], errstr);
171 
172 	mchars_alloc();
173 	parser = mparse_alloc(MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1,
174 	    MANDOCERR_MAX, NULL, MANDOC_OS_OTHER, defos);
175 
176 	memset(&options, 0, sizeof(options));
177 	switch (outtype) {
178 	case OUTT_ASCII:
179 		formatter = ascii_alloc(&options);
180 		break;
181 	case OUTT_UTF8:
182 		formatter = utf8_alloc(&options);
183 		break;
184 	case OUTT_HTML:
185 		options.fragment = 1;
186 		formatter = html_alloc(&options);
187 		break;
188 	}
189 
190 	state = 1;  /* work to do */
191 	fflush(stdout);
192 	fflush(stderr);
193 	if ((old_stdin = dup(STDIN_FILENO)) == -1 ||
194 	    (old_stdout = dup(STDOUT_FILENO)) == -1 ||
195 	    (old_stderr = dup(STDERR_FILENO)) == -1) {
196 		warn("dup");
197 		state = -1;  /* error */
198 	}
199 
200 	while (state == 1 && (state = read_fds(clientfd, fds)) == 1) {
201 		if (dup2(fds[0], STDIN_FILENO) == -1 ||
202 		    dup2(fds[1], STDOUT_FILENO) == -1 ||
203 		    dup2(fds[2], STDERR_FILENO) == -1) {
204 			warn("dup2");
205 			state = -1;
206 			break;
207 		}
208 
209 		close(fds[0]);
210 		close(fds[1]);
211 		close(fds[2]);
212 
213 		process(parser, outtype, formatter);
214 		mparse_reset(parser);
215 
216 		fflush(stdout);
217 		fflush(stderr);
218 		/* Close file descriptors by restoring the old ones. */
219 		if (dup2(old_stderr, STDERR_FILENO) == -1 ||
220 		    dup2(old_stdout, STDOUT_FILENO) == -1 ||
221 		    dup2(old_stdin, STDIN_FILENO) == -1) {
222 			warn("dup2");
223 			state = -1;
224 			break;
225 		}
226 	}
227 
228 	close(clientfd);
229 	switch (outtype) {
230 	case OUTT_ASCII:
231 	case OUTT_UTF8:
232 		ascii_free(formatter);
233 		break;
234 	case OUTT_HTML:
235 		html_free(formatter);
236 		break;
237 	}
238 	mparse_free(parser);
239 	mchars_free();
240 	return state == -1 ? 1 : 0;
241 }
242 
243 static void
244 process(struct mparse *parser, enum outt outtype, void *formatter)
245 {
246 	struct roff_man	 *man;
247 
248 	mparse_readfd(parser, STDIN_FILENO, "<unixfd>");
249 	mparse_result(parser, &man, NULL);
250 
251 	if (man == NULL)
252 		return;
253 
254 	if (man->macroset == MACROSET_MDOC) {
255 		mdoc_validate(man);
256 		switch (outtype) {
257 		case OUTT_ASCII:
258 		case OUTT_UTF8:
259 			terminal_mdoc(formatter, man);
260 			break;
261 		case OUTT_HTML:
262 			html_mdoc(formatter, man);
263 			break;
264 		}
265 	}
266 	if (man->macroset == MACROSET_MAN) {
267 		man_validate(man);
268 		switch (outtype) {
269 		case OUTT_ASCII:
270 		case OUTT_UTF8:
271 			terminal_man(formatter, man);
272 			break;
273 		case OUTT_HTML:
274 			html_man(formatter, man);
275 			break;
276 		}
277 	}
278 }
279 
280 void
281 usage(void)
282 {
283 	fprintf(stderr, "usage: mandocd [-I os=name] [-T output] socket_fd\n");
284 	exit(1);
285 }
286