From ba45aad1a2b93f05565e0e0fef4efbde3b2bd017 Mon Sep 17 00:00:00 2001 From: Linus Svensson Date: Mon, 29 Aug 2016 12:34:24 +0200 Subject: [PATCH] module: Support for loading of modules A module is a shared library existing in, or a subdirectory of, the module directory. The directory is by default ${libdir}/pinos-$(MAJORMINOR)/modules, but can be specified with --with-module-dir=PATH when running configure. It is also possible to override the module directory runtime with the environment variable PINOS_MODULE_DIR, which is a list of directories separated with ":". --- configure.ac | 14 ++ pinos/Makefile.am | 13 +- pinos/daemon/main.c | 2 + pinos/server/module.c | 293 +++++++++++++++++++++++++++++++++++++++ pinos/server/module.h | 100 +++++++++++++ pkgconfig/libpinos.pc.in | 2 +- 6 files changed, 418 insertions(+), 6 deletions(-) create mode 100644 pinos/server/module.c create mode 100644 pinos/server/module.h diff --git a/configure.ac b/configure.ac index b79a93065..8df48cbc5 100644 --- a/configure.ac +++ b/configure.ac @@ -90,6 +90,14 @@ USE_NLS=no AC_SUBST(USE_NLS) fi +AC_ARG_WITH([module-dir], + AS_HELP_STRING([--with-module-dir=PATH], + [Directory where modules are located (default is ${libdir}/pinos/modules]), + [moduledir=$withval], + [moduledir="${libdir}/pinos-${PINOS_MAJORMINOR}/modules"]) +AC_SUBST(moduledir) +AX_DEFINE_DIR(MODULEDIR, moduledir, [Directory where modules are located]) + #### Compiler flags #### AX_APPEND_COMPILE_FLAGS( @@ -193,6 +201,11 @@ PKG_CHECK_MODULES(GIOUNIX, gio-unix-2.0 >= 2.32, dummy=yes, GLIB_CFLAGS="$GLIB_CFLAGS $GIOUNIX_CFLAGS" GLIB_LIBS="$GLIB_LIBS $GIOUNIX_LIBS" +PKG_CHECK_MODULES(GMODULE, gmodule-no-export-2.0 >= 2.32, dummy=yes, + AC_MSG_ERROR(GModule >= 2.32 is required)) +GLIB_CFLAGS="$GLIB_CFLAGS $GMODULE_CFLAGS" +GLIB_LIBS="$GLIB_LIBS $GMODULE_LIBS" + AC_MSG_CHECKING([for glib-mkenums script]) GLIB_MKENUMS=`$PKG_CONFIG --variable=glib_mkenums glib-2.0` AS_IF([test "x$GLIB_MKENUMS" = "x"], @@ -263,6 +276,7 @@ Configuration Version : ${VERSION} Source code location : ${srcdir} Prefix : ${prefix} + Moduledir : ${moduledir} Compiler : ${CC} pinos configured. Type 'make' to build. diff --git a/pinos/Makefile.am b/pinos/Makefile.am index 9727f925d..5edb9d01a 100644 --- a/pinos/Makefile.am +++ b/pinos/Makefile.am @@ -204,6 +204,7 @@ libpinoscore_@PINOS_MAJORMINOR@_la_SOURCES = \ server/client-node.c server/client-node.h \ server/daemon.c server/daemon.h \ server/link.c server/link.h \ + server/module.c server/module.h \ server/node.c server/node.h \ server/port.c server/port.h \ server/node-factory.c server/node-factory.h \ @@ -253,12 +254,14 @@ noinst_HEADERS = gst/gstburstcache.h gst/gstpinossrc.h \ CLEANFILES += daemon/pinos.desktop DISTCLEANFILES = +INSTALL_DIRS = $(moduledir) -install-exec-hook: - rm -f $(DESTDIR)$(modlibexecdir)/*.la +install-directories-hook: + $(MKDIR_P) $(addprefix $(DESTDIR),$(INSTALL_DIRS)) -uninstall-hook: - rm -f $(DESTDIR)$(modlibexecdir)/*.so +INSTALL_EXEC_HOOKS = install-directories-hook + +install-exec-hook: $(INSTALL_EXEC_HOOKS) # Automatically generate linker version script. We use the same one for all public .sos update-map-file: @@ -271,4 +274,4 @@ update-map-file: update-all: update-map-file -.PHONY: update-all update-map-file coverage +.PHONY: update-all update-map-file coverage $(INSTALL_EXEC_HOOKS) diff --git a/pinos/daemon/main.c b/pinos/daemon/main.c index 77ebcf8e3..66c118dd3 100644 --- a/pinos/daemon/main.c +++ b/pinos/daemon/main.c @@ -18,10 +18,12 @@ */ #include +#include #include #include #include +#include #include #include #include diff --git a/pinos/server/module.c b/pinos/server/module.c new file mode 100644 index 000000000..f725a144b --- /dev/null +++ b/pinos/server/module.c @@ -0,0 +1,293 @@ +/* Pinos + * Copyright (C) 2016 Axis Communications + * @author Linus Svensson + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include "pinos/server/module.h" + +#define PINOS_SYMBOL_MODULE_INIT "pinos__module_init" + +#define PINOS_MODULE_GET_PRIVATE(module) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((module), PINOS_TYPE_MODULE, PinosModulePrivate)) + +struct _PinosModulePrivate +{ + GModule *module; +}; + +G_DEFINE_TYPE (PinosModule, pinos_module, G_TYPE_OBJECT); + +enum +{ + PROP_0, + PROP_DAEMON, + PROP_NAME, +}; + +static void +pinos_module_finalize (GObject * object) +{ + PinosModule *module = PINOS_MODULE (object); + PinosModulePrivate *priv = module->priv; + + g_clear_object (&module->daemon); + g_free (module->name); + g_module_close (priv->module); + + G_OBJECT_CLASS (pinos_module_parent_class)->finalize (object); +} + +static void +pinos_module_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + PinosModule *module = PINOS_MODULE (object); + + switch (prop_id) { + case PROP_DAEMON: + module->daemon = g_value_dup_object (value); + break; + case PROP_NAME: + module->name = g_value_dup_string (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +pinos_module_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + PinosModule *module = PINOS_MODULE (object); + + switch (prop_id) { + case PROP_DAEMON: + g_value_set_object (value, module->daemon); + break; + case PROP_NAME: + g_value_set_string (value, module->name); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +pinos_module_class_init (PinosModuleClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (PinosModulePrivate)); + + gobject_class->finalize = pinos_module_finalize; + gobject_class->set_property = pinos_module_set_property; + gobject_class->get_property = pinos_module_get_property; + + g_object_class_install_property (gobject_class, + PROP_DAEMON, + g_param_spec_object ("daemon", + "Daemon", + "A Pinos Daemon", + PINOS_TYPE_DAEMON, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, + PROP_NAME, + g_param_spec_string ("name", + "Name", + "The name of the plugin", + NULL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); +} + +static void +pinos_module_init (PinosModule * module) +{ + PinosModulePrivate *priv = PINOS_MODULE_GET_PRIVATE (module); + module->priv = priv; +} + +GQuark +pinos_module_error_quark (void) +{ + static GQuark quark = 0; + + if (quark == 0) { + quark = g_quark_from_static_string ("pinos_module_error"); + } + + return quark; +} + +static PinosModule * +pinos_module_new (const gchar * name, PinosDaemon * daemon) +{ + return g_object_new (PINOS_TYPE_MODULE, "name", name, "daemon", daemon, NULL); +} + +static gchar * +find_module (const gchar * path, const gchar *name) +{ + gchar *filename; + GDir *dir; + const gchar *entry; + GError *err = NULL; + + filename = g_strconcat (path, G_DIR_SEPARATOR_S, name, ".", G_MODULE_SUFFIX, NULL); + + if (g_file_test (filename, G_FILE_TEST_IS_REGULAR)) { + /* found a regular file with name */ + return filename; + } + + g_clear_pointer (&filename, g_free); + + /* now recurse down in subdirectories and look for it there */ + + dir = g_dir_open (path, 0, &err); + if (dir == NULL) { + g_warning ("could not open %s: %s", path, err->message); + g_error_free (err); + return NULL; + } + + while ((entry = g_dir_read_name (dir))) { + gchar *newpath; + + newpath = g_build_filename (path, entry, NULL); + if (g_file_test (newpath, G_FILE_TEST_IS_DIR)) { + filename = find_module (newpath, name); + } + + if (filename != NULL) + break; + } + + g_dir_close (dir); + + return filename; +} + +/** + * pinos_module_load: + * @daemon: a #PinosDaemon + * @name: name of the module to load + * @err: Return location for a #GError, or %NULL + * + * Load module with @name. + * + * Returns: A #PinosModule if the module could be loaded, or %NULL on failure. + */ +PinosModule * +pinos_module_load (PinosDaemon * daemon, const gchar * name, GError ** err) +{ + PinosModule *module; + PinosModulePrivate *priv; + GModule *gmodule; + gchar *filename = NULL; + const gchar *module_dir; + PinosModuleInitFunc init_func; + + g_return_val_if_fail (name != NULL && name[0] != '\0', NULL); + g_return_val_if_fail (PINOS_IS_DAEMON (daemon), NULL); + + if (!g_module_supported ()) { + g_set_error (err, PINOS_MODULE_ERROR, PINOS_MODULE_ERROR_LOADING, + "Dynamic module loading not supported"); + return NULL; + } + + module_dir = g_getenv ("PINOS_MODULE_DIR"); + if (module_dir != NULL) { + gchar **l; + gint i; + + g_debug ("PINOS_MODULE_DIR set to: %s", module_dir); + + l = g_strsplit (module_dir, G_SEARCHPATH_SEPARATOR_S, 0); + for (i = 0; l[i] != NULL; i++) { + filename = find_module (l[i], name); + if (filename != NULL) + break; + } + g_strfreev (l); + } else { + g_debug ("moduledir set to: %s", MODULEDIR); + + filename = find_module (MODULEDIR, name); + } + + if (filename == NULL) { + g_set_error (err, PINOS_MODULE_ERROR, PINOS_MODULE_ERROR_NOT_FOUND, + "No module \"%s\" was found", name); + return NULL; + } + + g_debug ("trying to load module: %s (%s)", name, filename); + + gmodule = g_module_open (filename, G_MODULE_BIND_LOCAL); + if (gmodule == NULL) { + g_set_error (err, PINOS_MODULE_ERROR, PINOS_MODULE_ERROR_LOADING, + "Failed to open module: %s", g_module_error ()); + return NULL; + } + + if (!g_module_symbol (gmodule, PINOS_SYMBOL_MODULE_INIT, + (gpointer *) &init_func)) { + g_set_error (err, PINOS_MODULE_ERROR, PINOS_MODULE_ERROR_LOADING, + "\"%s\" is not a pinos module", name); + g_module_close (gmodule); + return NULL; + } + + module = pinos_module_new (name, daemon); + priv = module->priv; + priv->module = gmodule; + + /* don't unload this module again */ + g_module_make_resident (gmodule); + + if (!init_func (module)) { + g_set_error (err, PINOS_MODULE_ERROR, PINOS_MODULE_ERROR_INIT, + "\"%s\" failed to initialize", name); + g_object_unref (module); + return NULL; + } + + g_debug ("loaded module: %s", module->name); + + return module; +} diff --git a/pinos/server/module.h b/pinos/server/module.h new file mode 100644 index 000000000..be8fb9530 --- /dev/null +++ b/pinos/server/module.h @@ -0,0 +1,100 @@ +/* Pinos + * Copyright (C) 2016 Axis Communications + * @author Linus Svensson + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __PINOS_MODULE_H__ +#define __PINOS_MODULE_H__ + +#include + +G_BEGIN_DECLS + +#include + +#define PINOS_TYPE_MODULE (pinos_module_get_type ()) +#define PINOS_IS_MODULE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PINOS_TYPE_MODULE)) +#define PINOS_IS_MODULE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PINOS_TYPE_MODULE)) +#define PINOS_MODULE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PINOS_TYPE_MODULE, PinosModuleClass)) +#define PINOS_MODULE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PINOS_TYPE_MODULE, PinosModule)) +#define PINOS_MODULE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PINOS_TYPE_MODULE, PinosModuleClass)) +#define PINOS_MODULE_CAST(obj) ((PinosModule*)(obj)) +#define PINOS_MODULE_CLASS_CAST(klass) ((PinosModuleClass*)(klass)) + +typedef struct _PinosModule PinosModule; +typedef struct _PinosModuleClass PinosModuleClass; +typedef struct _PinosModulePrivate PinosModulePrivate; + +struct _PinosModule { + GObject object; + + gchar *name; + PinosDaemon *daemon; + + PinosModulePrivate *priv; +}; + +struct _PinosModuleClass { + GObjectClass parent_class; +}; + +GQuark pinos_module_error_quark (void); +/** + * PINOS_MODULE_ERROR: + * + * Pinos module error. + */ +#define PINOS_MODULE_ERROR pinos_module_error_quark () + +/** + * PinosModuleError: + * @PINOS_MODULE_ERROR_GENERIC: Generic module error. + * @PINOS_MODULE_ERROR_NOT_FOUND: Module could not be found. + * @PINOS_MODULE_ERROR_LOADING: Module could not be loaded. + * @PINOS_MODULE_ERROR_INIT: The module failed to initialize. + * + * Error codes for Pinos modules. + */ +typedef enum +{ + PINOS_MODULE_ERROR_GENERIC, + PINOS_MODULE_ERROR_NOT_FOUND, + PINOS_MODULE_ERROR_LOADING, + PINOS_MODULE_ERROR_INIT, +} PinosModuleError; + +/** + * PinosModuleInitFunc: + * @module: A #PinosModule + * + * A module should provide an init function with this signature. This function + * will be called when a module is loaded. + * + * Returns: %TRUE on success, %FALSE otherwise + */ +typedef gboolean (*PinosModuleInitFunc) (PinosModule *module); + +GType pinos_module_get_type (void); + +PinosModule * pinos_module_load (PinosDaemon *daemon, + const gchar *name, + GError **err); + +G_END_DECLS + +#endif /* __PINOS_MODULE_H__ */ diff --git a/pkgconfig/libpinos.pc.in b/pkgconfig/libpinos.pc.in index 466e34833..c41863cf3 100644 --- a/pkgconfig/libpinos.pc.in +++ b/pkgconfig/libpinos.pc.in @@ -2,7 +2,7 @@ prefix=@prefix@ exec_prefix=@exec_prefix@ libdir=@libdir@ includedir=@includedir@/ -modlibexecdir=@modlibexecdir@ +moduledir=@moduledir@ Name: libpinos Description: Pinos Client Interface