1 /* $OpenBSD: fdpass.c,v 1.3 2016/01/08 16:17:31 ratchov Exp $ */ 2 /* 3 * Copyright (c) 2015 Alexandre Ratchov <alex@caoua.org> 4 * 5 * Permission to use, copy, modify, and distribute this software for any 6 * purpose with or without fee is hereby granted, provided that the above 7 * copyright notice and this permission notice appear in all copies. 8 * 9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 */ 17 #include <sys/socket.h> 18 #include <errno.h> 19 #include <fcntl.h> 20 #include <poll.h> 21 #include <sndio.h> 22 #include <string.h> 23 #include <unistd.h> 24 #include "dev.h" 25 #include "fdpass.h" 26 #include "file.h" 27 #include "listen.h" 28 #include "midi.h" 29 #include "sock.h" 30 #include "utils.h" 31 32 struct fdpass_msg { 33 #define FDPASS_OPEN_SND 0 /* open an audio device */ 34 #define FDPASS_OPEN_MIDI 1 /* open a midi port */ 35 #define FDPASS_RETURN 3 /* return after above commands */ 36 unsigned int cmd; /* one of above */ 37 unsigned int num; /* audio device or midi port number */ 38 unsigned int mode; /* SIO_PLAY, SIO_REC, MIO_IN, ... */ 39 }; 40 41 int fdpass_pollfd(void *, struct pollfd *); 42 int fdpass_revents(void *, struct pollfd *); 43 void fdpass_in_worker(void *); 44 void fdpass_in_helper(void *); 45 void fdpass_out(void *); 46 void fdpass_hup(void *); 47 48 struct fileops worker_fileops = { 49 "worker", 50 fdpass_pollfd, 51 fdpass_revents, 52 fdpass_in_worker, 53 fdpass_out, 54 fdpass_hup 55 }; 56 57 struct fileops helper_fileops = { 58 "helper", 59 fdpass_pollfd, 60 fdpass_revents, 61 fdpass_in_helper, 62 fdpass_out, 63 fdpass_hup 64 }; 65 66 struct fdpass { 67 struct file *file; 68 int fd; 69 } *fdpass_peer = NULL; 70 71 static void 72 fdpass_log(struct fdpass *f) 73 { 74 log_puts(f->file->name); 75 } 76 77 static int 78 fdpass_send(struct fdpass *f, int cmd, int num, int mode, int fd) 79 { 80 struct fdpass_msg data; 81 struct msghdr msg; 82 struct cmsghdr *cmsg; 83 union { 84 struct cmsghdr hdr; 85 unsigned char buf[CMSG_SPACE(sizeof(int))]; 86 } cmsgbuf; 87 struct iovec iov; 88 ssize_t n; 89 90 data.cmd = cmd; 91 data.num = num; 92 data.mode = mode; 93 iov.iov_base = &data; 94 iov.iov_len = sizeof(struct fdpass_msg); 95 memset(&msg, 0, sizeof(msg)); 96 msg.msg_iov = &iov; 97 msg.msg_iovlen = 1; 98 if (fd >= 0) { 99 msg.msg_control = &cmsgbuf.buf; 100 msg.msg_controllen = sizeof(cmsgbuf.buf); 101 cmsg = CMSG_FIRSTHDR(&msg); 102 cmsg->cmsg_len = CMSG_LEN(sizeof(int)); 103 cmsg->cmsg_level = SOL_SOCKET; 104 cmsg->cmsg_type = SCM_RIGHTS; 105 *(int *)CMSG_DATA(cmsg) = fd; 106 } 107 n = sendmsg(f->fd, &msg, 0); 108 if (n < 0) { 109 if (log_level >= 1) { 110 fdpass_log(f); 111 log_puts(": sendmsg failed\n"); 112 } 113 fdpass_close(f); 114 return 0; 115 } 116 if (n != sizeof(struct fdpass_msg)) { 117 if (log_level >= 1) { 118 fdpass_log(f); 119 log_puts(": short write\n"); 120 } 121 fdpass_close(f); 122 return 0; 123 } 124 #ifdef DEBUG 125 if (log_level >= 3) { 126 fdpass_log(f); 127 log_puts(": send: cmd = "); 128 log_puti(cmd); 129 log_puts(", num = "); 130 log_puti(num); 131 log_puts(", mode = "); 132 log_puti(mode); 133 log_puts(", fd = "); 134 log_puti(fd); 135 log_puts("\n"); 136 } 137 #endif 138 if (fd >= 0) 139 close(fd); 140 return 1; 141 } 142 143 static int 144 fdpass_recv(struct fdpass *f, int *cmd, int *num, int *mode, int *fd) 145 { 146 struct fdpass_msg data; 147 struct msghdr msg; 148 struct cmsghdr *cmsg; 149 union { 150 struct cmsghdr hdr; 151 unsigned char buf[CMSG_SPACE(sizeof(int))]; 152 } cmsgbuf; 153 struct iovec iov; 154 ssize_t n; 155 156 iov.iov_base = &data; 157 iov.iov_len = sizeof(struct fdpass_msg); 158 memset(&msg, 0, sizeof(msg)); 159 msg.msg_control = &cmsgbuf.buf; 160 msg.msg_controllen = sizeof(cmsgbuf.buf); 161 msg.msg_iov = &iov; 162 msg.msg_iovlen = 1; 163 n = recvmsg(f->fd, &msg, MSG_WAITALL); 164 if (n < 0 && errno == EMSGSIZE) { 165 if (log_level >= 1) { 166 fdpass_log(f); 167 log_puts(": out of fds\n"); 168 } 169 /* 170 * ancillary data (ie the fd) is discarded, 171 * retrieve the message 172 */ 173 n = recvmsg(f->fd, &msg, MSG_WAITALL); 174 } 175 if (n < 0) { 176 if (log_level >= 1) { 177 fdpass_log(f); 178 log_puts(": recvmsg failed\n"); 179 } 180 fdpass_close(f); 181 return 0; 182 } 183 if (n == 0) { 184 if (log_level >= 3) { 185 fdpass_log(f); 186 log_puts(": recvmsg eof\n"); 187 } 188 fdpass_close(f); 189 return 0; 190 } 191 if (msg.msg_flags & (MSG_TRUNC | MSG_CTRUNC)) { 192 if (log_level >= 1) { 193 fdpass_log(f); 194 log_puts(": truncated\n"); 195 } 196 fdpass_close(f); 197 return 0; 198 } 199 cmsg = CMSG_FIRSTHDR(&msg); 200 for (;;) { 201 if (cmsg == NULL) { 202 *fd = -1; 203 break; 204 } 205 if (cmsg->cmsg_len == CMSG_LEN(sizeof(int)) && 206 cmsg->cmsg_level == SOL_SOCKET && 207 cmsg->cmsg_type == SCM_RIGHTS) { 208 *fd = *(int *)CMSG_DATA(cmsg); 209 break; 210 } 211 cmsg = CMSG_NXTHDR(&msg, cmsg); 212 } 213 *cmd = data.cmd; 214 *num = data.num; 215 *mode = data.mode; 216 #ifdef DEBUG 217 if (log_level >= 3) { 218 fdpass_log(f); 219 log_puts(": recv: cmd = "); 220 log_puti(*cmd); 221 log_puts(", num = "); 222 log_puti(*num); 223 log_puts(", mode = "); 224 log_puti(*mode); 225 log_puts(", fd = "); 226 log_puti(*fd); 227 log_puts("\n"); 228 } 229 #endif 230 return 1; 231 } 232 233 static int 234 fdpass_waitret(struct fdpass *f, int *retfd) 235 { 236 int cmd, unused; 237 238 if (!fdpass_recv(fdpass_peer, &cmd, &unused, &unused, retfd)) 239 return 0; 240 if (cmd != FDPASS_RETURN) { 241 if (log_level >= 1) { 242 fdpass_log(f); 243 log_puts(": expected RETURN message\n"); 244 } 245 fdpass_close(f); 246 return 0; 247 } 248 return 1; 249 } 250 251 struct sio_hdl * 252 fdpass_sio_open(int num, unsigned int mode) 253 { 254 int fd; 255 256 if (!fdpass_send(fdpass_peer, FDPASS_OPEN_SND, num, mode, -1)) 257 return NULL; 258 if (!fdpass_waitret(fdpass_peer, &fd)) 259 return NULL; 260 if (fd < 0) 261 return NULL; 262 return sio_sun_fdopen(fd, mode, 1); 263 } 264 265 struct mio_hdl * 266 fdpass_mio_open(int num, unsigned int mode) 267 { 268 int fd; 269 270 if (!fdpass_send(fdpass_peer, FDPASS_OPEN_MIDI, num, mode, -1)) 271 return NULL; 272 if (!fdpass_waitret(fdpass_peer, &fd)) 273 return NULL; 274 if (fd < 0) 275 return NULL; 276 return mio_rmidi_fdopen(fd, mode, 1); 277 } 278 279 void 280 fdpass_in_worker(void *arg) 281 { 282 struct fdpass *f = arg; 283 284 if (log_level >= 3) { 285 fdpass_log(f); 286 log_puts(": exit\n"); 287 } 288 fdpass_close(f); 289 return; 290 } 291 292 void 293 fdpass_in_helper(void *arg) 294 { 295 int cmd, num, mode, fd; 296 struct fdpass *f = arg; 297 struct dev *d; 298 struct port *p; 299 300 if (!fdpass_recv(f, &cmd, &num, &mode, &fd)) 301 return; 302 switch (cmd) { 303 case FDPASS_OPEN_SND: 304 d = dev_bynum(num); 305 if (d == NULL || !(mode & (SIO_PLAY | SIO_REC))) { 306 if (log_level >= 1) { 307 fdpass_log(f); 308 log_puts(": bad audio device or mode\n"); 309 } 310 fdpass_close(f); 311 return; 312 } 313 fd = sio_sun_getfd(d->path, mode, 1); 314 break; 315 case FDPASS_OPEN_MIDI: 316 p = port_bynum(num); 317 if (p == NULL || !(mode & (MIO_IN | MIO_OUT))) { 318 if (log_level >= 1) { 319 fdpass_log(f); 320 log_puts(": bad midi port or mode\n"); 321 } 322 fdpass_close(f); 323 return; 324 } 325 fd = mio_rmidi_getfd(p->path, mode, 1); 326 break; 327 default: 328 fdpass_close(f); 329 return; 330 } 331 fdpass_send(f, FDPASS_RETURN, 0, 0, fd); 332 } 333 334 void 335 fdpass_out(void *arg) 336 { 337 } 338 339 void 340 fdpass_hup(void *arg) 341 { 342 struct fdpass *f = arg; 343 344 if (log_level >= 3) { 345 fdpass_log(f); 346 log_puts(": hup\n"); 347 } 348 fdpass_close(f); 349 } 350 351 struct fdpass * 352 fdpass_new(int sock, struct fileops *ops) 353 { 354 struct fdpass *f; 355 356 f = xmalloc(sizeof(struct fdpass)); 357 f->file = file_new(ops, f, ops->name, 1); 358 if (f->file == NULL) { 359 close(sock); 360 return NULL; 361 } 362 f->fd = sock; 363 fdpass_peer = f; 364 return f; 365 } 366 367 void 368 fdpass_close(struct fdpass *f) 369 { 370 fdpass_peer = NULL; 371 file_del(f->file); 372 close(f->fd); 373 xfree(f); 374 } 375 376 int 377 fdpass_pollfd(void *arg, struct pollfd *pfd) 378 { 379 struct fdpass *f = arg; 380 381 pfd->fd = f->fd; 382 pfd->events = POLLIN; 383 return 1; 384 } 385 386 int 387 fdpass_revents(void *arg, struct pollfd *pfd) 388 { 389 return pfd->revents; 390 } 391