diff --git a/aserver/aserver.c b/aserver/aserver.c index 2838702..c2c7f91 100644 --- a/aserver/aserver.c +++ b/aserver/aserver.c @@ -35,6 +35,8 @@ #include "aserver.h" +#undef open + char *command; #if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 95) diff --git a/aserver/aserver.c.glibc-open b/aserver/aserver.c.glibc-open new file mode 100644 index 0000000..2838702 --- /dev/null +++ b/aserver/aserver.c.glibc-open @@ -0,0 +1,1091 @@ +/* + * ALSA server + * Copyright (c) by Abramo Bagnara + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "aserver.h" + +char *command; + +#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 95) +#define ERROR(...) do {\ + fprintf(stderr, "%s %s:%i:(%s) ", command, __FILE__, __LINE__, __func__); \ + fprintf(stderr, __VA_ARGS__); \ + putc('\n', stderr); \ +} while (0) +#else +#define ERROR(args...) do {\ + fprintf(stderr, "%s %s:%i:(%s) ", command, __FILE__, __LINE__, __func__); \ + fprintf(stderr, ##args); \ + putc('\n', stderr); \ +} while (0) +#endif + +#define SYSERROR(string) ERROR(string ": %s", strerror(errno)) + +static int make_local_socket(const char *filename) +{ + size_t l = strlen(filename); + size_t size = offsetof(struct sockaddr_un, sun_path) + l; + struct sockaddr_un *addr = alloca(size); + int sock; + + sock = socket(PF_LOCAL, SOCK_STREAM, 0); + if (sock < 0) { + int result = -errno; + SYSERROR("socket failed"); + return result; + } + + unlink(filename); + + addr->sun_family = AF_LOCAL; + memcpy(addr->sun_path, filename, l); + + if (bind(sock, (struct sockaddr *) addr, size) < 0) { + int result = -errno; + SYSERROR("bind failed"); + close(sock); + return result; + } + + return sock; +} + +static int make_inet_socket(int port) +{ + struct sockaddr_in addr; + int sock; + + sock = socket(PF_INET, SOCK_STREAM, 0); + if (sock < 0) { + int result = -errno; + SYSERROR("socket failed"); + return result; + } + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = INADDR_ANY; + + if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + int result = -errno; + SYSERROR("bind failed"); + close(sock); + return result; + } + + return sock; +} + +struct pollfd *pollfds; +unsigned int pollfds_count = 0; +typedef struct waiter waiter_t; +typedef int (*waiter_handler_t)(waiter_t *waiter, unsigned short events); +struct waiter { + int fd; + void *private_data; + waiter_handler_t handler; +}; +waiter_t *waiters; + +static void add_waiter(int fd, unsigned short events, waiter_handler_t handler, + void *data) +{ + waiter_t *w = &waiters[fd]; + struct pollfd *pfd = &pollfds[pollfds_count]; + assert(!w->handler); + pfd->fd = fd; + pfd->events = events; + pfd->revents = 0; + w->fd = fd; + w->private_data = data; + w->handler = handler; + pollfds_count++; +} + +static void del_waiter(int fd) +{ + waiter_t *w = &waiters[fd]; + unsigned int k; + assert(w->handler); + w->handler = 0; + for (k = 0; k < pollfds_count; ++k) { + if (pollfds[k].fd == fd) + break; + } + assert(k < pollfds_count); + pollfds_count--; + memmove(&pollfds[k], &pollfds[k + 1], pollfds_count - k); +} + +typedef struct client client_t; + +typedef struct { + int (*open)(client_t *client, int *cookie); + int (*cmd)(client_t *client); + int (*close)(client_t *client); +} transport_ops_t; + +struct client { + struct list_head list; + int poll_fd; + int ctrl_fd; + int local; + int transport_type; + int dev_type; + char name[256]; + int stream; + int mode; + transport_ops_t *ops; + snd_async_handler_t *async_handler; + int async_sig; + pid_t async_pid; + union { + struct { + snd_pcm_t *handle; + int fd; + } pcm; + struct { + snd_ctl_t *handle; + int fd; + } ctl; +#if 0 + struct { + snd_rawmidi_t *handle; + } rawmidi; + struct { + snd_timer_open_t *handle; + } timer; + struct { + snd_hwdep_t *handle; + } hwdep; + struct { + snd_seq_t *handle; + } seq; +#endif + } device; + int polling; + int open; + int cookie; + union { + struct { + int ctrl_id; + void *ctrl; + } shm; + } transport; +}; + +LIST_HEAD(clients); + +typedef struct { + struct list_head list; + int fd; + uint32_t cookie; +} inet_pending_t; +LIST_HEAD(inet_pendings); + +#if 0 +static int pcm_handler(waiter_t *waiter, unsigned short events) +{ + client_t *client = waiter->private_data; + char buf[1]; + ssize_t n; + if (events & POLLIN) { + n = write(client->poll_fd, buf, 1); + if (n != 1) { + SYSERROR("write failed"); + return -errno; + } + } else if (events & POLLOUT) { + n = read(client->poll_fd, buf, 1); + if (n != 1) { + SYSERROR("read failed"); + return -errno; + } + } + del_waiter(waiter->fd); + client->polling = 0; + return 0; +} +#endif + +static void pcm_shm_hw_ptr_changed(snd_pcm_t *pcm, snd_pcm_t *src ATTRIBUTE_UNUSED) +{ + client_t *client = pcm->hw.private_data; + volatile snd_pcm_shm_ctrl_t *ctrl = client->transport.shm.ctrl; + snd_pcm_t *loop; + + ctrl->hw.changed = 1; + if (pcm->hw.fd >= 0) { + ctrl->hw.use_mmap = 1; + ctrl->hw.offset = pcm->hw.offset; + return; + } + ctrl->hw.use_mmap = 0; + ctrl->hw.ptr = pcm->hw.ptr ? *pcm->hw.ptr : 0; + for (loop = pcm->hw.master; loop; loop = loop->hw.master) + loop->hw.ptr = &ctrl->hw.ptr; + pcm->hw.ptr = &ctrl->hw.ptr; +} + +static void pcm_shm_appl_ptr_changed(snd_pcm_t *pcm, snd_pcm_t *src ATTRIBUTE_UNUSED) +{ + client_t *client = pcm->appl.private_data; + volatile snd_pcm_shm_ctrl_t *ctrl = client->transport.shm.ctrl; + snd_pcm_t *loop; + + ctrl->appl.changed = 1; + if (pcm->appl.fd >= 0) { + ctrl->appl.use_mmap = 1; + ctrl->appl.offset = pcm->appl.offset; + return; + } + ctrl->appl.use_mmap = 0; + ctrl->appl.ptr = pcm->appl.ptr ? *pcm->appl.ptr : 0; + for (loop = pcm->appl.master; loop; loop = loop->appl.master) + loop->appl.ptr = &ctrl->appl.ptr; + pcm->appl.ptr = &ctrl->appl.ptr; +} + +static int pcm_shm_open(client_t *client, int *cookie) +{ + int shmid; + snd_pcm_t *pcm; + int err; + int result; + err = snd_pcm_open(&pcm, client->name, client->stream, SND_PCM_NONBLOCK); + if (err < 0) + return err; + client->device.pcm.handle = pcm; + client->device.pcm.fd = _snd_pcm_poll_descriptor(pcm); + pcm->hw.private_data = client; + pcm->hw.changed = pcm_shm_hw_ptr_changed; + pcm->appl.private_data = client; + pcm->appl.changed = pcm_shm_appl_ptr_changed; + + shmid = shmget(IPC_PRIVATE, PCM_SHM_SIZE, 0666); + if (shmid < 0) { + result = -errno; + SYSERROR("shmget failed"); + goto _err; + } + client->transport.shm.ctrl_id = shmid; + client->transport.shm.ctrl = shmat(shmid, 0, 0); + if (client->transport.shm.ctrl == (void*) -1) { + result = -errno; + shmctl(shmid, IPC_RMID, 0); + SYSERROR("shmat failed"); + goto _err; + } + *cookie = shmid; + return 0; + + _err: + snd_pcm_close(pcm); + return result; + +} + +static int pcm_shm_close(client_t *client) +{ + int err; + snd_pcm_shm_ctrl_t *ctrl = client->transport.shm.ctrl; + if (client->polling) { + del_waiter(client->device.pcm.fd); + client->polling = 0; + } + err = snd_pcm_close(client->device.pcm.handle); + ctrl->result = err; + if (err < 0) + ERROR("snd_pcm_close"); + if (client->transport.shm.ctrl) { + err = shmdt((void *)client->transport.shm.ctrl); + if (err < 0) + SYSERROR("shmdt failed"); + err = shmctl(client->transport.shm.ctrl_id, IPC_RMID, 0); + if (err < 0) + SYSERROR("shmctl IPC_RMID failed"); + client->transport.shm.ctrl = 0; + } + client->open = 0; + return 0; +} + +static int shm_ack(client_t *client) +{ + struct pollfd pfd; + int err; + char buf[1]; + pfd.fd = client->ctrl_fd; + pfd.events = POLLHUP; + if (poll(&pfd, 1, 0) == 1) + return -EBADFD; + err = write(client->ctrl_fd, buf, 1); + if (err != 1) + return -EBADFD; + return 0; +} + +static int shm_ack_fd(client_t *client, int fd) +{ + struct pollfd pfd; + int err; + char buf[1]; + pfd.fd = client->ctrl_fd; + pfd.events = POLLHUP; + if (poll(&pfd, 1, 0) == 1) + return -EBADFD; + err = snd_send_fd(client->ctrl_fd, buf, 1, fd); + if (err != 1) + return -EBADFD; + return 0; +} + +static int shm_rbptr_fd(client_t *client, snd_pcm_rbptr_t *rbptr) +{ + if (rbptr->fd < 0) + return -EINVAL; + return shm_ack_fd(client, rbptr->fd); +} + +static void async_handler(snd_async_handler_t *handler) +{ + client_t *client = snd_async_handler_get_callback_private(handler); + /* FIXME: use sigqueue */ + kill(client->async_pid, client->async_sig); +} + +static int pcm_shm_cmd(client_t *client) +{ + volatile snd_pcm_shm_ctrl_t *ctrl = client->transport.shm.ctrl; + char buf[1]; + int err; + int cmd; + snd_pcm_t *pcm; + err = read(client->ctrl_fd, buf, 1); + if (err != 1) + return -EBADFD; + cmd = ctrl->cmd; + ctrl->cmd = 0; + pcm = client->device.pcm.handle; + switch (cmd) { + case SND_PCM_IOCTL_ASYNC: + ctrl->result = snd_pcm_async(pcm, ctrl->u.async.sig, ctrl->u.async.pid); + if (ctrl->result < 0) + break; + if (ctrl->u.async.sig >= 0) { + assert(client->async_sig < 0); + ctrl->result = snd_async_add_pcm_handler(&client->async_handler, pcm, async_handler, client); + if (ctrl->result < 0) + break; + } else { + assert(client->async_sig >= 0); + snd_async_del_handler(client->async_handler); + } + client->async_sig = ctrl->u.async.sig; + client->async_pid = ctrl->u.async.pid; + break; + case SNDRV_PCM_IOCTL_INFO: + ctrl->result = snd_pcm_info(pcm, (snd_pcm_info_t *) &ctrl->u.info); + break; + case SNDRV_PCM_IOCTL_HW_REFINE: + ctrl->result = snd_pcm_hw_refine(pcm, (snd_pcm_hw_params_t *) &ctrl->u.hw_refine); + break; + case SNDRV_PCM_IOCTL_HW_PARAMS: + ctrl->result = snd_pcm_hw_params(pcm, (snd_pcm_hw_params_t *) &ctrl->u.hw_params); + break; + case SNDRV_PCM_IOCTL_HW_FREE: + ctrl->result = snd_pcm_hw_free(pcm); + break; + case SNDRV_PCM_IOCTL_SW_PARAMS: + ctrl->result = snd_pcm_sw_params(pcm, (snd_pcm_sw_params_t *) &ctrl->u.sw_params); + break; + case SNDRV_PCM_IOCTL_STATUS: + ctrl->result = snd_pcm_status(pcm, (snd_pcm_status_t *) &ctrl->u.status); + break; + case SND_PCM_IOCTL_STATE: + ctrl->result = snd_pcm_state(pcm); + break; + case SND_PCM_IOCTL_HWSYNC: + ctrl->result = snd_pcm_hwsync(pcm); + break; + case SNDRV_PCM_IOCTL_DELAY: + ctrl->result = snd_pcm_delay(pcm, (snd_pcm_sframes_t *) &ctrl->u.delay.frames); + break; + case SND_PCM_IOCTL_AVAIL_UPDATE: + ctrl->result = snd_pcm_avail_update(pcm); + break; + case SNDRV_PCM_IOCTL_PREPARE: + ctrl->result = snd_pcm_prepare(pcm); + break; + case SNDRV_PCM_IOCTL_RESET: + ctrl->result = snd_pcm_reset(pcm); + break; + case SNDRV_PCM_IOCTL_START: + ctrl->result = snd_pcm_start(pcm); + break; + case SNDRV_PCM_IOCTL_DRAIN: + ctrl->result = snd_pcm_drain(pcm); + break; + case SNDRV_PCM_IOCTL_DROP: + ctrl->result = snd_pcm_drop(pcm); + break; + case SNDRV_PCM_IOCTL_PAUSE: + ctrl->result = snd_pcm_pause(pcm, ctrl->u.pause.enable); + break; + case SNDRV_PCM_IOCTL_CHANNEL_INFO: + ctrl->result = snd_pcm_channel_info(pcm, (snd_pcm_channel_info_t *) &ctrl->u.channel_info); + if (ctrl->result >= 0 && + ctrl->u.channel_info.type == SND_PCM_AREA_MMAP) + return shm_ack_fd(client, ctrl->u.channel_info.u.mmap.fd); + break; + case SNDRV_PCM_IOCTL_REWIND: + ctrl->result = snd_pcm_rewind(pcm, ctrl->u.rewind.frames); + break; + case SND_PCM_IOCTL_FORWARD: + ctrl->result = snd_pcm_forward(pcm, ctrl->u.forward.frames); + break; + case SNDRV_PCM_IOCTL_LINK: + { + /* FIXME */ + ctrl->result = -ENOSYS; + break; + } + case SNDRV_PCM_IOCTL_UNLINK: + ctrl->result = snd_pcm_unlink(pcm); + break; + case SNDRV_PCM_IOCTL_RESUME: + ctrl->result = snd_pcm_resume(pcm); + break; + case SND_PCM_IOCTL_MMAP: + { + ctrl->result = snd_pcm_mmap(pcm); + break; + } + case SND_PCM_IOCTL_MUNMAP: + { + ctrl->result = snd_pcm_munmap(pcm); + break; + } + case SND_PCM_IOCTL_MMAP_COMMIT: + ctrl->result = snd_pcm_mmap_commit(pcm, + ctrl->u.mmap_commit.offset, + ctrl->u.mmap_commit.frames); + break; + case SND_PCM_IOCTL_POLL_DESCRIPTOR: + ctrl->result = 0; + return shm_ack_fd(client, _snd_pcm_poll_descriptor(pcm)); + case SND_PCM_IOCTL_CLOSE: + client->ops->close(client); + break; + case SND_PCM_IOCTL_HW_PTR_FD: + return shm_rbptr_fd(client, &pcm->hw); + case SND_PCM_IOCTL_APPL_PTR_FD: + return shm_rbptr_fd(client, &pcm->appl); + default: + ERROR("Bogus cmd: %x", ctrl->cmd); + ctrl->result = -ENOSYS; + } + return shm_ack(client); +} + +transport_ops_t pcm_shm_ops = { + .open = pcm_shm_open, + .cmd = pcm_shm_cmd, + .close = pcm_shm_close, +}; + +static int ctl_handler(waiter_t *waiter, unsigned short events) +{ + client_t *client = waiter->private_data; + char buf[1]; + ssize_t n; + if (events & POLLIN) { + n = write(client->poll_fd, buf, 1); + if (n != 1) { + SYSERROR("write failed"); + return -errno; + } + } + del_waiter(waiter->fd); + client->polling = 0; + return 0; +} + +static int ctl_shm_open(client_t *client, int *cookie) +{ + int shmid; + snd_ctl_t *ctl; + int err; + int result; + err = snd_ctl_open(&ctl, client->name, SND_CTL_NONBLOCK); + if (err < 0) + return err; + client->device.ctl.handle = ctl; + client->device.ctl.fd = _snd_ctl_poll_descriptor(ctl); + + shmid = shmget(IPC_PRIVATE, CTL_SHM_SIZE, 0666); + if (shmid < 0) { + result = -errno; + SYSERROR("shmget failed"); + goto _err; + } + client->transport.shm.ctrl_id = shmid; + client->transport.shm.ctrl = shmat(shmid, 0, 0); + if (!client->transport.shm.ctrl) { + result = -errno; + shmctl(shmid, IPC_RMID, 0); + SYSERROR("shmat failed"); + goto _err; + } + *cookie = shmid; + add_waiter(client->device.ctl.fd, POLLIN, ctl_handler, client); + client->polling = 1; + return 0; + + _err: + snd_ctl_close(ctl); + return result; + +} + +static int ctl_shm_close(client_t *client) +{ + int err; + snd_ctl_shm_ctrl_t *ctrl = client->transport.shm.ctrl; + if (client->polling) { + del_waiter(client->device.ctl.fd); + client->polling = 0; + } + err = snd_ctl_close(client->device.ctl.handle); + ctrl->result = err; + if (err < 0) + ERROR("snd_ctl_close"); + if (client->transport.shm.ctrl) { + err = shmdt((void *)client->transport.shm.ctrl); + if (err < 0) + SYSERROR("shmdt failed"); + err = shmctl(client->transport.shm.ctrl_id, IPC_RMID, 0); + if (err < 0) + SYSERROR("shmctl failed"); + client->transport.shm.ctrl = 0; + } + client->open = 0; + return 0; +} + +static int ctl_shm_cmd(client_t *client) +{ + snd_ctl_shm_ctrl_t *ctrl = client->transport.shm.ctrl; + char buf[1]; + int err; + int cmd; + snd_ctl_t *ctl; + err = read(client->ctrl_fd, buf, 1); + if (err != 1) + return -EBADFD; + cmd = ctrl->cmd; + ctrl->cmd = 0; + ctl = client->device.ctl.handle; + switch (cmd) { + case SND_CTL_IOCTL_ASYNC: + ctrl->result = snd_ctl_async(ctl, ctrl->u.async.sig, ctrl->u.async.pid); + if (ctrl->result < 0) + break; + if (ctrl->u.async.sig >= 0) { + assert(client->async_sig < 0); + ctrl->result = snd_async_add_ctl_handler(&client->async_handler, ctl, async_handler, client); + if (ctrl->result < 0) + break; + } else { + assert(client->async_sig >= 0); + snd_async_del_handler(client->async_handler); + } + client->async_sig = ctrl->u.async.sig; + client->async_pid = ctrl->u.async.pid; + break; + break; + case SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS: + ctrl->result = snd_ctl_subscribe_events(ctl, ctrl->u.subscribe_events); + break; + case SNDRV_CTL_IOCTL_CARD_INFO: + ctrl->result = snd_ctl_card_info(ctl, &ctrl->u.card_info); + break; + case SNDRV_CTL_IOCTL_ELEM_LIST: + { + size_t maxsize = CTL_SHM_DATA_MAXLEN; + if (ctrl->u.element_list.space * sizeof(*ctrl->u.element_list.pids) > maxsize) { + ctrl->result = -EFAULT; + break; + } + ctrl->u.element_list.pids = (snd_ctl_elem_id_t*) ctrl->data; + ctrl->result = snd_ctl_elem_list(ctl, &ctrl->u.element_list); + break; + } + case SNDRV_CTL_IOCTL_ELEM_INFO: + ctrl->result = snd_ctl_elem_info(ctl, &ctrl->u.element_info); + break; + case SNDRV_CTL_IOCTL_ELEM_READ: + ctrl->result = snd_ctl_elem_read(ctl, &ctrl->u.element_read); + break; + case SNDRV_CTL_IOCTL_ELEM_WRITE: + ctrl->result = snd_ctl_elem_write(ctl, &ctrl->u.element_write); + break; + case SNDRV_CTL_IOCTL_ELEM_LOCK: + ctrl->result = snd_ctl_elem_lock(ctl, &ctrl->u.element_lock); + break; + case SNDRV_CTL_IOCTL_ELEM_UNLOCK: + ctrl->result = snd_ctl_elem_unlock(ctl, &ctrl->u.element_unlock); + break; + case SNDRV_CTL_IOCTL_HWDEP_NEXT_DEVICE: + ctrl->result = snd_ctl_hwdep_next_device(ctl, &ctrl->u.device); + break; + case SNDRV_CTL_IOCTL_HWDEP_INFO: + ctrl->result = snd_ctl_hwdep_info(ctl, &ctrl->u.hwdep_info); + break; + case SNDRV_CTL_IOCTL_PCM_NEXT_DEVICE: + ctrl->result = snd_ctl_pcm_next_device(ctl, &ctrl->u.device); + break; + case SNDRV_CTL_IOCTL_PCM_INFO: + ctrl->result = snd_ctl_pcm_info(ctl, &ctrl->u.pcm_info); + break; + case SNDRV_CTL_IOCTL_PCM_PREFER_SUBDEVICE: + ctrl->result = snd_ctl_pcm_prefer_subdevice(ctl, ctrl->u.pcm_prefer_subdevice); + break; + case SNDRV_CTL_IOCTL_RAWMIDI_NEXT_DEVICE: + ctrl->result = snd_ctl_rawmidi_next_device(ctl, &ctrl->u.device); + break; + case SNDRV_CTL_IOCTL_RAWMIDI_INFO: + ctrl->result = snd_ctl_rawmidi_info(ctl, &ctrl->u.rawmidi_info); + break; + case SNDRV_CTL_IOCTL_RAWMIDI_PREFER_SUBDEVICE: + ctrl->result = snd_ctl_rawmidi_prefer_subdevice(ctl, ctrl->u.rawmidi_prefer_subdevice); + break; + case SNDRV_CTL_IOCTL_POWER: + ctrl->result = snd_ctl_set_power_state(ctl, ctrl->u.power_state); + break; + case SNDRV_CTL_IOCTL_POWER_STATE: + ctrl->result = snd_ctl_get_power_state(ctl, &ctrl->u.power_state); + break; + case SND_CTL_IOCTL_READ: + ctrl->result = snd_ctl_read(ctl, &ctrl->u.read); + break; + case SND_CTL_IOCTL_CLOSE: + client->ops->close(client); + break; + case SND_CTL_IOCTL_POLL_DESCRIPTOR: + ctrl->result = 0; + return shm_ack_fd(client, _snd_ctl_poll_descriptor(ctl)); + default: + ERROR("Bogus cmd: %x", ctrl->cmd); + ctrl->result = -ENOSYS; + } + return shm_ack(client); +} + +transport_ops_t ctl_shm_ops = { + .open = ctl_shm_open, + .cmd = ctl_shm_cmd, + .close = ctl_shm_close, +}; + +static int snd_client_open(client_t *client) +{ + int err; + snd_client_open_request_t req; + snd_client_open_answer_t ans; + char *name; + memset(&ans, 0, sizeof(ans)); + err = read(client->ctrl_fd, &req, sizeof(req)); + if (err < 0) { + SYSERROR("read failed"); + exit(1); + } + if (err != sizeof(req)) { + ans.result = -EINVAL; + goto _answer; + } + name = alloca(req.namelen); + err = read(client->ctrl_fd, name, req.namelen); + if (err < 0) { + SYSERROR("read failed"); + exit(1); + } + if (err != req.namelen) { + ans.result = -EINVAL; + goto _answer; + } + + switch (req.transport_type) { + case SND_TRANSPORT_TYPE_SHM: + if (!client->local) { + ans.result = -EINVAL; + goto _answer; + } + switch (req.dev_type) { + case SND_DEV_TYPE_PCM: + client->ops = &pcm_shm_ops; + break; + case SND_DEV_TYPE_CONTROL: + client->ops = &ctl_shm_ops; + break; + default: + ans.result = -EINVAL; + goto _answer; + } + break; + default: + ans.result = -EINVAL; + goto _answer; + } + + name[req.namelen] = '\0'; + + client->transport_type = req.transport_type; + strcpy(client->name, name); + client->stream = req.stream; + client->mode = req.mode; + + err = client->ops->open(client, &ans.cookie); + if (err < 0) { + ans.result = err; + } else { + client->open = 1; + ans.result = 0; + } + + _answer: + err = write(client->ctrl_fd, &ans, sizeof(ans)); + if (err != sizeof(ans)) { + SYSERROR("write failed"); + exit(1); + } + return 0; +} + +static int client_poll_handler(waiter_t *waiter, unsigned short events ATTRIBUTE_UNUSED) +{ + client_t *client = waiter->private_data; + if (client->open) + client->ops->close(client); + close(client->poll_fd); + close(client->ctrl_fd); + del_waiter(client->poll_fd); + del_waiter(client->ctrl_fd); + list_del(&client->list); + free(client); + return 0; +} + +static int client_ctrl_handler(waiter_t *waiter, unsigned short events) +{ + client_t *client = waiter->private_data; + if (events & POLLHUP) { + if (client->open) + client->ops->close(client); + close(client->ctrl_fd); + del_waiter(client->ctrl_fd); + list_del(&client->list); + free(client); + return 0; + } + if (client->open) + return client->ops->cmd(client); + else + return snd_client_open(client); +} + +static int inet_pending_handler(waiter_t *waiter, unsigned short events) +{ + inet_pending_t *pending = waiter->private_data; + inet_pending_t *pdata; + client_t *client; + uint32_t cookie; + struct list_head *item; + int remove = 0; + if (events & POLLHUP) + remove = 1; + else { + int err = read(waiter->fd, &cookie, sizeof(cookie)); + if (err != sizeof(cookie)) + remove = 1; + else { + err = write(waiter->fd, &cookie, sizeof(cookie)); + if (err != sizeof(cookie)) + remove = 1; + } + } + del_waiter(waiter->fd); + if (remove) { + close(waiter->fd); + list_del(&pending->list); + free(pending); + return 0; + } + + list_for_each(item, &inet_pendings) { + pdata = list_entry(item, inet_pending_t, list); + if (pdata->cookie == cookie) + goto found; + } + pending->cookie = cookie; + return 0; + + found: + client = calloc(1, sizeof(*client)); + client->local = 0; + client->poll_fd = pdata->fd; + client->ctrl_fd = waiter->fd; + add_waiter(client->ctrl_fd, POLLIN | POLLHUP, client_ctrl_handler, client); + add_waiter(client->poll_fd, POLLHUP, client_poll_handler, client); + client->open = 0; + list_add_tail(&client->list, &clients); + list_del(&pending->list); + list_del(&pdata->list); + free(pending); + free(pdata); + return 0; +} + +static int local_handler(waiter_t *waiter, unsigned short events ATTRIBUTE_UNUSED) +{ + int sock; + sock = accept(waiter->fd, 0, 0); + if (sock < 0) { + int result = -errno; + SYSERROR("accept failed"); + return result; + } else { + client_t *client = calloc(1, sizeof(*client)); + client->ctrl_fd = sock; + client->local = 1; + client->open = 0; + add_waiter(sock, POLLIN | POLLHUP, client_ctrl_handler, client); + list_add_tail(&client->list, &clients); + } + return 0; +} + +static int inet_handler(waiter_t *waiter, unsigned short events ATTRIBUTE_UNUSED) +{ + int sock; + sock = accept(waiter->fd, 0, 0); + if (sock < 0) { + int result = -errno; + SYSERROR("accept failed"); + return result; + } else { + inet_pending_t *pending = calloc(1, sizeof(*pending)); + pending->fd = sock; + pending->cookie = 0; + add_waiter(sock, POLLIN, inet_pending_handler, pending); + list_add_tail(&pending->list, &inet_pendings); + } + return 0; +} + +static int server(const char *sockname, int port) +{ + int err, result, sockn = -1, socki = -1; + unsigned int k; + long open_max; + + if (!sockname && port < 0) + return -EINVAL; + open_max = sysconf(_SC_OPEN_MAX); + if (open_max < 0) { + result = -errno; + SYSERROR("sysconf failed"); + return result; + } + pollfds = calloc((size_t) open_max, sizeof(*pollfds)); + waiters = calloc((size_t) open_max, sizeof(*waiters)); + + if (sockname) { + sockn = make_local_socket(sockname); + if (sockn < 0) + return sockn; + if (fcntl(sockn, F_SETFL, O_NONBLOCK) < 0) { + result = -errno; + SYSERROR("fcntl O_NONBLOCK failed"); + goto _end; + } + if (listen(sockn, 4) < 0) { + result = -errno; + SYSERROR("listen failed"); + goto _end; + } + add_waiter(sockn, POLLIN, local_handler, NULL); + } + if (port >= 0) { + socki = make_inet_socket(port); + if (socki < 0) + return socki; + if (fcntl(socki, F_SETFL, O_NONBLOCK) < 0) { + result = -errno; + SYSERROR("fcntl failed"); + goto _end; + } + if (listen(socki, 4) < 0) { + result = -errno; + SYSERROR("listen failed"); + goto _end; + } + add_waiter(socki, POLLIN, inet_handler, NULL); + } + + while (1) { + struct pollfd pfds[open_max]; + size_t pfds_count; + do { + err = poll(pollfds, pollfds_count, -1); + } while (err == 0); + if (err < 0) { + SYSERROR("poll failed"); + continue; + } + + pfds_count = pollfds_count; + memcpy(pfds, pollfds, sizeof(*pfds) * pfds_count); + for (k = 0; k < pfds_count; k++) { + struct pollfd *pfd = &pfds[k]; + if (pfd->revents) { + waiter_t *w = &waiters[pfd->fd]; + if (!w->handler) + continue; + err = w->handler(w, pfd->revents); + if (err < 0) + ERROR("waiter handler failed"); + } + } + } + _end: + if (sockn >= 0) + close(sockn); + if (socki >= 0) + close(socki); + free(pollfds); + free(waiters); + return result; +} + + +static void usage(void) +{ + fprintf(stderr, + "Usage: %s [OPTIONS] server\n" + "--help help\n", + command); +} + +int main(int argc, char **argv) +{ + static const struct option long_options[] = { + {"help", 0, 0, 'h'}, + { 0 , 0 , 0, 0 } + }; + int c; + snd_config_t *conf; + snd_config_iterator_t i, next; + const char *sockname = NULL; + long port = -1; + int err; + char *srvname; + + command = argv[0]; + while ((c = getopt_long(argc, argv, "h", long_options, 0)) != -1) { + switch (c) { + case 'h': + usage(); + return 0; + default: + fprintf(stderr, "Try `%s --help' for more information\n", command); + return 1; + } + } + if (argc - optind != 1) { + ERROR("you need to specify server name"); + return 1; + } + err = snd_config_update(); + if (err < 0) { + ERROR("cannot read configuration file"); + return 1; + } + srvname = argv[optind]; + err = snd_config_search_definition(snd_config, "server", srvname, &conf); + if (err < 0) { + ERROR("Missing definition for server %s", srvname); + return 1; + } + if (snd_config_get_type(conf) != SND_CONFIG_TYPE_COMPOUND) { + SNDERR("Invalid type for server %s definition", srvname); + return -EINVAL; + } + snd_config_for_each(i, next, conf) { + snd_config_t *n = snd_config_iterator_entry(i); + const char *id; + if (snd_config_get_id(n, &id) < 0) + continue; + if (strcmp(id, "comment") == 0) + continue; + if (strcmp(id, "host") == 0) + continue; + if (strcmp(id, "socket") == 0) { + err = snd_config_get_string(n, &sockname); + if (err < 0) { + ERROR("Invalid type for %s", id); + return 1; + } + continue; + } + if (strcmp(id, "port") == 0) { + err = snd_config_get_integer(n, &port); + if (err < 0) { + ERROR("Invalid type for %s", id); + return 1; + } + continue; + } + ERROR("Unknown field %s", id); + return 1; + } + if (!sockname && port < 0) { + ERROR("either socket or port need to be defined"); + return 1; + } + server(sockname, port); + return 0; +}