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