<commit_before>/*
 * SysDB - src/core/plugin.c
 * Copyright (C) 2012 Sebastian 'tokkee' Harl <<EMAIL>>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#if HAVE_CONFIG_H
#	include "config.h"
#endif /* HAVE_CONFIG_H */

#include "sysdb.h"
#include "core/plugin.h"
#include "core/time.h"
#include "utils/error.h"
#include "utils/llist.h"
#include "utils/strbuf.h"

#include <assert.h>

#include <errno.h>

#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>

#include <ltdl.h>

#include <pthread.h>

/*
 * private data types
 */

struct sdb_plugin_info {
	char *plugin_name;
	char *filename;

	/* public attributes */
	char *name;

	char *description;
	char *copyright;
	char *license;

	int   version;
	int   plugin_version;
};
#define SDB_PLUGIN_INFO_INIT { \
	/* plugin_name */ NULL, /* filename */ NULL, \
	/* name */ NULL, /* desc */ NULL, \
	/* copyright */ NULL, /* license */ NULL, \
	/* version */ -1, /* plugin_version */ -1 }
#define INFO_GET(i, attr) \
	((i)->attr ? (i)->attr : #attr" not set")

typedef struct {
	sdb_object_t super;
	sdb_plugin_ctx_t public;

	sdb_plugin_info_t info;
	lt_dlhandle handle;

	/* The usage count differs from the object's ref count
	 * in that it provides higher level information about how
	 * the plugin is in use. */
	size_t use_cnt;
} ctx_t;
#define CTX_INIT { SDB_OBJECT_INIT, \
	SDB_PLUGIN_CTX_INIT, SDB_PLUGIN_INFO_INIT, NULL, 0 }

#define CTX(obj) ((ctx_t *)(obj))

typedef struct {
	sdb_object_t super;
	void *cb_callback;
	sdb_object_t *cb_user_data;
	ctx_t *cb_ctx;
} sdb_plugin_cb_t;
#define SDB_PLUGIN_CB_INIT { SDB_OBJECT_INIT, \
	/* callback = */ NULL, /* user_data = */ NULL, \
	SDB_PLUGIN_CTX_INIT }

typedef struct {
	sdb_plugin_cb_t super;
#define ccb_callback super.cb_callback
#define ccb_user_data super.cb_user_data
#define ccb_ctx super.cb_ctx
	sdb_time_t ccb_interval;
	sdb_time_t ccb_next_update;
} sdb_plugin_collector_cb_t;

#define SDB_PLUGIN_CB(obj) ((sdb_plugin_cb_t *)(obj))
#define SDB_CONST_PLUGIN_CB(obj) ((const sdb_plugin_cb_t *)(obj))
#define SDB_PLUGIN_CCB(obj) ((sdb_plugin_collector_cb_t *)(obj))
#define SDB_CONST_PLUGIN_CCB(obj) ((const sdb_plugin_collector_cb_t *)(obj))

/*
 * private variables
 */

static sdb_plugin_ctx_t  plugin_default_ctx  = SDB_PLUGIN_CTX_INIT;
static sdb_plugin_info_t plugin_default_info = SDB_PLUGIN_INFO_INIT;

static pthread_key_t     plugin_ctx_key;
static _Bool             plugin_ctx_key_initialized = 0;

/* a list of the plugin contexts of all registered plugins */
static sdb_llist_t      *all_plugins = NULL;

static sdb_llist_t      *config_list = NULL;
static sdb_llist_t      *init_list = NULL;
static sdb_llist_t      *collector_list = NULL;
static sdb_llist_t      *cname_list = NULL;
static sdb_llist_t      *shutdown_list = NULL;
static sdb_llist_t      *log_list = NULL;

/*
 * private helper functions
 */

static void
plugin_info_clear(sdb_plugin_info_t *info)
{
	sdb_plugin_info_t empty_info = SDB_PLUGIN_INFO_INIT;
	if (! info)
		return;

	if (info->plugin_name)
		free(info->plugin_name);
	if (info->filename)
		free(info->filename);

	if (info->name)
		free(info->name);
	if (info->description)
		free(info->description);
	if (info->copyright)
		free(info->copyright);
	if (info->license)
		free(info->license);

	*info = empty_info;
} /* plugin_info_clear */

static void
ctx_key_init(void)
{
	if (plugin_ctx_key_initialized)
		return;

	pthread_key_create(&plugin_ctx_key, /* destructor */ NULL);
	plugin_ctx_key_initialized = 1;
} /* ctx_key_init */

static int
plugin_cmp_next_update(const sdb_object_t *a, const sdb_object_t *b)
{
	const sdb_plugin_collector_cb_t *ccb1
		= (const sdb_plugin_collector_cb_t *)a;
	const sdb_plugin_collector_cb_t *ccb2
		= (const sdb_plugin_collector_cb_t *)b;

	assert(ccb1 && ccb2);

	return (ccb1->ccb_next_update > ccb2->ccb_next_update)
		? 1 : (ccb1->ccb_next_update < ccb2->ccb_next_update)
		? -1 : 0;
} /* plugin_cmp_next_update */

static int
plugin_lookup_by_name(const sdb_object_t *obj, const void *id)
{
	const sdb_plugin_cb_t *cb = SDB_CONST_PLUGIN_CB(obj);
	const char *name = id;

	assert(cb && id && cb->cb_ctx);
	if (!strcasecmp(cb->cb_ctx->info.plugin_name, name))
		return 0;
	return 1;
} /* plugin_lookup_by_name */

static void
plugin_unregister_by_name(const char *plugin_name)
{
	sdb_object_t *obj;
	size_t i;

	struct {
		const char  *type;
		sdb_llist_t *list;
	} all_lists[] = {
		{ "config",    config_list },
		{ "init",      init_list },
		{ "collector", collector_list },
		{ "cname",     cname_list },
		{ "shutdown",  shutdown_list },
		{ "log",       log_list },
	};

	for (i = 0; i < SDB_STATIC_ARRAY_LEN(all_lists); ++i) {
		const char  *type = all_lists[i].type;
		sdb_llist_t *list = all_lists[i].list;

		while (1) {
			sdb_plugin_cb_t *cb;

			cb = SDB_PLUGIN_CB(sdb_llist_remove(list,
						plugin_lookup_by_name, plugin_name));
			if (! cb)
				break;

			sdb_log(SDB_LOG_INFO, "core: Unregistering "
					"%s callback '%s' (module %s)", type, cb->super.name,
					cb->cb_ctx->info.plugin_name);
			sdb_object_deref(SDB_OBJ(cb));
		}
	}

	obj = sdb_llist_search_by_name(all_plugins, plugin_name);
	if (obj->ref_cnt <= 1)
		sdb_llist_remove_by_name(all_plugins, plugin_name);
	/* else: other callbacks still reference it */
	sdb_object_deref(obj);
} /* plugin_unregister_by_name */

/*
 * private types
 */

static int
ctx_init(sdb_object_t *obj, va_list __attribute__((unused)) ap)
{
	ctx_t *ctx = CTX(obj);

	assert(ctx);

	ctx->public = plugin_default_ctx;
	ctx->info = plugin_default_info;
	ctx->handle = NULL;
	ctx->use_cnt = 1;
	return 0;
} /* ctx_init */

static void
ctx_destroy(sdb_object_t *obj)
{
	ctx_t *ctx = CTX(obj);

	if (ctx->handle) {
		const char *err;

		sdb_log(SDB_LOG_INFO, "core: Unloading module %s",
				ctx->info.plugin_name);

		lt_dlerror();
		lt_dlclose(ctx->handle);
		if ((err = lt_dlerror()))
			sdb_log(SDB_LOG_WARNING, "core: Failed to unload module %s: %s",
					ctx->info.plugin_name, err);
	}

	plugin_info_clear(&ctx->info);
} /* ctx_destroy */

static sdb_type_t ctx_type = {
	sizeof(ctx_t),

	ctx_init,
	ctx_destroy
};

static ctx_t *
ctx_create(const char *name)
{
	ctx_t *ctx;

	ctx = CTX(sdb_object_create(name, ctx_type));
	if (! ctx)
		return NULL;

	if (! plugin_ctx_key_initialized)
		ctx_key_init();
	sdb_object_ref(SDB_OBJ(ctx));
	pthread_setspecific(plugin_ctx_key, ctx);
	return ctx;
} /* ctx_create */

static ctx_t *
ctx_get(void)
{
	if (! plugin_ctx_key_initialized)
		ctx_key_init();
	return pthread_getspecific(plugin_ctx_key);
} /* ctx_get */

static ctx_t *
ctx_set(ctx_t *new)
{
	ctx_t *old;

	if (! plugin_ctx_key_initialized)
		ctx_key_init();

	old = pthread_getspecific(plugin_ctx_key);
	if (old)
		sdb_object_deref(SDB_OBJ(old));
	if (new)
		sdb_object_ref(SDB_OBJ(new));
	pthread_setspecific(plugin_ctx_key, new);
	return old;
} /* ctx_set */

static int
plugin_cb_init(sdb_object_t *obj, va_list ap)
{
	sdb_llist_t **list = va_arg(ap, sdb_llist_t **);
	const char   *type = va_arg(ap, const char *);
	void     *callback = va_arg(ap, void *);
	sdb_object_t   *ud = va_arg(ap, sdb_object_t *);

	assert(list);
	assert(type);
	assert(obj);

	if (sdb_llist_search_by_name(*list, obj->name)) {
		sdb_log(SDB_LOG_WARNING, "core: %s callback '%s' "
				"has already been registered. Ignoring newly "
				"registered version.", type, obj->name);
		return -1;
	}

	SDB_PLUGIN_CB(obj)->cb_callback = callback;
	SDB_PLUGIN_CB(obj)->cb_ctx      = ctx_get();
	sdb_object_ref(SDB_OBJ(SDB_PLUGIN_CB(obj)->cb_ctx));

	sdb_object_ref(ud);
	SDB_PLUGIN_CB(obj)->cb_user_data = ud;
	return 0;
} /* plugin_cb_init */

static void
plugin_cb_destroy(sdb_object_t *obj)
{
	assert(obj);
	sdb_object_deref(SDB_PLUGIN_CB(obj)->cb_user_data);
	sdb_object_deref(SDB_OBJ(SDB_PLUGIN_CB(obj)->cb_ctx));
} /* plugin_cb_destroy */

static sdb_type_t sdb_plugin_cb_type = {
	sizeof(sdb_plugin_cb_t),

	plugin_cb_init,
	plugin_cb_destroy
};

static sdb_type_t sdb_plugin_collector_cb_type = {
	sizeof(sdb_plugin_collector_cb_t),

	plugin_cb_init,
	plugin_cb_destroy
};

static int
plugin_add_callback(sdb_llist_t **list, const char *type,
		const char *name, void *callback, sdb_object_t *user_data)
{
	sdb_object_t *obj;

	if ((! name) || (! callback))
		return -1;

	assert(list);

	if (! *list)
		*list = sdb_llist_create();
	if (! *list)
		return -1;

	obj = sdb_object_create(name, sdb_plugin_cb_type,
			list, type, callback, user_data);
	if (! obj)
		return -1;

	if (sdb_llist_append(*list, obj)) {
		sdb_object_deref(obj);
		return -1;
	}

	/* pass control to the list */
	sdb_object_deref(obj);

	sdb_log(SDB_LOG_INFO, "core: Registered %s callback '%s'.",
			type, name);
	return 0;
} /* plugin_add_callback */

/*
 * public API
 */

int
sdb_plugin_load(const char *name, const sdb_plugin_ctx_t *plugin_ctx)
{
	char  base_name[name ? strlen(name) + 1 : 1];
	const char *name_ptr;
	char *tmp;

	char filename[1024];
	ctx_t *ctx;

	lt_dlhandle lh;

	int (*mod_init)(sdb_plugin_info_t *);
	int status;

	if ((! name) || (! *name))
		return -1;

	base_name[0] = '\0';
	name_ptr = name;

	while ((tmp = strstr(name_ptr, "::"))) {
		strncat(base_name, name_ptr, (size_t)(tmp - name_ptr));
		strcat(base_name, "/");
		name_ptr = tmp + strlen("::");
	}
	strcat(base_name, name_ptr);

	ctx = CTX(sdb_llist_search_by_name(all_plugins, name));
	if (ctx) {
		/* plugin already loaded */
		++ctx->use_cnt;
		return 0;
	}

	snprintf(filename, sizeof(filename), "%s/%s.so",
			PKGLIBDIR, base_name);
	filename[sizeof(filename) - 1] = '\0';

	if (access(filename, R_OK)) {
		char errbuf[1024];
		sdb_log(SDB_LOG_ERR, "core: Failed to load plugin '%s' (%s): %s",
				name, filename, sdb_strerror(errno, errbuf, sizeof(errbuf)));
		return -1;
	}

	lt_dlinit();
	lt_dlerror();

	lh = lt_dlopen(filename);
	if (! lh) {
		sdb_log(SDB_LOG_ERR, "core: Failed to load plugin '%s': %s"
				"The most common cause for this problem are missing "
				"dependencies.\n", name, lt_dlerror());
		return -1;
	}

	if (ctx_get())
		sdb_log(SDB_LOG_WARNING, "core: Discarding old plugin context");

	ctx = ctx_create(name);
	if (! ctx) {
		sdb_log(SDB_LOG_ERR, "core: Failed to initialize plugin context");
		return -1;
	}

	ctx->info.plugin_name = strdup(name);
	ctx->info.filename = strdup(filename);
	ctx->handle = lh;

	if (plugin_ctx)
		ctx->public = *plugin_ctx;

	mod_init = (int (*)(sdb_plugin_info_t *))lt_dlsym(lh, "sdb_module_init");
	if (! mod_init) {
		sdb_log(SDB_LOG_ERR, "core: Failed to load plugin '%s': "
				"could not find symbol 'sdb_module_init'", name);
		sdb_object_deref(SDB_OBJ(ctx));
		return -1;
	}

	status = mod_init(&ctx->info);
	if (status) {
		sdb_log(SDB_LOG_ERR, "core: Failed to initialize "
				"module '%s'", name);
		plugin_unregister_by_name(ctx->info.plugin_name);
		sdb_object_deref(SDB_OBJ(ctx));
		return -1;
	}

	/* compare minor version */
	if ((ctx->info.version < 0)
			|| ((int)(ctx->info.version / 100) != (int)(SDB_VERSION / 100)))
		sdb_log(SDB_LOG_WARNING, "core: WARNING: version of "
				"plugin '%s' (%i.%i.%i) does not match our version "
				"(%i.%i.%i); this might cause problems",
				name, SDB_VERSION_DECODE(ctx->info.version),
				SDB_VERSION_DECODE(SDB_VERSION));

	if (! all_plugins) {
		if (! (all_plugins = sdb_llist_create())) {
			sdb_log(SDB_LOG_ERR, "core: Failed to load plugin '%s': "
					"internal error while creating linked list", name);
			plugin_unregister_by_name(ctx->info.plugin_name);
			sdb_object_deref(SDB_OBJ(ctx));
			return -1;
		}
	}

	sdb_llist_append(all_plugins, SDB_OBJ(ctx));

	sdb_log(SDB_LOG_INFO, "core: Successfully loaded "
			"plugin '%s' v%i (%s)\n\t%s\n\tLicense: %s",
			INFO_GET(&ctx->info, name), ctx->info.plugin_version,
			INFO_GET(&ctx->info, description),
			INFO_GET(&ctx->info, copyright),
			INFO_GET(&ctx->info, license));

	/* any registered callbacks took ownership of the context */
	sdb_object_deref(SDB_OBJ(ctx));

	/* reset */
	ctx_set(NULL);
	return 0;
} /* sdb_plugin_load */

int
sdb_plugin_set_info(sdb_plugin_info_t *info, int type, ...)
{
	va_list ap;

	if (! info)
		return -1;

	va_start(ap, type);

	switch (type) {
		case SDB_PLUGIN_INFO_NAME:
			{
				char *name = va_arg(ap, char *);
				if (name) {
					if (info->name)
						free(info->name);
					info->name = strdup(name);
				}
			}
			break;
		case SDB_PLUGIN_INFO_DESC:<commit_msg>plugin: Split sdb_plugin_load() into multiple functions.<commit_after>/*
 * SysDB - src/core/plugin.c
 * Copyright (C) 2012 Sebastian 'tokkee' Harl <<EMAIL>>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#if HAVE_CONFIG_H
#	include "config.h"
#endif /* HAVE_CONFIG_H */

#include "sysdb.h"
#include "core/plugin.h"
#include "core/time.h"
#include "utils/error.h"
#include "utils/llist.h"
#include "utils/strbuf.h"

#include <assert.h>

#include <errno.h>

#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>

#include <ltdl.h>

#include <pthread.h>

/*
 * private data types
 */

struct sdb_plugin_info {
	char *plugin_name;
	char *filename;

	/* public attributes */
	char *name;

	char *description;
	char *copyright;
	char *license;

	int   version;
	int   plugin_version;
};
#define SDB_PLUGIN_INFO_INIT { \
	/* plugin_name */ NULL, /* filename */ NULL, \
	/* name */ NULL, /* desc */ NULL, \
	/* copyright */ NULL, /* license */ NULL, \
	/* version */ -1, /* plugin_version */ -1 }
#define INFO_GET(i, attr) \
	((i)->attr ? (i)->attr : #attr" not set")

typedef struct {
	sdb_object_t super;
	sdb_plugin_ctx_t public;

	sdb_plugin_info_t info;
	lt_dlhandle handle;

	/* The usage count differs from the object's ref count
	 * in that it provides higher level information about how
	 * the plugin is in use. */
	size_t use_cnt;
} ctx_t;
#define CTX_INIT { SDB_OBJECT_INIT, \
	SDB_PLUGIN_CTX_INIT, SDB_PLUGIN_INFO_INIT, NULL, 0 }

#define CTX(obj) ((ctx_t *)(obj))

typedef struct {
	sdb_object_t super;
	void *cb_callback;
	sdb_object_t *cb_user_data;
	ctx_t *cb_ctx;
} sdb_plugin_cb_t;
#define SDB_PLUGIN_CB_INIT { SDB_OBJECT_INIT, \
	/* callback = */ NULL, /* user_data = */ NULL, \
	SDB_PLUGIN_CTX_INIT }

typedef struct {
	sdb_plugin_cb_t super;
#define ccb_callback super.cb_callback
#define ccb_user_data super.cb_user_data
#define ccb_ctx super.cb_ctx
	sdb_time_t ccb_interval;
	sdb_time_t ccb_next_update;
} sdb_plugin_collector_cb_t;

#define SDB_PLUGIN_CB(obj) ((sdb_plugin_cb_t *)(obj))
#define SDB_CONST_PLUGIN_CB(obj) ((const sdb_plugin_cb_t *)(obj))
#define SDB_PLUGIN_CCB(obj) ((sdb_plugin_collector_cb_t *)(obj))
#define SDB_CONST_PLUGIN_CCB(obj) ((const sdb_plugin_collector_cb_t *)(obj))

/*
 * private variables
 */

static sdb_plugin_ctx_t  plugin_default_ctx  = SDB_PLUGIN_CTX_INIT;
static sdb_plugin_info_t plugin_default_info = SDB_PLUGIN_INFO_INIT;

static pthread_key_t     plugin_ctx_key;
static _Bool             plugin_ctx_key_initialized = 0;

/* a list of the plugin contexts of all registered plugins */
static sdb_llist_t      *all_plugins = NULL;

static sdb_llist_t      *config_list = NULL;
static sdb_llist_t      *init_list = NULL;
static sdb_llist_t      *collector_list = NULL;
static sdb_llist_t      *cname_list = NULL;
static sdb_llist_t      *shutdown_list = NULL;
static sdb_llist_t      *log_list = NULL;

/*
 * private helper functions
 */

static void
plugin_info_clear(sdb_plugin_info_t *info)
{
	sdb_plugin_info_t empty_info = SDB_PLUGIN_INFO_INIT;
	if (! info)
		return;

	if (info->plugin_name)
		free(info->plugin_name);
	if (info->filename)
		free(info->filename);

	if (info->name)
		free(info->name);
	if (info->description)
		free(info->description);
	if (info->copyright)
		free(info->copyright);
	if (info->license)
		free(info->license);

	*info = empty_info;
} /* plugin_info_clear */

static void
ctx_key_init(void)
{
	if (plugin_ctx_key_initialized)
		return;

	pthread_key_create(&plugin_ctx_key, /* destructor */ NULL);
	plugin_ctx_key_initialized = 1;
} /* ctx_key_init */

static int
plugin_cmp_next_update(const sdb_object_t *a, const sdb_object_t *b)
{
	const sdb_plugin_collector_cb_t *ccb1
		= (const sdb_plugin_collector_cb_t *)a;
	const sdb_plugin_collector_cb_t *ccb2
		= (const sdb_plugin_collector_cb_t *)b;

	assert(ccb1 && ccb2);

	return (ccb1->ccb_next_update > ccb2->ccb_next_update)
		? 1 : (ccb1->ccb_next_update < ccb2->ccb_next_update)
		? -1 : 0;
} /* plugin_cmp_next_update */

static int
plugin_lookup_by_name(const sdb_object_t *obj, const void *id)
{
	const sdb_plugin_cb_t *cb = SDB_CONST_PLUGIN_CB(obj);
	const char *name = id;

	assert(cb && id && cb->cb_ctx);
	if (!strcasecmp(cb->cb_ctx->info.plugin_name, name))
		return 0;
	return 1;
} /* plugin_lookup_by_name */

static void
plugin_unregister_by_name(const char *plugin_name)
{
	sdb_object_t *obj;
	size_t i;

	struct {
		const char  *type;
		sdb_llist_t *list;
	} all_lists[] = {
		{ "config",    config_list },
		{ "init",      init_list },
		{ "collector", collector_list },
		{ "cname",     cname_list },
		{ "shutdown",  shutdown_list },
		{ "log",       log_list },
	};

	for (i = 0; i < SDB_STATIC_ARRAY_LEN(all_lists); ++i) {
		const char  *type = all_lists[i].type;
		sdb_llist_t *list = all_lists[i].list;

		while (1) {
			sdb_plugin_cb_t *cb;

			cb = SDB_PLUGIN_CB(sdb_llist_remove(list,
						plugin_lookup_by_name, plugin_name));
			if (! cb)
				break;

			sdb_log(SDB_LOG_INFO, "core: Unregistering "
					"%s callback '%s' (module %s)", type, cb->super.name,
					cb->cb_ctx->info.plugin_name);
			sdb_object_deref(SDB_OBJ(cb));
		}
	}

	obj = sdb_llist_search_by_name(all_plugins, plugin_name);
	if (obj->ref_cnt <= 1)
		sdb_llist_remove_by_name(all_plugins, plugin_name);
	/* else: other callbacks still reference it */
	sdb_object_deref(obj);
} /* plugin_unregister_by_name */

/*
 * private types
 */

static int
ctx_init(sdb_object_t *obj, va_list __attribute__((unused)) ap)
{
	ctx_t *ctx = CTX(obj);

	assert(ctx);

	ctx->public = plugin_default_ctx;
	ctx->info = plugin_default_info;
	ctx->handle = NULL;
	ctx->use_cnt = 1;
	return 0;
} /* ctx_init */

static void
ctx_destroy(sdb_object_t *obj)
{
	ctx_t *ctx = CTX(obj);

	if (ctx->handle) {
		const char *err;

		sdb_log(SDB_LOG_INFO, "core: Unloading module %s",
				ctx->info.plugin_name);

		lt_dlerror();
		lt_dlclose(ctx->handle);
		if ((err = lt_dlerror()))
			sdb_log(SDB_LOG_WARNING, "core: Failed to unload module %s: %s",
					ctx->info.plugin_name, err);
	}

	plugin_info_clear(&ctx->info);
} /* ctx_destroy */

static sdb_type_t ctx_type = {
	sizeof(ctx_t),

	ctx_init,
	ctx_destroy
};

static ctx_t *
ctx_create(const char *name)
{
	ctx_t *ctx;

	ctx = CTX(sdb_object_create(name, ctx_type));
	if (! ctx)
		return NULL;

	if (! plugin_ctx_key_initialized)
		ctx_key_init();
	sdb_object_ref(SDB_OBJ(ctx));
	pthread_setspecific(plugin_ctx_key, ctx);
	return ctx;
} /* ctx_create */

static ctx_t *
ctx_get(void)
{
	if (! plugin_ctx_key_initialized)
		ctx_key_init();
	return pthread_getspecific(plugin_ctx_key);
} /* ctx_get */

static ctx_t *
ctx_set(ctx_t *new)
{
	ctx_t *old;

	if (! plugin_ctx_key_initialized)
		ctx_key_init();

	old = pthread_getspecific(plugin_ctx_key);
	if (old)
		sdb_object_deref(SDB_OBJ(old));
	if (new)
		sdb_object_ref(SDB_OBJ(new));
	pthread_setspecific(plugin_ctx_key, new);
	return old;
} /* ctx_set */

static int
plugin_cb_init(sdb_object_t *obj, va_list ap)
{
	sdb_llist_t **list = va_arg(ap, sdb_llist_t **);
	const char   *type = va_arg(ap, const char *);
	void     *callback = va_arg(ap, void *);
	sdb_object_t   *ud = va_arg(ap, sdb_object_t *);

	assert(list);
	assert(type);
	assert(obj);

	if (sdb_llist_search_by_name(*list, obj->name)) {
		sdb_log(SDB_LOG_WARNING, "core: %s callback '%s' "
				"has already been registered. Ignoring newly "
				"registered version.", type, obj->name);
		return -1;
	}

	SDB_PLUGIN_CB(obj)->cb_callback = callback;
	SDB_PLUGIN_CB(obj)->cb_ctx      = ctx_get();
	sdb_object_ref(SDB_OBJ(SDB_PLUGIN_CB(obj)->cb_ctx));

	sdb_object_ref(ud);
	SDB_PLUGIN_CB(obj)->cb_user_data = ud;
	return 0;
} /* plugin_cb_init */

static void
plugin_cb_destroy(sdb_object_t *obj)
{
	assert(obj);
	sdb_object_deref(SDB_PLUGIN_CB(obj)->cb_user_data);
	sdb_object_deref(SDB_OBJ(SDB_PLUGIN_CB(obj)->cb_ctx));
} /* plugin_cb_destroy */

static sdb_type_t sdb_plugin_cb_type = {
	sizeof(sdb_plugin_cb_t),

	plugin_cb_init,
	plugin_cb_destroy
};

static sdb_type_t sdb_plugin_collector_cb_type = {
	sizeof(sdb_plugin_collector_cb_t),

	plugin_cb_init,
	plugin_cb_destroy
};

static int
module_init(const char *name, lt_dlhandle lh, sdb_plugin_info_t *info)
{
	int (*mod_init)(sdb_plugin_info_t *);
	int status;

	mod_init = (int (*)(sdb_plugin_info_t *))lt_dlsym(lh, "sdb_module_init");
	if (! mod_init) {
		sdb_log(SDB_LOG_ERR, "core: Failed to load plugin '%s': "
				"could not find symbol 'sdb_module_init'", name);
		return -1;
	}

	status = mod_init(info);
	if (status) {
		sdb_log(SDB_LOG_ERR, "core: Failed to initialize "
				"module '%s'", name);
		plugin_unregister_by_name(name);
		return -1;
	}
	return 0;
} /* module_init */

static int
module_load(const char *name, const sdb_plugin_ctx_t *plugin_ctx)
{
	char  base_name[name ? strlen(name) + 1 : 1];
	const char *name_ptr;
	char *tmp;

	char filename[1024];
	lt_dlhandle lh;

	ctx_t *ctx;

	int status;

	base_name[0] = '\0';
	name_ptr = name;

	while ((tmp = strstr(name_ptr, "::"))) {
		strncat(base_name, name_ptr, (size_t)(tmp - name_ptr));
		strcat(base_name, "/");
		name_ptr = tmp + strlen("::");
	}
	strcat(base_name, name_ptr);

	snprintf(filename, sizeof(filename), "%s/%s.so",
			PKGLIBDIR, base_name);
	filename[sizeof(filename) - 1] = '\0';

	if (access(filename, R_OK)) {
		char errbuf[1024];
		sdb_log(SDB_LOG_ERR, "core: Failed to load plugin '%s' (%s): %s",
				name, filename, sdb_strerror(errno, errbuf, sizeof(errbuf)));
		return -1;
	}

	lt_dlinit();
	lt_dlerror();

	lh = lt_dlopen(filename);
	if (! lh) {
		sdb_log(SDB_LOG_ERR, "core: Failed to load plugin '%s': %s"
				"The most common cause for this problem are missing "
				"dependencies.\n", name, lt_dlerror());
		return -1;
	}

	if (ctx_get())
		sdb_log(SDB_LOG_WARNING, "core: Discarding old plugin context");

	ctx = ctx_create(name);
	if (! ctx) {
		sdb_log(SDB_LOG_ERR, "core: Failed to initialize plugin context");
		return -1;
	}

	ctx->info.plugin_name = strdup(name);
	ctx->info.filename = strdup(filename);
	ctx->handle = lh;

	if (plugin_ctx)
		ctx->public = *plugin_ctx;

	if ((status = module_init(name, lh, &ctx->info))) {
		sdb_object_deref(SDB_OBJ(ctx));
		return status;
	}

	/* compare minor version */
	if ((ctx->info.version < 0)
			|| ((int)(ctx->info.version / 100) != (int)(SDB_VERSION / 100)))
		sdb_log(SDB_LOG_WARNING, "core: WARNING: version of "
				"plugin '%s' (%i.%i.%i) does not match our version "
				"(%i.%i.%i); this might cause problems",
				name, SDB_VERSION_DECODE(ctx->info.version),
				SDB_VERSION_DECODE(SDB_VERSION));

	sdb_llist_append(all_plugins, SDB_OBJ(ctx));

	sdb_log(SDB_LOG_INFO, "core: Successfully loaded "
			"plugin '%s' v%i (%s)\n\t%s\n\tLicense: %s",
			INFO_GET(&ctx->info, name), ctx->info.plugin_version,
			INFO_GET(&ctx->info, description),
			INFO_GET(&ctx->info, copyright),
			INFO_GET(&ctx->info, license));

	/* any registered callbacks took ownership of the context */
	sdb_object_deref(SDB_OBJ(ctx));

	/* reset */
	ctx_set(NULL);
	return 0;
} /* module_load */

static int
plugin_add_callback(sdb_llist_t **list, const char *type,
		const char *name, void *callback, sdb_object_t *user_data)
{
	sdb_object_t *obj;

	if ((! name) || (! callback))
		return -1;

	assert(list);

	if (! *list)
		*list = sdb_llist_create();
	if (! *list)
		return -1;

	obj = sdb_object_create(name, sdb_plugin_cb_type,
			list, type, callback, user_data);
	if (! obj)
		return -1;

	if (sdb_llist_append(*list, obj)) {
		sdb_object_deref(obj);
		return -1;
	}

	/* pass control to the list */
	sdb_object_deref(obj);

	sdb_log(SDB_LOG_INFO, "core: Registered %s callback '%s'.",
			type, name);
	return 0;
} /* plugin_add_callback */

/*
 * public API
 */

int
sdb_plugin_load(const char *name, const sdb_plugin_ctx_t *plugin_ctx)
{
	ctx_t *ctx;

	if ((! name) || (! *name))
		return -1;

	if (! all_plugins) {
		if (! (all_plugins = sdb_llist_create())) {
			sdb_log(SDB_LOG_ERR, "core: Failed to load plugin '%s': "
					"internal error while creating linked list", name);
			return -1;
		}
	}

	ctx = CTX(sdb_llist_search_by_name(all_plugins, name));
	if (ctx) {
		/* plugin already loaded */
		++ctx->use_cnt;
		return 0;
	}

	return module_load(name, plugin_ctx);
} /* sdb_plugin_load */

int
sdb_plugin_set_info(sdb_plugin_info_t *info, int type, ...)
{
	va_list ap;

	if (! info)
		return -1;

	va_start(ap, type);

	switch (type) {
		case SDB_PLUGIN_INFO_NAME:
			{
				char *name = va_arg(ap, char *);
				if (name) {
					if (info->name)
						free(info->name);
					info->name = strdup(name);
				}
			}
			break;
		case SDB_PLUGIN_INFO_DESC:
{'fraction_non_alphanumeric': 0.1341764791394098, 'fraction_numerical': 0.004906236371565635, 'mean_word_length': 4.359758472925594, 'pattern_counts': {'":': 4, '<': 33, '<?xml version=': 0, '>': 147, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2924458', 'n_tokens_mistral': 11530, 'n_tokens_neox': 10733, 'n_words': 2422}