/* * 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" #undef open 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; }