* New "general-purpose" datastore upgrade callback added which i called once on startup, intended for lo

w-level general upgrades and as a complement to module-specific upgrade.
  * Called on startup after initial XML parsing, but before module-specific upgrades
  * Enabled by definign the `.ca_datastore_upgrade`
  * [General-purpose upgrade documentation](https://clixon-docs.readthedocs.io/en/latest/backend.html#ge
neral-purpose)
* JSON parse error messages change from ` on line x: syntax error,..` to `json_parse: line x: syntax err
or`
* Unknown-element error message is more descriptive, eg from `namespace is: urn:example:clixon` to: `Fai
led to find YANG spec of XML node: x with parent: xp in namespace urn:example:clixon`.
* C-API parse and validation API more capable
  * `xml_spec_populate` family of functions extended with three-value return values
    * -1: error, 0: parse OK, 1: parse and YANG binding OK.
  * `xml_parse` and `json_parse` API changes
    * Three value returns: -1: error, 0: parse OK, 1: parse and YANG binding OK.
    * Extended `xml_parse_file2` and `xml_parse_string2` extended API functions with all options available.
      * New concept called `yang_bind` that defines how XML symbols are bound to YANG after parsing
    * Existing API same except `xml_parse_file` `endtag` argument moved to `xml_parse_file2`
* C-API: Added instrumentation: `xml_size` and `xml_stats_get`.
* Fixed: Enabling modstate (CLICON_XMLDB_MODSTATE), changing a revision on a yang, and restarting made the backend daemon exit at start (thanks Matt)
  * Also: ensure to load `ietf-yang-library.yang ` if CLICON_XMLDB_MODSTATE is set
This commit is contained in:
Olof hagsand 2020-02-20 14:00:01 +01:00
parent d665992f7c
commit 9fa5e216c4
80 changed files with 2204 additions and 755 deletions

View file

@ -68,6 +68,7 @@
#include "clixon_xml.h"
#include "clixon_xml_sort.h"
#include "clixon_options.h"
#include "clixon_yang_module.h"
#include "clixon_plugin.h"
#include "clixon_xpath_ctx.h"
#include "clixon_xpath.h"

View file

@ -69,12 +69,11 @@
#include "clixon_file.h"
#include "clixon_yang.h"
#include "clixon_xml.h"
#include "clixon_yang_module.h"
#include "clixon_plugin.h"
#include "clixon_options.h"
#include "clixon_data.h"
#include "clixon_yang_module.h"
#include "clixon_datastore.h"
#include "clixon_datastore_write.h"
#include "clixon_datastore_read.h"

View file

@ -3,6 +3,7 @@
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
This file is part of CLIXON.
@ -245,6 +246,7 @@ text_read_modstate(clicon_handle h,
/* 1) There is no modules-state info in the file */
}
else if (xmcache && msd){
msd->md_status = 1; /* There is module state in the file */
/* Create diff trees */
if (xml_parse_string("<modules-state xmlns=\"urn:ietf:params:xml:ns:yang:ietf-yang-library\"/>", yspec, &msd->md_del) < 0)
goto done;
@ -257,6 +259,12 @@ text_read_modstate(clicon_handle h,
/* 3) For each module state m in the file */
while ((xm = xml_child_each(xmodst, xm, CX_ELMNT)) != NULL) {
if (strcmp(xml_name(xm), "module-set-id") == 0){
if (xml_body(xm) && (msd->md_set_id = strdup(xml_body(xm))) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
}
if (strcmp(xml_name(xm), "module"))
continue; /* ignore other tags, such as module-set-id */
if ((name = xml_find_body(xm, "name")) == NULL)
@ -342,7 +350,7 @@ xmldb_readfile(clicon_handle h,
if ((ret = json_parse_file(fd, yspec, &x0, NULL)) < 0) /* XXX: ret == 0*/
goto done;
}
else if ((xml_parse_file(fd, "</config>", yspec, &x0)) < 0)
else if ((xml_parse_file2(fd, YB_TOP, yspec, "</config>", &x0, NULL)) < 0)
goto done;
/* Always assert a top-level called "config".
@ -548,7 +556,6 @@ xmldb_get_cache(clicon_handle h,
/* XXX where should we apply default values once? */
if (xml_apply(x1t, CX_ELMNT, xml_default, h) < 0)
goto done;
/* Copy the matching parts of the (relevant) XML tree.
* If cache was empty, also update to datastore cache
*/
@ -671,7 +678,7 @@ xmldb_get(clicon_handle h,
* The tree returned may be the actual cache, therefore calls for cleaning and
* freeing tree must be made after use.
* @param[in] h Clicon handle
* @param[in] db Name of database to search in (filename including dir path
* @param[in] db Name of datastore, eg "running"
* @param[in] nsc External XML namespace context, or NULL
* @param[in] xpath String with XPATH syntax. or NULL for all
* @param[in] copy Force copy. Overrides cache_zerocopy -> cache

View file

@ -2,7 +2,8 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2020 Olof Hagsand
Copyright (C) 2009-2019 Olof Hagsand
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
This file is part of CLIXON.
@ -203,7 +204,7 @@ check_identityref(cxobj *x0,
}
/*! Modify a base tree x0 with x1 with yang spec y according to operation op
* @param[in] th Datastore text handle
* @param[in] h Clicon handle
* @param[in] x0 Base xml tree (can be NULL in add scenarios)
* @param[in] y0 Yang spec corresponding to xml-node x0. NULL if x0 is NULL
* @param[in] x0p Parent of x0
@ -616,7 +617,7 @@ text_modify(clicon_handle h,
} /* text_modify */
/*! Modify a top-level base tree x0 with modification tree x1
* @param[in] th Datastore text handle
* @param[in] h Clicon handle
* @param[in] x0 Base xml tree (can be NULL in add scenarios)
* @param[in] x1 XML tree which modifies base
* @param[in] yspec Top-level yang spec (if y is NULL)
@ -847,7 +848,7 @@ xmldb_put(clicon_handle h,
clicon_err(OE_YANG, ENOENT, "No yang spec");
goto done;
}
if (x1 && strcmp(xml_name(x1),"config")!=0){
if (x1 && strcmp(xml_name(x1), "config") != 0){
clicon_err(OE_XML, 0, "Top-level symbol of modification tree is %s, expected \"config\"",
xml_name(x1));
goto done;

View file

@ -3,6 +3,7 @@
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
This file is part of CLIXON.
@ -57,6 +58,7 @@
#include "clixon_err.h"
#include "clixon_queue.h"
#include "clixon_string.h"
#include "clixon_log.h"
#include "clixon_file.h"
/*! qsort "compar" for directory alphabetically sorting, see qsort(3)
@ -173,12 +175,12 @@ int
clicon_file_copy(char *src,
char *target)
{
int inF = 0, ouF = 0;
int err = 0;
char line[512];
int bytes;
int retval = -1;
int inF = 0, ouF = 0;
int err = 0;
char line[512];
int bytes;
struct stat st;
int retval = -1;
if (stat(src, &st) != 0){
clicon_err(OE_UNIX, errno, "stat");

View file

@ -2,7 +2,8 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2020 Olof Hagsand
Copyright (C) 2009-2019 Olof Hagsand
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
This file is part of CLIXON.
@ -34,6 +35,7 @@
* JSON support functions.
* JSON syntax is according to:
* http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf
* RFC 7951 JSON Encoding of Data Modeled with YANG
*/
#ifdef HAVE_CONFIG_H
@ -421,7 +423,7 @@ json2xml_decode(cxobj *x,
goto done;
if (ytype){
if (strcmp(yang_argument_get(ytype),"identityref")==0){
if (strcmp(yang_argument_get(ytype), "identityref")==0){
if ((ret = json2xml_decode_identityref(x, y, xerr)) < 0)
goto done;
if (ret == 0)
@ -1062,34 +1064,25 @@ json_xmlns_translate(yang_stmt *yspec,
{
int retval = -1;
yang_stmt *ymod;
char *namespace0;
char *namespace;
char *prefix = NULL;
char *modname = NULL;
char *prefix;
cxobj *xc;
int ret;
prefix = xml_prefix(x); /* prefix is here module name */
if (prefix != NULL){
if ((ymod = yang_find_module_by_name(yspec, prefix)) == NULL){
if ((modname = xml_prefix(x)) != NULL){ /* prefix is here module name */
if ((ymod = yang_find_module_by_name(yspec, modname)) == NULL){
if (xerr &&
netconf_unknown_namespace_xml(xerr, "application",
prefix,
modname,
"No yang module found corresponding to prefix") < 0)
goto done;
goto fail;
}
namespace = yang_find_mynamespace(ymod);
/* Get existing default namespace in tree */
if (xml2ns(x, NULL, &namespace0) < 0)
prefix = yang_find_myprefix(ymod);
if (xml_namespace_change(x, namespace, prefix) < 0)
goto done;
/* Set xmlns="" default namespace attribute (if diff from default) */
if (namespace0 == NULL ||
strcmp(namespace0, namespace)){
if (xmlns_set(x, NULL, namespace) < 0)
goto done;
/* and remove prefix */
xml_prefix_set(x, NULL);
}
}
xc = NULL;
while ((xc = xml_child_each(x, xc, CX_ELMNT)) != NULL){
@ -1112,12 +1105,12 @@ json_xmlns_translate(yang_stmt *yspec,
* are split and interpreted as in RFC7951
*
* @param[in] str Input string containing JSON
* @param[in] yb How to bind yang to XML top-level when parsing
* @param[in] yspec If set, also do yang validation
* @param[in] name Log string, typically filename
* @param[out] xt XML top of tree typically w/o children on entry (but created)
* @param[out] xerr Reason for invalid returned as netconf err msg
*
* @see _xml_parse for XML variant
* @see _xml_parse for XML variant
* @retval 1 OK and valid
* @retval 0 Invalid (only if yang spec)
* @retval -1 Error with clicon_err called
@ -1125,57 +1118,88 @@ json_xmlns_translate(yang_stmt *yspec,
* @see RFC 7951
*/
static int
json_parse(char *str,
yang_stmt *yspec,
const char *name,
cxobj *xt,
cxobj **xerr)
_json_parse(char *str,
enum yang_bind yb,
yang_stmt *yspec,
cxobj *xt,
cxobj **xerr)
{
int retval = -1;
struct clicon_json_yacc_arg jy = {0,};
int ret;
clicon_debug(1, "%s", __FUNCTION__);
int retval = -1;
clixon_json_yacc jy = {0,};
int ret;
cxobj *x;
cbuf *cberr = NULL;
int i;
clicon_debug(1, "%s %s", __FUNCTION__, str);
jy.jy_parse_string = str;
jy.jy_name = name;
jy.jy_linenum = 1;
jy.jy_current = xt;
jy.jy_xtop = xt;
if (json_scan_init(&jy) < 0)
goto done;
if (json_parse_init(&jy) < 0)
goto done;
if (clixon_json_parseparse(&jy) != 0) { /* yacc returns 1 on error */
clicon_log(LOG_NOTICE, "JSON error: %s on line %d", name, jy.jy_linenum);
clicon_log(LOG_NOTICE, "JSON error: line %d", jy.jy_linenum);
if (clicon_errno == 0)
clicon_err(OE_XML, 0, "JSON parser error with no error code (should not happen)");
goto done;
}
if (yspec){
/* Traverse new objects */
for (i = 0; i < jy.jy_xlen; i++) {
x = jy.jy_xvec[i];
/* RFC 7951 Section 4: A namespace-qualified member name MUST be used for all
* members of a top-level JSON object
*/
if (yspec && xml_prefix(x) == NULL){
if ((cberr = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(cberr, "Top-level JSON object %s is not qualified with namespace which is a MUST according to RFC 7951", xml_name(x));
if (netconf_malformed_message_xml(xerr, cbuf_get(cberr)) < 0)
goto done;
goto fail;
}
/* Names are split into name/prefix, but now add namespace info */
if ((ret = json_xmlns_translate(yspec, xt, xerr)) < 0)
if ((ret = json_xmlns_translate(yspec, x, xerr)) < 0)
goto done;
if (ret == 0)
goto fail;
/* Populate, ie associate xml nodes with yang specs.
* XXX But this wrong if xt is not on top-level, can give false positives.
/* Now assign yang stmts to each XML node
* XXX should be xml_spec_populate0_parent() sometimes.
*/
if (xml_apply0(xt, CX_ELMNT, xml_spec_populate, yspec) < 0){
goto done;
switch (yb){
case YB_NONE:
break;
case YB_PARENT:
if (xml_spec_populate0_parent(x, NULL) < 0)
goto done;
break;
case YB_TOP:
if (xml_spec_populate0(x, yspec, NULL) < 0)
goto done;
break;
}
if (xml_apply0(xt, CX_ELMNT, xml_sort, NULL) < 0)
goto done;
/* Now find leafs with identityrefs (+transitive) and translate
* prefixes in values to XML namespaces */
if ((ret = json2xml_decode(xt, xerr)) < 0)
if ((ret = json2xml_decode(x, xerr)) < 0)
goto done;
if (ret == 0) /* XXX necessary? */
goto fail;
}
if (xml_apply0(xt, CX_ELMNT, xml_sort, NULL) < 0)
goto done;
retval = 1;
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
if (cberr)
cbuf_free(cberr);
json_parse_exit(&jy);
json_scan_exit(&jy);
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
if (jy.jy_xvec)
free(jy.jy_xvec);
return retval;
fail: /* invalid */
retval = 0;
@ -1185,7 +1209,7 @@ json_parse(char *str,
/*! Parse string containing JSON and return an XML tree
*
* @param[in] str String containing JSON
* @param[in] yspec Yang specification, or NULL
* @param[in] yspec Yang specification, mandatory to make module->xmlns translation
* @param[in,out] xt Top object, if not exists, on success it is created with name 'top'
* @param[out] xerr Reason for invalid returned as netconf err msg
*
@ -1208,11 +1232,23 @@ json_parse_str(char *str,
cxobj **xt,
cxobj **xerr)
{
enum yang_bind yb = YB_PARENT;
clicon_debug(1, "%s", __FUNCTION__);
if (*xt == NULL)
if (xt==NULL){
clicon_err(OE_XML, EINVAL, "xt is NULL");
return -1;
}
if (*xt == NULL){
yb = YB_TOP; /* ad-hoc #1 */
if ((*xt = xml_new("top", NULL, NULL)) == NULL)
return -1;
return json_parse(str, yspec, "", *xt, xerr);
}
else{
if (xml_spec(*xt) == NULL)
yb = YB_TOP; /* ad-hoc #2 */
}
return _json_parse(str, yb, yspec, *xt, xerr);
}
/*! Read a JSON definition from file and parse it into a parse-tree.
@ -1263,7 +1299,14 @@ json_parse_file(int fd,
char *ptr;
char ch;
int len = 0;
enum yang_bind yb = YB_PARENT;
if (xt==NULL){
clicon_err(OE_XML, EINVAL, "xt is NULL");
return -1;
}
if (*xt==NULL)
yb = YB_TOP;
if ((jsonbuf = malloc(jsonbuflen)) == NULL){
clicon_err(OE_XML, errno, "malloc");
goto done;
@ -1282,7 +1325,7 @@ json_parse_file(int fd,
if ((*xt = xml_new(JSON_TOP_SYMBOL, NULL, NULL)) == NULL)
goto done;
if (len){
if ((ret = json_parse(ptr, yspec, "", *xt, xerr)) < 0)
if ((ret = _json_parse(ptr, yb, yspec, *xt, xerr)) < 0)
goto done;
if (ret == 0)
goto fail;

View file

@ -2,7 +2,8 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2020 Olof Hagsand
Copyright (C) 2009-2019 Olof Hagsand
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
This file is part of CLIXON.
@ -39,13 +40,16 @@
* Types
*/
struct clicon_json_yacc_arg{ /* XXX: mostly unrelevant */
const char *jy_name; /* Name of syntax (for error string) */
int jy_linenum; /* Number of \n in parsed buffer */
char *jy_parse_string; /* original (copy of) parse string */
void *jy_lexbuf; /* internal parse buffer from lex */
cxobj *jy_current;
struct clixon_json_yacc_arg{ /* XXX: mostly unrelevant */
int jy_linenum; /* Number of \n in parsed buffer */
char *jy_parse_string; /* original (copy of) parse string */
void *jy_lexbuf; /* internal parse buffer from lex */
cxobj *jy_xtop; /* cxobj top element (fixed) */
cxobj *jy_current; /* cxobj active element (changes with parse context) */
cxobj **jy_xvec; /* Vector of created top-level nodes (to know which are created) */
size_t jy_xlen; /* Length of jy_xvec */
};
typedef struct clixon_json_yacc_arg clixon_json_yacc;
/*
* Variables
@ -55,11 +59,11 @@ extern char *clixon_json_parsetext;
/*
* Prototypes
*/
int json_scan_init(struct clicon_json_yacc_arg *jy);
int json_scan_exit(struct clicon_json_yacc_arg *jy);
int json_scan_init(clixon_json_yacc *jy);
int json_scan_exit(clixon_json_yacc *jy);
int json_parse_init(struct clicon_json_yacc_arg *jy);
int json_parse_exit(struct clicon_json_yacc_arg *jy);
int json_parse_init(clixon_json_yacc *jy);
int json_parse_exit(clixon_json_yacc *jy);
int clixon_json_parselex(void *);
int clixon_json_parseparse(void *);

View file

@ -2,7 +2,8 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2020 Olof Hagsand
Copyright (C) 2009-2019 Olof Hagsand
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
This file is part of CLIXON.
@ -62,7 +63,7 @@
#define YY_NO_INPUT
/* typecast macro */
#define _JY ((struct clicon_json_yacc_arg *)_yy)
#define _JY ((clixon_json_yacc *)_yy)
#define MAXBUF 4*4*64*1024
@ -119,7 +120,7 @@ exp ({integer}|{real})[eE][+-]{integer}
/*! Initialize scanner.
*/
int
json_scan_init(struct clicon_json_yacc_arg *jy)
json_scan_init(clixon_json_yacc *jy)
{
BEGIN(START);
jy->jy_lexbuf = yy_scan_string (jy->jy_parse_string);
@ -136,7 +137,7 @@ json_scan_init(struct clicon_json_yacc_arg *jy)
* Even within Flex version 2.5 (this is assumed), freeing buffers is different.
*/
int
json_scan_exit(struct clicon_json_yacc_arg *jy)
json_scan_exit(clixon_json_yacc *jy)
{
yy_delete_buffer(jy->jy_lexbuf);
clixon_json_parselex_destroy(); /* modern */

View file

@ -2,7 +2,8 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2020 Olof Hagsand
Copyright (C) 2009-2019 Olof Hagsand
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
This file is part of CLIXON.
@ -99,7 +100,7 @@ object.
/* Here starts user C-code */
/* typecast macro */
#define _JY ((struct clicon_json_yacc_arg *)_jy)
#define _JY ((clixon_json_yacc *)_jy)
#define _YYERROR(msg) {clicon_err(OE_XML, 0, "YYERROR %s '%s' %d", (msg), clixon_json_parsetext, _JY->jy_linenum); YYERROR;}
@ -142,8 +143,7 @@ void
clixon_json_parseerror(void *_jy,
char *s)
{
clicon_err(OE_XML, 0, "%s on line %d: %s at or before: '%s'",
_JY->jy_name,
clicon_err(OE_XML, XMLPARSE_ERRNO, "json_parse: line %d: %s at or before: '%s'",
_JY->jy_linenum ,
s,
clixon_json_parsetext);
@ -151,14 +151,14 @@ clixon_json_parseerror(void *_jy,
}
int
json_parse_init(struct clicon_json_yacc_arg *jy)
json_parse_init(clixon_json_yacc *jy)
{
// clicon_debug_init(2, NULL);
return 0;
}
int
json_parse_exit(struct clicon_json_yacc_arg *jy)
json_parse_exit(clixon_json_yacc *jy)
{
return 0;
}
@ -167,8 +167,8 @@ json_parse_exit(struct clicon_json_yacc_arg *jy)
* Split name into prefix:name (extended JSON RFC7951)
*/
static int
json_current_new(struct clicon_json_yacc_arg *jy,
char *name)
json_current_new(clixon_json_yacc *jy,
char *name)
{
int retval = -1;
cxobj *x;
@ -183,6 +183,11 @@ json_current_new(struct clicon_json_yacc_arg *jy,
goto done;
if (prefix && xml_prefix_set(x, prefix) < 0)
goto done;
/* If topmost, add to top-list created list */
if (jy->jy_current == jy->jy_xtop){
if (cxvec_append(x, &jy->jy_xvec, &jy->jy_xlen) < 0)
goto done;
}
jy->jy_current = x;
retval = 0;
done:
@ -194,7 +199,7 @@ json_current_new(struct clicon_json_yacc_arg *jy,
}
static int
json_current_pop(struct clicon_json_yacc_arg *jy)
json_current_pop(clixon_json_yacc *jy)
{
if (jy->jy_current)
jy->jy_current = xml_parent(jy->jy_current);
@ -202,7 +207,7 @@ json_current_pop(struct clicon_json_yacc_arg *jy)
}
static int
json_current_clone(struct clicon_json_yacc_arg *jy)
json_current_clone(clixon_json_yacc *jy)
{
cxobj *xn;
@ -217,8 +222,8 @@ json_current_clone(struct clicon_json_yacc_arg *jy)
}
static int
json_current_body(struct clicon_json_yacc_arg *jy,
char *value)
json_current_body(clixon_json_yacc *jy,
char *value)
{
int retval = -1;
cxobj *xn;

View file

@ -3,7 +3,8 @@
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
Copyright (C) 2017-2020 Olof Hagsand
Copyright (C) 2017-2019 Olof Hagsand
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
This file is part of CLIXON.
@ -240,7 +241,7 @@ parse_configfile(clicon_handle h,
}
clicon_debug(2, "%s: Reading config file %s", __FUNCTION__, filename);
fd = fileno(f);
if (xml_parse_file(fd, "</clicon>", yspec, &xt) < 0)
if (xml_parse_file(fd, yspec, &xt) < 0)
goto done;
if (xml_child_nr(xt)==1 && xml_child_nr_type(xt, CX_BODY)==1){
clicon_err(OE_CFG, 0, "Config file %s: Expected XML but is probably old sh style", filename);

View file

@ -2,7 +2,8 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2020 Olof Hagsand
Copyright (C) 2009-2019 Olof Hagsand
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
This file is part of CLIXON.
@ -1484,8 +1485,8 @@ clixon_path_search(cxobj *xt,
* @code
* cxobj **xvec = NULL;
* size_t xlen;
* if (clixon_xml_find_api_path(x, &xvec, &xlen, "/symbol/%s", "foo") < 0)
* goto err;
* if (clixon_xml_find_api_path(x, yspec, &xvec, &xlen, "/symbol/%s", "foo") < 0)
* err;
* for (i=0; i<xlen; i++){
* xn = xvec[i];
* ...
@ -1547,9 +1548,9 @@ clixon_xml_find_api_path(cxobj *xt,
goto done;
}
/*! Given (instance-id) path and an XML tree, return matching xml node vector using stdarg
/*! Given (instance-id) path and XML tree, return matching xml node vector using stdarg
*
* Instance-identifier is a subset of XML Xpaths and defined in Yang, used in NACM for example.
* Instance-identifier is a subset of XML XPaths and defined in Yang, used in NACM for example.
* @param[in] xt Top xml-tree where to search
* @param[in] yt Yang statement of top symbol (can be yang-spec if top-level)
* @param[out] xvec Vector of xml-trees. Vector must be free():d after use
@ -1573,7 +1574,7 @@ clixon_xml_find_api_path(cxobj *xt,
* }
* free(xvec);
* @endcode
* @see xpath_vec for full XML Xpaths
* @see xpath_vec for full XML XPaths
* @see api_path_search for RESTCONF api-paths
* @see RFC7950 Sec 9.13
*/

View file

@ -2,7 +2,9 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
Copyright (C) 2017-2019 Olof Hagsand
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
This file is part of CLIXON.
@ -59,6 +61,7 @@
#include "clixon_handle.h"
#include "clixon_yang.h"
#include "clixon_xml.h"
#include "clixon_yang_module.h"
#include "clixon_plugin.h"
/* List of plugins XXX
@ -361,7 +364,7 @@ clixon_plugin_start(clicon_handle h)
if ((startfn = cp->cp_api.ca_start) == NULL)
continue;
if (startfn(h) < 0) {
clicon_debug(1, "plugin_start() failed");
clicon_debug(1, "%s() failed", __FUNCTION__);
return -1;
}
}
@ -384,7 +387,7 @@ clixon_plugin_exit(clicon_handle h)
if ((exitfn = cp->cp_api.ca_exit) == NULL)
continue;
if (exitfn(h) < 0) {
clicon_debug(1, "plugin_exit() failed");
clicon_debug(1, "%s() failed", __FUNCTION__);
return -1;
}
if (dlclose(cp->cp_handle) != 0) {
@ -425,7 +428,7 @@ clixon_plugin_auth(clicon_handle h,
if ((authfn = cp->cp_api.ca_auth) == NULL)
continue;
if ((retval = authfn(h, arg)) < 0) {
clicon_debug(1, "plugin_auth() failed");
clicon_debug(1, "%s() failed", __FUNCTION__);
return -1;
}
break;
@ -460,13 +463,48 @@ clixon_plugin_extension(clicon_handle h,
if ((extfn = cp->cp_api.ca_extension) == NULL)
continue;
if ((retval = extfn(h, yext, ys)) < 0) {
clicon_debug(1, "plugin_extension() failed");
clicon_debug(1, "%s() failed", __FUNCTION__);
return -1;
}
}
return retval;
}
/*! Call plugingeneral-purpose datastore upgrade in all plugins
*
* @param[in] h Clicon handle
* @param[in] db Name of datastore, eg "running", "startup" or "tmp"
* @param[in] xt XML tree. Upgrade this "in place"
* @param[in] msd Module-state diff, info on datastore module-state
* @retval -1 Error
* @retval 0 OK
* Upgrade datastore on load before or as an alternative to module-specific upgrading mechanism
* @param[in] h Clicon handle
* Call plugin start functions (if defined)
* @note Start functions used to have argc/argv. Use clicon_argv_get() instead
*/
int
clixon_plugin_datastore_upgrade(clicon_handle h,
char *db,
cxobj *xt,
modstate_diff_t *msd)
{
clixon_plugin *cp;
int i;
datastore_upgrade_t *repairfn;
for (i = 0; i < _clixon_nplugins; i++) {
cp = &_clixon_plugins[i];
if ((repairfn = cp->cp_api.ca_datastore_upgrade) == NULL)
continue;
if (repairfn(h, db, xt, msd) < 0) {
clicon_debug(1, "%s() failed", __FUNCTION__);
return -1;
}
}
return 0;
}
/*--------------------------------------------------------------------
* RPC callbacks for both client/frontend and backend plugins.
* RPC callbacks are explicitly registered in the plugin_init() function
@ -715,7 +753,7 @@ upgrade_callback_call(clicon_handle h,
int ret;
if (upgrade_cb_list == NULL)
return 0;
return 1;
uc = upgrade_cb_list;
do {
/* For matching an upgrade callback:
@ -753,3 +791,4 @@ upgrade_callback_call(clicon_handle h,
retval =0;
goto done;
}

View file

@ -3,7 +3,8 @@
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
Copyright (C) 2017-2020 Olof Hagsand
Copyright (C) 2017-2019 Olof Hagsand
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
This file is part of CLIXON.
@ -65,6 +66,7 @@
#include "clixon_xml.h"
#include "clixon_options.h"
#include "clixon_data.h"
#include "clixon_yang_module.h"
#include "clixon_plugin.h"
#include "clixon_string.h"
#include "clixon_xpath_ctx.h"
@ -73,13 +75,15 @@
#include "clixon_err.h"
#include "clixon_err_string.h"
#include "clixon_xml_nsctx.h"
#include "clixon_xml_map.h"
#include "clixon_xml_sort.h"
#include "clixon_netconf_lib.h"
#include "clixon_proto_client.h"
/*! Send internal netconf rpc from client to backend
* @param[in] h CLICON handle
* @param[in] msg Encoded message. Deallocate woth free
* @param[out] xret Return value from backend as xml tree. Free w xml_free
* @param[out] xret0 Return value from backend as xml tree. Free w xml_free
* @param[inout] sock0 If pointer exists, do not close socket to backend on success
* and return it here. For keeping a notify socket open
* @note sock0 is if connection should be persistent, like a notification/subscribe api
@ -96,7 +100,6 @@ clicon_rpc_msg(clicon_handle h,
int port;
char *retdata = NULL;
cxobj *xret = NULL;
yang_stmt *yspec;
#ifdef RPC_USERNAME_ASSERT
assert(strstr(msg->op_body, "username")!=NULL); /* XXX */
@ -135,8 +138,10 @@ clicon_rpc_msg(clicon_handle h,
clicon_debug(1, "%s retdata:%s", __FUNCTION__, retdata);
if (retdata){
yspec = clicon_dbspec_yang(h);
if (xml_parse_string(retdata, yspec, &xret) < 0)
/* Cannot populate xret here because need to know RPC name (eg "lock") in order to associate yang
* to reply.
*/
if (xml_parse_string2(retdata, YB_NONE, NULL, &xret, NULL) < 0)
goto done;
}
if (xret0){
@ -239,17 +244,33 @@ clicon_rpc_netconf_xml(clicon_handle h,
cxobj **xret,
int *sp)
{
int retval = -1;
cbuf *cb = NULL;
int retval = -1;
cbuf *cb = NULL;
cxobj *xname;
char *rpcname;
cxobj *xreply;
yang_stmt *yspec;
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
if ((xname = xml_child_i_type(xml, 0, 0)) == NULL){
clicon_err(OE_NETCONF, EINVAL, "Missing rpc name");
goto done;
}
rpcname = xml_name(xname); /* Store rpc name and use in yang binding after reply */
if (clicon_xml2cbuf(cb, xml, 0, 0, -1) < 0)
goto done;
if (clicon_rpc_netconf(h, cbuf_get(cb), xret, sp) < 0)
goto done;
if ((xreply = xml_find_type(*xret, NULL, "rpc-reply", CX_ELMNT)) != NULL &&
xml_find_type(xreply, NULL, "rpc-error", CX_ELMNT) == NULL){
yspec = clicon_dbspec_yang(h);
/* Here use rpc name to bind to yang */
if (xml_spec_populate_rpc_reply(xreply, rpcname, yspec, NULL) < 0)
goto done;
}
retval = 0;
done:
if (cb)
@ -318,7 +339,9 @@ clicon_rpc_generate_error(cxobj *xerr,
* if (nsc)
* xml_nsctx_free(nsc);
* @endcode
* @see clicon_rpc_get
* @see clicon_rpc_generate_error
* @note the netconf return message us yang populated, but returned data is not
*/
int
clicon_rpc_get_config(clicon_handle h,
@ -388,7 +411,6 @@ clicon_rpc_get_config(clicon_handle h,
return retval;
}
/*! Send database entries as XML to backend daemon
* @param[in] h CLICON handle
* @param[in] db Name of database
@ -650,6 +672,7 @@ clicon_rpc_unlock(clicon_handle h,
* @endcode
* @see clicon_rpc_get_config which is almost the same as with content=config, but you can also select dbname
* @see clicon_rpc_generate_error
* @note the netconf return message us yang populated, but returned data is not
*/
int
clicon_rpc_get(clicon_handle h,

View file

@ -3,7 +3,8 @@
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
Copyright (C) 2017-2020 Olof Hagsand
Copyright (C) 2017-2019 Olof Hagsand
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
This file is part of CLIXON.
@ -233,10 +234,10 @@ validate_identityref(cxobj *xt,
goto fail;
}
#if 0 /* Assume proper namespace, otherwise we assume module prefixes,
* see IDENTITYREF_KLUDGE
*/
{
/* Assume proper namespace, otherwise we assume module prefixes,
* see IDENTITYREF_KLUDGE
*/
if (0){
char *namespace;
yang_stmt *ymod;
yang_stmt *yspec;
@ -255,7 +256,7 @@ validate_identityref(cxobj *xt,
}
cprintf(cb, "%s:%s", yang_argument_get(ymod), id);
}
#else
#if 1
{
yang_stmt *ymod;
/* idref from prefix:id to module:id */
@ -1071,6 +1072,7 @@ xml_yang_validate_all(clicon_handle h,
int nr;
int ret;
cxobj *x;
cxobj *xp;
char *namespace = NULL;
cbuf *cb = NULL;
cvec *nsc = NULL;
@ -1085,13 +1087,16 @@ xml_yang_validate_all(clicon_handle h,
}
if (xml2ns(xt, xml_prefix(xt), &namespace) < 0)
goto done;
cprintf(cb, "Failed to find YANG spec of XML node: %s", xml_name(xt));
if ((xp = xml_parent(xt)) != NULL)
cprintf(cb, " with parent: %s", xml_name(xp));
if (namespace){
cprintf(cb, "namespace is: %s", namespace);
cprintf(cb, " in namespace: %s", namespace);
if (netconf_unknown_element_xml(xret, "application", xml_name(xt), cbuf_get(cb)) < 0)
goto done;
}
else
if (netconf_unknown_element_xml(xret, "application", xml_name(xt), NULL) < 0)
if (netconf_unknown_element_xml(xret, "application", xml_name(xt), cbuf_get(cb)) < 0)
goto done;
goto fail;
}

View file

@ -3,7 +3,8 @@
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
Copyright (C) 2017-2020 Olof Hagsand
Copyright (C) 2017-2019 Olof Hagsand
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
This file is part of CLIXON.
@ -45,11 +46,12 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <limits.h>
#include <stdint.h>
#include <assert.h>
/* cligen */
@ -165,6 +167,72 @@ xml_type2str(enum cxobj_type type)
return (char*)clicon_int2str(xsmap, type);
}
/* Stats */
uint64_t _stats_nr = 0;
/*! Get global statistics about XML objects
*/
int
xml_stats_get(uint64_t *nr)
{
if (nr)
*nr = _stats_nr;
return 0;
}
/*! Return the alloced memory of a single XML obj
* @param[in] x XML object
* @param[out] szp Size of this XML obj
* @retval 0 OK
* (baseline: 96 bytes per object on x86-64)
*/
static int
xml_size_one(cxobj *x,
size_t *szp)
{
size_t sz = 0;
sz += sizeof(struct xml);
if (x->x_name)
sz += strlen(x->x_name) + 1;
if (x->x_prefix)
sz += strlen(x->x_prefix) + 1;
sz += x->x_childvec_max*sizeof(struct xml*);
if (x->x_value_cb)
sz += cbuf_buflen(x->x_value_cb);
if (x->x_cv)
sz += cv_size(x->x_cv);
if (x->x_ns_cache)
sz += cvec_size(x->x_ns_cache);
if (szp)
*szp = sz;
return 0;
}
/*! Return the alloced memory of a XML obj tree recursively
* @param[in] x XML object
* @param[out] szp Size of this XML obj recursively
* @retval 0 OK
*/
size_t
xml_size(cxobj *xt,
size_t *szp)
{
size_t sz = 0;
cxobj *xc;
xml_size_one(xt, &sz);
if (szp)
*szp += sz;
xc = NULL;
while ((xc = xml_child_each(xt, xc, -1)) != NULL) {
xml_size(xc, &sz);
if (szp)
*szp += sz;
}
return 0;
}
/*
* Access functions
*/
@ -353,6 +421,10 @@ nscache_clear(cxobj *x)
* @param[out] namespace URI namespace (or NULL). Note pointer into xml tree
* @retval 0 OK
* @retval -1 Error
* @code
* if (xml2ns(xt, NULL, &namespace) < 0)
* err;
* @endcode
* @see xmlns_check
* @see xmlns_set cache is set
* @note, this function uses a cache.
@ -444,7 +516,7 @@ xmlns_set(cxobj *x,
* @param[out] prefixp Pointer to prefix if found
* @retval -1 Error
* @retval 0 No namespace found
* @retval 1 Namespace found
* @retval 1 Namespace found, prefix returned in prefixp
*/
int
xml2prefix(cxobj *xn,
@ -455,6 +527,7 @@ xml2prefix(cxobj *xn,
cxobj *xa = NULL;
cxobj *xp;
char *prefix = NULL;
char *xaprefix;
int ret;
if (nscache_get_prefix(xn, namespace, &prefix) == 1) /* found */
@ -471,7 +544,8 @@ xml2prefix(cxobj *xn,
}
}
/* xmlns:prefix=namespace */
else if (strcmp("xmlns", xml_prefix(xa)) == 0){
else if ((xaprefix=xml_prefix(xa)) != NULL &&
strcmp("xmlns", xaprefix) == 0){
if (strcmp(xml_value(xa), namespace) == 0){
prefix = xml_name(xa);
if (nscache_set(xn, prefix, namespace) < 0)
@ -837,10 +911,6 @@ xml_child_order(cxobj *xp,
}
/*! Iterator over xml children objects
*
* @note Never manipulate the child-list during operation or using the
* same object recursively, the function uses an internal field to remember the
* index used. It works as long as the same object is not iterated concurrently.
*
* @param[in] xparent xml tree node whose children should be iterated
* @param[in] xprev previous child, or NULL on init
@ -852,6 +922,9 @@ xml_child_order(cxobj *xp,
* }
* @endcode
* @note makes uses _x_vector_i:can be changed if list changed between calls
* @note Never manipulate the child-list during operation or using the
* same object recursively, the function uses an internal field to remember the
* index used. It works as long as the same object is not iterated concurrently.
*/
cxobj *
xml_child_each(cxobj *xparent,
@ -867,7 +940,7 @@ xml_child_each(cxobj *xparent,
xn = xparent->x_childvec[i];
if (xn == NULL)
continue;
if (type != CX_ERROR && xn->x_type != type)
if (type != CX_ERROR && xml_type(xn) != type)
continue;
break; /* this is next object after previous */
}
@ -994,6 +1067,7 @@ xml_new(char *name,
x->_x_i = xml_child_nr(xp)-1;
}
x->x_spec = yspec; /* Can be NULL */
_stats_nr++;
return x;
}
@ -1236,9 +1310,8 @@ xml_child_rm(cxobj *xp,
xp->x_childvec[i] = NULL;
xml_parent_set(xc, NULL);
xp->x_childvec_len--;
/* shift up, note same index i used but ok since we break */
for (; i<xp->x_childvec_len; i++)
xp->x_childvec[i] = xp->x_childvec[i+1];
if (i<xp->x_childvec_len)
memmove(&xp->x_childvec[i], &xp->x_childvec[i+1], (xp->x_childvec_len-i)*sizeof(cxobj*));
retval = 0;
done:
return retval;
@ -1655,6 +1728,7 @@ xml_free(cxobj *x)
if (x->x_ns_cache)
xml_nsctx_free(x->x_ns_cache);
free(x);
_stats_nr--;
return 0;
}
@ -1717,7 +1791,7 @@ clicon_xml2file(FILE *f,
xc = NULL;
/* print attributes only */
while ((xc = xml_child_each(x, xc, -1)) != NULL) {
switch (xc->x_type){
switch (xml_type(xc)){
case CX_ATTR:
if (clicon_xml2file(f, xc, level+1, prettyprint) <0)
goto done;
@ -1846,7 +1920,7 @@ clicon_xml2cbuf(cbuf *cb,
xc = NULL;
/* print attributes only */
while ((xc = xml_child_each(x, xc, -1)) != NULL)
switch (xc->x_type){
switch (xml_type(xc)){
case CX_ATTR:
if (clicon_xml2cbuf(cb, xc, level+1, prettyprint, -1) < 0)
goto done;
@ -1938,22 +2012,34 @@ xmltree2cbuf(cbuf *cb,
*
* Given a string containing XML, parse into existing XML tree and return
* @param[in] str Pointer to string containing XML definition.
* @param[in] yspec Yang specification or NULL
* @param[in] yb How to bind yang to XML top-level when parsing
* @param[in] yspec Yang specification (only if bind is TOP or CONFIG)
* @param[in,out] xtop Top of XML parse tree. Assume created. Holds new tree.
* @param[out] xerr Reason for failure (yang assignment not made)
* @retval 1 Parse OK and all yang assignment made
* @retval 0 Parse OK but yang assigment not made (or only partial) and xerr set
* @retval -1 Error with clicon_err called. Includes parse error
* @see xml_parse_file
* @see xml_parse_string
* @see xml_parse_va
* @see _json_parse
* @note special case is empty XML where the parser is not invoked.
*/
static int
_xml_parse(const char *str,
yang_stmt *yspec,
cxobj *xt)
_xml_parse(const char *str,
enum yang_bind yb,
yang_stmt *yspec,
cxobj *xt,
cxobj **xerr)
{
int retval = -1;
struct xml_parse_yacc_arg ya = {0,};
clixon_xml_yacc ya = {0,};
cxobj *x;
int ret;
int failed = 0; /* yang assignment */
int i;
clicon_debug(1, "%s %s", __FUNCTION__, str);
if (strlen(str) == 0)
return 0; /* OK */
if (xt == NULL){
@ -1964,35 +2050,109 @@ _xml_parse(const char *str,
clicon_err(OE_XML, errno, "strdup");
return -1;
}
ya.ya_xtop = xt;
ya.ya_xparent = xt;
ya.ya_yspec = yspec;
if (clixon_xml_parsel_init(&ya) < 0)
goto done;
if (clixon_xml_parseparse(&ya) != 0) /* yacc returns 1 on error */
goto done;
/* Purge all top-level body objects */
x = NULL;
while ((x = xml_find_type(xt, NULL, "body", CX_BODY)) != NULL)
xml_purge(x);
/* Verify namespaces after parsing */
if (xml_apply0(xt, CX_ELMNT, xml_localname_check, NULL) < 0)
goto done;
/* Sort the complete tree after parsing */
if (yspec){
/* Populate, ie associate xml nodes with yang specs */
if (xml_apply0(xt, CX_ELMNT, xml_spec_populate, yspec) < 0)
goto done;
/* Sort according to yang */
if (xml_apply0(xt, CX_ELMNT, xml_sort, NULL) < 0)
/* Traverse new objects */
for (i = 0; i < ya.ya_xlen; i++) {
x = ya.ya_xvec[i];
/* Verify namespaces after parsing */
if (xml_apply0(x, CX_ELMNT, xml_localname_check, NULL) < 0)
goto done;
/* Populate, ie associate xml nodes with yang specs
*/
switch (yb){
case YB_NONE:
break;
case YB_PARENT:
/* xt:n Has spec
* x: <a> <-- populate from parent
*/
if ((ret = xml_spec_populate0_parent(x, xerr)) < 0)
goto done;
if (ret == 0)
failed++;
break;
case YB_TOP:
/* xt:<top> nospec
* x: <a> <-- populate from modules
*/
#ifdef XMLDB_CONFIG_HACK
if (strcmp(xml_name(x),"config") == 0){
/* xt:<top> nospec
* x: <config>
* <a> <-- populate from modules
*/
if ((ret = xml_spec_populate(x, yspec, xerr)) < 0)
goto done;
}
else
#endif
if ((ret = xml_spec_populate0(x, yspec, xerr)) < 0)
goto done;
if (ret == 0)
failed++;
break;
}
}
retval = 0;
/* Sort the complete tree after parsing. Sorting is less meaningful if Yang not bound */
if (xml_apply0(xt, CX_ELMNT, xml_sort, NULL) < 0)
goto done;
retval = (failed==0) ? 1 : 0;
done:
clixon_xml_parsel_exit(&ya);
if (ya.ya_parse_string != NULL)
free(ya.ya_parse_string);
if (ya.ya_xvec)
free(ya.ya_xvec);
return retval;
}
/*! Read an XML definition from file and parse it into a parse-tree.
*
* @param[in] fd A file descriptor containing the XML file (as ASCII characters)
* @param[in] yspec Yang specification, or NULL
* @param[in,out] xt Pointer to XML parse tree. If empty, create.
* @retval 1 Parse OK and all yang assignment made
* @retval 0 Parse OK but yang assigment not made (or only partial)
* @retval -1 Error with clicon_err called. Includes parse error *
* @code
* cxobj *xt = NULL;
* int fd;
* fd = open(filename, O_RDONLY);
* xml_parse_file(fd, yspec, &xt);
* xml_free(xt);
* @endcode
* @see xml_parse_string
* @see xml_parse_va
* @note, If xt empty, a top-level symbol will be added so that <tree../> will be: <top><tree.../></tree></top>
* @note May block on file I/O
* @see xml_parse_file2 for a more advanced API
*/
int
xml_parse_file(int fd,
yang_stmt *yspec,
cxobj **xt)
{
enum yang_bind yb = YB_PARENT;
if (xt==NULL){
clicon_err(OE_XML, EINVAL, "xt is NULL");
return -1;
}
if (*xt==NULL)
yb = YB_TOP;
return xml_parse_file2(fd, yb, yspec, NULL, xt, NULL);
}
/*! FSM to detect substring
*/
static inline int
@ -2006,32 +2166,38 @@ FSM(char *tag,
return 0;
}
/*! Read an XML definition from file and parse it into a parse-tree.
/*! Read an XML definition from file and parse it into a parse-tree, advanced API
*
* @param[in] fd A file descriptor containing the XML file (as ASCII characters)
* @param[in] endtag Read until encounter "endtag" in the stream, or NULL
* @param[in] yspec Yang specification, or NULL
* @param[in,out] xt Pointer to XML parse tree. If empty, create.
* @retval 0 OK
* @retval -1 Error with clicon_err called
* @param[in] fd A file descriptor containing the XML file (as ASCII characters)
* @param[in] yb How to bind yang to XML top-level when parsing
* @param[in] yspec Yang specification (only if bind is TOP or CONFIG)
* @param[in] endtag Read until encounter "endtag" in the stream, or NULL
* @param[in,out] xt Pointer to XML parse tree. If empty, create.
* @retval 1 Parse OK and all yang assignment made
* @retval 0 Parse OK but yang assigment not made (or only partial) and xerr set
* @retval -1 Error with clicon_err called. Includes parse error
*
* @code
* cxobj *xt = NULL;
* cxobj *xerr = NULL;
* int fd;
* fd = open(filename, O_RDONLY);
* xml_parse_file(fd, "</config>", yspec, &xt);
* if ((ret = xml_parse_file2(fd, YB_TOP, yspec, "</config>", &xt, &xerr)) < 0)
* err;
* xml_free(xt);
* @endcode
* @see xml_parse_string
* @see xml_parse_va
* @see xml_parse_file
* @note, If xt empty, a top-level symbol will be added so that <tree../> will be: <top><tree.../></tree></top>
* @note May block on file I/O
*/
int
xml_parse_file(int fd,
char *endtag,
yang_stmt *yspec,
cxobj **xt)
xml_parse_file2(int fd,
enum yang_bind yb,
yang_stmt *yspec,
char *endtag,
cxobj **xt,
cxobj **xerr)
{
int retval = -1;
int ret;
@ -2043,6 +2209,7 @@ xml_parse_file(int fd,
int endtaglen = 0;
int state = 0;
int oldxmlbuflen;
int failed = 0;
if (endtag != NULL)
endtaglen = strlen(endtag);
@ -2069,8 +2236,10 @@ xml_parse_file(int fd,
if (*xt == NULL)
if ((*xt = xml_new(XML_TOP_SYMBOL, NULL, NULL)) == NULL)
goto done;
if (_xml_parse(ptr, yspec, *xt) < 0)
if ((ret = _xml_parse(ptr, yb, yspec, *xt, xerr)) < 0)
goto done;
if (ret == 0)
failed++;
break;
}
if (len>=xmlbuflen-1){ /* Space: one for the null character */
@ -2084,7 +2253,7 @@ xml_parse_file(int fd,
ptr = xmlbuf;
}
} /* while */
retval = 0;
retval = (failed==0) ? 1 : 0;
done:
if (retval < 0 && *xt){
free(*xt);
@ -2095,12 +2264,55 @@ xml_parse_file(int fd,
return retval;
}
/*! Read an XML definition from string and parse it into a parse-tree.
/*! Read an XML definition from string and parse it into a parse-tree, advanced API
*
* @param[in] str String containing XML definition.
* @param[in] yb How to bind yang to XML top-level when parsing
* @param[in] yspec Yang specification, or NULL
* @param[in,out] xt Pointer to XML parse tree. If empty will be created.
* @param[out] xerr Reason for failure (yang assignment not made)
* @retval 1 Parse OK and all yang assignment made
* @retval 0 Parse OK but yang assigment not made (or only partial)
* @retval -1 Error with clicon_err called. Includes parse error
*
* @code
* cxobj *xt = NULL;
* cxobj *xerr = NULL;
* if (xml_parse_string2(str, YB_TOP, yspec, &xt, &xerr) < 0)
* err;
* if (xml_rootchild(xt, 0, &xt) < 0) # If you want to remove TOP
* err;
* @endcode
* @see xml_parse_file
* @see xml_parse_va
* @note You need to free the xml parse tree after use, using xml_free()
* @note If empty on entry, a new TOP xml will be created named "top"
*/
int
xml_parse_string2(const char *str,
enum yang_bind yb,
yang_stmt *yspec,
cxobj **xt,
cxobj **xerr)
{
if (xt==NULL){
clicon_err(OE_XML, EINVAL, "xt is NULL");
return -1;
}
if (*xt == NULL){
if ((*xt = xml_new(XML_TOP_SYMBOL, NULL, NULL)) == NULL)
return -1;
}
return _xml_parse(str, yb, yspec, *xt, xerr);
}
/*! Read an XML definition from string and parse it into a parse-tree
*
* @param[in] str String containing XML definition.
* @param[in] yspec Yang specification, or NULL
* @param[in,out] xt Pointer to XML parse tree. If empty will be created.
* @retval 0 OK
* @retval 1 Parse OK and all yang assignment made
* @retval 0 Parse OK but yang assigment not made (or only partial)
* @retval -1 Error with clicon_err called. Includes parse error
*
* @code
@ -2118,24 +2330,36 @@ xml_parse_file(int fd,
int
xml_parse_string(const char *str,
yang_stmt *yspec,
cxobj **xtop)
cxobj **xt)
{
if (*xtop == NULL)
if ((*xtop = xml_new(XML_TOP_SYMBOL, NULL, NULL)) == NULL)
enum yang_bind yb = YB_PARENT;
if (xt==NULL){
clicon_err(OE_XML, EINVAL, "xt is NULL");
return -1;
}
if (*xt == NULL){
yb = YB_TOP; /* ad-hoc #1 */
if ((*xt = xml_new(XML_TOP_SYMBOL, NULL, NULL)) == NULL)
return -1;
return _xml_parse(str, yspec, *xtop);
}
else{
if (xml_spec(*xt) == NULL)
yb = YB_TOP; /* ad-hoc #2 */
}
return _xml_parse(str, yb, yspec, *xt, NULL);
}
/*! Read XML from var-arg list and parse it into xml tree
*
* Utility function using stdarg instead of static string.
* @param[in,out] xtop Top of XML parse tree. If it is NULL, top element
called 'top' will be created. Call xml_free() after use
* @param[in] yspec Yang specification, or NULL
* @param[in] format Format string for stdarg according to printf(3)
* @retval 0 OK
* @retval -1 Error with clicon_err called
* @param[in,out] xtop Top of XML parse tree. If it is NULL, top element
called 'top' will be created. Call xml_free() after use
* @param[in] yspec Yang specification, or NULL
* @param[in] format Format string for stdarg according to printf(3)
* @retval 1 Parse OK and all yang assignment made
* @retval 0 Parse OK but yang assigment not made (or only partial)
* @retval -1 Error with clicon_err called. Includes parse error
*
* @code
* cxobj *xt = NULL;
@ -2168,9 +2392,7 @@ xml_parse_va(cxobj **xtop,
va_start(args, format);
len = vsnprintf(str, len, format, args) + 1;
va_end(args);
if (xml_parse_string(str, yspec, xtop) < 0)
goto done;
retval = 0;
retval = xml_parse_string(str, yspec, xtop); /* xml_parse_string2 */
done:
if (str)
free(str);
@ -2201,6 +2423,7 @@ xml_copy_one(cxobj *x0,
if ((s = xml_prefix(x0))) /* malloced string */
if ((xml_prefix_set(x1, s)) < 0)
goto done;
xml_spec_set(x1, xml_spec(x0));
retval = 0;
done:
return retval;
@ -2219,8 +2442,9 @@ xml_copy_one(cxobj *x0,
* if (xml_copy(x0, x1) < 0)
* err;
* @endcode
* @see xml_dup
*/
int
int
xml_copy(cxobj *x0,
cxobj *x1)
{
@ -2249,6 +2473,7 @@ xml_copy(cxobj *x0,
* x1 = xml_dup(x0);
* @endcode
* Note, returned tree should be freed as: xml_free(x1)
* @see xml_cp
*/
cxobj *
xml_dup(cxobj *x0)
@ -2357,7 +2582,6 @@ cxvec_prepend(cxobj *x,
return retval;
}
/*! Apply a function call recursively on all xml node children recursively
* Recursively traverse all xml nodes in a parse-tree and apply fn(arg) for
* each object found. The function is called with the xml node and an
@ -2751,4 +2975,3 @@ clicon_log_xml(int level,
free(msg);
return retval;
}

View file

@ -3,7 +3,8 @@
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
Copyright (C) 2017-2020 Olof Hagsand
Copyright (C) 2017-2019 Olof Hagsand
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
This file is part of CLIXON.
@ -445,7 +446,7 @@ clixon_xml_changelog_init(clicon_handle h)
clicon_err(OE_UNIX, errno, "open(%s)", filename);
goto done;
}
if (xml_parse_file(fd, NULL, yspec, &xt) < 0)
if (xml_parse_file(fd, yspec, &xt) < 0)
goto done;
if (xml_rootchild(xt, 0, &xt) < 0)
goto done;

View file

@ -3,7 +3,8 @@
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
Copyright (C) 2017-2020 Olof Hagsand
Copyright (C) 2017-2019 Olof Hagsand
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
This file is part of CLIXON.
@ -65,6 +66,8 @@
#include "clixon_yang.h"
#include "clixon_xml.h"
#include "clixon_options.h"
#include "clixon_data.h"
#include "clixon_yang_module.h"
#include "clixon_plugin.h"
#include "clixon_xml_nsctx.h"
#include "clixon_xpath_ctx.h"
@ -612,12 +615,19 @@ xml_tree_prune_flagged_sub(cxobj *xt,
int iskey;
int anykey=0;
yang_stmt *yt;
int i;
mark = 0;
yt = xml_spec(xt); /* xan be null */
x = NULL;
xprev = x = NULL;
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
i = 0;
while ((x = xml_child_each(xt, x, -1)) != NULL) {
i++;
if (xml_type(x) != CX_ELMNT){
xprev = x;
continue;
}
if (xml_flag(x, flag) == test?flag:0){
/* Pass test */
mark++;
@ -641,8 +651,9 @@ xml_tree_prune_flagged_sub(cxobj *xt,
if (submark)
mark++;
else{ /* Safe with xml_child_each if last */
if (xml_purge(x) < 0)
if (xml_child_rm(xt, i-1) < 0)
goto done;
i--;
x = xprev;
}
xprev = x;
@ -651,13 +662,20 @@ xml_tree_prune_flagged_sub(cxobj *xt,
if (anykey && !mark){
x = NULL;
xprev = x = NULL;
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
i = 0;
while ((x = xml_child_each(xt, x, -1)) != NULL) {
i++;
if (xml_type(x) != CX_ELMNT){
xprev = x;
continue;
}
/* If it is key remove it here */
if (yt){
if ((iskey = yang_key_match(yt, xml_name(x))) < 0)
goto done;
if (iskey && xml_purge(x) < 0)
if (xml_child_rm(xt, i-1) < 0)
goto done;
i--;
x = xprev;
}
xprev = x;
@ -710,7 +728,6 @@ xml_tree_prune_flagged(cxobj *xt,
*/
static int
add_namespace(cxobj *x1, /* target */
cxobj *x1p,
char *prefix1,
char *namespace)
{
@ -746,6 +763,47 @@ add_namespace(cxobj *x1, /* target */
return retval;
}
/*! Change namespace of XML node
*
* @param[in] x XML node
* @param[in] namespace Change to this namespace (if ns does not exist in tree)
* @param[in] prefix If change, use this prefix
* @param 0 OK
* @param -1 Error
*/
int
xml_namespace_change(cxobj *x,
char *namespace,
char *prefix)
{
int retval = -1;
char *ns0 = NULL; /* existing namespace */
char *prefix0 = NULL; /* existing prefix */
ns0 = NULL;
if (xml2ns(x, xml_prefix(x), &ns0) < 0)
goto done;
if (ns0 && strcmp(ns0, namespace) == 0)
goto ok; /* Already has right namespace */
/* Is namespace already declared? */
if (xml2prefix(x, namespace, &prefix0) == 1){
/* Yes it is declared and the prefix is pexists */
if (xml_prefix_set(x, prefix0) < 0)
goto done;
}
else{ /* Namespace does not exist, add it */
/* Clear old prefix if any */
if (xml_prefix_set(x, NULL) < 0)
goto done;
if (add_namespace(x, prefix0, namespace) < 0)
goto done;
}
ok:
retval = 0;
done:
return retval;
}
/*! Add default values (if not set)
* @param[in] xt XML tree with some node marked
* @param[in] arg Ignored
@ -799,7 +857,7 @@ xml_default(cxobj *xt,
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
if (add_namespace(xc, xt, prefix, namespace) < 0)
if (add_namespace(xc, prefix, namespace) < 0)
goto done;
}
}
@ -878,31 +936,36 @@ xml_non_config_data(cxobj *xt,
return retval;
}
/*! Find yang spec association of XML node for incoming RPC
/*! Find yang spec association of XML node for incoming RPC starting with <rpc>
*
* Incoming RPC has an "input" structure that is not taken care of by xml_spec_populate
* @param[in] xt XML tree node
* @param[in] arg Yang spec
* @retval 0 OK
* @retval -1 Error
* @param[in] xrpc XML rpc node
* @param[in] yspec Yang spec
* @param[out] xerr Reason for failure, or NULL
* @retval 1 OK yang assignment made
* @retval 0 Partial or no yang assigment made (at least one failed) and xerr set
* @retval -1 Error
* The
* @code
* xml_apply(xc, CX_ELMNT, xml_spec_populate_rpc_input, yspec)
* if (xml_spec_populate_rpc(h, x, NULL) < 0)
* err;
* @endcode
* @see xml_spec_populate For other generic cases
* @see xml_spec_populate_rpc_reply
*/
int
xml_spec_populate_rpc_input(clicon_handle h,
cxobj *xrpc,
yang_stmt *yspec)
xml_spec_populate_rpc(cxobj *xrpc,
yang_stmt *yspec,
cxobj **xerr)
{
int retval = -1;
yang_stmt *yrpc = NULL; /* yang node */
yang_stmt *ymod=NULL; /* yang module */
yang_stmt *yi = NULL; /* input */
cxobj *x;
int ret;
if ((strcmp(xml_name(xrpc), "rpc"))!=0){
if ((strcmp(xml_name(xrpc), "rpc")) != 0){
clicon_err(OE_UNIX, EINVAL, "RPC expected");
goto done;
}
@ -921,14 +984,225 @@ xml_spec_populate_rpc_input(clicon_handle h,
* recursive population to work. Therefore, assign input yang
* to rpc level although not 100% intuitive */
xml_spec_set(x, yi);
if (xml_apply(x, CX_ELMNT, xml_spec_populate, yspec) < 0)
if ((ret = xml_spec_populate_parent(x, xerr)) < 0)
goto done;
if (ret == 0)
goto fail;
}
}
}
retval = 0;
retval = 1;
done:
return retval;
fail:
retval = 0;
goto done;
}
/*! Find yang spec association of XML node for outgoing RPC starting with <rpc-reply>
*
* Incoming RPC has an "input" structure that is not taken care of by xml_spec_populate
* @param[in] xrpc XML rpc node
* @param[in] name Name of RPC (not seen in output/reply)
* @param[in] yspec Yang spec
* @param[out] xerr Reason for failure, or NULL
* @retval 1 OK yang assignment made
* @retval 0 Partial or no yang assigment made (at least one failed) and xerr set
* @retval -1 Error
*
* @code
* if (xml_spec_populate_rpc_reply(x, "get-config", yspec, name) < 0)
* err;
* @endcode
* @see xml_spec_populate For other generic cases
*/
int
xml_spec_populate_rpc_reply(cxobj *xrpc,
char *name,
yang_stmt *yspec,
cxobj **xerr)
{
int retval = -1;
yang_stmt *yrpc = NULL; /* yang node */
yang_stmt *ymod=NULL; /* yang module */
yang_stmt *yo = NULL; /* output */
cxobj *x;
int ret;
if (strcmp(xml_name(xrpc), "rpc-reply")){
clicon_err(OE_UNIX, EINVAL, "rpc-reply expected");
goto done;
}
x = NULL;
while ((x = xml_child_each(xrpc, x, CX_ELMNT)) != NULL) {
if (ys_module_by_xml(yspec, x, &ymod) < 0)
goto done;
if (ymod == NULL)
continue;
if ((yrpc = yang_find(ymod, Y_RPC, name)) == NULL)
continue;
// xml_spec_set(xrpc, yrpc);
if ((yo = yang_find(yrpc, Y_OUTPUT, NULL)) == NULL)
continue;
/* xml_spec_populate need to have parent with yang spec for
* recursive population to work. Therefore, assign input yang
* to rpc level although not 100% intuitive */
break;
}
if (yo != NULL){
xml_spec_set(xrpc, yo);
if ((ret = xml_spec_populate(xrpc, yspec, xerr)) < 0)
goto done;
if (ret == 0)
goto fail;
}
retval = 1;
done:
return retval;
fail:
retval = 0;
goto done;
}
/*! Associate XML node x with x:s parents yang:s matching child
*
* @param[in] xt XML tree node
* @param[out] xerr Reason for failure, or NULL
* @retval 2 OK yang assignment made
* @retval 1 OK, Yang assignment not made because yang parent is anyxml or anydata
* @retval 0 Yang assigment not made and xerr set
* @retval -1 Error
* @see populate_self_top
*/
static int
populate_self_parent(cxobj *xt,
cxobj **xerr)
{
int retval = -1;
yang_stmt *y = NULL; /* yang node */
yang_stmt *yparent; /* yang parent */
cxobj *xp = NULL; /* xml parent */
char *name;
char *ns = NULL; /* XML namespace of xt */
char *nsy = NULL; /* Yang namespace of xt */
xp = xml_parent(xt);
name = xml_name(xt);
if (xp == NULL){
if (xerr &&
netconf_bad_element_xml(xerr, "application", name, "Missing parent") < 0)
goto done;
goto fail;
}
if ((yparent = xml_spec(xp)) == NULL){
if (xerr &&
netconf_bad_element_xml(xerr, "application", name, "Missing parent yang node") < 0)
goto done;
goto fail;
}
if (yang_keyword_get(yparent) == Y_ANYXML || yang_keyword_get(yparent) == Y_ANYDATA){
retval = 1;
goto done;
}
if ((y = yang_find_datanode(yparent, name)) == NULL){
if (xerr &&
netconf_bad_element_xml(xerr, "application", name, "Missing matching yang node") < 0)
goto done;
goto fail;
}
if (xml2ns(xt, xml_prefix(xt), &ns) < 0)
goto done;
nsy = yang_find_mynamespace(y);
if (ns == NULL || nsy == NULL){
if (xerr &&
netconf_bad_element_xml(xerr, "application", name, "Missing namespace") < 0)
goto done;
goto fail;
}
/* Assign spec only if namespaces match */
if (strcmp(ns, nsy) != 0){
if (xerr &&
netconf_bad_element_xml(xerr, "application", name, "Namespace mismatch") < 0)
goto done;
goto fail;
}
xml_spec_set(xt, y);
retval = 2;
done:
return retval;
fail:
retval = 0;
goto done;
}
/*! Associate XML node x with yang spec y by going through all top-level modules and finding match
*
* @param[in] xt XML tree node
* @param[in] yspec Yang spec
* @param[out] xerr Reason for failure, or NULL
* @retval 2 OK yang assignment made
* @retval 1 OK, Yang assignment not made because yang parent is anyxml or anydata
* @retval 0 yang assigment not made and xerr set
* @retval -1 Error
* @see populate_self_parent
*/
static int
populate_self_top(cxobj *xt,
yang_stmt *yspec,
cxobj **xerr)
{
int retval = -1;
yang_stmt *y = NULL; /* yang node */
yang_stmt *ymod; /* yang module */
char *name;
char *ns = NULL; /* XML namespace of xt */
char *nsy = NULL; /* Yang namespace of xt */
name = xml_name(xt);
if (yspec == NULL){
if (xerr &&
netconf_bad_element_xml(xerr, "application", name, "Missing yang spec") < 0)
goto done;
goto fail;
}
if (ys_module_by_xml(yspec, xt, &ymod) < 0)
goto done;
/* ymod is "real" module, name may belong to included submodule */
if (ymod == NULL){
if (xerr &&
netconf_bad_element_xml(xerr, "application", name, "No such yang module") < 0)
goto done;
goto fail;
}
if ((y = yang_find_schemanode(ymod, name)) == NULL){ /* also rpc */
if (xerr &&
netconf_bad_element_xml(xerr, "application", name, "Missing matching yang node") < 0)
goto done;
goto fail;
}
if (xml2ns(xt, xml_prefix(xt), &ns) < 0)
goto done;
nsy = yang_find_mynamespace(y);
if (ns == NULL || nsy == NULL){
if (xerr &&
netconf_bad_element_xml(xerr, "application", name, "Missing namespace") < 0)
goto done;
goto fail;
}
/* Assign spec only if namespaces match */
if (strcmp(ns, nsy) != 0){
if (xerr &&
netconf_bad_element_xml(xerr, "application", name, "Namespace mismatch") < 0)
goto done;
goto fail;
}
xml_spec_set(xt, y);
retval = 1;
done:
return retval;
fail:
retval = 0;
goto done;
}
/*! Find yang spec association of XML node
@ -937,95 +1211,111 @@ xml_spec_populate_rpc_input(clicon_handle h,
* need specialized function.
* XXX: maybe it can be built into the same function, but you dont know whether it is input or output rpc, so
* it seems like you need specialized functions.
* @param[in] xt XML tree node
* @param[in] arg Yang spec
* @retval 0 OK
* @retval -1 Error
* @param[in] xt XML tree node
* @param[in] yspec Yang spec
* @param[out] xerr Reason for failure, or NULL
* @retval 1 OK yang assignment made
* @retval 0 Partial or no yang assigment made (at least one failed) and xerr set
* @retval -1 Error
* @code
* xml_apply(xc, CX_ELMNT, xml_spec_populate, yspec)
* if (xml_spec_populate(x, yspec, NULL) < 0)
* err;
* @endcode
* @note For subs to anyxml nodes will not have spec set
* @see xml_spec_populate_rpc_input for incoming rpc
* XXX: can give false positives
* @see xml_spec_populate_rpc for incoming rpc
*/
#undef DEBUG /* Set this for debug */
int
xml_spec_populate(cxobj *x,
void *arg)
xml_spec_populate(cxobj *xt,
yang_stmt *yspec,
cxobj **xerr)
{
int retval = -1;
yang_stmt *yspec = NULL; /* yang spec */
yang_stmt *y = NULL; /* yang node */
yang_stmt *yparent; /* yang parent */
yang_stmt *ymod; /* yang module */
cxobj *xp = NULL; /* xml parent */
char *name;
char *ns = NULL; /* XML namespace of x */
char *nsy = NULL; /* Yang namespace of x */
int retval = -1;
cxobj *xc; /* xml child */
int ret;
int failed = 0; /* we continue loop after failure, should we stop at fail?`*/
yspec = (yang_stmt*)arg;
xp = xml_parent(x);
name = xml_name(x);
#ifdef DEBUG
clicon_debug(1, "%s name:%s", __FUNCTION__, name);
#endif
if (xml2ns(x, xml_prefix(x), &ns) < 0)
xc = NULL; /* Apply on children */
while ((xc = xml_child_each(xt, xc, CX_ELMNT)) != NULL) {
if ((ret = xml_spec_populate0(xc, yspec, xerr)) < 0)
goto done;
if (ret == 0)
failed++;
}
retval = (failed==0) ? 1 : 0;
done:
return retval;
}
int
xml_spec_populate_parent(cxobj *xt,
cxobj **xerr)
{
int retval = -1;
cxobj *xc; /* xml child */
int ret;
int failed = 0; /* we continue loop after failure, should we stop at fail?`*/
xc = NULL; /* Apply on children */
while ((xc = xml_child_each(xt, xc, CX_ELMNT)) != NULL) {
if ((ret = xml_spec_populate0_parent(xc, xerr)) < 0)
goto done;
if (ret == 0)
failed++;
}
retval = (failed==0) ? 1 : 0;
done:
return retval;
}
int
xml_spec_populate0(cxobj *xt,
yang_stmt *yspec,
cxobj **xerr)
{
int retval = -1;
cxobj *xc; /* xml child */
int ret;
int failed = 0; /* we continue loop after failure, should we stop at fail?`*/
if ((ret = populate_self_top(xt, yspec, xerr)) < 0)
goto done;
if (xp && (yparent = xml_spec(xp)) != NULL){
#ifdef DEBUG
clicon_debug(1, "%s yang parent:%s", __FUNCTION__, yang_argument_get(yparent));
#endif
y = yang_find_datanode(yparent, name);
}
else if (yspec){ /* XXX this gives false positives */
if (ys_module_by_xml(yspec, x, &ymod) < 0)
goto done;
/* ymod is "real" module, name may belong to included submodule */
if (ymod != NULL){
#ifdef DEBUG
clicon_debug(1, "%s %s mod:%s", __FUNCTION__, name, yang_argument_get(ymod));
#endif
y = yang_find_schemanode(ymod, name);
}
#ifdef DEBUG
else
clicon_debug(1, "%s %s mod:NULL", __FUNCTION__, name);
#endif
}
if (y) {
nsy = yang_find_mynamespace(y);
if (ns == NULL || nsy == NULL){
clicon_err(OE_XML, EFAULT, "Namespace NULL");
goto done;
}
#ifdef DEBUG
clicon_debug(1, "%s y:%s", __FUNCTION__, yang_argument_get(y));
#endif
/* Assign spec only if namespaces match */
if (strcmp(ns, nsy) == 0){
#if 0
/* Cornercase:
* The XML parser may have kept pretty-printed whitespaces in empty nodes, eg "<x> </x>"
* since it is valid leaf(-list) content.
* But if it is not a container, list or something, else, the content should be stripped.
* But we cannot do this because of false positives (above)
*/
if (xml_spec(x) == NULL && /* Not assigned yet, ensure to do just once,... */
yang_keyword_get(y) != Y_LEAF &&
yang_keyword_get(y) != Y_LEAF_LIST){
if (xml_rm_children(x, CX_BODY) < 0)
goto done;
}
#endif
xml_spec_set(x, y);
if (ret == 1){
xc = NULL; /* Apply on children */
while ((xc = xml_child_each(xt, xc, CX_ELMNT)) != NULL) {
if ((ret = xml_spec_populate0_parent(xc, xerr)) < 0)
goto done;
if (ret == 0)
failed++;
}
}
else{
#ifdef DEBUG
clicon_debug(1, "%s y:NULL", __FUNCTION__);
#endif
retval = (failed==0) ? 1 : 0;
done:
return retval;
}
int
xml_spec_populate0_parent(cxobj *xt,
cxobj **xerr)
{
int retval = -1;
cxobj *xc; /* xml child */
int ret;
int failed = 0; /* we continue loop after failure, should we stop at fail?`*/
if ((ret = populate_self_parent(xt, xerr)) < 0)
goto done;
if (ret == 0)
failed++;
else if (ret != 1){ /* 1 means anyxml parent */
xc = NULL; /* Apply on children */
while ((xc = xml_child_each(xt, xc, CX_ELMNT)) != NULL) {
if ((ret = xml_spec_populate0_parent(xc, xerr)) < 0)
goto done;
if (ret == 0)
failed++;
}
}
retval = 0;
retval = (failed==0) ? 1 : 0;
done:
return retval;
}
@ -1242,11 +1532,8 @@ assign_namespaces(cxobj *x0, /* source */
* Check if it is exists in x1 itself */
if (xml2prefix(x1, namespace, &pexist) == 1){
/* Yes it exists, but is it equal? */
if ((pexist == NULL && prefix0 == NULL) ||
(pexist && prefix0 &&
strcmp(pexist, prefix0)==0)){ /* Equal, reuse */
;
}
if (clicon_strcmp(pexist, prefix0) == 0)
; /* Equal, reuse */
else{ /* namespace exist, but not equal, use existing */
/* Add prefix to x1, if any */
if (pexist && xml_prefix_set(x1, pexist) < 0)
@ -1254,8 +1541,7 @@ assign_namespaces(cxobj *x0, /* source */
}
goto ok; /* skip */
}
else
{ /* namespace does not exist in target x1, use source prefix
else { /* namespace does not exist in target x1, use source prefix
* use the prefix defined in the module
*/
if (isroot){
@ -1272,7 +1558,7 @@ assign_namespaces(cxobj *x0, /* source */
}
}
}
if (add_namespace(x1, x1p, prefix1, namespace) < 0)
if (add_namespace(x1, prefix1, namespace) < 0)
goto done;
}
ok:

View file

@ -2,7 +2,9 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
Copyright (C) 2017-2019 Olof Hagsand
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
This file is part of CLIXON.
@ -356,10 +358,20 @@ xml_nsctx_yang(yang_stmt *yn,
*
* That is, create a "canonical" XML namespace mapping from all loaded yang
* modules which are children of the yang specification.
* ALso add netconf base namespace: nc , urn:ietf:params:xml:ns:netconf:base:1.0
* Also add netconf base namespace: nc , urn:ietf:params:xml:ns:netconf:base:1.0
* Fully explore all prefix:namespace pairs of all yang modules
* @param[in] yspec Yang spec
* @param[out] ncp XML namespace context
* @retval 0 OK
* @retval -1 Error
* @code
* cvec *nsc = NULL;
* yang_stmt *yspec = clicon_dbspec_yang(h);
* if (xml_nsctx_yangspec(yspec, &nsc) < 0)
* goto done;
* ...
* cvec_free(nsc);
* @endcode
*/
int
xml_nsctx_yangspec(yang_stmt *yspec,

View file

@ -3,7 +3,8 @@
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
Copyright (C) 2017-2020 Olof Hagsand
Copyright (C) 2017-2019 Olof Hagsand
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
This file is part of CLIXON.
@ -47,20 +48,23 @@ struct xml_parse_yacc_arg{
char *ya_parse_string; /* original (copy of) parse string */
int ya_linenum; /* Number of \n in parsed buffer */
void *ya_lexbuf; /* internal parse buffer from lex */
cxobj *ya_xelement; /* xml active element */
cxobj *ya_xparent; /* xml parent element*/
cxobj *ya_xtop; /* cxobj top element (fixed) */
cxobj *ya_xelement; /* cxobj active element (changes with parse context) */
cxobj *ya_xparent; /* cxobj parent element (changes with parse context) */
yang_stmt *ya_yspec; /* If set, top-level yang-spec */
int ya_lex_state; /* lex return state */
cxobj **ya_xvec; /* Vector of created top-level nodes (to know which are created) */
size_t ya_xlen; /* Length of ya_xvec */
};
typedef struct xml_parse_yacc_arg clixon_xml_yacc;
extern char *clixon_xml_parsetext;
/*
* Prototypes
*/
int clixon_xml_parsel_init(struct xml_parse_yacc_arg *ya);
int clixon_xml_parsel_exit(struct xml_parse_yacc_arg *ya);
int clixon_xml_parsel_init(clixon_xml_yacc *ya);
int clixon_xml_parsel_exit(clixon_xml_yacc *ya);
int clixon_xml_parsel_linenr(void);
int clixon_xml_parselex(void *);

View file

@ -2,7 +2,9 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
Copyright (C) 2017-2019 Olof Hagsand
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
This file is part of CLIXON.
@ -66,7 +68,7 @@
#define YY_NO_INPUT
/* typecast macro */
#define _YA ((struct xml_parse_yacc_arg *)_ya)
#define _YA ((clixon_xml_yacc *)_ya)
#undef clixon_xml_parsewrap
int clixon_xml_parsewrap(void)
@ -182,7 +184,7 @@ ncname {namestart}{namechar}*
/*! Initialize XML scanner.
*/
int
clixon_xml_parsel_init(struct xml_parse_yacc_arg *ya)
clixon_xml_parsel_init(clixon_xml_yacc *ya)
{
BEGIN(START);
ya->ya_lexbuf = yy_scan_string (ya->ya_parse_string);
@ -193,7 +195,7 @@ clixon_xml_parsel_init(struct xml_parse_yacc_arg *ya)
/*! Exit xml scanner */
int
clixon_xml_parsel_exit(struct xml_parse_yacc_arg *ya)
clixon_xml_parsel_exit(clixon_xml_yacc *ya)
{
yy_delete_buffer(ya->ya_lexbuf);
clixon_xml_parselex_destroy(); /* modern */

View file

@ -2,7 +2,9 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
Copyright (C) 2017-2019 Olof Hagsand
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
This file is part of CLIXON.
@ -58,7 +60,7 @@
%{
/* typecast macro */
#define _YA ((struct xml_parse_yacc_arg *)_ya)
#define _YA ((clixon_xml_yacc *)_ya)
#include <stdio.h>
#include <stdint.h>
@ -82,11 +84,14 @@
#include "clixon_xml_parse.h"
void
clixon_xml_parseerror(void *_ya, char *s)
clixon_xml_parseerror(void *_ya,
char *s)
{
clicon_err(OE_XML, XMLPARSE_ERRNO, "xml_parse: line %d: %s: at or before: %s",
_YA->ya_linenum, s, clixon_xml_parsetext);
return;
clicon_err(OE_XML, XMLPARSE_ERRNO, "xml_parse: line %d: %s: at or before: %s",
_YA->ya_linenum,
s,
clixon_xml_parsetext);
return;
}
/*
@ -94,7 +99,7 @@ clixon_xml_parseerror(void *_ya, char *s)
* there may also be some leakage here on NULL return
*/
static int
xml_parse_content(struct xml_parse_yacc_arg *ya,
xml_parse_content(clixon_xml_yacc *ya,
char *str)
{
cxobj *xn = ya->ya_xelement;
@ -120,7 +125,7 @@ xml_parse_content(struct xml_parse_yacc_arg *ya,
* But if there is an element, then skip all whitespace.
*/
static int
xml_parse_whitespace(struct xml_parse_yacc_arg *ya,
xml_parse_whitespace(clixon_xml_yacc *ya,
char *str)
{
cxobj *xn = ya->ya_xelement;
@ -151,7 +156,7 @@ xml_parse_whitespace(struct xml_parse_yacc_arg *ya,
}
static int
xml_parse_version(struct xml_parse_yacc_arg *ya,
xml_parse_version(clixon_xml_yacc *ya,
char *ver)
{
if(strcmp(ver, "1.0")){
@ -164,59 +169,34 @@ xml_parse_version(struct xml_parse_yacc_arg *ya,
return 0;
}
/*! Parse Qualified name --> Unprefixed name
* @param[in] ya XML parser yacc handler struct
* @param[in] localpart Name
* @note the call to xml_child_spec() may not have xmlns attribute read yet XXX
*/
static int
xml_parse_unprefixed_name(struct xml_parse_yacc_arg *ya,
char *name)
{
int retval = -1;
cxobj *x;
yang_stmt *y = NULL; /* yang node */
cxobj *xp; /* xml parent */
xp = ya->ya_xparent;
if ((x = xml_new(name, xp, NULL)) == NULL)
goto done;
if (xml_child_spec(x, xp, ya->ya_yspec, &y) < 0)
goto done;
if (y && xml_spec_set(x, y) < 0)
goto done;
ya->ya_xelement = x;
retval = 0;
done:
free(name);
return retval;
}
/*! Parse Qualified name -> PrefixedName
/*! Parse Qualified name -> (Un)PrefixedName
*
* This is where all (parsed) xml elements are created
* @param[in] ya XML parser yacc handler struct
* @param[in] prefix Prefix, namespace, or NULL
* @param[in] localpart Name
*/
static int
xml_parse_prefixed_name(struct xml_parse_yacc_arg *ya,
xml_parse_prefixed_name(clixon_xml_yacc *ya,
char *prefix,
char *name)
{
int retval = -1;
cxobj *x;
yang_stmt *y = NULL; /* yang node */
cxobj *xp; /* xml parent */
xp = ya->ya_xparent;
if ((x = xml_new(name, xp, NULL)) == NULL)
goto done;
if (xml_child_spec(x, xp, ya->ya_yspec, &y) < 0)
goto done;
if (y && xml_spec_set(x, y) < 0)
goto done;
if (xml_prefix_set(x, prefix) < 0)
xml_type_set(x, CX_ELMNT);
if (prefix && xml_prefix_set(x, prefix) < 0)
goto done;
ya->ya_xelement = x;
/* If topmost, add to top-list created list */
if (xp == ya->ya_xtop){
if (cxvec_append(x, &ya->ya_xvec, &ya->ya_xlen) < 0)
goto done;
}
retval = 0;
done:
if (prefix)
@ -226,7 +206,7 @@ xml_parse_prefixed_name(struct xml_parse_yacc_arg *ya,
}
static int
xml_parse_endslash_pre(struct xml_parse_yacc_arg *ya)
xml_parse_endslash_pre(clixon_xml_yacc *ya)
{
ya->ya_xparent = ya->ya_xelement;
ya->ya_xelement = NULL;
@ -234,7 +214,7 @@ xml_parse_endslash_pre(struct xml_parse_yacc_arg *ya)
}
static int
xml_parse_endslash_mid(struct xml_parse_yacc_arg *ya)
xml_parse_endslash_mid(clixon_xml_yacc *ya)
{
if (ya->ya_xelement != NULL)
ya->ya_xelement = xml_parent(ya->ya_xelement);
@ -245,7 +225,7 @@ xml_parse_endslash_mid(struct xml_parse_yacc_arg *ya)
}
static int
xml_parse_endslash_post(struct xml_parse_yacc_arg *ya)
xml_parse_endslash_post(clixon_xml_yacc *ya)
{
ya->ya_xelement = NULL;
return 0;
@ -261,7 +241,7 @@ xml_parse_endslash_post(struct xml_parse_yacc_arg *ya)
* @param[in] name
*/
static int
xml_parse_bslash(struct xml_parse_yacc_arg *ya,
xml_parse_bslash(clixon_xml_yacc *ya,
char *prefix,
char *name)
{
@ -309,7 +289,7 @@ xml_parse_bslash(struct xml_parse_yacc_arg *ya,
* - PrefixedAttName: xmlns:NAME
*/
static int
xml_parse_attr(struct xml_parse_yacc_arg *ya,
xml_parse_attr(clixon_xml_yacc *ya,
char *prefix,
char *name,
char *attval)
@ -389,7 +369,7 @@ element : '<' qname attrs element1
{ clicon_debug(2, "element -> < qname attrs element1"); }
;
qname : NAME { if (xml_parse_unprefixed_name(_YA, $1) < 0) YYABORT;
qname : NAME { if (xml_parse_prefixed_name(_YA, NULL, $1) < 0) YYABORT;
clicon_debug(2, "qname -> NAME %s", $1);}
| NAME ':' NAME { if (xml_parse_prefixed_name(_YA, $1, $3) < 0) YYABORT;
clicon_debug(2, "qname -> NAME : NAME");}

View file

@ -3,7 +3,8 @@
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
Copyright (C) 2017-2020 Olof Hagsand
Copyright (C) 2017-2019 Olof Hagsand
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
This file is part of CLIXON.
@ -135,61 +136,6 @@ xml_cv_cache(cxobj *x,
return retval;
}
/*! Given a child name and an XML object, return yang stmt of child
* If no xml parent, find root yang stmt matching name
* @param[in] x Child
* @param[in] xp XML parent, can be NULL.
* @param[in] yspec Yang specification (top level)
* @param[out] yresult Pointer to yang stmt of result, or NULL, if not found
* @retval 0 OK
* @retval -1 Error
* @note special rule for rpc, ie <rpc><foo>,look for top "foo" node.
* @note works for import prefix, but not work for generic XML parsing where
* xmlns and xmlns:ns are used.
*/
int
xml_child_spec(cxobj *x,
cxobj *xp,
yang_stmt *yspec,
yang_stmt **yresult)
{
int retval = -1;
yang_stmt *y = NULL; /* result yang node */
yang_stmt *yparent; /* parent yang */
yang_stmt *ymod = NULL;
yang_stmt *yi;
char *name;
name = xml_name(x);
if (xp && (yparent = xml_spec(xp)) != NULL){
/* First case: parent already has an associated yang statement,
* then find matching child of that */
if (yang_keyword_get(yparent) == Y_RPC){
if ((yi = yang_find(yparent, Y_INPUT, NULL)) != NULL)
y = yang_find_datanode(yi, name);
}
else
y = yang_find_datanode(yparent, name);
}
else if (yspec){
/* Second case, this is a "root", need to find yang stmt from spec
*/
if (ys_module_by_xml(yspec, xp, &ymod) < 0)
goto done;
if (ymod != NULL)
y = yang_find_schemanode(ymod, name);
}
else
y = NULL;
/* kludge rpc -> input */
if (y && yang_keyword_get(y) == Y_RPC && yang_find(y, Y_INPUT, NULL))
y = yang_find(y, Y_INPUT, NULL);
*yresult = y;
retval = 0;
done:
return retval;
}
/*! Help function to qsort for sorting entries in xml child vector same parent
* @param[in] x1 object 1
* @param[in] x2 object 2

View file

@ -2,7 +2,8 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2020 Olof Hagsand
Copyright (C) 2009-2019 Olof Hagsand
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
This file is part of CLIXON.
@ -123,11 +124,11 @@ static const map_str2int xpath_tree_map[] = {
static const map_str2int axis_type_map[] = {
{"NaN", A_NAN},
{"ancestor", A_ANCESTOR},
{"ancestor-or-selgf", A_ANCESTOR_OR_SELF},
{"ancestor-or-self", A_ANCESTOR_OR_SELF},
{"attribute", A_ATTRIBUTE},
{"child", A_CHILD},
{"descendant", A_DESCENDANT},
{"descendeant-or-self", A_DESCENDANT_OR_SELF},
{"descendant-or-self", A_DESCENDANT_OR_SELF},
{"following", A_FOLLOWING},
{"following-sibling", A_FOLLOWING_SIBLING},
{"namespace", A_NAMESPACE},
@ -702,7 +703,7 @@ xpath_first_localonly(cxobj *xcur,
* cxobj **vec;
* size_t veclen;
* if (xpath_vec(xcur, nsc, "//symbol/foo", &vec, &veclen) < 0)
* goto err;
* err;
* for (i=0; i<veclen; i++){
* xn = vec[i];
* ...

View file

@ -2,7 +2,8 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2020 Olof Hagsand
Copyright (C) 2009-2019 Olof Hagsand
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
This file is part of CLIXON.
@ -354,8 +355,23 @@ xp_eval_step(xp_ctx *xc0,
}
ctx_nodeset_replace(xc, vec, veclen);
break;
case A_DESCENDANT:
case A_DESCENDANT_OR_SELF:
for (i=0; i<xc->xc_size; i++){
xv = xc->xc_nodeset[i];
if (nodetest_recursive(xv, xs->xs_c0, CX_ELMNT, 0x0, nsc, localonly, &vec, &veclen) < 0)
goto done;
}
for (i=0; i<veclen; i++){
x = vec[i];
if (cxvec_append(x, &xc->xc_nodeset, &xc->xc_size) < 0)
goto done;
}
if (vec){
free(vec);
vec = NULL;
}
break;
case A_DESCENDANT:
for (i=0; i<xc->xc_size; i++){
xv = xc->xc_nodeset[i];
if (nodetest_recursive(xv, xs->xs_c0, CX_ELMNT, 0x0, nsc, localonly, &vec, &veclen) < 0)

View file

@ -74,13 +74,13 @@
#include "clixon_yang.h"
#include "clixon_hash.h"
#include "clixon_xml.h"
#include "clixon_yang_module.h"
#include "clixon_plugin.h"
#include "clixon_data.h"
#include "clixon_options.h"
#include "clixon_yang_parse.h"
#include "clixon_yang_parse_lib.h"
#include "clixon_yang_cardinality.h"
#include "clixon_yang_module.h"
#include "clixon_yang_type.h"
#include "clixon_yang_internal.h" /* internal included by this file only, not API*/

View file

@ -2,7 +2,8 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2020 Olof Hagsand
Copyright (C) 2009-2019 Olof Hagsand
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
This file is part of CLIXON.
@ -74,11 +75,11 @@
#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"
#include "clixon_yang_module.h"
modstate_diff_t *
modstate_diff_new(void)
@ -97,6 +98,8 @@ modstate_diff_free(modstate_diff_t *md)
{
if (md == NULL)
return 0;
if (md->md_set_id)
free(md->md_set_id);
if (md->md_del)
xml_free(md->md_del);
if (md->md_mod)

View file

@ -88,13 +88,13 @@
#include "clixon_yang_internal.h"
#include "clixon_hash.h"
#include "clixon_xml.h"
#include "clixon_yang_module.h"
#include "clixon_plugin.h"
#include "clixon_data.h"
#include "clixon_options.h"
#include "clixon_yang_type.h"
#include "clixon_yang_parse.h"
#include "clixon_yang_cardinality.h"
#include "clixon_yang_module.h"
#include "clixon_yang_parse_lib.h"
/* Size of json read buffer when reading from file*/

View file

@ -94,9 +94,9 @@
#include "clixon_hash.h"
#include "clixon_yang.h"
#include "clixon_xml.h"
#include "clixon_yang_module.h"
#include "clixon_plugin.h"
#include "clixon_options.h"
#include "clixon_yang_module.h"
#include "clixon_yang_type.h"
/*