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