kmscon/src/eloop.c
David Herrmann 39b3cc1237 Eloop: Fix function name typo
Signed-off-by: David Herrmann <dh.herrmann@googlemail.com>
2011-12-03 16:38:14 +01:00

586 lines
10 KiB
C

/*
* kmscon - Event Loop
*
* Copyright (c) 2011 David Herrmann <dh.herrmann@googlemail.com>
* Copyright (c) 2011 University of Tuebingen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* Event Loop
* This provides a basic event loop similar to those provided by glib etc.
* It uses linux specific features like signalfd so it may not be easy to port
* it to other platforms.
*/
#include <errno.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/signalfd.h>
#include <unistd.h>
#include "eloop.h"
#include "log.h"
struct kmscon_eloop {
int efd;
unsigned long ref;
struct kmscon_idle *idle_list;
struct kmscon_idle *cur_idle;
struct epoll_event *cur_fds;
size_t cur_fds_cnt;
};
struct kmscon_idle {
unsigned long ref;
struct kmscon_eloop *loop;
struct kmscon_idle *next;
struct kmscon_idle *prev;
kmscon_idle_cb cb;
void *data;
};
struct kmscon_fd {
unsigned long ref;
struct kmscon_eloop *loop;
kmscon_fd_cb cb;
void *data;
int fd;
};
struct kmscon_signal {
unsigned long ref;
struct kmscon_fd *fd;
kmscon_signal_cb cb;
void *data;
};
int kmscon_idle_new(struct kmscon_idle **out)
{
struct kmscon_idle *idle;
if (!out)
return -EINVAL;
idle = malloc(sizeof(*idle));
if (!idle)
return -ENOMEM;
memset(idle, 0, sizeof(*idle));
idle->ref = 1;
*out = idle;
return 0;
}
void kmscon_idle_ref(struct kmscon_idle *idle)
{
if (!idle)
return;
++idle->ref;
}
void kmscon_idle_unref(struct kmscon_idle *idle)
{
if (!idle || !idle->ref)
return;
if (--idle->ref)
return;
free(idle);
}
int kmscon_eloop_new_idle(struct kmscon_eloop *loop, struct kmscon_idle **out,
kmscon_idle_cb cb, void *data)
{
struct kmscon_idle *idle;
int ret;
if (!out)
return -EINVAL;
ret = kmscon_idle_new(&idle);
if (ret)
return ret;
ret = kmscon_eloop_add_idle(loop, idle, cb, data);
if (ret) {
kmscon_idle_unref(idle);
return ret;
}
kmscon_idle_unref(idle);
*out = idle;
return 0;
}
int kmscon_eloop_add_idle(struct kmscon_eloop *loop, struct kmscon_idle *idle,
kmscon_idle_cb cb, void *data)
{
if (!loop || !idle || !cb)
return -EINVAL;
if (idle->next || idle->prev || idle->loop)
return -EALREADY;
idle->next = loop->idle_list;
if (idle->next)
idle->next->prev = idle;
loop->idle_list = idle;
idle->loop = loop;
idle->cb = cb;
idle->data = data;
kmscon_idle_ref(idle);
kmscon_eloop_ref(loop);
return 0;
}
void kmscon_eloop_rm_idle(struct kmscon_idle *idle)
{
struct kmscon_eloop *loop;
if (!idle || !idle->loop)
return;
loop = idle->loop;
/*
* If the loop is currently dispatching, we need to check whether we are
* the current element and correctly set it to the next element.
*/
if (loop->cur_idle == idle)
loop->cur_idle = idle->next;
if (idle->prev)
idle->prev->next = idle->next;
if (idle->next)
idle->next->prev = idle->prev;
if (loop->idle_list == idle)
loop->idle_list = idle->next;
idle->next = NULL;
idle->prev = NULL;
idle->loop = NULL;
idle->cb = NULL;
idle->data = NULL;
kmscon_idle_unref(idle);
kmscon_eloop_unref(loop);
}
int kmscon_fd_new(struct kmscon_fd **out)
{
struct kmscon_fd *fd;
if (!out)
return -EINVAL;
fd = malloc(sizeof(*fd));
if (!fd)
return -ENOMEM;
memset(fd, 0, sizeof(*fd));
fd->ref = 1;
fd->fd = -1;
*out = fd;
return 0;
}
void kmscon_fd_ref(struct kmscon_fd *fd)
{
if (!fd)
return;
++fd->ref;
}
void kmscon_fd_unref(struct kmscon_fd *fd)
{
if (!fd || !fd->ref)
return;
if (--fd->ref)
return;
free(fd);
}
int kmscon_eloop_new_fd(struct kmscon_eloop *loop, struct kmscon_fd **out,
int rfd, int mask, kmscon_fd_cb cb, void *data)
{
struct kmscon_fd *fd;
int ret;
if (!out)
return -EINVAL;
ret = kmscon_fd_new(&fd);
if (ret)
return ret;
ret = kmscon_eloop_add_fd(loop, fd, rfd, mask, cb, data);
if (ret) {
kmscon_fd_unref(fd);
return ret;
}
kmscon_fd_unref(fd);
*out = fd;
return 0;
}
int kmscon_eloop_add_fd(struct kmscon_eloop *loop, struct kmscon_fd *fd,
int rfd, int mask, kmscon_fd_cb cb, void *data)
{
struct epoll_event ep;
if (!loop || !fd || !cb)
return -EINVAL;
if (fd->loop)
return -EALREADY;
memset(&ep, 0, sizeof(ep));
if (mask & KMSCON_READABLE)
ep.events |= EPOLLIN;
if (mask & KMSCON_WRITEABLE)
ep.events |= EPOLLOUT;
ep.data.ptr = fd;
if (epoll_ctl(loop->efd, EPOLL_CTL_ADD, rfd, &ep) < 0)
return -errno;
fd->loop = loop;
fd->cb = cb;
fd->data = data;
fd->fd = rfd;
kmscon_fd_ref(fd);
kmscon_eloop_ref(loop);
return 0;
}
void kmscon_eloop_rm_fd(struct kmscon_fd *fd)
{
struct kmscon_eloop *loop;
int i;
if (!fd || !fd->loop)
return;
loop = fd->loop;
epoll_ctl(loop->efd, EPOLL_CTL_DEL, fd->fd, NULL);
/*
* If we are currently dispatching events, we need to remove ourself
* from the temporary event list.
*/
for (i = 0; i < loop->cur_fds_cnt; ++i) {
if (fd == loop->cur_fds[i].data.ptr)
loop->cur_fds[i].data.ptr = NULL;
}
fd->loop = NULL;
fd->cb = NULL;
fd->data = NULL;
fd->fd = -1;
kmscon_fd_unref(fd);
kmscon_eloop_unref(loop);
}
int kmscon_eloop_update_fd(struct kmscon_fd *fd, int mask)
{
struct epoll_event ep;
if (!fd || !fd->loop)
return -EINVAL;
memset(&ep, 0, sizeof(ep));
if (mask & KMSCON_READABLE)
ep.events |= EPOLLIN;
if (mask & KMSCON_WRITEABLE)
ep.events |= EPOLLOUT;
ep.data.ptr = fd;
if (epoll_ctl(fd->loop->efd, EPOLL_CTL_MOD, fd->fd, &ep))
return -errno;
return 0;
}
int kmscon_signal_new(struct kmscon_signal **out)
{
struct kmscon_signal *sig;
int ret;
if (!out)
return -EINVAL;
sig = malloc(sizeof(*sig));
if (!sig)
return -ENOMEM;
memset(sig, 0, sizeof(*sig));
sig->ref = 1;
ret = kmscon_fd_new(&sig->fd);
if (ret) {
free(sig);
return ret;
}
*out = sig;
return 0;
}
void kmscon_signal_ref(struct kmscon_signal *sig)
{
if (!sig)
return;
++sig->ref;
}
void kmscon_signal_unref(struct kmscon_signal *sig)
{
if (!sig || !sig->ref)
return;
if (--sig->ref)
return;
kmscon_fd_unref(sig->fd);
free(sig);
}
int kmscon_eloop_new_signal(struct kmscon_eloop *loop,
struct kmscon_signal **out, int signum, kmscon_signal_cb cb,
void *data)
{
struct kmscon_signal *sig;
int ret;
if (!out)
return -EINVAL;
ret = kmscon_signal_new(&sig);
if (ret)
return ret;
ret = kmscon_eloop_add_signal(loop, sig, signum, cb, data);
if (ret) {
kmscon_signal_unref(sig);
return ret;
}
kmscon_signal_unref(sig);
*out = sig;
return 0;
}
static void signal_cb(struct kmscon_fd *fd, int mask, void *data)
{
struct kmscon_signal *sig = data;
struct signalfd_siginfo signal_info;
int len;
if (mask & KMSCON_READABLE) {
len = read(fd->fd, &signal_info, sizeof(signal_info));
if (len != sizeof(signal_info))
log_warning("eloop: cannot read signalfd\n");
else
sig->cb(sig, signal_info.ssi_signo, sig->data);
}
}
int kmscon_eloop_add_signal(struct kmscon_eloop *loop,
struct kmscon_signal *sig, int signum, kmscon_signal_cb cb, void *data)
{
sigset_t mask;
int ret, fd;
if (!loop || !sig)
return -EINVAL;
if (sig->fd->loop)
return -EALREADY;
sigemptyset(&mask);
sigaddset(&mask, signum);
fd = signalfd(-1, &mask, SFD_CLOEXEC);
if (fd < 0)
return -errno;
ret = kmscon_eloop_add_fd(loop, sig->fd, fd, KMSCON_READABLE,
signal_cb, sig);
if (ret) {
close(fd);
return ret;
}
sigprocmask(SIG_BLOCK, &mask, NULL);
sig->cb = cb;
sig->data = data;
kmscon_signal_ref(sig);
return 0;
}
void kmscon_eloop_rm_signal(struct kmscon_signal *sig)
{
int fd;
if (!sig || !sig->fd->loop)
return;
fd = sig->fd->fd;
kmscon_eloop_rm_fd(sig->fd);
close(fd);
kmscon_signal_unref(sig);
/*
* We cannot unblock the signal here because we do not know whether some
* other subsystem also added the signal to the sigprocmask.
*/
}
int kmscon_eloop_new(struct kmscon_eloop **out)
{
struct kmscon_eloop *loop;
if (!out)
return -EINVAL;
loop = malloc(sizeof(*loop));
if (!loop)
return -ENOMEM;
memset(loop, 0, sizeof(*loop));
loop->ref = 1;
loop->efd = epoll_create1(EPOLL_CLOEXEC);
if (loop->efd < 0) {
free(loop);
return -errno;
}
log_debug("eloop: create eloop object\n");
*out = loop;
return 0;
}
void kmscon_eloop_ref(struct kmscon_eloop *loop)
{
if (!loop)
return;
++loop->ref;
}
void kmscon_eloop_unref(struct kmscon_eloop *loop)
{
if (!loop || !loop->ref)
return;
if (--loop->ref)
return;
log_debug("eloop: destroy eloop object\n");
close(loop->efd);
free(loop);
}
int kmscon_eloop_dispatch(struct kmscon_eloop *loop, int timeout)
{
struct epoll_event ep[32];
struct kmscon_fd *fd;
int i, count, mask;
if (!loop)
return -EINVAL;
/* dispatch idle events */
loop->cur_idle = loop->idle_list;
while (loop->cur_idle) {
loop->cur_idle->cb(loop->cur_idle, loop->cur_idle->data);
if (loop->cur_idle)
loop->cur_idle = loop->cur_idle->next;
}
/* dispatch fd events */
count = epoll_wait(loop->efd, ep, 32, timeout);
if (count < 0) {
log_warning("eloop: epoll_wait dispatching failed\n");
return -errno;
}
log_debug("eloop: dispatch %d fd event(s)\n", count);
loop->cur_fds = ep;
loop->cur_fds_cnt = count;
for (i = 0; i < count; ++i) {
fd = ep[i].data.ptr;
if (!fd || !fd->cb)
continue;
mask = 0;
if (ep[i].events & EPOLLIN)
mask |= KMSCON_READABLE;
if (ep[i].events & EPOLLOUT)
mask |= KMSCON_WRITEABLE;
if (ep[i].events & EPOLLHUP)
mask |= KMSCON_HUP;
fd->cb(fd, mask, fd->data);
}
loop->cur_fds = NULL;
loop->cur_fds_cnt = 0;
return 0;
}
int kmscon_eloop_get_fd(struct kmscon_eloop *loop)
{
if (!loop)
return -EINVAL;
return loop->efd;
}