diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0083e82a..f5142b9a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,7 @@
# Clixon CHANGELOG
+
+- removed api_path extension from internal netconf
+- Strings in xmldb_put not properly encoded, eg eth/0 became eth.00000
## 3.3.0
diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c
index 022e61d6..671ff219 100644
--- a/apps/backend/backend_client.c
+++ b/apps/backend/backend_client.c
@@ -256,8 +256,6 @@ from_client_get_config(clicon_handle h,
* @param[in] xe Netconf request xml tree
* @param[in] mypid Process/session id of calling client
* @param[out] cbret Return xml value cligen buffer
- * CLIXON addition:
- *
*/
static int
from_client_edit_config(clicon_handle h,
@@ -270,10 +268,8 @@ from_client_edit_config(clicon_handle h,
cbuf *cb = NULL;
cxobj *xret = NULL;
cxobj *xc;
- cxobj *xfilter;
cxobj *x;
enum operation_type operation = OP_MERGE;
- char *api_path = NULL;
int piddb;
if ((target = netconf_db_find(xn, "target")) == NULL){
@@ -293,9 +289,6 @@ from_client_edit_config(clicon_handle h,
piddb);
goto ok;
}
- /* ie /> */
- if ((xfilter = xpath_first(xn, "filter")) != NULL)
- api_path = xml_find_value(xfilter, "select");
if ((x = xpath_first(xn, "default-operation")) != NULL){
if (xml_operation(xml_body(x), &operation) < 0){
cprintf(cbret, ""
@@ -307,7 +300,7 @@ from_client_edit_config(clicon_handle h,
}
}
if ((xc = xpath_first(xn, "config")) != NULL){
- if (xmldb_put(h, target, operation, api_path, xc) < 0){
+ if (xmldb_put(h, target, operation, xc) < 0){
cprintf(cbret, ""
"operation-failed"
"protocol"
diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c
index 4600eedd..52f11bdc 100644
--- a/apps/backend/backend_main.c
+++ b/apps/backend/backend_main.c
@@ -194,7 +194,7 @@ rundb_main(clicon_handle h,
if (clicon_xml_parse_file(fd, &xt, "") < 0)
goto done;
if ((xn = xml_child_i(xt, 0)) != NULL)
- if (xmldb_put(h, "tmp", OP_MERGE, NULL, xn) < 0)
+ if (xmldb_put(h, "tmp", OP_MERGE, xn) < 0)
goto done;
if (candidate_commit(h, "tmp") < 0)
goto done;
diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c
index 847f1d7f..71f58047 100644
--- a/apps/cli/cli_common.c
+++ b/apps/cli/cli_common.c
@@ -207,38 +207,64 @@ cli_dbxml(clicon_handle h,
{
int retval = -1;
char *str = NULL;
- char *xkfmt; /* xml key format */
- char *xk = NULL; /* xml key */
+ char *api_path_fmt; /* xml key format */
+ char *api_path = NULL; /* xml key */
cg_var *cval;
int len;
cg_var *arg;
cbuf *cb = NULL;
+ yang_spec *yspec;
+ cxobj *xbot = NULL; /* xpath, NULL if datastore */
+ yang_node *y = NULL; /* yang spec of xpath */
+ cxobj *xtop = NULL; /* xpath root */
+ cxobj *xa; /* attribute */
+ cxobj *xb; /* body */
if (cvec_len(argv) != 1){
clicon_err(OE_PLUGIN, 0, "%s: Requires one element to be xml key format string", __FUNCTION__);
goto done;
}
- arg = cvec_i(argv, 0);
- xkfmt = cv_string_get(arg);
- if (xmlkeyfmt2key(xkfmt, cvv, &xk) < 0)
+ if ((yspec = clicon_dbspec_yang(h)) == NULL){
+ clicon_err(OE_FATAL, 0, "No DB_SPEC");
goto done;
- len = cvec_len(cvv);
- if (len > 1){
- cval = cvec_i(cvv, len-1);
- if ((str = cv2str_dup(cval)) == NULL){
- clicon_err(OE_UNIX, errno, "cv2str_dup");
- goto done;
+ }
+ arg = cvec_i(argv, 0);
+ api_path_fmt = cv_string_get(arg);
+ if (api_path_fmt2api_path(api_path_fmt, cvv, &api_path) < 0)
+ goto done;
+ /* Create config top-of-tree */
+ if ((xtop = xml_new("config", NULL)) == NULL)
+ goto done;
+ xbot = xtop;
+ if (api_path && api_path2xml(api_path, yspec, xtop, &xbot, &y) < 0)
+ goto done;
+ if ((xa = xml_new("operation", xbot)) == NULL)
+ goto done;
+ xml_type_set(xa, CX_ATTR);
+ if (xml_value_set(xa, xml_operation2str(op)) < 0)
+ goto done;
+ if (y->yn_keyword != Y_LIST){
+ len = cvec_len(cvv);
+ if (len > 1){
+ cval = cvec_i(cvv, len-1);
+ if ((str = cv2str_dup(cval)) == NULL){
+ clicon_err(OE_UNIX, errno, "cv2str_dup");
+ goto done;
+ }
+ if ((xb = xml_new("body", xbot)) == NULL)
+ goto done;
+ xml_type_set(xb, CX_BODY);
+ if (xml_value_set(xb, str) < 0)
+ goto done;
}
}
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
- if (str)
- cprintf(cb, "%s", str);
- else
- cprintf(cb, "");
- if (clicon_rpc_edit_config(h, "candidate", op, xk, cbuf_get(cb)) < 0)
+ if (clicon_xml2cbuf(cb, xtop, 0, 0) < 0)
+ goto done;
+ if (clicon_rpc_edit_config(h, "candidate", OP_NONE, cbuf_get(cb)) < 0)
goto done;
if (clicon_autocommit(h)) {
if (clicon_rpc_commit(h) < 0)
@@ -250,8 +276,10 @@ cli_dbxml(clicon_handle h,
cbuf_free(cb);
if (str)
free(str);
- if (xk)
- free(xk);
+ if (api_path)
+ free(api_path);
+ if (xtop)
+ xml_free(xtop);
return retval;
}
@@ -287,6 +315,31 @@ int cli_mergev(clicon_handle h, cvec *vars, cvec *argv)
return cli_merge(h, vars, argv);
}
+int
+cli_create(clicon_handle h, cvec *cvv, cvec *argv)
+{
+ int retval = -1;
+
+ if (cli_dbxml(h, cvv, argv, OP_CREATE) < 0)
+ goto done;
+ retval = 0;
+ done:
+ return retval;
+}
+/*!
+ * @see cli_del
+ */
+int
+cli_remove(clicon_handle h, cvec *cvv, cvec *argv)
+{
+ int retval = -1;
+
+ if (cli_dbxml(h, cvv, argv, OP_REMOVE) < 0)
+ goto done;
+ retval = 0;
+ done:
+ return retval;
+}
int
cli_del(clicon_handle h, cvec *cvv, cvec *argv)
@@ -704,7 +757,6 @@ load_config_file(clicon_handle h,
}
if (clicon_rpc_edit_config(h, "candidate",
replace?OP_REPLACE:OP_MERGE,
- "",
cbuf_get(cbxml)) < 0)
goto done;
cbuf_free(cbxml);
@@ -1135,7 +1187,7 @@ cli_copy_config(clicon_handle h,
cbuf_reset(cb);
/* create xml copy tree and merge it with database configuration */
clicon_xml2cbuf(cb, x2, 0, 0);
- if (clicon_rpc_edit_config(h, db, OP_MERGE, NULL, cbuf_get(cb)) < 0)
+ if (clicon_rpc_edit_config(h, db, OP_MERGE, cbuf_get(cb)) < 0)
goto done;
retval = 0;
done:
diff --git a/apps/cli/cli_generate.c b/apps/cli/cli_generate.c
index b37babad..a5eca1d7 100644
--- a/apps/cli/cli_generate.c
+++ b/apps/cli/cli_generate.c
@@ -64,7 +64,7 @@
#include "cli_generate.h"
/* This is the default callback function. But this is typically overwritten */
-#define GENERATE_CALLBACK "cli_set"
+#define GENERATE_CALLBACK "overwrite_me"
/* variable expand function */
#define GENERATE_EXPAND_XMLDB "expand_dbvar"
@@ -115,13 +115,12 @@ cli_expand_var_generate(clicon_handle h,
enum cv_type cvtype,
cbuf *cb0,
int options,
- uint8_t fraction_digits
- )
+ uint8_t fraction_digits)
{
int retval = -1;
- char *xkfmt = NULL;
+ char *api_path_fmt = NULL;
- if (yang2xmlkeyfmt(ys, 1, &xkfmt) < 0)
+ if (yang2api_path_fmt(ys, 1, &api_path_fmt) < 0)
goto done;
cprintf(cb0, "|<%s:%s", ys->ys_argument,
cv_type2str(cvtype));
@@ -129,15 +128,15 @@ cli_expand_var_generate(clicon_handle h,
cprintf(cb0, " fraction-digits:%u", fraction_digits);
cprintf(cb0, " %s(\"candidate\",\"%s\")>",
GENERATE_EXPAND_XMLDB,
- xkfmt);
+ api_path_fmt);
retval = 0;
done:
- if (xkfmt)
- free(xkfmt);
+ if (api_path_fmt)
+ free(api_path_fmt);
return retval;
}
-/*! Create callback with xmlkey format string as argument
+/*! Create callback with api_path format string as argument
* @param[in] h clicon handle
* @param[in] ys yang_stmt of the node at hand
* @param[in] cb0 The string where the result format string is inserted.
@@ -149,15 +148,16 @@ cli_callback_generate(clicon_handle h,
cbuf *cb0)
{
int retval = -1;
- char *xkfmt = NULL;
+ char *api_path_fmt = NULL;
- if (yang2xmlkeyfmt(ys, 0, &xkfmt) < 0)
+ if (yang2api_path_fmt(ys, 0, &api_path_fmt) < 0)
goto done;
- cprintf(cb0, ",%s(\"%s\")", GENERATE_CALLBACK, xkfmt);
+ cprintf(cb0, ",%s(\"%s\")", GENERATE_CALLBACK,
+ api_path_fmt);
retval = 0;
done:
- if (xkfmt)
- free(xkfmt);
+ if (api_path_fmt)
+ free(api_path_fmt);
return retval;
}
@@ -166,8 +166,7 @@ static int yang2cli_stmt(clicon_handle h, yang_stmt *ys,
enum genmodel_type gt,
int level);
-/*
- * Check for completion (of already existent values), ranges (eg range[min:max]) and
+/*! Check for completion (of already existent values), ranges (eg range[min:max]) and
* patterns, (eg regexp:"[0.9]*").
*/
static int
@@ -403,7 +402,6 @@ yang2cli_leaf(clicon_handle h,
return retval;
}
-
static int
yang2cli_container(clicon_handle h,
yang_stmt *ys,
diff --git a/apps/cli/cli_show.c b/apps/cli/cli_show.c
index 95df07b8..f86fc9da 100644
--- a/apps/cli/cli_show.c
+++ b/apps/cli/cli_show.c
@@ -99,10 +99,10 @@ expand_dbvar(void *h,
cvec *helptexts)
{
int retval = -1;
- char *xkfmt;
+ char *api_path;
char *dbstr;
cxobj *xt = NULL;
- char *xkpath = NULL;
+ char *xpath = NULL;
cxobj **xvec = NULL;
size_t xlen = 0;
cxobj *x;
@@ -129,23 +129,24 @@ expand_dbvar(void *h,
goto done;
}
if ((cv = cvec_i(argv, 1)) == NULL){
- clicon_err(OE_PLUGIN, 0, "%s: Error when accessing argument ");
+ clicon_err(OE_PLUGIN, 0, "%s: Error when accessing argument ");
goto done;
}
- xkfmt = cv_string_get(cv);
- /* xkfmt = /interface/%s/address/%s
+ api_path = cv_string_get(cv);
+ /* api_path = /interface/%s/address/%s
--> ^/interface/eth0/address/.*$
--> /interface/[name=eth0]/address
*/
- if (xmlkeyfmt2xpath(xkfmt, cvv, &xkpath) < 0)
+ if (api_path_fmt2xpath(api_path, cvv, &xpath) < 0)
goto done;
+ /* XXX read whole configuration, why not send xpath? */
if (clicon_rpc_get_config(h, dbstr, "/", &xt) < 0)
goto done;
/* One round to detect duplicates
* XXX The code below would benefit from some cleanup
*/
j = 0;
- if (xpath_vec(xt, xkpath, &xvec, &xlen) < 0)
+ if (xpath_vec(xt, xpath, &xvec, &xlen) < 0)
goto done;
for (i = 0; i < xlen; i++) {
char *str;
@@ -190,8 +191,8 @@ expand_dbvar(void *h,
free(xvec);
if (xt)
xml_free(xt);
- if (xkpath)
- free(xkpath);
+ if (xpath)
+ free(xpath);
return retval;
}
int
diff --git a/apps/cli/clixon_cli_api.h b/apps/cli/clixon_cli_api.h
index 1426a670..8d3c7be2 100644
--- a/apps/cli/clixon_cli_api.h
+++ b/apps/cli/clixon_cli_api.h
@@ -82,6 +82,9 @@ int cli_setv(clicon_handle h, cvec *vars, cvec *argv);
int cli_merge(clicon_handle h, cvec *vars, cvec *argv);
int cli_mergev(clicon_handle h, cvec *vars, cvec *argv);
+int cli_create(clicon_handle h, cvec *vars, cvec *argv);
+int cli_remove(clicon_handle h, cvec *vars, cvec *argv);
+
int cli_del(clicon_handle h, cvec *vars, cvec *argv);
int cli_delv(clicon_handle h, cvec *vars, cvec *argv);
diff --git a/apps/restconf/README.md b/apps/restconf/README.md
index 81905802..9dfb15d4 100644
--- a/apps/restconf/README.md
+++ b/apps/restconf/README.md
@@ -67,8 +67,7 @@ curl -sX POST -d '{"clicon":{"interfaces":{"interface":{"name":"eth1","type":"et
Start the restconf fastcgi program with debug flag:
```
-sudo su -c "/www-data/clixon_restconf -Df /usr/local/etc/routing.conf" -s /bin/sh www-
-data
+sudo su -c "/www-data/clixon_restconf -Df /usr/local/etc/routing.conf" -s /bin/sh www-data
```
Look at syslog:
```
diff --git a/apps/restconf/restconf_lib.c b/apps/restconf/restconf_lib.c
index 150c50c7..dd1d6d20 100644
--- a/apps/restconf/restconf_lib.c
+++ b/apps/restconf/restconf_lib.c
@@ -69,11 +69,12 @@ notfound(FCGX_Request *r)
path = FCGX_GetParam("DOCUMENT_URI", r->envp);
FCGX_FPrintF(r->out, "Status: 404\r\n"); /* 404 not found */
FCGX_FPrintF(r->out, "Content-Type: text/html\r\n\r\n");
- FCGX_FPrintF(r->out, "Clixon Not Found
\n");
+ FCGX_FPrintF(r->out, "Not Found
\n");
FCGX_FPrintF(r->out, "The requested URL %s was not found on this server.\n",
path);
return 0;
}
+
int
badrequest(FCGX_Request *r)
{
@@ -89,6 +90,16 @@ badrequest(FCGX_Request *r)
return 0;
}
+int
+conflict(FCGX_Request *r)
+{
+ clicon_debug(1, "%s", __FUNCTION__);
+ FCGX_FPrintF(r->out, "Status: 409\r\n"); /* 409 Conflict */
+ FCGX_FPrintF(r->out, "Content-Type: text/html\r\n\r\n");
+ FCGX_FPrintF(r->out, "Data resource already exists
\n");
+ return 0;
+}
+
/*! Specialization of clicon_debug with xml tree */
int
clicon_debug_xml(int dbglevel,
diff --git a/apps/restconf/restconf_lib.h b/apps/restconf/restconf_lib.h
index f133b15f..b0cdf7c9 100644
--- a/apps/restconf/restconf_lib.h
+++ b/apps/restconf/restconf_lib.h
@@ -45,6 +45,7 @@
*/
int notfound(FCGX_Request *r);
int badrequest(FCGX_Request *r);
+int conflict(FCGX_Request *r);
int clicon_debug_xml(int dbglevel, char *str, cxobj *cx);
int test(FCGX_Request *r, int dbg);
cbuf *readdata(FCGX_Request *r);
diff --git a/apps/restconf/restconf_main.c b/apps/restconf/restconf_main.c
index 90d0e3e2..cd778f15 100644
--- a/apps/restconf/restconf_main.c
+++ b/apps/restconf/restconf_main.c
@@ -141,7 +141,7 @@ request_process(clicon_handle h,
int auth = 0;
clicon_debug(1, "%s", __FUNCTION__);
- path = FCGX_GetParam("DOCUMENT_URI", r->envp);
+ path = FCGX_GetParam("REQUEST_URI", r->envp);
query = FCGX_GetParam("QUERY_STRING", r->envp);
if ((pvec = clicon_strsep(path, "/", &pn)) == NULL)
goto done;
@@ -359,7 +359,7 @@ main(int argc,
goto done;
}
clicon_debug(1, "------------");
- if ((path = FCGX_GetParam("DOCUMENT_URI", r->envp)) != NULL){
+ if ((path = FCGX_GetParam("REQUEST_URI", r->envp)) != NULL){
if (strncmp(path, RESTCONF_API_ROOT, strlen(RESTCONF_API_ROOT)) == 0 ||
strncmp(path, RESTCONF_API_ROOT, strlen(RESTCONF_API_ROOT)-1) == 0)
request_process(h, r);
diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c
index 3194c4d6..8c1a9073 100644
--- a/apps/restconf/restconf_methods.c
+++ b/apps/restconf/restconf_methods.c
@@ -34,8 +34,7 @@
*/
/*
- * See draft-ietf-netconf-restconf-13.txt [draft]
- * See draft-ietf-netconf-restconf-17.txt [draft]
+ * See rfc8040
* sudo apt-get install libfcgi-dev
* gcc -o fastcgi fastcgi.c -lfcgi
@@ -120,7 +119,7 @@ Mapping netconf error-tag -> status code
#include "restconf_methods.h"
/*! REST OPTIONS method
- * According to restconf (Sec 3.5.1.1 in [draft])
+ * According to restconf
* @param[in] h Clixon handle
* @param[in] r Fastcgi request handle
* @code
@@ -227,7 +226,7 @@ api_data_head(clicon_handle h,
}
/*! REST GET method
- * According to restconf (Sec 3.5.1.1 in [draft])
+ * According to restconf
* @param[in] h Clixon handle
* @param[in] r Fastcgi request handle
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
@@ -259,77 +258,23 @@ api_data_get(clicon_handle h,
return api_data_get_gen(h, r, pcvec, pi, qvec, 0);
}
-/*! Generic edit-config method: PUT/POST/PATCH
- */
-static int
-api_data_edit(clicon_handle h,
- FCGX_Request *r,
- char *api_path,
- cvec *pcvec,
- int pi,
- cvec *qvec,
- char *data,
- enum operation_type operation)
-{
- int retval = -1;
- int i;
- cxobj *xdata = NULL;
- cbuf *cbx = NULL;
- cxobj *x;
-
- clicon_debug(1, "%s api_path:\"%s\" json:\"%s\"",
- __FUNCTION__,
- api_path, data);
- for (i=0; i");
- x = NULL;
- while ((x = xml_child_each(xdata, x, -1)) != NULL) {
- if (clicon_xml2cbuf(cbx, x, 0, 0) < 0)
- goto done;
- }
- cprintf(cbx, "");
- clicon_debug(1, "%s xml: %s api_path:%s",__FUNCTION__, cbuf_get(cbx), api_path);
- if (clicon_rpc_edit_config(h, "candidate",
- operation,
- api_path,
- cbuf_get(cbx)) < 0){
- notfound(r);
- goto done;
- }
-
- if (clicon_rpc_commit(h) < 0)
- goto done;
- FCGX_SetExitStatus(201, r->out); /* Created */
- FCGX_FPrintF(r->out, "Content-Type: text/plain\r\n");
- FCGX_FPrintF(r->out, "\r\n");
- retval = 0;
- done:
- clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
- if (xdata)
- xml_free(xdata);
- if (cbx)
- cbuf_free(cbx);
- return retval;
-}
-
-
/*! REST POST method
* @param[in] h CLIXON handle
* @param[in] r Fastcgi request handle
- * @param[in] api_path According to restconf (Sec 3.5.1.1 in [draft])
+ * @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040)
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where to start pcvec
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] data Stream input data
+ * @note We map post to edit-config create.
POST:
+ target resource type is datastore --> create a top-level resource
+ target resource type is data resource --> create child resource
+
+ The message-body MUST contain exactly one instance of the
+ expected data resource. The data model for the child tree is the
+ subtree, as defined by YANG for the child resource.
+
If the POST method succeeds, a "201 Created" status-line is returned
and there is no response message-body. A "Location" header
identifying the child resource that was created MUST be present in
@@ -338,7 +283,6 @@ api_data_edit(clicon_handle h,
If the data resource already exists, then the POST request MUST fail
and a "409 Conflict" status-line MUST be returned.
* Netconf: (nc:operation="create") | invoke an RPC operation * @example
-
*/
int
api_data_post(clicon_handle h,
@@ -349,13 +293,91 @@ api_data_post(clicon_handle h,
cvec *qvec,
char *data)
{
- return api_data_edit(h, r, api_path, pcvec, pi, qvec, data, OP_CREATE);
+ enum operation_type op = OP_CREATE;
+ int retval = -1;
+ int i;
+ cxobj *xdata = NULL;
+ cxobj *xtop = NULL; /* xpath root */
+ cbuf *cbx = NULL;
+ cxobj *xbot = NULL;
+ cxobj *x;
+ yang_node *y = NULL;
+ yang_spec *yspec;
+ cxobj *xa;
+
+ clicon_debug(1, "%s api_path:\"%s\" json:\"%s\"",
+ __FUNCTION__,
+ api_path, data);
+ if ((yspec = clicon_dbspec_yang(h)) == NULL){
+ clicon_err(OE_FATAL, 0, "No DB_SPEC");
+ goto done;
+ }
+ for (i=0; iout); /* Created */
+ FCGX_FPrintF(r->out, "Content-Type: text/plain\r\n");
+ // XXX api_path can be null FCGX_FPrintF(r->out, "Location: %s\r\n", api_path);
+ FCGX_FPrintF(r->out, "\r\n");
+ retval = 0;
+ done:
+ clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
+ if (xtop)
+ xml_free(xtop);
+ if (xdata)
+ xml_free(xdata);
+ if (cbx)
+ cbuf_free(cbx);
+ return retval;
}
/*! Generic REST PUT method
* @param[in] h CLIXON handle
* @param[in] r Fastcgi request handle
- * @param[in] api_path According to restconf (Sec 3.5.1.1 in [draft])
+ * @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040)
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where to start pcvec
* @param[in] qvec Vector of query string (QUERY_STRING)
@@ -379,14 +401,96 @@ api_data_put(clicon_handle h,
cvec *qvec,
char *data)
{
- /* XXX: OP_CREATE? */
- return api_data_edit(h, r, api_path, pcvec, pi, qvec, data, OP_REPLACE);
+ int retval = -1;
+ enum operation_type op = OP_REPLACE;
+ int i;
+ cxobj *xdata = NULL;
+ cbuf *cbx = NULL;
+ cxobj *x;
+ cxobj *xbot = NULL;
+ cxobj *xtop;
+ cxobj *xp;
+ yang_node *y = NULL;
+ yang_spec *yspec;
+ cxobj *xa;
+
+ clicon_debug(1, "%s api_path:\"%s\" json:\"%s\"",
+ __FUNCTION__,
+ api_path, data);
+ if ((yspec = clicon_dbspec_yang(h)) == NULL){
+ clicon_err(OE_FATAL, 0, "No DB_SPEC");
+ goto done;
+ }
+ for (i=0; iout); /* Created */
+ FCGX_FPrintF(r->out, "Content-Type: text/plain\r\n");
+ FCGX_FPrintF(r->out, "\r\n");
+ retval = 0;
+ done:
+ clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
+ if (xtop)
+ xml_free(xtop);
+ if (xdata)
+ xml_free(xdata);
+ if (cbx)
+ cbuf_free(cbx);
+ return retval;
+
}
/*! Generic REST PATCH method
* @param[in] h CLIXON handle
* @param[in] r Fastcgi request handle
- * @param[in] api_path According to restconf (Sec 3.5.1.1 in [draft])
+ * @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040)
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where to start pcvec
* @param[in] qvec Vector of query string (QUERY_STRING)
@@ -402,13 +506,15 @@ api_data_patch(clicon_handle h,
cvec *qvec,
char *data)
{
- return api_data_edit(h, r, api_path, pcvec, pi, qvec, data, OP_MERGE);
+ badrequest(r);
+ // return api_data_edit(h, r, api_path, pcvec, pi, qvec, data, OP_MERGE);
+ return 0;
}
/*! Generic REST DELETE method
* @param[in] h CLIXON handle
* @param[in] r Fastcgi request handle
- * @param[in] api_path According to restconf (Sec 3.5.1.1 in [draft])
+ * @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040)
* @param[in] pi Offset, where path starts
* Example:
* curl -X DELETE http://127.0.0.1/restconf/data/interfaces/interface=eth0
@@ -422,14 +528,39 @@ api_data_delete(clicon_handle h,
{
int retval = -1;
int i;
+ cxobj *xtop = NULL; /* xpath root */
+ cxobj *xbot = NULL;
+ cxobj *xa;
+ cbuf *cbx = NULL;
+ yang_node *y = NULL;
+ yang_spec *yspec;
+ enum operation_type op = OP_DELETE;
clicon_debug(1, "%s api_path:%s", __FUNCTION__, api_path);
+ if ((yspec = clicon_dbspec_yang(h)) == NULL){
+ clicon_err(OE_FATAL, 0, "No DB_SPEC");
+ goto done;
+ }
for (i=0; i") < 0){
+ OP_NONE,
+ cbuf_get(cbx)) < 0){
notfound(r);
goto done;
}
@@ -440,6 +571,10 @@ api_data_delete(clicon_handle h,
FCGX_FPrintF(r->out, "\r\n");
retval = 0;
done:
+ if (cbx)
+ cbuf_free(cbx);
+ if (xtop)
+ xml_free(xtop);
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
return retval;
}
diff --git a/configure.ac b/configure.ac
index c7f30742..7d5c12df 100644
--- a/configure.ac
+++ b/configure.ac
@@ -43,7 +43,7 @@ AC_INIT(lib/clixon/clixon.h.in)
CLIXON_VERSION_MAJOR="3"
CLIXON_VERSION_MINOR="3"
-CLIXON_VERSION_PATCH="0"
+CLIXON_VERSION_PATCH="1"
CLIXON_VERSION="\"${CLIXON_VERSION_MAJOR}.${CLIXON_VERSION_MINOR}.${CLIXON_VERSION_PATCH}\""
# Fix to specific version (eg 3.5) or head (3)
CLIGEN_VERSION="3"
diff --git a/datastore/datastore_client.c b/datastore/datastore_client.c
index d9ceaf61..d2a023f8 100644
--- a/datastore/datastore_client.c
+++ b/datastore/datastore_client.c
@@ -89,7 +89,7 @@ usage(char *argv0)
"\t-m \tYang module. Mandatory\n"
"and command is either:\n"
"\tget \n"
- "\tput (merge|replace|create|delete|remove) \n"
+ "\tput (merge|replace|create|delete|remove) \n"
"\tcopy \n"
"\tlock \n"
"\tunlock\n"
@@ -220,7 +220,7 @@ main(int argc, char **argv)
fprintf(stdout, "\n");
}
else if (strcmp(cmd, "put")==0){
- if (argc != 3 && argc != 4){
+ if (argc != 3){
clicon_err(OE_DB, 0, "Unexpected nr of args: %d", argc);
usage(argv0);
}
@@ -228,13 +228,11 @@ main(int argc, char **argv)
clicon_err(OE_DB, 0, "Unrecognized operation: %s", argv[1]);
usage(argv0);
}
- if (argc == 4){
- if (clicon_xml_parse_str(argv[3], &xt) < 0)
- goto done;
- if (xml_rootchild(xt, 0, &xt) < 0)
- goto done;
- }
- if (xmldb_put(h, db, op, argv[2], xt) < 0)
+ if (clicon_xml_parse_str(argv[2], &xt) < 0)
+ goto done;
+ if (xml_rootchild(xt, 0, &xt) < 0)
+ goto done;
+ if (xmldb_put(h, db, op, xt) < 0)
goto done;
}
else if (strcmp(cmd, "copy")==0){
diff --git a/datastore/keyvalue/Makefile.in b/datastore/keyvalue/Makefile.in
index 9a116997..27c88f27 100644
--- a/datastore/keyvalue/Makefile.in
+++ b/datastore/keyvalue/Makefile.in
@@ -65,7 +65,7 @@ all: $(PLUGIN)
-include $(DESTDIR)$(datarootdir)/clixon/clixon.mk
$(PLUGIN): $(SRC)
- $(CC) $(CPPFLAGS) $(INCLUDES) $(CFLAGS) -shared -o $@ -lc $^ $(LIBS)
+ $(CC) $(CPPFLAGS) $(INCLUDES) $(CFLAGS) $(LDFLAGS) -shared -o $@ -lc $^ $(LIBS)
clean:
rm -f $(PLUGIN) $(OBJS) *.core
@@ -81,12 +81,12 @@ distclean: clean
install: $(PLUGIN)
install -d $(DESTDIR)$(clixon_LIBDIR)/xmldb
- install $(PLUGIN) $(DESTDIR)$(clixon_LIBDIR)/xmldb;
+ install $(PLUGIN) $(DESTDIR)$(clixon_LIBDIR)/xmldb
install-include:
uninstall:
- rm -rf $(DESTDIR)$(clixon_LIBDIR)/xmldb/$(PLUGIN);
+ rm -rf $(DESTDIR)$(clixon_LIBDIR)/xmldb/$(PLUGIN)
TAGS:
find . -name '*.[chyl]' -print | etags -
diff --git a/datastore/keyvalue/clixon_keyvalue.c b/datastore/keyvalue/clixon_keyvalue.c
index 68b2527d..7207acfb 100644
--- a/datastore/keyvalue/clixon_keyvalue.c
+++ b/datastore/keyvalue/clixon_keyvalue.c
@@ -50,15 +50,15 @@
* The relations between the functions and formats are as follows:
*
* +-----------------+ +-----------------+
- * | yang-stmt | yang2xmlkeyfmt | xmlkeyfmt | xmlkeyfmt2xpath
+ * | yang-stmt | yang2api_path_fmt | api_path_fmt | api_path_fmt2xpath
* | list aa,leaf k | ----------------->| /aa=%s |---------------->
* +-----------------+ +-----------------+
* |
- * | xmlkeyfmt2key
+ * | api_path_fmt2api_path
* | k=17
* v
* +-------------------+ +-----------------+
- * | xml-tree/cxobj | xmlkey2xml | xmlkey RFC3986|
+ * | xml-tree/cxobj | xmlkey2xml |api_path RFC3986|
* | 17| <------------- | /aa=17 |
* +-------------------+ +-----------------+
*
@@ -68,7 +68,7 @@
*
* Paths through the code (for coverage)
* cli_callback_generate +----------------+
- * cli_expand_var_generate | yang2xmlkeyfmt |
+ * cli_expand_var_generate | yang2api_path_fmt |
* yang -------------> | |
* +----------------+
* xmldb_get_tree
@@ -785,494 +785,6 @@ put(char *dbfile,
return retval;
}
-/*! Modify database provided an XML database key and an operation
- * @param[in] kh Keyvalue handle
- * @param[in] db Database name
- * @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc
- * @param[in] xk XML Key, eg /aa/bb=17/name
- * @param[in] val Key value, eg "17"
-
- * @retval 0 OK
- * @retval -1 Error
- * @code
- * if (xmldb_put_xkey(h, db, OP_MERGE, "/aa/bb=17/name", "17") < 0)
- * err;
- * @endcode
- * @see xmldb_put with xml-tree, no path
- */
-static int
-xmldb_put_xkey(struct kv_handle *kh,
- char *db,
- enum operation_type op,
- char *xk,
- char *val)
-
-{
- int retval = -1;
- yang_stmt *y = NULL;
- yang_stmt *ykey;
- char **vec = NULL;
- int nvec;
- char **valvec = NULL;
- int nvalvec;
- int i;
- int j;
- char *name;
- char *restval;
- cg_var *cvi;
- cvec *cvk = NULL; /* vector of index keys */
- char *val2 = NULL;
- cbuf *ckey=NULL; /* partial keys */
- cbuf *csubkey=NULL; /* partial keys */
- cbuf *crx=NULL; /* partial keys */
- char *keyname;
- int exists;
- int npairs;
- struct db_pair *pairs;
- yang_spec *yspec;
- char *filename = NULL;
-
- // clicon_log(LOG_WARNING, "%s", __FUNCTION__);
- if ((yspec = kh->kh_yangspec) == NULL){
- clicon_err(OE_YANG, ENOENT, "No yang spec");
- goto done;
- }
- if (kv_db2file(kh, db, &filename) < 0)
- goto done;
- if (xk == NULL || *xk!='/'){
- clicon_err(OE_DB, 0, "Invalid api_path: %s", xk);
- goto done;
- }
- if ((ckey = cbuf_new()) == NULL){
- clicon_err(OE_UNIX, errno, "cbuf_new");
- goto done;
- }
- if ((csubkey = cbuf_new()) == NULL){
- clicon_err(OE_UNIX, errno, "cbuf_new");
- goto done;
- }
- if ((vec = clicon_strsep(xk, "/", &nvec)) == NULL)
- goto done;
- /* Remove trailing '/'. Like in /a/ -> /a */
- if (nvec > 1 && !strlen(vec[nvec-1]))
- nvec--;
- if (nvec < 2){
- clicon_err(OE_XML, 0, "Malformed key: %s", xk);
- goto done;
- }
- i = 1;
- while (i name:x restval=1,2 */
- if ((restval = index(name, '=')) != NULL){
- *restval = '\0';
- restval++;
- }
- if (i==1){
- if (strlen(name)==0 && (op==OP_DELETE || op == OP_REMOVE)){
- /* Special handling of "/" */
- cprintf(ckey, "/");
- break;
- }
- else if ((y = yang_find_topnode(yspec, name)) == NULL){
- clicon_err(OE_UNIX, errno, "No yang node found: %s", name);
- goto done;
- }
- }
- else
- if ((y = yang_find_syntax((yang_node*)y, name)) == NULL){
- clicon_err(OE_UNIX, errno, "No yang node found: %s", name);
- goto done;
- }
- if ((op==OP_DELETE || op == OP_REMOVE) &&
- y->ys_keyword == Y_LEAF &&
- y->ys_parent->yn_keyword == Y_LIST &&
- yang_key_match(y->ys_parent, y->ys_argument))
- /* Special rule if key, dont write last key-name, rm whole*/;
- else
- cprintf(ckey, "/%s", name);
- i++;
- switch (y->ys_keyword){
- case Y_LEAF_LIST:
- if (restval==NULL){
- clicon_err(OE_XML, 0, "malformed key, expected '='");
- goto done;
- }
- cprintf(ckey, "=%s", restval);
- break;
- case Y_LIST:
- if ((ykey = yang_find((yang_node*)y, Y_KEY, NULL)) == NULL){
- clicon_err(OE_XML, errno, "%s: List statement \"%s\" has no key",
- __FUNCTION__, y->ys_argument);
- goto done;
- }
- /* The value is a list of keys: [ ]* */
- if ((cvk = yang_arg2cvec(ykey, " ")) == NULL)
- goto done;
- if (restval==NULL){
- clicon_err(OE_XML, 0, "malformed key, expected '='");
- goto done;
- }
- if (valvec)
- free(valvec);
- if ((valvec = clicon_strsep(restval, ",", &nvalvec)) == NULL)
- goto done;
- if (cvec_len(cvk) != nvalvec){
- clicon_err(OE_XML, errno, "List %s key length mismatch", name);
- goto done;
- }
- cvi = NULL;
- /* Iterate over individual yang keys */
- j = 0;
- while ((cvi = cvec_each(cvk, cvi)) != NULL) {
- keyname = cv_string_get(cvi);
- if (j)
- cprintf(ckey, ",");
- else
- cprintf(ckey, "=");
- val2 = valvec[j++];
- cprintf(ckey, "%s", val2);
- cbuf_reset(csubkey);
- cprintf(csubkey, "%s/%s", cbuf_get(ckey), keyname);
- if (op == OP_MERGE || op == OP_REPLACE || op == OP_CREATE)
- if (db_set(filename, cbuf_get(csubkey), val2, strlen(val2)+1) < 0)
- goto done;
- }
- if (cvk){
- cvec_free(cvk);
- cvk = NULL;
- }
- break;
- default:
- if (op == OP_MERGE || op == OP_REPLACE || op == OP_CREATE)
- if (db_set(filename, cbuf_get(ckey), NULL, 0) < 0)
- goto done;
- break;
- }
- }
- xk = cbuf_get(ckey);
- /* final key */
- switch (op){
- case OP_CREATE:
- if ((exists = db_exists(filename, xk)) < 0)
- goto done;
- if (exists == 1){
- clicon_err(OE_DB, 0, "OP_CREATE: %s already exists in database", xk);
- goto done;
- }
- case OP_MERGE:
- case OP_REPLACE:
- if (y->ys_keyword == Y_LEAF || y->ys_keyword == Y_LEAF_LIST){
- if (db_set(filename, xk, val, val?strlen(val)+1:0) < 0)
- goto done;
- }
- else
- if (db_set(filename, xk, NULL, 0) < 0)
- goto done;
- break;
- case OP_DELETE:
- if ((exists = db_exists(filename, xk)) < 0)
- goto done;
- if (exists == 0){
- clicon_err(OE_DB, 0, "OP_DELETE: %s does not exist in database", xk);
- goto done;
- }
- case OP_REMOVE:
- /* Read in complete database (this can be optimized) */
- if ((crx = cbuf_new()) == NULL){
- clicon_err(OE_UNIX, errno, "cbuf_new");
- goto done;
- }
- cprintf(crx, "^%s.*$", xk);
- if ((npairs = db_regexp(filename, cbuf_get(crx), __FUNCTION__, &pairs, 0)) < 0)
- goto done;
- for (i = 0; i < npairs; i++) {
- if (db_del(filename, pairs[i].dp_key) < 0)
- goto done;
- }
- break;
- default:
- break;
- }
- retval = 0;
- done:
- if (filename)
- free(filename);
- if (ckey)
- cbuf_free(ckey);
- if (csubkey)
- cbuf_free(csubkey);
- if (crx)
- cbuf_free(crx);
- if (cvk)
- cvec_free(cvk);
- if (vec)
- free(vec);
- if (valvec)
- free(valvec);
-
- unchunk_group(__FUNCTION__);
- return retval;
-}
-
-/*! Modify database provided an xml tree, a restconf api_path and an operation
- *
- * @param[in] kh Keyvalue handle
- * @param[in] db running or candidate
- * @param[in] op OP_MERGE: just add it.
- * OP_REPLACE: first delete whole database
- * OP_NONE: operation attribute in xml determines operation
- * @param[in] api_path According to restconf (Sec 3.5.1.1 in [restconf-draft 13])
- * @param[in] xt xml-tree. Top-level symbol is dummy
- * @retval 0 OK
- * @retval -1 Error
- * example:
- * container top {
- * list list1 {
- * key "key1 key2 key3";
- * is referenced as
- * /restconf/data/top/list1=a,,foo
- * @see xmldb_put
- */
-static int
-xmldb_put_restconf_api_path(struct kv_handle *kh,
- char *db,
- enum operation_type op,
- char *xk,
- cxobj *xt)
-{
- int retval = -1;
- yang_stmt *y = NULL;
- yang_stmt *ykey;
- char **vec = NULL;
- int nvec;
-#if 0
- char **valvec = NULL;
- int nvalvec;
- int j;
- char *restval;
-#endif
- int i;
- char *name;
- cg_var *cvi;
- cvec *cvk = NULL; /* vector of index keys */
- char *val2;
- cbuf *ckey=NULL; /* partial keys */
- cbuf *csubkey=NULL; /* partial keys */
- cbuf *crx=NULL; /* partial keys */
- char *keyname;
- int exists;
- int npairs;
- struct db_pair *pairs;
- yang_spec *yspec;
- yang_stmt *ys;
- char *filename = NULL;
- char *key;
- char *keys;
-
- if ((yspec = kh->kh_yangspec) == NULL){
- clicon_err(OE_YANG, ENOENT, "No yang spec");
- goto done;
- }
- if (kv_db2file(kh, db, &filename) < 0)
- goto done;
- if (xk == NULL || *xk!='/'){
- clicon_err(OE_DB, 0, "Invalid api path: %s", xk);
- goto done;
- }
- if ((ckey = cbuf_new()) == NULL){
- clicon_err(OE_UNIX, errno, "cbuf_new");
- goto done;
- }
- if ((csubkey = cbuf_new()) == NULL){
- clicon_err(OE_UNIX, errno, "cbuf_new");
- goto done;
- }
- if ((vec = clicon_strsep(xk, "/", &nvec)) == NULL)
- goto done;
- /* Remove trailing '/'. Like in /a/ -> /a */
- if (nvec > 1 && !strlen(vec[nvec-1]))
- nvec--;
- if (nvec < 2){
- clicon_err(OE_XML, 0, "Malformed key: %s", xk);
- goto done;
- }
- i = 1;
- while (iys_keyword == Y_LEAF &&
- y->ys_parent->yn_keyword == Y_LIST &&
- yang_key_match(y->ys_parent, y->ys_argument))
- /* Special rule if key, dont write last key-name, rm whole*/;
- else
- cprintf(ckey, "/%s", name);
- i++;
- switch (y->ys_keyword){
- case Y_LEAF_LIST:
- /* For leaf-list 'keys' is value, see 3.5.1 in restconf draft */
- val2 = keys;
- cprintf(ckey, "/%s", keys);
- break;
- case Y_LIST:
- if ((ykey = yang_find((yang_node*)y, Y_KEY, NULL)) == NULL){
- clicon_err(OE_XML, errno, "%s: List statement \"%s\" has no key",
- __FUNCTION__, y->ys_argument);
- goto done;
- }
- /* The value is a list of keys: [ ]* */
- if ((cvk = yang_arg2cvec(ykey, " ")) == NULL)
- goto done;
-#if 0
- if (restval==NULL){
- clicon_err(OE_XML, 0, "malformed key, expected '='");
- goto done;
- }
- if (valvec)
- free(valvec);
- if ((valvec = clicon_strsep(restval, ",", &nvalvec)) == NULL)
- goto done;
- if (cvec_len(cvk) != nvalvec){
- clicon_err(OE_XML, errno, "List %s key length mismatch", name);
- goto done;
- }
- j = 0;
-#endif
- cvi = NULL;
- /* Iterate over individual yang keys */
- while ((cvi = cvec_each(cvk, cvi)) != NULL) {
- keyname = cv_string_get(cvi);
-#if 0
- if (j)
- cprintf(ckey, ",");
- else
- cprintf(ckey, "=");
- val2 = valvec[j++];
- cprintf(ckey, "%s", val2);
-#else
- // val2 = vec[i++]; /* No */
- val2 = keys;
- if (i>nvec){ /* XXX >= ? */
- clicon_err(OE_XML, errno, "List %s without argument", name);
- goto done;
- }
- cprintf(ckey, "=%s", val2);
-#endif
- cbuf_reset(csubkey);
- cprintf(csubkey, "%s/%s", cbuf_get(ckey), keyname);
- if (op == OP_MERGE || op == OP_REPLACE || op == OP_CREATE)
- if (db_set(filename, cbuf_get(csubkey), val2, strlen(val2)+1) < 0)
- goto done;
- }
- if (cvk){
- cvec_free(cvk);
- cvk = NULL;
- }
- break;
- default:
- if (op == OP_MERGE || op == OP_REPLACE || op == OP_CREATE)
- if (db_set(filename, cbuf_get(ckey), NULL, 0) < 0)
- goto done;
- break;
- }
- }
- key = cbuf_get(ckey);
- /* final key */
- switch (op){
- case OP_CREATE:
- if ((exists = db_exists(filename, key)) < 0)
- goto done;
- if (exists == 1){
- clicon_err(OE_DB, 0, "OP_CREATE: %s already exists in database", key);
- goto done;
- }
- case OP_MERGE:
- case OP_REPLACE:
- if (xt==NULL){
- clicon_err(OE_DB, 0, "%s: no xml when yang node %s required",
- __FUNCTION__, y->ys_argument);
- goto done;
- }
- if ((ys = yang_find_syntax((yang_node*)y, xml_name(xt))) == NULL){
- clicon_err(OE_DB, 0, "%s: child %s not found under node %s",
- __FUNCTION__, xml_name(xt), y->ys_argument);
- goto done;
- }
- y = ys;
- if (put(filename, xt, y, op, key) < 0)
- goto done;
- break;
- case OP_DELETE:
- if ((exists = db_exists(filename, key)) < 0)
- goto done;
- if (exists == 0){
- clicon_err(OE_DB, 0, "OP_DELETE: %s does not exists in database", key);
- goto done;
- }
- case OP_REMOVE:
- /* Read in complete database (this can be optimized) */
- if ((crx = cbuf_new()) == NULL){
- clicon_err(OE_UNIX, errno, "cbuf_new");
- goto done;
- }
- cprintf(crx, "^%s.*$", key);
- if ((npairs = db_regexp(filename, cbuf_get(crx), __FUNCTION__, &pairs, 0)) < 0)
- goto done;
- for (i = 0; i < npairs; i++) {
- if (db_del(filename, pairs[i].dp_key) < 0)
- goto done;
- }
- break;
- default:
- break;
- }
- retval = 0;
- done:
- // clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
- if (filename)
- free(filename);
- if (ckey)
- cbuf_free(ckey);
- if (csubkey)
- cbuf_free(csubkey);
- if (crx)
- cbuf_free(crx);
- if (cvk)
- cvec_free(cvk);
- if (vec)
- free(vec);
-#if 0
- if (valvec)
- free(valvec);
-#endif
- unchunk_group(__FUNCTION__);
- return retval;
-}
/*! Modify database provided an xml tree and an operation
*
@@ -1299,7 +811,6 @@ int
kv_put(xmldb_handle xh,
char *db,
enum operation_type op,
- char *api_path,
cxobj *xt)
{
int retval = -1;
@@ -1309,9 +820,6 @@ kv_put(xmldb_handle xh,
yang_spec *yspec;
char *dbfilename = NULL;
- if (xt && (xml_child_nr(xt)==0 || xml_body(xt)!= NULL) &&
- api_path && strlen(api_path) && strcmp(api_path,"/"))
- return xmldb_put_xkey(kh, db, op, api_path, xml_body(xt));
if ((yspec = kh->kh_yangspec) == NULL){
clicon_err(OE_YANG, ENOENT, "No yang spec");
goto done;
@@ -1324,28 +832,19 @@ kv_put(xmldb_handle xh,
if (db_init(dbfilename) < 0)
goto done;
}
- if (api_path && strlen(api_path) && strcmp(api_path,"/")){
- // clicon_log(LOG_WARNING, "xmldb_put_restconf_api_path");
- while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL){
- if (xmldb_put_restconf_api_path(kh, db, op, api_path, x) < 0)
- goto done;
- }
- }
- else{
- // clicon_log(LOG_WARNING, "%s", __FUNCTION__);
- while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL){
- if ((ys = yang_find_topnode(yspec, xml_name(x))) == NULL){
- clicon_err(OE_UNIX, errno, "No yang node found: %s", xml_name(x));
- goto done;
- }
- if (put(dbfilename, /* database name */
- x, /* xml root node */
- ys, /* yang statement of xml node */
- op, /* operation, eg merge/delete */
- "" /* aggregate xml key */
- ) < 0)
- goto done;
+ // clicon_log(LOG_WARNING, "%s", __FUNCTION__);
+ while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL){
+ if ((ys = yang_find_topnode(yspec, xml_name(x))) == NULL){
+ clicon_err(OE_UNIX, errno, "No yang node found: %s", xml_name(x));
+ goto done;
}
+ if (put(dbfilename, /* database name */
+ x, /* xml root node */
+ ys, /* yang statement of xml node */
+ op, /* operation, eg merge/delete */
+ "" /* aggregate xml key */
+ ) < 0)
+ goto done;
}
retval = 0;
done:
diff --git a/datastore/keyvalue/clixon_keyvalue.h b/datastore/keyvalue/clixon_keyvalue.h
index 88ae2621..a2a77e6c 100644
--- a/datastore/keyvalue/clixon_keyvalue.h
+++ b/datastore/keyvalue/clixon_keyvalue.h
@@ -41,8 +41,7 @@
*/
int kv_get(xmldb_handle h, char *db, char *xpath,
cxobj **xtop, cxobj ***xvec, size_t *xlen);
-int kv_put(xmldb_handle h, char *db, enum operation_type op,
- char *api_path, cxobj *xt);
+int kv_put(xmldb_handle h, char *db, enum operation_type op, cxobj *xt);
int kv_dump(FILE *f, char *dbfilename, char *rxkey);
int kv_copy(xmldb_handle h, char *from, char *to);
int kv_lock(xmldb_handle h, char *db, int pid);
diff --git a/datastore/text/Makefile.in b/datastore/text/Makefile.in
index 7b7cf6e9..b9dd3806 100644
--- a/datastore/text/Makefile.in
+++ b/datastore/text/Makefile.in
@@ -64,7 +64,7 @@ all: $(PLUGIN)
-include $(DESTDIR)$(datarootdir)/clixon/clixon.mk
$(PLUGIN): $(SRC)
- $(CC) $(CPPFLAGS) $(INCLUDES) $(CFLAGS) -shared -o $@ -lc $^ $(LIBS)
+ $(CC) $(CPPFLAGS) $(INCLUDES) $(CFLAGS) $(LDFLAGS) -shared -o $@ -lc $^ $(LIBS)
clean:
rm -f $(PLUGIN) $(OBJS) *.core
@@ -80,12 +80,12 @@ distclean: clean
install: $(PLUGIN)
install -d $(DESTDIR)$(clixon_LIBDIR)/xmldb
- install $(PLUGIN) $(DESTDIR)$(clixon_LIBDIR)/xmldb;
+ install $(PLUGIN) $(DESTDIR)$(clixon_LIBDIR)/xmldb
install-include:
uninstall:
- rm -rf $(DESTDIR)$(clixon_LIBDIR)/xmldb/$(PLUGIN);
+ rm -rf $(DESTDIR)$(clixon_LIBDIR)/xmldb/$(PLUGIN)
TAGS:
find . -name '*.[chyl]' -print | etags -
diff --git a/datastore/text/clixon_xmldb_text.c b/datastore/text/clixon_xmldb_text.c
index 6ef443e8..e96d7067 100644
--- a/datastore/text/clixon_xmldb_text.c
+++ b/datastore/text/clixon_xmldb_text.c
@@ -265,9 +265,52 @@ xml_spec_populate(cxobj *x,
y = yang_find_syntax((yang_node*)yp, xml_name(x));
else
y = yang_find_topnode(yspec, name); /* still NULL for config */
+ if (y==NULL){
+ clicon_err(OE_XML, EBADF, "yang spec not found for xml node '%s' xml parent name: '%s' yangspec:'",
+ name,
+ xp?xml_name(xp):"", yp?yp->ys_argument:"");
+ goto done;
+ }
xml_spec_set(x, y);
retval = 0;
- // done:
+ done:
+ return retval;
+}
+
+/*! Ensure that xt only has a single sub-element and that is "config"
+ */
+static int
+singleconfigroot(cxobj *xt,
+ cxobj **xp)
+{
+ int retval = -1;
+ cxobj *x = NULL;
+ int i = 0;
+
+ /* There should only be one element and called config */
+ x = NULL;
+ while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL){
+ i++;
+ if (strcmp(xml_name(x), "config")){
+ clicon_err(OE_DB, ENOENT, "Wrong top-element %s expected config",
+ xml_name(x));
+ goto done;
+ }
+ }
+ if (i != 1){
+ clicon_err(OE_DB, ENOENT, "Top-element is not unique, expecting single config");
+ goto done;
+ }
+ x = NULL;
+ while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL){
+ if (xml_rm(x) < 0)
+ goto done;
+ if (xml_free(xt) < 0)
+ goto done;
+ *xp = x;
+ }
+ retval = 0;
+ done:
return retval;
}
@@ -344,10 +387,15 @@ text_get(xmldb_handle xh,
}
/* 2. File is not empty ... -> replace root */
else{
- assert(xml_child_nr(xt)==1);
- if (xml_rootchild(xt, 0, &xt) < 0)
+ /* There should only be one element and called config */
+ if (singleconfigroot(xt, &xt) < 0)
goto done;
}
+ /* Here xt looks like: ... */
+ /* Validate existing config tree */
+ if (xml_apply(xt, CX_ELMNT, xml_spec_populate, yspec) < 0)
+ goto done;
+
/* XXX Maybe the below is general function and should be moved to xmldb? */
if (xpath_vec(xt, xpath?xpath:"/", &xvec, &xlen) < 0)
goto done;
@@ -398,271 +446,19 @@ text_get(xmldb_handle xh,
return retval;
}
-/*! Check if child with fullmatch exists
- * param[in] cvk vector of index keys
-*/
-static cxobj *
-find_keys_vec(cxobj *xt,
- char *name,
- cvec *cvk,
- char **valvec)
-{
- cxobj *xi = NULL;
- int j;
- char *keyname;
- char *val;
- cg_var *cvi;
- char *body;
-
- while ((xi = xml_child_each(xt, xi, CX_ELMNT)) != NULL)
- if (strcmp(xml_name(xi), name) == 0){
- j = 0;
- cvi = NULL;
- while ((cvi = cvec_each(cvk, cvi)) != NULL) {
- keyname = cv_string_get(cvi);
- val = valvec[j++];
- if ((body = xml_find_body(xi, keyname)) == NULL)
- break;
- if (strcmp(body, val))
- break;
- }
- /* All keys must match: loop terminates. */
- if (cvi==NULL)
- return xi;
- }
- return NULL;
-}
-
-/*! Create 'modification' tree from api-path, ie fill in xml tree from the path
- * @param[in] api_path api-path expression
- * @param[in] xt XML tree. Find api-path (or create) in this tree
- * @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc
- * @param[in] yspec Yang spec
- * @param[out] xp Resulting xml tree corresponding to xt
- * @param[out] xparp Parent of xp (xp can be NULL)
- * @param[out] yp Yang spec matching xp
- * @see xmldb_put_xkey for example
- */
-static int
-text_apipath_modify(char *api_path,
- cxobj *xt,
- enum operation_type op,
- yang_spec *yspec,
- cxobj **xp,
- cxobj **xparp,
- yang_node **yp)
-{
- int retval = -1;
- char **vec = NULL;
- int nvec;
- int i;
- int j;
- char *name;
- char *restval;
- yang_stmt *y = NULL;
- yang_stmt *ykey;
- cxobj *x = NULL;
- cxobj *xpar = NULL;
- cxobj *xn = NULL; /* new */
- cxobj *xb; /* body */
- cvec *cvk = NULL; /* vector of index keys */
- char **valvec = NULL;
- int nvalvec;
- cg_var *cvi;
- char *keyname;
- char *val2;
-
- x = xt;
- xpar = xml_parent(xt);
- if (api_path == NULL || *api_path!='/'){
- clicon_err(OE_DB, 0, "Invalid key: %s", api_path);
- goto done;
- }
- if ((vec = clicon_strsep(api_path, "/", &nvec)) == NULL)
- goto done;
- /* Remove trailing '/'. Like in /a/ -> /a */
- if (nvec > 1 && !strlen(vec[nvec-1]))
- nvec--;
- if (nvec < 1){
- clicon_err(OE_XML, 0, "Malformed key: %s", api_path);
- goto done;
- }
- i = 1;
- while (i name:x restval=1,2 */
- if ((restval = index(name, '=')) != NULL){
- *restval = '\0';
- restval++;
- }
- if (y == NULL) /* top-node */
- y = yang_find_topnode(yspec, name);
- else
- y = yang_find_syntax((yang_node*)y, name);
- if (y == NULL){
- clicon_err(OE_YANG, errno, "No yang node found: %s", name);
- goto done;
- }
- i++;
- switch (y->ys_keyword){
- case Y_LEAF_LIST:
- if (restval==NULL){
- clicon_err(OE_XML, 0, "malformed key, expected '='");
- goto done;
- }
- /* See if it exists */
- xn = NULL;
- while ((xn = xml_child_each(x, xn, CX_ELMNT)) != NULL)
- if (strcmp(name, xml_name(xn)) == 0 &&
- strcmp(xml_body(xn),restval)==0)
- break;
- if (xn == NULL){ /* Not found, does not exist */
- switch (op){
- case OP_DELETE: /* not here, should be here */
- clicon_err(OE_XML, 0, "Object to delete does not exist");
- goto done;
- break;
- case OP_REMOVE:
- goto ok; /* not here, no need to remove */
- break;
- case OP_CREATE:
- if (i==nvec) /* Last, dont create here */
- break;
- default:
- //XXX create_keyvalues(cxobj *x,
- if ((xn = xml_new_spec(y->ys_argument, x, y)) == NULL)
- goto done;
- // xml_type_set(xn, CX_ELMNT);
- if ((xb = xml_new("body", xn)) == NULL)
- goto done;
- xml_type_set(xb, CX_BODY);
- if (xml_value_set(xb, restval) < 0)
- goto done;
- break;
- }
- }
- xpar = x;
- x = xn;
- break;
- case Y_LIST:
- /* Get the yang list key */
- if ((ykey = yang_find((yang_node*)y, Y_KEY, NULL)) == NULL){
- clicon_err(OE_XML, errno, "%s: List statement \"%s\" has no key",
- __FUNCTION__, y->ys_argument);
- goto done;
- }
- /* The value is a list of keys: [ ]* */
- if ((cvk = yang_arg2cvec(ykey, " ")) == NULL)
- goto done;
- if (restval==NULL){
- clicon_err(OE_XML, 0, "malformed key, expected '='");
- goto done;
- }
- if (valvec)
- free(valvec);
- if ((valvec = clicon_strsep(restval, ",", &nvalvec)) == NULL)
- goto done;
-
- if (cvec_len(cvk) != nvalvec){
- clicon_err(OE_XML, errno, "List %s key length mismatch", name);
- goto done;
- }
- cvi = NULL;
- /* Check if exists, if not, create */
- if ((xn = find_keys_vec(x, name, cvk, valvec)) == NULL){
- /* create them, but not if delete op */
- switch (op){
- case OP_DELETE: /* not here, should be here */
- clicon_err(OE_XML, 0, "Object to delete does not exist");
- goto done;
- break;
- case OP_REMOVE:
- goto ok; /* not here, no need to remove */
- break;
- default:
- if ((xn = xml_new(name, x)) == NULL)
- goto done;
- xml_type_set(xn, CX_ELMNT);
- break;
- }
- xpar = x;
- x = xn;
- j = 0;
- while ((cvi = cvec_each(cvk, cvi)) != NULL) {
- keyname = cv_string_get(cvi);
- val2 = valvec[j++];
- if ((xn = xml_new(keyname, x)) == NULL)
- goto done;
- xml_type_set(xn, CX_ELMNT);
- if ((xb = xml_new("body", xn)) == NULL)
- goto done;
- xml_type_set(xb, CX_BODY);
- if (xml_value_set(xb, val2) <0)
- goto done;
- }
- }
- else{
- xpar = x;
- x = xn;
- }
- if (cvk){
- cvec_free(cvk);
- cvk = NULL;
- }
- break;
- default: /* eg Y_CONTAINER, Y_LEAF */
- if ((xn = xml_find(x, name)) == NULL){
- switch (op){
- case OP_DELETE: /* not here, should be here */
- clicon_err(OE_XML, 0, "Object to delete does not exist");
- goto done;
- break;
- case OP_REMOVE:
- goto ok; /* not here, no need to remove */
- break;
- case OP_CREATE:
- if (i==nvec) /* Last, dont create here */
- break;
- default:
- if ((xn = xml_new(name, x)) == NULL)
- goto done;
- xml_type_set(xn, CX_ELMNT);
- break;
- }
- }
- else{
- if (op==OP_CREATE && i==nvec){ /* here, should not be here */
- clicon_err(OE_XML, 0, "Object to create already exists");
- goto done;
- }
- }
- xpar = x;
- x = xn;
- break;
- }
- }
- *xp = x;
- *xparp = xpar;
- *yp = (yang_node*)y;
- ok:
- retval = 0;
- done:
- if (vec)
- free(vec);
- if (valvec)
- free(valvec);
- return retval;
-}
-
/*! Given a modification tree, check existing matching child in the base tree
- * param[in] x0 Base tree node
- * param[in] x1c Modification tree child
- * param[in] yc Yang spec of tree child
+ * param[in] x0 Base tree node
+ * param[in] x1c Modification tree child
+ * param[in] yc Yang spec of tree child
+ * param[out] x0cp Matching base tree child (if any)
*/
-static cxobj *
+static int
match_base_child(cxobj *x0,
cxobj *x1c,
- yang_stmt *yc)
+ yang_stmt *yc,
+ cxobj **x0cp)
{
+ int retval = -1;
cxobj *x0c = NULL;
char *keyname;
cvec *cvk = NULL;
@@ -719,10 +515,12 @@ match_base_child(cxobj *x0,
x0c = xml_find(x0, cname);
break;
}
+ *x0cp = x0c;
+ retval = 0;
done:
if (cvk)
cvec_free(cvk);
- return x0c;
+ return retval;
}
/*! Modify a base tree x0 with x1 with yang spec y according to operation op
@@ -730,165 +528,211 @@ match_base_child(cxobj *x0,
* @param[in] x0p Parent of x0
* @param[in] x1 xml tree which modifies base
* @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc
- * @param[in] y Yang spec corresponding to xml-node x0. NULL if x0 is NULL
+ * @param[in] y0 Yang spec corresponding to xml-node x0. NULL if x0 is NULL
* @param[in] yspec Top-level yang spec (if y is NULL)
* Assume x0 and x1 are same on entry and that y is the spec
* @see put in clixon_keyvalue.c
*/
static int
text_modify(cxobj *x0,
+ yang_node *y0,
cxobj *x0p,
cxobj *x1,
- enum operation_type op,
- yang_node *y,
- yang_spec *yspec)
+ enum operation_type op)
{
int retval = -1;
char *opstr;
- char *name;
- char *cname; /* child name */
+ char *x1name;
+ char *x1cname; /* child name */
cxobj *x0c; /* base child */
cxobj *x0b; /* base body */
cxobj *x1c; /* mod child */
char *x1bstr; /* mod body string */
yang_stmt *yc; /* yang child */
- clicon_debug(1, "%s %s", __FUNCTION__, x0?xml_name(x0):"");
+ assert(x1 && xml_type(x1) == CX_ELMNT);
+ assert(y0);
/* Check for operations embedded in tree according to netconf */
- if (x1 && (opstr = xml_find_value(x1, "operation")) != NULL)
+ if ((opstr = xml_find_value(x1, "operation")) != NULL)
if (xml_operation(opstr, &op) < 0)
goto done;
- if (x1 == NULL){
+ x1name = xml_name(x1);
+ if (y0->yn_keyword == Y_LEAF_LIST || y0->yn_keyword == Y_LEAF){
+ x1bstr = xml_body(x1);
switch(op){
- case OP_REPLACE:
- if (x0)
- xml_purge(x0);
- case OP_CREATE:
- case OP_MERGE:
- break;
+ case OP_CREATE:
+ if (x0){
+ clicon_err(OE_XML, 0, "Object to create already exists");
+ goto done;
+ }
+ case OP_NONE: /* fall thru */
+ case OP_MERGE:
+ case OP_REPLACE:
+ if (x0==NULL){
+ // int iamkey=0;
+ if ((x0 = xml_new_spec(x1name, x0p, y0)) == NULL)
+ goto done;
+#if 0
+ /* If it is key I dont want to mark it */
+ if ((iamkey=yang_key_match(y0->yn_parent, x1name)) < 0)
+ goto done;
+ if (!iamkey && op==OP_NONE)
+#else
+ if (op==OP_NONE)
+#endif
+ xml_flag_set(x0, XML_FLAG_NONE); /* Mark for potential deletion */
+ if (x1bstr){ /* empty type does not have body */
+ if ((x0b = xml_new("body", x0)) == NULL)
+ goto done;
+ xml_type_set(x0b, CX_BODY);
+ }
+ }
+ if (x1bstr){
+ if ((x0b = xml_body_get(x0)) == NULL){
+ if ((x0b = xml_new("body", x0)) == NULL)
+ goto done;
+ xml_type_set(x0b, CX_BODY);
+ }
+ if (xml_value_set(x0b, x1bstr) < 0)
+ goto done;
+ }
+ break;
+ case OP_DELETE:
+ if (x0==NULL){
+ clicon_err(OE_XML, 0, "Object to delete does not exist");
+ goto done;
+ }
+ case OP_REMOVE: /* fall thru */
+ if (x0)
+ xml_purge(x0);
+ break;
default:
break;
- }
- }
- else {
- assert(xml_type(x1) == CX_ELMNT);
- name = xml_name(x1);
- if (y && (y->yn_keyword == Y_LEAF_LIST || y->yn_keyword == Y_LEAF)){
- x1bstr = xml_body(x1);
- switch(op){
- case OP_CREATE:
- if (x0){
- clicon_err(OE_XML, 0, "Object to create already exists");
+ } /* switch op */
+ } /* if LEAF|LEAF_LIST */
+ else { /* eg Y_CONTAINER, Y_LIST */
+ switch(op){
+ case OP_CREATE:
+ if (x0){
+ clicon_err(OE_XML, 0, "Object to create already exists");
+ goto done;
+ }
+ case OP_REPLACE: /* fall thru */
+ if (x0){
+ xml_purge(x0);
+ x0 = NULL;
+ }
+ case OP_NONE: /* fall thru */
+ case OP_MERGE:
+ if (x0==NULL){
+ if ((x0 = xml_new_spec(x1name, x0p, y0)) == NULL)
+ goto done;
+ if (op==OP_NONE)
+ xml_flag_set(x0, XML_FLAG_NONE); /* Mark for potential deletion */
+ }
+ /* Loop through children of the modification tree */
+ x1c = NULL;
+ while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) {
+ x1cname = xml_name(x1c);
+ /* Get yang spec of the child */
+ if ((yc = yang_find_syntax(y0, x1cname)) == NULL){
+ clicon_err(OE_YANG, errno, "No yang node found: %s", x1cname);
goto done;
}
- /* Fall thru */
- case OP_NONE: /* XXX */
- case OP_MERGE:
- case OP_REPLACE:
- if (x0==NULL){
- if ((x0 = xml_new_spec(name, x0p, y)) == NULL)
- goto done;
- if (op==OP_NONE)
- xml_flag_set(x0, XML_FLAG_NONE); /* Mark for potential deletion */
- if (x1bstr){ /* empty type does not have body */
- if ((x0b = xml_new("body", x0)) == NULL)
- goto done;
- xml_type_set(x0b, CX_BODY);
- }
- }
- if (x1bstr){
- if ((x0b = xml_body_get(x0)) == NULL){
- if ((x0b = xml_new("body", x0)) == NULL)
- goto done;
- xml_type_set(x0b, CX_BODY);
- }
- if (xml_value_set(x0b, x1bstr) < 0)
- goto done;
- }
- break;
- case OP_DELETE:
- if (x0==NULL){
- clicon_err(OE_XML, 0, "Object to delete does not exist");
+ /* See if there is a corresponding node in the base tree */
+ x0c = NULL;
+ if (yc && match_base_child(x0, x1c, yc, &x0c) < 0)
goto done;
- }
- case OP_REMOVE:
- if (x0)
- xml_purge(x0);
- break;
- default:
- break;
- } /* switch op */
- } /* if LEAF|LEAF_LIST */
- else { /* eg Y_CONTAINER */
- switch(op){
- case OP_CREATE:
- /* top-level object is a special case, ie when
- * x0 parent is NULL
- * or x1 is empty
- */
- if ((x0p && x0) ||
- (x0p==NULL && xml_child_nr(x1) == 0)){
- clicon_err(OE_XML, 0, "Object to create already exists");
+ if (text_modify(x0c, (yang_node*)yc, x0, x1c, op) < 0)
goto done;
- }
- case OP_REPLACE:
- /* top-level object is a special case, ie when
- * x0 parent is NULL,
- * or x1 is empty
- */
- if ((x0p && x0) ||
- (x0p==NULL && xml_child_nr(x1) == 0)){
- xml_purge(x0);
- x0 = NULL;
- }
- case OP_NONE: /* XXX */
- case OP_MERGE:
- if (x0==NULL){
- if ((x0 = xml_new_spec(name, x0p, y)) == NULL)
- goto done;
- if (op==OP_NONE)
- xml_flag_set(x0, XML_FLAG_NONE); /* Mark for potential deletion */
- }
-
- /* Loop through children of the modification tree */
- x1c = NULL;
- while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) {
- cname = xml_name(x1c);
- /* Get yang spec of the child */
- if (y == NULL)
- yc = yang_find_topnode(yspec, cname); /* still NULL for config */
- else{
- if ((yc = yang_find_syntax(y, cname)) == NULL){
- clicon_err(OE_YANG, errno, "No yang node found: %s", cname);
- goto done;
- }
- }
- /* See if there is a corresponding node in the base tree */
- x0c = yc?match_base_child(x0, x1c, yc):NULL;
- if (text_modify(x0c, x0, x1c, op, (yang_node*)yc, yspec) < 0)
- goto done;
- }
- break;
- case OP_DELETE:
- if (x0==NULL){
- clicon_err(OE_XML, 0, "Object to delete does not exist");
- goto done;
- }
- case OP_REMOVE:
- if (x0)
- xml_purge(x0);
- break;
- default:
- break;
- } /* CONTAINER switch op */
- } /* else Y_CONTAINER */
- } /* x1 != NULL */
+ }
+ break;
+ case OP_DELETE:
+ if (x0==NULL){
+ clicon_err(OE_XML, 0, "Object to delete does not exist");
+ goto done;
+ }
+ case OP_REMOVE: /* fall thru */
+ if (x0)
+ xml_purge(x0);
+ break;
+ default:
+ break;
+ } /* CONTAINER switch op */
+ } /* else Y_CONTAINER */
// ok:
retval = 0;
done:
return retval;
}
+/*! Modify a top-level base tree x0 with modification tree x1
+ * @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)
+ * @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc
+ * @see text_modify
+ */
+static int
+text_modify_top(cxobj *x0,
+ cxobj *x1,
+ yang_spec *yspec,
+ enum operation_type op)
+{
+ int retval = -1;
+ char *x1cname; /* child name */
+ cxobj *x0c; /* base child */
+ cxobj *x1c; /* mod child */
+ yang_stmt *yc; /* yang child */
+
+ /* Assure top-levels are 'config' */
+ assert(x0 && strcmp(xml_name(x0),"config")==0);
+ assert(x1 && strcmp(xml_name(x1),"config")==0);
+
+ /* Special case if x1 is empty, top-level only */
+ if (!xml_child_nr(x1)){ /* base tree not empty */
+ if (xml_child_nr(x0))
+ switch(op){
+ case OP_DELETE:
+ case OP_REMOVE:
+ case OP_REPLACE:
+ x0c = NULL;
+ while ((x0c = xml_child_each(x0, x0c, CX_ELMNT)) != NULL)
+ xml_purge(x0c);
+ break;
+ default:
+ break;
+ }
+ else /* base tree empty */
+ switch(op){
+ case OP_DELETE:
+ clicon_err(OE_XML, 0, "Object to delete does not exist");
+ break;
+ default:
+ break;
+ }
+ }
+ /* Loop through children of the modification tree */
+ x1c = NULL;
+ while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) {
+ x1cname = xml_name(x1c);
+ /* Get yang spec of the child */
+ if ((yc = yang_find_topnode(yspec, x1cname)) == NULL){
+ clicon_err(OE_YANG, ENOENT, "No yang spec");
+ goto done;
+ }
+ /* See if there is a corresponding node in the base tree */
+ if (match_base_child(x0, x1c, yc, &x0c) < 0)
+ goto done;
+ if (text_modify(x0c, (yang_node*)yc, x0, x1c, op) < 0)
+ goto done;
+ }
+ retval = 0;
+ done:
+ return retval;
+}
+
+
/*! Modify database provided an xml tree and an operation
*
* @param[in] xh XMLDB handle
@@ -896,9 +740,7 @@ text_modify(cxobj *x0,
* @param[in] op OP_MERGE: just add it.
* OP_REPLACE: first delete whole database
* OP_NONE: operation attribute in xml determines operation
- * @param[in] api_path According to restconf (Sec 3.5.1.1 in [restconf-draft 13
-])
- * @param[in] xadd xml-tree to merge/replace. Top-level symbol is 'config'.
+ * @param[in] x1 xml-tree to merge/replace. Top-level symbol is 'config'.
* Should be empty or '' if delete?
* @retval 0 OK
* @retval -1 Error
@@ -910,34 +752,21 @@ text_modify(cxobj *x0,
* if (xmldb_put(h, "running", OP_MERGE, "/", xt) < 0)
* err;
* @endcode
- */
+y */
int
text_put(xmldb_handle xh,
char *db,
enum operation_type op,
- char *api_path,
- cxobj *xmod)
+ cxobj *x1)
{
int retval = -1;
struct text_handle *th = handle(xh);
char *dbfile = NULL;
int fd = -1;
cbuf *cb = NULL;
- cbuf *xpcb = NULL; /* xpath cbuf */
yang_spec *yspec;
- cxobj *xt = NULL;
- cxobj *xbase = NULL;
- cxobj *xbasep = NULL; /* parent */
- cxobj *xc;
- cxobj *xnew = NULL;
- yang_node *y = NULL;
+ cxobj *x0 = NULL;
-#if 0 /* Just ignore */
- if ((op==OP_DELETE || op==OP_REMOVE) && xmod){
- clicon_err(OE_XML, 0, "xml tree should be NULL for REMOVE/DELETE");
- goto done;
- }
-#endif
if (text_db2file(th, db, &dbfile) < 0)
goto done;
if (dbfile==NULL){
@@ -953,56 +782,41 @@ text_put(xmldb_handle xh,
goto done;
}
/* Parse file into XML tree */
- if ((clicon_xml_parse_file(fd, &xt, "")) < 0)
+ if ((clicon_xml_parse_file(fd, &x0, "")) < 0)
goto done;
/* Always assert a top-level called "config".
To ensure that, deal with two cases:
1. File is empty -> rename top-level to "config" */
- if (xml_child_nr(xt) == 0){
- if (xml_name_set(xt, "config") < 0)
+ if (xml_child_nr(x0) == 0){
+ if (xml_name_set(x0, "config") < 0)
goto done;
}
/* 2. File is not empty ... -> replace root */
else{
- assert(xml_child_nr(xt)==1);
- if (xml_rootchild(xt, 0, &xt) < 0)
+ /* There should only be one element and called config */
+ if (singleconfigroot(x0, &x0) < 0)
goto done;
}
- /* here xt looks like: ... */
- /* If xpath find first occurence or api-path (this is where we apply xml) */
- if (api_path){
- if (text_apipath_modify(api_path, xt, op, yspec, &xbase, &xbasep, &y) < 0)
- goto done;
- }
- else{
- xbase = xt; /* defer y since x points to config */
- xbasep = xml_parent(xt); /* NULL */
- assert(strcmp(xml_name(xbase),"config")==0);
- }
+ assert(strcmp(xml_name(x0),"config")==0);
+ /* Here x0 looks like: ... */
+ /* Validate existing config tree */
+ if (xml_apply(x0, CX_ELMNT, xml_spec_populate, yspec) < 0)
+ goto done;
+
+ /* Validate modification tree */
+ if (xml_apply(x1, CX_ELMNT, xml_spec_populate, yspec) < 0)
+ goto done;
/*
- * Modify base tree x with modification xmod
+ * Modify base tree x with modification x1
*/
- if (op == OP_DELETE || op == OP_REMOVE){
- /* special case if top-level, dont purge top-level */
- if (xt == xbase){
- xc = NULL;
- while ((xc = xml_child_each(xt, xc, CX_ELMNT)) != NULL){
- xml_purge(xc);
- xc = NULL; /* reset iterator */
- }
- }
- else
- if (xbase)
- xml_purge(xbase);
- }
- else
- if (text_modify(xbase, xbasep, xmod, op, (yang_node*)y, yspec) < 0)
- goto done;
- /* Remove NONE nodes if all subs recursively are also NONE */
- if (xml_tree_prune_flagged(xt, XML_FLAG_NONE, 0, NULL) <0)
+ if (text_modify_top(x0, x1, yspec, op) < 0)
goto done;
- if (xml_apply(xt, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset,
+
+ /* Remove NONE nodes if all subs recursively are also NONE */
+ if (xml_tree_prune_flagged(x0, XML_FLAG_NONE, 0, NULL) <0)
+ goto done;
+ if (xml_apply(x0, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset,
(void*)XML_FLAG_NONE) < 0)
goto done;
// output:
@@ -1011,7 +825,7 @@ text_put(xmldb_handle xh,
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
- if (clicon_xml2cbuf(cb, xt, 0, 0) < 0)
+ if (clicon_xml2cbuf(cb, x0, 0, 1) < 0)
goto done;
/* Reopen file in write mode */
close(fd);
@@ -1019,7 +833,7 @@ text_put(xmldb_handle xh,
clicon_err(OE_UNIX, errno, "open(%s)", dbfile);
goto done;
}
- if (write(fd, cbuf_get(cb), cbuf_len(cb)+1) < 0){
+ if (write(fd, cbuf_get(cb), cbuf_len(cb)) < 0){
clicon_err(OE_UNIX, errno, "write(%s)", dbfile);
goto done;
}
@@ -1031,12 +845,8 @@ text_put(xmldb_handle xh,
close(fd);
if (cb)
cbuf_free(cb);
- if (xpcb)
- cbuf_free(xpcb);
- if (xt)
- xml_free(xt);
- if (xnew)
- xml_free(xnew);
+ if (x0)
+ xml_free(x0);
return retval;
}
diff --git a/datastore/text/clixon_xmldb_text.h b/datastore/text/clixon_xmldb_text.h
index c82765bc..7a53fdec 100644
--- a/datastore/text/clixon_xmldb_text.h
+++ b/datastore/text/clixon_xmldb_text.h
@@ -41,8 +41,7 @@
*/
int text_get(xmldb_handle h, char *db, char *xpath,
cxobj **xtop, cxobj ***xvec, size_t *xlen);
-int text_put(xmldb_handle h, char *db, enum operation_type op,
- char *api_path, cxobj *xt);
+int text_put(xmldb_handle h, char *db, enum operation_type op, cxobj *xt);
int text_dump(FILE *f, char *dbfilename, char *rxkey);
int text_copy(xmldb_handle h, char *from, char *to);
int text_lock(xmldb_handle h, char *db, int pid);
diff --git a/example/routing_cli.cli b/example/routing_cli.cli
index 2b95fca9..78899c77 100644
--- a/example/routing_cli.cli
+++ b/example/routing_cli.cli
@@ -4,9 +4,9 @@ CLICON_PROMPT="%U@%H> ";
CLICON_PLUGIN="routing_cli";
# Note, when switching to PT, change datamodel to only @datamodel
-set @datamodel:ietf-ip, cli_merge();
-
-#delete("Delete a configuration item") @datamodel:ietf-ipv4-unicast-routing, cli_del();
+set @datamodel:ietf-ip, cli_set();
+merge @datamodel:ietf-ip, cli_merge();
+create @datamodel:ietf-ip, cli_create();
delete("Delete a configuration item") @datamodel:ietf-ip, cli_del();
validate("Validate changes"), cli_validate();
diff --git a/lib/clixon/clixon_proto_client.h b/lib/clixon/clixon_proto_client.h
index 29ca939f..c19997d1 100644
--- a/lib/clixon/clixon_proto_client.h
+++ b/lib/clixon/clixon_proto_client.h
@@ -47,7 +47,7 @@ int clicon_rpc_netconf_xml(clicon_handle h, cxobj *xml, cxobj **xret, int *sp);
int clicon_rpc_generate_error(cxobj *xerr);
int clicon_rpc_get_config(clicon_handle h, char *db, char *xpath, cxobj **xret);
int clicon_rpc_edit_config(clicon_handle h, char *db, enum operation_type op,
- char *api_path, char *xml);
+ char *xml);
int clicon_rpc_copy_config(clicon_handle h, char *db1, char *db2);
int clicon_rpc_delete_config(clicon_handle h, char *db);
int clicon_rpc_lock(clicon_handle h, char *db);
diff --git a/lib/clixon/clixon_xml.h b/lib/clixon/clixon_xml.h
index cf40ff39..e98887ff 100644
--- a/lib/clixon/clixon_xml.h
+++ b/lib/clixon/clixon_xml.h
@@ -45,7 +45,7 @@ enum operation_type{ /* edit-configo */
OP_REPLACE,/* replace or create config-data */
OP_CREATE, /* create config data, error if exist */
OP_DELETE, /* delete config data, error if it does not exist */
- OP_REMOVE, /* delete config data */
+ OP_REMOVE, /* delete config data (not a netconf feature) */
OP_NONE
};
diff --git a/lib/clixon/clixon_xml_db.h b/lib/clixon/clixon_xml_db.h
index 87f65411..8cd645cb 100644
--- a/lib/clixon/clixon_xml_db.h
+++ b/lib/clixon/clixon_xml_db.h
@@ -80,7 +80,7 @@ typedef int (xmldb_get_t)(xmldb_handle xh, char *db, char *xpath,
/* Type of xmldb put function */
typedef int (xmldb_put_t)(xmldb_handle xh, char *db, enum operation_type op,
- char *api_path, cxobj *xt);
+ cxobj *xt);
/* Type of xmldb copy function */
typedef int (xmldb_copy_t)(xmldb_handle xh, char *from, char *to);
@@ -141,8 +141,7 @@ int xmldb_getopt(clicon_handle h, char *optname, void **value);
int xmldb_setopt(clicon_handle h, char *optname, void *value);
int xmldb_get(clicon_handle h, char *db, char *xpath,
cxobj **xtop, cxobj ***xvec, size_t *xlen);
-int xmldb_put(clicon_handle h, char *db, enum operation_type op,
- char *api_path, cxobj *xt);
+int xmldb_put(clicon_handle h, char *db, enum operation_type op, cxobj *xt);
int xmldb_copy(clicon_handle h, char *from, char *to);
int xmldb_lock(clicon_handle h, char *db, int pid);
int xmldb_unlock(clicon_handle h, char *db);
diff --git a/lib/clixon/clixon_xml_map.h b/lib/clixon/clixon_xml_map.h
index 437ac46f..abcf26c5 100644
--- a/lib/clixon/clixon_xml_map.h
+++ b/lib/clixon/clixon_xml_map.h
@@ -61,14 +61,15 @@ int xml_diff(yang_spec *yspec, cxobj *xt1, cxobj *xt2,
cxobj ***first, size_t *firstlen,
cxobj ***second, size_t *secondlen,
cxobj ***changed1, cxobj ***changed2, size_t *changedlen);
-int yang2xmlkeyfmt(yang_stmt *ys, int inclkey, char **xkfmt);
-int xmlkeyfmt2key(char *xkfmt, cvec *cvv, char **xk);
-int xmlkeyfmt2xpath(char *xkfmt, cvec *cvv, char **xk);
+int yang2api_path_fmt(yang_stmt *ys, int inclkey, char **api_path_fmt);
+int api_path_fmt2api_path(char *api_path_fmt, cvec *cvv, char **api_path);
+int api_path_fmt2xpath(char *api_path_fmt, cvec *cvv, char **xpath);
int xml_tree_prune_flagged(cxobj *xt, int flag, int test, int *upmark);
int xml_default(cxobj *x, void *arg);
int xml_order(cxobj *x, void *arg);
int xml_sanity(cxobj *x, void *arg);
int api_path2xpath_cvv(yang_spec *yspec, cvec *cvv, int offset, cbuf *xpath);
int api_path2xpath(yang_spec *yspec, char *api_path, cbuf *xpath);
+int api_path2xml(char *api_path, yang_spec *yspec, cxobj *xtop, cxobj **xpathp, yang_node **ypathp);
#endif /* _CLIXON_XML_MAP_H_ */
diff --git a/lib/src/clixon_json_parse.y b/lib/src/clixon_json_parse.y
index 0279fc55..245e31be 100644
--- a/lib/src/clixon_json_parse.y
+++ b/lib/src/clixon_json_parse.y
@@ -224,7 +224,7 @@ json_current_body(struct clicon_json_yacc_arg *jy,
*/
/* top: json -> value is also possible */
-json : value J_EOF { clicon_debug(1,"json->object"); YYACCEPT; }
+json : value J_EOF { clicon_debug(2,"json->object"); YYACCEPT; }
;
value : J_TRUE { json_current_body(_JY, "true");}
diff --git a/lib/src/clixon_proto.c b/lib/src/clixon_proto.c
index bd782234..c7b95e16 100644
--- a/lib/src/clixon_proto.c
+++ b/lib/src/clixon_proto.c
@@ -119,9 +119,12 @@ format_str2int(char *str)
return fv?fv->fv_int:-1;
}
-/*! Encode a clicon netconf message
+/*! Encode a clicon netconf message using variable argument lists
* @param[in] format Variable agrument list format an XML netconf string
* @retval msg Clicon message to send to eg clicon_msg_send()
+ * @note if format includes %, they will be expanded according to printf rules.
+ * if this is a problem, use ("%s", xml) instaead of (xml)
+ * Notaly this may an issue of RFC 3896 encoded strings
*/
struct clicon_msg *
clicon_msg_encode(char *format, ...)
diff --git a/lib/src/clixon_proto_client.c b/lib/src/clixon_proto_client.c
index 03558c57..e21d9600 100644
--- a/lib/src/clixon_proto_client.c
+++ b/lib/src/clixon_proto_client.c
@@ -260,7 +260,7 @@ clicon_rpc_get_config(clicon_handle h,
if (xpath && strlen(xpath))
cprintf(cb, "", xpath);
cprintf(cb, "");
- if ((msg = clicon_msg_encode(cbuf_get(cb))) == NULL)
+ if ((msg = clicon_msg_encode("%s", cbuf_get(cb))) == NULL)
goto done;
if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
goto done;
@@ -291,13 +291,12 @@ clicon_rpc_get_config(clicon_handle h,
* @param[in] h CLICON handle
* @param[in] db Name of database
* @param[in] op Operation on database item: OP_MERGE, OP_REPLACE
- * @param[in] api_path restconf API Path (or "")
* @param[in] xml XML string. Ex: .....
* @retval 0 OK
* @retval -1 Error
* @note xml arg need to have as top element
* @code
- * if (clicon_rpc_edit_config(h, "running", OP_MERGE, "/",
+ * if (clicon_rpc_edit_config(h, "running", OP_MERGE,
* "4") < 0)
* err;
* @endcode
@@ -306,7 +305,6 @@ int
clicon_rpc_edit_config(clicon_handle h,
char *db,
enum operation_type op,
- char *api_path,
char *xmlstr)
{
int retval = -1;
@@ -320,12 +318,10 @@ clicon_rpc_edit_config(clicon_handle h,
cprintf(cb, "<%s/>", db);
cprintf(cb, "%s",
xml_operation2str(op));
- if (api_path && strlen(api_path))
- cprintf(cb, "", api_path);
if (xmlstr)
cprintf(cb, "%s", xmlstr);
cprintf(cb, "");
- if ((msg = clicon_msg_encode(cbuf_get(cb))) == NULL)
+ if ((msg = clicon_msg_encode("%s", cbuf_get(cb))) == NULL)
goto done;
if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
goto done;
diff --git a/lib/src/clixon_xml_db.c b/lib/src/clixon_xml_db.c
index 9bc2c2c0..0ed0bafe 100644
--- a/lib/src/clixon_xml_db.c
+++ b/lib/src/clixon_xml_db.c
@@ -374,7 +374,6 @@ xmldb_get(clicon_handle h,
* @param[in] op OP_MERGE: just add it.
* OP_REPLACE: first delete whole database
* OP_NONE: operation attribute in xml determines operation
- * @param[in] api_path According to restconf (Sec 3.5.1.1 in [restconf-draft 13])
* @retval 0 OK
* @retval -1 Error
* The xml may contain the "operation" attribute which defines the operation.
@@ -382,16 +381,14 @@ xmldb_get(clicon_handle h,
* cxobj *xt;
* if (clicon_xml_parse_str("17", &xt) < 0)
* err;
- * if (xmldb_put(xh, "running", OP_MERGE, NULL, xt) < 0)
+ * if (xmldb_put(xh, "running", OP_MERGE, xt) < 0)
* err;
* @endcode
- * @see xmldb_put_xkey for single key
*/
int
xmldb_put(clicon_handle h,
char *db,
enum operation_type op,
- char *api_path,
cxobj *xt)
{
int retval = -1;
@@ -417,12 +414,12 @@ xmldb_put(clicon_handle h,
if (clicon_xml2cbuf(cb, xt, 0, 0) < 0)
goto done;
- clicon_log(LOG_WARNING, "%s: db:%s op:%d api_path:%s xml:%s", __FUNCTION__,
- db, op, api_path, cbuf_get(cb));
+ clicon_log(LOG_WARNING, "%s: db:%s op:%d xml:%s", __FUNCTION__,
+ db, op, cbuf_get(cb));
cbuf_free(cb);
}
#endif
- retval = xa->xa_put_fn(xh, db, op, api_path, xt);
+ retval = xa->xa_put_fn(xh, db, op, xt);
done:
return retval;
}
diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c
index 719eddf1..1bd2409d 100644
--- a/lib/src/clixon_xml_map.c
+++ b/lib/src/clixon_xml_map.c
@@ -121,7 +121,9 @@ tleaf(cxobj *x)
* @param[in] level print 4 spaces per level in front of each line
*/
int
-xml2txt(FILE *f, cxobj *x, int level)
+xml2txt(FILE *f,
+ cxobj *x,
+ int level)
{
cxobj *xe = NULL;
int children=0;
@@ -799,13 +801,13 @@ xml_diff(yang_spec *yspec,
* yang: container a -> list b -> key c -> leaf d
* xpath: /a/b/%s/d
* @param[in] ys Yang statement
- * @param[in] inclkey If inclkey then include key leaf (eg last leaf d in ex)
- * @param[out] cbuf keyfmt
+ * @param[in] inclkey If set include key leaf (eg last leaf d in ex)
+ * @param[out] cb api_path_fmt,
*/
static int
-yang2xmlkeyfmt_1(yang_stmt *ys,
- int inclkey,
- cbuf *cb)
+yang2api_path_fmt_1(yang_stmt *ys,
+ int inclkey,
+ cbuf *cb)
{
yang_node *yp; /* parent */
yang_stmt *ykey;
@@ -817,7 +819,7 @@ yang2xmlkeyfmt_1(yang_stmt *ys,
if (yp != NULL &&
yp->yn_keyword != Y_MODULE &&
yp->yn_keyword != Y_SUBMODULE){
- if (yang2xmlkeyfmt_1((yang_stmt *)yp, 1, cb) < 0)
+ if (yang2api_path_fmt_1((yang_stmt *)yp, 1, cb) < 0)
goto done;
}
if (inclkey){
@@ -825,11 +827,14 @@ yang2xmlkeyfmt_1(yang_stmt *ys,
cprintf(cb, "/%s", ys->ys_argument);
}
else{
- if (ys->ys_keyword == Y_LEAF && yp && yp->yn_keyword == Y_LIST){
+#if 1
+ if (ys->ys_keyword == Y_LEAF && yp &&
+ yp->yn_keyword == Y_LIST){
if (yang_key_match(yp, ys->ys_argument) == 0)
cprintf(cb, "/%s", ys->ys_argument); /* Not if leaf and key */
}
- else
+ else
+#endif
if (ys->ys_keyword != Y_CHOICE && ys->ys_keyword != Y_CASE)
cprintf(cb, "/%s", ys->ys_argument);
}
@@ -866,19 +871,19 @@ yang2xmlkeyfmt_1(yang_stmt *ys,
return retval;
}
-/*! Construct an xml key format from yang statement using wildcards for keys
+/*! Construct an api_path_format from yang statement using wildcards for keys
* Recursively construct it to the top.
* Example:
* yang: container a -> list b -> key c -> leaf d
- * xpath: /a/b=%s/d
- * @param[in] ys Yang statement
- * @param[in] inclkey If !inclkey then dont include key leaf
- * @param[out] xkfmt XML key format. Needs to be freed after use.
+ * api_path: /a/b=%s/d
+ * @param[in] ys Yang statement
+ * @param[in] inclkey If set include key leaf (eg last leaf d in ex)
+ * @param[out] api_path_fmt XML api path. Needs to be freed after use.
*/
int
-yang2xmlkeyfmt(yang_stmt *ys,
- int inclkey,
- char **xkfmt)
+yang2api_path_fmt(yang_stmt *ys,
+ int inclkey,
+ char **api_path_fmt)
{
int retval = -1;
cbuf *cb = NULL;
@@ -887,9 +892,9 @@ yang2xmlkeyfmt(yang_stmt *ys,
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
- if (yang2xmlkeyfmt_1(ys, inclkey, cb) < 0)
+ if (yang2api_path_fmt_1(ys, inclkey, cb) < 0)
goto done;
- if ((*xkfmt = strdup(cbuf_get(cb))) == NULL){
+ if ((*api_path_fmt = strdup(cbuf_get(cb))) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
@@ -904,19 +909,20 @@ yang2xmlkeyfmt(yang_stmt *ys,
/*! Transform an xml key format and a vector of values to an XML key
* Used for actual key, eg in clicon_rpc_change(), xmldb_put_xkey()
* Example:
- * xmlkeyfmt: /aaa/%s
+ * xmlkeyfmt: /aaa/%s/name
* cvv: key=17
- * xmlkey: /aaa/17
- * @param[in] xkfmt XML key format, eg /aaa/%s
- * @param[in] cvv cligen variable vector, one for every wildchar in xkfmt
- * @param[out] xk XML key, eg /aaa/17. Free after use
+ * xmlkey: /aaa/17/name
+ * @param[in] api_path_fmt XML key format, eg /aaa/%s/name
+ * @param[in] cvv cligen variable vector, one for every wildchar in api_path_fmt
+ * @param[out] api_path api_path, eg /aaa/17. Free after use
+ * @param[out] yang_arg yang-stmt argument name. Free after use
* @note first and last elements of cvv are not used,..
* @see cli_dbxml where this function is called
*/
int
-xmlkeyfmt2key(char *xkfmt,
- cvec *cvv,
- char **xk)
+api_path_fmt2api_path(char *api_path_fmt,
+ cvec *cvv,
+ char **api_path)
{
int retval = -1;
char c;
@@ -927,16 +933,15 @@ xmlkeyfmt2key(char *xkfmt,
char *str;
char *strenc=NULL;
-
/* Sanity check */
#if 1
j = 0; /* Count % */
- for (i=0; i name:x restval=1,2 */
+ /* restval is RFC 3896 encoded */
+ if ((restval_enc = index(name, '=')) != NULL){
+ *restval_enc = '\0';
+ restval_enc++;
+ if (percent_decode(restval_enc, &restval) < 0)
+ goto done;
+ }
+ if (y0->yn_keyword == Y_SPEC) /* top-node */
+ y = yang_find_topnode((yang_spec*)y0, name);
+ else
+ y = yang_find_syntax((yang_node*)y0, name);
+ if (y == NULL){
+ clicon_err(OE_YANG, errno, "No yang node found: %s", name);
+ goto done;
+ }
+ switch (y->ys_keyword){
+ case Y_LEAF_LIST:
+ if (restval==NULL){
+ clicon_err(OE_XML, 0, "malformed key, expected '='");
+ goto done;
+ }
+ if ((x = xml_new_spec(y->ys_argument, x0, y)) == NULL)
+ goto done;
+ xml_type_set(x, CX_ELMNT);
+ if ((xb = xml_new("body", x)) == NULL)
+ goto done;
+ xml_type_set(xb, CX_BODY);
+ if (xml_value_set(xb, restval) < 0)
+ goto done;
+ break;
+ case Y_LIST:
+ /* Get the yang list key */
+ if ((ykey = yang_find((yang_node*)y, Y_KEY, NULL)) == NULL){
+ clicon_err(OE_XML, errno, "%s: List statement \"%s\" has no key",
+ __FUNCTION__, y->ys_argument);
+ goto done;
+ }
+ /* The value is a list of keys: [ ]* */
+ if ((cvk = yang_arg2cvec(ykey, " ")) == NULL)
+ goto done;
+ if (restval==NULL){
+ clicon_err(OE_XML, 0, "malformed key, expected '='");
+ goto done;
+ }
+ if (valvec)
+ free(valvec);
+ if ((valvec = clicon_strsep(restval, ",", &nvalvec)) == NULL)
+ goto done;
+
+ if (cvec_len(cvk) != nvalvec){
+ clicon_err(OE_XML, errno, "List %s key length mismatch", name);
+ goto done;
+ }
+ cvi = NULL;
+ /* create list object */
+ if ((x = xml_new_spec(name, x0, y)) == NULL)
+ goto done;
+ xml_type_set(x, CX_ELMNT);
+ j = 0;
+ /* Create keys */
+ while ((cvi = cvec_each(cvk, cvi)) != NULL) {
+ keyname = cv_string_get(cvi);
+ val2 = valvec[j++];
+ if ((xn = xml_new(keyname, x)) == NULL)
+ goto done;
+ xml_type_set(xn, CX_ELMNT);
+ if ((xb = xml_new("body", xn)) == NULL)
+ goto done;
+ xml_type_set(xb, CX_BODY);
+ if (xml_value_set(xb, val2) <0)
+ goto done;
+ }
+ if (cvk){
+ cvec_free(cvk);
+ cvk = NULL;
+ }
+ break;
+ default: /* eg Y_CONTAINER, Y_LEAF */
+ if ((x = xml_new_spec(name, x0, y)) == NULL)
+ goto done;
+ xml_type_set(x, CX_ELMNT);
+ break;
+ }
+ if (api_path2xml_vec(vec+1, nvec-1,
+ x, (yang_node*)y,
+ xpathp, ypathp) < 0)
+ goto done;
+ retval = 0;
+ done:
+ if (restval)
+ free(restval);
+ if (valvec)
+ free(valvec);
+ return retval;
+}
+
+/*! Create xml tree from api-path
+ * @param[in] api_path API-path as defined in RFC 8040
+ * @param[out] xpathp Resulting xml tree
+ * @param[out] ypathp Yang spec matching xpathp
+ * @see api_path2xml_vec
+ */
+int
+api_path2xml(char *api_path,
+ yang_spec *yspec,
+ cxobj *xpath,
+ cxobj **xpathp,
+ yang_node **ypathp)
+{
+ int retval = -1;
+ char **vec = NULL;
+ int nvec;
+
+ clicon_debug(1, "%s 0", __FUNCTION__);
+ if (*api_path!='/'){
+ clicon_err(OE_DB, 0, "Invalid key: %s", api_path);
+ goto done;
+ }
+ if ((vec = clicon_strsep(api_path, "/", &nvec)) == NULL)
+ goto done;
+ /* Remove trailing '/'. Like in /a/ -> /a */
+ if (nvec > 1 && !strlen(vec[nvec-1]))
+ nvec--;
+ if (nvec < 1){
+ clicon_err(OE_XML, 0, "Malformed key: %s", api_path);
+ goto done;
+ }
+ nvec--; /* NULL-terminated */
+ if (api_path2xml_vec(vec+1, nvec,
+ xpath, (yang_node*)yspec,
+ xpathp, ypathp) < 0)
+ goto done;
+ retval = 0;
+ done:
+ if (vec)
+ free(vec);
+ return retval;
+}
diff --git a/lib/src/clixon_xml_parse.y b/lib/src/clixon_xml_parse.y
index 113b518f..1ba389a1 100644
--- a/lib/src/clixon_xml_parse.y
+++ b/lib/src/clixon_xml_parse.y
@@ -79,23 +79,6 @@ clixon_xml_parseerror(void *_ya, char *s)
return;
}
-
-static int
-xml_attr_new(struct xml_parse_yacc_arg *ya,
- cxobj *xn,
- char *name,
- char *val)
-{
- cxobj *xa;
-
- if ((xa = xml_new(name, xn)) == NULL)
- return -1;
- xml_type_set(xa, CX_ATTR);
- if (xml_value_set(xa, val) < 0)
- return -1;
- return 0;
-}
-
/* note that we dont handle escaped characters correctly
there may also be some leakage here on NULL return
*/
@@ -194,17 +177,21 @@ xml_parse_bslash1(struct xml_parse_yacc_arg *ya,
xml_namespace(x), xml_name(x), name);
goto done;
}
-
- /* remove all non-terminal bodies (strip pretty-print) */
+ /* Strip pretty-print. Ad-hoc algorithm
+ * It ok with x:[body], but not with x:[ex,body]
+ * It is also ok with x:[attr,body]
+ * So the rule is: if there is at least on element, then remove all bodies?
+ */
if (ya->ya_skipspace){
- if (xml_child_nr(x) == 1 && (xml_type(xml_child_i(x, 0))==CX_BODY))
- ;
- else{
+ xc = NULL;
+ while ((xc = xml_child_each(x, xc, CX_ELMNT)) != NULL)
+ break;
+ if (xc != NULL){ /* at least one element */
xc = NULL;
while ((xc = xml_child_each(x, xc, CX_BODY)) != NULL) {
xml_purge(xc);
xc = NULL; /* reset iterator */
- }
+ }
}
}
retval = 0;
@@ -240,14 +227,20 @@ xml_parse_bslash2(struct xml_parse_yacc_arg *ya,
name);
goto done;
}
- /* remove all non-terminal bodies (strip pretty-print) */
+ /* Strip pretty-print. Ad-hoc algorithm
+ * It ok with x:[body], but not with x:[ex,body]
+ * It is also ok with x:[attr,body]
+ * So the rule is: if there is at least on element, then remove all bodies?
+ */
if (ya->ya_skipspace){
- if (xml_child_nr(x) == 1 && (xml_type(xml_child_i(x, 0))==CX_BODY))
- ;
- else{
+ xc = NULL;
+ while ((xc = xml_child_each(x, xc, CX_ELMNT)) != NULL)
+ break;
+ if (xc != NULL){ /* at least one element */
xc = NULL;
- while ((xc = xml_child_each(x, xc, CX_BODY)) != NULL)
+ while ((xc = xml_child_each(x, xc, CX_BODY)) != NULL) {
xml_value_set(xc, ""); /* XXX remove */
+ }
}
}
retval = 0;
@@ -276,8 +269,12 @@ static int
xml_parse_attr(struct xml_parse_yacc_arg *ya, char *id, char *val)
{
int retval = -1;
+ cxobj *xa;
- if (xml_attr_new(ya, ya->ya_xelement, id, val) < 0)
+ if ((xa = xml_new(id, ya->ya_xelement)) == NULL)
+ goto done;
+ xml_type_set(xa, CX_ATTR);
+ if (xml_value_set(xa, val) < 0)
goto done;
retval = 0;
done:
diff --git a/test/lib.sh b/test/lib.sh
index 83353f18..61796c86 100755
--- a/test/lib.sh
+++ b/test/lib.sh
@@ -21,6 +21,11 @@ new(){
expectfn(){
cmd=$1
expect=$2
+ if [ $# = 3 ]; then
+ expect2=$3
+ else
+ expect2=
+ fi
ret=`$cmd`
if [ $? -ne 0 ]; then
err
@@ -30,12 +35,18 @@ expectfn(){
return
fi
# grep extended grep
- match=`echo "$ret" | grep -Eo "$expect"`
-# echo "ret:<$ret>"
-# echo "expect:$expect"
-# echo "match:$match"
+ match=`echo "$ret" | grep -EZo "$expect"`
+# echo "ret:\"$ret\""
+# echo "expect:\"$expect\""
+# echo "match:\"$match\""
if [ -z "$match" ]; then
- err "\nExpected:\t\"$expect\"\nGot:\t\"$ret\""
+ err "Expected:\"$expect\" Got:\"$ret\""
+ fi
+ if [ -n "$expect2" ]; then
+ match=`echo "$ret" | grep -EZo "$expect2"`
+ if [ -z "$match" ]; then
+ err "Expected:\"$expect\" Got: \"$ret\""
+ fi
fi
}
@@ -56,8 +67,11 @@ EOF
return
fi
match=`echo "$ret" | grep -Eo "$expect"`
+# echo "ret:\"$ret\""
+# echo "expect:\"$expect\""
+# echo "match:\"$match\""
if [ -z "$match" ]; then
- err "\nExpected:\t\"$expect\"\nGot:\t\"$ret\""
+ err "Expected:\"$expect\" Got: \"$ret\""
fi
}
diff --git a/test/test1.sh b/test/test1.sh
index b75e8434..e7f34af4 100755
--- a/test/test1.sh
+++ b/test/test1.sh
@@ -41,25 +41,24 @@ new "cli show configuration delete top"
expectfn "$clixon_cli -1f $clixon_cf show conf cli" ""
new "cli configure"
-expectfn "$clixon_cli -1f $clixon_cf set interfaces interface eth0" ""
+expectfn "$clixon_cli -1f $clixon_cf set interfaces interface eth/0/0" ""
new "cli show configuration"
-expectfn "$clixon_cli -1f $clixon_cf show conf cli" "^interfaces interface name eth0
-interfaces interface enabled true$"
+expectfn "$clixon_cli -1f $clixon_cf show conf cli" "^interfaces interface name eth/0/0" "interfaces interface enabled true$"
new "cli failed validate"
expectfn "$clixon_cli -1f $clixon_cf -l o validate" "Missing mandatory variable"
new "cli configure more"
-expectfn "$clixon_cli -1f $clixon_cf set interfaces interface eth0 ipv4 address 1.2.3.4 prefix-length 24" ""
-expectfn "$clixon_cli -1f $clixon_cf set interfaces interface eth0 description mydesc" ""
-expectfn "$clixon_cli -1f $clixon_cf set interfaces interface eth0 type bgp" ""
+expectfn "$clixon_cli -1f $clixon_cf set interfaces interface eth/0/0 ipv4 address 1.2.3.4 prefix-length 24" ""
+expectfn "$clixon_cli -1f $clixon_cf set interfaces interface eth/0/0 description mydesc" ""
+expectfn "$clixon_cli -1f $clixon_cf set interfaces interface eth/0/0 type bgp" ""
new "cli show xpath description"
expectfn "$clixon_cli -1f $clixon_cf -l o show xpath /interfaces/interface/description" "mydesc"
new "cli delete description"
-expectfn "$clixon_cli -1f $clixon_cf -l o delete interfaces interface eth0 description mydesc"
+expectfn "$clixon_cli -1f $clixon_cf -l o delete interfaces interface eth/0/0 description mydesc"
new "cli show xpath no description"
expectfn "$clixon_cli -1f $clixon_cf -l o show xpath /interfaces/interface/description" ""
@@ -80,8 +79,7 @@ new "cli load"
expectfn "$clixon_cli -1f $clixon_cf -l o load /tmp/foo" ""
new "cli check load"
-expectfn "$clixon_cli -1f $clixon_cf -l o show conf cli" "^interfaces interface name eth0
-interfaces interface enabled true$"
+expectfn "$clixon_cli -1f $clixon_cf -l o show conf cli" "^interfaces interface name eth/0/0" "interfaces interface enabled true$"
new "cli debug"
expectfn "$clixon_cli -1f $clixon_cf -l o debug level 1" ""
diff --git a/test/test2.sh b/test/test2.sh
index e6066427..6b7cc263 100755
--- a/test/test2.sh
+++ b/test/test2.sh
@@ -26,38 +26,38 @@ new "netconf tests"
new "netconf get empty config"
expecteof "$clixon_netconf -qf $clixon_cf" ']]>]]>' '^]]>]]>$'
-new "Add subtree eth0 using none which should not change anything"
-expecteof "$clixon_netconf -qf $clixon_cf" "noneeth0]]>]]>" "^]]>]]>$"
+new "Add subtree eth/0/0 using none which should not change anything"
+expecteof "$clixon_netconf -qf $clixon_cf" "noneeth/0/0]]>]]>" "^]]>]]>$"
new "Check nothing added"
expecteof "$clixon_netconf -qf $clixon_cf" ']]>]]>' '^]]>]]>$'
-new "Add subtree eth0 using none and create which should add eth0"
-expecteof "$clixon_netconf -qf $clixon_cf" 'eth0ethnone ]]>]]>' "^]]>]]>$"
+new "Add subtree eth/0/0 using none and create which should add eth/0/0"
+expecteof "$clixon_netconf -qf $clixon_cf" 'eth/0/0ethnone ]]>]]>' "^]]>]]>$"
-new "Check eth0 added using xpath"
-expecteof "$clixon_netconf -qf $clixon_cf" ']]>]]>' "^eth0ethtrue]]>]]>$"
+new "Check eth/0/0 added using xpath"
+expecteof "$clixon_netconf -qf $clixon_cf" ']]>]]>' "^eth/0/0ethtrue]]>]]>$"
-new "Re-create same eth0 which should generate error"
-expecteof "$clixon_netconf -qf $clixon_cf" 'eth0ethnone ]]>]]>' "^"
+new "Re-create same eth/0/0 which should generate error"
+expecteof "$clixon_netconf -qf $clixon_cf" 'eth/0/0ethnone ]]>]]>' "^"
-new "Delete eth0 using none config"
-expecteof "$clixon_netconf -qf $clixon_cf" 'eth0ethnone ]]>]]>' "^]]>]]>$"
+new "Delete eth/0/0 using none config"
+expecteof "$clixon_netconf -qf $clixon_cf" 'eth/0/0ethnone ]]>]]>' "^]]>]]>$"
-new "Check deleted eth0"
+new "Check deleted eth/0/0"
expecteof "$clixon_netconf -qf $clixon_cf" ']]>]]>' '^]]>]]>$'
-new "Re-Delete eth0 using none should generate error"
-expecteof "$clixon_netconf -qf $clixon_cf" 'eth0ethnone ]]>]]>' "^"
+new "Re-Delete eth/0/0 using none should generate error"
+expecteof "$clixon_netconf -qf $clixon_cf" 'eth/0/0ethnone ]]>]]>' "^"
new "netconf edit config"
-expecteof "$clixon_netconf -qf $clixon_cf" "eth0eth1true9.2.3.424]]>]]>" "^]]>]]>$"
+expecteof "$clixon_netconf -qf $clixon_cf" "eth/0/0eth1true9.2.3.424]]>]]>" "^]]>]]>$"
new "netconf get config xpath"
-expecteof "$clixon_netconf -qf $clixon_cf" ']]>]]>' "^true]]>]]>$"
+expecteof "$clixon_netconf -qf $clixon_cf" ']]>]]>' "^eth1true]]>]]>$"
new "netconf get config xpath parent"
-expecteof "$clixon_netconf -qf $clixon_cf" ']]>]]>' "^eth0trueeth1truetruefalse9.2.3.424]]>]]>$"
+expecteof "$clixon_netconf -qf $clixon_cf" ']]>]]>' "^eth/0/0trueeth1truetruefalse9.2.3.424]]>]]>$"
new "netconf validate missing type"
expecteof "$clixon_netconf -qf $clixon_cf" "]]>]]>" "^"
diff --git a/test/test3.sh b/test/test3.sh
index 45efc73d..7ff9905f 100755
--- a/test/test3.sh
+++ b/test/test3.sh
@@ -27,43 +27,60 @@ sleep 1
new "restconf tests"
new "restconf options"
-expectfn "curl -i -s -X OPTIONS http://localhost/restconf/data" "Allow: OPTIONS,HEAD,GET,POST,PUT,DELETE"
+expectfn "curl -i -sS -X OPTIONS http://localhost/restconf/data" "Allow: OPTIONS,HEAD,GET,POST,PUT,DELETE"
new "restconf head"
-expectfn "curl -s -I http://localhost/restconf/data" "Content-Type: application/yang.data\+json"
+expectfn "curl -sS -I http://localhost/restconf/data" "Content-Type: application/yang.data\+json"
new "restconf get empty config"
-expectfn "curl -sG http://localhost/restconf/data" "^null
$"
+expectfn "curl -sSG http://localhost/restconf/data" "^null
$"
#
-new "Add subtree eth0,eth1 using POST"
-expectfn 'curl -sX POST -d {"interfaces":{"interface":[{"name":"eth0","type":"eth","enabled":"true"},{"name":"eth1","type":"eth","enabled":"true"}]}} http://localhost/restconf/data' ""
+new "Add subtree to datastore using POST"
+expectfn 'curl -sS -X POST -d {"interfaces":{"interface":{"name":"eth/0/0","type":"eth","enabled":"true"}}} http://localhost/restconf/data' ""
-new "Check eth0 added"
-expectfn "curl -sG http://localhost/restconf/data" '{"interfaces": {"interface": \[{"name": "eth0","type": "eth","enabled": "true"},{ "name": "eth1","type": "eth","enabled": "true"}\]}}
+new "Check interfaces eth/0/0 added"
+expectfn "curl -sS -G http://localhost/restconf/data" '{"interfaces": {"interface": {"name": "eth/0/0","type": "eth","enabled": "true"}}}
$'
-new "Re-post eth0 which should generate error"
-expectfn 'curl -sX POST -d {"interfaces":{"interface":{"name":"eth0","type":"eth","enabled":"true"}}} http://localhost/restconf/data' "Not Found"
+new "delete interfaces"
+expectfn 'curl -sS -X DELETE http://localhost/restconf/data/interfaces' ""
-new "delete eth0"
-expectfn 'curl -sX DELETE http://localhost/restconf/data/interfaces/interface=eth0' ""
+new "Check empty config"
+expectfn "curl -sSG http://localhost/restconf/data" "^null
$"
-new "Check deleted eth0"
-expectfn 'curl -sG http://localhost/restconf/data' '{"interfaces": {"interface": {"name": "eth1","type": "eth","enabled": "true"}}}
+new "Add interfaces subtree eth/0/0 using POST"
+expectfn 'curl -sS -X POST -d {"interface":{"name":"eth/0/0","type":"eth","enabled":"true"}} http://localhost/restconf/data/interfaces' ""
+
+new "Check eth/0/0 added"
+expectfn "curl -sS -G http://localhost/restconf/data" '{"interfaces": {"interface": {"name": "eth/0/0","type": "eth","enabled": "true"}}}
$'
-new "Re-Delete eth0 using none should generate error"
-expectfn 'curl -sX DELETE http://localhost/restconf/data/interfaces/interface=eth0' "Not Found"
+new "Re-post eth/0/0 which should generate error"
+expectfn 'curl -sS -X POST -d {"interface":{"name":"eth/0/0","type":"eth","enabled":"true"}} http://localhost/restconf/data/interfaces' "Data resource already exists"
-if false; then # XXX restconf dont support patch and put fully
+new "Add leaf description using POST"
+expectfn 'curl -sS -X POST -d {"description":"The-first-interface"} http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' ""
- new "restconf PATCH config"
- expectfn 'curl -sX PATCH -d {"type":"eth"} http://localhost/restconf/data/interfaces/interface=eth4' ""
+new "Check description added"
+expectfn "curl -sS -G http://localhost/restconf/data" '{"interfaces": {"interface": {"name": "eth/0/0","description": "The-first-interface","type": "eth","enabled": "true"}}
+$'
- new "restconf PUT"
- expectfn 'curl -sX PUT -d {"type":"eth"} http://localhost/restconf/data/interfaces/interface=eth5' ""
-fi
+new "delete eth/0/0"
+expectfn 'curl -sS -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' ""
+
+new "Check deleted eth/0/0"
+expectfn 'curl -sS -G http://localhost/restconf/data' '{"interfaces": null}
+$'
+
+new "Re-Delete eth/0/0 using none should generate error"
+expectfn 'curl -sS -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' "Not Found"
+
+new "Add subtree eth/0/0 using PUT"
+expectfn 'curl -sS -X PUT -d {"interface":{"name":"eth/0/0","type":"eth","enabled":"true"}} http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' ""
+
+expectfn "curl -sS -G http://localhost/restconf/data" '{"interfaces": {"interface": {"name": "eth/0/0","type": "eth","enabled": "true"}}}
+$'
new "Kill restconf daemon"
#sudo pkill -u www-data clixon_restconf
diff --git a/test/test5.sh b/test/test5.sh
index 657ddc50..148ed1ab 100755
--- a/test/test5.sh
+++ b/test/test5.sh
@@ -60,37 +60,37 @@ run(){
# Whole tree operations
new "datastore $name put all replace"
- expectfn "$datastore $conf put replace / $db" ""
+ expectfn "$datastore $conf put replace $db" ""
new "datastore $name get"
expectfn "$datastore $conf get /" "^$db$"
new "datastore $name put all remove"
- expectfn "$datastore $conf put remove /"
+ expectfn "$datastore $conf put remove "
new "datastore $name get"
expectfn "$datastore $conf get /" "^$"
new "datastore $name put all merge"
- expectfn "$datastore $conf put merge / $db" ""
+ expectfn "$datastore $conf put merge $db" ""
new "datastore $name get"
expectfn "$datastore $conf get /" "^$db$"
new "datastore $name put all delete"
- expectfn "$datastore $conf put remove /"
+ expectfn "$datastore $conf put remove "
new "datastore $name get"
expectfn "$datastore $conf get /" "^$"
new "datastore $name put all create"
- expectfn "$datastore $conf put create / $db" ""
+ expectfn "$datastore $conf put create $db" ""
new "datastore $name get"
expectfn "$datastore $conf get /" "^$db$"
new "datastore $name put top create"
- expectfn "$datastore $conf put create / " "" # error
+ expectfn "$datastore $conf put create " "" # error
# Single key operations
# leaf
@@ -101,43 +101,43 @@ run(){
expectfn "$datastore $conf init" ""
new "datastore $name create leaf"
- expectfn "$datastore $conf put create /x/y=1,3/c newentry"
+ expectfn "$datastore $conf put create 13newentry"
new "datastore $name create leaf"
- expectfn "$datastore $conf put create /x/y=1,3/c newentry"
+ expectfn "$datastore $conf put create 13newentry"
new "datastore $name delete leaf"
- expectfn "$datastore $conf put delete /x/y=1,3"
+ expectfn "$datastore $conf put delete 13"
new "datastore $name replace leaf"
- expectfn "$datastore $conf put create /x/y=1,3/c newentry"
+ expectfn "$datastore $conf put create 13newentry"
new "datastore $name remove leaf"
- expectfn "$datastore $conf put remove /x/g"
+ expectfn "$datastore $conf put remove "
new "datastore $name remove leaf"
- expectfn "$datastore $conf put remove /x/y=1,3/c"
+ expectfn "$datastore $conf put remove 13"
new "datastore $name delete leaf"
- expectfn "$datastore $conf put delete /x/g"
+ expectfn "$datastore $conf put delete "
new "datastore $name merge leaf"
- expectfn "$datastore $conf put merge /x/g nalle"
+ expectfn "$datastore $conf put merge nalle"
new "datastore $name replace leaf"
- expectfn "$datastore $conf put replace /x/g nalle"
+ expectfn "$datastore $conf put replace nalle"
new "datastore $name merge leaf"
- expectfn "$datastore $conf put merge /x/y=1,3/c newentry"
+ expectfn "$datastore $conf put merge 13newentry"
new "datastore $name replace leaf"
- expectfn "$datastore $conf put replace /x/y=1,3/c newentry"
+ expectfn "$datastore $conf put replace 13newentry"
new "datastore $name create leaf"
- expectfn "$datastore $conf put create /x/h aaa"
+ expectfn "$datastore $conf put create aaa"
new "datastore $name create leaf"
- expectfn "$datastore $conf put create /x/y=1,3/c newentry"
+ expectfn "$datastore $conf put create 13newentry"
#leaf-list