plugins FEATURE manual loading of external plugins
Add lyplg_add() function to manually load external plugins (both
types and extensions) from a specific dynamic library.
diff --git a/src/plugins.c b/src/plugins.c
index fa243c8..e378773 100644
--- a/src/plugins.c
+++ b/src/plugins.c
@@ -18,6 +18,7 @@
#include <assert.h>
#include <dlfcn.h>
#include <pthread.h>
+#include <stddef.h>
#include <string.h>
#include "common.h"
@@ -74,10 +75,35 @@
int8_t plugin[]; /**< specific plugin type's data - ::lyplg_ext or ::lyplg_type */
};
+static struct ly_set plugins_handlers = {0};
static struct ly_set plugins_types = {0};
static struct ly_set plugins_extensions = {0};
/**
+ * @brief Just a variadic data to cover extension and type plugins by a single ::plugins_load() function.
+ *
+ * The values are taken from ::LY_PLUGINS_EXTENSIONS and ::LYPLG_TYPES macros.
+ */
+static const struct {
+ const char *id; /**< string identifier: type/extension */
+ const char *apiver_var; /**< expected variable name holding API version value */
+ const char *plugins_var; /**< expected variable name holding plugin records */
+ uint32_t apiver; /**< expected API version */
+} plugins_load_info[] = {
+ { /* LYPLG_TYPE */
+ .id = "type",
+ .apiver_var = "plugins_types_apiver__",
+ .plugins_var = "plugins_types__",
+ .apiver = LYPLG_TYPE_API_VERSION
+ }, {/* LYPLG_EXTENSION */
+ .id = "extension",
+ .apiver_var = "plugins_extensions_apiver__",
+ .plugins_var = "plugins_extensions__",
+ .apiver = LYPLG_EXT_API_VERSION
+ }
+};
+
+/**
* @brief Iterate over list of loaded plugins of the given @p type.
*
* @param[in] type Type of the plugins to iterate.
@@ -151,7 +177,7 @@
for (uint32_t i = 0; rec[i].name; i++) {
LY_CHECK_RET(ly_set_add(&plugins_extensions, (void *)&rec[i], 0, NULL));
}
- } else { /* LY_PLUGIN_TYPE */
+ } else { /* LYPLG_TYPE */
const struct lyplg_type_record *rec = (const struct lyplg_type_record *)recs;
for (uint32_t i = 0; rec[i].name; i++) {
@@ -172,6 +198,7 @@
ly_set_erase(&plugins_types, NULL);
ly_set_erase(&plugins_extensions, NULL);
+ ly_set_erase(&plugins_handlers, (void(*)(void *))dlclose);
}
void
@@ -182,6 +209,104 @@
pthread_mutex_unlock(&plugins_guard);
}
+/**
+ * @brief Get the expected plugin objects from the loaded dynamic object and add the defined plugins into the lists of
+ * available extensions/types plugins.
+ *
+ * @param[in] dlhandler Loaded dynamic library handler.
+ * @param[in] pathname Path of the loaded library for logging.
+ * @param[in] type Type of the plugins to get from the dynamic library. Note that a single library can hold both types
+ * and extensions plugins implementations, so this function should be called twice (once for each plugin type) with
+ * different @p type values
+ * @return LY_ERR values.
+ */
+static LY_ERR
+plugins_load(void *dlhandler, const char *pathname, enum LYPLG type)
+{
+ const void *plugins;
+ uint32_t *version;
+
+ /* type plugin */
+ version = dlsym(dlhandler, plugins_load_info[type].apiver_var);
+ if (version) {
+ /* check version ... */
+ if (*version != plugins_load_info[type].apiver) {
+ LOGERR(NULL, LY_EINVAL, "Processing user %s plugin \"%s\" failed, wrong API version - %d expected, %d found.",
+ plugins_load_info[type].id, pathname, plugins_load_info[type].apiver, *version);
+ return LY_EINVAL;
+ }
+
+ /* ... get types plugins information ... */
+ if (!(plugins = dlsym(dlhandler, plugins_load_info[type].plugins_var))) {
+ char *errstr = dlerror();
+ LOGERR(NULL, LY_EINVAL, "Processing user %s plugin \"%s\" failed, missing %s plugins information (%s).",
+ plugins_load_info[type].id, pathname, plugins_load_info[type].id, errstr);
+ return LY_EINVAL;
+ }
+
+ /* ... and load all the types plugins */
+ LY_CHECK_RET(plugins_insert(type, plugins));
+ }
+
+ return LY_SUCCESS;
+}
+
+static LY_ERR
+plugins_load_module(const char *pathname)
+{
+ LY_ERR ret = LY_SUCCESS;
+ void *dlhandler;
+ uint32_t types_count = 0, extensions_count = 0;
+
+ dlerror(); /* Clear any existing error */
+
+ dlhandler = dlopen(pathname, RTLD_NOW);
+ if (!dlhandler) {
+ LOGERR(NULL, LY_ESYS, "Loading \"%s\" as a plugin failed (%s).", pathname, dlerror());
+ return LY_ESYS;
+ }
+
+ if (ly_set_contains(&plugins_handlers, dlhandler, NULL)) {
+ /* the plugin is already loaded */
+ LOGVRB("Plugin \"%s\" already loaded.", pathname);
+
+ /* keep the correct refcount */
+ dlclose(dlhandler);
+ return LY_SUCCESS;
+ }
+
+ /* remember the current plugins lists for recovery */
+ types_count = plugins_types.count;
+ extensions_count = plugins_extensions.count;
+
+ /* type plugin */
+ ret = plugins_load(dlhandler, pathname, LYPLG_TYPE);
+ LY_CHECK_GOTO(ret, error);
+
+ /* extension plugin */
+ ret = plugins_load(dlhandler, pathname, LYPLG_EXTENSION);
+ LY_CHECK_GOTO(ret, error);
+
+ /* remember the dynamic plugin */
+ ret = ly_set_add(&plugins_handlers, dlhandler, 1, NULL);
+ LY_CHECK_GOTO(ret, error);
+
+ return LY_SUCCESS;
+
+error:
+ dlclose(dlhandler);
+
+ /* revert changes in the lists */
+ while (plugins_types.count > types_count) {
+ ly_set_rm_index(&plugins_types, plugins_types.count - 1, NULL);
+ }
+ while (plugins_extensions.count > extensions_count) {
+ ly_set_rm_index(&plugins_extensions, plugins_extensions.count - 1, NULL);
+ }
+
+ return ret;
+}
+
LY_ERR
lyplg_init(void)
{
@@ -230,3 +355,26 @@
}
return ret;
}
+
+API LY_ERR
+lyplg_add(const char *pathname)
+{
+ LY_ERR ret = LY_SUCCESS;
+
+ LY_CHECK_ARG_RET(NULL, pathname, LY_EINVAL);
+
+ /* works only in case a context exists */
+ pthread_mutex_lock(&plugins_guard);
+ if (!context_refcount) {
+ /* no context */
+ pthread_mutex_unlock(&plugins_guard);
+ LOGERR(NULL, LY_EDENIED, "To add a plugin, at least one context must exists.");
+ return LY_EDENIED;
+ }
+
+ ret = plugins_load_module(pathname);
+
+ pthread_mutex_unlock(&plugins_guard);
+
+ return ret;
+}
diff --git a/src/plugins.h b/src/plugins.h
index 7baa00f..13ed304 100644
--- a/src/plugins.h
+++ b/src/plugins.h
@@ -35,6 +35,22 @@
LYPLG_EXTENSION /**< YANG extension */
};
+/**
+ * @brief Manually load a plugin file.
+ *
+ * Note, that a plugin can be loaded only if there is at least one context. The loaded plugins are connected with the
+ * existence of a context. When all the contexts are destroyed, all the plugins are unloaded.
+ *
+ * @param[in] pathname Path to the plugin file. It can contain types or extensions plugins, both are accepted and correctly
+ * loaded.
+ *
+ * @return LY_SUCCESS if the file contains valid plugin compatible with the library version.
+ * @return LY_EDENIED in case there is no context and the plugin cannot be loaded.
+ * @return LY_EINVAL when pathname is NULL or the plugin contains invalid content for this libyang version.
+ * @return LY_ESYS when the plugin file cannot be loaded.
+ */
+LY_ERR lyplg_add(const char *pathname);
+
/** @} plugins */
#ifdef __cplusplus
diff --git a/src/schema_compile_node.c b/src/schema_compile_node.c
index 24187ad..b2cc259 100644
--- a/src/schema_compile_node.c
+++ b/src/schema_compile_node.c
@@ -1738,6 +1738,29 @@
return ret;
}
+/**
+ * @brief Find the correct plugin implementing the described type
+ *
+ * @param[in] mod Module where the type is defined
+ * @param[in] name Name of the type (typedef)
+ * @param[in] basetype Type's basetype (when the built-in base plugin is supposed to be used)
+ * @return Pointer to the plugin implementing the described data type.
+ */
+static struct lyplg_type *
+lys_compile_type_get_plugin(struct lys_module *mod, const char *name, LY_DATA_TYPE basetype)
+{
+ struct lyplg_type *p;
+
+ /* try to find loaded user type plugins */
+ p = lyplg_find(LYPLG_TYPE, mod->name, mod->revision, name);
+ if (!p) {
+ /* use the internal built-in type implementation */
+ p = lyplg_find(LYPLG_TYPE, "", NULL, ly_data_type2str[basetype]);
+ }
+
+ return p;
+}
+
LY_ERR
lys_compile_type(struct lysc_ctx *ctx, struct lysp_node *context_pnode, uint16_t context_flags, const char *context_name,
struct lysp_type *type_p, struct lysc_type **type, const char **units, struct lysp_qname **dflt)
@@ -1937,8 +1960,7 @@
}
(*type)->basetype = basetype;
- /* TODO user type plugins */
- (*type)->plugin = lyplg_find(LYPLG_TYPE, "", NULL, ly_data_type2str[basetype]);
+ (*type)->plugin = lys_compile_type_get_plugin(tctx->tpdf->type.pmod->mod, tctx->tpdf->name, basetype);
prev_type = *type;
ret = lys_compile_type_(ctx, tctx->node, tctx->tpdf->flags, tctx->tpdf->name,
&((struct lysp_tpdf *)tctx->tpdf)->type, basetype, tctx->tpdf->name, base, type);
@@ -1952,8 +1974,7 @@
if (type_p->flags || !base || (basetype == LY_TYPE_LEAFREF)) {
/* get restrictions from the node itself */
(*type)->basetype = basetype;
- /* TODO user type plugins */
- (*type)->plugin = lyplg_find(LYPLG_TYPE, "", NULL, ly_data_type2str[basetype]);
+ (*type)->plugin = base ? base->plugin : lyplg_find(LYPLG_TYPE, "", NULL, ly_data_type2str[basetype]);
++(*type)->refcount;
ret = lys_compile_type_(ctx, context_pnode, context_flags, context_name, type_p, basetype, NULL, base, type);
LY_CHECK_GOTO(ret, cleanup);
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 8322351..8735388 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -24,7 +24,7 @@
# Set common attributes of all tests
set_target_properties(${TEST_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/tests")
- target_link_libraries(${TEST_NAME} ${CMOCKA_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ${PCRE2_LIBRARIES} m)
+ target_link_libraries(${TEST_NAME} ${CMOCKA_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ${PCRE2_LIBRARIES} ${CMAKE_DL_LIBS} m)
if (NOT APPLE)
if (ADDTEST_WRAP)
set_target_properties(${TEST_NAME} PROPERTIES LINK_FLAGS "${ADDTEST_WRAP}")
diff --git a/tests/ld.supp b/tests/ld.supp
index 53876b6..e501a3b 100644
--- a/tests/ld.supp
+++ b/tests/ld.supp
@@ -1,10 +1,8 @@
{
- <insert_a_suppression_name_here>
+ dl's thread-local data
Memcheck:Leak
match-leak-kinds: reachable
fun:calloc
fun:_dlerror_run
fun:dlopen@@GLIBC_2.2.5
- fun:lyext_load_plugins
- fun:ly_ctx_new
}