blob: 5ba5f2be604ffddc36d318a52cebf16904e6e81a [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);
Michal Vasko2a12cd92022-02-14 09:34:54 +0100151 (void)r;
Michal Vaskoddd76592022-01-17 13:34:48 +0100152 assert((!r && !cb_data.sm_shared) || ((r == LY_EEXIST) && cb_data.sm_shared));
153
154 return cb_data.sm_shared;
155}
156
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100157/**
158 * @brief Schema mount compile.
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100159 * Checks if it can be a valid extension instance for yang schema mount.
160 *
161 * Implementation of ::lyplg_ext_compile_clb callback set as lyext_plugin::compile.
162 */
163static LY_ERR
Michal Vaskoddd76592022-01-17 13:34:48 +0100164schema_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 +0100165{
166 const struct lys_module *cur_mod;
Michal Vaskoddd76592022-01-17 13:34:48 +0100167 struct lyplg_ext_sm *sm_data;
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100168
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100169 assert(!strcmp(p_ext->name, "yangmnt:mount-point"));
170
Michal Vaskoddd76592022-01-17 13:34:48 +0100171 /* check YANG version 1.1 */
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100172 cur_mod = lysc_ctx_get_cur_mod(cctx);
173 if (cur_mod->parsed->version != LYS_VERSION_1_1) {
Michal Vaskoddd76592022-01-17 13:34:48 +0100174 lyplg_ext_log(c_ext, LY_LLERR, LY_EVALID, lysc_ctx_get_path(cctx),
175 "Extension \"%s\" instance not allowed in YANG version 1 module.", p_ext->name);
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100176 return LY_EINVAL;
177 }
178
Michal Vaskoddd76592022-01-17 13:34:48 +0100179 /* check parent nodetype */
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100180 if ((p_ext->parent_stmt != LY_STMT_CONTAINER) && (p_ext->parent_stmt != LY_STMT_LIST)) {
Michal Vaskoddd76592022-01-17 13:34:48 +0100181 lyplg_ext_log(c_ext, LY_LLERR, LY_EVALID, lysc_ctx_get_path(cctx),
182 "Extension \"%s\" instance allowed only in container or list statement.", p_ext->name);
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100183 return LY_EINVAL;
184 }
185
Michal Vaskoddd76592022-01-17 13:34:48 +0100186 /* check uniqueness */
187 if (schema_mount_compile_unique_mp(cctx, c_ext, p_ext)) {
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100188 return LY_EINVAL;
189 }
190
Michal Vaskoddd76592022-01-17 13:34:48 +0100191 /* init internal data */
192 sm_data = calloc(1, sizeof *sm_data);
193 if (!sm_data) {
194 EXT_LOGERR_MEM_RET(c_ext);
195 }
196 c_ext->data = sm_data;
197
198 /* reuse/init shared schema */
199 sm_data->shared = schema_mount_compile_find_shared(c_ext->module, c_ext);
200 if (sm_data->shared) {
201 ++sm_data->shared->ref_count;
202 } else {
203 sm_data->shared = calloc(1, sizeof *sm_data->shared);
204 if (!sm_data->shared) {
205 free(sm_data);
206 EXT_LOGERR_MEM_RET(c_ext);
207 }
208 pthread_mutex_init(&sm_data->shared->lock, NULL);
209 sm_data->shared->ref_count = 1;
210 }
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100211
212 return LY_SUCCESS;
213}
214
215/**
Michal Vaskoddd76592022-01-17 13:34:48 +0100216 * @brief Learn details about the current mount point.
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100217 *
Michal Vaskoddd76592022-01-17 13:34:48 +0100218 * @param[in] ext Compiled extension instance.
219 * @param[in] ext_data Extension data retrieved by the callback.
220 * @param[out] config Whether the whole schema should keep its config or be set to false.
221 * @param[out] shared Whether the schema is shared or inline.
222 * @return LY_ERR value.
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100223 */
224static LY_ERR
Michal Vaskoddd76592022-01-17 13:34:48 +0100225schema_mount_get_smount(const struct lysc_ext_instance *ext, const struct lyd_node *ext_data, ly_bool *config,
226 ly_bool *shared)
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100227{
Michal Vaskoddd76592022-01-17 13:34:48 +0100228 struct lyd_node *mpoint, *node;
229 char *path = NULL;
230 LY_ERR r;
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100231
Michal Vaskoddd76592022-01-17 13:34:48 +0100232 /* find the mount point */
233 if (asprintf(&path, "/ietf-yang-schema-mount:schema-mounts/mount-point[module='%s'][label='%s']", ext->module->name,
234 ext->argument) == -1) {
235 EXT_LOGERR_MEM_RET(ext);
236 }
237 r = ext_data ? lyd_find_path(ext_data, path, 0, &mpoint) : LY_ENOTFOUND;
238 free(path);
239 if (r) {
240 /* missing mount-point, cannot be data for this extension (https://datatracker.ietf.org/doc/html/rfc8528#page-10) */
241 return LY_ENOT;
242 }
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100243
Michal Vaskoddd76592022-01-17 13:34:48 +0100244 /* check config */
245 if (!lyd_find_path(mpoint, "config", 0, &node) && !strcmp(lyd_get_value(node), "false")) {
246 *config = 0;
247 } else {
248 *config = 1;
249 }
250
251 /* check schema-ref */
252 if (lyd_find_path(mpoint, "shared-schema", 0, NULL)) {
253 if (lyd_find_path(mpoint, "inline", 0, NULL)) {
254 EXT_LOGERR_INT_RET(ext);
255 }
256 *shared = 0;
257 } else {
258 *shared = 1;
259 }
260
261 return LY_SUCCESS;
262}
263
264/**
265 * @brief Create schema (context) based on retrieved extension data.
266 *
267 * @param[in] ext Compiled extension instance.
268 * @param[in] ext_data Extension data retrieved by the callback.
269 * @param[in] config Whether the whole schema should keep its config or be set to false.
270 * @param[out] ext_ctx Schema to use for parsing the data.
271 * @return LY_ERR value.
272 */
273static LY_ERR
274schema_mount_create_ctx(const struct lysc_ext_instance *ext, const struct lyd_node *ext_data, ly_bool config,
275 struct ly_ctx **ext_ctx)
276{
277 LY_ERR r;
278 const char * const *searchdirs;
279 const struct lys_module *mod;
280 struct lysc_node *root, *node;
281 uint32_t idx = 0;
282
283 /* get searchdirs from the current context */
284 searchdirs = ly_ctx_get_searchdirs(ext->module->ctx);
285
286 /* create the context based on the data */
287 if ((r = ly_ctx_new_yldata(searchdirs ? searchdirs[0] : NULL, ext_data, ly_ctx_get_options(ext->module->ctx), ext_ctx))) {
288 lyplg_ext_log(ext, LY_LLERR, r, NULL, "Failed to create context for the schema-mount data.");
289 return r;
290 }
291
292 if (!config) {
293 /* manually change the config of all schema nodes in all the modules */
294 while ((mod = ly_ctx_get_module_iter(*ext_ctx, &idx))) {
295 if (!mod->implemented) {
296 continue;
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100297 }
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100298
Michal Vaskoddd76592022-01-17 13:34:48 +0100299 LY_LIST_FOR(mod->compiled->data, root) {
300 LYSC_TREE_DFS_BEGIN(root, node) {
301 node->flags &= ~LYS_CONFIG_W;
302 node->flags |= LYS_CONFIG_R;
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100303
Michal Vaskoddd76592022-01-17 13:34:48 +0100304 LYSC_TREE_DFS_END(root, node);
305 }
306 }
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100307 }
308 }
309
Michal Vaskoddd76592022-01-17 13:34:48 +0100310 return LY_SUCCESS;
311}
312
313/**
314 * @brief Get schema (context) for a shared-schema mount point.
315 *
316 * @param[in] ext Compiled extension instance.
317 * @param[in] ext_data Extension data retrieved by the callback.
318 * @param[in] config Whether the whole schema should keep its config or be set to false.
319 * @param[out] ext_ctx Schema to use for parsing the data.
320 * @return LY_ERR value.
321 */
322static LY_ERR
323schema_mount_get_ctx_shared(struct lysc_ext_instance *ext, const struct lyd_node *ext_data, ly_bool config,
324 const struct ly_ctx **ext_ctx)
325{
326 struct lyplg_ext_sm *sm_data = ext->data;
327 LY_ERR ret = LY_SUCCESS, r;
328 struct lyd_node *node = NULL;
329 struct ly_ctx *new_ctx;
330 uint32_t i;
331 const char *content_id = NULL;
332 void *mem;
333
334 assert(sm_data && sm_data->shared);
335
336 /* get yang-library content-id or module-set-id */
337 if (ext_data) {
338 lyd_find_path(ext_data, "/ietf-yang-library:yang-library/content-id", 0, &node);
339 if (!node) {
340 lyd_find_path(ext_data, "/ietf-yang-library:modules-state/module-set-id", 0, &node);
341 }
342 if (node) {
343 content_id = lyd_get_value(node);
344 }
345 }
346 if (!content_id) {
347 lyplg_ext_log(ext, LY_LLERR, LY_EVALID, NULL, "Missing \"content-id\" or \"module-set-id\" in ietf-yang-library data.");
348 return LY_EVALID;
349 }
350
351 /* LOCK */
352 if ((r = pthread_mutex_lock(&sm_data->shared->lock))) {
353 lyplg_ext_log(ext, LY_LLERR, LY_ESYS, NULL, "Mutex lock failed (%s).", strerror(r));
354 return LY_ESYS;
355 }
356
357 /* try to find this mount point */
358 for (i = 0; i < sm_data->shared->schema_count; ++i) {
359 if (ext->argument == sm_data->shared->schemas[i].mount_point) {
360 break;
361 }
362 }
363
364 if (i < sm_data->shared->schema_count) {
365 /* schema exists already */
366 if (strcmp(content_id, sm_data->shared->schemas[i].content_id)) {
367 lyplg_ext_log(ext, LY_LLERR, LY_EVALID, "/ietf-yang-library:yang-library/content-id",
368 "Shared-schema yang-library content-id \"%s\" differs from \"%s\" used previously.",
369 content_id, sm_data->shared->schemas[i].content_id);
370 ret = LY_EVALID;
371 goto cleanup;
372 }
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100373 } else {
Michal Vaskoddd76592022-01-17 13:34:48 +0100374 /* no schema found, create it */
375 if ((r = schema_mount_create_ctx(ext, ext_data, config, &new_ctx))) {
376 ret = r;
377 goto cleanup;
378 }
379
380 /* new entry */
381 mem = realloc(sm_data->shared->schemas, (i + 1) * sizeof *sm_data->shared->schemas);
382 if (!mem) {
383 ly_ctx_destroy(new_ctx);
384 EXT_LOGERR_MEM_GOTO(ext, ret, cleanup);
385 }
386 sm_data->shared->schemas = mem;
387 ++sm_data->shared->schema_count;
388
389 /* fill entry */
390 sm_data->shared->schemas[i].ctx = new_ctx;
391 sm_data->shared->schemas[i].mount_point = ext->argument;
392 lydict_insert(ext->module->ctx, content_id, 0, &sm_data->shared->schemas[i].content_id);
393 }
394
395 /* use the context */
396 *ext_ctx = sm_data->shared->schemas[i].ctx;
397
398cleanup:
399 /* UNLOCK */
400 pthread_mutex_unlock(&sm_data->shared->lock);
401
402 return ret;
403}
404
405/**
406 * @brief Get schema (context) for an inline mount point.
407 *
408 * @param[in] ext Compiled extension instance.
409 * @param[in] ext_data Extension data retrieved by the callback.
410 * @param[in] config Whether the whole schema should keep its config or be set to false.
411 * @param[out] ext_ctx Schema to use for parsing the data.
412 * @return LY_ERR value.
413 */
414static LY_ERR
415schema_mount_get_ctx_inline(struct lysc_ext_instance *ext, const struct lyd_node *ext_data, ly_bool config,
416 const struct ly_ctx **ext_ctx)
417{
418 struct lyplg_ext_sm *sm_data = ext->data;
419 LY_ERR r;
420 struct ly_ctx *new_ctx;
421 uint32_t i;
422 void *mem;
423
424 assert(sm_data && sm_data->shared);
425
426 i = sm_data->inln.schema_count;
427
428 /* always new schema required, create context */
429 if ((r = schema_mount_create_ctx(ext, ext_data, config, &new_ctx))) {
430 return r;
431 }
432
433 /* new entry */
434 mem = realloc(sm_data->inln.schemas, (i + 1) * sizeof *sm_data->inln.schemas);
435 if (!mem) {
436 ly_ctx_destroy(new_ctx);
437 EXT_LOGERR_MEM_RET(ext);
438 }
439 sm_data->inln.schemas = mem;
440 ++sm_data->inln.schema_count;
441
442 /* fill entry */
443 sm_data->inln.schemas[i].ctx = new_ctx;
444
445 /* use the context */
446 *ext_ctx = sm_data->inln.schemas[i].ctx;
447 return LY_SUCCESS;
448}
449
450/**
451 * @brief Get schema (context) for a mount point.
452 *
453 * @param[in] ext Compiled extension instance.
454 * @param[out] ext_ctx Schema to use for parsing the data.
455 * @return LY_ERR value.
456 */
457static LY_ERR
458schema_mount_get_ctx(struct lysc_ext_instance *ext, const struct ly_ctx **ext_ctx)
459{
460 LY_ERR ret = LY_SUCCESS, r;
461 struct lyd_node *iter, *ext_data = NULL;
462 ly_bool ext_data_free = 0, config, shared;
463
464 *ext_ctx = NULL;
465
466 /* get operational data with ietf-yang-library and ietf-yang-schema-mount data */
467 if ((r = lyplg_ext_get_data(ext->module->ctx, ext, (void **)&ext_data, &ext_data_free))) {
468 ret = r;
469 goto cleanup;
470 }
471
472 LY_LIST_FOR(ext_data, iter) {
473 if (iter->flags & LYD_NEW) {
474 /* must be validated for the parent-reference prefix data to be stored */
475 lyplg_ext_log(ext, LY_LLERR, LY_EINVAL, NULL, "Provided ext data have not been validated.");
476 ret = LY_EINVAL;
477 goto cleanup;
478 }
479 }
480
481 /* learn about this mount point */
482 if ((r = schema_mount_get_smount(ext, ext_data, &config, &shared))) {
483 ret = r;
484 goto cleanup;
485 }
486
487 /* create/get the context for parsing the data */
488 if (shared) {
489 r = schema_mount_get_ctx_shared(ext, ext_data, config, ext_ctx);
490 } else {
491 r = schema_mount_get_ctx_inline(ext, ext_data, config, ext_ctx);
492 }
493 if (r) {
494 ret = r;
495 goto cleanup;
496 }
497
498cleanup:
499 if (ext_data_free) {
500 lyd_free_all(ext_data);
501 }
502 return ret;
503}
504
505/**
506 * @brief Parse callback for schema mount.
507 * Check if data if valid for schema mount and inserts it to the parent.
508 */
509static LY_ERR
510schema_mount_parse(struct ly_in *in, LYD_FORMAT format, struct lysc_ext_instance *ext, struct lyd_node *parent,
511 uint32_t parse_opts)
512{
513 LY_ERR ret = LY_SUCCESS, r;
514 const struct ly_ctx *ext_ctx;
515 struct lyd_node *subtree, *first = NULL;
516 struct ly_err_item *err;
517 uint32_t old_log_opts;
518
519 /* get context based on ietf-yang-library data */
520 if ((r = schema_mount_get_ctx(ext, &ext_ctx))) {
521 return r;
522 }
523
524 /* prepare opts */
525 old_log_opts = ly_log_options(LY_LOSTORE_LAST);
526 assert(parse_opts & LYD_PARSE_ONLY);
527 parse_opts |= LYD_PARSE_SUBTREE;
528
529 do {
530 /* parse by nested subtrees */
531 r = lyd_parse_data(ext_ctx, NULL, in, format, parse_opts, 0, &subtree);
532 if (r && (r != LY_ENOT)) {
533 /* error, maybe valid, maybe not, print as verbose */
534 err = ly_err_first(ext_ctx);
535 if (!err) {
536 lyplg_ext_log(ext, LY_LLVRB, 0, NULL, "Unknown parsing error (err code %d).", r);
537 } else {
538 lyplg_ext_log(ext, LY_LLVRB, 0, NULL, "%s (err code %d).", err->msg, err->no);
539 }
540 ret = LY_ENOT;
541 goto cleanup;
542 }
543
544 /* set the special flag and insert into siblings */
545 subtree->flags |= LYD_EXT;
546 lyd_insert_sibling(first, subtree, &first);
547 } while (r == LY_ENOT);
548
549 /* append to parent */
550 if (first && (r = lyd_insert_ext(parent, first))) {
551 lyplg_ext_log(ext, LY_LLERR, r, NULL, "Failed to append parsed data.");
552 ret = r;
553 goto cleanup;
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100554 }
555
556cleanup:
557 ly_log_options(old_log_opts);
Michal Vaskoddd76592022-01-17 13:34:48 +0100558 if (ret) {
559 lyd_free_siblings(first);
560 }
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100561 return ret;
562}
563
564/**
Michal Vaskoddd76592022-01-17 13:34:48 +0100565 * @brief Duplicate all accessible parent references for a shared-schema mount point.
566 *
567 * @param[in] ext Compiled extension instance.
568 * @param[in] ctx_node Context node for evaluating the parent-reference XPath expressions.
569 * @param[in] ext_data Extension data retrieved by the callback.
570 * @param[in] trg_ctx Mounted data context to use for duplication.
571 * @param[out] ref_set Set of all top-level parent-ref subtrees connected to each other, may be empty.
572 * @return LY_ERR value.
573 */
574static LY_ERR
575schema_mount_dup_parent_ref(const struct lysc_ext_instance *ext, const struct lyd_node *ctx_node,
576 const struct lyd_node *ext_data, const struct ly_ctx *trg_ctx, struct ly_set **ref_set)
577{
578 LY_ERR ret = LY_SUCCESS;
579 char *path = NULL;
580 struct ly_set *set = NULL, *par_set = NULL;
581 struct lyd_node_term *term;
582 struct lyd_node *dup = NULL, *top_node, *first;
583 struct lyd_value_xpath10 *xp_val;
584 uint32_t i, j;
585
586 *ref_set = NULL;
587
588 if (!ext_data) {
589 /* we expect the same ext data as before and there must be some for data to be parsed */
590 lyplg_ext_log(ext, LY_LLERR, LY_EINVAL, NULL, "No ext data provided.");
591 ret = LY_EINVAL;
592 goto cleanup;
593 }
594
595 /* get all parent references of this mount point */
596 if (asprintf(&path, "/ietf-yang-schema-mount:schema-mounts/mount-point[module='%s'][label='%s']"
597 "/shared-schema/parent-reference", ext->module->name, ext->argument) == -1) {
598 EXT_LOGERR_MEM_GOTO(ext, ret, cleanup);
599 }
600 if ((ret = lyd_find_xpath(ext_data, path, &set))) {
601 goto cleanup;
602 }
603
604 /* prepare result set */
605 if ((ret = ly_set_new(ref_set))) {
606 goto cleanup;
607 }
608
609 first = NULL;
610 for (i = 0; i < set->count; ++i) {
611 term = set->objs[i];
612
613 /* get the referenced nodes (subtrees) */
614 LYD_VALUE_GET(&term->value, xp_val);
615 if ((ret = lyd_find_xpath4(ctx_node, ctx_node, lyxp_get_expr(xp_val->exp), xp_val->format, xp_val->prefix_data,
616 NULL, &par_set))) {
617 goto cleanup;
618 }
619
620 for (j = 0; j < par_set->count; ++j) {
621 /* duplicate with parents in the context of the mounted data */
622 if ((ret = lyd_dup_single_to_ctx(par_set->dnodes[j], trg_ctx, NULL,
623 LYD_DUP_RECURSIVE | LYD_DUP_WITH_PARENTS | LYD_DUP_WITH_FLAGS, &dup))) {
624 goto cleanup;
625 }
626
627 /* go top-level */
628 while (dup->parent) {
629 dup = lyd_parent(dup);
630 }
631
632 /* check whether the top-level node exists */
633 if (first) {
634 if ((ret = lyd_find_sibling_first(first, dup, &top_node)) && (ret != LY_ENOTFOUND)) {
635 goto cleanup;
636 }
637 } else {
638 top_node = NULL;
639 }
640
641 if (top_node) {
642 /* merge */
643 ret = lyd_merge_tree(&first, dup, LYD_MERGE_DESTRUCT);
644 dup = NULL;
645 if (ret) {
646 goto cleanup;
647 }
648 } else {
649 /* insert */
650 if ((ret = lyd_insert_sibling(first, dup, &first))) {
651 goto cleanup;
652 }
653
654 /* add into the result set because a new top-level node was added */
655 if ((ret = ly_set_add(*ref_set, dup, 1, NULL))) {
656 goto cleanup;
657 }
658 dup = NULL;
659 }
660 }
661 }
662
663cleanup:
664 free(path);
665 ly_set_free(set, NULL);
666 ly_set_free(par_set, NULL);
667 lyd_free_tree(dup);
668 if (ret && *ref_set) {
669 if ((*ref_set)->count) {
670 lyd_free_siblings((*ref_set)->dnodes[0]);
671 }
672 ly_set_free(*ref_set, NULL);
673 *ref_set = NULL;
674 }
675 return ret;
676}
677
678/**
679 * @brief Validate callback for schema mount.
680 */
681static LY_ERR
682schema_mount_validate(struct lysc_ext_instance *ext, struct lyd_node *sibling, uint32_t val_opts)
683{
684 LY_ERR ret = LY_SUCCESS;
685 uint32_t old_log_opts, i;
686 struct ly_err_item *err;
687 struct lyd_node *iter, *ext_data = NULL, *ref_first = NULL, *orig_parent = lyd_parent(sibling);
688 ly_bool ext_data_free = 0;
689 struct ly_set *ref_set = NULL;
690
691 if (!sibling) {
692 /* some data had to be parsed for this callback to be called */
693 EXT_LOGERR_INT_RET(ext);
694 }
695
696 /* get operational data with ietf-yang-library and ietf-yang-schema-mount data */
697 if ((ret = lyplg_ext_get_data(ext->module->ctx, ext, (void **)&ext_data, &ext_data_free))) {
698 goto cleanup;
699 }
700
701 LY_LIST_FOR(ext_data, iter) {
702 if (iter->flags & LYD_NEW) {
703 /* must be validated for the parent-reference prefix data to be stored */
704 lyplg_ext_log(ext, LY_LLERR, LY_EINVAL, NULL, "Provided ext data have not been validated.");
705 ret = LY_EINVAL;
706 goto cleanup;
707 }
708 }
709
710 /* duplicate the referenced parent nodes into ext context */
711 if ((ret = schema_mount_dup_parent_ref(ext, orig_parent, ext_data, LYD_CTX(sibling), &ref_set))) {
712 goto cleanup;
713 }
714
715 /* create accessible tree, remove LYD_EXT to not call this callback recursively */
716 lyd_unlink_siblings(sibling);
717 LY_LIST_FOR(sibling, iter) {
718 iter->flags &= ~LYD_EXT;
719 }
720 if (ref_set->count) {
721 if ((ret = lyd_insert_sibling(sibling, ref_set->dnodes[0], &sibling))) {
722 goto cleanup;
723 }
724 }
725
726 /* only store messages in the context, log as an extension */
727 old_log_opts = ly_log_options(LY_LOSTORE_LAST);
728
729 /* validate all the data */
730 ret = lyd_validate_all(&sibling, NULL, val_opts, NULL);
731 ly_log_options(old_log_opts);
732
733 /* restore sibling tree */
734 for (i = 0; i < ref_set->count; ++i) {
735 if (ref_set->dnodes[i] == sibling) {
736 sibling = sibling->next;
737 }
738 lyd_free_tree(ref_set->dnodes[i]);
739 }
740 LY_LIST_FOR(sibling, iter) {
741 iter->flags |= LYD_EXT;
742 }
743 lyd_insert_ext(orig_parent, sibling);
744
745 if (ret) {
746 /* log the error in the original context */
747 err = ly_err_first(LYD_CTX(sibling));
748 if (!err) {
749 lyplg_ext_log(ext, LY_LLERR, ret, NULL, "Unknown validation error (err code %d).", ret);
750 } else {
751 lyplg_ext_log(ext, LY_LLERR, err->no, err->path, "%s", err->msg);
752 }
753 goto cleanup;
754 }
755
756cleanup:
757 ly_set_free(ref_set, NULL);
758 lyd_free_siblings(ref_first);
759 if (ext_data_free) {
760 lyd_free_all(ext_data);
761 }
762 return ret;
763}
764
765/**
766 * @brief Schema mount free.
767 *
768 * Implementation of ::lyplg_ext_free_clb callback set as ::lyext_plugin::free.
769 */
770static void
771schema_mount_free(struct ly_ctx *ctx, struct lysc_ext_instance *ext)
772{
773 struct lyplg_ext_sm *sm_data = ext->data;
774 uint32_t i;
775
776 if (!sm_data) {
777 return;
778 }
779
780 if (!--sm_data->shared->ref_count) {
781 for (i = 0; i < sm_data->shared->schema_count; ++i) {
782 ly_ctx_destroy(sm_data->shared->schemas[i].ctx);
783 lydict_remove(ctx, sm_data->shared->schemas[i].content_id);
784 }
785 free(sm_data->shared->schemas);
786 pthread_mutex_destroy(&sm_data->shared->lock);
787 free(sm_data->shared);
788 }
789
790 for (i = 0; i < sm_data->inln.schema_count; ++i) {
791 ly_ctx_destroy(sm_data->inln.schemas[i].ctx);
792 }
793 free(sm_data->inln.schemas);
794 free(sm_data);
795}
796
797/**
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100798 * @brief Plugin descriptions for the Yang Schema Mount extension.
799 *
800 * Note that external plugins are supposed to use:
801 *
802 * LYPLG_EXTENSIONS = {
803 */
804const struct lyplg_ext_record plugins_schema_mount[] = {
805 {
806 .module = "ietf-yang-schema-mount",
807 .revision = "2019-01-14",
808 .name = "mount-point",
809
810 .plugin.id = "libyang 2 - Schema Mount, version 1",
811 .plugin.compile = &schema_mount_compile,
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100812 .plugin.sprinter = NULL,
Michal Vaskoddd76592022-01-17 13:34:48 +0100813 .plugin.free = &schema_mount_free,
814 .plugin.parse = &schema_mount_parse,
815 .plugin.validate = &schema_mount_validate
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100816 },
817 {0} /* terminating zeroed item */
818};