886 lines
28 KiB
C
886 lines
28 KiB
C
/*
|
|
*
|
|
***** BEGIN LICENSE BLOCK *****
|
|
|
|
Copyright (C) 2009-2019 Olof Hagsand
|
|
Copyright (C) 2020-2022 Olof Hagsand and Rubicon Communications, LLC(Netgate)
|
|
|
|
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 *****
|
|
|
|
* Yang module and feature handling
|
|
* @see https://tools.ietf.org/html/rfc7895
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "clixon_config.h" /* generated by config & autoconf */
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <errno.h>
|
|
#include <limits.h>
|
|
#include <ctype.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <arpa/inet.h>
|
|
#include <regex.h>
|
|
#include <dirent.h>
|
|
#include <sys/types.h>
|
|
#include <fcntl.h>
|
|
#include <syslog.h>
|
|
#include <sys/stat.h>
|
|
#include <netinet/in.h>
|
|
#include <sys/param.h>
|
|
|
|
/* cligen */
|
|
#include <cligen/cligen.h>
|
|
|
|
/* clicon */
|
|
#include "clixon_log.h"
|
|
#include "clixon_err.h"
|
|
#include "clixon_string.h"
|
|
#include "clixon_queue.h"
|
|
#include "clixon_hash.h"
|
|
#include "clixon_handle.h"
|
|
#include "clixon_file.h"
|
|
#include "clixon_yang.h"
|
|
#include "clixon_xml.h"
|
|
#include "clixon_xml_io.h"
|
|
#include "clixon_xml_nsctx.h"
|
|
#include "clixon_xpath_ctx.h"
|
|
#include "clixon_xpath.h"
|
|
#include "clixon_options.h"
|
|
#include "clixon_data.h"
|
|
#include "clixon_yang_module.h"
|
|
#include "clixon_plugin.h"
|
|
#include "clixon_netconf_lib.h"
|
|
#include "clixon_xml_map.h"
|
|
#include "clixon_yang_parse_lib.h"
|
|
|
|
/*! Force add ietf-yang-library@2019-01-04 on all mount-points
|
|
* This is a limitation of othe current implementation
|
|
*/
|
|
#define YANG_SCHEMA_MOUNT_YANG_LIB_FORCE
|
|
|
|
/*! Create modstate structure
|
|
*
|
|
* @retval md modstate struct
|
|
* @retval NULL Error
|
|
* @see modstate_diff_free
|
|
*/
|
|
modstate_diff_t *
|
|
modstate_diff_new(void)
|
|
{
|
|
modstate_diff_t *md;
|
|
|
|
if ((md = malloc(sizeof(modstate_diff_t))) == NULL){
|
|
clicon_err(OE_UNIX, errno, "malloc");
|
|
return NULL;
|
|
}
|
|
memset(md, 0, sizeof(modstate_diff_t));
|
|
return md;
|
|
}
|
|
|
|
/*! Free modstate structure
|
|
*
|
|
* @param[in] md Modstate struct
|
|
* @retval 0 OK
|
|
* @see modstate_diff_new
|
|
*/
|
|
int
|
|
modstate_diff_free(modstate_diff_t *md)
|
|
{
|
|
if (md == NULL)
|
|
return 0;
|
|
if (md->md_content_id)
|
|
free(md->md_content_id);
|
|
if (md->md_diff)
|
|
xml_free(md->md_diff);
|
|
free(md);
|
|
return 0;
|
|
}
|
|
|
|
/*! Init the Yang module library
|
|
*
|
|
* Load RFC7895 yang spec, module-set-id, etc.
|
|
* @param[in] h Clicon handle
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
* @see netconf_module_load
|
|
*/
|
|
int
|
|
yang_modules_init(clicon_handle h)
|
|
{
|
|
int retval = -1;
|
|
yang_stmt *yspec;
|
|
|
|
yspec = clicon_dbspec_yang(h);
|
|
if (!clicon_option_bool(h, "CLICON_YANG_LIBRARY"))
|
|
goto ok;
|
|
/* Ensure module-set-id is set */
|
|
if (!clicon_option_exists(h, "CLICON_MODULE_SET_ID")){
|
|
clicon_err(OE_CFG, ENOENT, "CLICON_MODULE_SET_ID must be defined when CLICON_YANG_LIBRARY is enabled");
|
|
goto done;
|
|
}
|
|
/* Ensure revision exists is set */
|
|
if (yang_spec_parse_module(h, "ietf-yang-library", NULL, yspec)< 0)
|
|
goto done;
|
|
/* Find revision */
|
|
if (yang_modules_revision(h) == NULL){
|
|
clicon_err(OE_CFG, ENOENT, "Yang client library yang spec has no revision");
|
|
goto done;
|
|
}
|
|
ok:
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Return RFC7895 revision (if parsed)
|
|
* @param[in] h Clicon handle
|
|
* @retval revision String (dont free)
|
|
* @retval NULL Error: RFC7895 not loaded or revision not found
|
|
*/
|
|
char *
|
|
yang_modules_revision(clicon_handle h)
|
|
{
|
|
yang_stmt *yspec;
|
|
yang_stmt *ymod;
|
|
yang_stmt *yrev;
|
|
char *revision = NULL;
|
|
|
|
yspec = clicon_dbspec_yang(h);
|
|
if ((ymod = yang_find(yspec, Y_MODULE, "ietf-yang-library")) != NULL ||
|
|
(ymod = yang_find(yspec, Y_SUBMODULE, "ietf-yang-library")) != NULL){
|
|
if ((yrev = yang_find(ymod, Y_REVISION, NULL)) != NULL){
|
|
revision = yang_argument_get(yrev);
|
|
}
|
|
}
|
|
return revision;
|
|
}
|
|
|
|
/*! Actually build the yang modules state XML tree according to RFC8525
|
|
*
|
|
* @param[in] h Clixon handle
|
|
* @param[in] yspec
|
|
* @param[in] msid
|
|
* @param[in] brief
|
|
* @param[out] cb
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
* This assumes CLICON_YANG_LIBRARY is enabled
|
|
* If also CLICON_MODULE_LIBRARY_RFC7895 is set, module-state is built according to RFC7895 instead
|
|
* @see RFC8525
|
|
*/
|
|
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 */
|
|
char *module = "ietf-yang-library";
|
|
yang_stmt *ys;
|
|
yang_stmt *yc;
|
|
yang_stmt *ymod; /* generic module */
|
|
yang_stmt *yns = NULL; /* namespace */
|
|
yang_stmt *yinc;
|
|
yang_stmt *ysub;
|
|
char *name;
|
|
|
|
/* In case of several mountpoints, this is always the top-level */
|
|
if ((ylib = yang_find(yspec, Y_MODULE, module)) == NULL
|
|
/* && (ylib = yang_find(yspec0, Y_SUBMODULE, module)) == NULL */
|
|
){
|
|
clicon_err(OE_YANG, 0, "%s not found", module);
|
|
goto done;
|
|
}
|
|
if ((yns = yang_find(ylib, Y_NAMESPACE, NULL)) == NULL){
|
|
clicon_err(OE_YANG, 0, "%s yang namespace not found", module);
|
|
goto done;
|
|
}
|
|
if (clicon_option_bool(h, "CLICON_MODULE_LIBRARY_RFC7895")){
|
|
cprintf(cb,"<modules-state xmlns=\"%s\">", yang_argument_get(yns));
|
|
cprintf(cb,"<module-set-id>%s</module-set-id>", msid);
|
|
}
|
|
else { /* RFC 8525 */
|
|
cprintf(cb,"<yang-library xmlns=\"%s\">", yang_argument_get(yns));
|
|
cprintf(cb,"<content-id>%s</content-id>", msid);
|
|
cprintf(cb,"<module-set><name>default</name>");
|
|
}
|
|
ymod = NULL;
|
|
while ((ymod = yn_each(yspec, ymod)) != NULL) {
|
|
if (yang_keyword_get(ymod) != Y_MODULE)
|
|
continue;
|
|
cprintf(cb,"<module>");
|
|
cprintf(cb,"<name>%s</name>", yang_argument_get(ymod));
|
|
if ((ys = yang_find(ymod, Y_REVISION, NULL)) != NULL)
|
|
cprintf(cb,"<revision>%s</revision>", yang_argument_get(ys));
|
|
else{
|
|
/* RFC7895 1 If no (such) revision statement exists, the module's or
|
|
submodule's revision is the zero-length string.
|
|
But in RFC8525 this has changed to:
|
|
If no revision statement is present in the YANG module or submodule, this
|
|
leaf is not instantiated.
|
|
cprintf(cb,"<revision></revision>");
|
|
*/
|
|
}
|
|
if ((ys = yang_find(ymod, Y_NAMESPACE, NULL)) != NULL)
|
|
cprintf(cb,"<namespace>%s</namespace>", yang_argument_get(ys));
|
|
else
|
|
cprintf(cb,"<namespace></namespace>");
|
|
/* This follows order in rfc 7895: feature, conformance-type,
|
|
submodules */
|
|
if (!brief){
|
|
yc = NULL;
|
|
while ((yc = yn_each(ymod, yc)) != NULL) {
|
|
switch(yang_keyword_get(yc)){
|
|
case Y_FEATURE:
|
|
if (yang_cv_get(yc) && cv_bool_get(yang_cv_get(yc)))
|
|
cprintf(cb,"<feature>%s</feature>", yang_argument_get(yc));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
if (clicon_option_bool(h, "CLICON_MODULE_LIBRARY_RFC7895"))
|
|
cprintf(cb, "<conformance-type>implement</conformance-type>");
|
|
}
|
|
yinc = NULL;
|
|
while ((yinc = yn_each(ymod, yinc)) != NULL) {
|
|
if (yang_keyword_get(yinc) != Y_INCLUDE)
|
|
continue;
|
|
cprintf(cb,"<submodule>");
|
|
name = yang_argument_get(yinc);
|
|
cprintf(cb,"<name>%s</name>", name);
|
|
if ((ysub = yang_find(yspec, Y_SUBMODULE, name)) != NULL){
|
|
if ((ys = yang_find(ysub, Y_REVISION, NULL)) != NULL)
|
|
cprintf(cb,"<revision>%s</revision>", yang_argument_get(ys));
|
|
}
|
|
cprintf(cb,"</submodule>");
|
|
}
|
|
cprintf(cb,"</module>");
|
|
}
|
|
if (clicon_option_bool(h, "CLICON_MODULE_LIBRARY_RFC7895")){
|
|
cprintf(cb,"</modules-state>");
|
|
}
|
|
else{
|
|
cprintf(cb,"</module-set></yang-library>");
|
|
}
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Get modules state according to RFC 7895
|
|
* @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] brief Just name, revision and uri (no cache)
|
|
* @param[in,out] xret Existing XML tree, merge x into this
|
|
* @retval 1 OK
|
|
* @retval 0 Statedata callback failed
|
|
* @retval -1 Error (fatal)
|
|
* @notes NYI: schema, deviation
|
|
x +--ro modules-state
|
|
x +--ro module-set-id string
|
|
x +--ro module* [name revision]
|
|
x +--ro name yang:yang-identifier
|
|
x +--ro revision union
|
|
+--ro schema? inet:uri
|
|
x +--ro namespace inet:uri
|
|
+--ro feature* yang:yang-identifier
|
|
+--ro deviation* [name revision]
|
|
| +--ro name yang:yang-identifier
|
|
| +--ro revision union
|
|
+--ro conformance-type enumeration
|
|
+--ro submodule* [name revision]
|
|
+--ro name yang:yang-identifier
|
|
+--ro revision union
|
|
+--ro schema? inet:uri
|
|
* @see netconf_hello_server
|
|
*/
|
|
int
|
|
yang_modules_state_get(clicon_handle h,
|
|
yang_stmt *yspec,
|
|
char *xpath,
|
|
cvec *nsc,
|
|
int brief,
|
|
cxobj **xret)
|
|
{
|
|
int retval = -1;
|
|
cxobj *x = NULL; /* Top tree, some juggling w top symbol */
|
|
char *msid; /* modules-set-id */
|
|
cxobj *xc; /* original cache */
|
|
cbuf *cb = NULL;
|
|
int ret;
|
|
cxobj **xvec = NULL;
|
|
size_t xlen;
|
|
int i;
|
|
|
|
msid = clicon_option_str(h, "CLICON_MODULE_SET_ID"); /* In RFC 8525 changed to "content-id" */
|
|
if ((xc = clicon_modst_cache_get(h, brief)) != NULL){
|
|
cxobj *xw; /* tmp top wrap object */
|
|
/* xc is here: <modules-state>...
|
|
* need to wrap it for xpath: <top><modules-state> */
|
|
/* xc is also original tree, need to copy it */
|
|
if ((xw = xml_wrap(xc, "top")) == NULL)
|
|
goto done;
|
|
if (xpath_first(xw, nsc, "%s", xpath)){
|
|
if ((x = xml_dup(xc)) == NULL) /* Make copy and use below */
|
|
goto done;
|
|
}
|
|
if (xml_rootchild_node(xw, xc) < 0) /* Unwrap x / free xw */
|
|
goto done;
|
|
}
|
|
else { /* No cache -> build the tree */
|
|
if ((cb = cbuf_new()) == NULL){
|
|
clicon_err(OE_UNIX, 0, "clicon buffer");
|
|
goto done;
|
|
}
|
|
/* Build a cb string: <modules-state>... */
|
|
if (yang_modules_state_build(h, yspec, msid, brief, cb) < 0)
|
|
goto done;
|
|
/* Parse cb, x is on the form: <top><modules-state>...
|
|
* Note, list is not sorted since it is state (should not be)
|
|
*/
|
|
if (clixon_xml_parse_string(cbuf_get(cb), YB_MODULE, yspec, &x, NULL) < 0){
|
|
if (xret && netconf_operation_failed_xml(xret, "protocol", clicon_err_reason)< 0)
|
|
goto done;
|
|
goto fail;
|
|
}
|
|
if (xml_rootchild(x, 0, &x) < 0)
|
|
goto done;
|
|
/* x is now: <modules-state>... */
|
|
if (clicon_modst_cache_set(h, brief, x) < 0) /* move to fn above? */
|
|
goto done;
|
|
}
|
|
if (x){ /* x is here a copy (This code is ugly and I think wrong) */
|
|
/* Wrap x (again) with new top-level node "top" which xpath wants */
|
|
if ((x = xml_wrap(x, "top")) < 0)
|
|
goto done;
|
|
/* extract xpath part of module-state tree */
|
|
if (xpath_vec(x, nsc, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
|
|
goto done;
|
|
if (xvec != NULL){
|
|
for (i=0; i<xlen; i++)
|
|
xml_flag_set(xvec[i], XML_FLAG_MARK);
|
|
}
|
|
/* 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)
|
|
goto fail;
|
|
}
|
|
retval = 1;
|
|
done:
|
|
clicon_debug(1, "%s %d", __FUNCTION__, retval);
|
|
if (xvec)
|
|
free(xvec);
|
|
if (cb)
|
|
cbuf_free(cb);
|
|
if (x)
|
|
xml_free(x);
|
|
return retval;
|
|
fail:
|
|
retval = 0;
|
|
goto done;
|
|
}
|
|
|
|
/*! For single module state with namespace, get revisions and send upgrade callbacks
|
|
* @param[in] h Clicon handle
|
|
* @param[in] xt Top-level XML tree to be updated (includes other ns as well)
|
|
* @param[in] xmod XML module state diff (for one yang module)
|
|
* @param[in] ns Namespace of module state we are looking for
|
|
* @param[out] cbret Netconf error message if invalid
|
|
* @retval 1 OK
|
|
* @retval 0 Validation failed (cbret set)
|
|
* @retval -1 Error
|
|
*/
|
|
static int
|
|
mod_ns_upgrade(clicon_handle h,
|
|
cxobj *xt,
|
|
cxobj *xmod,
|
|
char *ns,
|
|
cbuf *cbret)
|
|
{
|
|
int retval = -1;
|
|
char *b; /* string body */
|
|
yang_stmt *ymod;
|
|
yang_stmt *yrev;
|
|
uint32_t from = 0;
|
|
uint32_t to = 0;
|
|
int ret;
|
|
yang_stmt *yspec;
|
|
|
|
/* If modified or removed get from revision from file */
|
|
if (xml_flag(xmod, (XML_FLAG_CHANGE|XML_FLAG_DEL)) != 0x0){
|
|
if ((b = xml_find_body(xmod, "revision")) != NULL) /* Module revision */
|
|
if (ys_parse_date_arg(b, &from) < 0)
|
|
goto done;
|
|
}
|
|
/* If modified or added get to revision from system */
|
|
if (xml_flag(xmod, (XML_FLAG_CHANGE|XML_FLAG_ADD)) != 0x0){
|
|
yspec = clicon_dbspec_yang(h);
|
|
if ((ymod = yang_find_module_by_namespace(yspec, ns)) == NULL){
|
|
cprintf(cbret, "Module-set upgrade header contains namespace %s, but is not found in running system", ns);
|
|
goto fail;
|
|
}
|
|
if ((yrev = yang_find(ymod, Y_REVISION, NULL)) == NULL){
|
|
retval = 1;
|
|
goto done;
|
|
}
|
|
if (ys_parse_date_arg(yang_argument_get(yrev), &to) < 0)
|
|
goto done;
|
|
}
|
|
if ((ret = upgrade_callback_call(h, xt, ns,
|
|
xml_flag(xmod, (XML_FLAG_ADD|XML_FLAG_DEL|XML_FLAG_CHANGE)),
|
|
from, to, cbret)) < 0)
|
|
goto done;
|
|
if (ret == 0) /* XXX ignore and continue? */
|
|
goto fail;
|
|
retval = 1;
|
|
done:
|
|
return retval;
|
|
fail:
|
|
retval = 0;
|
|
goto done;
|
|
}
|
|
|
|
/*! Upgrade XML
|
|
* @param[in] h Clicon handle
|
|
* @param[in] xt XML tree (to upgrade)
|
|
* @param[in] msd Modules-state differences of xt
|
|
* @param[out] cbret Netconf error message if invalid
|
|
* @retval 1 OK
|
|
* @retval 0 Validation failed (cbret set)
|
|
* @retval -1 Error
|
|
*/
|
|
int
|
|
clixon_module_upgrade(clicon_handle h,
|
|
cxobj *xt,
|
|
modstate_diff_t *msd,
|
|
cbuf *cbret)
|
|
{
|
|
int retval = -1;
|
|
char *ns; /* Namespace */
|
|
cxobj *xmod; /* XML module state diff */
|
|
int ret;
|
|
|
|
if (msd == NULL){
|
|
clicon_err(OE_CFG, EINVAL, "No modstate");
|
|
goto done;
|
|
}
|
|
if (msd->md_status == 0) /* No modstate in startup */
|
|
goto ok;
|
|
/* Iterate through xml modified module state
|
|
* Note top-level here is typically module-set
|
|
*/
|
|
xmod = NULL;
|
|
while ((xmod = xml_child_each(msd->md_diff, xmod, CX_ELMNT)) != NULL) {
|
|
/* Extract namespace */
|
|
if ((ns = xml_find_body(xmod, "namespace")) == NULL)
|
|
goto done;
|
|
/* Extract revisions and make callbacks */
|
|
if ((ret = mod_ns_upgrade(h, xt, xmod, ns, cbret)) < 0)
|
|
goto done;
|
|
if (ret == 0)
|
|
goto fail;
|
|
}
|
|
ok:
|
|
retval = 1;
|
|
done:
|
|
return retval;
|
|
fail:
|
|
retval = 0;
|
|
goto done;
|
|
}
|
|
|
|
/*! Given a yang statement and a prefix, return yang module to that relative prefix
|
|
* Note, not the other module but the proxy import statement only
|
|
* @param[in] ys A yang statement
|
|
* @param[in] prefix prefix
|
|
* @retval ymod Yang module statement if found
|
|
* @retval NULL not found
|
|
* @note Prefixes are relative to the module they are defined
|
|
* @see yang_find_module_by_name
|
|
* @see yang_find_module_by_namespace
|
|
* @see yang_find_namespace_by_prefix
|
|
*/
|
|
yang_stmt *
|
|
yang_find_module_by_prefix(yang_stmt *ys,
|
|
char *prefix)
|
|
{
|
|
yang_stmt *yimport;
|
|
yang_stmt *yprefix;
|
|
yang_stmt *my_ymod;
|
|
yang_stmt *ymod = NULL;
|
|
yang_stmt *yspec;
|
|
char *myprefix;
|
|
|
|
if ((yspec = ys_spec(ys)) == NULL){
|
|
clicon_err(OE_YANG, 0, "My yang spec not found");
|
|
goto done;
|
|
}
|
|
/* First try own module */
|
|
if ((my_ymod = ys_module(ys)) == NULL){
|
|
clicon_err(OE_YANG, 0, "My yang module not found");
|
|
goto done;
|
|
}
|
|
myprefix = yang_find_myprefix(ys);
|
|
if (myprefix && strcmp(myprefix, prefix) == 0){
|
|
ymod = my_ymod;
|
|
goto done;
|
|
}
|
|
/* If no match, try imported modules */
|
|
yimport = NULL;
|
|
while ((yimport = yn_each(my_ymod, yimport)) != NULL) {
|
|
if (yang_keyword_get(yimport) != Y_IMPORT)
|
|
continue;
|
|
if ((yprefix = yang_find(yimport, Y_PREFIX, NULL)) != NULL &&
|
|
strcmp(yang_argument_get(yprefix), prefix) == 0){
|
|
break;
|
|
}
|
|
}
|
|
if (yimport){
|
|
if ((ymod = yang_find(yspec, Y_MODULE, yang_argument_get(yimport))) == NULL){
|
|
clicon_err(OE_YANG, 0, "No module or sub-module found with prefix %s",
|
|
prefix);
|
|
yimport = NULL;
|
|
goto done; /* unresolved */
|
|
}
|
|
}
|
|
done:
|
|
return ymod;
|
|
}
|
|
|
|
/* Get module from its own prefix
|
|
* This is really not a valid usecase, a kludge for the identityref derived
|
|
* list workaround (IDENTITYREF_KLUDGE)
|
|
* Actually, for canonical prefixes it is!
|
|
*/
|
|
yang_stmt *
|
|
yang_find_module_by_prefix_yspec(yang_stmt *yspec,
|
|
char *prefix)
|
|
{
|
|
yang_stmt *ymod = NULL;
|
|
yang_stmt *yprefix;
|
|
|
|
while ((ymod = yn_each(yspec, ymod)) != NULL)
|
|
if (yang_keyword_get(ymod) == Y_MODULE &&
|
|
(yprefix = yang_find(ymod, Y_PREFIX, NULL)) != NULL &&
|
|
strcmp(yang_argument_get(yprefix), prefix) == 0)
|
|
return ymod;
|
|
return NULL;
|
|
}
|
|
|
|
/*! Given a yang spec and a namespace, return yang module
|
|
*
|
|
* @param[in] yspec A yang specification
|
|
* @param[in] ns namespace
|
|
* @retval ymod Yang module statement if found
|
|
* @retval NULL not found
|
|
* @see yang_find_module_by_name
|
|
* @see yang_find_module_by_prefix module-specific prefix
|
|
* @see yang_find_prefix_by_namespace
|
|
*/
|
|
yang_stmt *
|
|
yang_find_module_by_namespace(yang_stmt *yspec,
|
|
char *ns)
|
|
{
|
|
yang_stmt *ymod = NULL;
|
|
|
|
if (ns == NULL)
|
|
goto done;
|
|
while ((ymod = yn_each(yspec, ymod)) != NULL) {
|
|
if (yang_find(ymod, Y_NAMESPACE, ns) != NULL)
|
|
break;
|
|
}
|
|
done:
|
|
return ymod;
|
|
}
|
|
|
|
/*! Given a yang spec, a namespace and revision, return yang module
|
|
*
|
|
* @param[in] yspec A yang specification
|
|
* @param[in] ns Namespace
|
|
* @param[in] rev Revision
|
|
* @retval ymod Yang module statement if found
|
|
* @retval NULL not found
|
|
* @see yang_find_module_by_namespace
|
|
* @note a module may have many revisions, but only the first is significant
|
|
*/
|
|
yang_stmt *
|
|
yang_find_module_by_namespace_revision(yang_stmt *yspec,
|
|
const char *ns,
|
|
const char *rev)
|
|
{
|
|
yang_stmt *ymod = NULL;
|
|
yang_stmt *yrev;
|
|
char *rev1;
|
|
|
|
if (ns == NULL || rev == NULL){
|
|
clicon_err(OE_CFG, EINVAL, "No ns or rev");
|
|
goto done;
|
|
}
|
|
while ((ymod = yn_each(yspec, ymod)) != NULL) {
|
|
if (yang_find(ymod, Y_NAMESPACE, ns) != NULL)
|
|
/* Get FIRST revision */
|
|
if ((yrev = yang_find(ymod, Y_REVISION, NULL)) != NULL){
|
|
rev1 = yang_argument_get(yrev);
|
|
if (strcmp(rev, rev1) == 0)
|
|
break; /* return this ymod */
|
|
}
|
|
}
|
|
done:
|
|
return ymod;
|
|
}
|
|
|
|
/*! Given a yang spec, name and revision, return yang module
|
|
*
|
|
* @param[in] yspec A yang specification
|
|
* @param[in] name Name
|
|
* @param[in] rev Revision
|
|
* @retval ymod Yang module statement if found
|
|
* @retval NULL not found
|
|
* @see yang_find_module_by_namespace
|
|
* @note a module may have many revisions, but only the first is significant
|
|
*/
|
|
yang_stmt *
|
|
yang_find_module_by_name_revision(yang_stmt *yspec,
|
|
const char *name,
|
|
const char *rev)
|
|
{
|
|
yang_stmt *ymod = NULL;
|
|
yang_stmt *yrev;
|
|
char *rev1;
|
|
|
|
if (name == NULL){
|
|
clicon_err(OE_CFG, EINVAL, "No ns or rev");
|
|
goto done;
|
|
}
|
|
while ((ymod = yn_each(yspec, ymod)) != NULL) {
|
|
if (yang_keyword_get(ymod) != Y_MODULE)
|
|
continue;
|
|
if (strcmp(yang_argument_get(ymod), name) != 0)
|
|
continue;
|
|
if (rev == NULL)
|
|
break; /* Matching revision is NULL, match that */
|
|
/* Get FIRST revision */
|
|
if ((yrev = yang_find(ymod, Y_REVISION, NULL)) != NULL){
|
|
rev1 = yang_argument_get(yrev);
|
|
if (strcmp(rev, rev1) == 0)
|
|
break; /* return this ymod */
|
|
}
|
|
}
|
|
done:
|
|
return ymod;
|
|
}
|
|
|
|
/*! Given a yang spec and a module name, return yang module or submodule
|
|
*
|
|
* @param[in] yspec A yang specification
|
|
* @param[in] name Name of module
|
|
* @retval ymod Yang module statement if found
|
|
* @retval NULL not found
|
|
* @see yang_find_module_by_namespace
|
|
* @see yang_find_module_by_prefix module-specific prefix
|
|
*/
|
|
yang_stmt *
|
|
yang_find_module_by_name(yang_stmt *yspec,
|
|
char *name)
|
|
{
|
|
yang_stmt *ymod = NULL;
|
|
|
|
while ((ymod = yn_each(yspec, ymod)) != NULL)
|
|
if ((yang_keyword_get(ymod) == Y_MODULE || yang_keyword_get(ymod) == Y_SUBMODULE) &&
|
|
strcmp(yang_argument_get(ymod), name)==0)
|
|
return ymod;
|
|
return NULL;
|
|
}
|
|
|
|
/*! Callback for handling RFC 7952 annotations
|
|
*
|
|
* A server indicates that it is prepared to handle that annotation according to the
|
|
* annotation's definition. That is, an annotation advertised by the
|
|
* server may be attached to an instance of a data node defined in any
|
|
* YANG module that is implemented by the server.
|
|
* Possibly add them to yang parsing, cardinality, etc?
|
|
* as described in Section 3.
|
|
* Note this is called by the module using the extension md:annotate, not by
|
|
* ietf-yang-metadata.yang
|
|
* @see yang_metadata_annotation_check
|
|
*/
|
|
static int
|
|
ietf_yang_metadata_extension_cb(clicon_handle h,
|
|
yang_stmt *yext,
|
|
yang_stmt *ys)
|
|
{
|
|
int retval = -1;
|
|
char *extname;
|
|
char *modname;
|
|
yang_stmt *ymod;
|
|
char *name;
|
|
|
|
ymod = ys_module(yext);
|
|
modname = yang_argument_get(ymod);
|
|
extname = yang_argument_get(yext);
|
|
if (strcmp(modname, "ietf-yang-metadata") != 0 || strcmp(extname, "annotation") != 0)
|
|
goto ok;
|
|
name = cv_string_get(yang_cv_get(ys));
|
|
clicon_debug(1, "%s Enabled extension:%s:%s:%s", __FUNCTION__, modname, extname, name);
|
|
/* XXX Nothing yet - this should signal that xml attribute annotations are allowed
|
|
* Possibly, add an "annotation" YANG node.
|
|
*/
|
|
ok:
|
|
retval = 0;
|
|
// done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Check annotation extension
|
|
*
|
|
* @param[in] xa XML attribute
|
|
* @param[in] ys YANG something
|
|
* @param[out] ismeta Set to 1 if this is an annotation
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
* @see ietf_yang_metadata_extension_cb
|
|
* XXX maybe a cache would be appropriate?
|
|
* XXX: return type?
|
|
*/
|
|
int
|
|
yang_metadata_annotation_check(cxobj *xa,
|
|
yang_stmt *ymod,
|
|
int *ismeta)
|
|
{
|
|
int retval = -1;
|
|
yang_stmt *yma = NULL;
|
|
char *name;
|
|
cg_var *cv;
|
|
|
|
/* Loop through annotations */
|
|
while ((yma = yn_each(ymod, yma)) != NULL){
|
|
/* Assume here md:annotation is written using canonical prefix */
|
|
if (yang_keyword_get(yma) != Y_UNKNOWN)
|
|
continue;
|
|
if (strcmp(yang_argument_get(yma), "md:annotation") != 0)
|
|
continue;
|
|
if ((cv = yang_cv_get(yma)) != NULL &&
|
|
(name = cv_string_get(cv)) != NULL){
|
|
if (strcmp(name, xml_name(xa)) == 0){
|
|
/* XXX: yang_find(yma,Y_TYPE,0) */
|
|
*ismeta = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
retval = 0;
|
|
// done:
|
|
return retval;
|
|
}
|
|
|
|
/*! In case ietf-yang-metadata is loaded by application, handle annotation extension
|
|
* Consider moving fn
|
|
* Must be called after clixon_plugin_module_init
|
|
*/
|
|
int
|
|
yang_metadata_init(clicon_handle h)
|
|
{
|
|
int retval = -1;
|
|
clixon_plugin_t *cp = NULL;
|
|
|
|
/* Create a pseudo-plugin to create extension callback to set the ietf-yang-meta
|
|
* yang-data extension for api-root top-level restconf function.
|
|
*/
|
|
if (clixon_pseudo_plugin(h, "pseudo yang metadata", &cp) < 0)
|
|
goto done;
|
|
clixon_plugin_api_get(cp)->ca_extension = ietf_yang_metadata_extension_cb;
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Given yang-lib module-set XML tree, parse all modules into an yspec
|
|
*
|
|
* This function is used where a yang-lib module-set is available to populate
|
|
* an XML mount-point.
|
|
* @param[in] h Clicon handle
|
|
* @param[in] xylib yang-lib XML tree on the form <yang-lib>...
|
|
* @param[in] yspec Will be populated with YANGs, is consumed
|
|
* @retval 1 OK
|
|
* @retval 0 Parse error
|
|
* @retval -1 Error
|
|
* @see xml_schema_add_mount_points
|
|
*/
|
|
int
|
|
yang_lib2yspec(clicon_handle h,
|
|
cxobj *yanglib,
|
|
yang_stmt *yspec)
|
|
{
|
|
int retval = -1;
|
|
cxobj *xi;
|
|
char *name;
|
|
char *revision;
|
|
cvec *nsc = NULL;
|
|
cxobj **vec = NULL;
|
|
size_t veclen;
|
|
int i;
|
|
|
|
if (xpath_vec(yanglib, nsc, "module-set/module", &vec, &veclen) < 0)
|
|
goto done;
|
|
for (i=0; i<veclen; i++){
|
|
xi = vec[i];
|
|
if ((name = xml_find_body(xi, "name")) == NULL)
|
|
continue;
|
|
if ((revision = xml_find_body(xi, "revision")) == NULL)
|
|
continue;
|
|
if (yang_parse_module(h, name, revision, yspec, NULL) == NULL)
|
|
goto fail;
|
|
}
|
|
#ifdef YANG_SCHEMA_MOUNT_YANG_LIB_FORCE
|
|
/* XXX: Ensure yang-lib is always there otherwise get state dont work for mountpoint */
|
|
if (yang_parse_module(h, "ietf-yang-library", "2019-01-04", yspec, NULL) < 0)
|
|
goto fail;
|
|
#endif
|
|
if (yang_parse_post(h, yspec, 0) < 0)
|
|
goto done;
|
|
retval = 1;
|
|
done:
|
|
if (vec)
|
|
free(vec);
|
|
return retval;
|
|
fail:
|
|
retval = 0;
|
|
goto done;
|
|
}
|