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;
+}