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