tadeas-vintrlik | bf8aa6e | 2021-11-03 13:07:34 +0100 | [diff] [blame^] | 1 | /** |
| 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> |
| 18 | #include <stdint.h> |
| 19 | #include <stdlib.h> |
| 20 | #include <string.h> |
| 21 | |
| 22 | #include "dict.h" |
| 23 | #include "libyang.h" |
| 24 | #include "log.h" |
| 25 | #include "plugins_exts.h" |
| 26 | #include "tree_data.h" |
| 27 | #include "tree_schema.h" |
| 28 | |
| 29 | /** |
| 30 | * @brief Check if given mount point is unique among its' siblings |
| 31 | * |
| 32 | * @param cctx Compilation context. |
| 33 | * @param c_ext Compiled extension instance for checking uniqueness. |
| 34 | * @param p_ext Extension instance of the mount-point for comparison. |
| 35 | * |
| 36 | * @return LY_SUCCESS if is unique. LY_EINVAL otherwise. |
| 37 | */ |
| 38 | static LY_ERR |
| 39 | schema_mount_unique_mount_point(struct lysc_ctx *cctx, const struct lysc_ext_instance *c_ext, |
| 40 | const struct lysp_ext_instance *p_ext) |
| 41 | { |
| 42 | struct lysp_module *pmod; |
| 43 | struct lysp_ext_instance *exts; |
| 44 | LY_ARRAY_COUNT_TYPE u, v; |
| 45 | struct lysp_node *parent; |
| 46 | struct lysp_import *module; |
| 47 | char *ext_prefix, *ext_name; |
| 48 | |
| 49 | /* Check if it is the only instance of the mount-point among its' siblings */ |
| 50 | parent = (struct lysp_node *) c_ext->parent; |
| 51 | exts = parent->exts; |
| 52 | pmod = lysc_ctx_get_pmod(cctx); |
| 53 | LY_ARRAY_FOR(exts, u) { |
| 54 | /* Extract prefix and name of the extension */ |
| 55 | ext_prefix = strdup(exts[u].name); |
| 56 | ext_name = strstr(exts[u].name, ":"); |
| 57 | ext_name++; |
| 58 | ext_prefix[strstr(ext_prefix, ":") - ext_prefix] = '\0'; |
| 59 | |
| 60 | module = NULL; |
| 61 | LY_ARRAY_FOR(pmod->imports, v) { |
| 62 | if (!strcmp(pmod->imports[v].prefix, ext_prefix)) { |
| 63 | /* Found the matching module */ |
| 64 | module = &pmod->imports[v]; |
| 65 | break; |
| 66 | } |
| 67 | } |
| 68 | free(ext_prefix); |
| 69 | if ((&exts[u] != p_ext) && module && (!strcmp(module->name, "ietf-yang-schema-mount")) && |
| 70 | (!strcmp(exts[u].name, "mount-point"))) { |
| 71 | /* Found another instance of mount-point only one allowed per node */ |
| 72 | return LY_EINVAL; |
| 73 | } |
| 74 | } |
| 75 | return LY_SUCCESS; |
| 76 | } |
| 77 | |
| 78 | /** |
| 79 | * @brief Schema mount compile. |
| 80 | * |
| 81 | * Checks if it can be a valid extension instance for yang schema mount. |
| 82 | * |
| 83 | * Implementation of ::lyplg_ext_compile_clb callback set as lyext_plugin::compile. |
| 84 | */ |
| 85 | static LY_ERR |
| 86 | schema_mount_compile(struct lysc_ctx *cctx, const struct lysp_ext_instance *p_ext, |
| 87 | struct lysc_ext_instance *c_ext) |
| 88 | { |
| 89 | const struct lys_module *cur_mod; |
| 90 | |
| 91 | /* Check if processing right callback */ |
| 92 | assert(!strcmp(p_ext->name, "yangmnt:mount-point")); |
| 93 | |
| 94 | /* Check if mount point was found in YANG version 1.1 module */ |
| 95 | cur_mod = lysc_ctx_get_cur_mod(cctx); |
| 96 | if (cur_mod->parsed->version != LYS_VERSION_1_1) { |
| 97 | return LY_EINVAL; |
| 98 | } |
| 99 | |
| 100 | /* Check if its' parent is a container or a list */ |
| 101 | if ((p_ext->parent_stmt != LY_STMT_CONTAINER) && (p_ext->parent_stmt != LY_STMT_LIST)) { |
| 102 | return LY_EINVAL; |
| 103 | } |
| 104 | |
| 105 | /* Check if the only mount-point among siblings */ |
| 106 | if (schema_mount_unique_mount_point(cctx, c_ext, p_ext)) { |
| 107 | return LY_EINVAL; |
| 108 | } |
| 109 | |
| 110 | (void)c_ext; |
| 111 | |
| 112 | return LY_SUCCESS; |
| 113 | } |
| 114 | |
| 115 | /** |
| 116 | * @brief Parse callback for schema mount. |
| 117 | * |
| 118 | * Check if data is valid for schema mount and inserts it to the parent. |
| 119 | */ |
| 120 | static LY_ERR |
| 121 | schema_mount_parse(struct ly_in *in, LYD_FORMAT format, struct lysc_ext_instance *ext, |
| 122 | struct lyd_node *parent, uint32_t parse_opts, uint32_t val_opts) |
| 123 | { |
| 124 | LY_ERR ret = LY_SUCCESS; |
| 125 | const struct ly_ctx *ctx; |
| 126 | struct lyd_node *subtree, *yanglib, *mount_point, *final = NULL; |
| 127 | struct ly_err_item *err; |
| 128 | ly_bool found_yanglib = 0, found_mount_point = 0; |
| 129 | uint32_t old_log_opts; |
| 130 | |
| 131 | ctx = LYD_CTX(parent); |
| 132 | |
| 133 | old_log_opts = ly_log_options(LY_LOSTORE_LAST); |
| 134 | /* Check if intended for schema-mount - had both required data nodes */ |
| 135 | while (1) { |
| 136 | /* Parse by sub-trees */ |
| 137 | if (lyd_parse_data(ctx, NULL, in, 0, parse_opts, val_opts, &subtree)) { |
| 138 | /* Either end or error - must check */ |
| 139 | err = ly_err_first(ctx); |
| 140 | if (err->vecode == LYVE_SYNTAX_XML) { |
| 141 | /* Could just be EOF - check */ |
| 142 | /* TODO: Search in error message if EOF then break */ |
| 143 | lyd_insert_sibling(final, subtree, NULL); |
| 144 | break; |
| 145 | } else { |
| 146 | /* Other parsing error encountered */ |
| 147 | ret = LY_EINVAL; |
| 148 | goto cleanup; |
| 149 | } |
| 150 | } |
| 151 | |
| 152 | if (!final) { |
| 153 | /* If there was nothing inserted yet this subtree becomes the one to insert into */ |
| 154 | final = subtree; |
| 155 | } |
| 156 | |
| 157 | lyd_find_path(subtree, "/ietf-yang-library:yang-library", 0, &yanglib); |
| 158 | if (yanglib && !(yanglib->flags & LYD_DEFAULT)) { |
| 159 | /* Found and not created by flags */ |
| 160 | found_yanglib = 1; |
| 161 | lyd_insert_sibling(final, yanglib, NULL); |
| 162 | continue; |
| 163 | } |
| 164 | lyd_find_path(subtree, "/ietf-yang-schema-mount:mount-points", 0, &mount_point); |
| 165 | if (mount_point && !(mount_point->flags & LYD_DEFAULT)) { |
| 166 | /* Was found and not created by flags */ |
| 167 | found_mount_point = 1; |
| 168 | lyd_insert_sibling(final, mount_point, NULL); |
| 169 | continue; |
| 170 | } |
| 171 | } |
| 172 | |
| 173 | if (found_mount_point && found_yanglib) { |
| 174 | /* It is valid data and can be inserted into the parent */ |
| 175 | lyd_insert_child(parent, final); |
| 176 | } else { |
| 177 | /* It was not data for schema mount */ |
| 178 | lyd_free_tree(final); |
| 179 | ret = LY_ENOT; |
| 180 | } |
| 181 | |
| 182 | cleanup: |
| 183 | ly_log_options(old_log_opts); |
| 184 | return ret; |
| 185 | } |
| 186 | |
| 187 | /** |
| 188 | * @brief Plugin descriptions for the Yang Schema Mount extension. |
| 189 | * |
| 190 | * Note that external plugins are supposed to use: |
| 191 | * |
| 192 | * LYPLG_EXTENSIONS = { |
| 193 | */ |
| 194 | const struct lyplg_ext_record plugins_schema_mount[] = { |
| 195 | { |
| 196 | .module = "ietf-yang-schema-mount", |
| 197 | .revision = "2019-01-14", |
| 198 | .name = "mount-point", |
| 199 | |
| 200 | .plugin.id = "libyang 2 - Schema Mount, version 1", |
| 201 | .plugin.compile = &schema_mount_compile, |
| 202 | .plugin.parse = &schema_mount_parse, |
| 203 | .plugin.validate = NULL, |
| 204 | .plugin.sprinter = NULL, |
| 205 | .plugin.free = NULL |
| 206 | }, |
| 207 | {0} /* terminating zeroed item */ |
| 208 | }; |