blob: 81e64de856bf327d445951e8fc73e40fc9c186bc [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"
Michal Vasko8cc3f662022-03-29 11:25:51 +020027#include "plugins_types.h"
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +010028#include "tree_data.h"
29#include "tree_schema.h"
30
31/**
Michal Vaskoddd76592022-01-17 13:34:48 +010032 * @brief Internal schema mount data structure for holding all the contexts of parsed data.
33 */
34struct lyplg_ext_sm {
35 struct lyplg_ext_sm_shared {
36 pthread_mutex_t lock; /**< lock for accessing this shared structure */
37
38 struct {
39 struct ly_ctx *ctx; /**< context shared between all data of this mount point */
40 const char *mount_point; /**< mount point name */
41 const char *content_id; /**< yang-library content-id (alternatively module-set-id),
42 stored in the dictionary of the ext instance context */
43 } *schemas; /**< array of shared schema schemas */
44 uint32_t schema_count; /**< length of schemas array */
45 uint32_t ref_count; /**< number of references to this structure (mount-points with the same name
46 in the module) */
47 } *shared; /**< shared schema mount points */
48
49 struct lyplg_ext_sm_inln {
50 struct {
51 struct ly_ctx *ctx; /**< context created for single inline schema data */
52 } *schemas; /**< array of inline schemas */
53 uint32_t schema_count; /**< length of schemas array */
54 } inln; /**< inline mount points */
55};
56
57#define EXT_LOGERR_MEM_RET(ext) \
58 lyplg_ext_log(ext, LY_LLERR, LY_EMEM, NULL, "Memory allocation failed (%s:%d).", __FILE__, __LINE__); \
59 return LY_EMEM
60
61#define EXT_LOGERR_MEM_GOTO(ext, rc, label) \
62 lyplg_ext_log(ext, LY_LLERR, LY_EMEM, NULL, "Memory allocation failed (%s:%d).", __FILE__, __LINE__); \
63 rc = LY_EMEM; \
64 goto label
65
66#define EXT_LOGERR_INT_RET(ext) \
67 lyplg_ext_log(ext, LY_LLERR, LY_EINT, NULL, "Internal error (%s:%d).", __FILE__, __LINE__); \
68 return LY_EINT
69
70/**
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +010071 * @brief Check if given mount point is unique among its' siblings
72 *
73 * @param cctx Compilation context.
74 * @param c_ext Compiled extension instance for checking uniqueness.
75 * @param p_ext Extension instance of the mount-point for comparison.
Michal Vaskoddd76592022-01-17 13:34:48 +010076 * @return LY_SUCCESS if is unique;
77 * @return LY_EINVAL otherwise.
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +010078 */
79static LY_ERR
Michal Vaskoddd76592022-01-17 13:34:48 +010080schema_mount_compile_unique_mp(struct lysc_ctx *cctx, const struct lysc_ext_instance *c_ext,
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +010081 const struct lysp_ext_instance *p_ext)
82{
Michal Vaskoddd76592022-01-17 13:34:48 +010083 struct lysc_ext_instance *exts;
84 LY_ARRAY_COUNT_TYPE u;
85 struct lysc_node *parent;
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +010086
Michal Vaskoddd76592022-01-17 13:34:48 +010087 /* check if it is the only instance of the mount-point among its' siblings */
88 parent = (struct lysc_node *)c_ext->parent;
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +010089 exts = parent->exts;
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +010090 LY_ARRAY_FOR(exts, u) {
Michal Vaskoddd76592022-01-17 13:34:48 +010091 if (&exts[u] == c_ext) {
92 continue;
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +010093 }
Michal Vaskoddd76592022-01-17 13:34:48 +010094
95 if (!strcmp(exts[u].def->module->name, "ietf-yang-schema-mount") && !strcmp(exts[u].def->name, "mount-point")) {
96 lyplg_ext_log(c_ext, LY_LLERR, LY_EVALID, lysc_ctx_get_path(cctx), "Multiple extension \"%s\" instances.",
97 p_ext->name);
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +010098 return LY_EINVAL;
99 }
100 }
101 return LY_SUCCESS;
102}
103
Michal Vaskoddd76592022-01-17 13:34:48 +0100104struct lyplg_ext_sm_shared_cb_data {
105 const struct lysc_ext_instance *ext;
106 struct lyplg_ext_sm_shared *sm_shared;
107};
108
109static LY_ERR
110schema_mount_compile_mod_dfs_cb(struct lysc_node *node, void *data, ly_bool *dfs_continue)
111{
112 struct lyplg_ext_sm_shared_cb_data *cb_data = data;
113 struct lyplg_ext_sm *sm_data;
114 struct lysc_ext_instance *exts;
115 LY_ARRAY_COUNT_TYPE u;
116
117 (void)dfs_continue;
118
119 if (node == cb_data->ext->parent) {
120 /* parent of the current compiled extension, skip */
121 return LY_SUCCESS;
122 }
123
124 /* find the same mount point */
125 exts = node->exts;
126 LY_ARRAY_FOR(exts, u) {
127 if (!strcmp(exts[u].def->module->name, "ietf-yang-schema-mount") && !strcmp(exts[u].def->name, "mount-point") &&
128 (exts[u].argument == cb_data->ext->argument)) {
129 /* same mount point, break the DFS search */
130 sm_data = exts[u].data;
131 cb_data->sm_shared = sm_data->shared;
132 return LY_EEXIST;
133 }
134 }
135
136 /* not found, continue search */
137 return LY_SUCCESS;
138}
139
140static struct lyplg_ext_sm_shared *
141schema_mount_compile_find_shared(const struct lys_module *mod, const struct lysc_ext_instance *ext)
142{
143 struct lyplg_ext_sm_shared_cb_data cb_data;
144 LY_ERR r;
145
146 /* prepare cb_data */
147 cb_data.ext = ext;
148 cb_data.sm_shared = NULL;
149
150 /* try to find the same mount point */
151 r = lysc_module_dfs_full(mod, schema_mount_compile_mod_dfs_cb, &cb_data);
Michal Vasko2a12cd92022-02-14 09:34:54 +0100152 (void)r;
Michal Vaskoddd76592022-01-17 13:34:48 +0100153 assert((!r && !cb_data.sm_shared) || ((r == LY_EEXIST) && cb_data.sm_shared));
154
155 return cb_data.sm_shared;
156}
157
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100158/**
159 * @brief Schema mount compile.
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100160 * Checks if it can be a valid extension instance for yang schema mount.
161 *
162 * Implementation of ::lyplg_ext_compile_clb callback set as lyext_plugin::compile.
163 */
164static LY_ERR
Michal Vaskoddd76592022-01-17 13:34:48 +0100165schema_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 +0100166{
167 const struct lys_module *cur_mod;
Michal Vaskodb641792022-03-21 10:04:54 +0100168 const struct lysc_node *node;
Michal Vaskoddd76592022-01-17 13:34:48 +0100169 struct lyplg_ext_sm *sm_data;
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100170
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100171 assert(!strcmp(p_ext->name, "yangmnt:mount-point"));
172
Michal Vaskoddd76592022-01-17 13:34:48 +0100173 /* check YANG version 1.1 */
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100174 cur_mod = lysc_ctx_get_cur_mod(cctx);
175 if (cur_mod->parsed->version != LYS_VERSION_1_1) {
Michal Vaskoddd76592022-01-17 13:34:48 +0100176 lyplg_ext_log(c_ext, LY_LLERR, LY_EVALID, lysc_ctx_get_path(cctx),
177 "Extension \"%s\" instance not allowed in YANG version 1 module.", p_ext->name);
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100178 return LY_EINVAL;
179 }
180
Michal Vaskoddd76592022-01-17 13:34:48 +0100181 /* check parent nodetype */
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100182 if ((p_ext->parent_stmt != LY_STMT_CONTAINER) && (p_ext->parent_stmt != LY_STMT_LIST)) {
Michal Vaskoddd76592022-01-17 13:34:48 +0100183 lyplg_ext_log(c_ext, LY_LLERR, LY_EVALID, lysc_ctx_get_path(cctx),
184 "Extension \"%s\" instance allowed only in container or list statement.", p_ext->name);
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100185 return LY_EINVAL;
186 }
187
Michal Vaskoddd76592022-01-17 13:34:48 +0100188 /* check uniqueness */
189 if (schema_mount_compile_unique_mp(cctx, c_ext, p_ext)) {
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100190 return LY_EINVAL;
191 }
192
Michal Vaskoddd76592022-01-17 13:34:48 +0100193 /* init internal data */
194 sm_data = calloc(1, sizeof *sm_data);
195 if (!sm_data) {
196 EXT_LOGERR_MEM_RET(c_ext);
197 }
198 c_ext->data = sm_data;
199
Michal Vaskodb641792022-03-21 10:04:54 +0100200 /* find the owner module */
201 node = c_ext->parent;
202 while (node->parent) {
203 node = node->parent;
204 }
205
Michal Vaskoddd76592022-01-17 13:34:48 +0100206 /* reuse/init shared schema */
Michal Vaskodb641792022-03-21 10:04:54 +0100207 sm_data->shared = schema_mount_compile_find_shared(node->module, c_ext);
Michal Vaskoddd76592022-01-17 13:34:48 +0100208 if (sm_data->shared) {
209 ++sm_data->shared->ref_count;
210 } else {
211 sm_data->shared = calloc(1, sizeof *sm_data->shared);
212 if (!sm_data->shared) {
213 free(sm_data);
214 EXT_LOGERR_MEM_RET(c_ext);
215 }
216 pthread_mutex_init(&sm_data->shared->lock, NULL);
217 sm_data->shared->ref_count = 1;
218 }
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100219
220 return LY_SUCCESS;
221}
222
223/**
Michal Vaskoddd76592022-01-17 13:34:48 +0100224 * @brief Learn details about the current mount point.
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100225 *
Michal Vaskoddd76592022-01-17 13:34:48 +0100226 * @param[in] ext Compiled extension instance.
227 * @param[in] ext_data Extension data retrieved by the callback.
228 * @param[out] config Whether the whole schema should keep its config or be set to false.
229 * @param[out] shared Whether the schema is shared or inline.
230 * @return LY_ERR value.
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100231 */
232static LY_ERR
Michal Vaskoddd76592022-01-17 13:34:48 +0100233schema_mount_get_smount(const struct lysc_ext_instance *ext, const struct lyd_node *ext_data, ly_bool *config,
234 ly_bool *shared)
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100235{
Michal Vaskoddd76592022-01-17 13:34:48 +0100236 struct lyd_node *mpoint, *node;
237 char *path = NULL;
238 LY_ERR r;
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100239
Michal Vaskoddd76592022-01-17 13:34:48 +0100240 /* find the mount point */
241 if (asprintf(&path, "/ietf-yang-schema-mount:schema-mounts/mount-point[module='%s'][label='%s']", ext->module->name,
242 ext->argument) == -1) {
243 EXT_LOGERR_MEM_RET(ext);
244 }
245 r = ext_data ? lyd_find_path(ext_data, path, 0, &mpoint) : LY_ENOTFOUND;
246 free(path);
247 if (r) {
248 /* missing mount-point, cannot be data for this extension (https://datatracker.ietf.org/doc/html/rfc8528#page-10) */
249 return LY_ENOT;
250 }
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100251
Michal Vaskoddd76592022-01-17 13:34:48 +0100252 /* check config */
253 if (!lyd_find_path(mpoint, "config", 0, &node) && !strcmp(lyd_get_value(node), "false")) {
254 *config = 0;
255 } else {
256 *config = 1;
257 }
258
259 /* check schema-ref */
260 if (lyd_find_path(mpoint, "shared-schema", 0, NULL)) {
261 if (lyd_find_path(mpoint, "inline", 0, NULL)) {
262 EXT_LOGERR_INT_RET(ext);
263 }
264 *shared = 0;
265 } else {
266 *shared = 1;
267 }
268
269 return LY_SUCCESS;
270}
271
272/**
273 * @brief Create schema (context) based on retrieved extension data.
274 *
275 * @param[in] ext Compiled extension instance.
276 * @param[in] ext_data Extension data retrieved by the callback.
277 * @param[in] config Whether the whole schema should keep its config or be set to false.
278 * @param[out] ext_ctx Schema to use for parsing the data.
279 * @return LY_ERR value.
280 */
281static LY_ERR
282schema_mount_create_ctx(const struct lysc_ext_instance *ext, const struct lyd_node *ext_data, ly_bool config,
283 struct ly_ctx **ext_ctx)
284{
285 LY_ERR r;
286 const char * const *searchdirs;
287 const struct lys_module *mod;
288 struct lysc_node *root, *node;
289 uint32_t idx = 0;
290
291 /* get searchdirs from the current context */
292 searchdirs = ly_ctx_get_searchdirs(ext->module->ctx);
293
294 /* create the context based on the data */
295 if ((r = ly_ctx_new_yldata(searchdirs ? searchdirs[0] : NULL, ext_data, ly_ctx_get_options(ext->module->ctx), ext_ctx))) {
296 lyplg_ext_log(ext, LY_LLERR, r, NULL, "Failed to create context for the schema-mount data.");
297 return r;
298 }
299
300 if (!config) {
301 /* manually change the config of all schema nodes in all the modules */
302 while ((mod = ly_ctx_get_module_iter(*ext_ctx, &idx))) {
303 if (!mod->implemented) {
304 continue;
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100305 }
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100306
Michal Vaskoddd76592022-01-17 13:34:48 +0100307 LY_LIST_FOR(mod->compiled->data, root) {
308 LYSC_TREE_DFS_BEGIN(root, node) {
309 node->flags &= ~LYS_CONFIG_W;
310 node->flags |= LYS_CONFIG_R;
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100311
Michal Vaskoddd76592022-01-17 13:34:48 +0100312 LYSC_TREE_DFS_END(root, node);
313 }
314 }
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100315 }
316 }
317
Michal Vaskoddd76592022-01-17 13:34:48 +0100318 return LY_SUCCESS;
319}
320
321/**
322 * @brief Get schema (context) for a shared-schema mount point.
323 *
324 * @param[in] ext Compiled extension instance.
325 * @param[in] ext_data Extension data retrieved by the callback.
326 * @param[in] config Whether the whole schema should keep its config or be set to false.
327 * @param[out] ext_ctx Schema to use for parsing the data.
328 * @return LY_ERR value.
329 */
330static LY_ERR
331schema_mount_get_ctx_shared(struct lysc_ext_instance *ext, const struct lyd_node *ext_data, ly_bool config,
332 const struct ly_ctx **ext_ctx)
333{
334 struct lyplg_ext_sm *sm_data = ext->data;
335 LY_ERR ret = LY_SUCCESS, r;
336 struct lyd_node *node = NULL;
337 struct ly_ctx *new_ctx;
338 uint32_t i;
339 const char *content_id = NULL;
340 void *mem;
341
342 assert(sm_data && sm_data->shared);
343
344 /* get yang-library content-id or module-set-id */
345 if (ext_data) {
346 lyd_find_path(ext_data, "/ietf-yang-library:yang-library/content-id", 0, &node);
347 if (!node) {
348 lyd_find_path(ext_data, "/ietf-yang-library:modules-state/module-set-id", 0, &node);
349 }
350 if (node) {
351 content_id = lyd_get_value(node);
352 }
353 }
354 if (!content_id) {
355 lyplg_ext_log(ext, LY_LLERR, LY_EVALID, NULL, "Missing \"content-id\" or \"module-set-id\" in ietf-yang-library data.");
356 return LY_EVALID;
357 }
358
359 /* LOCK */
360 if ((r = pthread_mutex_lock(&sm_data->shared->lock))) {
361 lyplg_ext_log(ext, LY_LLERR, LY_ESYS, NULL, "Mutex lock failed (%s).", strerror(r));
362 return LY_ESYS;
363 }
364
365 /* try to find this mount point */
366 for (i = 0; i < sm_data->shared->schema_count; ++i) {
367 if (ext->argument == sm_data->shared->schemas[i].mount_point) {
368 break;
369 }
370 }
371
372 if (i < sm_data->shared->schema_count) {
373 /* schema exists already */
374 if (strcmp(content_id, sm_data->shared->schemas[i].content_id)) {
375 lyplg_ext_log(ext, LY_LLERR, LY_EVALID, "/ietf-yang-library:yang-library/content-id",
376 "Shared-schema yang-library content-id \"%s\" differs from \"%s\" used previously.",
377 content_id, sm_data->shared->schemas[i].content_id);
378 ret = LY_EVALID;
379 goto cleanup;
380 }
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100381 } else {
Michal Vaskoddd76592022-01-17 13:34:48 +0100382 /* no schema found, create it */
383 if ((r = schema_mount_create_ctx(ext, ext_data, config, &new_ctx))) {
384 ret = r;
385 goto cleanup;
386 }
387
388 /* new entry */
389 mem = realloc(sm_data->shared->schemas, (i + 1) * sizeof *sm_data->shared->schemas);
390 if (!mem) {
391 ly_ctx_destroy(new_ctx);
392 EXT_LOGERR_MEM_GOTO(ext, ret, cleanup);
393 }
394 sm_data->shared->schemas = mem;
395 ++sm_data->shared->schema_count;
396
397 /* fill entry */
398 sm_data->shared->schemas[i].ctx = new_ctx;
399 sm_data->shared->schemas[i].mount_point = ext->argument;
400 lydict_insert(ext->module->ctx, content_id, 0, &sm_data->shared->schemas[i].content_id);
401 }
402
403 /* use the context */
404 *ext_ctx = sm_data->shared->schemas[i].ctx;
405
406cleanup:
407 /* UNLOCK */
408 pthread_mutex_unlock(&sm_data->shared->lock);
409
410 return ret;
411}
412
413/**
414 * @brief Get schema (context) for an inline mount point.
415 *
416 * @param[in] ext Compiled extension instance.
417 * @param[in] ext_data Extension data retrieved by the callback.
418 * @param[in] config Whether the whole schema should keep its config or be set to false.
419 * @param[out] ext_ctx Schema to use for parsing the data.
420 * @return LY_ERR value.
421 */
422static LY_ERR
423schema_mount_get_ctx_inline(struct lysc_ext_instance *ext, const struct lyd_node *ext_data, ly_bool config,
424 const struct ly_ctx **ext_ctx)
425{
426 struct lyplg_ext_sm *sm_data = ext->data;
427 LY_ERR r;
428 struct ly_ctx *new_ctx;
429 uint32_t i;
430 void *mem;
431
432 assert(sm_data && sm_data->shared);
433
434 i = sm_data->inln.schema_count;
435
436 /* always new schema required, create context */
437 if ((r = schema_mount_create_ctx(ext, ext_data, config, &new_ctx))) {
438 return r;
439 }
440
441 /* new entry */
442 mem = realloc(sm_data->inln.schemas, (i + 1) * sizeof *sm_data->inln.schemas);
443 if (!mem) {
444 ly_ctx_destroy(new_ctx);
445 EXT_LOGERR_MEM_RET(ext);
446 }
447 sm_data->inln.schemas = mem;
448 ++sm_data->inln.schema_count;
449
450 /* fill entry */
451 sm_data->inln.schemas[i].ctx = new_ctx;
452
453 /* use the context */
454 *ext_ctx = sm_data->inln.schemas[i].ctx;
455 return LY_SUCCESS;
456}
457
458/**
459 * @brief Get schema (context) for a mount point.
460 *
461 * @param[in] ext Compiled extension instance.
462 * @param[out] ext_ctx Schema to use for parsing the data.
463 * @return LY_ERR value.
464 */
465static LY_ERR
466schema_mount_get_ctx(struct lysc_ext_instance *ext, const struct ly_ctx **ext_ctx)
467{
468 LY_ERR ret = LY_SUCCESS, r;
469 struct lyd_node *iter, *ext_data = NULL;
470 ly_bool ext_data_free = 0, config, shared;
471
472 *ext_ctx = NULL;
473
474 /* get operational data with ietf-yang-library and ietf-yang-schema-mount data */
475 if ((r = lyplg_ext_get_data(ext->module->ctx, ext, (void **)&ext_data, &ext_data_free))) {
476 ret = r;
477 goto cleanup;
478 }
479
480 LY_LIST_FOR(ext_data, iter) {
481 if (iter->flags & LYD_NEW) {
482 /* must be validated for the parent-reference prefix data to be stored */
483 lyplg_ext_log(ext, LY_LLERR, LY_EINVAL, NULL, "Provided ext data have not been validated.");
484 ret = LY_EINVAL;
485 goto cleanup;
486 }
487 }
488
489 /* learn about this mount point */
490 if ((r = schema_mount_get_smount(ext, ext_data, &config, &shared))) {
491 ret = r;
492 goto cleanup;
493 }
494
495 /* create/get the context for parsing the data */
496 if (shared) {
497 r = schema_mount_get_ctx_shared(ext, ext_data, config, ext_ctx);
498 } else {
499 r = schema_mount_get_ctx_inline(ext, ext_data, config, ext_ctx);
500 }
501 if (r) {
502 ret = r;
503 goto cleanup;
504 }
505
506cleanup:
507 if (ext_data_free) {
508 lyd_free_all(ext_data);
509 }
510 return ret;
511}
512
513/**
Michal Vasko8cc3f662022-03-29 11:25:51 +0200514 * @brief Snode callback for schema mount.
515 * Check if data are valid for schema mount and returns their schema node.
Michal Vaskoddd76592022-01-17 13:34:48 +0100516 */
517static LY_ERR
Michal Vasko8cc3f662022-03-29 11:25:51 +0200518schema_mount_snode(struct lysc_ext_instance *ext, const struct lyd_node *parent, const struct lysc_node *sparent,
519 const char *prefix, size_t prefix_len, LY_VALUE_FORMAT format, void *prefix_data, const char *name, size_t name_len,
520 const struct lysc_node **snode)
Michal Vaskoddd76592022-01-17 13:34:48 +0100521{
Michal Vasko8cc3f662022-03-29 11:25:51 +0200522 LY_ERR r;
523 const struct lys_module *mod;
Michal Vaskoddd76592022-01-17 13:34:48 +0100524 const struct ly_ctx *ext_ctx;
Michal Vaskoddd76592022-01-17 13:34:48 +0100525
526 /* get context based on ietf-yang-library data */
527 if ((r = schema_mount_get_ctx(ext, &ext_ctx))) {
528 return r;
529 }
530
Michal Vasko8cc3f662022-03-29 11:25:51 +0200531 /* get the module */
532 mod = lyplg_type_identity_module(ext_ctx, parent ? parent->schema : sparent, prefix, prefix_len, format, prefix_data);
533 if (!mod) {
534 return LY_ENOT;
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100535 }
536
Michal Vasko8cc3f662022-03-29 11:25:51 +0200537 /* get the top-level schema node */
538 *snode = lys_find_child(NULL, mod, name, name_len, 0, 0);
539 return *snode ? LY_SUCCESS : LY_ENOT;
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100540}
541
542/**
Michal Vaskoddd76592022-01-17 13:34:48 +0100543 * @brief Duplicate all accessible parent references for a shared-schema mount point.
544 *
545 * @param[in] ext Compiled extension instance.
546 * @param[in] ctx_node Context node for evaluating the parent-reference XPath expressions.
547 * @param[in] ext_data Extension data retrieved by the callback.
548 * @param[in] trg_ctx Mounted data context to use for duplication.
549 * @param[out] ref_set Set of all top-level parent-ref subtrees connected to each other, may be empty.
550 * @return LY_ERR value.
551 */
552static LY_ERR
553schema_mount_dup_parent_ref(const struct lysc_ext_instance *ext, const struct lyd_node *ctx_node,
554 const struct lyd_node *ext_data, const struct ly_ctx *trg_ctx, struct ly_set **ref_set)
555{
556 LY_ERR ret = LY_SUCCESS;
557 char *path = NULL;
558 struct ly_set *set = NULL, *par_set = NULL;
559 struct lyd_node_term *term;
560 struct lyd_node *dup = NULL, *top_node, *first;
561 struct lyd_value_xpath10 *xp_val;
562 uint32_t i, j;
563
564 *ref_set = NULL;
565
566 if (!ext_data) {
567 /* we expect the same ext data as before and there must be some for data to be parsed */
568 lyplg_ext_log(ext, LY_LLERR, LY_EINVAL, NULL, "No ext data provided.");
569 ret = LY_EINVAL;
570 goto cleanup;
571 }
572
573 /* get all parent references of this mount point */
574 if (asprintf(&path, "/ietf-yang-schema-mount:schema-mounts/mount-point[module='%s'][label='%s']"
575 "/shared-schema/parent-reference", ext->module->name, ext->argument) == -1) {
576 EXT_LOGERR_MEM_GOTO(ext, ret, cleanup);
577 }
578 if ((ret = lyd_find_xpath(ext_data, path, &set))) {
579 goto cleanup;
580 }
581
582 /* prepare result set */
583 if ((ret = ly_set_new(ref_set))) {
584 goto cleanup;
585 }
586
587 first = NULL;
588 for (i = 0; i < set->count; ++i) {
589 term = set->objs[i];
590
591 /* get the referenced nodes (subtrees) */
592 LYD_VALUE_GET(&term->value, xp_val);
593 if ((ret = lyd_find_xpath4(ctx_node, ctx_node, lyxp_get_expr(xp_val->exp), xp_val->format, xp_val->prefix_data,
594 NULL, &par_set))) {
595 goto cleanup;
596 }
597
598 for (j = 0; j < par_set->count; ++j) {
599 /* duplicate with parents in the context of the mounted data */
600 if ((ret = lyd_dup_single_to_ctx(par_set->dnodes[j], trg_ctx, NULL,
601 LYD_DUP_RECURSIVE | LYD_DUP_WITH_PARENTS | LYD_DUP_WITH_FLAGS, &dup))) {
602 goto cleanup;
603 }
604
605 /* go top-level */
606 while (dup->parent) {
607 dup = lyd_parent(dup);
608 }
609
610 /* check whether the top-level node exists */
611 if (first) {
612 if ((ret = lyd_find_sibling_first(first, dup, &top_node)) && (ret != LY_ENOTFOUND)) {
613 goto cleanup;
614 }
615 } else {
616 top_node = NULL;
617 }
618
619 if (top_node) {
620 /* merge */
621 ret = lyd_merge_tree(&first, dup, LYD_MERGE_DESTRUCT);
622 dup = NULL;
623 if (ret) {
624 goto cleanup;
625 }
626 } else {
627 /* insert */
628 if ((ret = lyd_insert_sibling(first, dup, &first))) {
629 goto cleanup;
630 }
631
632 /* add into the result set because a new top-level node was added */
633 if ((ret = ly_set_add(*ref_set, dup, 1, NULL))) {
634 goto cleanup;
635 }
636 dup = NULL;
637 }
638 }
639 }
640
641cleanup:
642 free(path);
643 ly_set_free(set, NULL);
644 ly_set_free(par_set, NULL);
645 lyd_free_tree(dup);
646 if (ret && *ref_set) {
647 if ((*ref_set)->count) {
648 lyd_free_siblings((*ref_set)->dnodes[0]);
649 }
650 ly_set_free(*ref_set, NULL);
651 *ref_set = NULL;
652 }
653 return ret;
654}
655
656/**
657 * @brief Validate callback for schema mount.
658 */
659static LY_ERR
Michal Vaskof4c6f002022-04-01 09:12:22 +0200660schema_mount_validate(struct lysc_ext_instance *ext, struct lyd_node *sibling, uint32_t val_opts, struct lyd_node **diff)
Michal Vaskoddd76592022-01-17 13:34:48 +0100661{
662 LY_ERR ret = LY_SUCCESS;
663 uint32_t old_log_opts, i;
664 struct ly_err_item *err;
665 struct lyd_node *iter, *ext_data = NULL, *ref_first = NULL, *orig_parent = lyd_parent(sibling);
Michal Vaskof4c6f002022-04-01 09:12:22 +0200666 struct lyd_node *ext_diff = NULL, *diff_parent = NULL;
Michal Vaskoddd76592022-01-17 13:34:48 +0100667 ly_bool ext_data_free = 0;
668 struct ly_set *ref_set = NULL;
669
670 if (!sibling) {
671 /* some data had to be parsed for this callback to be called */
672 EXT_LOGERR_INT_RET(ext);
673 }
674
675 /* get operational data with ietf-yang-library and ietf-yang-schema-mount data */
676 if ((ret = lyplg_ext_get_data(ext->module->ctx, ext, (void **)&ext_data, &ext_data_free))) {
677 goto cleanup;
678 }
679
680 LY_LIST_FOR(ext_data, iter) {
681 if (iter->flags & LYD_NEW) {
682 /* must be validated for the parent-reference prefix data to be stored */
683 lyplg_ext_log(ext, LY_LLERR, LY_EINVAL, NULL, "Provided ext data have not been validated.");
684 ret = LY_EINVAL;
685 goto cleanup;
686 }
687 }
688
689 /* duplicate the referenced parent nodes into ext context */
690 if ((ret = schema_mount_dup_parent_ref(ext, orig_parent, ext_data, LYD_CTX(sibling), &ref_set))) {
691 goto cleanup;
692 }
693
694 /* create accessible tree, remove LYD_EXT to not call this callback recursively */
695 lyd_unlink_siblings(sibling);
696 LY_LIST_FOR(sibling, iter) {
697 iter->flags &= ~LYD_EXT;
698 }
699 if (ref_set->count) {
700 if ((ret = lyd_insert_sibling(sibling, ref_set->dnodes[0], &sibling))) {
701 goto cleanup;
702 }
703 }
704
705 /* only store messages in the context, log as an extension */
706 old_log_opts = ly_log_options(LY_LOSTORE_LAST);
707
708 /* validate all the data */
Michal Vaskof4c6f002022-04-01 09:12:22 +0200709 ret = lyd_validate_all(&sibling, NULL, val_opts, diff ? &ext_diff : NULL);
Michal Vaskoddd76592022-01-17 13:34:48 +0100710 ly_log_options(old_log_opts);
711
712 /* restore sibling tree */
713 for (i = 0; i < ref_set->count; ++i) {
714 if (ref_set->dnodes[i] == sibling) {
715 sibling = sibling->next;
716 }
717 lyd_free_tree(ref_set->dnodes[i]);
718 }
719 LY_LIST_FOR(sibling, iter) {
720 iter->flags |= LYD_EXT;
721 }
722 lyd_insert_ext(orig_parent, sibling);
723
724 if (ret) {
725 /* log the error in the original context */
726 err = ly_err_first(LYD_CTX(sibling));
727 if (!err) {
728 lyplg_ext_log(ext, LY_LLERR, ret, NULL, "Unknown validation error (err code %d).", ret);
729 } else {
730 lyplg_ext_log(ext, LY_LLERR, err->no, err->path, "%s", err->msg);
731 }
732 goto cleanup;
733 }
734
Michal Vaskof4c6f002022-04-01 09:12:22 +0200735 /* create proper diff */
736 if (diff && ext_diff) {
737 /* diff nodes from an extension instance */
738 LY_LIST_FOR(ext_diff, iter) {
739 iter->flags |= LYD_EXT;
740 }
741
742 /* create the parent and insert the diff */
743 if ((ret = lyd_dup_single(lyd_parent(sibling), NULL, LYD_DUP_WITH_PARENTS | LYD_DUP_NO_META, &diff_parent))) {
744 goto cleanup;
745 }
746 if ((ret = lyd_insert_ext(diff_parent, ext_diff))) {
747 goto cleanup;
748 }
749 ext_diff = NULL;
750
751 /* go top-level and set the operation */
752 while (lyd_parent(diff_parent)) {
753 diff_parent = lyd_parent(diff_parent);
754 }
755 if ((ret = lyd_new_meta(LYD_CTX(diff_parent), diff_parent, NULL, "yang:operation", "none", 0, NULL))) {
756 goto cleanup;
757 }
758
759 /* finally merge into the global diff */
760 if ((ret = lyd_diff_merge_all(diff, diff_parent, LYD_DIFF_MERGE_DEFAULTS))) {
761 goto cleanup;
762 }
763 }
764
Michal Vaskoddd76592022-01-17 13:34:48 +0100765cleanup:
766 ly_set_free(ref_set, NULL);
767 lyd_free_siblings(ref_first);
Michal Vaskof4c6f002022-04-01 09:12:22 +0200768 lyd_free_tree(ext_diff);
769 lyd_free_all(diff_parent);
Michal Vaskoddd76592022-01-17 13:34:48 +0100770 if (ext_data_free) {
771 lyd_free_all(ext_data);
772 }
773 return ret;
774}
775
776/**
777 * @brief Schema mount free.
778 *
779 * Implementation of ::lyplg_ext_free_clb callback set as ::lyext_plugin::free.
780 */
781static void
782schema_mount_free(struct ly_ctx *ctx, struct lysc_ext_instance *ext)
783{
784 struct lyplg_ext_sm *sm_data = ext->data;
785 uint32_t i;
786
787 if (!sm_data) {
788 return;
789 }
790
791 if (!--sm_data->shared->ref_count) {
792 for (i = 0; i < sm_data->shared->schema_count; ++i) {
793 ly_ctx_destroy(sm_data->shared->schemas[i].ctx);
794 lydict_remove(ctx, sm_data->shared->schemas[i].content_id);
795 }
796 free(sm_data->shared->schemas);
797 pthread_mutex_destroy(&sm_data->shared->lock);
798 free(sm_data->shared);
799 }
800
801 for (i = 0; i < sm_data->inln.schema_count; ++i) {
802 ly_ctx_destroy(sm_data->inln.schemas[i].ctx);
803 }
804 free(sm_data->inln.schemas);
805 free(sm_data);
806}
807
808/**
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100809 * @brief Plugin descriptions for the Yang Schema Mount extension.
810 *
811 * Note that external plugins are supposed to use:
812 *
813 * LYPLG_EXTENSIONS = {
814 */
815const struct lyplg_ext_record plugins_schema_mount[] = {
816 {
817 .module = "ietf-yang-schema-mount",
818 .revision = "2019-01-14",
819 .name = "mount-point",
820
821 .plugin.id = "libyang 2 - Schema Mount, version 1",
822 .plugin.compile = &schema_mount_compile,
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100823 .plugin.sprinter = NULL,
Michal Vaskoddd76592022-01-17 13:34:48 +0100824 .plugin.free = &schema_mount_free,
Michal Vasko8cc3f662022-03-29 11:25:51 +0200825 .plugin.snode = &schema_mount_snode,
Michal Vaskoddd76592022-01-17 13:34:48 +0100826 .plugin.validate = &schema_mount_validate
tadeas-vintrlik2aa36b42021-11-03 13:07:34 +0100827 },
828 {0} /* terminating zeroed item */
829};