summaryrefslogtreecommitdiffstats
path: root/src/plugins.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/plugins.c550
1 files changed, 550 insertions, 0 deletions
diff --git a/src/plugins.c b/src/plugins.c
new file mode 100644
index 0000000..d62da1c
--- /dev/null
+++ b/src/plugins.c
@@ -0,0 +1,550 @@
+/**
+ * @file plugins.c
+ * @author Radek Krejci <rkrejci@cesnet.cz>
+ * @brief Manipulate with the type and extension plugins.
+ *
+ * Copyright (c) 2021 CESNET, z.s.p.o.
+ *
+ * This source code is licensed under BSD 3-Clause License (the "License").
+ * You may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ */
+
+#define _GNU_SOURCE
+
+#include "plugins.h"
+#include "plugins_internal.h"
+
+#include <assert.h>
+#include <dirent.h>
+#include <dlfcn.h>
+#include <errno.h>
+#include <limits.h>
+#include <pthread.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "common.h"
+#include "config.h"
+#include "plugins_exts.h"
+#include "plugins_types.h"
+#include "set.h"
+
+/*
+ * internal type plugins records
+ */
+extern const struct lyplg_type_record plugins_binary[];
+extern const struct lyplg_type_record plugins_bits[];
+extern const struct lyplg_type_record plugins_boolean[];
+extern const struct lyplg_type_record plugins_decimal64[];
+extern const struct lyplg_type_record plugins_empty[];
+extern const struct lyplg_type_record plugins_enumeration[];
+extern const struct lyplg_type_record plugins_identityref[];
+extern const struct lyplg_type_record plugins_instanceid[];
+extern const struct lyplg_type_record plugins_integer[];
+extern const struct lyplg_type_record plugins_leafref[];
+extern const struct lyplg_type_record plugins_string[];
+extern const struct lyplg_type_record plugins_union[];
+
+/*
+ * yang
+ */
+extern const struct lyplg_type_record plugins_instanceid_keys[];
+
+/*
+ * ietf-inet-types
+ */
+extern const struct lyplg_type_record plugins_ipv4_address[];
+extern const struct lyplg_type_record plugins_ipv4_address_no_zone[];
+extern const struct lyplg_type_record plugins_ipv6_address[];
+extern const struct lyplg_type_record plugins_ipv6_address_no_zone[];
+extern const struct lyplg_type_record plugins_ipv4_prefix[];
+extern const struct lyplg_type_record plugins_ipv6_prefix[];
+
+/*
+ * ietf-yang-types
+ */
+extern const struct lyplg_type_record plugins_date_and_time[];
+extern const struct lyplg_type_record plugins_xpath10[];
+
+/*
+ * ietf-netconf-acm
+ */
+extern const struct lyplg_type_record plugins_node_instanceid[];
+
+/*
+ * internal extension plugins records
+ */
+extern struct lyplg_ext_record plugins_metadata[];
+extern struct lyplg_ext_record plugins_nacm[];
+extern struct lyplg_ext_record plugins_yangdata[];
+extern struct lyplg_ext_record plugins_schema_mount[];
+extern struct lyplg_ext_record plugins_structure[];
+
+static pthread_mutex_t plugins_guard = PTHREAD_MUTEX_INITIALIZER;
+
+/**
+ * @brief Counter for currently present contexts able to refer to the loaded plugins.
+ *
+ * Plugins are shared among all the created contexts. They are loaded with the creation of the very first context and
+ * unloaded with the destroy of the last context. Therefore, to reload the list of plugins, all the contexts must be
+ * destroyed and with the creation of a first new context after that, the plugins will be reloaded.
+ */
+static uint32_t context_refcount = 0;
+
+/**
+ * @brief Record describing an implemented extension.
+ *
+ * Matches ::lyplg_ext_record and ::lyplg_type_record
+ */
+struct lyplg_record {
+ const char *module; /**< name of the module where the extension/type is defined */
+ const char *revision; /**< optional module revision - if not specified, the plugin applies to any revision,
+ which is not an optimal approach due to a possible future revisions of the module.
+ Instead, there should be defined multiple items in the plugins list, each with the
+ different revision, but all with the same pointer to the plugin functions. The
+ only valid use case for the NULL revision is the case the module has no revision. */
+ const char *name; /**< name of the extension/typedef */
+ int8_t plugin[]; /**< specific plugin type's data - ::lyplg_ext or ::lyplg_type */
+};
+
+#ifndef STATIC
+static struct ly_set plugins_handlers = {0};
+#endif
+static struct ly_set plugins_types = {0};
+static struct ly_set plugins_extensions = {0};
+
+/**
+ * @brief Iterate over list of loaded plugins of the given @p type.
+ *
+ * @param[in] type Type of the plugins to iterate.
+ * @param[in,out] index The iterator - set to 0 for the first call.
+ * @return The plugin records, NULL if no more record is available.
+ */
+static struct lyplg_record *
+plugins_iter(enum LYPLG type, uint32_t *index)
+{
+ struct ly_set *plugins;
+
+ assert(index);
+
+ if (type == LYPLG_EXTENSION) {
+ plugins = &plugins_extensions;
+ } else {
+ plugins = &plugins_types;
+ }
+
+ if (*index == plugins->count) {
+ return NULL;
+ }
+
+ *index += 1;
+ return plugins->objs[*index - 1];
+}
+
+static void *
+lyplg_record_find(enum LYPLG type, const char *module, const char *revision, const char *name)
+{
+ uint32_t i = 0;
+ struct lyplg_record *item;
+
+ assert(module);
+ assert(name);
+
+ while ((item = plugins_iter(type, &i)) != NULL) {
+ if (!strcmp(item->module, module) && !strcmp(item->name, name)) {
+ if (item->revision && revision && strcmp(item->revision, revision)) {
+ continue;
+ } else if (!revision && item->revision) {
+ continue;
+ }
+
+ return item;
+ }
+ }
+
+ return NULL;
+}
+
+struct lyplg_type *
+lyplg_type_plugin_find(const char *module, const char *revision, const char *name)
+{
+ struct lyplg_record *record;
+
+ record = lyplg_record_find(LYPLG_TYPE, module, revision, name);
+ return record ? &((struct lyplg_type_record *)record)->plugin : NULL;
+}
+
+struct lyplg_ext_record *
+lyplg_ext_record_find(const char *module, const char *revision, const char *name)
+{
+ return lyplg_record_find(LYPLG_EXTENSION, module, revision, name);
+}
+
+/**
+ * @brief Insert the provided extension plugin records into the internal set of extension plugins for use by libyang.
+ *
+ * @param[in] recs An array of plugin records provided by the plugin implementation. The array must be terminated by a zeroed
+ * record.
+ * @return LY_SUCCESS in case of success
+ * @return LY_EINVAL for invalid information in @p recs.
+ * @return LY_EMEM in case of memory allocation failure.
+ */
+static LY_ERR
+plugins_insert(enum LYPLG type, const void *recs)
+{
+ if (!recs) {
+ return LY_SUCCESS;
+ }
+
+ if (type == LYPLG_EXTENSION) {
+ const struct lyplg_ext_record *rec = (const struct lyplg_ext_record *)recs;
+
+ for (uint32_t i = 0; rec[i].name; i++) {
+ LY_CHECK_RET(ly_set_add(&plugins_extensions, (void *)&rec[i], 0, NULL));
+ }
+ } else { /* LYPLG_TYPE */
+ const struct lyplg_type_record *rec = (const struct lyplg_type_record *)recs;
+
+ for (uint32_t i = 0; rec[i].name; i++) {
+ LY_CHECK_RET(ly_set_add(&plugins_types, (void *)&rec[i], 0, NULL));
+ }
+ }
+
+ return LY_SUCCESS;
+}
+
+#ifndef STATIC
+
+static void
+lyplg_close_cb(void *handle)
+{
+ dlclose(handle);
+}
+
+static void
+lyplg_clean_(void)
+{
+ if (--context_refcount) {
+ /* there is still some other context, do not remove the plugins */
+ return;
+ }
+
+ ly_set_erase(&plugins_types, NULL);
+ ly_set_erase(&plugins_extensions, NULL);
+ ly_set_erase(&plugins_handlers, lyplg_close_cb);
+}
+
+#endif
+
+void
+lyplg_clean(void)
+{
+#ifndef STATIC
+ pthread_mutex_lock(&plugins_guard);
+ lyplg_clean_();
+ pthread_mutex_unlock(&plugins_guard);
+#endif
+}
+
+#ifndef STATIC
+
+/**
+ * @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 */
+ const char *envdir; /**< environment variable containing directory with the plugins */
+ const char *dir; /**< default directory with the plugins (has less priority than envdir) */
+ uint32_t apiver; /**< expected API version */
+} plugins_load_info[] = {
+ { /* LYPLG_TYPE */
+ .id = "type",
+ .apiver_var = "plugins_types_apiver__",
+ .plugins_var = "plugins_types__",
+ .envdir = "LIBYANG_TYPES_PLUGINS_DIR",
+ .dir = LYPLG_TYPE_DIR,
+ .apiver = LYPLG_TYPE_API_VERSION
+ }, {/* LYPLG_EXTENSION */
+ .id = "extension",
+ .apiver_var = "plugins_extensions_apiver__",
+ .plugins_var = "plugins_extensions__",
+ .envdir = "LIBYANG_EXTENSIONS_PLUGINS_DIR",
+ .dir = LYPLG_EXT_DIR,
+ .apiver = LYPLG_EXT_API_VERSION
+ }
+};
+
+/**
+ * @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;
+}
+
+static LY_ERR
+plugins_insert_dir(enum LYPLG type)
+{
+ LY_ERR ret = LY_SUCCESS;
+ const char *pluginsdir;
+ DIR *dir;
+ ly_bool default_dir = 0;
+
+ /* try to get the plugins directory from environment variable */
+ pluginsdir = getenv(plugins_load_info[type].envdir);
+ if (!pluginsdir) {
+ /* remember that we are going to a default dir and do not print warning if the directory doesn't exist */
+ default_dir = 1;
+ pluginsdir = plugins_load_info[type].dir;
+ }
+
+ dir = opendir(pluginsdir);
+ if (!dir) {
+ /* no directory (or no access to it), no extension plugins */
+ if (!default_dir || (errno != ENOENT)) {
+ LOGWRN(NULL, "Failed to open libyang %s plugins directory \"%s\" (%s).", plugins_load_info[type].id,
+ pluginsdir, strerror(errno));
+ }
+ } else {
+ struct dirent *file;
+
+ while ((file = readdir(dir))) {
+ size_t len;
+ char pathname[PATH_MAX];
+
+ /* required format of the filename is *LYPLG_SUFFIX */
+ len = strlen(file->d_name);
+ if ((len < LYPLG_SUFFIX_LEN + 1) || strcmp(&file->d_name[len - LYPLG_SUFFIX_LEN], LYPLG_SUFFIX)) {
+ continue;
+ }
+
+ /* and construct the filepath */
+ snprintf(pathname, PATH_MAX, "%s/%s", pluginsdir, file->d_name);
+
+ ret = plugins_load_module(pathname);
+ if (ret) {
+ break;
+ }
+ }
+ closedir(dir);
+ }
+
+ return ret;
+}
+
+#endif
+
+LY_ERR
+lyplg_init(void)
+{
+ LY_ERR ret;
+
+ pthread_mutex_lock(&plugins_guard);
+ /* let only the first context to initiate plugins, but let others wait for finishing the initiation */
+ if (context_refcount++) {
+ /* already initiated */
+ pthread_mutex_unlock(&plugins_guard);
+ return LY_SUCCESS;
+ }
+
+ /* internal types */
+ LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_binary), error);
+ LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_bits), error);
+ LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_boolean), error);
+ LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_decimal64), error);
+ LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_empty), error);
+ LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_enumeration), error);
+ LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_identityref), error);
+ LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_instanceid), error);
+ LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_integer), error);
+ LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_leafref), error);
+ LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_string), error);
+ LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_union), error);
+
+ /* yang */
+ LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_instanceid_keys), error);
+
+ /* ietf-inet-types */
+ LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_ipv4_address), error);
+ LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_ipv4_address_no_zone), error);
+ LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_ipv6_address), error);
+ LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_ipv6_address_no_zone), error);
+ LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_ipv4_prefix), error);
+ LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_ipv6_prefix), error);
+
+ /* ietf-yang-types */
+ LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_date_and_time), error);
+ LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_xpath10), error);
+
+ /* ietf-netconf-acm */
+ LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_node_instanceid), error);
+
+ /* internal extensions */
+ LY_CHECK_GOTO(ret = plugins_insert(LYPLG_EXTENSION, plugins_metadata), error);
+ LY_CHECK_GOTO(ret = plugins_insert(LYPLG_EXTENSION, plugins_nacm), error);
+ LY_CHECK_GOTO(ret = plugins_insert(LYPLG_EXTENSION, plugins_yangdata), error);
+ LY_CHECK_GOTO(ret = plugins_insert(LYPLG_EXTENSION, plugins_schema_mount), error);
+ LY_CHECK_GOTO(ret = plugins_insert(LYPLG_EXTENSION, plugins_structure), error);
+
+#ifndef STATIC
+ /* external types */
+ LY_CHECK_GOTO(ret = plugins_insert_dir(LYPLG_TYPE), error);
+
+ /* external extensions */
+ LY_CHECK_GOTO(ret = plugins_insert_dir(LYPLG_EXTENSION), error);
+#endif
+
+ /* initiation done, wake-up possibly waiting threads creating another contexts */
+ pthread_mutex_unlock(&plugins_guard);
+
+ return LY_SUCCESS;
+
+error:
+ /* initiation was not successful - cleanup (and let others to try) */
+#ifndef STATIC
+ lyplg_clean_();
+#endif
+ pthread_mutex_unlock(&plugins_guard);
+
+ if (ret == LY_EINVAL) {
+ /* all the plugins here are internal, invalid record actually means an internal libyang error */
+ ret = LY_EINT;
+ }
+ return ret;
+}
+
+LIBYANG_API_DEF LY_ERR
+lyplg_add(const char *pathname)
+{
+#ifdef STATIC
+ (void)pathname;
+
+ LOGERR(NULL, LY_EINVAL, "Plugins are not supported in statically built library.");
+ return LY_EINVAL;
+#elif defined (_WIN32)
+ (void)pathname;
+
+ LOGERR(NULL, LY_EINVAL, "Plugins are not (yet) supported on Windows.");
+ return LY_EINVAL;
+#else
+ 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;
+#endif
+}