blob: d0b28a02fc020c135a1812c371d90eab1c78d2c8 [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
Radek Krejci968d7552021-03-26 20:33:51 +010015#define _GNU_SOURCE
16
Radek Krejci3e6632f2021-03-22 22:08:21 +010017#include "plugins.h"
18#include "plugins_internal.h"
19
20#include <assert.h>
Radek Krejci968d7552021-03-26 20:33:51 +010021#include <dirent.h>
Radek Krejci3e6632f2021-03-22 22:08:21 +010022#include <dlfcn.h>
Radek Krejci968d7552021-03-26 20:33:51 +010023#include <errno.h>
24#include <limits.h>
Radek Krejci3e6632f2021-03-22 22:08:21 +010025#include <pthread.h>
Radek Krejcibf940f92021-03-24 21:04:13 +010026#include <stddef.h>
Radek Krejci0aa1f702021-04-01 16:16:19 +020027#include <stdint.h>
28#include <stdio.h>
29#include <stdlib.h>
Radek Krejci3e6632f2021-03-22 22:08:21 +010030#include <string.h>
31
32#include "common.h"
Radek Krejci12a28c72021-04-06 17:23:37 +020033#include "config.h"
Radek Krejci3e6632f2021-03-22 22:08:21 +010034#include "plugins_exts.h"
35#include "plugins_types.h"
Radek Krejci0aa1f702021-04-01 16:16:19 +020036#include "set.h"
Radek Krejci3e6632f2021-03-22 22:08:21 +010037
38/*
39 * internal type plugins records
40 */
41extern const struct lyplg_type_record plugins_binary[];
42extern const struct lyplg_type_record plugins_bits[];
43extern const struct lyplg_type_record plugins_boolean[];
44extern const struct lyplg_type_record plugins_decimal64[];
45extern const struct lyplg_type_record plugins_empty[];
46extern const struct lyplg_type_record plugins_enumeration[];
47extern const struct lyplg_type_record plugins_identityref[];
48extern const struct lyplg_type_record plugins_instanceid[];
49extern const struct lyplg_type_record plugins_integer[];
50extern const struct lyplg_type_record plugins_leafref[];
51extern const struct lyplg_type_record plugins_string[];
52extern const struct lyplg_type_record plugins_union[];
53
Michal Vaskode4a3412021-04-14 15:38:27 +020054/*
55 * ietf-inet-types
56 */
Michal Vasko3e52de52021-04-13 13:45:55 +020057extern const struct lyplg_type_record plugins_ip_address[];
58extern const struct lyplg_type_record plugins_ip_prefix[];
59
Radek Krejci3e6632f2021-03-22 22:08:21 +010060/*
Michal Vaskode4a3412021-04-14 15:38:27 +020061 * ietf-yang-types
62 */
63extern const struct lyplg_type_record plugins_date_and_time[];
64extern const struct lyplg_type_record plugins_hex_string[];
65extern const struct lyplg_type_record plugins_xpath10[];
66
67/*
Radek Krejci3e6632f2021-03-22 22:08:21 +010068 * internal extension plugins records
69 */
70extern struct lyplg_ext_record plugins_metadata[];
71extern struct lyplg_ext_record plugins_nacm[];
72extern struct lyplg_ext_record plugins_yangdata[];
73
74static pthread_mutex_t plugins_guard = PTHREAD_MUTEX_INITIALIZER;
75
76/**
77 * @brief Counter for currently present contexts able to refer to the loaded plugins.
78 *
79 * Plugins are shared among all the created contexts. They are loaded with the creation of the very first context and
80 * unloaded with the destroy of the last context. Therefore, to reload the list of plugins, all the contexts must be
81 * destroyed and with the creation of a first new context after that, the plugins will be reloaded.
82 */
83static uint32_t context_refcount = 0;
84
85/**
86 * @brief Record describing an implemented extension.
87 *
88 * Matches ::lyplg_ext_record and ::lyplg_type_record
89 */
90struct lyplg_record {
91 const char *module; /**< name of the module where the extension/type is defined */
92 const char *revision; /**< optional module revision - if not specified, the plugin applies to any revision,
93 which is not an optimal approach due to a possible future revisions of the module.
94 Instead, there should be defined multiple items in the plugins list, each with the
95 different revision, but all with the same pointer to the plugin functions. The
96 only valid use case for the NULL revision is the case the module has no revision. */
97 const char *name; /**< name of the extension/typedef */
98 int8_t plugin[]; /**< specific plugin type's data - ::lyplg_ext or ::lyplg_type */
99};
100
Radek Krejcibf940f92021-03-24 21:04:13 +0100101static struct ly_set plugins_handlers = {0};
Radek Krejci3e6632f2021-03-22 22:08:21 +0100102static struct ly_set plugins_types = {0};
103static struct ly_set plugins_extensions = {0};
104
105/**
Radek Krejcibf940f92021-03-24 21:04:13 +0100106 * @brief Just a variadic data to cover extension and type plugins by a single ::plugins_load() function.
107 *
108 * The values are taken from ::LY_PLUGINS_EXTENSIONS and ::LYPLG_TYPES macros.
109 */
110static const struct {
111 const char *id; /**< string identifier: type/extension */
112 const char *apiver_var; /**< expected variable name holding API version value */
113 const char *plugins_var; /**< expected variable name holding plugin records */
Radek Krejci968d7552021-03-26 20:33:51 +0100114 const char *envdir; /**< environment variable containing directory with the plugins */
115 const char *dir; /**< default directory with the plugins (has less priority than envdir) */
Radek Krejcibf940f92021-03-24 21:04:13 +0100116 uint32_t apiver; /**< expected API version */
117} plugins_load_info[] = {
118 { /* LYPLG_TYPE */
119 .id = "type",
120 .apiver_var = "plugins_types_apiver__",
121 .plugins_var = "plugins_types__",
Radek Krejci968d7552021-03-26 20:33:51 +0100122 .envdir = "LIBYANG_TYPES_PLUGINS_DIR",
123 .dir = LYPLG_TYPE_DIR,
Radek Krejcibf940f92021-03-24 21:04:13 +0100124 .apiver = LYPLG_TYPE_API_VERSION
125 }, {/* LYPLG_EXTENSION */
126 .id = "extension",
127 .apiver_var = "plugins_extensions_apiver__",
128 .plugins_var = "plugins_extensions__",
Radek Krejci968d7552021-03-26 20:33:51 +0100129 .envdir = "LIBYANG_EXTENSIONS_PLUGINS_DIR",
130 .dir = LYPLG_EXT_DIR,
Radek Krejcibf940f92021-03-24 21:04:13 +0100131 .apiver = LYPLG_EXT_API_VERSION
132 }
133};
134
135/**
Radek Krejci3e6632f2021-03-22 22:08:21 +0100136 * @brief Iterate over list of loaded plugins of the given @p type.
137 *
138 * @param[in] type Type of the plugins to iterate.
139 * @param[in,out] index The iterator - set to 0 for the first call.
140 * @return The plugin records, NULL if no more record is available.
141 */
142static struct lyplg_record *
143plugins_iter(enum LYPLG type, uint32_t *index)
144{
145 struct ly_set *plugins;
146
147 assert(index);
148
149 if (type == LYPLG_EXTENSION) {
150 plugins = &plugins_extensions;
151 } else {
152 plugins = &plugins_types;
153 }
154
155 if (*index == plugins->count) {
156 return NULL;
157 }
158
159 *index += 1;
160 return plugins->objs[*index - 1];
161}
162
163void *
164lyplg_find(enum LYPLG type, const char *module, const char *revision, const char *name)
165{
166 uint32_t i = 0;
167 struct lyplg_record *item;
168
169 assert(module);
170 assert(name);
171
172 while ((item = plugins_iter(type, &i)) != NULL) {
173 if (!strcmp(item->module, module) && !strcmp(item->name, name)) {
174 if (item->revision && revision && strcmp(item->revision, revision)) {
175 continue;
176 } else if (!revision && item->revision) {
177 continue;
178 }
179
180 return &item->plugin;
181 }
182 }
183
184 return NULL;
185}
186
187/**
188 * @brief Insert the provided extension plugin records into the internal set of extension plugins for use by libyang.
189 *
190 * @param[in] recs An array of plugin records provided by the plugin implementation. The array must be terminated by a zeroed
191 * record.
192 * @return LY_SUCCESS in case of success
193 * @return LY_EINVAL for invalid information in @p recs.
194 * @return LY_EMEM in case of memory allocation failure.
195 */
196static LY_ERR
197plugins_insert(enum LYPLG type, const void *recs)
198{
199 if (!recs) {
200 return LY_SUCCESS;
201 }
202
203 if (type == LYPLG_EXTENSION) {
204 const struct lyplg_ext_record *rec = (const struct lyplg_ext_record *)recs;
205
206 for (uint32_t i = 0; rec[i].name; i++) {
207 LY_CHECK_RET(ly_set_add(&plugins_extensions, (void *)&rec[i], 0, NULL));
208 }
Radek Krejcibf940f92021-03-24 21:04:13 +0100209 } else { /* LYPLG_TYPE */
Radek Krejci3e6632f2021-03-22 22:08:21 +0100210 const struct lyplg_type_record *rec = (const struct lyplg_type_record *)recs;
211
212 for (uint32_t i = 0; rec[i].name; i++) {
213 LY_CHECK_RET(ly_set_add(&plugins_types, (void *)&rec[i], 0, NULL));
214 }
215 }
216
217 return LY_SUCCESS;
218}
219
220static void
Michal Vaskoe43a34e2021-04-13 13:43:04 +0200221lyplg_close_cb(void *handle)
222{
223 dlclose(handle);
224}
225
226static void
Radek Krejci3e6632f2021-03-22 22:08:21 +0100227lyplg_clean_(void)
228{
229 if (--context_refcount) {
230 /* there is still some other context, do not remove the plugins */
231 return;
232 }
233
234 ly_set_erase(&plugins_types, NULL);
235 ly_set_erase(&plugins_extensions, NULL);
Michal Vaskoe43a34e2021-04-13 13:43:04 +0200236 ly_set_erase(&plugins_handlers, lyplg_close_cb);
Radek Krejci3e6632f2021-03-22 22:08:21 +0100237}
238
239void
240lyplg_clean(void)
241{
242 pthread_mutex_lock(&plugins_guard);
243 lyplg_clean_();
244 pthread_mutex_unlock(&plugins_guard);
245}
246
Radek Krejcibf940f92021-03-24 21:04:13 +0100247/**
248 * @brief Get the expected plugin objects from the loaded dynamic object and add the defined plugins into the lists of
249 * available extensions/types plugins.
250 *
251 * @param[in] dlhandler Loaded dynamic library handler.
252 * @param[in] pathname Path of the loaded library for logging.
253 * @param[in] type Type of the plugins to get from the dynamic library. Note that a single library can hold both types
254 * and extensions plugins implementations, so this function should be called twice (once for each plugin type) with
255 * different @p type values
256 * @return LY_ERR values.
257 */
258static LY_ERR
259plugins_load(void *dlhandler, const char *pathname, enum LYPLG type)
260{
261 const void *plugins;
262 uint32_t *version;
263
264 /* type plugin */
265 version = dlsym(dlhandler, plugins_load_info[type].apiver_var);
266 if (version) {
267 /* check version ... */
268 if (*version != plugins_load_info[type].apiver) {
269 LOGERR(NULL, LY_EINVAL, "Processing user %s plugin \"%s\" failed, wrong API version - %d expected, %d found.",
270 plugins_load_info[type].id, pathname, plugins_load_info[type].apiver, *version);
271 return LY_EINVAL;
272 }
273
274 /* ... get types plugins information ... */
275 if (!(plugins = dlsym(dlhandler, plugins_load_info[type].plugins_var))) {
276 char *errstr = dlerror();
277 LOGERR(NULL, LY_EINVAL, "Processing user %s plugin \"%s\" failed, missing %s plugins information (%s).",
278 plugins_load_info[type].id, pathname, plugins_load_info[type].id, errstr);
279 return LY_EINVAL;
280 }
281
282 /* ... and load all the types plugins */
283 LY_CHECK_RET(plugins_insert(type, plugins));
284 }
285
286 return LY_SUCCESS;
287}
288
289static LY_ERR
290plugins_load_module(const char *pathname)
291{
292 LY_ERR ret = LY_SUCCESS;
293 void *dlhandler;
294 uint32_t types_count = 0, extensions_count = 0;
295
296 dlerror(); /* Clear any existing error */
297
298 dlhandler = dlopen(pathname, RTLD_NOW);
299 if (!dlhandler) {
300 LOGERR(NULL, LY_ESYS, "Loading \"%s\" as a plugin failed (%s).", pathname, dlerror());
301 return LY_ESYS;
302 }
303
304 if (ly_set_contains(&plugins_handlers, dlhandler, NULL)) {
305 /* the plugin is already loaded */
306 LOGVRB("Plugin \"%s\" already loaded.", pathname);
307
308 /* keep the correct refcount */
309 dlclose(dlhandler);
310 return LY_SUCCESS;
311 }
312
313 /* remember the current plugins lists for recovery */
314 types_count = plugins_types.count;
315 extensions_count = plugins_extensions.count;
316
317 /* type plugin */
318 ret = plugins_load(dlhandler, pathname, LYPLG_TYPE);
319 LY_CHECK_GOTO(ret, error);
320
321 /* extension plugin */
322 ret = plugins_load(dlhandler, pathname, LYPLG_EXTENSION);
323 LY_CHECK_GOTO(ret, error);
324
325 /* remember the dynamic plugin */
326 ret = ly_set_add(&plugins_handlers, dlhandler, 1, NULL);
327 LY_CHECK_GOTO(ret, error);
328
329 return LY_SUCCESS;
330
331error:
332 dlclose(dlhandler);
333
334 /* revert changes in the lists */
335 while (plugins_types.count > types_count) {
336 ly_set_rm_index(&plugins_types, plugins_types.count - 1, NULL);
337 }
338 while (plugins_extensions.count > extensions_count) {
339 ly_set_rm_index(&plugins_extensions, plugins_extensions.count - 1, NULL);
340 }
341
342 return ret;
343}
344
Radek Krejci968d7552021-03-26 20:33:51 +0100345static LY_ERR
346plugins_insert_dir(enum LYPLG type)
347{
348 LY_ERR ret = LY_SUCCESS;
349 const char *pluginsdir;
350 DIR *dir;
351 ly_bool default_dir = 0;
352
353 /* try to get the plugins directory from environment variable */
354 pluginsdir = getenv(plugins_load_info[type].envdir);
355 if (!pluginsdir) {
356 /* remember that we are going to a default dir and do not print warning if the directory doesn't exist */
357 default_dir = 1;
358 pluginsdir = plugins_load_info[type].dir;
359 }
360
361 dir = opendir(pluginsdir);
362 if (!dir) {
363 /* no directory (or no access to it), no extension plugins */
364 if (!default_dir || (errno != ENOENT)) {
365 LOGWRN(NULL, "Failed to open libyang %s plugins directory \"%s\" (%s).", plugins_load_info[type].id,
366 pluginsdir, strerror(errno));
367 }
368 } else {
369 struct dirent *file;
370
371 while ((file = readdir(dir))) {
372 size_t len;
373 char pathname[PATH_MAX];
374
375 /* required format of the filename is *LYPLG_SUFFIX */
376 len = strlen(file->d_name);
377 if ((len < LYPLG_SUFFIX_LEN + 1) || strcmp(&file->d_name[len - LYPLG_SUFFIX_LEN], LYPLG_SUFFIX)) {
378 continue;
379 }
380
381 /* and construct the filepath */
382 snprintf(pathname, PATH_MAX, "%s/%s", pluginsdir, file->d_name);
383
384 ret = plugins_load_module(pathname);
385 if (ret) {
386 break;
387 }
388 }
389 closedir(dir);
390 }
391
392 return ret;
393}
394
Radek Krejci3e6632f2021-03-22 22:08:21 +0100395LY_ERR
396lyplg_init(void)
397{
398 LY_ERR ret;
399
400 pthread_mutex_lock(&plugins_guard);
401 /* let only the first context to initiate plugins, but let others wait for finishing the initiation */
402 if (context_refcount++) {
403 /* already initiated */
404 pthread_mutex_unlock(&plugins_guard);
405 return LY_SUCCESS;
406 }
407
408 /* internal types */
409 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_binary), error);
410 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_bits), error);
411 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_boolean), error);
412 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_decimal64), error);
413 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_empty), error);
414 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_enumeration), error);
415 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_identityref), error);
416 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_instanceid), error);
417 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_integer), error);
418 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_leafref), error);
419 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_string), error);
420 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_union), error);
421
Michal Vaskode4a3412021-04-14 15:38:27 +0200422 /* ietf-inet-types */
Michal Vasko3e52de52021-04-13 13:45:55 +0200423 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_ip_address), error);
424 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_ip_prefix), error);
425
Michal Vaskode4a3412021-04-14 15:38:27 +0200426 /* ietf-yang-types */
427 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_date_and_time), error);
428 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_hex_string), error);
429 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_xpath10), error);
430
Radek Krejci3e6632f2021-03-22 22:08:21 +0100431 /* internal extensions */
432 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_EXTENSION, plugins_metadata), error);
433 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_EXTENSION, plugins_nacm), error);
434 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_EXTENSION, plugins_yangdata), error);
435
Radek Krejci968d7552021-03-26 20:33:51 +0100436 /* external types */
437 LY_CHECK_GOTO(ret = plugins_insert_dir(LYPLG_TYPE), error);
438
439 /* external extensions */
440 LY_CHECK_GOTO(ret = plugins_insert_dir(LYPLG_EXTENSION), error);
441
Radek Krejci3e6632f2021-03-22 22:08:21 +0100442 /* initiation done, wake-up possibly waiting threads creating another contexts */
443 pthread_mutex_unlock(&plugins_guard);
444
445 return LY_SUCCESS;
446
447error:
448 /* initiation was not successful - cleanup (and let others to try) */
449 lyplg_clean_();
450 pthread_mutex_unlock(&plugins_guard);
451
452 if (ret == LY_EINVAL) {
453 /* all the plugins here are internal, invalid record actually means an internal libyang error */
454 ret = LY_EINT;
455 }
456 return ret;
457}
Radek Krejcibf940f92021-03-24 21:04:13 +0100458
459API LY_ERR
460lyplg_add(const char *pathname)
461{
462 LY_ERR ret = LY_SUCCESS;
463
464 LY_CHECK_ARG_RET(NULL, pathname, LY_EINVAL);
465
466 /* works only in case a context exists */
467 pthread_mutex_lock(&plugins_guard);
468 if (!context_refcount) {
469 /* no context */
470 pthread_mutex_unlock(&plugins_guard);
471 LOGERR(NULL, LY_EDENIED, "To add a plugin, at least one context must exists.");
472 return LY_EDENIED;
473 }
474
475 ret = plugins_load_module(pathname);
476
477 pthread_mutex_unlock(&plugins_guard);
478
479 return ret;
480}