diff --git a/CHANGELOG.md b/CHANGELOG.md
index 703ddc73..5c72bdb0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,35 +1,20 @@
# Clixon Changelog
## 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.
* New CLICON_XML_SORT configuration option. Default is 1. Disable by setting to 0.
* 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.
* 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()
-
-* 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.
+ * Cached keys in yang Y_LIST node as cligen vector, see ys_populate_list()
* 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 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.
+
+* 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.
* xml_new(name, parent) --> xml_new(name, xn_parent, yspec)
* xml_new_spec(name, parent, spec) --> xml_new(name, parent, spec)
* clicon_xml_parse(&xt, format, ...) --> xml_parse_va(&xt, yspec, format, ...)
@@ -42,6 +27,24 @@
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)
Thanks to Matthew Smith, Joe Loeliger at Netgate; Fredrik Pettai at
diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c
index 7d722979..ef094a39 100644
--- a/apps/backend/backend_main.c
+++ b/apps/backend/backend_main.c
@@ -666,6 +666,8 @@ main(int argc, char **argv)
int sockfamily;
char *xmldb_plugin;
int xml_cache;
+ int xml_pretty;
+ char *xml_format;
/* In the startup, logs to stderr & syslog and debug flag set later */
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 (xmldb_setopt(h, "xml_cache", (void*)(intptr_t)xml_cache) < 0)
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 */
startup_mode = clicon_startup_mode(h);
if (startup_mode == -1){ /* Old style, fragmented mode, phase out */
diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c
index cd063168..82c19c0b 100644
--- a/apps/cli/cli_common.c
+++ b/apps/cli/cli_common.c
@@ -724,8 +724,7 @@ load_config_file(clicon_handle h,
opstr = cv_string_get(cvec_i(argv, 1));
if (strcmp(opstr, "merge") == 0)
replace = 0;
- else
- if (strcmp(opstr, "replace") == 0)
+ else if (strcmp(opstr, "replace") == 0)
replace = 1;
else{
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);
if (stat(filename, &st) < 0){
clicon_err(OE_UNIX, 0, "load_config: stat(%s): %s",
- filename, strerror(errno));
+ filename, strerror(errno));
goto done;
}
/* Open and parse local file into xml */
@@ -750,26 +749,23 @@ load_config_file(clicon_handle h,
goto done;
if (xt == NULL)
goto done;
-
- // if ((xn = xml_child_i(xt, 0)) != NULL){
-
- if ((cbxml = cbuf_new()) == NULL)
+ if ((cbxml = cbuf_new()) == NULL)
+ goto done;
+ x = 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;
- x = 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;
- }
- if (clicon_rpc_edit_config(h, "candidate",
- replace?OP_REPLACE:OP_MERGE,
- cbuf_get(cbxml)) < 0)
- 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;
- done:
+ done:
if (xt)
xml_free(xt);
if (fd != -1)
diff --git a/datastore/text/clixon_xmldb_text.c b/datastore/text/clixon_xmldb_text.c
index c58059b0..cdf7aea8 100644
--- a/datastore/text/clixon_xmldb_text.c
+++ b/datastore/text/clixon_xmldb_text.c
@@ -93,10 +93,16 @@ struct db_element{
/* Keep datastore text in memory so that get operation need only read memory.
* Write to file on modification or file change.
* Assumes single backend
- * Experimental
+ * XXX MOVE TO HANDLE all three below
*/
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
* return 0 if OK, -1 if fail.
*/
@@ -232,6 +238,12 @@ text_getopt(xmldb_handle xh,
*value = th->th_yangspec;
else if (strcmp(optname, "dbdir") == 0)
*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{
clicon_err(OE_PLUGIN, 0, "Option %s not implemented by plugin", optname);
goto done;
@@ -243,7 +255,7 @@ text_getopt(xmldb_handle xh,
/*! Set value of generic plugin option. Type of value is given by context
* @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
* @retval 0 OK
* @retval -1 Error
@@ -267,6 +279,19 @@ text_setopt(xmldb_handle xh,
else if (strcmp(optname, "xml_cache") == 0){
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{
clicon_err(OE_PLUGIN, 0, "Option %s not implemented by plugin", optname);
goto done;
@@ -430,7 +455,11 @@ text_get(xmldb_handle xh,
goto done;
}
/* Parse file into XML tree */
- if ((xml_parse_file(fd, "", 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, "", yspec, &xt)) < 0)
goto done;
/* Always assert a top-level called "config".
To ensure that, deal with two cases:
@@ -445,7 +474,7 @@ text_get(xmldb_handle xh,
if (singleconfigroot(xt, &xt) < 0)
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)
goto done;
} /* xt == NULL */
@@ -511,9 +540,11 @@ text_get(xmldb_handle xh,
/* Order XML children according to YANG */
if (!xml_child_sort && xml_apply(xt, CX_ELMNT, xml_order, NULL) < 0)
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)
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)
clicon_xml2file(stderr, xt, 0, 1);
*xtop = xt;
@@ -854,7 +885,11 @@ text_put(xmldb_handle xh,
goto done;
}
/* Parse file into XML tree */
- if ((xml_parse_file(fd, "", 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, "", yspec, &x0)) < 0)
goto done;
/* Always assert a top-level called "config".
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);
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;
retval = 0;
done:
@@ -1315,7 +1354,14 @@ main(int argc,
xpath = argc>5?argv[5]:NULL;
if (xmldb_get(h, db, xpath, &xt, NULL, 1, NULL) < 0)
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
if (strcmp(cmd, "put")==0){
diff --git a/lib/clixon/clixon_json.h b/lib/clixon/clixon_json.h
index 00fed07e..e18279b5 100644
--- a/lib/clixon/clixon_json.h
+++ b/lib/clixon/clixon_json.h
@@ -39,10 +39,11 @@
/*
* Prototypes
*/
-int json_parse_str(char *str, cxobj **xt);
int xml2json_cbuf(cbuf *cb, cxobj *x, 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_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 */
diff --git a/lib/clixon/clixon_xml.h b/lib/clixon/clixon_xml.h
index 59a0749b..f793ee11 100644
--- a/lib/clixon/clixon_xml.h
+++ b/lib/clixon/clixon_xml.h
@@ -160,7 +160,6 @@ int xml_body_int32(cxobj *xb, int32_t *val);
int xml_body_uint32(cxobj *xb, uint32_t *val);
int xml_operation(char *opstr, enum operation_type *op);
char *xml_operation2str(enum operation_type op);
-int xml_sort(cxobj *x0, void *arg);
#ifdef XML_COMPAT /* See CHANGELOG */
/* MANUAL CHANGE: xml_new(name, parent) --> xml_new(name, parent, NULL) */
diff --git a/lib/clixon/clixon_xml_sort.h b/lib/clixon/clixon_xml_sort.h
index 56dcf29b..fc3a1e1a 100644
--- a/lib/clixon/clixon_xml_sort.h
+++ b/lib/clixon/clixon_xml_sort.h
@@ -45,8 +45,12 @@ extern int xml_child_sort;
* Prototypes
*/
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_sort_insert(cxobj *x0, cxobj *x);
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 */
diff --git a/lib/src/clixon_json.c b/lib/src/clixon_json.c
index a19070ee..e557ecb3 100644
--- a/lib/src/clixon_json.c
+++ b/lib/src/clixon_json.c
@@ -69,6 +69,12 @@
*/
#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{
NO_ARRAY=0,
FIRST_ARRAY,
@@ -185,32 +191,40 @@ json_escape(char *str)
j = 0;
for (i=0;i will be:
+ * @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
diff --git a/lib/src/clixon_xml.c b/lib/src/clixon_xml.c
index 106c8344..ae63e306 100644
--- a/lib/src/clixon_xml.c
+++ b/lib/src/clixon_xml.c
@@ -63,6 +63,7 @@
#include "clixon_handle.h"
#include "clixon_yang.h"
#include "clixon_xml.h"
+#include "clixon_xml_sort.h"
#include "clixon_xml_parse.h"
/*
@@ -528,6 +529,7 @@ xml_childvec_get(cxobj *x)
* @endcode
* @note yspec may be NULL either because it is not known or it is irrelevant,
* eg for body or attribute
+ * @see xml_sort_insert
*/
cxobj *
xml_new(char *name,
@@ -545,7 +547,7 @@ xml_new(char *name,
return NULL;
xml_parent_set(x, xp);
- if (xp && xml_child_append(xp, x) < 0)
+ if (xp && xml_child_append(xp, x) < 0)
return NULL;
x->x_spec = spec; /* Can be NULL */
return x;
@@ -1290,7 +1292,6 @@ xml_parse_file(int fd,
if (endtag != NULL)
endtaglen = strlen(endtag);
- *xt = NULL;
if ((xmlbuf = malloc(xmlbuflen)) == NULL){
clicon_err(OE_XML, errno, "%s: malloc", __FUNCTION__);
goto done;
@@ -1558,7 +1559,7 @@ cxvec_append(cxobj *x,
* The tree is traversed depth-first, which at least guarantees that a parent is
* traversed before a child.
* @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] arg Argument
* @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
+ * @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 0 OK, all nodes traversed (subparts may have been skipped)
* @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
diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c
index 5d91ce2c..0cfdda27 100644
--- a/lib/src/clixon_xml_map.c
+++ b/lib/src/clixon_xml_map.c
@@ -229,7 +229,10 @@ xml2cli(FILE *f,
term = xml_name(x);
if (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;
goto done;
}
diff --git a/lib/src/clixon_xml_sort.c b/lib/src/clixon_xml_sort.c
index 1ac1197c..9212ca58 100644
--- a/lib/src/clixon_xml_sort.c
+++ b/lib/src/clixon_xml_sort.c
@@ -100,6 +100,80 @@ xml_child_spec(char *name,
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] 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 arg1 is less than arg2
* @retval >0 if arg1 is greater than arg2
+ * @see xml_cmp Similar, but for two objects
*/
static int
xml_cmp1(cxobj *x,
@@ -155,6 +230,21 @@ xml_cmp1(cxobj *x,
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 *
xml_search_userorder(cxobj *x0,
yang_stmt *y,
@@ -236,7 +326,7 @@ xml_search1(cxobj *x0,
return NULL;
}
-/*!
+/*! Find XML children using binary search
* @param[in] yangi yang child order
* @param[in] keynr Length of keyvec/keyval vector when applicable
* @param[in] keyvec Array of of yang key identifiers
@@ -255,6 +345,63 @@ xml_search(cxobj *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;ikeyword, name
* list: x0, y->keyword, y->key, name
@@ -350,6 +497,34 @@ xml_match(cxobj *x0,
ok:
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
* param[in] x0 Base tree node
* param[in] x1c Modification tree child
diff --git a/test/test_cli.sh b/test/test_cli.sh
index d5cbe3f3..a77329ca 100755
--- a/test/test_cli.sh
+++ b/test/test_cli.sh
@@ -21,8 +21,7 @@ sudo clixon_backend -z -f $cfg
if [ $? -ne 0 ]; then
err
fi
-new "start backend"
-# start new backend
+new "start backend -s init -f $cfg"
sudo clixon_backend -s init -f $cfg
if [ $? -ne 0 ]; then
err
diff --git a/test/test_order.sh b/test/test_order.sh
index 0e07f47c..16ea036f 100755
--- a/test/test_order.sh
+++ b/test/test_order.sh
@@ -171,7 +171,7 @@ if [ -z "$pid" ]; then
err "backend already dead"
fi
# kill backend
-#sudo clixon_backend -zf $cfg
+sudo clixon_backend -zf $cfg
if [ $? -ne 0 ]; then
err "kill backend"
fi
diff --git a/test/test_yang.sh b/test/test_yang.sh
index 858954d4..506109b3 100755
--- a/test/test_yang.sh
+++ b/test/test_yang.sh
@@ -86,7 +86,7 @@ if [ $? -ne 0 ]; then
err
fi
-new "start backend"
+new "start backend -s init -f $cfg -y $fyang"
# start new backend
sudo clixon_backend -s init -f $cfg -y $fyang
if [ $? -ne 0 ]; then
@@ -147,7 +147,7 @@ if [ -z "$pid" ]; then
err "backend already dead"
fi
# kill backend
-#sudo clixon_backend -zf $cfg
+sudo clixon_backend -zf $cfg
if [ $? -ne 0 ]; then
err "kill backend"
fi
diff --git a/yang/clixon-config@2017-12-27.yang b/yang/clixon-config@2017-12-27.yang
index 50fb9981..1bbc1dcb 100644
--- a/yang/clixon-config@2017-12-27.yang
+++ b/yang/clixon-config@2017-12-27.yang
@@ -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 {
leaf CLICON_CONFIGFILE{
type string;
@@ -241,6 +253,19 @@ module clixon-config {
If set, XML candidate/running parsed tree is stored in memory
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 {
type boolean;
default true;