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
fdpass_send(struct fdpass * f,int cmd,int num,int mode,int fd)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
fdpass_recv(struct fdpass * f,int * cmd,int * num,int * mode,int * fd)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
fdpass_waitret(struct fdpass * f,int * retfd)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 *
fdpass_sio_open(int num,unsigned int mode)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 *
fdpass_mio_open(int num,unsigned int mode)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 *
fdpass_sioctl_open(int num,unsigned int mode)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
fdpass_in_worker(void * arg)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
fdpass_in_helper(void * arg)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
fdpass_out(void * arg)309 fdpass_out(void *arg)
310 {
311 }
312
313 void
fdpass_hup(void * arg)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 *
fdpass_new(int sock,struct fileops * ops)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
fdpass_close(struct fdpass * f)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
fdpass_pollfd(void * arg,struct pollfd * pfd)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
fdpass_revents(void * arg,struct pollfd * pfd)359 fdpass_revents(void *arg, struct pollfd *pfd)
360 {
361 return pfd->revents;
362 }
363