1075 lines
32 KiB
C
1075 lines
32 KiB
C
/*
|
|
*
|
|
***** 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
|
|
*
|
|
* Extend a container with ietf-yang-schema-mount:mount-point.
|
|
* Structure of mount-points in YANG anc config:
|
|
*
|
|
* module ietf-yang-schema-mount { # Existing module
|
|
* extension mount-point
|
|
*
|
|
* module mymodule { # Your module
|
|
* ...
|
|
* container root{ (ymnt)
|
|
* yangmnt:mount-point "mylabel"; (yext)
|
|
* }
|
|
*
|
|
* <config> # Your XML config
|
|
* ...
|
|
* <root> (xmnt)
|
|
*
|
|
* The API handles the relation between yext -->* ymnt -->* xmnt
|
|
* Structure:
|
|
*
|
|
* yspec0(1) xtop(1)
|
|
* | | (xpath)
|
|
* ymnt(*) <-- xmnt(*)
|
|
* / \
|
|
* yext(1) cvec: [xpath = yspec](*)
|
|
* | |
|
|
* cv:label ymod(*)
|
|
*
|
|
* The calls in this code are:
|
|
* - yang_schema_mount_point(): Is ymnt a yang mount-point? (ymnt)
|
|
* - yang_mount_get(): ymnt + xpath -> yspec
|
|
* - yang_mount_set(): ymnt + xpath -> yspec
|
|
* - xml_yang_mount_get(): xmnt-> yspec
|
|
* - xml_yang_mount_set(): xmnt -> yspec
|
|
* - yang_mount_get_yspec_any(): ymnt -> yspec
|
|
* - yang_mounto_freeall(): ymnt-> free cvec
|
|
* - yang_mount_xmnt2ymnt_xpath(): xmnt -> ymnt + xpath
|
|
* - yang_mount_xtop2xmnt(): top-level xml -> xmnt vector
|
|
* - yang_mount_yspec2ymnt(): top-level yspec -> ymnt vector
|
|
* - yang_schema_mount_statistics(): Given xtop -> find all xmnt -> stats
|
|
|
|
*
|
|
* Note: the xpath used as key in yang unknown cvec is "canonical" in the sense:
|
|
* - it uses prefixes of the yang spec of relevance
|
|
* - it uses '' not "" in prefixes (eg a[x='foo']. The reason is '' is easier printed in clispecs
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "clixon_config.h" /* generated by config & autoconf */
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <errno.h>
|
|
#include <inttypes.h>
|
|
#include <limits.h>
|
|
#include <string.h>
|
|
#include <sys/param.h>
|
|
|
|
/* cligen */
|
|
#include <cligen/cligen.h>
|
|
|
|
/* clixon */
|
|
#include "clixon_queue.h"
|
|
#include "clixon_hash.h"
|
|
#include "clixon_string.h"
|
|
#include "clixon_handle.h"
|
|
#include "clixon_yang.h"
|
|
#include "clixon_xml.h"
|
|
#include "clixon_err.h"
|
|
#include "clixon_log.h"
|
|
#include "clixon_debug.h"
|
|
#include "clixon_options.h"
|
|
#include "clixon_netconf_lib.h"
|
|
#include "clixon_xml_io.h"
|
|
#include "clixon_xml_map.h"
|
|
#include "clixon_data.h"
|
|
#include "clixon_xpath_ctx.h"
|
|
#include "clixon_xpath.h"
|
|
#include "clixon_yang_module.h"
|
|
#include "clixon_yang_parse_lib.h"
|
|
#include "clixon_plugin.h"
|
|
#include "clixon_xml_bind.h"
|
|
#include "clixon_xml_nsctx.h"
|
|
#include "clixon_yang_schema_mount.h"
|
|
|
|
/*! Check if YANG node 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_point0(yang_stmt *y)
|
|
{
|
|
int retval = -1;
|
|
enum rfc_6020 keyw;
|
|
int exist = 0;
|
|
char *value = NULL;
|
|
|
|
if (y == NULL){
|
|
clixon_err(OE_YANG, EINVAL, "y is NULL");
|
|
goto done;
|
|
}
|
|
keyw = yang_keyword_get(y);
|
|
if (keyw != Y_CONTAINER
|
|
#ifndef YANG_SCHEMA_MOUNT_ONLY_PRESENCE_CONTAINERS
|
|
&& keyw != Y_LIST
|
|
#endif
|
|
#if 0 /* See this in some standard YANGs but RFC 8528 does not allow it */
|
|
&& keyw != Y_ANYDATA
|
|
#endif
|
|
)
|
|
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;
|
|
}
|
|
|
|
/*! Cached variant of yang_schema_mount_point0
|
|
*
|
|
* @param[in] y Yang node
|
|
* @retval 1 Yes, node is potential mountpoint
|
|
* @retval 0 No, node is not potential mountpoint
|
|
*/
|
|
int
|
|
yang_schema_mount_point(yang_stmt *y)
|
|
{
|
|
return yang_flag_get(y, YANG_FLAG_MTPOINT_POTENTIAL) ? 1 : 0;
|
|
}
|
|
|
|
/*! Get yangspec mount-point
|
|
*
|
|
* @param[in] y Yang container/list containing unknown node
|
|
* @param[in] xpath Key for yspec on y
|
|
* @param[out] yspec YANG stmt spec
|
|
* @retval 0 OK
|
|
*/
|
|
int
|
|
yang_mount_get(yang_stmt *y,
|
|
char *xpath,
|
|
yang_stmt **yspec)
|
|
{
|
|
cvec *cvv;
|
|
cg_var *cv;
|
|
|
|
clixon_debug(CLIXON_DBG_YANG | CLIXON_DBG_DETAIL, "%s %p", xpath, y);
|
|
if ((cvv = yang_cvec_get(y)) != NULL &&
|
|
(cv = cvec_find(cvv, xpath)) != NULL &&
|
|
yspec)
|
|
*yspec = cv_void_get(cv);
|
|
else
|
|
*yspec = NULL;
|
|
return 0;
|
|
}
|
|
|
|
/*! Get any yspec of a mount-point, special function
|
|
*
|
|
* Get (the first) mounted yspec.
|
|
* A more generic way would be to call plugin_mount to get the yanglib and from that get the
|
|
* yspec. But there is clixon code that cant call the plugin since h is not available
|
|
* @param[in] y Yang container/list containing unknown node
|
|
* @param[out] yspec YANG stmt spec
|
|
* @retval 1 yspec found and set
|
|
* @retval 0 Not found
|
|
*/
|
|
int
|
|
yang_mount_get_yspec_any(yang_stmt *y,
|
|
yang_stmt **yspec)
|
|
{
|
|
cvec *cvv;
|
|
cg_var *cv;
|
|
void *p;
|
|
|
|
/* Special value in yang unknown node for mount-points: mapping from xpath->mounted yspec */
|
|
if ((cvv = yang_cvec_get(y)) != NULL &&
|
|
(cv = cvec_i(cvv, 0)) != NULL &&
|
|
(p = cv_void_get(cv)) != NULL){
|
|
*yspec = p;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*! Set yangspec mount-point on yang node containing extension
|
|
*
|
|
* Mount-points are stored in yang cvec in container/list node taht is a mount-point
|
|
* as defined in yang_schema_mount_point()
|
|
* @param[in] y Yang container/list containing unknown node
|
|
* @param[in] xpath Key for yspec on y, in canonical form
|
|
* @param[in] yspec Yangspec for this mount-point (consumed)
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*/
|
|
int
|
|
yang_mount_set(yang_stmt *y,
|
|
char *xpath,
|
|
yang_stmt *yspec)
|
|
{
|
|
int retval = -1;
|
|
yang_stmt *yspec0;
|
|
cvec *cvv;
|
|
cg_var *cv;
|
|
cg_var *cv2;
|
|
|
|
clixon_debug(CLIXON_DBG_YANG, "%s %p", xpath, y);
|
|
if ((cvv = yang_cvec_get(y)) != NULL &&
|
|
(cv = cvec_find(cvv, xpath)) != NULL &&
|
|
(yspec0 = cv_void_get(cv)) != NULL){
|
|
#if 0 /* Problematic to free yang specs here, upper layers should handle it? */
|
|
ys_free(yspec0);
|
|
#endif
|
|
yang_ref_dec(yspec0);
|
|
cv_void_set(cv, NULL);
|
|
}
|
|
else if ((cv = yang_cvec_add(y, CGV_VOID, xpath)) == NULL)
|
|
goto done;
|
|
if ((cv2 = cv_new(CGV_STRING)) == NULL){
|
|
clixon_err(OE_YANG, errno, "cv_new");
|
|
goto done;
|
|
}
|
|
if (cv_string_set(cv2, xpath) == NULL){
|
|
clixon_err(OE_UNIX, errno, "cv_string_set");
|
|
goto done;
|
|
}
|
|
/* tag yspec with key/xpath */
|
|
yang_cv_set(yspec, cv2);
|
|
cv_void_set(cv, yspec);
|
|
yang_ref_inc(yspec); /* share */
|
|
yang_flag_set(y, YANG_FLAG_MOUNTPOINT); /* Cache value */
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Given an XML mount-point return YANG mount and XPath
|
|
*
|
|
* @param[in] h Clixon handle
|
|
* @param[in] xmnt XML mount-point
|
|
* @param[out] ymnt YANG mount-point
|
|
* @param[out] xpath Canonical XPath from XML top-level to xmnt
|
|
* @retval 1 OK, xmnt is a mount-point with ymnt and xpath returned
|
|
* @retval 0 OK, xmnt is not a mount point
|
|
* @retval -1 Error
|
|
*/
|
|
static int
|
|
yang_mount_xmnt2ymnt_xpath(clixon_handle h,
|
|
cxobj *xmnt,
|
|
yang_stmt **ymntp,
|
|
char **xpath)
|
|
{
|
|
int retval = -1;
|
|
yang_stmt *yspec;
|
|
yang_stmt *ymnt;
|
|
char *xpath0 = NULL;
|
|
cvec *nsc0 = NULL;
|
|
cvec *nsc1 = NULL;
|
|
cbuf *reason = NULL;
|
|
int ret;
|
|
|
|
if (xmnt == NULL){
|
|
clixon_err(OE_YANG, EINVAL, "xmnt is NULL");
|
|
goto done;
|
|
}
|
|
if ((ymnt = xml_spec(xmnt)) == NULL)
|
|
goto fail;
|
|
if (yang_schema_mount_point(ymnt) == 0)
|
|
goto fail;
|
|
if (xml2xpath(xmnt, NULL, 1, 0, &xpath0) < 0)
|
|
goto done;
|
|
if (xml_nsctx_node(xmnt, &nsc0) < 0)
|
|
goto done;
|
|
yspec = clicon_dbspec_yang(h);
|
|
if ((ret = xpath2canonical(xpath0, nsc0, yspec, xpath, &nsc1, &reason)) < 0)
|
|
goto done;
|
|
if (ret == 0)
|
|
goto fail;
|
|
if (ymntp)
|
|
*ymntp = ymnt;
|
|
retval = 1;
|
|
done:
|
|
if (xpath0)
|
|
free(xpath0);
|
|
if (nsc0)
|
|
cvec_free(nsc0);
|
|
if (nsc1)
|
|
cvec_free(nsc1);
|
|
if (reason)
|
|
cbuf_free(reason);
|
|
return retval;
|
|
fail:
|
|
retval = 0;
|
|
goto done;
|
|
}
|
|
|
|
/*! Get yangspec mount-point
|
|
*
|
|
* @param[in] h Clixon handle
|
|
* @param[in] xmnt XML mount-point
|
|
* @param[out] vallevel Do or dont do full RFC 7950 validation if given
|
|
* @param[out] yspec YANG stmt spec of mount-point (if ret is 1)
|
|
* @retval 1 x is a mount-point: yspec may be set
|
|
* @retval 0 x is not a mount point
|
|
* @retval -1 Error
|
|
*/
|
|
int
|
|
xml_yang_mount_get(clixon_handle h,
|
|
cxobj *xmnt,
|
|
validate_level *vl,
|
|
yang_stmt **yspec)
|
|
{
|
|
int retval = -1;
|
|
yang_stmt *ymnt = NULL;
|
|
char *xpath = NULL;
|
|
int ret;
|
|
|
|
if ((ret = yang_mount_xmnt2ymnt_xpath(h, xmnt, &ymnt, &xpath)) < 0)
|
|
goto done;
|
|
if (ret == 0)
|
|
goto fail;
|
|
/* Check validate level */
|
|
if (vl && clixon_plugin_yang_mount_all(h, xmnt, NULL, vl, NULL) < 0)
|
|
goto done;
|
|
if (yspec && yang_mount_get(ymnt, xpath, yspec) < 0)
|
|
goto done;
|
|
retval = 1;
|
|
done:
|
|
if (xpath)
|
|
free(xpath);
|
|
return retval;
|
|
fail:
|
|
retval = 0;
|
|
goto done;
|
|
}
|
|
|
|
/*! Set yangspec mount-point via XML mount-point node
|
|
*
|
|
* Stored in a separate structure (not in XML config tree)
|
|
* @param[in] h Clixon handle
|
|
* @param[in] xmnt XML mount-point
|
|
* @param[in] yspec Yangspec for this mount-point (consumed)
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*/
|
|
int
|
|
xml_yang_mount_set(clixon_handle h,
|
|
cxobj *xmnt,
|
|
yang_stmt *yspec)
|
|
{
|
|
int retval = -1;
|
|
yang_stmt *ymnt;
|
|
char *xpath = NULL;
|
|
int ret;
|
|
|
|
if ((ret = yang_mount_xmnt2ymnt_xpath(h, xmnt, &ymnt, &xpath)) < 0)
|
|
goto done;
|
|
if (ret == 0){
|
|
clixon_err(OE_YANG, 0, "Mapping xmnt to ymnt and xpath");
|
|
goto done;
|
|
}
|
|
if (yang_mount_set(ymnt, xpath, yspec) < 0)
|
|
goto done;
|
|
retval = 0;
|
|
done:
|
|
if (xpath)
|
|
free(xpath);
|
|
return retval;
|
|
}
|
|
|
|
/*! Free all yspec yang-mounts
|
|
*
|
|
* @param[in] ymnt YANG mount-point
|
|
* @retval 0 OK
|
|
*/
|
|
int
|
|
yang_mount_freeall(yang_stmt *ymnt)
|
|
{
|
|
cvec *cvv;
|
|
cg_var *cv;
|
|
yang_stmt *ys;
|
|
|
|
if ((cvv = yang_cvec_get(ymnt)) != NULL){
|
|
cv = NULL;
|
|
while ((cv = cvec_each(cvv, cv)) != NULL){
|
|
if ((ys = cv_void_get(cv)) != NULL)
|
|
ys_free(ys);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*! Find schema mounts - callback function for xml_apply
|
|
*
|
|
* @param[in] x XML node
|
|
* @param[in] arg cvec, if match add node
|
|
* @retval 2 Locally abort this subtree, continue with others
|
|
* @retval 1 Abort, dont continue with others, return 1 to end user
|
|
* @retval 0 OK, continue
|
|
* @retval -1 Error, aborted at first error encounter, return -1 to end user
|
|
*/
|
|
static int
|
|
find_xml_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){
|
|
clixon_err(OE_UNIX, errno, "cvec_add");
|
|
return -1;
|
|
}
|
|
cv_void_set(cv, x);
|
|
return 0;
|
|
}
|
|
|
|
/*! Given XML top-of-tree, find all XML mount-points and return in a vector
|
|
*
|
|
* @param[in] h Clixon handle
|
|
* @param[in] xtop XML top-node
|
|
* @param[out] cvv Cligen vector of XML moint-points, deallocate with cvec_fee()
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*/
|
|
int
|
|
yang_mount_xtop2xmnt(cxobj *xtop,
|
|
cvec **cvvp)
|
|
{
|
|
int retval = -1;
|
|
cvec *cvv = NULL;
|
|
|
|
if ((cvv = cvec_new(0)) == NULL){
|
|
clixon_err(OE_UNIX, errno, "cvec_new");
|
|
goto done;
|
|
}
|
|
if (xml_apply(xtop, CX_ELMNT, find_xml_schema_mounts, cvv) < 0)
|
|
goto done;
|
|
if (cvvp)
|
|
*cvvp = cvv;
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Find schema mounts - callback function for yang_apply
|
|
*
|
|
* @param[in] yn yang node
|
|
* @param[in] arg Argument
|
|
* @retval n OK, abort traversal and return to caller with "n"
|
|
* @retval 0 OK, continue with next
|
|
* @retval -1 Error, abort
|
|
*/
|
|
static int
|
|
find_yang_schema_mounts(yang_stmt *y,
|
|
void *arg)
|
|
{
|
|
int ret;
|
|
cvec *cvv = (cvec *)arg;
|
|
cg_var *cv;
|
|
|
|
if (yang_config(y) == 0)
|
|
return 0;
|
|
if ((ret = yang_schema_mount_point(y)) < 0)
|
|
return -1;
|
|
if (ret == 0)
|
|
return 0;
|
|
if ((cv = cvec_add(cvv, CGV_VOID)) == NULL){
|
|
clixon_err(OE_UNIX, errno, "cvec_add");
|
|
return -1;
|
|
}
|
|
cv_void_set(cv, y);
|
|
return 0;
|
|
}
|
|
|
|
/*! Given top-level YANG spec, find all YANG mount-points and return in a vector
|
|
*
|
|
* @param[in] h Clixon handle
|
|
* @param[in] yspec YANG top-level spec
|
|
* @param[out] cvv Cligen vector of YANG moint-points, free with cvec_fee()
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*/
|
|
int
|
|
yang_mount_yspec2ymnt(yang_stmt *yspec,
|
|
cvec **cvvp)
|
|
{
|
|
int retval = -1;
|
|
cvec *cvv = NULL;
|
|
|
|
if ((cvv = cvec_new(0)) == NULL){
|
|
clixon_err(OE_UNIX, errno, "cvec_new");
|
|
goto done;
|
|
}
|
|
if (yang_apply(yspec, -1, find_yang_schema_mounts, 1, cvv) < 0)
|
|
goto done;
|
|
if (cvvp)
|
|
*cvvp = cvv;
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! 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 Clixon handle
|
|
* @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
|
|
* XXX Mountpoints must exist in xret on entry, which is problematic:
|
|
* XXX A get state may have an xpath not including their config, ie:
|
|
* XXX xpath=/top/mymount/yang-library does not include /top/mymount and therefore
|
|
* XXX the mountpoint will not be present in xret
|
|
* XXX see: https://github.com/clicon/clixon/issues/485
|
|
*/
|
|
static int
|
|
yang_schema_mount_statedata_yanglib(clixon_handle h,
|
|
char *xpath,
|
|
cvec *nsc,
|
|
cxobj **xret,
|
|
cxobj **xerr)
|
|
{
|
|
int retval = -1;
|
|
cvec *cvv = NULL;
|
|
cg_var *cv;
|
|
cxobj *xmnt;
|
|
cxobj *yanglib = NULL; /* xml yang-lib */
|
|
cbuf *cb = NULL;
|
|
yang_stmt *yspec;
|
|
int ret;
|
|
int config = 1;
|
|
validate_level vl = VL_FULL;
|
|
|
|
if ((cb = cbuf_new()) == NULL){
|
|
clixon_err(OE_UNIX, 0, "clicon buffer");
|
|
goto done;
|
|
}
|
|
if (yang_mount_xtop2xmnt(*xret, &cvv) < 0)
|
|
goto done;
|
|
yspec = clicon_dbspec_yang(h);
|
|
cv = NULL;
|
|
while ((cv = cvec_each(cvv, cv)) != NULL) {
|
|
xmnt = cv_void_get(cv);
|
|
yanglib = NULL;
|
|
/* User callback */
|
|
if (clixon_plugin_yang_mount_all(h, xmnt, &config, &vl, &yanglib) < 0)
|
|
goto done;
|
|
if (yanglib == NULL)
|
|
continue;
|
|
if ((ret = xml_bind_yang0(h, yanglib, YB_MODULE, yspec, xerr)) < 0)
|
|
goto done;
|
|
if (ret == 0)
|
|
goto fail;
|
|
if (xml_addsub(xmnt, yanglib) < 0)
|
|
goto done;
|
|
yanglib = NULL;
|
|
}
|
|
retval = 1;
|
|
done:
|
|
if (cvv)
|
|
cvec_free(cvv);
|
|
if (cb)
|
|
cbuf_free(cb);
|
|
return retval;
|
|
fail:
|
|
retval = 0;
|
|
goto done;
|
|
}
|
|
|
|
/*! Get schema mount-point state according to RFC 8528
|
|
*
|
|
* @param[in] h Clixon 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
|
|
yang_schema_mount_statedata(clixon_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;
|
|
// clixon_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){
|
|
clixon_err(OE_XML, errno, "cbuf_new");
|
|
goto done;
|
|
}
|
|
cprintf(cb, "<schema-mounts xmlns=\"%s\">", 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){
|
|
clixon_err(OE_YANG, 0, "mount-point extension must have label");
|
|
goto done;
|
|
}
|
|
label = cv_string_get(cv1);
|
|
cprintf(cb, "<mount-point>");
|
|
cprintf(cb, "<module>%s</module>", yang_argument_get(ymod));
|
|
cprintf(cb, "<label>%s</label>", label);
|
|
cprintf(cb, "<inline/>");
|
|
cprintf(cb, "</mount-point>");
|
|
}
|
|
cprintf(cb, "</schema-mounts>");
|
|
if ((ret = clixon_xml_parse_string(cbuf_get(cb), YB_MODULE, yspec, &x1, xerr)) < 0)
|
|
goto done;
|
|
if (ret == 0)
|
|
goto fail;
|
|
if (xpath_first(x1, nsc, "%s", xpath) != NULL){
|
|
if ((ret = netconf_trymerge(x1, yspec, xret)) < 0)
|
|
goto done;
|
|
if (ret == 0)
|
|
goto fail;
|
|
}
|
|
}
|
|
/* Find mount-points and return yang-library state */
|
|
if (yang_schema_mount_statedata_yanglib(h, 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;
|
|
}
|
|
|
|
/*! Statistics about mountpoints
|
|
*
|
|
* @param[in] h Clixon handle
|
|
* @param[in] xtop Top XML node
|
|
* @param[in] modules
|
|
* @param[in] cb
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
* @see yang_schema_mount_statedata
|
|
*/
|
|
int
|
|
yang_schema_mount_statistics(clixon_handle h,
|
|
cxobj *xtop,
|
|
int modules,
|
|
cbuf *cb)
|
|
{
|
|
int retval = -1;
|
|
cvec *cvv = NULL;
|
|
cg_var *cv;
|
|
cxobj *xmnt;
|
|
yang_stmt *yspec;
|
|
yang_stmt *ym;
|
|
int ret;
|
|
char *xpath = NULL;
|
|
uint64_t nr;
|
|
size_t sz;
|
|
|
|
if (yang_mount_xtop2xmnt(xtop, &cvv) < 0)
|
|
goto done;
|
|
cv = NULL;
|
|
while ((cv = cvec_each(cvv, cv)) != NULL) {
|
|
if ((xmnt = cv_void_get(cv)) == NULL)
|
|
continue;
|
|
if ((ret = xml_yang_mount_get(h, xmnt, NULL, &yspec)) < 0)
|
|
goto done;
|
|
if (ret == 0)
|
|
continue;
|
|
if (xml2xpath(xmnt, NULL, 1, 0, &xpath) < 0)
|
|
goto done;
|
|
cprintf(cb, "<module-set><name>mountpoint: ");
|
|
xml_chardata_cbuf_append(cb, 0, xpath);
|
|
cprintf(cb, "</name>");
|
|
nr = 0; sz = 0;
|
|
if (yang_stats(yspec, &nr, &sz) < 0)
|
|
goto done;
|
|
cprintf(cb, "<nr>%" PRIu64 "</nr><size>%zu</size>", nr, sz);
|
|
if (modules){
|
|
ym = NULL;
|
|
while ((ym = yn_each(yspec, ym)) != NULL) {
|
|
cprintf(cb, "<module><name>%s</name>", yang_argument_get(ym));
|
|
nr = 0; sz = 0;
|
|
if (yang_stats(ym, &nr, &sz) < 0)
|
|
goto done;
|
|
cprintf(cb, "<nr>%" PRIu64 "</nr><size>%zu</size>", nr, sz);
|
|
cprintf(cb, "</module>");
|
|
}
|
|
}
|
|
cprintf(cb, "</module-set>");
|
|
if (xpath){
|
|
free(xpath);
|
|
xpath = NULL;
|
|
}
|
|
}
|
|
retval = 0;
|
|
done:
|
|
if (xpath)
|
|
free(xpath);
|
|
if (cvv)
|
|
cvec_free(cvv);
|
|
return retval;
|
|
}
|
|
|
|
#ifdef YANG_SCHEMA_CMP_KLUDGE
|
|
/*! XXX: This comparison is kludgy since xyanglib is not proper RFC8525
|
|
* XXX: it should be enough to compare xyanglib with xmodst
|
|
* @param[in] h CLixon handle
|
|
* @param[in] xyanglib Only modules
|
|
* @param[in] xmodst Has submodules
|
|
* @retval 1 Equal
|
|
* @retval 0 Found
|
|
* @retval -1 Error
|
|
*/
|
|
static int
|
|
yang_schema_cmp_kludge(clixon_handle h,
|
|
cxobj *xyanglib,
|
|
cxobj *xmodst)
|
|
{
|
|
int retval = -1;
|
|
cxobj *xy1;
|
|
cxobj *xy2;
|
|
cxobj *x1;
|
|
cxobj *x2;
|
|
char *name;
|
|
char *revision1;
|
|
char *revision2;
|
|
int nr1;
|
|
int nr2;
|
|
|
|
if ((xy1 = xml_find_type(xyanglib, NULL, "module-set", CX_ELMNT)) == NULL ||
|
|
(xy2 = xml_find_type(xmodst, NULL, "module-set", CX_ELMNT)) == NULL)
|
|
goto noteq;
|
|
nr1 = 0;
|
|
x1 = NULL;
|
|
while ((x1 = xml_child_each(xy1, x1, CX_ELMNT)) != NULL) {
|
|
if (strcmp(xml_name(x1), "module"))
|
|
continue;
|
|
if ((name = xml_find_body(x1, "name")) == NULL){
|
|
clixon_debug(CLIXON_DBG_YANG, "no name");
|
|
goto noteq;
|
|
}
|
|
revision1 = xml_find_body(x1, "revision");
|
|
if ((x2 = xpath_first(xy2, NULL, "module[name='%s']", name)) == NULL){
|
|
if ((x2 = xpath_first(xy2, NULL, "module/submodule[name='%s']", name)) == NULL){
|
|
clixon_debug(CLIXON_DBG_YANG, "name mismatch %s\n", name);
|
|
goto noteq;
|
|
}
|
|
}
|
|
revision2 = xml_find_body(x2, "revision");
|
|
if (clicon_strcmp(revision1, revision2) != 0){
|
|
clixon_debug(CLIXON_DBG_YANG, "revision mismatch %s %s\n", revision1, revision2);
|
|
goto noteq;
|
|
}
|
|
nr1++;
|
|
}
|
|
nr2 = 0;
|
|
x2 = NULL;
|
|
while ((x2 = xml_child_each(xy2, x2, CX_ELMNT)) != NULL) {
|
|
if (strcmp(xml_name(x2), "module"))
|
|
continue;
|
|
if ((name = xml_find_body(x2, "name")) == NULL){
|
|
clixon_debug(CLIXON_DBG_YANG, "no name");
|
|
goto noteq;
|
|
}
|
|
revision2 = xml_find_body(x2, "revision");
|
|
if ((x1 = xpath_first(xy1, NULL, "module[name='%s']", name)) == NULL){
|
|
#ifdef YANG_SCHEMA_MOUNT_YANG_LIB_FORCE
|
|
/* Skip ietf-yang-library and imports since they are force-loaded in
|
|
* yang_lib2yspec
|
|
*/
|
|
if (strcmp(name, "ietf-yang-library") == 0 ||
|
|
strcmp(name, "ietf-yang-types") == 0 ||
|
|
strcmp(name, "ietf-inet-types") == 0 ||
|
|
strcmp(name, "ietf-datastores") == 0){
|
|
continue;
|
|
}
|
|
#endif
|
|
if ((x1 = xpath_first(xy1, NULL, "module/submodule[name='%s']", name)) == NULL){
|
|
clixon_debug(CLIXON_DBG_YANG, "name mismatch %s\n", name);
|
|
goto noteq;
|
|
}
|
|
}
|
|
revision1 = xml_find_body(x1, "revision");
|
|
if (clicon_strcmp(revision1, revision2) != 0){
|
|
clixon_debug(CLIXON_DBG_YANG, "revision mismatch %s %s\n", revision1, revision2);
|
|
goto noteq;
|
|
}
|
|
nr2++;
|
|
}
|
|
if (nr1 != nr2){
|
|
clixon_debug(CLIXON_DBG_YANG, "nr mismatch %d %d", nr1, nr2);
|
|
goto noteq;
|
|
}
|
|
retval = 1;
|
|
done:
|
|
return retval;
|
|
noteq:
|
|
retval = 0;
|
|
goto done;
|
|
}
|
|
#endif // KLUDGE
|
|
|
|
/*! Given xml mount-point node, find existing mountpoint
|
|
*
|
|
* 1. Get modstate (xyanglib) of node: xyanglib, by querying backend state (via callback)
|
|
* XXX this xyanglib is not proper RFC8525, submodules appear as modules WHY?
|
|
* 2. Loop through other existing mountpoints of same YANG (dont consider other YANG mtpoints):
|
|
* 3. Get yspec of mountpoint.
|
|
* 4: Get RFC 8525 module-state of yspec (proper RFC 8525)
|
|
* 5: Compare to given modstate
|
|
* XXX: This comparison is kludgy since xyanglib is not proper RFC8525
|
|
* 6. If equal, then use existing yspec
|
|
* 7. Otherwise create new and parse yspec
|
|
* 8. set as new
|
|
* @param[in] h Clixon handle
|
|
* @param[in] xt XML tree node
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*/
|
|
static int
|
|
yang_schema_find_share(clixon_handle h,
|
|
cxobj *xt,
|
|
cxobj *xyanglib,
|
|
yang_stmt **yspecp)
|
|
{
|
|
int retval = -1;
|
|
yang_stmt *yt;
|
|
cvec *cvv;
|
|
cg_var *cv;
|
|
yang_stmt *yspec1 = NULL;
|
|
cbuf *cb = NULL;
|
|
cxobj *xmodst = NULL;
|
|
int ret;
|
|
|
|
yt = xml_spec(xt);
|
|
if ((cvv = yang_cvec_get(yt)) != NULL){
|
|
if ((cb = cbuf_new()) == NULL){
|
|
clixon_err(OE_UNIX, 0, "clicon buffer");
|
|
goto done;
|
|
}
|
|
/* 2. Loop through other existing mountpoints of same YANG */
|
|
cv = NULL;
|
|
while ((cv = cvec_each(cvv, cv)) != NULL) {
|
|
/* 3. Get yspec of mountpoint */
|
|
if ((yspec1 = cv_void_get(cv)) == NULL)
|
|
continue;
|
|
/* 4: Get RFC 8525 module-state of yspec */
|
|
cbuf_reset(cb);
|
|
/* Build a cb string: <modules-state>... */
|
|
if (yang_modules_state_build(h, yspec1, "4242", 1, 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, yspec1, &xmodst, NULL) < 0)
|
|
goto done;
|
|
/* 5: Compare to given modstate
|
|
*/
|
|
if (xml_rootchild(xmodst, 0, &xmodst) < 0)
|
|
goto done;
|
|
if ((ret = yang_schema_cmp_kludge(h, xyanglib, xmodst)) < 0)
|
|
goto done;
|
|
if (ret == 1){ /* equal */
|
|
*yspecp = yspec1;
|
|
break;
|
|
}
|
|
if (xmodst){
|
|
xml_free(xmodst);
|
|
xmodst = NULL;
|
|
}
|
|
} /* while */
|
|
}
|
|
retval = 0;
|
|
done:
|
|
if (xmodst)
|
|
xml_free(xmodst);
|
|
if (cb)
|
|
cbuf_free(cb);
|
|
return retval;
|
|
}
|
|
|
|
/*! Get yanglib from user plugin callback, parse it and mount it
|
|
*
|
|
* Optionally check for shared yspec
|
|
* @param[in] h Clixon handle
|
|
* @param[in] xt XML tree node
|
|
* @retval 1 OK
|
|
* @retval 0 No yanglib or problem when parsing yanglib
|
|
* @retval -1 Error
|
|
*/
|
|
int
|
|
yang_schema_yanglib_parse_mount(clixon_handle h,
|
|
cxobj *xt)
|
|
{
|
|
int retval = -1;
|
|
cxobj *xyanglib = NULL;
|
|
yang_stmt *yspec = NULL;
|
|
int ret;
|
|
|
|
/* 1. Get modstate (xyanglib) of node: xyanglib, by querying backend state (via callback)
|
|
* XXX this xyanglib is not proper RFC8525, submodules appear as modules WHY?
|
|
*/
|
|
if (clixon_plugin_yang_mount_all(h, xt, NULL, NULL, &xyanglib) < 0)
|
|
goto done;
|
|
if (xyanglib == NULL)
|
|
goto anydata;
|
|
/* Optimization: find equal yspec from other mount-point */
|
|
if (clicon_option_bool(h, "CLICON_YANG_SCHEMA_MOUNT_SHARE")) {
|
|
if (yang_schema_find_share(h, xt, xyanglib, &yspec) < 0)
|
|
goto done;
|
|
}
|
|
if (yspec == NULL){
|
|
/* Parse it and set mount-point */
|
|
if ((yspec = yspec_new()) == NULL)
|
|
goto done;
|
|
if ((ret = yang_lib2yspec(h, xyanglib, yspec)) < 0)
|
|
goto done;
|
|
if (ret == 0)
|
|
goto anydata;
|
|
}
|
|
if (xml_yang_mount_set(h, xt, yspec) < 0)
|
|
goto done;
|
|
yspec = NULL;
|
|
retval = 1;
|
|
done:
|
|
if (yspec)
|
|
ys_free(yspec);
|
|
if (xyanglib)
|
|
xml_free(xyanglib);
|
|
return retval;
|
|
anydata: // Treat as anydata
|
|
retval = 0;
|
|
goto done;
|
|
}
|
|
|
|
/*! Check if XML node is mount-point and return matching YANG child
|
|
*
|
|
* @param[in] h Clixon handle
|
|
* @param[in] x1 XML node
|
|
* @param[in] x1c A child of x1
|
|
* @param[out] yc YANG child
|
|
* @retval 1 OK, yc contains child
|
|
* @retval 0 No such child
|
|
* @retval -1 Error
|
|
* XXX maybe not needed
|
|
*/
|
|
int
|
|
yang_schema_get_child(clixon_handle h,
|
|
cxobj *x1,
|
|
cxobj *x1c,
|
|
yang_stmt **yc)
|
|
{
|
|
int retval = -1;
|
|
yang_stmt *yspec1;
|
|
yang_stmt *ymod1 = NULL;
|
|
char *x1cname;
|
|
int ret;
|
|
|
|
x1cname = xml_name(x1c);
|
|
if ((ret = xml_yang_mount_get(h, x1, NULL, &yspec1)) < 0)
|
|
goto done;
|
|
if (ret == 1 && yspec1 != NULL){
|
|
if (ys_module_by_xml(yspec1, x1c, &ymod1) <0)
|
|
goto done;
|
|
if (ymod1 != NULL)
|
|
*yc = yang_find_datanode(ymod1, x1cname);
|
|
else{ /* It is in fact a mountpoint, there is a yang mount, but it is not found */
|
|
goto fail;
|
|
}
|
|
}
|
|
retval = 1;
|
|
done:
|
|
return retval;
|
|
fail:
|
|
retval = 0;
|
|
goto done;
|
|
}
|