diff --git a/.gitignore b/.gitignore index 4927378c..6826005a 100644 --- a/.gitignore +++ b/.gitignore @@ -49,6 +49,7 @@ build-root/*.rpm build-root/rpmbuild util/clixon_util_datastore +util/clixon_util_insert util/clixon_util_json util/clixon_util_stream util/clixon_util_xml diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a658ce8..2fb75b97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -86,7 +86,15 @@ * rpc get and get-config api function has an added namespace argument: * `clicon_rpc_get_config(clicon_handle h, char *db, char *xpath, char *namespace, cxobj **xt);` * `int clicon_rpc_get(clicon_handle h, char *xpath, char *namespace, cxobj **xt);` - +* Error messages for invalid number ranges and string lengths have been uniformed and changed. + * Error messages for invalid ranges are now on the form: + ``` + Number 23 out of range: 1 - 10 + String length 23 out of range: 1 - 10 + ``` +* On validation callbacks, XML_FLAG_ADD is added to all nodes at startup validation, not just the top-level. This is the same behaviour as for steady-state validation. +* All hash_ functions have been prefixed with `clicon_` to avoid name collision with other packages (frr) + * All calls to the following functions must be changed: `hash_init`, `hash_free`, `hash_lookup`, `hash_value`, `hash_add`, `hash_del`, `hash_dump`, and `hash_keys`. * RESTCONF strict namespace validation of data in POST and PUT. * Accepted: ``` @@ -209,7 +217,9 @@ ### Minor changes +* Added new API function `xpath_parse()` to split parsing and xml evaluation. * Rewrote `api_path2xpath` to handle namespaces. +* `api_path2xml_vec` strict mode check added if list key length mismatch * `startup_extraxml` triggers unnecessary validation * Renamed startup_db_reset -> xmldb_db_reset (its a general function) * In startup_extraxml(), check if reset callbacks or extraxml file actually makes and changes to the tmp db. @@ -260,6 +270,9 @@ ### Corrected Bugs +* Return 404 Not found error if restconf GET does not return requested instance +* Fixed [Wrong yang-generated cli code for typeref identityref combination #88](https://github.com/clicon/clixon/issues/88) +* Fixed [identityref validation fails when using typedef #87](https://github.com/clicon/clixon/issues/87) * Fixed a problem with some netconf error messages caused restconf daemon to exit due to no XML encoding * Check cligen tab mode, dont start if CLICON_CLI_TAB_MODE is undefined * Startup transactions did not mark added tree with XML_FLAG_ADD as it should. diff --git a/apps/backend/backend_commit.c b/apps/backend/backend_commit.c index 357ac8a2..66d2a628 100644 --- a/apps/backend/backend_commit.c +++ b/apps/backend/backend_commit.c @@ -214,7 +214,8 @@ startup_common(clicon_handle h, xt = NULL; x = NULL; while ((x = xml_child_each(td->td_target, x, CX_ELMNT)) != NULL){ - xml_flag_set(x, XML_FLAG_ADD); + xml_flag_set(x, XML_FLAG_ADD); /* Also down */ + xml_apply(x, CX_ELMNT, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_ADD); if (cxvec_append(x, &td->td_avec, &td->td_alen) < 0) goto done; } diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c index 1a0d013e..206aac7d 100644 --- a/apps/cli/cli_common.c +++ b/apps/cli/cli_common.c @@ -107,7 +107,7 @@ cli_notification_register(clicon_handle h, goto done; } snprintf(logname, len, "log_socket_%s", stream); - if ((p = hash_value(cdat, logname, &len)) != NULL) + if ((p = clicon_hash_value(cdat, logname, &len)) != NULL) s_exist = *(int*)p; if (status){ /* start */ @@ -119,14 +119,14 @@ cli_notification_register(clicon_handle h, goto done; if (cligen_regfd(s, fn, arg) < 0) goto done; - if (hash_add(cdat, logname, &s, sizeof(s)) == NULL) + if (clicon_hash_add(cdat, logname, &s, sizeof(s)) == NULL) goto done; } else{ /* stop */ if (s_exist != -1){ cligen_unregfd(s_exist); } - hash_del(cdat, logname); + clicon_hash_del(cdat, logname); #if 0 /* cant turn off */ if (clicon_rpc_create_subscription(h, status, stream, format, filter, NULL) < 0) goto done; diff --git a/apps/cli/cli_generate.c b/apps/cli/cli_generate.c index 5f95915e..5a0c4cf6 100644 --- a/apps/cli/cli_generate.c +++ b/apps/cli/cli_generate.c @@ -163,7 +163,6 @@ cli_callback_generate(clicon_handle h, return retval; } - /*! Generate identityref statements for CLI variables * @param[in] ys Yang statement * @param[in] ytype Yang union type being resolved @@ -186,13 +185,13 @@ yang2cli_var_identityref(yang_stmt *ys, char *id; int i; - /* Add a wildchar string first -let validate take it for default prefix */ - cprintf(cb, ">"); - if (helptext) - cprintf(cb, "(\"%s\")", helptext); if ((ybaseref = yang_find(ytype, Y_BASE, NULL)) != NULL && (ybaseid = yang_find_identity(ys, yang_argument_get(ybaseref))) != NULL){ if (cvec_len(yang_cvec_get(ybaseid)) > 0){ + /* Add a wildchar string first -let validate take it for default prefix */ + cprintf(cb, ">"); + if (helptext) + cprintf(cb, "(\"%s\")", helptext); cprintf(cb, "|<%s:%s choice:", yang_argument_get(ys), cvtypestr); i = 0; while ((cv = cvec_each(yang_cvec_get(ybaseid), cv)) != NULL){ diff --git a/apps/restconf/clixon_restconf.h b/apps/restconf/clixon_restconf.h index 4c93c104..06c6ed86 100644 --- a/apps/restconf/clixon_restconf.h +++ b/apps/restconf/clixon_restconf.h @@ -55,7 +55,7 @@ int test(FCGX_Request *r, int dbg); cbuf *readdata(FCGX_Request *r); int get_user_cookie(char *cookiestr, char *attribute, char **val); int api_return_err(clicon_handle h, FCGX_Request *r, cxobj *xerr, - int pretty, int use_xml); + int pretty, int use_xml, int code); #endif /* _CLIXON_RESTCONF_H_ */ diff --git a/apps/restconf/restconf_lib.c b/apps/restconf/restconf_lib.c index bb2c8771..43f094e3 100644 --- a/apps/restconf/restconf_lib.c +++ b/apps/restconf/restconf_lib.c @@ -394,13 +394,16 @@ get_user_cookie(char *cookiestr, * @param[in] xerr XML error message from backend * @param[in] pretty Set to 1 for pretty-printed xml/json output * @param[in] use_xml Set to 0 for JSON and 1 for XML + * @param[in] code If 0 use rfc8040 sec 7 netconf2restconf error-tag mapping + * otherwise use this code */ int api_return_err(clicon_handle h, FCGX_Request *r, cxobj *xerr, int pretty, - int use_xml) + int use_xml, + int code0) { int retval = -1; cbuf *cb = NULL; @@ -417,8 +420,12 @@ api_return_err(clicon_handle h, goto ok; } tagstr = xml_body(xtag); - if ((code = restconf_err2code(tagstr)) < 0) - code = 500; /* internal server error */ + if (code0 != 0) + code = code0; + else{ + if ((code = restconf_err2code(tagstr)) < 0) + code = 500; /* internal server error */ + } if ((reason_phrase = restconf_code2reason(code)) == NULL) reason_phrase=""; if (xml_name_set(xerr, "error") < 0) diff --git a/apps/restconf/restconf_lib.h b/apps/restconf/restconf_lib.h index 57839bc8..1b90ef30 100644 --- a/apps/restconf/restconf_lib.h +++ b/apps/restconf/restconf_lib.h @@ -60,7 +60,7 @@ int test(FCGX_Request *r, int dbg); cbuf *readdata(FCGX_Request *r); int get_user_cookie(char *cookiestr, char *attribute, char **val); int api_return_err(clicon_handle h, FCGX_Request *r, cxobj *xerr, - int pretty, int use_xml); + int pretty, int use_xml, int code); int restconf_terminate(clicon_handle h); #endif /* _RESTCONF_LIB_H_ */ diff --git a/apps/restconf/restconf_main.c b/apps/restconf/restconf_main.c index 74dd5e02..8a4931f7 100644 --- a/apps/restconf/restconf_main.c +++ b/apps/restconf/restconf_main.c @@ -397,7 +397,7 @@ api_restconf(clicon_handle h, if (netconf_access_denied_xml(&xret, "protocol", "The requested URL was unauthorized") < 0) goto done; if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){ - if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + if (api_return_err(h, r, xerr, pretty, use_xml, 0) < 0) goto done; goto ok; } diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index c6aa7c59..5c21d3a5 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -216,7 +216,7 @@ api_data_get2(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, use_xml) < 0) + if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0) goto done; goto ok; } @@ -233,7 +233,7 @@ api_data_get2(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, use_xml) < 0) + if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0) goto done; goto ok; } @@ -252,7 +252,7 @@ api_data_get2(clicon_handle h, #endif /* Check if error return */ if ((xe = xpath_first(xret, NULL, "//rpc-error")) != NULL){ - if (api_return_err(h, r, xe, pretty, use_xml) < 0) + if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0) goto done; goto ok; } @@ -283,7 +283,20 @@ api_data_get2(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, use_xml) < 0) + if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0) + goto done; + goto ok; + } + /* Check if not exists */ + if (xlen == 0){ + /* 4.3: If a retrieval request for a data resource represents an + instance that does not exist, then an error response containing + a "404 Not Found" status-line MUST be returned by the server. + The error-tag value "invalid-value" is used in this case. */ + if (netconf_invalid_value_xml(&xe, "application", "Instance does not exist") < 0) + goto done; + /* override invalid-value default 400 with 404 */ + if (api_return_err(h, r, xe, pretty, use_xml, 404) < 0) goto done; goto ok; } @@ -495,7 +508,7 @@ api_data_post(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, use_xml) < 0) + if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0) goto done; goto ok; } @@ -509,7 +522,7 @@ api_data_post(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, use_xml) < 0) + if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0) goto done; goto ok; } @@ -522,7 +535,7 @@ api_data_post(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, use_xml) < 0) + if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0) goto done; goto ok; } @@ -531,7 +544,7 @@ api_data_post(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, use_xml) < 0) + if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0) goto done; goto ok; } @@ -546,7 +559,7 @@ api_data_post(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, use_xml) < 0) + if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0) goto done; goto ok; } @@ -568,14 +581,15 @@ api_data_post(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (debug){ - cbuf *ccc=cbuf_new(); - if (clicon_xml2cbuf(ccc, xe, 0, 0) < 0) - goto done; - clicon_debug(1, "%s XE:%s", __FUNCTION__, cbuf_get(ccc)); - } - - if (api_return_err(h, r, xe, pretty, use_xml) < 0) +#if 0 + if (debug){ + cbuf *ccc=cbuf_new(); + if (clicon_xml2cbuf(ccc, xe, 0, 0) < 0) + goto done; + clicon_debug(1, "%s XE:%s", __FUNCTION__, cbuf_get(ccc)); + } +#endif + if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0) goto done; goto ok; } @@ -607,7 +621,7 @@ api_data_post(clicon_handle h, if (clicon_rpc_netconf(h, cbuf_get(cbx), &xret, NULL) < 0) goto done; if ((xe = xpath_first(xret, NULL, "//rpc-error")) != NULL){ - if (api_return_err(h, r, xe, pretty, use_xml) < 0) + if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0) goto done; goto ok; } @@ -629,7 +643,7 @@ api_data_post(clicon_handle h, /* log errors from discard, but ignore */ if ((xpath_first(xretdis, NULL, "//rpc-error")) != NULL) clicon_log(LOG_WARNING, "%s: discard-changes failed which may lead candidate in an inconsistent state", __FUNCTION__); - if (api_return_err(h, r, xe, pretty, use_xml) < 0) /* Use original xe */ + if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0) /* Use original xe */ goto done; goto ok; } @@ -843,7 +857,7 @@ api_data_put(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, use_xml) < 0) + if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0) goto done; goto ok; } @@ -858,7 +872,7 @@ api_data_put(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, use_xml) < 0) + if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0) goto done; goto ok; } @@ -871,7 +885,7 @@ api_data_put(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, use_xml) < 0) + if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0) goto done; goto ok; } @@ -880,7 +894,7 @@ api_data_put(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, use_xml) < 0) + if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0) goto done; goto ok; } @@ -895,7 +909,7 @@ api_data_put(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, use_xml) < 0) + if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0) goto done; goto ok; } @@ -915,7 +929,7 @@ api_data_put(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, use_xml) < 0) + if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0) goto done; goto ok; } @@ -955,7 +969,7 @@ api_data_put(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, use_xml) < 0) + if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0) goto done; goto ok; } @@ -980,7 +994,7 @@ api_data_put(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, use_xml) < 0) + if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0) goto done; goto ok; } @@ -1004,7 +1018,7 @@ api_data_put(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, use_xml) < 0) + if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0) goto done; goto ok; } @@ -1039,9 +1053,8 @@ api_data_put(clicon_handle h, clicon_debug(1, "%s xml: %s api_path:%s",__FUNCTION__, cbuf_get(cbx), api_path); if (clicon_rpc_netconf(h, cbuf_get(cbx), &xret, NULL) < 0) goto done; - if ((xe = xpath_first(xret, NULL, "//rpc-error")) != NULL){ - if (api_return_err(h, r, xe, pretty, use_xml) < 0) + if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0) goto done; goto ok; } @@ -1062,7 +1075,7 @@ api_data_put(clicon_handle h, /* log errors from discard, but ignore */ if ((xpath_first(xretdis, NULL, "//rpc-error")) != NULL) clicon_log(LOG_WARNING, "%s: discard-changes failed which may lead candidate in an inconsistent state", __FUNCTION__); - if (api_return_err(h, r, xe, pretty, use_xml) < 0) + if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0) goto done; goto ok; } @@ -1195,7 +1208,7 @@ api_data_delete(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, use_xml) < 0) + if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0) goto done; goto ok; } @@ -1219,7 +1232,7 @@ api_data_delete(clicon_handle h, if (clicon_rpc_netconf(h, cbuf_get(cbx), &xret, NULL) < 0) goto done; if ((xe = xpath_first(xret, NULL, "//rpc-error")) != NULL){ - if (api_return_err(h, r, xe, pretty, use_xml) < 0) + if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0) goto done; goto ok; } @@ -1241,7 +1254,7 @@ api_data_delete(clicon_handle h, /* log errors from discard, but ignore */ if ((xpath_first(xretdis, NULL, "//rpc-error")) != NULL) clicon_log(LOG_WARNING, "%s: discard-changes failed which may lead candidate in an inconsistent state", __FUNCTION__); - if (api_return_err(h, r, xe, pretty, use_xml) < 0) + if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0) goto done; goto ok; } @@ -1433,7 +1446,7 @@ api_operations_post_input(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, use_xml) < 0) + if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0) goto done; goto fail; } @@ -1446,7 +1459,7 @@ api_operations_post_input(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, use_xml) < 0) + if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0) goto done; goto fail; } @@ -1455,7 +1468,7 @@ api_operations_post_input(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, use_xml) < 0) + if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0) goto done; goto fail; } @@ -1488,7 +1501,7 @@ api_operations_post_input(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, use_xml) < 0) + if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0) goto done; goto fail; } @@ -1562,7 +1575,7 @@ api_operations_post_output(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, use_xml) < 0) + if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0) goto done; goto fail; } @@ -1599,7 +1612,7 @@ api_operations_post_output(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, use_xml) < 0) + if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0) goto done; goto fail; } @@ -1732,7 +1745,7 @@ api_operations_post(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, use_xml) < 0) + if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0) goto done; goto ok; } @@ -1751,7 +1764,7 @@ api_operations_post(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, use_xml) < 0) + if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0) goto done; goto ok; } @@ -1762,7 +1775,7 @@ api_operations_post(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, use_xml) < 0) + if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0) goto done; goto ok; } @@ -1791,7 +1804,7 @@ api_operations_post(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, use_xml) < 0) + if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0) goto done; goto ok; } @@ -1831,7 +1844,7 @@ api_operations_post(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto ok; } - if (api_return_err(h, r, xe, pretty, use_xml) < 0) + if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0) goto done; goto ok; } @@ -1860,7 +1873,7 @@ api_operations_post(clicon_handle h, goto done; /* Local error: return it and quit */ if ((xe = xpath_first(xret, NULL, "rpc-reply/rpc-error")) != NULL){ - if (api_return_err(h, r, xe, pretty, use_xml) < 0) + if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0) goto done; goto ok; } @@ -1869,7 +1882,7 @@ api_operations_post(clicon_handle h, if (clicon_rpc_netconf_xml(h, xtop, &xret, NULL) < 0) goto done; if ((xe = xpath_first(xret, NULL, "rpc-reply/rpc-error")) != NULL){ - if (api_return_err(h, r, xe, pretty, use_xml) < 0) + if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0) goto done; goto ok; } diff --git a/apps/restconf/restconf_stream.c b/apps/restconf/restconf_stream.c index 7d825b4a..7ab03e48 100644 --- a/apps/restconf/restconf_stream.c +++ b/apps/restconf/restconf_stream.c @@ -269,7 +269,7 @@ restconf_stream(clicon_handle h, if (clicon_rpc_netconf(h, cbuf_get(cb), &xret, &s) < 0) goto done; if ((xe = xpath_first(xret, NULL, "rpc-reply/rpc-error")) != NULL){ - if (api_return_err(h, r, xe, pretty, use_xml) < 0) + if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0) goto done; goto ok; } @@ -417,7 +417,7 @@ api_stream(clicon_handle h, if (netconf_access_denied_xml(&xret, "protocol", "The requested URL was unauthorized") < 0) goto done; if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){ - if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + if (api_return_err(h, r, xerr, pretty, use_xml, 0) < 0) goto done; goto ok; } diff --git a/doc/INSTALL.md b/doc/INSTALL.md index 6efca64f..4afd8173 100644 --- a/doc/INSTALL.md +++ b/doc/INSTALL.md @@ -42,6 +42,51 @@ Docker is used to build Alpine Linux ### Build docker image ## FreeBSD -### Package install + +FreeBSD has ports for both cligen and clixon available. +You can install them as binary packages, or you can build +them in a ports source tree locally. + +If you install using binary packages or build from the +ports collection, the installation locations comply +with FreeBSD standards and you have some assurance +that the installed package is correct and functional. + +The nginx setup for RESTCONF is altered - the system user +www is used, and the restconf daemon is placed in +/usr/local/sbin. + +### Binary package install + +To install the pre-built binary package, use the FreeBSD +pkg command. + +``` +% pkg install clixon +``` + +This will install clixon and all the dependencies needed. + ### Build from source +If you prefer you can also build clixon from the +[FreeBSD ports collection](https://www.freebsd.org/doc/handbook/ports-using.html) + +Once you have the Ports Collection installed, you build +clixon like this: + +``` +% cd /usr/ports/devel/clixon +% make && make install +``` + +One issue with using the Ports Collection is that it may +not install the latest version from GitHub. The port is +generally updated soon after an official release, but there +is still a lag between it and the master branch. The maintainer +for the port tries to assure that the master branch will +compile always, but no FreeBSD specific functional testing +is done. + + + diff --git a/lib/clixon/clixon_hash.h b/lib/clixon/clixon_hash.h index 090af542..6fcca465 100644 --- a/lib/clixon/clixon_hash.h +++ b/lib/clixon/clixon_hash.h @@ -44,14 +44,14 @@ struct clicon_hash { }; typedef struct clicon_hash *clicon_hash_t; -clicon_hash_t *hash_init (void); -void hash_free (clicon_hash_t *); -clicon_hash_t hash_lookup (clicon_hash_t *head, const char *key); -void *hash_value (clicon_hash_t *head, const char *key, size_t *vlen); -clicon_hash_t hash_add (clicon_hash_t *head, const char *key, void *val, size_t vlen); -int hash_del (clicon_hash_t *head, const char *key); -int hash_dump(clicon_hash_t *head, FILE *f); -int hash_keys(clicon_hash_t *hash, char ***vector, size_t *nkeys); +clicon_hash_t *clicon_hash_init (void); +void clicon_hash_free (clicon_hash_t *); +clicon_hash_t clicon_hash_lookup (clicon_hash_t *head, const char *key); +void *clicon_hash_value (clicon_hash_t *head, const char *key, size_t *vlen); +clicon_hash_t clicon_hash_add (clicon_hash_t *head, const char *key, void *val, size_t vlen); +int clicon_hash_del (clicon_hash_t *head, const char *key); +int clicon_hash_dump(clicon_hash_t *head, FILE *f); +int clicon_hash_keys(clicon_hash_t *hash, char ***vector, size_t *nkeys); /* * Macros to iterate over hash contents. @@ -59,24 +59,23 @@ int hash_keys(clicon_hash_t *hash, char ***vector, size_t *nkeys); * * Example: * char *k; - * clicon_hash_t *h = hash_init(); + * clicon_hash_t *h = clicon_hash_init(); * - * hash_add(h, "colour", "red", 6); - * hash_add(h, "name", "rudolf" 7); - * hash_add(h, "species", "reindeer" 9); + * clicon_hash_add(h, "colour", "red", 6); + * clicon_hash_add(h, "name", "rudolf" 7); + * clicon_hash_add(h, "species", "reindeer" 9); * - * hash_each(h, k) { - * printf ("%s = %s\n", k, (char *)hash_value(h, k, NULL)); + * clicon_hash_each(h, k) { + * printf ("%s = %s\n", k, (char *)clicon_hash_value(h, k, NULL)); * } hash_each_end(); */ -#define hash_each(__hash__, __key__) \ +#define clicon_hash_each(__hash__, __key__) \ { \ int __i__; \ size_t __n__; \ char **__k__ = hash_keys((__hash__),&__n__); \ if (__k__) { \ for(__i__ = 0; __i__ < __n__ && ((__key__) = __k__[__i__]); __i__++) -#define hash_each_end(__hash__) if (__k__) free(__k__); } } - +#define clicon_hash_each_end(__hash__) if (__k__) free(__k__); } } #endif /* _CLIXON_HASH_H_ */ diff --git a/lib/clixon/clixon_netconf_lib.h b/lib/clixon/clixon_netconf_lib.h index f3651da5..83fc2117 100644 --- a/lib/clixon/clixon_netconf_lib.h +++ b/lib/clixon/clixon_netconf_lib.h @@ -43,6 +43,7 @@ */ int netconf_in_use(cbuf *cb, char *type, char *message); int netconf_invalid_value(cbuf *cb, char *type, char *message); +int netconf_invalid_value_xml(cxobj **xret, char *type, char *message); int netconf_too_big(cbuf *cb, char *type, char *message); int netconf_missing_attribute(cbuf *cb, char *type, char *info, char *message); int netconf_bad_attribute(cbuf *cb, char *type, char *info, char *message); diff --git a/lib/clixon/clixon_xpath.h b/lib/clixon/clixon_xpath.h index f159579d..50856b8a 100644 --- a/lib/clixon/clixon_xpath.h +++ b/lib/clixon/clixon_xpath.h @@ -75,31 +75,61 @@ enum axis_type{ A_ROOT /* XXX Not in https://www.w3.org/TR/xpath-10 */ }; -/* - * Variables - */ -extern const map_str2int xpopmap[]; +/* used as non-terminal type in yacc rules */ +enum xp_type{ + XP_EXP, + XP_AND, + XP_RELEX, + XP_ADD, + XP_UNION, + XP_PATHEXPR, + XP_LOCPATH, + XP_ABSPATH, + XP_RELLOCPATH, + XP_STEP, + XP_NODE, /* s0 is namespace prefix, s1 is name */ + XP_NODE_FN, + XP_PRED, + XP_PRI0, + XP_PRIME_NR, + XP_PRIME_STR, + XP_PRIME_FN, +}; -extern int xpatherrordiff; +/*! XPATH Parsing generates a tree of nodes that is later traversed + */ +struct xpath_tree{ + enum xp_type xs_type; + int xs_int; + double xs_double; + char *xs_s0; + char *xs_s1; + struct xpath_tree *xs_c0; /* child 0 */ + struct xpath_tree *xs_c1; /* child 1 */ +}; +typedef struct xpath_tree xpath_tree; /* * Prototypes */ +char* xpath_tree_int2str(int nodetype); +int xpath_tree_print(cbuf *cb, xpath_tree *xs); +int xpath_tree_free(xpath_tree *xs); +int xpath_parse(cvec *nsc, char *xpath, xpath_tree **xptree); + #if defined(__GNUC__) && __GNUC__ >= 3 +cxobj *xpath_first(cxobj *xcur, cvec *nsc, char *format, ...) __attribute__ ((format (printf, 3, 4))); int xpath_vec(cxobj *xcur, cvec *nsc, char *format, cxobj ***vec, size_t *veclen, ...) __attribute__ ((format (printf, 3, 6))); int xpath_vec_flag(cxobj *xcur, cvec *nsc, char *format, uint16_t flags, cxobj ***vec, size_t *veclen, ...) __attribute__ ((format (printf, 3, 7))); -cxobj *xpath_first(cxobj *xcur, cvec *nsc, char *format, ...) __attribute__ ((format (printf, 3, 4))); int xpath_vec_bool(cxobj *xcur, cvec *nsc, char *format, ...) __attribute__ ((format (printf, 3, 4))); - #else +cxobj *xpath_first(cxobj *xcur, cvec *nsc, char *format, ...); int xpath_vec(cxobj *xcur, cvec *nsc, char *format, cxobj ***vec, size_t *veclen, ...); int xpath_vec_flag(cxobj *xcur, cvec *nsc, char *format, uint16_t flags, cxobj ***vec, size_t *veclen, ...); -cxobj *xpath_first(cxobj *xcur, cvec *nsc, char *format, ...); int xpath_vec_bool(cxobj *xcur, cvec *nsc, char *format, ...); #endif - int xpath_vec_ctx(cxobj *xcur, cvec *nsc, char *xpath, xp_ctx **xrp); #endif /* _CLIXON_XPATH_H */ diff --git a/lib/src/Makefile.in b/lib/src/Makefile.in index 57c0ee75..ef2c4d21 100644 --- a/lib/src/Makefile.in +++ b/lib/src/Makefile.in @@ -73,7 +73,7 @@ SRC = clixon_sig.c clixon_log.c clixon_err.c clixon_event.c \ clixon_yang_cardinality.c clixon_xml_changelog.c clixon_xml_nsctx.c \ clixon_hash.c clixon_options.c clixon_data.c clixon_plugin.c \ clixon_proto.c clixon_proto_client.c \ - clixon_xpath.c clixon_xpath_ctx.c clixon_sha1.c \ + clixon_xpath.c clixon_xpath_ctx.c clixon_xpath_eval.c clixon_sha1.c \ clixon_datastore.c clixon_datastore_write.c clixon_datastore_read.c \ clixon_datastore_tree.c \ clixon_netconf_lib.c clixon_stream.c clixon_nacm.c diff --git a/lib/src/clixon_data.c b/lib/src/clixon_data.c index 56871268..cb38c34d 100644 --- a/lib/src/clixon_data.c +++ b/lib/src/clixon_data.c @@ -83,7 +83,7 @@ clicon_dbspec_yang(clicon_handle h) size_t len; void *p; - if ((p = hash_value(cdat, "dbspec_yang", &len)) != NULL) + if ((p = clicon_hash_value(cdat, "dbspec_yang", &len)) != NULL) return *(yang_stmt **)p; return NULL; } @@ -100,7 +100,7 @@ clicon_dbspec_yang_set(clicon_handle h, /* It is the pointer to ys that should be copied by hash, so we send a ptr to the ptr to indicate what to copy. */ - if (hash_add(cdat, "dbspec_yang", &ys, sizeof(ys)) == NULL) + if (clicon_hash_add(cdat, "dbspec_yang", &ys, sizeof(ys)) == NULL) return -1; return 0; } @@ -118,7 +118,7 @@ clicon_nacm_ext(clicon_handle h) size_t len; void *p; - if ((p = hash_value(cdat, "nacm_xml", &len)) != NULL) + if ((p = clicon_hash_value(cdat, "nacm_xml", &len)) != NULL) return *(cxobj **)p; return NULL; } @@ -141,7 +141,7 @@ clicon_nacm_ext_set(clicon_handle h, /* It is the pointer to xn that should be copied by hash, so we send a ptr to the ptr to indicate what to copy. */ - if (hash_add(cdat, "nacm_xml", &xn, sizeof(xn)) == NULL) + if (clicon_hash_add(cdat, "nacm_xml", &xn, sizeof(xn)) == NULL) return -1; return 0; } @@ -158,7 +158,7 @@ clicon_config_yang(clicon_handle h) size_t len; void *p; - if ((p = hash_value(cdat, "control_yang", &len)) != NULL) + if ((p = clicon_hash_value(cdat, "control_yang", &len)) != NULL) return *(yang_stmt **)p; return NULL; } @@ -175,7 +175,7 @@ clicon_config_yang_set(clicon_handle h, /* It is the pointer to ys that should be copied by hash, so we send a ptr to the ptr to indicate what to copy. */ - if (hash_add(cdat, "control_yang", &ys, sizeof(ys)) == NULL) + if (clicon_hash_add(cdat, "control_yang", &ys, sizeof(ys)) == NULL) return -1; return 0; } @@ -192,7 +192,7 @@ clicon_conf_xml(clicon_handle h) size_t len; void *p; - if ((p = hash_value(cdat, "clixon_conf", &len)) != NULL) + if ((p = clicon_hash_value(cdat, "clixon_conf", &len)) != NULL) return *(cxobj **)p; return NULL; } @@ -209,7 +209,7 @@ clicon_conf_xml_set(clicon_handle h, /* It is the pointer to x that should be copied by hash, * so we send a ptr to the ptr to indicate what to copy. */ - if (hash_add(cdat, "clixon_conf", &x, sizeof(x)) == NULL) + if (clicon_hash_add(cdat, "clixon_conf", &x, sizeof(x)) == NULL) return -1; return 0; } @@ -223,7 +223,7 @@ clicon_username_get(clicon_handle h) { clicon_hash_t *cdat = clicon_data(h); - return (char*)hash_value(cdat, "username", NULL); + return (char*)clicon_hash_value(cdat, "username", NULL); } /*! Set authorized user name @@ -238,8 +238,8 @@ clicon_username_set(clicon_handle h, clicon_hash_t *cdat = clicon_data(h); if (username == NULL) - return hash_del(cdat, "username"); - return hash_add(cdat, "username", username, strlen(username)+1)==NULL?-1:0; + return clicon_hash_del(cdat, "username"); + return clicon_hash_add(cdat, "username", username, strlen(username)+1)==NULL?-1:0; } /*! Get backend daemon startup status @@ -252,7 +252,7 @@ clicon_startup_status_get(clicon_handle h) clicon_hash_t *cdat = clicon_data(h); void *p; - if ((p = hash_value(cdat, "startup_status", NULL)) != NULL) + if ((p = clicon_hash_value(cdat, "startup_status", NULL)) != NULL) return *(enum startup_status *)p; return STARTUP_ERR; } @@ -268,7 +268,7 @@ clicon_startup_status_set(clicon_handle h, enum startup_status status) { clicon_hash_t *cdat = clicon_data(h); - if (hash_add(cdat, "startup_status", &status, sizeof(status))==NULL) + if (clicon_hash_add(cdat, "startup_status", &status, sizeof(status))==NULL) return -1; return 0; } @@ -284,7 +284,7 @@ clicon_socket_get(clicon_handle h) clicon_hash_t *cdat = clicon_data(h); void *p; - if ((p = hash_value(cdat, "socket", NULL)) == NULL) + if ((p = clicon_hash_value(cdat, "socket", NULL)) == NULL) return -1; return *(int*)p; } @@ -302,8 +302,8 @@ clicon_socket_set(clicon_handle h, clicon_hash_t *cdat = clicon_data(h); if (s == -1) - return hash_del(cdat, "socket"); - return hash_add(cdat, "socket", &s, sizeof(int))==NULL?-1:0; + return clicon_hash_del(cdat, "socket"); + return clicon_hash_add(cdat, "socket", &s, sizeof(int))==NULL?-1:0; } /*! Get module state cache @@ -319,7 +319,7 @@ clicon_modst_cache_get(clicon_handle h, clicon_hash_t *cdat = clicon_data(h); void *p; - if ((p = hash_value(cdat, brief?"modst_brief":"modst_full", NULL)) != NULL) + if ((p = clicon_hash_value(cdat, brief?"modst_brief":"modst_full", NULL)) != NULL) return *(cxobj **)p; return NULL; } @@ -346,7 +346,7 @@ clicon_modst_cache_set(clicon_handle h, assert(strcmp(xml_name(xms),"modules-state")==0); if ((x = xml_dup(xms)) == NULL) return -1; - if (hash_add(cdat, brief?"modst_brief":"modst_full", &x, sizeof(x))==NULL) + if (clicon_hash_add(cdat, brief?"modst_brief":"modst_full", &x, sizeof(x))==NULL) return -1; ok: return 0; @@ -363,7 +363,7 @@ clicon_xml_changelog_get(clicon_handle h) clicon_hash_t *cdat = clicon_data(h); void *p; - if ((p = hash_value(cdat, "xml-changelog", NULL)) != NULL) + if ((p = clicon_hash_value(cdat, "xml-changelog", NULL)) != NULL) return *(cxobj **)p; return NULL; } @@ -381,7 +381,7 @@ clicon_xml_changelog_set(clicon_handle h, { clicon_hash_t *cdat = clicon_data(h); - if (hash_add(cdat, "xml-changelog", &xchlog, sizeof(xchlog))==NULL) + if (clicon_hash_add(cdat, "xml-changelog", &xchlog, sizeof(xchlog))==NULL) return -1; return 0; } @@ -403,12 +403,12 @@ clicon_argv_get(clicon_handle h, void *p; if (argc){ - if ((p = hash_value(cdat, "argc", NULL)) == NULL) + if ((p = clicon_hash_value(cdat, "argc", NULL)) == NULL) return -1; *argc = *(int*)p; } if (argv){ - if ((p = hash_value(cdat, "argv", NULL)) == NULL) + if ((p = clicon_hash_value(cdat, "argv", NULL)) == NULL) return -1; *argv = (char**)p; } @@ -444,10 +444,10 @@ clicon_argv_set(clicon_handle h, memcpy(argvv+1, argv, argc*sizeof(char*)); argvv[0] = prgm; /* Note the value is the argv vector (which is copied) */ - if (hash_add(cdat, "argv", argvv, len*sizeof(char*))==NULL) + if (clicon_hash_add(cdat, "argv", argvv, len*sizeof(char*))==NULL) goto done; argc += 1; - if (hash_add(cdat, "argc", &argc, sizeof(argc))==NULL) + if (clicon_hash_add(cdat, "argc", &argc, sizeof(argc))==NULL) goto done; retval = 0; done: @@ -470,7 +470,7 @@ clicon_db_elmnt_get(clicon_handle h, clicon_hash_t *cdat = clicon_db_elmnt(h); void *p; - if ((p = hash_value(cdat, db, NULL)) != NULL) + if ((p = clicon_hash_value(cdat, db, NULL)) != NULL) return (db_elmnt *)p; return NULL; } @@ -491,7 +491,7 @@ clicon_db_elmnt_set(clicon_handle h, { clicon_hash_t *cdat = clicon_db_elmnt(h); - if (hash_add(cdat, db, de, sizeof(*de))==NULL) + if (clicon_hash_add(cdat, db, de, sizeof(*de))==NULL) return -1; return 0; } diff --git a/lib/src/clixon_datastore.c b/lib/src/clixon_datastore.c index d2f58762..a7becb1c 100644 --- a/lib/src/clixon_datastore.c +++ b/lib/src/clixon_datastore.c @@ -161,10 +161,10 @@ xmldb_disconnect(clicon_handle h) int i; db_elmnt *de; - if (hash_keys(clicon_db_elmnt(h), &keys, &klen) < 0) + if (clicon_hash_keys(clicon_db_elmnt(h), &keys, &klen) < 0) goto done; for(i = 0; i < klen; i++) - if ((de = hash_value(clicon_db_elmnt(h), keys[i], NULL)) != NULL){ + if ((de = clicon_hash_value(clicon_db_elmnt(h), keys[i], NULL)) != NULL){ if (de->de_xml){ xml_free(de->de_xml); de->de_xml = NULL; @@ -311,7 +311,7 @@ xmldb_unlock_all(clicon_handle h, int i; db_elmnt *de; - if (hash_keys(clicon_db_elmnt(h), &keys, &klen) < 0) + if (clicon_hash_keys(clicon_db_elmnt(h), &keys, &klen) < 0) goto done; for (i = 0; i < klen; i++) if ((de = clicon_db_elmnt_get(h, keys[i])) != NULL && diff --git a/lib/src/clixon_datastore_read.c b/lib/src/clixon_datastore_read.c index ca5cb90a..966d2bc4 100644 --- a/lib/src/clixon_datastore_read.c +++ b/lib/src/clixon_datastore_read.c @@ -596,10 +596,6 @@ xmldb_get_zerocopy(clicon_handle h, db_elmnt *de = NULL; db_elmnt de0 = {0,}; - if (!clicon_option_bool(h, "CLICON_XMLDB_CACHE")){ - clicon_err(OE_CFG, 0, "CLICON_XMLDB_CACHE must be set"); - goto done; - } if ((yspec = clicon_dbspec_yang(h)) == NULL){ clicon_err(OE_YANG, ENOENT, "No yang spec"); goto done; diff --git a/lib/src/clixon_handle.c b/lib/src/clixon_handle.c index 5d91c7c8..b4b5a303 100644 --- a/lib/src/clixon_handle.c +++ b/lib/src/clixon_handle.c @@ -111,15 +111,15 @@ clicon_handle_init0(int size) } memset(ch, 0, size); ch->ch_magic = CLICON_MAGIC; - if ((ch->ch_copt = hash_init()) == NULL){ + if ((ch->ch_copt = clicon_hash_init()) == NULL){ clicon_handle_exit((clicon_handle)ch); goto done; } - if ((ch->ch_data = hash_init()) == NULL){ + if ((ch->ch_data = clicon_hash_init()) == NULL){ clicon_handle_exit((clicon_handle)ch); goto done; } - if ((ch->ch_db_elmnt = hash_init()) == NULL){ + if ((ch->ch_db_elmnt = clicon_hash_init()) == NULL){ clicon_handle_exit((clicon_handle)ch); goto done; } @@ -154,12 +154,12 @@ clicon_handle_exit(clicon_handle h) clicon_hash_t *ha; if ((ha = clicon_options(h)) != NULL) - hash_free(ha); + clicon_hash_free(ha); if ((ha = clicon_data(h)) != NULL) - hash_free(ha); + clicon_hash_free(ha); if ((ha = clicon_db_elmnt(h)) != NULL) - hash_free(ha); + clicon_hash_free(ha); stream_delete_all(h, 1); free(ch); retval = 0; diff --git a/lib/src/clixon_hash.c b/lib/src/clixon_hash.c index b14e10ad..f757743b 100644 --- a/lib/src/clixon_hash.c +++ b/lib/src/clixon_hash.c @@ -115,7 +115,7 @@ hash_bucket(const char *str) * @see hash_free For freeing the hash-table */ clicon_hash_t * -hash_init(void) +clicon_hash_init(void) { clicon_hash_t *hash; @@ -133,7 +133,7 @@ hash_init(void) * @retval void */ void -hash_free(clicon_hash_t *hash) +clicon_hash_free(clicon_hash_t *hash) { int i; clicon_hash_t tmp; @@ -157,8 +157,8 @@ hash_free(clicon_hash_t *hash) * @retval NULL Not found */ clicon_hash_t -hash_lookup(clicon_hash_t *hash, - const char *key) +clicon_hash_lookup(clicon_hash_t *hash, + const char *key) { uint32_t bkt; clicon_hash_t h; @@ -183,13 +183,13 @@ hash_lookup(clicon_hash_t *hash, * @retval NULL Key not found or value NULL */ void * -hash_value(clicon_hash_t *hash, - const char *key, - size_t *vlen) +clicon_hash_value(clicon_hash_t *hash, + const char *key, + size_t *vlen) { clicon_hash_t h; - h = hash_lookup(hash, key); + h = clicon_hash_lookup(hash, key); if (h == NULL) return NULL; /* OK, key not found */ @@ -209,10 +209,10 @@ hash_value(clicon_hash_t *hash, * @note special case val is NULL and vlen==0 */ clicon_hash_t -hash_add(clicon_hash_t *hash, - const char *key, - void *val, - size_t vlen) +clicon_hash_add(clicon_hash_t *hash, + const char *key, + void *val, + size_t vlen) { void *newval = NULL; clicon_hash_t h; @@ -225,7 +225,7 @@ hash_add(clicon_hash_t *hash, goto catch; } /* If variable exist, don't allocate a new. just replace value */ - h = hash_lookup(hash, key); + h = clicon_hash_lookup(hash, key); if (h == NULL) { if ((new = (clicon_hash_t)malloc(sizeof(*new))) == NULL){ clicon_err(OE_UNIX, errno, "malloc: %s", strerror(errno)); @@ -283,12 +283,12 @@ catch: * @retval -1 Key not found */ int -hash_del(clicon_hash_t *hash, - const char *key) +clicon_hash_del(clicon_hash_t *hash, + const char *key) { clicon_hash_t h; - h = hash_lookup(hash, key); + h = clicon_hash_lookup(hash, key); if (h == NULL) return -1; @@ -311,9 +311,9 @@ hash_del(clicon_hash_t *hash, * @note: vector needs to be deallocated with free */ int -hash_keys(clicon_hash_t *hash, - char ***vector, - size_t *nkeys) +clicon_hash_keys(clicon_hash_t *hash, + char ***vector, + size_t *nkeys) { int retval = -1; int bkt; @@ -357,8 +357,8 @@ catch: * @retval -1 Error */ int -hash_dump(clicon_hash_t *hash, - FILE *f) +clicon_hash_dump(clicon_hash_t *hash, + FILE *f) { int retval = -1; int i; @@ -369,10 +369,10 @@ hash_dump(clicon_hash_t *hash, if (hash == NULL) goto ok; - if (hash_keys(hash, &keys, &klen) < 0) + if (clicon_hash_keys(hash, &keys, &klen) < 0) goto done; for(i = 0; i < klen; i++) { - val = hash_value(hash, keys[i], &vlen); + val = clicon_hash_value(hash, keys[i], &vlen); printf("%s =\t 0x%p , length %zu\n", keys[i], val, vlen); } diff --git a/lib/src/clixon_netconf_lib.c b/lib/src/clixon_netconf_lib.c index 42d9f382..2db4e3ea 100644 --- a/lib/src/clixon_netconf_lib.c +++ b/lib/src/clixon_netconf_lib.c @@ -108,6 +108,48 @@ netconf_in_use(cbuf *cb, goto done; } +/*! Create Netconf invalid-value error XML tree according to RFC 6241 Appendix A + * + * The request specifies an unacceptable value for one or more parameters. + * @param[out] xret Error XML tree. Free with xml_free after use + * @param[in] type Error type: "application" or "protocol" + * @param[in] message Error message (will be XML encoded) + */ +int +netconf_invalid_value_xml(cxobj **xret, + char *type, + char *message) +{ + int retval =-1; + cxobj *xerr; + char *encstr = NULL; + + if (*xret == NULL){ + if ((*xret = xml_new("rpc-reply", NULL, NULL)) == NULL) + goto done; + } + else if (xml_name_set(*xret, "rpc-reply") < 0) + goto done; + if ((xerr = xml_new("rpc-error", *xret, NULL)) == NULL) + goto done; + if (xml_parse_va(&xerr, NULL, "%s" + "invalid-value" + "error", type) < 0) + goto done; + if (message){ + if (xml_chardata_encode(&encstr, "%s", message) < 0) + goto done; + if (xml_parse_va(&xerr, NULL, "%s", + encstr) < 0) + goto done; + } + retval = 0; + done: + if (encstr) + free(encstr); + return retval; +} + /*! Create Netconf invalid-value error XML tree according to RFC 6241 Appendix A * * The request specifies an unacceptable value for one or more parameters. @@ -120,6 +162,20 @@ netconf_invalid_value(cbuf *cb, char *type, char *message) { +#if 1 + int retval = -1; + cxobj *xret = NULL; + + if (netconf_invalid_value_xml(&xret, type, message) < 0) + goto done; + if (clicon_xml2cbuf(cb, xret, 0, 0) < 0) + goto done; + retval = 0; + done: + if (xret) + xml_free(xret); + return retval; +#else int retval = -1; char *encstr = NULL; @@ -145,6 +201,7 @@ netconf_invalid_value(cbuf *cb, err: clicon_err(OE_XML, errno, "cprintf"); goto done; +#endif } /*! Create Netconf too-big error XML tree according to RFC 6241 Appendix A diff --git a/lib/src/clixon_options.c b/lib/src/clixon_options.c index 1986d684..4c7ee2d1 100644 --- a/lib/src/clixon_options.c +++ b/lib/src/clixon_options.c @@ -112,10 +112,10 @@ clicon_option_dump(clicon_handle h, size_t vlen; cxobj *x = NULL; - if (hash_keys(hash, &keys, &klen) < 0) + if (clicon_hash_keys(hash, &keys, &klen) < 0) goto done; for(i = 0; i < klen; i++) { - val = hash_value(hash, keys[i], &vlen); + val = clicon_hash_value(hash, keys[i], &vlen); if (vlen){ if (((char*)val)[vlen-1]=='\0') /* assume string */ clicon_debug(dbglevel, "%s =\t \"%s\"", keys[i], (char*)val); @@ -234,7 +234,7 @@ parse_configfile(clicon_handle h, /* Used as an arg to this fn */ if (strcmp(name,"CLICON_CONFIGFILE")==0) continue; - if (hash_add(copt, + if (clicon_hash_add(copt, name, body, strlen(body)+1) == NULL) @@ -284,7 +284,7 @@ clicon_option_add(clicon_handle h, name, value, name) < 0) goto done; } - if (hash_add(copt, + if (clicon_hash_add(copt, name, value, strlen(value)+1) == NULL) @@ -319,10 +319,10 @@ clicon_options_main(clicon_handle h, /* * Set configure file if not set by command-line above */ - if (!hash_lookup(copt, "CLICON_CONFIGFILE")){ + if (!clicon_hash_lookup(copt, "CLICON_CONFIGFILE")){ clicon_option_str_set(h, "CLICON_CONFIGFILE", CLIXON_DEFAULT_CONFIG); } - configfile = hash_value(copt, "CLICON_CONFIGFILE", NULL); + configfile = clicon_hash_value(copt, "CLICON_CONFIGFILE", NULL); clicon_debug(1, "CLICON_CONFIGFILE=%s", configfile); /* File must end with .xml */ if ((suffix = rindex(configfile, '.')) != NULL){ @@ -385,7 +385,7 @@ clicon_option_exists(clicon_handle h, { clicon_hash_t *copt = clicon_options(h); - return (hash_lookup(copt, (char*)name) != NULL); + return (clicon_hash_lookup(copt, (char*)name) != NULL); } /*! Get a single string option string via handle @@ -404,9 +404,9 @@ clicon_option_str(clicon_handle h, { clicon_hash_t *copt = clicon_options(h); - if (hash_lookup(copt, (char*)name) == NULL) + if (clicon_hash_lookup(copt, (char*)name) == NULL) return NULL; - return hash_value(copt, (char*)name, NULL); + return clicon_hash_value(copt, (char*)name, NULL); } /*! Set a single string option via handle @@ -423,7 +423,7 @@ clicon_option_str_set(clicon_handle h, { clicon_hash_t *copt = clicon_options(h); - return hash_add(copt, (char*)name, val, strlen(val)+1)==NULL?-1:0; + return clicon_hash_add(copt, (char*)name, val, strlen(val)+1)==NULL?-1:0; } /*! Get options as integer but stored as string @@ -518,7 +518,7 @@ clicon_option_del(clicon_handle h, { clicon_hash_t *copt = clicon_options(h); - return hash_del(copt, (char*)name); + return clicon_hash_del(copt, (char*)name); } /*----------------------------------------------------------------- diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 89bbf0c2..08c13588 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -343,11 +343,12 @@ validate_identityref(cxobj *xt, { int retval = -1; - char *node; + char *node = NULL; yang_stmt *ybaseref; /* This is the type's base reference */ yang_stmt *ybaseid; char *prefix = NULL; cbuf *cb = NULL; + cbuf *cb2 = NULL; /* Get idref value. Then see if this value is derived from ytype. * Always add default prefix because derived identifiers are stored with @@ -380,10 +381,13 @@ validate_identityref(cxobj *xt, * The derived node list is a cvec computed XXX */ if (cvec_find(yang_cvec_get(ybaseid), node) == NULL){ - cbuf_reset(cb); - cprintf(cb, "Identityref validation failed, %s not derived from %s", + if ((cb2 = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(cb2, "Identityref validation failed, %s not derived from %s", node, yang_argument_get(ybaseid)); - if (netconf_operation_failed_xml(xret, "application", cbuf_get(cb)) < 0) + if (netconf_operation_failed_xml(xret, "application", cbuf_get(cb2)) < 0) goto done; goto fail; } @@ -391,6 +395,8 @@ validate_identityref(cxobj *xt, done: if (cb) cbuf_free(cb); + if (cb2) + cbuf_free(cb2); return retval; fail: retval = 0; @@ -1221,19 +1227,20 @@ xml_yang_validate_all(clicon_handle h, /* Special case if leaf is leafref, then first check against current xml tree */ - if ((yc = yang_find(ys, Y_TYPE, NULL)) != NULL){ - if (strcmp(yc->ys_argument, "leafref") == 0){ - if ((ret = validate_leafref(xt, yc, xret)) < 0) - goto done; - if (ret == 0) - goto fail; - } - else if (strcmp(yc->ys_argument, "identityref") == 0){ - if ((ret = validate_identityref(xt, ys, yc, xret)) < 0) - goto done; - if (ret == 0) - goto fail; + /* Get base type yc */ + if (yang_type_get(ys, NULL, &yc, NULL, NULL, NULL, NULL, NULL) < 0) + goto done; + if (strcmp(yang_argument_get(yc), "leafref") == 0){ + if ((ret = validate_leafref(xt, yc, xret)) < 0) + goto done; + if (ret == 0) + goto fail; } + else if (strcmp(yang_argument_get(yc), "identityref") == 0){ + if ((ret = validate_identityref(xt, ys, yc, xret)) < 0) + goto done; + if (ret == 0) + goto fail; } break; default: @@ -2557,7 +2564,7 @@ api_path2xml_vec(char **vec, else{ if ((valvec = clicon_strsep(restval, ",", &nvalvec)) == NULL) goto done; - if (nvalvec != cvec_len(cvk)){ + if ((nvalvec != cvec_len(cvk)) && strict){ clicon_err(OE_XML, EINVAL, "List key %s length mismatch", name); goto fail; } diff --git a/lib/src/clixon_xpath.c b/lib/src/clixon_xpath.c index 2eec425d..e6d99843 100644 --- a/lib/src/clixon_xpath.c +++ b/lib/src/clixon_xpath.c @@ -52,36 +52,8 @@ * Then you need to fix API functions and this is the real work: * - Replace all existing functions or create new? * - Expose explicit namespace parameter, or xml object, or default namespace? - * - * On namespaces and xpath - * ======================= - * XPATHs may contain prefixes such as /if:a/if:b - * XPATHs excecutes in a "namespace context" (nsc) - * The namespace context is either: - * (1) the same as the xml that is evaluated, typical for basic XML, or - * (2) separate from the XML that is evaluated. typical netconf and yang. - * 1. Same nsc as XML - * This happens in base XML (not yang), where the nsc is given implicitly by - * the XML being evaluated. In node comparisons (eg of ip:a) only name and - * prefixes are compared. - * XML: - * XPATH: /if:a/ip:b - * When you call an xpath function, then call it with nsc=NULL. - * 2. Separate nsc. - * This happens if the namespace context is independent from the XML. It can - * happen for example in NETCONF GET using :xpath when the XML is not known - * so that xpath and xml may use different prefixes for the same namespace. - * In that case you cannot rely on the prefix but must compare namespaces. - * The namespace context of the XML is given (by the XML), but the xpath - * nsc must then be explicitly given in the xpath call. - * Example: - * XML: - * NETCONF: int */ -const map_str2int xpopmap[] = { - {"and", XO_AND}, - {"or", XO_OR}, - {"div", XO_DIV}, - {"mod", XO_MOD}, - {"+", XO_ADD}, - {"*", XO_MULT}, - {"-", XO_SUB}, - {"=", XO_EQ}, - {"!=", XO_NE}, - {">=", XO_GE}, - {"<=", XO_LE}, - {"<", XO_LT}, - {">", XO_GT}, - {"|", XO_UNION}, - {NULL, -1} -}; +/* Mapping between xpath_tree node name string <--> int */ static const map_str2int xpath_tree_map[] = { {"expr", XP_EXP}, {"andexpr", XP_AND}, @@ -163,16 +116,26 @@ static const map_str2int xpath_tree_map[] = { {NULL, -1} }; + /* * XPATH parse tree type */ + +/*! Map from xpath_tree node name int to string + */ +char* +xpath_tree_int2str(int nodetype) +{ + return (char*)clicon_int2str(xpath_tree_map, nodetype); +} + /*! Print XPATH parse tree */ static int xpath_tree_print0(cbuf *cb, xpath_tree *xs, int level) { - cprintf(cb, "%*s%s:", level*3, "", clicon_int2str(xpath_tree_map, xs->xs_type)); + cprintf(cb, "%*s%s:", level*3, "", xpath_tree_int2str(xs->xs_type)); if (xs->xs_s0){ cprintf(cb, "\"%s\" ", xs->xs_s0); if (xs->xs_s1) @@ -186,7 +149,11 @@ xpath_tree_print0(cbuf *cb, return 0; } -static int +/*! Print a xpath_tree + * @param[out] cb CLIgen buffer + * @param[in] xs XPATH tree + */ +int xpath_tree_print(cbuf *cb, xpath_tree *xs) { @@ -194,7 +161,11 @@ xpath_tree_print(cbuf *cb, return 0; } -static int +/*! Free a xpath_tree + * @param[in] xs XPATH tree + * @see xpath_parse creates a xpath_tree + */ +int xpath_tree_free(xpath_tree *xs) { if (xs->xs_s0) @@ -209,971 +180,12 @@ xpath_tree_free(xpath_tree *xs) return 0; } -/*! - * @retval -1 Error XXX: retval -1 not properly handled - * @retval 0 No match - * @retval 1 Match - */ -static int -nodetest_eval_node(cxobj *x, - xpath_tree *xs, - cvec *nsc) -{ - int retval = -1; - char *name1 = xml_name(x); - char *prefix1 = xml_prefix(x); - char *nsxml = NULL; /* xml body namespace */ - char *nsxpath = NULL; /* xpath context namespace */ - char *prefix2 = NULL; - char *name2 = NULL; - - /* Namespaces is s0, name is s1 */ - if (strcmp(xs->xs_s1, "*")==0) - return 1; - /* get namespace of xml tree */ - if (xml2ns(x, prefix1, &nsxml) < 0) - goto done; - prefix2 = xs->xs_s0; - name2 = xs->xs_s1; - /* Before going into namespaces, check name equality and filter out noteq */ - if (strcmp(name1, name2) != 0){ - retval = 0; /* no match */ - goto done; - } - /* here names are equal - * Now look for namespaces - * 1) prefix1 and prefix2 point to same namespace <<-- try this first - * 2) prefix1 is equal to prefix2 <<-- then try this - * (1) is strict yang xml - * (2) without yang - */ - if (nsc != NULL) { /* solution (1) */ - nsxpath = xml_nsctx_get(nsc, prefix2); - if (nsxml != NULL && nsxpath != NULL) - retval = (strcmp(nsxml, nsxpath) == 0); - else - retval = (nsxml == nsxpath); /* True only if both are NULL */ - } - else{ /* solution (2) */ - if (prefix1 == NULL && prefix2 == NULL) - retval = 1; - else if (prefix1 == NULL || prefix2 == NULL) - retval = 0; - else - retval = strcmp(prefix1, prefix2) == 0; - } - /* If retval == 0 here, then there is name match, but not ns match */ - if (retval == 0){ - fprintf(stderr, "%s NOMATCH xml: (%s)%s\n\t\t xpath: (%s)%s\n", __FUNCTION__, - name1, nsxml, - name2, nsxpath); - if (xpatherrordiff) - assert(retval == 1); - } - done: - return retval; -} - -/*! Make a nodetest - * @param[in] xs XPATH stack of type XP_NODE or XP_NODE_FN - * @param[in] nsc XML Namespace context - * @retval -1 Error - * @retval 0 No match - * @retval 1 Match - * - node() is true for any node of any type whatsoever. - * - text() is true for any text node. - */ -static int -nodetest_eval(cxobj *x, - xpath_tree *xs, - cvec *nsc) -{ - int retval = 0; /* NB: no match is default (not error) */ - char *fn; - - if (xs->xs_type == XP_NODE) - retval = nodetest_eval_node(x, xs, nsc); - else if (xs->xs_type == XP_NODE_FN){ - fn = xs->xs_s0; - if (strcmp(fn, "node")==0) - retval = 1; - else if (strcmp(fn, "text")==0) - retval = 1; - } - /* note, retval set by previous statement */ - return retval; -} - -/*! - * @param[in] xn - * @param[in] nodetest XPATH stack - * @param[in] node_type - * @param[in] flags - * @param[in] nsc XML Namespace context - * @param[out] vec0 - * @param[out] vec0len - */ -int -nodetest_recursive(cxobj *xn, - xpath_tree *nodetest, - int node_type, - uint16_t flags, - cvec *nsc, - cxobj ***vec0, - size_t *vec0len) -{ - int retval = -1; - cxobj *xsub; - cxobj **vec = *vec0; - size_t veclen = *vec0len; - - xsub = NULL; - while ((xsub = xml_child_each(xn, xsub, node_type)) != NULL) { - if (nodetest_eval(xsub, nodetest, nsc) == 1){ - clicon_debug(2, "%s %x %x", __FUNCTION__, flags, xml_flag(xsub, flags)); - if (flags==0x0 || xml_flag(xsub, flags)) - if (cxvec_append(xsub, &vec, &veclen) < 0) - goto done; - // continue; /* Dont go deeper */ - } - if (nodetest_recursive(xsub, nodetest, node_type, flags, nsc, &vec, &veclen) < 0) - goto done; - } - retval = 0; - *vec0 = vec; - *vec0len = veclen; - done: - return retval; -} - -static int xp_eval(xp_ctx *xc, xpath_tree *xs, cvec *nsc, xp_ctx **xrp); - -/*! Evaluate xpath step rule of an XML tree - * - * @param[in] xc0 Incoming context - * @param[in] xs XPATH node tree - * @param[in] nsc XML Namespace context - * @param[out] xrp Resulting context - * - * - A node test that is a QName is true if and only if the type of the node (see [5 Data Model]) - * is the principal node type and has an expanded-name equal to the expanded-name specified by the QName. - * - A node test * is true for any node of the principal node type. - * - node() is true for any node of any type whatsoever. - * - text() is true for any text node. - */ -static int -xp_eval_step(xp_ctx *xc0, - xpath_tree *xs, - cvec *nsc, - xp_ctx **xrp) -{ - int retval = -1; - int i; - cxobj *x; - cxobj *xv; - cxobj *xp; - cxobj **vec = NULL; - size_t veclen = 0; - xpath_tree *nodetest = xs->xs_c0; - xp_ctx *xc = NULL; - - /* Create new xc */ - if ((xc = ctx_dup(xc0)) == NULL) - goto done; - switch (xs->xs_int){ - case A_ANCESTOR: - break; - case A_ANCESTOR_OR_SELF: - break; - case A_ATTRIBUTE: /* principal node type is attribute */ - break; - case A_CHILD: - if (xc->xc_descendant){ - for (i=0; ixc_size; i++){ - xv = xc->xc_nodeset[i]; - if (nodetest_recursive(xv, nodetest, CX_ELMNT, 0x0, nsc, &vec, &veclen) < 0) - goto done; - } - xc->xc_descendant = 0; - } - else{ - if (nodetest->xs_type==XP_NODE_FN && - nodetest->xs_s0 && - strcmp(nodetest->xs_s0,"current")==0){ - if (cxvec_append(xc->xc_initial, &vec, &veclen) < 0) - goto done; - } - else for (i=0; ixc_size; i++){ - xv = xc->xc_nodeset[i]; - x = NULL; - while ((x = xml_child_each(xv, x, CX_ELMNT)) != NULL) { - /* xs->xs_c0 is nodetest */ - if (nodetest == NULL || nodetest_eval(x, nodetest, nsc) == 1) - if (cxvec_append(x, &vec, &veclen) < 0) - goto done; - } - } - } - ctx_nodeset_replace(xc, vec, veclen); - break; - case A_DESCENDANT: - case A_DESCENDANT_OR_SELF: - for (i=0; ixc_size; i++){ - xv = xc->xc_nodeset[i]; - if (nodetest_recursive(xv, xs->xs_c0, CX_ELMNT, 0x0, nsc, &vec, &veclen) < 0) - goto done; - } - ctx_nodeset_replace(xc, vec, veclen); - break; - case A_FOLLOWING: - break; - case A_FOLLOWING_SIBLING: - break; - case A_NAMESPACE: /* principal node type is namespace */ - break; - case A_PARENT: - veclen = xc->xc_size; - vec = xc->xc_nodeset; - xc->xc_size = 0; - xc->xc_nodeset = NULL; - for (i=0; ixc_nodeset, &xc->xc_size) < 0) - goto done; - } - if (vec){ - free(vec); - vec = NULL; - } - break; - case A_PRECEEDING: - break; - case A_PRECEEDING_SIBLING: - break; - case A_SELF: - break; - default: - clicon_err(OE_XML, 0, "No such axisname: %d", xs->xs_int); - goto done; - break; - } - if (xs->xs_c1){ - if (xp_eval(xc, xs->xs_c1, nsc, xrp) < 0) - goto done; - } - else{ - *xrp = xc; - xc = NULL; - } - assert(*xrp); - retval = 0; - done: - if (xc) - ctx_free(xc); - return retval; -} - -/*! Evaluate xpath predicates rule - * - * pred -> pred expr - * @param[in] xc Incoming context - * @param[in] xs XPATH node tree - * @param[in] nsc XML Namespace context - * @param[out] xrp Resulting context - * - * A predicate filters a node-set with respect to an axis to produce a new - * node-set. For each node in the node-set to be filtered, the PredicateExpr is - * evaluated with that node as the context node, with the number of nodes in - * the node-set as the context size, and with the proximity position of the node - * in the node-set with respect to the axis as the context position; if - * PredicateExpr evaluates to true for that node, the node is included in the - * new node-set; otherwise, it is not included. - * A PredicateExpr is evaluated by evaluating the Expr and converting the result - * to a boolean. If the result is a - * - number, the result will be converted to true if the number is equal to the - * context position and will be converted to false otherwise; - * - if the result is not a number, then the result will be converted as if by a - * call to the boolean function. - * Thus a location path para[3] is equivalent to para[position()=3]. - */ -static int -xp_eval_predicate(xp_ctx *xc, - xpath_tree *xs, - cvec *nsc, - xp_ctx **xrp) -{ - int retval = -1; - xp_ctx *xr0 = NULL; - xp_ctx *xr1 = NULL; - xp_ctx *xrc = NULL; - int i; - cxobj *x; - xp_ctx *xcc; - - if (xs->xs_c0 == NULL){ /* empty */ - if ((xr0 = ctx_dup(xc)) == NULL) - goto done; - } - else{ /* eval previous predicates */ - if (xp_eval(xc, xs->xs_c0, nsc, &xr0) < 0) - goto done; - } - if (xs->xs_c1){ - /* Loop over each node in the nodeset */ - assert (xr0->xc_type == XT_NODESET); - if ((xr1 = malloc(sizeof(*xr1))) == NULL){ - clicon_err(OE_UNIX, errno, "malloc"); - goto done; - } - memset(xr1, 0, sizeof(*xr1)); - xr1->xc_type = XT_NODESET; - xr1->xc_node = xc->xc_node; - xr1->xc_initial = xc->xc_initial; - for (i=0; ixc_size; i++){ - x = xr0->xc_nodeset[i]; - /* Create new context */ - if ((xcc = malloc(sizeof(*xcc))) == NULL){ - clicon_err(OE_XML, errno, "malloc"); - goto done; - } - memset(xcc, 0, sizeof(*xcc)); - xcc->xc_type = XT_NODESET; - xcc->xc_initial = xc->xc_initial; - xcc->xc_node = x; - /* For each node in the node-set to be filtered, the PredicateExpr is - * evaluated with that node as the context node */ - if (cxvec_append(x, &xcc->xc_nodeset, &xcc->xc_size) < 0) - goto done; - if (xp_eval(xcc, xs->xs_c1, nsc, &xrc) < 0) - goto done; - if (xcc) - ctx_free(xcc); - if (xrc->xc_type == XT_NUMBER){ - /* If the result is a number, the result will be converted to true - if the number is equal to the context position */ - if ((int)xrc->xc_number == i) - if (cxvec_append(x, &xr1->xc_nodeset, &xr1->xc_size) < 0) - goto done; - } - else { - /* if PredicateExpr evaluates to true for that node, the node is - included in the new node-set */ - if (ctx2boolean(xrc)) - if (cxvec_append(x, &xr1->xc_nodeset, &xr1->xc_size) < 0) - goto done; - } - if (xrc) - ctx_free(xrc); - } - - } - assert(xr0||xr1); - if (xr1){ - *xrp = xr1; - xr1 = NULL; - } - else - if (xr0){ - *xrp = xr0; - xr0 = NULL; - } - retval = 0; - done: - if (xr0) - ctx_free(xr0); - if (xr1) - ctx_free(xr1); - return retval; -} - -/*! Given two XPATH contexts, eval logical operations: or,and - * The logical operators convert their operands to booleans - * @param[in] xc1 Context of operand1 - * @param[in] xc2 Context of operand2 - * @param[in] op Relational operator - * @param[out] xrp Result context - * @retval 0 OK - * @retval -1 Error - */ -static int -xp_logop(xp_ctx *xc1, - xp_ctx *xc2, - enum xp_op op, - xp_ctx **xrp) -{ - int retval = -1; - xp_ctx *xr = NULL; - int b1; - int b2; - - if ((xr = malloc(sizeof(*xr))) == NULL){ - clicon_err(OE_UNIX, errno, "malloc"); - goto done; - } - memset(xr, 0, sizeof(*xr)); - xr->xc_initial = xc1->xc_initial; - xr->xc_type = XT_BOOL; - if ((b1 = ctx2boolean(xc1)) < 0) - goto done; - if ((b2 = ctx2boolean(xc2)) < 0) - goto done; - switch (op){ - case XO_AND: - xr->xc_bool = b1 && b2; - break; - case XO_OR: - xr->xc_bool = b1 || b2; - break; - default: - clicon_err(OE_UNIX, errno, "%s:Invalid operator %s in this context", - __FUNCTION__, clicon_int2str(xpopmap,op)); - goto done; - } - *xrp = xr; - retval = 0; - done: - return retval; -} - -/*! Given two XPATH contexts, eval numeric operations: +-*,div,mod - * The numeric operators convert their operands to numbers as if by - * calling the number function. - * @param[in] xc1 Context of operand1 - * @param[in] xc2 Context of operand2 - * @param[in] op Relational operator - * @param[out] xrp Result context - * @retval 0 OK - * @retval -1 Error - */ -static int -xp_numop(xp_ctx *xc1, - xp_ctx *xc2, - enum xp_op op, - xp_ctx **xrp) -{ - int retval = -1; - xp_ctx *xr = NULL; - double n1; - double n2; - - if ((xr = malloc(sizeof(*xr))) == NULL){ - clicon_err(OE_UNIX, errno, "malloc"); - goto done; - } - memset(xr, 0, sizeof(*xr)); - xr->xc_initial = xc1->xc_initial; - xr->xc_type = XT_NUMBER; - if (ctx2number(xc1, &n1) < 0) - goto done; - if (ctx2number(xc2, &n2) < 0) - goto done; - if (isnan(n1) || isnan(n2)) - xr->xc_number = NAN; - else - switch (op){ - case XO_DIV: - xr->xc_number = n1/n2; - break; - case XO_MOD: - xr->xc_number = ((int)n1)%((int)n2); - break; - case XO_ADD: - xr->xc_number = n1+n2; - break; - case XO_MULT: - xr->xc_number = n1*n2; - break; - case XO_SUB: - xr->xc_number = n1-n2; - break; - default: - clicon_err(OE_UNIX, errno, "Invalid operator %s in this context", - clicon_int2str(xpopmap,op)); - goto done; - } - *xrp = xr; - retval = 0; - done: - return retval; -} - -/*! Given two XPATH contexts, eval relational operations: <>= - * A RelationalExpr is evaluated by comparing the objects that result from - * evaluating the two operands. - * This is covered: - * (a) Both are INTs, BOOLs, STRINGs. Result type is boolean - * (b) Both are nodesets and one is empty. Result type is boolean. - * (c) One is nodeset and other is INT or STRING. Result type is nodeset - * (d) All others (eg two nodesets, BOOL+STRING) are not supported. - * Op is = EQ - * From XPATH 1.0 standard, the evaluation has three variants: - * (1) comparisons that involve node-sets are defined in terms of comparisons that - * do not involve node-sets; this is defined uniformly for =, !=, <=, <, >= and >. - * (2) comparisons that do not involve node-sets are defined for = and !=. - * (3) comparisons that do not involve node-sets are defined for <=, <, >= and >. - * @param[in] xc1 Context of operand1 - * @param[in] xc2 Context of operand2 - * @param[in] op Relational operator - * @param[out] xrp Result context - * @retval 0 OK - * @retval -1 Error - */ -static int -xp_relop(xp_ctx *xc1, - xp_ctx *xc2, - enum xp_op op, - xp_ctx **xrp) -{ - int retval = -1; - xp_ctx *xr = NULL; - xp_ctx *xc; - cxobj *x; - int i; - int j; - int b; - char *s1; - char *s2; - int reverse = 0; - double n1, n2; - - if ((xr = malloc(sizeof(*xr))) == NULL){ - clicon_err(OE_UNIX, errno, "malloc"); - goto done; - } - memset(xr, 0, sizeof(*xr)); - xr->xc_initial = xc1->xc_initial; - xr->xc_type = XT_BOOL; - if (xc1->xc_type == xc2->xc_type){ /* cases (2-3) above */ - switch (xc1->xc_type){ - case XT_NODESET: - /* If both are node-sets, then it is true iff the string value of one - node in the first node-set and one in the second node-set is true */ - for (i=0; ixc_size; i++){ - if ((s1 = xml_body(xc1->xc_nodeset[i])) == NULL){ - xr->xc_bool = 0; - goto ok; - } - for (j=0; jxc_size; j++){ - if ((s2 = xml_body(xc2->xc_nodeset[j])) == NULL){ - xr->xc_bool = 0; - goto ok; - } - switch(op){ - case XO_EQ: - xr->xc_bool = (strcmp(s1, s2)==0); - break; - case XO_NE: - xr->xc_bool = (strcmp(s1, s2)!=0); - break; - case XO_GE: - xr->xc_bool = (strcmp(s1, s2)>=0); - break; - case XO_LE: - xr->xc_bool = (strcmp(s1, s2)<=0); - break; - case XO_LT: - xr->xc_bool = (strcmp(s1, s2)<0); - break; - case XO_GT: - xr->xc_bool = (strcmp(s1, s2)>0); - break; - default: - clicon_err(OE_XML, 0, "Operator %s not supported for nodeset/nodeset comparison", clicon_int2str(xpopmap,op)); - goto done; - break; - } - if (xr->xc_bool) /* enough to find a single node */ - break; - } - if (xr->xc_bool) /* enough to find a single node */ - break; - } - break; - case XT_BOOL: - xr->xc_bool = (xc1->xc_bool == xc2->xc_bool); - break; - case XT_NUMBER: - xr->xc_bool = (xc1->xc_number == xc2->xc_number); - break; - case XT_STRING: - xr->xc_bool = (strcmp(xc1->xc_string, xc2->xc_string)==0); - break; - } - } - else if (xc1->xc_type != XT_NODESET && - xc2->xc_type != XT_NODESET){ - clicon_err(OE_XML, 0, "Mixed types not supported, %d %d", xc1->xc_type, xc2->xc_type); - goto done; - } - else{ /* one is nodeset, ie (1) above */ - if (xc2->xc_type == XT_NODESET){ - xc = xc2; - xc2 = xc1; - xc1 = xc; - reverse++; /* reverse */ - } - /* xc1 is nodeset - * xc2 is something else */ - switch (xc2->xc_type){ - case XT_BOOL: - /* comparison on the boolean and the result of converting the - node-set to a boolean using the boolean function is true. */ - b = ctx2boolean(xc1); - switch(op){ - case XO_EQ: - xr->xc_bool = (b == xc2->xc_bool); - break; - case XO_NE: - xr->xc_bool = (b != xc2->xc_bool); - break; - default: - clicon_err(OE_XML, 0, "Operator %s not supported for nodeset and bool", clicon_int2str(xpopmap,op)); - goto done; - break; - } /* switch op */ - break; - case XT_STRING: - /* If one object to be compared is a node-set and the - other is a string, then the comparison will be true if and only - if there is a node in the node-set such that the result of - performing the comparison on the string-value of the node and - the other string is true.*/ - s2 = xc2->xc_string; - for (i=0; ixc_size; i++){ - x = xc1->xc_nodeset[i]; /* node in nodeset */ - s1 = xml_body(x); - switch(op){ - case XO_EQ: - if (s1 == NULL || s2 == NULL) - xr->xc_bool = (s1==NULL && s2 == NULL); - else - xr->xc_bool = (strcmp(s1, s2)==0); - break; - case XO_NE: - if (s1 == NULL || s2 == NULL) - xr->xc_bool = !(s1==NULL && s2 == NULL); - else - xr->xc_bool = (strcmp(s1, s2)); - break; - default: - clicon_err(OE_XML, 0, "Operator %s not supported for nodeset and string", clicon_int2str(xpopmap,op)); - goto done; - break; - } - if (xr->xc_bool) /* enough to find a single node */ - break; - } - break; - case XT_NUMBER: - for (i=0; ixc_size; i++){ - x = xc1->xc_nodeset[i]; /* node in nodeset */ - if (sscanf(xml_body(x), "%lf", &n1) != 1) - n1 = NAN; - n2 = xc2->xc_number; - switch(op){ - case XO_EQ: - xr->xc_bool = (n1 == n2); - break; - case XO_NE: - xr->xc_bool = (n1 != n2); - break; - case XO_GE: - xr->xc_bool = reverse?(n2 >= n1):(n1 >= n2); - break; - case XO_LE: - xr->xc_bool = reverse?(n2 <= n1):(n1 <= n2); - break; - case XO_LT: - xr->xc_bool = reverse?(n2 < n1):(n1 < n2); - break; - case XO_GT: - xr->xc_bool = reverse?(n2 > n1):(n1 > n2); - break; - default: - clicon_err(OE_XML, 0, "Operator %s not supported for nodeset and number", clicon_int2str(xpopmap,op)); - goto done; - break; - } - if (xr->xc_bool) /* enough to find a single node */ - break; - } - break; - default: - clicon_err(OE_XML, 0, "Type %d not supported", xc2->xc_type); - } /* switch type */ - } - ok: - /* Just ensure bool is 0 or 1 */ - if (xr->xc_type == XT_BOOL && xr->xc_bool != 0) - xr->xc_bool = 1; - *xrp = xr; - retval = 0; - done: - return retval; -} - -/*! Given two XPATH contexts, eval union operation - * Both operands must be nodesets, otherwise empty nodeset is returned - * @param[in] xc1 Context of operand1 - * @param[in] xc2 Context of operand2 - * @param[in] op Relational operator - * @param[out] xrp Result context - * @retval 0 OK - * @retval -1 Error - */ -static int -xp_union(xp_ctx *xc1, - xp_ctx *xc2, - enum xp_op op, - xp_ctx **xrp) -{ - int retval = -1; - xp_ctx *xr = NULL; - int i; - - if (op != XO_UNION){ - clicon_err(OE_UNIX, errno, "%s:Invalid operator %s in this context", - __FUNCTION__, clicon_int2str(xpopmap,op)); - goto done; - } - if ((xr = malloc(sizeof(*xr))) == NULL){ - clicon_err(OE_UNIX, errno, "malloc"); - goto done; - } - memset(xr, 0, sizeof(*xr)); - xr->xc_initial = xc1->xc_initial; - xr->xc_type = XT_NODESET; - - for (i=0; ixc_size; i++) - if (cxvec_append(xc1->xc_nodeset[i], &xr->xc_nodeset, &xr->xc_size) < 0) - goto done; - for (i=0; ixc_size; i++){ - if (cxvec_append(xc2->xc_nodeset[i], &xr->xc_nodeset, &xr->xc_size) < 0) - goto done; - } - *xrp = xr; - retval = 0; - done: - return retval; -} - -/*! Evaluate an XPATH on an XML tree - - * The initial sequence of steps selects a set of nodes relative to a context node. - * Each node in that set is used as a context node for the following step. - * @param[in] xc Incoming context - * @param[in] xs XPATH node tree - * @param[in] nsc XML Namespace context - * @param[out] xrp Resulting context - * @retval 0 OK - * @retval -1 Error - */ -static int -xp_eval(xp_ctx *xc, - xpath_tree *xs, - cvec *nsc, - xp_ctx **xrp) -{ - int retval = -1; - cxobj *x; - xp_ctx *xr0 = NULL; - xp_ctx *xr1 = NULL; - xp_ctx *xr2 = NULL; - int use_xr0 = 0; /* In 2nd child use transitively result of 1st child */ - - if (debug>1){ - cbuf *cb; - if ((cb = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - ctx_print(cb, +2, xc, (char*)clicon_int2str(xpath_tree_map, xs->xs_type)); - clicon_debug(2, "%s", cbuf_get(cb)); - cbuf_free(cb); - } - /* Pre-actions before check first child c0 - */ - switch (xs->xs_type){ - case XP_RELLOCPATH: - if (xs->xs_int == A_DESCENDANT_OR_SELF) - xc->xc_descendant = 1; /* XXX need to set to 0 in sub */ - break; - case XP_ABSPATH: - /* Set context node to top node, and nodeset to that node only */ - x = xc->xc_node; - while (xml_parent(x) != NULL) - x = xml_parent(x); - xc->xc_node = x; - xc->xc_nodeset[0] = x; - xc->xc_size=1; - /* // is short for /descendant-or-self::node()/ */ - if (xs->xs_int == A_DESCENDANT_OR_SELF) - xc->xc_descendant = 1; /* XXX need to set to 0 in sub */ - - break; - case XP_STEP: /* XP_NODE is first argument -not called explicitly */ - if (xp_eval_step(xc, xs, nsc, xrp) < 0) - goto done; - goto ok; - break; - case XP_PRED: - if (xp_eval_predicate(xc, xs, nsc, xrp) < 0) - goto done; - goto ok; - break; - default: - break; - } - /* Eval first child c0 - */ - if (xs->xs_c0){ - if (xp_eval(xc, xs->xs_c0, nsc, &xr0) < 0) - goto done; - } - /* Actions between first and second child - */ - switch (xs->xs_type){ - case XP_EXP: - break; - case XP_AND: - break; - case XP_RELEX: /* relexpr --> addexpr | relexpr relop addexpr */ - break; - case XP_ADD: /* combine mult and add ops */ - break; - case XP_UNION: - break; - case XP_PATHEXPR: - break; - case XP_LOCPATH: - break; - case XP_ABSPATH: - use_xr0++; - /* Special case, no c0 or c1, single "/" */ - if (xs->xs_c0 == NULL){ - if ((xr0 = malloc(sizeof(*xr0))) == NULL){ - clicon_err(OE_UNIX, errno, "malloc"); - goto done; - } - memset(xr0, 0, sizeof(*xr0)); - xr0->xc_initial = xc->xc_initial; - xr0->xc_type = XT_NODESET; - x = NULL; - while ((x = xml_child_each(xc->xc_node, x, CX_ELMNT)) != NULL) { - if (cxvec_append(x, &xr0->xc_nodeset, &xr0->xc_size) < 0) - goto done; - } - } - break; - case XP_RELLOCPATH: - use_xr0++; - if (xs->xs_int == A_DESCENDANT_OR_SELF) - xc->xc_descendant = 1; /* XXX need to set to 0 in sub */ - break; - case XP_NODE: - break; - case XP_NODE_FN: - break; - case XP_PRI0: - break; - case XP_PRIME_NR: /* primaryexpr -> [] */ - if ((xr0 = malloc(sizeof(*xr0))) == NULL){ - clicon_err(OE_UNIX, errno, "malloc"); - goto done; - } - memset(xr0, 0, sizeof(*xr0)); - xr0->xc_initial = xc->xc_initial; - xr0->xc_type = XT_NUMBER; - xr0->xc_number = xs->xs_double; - break; - case XP_PRIME_STR: - if ((xr0 = malloc(sizeof(*xr0))) == NULL){ - clicon_err(OE_UNIX, errno, "malloc"); - goto done; - } - memset(xr0, 0, sizeof(*xr0)); - xr0->xc_initial = xc->xc_initial; - xr0->xc_type = XT_STRING; - xr0->xc_string = xs->xs_s0?strdup(xs->xs_s0):NULL; - break; - case XP_PRIME_FN: - break; - default: - break; - } - /* Eval second child c0 - * Note, some operators like locationpath, need transitive context (use_xr0) - */ - if (xs->xs_c1) - if (xp_eval(use_xr0?xr0:xc, xs->xs_c1, nsc, &xr1) < 0) - goto done; - /* Actions after second child - */ - if (xs->xs_c1) - switch (xs->xs_type){ - case XP_AND: /* combine and and or ops */ - if (xp_logop(xr0, xr1, xs->xs_int, &xr2) < 0) - goto done; - break; - case XP_RELEX: /* relexpr --> addexpr | relexpr relop addexpr */ - if (xp_relop(xr0, xr1, xs->xs_int, &xr2) < 0) - goto done; - break; - case XP_ADD: /* combine mult and add ops */ - if (xp_numop(xr0, xr1, xs->xs_int, &xr2) < 0) - goto done; - break; - case XP_UNION: /* combine and and or ops */ - if (xp_union(xr0, xr1, xs->xs_int, &xr2) < 0) - goto done; - default: - break; - } - xc->xc_descendant = 0; - assert(xr0||xr1||xr2); - if (xr2){ - *xrp = xr2; - xr2 = NULL; - } - else if (xr1){ - *xrp = xr1; - xr1 = NULL; - } - else - if (xr0){ - *xrp = xr0; - xr0 = NULL; - } - ok: - if (debug){ - cbuf *cb; - if ((cb = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - ctx_print(cb, -2, *xrp, (char*)clicon_int2str(xpath_tree_map, xs->xs_type)); - clicon_debug(2, "%s", cbuf_get(cb)); - cbuf_free(cb); - } - retval = 0; - done: - if (xr2) - ctx_free(xr2); - if (xr1) - ctx_free(xr1); - if (xr0) - ctx_free(xr0); - return retval; -} /* xp_eval */ - /*! Given XML tree and xpath, parse xpath, eval it and return xpath context, * This is a raw form of xpath where you can do type conversion, etc, * not just a nodeset. - * @param[in] xcur XML-tree where to search - * @param[in] xpath String with XPATH 1.0 syntax * @param[in] nsc XML Namespace context - * @param[out] xrp Return XPATH context + * @param[in] xpath String with XPATH 1.0 syntax + * @param[out] xptree Xpath-tree, parsed, structured XPATH, free:xpath_tree_free * @retval 0 OK * @retval -1 Error * @code @@ -1183,15 +195,14 @@ xp_eval(xp_ctx *xc, * if (xc) * ctx_free(xc); * @endcode + * @see xpath_tree_free */ int -xpath_vec_ctx(cxobj *xcur, - cvec *nsc, - char *xpath, - xp_ctx **xrp) +xpath_parse(cvec *nsc, + char *xpath, + xpath_tree **xptree) { int retval = -1; - xp_ctx xc = {0,}; struct clicon_xpath_yacc_arg xy = {0,}; xy.xy_parse_string = xpath; @@ -1214,24 +225,60 @@ xpath_vec_ctx(cxobj *xcur, clicon_debug(2, "xpath parse tree:\n%s", cbuf_get(cb)); cbuf_free(cb); } + /* done: */ + xpath_parse_exit(&xy); + xpath_scan_exit(&xy); + *xptree = xy.xy_top; + retval = 0; + done: + return retval; +} + +/*! Given XML tree and xpath, parse xpath, eval it and return xpath context, + * This is a raw form of xpath where you can do type conversion of the return + * value, etc, not just a nodeset. + * @param[in] xcur XML-tree where to search + * @param[in] nsc XML Namespace context + * @param[in] xpath String with XPATH 1.0 syntax + * @param[out] xrp Return XPATH context + * @retval 0 OK + * @retval -1 Error + * @code + * xp_ctx *xc = NULL; + * if (xpath_vec_ctx(x, NULL, xpath, &xc) < 0) + * err; + * if (xc) + * ctx_free(xc); + * @endcode + */ +int +xpath_vec_ctx(cxobj *xcur, + cvec *nsc, + char *xpath, + xp_ctx **xrp) +{ + int retval = -1; + xpath_tree *xptree = NULL; + xp_ctx xc = {0,}; + + if (xpath_parse(nsc, xpath, &xptree) < 0) + goto done; xc.xc_type = XT_NODESET; xc.xc_node = xcur; xc.xc_initial = xcur; if (cxvec_append(xcur, &xc.xc_nodeset, &xc.xc_size) < 0) goto done; - if (xp_eval(&xc, xy.xy_top, nsc, xrp) < 0) + if (xp_eval(&xc, xptree, nsc, xrp) < 0) goto done; if (xc.xc_nodeset){ free(xc.xc_nodeset); xc.xc_nodeset = NULL; } - /* done: */ - xpath_parse_exit(&xy); - xpath_scan_exit(&xy); retval = 0; done: - if (xy.xy_top) - xpath_tree_free(xy.xy_top); + if (xptree) + xpath_tree_free(xptree); + return retval; } @@ -1295,7 +342,6 @@ xpath_first(cxobj *xcur, return cx; } - /*! Given XML tree and xpath, returns nodeset as xml node vector * If result is not nodeset, return empty nodeset * @param[in] xcur xml-tree where to search diff --git a/lib/src/clixon_xpath_ctx.c b/lib/src/clixon_xpath_ctx.c index bf7b5dec..5915a184 100644 --- a/lib/src/clixon_xpath_ctx.c +++ b/lib/src/clixon_xpath_ctx.c @@ -59,8 +59,9 @@ #include "clixon_handle.h" #include "clixon_yang.h" #include "clixon_xml.h" -#include "clixon_xpath_parse.h" #include "clixon_xpath_ctx.h" +#include "clixon_xpath.h" +#include "clixon_xpath_parse.h" /* * Variables diff --git a/lib/src/clixon_xpath_eval.c b/lib/src/clixon_xpath_eval.c new file mode 100644 index 00000000..41bdf200 --- /dev/null +++ b/lib/src/clixon_xpath_eval.c @@ -0,0 +1,1062 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren + + 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 ***** + + * Clixon XML XPATH 1.0 according to https://www.w3.org/TR/xpath-10 + * + * Some notes on namespace extensions in Netconf/Yang + * RFC6241 8.9.1 + * The set of namespace declarations are those in scope on the element. + * + * + * + * + * We need to add namespace context to the cpath tree, typically in eval. How do + * we do that? + * One observation is that the namespace context is static, so it can not be a part + * of the xpath-tree, which is context-dependent. + * Best is to send it as a (read-only) parameter to the xp_eval family of functions + * as an exlicit namespace context. + * For that you need an API to get/set namespaces: clixon_xml_nscache.c? + * Then you need to fix API functions and this is the real work: + * - Replace all existing functions or create new? + * - Expose explicit namespace parameter, or xml object, or default namespace? + */ +#ifdef HAVE_CONFIG_H +#include "clixon_config.h" /* generated by config & autoconf */ +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* cligen */ +#include + +/* clicon */ +#include "clixon_err.h" +#include "clixon_log.h" +#include "clixon_string.h" +#include "clixon_queue.h" +#include "clixon_hash.h" +#include "clixon_handle.h" +#include "clixon_yang.h" +#include "clixon_xml.h" +#include "clixon_xml_nsctx.h" +#include "clixon_xpath_ctx.h" +#include "clixon_xpath.h" +#include "clixon_xpath_eval.h" + +/* Mapping between XPATH operator string <--> int */ +const map_str2int xpopmap[] = { + {"and", XO_AND}, + {"or", XO_OR}, + {"div", XO_DIV}, + {"mod", XO_MOD}, + {"+", XO_ADD}, + {"*", XO_MULT}, + {"-", XO_SUB}, + {"=", XO_EQ}, + {"!=", XO_NE}, + {">=", XO_GE}, + {"<=", XO_LE}, + {"<", XO_LT}, + {">", XO_GT}, + {"|", XO_UNION}, + {NULL, -1} +}; + +/*! + * @retval -1 Error XXX: retval -1 not properly handled + * @retval 0 No match + * @retval 1 Match + */ +static int +nodetest_eval_node(cxobj *x, + xpath_tree *xs, + cvec *nsc) +{ + int retval = -1; + char *name1 = xml_name(x); + char *prefix1 = xml_prefix(x); + char *nsxml = NULL; /* xml body namespace */ + char *nsxpath = NULL; /* xpath context namespace */ + char *prefix2 = NULL; + char *name2 = NULL; + + /* Namespaces is s0, name is s1 */ + if (strcmp(xs->xs_s1, "*")==0) + return 1; + /* get namespace of xml tree */ + if (xml2ns(x, prefix1, &nsxml) < 0) + goto done; + prefix2 = xs->xs_s0; + name2 = xs->xs_s1; + /* Before going into namespaces, check name equality and filter out noteq */ + if (strcmp(name1, name2) != 0){ + retval = 0; /* no match */ + goto done; + } + /* here names are equal + * Now look for namespaces + * 1) prefix1 and prefix2 point to same namespace <<-- try this first + * 2) prefix1 is equal to prefix2 <<-- then try this + * (1) is strict yang xml + * (2) without yang + */ + if (nsc != NULL) { /* solution (1) */ + nsxpath = xml_nsctx_get(nsc, prefix2); + if (nsxml != NULL && nsxpath != NULL) + retval = (strcmp(nsxml, nsxpath) == 0); + else + retval = (nsxml == nsxpath); /* True only if both are NULL */ + } + else{ /* solution (2) */ + if (prefix1 == NULL && prefix2 == NULL) + retval = 1; + else if (prefix1 == NULL || prefix2 == NULL) + retval = 0; + else + retval = strcmp(prefix1, prefix2) == 0; + } + /* If retval == 0 here, then there is name match, but not ns match */ + if (retval == 0){ + fprintf(stderr, "%s NOMATCH xml: (%s)%s\n\t\t xpath: (%s)%s\n", __FUNCTION__, + name1, nsxml, + name2, nsxpath); + } + done: + return retval; +} + +/*! Make a nodetest + * @param[in] xs XPATH stack of type XP_NODE or XP_NODE_FN + * @param[in] nsc XML Namespace context + * @retval -1 Error + * @retval 0 No match + * @retval 1 Match + * - node() is true for any node of any type whatsoever. + * - text() is true for any text node. + */ +static int +nodetest_eval(cxobj *x, + xpath_tree *xs, + cvec *nsc) +{ + int retval = 0; /* NB: no match is default (not error) */ + char *fn; + + if (xs->xs_type == XP_NODE) + retval = nodetest_eval_node(x, xs, nsc); + else if (xs->xs_type == XP_NODE_FN){ + fn = xs->xs_s0; + if (strcmp(fn, "node")==0) + retval = 1; + else if (strcmp(fn, "text")==0) + retval = 1; + } + /* note, retval set by previous statement */ + return retval; +} + +/*! + * @param[in] xn + * @param[in] nodetest XPATH stack + * @param[in] node_type + * @param[in] flags + * @param[in] nsc XML Namespace context + * @param[out] vec0 + * @param[out] vec0len + */ +int +nodetest_recursive(cxobj *xn, + xpath_tree *nodetest, + int node_type, + uint16_t flags, + cvec *nsc, + cxobj ***vec0, + size_t *vec0len) +{ + int retval = -1; + cxobj *xsub; + cxobj **vec = *vec0; + size_t veclen = *vec0len; + + xsub = NULL; + while ((xsub = xml_child_each(xn, xsub, node_type)) != NULL) { + if (nodetest_eval(xsub, nodetest, nsc) == 1){ + clicon_debug(2, "%s %x %x", __FUNCTION__, flags, xml_flag(xsub, flags)); + if (flags==0x0 || xml_flag(xsub, flags)) + if (cxvec_append(xsub, &vec, &veclen) < 0) + goto done; + // continue; /* Dont go deeper */ + } + if (nodetest_recursive(xsub, nodetest, node_type, flags, nsc, &vec, &veclen) < 0) + goto done; + } + retval = 0; + *vec0 = vec; + *vec0len = veclen; + done: + return retval; +} + +/*! Evaluate xpath step rule of an XML tree + * + * @param[in] xc0 Incoming context + * @param[in] xs XPATH node tree + * @param[in] nsc XML Namespace context + * @param[out] xrp Resulting context + * + * - A node test that is a QName is true if and only if the type of the node (see [5 Data Model]) + * is the principal node type and has an expanded-name equal to the expanded-name specified by the QName. + * - A node test * is true for any node of the principal node type. + * - node() is true for any node of any type whatsoever. + * - text() is true for any text node. + */ +static int +xp_eval_step(xp_ctx *xc0, + xpath_tree *xs, + cvec *nsc, + xp_ctx **xrp) +{ + int retval = -1; + int i; + cxobj *x; + cxobj *xv; + cxobj *xp; + cxobj **vec = NULL; + size_t veclen = 0; + xpath_tree *nodetest = xs->xs_c0; + xp_ctx *xc = NULL; + + /* Create new xc */ + if ((xc = ctx_dup(xc0)) == NULL) + goto done; + switch (xs->xs_int){ + case A_ANCESTOR: + break; + case A_ANCESTOR_OR_SELF: + break; + case A_ATTRIBUTE: /* principal node type is attribute */ + break; + case A_CHILD: + if (xc->xc_descendant){ + for (i=0; ixc_size; i++){ + xv = xc->xc_nodeset[i]; + if (nodetest_recursive(xv, nodetest, CX_ELMNT, 0x0, nsc, &vec, &veclen) < 0) + goto done; + } + xc->xc_descendant = 0; + } + else{ + if (nodetest->xs_type==XP_NODE_FN && + nodetest->xs_s0 && + strcmp(nodetest->xs_s0,"current")==0){ + if (cxvec_append(xc->xc_initial, &vec, &veclen) < 0) + goto done; + } + else for (i=0; ixc_size; i++){ + xv = xc->xc_nodeset[i]; + x = NULL; + while ((x = xml_child_each(xv, x, CX_ELMNT)) != NULL) { + /* xs->xs_c0 is nodetest */ + if (nodetest == NULL || nodetest_eval(x, nodetest, nsc) == 1) + if (cxvec_append(x, &vec, &veclen) < 0) + goto done; + } + } + } + ctx_nodeset_replace(xc, vec, veclen); + break; + case A_DESCENDANT: + case A_DESCENDANT_OR_SELF: + for (i=0; ixc_size; i++){ + xv = xc->xc_nodeset[i]; + if (nodetest_recursive(xv, xs->xs_c0, CX_ELMNT, 0x0, nsc, &vec, &veclen) < 0) + goto done; + } + ctx_nodeset_replace(xc, vec, veclen); + break; + case A_FOLLOWING: + break; + case A_FOLLOWING_SIBLING: + break; + case A_NAMESPACE: /* principal node type is namespace */ + break; + case A_PARENT: + veclen = xc->xc_size; + vec = xc->xc_nodeset; + xc->xc_size = 0; + xc->xc_nodeset = NULL; + for (i=0; ixc_nodeset, &xc->xc_size) < 0) + goto done; + } + if (vec){ + free(vec); + vec = NULL; + } + break; + case A_PRECEEDING: + break; + case A_PRECEEDING_SIBLING: + break; + case A_SELF: + break; + default: + clicon_err(OE_XML, 0, "No such axisname: %d", xs->xs_int); + goto done; + break; + } + if (xs->xs_c1){ + if (xp_eval(xc, xs->xs_c1, nsc, xrp) < 0) + goto done; + } + else{ + *xrp = xc; + xc = NULL; + } + assert(*xrp); + retval = 0; + done: + if (xc) + ctx_free(xc); + return retval; +} + +/*! Evaluate xpath predicates rule + * + * pred -> pred expr + * @param[in] xc Incoming context + * @param[in] xs XPATH node tree + * @param[in] nsc XML Namespace context + * @param[out] xrp Resulting context + * + * A predicate filters a node-set with respect to an axis to produce a new + * node-set. For each node in the node-set to be filtered, the PredicateExpr is + * evaluated with that node as the context node, with the number of nodes in + * the node-set as the context size, and with the proximity position of the node + * in the node-set with respect to the axis as the context position; if + * PredicateExpr evaluates to true for that node, the node is included in the + * new node-set; otherwise, it is not included. + * A PredicateExpr is evaluated by evaluating the Expr and converting the result + * to a boolean. If the result is a + * - number, the result will be converted to true if the number is equal to the + * context position and will be converted to false otherwise; + * - if the result is not a number, then the result will be converted as if by a + * call to the boolean function. + * Thus a location path para[3] is equivalent to para[position()=3]. + */ +static int +xp_eval_predicate(xp_ctx *xc, + xpath_tree *xs, + cvec *nsc, + xp_ctx **xrp) +{ + int retval = -1; + xp_ctx *xr0 = NULL; + xp_ctx *xr1 = NULL; + xp_ctx *xrc = NULL; + int i; + cxobj *x; + xp_ctx *xcc; + + if (xs->xs_c0 == NULL){ /* empty */ + if ((xr0 = ctx_dup(xc)) == NULL) + goto done; + } + else{ /* eval previous predicates */ + if (xp_eval(xc, xs->xs_c0, nsc, &xr0) < 0) + goto done; + } + if (xs->xs_c1){ + /* Loop over each node in the nodeset */ + assert (xr0->xc_type == XT_NODESET); + if ((xr1 = malloc(sizeof(*xr1))) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + goto done; + } + memset(xr1, 0, sizeof(*xr1)); + xr1->xc_type = XT_NODESET; + xr1->xc_node = xc->xc_node; + xr1->xc_initial = xc->xc_initial; + for (i=0; ixc_size; i++){ + x = xr0->xc_nodeset[i]; + /* Create new context */ + if ((xcc = malloc(sizeof(*xcc))) == NULL){ + clicon_err(OE_XML, errno, "malloc"); + goto done; + } + memset(xcc, 0, sizeof(*xcc)); + xcc->xc_type = XT_NODESET; + xcc->xc_initial = xc->xc_initial; + xcc->xc_node = x; + /* For each node in the node-set to be filtered, the PredicateExpr is + * evaluated with that node as the context node */ + if (cxvec_append(x, &xcc->xc_nodeset, &xcc->xc_size) < 0) + goto done; + if (xp_eval(xcc, xs->xs_c1, nsc, &xrc) < 0) + goto done; + if (xcc) + ctx_free(xcc); + if (xrc->xc_type == XT_NUMBER){ + /* If the result is a number, the result will be converted to true + if the number is equal to the context position */ + if ((int)xrc->xc_number == i) + if (cxvec_append(x, &xr1->xc_nodeset, &xr1->xc_size) < 0) + goto done; + } + else { + /* if PredicateExpr evaluates to true for that node, the node is + included in the new node-set */ + if (ctx2boolean(xrc)) + if (cxvec_append(x, &xr1->xc_nodeset, &xr1->xc_size) < 0) + goto done; + } + if (xrc) + ctx_free(xrc); + } + + } + assert(xr0||xr1); + if (xr1){ + *xrp = xr1; + xr1 = NULL; + } + else + if (xr0){ + *xrp = xr0; + xr0 = NULL; + } + retval = 0; + done: + if (xr0) + ctx_free(xr0); + if (xr1) + ctx_free(xr1); + return retval; +} + +/*! Given two XPATH contexts, eval logical operations: or,and + * The logical operators convert their operands to booleans + * @param[in] xc1 Context of operand1 + * @param[in] xc2 Context of operand2 + * @param[in] op Relational operator + * @param[out] xrp Result context + * @retval 0 OK + * @retval -1 Error + */ +static int +xp_logop(xp_ctx *xc1, + xp_ctx *xc2, + enum xp_op op, + xp_ctx **xrp) +{ + int retval = -1; + xp_ctx *xr = NULL; + int b1; + int b2; + + if ((xr = malloc(sizeof(*xr))) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + goto done; + } + memset(xr, 0, sizeof(*xr)); + xr->xc_initial = xc1->xc_initial; + xr->xc_type = XT_BOOL; + if ((b1 = ctx2boolean(xc1)) < 0) + goto done; + if ((b2 = ctx2boolean(xc2)) < 0) + goto done; + switch (op){ + case XO_AND: + xr->xc_bool = b1 && b2; + break; + case XO_OR: + xr->xc_bool = b1 || b2; + break; + default: + clicon_err(OE_UNIX, errno, "%s:Invalid operator %s in this context", + __FUNCTION__, clicon_int2str(xpopmap,op)); + goto done; + } + *xrp = xr; + retval = 0; + done: + return retval; +} + +/*! Given two XPATH contexts, eval numeric operations: +-*,div,mod + * The numeric operators convert their operands to numbers as if by + * calling the number function. + * @param[in] xc1 Context of operand1 + * @param[in] xc2 Context of operand2 + * @param[in] op Relational operator + * @param[out] xrp Result context + * @retval 0 OK + * @retval -1 Error + */ +static int +xp_numop(xp_ctx *xc1, + xp_ctx *xc2, + enum xp_op op, + xp_ctx **xrp) +{ + int retval = -1; + xp_ctx *xr = NULL; + double n1; + double n2; + + if ((xr = malloc(sizeof(*xr))) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + goto done; + } + memset(xr, 0, sizeof(*xr)); + xr->xc_initial = xc1->xc_initial; + xr->xc_type = XT_NUMBER; + if (ctx2number(xc1, &n1) < 0) + goto done; + if (ctx2number(xc2, &n2) < 0) + goto done; + if (isnan(n1) || isnan(n2)) + xr->xc_number = NAN; + else + switch (op){ + case XO_DIV: + xr->xc_number = n1/n2; + break; + case XO_MOD: + xr->xc_number = ((int)n1)%((int)n2); + break; + case XO_ADD: + xr->xc_number = n1+n2; + break; + case XO_MULT: + xr->xc_number = n1*n2; + break; + case XO_SUB: + xr->xc_number = n1-n2; + break; + default: + clicon_err(OE_UNIX, errno, "Invalid operator %s in this context", + clicon_int2str(xpopmap,op)); + goto done; + } + *xrp = xr; + retval = 0; + done: + return retval; +} + +/*! Given two XPATH contexts, eval relational operations: <>= + * A RelationalExpr is evaluated by comparing the objects that result from + * evaluating the two operands. + * This is covered: + * (a) Both are INTs, BOOLs, STRINGs. Result type is boolean + * (b) Both are nodesets and one is empty. Result type is boolean. + * (c) One is nodeset and other is INT or STRING. Result type is nodeset + * (d) All others (eg two nodesets, BOOL+STRING) are not supported. + * Op is = EQ + * From XPATH 1.0 standard, the evaluation has three variants: + * (1) comparisons that involve node-sets are defined in terms of comparisons that + * do not involve node-sets; this is defined uniformly for =, !=, <=, <, >= and >. + * (2) comparisons that do not involve node-sets are defined for = and !=. + * (3) comparisons that do not involve node-sets are defined for <=, <, >= and >. + * @param[in] xc1 Context of operand1 + * @param[in] xc2 Context of operand2 + * @param[in] op Relational operator + * @param[out] xrp Result context + * @retval 0 OK + * @retval -1 Error + */ +static int +xp_relop(xp_ctx *xc1, + xp_ctx *xc2, + enum xp_op op, + xp_ctx **xrp) +{ + int retval = -1; + xp_ctx *xr = NULL; + xp_ctx *xc; + cxobj *x; + int i; + int j; + int b; + char *s1; + char *s2; + int reverse = 0; + double n1, n2; + + if ((xr = malloc(sizeof(*xr))) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + goto done; + } + memset(xr, 0, sizeof(*xr)); + xr->xc_initial = xc1->xc_initial; + xr->xc_type = XT_BOOL; + if (xc1->xc_type == xc2->xc_type){ /* cases (2-3) above */ + switch (xc1->xc_type){ + case XT_NODESET: + /* If both are node-sets, then it is true iff the string value of one + node in the first node-set and one in the second node-set is true */ + for (i=0; ixc_size; i++){ + if ((s1 = xml_body(xc1->xc_nodeset[i])) == NULL){ + xr->xc_bool = 0; + goto ok; + } + for (j=0; jxc_size; j++){ + if ((s2 = xml_body(xc2->xc_nodeset[j])) == NULL){ + xr->xc_bool = 0; + goto ok; + } + switch(op){ + case XO_EQ: + xr->xc_bool = (strcmp(s1, s2)==0); + break; + case XO_NE: + xr->xc_bool = (strcmp(s1, s2)!=0); + break; + case XO_GE: + xr->xc_bool = (strcmp(s1, s2)>=0); + break; + case XO_LE: + xr->xc_bool = (strcmp(s1, s2)<=0); + break; + case XO_LT: + xr->xc_bool = (strcmp(s1, s2)<0); + break; + case XO_GT: + xr->xc_bool = (strcmp(s1, s2)>0); + break; + default: + clicon_err(OE_XML, 0, "Operator %s not supported for nodeset/nodeset comparison", clicon_int2str(xpopmap,op)); + goto done; + break; + } + if (xr->xc_bool) /* enough to find a single node */ + break; + } + if (xr->xc_bool) /* enough to find a single node */ + break; + } + break; + case XT_BOOL: + xr->xc_bool = (xc1->xc_bool == xc2->xc_bool); + break; + case XT_NUMBER: + xr->xc_bool = (xc1->xc_number == xc2->xc_number); + break; + case XT_STRING: + xr->xc_bool = (strcmp(xc1->xc_string, xc2->xc_string)==0); + break; + } + } + else if (xc1->xc_type != XT_NODESET && + xc2->xc_type != XT_NODESET){ + clicon_err(OE_XML, 0, "Mixed types not supported, %d %d", xc1->xc_type, xc2->xc_type); + goto done; + } + else{ /* one is nodeset, ie (1) above */ + if (xc2->xc_type == XT_NODESET){ + xc = xc2; + xc2 = xc1; + xc1 = xc; + reverse++; /* reverse */ + } + /* xc1 is nodeset + * xc2 is something else */ + switch (xc2->xc_type){ + case XT_BOOL: + /* comparison on the boolean and the result of converting the + node-set to a boolean using the boolean function is true. */ + b = ctx2boolean(xc1); + switch(op){ + case XO_EQ: + xr->xc_bool = (b == xc2->xc_bool); + break; + case XO_NE: + xr->xc_bool = (b != xc2->xc_bool); + break; + default: + clicon_err(OE_XML, 0, "Operator %s not supported for nodeset and bool", clicon_int2str(xpopmap,op)); + goto done; + break; + } /* switch op */ + break; + case XT_STRING: + /* If one object to be compared is a node-set and the + other is a string, then the comparison will be true if and only + if there is a node in the node-set such that the result of + performing the comparison on the string-value of the node and + the other string is true.*/ + s2 = xc2->xc_string; + for (i=0; ixc_size; i++){ + x = xc1->xc_nodeset[i]; /* node in nodeset */ + s1 = xml_body(x); + switch(op){ + case XO_EQ: + if (s1 == NULL || s2 == NULL) + xr->xc_bool = (s1==NULL && s2 == NULL); + else + xr->xc_bool = (strcmp(s1, s2)==0); + break; + case XO_NE: + if (s1 == NULL || s2 == NULL) + xr->xc_bool = !(s1==NULL && s2 == NULL); + else + xr->xc_bool = (strcmp(s1, s2)); + break; + default: + clicon_err(OE_XML, 0, "Operator %s not supported for nodeset and string", clicon_int2str(xpopmap,op)); + goto done; + break; + } + if (xr->xc_bool) /* enough to find a single node */ + break; + } + break; + case XT_NUMBER: + for (i=0; ixc_size; i++){ + x = xc1->xc_nodeset[i]; /* node in nodeset */ + if (sscanf(xml_body(x), "%lf", &n1) != 1) + n1 = NAN; + n2 = xc2->xc_number; + switch(op){ + case XO_EQ: + xr->xc_bool = (n1 == n2); + break; + case XO_NE: + xr->xc_bool = (n1 != n2); + break; + case XO_GE: + xr->xc_bool = reverse?(n2 >= n1):(n1 >= n2); + break; + case XO_LE: + xr->xc_bool = reverse?(n2 <= n1):(n1 <= n2); + break; + case XO_LT: + xr->xc_bool = reverse?(n2 < n1):(n1 < n2); + break; + case XO_GT: + xr->xc_bool = reverse?(n2 > n1):(n1 > n2); + break; + default: + clicon_err(OE_XML, 0, "Operator %s not supported for nodeset and number", clicon_int2str(xpopmap,op)); + goto done; + break; + } + if (xr->xc_bool) /* enough to find a single node */ + break; + } + break; + default: + clicon_err(OE_XML, 0, "Type %d not supported", xc2->xc_type); + } /* switch type */ + } + ok: + /* Just ensure bool is 0 or 1 */ + if (xr->xc_type == XT_BOOL && xr->xc_bool != 0) + xr->xc_bool = 1; + *xrp = xr; + retval = 0; + done: + return retval; +} + +/*! Given two XPATH contexts, eval union operation + * Both operands must be nodesets, otherwise empty nodeset is returned + * @param[in] xc1 Context of operand1 + * @param[in] xc2 Context of operand2 + * @param[in] op Relational operator + * @param[out] xrp Result context + * @retval 0 OK + * @retval -1 Error + */ +static int +xp_union(xp_ctx *xc1, + xp_ctx *xc2, + enum xp_op op, + xp_ctx **xrp) +{ + int retval = -1; + xp_ctx *xr = NULL; + int i; + + if (op != XO_UNION){ + clicon_err(OE_UNIX, errno, "%s:Invalid operator %s in this context", + __FUNCTION__, clicon_int2str(xpopmap,op)); + goto done; + } + if ((xr = malloc(sizeof(*xr))) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + goto done; + } + memset(xr, 0, sizeof(*xr)); + xr->xc_initial = xc1->xc_initial; + xr->xc_type = XT_NODESET; + + for (i=0; ixc_size; i++) + if (cxvec_append(xc1->xc_nodeset[i], &xr->xc_nodeset, &xr->xc_size) < 0) + goto done; + for (i=0; ixc_size; i++){ + if (cxvec_append(xc2->xc_nodeset[i], &xr->xc_nodeset, &xr->xc_size) < 0) + goto done; + } + *xrp = xr; + retval = 0; + done: + return retval; +} + +/*! Evaluate an XPATH on an XML tree + + * The initial sequence of steps selects a set of nodes relative to a context node. + * Each node in that set is used as a context node for the following step. + * @param[in] xc Incoming context + * @param[in] xs XPATH node tree + * @param[in] nsc XML Namespace context + * @param[out] xrp Resulting context + * @retval 0 OK + * @retval -1 Error + */ +int +xp_eval(xp_ctx *xc, + xpath_tree *xs, + cvec *nsc, + xp_ctx **xrp) +{ + int retval = -1; + cxobj *x; + xp_ctx *xr0 = NULL; + xp_ctx *xr1 = NULL; + xp_ctx *xr2 = NULL; + int use_xr0 = 0; /* In 2nd child use transitively result of 1st child */ + + if (debug>1){ + cbuf *cb; + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + ctx_print(cb, +2, xc, xpath_tree_int2str(xs->xs_type)); + clicon_debug(2, "%s", cbuf_get(cb)); + cbuf_free(cb); + } + /* Pre-actions before check first child c0 + */ + switch (xs->xs_type){ + case XP_RELLOCPATH: + if (xs->xs_int == A_DESCENDANT_OR_SELF) + xc->xc_descendant = 1; /* XXX need to set to 0 in sub */ + break; + case XP_ABSPATH: + /* Set context node to top node, and nodeset to that node only */ + x = xc->xc_node; + while (xml_parent(x) != NULL) + x = xml_parent(x); + xc->xc_node = x; + xc->xc_nodeset[0] = x; + xc->xc_size=1; + /* // is short for /descendant-or-self::node()/ */ + if (xs->xs_int == A_DESCENDANT_OR_SELF) + xc->xc_descendant = 1; /* XXX need to set to 0 in sub */ + + break; + case XP_STEP: /* XP_NODE is first argument -not called explicitly */ + if (xp_eval_step(xc, xs, nsc, xrp) < 0) + goto done; + goto ok; + break; + case XP_PRED: + if (xp_eval_predicate(xc, xs, nsc, xrp) < 0) + goto done; + goto ok; + break; + default: + break; + } + /* Eval first child c0 + */ + if (xs->xs_c0){ + if (xp_eval(xc, xs->xs_c0, nsc, &xr0) < 0) + goto done; + } + /* Actions between first and second child + */ + switch (xs->xs_type){ + case XP_EXP: + break; + case XP_AND: + break; + case XP_RELEX: /* relexpr --> addexpr | relexpr relop addexpr */ + break; + case XP_ADD: /* combine mult and add ops */ + break; + case XP_UNION: + break; + case XP_PATHEXPR: + break; + case XP_LOCPATH: + break; + case XP_ABSPATH: + use_xr0++; + /* Special case, no c0 or c1, single "/" */ + if (xs->xs_c0 == NULL){ + if ((xr0 = malloc(sizeof(*xr0))) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + goto done; + } + memset(xr0, 0, sizeof(*xr0)); + xr0->xc_initial = xc->xc_initial; + xr0->xc_type = XT_NODESET; + x = NULL; + while ((x = xml_child_each(xc->xc_node, x, CX_ELMNT)) != NULL) { + if (cxvec_append(x, &xr0->xc_nodeset, &xr0->xc_size) < 0) + goto done; + } + } + break; + case XP_RELLOCPATH: + use_xr0++; + if (xs->xs_int == A_DESCENDANT_OR_SELF) + xc->xc_descendant = 1; /* XXX need to set to 0 in sub */ + break; + case XP_NODE: + break; + case XP_NODE_FN: + break; + case XP_PRI0: + break; + case XP_PRIME_NR: /* primaryexpr -> [] */ + if ((xr0 = malloc(sizeof(*xr0))) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + goto done; + } + memset(xr0, 0, sizeof(*xr0)); + xr0->xc_initial = xc->xc_initial; + xr0->xc_type = XT_NUMBER; + xr0->xc_number = xs->xs_double; + break; + case XP_PRIME_STR: + if ((xr0 = malloc(sizeof(*xr0))) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + goto done; + } + memset(xr0, 0, sizeof(*xr0)); + xr0->xc_initial = xc->xc_initial; + xr0->xc_type = XT_STRING; + xr0->xc_string = xs->xs_s0?strdup(xs->xs_s0):NULL; + break; + case XP_PRIME_FN: + break; + default: + break; + } + /* Eval second child c0 + * Note, some operators like locationpath, need transitive context (use_xr0) + */ + if (xs->xs_c1) + if (xp_eval(use_xr0?xr0:xc, xs->xs_c1, nsc, &xr1) < 0) + goto done; + /* Actions after second child + */ + if (xs->xs_c1) + switch (xs->xs_type){ + case XP_AND: /* combine and and or ops */ + if (xp_logop(xr0, xr1, xs->xs_int, &xr2) < 0) + goto done; + break; + case XP_RELEX: /* relexpr --> addexpr | relexpr relop addexpr */ + if (xp_relop(xr0, xr1, xs->xs_int, &xr2) < 0) + goto done; + break; + case XP_ADD: /* combine mult and add ops */ + if (xp_numop(xr0, xr1, xs->xs_int, &xr2) < 0) + goto done; + break; + case XP_UNION: /* combine and and or ops */ + if (xp_union(xr0, xr1, xs->xs_int, &xr2) < 0) + goto done; + default: + break; + } + xc->xc_descendant = 0; + assert(xr0||xr1||xr2); + if (xr2){ + *xrp = xr2; + xr2 = NULL; + } + else if (xr1){ + *xrp = xr1; + xr1 = NULL; + } + else + if (xr0){ + *xrp = xr0; + xr0 = NULL; + } + ok: + if (debug){ + cbuf *cb; + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + ctx_print(cb, -2, *xrp, xpath_tree_int2str(xs->xs_type)); + clicon_debug(2, "%s", cbuf_get(cb)); + cbuf_free(cb); + } + retval = 0; + done: + if (xr2) + ctx_free(xr2); + if (xr1) + ctx_free(xr1); + if (xr0) + ctx_free(xr0); + return retval; +} /* xp_eval */ + diff --git a/lib/src/clixon_xpath_eval.h b/lib/src/clixon_xpath_eval.h new file mode 100644 index 00000000..5b74bb5a --- /dev/null +++ b/lib/src/clixon_xpath_eval.h @@ -0,0 +1,49 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren + + 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 ***** + + * Clixon XML XPATH 1.0 according to https://www.w3.org/TR/xpath-10 + */ +#ifndef _CLIXON_XPATH_EVAL_H +#define _CLIXON_XPATH_EVAL_H + +/* + * Variables + */ +extern const map_str2int xpopmap[]; + +/* + * Prototypes + */ +int xp_eval(xp_ctx *xc, xpath_tree *xs, cvec *nsc, xp_ctx **xrp); + +#endif /* _CLIXON_XPATH_EVAL_H */ diff --git a/lib/src/clixon_xpath_parse.h b/lib/src/clixon_xpath_parse.h index 3e23e8c2..72ba992b 100644 --- a/lib/src/clixon_xpath_parse.h +++ b/lib/src/clixon_xpath_parse.h @@ -39,40 +39,6 @@ /* * Types */ -/* used as non-terminal type in yacc rules */ -enum xp_type{ - XP_EXP, - XP_AND, - XP_RELEX, - XP_ADD, - XP_UNION, - XP_PATHEXPR, - XP_LOCPATH, - XP_ABSPATH, - XP_RELLOCPATH, - XP_STEP, - XP_NODE, /* s0 is namespace prefix, s1 is name */ - XP_NODE_FN, - XP_PRED, - XP_PRI0, - XP_PRIME_NR, - XP_PRIME_STR, - XP_PRIME_FN, -}; - -/*! XPATH Parsing generates a tree of nodes that is later traversed - */ -struct xpath_tree{ - enum xp_type xs_type; - int xs_int; - double xs_double; - char *xs_s0; - char *xs_s1; - struct xpath_tree *xs_c0; /* child 0 */ - struct xpath_tree *xs_c1; /* child 1 */ -}; -typedef struct xpath_tree xpath_tree; - struct clicon_xpath_yacc_arg{ /* XXX: mostly unrelevant */ const char *xy_name; /* Name of syntax (for error string) */ int xy_linenum; /* Number of \n in parsed buffer */ diff --git a/lib/src/clixon_xpath_parse.l b/lib/src/clixon_xpath_parse.l index 7fd27c38..180adc9c 100644 --- a/lib/src/clixon_xpath_parse.l +++ b/lib/src/clixon_xpath_parse.l @@ -57,6 +57,7 @@ #include "clixon_xpath_ctx.h" #include "clixon_xpath.h" #include "clixon_xpath_parse.h" +#include "clixon_xpath_eval.h" /* Redefine main lex function so that you can send arguments to it: _yy is added to arg list */ #define YY_DECL int clixon_xpath_parselex(void *_yy) diff --git a/lib/src/clixon_yang_type.c b/lib/src/clixon_yang_type.c index a6b2fdd6..36818ecd 100644 --- a/lib/src/clixon_yang_type.c +++ b/lib/src/clixon_yang_type.c @@ -588,6 +588,105 @@ cv_validate_pattern(clicon_handle h, (rmax && (i) > cv_##type##_get(rmax))) +/*! Error messsage for int violating ranges + * @note contains kludge - duplicate loop + */ +static int +outofrange(cg_var *cv0, + cvec *cvv, + char **reason) +{ + int retval = -1; + cbuf *cb = NULL; + cg_var *cv1; + cg_var *cv2; + int i; + + if ((cb = cbuf_new()) == NULL) + goto done; + cprintf(cb, "Number "); + cv2cbuf(cv0, cb); + cprintf(cb, " out of range: "); + /* Kludge: need to repeat the same loop as in the main function in + cv_validate1 */ + i = 0; + while (i2) + cprintf(cb, ", "); + cv2cbuf(cv1, cb); + cprintf(cb, " - "); + cv2cbuf(cv2, cb); + } + if (reason && (*reason = strdup(cbuf_get(cb))) == NULL) + goto done; + if (cb) + cbuf_free(cb); + retval = 0; + done: + return retval; +} + +/*! Error messsage for string violating string limits + * @note contains kludge - duplicate loop + */ +static int +outoflength(uint64_t u64, + cvec *cvv, + char **reason) +{ + int retval = -1; + cbuf *cb = NULL; + cg_var *cv1; + cg_var *cv2; + int i; + + if ((cb = cbuf_new()) == NULL) + goto done; + cprintf(cb, "String length %" PRIu64 " out of range: ", u64); + + /* Kludge: need to repeat the same loop as in the main function in + cv_validate1 */ + i = 0; + while (i2) + cprintf(cb, ", "); + cv2cbuf(cv1, cb); + cprintf(cb, " - "); + cv2cbuf(cv2, cb); + } + if (reason && (*reason = strdup(cbuf_get(cb))) == NULL) + goto done; + if (cb) + cbuf_free(cb); + retval = 0; + done: + return retval; +} + /*! Validate CLIgen variable * @param[in] h Clicon handle * @param[in] cv A cligen variable to validate. This is a correctly parsed cv. @@ -600,6 +699,7 @@ cv_validate_pattern(clicon_handle h, * @retval 0 Validation not OK, malloced reason is returned. Free reason with free() * @retval 1 Validation OK * @note reason if given must be freed by caller + * @see cv_validate Corresponding type check in cligen */ static int cv_validate1(clicon_handle h, @@ -705,14 +805,13 @@ cv_validate1(clicon_handle h, /* Check fails */ if (i==cvec_len(cvv)){ /* And it is last */ if (reason){ - if (reti) - *reason = cligen_reason("Number out of range: %" - PRId64, ii); - else if (retu) - *reason = cligen_reason("Number out of range: %" - PRIu64, uu); + if (reti || retu){ + if (outofrange(cv, cvv, reason) < 0) + goto done; + } else - *reason = cligen_reason("string length out of range: %" PRIu64, uu); + if (outoflength(uu, cvv, reason) < 0) + goto done; } goto fail; } @@ -1317,13 +1416,14 @@ yang_type_resolve(yang_stmt *yorig, * * @code * yang_stmt *yrestype; + * char *origtype = NULL; * int options; * cvec *cvv = NULL; * cvec *patterns = cvec_new(0); * cvec *regexps = cvec_new(0); * uint8_t fraction; * - * if (yang_type_get(ys, &type, &yrestype, &options, &cvv, + * if (yang_type_get(ys, &origtype, &yrestype, &options, &cvv, * patterns, regexps, &fraction) < 0) * goto err; * if (yrestype == NULL) # unresolved diff --git a/test/all.sh b/test/all.sh index 6711558f..18ebde01 100755 --- a/test/all.sh +++ b/test/all.sh @@ -6,7 +6,8 @@ : ${pattern:=test_*.sh} if [ $# -gt 0 ]; then - echo "usage: $0 # detailed logs and stopon first error" + echo "usage: $0 # detailed logs and stop on first error. Use pattern=\"\" $0 to" + echo " Use pattern= $0 to narrow down test cases" exit -1 fi diff --git a/test/mem.sh b/test/mem.sh index d9c0e1dd..f40f52cd 100755 --- a/test/mem.sh +++ b/test/mem.sh @@ -86,7 +86,20 @@ done testnr=0 for c in $cmds; do if [ $testnr != 0 ]; then echo; fi - echo "Mem test for $c" - echo "=================" + echo "Mem test $c begin" + length=$(echo "Mem test $c begin" | wc -c) + let i=1 + while [ $i -lt $length ]; do + echo -n "=" + let i++ + done + echo memonce $c + echo "Mem test $c done" + let i=1 + while [ $i -lt $length ]; do + echo -n "=" + let i++ + done + echo done diff --git a/test/test_augment.sh b/test/test_augment.sh index a3a9d7ef..a5a61153 100755 --- a/test/test_augment.sh +++ b/test/test_augment.sh @@ -154,7 +154,7 @@ if [ $BE -ne 0 ]; then start_backend -s init -f $cfg new "waiting" - sleep $RCWAIT + wait_backend fi # mandatory-leaf See RFC7950 Sec 7.17 diff --git a/test/test_choice.sh b/test/test_choice.sh index 145f0dac..2e352993 100755 --- a/test/test_choice.sh +++ b/test/test_choice.sh @@ -124,7 +124,9 @@ new "start restconf daemon" start_restconf -f $cfg new "waiting" -sleep $RCWAIT +wait_backend +wait_restconf + # First vanilla (protocol) case new "netconf validate empty" diff --git a/test/test_cli_history.sh b/test/test_cli_history.sh index f6c50382..034daf1d 100755 --- a/test/test_cli_history.sh +++ b/test/test_cli_history.sh @@ -48,7 +48,7 @@ if [ $BE -ne 0 ]; then start_backend -s init -f $cfg new "waiting" - sleep $RCWAIT + wait_backend fi new "cli read and add entry to existing history" diff --git a/test/test_feature.sh b/test/test_feature.sh index 98febbe5..619e0c1c 100755 --- a/test/test_feature.sh +++ b/test/test_feature.sh @@ -85,7 +85,7 @@ if [ $BE -ne 0 ]; then start_backend -s init -f $cfg new "waiting" - sleep $RCWAIT + wait_backend fi new "cli enabled feature" diff --git a/test/test_identity.sh b/test/test_identity.sh index a31b1bbb..81f956e5 100755 --- a/test/test_identity.sh +++ b/test/test_identity.sh @@ -106,7 +106,37 @@ cat < $fyang container aes-parameters { when "../crypto = 'mc:aes'"; } - } + identity acl-base; + typedef acl-type { + description "problem detected in ietf-access-control-list.yang"; + type identityref { + base acl-base; + } + } + identity ipv4-acl-type { + base mc:acl-base; + } + identity ipv6-acl-type { + base mc:acl-base; + } + container acls { + list acl { + key name; + leaf name { + type string; + } + leaf type { + type acl-type; + } + } + } + identity empty; /* some errors with an empty identity set */ + leaf e { + type identityref { + base mc:empty; + } + } + } EOF new "test params: -f $cfg" @@ -120,7 +150,7 @@ if [ $BE -ne 0 ]; then start_backend -s init -f $cfg new "waiting" - sleep $RCWAIT + wait_backend fi new "Set crypto to aes" @@ -186,6 +216,43 @@ expectfn "$clixon_cli -1 -f $cfg -l o set crypto des:des3" 0 "^$" new "cli validate" expectfn "$clixon_cli -1 -f $cfg -l o validate" 0 "^$" +new "Netconf set acl-type" +expecteof "$clixon_netconf -qf $cfg" 0 'xmc:ipv4-acl-type]]>]]>' '^]]>]]>$' + +new "netconf validate " +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" + +new "Netconf set undefined acl-type" +expecteof "$clixon_netconf -qf $cfg" 0 'xundefined]]>]]>' '^]]>]]>$' + +new "netconf validate fail" +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" '^applicationoperation-failederrorIdentityref validation failed, mc:undefined not derived from acl-base]]>]]>' + +new "netconf discard-changes" +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" + +new "CLI set acl-type" +expectfn "$clixon_cli -1 -f $cfg -l o set acls acl x type mc:ipv4-acl-type" 0 "^$" + +new "cli validate" +expectfn "$clixon_cli -1 -f $cfg -l o validate" 0 "^$" + +new "CLI set wrong acl-type" +expectfn "$clixon_cli -1 -f $cfg -l o set acls acl x type undefined" 0 "^$" + +new "cli validate" +expectfn "$clixon_cli -1 -f $cfg -l o validate" 255 "Identityref validation failed" + +# test empty identityref list +new "cli set empty" +expectfn "$clixon_cli -1 -f $cfg -l o set e undefined" 0 "^$" + +new "cli validate" +expectfn "$clixon_cli -1 -f $cfg -l o validate" 255 "Identityref validation failed" + +new "netconf discard-changes" +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" + if [ $BE -eq 0 ]; then exit # BE fi diff --git a/test/test_insert.sh b/test/test_insert.sh index 9699b314..97a53327 100755 --- a/test/test_insert.sh +++ b/test/test_insert.sh @@ -26,7 +26,6 @@ cat < $cfg /usr/local/var/$APPNAME/$APPNAME.sock /usr/local/var/$APPNAME/$APPNAME.pidfile $dir - true EOF diff --git a/test/test_leafref.sh b/test/test_leafref.sh index b67cb26e..ef8c62b5 100755 --- a/test/test_leafref.sh +++ b/test/test_leafref.sh @@ -125,7 +125,7 @@ if [ $BE -ne 0 ]; then start_backend -s init -f $cfg new "waiting" - sleep $RCWAIT + wait_backend fi new "leafref base config" diff --git a/test/test_minmax.sh b/test/test_minmax.sh index 5c7faf5d..fd6da008 100755 --- a/test/test_minmax.sh +++ b/test/test_minmax.sh @@ -115,7 +115,7 @@ if [ $BE -ne 0 ]; then start_backend -s init -f $cfg new "waiting" - sleep $RCWAIT + wait_backend fi new "minmax: minimal" diff --git a/test/test_nacm.sh b/test/test_nacm.sh index b7b914ae..c50308ab 100755 --- a/test/test_nacm.sh +++ b/test/test_nacm.sh @@ -124,16 +124,15 @@ fi new "kill old restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" -sleep 1 new "start restconf daemon (-a is enable basic authentication)" start_restconf -f $cfg -- -a new "waiting" -sleep $RCWAIT +wait_backend +wait_restconf new "auth get" -expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 'null - ' +expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-type": "application","error-tag": "invalid-value","error-severity": "error","error-message": "Instance does not exist"}}}} ' # explicitly disable nacm (regression on netgate bug) new "disable nacm" diff --git a/test/test_nacm_default.sh b/test/test_nacm_default.sh index 715ee306..5916ffa4 100755 --- a/test/test_nacm_default.sh +++ b/test/test_nacm_default.sh @@ -101,14 +101,12 @@ EOF new "kill old restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" - sleep 1 new "start restconf daemon (-a is enable basic authentication)" start_restconf -f $cfg -- -a new "waiting" wait_backend wait_restconf - #----------- First get case "$ret1" in @@ -117,8 +115,7 @@ EOF ;; 1) ret='{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} ' ;; - 2) ret='null - ' + 2) ret='{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-type": "application","error-tag": "invalid-value","error-severity": "error","error-message": "Instance does not exist"}}}} ' ;; esac @@ -142,8 +139,7 @@ EOF ;; 1) ret='{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} ' ;; - 2) ret='null - ' + 2) ret='{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-type": "application","error-tag": "invalid-value","error-severity": "error","error-message": "Instance does not exist"}}}} ' ;; 3) ret='{"nacm-example:x": 42} ' diff --git a/test/test_nacm_module_read.sh b/test/test_nacm_module_read.sh index b32774c0..1d87a9c1 100755 --- a/test/test_nacm_module_read.sh +++ b/test/test_nacm_module_read.sh @@ -139,12 +139,12 @@ fi new "kill old restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" -sleep 1 new "start restconf daemon (-a is enable basic authentication)" start_restconf -f $cfg -- -a new "waiting" -sleep $RCWAIT +wait_backend +wait_restconf new "auth set authentication config" expecteof "$clixon_netconf -qf $cfg" 0 "$RULES]]>]]>" "^]]>]]>$" @@ -200,8 +200,7 @@ expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/clixon-e ' new "limit read other module fail" -expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 'null - ' +expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-type": "application","error-tag": "invalid-value","error-severity": "error","error-message": "Instance does not exist"}}}} ' new "limit read state OK" expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/clixon-example:state)" 0 '{"clixon-example:state": {"op": ["42","41","43"]}} diff --git a/test/test_nacm_module_write.sh b/test/test_nacm_module_write.sh index d1bbe6d0..a88951e8 100755 --- a/test/test_nacm_module_write.sh +++ b/test/test_nacm_module_write.sh @@ -148,12 +148,12 @@ fi new "kill old restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" -sleep 1 new "start restconf daemon (-a is enable basic authentication)" start_restconf -f $cfg -- -a new "waiting" -sleep $RCWAIT +wait_backend +wait_restconf # Set nacm from scratch nacm(){ diff --git a/test/test_nacm_protocol.sh b/test/test_nacm_protocol.sh index c9092ac9..59ce33c7 100755 --- a/test/test_nacm_protocol.sh +++ b/test/test_nacm_protocol.sh @@ -148,12 +148,12 @@ fi new "kill old restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" -sleep 1 new "start restconf daemon (-a is enable basic authentication)" start_restconf -f $cfg -- -a new "waiting" -sleep $RCWAIT +wait_backend +wait_restconf new "auth set authentication config" expecteof "$clixon_netconf -qf $cfg" 0 "$RULES]]>]]>" "^]]>]]>$" @@ -191,9 +191,8 @@ expecteq "$(curl -u guest:bar -sS -X DELETE http://localhost/restconf/data)" 0 ' new "deny-delete-config: limited fail (restconf) ok" expecteq "$(curl -u wilma:bar -sS -X DELETE http://localhost/restconf/data)" 0 '' -new "admin get nacm (should be null)" -expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 'null - ' +new "admin get nacm (should fail)" +expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-type": "application","error-tag": "invalid-value","error-severity": "error","error-message": "Instance does not exist"}}}} ' new "deny-delete-config: admin ok (restconf)" expecteq "$(curl -u andy:bar -sS -X DELETE http://localhost/restconf/data)" 0 '' diff --git a/test/test_order.sh b/test/test_order.sh index 9bb23462..0f4ea9f3 100755 --- a/test/test_order.sh +++ b/test/test_order.sh @@ -162,7 +162,7 @@ if [ $BE -ne 0 ]; then start_backend -s running -f $cfg -- -s new "waiting" - sleep $RCWAIT + wait_backend fi # STATE (should not be ordered) diff --git a/test/test_restconf2.sh b/test/test_restconf2.sh index 84aba720..ec1486d7 100755 --- a/test/test_restconf2.sh +++ b/test/test_restconf2.sh @@ -137,7 +137,7 @@ new "restconf DELETE" expectfn 'curl -s -X DELETE http://localhost/restconf/data/example:cont1' 0 "" new "restconf GET null datastore" -expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 'null' +expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-type": "application","error-tag": "invalid-value","error-severity": "error","error-message": "Instance does not exist"}}}}' new "restconf POST initial tree" expectfn 'curl -s -X POST -d {"example:cont1":{"interface":{"name":"local0","type":"regular"}}} http://localhost/restconf/data' 0 "" @@ -149,7 +149,7 @@ new "restconf DELETE whole datastore" expectfn 'curl -s -X DELETE http://localhost/restconf/data' 0 "" new "restconf GET null datastore" -expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 'null' +expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-type": "application","error-tag": "invalid-value","error-severity": "error","error-message": "Instance does not exist"}}}}' new "restconf PUT initial datastore" expectfn 'curl -s -X PUT -d {"data":{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}} http://localhost/restconf/data' 0 "" diff --git a/test/test_restconf_err.sh b/test/test_restconf_err.sh new file mode 100755 index 00000000..fb5e27da --- /dev/null +++ b/test/test_restconf_err.sh @@ -0,0 +1,120 @@ +#!/bin/bash +# Restconf error-code functionality +# See RFC8040 +# Testcases: +# Sec 4.3 (GET): If a retrieval request for a data resource represents an +# instance that does not exist, then an error response containing a "404 Not +# Found" status-line MUST be returned by the server. The error-tag +# value "invalid-value" is used in this case. + +# Magic line must be first in script (see README.md) +s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi + +APPNAME=example + +cfg=$dir/conf.xml +fyang=$dir/restconf.yang +fxml=$dir/initial.xml + +# example +cat < $cfg + + $cfg + /usr/local/share/clixon + $IETFRFC + $fyang + false + /usr/local/var/$APPNAME/$APPNAME.sock + $dir/restconf.pidfile + /usr/local/var/$APPNAME + +EOF + +cat < $fyang +module example{ + yang-version 1.1; + namespace "urn:example:clixon"; + prefix ex; + list a { + key k; + leaf k { + type int32; + } + leaf description{ + type string; + } + leaf b{ + type string; + } + container c{ + presence "for test"; + } + list d{ + key k; + leaf k { + type string; + } + } + } +} +EOF + +# Initial tree +XML=$(cat <0No leaf b, No container c, No leaf d +EOF + ) + +new "test params: -f $cfg" + +if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -zf $cfg + if [ $? -ne 0 ]; then + err + fi + sudo pkill clixon_backend # to be sure + new "start backend -s init -f $cfg" + start_backend -s init -f $cfg +fi + +new "kill old restconf daemon" +sudo pkill -u www-data -f "/www-data/clixon_restconf" + +new "start restconf daemon" +start_restconf -f $cfg + +new "waiting" +wait_backend +wait_restconf + +new "restconf POST initial tree" +expecteq "$(curl -s -X POST -H 'Content-Type: application/yang-data+xml' -d "$XML" http://localhost/restconf/data)" 0 '' + +new "restconf GET initial datastore" +expecteq "$(curl -s -X GET -H 'Accept: application/yang-data+xml' http://localhost/restconf/data/example:a)" 0 "$XML + " + +new "restconf GET non-existent container header" +expectfn "curl -s -I -X GET http://localhost/restconf/data/example:a/c" 0 "HTTP/1.1 404 Not Found" + +new "restconf GET non-existent container body" +expectfn "curl -s -X GET http://localhost/restconf/data/example:a/c" 0 '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-type": "application","error-tag": "invalid-value","error-severity": "error","error-message": "Instance does not exist"}}}}' + +new "Kill restconf daemon" +stop_restconf + +if [ $BE -eq 0 ]; then + exit # BE +fi + +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 + +rm -rf $dir diff --git a/test/test_restconf_startup.sh b/test/test_restconf_startup.sh index 36037af8..2513c7a8 100755 --- a/test/test_restconf_startup.sh +++ b/test/test_restconf_startup.sh @@ -68,7 +68,8 @@ testrun(){ start_restconf -f $cfg -y $fyang $option new "waiting" - sleep $RCWAIT + wait_backend + wait_restconf new "restconf put 42" expecteq "$(curl -s -X PUT http://localhost/restconf/data/example:x/y=42 -d '{"example:y":{"a":"42","b":"42"}}')" 0 "" diff --git a/test/test_rpc.sh b/test/test_rpc.sh index 64ed55c3..8190608a 100755 --- a/test/test_rpc.sh +++ b/test/test_rpc.sh @@ -50,7 +50,9 @@ new "start restconf daemon" start_restconf -f $cfg new "waiting" -sleep $RCWAIT +wait_backend +wait_restconf + new "rpc tests" diff --git a/test/test_startup.sh b/test/test_startup.sh index 691c7df7..afb35ed8 100755 --- a/test/test_startup.sh +++ b/test/test_startup.sh @@ -86,7 +86,7 @@ testrun(){ start_backend -s $mode -f $cfg -c $dir/extra_db new "waiting" - sleep $RCWAIT + wait_backend else new "Restart backend as eg follows: -Ff $cfg -s $mode -c $dir/extra_db # $BETIMEOUT s" sleep $BETIMEOUT diff --git a/test/test_transaction.sh b/test/test_transaction.sh index 69bddea0..fefe6122 100755 --- a/test/test_transaction.sh +++ b/test/test_transaction.sh @@ -121,8 +121,9 @@ if [ $BE -ne 0 ]; then fi new "start backend -s init -f $cfg -l f$flog -- -t /x/y[a=$errnr]" start_backend -s init -f $cfg -l f$flog -- -t /x/y[a=$errnr] # -t means transaction logging + new "waiting" - sleep $RCWAIT + wait_backend fi let nr=0 @@ -177,7 +178,7 @@ new "3. Validate system-error config (9999 not in range)" expecteof "$clixon_netconf -qf $cfg" 0 "$nr9999]]>]]>" '^]]>]]>$' new "Validate system-error validate (should fail)" -expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^applicationbad-elementberrorNumber out of range: 9999]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^applicationbad-elementberrorNumber 9999 out of range: 0 - 100]]>]]>$' new "Validate system-error discard-changes" expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" diff --git a/test/test_type.sh b/test/test_type.sh index fb3e9aaa..96ac57da 100755 --- a/test/test_type.sh +++ b/test/test_type.sh @@ -192,6 +192,10 @@ module example{ pattern '[a-zA-Z_][a-zA-Z0-9_\-.]*'; } } + leaf bool { + description "For testing different truth values in CLI"; + type boolean; + } } EOF @@ -231,14 +235,14 @@ EOF start_backend -s init -f $cfg new "waiting" - sleep $RCWAIT + wait_backend fi new "cli set transitive string. type is alpha followed by number and is defined in three levels of modules" expectfn "$clixon_cli -1f $cfg -l o set c talle x99" 0 '^$' new "cli set transitive string error. Wrong type" - expectfn "$clixon_cli -1f $cfg -l o set c talle 9xx" 255 '^CLI syntax error: "set c talle 9xx": Unknown command$' + expectfn "$clixon_cli -1f $cfg -l o set c talle 9xx" 255 '^CLI syntax error: "set c talle 9xx": regexp match fail: 9xx does not match \[a-z\]\[0-9\]\*$' new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" @@ -266,10 +270,10 @@ EOF expectfn "$clixon_cli -1f $cfg -l o -l o validate" 0 '^$' new "cli set transitive union error. should fail" - expectfn "$clixon_cli -1f $cfg -l o set c ulle kalle" 255 '^CLI syntax error: "set c ulle kalle": Unknown command$' + expectfn "$clixon_cli -1f $cfg -l o set c ulle kalle" 255 "^CLI syntax error: \"set c ulle kalle\": 'kalle' is not a number$" new "cli set transitive union error int" - expectfn "$clixon_cli -1f $cfg -l o set c ulle 55" 255 '^CLI syntax error: "set c ulle 55": Unknown command$' + expectfn "$clixon_cli -1f $cfg -l o set c ulle 55" 255 '^CLI syntax error: "set c ulle 55": Number 55 out of range: 4 - 44$' new "netconf set transitive union error int" expecteof "$clixon_netconf -qf $cfg" 0 '55]]>]]>' "^]]>]]>" @@ -354,13 +358,13 @@ EOF #expectfn "$clixon_cli -1f $cfg -l o set num1 \-100" 0 '^$' new "cli range test num1 2 error" - expectfn "$clixon_cli -1f $cfg -l o set num1 2" 255 '^CLI syntax error: "set num1 2": Unknown command$' + expectfn "$clixon_cli -1f $cfg -l o set num1 2" 255 '^CLI syntax error: "set num1 2": Number 2 out of range: 1 - 1$' new "netconf range set num1 -1" expecteof "$clixon_netconf -qf $cfg" 0 '-1]]>]]>' "^]]>]]>$" new "netconf validate num1 -1 wrong" - expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" '^applicationbad-elementnum1errorNumber out of range: -1]]>]]>$' + expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" '^applicationbad-elementnum1errorNumber -1 out of range: 1 - 1]]>]]>$' new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" @@ -368,19 +372,19 @@ EOF #-------- num2 range and blanks new "cli range test num2 3 error" - expectfn "$clixon_cli -1f $cfg -l o set num2 3" 255 '^CLI syntax error: "set num2 3": Number out of range: 3$' + expectfn "$clixon_cli -1f $cfg -l o set num2 3" 255 '^CLI syntax error: "set num2 3": Number 3 out of range: 4 - 4000$' new "cli range test num2 1000 ok" expectfn "$clixon_cli -1f $cfg -l o set num2 1000" 0 '^$' new "cli range test num2 5000 error" - expectfn "$clixon_cli -1f $cfg -l o set num2 5000" 255 '^CLI syntax error: "set num2 5000": Unknown command$' + expectfn "$clixon_cli -1f $cfg -l o set num2 5000" 255 '^CLI syntax error: "set num2 5000": Number 5000 out of range: 4 - 4000$' new "netconf range set num2 3 fail" expecteof "$clixon_netconf -qf $cfg" 0 '3]]>]]>' "^]]>]]>$" new "netconf validate num2 3 fail" - expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" '^applicationbad-elementnum2errorNumber out of range: 3]]>]]>$' + expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" '^applicationbad-elementnum2errorNumber 3 out of range: 4 - 4000]]>]]>$' new "netconf range set num2 1000 ok" expecteof "$clixon_netconf -qf $cfg" 0 '1000]]>]]>' "^]]>]]>$" @@ -392,7 +396,7 @@ EOF expecteof "$clixon_netconf -qf $cfg" 0 '5000]]>]]>' "^]]>]]>$" new "netconf validate num2 5000 fail" - expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" '^applicationbad-elementnum2errorNumber out of range: 5000]]>]]>$' + expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" '^applicationbad-elementnum2errorNumber 5000 out of range: 4 - 4000]]>]]>$' new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" @@ -403,7 +407,7 @@ EOF expectfn "$clixon_cli -1f $cfg -l o set num3 42" 0 '^$' new "cli range test num3 260 fail" - expectfn "$clixon_cli -1f $cfg -l o set num3 260" 255 '^CLI syntax error: "set num3 260": Unknown command$' + expectfn "$clixon_cli -1f $cfg -l o set num3 260" 255 '^CLI syntax error: "set num3 260": Number 260 out of range: 0 - 255$' new "cli range test num3 -1 fail" expectfn "$clixon_cli -1f $cfg -l o set num3 -1" 255 "CLI syntax error:" @@ -412,7 +416,7 @@ EOF expecteof "$clixon_netconf -qf $cfg" 0 '260]]>]]>' "^]]>]]>$" new "netconf validate num3 260 fail" - expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" '^applicationbad-elementnum3error260 is out of range(type is uint8)]]>]]>$' + expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" '^applicationbad-elementnum3errorNumber 260 out of range: 0 - 255]]>]]>$' new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" @@ -420,19 +424,19 @@ EOF #-------- num4 multiple ranges 1..2 | 42..50 new "cli range test num4 multiple 0 fail" - expectfn "$clixon_cli -1f $cfg -l o set num4 0" 255 '^CLI syntax error: "set num4 0": Number out of range: 0$' + expectfn "$clixon_cli -1f $cfg -l o set num4 0" 255 '^CLI syntax error: "set num4 0": Number 0 out of range: 1 - 2, 42 - 50$' new "cli range test num4 multiple 2 ok" expectfn "$clixon_cli -1f $cfg -l e set num4 2" 0 '^$' new "cli range test num4 multiple 20 fail" - expectfn "$clixon_cli -1f $cfg -l o set num4 20" 255 '^CLI syntax error: "set num4 20": Unknown command$' + expectfn "$clixon_cli -1f $cfg -l o set num4 20" 255 '^CLI syntax error: "set num4 20": Number 20 out of range: 1 - 2, 42 - 50$' new "cli range test num4 multiple 42 ok" expectfn "$clixon_cli -1f $cfg -l o set num4 42" 0 '^$' new "cli range test num4 multiple 99 fail" - expectfn "$clixon_cli -1f $cfg -l o set num4 99" 255 '^CLI syntax error: "set num4 99": Unknown command$' + expectfn "$clixon_cli -1f $cfg -l o set num4 99" 255 '^CLI syntax error: "set num4 99": Number 99 out of range: 1 - 2, 42 - 50$' new "netconf range set num4 multiple 2" expecteof "$clixon_netconf -qf $cfg" 0 '42]]>]]>' "^]]>]]>$" @@ -467,7 +471,7 @@ EOF expectfn "$clixon_cli -1f $cfg -l o set dec 15.0" 0 '^$' new "cli range dec64 multiple 30.0 fail" - expectfn "$clixon_cli -1f $cfg -l o set dec 30.0" 255 '^CLI syntax error: "set dec 30.0": Unknown command$' + expectfn "$clixon_cli -1f $cfg -l o set dec 30.0" 255 '^CLI syntax error: "set dec 30.0": Number 30.000 out of range: -3.500 - -2.500, 0.000 - 0.000, 10.000 - 20.000$' new "dec64 discard-changes" expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" @@ -477,7 +481,7 @@ EOF expecteof "$clixon_netconf -qf $cfg" 0 '-3.59]]>]]>' "^]]>]]>$" new "netconf range dec64 -3.59 validate fail" - expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" '^applicationbad-elementdecerrorNumber out of range' + expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" '^applicationbad-elementdecerrorNumber -3.590 out of range' new "netconf range dec64 -3.5" expecteof "$clixon_netconf -qf $cfg" 0 '-3.500]]>]]>' "^]]>]]>$" @@ -489,13 +493,13 @@ EOF expecteof "$clixon_netconf -qf $cfg" 0 '-2]]>]]>' "^]]>]]>$" new "netconf range dec64 -2 validate fail" - expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" '^applicationbad-elementdecerrorNumber out of range' + expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" '^applicationbad-elementdecerrorNumber -2.000 out of range' new "netconf range dec64 -0.001" expecteof "$clixon_netconf -qf $cfg" 0 '-0.001]]>]]>' "^]]>]]>$" new "netconf range dec64 -0.001 validate fail" - expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" '^applicationbad-elementdecerrorNumber out of range' + expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" '^applicationbad-elementdecerrorNumber -0.001 out of range' new "netconf range dec64 0.0" expecteof "$clixon_netconf -qf $cfg" 0 '0.0]]>]]>' "^]]>]]>$" @@ -507,18 +511,18 @@ EOF expecteof "$clixon_netconf -qf $cfg" 0 '+0.001]]>]]>' "^]]>]]>$" new "netconf range dec64 +0.001 validate fail" - expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" '^applicationbad-elementdecerrorNumber out of range' + expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" '^applicationbad-elementdecerrorNumber 0.001 out of range' #----------------string ranges--------------------- #-------- len1 single range (2) new "cli length test len1 1 fail" - expectfn "$clixon_cli -1f $cfg -l o set len1 x" 255 '^CLI syntax error: "set len1 x": String length not within limits: 1$' + expectfn "$clixon_cli -1f $cfg -l o set len1 x" 255 '^CLI syntax error: "set len1 x": String length 1 out of range: 2 - 2$' new "cli length test len1 2 OK" expectfn "$clixon_cli -1f $cfg -l o set len1 xy" 0 '^$' new "cli length test len1 3 error" - expectfn "$clixon_cli -1f $cfg -l o set len1 hej" 255 '^CLI syntax error: "set len1 hej": Unknown command$' + expectfn "$clixon_cli -1f $cfg -l o set len1 hej" 255 '^CLI syntax error: "set len1 hej": String length 3 out of range: 2 - 2$' new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" @@ -527,12 +531,12 @@ EOF expecteof "$clixon_netconf -qf $cfg" 0 'x]]>]]>' "^]]>]]>$" new "netconf validate len1 1 wrong" - expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" '^applicationbad-elementlen1errorstring length out of range: 1]]>]]>$' + expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" '^applicationbad-elementlen1errorString length 1 out of range: 2 - 2]]>]]>$' #-------- len2 range and blanks new "cli length test len2 3 error" - expectfn "$clixon_cli -1f $cfg -l o set len2 ab" 255 '^CLI syntax error: "set len2 ab": String length not within limits: 2$' + expectfn "$clixon_cli -1f $cfg -l o set len2 ab" 255 '^CLI syntax error: "set len2 ab": String length 2 out of range: 4 - 4000$' new "cli length test len2 42 ok" expectfn "$clixon_cli -1f $cfg -l o set len2 hejhophdsakjhkjsadhkjsahdkjsad" 0 '^$' @@ -547,32 +551,32 @@ EOF #-------- len4 multiple ranges 2..3 | 20-29 new "cli length test len4 1 error" - expectfn "$clixon_cli -1f $cfg -l o set len4 a" 255 '^CLI syntax error: "set len4 a": String length not within limits: 1$' + expectfn "$clixon_cli -1f $cfg -l o set len4 a" 255 '^CLI syntax error: "set len4 a": String length 1 out of range: 2 - 3, 20 - 29$' new "cli length test len4 2 ok" expectfn "$clixon_cli -1f $cfg -l o set len4 ab" 0 '^$' new "cli length test len4 10 error" - expectfn "$clixon_cli -1f $cfg -l o set len4 abcdefghij" 255 '^CLI syntax error: "set len4 abcdefghij": Unknown command$' + expectfn "$clixon_cli -1f $cfg -l o set len4 abcdefghij" 255 '^CLI syntax error: "set len4 abcdefghij": String length 10 out of range: 2 - 3, 20 - 29$' new "cli length test len4 20 ok" expectfn "$clixon_cli -1f $cfg -l o set len4 abcdefghijabcdefghija" 0 '^$' new "cli length test len4 30 error" - expectfn "$clixon_cli -1f $cfg -l o set len4 abcdefghijabcdefghijabcdefghij" 255 '^CLI syntax error: "set len4 abcdefghijabcdefghijabcdefghij": Unknown command$' + expectfn "$clixon_cli -1f $cfg -l o set len4 abcdefghijabcdefghijabcdefghij" 255 '^CLI syntax error: "set len4 abcdefghijabcdefghijabcdefghij": String length 30 out of range: 2 - 3, 20 - 29$' # XSD schema -> POSIX ECE translation new "cli yang pattern \d ok" expectfn "$clixon_cli -1f $cfg -l o set digit4 0123" 0 '^$' new "cli yang pattern \d error" - expectfn "$clixon_cli -1f $cfg -l o set digit4 01b2" 255 '^CLI syntax error: "set digit4 01b2": Unknown command$' + expectfn "$clixon_cli -1f $cfg -l o set digit4 01b2" 255 '^CLI syntax error: "set digit4 01b2": regexp match fail: 01b2 does not match' new "cli yang pattern \w ok" expectfn "$clixon_cli -1f $cfg -l o set word4 abc9" 0 '^$' new "cli yang pattern \w error" - expectfn "$clixon_cli -1f $cfg -l o set word4 ab%3" 255 '^CLI syntax error: "set word4 ab%3": Unknown command$' + expectfn "$clixon_cli -1f $cfg -l o set word4 ab%3" 255 '^CLI syntax error: "set word4 ab%3": regexp match fail: ab%3 does not match' new "netconf pattern \w" expecteof "$clixon_netconf -qf $cfg" 0 'aXG9]]>]]>' "^]]>]]>$" @@ -589,7 +593,6 @@ EOF new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" - #------ minus new "type with minus" @@ -601,6 +604,27 @@ EOF #new "cli type with minus" #expectfn "$clixon_cli -1f $cfg -l o set name my-name" 0 '^$' + #------ cli truth-values: true/on/enable false/off/disable + + new "cli truth: true" + expectfn "$clixon_cli -1f $cfg -l o set bool true" 0 '^$' + new "cli truth: false" + expectfn "$clixon_cli -1f $cfg -l o set bool false" 0 '^$' + new "cli truth: on" + expectfn "$clixon_cli -1f $cfg -l o set bool on" 0 '^$' + new "cli verify on translates to true" + expectfn "$clixon_cli -1f $cfg -l o show conf" 0 'bool true;' + new "cli truth: off" + expectfn "$clixon_cli -1f $cfg -l o set bool off" 0 '^$' + new "cli verify off translates to false" + expectfn "$clixon_cli -1f $cfg -l o show conf" 0 'bool false;' + new "cli truth: enable" + expectfn "$clixon_cli -1f $cfg -l o set bool enable" 0 '^$' + new "cli truth: disable" + expectfn "$clixon_cli -1f $cfg -l o set bool disable" 0 '^$' + new "cli truth: wrong" + expectfn "$clixon_cli -1f $cfg -l o set bool wrong" 255 "'wrong' is not a boolean value" + if [ $BE -ne 0 ]; then new "Kill backend" # Check if premature kill @@ -619,7 +643,7 @@ testrun nocache # Run with db cache testrun cache -# Run with -testrun cache-zerocopy +# Run with zero-copy XXX does not work +#testrun cache-zerocopy rm -rf $dir diff --git a/test/test_type_range.sh b/test/test_type_range.sh new file mode 100755 index 00000000..0760d485 --- /dev/null +++ b/test/test_type_range.sh @@ -0,0 +1,317 @@ +#!/bin/bash +# Range type tests. +# Mainly error messages and multiple ranges +# Tests all int types including decimal64 and string length ranges +# See also test_type.sh + +# Magic line must be first in script (see README.md) +s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi + +APPNAME=example + +# Which format to use as datastore format internally +: ${format:=xml} + +cfg=$dir/conf_yang.xml +fyang=$dir/type.yang +dclispec=$dir/clispec/ + +# XXX: add more types, now only uint8 and int8 +cat < $fyang +module example{ + yang-version 1.1; + namespace "urn:example:clixon"; + prefix ex; + typedef tint8{ + type int8{ + range "1..10 | 14..20"; + } + } + typedef tint16{ + type int16{ + range "1..10 | 14..20"; + } + } + typedef tint32{ + type int32{ + range "1..10 | 14..20"; + } + } + typedef tint64{ + type int64{ + range "1..10 | 14..20"; + } + } + typedef tuint8{ + type uint8{ + range "1..10 | 14..20"; + } + } + typedef tuint16{ + type uint16{ + range "1..10 | 14..20"; + } + } + typedef tuint32{ + type uint32{ + range "1..10 | 14..20"; + } + } + typedef tuint64{ + type uint64{ + range "1..10 | 14..20"; + } + } + typedef tdecimal64{ + type decimal64{ + fraction-digits 3; + range "1..10 | 14..20"; + } + } + typedef tstring{ + type string{ + length "1..10 | 14..20"; + } + } + /* here follows constrained ints */ + leaf lint8 { + type tint8; + } + leaf lint16 { + type tint16; + } + leaf lint32 { + type tint32; + } + leaf lint64 { + type tint64; + } + leaf luint8 { + type tuint8; + } + leaf luint16 { + type tuint16; + } + leaf luint32 { + type tuint32; + } + leaf luint64 { + type tuint64; + } + leaf ldecimal64 { + type tdecimal64; + } + leaf lstring { + type tstring; + } + /* here follows unlimited ints */ + leaf rint8 { + type int8; + } + leaf rint16 { + type int16; + } + leaf rint32 { + type int32; + } + leaf rint64 { + type int64; + } + leaf ruint8 { + type uint8; + } + leaf ruint16 { + type uint16; + } + leaf ruint32 { + type uint32; + } + leaf ruint64 { + type uint64; + } + leaf rdecimal64 { + type decimal64{ + fraction-digits 3; + } + } +} +EOF + +mkdir $dclispec + +# clispec for both generated cli and a hardcoded range check +cat < $dclispec/clispec.cli + CLICON_MODE="example"; + CLICON_PROMPT="%U@%H> "; + CLICON_PLUGIN="example_cli"; + + # Manually added (not generated) + manual hint8 ; + manual hint16 ; + manual hint32 ; + manual hint64 ; + + manual huint8 ; + manual huint16 ; + manual huint32 ; + manual huint64 ; + + manual hdecimal64 ; + + manual hstring ; + + # Generated cli + set @datamodel, cli_set(); + merge @datamodel, cli_merge(); + create @datamodel, cli_create(); + show, cli_show_config("candidate", "text", "/"); + quit("Quit"), cli_quit(); +EOF + +cat < $cfg + + $cfg + $dir + /usr/local/share/clixon + $IETFRFC + $fyang + $dclispec + /usr/local/lib/$APPNAME/cli + $APPNAME + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile + 1 + /usr/local/var/$APPNAME + $format + +EOF + +# Type range tests. +# Parameters: 1: type (eg uint8) +# 2: val OK +# 3: eval Invalid value +# 4: post (eg .000 - special for decimal64, others should have "") +testrange(){ + t=$1 + val=$2 + eval=$3 + post=$4 + + if [ $t = "string" ]; then # special case for string type error msg + len=$(echo -n "$eval" | wc -c) + errmsg="String length $len out of range: 1$post - 10$post, 14$post - 20$post" + else + errmsg="Number $eval$post out of range: 1$post - 10$post, 14$post - 20$post" + fi + + new "generated cli set $t leaf invalid" + expectfn "$clixon_cli -1f $cfg -l o set l$t $eval" 255 "$errmsg"; + + new "generated cli set $t leaf OK" + expectfn "$clixon_cli -1f $cfg -l o set l$t $val" 0 '^$' + + # XXX Error in cligen order: Unknown command vs Number out of range + # olof@vandal> set luint8 0 + # CLI syntax error: "set luint8 0": Number 0 is out of range: 14 - 20 + # olof@vandal> set luint8 1 + # olof@vandal> set luint8 0 + # CLI syntax error: "set luint8 0": Unknown command +# (SAME AS FIRST ^) + new "generated cli set $t leaf invalid" + expectfn "$clixon_cli -1f $cfg -l o set l$t $eval" 255 "$errmsg" + + new "manual cli set $t leaf OK" + expectfn "$clixon_cli -1f $cfg -l o man h$t $val" 0 '^$' + + new "manual cli set $t leaf invalid" + echo "$clixon_cli -1f $cfg -l o set h$t $eval" + expectfn "$clixon_cli -1f $cfg -l o set l$t $eval" 255 "$errmsg" + + new "discard" + expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" + + new "Netconf set invalid $t leaf" + expecteof "$clixon_netconf -qf $cfg" 0 "$eval]]>]]>" "^]]>]]>$" + + new "netconf get config" + expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^$eval]]>]]>$" + + new "netconf validate invalid range" + expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^applicationbad-elementl$terror$errmsg]]>]]>$" + + new "discard" + expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" +} + +# Type unlimited value range test. Only test invalid number out of range of type +# Parameters: 1: type (eg uint8) +# 2: val +# 3: post (eg .000 - special for decimal64, others should have "") +testunlimit(){ + t=$1 + val=$2 + rmin=$3 + rmax=$4 + post=$5 + + errmsg="Number $val$post out of range: $rmin$post - $rmax$post" + + new "Netconf set invalid $t leaf" + expecteof "$clixon_netconf -qf $cfg" 0 "$val]]>]]>" "^]]>]]>$" + + new "netconf validate invalid range" + expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^applicationbad-elementr$terror$errmsg]]>]]>$" + + new "discard" + expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" +} + +if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -zf $cfg + if [ $? -ne 0 ]; then + err + fi + new "start backend -s init -f $cfg" + start_backend -s init -f $cfg + + new "waiting" + wait_backend +fi + +new "test params: -f $cfg" + +# Test all int types +testunlimit int8 300 -128 127 "" +testunlimit int16 73000 -32768 32767 "" +testunlimit int32 4900000000 -2147483648 2147483647 "" +testunlimit int64 49739274983274983274983274 -9223372036854775808 9223372036854775807 "" +testunlimit uint8 300 0 255 "" +testunlimit uint16 73000 0 65535 "" +testunlimit uint32 4900000000 0 4294967295 "" +testunlimit uint64 49739274983274983274983274 0 18446744073709551615 "" +#testunlimit decimal64 49739274983274983274983274 -9223372036854775808 9223372036854775807 ".000" + +# Test all int types +for t in int8 int16 int32 int64 uint8 uint16 uint32 uint64; do + testrange $t 1 0 "" +done + +# decimal64 requires 3 decimals as postfix +testrange decimal64 1 0 ".000" + +# test string with lengthlimit +testrange string "012" "01234567890" "" + +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 + +rm -rf $dir diff --git a/test/test_union.sh b/test/test_union.sh index 8fa52a7b..7931ede3 100755 --- a/test/test_union.sh +++ b/test/test_union.sh @@ -89,7 +89,7 @@ if [ $BE -ne 0 ]; then start_backend -s init -f $cfg new "waiting" - sleep $RCWAIT + wait_backend fi new "cli set transitive string" @@ -99,7 +99,7 @@ new "cli set transitive union" expectfn "$clixon_cli -1f $cfg -l o set c ulle 33" 0 "^$" new "cli set transitive union error" -expectfn "$clixon_cli -1f $cfg -l o set c ulle kalle" 255 '^CLI syntax error: "set c ulle kalle": Unknown command$' +expectfn "$clixon_cli -1f $cfg -l o set c ulle kalle" 255 "^CLI syntax error: \"set c ulle kalle\": 'kalle' is not a number$" if [ $BE -eq 0 ]; then exit # BE diff --git a/test/test_unique.sh b/test/test_unique.sh index 799a51bb..a921ff53 100755 --- a/test/test_unique.sh +++ b/test/test_unique.sh @@ -92,7 +92,7 @@ if [ $BE -ne 0 ]; then start_backend -s init -f $cfg new "waiting" - sleep $RCWAIT + wait_backend fi # RFC test two-field caes diff --git a/test/test_upgrade.sh b/test/test_upgrade.sh index 03b59f7b..a3885f28 100755 --- a/test/test_upgrade.sh +++ b/test/test_upgrade.sh @@ -265,7 +265,7 @@ runtest(){ start_backend -s $mode -f $cfg -o "CLICON_XMLDB_MODSTATE=$modstate" new "waiting" - sleep $RCWAIT + wait_backend else new "Restart backend as eg follows: -Ff $cfg -s $mode -o \"CLICON_XMLDB_MODSTATE=$modstate\" ($BETIMEOUT s)" sleep $BETIMEOUT diff --git a/test/test_upgrade_auto.sh b/test/test_upgrade_auto.sh index 2165ecbe..bc78c449 100755 --- a/test/test_upgrade_auto.sh +++ b/test/test_upgrade_auto.sh @@ -254,8 +254,6 @@ if [ $BE -ne 0 ]; then new "start backend -s $mode -f $cfg" start_backend -s $mode -f $cfg fi -new "waiting" -sleep $RCWAIT new "kill old restconf daemon" sudo pkill -u www-data clixon_restconf @@ -264,7 +262,8 @@ new "start restconf daemon" start_restconf -f $cfg new "waiting" -sleep $RCWAIT +wait_backend +wait_restconf new "Check running db content" expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' "^$XML]]>]]>$" diff --git a/test/test_upgrade_interfaces.sh b/test/test_upgrade_interfaces.sh index c72e7b2c..5bba6688 100755 --- a/test/test_upgrade_interfaces.sh +++ b/test/test_upgrade_interfaces.sh @@ -265,8 +265,6 @@ testrun(){ new "start backend -s startup -f $cfg -- -u" start_backend -s startup -f $cfg -- -u fi - new "waiting" - sleep $RCWAIT new "kill old restconf daemon" sudo pkill -u www-data clixon_restconf @@ -275,7 +273,8 @@ testrun(){ start_restconf -f $cfg new "waiting" - sleep $RCWAIT + wait_backend + wait_restconf new "Check running db content" expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' "^$runxml]]>]]>$" diff --git a/test/test_upgrade_repair.sh b/test/test_upgrade_repair.sh index e752d1d5..a9c9bb80 100755 --- a/test/test_upgrade_repair.sh +++ b/test/test_upgrade_repair.sh @@ -112,8 +112,6 @@ if [ $BE -ne 0 ]; then new "start backend -s $mode -f $cfg" start_backend -s $mode -f $cfg fi -new "waiting" -sleep $RCWAIT new "kill old restconf daemon" sudo pkill -u www-data clixon_restconf @@ -122,7 +120,8 @@ new "start restconf daemon" start_restconf -f $cfg new "waiting" -sleep $RCWAIT +wait_backend +wait_restconf new "Check running db content is failsafe" expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' "^$SAMEXML]]>]]>$" diff --git a/test/test_when_must.sh b/test/test_when_must.sh index a57c4fe4..2fc4dcf7 100755 --- a/test/test_when_must.sh +++ b/test/test_when_must.sh @@ -102,7 +102,7 @@ if [ $BE -ne 0 ]; then start_backend -s init -f $cfg new "waiting" - sleep $RCWAIT + wait_backend fi new "when: add static route" diff --git a/test/test_yang_load.sh b/test/test_yang_load.sh index 19c826d5..df5e7195 100755 --- a/test/test_yang_load.sh +++ b/test/test_yang_load.sh @@ -85,7 +85,7 @@ if [ $BE -ne 0 ]; then start_backend -s init -f $cfg new "waiting" - sleep $RCWAIT + wait_backend fi new "1. Set newex" @@ -134,7 +134,7 @@ if [ $BE -ne 0 ]; then start_backend -s init -f $cfg new "waiting" - sleep $RCWAIT + wait_backend fi new "Set oldex" @@ -178,7 +178,7 @@ if [ $BE -ne 0 ]; then start_backend -s init -f $cfg new "waiting" - sleep $RCWAIT + wait_backend fi new "Set newex" @@ -222,7 +222,7 @@ if [ $BE -ne 0 ]; then start_backend -s init -f $cfg new "waiting" - sleep $RCWAIT + wait_backend fi new "Set oldex" @@ -266,7 +266,7 @@ if [ $BE -ne 0 ]; then start_backend -s init -f $cfg new "waiting" - sleep $RCWAIT + wait_backend fi new "Set newex" @@ -312,7 +312,7 @@ if [ $BE -ne 0 ]; then start_backend -s init -f $cfg new "waiting" - sleep $RCWAIT + wait_backend fi new "Set oldex" expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' @@ -357,7 +357,7 @@ if [ $BE -ne 0 ]; then start_backend -s init -f $cfg new "waiting" - sleep $RCWAIT + wait_backend fi new "Set oldex" @@ -403,7 +403,7 @@ if [ $BE -ne 0 ]; then start_backend -s init -f $cfg new "waiting" - sleep $RCWAIT + wait_backend fi new "Set oldex" diff --git a/test/test_yang_namespace.sh b/test/test_yang_namespace.sh index d5c925eb..113d0426 100755 --- a/test/test_yang_namespace.sh +++ b/test/test_yang_namespace.sh @@ -83,7 +83,8 @@ new "start restconf daemon" start_restconf -f $cfg new "waiting" -sleep $RCWAIT +wait_backend +wait_restconf new "netconf set x in example1" expecteof "$clixon_netconf -qf $cfg" 0 '42]]>]]>' '^]]>]]>$'