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