1
0
mirror of https://github.com/ericonr/sndio.git synced 2024-02-18 04:45:21 -06:00
sndio/libsndio/aucat.c
Alexandre Ratchov 285eafeec0 Add affinity between the program and its mixer control.
Currently, if there are two instances of the same program, sndiod will
allocate one volume control to each. If both programs disconnect and
reconnect, the information of which control is assigned to which
program is lost. This makes difficult to run two instances of a player
and crossfade between each other with a MIDI controller.

To address this, the program chooses a 32-bit "id" (for now the
process pid) and sends it to the server. The server records the id in
the client's slot structure.  When the server accepts a new
connection, it uses the id to identify the slot the client used during
the previous connection; if it was not recycled yet, it's assigned to
the program.
2019-07-12 08:31:01 +02:00

636 lines
13 KiB
C

/* $OpenBSD$ */
/*
* Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <netdb.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "aucat.h"
#include "debug.h"
#include "bsd-compat.h"
#ifndef HAVE_ARC4RANDOM
#ifndef DEV_RANDOM
#define DEV_RANDOM "/dev/urandom"
#endif
static int
random_bytes(unsigned char *buf, int len)
{
ssize_t n;
int fd;
fd = open(DEV_RANDOM, O_RDONLY);
if (fd == -1) {
DPERROR(DEV_RANDOM);
return 0;
}
while (len > 0) {
n = read(fd, buf, len);
if (n == -1) {
if (errno == EINTR)
continue;
DPERROR(DEV_RANDOM);
close(fd);
return 0;
}
if (n == 0) {
DPRINTF("%s: unexpected eof\n", DEV_RANDOM);
close(fd);
return 0;
}
buf += n;
len -= n;
}
close(fd);
return 1;
}
#endif
/*
* read a message, return 0 if not completed
*/
int
_aucat_rmsg(struct aucat *hdl, int *eof)
{
ssize_t n;
unsigned char *data;
if (hdl->rstate != RSTATE_MSG) {
DPRINTF("_aucat_rmsg: bad state\n");
abort();
}
while (hdl->rtodo > 0) {
data = (unsigned char *)&hdl->rmsg;
data += sizeof(struct amsg) - hdl->rtodo;
while ((n = read(hdl->fd, data, hdl->rtodo)) == -1) {
if (errno == EINTR)
continue;
if (errno != EAGAIN) {
*eof = 1;
DPERROR("_aucat_rmsg: read");
}
return 0;
}
if (n == 0) {
DPRINTF("_aucat_rmsg: eof\n");
*eof = 1;
return 0;
}
hdl->rtodo -= n;
}
if (ntohl(hdl->rmsg.cmd) == AMSG_DATA) {
hdl->rtodo = ntohl(hdl->rmsg.u.data.size);
hdl->rstate = RSTATE_DATA;
} else {
hdl->rtodo = sizeof(struct amsg);
hdl->rstate = RSTATE_MSG;
}
return 1;
}
/*
* write a message, return 0 if not completed
*/
int
_aucat_wmsg(struct aucat *hdl, int *eof)
{
ssize_t n;
unsigned char *data;
if (hdl->wstate == WSTATE_IDLE) {
hdl->wstate = WSTATE_MSG;
hdl->wtodo = sizeof(struct amsg);
}
if (hdl->wstate != WSTATE_MSG) {
DPRINTF("_aucat_wmsg: bad state\n");
abort();
}
while (hdl->wtodo > 0) {
data = (unsigned char *)&hdl->wmsg;
data += sizeof(struct amsg) - hdl->wtodo;
while ((n = write(hdl->fd, data, hdl->wtodo)) == -1) {
if (errno == EINTR)
continue;
if (errno != EAGAIN) {
*eof = 1;
DPERROR("_aucat_wmsg: write");
}
return 0;
}
hdl->wtodo -= n;
}
if (ntohl(hdl->wmsg.cmd) == AMSG_DATA) {
hdl->wtodo = ntohl(hdl->wmsg.u.data.size);
hdl->wstate = WSTATE_DATA;
} else {
hdl->wtodo = 0xdeadbeef;
hdl->wstate = WSTATE_IDLE;
}
return 1;
}
size_t
_aucat_rdata(struct aucat *hdl, void *buf, size_t len, int *eof)
{
ssize_t n;
if (hdl->rstate != RSTATE_DATA) {
DPRINTF("_aucat_rdata: bad state\n");
abort();
}
if (len > hdl->rtodo)
len = hdl->rtodo;
while ((n = read(hdl->fd, buf, len)) == -1) {
if (errno == EINTR)
continue;
if (errno != EAGAIN) {
*eof = 1;
DPERROR("_aucat_rdata: read");
}
return 0;
}
if (n == 0) {
DPRINTF("_aucat_rdata: eof\n");
*eof = 1;
return 0;
}
hdl->rtodo -= n;
if (hdl->rtodo == 0) {
hdl->rstate = RSTATE_MSG;
hdl->rtodo = sizeof(struct amsg);
}
DPRINTFN(2, "_aucat_rdata: read: n = %zd\n", n);
return n;
}
size_t
_aucat_wdata(struct aucat *hdl, const void *buf, size_t len,
unsigned int wbpf, int *eof)
{
ssize_t n;
size_t datasize;
switch (hdl->wstate) {
case WSTATE_IDLE:
datasize = len;
if (datasize > AMSG_DATAMAX)
datasize = AMSG_DATAMAX;
datasize -= datasize % wbpf;
if (datasize == 0)
datasize = wbpf;
hdl->wmsg.cmd = htonl(AMSG_DATA);
hdl->wmsg.u.data.size = htonl(datasize);
hdl->wtodo = sizeof(struct amsg);
hdl->wstate = WSTATE_MSG;
/* FALLTHROUGH */
case WSTATE_MSG:
if (!_aucat_wmsg(hdl, eof))
return 0;
}
if (len > hdl->wtodo)
len = hdl->wtodo;
if (len == 0) {
DPRINTF("_aucat_wdata: len == 0\n");
abort();
}
while ((n = write(hdl->fd, buf, len)) == -1) {
if (errno == EINTR)
continue;
if (errno != EAGAIN) {
*eof = 1;
DPERROR("_aucat_wdata: write");
}
return 0;
}
DPRINTFN(2, "_aucat_wdata: write: n = %zd\n", n);
hdl->wtodo -= n;
if (hdl->wtodo == 0) {
hdl->wstate = WSTATE_IDLE;
hdl->wtodo = 0xdeadbeef;
}
return n;
}
static int
aucat_mkcookie(unsigned char *cookie)
{
#define COOKIE_DIR "/.sndio"
#define COOKIE_SUFFIX "/.sndio/cookie"
#define TEMPL_SUFFIX ".XXXXXXXX"
struct stat sb;
char *home, *path = NULL, *tmp = NULL;
size_t home_len, path_len;
int fd, len;
/* please gcc */
path_len = 0xdeadbeef;
/*
* try to load the cookie
*/
home = issetugid() ? NULL : getenv("HOME");
if (home == NULL)
goto bad_gen;
home_len = strlen(home);
path = malloc(home_len + sizeof(COOKIE_SUFFIX));
if (path == NULL)
goto bad_gen;
memcpy(path, home, home_len);
memcpy(path + home_len, COOKIE_SUFFIX, sizeof(COOKIE_SUFFIX));
path_len = home_len + sizeof(COOKIE_SUFFIX) - 1;
fd = open(path, O_RDONLY);
if (fd == -1) {
if (errno != ENOENT)
DPERROR(path);
goto bad_gen;
}
if (fstat(fd, &sb) == -1) {
DPERROR(path);
goto bad_close;
}
if (sb.st_mode & 0077) {
DPRINTF("%s has wrong permissions\n", path);
goto bad_close;
}
len = read(fd, cookie, AMSG_COOKIELEN);
if (len == -1) {
DPERROR(path);
goto bad_close;
}
if (len != AMSG_COOKIELEN) {
DPRINTF("%s: short read\n", path);
goto bad_close;
}
close(fd);
goto done;
bad_close:
close(fd);
bad_gen:
/*
* generate a new cookie
*/
#ifdef HAVE_ARC4RANDOM
arc4random_buf(cookie, AMSG_COOKIELEN);
#else
if (!random_bytes(cookie, AMSG_COOKIELEN)) {
if (path)
free(path);
return 0;
}
#endif
/*
* try to save the cookie
*/
if (home == NULL)
goto done;
tmp = malloc(path_len + sizeof(TEMPL_SUFFIX));
if (tmp == NULL)
goto done;
/* create ~/.sndio directory */
memcpy(tmp, home, home_len);
memcpy(tmp + home_len, COOKIE_DIR, sizeof(COOKIE_DIR));
if (mkdir(tmp, 0755) == -1 && errno != EEXIST)
goto done;
/* create cookie file in it */
memcpy(tmp, path, path_len);
memcpy(tmp + path_len, TEMPL_SUFFIX, sizeof(TEMPL_SUFFIX));
fd = mkstemp(tmp);
if (fd == -1) {
DPERROR(tmp);
goto done;
}
if (write(fd, cookie, AMSG_COOKIELEN) == -1) {
DPERROR(tmp);
unlink(tmp);
close(fd);
goto done;
}
close(fd);
if (rename(tmp, path) == -1) {
DPERROR(tmp);
unlink(tmp);
}
done:
free(tmp);
free(path);
return 1;
}
static int
aucat_connect_tcp(struct aucat *hdl, char *host, unsigned int unit)
{
int s, error, opt;
struct addrinfo *ailist, *ai, aihints;
char serv[NI_MAXSERV];
snprintf(serv, sizeof(serv), "%u", unit + AUCAT_PORT);
memset(&aihints, 0, sizeof(struct addrinfo));
aihints.ai_socktype = SOCK_STREAM;
aihints.ai_protocol = IPPROTO_TCP;
error = getaddrinfo(host, serv, &aihints, &ailist);
if (error) {
DPRINTF("%s: %s\n", host, gai_strerror(error));
return 0;
}
s = -1;
for (ai = ailist; ai != NULL; ai = ai->ai_next) {
s = socket(ai->ai_family, ai->ai_socktype | SOCK_CLOEXEC,
ai->ai_protocol);
if (s == -1) {
DPERROR("socket");
continue;
}
#ifndef HAVE_SOCK_CLOEXEC
if (fcntl(s, F_SETFL, FD_CLOEXEC) == -1) {
DPERROR("FD_CLOEXEC");
close(s);
s = - 1;
continue;
}
#endif
restart:
if (connect(s, ai->ai_addr, ai->ai_addrlen) == -1) {
if (errno == EINTR)
goto restart;
DPERROR("connect");
close(s);
s = -1;
continue;
}
break;
}
freeaddrinfo(ailist);
if (s == -1)
return 0;
opt = 1;
if (setsockopt(s, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(int)) == -1) {
DPERROR("setsockopt");
close(s);
return 0;
}
hdl->fd = s;
return 1;
}
static int
aucat_connect_un(struct aucat *hdl, unsigned int unit)
{
struct sockaddr_un ca;
socklen_t len = sizeof(struct sockaddr_un);
uid_t uid;
int s;
uid = geteuid();
snprintf(ca.sun_path, sizeof(ca.sun_path),
SOCKPATH_DIR "-%u/" SOCKPATH_FILE "%u", uid, unit);
ca.sun_family = AF_UNIX;
s = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
if (s == -1)
return 0;
#ifndef HAVE_SOCK_CLOEXEC
if (fcntl(s, F_SETFL, FD_CLOEXEC) == -1) {
DPERROR("FD_CLOEXEC");
close(s);
return 0;
}
#endif
while (connect(s, (struct sockaddr *)&ca, len) == -1) {
if (errno == EINTR)
continue;
DPERROR(ca.sun_path);
/* try shared server */
snprintf(ca.sun_path, sizeof(ca.sun_path),
SOCKPATH_DIR "/" SOCKPATH_FILE "%u", unit);
while (connect(s, (struct sockaddr *)&ca, len) == -1) {
if (errno == EINTR)
continue;
DPERROR(ca.sun_path);
close(s);
return 0;
}
break;
}
hdl->fd = s;
DPRINTFN(2, "%s: connected\n", ca.sun_path);
return 1;
}
static const char *
parsestr(const char *str, char *rstr, unsigned int max)
{
const char *p = str;
while (*p != '\0' && *p != ',' && *p != '/') {
if (--max == 0) {
DPRINTF("%s: string too long\n", str);
return NULL;
}
*rstr++ = *p++;
}
if (str == p) {
DPRINTF("%s: string expected\n", str);
return NULL;
}
*rstr = '\0';
return p;
}
int
_aucat_open(struct aucat *hdl, const char *str, unsigned int mode)
{
extern char *__progname;
int eof;
char host[NI_MAXHOST], opt[AMSG_OPTMAX];
const char *p;
unsigned int unit, devnum, type;
if ((p = _sndio_parsetype(str, "snd")) != NULL)
type = 0;
else if ((p = _sndio_parsetype(str, "midithru")) != NULL)
type = 1;
else if ((p = _sndio_parsetype(str, "midi")) != NULL)
type = 2;
else {
DPRINTF("%s: unsupported device type\n", str);
return -1;
}
if (*p == '@') {
p = parsestr(++p, host, NI_MAXHOST);
if (p == NULL)
return 0;
} else
*host = '\0';
if (*p == ',') {
p = _sndio_parsenum(++p, &unit, 15);
if (p == NULL)
return 0;
} else
unit = 0;
if (*p != '/') {
DPRINTF("%s: '/' expected\n", str);
return 0;
}
p = _sndio_parsenum(++p, &devnum, 15);
if (p == NULL)
return 0;
if (*p == '.') {
p = parsestr(++p, opt, AMSG_OPTMAX);
if (p == NULL)
return 0;
} else
strlcpy(opt, "default", AMSG_OPTMAX);
if (*p != '\0') {
DPRINTF("%s: junk at end of dev name\n", p);
return 0;
}
devnum += type * 16; /* XXX */
DPRINTFN(2, "_aucat_open: host=%s unit=%u devnum=%u opt=%s\n",
host, unit, devnum, opt);
if (host[0] != '\0') {
if (!aucat_connect_tcp(hdl, host, unit))
return 0;
} else {
if (!aucat_connect_un(hdl, unit))
return 0;
}
hdl->rstate = RSTATE_MSG;
hdl->rtodo = sizeof(struct amsg);
hdl->wstate = WSTATE_IDLE;
hdl->wtodo = 0xdeadbeef;
hdl->maxwrite = 0;
/*
* say hello to server
*/
AMSG_INIT(&hdl->wmsg);
hdl->wmsg.cmd = htonl(AMSG_AUTH);
if (!aucat_mkcookie(hdl->wmsg.u.auth.cookie))
goto bad_connect;
hdl->wtodo = sizeof(struct amsg);
if (!_aucat_wmsg(hdl, &eof))
goto bad_connect;
AMSG_INIT(&hdl->wmsg);
hdl->wmsg.cmd = htonl(AMSG_HELLO);
hdl->wmsg.u.hello.version = AMSG_VERSION;
hdl->wmsg.u.hello.mode = htons(mode);
hdl->wmsg.u.hello.devnum = devnum;
hdl->wmsg.u.hello.id = htonl(getpid());
strlcpy(hdl->wmsg.u.hello.who, __progname,
sizeof(hdl->wmsg.u.hello.who));
strlcpy(hdl->wmsg.u.hello.opt, opt,
sizeof(hdl->wmsg.u.hello.opt));
hdl->wtodo = sizeof(struct amsg);
if (!_aucat_wmsg(hdl, &eof))
goto bad_connect;
hdl->rtodo = sizeof(struct amsg);
if (!_aucat_rmsg(hdl, &eof)) {
DPRINTF("aucat_init: mode refused\n");
goto bad_connect;
}
if (ntohl(hdl->rmsg.cmd) != AMSG_ACK) {
DPRINTF("aucat_init: protocol err\n");
goto bad_connect;
}
return 1;
bad_connect:
while (close(hdl->fd) == -1 && errno == EINTR)
; /* retry */
return 0;
}
void
_aucat_close(struct aucat *hdl, int eof)
{
char dummy[sizeof(struct amsg)];
ssize_t n;
if (!eof) {
AMSG_INIT(&hdl->wmsg);
hdl->wmsg.cmd = htonl(AMSG_BYE);
hdl->wtodo = sizeof(struct amsg);
if (!_aucat_wmsg(hdl, &eof))
goto bad_close;
/*
* block until the peer disconnects
*/
while (1) {
n = read(hdl->fd, dummy, sizeof(dummy));
if (n == -1) {
if (errno == EINTR)
continue;
break;
}
if (n == 0)
break;
}
}
bad_close:
while (close(hdl->fd) == -1 && errno == EINTR)
; /* nothing */
}
int
_aucat_setfl(struct aucat *hdl, int nbio, int *eof)
{
if (fcntl(hdl->fd, F_SETFL, nbio ? O_NONBLOCK : 0) == -1) {
DPERROR("_aucat_setfl: fcntl");
*eof = 1;
return 0;
}
return 1;
}
int
_aucat_pollfd(struct aucat *hdl, struct pollfd *pfd, int events)
{
if (hdl->rstate == RSTATE_MSG)
events |= POLLIN;
pfd->fd = hdl->fd;
pfd->events = events;
return 1;
}
int
_aucat_revents(struct aucat *hdl, struct pollfd *pfd)
{
int revents = pfd->revents;
DPRINTFN(2, "_aucat_revents: revents: %x\n", revents);
return revents;
}