xref: /openbsd/usr.bin/sndiod/fdpass.c (revision 36355b88)
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 
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 void
fdpass_log(struct fdpass * f)73 fdpass_log(struct fdpass *f)
74 {
75 	log_puts(f->file->name);
76 }
77 
78 static int
fdpass_send(struct fdpass * f,int cmd,int num,int mode,int fd)79 fdpass_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 
144 static int
fdpass_recv(struct fdpass * f,int * cmd,int * num,int * mode,int * fd)145 fdpass_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 
234 static int
fdpass_waitret(struct fdpass * f,int * retfd)235 fdpass_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 
252 struct sio_hdl *
fdpass_sio_open(int num,unsigned int mode)253 fdpass_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 
268 struct mio_hdl *
fdpass_mio_open(int num,unsigned int mode)269 fdpass_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 
284 struct sioctl_hdl *
fdpass_sioctl_open(int num,unsigned int mode)285 fdpass_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 
300 void
fdpass_in_worker(void * arg)301 fdpass_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 
313 void
fdpass_in_helper(void * arg)314 fdpass_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 
367 void
fdpass_out(void * arg)368 fdpass_out(void *arg)
369 {
370 }
371 
372 void
fdpass_hup(void * arg)373 fdpass_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 
384 struct fdpass *
fdpass_new(int sock,struct fileops * ops)385 fdpass_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 
401 void
fdpass_close(struct fdpass * f)402 fdpass_close(struct fdpass *f)
403 {
404 	fdpass_peer = NULL;
405 	file_del(f->file);
406 	close(f->fd);
407 	xfree(f);
408 }
409 
410 int
fdpass_pollfd(void * arg,struct pollfd * pfd)411 fdpass_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 
420 int
fdpass_revents(void * arg,struct pollfd * pfd)421 fdpass_revents(void *arg, struct pollfd *pfd)
422 {
423 	return pfd->revents;
424 }
425