Coverage Report

Created: 2024-02-29 06:11

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