xref: /openbsd-src/usr.bin/sndiod/fdpass.c (revision 7350f337b9e3eb4461d99580e625c7ef148d107c)
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