blob: 85e37458ab87fa0d636cae9450a8eaf5c9f1f884 [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
Radek Krejcibf940f92021-03-24 21:04:13 +0100104static struct ly_set plugins_handlers = {0};
Radek Krejci3e6632f2021-03-22 22:08:21 +0100105static struct ly_set plugins_types = {0};
106static struct ly_set plugins_extensions = {0};
107
108/**
Radek Krejcibf940f92021-03-24 21:04:13 +0100109 * @brief Just a variadic data to cover extension and type plugins by a single ::plugins_load() function.
110 *
111 * The values are taken from ::LY_PLUGINS_EXTENSIONS and ::LYPLG_TYPES macros.
112 */
113static const struct {
114 const char *id; /**< string identifier: type/extension */
115 const char *apiver_var; /**< expected variable name holding API version value */
116 const char *plugins_var; /**< expected variable name holding plugin records */
Radek Krejci968d7552021-03-26 20:33:51 +0100117 const char *envdir; /**< environment variable containing directory with the plugins */
118 const char *dir; /**< default directory with the plugins (has less priority than envdir) */
Radek Krejcibf940f92021-03-24 21:04:13 +0100119 uint32_t apiver; /**< expected API version */
120} plugins_load_info[] = {
121 { /* LYPLG_TYPE */
122 .id = "type",
123 .apiver_var = "plugins_types_apiver__",
124 .plugins_var = "plugins_types__",
Radek Krejci968d7552021-03-26 20:33:51 +0100125 .envdir = "LIBYANG_TYPES_PLUGINS_DIR",
126 .dir = LYPLG_TYPE_DIR,
Radek Krejcibf940f92021-03-24 21:04:13 +0100127 .apiver = LYPLG_TYPE_API_VERSION
128 }, {/* LYPLG_EXTENSION */
129 .id = "extension",
130 .apiver_var = "plugins_extensions_apiver__",
131 .plugins_var = "plugins_extensions__",
Radek Krejci968d7552021-03-26 20:33:51 +0100132 .envdir = "LIBYANG_EXTENSIONS_PLUGINS_DIR",
133 .dir = LYPLG_EXT_DIR,
Radek Krejcibf940f92021-03-24 21:04:13 +0100134 .apiver = LYPLG_EXT_API_VERSION
135 }
136};
137
138/**
Radek Krejci3e6632f2021-03-22 22:08:21 +0100139 * @brief Iterate over list of loaded plugins of the given @p type.
140 *
141 * @param[in] type Type of the plugins to iterate.
142 * @param[in,out] index The iterator - set to 0 for the first call.
143 * @return The plugin records, NULL if no more record is available.
144 */
145static struct lyplg_record *
146plugins_iter(enum LYPLG type, uint32_t *index)
147{
148 struct ly_set *plugins;
149
150 assert(index);
151
152 if (type == LYPLG_EXTENSION) {
153 plugins = &plugins_extensions;
154 } else {
155 plugins = &plugins_types;
156 }
157
158 if (*index == plugins->count) {
159 return NULL;
160 }
161
162 *index += 1;
163 return plugins->objs[*index - 1];
164}
165
166void *
167lyplg_find(enum LYPLG type, const char *module, const char *revision, const char *name)
168{
169 uint32_t i = 0;
170 struct lyplg_record *item;
171
172 assert(module);
173 assert(name);
174
175 while ((item = plugins_iter(type, &i)) != NULL) {
176 if (!strcmp(item->module, module) && !strcmp(item->name, name)) {
177 if (item->revision && revision && strcmp(item->revision, revision)) {
178 continue;
179 } else if (!revision && item->revision) {
180 continue;
181 }
182
183 return &item->plugin;
184 }
185 }
186
187 return NULL;
188}
189
190/**
191 * @brief Insert the provided extension plugin records into the internal set of extension plugins for use by libyang.
192 *
193 * @param[in] recs An array of plugin records provided by the plugin implementation. The array must be terminated by a zeroed
194 * record.
195 * @return LY_SUCCESS in case of success
196 * @return LY_EINVAL for invalid information in @p recs.
197 * @return LY_EMEM in case of memory allocation failure.
198 */
199static LY_ERR
200plugins_insert(enum LYPLG type, const void *recs)
201{
202 if (!recs) {
203 return LY_SUCCESS;
204 }
205
206 if (type == LYPLG_EXTENSION) {
207 const struct lyplg_ext_record *rec = (const struct lyplg_ext_record *)recs;
208
209 for (uint32_t i = 0; rec[i].name; i++) {
210 LY_CHECK_RET(ly_set_add(&plugins_extensions, (void *)&rec[i], 0, NULL));
211 }
Radek Krejcibf940f92021-03-24 21:04:13 +0100212 } else { /* LYPLG_TYPE */
Radek Krejci3e6632f2021-03-22 22:08:21 +0100213 const struct lyplg_type_record *rec = (const struct lyplg_type_record *)recs;
214
215 for (uint32_t i = 0; rec[i].name; i++) {
216 LY_CHECK_RET(ly_set_add(&plugins_types, (void *)&rec[i], 0, NULL));
217 }
218 }
219
220 return LY_SUCCESS;
221}
222
223static void
Michal Vaskoe43a34e2021-04-13 13:43:04 +0200224lyplg_close_cb(void *handle)
225{
226 dlclose(handle);
227}
228
229static void
Radek Krejci3e6632f2021-03-22 22:08:21 +0100230lyplg_clean_(void)
231{
232 if (--context_refcount) {
233 /* there is still some other context, do not remove the plugins */
234 return;
235 }
236
237 ly_set_erase(&plugins_types, NULL);
238 ly_set_erase(&plugins_extensions, NULL);
Michal Vaskoe43a34e2021-04-13 13:43:04 +0200239 ly_set_erase(&plugins_handlers, lyplg_close_cb);
Radek Krejci3e6632f2021-03-22 22:08:21 +0100240}
241
242void
243lyplg_clean(void)
244{
245 pthread_mutex_lock(&plugins_guard);
246 lyplg_clean_();
247 pthread_mutex_unlock(&plugins_guard);
248}
249
Radek Krejcibf940f92021-03-24 21:04:13 +0100250/**
251 * @brief Get the expected plugin objects from the loaded dynamic object and add the defined plugins into the lists of
252 * available extensions/types plugins.
253 *
254 * @param[in] dlhandler Loaded dynamic library handler.
255 * @param[in] pathname Path of the loaded library for logging.
256 * @param[in] type Type of the plugins to get from the dynamic library. Note that a single library can hold both types
257 * and extensions plugins implementations, so this function should be called twice (once for each plugin type) with
258 * different @p type values
259 * @return LY_ERR values.
260 */
261static LY_ERR
262plugins_load(void *dlhandler, const char *pathname, enum LYPLG type)
263{
264 const void *plugins;
265 uint32_t *version;
266
267 /* type plugin */
268 version = dlsym(dlhandler, plugins_load_info[type].apiver_var);
269 if (version) {
270 /* check version ... */
271 if (*version != plugins_load_info[type].apiver) {
272 LOGERR(NULL, LY_EINVAL, "Processing user %s plugin \"%s\" failed, wrong API version - %d expected, %d found.",
273 plugins_load_info[type].id, pathname, plugins_load_info[type].apiver, *version);
274 return LY_EINVAL;
275 }
276
277 /* ... get types plugins information ... */
278 if (!(plugins = dlsym(dlhandler, plugins_load_info[type].plugins_var))) {
279 char *errstr = dlerror();
280 LOGERR(NULL, LY_EINVAL, "Processing user %s plugin \"%s\" failed, missing %s plugins information (%s).",
281 plugins_load_info[type].id, pathname, plugins_load_info[type].id, errstr);
282 return LY_EINVAL;
283 }
284
285 /* ... and load all the types plugins */
286 LY_CHECK_RET(plugins_insert(type, plugins));
287 }
288
289 return LY_SUCCESS;
290}
291
292static LY_ERR
293plugins_load_module(const char *pathname)
294{
295 LY_ERR ret = LY_SUCCESS;
296 void *dlhandler;
297 uint32_t types_count = 0, extensions_count = 0;
298
299 dlerror(); /* Clear any existing error */
300
301 dlhandler = dlopen(pathname, RTLD_NOW);
302 if (!dlhandler) {
303 LOGERR(NULL, LY_ESYS, "Loading \"%s\" as a plugin failed (%s).", pathname, dlerror());
304 return LY_ESYS;
305 }
306
307 if (ly_set_contains(&plugins_handlers, dlhandler, NULL)) {
308 /* the plugin is already loaded */
309 LOGVRB("Plugin \"%s\" already loaded.", pathname);
310
311 /* keep the correct refcount */
312 dlclose(dlhandler);
313 return LY_SUCCESS;
314 }
315
316 /* remember the current plugins lists for recovery */
317 types_count = plugins_types.count;
318 extensions_count = plugins_extensions.count;
319
320 /* type plugin */
321 ret = plugins_load(dlhandler, pathname, LYPLG_TYPE);
322 LY_CHECK_GOTO(ret, error);
323
324 /* extension plugin */
325 ret = plugins_load(dlhandler, pathname, LYPLG_EXTENSION);
326 LY_CHECK_GOTO(ret, error);
327
328 /* remember the dynamic plugin */
329 ret = ly_set_add(&plugins_handlers, dlhandler, 1, NULL);
330 LY_CHECK_GOTO(ret, error);
331
332 return LY_SUCCESS;
333
334error:
335 dlclose(dlhandler);
336
337 /* revert changes in the lists */
338 while (plugins_types.count > types_count) {
339 ly_set_rm_index(&plugins_types, plugins_types.count - 1, NULL);
340 }
341 while (plugins_extensions.count > extensions_count) {
342 ly_set_rm_index(&plugins_extensions, plugins_extensions.count - 1, NULL);
343 }
344
345 return ret;
346}
347
Radek Krejci968d7552021-03-26 20:33:51 +0100348static LY_ERR
349plugins_insert_dir(enum LYPLG type)
350{
351 LY_ERR ret = LY_SUCCESS;
352 const char *pluginsdir;
353 DIR *dir;
354 ly_bool default_dir = 0;
355
356 /* try to get the plugins directory from environment variable */
357 pluginsdir = getenv(plugins_load_info[type].envdir);
358 if (!pluginsdir) {
359 /* remember that we are going to a default dir and do not print warning if the directory doesn't exist */
360 default_dir = 1;
361 pluginsdir = plugins_load_info[type].dir;
362 }
363
364 dir = opendir(pluginsdir);
365 if (!dir) {
366 /* no directory (or no access to it), no extension plugins */
367 if (!default_dir || (errno != ENOENT)) {
368 LOGWRN(NULL, "Failed to open libyang %s plugins directory \"%s\" (%s).", plugins_load_info[type].id,
369 pluginsdir, strerror(errno));
370 }
371 } else {
372 struct dirent *file;
373
374 while ((file = readdir(dir))) {
375 size_t len;
376 char pathname[PATH_MAX];
377
378 /* required format of the filename is *LYPLG_SUFFIX */
379 len = strlen(file->d_name);
380 if ((len < LYPLG_SUFFIX_LEN + 1) || strcmp(&file->d_name[len - LYPLG_SUFFIX_LEN], LYPLG_SUFFIX)) {
381 continue;
382 }
383
384 /* and construct the filepath */
385 snprintf(pathname, PATH_MAX, "%s/%s", pluginsdir, file->d_name);
386
387 ret = plugins_load_module(pathname);
388 if (ret) {
389 break;
390 }
391 }
392 closedir(dir);
393 }
394
395 return ret;
396}
397
Radek Krejci3e6632f2021-03-22 22:08:21 +0100398LY_ERR
399lyplg_init(void)
400{
401 LY_ERR ret;
402
403 pthread_mutex_lock(&plugins_guard);
404 /* let only the first context to initiate plugins, but let others wait for finishing the initiation */
405 if (context_refcount++) {
406 /* already initiated */
407 pthread_mutex_unlock(&plugins_guard);
408 return LY_SUCCESS;
409 }
410
411 /* internal types */
412 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_binary), error);
413 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_bits), error);
414 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_boolean), error);
415 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_decimal64), error);
416 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_empty), error);
417 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_enumeration), error);
418 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_identityref), error);
419 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_instanceid), error);
420 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_integer), error);
421 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_leafref), error);
422 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_string), error);
423 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_union), error);
424
Michal Vaskode4a3412021-04-14 15:38:27 +0200425 /* ietf-inet-types */
Michal Vasko3159e782021-05-03 15:12:35 +0200426 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_ipv4_address), error);
Michal Vasko82bf15e2021-05-06 16:01:56 +0200427 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_ipv4_address_no_zone), error);
Michal Vasko7caa3e62021-05-03 14:59:25 +0200428 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_ipv6_address), error);
Michal Vasko18a4a732021-05-06 16:21:44 +0200429 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_ipv6_address_no_zone), error);
Michal Vasko15dc9fa2021-05-03 14:33:05 +0200430 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_ipv4_prefix), error);
431 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_ipv6_prefix), error);
Michal Vasko3e52de52021-04-13 13:45:55 +0200432
Michal Vaskode4a3412021-04-14 15:38:27 +0200433 /* ietf-yang-types */
434 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_date_and_time), error);
Michal Vaskode4a3412021-04-14 15:38:27 +0200435 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_xpath10), error);
436
Radek Krejci3e6632f2021-03-22 22:08:21 +0100437 /* internal extensions */
438 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_EXTENSION, plugins_metadata), error);
439 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_EXTENSION, plugins_nacm), error);
440 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_EXTENSION, plugins_yangdata), error);
441
Radek Krejci968d7552021-03-26 20:33:51 +0100442 /* external types */
443 LY_CHECK_GOTO(ret = plugins_insert_dir(LYPLG_TYPE), error);
444
445 /* external extensions */
446 LY_CHECK_GOTO(ret = plugins_insert_dir(LYPLG_EXTENSION), error);
447
Radek Krejci3e6632f2021-03-22 22:08:21 +0100448 /* initiation done, wake-up possibly waiting threads creating another contexts */
449 pthread_mutex_unlock(&plugins_guard);
450
451 return LY_SUCCESS;
452
453error:
454 /* initiation was not successful - cleanup (and let others to try) */
455 lyplg_clean_();
456 pthread_mutex_unlock(&plugins_guard);
457
458 if (ret == LY_EINVAL) {
459 /* all the plugins here are internal, invalid record actually means an internal libyang error */
460 ret = LY_EINT;
461 }
462 return ret;
463}
Radek Krejcibf940f92021-03-24 21:04:13 +0100464
465API LY_ERR
466lyplg_add(const char *pathname)
467{
468 LY_ERR ret = LY_SUCCESS;
469
470 LY_CHECK_ARG_RET(NULL, pathname, LY_EINVAL);
471
472 /* works only in case a context exists */
473 pthread_mutex_lock(&plugins_guard);
474 if (!context_refcount) {
475 /* no context */
476 pthread_mutex_unlock(&plugins_guard);
477 LOGERR(NULL, LY_EDENIED, "To add a plugin, at least one context must exists.");
478 return LY_EDENIED;
479 }
480
481 ret = plugins_load_module(pathname);
482
483 pthread_mutex_unlock(&plugins_guard);
484
485 return ret;
486}