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