blob: d367fe73cc08c57847deeb25676386bd48a7ea30 [file] [log] [blame]
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +01001/**
2 * @file schema_mount.c
3 * @author Tadeas Vintrlik <xvintr04@stud.fit.vutbr.cz>
4 * @brief libyang extension plugin - Schema Mount (RFC 8528)
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 <assert.h>
Michal Vaskoddd76592022-01-17 13:34:48 +010018#include <pthread.h>
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +010019#include <stdint.h>
20#include <stdlib.h>
21#include <string.h>
22
23#include "dict.h"
24#include "libyang.h"
25#include "log.h"
26#include "plugins_exts.h"
27#include "tree_data.h"
28#include "tree_schema.h"
29
30/**
Michal Vaskoddd76592022-01-17 13:34:48 +010031 * @brief Internal schema mount data structure for holding all the contexts of parsed data.
32 */
33struct lyplg_ext_sm {
34 struct lyplg_ext_sm_shared {
35 pthread_mutex_t lock; /**< lock for accessing this shared structure */
36
37 struct {
38 struct ly_ctx *ctx; /**< context shared between all data of this mount point */
39 const char *mount_point; /**< mount point name */
40 const char *content_id; /**< yang-library content-id (alternatively module-set-id),
41 stored in the dictionary of the ext instance context */
42 } *schemas; /**< array of shared schema schemas */
43 uint32_t schema_count; /**< length of schemas array */
44 uint32_t ref_count; /**< number of references to this structure (mount-points with the same name
45 in the module) */
46 } *shared; /**< shared schema mount points */
47
48 struct lyplg_ext_sm_inln {
49 struct {
50 struct ly_ctx *ctx; /**< context created for single inline schema data */
51 } *schemas; /**< array of inline schemas */
52 uint32_t schema_count; /**< length of schemas array */
53 } inln; /**< inline mount points */
54};
55
56#define EXT_LOGERR_MEM_RET(ext) \
57 lyplg_ext_log(ext, LY_LLERR, LY_EMEM, NULL, "Memory allocation failed (%s:%d).", __FILE__, __LINE__); \
58 return LY_EMEM
59
60#define EXT_LOGERR_MEM_GOTO(ext, rc, label) \
61 lyplg_ext_log(ext, LY_LLERR, LY_EMEM, NULL, "Memory allocation failed (%s:%d).", __FILE__, __LINE__); \
62 rc = LY_EMEM; \
63 goto label
64
65#define EXT_LOGERR_INT_RET(ext) \
66 lyplg_ext_log(ext, LY_LLERR, LY_EINT, NULL, "Internal error (%s:%d).", __FILE__, __LINE__); \
67 return LY_EINT
68
69/**
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +010070 * @brief Check if given mount point is unique among its' siblings
71 *
72 * @param cctx Compilation context.
73 * @param c_ext Compiled extension instance for checking uniqueness.
74 * @param p_ext Extension instance of the mount-point for comparison.
Michal Vaskoddd76592022-01-17 13:34:48 +010075 * @return LY_SUCCESS if is unique;
76 * @return LY_EINVAL otherwise.
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +010077 */
78static LY_ERR
Michal Vaskoddd76592022-01-17 13:34:48 +010079schema_mount_compile_unique_mp(struct lysc_ctx *cctx, const struct lysc_ext_instance *c_ext,
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +010080 const struct lysp_ext_instance *p_ext)
81{
Michal Vaskoddd76592022-01-17 13:34:48 +010082 struct lysc_ext_instance *exts;
83 LY_ARRAY_COUNT_TYPE u;
84 struct lysc_node *parent;
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +010085
Michal Vaskoddd76592022-01-17 13:34:48 +010086 /* check if it is the only instance of the mount-point among its' siblings */
87 parent = (struct lysc_node *)c_ext->parent;
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +010088 exts = parent->exts;
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +010089 LY_ARRAY_FOR(exts, u) {
Michal Vaskoddd76592022-01-17 13:34:48 +010090 if (&exts[u] == c_ext) {
91 continue;
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +010092 }
Michal Vaskoddd76592022-01-17 13:34:48 +010093
94 if (!strcmp(exts[u].def->module->name, "ietf-yang-schema-mount") && !strcmp(exts[u].def->name, "mount-point")) {
95 lyplg_ext_log(c_ext, LY_LLERR, LY_EVALID, lysc_ctx_get_path(cctx), "Multiple extension \"%s\" instances.",
96 p_ext->name);
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +010097 return LY_EINVAL;
98 }
99 }
100 return LY_SUCCESS;
101}
102
Michal Vaskoddd76592022-01-17 13:34:48 +0100103struct lyplg_ext_sm_shared_cb_data {
104 const struct lysc_ext_instance *ext;
105 struct lyplg_ext_sm_shared *sm_shared;
106};
107
108static LY_ERR
109schema_mount_compile_mod_dfs_cb(struct lysc_node *node, void *data, ly_bool *dfs_continue)
110{
111 struct lyplg_ext_sm_shared_cb_data *cb_data = data;
112 struct lyplg_ext_sm *sm_data;
113 struct lysc_ext_instance *exts;
114 LY_ARRAY_COUNT_TYPE u;
115
116 (void)dfs_continue;
117
118 if (node == cb_data->ext->parent) {
119 /* parent of the current compiled extension, skip */
120 return LY_SUCCESS;
121 }
122
123 /* find the same mount point */
124 exts = node->exts;
125 LY_ARRAY_FOR(exts, u) {
126 if (!strcmp(exts[u].def->module->name, "ietf-yang-schema-mount") && !strcmp(exts[u].def->name, "mount-point") &&
127 (exts[u].argument == cb_data->ext->argument)) {
128 /* same mount point, break the DFS search */
129 sm_data = exts[u].data;
130 cb_data->sm_shared = sm_data->shared;
131 return LY_EEXIST;
132 }
133 }
134
135 /* not found, continue search */
136 return LY_SUCCESS;
137}
138
139static struct lyplg_ext_sm_shared *
140schema_mount_compile_find_shared(const struct lys_module *mod, const struct lysc_ext_instance *ext)
141{
142 struct lyplg_ext_sm_shared_cb_data cb_data;
143 LY_ERR r;
144
145 /* prepare cb_data */
146 cb_data.ext = ext;
147 cb_data.sm_shared = NULL;
148
149 /* try to find the same mount point */
150 r = lysc_module_dfs_full(mod, schema_mount_compile_mod_dfs_cb, &cb_data);
151 assert((!r && !cb_data.sm_shared) || ((r == LY_EEXIST) && cb_data.sm_shared));
152
153 return cb_data.sm_shared;
154}
155
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100156/**
157 * @brief Schema mount compile.
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100158 * Checks if it can be a valid extension instance for yang schema mount.
159 *
160 * Implementation of ::lyplg_ext_compile_clb callback set as lyext_plugin::compile.
161 */
162static LY_ERR
Michal Vaskoddd76592022-01-17 13:34:48 +0100163schema_mount_compile(struct lysc_ctx *cctx, const struct lysp_ext_instance *p_ext, struct lysc_ext_instance *c_ext)
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100164{
165 const struct lys_module *cur_mod;
Michal Vaskoddd76592022-01-17 13:34:48 +0100166 struct lyplg_ext_sm *sm_data;
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100167
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100168 assert(!strcmp(p_ext->name, "yangmnt:mount-point"));
169
Michal Vaskoddd76592022-01-17 13:34:48 +0100170 /* check YANG version 1.1 */
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100171 cur_mod = lysc_ctx_get_cur_mod(cctx);
172 if (cur_mod->parsed->version != LYS_VERSION_1_1) {
Michal Vaskoddd76592022-01-17 13:34:48 +0100173 lyplg_ext_log(c_ext, LY_LLERR, LY_EVALID, lysc_ctx_get_path(cctx),
174 "Extension \"%s\" instance not allowed in YANG version 1 module.", p_ext->name);
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100175 return LY_EINVAL;
176 }
177
Michal Vaskoddd76592022-01-17 13:34:48 +0100178 /* check parent nodetype */
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100179 if ((p_ext->parent_stmt != LY_STMT_CONTAINER) && (p_ext->parent_stmt != LY_STMT_LIST)) {
Michal Vaskoddd76592022-01-17 13:34:48 +0100180 lyplg_ext_log(c_ext, LY_LLERR, LY_EVALID, lysc_ctx_get_path(cctx),
181 "Extension \"%s\" instance allowed only in container or list statement.", p_ext->name);
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100182 return LY_EINVAL;
183 }
184
Michal Vaskoddd76592022-01-17 13:34:48 +0100185 /* check uniqueness */
186 if (schema_mount_compile_unique_mp(cctx, c_ext, p_ext)) {
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100187 return LY_EINVAL;
188 }
189
Michal Vaskoddd76592022-01-17 13:34:48 +0100190 /* init internal data */
191 sm_data = calloc(1, sizeof *sm_data);
192 if (!sm_data) {
193 EXT_LOGERR_MEM_RET(c_ext);
194 }
195 c_ext->data = sm_data;
196
197 /* reuse/init shared schema */
198 sm_data->shared = schema_mount_compile_find_shared(c_ext->module, c_ext);
199 if (sm_data->shared) {
200 ++sm_data->shared->ref_count;
201 } else {
202 sm_data->shared = calloc(1, sizeof *sm_data->shared);
203 if (!sm_data->shared) {
204 free(sm_data);
205 EXT_LOGERR_MEM_RET(c_ext);
206 }
207 pthread_mutex_init(&sm_data->shared->lock, NULL);
208 sm_data->shared->ref_count = 1;
209 }
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100210
211 return LY_SUCCESS;
212}
213
214/**
Michal Vaskoddd76592022-01-17 13:34:48 +0100215 * @brief Learn details about the current mount point.
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100216 *
Michal Vaskoddd76592022-01-17 13:34:48 +0100217 * @param[in] ext Compiled extension instance.
218 * @param[in] ext_data Extension data retrieved by the callback.
219 * @param[out] config Whether the whole schema should keep its config or be set to false.
220 * @param[out] shared Whether the schema is shared or inline.
221 * @return LY_ERR value.
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100222 */
223static LY_ERR
Michal Vaskoddd76592022-01-17 13:34:48 +0100224schema_mount_get_smount(const struct lysc_ext_instance *ext, const struct lyd_node *ext_data, ly_bool *config,
225 ly_bool *shared)
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100226{
Michal Vaskoddd76592022-01-17 13:34:48 +0100227 struct lyd_node *mpoint, *node;
228 char *path = NULL;
229 LY_ERR r;
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100230
Michal Vaskoddd76592022-01-17 13:34:48 +0100231 /* find the mount point */
232 if (asprintf(&path, "/ietf-yang-schema-mount:schema-mounts/mount-point[module='%s'][label='%s']", ext->module->name,
233 ext->argument) == -1) {
234 EXT_LOGERR_MEM_RET(ext);
235 }
236 r = ext_data ? lyd_find_path(ext_data, path, 0, &mpoint) : LY_ENOTFOUND;
237 free(path);
238 if (r) {
239 /* missing mount-point, cannot be data for this extension (https://datatracker.ietf.org/doc/html/rfc8528#page-10) */
240 return LY_ENOT;
241 }
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100242
Michal Vaskoddd76592022-01-17 13:34:48 +0100243 /* check config */
244 if (!lyd_find_path(mpoint, "config", 0, &node) && !strcmp(lyd_get_value(node), "false")) {
245 *config = 0;
246 } else {
247 *config = 1;
248 }
249
250 /* check schema-ref */
251 if (lyd_find_path(mpoint, "shared-schema", 0, NULL)) {
252 if (lyd_find_path(mpoint, "inline", 0, NULL)) {
253 EXT_LOGERR_INT_RET(ext);
254 }
255 *shared = 0;
256 } else {
257 *shared = 1;
258 }
259
260 return LY_SUCCESS;
261}
262
263/**
264 * @brief Create schema (context) based on retrieved extension data.
265 *
266 * @param[in] ext Compiled extension instance.
267 * @param[in] ext_data Extension data retrieved by the callback.
268 * @param[in] config Whether the whole schema should keep its config or be set to false.
269 * @param[out] ext_ctx Schema to use for parsing the data.
270 * @return LY_ERR value.
271 */
272static LY_ERR
273schema_mount_create_ctx(const struct lysc_ext_instance *ext, const struct lyd_node *ext_data, ly_bool config,
274 struct ly_ctx **ext_ctx)
275{
276 LY_ERR r;
277 const char * const *searchdirs;
278 const struct lys_module *mod;
279 struct lysc_node *root, *node;
280 uint32_t idx = 0;
281
282 /* get searchdirs from the current context */
283 searchdirs = ly_ctx_get_searchdirs(ext->module->ctx);
284
285 /* create the context based on the data */
286 if ((r = ly_ctx_new_yldata(searchdirs ? searchdirs[0] : NULL, ext_data, ly_ctx_get_options(ext->module->ctx), ext_ctx))) {
287 lyplg_ext_log(ext, LY_LLERR, r, NULL, "Failed to create context for the schema-mount data.");
288 return r;
289 }
290
291 if (!config) {
292 /* manually change the config of all schema nodes in all the modules */
293 while ((mod = ly_ctx_get_module_iter(*ext_ctx, &idx))) {
294 if (!mod->implemented) {
295 continue;
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100296 }
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100297
Michal Vaskoddd76592022-01-17 13:34:48 +0100298 LY_LIST_FOR(mod->compiled->data, root) {
299 LYSC_TREE_DFS_BEGIN(root, node) {
300 node->flags &= ~LYS_CONFIG_W;
301 node->flags |= LYS_CONFIG_R;
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100302
Michal Vaskoddd76592022-01-17 13:34:48 +0100303 LYSC_TREE_DFS_END(root, node);
304 }
305 }
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100306 }
307 }
308
Michal Vaskoddd76592022-01-17 13:34:48 +0100309 return LY_SUCCESS;
310}
311
312/**
313 * @brief Get schema (context) for a shared-schema mount point.
314 *
315 * @param[in] ext Compiled extension instance.
316 * @param[in] ext_data Extension data retrieved by the callback.
317 * @param[in] config Whether the whole schema should keep its config or be set to false.
318 * @param[out] ext_ctx Schema to use for parsing the data.
319 * @return LY_ERR value.
320 */
321static LY_ERR
322schema_mount_get_ctx_shared(struct lysc_ext_instance *ext, const struct lyd_node *ext_data, ly_bool config,
323 const struct ly_ctx **ext_ctx)
324{
325 struct lyplg_ext_sm *sm_data = ext->data;
326 LY_ERR ret = LY_SUCCESS, r;
327 struct lyd_node *node = NULL;
328 struct ly_ctx *new_ctx;
329 uint32_t i;
330 const char *content_id = NULL;
331 void *mem;
332
333 assert(sm_data && sm_data->shared);
334
335 /* get yang-library content-id or module-set-id */
336 if (ext_data) {
337 lyd_find_path(ext_data, "/ietf-yang-library:yang-library/content-id", 0, &node);
338 if (!node) {
339 lyd_find_path(ext_data, "/ietf-yang-library:modules-state/module-set-id", 0, &node);
340 }
341 if (node) {
342 content_id = lyd_get_value(node);
343 }
344 }
345 if (!content_id) {
346 lyplg_ext_log(ext, LY_LLERR, LY_EVALID, NULL, "Missing \"content-id\" or \"module-set-id\" in ietf-yang-library data.");
347 return LY_EVALID;
348 }
349
350 /* LOCK */
351 if ((r = pthread_mutex_lock(&sm_data->shared->lock))) {
352 lyplg_ext_log(ext, LY_LLERR, LY_ESYS, NULL, "Mutex lock failed (%s).", strerror(r));
353 return LY_ESYS;
354 }
355
356 /* try to find this mount point */
357 for (i = 0; i < sm_data->shared->schema_count; ++i) {
358 if (ext->argument == sm_data->shared->schemas[i].mount_point) {
359 break;
360 }
361 }
362
363 if (i < sm_data->shared->schema_count) {
364 /* schema exists already */
365 if (strcmp(content_id, sm_data->shared->schemas[i].content_id)) {
366 lyplg_ext_log(ext, LY_LLERR, LY_EVALID, "/ietf-yang-library:yang-library/content-id",
367 "Shared-schema yang-library content-id \"%s\" differs from \"%s\" used previously.",
368 content_id, sm_data->shared->schemas[i].content_id);
369 ret = LY_EVALID;
370 goto cleanup;
371 }
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100372 } else {
Michal Vaskoddd76592022-01-17 13:34:48 +0100373 /* no schema found, create it */
374 if ((r = schema_mount_create_ctx(ext, ext_data, config, &new_ctx))) {
375 ret = r;
376 goto cleanup;
377 }
378
379 /* new entry */
380 mem = realloc(sm_data->shared->schemas, (i + 1) * sizeof *sm_data->shared->schemas);
381 if (!mem) {
382 ly_ctx_destroy(new_ctx);
383 EXT_LOGERR_MEM_GOTO(ext, ret, cleanup);
384 }
385 sm_data->shared->schemas = mem;
386 ++sm_data->shared->schema_count;
387
388 /* fill entry */
389 sm_data->shared->schemas[i].ctx = new_ctx;
390 sm_data->shared->schemas[i].mount_point = ext->argument;
391 lydict_insert(ext->module->ctx, content_id, 0, &sm_data->shared->schemas[i].content_id);
392 }
393
394 /* use the context */
395 *ext_ctx = sm_data->shared->schemas[i].ctx;
396
397cleanup:
398 /* UNLOCK */
399 pthread_mutex_unlock(&sm_data->shared->lock);
400
401 return ret;
402}
403
404/**
405 * @brief Get schema (context) for an inline mount point.
406 *
407 * @param[in] ext Compiled extension instance.
408 * @param[in] ext_data Extension data retrieved by the callback.
409 * @param[in] config Whether the whole schema should keep its config or be set to false.
410 * @param[out] ext_ctx Schema to use for parsing the data.
411 * @return LY_ERR value.
412 */
413static LY_ERR
414schema_mount_get_ctx_inline(struct lysc_ext_instance *ext, const struct lyd_node *ext_data, ly_bool config,
415 const struct ly_ctx **ext_ctx)
416{
417 struct lyplg_ext_sm *sm_data = ext->data;
418 LY_ERR r;
419 struct ly_ctx *new_ctx;
420 uint32_t i;
421 void *mem;
422
423 assert(sm_data && sm_data->shared);
424
425 i = sm_data->inln.schema_count;
426
427 /* always new schema required, create context */
428 if ((r = schema_mount_create_ctx(ext, ext_data, config, &new_ctx))) {
429 return r;
430 }
431
432 /* new entry */
433 mem = realloc(sm_data->inln.schemas, (i + 1) * sizeof *sm_data->inln.schemas);
434 if (!mem) {
435 ly_ctx_destroy(new_ctx);
436 EXT_LOGERR_MEM_RET(ext);
437 }
438 sm_data->inln.schemas = mem;
439 ++sm_data->inln.schema_count;
440
441 /* fill entry */
442 sm_data->inln.schemas[i].ctx = new_ctx;
443
444 /* use the context */
445 *ext_ctx = sm_data->inln.schemas[i].ctx;
446 return LY_SUCCESS;
447}
448
449/**
450 * @brief Get schema (context) for a mount point.
451 *
452 * @param[in] ext Compiled extension instance.
453 * @param[out] ext_ctx Schema to use for parsing the data.
454 * @return LY_ERR value.
455 */
456static LY_ERR
457schema_mount_get_ctx(struct lysc_ext_instance *ext, const struct ly_ctx **ext_ctx)
458{
459 LY_ERR ret = LY_SUCCESS, r;
460 struct lyd_node *iter, *ext_data = NULL;
461 ly_bool ext_data_free = 0, config, shared;
462
463 *ext_ctx = NULL;
464
465 /* get operational data with ietf-yang-library and ietf-yang-schema-mount data */
466 if ((r = lyplg_ext_get_data(ext->module->ctx, ext, (void **)&ext_data, &ext_data_free))) {
467 ret = r;
468 goto cleanup;
469 }
470
471 LY_LIST_FOR(ext_data, iter) {
472 if (iter->flags & LYD_NEW) {
473 /* must be validated for the parent-reference prefix data to be stored */
474 lyplg_ext_log(ext, LY_LLERR, LY_EINVAL, NULL, "Provided ext data have not been validated.");
475 ret = LY_EINVAL;
476 goto cleanup;
477 }
478 }
479
480 /* learn about this mount point */
481 if ((r = schema_mount_get_smount(ext, ext_data, &config, &shared))) {
482 ret = r;
483 goto cleanup;
484 }
485
486 /* create/get the context for parsing the data */
487 if (shared) {
488 r = schema_mount_get_ctx_shared(ext, ext_data, config, ext_ctx);
489 } else {
490 r = schema_mount_get_ctx_inline(ext, ext_data, config, ext_ctx);
491 }
492 if (r) {
493 ret = r;
494 goto cleanup;
495 }
496
497cleanup:
498 if (ext_data_free) {
499 lyd_free_all(ext_data);
500 }
501 return ret;
502}
503
504/**
505 * @brief Parse callback for schema mount.
506 * Check if data if valid for schema mount and inserts it to the parent.
507 */
508static LY_ERR
509schema_mount_parse(struct ly_in *in, LYD_FORMAT format, struct lysc_ext_instance *ext, struct lyd_node *parent,
510 uint32_t parse_opts)
511{
512 LY_ERR ret = LY_SUCCESS, r;
513 const struct ly_ctx *ext_ctx;
514 struct lyd_node *subtree, *first = NULL;
515 struct ly_err_item *err;
516 uint32_t old_log_opts;
517
518 /* get context based on ietf-yang-library data */
519 if ((r = schema_mount_get_ctx(ext, &ext_ctx))) {
520 return r;
521 }
522
523 /* prepare opts */
524 old_log_opts = ly_log_options(LY_LOSTORE_LAST);
525 assert(parse_opts & LYD_PARSE_ONLY);
526 parse_opts |= LYD_PARSE_SUBTREE;
527
528 do {
529 /* parse by nested subtrees */
530 r = lyd_parse_data(ext_ctx, NULL, in, format, parse_opts, 0, &subtree);
531 if (r && (r != LY_ENOT)) {
532 /* error, maybe valid, maybe not, print as verbose */
533 err = ly_err_first(ext_ctx);
534 if (!err) {
535 lyplg_ext_log(ext, LY_LLVRB, 0, NULL, "Unknown parsing error (err code %d).", r);
536 } else {
537 lyplg_ext_log(ext, LY_LLVRB, 0, NULL, "%s (err code %d).", err->msg, err->no);
538 }
539 ret = LY_ENOT;
540 goto cleanup;
541 }
542
543 /* set the special flag and insert into siblings */
544 subtree->flags |= LYD_EXT;
545 lyd_insert_sibling(first, subtree, &first);
546 } while (r == LY_ENOT);
547
548 /* append to parent */
549 if (first && (r = lyd_insert_ext(parent, first))) {
550 lyplg_ext_log(ext, LY_LLERR, r, NULL, "Failed to append parsed data.");
551 ret = r;
552 goto cleanup;
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100553 }
554
555cleanup:
556 ly_log_options(old_log_opts);
Michal Vaskoddd76592022-01-17 13:34:48 +0100557 if (ret) {
558 lyd_free_siblings(first);
559 }
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100560 return ret;
561}
562
563/**
Michal Vaskoddd76592022-01-17 13:34:48 +0100564 * @brief Duplicate all accessible parent references for a shared-schema mount point.
565 *
566 * @param[in] ext Compiled extension instance.
567 * @param[in] ctx_node Context node for evaluating the parent-reference XPath expressions.
568 * @param[in] ext_data Extension data retrieved by the callback.
569 * @param[in] trg_ctx Mounted data context to use for duplication.
570 * @param[out] ref_set Set of all top-level parent-ref subtrees connected to each other, may be empty.
571 * @return LY_ERR value.
572 */
573static LY_ERR
574schema_mount_dup_parent_ref(const struct lysc_ext_instance *ext, const struct lyd_node *ctx_node,
575 const struct lyd_node *ext_data, const struct ly_ctx *trg_ctx, struct ly_set **ref_set)
576{
577 LY_ERR ret = LY_SUCCESS;
578 char *path = NULL;
579 struct ly_set *set = NULL, *par_set = NULL;
580 struct lyd_node_term *term;
581 struct lyd_node *dup = NULL, *top_node, *first;
582 struct lyd_value_xpath10 *xp_val;
583 uint32_t i, j;
584
585 *ref_set = NULL;
586
587 if (!ext_data) {
588 /* we expect the same ext data as before and there must be some for data to be parsed */
589 lyplg_ext_log(ext, LY_LLERR, LY_EINVAL, NULL, "No ext data provided.");
590 ret = LY_EINVAL;
591 goto cleanup;
592 }
593
594 /* get all parent references of this mount point */
595 if (asprintf(&path, "/ietf-yang-schema-mount:schema-mounts/mount-point[module='%s'][label='%s']"
596 "/shared-schema/parent-reference", ext->module->name, ext->argument) == -1) {
597 EXT_LOGERR_MEM_GOTO(ext, ret, cleanup);
598 }
599 if ((ret = lyd_find_xpath(ext_data, path, &set))) {
600 goto cleanup;
601 }
602
603 /* prepare result set */
604 if ((ret = ly_set_new(ref_set))) {
605 goto cleanup;
606 }
607
608 first = NULL;
609 for (i = 0; i < set->count; ++i) {
610 term = set->objs[i];
611
612 /* get the referenced nodes (subtrees) */
613 LYD_VALUE_GET(&term->value, xp_val);
614 if ((ret = lyd_find_xpath4(ctx_node, ctx_node, lyxp_get_expr(xp_val->exp), xp_val->format, xp_val->prefix_data,
615 NULL, &par_set))) {
616 goto cleanup;
617 }
618
619 for (j = 0; j < par_set->count; ++j) {
620 /* duplicate with parents in the context of the mounted data */
621 if ((ret = lyd_dup_single_to_ctx(par_set->dnodes[j], trg_ctx, NULL,
622 LYD_DUP_RECURSIVE | LYD_DUP_WITH_PARENTS | LYD_DUP_WITH_FLAGS, &dup))) {
623 goto cleanup;
624 }
625
626 /* go top-level */
627 while (dup->parent) {
628 dup = lyd_parent(dup);
629 }
630
631 /* check whether the top-level node exists */
632 if (first) {
633 if ((ret = lyd_find_sibling_first(first, dup, &top_node)) && (ret != LY_ENOTFOUND)) {
634 goto cleanup;
635 }
636 } else {
637 top_node = NULL;
638 }
639
640 if (top_node) {
641 /* merge */
642 ret = lyd_merge_tree(&first, dup, LYD_MERGE_DESTRUCT);
643 dup = NULL;
644 if (ret) {
645 goto cleanup;
646 }
647 } else {
648 /* insert */
649 if ((ret = lyd_insert_sibling(first, dup, &first))) {
650 goto cleanup;
651 }
652
653 /* add into the result set because a new top-level node was added */
654 if ((ret = ly_set_add(*ref_set, dup, 1, NULL))) {
655 goto cleanup;
656 }
657 dup = NULL;
658 }
659 }
660 }
661
662cleanup:
663 free(path);
664 ly_set_free(set, NULL);
665 ly_set_free(par_set, NULL);
666 lyd_free_tree(dup);
667 if (ret && *ref_set) {
668 if ((*ref_set)->count) {
669 lyd_free_siblings((*ref_set)->dnodes[0]);
670 }
671 ly_set_free(*ref_set, NULL);
672 *ref_set = NULL;
673 }
674 return ret;
675}
676
677/**
678 * @brief Validate callback for schema mount.
679 */
680static LY_ERR
681schema_mount_validate(struct lysc_ext_instance *ext, struct lyd_node *sibling, uint32_t val_opts)
682{
683 LY_ERR ret = LY_SUCCESS;
684 uint32_t old_log_opts, i;
685 struct ly_err_item *err;
686 struct lyd_node *iter, *ext_data = NULL, *ref_first = NULL, *orig_parent = lyd_parent(sibling);
687 ly_bool ext_data_free = 0;
688 struct ly_set *ref_set = NULL;
689
690 if (!sibling) {
691 /* some data had to be parsed for this callback to be called */
692 EXT_LOGERR_INT_RET(ext);
693 }
694
695 /* get operational data with ietf-yang-library and ietf-yang-schema-mount data */
696 if ((ret = lyplg_ext_get_data(ext->module->ctx, ext, (void **)&ext_data, &ext_data_free))) {
697 goto cleanup;
698 }
699
700 LY_LIST_FOR(ext_data, iter) {
701 if (iter->flags & LYD_NEW) {
702 /* must be validated for the parent-reference prefix data to be stored */
703 lyplg_ext_log(ext, LY_LLERR, LY_EINVAL, NULL, "Provided ext data have not been validated.");
704 ret = LY_EINVAL;
705 goto cleanup;
706 }
707 }
708
709 /* duplicate the referenced parent nodes into ext context */
710 if ((ret = schema_mount_dup_parent_ref(ext, orig_parent, ext_data, LYD_CTX(sibling), &ref_set))) {
711 goto cleanup;
712 }
713
714 /* create accessible tree, remove LYD_EXT to not call this callback recursively */
715 lyd_unlink_siblings(sibling);
716 LY_LIST_FOR(sibling, iter) {
717 iter->flags &= ~LYD_EXT;
718 }
719 if (ref_set->count) {
720 if ((ret = lyd_insert_sibling(sibling, ref_set->dnodes[0], &sibling))) {
721 goto cleanup;
722 }
723 }
724
725 /* only store messages in the context, log as an extension */
726 old_log_opts = ly_log_options(LY_LOSTORE_LAST);
727
728 /* validate all the data */
729 ret = lyd_validate_all(&sibling, NULL, val_opts, NULL);
730 ly_log_options(old_log_opts);
731
732 /* restore sibling tree */
733 for (i = 0; i < ref_set->count; ++i) {
734 if (ref_set->dnodes[i] == sibling) {
735 sibling = sibling->next;
736 }
737 lyd_free_tree(ref_set->dnodes[i]);
738 }
739 LY_LIST_FOR(sibling, iter) {
740 iter->flags |= LYD_EXT;
741 }
742 lyd_insert_ext(orig_parent, sibling);
743
744 if (ret) {
745 /* log the error in the original context */
746 err = ly_err_first(LYD_CTX(sibling));
747 if (!err) {
748 lyplg_ext_log(ext, LY_LLERR, ret, NULL, "Unknown validation error (err code %d).", ret);
749 } else {
750 lyplg_ext_log(ext, LY_LLERR, err->no, err->path, "%s", err->msg);
751 }
752 goto cleanup;
753 }
754
755cleanup:
756 ly_set_free(ref_set, NULL);
757 lyd_free_siblings(ref_first);
758 if (ext_data_free) {
759 lyd_free_all(ext_data);
760 }
761 return ret;
762}
763
764/**
765 * @brief Schema mount free.
766 *
767 * Implementation of ::lyplg_ext_free_clb callback set as ::lyext_plugin::free.
768 */
769static void
770schema_mount_free(struct ly_ctx *ctx, struct lysc_ext_instance *ext)
771{
772 struct lyplg_ext_sm *sm_data = ext->data;
773 uint32_t i;
774
775 if (!sm_data) {
776 return;
777 }
778
779 if (!--sm_data->shared->ref_count) {
780 for (i = 0; i < sm_data->shared->schema_count; ++i) {
781 ly_ctx_destroy(sm_data->shared->schemas[i].ctx);
782 lydict_remove(ctx, sm_data->shared->schemas[i].content_id);
783 }
784 free(sm_data->shared->schemas);
785 pthread_mutex_destroy(&sm_data->shared->lock);
786 free(sm_data->shared);
787 }
788
789 for (i = 0; i < sm_data->inln.schema_count; ++i) {
790 ly_ctx_destroy(sm_data->inln.schemas[i].ctx);
791 }
792 free(sm_data->inln.schemas);
793 free(sm_data);
794}
795
796/**
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100797 * @brief Plugin descriptions for the Yang Schema Mount extension.
798 *
799 * Note that external plugins are supposed to use:
800 *
801 * LYPLG_EXTENSIONS = {
802 */
803const struct lyplg_ext_record plugins_schema_mount[] = {
804 {
805 .module = "ietf-yang-schema-mount",
806 .revision = "2019-01-14",
807 .name = "mount-point",
808
809 .plugin.id = "libyang 2 - Schema Mount, version 1",
810 .plugin.compile = &schema_mount_compile,
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100811 .plugin.sprinter = NULL,
Michal Vaskoddd76592022-01-17 13:34:48 +0100812 .plugin.free = &schema_mount_free,
813 .plugin.parse = &schema_mount_parse,
814 .plugin.validate = &schema_mount_validate
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100815 },
816 {0} /* terminating zeroed item */
817};