Added CLICON_XMLDB_PRETTY option. If set to false, XML database files will be more compact.

Added CLICON_XMLDB_FORMAT option. Default is "xml". If set to "json", XML database files uses JSON format.
Escape " in JSON names and strings and values
Optimized search performance for large lists by sorting and binary search.
This commit is contained in:
Olof hagsand 2017-12-29 18:09:26 +01:00
parent 4b92dbdc10
commit 174cfc02c6
15 changed files with 428 additions and 161 deletions

View file

@ -1,35 +1,20 @@
# Clixon Changelog # Clixon Changelog
## 3.4.0 (Upcoming) ## 3.4.0 (Upcoming)
### Known issues
* Please use text datastore, key-value datastore no up-to-date
### Major changes:
* Optimized search performance for large lists by sorting and binary search. * Optimized search performance for large lists by sorting and binary search.
* New CLICON_XML_SORT configuration option. Default is 1. Disable by setting to 0. * New CLICON_XML_SORT configuration option. Default is 1. Disable by setting to 0.
* New yang config file: clixon-config@2017-12-27.yang * New yang config file: clixon-config@2017-12-27.yang
* Added yang ordered-by user. The default (ordered-by system) will now sort lists and leaf-lists alphabetically to increase search performance. * Added yang ordered-by user. The default (ordered-by system) will now sort lists and leaf-lists alphabetically to increase search performance.
* This replaces XML hash experimental code, ie xml_child_hash variables and all xml_hash_ functions have been removed. * This replaces XML hash experimental code, ie xml_child_hash variables and all xml_hash_ functions have been removed.
* Cached keys in yang Y_LIST node as cligen vector, see ys_populate_list()
* Cached keys in yang Y_LIST node as cligen vector, see ys_populate_list()
* Clixon_backend now returns -1/255 on error instead of 0. Useful for systemd restarts, for example.
* Fixed bug that deletes running on startup if backup started with -m running.
When clixon starts again, running is lost.
The error was that the running (or startup) configuration may fail when
clixon backend starts.
The fix now makes a copy of running and copies it back on failure.
* experimental netconf yang rpc
* datastore/keyvalue/Makefile is left behind on make distclean. Fixed by conditional configure. Thanks renato@netgate.com.
* Better semantic versioning, eg MAJOR/MINOR/PATCH, where increment in PATCH does not change API.
* Datastore cache introduced: cache XML tree in memory for faster get access. Use CLICON_XMLDB_CACHE configuration option. Default is 1. * Datastore cache introduced: cache XML tree in memory for faster get access. Use CLICON_XMLDB_CACHE configuration option. Default is 1.
* Moved XML_CHILD_HASH to variable instead of constant. * Changed C functional API for XML creation and parsing for more coherency and closer YANG/XML integration. A new yang spec parameter has been added (default NULL) and functions have been removed and renamed. You may need to change the XML calls as follows.
* Changed XML creation and parse API for more coherency and closer YANG/XML integration. A new yang spec parameter has been added (default NULL) and functions have been removed and renamed. You may need to change the XML calls as follows.
* xml_new(name, parent) --> xml_new(name, xn_parent, yspec) * xml_new(name, parent) --> xml_new(name, xn_parent, yspec)
* xml_new_spec(name, parent, spec) --> xml_new(name, parent, spec) * xml_new_spec(name, parent, spec) --> xml_new(name, parent, spec)
* clicon_xml_parse(&xt, format, ...) --> xml_parse_va(&xt, yspec, format, ...) * clicon_xml_parse(&xt, format, ...) --> xml_parse_va(&xt, yspec, format, ...)
@ -42,6 +27,24 @@
configure --with-xml-compat configure --with-xml-compat
``` ```
### Minor changes:
* Better semantic versioning, eg MAJOR/MINOR/PATCH, where increment in PATCH does not change API.
* Added CLICON_XMLDB_PRETTY option. If set to false, XML database files will be more compact.
* Added CLICON_XMLDB_FORMAT option. Default is "xml". If set to "json", XML database files uses JSON format.
* Clixon_backend now returns -1/255 on error instead of 0. Useful for systemd restarts, for example.
* Experimental: netconf yang rpc. That is, using ietf-netconf@2011-06-01.yang
formal specification instead of hardcoded C-code.
### Corrected Bugs
* Fixed bug that deletes running on startup if backup started with -m running.
When clixon starts again, running is lost.
The error was that the running (or startup) configuration may fail when
clixon backend starts.
The fix now makes a copy of running and copies it back on failure.
* datastore/keyvalue/Makefile was left behind on make distclean. Fixed by conditional configure. Thanks renato@netgate.com.
* Escape " in JSON names and strings and values
## 3.3.3 (25 November 2017) ## 3.3.3 (25 November 2017)
Thanks to Matthew Smith, Joe Loeliger at Netgate; Fredrik Pettai at Thanks to Matthew Smith, Joe Loeliger at Netgate; Fredrik Pettai at

View file

@ -666,6 +666,8 @@ main(int argc, char **argv)
int sockfamily; int sockfamily;
char *xmldb_plugin; char *xmldb_plugin;
int xml_cache; int xml_cache;
int xml_pretty;
char *xml_format;
/* In the startup, logs to stderr & syslog and debug flag set later */ /* In the startup, logs to stderr & syslog and debug flag set later */
clicon_log_init(__PROGRAM__, LOG_INFO, CLICON_LOG_STDERR|CLICON_LOG_SYSLOG); clicon_log_init(__PROGRAM__, LOG_INFO, CLICON_LOG_STDERR|CLICON_LOG_SYSLOG);
@ -879,6 +881,12 @@ main(int argc, char **argv)
if ((xml_cache = clicon_option_bool(h, "CLICON_XMLDB_CACHE")) >= 0) if ((xml_cache = clicon_option_bool(h, "CLICON_XMLDB_CACHE")) >= 0)
if (xmldb_setopt(h, "xml_cache", (void*)(intptr_t)xml_cache) < 0) if (xmldb_setopt(h, "xml_cache", (void*)(intptr_t)xml_cache) < 0)
goto done; goto done;
if ((xml_format = clicon_option_str(h, "CLICON_XMLDB_FORMAT")) >= 0)
if (xmldb_setopt(h, "format", (void*)xml_format) < 0)
goto done;
if ((xml_pretty = clicon_option_bool(h, "CLICON_XMLDB_PRETTY")) >= 0)
if (xmldb_setopt(h, "pretty", (void*)(intptr_t)xml_pretty) < 0)
goto done;
/* If startup mode is not defined, eg via OPTION or -s, assume old method */ /* If startup mode is not defined, eg via OPTION or -s, assume old method */
startup_mode = clicon_startup_mode(h); startup_mode = clicon_startup_mode(h);
if (startup_mode == -1){ /* Old style, fragmented mode, phase out */ if (startup_mode == -1){ /* Old style, fragmented mode, phase out */

View file

@ -724,8 +724,7 @@ load_config_file(clicon_handle h,
opstr = cv_string_get(cvec_i(argv, 1)); opstr = cv_string_get(cvec_i(argv, 1));
if (strcmp(opstr, "merge") == 0) if (strcmp(opstr, "merge") == 0)
replace = 0; replace = 0;
else else if (strcmp(opstr, "replace") == 0)
if (strcmp(opstr, "replace") == 0)
replace = 1; replace = 1;
else{ else{
clicon_err(OE_PLUGIN, 0, "No such op: %s, expected merge or replace", opstr); clicon_err(OE_PLUGIN, 0, "No such op: %s, expected merge or replace", opstr);
@ -738,7 +737,7 @@ load_config_file(clicon_handle h,
filename = cv_string_get(cv); filename = cv_string_get(cv);
if (stat(filename, &st) < 0){ if (stat(filename, &st) < 0){
clicon_err(OE_UNIX, 0, "load_config: stat(%s): %s", clicon_err(OE_UNIX, 0, "load_config: stat(%s): %s",
filename, strerror(errno)); filename, strerror(errno));
goto done; goto done;
} }
/* Open and parse local file into xml */ /* Open and parse local file into xml */
@ -750,26 +749,23 @@ load_config_file(clicon_handle h,
goto done; goto done;
if (xt == NULL) if (xt == NULL)
goto done; goto done;
if ((cbxml = cbuf_new()) == NULL)
// if ((xn = xml_child_i(xt, 0)) != NULL){ goto done;
x = NULL;
if ((cbxml = cbuf_new()) == NULL) while ((x = xml_child_each(xt, x, -1)) != NULL) {
/* Ensure top-level is "config", maybe this is too rough? */
xml_name_set(x, "config");
if (clicon_xml2cbuf(cbxml, x, 0, 0) < 0)
goto done; goto done;
x = NULL; }
while ((x = xml_child_each(xt, x, -1)) != NULL) { if (clicon_rpc_edit_config(h, "candidate",
/* Ensure top-level is "config", maybe this is too rough? */ replace?OP_REPLACE:OP_MERGE,
xml_name_set(x, "config"); cbuf_get(cbxml)) < 0)
if (clicon_xml2cbuf(cbxml, x, 0, 0) < 0) goto done;
goto done; cbuf_free(cbxml);
} // }
if (clicon_rpc_edit_config(h, "candidate",
replace?OP_REPLACE:OP_MERGE,
cbuf_get(cbxml)) < 0)
goto done;
cbuf_free(cbxml);
// }
ret = 0; ret = 0;
done: done:
if (xt) if (xt)
xml_free(xt); xml_free(xt);
if (fd != -1) if (fd != -1)

View file

@ -93,10 +93,16 @@ struct db_element{
/* Keep datastore text in memory so that get operation need only read memory. /* Keep datastore text in memory so that get operation need only read memory.
* Write to file on modification or file change. * Write to file on modification or file change.
* Assumes single backend * Assumes single backend
* Experimental * XXX MOVE TO HANDLE all three below
*/ */
static int xmltree_cache = 1; static int xmltree_cache = 1;
/* Format */
static char *xml_format = "xml";
/* Store xml/json pretty-printed. Or not. */
static int xml_pretty = 1;
/*! Check struct magic number for sanity checks /*! Check struct magic number for sanity checks
* return 0 if OK, -1 if fail. * return 0 if OK, -1 if fail.
*/ */
@ -232,6 +238,12 @@ text_getopt(xmldb_handle xh,
*value = th->th_yangspec; *value = th->th_yangspec;
else if (strcmp(optname, "dbdir") == 0) else if (strcmp(optname, "dbdir") == 0)
*value = th->th_dbdir; *value = th->th_dbdir;
else if (strcmp(optname, "xml_cache") == 0)
*value = &xmltree_cache;
else if (strcmp(optname, "format") == 0)
*value = xml_format;
else if (strcmp(optname, "pretty") == 0)
*value = &xml_pretty;
else{ else{
clicon_err(OE_PLUGIN, 0, "Option %s not implemented by plugin", optname); clicon_err(OE_PLUGIN, 0, "Option %s not implemented by plugin", optname);
goto done; goto done;
@ -243,7 +255,7 @@ text_getopt(xmldb_handle xh,
/*! Set value of generic plugin option. Type of value is given by context /*! Set value of generic plugin option. Type of value is given by context
* @param[in] xh XMLDB handle * @param[in] xh XMLDB handle
* @param[in] optname Option name * @param[in] optname Option name: yangspec, xml_cache, format, prettyprint
* @param[in] value Value of option * @param[in] value Value of option
* @retval 0 OK * @retval 0 OK
* @retval -1 Error * @retval -1 Error
@ -267,6 +279,19 @@ text_setopt(xmldb_handle xh,
else if (strcmp(optname, "xml_cache") == 0){ else if (strcmp(optname, "xml_cache") == 0){
xmltree_cache = (intptr_t)value; xmltree_cache = (intptr_t)value;
} }
else if (strcmp(optname, "format") == 0){
if (strcmp(value,"xml")==0)
xml_format = "xml";
else if (strcmp(value,"json")==0)
xml_format = "json";
else{
clicon_err(OE_PLUGIN, 0, "Option %s unrecognized format: %s", optname, value);
goto done;
}
}
else if (strcmp(optname, "pretty") == 0){
xml_pretty = (intptr_t)value;
}
else{ else{
clicon_err(OE_PLUGIN, 0, "Option %s not implemented by plugin", optname); clicon_err(OE_PLUGIN, 0, "Option %s not implemented by plugin", optname);
goto done; goto done;
@ -430,7 +455,11 @@ text_get(xmldb_handle xh,
goto done; goto done;
} }
/* Parse file into XML tree */ /* Parse file into XML tree */
if ((xml_parse_file(fd, "</config>", yspec, &xt)) < 0) if (strcmp(xml_format,"json")==0){
if ((json_parse_file(fd, yspec, &xt)) < 0)
goto done;
}
else if ((xml_parse_file(fd, "</config>", yspec, &xt)) < 0)
goto done; goto done;
/* Always assert a top-level called "config". /* Always assert a top-level called "config".
To ensure that, deal with two cases: To ensure that, deal with two cases:
@ -445,7 +474,7 @@ text_get(xmldb_handle xh,
if (singleconfigroot(xt, &xt) < 0) if (singleconfigroot(xt, &xt) < 0)
goto done; goto done;
} }
/* Sort XML children according to YANG and ordered-by XXX */ /* Sort XML children according to YANG */
if (xml_child_sort && xml_apply0(xt, CX_ELMNT, xml_sort, NULL) < 0) if (xml_child_sort && xml_apply0(xt, CX_ELMNT, xml_sort, NULL) < 0)
goto done; goto done;
} /* xt == NULL */ } /* xt == NULL */
@ -511,9 +540,11 @@ text_get(xmldb_handle xh,
/* Order XML children according to YANG */ /* Order XML children according to YANG */
if (!xml_child_sort && xml_apply(xt, CX_ELMNT, xml_order, NULL) < 0) if (!xml_child_sort && xml_apply(xt, CX_ELMNT, xml_order, NULL) < 0)
goto done; goto done;
/* XXX again just so default values are placed correctly */ /* Again just so default values are placed correctly */
if (xml_child_sort && xml_apply0(xt, CX_ELMNT, xml_sort, NULL) < 0) if (xml_child_sort && xml_apply0(xt, CX_ELMNT, xml_sort, NULL) < 0)
goto done; goto done;
if (xml_child_sort && xml_apply0(xt, -1, xml_sort_verify, NULL) < 0)
clicon_log(LOG_NOTICE, "%s: verify failed #2", __FUNCTION__);
if (debug>1) if (debug>1)
clicon_xml2file(stderr, xt, 0, 1); clicon_xml2file(stderr, xt, 0, 1);
*xtop = xt; *xtop = xt;
@ -854,7 +885,11 @@ text_put(xmldb_handle xh,
goto done; goto done;
} }
/* Parse file into XML tree */ /* Parse file into XML tree */
if ((xml_parse_file(fd, "</config>", yspec, &x0)) < 0) if (strcmp(xml_format,"json")==0){
if ((json_parse_file(fd, yspec, &x0)) < 0)
goto done;
}
else if ((xml_parse_file(fd, "</config>", yspec, &x0)) < 0)
goto done; goto done;
/* Always assert a top-level called "config". /* Always assert a top-level called "config".
To ensure that, deal with two cases: To ensure that, deal with two cases:
@ -931,7 +966,11 @@ text_put(xmldb_handle xh,
clicon_err(OE_CFG, errno, "Creating file %s", dbfile); clicon_err(OE_CFG, errno, "Creating file %s", dbfile);
goto done; goto done;
} }
if (xml_print(f, x0) < 0) if (strcmp(xml_format,"json")==0){
if (xml2json(f, x0, xml_pretty) < 0)
goto done;
}
else if (clicon_xml2file(f, x0, 0, xml_pretty) < 0)
goto done; goto done;
retval = 0; retval = 0;
done: done:
@ -1315,7 +1354,14 @@ main(int argc,
xpath = argc>5?argv[5]:NULL; xpath = argc>5?argv[5]:NULL;
if (xmldb_get(h, db, xpath, &xt, NULL, 1, NULL) < 0) if (xmldb_get(h, db, xpath, &xt, NULL, 1, NULL) < 0)
goto done; goto done;
clicon_xml2file(stdout, xt, 0, 1); if (strcmp(xml_format,"json")==0){
if (xml2json(stdout, xt, xml_pretty) < 0)
goto done;
}
else{
if (clicon_xml2file(stdout, xt, 0, xml_pretty) < 0)
goto done;
}
} }
else else
if (strcmp(cmd, "put")==0){ if (strcmp(cmd, "put")==0){

View file

@ -39,10 +39,11 @@
/* /*
* Prototypes * Prototypes
*/ */
int json_parse_str(char *str, cxobj **xt);
int xml2json_cbuf(cbuf *cb, cxobj *x, int pretty); int xml2json_cbuf(cbuf *cb, cxobj *x, int pretty);
int xml2json_cbuf_vec(cbuf *cb, cxobj **vec, size_t veclen, int pretty); int xml2json_cbuf_vec(cbuf *cb, cxobj **vec, size_t veclen, int pretty);
int xml2json(FILE *f, cxobj *x, int pretty); int xml2json(FILE *f, cxobj *x, int pretty);
int xml2json_vec(FILE *f, cxobj **vec, size_t veclen, int pretty); int xml2json_vec(FILE *f, cxobj **vec, size_t veclen, int pretty);
int json_parse_str(char *str, cxobj **xt);
int json_parse_file(int fd, yang_spec *yspec, cxobj **xt);
#endif /* _CLIXON_JSON_H */ #endif /* _CLIXON_JSON_H */

View file

@ -160,7 +160,6 @@ int xml_body_int32(cxobj *xb, int32_t *val);
int xml_body_uint32(cxobj *xb, uint32_t *val); int xml_body_uint32(cxobj *xb, uint32_t *val);
int xml_operation(char *opstr, enum operation_type *op); int xml_operation(char *opstr, enum operation_type *op);
char *xml_operation2str(enum operation_type op); char *xml_operation2str(enum operation_type op);
int xml_sort(cxobj *x0, void *arg);
#ifdef XML_COMPAT /* See CHANGELOG */ #ifdef XML_COMPAT /* See CHANGELOG */
/* MANUAL CHANGE: xml_new(name, parent) --> xml_new(name, parent, NULL) */ /* MANUAL CHANGE: xml_new(name, parent) --> xml_new(name, parent, NULL) */

View file

@ -45,8 +45,12 @@ extern int xml_child_sort;
* Prototypes * Prototypes
*/ */
int xml_child_spec(char *name, cxobj *xp, yang_spec *yspec, yang_stmt **yp); int xml_child_spec(char *name, cxobj *xp, yang_spec *yspec, yang_stmt **yp);
int xml_cmp(const void* arg1, const void* arg2);
int xml_sort(cxobj *x0, void *arg);
cxobj *xml_search(cxobj *x, char *name, int yangi, enum rfc_6020 keyword, int keynr, char **keyvec, char **keyval); cxobj *xml_search(cxobj *x, char *name, int yangi, enum rfc_6020 keyword, int keynr, char **keyvec, char **keyval);
cxobj *xml_sort_insert(cxobj *x0, cxobj *x);
cxobj *xml_match(cxobj *x0, char *name, enum rfc_6020 keyword, int keynr, char **keyvec, char **keyval); cxobj *xml_match(cxobj *x0, char *name, enum rfc_6020 keyword, int keynr, char **keyvec, char **keyval);
int match_base_child(cxobj *x0, cxobj *x1c, cxobj **x0cp, yang_stmt *yc); int xml_sort_verify(cxobj *x, void *arg);
int match_base_child(cxobj *x0, cxobj *x1c, cxobj **x0cp, yang_stmt *yc);
#endif /* _CLIXON_XML_SORT_H */ #endif /* _CLIXON_XML_SORT_H */

View file

@ -69,6 +69,12 @@
*/ */
#define VEC_ARRAY 1 #define VEC_ARRAY 1
/* Size of json read buffer when reading from file*/
#define BUFLEN 1024
/* Name of xml top object created by xml parse functions */
#define JSON_TOP_SYMBOL "top"
enum array_element_type{ enum array_element_type{
NO_ARRAY=0, NO_ARRAY=0,
FIRST_ARRAY, FIRST_ARRAY,
@ -185,32 +191,40 @@ json_escape(char *str)
j = 0; j = 0;
for (i=0;i<strlen(str);i++) for (i=0;i<strlen(str);i++)
if (str[i]=='\n') switch (str[i]){
case '\n':
case '\"':
case '\\':
j++; j++;
break;
}
if ((snew = malloc(strlen(str)+1+j))==NULL){ if ((snew = malloc(strlen(str)+1+j))==NULL){
clicon_err(OE_XML, errno, "malloc"); clicon_err(OE_XML, errno, "malloc");
return NULL; return NULL;
} }
j = 0; j = 0;
for (i=0;i<strlen(str);i++) for (i=0;i<strlen(str);i++)
if (str[i]=='\n'){ switch (str[i]){
case '\n':
case '\"':
case '\\':
snew[j++]='\\'; snew[j++]='\\';
snew[j++]='n'; default: /* fall thru */
}
else
snew[j++]=str[i]; snew[j++]=str[i];
}
snew[j++]='\0'; snew[j++]='\0';
return snew; return snew;
} }
/*! Do the actual work of translating XML to JSON /*! Do the actual work of translating XML to JSON
* @param[out] cb Cligen text buffer containing json on exit * @param[out] cb Cligen text buffer containing json on exit
* @param[in] x XML tree structure containing XML to translate * @param[in] x XML tree structure containing XML to translate
* @param[in] arraytype Does x occur in a array (of its parent) and how? * @param[in] arraytype Does x occur in a array (of its parent) and how?
* @param[in] level Indentation level * @param[in] level Indentation level
* @param[in] pretty Pretty-print output (2 means debug) * @param[in] pretty Pretty-print output (2 means debug)
* @param[in] flat Dont print NO_ARRAY object name (for _vec call) * @param[in] flat Dont print NO_ARRAY object name (for _vec call)
* *
* @note Does not work with XML attributes
* The following matrix explains how the mapping is done. * The following matrix explains how the mapping is done.
* You need to understand what arraytype means (no/first/middle/last) * You need to understand what arraytype means (no/first/middle/last)
* and what childtype is (null,body,any) * and what childtype is (null,body,any)
@ -246,10 +260,11 @@ xml2json1_cbuf(cbuf *cb,
int pretty, int pretty,
int flat) int flat)
{ {
int retval = -1; int retval = -1;
int i; int i;
cxobj *xc; cxobj *xc;
enum childtype childt; enum childtype childt;
enum array_element_type xc_arraytype;
childt = childtype(x); childt = childtype(x);
if (pretty==2) if (pretty==2)
@ -326,7 +341,6 @@ xml2json1_cbuf(cbuf *cb,
break; break;
} }
for (i=0; i<xml_child_nr(x); i++){ for (i=0; i<xml_child_nr(x); i++){
enum array_element_type xc_arraytype;
xc = xml_child_i(x, i); xc = xml_child_i(x, i);
xc_arraytype = array_eval(i?xml_child_i(x,i-1):NULL, xc_arraytype = array_eval(i?xml_child_i(x,i-1):NULL,
xc, xc,
@ -628,6 +642,82 @@ json_parse_str(char *str,
return json_parse(str, "", *xt); return json_parse(str, "", *xt);
} }
/*! Read a JSON definition from file and parse it into a parse-tree.
*
* @param[in] fd A file descriptor containing the JSON file (as ASCII characters)
* @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
*
* @code
* cxobj *xt = NULL;
* if (json_parse_file(0, NULL, &xt) < 0)
* err;
* xml_free(xt);
* @endcode
* @note you need to free the xml parse tree after use, using xml_free()
* @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
json_parse_file(int fd,
yang_spec *yspec,
cxobj **xt)
{
int retval = -1;
int ret;
char *jsonbuf = NULL;
int jsonbuflen = BUFLEN; /* start size */
int oldjsonbuflen;
char *ptr;
char ch;
int len = 0;
if ((jsonbuf = malloc(jsonbuflen)) == NULL){
clicon_err(OE_XML, errno, "%s: malloc", __FUNCTION__);
goto done;
}
memset(jsonbuf, 0, jsonbuflen);
ptr = jsonbuf;
while (1){
if ((ret = read(fd, &ch, 1)) < 0){
clicon_err(OE_XML, errno, "%s: read: [pid:%d]\n",
__FUNCTION__,
(int)getpid());
break;
}
if (ret != 0)
jsonbuf[len++] = ch;
if (ret == 0){
if (*xt == NULL)
if ((*xt = xml_new(JSON_TOP_SYMBOL, NULL, NULL)) == NULL)
goto done;
if (len && json_parse(ptr, "", *xt) < 0)
goto done;
break;
}
if (len>=jsonbuflen-1){ /* Space: one for the null character */
oldjsonbuflen = jsonbuflen;
jsonbuflen *= 2;
if ((jsonbuf = realloc(jsonbuf, jsonbuflen)) == NULL){
clicon_err(OE_XML, errno, "%s: realloc", __FUNCTION__);
goto done;
}
memset(jsonbuf+oldjsonbuflen, 0, jsonbuflen-oldjsonbuflen);
ptr = jsonbuf;
}
}
retval = 0;
done:
if (retval < 0 && *xt){
free(*xt);
*xt = NULL;
}
if (jsonbuf)
free(jsonbuf);
return retval;
}
/* /*
* Turn this on to get a json parse and pretty print test program * Turn this on to get a json parse and pretty print test program

View file

@ -63,6 +63,7 @@
#include "clixon_handle.h" #include "clixon_handle.h"
#include "clixon_yang.h" #include "clixon_yang.h"
#include "clixon_xml.h" #include "clixon_xml.h"
#include "clixon_xml_sort.h"
#include "clixon_xml_parse.h" #include "clixon_xml_parse.h"
/* /*
@ -528,6 +529,7 @@ xml_childvec_get(cxobj *x)
* @endcode * @endcode
* @note yspec may be NULL either because it is not known or it is irrelevant, * @note yspec may be NULL either because it is not known or it is irrelevant,
* eg for body or attribute * eg for body or attribute
* @see xml_sort_insert
*/ */
cxobj * cxobj *
xml_new(char *name, xml_new(char *name,
@ -545,7 +547,7 @@ xml_new(char *name,
return NULL; return NULL;
xml_parent_set(x, xp); xml_parent_set(x, xp);
if (xp && xml_child_append(xp, x) < 0) if (xp && xml_child_append(xp, x) < 0)
return NULL; return NULL;
x->x_spec = spec; /* Can be NULL */ x->x_spec = spec; /* Can be NULL */
return x; return x;
@ -1290,7 +1292,6 @@ xml_parse_file(int fd,
if (endtag != NULL) if (endtag != NULL)
endtaglen = strlen(endtag); endtaglen = strlen(endtag);
*xt = NULL;
if ((xmlbuf = malloc(xmlbuflen)) == NULL){ if ((xmlbuf = malloc(xmlbuflen)) == NULL){
clicon_err(OE_XML, errno, "%s: malloc", __FUNCTION__); clicon_err(OE_XML, errno, "%s: malloc", __FUNCTION__);
goto done; goto done;
@ -1558,7 +1559,7 @@ cxvec_append(cxobj *x,
* The tree is traversed depth-first, which at least guarantees that a parent is * The tree is traversed depth-first, which at least guarantees that a parent is
* traversed before a child. * traversed before a child.
* @param[in] xn XML node * @param[in] xn XML node
* @param[in] type matching type or -1 for any * @param[in] type Matching type or -1 for any
* @param[in] fn Callback * @param[in] fn Callback
* @param[in] arg Argument * @param[in] arg Argument
* @retval -1 Error, aborted at first error encounter * @retval -1 Error, aborted at first error encounter
@ -1610,6 +1611,10 @@ xml_apply(cxobj *xn,
} }
/*! Apply a function call on top object and all xml node children recursively /*! Apply a function call on top object and all xml node children recursively
* @param[in] xn XML node
* @param[in] type Matching type or -1 for any
* @param[in] fn Callback
* @param[in] arg Argument
* @retval -1 Error, aborted at first error encounter * @retval -1 Error, aborted at first error encounter
* @retval 0 OK, all nodes traversed (subparts may have been skipped) * @retval 0 OK, all nodes traversed (subparts may have been skipped)
* @retval 1 OK, aborted on first fn returned 1 * @retval 1 OK, aborted on first fn returned 1
@ -1836,93 +1841,6 @@ xml_operation2str(enum operation_type op)
} }
} }
/*! Help function to qsort for sorting entries in xml child vector
* @param[in] arg1
* @param[in] arg2
* @retval 0 If equal
* @retval <0 if arg1 is less than arg2
* @retval >0 if arg1 is greater than arg2
* @note must be in clixon_xml.c since it uses internal (hidden) struct xml
*/
static int
xml_cmp(const void* arg1,
const void* arg2)
{
struct xml *x1 = *(struct xml**)arg1;
struct xml *x2 = *(struct xml**)arg2;
yang_stmt *y1;
yang_stmt *y2;
int yi1;
int yi2;
cvec *cvk = NULL; /* vector of index keys */
cg_var *cvi;
int equal = 0;
char *b1;
char *b2;
char *keyname;
if (x1 == NULL){
if (x2 == NULL)
return 0;
else
return -1;
}
else if (x2 == NULL)
return 1;
y1 = xml_spec(x1);
y2 = xml_spec(x2);
if (y1==NULL || y2==NULL)
return 0; /* just ignore */
if (y1 != y2){
yi1 = yang_order(y1);
yi2 = yang_order(y2);
if ((equal = yi1-yi2) != 0)
return equal;
}
/* Now y1=y2, same Yang spec, can only be list or leaf-list,
* sort according to key
*/
if (yang_find((yang_node*)y1, Y_ORDERED_BY, "user") != NULL)
return 0; /* Ordered by user: maintain existing order */
switch (y1->ys_keyword){
case Y_LEAF_LIST: /* Match with name and value */
equal = strcmp(xml_body(x1), xml_body(x2));
break;
case Y_LIST: /* Match with key values
* Use Y_LIST cache (see struct yang_stmt)
*/
cvk = y1->ys_cvec; /* Use Y_LIST cache, see ys_populate_list() */
cvi = NULL;
while ((cvi = cvec_each(cvk, cvi)) != NULL) {
keyname = cv_string_get(cvi);
b1 = xml_find_body(x1, keyname);
b2 = xml_find_body(x2, keyname);
if ((equal = strcmp(b1,b2)) != 0)
goto done;
}
equal = 0;
break;
default:
break;
}
done:
return equal;
}
/*! Sort children of an XML node
* Assume populated by yang spec.
* @param[in] x0 XML node
* @param[in] arg Dummy so it can be called by xml_apply()
* @note must be in clixon_xml.c since it uses internal (hidden) struct xml
*/
int
xml_sort(cxobj *x,
void *arg)
{
qsort(x->x_childvec, x->x_childvec_len, sizeof(struct xml*), xml_cmp);
return 0;
}
/* /*
* Turn this on to get a xml parse and pretty print test program * Turn this on to get a xml parse and pretty print test program

View file

@ -229,7 +229,10 @@ xml2cli(FILE *f,
term = xml_name(x); term = xml_name(x);
if (prepend0) if (prepend0)
fprintf(f, "%s ", prepend0); fprintf(f, "%s ", prepend0);
fprintf(f, "%s\n", term); if (index(term, ' '))
fprintf(f, "\"%s\"\n", term);
else
fprintf(f, "%s\n", term);
retval = 0; retval = 0;
goto done; goto done;
} }

View file

@ -100,6 +100,80 @@ xml_child_spec(char *name,
return 0; return 0;
} }
/*! Help function to qsort for sorting entries in xml child vector
* @param[in] arg1 - actually cxobj**
* @param[in] arg2 - actually cxobj**
* @retval 0 If equal
* @retval <0 if arg1 is less than arg2
* @retval >0 if arg1 is greater than arg2
* @note args are pointer ot pointers, to fit into qsort cmp function
* @see xml_cmp1 Similar, but for one object
*/
int
xml_cmp(const void* arg1,
const void* arg2)
{
cxobj *x1 = *(struct xml**)arg1;
cxobj *x2 = *(struct xml**)arg2;
yang_stmt *y1;
yang_stmt *y2;
int yi1;
int yi2;
cvec *cvk = NULL; /* vector of index keys */
cg_var *cvi;
int equal = 0;
char *b1;
char *b2;
char *keyname;
if (x1 == NULL){
if (x2 == NULL)
return 0;
else
return -1;
}
else if (x2 == NULL)
return 1;
y1 = xml_spec(x1);
y2 = xml_spec(x2);
if (y1==NULL || y2==NULL)
return 0; /* just ignore */
if (y1 != y2){
yi1 = yang_order(y1);
yi2 = yang_order(y2);
if ((equal = yi1-yi2) != 0)
return equal;
}
/* Now y1=y2, same Yang spec, can only be list or leaf-list,
* sort according to key
*/
if (yang_find((yang_node*)y1, Y_ORDERED_BY, "user") != NULL)
return 0; /* Ordered by user: maintain existing order */
switch (y1->ys_keyword){
case Y_LEAF_LIST: /* Match with name and value */
equal = strcmp(xml_body(x1), xml_body(x2));
break;
case Y_LIST: /* Match with key values
* Use Y_LIST cache (see struct yang_stmt)
*/
cvk = y1->ys_cvec; /* Use Y_LIST cache, see ys_populate_list() */
cvi = NULL;
while ((cvi = cvec_each(cvk, cvi)) != NULL) {
keyname = cv_string_get(cvi);
b1 = xml_find_body(x1, keyname);
b2 = xml_find_body(x2, keyname);
if ((equal = strcmp(b1,b2)) != 0)
goto done;
}
equal = 0;
break;
default:
break;
}
done:
return equal;
}
/*! /*!
* @param[in] yangi Yang order * @param[in] yangi Yang order
* @param[in] keynr Length of keyvec/keyval vector when applicable * @param[in] keynr Length of keyvec/keyval vector when applicable
@ -109,6 +183,7 @@ xml_child_spec(char *name,
* @retval 0 If equal (or userorder set) * @retval 0 If equal (or userorder set)
* @retval <0 if arg1 is less than arg2 * @retval <0 if arg1 is less than arg2
* @retval >0 if arg1 is greater than arg2 * @retval >0 if arg1 is greater than arg2
* @see xml_cmp Similar, but for two objects
*/ */
static int static int
xml_cmp1(cxobj *x, xml_cmp1(cxobj *x,
@ -155,6 +230,21 @@ xml_cmp1(cxobj *x,
return 0; /* should not reach here */ return 0; /* should not reach here */
} }
/*! Sort children of an XML node
* Assume populated by yang spec.
* @param[in] x0 XML node
* @param[in] arg Dummy so it can be called by xml_apply()
*/
int
xml_sort(cxobj *x,
void *arg)
{
qsort(xml_childvec_get(x), xml_child_nr(x), sizeof(cxobj *), xml_cmp);
return 0;
}
/*! Special case search for ordered-by user where linear sort is used
*/
static cxobj * static cxobj *
xml_search_userorder(cxobj *x0, xml_search_userorder(cxobj *x0,
yang_stmt *y, yang_stmt *y,
@ -236,7 +326,7 @@ xml_search1(cxobj *x0,
return NULL; return NULL;
} }
/*! /*! Find XML children using binary search
* @param[in] yangi yang child order * @param[in] yangi yang child order
* @param[in] keynr Length of keyvec/keyval vector when applicable * @param[in] keynr Length of keyvec/keyval vector when applicable
* @param[in] keyvec Array of of yang key identifiers * @param[in] keyvec Array of of yang key identifiers
@ -255,6 +345,63 @@ xml_search(cxobj *x0,
0, xml_child_nr(x0)); 0, xml_child_nr(x0));
} }
static int
xml_insert_pos(cxobj *x0,
cxobj *x,
int low,
int upper)
{
int mid;
cxobj *xc;
int cmp;
int i;
if (upper < low)
return low; /* not found */
mid = (low + upper) / 2;
if (mid >= xml_child_nr(x0))
return xml_child_nr(x0);
xc = xml_child_i(x0, mid);
cmp = xml_cmp(&x, &xc);
if (cmp == 0){
/* Special case: append last of equals if ordered by user */
for (i=mid+1;i<xml_child_nr(x0);i++){
xc = xml_child_i(x0, i);
if (xml_cmp(&x, &xc) != 0)
break;
mid=i; /* still ok */
}
return mid;
}
else if (cmp < 0)
return xml_insert_pos(x0, x, low, mid-1);
else
return xml_insert_pos(x0, x, mid+1, upper);
}
/*! Add xml object in sorted position
* @param[in] x0 XML parent node.
* @param[in] x XML node (to insert)
* Assume already under x0
* XXX WORK IN PROGRESS
*/
cxobj *
xml_sort_insert(cxobj *x0,
cxobj *x)
{
int pos;
/* find closest to x, insert after pos. */
xml_rm(x);
pos = xml_insert_pos(x0, x, 0, xml_child_nr(x0));
fprintf(stderr, "%d\n", pos);
#if 0
if (pos < xml_child_nr(x0))
xml_child_i_set(x0, pos);
xml_parent_set(x, x0);
#endif
return x;
}
/*! Find matching xml child given name and optional key values /*! Find matching xml child given name and optional key values
* container: x0, y->keyword, name * container: x0, y->keyword, name
* list: x0, y->keyword, y->key, name * list: x0, y->keyword, y->key, name
@ -350,6 +497,34 @@ xml_match(cxobj *x0,
ok: ok:
return x; return x;
} }
/*! Verify all children of XML node are sorted according to xml_sort()
* @param[in] x XML node. Check its children
* @param[in] arg Dummy. Ensures xml_apply can be used with this fn
@ @retval 0 Sorted
@ @retval -1 Not sorted
* @see xml_apply
*/
int
xml_sort_verify(cxobj *x0,
void *arg)
{
int retval = -1;
cxobj *x = NULL;
cxobj *xprev = NULL;
while ((x = xml_child_each(x0, x, -1)) != NULL) {
if (xprev != NULL){ /* Check xprev <= x */
if (xml_cmp(&xprev, &x) > 0)
goto done;
}
xprev = x;
}
retval = 0;
done:
return retval;
}
/*! Given child tree x1c, find matching child in base tree x0 /*! Given child tree x1c, find matching child in base tree x0
* param[in] x0 Base tree node * param[in] x0 Base tree node
* param[in] x1c Modification tree child * param[in] x1c Modification tree child

View file

@ -21,8 +21,7 @@ sudo clixon_backend -z -f $cfg
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
err err
fi fi
new "start backend" new "start backend -s init -f $cfg"
# start new backend
sudo clixon_backend -s init -f $cfg sudo clixon_backend -s init -f $cfg
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
err err

View file

@ -171,7 +171,7 @@ if [ -z "$pid" ]; then
err "backend already dead" err "backend already dead"
fi fi
# kill backend # kill backend
#sudo clixon_backend -zf $cfg sudo clixon_backend -zf $cfg
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
err "kill backend" err "kill backend"
fi fi

View file

@ -86,7 +86,7 @@ if [ $? -ne 0 ]; then
err err
fi fi
new "start backend" new "start backend -s init -f $cfg -y $fyang"
# start new backend # start new backend
sudo clixon_backend -s init -f $cfg -y $fyang sudo clixon_backend -s init -f $cfg -y $fyang
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
@ -147,7 +147,7 @@ if [ -z "$pid" ]; then
err "backend already dead" err "backend already dead"
fi fi
# kill backend # kill backend
#sudo clixon_backend -zf $cfg sudo clixon_backend -zf $cfg
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
err "kill backend" err "kill backend"
fi fi

View file

@ -70,6 +70,18 @@ module clixon-config {
} }
} }
} }
typedef xmldb_format{
description
"Format of TEXT xml database format.";
type enumeration{
enum xml{
description "Save and load xmldb as XML";
}
enum json{
description "Save and load xmldb as JSON";
}
}
}
container config { container config {
leaf CLICON_CONFIGFILE{ leaf CLICON_CONFIGFILE{
type string; type string;
@ -241,6 +253,19 @@ module clixon-config {
If set, XML candidate/running parsed tree is stored in memory If set, XML candidate/running parsed tree is stored in memory
If not set, candidate/running is always accessed via disk."; If not set, candidate/running is always accessed via disk.";
} }
leaf CLICON_XMLDB_FORMAT {
type xmldb_format;
default xml;
description "XMLDB datastore format.";
}
leaf CLICON_XMLDB_PRETTY {
type boolean;
default true;
description
"XMLDB datastore pretty print.
If set, insert spaces and line-feeds making the XML/JSON human
readable. If not set, make the XML/JSON more compact.";
}
leaf CLICON_XML_SORT { leaf CLICON_XML_SORT {
type boolean; type boolean;
default true; default true;