#include <sys/socket.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <sndio.h>
#include <string.h>
#include <unistd.h>
#include "dev.h"
#include "fdpass.h"
#include "file.h"
#include "listen.h"
#include "midi.h"
#include "sock.h"
#include "utils.h"
struct fdpass_msg {
#define FDPASS_OPEN_SND 0
#define FDPASS_OPEN_MIDI 1
#define FDPASS_OPEN_CTL 2
#define FDPASS_RETURN 3
unsigned int cmd;
unsigned int num;
unsigned int mode;
};
int fdpass_pollfd(void *, struct pollfd *);
int fdpass_revents(void *, struct pollfd *);
void fdpass_in_worker(void *);
void fdpass_in_helper(void *);
void fdpass_out(void *);
void fdpass_hup(void *);
struct fileops worker_fileops = {
"worker",
fdpass_pollfd,
fdpass_revents,
fdpass_in_worker,
fdpass_out,
fdpass_hup
};
struct fileops helper_fileops = {
"helper",
fdpass_pollfd,
fdpass_revents,
fdpass_in_helper,
fdpass_out,
fdpass_hup
};
struct fdpass {
struct file *file;
int fd;
} *fdpass_peer = NULL;
static int
fdpass_send(struct fdpass *f, int cmd, int num, int mode, int fd)
{
struct fdpass_msg data;
struct msghdr msg;
struct cmsghdr *cmsg;
union {
struct cmsghdr hdr;
unsigned char buf[CMSG_SPACE(sizeof(int))];
} cmsgbuf;
struct iovec iov;
ssize_t n;
data.cmd = cmd;
data.num = num;
data.mode = mode;
iov.iov_base = &data;
iov.iov_len = sizeof(struct fdpass_msg);
memset(&msg, 0, sizeof(msg));
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
if (fd >= 0) {
msg.msg_control = &cmsgbuf.buf;
msg.msg_controllen = sizeof(cmsgbuf.buf);
cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
*(int *)CMSG_DATA(cmsg) = fd;
}
n = sendmsg(f->fd, &msg, 0);
if (n == -1) {
logx(1, "%s: sendmsg failed", f->file->name);
fdpass_close(f);
return 0;
}
if (n != sizeof(struct fdpass_msg)) {
logx(1, "%s: short write", f->file->name);
fdpass_close(f);
return 0;
}
#ifdef DEBUG
logx(3, "%s: send: cmd = %d, num = %d, mode = %d, fd = %d",
f->file->name, cmd, num, mode, fd);
#endif
if (fd >= 0)
close(fd);
return 1;
}
static int
fdpass_recv(struct fdpass *f, int *cmd, int *num, int *mode, int *fd)
{
struct fdpass_msg data;
struct msghdr msg;
struct cmsghdr *cmsg;
union {
struct cmsghdr hdr;
unsigned char buf[CMSG_SPACE(sizeof(int))];
} cmsgbuf;
struct iovec iov;
ssize_t n;
iov.iov_base = &data;
iov.iov_len = sizeof(struct fdpass_msg);
memset(&msg, 0, sizeof(msg));
msg.msg_control = &cmsgbuf.buf;
msg.msg_controllen = sizeof(cmsgbuf.buf);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
n = recvmsg(f->fd, &msg, MSG_WAITALL);
if (n == -1 && errno == EMSGSIZE) {
logx(1, "%s: out of fds", f->file->name);
n = recvmsg(f->fd, &msg, MSG_WAITALL);
}
if (n == -1) {
logx(1, "%s: recvmsg failed", f->file->name);
fdpass_close(f);
return 0;
}
if (n == 0) {
logx(3, "%s: recvmsg eof", f->file->name);
fdpass_close(f);
return 0;
}
if (msg.msg_flags & (MSG_TRUNC | MSG_CTRUNC)) {
logx(1, "%s: truncated", f->file->name);
fdpass_close(f);
return 0;
}
cmsg = CMSG_FIRSTHDR(&msg);
for (;;) {
if (cmsg == NULL) {
*fd = -1;
break;
}
if (cmsg->cmsg_len == CMSG_LEN(sizeof(int)) &&
cmsg->cmsg_level == SOL_SOCKET &&
cmsg->cmsg_type == SCM_RIGHTS) {
*fd = *(int *)CMSG_DATA(cmsg);
break;
}
cmsg = CMSG_NXTHDR(&msg, cmsg);
}
*cmd = data.cmd;
*num = data.num;
*mode = data.mode;
#ifdef DEBUG
logx(3, "%s: recv: cmd = %d, num = %d, mode = %d, fd = %d",
f->file->name, *cmd, *num, *mode, *fd);
#endif
return 1;
}
static int
fdpass_waitret(struct fdpass *f, int *retfd)
{
int cmd, unused;
if (!fdpass_recv(fdpass_peer, &cmd, &unused, &unused, retfd))
return 0;
if (cmd != FDPASS_RETURN) {
logx(1, "%s: expected RETURN message", f->file->name);
fdpass_close(f);
return 0;
}
return 1;
}
struct sio_hdl *
fdpass_sio_open(int num, unsigned int mode)
{
int fd;
if (fdpass_peer == NULL)
return NULL;
if (!fdpass_send(fdpass_peer, FDPASS_OPEN_SND, num, mode, -1))
return NULL;
if (!fdpass_waitret(fdpass_peer, &fd))
return NULL;
if (fd < 0)
return NULL;
return sio_sun_fdopen(fd, mode, 1);
}
struct mio_hdl *
fdpass_mio_open(int num, unsigned int mode)
{
int fd;
if (fdpass_peer == NULL)
return NULL;
if (!fdpass_send(fdpass_peer, FDPASS_OPEN_MIDI, num, mode, -1))
return NULL;
if (!fdpass_waitret(fdpass_peer, &fd))
return NULL;
if (fd < 0)
return NULL;
return mio_rmidi_fdopen(fd, mode, 1);
}
struct sioctl_hdl *
fdpass_sioctl_open(int num, unsigned int mode)
{
int fd;
if (fdpass_peer == NULL)
return NULL;
if (!fdpass_send(fdpass_peer, FDPASS_OPEN_CTL, num, mode, -1))
return NULL;
if (!fdpass_waitret(fdpass_peer, &fd))
return NULL;
if (fd < 0)
return NULL;
return sioctl_sun_fdopen(fd, mode, 1);
}
void
fdpass_in_worker(void *arg)
{
struct fdpass *f = arg;
logx(3, "%s: exit", f->file->name);
fdpass_close(f);
return;
}
void
fdpass_in_helper(void *arg)
{
int cmd, num, mode, fd;
struct fdpass *f = arg;
struct dev *d;
struct port *p;
if (!fdpass_recv(f, &cmd, &num, &mode, &fd))
return;
switch (cmd) {
case FDPASS_OPEN_SND:
d = dev_bynum(num);
if (d == NULL || !(mode & (SIO_PLAY | SIO_REC))) {
logx(1, "%s: bad audio device or mode", f->file->name);
fdpass_close(f);
return;
}
fd = sio_sun_getfd(d->path, mode, 1);
break;
case FDPASS_OPEN_MIDI:
p = port_bynum(num);
if (p == NULL || !(mode & (MIO_IN | MIO_OUT))) {
logx(1, "%s: bad midi port or mode", f->file->name);
fdpass_close(f);
return;
}
fd = mio_rmidi_getfd(p->path, mode, 1);
break;
case FDPASS_OPEN_CTL:
d = dev_bynum(num);
if (d == NULL || !(mode & (SIOCTL_READ | SIOCTL_WRITE))) {
logx(1, "%s: bad control device", f->file->name);
fdpass_close(f);
return;
}
fd = sioctl_sun_getfd(d->path, mode, 1);
break;
default:
fdpass_close(f);
return;
}
fdpass_send(f, FDPASS_RETURN, 0, 0, fd);
}
void
fdpass_out(void *arg)
{
}
void
fdpass_hup(void *arg)
{
struct fdpass *f = arg;
logx(3, "%s: hup", f->file->name);
fdpass_close(f);
}
struct fdpass *
fdpass_new(int sock, struct fileops *ops)
{
struct fdpass *f;
f = xmalloc(sizeof(struct fdpass));
f->file = file_new(ops, f, ops->name, 1);
if (f->file == NULL) {
close(sock);
xfree(f);
return NULL;
}
f->fd = sock;
fdpass_peer = f;
return f;
}
void
fdpass_close(struct fdpass *f)
{
fdpass_peer = NULL;
file_del(f->file);
close(f->fd);
xfree(f);
}
int
fdpass_pollfd(void *arg, struct pollfd *pfd)
{
struct fdpass *f = arg;
pfd->fd = f->fd;
pfd->events = POLLIN;
return 1;
}
int
fdpass_revents(void *arg, struct pollfd *pfd)
{
return pfd->revents;
}