blob: 3b96c3ffd394312480e0b018a9451548460c06fb [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
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
Michal Vaskoe43a34e2021-04-13 13:43:04 +0200208lyplg_close_cb(void *handle)
209{
210 dlclose(handle);
211}
212
213static void
Radek Krejci3e6632f2021-03-22 22:08:21 +0100214lyplg_clean_(void)
215{
216 if (--context_refcount) {
217 /* there is still some other context, do not remove the plugins */
218 return;
219 }
220
221 ly_set_erase(&plugins_types, NULL);
222 ly_set_erase(&plugins_extensions, NULL);
Michal Vaskoe43a34e2021-04-13 13:43:04 +0200223 ly_set_erase(&plugins_handlers, lyplg_close_cb);
Radek Krejci3e6632f2021-03-22 22:08:21 +0100224}
225
226void
227lyplg_clean(void)
228{
229 pthread_mutex_lock(&plugins_guard);
230 lyplg_clean_();
231 pthread_mutex_unlock(&plugins_guard);
232}
233
Radek Krejcibf940f92021-03-24 21:04:13 +0100234/**
235 * @brief Get the expected plugin objects from the loaded dynamic object and add the defined plugins into the lists of
236 * available extensions/types plugins.
237 *
238 * @param[in] dlhandler Loaded dynamic library handler.
239 * @param[in] pathname Path of the loaded library for logging.
240 * @param[in] type Type of the plugins to get from the dynamic library. Note that a single library can hold both types
241 * and extensions plugins implementations, so this function should be called twice (once for each plugin type) with
242 * different @p type values
243 * @return LY_ERR values.
244 */
245static LY_ERR
246plugins_load(void *dlhandler, const char *pathname, enum LYPLG type)
247{
248 const void *plugins;
249 uint32_t *version;
250
251 /* type plugin */
252 version = dlsym(dlhandler, plugins_load_info[type].apiver_var);
253 if (version) {
254 /* check version ... */
255 if (*version != plugins_load_info[type].apiver) {
256 LOGERR(NULL, LY_EINVAL, "Processing user %s plugin \"%s\" failed, wrong API version - %d expected, %d found.",
257 plugins_load_info[type].id, pathname, plugins_load_info[type].apiver, *version);
258 return LY_EINVAL;
259 }
260
261 /* ... get types plugins information ... */
262 if (!(plugins = dlsym(dlhandler, plugins_load_info[type].plugins_var))) {
263 char *errstr = dlerror();
264 LOGERR(NULL, LY_EINVAL, "Processing user %s plugin \"%s\" failed, missing %s plugins information (%s).",
265 plugins_load_info[type].id, pathname, plugins_load_info[type].id, errstr);
266 return LY_EINVAL;
267 }
268
269 /* ... and load all the types plugins */
270 LY_CHECK_RET(plugins_insert(type, plugins));
271 }
272
273 return LY_SUCCESS;
274}
275
276static LY_ERR
277plugins_load_module(const char *pathname)
278{
279 LY_ERR ret = LY_SUCCESS;
280 void *dlhandler;
281 uint32_t types_count = 0, extensions_count = 0;
282
283 dlerror(); /* Clear any existing error */
284
285 dlhandler = dlopen(pathname, RTLD_NOW);
286 if (!dlhandler) {
287 LOGERR(NULL, LY_ESYS, "Loading \"%s\" as a plugin failed (%s).", pathname, dlerror());
288 return LY_ESYS;
289 }
290
291 if (ly_set_contains(&plugins_handlers, dlhandler, NULL)) {
292 /* the plugin is already loaded */
293 LOGVRB("Plugin \"%s\" already loaded.", pathname);
294
295 /* keep the correct refcount */
296 dlclose(dlhandler);
297 return LY_SUCCESS;
298 }
299
300 /* remember the current plugins lists for recovery */
301 types_count = plugins_types.count;
302 extensions_count = plugins_extensions.count;
303
304 /* type plugin */
305 ret = plugins_load(dlhandler, pathname, LYPLG_TYPE);
306 LY_CHECK_GOTO(ret, error);
307
308 /* extension plugin */
309 ret = plugins_load(dlhandler, pathname, LYPLG_EXTENSION);
310 LY_CHECK_GOTO(ret, error);
311
312 /* remember the dynamic plugin */
313 ret = ly_set_add(&plugins_handlers, dlhandler, 1, NULL);
314 LY_CHECK_GOTO(ret, error);
315
316 return LY_SUCCESS;
317
318error:
319 dlclose(dlhandler);
320
321 /* revert changes in the lists */
322 while (plugins_types.count > types_count) {
323 ly_set_rm_index(&plugins_types, plugins_types.count - 1, NULL);
324 }
325 while (plugins_extensions.count > extensions_count) {
326 ly_set_rm_index(&plugins_extensions, plugins_extensions.count - 1, NULL);
327 }
328
329 return ret;
330}
331
Radek Krejci968d7552021-03-26 20:33:51 +0100332static LY_ERR
333plugins_insert_dir(enum LYPLG type)
334{
335 LY_ERR ret = LY_SUCCESS;
336 const char *pluginsdir;
337 DIR *dir;
338 ly_bool default_dir = 0;
339
340 /* try to get the plugins directory from environment variable */
341 pluginsdir = getenv(plugins_load_info[type].envdir);
342 if (!pluginsdir) {
343 /* remember that we are going to a default dir and do not print warning if the directory doesn't exist */
344 default_dir = 1;
345 pluginsdir = plugins_load_info[type].dir;
346 }
347
348 dir = opendir(pluginsdir);
349 if (!dir) {
350 /* no directory (or no access to it), no extension plugins */
351 if (!default_dir || (errno != ENOENT)) {
352 LOGWRN(NULL, "Failed to open libyang %s plugins directory \"%s\" (%s).", plugins_load_info[type].id,
353 pluginsdir, strerror(errno));
354 }
355 } else {
356 struct dirent *file;
357
358 while ((file = readdir(dir))) {
359 size_t len;
360 char pathname[PATH_MAX];
361
362 /* required format of the filename is *LYPLG_SUFFIX */
363 len = strlen(file->d_name);
364 if ((len < LYPLG_SUFFIX_LEN + 1) || strcmp(&file->d_name[len - LYPLG_SUFFIX_LEN], LYPLG_SUFFIX)) {
365 continue;
366 }
367
368 /* and construct the filepath */
369 snprintf(pathname, PATH_MAX, "%s/%s", pluginsdir, file->d_name);
370
371 ret = plugins_load_module(pathname);
372 if (ret) {
373 break;
374 }
375 }
376 closedir(dir);
377 }
378
379 return ret;
380}
381
Radek Krejci3e6632f2021-03-22 22:08:21 +0100382LY_ERR
383lyplg_init(void)
384{
385 LY_ERR ret;
386
387 pthread_mutex_lock(&plugins_guard);
388 /* let only the first context to initiate plugins, but let others wait for finishing the initiation */
389 if (context_refcount++) {
390 /* already initiated */
391 pthread_mutex_unlock(&plugins_guard);
392 return LY_SUCCESS;
393 }
394
395 /* internal types */
396 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_binary), error);
397 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_bits), error);
398 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_boolean), error);
399 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_decimal64), error);
400 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_empty), error);
401 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_enumeration), error);
402 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_identityref), error);
403 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_instanceid), error);
404 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_integer), error);
405 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_leafref), error);
406 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_string), error);
407 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_union), error);
408
409 /* internal extensions */
410 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_EXTENSION, plugins_metadata), error);
411 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_EXTENSION, plugins_nacm), error);
412 LY_CHECK_GOTO(ret = plugins_insert(LYPLG_EXTENSION, plugins_yangdata), error);
413
Radek Krejci968d7552021-03-26 20:33:51 +0100414 /* external types */
415 LY_CHECK_GOTO(ret = plugins_insert_dir(LYPLG_TYPE), error);
416
417 /* external extensions */
418 LY_CHECK_GOTO(ret = plugins_insert_dir(LYPLG_EXTENSION), error);
419
Radek Krejci3e6632f2021-03-22 22:08:21 +0100420 /* initiation done, wake-up possibly waiting threads creating another contexts */
421 pthread_mutex_unlock(&plugins_guard);
422
423 return LY_SUCCESS;
424
425error:
426 /* initiation was not successful - cleanup (and let others to try) */
427 lyplg_clean_();
428 pthread_mutex_unlock(&plugins_guard);
429
430 if (ret == LY_EINVAL) {
431 /* all the plugins here are internal, invalid record actually means an internal libyang error */
432 ret = LY_EINT;
433 }
434 return ret;
435}
Radek Krejcibf940f92021-03-24 21:04:13 +0100436
437API LY_ERR
438lyplg_add(const char *pathname)
439{
440 LY_ERR ret = LY_SUCCESS;
441
442 LY_CHECK_ARG_RET(NULL, pathname, LY_EINVAL);
443
444 /* works only in case a context exists */
445 pthread_mutex_lock(&plugins_guard);
446 if (!context_refcount) {
447 /* no context */
448 pthread_mutex_unlock(&plugins_guard);
449 LOGERR(NULL, LY_EDENIED, "To add a plugin, at least one context must exists.");
450 return LY_EDENIED;
451 }
452
453 ret = plugins_load_module(pathname);
454
455 pthread_mutex_unlock(&plugins_guard);
456
457 return ret;
458}