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