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