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