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