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