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