blob: 639f69fc56a72c5bb04e722663ae67186c104005 [file] [log] [blame]
Michal Vaskoc6cd3f02018-03-02 14:07:42 +01001/**
2 * @file plugins.c
3 * @author Radek Krejci <rkrejci@cesnet.cz>
4 * @author Michal Vasko <mvasko@cesnet.cz>
5 * @brief YANG plugin routines implementation
6 *
7 * Copyright (c) 2015 - 2018 CESNET, z.s.p.o.
8 *
9 * This source code is licensed under BSD 3-Clause License (the "License").
10 * You may not use this file except in compliance with the License.
11 * You may obtain a copy of the License at
12 *
13 * https://opensource.org/licenses/BSD-3-Clause
14 */
15#define _GNU_SOURCE
16
17#include <assert.h>
18#include <errno.h>
19#include <dirent.h>
20#include <dlfcn.h>
21#include <pthread.h>
22#include <stdio.h>
23#include <stdlib.h>
24#include <string.h>
25#include <sys/types.h>
Michal Vaskoc6cd3f02018-03-02 14:07:42 +010026
27#include "common.h"
28#include "extensions.h"
29#include "user_types.h"
30#include "plugin_config.h"
31#include "libyang.h"
32#include "parser.h"
33
34/* internal structures storing the plugins */
35static struct lyext_plugin_list *ext_plugins = NULL;
36static uint16_t ext_plugins_count = 0; /* size of the ext_plugins array */
37
38static struct lytype_plugin_list *type_plugins = NULL;
39static uint16_t type_plugins_count = 0;
40
41static struct ly_set dlhandlers = {0, 0, {NULL}};
42static pthread_mutex_t plugins_lock = PTHREAD_MUTEX_INITIALIZER;
43
Michal Vasko3b887ec2018-05-31 09:31:46 +020044static char **loaded_plugins = NULL; /* both ext and type plugin names */
45static uint16_t loaded_plugins_count = 0;
46
Michal Vaskoc6cd3f02018-03-02 14:07:42 +010047/**
48 * @brief reference counter for the plugins, it actually counts number of contexts
49 */
50static uint32_t plugin_refs;
51
Michal Vasko3b887ec2018-05-31 09:31:46 +020052API const char * const *
53ly_get_loaded_plugins(void)
54{
55 return (const char * const *)loaded_plugins;
56}
57
Michal Vaskoc6cd3f02018-03-02 14:07:42 +010058API int
59ly_clean_plugins(void)
60{
61 unsigned int u;
62 int ret = EXIT_SUCCESS;
63
Mislav Novakovic61c79082018-05-21 13:08:24 +020064#ifdef STATIC
65 /* lock the extension plugins list */
66 pthread_mutex_lock(&plugins_lock);
67
68 if(ext_plugins) {
69 free(ext_plugins);
70 ext_plugins = NULL;
71 ext_plugins_count = 0;
72 }
73
74 if(type_plugins) {
75 free(type_plugins);
76 type_plugins = NULL;
77 type_plugins_count = 0;
78 }
79
Mislav Novakovic74726932018-06-04 13:14:03 +020080 for (u = 0; u < loaded_plugins_count; ++u) {
81 free(loaded_plugins[u]);
82 }
83 free(loaded_plugins);
84 loaded_plugins = NULL;
85 loaded_plugins_count = 0;
86
Mislav Novakovic61c79082018-05-21 13:08:24 +020087 /* unlock the global structures */
88 pthread_mutex_unlock(&plugins_lock);
89 return ret;
90#endif /* STATIC */
91
Michal Vaskoc6cd3f02018-03-02 14:07:42 +010092 /* lock the extension plugins list */
93 pthread_mutex_lock(&plugins_lock);
94
95 if (--plugin_refs) {
96 /* there is a context that may refer to the plugins, so we cannot remove them */
97 ret = EXIT_FAILURE;
98 goto cleanup;
99 }
100
101 if (!ext_plugins_count && !type_plugins_count) {
102 /* no plugin loaded - nothing to do */
103 goto cleanup;
104 }
105
106 /* clean the lists */
107 free(ext_plugins);
108 ext_plugins = NULL;
109 ext_plugins_count = 0;
110
111 free(type_plugins);
112 type_plugins = NULL;
113 type_plugins_count = 0;
114
Michal Vasko3b887ec2018-05-31 09:31:46 +0200115 for (u = 0; u < loaded_plugins_count; ++u) {
116 free(loaded_plugins[u]);
117 }
118 free(loaded_plugins);
119 loaded_plugins = NULL;
120 loaded_plugins_count = 0;
121
Michal Vaskoc6cd3f02018-03-02 14:07:42 +0100122 /* close the dl handlers */
123 for (u = 0; u < dlhandlers.number; u++) {
124 dlclose(dlhandlers.set.g[u]);
125 }
126 free(dlhandlers.set.g);
127 dlhandlers.set.g = NULL;
128 dlhandlers.size = 0;
129 dlhandlers.number = 0;
130
131cleanup:
132 /* unlock the global structures */
133 pthread_mutex_unlock(&plugins_lock);
134
135 return ret;
136}
137
138static int
139lytype_load_plugin(void *dlhandler, const char *file_name)
140{
David Lamparter78dc9c22018-11-12 13:02:19 +0100141 struct lytype_plugin_list *plugin;
Michal Vaskoc6cd3f02018-03-02 14:07:42 +0100142 char *str;
143
Mislav Novakovic61c79082018-05-21 13:08:24 +0200144#ifdef STATIC
145 return 0;
146#endif /* STATIC */
147
Michal Vaskoc6cd3f02018-03-02 14:07:42 +0100148 /* get the plugin data */
149 plugin = dlsym(dlhandler, file_name);
150 str = dlerror();
151 if (str) {
152 LOGERR(NULL, LY_ESYS, "Processing \"%s\" user type plugin failed, missing plugin list object (%s).", file_name, str);
153 return 1;
154 }
David Lamparter78dc9c22018-11-12 13:02:19 +0100155 return ly_register_types(plugin, file_name);
156}
157
158API int
159ly_register_types(struct lytype_plugin_list *plugin, const char *log_name)
160{
161 struct lytype_plugin_list *p;
162 uint32_t u, v;
Michal Vaskoc6cd3f02018-03-02 14:07:42 +0100163
164 for (u = 0; plugin[u].name; u++) {
165 /* check user type implementations for collisions */
166 for (v = 0; v < type_plugins_count; v++) {
167 if (!strcmp(plugin[u].name, type_plugins[v].name) &&
168 !strcmp(plugin[u].module, type_plugins[v].module) &&
169 (!plugin[u].revision || !type_plugins[v].revision || !strcmp(plugin[u].revision, type_plugins[v].revision))) {
170 LOGERR(NULL, LY_ESYS, "Processing \"%s\" extension plugin failed,"
171 "implementation collision for extension %s from module %s%s%s.",
David Lamparter78dc9c22018-11-12 13:02:19 +0100172 log_name, plugin[u].name, plugin[u].module, plugin[u].revision ? "@" : "",
Michal Vaskoc6cd3f02018-03-02 14:07:42 +0100173 plugin[u].revision ? plugin[u].revision : "");
174 return 1;
175 }
176 }
177 }
178
179 /* add the new plugins, we have number of new plugins as u */
180 p = realloc(type_plugins, (type_plugins_count + u) * sizeof *type_plugins);
181 if (!p) {
182 LOGMEM(NULL);
183 return -1;
184 }
185 type_plugins = p;
186 for (; u; u--) {
187 memcpy(&type_plugins[type_plugins_count], &plugin[u - 1], sizeof *plugin);
188 type_plugins_count++;
189 }
190
191 return 0;
192}
193
194static int
195lyext_load_plugin(void *dlhandler, const char *file_name)
196{
David Lamparter78dc9c22018-11-12 13:02:19 +0100197 struct lyext_plugin_list *plugin;
Michal Vaskoc6cd3f02018-03-02 14:07:42 +0100198 char *str;
199
Mislav Novakovic7073ef52018-05-24 17:23:02 +0200200#ifdef STATIC
201 return 0;
202#endif /* STATIC */
203
Michal Vaskoc6cd3f02018-03-02 14:07:42 +0100204 /* get the plugin data */
205 plugin = dlsym(dlhandler, file_name);
206 str = dlerror();
207 if (str) {
208 LOGERR(NULL, LY_ESYS, "Processing \"%s\" extension plugin failed, missing plugin list object (%s).", file_name, str);
209 return 1;
210 }
David Lamparter78dc9c22018-11-12 13:02:19 +0100211 return ly_register_exts(plugin, file_name);
212}
213
214API int
215ly_register_exts(struct lyext_plugin_list *plugin, const char *log_name)
216{
217 struct lyext_plugin_list *p;
218 struct lyext_plugin_complex *pluginc;
219 uint32_t u, v;
Michal Vaskoc6cd3f02018-03-02 14:07:42 +0100220
221 for (u = 0; plugin[u].name; u++) {
222 /* check extension implementations for collisions */
223 for (v = 0; v < ext_plugins_count; v++) {
224 if (!strcmp(plugin[u].name, ext_plugins[v].name) &&
225 !strcmp(plugin[u].module, ext_plugins[v].module) &&
226 (!plugin[u].revision || !ext_plugins[v].revision || !strcmp(plugin[u].revision, ext_plugins[v].revision))) {
227 LOGERR(NULL, LY_ESYS, "Processing \"%s\" extension plugin failed,"
228 "implementation collision for extension %s from module %s%s%s.",
David Lamparter78dc9c22018-11-12 13:02:19 +0100229 log_name, plugin[u].name, plugin[u].module, plugin[u].revision ? "@" : "",
Michal Vaskoc6cd3f02018-03-02 14:07:42 +0100230 plugin[u].revision ? plugin[u].revision : "");
231 return 1;
232 }
233 }
234
235 /* check for valid supported substatements in case of complex extension */
236 if (plugin[u].plugin->type == LYEXT_COMPLEX && ((struct lyext_plugin_complex *)plugin[u].plugin)->substmt) {
237 pluginc = (struct lyext_plugin_complex *)plugin[u].plugin;
238 for (v = 0; pluginc->substmt[v].stmt; v++) {
239 if (pluginc->substmt[v].stmt >= LY_STMT_SUBMODULE ||
240 pluginc->substmt[v].stmt == LY_STMT_VERSION ||
241 pluginc->substmt[v].stmt == LY_STMT_YINELEM) {
242 LOGERR(NULL, LY_EINVAL,
243 "Extension plugin \"%s\" (extension %s) allows not supported extension substatement (%s)",
David Lamparter78dc9c22018-11-12 13:02:19 +0100244 log_name, plugin[u].name, ly_stmt_str[pluginc->substmt[v].stmt]);
Michal Vaskoc6cd3f02018-03-02 14:07:42 +0100245 return 1;
246 }
247 if (pluginc->substmt[v].cardinality > LY_STMT_CARD_MAND &&
248 pluginc->substmt[v].stmt >= LY_STMT_MODIFIER &&
249 pluginc->substmt[v].stmt <= LY_STMT_STATUS) {
250 LOGERR(NULL, LY_EINVAL, "Extension plugin \"%s\" (extension %s) allows multiple instances on \"%s\" "
251 "substatement, which is not supported.",
David Lamparter78dc9c22018-11-12 13:02:19 +0100252 log_name, plugin[u].name, ly_stmt_str[pluginc->substmt[v].stmt]);
Michal Vaskoc6cd3f02018-03-02 14:07:42 +0100253 return 1;
254 }
255 }
256 }
257 }
258
259 /* add the new plugins, we have number of new plugins as u */
260 p = realloc(ext_plugins, (ext_plugins_count + u) * sizeof *ext_plugins);
261 if (!p) {
262 LOGMEM(NULL);
263 return -1;
264 }
265 ext_plugins = p;
266 for (; u; u--) {
267 memcpy(&ext_plugins[ext_plugins_count], &plugin[u - 1], sizeof *plugin);
268 ext_plugins_count++;
269 }
270
271 return 0;
272}
273
Michal Vasko3b887ec2018-05-31 09:31:46 +0200274/* spends name */
275static void
276ly_add_loaded_plugin(char *name)
277{
278 loaded_plugins = ly_realloc(loaded_plugins, (loaded_plugins_count + 2) * sizeof *loaded_plugins);
279 LY_CHECK_ERR_RETURN(!loaded_plugins, free(name); LOGMEM(NULL), );
280 ++loaded_plugins_count;
281
282 loaded_plugins[loaded_plugins_count - 1] = name;
283 loaded_plugins[loaded_plugins_count] = NULL;
284}
285
Michal Vaskoc6cd3f02018-03-02 14:07:42 +0100286static void
287ly_load_plugins_dir(DIR *dir, const char *dir_path, int ext_or_type)
288{
289 struct dirent *file;
290 size_t len;
Michal Vasko3b887ec2018-05-31 09:31:46 +0200291 char *str, *name;
Michal Vaskoc6cd3f02018-03-02 14:07:42 +0100292 void *dlhandler;
293 int ret;
294
Mislav Novakovic61c79082018-05-21 13:08:24 +0200295#ifdef STATIC
296 return;
297#endif /* STATIC */
298
Michal Vaskoc6cd3f02018-03-02 14:07:42 +0100299 while ((file = readdir(dir))) {
300 /* required format of the filename is *LY_PLUGIN_SUFFIX */
301 len = strlen(file->d_name);
302 if (len < LY_PLUGIN_SUFFIX_LEN + 1 ||
303 strcmp(&file->d_name[len - LY_PLUGIN_SUFFIX_LEN], LY_PLUGIN_SUFFIX)) {
304 continue;
305 }
306
Michal Vaskoc6cd3f02018-03-02 14:07:42 +0100307 /* and construct the filepath */
Michal Vasko87e29a82018-05-21 13:50:43 +0200308 if (asprintf(&str, "%s/%s", dir_path, file->d_name) == -1) {
309 LOGMEM(NULL);
310 return;
311 }
Michal Vaskoc6cd3f02018-03-02 14:07:42 +0100312
Radek Krejcib1473bb2018-04-18 09:23:17 +0200313 /* load the plugin */
314 dlhandler = dlopen(str, RTLD_NOW);
315 if (!dlhandler) {
316 LOGERR(NULL, LY_ESYS, "Loading \"%s\" as a plugin failed (%s).", str, dlerror());
317 free(str);
318 continue;
319 }
320 if (ly_set_contains(&dlhandlers, dlhandler) != -1) {
Michal Vaskoc6cd3f02018-03-02 14:07:42 +0100321 /* the plugin is already loaded */
322 LOGVRB("Plugin \"%s\" already loaded.", str);
323 free(str);
324
325 /* keep the refcount of the shared object correct */
326 dlclose(dlhandler);
327 continue;
328 }
Michal Vaskoc6cd3f02018-03-02 14:07:42 +0100329 dlerror(); /* Clear any existing error */
330
Michal Vasko3b887ec2018-05-31 09:31:46 +0200331 /* store the name without the suffix */
332 name = strndup(file->d_name, len - LY_PLUGIN_SUFFIX_LEN);
333 if (!name) {
334 LOGMEM(NULL);
Michal Vasko8c8e1db2018-08-17 10:30:16 +0200335 dlclose(dlhandler);
Michal Vasko3b887ec2018-05-31 09:31:46 +0200336 free(str);
337 return;
338 }
339
Michal Vaskoc6cd3f02018-03-02 14:07:42 +0100340 if (ext_or_type) {
341 ret = lyext_load_plugin(dlhandler, name);
342 } else {
343 ret = lytype_load_plugin(dlhandler, name);
344 }
Michal Vasko3b887ec2018-05-31 09:31:46 +0200345 if (!ret) {
346 LOGVRB("Plugin \"%s\" successfully loaded.", str);
347 /* spends name */
348 ly_add_loaded_plugin(name);
349 /* keep the handler */
350 ly_set_add(&dlhandlers, dlhandler, LY_SET_OPT_USEASLIST);
351 } else {
352 free(name);
Michal Vaskoc6cd3f02018-03-02 14:07:42 +0100353 dlclose(dlhandler);
Michal Vaskoc6cd3f02018-03-02 14:07:42 +0100354 }
Michal Vaskoe65f39a2018-04-25 11:29:10 +0200355 free(str);
Michal Vaskoc6cd3f02018-03-02 14:07:42 +0100356
Michal Vasko3b887ec2018-05-31 09:31:46 +0200357 if (ret == -1) {
358 /* finish on error */
359 break;
360 }
Michal Vaskoc6cd3f02018-03-02 14:07:42 +0100361 }
362}
363
364API void
365ly_load_plugins(void)
366{
367 DIR* dir;
368 const char *pluginsdir;
369
Mislav Novakovic61c79082018-05-21 13:08:24 +0200370#ifdef STATIC
371 /* lock the extension plugins list */
372 pthread_mutex_lock(&plugins_lock);
373
374 ext_plugins = static_load_lyext_plugins(&ext_plugins_count);
375 type_plugins = static_load_lytype_plugins(&type_plugins_count);
376
Mislav Novakovic74726932018-06-04 13:14:03 +0200377 int u;
378 for (u = 0; u < static_loaded_plugins_count; u++) {
379 ly_add_loaded_plugin(strdup(static_loaded_plugins[u]));
380 }
381
Mislav Novakovic61c79082018-05-21 13:08:24 +0200382 /* unlock the global structures */
383 pthread_mutex_unlock(&plugins_lock);
384 return;
385#endif /* STATIC */
386
Michal Vaskoc6cd3f02018-03-02 14:07:42 +0100387 /* lock the extension plugins list */
388 pthread_mutex_lock(&plugins_lock);
389
390 /* increase references */
391 ++plugin_refs;
392
393 /* try to get the plugins directory from environment variable */
394 pluginsdir = getenv("LIBYANG_EXTENSIONS_PLUGINS_DIR");
395 if (!pluginsdir) {
396 pluginsdir = LYEXT_PLUGINS_DIR;
397 }
398
399 dir = opendir(pluginsdir);
400 if (!dir) {
401 /* no directory (or no access to it), no extension plugins */
402 LOGWRN(NULL, "Failed to open libyang extensions plugins directory \"%s\" (%s).", pluginsdir, strerror(errno));
403 } else {
404 ly_load_plugins_dir(dir, pluginsdir, 1);
405 closedir(dir);
406 }
407
408 /* try to get the plugins directory from environment variable */
409 pluginsdir = getenv("LIBYANG_USER_TYPES_PLUGINS_DIR");
410 if (!pluginsdir) {
411 pluginsdir = LY_USER_TYPES_PLUGINS_DIR;
412 }
413
414 dir = opendir(pluginsdir);
415 if (!dir) {
416 /* no directory (or no access to it), no extension plugins */
417 LOGWRN(NULL, "Failed to open libyang user types plugins directory \"%s\" (%s).", pluginsdir, strerror(errno));
418 } else {
419 ly_load_plugins_dir(dir, pluginsdir, 0);
420 closedir(dir);
421 }
422
423 /* unlock the global structures */
424 pthread_mutex_unlock(&plugins_lock);
425}
426
427struct lyext_plugin *
428ext_get_plugin(const char *name, const char *module, const char *revision)
429{
430 uint16_t u;
431
432 assert(name);
433 assert(module);
434
435 for (u = 0; u < ext_plugins_count; u++) {
436 if (!strcmp(name, ext_plugins[u].name) &&
437 !strcmp(module, ext_plugins[u].module) &&
438 (!ext_plugins[u].revision || !strcmp(revision, ext_plugins[u].revision))) {
439 /* we have the match */
440 return ext_plugins[u].plugin;
441 }
442 }
443
444 /* plugin not found */
445 return NULL;
446}
447
448API int
Michal Vaskoa310a452018-11-21 12:34:29 +0100449lys_ext_instance_presence(struct lys_ext *def, struct lys_ext_instance **ext, uint8_t ext_size)
Michal Vaskoc6cd3f02018-03-02 14:07:42 +0100450{
Michal Vaskoa310a452018-11-21 12:34:29 +0100451 uint8_t index;
Michal Vaskoc6cd3f02018-03-02 14:07:42 +0100452
453 if (!def || (ext_size && !ext)) {
454 LOGARG;
455 return -1;
456 }
457
458 /* search for the extension instance */
459 for (index = 0; index < ext_size; index++) {
Michal Vaskoe5805292018-07-11 08:56:14 +0200460 if (ext[index]->module->ctx == def->module->ctx) {
461 /* from the same context */
462 if (ext[index]->def == def) {
463 return index;
464 }
465 } else {
466 /* from different contexts */
467 if (ly_strequal0(ext[index]->def->name, def->name)
468 && ly_strequal0(lys_main_module(ext[index]->def->module)->name, lys_main_module(def->module)->name)) {
469 return index;
470 }
Michal Vaskoc6cd3f02018-03-02 14:07:42 +0100471 }
472 }
473
474 /* not found */
475 return -1;
476}
477
478API void *
479lys_ext_complex_get_substmt(LY_STMT stmt, struct lys_ext_instance_complex *ext, struct lyext_substmt **info)
480{
481 int i;
482
483 if (!ext || !ext->def || !ext->def->plugin || ext->def->plugin->type != LYEXT_COMPLEX) {
484 LOGARG;
485 return NULL;
486 }
487
488 if (!ext->substmt) {
489 /* no substatement defined in the plugin */
490 if (info) {
491 *info = NULL;
492 }
493 return NULL;
494 }
495
496 /* search the substatements defined by the plugin */
497 for (i = 0; ext->substmt[i].stmt; i++) {
498 if (stmt == LY_STMT_NODE) {
499 if (ext->substmt[i].stmt >= LY_STMT_ACTION && ext->substmt[i].stmt <= LY_STMT_USES) {
500 if (info) {
501 *info = &ext->substmt[i];
502 }
503 break;
504 }
505 } else if (ext->substmt[i].stmt == stmt) {
506 if (info) {
507 *info = &ext->substmt[i];
508 }
509 break;
510 }
511 }
512
513 if (ext->substmt[i].stmt) {
514 return &ext->content[ext->substmt[i].offset];
515 } else {
516 return NULL;
517 }
518}
519
520LY_STMT
521lys_snode2stmt(LYS_NODE nodetype)
522{
523 switch(nodetype) {
524 case LYS_CONTAINER:
525 return LY_STMT_CONTAINER;
526 case LYS_CHOICE:
527 return LY_STMT_CHOICE;
528 case LYS_LEAF:
529 return LY_STMT_LEAF;
530 case LYS_LEAFLIST:
531 return LY_STMT_LEAFLIST;
532 case LYS_LIST:
533 return LY_STMT_LIST;
534 case LYS_ANYXML:
535 case LYS_ANYDATA:
536 return LY_STMT_ANYDATA;
537 case LYS_CASE:
538 return LY_STMT_CASE;
539 case LYS_NOTIF:
540 return LY_STMT_NOTIFICATION;
541 case LYS_RPC:
542 return LY_STMT_RPC;
543 case LYS_INPUT:
544 return LY_STMT_INPUT;
545 case LYS_OUTPUT:
546 return LY_STMT_OUTPUT;
547 case LYS_GROUPING:
548 return LY_STMT_GROUPING;
549 case LYS_USES:
550 return LY_STMT_USES;
551 case LYS_AUGMENT:
552 return LY_STMT_AUGMENT;
553 case LYS_ACTION:
554 return LY_STMT_ACTION;
555 default:
556 return LY_STMT_NODE;
557 }
558}
559
560static struct lytype_plugin_list *
561lytype_find(const char *module, const char *revision, const char *type_name)
562{
563 uint16_t u;
564
565 for (u = 0; u < type_plugins_count; ++u) {
566 if (ly_strequal(module, type_plugins[u].module, 0) && ((!revision && !type_plugins[u].revision)
567 || (revision && ly_strequal(revision, type_plugins[u].revision, 0)))
568 && ly_strequal(type_name, type_plugins[u].name, 0)) {
569 return &(type_plugins[u]);
570 }
571 }
572
573 return NULL;
574}
575
576int
577lytype_store(const struct lys_module *mod, const char *type_name, const char *value_str, lyd_val *value)
578{
579 struct lytype_plugin_list *p;
580 char *err_msg = NULL;
581
582 assert(mod && type_name && value_str && value);
583
584 p = lytype_find(mod->name, mod->rev_size ? mod->rev[0].date : NULL, type_name);
585 if (p) {
586 if (p->store_clb(type_name, value_str, value, &err_msg)) {
587 if (!err_msg) {
588 if (asprintf(&err_msg, "Failed to store value \"%s\" of user type \"%s\".", value_str, type_name) == -1) {
589 LOGMEM(mod->ctx);
590 return -1;
591 }
592 }
593 LOGERR(mod->ctx, LY_EPLUGIN, err_msg);
594 free(err_msg);
595 return -1;
596 }
597
598 /* value successfully stored */
599 return 0;
600 }
601
602 return 1;
603}
604
605void
606lytype_free(const struct lys_module *mod, const char *type_name, lyd_val value)
607{
608 struct lytype_plugin_list *p;
609
610 p = lytype_find(mod->name, mod->rev_size ? mod->rev[0].date : NULL, type_name);
611 if (!p) {
612 LOGINT(mod->ctx);
613 return;
614 }
615
616 if (p->free_clb) {
617 p->free_clb(value.ptr);
618 }
619}