From b3dcee96393a261984e64b5790ea1725b5d1f6ce Mon Sep 17 00:00:00 2001 From: Olof Hagsand Date: Fri, 20 Jan 2023 16:06:45 +0100 Subject: [PATCH] * YANG schema mount RFC 8528, Initial commit (work in progress) * Keep track of YANG unknowns with ys_cvec of EXTENSION * C-API: Init ys_cvec to NULL, added yang_cvec_add() and adjusted code to use it --- CHANGELOG.md | 7 +- apps/backend/backend_get.c | 13 + apps/cli/cli_show.c | 6 - apps/restconf/restconf_methods.c | 3 +- apps/snmp/snmp_lib.c | 3 +- include/clixon_custom.h | 6 + lib/clixon/clixon.h.in | 1 + lib/clixon/clixon_plugin.h | 6 +- lib/clixon/clixon_xml.h | 3 + lib/clixon/clixon_yang_module.h | 2 +- lib/clixon/clixon_yang_schema_mount.h | 55 ++++ lib/src/Makefile.in | 3 +- lib/src/clixon_datastore_write.c | 40 ++- lib/src/clixon_path.c | 6 +- lib/src/clixon_validate_minmax.c | 1 - lib/src/clixon_xml_bind.c | 2 +- lib/src/clixon_xml_default.c | 2 +- lib/src/clixon_yang.c | 110 +++++--- lib/src/clixon_yang_internal.h | 1 + lib/src/clixon_yang_module.c | 15 +- lib/src/clixon_yang_schema_mount.c | 346 ++++++++++++++++++++++++++ 21 files changed, 552 insertions(+), 79 deletions(-) create mode 100644 lib/clixon/clixon_yang_schema_mount.h create mode 100644 lib/src/clixon_yang_schema_mount.c diff --git a/CHANGELOG.md b/CHANGELOG.md index 20703d0b..9ef25ddc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,7 +43,12 @@ Expected: beginning of 2023 ### New features -* Netconf monitoring, part 2 +* YANG schema mount RFC 8528 (work in progress) + * Restrictions: + * only schema-ref=inline, not shared-schema + * Standards: RFC 8528 + * Enable `YANG_SCHEMA_MOUNT` +* Netconf monitoring RFC 6022 , part 2 * Datastores and sessions * Added clixon-specific transport identities: cli, snmp, netconf, restconf * Added source-host fro native restonf, but no other transports diff --git a/apps/backend/backend_get.c b/apps/backend/backend_get.c index 4f5ab14d..cd168714 100644 --- a/apps/backend/backend_get.c +++ b/apps/backend/backend_get.c @@ -297,6 +297,19 @@ get_client_statedata(clicon_handle h, if (ret == 0) goto fail; } +#ifdef YANG_SCHEMA_MOUNT + if ((ret = schema_mounts_state_get(h, yspec, xpath, nsc, xret, &xerr)) < 0) + goto done; + if (ret == 0){ + if (clixon_netconf_internal_error(xerr, " . Internal error, schema_mounts_state_get returned invalid XML", NULL) < 0) + goto done; + if (*xret) + xml_free(*xret); + *xret = xerr; + xerr = NULL; + goto fail; + } +#endif /* Use plugin state callbacks */ if ((ret = clixon_plugin_statedata_all(h, yspec, nsc, xpath, wdef, xret)) < 0) goto done; diff --git a/apps/cli/cli_show.c b/apps/cli/cli_show.c index e5a6d4c4..2d9ea3e8 100644 --- a/apps/cli/cli_show.c +++ b/apps/cli/cli_show.c @@ -266,12 +266,6 @@ expand_dbvar(void *h, /* Transform api-path to xpath for netconf */ if (api_path2xpath(api_path, yspec, &xpath, &nsc, NULL) < 0) goto done; - if (nsc != NULL){ - cvec_free(nsc); - nsc = NULL; - } - if (xml_nsctx_yang(y, &nsc) < 0) - goto done; if ((cbxpath = cbuf_new()) == NULL){ clicon_err(OE_UNIX, errno, "cbuf_new"); goto done; diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index 66ad38df..781af9e7 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -143,7 +143,8 @@ match_list_keys(yang_stmt *y, clicon_debug(1, "%s", __FUNCTION__); switch (yang_keyword_get(y)){ case Y_LIST: - cvk = yang_cvec_get(y); /* Use Y_LIST cache, see ys_populate_list() */ + if ((cvk = yang_cvec_get(y)) == NULL) /* Use Y_LIST cache, see ys_populate_list() */ + break; cvi = NULL; while ((cvi = cvec_each(cvk, cvi)) != NULL) { keyname = cv_string_get(cvi); diff --git a/apps/snmp/snmp_lib.c b/apps/snmp/snmp_lib.c index 8b1236cf..1a0fa25a 100644 --- a/apps/snmp/snmp_lib.c +++ b/apps/snmp/snmp_lib.c @@ -900,7 +900,8 @@ snmp_yang2xpath_cb(yang_stmt *ys, } switch (yang_keyword_get(ys)){ case Y_LIST: - cvk = yang_cvec_get(ys); /* Use Y_LIST cache, see ys_populate_list() */ + if ((cvk = yang_cvec_get(ys)) == NULL) /* Use Y_LIST cache, see ys_populate_list() */ + break; /* Iterate over individual keys */ assert(keyvec && cvec_len(cvk) == cvec_len(keyvec)); for (i=0; i #include #include +#include #include #include #include diff --git a/lib/clixon/clixon_plugin.h b/lib/clixon/clixon_plugin.h index 6583244d..6babbfd8 100644 --- a/lib/clixon/clixon_plugin.h +++ b/lib/clixon/clixon_plugin.h @@ -132,8 +132,8 @@ typedef int (plgdaemon_t)(clicon_handle); /* Plugin pre/post daemon */ typedef int (plgexit_t)(clicon_handle); /* Plugin exit */ -/* For yang extension handling. - * Called at parsing of yang module containing a statement of an extension. +/* For yang extension/unknown handling. + * Called at parsing of yang module containing an unknown statement of an extension. * A plugin may identify the extension by its name, and perform actions * on the yang statement, such as transforming the yang. * A callback is made for every statement, which means that several calls per @@ -294,7 +294,7 @@ struct clixon_plugin_api{ plginit2_t *ca_init; /* Clixon plugin Init (implicit) */ plgstart_t *ca_start; /* Plugin start */ plgexit_t *ca_exit; /* Plugin exit */ - plgextension_t *ca_extension; /* Yang extension handler */ + plgextension_t *ca_extension; /* Yang extension/unknown handler */ union { struct { /* cli-specific */ cli_prompthook_t *ci_prompt; /* Prompt hook */ diff --git a/lib/clixon/clixon_xml.h b/lib/clixon/clixon_xml.h index f08851ab..d70e2a02 100644 --- a/lib/clixon/clixon_xml.h +++ b/lib/clixon/clixon_xml.h @@ -165,6 +165,9 @@ typedef enum yang_bind yang_bind; typedef struct xml cxobj; /* struct defined in clicon_xml.c */ /*! Callback function type for xml_apply + * + * @param[in] x XML node + * @param[in] arg General-purpose argument * @retval -1 Error, aborted at first error encounter, return -1 to end user * @retval 0 OK, continue * @retval 1 Abort, dont continue with others, return 1 to end user diff --git a/lib/clixon/clixon_yang_module.h b/lib/clixon/clixon_yang_module.h index 3ea50f3a..121c4ab1 100644 --- a/lib/clixon/clixon_yang_module.h +++ b/lib/clixon/clixon_yang_module.h @@ -66,7 +66,7 @@ int modstate_diff_free(modstate_diff_t *); int yang_modules_init(clicon_handle h); char *yang_modules_revision(clicon_handle h); - +int yang_modules_state_build(clicon_handle h, yang_stmt *yspec, char *msid, int brief, cbuf *cb); int yang_modules_state_get(clicon_handle h, yang_stmt *yspec, char *xpath, cvec *nsc, int brief, cxobj **xret); int clixon_module_upgrade(clicon_handle h, cxobj *xt, modstate_diff_t *msd, cbuf *cb); diff --git a/lib/clixon/clixon_yang_schema_mount.h b/lib/clixon/clixon_yang_schema_mount.h new file mode 100644 index 00000000..412c5a58 --- /dev/null +++ b/lib/clixon/clixon_yang_schema_mount.h @@ -0,0 +1,55 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2023 Olof Hagsand + + This file is part of CLIXON. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Alternatively, the contents of this file may be used under the terms of + the GNU General Public License Version 3 or later (the "GPL"), + in which case the provisions of the GPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of the GPL, and not to allow others to + use your version of this file under the terms of Apache License version 2, + indicate your decision by deleting the provisions above and replace them with + the notice and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the Apache License version 2 or the GPL. + + ***** END LICENSE BLOCK ***** + + */ + +#ifndef _CLIXON_YANG_SCHEMA_MOUNT_H_ +#define _CLIXON_YANG_SCHEMA_MOUNT_H_ + +/* + * Constants + */ + +/* RFC 8528 YANG Schema Mount + */ +#define YANG_SCHEMA_MOUNT_NAMESPACE "urn:ietf:params:xml:ns:yang:ietf-yang-schema-mount" + +/* + * Prototypes + */ +int yang_schema_mount_point(yang_stmt *y); + +int schema_mounts_state_get(clicon_handle h, yang_stmt *yspec, char *xpath, cvec *nsc, cxobj **xret, cxobj **xerr); +int yang_schema_unknown(clicon_handle h, yang_stmt *yext, yang_stmt *ys); + +#endif /* _CLIXON_YANG_SCHEMA_MOUNT_H_ */ diff --git a/lib/src/Makefile.in b/lib/src/Makefile.in index 1d7103aa..edbbffaf 100644 --- a/lib/src/Makefile.in +++ b/lib/src/Makefile.in @@ -84,7 +84,8 @@ SRC = clixon_sig.c clixon_uid.c clixon_log.c clixon_err.c clixon_event.c \ clixon_xml_default.c clixon_xml_bind.c clixon_json.c clixon_proc.c \ clixon_yang.c clixon_yang_type.c clixon_yang_module.c clixon_netconf_monitoring.c \ clixon_yang_parse_lib.c clixon_yang_sub_parse.c \ - clixon_yang_cardinality.c clixon_xml_changelog.c clixon_xml_nsctx.c \ + clixon_yang_cardinality.c clixon_yang_schema_mount.c \ + clixon_xml_changelog.c clixon_xml_nsctx.c \ clixon_path.c clixon_validate.c clixon_validate_minmax.c \ clixon_hash.c clixon_options.c clixon_data.c clixon_plugin.c \ clixon_proto.c clixon_proto_client.c \ diff --git a/lib/src/clixon_datastore_write.c b/lib/src/clixon_datastore_write.c index a3ce6e93..1c1cb30c 100644 --- a/lib/src/clixon_datastore_write.c +++ b/lib/src/clixon_datastore_write.c @@ -49,7 +49,7 @@ #include #include #include -#include +#include #include /* cligen */ @@ -527,6 +527,7 @@ text_modify(clicon_handle h, } } x1name = xml_name(x1); + if (yang_keyword_get(y0) == Y_LEAF_LIST || yang_keyword_get(y0) == Y_LEAF){ /* This is a check that a leaf does not have sub-elements @@ -897,6 +898,7 @@ text_modify(clicon_handle h, while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) { x0c = x0vec[i++]; x1cname = xml_name(x1c); + yc = yang_find_datanode(y0, x1cname); if ((ret = text_modify(h, x0c, x0, x0t, x1c, x1t, yc, op, @@ -963,10 +965,8 @@ text_modify(clicon_handle h, /*! Modify a top-level base tree x0 with modification tree x1 * @param[in] h Clicon handle - * @param[in] x0 Base xml tree (can be NULL in add scenarios) - * @param[in] x0t Top level of existing tree, eg needed for NACM rules - * @param[in] x1 XML tree which modifies base - * @param[in] x1t Request root node (nacm needs this) + * @param[in] x0t Base xml tree (can be NULL in add scenarios) + * @param[in] x1t XML tree which modifies base * @param[in] yspec Top-level yang spec (if y is NULL) * @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc * @param[in] username User name of requestor for nacm @@ -980,9 +980,7 @@ text_modify(clicon_handle h, */ static int text_modify_top(clicon_handle h, - cxobj *x0, cxobj *x0t, - cxobj *x1, cxobj *x1t, yang_stmt *yspec, enum operation_type op, @@ -1002,7 +1000,7 @@ text_modify_top(clicon_handle h, char *createstr = NULL; /* Check for operations embedded in tree according to netconf */ - if ((ret = attr_ns_value(x1, + if ((ret = attr_ns_value(x1t, "operation", NETCONF_BASE_NAMESPACE, cbret, &opstr)) < 0) goto done; @@ -1011,25 +1009,25 @@ text_modify_top(clicon_handle h, if (opstr != NULL) if (xml_operation(opstr, &op) < 0) goto done; - if ((ret = attr_ns_value(x1, "objectcreate", NULL, cbret, &createstr)) < 0) + if ((ret = attr_ns_value(x1t, "objectcreate", NULL, cbret, &createstr)) < 0) goto done; if (ret == 0) goto fail; - /* Special case if incoming x1 is empty, top-level only */ - if (xml_child_nr_type(x1, CX_ELMNT) == 0){ - if (xml_child_nr_type(x0, CX_ELMNT)){ /* base tree not empty */ + /* Special case if incoming x1t is empty, top-level only */ + if (xml_child_nr_type(x1t, CX_ELMNT) == 0){ + if (xml_child_nr_type(x0t, CX_ELMNT)){ /* base tree not empty */ switch(op){ case OP_DELETE: case OP_REMOVE: case OP_REPLACE: if (!permit && xnacm){ - if ((ret = nacm_datanode_write(h, x0, x0t, NACM_DELETE, username, xnacm, cbret)) < 0) + if ((ret = nacm_datanode_write(h, x0t, x0t, NACM_DELETE, username, xnacm, cbret)) < 0) goto done; if (ret == 0) goto fail; permit = 1; } - while ((x0c = xml_child_i(x0, 0)) != 0) + while ((x0c = xml_child_i(x0t, 0)) != 0) if (xml_purge(x0c) < 0) goto done; break; @@ -1057,25 +1055,25 @@ text_modify_top(clicon_handle h, /* Special case top-level replace */ else if (op == OP_REPLACE || op == OP_DELETE){ if (createstr != NULL){ - if (xml_child_nr_type(x0, CX_ELMNT)) /* base tree not empty */ + if (xml_child_nr_type(x0t, CX_ELMNT)) /* base tree not empty */ clicon_data_set(h, "objectexisted", "true"); else clicon_data_set(h, "objectexisted", "false"); } if (!permit && xnacm){ - if ((ret = nacm_datanode_write(h, x1, x1t, NACM_UPDATE, username, xnacm, cbret)) < 0) + if ((ret = nacm_datanode_write(h, x1t, x1t, NACM_UPDATE, username, xnacm, cbret)) < 0) goto done; if (ret == 0) goto fail; permit = 1; } - while ((x0c = xml_child_i(x0, 0)) != 0) + while ((x0c = xml_child_i(x0t, 0)) != 0) if (xml_purge(x0c) < 0) goto done; } /* Loop through children of the modification tree */ x1c = NULL; - while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) { + while ((x1c = xml_child_each(x1t, x1c, CX_ELMNT)) != NULL) { x1cname = xml_name(x1c); /* Get yang spec of the child */ yc = NULL; @@ -1101,7 +1099,7 @@ text_modify_top(clicon_handle h, } } /* See if there is a corresponding node in the base tree */ - if (match_base_child(x0, x1c, yc, &x0c) < 0) + if (match_base_child(x0t, x1c, yc, &x0c) < 0) goto done; if (x0c && (yc != xml_spec(x0c))){ /* There is a match but is should be replaced (choice)*/ @@ -1109,7 +1107,7 @@ text_modify_top(clicon_handle h, goto done; x0c = NULL; } - if ((ret = text_modify(h, x0c, x0, x0t, x1c, x1t, + if ((ret = text_modify(h, x0c, x0t, x0t, x1c, x1t, yc, op, username, xnacm, permit, cbret)) < 0) goto done; @@ -1228,7 +1226,7 @@ xmldb_put(clicon_handle h, * Modify base tree x with modification x1. This is where the * new tree is made. */ - if ((ret = text_modify_top(h, x0, x0, x1, x1, yspec, op, username, xnacm, permit, cbret)) < 0) + if ((ret = text_modify_top(h, x0, x1, yspec, op, username, xnacm, permit, cbret)) < 0) goto done; /* If xml return - ie netconf error xml tree, then stop and return OK */ if (ret == 0){ diff --git a/lib/src/clixon_path.c b/lib/src/clixon_path.c index 22ecbe6a..b2b28cb8 100644 --- a/lib/src/clixon_path.c +++ b/lib/src/clixon_path.c @@ -1344,6 +1344,7 @@ api_path_resolve(clixon_path *cplist, int i; cg_var *cva; cg_var *cvy; + cvec *cvk; if ((cp = cplist) != NULL){ do { @@ -1372,7 +1373,8 @@ api_path_resolve(clixon_path *cplist, } } else if (yang_keyword_get(yc) == Y_LIST){ - if (cvec_len(cp->cp_cvk) > cvec_len(yang_cvec_get(yc))){ + cvk = yang_cvec_get(yc); + if (cvec_len(cp->cp_cvk) > cvec_len(cvk)){ clicon_err(OE_YANG, ENOENT, "Number of keys in key-value list does not match Yang list"); goto fail; } @@ -1384,7 +1386,7 @@ api_path_resolve(clixon_path *cplist, cv_name_get(cva)); goto fail; } - cvy = cvec_i(yang_cvec_get(yc), i++); + cvy = cvec_i(cvk, i++); cv_name_set(cva, cv_string_get(cvy)); } } diff --git a/lib/src/clixon_validate_minmax.c b/lib/src/clixon_validate_minmax.c index 3c52779e..99c2c8c3 100644 --- a/lib/src/clixon_validate_minmax.c +++ b/lib/src/clixon_validate_minmax.c @@ -227,7 +227,6 @@ check_unique_list_direct(cxobj *x, char *str; cvec *cvk; - cvk = yang_cvec_get(yu); /* If list and is sorted by system, then it is assumed elements are in key-order which is optimized * Other cases are "unique" constraint or list sorted by user which is quadratic in complexity * This second case COULD be optimized if binary insert is made on the vec vector. diff --git a/lib/src/clixon_xml_bind.c b/lib/src/clixon_xml_bind.c index ef4fb7c8..7c4ad3f5 100644 --- a/lib/src/clixon_xml_bind.c +++ b/lib/src/clixon_xml_bind.c @@ -137,8 +137,8 @@ strip_body_objects(cxobj *xt) * * @param[in] xt XML tree node * @param[out] xerr Reason for failure, or NULL - * @retval 1 OK Yang assignment made * @retval 2 OK Yang assignment not made because yang parent is anyxml or anydata + * @retval 1 OK Yang assignment made * @retval 0 Yang assigment not made and xerr set * @retval -1 Error * @note retval = 2 is special diff --git a/lib/src/clixon_xml_default.c b/lib/src/clixon_xml_default.c index 089d9ed3..ed4c3884 100644 --- a/lib/src/clixon_xml_default.c +++ b/lib/src/clixon_xml_default.c @@ -178,7 +178,7 @@ xml_default_choice(yang_stmt *yc, yang_stmt *yca = NULL; yang_stmt *ydef; - clicon_debug(1, "%s", __FUNCTION__); + clicon_debug(2, "%s", __FUNCTION__); /* 1. Is there a default case and no child under this choice? */ x = NULL; diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index 8b4e7800..37f57996 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -84,6 +84,7 @@ #include "clixon_yang_parse_lib.h" #include "clixon_yang_cardinality.h" #include "clixon_yang_type.h" +#include "clixon_yang_schema_mount.h" #include "clixon_yang_internal.h" /* internal included by this file only, not API*/ #ifdef XML_EXPLICIT_INDEX @@ -274,6 +275,7 @@ yang_cv_set(yang_stmt *ys, /*! Get yang statement CLIgen variable vector * @param[in] ys Yang statement node + * @note To add entries, use yang_cvec_add(), since this function may return NULL */ cvec* yang_cvec_get(yang_stmt *ys) @@ -297,6 +299,41 @@ yang_cvec_set(yang_stmt *ys, return 0; } +/*! Add new value to yang cvec, create if not exist + * + * @param[in] ys Yang statement + * @param[in] type Append a new cv to the vector with this type + * @param[in] name Name of variable + * @retval cv The new cligen variable + * @retval NULL Error + */ +static cg_var * +yang_cvec_add(yang_stmt *ys, + enum cv_type type, + char *name) + +{ + cg_var *cv; + cvec *cvv; + + if ((cvv = yang_cvec_get(ys)) == NULL){ + if ((cvv = cvec_new(0)) == NULL){ + clicon_err(OE_YANG, errno, "cvec_new"); + return NULL; + } + yang_cvec_set(ys, cvv); + } + if ((cv = cvec_add(cvv, type)) == NULL){ + clicon_err(OE_YANG, errno, "cvec_add"); + return NULL; + } + if (cv_name_set(cv, name) == NULL){ + clicon_err(OE_YANG, errno, "cv_name_set(%s)", name); + return NULL; + } + return cv; +} + /*! Get yang stmt flags, used for internal algorithms * @param[in] ys Yang statement * @param[in] flag Flags value(s) to get, see YANG_FLAG_* @@ -594,7 +631,6 @@ yang_stmt * ys_new(enum rfc_6020 keyw) { yang_stmt *ys; - cvec *cvv; if ((ys = malloc(sizeof(*ys))) == NULL){ clicon_err(OE_YANG, errno, "malloc"); @@ -602,13 +638,6 @@ ys_new(enum rfc_6020 keyw) } memset(ys, 0, sizeof(*ys)); ys->ys_keyword = keyw; - /* The cvec contains stmt-specific variables. Only few stmts need variables so the - cvec could be lazily created to save some heap and cycles. */ - if ((cvv = cvec_new(0)) == NULL){ - clicon_err(OE_YANG, errno, "cvec_new"); - return NULL; - } - yang_cvec_set(ys, cvv); _stats_yang_nr++; return ys; } @@ -1082,13 +1111,17 @@ yang_match(yang_stmt *yn, return match; } + /*! Find child data node with matching argument (container, leaf, list, leaf-list) * * @param[in] yn Yang node, current context node. - * @param[in] argument if NULL, match any(first) argument. XXX is that really a case? + * @param[in] argument Argument that child should match with + * @retval ymatch Matching child + * @retval NULL No match or error * * @see yang_find Looks for any node * @note May deviate from RFC since it explores choice/case not just return it. + * XXX: differentiate between not found and error */ yang_stmt * yang_find_datanode(yang_stmt *yn, @@ -1099,7 +1132,16 @@ yang_find_datanode(yang_stmt *yn, yang_stmt *yspec; yang_stmt *ysmatch = NULL; char *name; - +#ifdef YANG_SCHEMA_MOUNT + int ret; + + /* Sanity-check mount-point extension */ + if ((ret = yang_schema_mount_point(yn)) < 0) + goto done; + if (ret == 1){ + ; // NYI + } +#endif ys = NULL; while ((ys = yn_each(yn, ys)) != NULL){ if (yang_keyword_get(ys) == Y_CHOICE){ /* Look for its children */ @@ -1109,14 +1151,11 @@ yang_find_datanode(yang_stmt *yn, ysmatch = yang_find_datanode(yc, argument); else if (yang_datanode(yc)){ - if (argument == NULL) + if (yc->ys_argument && strcmp(argument, yc->ys_argument) == 0) ysmatch = yc; - else - if (yc->ys_argument && strcmp(argument, yc->ys_argument) == 0) - ysmatch = yc; } if (ysmatch) - goto match; // maybe break? + goto done; // maybe break? } } /* Y_CHOICE */ else if (yang_keyword_get(ys) == Y_INPUT || @@ -1131,7 +1170,7 @@ yang_find_datanode(yang_stmt *yn, if (ys->ys_argument && strcmp(argument, ys->ys_argument) == 0) ysmatch = ys; if (ysmatch) - goto match; // maybe break? + goto done; // maybe break? } } /* Special case: if not match and yang node is module or submodule, extend @@ -1150,7 +1189,7 @@ yang_find_datanode(yang_stmt *yn, } } } - match: + done: return ysmatch; } @@ -2265,14 +2304,12 @@ bound_add(yang_stmt *ys, char *reason = NULL; int ret = 1; - if ((cv = cvec_add(ys->ys_cvec, cvtype)) == NULL){ - clicon_err(OE_YANG, errno, "cvec_add"); + if (ys == NULL){ + clicon_err(OE_YANG, EINVAL, "ys is NULL"); goto done; } - if (cv_name_set(cv, name) == NULL){ - clicon_err(OE_YANG, errno, "cv_name_set(%s)", name); + if ((cv = yang_cvec_add(ys, cvtype, name)) == NULL) goto done; - } if (cvtype == CGV_DEC64) cv_dec64_n_set(cv, fraction_digits); if (strcmp(val, "min") == 0) @@ -2480,7 +2517,6 @@ ys_populate_identity(clicon_handle h, int retval = -1; yang_stmt *yc = NULL; yang_stmt *ybaseid; - cg_var *cv; char *baseid; char *prefix = NULL; char *id = NULL; @@ -2526,17 +2562,10 @@ ys_populate_identity(clicon_handle h, if (cvec_find(idrefvec, idref) != NULL) continue; /* Add derived id to ybaseid */ - if ((cv = cv_new(CGV_STRING)) == NULL){ + if (yang_cvec_add(ybaseid, CGV_STRING, idref) == NULL){ clicon_err(OE_UNIX, errno, "cv_new"); goto done; } - /* add prefix */ - cv_name_set(cv, idref); - cvec_append_var(idrefvec, cv); /* cv copied */ - if (cv){ - cv_free(cv); - cv = NULL; - } /* Transitive to the root */ if (ys_populate_identity(h, ybaseid, idref) < 0) goto done; @@ -2662,11 +2691,20 @@ ys_populate_unique(clicon_handle h, } /*! Populate unknown node with extension + * * @param[in] h Clicon handle * @param[in] ys The yang statement (unknown) to populate. + * @retval 0 OK + * @retval -1 Error * RFC 7950 Sec 7.19: * If no "argument" statement is present, the keyword expects no argument when * it is used. + * A pointer to the unknwon node is stored in the cvec of the extension: + * 1 n + * yang-extension ext1 (cvec) ---> unknown of ext1 + * (yext) (ys) + * + * @note this breaks if yangs are freed * Note there is some complexity in different yang modules with unknown-statements. * y0) The location of the extension definition. E.g. extension autocli-op * y1) The location of the unknown-statement (ys). This is for example: cl:autocli-op hide. @@ -2717,6 +2755,10 @@ ys_populate_unknown(clicon_handle h, clicon_debug(1, "plugin_extension() failed"); return -1; } +#endif +#ifdef YANG_SCHEMA_MOUNT + if (yang_schema_unknown(h, yext, ys) < 0) + goto done; #endif /* Make extension callbacks that may alter yang structure * Note: this may be a "layering" violation: assuming plugins are loaded @@ -2724,6 +2766,12 @@ ys_populate_unknown(clicon_handle h, */ if (clixon_plugin_extension_all(h, yext, ys) < 0) goto done; +#if 1 + /* Add unknown to vector in extension */ + if ((cv = yang_cvec_add(yext, CGV_VOID, yang_argument_get(ys))) == NULL) + goto done; + cv_void_set(cv, ys); +#endif retval = 0; done: if (prefix) diff --git a/lib/src/clixon_yang_internal.h b/lib/src/clixon_yang_internal.h index 3654ff83..a240df2f 100644 --- a/lib/src/clixon_yang_internal.h +++ b/lib/src/clixon_yang_internal.h @@ -95,6 +95,7 @@ struct yang_stmt{ Y_TYPE & identity: store all derived types as : list Y_UNIQUE: vector of descendant schema node ids + Y_EXTENSION: vector of instantiated UNKNOWNSo */ yang_type_cache *ys_typecache; /* If ys_keyword==Y_TYPE, cache all typedef data except unions */ char *ys_when_xpath; /* Special conditional for a "when"-associated augment/uses xpath */ diff --git a/lib/src/clixon_yang_module.c b/lib/src/clixon_yang_module.c index db2d0981..b55e0b3b 100644 --- a/lib/src/clixon_yang_module.c +++ b/lib/src/clixon_yang_module.c @@ -192,12 +192,12 @@ yang_modules_revision(clicon_handle h) * If also CLICON_MODULE_LIBRARY_RFC7895 is set, module-state is built according to RFC7895 instead * @see RFC8525 */ -static int -yms_build(clicon_handle h, - yang_stmt *yspec, - char *msid, - int brief, - cbuf *cb) +int +yang_modules_state_build(clicon_handle h, + yang_stmt *yspec, + char *msid, + int brief, + cbuf *cb) { int retval = -1; yang_stmt *ylib = NULL; /* ietf-yang-library */ @@ -361,7 +361,7 @@ yang_modules_state_get(clicon_handle h, goto done; } /* Build a cb string: ... */ - if (yms_build(h, yspec, msid, brief, cb) < 0) + if (yang_modules_state_build(h, yspec, msid, brief, cb) < 0) goto done; /* Parse cb, x is on the form: ... * Note, list is not sorted since it is state (should not be) @@ -391,7 +391,6 @@ yang_modules_state_get(clicon_handle h, /* Remove everything that is not marked */ if (xml_tree_prune_flagged_sub(x, XML_FLAG_MARK, 1, NULL) < 0) goto done; - if ((ret = netconf_trymerge(x, yspec, xret)) < 0) goto done; if (ret == 0) diff --git a/lib/src/clixon_yang_schema_mount.c b/lib/src/clixon_yang_schema_mount.c new file mode 100644 index 00000000..e79f2ee8 --- /dev/null +++ b/lib/src/clixon_yang_schema_mount.c @@ -0,0 +1,346 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2023 Olof Hagsand + + This file is part of CLIXON. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Alternatively, the contents of this file may be used under the terms of + the GNU General Public License Version 3 or later (the "GPL"), + in which case the provisions of the GPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of the GPL, and not to allow others to + use your version of this file under the terms of Apache License version 2, + indicate your decision by deleting the provisions above and replace them with + the notice and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the Apache License version 2 or the GPL. + + ***** END LICENSE BLOCK ***** + + * RFC 8525 Yang schema mount support + */ + +#ifdef HAVE_CONFIG_H +#include "clixon_config.h" /* generated by config & autoconf */ +#endif + +#include +#include +#include +#include +#include + +/* cligen */ +#include + +/* clixon */ +#include "clixon_queue.h" +#include "clixon_hash.h" +#include "clixon_handle.h" +#include "clixon_err.h" +#include "clixon_log.h" +#include "clixon_yang.h" +#include "clixon_xml.h" +#include "clixon_xml_io.h" +#include "clixon_yang_module.h" +#include "clixon_netconf_lib.h" + +#include "clixon_yang_schema_mount.h" + +/*! Check if y is a RFC 8525 YANG schema mount + * + * Check if: + * - y is CONTAINER or LIST, AND + * - y has YANG schema mount "mount-point" as child element, AND + * - the extension label matches y (see note below) + * If so, then return 1 + * @param[in] y Yang statement + * @retval 1 Yes, y is a RFC 8525 YANG mount-point + * @retval 0 No, y is not + * @retval -1 Error + * @note That this may be a restriction on the usage of "label". The RFC is somewhat unclear. + */ +int +yang_schema_mount_point(yang_stmt *y) +{ + int retval = -1; + enum rfc_6020 keyw; + int exist = 0; + char *value = NULL; + + if (y == NULL){ + clicon_err(OE_YANG, EINVAL, "y is NULL"); + goto done; + } + keyw = yang_keyword_get(y); + if (keyw != Y_CONTAINER && keyw != Y_LIST) + goto fail; + if (yang_extension_value(y, "mount-point", YANG_SCHEMA_MOUNT_NAMESPACE, &exist, &value) < 0) + goto done; + if (exist == 0) + goto fail; + if (value == NULL) + goto fail; + retval = 1; + done: + return retval; + fail: + retval = 0; + goto done; +} + +/*! Find schema mounts - callback function for xml_apply + * + * @param[in] x XML node + * @param[in] arg cvec, if match add node + * @retval -1 Error, aborted at first error encounter, return -1 to end user + * @retval 0 OK, continue + * @retval 1 Abort, dont continue with others, return 1 to end user + * @retval 2 Locally abort this subtree, continue with others + */ +static int +find_schema_mounts(cxobj *x, + void *arg) +{ + int ret; + yang_stmt *y; + cvec *cvv = (cvec *)arg; + cg_var *cv; + + if ((y = xml_spec(x)) == NULL) + return 2; + if (yang_config(y) == 0) + return 2; + if ((ret = yang_schema_mount_point(y)) < 0) + return -1; + if (ret == 0) + return 0; + if ((cv = cvec_add(cvv, CGV_VOID)) == NULL){ + clicon_err(OE_UNIX, errno, "cvec_add"); + return -1; + } + cv_void_set(cv, x); + return 0; +} + +/*! Find mount-points and return yang-library state + * + * Brute force: traverse whole XML, match all x that have ymount as yspec + * Add yang-library state for all x + * @param[in] h Clicon handle + * @param[in] yspec Yang spec + * @param[in] xpath XML Xpath + * @param[in] nsc XML Namespace context for xpath + * @param[in,out] xret Existing XML tree, merge x into this + * @param[out] xerr XML error tree, if retval = 0 + * @retval 1 OK + * @retval 0 Validation failed, error in xret + * @retval -1 Error (fatal) + * + * RFC 8528 Section 3.4: + * A schema for a mount point contained in a mounted module can be + * specified by implementing the "ietf-yang-library" and + * "ietf-yang-schema-mount" modules in the mounted schema and specifying + * the schemas in exactly the same way as the top-level schema. + * Alt: see snmp_yang2xml to get instances instead of brute force traverse of whole tree + */ +static int +schema_mounts_yang_library(clicon_handle h, + yang_stmt *yspec, + char *xpath, + cvec *nsc, + cxobj **xret, + cxobj **xerr) +{ + int retval = -1; + cvec *cvv = NULL; + cg_var *cv; + cxobj *xmp; /* xml mount-point */ + cxobj *xylib = NULL; /* xml yang-lib */ + cbuf *cb = NULL; + char *msid = "mount-point"; /* modules-set-id dummy */ + + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, 0, "clicon buffer"); + goto done; + } + if ((cvv = cvec_new(0)) == NULL){ + clicon_err(OE_UNIX, errno, "cvec_new"); + goto done; + } + if (xml_apply(*xret, CX_ELMNT, find_schema_mounts, cvv) < 0) + goto done; + cv = NULL; + while ((cv = cvec_each(cvv, cv)) != NULL) { + xmp = cv_void_get(cv); + /* addsub here */ + cbuf_reset(cb); + // XXX change yspec to mount-point + /* Build a cb string: ... */ + if (yang_modules_state_build(h, yspec, msid, 0, cb) < 0) + goto done; + /* Parse cb, x is on the form: ... + * Note, list is not sorted since it is state (should not be) + */ + if (clixon_xml_parse_string(cbuf_get(cb), YB_MODULE, yspec, &xylib, NULL) < 0){ + if (xret && netconf_operation_failed_xml(xret, "protocol", clicon_err_reason)< 0) + goto done; + goto fail; + } + if (xml_rootchild(xylib, 0, &xylib) < 0) + goto done; + if (xml_addsub(xmp, xylib) < 0) + goto done; + xylib = NULL; + } + retval = 1; + done: + if (cvv) + cvec_free(cvv); + if (cb) + cbuf_free(cb); + return retval; + fail: + retval = 0; + goto done; +} + +/*! Get modules state according to RFC 8528 + * + * @param[in] h Clicon handle + * @param[in] yspec Yang spec + * @param[in] xpath XML Xpath + * @param[in] nsc XML Namespace context for xpath + * @param[in,out] xret Existing XML tree, merge x into this + * @param[out] xerr XML error tree, if retval = 0 + * @retval 1 OK + * @retval 0 Validation failed, error in xret + * @retval -1 Error (fatal) + * @note Only "inline" specification of mounted schema supported, not "shared schema" + */ +int +schema_mounts_state_get(clicon_handle h, + yang_stmt *yspec, + char *xpath, + cvec *nsc, + cxobj **xret, + cxobj **xerr) +{ + int retval = -1; + cbuf *cb = NULL; + int ret; + yang_stmt *yext; + yang_stmt *ymount; + yang_stmt *ymodext; + yang_stmt *ymod; + cg_var *cv; + cg_var *cv1; + char *label; + cvec *cvv; + cxobj *x1 = NULL; + + if ((ymodext = yang_find(yspec, Y_MODULE, "ietf-yang-schema-mount")) == NULL || + (yext = yang_find(ymodext, Y_EXTENSION, "mount-point")) == NULL){ + goto ok; + // clicon_err(OE_YANG, 0, "yang schema mount-point extension not found"); + // goto done; + } + if ((cvv = yang_cvec_get(yext)) != NULL){ + if ((cb = cbuf_new()) ==NULL){ + clicon_err(OE_XML, errno, "cbuf_new"); + goto done; + } + cprintf(cb, "", YANG_SCHEMA_MOUNT_NAMESPACE); // XXX only if hit + cv = NULL; + while ((cv = cvec_each(cvv, cv)) != NULL){ + ymount = (yang_stmt*)cv_void_get(cv); + ymod = ys_module(ymount); + if ((cv1 = yang_cv_get(ymount)) == NULL){ + clicon_err(OE_YANG, 0, "mount-point extension must have label"); + goto done; + } + label = cv_string_get(cv1); + cprintf(cb, ""); + cprintf(cb, "%s", yang_argument_get(ymod)); + cprintf(cb, "", label); + cprintf(cb, ""); + cprintf(cb, ""); + } + cprintf(cb, ""); + if ((ret = clixon_xml_parse_string(cbuf_get(cb), YB_MODULE, yspec, &x1, xerr)) < 0) + goto done; + if (ret == 0) + goto fail; + if ((ret = netconf_trymerge(x1, yspec, xret)) < 0) + goto done; + if (ret == 0) + goto fail; + } + /* Find mount-points and return yang-library state */ + if (0 && schema_mounts_yang_library(h, yspec, xpath, nsc, xret, xerr) < 0) + goto done; + ok: + retval = 1; + done: + if (x1) + xml_free(x1); + if (cb) + cbuf_free(cb); + return retval; + fail: + retval = 0; + goto done; +} + +/*! Callback for yang schema mount-point extension + * + * @param[in] h Clixon handle + * @param[in] yext Yang node of extension + * @param[in] ys Yang node of (unknown) statement belonging to extension + * @retval 0 OK + * @retval -1 Error + // XXX his may not even be necessary + */ +int +yang_schema_unknown(clicon_handle h, + yang_stmt *yext, + yang_stmt *ys) +{ + int retval = -1; + char *extname; + char *modname; + yang_stmt *ymod; + cg_var *cv; + char *label; + + ymod = ys_module(yext); + modname = yang_argument_get(ymod); + extname = yang_argument_get(yext); + if (strcmp(modname, "ietf-yang-schema-mount") != 0 || strcmp(extname, "mount-point") != 0) + goto ok; + if ((cv = yang_cv_get(ys)) == NULL){ + clicon_err(OE_YANG, 0, "mount-point extension must have label"); + goto done; + } + label = cv_string_get(cv); + clicon_debug(1, "%s Enabled extension:%s:%s label:%s", __FUNCTION__, modname, extname, label); + // XXX his may not even be necessary + ok: + retval = 0; + done: + return retval; +}