mirror of
https://github.com/ericonr/sndio.git
synced 2024-02-18 04:45:21 -06:00
285eafeec0
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.
636 lines
13 KiB
C
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;
|
|
}
|