diff --git a/CHANGELOG.md b/CHANGELOG.md index a39ee238..5a658ce8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,6 +69,24 @@ ### API changes on existing features (you may need to change your code) +* The Clixon API has been extended with namespaces, or namespace contexts in the following cases: + * CLIspec functions have added namespace parameter: + * `cli_show_config ` --> `cli_show_config ` + * `cli_copy_config ...` --> `cli_copy_config ...` + * Xpath API + * `xpath_first(x, format, ...)` --> `xpath_first(x, nsc, format, ...)` + * `xpath_vec(x, format, vec, veclen, ...)` --> `xpath_vec(x, nsc, format, vec, veclen, ...)` + * `xpath_vec_flag(x, format, flags, vec, veclen, ...)` --> `xpath_vec_flag(x, format, flags, vec, veclen, ...)` + * `xpath_vec_bool(x, format, ...)` --> `xpath_vec_bool(x, nsc, format, ...)` + * `xpath_vec_ctx(x, xpath, xp)` --> `xpath_vec_ctx(x, nsc, xpath, xp)` + * xmldb_get0 has an added `nsc` parameter: + * `xmldb_get0(h, db, xpath, copy, xret, msd)` --> `xmldb_get0(h, db, nsc, xpath, copy, xret, msd)` + * The plugin statedata callback (ca_statedata) has been extended with an nsc parameter: + * `int example_statedata(clicon_handle h, cvec *nsc, char *xpath, cxobj *xstate);` + * 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);` + * RESTCONF strict namespace validation of data in POST and PUT. * Accepted: ``` @@ -191,6 +209,7 @@ ### Minor changes +* Rewrote `api_path2xpath` to handle namespaces. * `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. diff --git a/README.md b/README.md index e2354c15..50263250 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Clixon is a YANG-based configuration manager, with interactive CLI, NETCONF and RESTCONF interfaces, an embedded database and transaction -support. +mechanism. * [Background](#background) * [Frequently asked questions (FAQ)](doc/FAQ.md) @@ -17,7 +17,7 @@ support. * [Extending](#extending) * [Yang](#yang) * [CLI](doc/CLI.md) - * [XML and XPATH](#xml) + * [XML and XPATH](#xml-and-xpath) * [Netconf](#netconf) * [Restconf](#restconf) * [Datastore](datastore/README.md) @@ -36,10 +36,10 @@ support. ## Background Clixon was implemented to provide an open-source generic configuration -tool. The existing [CLIgen](http://www.cligen.se) tool was for command-lines only, while Clixon is a system with configuration database, xml and rest interfaces all defined by Yang. Most of the projects using Clixon are for embedded network and measuring devices. But Clixon can be used for other systems as well due to its modular and pluggable architecture. +tool. The existing [CLIgen](http://www.cligen.se) tool was for command-lines only, while Clixon is a system with configuration database, XML and REST interfaces all defined by Yang. Most of the projects using Clixon are for embedded network and measuring devices. But Clixon can be used for other systems as well due to its modular and pluggable architecture. Users of Clixon currently include: - * [Netgate](https://www.netgate.com) + * [Netgate](https://www.netgate.com) in particular the [Tnsr product](https://www.tnsr.com/product#architecture) * [CloudMon360](http://cloudmon360.com) * [Grideye](http://hagsand.se/grideye) * [Netclean](https://www.netclean.com/solutions/whitebox) # only CLIgen @@ -122,7 +122,7 @@ You then need to set the following configure option: libxml2 ``` -## XML +## XML and XPATH Clixon has its own implementation of XML and XPATH implementation. @@ -151,6 +151,53 @@ Note that base netconf namespace syntax is not enforced but recommended, which m ``` All other namespaces are enforced. +### XPATH and Namespaces + +XPATHs may contain prefixes. Example: `/if:a/if:b`. The prefixes have +associated namespaces. For example, `if` may be bound to +`urn:ietf:params:xml:ns:yang:ietf-interfaces`. The prefix to namespace binding is called a _namespace context_ (nsc). + +In the Clixon API, there are two variants on namespace contexts: _implicit_ (given by the XML); or _explicit_ given by an external mapping. +#### 1. Implicit namespace mapping + +Implicit mapping is typical for basic known XML, where the context is +given implicitly by the XML being evaluated. In node comparisons (eg +of `if:a`) only name and prefixes are compared. + +Example: +``` + XML: + XPATH: /if:a/ip:b +``` +When you call an xpath API function, call it with nsc set to NULL. This is the default. + +#### 2. Explicit namespace mapping + +Explicit binding is typical if the namespace context is independent +from the XML. Examples include 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: + * The set of namespace declarations are those in scope on the + * element. + */ + else + if (xml_nsctx_node(xfilter, &nsc) < 0) + goto done; + } /* Note xret can be pruned by nacm below (and change name), * so zero-copy cant be used + * Also, must use external namespace context here due to + * The set of namespace declarations are those in scope on the + * element. + */ + else + if (xml_nsctx_node(xfilter, &nsc) < 0) + goto done; + } /* Get config * Note xret can be pruned by nacm below and change name and * metrged with state data, so zero-copy cant be used + * Also, must use external namespace context here due to td_target, NULL) < 0) + if (xmldb_get0(h, candidate, NULL, "/", 0, &td->td_target, NULL) < 0) goto done; /* Clear flags xpath for get */ @@ -422,7 +422,7 @@ from_validate_common(clicon_handle h, /* 2. Parse xml trees * This is the state we are going from */ - if (xmldb_get0(h, "running", "/", 0, &td->td_src, NULL) < 0) + if (xmldb_get0(h, "running", NULL, "/", 0, &td->td_src, NULL) < 0) goto done; /* Clear flags xpath for get */ xml_apply0(td->td_src, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, diff --git a/apps/backend/backend_plugin.c b/apps/backend/backend_plugin.c index 754d4caf..711a8de0 100644 --- a/apps/backend/backend_plugin.c +++ b/apps/backend/backend_plugin.c @@ -122,6 +122,7 @@ clixon_plugin_reset(clicon_handle h, int clixon_plugin_statedata(clicon_handle h, yang_stmt *yspec, + cvec *nsc, char *xpath, cxobj **xret) { @@ -136,7 +137,7 @@ clixon_plugin_statedata(clicon_handle h, continue; if ((x = xml_new("config", NULL, NULL)) == NULL) goto done; - if (fn(h, xpath, x) < 0) + if (fn(h, nsc, xpath, x) < 0) goto fail; /* Dont quit here on user callbacks */ if (xml_apply(x, CX_ELMNT, xml_spec_populate, yspec) < 0) goto done; diff --git a/apps/backend/backend_plugin.h b/apps/backend/backend_plugin.h index 6b4643e3..1071fc4e 100644 --- a/apps/backend/backend_plugin.h +++ b/apps/backend/backend_plugin.h @@ -71,7 +71,8 @@ int backend_plugin_initiate(clicon_handle h); int clixon_plugin_reset(clicon_handle h, char *db); -int clixon_plugin_statedata(clicon_handle h, yang_stmt *yspec, char *xpath, cxobj **xtop); +int clixon_plugin_statedata(clicon_handle h, yang_stmt *yspec, cvec *nsc, + char *xpath, cxobj **xtop); transaction_data_t * transaction_new(void); int transaction_free(transaction_data_t *); diff --git a/apps/backend/backend_startup.c b/apps/backend/backend_startup.c index 8fd12e0e..760bc458 100644 --- a/apps/backend/backend_startup.c +++ b/apps/backend/backend_startup.c @@ -86,7 +86,7 @@ db_merge(clicon_handle h, cxobj *xt = NULL; /* Get data as xml from db1 */ - if (xmldb_get0(h, (char*)db1, NULL, 0, &xt, NULL) < 0) + if (xmldb_get0(h, (char*)db1, NULL, NULL, 0, &xt, NULL) < 0) goto done; /* Merge xml into db2. Without commit */ retval = xmldb_put(h, (char*)db2, OP_MERGE, xt, clicon_username_get(h), cbret); @@ -339,7 +339,7 @@ startup_module_state(clicon_handle h, goto ok; /* Set up cache * Now, access brief module cache with clicon_modst_cache_get(h, 1) */ - if ((ret = yang_modules_state_get(h, yspec, NULL, 1, &x)) < 0) + if ((ret = yang_modules_state_get(h, yspec, NULL, NULL, 1, &x)) < 0) goto done; if (ret == 0) goto fail; diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c index b871c7a3..1a0d013e 100644 --- a/apps/cli/cli_common.c +++ b/apps/cli/cli_common.c @@ -680,15 +680,15 @@ compare_dbs(clicon_handle h, astext = cv_int32_get(cvec_i(argv, 0)); else astext = 0; - if (clicon_rpc_get_config(h, "running", "/", &xc1) < 0) + if (clicon_rpc_get_config(h, "running", "/", NULL, &xc1) < 0) goto done; - if ((xerr = xpath_first(xc1, "/rpc-error")) != NULL){ + if ((xerr = xpath_first(xc1, NULL, "/rpc-error")) != NULL){ clicon_rpc_generate_error("Get configuration", xerr); goto done; } - if (clicon_rpc_get_config(h, "candidate", "/", &xc2) < 0) + if (clicon_rpc_get_config(h, "candidate", "/", NULL, &xc2) < 0) goto done; - if ((xerr = xpath_first(xc2, "/rpc-error")) != NULL){ + if ((xerr = xpath_first(xc2, NULL, "/rpc-error")) != NULL){ clicon_rpc_generate_error("Get configuration", xerr); goto done; } @@ -847,13 +847,13 @@ save_config_file(clicon_handle h, goto done; } filename = cv_string_get(cv); - if (clicon_rpc_get_config(h, dbstr,"/", &xt) < 0) + if (clicon_rpc_get_config(h, dbstr,"/", NULL, &xt) < 0) goto done; if (xt == NULL){ clicon_err(OE_CFG, 0, "get config: empty tree"); /* Shouldnt happen */ goto done; } - if ((xerr = xpath_first(xt, "/rpc-error")) != NULL){ + if ((xerr = xpath_first(xt, NULL, "/rpc-error")) != NULL){ clicon_rpc_generate_error("Get configuration", xerr); goto done; } @@ -961,7 +961,7 @@ cli_notification_cb(int s, } if (clicon_msg_decode(reply, NULL, &xt) < 0) /* XXX pass yang_spec */ goto done; - if ((xe = xpath_first(xt, "//event")) != NULL){ + if ((xe = xpath_first(xt, NULL, "//event")) != NULL){ x = NULL; while ((x = xml_child_each(xe, x, -1)) != NULL) { switch (format){ @@ -1110,14 +1110,15 @@ cli_unlock(clicon_handle h, * @param[in] cvv Vector of variables from CLIgen command-line * @param[in] argv Vector: , , , , * Explanation of argv fields: - * db: Database name, eg candidate|tmp|startup - * xpath: XPATH expression with exactly two %s pointing to field and from name - * field: Name of list key, eg name - * fromvar:Name of variable containing name of object to copy from (given by xpath) - * tovar: Name of variable containing name of object to copy to. + * db: Database name, eg candidate|tmp|startup + * xpath: XPATH expression with exactly two %s pointing to field and from name + * namespace: XPATH default namespace + * field: Name of list key, eg name + * fromvar: Name of variable containing name of object to copy from (given by xpath) + * tovar: Name of variable containing name of object to copy to. * @code * cli spec: - * copy snd to , cli_copy_config("candidate", "/sender[%s='%s']", "from", "n1", "n2"); + * copy snd to , cli_copy_config("candidate", "/sender[%s='%s']", "urn:example:clixon", "from", "n1", "n2"); * cli command: * copy snd from to to * @endcode @@ -1133,6 +1134,7 @@ cli_copy_config(clicon_handle h, cxobj *x2 = NULL; cxobj *x; char *xpath; + char *namespace; int i; int j; cbuf *cb = NULL; @@ -1144,21 +1146,24 @@ cli_copy_config(clicon_handle h, cg_var *tocv; char *toname; cxobj *xerr; + cvec *nsc = NULL; - if (cvec_len(argv) != 5){ - clicon_err(OE_PLUGIN, 0, "Requires four elements: "); + if (cvec_len(argv) != 6){ + clicon_err(OE_PLUGIN, 0, "Requires 6 elements: "); goto done; } /* First argv argument: Database */ db = cv_string_get(cvec_i(argv, 0)); /* Second argv argument: xpath */ xpath = cv_string_get(cvec_i(argv, 1)); + /* Third argv argument: namespace */ + namespace = cv_string_get(cvec_i(argv, 2)); /* Third argv argument: name of keyname */ - keyname = cv_string_get(cvec_i(argv, 2)); + keyname = cv_string_get(cvec_i(argv, 3)); /* Fourth argv argument: from variable */ - fromvar = cv_string_get(cvec_i(argv, 3)); + fromvar = cv_string_get(cvec_i(argv, 4)); /* Fifth argv argument: to variable */ - tovar = cv_string_get(cvec_i(argv, 4)); + tovar = cv_string_get(cvec_i(argv, 5)); /* Get from variable -> cv -> from name */ if ((fromcv = cvec_find(cvv, fromvar)) == NULL){ @@ -1184,9 +1189,9 @@ cli_copy_config(clicon_handle h, } cprintf(cb, xpath, keyname, fromname); /* Get from object configuration and store in x1 */ - if (clicon_rpc_get_config(h, db, cbuf_get(cb), &x1) < 0) + if (clicon_rpc_get_config(h, db, cbuf_get(cb), namespace, &x1) < 0) goto done; - if ((xerr = xpath_first(x1, "/rpc-error")) != NULL){ + if ((xerr = xpath_first(x1, NULL, "/rpc-error")) != NULL){ clicon_rpc_generate_error("Get configuration", xerr); goto done; } @@ -1204,7 +1209,9 @@ cli_copy_config(clicon_handle h, goto done; xml_name_set(x2, "config"); cprintf(cb, "/%s", keyname); - if ((x = xpath_first(x2, "%s", cbuf_get(cb))) == NULL){ + if ((nsc = xml_nsctx_init(NULL, namespace)) == NULL) + goto done; + if ((x = xpath_first(x2, nsc, "%s", cbuf_get(cb))) == NULL){ clicon_err(OE_PLUGIN, 0, "Field %s not found in copy tree", keyname); goto done; } @@ -1218,6 +1225,8 @@ cli_copy_config(clicon_handle h, goto done; retval = 0; done: + if (nsc) + xml_nsctx_free(nsc); if (cb) cbuf_free(cb); if (x1 != NULL) diff --git a/apps/cli/cli_generate.c b/apps/cli/cli_generate.c index c5d26013..5f95915e 100644 --- a/apps/cli/cli_generate.c +++ b/apps/cli/cli_generate.c @@ -178,7 +178,7 @@ yang2cli_var_identityref(yang_stmt *ys, char *helptext, cbuf *cb) { - int retval = -1; + int retval = -1; yang_stmt *ybaseref; yang_stmt *ybaseid; cg_var *cv = NULL; diff --git a/apps/cli/cli_show.c b/apps/cli/cli_show.c index a79978ea..9c724bce 100644 --- a/apps/cli/cli_show.c +++ b/apps/cli/cli_show.c @@ -119,6 +119,8 @@ expand_dbvar(void *h, cxobj *xcur; char *xpathcur; char *reason = NULL; + char *namespace = NULL; + cvec *nsc = NULL; if (argv == NULL || cvec_len(argv) != 2){ clicon_err(OE_PLUGIN, 0, "requires arguments: "); @@ -148,14 +150,14 @@ expand_dbvar(void *h, --> ^/interface/eth0/address/.*$ --> /interface/[name="eth0"]/address */ - if (api_path_fmt2xpath(api_path_fmt, cvv, &xpath) < 0) - goto done; if (api_path_fmt2api_path(api_path_fmt, cvv, &api_path) < 0) goto done; + if (api_path2xpath(api_path, yspec, &xpath, &namespace) < 0) + goto done; /* Get configuration */ - if (clicon_rpc_get_config(h, dbstr, xpath, &xt) < 0) + if (clicon_rpc_get_config(h, dbstr, xpath, namespace, &xt) < 0) /* XXX */ goto done; - if ((xerr = xpath_first(xt, "/rpc-error")) != NULL){ + if ((xerr = xpath_first(xt, NULL, "/rpc-error")) != NULL){ clicon_rpc_generate_error("Get configuration", xerr); goto ok; } @@ -172,6 +174,9 @@ expand_dbvar(void *h, goto done; if (y==NULL) goto ok; + if ((nsc = xml_nsctx_init(NULL, namespace)) == NULL) + goto done; + /* Special case for leafref. Detect leafref via Yang-type, * Get Yang path element, tentatively add the new syntax to the whole * tree and apply the path to that. @@ -193,12 +198,12 @@ expand_dbvar(void *h, fprintf(stderr, "%s\n", reason); goto done; } - if ((xcur = xpath_first(xt, "%s", xpath)) == NULL){ + if ((xcur = xpath_first(xt, nsc, "%s", xpath)) == NULL){ clicon_err(OE_DB, 0, "xpath %s should return merged content", xpath); goto done; } } - if (xpath_vec(xcur, "%s", &xvec, &xlen, xpathcur) < 0) + if (xpath_vec(xcur, nsc, "%s", &xvec, &xlen, xpathcur) < 0) goto done; bodystr0 = NULL; /* Assume sorted XML where duplicates are adjacent */ for (i = 0; i < xlen; i++) { @@ -217,6 +222,8 @@ expand_dbvar(void *h, ok: retval = 0; done: + if (nsc) + xml_nsctx_free(nsc); if (reason) free(reason); if (api_path) @@ -231,6 +238,7 @@ expand_dbvar(void *h, free(xpath); return retval; } + int expandv_dbvar(void *h, char *name, @@ -241,6 +249,7 @@ expandv_dbvar(void *h, { return expand_dbvar(h, name, cvv, argv, commands, helptexts); } + /*! List files in a directory */ int @@ -390,10 +399,10 @@ show_yang(clicon_handle h, * Format of argv: * "running"|"candidate"|"startup" # note only running state=1 * "text"|"xml"|"json"|"cli"|"netconf" (see format_enum) - * xpath expression, that may contain one %, eg "/sender[name="%s"]" - * optional name of variable in cvv. If set, xpath must have a '%s' + * xpath expression, that may contain one %, eg "/sender[name='foo']" + * If xpath set, the namespace the symbols in xpath belong to (optional) * @code - * show config id , cli_show_config("running","xml","iface[name="%s"]","n"); + * show config id , cli_show_config("running","xml","iface[name='foo']","urn:example:example"); * @endcode * @note if state parameter is set, then db must be running */ @@ -409,16 +418,13 @@ cli_show_config1(clicon_handle h, char *xpath; enum format_enum format; cbuf *cbxpath = NULL; - char *attr = NULL; - int i; - int j; - cg_var *cvattr; char *val = NULL; cxobj *xt = NULL; cxobj *xc; cxobj *xerr; enum genmodel_type gt; yang_stmt *yspec; + char *namespace = NULL; if (cvec_len(argv) != 3 && cvec_len(argv) != 4){ clicon_err(OE_PLUGIN, 0, "Got %d arguments. Expected: ,,[,]", cvec_len(argv)); @@ -441,33 +447,12 @@ cli_show_config1(clicon_handle h, clicon_err(OE_PLUGIN, errno, "cbuf_new"); goto done; } - /* Fourth argument is stdarg to xpath format string */ - if (cvec_len(argv) == 4){ - attr = cv_string_get(cvec_i(argv, 3)); - j = 0; - for (i=0; i "running"|"candidate"|"startup" * "text"|"xml"|"json"|"cli"|"netconf" (see format_enum) * xpath expression, that may contain one %, eg "/sender[name="%s"]" - * optional name of variable in cvv. If set, xpath must have a '%s' + * If xpath set, the namespace the symbols in xpath belong to (optional) * @code - * show config id , cli_show_config("running","xml","iface[name="%s"]","n"); + * show config id , cli_show_config("running","xml","iface[name='foo']","urn:example:example"); * @endcode * @see cli_show_config_state For config and state data (not only config) */ @@ -565,7 +550,7 @@ cli_show_config(clicon_handle h, * xpath expression, that may contain one %, eg "/sender[name="%s"]" * optional name of variable in cvv. If set, xpath must have a '%s' * @code - * show config id , cli_show_config_state("running","xml","iface[name="%s"]","n"); + * show state id , cli_show_config_state("running","xml","iface[name='foo']","urn:example:example"); * @endcode * @see cli_show_config For config-only, no state */ @@ -580,9 +565,9 @@ cli_show_config_state(clicon_handle h, /*! Show configuration as text given an xpath * Utility function used by cligen spec file * @param[in] h CLICON handle - * @param[in] cvv Vector of variables from CLIgen command-line - * @param[in] arg A string: - * @note Hardcoded that a variable in cvv is named "xpath" + * @param[in] cvv Vector of variables from CLIgen command-line must contain xpath and ns variables + * @param[in] argv A string: + * @note Hardcoded that variable xpath and ns cvv must exist. (kludge) */ int show_conf_xpath(clicon_handle h, @@ -598,6 +583,8 @@ show_conf_xpath(clicon_handle h, cxobj **xv = NULL; size_t xlen; int i; + char *namespace = NULL; + cvec *nsc = NULL; if (cvec_len(argv) != 1){ clicon_err(OE_PLUGIN, 0, "Requires one element to be "); @@ -611,21 +598,31 @@ show_conf_xpath(clicon_handle h, clicon_err(OE_PLUGIN, 0, "No such db name: %s", str); goto done; } + /* Look for xpath in command (kludge: cv must be called "xpath") */ cv = cvec_find(cvv, "xpath"); xpath = cv_string_get(cv); - if (clicon_rpc_get_config(h, str, xpath, &xt) < 0) + + /* Look for namespace in command (kludge: cv must be called "ns") */ + cv = cvec_find(cvv, "ns"); + namespace = cv_string_get(cv); + + if (clicon_rpc_get_config(h, str, xpath, namespace, &xt) < 0) goto done; - if ((xerr = xpath_first(xt, "/rpc-error")) != NULL){ + if ((xerr = xpath_first(xt, NULL, "/rpc-error")) != NULL){ clicon_rpc_generate_error("Get configuration", xerr); goto done; } - if (xpath_vec(xt, "%s", &xv, &xlen, xpath) < 0) + if ((nsc = xml_nsctx_init(NULL, namespace)) == NULL) + goto done; + if (xpath_vec(xt, nsc, "%s", &xv, &xlen, xpath) < 0) goto done; for (i=0; i "running"|"candidate"|"startup" * "text"|"xml"|"json"|"cli"|"netconf" (see format_enum) * @note if state parameter is set, then db must be running + * @note that first argument is generated by code. */ static int cli_show_auto1(clicon_handle h, @@ -664,12 +662,15 @@ cli_show_auto1(clicon_handle h, // char *api_path = NULL; /* xml key */ char *db; char *xpath = NULL; + cvec *nsc = NULL; char *formatstr; enum format_enum format = FORMAT_XML; cxobj *xt = NULL; cxobj *xp; cxobj *xerr; enum genmodel_type gt; + char *namespace = NULL; + char *api_path = NULL; if (cvec_len(argv) != 3){ clicon_err(OE_PLUGIN, 0, "Usage: * . (*) generated."); @@ -689,19 +690,20 @@ cli_show_auto1(clicon_handle h, clicon_err(OE_FATAL, 0, "No DB_SPEC"); goto done; } - - // if (api_path_fmt2api_path(api_path_fmt, cvv, &api_path) < 0) - // goto done; - if (api_path_fmt2xpath(api_path_fmt, cvv, &xpath) < 0) + if (api_path_fmt2api_path(api_path_fmt, cvv, &api_path) < 0) + goto done; + if (api_path2xpath(api_path, yspec, &xpath, &namespace) < 0) goto done; /* XXX Kludge to overcome a trailing / in show, that I cannot add to * yang2api_path_fmt_1 where it should belong. */ if (xpath[strlen(xpath)-1] == '/') xpath[strlen(xpath)-1] = '\0'; + if ((nsc = xml_nsctx_init(NULL, namespace)) == NULL) + goto done; if (state == 0){ /* Get configuration-only from database */ - if (clicon_rpc_get_config(h, db, xpath, &xt) < 0) + if (clicon_rpc_get_config(h, db, xpath, namespace, &xt) < 0) goto done; } else{ /* Get configuration and state from database */ @@ -709,15 +711,15 @@ cli_show_auto1(clicon_handle h, clicon_err(OE_FATAL, 0, "Show state only for running database, not %s", db); goto done; } - if (clicon_rpc_get(h, xpath, &xt) < 0) + if (clicon_rpc_get(h, xpath, namespace, &xt) < 0) goto done; } - if ((xerr = xpath_first(xt, "/rpc-error")) != NULL){ + if ((xerr = xpath_first(xt, NULL, "/rpc-error")) != NULL){ clicon_rpc_generate_error("Get configuration", xerr); goto done; } - if ((xp = xpath_first(xt, "%s", xpath)) != NULL) + if ((xp = xpath_first(xt, nsc, "%s", xpath)) != NULL) /* Print configuration according to format */ switch (format){ case FORMAT_XML: @@ -744,6 +746,10 @@ cli_show_auto1(clicon_handle h, } retval = 0; done: + if (nsc) + xml_nsctx_free(nsc); + if (api_path) + free(api_path); if (xpath) free(xpath); if (xt) diff --git a/apps/netconf/netconf_hello.c b/apps/netconf/netconf_hello.c index 9c4b07ba..56ebc3eb 100644 --- a/apps/netconf/netconf_hello.c +++ b/apps/netconf/netconf_hello.c @@ -100,7 +100,7 @@ netconf_hello_dispatch(cxobj *xn) cxobj *xp; int retval = -1; - if ((xp = xpath_first(xn, "//hello")) != NULL) + if ((xp = xpath_first(xn, NULL, "//hello")) != NULL) retval = netconf_hello(xp); return retval; } diff --git a/apps/netconf/netconf_lib.c b/apps/netconf/netconf_lib.c index 0a6b869b..279d8b5e 100644 --- a/apps/netconf/netconf_lib.c +++ b/apps/netconf/netconf_lib.c @@ -164,14 +164,14 @@ netconf_get_target(cxobj *xn, cxobj *x; char *target = NULL; - if ((x = xpath_first(xn, "%s", path)) != NULL){ - if (xpath_first(x, "candidate") != NULL) + if ((x = xpath_first(xn, NULL, "%s", path)) != NULL){ + if (xpath_first(x, NULL, "candidate") != NULL) target = "candidate"; else - if (xpath_first(x, "running") != NULL) + if (xpath_first(x, NULL, "running") != NULL) target = "running"; else - if (xpath_first(x, "startup") != NULL) + if (xpath_first(x, NULL, "startup") != NULL) target = "startup"; } return target; diff --git a/apps/netconf/netconf_main.c b/apps/netconf/netconf_main.c index a2c90486..bf913ece 100644 --- a/apps/netconf/netconf_main.c +++ b/apps/netconf/netconf_main.c @@ -121,7 +121,7 @@ netconf_input_packet(clicon_handle h, goto done; } free(str0); - if ((xrpc=xpath_first(xreq, "//rpc")) != NULL){ + if ((xrpc=xpath_first(xreq, NULL, "//rpc")) != NULL){ isrpc++; if (xml_spec_populate_rpc(h, xrpc, yspec) < 0) goto done; @@ -134,7 +134,7 @@ netconf_input_packet(clicon_handle h, } } else - if (xpath_first(xreq, "//hello") != NULL) + if (xpath_first(xreq, NULL, "//hello") != NULL) ; else{ clicon_log(LOG_WARNING, "Invalid netconf msg: neither rpc or hello: dropped"); diff --git a/apps/netconf/netconf_rpc.c b/apps/netconf/netconf_rpc.c index 6668c018..161b73da 100644 --- a/apps/netconf/netconf_rpc.c +++ b/apps/netconf/netconf_rpc.c @@ -140,7 +140,7 @@ netconf_get_config(clicon_handle h, cxobj *xconf; /* ie ... */ - if ((xfilter = xpath_first(xn, "filter")) != NULL) + if ((xfilter = xpath_first(xn, NULL, "filter")) != NULL) ftype = xml_find_value(xfilter, "type"); if (ftype == NULL || strcmp(ftype, "xpath")==0){ if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) @@ -154,8 +154,8 @@ netconf_get_config(clicon_handle h, if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) goto done; if (xfilter && - (xfilterconf = xpath_first(xfilter, "//configuration"))!= NULL && - (xconf = xpath_first(*xret, "/rpc-reply/data")) != NULL){ + (xfilterconf = xpath_first(xfilter, NULL, "//configuration"))!= NULL && + (xconf = xpath_first(*xret, NULL, "/rpc-reply/data")) != NULL){ /* xml_filter removes parts of xml tree not matching */ if ((strcmp(xml_name(xfilterconf), xml_name(xconf))!=0) || xml_filter(xfilterconf, xconf) < 0){ @@ -208,7 +208,7 @@ get_edit_opts(cxobj *xn, cxobj *x; char *optstr; - if ((x = xpath_first(xn, "test-option")) != NULL){ + if ((x = xpath_first(xn, NULL, "test-option")) != NULL){ if ((optstr = xml_body(x)) != NULL){ if (strcmp(optstr, "test-then-set") == 0) *testopt = TEST_THEN_SET; @@ -220,7 +220,7 @@ get_edit_opts(cxobj *xn, goto parerr; } } - if ((x = xpath_first(xn, "error-option")) != NULL){ + if ((x = xpath_first(xn, NULL, "error-option")) != NULL){ if ((optstr = xml_body(x)) != NULL){ if (strcmp(optstr, "stop-on-error") == 0) *erropt = STOP_ON_ERROR; @@ -352,7 +352,7 @@ netconf_get(clicon_handle h, cxobj *xconf; /* ie ... */ - if ((xfilter = xpath_first(xn, "filter")) != NULL) + if ((xfilter = xpath_first(xn, NULL, "filter")) != NULL) ftype = xml_find_value(xfilter, "type"); if (ftype == NULL || strcmp(ftype, "xpath")==0){ if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) @@ -366,8 +366,8 @@ netconf_get(clicon_handle h, if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) goto done; if (xfilter && - (xfilterconf = xpath_first(xfilter, "//configuration"))!= NULL && - (xconf = xpath_first(*xret, "/rpc-reply/data")) != NULL){ + (xfilterconf = xpath_first(xfilter, NULL, "//configuration"))!= NULL && + (xconf = xpath_first(*xret, NULL, "/rpc-reply/data")) != NULL){ /* xml_filter removes parts of xml tree not matching */ if ((strcmp(xml_name(xfilterconf), xml_name(xconf))!=0) || xml_filter(xfilterconf, xconf) < 0){ @@ -428,6 +428,7 @@ netconf_notification_cb(int s, cxobj *xt = NULL; /* top xml */ clicon_handle h = (clicon_handle)arg; yang_stmt *yspec = NULL; + cvec *nsc = NULL; clicon_debug(1, "%s", __FUNCTION__); /* get msg (this is the reason this function is called) */ @@ -444,7 +445,10 @@ netconf_notification_cb(int s, yspec = clicon_dbspec_yang(h); if (clicon_msg_decode(reply, yspec, &xt) < 0) goto done; - if ((xn = xpath_first(xt, "notification")) == NULL) + + if ((nsc = xml_nsctx_init(NULL, "urn:ietf:params:xml:ns:netconf:notification:1.0")) == NULL) + goto done; + if ((xn = xpath_first(xt, nsc, "notification")) == NULL) goto ok; /* create netconf message */ if ((cb = cbuf_new()) == NULL){ @@ -460,9 +464,11 @@ netconf_notification_cb(int s, } fflush(stdout); cbuf_free(cb); -ok: + ok: retval = 0; - done: + done: + if (nsc) + xml_nsctx_free(nsc); if (xt != NULL) xml_free(xt); if (reply) @@ -494,7 +500,7 @@ netconf_create_subscription(clicon_handle h, int s; char *ftype; - if ((xfilter = xpath_first(xn, "//filter")) != NULL){ + if ((xfilter = xpath_first(xn, NULL, "//filter")) != NULL){ if ((ftype = xml_find_value(xfilter, "type")) != NULL){ if (strcmp(ftype, "xpath") != 0){ xml_parse_va(xret, NULL, "" @@ -510,7 +516,7 @@ netconf_create_subscription(clicon_handle h, } if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, &s) < 0) goto done; - if (xpath_first(*xret, "rpc-reply/rpc-error") != NULL) + if (xpath_first(*xret, NULL, "rpc-reply/rpc-error") != NULL) goto ok; if (event_reg_fd(s, netconf_notification_cb, @@ -616,7 +622,7 @@ netconf_application_rpc(clicon_handle h, */ if (0) if ((youtput = yang_find(yrpc, Y_OUTPUT, NULL)) != NULL){ - xoutput=xpath_first(*xret, "/"); + xoutput=xpath_first(*xret, NULL, "/"); xml_spec_set(xoutput, youtput); /* needed for xml_spec_populate */ if (xml_apply(xoutput, CX_ELMNT, xml_spec_populate, yspec) < 0) goto done; diff --git a/apps/restconf/restconf_lib.c b/apps/restconf/restconf_lib.c index 8bcb7c38..bb2c8771 100644 --- a/apps/restconf/restconf_lib.c +++ b/apps/restconf/restconf_lib.c @@ -412,7 +412,7 @@ api_return_err(clicon_handle h, clicon_debug(1, "%s", __FUNCTION__); if ((cb = cbuf_new()) == NULL) goto done; - if ((xtag = xpath_first(xerr, "//error-tag")) == NULL){ + if ((xtag = xpath_first(xerr, NULL, "//error-tag")) == NULL){ notfound(r); goto ok; } diff --git a/apps/restconf/restconf_main.c b/apps/restconf/restconf_main.c index 92822e80..74dd5e02 100644 --- a/apps/restconf/restconf_main.c +++ b/apps/restconf/restconf_main.c @@ -396,7 +396,7 @@ api_restconf(clicon_handle h, else{ if (netconf_access_denied_xml(&xret, "protocol", "The requested URL was unauthorized") < 0) goto done; - if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){ + if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){ if (api_return_err(h, r, xerr, pretty, use_xml) < 0) goto done; goto ok; diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index 79571982..c6aa7c59 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -186,7 +186,7 @@ api_data_get2(clicon_handle h, { int retval = -1; cbuf *cbpath = NULL; - char *path; + char *xpath = NULL; cbuf *cbx = NULL; yang_stmt *yspec; cxobj *xret = NULL; @@ -197,6 +197,8 @@ api_data_get2(clicon_handle h, int i; cxobj *x; int ret; + char *namespace = NULL; + cvec *nsc = NULL; clicon_debug(1, "%s", __FUNCTION__); yspec = clicon_dbspec_yang(h); @@ -204,13 +206,13 @@ api_data_get2(clicon_handle h, goto done; cprintf(cbpath, "/"); /* We know "data" is element pi-1 */ - if ((ret = api_path2xpath(yspec, pcvec, pi, cbpath)) < 0) + if ((ret = api_path2xpath_cvv(pcvec, pi, yspec, cbpath, &namespace)) < 0) goto done; if (ret == 0){ if (netconf_operation_failed_xml(&xerr, "protocol", clicon_err_reason) < 0) goto done; clicon_err_reset(); - if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + if ((xe = xpath_first(xerr, NULL, "rpc-error")) == NULL){ clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } @@ -218,12 +220,16 @@ api_data_get2(clicon_handle h, goto done; goto ok; } - path = cbuf_get(cbpath); - clicon_debug(1, "%s path:%s", __FUNCTION__, path); - if (clicon_rpc_get(h, path, &xret) < 0){ + xpath = cbuf_get(cbpath); + clicon_debug(1, "%s path:%s", __FUNCTION__, xpath); + /* Create a namespace context for ymod as the default namespace to use with + * xpath expressions */ + if ((nsc = xml_nsctx_init(NULL, namespace)) == NULL) + goto done; + if (clicon_rpc_get(h, xpath, namespace, &xret) < 0){ if (netconf_operation_failed_xml(&xerr, "protocol", clicon_err_reason) < 0) goto done; - if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + if ((xe = xpath_first(xerr, NULL, "rpc-error")) == NULL){ clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } @@ -245,7 +251,7 @@ api_data_get2(clicon_handle h, } #endif /* Check if error return */ - if ((xe = xpath_first(xret, "//rpc-error")) != NULL){ + if ((xe = xpath_first(xret, NULL, "//rpc-error")) != NULL){ if (api_return_err(h, r, xe, pretty, use_xml) < 0) goto done; goto ok; @@ -259,7 +265,7 @@ api_data_get2(clicon_handle h, FCGX_FPrintF(r->out, "\r\n"); goto ok; } - if (path==NULL || strcmp(path,"/")==0){ /* Special case: data root */ + if (xpath==NULL || strcmp(xpath,"/")==0){ /* Special case: data root */ if (use_xml){ if (clicon_xml2cbuf(cbx, xret, 0, pretty) < 0) /* Dont print top object? */ goto done; @@ -270,10 +276,10 @@ api_data_get2(clicon_handle h, } } else{ - if (xpath_vec(xret, "%s", &xvec, &xlen, path) < 0){ + if (xpath_vec(xret, nsc, "%s", &xvec, &xlen, xpath) < 0){ if (netconf_operation_failed_xml(&xerr, "application", clicon_err_reason) < 0) goto done; - if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + if ((xe = xpath_first(xerr, NULL, "rpc-error")) == NULL){ clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } @@ -283,14 +289,14 @@ api_data_get2(clicon_handle h, } if (use_xml){ for (i=0; i"); if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretcom, NULL) < 0) goto done; - if ((xe = xpath_first(xretcom, "//rpc-error")) != NULL){ + if ((xe = xpath_first(xretcom, NULL, "//rpc-error")) != NULL){ cbuf_reset(cbx); cprintf(cbx, "", username?username:""); cprintf(cbx, ""); if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretdis, NULL) < 0) goto done; /* log errors from discard, but ignore */ - if ((xpath_first(xretdis, "//rpc-error")) != NULL) + 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 */ goto done; @@ -642,7 +650,7 @@ api_data_post(clicon_handle h, if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretcom, NULL) < 0) goto done; /* If copy-config failed, log and ignore (already committed) */ - if ((xe = xpath_first(xretcom, "//rpc-error")) != NULL){ + if ((xe = xpath_first(xretcom, NULL, "//rpc-error")) != NULL){ clicon_log(LOG_WARNING, "%s: copy-config running->startup failed", __FUNCTION__); } @@ -831,7 +839,7 @@ api_data_put(clicon_handle h, if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) goto done; clicon_err_reset(); - if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + if ((xe = xpath_first(xerr, NULL, "rpc-error")) == NULL){ clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } @@ -846,7 +854,7 @@ api_data_put(clicon_handle h, if (xml_parse_string(data, yspec, &xdata0) < 0){ if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) goto done; - if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + if ((xe = xpath_first(xerr, NULL, "rpc-error")) == NULL){ clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } @@ -859,7 +867,7 @@ api_data_put(clicon_handle h, if ((ret = json_parse_str(data, yspec, &xdata0, &xerr)) < 0){ if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) goto done; - if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + if ((xe = xpath_first(xerr, NULL, "rpc-error")) == NULL){ clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } @@ -868,7 +876,7 @@ api_data_put(clicon_handle h, goto ok; } if (ret == 0){ - if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + if ((xe = xpath_first(xerr, NULL, "rpc-error")) == NULL){ clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } @@ -883,7 +891,7 @@ api_data_put(clicon_handle h, if (xml_child_nr(xdata0) != 1){ if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) goto done; - if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + if ((xe = xpath_first(xerr, NULL, "rpc-error")) == NULL){ clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } @@ -903,7 +911,7 @@ api_data_put(clicon_handle h, if (ymoddata != ymodapi){ if (netconf_malformed_message_xml(&xerr, "Data is not prefixed with matching namespace") < 0) goto done; - if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + if ((xe = xpath_first(xerr, NULL, "rpc-error")) == NULL){ clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } @@ -943,7 +951,7 @@ api_data_put(clicon_handle h, if (strcmp(dname, xml_name(xbot))){ if (netconf_operation_failed_xml(&xerr, "protocol", "Not same symbol in api-path as data") < 0) goto done; - if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + if ((xe = xpath_first(xerr, NULL, "rpc-error")) == NULL){ clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } @@ -968,7 +976,7 @@ api_data_put(clicon_handle h, if (match_list_keys(ybot, xdata, xbot) < 0){ if (netconf_operation_failed_xml(&xerr, "protocol", "api-path keys do not match data keys") < 0) goto done; - if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + if ((xe = xpath_first(xerr, NULL, "rpc-error")) == NULL){ clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } @@ -992,7 +1000,7 @@ api_data_put(clicon_handle h, if (parbod == NULL || strcmp(parbod, xml_body(xdata))){ if (netconf_operation_failed_xml(&xerr, "protocol", "api-path keys do not match data keys") < 0) goto done; - if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + if ((xe = xpath_first(xerr, NULL, "rpc-error")) == NULL){ clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } @@ -1032,7 +1040,7 @@ api_data_put(clicon_handle h, if (clicon_rpc_netconf(h, cbuf_get(cbx), &xret, NULL) < 0) goto done; - if ((xe = xpath_first(xret, "//rpc-error")) != NULL){ + if ((xe = xpath_first(xret, NULL, "//rpc-error")) != NULL){ if (api_return_err(h, r, xe, pretty, use_xml) < 0) goto done; goto ok; @@ -1045,14 +1053,14 @@ api_data_put(clicon_handle h, cprintf(cbx, ""); if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretcom, NULL) < 0) goto done; - if ((xe = xpath_first(xretcom, "//rpc-error")) != NULL){ + if ((xe = xpath_first(xretcom, NULL, "//rpc-error")) != NULL){ cbuf_reset(cbx); cprintf(cbx, "", username?username:""); cprintf(cbx, ""); if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretdis, NULL) < 0) goto done; /* log errors from discard, but ignore */ - if ((xpath_first(xretdis, "//rpc-error")) != NULL) + 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) goto done; @@ -1075,7 +1083,7 @@ api_data_put(clicon_handle h, if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretcom, NULL) < 0) goto done; /* If copy-config failed, log and ignore (already committed) */ - if ((xe = xpath_first(xretcom, "//rpc-error")) != NULL){ + if ((xe = xpath_first(xretcom, NULL, "//rpc-error")) != NULL){ clicon_log(LOG_WARNING, "%s: copy-config running->startup failed", __FUNCTION__); } @@ -1183,7 +1191,7 @@ api_data_delete(clicon_handle h, if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) goto done; clicon_err_reset(); - if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + if ((xe = xpath_first(xerr, NULL, "rpc-error")) == NULL){ clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } @@ -1210,7 +1218,7 @@ api_data_delete(clicon_handle h, cprintf(cbx, ""); if (clicon_rpc_netconf(h, cbuf_get(cbx), &xret, NULL) < 0) goto done; - if ((xe = xpath_first(xret, "//rpc-error")) != NULL){ + if ((xe = xpath_first(xret, NULL, "//rpc-error")) != NULL){ if (api_return_err(h, r, xe, pretty, use_xml) < 0) goto done; goto ok; @@ -1224,14 +1232,14 @@ api_data_delete(clicon_handle h, cprintf(cbx, ""); if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretcom, NULL) < 0) goto done; - if ((xe = xpath_first(xretcom, "//rpc-error")) != NULL){ + if ((xe = xpath_first(xretcom, NULL, "//rpc-error")) != NULL){ cbuf_reset(cbx); cprintf(cbx, "", NACM_RECOVERY_USER); cprintf(cbx, ""); if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretdis, NULL) < 0) goto done; /* log errors from discard, but ignore */ - if ((xpath_first(xretdis, "//rpc-error")) != NULL) + 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) goto done; @@ -1254,7 +1262,7 @@ api_data_delete(clicon_handle h, if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretcom, NULL) < 0) goto done; /* If copy-config failed, log and ignore (already committed) */ - if ((xe = xpath_first(xretcom, "//rpc-error")) != NULL){ + if ((xe = xpath_first(xretcom, NULL, "//rpc-error")) != NULL){ clicon_log(LOG_WARNING, "%s: copy-config running->startup failed", __FUNCTION__); } @@ -1421,7 +1429,7 @@ api_operations_post_input(clicon_handle h, if (xml_parse_string(data, yspec, &xdata) < 0){ if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) goto done; - if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + if ((xe = xpath_first(xerr, NULL, "rpc-error")) == NULL){ clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } @@ -1434,7 +1442,7 @@ api_operations_post_input(clicon_handle h, if ((ret = json_parse_str(data, yspec, &xdata, &xerr)) < 0){ if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) goto done; - if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + if ((xe = xpath_first(xerr, NULL, "rpc-error")) == NULL){ clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } @@ -1443,7 +1451,7 @@ api_operations_post_input(clicon_handle h, goto fail; } if (ret == 0){ - if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + if ((xe = xpath_first(xerr, NULL, "rpc-error")) == NULL){ clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } @@ -1476,7 +1484,7 @@ api_operations_post_input(clicon_handle h, else if (netconf_malformed_message_xml(&xerr, "restconf RPC has malformed input statement (multiple or not called input)") < 0) goto done; - if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + if ((xe = xpath_first(xerr, NULL, "rpc-error")) == NULL){ clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } @@ -1550,7 +1558,7 @@ api_operations_post_output(clicon_handle h, xml_child_nr_type(xret, CX_ELMNT) != 1){ if (netconf_malformed_message_xml(&xerr, "restconf RPC does not have single input") < 0) goto done; - if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + if ((xe = xpath_first(xerr, NULL, "rpc-error")) == NULL){ clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } @@ -1587,7 +1595,7 @@ api_operations_post_output(clicon_handle h, (ret = xml_yang_validate_add(h, xoutput, &xerr)) < 0) goto done; if (ret == 0){ /* validation failed */ - if ((xe = xpath_first(xerr, "rpc-reply/rpc-error")) == NULL){ + if ((xe = xpath_first(xerr, NULL, "rpc-reply/rpc-error")) == NULL){ clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } @@ -1720,7 +1728,7 @@ api_operations_post(clicon_handle h, if (oppath == NULL || strcmp(oppath,"/")==0){ if (netconf_operation_failed_xml(&xerr, "protocol", "Operation name expected") < 0) goto done; - if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + if ((xe = xpath_first(xerr, NULL, "rpc-error")) == NULL){ clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } @@ -1739,7 +1747,7 @@ api_operations_post(clicon_handle h, if ((ys = yang_find(yspec, Y_MODULE, prefix)) == NULL){ if (netconf_operation_failed_xml(&xerr, "protocol", "yang module not found") < 0) goto done; - if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + if ((xe = xpath_first(xerr, NULL, "rpc-error")) == NULL){ clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } @@ -1750,7 +1758,7 @@ api_operations_post(clicon_handle h, if ((yrpc = yang_find(ys, Y_RPC, id)) == NULL){ if (netconf_missing_element_xml(&xerr, "application", id, "RPC not defined") < 0) goto done; - if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + if ((xe = xpath_first(xerr, NULL, "rpc-error")) == NULL){ clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } @@ -1779,7 +1787,7 @@ api_operations_post(clicon_handle h, if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) goto done; clicon_err_reset(); - if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + if ((xe = xpath_first(xerr, NULL, "rpc-error")) == NULL){ clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } @@ -1819,7 +1827,7 @@ api_operations_post(clicon_handle h, if ((ret = xml_yang_validate_rpc(h, xtop, &xret)) < 0) goto done; if (ret == 0){ - if ((xe = xpath_first(xret, "rpc-error")) == NULL){ + if ((xe = xpath_first(xret, NULL, "rpc-error")) == NULL){ clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto ok; } @@ -1851,7 +1859,7 @@ api_operations_post(clicon_handle h, if (xml_parse_string(cbuf_get(cbret), NULL, &xret) < 0) goto done; /* Local error: return it and quit */ - if ((xe = xpath_first(xret, "rpc-reply/rpc-error")) != NULL){ + if ((xe = xpath_first(xret, NULL, "rpc-reply/rpc-error")) != NULL){ if (api_return_err(h, r, xe, pretty, use_xml) < 0) goto done; goto ok; @@ -1860,7 +1868,7 @@ api_operations_post(clicon_handle h, else { /* Send to backend */ if (clicon_rpc_netconf_xml(h, xtop, &xret, NULL) < 0) goto done; - if ((xe = xpath_first(xret, "rpc-reply/rpc-error")) != NULL){ + if ((xe = xpath_first(xret, NULL, "rpc-reply/rpc-error")) != NULL){ if (api_return_err(h, r, xe, pretty, use_xml) < 0) goto done; goto ok; diff --git a/apps/restconf/restconf_stream.c b/apps/restconf/restconf_stream.c index 3d34c798..7d825b4a 100644 --- a/apps/restconf/restconf_stream.c +++ b/apps/restconf/restconf_stream.c @@ -187,11 +187,11 @@ restconf_stream_cb(int s, clicon_err(OE_PLUGIN, errno, "cbuf_new"); goto done; } - if ((xn = xpath_first(xtop, "notification")) == NULL) + if ((xn = xpath_first(xtop, NULL, "notification")) == NULL) goto ok; #ifdef notused - xt = xpath_first(xn, "eventTime"); - if ((xe = xpath_first(xn, "event")) == NULL) /* event can depend on yang? */ + xt = xpath_first(xn, NULL, "eventTime"); + if ((xe = xpath_first(xn, NULL, "event")) == NULL) /* event can depend on yang? */ goto ok; if (xt) @@ -268,7 +268,7 @@ restconf_stream(clicon_handle h, cprintf(cb, "]]>]]>"); if (clicon_rpc_netconf(h, cbuf_get(cb), &xret, &s) < 0) goto done; - if ((xe = xpath_first(xret, "rpc-reply/rpc-error")) != NULL){ + if ((xe = xpath_first(xret, NULL, "rpc-reply/rpc-error")) != NULL){ if (api_return_err(h, r, xe, pretty, use_xml) < 0) goto done; goto ok; @@ -416,7 +416,7 @@ api_stream(clicon_handle h, else{ if (netconf_access_denied_xml(&xret, "protocol", "The requested URL was unauthorized") < 0) goto done; - if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){ + if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){ if (api_return_err(h, r, xerr, pretty, use_xml) < 0) goto done; goto ok; diff --git a/example/hello/hello_cli.cli b/example/hello/hello_cli.cli index a3b469f5..6c20276c 100644 --- a/example/hello/hello_cli.cli +++ b/example/hello/hello_cli.cli @@ -22,7 +22,7 @@ discard("Discard edits (rollback 0)"), discard_changes(); compare("Compare running and candidate"), compare_dbs((int32)1); show("Show a particular state of the system"){ - xpath("Show configuration") ("XPATH expression"), show_conf_xpath("candidate"); + xpath("Show configuration") ("XPATH expression") ("Namespace"), show_conf_xpath("candidate"); version("Show version"), cli_show_version("candidate", "text", "/"); compare("Compare candidate and running databases"), compare_dbs((int32)0);{ xml("Show comparison in xml"), compare_dbs((int32)0); diff --git a/example/main/example_backend.c b/example/main/example_backend.c index 6823d29f..e4b8d8a6 100644 --- a/example/main/example_backend.c +++ b/example/main/example_backend.c @@ -118,17 +118,24 @@ main_commit(clicon_handle h, cxobj **vec = NULL; int i; size_t len; + cvec *nsc = NULL; if (_transaction_log) transaction_log(h, td, LOG_NOTICE, __FUNCTION__); + /* Create namespace context for xpath */ + if ((nsc = xml_nsctx_init(NULL, "urn:ietf:params:xml:ns:yang:ietf-interfaces")) == NULL) + goto done; + /* Get all added i/fs */ - if (xpath_vec_flag(target, "//interface", XML_FLAG_ADD, &vec, &len) < 0) + if (xpath_vec_flag(target, NULL, "//interface", XML_FLAG_ADD, &vec, &len) < 0) return -1; if (debug) for (i=0; i on entry. * @retval 0 OK @@ -280,16 +288,18 @@ example_copy_extra(clicon_handle h, /* Clicon handle */ */ int example_statedata(clicon_handle h, - char *xpath, - cxobj *xstate) + cvec *nsc, + char *xpath, + cxobj *xstate) { int retval = -1; cxobj **xvec = NULL; - size_t xlen; + size_t xlen = 0; cbuf *cb = cbuf_new(); int i; cxobj *xt = NULL; char *name; + cvec *nsc1 = NULL; if (!_state) goto ok; @@ -298,10 +308,18 @@ example_statedata(clicon_handle h, * state information. In this case adding dummy interface operation state * to configured interfaces. * Get config according to xpath */ - if (xmldb_get(h, "running", xpath, &xt) < 0) - goto done; - /* From that subset, only get the names */ - if (xpath_vec(xt, "/interfaces/interface/name", &xvec, &xlen) < 0) + if (xmldb_get0(h, "running", nsc, xpath, 1, &xt, NULL) < 0) + goto done; + + /* Here a separate namespace context nsc1 is created. The original nsc + * created by the system cannot be used trivially, since we dont know + * the prefixes, although we could by a complex mechanism find the prefix + * (if it exists) and use that when creating our xpath. + * But it is easier creating a new namespace context nsc1. + */ + if ((nsc1 = xml_nsctx_init(NULL, "urn:ietf:params:xml:ns:yang:ietf-interfaces")) == NULL) + goto done; + if (xpath_vec(xt, nsc1, "/interfaces/interface/name", &xvec, &xlen) < 0) goto done; if (xlen){ cprintf(cb, ""); @@ -322,6 +340,8 @@ example_statedata(clicon_handle h, ok: retval = 0; done: + if (nsc1) + xml_nsctx_free(nsc1); if (xt) xml_free(xt); if (cb) @@ -395,7 +415,7 @@ upgrade_2016(clicon_handle h, if ((name = xml_find_body(xi, "name")) == NULL) continue; /* shouldnt happen */ /* Get corresponding /interfaces/interface entry */ - xif = xpath_first(xt, "/interfaces/interface[name=\"%s\"]", name); + xif = xpath_first(xt, NULL, "/interfaces/interface[name=\"%s\"]", name); /* - Move /if:interfaces-state/if:interface/if:admin-status to * /if:interfaces/if:interface/ */ if ((x = xml_find(xi, "admin-status")) != NULL && xif){ @@ -498,7 +518,7 @@ upgrade_2018(clicon_handle h, /* Change type /interfaces/interface/statistics/in-octets to * decimal64 with fraction-digits 3 and divide values with 1000 */ - if ((x = xpath_first(xi, "statistics/in-octets")) != NULL){ + if ((x = xpath_first(xi, NULL, "statistics/in-octets")) != NULL){ if ((xb = xml_body_get(x)) != NULL){ uint64_t u64; cbuf *cb = cbuf_new(); diff --git a/example/main/example_backend_nacm.c b/example/main/example_backend_nacm.c index 1614f985..a3c42657 100644 --- a/example/main/example_backend_nacm.c +++ b/example/main/example_backend_nacm.c @@ -87,7 +87,7 @@ nacm_validate(clicon_handle h, if (_transaction_log){ transaction_log(h, td, LOG_NOTICE, __FUNCTION__); if (_transaction_error_toggle==0 && - xpath_first(transaction_target(td), "%s", _transaction_xpath)){ + xpath_first(transaction_target(td), NULL, "%s", _transaction_xpath)){ _transaction_error_toggle=1; /* toggle if triggered */ clicon_err(OE_XML, 0, "User error"); return -1; /* induce fail */ @@ -116,7 +116,7 @@ nacm_commit(clicon_handle h, if (_transaction_log){ transaction_log(h, td, LOG_NOTICE, __FUNCTION__); if (_transaction_error_toggle==1 && - xpath_first(target, "%s", _transaction_xpath)){ + xpath_first(target, NULL, "%s", _transaction_xpath)){ _transaction_error_toggle=0; /* toggle if triggered */ clicon_err(OE_XML, 0, "User error"); return -1; /* induce fail */ @@ -154,6 +154,7 @@ nacm_abort(clicon_handle h, /*! Called to get NACM state data * @param[in] h Clicon handle + * @param[in] nsc External XML namespace context, or NULL * @param[in] xpath String with XPATH syntax. or NULL for all * @param[in] xtop XML tree, on entry. * @retval 0 OK @@ -164,6 +165,7 @@ nacm_abort(clicon_handle h, */ int nacm_statedata(clicon_handle h, + cvec *nsc, char *xpath, cxobj *xstate) { diff --git a/example/main/example_cli.c b/example/main/example_cli.c index 56d3b82d..39ae19ea 100644 --- a/example/main/example_cli.c +++ b/example/main/example_cli.c @@ -69,6 +69,7 @@ mycallback(clicon_handle h, cvec *cvv, cvec *argv) /* Show eth0 interfaces config using XPATH */ if (clicon_rpc_get_config(h, "running", "/interfaces/interface[name='eth0']", + "urn:example:clixon", &xret) < 0) goto done; xml_print(stdout, xret); @@ -104,7 +105,7 @@ example_client_rpc(clicon_handle h, /* Send to backend */ if (clicon_rpc_netconf_xml(h, xrpc, &xret, NULL) < 0) goto done; - if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){ + if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){ clicon_rpc_generate_error("Get configuration", xerr); goto done; } diff --git a/example/main/example_cli.cli b/example/main/example_cli.cli index 5ffcd2aa..4f793319 100644 --- a/example/main/example_cli.cli +++ b/example/main/example_cli.cli @@ -24,14 +24,14 @@ debug("Debugging parts of the system"), cli_debug_cli((int32)1);{ } copy("Copy and create a new object") { interface("Copy interface"){ - (|("name of interface to copy from")) to("Copy to interface") ("Name of interface to copy to"), cli_copy_config("candidate","//interface[%s='%s']","name","name","toname"); + (|("name of interface to copy from")) to("Copy to interface") ("Name of interface to copy to"), cli_copy_config("candidate","//interface[%s='%s']","urn:ietf:params:xml:ns:yang:ietf-interfaces","name","name","toname"); } } discard("Discard edits (rollback 0)"), discard_changes(); compare("Compare running and candidate"), compare_dbs((int32)1); show("Show a particular state of the system"){ - xpath("Show configuration") ("XPATH expression"), show_conf_xpath("candidate"); + xpath("Show configuration") ("XPATH expression") ("Namespace"), show_conf_xpath("candidate"); version("Show version"), cli_show_version("candidate", "text", "/"); compare("Compare candidate and running databases"), compare_dbs((int32)0);{ xml("Show comparison in xml"), compare_dbs((int32)0); diff --git a/include/clixon_custom.h b/include/clixon_custom.h index 69ffe850..c454fbfa 100644 --- a/include/clixon_custom.h +++ b/include/clixon_custom.h @@ -41,6 +41,16 @@ */ #undef RPC_USERNAME_ASSERT +/* If rpc call does not have a namespace (eg using xmlns) then use the default + * NETCONF namespace (see rfc6241 3.1) + * Undefine it if you want to ensure strict namespace assignment on all netconf and + * XML statements. + */ +#define USE_NETCONF_NS_AS_DEFAULT + /* Make namespace check on RESTCONF PUT and POST -d data + * Should be defined according to standard + * Undefine it if you want allow no namespace (pick first name it finds in list + * of loaded modules). */ #define RESTCONF_NS_DATA_CHECK diff --git a/lib/clixon/clixon.h.in b/lib/clixon/clixon.h.in index 4751b1ad..c4170742 100644 --- a/lib/clixon/clixon.h.in +++ b/lib/clixon/clixon.h.in @@ -93,6 +93,7 @@ #include #include #include +#include /* * Global variables generated by Makefile diff --git a/lib/clixon/clixon_datastore.h b/lib/clixon/clixon_datastore.h index a600c30d..3741600d 100644 --- a/lib/clixon/clixon_datastore.h +++ b/lib/clixon/clixon_datastore.h @@ -50,7 +50,9 @@ int xmldb_connect(clicon_handle h); int xmldb_disconnect(clicon_handle h); /* in clixon_datastore_read.[ch] */ int xmldb_get(clicon_handle h, const char *db, char *xpath, cxobj **xtop); -int xmldb_get0(clicon_handle h, const char *db, char *xpath, int copy, cxobj **xtop, modstate_diff_t *msd); +int xmldb_get0(clicon_handle h, const char *db, + cvec *nc, char *xpath, + int copy, cxobj **xtop, modstate_diff_t *msd); int xmldb_get0_clear(clicon_handle h, cxobj *x); int xmldb_get0_free(clicon_handle h, cxobj **xp); int xmldb_put(clicon_handle h, const char *db, enum operation_type op, cxobj *xt, char *username, cbuf *cbret); /* in clixon_datastore_write.[ch] */ diff --git a/lib/clixon/clixon_plugin.h b/lib/clixon/clixon_plugin.h index 0ac4aae6..ce8e20e1 100644 --- a/lib/clixon/clixon_plugin.h +++ b/lib/clixon/clixon_plugin.h @@ -119,7 +119,16 @@ typedef int (plgexit_t)(clicon_handle); /* Plugin exit */ typedef int (plgauth_t)(clicon_handle, void *); typedef int (plgreset_t)(clicon_handle h, const char *db); /* Reset system status */ -typedef int (plgstatedata_t)(clicon_handle h, char *xpath, cxobj *xtop); + +/* Plugin statedata + * @param[in] Clicon handle + * @param[in] xpath Part of state requested + * @param[in] nsc XPATH namespace context. + * @param[in] xtop XML tree where statedata is added + * @retval -1 Fatal error + * @retval 0 OK + */ +typedef int (plgstatedata_t)(clicon_handle h, cvec *nsc, char *xpath, cxobj *xtop); typedef void *transaction_data; diff --git a/lib/clixon/clixon_proto_client.h b/lib/clixon/clixon_proto_client.h index 07d808b5..0ac3783b 100644 --- a/lib/clixon/clixon_proto_client.h +++ b/lib/clixon/clixon_proto_client.h @@ -45,14 +45,14 @@ int clicon_rpc_msg(clicon_handle h, struct clicon_msg *msg, cxobj **xret0, int clicon_rpc_netconf(clicon_handle h, char *xmlst, cxobj **xret, int *sp); int clicon_rpc_netconf_xml(clicon_handle h, cxobj *xml, cxobj **xret, int *sp); int clicon_rpc_generate_error(char *format, cxobj *xerr); -int clicon_rpc_get_config(clicon_handle h, char *db, char *xpath, cxobj **xret); +int clicon_rpc_get_config(clicon_handle h, char *db, char *xpath, char *namespace, cxobj **xret); int clicon_rpc_edit_config(clicon_handle h, char *db, enum operation_type op, char *xml); int clicon_rpc_copy_config(clicon_handle h, char *db1, char *db2); int clicon_rpc_delete_config(clicon_handle h, char *db); int clicon_rpc_lock(clicon_handle h, char *db); int clicon_rpc_unlock(clicon_handle h, char *db); -int clicon_rpc_get(clicon_handle h, char *xpath, cxobj **xret); +int clicon_rpc_get(clicon_handle h, char *xpath, char *namespace, cxobj **xret); int clicon_rpc_close_session(clicon_handle h); int clicon_rpc_kill_session(clicon_handle h, int session_id); int clicon_rpc_validate(clicon_handle h, char *db); diff --git a/lib/clixon/clixon_xml.h b/lib/clixon/clixon_xml.h index 879aa453..15a181ba 100644 --- a/lib/clixon/clixon_xml.h +++ b/lib/clixon/clixon_xml.h @@ -41,12 +41,11 @@ /* * Constants */ -/* If rpc call does not have a namespace (eg w xmlns) then use the default NETCONF - * namespace (rfc6241 3.1) + +/* Default NETCONF namespace (see rfc6241 3.1) + * See USE_NETCONF_NS_AS_DEFAULT for use of this namespace as default */ -#define DEFAULT_XML_RPC_NAMESPACE "urn:ietf:params:xml:ns:netconf:base:1.0" -/* default namespace statement, such as in */ -#define DEFAULT_XMLNS "xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\"" +#define NETCONF_BASE_NAMESPACE "urn:ietf:params:xml:ns:netconf:base:1.0" /* * Types diff --git a/lib/clixon/clixon_xml_map.h b/lib/clixon/clixon_xml_map.h index b6a09ccc..0b3b9842 100644 --- a/lib/clixon/clixon_xml_map.h +++ b/lib/clixon/clixon_xml_map.h @@ -70,7 +70,8 @@ int xml_sanity(cxobj *x, void *arg); int xml_non_config_data(cxobj *xt, void *arg); int xml_spec_populate_rpc(clicon_handle h, cxobj *x, yang_stmt *yspec); int xml_spec_populate(cxobj *x, void *arg); -int api_path2xpath(yang_stmt *yspec, cvec *cvv, int offset, cbuf *xpath); +int api_path2xpath_cvv(cvec *api_path, int offset, yang_stmt *yspec, cbuf *xpath, char **namespace); +int api_path2xpath(char *api_path, yang_stmt *yspec, char **xpath, char **namespace); int api_path2xml(char *api_path, yang_stmt *yspec, cxobj *xtop, yang_class nodeclass, int strict, cxobj **xpathp, yang_stmt **ypathp); diff --git a/lib/clixon/clixon_xml_nsctx.h b/lib/clixon/clixon_xml_nsctx.h new file mode 100644 index 00000000..98b7c2a3 --- /dev/null +++ b/lib/clixon/clixon_xml_nsctx.h @@ -0,0 +1,59 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2019 Olof Hagsand + + 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 ***** + + * XML support functions. + * @see https://www.w3.org/TR/2009/REC-xml-names-20091208/ + */ +#ifndef _CLIXON_XML_NSCTX_H +#define _CLIXON_XML_NSCTX_H + +/* + * An xml namespace context is a cligen variable vector containing a list of + * pairs. + * It is encoded in a cvv as a list of string values, where the c name is the + * prefix and the string values are the namespace URI. + * The default namespace is decoded as having the name NULL + */ + +/* + * Prototypes + */ +char *xml_nsctx_get(cvec *nsc, char *prefix); +int xml_nsctx_get_prefix(cvec *cvv, char *namespace, char **prefix); +int xml_nsctx_set(cvec *nsc, char *prefix, char *namespace); +cvec *xml_nsctx_init(char *prefix, char *namespace); +int xml_nsctx_node(cxobj *x, cvec **ncp); +int xml_nsctx_yang(yang_stmt *yn, cvec **ncp); +int xml_nsctx_free(cvec *ncs); + +#endif /* _CLIXON_XML_NSCTX_H */ diff --git a/lib/clixon/clixon_xpath.h b/lib/clixon/clixon_xpath.h index d0d6a360..f159579d 100644 --- a/lib/clixon/clixon_xpath.h +++ b/lib/clixon/clixon_xpath.h @@ -80,24 +80,26 @@ enum axis_type{ */ extern const map_str2int xpopmap[]; +extern int xpatherrordiff; + /* * Prototypes */ #if defined(__GNUC__) && __GNUC__ >= 3 -int xpath_vec(cxobj *xcur, char *format, cxobj ***vec, size_t *veclen, ...) __attribute__ ((format (printf, 2, 5))); -int xpath_vec_flag(cxobj *xcur, char *format, uint16_t flags, - cxobj ***vec, size_t *veclen, ...) __attribute__ ((format (printf, 2, 6))); -cxobj *xpath_first(cxobj *xcur, char *format, ...) __attribute__ ((format (printf, 2, 3))); -int xpath_vec_bool(cxobj *xcur, char *format, ...) __attribute__ ((format (printf, 2, 3))); +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 -int xpath_vec(cxobj *xcur, char *format, cxobj ***vec, size_t *veclen, ...); -int xpath_vec_flag(cxobj *xcur, char *format, uint16_t flags, - cxobj ***vec, size_t *veclen, ...); -cxobj *xpath_first(cxobj *xcur, char *format, ...); -int xpath_vec_bool(cxobj *xcur, 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, char *xpath, xp_ctx **xrp); + +int xpath_vec_ctx(cxobj *xcur, cvec *nsc, char *xpath, xp_ctx **xrp); #endif /* _CLIXON_XPATH_H */ diff --git a/lib/clixon/clixon_yang.h b/lib/clixon/clixon_yang.h index 821cf64d..1c8c3e7b 100644 --- a/lib/clixon/clixon_yang.h +++ b/lib/clixon/clixon_yang.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand This file is part of CLIXON. diff --git a/lib/clixon/clixon_yang_module.h b/lib/clixon/clixon_yang_module.h index 0381864e..da53e7bd 100644 --- a/lib/clixon/clixon_yang_module.h +++ b/lib/clixon/clixon_yang_module.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand This file is part of CLIXON. @@ -65,7 +65,7 @@ int yang_modules_init(clicon_handle h); char *yang_modules_revision(clicon_handle h); int yang_modules_state_get(clicon_handle h, yang_stmt *yspec, char *xpath, - int brief, cxobj **xret); + cvec *nsc, int brief, cxobj **xret); int clixon_module_upgrade(clicon_handle h, cxobj *xt, modstate_diff_t *msd, cbuf *cb); diff --git a/lib/clixon/clixon_yang_type.h b/lib/clixon/clixon_yang_type.h index a0f8a08f..ecf2c174 100644 --- a/lib/clixon/clixon_yang_type.h +++ b/lib/clixon/clixon_yang_type.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand This file is part of CLIXON. diff --git a/lib/src/Makefile.in b/lib/src/Makefile.in index bba70a2f..57c0ee75 100644 --- a/lib/src/Makefile.in +++ b/lib/src/Makefile.in @@ -70,7 +70,7 @@ SRC = clixon_sig.c clixon_log.c clixon_err.c clixon_event.c \ clixon_string.c clixon_regex.c clixon_handle.c \ clixon_xml.c clixon_xml_sort.c clixon_xml_map.c clixon_file.c \ clixon_json.c clixon_yang.c clixon_yang_type.c clixon_yang_module.c \ - clixon_yang_cardinality.c clixon_xml_changelog.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 \ diff --git a/lib/src/clixon_datastore_read.c b/lib/src/clixon_datastore_read.c index b4358ae7..ca5cb90a 100644 --- a/lib/src/clixon_datastore_read.c +++ b/lib/src/clixon_datastore_read.c @@ -75,6 +75,7 @@ #include "clixon_netconf_lib.h" #include "clixon_yang_module.h" #include "clixon_xml_map.h" +#include "clixon_xml_nsctx.h" #include "clixon_datastore.h" #include "clixon_datastore_read.h" @@ -257,7 +258,7 @@ text_read_modstate(clicon_handle h, if ((name = xml_find_body(xm, "name")) == NULL) continue; /* 3a) There is no such module in the system */ - if ((xs = xpath_first(xmcache, "module[name=\"%s\"]", name)) == NULL){ + if ((xs = xpath_first(xmcache, NULL, "module[name=\"%s\"]", name)) == NULL){ // fprintf(stderr, "%s: Module %s: not in system\n", __FUNCTION__, name); if ((xm2 = xml_dup(xm)) == NULL) goto done; @@ -385,6 +386,7 @@ xmldb_readfile(clicon_handle h, * This is a clixon datastore plugin of the the xmldb api * @param[in] h Clicon handle * @param[in] db Name of database to search in (filename including dir path + * @param[in] nsc External XML namespace context, or NULL * @param[in] xpath String with XPATH syntax. or NULL for all * @param[out] xret Single return XML tree. Free with xml_free() * @param[out] msd If set, return modules-state differences @@ -395,6 +397,7 @@ xmldb_readfile(clicon_handle h, static int xmldb_get_nocache(clicon_handle h, const char *db, + cvec *nsc, char *xpath, cxobj **xtop, modstate_diff_t *msd) @@ -417,7 +420,7 @@ xmldb_get_nocache(clicon_handle h, goto done; /* Here xt looks like: ... */ /* Given the xpath, return a vector of matches in xvec */ - if (xpath_vec(xt, "%s", &xvec, &xlen, xpath?xpath:"/") < 0) + if (xpath_vec(xt, nsc, "%s", &xvec, &xlen, xpath?xpath:"/") < 0) goto done; /* If vectors are specified then mark the nodes found with all ancestors @@ -467,6 +470,7 @@ xmldb_get_nocache(clicon_handle h, * This is a clixon datastore plugin of the the xmldb api * @param[in] h Clicon handle * @param[in] db Name of database to search in (filename including dir path + * @param[in] nsc External XML namespace context, or NULL * @param[in] xpath String with XPATH syntax. or NULL for all * @param[out] xret Single return XML tree. Free with xml_free() * @param[out] msd If set, return modules-state differences @@ -475,11 +479,12 @@ xmldb_get_nocache(clicon_handle h, * @see xmldb_get the generic API function */ static int -xmldb_get_cache(clicon_handle h, - const char *db, - char *xpath, - cxobj **xtop, - modstate_diff_t *msd) +xmldb_get_cache(clicon_handle h, + const char *db, + cvec *nsc, + char *xpath, + cxobj **xtop, + modstate_diff_t *msd) { int retval = -1; yang_stmt *yspec; @@ -521,7 +526,7 @@ xmldb_get_cache(clicon_handle h, */ /* Here xt looks like: ... */ - if (xpath_vec(x0t, "%s", &xvec, &xlen, xpath?xpath:"/") < 0) + if (xpath_vec(x0t, nsc, "%s", &xvec, &xlen, xpath?xpath:"/") < 0) goto done; /* Make new tree by copying top-of-tree from x0t to x1t */ @@ -565,6 +570,7 @@ xmldb_get_cache(clicon_handle h, * This is a clixon datastore plugin of the the xmldb api * @param[in] h Clicon handle * @param[in] db Name of database to search in (filename including dir path + * @param[in] nsc External XML namespace context, or NULL * @param[in] xpath String with XPATH syntax. or NULL for all * @param[in] config If set only configuration data, else also state * @param[out] xret Single return XML tree. Free with xml_free() @@ -573,11 +579,12 @@ xmldb_get_cache(clicon_handle h, * @retval -1 Error */ static int -xmldb_get_zerocopy(clicon_handle h, - const char *db, - char *xpath, - cxobj **xtop, - modstate_diff_t *msd) +xmldb_get_zerocopy(clicon_handle h, + const char *db, + cvec *nsc, + char *xpath, + cxobj **xtop, + modstate_diff_t *msd) { int retval = -1; yang_stmt *yspec; @@ -612,7 +619,7 @@ xmldb_get_zerocopy(clicon_handle h, else x0t = de->de_xml; /* Here xt looks like: ... */ - if (xpath_vec(x0t, "%s", &xvec, &xlen, xpath?xpath:"/") < 0) + if (xpath_vec(x0t, nsc, "%s", &xvec, &xlen, xpath?xpath:"/") < 0) goto done; /* Iterate through the match vector * For every node found in x0, mark the tree up to t1 @@ -656,7 +663,7 @@ xmldb_get(clicon_handle h, char *xpath, cxobj **xret) { - return xmldb_get0(h, db, xpath, 1, xret, NULL); + return xmldb_get0(h, db, NULL, xpath, 1, xret, NULL); } /*! Zero-copy variant of get content of database @@ -669,6 +676,7 @@ xmldb_get(clicon_handle h, * freeing tree must be made after use. * @param[in] h Clicon handle * @param[in] db Name of database to search in (filename including dir path + * @param[in] nsc External XML namespace context, or NULL * @param[in] xpath String with XPATH syntax. or NULL for all * @param[in] copy Force copy. Overrides cache_zerocopy -> cache * @param[out] xret Single return XML tree. Free with xml_free() @@ -677,17 +685,19 @@ xmldb_get(clicon_handle h, * @retval -1 Error * @code * cxobj *xt; - * if (xmldb_get0(xh, "running", "/interface[name="eth"]", 0, &xt, NULL) < 0) + * if (xmldb_get0(xh, "running", nsc, "/interface[name="eth"]", 0, &xt, NULL) < 0) * err; * ... * xmldb_get0_clear(h, xt); # Clear tree from default values and flags * xmldb_get0_free(h, &xt); # Free tree * @endcode + * @see xml_nsctx_node to get a XML namespace context from XML tree * @see xmldb_get for a copy version (old-style) */ int xmldb_get0(clicon_handle h, const char *db, + cvec *nsc, char *xpath, int copy, cxobj **xret, @@ -701,7 +711,7 @@ xmldb_get0(clicon_handle h, * Add default values in copy * Copy deleted by xmldb_free */ - retval = xmldb_get_nocache(h, db, xpath, xret, msd); + retval = xmldb_get_nocache(h, db, nsc, xpath, xret, msd); break; case DATASTORE_CACHE_ZEROCOPY: /* Get cache (file if empty) mark xpath match in original tree @@ -709,7 +719,7 @@ xmldb_get0(clicon_handle h, * Default values and markings removed in xmldb_clear */ if (!copy){ - retval = xmldb_get_zerocopy(h, db, xpath, xret, msd); + retval = xmldb_get_zerocopy(h, db, nsc, xpath, xret, msd); break; } /* fall through */ @@ -718,7 +728,7 @@ xmldb_get0(clicon_handle h, * Add default values in copy, return copy * Copy deleted by xmldb_free */ - retval = xmldb_get_cache(h, db, xpath, xret, msd); + retval = xmldb_get_cache(h, db, nsc, xpath, xret, msd); break; } return retval; diff --git a/lib/src/clixon_datastore_write.c b/lib/src/clixon_datastore_write.c index fff7558d..1e4247ee 100644 --- a/lib/src/clixon_datastore_write.c +++ b/lib/src/clixon_datastore_write.c @@ -75,6 +75,7 @@ #include "clixon_nacm.h" #include "clixon_netconf_lib.h" #include "clixon_yang_module.h" +#include "clixon_xml_nsctx.h" #include "clixon_xml_map.h" #include "clixon_datastore.h" @@ -614,6 +615,7 @@ xmldb_put(clicon_handle h, cxobj *x; int permit = 0; /* nacm permit all */ char *format; + cvec *nsc = NULL; /* nacm namespace context */ if (cbret == NULL){ clicon_err(OE_XML, EINVAL, "cbret is NULL"); @@ -656,8 +658,11 @@ xmldb_put(clicon_handle h, else if (strcmp(mode, "internal")==0) xnacm0 = x0; } + /* Create namespace context for with nacm namespace as default */ + if ((nsc = xml_nsctx_init(NULL, "urn:ietf:params:xml:ns:yang:ietf-netconf-acm")) == NULL) + goto done; if (xnacm0 != NULL && - (xnacm = xpath_first(xnacm0, "nacm")) != NULL){ + (xnacm = xpath_first(xnacm0, nsc, "nacm")) != NULL){ /* Pre-NACM access step, if permit, then dont do any nacm checks in * text_modify_* below */ if ((permit = nacm_access(mode, xnacm, username)) < 0) @@ -744,6 +749,8 @@ xmldb_put(clicon_handle h, done: if (f != NULL) fclose(f); + if (nsc) + xml_nsctx_free(nsc); if (dbfile) free(dbfile); if (cb) diff --git a/lib/src/clixon_nacm.c b/lib/src/clixon_nacm.c index 3bb072bd..27b9eea2 100644 --- a/lib/src/clixon_nacm.c +++ b/lib/src/clixon_nacm.c @@ -68,8 +68,12 @@ #include "clixon_xpath.h" #include "clixon_yang_module.h" #include "clixon_datastore.h" +#include "clixon_xml_nsctx.h" #include "clixon_nacm.h" +/* NACM namespace for use with xml namespace contexts and xpath */ +#define NACM_NS "urn:ietf:params:xml:ns:yang:ietf-netconf-acm" + /*! Match nacm access operations according to RFC8341 3.4.4. * Incoming RPC Message Validation Step 7 (c) * The rule's "access-operations" leaf has the "exec" bit set or @@ -191,7 +195,11 @@ nacm_rpc(char *rpc, char *gname; char *action; int match= 0; + cvec *nsc = NULL; + /* Create namespace context for with nacm namespace as default */ + if ((nsc = xml_nsctx_init(NULL, NACM_NS)) == NULL) + goto done; /* 3. If the requested operation is the NETCONF protocol operation, then the protocol operation is permitted. */ @@ -204,8 +212,9 @@ nacm_rpc(char *rpc, transport layer.) */ if (username == NULL) goto step10; + /* User's group */ - if (xpath_vec(xnacm, "groups/group[user-name='%s']", &gvec, &glen, username) < 0) + if (xpath_vec(xnacm, nsc, "groups/group[user-name='%s']", &gvec, &glen, username) < 0) goto done; /* 5. If no groups are found, continue with step 10. */ if (glen == 0) @@ -214,14 +223,14 @@ nacm_rpc(char *rpc, configuration. If a rule-list's "group" leaf-list does not match any of the user's groups, proceed to the next rule-list entry. */ - if (xpath_vec(xnacm, "rule-list", &rlistvec, &rlistlen) < 0) + if (xpath_vec(xnacm, nsc, "rule-list", &rlistvec, &rlistlen) < 0) goto done; for (i=0; i constants, see clixon-config.yang type startup_mode */ static const map_str2int startup_mode_map[] = { @@ -165,6 +172,7 @@ parse_configfile(clicon_handle h, cbuf *cbret = NULL; cxobj *xret = NULL; int ret; + cvec *nsc = NULL; if (filename == NULL || !strlen(filename)){ clicon_err(OE_UNIX, 0, "Not specified"); @@ -191,25 +199,12 @@ parse_configfile(clicon_handle h, goto done; } /* Hard-coded config for < 3.10 and clixon-config for >= 3.10 */ - if ((xc = xpath_first(xt, "clixon-config")) == NULL){ - /* Backward compatible code to accept "config" as top-level symbol. - This cannot be controlled by config option due to bootstrap */ -#if 0 - if ((xc = xpath_first(xt, "config")) != NULL){ - if (xml_name_set(xc, "clixon-config") < 0) - goto done; - if (xml_apply0(xc, CX_ELMNT, xml_spec_populate, yspec) < 0) - goto done; - if (xml_apply0(xc, CX_ELMNT, xml_sort, h) < 0) - goto done; - } - else -#endif - { - clicon_err(OE_CFG, 0, "Config file %s: Lacks top-level \"clixon-config\" element\nClixon config files should begin with: ", filename); + if ((nsc = xml_nsctx_init(NULL, CLIXON_CONF_NS)) == NULL) + goto done; + if ((xc = xpath_first(xt, nsc, "clixon-config")) == NULL){ + clicon_err(OE_CFG, 0, "Config file %s: Lacks top-level \"clixon-config\" element\nClixon config files should begin with: ", filename, CLIXON_CONF_NS); - goto done; - } + goto done; } if (xml_apply0(xc, CX_ELMNT, xml_default, h) < 0) goto done; @@ -249,6 +244,8 @@ parse_configfile(clicon_handle h, *xconfig = xt; xt = NULL; done: + if (nsc) + xml_nsctx_free(nsc); if (cbret) cbuf_free(cbret); if (xret) @@ -364,7 +361,7 @@ clicon_options_main(clicon_handle h, if (xml_rootchild(xconfig, 0, &xconfig) < 0) goto done; if (xml_spec(xconfig) == NULL){ - clicon_err(OE_CFG, 0, "Config file %s: did not find corresponding Yang specification\nHint: File does not begin with: or clixon-config.yang not found?", configfile); + clicon_err(OE_CFG, 0, "Config file %s: did not find corresponding Yang specification\nHint: File does not begin with: or clixon-config.yang not found?", configfile, CLIXON_CONF_NS); goto done; } diff --git a/lib/src/clixon_proto_client.c b/lib/src/clixon_proto_client.c index 03a1bf7d..f0de1800 100644 --- a/lib/src/clixon_proto_client.c +++ b/lib/src/clixon_proto_client.c @@ -253,15 +253,16 @@ clicon_rpc_generate_error(char *prefix, * @param[in] h CLICON handle * @param[in] db Name of database * @param[in] xpath XPath (or "") + * @param[in] namespace Namespace associated w xpath * @param[out] xt XML tree. Free with xml_free. * Either or . * @retval 0 OK * @retval -1 Error, fatal or xml * @code * cxobj *xt = NULL; - * if (clicon_rpc_get_config(h, "running", "/", &xt) < 0) + * if (clicon_rpc_get_config(h, "running", "/hello/world", "urn:example:hello", &xt) < 0) * err; - * if ((xerr = xpath_first(xt, "/rpc-error")) != NULL){ + * if ((xerr = xpath_first(xt, NULL, "/rpc-error")) != NULL){ * clicon_rpc_generate_error("", xerr); * err; * } @@ -274,6 +275,7 @@ int clicon_rpc_get_config(clicon_handle h, char *db, char *xpath, + char *namespace, cxobj **xt) { int retval = -1; @@ -288,18 +290,25 @@ clicon_rpc_get_config(clicon_handle h, cprintf(cb, "<%s/>", db); - if (xpath && strlen(xpath)) - cprintf(cb, "", xpath); + if (xpath && strlen(xpath)){ + if (namespace) + cprintf(cb, "", + xpath, namespace); + else /* XXX shouldnt happen */ + cprintf(cb, "", xpath); + } cprintf(cb, ""); if ((msg = clicon_msg_encode("%s", cbuf_get(cb))) == NULL) goto done; if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) goto done; /* Send xml error back: first check error, then ok */ - if ((xd = xpath_first(xret, "/rpc-reply/rpc-error")) != NULL) + if ((xd = xpath_first(xret, NULL, "/rpc-reply/rpc-error")) != NULL) xd = xml_parent(xd); /* point to rpc-reply */ - else if ((xd = xpath_first(xret, "/rpc-reply/data")) == NULL) + else if ((xd = xpath_first(xret, NULL, "/rpc-reply/data")) == NULL) if ((xd = xml_new("data", NULL, NULL)) == NULL) goto done; if (xt){ @@ -347,7 +356,7 @@ clicon_rpc_edit_config(clicon_handle h, if ((cb = cbuf_new()) == NULL) goto done; - cprintf(cb, "<%s/>", db); @@ -360,7 +369,7 @@ clicon_rpc_edit_config(clicon_handle h, goto done; if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) goto done; - if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){ + if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){ clicon_rpc_generate_error("Editing configuration", xerr); goto done; } @@ -406,7 +415,7 @@ clicon_rpc_copy_config(clicon_handle h, goto done; if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) goto done; - if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){ + if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){ clicon_rpc_generate_error("Copying configuration", xerr); goto done; } @@ -445,7 +454,7 @@ clicon_rpc_delete_config(clicon_handle h, goto done; if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) goto done; - if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){ + if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){ clicon_rpc_generate_error("Deleting configuration", xerr); goto done; } @@ -480,7 +489,7 @@ clicon_rpc_lock(clicon_handle h, goto done; if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) goto done; - if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){ + if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){ clicon_rpc_generate_error("Locking configuration", xerr); goto done; } @@ -514,7 +523,7 @@ clicon_rpc_unlock(clicon_handle h, goto done; if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) goto done; - if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){ + if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){ clicon_rpc_generate_error("Configuration unlock", xerr); goto done; } @@ -528,17 +537,20 @@ clicon_rpc_unlock(clicon_handle h, } /*! Get database configuration and state data - * @param[in] h CLICON handle - * @param[in] xpath XPath (or "") - * @param[out] xt XML tree. Free with xml_free. - * Either or . - * @retval 0 OK - * @retval -1 Error, fatal or xml + * @param[in] h Clicon handle + * @param[in] xpath XPath in a filter stmt (or NULL/"" for no filter) + * @param[in] namespace Namespace associated w xpath + * @param[out] xt XML tree. Free with xml_free. + * Either or . + * @retval 0 OK + * @retval -1 Error, fatal or xml + * @note if xpath is set but namespace is NULL, the default, netconf base + * namespace will be used which is most probably wrong. * @code * cxobj *xt = NULL; - * if (clicon_rpc_get(h, "/", &xt) < 0) + * if (clicon_rpc_get(h, "/hello/world", "urn:example:hello", &xt) < 0) * err; - * if ((xerr = xpath_first(xt, "/rpc-error")) != NULL){ + * if ((xerr = xpath_first(xt, NULL, "/rpc-error")) != NULL){ * clicon_rpc_generate_error(xerr); * err; * } @@ -550,6 +562,7 @@ clicon_rpc_unlock(clicon_handle h, int clicon_rpc_get(clicon_handle h, char *xpath, + char *namespace, cxobj **xt) { int retval = -1; @@ -564,18 +577,25 @@ clicon_rpc_get(clicon_handle h, cprintf(cb, ""); - if (xpath && strlen(xpath)) - cprintf(cb, "", xpath); + if (xpath && strlen(xpath)) { + if (namespace) + cprintf(cb, "", + xpath, namespace); + else /* XXX shouldnt happen */ + cprintf(cb, "", xpath); + } cprintf(cb, ""); if ((msg = clicon_msg_encode("%s", cbuf_get(cb))) == NULL) goto done; if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) goto done; /* Send xml error back: first check error, then ok */ - if ((xd = xpath_first(xret, "/rpc-reply/rpc-error")) != NULL) + if ((xd = xpath_first(xret, NULL, "/rpc-reply/rpc-error")) != NULL) xd = xml_parent(xd); /* point to rpc-reply */ - else if ((xd = xpath_first(xret, "/rpc-reply/data")) == NULL) + else if ((xd = xpath_first(xret, NULL, "/rpc-reply/data")) == NULL) if ((xd = xml_new("data", NULL, NULL)) == NULL) goto done; if (xt){ @@ -614,7 +634,7 @@ clicon_rpc_close_session(clicon_handle h) goto done; if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) goto done; - if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){ + if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){ clicon_rpc_generate_error("Close session", xerr); goto done; } @@ -649,7 +669,7 @@ clicon_rpc_kill_session(clicon_handle h, goto done; if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) goto done; - if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){ + if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){ clicon_rpc_generate_error("Kill session", xerr); goto done; } @@ -683,7 +703,7 @@ clicon_rpc_validate(clicon_handle h, goto done; if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) goto done; - if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){ + if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){ clicon_rpc_generate_error(CLIXON_ERRSTR_VALIDATE_FAILED, xerr); goto done; } @@ -715,7 +735,7 @@ clicon_rpc_commit(clicon_handle h) goto done; if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) goto done; - if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){ + if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){ clicon_rpc_generate_error(CLIXON_ERRSTR_COMMIT_FAILED, xerr); goto done; } @@ -747,7 +767,7 @@ clicon_rpc_discard_changes(clicon_handle h) goto done; if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) goto done; - if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){ + if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){ clicon_rpc_generate_error("Discard changes", xerr); goto done; } @@ -792,7 +812,7 @@ clicon_rpc_create_subscription(clicon_handle h, goto done; if (clicon_rpc_msg(h, msg, &xret, s0) < 0) goto done; - if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){ + if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){ clicon_rpc_generate_error("Create subscription", xerr); goto done; } @@ -827,11 +847,11 @@ clicon_rpc_debug(clicon_handle h, goto done; if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) goto done; - if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){ + if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){ clicon_rpc_generate_error("Debug",xerr); goto done; } - if (xpath_first(xret, "//rpc-reply/ok") == NULL){ + if (xpath_first(xret, NULL, "//rpc-reply/ok") == NULL){ clicon_err(OE_XML, 0, "rpc error"); /* XXX extract info from rpc-error */ goto done; } diff --git a/lib/src/clixon_stream.c b/lib/src/clixon_stream.c index 7cc3b119..6fb81be6 100644 --- a/lib/src/clixon_stream.c +++ b/lib/src/clixon_stream.c @@ -514,7 +514,7 @@ stream_notify1(clicon_handle h, else{ /* xpath match */ if (ss->ss_xpath == NULL || strlen(ss->ss_xpath)==0 || - xpath_first(xevent, "%s", ss->ss_xpath) != NULL) + xpath_first(xevent, NULL, "%s", ss->ss_xpath) != NULL) if ((*ss->ss_fn)(h, 0, xevent, ss->ss_arg) < 0) goto done; ss = NEXTQ(struct stream_subscription *, ss); diff --git a/lib/src/clixon_xml.c b/lib/src/clixon_xml.c index 1783dd05..992a960e 100644 --- a/lib/src/clixon_xml.c +++ b/lib/src/clixon_xml.c @@ -66,6 +66,7 @@ #include "clixon_xml_map.h" /* xml_spec_populate */ #include "clixon_xml_sort.h" #include "clixon_xml_parse.h" +#include "clixon_xml_nsctx.h" /* * Constants @@ -130,7 +131,7 @@ struct xml{ reference, dont free */ cg_var *x_cv; /* Cached value as cligen variable (eg xml_cmp) */ - char *x_ns_cache; /* Cached namespace */ + cvec *x_ns_cache; /* Cached vector of namespaces */ int _x_vector_i; /* internal use: xml_child_each */ int _x_i; /* internal use for sorting: see xml_enumerate and xml_cmp */ @@ -229,6 +230,47 @@ xml_prefix_set(cxobj *xn, return 0; } +/*! Get cached namespace + * @param[in] x XML node + * @param[in] prefix Namespace prefix, or NULL for default + * @retval ns Cached namespace + * @retval NULL No namespace found (not cached or not found) + * @note may want to distinguish between not set cache and no namespace? + */ +static char* +nscache_get(cxobj *x, + char *prefix) +{ + if (x->x_ns_cache != NULL) + return xml_nsctx_get(x->x_ns_cache, prefix); + return NULL; +} + +/*! Set cached namespace. Replace if necessary + * @param[in] x XML node + * @param[in] prefix Namespace prefix, or NULL for default + * @param[in] namespace Cached namespace to set (assume non-null?) + * @retval 0 OK + * @retval -1 Error + */ +static int +nscache_set(cxobj *x, + char *prefix, + char *namespace) +{ + int retval = -1; + + if (x->x_ns_cache == NULL){ + if ((x->x_ns_cache = xml_nsctx_init(prefix, namespace)) == NULL) + goto done; + } + else + return xml_nsctx_set(x->x_ns_cache, prefix, namespace); + retval = 0; + done: + return retval; +} + /*! Given an xml tree return URI namespace recursively : default or localname given * * Given an XML tree and a prefix (or NULL) return URI namespace. @@ -238,7 +280,7 @@ xml_prefix_set(cxobj *xn, * @retval 0 OK * @retval -1 Error * @see xmlns_check XXX can these be merged? - * @see xml2ns_set cache is set + * @see xmlns_set cache is set * @note, this function uses a cache. */ int @@ -250,13 +292,13 @@ xml2ns(cxobj *x, char *ns = NULL; cxobj *xp; - if ((ns = x->x_ns_cache) != NULL) + if ((ns = nscache_get(x, prefix)) != NULL) goto ok; if (prefix != NULL) /* xmlns:="" */ ns = xml_find_type_value(x, "xmlns", prefix, CX_ATTR); - else /* xmlns="" */ + else{ /* xmlns="" */ ns = xml_find_type_value(x, NULL, "xmlns", CX_ATTR); - + } /* namespace not found, try parent */ if (ns == NULL){ if ((xp = xml_parent(x)) != NULL){ @@ -264,15 +306,15 @@ xml2ns(cxobj *x, goto done; } /* If no parent, return default namespace if defined */ -#if defined(DEFAULT_XML_RPC_NAMESPACE) +#ifdef USE_NETCONF_NS_AS_DEFAULT else - ns = DEFAULT_XML_RPC_NAMESPACE; + ns = NETCONF_BASE_NAMESPACE; #endif } - if (ns && (x->x_ns_cache = strdup(ns)) == NULL){ - clicon_err(OE_XML, errno, "strdup"); + /* Set default namespace cache (since code is at this point, + * no cache was found */ + if (ns && nscache_set(x, prefix, ns) < 0) goto done; - } ok: if (namespace) *namespace = ns; @@ -311,12 +353,8 @@ xmlns_set(cxobj *x, if (xml_value_set(xa, ns) < 0) goto done; /* (re)set namespace cache (as used in xml2ns) */ - if (x->x_ns_cache) - free(x->x_ns_cache); - if ((x->x_ns_cache = strdup(ns)) == NULL){ - clicon_err(OE_XML, errno, "strdup"); + if (ns && nscache_set(x, prefix, ns) < 0) goto done; - } retval = 0; done: return retval; @@ -1406,7 +1444,7 @@ xml_free(cxobj *x) if (x->x_cv) cv_free(x->x_cv); if (x->x_ns_cache) - free(x->x_ns_cache); + xml_nsctx_free(x->x_ns_cache); free(x); return 0; } diff --git a/lib/src/clixon_xml_changelog.c b/lib/src/clixon_xml_changelog.c index 850a383c..9216f7cb 100644 --- a/lib/src/clixon_xml_changelog.c +++ b/lib/src/clixon_xml_changelog.c @@ -68,6 +68,7 @@ #include "clixon_data.h" #include "clixon_yang_module.h" #include "clixon_netconf_lib.h" +#include "clixon_xml_nsctx.h" #include "clixon_xml_map.h" #include "clixon_xml_changelog.h" #include "clixon_xpath_ctx.h" @@ -77,6 +78,7 @@ static int changelog_rename(clicon_handle h, cxobj *xt, cxobj *xw, + cvec *nsc, char *tag) { int retval = -1; @@ -87,7 +89,7 @@ changelog_rename(clicon_handle h, clicon_err(OE_XML, 0, "tag required"); goto done; } - if (xpath_vec_ctx(xw, tag, &xctx) < 0) + if (xpath_vec_ctx(xw, nsc, tag, &xctx) < 0) goto done; if (ctx2string(xctx, &str) < 0) goto done; @@ -192,12 +194,13 @@ static int changelog_move(clicon_handle h, cxobj *xt, cxobj *xw, + cvec *nsc, char *dst) { int retval = -1; cxobj *xp; /* destination parent node */ - if ((xp = xpath_first(xt, "%s", dst)) == NULL){ + if ((xp = xpath_first(xt, nsc, "%s", dst)) == NULL){ clicon_err(OE_XML, 0, "path required"); goto done; } @@ -235,7 +238,11 @@ changelog_op(clicon_handle h, int ret; xp_ctx *xctx = NULL; int i; + cvec *nsc = NULL; + /* Get namespace context from changelog item */ + if (xml_nsctx_node(xi, &nsc) < 0) + goto done; if ((op = xml_find_body(xi, "op")) == NULL) goto ok; /* get common variables that may be used in the operations below */ @@ -246,13 +253,13 @@ changelog_op(clicon_handle h, if ((wxpath = xml_find_body(xi, "where")) == NULL) goto ok; /* Get vector of target nodes meeting the where requirement */ - if (xpath_vec(xt, "%s", &wvec, &wlen, wxpath) < 0) + if (xpath_vec(xt, nsc, "%s", &wvec, &wlen, wxpath) < 0) goto done; for (i=0; iys_keyword != Y_MUST) continue; xpath = yc->ys_argument; /* "must" has xpath argument */ - if ((nr = xpath_vec_bool(xt, "%s", xpath)) < 0) + if ((nr = xpath_vec_bool(xt, NULL, "%s", xpath)) < 0) goto done; if (!nr){ ye = yang_find(yc, Y_ERROR_MESSAGE, NULL); @@ -1244,7 +1259,7 @@ xml_yang_validate_all(clicon_handle h, /* "when" sub-node RFC 7950 Sec 7.21.5. Can only be one. */ if ((yc = yang_find(ys, Y_WHEN, NULL)) != NULL){ xpath = yc->ys_argument; /* "when" has xpath argument */ - if ((nr = xpath_vec_bool(xt, "%s", xpath)) < 0) + if ((nr = xpath_vec_bool(xt, NULL, "%s", xpath)) < 0) goto done; if (!nr){ if (netconf_operation_failed_xml(xret, "application", @@ -1768,7 +1783,6 @@ yang2api_path_fmt(yang_stmt *ys, * @param[in] cvv cligen variable vector, one for every wildchar in * api_path_fmt * @param[out] api_path api_path, eg /aaa/17. Free after use - * @param[out] yang_arg yang-stmt argument name. Free after use * @note first and last elements of cvv are not used,.. * @see api_path_fmt2xpath * @example @@ -1866,7 +1880,9 @@ api_path_fmt2api_path(char *api_path_fmt, * api_path_fmt: /subif-entry=%s,%s/subid * cvv: foo * xpath: /subif-entry[if-name=foo]/subid" - * + * @example + * api_path_fmt: /a:b/c + * xpath : /b/c prefix:a * "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3 */ int @@ -2259,13 +2275,14 @@ xml_spec_populate(cxobj *x, /*! Translate from restconf api-path in cvv form to xml xpath * eg a/b=c -> a/[b=c] * eg example:a/b -> ex:a/b - * @param[in] yspec Yang spec - * @param[in] cvv api-path as cvec - * @param[in] offset Offset of cvec, where api-path starts - * @param[out] xpath The xpath as cbuf variable string, must be initializeed - * @retval 1 OK - * @retval 0 Invalid api_path or associated XML, clicon_err called - * @retval -1 Fatal error, clicon_err called + * @param[in] api_path api-path as cvec + * @param[in] offset Offset of cvec, where api-path starts + * @param[in] yspec Yang spec + * @param[in,out] xpath The xpath as cbuf (must be created and may have content) + * @param[out] namespace Namespace of xpath (direct pointer don't free) + * @retval 1 OK + * @retval 0 Invalid api_path or associated XML, clicon_err called + * @retval -1 Fatal error, clicon_err called * * @note both retval 0 and -1 set clicon_err, but the later is fatal * @note Not proper namespace translation from api-path 2 xpath @@ -2279,7 +2296,7 @@ xml_spec_populate(cxobj *x, * cvec *cvv = NULL; * if (str2cvec("www.foo.com/restconf/a/b=c", '/', '=', &cvv) < 0) * err; - * if ((ret = api_path2xpath(yspec, cvv, 0, cxpath)) < 0) + * if ((ret = api_path2xpath(yspec, cvv, 0, cxpath, NULL)) < 0) * err; * if (ret == 0){ * ... access error string in clicon_err_reason @@ -2293,10 +2310,11 @@ xml_spec_populate(cxobj *x, * @see api_path2xml For api-path to xml tree */ int -api_path2xpath(yang_stmt *yspec, - cvec *cvv, - int offset, - cbuf *xpath) +api_path2xpath_cvv(cvec *api_path, + int offset, + yang_stmt *yspec, + cbuf *xpath, + char **namespace) { int retval = -1; int i; @@ -2306,13 +2324,13 @@ api_path2xpath(yang_stmt *yspec, char *name = NULL; cvec *cvk = NULL; /* vector of index keys */ yang_stmt *y = NULL; - yang_stmt *ymod; + yang_stmt *ymod = NULL; char *val; char *v; cg_var *cvi; - for (i=offset; i + * + * 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? + * + * 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}, @@ -141,8 +195,7 @@ xpath_tree_print(cbuf *cb, } static int -xpath_tree_free( - xpath_tree *xs) +xpath_tree_free(xpath_tree *xs) { if (xs->xs_s0) free(xs->xs_s0); @@ -156,45 +209,118 @@ xpath_tree_free( 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 - * @retval 0 Match - * @retval 1 No match + * @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) + xpath_tree *xs, + cvec *nsc) { + int retval = 0; /* NB: no match is default (not error) */ char *fn; - if (xs->xs_type == XP_NODE){ - /* Namespaces is s0, name is s1 */ - if (strcmp(xs->xs_s1, "*")==0) - return 1; - else if (strcmp(xml_name(x), xs->xs_s1)==0) - return 1; - else - return 0; - } + 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) - return 1; + retval = 1; else if (strcmp(fn, "text")==0) - return 1; + retval = 1; } - return 0; + /* 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, +nodetest_recursive(cxobj *xn, xpath_tree *nodetest, - int node_type, - uint16_t flags, - cxobj ***vec0, - size_t *vec0len) + int node_type, + uint16_t flags, + cvec *nsc, + cxobj ***vec0, + size_t *vec0len) { int retval = -1; cxobj *xsub; @@ -203,14 +329,14 @@ nodetest_recursive(cxobj *xn, xsub = NULL; while ((xsub = xml_child_each(xn, xsub, node_type)) != NULL) { - if (nodetest_eval(xsub, nodetest) == 1){ + 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, &vec, &veclen) < 0) + if (nodetest_recursive(xsub, nodetest, node_type, flags, nsc, &vec, &veclen) < 0) goto done; } retval = 0; @@ -220,12 +346,13 @@ nodetest_recursive(cxobj *xn, return retval; } -static int xp_eval(xp_ctx *xc, xpath_tree *xs, xp_ctx **xrp); +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]) @@ -237,6 +364,7 @@ static int xp_eval(xp_ctx *xc, xpath_tree *xs, xp_ctx **xrp); static int xp_eval_step(xp_ctx *xc0, xpath_tree *xs, + cvec *nsc, xp_ctx **xrp) { int retval = -1; @@ -263,7 +391,7 @@ xp_eval_step(xp_ctx *xc0, if (xc->xc_descendant){ for (i=0; ixc_size; i++){ xv = xc->xc_nodeset[i]; - if (nodetest_recursive(xv, nodetest, CX_ELMNT, 0x0, &vec, &veclen) < 0) + if (nodetest_recursive(xv, nodetest, CX_ELMNT, 0x0, nsc, &vec, &veclen) < 0) goto done; } xc->xc_descendant = 0; @@ -280,7 +408,7 @@ xp_eval_step(xp_ctx *xc0, x = NULL; while ((x = xml_child_each(xv, x, CX_ELMNT)) != NULL) { /* xs->xs_c0 is nodetest */ - if (nodetest == NULL || nodetest_eval(x, nodetest)) + if (nodetest == NULL || nodetest_eval(x, nodetest, nsc) == 1) if (cxvec_append(x, &vec, &veclen) < 0) goto done; } @@ -292,7 +420,7 @@ xp_eval_step(xp_ctx *xc0, 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, &vec, &veclen) < 0) + if (nodetest_recursive(xv, xs->xs_c0, CX_ELMNT, 0x0, nsc, &vec, &veclen) < 0) goto done; } ctx_nodeset_replace(xc, vec, veclen); @@ -331,7 +459,7 @@ xp_eval_step(xp_ctx *xc0, break; } if (xs->xs_c1){ - if (xp_eval(xc, xs->xs_c1, xrp) < 0) + if (xp_eval(xc, xs->xs_c1, nsc, xrp) < 0) goto done; } else{ @@ -351,6 +479,7 @@ xp_eval_step(xp_ctx *xc0, * 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 @@ -371,6 +500,7 @@ xp_eval_step(xp_ctx *xc0, static int xp_eval_predicate(xp_ctx *xc, xpath_tree *xs, + cvec *nsc, xp_ctx **xrp) { int retval = -1; @@ -386,7 +516,7 @@ xp_eval_predicate(xp_ctx *xc, goto done; } else{ /* eval previous predicates */ - if (xp_eval(xc, xs->xs_c0, &xr0) < 0) + if (xp_eval(xc, xs->xs_c0, nsc, &xr0) < 0) goto done; } if (xs->xs_c1){ @@ -415,7 +545,7 @@ xp_eval_predicate(xp_ctx *xc, * 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, &xrc) < 0) + if (xp_eval(xcc, xs->xs_c1, nsc, &xrc) < 0) goto done; if (xcc) ctx_free(xcc); @@ -834,13 +964,16 @@ xp_union(xp_ctx *xc1, * 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; @@ -880,12 +1013,12 @@ xp_eval(xp_ctx *xc, break; case XP_STEP: /* XP_NODE is first argument -not called explicitly */ - if (xp_eval_step(xc, xs, xrp) < 0) + if (xp_eval_step(xc, xs, nsc, xrp) < 0) goto done; goto ok; break; case XP_PRED: - if (xp_eval_predicate(xc, xs, xrp) < 0) + if (xp_eval_predicate(xc, xs, nsc, xrp) < 0) goto done; goto ok; break; @@ -895,7 +1028,7 @@ xp_eval(xp_ctx *xc, /* Eval first child c0 */ if (xs->xs_c0){ - if (xp_eval(xc, xs->xs_c0, &xr0) < 0) + if (xp_eval(xc, xs->xs_c0, nsc, &xr0) < 0) goto done; } /* Actions between first and second child @@ -973,7 +1106,7 @@ xp_eval(xp_ctx *xc, * Note, some operators like locationpath, need transitive context (use_xr0) */ if (xs->xs_c1) - if (xp_eval(use_xr0?xr0:xc, xs->xs_c1, &xr1) < 0) + if (xp_eval(use_xr0?xr0:xc, xs->xs_c1, nsc, &xr1) < 0) goto done; /* Actions after second child */ @@ -1032,19 +1165,20 @@ xp_eval(xp_ctx *xc, if (xr0) ctx_free(xr0); return retval; -} +} /* xp_eval */ -/*! Given XML tree and xpath, returns xpath context +/*! 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 * @retval 0 OK * @retval -1 Error * @code * xp_ctx *xc = NULL; - * if (xpath_vec_ctx(x, xpath, &xc) < 0) + * if (xpath_vec_ctx(x, NULL, xpath, &xc) < 0) * err; * if (xc) * ctx_free(xc); @@ -1052,6 +1186,7 @@ xp_eval(xp_ctx *xc, */ int xpath_vec_ctx(cxobj *xcur, + cvec *nsc, char *xpath, xp_ctx **xrp) { @@ -1084,7 +1219,7 @@ xpath_vec_ctx(cxobj *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, xrp) < 0) + if (xp_eval(&xc, xy.xy_top, nsc, xrp) < 0) goto done; if (xc.xc_nodeset){ free(xc.xc_nodeset); @@ -1102,23 +1237,27 @@ xpath_vec_ctx(cxobj *xcur, /*! Xpath nodeset function where only the first matching entry is returned * args: - * @param[in] xcur xml-tree where to search - * @param[in] xpath string with XPATH syntax - * @retval xml-tree of first match - * @retval NULL Error or not found + * @param[in] xcur XML tree where to search + * @param[in] nsc XML Namespace context + * @param[in] format string with XPATH syntax + * @retval xml-tree XML tree of first match + * @retval NULL Error or not found * * @code * cxobj *x; - * if ((x = xpath_first(xtop, "//symbol/foo")) != NULL) { + * cvec *nsc; // namespace context + * if ((x = xpath_first(xtop, nsc, "//symbol/foo")) != NULL) { * ... * } * @endcode * @note the returned pointer points into the original tree so should not be freed fter use. * @note return value does not see difference between error and not found * @see also xpath_vec. + * @experimental */ cxobj * xpath_first(cxobj *xcur, + cvec *nsc, char *format, ...) { @@ -1144,9 +1283,8 @@ xpath_first(cxobj *xcur, goto done; } va_end(ap); - if (xpath_vec_ctx(xcur, xpath, &xr) < 0) + if (xpath_vec_ctx(xcur, nsc, xpath, &xr) < 0) goto done; - if (xr && xr->xc_type == XT_NODESET && xr->xc_size) cx = xr->xc_nodeset[0]; done: @@ -1157,18 +1295,21 @@ 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 - * @param[in] xpath stdarg string with XPATH 1.0 syntax + * @param[in] format stdarg string with XPATH 1.0 syntax + * @param[in] nsc XML Namespace context for XPATH * @param[out] vec vector of xml-trees. Vector must be free():d after use * @param[out] veclen returns length of vector in return value * @retval 0 OK * @retval -1 Error * @code + * cvec *nsc; // namespace context * cxobj **vec; * size_t veclen; - * if (xpath_vec(xcur, "//symbol/foo", &vec, &veclen) < 0) + * if (xpath_vec(xcur, nsc, "//symbol/foo", &vec, &veclen) < 0) * goto err; * for (i=0; ixc_type == XT_NODESET){ *vec = xr->xc_nodeset; @@ -1227,6 +1370,7 @@ xpath_vec(cxobj *xcur, /* Xpath that returns a vector of matches (only nodes marked with flags) * @param[in] xcur xml-tree where to search * @param[in] xpath string with XPATH syntax + * @param[in] nsc External XML namespace context, or NULL * @param[in] flags Set of flags that return nodes must match (0 if all) * @param[out] vec vector of xml-trees. Vector must be free():d after use * @param[out] veclen returns length of vector in return value @@ -1235,7 +1379,8 @@ xpath_vec(cxobj *xcur, * @code * cxobj **vec; * size_t veclen; - * if (xpath_vec_flag(xcur, "//symbol/foo", XML_FLAG_ADD, &vec, &veclen) < 0) + * cvec *nsc; // namespace context (not NULL) + * if (xpath_vec_flag(xcur, nsc, "//symbol/foo", XML_FLAG_ADD, &vec, &veclen) < 0) * goto err; * for (i=0; ixc_type == XT_NODESET){ for (i=0; ixc_size; i++){ @@ -1303,14 +1449,16 @@ xpath_vec_flag(cxobj *xcur, /*! Given XML tree and xpath, returns boolean * Returns true if the nodeset is non-empty * @param[in] xcur xml-tree where to search + * @param[in] nsc External XML namespace context, or NULL * @param[in] xpath stdarg string with XPATH 1.0 syntax * @retval 1 True * @retval 0 False * @retval -1 Error */ int -xpath_vec_bool(cxobj *xcur, - char *format, +xpath_vec_bool(cxobj *xcur, + cvec *nsc, + char *format, ...) { int retval = -1; @@ -1335,7 +1483,7 @@ xpath_vec_bool(cxobj *xcur, goto done; } va_end(ap); - if (xpath_vec_ctx(xcur, xpath, &xr) < 0) + if (xpath_vec_ctx(xcur, nsc, xpath, &xr) < 0) goto done; if (xr) retval = ctx2boolean(xr); diff --git a/lib/src/clixon_xpath_ctx.c b/lib/src/clixon_xpath_ctx.c index a50ff5c2..bf7b5dec 100644 --- a/lib/src/clixon_xpath_ctx.c +++ b/lib/src/clixon_xpath_ctx.c @@ -32,7 +32,7 @@ ***** END LICENSE BLOCK ***** * Clixon XML XPATH 1.0 according to https://www.w3.org/TR/xpath-10 - * This file defines XPATH contexts using in traversing the XPATH parse tree. + * This file defines XPATH contexts used in traversing the XPATH parse tree. */ #include #include diff --git a/lib/src/clixon_xpath_parse.h b/lib/src/clixon_xpath_parse.h index 6ba57672..3e23e8c2 100644 --- a/lib/src/clixon_xpath_parse.h +++ b/lib/src/clixon_xpath_parse.h @@ -31,6 +31,7 @@ ***** END LICENSE BLOCK ***** + * Clixon XML XPATH 1.0 according to https://www.w3.org/TR/xpath-10 */ #ifndef _CLIXON_XPATH_PARSE_H_ #define _CLIXON_XPATH_PARSE_H_ @@ -50,7 +51,7 @@ enum xp_type{ XP_ABSPATH, XP_RELLOCPATH, XP_STEP, - XP_NODE, + XP_NODE, /* s0 is namespace prefix, s1 is name */ XP_NODE_FN, XP_PRED, XP_PRI0, diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index e535451b..3e2cff9e 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand This file is part of CLIXON. @@ -789,12 +789,12 @@ yang_find_mynamespace(yang_stmt *ys) char *namespace = NULL; if ((ymod = ys_real_module(ys)) == NULL){ - clicon_err(OE_YANG, 0, "My yang module not found"); + clicon_err(OE_YANG, ENOENT, "My yang module not found"); goto done; } if ((ynamespace = yang_find(ymod, Y_NAMESPACE, NULL)) == NULL) goto done; - namespace = ynamespace->ys_argument; + namespace = yang_argument_get(ynamespace); done: return namespace; } @@ -1176,17 +1176,17 @@ yang_find_module_by_prefix(yang_stmt *ys, } yimport = NULL; while ((yimport = yn_each(my_ymod, yimport)) != NULL) { - if (yimport->ys_keyword != Y_IMPORT && - yimport->ys_keyword != Y_INCLUDE) + if (yang_keyword_get(yimport) != Y_IMPORT && + yang_keyword_get(yimport) != Y_INCLUDE) continue; if ((yprefix = yang_find(yimport, Y_PREFIX, NULL)) != NULL && - strcmp(yprefix->ys_argument, prefix) == 0){ + strcmp(yang_argument_get(yprefix), prefix) == 0){ break; } } if (yimport){ - if ((ymod = yang_find(yspec, Y_MODULE, yimport->ys_argument)) == NULL && - (ymod = yang_find(yspec, Y_SUBMODULE, yimport->ys_argument)) == NULL){ + if ((ymod = yang_find(yspec, Y_MODULE, yang_argument_get(yimport))) == NULL && + (ymod = yang_find(yspec, Y_SUBMODULE, yang_argument_get(yimport))) == NULL){ clicon_err(OE_YANG, 0, "No module or sub-module found with prefix %s", prefix); yimport = NULL; @@ -1222,7 +1222,7 @@ yang_find_module_by_namespace(yang_stmt *yspec, return ymod; } -/*! Given a yang spec and a module name, return yang module +/*! Given a yang spec and a module name, return yang module or submodule * * @param[in] yspec A yang specification * @param[in] name Name of module diff --git a/lib/src/clixon_yang_cardinality.c b/lib/src/clixon_yang_cardinality.c index 06fdf5bf..f5f0dcdc 100644 --- a/lib/src/clixon_yang_cardinality.c +++ b/lib/src/clixon_yang_cardinality.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand This file is part of CLIXON. diff --git a/lib/src/clixon_yang_cardinality.h b/lib/src/clixon_yang_cardinality.h index 815d8d56..6d794a78 100644 --- a/lib/src/clixon_yang_cardinality.h +++ b/lib/src/clixon_yang_cardinality.h @@ -1,7 +1,7 @@ /* ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand This file is part of CLIXON. diff --git a/lib/src/clixon_yang_internal.h b/lib/src/clixon_yang_internal.h index 98aefbab..12edd24c 100644 --- a/lib/src/clixon_yang_internal.h +++ b/lib/src/clixon_yang_internal.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand This file is part of CLIXON. @@ -80,7 +80,7 @@ struct yang_stmt{ char *ys_argument; /* String / argument depending on keyword */ int ys_flags; /* Flags according to YANG_FLAG_* above */ yang_stmt *ys_mymodule; /* Shortcut to "my" module. Augmented - nodes can belong to other + nodes can belong to other modules than the ancestor module */ char *ys_extra; /* For unknown */ diff --git a/lib/src/clixon_yang_module.c b/lib/src/clixon_yang_module.c index 341bdfd0..953cb5f1 100644 --- a/lib/src/clixon_yang_module.c +++ b/lib/src/clixon_yang_module.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand This file is part of CLIXON. @@ -69,6 +69,7 @@ #include "clixon_file.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_options.h" @@ -253,6 +254,7 @@ yms_build(clicon_handle h, * @param[in] h Clicon handle * @param[in] yspec Yang spec * @param[in] xpath XML Xpath + * @param[in] nsc XML Namespace context for xpath * @param[in] brief Just name, revision and uri (no cache) * @param[in,out] xret Existing XML tree, merge x into this * @retval -1 Error (fatal) @@ -281,6 +283,7 @@ int yang_modules_state_get(clicon_handle h, yang_stmt *yspec, char *xpath, + cvec *nsc, int brief, cxobj **xret) { @@ -302,7 +305,7 @@ yang_modules_state_get(clicon_handle h, /* xc is also original tree, need to copy it */ if ((xw = xml_wrap(xc, "top")) == NULL) goto done; - if (xpath_first(xw, "%s", xpath)){ + if (xpath_first(xw, NULL, "%s", xpath)){ if ((x = xml_dup(xc)) == NULL) /* Make copy and use below */ goto done; } @@ -334,7 +337,7 @@ yang_modules_state_get(clicon_handle h, if ((x = xml_wrap(x, "top")) < 0) goto done; /* extract xpath part of module-state tree */ - if (xpath_vec(x, "%s", &xvec, &xlen, xpath?xpath:"/") < 0) + if (xpath_vec(x, nsc, "%s", &xvec, &xlen, xpath?xpath:"/") < 0) goto done; if (xvec != NULL){ for (i=0; i]]>]]>' | clixon_netconf -qef $cfg 2> /dev/null) + reply=$(echo ']]>]]>' | clixon_netconf -qef $cfg 2> /dev/null) let i=0; while [[ $reply != "mydesc" +expectfn "$clixon_cli -1 -f $cfg -l o show xpath /interfaces/interface/description urn:ietf:params:xml:ns:yang:ietf-interfaces" 0 "mydesc" new "cli delete description" expectfn "$clixon_cli -1 -f $cfg -l o delete interfaces interface eth/0/0 description mydesc" 0 "" new "cli show xpath no description" -expectfn "$clixon_cli -1 -f $cfg -l o show xpath /interfaces/interface/description" 0 "^$" +expectfn "$clixon_cli -1 -f $cfg -l o show xpath /interfaces/interface/description urn:ietf:params:xml:ns:yang:ietf-interfaces" 0 "^$" new "cli copy interface" expectfn "$clixon_cli -1 -f $cfg copy interface eth/0/0 to eth99" 0 "^$" diff --git a/test/test_feature.sh b/test/test_feature.sh index 6db7c439..98febbe5 100755 --- a/test/test_feature.sh +++ b/test/test_feature.sh @@ -116,7 +116,7 @@ expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]> +]]>]]> EOF ) #echo $ret diff --git a/test/test_insert.sh b/test/test_insert.sh index dccdfae3..9699b314 100755 --- a/test/test_insert.sh +++ b/test/test_insert.sh @@ -1,15 +1,14 @@ #!/bin/bash -# XML Insert unit test +# XML Insert elements and test if they are sorted according to yang # First a list with 0-5 base elements, insert in different places # Second varying yangs: container, leaf, list, leaf-list, choice, user-order list - # Magic line must be first in script (see README.md) s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi : ${clixon_util_insert:=clixon_util_insert} -OPTS="-D $DBG -s" +OPTS="-D $DBG" APPNAME=example @@ -63,7 +62,7 @@ testrun(){ err "length of retval is zero" fi # echo "rs:$rs" -# echo "r0:$r0" + # echo "r0:$r0" # Check they are equal if [[ "$r0" != "$rs" ]]; then err "$rs" "$r0" diff --git a/test/test_leafref.sh b/test/test_leafref.sh index 1c8f7eae..b67cb26e 100755 --- a/test/test_leafref.sh +++ b/test/test_leafref.sh @@ -45,21 +45,29 @@ module example{ } container default-address { leaf absname { + description "Absolute references existing interfaces in if module"; type leafref { - path "/ip:interfaces/ip:interface/ip:name"; + path "/if:interfaces/if:interface/if:name"; } } leaf relname { type leafref { - path "../../interfaces/interface/name"; + path "../../if:interfaces/if:interface/if:name"; } } leaf address { + description "From RFC7950 9.9.5"; type leafref { - path "../../interfaces/interface[name = current()/../relname]" - + "/ipv4/address/ip"; + path "../../if:interfaces/if:interface[if:name = current()/../relname]" + + "/if:ipv4/if:address/if:ip"; } } + leaf wrong { + description "References leading nowhere in yang"; + type leafref { + path "/ip:interfaces/ip:interface/ip:name"; + } + } } list sender{ key name; @@ -75,6 +83,36 @@ module example{ } EOF +BASEXML=$(cat < + + eth0 + ex:eth + +
+ 192.0.2.1 + 24 +
+
+ 192.0.2.2 + 24 +
+
+
+ + lo + ex:lo + +
+ 127.0.0.1 + 32 +
+
+
+ +EOF +) + new "test params: -f $cfg" if [ $BE -ne 0 ]; then @@ -91,33 +129,40 @@ if [ $BE -ne 0 ]; then fi new "leafref base config" -expecteof "$clixon_netconf -qf $cfg" 0 'eth0ex:eth
192.0.2.124
192.0.2.224
loex:lo
127.0.0.132
]]>]]>' '^]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 "$BASEXML]]>]]>" '^]]>]]>$' new "leafref get config" -expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^eth0' +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' "^$BASEXML]]>]]>" new "leafref base commit" expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" -new "leafref get config" -expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^eth0' - -new "leafref add wrong ref" +new "leafref add non-existing ref" expecteof "$clixon_netconf -qf $cfg" 0 'eth3
10.0.4.6
]]>]]>' '^]]>]]>$' new "leafref validate" expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" '^applicationbad-elementeth3errorLeafref validation failed: No such leaf]]>]]>$' +#new "leafref wrong ref" +#expecteof "$clixon_netconf -qf $cfg" 0 'eth3
10.0.4.6
]]>]]>' '^]]>]]>$' + new "leafref discard-changes" expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" new "leafref add correct absref" expecteof "$clixon_netconf -qf $cfg" 0 'eth0]]>]]>' '^]]>]]>$' +new "leafref validate (ok)" +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^" + new "leafref add correct relref" expecteof "$clixon_netconf -qf $cfg" 0 'eth0]]>]]>' '^]]>]]>$' -# XXX add address +new "leafref validate (ok)" +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^" + +new "leafref add correct address" +expecteof "$clixon_netconf -qf $cfg" 0 '
192.0.2.1
]]>]]>' '^]]>]]>$' new "leafref validate (ok)" expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^" diff --git a/test/test_nacm_default.sh b/test/test_nacm_default.sh index 6645ef11..715ee306 100755 --- a/test/test_nacm_default.sh +++ b/test/test_nacm_default.sh @@ -91,7 +91,7 @@ EOF if [ $? -ne 0 ]; then err fi - new "start backend -s init -f $cfg" + new "start backend -s startup -f $cfg" start_backend -s startup -f $cfg else new "Restart backend as eg follows: -Ff $cfg -s startup" diff --git a/test/test_nacm_ext.sh b/test/test_nacm_ext.sh index 9453978a..102af693 100755 --- a/test/test_nacm_ext.sh +++ b/test/test_nacm_ext.sh @@ -76,7 +76,7 @@ module nacm-example{ EOF cat < $nacmfile - + true permit deny diff --git a/test/test_nacm_module_read.sh b/test/test_nacm_module_read.sh index 16755175..b32774c0 100755 --- a/test/test_nacm_module_read.sh +++ b/test/test_nacm_module_read.sh @@ -164,7 +164,7 @@ expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/clixon-ex ' new "admin read netconf ok" -expecteof "$clixon_netconf -U andy -qf $cfg" 0 ']]>]]>' '^key42val42key43val43]]>]]>$' +expecteof "$clixon_netconf -U andy -qf $cfg" 0 ']]>]]>' '^key42val42key43val43]]>]]>$' new "admin read element ok" expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/clixon-example:translate=key42/value)" 0 '{"clixon-example:value": "val42"} @@ -193,7 +193,7 @@ expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/clixon-e ' new "limit read netconf ok" -expecteof "$clixon_netconf -U wilma -qf $cfg" 0 ']]>]]>' '^key42val42key43val43]]>]]>$' +expecteof "$clixon_netconf -U wilma -qf $cfg" 0 ']]>]]>' '^key42val42key43val43]]>]]>$' new "limit read element ok" expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/clixon-example:translate=key42/value)" 0 '{"clixon-example:value": "val42"} @@ -217,7 +217,7 @@ new "guest read fail" expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/clixon-example:translate)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} ' new "guest read netconf fail" -expecteof "$clixon_netconf -U guest -qf $cfg" 0 ']]>]]>' '^applicationaccess-deniederrordefault deny]]>]]>$' +expecteof "$clixon_netconf -U guest -qf $cfg" 0 ']]>]]>' '^applicationaccess-deniederrordefault deny]]>]]>$' new "guest read element fail" expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/clixon-example:translate=key42/value)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} ' diff --git a/test/test_netconf.sh b/test/test_netconf.sh index 05cfdfe5..3ccd6c08 100755 --- a/test/test_netconf.sh +++ b/test/test_netconf.sh @@ -69,7 +69,7 @@ expecteof "$clixon_netconf -qf $cfg" 0 ' $tmp # new -]]>]]> +]]>]]> EOF new "Check eth/0/0 added using xpath" @@ -98,7 +98,7 @@ expecteof "$clixon_netconf -qf $cfg" 0 ' $tmp # new -]]>]]> +]]>]]> EOF new "netconf get config xpath" @@ -106,7 +106,7 @@ expecteof "$clixon_netconf -qf $cfg" 0 "$(cat $tmp)" '^ $tmp # new -]]>]]> +]]>]]> EOF new "netconf get config xpath parent" @@ -162,7 +162,7 @@ new "netconf edit state operation should fail" expecteof "$clixon_netconf -qf $cfg" 0 'e0up]]>]]>' "^protocolinvalid-valueerrorState data not allowed]]>]]>" new "netconf get state operation" -expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" '^eth1ex:ethtrueup]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" '^eth1ex:ethtrueup]]>]]>$' new "netconf lock/unlock" expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>]]>]]>" "^]]>]]>]]>]]>$" diff --git a/test/test_order.sh b/test/test_order.sh index 20393f18..9bb23462 100755 --- a/test/test_order.sh +++ b/test/test_order.sh @@ -49,7 +49,7 @@ cat < $fyang module order-example{ yang-version 1.1; namespace "urn:example:order"; - prefix ex; + prefix exo; import clixon-example { /* for state callback */ prefix ex; } @@ -168,7 +168,7 @@ fi # STATE (should not be ordered) new "state data (should be unordered: 42,41,43)" cat < $tmp -]]>]]> +]]>]]> EOF expecteof "$clixon_netconf -qf $cfg" 0 "$(cat $tmp)" '424143]]>]]>' @@ -177,16 +177,16 @@ new "verify running from start, should be: c,l,y0,y1,y2,y3; y1 and y3 sorted." expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^hejhoppdbcaabcddbarabarcbarbbarabarbbarcbardbar]]>]]>$' new "get each ordered-by user leaf-list" -expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" '^abar]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" '^abar]]>]]>$' new "get each ordered-by user leaf-list" -expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" '^abar]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" '^abar]]>]]>$' new "get each ordered-by user leaf-list" -expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" '^bbar]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" '^bbar]]>]]>$' new "get each ordered-by user leaf-list" -expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" '^bbar]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" '^bbar]]>]]>$' new "delete candidate" expecteof "$clixon_netconf -qf $cfg" 0 'none]]>]]>' "^]]>]]>$" @@ -209,7 +209,7 @@ new "netconf commit" expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" new "verify leaf-list user order in running (as entered: c,b,a,0)" -expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^cba0]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^cba0]]>]]>$' # LISTS @@ -220,7 +220,7 @@ new "add one entry to list user order" expecteof "$clixon_netconf -qf $cfg" 0 'afie]]>]]>' "^]]>]]>$" new "verify list user order (as entered)" -expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^cbarbfooafie]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^cbarbfooafie]]>]]>$' new "Overwrite existing ordered-by user y2->c" expecteof "$clixon_netconf -qf $cfg" 0 ' @@ -238,7 +238,7 @@ expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' new "Tests for no duplicates." -expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^cnewcbnewbanewa]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^cnewcbnewbanewa]]>]]>$' #-- order by type rather than strings. # there are three leaf-lists:strings, ints, and decimal64, and two lists: @@ -252,7 +252,7 @@ expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' "^]]>]]>$" new "check string order (1,10,2)" -expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^1102]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^1102]]>]]>$' new "put leaf-list int (10,2,1)" expecteof "$clixon_netconf -qf $cfg" 0 ' @@ -260,7 +260,7 @@ expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' "^]]>]]>$" new "check leaf-list int order (1,2,10)" -expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^1210]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^1210]]>]]>$' new "put list int (10,2,1)" expecteof "$clixon_netconf -qf $cfg" 0 ' @@ -268,7 +268,7 @@ expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' "^]]>]]>$" new "check list int order (1,2,10)" -expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^1210]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^1210]]>]]>$' new "put leaf-list decimal64 (10,2,1)" expecteof "$clixon_netconf -qf $cfg" 0 ' @@ -276,7 +276,7 @@ expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' "^]]>]]>$" new "check leaf-list decimal64 order (1,2,10)" -expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^1.02.010.0]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^1.02.010.0]]>]]>$' new "put list decimal64 (10,2,1)" expecteof "$clixon_netconf -qf $cfg" 0 ' @@ -284,7 +284,7 @@ expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' "^]]>]]>$" new "check list decimal64 order (1,2,10)" -expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^1.02.010.0]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^1.02.010.0]]>]]>$' if [ $BE -eq 0 ]; then exit # BE diff --git a/test/test_perf.sh b/test/test_perf.sh index a953c346..65c20531 100755 --- a/test/test_perf.sh +++ b/test/test_perf.sh @@ -11,7 +11,7 @@ s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi : ${format:=xml} # Number of list/leaf-list entries in file -: ${perfnr:=5000} +: ${perfnr:=1000} # Number of requests made get/put : ${perfreq:=100} @@ -27,7 +27,7 @@ cat < $fyang module scaling{ yang-version 1.1; namespace "urn:example:clixon"; - prefix ip; + prefix ex; container x { list y { key "a"; @@ -120,7 +120,7 @@ expecteof "/usr/bin/time -f %e $clixon_netconf -qf $cfg" 0 " new "netconf get $perfreq small config" { time -p for (( i=0; i<$perfreq; i++ )); do rnd=$(( ( RANDOM % $perfnr ) )) - echo "]]>]]>" + echo "]]>]]>" done | $clixon_netconf -qf $cfg > /dev/null; } 2>&1 | awk '/real/ {print $2}' # NETCONF add diff --git a/test/test_perf_state.sh b/test/test_perf_state.sh index 8c4d76b7..1449f7d4 100755 --- a/test/test_perf_state.sh +++ b/test/test_perf_state.sh @@ -83,23 +83,22 @@ new "netconf commit large config" expecteof "/usr/bin/time -f %e $clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" # START actual tests - # Having a large db, get single entries many times # NETCONF get new "netconf get test single req" -sel="/ietf-interfaces:interfaces/interface[name='e1']" -msg="]]>]]>" +sel="/if:interfaces/if:interface[if:name='e1']" +msg="]]>]]>" expecteof "$clixon_netconf -qf $cfg" 0 "$msg" '^e1ex:ethtrueup]]>]]>$' new "netconf get $perfreq single reqs" { time -p for (( i=0; i<$perfreq; i++ )); do rnd=$(( ( RANDOM % $perfnr ) )) - sel="/ietf-interfaces:interfaces/interface[name='e$rnd']" - echo "]]>]]>" + sel="/if:interfaces/if:interface[if:name='e$rnd']" + echo "]]>]]>" done | $clixon_netconf -qf $cfg > /dev/null; } 2>&1 | awk '/real/ {print $2}' # RESTCONF get -new "restconf get test single req" +new "restconf get test single req XXX" expecteq "$(curl -s -X GET http://localhost/restconf/data/ietf-interfaces:interfaces/interface=e1)" 0 '{"ietf-interfaces:interface": [{"name": "e1","type": "ex:eth","enabled": true,"oper-status": "up"}]} ' @@ -129,7 +128,7 @@ done } 2>&1 | awk '/real/ {print $2}' # Get config in one large get new "netconf get large config" -/usr/bin/time -f %e echo " ]]>]]>" | $clixon_netconf -qf $cfg > /tmp/netconf +/usr/bin/time -f %e echo " ]]>]]>" | $clixon_netconf -qf $cfg > /tmp/netconf new "restconf get large config" /usr/bin/time -f %e curl -sG http://localhost/restconf/data/ietf-interfaces:interfaces | wc diff --git a/test/test_stream.sh b/test/test_stream.sh index 806aa9b8..2f8af4bd 100755 --- a/test/test_stream.sh +++ b/test/test_stream.sh @@ -129,10 +129,10 @@ wait_restconf new "1. Netconf RFC5277 stream testing" # 1.1 Stream discovery new "netconf event stream discovery RFC5277 Sec 3.2.5" -expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' 'EXAMPLEExample event streamtrue]]>]]>' +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' 'EXAMPLEExample event streamtrue]]>]]>' new "netconf event stream discovery RFC8040 Sec 6.2" -expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' 'EXAMPLEExample event streamtruexmlhttps://localhost/streams/EXAMPLE]]>]]>' +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' 'EXAMPLEExample event streamtruexmlhttps://localhost/streams/EXAMPLE]]>]]>' # # 1.2 Netconf stream subscription diff --git a/test/test_type.sh b/test/test_type.sh index 52265bd3..fb3e9aaa 100755 --- a/test/test_type.sh +++ b/test/test_type.sh @@ -197,7 +197,7 @@ EOF # Type tests. # Parameters: -# 1: dbcache true/false +# 1: dbcache: cache, nocache, cache-zerocopy testrun(){ dbcache=$1 new "test params: -f $cfg # dbcache: $dbcache" @@ -216,7 +216,7 @@ testrun(){ /usr/local/var/$APPNAME/$APPNAME.pidfile 1 /usr/local/var/$APPNAME - $dbcache + $dbcache $format EOF @@ -614,9 +614,12 @@ EOF } # Run without db cache -testrun false +testrun nocache # Run with db cache -testrun true +testrun cache + +# Run with +testrun cache-zerocopy rm -rf $dir diff --git a/test/test_upgrade_auto.sh b/test/test_upgrade_auto.sh index 7991864c..2165ecbe 100755 --- a/test/test_upgrade_auto.sh +++ b/test/test_upgrade_auto.sh @@ -191,7 +191,7 @@ EOF # Changelog of example-a: cat < $changelog - + urn:example:b 2017-12-01 diff --git a/test/test_xpath.sh b/test/test_xpath.sh index 85822fb5..4a1f3a3d 100755 --- a/test/test_xpath.sh +++ b/test/test_xpath.sh @@ -139,7 +139,7 @@ new "xpath ../type='rt:static'" expecteof "$clixon_util_xpath -f $xml2 -i /aaa/bbb/here" 0 "../type='rt:static'" "^bool:true$" new "xpath rib-name != ../../name" -expecteof "$clixon_util_xpath -f $xml2 -i /aaa/bbb" 0 "rib-name != ../../name" "^bool:true$" +expecteof "$clixon_util_xpath -f $xml2 -i /aaa/bbb" 0 "rib-name != ../name" "^bool:true$" new "xpath routing/ribs/rib[name=current()/rib-name]/address-family=../../address-family" expecteof "$clixon_util_xpath -f $xml2 -i /aaa/bbb" 0 "routing/ribs/rib[name=current()/rib-name]/address-family=../../address-family" "^bool:true$" diff --git a/test/test_yang.sh b/test/test_yang.sh index 06161c00..32926c89 100755 --- a/test/test_yang.sh +++ b/test/test_yang.sh @@ -193,16 +193,16 @@ new "netconf commit 2nd" expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" new "netconf get config xpath" -expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" '^125one]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" '^125one]]>]]>$' new "netconf edit leaf-list" expecteof "$clixon_netconf -qf $cfg" 0 'hejhopp]]>]]>' "^]]>]]>$" new "netconf get leaf-list" -expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^hejhopp]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^hejhopp]]>]]>$' new "netconf get leaf-list path" -expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^hejhopp]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^hejhopp]]>]]>$" new "netconf get (should be some)" expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" '^125one' @@ -211,7 +211,7 @@ new "cli set leaf-list" expectfn "$clixon_cli -1f $cfg set x f e foo" 0 "" new "cli show leaf-list" -expectfn "$clixon_cli -1f $cfg show xpath /x/f/e" 0 "foo" +expectfn "$clixon_cli -1f $cfg show xpath /x/f/e urn:example:clixon" 0 "foo" new "netconf set state data (not allowed)" expecteof "$clixon_netconf -qf $cfg" 0 '42]]>]]>' '^protocolinvalid-valueerrorState data not allowed]]>]]>$' @@ -220,10 +220,10 @@ new "netconf set presence and not present" expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' "^]]>]]>$" new "netconf get presence only" -expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^]]>]]>$' new "netconf get presence only" -expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' "^]]>]]>$" new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" diff --git a/util/clixon_util_insert.c b/util/clixon_util_insert.c index a9b57bed..49fecd09 100644 --- a/util/clixon_util_insert.c +++ b/util/clixon_util_insert.c @@ -91,7 +91,7 @@ main(int argc, char **argv) cxobj *xi = NULL; int sort = 0; clicon_handle h; - + clicon_log_init("clixon_insert", LOG_DEBUG, CLICON_LOG_STDERR); if ((h = clicon_handle_init()) == NULL) goto done; @@ -154,7 +154,7 @@ main(int argc, char **argv) } if (xml_apply(x0, CX_ELMNT, xml_spec_populate, yspec) < 0) goto done; - if ((xb = xpath_first(x0, "%s", xpath)) == NULL){ + if ((xb = xpath_first(x0, NULL, "%s", xpath)) == NULL){ clicon_err(OE_XML, 0, "xpath: %s not found in x0", xpath); goto done; } @@ -169,7 +169,7 @@ main(int argc, char **argv) } if (xml_apply(xi, CX_ELMNT, xml_spec_populate, yspec) < 0) goto done; - if ((xi = xpath_first(xi, "%s", xpath)) == NULL){ + if ((xi = xpath_first(xi, NULL, "%s", xpath)) == NULL){ clicon_err(OE_XML, 0, "xpath: %s not found in xi", xpath); goto done; } diff --git a/util/clixon_util_xpath.c b/util/clixon_util_xpath.c index 198cdb8a..5fb8d3da 100644 --- a/util/clixon_util_xpath.c +++ b/util/clixon_util_xpath.c @@ -191,9 +191,10 @@ main(int argc, char **argv) fprintf(stderr, "Error: parsing: %s\n", clicon_err_reason); return -1; } + /* If xpath0 given, position current x */ if (xpath0){ - if ((x = xpath_first(x0, "%s", xpath0)) == NULL){ + if ((x = xpath_first(x0, NULL, "%s", xpath0)) == NULL){ fprintf(stderr, "Error: xpath0 returned NULL\n"); return -1; } @@ -201,8 +202,8 @@ main(int argc, char **argv) else x = x0; - /* Parse XML */ - if (xpath_vec_ctx(x, xpath, &xc) < 0) + /* Parse XML (use nsc == NULL to indicate dont use) */ + if (xpath_vec_ctx(x, NULL, xpath, &xc) < 0) return -1; /* Print results */ cb = cbuf_new();