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