From 0c72100a0577f5314fc17c1df797790e841e3540 Mon Sep 17 00:00:00 2001 From: Alan Yaniger Date: Mon, 19 Jul 2021 13:17:10 +0300 Subject: [PATCH 01/49] implement yang-patch --- apps/restconf/restconf_api.h | 2 + apps/restconf/restconf_api_native.c | 1 + apps/restconf/restconf_main_fcgi.c | 6 + apps/restconf/restconf_main_native.c | 6 + apps/restconf/restconf_methods.c | 540 +++++++++++++++++++- apps/restconf/restconf_methods_post.c | 3 +- apps/restconf/restconf_methods_post.h | 1 + apps/restconf/restconf_root.c | 2 +- example/main/clixon-example@2020-12-01.yang | 3 + example/main/example.xml | 2 + lib/clixon/clixon_json.h | 1 - yang/mandatory/Makefile.in | 1 + 12 files changed, 563 insertions(+), 5 deletions(-) diff --git a/apps/restconf/restconf_api.h b/apps/restconf/restconf_api.h index a44cb440..f94b8ce2 100644 --- a/apps/restconf/restconf_api.h +++ b/apps/restconf/restconf_api.h @@ -52,4 +52,6 @@ int restconf_reply_send(void *req, int code, cbuf *cb, int head); cbuf *restconf_get_indata(void *req); +#define YANG_PATCH + #endif /* _RESTCONF_API_H_ */ diff --git a/apps/restconf/restconf_api_native.c b/apps/restconf/restconf_api_native.c index c4162b2e..70a8915a 100644 --- a/apps/restconf/restconf_api_native.c +++ b/apps/restconf/restconf_api_native.c @@ -191,3 +191,4 @@ restconf_get_indata(void *req0) return cb; } + diff --git a/apps/restconf/restconf_main_fcgi.c b/apps/restconf/restconf_main_fcgi.c index 88622b6a..b8508102 100644 --- a/apps/restconf/restconf_main_fcgi.c +++ b/apps/restconf/restconf_main_fcgi.c @@ -419,6 +419,12 @@ main(int argc, if (yang_spec_parse_module(h, "ietf-restconf", NULL, yspec)< 0) goto done; +#ifdef YANG_PATCH + /* Load yang restconf patch module */ + if (yang_spec_parse_module(h, "ietf-yang-patch", NULL, yspec)< 0) + goto done; +#endif // YANG_PATCH + /* Add netconf yang spec, used as internal protocol */ if (netconf_module_load(h) < 0) goto done; diff --git a/apps/restconf/restconf_main_native.c b/apps/restconf/restconf_main_native.c index 631701c1..e2f940b1 100644 --- a/apps/restconf/restconf_main_native.c +++ b/apps/restconf/restconf_main_native.c @@ -1728,6 +1728,12 @@ restconf_clixon_init(clicon_handle h, if (yang_spec_parse_module(h, "ietf-restconf", NULL, yspec)< 0) goto done; +#ifdef YANG_PATCH + /* Load yang restconf patch module */ + if (yang_spec_parse_module(h, "ietf-yang-patch", NULL, yspec)< 0) + goto done; +#endif // YANG_PATCH + /* Add netconf yang spec, used as internal protocol */ if (netconf_module_load(h) < 0) goto done; diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index 10844869..3d9630bb 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -74,6 +74,7 @@ #include "restconf_api.h" #include "restconf_err.h" #include "restconf_methods.h" +#include "restconf_methods_post.h" /*! REST OPTIONS method * According to restconf @@ -579,6 +580,536 @@ api_data_write(clicon_handle h, return retval; } /* api_data_write */ +#ifdef YANG_PATCH +/*! YANG PATCH method + * @param[in] h Clixon handle + * @param[in] req Generic Www handle + * @param[in] api_path0 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 + * @param[in] pretty Set to 1 for pretty-printed xml/json output + * @param[in] media_out Output media + * Netconf: (nc:operation="merge") + * See RFC8072 + * YANG patch can be used to "create", "delete", "insert", "merge", "move", "replace", and/or + "remove" a resource within the target resource. + * Currently "move" not supported + */ +static int +api_data_yang_patch(clicon_handle h, + void *req, + char *api_path0, + cvec *pcvec, + int pi, + cvec *qvec, + char *data, + int pretty, + restconf_media media_out, + ietf_ds_t ds) +{ + int retval = -1; + int i; + cxobj *xdata0 = NULL; /* Original -d data struct (including top symbol) */ + cbuf *cbx = NULL; + cxobj *xtop = NULL; /* top of api-path */ + cxobj *xbot = NULL; /* bottom of api-path */ + yang_stmt *ybot = NULL; /* yang of xbot */ + cxobj *xbot_tmp = NULL; + yang_stmt *yspec; + char *api_path; + cxobj *xret = NULL; + cxobj *xretcom = NULL; /* return from commit */ + cxobj *xretdis = NULL; /* return from discard-changes */ + cxobj *xerr = NULL; /* malloced must be freed */ + int ret; + cvec *nsc = NULL; + yang_bind yb; + char *xpath = NULL; + const int temp_str_malloc_size = 5000; + char *path_orig_1 = NULL; + + clicon_debug(1, "%s api_path:\"%s\"", __FUNCTION__, api_path0); + if ((yspec = clicon_dbspec_yang(h)) == NULL){ + clicon_err(OE_FATAL, 0, "No DB_SPEC"); + goto done; + } + api_path=api_path0; + /* strip /... from start */ + for (i=0; i= 0; l--) { + if (target_val[l] == '/') { + idx = l; + break; + } + } + strncpy(post_req_uri, target_val, idx); + strcat(simple_patch_request_uri, post_req_uri); + free(post_req_uri); + for (int l = strlen(json_simple_patch); l>= 0; l--) { + char c = json_simple_patch[l]; + if (c == '}') { + json_simple_patch[l] = ']'; + json_simple_patch[l + 1] = '}'; + break; + } + } + + // Send the POST request + ret = api_data_post(h, req, simple_patch_request_uri, pi, qvec, json_simple_patch, pretty, YANG_DATA_JSON, media_out, ds ); + if (value_vec_tmp != NULL) + free(value_vec_tmp); + free(x_simple_patch); + free(patch_header); // NULL check was already done before + if (ret != 0) + goto done; + break; + } + + // For "create", put all the data values into a single POST request + if (strcmp(op_val,"create") == 0) { + for (int k = 0; k < value_vec_len; k++) { + if (value_vec[k] != NULL) { + value_vec_tmp = xml_dup(value_vec[k]); + xml_addsub(x_simple_patch, value_vec_tmp); + } + } + + // Send the POST request + cbuf* cb = cbuf_new(); + xml2json_cbuf(cb, x_simple_patch, 1); + char *json_simple_patch = cbuf_get(cb); + ret = api_data_post(h, req, simple_patch_request_uri, pi, qvec, json_simple_patch, pretty, YANG_DATA_JSON, media_out, ds ); + if (value_vec_tmp != NULL) + free(value_vec_tmp); + free(x_simple_patch); + free(patch_header); // NULL check was already done before + if (ret != 0) + goto done; + break; + } + // For "insert", make a api_data_post request + if (strcmp(op_val, "insert") == 0) { + char *json_simple_patch = malloc(temp_str_malloc_size); + if (json_simple_patch == NULL) + goto done; + memset(json_simple_patch, 0, temp_str_malloc_size); + + // Loop through the XML, and get each value + for (int k = 0; k < value_vec_len; k++) { + if (value_vec[k] != NULL) { + value_vec_tmp = xml_dup(value_vec[k]); + xml_addsub(x_simple_patch, value_vec_tmp); + } + } + cbuf* cb = cbuf_new(); + xml2json_cbuf(cb, x_simple_patch, 1); + + // Some ugly text processing to get the JSON to match what api_data_post() expects + char *json_simple_patch_tmp = cbuf_get(cb); + int brace_count = 0; + for (int l = 0; l < strlen(json_simple_patch_tmp); l++) { + char c = json_simple_patch_tmp[l]; + if (c == '{') { + brace_count++; + if (brace_count == 2) { + json_simple_patch[strlen(json_simple_patch)] = '['; + } + } + json_simple_patch[strlen(json_simple_patch)] = c; + } + for (int l = strlen(json_simple_patch); l>= 0; l--) { + char c = json_simple_patch[l]; + if (c == '}') { + json_simple_patch[l] = ']'; + json_simple_patch[l + 1] = '}'; + break; + } + } + + // Set the insert attributes + cvec* qvec_tmp = NULL; + qvec_tmp = cvec_new(0); + if (qvec_tmp == NULL) + goto done; + cg_var *cv; + if ((cv = cvec_add(qvec_tmp, CGV_STRING)) == NULL){ + goto done; + } + cv_name_set(cv, "insert"); + cv_string_set(cv, where_val); + char *point_str = malloc(temp_str_malloc_size); + if (point_str == NULL) + goto done; + memset(point_str, 0, temp_str_malloc_size); + strcpy(point_str, api_path); + strcat(point_str, point_val); + if ((cv = cvec_add(qvec_tmp, CGV_STRING)) == NULL){ + goto done; + } + cv_name_set(cv, "point"); + cv_string_set(cv, point_str); + + // Send the POST request + ret = api_data_post(h, req, simple_patch_request_uri, pi, qvec_tmp, json_simple_patch, pretty, YANG_DATA_JSON, media_out, ds ); + if (cb != NULL) + cbuf_free(cb); + if (value_vec_tmp != NULL) + free(value_vec_tmp); + free(point_str); // NULL check was already done above + free(json_simple_patch); // NULL check was already done above + free(patch_header); // NULL check was already done before + if (x_simple_patch != NULL) + free(x_simple_patch); + break; + } + + // For merge", make single simple patch requests for each value + if (strcmp(op_val,"merge") == 0) { + if (key_xn != NULL) + xml_addsub(x_simple_patch, key_xn); + + char *json_simple_patch = malloc(temp_str_malloc_size); + if (json_simple_patch == NULL) + goto done; + + // Loop through the XML, create JSON from each one, and submit a simple patch + for (int k = 0; k < value_vec_len; k++) { + if (value_vec[k] != NULL) { + value_vec_tmp = xml_dup(value_vec[k]); + xml_addsub(x_simple_patch, value_vec_tmp); + } + cbuf* cb = cbuf_new(); + xml2json_cbuf(cb, x_simple_patch, 1); + + // Some ugly text processing to get the JSON to match what api_data_write() expects for a simple patch + char *json_simple_patch_tmp = cbuf_get(cb); + memset(json_simple_patch, 0, temp_str_malloc_size); + int brace_count = 0; + for (int l = 0; l < strlen(json_simple_patch_tmp); l++) { + char c = json_simple_patch_tmp[l]; + if (c == '{') { + brace_count++; + if (brace_count == 2) { + json_simple_patch[strlen(json_simple_patch)] = '['; + } + } + json_simple_patch[strlen(json_simple_patch)] = c; + } + for (int l = strlen(json_simple_patch); l>= 0; l--) { + char c = json_simple_patch[l]; + if (c == '}') { + json_simple_patch[l] = ']'; + json_simple_patch[l + 1] = '}'; + break; + } + } + if (value_vec_tmp != NULL) + free(value_vec_tmp); + // Send the simple patch request + ret = api_data_write(h, req, simple_patch_request_uri, pcvec, pi, qvec, json_simple_patch, pretty, YANG_DATA_JSON, media_out, plain_patch_val, ds ); + cbuf_free(cb); + } + free(json_simple_patch); // NULL check was already done above + free(patch_header); // NULL check was already done before + if (x_simple_patch != NULL) + free(x_simple_patch); + } + } + if ((strcmp(op_val, "delete") == 0) || + (strcmp(op_val, "remove") == 0)) { + strcat(simple_patch_request_uri, target_val); + if (strcmp(op_val, "delete") == 0) { + // TODO - send error + } else { + // TODO - do not send error + } + api_data_delete(h, req, simple_patch_request_uri, pi, pretty, YANG_DATA_JSON, ds); + } + if (simple_patch_request_uri) + free(simple_patch_request_uri); + if (api_path_target) + free(api_path_target); + } + ok: + retval = 0; + done: + if (path_orig_1 != NULL) + free(path_orig_1); + if (vec) + free(vec); + if (xpath) + free(xpath); + if (nsc) + xml_nsctx_free(nsc); + if (xret) + xml_free(xret); + if (xerr) + xml_free(xerr); + if (xretcom) + xml_free(xretcom); + if (xretdis) + xml_free(xretdis); + if (xtop) + xml_free(xtop); + if (xdata0) + xml_free(xdata0); + if (cbx) + cbuf_free(cbx); + return retval; +} +#endif // YANG_PATCH + /*! Generic REST PUT method * @param[in] h Clixon handle * @param[in] req Generic Www handle @@ -673,9 +1204,16 @@ api_data_patch(clicon_handle h, media_in, media_out, 1, ds); break; case YANG_PATCH_XML: - case YANG_PATCH_JSON: /* RFC 8072 patch */ ret = restconf_notimplemented(h, req, pretty, media_out); break; + case YANG_PATCH_JSON: /* RFC 8072 patch */ +#ifdef YANG_PATCH + ret = api_data_yang_patch(h, req, api_path0, pcvec, pi, qvec, data, pretty, + media_out, ds); +#else + ret = restconf_unsupported_media(h, req, pretty, media_out); +#endif + break; default: ret = restconf_unsupported_media(h, req, pretty, media_out); break; diff --git a/apps/restconf/restconf_methods_post.c b/apps/restconf/restconf_methods_post.c index bc0e2bfb..d018b9e7 100644 --- a/apps/restconf/restconf_methods_post.c +++ b/apps/restconf/restconf_methods_post.c @@ -155,6 +155,7 @@ api_data_post(clicon_handle h, cvec *qvec, char *data, int pretty, + restconf_media media_in, restconf_media media_out, ietf_ds_t ds) { @@ -178,7 +179,6 @@ api_data_post(clicon_handle h, cxobj *x; char *username; int ret; - restconf_media media_in; int nrchildren0 = 0; yang_bind yb; @@ -231,7 +231,6 @@ api_data_post(clicon_handle h, * If xbot is top-level (api_path=null) it does not have a spec therefore look for * top-level (yspec) otherwise assume parent (xbot) is populated. */ - media_in = restconf_content_type(h); switch (media_in){ case YANG_DATA_XML: if ((ret = clixon_xml_parse_string(data, yb, yspec, &xbot, &xerr)) < 0){ diff --git a/apps/restconf/restconf_methods_post.h b/apps/restconf/restconf_methods_post.h index f01e53ee..3317d3ea 100644 --- a/apps/restconf/restconf_methods_post.h +++ b/apps/restconf/restconf_methods_post.h @@ -44,6 +44,7 @@ int api_data_post(clicon_handle h, void *req, char *api_path, int pi, cvec *qvec, char *data, int pretty, + restconf_media media_in, restconf_media media_out, ietf_ds_t ds); int api_operations_post(clicon_handle h, void *req, char *api_path, diff --git a/apps/restconf/restconf_root.c b/apps/restconf/restconf_root.c index aa79953e..068acb4b 100644 --- a/apps/restconf/restconf_root.c +++ b/apps/restconf/restconf_root.c @@ -331,7 +331,7 @@ api_data(clicon_handle h, retval = api_data_get(h, req, api_path, pcvec, pi, qvec, pretty, media_out, ds); } else if (strcmp(request_method, "POST")==0) { - retval = api_data_post(h, req, api_path, pi, qvec, data, pretty, media_out, ds); + retval = api_data_post(h, req, api_path, pi, qvec, data, pretty, restconf_content_type(h), media_out, ds); } else if (strcmp(request_method, "PUT")==0) { if (read_only) diff --git a/example/main/clixon-example@2020-12-01.yang b/example/main/clixon-example@2020-12-01.yang index 412e571d..361d81f0 100644 --- a/example/main/clixon-example@2020-12-01.yang +++ b/example/main/clixon-example@2020-12-01.yang @@ -15,6 +15,9 @@ module clixon-example { import ietf-datastores { prefix ds; } + import example-jukebox { + prefix ej; + } description "Clixon example used as a part of the Clixon test suite. It can be used as a basis for making new Clixon applications. diff --git a/example/main/example.xml b/example/main/example.xml index d745a759..058ecf9b 100644 --- a/example/main/example.xml +++ b/example/main/example.xml @@ -1,6 +1,7 @@ /usr/local/etc/example.xml ietf-netconf:startup + clixon-restconf:allow-auth-none /usr/local/share/clixon clixon-example example @@ -21,4 +22,5 @@ disabled true false + truenonedefault
0.0.0.0
80false
diff --git a/lib/clixon/clixon_json.h b/lib/clixon/clixon_json.h index 8bd45d10..2d4e1175 100644 --- a/lib/clixon/clixon_json.h +++ b/lib/clixon/clixon_json.h @@ -52,5 +52,4 @@ int json_print(FILE *f, cxobj *x); int xml2json_vec(FILE *f, cxobj **vec, size_t veclen, int pretty); int clixon_json_parse_string(char *str, yang_bind yb, yang_stmt *yspec, cxobj **xt, cxobj **xret); int clixon_json_parse_file(FILE *fp, yang_bind yb, yang_stmt *yspec, cxobj **xt, cxobj **xret); - #endif /* _CLIXON_JSON_H */ diff --git a/yang/mandatory/Makefile.in b/yang/mandatory/Makefile.in index ca68b32a..7a1d299e 100644 --- a/yang/mandatory/Makefile.in +++ b/yang/mandatory/Makefile.in @@ -50,6 +50,7 @@ YANGSPECS += ietf-restconf-monitoring@2017-01-26.yang YANGSPECS += ietf-yang-library@2019-01-04.yang YANGSPECS += ietf-yang-types@2013-07-15.yang YANGSPECS += ietf-datastores@2018-02-14.yang +YANGSPECS += ietf-yang-patch@2021-07-01.yang all: From df6f26c0debc38ebba5761aeb2eaf5709a2b69bd Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Mon, 19 Jul 2021 22:07:21 +0200 Subject: [PATCH 02/49] Misc cleanups, remove old yangs, typos, etc --- example/main/Makefile.in | 1 + example/main/example_backend.c | 1 + include/clixon_custom.h | 2 +- lib/src/clixon_yang_module.c | 1 + yang/clixon/Makefile.in | 2 +- yang/clixon/clixon-config.yang | 1 - ...-08.yang => clixon-config@2021-07-11.yang} | 95 +++++++- yang/clixon/clixon-lib@2020-12-30.yang | 180 -------------- yang/clixon/clixon-restconf@2021-03-15.yang | 221 ------------------ 9 files changed, 96 insertions(+), 408 deletions(-) delete mode 120000 yang/clixon/clixon-config.yang rename yang/clixon/{clixon-config@2021-03-08.yang => clixon-config@2021-07-11.yang} (90%) delete mode 100644 yang/clixon/clixon-lib@2020-12-30.yang delete mode 100644 yang/clixon/clixon-restconf@2021-03-15.yang diff --git a/example/main/Makefile.in b/example/main/Makefile.in index 356426c8..f1c28e09 100644 --- a/example/main/Makefile.in +++ b/example/main/Makefile.in @@ -101,6 +101,7 @@ BE_SRC = $(APPNAME)_backend.c BE_OBJ = $(BE_SRC:%.c=%.o) $(BE_PLUGIN): $(BE_OBJ) ifeq ($(LINKAGE),static) +# can include -L in LDFLAGS? $(CC) -Wall -shared $(LDFLAGS) -o $@ -lc $< -lclixon -L ../../apps/backend/ -lclixon_backend else $(CC) -Wall -shared $(LDFLAGS) -o $@ -lc $< -lclixon -lclixon_backend diff --git a/example/main/example_backend.c b/example/main/example_backend.c index cad6dc30..1d907030 100644 --- a/example/main/example_backend.c +++ b/example/main/example_backend.c @@ -1012,6 +1012,7 @@ example_exit(clicon_handle h) return 0; } +/* Forward declaration */ clixon_plugin_api *clixon_plugin_init(clicon_handle h); static clixon_plugin_api api = { diff --git a/include/clixon_custom.h b/include/clixon_custom.h index 30e114d0..d4af4463 100644 --- a/include/clixon_custom.h +++ b/include/clixon_custom.h @@ -108,7 +108,7 @@ * added to its parent but then it is more difficult to check trhe when condition. * This fix add the parent x0p as a "candidate" so that the xpath-eval function can use it as * an alernative if it exists. - * Note although this solves many usecases involving parents and absolute paths, itstill does not + * Note although this solves many usecases involving parents and absolute paths, it still does not * solve all usecases, such as absolute usecases where the added node is looked for */ #define XML_PARENT_CANDIDATE diff --git a/lib/src/clixon_yang_module.c b/lib/src/clixon_yang_module.c index 03e9695c..3d47f6f7 100644 --- a/lib/src/clixon_yang_module.c +++ b/lib/src/clixon_yang_module.c @@ -112,6 +112,7 @@ modstate_diff_free(modstate_diff_t *md) * * Load RFC7895 yang spec, module-set-id, etc. * @param[in] h Clicon handle + * @see netconf_module_load */ int yang_modules_init(clicon_handle h) diff --git a/yang/clixon/Makefile.in b/yang/clixon/Makefile.in index 555662be..ddd83935 100644 --- a/yang/clixon/Makefile.in +++ b/yang/clixon/Makefile.in @@ -41,7 +41,7 @@ datarootdir = @datarootdir@ # See also OPT_YANG_INSTALLDIR for the standard yang files YANG_INSTALLDIR = @YANG_INSTALLDIR@ -YANGSPECS = clixon-config@2021-05-20.yang # 5.2 +YANGSPECS = clixon-config@2021-07-11.yang # 5.3 YANGSPECS += clixon-lib@2021-03-08.yang # 5.1 YANGSPECS += clixon-rfc5277@2008-07-01.yang YANGSPECS += clixon-xml-changelog@2019-03-21.yang diff --git a/yang/clixon/clixon-config.yang b/yang/clixon/clixon-config.yang deleted file mode 120000 index 91d563e9..00000000 --- a/yang/clixon/clixon-config.yang +++ /dev/null @@ -1 +0,0 @@ -clixon-config@2021-03-08.yang \ No newline at end of file diff --git a/yang/clixon/clixon-config@2021-03-08.yang b/yang/clixon/clixon-config@2021-07-11.yang similarity index 90% rename from yang/clixon/clixon-config@2021-03-08.yang rename to yang/clixon/clixon-config@2021-07-11.yang index eb217e2e..dfc8d54b 100644 --- a/yang/clixon/clixon-config@2021-03-08.yang +++ b/yang/clixon/clixon-config@2021-07-11.yang @@ -43,12 +43,28 @@ module clixon-config { ***** END LICENSE BLOCK *****"; + revision 2021-07-11 { + description + "Added option + CLICON_SYSTEM_CAPABILITIES"; + } + revision 2021-05-20 { + description + "Added option: + CLICON_RESTCONF_USER + CLICON_RESTCONF_PRIVILEGES + CLICON_RESTCONF_INSTALLDIR + CLICON_RESTCONF_STARTUP_DONTUPDATE + CLICON_NETCONF_MESSAGE_ID_OPTIONAL + Released in Clixon 5.2"; + } revision 2021-03-08 { description "Added option: CLICON_NETCONF_HELLO_OPTIONAL CLICON_CLI_AUTOCLI_EXCLUDE - CLICON_XMLDB_UPGRADE_CHECKOLD"; + CLICON_XMLDB_UPGRADE_CHECKOLD + Released in Clixon 5.1"; } revision 2020-12-30 { description @@ -171,6 +187,10 @@ module clixon-config { "Commit startup configuration into running state After reboot when no persistent running db exists"; } + enum running-startup{ + description + "First try running db, if it is empty try startup db."; + } } } typedef datastore_format{ @@ -406,7 +426,11 @@ module clixon-config { "If false, skip Yang list check sanity checks from RFC 7950, Sec 7.8.2: The 'key' statement, which MUST be present if the list represents configuration. Some yang specs seem not to fulfil this. However, if you reset this, there may - be follow-up errors due to code that assumes a configuration list has keys"; + be follow-up errors due to code that assumes a configuration list has keys + Marked as obsolete since the observation above seemed to be related to the + yang-data extension in RFC8040 allows non-key lists. This has been implemented + by a YANG_FLAG_NOKEY yang flag mechanism"; + status obsolete; } leaf CLICON_YANG_UNKNOWN_ANYDATA{ type boolean; @@ -421,6 +445,18 @@ module clixon-config { only loading from startup but may occur in other circumstances as well. This means that sanity checks of erroneous XML/JSON may not be properly signalled."; } + leaf CLICON_SYSTEM_CAPABILITIES { + type boolean; + default false; + description + "Enable module ietf-system-capabilities and ietf-notification-capabilities + Note: There are several dependencies: + - ietf-yang-library revision 2019-01-04 is REQUIRED + - nacm + - ietf-yang-structure-ext.yang, + - ietf-yang-instance-data + see draft-ietf-netconf-notification-capabilities-17"; + } leaf CLICON_BACKEND_DIR { type string; description @@ -451,6 +487,16 @@ module clixon-config { is returned, which conforms to the RFC. Note this applies only to external NETCONF, not the internal (IPC) netconf"; } + leaf CLICON_NETCONF_MESSAGE_ID_OPTIONAL { + type boolean; + default false; + description + "This option relates to RFC 6241 Sec 4.1 Element + The element has a mandatory attribute 'message-id', which is a + string chosen by the sender of the RPC. + If true, an RPC can be sent without a message-id. + This applies to both external NETCONF and internal (IPC) netconf"; + } leaf CLICON_RESTCONF_DIR { type string; description @@ -470,7 +516,28 @@ module clixon-config { Note: Obsolete, use fcgi-socket in clixon-restconf.yang instead"; status obsolete; } - + leaf CLICON_RESTCONF_INSTALLDIR { + type string; + default "/usr/local/sbin"; + description + "Path to dir of clixon-restconf daemon binary as used by backend if started internally + Discussion: Somewhat problematic to have it as run time option. It may think it + should be known at configure or install time, but for example the main docker + installation moves the binaries, and this may be true elsewehere too. + Maybe one could locate it via PATHs search"; + } + leaf CLICON_RESTCONF_STARTUP_DONTUPDATE { + type boolean; + default false; + description + "According to RFC 8040 Sec 1.4: + If the NETCONF server supports :startup, the RESTCONF server MUST automatically + update the [...] startup configuration [...] as a consequence of a RESTCONF + edit operation. + Setting this option disables this behaviour, ie the startup configuration is NOT + automatically updated. + If this option is false, the startup is autoamtically updated following the RFC"; + } leaf CLICON_RESTCONF_PRETTY { type boolean; default true; @@ -486,6 +553,26 @@ module clixon-config { Note: Obsolete, use pretty in clixon-restconf.yang instead"; status obsolete; } + leaf CLICON_RESTCONF_USER { + type string; + description + "Run clixon_daemon as this user + When drop privileges is used, the daemon will drop privileges to this user. + In pre-5.2 code this was configured as compile-time constant WWWUSER with + default value www-data + See also CLICON_PRIVILEGES setting"; + default www-data; + } + leaf CLICON_RESTCONF_PRIVILEGES { + type priv_mode; + default drop_perm; + description + "Restconf privileges mode. + If drop_perm or drop_temp then drop privileges to CLICON_RESTCONF_USER. + If the platform does not support getresuid and accompanying functions, the mode + must be set to 'none'. + "; + } leaf CLICON_CLI_DIR { type string; description @@ -706,7 +793,7 @@ module clixon-config { user (eg datastores). It also sets the backend unix socket owner to this user, but its group is set by CLICON_SOCK_GROUP. - See also CLICON_PRIVILEGES setting"; + See also CLICON_BACKEND_PRIVILEGES setting"; } leaf CLICON_BACKEND_PRIVILEGES { type priv_mode; diff --git a/yang/clixon/clixon-lib@2020-12-30.yang b/yang/clixon/clixon-lib@2020-12-30.yang deleted file mode 100644 index c3780d1e..00000000 --- a/yang/clixon/clixon-lib@2020-12-30.yang +++ /dev/null @@ -1,180 +0,0 @@ -module clixon-lib { - yang-version 1.1; - namespace "http://clicon.org/lib"; - prefix cl; - - organization - "Clicon / Clixon"; - - contact - "Olof Hagsand "; - - description - "Clixon Netconf extensions for communication between clients and backend. - - ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2019 Olof Hagsand - Copyright (C) 2020-2021 Olof Hagsand and Rubicon Communications, LLC(Netgate) - - This file is part of CLIXON - - Licensed under the Apache License, Version 2.0 (the \"License\"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an \"AS IS\" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - Alternatively, the contents of this file may be used under the terms of - the GNU General Public License Version 3 or later (the \"GPL\"), - in which case the provisions of the GPL are applicable instead - of those above. If you wish to allow use of your version of this file only - under the terms of the GPL, and not to allow others to - use your version of this file under the terms of Apache License version 2, - indicate your decision by deleting the provisions above and replace them with - the notice and other provisions required by the GPL. If you do not delete - the provisions above, a recipient may use your version of this file under - the terms of any one of the Apache License version 2 or the GPL. - - ***** END LICENSE BLOCK *****"; - - revision 2020-12-30 { - description - "Changed: RPC process-control output parameter status to pid"; - } - revision 2020-12-08 { - description - "Added: autocli-op extension. - rpc process-control for process/daemon management - Released in clixon 4.9"; - } - revision 2020-04-23 { - description - "Added: stats RPC for clixon XML and memory statistics. - Added: restart-plugin RPC for restarting individual plugins without restarting backend."; - } - revision 2019-08-13 { - description - "No changes (reverted change)"; - } - revision 2019-06-05 { - description - "ping rpc added for liveness"; - } - revision 2019-01-02 { - description - "Released in Clixon 3.9"; - } - typedef service-operation { - type enumeration { - enum start { - description - "Start if not already running"; - } - enum stop { - description - "Stop if running"; - } - enum restart { - description - "Stop if running, then start"; - } - enum status { - description - "Check status"; - } - } - description - "Common operations that can be performed on a service"; - } - extension autocli-op { - description - "Takes an argument an operation defing how to modify the clispec at - this point in the YANG tree for the automated generated CLI. - Note that this extension is only used in clixon_cli. - Operations is expected to be extended, but the following operations are defined: - - hide This command is active but not shown by ? or TAB"; - argument cliop; - } - rpc debug { - description "Set debug level of backend."; - input { - leaf level { - type uint32; - } - } - } - rpc ping { - description "Check aliveness of backend daemon."; - } - rpc stats { - description "Clixon XML statistics."; - output { - container global{ - description "Clixon global statistics"; - leaf xmlnr{ - description "Number of XML objects: number of residing xml/json objects - in the internal 'cxobj' representation."; - type uint64; - } - } - list datastore{ - description "Datastore statistics"; - key "name"; - leaf name{ - description "name of datastore (eg running)."; - type string; - } - leaf nr{ - description "Number of XML objects. That is number of residing xml/json objects - in the internal 'cxobj' representation."; - type uint64; - } - leaf size{ - description "Size in bytes of internal datastore cache of datastore tree."; - type uint64; - } - } - - } - } - rpc restart-plugin { - description "Restart specific backend plugins."; - input { - leaf-list plugin { - description "Name of plugin to restart"; - type string; - } - } - } - - rpc process-control { - description - "Control a specific process or daemon: start/stop, etc. - This is for direct managing of a process by the backend. - Alternatively one can manage a daemon via systemd, containerd, kubernetes, etc."; - input { - leaf name { - description "Name of process"; - type string; - mandatory true; - } - leaf operation { - type service-operation; - mandatory true; - description - "One of the strings 'start', 'stop', 'restart', or 'status'."; - } - } - output { - leaf pid { - description "Process-id of running process or 0 if not running - Value is only valid for operation status"; - type uint32; - } - } - } -} diff --git a/yang/clixon/clixon-restconf@2021-03-15.yang b/yang/clixon/clixon-restconf@2021-03-15.yang deleted file mode 100644 index 7180054a..00000000 --- a/yang/clixon/clixon-restconf@2021-03-15.yang +++ /dev/null @@ -1,221 +0,0 @@ -module clixon-restconf { - yang-version 1.1; - namespace "http://clicon.org/restconf"; - prefix "clrc"; - - import ietf-inet-types { - prefix inet; - } - - organization - "Clixon"; - - contact - "Olof Hagsand "; - - description - "This YANG module provides a data-model for the Clixon RESTCONF daemon. - ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate) - - This file is part of CLIXON - - Licensed under the Apache License, Version 2.0 (the \"License\"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an \"AS IS\" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - Alternatively, the contents of this file may be used under the terms of - the GNU General Public License Version 3 or later (the \"GPL\"), - in which case the provisions of the GPL are applicable instead - of those above. If you wish to allow use of your version of this file only - under the terms of the GPL, and not to allow others to - use your version of this file under the terms of Apache License version 2, - indicate your decision by deleting the provisions above and replace them with - the notice and other provisions required by the GPL. If you do not delete - the provisions above, a recipient may use your version of this file under - the terms of any one of the Apache License version 2 or the GPL. - - ***** END LICENSE BLOCK *****"; - - revision 2021-03-15 { - description - "make authentication-type none a feature - Added flag to enable core dumps"; - } - revision 2020-12-30 { - description - "Added: debug field - Added 'none' as default value for auth-type - Changed http-auth-type enum from 'password' to 'user'"; - } - revision 2020-10-30 { - description - "Initial release"; - } - - feature fcgi { - description - "This feature indicates that the restconf server supports the fast-cgi reverse - proxy solution. - That is, a reverse proxy is the HTTP front-end and the restconf daemon listens - to a fcgi socket. - The alternative is the internal HTTP solution using evhtp."; - } - - feature allow-auth-none { - description - "This feature allows the use of authentication-type none."; - } - - typedef http-auth-type { - type enumeration { - enum none { - if-feature "allow-auth-none"; - description - "Incoming message are set to authenticated by default. No ca-auth callback is called, - Authenticated user is set to special user 'none'. - Typically assumes NACM is not enabled."; - } - enum client-certificate { - description - "TLS client certificate validation is made on each incoming message. If it passes - the authenticated user is extracted from the SSL_CN parameter - The ca-auth callback can be used to revise this behavior."; - } - enum user { - description - "User-defined authentication as defined by the ca-auth callback. - One example is some form of password authentication, such as basic auth."; - } - } - description - "Enumeration of HTTP authorization types."; - } - grouping clixon-restconf{ - description - "HTTP RESTCONF configuration."; - leaf enable { - type boolean; - default "false"; - description - "Enables RESTCONF functionality. - Note that starting/stopping of a restconf daemon is different from it being - enabled or not. - For example, if the restconf daemon is under systemd management, the restconf - daemon will only start if enable=true."; - } - leaf auth-type { - type http-auth-type; - description - "The authentication type. - Note client-certificate applies only if ssl-enable is true and socket has ssl"; - default user; - } - leaf debug { - description - "Set debug level of restconf daemon. - 0 is no debug, 1 is debugging, more is detailed debug. - Debug logs will be directed to syslog with - ident: clixon_restconf and PID - facility: LOG_USER - level: LOG_DEBUG"; - type uint32; - default 0; - } - leaf enable-core-dump { - description - "enable core dumps. - this is a no-op on systems that don't support it."; - type boolean; - default false; - } - leaf pretty { - type boolean; - default true; - description - "Restconf return value pretty print. - Restconf clients may add HTTP header: - Accept: application/yang-data+json, or - Accept: application/yang-data+xml - to get return value in XML or JSON. - RFC 8040 examples print XML and JSON in pretty-printed form. - Setting this value to false makes restconf return not pretty-printed - which may be desirable for performance or tests - This replaces the CLICON_RESTCONF_PRETTY option in clixon-config.yang"; - } - /* From this point only specific options - * First fcgi-specific options - */ - leaf fcgi-socket { - if-feature fcgi; /* Set by default by fcgi clixon_restconf daemon */ - type string; - default "/www-data/fastcgi_restconf.sock"; - description - "Path to FastCGI unix socket. Should be specified in webserver - Eg in nginx: fastcgi_pass unix:/www-data/clicon_restconf.sock - Only if with-restconf=fcgi, NOT evhtp - This replaces CLICON_RESTCONF_PATH option in clixon-config.yang"; - } - /* Second, evhtp-specific options */ - leaf server-cert-path { - type string; - description - "Path to server certificate file. - Note only applies if socket has ssl enabled"; - } - leaf server-key-path { - type string; - description - "Path to server key file - Note only applies if socket has ssl enabled"; - } - leaf server-ca-cert-path { - type string; - description - "Path to server CA cert file - Note only applies if socket has ssl enabled"; - } - list socket { - description - "List of server sockets that the restconf daemon listens to"; - key "namespace address port"; - leaf namespace { - type string; - description - "Network namespace. - On platforms where namespaces are not suppported, 'default' - Default value can be changed by RESTCONF_NETNS_DEFAULT"; - } - leaf address { - type inet:ip-address; - description "IP address to bind to"; - } - leaf port { - type inet:port-number; - description "TCP port to bind to"; - } - leaf ssl { - type boolean; - default true; - description "Enable for HTTPS otherwise HTTP protocol"; - } - } - } - container restconf { - description - "This presence is strictly not necessary since the enable flag - in clixon-restconf is the flag bearing the actual semantics. - However, removing the presence leads to default config in all - clixon installations, even those which do not use backend-started restconf. - One could see this as mostly cosmetically annoying. - Alternative would be to make the inclusion of this yang conditional."; - presence "Enables RESTCONF"; - uses clixon-restconf; - } -} From b1e5e8548a27072311e63538c6d28d00745a2517 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Tue, 20 Jul 2021 15:54:42 +0200 Subject: [PATCH 03/49] added ietf-yang-patch.yang and enabled clixon_util_validate --- util/Makefile.in | 2 +- .../mandatory/ietf-yang-patch@2017-02-22.yang | 390 ++++++++++++++++++ 2 files changed, 391 insertions(+), 1 deletion(-) create mode 100644 yang/mandatory/ietf-yang-patch@2017-02-22.yang diff --git a/util/Makefile.in b/util/Makefile.in index f857033e..5737529b 100644 --- a/util/Makefile.in +++ b/util/Makefile.in @@ -91,7 +91,7 @@ APPSRC += clixon_util_path.c APPSRC += clixon_util_datastore.c APPSRC += clixon_util_regexp.c APPSRC += clixon_util_socket.c -# APPSRC += clixon_util_validate.c +APPSRC += clixon_util_validate.c APPSRC += clixon_netconf_ssh_callhome.c APPSRC += clixon_netconf_ssh_callhome_client.c ifdef with_restconf diff --git a/yang/mandatory/ietf-yang-patch@2017-02-22.yang b/yang/mandatory/ietf-yang-patch@2017-02-22.yang new file mode 100644 index 00000000..d0029ed2 --- /dev/null +++ b/yang/mandatory/ietf-yang-patch@2017-02-22.yang @@ -0,0 +1,390 @@ +module ietf-yang-patch { + yang-version 1.1; + namespace "urn:ietf:params:xml:ns:yang:ietf-yang-patch"; + prefix "ypatch"; + + import ietf-restconf { prefix rc; } + + organization + "IETF NETCONF (Network Configuration) Working Group"; + + contact + "WG Web: + WG List: + + Author: Andy Bierman + + + Author: Martin Bjorklund + + + Author: Kent Watsen + "; + + description + "This module contains conceptual YANG specifications + for the YANG Patch and YANG Patch Status data structures. + + Note that the YANG definitions within this module do not + represent configuration data of any kind. + The YANG grouping statements provide a normative syntax + for XML and JSON message-encoding purposes. + + Copyright (c) 2017 IETF Trust and the persons identified as + authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Simplified BSD License + set forth in Section 4.c of the IETF Trust's Legal Provisions + Relating to IETF Documents + (http://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC 8072; see + the RFC itself for full legal notices."; + + revision 2017-02-22 { + description + "Initial revision."; + reference + "RFC 8072: YANG Patch Media Type."; + } + + typedef target-resource-offset { + type string; + description + "Contains a data resource identifier string representing + a sub-resource within the target resource. + The document root for this expression is the + target resource that is specified in the + protocol operation (e.g., the URI for the PATCH request). + + This string is encoded according to the same rules as those + for a data resource identifier in a RESTCONF request URI."; + reference + "RFC 8040, Section 3.5.3."; + } + + rc:yang-data "yang-patch" { + uses yang-patch; + } + + rc:yang-data "yang-patch-status" { + uses yang-patch-status; + } + + grouping yang-patch { + + description + "A grouping that contains a YANG container representing the + syntax and semantics of a YANG Patch edit request message."; + + container yang-patch { + description + "Represents a conceptual sequence of datastore edits, + called a patch. Each patch is given a client-assigned + patch identifier. Each edit MUST be applied + in ascending order, and all edits MUST be applied. + If any errors occur, then the target datastore MUST NOT + be changed by the YANG Patch operation. + + It is possible for a datastore constraint violation to occur + due to any node in the datastore, including nodes not + included in the 'edit' list. Any validation errors MUST + be reported in the reply message."; + + reference + "RFC 7950, Section 8.3."; + + leaf patch-id { + type string; + mandatory true; + description + "An arbitrary string provided by the client to identify + the entire patch. Error messages returned by the server + that pertain to this patch will be identified by this + 'patch-id' value. A client SHOULD attempt to generate + unique 'patch-id' values to distinguish between + transactions from multiple clients in any audit logs + maintained by the server."; + } + + leaf comment { + type string; + description + "An arbitrary string provided by the client to describe + the entire patch. This value SHOULD be present in any + audit logging records generated by the server for the + patch."; + } + + list edit { + key edit-id; + ordered-by user; + + description + "Represents one edit within the YANG Patch request message. + The 'edit' list is applied in the following manner: + + - The first edit is conceptually applied to a copy + of the existing target datastore, e.g., the + running configuration datastore. + - Each ascending edit is conceptually applied to + the result of the previous edit(s). + - After all edits have been successfully processed, + the result is validated according to YANG constraints. + - If successful, the server will attempt to apply + the result to the target datastore."; + + leaf edit-id { + type string; + description + "Arbitrary string index for the edit. + Error messages returned by the server that pertain + to a specific edit will be identified by this value."; + } + + leaf operation { + type enumeration { + enum create { + description + "The target data node is created using the supplied + value, only if it does not already exist. The + 'target' leaf identifies the data node to be + created, not the parent data node."; + } + enum delete { + description + "Delete the target node, only if the data resource + currently exists; otherwise, return an error."; + } + + enum insert { + description + "Insert the supplied value into a user-ordered + list or leaf-list entry. The target node must + represent a new data resource. If the 'where' + parameter is set to 'before' or 'after', then + the 'point' parameter identifies the insertion + point for the target node."; + } + enum merge { + description + "The supplied value is merged with the target data + node."; + } + enum move { + description + "Move the target node. Reorder a user-ordered + list or leaf-list. The target node must represent + an existing data resource. If the 'where' parameter + is set to 'before' or 'after', then the 'point' + parameter identifies the insertion point to move + the target node."; + } + enum replace { + description + "The supplied value is used to replace the target + data node."; + } + enum remove { + description + "Delete the target node if it currently exists."; + } + } + mandatory true; + description + "The datastore operation requested for the associated + 'edit' entry."; + } + + leaf target { + type target-resource-offset; + mandatory true; + description + "Identifies the target data node for the edit + operation. If the target has the value '/', then + the target data node is the target resource. + The target node MUST identify a data resource, + not the datastore resource."; + } + + leaf point { + when "(../operation = 'insert' or ../operation = 'move')" + + "and (../where = 'before' or ../where = 'after')" { + description + "This leaf only applies for 'insert' or 'move' + operations, before or after an existing entry."; + } + type target-resource-offset; + description + "The absolute URL path for the data node that is being + used as the insertion point or move point for the + target of this 'edit' entry."; + } + + leaf where { + when "../operation = 'insert' or ../operation = 'move'" { + description + "This leaf only applies for 'insert' or 'move' + operations."; + } + type enumeration { + enum before { + description + "Insert or move a data node before the data resource + identified by the 'point' parameter."; + } + enum after { + description + "Insert or move a data node after the data resource + identified by the 'point' parameter."; + } + + enum first { + description + "Insert or move a data node so it becomes ordered + as the first entry."; + } + enum last { + description + "Insert or move a data node so it becomes ordered + as the last entry."; + } + } + default last; + description + "Identifies where a data resource will be inserted + or moved. YANG only allows these operations for + list and leaf-list data nodes that are + 'ordered-by user'."; + } + + anydata value { + when "../operation = 'create' " + + "or ../operation = 'merge' " + + "or ../operation = 'replace' " + + "or ../operation = 'insert'" { + description + "The anydata 'value' is only used for 'create', + 'merge', 'replace', and 'insert' operations."; + } + description + "Value used for this edit operation. The anydata 'value' + contains the target resource associated with the + 'target' leaf. + + For example, suppose the target node is a YANG container + named foo: + + container foo { + leaf a { type string; } + leaf b { type int32; } + } + + The 'value' node contains one instance of foo: + + + + some value + 42 + + + "; + } + } + } + + } // grouping yang-patch + + grouping yang-patch-status { + + description + "A grouping that contains a YANG container representing the + syntax and semantics of a YANG Patch Status response + message."; + + container yang-patch-status { + description + "A container representing the response message sent by the + server after a YANG Patch edit request message has been + processed."; + + leaf patch-id { + type string; + mandatory true; + description + "The 'patch-id' value used in the request."; + } + + choice global-status { + description + "Report global errors or complete success. + If there is no case selected, then errors + are reported in the 'edit-status' container."; + + case global-errors { + uses rc:errors; + description + "This container will be present if global errors that + are unrelated to a specific edit occurred."; + } + leaf ok { + type empty; + description + "This leaf will be present if the request succeeded + and there are no errors reported in the 'edit-status' + container."; + } + } + + container edit-status { + description + "This container will be present if there are + edit-specific status responses to report. + If all edits succeeded and the 'global-status' + returned is 'ok', then a server MAY omit this + container."; + + list edit { + key edit-id; + + description + "Represents a list of status responses, + corresponding to edits in the YANG Patch + request message. If an 'edit' entry was + skipped or not reached by the server, + then this list will not contain a corresponding + entry for that edit."; + + leaf edit-id { + type string; + description + "Response status is for the 'edit' list entry + with this 'edit-id' value."; + } + + choice edit-status-choice { + description + "A choice between different types of status + responses for each 'edit' entry."; + leaf ok { + type empty; + description + "This 'edit' entry was invoked without any + errors detected by the server associated + with this edit."; + } + case errors { + uses rc:errors; + description + "The server detected errors associated with the + edit identified by the same 'edit-id' value."; + } + } + } + } + } + } // grouping yang-patch-status + +} From 91313b31e0de4b0fb2476945f1f988f6fe67b597 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sat, 24 Jul 2021 16:43:47 +0200 Subject: [PATCH 04/49] Removed space from xpath canonical form of relex/unionex --- lib/src/clixon_xpath.c | 7 +++++-- test/test_xpath_canonical.sh | 6 +++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/src/clixon_xpath.c b/lib/src/clixon_xpath.c index 64352055..43845787 100644 --- a/lib/src/clixon_xpath.c +++ b/lib/src/clixon_xpath.c @@ -290,11 +290,14 @@ xpath_tree2cbuf(xpath_tree *xs, switch (xs->xs_type){ case XP_AND: /* and or */ case XP_ADD: /* div mod + * - */ - case XP_RELEX: /* !=, >= <= < > = */ - case XP_UNION: if (xs->xs_c1) cprintf(xcb, " %s ", clicon_int2str(xpopmap, xs->xs_int)); break; + case XP_RELEX: /* !=, >= <= < > = */ + case XP_UNION: /* | */ + if (xs->xs_c1) + cprintf(xcb, "%s", clicon_int2str(xpopmap, xs->xs_int)); + break; case XP_PATHEXPR: /* [19] PathExpr ::= | FilterExpr '/' RelativeLocationPath | FilterExpr '//' RelativeLocationPath diff --git a/test/test_xpath_canonical.sh b/test/test_xpath_canonical.sh index e541234b..d339d31c 100755 --- a/test/test_xpath_canonical.sh +++ b/test/test_xpath_canonical.sh @@ -48,13 +48,13 @@ new "xpath canonical form (other)" expectpart "$($clixon_util_xpath -c -y $ydir -p /i:x/j:y -n i:urn:example:a -n j:urn:example:b)" 0 '/a:x/b:y' '0 : a = "urn:example:a"' '1 : b = "urn:example:b"' new "xpath canonical form predicate 1" -expectpart "$($clixon_util_xpath -c -y $ydir -p "/i:x[j:y='e1']" -n i:urn:example:a -n j:urn:example:b)" 0 "/a:x\[b:y = 'e1'\]" '0 : a = "urn:example:a"' '1 : b = "urn:example:b"' +expectpart "$($clixon_util_xpath -c -y $ydir -p "/i:x[j:y='e1']" -n i:urn:example:a -n j:urn:example:b)" 0 "/a:x\[b:y='e1'\]" '0 : a = "urn:example:a"' '1 : b = "urn:example:b"' new "xpath canonical form predicate self" -expectpart "$($clixon_util_xpath -c -y $ydir -p "/i:x[.='42']" -n i:urn:example:a -n j:urn:example:b)" 0 "/a:x\[. = '42'\]" '0 : a = "urn:example:a"' +expectpart "$($clixon_util_xpath -c -y $ydir -p "/i:x[.='42']" -n i:urn:example:a -n j:urn:example:b)" 0 "/a:x\[.='42'\]" '0 : a = "urn:example:a"' new "xpath canonical form descendants" -expectpart "$($clixon_util_xpath -c -y $ydir -p "//x[.='42']" -n null:urn:example:a -n j:urn:example:b)" 0 "//a:x\[. = '42'\]" '0 : a = "urn:example:a"' +expectpart "$($clixon_util_xpath -c -y $ydir -p "//x[.='42']" -n null:urn:example:a -n j:urn:example:b)" 0 "//a:x\[.='42'\]" '0 : a = "urn:example:a"' new "xpath canonical form (no default should fail)" expectpart "$($clixon_util_xpath -c -y $ydir -p /x/j:y -n i:urn:example:a -n j:urn:example:b 2> /dev/null)" 255 From 46d07c1372e78010071883c17afa172f1beb300b Mon Sep 17 00:00:00 2001 From: Alan Yaniger Date: Sun, 25 Jul 2021 09:04:26 +0300 Subject: [PATCH 05/49] split up long function, use safe string handling functions --- apps/restconf/restconf_methods.c | 690 ++++++++++++++++++------------- apps/restconf/restconf_methods.h | 1 + 2 files changed, 409 insertions(+), 282 deletions(-) diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index 3d9630bb..a13ba562 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -581,6 +581,367 @@ api_data_write(clicon_handle h, } /* api_data_write */ #ifdef YANG_PATCH + +char * init_str() +{ + char* s; + s = malloc(TEMP_STR_MALLOC_SIZE); + memset(s, 0, TEMP_STR_MALLOC_SIZE); + return s; +} + +int cpy_str(char *dest, char *src, size_t size) +{ + if (src == NULL) { + return 0; + } + if (dest == NULL) { + init_str(dest); + } + if (size <= 0 || size > TEMP_STR_MALLOC_SIZE) { + return 0; + } + size_t i; + for (i = 0; i < size - 1 && src[i]; i++) { + dest[i] = src[i]; + } + dest[i] = '\0'; + return i; +} + +int cat_str(char *dest, char *src, size_t size) +{ + if (src == NULL) { + return 0; + } + if (dest == NULL) { + init_str(dest); + } + if (size <= 0 || size > TEMP_STR_MALLOC_SIZE) { + return 0; + } + size_t i; + int old_len = strlen(dest); + for (i = 0; i < size - 1 && src[i]; i++) { + dest[i + old_len] = src[i]; + } + dest[i + old_len] = '\0'; + return i; +} + +void free_mem(void *str) +{ + if (str != NULL) + free(str); +} + +int get_xval( + cvec* nsc, + cxobj* xn, + char* val, + cxobj **vec, + size_t veclen, + const char* key + ) +{ + char* tmp_val = NULL; + int ret = xpath_vec(xn, nsc, "%s", &vec, &veclen, key); + if (ret < 0) { + return ret; + } + for (int j = 0; j < veclen; j++) { + cxobj *xn = vec[j]; + tmp_val = xml_body(xn); + } + cpy_str(val, tmp_val, TEMP_STR_MALLOC_SIZE); + return 0; +} + +int do_replace ( + clicon_handle h, + void *req, + int pi, + cvec *qvec, + int pretty, + restconf_media media_out, + ietf_ds_t ds, + char* simple_patch_request_uri, + char* target_val, + int value_vec_len, + cxobj** value_vec, + cxobj * value_vec_tmp, + cxobj *x_simple_patch, + char *patch_header + ) +{ + char *delete_req_uri = init_str(); + if (delete_req_uri == NULL) + return 1; + + if (cpy_str(delete_req_uri, simple_patch_request_uri, TEMP_STR_MALLOC_SIZE) <= 0) + return 1; + + if (cat_str(delete_req_uri, target_val, TEMP_STR_MALLOC_SIZE) <= 0) + return 1; + + // Delete the object with the old values + int ret = api_data_delete(h, req, delete_req_uri, pi, pretty, YANG_DATA_JSON, ds ); + free_mem((void *)delete_req_uri); + if (ret != 0) + return ret; + + // Now insert the object with the new values + char *json_simple_patch = init_str(); + if (json_simple_patch == NULL) + return 1; // goto done; + + for (int k = 0; k < value_vec_len; k++) { + if (value_vec[k] != NULL) { + value_vec_tmp = xml_dup(value_vec[k]); + xml_addsub(x_simple_patch, value_vec_tmp); + } + } + cbuf* cb = cbuf_new(); + xml2json_cbuf(cb, x_simple_patch, 1); + + // Some ugly text processing to get the JSON to match what api_data_post() expects + char *json_simple_patch_tmp = cbuf_get(cb); + int brace_count = 0; + for (int l = 0; l < strlen(json_simple_patch_tmp); l++) { + char c = json_simple_patch_tmp[l]; + if (c == '{') { + brace_count++; + if (brace_count == 2) { + json_simple_patch[strlen(json_simple_patch)] = '['; + } + } + json_simple_patch[strlen(json_simple_patch)] = c; + } + + /* strip /... from end */ + char *post_req_uri = init_str(); + if (post_req_uri == NULL) + return 1; + + int idx = strlen(target_val); + for (int l = strlen(target_val); l>= 0; l--) { + if (target_val[l] == '/') { + idx = l; + break; + } + } + cpy_str(post_req_uri, target_val, idx); + cat_str(simple_patch_request_uri, post_req_uri, TEMP_STR_MALLOC_SIZE); + free_mem((void *)post_req_uri); + for (int l = strlen(json_simple_patch); l>= 0; l--) { + char c = json_simple_patch[l]; + if (c == '}') { + json_simple_patch[l] = ']'; + json_simple_patch[l + 1] = '}'; + break; + } + } + + // Send the POST request + ret = api_data_post(h, req, simple_patch_request_uri, pi, qvec, json_simple_patch, pretty, YANG_DATA_JSON, media_out, ds ); + + free_mem((void *)value_vec_tmp); + free_mem((void *)x_simple_patch); + free_mem((void *)patch_header); + return ret; +} + +int do_create ( + clicon_handle h, + void *req, + int pi, + cvec *qvec, + int pretty, + restconf_media media_out, + ietf_ds_t ds, + char* simple_patch_request_uri, + int value_vec_len, + cxobj** value_vec, + cxobj * value_vec_tmp, + cxobj *x_simple_patch, + char *patch_header + ) +{ + for (int k = 0; k < value_vec_len; k++) { + if (value_vec[k] != NULL) { + value_vec_tmp = xml_dup(value_vec[k]); + xml_addsub(x_simple_patch, value_vec_tmp); + } + } + + // Send the POST request + cbuf* cb = cbuf_new(); + xml2json_cbuf(cb, x_simple_patch, 1); + char *json_simple_patch = cbuf_get(cb); + int ret = api_data_post(h, req, simple_patch_request_uri, pi, qvec, json_simple_patch, pretty, YANG_DATA_JSON, media_out, ds ); + free_mem((void *)value_vec_tmp); + free_mem((void *)x_simple_patch); + free_mem((void *)patch_header); + return ret; +} + +int do_insert ( + clicon_handle h, + void *req, + int pi, + int pretty, + restconf_media media_out, + ietf_ds_t ds, + char* simple_patch_request_uri, + int value_vec_len, + cxobj** value_vec, + cxobj * value_vec_tmp, + cxobj *x_simple_patch, + char *patch_header, + char* where_val, + char* api_path, + char *point_val + ) +{ + char *json_simple_patch = init_str(); + if (json_simple_patch == NULL) + return 1; + + // Loop through the XML, and get each value + for (int k = 0; k < value_vec_len; k++) { + if (value_vec[k] != NULL) { + value_vec_tmp = xml_dup(value_vec[k]); + xml_addsub(x_simple_patch, value_vec_tmp); + } + } + cbuf* cb = cbuf_new(); + xml2json_cbuf(cb, x_simple_patch, 1); + + // Some ugly text processing to get the JSON to match what api_data_post() expects + char *json_simple_patch_tmp = cbuf_get(cb); + int brace_count = 0; + for (int l = 0; l < strlen(json_simple_patch_tmp); l++) { + char c = json_simple_patch_tmp[l]; + if (c == '{') { + brace_count++; + if (brace_count == 2) { + json_simple_patch[strlen(json_simple_patch)] = '['; + } + } + json_simple_patch[strlen(json_simple_patch)] = c; + } + for (int l = strlen(json_simple_patch); l>= 0; l--) { + char c = json_simple_patch[l]; + if (c == '}') { + json_simple_patch[l] = ']'; + json_simple_patch[l + 1] = '}'; + break; + } + } + + // Set the insert attributes + cvec* qvec_tmp = NULL; + qvec_tmp = cvec_new(0); + if (qvec_tmp == NULL) + return 1; + cg_var *cv; + if ((cv = cvec_add(qvec_tmp, CGV_STRING)) == NULL){ + return 1; + } + cv_name_set(cv, "insert"); + cv_string_set(cv, where_val); + char *point_str = init_str(); + if (point_str == NULL) + return 1; + cpy_str(point_str, api_path, TEMP_STR_MALLOC_SIZE); + cat_str(point_str, point_val, TEMP_STR_MALLOC_SIZE); + if ((cv = cvec_add(qvec_tmp, CGV_STRING)) == NULL){ + return 1; + } + cv_name_set(cv, "point"); + cv_string_set(cv, point_str); + + // Send the POST request + int ret = api_data_post(h, req, simple_patch_request_uri, pi, qvec_tmp, json_simple_patch, pretty, YANG_DATA_JSON, media_out, ds ); + if (cb != NULL) + cbuf_free(cb); + free_mem((void *)value_vec_tmp); + free_mem((void *)point_str); + free_mem((void *)json_simple_patch); + free_mem((void *)patch_header); + free_mem((void *)x_simple_patch); + return ret; +} + +int do_merge ( + clicon_handle h, + void *req, + cvec *pcvec, + int pi, + cvec *qvec, + int pretty, + restconf_media media_out, + ietf_ds_t ds, + char* simple_patch_request_uri, + int value_vec_len, + cxobj** value_vec, + cxobj * value_vec_tmp, + cxobj *x_simple_patch, + cxobj *key_xn, + int plain_patch_val, + char *patch_header + ) +{ + int ret = -1; + if (key_xn != NULL) + xml_addsub(x_simple_patch, key_xn); + + char *json_simple_patch = init_str(); + if (json_simple_patch == NULL) + return 1; + + // Loop through the XML, create JSON from each one, and submit a simple patch + for (int k = 0; k < value_vec_len; k++) { + if (value_vec[k] != NULL) { + value_vec_tmp = xml_dup(value_vec[k]); + xml_addsub(x_simple_patch, value_vec_tmp); + } + cbuf* cb = cbuf_new(); + xml2json_cbuf(cb, x_simple_patch, 1); + + // Some ugly text processing to get the JSON to match what api_data_write() expects for a simple patch + char *json_simple_patch_tmp = cbuf_get(cb); + memset(json_simple_patch, 0, TEMP_STR_MALLOC_SIZE); + int brace_count = 0; + for (int l = 0; l < strlen(json_simple_patch_tmp); l++) { + char c = json_simple_patch_tmp[l]; + if (c == '{') { + brace_count++; + if (brace_count == 2) { + json_simple_patch[strlen(json_simple_patch)] = '['; + } + } + json_simple_patch[strlen(json_simple_patch)] = c; + } + for (int l = strlen(json_simple_patch); l>= 0; l--) { + char c = json_simple_patch[l]; + if (c == '}') { + json_simple_patch[l] = ']'; + json_simple_patch[l + 1] = '}'; + break; + } + } + free_mem(value_vec_tmp); + // Send the simple patch request + ret = api_data_write(h, req, simple_patch_request_uri, pcvec, pi, qvec, json_simple_patch, pretty, YANG_DATA_JSON, media_out, plain_patch_val, ds ); + cbuf_free(cb); + } + free_mem(json_simple_patch); + free_mem(patch_header); + free_mem(x_simple_patch); + return ret; +} + /*! YANG PATCH method * @param[in] h Clixon handle * @param[in] req Generic Www handle @@ -627,7 +988,6 @@ api_data_yang_patch(clicon_handle h, cvec *nsc = NULL; yang_bind yb; char *xpath = NULL; - const int temp_str_malloc_size = 5000; char *path_orig_1 = NULL; clicon_debug(1, "%s api_path:\"%s\"", __FUNCTION__, api_path0); @@ -697,84 +1057,58 @@ api_data_yang_patch(clicon_handle h, continue; } } - path_orig_1 = malloc(temp_str_malloc_size); + path_orig_1 = init_str(); if (path_orig_1 == NULL) { goto done; } else { - strcpy(path_orig_1, restconf_uripath(h)); + cpy_str(path_orig_1, restconf_uripath(h), TEMP_STR_MALLOC_SIZE); } // Loop through the edits for (int i = 0; i < veclen; i++) { + cxobj **tmp_vec = NULL; + size_t tmp_veclen = 0; + cxobj *xn = vec[i]; - // Get target - char *target_val = NULL; - cxobj **target_vec = NULL; - size_t target_veclen; - ret = xpath_vec(xn, nsc, "target", &target_vec, &target_veclen); + char *target_val = init_str(); + ret = get_xval(nsc, xn, target_val, tmp_vec, tmp_veclen, "target"); if (ret < 0) { goto done; } - for (int j = 0; j < target_veclen; j++) { - cxobj *target_xn = target_vec[j]; - target_val = xml_body(target_xn); - } - // Get operation - char *op_val = NULL; - cxobj **operation_vec = NULL; - size_t operation_veclen; - ret = xpath_vec(xn, nsc, "operation", &operation_vec, &operation_veclen); + char *op_val = init_str(); + ret = get_xval(nsc, xn, op_val, tmp_vec, tmp_veclen, "operation"); if (ret < 0) { goto done; } - for (int j = 0; j < operation_veclen; j++) { - cxobj *operation_xn = operation_vec[j]; - op_val = xml_body(operation_xn); - } - // Get "point" and "where" for insert operations - char *point_val = NULL; - cxobj **point_vec = NULL; - size_t point_veclen; - if (strcmp(op_val, "insert") == 0) { - ret = xpath_vec(xn, nsc, "point", &point_vec, &point_veclen); + char *point_val = init_str(); + char *where_val = init_str(); + if (strcmp(op_val, "insert") == 0) { // TODO - test + point_val = init_str(); + ret = get_xval(nsc, xn, point_val, tmp_vec, tmp_veclen, "point"); if (ret < 0) { goto done; } - for (int j = 0; j < point_veclen; j++) { - cxobj *point_xn = point_vec[j]; - point_val = xml_body(point_xn); - } - } - char *where_val = NULL; - cxobj **where_vec = NULL; - size_t where_veclen; - if (strcmp(op_val, "insert") == 0) { - ret = xpath_vec(xn, nsc, "where", &where_vec, &where_veclen); + where_val = init_str(); + ret = get_xval(nsc, xn, where_val, tmp_vec, tmp_veclen, "where"); if (ret < 0) { goto done; } - for (int j = 0; j < where_veclen; j++) { - cxobj *where_xn = where_vec[j]; - where_val = xml_body(where_xn); - } } // Construct request URI - char* simple_patch_request_uri = NULL; - simple_patch_request_uri = malloc(temp_str_malloc_size); - strcpy(simple_patch_request_uri, path_orig_1); + char* simple_patch_request_uri = init_str(); + cpy_str(simple_patch_request_uri, path_orig_1, TEMP_STR_MALLOC_SIZE); int plain_patch_val = 0; - char* api_path_target = NULL; - api_path_target = malloc(temp_str_malloc_size); - strcpy(api_path_target, api_path); + char* api_path_target = init_str(); + cpy_str(api_path_target, api_path, TEMP_STR_MALLOC_SIZE); if (strcmp(op_val, "merge") == 0) { plain_patch_val = 1; - strcat(api_path_target, target_val); - strcat(simple_patch_request_uri, target_val); + cat_str(api_path_target, target_val, TEMP_STR_MALLOC_SIZE); + cat_str(simple_patch_request_uri, target_val, TEMP_STR_MALLOC_SIZE); } if (xerr) @@ -809,266 +1143,63 @@ api_data_yang_patch(clicon_handle h, key_xn = key_vec[0]; } - // Get values (for "delete", there are no values) - cxobj **values_vec = NULL; - size_t values_veclen; - xpath_vec(xn, nsc, "value", &values_vec, &values_veclen); + // Get values (for "delete" and "remove", there are no values) + xpath_vec(xn, nsc, "value", &tmp_vec, &tmp_veclen); key_node_id = NULL; // Loop through the values - for (int j = 0; j < values_veclen; j++) { - cxobj *values_xn = values_vec[j]; + for (int j = 0; j < tmp_veclen; j++) { + cxobj *values_xn = tmp_vec[j]; cxobj** values_child_vec = xml_childvec_get(values_xn); if (key_node_id == NULL) key_node_id = xml_name(*values_child_vec); - char *patch_header = NULL; - patch_header = malloc(temp_str_malloc_size); + char *patch_header = init_str(); if (patch_header == NULL) { goto done; } - strcpy(patch_header, modname); - strcat(patch_header, ":"); - strcat(patch_header, key_node_id); + cpy_str(patch_header, modname, TEMP_STR_MALLOC_SIZE); + cat_str(patch_header, ":", TEMP_STR_MALLOC_SIZE); + cat_str(patch_header, key_node_id, TEMP_STR_MALLOC_SIZE); cxobj *x_simple_patch = xml_new(patch_header, NULL, CX_ELMNT); if (x_simple_patch == NULL) goto done; int value_vec_len = xml_child_nr(*values_child_vec); cxobj** value_vec = xml_childvec_get(*values_child_vec); cxobj * value_vec_tmp = NULL; - // For "replace", delete the item and then POST it // TODO - in an ordered list, insert it into its original position if (strcmp(op_val,"replace") == 0) { - char *delete_req_uri = malloc(temp_str_malloc_size); - if (delete_req_uri == NULL) - break; - - strcpy(delete_req_uri, simple_patch_request_uri); - strcat(delete_req_uri, target_val); - - // Delete the object with the old values - ret = api_data_delete(h, req, delete_req_uri, pi, pretty, YANG_DATA_JSON, ds ); - free(delete_req_uri); - - // Now insert the object with the new values - char *json_simple_patch = malloc(temp_str_malloc_size); - if (json_simple_patch == NULL) + ret = do_replace(h, req, pi, qvec, pretty, media_out, ds, simple_patch_request_uri, target_val, value_vec_len, value_vec, value_vec_tmp, x_simple_patch, patch_header); + if (ret != 0) { goto done; - memset(json_simple_patch, 0, temp_str_malloc_size); - - for (int k = 0; k < value_vec_len; k++) { - if (value_vec[k] != NULL) { - value_vec_tmp = xml_dup(value_vec[k]); - xml_addsub(x_simple_patch, value_vec_tmp); - } } - cbuf* cb = cbuf_new(); - xml2json_cbuf(cb, x_simple_patch, 1); - - // Some ugly text processing to get the JSON to match what api_data_post() expects - char *json_simple_patch_tmp = cbuf_get(cb); - int brace_count = 0; - for (int l = 0; l < strlen(json_simple_patch_tmp); l++) { - char c = json_simple_patch_tmp[l]; - if (c == '{') { - brace_count++; - if (brace_count == 2) { - json_simple_patch[strlen(json_simple_patch)] = '['; - } - } - json_simple_patch[strlen(json_simple_patch)] = c; - } - /* strip /... from end */ - char *post_req_uri = malloc(temp_str_malloc_size); - if (post_req_uri == NULL) - break; - memset(post_req_uri, 0, temp_str_malloc_size); - if (post_req_uri == NULL) - break; - int idx = strlen(target_val); - for (int l = strlen(target_val); l>= 0; l--) { - if (target_val[l] == '/') { - idx = l; - break; - } - } - strncpy(post_req_uri, target_val, idx); - strcat(simple_patch_request_uri, post_req_uri); - free(post_req_uri); - for (int l = strlen(json_simple_patch); l>= 0; l--) { - char c = json_simple_patch[l]; - if (c == '}') { - json_simple_patch[l] = ']'; - json_simple_patch[l + 1] = '}'; - break; - } - } - - // Send the POST request - ret = api_data_post(h, req, simple_patch_request_uri, pi, qvec, json_simple_patch, pretty, YANG_DATA_JSON, media_out, ds ); - if (value_vec_tmp != NULL) - free(value_vec_tmp); - free(x_simple_patch); - free(patch_header); // NULL check was already done before - if (ret != 0) - goto done; - break; } - // For "create", put all the data values into a single POST request if (strcmp(op_val,"create") == 0) { - for (int k = 0; k < value_vec_len; k++) { - if (value_vec[k] != NULL) { - value_vec_tmp = xml_dup(value_vec[k]); - xml_addsub(x_simple_patch, value_vec_tmp); - } - } - - // Send the POST request - cbuf* cb = cbuf_new(); - xml2json_cbuf(cb, x_simple_patch, 1); - char *json_simple_patch = cbuf_get(cb); - ret = api_data_post(h, req, simple_patch_request_uri, pi, qvec, json_simple_patch, pretty, YANG_DATA_JSON, media_out, ds ); - if (value_vec_tmp != NULL) - free(value_vec_tmp); - free(x_simple_patch); - free(patch_header); // NULL check was already done before - if (ret != 0) + ret = do_create(h, req, pi, qvec, pretty, media_out, ds, simple_patch_request_uri, value_vec_len, value_vec, value_vec_tmp, x_simple_patch, patch_header); + if (ret != 0) { goto done; - break; + } } - // For "insert", make a api_data_post request + // For "insert", make a api_data_post request // TODO - test if (strcmp(op_val, "insert") == 0) { - char *json_simple_patch = malloc(temp_str_malloc_size); - if (json_simple_patch == NULL) - goto done; - memset(json_simple_patch, 0, temp_str_malloc_size); - - // Loop through the XML, and get each value - for (int k = 0; k < value_vec_len; k++) { - if (value_vec[k] != NULL) { - value_vec_tmp = xml_dup(value_vec[k]); - xml_addsub(x_simple_patch, value_vec_tmp); - } - } - cbuf* cb = cbuf_new(); - xml2json_cbuf(cb, x_simple_patch, 1); - - // Some ugly text processing to get the JSON to match what api_data_post() expects - char *json_simple_patch_tmp = cbuf_get(cb); - int brace_count = 0; - for (int l = 0; l < strlen(json_simple_patch_tmp); l++) { - char c = json_simple_patch_tmp[l]; - if (c == '{') { - brace_count++; - if (brace_count == 2) { - json_simple_patch[strlen(json_simple_patch)] = '['; - } - } - json_simple_patch[strlen(json_simple_patch)] = c; - } - for (int l = strlen(json_simple_patch); l>= 0; l--) { - char c = json_simple_patch[l]; - if (c == '}') { - json_simple_patch[l] = ']'; - json_simple_patch[l + 1] = '}'; - break; - } - } - - // Set the insert attributes - cvec* qvec_tmp = NULL; - qvec_tmp = cvec_new(0); - if (qvec_tmp == NULL) - goto done; - cg_var *cv; - if ((cv = cvec_add(qvec_tmp, CGV_STRING)) == NULL){ + ret = do_insert(h, req, pi, pretty, media_out, ds, simple_patch_request_uri, value_vec_len, value_vec, value_vec_tmp, x_simple_patch, patch_header, where_val, api_path, point_val); + if (ret != 0) { goto done; } - cv_name_set(cv, "insert"); - cv_string_set(cv, where_val); - char *point_str = malloc(temp_str_malloc_size); - if (point_str == NULL) - goto done; - memset(point_str, 0, temp_str_malloc_size); - strcpy(point_str, api_path); - strcat(point_str, point_val); - if ((cv = cvec_add(qvec_tmp, CGV_STRING)) == NULL){ - goto done; - } - cv_name_set(cv, "point"); - cv_string_set(cv, point_str); - - // Send the POST request - ret = api_data_post(h, req, simple_patch_request_uri, pi, qvec_tmp, json_simple_patch, pretty, YANG_DATA_JSON, media_out, ds ); - if (cb != NULL) - cbuf_free(cb); - if (value_vec_tmp != NULL) - free(value_vec_tmp); - free(point_str); // NULL check was already done above - free(json_simple_patch); // NULL check was already done above - free(patch_header); // NULL check was already done before - if (x_simple_patch != NULL) - free(x_simple_patch); - break; } - // For merge", make single simple patch requests for each value if (strcmp(op_val,"merge") == 0) { - if (key_xn != NULL) - xml_addsub(x_simple_patch, key_xn); - - char *json_simple_patch = malloc(temp_str_malloc_size); - if (json_simple_patch == NULL) + ret = do_merge(h, req, pcvec, pi, qvec, pretty, media_out, ds, simple_patch_request_uri, value_vec_len, value_vec, value_vec_tmp, x_simple_patch, key_xn, plain_patch_val, patch_header); + if (ret != 0) { goto done; - - // Loop through the XML, create JSON from each one, and submit a simple patch - for (int k = 0; k < value_vec_len; k++) { - if (value_vec[k] != NULL) { - value_vec_tmp = xml_dup(value_vec[k]); - xml_addsub(x_simple_patch, value_vec_tmp); - } - cbuf* cb = cbuf_new(); - xml2json_cbuf(cb, x_simple_patch, 1); - - // Some ugly text processing to get the JSON to match what api_data_write() expects for a simple patch - char *json_simple_patch_tmp = cbuf_get(cb); - memset(json_simple_patch, 0, temp_str_malloc_size); - int brace_count = 0; - for (int l = 0; l < strlen(json_simple_patch_tmp); l++) { - char c = json_simple_patch_tmp[l]; - if (c == '{') { - brace_count++; - if (brace_count == 2) { - json_simple_patch[strlen(json_simple_patch)] = '['; - } - } - json_simple_patch[strlen(json_simple_patch)] = c; - } - for (int l = strlen(json_simple_patch); l>= 0; l--) { - char c = json_simple_patch[l]; - if (c == '}') { - json_simple_patch[l] = ']'; - json_simple_patch[l + 1] = '}'; - break; - } - } - if (value_vec_tmp != NULL) - free(value_vec_tmp); - // Send the simple patch request - ret = api_data_write(h, req, simple_patch_request_uri, pcvec, pi, qvec, json_simple_patch, pretty, YANG_DATA_JSON, media_out, plain_patch_val, ds ); - cbuf_free(cb); } - free(json_simple_patch); // NULL check was already done above - free(patch_header); // NULL check was already done before - if (x_simple_patch != NULL) - free(x_simple_patch); } } if ((strcmp(op_val, "delete") == 0) || (strcmp(op_val, "remove") == 0)) { - strcat(simple_patch_request_uri, target_val); + cat_str(simple_patch_request_uri, target_val, TEMP_STR_MALLOC_SIZE); if (strcmp(op_val, "delete") == 0) { // TODO - send error } else { @@ -1076,20 +1207,15 @@ api_data_yang_patch(clicon_handle h, } api_data_delete(h, req, simple_patch_request_uri, pi, pretty, YANG_DATA_JSON, ds); } - if (simple_patch_request_uri) - free(simple_patch_request_uri); - if (api_path_target) - free(api_path_target); + free_mem((void *)simple_patch_request_uri); + free_mem((void *)api_path_target); } ok: retval = 0; done: - if (path_orig_1 != NULL) - free(path_orig_1); - if (vec) - free(vec); - if (xpath) - free(xpath); + free_mem((void *)path_orig_1); + free_mem((void *)vec); + free_mem((void *)xpath); if (nsc) xml_nsctx_free(nsc); if (xret) @@ -1194,7 +1320,7 @@ api_data_patch(clicon_handle h, ietf_ds_t ds) { restconf_media media_in; - int ret; + int ret = -1; media_in = restconf_content_type(h); switch (media_in){ diff --git a/apps/restconf/restconf_methods.h b/apps/restconf/restconf_methods.h index 975b63eb..95943ba6 100644 --- a/apps/restconf/restconf_methods.h +++ b/apps/restconf/restconf_methods.h @@ -39,6 +39,7 @@ #ifndef _RESTCONF_METHODS_H_ #define _RESTCONF_METHODS_H_ +#define TEMP_STR_MALLOC_SIZE 5000 /* * Prototypes */ From c1c4e5b3f3a1059f3cf25f8fa56e8da3a3857327 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sun, 25 Jul 2021 22:21:37 +0200 Subject: [PATCH 06/49] - cli set debug vars - fixed: restconf native evhtp appended indata to old data --- apps/cli/cli_common.c | 6 +++--- apps/restconf/restconf_evhtp.c | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c index 2a3c74a9..4f1c6fdb 100644 --- a/apps/cli/cli_common.c +++ b/apps/cli/cli_common.c @@ -448,7 +448,7 @@ cli_debug_cli(clicon_handle h, cg_var *cv; int level; - if ((cv = cvec_find(vars, "level")) == NULL){ + if ((cv = cvec_find_var(vars, "level")) == NULL){ if (cvec_len(argv) != 1){ clicon_err(OE_PLUGIN, EINVAL, "Requires either label var or single arg: 0|1"); goto done; @@ -479,7 +479,7 @@ cli_debug_backend(clicon_handle h, cg_var *cv; int level; - if ((cv = cvec_find(vars, "level")) == NULL){ + if ((cv = cvec_find_var(vars, "level")) == NULL){ if (cvec_len(argv) != 1){ clicon_err(OE_PLUGIN, EINVAL, "Requires either label var or single arg: 0|1"); goto done; @@ -513,7 +513,7 @@ cli_debug_restconf(clicon_handle h, cg_var *cv; int level; - if ((cv = cvec_find(vars, "level")) == NULL){ + if ((cv = cvec_find_var(vars, "level")) == NULL){ if (cvec_len(argv) != 1){ clicon_err(OE_PLUGIN, EINVAL, "Requires either label var or single arg: 0|1"); goto done; diff --git a/apps/restconf/restconf_evhtp.c b/apps/restconf/restconf_evhtp.c index dfb43597..44983de8 100644 --- a/apps/restconf/restconf_evhtp.c +++ b/apps/restconf/restconf_evhtp.c @@ -529,6 +529,7 @@ restconf_path_root(evhtp_request_t *req, clicon_err(OE_CFG, errno, "evbuffer_pullup"); goto done; } + cbuf_reset(sd->sd_indata); /* Note the pullup may not be null-terminated */ cbuf_append_buf(sd->sd_indata, buf, len); } From 600f29a11771d2c211b284d90b77a1a19d385ea6 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Mon, 26 Jul 2021 12:20:06 +0200 Subject: [PATCH 07/49] - Added yang patch test: test_restconf_yang_patch.sh as placeholder for rfc 8072 tests - Added patch-xml as valid media --- apps/restconf/restconf_methods.c | 7 +- test/test_restconf_yang_patch.sh | 205 +++++++++++++++++++++++++++++++ 2 files changed, 208 insertions(+), 4 deletions(-) create mode 100755 test/test_restconf_yang_patch.sh diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index 3d9630bb..355f77e3 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -1203,16 +1203,15 @@ api_data_patch(clicon_handle h, ret = api_data_write(h, req, api_path0, pcvec, pi, qvec, data, pretty, media_in, media_out, 1, ds); break; - case YANG_PATCH_XML: - ret = restconf_notimplemented(h, req, pretty, media_out); - break; case YANG_PATCH_JSON: /* RFC 8072 patch */ + case YANG_PATCH_XML: #ifdef YANG_PATCH ret = api_data_yang_patch(h, req, api_path0, pcvec, pi, qvec, data, pretty, media_out, ds); #else - ret = restconf_unsupported_media(h, req, pretty, media_out); + ret = restconf_notimplemented(h, req, pretty, media_out); #endif + break; break; default: ret = restconf_unsupported_media(h, req, pretty, media_out); diff --git a/test/test_restconf_yang_patch.sh b/test/test_restconf_yang_patch.sh new file mode 100755 index 00000000..6d91704a --- /dev/null +++ b/test/test_restconf_yang_patch.sh @@ -0,0 +1,205 @@ +#!/usr/bin/env bash +# Restconf RFC8072 yang patch +# XXX enable YANG_PACTH in include/clixon_custom.h to run this test +# Use nacm module in example/main/example_restconf.c hardcoded to +# andy:bar and wilma:bar + +# Magic line must be first in script (see README.md) +s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi + +echo "...skipped: YANG_PATCH NYI" +if [ "$s" = $0 ]; then exit 0; else return 0; fi + +APPNAME=example + +cfg=$dir/conf.xml +startupdb=$dir/startup_db +fjukebox=$dir/example-jukebox.yang + +# Define default restconfig config: RESTCONFIG +RESTCONFIG=$(restconf_config user false) + +cat < $cfg + + $cfg + /usr/local/share/clixon + $IETFRFC + $dir + /usr/local/var/$APPNAME/$APPNAME.sock + ietf-netconf:startup + /usr/local/lib/$APPNAME/restconf + $dir/restconf.pidfile + $dir + internal + true + $RESTCONFIG + +EOF + +NACM0=" + true + deny + deny + permit + + + admin + andy + + + limited + wilma + + + + admin + admin + + permit-all + * + * + permit + + Allow the 'admin' group complete access to all operations and data. + + + + + limited + limited + + limit-jukebox + jukebox-example + read create delete + deny + + + +" + +cat< $startupdb +<${DATASTORE_TOP}> + $NACM0 + +EOF + +# An extra testmodule that includes nacm +cat < $dir/example-system.yang + module example-system { + namespace "http://example.com/ns/example-system"; + prefix "ex"; + import ietf-netconf-acm { + prefix nacm; + } + container system { + leaf enable-jukebox-streaming { + type boolean; + } + leaf extraleaf { + type string; + } + } + } +EOF + +# Common Jukebox spec (fjukebox must be set) +. ./jukebox.sh + +new "test params: -s startup -f $cfg" +if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -zf $cfg + if [ $? -ne 0 ]; then + err + fi + sudo pkill -f clixon_backend # to be sure + + new "start backend -s startup -f $cfg" + start_backend -s startup -f $cfg +fi + +new "wait backend" +wait_backend + +if [ $RC -ne 0 ]; then + new "kill old restconf daemon" + stop_restconf_pre + + new "start restconf daemon" + start_restconf -f $cfg +fi + +new "wait restconf" +wait_restconf + +# RFC 8072 A.1.1 +REQ=' + add-songs-patch + + edit1 + create + /song=Bridge%20Burning + + + Bridge Burning + /media/bridge_burning.mp3 + MP3 + 288 + + + + + edit2 + create + /song=Rope + + + Rope + /media/rope.mp3 + MP3 + 259 + + + + + edit3 + create + /song=Dear%20Rosemary + + + Dear Rosemary + /media/dear_rosemary.mp3 + MP3 + 269 + + + + ' + +new "RFC 8072 A.1.1 Add resources: Error." +expectpart "$(curl -u andy:bar $CURLOPTS -X PATCH -H 'Content-Type: application/yang-patch+xml' -H 'Accept: application/yang-data+xml' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters/album=Wasting%20Light -d "$REQ")" 0 "HTTP/$HVER 409" + + +if [ $RC -ne 0 ]; then + new "Kill restconf daemon" + stop_restconf +fi + +if [ $BE -ne 0 ]; then + new "Kill backend" + # Check if premature kill + pid=$(pgrep -u root -f clixon_backend) + if [ -z "$pid" ]; then + err "backend already dead" + fi + # kill backend + stop_backend -f $cfg +fi + +# Set by restconf_config +unset RESTCONFIG + +rm -rf $dir + +new "endtest" +endtest From dee081646cbbf08419c2778f8fa01f0a1fb5a46d Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Tue, 27 Jul 2021 09:39:44 +0200 Subject: [PATCH 08/49] * Fixed: SEGV in clixon_netconf_lib functions from internal errors including validation. * Check xerr argument both before and after call on netconf lib functions --- CHANGELOG.md | 2 ++ apps/backend/backend_client.c | 2 +- apps/backend/backend_commit.c | 33 ++++++++++++++++------ lib/clixon/clixon_netconf_lib.h | 3 +- lib/src/clixon_datastore_read.c | 2 +- lib/src/clixon_json.c | 2 +- lib/src/clixon_netconf_lib.c | 50 ++++++++++++++++++++++++++++++++- lib/src/clixon_validate.c | 46 +++++++++++++++--------------- lib/src/clixon_yang_module.c | 2 +- 9 files changed, 103 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61d19acf..a922c444 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,8 @@ Developers may need to change their code ### Corrected Bugs +* Fixed: SEGV in clixon_netconf_lib functions from internal errors including validation. + * Check xerr argument both before and after call on netconf lib functions * Fixed: RFC 8040 yang-data extension allows non-key lists * Added YANG_FLAG_NOKEY as exception to mandatory key lists * Fixed: mandatory leaf in a uses statement caused abort diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index 6244d200..be7b9377 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -238,7 +238,7 @@ client_get_streams(clicon_handle h, cprintf(cb,"", top); if (clixon_xml_parse_string(cbuf_get(cb), YB_MODULE, yspec, &x, NULL) < 0){ - if (netconf_operation_failed_xml(xret, "protocol", clicon_err_reason)< 0) + if (xret && netconf_operation_failed_xml(xret, "protocol", clicon_err_reason)< 0) goto done; goto fail; } diff --git a/apps/backend/backend_commit.c b/apps/backend/backend_commit.c index 03e318cf..8abcbcbc 100644 --- a/apps/backend/backend_commit.c +++ b/apps/backend/backend_commit.c @@ -132,7 +132,7 @@ generic_validate(clicon_handle h, cprintf(cb, "Mandatory variable of %s in module %s", xml_parent(x1)?xml_name(xml_parent(x1)):"", yang_argument_get(ys_module(ys))); - if (netconf_missing_element_xml(xret, "protocol", xml_name(x1), cbuf_get(cb)) < 0) + if (xret && netconf_missing_element_xml(xret, "protocol", xml_name(x1), cbuf_get(cb)) < 0) goto done; goto fail; } @@ -480,9 +480,9 @@ startup_commit(clicon_handle h, * and call application callback validations. * @param[in] h Clicon handle * @param[in] candidate The candidate database. The wanted backend state - * @param[out] xret Error XML tree. Free with xml_free after use + * @param[out] xret Error XML tree, if retval is 0. Free with xml_free after use * @retval -1 Error - or validation failed (but cbret not set) - * @retval 0 Validation failed (with cbret set) + * @retval 0 Validation failed (with xret set) * @retval 1 Validation OK * @note Need to differentiate between error and validation fail * (only done for generic_validate) @@ -505,16 +505,19 @@ validate_common(clicon_handle h, goto done; } /* This is the state we are going to */ - if (xmldb_get0(h, db, YB_MODULE, NULL, "/", 0, &td->td_target, NULL, NULL) < 0) + if ((ret = xmldb_get0(h, db, YB_MODULE, NULL, "/", 0, &td->td_target, NULL, xret)) < 0) goto done; - + if (ret == 0) + goto fail; /* Clear flags xpath for get */ xml_apply0(td->td_target, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)(XML_FLAG_MARK|XML_FLAG_CHANGE)); /* 2. Parse xml trees * This is the state we are going from */ - if (xmldb_get0(h, "running", YB_MODULE, NULL, "/", 0, &td->td_src, NULL, NULL) < 0) + if ((ret = xmldb_get0(h, "running", YB_MODULE, NULL, "/", 0, &td->td_src, NULL, xret)) < 0) goto done; + if (ret == 0) + goto fail; /* Clear flags xpath for get */ xml_apply0(td->td_src, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)(XML_FLAG_MARK|XML_FLAG_CHANGE)); @@ -606,11 +609,23 @@ candidate_validate(clicon_handle h, if ((td = transaction_new()) == NULL) goto done; /* Common steps (with commit) */ - if ((ret = validate_common(h, db, td, &xret)) < 1){ + if ((ret = validate_common(h, db, td, &xret)) < 0){ /* A little complex due to several sources of validation fails or errors. * (1) xerr is set -> translate to cbret; (2) cbret set use that; otherwise - * use clicon_err. */ - if (xret && clicon_xml2cbuf(cbret, xret, 0, 0, -1) < 0) + * use clicon_err. + * TODO: -1 return should be fatal error, not failed validation + */ + if (!cbuf_len(cbret) && + netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) + goto done; + goto fail; + } + if (ret == 0){ + if (xret == NULL){ + clicon_err(OE_CFG, EINVAL, "xret is NULL"); + goto done; + } + if (clicon_xml2cbuf(cbret, xret, 0, 0, -1) < 0) goto done; if (!cbuf_len(cbret) && netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) diff --git a/lib/clixon/clixon_netconf_lib.h b/lib/clixon/clixon_netconf_lib.h index f42cf33d..40b9bc96 100644 --- a/lib/clixon/clixon_netconf_lib.h +++ b/lib/clixon/clixon_netconf_lib.h @@ -127,8 +127,7 @@ int netconf_operation_failed(cbuf *cb, char *type, char *message); int netconf_operation_failed_xml(cxobj **xret, char *type, char *message); int netconf_malformed_message(cbuf *cb, char *message); int netconf_malformed_message_xml(cxobj **xret, char *message); -int netconf_data_not_unique_xml(cxobj **xret, cxobj *x, cvec *cvk); - +int netconf_data_not_unique_xml(cxobj **xret, cxobj *x, cvec *cvk); int netconf_minmax_elements_xml(cxobj **xret, cxobj *xp, char *name, int max); int netconf_trymerge(cxobj *x, yang_stmt *yspec, cxobj **xret); int netconf_module_features(clicon_handle h); diff --git a/lib/src/clixon_datastore_read.c b/lib/src/clixon_datastore_read.c index 0413d2c6..c4cf0293 100644 --- a/lib/src/clixon_datastore_read.c +++ b/lib/src/clixon_datastore_read.c @@ -557,7 +557,7 @@ xmldb_readfile(clicon_handle h, } cprintf(cberr, "Internal error: %s", clicon_err_reason); clicon_err_reset(); - if (netconf_operation_failed_xml(xerr, "application", cbuf_get(cberr))< 0) + if (xerr && netconf_operation_failed_xml(xerr, "application", cbuf_get(cberr))< 0) goto done; cbuf_free(cberr); goto fail; diff --git a/lib/src/clixon_json.c b/lib/src/clixon_json.c index b27733b4..caf79351 100644 --- a/lib/src/clixon_json.c +++ b/lib/src/clixon_json.c @@ -1238,7 +1238,7 @@ _json_parse(char *str, goto done; } cprintf(cberr, "Top-level JSON object %s is not qualified with namespace which is a MUST according to RFC 7951", xml_name(x)); - if (netconf_malformed_message_xml(xerr, cbuf_get(cberr)) < 0) + if (xerr && netconf_malformed_message_xml(xerr, cbuf_get(cberr)) < 0) goto done; goto fail; } diff --git a/lib/src/clixon_netconf_lib.c b/lib/src/clixon_netconf_lib.c index febf5b93..8dadfbd5 100644 --- a/lib/src/clixon_netconf_lib.c +++ b/lib/src/clixon_netconf_lib.c @@ -131,6 +131,10 @@ netconf_invalid_value_xml(cxobj **xret, char *encstr = NULL; cxobj *xa; + if (xret == NULL){ + clicon_err(OE_NETCONF, EINVAL, "xret is NULL"); + goto done; + } if (*xret == NULL){ if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL) goto done; @@ -248,6 +252,10 @@ netconf_missing_attribute_xml(cxobj **xret, char *encstr = NULL; cxobj *xa; + if (xret == NULL){ + clicon_err(OE_NETCONF, EINVAL, "xret is NULL"); + goto done; + } if (*xret == NULL){ if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL) goto done; @@ -356,6 +364,10 @@ netconf_bad_attribute_xml(cxobj **xret, char *encstr = NULL; cxobj *xa; + if (xret == NULL){ + clicon_err(OE_NETCONF, EINVAL, "xret is NULL"); + goto done; + } if (*xret == NULL){ if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL) goto done; @@ -449,6 +461,10 @@ netconf_common_xml(cxobj **xret, char *encstr = NULL; cxobj *xa; + if (xret == NULL){ + clicon_err(OE_NETCONF, EINVAL, "xret is NULL"); + goto done; + } if (*xret == NULL){ if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL) goto done; @@ -705,6 +721,10 @@ netconf_access_denied_xml(cxobj **xret, char *encstr = NULL; cxobj *xa; + if (xret == NULL){ + clicon_err(OE_NETCONF, EINVAL, "xret is NULL"); + goto done; + } if (*xret == NULL){ if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL) goto done; @@ -941,6 +961,10 @@ netconf_data_missing_xml(cxobj **xret, cxobj *xerr; cxobj *xa; + if (xret == NULL){ + clicon_err(OE_NETCONF, EINVAL, "xret is NULL"); + goto done; + } if (*xret == NULL){ if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL) goto done; @@ -1005,6 +1029,10 @@ netconf_operation_not_supported_xml(cxobj **xret, char *encstr = NULL; cxobj *xa; + if (xret == NULL){ + clicon_err(OE_NETCONF, EINVAL, "xret is NULL"); + goto done; + } if (*xret == NULL){ if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL) goto done; @@ -1116,6 +1144,10 @@ netconf_operation_failed_xml(cxobj **xret, char *encstr = NULL; cxobj *xa; + if (xret == NULL){ + clicon_err(OE_NETCONF, EINVAL, "xret is NULL"); + goto done; + } if (*xret == NULL){ if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL) goto done; @@ -1200,6 +1232,10 @@ netconf_malformed_message_xml(cxobj **xret, char *encstr = NULL; cxobj *xa; + if (xret == NULL){ + clicon_err(OE_NETCONF, EINVAL, "xret is NULL"); + goto done; + } if (*xret == NULL){ if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL) goto done; @@ -1250,8 +1286,12 @@ netconf_data_not_unique_xml(cxobj **xret, cxobj *xerr; cxobj *xinfo; cbuf *cb = NULL; - cxobj *xa; + cxobj *xa; + if (xret == NULL){ + clicon_err(OE_NETCONF, EINVAL, "xret is NULL"); + goto done; + } if (*xret == NULL){ if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL) goto done; @@ -1316,6 +1356,10 @@ netconf_minmax_elements_xml(cxobj **xret, cbuf *cb = NULL; cxobj *xa; + if (xret == NULL){ + clicon_err(OE_NETCONF, EINVAL, "xret is NULL"); + goto done; + } if (*xret == NULL){ if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL) goto done; @@ -1373,6 +1417,10 @@ netconf_trymerge(cxobj *x, char *reason = NULL; cxobj *xc; + if (xret == NULL){ + clicon_err(OE_NETCONF, EINVAL, "xret is NULL"); + goto done; + } if (*xret == NULL){ if ((*xret = xml_dup(x)) == NULL) goto done; diff --git a/lib/src/clixon_validate.c b/lib/src/clixon_validate.c index 274d339a..802330c8 100644 --- a/lib/src/clixon_validate.c +++ b/lib/src/clixon_validate.c @@ -118,7 +118,7 @@ validate_leafref(cxobj *xt, if ((leafrefbody = xml_body(xt)) == NULL) goto ok; if ((ypath = yang_find(ytype, Y_PATH, NULL)) == NULL){ - if (netconf_missing_element_xml(xret, "application", yang_argument_get(ytype), "Leafref requires path statement") < 0) + if (xret && netconf_missing_element_xml(xret, "application", yang_argument_get(ytype), "Leafref requires path statement") < 0) goto done; goto fail; } @@ -141,7 +141,7 @@ validate_leafref(cxobj *xt, goto done; } cprintf(cberr, "Leafref validation failed: No leaf %s matching path %s", leafrefbody, path); - if (netconf_bad_element_xml(xret, "application", leafrefbody, cbuf_get(cberr)) < 0) + if (xret && netconf_bad_element_xml(xret, "application", leafrefbody, cbuf_get(cberr)) < 0) goto done; goto fail; } @@ -211,7 +211,7 @@ validate_identityref(cxobj *xt, /* Get idref value. Then see if this value is derived from ytype. */ if ((node = xml_body(xt)) == NULL){ /* It may not be empty */ - if (netconf_bad_element_xml(xret, "application", xml_name(xt), "Identityref should not be empty") < 0) + if (xret && netconf_bad_element_xml(xret, "application", xml_name(xt), "Identityref should not be empty") < 0) goto done; goto fail; } @@ -219,13 +219,13 @@ validate_identityref(cxobj *xt, goto done; /* This is the type's base reference */ if ((ybaseref = yang_find(ytype, Y_BASE, NULL)) == NULL){ - if (netconf_missing_element_xml(xret, "application", yang_argument_get(ytype), "Identityref validation failed, no base") < 0) + if (xret && netconf_missing_element_xml(xret, "application", yang_argument_get(ytype), "Identityref validation failed, no base") < 0) goto done; goto fail; } /* This is the actual base identity */ if ((ybaseid = yang_find_identity(ybaseref, yang_argument_get(ybaseref))) == NULL){ - if (netconf_missing_element_xml(xret, "application", yang_argument_get(ybaseref), "Identityref validation failed, no base identity") < 0) + if (xret && netconf_missing_element_xml(xret, "application", yang_argument_get(ybaseref), "Identityref validation failed, no base identity") < 0) goto done; goto fail; } @@ -254,7 +254,7 @@ validate_identityref(cxobj *xt, if (cvec_find(idrefvec, idref) == NULL){ cprintf(cberr, "Identityref validation failed, %s not derived from %s", node, yang_argument_get(ybaseid)); - if (netconf_operation_failed_xml(xret, "application", cbuf_get(cberr)) < 0) + if (xret && netconf_operation_failed_xml(xret, "application", cbuf_get(cberr)) < 0) goto done; goto fail; } @@ -337,7 +337,7 @@ xml_yang_validate_rpc(clicon_handle h, goto done; /* Only accept resolved NETCONF base namespace */ if (namespace == NULL || strcmp(namespace, NETCONF_BASE_NAMESPACE) != 0){ - if (netconf_unknown_namespace_xml(xret, "protocol", rpcprefix, "No appropriate namespace associated with prefix")< 0) + if (xret && netconf_unknown_namespace_xml(xret, "protocol", rpcprefix, "No appropriate namespace associated with prefix")< 0) goto done; goto fail; } @@ -345,7 +345,7 @@ xml_yang_validate_rpc(clicon_handle h, /* xn is name of rpc, ie */ while ((xn = xml_child_each(xrpc, xn, CX_ELMNT)) != NULL) { if ((yn = xml_spec(xn)) == NULL){ - if (netconf_unknown_element_xml(xret, "application", xml_name(xn), NULL) < 0) + if (xret && netconf_unknown_element_xml(xret, "application", xml_name(xn), NULL) < 0) goto done; goto fail; } @@ -434,7 +434,7 @@ check_choice(cxobj *xt, continue; /* not choice */ break; } - if (netconf_bad_element_xml(xret, "application", xml_name(x), "Element in choice statement already exists") < 0) + if (xret && netconf_bad_element_xml(xret, "application", xml_name(x), "Element in choice statement already exists") < 0) goto done; goto fail; } /* while */ @@ -487,7 +487,7 @@ check_list_key(cxobj *xt, while ((cvi = cvec_each(cvk, cvi)) != NULL) { keyname = cv_string_get(cvi); if (xml_find_type(xt, NULL, keyname, CX_ELMNT) == NULL){ - if (netconf_missing_element_xml(xret, "application", keyname, "Mandatory key") < 0) + if (xret && netconf_missing_element_xml(xret, "application", keyname, "Mandatory key") < 0) goto done; goto fail; } @@ -557,7 +557,7 @@ check_mandatory(cxobj *xt, goto done; } cprintf(cb, "Mandatory variable of %s in module %s", xml_name(xt), yang_argument_get(ys_module(yc))); - if (netconf_missing_element_xml(xret, "application", yang_argument_get(yc), cbuf_get(cb)) < 0) + if (xret && netconf_missing_element_xml(xret, "application", yang_argument_get(yc), cbuf_get(cb)) < 0) goto done; goto fail; } @@ -574,7 +574,7 @@ check_mandatory(cxobj *xt, if (x == NULL){ /* @see RFC7950: 15.6 Error Message for Data That Violates * a Mandatory "choice" Statement */ - if (netconf_data_missing_xml(xret, yang_argument_get(yc), NULL) < 0) + if (xret && netconf_data_missing_xml(xret, yang_argument_get(yc), NULL) < 0) goto done; goto fail; } @@ -707,7 +707,7 @@ check_unique_list(cxobj *x, if (cvi==NULL){ /* Last element (i) is newly inserted, see if it is already there */ if (check_insert_duplicate(vec, i, vlen, sorted) < 0){ - if (netconf_data_not_unique_xml(xret, x, cvk) < 0) + if (xret && netconf_data_not_unique_xml(xret, x, cvk) < 0) goto done; goto fail; } @@ -751,7 +751,7 @@ check_min_max(cxobj *xp, if ((ymin = yang_find(y, Y_MIN_ELEMENTS, NULL)) != NULL){ cv = yang_cv_get(ymin); if (nr < cv_uint32_get(cv)){ - if (netconf_minmax_elements_xml(xret, xp, yang_argument_get(y), 0) < 0) + if (xret && netconf_minmax_elements_xml(xret, xp, yang_argument_get(y), 0) < 0) goto done; goto fail; } @@ -760,7 +760,7 @@ check_min_max(cxobj *xp, cv = yang_cv_get(ymax); if (cv_uint32_get(cv) > 0 && /* 0 means unbounded */ nr > cv_uint32_get(cv)){ - if (netconf_minmax_elements_xml(xret, xp, yang_argument_get(y), 1) < 0) + if (xret && netconf_minmax_elements_xml(xret, xp, yang_argument_get(y), 1) < 0) goto done; goto fail; } @@ -851,7 +851,7 @@ check_list_unique_minmax(cxobj *xt, /* Only lists and leaf-lists are allowed to be many * This checks duplicate container and leafs */ - if (netconf_minmax_elements_xml(xret, xt, xml_name(x), 1) < 0) + if (xret && netconf_minmax_elements_xml(xret, xt, xml_name(x), 1) < 0) goto done; goto fail; } @@ -1018,14 +1018,14 @@ xml_yang_validate_add(clicon_handle h, * are considered as "" */ cvtype = cv_type_get(cv); if (cv_isint(cvtype) || cvtype == CGV_BOOL || cvtype == CGV_DEC64){ - if (netconf_bad_element_xml(xret, "application", yang_argument_get(yt), "Invalid NULL value") < 0) + if (xret && netconf_bad_element_xml(xret, "application", yang_argument_get(yt), "Invalid NULL value") < 0) goto done; goto fail; } } else{ if (cv_parse1(body, cv, &reason) != 1){ - if (netconf_bad_element_xml(xret, "application", yang_argument_get(yt), reason) < 0) + if (xret && netconf_bad_element_xml(xret, "application", yang_argument_get(yt), reason) < 0) goto done; goto fail; } @@ -1033,7 +1033,7 @@ xml_yang_validate_add(clicon_handle h, if ((ret = ys_cv_validate(h, cv, yt, NULL, &reason)) < 0) goto done; if (ret == 0){ - if (netconf_bad_element_xml(xret, "application", yang_argument_get(yt), reason) < 0) + if (xret && netconf_bad_element_xml(xret, "application", yang_argument_get(yt), reason) < 0) goto done; goto fail; } @@ -1158,7 +1158,7 @@ xml_yang_validate_all(clicon_handle h, goto done; if (ns) cprintf(cb, " in namespace: %s", ns); - if (netconf_unknown_element_xml(xret, "application", xml_name(xt), cbuf_get(cb)) < 0) + if (xret && netconf_unknown_element_xml(xret, "application", xml_name(xt), cbuf_get(cb)) < 0) goto done; goto fail; } @@ -1217,7 +1217,7 @@ xml_yang_validate_all(clicon_handle h, } cprintf(cb, "Failed MUST xpath '%s' of '%s' in module %s", xpath, xml_name(xt), yang_argument_get(ys_module(ys))); - if (netconf_operation_failed_xml(xret, "application", + if (xret && netconf_operation_failed_xml(xret, "application", ye?yang_argument_get(ye):cbuf_get(cb)) < 0) goto done; goto fail; @@ -1247,7 +1247,7 @@ xml_yang_validate_all(clicon_handle h, cprintf(cb, "Failed WHEN condition of %s in module %s", xml_name(xt), yang_argument_get(ys_module(ys))); - if (netconf_operation_failed_xml(xret, "application", + if (xret && netconf_operation_failed_xml(xret, "application", cbuf_get(cb)) < 0) goto done; goto fail; @@ -1269,7 +1269,7 @@ xml_yang_validate_all(clicon_handle h, xpath, xml_name(xt), yang_argument_get(ys_module(ys))); - if (netconf_operation_failed_xml(xret, "application", + if (xret && netconf_operation_failed_xml(xret, "application", cbuf_get(cb)) < 0) goto done; goto fail; diff --git a/lib/src/clixon_yang_module.c b/lib/src/clixon_yang_module.c index 3d47f6f7..3718ceb8 100644 --- a/lib/src/clixon_yang_module.c +++ b/lib/src/clixon_yang_module.c @@ -329,7 +329,7 @@ yang_modules_state_get(clicon_handle h, * Note, list is not sorted since it is state (should not be) */ if (clixon_xml_parse_string(cbuf_get(cb), YB_MODULE, yspec, &x, NULL) < 0){ - if (netconf_operation_failed_xml(xret, "protocol", clicon_err_reason)< 0) + if (xret && netconf_operation_failed_xml(xret, "protocol", clicon_err_reason)< 0) goto done; goto fail; } From 93a4777f0f98291d4b34dd35df36048cee3cc094 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Tue, 27 Jul 2021 10:53:47 +0200 Subject: [PATCH 09/49] Removed default of `CLICON_RESTCONF_INSTALLDIR` * The default behaviour is changed to use the config $(sbindir) to locate `clixon_restconf` when starting restconf internally --- CHANGELOG.md | 7 ++++-- apps/backend/Makefile.in | 4 +++- apps/backend/backend_plugin_restconf.c | 15 +++++++++---- test/test_restconf_internal.sh | 3 --- test/test_restconf_internal_usecases.sh | 3 --- yang/clixon/clixon-config@2021-07-11.yang | 26 ++++++++++++++++------- 6 files changed, 37 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a922c444..c3d768ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,12 +39,15 @@ Expected: September, 2021 * Experimental: enable by setting YANG_PATCH in include/clixon_custom.h * Thanks to Alan Yaniger for providing this patch -### C/CLI-API changes on existing features +### API changes on existing protocol/config features -Developers may need to change their code +Users may have to change how they access the system * Native Restconf is now default, not fcgi/nginx * That is, to configure with fcgi, you need to explicitly configure: `--with-restconf=fcgi` +* New clixon-config@2021-07-11.yang revision + * Removed default of `CLICON_RESTCONF_INSTALLDIR` + * The default behaviour is changed to use the config $(sbindir) to locate `clixon_restconf` when starting restconf internally ### Corrected Bugs diff --git a/apps/backend/Makefile.in b/apps/backend/Makefile.in index aecf95c7..4c55d23f 100644 --- a/apps/backend/Makefile.in +++ b/apps/backend/Makefile.in @@ -152,7 +152,9 @@ install-include: clixon_backend.h clixon_backend_handle.h clixon_backend_transac .SUFFIXES: .c .o .c.o: - $(CC) $(INCLUDES) $(CPPFLAGS) -D__PROGRAM__=\"$(APPL)\" $(CFLAGS) -c $< + # Note: CLIXON_CONFIG_SBINDIR is where clixon_restconf is believed to be installed, unless + # overruled by CLICON_RESTCONF_INSTALLDIR option + $(CC) $(INCLUDES) $(CPPFLAGS) -D__PROGRAM__=\"$(APPL)\" -DCLIXON_CONFIG_SBINDIR=\"$(sbindir)\" $(CFLAGS) -c $< # Just link test programs test.c : diff --git a/apps/backend/backend_plugin_restconf.c b/apps/backend/backend_plugin_restconf.c index 6b82417d..fe39c8c5 100644 --- a/apps/backend/backend_plugin_restconf.c +++ b/apps/backend/backend_plugin_restconf.c @@ -245,6 +245,7 @@ restconf_pseudo_process_control(clicon_handle h) int i; int nr; cbuf *cb = NULL; + char *dir = NULL; nr = 10; if ((argv = calloc(nr, sizeof(char *))) == NULL){ @@ -256,12 +257,18 @@ restconf_pseudo_process_control(clicon_handle h) clicon_err(OE_UNIX, errno, "cbuf_new"); goto done; } - /* CLICON_RESTCONF_INSTALLDIR is where we think clixon_restconf is installed - * Problem is where to define it? Now in config file, but maybe it should be in configure? - * Tried Makefile but didnt work on Docker since it was moved around. + /* Try to figure out where clixon_restconf is installed + * If config option CLICON_RESTCONF_INSTALLDIR is installed, use that. + * If not, use the Makefile * Use PATH? */ - cprintf(cb, "%s/clixon_restconf", clicon_option_str(h, "CLICON_RESTCONF_INSTALLDIR")); + if ((dir = clicon_option_str(h, "CLICON_RESTCONF_INSTALLDIR")) == NULL){ + if ((dir = CLIXON_CONFIG_SBINDIR) == NULL){ + clicon_err(OE_RESTCONF, EINVAL, "Both option CLICON_RESTCONF_INSTALLDIR and makefile constant CLIXON_CONFIG_SBINDIR are NULL which make sit not possible to know where clixon_restconf is installed(shouldnt happen)"); + goto done; + } + } + cprintf(cb, "%s/clixon_restconf", dir); argv[i++] = cbuf_get(cb); argv[i++] = "-f"; argv[i++] = clicon_option_str(h, "CLICON_CONFIGFILE"); diff --git a/test/test_restconf_internal.sh b/test/test_restconf_internal.sh index 41e449f5..4eb894f0 100755 --- a/test/test_restconf_internal.sh +++ b/test/test_restconf_internal.sh @@ -23,8 +23,6 @@ startupdb=$dir/startup_db RESTCONFDBG=$DBG RCPROTO=http # no ssl here -RESTCONFDIR=$(dirname $(which clixon_restconf)) - # log-destination in restconf xml: syslog or file : ${LOGDST:=syslog} # Set daemon command-line to -f @@ -54,7 +52,6 @@ cat < $cfg /usr/local/lib/$APPNAME/backend example_backend.so$ /usr/local/lib/$APPNAME/restconf - $RESTCONFDIR /usr/local/lib/$APPNAME/cli $APPNAME /usr/local/var/$APPNAME/$APPNAME.sock diff --git a/test/test_restconf_internal_usecases.sh b/test/test_restconf_internal_usecases.sh index 70a94c37..f57c7cf9 100755 --- a/test/test_restconf_internal_usecases.sh +++ b/test/test_restconf_internal_usecases.sh @@ -34,8 +34,6 @@ startupdb=$dir/startup_db RESTCONFDBG=$DBG RCPROTO=http # no ssl here -RESTCONFDIR=$(dirname $(which clixon_restconf)) - INVALIDADDR=251.1.1.1 # used by fourth usecase as invalid # log-destination in restconf xml: syslog or file @@ -68,7 +66,6 @@ cat < $cfg /usr/local/lib/$APPNAME/backend example_backend.so$ /usr/local/lib/$APPNAME/restconf - $RESTCONFDIR /usr/local/lib/$APPNAME/cli $APPNAME /usr/local/var/$APPNAME/$APPNAME.sock diff --git a/yang/clixon/clixon-config@2021-07-11.yang b/yang/clixon/clixon-config@2021-07-11.yang index dfc8d54b..f4d065c8 100644 --- a/yang/clixon/clixon-config@2021-07-11.yang +++ b/yang/clixon/clixon-config@2021-07-11.yang @@ -45,8 +45,13 @@ module clixon-config { revision 2021-07-11 { description - "Added option - CLICON_SYSTEM_CAPABILITIES"; + "Added option: + CLICON_SYSTEM_CAPABILITIES + Removed default value: + CLICON_RESTCONF_INSTALLDIR + Marked as obsolete: + CLICON_YANG_LIST_CHECK + (Will be) Released in Clixon 5.3"; } revision 2021-05-20 { description @@ -518,13 +523,18 @@ module clixon-config { } leaf CLICON_RESTCONF_INSTALLDIR { type string; - default "/usr/local/sbin"; description - "Path to dir of clixon-restconf daemon binary as used by backend if started internally - Discussion: Somewhat problematic to have it as run time option. It may think it - should be known at configure or install time, but for example the main docker - installation moves the binaries, and this may be true elsewehere too. - Maybe one could locate it via PATHs search"; + "If set, path to dir of clixon-restconf daemon binary as used by backend if + started internally (run-time). + If this path is not set, clixon_restconf will be looked for according to + configured installdir: $(sbindir) (install-time) + Since programs can be moved around at install/cross-compile time the installed + dir may be difficult to know at install time, which is the reason why + CLICON_RESTCONF_INSTALLDIR exists, in order to override the Makefile + installdir. + Note on the installdir, DESTDIR is not included since according to man pages: + by specifying DESTDIR should not change the operation of the software in + any way, so its value should not be included in any file contents. "; } leaf CLICON_RESTCONF_STARTUP_DONTUPDATE { type boolean; From 0b08ba6ae56afd2aeba09225b88064886abb768c Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Tue, 27 Jul 2021 11:18:35 +0200 Subject: [PATCH 10/49] revert restconf internal tests using CLICON_RESTCONF_INSTALLDIR for docker --- test/test_restconf_internal.sh | 3 +++ test/test_restconf_internal_usecases.sh | 3 +++ 2 files changed, 6 insertions(+) diff --git a/test/test_restconf_internal.sh b/test/test_restconf_internal.sh index 4eb894f0..41e449f5 100755 --- a/test/test_restconf_internal.sh +++ b/test/test_restconf_internal.sh @@ -23,6 +23,8 @@ startupdb=$dir/startup_db RESTCONFDBG=$DBG RCPROTO=http # no ssl here +RESTCONFDIR=$(dirname $(which clixon_restconf)) + # log-destination in restconf xml: syslog or file : ${LOGDST:=syslog} # Set daemon command-line to -f @@ -52,6 +54,7 @@ cat < $cfg /usr/local/lib/$APPNAME/backend example_backend.so$ /usr/local/lib/$APPNAME/restconf + $RESTCONFDIR /usr/local/lib/$APPNAME/cli $APPNAME /usr/local/var/$APPNAME/$APPNAME.sock diff --git a/test/test_restconf_internal_usecases.sh b/test/test_restconf_internal_usecases.sh index f57c7cf9..70a94c37 100755 --- a/test/test_restconf_internal_usecases.sh +++ b/test/test_restconf_internal_usecases.sh @@ -34,6 +34,8 @@ startupdb=$dir/startup_db RESTCONFDBG=$DBG RCPROTO=http # no ssl here +RESTCONFDIR=$(dirname $(which clixon_restconf)) + INVALIDADDR=251.1.1.1 # used by fourth usecase as invalid # log-destination in restconf xml: syslog or file @@ -66,6 +68,7 @@ cat < $cfg /usr/local/lib/$APPNAME/backend example_backend.so$ /usr/local/lib/$APPNAME/restconf + $RESTCONFDIR /usr/local/lib/$APPNAME/cli $APPNAME /usr/local/var/$APPNAME/$APPNAME.sock From 948fa70b44859d4b3e845a2c12b30aa233a524db Mon Sep 17 00:00:00 2001 From: Alan Yaniger Date: Tue, 27 Jul 2021 18:36:46 +0300 Subject: [PATCH 11/49] - Add prefix "yang_patch_" to new functions - use clixon cbuf functions instead of new string functions - moved some code into separate functions - added comments - added documentation to functions that did not have it --- apps/restconf/restconf_methods.c | 559 +++++++++++++++++-------------- 1 file changed, 303 insertions(+), 256 deletions(-) diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index a13ba562..8f97ef00 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -65,6 +65,8 @@ /* cligen */ #include +// TODO - remove this include if cbuf_trunc() is added to cligen repo +#include "../cligen/cligen_buf_internal.h" /* clicon */ #include @@ -582,68 +584,34 @@ api_data_write(clicon_handle h, #ifdef YANG_PATCH -char * init_str() +/*! Free memory after a NULL pointer check + * + * @param [in] str void pointer to memory to be freed + * + */ +static void yang_patch_free_mem(void *p) { - char* s; - s = malloc(TEMP_STR_MALLOC_SIZE); - memset(s, 0, TEMP_STR_MALLOC_SIZE); - return s; + if (p != NULL) + free(p); } -int cpy_str(char *dest, char *src, size_t size) -{ - if (src == NULL) { - return 0; - } - if (dest == NULL) { - init_str(dest); - } - if (size <= 0 || size > TEMP_STR_MALLOC_SIZE) { - return 0; - } - size_t i; - for (i = 0; i < size - 1 && src[i]; i++) { - dest[i] = src[i]; - } - dest[i] = '\0'; - return i; -} - -int cat_str(char *dest, char *src, size_t size) -{ - if (src == NULL) { - return 0; - } - if (dest == NULL) { - init_str(dest); - } - if (size <= 0 || size > TEMP_STR_MALLOC_SIZE) { - return 0; - } - size_t i; - int old_len = strlen(dest); - for (i = 0; i < size - 1 && src[i]; i++) { - dest[i + old_len] = src[i]; - } - dest[i + old_len] = '\0'; - return i; -} - -void free_mem(void *str) -{ - if (str != NULL) - free(str); -} - -int get_xval( +/*! Return a value within XML tags + * @param [in] nsc namespace context + * @param [in] xn cxobj containing XML with the current edit + * @param [in] val cbuf to which the value will be written + * @param [in] key string containing the tag + * @retval 0 success + * @retval <0 failure + */ +static int yang_patch_get_xval( cvec* nsc, cxobj* xn, - char* val, - cxobj **vec, - size_t veclen, + cbuf* val, const char* key ) { + cxobj **vec = NULL; + size_t veclen = 0; char* tmp_val = NULL; int ret = xpath_vec(xn, nsc, "%s", &vec, &veclen, key); if (ret < 0) { @@ -653,11 +621,128 @@ int get_xval( cxobj *xn = vec[j]; tmp_val = xml_body(xn); } - cpy_str(val, tmp_val, TEMP_STR_MALLOC_SIZE); + cbuf_append_str(val, tmp_val); return 0; } -int do_replace ( +// TODO - add this to cligen repo if it is approved +/*! Truncate a cbuf + * + * @param [in] cb cligen buffer allocated by cbuf_new(), may be reallocated. + * @param [in] int pos position at which to truncate + * @retval new cbuf containing the truncated string (old buffer remains as it was) + * @retval NULL Error + */ +cbuf* +cbuf_trunc(cbuf *cb, + int pos) +{ + if (pos < 0 || pos > cb->cb_strlen){ + errno = EINVAL; + return NULL; + } + /* Ensure buffer is right size */ + cbuf* new_buf = cbuf_new_alloc(pos + 1); + if (new_buf == NULL) + return NULL; + strncpy(new_buf->cb_buffer, cb->cb_buffer, pos); + new_buf->cb_strlen = pos; + return new_buf; +} + +/*! Add square brackets after the surrounding curly brackets in JSON + * Needed, in order to modify the result of xml2json_cbuf() to be valid input + * to api_dta_post() and api_dta_write() + * @param [in] x_simple_patch a cxobj to pass to xml2json_cbuf() + * @retval new cbuf with the modified json + * @retval NULL Error + */ + +static cbuf* yang_patch_xml2json_modified_cbuf(cxobj* x_simple_patch) +{ + cbuf *json_simple_patch = cbuf_new(); + if (json_simple_patch == NULL) + return NULL; + cbuf* cb = cbuf_new(); + xml2json_cbuf(cb, x_simple_patch, 1); + + // Insert a '[' after the first '{' to get the JSON to match what api_data_post/write() expect + char *json_simple_patch_tmp = cbuf_get(cb); + int brace_count = 0; + for (int l = 0; l < strlen(json_simple_patch_tmp); l++) { + char c = json_simple_patch_tmp[l]; + if (c == '{') { + brace_count++; + if (brace_count == 2) { + cbuf_append(json_simple_patch,(int)'['); + } + } + cbuf_append(json_simple_patch,(int)c); + } + cbuf* json_simple_patch_2 = NULL; + + // Insert a ']' before the last '}' to get the JSON to match what api_data_post() expects + for (int l = cbuf_len(json_simple_patch); l>= 0; l--) { + char c = cbuf_get(json_simple_patch)[l]; + if (c == '}') { + // Truncate and add a string, as there is not a function to insert a char into a cbuf + json_simple_patch_2 = cbuf_trunc(json_simple_patch, l); + cbuf_append_str(json_simple_patch_2, "]}"); + break; + } + } + cbuf_free(json_simple_patch); + cbuf_free(cb); + return json_simple_patch_2; +} + +/*!yang_patch_strip_after_last_slash + * + * Strip /... from end of val + * so that e.g. "/interface=eth2" becomes "/" + * or "/interface_list=mylist/interface=eth2" becomes "/interface_list=mylist/" + * + * @param[in] val value to strip + * @retval new cbuf with the stripped string + * @retval NULL error + */ +static cbuf* yang_patch_strip_after_last_slash(cbuf* val) +{ + cbuf *cb = cbuf_new(); + cbuf* val_tmp = cbuf_new(); + cbuf_append_str(val_tmp, cbuf_get(val)); + int idx = cbuf_len(val_tmp); + for (int l = cbuf_len(val_tmp); l>= 0; l--) { + if (cbuf_get(val_tmp)[l] == '/') { + idx = l; + break; + } + } + cbuf* val_tmp_2 = cbuf_trunc(val_tmp, idx + 1); + if (val_tmp_2 == NULL) + return NULL; + if (cbuf_append_str(cb, cbuf_get(val_tmp_2)) < 0) + return NULL; + cbuf_free(val_tmp); + cbuf_free(val_tmp_2); + return cb; +} + +/*! YANG PATCH replace method + * @param[in] h Clixon handle + * @param[in] req Generic Www handle + * @param[in] pi Offset, where to start pcvec + * @param[in] qvec Vector of query string (QUERY_STRING) + * @param[in] pretty Set to 1 for pretty-printed xml/json output + * @param[in] media_out Output media + * @param[in] ds 0 if "data" resource, 1 if rfc8527 "ds" resource + * @param[in] simplepatch_request_uri URI for patch request, e.g. "/restconf/data/ietf-interfaces:interfaces" + * @param[in] target_val value in "target" field of edit in YANG patch + * @param[in] value_vec_len number of elements in the "value" array of an edit in YANG patch + * @param[in] value_vec pointer to the "value" array of an edit in YANG patch + * @param[in] x_simple_patch pointer to XML containing module name, e.g. + */ +static int yang_patch_do_replace ( clicon_handle h, void *req, int pi, @@ -665,93 +750,80 @@ int do_replace ( int pretty, restconf_media media_out, ietf_ds_t ds, - char* simple_patch_request_uri, - char* target_val, + cbuf* simple_patch_request_uri, + cbuf* target_val, int value_vec_len, cxobj** value_vec, - cxobj * value_vec_tmp, - cxobj *x_simple_patch, - char *patch_header + cxobj *x_simple_patch ) { - char *delete_req_uri = init_str(); + cxobj * value_vec_tmp = NULL; + cbuf* delete_req_uri = cbuf_new(); if (delete_req_uri == NULL) return 1; - if (cpy_str(delete_req_uri, simple_patch_request_uri, TEMP_STR_MALLOC_SIZE) <= 0) + // Make delete_req_uri something like "/restconf/data/ietf-interfaces:interfaces" + if (cbuf_append_str(delete_req_uri, cbuf_get(simple_patch_request_uri)) < 0) return 1; - if (cat_str(delete_req_uri, target_val, TEMP_STR_MALLOC_SIZE) <= 0) + // Add the target to delete_req_uri, + // so it's something like "/restconf/data/ietf-interfaces:interfaces/interface=eth2" + if (cbuf_append_str(delete_req_uri, cbuf_get(target_val)) < 0) return 1; // Delete the object with the old values - int ret = api_data_delete(h, req, delete_req_uri, pi, pretty, YANG_DATA_JSON, ds ); - free_mem((void *)delete_req_uri); + int ret = api_data_delete(h, req, cbuf_get(delete_req_uri), pi, pretty, YANG_DATA_JSON, ds ); + cbuf_free(delete_req_uri); if (ret != 0) return ret; - // Now insert the object with the new values - char *json_simple_patch = init_str(); - if (json_simple_patch == NULL) - return 1; // goto done; + // Now set up for the post request. + // Strip /... from end of target val + // so that e.g. "/interface=eth2" becomes "/" + // or "/interface_list=mylist/interface=eth2" becomes "/interface_list=mylist/" + cbuf* post_req_uri = yang_patch_strip_after_last_slash(target_val); + // Make post_req_uri something like "/restconf/data/ietf-interfaces:interfaces" + if (cbuf_append_str(simple_patch_request_uri, cbuf_get(post_req_uri))) + return 1; + cbuf_free(post_req_uri); + + // Now insert the new values into the data + // (which will include the key value and all other mandatory values) for (int k = 0; k < value_vec_len; k++) { if (value_vec[k] != NULL) { value_vec_tmp = xml_dup(value_vec[k]); xml_addsub(x_simple_patch, value_vec_tmp); } } - cbuf* cb = cbuf_new(); - xml2json_cbuf(cb, x_simple_patch, 1); - - // Some ugly text processing to get the JSON to match what api_data_post() expects - char *json_simple_patch_tmp = cbuf_get(cb); - int brace_count = 0; - for (int l = 0; l < strlen(json_simple_patch_tmp); l++) { - char c = json_simple_patch_tmp[l]; - if (c == '{') { - brace_count++; - if (brace_count == 2) { - json_simple_patch[strlen(json_simple_patch)] = '['; - } - } - json_simple_patch[strlen(json_simple_patch)] = c; - } - - /* strip /... from end */ - char *post_req_uri = init_str(); - if (post_req_uri == NULL) - return 1; - - int idx = strlen(target_val); - for (int l = strlen(target_val); l>= 0; l--) { - if (target_val[l] == '/') { - idx = l; - break; - } - } - cpy_str(post_req_uri, target_val, idx); - cat_str(simple_patch_request_uri, post_req_uri, TEMP_STR_MALLOC_SIZE); - free_mem((void *)post_req_uri); - for (int l = strlen(json_simple_patch); l>= 0; l--) { - char c = json_simple_patch[l]; - if (c == '}') { - json_simple_patch[l] = ']'; - json_simple_patch[l + 1] = '}'; - break; - } - } + // Convert the data to json + cbuf *json_simple_patch = cbuf_new(); + if (json_simple_patch == NULL) + return 1; + xml2json_cbuf(json_simple_patch, x_simple_patch, 1); // Send the POST request - ret = api_data_post(h, req, simple_patch_request_uri, pi, qvec, json_simple_patch, pretty, YANG_DATA_JSON, media_out, ds ); + ret = api_data_post(h, req, cbuf_get(simple_patch_request_uri), pi, qvec, cbuf_get(json_simple_patch), pretty, YANG_DATA_JSON, media_out, ds ); - free_mem((void *)value_vec_tmp); - free_mem((void *)x_simple_patch); - free_mem((void *)patch_header); + cbuf_free(json_simple_patch); + xml_free(value_vec_tmp); return ret; } -int do_create ( +/*! YANG PATCH create method + * @param[in] h Clixon handle + * @param[in] req Generic Www handle + * @param[in] pi Offset, where to start pcvec + * @param[in] qvec Vector of query string (QUERY_STRING) + * @param[in] pretty Set to 1 for pretty-printed xml/json output + * @param[in] media_out Output media + * @param[in] ds 0 if "data" resource, 1 if rfc8527 "ds" resource + * @param[in] simplepatch_request_uri URI for patch request, e.g. "/restconf/data/ietf-interfaces:interfaces" + * @param[in] value_vec_len number of elements in the "value" array of an edit in YANG patch + * @param[in] value_vec pointer to the "value" array of an edit in YANG patch + * @param[in] x_simple_patch pointer to XML containing module name, e.g. + */ +static int yang_patch_do_create ( clicon_handle h, void *req, int pi, @@ -759,14 +831,13 @@ int do_create ( int pretty, restconf_media media_out, ietf_ds_t ds, - char* simple_patch_request_uri, + cbuf* simple_patch_request_uri, int value_vec_len, cxobj** value_vec, - cxobj * value_vec_tmp, - cxobj *x_simple_patch, - char *patch_header + cxobj *x_simple_patch ) { + cxobj * value_vec_tmp = NULL; for (int k = 0; k < value_vec_len; k++) { if (value_vec[k] != NULL) { value_vec_tmp = xml_dup(value_vec[k]); @@ -778,34 +849,43 @@ int do_create ( cbuf* cb = cbuf_new(); xml2json_cbuf(cb, x_simple_patch, 1); char *json_simple_patch = cbuf_get(cb); - int ret = api_data_post(h, req, simple_patch_request_uri, pi, qvec, json_simple_patch, pretty, YANG_DATA_JSON, media_out, ds ); - free_mem((void *)value_vec_tmp); - free_mem((void *)x_simple_patch); - free_mem((void *)patch_header); + int ret = api_data_post(h, req, cbuf_get(simple_patch_request_uri), pi, qvec, json_simple_patch, pretty, YANG_DATA_JSON, media_out, ds ); + xml_free(value_vec_tmp); return ret; } -int do_insert ( +/*! YANG PATCH insert method + * @param[in] h Clixon handle + * @param[in] req Generic Www handle + * @param[in] pi Offset, where to start pcvec + * @param[in] pretty Set to 1 for pretty-printed xml/json output + * @param[in] media_out Output media + * @param[in] ds 0 if "data" resource, 1 if rfc8527 "ds" resource + * @param[in] simple_patch_request_uri URI for patch request, e.g. "/restconf/data/ietf-interfaces:interfaces" + * @param[in] value_vec_len number of elements in the "value" array of an edit in YANG patch + * @param[in] value_vec pointer to the "value" array of an edit in YANG patch + * @param[in] x_simple_patch pointer to XML containing module name, e.g. + * @param[in] where_val value in "where" field of edit in YANG patch + * @param[in] api_path full API path, e.g. "/restconf/data/example-jukebox:jukebox/playlist=Foo-One" + * @param[in] point_val value in "point" field of edit in YANG patch + */ +static int yang_patch_do_insert ( clicon_handle h, void *req, int pi, int pretty, restconf_media media_out, ietf_ds_t ds, - char* simple_patch_request_uri, + cbuf* simple_patch_request_uri, int value_vec_len, cxobj** value_vec, - cxobj * value_vec_tmp, cxobj *x_simple_patch, - char *patch_header, - char* where_val, + cbuf* where_val, char* api_path, - char *point_val + cbuf *point_val ) { - char *json_simple_patch = init_str(); - if (json_simple_patch == NULL) - return 1; + cxobj * value_vec_tmp = NULL; // Loop through the XML, and get each value for (int k = 0; k < value_vec_len; k++) { @@ -814,30 +894,9 @@ int do_insert ( xml_addsub(x_simple_patch, value_vec_tmp); } } - cbuf* cb = cbuf_new(); - xml2json_cbuf(cb, x_simple_patch, 1); - - // Some ugly text processing to get the JSON to match what api_data_post() expects - char *json_simple_patch_tmp = cbuf_get(cb); - int brace_count = 0; - for (int l = 0; l < strlen(json_simple_patch_tmp); l++) { - char c = json_simple_patch_tmp[l]; - if (c == '{') { - brace_count++; - if (brace_count == 2) { - json_simple_patch[strlen(json_simple_patch)] = '['; - } - } - json_simple_patch[strlen(json_simple_patch)] = c; - } - for (int l = strlen(json_simple_patch); l>= 0; l--) { - char c = json_simple_patch[l]; - if (c == '}') { - json_simple_patch[l] = ']'; - json_simple_patch[l + 1] = '}'; - break; - } - } + cbuf *json_simple_patch = yang_patch_xml2json_modified_cbuf(x_simple_patch); + if (json_simple_patch == NULL) + return 1; // Set the insert attributes cvec* qvec_tmp = NULL; @@ -849,31 +908,43 @@ int do_insert ( return 1; } cv_name_set(cv, "insert"); - cv_string_set(cv, where_val); - char *point_str = init_str(); + cv_string_set(cv, cbuf_get(where_val)); + cbuf *point_str = cbuf_new(); if (point_str == NULL) return 1; - cpy_str(point_str, api_path, TEMP_STR_MALLOC_SIZE); - cat_str(point_str, point_val, TEMP_STR_MALLOC_SIZE); + cbuf_append_str(point_str, api_path); + cbuf_append_str(point_str, cbuf_get(point_val)); if ((cv = cvec_add(qvec_tmp, CGV_STRING)) == NULL){ return 1; } cv_name_set(cv, "point"); - cv_string_set(cv, point_str); + cv_string_set(cv, cbuf_get(point_str)); // Send the POST request - int ret = api_data_post(h, req, simple_patch_request_uri, pi, qvec_tmp, json_simple_patch, pretty, YANG_DATA_JSON, media_out, ds ); - if (cb != NULL) - cbuf_free(cb); - free_mem((void *)value_vec_tmp); - free_mem((void *)point_str); - free_mem((void *)json_simple_patch); - free_mem((void *)patch_header); - free_mem((void *)x_simple_patch); + int ret = api_data_post(h, req, cbuf_get(simple_patch_request_uri), pi, qvec_tmp, cbuf_get(json_simple_patch), pretty, YANG_DATA_JSON, media_out, ds ); + xml_free(value_vec_tmp); + cbuf_free(point_str); + cbuf_free(json_simple_patch); return ret; } -int do_merge ( +/*! YANG PATCH merge method + * @param[in] h Clixon handle + * @param[in] req Generic Www handle + * @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] pretty Set to 1 for pretty-printed xml/json output + * @param[in] media_out Output media + * @param[in] ds 0 if "data" resource, 1 if rfc8527 "ds" resource + * @param[in] simple_patch_request_uri URI for patch request, e.g. "/restconf/data/ietf-interfaces:interfaces" + * @param[in] value_vec_len number of elements in the "value" array of an edit in YANG patch + * @param[in] value_vec pointer to the "value" array of an edit in YANG patch + * @param[in] x_simple_patch pointer to XML containing module name, e.g. "" + * @param[in] where_val value in "where" field of edit in YANG patch + * @param[in] key_xn XML with key tag and value, e.g. "Foo-One" + */ +static int yang_patch_do_merge ( clicon_handle h, void *req, cvec *pcvec, @@ -882,24 +953,18 @@ int do_merge ( int pretty, restconf_media media_out, ietf_ds_t ds, - char* simple_patch_request_uri, + cbuf* simple_patch_request_uri, int value_vec_len, cxobj** value_vec, - cxobj * value_vec_tmp, cxobj *x_simple_patch, - cxobj *key_xn, - int plain_patch_val, - char *patch_header + cxobj *key_xn ) { int ret = -1; + cxobj * value_vec_tmp = NULL; if (key_xn != NULL) xml_addsub(x_simple_patch, key_xn); - char *json_simple_patch = init_str(); - if (json_simple_patch == NULL) - return 1; - // Loop through the XML, create JSON from each one, and submit a simple patch for (int k = 0; k < value_vec_len; k++) { if (value_vec[k] != NULL) { @@ -909,36 +974,15 @@ int do_merge ( cbuf* cb = cbuf_new(); xml2json_cbuf(cb, x_simple_patch, 1); - // Some ugly text processing to get the JSON to match what api_data_write() expects for a simple patch - char *json_simple_patch_tmp = cbuf_get(cb); - memset(json_simple_patch, 0, TEMP_STR_MALLOC_SIZE); - int brace_count = 0; - for (int l = 0; l < strlen(json_simple_patch_tmp); l++) { - char c = json_simple_patch_tmp[l]; - if (c == '{') { - brace_count++; - if (brace_count == 2) { - json_simple_patch[strlen(json_simple_patch)] = '['; - } - } - json_simple_patch[strlen(json_simple_patch)] = c; - } - for (int l = strlen(json_simple_patch); l>= 0; l--) { - char c = json_simple_patch[l]; - if (c == '}') { - json_simple_patch[l] = ']'; - json_simple_patch[l + 1] = '}'; - break; - } - } - free_mem(value_vec_tmp); + cbuf *json_simple_patch = yang_patch_xml2json_modified_cbuf(x_simple_patch); + if (json_simple_patch == NULL) + return 1; + xml_free(value_vec_tmp); // Send the simple patch request - ret = api_data_write(h, req, simple_patch_request_uri, pcvec, pi, qvec, json_simple_patch, pretty, YANG_DATA_JSON, media_out, plain_patch_val, ds ); + ret = api_data_write(h, req, cbuf_get(simple_patch_request_uri), pcvec, pi, qvec, cbuf_get(json_simple_patch), pretty, YANG_DATA_JSON, media_out, 1, ds ); cbuf_free(cb); + cbuf_free(json_simple_patch); } - free_mem(json_simple_patch); - free_mem(patch_header); - free_mem(x_simple_patch); return ret; } @@ -988,7 +1032,7 @@ api_data_yang_patch(clicon_handle h, cvec *nsc = NULL; yang_bind yb; char *xpath = NULL; - char *path_orig_1 = NULL; + cbuf *path_orig_1 = NULL; clicon_debug(1, "%s api_path:\"%s\"", __FUNCTION__, api_path0); if ((yspec = clicon_dbspec_yang(h)) == NULL){ @@ -1057,11 +1101,11 @@ api_data_yang_patch(clicon_handle h, continue; } } - path_orig_1 = init_str(); + path_orig_1 = cbuf_new(); if (path_orig_1 == NULL) { goto done; } else { - cpy_str(path_orig_1, restconf_uripath(h), TEMP_STR_MALLOC_SIZE); + cbuf_append_str(path_orig_1, restconf_uripath(h)); } // Loop through the edits @@ -1070,45 +1114,44 @@ api_data_yang_patch(clicon_handle h, size_t tmp_veclen = 0; cxobj *xn = vec[i]; + clicon_log_xml(LOG_DEBUG, xn, "%s %d xn:", __FUNCTION__, __LINE__); // Get target - char *target_val = init_str(); - ret = get_xval(nsc, xn, target_val, tmp_vec, tmp_veclen, "target"); + cbuf *target_val = cbuf_new(); + ret = yang_patch_get_xval(nsc, xn, target_val, "target"); if (ret < 0) { goto done; } // Get operation - char *op_val = init_str(); - ret = get_xval(nsc, xn, op_val, tmp_vec, tmp_veclen, "operation"); + cbuf *op_val = cbuf_new(); + ret = yang_patch_get_xval(nsc, xn, op_val, "operation"); if (ret < 0) { goto done; } // Get "point" and "where" for insert operations - char *point_val = init_str(); - char *where_val = init_str(); - if (strcmp(op_val, "insert") == 0) { // TODO - test - point_val = init_str(); - ret = get_xval(nsc, xn, point_val, tmp_vec, tmp_veclen, "point"); + cbuf *point_val = NULL; + cbuf *where_val = cbuf_new(); + if (strcmp(cbuf_get(op_val), "insert") == 0) { + point_val = cbuf_new(); + ret = yang_patch_get_xval(nsc, xn, point_val, "point"); if (ret < 0) { goto done; } - where_val = init_str(); - ret = get_xval(nsc, xn, where_val, tmp_vec, tmp_veclen, "where"); + where_val = cbuf_new(); + ret = yang_patch_get_xval(nsc, xn, where_val, "where"); if (ret < 0) { goto done; } } // Construct request URI - char* simple_patch_request_uri = init_str(); - cpy_str(simple_patch_request_uri, path_orig_1, TEMP_STR_MALLOC_SIZE); + cbuf* simple_patch_request_uri = cbuf_new(); + cbuf_append_str(simple_patch_request_uri, cbuf_get(path_orig_1)); - int plain_patch_val = 0; - char* api_path_target = init_str(); - cpy_str(api_path_target, api_path, TEMP_STR_MALLOC_SIZE); - if (strcmp(op_val, "merge") == 0) { - plain_patch_val = 1; - cat_str(api_path_target, target_val, TEMP_STR_MALLOC_SIZE); - cat_str(simple_patch_request_uri, target_val, TEMP_STR_MALLOC_SIZE); + cbuf* api_path_target = cbuf_new(); + cbuf_append_str(api_path_target, api_path); + if (strcmp(cbuf_get(op_val), "merge") == 0) { + cbuf_append_str(api_path_target, cbuf_get(target_val)); + cbuf_append_str(simple_patch_request_uri, cbuf_get(target_val)); } if (xerr) @@ -1119,7 +1162,7 @@ api_data_yang_patch(clicon_handle h, // Get key field /* Translate api_path to xml in the form of xtop/xbot */ xbot_tmp = xtop; - if ((ret = api_path2xml(api_path_target, yspec, xtop, YC_DATANODE, 1, &xbot_tmp, &ybot, &xerr)) < 0) + if ((ret = api_path2xml(cbuf_get(api_path_target), yspec, xtop, YC_DATANODE, 1, &xbot_tmp, &ybot, &xerr)) < 0) goto done; if (ret == 0){ /* validation failed */ if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0) @@ -1142,7 +1185,6 @@ api_data_yang_patch(clicon_handle h, if (key_vec != NULL) { key_xn = key_vec[0]; } - // Get values (for "delete" and "remove", there are no values) xpath_vec(xn, nsc, "value", &tmp_vec, &tmp_veclen); key_node_id = NULL; @@ -1154,68 +1196,73 @@ api_data_yang_patch(clicon_handle h, if (key_node_id == NULL) key_node_id = xml_name(*values_child_vec); - char *patch_header = init_str(); + cbuf *patch_header = cbuf_new(); if (patch_header == NULL) { goto done; } - cpy_str(patch_header, modname, TEMP_STR_MALLOC_SIZE); - cat_str(patch_header, ":", TEMP_STR_MALLOC_SIZE); - cat_str(patch_header, key_node_id, TEMP_STR_MALLOC_SIZE); - cxobj *x_simple_patch = xml_new(patch_header, NULL, CX_ELMNT); + cbuf_append_str(patch_header, modname); + cbuf_append_str(patch_header, ":"); + cbuf_append_str(patch_header, key_node_id); + cxobj *x_simple_patch = xml_new(cbuf_get(patch_header), NULL, CX_ELMNT); if (x_simple_patch == NULL) goto done; int value_vec_len = xml_child_nr(*values_child_vec); cxobj** value_vec = xml_childvec_get(*values_child_vec); - cxobj * value_vec_tmp = NULL; // For "replace", delete the item and then POST it // TODO - in an ordered list, insert it into its original position - if (strcmp(op_val,"replace") == 0) { - ret = do_replace(h, req, pi, qvec, pretty, media_out, ds, simple_patch_request_uri, target_val, value_vec_len, value_vec, value_vec_tmp, x_simple_patch, patch_header); + if (strcmp(cbuf_get(op_val),"replace") == 0) { + ret = yang_patch_do_replace(h, req, pi, qvec, pretty, media_out, ds, simple_patch_request_uri, target_val, value_vec_len, value_vec, x_simple_patch); if (ret != 0) { goto done; } } // For "create", put all the data values into a single POST request - if (strcmp(op_val,"create") == 0) { - ret = do_create(h, req, pi, qvec, pretty, media_out, ds, simple_patch_request_uri, value_vec_len, value_vec, value_vec_tmp, x_simple_patch, patch_header); + if (strcmp(cbuf_get(op_val),"create") == 0) { + ret = yang_patch_do_create(h, req, pi, qvec, pretty, media_out, ds, simple_patch_request_uri, value_vec_len, value_vec, x_simple_patch); if (ret != 0) { goto done; } } - // For "insert", make a api_data_post request // TODO - test - if (strcmp(op_val, "insert") == 0) { - ret = do_insert(h, req, pi, pretty, media_out, ds, simple_patch_request_uri, value_vec_len, value_vec, value_vec_tmp, x_simple_patch, patch_header, where_val, api_path, point_val); + // For "insert", make a api_data_post request + if (strcmp(cbuf_get(op_val), "insert") == 0) { + ret = yang_patch_do_insert(h, req, pi, pretty, media_out, ds, simple_patch_request_uri, value_vec_len, value_vec, x_simple_patch, where_val, api_path, point_val); if (ret != 0) { goto done; } } // For merge", make single simple patch requests for each value - if (strcmp(op_val,"merge") == 0) { - ret = do_merge(h, req, pcvec, pi, qvec, pretty, media_out, ds, simple_patch_request_uri, value_vec_len, value_vec, value_vec_tmp, x_simple_patch, key_xn, plain_patch_val, patch_header); + if (strcmp(cbuf_get(op_val),"merge") == 0) { + ret = yang_patch_do_merge(h, req, pcvec, pi, qvec, pretty, media_out, ds, simple_patch_request_uri, value_vec_len, value_vec, x_simple_patch, key_xn); if (ret != 0) { goto done; } } + cbuf_free(patch_header); + yang_patch_free_mem((void *)x_simple_patch); // Using xml_free() causes crash } - if ((strcmp(op_val, "delete") == 0) || - (strcmp(op_val, "remove") == 0)) { - cat_str(simple_patch_request_uri, target_val, TEMP_STR_MALLOC_SIZE); - if (strcmp(op_val, "delete") == 0) { + if ((strcmp(cbuf_get(op_val), "delete") == 0) || + (strcmp(cbuf_get(op_val), "remove") == 0)) { + cbuf_append_str(simple_patch_request_uri, cbuf_get(target_val)); + if (strcmp(cbuf_get(op_val), "delete") == 0) { // TODO - send error } else { // TODO - do not send error } - api_data_delete(h, req, simple_patch_request_uri, pi, pretty, YANG_DATA_JSON, ds); + api_data_delete(h, req, cbuf_get(simple_patch_request_uri), pi, pretty, YANG_DATA_JSON, ds); } - free_mem((void *)simple_patch_request_uri); - free_mem((void *)api_path_target); + cbuf_free(simple_patch_request_uri); + cbuf_free(api_path_target); + cbuf_free(target_val); + cbuf_free(op_val); + cbuf_free(point_val); + cbuf_free(where_val); } ok: retval = 0; done: - free_mem((void *)path_orig_1); - free_mem((void *)vec); - free_mem((void *)xpath); + cbuf_free(path_orig_1); + yang_patch_free_mem((void *)vec); + yang_patch_free_mem((void *)xpath); if (nsc) xml_nsctx_free(nsc); if (xret) From 38f4cd82376f0d6f200d67083af1a4648967c5b9 Mon Sep 17 00:00:00 2001 From: Phil Heller Date: Thu, 29 Jul 2021 21:07:15 -0600 Subject: [PATCH 12/49] Testing dependency fixes, Netconf XML declaration and filter logic fix Install libnghttp2-devel in ubuntu and centos per required dependencies Ignore case when checking XML declaration encoding value per W3C recommendations Fix filter logic to follow RFC6241 (7.1, 7.7) and default to subtree --- apps/netconf/netconf_rpc.c | 97 ++++++++++++++++++------------------- lib/src/clixon_xml_parse.y | 3 +- test/test_netconf_filter.sh | 6 +++ test/test_netconf_hello.sh | 4 ++ test/vagrant/vagrant.sh | 4 +- 5 files changed, 62 insertions(+), 52 deletions(-) diff --git a/apps/netconf/netconf_rpc.c b/apps/netconf/netconf_rpc.c index 436d719a..8ad952cf 100644 --- a/apps/netconf/netconf_rpc.c +++ b/apps/netconf/netconf_rpc.c @@ -176,31 +176,30 @@ netconf_get_config(clicon_handle h, } /* ie ... */ - if ((xfilter = xpath_first(xn, nsc, "%s%sfilter", prefix ? prefix : "", prefix ? ":" : "")) != NULL) - ftype = xml_find_value(xfilter, "type"); - if (xfilter == NULL || ftype == NULL || strcmp(ftype, "xpath")==0){ - if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) - goto done; - } - else if (strcmp(ftype, "subtree")==0){ - /* Get whole config first, then filter. This is suboptimal - */ - if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) - goto done; - /* Now filter on whole tree */ - if (netconf_get_config_subtree(h, xfilter, xret) < 0) - goto done; - } - else{ - clixon_xml_parse_va(YB_NONE, NULL, xret, NULL, "" - "operation-failed" - "applicatio" - "error" - "filter type not supported" - "type" - "", - NETCONF_BASE_NAMESPACE); - } + if ((xfilter = xpath_first(xn, nsc, "%s%sfilter", prefix ? prefix : "", prefix ? ":" : "")) != NULL) + ftype = xml_find_value(xfilter, "type"); + if (xfilter == NULL || ftype == NULL || strcmp(ftype, "subtree") == 0) { + /* Get whole config first, then filter. This is suboptimal + */ + if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) + goto done; + /* Now filter on whole tree */ + if (netconf_get_config_subtree(h, xfilter, xret) < 0) + goto done; + } else if (strcmp(ftype, "xpath") == 0) { + if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) { + goto done; + } + } else { + clixon_xml_parse_va(YB_NONE, NULL, xret, NULL, "" + "operation-failed" + "applicatio" + "error" + "filter type not supported" + "type" + "", + NETCONF_BASE_NAMESPACE); + } retval = 0; done: if (nsc) @@ -388,31 +387,29 @@ netconf_get(clicon_handle h, } /* ie ... */ - if ((xfilter = xpath_first(xn, nsc, "%s%sfilter", prefix ? prefix : "", prefix ? ":" : "")) != NULL) - ftype = xml_find_value(xfilter, "type"); - if (xfilter == NULL || ftype == NULL || strcmp(ftype, "xpath")==0){ - if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) - goto done; - } - else if (strcmp(ftype, "subtree")==0){ - /* Get whole config + state first, then filter. This is suboptimal - */ - if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) - goto done; - /* Now filter on whole tree */ - if (netconf_get_config_subtree(h, xfilter, xret) < 0) - goto done; - } - else{ - clixon_xml_parse_va(YB_NONE, NULL, xret, NULL, "" - "operation-failed" - "applicatio" - "error" - "filter type not supported" - "type" - "", - NETCONF_BASE_NAMESPACE); - } + if ((xfilter = xpath_first(xn, nsc, "%s%sfilter", prefix ? prefix : "", prefix ? ":" : "")) != NULL) + ftype = xml_find_value(xfilter, "type"); + if (xfilter == NULL || ftype == NULL || strcmp(ftype, "subtree") == 0) { + /* Get whole config + state first, then filter. This is suboptimal + */ + if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) + goto done; + /* Now filter on whole tree */ + if (netconf_get_config_subtree(h, xfilter, xret) < 0) + goto done; + } else if (strcmp(ftype, "xpath") == 0) { + if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) + goto done; + } else { + clixon_xml_parse_va(YB_NONE, NULL, xret, NULL, "" + "operation-failed" + "applicatio" + "error" + "filter type not supported" + "type" + "", + NETCONF_BASE_NAMESPACE); + } retval = 0; done: if(nsc) diff --git a/lib/src/clixon_xml_parse.y b/lib/src/clixon_xml_parse.y index 9bfd6d00..65a3cd49 100644 --- a/lib/src/clixon_xml_parse.y +++ b/lib/src/clixon_xml_parse.y @@ -65,6 +65,7 @@ #include #include #include +#include #include #include @@ -196,7 +197,7 @@ static int xml_parse_encoding(clixon_xml_yacc *xy, char *enc) { - if(strcmp(enc, "UTF-8")){ + if(strcasecmp(enc, "UTF-8")){ clicon_err(OE_XML, XMLPARSE_ERRNO, "Unsupported XML encoding: %s expected UTF-8", enc); free(enc); return -1; diff --git a/test/test_netconf_filter.sh b/test/test_netconf_filter.sh index 73b5e4f6..ec7a9fcf 100755 --- a/test/test_netconf_filter.sh +++ b/test/test_netconf_filter.sh @@ -68,6 +68,12 @@ expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO1]]>]]>" "^operation-failedapplicatioerrorfilter type not supportedtype]]>]]>$" +new "get-config subtree one (subtree implicit)" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO1]]>]]>" "^11]]>]]>$" + +new "get subtree one (subtree implicit)" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO1]]>]]>" "^11]]>]]>$" + new "get-config subtree one" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO1]]>]]>" "^11]]>]]>$" diff --git a/test/test_netconf_hello.sh b/test/test_netconf_hello.sh index 7460efbe..a0b23ac5 100755 --- a/test/test_netconf_hello.sh +++ b/test/test_netconf_hello.sh @@ -58,6 +58,10 @@ wait_backend new "Netconf snd hello with xmldecl" expecteof "$clixon_netconf -qf $cfg" 0 "urn:ietf:params:netconf:base:1.1]]>]]>" '^$' '^$' +# Hello, lowercase encoding +new "Netconf snd hello with xmldecl (lowercase encoding)" +expecteof "$clixon_netconf -qf $cfg" 0 "urn:ietf:params:netconf:base:1.1]]>]]>" '^$' '^$' + new "Netconf snd hello without xmldecl" expecteof "$clixon_netconf -qf $cfg" 0 "urn:ietf:params:netconf:base:1.1]]>]]>" '^$' '^$' diff --git a/test/vagrant/vagrant.sh b/test/vagrant/vagrant.sh index 03fc22ad..e8bd8fe6 100755 --- a/test/vagrant/vagrant.sh +++ b/test/vagrant/vagrant.sh @@ -169,6 +169,8 @@ case $release in native) $sshcmd sudo yum install -y libevent openssl $sshcmd sudo yum install -y libevent-devel openssl-devel + $sshcmd sudo yum-config-manager --enable powertools + $sshcmd sudo yum install -y libnghttp2-devel ;; esac ;; @@ -213,7 +215,7 @@ case $release in ;; native) # $sshcmd sudo apt install -y libevent-2.1 - $sshcmd sudo apt install -y libevent-dev libssl-dev + $sshcmd sudo apt install -y libevent-dev libssl-dev libnghttp2-dev ;; esac ;; From db7e016aa07424182fd881e3e4df25529f47234f Mon Sep 17 00:00:00 2001 From: Alan Yaniger Date: Sun, 1 Aug 2021 16:50:04 +0300 Subject: [PATCH 13/49] fixed string length checks, removed unnecessary loop, changed some other code for clarity --- apps/restconf/restconf_methods.c | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index 8f97ef00..f2979e47 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -617,11 +617,14 @@ static int yang_patch_get_xval( if (ret < 0) { return ret; } - for (int j = 0; j < veclen; j++) { - cxobj *xn = vec[j]; - tmp_val = xml_body(xn); + cxobj *xn_tmp = NULL; + if (veclen > 0) { + xn_tmp = vec[0]; //veclen should always be 1 + } + if (xn_tmp != NULL) { + tmp_val = xml_body(xn_tmp); + cbuf_append_str(val, tmp_val); } - cbuf_append_str(val, tmp_val); return 0; } @@ -673,7 +676,7 @@ static cbuf* yang_patch_xml2json_modified_cbuf(cxobj* x_simple_patch) char c = json_simple_patch_tmp[l]; if (c == '{') { brace_count++; - if (brace_count == 2) { + if (brace_count == 2) { // We've reached the second brace, insert a '[' before it cbuf_append(json_simple_patch,(int)'['); } } @@ -682,7 +685,7 @@ static cbuf* yang_patch_xml2json_modified_cbuf(cxobj* x_simple_patch) cbuf* json_simple_patch_2 = NULL; // Insert a ']' before the last '}' to get the JSON to match what api_data_post() expects - for (int l = cbuf_len(json_simple_patch); l>= 0; l--) { + for (int l = cbuf_len(json_simple_patch) - 1; l >= 0; l--) { char c = cbuf_get(json_simple_patch)[l]; if (c == '}') { // Truncate and add a string, as there is not a function to insert a char into a cbuf @@ -712,15 +715,15 @@ static cbuf* yang_patch_strip_after_last_slash(cbuf* val) cbuf* val_tmp = cbuf_new(); cbuf_append_str(val_tmp, cbuf_get(val)); int idx = cbuf_len(val_tmp); - for (int l = cbuf_len(val_tmp); l>= 0; l--) { + for (int l = cbuf_len(val_tmp) - 1; l>= 0; l--) { if (cbuf_get(val_tmp)[l] == '/') { idx = l; break; } } - cbuf* val_tmp_2 = cbuf_trunc(val_tmp, idx + 1); - if (val_tmp_2 == NULL) + if (idx == cbuf_len(val_tmp)) // Didn't find a slash in the loop above return NULL; + cbuf* val_tmp_2 = cbuf_trunc(val_tmp, idx + 1); if (cbuf_append_str(cb, cbuf_get(val_tmp_2)) < 0) return NULL; cbuf_free(val_tmp); From d1f44e2f1f6fcb2248efedd52b46f37d83554917 Mon Sep 17 00:00:00 2001 From: Alan Yaniger Date: Sun, 1 Aug 2021 17:01:40 +0300 Subject: [PATCH 14/49] fixed check when getting value inside xml tag given the key --- apps/restconf/restconf_methods.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index f2979e47..9c06c693 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -618,8 +618,8 @@ static int yang_patch_get_xval( return ret; } cxobj *xn_tmp = NULL; - if (veclen > 0) { - xn_tmp = vec[0]; //veclen should always be 1 + if (veclen == 1) { //veclen should always be 1 + xn_tmp = vec[0]; } if (xn_tmp != NULL) { tmp_val = xml_body(xn_tmp); From babdc6f4969ee312166f5cb64e23e7ebf86aa3d1 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Mon, 2 Aug 2021 15:46:07 +0200 Subject: [PATCH 15/49] * Fixed: [OpenConfig BGP afi-safi and when condition issues #249](https://github.com/clicon/clixon/issues/249) * YANG when was not properly implemented for default values * Improved error message on leafref validation errors --- CHANGELOG.md | 2 + lib/clixon/clixon_xml_map.h | 1 + lib/src/clixon_validate.c | 63 ++++++++------------------- lib/src/clixon_xml_map.c | 82 ++++++++++++++++++++++++++++++++---- lib/src/clixon_yang.c | 4 +- test/test_leafref.sh | 4 +- test/test_leafref_augment.sh | 2 +- test/test_leafref_state.sh | 4 +- test/test_when_must.sh | 2 +- test/test_xpath_functions.sh | 8 ++-- 10 files changed, 107 insertions(+), 65 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3d768ad..d27b9226 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,8 @@ Users may have to change how they access the system ### Corrected Bugs +* Fixed: [OpenConfig BGP afi-safi and when condition issues #249](https://github.com/clicon/clixon/issues/249) + * YANG when was not properly implemented for default values * Fixed: SEGV in clixon_netconf_lib functions from internal errors including validation. * Check xerr argument both before and after call on netconf lib functions * Fixed: RFC 8040 yang-data extension allows non-key lists diff --git a/lib/clixon/clixon_xml_map.h b/lib/clixon/clixon_xml_map.h index 8f4a288f..feba656e 100644 --- a/lib/clixon/clixon_xml_map.h +++ b/lib/clixon/clixon_xml_map.h @@ -76,5 +76,6 @@ int assign_namespace_body(cxobj *x0, cxobj *x1); int xml_merge(cxobj *x0, cxobj *x1, yang_stmt *yspec, char **reason); int yang_enum_int_value(cxobj *node, int32_t *val); int xml_copy_marked(cxobj *x0, cxobj *x1); +int yang_when_xpath(cxobj *xn, cxobj *xp, yang_stmt *yn, int *hit, int *nrp, char **xpathp); #endif /* _CLIXON_XML_MAP_H_ */ diff --git a/lib/src/clixon_validate.c b/lib/src/clixon_validate.c index 802330c8..48070400 100644 --- a/lib/src/clixon_validate.c +++ b/lib/src/clixon_validate.c @@ -114,6 +114,7 @@ validate_leafref(cxobj *xt, cvec *nsc = NULL; cbuf *cberr = NULL; char *path; + yang_stmt *ymod; if ((leafrefbody = xml_body(xt)) == NULL) goto ok; @@ -140,7 +141,8 @@ validate_leafref(cxobj *xt, clicon_err(OE_UNIX, errno, "cbuf_new"); goto done; } - cprintf(cberr, "Leafref validation failed: No leaf %s matching path %s", leafrefbody, path); + ymod = ys_module(ys); + cprintf(cberr, "Leafref validation failed: No leaf %s matching path %s in module %s", leafrefbody, path, yang_argument_get(ymod)); if (xret && netconf_bad_element_xml(xret, "application", leafrefbody, cbuf_get(cberr)) < 0) goto done; goto fail; @@ -1136,6 +1138,7 @@ xml_yang_validate_all(clicon_handle h, char *ns = NULL; cbuf *cb = NULL; cvec *nsc = NULL; + int hit = 0; /* if not given by argument (overide) use default link and !Node has a config sub-statement and it is false */ @@ -1227,53 +1230,21 @@ xml_yang_validate_all(clicon_handle h, nsc = NULL; } } - /* First variant of when, actual "when" sub-node RFC 7950 Sec 7.21.5. Can only be one. */ - if ((yc = yang_find(ys, Y_WHEN, NULL)) != NULL){ - xpath = yang_argument_get(yc); /* "when" has xpath argument */ - /* WHEN xpath needs namespace context */ - if (xml_nsctx_yang(ys, &nsc) < 0) + if (yang_when_xpath(xt, xml_parent(xt), ys, &hit, &nr, &xpath) < 0) + goto done; + if (hit && nr == 0){ + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); goto done; - if ((nr = xpath_vec_bool(xt, nsc, "%s", xpath)) < 0) + } + cprintf(cb, "Failed WHEN condition of %s in module %s (WHEN xpath is %s)", + xml_name(xt), + yang_argument_get(ys_module(ys)), + xpath); + if (xret && netconf_operation_failed_xml(xret, "application", + cbuf_get(cb)) < 0) goto done; - if (nsc){ - xml_nsctx_free(nsc); - nsc = NULL; - } - if (nr == 0){ - if ((cb = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - cprintf(cb, "Failed WHEN condition of %s in module %s", - xml_name(xt), - yang_argument_get(ys_module(ys))); - if (xret && netconf_operation_failed_xml(xret, "application", - cbuf_get(cb)) < 0) - goto done; - goto fail; - } - } - /* Second variants of WHEN: - * Augmented and uses when using special info in node - */ - if ((xpath = yang_when_xpath_get(ys)) != NULL){ - if ((nr = xpath_vec_bool(xml_parent(xt), yang_when_nsc_get(ys), - "%s", xpath)) < 0) - goto done; - if (nr == 0){ - if ((cb = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - cprintf(cb, "Failed augmented 'when' condition '%s' of node '%s' in module '%s'", - xpath, - xml_name(xt), - yang_argument_get(ys_module(ys))); - if (xret && netconf_operation_failed_xml(xret, "application", - cbuf_get(cb)) < 0) - goto done; - goto fail; - } + goto fail; } } x = NULL; diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 0b933992..5c77c22d 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -1139,10 +1139,11 @@ xml_default1(yang_stmt *yt, int retval = -1; yang_stmt *yc; cxobj *xc; - int top=0; /* Top symbol (set default namespace) */ + int top = 0; /* Top symbol (set default namespace) */ int create = 0; char *xpath; - int nr; + int nr = 0; + int hit = 0; if (xt == NULL){ /* No xml */ clicon_err(OE_XML, EINVAL, "No XML argument"); @@ -1181,12 +1182,10 @@ xml_default1(yang_stmt *yt, case Y_CONTAINER: if (yang_find(yc, Y_PRESENCE, NULL) == NULL){ /* Check when statement from uses or augment */ - if ((xpath = yang_when_xpath_get(yc)) != NULL){ - if ((nr = xpath_vec_bool(xt, yang_when_nsc_get(yc), "%s", xpath)) < 0) - goto done; - if (nr == 0) - break; /* Do not create default if xpath fails */ - } + if (yang_when_xpath(NULL, xt, yc, &hit, &nr, &xpath) < 0) + goto done; + if (hit && nr == 0) + break; /* Do not create default if xpath fails */ /* If this is non-presence, (and it does not exist in xt) call * recursively and create nodes if any default value exist first. * Then continue and populate? @@ -2257,3 +2256,70 @@ xml_copy_marked(cxobj *x0, return retval; } +/*! Check when condition + * + * @param[in] h Clixon handle + * @param[in] xn XML node, can be NULL, in which case it is added as dummy under xp + * @param[in] xp XML parent + * @param[in] ys Yang node + * First variants of WHEN: Augmented and uses when using special info in node + * Second variant of when, actual "when" sub-node RFC 7950 Sec 7.21.5. Can only be one. + */ +int +yang_when_xpath(cxobj *xn, + cxobj *xp, + yang_stmt *yn, + int *hit, + int *nrp, + char **xpathp) +{ + int retval = 1; + yang_stmt *yc; + char *xpath = NULL; + cxobj *x = NULL; + int nr = 0; + cvec *nsc = NULL; + int xmalloc = 0; /* ugly help variable to clean temporary object */ + int nscmalloc = 0; /* ugly help variable to remove */ + + /* First variant */ + if ((xpath = yang_when_xpath_get(yn)) != NULL){ + x = xp; + nsc = yang_when_nsc_get(yn); + *hit = 1; + } + /* Second variant */ + else if ((yc = yang_find(yn, Y_WHEN, NULL)) != NULL){ + xpath = yang_argument_get(yc); /* "when" has xpath argument */ + /* Create dummy */ + if (xn == NULL){ + if ((x = xml_new(yang_argument_get(yn), xp, CX_ELMNT)) == NULL) + goto done; + xml_spec_set(x, yn); + xmalloc++; + } + else + x = xn; + if (xml_nsctx_yang(yn, &nsc) < 0) + goto done; + nscmalloc++; + *hit = 1; + } + else + *hit = 0; + if (x && xpath){ + if ((nr = xpath_vec_bool(x, nsc, "%s", xpath)) < 0) + goto done; + } + if (nrp) + *nrp = nr; + if (xpathp) + *xpathp = xpath; + retval = 0; + done: + if (xmalloc) + xml_purge(x); + if (nsc && nscmalloc) + xml_nsctx_free(nsc); + return retval; +} diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index 7c1af5a1..c90fe4ae 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -340,11 +340,13 @@ yang_flag_reset(yang_stmt *ys, * @param[in] ys Yang statement * @retval xpath xpath should evaluate to true at validation * @retval NULL Not set + * Note xpath context is PARENT which is different from when actual when child which is + * child itself */ char* yang_when_xpath_get(yang_stmt *ys) { - return ys->ys_when_xpath; + return ys->ys_when_xpath; } /*! Set yang xpath and namespace context for "when"-associated augment diff --git a/test/test_leafref.sh b/test/test_leafref.sh index a7a414c3..b425f61e 100755 --- a/test/test_leafref.sh +++ b/test/test_leafref.sh @@ -140,7 +140,7 @@ new "leafref add non-existing ref" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLOeth3
10.0.4.6
]]>]]>" "^]]>]]>$" new "leafref validate" -expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationbad-elementeth3errorLeafref validation failed: No leaf eth3 matching path /if:interfaces/if:interface/if:name]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationbad-elementeth3errorLeafref validation failed: No leaf eth3 matching path /if:interfaces/if:interface/if:name in module example]]>]]>$" #new "leafref wrong ref" #expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLOeth3
10.0.4.6
]]>]]>" "^]]>]]>$" @@ -170,7 +170,7 @@ new "leafref delete leaf" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLOeth0]]>]]>" "^" new "leafref validate (should fail)" -expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationbad-elementeth0errorLeafref validation failed: No leaf eth0 matching path /if:interfaces/if:interface/if:name]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationbad-elementeth0errorLeafref validation failed: No leaf eth0 matching path /if:interfaces/if:interface/if:name in module example]]>]]>$" new "leafref discard-changes" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" diff --git a/test/test_leafref_augment.sh b/test/test_leafref_augment.sh index 4c4c046a..c6472be9 100755 --- a/test/test_leafref_augment.sh +++ b/test/test_leafref_augment.sh @@ -209,7 +209,7 @@ new "leafref augment+leafref config wrong ref" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO$XML]]>]]>" "^]]>]]>$" new "leafref augment+leafref validate wrong ref" -expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationbad-elementxxxerrorLeafref validation failed: No leaf xxx matching path /ex:sender/ex:name]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationbad-elementxxxerrorLeafref validation failed: No leaf xxx matching path /ex:sender/ex:name in module augment]]>]]>$" new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" diff --git a/test/test_leafref_state.sh b/test/test_leafref_state.sh index 9ed123ad..feb4032e 100755 --- a/test/test_leafref_state.sh +++ b/test/test_leafref_state.sh @@ -168,10 +168,10 @@ expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failedxerrorLeafref validation failed: No leaf x matching path /ex:sender-config/ex:name. Internal error, state callback returned invalid XML]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failedxerrorLeafref validation failed: No leaf x matching path /ex:sender-config/ex:name in module leafref. Internal error, state callback returned invalid XML]]>]]>$" new "netconf get / state-only should fail" -expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failedxerrorLeafref validation failed: No leaf x matching path /ex:sender-config/ex:name. Internal error, state callback returned invalid XML]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failedxerrorLeafref validation failed: No leaf x matching path /ex:sender-config/ex:name in module leafref. Internal error, state callback returned invalid XML]]>]]>$" new "netconf get / config-only ok" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^y]]>]]>$" diff --git a/test/test_when_must.sh b/test/test_when_must.sh index 12c94da3..83bc9a6a 100755 --- a/test/test_when_must.sh +++ b/test/test_when_must.sh @@ -117,7 +117,7 @@ new "when get config" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^directr2staticr1]]>]]>$" new "when: validate fail" -expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failederrorFailed WHEN condition of static-routes in module example]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failederrorFailed WHEN condition of static-routes in module example (WHEN xpath is ../type='static')]]>]]>$" new "when: discard-changes" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" diff --git a/test/test_xpath_functions.sh b/test/test_xpath_functions.sh index f996ad1a..3b2cfbee 100755 --- a/test/test_xpath_functions.sh +++ b/test/test_xpath_functions.sh @@ -124,7 +124,7 @@ new "Set site to fie which invalidates the when contains" expecteof "$clixon_netconf -qf $cfg -D $DBG" 0 "$DEFAULTHELLOfie]]>]]>" "^]]>]]>" new "netconf validate not OK" -expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failederrorFailed WHEN condition of site in module example]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failederrorFailed WHEN condition of site in module example (WHEN xpath is contains(../../class,'foo') or contains(../../class,'bar'))]]>]]>$" new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" @@ -140,13 +140,13 @@ new "Change type to atm" expecteof "$clixon_netconf -qf $cfg -D $DBG" 0 "$DEFAULTHELLOe0atm]]>]]>" "^]]>]]>" new "netconf validate not OK (mtu not allowed)" -expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failederrorFailed augmented 'when' condition 'derived-from(type, \"ex:ethernet\")' of node 'mtu' in module 'example']]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failederrorFailed WHEN condition of mtu in module example (WHEN xpath is derived-from(type, \"ex:ethernet\"))]]>]]>$" new "Change type to ethernet (self)" expecteof "$clixon_netconf -qf $cfg -D $DBG" 0 "$DEFAULTHELLOe0ethernet]]>]]>" "^]]>]]>" new "netconf validate not OK (mtu not allowed on self)" -expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failederrorFailed augmented 'when' condition 'derived-from(type, \"ex:ethernet\")' of node 'mtu' in module 'example']]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failederrorFailed WHEN condition of mtu in module example (WHEN xpath is derived-from(type, \"ex:ethernet\"))]]>]]>$" new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" @@ -162,7 +162,7 @@ new "Change type to atm" expecteof "$clixon_netconf -qf $cfg -D $DBG" 0 "$DEFAULTHELLOe0atm]]>]]>" "^]]>]]>" new "netconf validate not OK (crc not allowed)" -expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failederrorFailed augmented 'when' condition 'derived-from-or-self(type, \"ex:ethernet\")' of node 'crc' in module 'example']]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failederrorFailed WHEN condition of crc in module example (WHEN xpath is derived-from-or-self(type, \"ex:ethernet\"))]]>]]>$" new "Change type to ethernet (self)" expecteof "$clixon_netconf -qf $cfg -D $DBG" 0 "$DEFAULTHELLOe0ethernet]]>]]>" "^]]>]]>" From 4d265d63bd43d001a6ffcea010e6b1d865a424a1 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Tue, 3 Aug 2021 11:15:45 +0200 Subject: [PATCH 16/49] Fixed: The auto-cli identityref did not expand identities in grouping/usecases properly. --- CHANGELOG.md | 1 + apps/cli/cli_generate.c | 72 ++++++++++++++++++----------------- test/test_identity.sh | 83 +++++++++++++++++++++++++++++++++++------ 3 files changed, 109 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d27b9226..ad0067a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,7 @@ Users may have to change how they access the system ### Corrected Bugs +* Fixed: The auto-cli identityref did not expand identities in grouping/usecases properly. * Fixed: [OpenConfig BGP afi-safi and when condition issues #249](https://github.com/clicon/clixon/issues/249) * YANG when was not properly implemented for default values * Fixed: SEGV in clixon_netconf_lib functions from internal errors including validation. diff --git a/apps/cli/cli_generate.c b/apps/cli/cli_generate.c index 701288cc..2f37a5aa 100644 --- a/apps/cli/cli_generate.c +++ b/apps/cli/cli_generate.c @@ -184,7 +184,7 @@ yang2cli_helptext(cbuf *cb, /*! Generate identityref statements for CLI variables * @param[in] ys Yang statement - * @param[in] ytype Yang union type being resolved + * @param[in] ytype Resolved yang type. * @param[in] helptext CLI help text * @param[out] cb Buffer where cligen code is written * @see yang2cli_var_sub Its sub-function @@ -208,42 +208,44 @@ yang2cli_var_identityref(yang_stmt *ys, yang_stmt *yprefix; yang_stmt *yspec; - if ((ybaseref = yang_find(ytype, Y_BASE, NULL)) != NULL && - (ybaseid = yang_find_identity(ys, yang_argument_get(ybaseref))) != NULL){ - idrefvec = yang_cvec_get(ybaseid); - if (cvec_len(idrefvec) > 0){ - /* Add a wildchar string first -let validate take it for default prefix */ - cprintf(cb, ">"); - yang2cli_helptext(cb, helptext); - cprintf(cb, "|<%s:%s choice:", yang_argument_get(ys), cvtypestr); - yspec = ys_spec(ys); - i = 0; - while ((cv = cvec_each(idrefvec, cv)) != NULL){ - if (nodeid_split(cv_name_get(cv), &prefix, &id) < 0) - goto done; - /* Translate from module-name(prefix) to global prefix - * This is really a kludge for true identityref prefix handling - * IDENTITYREF_KLUDGE - * This is actually quite complicated: the cli needs to generate - * a netconf statement with correct xmlns binding - */ - if ((ymod = yang_find_module_by_name(yspec, prefix)) != NULL && - (yprefix = yang_find(ymod, Y_PREFIX, NULL)) != NULL){ - if (i++) - cprintf(cb, "|"); - cprintf(cb, "%s:%s", yang_argument_get(yprefix), id); - } - if (prefix){ - free(prefix); - prefix = NULL; - } - if (id){ - free(id); - id = NULL; - } + if ((ybaseref = yang_find(ytype, Y_BASE, NULL)) == NULL) + goto ok; + if ((ybaseid = yang_find_identity(ytype, yang_argument_get(ybaseref))) == NULL) + goto ok; + idrefvec = yang_cvec_get(ybaseid); + if (cvec_len(idrefvec) > 0){ + /* Add a wildchar string first -let validate take it for default prefix */ + cprintf(cb, ">"); + yang2cli_helptext(cb, helptext); + cprintf(cb, "|<%s:%s choice:", yang_argument_get(ys), cvtypestr); + yspec = ys_spec(ys); + i = 0; + while ((cv = cvec_each(idrefvec, cv)) != NULL){ + if (nodeid_split(cv_name_get(cv), &prefix, &id) < 0) + goto done; + /* Translate from module-name(prefix) to global prefix + * This is really a kludge for true identityref prefix handling + * IDENTITYREF_KLUDGE + * This is actually quite complicated: the cli needs to generate + * a netconf statement with correct xmlns binding + */ + if ((ymod = yang_find_module_by_name(yspec, prefix)) != NULL && + (yprefix = yang_find(ymod, Y_PREFIX, NULL)) != NULL){ + if (i++) + cprintf(cb, "|"); + cprintf(cb, "%s:%s", yang_argument_get(yprefix), id); + } + if (prefix){ + free(prefix); + prefix = NULL; + } + if (id){ + free(id); + id = NULL; } } } + ok: retval = 0; done: if (prefix) @@ -371,7 +373,7 @@ static int yang2cli_var_union(clicon_handle h, yang_stmt *ys, char *origtype, * patterns, (eg regexp:"[0.9]*"). * @param[in] h Clixon handle * @param[in] ys Yang statement - * @param[in] ytype Yang union type being resolved + * @param[in] ytype Resolved yang type. * @param[in] helptext CLI help text * @param[in] cvtype * @param[in] options Flags field of optional values, see YANG_OPTIONS_* diff --git a/test/test_identity.sh b/test/test_identity.sh index d520205d..bd9d3b9f 100755 --- a/test/test_identity.sh +++ b/test/test_identity.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash # Identity and identityref tests # Example from RFC7950 Sec 7.18 and 9.10 +# Extended with a submodule # Magic line must be first in script (see README.md) s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi @@ -39,7 +40,7 @@ EOF # with two changes: the leaf statement is in the original module and # a transitive dependent identifier (foo) cat < $dir/example-crypto-base.yang - module example-crypto-base { +module example-crypto-base { yang-version 1.1; namespace "urn:example:crypto-base"; prefix "crypto"; @@ -59,8 +60,7 @@ cat < $dir/example-crypto-base.yang "Base identity used to identify public-key crypto algorithms."; } - } - +} EOF cat < $dir/example-des.yang @@ -85,10 +85,11 @@ cat < $dir/example-des.yang EOF cat < $fyang - module example { +module example-my-crypto { yang-version 1.1; namespace "urn:example:my-crypto"; prefix mc; + include "example-sub"; import "example-crypto-base" { prefix "crypto"; } @@ -141,7 +142,46 @@ cat < $fyang base mc:empty; } } + uses myname; +} +EOF + +# Only included from sub-module +# Introduce an identity only visible by example-sub submodule +cat < $dir/example-extra.yang +module example-extra { + yang-version 1.1; + namespace "urn:example:extra"; + prefix ee; + identity extra-base; + identity extra-new{ + base ee:extra-base; + } + identity extra-old{ + base ee:extra-base; + } +} +EOF + +# Sub-module +cat < $dir/example-sub.yang +submodule example-sub { + yang-version 1.1; + belongs-to example-my-crypto { + prefix mc; } + import example-extra { + prefix ee; + } + grouping myname { + leaf sub-name { + description "Uses identity accessed by only the submodule"; + type identityref { + base ee:extra-base; + } + } + } +} EOF new "test params: -f $cfg" @@ -269,42 +309,61 @@ expectpart "$($clixon_cli -1 -f $cfg -l o validate)" 255 "Validate failed. Edit new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" +# Special case sub-module +new "auto-cli cli expansion submodule identity" +expectpart "$(echo "set sub-name ?" | $clixon_cli -f $cfg 2>&1)" 0 "set sub-name" "ee:extra-new" "ee:extra-old" + +new "cli add identity" +expectpart "$($clixon_cli -1 -f $cfg -l o set sub-name ee:extra-new)" 0 "" + +new "cli validate submodule identity" +expectpart "$($clixon_cli -1 -f $cfg -l o validate)" 0 "" + +new "cli add wrong identity" +expectpart "$($clixon_cli -1 -f $cfg -l o set sub-name ee:foo)" 0 "" + +new "cli validate wrong id (expect fail)" +expectpart "$($clixon_cli -1 -f $cfg -l o validate 2>&1)" 255 "Identityref validation failed, ee:foo not derived from extra-base" + +new "netconf discard-changes" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" + # restconf and identities: # 1. set identity in own module with restconf (PUT and POST), read it with restconf and netconf # 2. set identity in other module with restconf , read it with restconf and netconf # 3. set identity in other module with netconf, read it with restconf and netconf new "restconf add own identity" -expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/example:crypto -d '{"example:crypto":"example:aes"}')" 0 "HTTP/$HVER 201" +expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/example-my-crypto:crypto -d '{"example-my-crypto:crypto":"example-my-crypto:aes"}')" 0 "HTTP/$HVER 201" new "restconf get own identity" -expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example:crypto)" 0 "HTTP/$HVER 200" '{"example:crypto":"aes"}' +expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example-my-crypto:crypto)" 0 "HTTP/$HVER 200" '{"example-my-crypto:crypto":"aes"}' new "netconf get own identity as set by restconf" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^aes" new "restconf delete identity" -expectpart "$(curl $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data/example:crypto)" 0 "HTTP/$HVER 204" +expectpart "$(curl $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data/example-my-crypto:crypto)" 0 "HTTP/$HVER 204" # 2. set identity in other module with restconf , read it with restconf and netconf if ! $YANG_UNKNOWN_ANYDATA ; then new "restconf add POST instead of PUT (should fail)" -expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/example:crypto -d '{"example:crypto":"example-des:des3"}')" 0 "HTTP/$HVER 400" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"crypto"},"error-severity":"error","error-message":"Failed to find YANG spec of XML node: crypto with parent: crypto in namespace: urn:example:my-crypto"}}}' +expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/example-my-crypto:crypto -d '{"example-my-crypto:crypto":"example-des:des3"}')" 0 "HTTP/$HVER 400" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"crypto"},"error-severity":"error","error-message":"Failed to find YANG spec of XML node: crypto with parent: crypto in namespace: urn:example:my-crypto"}}}' fi # Alternative error: #'{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"crypto"},"error-severity":"error","error-message":"Leaf contains sub-element"}}}' new "restconf add other (des) identity using POST" -expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data -d '{"example:crypto":"example-des:des3"}')" 0 "HTTP/$HVER 201" "Location: $RCPROTO://localhost/restconf/data/example:crypto" +expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data -d '{"example-my-crypto:crypto":"example-des:des3"}')" 0 "HTTP/$HVER 201" "Location: $RCPROTO://localhost/restconf/data/example-my-crypto:crypto" new "restconf get other identity" -expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example:crypto)" 0 "HTTP/$HVER 200" '{"example:crypto":"example-des:des3"}' +expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example-my-crypto:crypto)" 0 "HTTP/$HVER 200" '{"example-my-crypto:crypto":"example-des:des3"}' new "netconf get other identity" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^des:des3" new "restconf delete identity" -expectpart "$(curl $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data/example:crypto)" 0 "HTTP/$HVER 204" +expectpart "$(curl $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data/example-my-crypto:crypto)" 0 "HTTP/$HVER 204" # 3. set identity in other module with netconf, read it with restconf and netconf new "netconf set other identity" @@ -314,7 +373,7 @@ new "netconf commit" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" new "restconf get other identity (set by netconf)" -expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example:crypto)" 0 "HTTP/$HVER 200" '{"example:crypto":"example-des:des3"}' +expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example-my-crypto:crypto)" 0 "HTTP/$HVER 200" '{"example-my-crypto:crypto":"example-des:des3"}' new "netconf get other identity" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^des:des3" From 00645ee52b81bd10d86e57e8c462598da989e02c Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Tue, 3 Aug 2021 12:53:37 +0200 Subject: [PATCH 17/49] * Added linenumbers to all YANG symbols for better debug and errors * Improved error messages for YANG identityref:s and leafref:s by adding original line numbers --- CHANGELOG.md | 5 +++ lib/clixon/clixon_yang.h | 4 +++ lib/src/clixon_validate.c | 29 +++++++++++++---- lib/src/clixon_yang.c | 57 +++++++++++++++++++++++++++++++++ lib/src/clixon_yang_internal.h | 3 +- lib/src/clixon_yang_parse.y | 3 +- lib/src/clixon_yang_parse_lib.c | 3 ++ test/test_identity.sh | 8 ++--- test/test_leafref.sh | 4 +-- test/test_leafref_augment.sh | 2 +- test/test_leafref_state.sh | 4 +-- 11 files changed, 105 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad0067a2..420059b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,11 @@ Users may have to change how they access the system * Removed default of `CLICON_RESTCONF_INSTALLDIR` * The default behaviour is changed to use the config $(sbindir) to locate `clixon_restconf` when starting restconf internally +### Minor features + +* Added linenumbers to all YANG symbols for better debug and errors + * Improved error messages for YANG identityref:s and leafref:s by adding original line numbers + ### Corrected Bugs * Fixed: The auto-cli identityref did not expand identities in grouping/usecases properly. diff --git a/lib/clixon/clixon_yang.h b/lib/clixon/clixon_yang.h index 437318a7..879e6b8c 100644 --- a/lib/clixon/clixon_yang.h +++ b/lib/clixon/clixon_yang.h @@ -212,6 +212,10 @@ char *yang_when_xpath_get(yang_stmt *ys); int yang_when_xpath_set(yang_stmt *ys, char *xpath); cvec *yang_when_nsc_get(yang_stmt *ys); int yang_when_nsc_set(yang_stmt *ys, cvec *nsc); +const char *yang_filename_get(yang_stmt *ys); +int yang_filename_set(yang_stmt *ys, const char *filename); +int yang_linenum_get(yang_stmt *ys); +int yang_linenum_set(yang_stmt *ys, int linenum); /* Other functions */ yang_stmt *yspec_new(void); diff --git a/lib/src/clixon_validate.c b/lib/src/clixon_validate.c index 48070400..8923dfe6 100644 --- a/lib/src/clixon_validate.c +++ b/lib/src/clixon_validate.c @@ -119,7 +119,14 @@ validate_leafref(cxobj *xt, if ((leafrefbody = xml_body(xt)) == NULL) goto ok; if ((ypath = yang_find(ytype, Y_PATH, NULL)) == NULL){ - if (xret && netconf_missing_element_xml(xret, "application", yang_argument_get(ytype), "Leafref requires path statement") < 0) + if ((cberr = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(cberr, "Leafref requires path statement"); + if (xret && netconf_missing_element_xml(xret, "application", + yang_argument_get(ytype), + cbuf_get(cberr)) < 0) goto done; goto fail; } @@ -142,7 +149,11 @@ validate_leafref(cxobj *xt, goto done; } ymod = ys_module(ys); - cprintf(cberr, "Leafref validation failed: No leaf %s matching path %s in module %s", leafrefbody, path, yang_argument_get(ymod)); + cprintf(cberr, "Leafref validation failed: No leaf %s matching path %s in %s.yang:%d", + leafrefbody, + path, + yang_argument_get(ymod), + yang_linenum_get(ys)); if (xret && netconf_bad_element_xml(xret, "application", leafrefbody, cbuf_get(cberr)) < 0) goto done; goto fail; @@ -241,8 +252,11 @@ validate_identityref(cxobj *xt, #endif } if (ymod == NULL){ - cprintf(cberr, "Identityref validation failed, %s not derived from %s", - node, yang_argument_get(ybaseid)); + cprintf(cberr, "Identityref validation failed, %s not derived from %s in %s.yang:%d", + node, + yang_argument_get(ybaseid), + yang_argument_get(ys_module(ybaseid)), + yang_linenum_get(ybaseid)); if (xret && netconf_operation_failed_xml(xret, "application", cbuf_get(cberr)) < 0) goto done; goto fail; @@ -254,8 +268,11 @@ validate_identityref(cxobj *xt, */ idrefvec = yang_cvec_get(ybaseid); if (cvec_find(idrefvec, idref) == NULL){ - cprintf(cberr, "Identityref validation failed, %s not derived from %s", - node, yang_argument_get(ybaseid)); + cprintf(cberr, "Identityref validation failed, %s not derived from %s in %s.yang:%d", + node, + yang_argument_get(ybaseid), + yang_argument_get(ys_module(ybaseid)), + yang_linenum_get(ybaseid)); if (xret && netconf_operation_failed_xml(xret, "application", cbuf_get(cberr)) < 0) goto done; goto fail; diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index c90fe4ae..7234fd09 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -416,6 +416,61 @@ yang_when_nsc_set(yang_stmt *ys, return retval; } +/*! Get yang filename for error/debug purpose + * + * @param[in] ys Yang statement + * @retval filename + * @note there maye not always be a "filename" in case the yang is read from memory + */ +const char * +yang_filename_get(yang_stmt *ys) +{ + return ys->ys_filename; +} + +/*! Set yang filename for error/debug purpose + * + * @param[in] ys Yang statement + * @param[in] filename + * @retval 0 OK + * @retval -1 Error + * @note there maye not always be a "filename" in case the yang is read from memory + */ +int +yang_filename_set(yang_stmt *ys, + const char *filename) +{ + if ((ys->ys_filename = strdup(filename)) == NULL){ + clicon_err(OE_UNIX, errno, "strdup"); + return -1; + } + return 0; +} + +/*! Get line number of yang filename for error/debug purpose + * + * @param[in] ys Yang statement + * @retval linenum + */ +int +yang_linenum_get(yang_stmt *ys) +{ + return ys->ys_linenum; +} + +/*! Set line number of yang filename for error/debug purpose + * + * @param[in] ys Yang statement + * @param[in] linenum + */ +int +yang_linenum_set(yang_stmt *ys, + int linenum) +{ + ys->ys_linenum = linenum; + return 0; +} + /* End access functions */ /*! Create new yang specification @@ -498,6 +553,8 @@ ys_free1(yang_stmt *ys, cvec_free(ys->ys_when_nsc); if (ys->ys_stmt) free(ys->ys_stmt); + if (ys->ys_filename) + free(ys->ys_filename); if (self) free(ys); return 0; diff --git a/lib/src/clixon_yang_internal.h b/lib/src/clixon_yang_internal.h index d14e98ac..a1af0679 100644 --- a/lib/src/clixon_yang_internal.h +++ b/lib/src/clixon_yang_internal.h @@ -93,7 +93,8 @@ struct yang_stmt{ char *ys_when_xpath; /* Special conditional for a "when"-associated augment/uses xpath */ cvec *ys_when_nsc; /* Special conditional for a "when"-associated augment/uses namespace ctx */ int _ys_vector_i; /* internal use: yn_each */ - + char *ys_filename; /* For debug/errors: filename (only (sub)modules) */ + int ys_linenum; /* For debug/errors: line number (in ys_filename) */ }; diff --git a/lib/src/clixon_yang_parse.y b/lib/src/clixon_yang_parse.y index 6c44b2b9..3c144dcf 100644 --- a/lib/src/clixon_yang_parse.y +++ b/lib/src/clixon_yang_parse.y @@ -318,6 +318,7 @@ ysp_add(clixon_yang_yacc *yy, goto err; if (ys_parse_sub(ys, extra) < 0) /* Check statement-specific syntax */ goto err2; /* dont free since part of tree */ + yang_linenum_set(ys, yy->yy_linenum); /* For error/debugging */ // done: return ys; err: @@ -662,7 +663,7 @@ yin_element_stmt1 : K_YIN_ELEMENT bool_str stmtend {free($2);} /* Identity */ identity_stmt : K_IDENTITY identifier_str ';' - { if (ysp_add(_yy, Y_IDENTITY, $2, NULL) == NULL) _YYERROR("identity_stmt"); + { if (ysp_add(_yy, Y_IDENTITY, $2, NULL) == NULL) _YYERROR("identity_stmt"); _PARSE_DEBUG("identity-stmt -> IDENTITY string ;"); } | K_IDENTITY identifier_str diff --git a/lib/src/clixon_yang_parse_lib.c b/lib/src/clixon_yang_parse_lib.c index c472f292..28c52d68 100644 --- a/lib/src/clixon_yang_parse_lib.c +++ b/lib/src/clixon_yang_parse_lib.c @@ -773,6 +773,9 @@ yang_parse_str(char *str, goto done; } ymod = yy.yy_module; + /* Add filename for debugging and errors, see also ys_linenum on (each symbol?) */ + if (yang_filename_set(ymod, name) < 0) + goto done; done: ystack_pop(&yy); if (yy.yy_stack) diff --git a/test/test_identity.sh b/test/test_identity.sh index bd9d3b9f..3e900669 100755 --- a/test/test_identity.sh +++ b/test/test_identity.sh @@ -64,7 +64,7 @@ module example-crypto-base { EOF cat < $dir/example-des.yang - module example-des { +module example-des { yang-version 1.1; namespace "urn:example:des"; prefix "des"; @@ -81,7 +81,7 @@ cat < $dir/example-des.yang base "crypto:symmetric-key"; description "Triple DES crypto algorithm."; } - } +} EOF cat < $fyang @@ -252,7 +252,7 @@ new "Set crypto to foo:bar" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLOfoo:bar]]>]]>" "^]]>]]>$" new "netconf validate (expect fail)" -expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failederrorIdentityref validation failed, foo:bar not derived from crypto-alg]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failederrorIdentityref validation failed, foo:bar not derived from crypto-alg in example-crypto-base.yang:[0-9]*]]>]]>$" new "cli set crypto to mc:aes" expectpart "$($clixon_cli -1 -f $cfg -l o set crypto mc:aes)" 0 "^$" @@ -282,7 +282,7 @@ new "Netconf set undefined acl-type" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLOxundefined]]>]]>" "^]]>]]>$" new "netconf validate fail" -expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failederrorIdentityref validation failed, undefined not derived from acl-base]]>]]>" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failederrorIdentityref validation failed, undefined not derived from acl-base in example-my-crypto.yang:[0-9]*]]>]]>" new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" diff --git a/test/test_leafref.sh b/test/test_leafref.sh index b425f61e..a86e411d 100755 --- a/test/test_leafref.sh +++ b/test/test_leafref.sh @@ -140,7 +140,7 @@ new "leafref add non-existing ref" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLOeth3
10.0.4.6
]]>]]>" "^]]>]]>$" new "leafref validate" -expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationbad-elementeth3errorLeafref validation failed: No leaf eth3 matching path /if:interfaces/if:interface/if:name in module example]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationbad-elementeth3errorLeafref validation failed: No leaf eth3 matching path /if:interfaces/if:interface/if:name in example.yang:[0-9]*]]>]]>$" #new "leafref wrong ref" #expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLOeth3
10.0.4.6
]]>]]>" "^]]>]]>$" @@ -170,7 +170,7 @@ new "leafref delete leaf" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLOeth0]]>]]>" "^" new "leafref validate (should fail)" -expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationbad-elementeth0errorLeafref validation failed: No leaf eth0 matching path /if:interfaces/if:interface/if:name in module example]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationbad-elementeth0errorLeafref validation failed: No leaf eth0 matching path /if:interfaces/if:interface/if:name in example.yang:[0-9]*]]>]]>$" new "leafref discard-changes" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" diff --git a/test/test_leafref_augment.sh b/test/test_leafref_augment.sh index c6472be9..4c5032a2 100755 --- a/test/test_leafref_augment.sh +++ b/test/test_leafref_augment.sh @@ -209,7 +209,7 @@ new "leafref augment+leafref config wrong ref" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO$XML]]>]]>" "^]]>]]>$" new "leafref augment+leafref validate wrong ref" -expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationbad-elementxxxerrorLeafref validation failed: No leaf xxx matching path /ex:sender/ex:name in module augment]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationbad-elementxxxerrorLeafref validation failed: No leaf xxx matching path /ex:sender/ex:name in augment.yang:[0-9]*]]>]]>$" new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" diff --git a/test/test_leafref_state.sh b/test/test_leafref_state.sh index feb4032e..31ce8cd5 100755 --- a/test/test_leafref_state.sh +++ b/test/test_leafref_state.sh @@ -168,10 +168,10 @@ expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failedxerrorLeafref validation failed: No leaf x matching path /ex:sender-config/ex:name in module leafref. Internal error, state callback returned invalid XML]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failedxerrorLeafref validation failed: No leaf x matching path /ex:sender-config/ex:name in leafref.yang:[0-9]*. Internal error, state callback returned invalid XML]]>]]>$" new "netconf get / state-only should fail" -expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failedxerrorLeafref validation failed: No leaf x matching path /ex:sender-config/ex:name in module leafref. Internal error, state callback returned invalid XML]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failedxerrorLeafref validation failed: No leaf x matching path /ex:sender-config/ex:name in leafref.yang:[0-9]*. Internal error, state callback returned invalid XML]]>]]>$" new "netconf get / config-only ok" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^y]]>]]>$" From 603f9724ce6e603a0c1c078c14320467f8267e97 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Tue, 3 Aug 2021 13:38:31 +0200 Subject: [PATCH 18/49] * Fixed: YANG when was not properly implemented for LEAF default values --- lib/clixon/clixon_xml_map.h | 2 +- lib/src/clixon_validate.c | 2 +- lib/src/clixon_xml_map.c | 28 +++++++++++++--------------- test/test_leaf_default.sh | 20 ++++++++++++++++++++ 4 files changed, 35 insertions(+), 17 deletions(-) diff --git a/lib/clixon/clixon_xml_map.h b/lib/clixon/clixon_xml_map.h index feba656e..158d2cb8 100644 --- a/lib/clixon/clixon_xml_map.h +++ b/lib/clixon/clixon_xml_map.h @@ -76,6 +76,6 @@ int assign_namespace_body(cxobj *x0, cxobj *x1); int xml_merge(cxobj *x0, cxobj *x1, yang_stmt *yspec, char **reason); int yang_enum_int_value(cxobj *node, int32_t *val); int xml_copy_marked(cxobj *x0, cxobj *x1); -int yang_when_xpath(cxobj *xn, cxobj *xp, yang_stmt *yn, int *hit, int *nrp, char **xpathp); +int yang_check_when_xpath(cxobj *xn, cxobj *xp, yang_stmt *yn, int *hit, int *nrp, char **xpathp); #endif /* _CLIXON_XML_MAP_H_ */ diff --git a/lib/src/clixon_validate.c b/lib/src/clixon_validate.c index 8923dfe6..710d209b 100644 --- a/lib/src/clixon_validate.c +++ b/lib/src/clixon_validate.c @@ -1247,7 +1247,7 @@ xml_yang_validate_all(clicon_handle h, nsc = NULL; } } - if (yang_when_xpath(xt, xml_parent(xt), ys, &hit, &nr, &xpath) < 0) + if (yang_check_when_xpath(xt, xml_parent(xt), ys, &hit, &nr, &xpath) < 0) goto done; if (hit && nr == 0){ if ((cb = cbuf_new()) == NULL){ diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 5c77c22d..e288e129 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -1164,13 +1164,11 @@ xml_default1(yang_stmt *yt, switch (yang_keyword_get(yc)){ case Y_LEAF: if (!cv_flag(yang_cv_get(yc), V_UNSET)){ /* Default value exists */ - /* Check when statement from uses or augment */ - if ((xpath = yang_when_xpath_get(yc)) != NULL){ - if ((nr = xpath_vec_bool(xt, yang_when_nsc_get(yc), "%s", xpath)) < 0) - goto done; - if (nr == 0) - break; /* Do not create default if xpath fails */ - } + /* Check when condition */ + if (yang_check_when_xpath(NULL, xt, yc, &hit, &nr, &xpath) < 0) + goto done; + if (hit && nr == 0) + break; /* Do not create default if xpath fails */ if (xml_find_type(xt, NULL, yang_argument_get(yc), CX_ELMNT) == NULL){ /* No such child exist, create this leaf */ if (xml_default_create(yc, xt, top) < 0) @@ -1181,8 +1179,8 @@ xml_default1(yang_stmt *yt, break; case Y_CONTAINER: if (yang_find(yc, Y_PRESENCE, NULL) == NULL){ - /* Check when statement from uses or augment */ - if (yang_when_xpath(NULL, xt, yc, &hit, &nr, &xpath) < 0) + /* Check when condition */ + if (yang_check_when_xpath(NULL, xt, yc, &hit, &nr, &xpath) < 0) goto done; if (hit && nr == 0) break; /* Do not create default if xpath fails */ @@ -2266,12 +2264,12 @@ xml_copy_marked(cxobj *x0, * Second variant of when, actual "when" sub-node RFC 7950 Sec 7.21.5. Can only be one. */ int -yang_when_xpath(cxobj *xn, - cxobj *xp, - yang_stmt *yn, - int *hit, - int *nrp, - char **xpathp) +yang_check_when_xpath(cxobj *xn, + cxobj *xp, + yang_stmt *yn, + int *hit, + int *nrp, + char **xpathp) { int retval = 1; yang_stmt *yc; diff --git a/test/test_leaf_default.sh b/test/test_leaf_default.sh index 4dbd1032..d36553c0 100755 --- a/test/test_leaf_default.sh +++ b/test/test_leaf_default.sh @@ -67,6 +67,19 @@ module example{ default 31; /* should be set on startup */ } } + /* Extra rules to check when condition */ + leaf npleaf{ + when "../s3 = '99'"; + type uint32; + default 98; + } + container npcont{ + when "../s3 = '99'"; + leaf npext{ + type uint32; + default 99; + } + } } container p4{ presence "A presence container"; @@ -144,6 +157,13 @@ expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^$XMLafalsefalse]]>]]>$" +# Set s3 leaf to 99 triggering when condition for default values +new "Set s3 to 99" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO99]]>]]>" "^]]>]]>$" + +new "get config np3 with npleaf and npext" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^99319899]]>]]>$" + if [ $BE -ne 0 ]; then new "Kill backend" # Check if premature kill From dcaeb581a1548f7013bc5ccd88e60b4c91abc944 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Thu, 5 Aug 2021 09:59:20 +0200 Subject: [PATCH 19/49] * Fixed: Yang patterns: \n and other non-printable characters were broken * Example: Clixon interpereted them two characters: `\\ n` instead of ascii 10 --- CHANGELOG.md | 2 + lib/clixon/clixon_string.h | 2 + lib/src/clixon_string.c | 77 +++++++++++++++++++++++++++++++++ lib/src/clixon_yang_parse_lib.c | 12 +++++ test/test_pattern.sh | 14 ++++++ 5 files changed, 107 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 420059b2..7f09de7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,6 +56,8 @@ Users may have to change how they access the system ### Corrected Bugs +* Fixed: Yang patterns: \n and other non-printable characters were broken + * Example: Clixon interpereted them two characters: `\\ n` instead of ascii 10 * Fixed: The auto-cli identityref did not expand identities in grouping/usecases properly. * Fixed: [OpenConfig BGP afi-safi and when condition issues #249](https://github.com/clicon/clixon/issues/249) * YANG when was not properly implemented for default values diff --git a/lib/clixon/clixon_string.h b/lib/clixon/clixon_string.h index fea7fd8a..689623e3 100644 --- a/lib/clixon/clixon_string.h +++ b/lib/clixon/clixon_string.h @@ -100,6 +100,8 @@ int xml_chardata_encode(char **escp, const char *fmt, ...); #endif int xml_chardata_cbuf_append(cbuf *cb, char *str); int uri_percent_decode(char *enc, char **str); +int nonprint_encode(char *orig, char **encp); + const char *clicon_int2str(const map_str2int *mstab, int i); int clicon_str2int(const map_str2int *mstab, char *str); int clicon_str2int_search(const map_str2int *mstab, char *str, int upper); diff --git a/lib/src/clixon_string.c b/lib/src/clixon_string.c index d6004a32..0e73b28a 100644 --- a/lib/src/clixon_string.c +++ b/lib/src/clixon_string.c @@ -646,6 +646,83 @@ uri_str2cvec(char *string, goto done; } +/*! Translate \n and others from \\n (two chars) to \n (one char) + * + * This is needed in yang regex it seems. + * It was triggered by eg draft-wwlh-netconf-list-pagination-00 module example-social tagline + * leaf tagline { + * type string { + * length "1..80"; + * pattern '.*[\n].*' { + * modifier invert-match; + * @param[in] orig Original string eg with \\n + * @param[out] enc Encoded string with \n, malloced. + * + * @see https://www.regular-expressions.info/nonprint.html + */ +int +nonprint_encode(char *orig, + char **encp) +{ + int retval = -1; + char *enc = NULL; + int i; + int j; + int esc = 0; + char c; + char c1; + + if (orig == NULL){ + clicon_err(OE_UNIX, EINVAL, "orig is NULL"); + goto done; + } + /* Encoded string is equal or shorter */ + if ((enc = malloc(strlen(orig)+1)) == NULL){ + clicon_err(OE_UNIX, errno, "strdup"); + goto done; + } + j = 0; + for (i=0; i Date: Thu, 5 Aug 2021 12:07:07 +0200 Subject: [PATCH 20/49] cli exclude clixon-restconf, extra cv check in default1 --- lib/src/clixon_xml_map.c | 8 +++++++- yang/clixon/clixon-config@2021-07-11.yang | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index e288e129..0422e650 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -1144,6 +1144,7 @@ xml_default1(yang_stmt *yt, char *xpath; int nr = 0; int hit = 0; + cg_var *cv; if (xt == NULL){ /* No xml */ clicon_err(OE_XML, EINVAL, "No XML argument"); @@ -1163,7 +1164,12 @@ xml_default1(yang_stmt *yt, continue; switch (yang_keyword_get(yc)){ case Y_LEAF: - if (!cv_flag(yang_cv_get(yc), V_UNSET)){ /* Default value exists */ + if ((cv = yang_cv_get(yc)) == NULL){ + clicon_err(OE_YANG,0, "Internal error: yang leaf %s not populated with cv as it should", + yang_argument_get(yc)); + goto done; + } + if (!cv_flag(cv, V_UNSET)){ /* Default value exists */ /* Check when condition */ if (yang_check_when_xpath(NULL, xt, yc, &hit, &nr, &xpath) < 0) goto done; diff --git a/yang/clixon/clixon-config@2021-07-11.yang b/yang/clixon/clixon-config@2021-07-11.yang index f4d065c8..c4dea419 100644 --- a/yang/clixon/clixon-config@2021-07-11.yang +++ b/yang/clixon/clixon-config@2021-07-11.yang @@ -654,6 +654,7 @@ module clixon-config { clixon-restconf means generate autocli for all models except clixon-restconf.yang The value can be a list of space separated module names"; + default "clixon-restconf"; } leaf CLICON_CLI_VARONLY { type int32; From 9aad253f1e6263d15ceab6d73dbc67032ff8e4ba Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Thu, 5 Aug 2021 12:53:26 +0200 Subject: [PATCH 21/49] Move Yang patterns: \n match from yang parse to regex compile stage --- lib/clixon/clixon_string.h | 1 - lib/src/clixon_regex.c | 10 +++++ lib/src/clixon_string.c | 77 --------------------------------- lib/src/clixon_yang_parse_lib.c | 12 ----- 4 files changed, 10 insertions(+), 90 deletions(-) diff --git a/lib/clixon/clixon_string.h b/lib/clixon/clixon_string.h index 689623e3..e1427863 100644 --- a/lib/clixon/clixon_string.h +++ b/lib/clixon/clixon_string.h @@ -100,7 +100,6 @@ int xml_chardata_encode(char **escp, const char *fmt, ...); #endif int xml_chardata_cbuf_append(cbuf *cb, char *str); int uri_percent_decode(char *enc, char **str); -int nonprint_encode(char *orig, char **encp); const char *clicon_int2str(const map_str2int *mstab, int i); int clicon_str2int(const map_str2int *mstab, char *str); diff --git a/lib/src/clixon_regex.c b/lib/src/clixon_regex.c index 60034ff4..73448bdc 100644 --- a/lib/src/clixon_regex.c +++ b/lib/src/clixon_regex.c @@ -89,6 +89,7 @@ * \p{Z} Separators [slp]? * \p{S} Symbols [mcko]? * \p{O} Other [cfon]? + * For non-printable, \n, \t, \r see https://www.regular-expressions.info/nonprint.html */ int regexp_xsd2posix(char *xsd, @@ -124,6 +125,9 @@ regexp_xsd2posix(char *xsd, case 'i': /* initial */ cprintf(cb, "[a-zA-Z_:]"); break; + case 'n': /* non-printable \n */ + cprintf(cb, "\n"); + break; case 'p': /* category escape: \p{IsCategory} */ j = i+1; if (j+2 < strlen(xsd) && @@ -161,12 +165,18 @@ regexp_xsd2posix(char *xsd, } /* if syntax error, just leave it */ break; + case 'r': /* non-printable */ + cprintf(cb, "\r"); + break; case 's': cprintf(cb, "[ \t\r\n]"); break; case 'S': cprintf(cb, "[^ \t\r\n]"); break; + case 't': /* non-printable */ + cprintf(cb, "\t"); + break; case 'w': /* word */ //cprintf(cb, "[0-9a-zA-Z_\\\\-]") cprintf(cb, "[^[:punct:][:space:][:cntrl:]]"); diff --git a/lib/src/clixon_string.c b/lib/src/clixon_string.c index 0e73b28a..d6004a32 100644 --- a/lib/src/clixon_string.c +++ b/lib/src/clixon_string.c @@ -646,83 +646,6 @@ uri_str2cvec(char *string, goto done; } -/*! Translate \n and others from \\n (two chars) to \n (one char) - * - * This is needed in yang regex it seems. - * It was triggered by eg draft-wwlh-netconf-list-pagination-00 module example-social tagline - * leaf tagline { - * type string { - * length "1..80"; - * pattern '.*[\n].*' { - * modifier invert-match; - * @param[in] orig Original string eg with \\n - * @param[out] enc Encoded string with \n, malloced. - * - * @see https://www.regular-expressions.info/nonprint.html - */ -int -nonprint_encode(char *orig, - char **encp) -{ - int retval = -1; - char *enc = NULL; - int i; - int j; - int esc = 0; - char c; - char c1; - - if (orig == NULL){ - clicon_err(OE_UNIX, EINVAL, "orig is NULL"); - goto done; - } - /* Encoded string is equal or shorter */ - if ((enc = malloc(strlen(orig)+1)) == NULL){ - clicon_err(OE_UNIX, errno, "strdup"); - goto done; - } - j = 0; - for (i=0; i Date: Mon, 19 Jul 2021 13:17:10 +0300 Subject: [PATCH 22/49] implement yang-patch --- apps/restconf/restconf_api.h | 2 + apps/restconf/restconf_api_native.c | 1 + apps/restconf/restconf_main_fcgi.c | 6 + apps/restconf/restconf_main_native.c | 6 + apps/restconf/restconf_methods.c | 540 +++++++++++++++++++- apps/restconf/restconf_methods_post.c | 3 +- apps/restconf/restconf_methods_post.h | 1 + apps/restconf/restconf_root.c | 2 +- example/main/clixon-example@2020-12-01.yang | 3 + example/main/example.xml | 1 + lib/clixon/clixon_json.h | 1 - yang/mandatory/Makefile.in | 1 + 12 files changed, 562 insertions(+), 5 deletions(-) diff --git a/apps/restconf/restconf_api.h b/apps/restconf/restconf_api.h index a44cb440..f94b8ce2 100644 --- a/apps/restconf/restconf_api.h +++ b/apps/restconf/restconf_api.h @@ -52,4 +52,6 @@ int restconf_reply_send(void *req, int code, cbuf *cb, int head); cbuf *restconf_get_indata(void *req); +#define YANG_PATCH + #endif /* _RESTCONF_API_H_ */ diff --git a/apps/restconf/restconf_api_native.c b/apps/restconf/restconf_api_native.c index c4162b2e..70a8915a 100644 --- a/apps/restconf/restconf_api_native.c +++ b/apps/restconf/restconf_api_native.c @@ -191,3 +191,4 @@ restconf_get_indata(void *req0) return cb; } + diff --git a/apps/restconf/restconf_main_fcgi.c b/apps/restconf/restconf_main_fcgi.c index 88622b6a..b8508102 100644 --- a/apps/restconf/restconf_main_fcgi.c +++ b/apps/restconf/restconf_main_fcgi.c @@ -419,6 +419,12 @@ main(int argc, if (yang_spec_parse_module(h, "ietf-restconf", NULL, yspec)< 0) goto done; +#ifdef YANG_PATCH + /* Load yang restconf patch module */ + if (yang_spec_parse_module(h, "ietf-yang-patch", NULL, yspec)< 0) + goto done; +#endif // YANG_PATCH + /* Add netconf yang spec, used as internal protocol */ if (netconf_module_load(h) < 0) goto done; diff --git a/apps/restconf/restconf_main_native.c b/apps/restconf/restconf_main_native.c index 29e65b7d..80a5a7a8 100644 --- a/apps/restconf/restconf_main_native.c +++ b/apps/restconf/restconf_main_native.c @@ -1737,6 +1737,12 @@ restconf_clixon_init(clicon_handle h, if (yang_spec_parse_module(h, "ietf-restconf", NULL, yspec)< 0) goto done; +#ifdef YANG_PATCH + /* Load yang restconf patch module */ + if (yang_spec_parse_module(h, "ietf-yang-patch", NULL, yspec)< 0) + goto done; +#endif // YANG_PATCH + /* Add netconf yang spec, used as internal protocol */ if (netconf_module_load(h) < 0) goto done; diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index 10844869..3d9630bb 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -74,6 +74,7 @@ #include "restconf_api.h" #include "restconf_err.h" #include "restconf_methods.h" +#include "restconf_methods_post.h" /*! REST OPTIONS method * According to restconf @@ -579,6 +580,536 @@ api_data_write(clicon_handle h, return retval; } /* api_data_write */ +#ifdef YANG_PATCH +/*! YANG PATCH method + * @param[in] h Clixon handle + * @param[in] req Generic Www handle + * @param[in] api_path0 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 + * @param[in] pretty Set to 1 for pretty-printed xml/json output + * @param[in] media_out Output media + * Netconf: (nc:operation="merge") + * See RFC8072 + * YANG patch can be used to "create", "delete", "insert", "merge", "move", "replace", and/or + "remove" a resource within the target resource. + * Currently "move" not supported + */ +static int +api_data_yang_patch(clicon_handle h, + void *req, + char *api_path0, + cvec *pcvec, + int pi, + cvec *qvec, + char *data, + int pretty, + restconf_media media_out, + ietf_ds_t ds) +{ + int retval = -1; + int i; + cxobj *xdata0 = NULL; /* Original -d data struct (including top symbol) */ + cbuf *cbx = NULL; + cxobj *xtop = NULL; /* top of api-path */ + cxobj *xbot = NULL; /* bottom of api-path */ + yang_stmt *ybot = NULL; /* yang of xbot */ + cxobj *xbot_tmp = NULL; + yang_stmt *yspec; + char *api_path; + cxobj *xret = NULL; + cxobj *xretcom = NULL; /* return from commit */ + cxobj *xretdis = NULL; /* return from discard-changes */ + cxobj *xerr = NULL; /* malloced must be freed */ + int ret; + cvec *nsc = NULL; + yang_bind yb; + char *xpath = NULL; + const int temp_str_malloc_size = 5000; + char *path_orig_1 = NULL; + + clicon_debug(1, "%s api_path:\"%s\"", __FUNCTION__, api_path0); + if ((yspec = clicon_dbspec_yang(h)) == NULL){ + clicon_err(OE_FATAL, 0, "No DB_SPEC"); + goto done; + } + api_path=api_path0; + /* strip /... from start */ + for (i=0; i= 0; l--) { + if (target_val[l] == '/') { + idx = l; + break; + } + } + strncpy(post_req_uri, target_val, idx); + strcat(simple_patch_request_uri, post_req_uri); + free(post_req_uri); + for (int l = strlen(json_simple_patch); l>= 0; l--) { + char c = json_simple_patch[l]; + if (c == '}') { + json_simple_patch[l] = ']'; + json_simple_patch[l + 1] = '}'; + break; + } + } + + // Send the POST request + ret = api_data_post(h, req, simple_patch_request_uri, pi, qvec, json_simple_patch, pretty, YANG_DATA_JSON, media_out, ds ); + if (value_vec_tmp != NULL) + free(value_vec_tmp); + free(x_simple_patch); + free(patch_header); // NULL check was already done before + if (ret != 0) + goto done; + break; + } + + // For "create", put all the data values into a single POST request + if (strcmp(op_val,"create") == 0) { + for (int k = 0; k < value_vec_len; k++) { + if (value_vec[k] != NULL) { + value_vec_tmp = xml_dup(value_vec[k]); + xml_addsub(x_simple_patch, value_vec_tmp); + } + } + + // Send the POST request + cbuf* cb = cbuf_new(); + xml2json_cbuf(cb, x_simple_patch, 1); + char *json_simple_patch = cbuf_get(cb); + ret = api_data_post(h, req, simple_patch_request_uri, pi, qvec, json_simple_patch, pretty, YANG_DATA_JSON, media_out, ds ); + if (value_vec_tmp != NULL) + free(value_vec_tmp); + free(x_simple_patch); + free(patch_header); // NULL check was already done before + if (ret != 0) + goto done; + break; + } + // For "insert", make a api_data_post request + if (strcmp(op_val, "insert") == 0) { + char *json_simple_patch = malloc(temp_str_malloc_size); + if (json_simple_patch == NULL) + goto done; + memset(json_simple_patch, 0, temp_str_malloc_size); + + // Loop through the XML, and get each value + for (int k = 0; k < value_vec_len; k++) { + if (value_vec[k] != NULL) { + value_vec_tmp = xml_dup(value_vec[k]); + xml_addsub(x_simple_patch, value_vec_tmp); + } + } + cbuf* cb = cbuf_new(); + xml2json_cbuf(cb, x_simple_patch, 1); + + // Some ugly text processing to get the JSON to match what api_data_post() expects + char *json_simple_patch_tmp = cbuf_get(cb); + int brace_count = 0; + for (int l = 0; l < strlen(json_simple_patch_tmp); l++) { + char c = json_simple_patch_tmp[l]; + if (c == '{') { + brace_count++; + if (brace_count == 2) { + json_simple_patch[strlen(json_simple_patch)] = '['; + } + } + json_simple_patch[strlen(json_simple_patch)] = c; + } + for (int l = strlen(json_simple_patch); l>= 0; l--) { + char c = json_simple_patch[l]; + if (c == '}') { + json_simple_patch[l] = ']'; + json_simple_patch[l + 1] = '}'; + break; + } + } + + // Set the insert attributes + cvec* qvec_tmp = NULL; + qvec_tmp = cvec_new(0); + if (qvec_tmp == NULL) + goto done; + cg_var *cv; + if ((cv = cvec_add(qvec_tmp, CGV_STRING)) == NULL){ + goto done; + } + cv_name_set(cv, "insert"); + cv_string_set(cv, where_val); + char *point_str = malloc(temp_str_malloc_size); + if (point_str == NULL) + goto done; + memset(point_str, 0, temp_str_malloc_size); + strcpy(point_str, api_path); + strcat(point_str, point_val); + if ((cv = cvec_add(qvec_tmp, CGV_STRING)) == NULL){ + goto done; + } + cv_name_set(cv, "point"); + cv_string_set(cv, point_str); + + // Send the POST request + ret = api_data_post(h, req, simple_patch_request_uri, pi, qvec_tmp, json_simple_patch, pretty, YANG_DATA_JSON, media_out, ds ); + if (cb != NULL) + cbuf_free(cb); + if (value_vec_tmp != NULL) + free(value_vec_tmp); + free(point_str); // NULL check was already done above + free(json_simple_patch); // NULL check was already done above + free(patch_header); // NULL check was already done before + if (x_simple_patch != NULL) + free(x_simple_patch); + break; + } + + // For merge", make single simple patch requests for each value + if (strcmp(op_val,"merge") == 0) { + if (key_xn != NULL) + xml_addsub(x_simple_patch, key_xn); + + char *json_simple_patch = malloc(temp_str_malloc_size); + if (json_simple_patch == NULL) + goto done; + + // Loop through the XML, create JSON from each one, and submit a simple patch + for (int k = 0; k < value_vec_len; k++) { + if (value_vec[k] != NULL) { + value_vec_tmp = xml_dup(value_vec[k]); + xml_addsub(x_simple_patch, value_vec_tmp); + } + cbuf* cb = cbuf_new(); + xml2json_cbuf(cb, x_simple_patch, 1); + + // Some ugly text processing to get the JSON to match what api_data_write() expects for a simple patch + char *json_simple_patch_tmp = cbuf_get(cb); + memset(json_simple_patch, 0, temp_str_malloc_size); + int brace_count = 0; + for (int l = 0; l < strlen(json_simple_patch_tmp); l++) { + char c = json_simple_patch_tmp[l]; + if (c == '{') { + brace_count++; + if (brace_count == 2) { + json_simple_patch[strlen(json_simple_patch)] = '['; + } + } + json_simple_patch[strlen(json_simple_patch)] = c; + } + for (int l = strlen(json_simple_patch); l>= 0; l--) { + char c = json_simple_patch[l]; + if (c == '}') { + json_simple_patch[l] = ']'; + json_simple_patch[l + 1] = '}'; + break; + } + } + if (value_vec_tmp != NULL) + free(value_vec_tmp); + // Send the simple patch request + ret = api_data_write(h, req, simple_patch_request_uri, pcvec, pi, qvec, json_simple_patch, pretty, YANG_DATA_JSON, media_out, plain_patch_val, ds ); + cbuf_free(cb); + } + free(json_simple_patch); // NULL check was already done above + free(patch_header); // NULL check was already done before + if (x_simple_patch != NULL) + free(x_simple_patch); + } + } + if ((strcmp(op_val, "delete") == 0) || + (strcmp(op_val, "remove") == 0)) { + strcat(simple_patch_request_uri, target_val); + if (strcmp(op_val, "delete") == 0) { + // TODO - send error + } else { + // TODO - do not send error + } + api_data_delete(h, req, simple_patch_request_uri, pi, pretty, YANG_DATA_JSON, ds); + } + if (simple_patch_request_uri) + free(simple_patch_request_uri); + if (api_path_target) + free(api_path_target); + } + ok: + retval = 0; + done: + if (path_orig_1 != NULL) + free(path_orig_1); + if (vec) + free(vec); + if (xpath) + free(xpath); + if (nsc) + xml_nsctx_free(nsc); + if (xret) + xml_free(xret); + if (xerr) + xml_free(xerr); + if (xretcom) + xml_free(xretcom); + if (xretdis) + xml_free(xretdis); + if (xtop) + xml_free(xtop); + if (xdata0) + xml_free(xdata0); + if (cbx) + cbuf_free(cbx); + return retval; +} +#endif // YANG_PATCH + /*! Generic REST PUT method * @param[in] h Clixon handle * @param[in] req Generic Www handle @@ -673,9 +1204,16 @@ api_data_patch(clicon_handle h, media_in, media_out, 1, ds); break; case YANG_PATCH_XML: - case YANG_PATCH_JSON: /* RFC 8072 patch */ ret = restconf_notimplemented(h, req, pretty, media_out); break; + case YANG_PATCH_JSON: /* RFC 8072 patch */ +#ifdef YANG_PATCH + ret = api_data_yang_patch(h, req, api_path0, pcvec, pi, qvec, data, pretty, + media_out, ds); +#else + ret = restconf_unsupported_media(h, req, pretty, media_out); +#endif + break; default: ret = restconf_unsupported_media(h, req, pretty, media_out); break; diff --git a/apps/restconf/restconf_methods_post.c b/apps/restconf/restconf_methods_post.c index bc0e2bfb..d018b9e7 100644 --- a/apps/restconf/restconf_methods_post.c +++ b/apps/restconf/restconf_methods_post.c @@ -155,6 +155,7 @@ api_data_post(clicon_handle h, cvec *qvec, char *data, int pretty, + restconf_media media_in, restconf_media media_out, ietf_ds_t ds) { @@ -178,7 +179,6 @@ api_data_post(clicon_handle h, cxobj *x; char *username; int ret; - restconf_media media_in; int nrchildren0 = 0; yang_bind yb; @@ -231,7 +231,6 @@ api_data_post(clicon_handle h, * If xbot is top-level (api_path=null) it does not have a spec therefore look for * top-level (yspec) otherwise assume parent (xbot) is populated. */ - media_in = restconf_content_type(h); switch (media_in){ case YANG_DATA_XML: if ((ret = clixon_xml_parse_string(data, yb, yspec, &xbot, &xerr)) < 0){ diff --git a/apps/restconf/restconf_methods_post.h b/apps/restconf/restconf_methods_post.h index f01e53ee..3317d3ea 100644 --- a/apps/restconf/restconf_methods_post.h +++ b/apps/restconf/restconf_methods_post.h @@ -44,6 +44,7 @@ int api_data_post(clicon_handle h, void *req, char *api_path, int pi, cvec *qvec, char *data, int pretty, + restconf_media media_in, restconf_media media_out, ietf_ds_t ds); int api_operations_post(clicon_handle h, void *req, char *api_path, diff --git a/apps/restconf/restconf_root.c b/apps/restconf/restconf_root.c index aa79953e..068acb4b 100644 --- a/apps/restconf/restconf_root.c +++ b/apps/restconf/restconf_root.c @@ -331,7 +331,7 @@ api_data(clicon_handle h, retval = api_data_get(h, req, api_path, pcvec, pi, qvec, pretty, media_out, ds); } else if (strcmp(request_method, "POST")==0) { - retval = api_data_post(h, req, api_path, pi, qvec, data, pretty, media_out, ds); + retval = api_data_post(h, req, api_path, pi, qvec, data, pretty, restconf_content_type(h), media_out, ds); } else if (strcmp(request_method, "PUT")==0) { if (read_only) diff --git a/example/main/clixon-example@2020-12-01.yang b/example/main/clixon-example@2020-12-01.yang index 412e571d..361d81f0 100644 --- a/example/main/clixon-example@2020-12-01.yang +++ b/example/main/clixon-example@2020-12-01.yang @@ -15,6 +15,9 @@ module clixon-example { import ietf-datastores { prefix ds; } + import example-jukebox { + prefix ej; + } description "Clixon example used as a part of the Clixon test suite. It can be used as a basis for making new Clixon applications. diff --git a/example/main/example.xml b/example/main/example.xml index 179db40f..e20615c6 100644 --- a/example/main/example.xml +++ b/example/main/example.xml @@ -24,4 +24,5 @@ true false truenonedefault
0.0.0.0
8081false
+ truenonedefault
0.0.0.0
80false
diff --git a/lib/clixon/clixon_json.h b/lib/clixon/clixon_json.h index 8bd45d10..2d4e1175 100644 --- a/lib/clixon/clixon_json.h +++ b/lib/clixon/clixon_json.h @@ -52,5 +52,4 @@ int json_print(FILE *f, cxobj *x); int xml2json_vec(FILE *f, cxobj **vec, size_t veclen, int pretty); int clixon_json_parse_string(char *str, yang_bind yb, yang_stmt *yspec, cxobj **xt, cxobj **xret); int clixon_json_parse_file(FILE *fp, yang_bind yb, yang_stmt *yspec, cxobj **xt, cxobj **xret); - #endif /* _CLIXON_JSON_H */ diff --git a/yang/mandatory/Makefile.in b/yang/mandatory/Makefile.in index ca68b32a..7a1d299e 100644 --- a/yang/mandatory/Makefile.in +++ b/yang/mandatory/Makefile.in @@ -50,6 +50,7 @@ YANGSPECS += ietf-restconf-monitoring@2017-01-26.yang YANGSPECS += ietf-yang-library@2019-01-04.yang YANGSPECS += ietf-yang-types@2013-07-15.yang YANGSPECS += ietf-datastores@2018-02-14.yang +YANGSPECS += ietf-yang-patch@2021-07-01.yang all: From 75f5dc8500919e4778518b480f449293a6649dbf Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Mon, 19 Jul 2021 22:07:21 +0200 Subject: [PATCH 23/49] Misc cleanups, remove old yangs, typos, etc --- example/main/Makefile.in | 1 + example/main/example_backend.c | 1 + include/clixon_custom.h | 2 +- lib/src/clixon_yang_module.c | 1 + yang/clixon/Makefile.in | 2 +- yang/clixon/clixon-config.yang | 1 - ...-08.yang => clixon-config@2021-07-11.yang} | 95 +++++++- yang/clixon/clixon-lib@2020-12-30.yang | 180 -------------- yang/clixon/clixon-restconf@2021-03-15.yang | 221 ------------------ 9 files changed, 96 insertions(+), 408 deletions(-) delete mode 120000 yang/clixon/clixon-config.yang rename yang/clixon/{clixon-config@2021-03-08.yang => clixon-config@2021-07-11.yang} (90%) delete mode 100644 yang/clixon/clixon-lib@2020-12-30.yang delete mode 100644 yang/clixon/clixon-restconf@2021-03-15.yang diff --git a/example/main/Makefile.in b/example/main/Makefile.in index 356426c8..f1c28e09 100644 --- a/example/main/Makefile.in +++ b/example/main/Makefile.in @@ -101,6 +101,7 @@ BE_SRC = $(APPNAME)_backend.c BE_OBJ = $(BE_SRC:%.c=%.o) $(BE_PLUGIN): $(BE_OBJ) ifeq ($(LINKAGE),static) +# can include -L in LDFLAGS? $(CC) -Wall -shared $(LDFLAGS) -o $@ -lc $< -lclixon -L ../../apps/backend/ -lclixon_backend else $(CC) -Wall -shared $(LDFLAGS) -o $@ -lc $< -lclixon -lclixon_backend diff --git a/example/main/example_backend.c b/example/main/example_backend.c index cad6dc30..1d907030 100644 --- a/example/main/example_backend.c +++ b/example/main/example_backend.c @@ -1012,6 +1012,7 @@ example_exit(clicon_handle h) return 0; } +/* Forward declaration */ clixon_plugin_api *clixon_plugin_init(clicon_handle h); static clixon_plugin_api api = { diff --git a/include/clixon_custom.h b/include/clixon_custom.h index 30e114d0..d4af4463 100644 --- a/include/clixon_custom.h +++ b/include/clixon_custom.h @@ -108,7 +108,7 @@ * added to its parent but then it is more difficult to check trhe when condition. * This fix add the parent x0p as a "candidate" so that the xpath-eval function can use it as * an alernative if it exists. - * Note although this solves many usecases involving parents and absolute paths, itstill does not + * Note although this solves many usecases involving parents and absolute paths, it still does not * solve all usecases, such as absolute usecases where the added node is looked for */ #define XML_PARENT_CANDIDATE diff --git a/lib/src/clixon_yang_module.c b/lib/src/clixon_yang_module.c index 03e9695c..3d47f6f7 100644 --- a/lib/src/clixon_yang_module.c +++ b/lib/src/clixon_yang_module.c @@ -112,6 +112,7 @@ modstate_diff_free(modstate_diff_t *md) * * Load RFC7895 yang spec, module-set-id, etc. * @param[in] h Clicon handle + * @see netconf_module_load */ int yang_modules_init(clicon_handle h) diff --git a/yang/clixon/Makefile.in b/yang/clixon/Makefile.in index 555662be..ddd83935 100644 --- a/yang/clixon/Makefile.in +++ b/yang/clixon/Makefile.in @@ -41,7 +41,7 @@ datarootdir = @datarootdir@ # See also OPT_YANG_INSTALLDIR for the standard yang files YANG_INSTALLDIR = @YANG_INSTALLDIR@ -YANGSPECS = clixon-config@2021-05-20.yang # 5.2 +YANGSPECS = clixon-config@2021-07-11.yang # 5.3 YANGSPECS += clixon-lib@2021-03-08.yang # 5.1 YANGSPECS += clixon-rfc5277@2008-07-01.yang YANGSPECS += clixon-xml-changelog@2019-03-21.yang diff --git a/yang/clixon/clixon-config.yang b/yang/clixon/clixon-config.yang deleted file mode 120000 index 91d563e9..00000000 --- a/yang/clixon/clixon-config.yang +++ /dev/null @@ -1 +0,0 @@ -clixon-config@2021-03-08.yang \ No newline at end of file diff --git a/yang/clixon/clixon-config@2021-03-08.yang b/yang/clixon/clixon-config@2021-07-11.yang similarity index 90% rename from yang/clixon/clixon-config@2021-03-08.yang rename to yang/clixon/clixon-config@2021-07-11.yang index eb217e2e..dfc8d54b 100644 --- a/yang/clixon/clixon-config@2021-03-08.yang +++ b/yang/clixon/clixon-config@2021-07-11.yang @@ -43,12 +43,28 @@ module clixon-config { ***** END LICENSE BLOCK *****"; + revision 2021-07-11 { + description + "Added option + CLICON_SYSTEM_CAPABILITIES"; + } + revision 2021-05-20 { + description + "Added option: + CLICON_RESTCONF_USER + CLICON_RESTCONF_PRIVILEGES + CLICON_RESTCONF_INSTALLDIR + CLICON_RESTCONF_STARTUP_DONTUPDATE + CLICON_NETCONF_MESSAGE_ID_OPTIONAL + Released in Clixon 5.2"; + } revision 2021-03-08 { description "Added option: CLICON_NETCONF_HELLO_OPTIONAL CLICON_CLI_AUTOCLI_EXCLUDE - CLICON_XMLDB_UPGRADE_CHECKOLD"; + CLICON_XMLDB_UPGRADE_CHECKOLD + Released in Clixon 5.1"; } revision 2020-12-30 { description @@ -171,6 +187,10 @@ module clixon-config { "Commit startup configuration into running state After reboot when no persistent running db exists"; } + enum running-startup{ + description + "First try running db, if it is empty try startup db."; + } } } typedef datastore_format{ @@ -406,7 +426,11 @@ module clixon-config { "If false, skip Yang list check sanity checks from RFC 7950, Sec 7.8.2: The 'key' statement, which MUST be present if the list represents configuration. Some yang specs seem not to fulfil this. However, if you reset this, there may - be follow-up errors due to code that assumes a configuration list has keys"; + be follow-up errors due to code that assumes a configuration list has keys + Marked as obsolete since the observation above seemed to be related to the + yang-data extension in RFC8040 allows non-key lists. This has been implemented + by a YANG_FLAG_NOKEY yang flag mechanism"; + status obsolete; } leaf CLICON_YANG_UNKNOWN_ANYDATA{ type boolean; @@ -421,6 +445,18 @@ module clixon-config { only loading from startup but may occur in other circumstances as well. This means that sanity checks of erroneous XML/JSON may not be properly signalled."; } + leaf CLICON_SYSTEM_CAPABILITIES { + type boolean; + default false; + description + "Enable module ietf-system-capabilities and ietf-notification-capabilities + Note: There are several dependencies: + - ietf-yang-library revision 2019-01-04 is REQUIRED + - nacm + - ietf-yang-structure-ext.yang, + - ietf-yang-instance-data + see draft-ietf-netconf-notification-capabilities-17"; + } leaf CLICON_BACKEND_DIR { type string; description @@ -451,6 +487,16 @@ module clixon-config { is returned, which conforms to the RFC. Note this applies only to external NETCONF, not the internal (IPC) netconf"; } + leaf CLICON_NETCONF_MESSAGE_ID_OPTIONAL { + type boolean; + default false; + description + "This option relates to RFC 6241 Sec 4.1 Element + The element has a mandatory attribute 'message-id', which is a + string chosen by the sender of the RPC. + If true, an RPC can be sent without a message-id. + This applies to both external NETCONF and internal (IPC) netconf"; + } leaf CLICON_RESTCONF_DIR { type string; description @@ -470,7 +516,28 @@ module clixon-config { Note: Obsolete, use fcgi-socket in clixon-restconf.yang instead"; status obsolete; } - + leaf CLICON_RESTCONF_INSTALLDIR { + type string; + default "/usr/local/sbin"; + description + "Path to dir of clixon-restconf daemon binary as used by backend if started internally + Discussion: Somewhat problematic to have it as run time option. It may think it + should be known at configure or install time, but for example the main docker + installation moves the binaries, and this may be true elsewehere too. + Maybe one could locate it via PATHs search"; + } + leaf CLICON_RESTCONF_STARTUP_DONTUPDATE { + type boolean; + default false; + description + "According to RFC 8040 Sec 1.4: + If the NETCONF server supports :startup, the RESTCONF server MUST automatically + update the [...] startup configuration [...] as a consequence of a RESTCONF + edit operation. + Setting this option disables this behaviour, ie the startup configuration is NOT + automatically updated. + If this option is false, the startup is autoamtically updated following the RFC"; + } leaf CLICON_RESTCONF_PRETTY { type boolean; default true; @@ -486,6 +553,26 @@ module clixon-config { Note: Obsolete, use pretty in clixon-restconf.yang instead"; status obsolete; } + leaf CLICON_RESTCONF_USER { + type string; + description + "Run clixon_daemon as this user + When drop privileges is used, the daemon will drop privileges to this user. + In pre-5.2 code this was configured as compile-time constant WWWUSER with + default value www-data + See also CLICON_PRIVILEGES setting"; + default www-data; + } + leaf CLICON_RESTCONF_PRIVILEGES { + type priv_mode; + default drop_perm; + description + "Restconf privileges mode. + If drop_perm or drop_temp then drop privileges to CLICON_RESTCONF_USER. + If the platform does not support getresuid and accompanying functions, the mode + must be set to 'none'. + "; + } leaf CLICON_CLI_DIR { type string; description @@ -706,7 +793,7 @@ module clixon-config { user (eg datastores). It also sets the backend unix socket owner to this user, but its group is set by CLICON_SOCK_GROUP. - See also CLICON_PRIVILEGES setting"; + See also CLICON_BACKEND_PRIVILEGES setting"; } leaf CLICON_BACKEND_PRIVILEGES { type priv_mode; diff --git a/yang/clixon/clixon-lib@2020-12-30.yang b/yang/clixon/clixon-lib@2020-12-30.yang deleted file mode 100644 index c3780d1e..00000000 --- a/yang/clixon/clixon-lib@2020-12-30.yang +++ /dev/null @@ -1,180 +0,0 @@ -module clixon-lib { - yang-version 1.1; - namespace "http://clicon.org/lib"; - prefix cl; - - organization - "Clicon / Clixon"; - - contact - "Olof Hagsand "; - - description - "Clixon Netconf extensions for communication between clients and backend. - - ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2019 Olof Hagsand - Copyright (C) 2020-2021 Olof Hagsand and Rubicon Communications, LLC(Netgate) - - This file is part of CLIXON - - Licensed under the Apache License, Version 2.0 (the \"License\"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an \"AS IS\" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - Alternatively, the contents of this file may be used under the terms of - the GNU General Public License Version 3 or later (the \"GPL\"), - in which case the provisions of the GPL are applicable instead - of those above. If you wish to allow use of your version of this file only - under the terms of the GPL, and not to allow others to - use your version of this file under the terms of Apache License version 2, - indicate your decision by deleting the provisions above and replace them with - the notice and other provisions required by the GPL. If you do not delete - the provisions above, a recipient may use your version of this file under - the terms of any one of the Apache License version 2 or the GPL. - - ***** END LICENSE BLOCK *****"; - - revision 2020-12-30 { - description - "Changed: RPC process-control output parameter status to pid"; - } - revision 2020-12-08 { - description - "Added: autocli-op extension. - rpc process-control for process/daemon management - Released in clixon 4.9"; - } - revision 2020-04-23 { - description - "Added: stats RPC for clixon XML and memory statistics. - Added: restart-plugin RPC for restarting individual plugins without restarting backend."; - } - revision 2019-08-13 { - description - "No changes (reverted change)"; - } - revision 2019-06-05 { - description - "ping rpc added for liveness"; - } - revision 2019-01-02 { - description - "Released in Clixon 3.9"; - } - typedef service-operation { - type enumeration { - enum start { - description - "Start if not already running"; - } - enum stop { - description - "Stop if running"; - } - enum restart { - description - "Stop if running, then start"; - } - enum status { - description - "Check status"; - } - } - description - "Common operations that can be performed on a service"; - } - extension autocli-op { - description - "Takes an argument an operation defing how to modify the clispec at - this point in the YANG tree for the automated generated CLI. - Note that this extension is only used in clixon_cli. - Operations is expected to be extended, but the following operations are defined: - - hide This command is active but not shown by ? or TAB"; - argument cliop; - } - rpc debug { - description "Set debug level of backend."; - input { - leaf level { - type uint32; - } - } - } - rpc ping { - description "Check aliveness of backend daemon."; - } - rpc stats { - description "Clixon XML statistics."; - output { - container global{ - description "Clixon global statistics"; - leaf xmlnr{ - description "Number of XML objects: number of residing xml/json objects - in the internal 'cxobj' representation."; - type uint64; - } - } - list datastore{ - description "Datastore statistics"; - key "name"; - leaf name{ - description "name of datastore (eg running)."; - type string; - } - leaf nr{ - description "Number of XML objects. That is number of residing xml/json objects - in the internal 'cxobj' representation."; - type uint64; - } - leaf size{ - description "Size in bytes of internal datastore cache of datastore tree."; - type uint64; - } - } - - } - } - rpc restart-plugin { - description "Restart specific backend plugins."; - input { - leaf-list plugin { - description "Name of plugin to restart"; - type string; - } - } - } - - rpc process-control { - description - "Control a specific process or daemon: start/stop, etc. - This is for direct managing of a process by the backend. - Alternatively one can manage a daemon via systemd, containerd, kubernetes, etc."; - input { - leaf name { - description "Name of process"; - type string; - mandatory true; - } - leaf operation { - type service-operation; - mandatory true; - description - "One of the strings 'start', 'stop', 'restart', or 'status'."; - } - } - output { - leaf pid { - description "Process-id of running process or 0 if not running - Value is only valid for operation status"; - type uint32; - } - } - } -} diff --git a/yang/clixon/clixon-restconf@2021-03-15.yang b/yang/clixon/clixon-restconf@2021-03-15.yang deleted file mode 100644 index 7180054a..00000000 --- a/yang/clixon/clixon-restconf@2021-03-15.yang +++ /dev/null @@ -1,221 +0,0 @@ -module clixon-restconf { - yang-version 1.1; - namespace "http://clicon.org/restconf"; - prefix "clrc"; - - import ietf-inet-types { - prefix inet; - } - - organization - "Clixon"; - - contact - "Olof Hagsand "; - - description - "This YANG module provides a data-model for the Clixon RESTCONF daemon. - ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate) - - This file is part of CLIXON - - Licensed under the Apache License, Version 2.0 (the \"License\"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an \"AS IS\" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - Alternatively, the contents of this file may be used under the terms of - the GNU General Public License Version 3 or later (the \"GPL\"), - in which case the provisions of the GPL are applicable instead - of those above. If you wish to allow use of your version of this file only - under the terms of the GPL, and not to allow others to - use your version of this file under the terms of Apache License version 2, - indicate your decision by deleting the provisions above and replace them with - the notice and other provisions required by the GPL. If you do not delete - the provisions above, a recipient may use your version of this file under - the terms of any one of the Apache License version 2 or the GPL. - - ***** END LICENSE BLOCK *****"; - - revision 2021-03-15 { - description - "make authentication-type none a feature - Added flag to enable core dumps"; - } - revision 2020-12-30 { - description - "Added: debug field - Added 'none' as default value for auth-type - Changed http-auth-type enum from 'password' to 'user'"; - } - revision 2020-10-30 { - description - "Initial release"; - } - - feature fcgi { - description - "This feature indicates that the restconf server supports the fast-cgi reverse - proxy solution. - That is, a reverse proxy is the HTTP front-end and the restconf daemon listens - to a fcgi socket. - The alternative is the internal HTTP solution using evhtp."; - } - - feature allow-auth-none { - description - "This feature allows the use of authentication-type none."; - } - - typedef http-auth-type { - type enumeration { - enum none { - if-feature "allow-auth-none"; - description - "Incoming message are set to authenticated by default. No ca-auth callback is called, - Authenticated user is set to special user 'none'. - Typically assumes NACM is not enabled."; - } - enum client-certificate { - description - "TLS client certificate validation is made on each incoming message. If it passes - the authenticated user is extracted from the SSL_CN parameter - The ca-auth callback can be used to revise this behavior."; - } - enum user { - description - "User-defined authentication as defined by the ca-auth callback. - One example is some form of password authentication, such as basic auth."; - } - } - description - "Enumeration of HTTP authorization types."; - } - grouping clixon-restconf{ - description - "HTTP RESTCONF configuration."; - leaf enable { - type boolean; - default "false"; - description - "Enables RESTCONF functionality. - Note that starting/stopping of a restconf daemon is different from it being - enabled or not. - For example, if the restconf daemon is under systemd management, the restconf - daemon will only start if enable=true."; - } - leaf auth-type { - type http-auth-type; - description - "The authentication type. - Note client-certificate applies only if ssl-enable is true and socket has ssl"; - default user; - } - leaf debug { - description - "Set debug level of restconf daemon. - 0 is no debug, 1 is debugging, more is detailed debug. - Debug logs will be directed to syslog with - ident: clixon_restconf and PID - facility: LOG_USER - level: LOG_DEBUG"; - type uint32; - default 0; - } - leaf enable-core-dump { - description - "enable core dumps. - this is a no-op on systems that don't support it."; - type boolean; - default false; - } - leaf pretty { - type boolean; - default true; - description - "Restconf return value pretty print. - Restconf clients may add HTTP header: - Accept: application/yang-data+json, or - Accept: application/yang-data+xml - to get return value in XML or JSON. - RFC 8040 examples print XML and JSON in pretty-printed form. - Setting this value to false makes restconf return not pretty-printed - which may be desirable for performance or tests - This replaces the CLICON_RESTCONF_PRETTY option in clixon-config.yang"; - } - /* From this point only specific options - * First fcgi-specific options - */ - leaf fcgi-socket { - if-feature fcgi; /* Set by default by fcgi clixon_restconf daemon */ - type string; - default "/www-data/fastcgi_restconf.sock"; - description - "Path to FastCGI unix socket. Should be specified in webserver - Eg in nginx: fastcgi_pass unix:/www-data/clicon_restconf.sock - Only if with-restconf=fcgi, NOT evhtp - This replaces CLICON_RESTCONF_PATH option in clixon-config.yang"; - } - /* Second, evhtp-specific options */ - leaf server-cert-path { - type string; - description - "Path to server certificate file. - Note only applies if socket has ssl enabled"; - } - leaf server-key-path { - type string; - description - "Path to server key file - Note only applies if socket has ssl enabled"; - } - leaf server-ca-cert-path { - type string; - description - "Path to server CA cert file - Note only applies if socket has ssl enabled"; - } - list socket { - description - "List of server sockets that the restconf daemon listens to"; - key "namespace address port"; - leaf namespace { - type string; - description - "Network namespace. - On platforms where namespaces are not suppported, 'default' - Default value can be changed by RESTCONF_NETNS_DEFAULT"; - } - leaf address { - type inet:ip-address; - description "IP address to bind to"; - } - leaf port { - type inet:port-number; - description "TCP port to bind to"; - } - leaf ssl { - type boolean; - default true; - description "Enable for HTTPS otherwise HTTP protocol"; - } - } - } - container restconf { - description - "This presence is strictly not necessary since the enable flag - in clixon-restconf is the flag bearing the actual semantics. - However, removing the presence leads to default config in all - clixon installations, even those which do not use backend-started restconf. - One could see this as mostly cosmetically annoying. - Alternative would be to make the inclusion of this yang conditional."; - presence "Enables RESTCONF"; - uses clixon-restconf; - } -} From 1631e23c98fbb6a2464e1a058520c50ad1ab06f1 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Tue, 20 Jul 2021 15:54:42 +0200 Subject: [PATCH 24/49] added ietf-yang-patch.yang and enabled clixon_util_validate --- util/Makefile.in | 2 +- .../mandatory/ietf-yang-patch@2017-02-22.yang | 390 ++++++++++++++++++ 2 files changed, 391 insertions(+), 1 deletion(-) create mode 100644 yang/mandatory/ietf-yang-patch@2017-02-22.yang diff --git a/util/Makefile.in b/util/Makefile.in index f857033e..5737529b 100644 --- a/util/Makefile.in +++ b/util/Makefile.in @@ -91,7 +91,7 @@ APPSRC += clixon_util_path.c APPSRC += clixon_util_datastore.c APPSRC += clixon_util_regexp.c APPSRC += clixon_util_socket.c -# APPSRC += clixon_util_validate.c +APPSRC += clixon_util_validate.c APPSRC += clixon_netconf_ssh_callhome.c APPSRC += clixon_netconf_ssh_callhome_client.c ifdef with_restconf diff --git a/yang/mandatory/ietf-yang-patch@2017-02-22.yang b/yang/mandatory/ietf-yang-patch@2017-02-22.yang new file mode 100644 index 00000000..d0029ed2 --- /dev/null +++ b/yang/mandatory/ietf-yang-patch@2017-02-22.yang @@ -0,0 +1,390 @@ +module ietf-yang-patch { + yang-version 1.1; + namespace "urn:ietf:params:xml:ns:yang:ietf-yang-patch"; + prefix "ypatch"; + + import ietf-restconf { prefix rc; } + + organization + "IETF NETCONF (Network Configuration) Working Group"; + + contact + "WG Web: + WG List: + + Author: Andy Bierman + + + Author: Martin Bjorklund + + + Author: Kent Watsen + "; + + description + "This module contains conceptual YANG specifications + for the YANG Patch and YANG Patch Status data structures. + + Note that the YANG definitions within this module do not + represent configuration data of any kind. + The YANG grouping statements provide a normative syntax + for XML and JSON message-encoding purposes. + + Copyright (c) 2017 IETF Trust and the persons identified as + authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Simplified BSD License + set forth in Section 4.c of the IETF Trust's Legal Provisions + Relating to IETF Documents + (http://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC 8072; see + the RFC itself for full legal notices."; + + revision 2017-02-22 { + description + "Initial revision."; + reference + "RFC 8072: YANG Patch Media Type."; + } + + typedef target-resource-offset { + type string; + description + "Contains a data resource identifier string representing + a sub-resource within the target resource. + The document root for this expression is the + target resource that is specified in the + protocol operation (e.g., the URI for the PATCH request). + + This string is encoded according to the same rules as those + for a data resource identifier in a RESTCONF request URI."; + reference + "RFC 8040, Section 3.5.3."; + } + + rc:yang-data "yang-patch" { + uses yang-patch; + } + + rc:yang-data "yang-patch-status" { + uses yang-patch-status; + } + + grouping yang-patch { + + description + "A grouping that contains a YANG container representing the + syntax and semantics of a YANG Patch edit request message."; + + container yang-patch { + description + "Represents a conceptual sequence of datastore edits, + called a patch. Each patch is given a client-assigned + patch identifier. Each edit MUST be applied + in ascending order, and all edits MUST be applied. + If any errors occur, then the target datastore MUST NOT + be changed by the YANG Patch operation. + + It is possible for a datastore constraint violation to occur + due to any node in the datastore, including nodes not + included in the 'edit' list. Any validation errors MUST + be reported in the reply message."; + + reference + "RFC 7950, Section 8.3."; + + leaf patch-id { + type string; + mandatory true; + description + "An arbitrary string provided by the client to identify + the entire patch. Error messages returned by the server + that pertain to this patch will be identified by this + 'patch-id' value. A client SHOULD attempt to generate + unique 'patch-id' values to distinguish between + transactions from multiple clients in any audit logs + maintained by the server."; + } + + leaf comment { + type string; + description + "An arbitrary string provided by the client to describe + the entire patch. This value SHOULD be present in any + audit logging records generated by the server for the + patch."; + } + + list edit { + key edit-id; + ordered-by user; + + description + "Represents one edit within the YANG Patch request message. + The 'edit' list is applied in the following manner: + + - The first edit is conceptually applied to a copy + of the existing target datastore, e.g., the + running configuration datastore. + - Each ascending edit is conceptually applied to + the result of the previous edit(s). + - After all edits have been successfully processed, + the result is validated according to YANG constraints. + - If successful, the server will attempt to apply + the result to the target datastore."; + + leaf edit-id { + type string; + description + "Arbitrary string index for the edit. + Error messages returned by the server that pertain + to a specific edit will be identified by this value."; + } + + leaf operation { + type enumeration { + enum create { + description + "The target data node is created using the supplied + value, only if it does not already exist. The + 'target' leaf identifies the data node to be + created, not the parent data node."; + } + enum delete { + description + "Delete the target node, only if the data resource + currently exists; otherwise, return an error."; + } + + enum insert { + description + "Insert the supplied value into a user-ordered + list or leaf-list entry. The target node must + represent a new data resource. If the 'where' + parameter is set to 'before' or 'after', then + the 'point' parameter identifies the insertion + point for the target node."; + } + enum merge { + description + "The supplied value is merged with the target data + node."; + } + enum move { + description + "Move the target node. Reorder a user-ordered + list or leaf-list. The target node must represent + an existing data resource. If the 'where' parameter + is set to 'before' or 'after', then the 'point' + parameter identifies the insertion point to move + the target node."; + } + enum replace { + description + "The supplied value is used to replace the target + data node."; + } + enum remove { + description + "Delete the target node if it currently exists."; + } + } + mandatory true; + description + "The datastore operation requested for the associated + 'edit' entry."; + } + + leaf target { + type target-resource-offset; + mandatory true; + description + "Identifies the target data node for the edit + operation. If the target has the value '/', then + the target data node is the target resource. + The target node MUST identify a data resource, + not the datastore resource."; + } + + leaf point { + when "(../operation = 'insert' or ../operation = 'move')" + + "and (../where = 'before' or ../where = 'after')" { + description + "This leaf only applies for 'insert' or 'move' + operations, before or after an existing entry."; + } + type target-resource-offset; + description + "The absolute URL path for the data node that is being + used as the insertion point or move point for the + target of this 'edit' entry."; + } + + leaf where { + when "../operation = 'insert' or ../operation = 'move'" { + description + "This leaf only applies for 'insert' or 'move' + operations."; + } + type enumeration { + enum before { + description + "Insert or move a data node before the data resource + identified by the 'point' parameter."; + } + enum after { + description + "Insert or move a data node after the data resource + identified by the 'point' parameter."; + } + + enum first { + description + "Insert or move a data node so it becomes ordered + as the first entry."; + } + enum last { + description + "Insert or move a data node so it becomes ordered + as the last entry."; + } + } + default last; + description + "Identifies where a data resource will be inserted + or moved. YANG only allows these operations for + list and leaf-list data nodes that are + 'ordered-by user'."; + } + + anydata value { + when "../operation = 'create' " + + "or ../operation = 'merge' " + + "or ../operation = 'replace' " + + "or ../operation = 'insert'" { + description + "The anydata 'value' is only used for 'create', + 'merge', 'replace', and 'insert' operations."; + } + description + "Value used for this edit operation. The anydata 'value' + contains the target resource associated with the + 'target' leaf. + + For example, suppose the target node is a YANG container + named foo: + + container foo { + leaf a { type string; } + leaf b { type int32; } + } + + The 'value' node contains one instance of foo: + + + + some value + 42 + + + "; + } + } + } + + } // grouping yang-patch + + grouping yang-patch-status { + + description + "A grouping that contains a YANG container representing the + syntax and semantics of a YANG Patch Status response + message."; + + container yang-patch-status { + description + "A container representing the response message sent by the + server after a YANG Patch edit request message has been + processed."; + + leaf patch-id { + type string; + mandatory true; + description + "The 'patch-id' value used in the request."; + } + + choice global-status { + description + "Report global errors or complete success. + If there is no case selected, then errors + are reported in the 'edit-status' container."; + + case global-errors { + uses rc:errors; + description + "This container will be present if global errors that + are unrelated to a specific edit occurred."; + } + leaf ok { + type empty; + description + "This leaf will be present if the request succeeded + and there are no errors reported in the 'edit-status' + container."; + } + } + + container edit-status { + description + "This container will be present if there are + edit-specific status responses to report. + If all edits succeeded and the 'global-status' + returned is 'ok', then a server MAY omit this + container."; + + list edit { + key edit-id; + + description + "Represents a list of status responses, + corresponding to edits in the YANG Patch + request message. If an 'edit' entry was + skipped or not reached by the server, + then this list will not contain a corresponding + entry for that edit."; + + leaf edit-id { + type string; + description + "Response status is for the 'edit' list entry + with this 'edit-id' value."; + } + + choice edit-status-choice { + description + "A choice between different types of status + responses for each 'edit' entry."; + leaf ok { + type empty; + description + "This 'edit' entry was invoked without any + errors detected by the server associated + with this edit."; + } + case errors { + uses rc:errors; + description + "The server detected errors associated with the + edit identified by the same 'edit-id' value."; + } + } + } + } + } + } // grouping yang-patch-status + +} From f64eff15a005ea2eb7c058879ad40570f9bbcfb6 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sat, 24 Jul 2021 16:43:47 +0200 Subject: [PATCH 25/49] Removed space from xpath canonical form of relex/unionex --- lib/src/clixon_xpath.c | 7 +++++-- test/test_xpath_canonical.sh | 6 +++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/src/clixon_xpath.c b/lib/src/clixon_xpath.c index 64352055..43845787 100644 --- a/lib/src/clixon_xpath.c +++ b/lib/src/clixon_xpath.c @@ -290,11 +290,14 @@ xpath_tree2cbuf(xpath_tree *xs, switch (xs->xs_type){ case XP_AND: /* and or */ case XP_ADD: /* div mod + * - */ - case XP_RELEX: /* !=, >= <= < > = */ - case XP_UNION: if (xs->xs_c1) cprintf(xcb, " %s ", clicon_int2str(xpopmap, xs->xs_int)); break; + case XP_RELEX: /* !=, >= <= < > = */ + case XP_UNION: /* | */ + if (xs->xs_c1) + cprintf(xcb, "%s", clicon_int2str(xpopmap, xs->xs_int)); + break; case XP_PATHEXPR: /* [19] PathExpr ::= | FilterExpr '/' RelativeLocationPath | FilterExpr '//' RelativeLocationPath diff --git a/test/test_xpath_canonical.sh b/test/test_xpath_canonical.sh index e541234b..d339d31c 100755 --- a/test/test_xpath_canonical.sh +++ b/test/test_xpath_canonical.sh @@ -48,13 +48,13 @@ new "xpath canonical form (other)" expectpart "$($clixon_util_xpath -c -y $ydir -p /i:x/j:y -n i:urn:example:a -n j:urn:example:b)" 0 '/a:x/b:y' '0 : a = "urn:example:a"' '1 : b = "urn:example:b"' new "xpath canonical form predicate 1" -expectpart "$($clixon_util_xpath -c -y $ydir -p "/i:x[j:y='e1']" -n i:urn:example:a -n j:urn:example:b)" 0 "/a:x\[b:y = 'e1'\]" '0 : a = "urn:example:a"' '1 : b = "urn:example:b"' +expectpart "$($clixon_util_xpath -c -y $ydir -p "/i:x[j:y='e1']" -n i:urn:example:a -n j:urn:example:b)" 0 "/a:x\[b:y='e1'\]" '0 : a = "urn:example:a"' '1 : b = "urn:example:b"' new "xpath canonical form predicate self" -expectpart "$($clixon_util_xpath -c -y $ydir -p "/i:x[.='42']" -n i:urn:example:a -n j:urn:example:b)" 0 "/a:x\[. = '42'\]" '0 : a = "urn:example:a"' +expectpart "$($clixon_util_xpath -c -y $ydir -p "/i:x[.='42']" -n i:urn:example:a -n j:urn:example:b)" 0 "/a:x\[.='42'\]" '0 : a = "urn:example:a"' new "xpath canonical form descendants" -expectpart "$($clixon_util_xpath -c -y $ydir -p "//x[.='42']" -n null:urn:example:a -n j:urn:example:b)" 0 "//a:x\[. = '42'\]" '0 : a = "urn:example:a"' +expectpart "$($clixon_util_xpath -c -y $ydir -p "//x[.='42']" -n null:urn:example:a -n j:urn:example:b)" 0 "//a:x\[.='42'\]" '0 : a = "urn:example:a"' new "xpath canonical form (no default should fail)" expectpart "$($clixon_util_xpath -c -y $ydir -p /x/j:y -n i:urn:example:a -n j:urn:example:b 2> /dev/null)" 255 From 86b64c4dd089992c4d796d4a9b591f3683c12c07 Mon Sep 17 00:00:00 2001 From: Alan Yaniger Date: Sun, 25 Jul 2021 09:04:26 +0300 Subject: [PATCH 26/49] split up long function, use safe string handling functions --- apps/restconf/restconf_methods.c | 690 ++++++++++++++++++------------- apps/restconf/restconf_methods.h | 1 + 2 files changed, 409 insertions(+), 282 deletions(-) diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index 3d9630bb..a13ba562 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -581,6 +581,367 @@ api_data_write(clicon_handle h, } /* api_data_write */ #ifdef YANG_PATCH + +char * init_str() +{ + char* s; + s = malloc(TEMP_STR_MALLOC_SIZE); + memset(s, 0, TEMP_STR_MALLOC_SIZE); + return s; +} + +int cpy_str(char *dest, char *src, size_t size) +{ + if (src == NULL) { + return 0; + } + if (dest == NULL) { + init_str(dest); + } + if (size <= 0 || size > TEMP_STR_MALLOC_SIZE) { + return 0; + } + size_t i; + for (i = 0; i < size - 1 && src[i]; i++) { + dest[i] = src[i]; + } + dest[i] = '\0'; + return i; +} + +int cat_str(char *dest, char *src, size_t size) +{ + if (src == NULL) { + return 0; + } + if (dest == NULL) { + init_str(dest); + } + if (size <= 0 || size > TEMP_STR_MALLOC_SIZE) { + return 0; + } + size_t i; + int old_len = strlen(dest); + for (i = 0; i < size - 1 && src[i]; i++) { + dest[i + old_len] = src[i]; + } + dest[i + old_len] = '\0'; + return i; +} + +void free_mem(void *str) +{ + if (str != NULL) + free(str); +} + +int get_xval( + cvec* nsc, + cxobj* xn, + char* val, + cxobj **vec, + size_t veclen, + const char* key + ) +{ + char* tmp_val = NULL; + int ret = xpath_vec(xn, nsc, "%s", &vec, &veclen, key); + if (ret < 0) { + return ret; + } + for (int j = 0; j < veclen; j++) { + cxobj *xn = vec[j]; + tmp_val = xml_body(xn); + } + cpy_str(val, tmp_val, TEMP_STR_MALLOC_SIZE); + return 0; +} + +int do_replace ( + clicon_handle h, + void *req, + int pi, + cvec *qvec, + int pretty, + restconf_media media_out, + ietf_ds_t ds, + char* simple_patch_request_uri, + char* target_val, + int value_vec_len, + cxobj** value_vec, + cxobj * value_vec_tmp, + cxobj *x_simple_patch, + char *patch_header + ) +{ + char *delete_req_uri = init_str(); + if (delete_req_uri == NULL) + return 1; + + if (cpy_str(delete_req_uri, simple_patch_request_uri, TEMP_STR_MALLOC_SIZE) <= 0) + return 1; + + if (cat_str(delete_req_uri, target_val, TEMP_STR_MALLOC_SIZE) <= 0) + return 1; + + // Delete the object with the old values + int ret = api_data_delete(h, req, delete_req_uri, pi, pretty, YANG_DATA_JSON, ds ); + free_mem((void *)delete_req_uri); + if (ret != 0) + return ret; + + // Now insert the object with the new values + char *json_simple_patch = init_str(); + if (json_simple_patch == NULL) + return 1; // goto done; + + for (int k = 0; k < value_vec_len; k++) { + if (value_vec[k] != NULL) { + value_vec_tmp = xml_dup(value_vec[k]); + xml_addsub(x_simple_patch, value_vec_tmp); + } + } + cbuf* cb = cbuf_new(); + xml2json_cbuf(cb, x_simple_patch, 1); + + // Some ugly text processing to get the JSON to match what api_data_post() expects + char *json_simple_patch_tmp = cbuf_get(cb); + int brace_count = 0; + for (int l = 0; l < strlen(json_simple_patch_tmp); l++) { + char c = json_simple_patch_tmp[l]; + if (c == '{') { + brace_count++; + if (brace_count == 2) { + json_simple_patch[strlen(json_simple_patch)] = '['; + } + } + json_simple_patch[strlen(json_simple_patch)] = c; + } + + /* strip /... from end */ + char *post_req_uri = init_str(); + if (post_req_uri == NULL) + return 1; + + int idx = strlen(target_val); + for (int l = strlen(target_val); l>= 0; l--) { + if (target_val[l] == '/') { + idx = l; + break; + } + } + cpy_str(post_req_uri, target_val, idx); + cat_str(simple_patch_request_uri, post_req_uri, TEMP_STR_MALLOC_SIZE); + free_mem((void *)post_req_uri); + for (int l = strlen(json_simple_patch); l>= 0; l--) { + char c = json_simple_patch[l]; + if (c == '}') { + json_simple_patch[l] = ']'; + json_simple_patch[l + 1] = '}'; + break; + } + } + + // Send the POST request + ret = api_data_post(h, req, simple_patch_request_uri, pi, qvec, json_simple_patch, pretty, YANG_DATA_JSON, media_out, ds ); + + free_mem((void *)value_vec_tmp); + free_mem((void *)x_simple_patch); + free_mem((void *)patch_header); + return ret; +} + +int do_create ( + clicon_handle h, + void *req, + int pi, + cvec *qvec, + int pretty, + restconf_media media_out, + ietf_ds_t ds, + char* simple_patch_request_uri, + int value_vec_len, + cxobj** value_vec, + cxobj * value_vec_tmp, + cxobj *x_simple_patch, + char *patch_header + ) +{ + for (int k = 0; k < value_vec_len; k++) { + if (value_vec[k] != NULL) { + value_vec_tmp = xml_dup(value_vec[k]); + xml_addsub(x_simple_patch, value_vec_tmp); + } + } + + // Send the POST request + cbuf* cb = cbuf_new(); + xml2json_cbuf(cb, x_simple_patch, 1); + char *json_simple_patch = cbuf_get(cb); + int ret = api_data_post(h, req, simple_patch_request_uri, pi, qvec, json_simple_patch, pretty, YANG_DATA_JSON, media_out, ds ); + free_mem((void *)value_vec_tmp); + free_mem((void *)x_simple_patch); + free_mem((void *)patch_header); + return ret; +} + +int do_insert ( + clicon_handle h, + void *req, + int pi, + int pretty, + restconf_media media_out, + ietf_ds_t ds, + char* simple_patch_request_uri, + int value_vec_len, + cxobj** value_vec, + cxobj * value_vec_tmp, + cxobj *x_simple_patch, + char *patch_header, + char* where_val, + char* api_path, + char *point_val + ) +{ + char *json_simple_patch = init_str(); + if (json_simple_patch == NULL) + return 1; + + // Loop through the XML, and get each value + for (int k = 0; k < value_vec_len; k++) { + if (value_vec[k] != NULL) { + value_vec_tmp = xml_dup(value_vec[k]); + xml_addsub(x_simple_patch, value_vec_tmp); + } + } + cbuf* cb = cbuf_new(); + xml2json_cbuf(cb, x_simple_patch, 1); + + // Some ugly text processing to get the JSON to match what api_data_post() expects + char *json_simple_patch_tmp = cbuf_get(cb); + int brace_count = 0; + for (int l = 0; l < strlen(json_simple_patch_tmp); l++) { + char c = json_simple_patch_tmp[l]; + if (c == '{') { + brace_count++; + if (brace_count == 2) { + json_simple_patch[strlen(json_simple_patch)] = '['; + } + } + json_simple_patch[strlen(json_simple_patch)] = c; + } + for (int l = strlen(json_simple_patch); l>= 0; l--) { + char c = json_simple_patch[l]; + if (c == '}') { + json_simple_patch[l] = ']'; + json_simple_patch[l + 1] = '}'; + break; + } + } + + // Set the insert attributes + cvec* qvec_tmp = NULL; + qvec_tmp = cvec_new(0); + if (qvec_tmp == NULL) + return 1; + cg_var *cv; + if ((cv = cvec_add(qvec_tmp, CGV_STRING)) == NULL){ + return 1; + } + cv_name_set(cv, "insert"); + cv_string_set(cv, where_val); + char *point_str = init_str(); + if (point_str == NULL) + return 1; + cpy_str(point_str, api_path, TEMP_STR_MALLOC_SIZE); + cat_str(point_str, point_val, TEMP_STR_MALLOC_SIZE); + if ((cv = cvec_add(qvec_tmp, CGV_STRING)) == NULL){ + return 1; + } + cv_name_set(cv, "point"); + cv_string_set(cv, point_str); + + // Send the POST request + int ret = api_data_post(h, req, simple_patch_request_uri, pi, qvec_tmp, json_simple_patch, pretty, YANG_DATA_JSON, media_out, ds ); + if (cb != NULL) + cbuf_free(cb); + free_mem((void *)value_vec_tmp); + free_mem((void *)point_str); + free_mem((void *)json_simple_patch); + free_mem((void *)patch_header); + free_mem((void *)x_simple_patch); + return ret; +} + +int do_merge ( + clicon_handle h, + void *req, + cvec *pcvec, + int pi, + cvec *qvec, + int pretty, + restconf_media media_out, + ietf_ds_t ds, + char* simple_patch_request_uri, + int value_vec_len, + cxobj** value_vec, + cxobj * value_vec_tmp, + cxobj *x_simple_patch, + cxobj *key_xn, + int plain_patch_val, + char *patch_header + ) +{ + int ret = -1; + if (key_xn != NULL) + xml_addsub(x_simple_patch, key_xn); + + char *json_simple_patch = init_str(); + if (json_simple_patch == NULL) + return 1; + + // Loop through the XML, create JSON from each one, and submit a simple patch + for (int k = 0; k < value_vec_len; k++) { + if (value_vec[k] != NULL) { + value_vec_tmp = xml_dup(value_vec[k]); + xml_addsub(x_simple_patch, value_vec_tmp); + } + cbuf* cb = cbuf_new(); + xml2json_cbuf(cb, x_simple_patch, 1); + + // Some ugly text processing to get the JSON to match what api_data_write() expects for a simple patch + char *json_simple_patch_tmp = cbuf_get(cb); + memset(json_simple_patch, 0, TEMP_STR_MALLOC_SIZE); + int brace_count = 0; + for (int l = 0; l < strlen(json_simple_patch_tmp); l++) { + char c = json_simple_patch_tmp[l]; + if (c == '{') { + brace_count++; + if (brace_count == 2) { + json_simple_patch[strlen(json_simple_patch)] = '['; + } + } + json_simple_patch[strlen(json_simple_patch)] = c; + } + for (int l = strlen(json_simple_patch); l>= 0; l--) { + char c = json_simple_patch[l]; + if (c == '}') { + json_simple_patch[l] = ']'; + json_simple_patch[l + 1] = '}'; + break; + } + } + free_mem(value_vec_tmp); + // Send the simple patch request + ret = api_data_write(h, req, simple_patch_request_uri, pcvec, pi, qvec, json_simple_patch, pretty, YANG_DATA_JSON, media_out, plain_patch_val, ds ); + cbuf_free(cb); + } + free_mem(json_simple_patch); + free_mem(patch_header); + free_mem(x_simple_patch); + return ret; +} + /*! YANG PATCH method * @param[in] h Clixon handle * @param[in] req Generic Www handle @@ -627,7 +988,6 @@ api_data_yang_patch(clicon_handle h, cvec *nsc = NULL; yang_bind yb; char *xpath = NULL; - const int temp_str_malloc_size = 5000; char *path_orig_1 = NULL; clicon_debug(1, "%s api_path:\"%s\"", __FUNCTION__, api_path0); @@ -697,84 +1057,58 @@ api_data_yang_patch(clicon_handle h, continue; } } - path_orig_1 = malloc(temp_str_malloc_size); + path_orig_1 = init_str(); if (path_orig_1 == NULL) { goto done; } else { - strcpy(path_orig_1, restconf_uripath(h)); + cpy_str(path_orig_1, restconf_uripath(h), TEMP_STR_MALLOC_SIZE); } // Loop through the edits for (int i = 0; i < veclen; i++) { + cxobj **tmp_vec = NULL; + size_t tmp_veclen = 0; + cxobj *xn = vec[i]; - // Get target - char *target_val = NULL; - cxobj **target_vec = NULL; - size_t target_veclen; - ret = xpath_vec(xn, nsc, "target", &target_vec, &target_veclen); + char *target_val = init_str(); + ret = get_xval(nsc, xn, target_val, tmp_vec, tmp_veclen, "target"); if (ret < 0) { goto done; } - for (int j = 0; j < target_veclen; j++) { - cxobj *target_xn = target_vec[j]; - target_val = xml_body(target_xn); - } - // Get operation - char *op_val = NULL; - cxobj **operation_vec = NULL; - size_t operation_veclen; - ret = xpath_vec(xn, nsc, "operation", &operation_vec, &operation_veclen); + char *op_val = init_str(); + ret = get_xval(nsc, xn, op_val, tmp_vec, tmp_veclen, "operation"); if (ret < 0) { goto done; } - for (int j = 0; j < operation_veclen; j++) { - cxobj *operation_xn = operation_vec[j]; - op_val = xml_body(operation_xn); - } - // Get "point" and "where" for insert operations - char *point_val = NULL; - cxobj **point_vec = NULL; - size_t point_veclen; - if (strcmp(op_val, "insert") == 0) { - ret = xpath_vec(xn, nsc, "point", &point_vec, &point_veclen); + char *point_val = init_str(); + char *where_val = init_str(); + if (strcmp(op_val, "insert") == 0) { // TODO - test + point_val = init_str(); + ret = get_xval(nsc, xn, point_val, tmp_vec, tmp_veclen, "point"); if (ret < 0) { goto done; } - for (int j = 0; j < point_veclen; j++) { - cxobj *point_xn = point_vec[j]; - point_val = xml_body(point_xn); - } - } - char *where_val = NULL; - cxobj **where_vec = NULL; - size_t where_veclen; - if (strcmp(op_val, "insert") == 0) { - ret = xpath_vec(xn, nsc, "where", &where_vec, &where_veclen); + where_val = init_str(); + ret = get_xval(nsc, xn, where_val, tmp_vec, tmp_veclen, "where"); if (ret < 0) { goto done; } - for (int j = 0; j < where_veclen; j++) { - cxobj *where_xn = where_vec[j]; - where_val = xml_body(where_xn); - } } // Construct request URI - char* simple_patch_request_uri = NULL; - simple_patch_request_uri = malloc(temp_str_malloc_size); - strcpy(simple_patch_request_uri, path_orig_1); + char* simple_patch_request_uri = init_str(); + cpy_str(simple_patch_request_uri, path_orig_1, TEMP_STR_MALLOC_SIZE); int plain_patch_val = 0; - char* api_path_target = NULL; - api_path_target = malloc(temp_str_malloc_size); - strcpy(api_path_target, api_path); + char* api_path_target = init_str(); + cpy_str(api_path_target, api_path, TEMP_STR_MALLOC_SIZE); if (strcmp(op_val, "merge") == 0) { plain_patch_val = 1; - strcat(api_path_target, target_val); - strcat(simple_patch_request_uri, target_val); + cat_str(api_path_target, target_val, TEMP_STR_MALLOC_SIZE); + cat_str(simple_patch_request_uri, target_val, TEMP_STR_MALLOC_SIZE); } if (xerr) @@ -809,266 +1143,63 @@ api_data_yang_patch(clicon_handle h, key_xn = key_vec[0]; } - // Get values (for "delete", there are no values) - cxobj **values_vec = NULL; - size_t values_veclen; - xpath_vec(xn, nsc, "value", &values_vec, &values_veclen); + // Get values (for "delete" and "remove", there are no values) + xpath_vec(xn, nsc, "value", &tmp_vec, &tmp_veclen); key_node_id = NULL; // Loop through the values - for (int j = 0; j < values_veclen; j++) { - cxobj *values_xn = values_vec[j]; + for (int j = 0; j < tmp_veclen; j++) { + cxobj *values_xn = tmp_vec[j]; cxobj** values_child_vec = xml_childvec_get(values_xn); if (key_node_id == NULL) key_node_id = xml_name(*values_child_vec); - char *patch_header = NULL; - patch_header = malloc(temp_str_malloc_size); + char *patch_header = init_str(); if (patch_header == NULL) { goto done; } - strcpy(patch_header, modname); - strcat(patch_header, ":"); - strcat(patch_header, key_node_id); + cpy_str(patch_header, modname, TEMP_STR_MALLOC_SIZE); + cat_str(patch_header, ":", TEMP_STR_MALLOC_SIZE); + cat_str(patch_header, key_node_id, TEMP_STR_MALLOC_SIZE); cxobj *x_simple_patch = xml_new(patch_header, NULL, CX_ELMNT); if (x_simple_patch == NULL) goto done; int value_vec_len = xml_child_nr(*values_child_vec); cxobj** value_vec = xml_childvec_get(*values_child_vec); cxobj * value_vec_tmp = NULL; - // For "replace", delete the item and then POST it // TODO - in an ordered list, insert it into its original position if (strcmp(op_val,"replace") == 0) { - char *delete_req_uri = malloc(temp_str_malloc_size); - if (delete_req_uri == NULL) - break; - - strcpy(delete_req_uri, simple_patch_request_uri); - strcat(delete_req_uri, target_val); - - // Delete the object with the old values - ret = api_data_delete(h, req, delete_req_uri, pi, pretty, YANG_DATA_JSON, ds ); - free(delete_req_uri); - - // Now insert the object with the new values - char *json_simple_patch = malloc(temp_str_malloc_size); - if (json_simple_patch == NULL) + ret = do_replace(h, req, pi, qvec, pretty, media_out, ds, simple_patch_request_uri, target_val, value_vec_len, value_vec, value_vec_tmp, x_simple_patch, patch_header); + if (ret != 0) { goto done; - memset(json_simple_patch, 0, temp_str_malloc_size); - - for (int k = 0; k < value_vec_len; k++) { - if (value_vec[k] != NULL) { - value_vec_tmp = xml_dup(value_vec[k]); - xml_addsub(x_simple_patch, value_vec_tmp); - } } - cbuf* cb = cbuf_new(); - xml2json_cbuf(cb, x_simple_patch, 1); - - // Some ugly text processing to get the JSON to match what api_data_post() expects - char *json_simple_patch_tmp = cbuf_get(cb); - int brace_count = 0; - for (int l = 0; l < strlen(json_simple_patch_tmp); l++) { - char c = json_simple_patch_tmp[l]; - if (c == '{') { - brace_count++; - if (brace_count == 2) { - json_simple_patch[strlen(json_simple_patch)] = '['; - } - } - json_simple_patch[strlen(json_simple_patch)] = c; - } - /* strip /... from end */ - char *post_req_uri = malloc(temp_str_malloc_size); - if (post_req_uri == NULL) - break; - memset(post_req_uri, 0, temp_str_malloc_size); - if (post_req_uri == NULL) - break; - int idx = strlen(target_val); - for (int l = strlen(target_val); l>= 0; l--) { - if (target_val[l] == '/') { - idx = l; - break; - } - } - strncpy(post_req_uri, target_val, idx); - strcat(simple_patch_request_uri, post_req_uri); - free(post_req_uri); - for (int l = strlen(json_simple_patch); l>= 0; l--) { - char c = json_simple_patch[l]; - if (c == '}') { - json_simple_patch[l] = ']'; - json_simple_patch[l + 1] = '}'; - break; - } - } - - // Send the POST request - ret = api_data_post(h, req, simple_patch_request_uri, pi, qvec, json_simple_patch, pretty, YANG_DATA_JSON, media_out, ds ); - if (value_vec_tmp != NULL) - free(value_vec_tmp); - free(x_simple_patch); - free(patch_header); // NULL check was already done before - if (ret != 0) - goto done; - break; } - // For "create", put all the data values into a single POST request if (strcmp(op_val,"create") == 0) { - for (int k = 0; k < value_vec_len; k++) { - if (value_vec[k] != NULL) { - value_vec_tmp = xml_dup(value_vec[k]); - xml_addsub(x_simple_patch, value_vec_tmp); - } - } - - // Send the POST request - cbuf* cb = cbuf_new(); - xml2json_cbuf(cb, x_simple_patch, 1); - char *json_simple_patch = cbuf_get(cb); - ret = api_data_post(h, req, simple_patch_request_uri, pi, qvec, json_simple_patch, pretty, YANG_DATA_JSON, media_out, ds ); - if (value_vec_tmp != NULL) - free(value_vec_tmp); - free(x_simple_patch); - free(patch_header); // NULL check was already done before - if (ret != 0) + ret = do_create(h, req, pi, qvec, pretty, media_out, ds, simple_patch_request_uri, value_vec_len, value_vec, value_vec_tmp, x_simple_patch, patch_header); + if (ret != 0) { goto done; - break; + } } - // For "insert", make a api_data_post request + // For "insert", make a api_data_post request // TODO - test if (strcmp(op_val, "insert") == 0) { - char *json_simple_patch = malloc(temp_str_malloc_size); - if (json_simple_patch == NULL) - goto done; - memset(json_simple_patch, 0, temp_str_malloc_size); - - // Loop through the XML, and get each value - for (int k = 0; k < value_vec_len; k++) { - if (value_vec[k] != NULL) { - value_vec_tmp = xml_dup(value_vec[k]); - xml_addsub(x_simple_patch, value_vec_tmp); - } - } - cbuf* cb = cbuf_new(); - xml2json_cbuf(cb, x_simple_patch, 1); - - // Some ugly text processing to get the JSON to match what api_data_post() expects - char *json_simple_patch_tmp = cbuf_get(cb); - int brace_count = 0; - for (int l = 0; l < strlen(json_simple_patch_tmp); l++) { - char c = json_simple_patch_tmp[l]; - if (c == '{') { - brace_count++; - if (brace_count == 2) { - json_simple_patch[strlen(json_simple_patch)] = '['; - } - } - json_simple_patch[strlen(json_simple_patch)] = c; - } - for (int l = strlen(json_simple_patch); l>= 0; l--) { - char c = json_simple_patch[l]; - if (c == '}') { - json_simple_patch[l] = ']'; - json_simple_patch[l + 1] = '}'; - break; - } - } - - // Set the insert attributes - cvec* qvec_tmp = NULL; - qvec_tmp = cvec_new(0); - if (qvec_tmp == NULL) - goto done; - cg_var *cv; - if ((cv = cvec_add(qvec_tmp, CGV_STRING)) == NULL){ + ret = do_insert(h, req, pi, pretty, media_out, ds, simple_patch_request_uri, value_vec_len, value_vec, value_vec_tmp, x_simple_patch, patch_header, where_val, api_path, point_val); + if (ret != 0) { goto done; } - cv_name_set(cv, "insert"); - cv_string_set(cv, where_val); - char *point_str = malloc(temp_str_malloc_size); - if (point_str == NULL) - goto done; - memset(point_str, 0, temp_str_malloc_size); - strcpy(point_str, api_path); - strcat(point_str, point_val); - if ((cv = cvec_add(qvec_tmp, CGV_STRING)) == NULL){ - goto done; - } - cv_name_set(cv, "point"); - cv_string_set(cv, point_str); - - // Send the POST request - ret = api_data_post(h, req, simple_patch_request_uri, pi, qvec_tmp, json_simple_patch, pretty, YANG_DATA_JSON, media_out, ds ); - if (cb != NULL) - cbuf_free(cb); - if (value_vec_tmp != NULL) - free(value_vec_tmp); - free(point_str); // NULL check was already done above - free(json_simple_patch); // NULL check was already done above - free(patch_header); // NULL check was already done before - if (x_simple_patch != NULL) - free(x_simple_patch); - break; } - // For merge", make single simple patch requests for each value if (strcmp(op_val,"merge") == 0) { - if (key_xn != NULL) - xml_addsub(x_simple_patch, key_xn); - - char *json_simple_patch = malloc(temp_str_malloc_size); - if (json_simple_patch == NULL) + ret = do_merge(h, req, pcvec, pi, qvec, pretty, media_out, ds, simple_patch_request_uri, value_vec_len, value_vec, value_vec_tmp, x_simple_patch, key_xn, plain_patch_val, patch_header); + if (ret != 0) { goto done; - - // Loop through the XML, create JSON from each one, and submit a simple patch - for (int k = 0; k < value_vec_len; k++) { - if (value_vec[k] != NULL) { - value_vec_tmp = xml_dup(value_vec[k]); - xml_addsub(x_simple_patch, value_vec_tmp); - } - cbuf* cb = cbuf_new(); - xml2json_cbuf(cb, x_simple_patch, 1); - - // Some ugly text processing to get the JSON to match what api_data_write() expects for a simple patch - char *json_simple_patch_tmp = cbuf_get(cb); - memset(json_simple_patch, 0, temp_str_malloc_size); - int brace_count = 0; - for (int l = 0; l < strlen(json_simple_patch_tmp); l++) { - char c = json_simple_patch_tmp[l]; - if (c == '{') { - brace_count++; - if (brace_count == 2) { - json_simple_patch[strlen(json_simple_patch)] = '['; - } - } - json_simple_patch[strlen(json_simple_patch)] = c; - } - for (int l = strlen(json_simple_patch); l>= 0; l--) { - char c = json_simple_patch[l]; - if (c == '}') { - json_simple_patch[l] = ']'; - json_simple_patch[l + 1] = '}'; - break; - } - } - if (value_vec_tmp != NULL) - free(value_vec_tmp); - // Send the simple patch request - ret = api_data_write(h, req, simple_patch_request_uri, pcvec, pi, qvec, json_simple_patch, pretty, YANG_DATA_JSON, media_out, plain_patch_val, ds ); - cbuf_free(cb); } - free(json_simple_patch); // NULL check was already done above - free(patch_header); // NULL check was already done before - if (x_simple_patch != NULL) - free(x_simple_patch); } } if ((strcmp(op_val, "delete") == 0) || (strcmp(op_val, "remove") == 0)) { - strcat(simple_patch_request_uri, target_val); + cat_str(simple_patch_request_uri, target_val, TEMP_STR_MALLOC_SIZE); if (strcmp(op_val, "delete") == 0) { // TODO - send error } else { @@ -1076,20 +1207,15 @@ api_data_yang_patch(clicon_handle h, } api_data_delete(h, req, simple_patch_request_uri, pi, pretty, YANG_DATA_JSON, ds); } - if (simple_patch_request_uri) - free(simple_patch_request_uri); - if (api_path_target) - free(api_path_target); + free_mem((void *)simple_patch_request_uri); + free_mem((void *)api_path_target); } ok: retval = 0; done: - if (path_orig_1 != NULL) - free(path_orig_1); - if (vec) - free(vec); - if (xpath) - free(xpath); + free_mem((void *)path_orig_1); + free_mem((void *)vec); + free_mem((void *)xpath); if (nsc) xml_nsctx_free(nsc); if (xret) @@ -1194,7 +1320,7 @@ api_data_patch(clicon_handle h, ietf_ds_t ds) { restconf_media media_in; - int ret; + int ret = -1; media_in = restconf_content_type(h); switch (media_in){ diff --git a/apps/restconf/restconf_methods.h b/apps/restconf/restconf_methods.h index 975b63eb..95943ba6 100644 --- a/apps/restconf/restconf_methods.h +++ b/apps/restconf/restconf_methods.h @@ -39,6 +39,7 @@ #ifndef _RESTCONF_METHODS_H_ #define _RESTCONF_METHODS_H_ +#define TEMP_STR_MALLOC_SIZE 5000 /* * Prototypes */ From 659aaac5c6e0d0556a54ebe3b404c1ca27e16f23 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sun, 25 Jul 2021 22:21:37 +0200 Subject: [PATCH 27/49] - cli set debug vars - fixed: restconf native evhtp appended indata to old data --- apps/cli/cli_common.c | 6 +++--- apps/restconf/restconf_evhtp.c | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c index 2a3c74a9..4f1c6fdb 100644 --- a/apps/cli/cli_common.c +++ b/apps/cli/cli_common.c @@ -448,7 +448,7 @@ cli_debug_cli(clicon_handle h, cg_var *cv; int level; - if ((cv = cvec_find(vars, "level")) == NULL){ + if ((cv = cvec_find_var(vars, "level")) == NULL){ if (cvec_len(argv) != 1){ clicon_err(OE_PLUGIN, EINVAL, "Requires either label var or single arg: 0|1"); goto done; @@ -479,7 +479,7 @@ cli_debug_backend(clicon_handle h, cg_var *cv; int level; - if ((cv = cvec_find(vars, "level")) == NULL){ + if ((cv = cvec_find_var(vars, "level")) == NULL){ if (cvec_len(argv) != 1){ clicon_err(OE_PLUGIN, EINVAL, "Requires either label var or single arg: 0|1"); goto done; @@ -513,7 +513,7 @@ cli_debug_restconf(clicon_handle h, cg_var *cv; int level; - if ((cv = cvec_find(vars, "level")) == NULL){ + if ((cv = cvec_find_var(vars, "level")) == NULL){ if (cvec_len(argv) != 1){ clicon_err(OE_PLUGIN, EINVAL, "Requires either label var or single arg: 0|1"); goto done; diff --git a/apps/restconf/restconf_evhtp.c b/apps/restconf/restconf_evhtp.c index dfb43597..44983de8 100644 --- a/apps/restconf/restconf_evhtp.c +++ b/apps/restconf/restconf_evhtp.c @@ -529,6 +529,7 @@ restconf_path_root(evhtp_request_t *req, clicon_err(OE_CFG, errno, "evbuffer_pullup"); goto done; } + cbuf_reset(sd->sd_indata); /* Note the pullup may not be null-terminated */ cbuf_append_buf(sd->sd_indata, buf, len); } From 128764a57089910b69df7f7fa97a2870737d1e71 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Mon, 26 Jul 2021 12:20:06 +0200 Subject: [PATCH 28/49] - Added yang patch test: test_restconf_yang_patch.sh as placeholder for rfc 8072 tests - Added patch-xml as valid media --- apps/restconf/restconf_methods.c | 7 +- test/test_restconf_yang_patch.sh | 205 +++++++++++++++++++++++++++++++ 2 files changed, 208 insertions(+), 4 deletions(-) create mode 100755 test/test_restconf_yang_patch.sh diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index a13ba562..141451d8 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -1329,16 +1329,15 @@ api_data_patch(clicon_handle h, ret = api_data_write(h, req, api_path0, pcvec, pi, qvec, data, pretty, media_in, media_out, 1, ds); break; - case YANG_PATCH_XML: - ret = restconf_notimplemented(h, req, pretty, media_out); - break; case YANG_PATCH_JSON: /* RFC 8072 patch */ + case YANG_PATCH_XML: #ifdef YANG_PATCH ret = api_data_yang_patch(h, req, api_path0, pcvec, pi, qvec, data, pretty, media_out, ds); #else - ret = restconf_unsupported_media(h, req, pretty, media_out); + ret = restconf_notimplemented(h, req, pretty, media_out); #endif + break; break; default: ret = restconf_unsupported_media(h, req, pretty, media_out); diff --git a/test/test_restconf_yang_patch.sh b/test/test_restconf_yang_patch.sh new file mode 100755 index 00000000..6d91704a --- /dev/null +++ b/test/test_restconf_yang_patch.sh @@ -0,0 +1,205 @@ +#!/usr/bin/env bash +# Restconf RFC8072 yang patch +# XXX enable YANG_PACTH in include/clixon_custom.h to run this test +# Use nacm module in example/main/example_restconf.c hardcoded to +# andy:bar and wilma:bar + +# Magic line must be first in script (see README.md) +s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi + +echo "...skipped: YANG_PATCH NYI" +if [ "$s" = $0 ]; then exit 0; else return 0; fi + +APPNAME=example + +cfg=$dir/conf.xml +startupdb=$dir/startup_db +fjukebox=$dir/example-jukebox.yang + +# Define default restconfig config: RESTCONFIG +RESTCONFIG=$(restconf_config user false) + +cat < $cfg + + $cfg + /usr/local/share/clixon + $IETFRFC + $dir + /usr/local/var/$APPNAME/$APPNAME.sock + ietf-netconf:startup + /usr/local/lib/$APPNAME/restconf + $dir/restconf.pidfile + $dir + internal + true + $RESTCONFIG + +EOF + +NACM0=" + true + deny + deny + permit + + + admin + andy + + + limited + wilma + + + + admin + admin + + permit-all + * + * + permit + + Allow the 'admin' group complete access to all operations and data. + + + + + limited + limited + + limit-jukebox + jukebox-example + read create delete + deny + + + +" + +cat< $startupdb +<${DATASTORE_TOP}> + $NACM0 + +EOF + +# An extra testmodule that includes nacm +cat < $dir/example-system.yang + module example-system { + namespace "http://example.com/ns/example-system"; + prefix "ex"; + import ietf-netconf-acm { + prefix nacm; + } + container system { + leaf enable-jukebox-streaming { + type boolean; + } + leaf extraleaf { + type string; + } + } + } +EOF + +# Common Jukebox spec (fjukebox must be set) +. ./jukebox.sh + +new "test params: -s startup -f $cfg" +if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -zf $cfg + if [ $? -ne 0 ]; then + err + fi + sudo pkill -f clixon_backend # to be sure + + new "start backend -s startup -f $cfg" + start_backend -s startup -f $cfg +fi + +new "wait backend" +wait_backend + +if [ $RC -ne 0 ]; then + new "kill old restconf daemon" + stop_restconf_pre + + new "start restconf daemon" + start_restconf -f $cfg +fi + +new "wait restconf" +wait_restconf + +# RFC 8072 A.1.1 +REQ=' + add-songs-patch + + edit1 + create + /song=Bridge%20Burning + + + Bridge Burning + /media/bridge_burning.mp3 + MP3 + 288 + + + + + edit2 + create + /song=Rope + + + Rope + /media/rope.mp3 + MP3 + 259 + + + + + edit3 + create + /song=Dear%20Rosemary + + + Dear Rosemary + /media/dear_rosemary.mp3 + MP3 + 269 + + + + ' + +new "RFC 8072 A.1.1 Add resources: Error." +expectpart "$(curl -u andy:bar $CURLOPTS -X PATCH -H 'Content-Type: application/yang-patch+xml' -H 'Accept: application/yang-data+xml' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters/album=Wasting%20Light -d "$REQ")" 0 "HTTP/$HVER 409" + + +if [ $RC -ne 0 ]; then + new "Kill restconf daemon" + stop_restconf +fi + +if [ $BE -ne 0 ]; then + new "Kill backend" + # Check if premature kill + pid=$(pgrep -u root -f clixon_backend) + if [ -z "$pid" ]; then + err "backend already dead" + fi + # kill backend + stop_backend -f $cfg +fi + +# Set by restconf_config +unset RESTCONFIG + +rm -rf $dir + +new "endtest" +endtest From 5de6d56822afb4ef0858b0de4c9dd0c318c730d4 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Tue, 27 Jul 2021 09:39:44 +0200 Subject: [PATCH 29/49] * Fixed: SEGV in clixon_netconf_lib functions from internal errors including validation. * Check xerr argument both before and after call on netconf lib functions --- CHANGELOG.md | 2 ++ apps/backend/backend_client.c | 2 +- apps/backend/backend_commit.c | 33 ++++++++++++++++------ lib/clixon/clixon_netconf_lib.h | 3 +- lib/src/clixon_datastore_read.c | 2 +- lib/src/clixon_json.c | 2 +- lib/src/clixon_netconf_lib.c | 50 ++++++++++++++++++++++++++++++++- lib/src/clixon_validate.c | 46 +++++++++++++++--------------- lib/src/clixon_yang_module.c | 2 +- 9 files changed, 103 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 919d820f..4c89efc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,8 @@ Developers may need to change their code ### Corrected Bugs +* Fixed: SEGV in clixon_netconf_lib functions from internal errors including validation. + * Check xerr argument both before and after call on netconf lib functions * Fixed: RFC 8040 yang-data extension allows non-key lists * Added YANG_FLAG_NOKEY as exception to mandatory key lists * Fixed: mandatory leaf in a uses statement caused abort diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index 6244d200..be7b9377 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -238,7 +238,7 @@ client_get_streams(clicon_handle h, cprintf(cb,"", top); if (clixon_xml_parse_string(cbuf_get(cb), YB_MODULE, yspec, &x, NULL) < 0){ - if (netconf_operation_failed_xml(xret, "protocol", clicon_err_reason)< 0) + if (xret && netconf_operation_failed_xml(xret, "protocol", clicon_err_reason)< 0) goto done; goto fail; } diff --git a/apps/backend/backend_commit.c b/apps/backend/backend_commit.c index 03e318cf..8abcbcbc 100644 --- a/apps/backend/backend_commit.c +++ b/apps/backend/backend_commit.c @@ -132,7 +132,7 @@ generic_validate(clicon_handle h, cprintf(cb, "Mandatory variable of %s in module %s", xml_parent(x1)?xml_name(xml_parent(x1)):"", yang_argument_get(ys_module(ys))); - if (netconf_missing_element_xml(xret, "protocol", xml_name(x1), cbuf_get(cb)) < 0) + if (xret && netconf_missing_element_xml(xret, "protocol", xml_name(x1), cbuf_get(cb)) < 0) goto done; goto fail; } @@ -480,9 +480,9 @@ startup_commit(clicon_handle h, * and call application callback validations. * @param[in] h Clicon handle * @param[in] candidate The candidate database. The wanted backend state - * @param[out] xret Error XML tree. Free with xml_free after use + * @param[out] xret Error XML tree, if retval is 0. Free with xml_free after use * @retval -1 Error - or validation failed (but cbret not set) - * @retval 0 Validation failed (with cbret set) + * @retval 0 Validation failed (with xret set) * @retval 1 Validation OK * @note Need to differentiate between error and validation fail * (only done for generic_validate) @@ -505,16 +505,19 @@ validate_common(clicon_handle h, goto done; } /* This is the state we are going to */ - if (xmldb_get0(h, db, YB_MODULE, NULL, "/", 0, &td->td_target, NULL, NULL) < 0) + if ((ret = xmldb_get0(h, db, YB_MODULE, NULL, "/", 0, &td->td_target, NULL, xret)) < 0) goto done; - + if (ret == 0) + goto fail; /* Clear flags xpath for get */ xml_apply0(td->td_target, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)(XML_FLAG_MARK|XML_FLAG_CHANGE)); /* 2. Parse xml trees * This is the state we are going from */ - if (xmldb_get0(h, "running", YB_MODULE, NULL, "/", 0, &td->td_src, NULL, NULL) < 0) + if ((ret = xmldb_get0(h, "running", YB_MODULE, NULL, "/", 0, &td->td_src, NULL, xret)) < 0) goto done; + if (ret == 0) + goto fail; /* Clear flags xpath for get */ xml_apply0(td->td_src, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)(XML_FLAG_MARK|XML_FLAG_CHANGE)); @@ -606,11 +609,23 @@ candidate_validate(clicon_handle h, if ((td = transaction_new()) == NULL) goto done; /* Common steps (with commit) */ - if ((ret = validate_common(h, db, td, &xret)) < 1){ + if ((ret = validate_common(h, db, td, &xret)) < 0){ /* A little complex due to several sources of validation fails or errors. * (1) xerr is set -> translate to cbret; (2) cbret set use that; otherwise - * use clicon_err. */ - if (xret && clicon_xml2cbuf(cbret, xret, 0, 0, -1) < 0) + * use clicon_err. + * TODO: -1 return should be fatal error, not failed validation + */ + if (!cbuf_len(cbret) && + netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) + goto done; + goto fail; + } + if (ret == 0){ + if (xret == NULL){ + clicon_err(OE_CFG, EINVAL, "xret is NULL"); + goto done; + } + if (clicon_xml2cbuf(cbret, xret, 0, 0, -1) < 0) goto done; if (!cbuf_len(cbret) && netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) diff --git a/lib/clixon/clixon_netconf_lib.h b/lib/clixon/clixon_netconf_lib.h index f42cf33d..40b9bc96 100644 --- a/lib/clixon/clixon_netconf_lib.h +++ b/lib/clixon/clixon_netconf_lib.h @@ -127,8 +127,7 @@ int netconf_operation_failed(cbuf *cb, char *type, char *message); int netconf_operation_failed_xml(cxobj **xret, char *type, char *message); int netconf_malformed_message(cbuf *cb, char *message); int netconf_malformed_message_xml(cxobj **xret, char *message); -int netconf_data_not_unique_xml(cxobj **xret, cxobj *x, cvec *cvk); - +int netconf_data_not_unique_xml(cxobj **xret, cxobj *x, cvec *cvk); int netconf_minmax_elements_xml(cxobj **xret, cxobj *xp, char *name, int max); int netconf_trymerge(cxobj *x, yang_stmt *yspec, cxobj **xret); int netconf_module_features(clicon_handle h); diff --git a/lib/src/clixon_datastore_read.c b/lib/src/clixon_datastore_read.c index 0413d2c6..c4cf0293 100644 --- a/lib/src/clixon_datastore_read.c +++ b/lib/src/clixon_datastore_read.c @@ -557,7 +557,7 @@ xmldb_readfile(clicon_handle h, } cprintf(cberr, "Internal error: %s", clicon_err_reason); clicon_err_reset(); - if (netconf_operation_failed_xml(xerr, "application", cbuf_get(cberr))< 0) + if (xerr && netconf_operation_failed_xml(xerr, "application", cbuf_get(cberr))< 0) goto done; cbuf_free(cberr); goto fail; diff --git a/lib/src/clixon_json.c b/lib/src/clixon_json.c index b27733b4..caf79351 100644 --- a/lib/src/clixon_json.c +++ b/lib/src/clixon_json.c @@ -1238,7 +1238,7 @@ _json_parse(char *str, goto done; } cprintf(cberr, "Top-level JSON object %s is not qualified with namespace which is a MUST according to RFC 7951", xml_name(x)); - if (netconf_malformed_message_xml(xerr, cbuf_get(cberr)) < 0) + if (xerr && netconf_malformed_message_xml(xerr, cbuf_get(cberr)) < 0) goto done; goto fail; } diff --git a/lib/src/clixon_netconf_lib.c b/lib/src/clixon_netconf_lib.c index febf5b93..8dadfbd5 100644 --- a/lib/src/clixon_netconf_lib.c +++ b/lib/src/clixon_netconf_lib.c @@ -131,6 +131,10 @@ netconf_invalid_value_xml(cxobj **xret, char *encstr = NULL; cxobj *xa; + if (xret == NULL){ + clicon_err(OE_NETCONF, EINVAL, "xret is NULL"); + goto done; + } if (*xret == NULL){ if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL) goto done; @@ -248,6 +252,10 @@ netconf_missing_attribute_xml(cxobj **xret, char *encstr = NULL; cxobj *xa; + if (xret == NULL){ + clicon_err(OE_NETCONF, EINVAL, "xret is NULL"); + goto done; + } if (*xret == NULL){ if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL) goto done; @@ -356,6 +364,10 @@ netconf_bad_attribute_xml(cxobj **xret, char *encstr = NULL; cxobj *xa; + if (xret == NULL){ + clicon_err(OE_NETCONF, EINVAL, "xret is NULL"); + goto done; + } if (*xret == NULL){ if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL) goto done; @@ -449,6 +461,10 @@ netconf_common_xml(cxobj **xret, char *encstr = NULL; cxobj *xa; + if (xret == NULL){ + clicon_err(OE_NETCONF, EINVAL, "xret is NULL"); + goto done; + } if (*xret == NULL){ if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL) goto done; @@ -705,6 +721,10 @@ netconf_access_denied_xml(cxobj **xret, char *encstr = NULL; cxobj *xa; + if (xret == NULL){ + clicon_err(OE_NETCONF, EINVAL, "xret is NULL"); + goto done; + } if (*xret == NULL){ if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL) goto done; @@ -941,6 +961,10 @@ netconf_data_missing_xml(cxobj **xret, cxobj *xerr; cxobj *xa; + if (xret == NULL){ + clicon_err(OE_NETCONF, EINVAL, "xret is NULL"); + goto done; + } if (*xret == NULL){ if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL) goto done; @@ -1005,6 +1029,10 @@ netconf_operation_not_supported_xml(cxobj **xret, char *encstr = NULL; cxobj *xa; + if (xret == NULL){ + clicon_err(OE_NETCONF, EINVAL, "xret is NULL"); + goto done; + } if (*xret == NULL){ if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL) goto done; @@ -1116,6 +1144,10 @@ netconf_operation_failed_xml(cxobj **xret, char *encstr = NULL; cxobj *xa; + if (xret == NULL){ + clicon_err(OE_NETCONF, EINVAL, "xret is NULL"); + goto done; + } if (*xret == NULL){ if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL) goto done; @@ -1200,6 +1232,10 @@ netconf_malformed_message_xml(cxobj **xret, char *encstr = NULL; cxobj *xa; + if (xret == NULL){ + clicon_err(OE_NETCONF, EINVAL, "xret is NULL"); + goto done; + } if (*xret == NULL){ if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL) goto done; @@ -1250,8 +1286,12 @@ netconf_data_not_unique_xml(cxobj **xret, cxobj *xerr; cxobj *xinfo; cbuf *cb = NULL; - cxobj *xa; + cxobj *xa; + if (xret == NULL){ + clicon_err(OE_NETCONF, EINVAL, "xret is NULL"); + goto done; + } if (*xret == NULL){ if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL) goto done; @@ -1316,6 +1356,10 @@ netconf_minmax_elements_xml(cxobj **xret, cbuf *cb = NULL; cxobj *xa; + if (xret == NULL){ + clicon_err(OE_NETCONF, EINVAL, "xret is NULL"); + goto done; + } if (*xret == NULL){ if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL) goto done; @@ -1373,6 +1417,10 @@ netconf_trymerge(cxobj *x, char *reason = NULL; cxobj *xc; + if (xret == NULL){ + clicon_err(OE_NETCONF, EINVAL, "xret is NULL"); + goto done; + } if (*xret == NULL){ if ((*xret = xml_dup(x)) == NULL) goto done; diff --git a/lib/src/clixon_validate.c b/lib/src/clixon_validate.c index 274d339a..802330c8 100644 --- a/lib/src/clixon_validate.c +++ b/lib/src/clixon_validate.c @@ -118,7 +118,7 @@ validate_leafref(cxobj *xt, if ((leafrefbody = xml_body(xt)) == NULL) goto ok; if ((ypath = yang_find(ytype, Y_PATH, NULL)) == NULL){ - if (netconf_missing_element_xml(xret, "application", yang_argument_get(ytype), "Leafref requires path statement") < 0) + if (xret && netconf_missing_element_xml(xret, "application", yang_argument_get(ytype), "Leafref requires path statement") < 0) goto done; goto fail; } @@ -141,7 +141,7 @@ validate_leafref(cxobj *xt, goto done; } cprintf(cberr, "Leafref validation failed: No leaf %s matching path %s", leafrefbody, path); - if (netconf_bad_element_xml(xret, "application", leafrefbody, cbuf_get(cberr)) < 0) + if (xret && netconf_bad_element_xml(xret, "application", leafrefbody, cbuf_get(cberr)) < 0) goto done; goto fail; } @@ -211,7 +211,7 @@ validate_identityref(cxobj *xt, /* Get idref value. Then see if this value is derived from ytype. */ if ((node = xml_body(xt)) == NULL){ /* It may not be empty */ - if (netconf_bad_element_xml(xret, "application", xml_name(xt), "Identityref should not be empty") < 0) + if (xret && netconf_bad_element_xml(xret, "application", xml_name(xt), "Identityref should not be empty") < 0) goto done; goto fail; } @@ -219,13 +219,13 @@ validate_identityref(cxobj *xt, goto done; /* This is the type's base reference */ if ((ybaseref = yang_find(ytype, Y_BASE, NULL)) == NULL){ - if (netconf_missing_element_xml(xret, "application", yang_argument_get(ytype), "Identityref validation failed, no base") < 0) + if (xret && netconf_missing_element_xml(xret, "application", yang_argument_get(ytype), "Identityref validation failed, no base") < 0) goto done; goto fail; } /* This is the actual base identity */ if ((ybaseid = yang_find_identity(ybaseref, yang_argument_get(ybaseref))) == NULL){ - if (netconf_missing_element_xml(xret, "application", yang_argument_get(ybaseref), "Identityref validation failed, no base identity") < 0) + if (xret && netconf_missing_element_xml(xret, "application", yang_argument_get(ybaseref), "Identityref validation failed, no base identity") < 0) goto done; goto fail; } @@ -254,7 +254,7 @@ validate_identityref(cxobj *xt, if (cvec_find(idrefvec, idref) == NULL){ cprintf(cberr, "Identityref validation failed, %s not derived from %s", node, yang_argument_get(ybaseid)); - if (netconf_operation_failed_xml(xret, "application", cbuf_get(cberr)) < 0) + if (xret && netconf_operation_failed_xml(xret, "application", cbuf_get(cberr)) < 0) goto done; goto fail; } @@ -337,7 +337,7 @@ xml_yang_validate_rpc(clicon_handle h, goto done; /* Only accept resolved NETCONF base namespace */ if (namespace == NULL || strcmp(namespace, NETCONF_BASE_NAMESPACE) != 0){ - if (netconf_unknown_namespace_xml(xret, "protocol", rpcprefix, "No appropriate namespace associated with prefix")< 0) + if (xret && netconf_unknown_namespace_xml(xret, "protocol", rpcprefix, "No appropriate namespace associated with prefix")< 0) goto done; goto fail; } @@ -345,7 +345,7 @@ xml_yang_validate_rpc(clicon_handle h, /* xn is name of rpc, ie */ while ((xn = xml_child_each(xrpc, xn, CX_ELMNT)) != NULL) { if ((yn = xml_spec(xn)) == NULL){ - if (netconf_unknown_element_xml(xret, "application", xml_name(xn), NULL) < 0) + if (xret && netconf_unknown_element_xml(xret, "application", xml_name(xn), NULL) < 0) goto done; goto fail; } @@ -434,7 +434,7 @@ check_choice(cxobj *xt, continue; /* not choice */ break; } - if (netconf_bad_element_xml(xret, "application", xml_name(x), "Element in choice statement already exists") < 0) + if (xret && netconf_bad_element_xml(xret, "application", xml_name(x), "Element in choice statement already exists") < 0) goto done; goto fail; } /* while */ @@ -487,7 +487,7 @@ check_list_key(cxobj *xt, while ((cvi = cvec_each(cvk, cvi)) != NULL) { keyname = cv_string_get(cvi); if (xml_find_type(xt, NULL, keyname, CX_ELMNT) == NULL){ - if (netconf_missing_element_xml(xret, "application", keyname, "Mandatory key") < 0) + if (xret && netconf_missing_element_xml(xret, "application", keyname, "Mandatory key") < 0) goto done; goto fail; } @@ -557,7 +557,7 @@ check_mandatory(cxobj *xt, goto done; } cprintf(cb, "Mandatory variable of %s in module %s", xml_name(xt), yang_argument_get(ys_module(yc))); - if (netconf_missing_element_xml(xret, "application", yang_argument_get(yc), cbuf_get(cb)) < 0) + if (xret && netconf_missing_element_xml(xret, "application", yang_argument_get(yc), cbuf_get(cb)) < 0) goto done; goto fail; } @@ -574,7 +574,7 @@ check_mandatory(cxobj *xt, if (x == NULL){ /* @see RFC7950: 15.6 Error Message for Data That Violates * a Mandatory "choice" Statement */ - if (netconf_data_missing_xml(xret, yang_argument_get(yc), NULL) < 0) + if (xret && netconf_data_missing_xml(xret, yang_argument_get(yc), NULL) < 0) goto done; goto fail; } @@ -707,7 +707,7 @@ check_unique_list(cxobj *x, if (cvi==NULL){ /* Last element (i) is newly inserted, see if it is already there */ if (check_insert_duplicate(vec, i, vlen, sorted) < 0){ - if (netconf_data_not_unique_xml(xret, x, cvk) < 0) + if (xret && netconf_data_not_unique_xml(xret, x, cvk) < 0) goto done; goto fail; } @@ -751,7 +751,7 @@ check_min_max(cxobj *xp, if ((ymin = yang_find(y, Y_MIN_ELEMENTS, NULL)) != NULL){ cv = yang_cv_get(ymin); if (nr < cv_uint32_get(cv)){ - if (netconf_minmax_elements_xml(xret, xp, yang_argument_get(y), 0) < 0) + if (xret && netconf_minmax_elements_xml(xret, xp, yang_argument_get(y), 0) < 0) goto done; goto fail; } @@ -760,7 +760,7 @@ check_min_max(cxobj *xp, cv = yang_cv_get(ymax); if (cv_uint32_get(cv) > 0 && /* 0 means unbounded */ nr > cv_uint32_get(cv)){ - if (netconf_minmax_elements_xml(xret, xp, yang_argument_get(y), 1) < 0) + if (xret && netconf_minmax_elements_xml(xret, xp, yang_argument_get(y), 1) < 0) goto done; goto fail; } @@ -851,7 +851,7 @@ check_list_unique_minmax(cxobj *xt, /* Only lists and leaf-lists are allowed to be many * This checks duplicate container and leafs */ - if (netconf_minmax_elements_xml(xret, xt, xml_name(x), 1) < 0) + if (xret && netconf_minmax_elements_xml(xret, xt, xml_name(x), 1) < 0) goto done; goto fail; } @@ -1018,14 +1018,14 @@ xml_yang_validate_add(clicon_handle h, * are considered as "" */ cvtype = cv_type_get(cv); if (cv_isint(cvtype) || cvtype == CGV_BOOL || cvtype == CGV_DEC64){ - if (netconf_bad_element_xml(xret, "application", yang_argument_get(yt), "Invalid NULL value") < 0) + if (xret && netconf_bad_element_xml(xret, "application", yang_argument_get(yt), "Invalid NULL value") < 0) goto done; goto fail; } } else{ if (cv_parse1(body, cv, &reason) != 1){ - if (netconf_bad_element_xml(xret, "application", yang_argument_get(yt), reason) < 0) + if (xret && netconf_bad_element_xml(xret, "application", yang_argument_get(yt), reason) < 0) goto done; goto fail; } @@ -1033,7 +1033,7 @@ xml_yang_validate_add(clicon_handle h, if ((ret = ys_cv_validate(h, cv, yt, NULL, &reason)) < 0) goto done; if (ret == 0){ - if (netconf_bad_element_xml(xret, "application", yang_argument_get(yt), reason) < 0) + if (xret && netconf_bad_element_xml(xret, "application", yang_argument_get(yt), reason) < 0) goto done; goto fail; } @@ -1158,7 +1158,7 @@ xml_yang_validate_all(clicon_handle h, goto done; if (ns) cprintf(cb, " in namespace: %s", ns); - if (netconf_unknown_element_xml(xret, "application", xml_name(xt), cbuf_get(cb)) < 0) + if (xret && netconf_unknown_element_xml(xret, "application", xml_name(xt), cbuf_get(cb)) < 0) goto done; goto fail; } @@ -1217,7 +1217,7 @@ xml_yang_validate_all(clicon_handle h, } cprintf(cb, "Failed MUST xpath '%s' of '%s' in module %s", xpath, xml_name(xt), yang_argument_get(ys_module(ys))); - if (netconf_operation_failed_xml(xret, "application", + if (xret && netconf_operation_failed_xml(xret, "application", ye?yang_argument_get(ye):cbuf_get(cb)) < 0) goto done; goto fail; @@ -1247,7 +1247,7 @@ xml_yang_validate_all(clicon_handle h, cprintf(cb, "Failed WHEN condition of %s in module %s", xml_name(xt), yang_argument_get(ys_module(ys))); - if (netconf_operation_failed_xml(xret, "application", + if (xret && netconf_operation_failed_xml(xret, "application", cbuf_get(cb)) < 0) goto done; goto fail; @@ -1269,7 +1269,7 @@ xml_yang_validate_all(clicon_handle h, xpath, xml_name(xt), yang_argument_get(ys_module(ys))); - if (netconf_operation_failed_xml(xret, "application", + if (xret && netconf_operation_failed_xml(xret, "application", cbuf_get(cb)) < 0) goto done; goto fail; diff --git a/lib/src/clixon_yang_module.c b/lib/src/clixon_yang_module.c index 3d47f6f7..3718ceb8 100644 --- a/lib/src/clixon_yang_module.c +++ b/lib/src/clixon_yang_module.c @@ -329,7 +329,7 @@ yang_modules_state_get(clicon_handle h, * Note, list is not sorted since it is state (should not be) */ if (clixon_xml_parse_string(cbuf_get(cb), YB_MODULE, yspec, &x, NULL) < 0){ - if (netconf_operation_failed_xml(xret, "protocol", clicon_err_reason)< 0) + if (xret && netconf_operation_failed_xml(xret, "protocol", clicon_err_reason)< 0) goto done; goto fail; } From 96a3ee98c6e13cd45315ccef6887b64f5ea27c20 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Tue, 27 Jul 2021 10:53:47 +0200 Subject: [PATCH 30/49] Removed default of `CLICON_RESTCONF_INSTALLDIR` * The default behaviour is changed to use the config $(sbindir) to locate `clixon_restconf` when starting restconf internally --- CHANGELOG.md | 12 ++++++++++- apps/backend/Makefile.in | 4 +++- apps/backend/backend_plugin_restconf.c | 15 +++++++++---- test/test_restconf_internal.sh | 3 --- test/test_restconf_internal_usecases.sh | 3 --- yang/clixon/clixon-config@2021-07-11.yang | 26 ++++++++++++++++------- 6 files changed, 43 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c89efc8..ed445805 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,11 +34,21 @@ Expected: September, 2021 ### C/CLI-API changes on existing features +### New features -Developers may need to change their code +* Restconf YANG PATCH according to RFC 8072 + * Experimental: enable by setting YANG_PATCH in include/clixon_custom.h + * Thanks to Alan Yaniger for providing this patch + +### API changes on existing protocol/config features + +Users may have to change how they access the system * Native Restconf is now default, not fcgi/nginx * That is, to configure with fcgi, you need to explicitly configure: `--with-restconf=fcgi` +* New clixon-config@2021-07-11.yang revision + * Removed default of `CLICON_RESTCONF_INSTALLDIR` + * The default behaviour is changed to use the config $(sbindir) to locate `clixon_restconf` when starting restconf internally ### Corrected Bugs diff --git a/apps/backend/Makefile.in b/apps/backend/Makefile.in index aecf95c7..4c55d23f 100644 --- a/apps/backend/Makefile.in +++ b/apps/backend/Makefile.in @@ -152,7 +152,9 @@ install-include: clixon_backend.h clixon_backend_handle.h clixon_backend_transac .SUFFIXES: .c .o .c.o: - $(CC) $(INCLUDES) $(CPPFLAGS) -D__PROGRAM__=\"$(APPL)\" $(CFLAGS) -c $< + # Note: CLIXON_CONFIG_SBINDIR is where clixon_restconf is believed to be installed, unless + # overruled by CLICON_RESTCONF_INSTALLDIR option + $(CC) $(INCLUDES) $(CPPFLAGS) -D__PROGRAM__=\"$(APPL)\" -DCLIXON_CONFIG_SBINDIR=\"$(sbindir)\" $(CFLAGS) -c $< # Just link test programs test.c : diff --git a/apps/backend/backend_plugin_restconf.c b/apps/backend/backend_plugin_restconf.c index 6b82417d..fe39c8c5 100644 --- a/apps/backend/backend_plugin_restconf.c +++ b/apps/backend/backend_plugin_restconf.c @@ -245,6 +245,7 @@ restconf_pseudo_process_control(clicon_handle h) int i; int nr; cbuf *cb = NULL; + char *dir = NULL; nr = 10; if ((argv = calloc(nr, sizeof(char *))) == NULL){ @@ -256,12 +257,18 @@ restconf_pseudo_process_control(clicon_handle h) clicon_err(OE_UNIX, errno, "cbuf_new"); goto done; } - /* CLICON_RESTCONF_INSTALLDIR is where we think clixon_restconf is installed - * Problem is where to define it? Now in config file, but maybe it should be in configure? - * Tried Makefile but didnt work on Docker since it was moved around. + /* Try to figure out where clixon_restconf is installed + * If config option CLICON_RESTCONF_INSTALLDIR is installed, use that. + * If not, use the Makefile * Use PATH? */ - cprintf(cb, "%s/clixon_restconf", clicon_option_str(h, "CLICON_RESTCONF_INSTALLDIR")); + if ((dir = clicon_option_str(h, "CLICON_RESTCONF_INSTALLDIR")) == NULL){ + if ((dir = CLIXON_CONFIG_SBINDIR) == NULL){ + clicon_err(OE_RESTCONF, EINVAL, "Both option CLICON_RESTCONF_INSTALLDIR and makefile constant CLIXON_CONFIG_SBINDIR are NULL which make sit not possible to know where clixon_restconf is installed(shouldnt happen)"); + goto done; + } + } + cprintf(cb, "%s/clixon_restconf", dir); argv[i++] = cbuf_get(cb); argv[i++] = "-f"; argv[i++] = clicon_option_str(h, "CLICON_CONFIGFILE"); diff --git a/test/test_restconf_internal.sh b/test/test_restconf_internal.sh index 41e449f5..4eb894f0 100755 --- a/test/test_restconf_internal.sh +++ b/test/test_restconf_internal.sh @@ -23,8 +23,6 @@ startupdb=$dir/startup_db RESTCONFDBG=$DBG RCPROTO=http # no ssl here -RESTCONFDIR=$(dirname $(which clixon_restconf)) - # log-destination in restconf xml: syslog or file : ${LOGDST:=syslog} # Set daemon command-line to -f @@ -54,7 +52,6 @@ cat < $cfg /usr/local/lib/$APPNAME/backend example_backend.so$ /usr/local/lib/$APPNAME/restconf - $RESTCONFDIR /usr/local/lib/$APPNAME/cli $APPNAME /usr/local/var/$APPNAME/$APPNAME.sock diff --git a/test/test_restconf_internal_usecases.sh b/test/test_restconf_internal_usecases.sh index 70a94c37..f57c7cf9 100755 --- a/test/test_restconf_internal_usecases.sh +++ b/test/test_restconf_internal_usecases.sh @@ -34,8 +34,6 @@ startupdb=$dir/startup_db RESTCONFDBG=$DBG RCPROTO=http # no ssl here -RESTCONFDIR=$(dirname $(which clixon_restconf)) - INVALIDADDR=251.1.1.1 # used by fourth usecase as invalid # log-destination in restconf xml: syslog or file @@ -68,7 +66,6 @@ cat < $cfg /usr/local/lib/$APPNAME/backend example_backend.so$ /usr/local/lib/$APPNAME/restconf - $RESTCONFDIR /usr/local/lib/$APPNAME/cli $APPNAME /usr/local/var/$APPNAME/$APPNAME.sock diff --git a/yang/clixon/clixon-config@2021-07-11.yang b/yang/clixon/clixon-config@2021-07-11.yang index dfc8d54b..f4d065c8 100644 --- a/yang/clixon/clixon-config@2021-07-11.yang +++ b/yang/clixon/clixon-config@2021-07-11.yang @@ -45,8 +45,13 @@ module clixon-config { revision 2021-07-11 { description - "Added option - CLICON_SYSTEM_CAPABILITIES"; + "Added option: + CLICON_SYSTEM_CAPABILITIES + Removed default value: + CLICON_RESTCONF_INSTALLDIR + Marked as obsolete: + CLICON_YANG_LIST_CHECK + (Will be) Released in Clixon 5.3"; } revision 2021-05-20 { description @@ -518,13 +523,18 @@ module clixon-config { } leaf CLICON_RESTCONF_INSTALLDIR { type string; - default "/usr/local/sbin"; description - "Path to dir of clixon-restconf daemon binary as used by backend if started internally - Discussion: Somewhat problematic to have it as run time option. It may think it - should be known at configure or install time, but for example the main docker - installation moves the binaries, and this may be true elsewehere too. - Maybe one could locate it via PATHs search"; + "If set, path to dir of clixon-restconf daemon binary as used by backend if + started internally (run-time). + If this path is not set, clixon_restconf will be looked for according to + configured installdir: $(sbindir) (install-time) + Since programs can be moved around at install/cross-compile time the installed + dir may be difficult to know at install time, which is the reason why + CLICON_RESTCONF_INSTALLDIR exists, in order to override the Makefile + installdir. + Note on the installdir, DESTDIR is not included since according to man pages: + by specifying DESTDIR should not change the operation of the software in + any way, so its value should not be included in any file contents. "; } leaf CLICON_RESTCONF_STARTUP_DONTUPDATE { type boolean; From ecceda35b66782d1e27175a33fb749638baa6ab9 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Tue, 27 Jul 2021 11:18:35 +0200 Subject: [PATCH 31/49] revert restconf internal tests using CLICON_RESTCONF_INSTALLDIR for docker --- test/test_restconf_internal.sh | 3 +++ test/test_restconf_internal_usecases.sh | 3 +++ 2 files changed, 6 insertions(+) diff --git a/test/test_restconf_internal.sh b/test/test_restconf_internal.sh index 4eb894f0..41e449f5 100755 --- a/test/test_restconf_internal.sh +++ b/test/test_restconf_internal.sh @@ -23,6 +23,8 @@ startupdb=$dir/startup_db RESTCONFDBG=$DBG RCPROTO=http # no ssl here +RESTCONFDIR=$(dirname $(which clixon_restconf)) + # log-destination in restconf xml: syslog or file : ${LOGDST:=syslog} # Set daemon command-line to -f @@ -52,6 +54,7 @@ cat < $cfg /usr/local/lib/$APPNAME/backend example_backend.so$ /usr/local/lib/$APPNAME/restconf + $RESTCONFDIR /usr/local/lib/$APPNAME/cli $APPNAME /usr/local/var/$APPNAME/$APPNAME.sock diff --git a/test/test_restconf_internal_usecases.sh b/test/test_restconf_internal_usecases.sh index f57c7cf9..70a94c37 100755 --- a/test/test_restconf_internal_usecases.sh +++ b/test/test_restconf_internal_usecases.sh @@ -34,6 +34,8 @@ startupdb=$dir/startup_db RESTCONFDBG=$DBG RCPROTO=http # no ssl here +RESTCONFDIR=$(dirname $(which clixon_restconf)) + INVALIDADDR=251.1.1.1 # used by fourth usecase as invalid # log-destination in restconf xml: syslog or file @@ -66,6 +68,7 @@ cat < $cfg /usr/local/lib/$APPNAME/backend example_backend.so$ /usr/local/lib/$APPNAME/restconf + $RESTCONFDIR /usr/local/lib/$APPNAME/cli $APPNAME /usr/local/var/$APPNAME/$APPNAME.sock From fd28fd498d988fe31e075ae60c47939f22754cbb Mon Sep 17 00:00:00 2001 From: Alan Yaniger Date: Tue, 27 Jul 2021 18:36:46 +0300 Subject: [PATCH 32/49] - Add prefix "yang_patch_" to new functions - use clixon cbuf functions instead of new string functions - moved some code into separate functions - added comments - added documentation to functions that did not have it --- apps/restconf/restconf_methods.c | 559 +++++++++++++++++-------------- 1 file changed, 303 insertions(+), 256 deletions(-) diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index 141451d8..1014f65c 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -65,6 +65,8 @@ /* cligen */ #include +// TODO - remove this include if cbuf_trunc() is added to cligen repo +#include "../cligen/cligen_buf_internal.h" /* clicon */ #include @@ -582,68 +584,34 @@ api_data_write(clicon_handle h, #ifdef YANG_PATCH -char * init_str() +/*! Free memory after a NULL pointer check + * + * @param [in] str void pointer to memory to be freed + * + */ +static void yang_patch_free_mem(void *p) { - char* s; - s = malloc(TEMP_STR_MALLOC_SIZE); - memset(s, 0, TEMP_STR_MALLOC_SIZE); - return s; + if (p != NULL) + free(p); } -int cpy_str(char *dest, char *src, size_t size) -{ - if (src == NULL) { - return 0; - } - if (dest == NULL) { - init_str(dest); - } - if (size <= 0 || size > TEMP_STR_MALLOC_SIZE) { - return 0; - } - size_t i; - for (i = 0; i < size - 1 && src[i]; i++) { - dest[i] = src[i]; - } - dest[i] = '\0'; - return i; -} - -int cat_str(char *dest, char *src, size_t size) -{ - if (src == NULL) { - return 0; - } - if (dest == NULL) { - init_str(dest); - } - if (size <= 0 || size > TEMP_STR_MALLOC_SIZE) { - return 0; - } - size_t i; - int old_len = strlen(dest); - for (i = 0; i < size - 1 && src[i]; i++) { - dest[i + old_len] = src[i]; - } - dest[i + old_len] = '\0'; - return i; -} - -void free_mem(void *str) -{ - if (str != NULL) - free(str); -} - -int get_xval( +/*! Return a value within XML tags + * @param [in] nsc namespace context + * @param [in] xn cxobj containing XML with the current edit + * @param [in] val cbuf to which the value will be written + * @param [in] key string containing the tag + * @retval 0 success + * @retval <0 failure + */ +static int yang_patch_get_xval( cvec* nsc, cxobj* xn, - char* val, - cxobj **vec, - size_t veclen, + cbuf* val, const char* key ) { + cxobj **vec = NULL; + size_t veclen = 0; char* tmp_val = NULL; int ret = xpath_vec(xn, nsc, "%s", &vec, &veclen, key); if (ret < 0) { @@ -653,11 +621,128 @@ int get_xval( cxobj *xn = vec[j]; tmp_val = xml_body(xn); } - cpy_str(val, tmp_val, TEMP_STR_MALLOC_SIZE); + cbuf_append_str(val, tmp_val); return 0; } -int do_replace ( +// TODO - add this to cligen repo if it is approved +/*! Truncate a cbuf + * + * @param [in] cb cligen buffer allocated by cbuf_new(), may be reallocated. + * @param [in] int pos position at which to truncate + * @retval new cbuf containing the truncated string (old buffer remains as it was) + * @retval NULL Error + */ +cbuf* +cbuf_trunc(cbuf *cb, + int pos) +{ + if (pos < 0 || pos > cb->cb_strlen){ + errno = EINVAL; + return NULL; + } + /* Ensure buffer is right size */ + cbuf* new_buf = cbuf_new_alloc(pos + 1); + if (new_buf == NULL) + return NULL; + strncpy(new_buf->cb_buffer, cb->cb_buffer, pos); + new_buf->cb_strlen = pos; + return new_buf; +} + +/*! Add square brackets after the surrounding curly brackets in JSON + * Needed, in order to modify the result of xml2json_cbuf() to be valid input + * to api_dta_post() and api_dta_write() + * @param [in] x_simple_patch a cxobj to pass to xml2json_cbuf() + * @retval new cbuf with the modified json + * @retval NULL Error + */ + +static cbuf* yang_patch_xml2json_modified_cbuf(cxobj* x_simple_patch) +{ + cbuf *json_simple_patch = cbuf_new(); + if (json_simple_patch == NULL) + return NULL; + cbuf* cb = cbuf_new(); + xml2json_cbuf(cb, x_simple_patch, 1); + + // Insert a '[' after the first '{' to get the JSON to match what api_data_post/write() expect + char *json_simple_patch_tmp = cbuf_get(cb); + int brace_count = 0; + for (int l = 0; l < strlen(json_simple_patch_tmp); l++) { + char c = json_simple_patch_tmp[l]; + if (c == '{') { + brace_count++; + if (brace_count == 2) { + cbuf_append(json_simple_patch,(int)'['); + } + } + cbuf_append(json_simple_patch,(int)c); + } + cbuf* json_simple_patch_2 = NULL; + + // Insert a ']' before the last '}' to get the JSON to match what api_data_post() expects + for (int l = cbuf_len(json_simple_patch); l>= 0; l--) { + char c = cbuf_get(json_simple_patch)[l]; + if (c == '}') { + // Truncate and add a string, as there is not a function to insert a char into a cbuf + json_simple_patch_2 = cbuf_trunc(json_simple_patch, l); + cbuf_append_str(json_simple_patch_2, "]}"); + break; + } + } + cbuf_free(json_simple_patch); + cbuf_free(cb); + return json_simple_patch_2; +} + +/*!yang_patch_strip_after_last_slash + * + * Strip /... from end of val + * so that e.g. "/interface=eth2" becomes "/" + * or "/interface_list=mylist/interface=eth2" becomes "/interface_list=mylist/" + * + * @param[in] val value to strip + * @retval new cbuf with the stripped string + * @retval NULL error + */ +static cbuf* yang_patch_strip_after_last_slash(cbuf* val) +{ + cbuf *cb = cbuf_new(); + cbuf* val_tmp = cbuf_new(); + cbuf_append_str(val_tmp, cbuf_get(val)); + int idx = cbuf_len(val_tmp); + for (int l = cbuf_len(val_tmp); l>= 0; l--) { + if (cbuf_get(val_tmp)[l] == '/') { + idx = l; + break; + } + } + cbuf* val_tmp_2 = cbuf_trunc(val_tmp, idx + 1); + if (val_tmp_2 == NULL) + return NULL; + if (cbuf_append_str(cb, cbuf_get(val_tmp_2)) < 0) + return NULL; + cbuf_free(val_tmp); + cbuf_free(val_tmp_2); + return cb; +} + +/*! YANG PATCH replace method + * @param[in] h Clixon handle + * @param[in] req Generic Www handle + * @param[in] pi Offset, where to start pcvec + * @param[in] qvec Vector of query string (QUERY_STRING) + * @param[in] pretty Set to 1 for pretty-printed xml/json output + * @param[in] media_out Output media + * @param[in] ds 0 if "data" resource, 1 if rfc8527 "ds" resource + * @param[in] simplepatch_request_uri URI for patch request, e.g. "/restconf/data/ietf-interfaces:interfaces" + * @param[in] target_val value in "target" field of edit in YANG patch + * @param[in] value_vec_len number of elements in the "value" array of an edit in YANG patch + * @param[in] value_vec pointer to the "value" array of an edit in YANG patch + * @param[in] x_simple_patch pointer to XML containing module name, e.g. + */ +static int yang_patch_do_replace ( clicon_handle h, void *req, int pi, @@ -665,93 +750,80 @@ int do_replace ( int pretty, restconf_media media_out, ietf_ds_t ds, - char* simple_patch_request_uri, - char* target_val, + cbuf* simple_patch_request_uri, + cbuf* target_val, int value_vec_len, cxobj** value_vec, - cxobj * value_vec_tmp, - cxobj *x_simple_patch, - char *patch_header + cxobj *x_simple_patch ) { - char *delete_req_uri = init_str(); + cxobj * value_vec_tmp = NULL; + cbuf* delete_req_uri = cbuf_new(); if (delete_req_uri == NULL) return 1; - if (cpy_str(delete_req_uri, simple_patch_request_uri, TEMP_STR_MALLOC_SIZE) <= 0) + // Make delete_req_uri something like "/restconf/data/ietf-interfaces:interfaces" + if (cbuf_append_str(delete_req_uri, cbuf_get(simple_patch_request_uri)) < 0) return 1; - if (cat_str(delete_req_uri, target_val, TEMP_STR_MALLOC_SIZE) <= 0) + // Add the target to delete_req_uri, + // so it's something like "/restconf/data/ietf-interfaces:interfaces/interface=eth2" + if (cbuf_append_str(delete_req_uri, cbuf_get(target_val)) < 0) return 1; // Delete the object with the old values - int ret = api_data_delete(h, req, delete_req_uri, pi, pretty, YANG_DATA_JSON, ds ); - free_mem((void *)delete_req_uri); + int ret = api_data_delete(h, req, cbuf_get(delete_req_uri), pi, pretty, YANG_DATA_JSON, ds ); + cbuf_free(delete_req_uri); if (ret != 0) return ret; - // Now insert the object with the new values - char *json_simple_patch = init_str(); - if (json_simple_patch == NULL) - return 1; // goto done; + // Now set up for the post request. + // Strip /... from end of target val + // so that e.g. "/interface=eth2" becomes "/" + // or "/interface_list=mylist/interface=eth2" becomes "/interface_list=mylist/" + cbuf* post_req_uri = yang_patch_strip_after_last_slash(target_val); + // Make post_req_uri something like "/restconf/data/ietf-interfaces:interfaces" + if (cbuf_append_str(simple_patch_request_uri, cbuf_get(post_req_uri))) + return 1; + cbuf_free(post_req_uri); + + // Now insert the new values into the data + // (which will include the key value and all other mandatory values) for (int k = 0; k < value_vec_len; k++) { if (value_vec[k] != NULL) { value_vec_tmp = xml_dup(value_vec[k]); xml_addsub(x_simple_patch, value_vec_tmp); } } - cbuf* cb = cbuf_new(); - xml2json_cbuf(cb, x_simple_patch, 1); - - // Some ugly text processing to get the JSON to match what api_data_post() expects - char *json_simple_patch_tmp = cbuf_get(cb); - int brace_count = 0; - for (int l = 0; l < strlen(json_simple_patch_tmp); l++) { - char c = json_simple_patch_tmp[l]; - if (c == '{') { - brace_count++; - if (brace_count == 2) { - json_simple_patch[strlen(json_simple_patch)] = '['; - } - } - json_simple_patch[strlen(json_simple_patch)] = c; - } - - /* strip /... from end */ - char *post_req_uri = init_str(); - if (post_req_uri == NULL) - return 1; - - int idx = strlen(target_val); - for (int l = strlen(target_val); l>= 0; l--) { - if (target_val[l] == '/') { - idx = l; - break; - } - } - cpy_str(post_req_uri, target_val, idx); - cat_str(simple_patch_request_uri, post_req_uri, TEMP_STR_MALLOC_SIZE); - free_mem((void *)post_req_uri); - for (int l = strlen(json_simple_patch); l>= 0; l--) { - char c = json_simple_patch[l]; - if (c == '}') { - json_simple_patch[l] = ']'; - json_simple_patch[l + 1] = '}'; - break; - } - } + // Convert the data to json + cbuf *json_simple_patch = cbuf_new(); + if (json_simple_patch == NULL) + return 1; + xml2json_cbuf(json_simple_patch, x_simple_patch, 1); // Send the POST request - ret = api_data_post(h, req, simple_patch_request_uri, pi, qvec, json_simple_patch, pretty, YANG_DATA_JSON, media_out, ds ); + ret = api_data_post(h, req, cbuf_get(simple_patch_request_uri), pi, qvec, cbuf_get(json_simple_patch), pretty, YANG_DATA_JSON, media_out, ds ); - free_mem((void *)value_vec_tmp); - free_mem((void *)x_simple_patch); - free_mem((void *)patch_header); + cbuf_free(json_simple_patch); + xml_free(value_vec_tmp); return ret; } -int do_create ( +/*! YANG PATCH create method + * @param[in] h Clixon handle + * @param[in] req Generic Www handle + * @param[in] pi Offset, where to start pcvec + * @param[in] qvec Vector of query string (QUERY_STRING) + * @param[in] pretty Set to 1 for pretty-printed xml/json output + * @param[in] media_out Output media + * @param[in] ds 0 if "data" resource, 1 if rfc8527 "ds" resource + * @param[in] simplepatch_request_uri URI for patch request, e.g. "/restconf/data/ietf-interfaces:interfaces" + * @param[in] value_vec_len number of elements in the "value" array of an edit in YANG patch + * @param[in] value_vec pointer to the "value" array of an edit in YANG patch + * @param[in] x_simple_patch pointer to XML containing module name, e.g. + */ +static int yang_patch_do_create ( clicon_handle h, void *req, int pi, @@ -759,14 +831,13 @@ int do_create ( int pretty, restconf_media media_out, ietf_ds_t ds, - char* simple_patch_request_uri, + cbuf* simple_patch_request_uri, int value_vec_len, cxobj** value_vec, - cxobj * value_vec_tmp, - cxobj *x_simple_patch, - char *patch_header + cxobj *x_simple_patch ) { + cxobj * value_vec_tmp = NULL; for (int k = 0; k < value_vec_len; k++) { if (value_vec[k] != NULL) { value_vec_tmp = xml_dup(value_vec[k]); @@ -778,34 +849,43 @@ int do_create ( cbuf* cb = cbuf_new(); xml2json_cbuf(cb, x_simple_patch, 1); char *json_simple_patch = cbuf_get(cb); - int ret = api_data_post(h, req, simple_patch_request_uri, pi, qvec, json_simple_patch, pretty, YANG_DATA_JSON, media_out, ds ); - free_mem((void *)value_vec_tmp); - free_mem((void *)x_simple_patch); - free_mem((void *)patch_header); + int ret = api_data_post(h, req, cbuf_get(simple_patch_request_uri), pi, qvec, json_simple_patch, pretty, YANG_DATA_JSON, media_out, ds ); + xml_free(value_vec_tmp); return ret; } -int do_insert ( +/*! YANG PATCH insert method + * @param[in] h Clixon handle + * @param[in] req Generic Www handle + * @param[in] pi Offset, where to start pcvec + * @param[in] pretty Set to 1 for pretty-printed xml/json output + * @param[in] media_out Output media + * @param[in] ds 0 if "data" resource, 1 if rfc8527 "ds" resource + * @param[in] simple_patch_request_uri URI for patch request, e.g. "/restconf/data/ietf-interfaces:interfaces" + * @param[in] value_vec_len number of elements in the "value" array of an edit in YANG patch + * @param[in] value_vec pointer to the "value" array of an edit in YANG patch + * @param[in] x_simple_patch pointer to XML containing module name, e.g. + * @param[in] where_val value in "where" field of edit in YANG patch + * @param[in] api_path full API path, e.g. "/restconf/data/example-jukebox:jukebox/playlist=Foo-One" + * @param[in] point_val value in "point" field of edit in YANG patch + */ +static int yang_patch_do_insert ( clicon_handle h, void *req, int pi, int pretty, restconf_media media_out, ietf_ds_t ds, - char* simple_patch_request_uri, + cbuf* simple_patch_request_uri, int value_vec_len, cxobj** value_vec, - cxobj * value_vec_tmp, cxobj *x_simple_patch, - char *patch_header, - char* where_val, + cbuf* where_val, char* api_path, - char *point_val + cbuf *point_val ) { - char *json_simple_patch = init_str(); - if (json_simple_patch == NULL) - return 1; + cxobj * value_vec_tmp = NULL; // Loop through the XML, and get each value for (int k = 0; k < value_vec_len; k++) { @@ -814,30 +894,9 @@ int do_insert ( xml_addsub(x_simple_patch, value_vec_tmp); } } - cbuf* cb = cbuf_new(); - xml2json_cbuf(cb, x_simple_patch, 1); - - // Some ugly text processing to get the JSON to match what api_data_post() expects - char *json_simple_patch_tmp = cbuf_get(cb); - int brace_count = 0; - for (int l = 0; l < strlen(json_simple_patch_tmp); l++) { - char c = json_simple_patch_tmp[l]; - if (c == '{') { - brace_count++; - if (brace_count == 2) { - json_simple_patch[strlen(json_simple_patch)] = '['; - } - } - json_simple_patch[strlen(json_simple_patch)] = c; - } - for (int l = strlen(json_simple_patch); l>= 0; l--) { - char c = json_simple_patch[l]; - if (c == '}') { - json_simple_patch[l] = ']'; - json_simple_patch[l + 1] = '}'; - break; - } - } + cbuf *json_simple_patch = yang_patch_xml2json_modified_cbuf(x_simple_patch); + if (json_simple_patch == NULL) + return 1; // Set the insert attributes cvec* qvec_tmp = NULL; @@ -849,31 +908,43 @@ int do_insert ( return 1; } cv_name_set(cv, "insert"); - cv_string_set(cv, where_val); - char *point_str = init_str(); + cv_string_set(cv, cbuf_get(where_val)); + cbuf *point_str = cbuf_new(); if (point_str == NULL) return 1; - cpy_str(point_str, api_path, TEMP_STR_MALLOC_SIZE); - cat_str(point_str, point_val, TEMP_STR_MALLOC_SIZE); + cbuf_append_str(point_str, api_path); + cbuf_append_str(point_str, cbuf_get(point_val)); if ((cv = cvec_add(qvec_tmp, CGV_STRING)) == NULL){ return 1; } cv_name_set(cv, "point"); - cv_string_set(cv, point_str); + cv_string_set(cv, cbuf_get(point_str)); // Send the POST request - int ret = api_data_post(h, req, simple_patch_request_uri, pi, qvec_tmp, json_simple_patch, pretty, YANG_DATA_JSON, media_out, ds ); - if (cb != NULL) - cbuf_free(cb); - free_mem((void *)value_vec_tmp); - free_mem((void *)point_str); - free_mem((void *)json_simple_patch); - free_mem((void *)patch_header); - free_mem((void *)x_simple_patch); + int ret = api_data_post(h, req, cbuf_get(simple_patch_request_uri), pi, qvec_tmp, cbuf_get(json_simple_patch), pretty, YANG_DATA_JSON, media_out, ds ); + xml_free(value_vec_tmp); + cbuf_free(point_str); + cbuf_free(json_simple_patch); return ret; } -int do_merge ( +/*! YANG PATCH merge method + * @param[in] h Clixon handle + * @param[in] req Generic Www handle + * @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] pretty Set to 1 for pretty-printed xml/json output + * @param[in] media_out Output media + * @param[in] ds 0 if "data" resource, 1 if rfc8527 "ds" resource + * @param[in] simple_patch_request_uri URI for patch request, e.g. "/restconf/data/ietf-interfaces:interfaces" + * @param[in] value_vec_len number of elements in the "value" array of an edit in YANG patch + * @param[in] value_vec pointer to the "value" array of an edit in YANG patch + * @param[in] x_simple_patch pointer to XML containing module name, e.g. "" + * @param[in] where_val value in "where" field of edit in YANG patch + * @param[in] key_xn XML with key tag and value, e.g. "Foo-One" + */ +static int yang_patch_do_merge ( clicon_handle h, void *req, cvec *pcvec, @@ -882,24 +953,18 @@ int do_merge ( int pretty, restconf_media media_out, ietf_ds_t ds, - char* simple_patch_request_uri, + cbuf* simple_patch_request_uri, int value_vec_len, cxobj** value_vec, - cxobj * value_vec_tmp, cxobj *x_simple_patch, - cxobj *key_xn, - int plain_patch_val, - char *patch_header + cxobj *key_xn ) { int ret = -1; + cxobj * value_vec_tmp = NULL; if (key_xn != NULL) xml_addsub(x_simple_patch, key_xn); - char *json_simple_patch = init_str(); - if (json_simple_patch == NULL) - return 1; - // Loop through the XML, create JSON from each one, and submit a simple patch for (int k = 0; k < value_vec_len; k++) { if (value_vec[k] != NULL) { @@ -909,36 +974,15 @@ int do_merge ( cbuf* cb = cbuf_new(); xml2json_cbuf(cb, x_simple_patch, 1); - // Some ugly text processing to get the JSON to match what api_data_write() expects for a simple patch - char *json_simple_patch_tmp = cbuf_get(cb); - memset(json_simple_patch, 0, TEMP_STR_MALLOC_SIZE); - int brace_count = 0; - for (int l = 0; l < strlen(json_simple_patch_tmp); l++) { - char c = json_simple_patch_tmp[l]; - if (c == '{') { - brace_count++; - if (brace_count == 2) { - json_simple_patch[strlen(json_simple_patch)] = '['; - } - } - json_simple_patch[strlen(json_simple_patch)] = c; - } - for (int l = strlen(json_simple_patch); l>= 0; l--) { - char c = json_simple_patch[l]; - if (c == '}') { - json_simple_patch[l] = ']'; - json_simple_patch[l + 1] = '}'; - break; - } - } - free_mem(value_vec_tmp); + cbuf *json_simple_patch = yang_patch_xml2json_modified_cbuf(x_simple_patch); + if (json_simple_patch == NULL) + return 1; + xml_free(value_vec_tmp); // Send the simple patch request - ret = api_data_write(h, req, simple_patch_request_uri, pcvec, pi, qvec, json_simple_patch, pretty, YANG_DATA_JSON, media_out, plain_patch_val, ds ); + ret = api_data_write(h, req, cbuf_get(simple_patch_request_uri), pcvec, pi, qvec, cbuf_get(json_simple_patch), pretty, YANG_DATA_JSON, media_out, 1, ds ); cbuf_free(cb); + cbuf_free(json_simple_patch); } - free_mem(json_simple_patch); - free_mem(patch_header); - free_mem(x_simple_patch); return ret; } @@ -988,7 +1032,7 @@ api_data_yang_patch(clicon_handle h, cvec *nsc = NULL; yang_bind yb; char *xpath = NULL; - char *path_orig_1 = NULL; + cbuf *path_orig_1 = NULL; clicon_debug(1, "%s api_path:\"%s\"", __FUNCTION__, api_path0); if ((yspec = clicon_dbspec_yang(h)) == NULL){ @@ -1057,11 +1101,11 @@ api_data_yang_patch(clicon_handle h, continue; } } - path_orig_1 = init_str(); + path_orig_1 = cbuf_new(); if (path_orig_1 == NULL) { goto done; } else { - cpy_str(path_orig_1, restconf_uripath(h), TEMP_STR_MALLOC_SIZE); + cbuf_append_str(path_orig_1, restconf_uripath(h)); } // Loop through the edits @@ -1070,45 +1114,44 @@ api_data_yang_patch(clicon_handle h, size_t tmp_veclen = 0; cxobj *xn = vec[i]; + clicon_log_xml(LOG_DEBUG, xn, "%s %d xn:", __FUNCTION__, __LINE__); // Get target - char *target_val = init_str(); - ret = get_xval(nsc, xn, target_val, tmp_vec, tmp_veclen, "target"); + cbuf *target_val = cbuf_new(); + ret = yang_patch_get_xval(nsc, xn, target_val, "target"); if (ret < 0) { goto done; } // Get operation - char *op_val = init_str(); - ret = get_xval(nsc, xn, op_val, tmp_vec, tmp_veclen, "operation"); + cbuf *op_val = cbuf_new(); + ret = yang_patch_get_xval(nsc, xn, op_val, "operation"); if (ret < 0) { goto done; } // Get "point" and "where" for insert operations - char *point_val = init_str(); - char *where_val = init_str(); - if (strcmp(op_val, "insert") == 0) { // TODO - test - point_val = init_str(); - ret = get_xval(nsc, xn, point_val, tmp_vec, tmp_veclen, "point"); + cbuf *point_val = NULL; + cbuf *where_val = cbuf_new(); + if (strcmp(cbuf_get(op_val), "insert") == 0) { + point_val = cbuf_new(); + ret = yang_patch_get_xval(nsc, xn, point_val, "point"); if (ret < 0) { goto done; } - where_val = init_str(); - ret = get_xval(nsc, xn, where_val, tmp_vec, tmp_veclen, "where"); + where_val = cbuf_new(); + ret = yang_patch_get_xval(nsc, xn, where_val, "where"); if (ret < 0) { goto done; } } // Construct request URI - char* simple_patch_request_uri = init_str(); - cpy_str(simple_patch_request_uri, path_orig_1, TEMP_STR_MALLOC_SIZE); + cbuf* simple_patch_request_uri = cbuf_new(); + cbuf_append_str(simple_patch_request_uri, cbuf_get(path_orig_1)); - int plain_patch_val = 0; - char* api_path_target = init_str(); - cpy_str(api_path_target, api_path, TEMP_STR_MALLOC_SIZE); - if (strcmp(op_val, "merge") == 0) { - plain_patch_val = 1; - cat_str(api_path_target, target_val, TEMP_STR_MALLOC_SIZE); - cat_str(simple_patch_request_uri, target_val, TEMP_STR_MALLOC_SIZE); + cbuf* api_path_target = cbuf_new(); + cbuf_append_str(api_path_target, api_path); + if (strcmp(cbuf_get(op_val), "merge") == 0) { + cbuf_append_str(api_path_target, cbuf_get(target_val)); + cbuf_append_str(simple_patch_request_uri, cbuf_get(target_val)); } if (xerr) @@ -1119,7 +1162,7 @@ api_data_yang_patch(clicon_handle h, // Get key field /* Translate api_path to xml in the form of xtop/xbot */ xbot_tmp = xtop; - if ((ret = api_path2xml(api_path_target, yspec, xtop, YC_DATANODE, 1, &xbot_tmp, &ybot, &xerr)) < 0) + if ((ret = api_path2xml(cbuf_get(api_path_target), yspec, xtop, YC_DATANODE, 1, &xbot_tmp, &ybot, &xerr)) < 0) goto done; if (ret == 0){ /* validation failed */ if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0) @@ -1142,7 +1185,6 @@ api_data_yang_patch(clicon_handle h, if (key_vec != NULL) { key_xn = key_vec[0]; } - // Get values (for "delete" and "remove", there are no values) xpath_vec(xn, nsc, "value", &tmp_vec, &tmp_veclen); key_node_id = NULL; @@ -1154,68 +1196,73 @@ api_data_yang_patch(clicon_handle h, if (key_node_id == NULL) key_node_id = xml_name(*values_child_vec); - char *patch_header = init_str(); + cbuf *patch_header = cbuf_new(); if (patch_header == NULL) { goto done; } - cpy_str(patch_header, modname, TEMP_STR_MALLOC_SIZE); - cat_str(patch_header, ":", TEMP_STR_MALLOC_SIZE); - cat_str(patch_header, key_node_id, TEMP_STR_MALLOC_SIZE); - cxobj *x_simple_patch = xml_new(patch_header, NULL, CX_ELMNT); + cbuf_append_str(patch_header, modname); + cbuf_append_str(patch_header, ":"); + cbuf_append_str(patch_header, key_node_id); + cxobj *x_simple_patch = xml_new(cbuf_get(patch_header), NULL, CX_ELMNT); if (x_simple_patch == NULL) goto done; int value_vec_len = xml_child_nr(*values_child_vec); cxobj** value_vec = xml_childvec_get(*values_child_vec); - cxobj * value_vec_tmp = NULL; // For "replace", delete the item and then POST it // TODO - in an ordered list, insert it into its original position - if (strcmp(op_val,"replace") == 0) { - ret = do_replace(h, req, pi, qvec, pretty, media_out, ds, simple_patch_request_uri, target_val, value_vec_len, value_vec, value_vec_tmp, x_simple_patch, patch_header); + if (strcmp(cbuf_get(op_val),"replace") == 0) { + ret = yang_patch_do_replace(h, req, pi, qvec, pretty, media_out, ds, simple_patch_request_uri, target_val, value_vec_len, value_vec, x_simple_patch); if (ret != 0) { goto done; } } // For "create", put all the data values into a single POST request - if (strcmp(op_val,"create") == 0) { - ret = do_create(h, req, pi, qvec, pretty, media_out, ds, simple_patch_request_uri, value_vec_len, value_vec, value_vec_tmp, x_simple_patch, patch_header); + if (strcmp(cbuf_get(op_val),"create") == 0) { + ret = yang_patch_do_create(h, req, pi, qvec, pretty, media_out, ds, simple_patch_request_uri, value_vec_len, value_vec, x_simple_patch); if (ret != 0) { goto done; } } - // For "insert", make a api_data_post request // TODO - test - if (strcmp(op_val, "insert") == 0) { - ret = do_insert(h, req, pi, pretty, media_out, ds, simple_patch_request_uri, value_vec_len, value_vec, value_vec_tmp, x_simple_patch, patch_header, where_val, api_path, point_val); + // For "insert", make a api_data_post request + if (strcmp(cbuf_get(op_val), "insert") == 0) { + ret = yang_patch_do_insert(h, req, pi, pretty, media_out, ds, simple_patch_request_uri, value_vec_len, value_vec, x_simple_patch, where_val, api_path, point_val); if (ret != 0) { goto done; } } // For merge", make single simple patch requests for each value - if (strcmp(op_val,"merge") == 0) { - ret = do_merge(h, req, pcvec, pi, qvec, pretty, media_out, ds, simple_patch_request_uri, value_vec_len, value_vec, value_vec_tmp, x_simple_patch, key_xn, plain_patch_val, patch_header); + if (strcmp(cbuf_get(op_val),"merge") == 0) { + ret = yang_patch_do_merge(h, req, pcvec, pi, qvec, pretty, media_out, ds, simple_patch_request_uri, value_vec_len, value_vec, x_simple_patch, key_xn); if (ret != 0) { goto done; } } + cbuf_free(patch_header); + yang_patch_free_mem((void *)x_simple_patch); // Using xml_free() causes crash } - if ((strcmp(op_val, "delete") == 0) || - (strcmp(op_val, "remove") == 0)) { - cat_str(simple_patch_request_uri, target_val, TEMP_STR_MALLOC_SIZE); - if (strcmp(op_val, "delete") == 0) { + if ((strcmp(cbuf_get(op_val), "delete") == 0) || + (strcmp(cbuf_get(op_val), "remove") == 0)) { + cbuf_append_str(simple_patch_request_uri, cbuf_get(target_val)); + if (strcmp(cbuf_get(op_val), "delete") == 0) { // TODO - send error } else { // TODO - do not send error } - api_data_delete(h, req, simple_patch_request_uri, pi, pretty, YANG_DATA_JSON, ds); + api_data_delete(h, req, cbuf_get(simple_patch_request_uri), pi, pretty, YANG_DATA_JSON, ds); } - free_mem((void *)simple_patch_request_uri); - free_mem((void *)api_path_target); + cbuf_free(simple_patch_request_uri); + cbuf_free(api_path_target); + cbuf_free(target_val); + cbuf_free(op_val); + cbuf_free(point_val); + cbuf_free(where_val); } ok: retval = 0; done: - free_mem((void *)path_orig_1); - free_mem((void *)vec); - free_mem((void *)xpath); + cbuf_free(path_orig_1); + yang_patch_free_mem((void *)vec); + yang_patch_free_mem((void *)xpath); if (nsc) xml_nsctx_free(nsc); if (xret) From 523407a9c1e2bdc6befe40bb7cc453b7df93eaa0 Mon Sep 17 00:00:00 2001 From: Alan Yaniger Date: Sun, 1 Aug 2021 16:50:04 +0300 Subject: [PATCH 33/49] fixed string length checks, removed unnecessary loop, changed some other code for clarity --- apps/restconf/restconf_methods.c | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index 1014f65c..2d407450 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -617,11 +617,14 @@ static int yang_patch_get_xval( if (ret < 0) { return ret; } - for (int j = 0; j < veclen; j++) { - cxobj *xn = vec[j]; - tmp_val = xml_body(xn); + cxobj *xn_tmp = NULL; + if (veclen > 0) { + xn_tmp = vec[0]; //veclen should always be 1 + } + if (xn_tmp != NULL) { + tmp_val = xml_body(xn_tmp); + cbuf_append_str(val, tmp_val); } - cbuf_append_str(val, tmp_val); return 0; } @@ -673,7 +676,7 @@ static cbuf* yang_patch_xml2json_modified_cbuf(cxobj* x_simple_patch) char c = json_simple_patch_tmp[l]; if (c == '{') { brace_count++; - if (brace_count == 2) { + if (brace_count == 2) { // We've reached the second brace, insert a '[' before it cbuf_append(json_simple_patch,(int)'['); } } @@ -682,7 +685,7 @@ static cbuf* yang_patch_xml2json_modified_cbuf(cxobj* x_simple_patch) cbuf* json_simple_patch_2 = NULL; // Insert a ']' before the last '}' to get the JSON to match what api_data_post() expects - for (int l = cbuf_len(json_simple_patch); l>= 0; l--) { + for (int l = cbuf_len(json_simple_patch) - 1; l >= 0; l--) { char c = cbuf_get(json_simple_patch)[l]; if (c == '}') { // Truncate and add a string, as there is not a function to insert a char into a cbuf @@ -712,15 +715,15 @@ static cbuf* yang_patch_strip_after_last_slash(cbuf* val) cbuf* val_tmp = cbuf_new(); cbuf_append_str(val_tmp, cbuf_get(val)); int idx = cbuf_len(val_tmp); - for (int l = cbuf_len(val_tmp); l>= 0; l--) { + for (int l = cbuf_len(val_tmp) - 1; l>= 0; l--) { if (cbuf_get(val_tmp)[l] == '/') { idx = l; break; } } - cbuf* val_tmp_2 = cbuf_trunc(val_tmp, idx + 1); - if (val_tmp_2 == NULL) + if (idx == cbuf_len(val_tmp)) // Didn't find a slash in the loop above return NULL; + cbuf* val_tmp_2 = cbuf_trunc(val_tmp, idx + 1); if (cbuf_append_str(cb, cbuf_get(val_tmp_2)) < 0) return NULL; cbuf_free(val_tmp); From ad950efc32d7c12f43bf4bc915842f593e22129b Mon Sep 17 00:00:00 2001 From: Alan Yaniger Date: Sun, 1 Aug 2021 17:01:40 +0300 Subject: [PATCH 34/49] fixed check when getting value inside xml tag given the key --- apps/restconf/restconf_methods.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index 2d407450..fff0275c 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -618,8 +618,8 @@ static int yang_patch_get_xval( return ret; } cxobj *xn_tmp = NULL; - if (veclen > 0) { - xn_tmp = vec[0]; //veclen should always be 1 + if (veclen == 1) { //veclen should always be 1 + xn_tmp = vec[0]; } if (xn_tmp != NULL) { tmp_val = xml_body(xn_tmp); From 551a985d1bcb1183c564c1f6c1a9e25474d6e956 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Mon, 2 Aug 2021 15:46:07 +0200 Subject: [PATCH 35/49] * Fixed: [OpenConfig BGP afi-safi and when condition issues #249](https://github.com/clicon/clixon/issues/249) * YANG when was not properly implemented for default values * Improved error message on leafref validation errors --- CHANGELOG.md | 2 + lib/clixon/clixon_xml_map.h | 1 + lib/src/clixon_validate.c | 63 ++++++++------------------- lib/src/clixon_xml_map.c | 82 ++++++++++++++++++++++++++++++++---- lib/src/clixon_yang.c | 4 +- test/test_leafref.sh | 4 +- test/test_leafref_augment.sh | 2 +- test/test_leafref_state.sh | 4 +- test/test_when_must.sh | 2 +- test/test_xpath_functions.sh | 8 ++-- 10 files changed, 107 insertions(+), 65 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed445805..801c8d64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,8 @@ Users may have to change how they access the system ### Corrected Bugs +* Fixed: [OpenConfig BGP afi-safi and when condition issues #249](https://github.com/clicon/clixon/issues/249) + * YANG when was not properly implemented for default values * Fixed: SEGV in clixon_netconf_lib functions from internal errors including validation. * Check xerr argument both before and after call on netconf lib functions * Fixed: RFC 8040 yang-data extension allows non-key lists diff --git a/lib/clixon/clixon_xml_map.h b/lib/clixon/clixon_xml_map.h index 8f4a288f..feba656e 100644 --- a/lib/clixon/clixon_xml_map.h +++ b/lib/clixon/clixon_xml_map.h @@ -76,5 +76,6 @@ int assign_namespace_body(cxobj *x0, cxobj *x1); int xml_merge(cxobj *x0, cxobj *x1, yang_stmt *yspec, char **reason); int yang_enum_int_value(cxobj *node, int32_t *val); int xml_copy_marked(cxobj *x0, cxobj *x1); +int yang_when_xpath(cxobj *xn, cxobj *xp, yang_stmt *yn, int *hit, int *nrp, char **xpathp); #endif /* _CLIXON_XML_MAP_H_ */ diff --git a/lib/src/clixon_validate.c b/lib/src/clixon_validate.c index 802330c8..48070400 100644 --- a/lib/src/clixon_validate.c +++ b/lib/src/clixon_validate.c @@ -114,6 +114,7 @@ validate_leafref(cxobj *xt, cvec *nsc = NULL; cbuf *cberr = NULL; char *path; + yang_stmt *ymod; if ((leafrefbody = xml_body(xt)) == NULL) goto ok; @@ -140,7 +141,8 @@ validate_leafref(cxobj *xt, clicon_err(OE_UNIX, errno, "cbuf_new"); goto done; } - cprintf(cberr, "Leafref validation failed: No leaf %s matching path %s", leafrefbody, path); + ymod = ys_module(ys); + cprintf(cberr, "Leafref validation failed: No leaf %s matching path %s in module %s", leafrefbody, path, yang_argument_get(ymod)); if (xret && netconf_bad_element_xml(xret, "application", leafrefbody, cbuf_get(cberr)) < 0) goto done; goto fail; @@ -1136,6 +1138,7 @@ xml_yang_validate_all(clicon_handle h, char *ns = NULL; cbuf *cb = NULL; cvec *nsc = NULL; + int hit = 0; /* if not given by argument (overide) use default link and !Node has a config sub-statement and it is false */ @@ -1227,53 +1230,21 @@ xml_yang_validate_all(clicon_handle h, nsc = NULL; } } - /* First variant of when, actual "when" sub-node RFC 7950 Sec 7.21.5. Can only be one. */ - if ((yc = yang_find(ys, Y_WHEN, NULL)) != NULL){ - xpath = yang_argument_get(yc); /* "when" has xpath argument */ - /* WHEN xpath needs namespace context */ - if (xml_nsctx_yang(ys, &nsc) < 0) + if (yang_when_xpath(xt, xml_parent(xt), ys, &hit, &nr, &xpath) < 0) + goto done; + if (hit && nr == 0){ + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); goto done; - if ((nr = xpath_vec_bool(xt, nsc, "%s", xpath)) < 0) + } + cprintf(cb, "Failed WHEN condition of %s in module %s (WHEN xpath is %s)", + xml_name(xt), + yang_argument_get(ys_module(ys)), + xpath); + if (xret && netconf_operation_failed_xml(xret, "application", + cbuf_get(cb)) < 0) goto done; - if (nsc){ - xml_nsctx_free(nsc); - nsc = NULL; - } - if (nr == 0){ - if ((cb = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - cprintf(cb, "Failed WHEN condition of %s in module %s", - xml_name(xt), - yang_argument_get(ys_module(ys))); - if (xret && netconf_operation_failed_xml(xret, "application", - cbuf_get(cb)) < 0) - goto done; - goto fail; - } - } - /* Second variants of WHEN: - * Augmented and uses when using special info in node - */ - if ((xpath = yang_when_xpath_get(ys)) != NULL){ - if ((nr = xpath_vec_bool(xml_parent(xt), yang_when_nsc_get(ys), - "%s", xpath)) < 0) - goto done; - if (nr == 0){ - if ((cb = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - cprintf(cb, "Failed augmented 'when' condition '%s' of node '%s' in module '%s'", - xpath, - xml_name(xt), - yang_argument_get(ys_module(ys))); - if (xret && netconf_operation_failed_xml(xret, "application", - cbuf_get(cb)) < 0) - goto done; - goto fail; - } + goto fail; } } x = NULL; diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 0b933992..5c77c22d 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -1139,10 +1139,11 @@ xml_default1(yang_stmt *yt, int retval = -1; yang_stmt *yc; cxobj *xc; - int top=0; /* Top symbol (set default namespace) */ + int top = 0; /* Top symbol (set default namespace) */ int create = 0; char *xpath; - int nr; + int nr = 0; + int hit = 0; if (xt == NULL){ /* No xml */ clicon_err(OE_XML, EINVAL, "No XML argument"); @@ -1181,12 +1182,10 @@ xml_default1(yang_stmt *yt, case Y_CONTAINER: if (yang_find(yc, Y_PRESENCE, NULL) == NULL){ /* Check when statement from uses or augment */ - if ((xpath = yang_when_xpath_get(yc)) != NULL){ - if ((nr = xpath_vec_bool(xt, yang_when_nsc_get(yc), "%s", xpath)) < 0) - goto done; - if (nr == 0) - break; /* Do not create default if xpath fails */ - } + if (yang_when_xpath(NULL, xt, yc, &hit, &nr, &xpath) < 0) + goto done; + if (hit && nr == 0) + break; /* Do not create default if xpath fails */ /* If this is non-presence, (and it does not exist in xt) call * recursively and create nodes if any default value exist first. * Then continue and populate? @@ -2257,3 +2256,70 @@ xml_copy_marked(cxobj *x0, return retval; } +/*! Check when condition + * + * @param[in] h Clixon handle + * @param[in] xn XML node, can be NULL, in which case it is added as dummy under xp + * @param[in] xp XML parent + * @param[in] ys Yang node + * First variants of WHEN: Augmented and uses when using special info in node + * Second variant of when, actual "when" sub-node RFC 7950 Sec 7.21.5. Can only be one. + */ +int +yang_when_xpath(cxobj *xn, + cxobj *xp, + yang_stmt *yn, + int *hit, + int *nrp, + char **xpathp) +{ + int retval = 1; + yang_stmt *yc; + char *xpath = NULL; + cxobj *x = NULL; + int nr = 0; + cvec *nsc = NULL; + int xmalloc = 0; /* ugly help variable to clean temporary object */ + int nscmalloc = 0; /* ugly help variable to remove */ + + /* First variant */ + if ((xpath = yang_when_xpath_get(yn)) != NULL){ + x = xp; + nsc = yang_when_nsc_get(yn); + *hit = 1; + } + /* Second variant */ + else if ((yc = yang_find(yn, Y_WHEN, NULL)) != NULL){ + xpath = yang_argument_get(yc); /* "when" has xpath argument */ + /* Create dummy */ + if (xn == NULL){ + if ((x = xml_new(yang_argument_get(yn), xp, CX_ELMNT)) == NULL) + goto done; + xml_spec_set(x, yn); + xmalloc++; + } + else + x = xn; + if (xml_nsctx_yang(yn, &nsc) < 0) + goto done; + nscmalloc++; + *hit = 1; + } + else + *hit = 0; + if (x && xpath){ + if ((nr = xpath_vec_bool(x, nsc, "%s", xpath)) < 0) + goto done; + } + if (nrp) + *nrp = nr; + if (xpathp) + *xpathp = xpath; + retval = 0; + done: + if (xmalloc) + xml_purge(x); + if (nsc && nscmalloc) + xml_nsctx_free(nsc); + return retval; +} diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index 7c1af5a1..c90fe4ae 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -340,11 +340,13 @@ yang_flag_reset(yang_stmt *ys, * @param[in] ys Yang statement * @retval xpath xpath should evaluate to true at validation * @retval NULL Not set + * Note xpath context is PARENT which is different from when actual when child which is + * child itself */ char* yang_when_xpath_get(yang_stmt *ys) { - return ys->ys_when_xpath; + return ys->ys_when_xpath; } /*! Set yang xpath and namespace context for "when"-associated augment diff --git a/test/test_leafref.sh b/test/test_leafref.sh index a7a414c3..b425f61e 100755 --- a/test/test_leafref.sh +++ b/test/test_leafref.sh @@ -140,7 +140,7 @@ new "leafref add non-existing ref" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLOeth3
10.0.4.6
]]>]]>" "^]]>]]>$" new "leafref validate" -expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationbad-elementeth3errorLeafref validation failed: No leaf eth3 matching path /if:interfaces/if:interface/if:name]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationbad-elementeth3errorLeafref validation failed: No leaf eth3 matching path /if:interfaces/if:interface/if:name in module example]]>]]>$" #new "leafref wrong ref" #expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLOeth3
10.0.4.6
]]>]]>" "^]]>]]>$" @@ -170,7 +170,7 @@ new "leafref delete leaf" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLOeth0]]>]]>" "^" new "leafref validate (should fail)" -expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationbad-elementeth0errorLeafref validation failed: No leaf eth0 matching path /if:interfaces/if:interface/if:name]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationbad-elementeth0errorLeafref validation failed: No leaf eth0 matching path /if:interfaces/if:interface/if:name in module example]]>]]>$" new "leafref discard-changes" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" diff --git a/test/test_leafref_augment.sh b/test/test_leafref_augment.sh index 4c4c046a..c6472be9 100755 --- a/test/test_leafref_augment.sh +++ b/test/test_leafref_augment.sh @@ -209,7 +209,7 @@ new "leafref augment+leafref config wrong ref" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO$XML]]>]]>" "^]]>]]>$" new "leafref augment+leafref validate wrong ref" -expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationbad-elementxxxerrorLeafref validation failed: No leaf xxx matching path /ex:sender/ex:name]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationbad-elementxxxerrorLeafref validation failed: No leaf xxx matching path /ex:sender/ex:name in module augment]]>]]>$" new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" diff --git a/test/test_leafref_state.sh b/test/test_leafref_state.sh index 9ed123ad..feb4032e 100755 --- a/test/test_leafref_state.sh +++ b/test/test_leafref_state.sh @@ -168,10 +168,10 @@ expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failedxerrorLeafref validation failed: No leaf x matching path /ex:sender-config/ex:name. Internal error, state callback returned invalid XML]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failedxerrorLeafref validation failed: No leaf x matching path /ex:sender-config/ex:name in module leafref. Internal error, state callback returned invalid XML]]>]]>$" new "netconf get / state-only should fail" -expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failedxerrorLeafref validation failed: No leaf x matching path /ex:sender-config/ex:name. Internal error, state callback returned invalid XML]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failedxerrorLeafref validation failed: No leaf x matching path /ex:sender-config/ex:name in module leafref. Internal error, state callback returned invalid XML]]>]]>$" new "netconf get / config-only ok" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^y]]>]]>$" diff --git a/test/test_when_must.sh b/test/test_when_must.sh index 12c94da3..83bc9a6a 100755 --- a/test/test_when_must.sh +++ b/test/test_when_must.sh @@ -117,7 +117,7 @@ new "when get config" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^directr2staticr1]]>]]>$" new "when: validate fail" -expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failederrorFailed WHEN condition of static-routes in module example]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failederrorFailed WHEN condition of static-routes in module example (WHEN xpath is ../type='static')]]>]]>$" new "when: discard-changes" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" diff --git a/test/test_xpath_functions.sh b/test/test_xpath_functions.sh index f996ad1a..3b2cfbee 100755 --- a/test/test_xpath_functions.sh +++ b/test/test_xpath_functions.sh @@ -124,7 +124,7 @@ new "Set site to fie which invalidates the when contains" expecteof "$clixon_netconf -qf $cfg -D $DBG" 0 "$DEFAULTHELLOfie]]>]]>" "^]]>]]>" new "netconf validate not OK" -expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failederrorFailed WHEN condition of site in module example]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failederrorFailed WHEN condition of site in module example (WHEN xpath is contains(../../class,'foo') or contains(../../class,'bar'))]]>]]>$" new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" @@ -140,13 +140,13 @@ new "Change type to atm" expecteof "$clixon_netconf -qf $cfg -D $DBG" 0 "$DEFAULTHELLOe0atm]]>]]>" "^]]>]]>" new "netconf validate not OK (mtu not allowed)" -expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failederrorFailed augmented 'when' condition 'derived-from(type, \"ex:ethernet\")' of node 'mtu' in module 'example']]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failederrorFailed WHEN condition of mtu in module example (WHEN xpath is derived-from(type, \"ex:ethernet\"))]]>]]>$" new "Change type to ethernet (self)" expecteof "$clixon_netconf -qf $cfg -D $DBG" 0 "$DEFAULTHELLOe0ethernet]]>]]>" "^]]>]]>" new "netconf validate not OK (mtu not allowed on self)" -expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failederrorFailed augmented 'when' condition 'derived-from(type, \"ex:ethernet\")' of node 'mtu' in module 'example']]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failederrorFailed WHEN condition of mtu in module example (WHEN xpath is derived-from(type, \"ex:ethernet\"))]]>]]>$" new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" @@ -162,7 +162,7 @@ new "Change type to atm" expecteof "$clixon_netconf -qf $cfg -D $DBG" 0 "$DEFAULTHELLOe0atm]]>]]>" "^]]>]]>" new "netconf validate not OK (crc not allowed)" -expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failederrorFailed augmented 'when' condition 'derived-from-or-self(type, \"ex:ethernet\")' of node 'crc' in module 'example']]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failederrorFailed WHEN condition of crc in module example (WHEN xpath is derived-from-or-self(type, \"ex:ethernet\"))]]>]]>$" new "Change type to ethernet (self)" expecteof "$clixon_netconf -qf $cfg -D $DBG" 0 "$DEFAULTHELLOe0ethernet]]>]]>" "^]]>]]>" From efcfb176ae1cd05d5f629132ca049339a7037e33 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Tue, 3 Aug 2021 11:15:45 +0200 Subject: [PATCH 36/49] Fixed: The auto-cli identityref did not expand identities in grouping/usecases properly. --- CHANGELOG.md | 1 + apps/cli/cli_generate.c | 72 ++++++++++++++++++----------------- test/test_identity.sh | 83 +++++++++++++++++++++++++++++++++++------ 3 files changed, 109 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 801c8d64..fe9b2649 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,7 @@ Users may have to change how they access the system ### Corrected Bugs +* Fixed: The auto-cli identityref did not expand identities in grouping/usecases properly. * Fixed: [OpenConfig BGP afi-safi and when condition issues #249](https://github.com/clicon/clixon/issues/249) * YANG when was not properly implemented for default values * Fixed: SEGV in clixon_netconf_lib functions from internal errors including validation. diff --git a/apps/cli/cli_generate.c b/apps/cli/cli_generate.c index 701288cc..2f37a5aa 100644 --- a/apps/cli/cli_generate.c +++ b/apps/cli/cli_generate.c @@ -184,7 +184,7 @@ yang2cli_helptext(cbuf *cb, /*! Generate identityref statements for CLI variables * @param[in] ys Yang statement - * @param[in] ytype Yang union type being resolved + * @param[in] ytype Resolved yang type. * @param[in] helptext CLI help text * @param[out] cb Buffer where cligen code is written * @see yang2cli_var_sub Its sub-function @@ -208,42 +208,44 @@ yang2cli_var_identityref(yang_stmt *ys, yang_stmt *yprefix; yang_stmt *yspec; - if ((ybaseref = yang_find(ytype, Y_BASE, NULL)) != NULL && - (ybaseid = yang_find_identity(ys, yang_argument_get(ybaseref))) != NULL){ - idrefvec = yang_cvec_get(ybaseid); - if (cvec_len(idrefvec) > 0){ - /* Add a wildchar string first -let validate take it for default prefix */ - cprintf(cb, ">"); - yang2cli_helptext(cb, helptext); - cprintf(cb, "|<%s:%s choice:", yang_argument_get(ys), cvtypestr); - yspec = ys_spec(ys); - i = 0; - while ((cv = cvec_each(idrefvec, cv)) != NULL){ - if (nodeid_split(cv_name_get(cv), &prefix, &id) < 0) - goto done; - /* Translate from module-name(prefix) to global prefix - * This is really a kludge for true identityref prefix handling - * IDENTITYREF_KLUDGE - * This is actually quite complicated: the cli needs to generate - * a netconf statement with correct xmlns binding - */ - if ((ymod = yang_find_module_by_name(yspec, prefix)) != NULL && - (yprefix = yang_find(ymod, Y_PREFIX, NULL)) != NULL){ - if (i++) - cprintf(cb, "|"); - cprintf(cb, "%s:%s", yang_argument_get(yprefix), id); - } - if (prefix){ - free(prefix); - prefix = NULL; - } - if (id){ - free(id); - id = NULL; - } + if ((ybaseref = yang_find(ytype, Y_BASE, NULL)) == NULL) + goto ok; + if ((ybaseid = yang_find_identity(ytype, yang_argument_get(ybaseref))) == NULL) + goto ok; + idrefvec = yang_cvec_get(ybaseid); + if (cvec_len(idrefvec) > 0){ + /* Add a wildchar string first -let validate take it for default prefix */ + cprintf(cb, ">"); + yang2cli_helptext(cb, helptext); + cprintf(cb, "|<%s:%s choice:", yang_argument_get(ys), cvtypestr); + yspec = ys_spec(ys); + i = 0; + while ((cv = cvec_each(idrefvec, cv)) != NULL){ + if (nodeid_split(cv_name_get(cv), &prefix, &id) < 0) + goto done; + /* Translate from module-name(prefix) to global prefix + * This is really a kludge for true identityref prefix handling + * IDENTITYREF_KLUDGE + * This is actually quite complicated: the cli needs to generate + * a netconf statement with correct xmlns binding + */ + if ((ymod = yang_find_module_by_name(yspec, prefix)) != NULL && + (yprefix = yang_find(ymod, Y_PREFIX, NULL)) != NULL){ + if (i++) + cprintf(cb, "|"); + cprintf(cb, "%s:%s", yang_argument_get(yprefix), id); + } + if (prefix){ + free(prefix); + prefix = NULL; + } + if (id){ + free(id); + id = NULL; } } } + ok: retval = 0; done: if (prefix) @@ -371,7 +373,7 @@ static int yang2cli_var_union(clicon_handle h, yang_stmt *ys, char *origtype, * patterns, (eg regexp:"[0.9]*"). * @param[in] h Clixon handle * @param[in] ys Yang statement - * @param[in] ytype Yang union type being resolved + * @param[in] ytype Resolved yang type. * @param[in] helptext CLI help text * @param[in] cvtype * @param[in] options Flags field of optional values, see YANG_OPTIONS_* diff --git a/test/test_identity.sh b/test/test_identity.sh index d520205d..bd9d3b9f 100755 --- a/test/test_identity.sh +++ b/test/test_identity.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash # Identity and identityref tests # Example from RFC7950 Sec 7.18 and 9.10 +# Extended with a submodule # Magic line must be first in script (see README.md) s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi @@ -39,7 +40,7 @@ EOF # with two changes: the leaf statement is in the original module and # a transitive dependent identifier (foo) cat < $dir/example-crypto-base.yang - module example-crypto-base { +module example-crypto-base { yang-version 1.1; namespace "urn:example:crypto-base"; prefix "crypto"; @@ -59,8 +60,7 @@ cat < $dir/example-crypto-base.yang "Base identity used to identify public-key crypto algorithms."; } - } - +} EOF cat < $dir/example-des.yang @@ -85,10 +85,11 @@ cat < $dir/example-des.yang EOF cat < $fyang - module example { +module example-my-crypto { yang-version 1.1; namespace "urn:example:my-crypto"; prefix mc; + include "example-sub"; import "example-crypto-base" { prefix "crypto"; } @@ -141,7 +142,46 @@ cat < $fyang base mc:empty; } } + uses myname; +} +EOF + +# Only included from sub-module +# Introduce an identity only visible by example-sub submodule +cat < $dir/example-extra.yang +module example-extra { + yang-version 1.1; + namespace "urn:example:extra"; + prefix ee; + identity extra-base; + identity extra-new{ + base ee:extra-base; + } + identity extra-old{ + base ee:extra-base; + } +} +EOF + +# Sub-module +cat < $dir/example-sub.yang +submodule example-sub { + yang-version 1.1; + belongs-to example-my-crypto { + prefix mc; } + import example-extra { + prefix ee; + } + grouping myname { + leaf sub-name { + description "Uses identity accessed by only the submodule"; + type identityref { + base ee:extra-base; + } + } + } +} EOF new "test params: -f $cfg" @@ -269,42 +309,61 @@ expectpart "$($clixon_cli -1 -f $cfg -l o validate)" 255 "Validate failed. Edit new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" +# Special case sub-module +new "auto-cli cli expansion submodule identity" +expectpart "$(echo "set sub-name ?" | $clixon_cli -f $cfg 2>&1)" 0 "set sub-name" "ee:extra-new" "ee:extra-old" + +new "cli add identity" +expectpart "$($clixon_cli -1 -f $cfg -l o set sub-name ee:extra-new)" 0 "" + +new "cli validate submodule identity" +expectpart "$($clixon_cli -1 -f $cfg -l o validate)" 0 "" + +new "cli add wrong identity" +expectpart "$($clixon_cli -1 -f $cfg -l o set sub-name ee:foo)" 0 "" + +new "cli validate wrong id (expect fail)" +expectpart "$($clixon_cli -1 -f $cfg -l o validate 2>&1)" 255 "Identityref validation failed, ee:foo not derived from extra-base" + +new "netconf discard-changes" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" + # restconf and identities: # 1. set identity in own module with restconf (PUT and POST), read it with restconf and netconf # 2. set identity in other module with restconf , read it with restconf and netconf # 3. set identity in other module with netconf, read it with restconf and netconf new "restconf add own identity" -expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/example:crypto -d '{"example:crypto":"example:aes"}')" 0 "HTTP/$HVER 201" +expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/example-my-crypto:crypto -d '{"example-my-crypto:crypto":"example-my-crypto:aes"}')" 0 "HTTP/$HVER 201" new "restconf get own identity" -expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example:crypto)" 0 "HTTP/$HVER 200" '{"example:crypto":"aes"}' +expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example-my-crypto:crypto)" 0 "HTTP/$HVER 200" '{"example-my-crypto:crypto":"aes"}' new "netconf get own identity as set by restconf" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^aes" new "restconf delete identity" -expectpart "$(curl $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data/example:crypto)" 0 "HTTP/$HVER 204" +expectpart "$(curl $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data/example-my-crypto:crypto)" 0 "HTTP/$HVER 204" # 2. set identity in other module with restconf , read it with restconf and netconf if ! $YANG_UNKNOWN_ANYDATA ; then new "restconf add POST instead of PUT (should fail)" -expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/example:crypto -d '{"example:crypto":"example-des:des3"}')" 0 "HTTP/$HVER 400" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"crypto"},"error-severity":"error","error-message":"Failed to find YANG spec of XML node: crypto with parent: crypto in namespace: urn:example:my-crypto"}}}' +expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/example-my-crypto:crypto -d '{"example-my-crypto:crypto":"example-des:des3"}')" 0 "HTTP/$HVER 400" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"crypto"},"error-severity":"error","error-message":"Failed to find YANG spec of XML node: crypto with parent: crypto in namespace: urn:example:my-crypto"}}}' fi # Alternative error: #'{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"crypto"},"error-severity":"error","error-message":"Leaf contains sub-element"}}}' new "restconf add other (des) identity using POST" -expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data -d '{"example:crypto":"example-des:des3"}')" 0 "HTTP/$HVER 201" "Location: $RCPROTO://localhost/restconf/data/example:crypto" +expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data -d '{"example-my-crypto:crypto":"example-des:des3"}')" 0 "HTTP/$HVER 201" "Location: $RCPROTO://localhost/restconf/data/example-my-crypto:crypto" new "restconf get other identity" -expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example:crypto)" 0 "HTTP/$HVER 200" '{"example:crypto":"example-des:des3"}' +expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example-my-crypto:crypto)" 0 "HTTP/$HVER 200" '{"example-my-crypto:crypto":"example-des:des3"}' new "netconf get other identity" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^des:des3" new "restconf delete identity" -expectpart "$(curl $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data/example:crypto)" 0 "HTTP/$HVER 204" +expectpart "$(curl $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data/example-my-crypto:crypto)" 0 "HTTP/$HVER 204" # 3. set identity in other module with netconf, read it with restconf and netconf new "netconf set other identity" @@ -314,7 +373,7 @@ new "netconf commit" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" new "restconf get other identity (set by netconf)" -expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example:crypto)" 0 "HTTP/$HVER 200" '{"example:crypto":"example-des:des3"}' +expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example-my-crypto:crypto)" 0 "HTTP/$HVER 200" '{"example-my-crypto:crypto":"example-des:des3"}' new "netconf get other identity" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^des:des3" From 8675620d224c278f7bc55a1b1ca95219ce0b9afd Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Tue, 3 Aug 2021 12:53:37 +0200 Subject: [PATCH 37/49] * Added linenumbers to all YANG symbols for better debug and errors * Improved error messages for YANG identityref:s and leafref:s by adding original line numbers --- CHANGELOG.md | 5 +++ lib/clixon/clixon_yang.h | 4 +++ lib/src/clixon_validate.c | 29 +++++++++++++---- lib/src/clixon_yang.c | 57 +++++++++++++++++++++++++++++++++ lib/src/clixon_yang_internal.h | 3 +- lib/src/clixon_yang_parse.y | 3 +- lib/src/clixon_yang_parse_lib.c | 3 ++ test/test_identity.sh | 8 ++--- test/test_leafref.sh | 4 +-- test/test_leafref_augment.sh | 2 +- test/test_leafref_state.sh | 4 +-- 11 files changed, 105 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe9b2649..09b6e184 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,11 @@ Users may have to change how they access the system * Removed default of `CLICON_RESTCONF_INSTALLDIR` * The default behaviour is changed to use the config $(sbindir) to locate `clixon_restconf` when starting restconf internally +### Minor features + +* Added linenumbers to all YANG symbols for better debug and errors + * Improved error messages for YANG identityref:s and leafref:s by adding original line numbers + ### Corrected Bugs * Fixed: The auto-cli identityref did not expand identities in grouping/usecases properly. diff --git a/lib/clixon/clixon_yang.h b/lib/clixon/clixon_yang.h index 437318a7..879e6b8c 100644 --- a/lib/clixon/clixon_yang.h +++ b/lib/clixon/clixon_yang.h @@ -212,6 +212,10 @@ char *yang_when_xpath_get(yang_stmt *ys); int yang_when_xpath_set(yang_stmt *ys, char *xpath); cvec *yang_when_nsc_get(yang_stmt *ys); int yang_when_nsc_set(yang_stmt *ys, cvec *nsc); +const char *yang_filename_get(yang_stmt *ys); +int yang_filename_set(yang_stmt *ys, const char *filename); +int yang_linenum_get(yang_stmt *ys); +int yang_linenum_set(yang_stmt *ys, int linenum); /* Other functions */ yang_stmt *yspec_new(void); diff --git a/lib/src/clixon_validate.c b/lib/src/clixon_validate.c index 48070400..8923dfe6 100644 --- a/lib/src/clixon_validate.c +++ b/lib/src/clixon_validate.c @@ -119,7 +119,14 @@ validate_leafref(cxobj *xt, if ((leafrefbody = xml_body(xt)) == NULL) goto ok; if ((ypath = yang_find(ytype, Y_PATH, NULL)) == NULL){ - if (xret && netconf_missing_element_xml(xret, "application", yang_argument_get(ytype), "Leafref requires path statement") < 0) + if ((cberr = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(cberr, "Leafref requires path statement"); + if (xret && netconf_missing_element_xml(xret, "application", + yang_argument_get(ytype), + cbuf_get(cberr)) < 0) goto done; goto fail; } @@ -142,7 +149,11 @@ validate_leafref(cxobj *xt, goto done; } ymod = ys_module(ys); - cprintf(cberr, "Leafref validation failed: No leaf %s matching path %s in module %s", leafrefbody, path, yang_argument_get(ymod)); + cprintf(cberr, "Leafref validation failed: No leaf %s matching path %s in %s.yang:%d", + leafrefbody, + path, + yang_argument_get(ymod), + yang_linenum_get(ys)); if (xret && netconf_bad_element_xml(xret, "application", leafrefbody, cbuf_get(cberr)) < 0) goto done; goto fail; @@ -241,8 +252,11 @@ validate_identityref(cxobj *xt, #endif } if (ymod == NULL){ - cprintf(cberr, "Identityref validation failed, %s not derived from %s", - node, yang_argument_get(ybaseid)); + cprintf(cberr, "Identityref validation failed, %s not derived from %s in %s.yang:%d", + node, + yang_argument_get(ybaseid), + yang_argument_get(ys_module(ybaseid)), + yang_linenum_get(ybaseid)); if (xret && netconf_operation_failed_xml(xret, "application", cbuf_get(cberr)) < 0) goto done; goto fail; @@ -254,8 +268,11 @@ validate_identityref(cxobj *xt, */ idrefvec = yang_cvec_get(ybaseid); if (cvec_find(idrefvec, idref) == NULL){ - cprintf(cberr, "Identityref validation failed, %s not derived from %s", - node, yang_argument_get(ybaseid)); + cprintf(cberr, "Identityref validation failed, %s not derived from %s in %s.yang:%d", + node, + yang_argument_get(ybaseid), + yang_argument_get(ys_module(ybaseid)), + yang_linenum_get(ybaseid)); if (xret && netconf_operation_failed_xml(xret, "application", cbuf_get(cberr)) < 0) goto done; goto fail; diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index c90fe4ae..7234fd09 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -416,6 +416,61 @@ yang_when_nsc_set(yang_stmt *ys, return retval; } +/*! Get yang filename for error/debug purpose + * + * @param[in] ys Yang statement + * @retval filename + * @note there maye not always be a "filename" in case the yang is read from memory + */ +const char * +yang_filename_get(yang_stmt *ys) +{ + return ys->ys_filename; +} + +/*! Set yang filename for error/debug purpose + * + * @param[in] ys Yang statement + * @param[in] filename + * @retval 0 OK + * @retval -1 Error + * @note there maye not always be a "filename" in case the yang is read from memory + */ +int +yang_filename_set(yang_stmt *ys, + const char *filename) +{ + if ((ys->ys_filename = strdup(filename)) == NULL){ + clicon_err(OE_UNIX, errno, "strdup"); + return -1; + } + return 0; +} + +/*! Get line number of yang filename for error/debug purpose + * + * @param[in] ys Yang statement + * @retval linenum + */ +int +yang_linenum_get(yang_stmt *ys) +{ + return ys->ys_linenum; +} + +/*! Set line number of yang filename for error/debug purpose + * + * @param[in] ys Yang statement + * @param[in] linenum + */ +int +yang_linenum_set(yang_stmt *ys, + int linenum) +{ + ys->ys_linenum = linenum; + return 0; +} + /* End access functions */ /*! Create new yang specification @@ -498,6 +553,8 @@ ys_free1(yang_stmt *ys, cvec_free(ys->ys_when_nsc); if (ys->ys_stmt) free(ys->ys_stmt); + if (ys->ys_filename) + free(ys->ys_filename); if (self) free(ys); return 0; diff --git a/lib/src/clixon_yang_internal.h b/lib/src/clixon_yang_internal.h index d14e98ac..a1af0679 100644 --- a/lib/src/clixon_yang_internal.h +++ b/lib/src/clixon_yang_internal.h @@ -93,7 +93,8 @@ struct yang_stmt{ char *ys_when_xpath; /* Special conditional for a "when"-associated augment/uses xpath */ cvec *ys_when_nsc; /* Special conditional for a "when"-associated augment/uses namespace ctx */ int _ys_vector_i; /* internal use: yn_each */ - + char *ys_filename; /* For debug/errors: filename (only (sub)modules) */ + int ys_linenum; /* For debug/errors: line number (in ys_filename) */ }; diff --git a/lib/src/clixon_yang_parse.y b/lib/src/clixon_yang_parse.y index 6c44b2b9..3c144dcf 100644 --- a/lib/src/clixon_yang_parse.y +++ b/lib/src/clixon_yang_parse.y @@ -318,6 +318,7 @@ ysp_add(clixon_yang_yacc *yy, goto err; if (ys_parse_sub(ys, extra) < 0) /* Check statement-specific syntax */ goto err2; /* dont free since part of tree */ + yang_linenum_set(ys, yy->yy_linenum); /* For error/debugging */ // done: return ys; err: @@ -662,7 +663,7 @@ yin_element_stmt1 : K_YIN_ELEMENT bool_str stmtend {free($2);} /* Identity */ identity_stmt : K_IDENTITY identifier_str ';' - { if (ysp_add(_yy, Y_IDENTITY, $2, NULL) == NULL) _YYERROR("identity_stmt"); + { if (ysp_add(_yy, Y_IDENTITY, $2, NULL) == NULL) _YYERROR("identity_stmt"); _PARSE_DEBUG("identity-stmt -> IDENTITY string ;"); } | K_IDENTITY identifier_str diff --git a/lib/src/clixon_yang_parse_lib.c b/lib/src/clixon_yang_parse_lib.c index c472f292..28c52d68 100644 --- a/lib/src/clixon_yang_parse_lib.c +++ b/lib/src/clixon_yang_parse_lib.c @@ -773,6 +773,9 @@ yang_parse_str(char *str, goto done; } ymod = yy.yy_module; + /* Add filename for debugging and errors, see also ys_linenum on (each symbol?) */ + if (yang_filename_set(ymod, name) < 0) + goto done; done: ystack_pop(&yy); if (yy.yy_stack) diff --git a/test/test_identity.sh b/test/test_identity.sh index bd9d3b9f..3e900669 100755 --- a/test/test_identity.sh +++ b/test/test_identity.sh @@ -64,7 +64,7 @@ module example-crypto-base { EOF cat < $dir/example-des.yang - module example-des { +module example-des { yang-version 1.1; namespace "urn:example:des"; prefix "des"; @@ -81,7 +81,7 @@ cat < $dir/example-des.yang base "crypto:symmetric-key"; description "Triple DES crypto algorithm."; } - } +} EOF cat < $fyang @@ -252,7 +252,7 @@ new "Set crypto to foo:bar" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLOfoo:bar]]>]]>" "^]]>]]>$" new "netconf validate (expect fail)" -expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failederrorIdentityref validation failed, foo:bar not derived from crypto-alg]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failederrorIdentityref validation failed, foo:bar not derived from crypto-alg in example-crypto-base.yang:[0-9]*]]>]]>$" new "cli set crypto to mc:aes" expectpart "$($clixon_cli -1 -f $cfg -l o set crypto mc:aes)" 0 "^$" @@ -282,7 +282,7 @@ new "Netconf set undefined acl-type" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLOxundefined]]>]]>" "^]]>]]>$" new "netconf validate fail" -expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failederrorIdentityref validation failed, undefined not derived from acl-base]]>]]>" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failederrorIdentityref validation failed, undefined not derived from acl-base in example-my-crypto.yang:[0-9]*]]>]]>" new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" diff --git a/test/test_leafref.sh b/test/test_leafref.sh index b425f61e..a86e411d 100755 --- a/test/test_leafref.sh +++ b/test/test_leafref.sh @@ -140,7 +140,7 @@ new "leafref add non-existing ref" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLOeth3
10.0.4.6
]]>]]>" "^]]>]]>$" new "leafref validate" -expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationbad-elementeth3errorLeafref validation failed: No leaf eth3 matching path /if:interfaces/if:interface/if:name in module example]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationbad-elementeth3errorLeafref validation failed: No leaf eth3 matching path /if:interfaces/if:interface/if:name in example.yang:[0-9]*]]>]]>$" #new "leafref wrong ref" #expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLOeth3
10.0.4.6
]]>]]>" "^]]>]]>$" @@ -170,7 +170,7 @@ new "leafref delete leaf" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLOeth0]]>]]>" "^" new "leafref validate (should fail)" -expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationbad-elementeth0errorLeafref validation failed: No leaf eth0 matching path /if:interfaces/if:interface/if:name in module example]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationbad-elementeth0errorLeafref validation failed: No leaf eth0 matching path /if:interfaces/if:interface/if:name in example.yang:[0-9]*]]>]]>$" new "leafref discard-changes" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" diff --git a/test/test_leafref_augment.sh b/test/test_leafref_augment.sh index c6472be9..4c5032a2 100755 --- a/test/test_leafref_augment.sh +++ b/test/test_leafref_augment.sh @@ -209,7 +209,7 @@ new "leafref augment+leafref config wrong ref" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO$XML]]>]]>" "^]]>]]>$" new "leafref augment+leafref validate wrong ref" -expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationbad-elementxxxerrorLeafref validation failed: No leaf xxx matching path /ex:sender/ex:name in module augment]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationbad-elementxxxerrorLeafref validation failed: No leaf xxx matching path /ex:sender/ex:name in augment.yang:[0-9]*]]>]]>$" new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" diff --git a/test/test_leafref_state.sh b/test/test_leafref_state.sh index feb4032e..31ce8cd5 100755 --- a/test/test_leafref_state.sh +++ b/test/test_leafref_state.sh @@ -168,10 +168,10 @@ expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failedxerrorLeafref validation failed: No leaf x matching path /ex:sender-config/ex:name in module leafref. Internal error, state callback returned invalid XML]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failedxerrorLeafref validation failed: No leaf x matching path /ex:sender-config/ex:name in leafref.yang:[0-9]*. Internal error, state callback returned invalid XML]]>]]>$" new "netconf get / state-only should fail" -expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failedxerrorLeafref validation failed: No leaf x matching path /ex:sender-config/ex:name in module leafref. Internal error, state callback returned invalid XML]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failedxerrorLeafref validation failed: No leaf x matching path /ex:sender-config/ex:name in leafref.yang:[0-9]*. Internal error, state callback returned invalid XML]]>]]>$" new "netconf get / config-only ok" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^y]]>]]>$" From dc2b3a80ca1818161f9a99380be507c0d2a7435e Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Tue, 3 Aug 2021 13:38:31 +0200 Subject: [PATCH 38/49] * Fixed: YANG when was not properly implemented for LEAF default values --- lib/clixon/clixon_xml_map.h | 2 +- lib/src/clixon_validate.c | 2 +- lib/src/clixon_xml_map.c | 28 +++++++++++++--------------- test/test_leaf_default.sh | 20 ++++++++++++++++++++ 4 files changed, 35 insertions(+), 17 deletions(-) diff --git a/lib/clixon/clixon_xml_map.h b/lib/clixon/clixon_xml_map.h index feba656e..158d2cb8 100644 --- a/lib/clixon/clixon_xml_map.h +++ b/lib/clixon/clixon_xml_map.h @@ -76,6 +76,6 @@ int assign_namespace_body(cxobj *x0, cxobj *x1); int xml_merge(cxobj *x0, cxobj *x1, yang_stmt *yspec, char **reason); int yang_enum_int_value(cxobj *node, int32_t *val); int xml_copy_marked(cxobj *x0, cxobj *x1); -int yang_when_xpath(cxobj *xn, cxobj *xp, yang_stmt *yn, int *hit, int *nrp, char **xpathp); +int yang_check_when_xpath(cxobj *xn, cxobj *xp, yang_stmt *yn, int *hit, int *nrp, char **xpathp); #endif /* _CLIXON_XML_MAP_H_ */ diff --git a/lib/src/clixon_validate.c b/lib/src/clixon_validate.c index 8923dfe6..710d209b 100644 --- a/lib/src/clixon_validate.c +++ b/lib/src/clixon_validate.c @@ -1247,7 +1247,7 @@ xml_yang_validate_all(clicon_handle h, nsc = NULL; } } - if (yang_when_xpath(xt, xml_parent(xt), ys, &hit, &nr, &xpath) < 0) + if (yang_check_when_xpath(xt, xml_parent(xt), ys, &hit, &nr, &xpath) < 0) goto done; if (hit && nr == 0){ if ((cb = cbuf_new()) == NULL){ diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 5c77c22d..e288e129 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -1164,13 +1164,11 @@ xml_default1(yang_stmt *yt, switch (yang_keyword_get(yc)){ case Y_LEAF: if (!cv_flag(yang_cv_get(yc), V_UNSET)){ /* Default value exists */ - /* Check when statement from uses or augment */ - if ((xpath = yang_when_xpath_get(yc)) != NULL){ - if ((nr = xpath_vec_bool(xt, yang_when_nsc_get(yc), "%s", xpath)) < 0) - goto done; - if (nr == 0) - break; /* Do not create default if xpath fails */ - } + /* Check when condition */ + if (yang_check_when_xpath(NULL, xt, yc, &hit, &nr, &xpath) < 0) + goto done; + if (hit && nr == 0) + break; /* Do not create default if xpath fails */ if (xml_find_type(xt, NULL, yang_argument_get(yc), CX_ELMNT) == NULL){ /* No such child exist, create this leaf */ if (xml_default_create(yc, xt, top) < 0) @@ -1181,8 +1179,8 @@ xml_default1(yang_stmt *yt, break; case Y_CONTAINER: if (yang_find(yc, Y_PRESENCE, NULL) == NULL){ - /* Check when statement from uses or augment */ - if (yang_when_xpath(NULL, xt, yc, &hit, &nr, &xpath) < 0) + /* Check when condition */ + if (yang_check_when_xpath(NULL, xt, yc, &hit, &nr, &xpath) < 0) goto done; if (hit && nr == 0) break; /* Do not create default if xpath fails */ @@ -2266,12 +2264,12 @@ xml_copy_marked(cxobj *x0, * Second variant of when, actual "when" sub-node RFC 7950 Sec 7.21.5. Can only be one. */ int -yang_when_xpath(cxobj *xn, - cxobj *xp, - yang_stmt *yn, - int *hit, - int *nrp, - char **xpathp) +yang_check_when_xpath(cxobj *xn, + cxobj *xp, + yang_stmt *yn, + int *hit, + int *nrp, + char **xpathp) { int retval = 1; yang_stmt *yc; diff --git a/test/test_leaf_default.sh b/test/test_leaf_default.sh index 4dbd1032..d36553c0 100755 --- a/test/test_leaf_default.sh +++ b/test/test_leaf_default.sh @@ -67,6 +67,19 @@ module example{ default 31; /* should be set on startup */ } } + /* Extra rules to check when condition */ + leaf npleaf{ + when "../s3 = '99'"; + type uint32; + default 98; + } + container npcont{ + when "../s3 = '99'"; + leaf npext{ + type uint32; + default 99; + } + } } container p4{ presence "A presence container"; @@ -144,6 +157,13 @@ expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^$XMLafalsefalse]]>]]>$" +# Set s3 leaf to 99 triggering when condition for default values +new "Set s3 to 99" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO99]]>]]>" "^]]>]]>$" + +new "get config np3 with npleaf and npext" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^99319899]]>]]>$" + if [ $BE -ne 0 ]; then new "Kill backend" # Check if premature kill From c743b90fddc475ef5c374410b8062194c8758895 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Thu, 5 Aug 2021 09:59:20 +0200 Subject: [PATCH 39/49] * Fixed: Yang patterns: \n and other non-printable characters were broken * Example: Clixon interpereted them two characters: `\\ n` instead of ascii 10 --- CHANGELOG.md | 2 + lib/clixon/clixon_string.h | 2 + lib/src/clixon_string.c | 77 +++++++++++++++++++++++++++++++++ lib/src/clixon_yang_parse_lib.c | 12 +++++ test/test_pattern.sh | 14 ++++++ 5 files changed, 107 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09b6e184..845fccd5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,8 @@ Users may have to change how they access the system ### Corrected Bugs +* Fixed: Yang patterns: \n and other non-printable characters were broken + * Example: Clixon interpereted them two characters: `\\ n` instead of ascii 10 * Fixed: The auto-cli identityref did not expand identities in grouping/usecases properly. * Fixed: [OpenConfig BGP afi-safi and when condition issues #249](https://github.com/clicon/clixon/issues/249) * YANG when was not properly implemented for default values diff --git a/lib/clixon/clixon_string.h b/lib/clixon/clixon_string.h index fea7fd8a..689623e3 100644 --- a/lib/clixon/clixon_string.h +++ b/lib/clixon/clixon_string.h @@ -100,6 +100,8 @@ int xml_chardata_encode(char **escp, const char *fmt, ...); #endif int xml_chardata_cbuf_append(cbuf *cb, char *str); int uri_percent_decode(char *enc, char **str); +int nonprint_encode(char *orig, char **encp); + const char *clicon_int2str(const map_str2int *mstab, int i); int clicon_str2int(const map_str2int *mstab, char *str); int clicon_str2int_search(const map_str2int *mstab, char *str, int upper); diff --git a/lib/src/clixon_string.c b/lib/src/clixon_string.c index d6004a32..0e73b28a 100644 --- a/lib/src/clixon_string.c +++ b/lib/src/clixon_string.c @@ -646,6 +646,83 @@ uri_str2cvec(char *string, goto done; } +/*! Translate \n and others from \\n (two chars) to \n (one char) + * + * This is needed in yang regex it seems. + * It was triggered by eg draft-wwlh-netconf-list-pagination-00 module example-social tagline + * leaf tagline { + * type string { + * length "1..80"; + * pattern '.*[\n].*' { + * modifier invert-match; + * @param[in] orig Original string eg with \\n + * @param[out] enc Encoded string with \n, malloced. + * + * @see https://www.regular-expressions.info/nonprint.html + */ +int +nonprint_encode(char *orig, + char **encp) +{ + int retval = -1; + char *enc = NULL; + int i; + int j; + int esc = 0; + char c; + char c1; + + if (orig == NULL){ + clicon_err(OE_UNIX, EINVAL, "orig is NULL"); + goto done; + } + /* Encoded string is equal or shorter */ + if ((enc = malloc(strlen(orig)+1)) == NULL){ + clicon_err(OE_UNIX, errno, "strdup"); + goto done; + } + j = 0; + for (i=0; i Date: Thu, 5 Aug 2021 12:07:07 +0200 Subject: [PATCH 40/49] cli exclude clixon-restconf, extra cv check in default1 --- lib/src/clixon_xml_map.c | 8 +++++++- yang/clixon/clixon-config@2021-07-11.yang | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index e288e129..0422e650 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -1144,6 +1144,7 @@ xml_default1(yang_stmt *yt, char *xpath; int nr = 0; int hit = 0; + cg_var *cv; if (xt == NULL){ /* No xml */ clicon_err(OE_XML, EINVAL, "No XML argument"); @@ -1163,7 +1164,12 @@ xml_default1(yang_stmt *yt, continue; switch (yang_keyword_get(yc)){ case Y_LEAF: - if (!cv_flag(yang_cv_get(yc), V_UNSET)){ /* Default value exists */ + if ((cv = yang_cv_get(yc)) == NULL){ + clicon_err(OE_YANG,0, "Internal error: yang leaf %s not populated with cv as it should", + yang_argument_get(yc)); + goto done; + } + if (!cv_flag(cv, V_UNSET)){ /* Default value exists */ /* Check when condition */ if (yang_check_when_xpath(NULL, xt, yc, &hit, &nr, &xpath) < 0) goto done; diff --git a/yang/clixon/clixon-config@2021-07-11.yang b/yang/clixon/clixon-config@2021-07-11.yang index f4d065c8..c4dea419 100644 --- a/yang/clixon/clixon-config@2021-07-11.yang +++ b/yang/clixon/clixon-config@2021-07-11.yang @@ -654,6 +654,7 @@ module clixon-config { clixon-restconf means generate autocli for all models except clixon-restconf.yang The value can be a list of space separated module names"; + default "clixon-restconf"; } leaf CLICON_CLI_VARONLY { type int32; From d4c77ffa90447e9b898da4b993e25a334db65625 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Thu, 5 Aug 2021 12:53:26 +0200 Subject: [PATCH 41/49] Move Yang patterns: \n match from yang parse to regex compile stage --- lib/clixon/clixon_string.h | 1 - lib/src/clixon_regex.c | 10 +++++ lib/src/clixon_string.c | 77 --------------------------------- lib/src/clixon_yang_parse_lib.c | 12 ----- 4 files changed, 10 insertions(+), 90 deletions(-) diff --git a/lib/clixon/clixon_string.h b/lib/clixon/clixon_string.h index 689623e3..e1427863 100644 --- a/lib/clixon/clixon_string.h +++ b/lib/clixon/clixon_string.h @@ -100,7 +100,6 @@ int xml_chardata_encode(char **escp, const char *fmt, ...); #endif int xml_chardata_cbuf_append(cbuf *cb, char *str); int uri_percent_decode(char *enc, char **str); -int nonprint_encode(char *orig, char **encp); const char *clicon_int2str(const map_str2int *mstab, int i); int clicon_str2int(const map_str2int *mstab, char *str); diff --git a/lib/src/clixon_regex.c b/lib/src/clixon_regex.c index 60034ff4..73448bdc 100644 --- a/lib/src/clixon_regex.c +++ b/lib/src/clixon_regex.c @@ -89,6 +89,7 @@ * \p{Z} Separators [slp]? * \p{S} Symbols [mcko]? * \p{O} Other [cfon]? + * For non-printable, \n, \t, \r see https://www.regular-expressions.info/nonprint.html */ int regexp_xsd2posix(char *xsd, @@ -124,6 +125,9 @@ regexp_xsd2posix(char *xsd, case 'i': /* initial */ cprintf(cb, "[a-zA-Z_:]"); break; + case 'n': /* non-printable \n */ + cprintf(cb, "\n"); + break; case 'p': /* category escape: \p{IsCategory} */ j = i+1; if (j+2 < strlen(xsd) && @@ -161,12 +165,18 @@ regexp_xsd2posix(char *xsd, } /* if syntax error, just leave it */ break; + case 'r': /* non-printable */ + cprintf(cb, "\r"); + break; case 's': cprintf(cb, "[ \t\r\n]"); break; case 'S': cprintf(cb, "[^ \t\r\n]"); break; + case 't': /* non-printable */ + cprintf(cb, "\t"); + break; case 'w': /* word */ //cprintf(cb, "[0-9a-zA-Z_\\\\-]") cprintf(cb, "[^[:punct:][:space:][:cntrl:]]"); diff --git a/lib/src/clixon_string.c b/lib/src/clixon_string.c index 0e73b28a..d6004a32 100644 --- a/lib/src/clixon_string.c +++ b/lib/src/clixon_string.c @@ -646,83 +646,6 @@ uri_str2cvec(char *string, goto done; } -/*! Translate \n and others from \\n (two chars) to \n (one char) - * - * This is needed in yang regex it seems. - * It was triggered by eg draft-wwlh-netconf-list-pagination-00 module example-social tagline - * leaf tagline { - * type string { - * length "1..80"; - * pattern '.*[\n].*' { - * modifier invert-match; - * @param[in] orig Original string eg with \\n - * @param[out] enc Encoded string with \n, malloced. - * - * @see https://www.regular-expressions.info/nonprint.html - */ -int -nonprint_encode(char *orig, - char **encp) -{ - int retval = -1; - char *enc = NULL; - int i; - int j; - int esc = 0; - char c; - char c1; - - if (orig == NULL){ - clicon_err(OE_UNIX, EINVAL, "orig is NULL"); - goto done; - } - /* Encoded string is equal or shorter */ - if ((enc = malloc(strlen(orig)+1)) == NULL){ - clicon_err(OE_UNIX, errno, "strdup"); - goto done; - } - j = 0; - for (i=0; i Date: Thu, 5 Aug 2021 15:34:38 +0300 Subject: [PATCH 42/49] fixed error in rebase --- include/clixon_custom.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/include/clixon_custom.h b/include/clixon_custom.h index d4af4463..920b45f4 100644 --- a/include/clixon_custom.h +++ b/include/clixon_custom.h @@ -112,3 +112,8 @@ * solve all usecases, such as absolute usecases where the added node is looked for */ #define XML_PARENT_CANDIDATE + +/*! Enable yang patch RFC 8072 + * Remove this when regression test + */ +#undef YANG_PATCH From 94b8f7fec58281426ae781e802889dfc6c844645 Mon Sep 17 00:00:00 2001 From: Alan Yaniger Date: Thu, 5 Aug 2021 15:39:27 +0300 Subject: [PATCH 43/49] fixed another rebase error --- apps/restconf/restconf_api.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/restconf/restconf_api.h b/apps/restconf/restconf_api.h index f94b8ce2..a44cb440 100644 --- a/apps/restconf/restconf_api.h +++ b/apps/restconf/restconf_api.h @@ -52,6 +52,4 @@ int restconf_reply_send(void *req, int code, cbuf *cb, int head); cbuf *restconf_get_indata(void *req); -#define YANG_PATCH - #endif /* _RESTCONF_API_H_ */ From 355ed7d96aa104e92ef4225dc5ee2e35a4539915 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Thu, 5 Aug 2021 21:44:52 +0200 Subject: [PATCH 44/49] * Fixed: [clixon_netconf errors on client XML Declaration with valid encoding spec](https://github.com/clicon/clixon/issues/250) --- CHANGELOG.md | 1 + lib/src/clixon_xml_parse.y | 2 +- test/test_xml.sh | 4 ++++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f09de7d..1215d15c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,6 +56,7 @@ Users may have to change how they access the system ### Corrected Bugs +* Fixed: [clixon_netconf errors on client XML Declaration with valid encoding spec](https://github.com/clicon/clixon/issues/250) * Fixed: Yang patterns: \n and other non-printable characters were broken * Example: Clixon interpereted them two characters: `\\ n` instead of ascii 10 * Fixed: The auto-cli identityref did not expand identities in grouping/usecases properly. diff --git a/lib/src/clixon_xml_parse.y b/lib/src/clixon_xml_parse.y index 9bfd6d00..19683ccb 100644 --- a/lib/src/clixon_xml_parse.y +++ b/lib/src/clixon_xml_parse.y @@ -196,7 +196,7 @@ static int xml_parse_encoding(clixon_xml_yacc *xy, char *enc) { - if(strcmp(enc, "UTF-8")){ + if(strcasecmp(enc, "UTF-8")){ clicon_err(OE_XML, XMLPARSE_ERRNO, "Unsupported XML encoding: %s expected UTF-8", enc); free(enc); return -1; diff --git a/test/test_xml.sh b/test/test_xml.sh index 2d6f57d6..65d76646 100755 --- a/test/test_xml.sh +++ b/test/test_xml.sh @@ -127,6 +127,10 @@ expecteof "$clixon_util_xml -ol o" 255 '' '' 2> /dev/nul new "XMLdecl version + encoding" expecteof "$clixon_util_xml -o" 0 '' '' +# XML processors SHOULD match character encoding names in a case-insensitive way +new "XMLdecl encoding case-insensitive" +expecteof "$clixon_util_xml -o" 0 '' '' + new "XMLdecl version + wrong encoding" expecteof "$clixon_util_xml -o" 255 '' '' 2> /dev/null From 2abbea4e3a81b75561bbe5473d818964569c8073 Mon Sep 17 00:00:00 2001 From: Alan Yaniger Date: Fri, 6 Aug 2021 12:00:38 +0300 Subject: [PATCH 45/49] fixed Makefile.in to use name of yang patch file --- yang/mandatory/Makefile.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yang/mandatory/Makefile.in b/yang/mandatory/Makefile.in index 7a1d299e..f2fb8c9d 100644 --- a/yang/mandatory/Makefile.in +++ b/yang/mandatory/Makefile.in @@ -50,7 +50,7 @@ YANGSPECS += ietf-restconf-monitoring@2017-01-26.yang YANGSPECS += ietf-yang-library@2019-01-04.yang YANGSPECS += ietf-yang-types@2013-07-15.yang YANGSPECS += ietf-datastores@2018-02-14.yang -YANGSPECS += ietf-yang-patch@2021-07-01.yang +YANGSPECS += ietf-yang-patch@2017-02-22.yang all: From 26a8cfcedb93d711d09fb939ee445b74297f9690 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Tue, 27 Jul 2021 10:53:47 +0200 Subject: [PATCH 46/49] Removed default of `CLICON_RESTCONF_INSTALLDIR` * The default behaviour is changed to use the config $(sbindir) to locate `clixon_restconf` when starting restconf internally --- test/test_restconf_internal.sh | 3 --- test/test_restconf_internal_usecases.sh | 3 --- 2 files changed, 6 deletions(-) diff --git a/test/test_restconf_internal.sh b/test/test_restconf_internal.sh index 41e449f5..4eb894f0 100755 --- a/test/test_restconf_internal.sh +++ b/test/test_restconf_internal.sh @@ -23,8 +23,6 @@ startupdb=$dir/startup_db RESTCONFDBG=$DBG RCPROTO=http # no ssl here -RESTCONFDIR=$(dirname $(which clixon_restconf)) - # log-destination in restconf xml: syslog or file : ${LOGDST:=syslog} # Set daemon command-line to -f @@ -54,7 +52,6 @@ cat < $cfg /usr/local/lib/$APPNAME/backend example_backend.so$ /usr/local/lib/$APPNAME/restconf - $RESTCONFDIR /usr/local/lib/$APPNAME/cli $APPNAME /usr/local/var/$APPNAME/$APPNAME.sock diff --git a/test/test_restconf_internal_usecases.sh b/test/test_restconf_internal_usecases.sh index 70a94c37..f57c7cf9 100755 --- a/test/test_restconf_internal_usecases.sh +++ b/test/test_restconf_internal_usecases.sh @@ -34,8 +34,6 @@ startupdb=$dir/startup_db RESTCONFDBG=$DBG RCPROTO=http # no ssl here -RESTCONFDIR=$(dirname $(which clixon_restconf)) - INVALIDADDR=251.1.1.1 # used by fourth usecase as invalid # log-destination in restconf xml: syslog or file @@ -68,7 +66,6 @@ cat < $cfg /usr/local/lib/$APPNAME/backend example_backend.so$ /usr/local/lib/$APPNAME/restconf - $RESTCONFDIR /usr/local/lib/$APPNAME/cli $APPNAME /usr/local/var/$APPNAME/$APPNAME.sock From b200361620fd62a0cf35effbb1f7ab5b658773de Mon Sep 17 00:00:00 2001 From: Phil Heller Date: Thu, 29 Jul 2021 21:07:15 -0600 Subject: [PATCH 47/49] Testing dependency fixes, Netconf XML declaration and filter logic fix Install libnghttp2-devel in ubuntu and centos per required dependencies Ignore case when checking XML declaration encoding value per W3C recommendations Fix filter logic to follow RFC6241 (7.1, 7.7) and default to subtree --- apps/netconf/netconf_rpc.c | 97 ++++++++++++++++++------------------- lib/src/clixon_xml_parse.y | 3 +- test/test_netconf_filter.sh | 6 +++ test/test_netconf_hello.sh | 4 ++ test/vagrant/vagrant.sh | 6 ++- 5 files changed, 64 insertions(+), 52 deletions(-) diff --git a/apps/netconf/netconf_rpc.c b/apps/netconf/netconf_rpc.c index 436d719a..8ad952cf 100644 --- a/apps/netconf/netconf_rpc.c +++ b/apps/netconf/netconf_rpc.c @@ -176,31 +176,30 @@ netconf_get_config(clicon_handle h, } /* ie ... */ - if ((xfilter = xpath_first(xn, nsc, "%s%sfilter", prefix ? prefix : "", prefix ? ":" : "")) != NULL) - ftype = xml_find_value(xfilter, "type"); - if (xfilter == NULL || ftype == NULL || strcmp(ftype, "xpath")==0){ - if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) - goto done; - } - else if (strcmp(ftype, "subtree")==0){ - /* Get whole config first, then filter. This is suboptimal - */ - if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) - goto done; - /* Now filter on whole tree */ - if (netconf_get_config_subtree(h, xfilter, xret) < 0) - goto done; - } - else{ - clixon_xml_parse_va(YB_NONE, NULL, xret, NULL, "" - "operation-failed" - "applicatio" - "error" - "filter type not supported" - "type" - "", - NETCONF_BASE_NAMESPACE); - } + if ((xfilter = xpath_first(xn, nsc, "%s%sfilter", prefix ? prefix : "", prefix ? ":" : "")) != NULL) + ftype = xml_find_value(xfilter, "type"); + if (xfilter == NULL || ftype == NULL || strcmp(ftype, "subtree") == 0) { + /* Get whole config first, then filter. This is suboptimal + */ + if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) + goto done; + /* Now filter on whole tree */ + if (netconf_get_config_subtree(h, xfilter, xret) < 0) + goto done; + } else if (strcmp(ftype, "xpath") == 0) { + if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) { + goto done; + } + } else { + clixon_xml_parse_va(YB_NONE, NULL, xret, NULL, "" + "operation-failed" + "applicatio" + "error" + "filter type not supported" + "type" + "", + NETCONF_BASE_NAMESPACE); + } retval = 0; done: if (nsc) @@ -388,31 +387,29 @@ netconf_get(clicon_handle h, } /* ie ... */ - if ((xfilter = xpath_first(xn, nsc, "%s%sfilter", prefix ? prefix : "", prefix ? ":" : "")) != NULL) - ftype = xml_find_value(xfilter, "type"); - if (xfilter == NULL || ftype == NULL || strcmp(ftype, "xpath")==0){ - if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) - goto done; - } - else if (strcmp(ftype, "subtree")==0){ - /* Get whole config + state first, then filter. This is suboptimal - */ - if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) - goto done; - /* Now filter on whole tree */ - if (netconf_get_config_subtree(h, xfilter, xret) < 0) - goto done; - } - else{ - clixon_xml_parse_va(YB_NONE, NULL, xret, NULL, "" - "operation-failed" - "applicatio" - "error" - "filter type not supported" - "type" - "", - NETCONF_BASE_NAMESPACE); - } + if ((xfilter = xpath_first(xn, nsc, "%s%sfilter", prefix ? prefix : "", prefix ? ":" : "")) != NULL) + ftype = xml_find_value(xfilter, "type"); + if (xfilter == NULL || ftype == NULL || strcmp(ftype, "subtree") == 0) { + /* Get whole config + state first, then filter. This is suboptimal + */ + if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) + goto done; + /* Now filter on whole tree */ + if (netconf_get_config_subtree(h, xfilter, xret) < 0) + goto done; + } else if (strcmp(ftype, "xpath") == 0) { + if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) + goto done; + } else { + clixon_xml_parse_va(YB_NONE, NULL, xret, NULL, "" + "operation-failed" + "applicatio" + "error" + "filter type not supported" + "type" + "", + NETCONF_BASE_NAMESPACE); + } retval = 0; done: if(nsc) diff --git a/lib/src/clixon_xml_parse.y b/lib/src/clixon_xml_parse.y index 9bfd6d00..65a3cd49 100644 --- a/lib/src/clixon_xml_parse.y +++ b/lib/src/clixon_xml_parse.y @@ -65,6 +65,7 @@ #include #include #include +#include #include #include @@ -196,7 +197,7 @@ static int xml_parse_encoding(clixon_xml_yacc *xy, char *enc) { - if(strcmp(enc, "UTF-8")){ + if(strcasecmp(enc, "UTF-8")){ clicon_err(OE_XML, XMLPARSE_ERRNO, "Unsupported XML encoding: %s expected UTF-8", enc); free(enc); return -1; diff --git a/test/test_netconf_filter.sh b/test/test_netconf_filter.sh index 73b5e4f6..ec7a9fcf 100755 --- a/test/test_netconf_filter.sh +++ b/test/test_netconf_filter.sh @@ -68,6 +68,12 @@ expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO1
]]>]]>" "^operation-failedapplicatioerrorfilter type not supportedtype]]>]]>$" +new "get-config subtree one (subtree implicit)" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO1]]>]]>" "^11]]>]]>$" + +new "get subtree one (subtree implicit)" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO1]]>]]>" "^11]]>]]>$" + new "get-config subtree one" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO1]]>]]>" "^11]]>]]>$" diff --git a/test/test_netconf_hello.sh b/test/test_netconf_hello.sh index 7460efbe..a0b23ac5 100755 --- a/test/test_netconf_hello.sh +++ b/test/test_netconf_hello.sh @@ -58,6 +58,10 @@ wait_backend new "Netconf snd hello with xmldecl" expecteof "$clixon_netconf -qf $cfg" 0 "urn:ietf:params:netconf:base:1.1]]>]]>" '^$' '^$' +# Hello, lowercase encoding +new "Netconf snd hello with xmldecl (lowercase encoding)" +expecteof "$clixon_netconf -qf $cfg" 0 "urn:ietf:params:netconf:base:1.1]]>]]>" '^$' '^$' + new "Netconf snd hello without xmldecl" expecteof "$clixon_netconf -qf $cfg" 0 "urn:ietf:params:netconf:base:1.1]]>]]>" '^$' '^$' diff --git a/test/vagrant/vagrant.sh b/test/vagrant/vagrant.sh index efadf216..b68ff8d9 100755 --- a/test/vagrant/vagrant.sh +++ b/test/vagrant/vagrant.sh @@ -169,7 +169,7 @@ case $release in native) $sshcmd sudo yum install -y libevent openssl $sshcmd sudo yum install -y libevent-devel openssl-devel - $sshcmd sudo dnf config-manager --set-enabled powertools + $sshcmd sudo yum-config-manager --enable powertools $sshcmd sudo yum install -y libnghttp2-devel ;; esac @@ -215,9 +215,13 @@ case $release in ;; native) # $sshcmd sudo apt install -y libevent-2.1 +<<<<<<< HEAD $sshcmd sudo apt install -y libssl-dev $sshcmd sudo apt install -y libevent-dev # evhtp $sshcmd sudo apt install -y libnghttp2-dev # nghttp2 +======= + $sshcmd sudo apt install -y libevent-dev libssl-dev libnghttp2-dev +>>>>>>> Testing dependency fixes, Netconf XML declaration and filter logic fix ;; esac ;; From e780ed963b9a9b98ad9994ae70178a80e4667d24 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Thu, 5 Aug 2021 21:44:52 +0200 Subject: [PATCH 48/49] * Fixed: [clixon_netconf errors on client XML Declaration with valid encoding spec](https://github.com/clicon/clixon/issues/250) --- CHANGELOG.md | 1 + test/test_xml.sh | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 845fccd5..447a2f51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,7 @@ Users may have to change how they access the system ### Corrected Bugs +* Fixed: [clixon_netconf errors on client XML Declaration with valid encoding spec](https://github.com/clicon/clixon/issues/250) * Fixed: Yang patterns: \n and other non-printable characters were broken * Example: Clixon interpereted them two characters: `\\ n` instead of ascii 10 * Fixed: The auto-cli identityref did not expand identities in grouping/usecases properly. diff --git a/test/test_xml.sh b/test/test_xml.sh index 2d6f57d6..65d76646 100755 --- a/test/test_xml.sh +++ b/test/test_xml.sh @@ -127,6 +127,10 @@ expecteof "$clixon_util_xml -ol o" 255 '' '' 2> /dev/nul new "XMLdecl version + encoding" expecteof "$clixon_util_xml -o" 0 '' '' +# XML processors SHOULD match character encoding names in a case-insensitive way +new "XMLdecl encoding case-insensitive" +expecteof "$clixon_util_xml -o" 0 '' '' + new "XMLdecl version + wrong encoding" expecteof "$clixon_util_xml -o" 255 '' '' 2> /dev/null From 8fafe4a67e47847e7b502ad5aa67fdcf70c37044 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Tue, 10 Aug 2021 19:47:46 +0200 Subject: [PATCH 49/49] revert main example --- example/main/clixon-example@2020-12-01.yang | 3 --- 1 file changed, 3 deletions(-) diff --git a/example/main/clixon-example@2020-12-01.yang b/example/main/clixon-example@2020-12-01.yang index 361d81f0..412e571d 100644 --- a/example/main/clixon-example@2020-12-01.yang +++ b/example/main/clixon-example@2020-12-01.yang @@ -15,9 +15,6 @@ module clixon-example { import ietf-datastores { prefix ds; } - import example-jukebox { - prefix ej; - } description "Clixon example used as a part of the Clixon test suite. It can be used as a basis for making new Clixon applications.