blob: e3787739414f18d0b69310a434cd7f86a3ff8667 [file] [log] [blame]
Radek Krejci3e6632f2021-03-22 22:08:21 +01001/**
2 * @file plugins.c
3 * @author Radek Krejci <rkrejci@cesnet.cz>
4 * @brief Manipulate with the type and extension plugins.
5 *
6 * Copyright (c) 2021 CESNET, z.s.p.o.
7 *
8 * This source code is licensed under BSD 3-Clause License (the "License").
9 * You may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
11 *
12 * https://opensource.org/licenses/BSD-3-Clause
13 */
14
15#include "plugins.h"
16#include "plugins_internal.h"
17
18#include <assert.h>
19#include <dlfcn.h>
20#include <pthread.h>
Radek Krejcibf940f92021-03-24 21:04:13 +010021#include <stddef.h>
Radek Krejci3e6632f2021-03-22 22:08:21 +010022#include <string.h>
23
24#include "common.h"
25#include "plugins_exts.h"
26#include "plugins_types.h"
27
28/*
29 * internal type plugins records
30 */
31extern const struct lyplg_type_record plugins_binary[];
32extern const struct lyplg_type_record plugins_bits[];
33extern const struct lyplg_type_record plugins_boolean[];
34extern const struct lyplg_type_record plugins_decimal64[];
35extern const struct lyplg_type_record plugins_empty[];
36extern const struct lyplg_type_record plugins_enumeration[];
37extern const struct lyplg_type_record plugins_identityref[];
38extern const struct lyplg_type_record plugins_instanceid[];
39extern const struct lyplg_type_record plugins_integer[];
40extern const struct lyplg_type_record plugins_leafref[];
41extern const struct lyplg_type_record plugins_string[];
42extern const struct lyplg_type_record plugins_union[];
43
44/*
45 * internal extension plugins records
46 */
47extern struct lyplg_ext_record plugins_metadata[];
48extern struct lyplg_ext_record plugins_nacm[];
49extern struct lyplg_ext_record plugins_yangdata[];
50
51static pthread_mutex_t plugins_guard = PTHREAD_MUTEX_INITIALIZER;
52
53/**
54 * @brief Counter for currently present contexts able to refer to the loaded plugins.
55 *
56 * Plugins are shared among all the created contexts. They are loaded with the creation of the very first context and
57 * unloaded with the destroy of the last context. Therefore, to reload the list of plugins, all the contexts must be
58 * destroyed and with the creation of a first new context after that, the plugins will be reloaded.
59 */
60static uint32_t context_refcount = 0;
61
62/**
63 * @brief Record describing an implemented extension.
64 *
65 * Matches ::lyplg_ext_record and ::lyplg_type_record
66 */
67struct lyplg_record {
68 const char *module; /**< name of the module where the extension/type is defined */
69 const char *revision; /**< optional module revision - if not specified, the plugin applies to any revision,
70 which is not an optimal approach due to a possible future revisions of the module.
71 Instead, there should be defined multiple items in the plugins list, each with the
72 different revision, but all with the same pointer to the plugin functions. The
73 only valid use case for the NULL revision is the case the module has no revision. */
74 const char *name; /**< name of the extension/typedef */
75 int8_t plugin[]; /**< specific plugin type's data - ::lyplg_ext or ::lyplg_type */
76};
77
Radek Krejcibf940f92021-03-24 21:04:13 +010078static struct ly_set plugins_handlers = {0};
Radek Krejci3e6632f2021-03-22 22:08:21 +010079static struct ly_set plugins_types = {0};
80static struct ly_set plugins_extensions = {0};
81
82/**
Radek Krejcibf940f92021-03-24 21:04:13 +010083 * @brief Just a variadic data to cover extension and type plugins by a single ::plugins_load() function.
84 *
85 * The values are taken from ::LY_PLUGINS_EXTENSIONS and ::LYPLG_TYPES macros.
86 */
87static const struct {
88 const char *id; /**< string identifier: type/extension */
89 const char *apiver_var; /**< expected variable name holding API version value */
90 const char *plugins_var; /**< expected variable name holding plugin records */
91 uint32_t apiver; /**< expected API version */
92} plugins_load_info[] = {
93 { /* LYPLG_TYPE */
94 .id = "type",
95 .apiver_var = "plugins_types_apiver__",
96 .plugins_var = "plugins_types__",
97 .apiver = LYPLG_TYPE_API_VERSION
98 }, {/* LYPLG_EXTENSION */
99 .id = "extension",
100 .apiver_var = "plugins_extensions_apiver__",
101 .plugins_var = "plugins_extensions__",
102 .apiver = LYPLG_EXT_API_VERSION
103 }
104};
105
106/**
Radek Krejci3e6632f2021-03-22 22:08:21 +0100107 * @brief Iterate over list of loaded plugins of the given @p type.
108 *
109 * @param[in] type Type of the plugins to iterate.
110 * @param[in,out] index The iterator - set to 0 for the first call.
111 * @return The plugin records, NULL if no more record is available.
112 */
113static struct lyplg_record *
114plugins_iter(enum LYPLG type, uint32_t *index)
115{
116 struct ly_set *plugins;
117
118 assert(index);
119
120 if (type == LYPLG_EXTENSION) {
121 plugins = &plugins_extensions;
122 } else {
123 plugins = &plugins_types;
124 }
125
126 if (*index == plugins->count) {
127 return NULL;
128 }
129
130 *index += 1;
131 return plugins->objs[*index - 1];
132}
133
134void *
135lyplg_find(enum LYPLG type, const char *module, const char *revision, const char *name)
136{
137 uint32_t i = 0;
138 struct lyplg_record *item;
139
140 assert(module);
141 assert(name);
142
143 while ((item = plugins_iter(type, &i)) != NULL) {
144 if (!strcmp(item->module, module) && !strcmp(item->name, name)) {
145 if (item->revision && revision && strcmp(item->revision, revision)) {
146 continue;
147 } else if (!revision && item->revision) {
148 continue;
149 }
150
151 return &item->plugin;
152 }
153 }
154
155 return NULL;
156}
157
158/**
159 * @brief Insert the provided extension plugin records into the internal set of extension plugins for use by libyang.
160 *
161 * @param[in] recs An array of plugin records provided by the plugin implementation. The array must be terminated by a zeroed
162 * record.
163 * @return LY_SUCCESS in case of success
164 * @return LY_EINVAL for invalid information in @p recs.
165 * @return LY_EMEM in case of memory allocation failure.
166 */
167static LY_ERR
168plugins_insert(enum LYPLG type, const void *recs)
169{
170 if (!recs) {
171 return LY_SUCCESS;
172 }
173
174 if (type == LYPLG_EXTENSION) {
175 const struct lyplg_ext_record *rec = (const struct lyplg_ext_record *)recs;
176
177 for (uint32_t i = 0; rec[i].name; i++) {
178 LY_CHECK_RET(ly_set_add(&plugins_extensions, (void *)&rec[i], 0, NULL));
179 }
Radek Krejcibf940f92021-03-24 21:04:13 +0100180 } else { /* LYPLG_TYPE */
Radek Krejci3e6632f2021-03-22 22:08:21 +0100181 const struct lyplg_type_record *rec = (const struct lyplg_type_record *)recs;
182
183 for (uint32_t i = 0; rec[i].name; i++) {
184 LY_CHECK_RET(ly_set_add(&plugins_types, (void *)&rec[i], 0, NULL));
185 }
186 }
187
188 return LY_SUCCESS;
189}
190
191static void
192lyplg_clean_(void)
193{
194 if (--context_refcount) {
195 /* there is still some other context, do not remove the plugins */
196 return;
197 }
198
199 ly_set_erase(&plugins_types, NULL);
200 ly_set_erase(&plugins_extensions, NULL);
Radek Krejcibf940f92021-03-24 21:04:13 +0100201 ly_set_erase(&plugins_handlers, (void(*)(void *))dlclose);
Radek Krejci3e6632f2021-03-22 22:08:21 +0100202}
203
204void
205lyplg_clean(void)
206{
207 pthread_mutex_lock(&plugins_guard);
208 lyplg_clean_();
209 pthread_mutex_unlock(&plugins_guard);
210}
211
Radek Krejcibf940f92021-03-24 21:04:13 +0100212/**
213 * @brief Get the expected plugin objects from the loaded dynamic object and add the defined plugins into the lists of
214 * available extensions/types plugins.
215 *
216 * @param[in] dlhandler Loaded dynamic library handler.
217 * @param[in] pathname Path of the loaded library for logging.
218 * @param[in] type Type of the plugins to get from the dynamic library. Note that a single library can hold both types
219 * and extensions plugins implementations, so this function should be called twice (once for each plugin type) with
220 * different @p type values
221 * @return LY_ERR values.
222 */
223static LY_ERR
224plugins_load(void *dlhandler, const char *pathname, enum LYPLG type)
225{
226 const void *plugins;
227 uint32_t *version;
228
229 /* type plugin */
230 version = dlsym(dlhandler, plugins_load_info[type].apiver_var);
231 if (version) {
232 /* check version ... */
233 if (*version != plugins_load_info[type].apiver) {
234 LOGERR(NULL, LY_EINVAL, "Processing user %s plugin \"%s\" failed, wrong API version - %d expected, %d found.",
235 plugins_load_info[type].id, pathname, plugins_load_info[type].apiver, *version);
236 return LY_EINVAL;
237 }
238
239 /* ... get types plugins information ... */
240 if (!(plugins = dlsym(dlhandler, plugins_load_info[type].plugins_var))) {
241 char *errstr = dlerror();
242 LOGERR(NULL, LY_EINVAL, "Processing user %s plugin \"%s\" failed, missing %s plugins information (%s).",
243 plugins_load_info[type].id, pathname, plugins_load_info[type].id, errstr);
244 return LY_EINVAL;
245 }
246
247 /* ... and load all the types plugins */
248 LY_CHECK_RET(plugins_insert(type, plugins));
249 }
250
251 return LY_SUCCESS;
252}
253
254static LY_ERR
255plugins_load_module(const char *pathname)
256{
257 LY_ERR ret = LY_SUCCESS;
258 void *dlhandler;
259 uint32_t types_count = 0, extensions_count = 0;
260
261 dlerror(); /* Clear any existing error */
262
263 dlhandler = dlopen(pathname, RTLD_NOW);
264 if (!dlhandler) {
265 LOGERR(NULL, LY_ESYS, "Loading \"%s\" as a plugin failed (%s).", pathname, dlerror());
266 return LY_ESYS;
267 }
268
269 if (ly_set_contains(&plugins_handlers, dlhandler, NULL)) {
270 /* the plugin is already loaded */
271 LOGVRB("Plugin \"%s\" already loaded.", pathname);
272
273 /* keep the correct refcount */
274 dlclose(dlhandler);
275 return LY_SUCCESS;
276 }
277
278 /* remember the current plugins lists for recovery */
279 types_count = plugins_types.count;
280 extensions_count = plugins_extensions.count;
281
282 /* type plugin */
283 ret = plugins_load(dlhandler, pathname, LYPLG_TYPE);
284 LY_CHECK_GOTO(ret, error);
285
286 /* extension plugin */
287 ret = plugins_load(dlhandler, pathname, LYPLG_EXTENSION);
288 LY_CHECK_GOTO(ret, error);
289
290 /* remember the dynamic plugin */
291 ret = ly_set_add(&plugins_handlers, dlhandler, 1, NULL);
292 LY_CHECK_GOTO(ret, error);
293
294 return LY_SUCCESS;
295
296error:
297 dlclose(dlhandler);
298
299 /* revert changes in the lists */
300 while (plugins_types.count > types_count) {
301 ly_set_rm_index(&plugins_types, plugins_types.count - 1, NULL);
302 }
303 while (plugins_extensions.count > extensions_count) {
304 ly_set_rm_index(&plugins_extensions, plugins_extensions.count - 1, NULL);
305 }
306
307 return ret;
308}
309
Radek Krejci3e6632f2021-03-22 22:08:21 +0100310LY_ERR
311lyplg_init(void)
312{
313 LY_ERR ret;
314
315 pthread_mutex_lock(&plugins_guard);
316 /* let only the first context to initiate plugins, but let others wait for finishing the initiation */
317 if (context_refcount++) {
318 /* already initiated */
319 pthread_mutex_unlock(&plugins_guard);
320 return LY_SUCCESS;
321 }
322
323 /* internal types */
324 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_binary), error);
325 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_bits), error);
326 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_boolean), error);
327 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_decimal64), error);
328 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_empty), error);
329 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_enumeration), error);
330 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_identityref), error);
331 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_instanceid), error);
332 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_integer), error);
333 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_leafref), error);
334 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_string), error);
335 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_union), error);
336
337 /* internal extensions */
338 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_EXTENSION, plugins_metadata), error);
339 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_EXTENSION, plugins_nacm), error);
340 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_EXTENSION, plugins_yangdata), error);
341
342 /* initiation done, wake-up possibly waiting threads creating another contexts */
343 pthread_mutex_unlock(&plugins_guard);
344
345 return LY_SUCCESS;
346
347error:
348 /* initiation was not successful - cleanup (and let others to try) */
349 lyplg_clean_();
350 pthread_mutex_unlock(&plugins_guard);
351
352 if (ret == LY_EINVAL) {
353 /* all the plugins here are internal, invalid record actually means an internal libyang error */
354 ret = LY_EINT;
355 }
356 return ret;
357}
Radek Krejcibf940f92021-03-24 21:04:13 +0100358
359API LY_ERR
360lyplg_add(const char *pathname)
361{
362 LY_ERR ret = LY_SUCCESS;
363
364 LY_CHECK_ARG_RET(NULL, pathname, LY_EINVAL);
365
366 /* works only in case a context exists */
367 pthread_mutex_lock(&plugins_guard);
368 if (!context_refcount) {
369 /* no context */
370 pthread_mutex_unlock(&plugins_guard);
371 LOGERR(NULL, LY_EDENIED, "To add a plugin, at least one context must exists.");
372 return LY_EDENIED;
373 }
374
375 ret = plugins_load_module(pathname);
376
377 pthread_mutex_unlock(&plugins_guard);
378
379 return ret;
380}