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" "eth0eth1true
9.2.3.424
]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $clixon_cf" "eth/0/0eth1true
9.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" ']]>]]>' "^eth0trueeth1truetruefalse
9.2.3.424
]]>]]>$" +expecteof "$clixon_netconf -qf $clixon_cf" ']]>]]>' "^eth/0/0trueeth1truetruefalse
9.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