Add event loop
authorDavid Herrmann <dh.herrmann@googlemail.com>
Sat, 3 Dec 2011 10:42:41 +0000 (11:42 +0100)
committerDavid Herrmann <dh.herrmann@googlemail.com>
Sat, 3 Dec 2011 10:42:41 +0000 (11:42 +0100)
Add event loop implementation with support for fd's and signal's through
signalfd.

Signed-off-by: David Herrmann <dh.herrmann@googlemail.com>
Makefile.am
src/eloop.c [new file with mode: 0644]
src/eloop.h [new file with mode: 0644]

index b071429..be9c664 100644 (file)
@@ -18,7 +18,8 @@ libkmscon_core_la_SOURCES = \
        src/console.c src/console.h \
        src/output.c src/output.h \
        src/console_char.c \
-       src/log.c src/log.h
+       src/log.c src/log.h \
+       src/eloop.c src/eloop.h
 
 libkmscon_core_la_CFLAGS = \
        $(AM_CFLAGS) \
diff --git a/src/eloop.c b/src/eloop.c
new file mode 100644 (file)
index 0000000..be3b0c7
--- /dev/null
@@ -0,0 +1,439 @@
+/*
+ * 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 epoll_event *cur_fds;
+       size_t cur_fds_cnt;
+};
+
+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_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, void *data)
+{
+       struct kmscon_signal *sig = data;
+       struct signalfd_siginfo signal_info;
+       int len;
+
+       len = read(fd->fd, &signal_info, sizeof(signal_info));
+       if (len != sizeof(signal_info)) {
+               log_warning("eloop: cannot read signalfd\n");
+               return;
+       }
+
+       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;
+       }
+
+       *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;
+
+       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;
+
+       count = epoll_wait(loop->efd, ep, 32, timeout);
+       if (count < 0)
+               return -errno;
+
+       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, 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;
+}
diff --git a/src/eloop.h b/src/eloop.h
new file mode 100644 (file)
index 0000000..5372a13
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ * 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.
+ */
+
+#ifndef KMSCON_ELOOP_H
+#define KMSCON_ELOOP_H
+
+#include <stdlib.h>
+
+struct kmscon_eloop;
+struct kmscon_fd;
+struct kmscon_signal;
+
+typedef void (*kmscon_fd_cb) (struct kmscon_fd *fd, void *data);
+typedef void (*kmscon_signal_cb)
+                       (struct kmscon_signal *sig, int signum, void *data);
+
+enum kmscon_eloop_flags {
+       KMSCON_READABLE = 0x01,
+       KMSCON_WRITEABLE = 0x02,
+       KMSCON_HUP = 0x04,
+};
+
+int kmscon_eloop_new(struct kmscon_eloop **out);
+void kmscon_eloop_ref(struct kmscon_eloop *loop);
+void kmscon_eloop_unref(struct kmscon_eloop *loop);
+
+int kmscon_eloop_get_fd(struct kmscon_eloop *loop);
+int kmscon_eloop_dispatch(struct kmscon_eloop *loop, int timeout);
+
+/* fd sources */
+
+int kmscon_fd_new(struct kmscon_fd **out);
+void kmscon_fd_ref(struct kmscon_fd *fd);
+void kmscon_fd_unref(struct kmscon_fd *fd);
+
+int kmscon_eloop_new_fd(struct kmscon_eloop *loop, struct kmscon_fd **out,
+                               int rfd, int mask, kmscon_fd_cb cb, void *data);
+int kmscon_eloop_add_fd(struct kmscon_eloop *loop, struct kmscon_fd *fd,
+                               int rfd, int mask, kmscon_fd_cb cb, void *data);
+void kmscon_eloop_rm_fd(struct kmscon_fd *fd);
+int kmscon_eloop_update_fd(struct kmscon_fd *fd, int mask);
+
+/* signal sources */
+
+int kmscon_signal_new(struct kmscon_signal **out);
+void kmscon_signal_ref(struct kmscon_signal *sig);
+void kmscon_signal_unref(struct kmscon_signal *sig);
+
+int kmscon_eloop_new_signal(struct kmscon_eloop *loop,
+       struct kmscon_signal **out, int signum, kmscon_signal_cb cb,
+                                                               void *data);
+int kmscon_eloop_add_signal(struct kmscon_eloop *loop,
+       struct kmscon_signal *sig, int signum, kmscon_signal_cb cb, void *data);
+void kmscon_eloop_rm_signal(struct kmscon_signal *sig);
+
+#endif /* KMSCON_ELOOP_H */