1 /* $OpenBSD: fdpass.c,v 1.5 2019/05/10 04:39:08 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_peer == NULL) 257 return NULL; 258 if (!fdpass_send(fdpass_peer, FDPASS_OPEN_SND, num, mode, -1)) 259 return NULL; 260 if (!fdpass_waitret(fdpass_peer, &fd)) 261 return NULL; 262 if (fd < 0) 263 return NULL; 264 return sio_sun_fdopen(fd, mode, 1); 265 } 266 267 struct mio_hdl * 268 fdpass_mio_open(int num, unsigned int mode) 269 { 270 int fd; 271 272 if (fdpass_peer == NULL) 273 return NULL; 274 if (!fdpass_send(fdpass_peer, FDPASS_OPEN_MIDI, num, mode, -1)) 275 return NULL; 276 if (!fdpass_waitret(fdpass_peer, &fd)) 277 return NULL; 278 if (fd < 0) 279 return NULL; 280 return mio_rmidi_fdopen(fd, mode, 1); 281 } 282 283 void 284 fdpass_in_worker(void *arg) 285 { 286 struct fdpass *f = arg; 287 288 if (log_level >= 3) { 289 fdpass_log(f); 290 log_puts(": exit\n"); 291 } 292 fdpass_close(f); 293 return; 294 } 295 296 void 297 fdpass_in_helper(void *arg) 298 { 299 int cmd, num, mode, fd; 300 struct fdpass *f = arg; 301 struct dev *d; 302 struct port *p; 303 304 if (!fdpass_recv(f, &cmd, &num, &mode, &fd)) 305 return; 306 switch (cmd) { 307 case FDPASS_OPEN_SND: 308 d = dev_bynum(num); 309 if (d == NULL || !(mode & (SIO_PLAY | SIO_REC))) { 310 if (log_level >= 1) { 311 fdpass_log(f); 312 log_puts(": bad audio device or mode\n"); 313 } 314 fdpass_close(f); 315 return; 316 } 317 fd = sio_sun_getfd(d->path, mode, 1); 318 break; 319 case FDPASS_OPEN_MIDI: 320 p = port_bynum(num); 321 if (p == NULL || !(mode & (MIO_IN | MIO_OUT))) { 322 if (log_level >= 1) { 323 fdpass_log(f); 324 log_puts(": bad midi port or mode\n"); 325 } 326 fdpass_close(f); 327 return; 328 } 329 fd = mio_rmidi_getfd(p->path, mode, 1); 330 break; 331 default: 332 fdpass_close(f); 333 return; 334 } 335 fdpass_send(f, FDPASS_RETURN, 0, 0, fd); 336 } 337 338 void 339 fdpass_out(void *arg) 340 { 341 } 342 343 void 344 fdpass_hup(void *arg) 345 { 346 struct fdpass *f = arg; 347 348 if (log_level >= 3) { 349 fdpass_log(f); 350 log_puts(": hup\n"); 351 } 352 fdpass_close(f); 353 } 354 355 struct fdpass * 356 fdpass_new(int sock, struct fileops *ops) 357 { 358 struct fdpass *f; 359 360 f = xmalloc(sizeof(struct fdpass)); 361 f->file = file_new(ops, f, ops->name, 1); 362 if (f->file == NULL) { 363 close(sock); 364 xfree(f); 365 return NULL; 366 } 367 f->fd = sock; 368 fdpass_peer = f; 369 return f; 370 } 371 372 void 373 fdpass_close(struct fdpass *f) 374 { 375 fdpass_peer = NULL; 376 file_del(f->file); 377 close(f->fd); 378 xfree(f); 379 } 380 381 int 382 fdpass_pollfd(void *arg, struct pollfd *pfd) 383 { 384 struct fdpass *f = arg; 385 386 pfd->fd = f->fd; 387 pfd->events = POLLIN; 388 return 1; 389 } 390 391 int 392 fdpass_revents(void *arg, struct pollfd *pfd) 393 { 394 return pfd->revents; 395 } 396