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