kmscon: add module support
authorDavid Herrmann <dh.herrmann@googlemail.com>
Thu, 3 Jan 2013 15:21:18 +0000 (16:21 +0100)
committerDavid Herrmann <dh.herrmann@googlemail.com>
Thu, 3 Jan 2013 15:21:18 +0000 (16:21 +0100)
Several parts of kmscon have huge external dependencies. However, we allow
them to be disabled during build-time. But this is not enough as it
requires distributions to choose which options to use. Therefore, we now
allow dynamicly loadable modules that can optionally be installed and
kmscon automatically picks them up.

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

index 5be1549..bd2faf6 100644 (file)
@@ -29,6 +29,9 @@ noinst_PROGRAMS = \
 noinst_LTLIBRARIES =
 lib_LTLIBRARIES =
 
+moduledir = @libdir@/kmscon
+module_LTLIBRARIES =
+
 #
 # Default CFlags
 # Make all files include "config.h" by default. This shouldn't cause any
@@ -44,6 +47,7 @@ lib_LTLIBRARIES =
 AM_CFLAGS = \
        -Wall
 AM_CPPFLAGS = \
+       -DBUILD_MODULE_DIR='"$(moduledir)"' \
        -include $(top_builddir)/config.h \
        -I $(srcdir)/src
 AM_LDFLAGS = \
@@ -431,6 +435,9 @@ kmscon_SOURCES = \
        src/pty.c \
        src/text.h \
        src/text.c \
+       src/kmscon_module_interface.h \
+       src/kmscon_module.h \
+       src/kmscon_module.c \
        src/kmscon_terminal.h \
        src/kmscon_dummy.h \
        src/kmscon_cdev.h \
@@ -450,6 +457,8 @@ kmscon_LDADD = \
        libuterm.la \
        $(TEXT_FONT_LIBS) \
        -lpthread
+kmscon_LDFLAGS = \
+       -rdynamic
 
 if BUILD_ENABLE_SESSION_DUMMY
 kmscon_SOURCES += src/kmscon_dummy.c
index 973c603..a293b34 100644 (file)
@@ -34,6 +34,7 @@
 #include "conf.h"
 #include "eloop.h"
 #include "kmscon_conf.h"
+#include "kmscon_module.h"
 #include "kmscon_seat.h"
 #include "log.h"
 #include "shl_dlist.h"
@@ -604,6 +605,7 @@ int main(int argc, char **argv)
                return 0;
        }
 
+       kmscon_load_modules();
        kmscon_font_register(&kmscon_font_8x16_ops);
        kmscon_text_load_all();
 
@@ -647,6 +649,7 @@ int main(int argc, char **argv)
 err_unload:
        kmscon_text_unload_all();
        kmscon_font_unregister(kmscon_font_8x16_ops.name);
+       kmscon_unload_modules();
 err_conf:
        kmscon_conf_free(conf_ctx);
 err_out:
diff --git a/src/kmscon_module.c b/src/kmscon_module.c
new file mode 100644 (file)
index 0000000..87a0d1e
--- /dev/null
@@ -0,0 +1,275 @@
+/*
+ * kmscon - Module handling
+ *
+ * Copyright (c) 2012-2013 David Herrmann <dh.herrmann@googlemail.com>
+ *
+ * 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.
+ */
+
+#include <dirent.h>
+#include <dlfcn.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include "githead.h"
+#include "kmscon_module.h"
+#include "kmscon_module_interface.h"
+#include "log.h"
+#include "shl_dlist.h"
+#include "shl_misc.h"
+
+#define LOG_SUBSYSTEM "module"
+
+static struct shl_dlist module_list = SHL_DLIST_INIT(module_list);
+
+int kmscon_module_open(struct kmscon_module **out, const char *file)
+{
+       int ret;
+       struct kmscon_module *module;
+       void *handle;
+
+       if (!out || !file)
+               return -EINVAL;
+
+       log_debug("opening module %s", file);
+
+       handle = dlopen(file, RTLD_NOW);
+       if (!handle) {
+               log_error("cannot open module %s (%d): %s",
+                         file, errno, dlerror());
+               return -EFAULT;
+       }
+
+       module = dlsym(handle, "module");
+       if (!module) {
+               log_error("cannot find module-info for %s", file);
+               ret = -EFAULT;
+               goto err_unload;
+       }
+
+       if (strcmp(module->info.githead, BUILD_GIT_HEAD)) {
+               log_error("incompatible module %s (%s != %s)",
+                         file, module->info.githead, BUILD_GIT_HEAD);
+               ret = -EFAULT;
+               goto err_unload;
+       }
+
+       if (module->ref != 0) {
+               log_error("module %s already loaded (%ld)",
+                         file, module->ref);
+               ret = -EFAULT;
+               goto err_unload;
+       }
+
+       log_debug("Initializing module: %s", file);
+
+       module->ref = 1;
+       module->loaded = false;
+       module->handle = handle;
+
+       module->file = strdup(file);
+       if (!module->file) {
+               ret = -ENOMEM;
+               goto err_unload;
+       }
+
+       log_debug("  Date: %s %s", module->info.date, module->info.time);
+       log_debug("  GIT: %s", module->info.githead);
+       log_debug("  Hooks: %p %p %p %p",
+                 module->info.init,
+                 module->info.load,
+                 module->info.unload,
+                 module->info.exit);
+
+       if (module->info.init) {
+               ret = module->info.init();
+               if (ret) {
+                       log_error("loading module %s failed: %d",
+                                 module->file, ret);
+                       goto err_file;
+               }
+       }
+
+       *out = module;
+       return 0;
+
+err_file:
+       free(module->file);
+err_unload:
+       dlclose(handle);
+       return ret;
+}
+
+void kmscon_module_ref(struct kmscon_module *module)
+{
+       if (!module || !module->ref)
+               return;
+
+       ++module->ref;
+}
+
+void kmscon_module_unref(struct kmscon_module *module)
+{
+       if (!module || !module->ref || --module->ref)
+               return;
+
+       log_debug("closing module %s", module->file);
+
+       if (module->info.exit)
+               module->info.exit();
+
+       free(module->file);
+       dlclose(module->handle);
+}
+
+int kmscon_module_load(struct kmscon_module *module)
+{
+       int ret;
+
+       if (!module)
+               return -EINVAL;
+
+       if (module->loaded)
+               return -EALREADY;
+
+       log_debug("loading module %s", module->file);
+
+       if (module->info.load)
+               ret = module->info.load();
+       else
+               ret = 0;
+
+       if (ret)
+               return ret;
+
+       module->loaded = true;
+       return 0;
+}
+
+void kmscon_module_unload(struct kmscon_module *module)
+{
+       if (!module || !module->loaded)
+               return;
+
+       log_debug("unloading module %s", module->file);
+
+       if (module->info.unload)
+               module->info.unload();
+       module->loaded = false;
+}
+
+void kmscon_load_modules(void)
+{
+       int ret;
+       DIR *ent;
+       struct dirent *buf, *de;
+       char *file;
+       struct kmscon_module *mod;
+
+       log_debug("loading global modules from %s", BUILD_MODULE_DIR);
+
+       if (!shl_dlist_empty(&module_list)) {
+               log_error("trying to load global modules twice");
+               return;
+       }
+
+       ent = opendir(BUILD_MODULE_DIR);
+       if (!ent) {
+               if (errno == ENOTDIR || errno == ENOENT)
+                       log_debug("module directory %s not available",
+                                 BUILD_MODULE_DIR);
+               else
+                       log_error("cannot open module directory %s (%d): %m",
+                                 BUILD_MODULE_DIR, errno);
+               return;
+       }
+
+       ret = shl_dirent(BUILD_MODULE_DIR, &buf);
+       if (ret) {
+               log_error("cannot allocate dirent object");
+               closedir(ent);
+               return;
+       }
+
+       while (true) {
+               ret = readdir_r(ent, buf, &de);
+               if (ret != 0) {
+                       log_error("cannot read directory %s: %d",
+                                 BUILD_MODULE_DIR, ret);
+                       break;
+               } else if (!de) {
+                       break;
+               }
+
+               if (de->d_type == DT_DIR)
+                       continue;
+
+               if (de->d_type != DT_REG &&
+                   de->d_type != DT_LNK &&
+                   de->d_type != DT_UNKNOWN) {
+                       log_warning("non-module file %s in module dir %s",
+                                   de->d_name, BUILD_MODULE_DIR);
+                       continue;
+               }
+
+               if (!shl_ends_with(de->d_name, ".so"))
+                       continue;
+
+               ret = asprintf(&file, "%s/%s", BUILD_MODULE_DIR, de->d_name);
+               if (ret < 0) {
+                       log_error("cannot allocate memory for module file name");
+                       continue;
+               }
+
+               ret = kmscon_module_open(&mod, file);
+               free(file);
+
+               if (ret)
+                       continue;
+
+               ret = kmscon_module_load(mod);
+               if (ret) {
+                       kmscon_module_unref(mod);
+                       continue;
+               }
+
+               shl_dlist_link(&module_list, &mod->list);
+       }
+
+       free(buf);
+       closedir(ent);
+}
+
+void kmscon_unload_modules(void)
+{
+       struct kmscon_module *module;
+
+       log_debug("unloading modules");
+
+       while (!shl_dlist_empty(&module_list)) {
+               module = shl_dlist_entry(module_list.prev, struct kmscon_module,
+                                        list);
+               shl_dlist_unlink(&module->list);
+               kmscon_module_unload(module);
+               kmscon_module_unref(module);
+       }
+}
diff --git a/src/kmscon_module.h b/src/kmscon_module.h
new file mode 100644 (file)
index 0000000..213c538
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * kmscon - Module handling
+ *
+ * Copyright (c) 2012-2013 David Herrmann <dh.herrmann@googlemail.com>
+ *
+ * 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.
+ */
+
+/*
+ * Module Handling
+ * Several subsystems of kmscon provide a generic interface that is implemented
+ * by different backends. The user can choose a backend that is then used.
+ * To make out-of-tree development easier and, more importantly, to reduce the
+ * direct dependencies to external libraries, this subsystem implements a
+ * dynamically-loadable module system.
+ *
+ * Modules can be loaded and unloaded during runtime. A module basically
+ * provides memory-storage for code. As long as any code of a module is still
+ * used (that is, registered as callback) we must not unload the module.
+ * Therefore, we use reference-counting to allow other subsystems to acquire and
+ * release code sections.
+ *
+ * A module needs to provide "module_init". Everything else is optional.
+ * "module_init" is called after the module has been loaded and should
+ * initialize the module. "module_exit" is called after the module has been
+ * unloaded and the last reference to the module has been dropped. Therefore, it
+ * is safe to release all allocated resources in "module_exit".
+ *
+ * "module_load" is called after "module_init". A module should register its
+ * resources here. "module_unload" is called when the module is scheduled for
+ * removal. A module should unregister its resources here. However, it must not
+ * release the resources as there might still be users of it. Only when
+ * "module_exit" is called, kmscon guarantees that there are no more users and
+ * the module can release its resources.
+ */
+
+#ifndef KMSCON_MODULE_H
+#define KMSCON_MODULE_H
+
+#include <stdlib.h>
+
+struct kmscon_module;
+
+int kmscon_module_open(struct kmscon_module **out, const char *file);
+void kmscon_module_ref(struct kmscon_module *module);
+void kmscon_module_unref(struct kmscon_module *module);
+
+int kmscon_module_load(struct kmscon_module *module);
+void kmscon_module_unload(struct kmscon_module *module);
+
+void kmscon_load_modules(void);
+void kmscon_unload_modules(void);
+
+#endif /* KMSCON_MODULE_H */
diff --git a/src/kmscon_module_interface.h b/src/kmscon_module_interface.h
new file mode 100644 (file)
index 0000000..2941967
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * kmscon - Module Interface
+ *
+ * Copyright (c) 2012-2013 David Herrmann <dh.herrmann@googlemail.com>
+ *
+ * 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.
+ */
+
+/*
+ * Public Module Interface
+ */
+
+#ifndef KMSCON_MODULE_INTERFACE_H
+#define KMSCON_MODULE_INTERFACE_H
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include "githead.h"
+#include "kmscon_module.h"
+#include "shl_dlist.h"
+
+struct kmscon_module_info {
+       const char *githead;
+       const char *date;
+       const char *time;
+       int (*init) (void);
+       int (*load) (void);
+       void (*unload) (void);
+       void (*exit) (void);
+};
+
+struct kmscon_module {
+       struct kmscon_module_info info;
+       struct shl_dlist list;
+       unsigned long ref;
+       bool loaded;
+       void *handle;
+       char *file;
+};
+
+#define KMSCON_MODULE(_init, _load, _unload, _exit) \
+       struct kmscon_module module = { \
+               .info = { \
+                       .githead = BUILD_GIT_HEAD, \
+                       .date = __DATE__, \
+                       .time = __TIME__, \
+                       .init = _init, \
+                       .load = _load, \
+                       .unload = _unload, \
+                       .exit = _exit, \
+               }, \
+       };
+
+extern struct kmscon_module module;
+#define KMSCON_THIS_MODULE (&module)
+
+#endif /* KMSCON_MODULE_INTERFACE_H */