blob: e669c4c6057197cf679d9d74778e00cf978b4eaa [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 Vasko3159e782021-05-03 15:12:35 +020057extern const struct lyplg_type_record plugins_ipv4_address[];
Michal Vasko82bf15e2021-05-06 16:01:56 +020058extern const struct lyplg_type_record plugins_ipv4_address_no_zone[];
Michal Vasko7caa3e62021-05-03 14:59:25 +020059extern const struct lyplg_type_record plugins_ipv6_address[];
Michal Vasko18a4a732021-05-06 16:21:44 +020060extern const struct lyplg_type_record plugins_ipv6_address_no_zone[];
Michal Vasko15dc9fa2021-05-03 14:33:05 +020061extern const struct lyplg_type_record plugins_ipv4_prefix[];
62extern const struct lyplg_type_record plugins_ipv6_prefix[];
Michal Vasko3e52de52021-04-13 13:45:55 +020063
Radek Krejci3e6632f2021-03-22 22:08:21 +010064/*
Michal Vaskode4a3412021-04-14 15:38:27 +020065 * ietf-yang-types
66 */
67extern const struct lyplg_type_record plugins_date_and_time[];
Michal Vaskode4a3412021-04-14 15:38:27 +020068extern const struct lyplg_type_record plugins_xpath10[];
69
70/*
Radek Krejci3e6632f2021-03-22 22:08:21 +010071 * internal extension plugins records
72 */
73extern struct lyplg_ext_record plugins_metadata[];
74extern struct lyplg_ext_record plugins_nacm[];
75extern struct lyplg_ext_record plugins_yangdata[];
76
77static pthread_mutex_t plugins_guard = PTHREAD_MUTEX_INITIALIZER;
78
79/**
80 * @brief Counter for currently present contexts able to refer to the loaded plugins.
81 *
82 * Plugins are shared among all the created contexts. They are loaded with the creation of the very first context and
83 * unloaded with the destroy of the last context. Therefore, to reload the list of plugins, all the contexts must be
84 * destroyed and with the creation of a first new context after that, the plugins will be reloaded.
85 */
86static uint32_t context_refcount = 0;
87
88/**
89 * @brief Record describing an implemented extension.
90 *
91 * Matches ::lyplg_ext_record and ::lyplg_type_record
92 */
93struct lyplg_record {
94 const char *module; /**< name of the module where the extension/type is defined */
95 const char *revision; /**< optional module revision - if not specified, the plugin applies to any revision,
96 which is not an optimal approach due to a possible future revisions of the module.
97 Instead, there should be defined multiple items in the plugins list, each with the
98 different revision, but all with the same pointer to the plugin functions. The
99 only valid use case for the NULL revision is the case the module has no revision. */
100 const char *name; /**< name of the extension/typedef */
101 int8_t plugin[]; /**< specific plugin type's data - ::lyplg_ext or ::lyplg_type */
102};
103
Michal Vasko9e2bc702021-06-09 11:43:36 +0200104#ifndef STATIC
Radek Krejcibf940f92021-03-24 21:04:13 +0100105static struct ly_set plugins_handlers = {0};
Michal Vasko9e2bc702021-06-09 11:43:36 +0200106#endif
Radek Krejci3e6632f2021-03-22 22:08:21 +0100107static struct ly_set plugins_types = {0};
108static struct ly_set plugins_extensions = {0};
109
110/**
111 * @brief Iterate over list of loaded plugins of the given @p type.
112 *
113 * @param[in] type Type of the plugins to iterate.
114 * @param[in,out] index The iterator - set to 0 for the first call.
115 * @return The plugin records, NULL if no more record is available.
116 */
117static struct lyplg_record *
118plugins_iter(enum LYPLG type, uint32_t *index)
119{
120 struct ly_set *plugins;
121
122 assert(index);
123
124 if (type == LYPLG_EXTENSION) {
125 plugins = &plugins_extensions;
126 } else {
127 plugins = &plugins_types;
128 }
129
130 if (*index == plugins->count) {
131 return NULL;
132 }
133
134 *index += 1;
135 return plugins->objs[*index - 1];
136}
137
138void *
139lyplg_find(enum LYPLG type, const char *module, const char *revision, const char *name)
140{
141 uint32_t i = 0;
142 struct lyplg_record *item;
143
144 assert(module);
145 assert(name);
146
147 while ((item = plugins_iter(type, &i)) != NULL) {
148 if (!strcmp(item->module, module) && !strcmp(item->name, name)) {
149 if (item->revision && revision && strcmp(item->revision, revision)) {
150 continue;
151 } else if (!revision && item->revision) {
152 continue;
153 }
154
155 return &item->plugin;
156 }
157 }
158
159 return NULL;
160}
161
162/**
163 * @brief Insert the provided extension plugin records into the internal set of extension plugins for use by libyang.
164 *
165 * @param[in] recs An array of plugin records provided by the plugin implementation. The array must be terminated by a zeroed
166 * record.
167 * @return LY_SUCCESS in case of success
168 * @return LY_EINVAL for invalid information in @p recs.
169 * @return LY_EMEM in case of memory allocation failure.
170 */
171static LY_ERR
172plugins_insert(enum LYPLG type, const void *recs)
173{
174 if (!recs) {
175 return LY_SUCCESS;
176 }
177
178 if (type == LYPLG_EXTENSION) {
179 const struct lyplg_ext_record *rec = (const struct lyplg_ext_record *)recs;
180
181 for (uint32_t i = 0; rec[i].name; i++) {
182 LY_CHECK_RET(ly_set_add(&plugins_extensions, (void *)&rec[i], 0, NULL));
183 }
Radek Krejcibf940f92021-03-24 21:04:13 +0100184 } else { /* LYPLG_TYPE */
Radek Krejci3e6632f2021-03-22 22:08:21 +0100185 const struct lyplg_type_record *rec = (const struct lyplg_type_record *)recs;
186
187 for (uint32_t i = 0; rec[i].name; i++) {
188 LY_CHECK_RET(ly_set_add(&plugins_types, (void *)&rec[i], 0, NULL));
189 }
190 }
191
192 return LY_SUCCESS;
193}
194
Michal Vasko9e2bc702021-06-09 11:43:36 +0200195#ifndef STATIC
196
Radek Krejci3e6632f2021-03-22 22:08:21 +0100197static void
Michal Vaskoe43a34e2021-04-13 13:43:04 +0200198lyplg_close_cb(void *handle)
199{
200 dlclose(handle);
201}
202
203static void
Radek Krejci3e6632f2021-03-22 22:08:21 +0100204lyplg_clean_(void)
205{
206 if (--context_refcount) {
207 /* there is still some other context, do not remove the plugins */
208 return;
209 }
210
211 ly_set_erase(&plugins_types, NULL);
212 ly_set_erase(&plugins_extensions, NULL);
Michal Vaskoe43a34e2021-04-13 13:43:04 +0200213 ly_set_erase(&plugins_handlers, lyplg_close_cb);
Radek Krejci3e6632f2021-03-22 22:08:21 +0100214}
215
Michal Vasko9e2bc702021-06-09 11:43:36 +0200216#endif
217
Radek Krejci3e6632f2021-03-22 22:08:21 +0100218void
219lyplg_clean(void)
220{
Michal Vasko9e2bc702021-06-09 11:43:36 +0200221#ifndef STATIC
Radek Krejci3e6632f2021-03-22 22:08:21 +0100222 pthread_mutex_lock(&plugins_guard);
223 lyplg_clean_();
224 pthread_mutex_unlock(&plugins_guard);
Michal Vasko9e2bc702021-06-09 11:43:36 +0200225#endif
Radek Krejci3e6632f2021-03-22 22:08:21 +0100226}
227
Michal Vasko9e2bc702021-06-09 11:43:36 +0200228#ifndef STATIC
229
230/**
231 * @brief Just a variadic data to cover extension and type plugins by a single ::plugins_load() function.
232 *
233 * The values are taken from ::LY_PLUGINS_EXTENSIONS and ::LYPLG_TYPES macros.
234 */
235static const struct {
236 const char *id; /**< string identifier: type/extension */
237 const char *apiver_var; /**< expected variable name holding API version value */
238 const char *plugins_var; /**< expected variable name holding plugin records */
239 const char *envdir; /**< environment variable containing directory with the plugins */
240 const char *dir; /**< default directory with the plugins (has less priority than envdir) */
241 uint32_t apiver; /**< expected API version */
242} plugins_load_info[] = {
243 { /* LYPLG_TYPE */
244 .id = "type",
245 .apiver_var = "plugins_types_apiver__",
246 .plugins_var = "plugins_types__",
247 .envdir = "LIBYANG_TYPES_PLUGINS_DIR",
248 .dir = LYPLG_TYPE_DIR,
249 .apiver = LYPLG_TYPE_API_VERSION
250 }, {/* LYPLG_EXTENSION */
251 .id = "extension",
252 .apiver_var = "plugins_extensions_apiver__",
253 .plugins_var = "plugins_extensions__",
254 .envdir = "LIBYANG_EXTENSIONS_PLUGINS_DIR",
255 .dir = LYPLG_EXT_DIR,
256 .apiver = LYPLG_EXT_API_VERSION
257 }
258};
259
Radek Krejcibf940f92021-03-24 21:04:13 +0100260/**
261 * @brief Get the expected plugin objects from the loaded dynamic object and add the defined plugins into the lists of
262 * available extensions/types plugins.
263 *
264 * @param[in] dlhandler Loaded dynamic library handler.
265 * @param[in] pathname Path of the loaded library for logging.
266 * @param[in] type Type of the plugins to get from the dynamic library. Note that a single library can hold both types
267 * and extensions plugins implementations, so this function should be called twice (once for each plugin type) with
268 * different @p type values
269 * @return LY_ERR values.
270 */
271static LY_ERR
272plugins_load(void *dlhandler, const char *pathname, enum LYPLG type)
273{
274 const void *plugins;
275 uint32_t *version;
276
277 /* type plugin */
278 version = dlsym(dlhandler, plugins_load_info[type].apiver_var);
279 if (version) {
280 /* check version ... */
281 if (*version != plugins_load_info[type].apiver) {
282 LOGERR(NULL, LY_EINVAL, "Processing user %s plugin \"%s\" failed, wrong API version - %d expected, %d found.",
283 plugins_load_info[type].id, pathname, plugins_load_info[type].apiver, *version);
284 return LY_EINVAL;
285 }
286
287 /* ... get types plugins information ... */
288 if (!(plugins = dlsym(dlhandler, plugins_load_info[type].plugins_var))) {
289 char *errstr = dlerror();
290 LOGERR(NULL, LY_EINVAL, "Processing user %s plugin \"%s\" failed, missing %s plugins information (%s).",
291 plugins_load_info[type].id, pathname, plugins_load_info[type].id, errstr);
292 return LY_EINVAL;
293 }
294
295 /* ... and load all the types plugins */
296 LY_CHECK_RET(plugins_insert(type, plugins));
297 }
298
299 return LY_SUCCESS;
300}
301
302static LY_ERR
303plugins_load_module(const char *pathname)
304{
305 LY_ERR ret = LY_SUCCESS;
306 void *dlhandler;
307 uint32_t types_count = 0, extensions_count = 0;
308
309 dlerror(); /* Clear any existing error */
310
311 dlhandler = dlopen(pathname, RTLD_NOW);
312 if (!dlhandler) {
313 LOGERR(NULL, LY_ESYS, "Loading \"%s\" as a plugin failed (%s).", pathname, dlerror());
314 return LY_ESYS;
315 }
316
317 if (ly_set_contains(&plugins_handlers, dlhandler, NULL)) {
318 /* the plugin is already loaded */
319 LOGVRB("Plugin \"%s\" already loaded.", pathname);
320
321 /* keep the correct refcount */
322 dlclose(dlhandler);
323 return LY_SUCCESS;
324 }
325
326 /* remember the current plugins lists for recovery */
327 types_count = plugins_types.count;
328 extensions_count = plugins_extensions.count;
329
330 /* type plugin */
331 ret = plugins_load(dlhandler, pathname, LYPLG_TYPE);
332 LY_CHECK_GOTO(ret, error);
333
334 /* extension plugin */
335 ret = plugins_load(dlhandler, pathname, LYPLG_EXTENSION);
336 LY_CHECK_GOTO(ret, error);
337
338 /* remember the dynamic plugin */
339 ret = ly_set_add(&plugins_handlers, dlhandler, 1, NULL);
340 LY_CHECK_GOTO(ret, error);
341
342 return LY_SUCCESS;
343
344error:
345 dlclose(dlhandler);
346
347 /* revert changes in the lists */
348 while (plugins_types.count > types_count) {
349 ly_set_rm_index(&plugins_types, plugins_types.count - 1, NULL);
350 }
351 while (plugins_extensions.count > extensions_count) {
352 ly_set_rm_index(&plugins_extensions, plugins_extensions.count - 1, NULL);
353 }
354
355 return ret;
356}
357
Radek Krejci968d7552021-03-26 20:33:51 +0100358static LY_ERR
359plugins_insert_dir(enum LYPLG type)
360{
361 LY_ERR ret = LY_SUCCESS;
362 const char *pluginsdir;
363 DIR *dir;
364 ly_bool default_dir = 0;
365
366 /* try to get the plugins directory from environment variable */
367 pluginsdir = getenv(plugins_load_info[type].envdir);
368 if (!pluginsdir) {
369 /* remember that we are going to a default dir and do not print warning if the directory doesn't exist */
370 default_dir = 1;
371 pluginsdir = plugins_load_info[type].dir;
372 }
373
374 dir = opendir(pluginsdir);
375 if (!dir) {
376 /* no directory (or no access to it), no extension plugins */
377 if (!default_dir || (errno != ENOENT)) {
378 LOGWRN(NULL, "Failed to open libyang %s plugins directory \"%s\" (%s).", plugins_load_info[type].id,
379 pluginsdir, strerror(errno));
380 }
381 } else {
382 struct dirent *file;
383
384 while ((file = readdir(dir))) {
385 size_t len;
386 char pathname[PATH_MAX];
387
388 /* required format of the filename is *LYPLG_SUFFIX */
389 len = strlen(file->d_name);
390 if ((len < LYPLG_SUFFIX_LEN + 1) || strcmp(&file->d_name[len - LYPLG_SUFFIX_LEN], LYPLG_SUFFIX)) {
391 continue;
392 }
393
394 /* and construct the filepath */
395 snprintf(pathname, PATH_MAX, "%s/%s", pluginsdir, file->d_name);
396
397 ret = plugins_load_module(pathname);
398 if (ret) {
399 break;
400 }
401 }
402 closedir(dir);
403 }
404
405 return ret;
406}
407
Michal Vasko9e2bc702021-06-09 11:43:36 +0200408#endif
409
Radek Krejci3e6632f2021-03-22 22:08:21 +0100410LY_ERR
411lyplg_init(void)
412{
413 LY_ERR ret;
414
415 pthread_mutex_lock(&plugins_guard);
416 /* let only the first context to initiate plugins, but let others wait for finishing the initiation */
417 if (context_refcount++) {
418 /* already initiated */
419 pthread_mutex_unlock(&plugins_guard);
420 return LY_SUCCESS;
421 }
422
423 /* internal types */
424 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_binary), error);
425 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_bits), error);
426 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_boolean), error);
427 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_decimal64), error);
428 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_empty), error);
429 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_enumeration), error);
430 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_identityref), error);
431 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_instanceid), error);
432 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_integer), error);
433 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_leafref), error);
434 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_string), error);
435 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_union), error);
436
Michal Vaskode4a3412021-04-14 15:38:27 +0200437 /* ietf-inet-types */
Michal Vasko3159e782021-05-03 15:12:35 +0200438 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_ipv4_address), error);
Michal Vasko82bf15e2021-05-06 16:01:56 +0200439 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_ipv4_address_no_zone), error);
Michal Vasko7caa3e62021-05-03 14:59:25 +0200440 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_ipv6_address), error);
Michal Vasko18a4a732021-05-06 16:21:44 +0200441 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_ipv6_address_no_zone), error);
Michal Vasko15dc9fa2021-05-03 14:33:05 +0200442 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_ipv4_prefix), error);
443 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_ipv6_prefix), error);
Michal Vasko3e52de52021-04-13 13:45:55 +0200444
Michal Vaskode4a3412021-04-14 15:38:27 +0200445 /* ietf-yang-types */
446 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_date_and_time), error);
Michal Vaskode4a3412021-04-14 15:38:27 +0200447 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_xpath10), error);
448
Radek Krejci3e6632f2021-03-22 22:08:21 +0100449 /* internal extensions */
450 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_EXTENSION, plugins_metadata), error);
451 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_EXTENSION, plugins_nacm), error);
452 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_EXTENSION, plugins_yangdata), error);
453
Michal Vasko9e2bc702021-06-09 11:43:36 +0200454#ifndef STATIC
Radek Krejci968d7552021-03-26 20:33:51 +0100455 /* external types */
456 LY_CHECK_GOTO(ret = plugins_insert_dir(LYPLG_TYPE), error);
457
458 /* external extensions */
459 LY_CHECK_GOTO(ret = plugins_insert_dir(LYPLG_EXTENSION), error);
Michal Vasko9e2bc702021-06-09 11:43:36 +0200460#endif
Radek Krejci968d7552021-03-26 20:33:51 +0100461
Radek Krejci3e6632f2021-03-22 22:08:21 +0100462 /* initiation done, wake-up possibly waiting threads creating another contexts */
463 pthread_mutex_unlock(&plugins_guard);
464
465 return LY_SUCCESS;
466
467error:
468 /* initiation was not successful - cleanup (and let others to try) */
Michal Vasko9e2bc702021-06-09 11:43:36 +0200469#ifndef STATIC
Radek Krejci3e6632f2021-03-22 22:08:21 +0100470 lyplg_clean_();
Michal Vasko9e2bc702021-06-09 11:43:36 +0200471#endif
Radek Krejci3e6632f2021-03-22 22:08:21 +0100472 pthread_mutex_unlock(&plugins_guard);
473
474 if (ret == LY_EINVAL) {
475 /* all the plugins here are internal, invalid record actually means an internal libyang error */
476 ret = LY_EINT;
477 }
478 return ret;
479}
Radek Krejcibf940f92021-03-24 21:04:13 +0100480
481API LY_ERR
482lyplg_add(const char *pathname)
483{
Michal Vasko9e2bc702021-06-09 11:43:36 +0200484#ifdef STATIC
485 (void)pathname;
486
487 LOGERR(NULL, LY_EINVAL, "Plugins are not supported in statically built library.");
488 return LY_EINVAL;
489#else
Radek Krejcibf940f92021-03-24 21:04:13 +0100490 LY_ERR ret = LY_SUCCESS;
491
492 LY_CHECK_ARG_RET(NULL, pathname, LY_EINVAL);
493
494 /* works only in case a context exists */
495 pthread_mutex_lock(&plugins_guard);
496 if (!context_refcount) {
497 /* no context */
498 pthread_mutex_unlock(&plugins_guard);
499 LOGERR(NULL, LY_EDENIED, "To add a plugin, at least one context must exists.");
500 return LY_EDENIED;
501 }
502
503 ret = plugins_load_module(pathname);
504
505 pthread_mutex_unlock(&plugins_guard);
506
507 return ret;
Michal Vasko9e2bc702021-06-09 11:43:36 +0200508#endif
Radek Krejcibf940f92021-03-24 21:04:13 +0100509}