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