Coverage Report

Created: 2025-12-05 06:31

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}