changeset 964:0dd6afe7386d

irccd: implement plugin loaders
author David Demelier <markand@malikania.fr>
date Fri, 29 Jan 2021 13:50:44 +0100
parents 371e1cc2c697
children a518664b20a0
files irccd/main.c lib/irccd/config.h.in lib/irccd/dl-plugin.c lib/irccd/dl-plugin.h lib/irccd/irccd.c lib/irccd/irccd.h lib/irccd/js-plugin.c lib/irccd/js-plugin.h lib/irccd/jsapi-plugin.c lib/irccd/limits.h lib/irccd/plugin.c lib/irccd/plugin.h
diffstat 12 files changed, 303 insertions(+), 113 deletions(-) [+]
line wrap: on
line diff
--- a/irccd/main.c	Thu Jan 28 14:20:58 2021 +0100
+++ b/irccd/main.c	Fri Jan 29 13:50:44 2021 +0100
@@ -19,6 +19,7 @@
 #include <stdio.h>
 #include <err.h>
 
+#include <irccd/dl-plugin.h>
 #include <irccd/irccd.h>
 #include <irccd/log.h>
 #include <irccd/server.h>
@@ -47,4 +48,14 @@
 
 	if (argc > 0)
 		return run(argc, argv);
+
+	irc_bot_init();
+
+	/* TODO: temp. */
+	irc_log_set_verbose(true);
+
+	irc_bot_plugin_loader_add(irc_dl_plugin_loader_new());
+	irc_bot_plugin_loader_add(irc_js_plugin_loader_new());
+	irc_bot_plugin_find("foo");
+
 }
--- a/lib/irccd/config.h.in	Thu Jan 28 14:20:58 2021 +0100
+++ b/lib/irccd/config.h.in	Fri Jan 29 13:50:44 2021 +0100
@@ -24,6 +24,8 @@
 #define IRCCD_VERSION_PATCH     @IRCCD_VERSION_PATCH@
 #define IRCCD_VERSION           "@IRCCD_VERSION_MAJOR@.@IRCCD_VERSION_MINOR@.@IRCCD_VERSION_PATCH@"
 
+#define IRCCD_LIBDIR            "@CMAKE_INSTALL_FULL_LIBDIR@"
+
 #cmakedefine IRCCD_WITH_JS
 #cmakedefine IRCCD_WITH_SSL
 
--- a/lib/irccd/dl-plugin.c	Thu Jan 28 14:20:58 2021 +0100
+++ b/lib/irccd/dl-plugin.c	Fri Jan 29 13:50:44 2021 +0100
@@ -16,13 +16,16 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include <sys/stat.h>
 #include <assert.h>
 #include <ctype.h>
 #include <dlfcn.h>
 #include <errno.h>
 #include <stdio.h>
+#include <stdlib.h>
 #include <string.h>
 
+#include "config.h"
 #include "dl-plugin.h"
 #include "log.h"
 #include "plugin.h"
@@ -38,6 +41,7 @@
 } while (0)
 
 struct self {
+	struct irc_plugin plugin;
 	char prefix[32];
 	void *handle;
 };
@@ -164,57 +168,98 @@
 	if (self->handle)
 		dlclose(self->handle);
 
+	free(self);
 	memset(self, 0, sizeof (*self));
 }
 
-static bool
-init(struct self *self, const char *path)
+static struct self *
+init(const char *path)
 {
-	memset(self, 0, sizeof (*self));
+	struct self self;
+	struct stat st;
 
-	if (!(self->handle = dlopen(path, RTLD_NOW))) {
-		irc_log_warn("plugin: %s: %s", strerror(errno));
-		return false;
+	/*
+	 * It's not possible to get the exact error code when loading a plugin
+	 * using dlopen, since we're trying a lot of files that potentially not
+	 * exist we check presence before even though there's a possible
+	 * condition but at least we can print an error message if there are
+	 * other errors than missing file.
+	 */
+	if (stat(path, &st) < 0 && errno == ENOENT)
+		return NULL;
+
+	if (!(self.handle = dlopen(path, RTLD_NOW))) {
+		irc_log_warn("plugin: %s: %s", path, dlerror());
+		return NULL;
 	}
 
 	/* Compute prefix name */
-	strlcpy(self->prefix, irc_util_basename(path), sizeof (self->prefix));
+	strlcpy(self.prefix, irc_util_basename(path), sizeof (self.prefix));
 
 	/* Remove plugin extension. */
-	self->prefix[strcspn(self->prefix, ".")] = '\0';
+	self.prefix[strcspn(self.prefix, ".")] = '\0';
 
 	/* Remove every invalid identifiers. */
-	for (char *p = self->prefix; *p; ++p)
+	for (char *p = self.prefix; *p; ++p)
 		if (!isalnum(*p))
 			*p = '_';
 
-	return true;
+	return irc_util_memdup(&self, sizeof (self));
 }
 
-bool
-irc_dl_plugin_open(struct irc_plugin *plg, const char *path)
+static struct irc_plugin *
+wrap_open(struct irc_plugin_loader *ldr, const char *path)
 {
-	struct self self;
+	(void)ldr;
+
+	return irc_dl_plugin_open(path);
+}
 
-	if (!init(&self, path))
+struct irc_plugin *
+irc_dl_plugin_open(const char *path)
+{
+	struct self *self;
+
+	if (!(self = init(path)))
 		return false;
 
 	/* Data and all callbacks. */
-	plg->data = irc_util_memdup(&self, sizeof (self));
-	plg->set_template = set_template;
-	plg->get_template = get_template;
-	plg->get_templates = get_templates;
-	plg->set_path = set_path;
-	plg->get_path = get_path;
-	plg->get_paths = get_paths;
-	plg->set_option = set_option;
-	plg->get_option = get_option;
-	plg->get_options = get_options;
-	plg->load = load;
-	plg->reload = reload;
-	plg->unload = unload;
-	plg->handle = handle;
-	plg->finish = finish;
+	self->plugin.data = self;
+	self->plugin.set_template = set_template;
+	self->plugin.get_template = get_template;
+	self->plugin.get_templates = get_templates;
+	self->plugin.set_path = set_path;
+	self->plugin.get_path = get_path;
+	self->plugin.get_paths = get_paths;
+	self->plugin.set_option = set_option;
+	self->plugin.get_option = get_option;
+	self->plugin.get_options = get_options;
+	self->plugin.load = load;
+	self->plugin.reload = reload;
+	self->plugin.unload = unload;
+	self->plugin.handle = handle;
+	self->plugin.finish = finish;
+
+	return &self->plugin;
+}
 
-	return true;
+struct irc_plugin_loader *
+irc_dl_plugin_loader_new(void)
+{
+	struct irc_plugin_loader *ldr;
+
+	ldr = irc_util_calloc(1, sizeof (*ldr));
+	ldr->open = wrap_open;
+
+#if defined(_WIN32)
+	strlcpy(ldr->extensions, "dll", sizeof (ldr->extensions));
+#elif defined(__APPLE__)
+	strlcpy(ldr->extensions, "so:dylib", sizeof (ldr->extensions));
+#else
+	strlcpy(ldr->extensions, "so", sizeof (ldr->extensions));
+#endif
+
+	strlcpy(ldr->paths, IRCCD_LIBDIR "/irccd", sizeof (ldr->paths));
+
+	return ldr;
 }
--- a/lib/irccd/dl-plugin.h	Thu Jan 28 14:20:58 2021 +0100
+++ b/lib/irccd/dl-plugin.h	Fri Jan 29 13:50:44 2021 +0100
@@ -19,9 +19,8 @@
 #ifndef IRCCD_DL_PLUGIN_H
 #define IRCCD_DL_PLUGIN_H
 
-#include <stdbool.h>
-
 struct irc_plugin;
+struct irc_plugin_loader;
 
 #if defined(_WIN32)
 #       define IRC_DL_EXPORT __declspec(dllexport)
@@ -29,13 +28,10 @@
 #       define IRC_DL_EXPORT
 #endif
 
-#if defined(__APPLE__)
-#       define IRC_DL_EXT ".dylib"
-#else
-#       define IRC_DL_EXT ".so"
-#endif
+struct irc_plugin *
+irc_dl_plugin_open(const char *);
 
-bool
-irc_dl_plugin_open(struct irc_plugin *, const char *);
+struct irc_plugin_loader *
+irc_dl_plugin_loader_new(void);
 
 #endif /* !IRCCD_DL_PLUGIN_H */
--- a/lib/irccd/irccd.c	Thu Jan 28 14:20:58 2021 +0100
+++ b/lib/irccd/irccd.c	Fri Jan 29 13:50:44 2021 +0100
@@ -385,8 +385,56 @@
 	irc_plugin_load(p);
 }
 
+static struct irc_plugin *
+find_plugin(struct irc_plugin_loader *ldr, const char *base, const char *name)
+{
+	char path[PATH_MAX], buf[IRC_EXTENSIONS_LEN], *t, *ext;
+	struct irc_plugin *p;
+
+	strlcpy(buf, ldr->extensions, sizeof (buf));
+
+	for (t = buf; (ext = strtok_r(t, ":", &t)); ) {
+		snprintf(path, sizeof (path), "%s/%s.%s", base, name, ext);
+		irc_log_info("irccd: trying %s", path);
+
+		if ((p = irc_plugin_loader_open(ldr, path)))
+			return p;
+	}
+
+	return NULL;
+}
+
+void
+irc_bot_plugin_find(const char *name)
+{
+	char buf[IRC_PATHS_LEN], *t, *token;
+	struct irc_plugin *p = NULL;
+	struct irc_plugin_loader *ldr;
+
+	irc_log_info("irccd: trying to find plugin %s", name);
+
+	SLIST_FOREACH(ldr, &irc.plugin_loaders, link) {
+		/* Copy the paths to tokenize it. */
+		strlcpy(buf, ldr->paths, sizeof (buf));
+
+		/*
+		 * For every directory (separated by colon) call find_plugin
+		 * which will append the extension and try to open it.
+		 */
+		for (t = buf; (token = strtok_r(t, ":", &t)); ) {
+			if ((p = find_plugin(ldr, token, name)))
+				break;
+		}
+	}
+
+	if (p)
+		irc_bot_plugin_add(p);
+	else
+		irc_log_warn("irccd: could not find plugin %s", name);
+}
+
 struct irc_plugin *
-irc_bot_plugin_find(const char *name)
+irc_bot_plugin_get(const char *name)
 {
 	struct irc_plugin *p;
 
@@ -402,7 +450,7 @@
 {
 	struct irc_plugin *p;
 
-	if (!(p = irc_bot_plugin_find(name)))
+	if (!(p = irc_bot_plugin_get(name)))
 		return;
 
 	irc_plugin_unload(p);
@@ -412,6 +460,14 @@
 }
 
 void
+irc_bot_plugin_loader_add(struct irc_plugin_loader *ldr)
+{
+	assert(ldr);
+
+	SLIST_INSERT_HEAD(&irc.plugin_loaders, ldr, link);
+}
+
+void
 irc_bot_rule_insert(struct irc_rule *rule, size_t index)
 {
 	assert(rule);
--- a/lib/irccd/irccd.h	Thu Jan 28 14:20:58 2021 +0100
+++ b/lib/irccd/irccd.h	Fri Jan 29 13:50:44 2021 +0100
@@ -19,8 +19,6 @@
 #ifndef IRCCD_H
 #define IRCCD_H
 
-#include <sys/queue.h>
-
 #include "peer.h"
 #include "plugin.h"
 #include "rule.h"
@@ -30,6 +28,7 @@
 	struct irc_server_list servers;
 	struct irc_peer_list peers;
 	struct irc_plugin_list plugins;
+	struct irc_plugin_loader_list plugin_loaders;
 	struct irc_rule_list rules;
 } irc;
 
@@ -51,13 +50,19 @@
 void
 irc_bot_plugin_add(struct irc_plugin *);
 
+void
+irc_bot_plugin_find(const char *);
+
 struct irc_plugin *
-irc_bot_plugin_find(const char *);
+irc_bot_plugin_get(const char *);
 
 void
 irc_bot_plugin_remove(const char *);
 
 void
+irc_bot_plugin_loader_add(struct irc_plugin_loader *);
+
+void
 irc_bot_rule_insert(struct irc_rule *, size_t);
 
 void
--- a/lib/irccd/js-plugin.c	Thu Jan 28 14:20:58 2021 +0100
+++ b/lib/irccd/js-plugin.c	Fri Jan 29 13:50:44 2021 +0100
@@ -386,7 +386,9 @@
 	return ret;
 
 err:
-	close(fd);
+	if (fd != -1)
+		close(fd);
+
 	free(ret);
 
 	return false;
@@ -416,38 +418,39 @@
 	free(ptr);
 }
 
-static bool
-init(struct irc_plugin *plg, const char *script)
+static struct irc_js_plugin_data *
+init(const char *path, const char *script)
 {
-	struct irc_js_plugin_data js = {0};
+	struct irc_js_plugin_data *js;
+
+	js = irc_util_calloc(1, sizeof (*js));
+	js->ctx = duk_create_heap(wrap_malloc, wrap_realloc, wrap_free, NULL, NULL);
 
-	/* Load all modules. */
-	js.ctx = duk_create_heap(wrap_malloc, wrap_realloc, wrap_free, NULL, NULL);
-	irc_jsapi_load(js.ctx);
-	irc_jsapi_chrono_load(js.ctx);
-	irc_jsapi_directory_load(js.ctx);
-	irc_jsapi_file_load(js.ctx);
-	irc_jsapi_logger_load(js.ctx);
-	irc_jsapi_plugin_load(js.ctx, plg);
-	irc_jsapi_server_load(js.ctx);
-	irc_jsapi_system_load(js.ctx);
-	irc_jsapi_timer_load(js.ctx);
-	irc_jsapi_unicode_load(js.ctx);
-	irc_jsapi_util_load(js.ctx);
+	irc_jsapi_load(js->ctx);
+	irc_jsapi_chrono_load(js->ctx);
+	irc_jsapi_directory_load(js->ctx);
+	irc_jsapi_file_load(js->ctx);
+	irc_jsapi_logger_load(js->ctx);
+	irc_jsapi_plugin_load(js->ctx, &js->plugin);
+	irc_jsapi_server_load(js->ctx);
+	irc_jsapi_system_load(js->ctx);
+	irc_jsapi_timer_load(js->ctx);
+	irc_jsapi_unicode_load(js->ctx);
+	irc_jsapi_util_load(js->ctx);
 
-	if (duk_peval_string(js.ctx, script) != 0) {
-		irc_log_warn("plugin %s: %s", plg->name, duk_to_string(js.ctx, -1));
-		duk_destroy_heap(js.ctx);
-		return false;
+	if (duk_peval_string(js->ctx, script) != 0) {
+		irc_log_warn("plugin: %s: %s", path, duk_to_string(js->ctx, -1));
+		duk_destroy_heap(js->ctx);
+		free(js);
+		return NULL;
 	}
 
-	plg->license = js.license = metadata(js.ctx, "license");
-	plg->version = js.version = metadata(js.ctx, "version");
-	plg->author = js.author = metadata(js.ctx, "author");
-	plg->description = js.description = metadata(js.ctx, "summary");
-	plg->data = irc_util_memdup(&js, sizeof (js));
+	js->plugin.license = js->license = metadata(js->ctx, "license");
+	js->plugin.version = js->version = metadata(js->ctx, "version");
+	js->plugin.author = js->author = metadata(js->ctx, "author");
+	js->plugin.description = js->description = metadata(js->ctx, "summary");
 
-	return true;
+	return js;
 }
 
 static void
@@ -476,12 +479,23 @@
 	if (self->ctx)
 		duk_destroy_heap(self->ctx);
 
+	freelist(self->options);
+	freelist(self->templates);
+	freelist(self->paths);
+
 	free(self->license);
 	free(self->version);
 	free(self->author);
 	free(self->description);
+	free(self);
+}
 
-	memset(self, 0, sizeof (*self));
+static struct irc_plugin *
+wrap_open(struct irc_plugin_loader *ldr, const char *path)
+{
+	(void)ldr;
+
+	return irc_js_plugin_open(path);
 }
 
 struct irc_plugin *
@@ -490,36 +504,56 @@
 	assert(path);
 
 	char *script = NULL;
-	struct irc_plugin *plg = irc_util_calloc(1, sizeof (*plg));
+	struct irc_js_plugin_data *self;
 
+	/*
+	 * Duktape can't open script from file path so we need to read the
+	 * whole script at once.
+	 */
 	if (!(script = eat(path))) {
-		irc_log_warn("plugin: %s", strerror(errno));
-		return NULL;
-	}
+		if (errno != ENOENT)
+			irc_log_warn("irccd: %s: %s", path, strerror(errno));
 
-	if (!(init(plg, script))) {
-		free(script);
-		free(plg);
 		return NULL;
 	}
 
-	plg->set_template = set_template;
-	plg->get_template = get_template;
-	plg->get_templates = get_templates;
-	plg->set_path = set_path;
-	plg->get_path = get_path;
-	plg->get_paths = get_paths;
-	plg->set_option = set_option;
-	plg->get_option = get_option;
-	plg->get_options = get_options;
-	plg->load = load;
-	plg->reload = reload;
-	plg->unload = unload;
-	plg->handle = handle;
-	plg->finish = finish;
+	/* Init already log errors. */
+	if (!(self = init(path, script))) {
+		free(script);
+		return NULL;
+	}
+
+	self->plugin.data = self;
+	self->plugin.set_template = set_template;
+	self->plugin.get_template = get_template;
+	self->plugin.get_templates = get_templates;
+	self->plugin.set_path = set_path;
+	self->plugin.get_path = get_path;
+	self->plugin.get_paths = get_paths;
+	self->plugin.set_option = set_option;
+	self->plugin.get_option = get_option;
+	self->plugin.get_options = get_options;
+	self->plugin.load = load;
+	self->plugin.reload = reload;
+	self->plugin.unload = unload;
+	self->plugin.handle = handle;
+	self->plugin.finish = finish;
 
 	/* No longer needed. */
 	free(script);
 
-	return plg;
+	return &self->plugin;
 }
+
+struct irc_plugin_loader *
+irc_js_plugin_loader_new(void)
+{
+	struct irc_plugin_loader *ldr;
+
+	ldr = irc_util_calloc(1, sizeof (*ldr));
+	ldr->open = wrap_open;
+	strlcpy(ldr->extensions, "js", sizeof (ldr->extensions));
+	strlcpy(ldr->paths, IRCCD_LIBDIR "/irccd", sizeof (ldr->paths));
+
+	return ldr;
+}
--- a/lib/irccd/js-plugin.h	Thu Jan 28 14:20:58 2021 +0100
+++ b/lib/irccd/js-plugin.h	Fri Jan 29 13:50:44 2021 +0100
@@ -19,13 +19,12 @@
 #ifndef IRCCD_JS_PLUGIN_H
 #define IRCCD_JS_PLUGIN_H
 
-#include <stdbool.h>
-
 #include <duktape.h>
 
-struct irc_plugin;
+#include "plugin.h"
 
 struct irc_js_plugin_data {
+	struct irc_plugin plugin;
 	duk_context *ctx;
 	char **options;
 	char **templates;
@@ -39,4 +38,7 @@
 struct irc_plugin *
 irc_js_plugin_open(const char *);
 
+struct irc_plugin_loader *
+irc_js_plugin_loader_new(void);
+
 #endif /* !IRCCD_JS_PLUGIN_H */
--- a/lib/irccd/jsapi-plugin.c	Thu Jan 28 14:20:58 2021 +0100
+++ b/lib/irccd/jsapi-plugin.c	Fri Jan 29 13:50:44 2021 +0100
@@ -134,7 +134,7 @@
 find(duk_context *ctx)
 {
 	const char *name = duk_require_string(ctx, 0);
-	struct irc_plugin *plg = irc_bot_plugin_find(name);
+	struct irc_plugin *plg = irc_bot_plugin_get(name);
 
 	if (!plg)
 		(void)duk_error(ctx, DUK_ERR_REFERENCE_ERROR, "plugin %s not found", name);
--- a/lib/irccd/limits.h	Thu Jan 28 14:20:58 2021 +0100
+++ b/lib/irccd/limits.h	Fri Jan 29 13:50:44 2021 +0100
@@ -19,27 +19,31 @@
 #ifndef IRCCD_LIMITS_H
 #define IRCCD_LIMITS_H
 
+#include <limits.h>
+
 /* Server limits. */
-#define IRC_NICKNAME_LEN        32      /* Nickname. */
-#define IRC_USERNAME_LEN        32      /* User name. */
-#define IRC_REALNAME_LEN        64      /* Real name. */
-#define IRC_CHANNEL_LEN         64      /* Channel name. */
-#define IRC_PASSWORD_LEN        64      /* Password length. */
-#define IRC_CTCPVERSION_LEN     64      /* Custom CTCP version answer. */
-#define IRC_USERMODES_LEN       8       /* Number of modes (e.g. ohv). */
-#define IRC_CHANTYPES_LEN       8
-#define IRC_CMDCHAR_LEN         4       /* Prefix for plugin commands (e.g. !). */
-#define IRC_MESSAGE_LEN         512     /* Official length per message. */
-#define IRC_ARGS_MAX            32      /* Own supported number of arguments per message. */
+#define IRC_NICKNAME_LEN        32              /* Nickname. */
+#define IRC_USERNAME_LEN        32              /* User name. */
+#define IRC_REALNAME_LEN        64              /* Real name. */
+#define IRC_CHANNEL_LEN         64              /* Channel name. */
+#define IRC_PASSWORD_LEN        64              /* Password length. */
+#define IRC_CTCPVERSION_LEN     64              /* Custom CTCP version answer. */
+#define IRC_USERMODES_LEN       8               /* Number of modes (e.g. ohv). */
+#define IRC_CHANTYPES_LEN       8               /* Channel types. */
+#define IRC_CMDCHAR_LEN         4               /* Prefix for plugin commands (e.g. !). */
+#define IRC_MESSAGE_LEN         512             /* Official length per message. */
+#define IRC_ARGS_MAX            32              /* Own supported number of arguments per message. */
 
 /* Network limits. */
-#define IRC_HOST_LEN            64      /* Hostname length.. */
-#define IRC_BUF_LEN             128000  /* Network buffer input/output. */
+#define IRC_HOST_LEN            64              /* Hostname length.. */
+#define IRC_BUF_LEN             128000          /* Network buffer input/output. */
 
 /* Generic limits. */
-#define IRC_ID_LEN              16      /* Plugin/server identifiers. */
+#define IRC_ID_LEN              16              /* Plugin/server identifiers. */
+#define IRC_PATHS_LEN           (PATH_MAX * 8)  /* Colon separated list of paths. */
+#define IRC_EXTENSIONS_LEN      32              /* Colon separated list of extensions for plugins. */
 
 /* Rule limits. */
-#define IRC_RULE_LEN            1024    /* Space-separated list of values. */
+#define IRC_RULE_LEN            1024            /* Space-separated list of values. */
 
 #endif /* !IRCCD_LIMITS_H */
--- a/lib/irccd/plugin.c	Thu Jan 28 14:20:58 2021 +0100
+++ b/lib/irccd/plugin.c	Fri Jan 29 13:50:44 2021 +0100
@@ -170,3 +170,21 @@
 
 	free(plg);
 }
+
+struct irc_plugin *
+irc_plugin_loader_open(struct irc_plugin_loader *ldr, const char *path)
+{
+	assert(ldr);
+	assert(path);
+
+	return ldr->open(ldr, path);
+}
+
+void
+irc_plugin_loader_finish(struct irc_plugin_loader *ldr)
+{
+	assert(ldr);
+
+	if (ldr->finish)
+		ldr->finish(ldr);
+}
--- a/lib/irccd/plugin.h	Thu Jan 28 14:20:58 2021 +0100
+++ b/lib/irccd/plugin.h	Fri Jan 29 13:50:44 2021 +0100
@@ -58,6 +58,17 @@
 
 LIST_HEAD(irc_plugin_list, irc_plugin);
 
+struct irc_plugin_loader {
+	char paths[IRC_PATHS_LEN];
+	char extensions[IRC_EXTENSIONS_LEN];
+	struct irc_plugin *(*open)(struct irc_plugin_loader *, const char *);
+	void (*finish)(struct irc_plugin_loader *);
+	void *data;
+	SLIST_ENTRY(irc_plugin_loader) link;
+};
+
+SLIST_HEAD(irc_plugin_loader_list, irc_plugin_loader);
+
 void
 irc_plugin_set_template(struct irc_plugin *, const char *, const char *);
 
@@ -100,4 +111,10 @@
 void
 irc_plugin_finish(struct irc_plugin *);
 
+struct irc_plugin *
+irc_plugin_loader_open(struct irc_plugin_loader *, const char *);
+
+void
+irc_plugin_loader_finish(struct irc_plugin_loader *);
+
 #endif /* !IRCCD_PLUGIN_H */