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