| /** |
| * @file context.c |
| * @author Radek Krejci <rkrejci@cesnet.cz> |
| * @brief context implementation for libyang |
| * |
| * Copyright (c) 2015 CESNET, z.s.p.o. |
| * |
| * This source code is licensed under BSD 3-Clause License (the "License"). |
| * You may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * https://opensource.org/licenses/BSD-3-Clause |
| */ |
| |
| #define _GNU_SOURCE |
| #include <pthread.h> |
| #include <stddef.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <unistd.h> |
| #include <limits.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| |
| #include "common.h" |
| #include "context.h" |
| #include "hash_table.h" |
| #include "parser.h" |
| #include "tree_internal.h" |
| #include "resolve.h" |
| |
| /* |
| * counter for references to the extensions plugins (for the number of contexts) |
| * located in extensions.c |
| */ |
| extern unsigned int ext_plugins_ref; |
| |
| #define IETF_YANG_METADATA_PATH "../models/ietf-yang-metadata@2016-08-05.h" |
| #define YANG_PATH "../models/yang@2017-02-20.h" |
| #define IETF_INET_TYPES_PATH "../models/ietf-inet-types@2013-07-15.h" |
| #define IETF_YANG_TYPES_PATH "../models/ietf-yang-types@2013-07-15.h" |
| #define IETF_DATASTORES "../models/ietf-datastores@2017-08-17.h" |
| #define IETF_YANG_LIB_PATH "../models/ietf-yang-library@2018-01-17.h" |
| #define IETF_YANG_LIB_REV "2018-01-17" |
| |
| #include IETF_YANG_METADATA_PATH |
| #include YANG_PATH |
| #include IETF_INET_TYPES_PATH |
| #include IETF_YANG_TYPES_PATH |
| #include IETF_DATASTORES |
| #include IETF_YANG_LIB_PATH |
| |
| #define LY_INTERNAL_MODULE_COUNT 6 |
| static struct internal_modules_s { |
| const char *name; |
| const char *revision; |
| const char *data; |
| uint8_t implemented; |
| LYS_INFORMAT format; |
| } internal_modules[LY_INTERNAL_MODULE_COUNT] = { |
| {"ietf-yang-metadata", "2016-08-05", (const char*)ietf_yang_metadata_2016_08_05_yin, 0, LYS_IN_YIN}, |
| {"yang", "2017-02-20", (const char*)yang_2017_02_20_yin, 1, LYS_IN_YIN}, |
| {"ietf-inet-types", "2013-07-15", (const char*)ietf_inet_types_2013_07_15_yin, 0, LYS_IN_YIN}, |
| {"ietf-yang-types", "2013-07-15", (const char*)ietf_yang_types_2013_07_15_yin, 0, LYS_IN_YIN}, |
| /* ietf-datastores and ietf-yang-library must be right here at the end of the list! */ |
| {"ietf-datastores", "2017-08-17", (const char*)ietf_datastores_2017_08_17_yin, 0, LYS_IN_YIN}, |
| {"ietf-yang-library", IETF_YANG_LIB_REV, (const char*)ietf_yang_library_2018_01_17_yin, 1, LYS_IN_YIN} |
| }; |
| |
| API unsigned int |
| ly_ctx_internal_modules_count(struct ly_ctx *ctx) |
| { |
| if (!ctx) { |
| return 0; |
| } |
| return ctx->internal_module_count; |
| } |
| |
| API struct ly_ctx * |
| ly_ctx_new(const char *search_dir, int options) |
| { |
| struct ly_ctx *ctx = NULL; |
| struct lys_module *module; |
| char *cwd = NULL; |
| char *search_dir_list; |
| char *sep, *dir; |
| int rc = EXIT_SUCCESS; |
| int i; |
| |
| ctx = calloc(1, sizeof *ctx); |
| LY_CHECK_ERR_RETURN(!ctx, LOGMEM(NULL), NULL); |
| |
| /* dictionary */ |
| lydict_init(&ctx->dict); |
| |
| /* plugins */ |
| ly_load_plugins(); |
| |
| /* initialize thread-specific key */ |
| while ((i = pthread_key_create(&ctx->errlist_key, ly_err_free)) == EAGAIN); |
| |
| /* models list */ |
| ctx->models.list = calloc(16, sizeof *ctx->models.list); |
| LY_CHECK_ERR_RETURN(!ctx->models.list, LOGMEM(NULL); free(ctx), NULL); |
| ctx->models.flags = options; |
| ctx->models.used = 0; |
| ctx->models.size = 16; |
| if (search_dir) { |
| search_dir_list = strdup(search_dir); |
| LY_CHECK_ERR_GOTO(!search_dir_list, LOGMEM(NULL), error); |
| |
| for (dir = search_dir_list; (sep = strchr(dir, ':')) != NULL && rc == EXIT_SUCCESS; dir = sep + 1) { |
| *sep = 0; |
| rc = ly_ctx_set_searchdir(ctx, dir); |
| } |
| if (*dir && rc == EXIT_SUCCESS) { |
| rc = ly_ctx_set_searchdir(ctx, dir); |
| } |
| free(search_dir_list); |
| /* If ly_ctx_set_searchdir() failed, the error is already logged. Just exit */ |
| if (rc != EXIT_SUCCESS) { |
| goto error; |
| } |
| } |
| ctx->models.module_set_id = 1; |
| |
| /* load internal modules */ |
| if (options & LY_CTX_NOYANGLIBRARY) { |
| ctx->internal_module_count = LY_INTERNAL_MODULE_COUNT - 2; |
| } else { |
| ctx->internal_module_count = LY_INTERNAL_MODULE_COUNT; |
| } |
| for (i = 0; i < ctx->internal_module_count; i++) { |
| module = (struct lys_module *)lys_parse_mem(ctx, internal_modules[i].data, internal_modules[i].format); |
| if (!module) { |
| goto error; |
| } |
| module->implemented = internal_modules[i].implemented; |
| } |
| |
| /* cleanup */ |
| free(cwd); |
| |
| return ctx; |
| |
| error: |
| free(cwd); |
| ly_ctx_destroy(ctx, NULL); |
| return NULL; |
| } |
| |
| static int |
| ly_ctx_new_yl_legacy(struct ly_ctx *ctx, struct lyd_node *yltree) |
| { |
| unsigned int i, u; |
| struct lyd_node *module, *node; |
| struct ly_set *set; |
| const char *name, *revision; |
| struct ly_set features = {0, 0, {NULL}}; |
| const struct lys_module *mod; |
| |
| set = lyd_find_path(yltree, "/ietf-yang-library:yang-library/modules-state/module"); |
| if (!set) { |
| return 1; |
| } |
| |
| /* process the data tree */ |
| for (i = 0; i < set->number; ++i) { |
| module = set->set.d[i]; |
| |
| /* initiate */ |
| name = NULL; |
| revision = NULL; |
| ly_set_clean(&features); |
| |
| LY_TREE_FOR(module->child, node) { |
| if (!strcmp(node->schema->name, "name")) { |
| name = ((struct lyd_node_leaf_list*)node)->value_str; |
| } else if (!strcmp(node->schema->name, "revision")) { |
| revision = ((struct lyd_node_leaf_list*)node)->value_str; |
| } else if (!strcmp(node->schema->name, "feature")) { |
| ly_set_add(&features, node, LY_SET_OPT_USEASLIST); |
| } else if (!strcmp(node->schema->name, "conformance-type") && |
| ((struct lyd_node_leaf_list*)node)->value.enm->value) { |
| /* imported module - skip it, it will be loaded as a side effect |
| * of loading another module */ |
| continue; |
| } |
| } |
| |
| /* use the gathered data to load the module */ |
| mod = ly_ctx_load_module(ctx, name, revision); |
| if (!mod) { |
| LOGERR(ctx, LY_EINVAL, "Unable to load module specified by yang library data."); |
| ly_set_free(set); |
| return 1; |
| } |
| |
| /* set features */ |
| for (u = 0; u < features.number; u++) { |
| lys_features_enable(mod, ((struct lyd_node_leaf_list*)features.set.d[u])->value_str); |
| } |
| } |
| |
| ly_set_free(set); |
| return 0; |
| } |
| |
| static struct ly_ctx * |
| ly_ctx_new_yl_common(const char *search_dir, const char *input, LYD_FORMAT format, int options, |
| struct lyd_node* (*parser_func)(struct ly_ctx*, const char*, LYD_FORMAT, int,...)) |
| { |
| unsigned int i, u; |
| struct lyd_node *module, *node; |
| const char *name, *revision; |
| struct ly_set features = {0, 0, {NULL}}; |
| const struct lys_module *mod; |
| struct lyd_node *yltree = NULL; |
| struct ly_ctx *ctx = NULL; |
| struct ly_set *set = NULL; |
| |
| /* create empty (with internal modules including ietf-yang-library) context */ |
| ctx = ly_ctx_new(search_dir, options); |
| if (!ctx) { |
| goto error; |
| } |
| |
| /* parse yang library data tree */ |
| yltree = parser_func(ctx, input, format, LYD_OPT_DATA, NULL); |
| if (!yltree) { |
| goto error; |
| } |
| |
| set = lyd_find_path(yltree, "/ietf-yang-library:yang-library/module-set[1]/module"); |
| if (!set) { |
| goto error; |
| } |
| |
| if (set->number == 0) { |
| /* perhaps a legacy data tree? */ |
| if (ly_ctx_new_yl_legacy(ctx, yltree)) { |
| goto error; |
| } |
| } else { |
| /* process the data tree */ |
| for (i = 0; i < set->number; ++i) { |
| module = set->set.d[i]; |
| |
| /* initiate */ |
| name = NULL; |
| revision = NULL; |
| ly_set_clean(&features); |
| |
| LY_TREE_FOR(module->child, node) { |
| if (!strcmp(node->schema->name, "name")) { |
| name = ((struct lyd_node_leaf_list*)node)->value_str; |
| } else if (!strcmp(node->schema->name, "revision")) { |
| revision = ((struct lyd_node_leaf_list*)node)->value_str; |
| } else if (!strcmp(node->schema->name, "feature")) { |
| ly_set_add(&features, node->child, LY_SET_OPT_USEASLIST); |
| } |
| } |
| |
| /* use the gathered data to load the module */ |
| mod = ly_ctx_load_module(ctx, name, revision); |
| if (!mod) { |
| LOGERR(NULL, LY_EINVAL, "Unable to load module specified by yang library data."); |
| goto error; |
| } |
| |
| /* set features */ |
| for (u = 0; u < features.number; u++) { |
| lys_features_enable(mod, ((struct lyd_node_leaf_list*)features.set.d[u])->value_str); |
| } |
| } |
| } |
| |
| if (0) { |
| /* skip context destroy in case of success */ |
| error: |
| ly_ctx_destroy(ctx, NULL); |
| ctx = NULL; |
| } |
| |
| /* cleanup */ |
| if (yltree) { |
| /* yang library data tree */ |
| lyd_free_withsiblings(yltree); |
| } |
| if (set) { |
| ly_set_free(set); |
| } |
| |
| return ctx; |
| } |
| |
| API struct ly_ctx * |
| ly_ctx_new_ylpath(const char *search_dir, const char *path, LYD_FORMAT format, int options) |
| { |
| return ly_ctx_new_yl_common(search_dir, path, format, options, lyd_parse_path); |
| } |
| |
| API struct ly_ctx * |
| ly_ctx_new_ylmem(const char *search_dir, const char *data, LYD_FORMAT format, int options) |
| { |
| return ly_ctx_new_yl_common(search_dir, data, format, options, lyd_parse_mem); |
| } |
| |
| static void |
| ly_ctx_set_option(struct ly_ctx *ctx, int options) |
| { |
| if (!ctx) { |
| return; |
| } |
| |
| ctx->models.flags |= options; |
| } |
| |
| static void |
| ly_ctx_unset_option(struct ly_ctx *ctx, int options) |
| { |
| if (!ctx) { |
| return; |
| } |
| |
| ctx->models.flags &= ~options; |
| } |
| |
| API void |
| ly_ctx_set_disable_searchdirs(struct ly_ctx *ctx) |
| { |
| ly_ctx_set_option(ctx, LY_CTX_DISABLE_SEARCHDIRS); |
| } |
| |
| API void |
| ly_ctx_unset_disable_searchdirs(struct ly_ctx *ctx) |
| { |
| ly_ctx_unset_option(ctx, LY_CTX_DISABLE_SEARCHDIRS); |
| } |
| |
| API void |
| ly_ctx_set_disable_searchdir_cwd(struct ly_ctx *ctx) |
| { |
| ly_ctx_set_option(ctx, LY_CTX_DISABLE_SEARCHDIR_CWD); |
| } |
| |
| API void |
| ly_ctx_unset_disable_searchdir_cwd(struct ly_ctx *ctx) |
| { |
| ly_ctx_unset_option(ctx, LY_CTX_DISABLE_SEARCHDIR_CWD); |
| } |
| |
| API void |
| ly_ctx_set_allimplemented(struct ly_ctx *ctx) |
| { |
| ly_ctx_set_option(ctx, LY_CTX_ALLIMPLEMENTED); |
| } |
| |
| API void |
| ly_ctx_unset_allimplemented(struct ly_ctx *ctx) |
| { |
| ly_ctx_unset_option(ctx, LY_CTX_ALLIMPLEMENTED); |
| } |
| |
| API void |
| ly_ctx_set_trusted(struct ly_ctx *ctx) |
| { |
| ly_ctx_set_option(ctx, LY_CTX_TRUSTED); |
| } |
| |
| API void |
| ly_ctx_unset_trusted(struct ly_ctx *ctx) |
| { |
| ly_ctx_unset_option(ctx, LY_CTX_TRUSTED); |
| } |
| |
| API int |
| ly_ctx_get_options(struct ly_ctx *ctx) |
| { |
| return ctx->models.flags; |
| } |
| |
| API int |
| ly_ctx_set_searchdir(struct ly_ctx *ctx, const char *search_dir) |
| { |
| char *new_dir = NULL; |
| int index = 0; |
| void *r; |
| int rc = EXIT_FAILURE; |
| |
| if (!ctx) { |
| LOGARG; |
| return EXIT_FAILURE; |
| } |
| |
| if (search_dir) { |
| if (access(search_dir, R_OK | X_OK)) { |
| LOGERR(ctx, LY_ESYS, "Unable to use search directory \"%s\" (%s)", |
| search_dir, strerror(errno)); |
| return EXIT_FAILURE; |
| } |
| |
| new_dir = realpath(search_dir, NULL); |
| LY_CHECK_ERR_GOTO(!new_dir, LOGERR(ctx, LY_ESYS, "realpath() call failed (%s).", strerror(errno)), cleanup); |
| if (!ctx->models.search_paths) { |
| ctx->models.search_paths = malloc(2 * sizeof *ctx->models.search_paths); |
| LY_CHECK_ERR_GOTO(!ctx->models.search_paths, LOGMEM(ctx), cleanup); |
| index = 0; |
| } else { |
| for (index = 0; ctx->models.search_paths[index]; index++) { |
| /* check for duplicities */ |
| if (!strcmp(new_dir, ctx->models.search_paths[index])) { |
| /* path is already present */ |
| goto success; |
| } |
| } |
| r = realloc(ctx->models.search_paths, (index + 2) * sizeof *ctx->models.search_paths); |
| LY_CHECK_ERR_GOTO(!r, LOGMEM(ctx), cleanup); |
| ctx->models.search_paths = r; |
| } |
| ctx->models.search_paths[index] = new_dir; |
| new_dir = NULL; |
| ctx->models.search_paths[index + 1] = NULL; |
| |
| success: |
| rc = EXIT_SUCCESS; |
| } else { |
| /* consider that no change is not actually an error */ |
| return EXIT_SUCCESS; |
| } |
| |
| cleanup: |
| free(new_dir); |
| return rc; |
| } |
| |
| API const char * const * |
| ly_ctx_get_searchdirs(const struct ly_ctx *ctx) |
| { |
| if (!ctx) { |
| LOGARG; |
| return NULL; |
| } |
| return (const char * const *)ctx->models.search_paths; |
| } |
| |
| API void |
| ly_ctx_unset_searchdirs(struct ly_ctx *ctx, int index) |
| { |
| int i; |
| |
| if (!ctx->models.search_paths) { |
| return; |
| } |
| |
| for (i = 0; ctx->models.search_paths[i]; i++) { |
| if (index < 0 || index == i) { |
| free(ctx->models.search_paths[i]); |
| ctx->models.search_paths[i] = NULL; |
| } else if (i > index) { |
| ctx->models.search_paths[i - 1] = ctx->models.search_paths[i]; |
| ctx->models.search_paths[i] = NULL; |
| } |
| } |
| if (index < 0 || !ctx->models.search_paths[0]) { |
| free(ctx->models.search_paths); |
| ctx->models.search_paths = NULL; |
| } |
| } |
| |
| API void |
| ly_ctx_destroy(struct ly_ctx *ctx, void (*private_destructor)(const struct lys_node *node, void *priv)) |
| { |
| int i; |
| |
| if (!ctx) { |
| return; |
| } |
| |
| /* models list */ |
| for (; ctx->models.used > 0; ctx->models.used--) { |
| /* remove the applied deviations and augments */ |
| lys_sub_module_remove_devs_augs(ctx->models.list[ctx->models.used - 1]); |
| /* remove the module */ |
| lys_free(ctx->models.list[ctx->models.used - 1], private_destructor, 1, 0); |
| } |
| if (ctx->models.search_paths) { |
| for(i = 0; ctx->models.search_paths[i]; i++) { |
| free(ctx->models.search_paths[i]); |
| } |
| free(ctx->models.search_paths); |
| } |
| free(ctx->models.list); |
| |
| /* clean the error list */ |
| ly_err_clean(ctx, 0); |
| pthread_key_delete(ctx->errlist_key); |
| |
| /* dictionary */ |
| lydict_clean(&ctx->dict); |
| |
| /* plugins - will be removed only if this is the last context */ |
| ly_clean_plugins(); |
| |
| free(ctx); |
| } |
| |
| API const struct lys_submodule * |
| ly_ctx_get_submodule2(const struct lys_module *main_module, const char *submodule) |
| { |
| const struct lys_submodule *result; |
| int i; |
| |
| if (!main_module || !submodule) { |
| LOGARG; |
| return NULL; |
| } |
| |
| /* search in submodules list */ |
| for (i = 0; i < main_module->inc_size; i++) { |
| result = main_module->inc[i].submodule; |
| if (ly_strequal(submodule, result->name, 0)) { |
| return result; |
| } |
| |
| /* in YANG 1.1 all the submodules must be included in the main module, so we are done. |
| * YANG 1.0 allows (is unclear about denying it) to include a submodule only in another submodule |
| * but when libyang parses such a module it adds the include into the main module so we are also done. |
| */ |
| } |
| |
| return NULL; |
| } |
| |
| API const struct lys_submodule * |
| ly_ctx_get_submodule(const struct ly_ctx *ctx, const char *module, const char *revision, const char *submodule, |
| const char *sub_revision) |
| { |
| const struct lys_module *mainmod; |
| const struct lys_submodule *ret = NULL, *submod; |
| uint32_t idx = 0; |
| |
| if (!ctx || !submodule || (revision && !module)) { |
| LOGARG; |
| return NULL; |
| } |
| |
| while ((mainmod = ly_ctx_get_module_iter(ctx, &idx))) { |
| if (module && strcmp(mainmod->name, module)) { |
| /* main module name does not match */ |
| continue; |
| } |
| |
| if (revision && (!mainmod->rev || strcmp(revision, mainmod->rev[0].date))) { |
| /* main module revision does not match */ |
| continue; |
| } |
| |
| submod = ly_ctx_get_submodule2(mainmod, submodule); |
| if (!submod) { |
| continue; |
| } |
| |
| if (!sub_revision) { |
| /* store only if newer */ |
| if (ret) { |
| if (submod->rev && (!ret->rev || (strcmp(submod->rev[0].date, ret->rev[0].date) > 0))) { |
| ret = submod; |
| } |
| } else { |
| ret = submod; |
| } |
| } else { |
| /* store only if revision matches, we are done if it does */ |
| if (!submod->rev) { |
| continue; |
| } else if (!strcmp(sub_revision, submod->rev[0].date)) { |
| ret = submod; |
| break; |
| } |
| } |
| } |
| |
| return ret; |
| } |
| |
| static const struct lys_module * |
| ly_ctx_get_module_by(const struct ly_ctx *ctx, const char *key, size_t key_len, int offset, const char *revision, |
| int with_disabled, int implemented) |
| { |
| int i; |
| char *val; |
| struct lys_module *result = NULL; |
| |
| if (!ctx || !key) { |
| LOGARG; |
| return NULL; |
| } |
| |
| for (i = 0; i < ctx->models.used; i++) { |
| if (!with_disabled && ctx->models.list[i]->disabled) { |
| /* skip the disabled modules */ |
| continue; |
| } |
| /* use offset to get address of the pointer to string (char**), remember that offset is in |
| * bytes, so we have to cast the pointer to the module to (char*), finally, we want to have |
| * string not the pointer to string |
| */ |
| val = *(char **)(((char *)ctx->models.list[i]) + offset); |
| if (!ctx->models.list[i] || (!key_len && strcmp(key, val)) || (key_len && (strncmp(key, val, key_len) || val[key_len]))) { |
| continue; |
| } |
| |
| if (!revision) { |
| /* compare revisons and remember the newest one */ |
| if (result) { |
| if (!ctx->models.list[i]->rev_size) { |
| /* the current have no revision, keep the previous with some revision */ |
| continue; |
| } |
| if (result->rev_size && strcmp(ctx->models.list[i]->rev[0].date, result->rev[0].date) < 0) { |
| /* the previous found matching module has a newer revision */ |
| continue; |
| } |
| } |
| if (implemented) { |
| if (ctx->models.list[i]->implemented) { |
| /* we have the implemented revision */ |
| result = ctx->models.list[i]; |
| break; |
| } else { |
| /* do not remember the result, we are supposed to return the implemented revision |
| * not the newest one */ |
| continue; |
| } |
| } |
| |
| /* remember the current match and search for newer version */ |
| result = ctx->models.list[i]; |
| } else { |
| if (ctx->models.list[i]->rev_size && !strcmp(revision, ctx->models.list[i]->rev[0].date)) { |
| /* matching revision */ |
| result = ctx->models.list[i]; |
| break; |
| } |
| } |
| } |
| |
| return result; |
| |
| } |
| |
| API const struct lys_module * |
| ly_ctx_get_module_by_ns(const struct ly_ctx *ctx, const char *ns, const char *revision, int implemented) |
| { |
| return ly_ctx_get_module_by(ctx, ns, 0, offsetof(struct lys_module, ns), revision, 0, implemented); |
| } |
| |
| API const struct lys_module * |
| ly_ctx_get_module(const struct ly_ctx *ctx, const char *name, const char *revision, int implemented) |
| { |
| return ly_ctx_get_module_by(ctx, name, 0, offsetof(struct lys_module, name), revision, 0, implemented); |
| } |
| |
| const struct lys_module * |
| ly_ctx_nget_module(const struct ly_ctx *ctx, const char *name, size_t name_len, const char *revision, int implemented) |
| { |
| return ly_ctx_get_module_by(ctx, name, name_len, offsetof(struct lys_module, name), revision, 0, implemented); |
| } |
| |
| API const struct lys_module * |
| ly_ctx_get_module_older(const struct ly_ctx *ctx, const struct lys_module *module) |
| { |
| int i; |
| const struct lys_module *result = NULL, *iter; |
| |
| if (!ctx || !module || !module->rev_size) { |
| LOGARG; |
| return NULL; |
| } |
| |
| |
| for (i = 0; i < ctx->models.used; i++) { |
| iter = ctx->models.list[i]; |
| if (iter->disabled) { |
| /* skip the disabled modules */ |
| continue; |
| } |
| if (iter == module || !iter->rev_size) { |
| /* iter is the module itself or iter has no revision */ |
| continue; |
| } |
| if (!ly_strequal(module->name, iter->name, 0)) { |
| /* different module */ |
| continue; |
| } |
| if (strcmp(iter->rev[0].date, module->rev[0].date) < 0) { |
| /* iter is older than module */ |
| if (result) { |
| if (strcmp(iter->rev[0].date, result->rev[0].date) > 0) { |
| /* iter is newer than current result */ |
| result = iter; |
| } |
| } else { |
| result = iter; |
| } |
| } |
| } |
| |
| return result; |
| } |
| |
| API void |
| ly_ctx_set_module_imp_clb(struct ly_ctx *ctx, ly_module_imp_clb clb, void *user_data) |
| { |
| if (!ctx) { |
| LOGARG; |
| return; |
| } |
| |
| ctx->imp_clb = clb; |
| ctx->imp_clb_data = user_data; |
| } |
| |
| API ly_module_imp_clb |
| ly_ctx_get_module_imp_clb(const struct ly_ctx *ctx, void **user_data) |
| { |
| if (!ctx) { |
| LOGARG; |
| return NULL; |
| } |
| |
| if (user_data) { |
| *user_data = ctx->imp_clb_data; |
| } |
| return ctx->imp_clb; |
| } |
| |
| API void |
| ly_ctx_set_module_data_clb(struct ly_ctx *ctx, ly_module_data_clb clb, void *user_data) |
| { |
| if (!ctx) { |
| LOGARG; |
| return; |
| } |
| |
| ctx->data_clb = clb; |
| ctx->data_clb_data = user_data; |
| } |
| |
| API ly_module_data_clb |
| ly_ctx_get_module_data_clb(const struct ly_ctx *ctx, void **user_data) |
| { |
| if (!ctx) { |
| LOGARG; |
| return NULL; |
| } |
| |
| if (user_data) { |
| *user_data = ctx->data_clb_data; |
| } |
| return ctx->data_clb; |
| } |
| |
| #ifdef LY_ENABLED_LYD_PRIV |
| |
| API void |
| ly_ctx_set_priv_dup_clb(struct ly_ctx *ctx, void *(*priv_dup_clb)(const void *priv)) |
| { |
| ctx->priv_dup_clb = priv_dup_clb; |
| } |
| |
| #endif |
| |
| /* if module is !NULL, then the function searches for submodule */ |
| static struct lys_module * |
| ly_ctx_load_localfile(struct ly_ctx *ctx, struct lys_module *module, const char *name, const char *revision, |
| int implement, struct unres_schema *unres) |
| { |
| size_t len; |
| int fd, i; |
| char *filepath = NULL, *dot, *rev, *filename; |
| LYS_INFORMAT format; |
| struct lys_module *result = NULL; |
| |
| if (lys_search_localfile(ly_ctx_get_searchdirs(ctx), !(ctx->models.flags & LY_CTX_DISABLE_SEARCHDIR_CWD), name, revision, |
| &filepath, &format)) { |
| goto cleanup; |
| } else if (!filepath) { |
| if (!module && !revision) { |
| /* otherwise the module would be already taken from the context */ |
| result = (struct lys_module *)ly_ctx_get_module(ctx, name, NULL, 0); |
| } |
| if (!result) { |
| LOGERR(ctx, LY_ESYS, "Data model \"%s\" not found.", name); |
| } |
| return result; |
| } |
| |
| LOGVRB("Loading schema from \"%s\" file.", filepath); |
| |
| /* cut the format for now */ |
| dot = strrchr(filepath, '.'); |
| dot[1] = '\0'; |
| |
| /* check that the same file was not already loaded - it make sense only in case of loading the newest revision, |
| * search also in disabled module - if the matching module is disabled, it will be enabled instead of loading it */ |
| if (!revision) { |
| for (i = 0; i < ctx->models.used; ++i) { |
| if (ctx->models.list[i]->filepath && !strcmp(name, ctx->models.list[i]->name) |
| && !strncmp(filepath, ctx->models.list[i]->filepath, strlen(filepath))) { |
| result = ctx->models.list[i]; |
| if (implement && !result->implemented) { |
| /* make it implemented now */ |
| if (lys_set_implemented(result)) { |
| result = NULL; |
| } |
| } else if (result->disabled) { |
| lys_set_enabled(result); |
| } |
| |
| goto cleanup; |
| } |
| } |
| } |
| |
| /* add the format back */ |
| dot[1] = 'y'; |
| |
| /* open the file */ |
| fd = open(filepath, O_RDONLY); |
| if (fd < 0) { |
| LOGERR(ctx, LY_ESYS, "Unable to open data model file \"%s\" (%s).", |
| filepath, strerror(errno)); |
| goto cleanup; |
| } |
| |
| if (module) { |
| result = (struct lys_module *)lys_sub_parse_fd(module, fd, format, unres); |
| } else { |
| result = (struct lys_module *)lys_parse_fd_(ctx, fd, format, revision, implement); |
| } |
| close(fd); |
| |
| if (!result) { |
| goto cleanup; |
| } |
| |
| /* check that name and revision match filename */ |
| filename = strrchr(filepath, '/'); |
| if (!filename) { |
| filename = filepath; |
| } else { |
| filename++; |
| } |
| rev = strchr(filename, '@'); |
| /* name */ |
| len = strlen(result->name); |
| if (strncmp(filename, result->name, len) || |
| ((rev && rev != &filename[len]) || (!rev && dot != &filename[len]))) { |
| LOGWRN(ctx, "File name \"%s\" does not match module name \"%s\".", filename, result->name); |
| } |
| if (rev) { |
| len = dot - ++rev; |
| if (!result->rev_size || len != 10 || strncmp(result->rev[0].date, rev, len)) { |
| LOGWRN(ctx, "File name \"%s\" does not match module revision \"%s\".", filename, |
| result->rev_size ? result->rev[0].date : "none"); |
| } |
| } |
| |
| /* success */ |
| cleanup: |
| free(filepath); |
| return result; |
| } |
| |
| const struct lys_module * |
| ly_ctx_load_sub_module(struct ly_ctx *ctx, struct lys_module *module, const char *name, const char *revision, |
| int implement, struct unres_schema *unres) |
| { |
| struct lys_module *mod = NULL; |
| char *module_data = NULL; |
| int i; |
| void (*module_data_free)(void *module_data) = NULL; |
| LYS_INFORMAT format = LYS_IN_UNKNOWN; |
| |
| if (!module) { |
| /* exception for internal modules */ |
| for (i = 0; i < ctx->internal_module_count; i++) { |
| if (ly_strequal(name, internal_modules[i].name, 0)) { |
| if (!revision || ly_strequal(revision, internal_modules[i].revision, 0)) { |
| /* return internal module */ |
| return (struct lys_module *)ly_ctx_get_module(ctx, name, revision, 0); |
| } |
| } |
| } |
| /* try to get the schema from the context (with or without revision), |
| * include the disabled modules in the search to avoid their duplication, |
| * they are enabled by the subsequent call to lys_set_implemented() */ |
| for (i = ctx->internal_module_count, mod = NULL; i < ctx->models.used; i++) { |
| mod = ctx->models.list[i]; /* shortcut */ |
| if (ly_strequal(name, mod->name, 0)) { |
| if (revision && mod->rev_size && !strcmp(revision, mod->rev[0].date)) { |
| /* the specific revision was already loaded */ |
| break; |
| } else if (!revision && mod->latest_revision) { |
| /* the latest revision of this module was already loaded */ |
| break; |
| } else if (implement && mod->implemented && !revision) { |
| /* we are not able to implement another module, so consider this module as the latest one */ |
| break; |
| } |
| } |
| mod = NULL; |
| } |
| if (mod) { |
| /* module must be enabled */ |
| if (mod->disabled) { |
| lys_set_enabled(mod); |
| } |
| /* module is supposed to be implemented */ |
| if (implement && lys_set_implemented(mod)) { |
| /* the schema cannot be implemented */ |
| mod = NULL; |
| } |
| return mod; |
| } |
| } |
| |
| /* module is not yet in context, use the user callback or try to find the schema on our own */ |
| if (ctx->imp_clb) { |
| ly_errno = LY_SUCCESS; |
| if (module) { |
| mod = lys_main_module(module); |
| module_data = ctx->imp_clb(mod->name, (mod->rev_size ? mod->rev[0].date : NULL), name, revision, ctx->imp_clb_data, &format, &module_data_free); |
| } else { |
| module_data = ctx->imp_clb(name, revision, NULL, NULL, ctx->imp_clb_data, &format, &module_data_free); |
| } |
| if (!module_data && (ly_errno != LY_SUCCESS)) { |
| /* callback encountered an error, do not change it */ |
| LOGERR(ctx, ly_errno, "User module retrieval callback failed!"); |
| return NULL; |
| } |
| } |
| |
| if (module_data) { |
| /* we got the module from the callback */ |
| if (module) { |
| mod = (struct lys_module *)lys_sub_parse_mem(module, module_data, format, unres); |
| } else { |
| mod = (struct lys_module *)lys_parse_mem_(ctx, module_data, format, NULL, 0, implement); |
| } |
| |
| if (module_data_free) { |
| module_data_free(module_data); |
| } |
| } else if (!(ctx->models.flags & LY_CTX_DISABLE_SEARCHDIRS)) { |
| /* module was not received from the callback or there is no callback set */ |
| mod = ly_ctx_load_localfile(ctx, module, name, revision, implement, unres); |
| } |
| |
| #ifdef LY_ENABLED_LATEST_REVISIONS |
| if (!revision && mod) { |
| /* module is the latest revision found */ |
| mod->latest_revision = 1; |
| } |
| #endif |
| |
| return mod; |
| } |
| |
| API const struct lys_module * |
| ly_ctx_load_module(struct ly_ctx *ctx, const char *name, const char *revision) |
| { |
| if (!ctx || !name) { |
| LOGARG; |
| return NULL; |
| } |
| |
| return ly_ctx_load_sub_module(ctx, NULL, name, revision && revision[0] ? revision : NULL, 1, NULL); |
| } |
| |
| /* |
| * mods - set of removed modules, if NULL all modules are supposed to be removed so any backlink is invalid |
| */ |
| static void |
| ctx_modules_undo_backlinks(struct ly_ctx *ctx, struct ly_set *mods) |
| { |
| int o; |
| uint8_t j; |
| unsigned int u, v; |
| struct lys_module *mod; |
| struct lys_node *elem, *next; |
| struct lys_node_leaf *leaf; |
| |
| /* maintain backlinks (start with internal ietf-yang-library which have leafs as possible targets of leafrefs */ |
| for (o = ctx->internal_module_count - 1; o < ctx->models.used; o++) { |
| mod = ctx->models.list[o]; /* shortcut */ |
| |
| /* 1) features */ |
| for (j = 0; j < mod->features_size; j++) { |
| if (!mod->features[j].depfeatures) { |
| continue; |
| } |
| for (v = 0; v < mod->features[j].depfeatures->number; v++) { |
| if (!mods || ly_set_contains(mods, ((struct lys_feature *)mod->features[j].depfeatures->set.g[v])->module) != -1) { |
| /* depending feature is in module to remove */ |
| ly_set_rm_index(mod->features[j].depfeatures, v); |
| v--; |
| } |
| } |
| if (!mod->features[j].depfeatures->number) { |
| /* all backlinks removed */ |
| ly_set_free(mod->features[j].depfeatures); |
| mod->features[j].depfeatures = NULL; |
| } |
| } |
| |
| /* 2) identities */ |
| for (u = 0; u < mod->ident_size; u++) { |
| if (!mod->ident[u].der) { |
| continue; |
| } |
| for (v = 0; v < mod->ident[u].der->number; v++) { |
| if (!mods || ly_set_contains(mods, ((struct lys_ident *)mod->ident[u].der->set.g[v])->module) != -1) { |
| /* derived identity is in module to remove */ |
| ly_set_rm_index(mod->ident[u].der, v); |
| v--; |
| } |
| } |
| if (!mod->ident[u].der->number) { |
| /* all backlinks removed */ |
| ly_set_free(mod->ident[u].der); |
| mod->ident[u].der = NULL; |
| } |
| } |
| |
| /* 3) leafrefs */ |
| for (elem = next = mod->data; elem; elem = next) { |
| if (elem->nodetype & (LYS_LEAF | LYS_LEAFLIST)) { |
| leaf = (struct lys_node_leaf *)elem; /* shortcut */ |
| if (leaf->backlinks) { |
| if (!mods) { |
| /* remove all backlinks */ |
| ly_set_free(leaf->backlinks); |
| leaf->backlinks = NULL; |
| } else { |
| for (v = 0; v < leaf->backlinks->number; v++) { |
| if (ly_set_contains(mods, leaf->backlinks->set.s[v]->module) != -1) { |
| /* derived identity is in module to remove */ |
| ly_set_rm_index(leaf->backlinks, v); |
| v--; |
| } |
| } |
| if (!leaf->backlinks->number) { |
| /* all backlinks removed */ |
| ly_set_free(leaf->backlinks); |
| leaf->backlinks = NULL; |
| } |
| } |
| } |
| } |
| |
| /* select next element to process */ |
| next = elem->child; |
| /* child exception for leafs, leaflists, anyxml and groupings */ |
| if (elem->nodetype & (LYS_LEAF | LYS_LEAFLIST | LYS_ANYDATA | LYS_GROUPING)) { |
| next = NULL; |
| } |
| if (!next) { |
| /* no children, try siblings */ |
| next = elem->next; |
| } |
| while (!next) { |
| /* parent is already processed, go to its sibling */ |
| elem = lys_parent(elem); |
| if (!elem) { |
| /* we are done, no next element to process */ |
| break; |
| } |
| /* no siblings, go back through parents */ |
| next = elem->next; |
| } |
| } |
| } |
| } |
| |
| static int |
| ctx_modules_redo_backlinks(struct ly_set *mods) |
| { |
| unsigned int i, j, k, s; |
| struct lys_module *mod; |
| struct lys_node *next, *elem; |
| struct lys_type *type; |
| struct lys_feature *feat; |
| |
| for (i = 0; i < mods->number; ++i) { |
| mod = (struct lys_module *)mods->set.g[i]; /* shortcut */ |
| |
| /* identities */ |
| if (mod->implemented) { |
| for (j = 0; j < mod->ident_size; j++) { |
| for (k = 0; k < mod->ident[j].base_size; k++) { |
| resolve_identity_backlink_update(&mod->ident[j], mod->ident[j].base[k]); |
| } |
| } |
| } |
| |
| /* features */ |
| for (j = 0; j < mod->features_size; j++) { |
| for (k = 0; k < mod->features[j].iffeature_size; k++) { |
| resolve_iffeature_getsizes(&mod->features[j].iffeature[k], NULL, &s); |
| while (s--) { |
| feat = mod->features[j].iffeature[k].features[s]; /* shortcut */ |
| if (!feat->depfeatures) { |
| feat->depfeatures = ly_set_new(); |
| } |
| ly_set_add(feat->depfeatures, &mod->features[j], LY_SET_OPT_USEASLIST); |
| } |
| } |
| } |
| |
| /* leafrefs */ |
| LY_TREE_DFS_BEGIN(mod->data, next, elem) { |
| if (elem->nodetype == LYS_GROUPING) { |
| goto next_sibling; |
| } |
| |
| if (elem->nodetype & (LYS_LEAF | LYS_LEAFLIST)) { |
| type = &((struct lys_node_leaf *)elem)->type; /* shortcut */ |
| if (type->base == LY_TYPE_LEAFREF) { |
| lys_leaf_add_leafref_target(type->info.lref.target, elem); |
| } |
| } |
| |
| /* select element for the next run - children first */ |
| next = elem->child; |
| |
| /* child exception for leafs, leaflists and anyxml without children */ |
| if (elem->nodetype & (LYS_LEAF | LYS_LEAFLIST | LYS_ANYDATA)) { |
| next = NULL; |
| } |
| if (!next) { |
| next_sibling: |
| /* no children */ |
| if (elem == mod->data) { |
| /* we are done, (START) has no children */ |
| break; |
| } |
| /* try siblings */ |
| next = elem->next; |
| } |
| while (!next) { |
| /* parent is already processed, go to its sibling */ |
| elem = lys_parent(elem); |
| |
| /* no siblings, go back through parents */ |
| if (lys_parent(elem) == lys_parent(mod->data)) { |
| /* we are done, no next element to process */ |
| break; |
| } |
| next = elem->next; |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| API int |
| lys_set_disabled(const struct lys_module *module) |
| { |
| struct ly_ctx *ctx; /* shortcut */ |
| struct lys_module *mod; |
| struct ly_set *mods; |
| uint8_t j, imported; |
| int i, o; |
| unsigned int u, v; |
| |
| if (!module) { |
| LOGARG; |
| return EXIT_FAILURE; |
| } else if (module->disabled) { |
| /* already disabled module */ |
| return EXIT_SUCCESS; |
| } |
| mod = (struct lys_module *)module; |
| ctx = mod->ctx; |
| |
| /* avoid disabling internal modules */ |
| for (i = 0; i < ctx->internal_module_count; i++) { |
| if (mod == ctx->models.list[i]) { |
| LOGERR(ctx, LY_EINVAL, "Internal module \"%s\" cannot be disabled.", mod->name); |
| return EXIT_FAILURE; |
| } |
| } |
| |
| /* disable the module */ |
| mod->disabled = 1; |
| |
| /* get the complete list of modules to disable because of dependencies, |
| * we are going also to disable all the imported (not implemented) modules |
| * that are not used in any other module */ |
| mods = ly_set_new(); |
| ly_set_add(mods, mod, 0); |
| checkdependency: |
| for (i = ctx->internal_module_count; i < ctx->models.used; i++) { |
| mod = ctx->models.list[i]; /* shortcut */ |
| if (mod->disabled) { |
| /* skip the already disabled modules */ |
| continue; |
| } |
| |
| /* check depndency of imported modules */ |
| for (j = 0; j < mod->imp_size; j++) { |
| for (u = 0; u < mods->number; u++) { |
| if (mod->imp[j].module == mods->set.g[u]) { |
| /* module is importing some module to disable, so it must be also disabled */ |
| mod->disabled = 1; |
| ly_set_add(mods, mod, 0); |
| /* we have to start again because some of the already checked modules can |
| * depend on the one we have just decided to disable */ |
| goto checkdependency; |
| } |
| } |
| } |
| /* check if the imported module is used in any module supposed to be kept */ |
| if (!mod->implemented) { |
| imported = 0; |
| for (o = ctx->internal_module_count; o < ctx->models.used; o++) { |
| if (ctx->models.list[o]->disabled) { |
| /* skip modules already disabled */ |
| continue; |
| } |
| for (j = 0; j < ctx->models.list[o]->imp_size; j++) { |
| if (ctx->models.list[o]->imp[j].module == mod) { |
| /* the module is used in some other module not yet selected to be disabled */ |
| imported = 1; |
| goto imported; |
| } |
| } |
| } |
| imported: |
| if (!imported) { |
| /* module is not implemented and neither imported by any other module in context |
| * which is supposed to be kept enabled after this operation, so we are going to disable also |
| * this module */ |
| mod->disabled = 1; |
| ly_set_add(mods, mod, 0); |
| /* we have to start again, this time not because other module can depend on this one |
| * (we know that there is no such module), but because the module can import module |
| * that could became useless. If there are no imports, we can continue */ |
| if (mod->imp_size) { |
| goto checkdependency; |
| } |
| } |
| } |
| } |
| |
| /* before removing applied deviations, augments and updating leafrefs, we have to enable the modules |
| * to disable to allow all that operations */ |
| for (u = 0; u < mods->number; u++) { |
| ((struct lys_module *)mods->set.g[u])->disabled = 0; |
| } |
| |
| /* maintain backlinks (start with internal ietf-yang-library which have leafs as possible targets of leafrefs */ |
| ctx_modules_undo_backlinks(ctx, mods); |
| |
| /* remove the applied deviations and augments */ |
| u = mods->number; |
| while (u--) { |
| lys_sub_module_remove_devs_augs((struct lys_module *)mods->set.g[u]); |
| } |
| |
| /* now again disable the modules to disable and disable also all its submodules */ |
| for (u = 0; u < mods->number; u++) { |
| mod = (struct lys_module *)mods->set.g[u]; |
| mod->disabled = 1; |
| for (v = 0; v < mod->inc_size; v++) { |
| mod->inc[v].submodule->disabled = 1; |
| } |
| } |
| |
| /* free the set */ |
| ly_set_free(mods); |
| |
| /* update the module-set-id */ |
| ctx->models.module_set_id++; |
| |
| return EXIT_SUCCESS; |
| } |
| |
| static void |
| lys_set_enabled_(struct ly_set *mods, struct lys_module *mod) |
| { |
| unsigned int i; |
| |
| ly_set_add(mods, mod, 0); |
| mod->disabled = 0; |
| |
| for (i = 0; i < mod->inc_size; i++) { |
| mod->inc[i].submodule->disabled = 0; |
| } |
| |
| /* go recursively */ |
| for (i = 0; i < mod->imp_size; i++) { |
| if (!mod->imp[i].module->disabled) { |
| continue; |
| } |
| |
| lys_set_enabled_(mods, mod->imp[i].module); |
| } |
| } |
| |
| API int |
| lys_set_enabled(const struct lys_module *module) |
| { |
| struct ly_ctx *ctx; /* shortcut */ |
| struct lys_module *mod; |
| struct ly_set *mods, *disabled; |
| int i; |
| unsigned int u, v, w; |
| |
| if (!module) { |
| LOGARG; |
| return EXIT_FAILURE; |
| } else if (!module->disabled) { |
| /* already enabled module */ |
| return EXIT_SUCCESS; |
| } |
| mod = (struct lys_module *)module; |
| ctx = mod->ctx; |
| |
| /* avoid disabling internal modules */ |
| for (i = 0; i < ctx->internal_module_count; i++) { |
| if (mod == ctx->models.list[i]) { |
| LOGERR(ctx, LY_EINVAL, "Internal module \"%s\" cannot be removed.", mod->name); |
| return EXIT_FAILURE; |
| } |
| } |
| |
| mods = ly_set_new(); |
| disabled = ly_set_new(); |
| |
| /* enable the module, including its dependencies */ |
| lys_set_enabled_(mods, mod); |
| |
| /* we will go through the all disabled modules in the context, if the module has no dependency (import) |
| * that is still disabled AND at least one of its imported module is from the set we are enabling now, |
| * it is going to be also enabled. This way we try to revert everething that was possibly done by |
| * lys_set_disabled(). */ |
| checkdependency: |
| for (i = ctx->internal_module_count; i < ctx->models.used; i++) { |
| mod = ctx->models.list[i]; /* shortcut */ |
| if (!mod->disabled || ly_set_contains(disabled, mod) != -1) { |
| /* skip the enabled modules */ |
| continue; |
| } |
| |
| /* check imported modules */ |
| for (u = 0; u < mod->imp_size; u++) { |
| if (mod->imp[u].module->disabled) { |
| /* it has disabled dependency so it must stay disabled */ |
| break; |
| } |
| } |
| if (u < mod->imp_size) { |
| /* it has disabled dependency, continue with the next module in the context */ |
| continue; |
| } |
| |
| /* get know if at least one of the imported modules is being enabled this time */ |
| for (u = 0; u < mod->imp_size; u++) { |
| for (v = 0; v < mods->number; v++) { |
| if (mod->imp[u].module == mods->set.g[v]) { |
| /* yes, it is, so they are connected and we are going to enable it as well, |
| * it is not necessary to call recursive lys_set_enable_() because we already |
| * know that there is no disabled import to enable */ |
| mod->disabled = 0; |
| ly_set_add(mods, mod, 0); |
| for (w = 0; w < mod->inc_size; w++) { |
| mod->inc[w].submodule->disabled = 0; |
| } |
| /* we have to start again because some of the already checked modules can |
| * depend on the one we have just decided to enable */ |
| goto checkdependency; |
| } |
| } |
| } |
| |
| /* this module is disabled, but it does not depend on any other disabled module and none |
| * of its imports was not enabled in this call. No future enabling of the disabled module |
| * will change this so we can remember the module and skip it next time we will have to go |
| * through the all context because of the checkdependency goto. |
| */ |
| ly_set_add(disabled, mod, 0); |
| } |
| |
| /* maintain backlinks (start with internal ietf-yang-library which have leafs as possible targets of leafrefs */ |
| ctx_modules_redo_backlinks(mods); |
| |
| /* re-apply the deviations and augments */ |
| for (v = 0; v < mods->number; v++) { |
| if (((struct lys_module *)mods->set.g[v])->implemented) { |
| lys_sub_module_apply_devs_augs((struct lys_module *)mods->set.g[v]); |
| } |
| } |
| |
| /* free the sets */ |
| ly_set_free(mods); |
| ly_set_free(disabled); |
| |
| /* update the module-set-id */ |
| ctx->models.module_set_id++; |
| |
| return EXIT_SUCCESS; |
| } |
| |
| API int |
| ly_ctx_remove_module(const struct lys_module *module, |
| void (*private_destructor)(const struct lys_node *node, void *priv)) |
| { |
| struct ly_ctx *ctx; /* shortcut */ |
| struct lys_module *mod = NULL; |
| struct ly_set *mods; |
| uint8_t j, imported; |
| int i, o; |
| unsigned int u; |
| |
| if (!module) { |
| LOGARG; |
| return EXIT_FAILURE; |
| } |
| |
| mod = (struct lys_module *)module; |
| ctx = mod->ctx; |
| |
| /* avoid removing internal modules ... */ |
| for (i = 0; i < ctx->internal_module_count; i++) { |
| if (mod == ctx->models.list[i]) { |
| LOGERR(ctx, LY_EINVAL, "Internal module \"%s\" cannot be removed.", mod->name); |
| return EXIT_FAILURE; |
| } |
| } |
| /* ... and hide the module from the further processing of the context modules list */ |
| for (i = ctx->internal_module_count; i < ctx->models.used; i++) { |
| if (mod == ctx->models.list[i]) { |
| ctx->models.list[i] = NULL; |
| break; |
| } |
| } |
| |
| /* get the complete list of modules to remove because of dependencies, |
| * we are going also to remove all the imported (not implemented) modules |
| * that are not used in any other module */ |
| mods = ly_set_new(); |
| ly_set_add(mods, mod, 0); |
| checkdependency: |
| for (i = ctx->internal_module_count; i < ctx->models.used; i++) { |
| mod = ctx->models.list[i]; /* shortcut */ |
| if (!mod) { |
| /* skip modules already selected for removing */ |
| continue; |
| } |
| |
| /* check depndency of imported modules */ |
| for (j = 0; j < mod->imp_size; j++) { |
| for (u = 0; u < mods->number; u++) { |
| if (mod->imp[j].module == mods->set.g[u]) { |
| /* module is importing some module to remove, so it must be also removed */ |
| ly_set_add(mods, mod, 0); |
| ctx->models.list[i] = NULL; |
| /* we have to start again because some of the already checked modules can |
| * depend on the one we have just decided to remove */ |
| goto checkdependency; |
| } |
| } |
| } |
| /* check if the imported module is used in any module supposed to be kept */ |
| if (!mod->implemented) { |
| imported = 0; |
| for (o = ctx->internal_module_count; o < ctx->models.used; o++) { |
| if (!ctx->models.list[o]) { |
| /* skip modules already selected for removing */ |
| continue; |
| } |
| for (j = 0; j < ctx->models.list[o]->imp_size; j++) { |
| if (ctx->models.list[o]->imp[j].module == mod) { |
| /* the module is used in some other module not yet selected to be deleted */ |
| imported = 1; |
| goto imported; |
| } |
| } |
| } |
| imported: |
| if (!imported) { |
| /* module is not implemented and neither imported by any other module in context |
| * which is supposed to be kept after this operation, so we are going to remove also |
| * this useless module */ |
| ly_set_add(mods, mod, 0); |
| ctx->models.list[i] = NULL; |
| /* we have to start again, this time not because other module can depend on this one |
| * (we know that there is no such module), but because the module can import module |
| * that could became useless. If there are no imports, we can continue */ |
| if (mod->imp_size) { |
| goto checkdependency; |
| } |
| } |
| } |
| } |
| |
| |
| /* consolidate the modules list */ |
| for (i = o = ctx->internal_module_count; i < ctx->models.used; i++) { |
| if (ctx->models.list[o]) { |
| /* used cell */ |
| o++; |
| } else { |
| /* the current output cell is empty, move here an input cell */ |
| ctx->models.list[o] = ctx->models.list[i]; |
| ctx->models.list[i] = NULL; |
| } |
| } |
| /* get the last used cell to get know the number of used */ |
| while (!ctx->models.list[o]) { |
| o--; |
| } |
| ctx->models.used = o + 1; |
| ctx->models.module_set_id++; |
| |
| /* maintain backlinks (start with internal ietf-yang-library which have leafs as possible targets of leafrefs */ |
| ctx_modules_undo_backlinks(ctx, mods); |
| |
| /* free the modules */ |
| for (u = 0; u < mods->number; u++) { |
| /* remove the applied deviations and augments */ |
| lys_sub_module_remove_devs_augs((struct lys_module *)mods->set.g[u]); |
| /* remove the module */ |
| lys_free((struct lys_module *)mods->set.g[u], private_destructor, 1, 0); |
| } |
| ly_set_free(mods); |
| |
| return EXIT_SUCCESS; |
| } |
| |
| API void |
| ly_ctx_clean(struct ly_ctx *ctx, void (*private_destructor)(const struct lys_node *node, void *priv)) |
| { |
| if (!ctx) { |
| return; |
| } |
| |
| /* models list */ |
| for (; ctx->models.used > ctx->internal_module_count; ctx->models.used--) { |
| /* remove the applied deviations and augments */ |
| lys_sub_module_remove_devs_augs(ctx->models.list[ctx->models.used - 1]); |
| /* remove the module */ |
| lys_free(ctx->models.list[ctx->models.used - 1], private_destructor, 1, 0); |
| /* clean it for safer future use */ |
| ctx->models.list[ctx->models.used - 1] = NULL; |
| } |
| ctx->models.module_set_id++; |
| |
| /* maintain backlinks (actually done only with ietf-yang-library since its leafs can be target of leafref) */ |
| ctx_modules_undo_backlinks(ctx, NULL); |
| } |
| |
| API const struct lys_module * |
| ly_ctx_get_module_iter(const struct ly_ctx *ctx, uint32_t *idx) |
| { |
| if (!ctx || !idx) { |
| LOGARG; |
| return NULL; |
| } |
| |
| for ( ; *idx < (unsigned)ctx->models.used; (*idx)++) { |
| if (!ctx->models.list[(*idx)]->disabled) { |
| return ctx->models.list[(*idx)++]; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| API const struct lys_module * |
| ly_ctx_get_disabled_module_iter(const struct ly_ctx *ctx, uint32_t *idx) |
| { |
| if (!ctx || !idx) { |
| LOGARG; |
| return NULL; |
| } |
| |
| for ( ; *idx < (unsigned)ctx->models.used; (*idx)++) { |
| if (ctx->models.list[(*idx)]->disabled) { |
| return ctx->models.list[(*idx)++]; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static int |
| ylib_feature(struct lyd_node *parent, struct lys_module *cur_mod, int bis) |
| { |
| int i, j; |
| struct lyd_node *list; |
| |
| /* module features */ |
| for (i = 0; i < cur_mod->features_size; ++i) { |
| if (!(cur_mod->features[i].flags & LYS_FENABLED)) { |
| continue; |
| } |
| |
| if (bis) { |
| if (!(list = lyd_new(parent, NULL, "feature")) || !lyd_new_leaf(list, NULL, "name", cur_mod->features[i].name)) { |
| return EXIT_FAILURE; |
| } |
| } else if (!lyd_new_leaf(parent, NULL, "feature", cur_mod->features[i].name)) { |
| return EXIT_FAILURE; |
| } |
| } |
| |
| /* submodule features */ |
| for (i = 0; i < cur_mod->inc_size && cur_mod->inc[i].submodule; ++i) { |
| for (j = 0; j < cur_mod->inc[i].submodule->features_size; ++j) { |
| if (!(cur_mod->inc[i].submodule->features[j].flags & LYS_FENABLED)) { |
| continue; |
| } |
| |
| if (bis) { |
| if (!(list = lyd_new(parent, NULL, "feature")) |
| || !lyd_new_leaf(list, NULL, "name", cur_mod->inc[i].submodule->features[j].name)) { |
| return EXIT_FAILURE; |
| } |
| } else if (!lyd_new_leaf(parent, NULL, "feature", cur_mod->inc[i].submodule->features[j].name)) { |
| return EXIT_FAILURE; |
| } |
| } |
| } |
| |
| return EXIT_SUCCESS; |
| } |
| |
| static int |
| ylib_deviation(struct lyd_node *parent, struct lys_module *cur_mod, int bis) |
| { |
| uint32_t i = 0, j; |
| const struct lys_module *mod; |
| struct lyd_node *cont; |
| const char *ptr; |
| |
| if (cur_mod->deviated) { |
| while ((mod = ly_ctx_get_module_iter(cur_mod->ctx, &i))) { |
| if (mod == cur_mod) { |
| continue; |
| } |
| |
| for (j = 0; j < mod->deviation_size; ++j) { |
| ptr = strstr(mod->deviation[j].target_name, cur_mod->name); |
| if (ptr && ptr[strlen(cur_mod->name)] == ':') { |
| cont = lyd_new(parent, NULL, "deviation"); |
| if (!cont) { |
| return EXIT_FAILURE; |
| } |
| |
| if (bis) { |
| if (!lyd_new_leaf(cont, NULL, "module", mod->name)) { |
| return EXIT_FAILURE; |
| } |
| } else { |
| if (!lyd_new_leaf(cont, NULL, "name", mod->name)) { |
| return EXIT_FAILURE; |
| } |
| if (!lyd_new_leaf(cont, NULL, "revision", (mod->rev_size ? mod->rev[0].date : ""))) { |
| return EXIT_FAILURE; |
| } |
| } |
| |
| break; |
| } |
| } |
| } |
| } |
| |
| return EXIT_SUCCESS; |
| } |
| |
| static int |
| ylib_submodules(struct lyd_node *parent, struct lys_module *cur_mod, int bis) |
| { |
| int i; |
| char *str; |
| struct lyd_node *item; |
| |
| for (i = 0; i < cur_mod->inc_size && cur_mod->inc[i].submodule; ++i) { |
| item = lyd_new(parent, NULL, "submodule"); |
| if (!item) { |
| return EXIT_FAILURE; |
| } |
| |
| if (!lyd_new_leaf(item, NULL, "name", cur_mod->inc[i].submodule->name)) { |
| return EXIT_FAILURE; |
| } |
| if ((!bis || cur_mod->inc[i].submodule->rev_size) |
| && !lyd_new_leaf(item, NULL, "revision", |
| (cur_mod->inc[i].submodule->rev_size ? cur_mod->inc[i].submodule->rev[0].date : ""))) { |
| return EXIT_FAILURE; |
| } |
| if (cur_mod->inc[i].submodule->filepath) { |
| if (asprintf(&str, "file://%s", cur_mod->inc[i].submodule->filepath) == -1) { |
| LOGMEM(cur_mod->ctx); |
| return EXIT_FAILURE; |
| } else if (!lyd_new_leaf(item, NULL, bis ? "location" : "schema", str)) { |
| free(str); |
| return EXIT_FAILURE; |
| } |
| free(str); |
| } |
| } |
| |
| return EXIT_SUCCESS; |
| } |
| |
| API uint16_t |
| ly_ctx_get_module_set_id(const struct ly_ctx *ctx) |
| { |
| return ctx->models.module_set_id; |
| } |
| |
| API struct lyd_node * |
| ly_ctx_info(struct ly_ctx *ctx) |
| { |
| int i, bis = 0; |
| char id[8]; |
| char *str; |
| const struct lys_module *mod; |
| struct lyd_node *root, *root_bis = NULL, *cont = NULL, *set_bis = NULL; |
| |
| if (!ctx) { |
| LOGARG; |
| return NULL; |
| } |
| |
| mod = ly_ctx_get_module(ctx, "ietf-yang-library", NULL, 1); |
| if (!mod || !mod->data) { |
| LOGERR(ctx, LY_EINVAL, "ietf-yang-library is not implemented."); |
| return NULL; |
| } |
| if (mod->rev && !strcmp(mod->rev[0].date, "2016-04-09")) { |
| bis = 0; |
| } else if (mod->rev && !strcmp(mod->rev[0].date, IETF_YANG_LIB_REV)) { |
| bis = 1; |
| } else { |
| LOGERR(ctx, LY_EINVAL, "Incompatible ietf-yang-library version in context."); |
| return NULL; |
| } |
| |
| root = lyd_new(NULL, mod, "modules-state"); |
| if (!root) { |
| return NULL; |
| } |
| |
| if (bis) { |
| if (!(root_bis = lyd_new(NULL, mod, "yang-library")) || !(set_bis = lyd_new(root_bis, NULL, "module-set"))) { |
| goto error; |
| } |
| |
| if (!lyd_new_leaf(set_bis, NULL, "name", "complete")) { |
| goto error; |
| } |
| |
| sprintf(id, "%u", ctx->models.module_set_id); |
| if (!lyd_new_leaf(set_bis, NULL, "checksum", id)) { |
| goto error; |
| } |
| } |
| |
| for (i = 0; i < ctx->models.used; ++i) { |
| if (ctx->models.list[i]->disabled) { |
| /* skip the disabled modules */ |
| continue; |
| } |
| |
| /* |
| * deprecated legacy |
| */ |
| cont = lyd_new(root, NULL, "module"); |
| if (!cont) { |
| goto error; |
| } |
| /* name */ |
| if (!lyd_new_leaf(cont, NULL, "name", ctx->models.list[i]->name)) { |
| goto error; |
| } |
| /* revision */ |
| if (!lyd_new_leaf(cont, NULL, "revision", (ctx->models.list[i]->rev_size ? ctx->models.list[i]->rev[0].date : ""))) { |
| goto error; |
| } |
| /* schema */ |
| if (ctx->models.list[i]->filepath) { |
| if (asprintf(&str, "file://%s", ctx->models.list[i]->filepath) == -1) { |
| LOGMEM(ctx); |
| goto error; |
| } |
| if (!lyd_new_leaf(cont, NULL, "schema", str)) { |
| free(str); |
| goto error; |
| } |
| free(str); |
| } |
| /* namespace */ |
| if (!lyd_new_leaf(cont, NULL, "namespace", ctx->models.list[i]->ns)) { |
| goto error; |
| } |
| /* feature leaf-list */ |
| if (ylib_feature(cont, ctx->models.list[i], 0)) { |
| goto error; |
| } |
| /* deviation list */ |
| if (ylib_deviation(cont, ctx->models.list[i], 0)) { |
| goto error; |
| } |
| /* conformance-type */ |
| if (!lyd_new_leaf(cont, NULL, "conformance-type", ctx->models.list[i]->implemented ? "implement" : "import")) { |
| goto error; |
| } |
| /* submodule list */ |
| if (ylib_submodules(cont, ctx->models.list[i], 0)) { |
| goto error; |
| } |
| |
| /* |
| * current revision |
| */ |
| if (bis) { |
| if (ctx->models.list[i]->implemented) { |
| if (!(cont = lyd_new(set_bis, NULL, "module"))) { |
| goto error; |
| } |
| } else { |
| if (!(cont = lyd_new(set_bis, NULL, "import-only-module"))) { |
| goto error; |
| } |
| } |
| /* name */ |
| if (!lyd_new_leaf(cont, NULL, "name", ctx->models.list[i]->name)) { |
| goto error; |
| } |
| /* revision */ |
| if ((!ctx->models.list[i]->implemented || ctx->models.list[i]->rev_size) |
| && !lyd_new_leaf(cont, NULL, "revision", ctx->models.list[i]->rev[0].date)) { |
| goto error; |
| } |
| /* namespace */ |
| if (!lyd_new_leaf(cont, NULL, "namespace", ctx->models.list[i]->ns)) { |
| goto error; |
| } |
| /* location */ |
| if (ctx->models.list[i]->filepath) { |
| if (asprintf(&str, "file://%s", ctx->models.list[i]->filepath) == -1) { |
| LOGMEM(ctx); |
| goto error; |
| } |
| if (!lyd_new_leaf(cont, NULL, "location", str)) { |
| free(str); |
| goto error; |
| } |
| free(str); |
| } |
| /* submodule list */ |
| if (ylib_submodules(cont, ctx->models.list[i], 1)) { |
| goto error; |
| } |
| if (ctx->models.list[i]->implemented) { |
| /* feature list */ |
| if (ylib_feature(cont, ctx->models.list[i], 1)) { |
| goto error; |
| } |
| /* deviation */ |
| if (ylib_deviation(cont, ctx->models.list[i], 1)) { |
| goto error; |
| } |
| } |
| } |
| } |
| |
| sprintf(id, "%u", ctx->models.module_set_id); |
| if (!lyd_new_leaf(root, NULL, "module-set-id", id)) { |
| goto error; |
| } |
| if (bis && !lyd_new_leaf(root_bis, NULL, "checksum", id)) { |
| goto error; |
| } |
| |
| if (root_bis) { |
| if (lyd_insert_sibling(&root_bis, root)) { |
| goto error; |
| } |
| root = root_bis; |
| root_bis = 0; |
| } |
| |
| if (lyd_validate(&root, LYD_OPT_NOSIBLINGS, NULL)) { |
| goto error; |
| } |
| |
| return root; |
| |
| error: |
| lyd_free_withsiblings(root); |
| lyd_free_withsiblings(root_bis); |
| return NULL; |
| } |
| |
| API const struct lys_node * |
| ly_ctx_get_node(struct ly_ctx *ctx, const struct lys_node *start, const char *nodeid, int output) |
| { |
| const struct lys_node *node; |
| |
| if ((!ctx && !start) || !nodeid || ((nodeid[0] != '/') && !start)) { |
| LOGARG; |
| return NULL; |
| } |
| |
| if (!ctx) { |
| ctx = start->module->ctx; |
| } |
| |
| /* sets error and everything */ |
| node = resolve_json_nodeid(nodeid, ctx, start, output); |
| |
| return node; |
| } |
| |
| API struct ly_set * |
| ly_ctx_find_path(struct ly_ctx *ctx, const char *path) |
| { |
| struct ly_set *resultset = NULL; |
| |
| if (!ctx || !path) { |
| LOGARG; |
| return NULL; |
| } |
| |
| /* start in internal module without data to make sure that all the nodes are prefixed */ |
| resolve_schema_nodeid(path, NULL, ctx->models.list[0], &resultset, 1, 1); |
| return resultset; |
| } |