1 /* $OpenBSD: fdpass.c,v 1.10 2020/06/18 05:11:13 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_OPEN_CTL 2 /* open an audio control device */ 36 #define FDPASS_RETURN 3 /* return after above commands */ 37 unsigned int cmd; /* one of above */ 38 unsigned int num; /* audio device or midi port number */ 39 unsigned int idx; /* index in the path list */ 40 unsigned int mode; /* SIO_PLAY, SIO_REC, MIO_IN, ... */ 41 }; 42 43 int fdpass_pollfd(void *, struct pollfd *); 44 int fdpass_revents(void *, struct pollfd *); 45 void fdpass_in_worker(void *); 46 void fdpass_in_helper(void *); 47 void fdpass_out(void *); 48 void fdpass_hup(void *); 49 50 struct fileops worker_fileops = { 51 "worker", 52 fdpass_pollfd, 53 fdpass_revents, 54 fdpass_in_worker, 55 fdpass_out, 56 fdpass_hup 57 }; 58 59 struct fileops helper_fileops = { 60 "helper", 61 fdpass_pollfd, 62 fdpass_revents, 63 fdpass_in_helper, 64 fdpass_out, 65 fdpass_hup 66 }; 67 68 struct fdpass { 69 struct file *file; 70 int fd; 71 } *fdpass_peer = NULL; 72 73 static void 74 fdpass_log(struct fdpass *f) 75 { 76 log_puts(f->file->name); 77 } 78 79 static int 80 fdpass_send(struct fdpass *f, int cmd, int num, int idx, int mode, int fd) 81 { 82 struct fdpass_msg data; 83 struct msghdr msg; 84 struct cmsghdr *cmsg; 85 union { 86 struct cmsghdr hdr; 87 unsigned char buf[CMSG_SPACE(sizeof(int))]; 88 } cmsgbuf; 89 struct iovec iov; 90 ssize_t n; 91 92 data.cmd = cmd; 93 data.num = num; 94 data.idx = idx; 95 data.mode = mode; 96 iov.iov_base = &data; 97 iov.iov_len = sizeof(struct fdpass_msg); 98 memset(&msg, 0, sizeof(msg)); 99 msg.msg_iov = &iov; 100 msg.msg_iovlen = 1; 101 if (fd >= 0) { 102 msg.msg_control = &cmsgbuf.buf; 103 msg.msg_controllen = sizeof(cmsgbuf.buf); 104 cmsg = CMSG_FIRSTHDR(&msg); 105 cmsg->cmsg_len = CMSG_LEN(sizeof(int)); 106 cmsg->cmsg_level = SOL_SOCKET; 107 cmsg->cmsg_type = SCM_RIGHTS; 108 *(int *)CMSG_DATA(cmsg) = fd; 109 } 110 n = sendmsg(f->fd, &msg, 0); 111 if (n == -1) { 112 if (log_level >= 1) { 113 fdpass_log(f); 114 log_puts(": sendmsg failed\n"); 115 } 116 fdpass_close(f); 117 return 0; 118 } 119 if (n != sizeof(struct fdpass_msg)) { 120 if (log_level >= 1) { 121 fdpass_log(f); 122 log_puts(": short write\n"); 123 } 124 fdpass_close(f); 125 return 0; 126 } 127 #ifdef DEBUG 128 if (log_level >= 3) { 129 fdpass_log(f); 130 log_puts(": send: cmd = "); 131 log_puti(cmd); 132 log_puts(", num = "); 133 log_puti(num); 134 log_puts(", idx = "); 135 log_puti(idx); 136 log_puts(", mode = "); 137 log_puti(mode); 138 log_puts(", fd = "); 139 log_puti(fd); 140 log_puts("\n"); 141 } 142 #endif 143 if (fd >= 0) 144 close(fd); 145 return 1; 146 } 147 148 static int 149 fdpass_recv(struct fdpass *f, int *cmd, int *num, int *idx, int *mode, int *fd) 150 { 151 struct fdpass_msg data; 152 struct msghdr msg; 153 struct cmsghdr *cmsg; 154 union { 155 struct cmsghdr hdr; 156 unsigned char buf[CMSG_SPACE(sizeof(int))]; 157 } cmsgbuf; 158 struct iovec iov; 159 ssize_t n; 160 161 iov.iov_base = &data; 162 iov.iov_len = sizeof(struct fdpass_msg); 163 memset(&msg, 0, sizeof(msg)); 164 msg.msg_control = &cmsgbuf.buf; 165 msg.msg_controllen = sizeof(cmsgbuf.buf); 166 msg.msg_iov = &iov; 167 msg.msg_iovlen = 1; 168 n = recvmsg(f->fd, &msg, MSG_WAITALL); 169 if (n == -1 && errno == EMSGSIZE) { 170 if (log_level >= 1) { 171 fdpass_log(f); 172 log_puts(": out of fds\n"); 173 } 174 /* 175 * ancillary data (ie the fd) is discarded, 176 * retrieve the message 177 */ 178 n = recvmsg(f->fd, &msg, MSG_WAITALL); 179 } 180 if (n == -1) { 181 if (log_level >= 1) { 182 fdpass_log(f); 183 log_puts(": recvmsg failed\n"); 184 } 185 fdpass_close(f); 186 return 0; 187 } 188 if (n == 0) { 189 if (log_level >= 3) { 190 fdpass_log(f); 191 log_puts(": recvmsg eof\n"); 192 } 193 fdpass_close(f); 194 return 0; 195 } 196 if (msg.msg_flags & (MSG_TRUNC | MSG_CTRUNC)) { 197 if (log_level >= 1) { 198 fdpass_log(f); 199 log_puts(": truncated\n"); 200 } 201 fdpass_close(f); 202 return 0; 203 } 204 cmsg = CMSG_FIRSTHDR(&msg); 205 for (;;) { 206 if (cmsg == NULL) { 207 *fd = -1; 208 break; 209 } 210 if (cmsg->cmsg_len == CMSG_LEN(sizeof(int)) && 211 cmsg->cmsg_level == SOL_SOCKET && 212 cmsg->cmsg_type == SCM_RIGHTS) { 213 *fd = *(int *)CMSG_DATA(cmsg); 214 break; 215 } 216 cmsg = CMSG_NXTHDR(&msg, cmsg); 217 } 218 *cmd = data.cmd; 219 *num = data.num; 220 *idx = data.idx; 221 *mode = data.mode; 222 #ifdef DEBUG 223 if (log_level >= 3) { 224 fdpass_log(f); 225 log_puts(": recv: cmd = "); 226 log_puti(*cmd); 227 log_puts(", num = "); 228 log_puti(*num); 229 log_puts(", idx = "); 230 log_puti(*idx); 231 log_puts(", mode = "); 232 log_puti(*mode); 233 log_puts(", fd = "); 234 log_puti(*fd); 235 log_puts("\n"); 236 } 237 #endif 238 return 1; 239 } 240 241 static int 242 fdpass_waitret(struct fdpass *f, int *retfd) 243 { 244 int cmd, unused; 245 246 if (!fdpass_recv(fdpass_peer, &cmd, &unused, &unused, &unused, retfd)) 247 return 0; 248 if (cmd != FDPASS_RETURN) { 249 if (log_level >= 1) { 250 fdpass_log(f); 251 log_puts(": expected RETURN message\n"); 252 } 253 fdpass_close(f); 254 return 0; 255 } 256 return 1; 257 } 258 259 struct sio_hdl * 260 fdpass_sio_open(int num, int idx, unsigned int mode) 261 { 262 int fd; 263 264 if (fdpass_peer == NULL) 265 return NULL; 266 if (!fdpass_send(fdpass_peer, FDPASS_OPEN_SND, num, idx, mode, -1)) 267 return NULL; 268 if (!fdpass_waitret(fdpass_peer, &fd)) 269 return NULL; 270 if (fd < 0) 271 return NULL; 272 return sio_sun_fdopen(fd, mode, 1); 273 } 274 275 struct mio_hdl * 276 fdpass_mio_open(int num, int idx, unsigned int mode) 277 { 278 int fd; 279 280 if (fdpass_peer == NULL) 281 return NULL; 282 if (!fdpass_send(fdpass_peer, FDPASS_OPEN_MIDI, num, idx, mode, -1)) 283 return NULL; 284 if (!fdpass_waitret(fdpass_peer, &fd)) 285 return NULL; 286 if (fd < 0) 287 return NULL; 288 return mio_rmidi_fdopen(fd, mode, 1); 289 } 290 291 struct sioctl_hdl * 292 fdpass_sioctl_open(int num, int idx, unsigned int mode) 293 { 294 int fd; 295 296 if (fdpass_peer == NULL) 297 return NULL; 298 if (!fdpass_send(fdpass_peer, FDPASS_OPEN_CTL, num, idx, mode, -1)) 299 return NULL; 300 if (!fdpass_waitret(fdpass_peer, &fd)) 301 return NULL; 302 if (fd < 0) 303 return NULL; 304 return sioctl_sun_fdopen(fd, mode, 1); 305 } 306 307 void 308 fdpass_in_worker(void *arg) 309 { 310 struct fdpass *f = arg; 311 312 if (log_level >= 3) { 313 fdpass_log(f); 314 log_puts(": exit\n"); 315 } 316 fdpass_close(f); 317 return; 318 } 319 320 void 321 fdpass_in_helper(void *arg) 322 { 323 int cmd, num, idx, mode, fd; 324 struct fdpass *f = arg; 325 struct dev *d; 326 struct dev_alt *da; 327 struct port *p; 328 char *path; 329 330 if (!fdpass_recv(f, &cmd, &num, &idx, &mode, &fd)) 331 return; 332 switch (cmd) { 333 case FDPASS_OPEN_SND: 334 d = dev_bynum(num); 335 if (d == NULL || !(mode & (SIO_PLAY | SIO_REC))) { 336 if (log_level >= 1) { 337 fdpass_log(f); 338 log_puts(": bad audio device or mode\n"); 339 } 340 fdpass_close(f); 341 return; 342 } 343 for (da = d->alt_list; ; da = da->next) { 344 if (da == NULL) { 345 fdpass_close(f); 346 return; 347 } 348 if (da->idx == idx) 349 break; 350 } 351 fd = sio_sun_getfd(da->name, mode, 1); 352 break; 353 case FDPASS_OPEN_MIDI: 354 p = port_bynum(num); 355 if (p == NULL || !(mode & (MIO_IN | MIO_OUT))) { 356 if (log_level >= 1) { 357 fdpass_log(f); 358 log_puts(": bad midi port or mode\n"); 359 } 360 fdpass_close(f); 361 return; 362 } 363 path = namelist_byindex(&p->path_list, idx); 364 if (path == NULL) { 365 fdpass_close(f); 366 return; 367 } 368 fd = mio_rmidi_getfd(path, mode, 1); 369 break; 370 case FDPASS_OPEN_CTL: 371 d = dev_bynum(num); 372 if (d == NULL || !(mode & (SIOCTL_READ | SIOCTL_WRITE))) { 373 if (log_level >= 1) { 374 fdpass_log(f); 375 log_puts(": bad audio control device\n"); 376 } 377 fdpass_close(f); 378 return; 379 } 380 for (da = d->alt_list; ; da = da->next) { 381 if (da == NULL) { 382 fdpass_close(f); 383 return; 384 } 385 if (da->idx == idx) 386 break; 387 } 388 fd = sioctl_sun_getfd(da->name, mode, 1); 389 break; 390 default: 391 fdpass_close(f); 392 return; 393 } 394 fdpass_send(f, FDPASS_RETURN, 0, 0, 0, fd); 395 } 396 397 void 398 fdpass_out(void *arg) 399 { 400 } 401 402 void 403 fdpass_hup(void *arg) 404 { 405 struct fdpass *f = arg; 406 407 if (log_level >= 3) { 408 fdpass_log(f); 409 log_puts(": hup\n"); 410 } 411 fdpass_close(f); 412 } 413 414 struct fdpass * 415 fdpass_new(int sock, struct fileops *ops) 416 { 417 struct fdpass *f; 418 419 f = xmalloc(sizeof(struct fdpass)); 420 f->file = file_new(ops, f, ops->name, 1); 421 if (f->file == NULL) { 422 close(sock); 423 xfree(f); 424 return NULL; 425 } 426 f->fd = sock; 427 fdpass_peer = f; 428 return f; 429 } 430 431 void 432 fdpass_close(struct fdpass *f) 433 { 434 fdpass_peer = NULL; 435 file_del(f->file); 436 close(f->fd); 437 xfree(f); 438 } 439 440 int 441 fdpass_pollfd(void *arg, struct pollfd *pfd) 442 { 443 struct fdpass *f = arg; 444 445 pfd->fd = f->fd; 446 pfd->events = POLLIN; 447 return 1; 448 } 449 450 int 451 fdpass_revents(void *arg, struct pollfd *pfd) 452 { 453 return pfd->revents; 454 } 455