From da2edceb7e10547211add872cc275eff8ed37e9b Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Tue, 21 Mar 2023 09:10:40 +0100 Subject: [PATCH] * Added new functions: xml_tree_equal and xpath2xml * RFC 8528 yang schema mount-points: * Made expand_dbvar and cli_dbxml mountpoint-aware (RFC 8528) * autocli supportgenerate * Made api_path2xml and xml2api_path mount-point-aware * Temporar fix in clixon_custom.h: XPATH_CANONICAL_SKIP_CHECK * `xml2xpath()`: Added `apostrophe` as 4th parameter, default 0 * removed extra assert.h includes --- CHANGELOG.md | 5 + apps/backend/backend_commit.c | 6 +- apps/backend/backend_get.c | 3 +- apps/backend/backend_main.c | 1 - apps/cli/cli_common.c | 154 ++++++++++--- apps/cli/cli_common.h | 1 + apps/cli/cli_generate.c | 39 +++- apps/cli/cli_show.c | 52 +++-- apps/restconf/restconf_main_native.c | 1 - apps/snmp/snmp_register.c | 1 - include/clixon_custom.h | 6 + lib/clixon/clixon_xml_map.h | 3 +- lib/clixon/clixon_xml_nsctx.h | 1 - lib/clixon/clixon_xpath.h | 4 +- lib/clixon/clixon_yang_schema_mount.h | 1 - lib/src/clixon_datastore_write.c | 2 +- lib/src/clixon_event.c | 9 +- lib/src/clixon_netconf_lib.c | 6 +- lib/src/clixon_path.c | 65 +++++- lib/src/clixon_proto.c | 1 - lib/src/clixon_sig.c | 1 - lib/src/clixon_xml_bind.c | 1 - lib/src/clixon_xml_default.c | 1 - lib/src/clixon_xml_io.c | 1 - lib/src/clixon_xml_map.c | 99 +++++++- lib/src/clixon_xml_nsctx.c | 29 ++- lib/src/clixon_xml_vec.c | 1 - lib/src/clixon_xpath.c | 269 +++++++++++++++++++--- lib/src/clixon_xpath_eval.c | 1 - lib/src/clixon_xpath_function.c | 1 - lib/src/clixon_xpath_optimize.c | 1 - lib/src/clixon_yang_internal.h | 1 + lib/src/clixon_yang_schema_mount.c | 27 ++- lib/src/clixon_yang_type.c | 1 - test/test_xpath_canonical.sh | 5 +- util/clixon_netconf_ssh_callhome_client.c | 1 - util/clixon_util_xpath.c | 2 +- 37 files changed, 658 insertions(+), 145 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a5cd8866..1fb4a9ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,12 +55,17 @@ Users may have to change how they access the system Developers may need to change their code * C-API + * `xml_diff`: removed 1st `yspec` parameter + * `xml2xpath()`: Added `int apostrophe` as 4th parameter, default 0 + * This is for being able to choose single or double quote as xpath literal quotes * `clicon_msg_rcv`: Added `intr` parameter for interrupting on `^C` (default 0) * Renamed include file: `clixon_backend_handle.h`to `clixon_backend_client.h` * `candidate_commit()`: validate_level (added in 6.1) marked obsolete ### Minor features +* RFC 8528 YANG schema mount + * Made cli/autocli mount-point-aware * Internal NETCONF (client <-> backend) * Ensure message-id increments * Separated rpc from notification socket in same session diff --git a/apps/backend/backend_commit.c b/apps/backend/backend_commit.c index f12ad873..87659380 100644 --- a/apps/backend/backend_commit.c +++ b/apps/backend/backend_commit.c @@ -510,8 +510,7 @@ validate_common(clicon_handle h, xml_apply0(td->td_src, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)(XML_FLAG_MARK|XML_FLAG_CHANGE)); /* 3. Compute differences */ - if (xml_diff(yspec, - td->td_src, + if (xml_diff(td->td_src, td->td_target, &td->td_dvec, /* removed: only in running */ &td->td_dlen, @@ -982,8 +981,7 @@ from_client_restart_one(clicon_handle h, goto done; /* 3. Compute differences */ - if (xml_diff(yspec, - td->td_src, + if (xml_diff(td->td_src, td->td_target, &td->td_dvec, /* removed: only in running */ &td->td_dlen, diff --git a/apps/backend/backend_get.c b/apps/backend/backend_get.c index 100abe85..cd1f073e 100644 --- a/apps/backend/backend_get.c +++ b/apps/backend/backend_get.c @@ -57,7 +57,6 @@ #include #include #include -#include #include /* cligen */ @@ -559,6 +558,8 @@ list_pagination_hdr(clicon_handle h, * @param[in] xe Request: * @param[in] content Get config/state/both * @param[in] db Database name + * @param[in] depth Depth attribute + * @param[in] yspec (Top-level) yang spec * @param[in] xpath XPath point to object to get * @param[in] nsc Namespace context of xpath * @param[in] username diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c index cf750092..a743240a 100644 --- a/apps/backend/backend_main.c +++ b/apps/backend/backend_main.c @@ -61,7 +61,6 @@ #include #include #include -#include /* cligen */ #include diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c index 8611e490..eab11f79 100644 --- a/apps/cli/cli_common.c +++ b/apps/cli/cli_common.c @@ -245,8 +245,8 @@ identityref_add_ns(cxobj *x, if (ns == NULL && (yns = yang_find_module_by_prefix_yspec(yspec, pf)) != NULL){ if ((ns = yang_find_mynamespace(yns)) != NULL) - if (xmlns_set(x, pf, ns) < 0) - goto done; + if (xmlns_set(x, pf, ns) < 0) + goto done; } } } @@ -260,10 +260,90 @@ identityref_add_ns(cxobj *x, return retval; } +/*! Given a top-level yspec and montpoint xpath compute a set of + * + * Manipulate top-level and a mointpoint: + * YSPEC: yspec0 yspec1 + * XML: top0-->bot0-->top1-->bot1 + * API-PATH: api-path0 api-path1 + * api-path01--------- + * The result computed from the top-level yspec and montpoint xpath are: + * - api_pathfmt10 Combined api-path for both trees + */ +int +mtpoint_paths(yang_stmt *yspec0, + char *mtpoint, + char *api_path_fmt1, + char **api_path_fmt01) +{ + int retval = -1; + yang_stmt *yu = NULL; + yang_stmt *ybot0 = NULL; + cvec *nsc0 = NULL; + int ret; + char *api_path_fmt0; + cbuf *cb = NULL; + cxobj *xbot0 = NULL; + cxobj *xtop0 = NULL; + yang_stmt *yspec1; + + if (api_path_fmt01 == NULL){ + clicon_err(OE_FATAL, EINVAL, "arg is NULL"); + goto done; + } + if ((xtop0 = xml_new(NETCONF_INPUT_CONFIG, NULL, CX_ELMNT)) == NULL) + goto done; + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + if (yang_path_arg(yspec0, mtpoint, &yu) < 0) + goto done; + if (yu == NULL){ + clicon_err(OE_FATAL, 0, "yu not found"); + goto done; + } + if (yang_mount_get(yu, mtpoint, &yspec1) < 0) + goto done; + if (yspec1 == NULL){ + clicon_err(OE_FATAL, 0, "yspec1 not found"); + goto done; + } + xbot0 = xtop0; + if (xml_nsctx_yangspec(yspec0, &nsc0) < 0) + goto done; + if ((ret = xpath2xml(mtpoint, nsc0, xtop0, yspec0, &xbot0, &ybot0, NULL)) < 0) + goto done; + if (xbot0 == NULL){ + clicon_err(OE_YANG, 0, "No xbot"); + goto done; + } + if (yang2api_path_fmt(ybot0, 0, &api_path_fmt0) < 0) + goto done; + if (api_path_fmt0 == NULL){ + clicon_err(OE_YANG, 0, "No api_path_fmt0"); + goto done; + } + cprintf(cb, "%s%s", api_path_fmt0, api_path_fmt1); + if ((*api_path_fmt01 = strdup(cbuf_get(cb))) == NULL){ + clicon_err(OE_YANG, errno, "strdup"); + goto done; + } + retval = 0; + done: + if (cb) + cbuf_free(cb); + if (api_path_fmt0) + free(api_path_fmt0); + if (nsc0) + cvec_free(nsc0); + return retval; +} + /*! Modify xml datastore from a callback using xml key format strings * @param[in] h Clicon handle * @param[in] cvv Vector of cli string and instantiated variables - * @param[in] argv Vector. First element xml key format string, eg "/aaa/%s" + * @param[in] argv Vector: [], eg "/aaa/%s" * @param[in] op Operation to perform on database * @param[in] nsctx Namespace context for last value added * cvv first contains the complete cli string, and then a set of optional @@ -289,43 +369,62 @@ cli_dbxml(clicon_handle h, { int retval = -1; char *api_path_fmt; /* xml key format */ - char *api_path = NULL; /* xml key */ + char *api_path_fmt01 = NULL; + char *api_path = NULL; cg_var *arg; cbuf *cb = NULL; - yang_stmt *yspec; cxobj *xbot = NULL; /* xpath, NULL if datastore */ yang_stmt *y = NULL; /* yang spec of xpath */ cxobj *xtop = NULL; /* xpath root */ cxobj *xerr = NULL; int ret; cg_var *cv; - int cvv_i = 0; + int cvvi = 0; + char *mtpoint = NULL; + yang_stmt *yspec0 = NULL; - if (cvec_len(argv) != 1){ + if (cvec_len(argv) != 1 && cvec_len(argv) != 2){ clicon_err(OE_PLUGIN, EINVAL, "Requires one element to be xml key format string"); goto done; } - if ((yspec = clicon_dbspec_yang(h)) == NULL){ + /* Top-level yspec */ + if ((yspec0 = clicon_dbspec_yang(h)) == NULL){ clicon_err(OE_FATAL, 0, "No DB_SPEC"); goto done; } arg = cvec_i(argv, 0); api_path_fmt = cv_string_get(arg); - + if (cvec_len(argv) > 1){ + arg = cvec_i(argv, 1); + mtpoint = cv_string_get(arg); + } /* Remove all keywords */ if (cvec_exclude_keys(cvv) < 0) goto done; - /* Transform template format string + cvv to actual api-path - * cvv_i indicates if all cvv entries were used - */ - if (api_path_fmt2api_path(api_path_fmt, cvv, &api_path, &cvv_i) < 0) - goto done; + if (mtpoint){ + /* Get and combined api-path01 */ + if (mtpoint_paths(yspec0, mtpoint, api_path_fmt, &api_path_fmt01) < 0) + goto done; + /* Transform template format string + cvv to actual api-path + * cvvi indicates if all cvv entries were used + */ + if (api_path_fmt2api_path(api_path_fmt01, cvv, &api_path, &cvvi) < 0) + goto done; + } + else { + /* Only top-level tree */ + /* Transform template format string + cvv to actual api-path + * cvvi indicates if all cvv entries were used + */ + if (api_path_fmt2api_path(api_path_fmt, cvv, &api_path, &cvvi) < 0) + goto done; + } /* Create config top-of-tree */ if ((xtop = xml_new(NETCONF_INPUT_CONFIG, NULL, CX_ELMNT)) == NULL) goto done; xbot = xtop; if (api_path){ - if ((ret = api_path2xml(api_path, yspec, xtop, YC_DATANODE, 1, &xbot, &y, &xerr)) < 0) + if ((ret = api_path2xml(api_path, yspec0, xtop, YC_DATANODE, 1, &xbot, &y, &xerr)) < 0) goto done; if (ret == 0){ if ((cb = cbuf_new()) == NULL){ @@ -352,7 +451,7 @@ cli_dbxml(clicon_handle h, * Discussion: one can claim (1) is "bad" usage but one could see cases where * you would want to delete a value if it has a specific value but not otherwise */ - if (cvv_i != cvec_len(cvv)) + if (cvvi != cvec_len(cvv)) if (dbxml_body(xbot, cvv) < 0) goto done; /* Loop over namespace context and add them to this leaf node */ @@ -367,7 +466,7 @@ cli_dbxml(clicon_handle h, /* Special handling of identityref:s whose body may be: : * Ensure the namespace is declared if it exists in YANG */ - if ((ret = xml_apply0(xbot, CX_ELMNT, identityref_add_ns, yspec)) < 0) + if ((ret = xml_apply0(xbot, CX_ELMNT, identityref_add_ns, yspec0)) < 0) goto done; if ((cb = cbuf_new()) == NULL){ clicon_err(OE_XML, errno, "cbuf_new"); @@ -379,6 +478,8 @@ cli_dbxml(clicon_handle h, goto done; retval = 0; done: + if (api_path_fmt01) + free(api_path_fmt01); if (xerr) xml_free(xerr); if (cb) @@ -695,23 +796,22 @@ cli_commit(clicon_handle h, cvec *vars, cvec *argv) { - int retval = -1; - uint32_t timeout = 0; /* any non-zero value means "confirmed-commit" */ - cg_var *timeout_var; - char *persist = NULL; - char *persist_id = NULL; - - int confirmed = (cvec_find_str(vars, "confirmed") != NULL); - int cancel = (cvec_find_str(vars, "cancel") != NULL); + int retval = -1; + uint32_t timeout = 0; /* any non-zero value means "confirmed-commit" */ + cg_var *timeout_var; + char *persist = NULL; + char *persist_id = NULL; + int confirmed; + int cancel; + confirmed = (cvec_find_str(vars, "confirmed") != NULL); + cancel = (cvec_find_str(vars, "cancel") != NULL); if ((timeout_var = cvec_find(vars, "timeout")) != NULL) { timeout = cv_uint32_get(timeout_var); clicon_debug(1, "commit confirmed with timeout %ul", timeout); } - persist = cvec_find_str(vars, "persist-val"); persist_id = cvec_find_str(vars, "persist-id-val"); - if (clicon_rpc_commit(h, confirmed, cancel, timeout, persist, persist_id) < 1) goto done; retval = 0; diff --git a/apps/cli/cli_common.h b/apps/cli/cli_common.h index 6cb8ed6d..79217c40 100644 --- a/apps/cli/cli_common.h +++ b/apps/cli/cli_common.h @@ -41,6 +41,7 @@ void cli_signal_block(clicon_handle h); void cli_signal_unblock(clicon_handle h); +int mtpoint_paths(yang_stmt *yspec0, char *mtpoint, char *api_path_fmt1, char **api_path_fmt01); /* If you do not find a function here it may be in clixon_cli_api.h which is the external API */ diff --git a/apps/cli/cli_generate.c b/apps/cli/cli_generate.c index 9960673f..850d1b9c 100644 --- a/apps/cli/cli_generate.c +++ b/apps/cli/cli_generate.c @@ -131,10 +131,14 @@ cli_expand_var_generate(clicon_handle h, int pre, cbuf *cb) { - int retval = -1; - char *api_path_fmt = NULL; - int extvalue = 0; - + int retval = -1; + char *api_path_fmt = NULL; + int extvalue = 0; + yang_stmt *yspec; + cg_var *cv = NULL; + + if ((yspec = ys_spec(ys)) != NULL) + cv = yang_cv_get(yspec); if (yang_extension_value(ys, "hide", CLIXON_AUTOCLI_NS, &extvalue, NULL) < 0) goto done; if (extvalue @@ -152,9 +156,12 @@ cli_expand_var_generate(clicon_handle h, cprintf(cb, "<%s:%s", yang_argument_get(ys), cvtypestr); if (options & YANG_OPTIONS_FRACTION_DIGITS) cprintf(cb, " fraction-digits:%u", fraction_digits); - cprintf(cb, " %s(\"candidate\",\"%s\")>", + cprintf(cb, " %s(\"candidate\",\"%s\"", GENERATE_EXPAND_XMLDB, api_path_fmt); + if (cv) /* Add optional mountpoint */ + cprintf(cb, ",\"%s\"", cv_string_get(cv)); + cprintf(cb, ")>"); retval = 0; done: if (api_path_fmt) @@ -176,11 +183,17 @@ cli_callback_generate(clicon_handle h, { int retval = -1; char *api_path_fmt = NULL; + yang_stmt *yspec; + cg_var *cv = NULL; + if ((yspec = ys_spec(ys)) != NULL) + cv = yang_cv_get(yspec); if (yang2api_path_fmt(ys, 0, &api_path_fmt) < 0) goto done; - cprintf(cb, ",%s(\"%s\")", GENERATE_CALLBACK, - api_path_fmt); + cprintf(cb, ",%s(\"%s\"", GENERATE_CALLBACK, api_path_fmt); + if (cv) /* Add optional mountpoint */ + cprintf(cb, ",\"%s\"", cv_string_get(cv)); + cprintf(cb, ")"); retval = 0; done: if (api_path_fmt) @@ -886,6 +899,7 @@ yang2cli_container(clicon_handle h, int compress = 0; yang_stmt *ymod = NULL; int extvalue = 0; + int ret; if (ys_real_module(ys, &ymod) < 0) goto done; @@ -930,6 +944,15 @@ yang2cli_container(clicon_handle h, } #endif cprintf(cb, ", act-container;{\n"); + + } + /* Is schema mount-point? */ + if (clicon_option_bool(h, "CLICON_YANG_SCHEMA_MOUNT")){ + if ((ret = yang_schema_mount_point(ys)) < 0) + goto done; + if (ret){ + cprintf(cb, "%*s%s", (level+1)*3, "", "@mountpoint;\n"); + } } yc = NULL; while ((yc = yn_each(ys, yc)) != NULL) @@ -1335,6 +1358,8 @@ yang2cli_post(clicon_handle h, * @param[in] treename Name of tree * @param[in] xautocli Autocli config tree (instance of clixon-autocli.yang) * @param[in] printgen Log the generated CLIgen syntax + * @retval 0 OK + * @retval -1 Error * @note Tie-break of same top-level symbol: prefix is NYI */ int diff --git a/apps/cli/cli_show.c b/apps/cli/cli_show.c index 2d9ea3e8..446f6e41 100644 --- a/apps/cli/cli_show.c +++ b/apps/cli/cli_show.c @@ -175,7 +175,7 @@ xpath_append(cbuf *cb0, * @param[in] h clicon handle * @param[in] name Name of this function (eg "expand_dbvar") * @param[in] cvv The command so far. Eg: cvec [0]:"a 5 b"; [1]: x=5; - * @param[in] argv Arguments given at the callback ("" "") + * @param[in] argv Arguments given at the callback: [] * @param[out] commands vector of function pointers to callback functions * @param[out] helptxt vector of pointers to helptexts * @see cli_expand_var_generate This is where arg is generated @@ -191,6 +191,7 @@ expand_dbvar(void *h, int retval = -1; char *api_path_fmt; char *api_path = NULL; + char *api_path_fmt01 = NULL; char *dbstr; cxobj *xt = NULL; char *xpath = NULL; @@ -203,7 +204,6 @@ expand_dbvar(void *h, int i; char *bodystr0 = NULL; /* previous */ cg_var *cv; - yang_stmt *yspec; cxobj *xtop = NULL; /* xpath root */ cxobj *xbot = NULL; /* xpath, NULL if datastore */ yang_stmt *y = NULL; /* yang spec of xpath */ @@ -214,12 +214,14 @@ expand_dbvar(void *h, cbuf *cbxpath = NULL; yang_stmt *ypath; yang_stmt *ytype; + char *mtpoint = NULL; + yang_stmt *yspec0 = NULL; - if (argv == NULL || cvec_len(argv) != 2){ - clicon_err(OE_PLUGIN, EINVAL, "requires arguments: "); + if (argv == NULL || (cvec_len(argv) != 2 && cvec_len(argv) != 3)){ + clicon_err(OE_PLUGIN, EINVAL, "requires arguments: []"); goto done; } - if ((yspec = clicon_dbspec_yang(h)) == NULL){ + if ((yspec0 = clicon_dbspec_yang(h)) == NULL){ clicon_err(OE_FATAL, 0, "No DB_SPEC"); goto done; } @@ -227,7 +229,7 @@ expand_dbvar(void *h, clicon_err(OE_PLUGIN, 0, "Error when accessing argument "); goto done; } - dbstr = cv_string_get(cv); + dbstr = cv_string_get(cv); if (strcmp(dbstr, "running") != 0 && strcmp(dbstr, "candidate") != 0 && strcmp(dbstr, "startup") != 0){ @@ -239,12 +241,28 @@ expand_dbvar(void *h, goto done; } api_path_fmt = cv_string_get(cv); - /* api_path_fmt = /interface/%s/address/%s - * api_path: --> /interface/eth0/address/.* - * xpath: --> /interface/[name="eth0"]/address - */ - if (api_path_fmt2api_path(api_path_fmt, cvv, &api_path, &cvvi) < 0) - goto done; + if (cvec_len(argv) > 2){ + cv = cvec_i(argv, 2); + mtpoint = cv_string_get(cv); + } + if (mtpoint){ + /* Get combined api-path01 */ + if (mtpoint_paths(yspec0, mtpoint, api_path_fmt, &api_path_fmt01) < 0) + goto done; + /* Transform template format string + cvv to actual api-path + * cvv_i indicates if all cvv entries were used + */ + if (api_path_fmt2api_path(api_path_fmt01, cvv, &api_path, &cvvi) < 0) + goto done; + } + else { + /* api_path_fmt = /interface/%s/address/%s + * api_path: --> /interface/eth0/address/.* + * xpath: --> /interface/[name="eth0"]/address + */ + if (api_path_fmt2api_path(api_path_fmt, cvv, &api_path, &cvvi) < 0) + goto done; + } /* Create config top-of-tree */ if ((xtop = xml_new(DATASTORE_TOP_SYMBOL, NULL, CX_ELMNT)) == NULL) goto done; @@ -254,18 +272,20 @@ expand_dbvar(void *h, * XXX: but y is just the first in this list, there could be other y:s? */ if (api_path){ - if ((ret = api_path2xml(api_path, yspec, xtop, YC_DATANODE, 0, &xbot, &y, &xerr)) < 0) + if ((ret = api_path2xml(api_path, yspec0, xtop, YC_DATANODE, 0, &xbot, &y, &xerr)) < 0) goto done; if (ret == 0){ + // XXX cf cli_dbxml clixon_netconf_error(xerr, "Expand datastore symbol", NULL); goto done; } } if (y==NULL) goto ok; - /* Transform api-path to xpath for netconf */ - if (api_path2xpath(api_path, yspec, &xpath, &nsc, NULL) < 0) + /* Transform api-path to xpath for netconf */ + if (api_path2xpath(api_path, yspec0, &xpath, &nsc, NULL) < 0) goto done; + if ((cbxpath = cbuf_new()) == NULL){ clicon_err(OE_UNIX, errno, "cbuf_new"); goto done; @@ -356,6 +376,8 @@ expand_dbvar(void *h, ok: retval = 0; done: + if (api_path_fmt01) + free(api_path_fmt01); if (cbxpath) cbuf_free(cbxpath); if (xerr) diff --git a/apps/restconf/restconf_main_native.c b/apps/restconf/restconf_main_native.c index 85ee33e1..e92b029a 100644 --- a/apps/restconf/restconf_main_native.c +++ b/apps/restconf/restconf_main_native.c @@ -123,7 +123,6 @@ #include #include #include -#include #include #include #include diff --git a/apps/snmp/snmp_register.c b/apps/snmp/snmp_register.c index f287fe1d..a01adb4f 100644 --- a/apps/snmp/snmp_register.c +++ b/apps/snmp/snmp_register.c @@ -63,7 +63,6 @@ #include #include #include -#include /* net-snmp */ #include diff --git a/include/clixon_custom.h b/include/clixon_custom.h index b51b3216..80cc94c9 100644 --- a/include/clixon_custom.h +++ b/include/clixon_custom.h @@ -201,3 +201,9 @@ * Introduced in 6.1, remove in 6.2 */ #define AUTOCLI_DEPRECATED_HIDE + +/*! Temporar fix for xpath_traverse_canonical for yang schema mount + * Must rewrite function to handle mountpoints, now just ignore errors + * See also + */ +#define XPATH_CANONICAL_SKIP_CHECK diff --git a/lib/clixon/clixon_xml_map.h b/lib/clixon/clixon_xml_map.h index 6beda9ce..fd381707 100644 --- a/lib/clixon/clixon_xml_map.h +++ b/lib/clixon/clixon_xml_map.h @@ -53,10 +53,11 @@ int isxmlns(cxobj *x); int xmlns_assign(cxobj *x); int xml2cvec(cxobj *xt, yang_stmt *ys, cvec **cvv0); int cvec2xml_1(cvec *cvv, char *toptag, cxobj *xp, cxobj **xt0); -int xml_diff(yang_stmt *yspec, cxobj *x0, cxobj *x1, +int xml_diff(cxobj *x0, cxobj *x1, cxobj ***first, int *firstlen, cxobj ***second, int *secondlen, cxobj ***changed_x0, cxobj ***changed_x1, int *changedlen); +int xml_tree_equal(cxobj *x0, cxobj *x1); int xml_tree_prune_flagged_sub(cxobj *xt, int flag, int test, int *upmark); int xml_tree_prune_flagged(cxobj *xt, int flag, int test); int xml_tree_prune_flags(cxobj *xt, int flags, int mask); diff --git a/lib/clixon/clixon_xml_nsctx.h b/lib/clixon/clixon_xml_nsctx.h index 84bb94ab..f40d3b63 100644 --- a/lib/clixon/clixon_xml_nsctx.h +++ b/lib/clixon/clixon_xml_nsctx.h @@ -60,7 +60,6 @@ int xml_nsctx_node(cxobj *x, cvec **ncp); int xml_nsctx_yang(yang_stmt *yn, cvec **ncp); int xml_nsctx_yangspec(yang_stmt *yspec, cvec **ncp); int xml_nsctx_cbuf(cbuf *cb, cvec *nsc); - int xml2ns(cxobj *x, char *localname, char **ns); int xml2ns_recurse(cxobj *x); int xmlns_set(cxobj *x, char *prefix, char *ns); diff --git a/lib/clixon/clixon_xpath.h b/lib/clixon/clixon_xpath.h index 5445af96..6001c703 100644 --- a/lib/clixon/clixon_xpath.h +++ b/lib/clixon/clixon_xpath.h @@ -150,6 +150,8 @@ int xpath_vec(cxobj *xcur, cvec *nsc, const char *xpformat, cxobj ***vec, siz int xpath2canonical(const char *xpath0, cvec *nsc0, yang_stmt *yspec, char **xpath1, cvec **nsc1, cbuf **cbreason); int xpath_count(cxobj *xcur, cvec *nsc, const char *xpath, uint32_t *count); -int xml2xpath(cxobj *x, cvec *nsc, int spec, char **xpath); +int xml2xpath(cxobj *x, cvec *nsc, int spec, int apostrophe, char **xpath); +int xpath2xml(char *xpath, cvec *nsc, cxobj *xtop, yang_stmt *ytop, + cxobj **xbotp, yang_stmt **ybotp, cxobj **xerr); #endif /* _CLIXON_XPATH_H */ diff --git a/lib/clixon/clixon_yang_schema_mount.h b/lib/clixon/clixon_yang_schema_mount.h index 2802850e..98ed6bce 100644 --- a/lib/clixon/clixon_yang_schema_mount.h +++ b/lib/clixon/clixon_yang_schema_mount.h @@ -54,7 +54,6 @@ * Prototypes */ int yang_schema_mount_point(yang_stmt *y); - int yang_mount_get(yang_stmt *yu, char *xpath, yang_stmt **yspec); int yang_mount_set(yang_stmt *yu, char *xpath, yang_stmt *yspec); int xml_yang_mount_get(clicon_handle h, cxobj *x, validate_level *vl, yang_stmt **yspec); diff --git a/lib/src/clixon_datastore_write.c b/lib/src/clixon_datastore_write.c index 5b8345ea..cfc3f4e8 100644 --- a/lib/src/clixon_datastore_write.c +++ b/lib/src/clixon_datastore_write.c @@ -1160,7 +1160,7 @@ text_modify_top(clicon_handle h, * @param[out] cbret Initialized cligen buffer. On exit contains XML if retval == 0 * @retval 1 OK * @retval 0 Failed, cbret contains error xml message - * @retval -1 Error + * @retval -1 Error * The xml may contain the "operation" attribute which defines the operation. * @code * cxobj *xt; diff --git a/lib/src/clixon_event.c b/lib/src/clixon_event.c index 34f2795f..da59eda4 100644 --- a/lib/src/clixon_event.c +++ b/lib/src/clixon_event.c @@ -227,6 +227,7 @@ clixon_event_unreg_fd(int s, } /*! Call a callback function at an absolute time + * * @param[in] t Absolute (not relative!) timestamp when callback is called * @param[in] fn Function to call at time t * @param[in] arg Argument to function fn @@ -241,11 +242,9 @@ clixon_event_unreg_fd(int s, * } * @endcode * - * Note that the timestamp is an absolute timestamp, not relative. - * Note also that the callback is not periodic, you need to make a new - * registration for each period, see example above. - * Note also that the first argument to fn is a dummy, just to get the same - * signature as for file-descriptor callbacks. + * @note The timestamp is an absolute timestamp, not relative. + * @note The callback is not periodic, you need to make a new registration for each period, see example. + * @note The first argument to fn is a dummy, just to get the same signature as for file-descriptor callbacks. * @see clixon_event_reg_fd * @see clixon_event_unreg_timeout */ diff --git a/lib/src/clixon_netconf_lib.c b/lib/src/clixon_netconf_lib.c index ecb113f6..186cfa63 100644 --- a/lib/src/clixon_netconf_lib.c +++ b/lib/src/clixon_netconf_lib.c @@ -1045,7 +1045,7 @@ netconf_missing_choice_xml(cxobj **xret, if ((xerr = xml_new("rpc-error", *xret, CX_ELMNT)) == NULL) goto done; /* error-path: Path to the element with the missing choice. */ - if (xml2xpath(x, NULL, 0, &path) < 0) + if (xml2xpath(x, NULL, 0, 0, &path) < 0) goto done; if (xml_chardata_encode(&encpath, "%s", path) < 0) goto done; @@ -1407,7 +1407,7 @@ netconf_data_not_unique_xml(cxobj **xret, if (cvec_len(cvk)){ if ((xinfo = xml_new("error-info", xerr, CX_ELMNT)) == NULL) goto done; - if (xml2xpath(x, NULL, 0, &path) < 0) + if (xml2xpath(x, NULL, 0, 0, &path) < 0) goto done; if (xml_chardata_encode(&encpath, "%s", path) < 0) goto done; @@ -1465,7 +1465,7 @@ netconf_minmax_elements_xml(cxobj **xret, if ((xerr = xml_new("rpc-error", *xret, CX_ELMNT)) == NULL) goto done; if (xml_parent(xp)){ /* Dont include root, eg */ - if (xml2xpath(xp, NULL, 0, &path) < 0) + if (xml2xpath(xp, NULL, 0, 0, &path) < 0) goto done; if (xml_chardata_encode(&encpath, "%s", path) < 0) goto done; diff --git a/lib/src/clixon_path.c b/lib/src/clixon_path.c index 60c4b5cb..e9949cee 100644 --- a/lib/src/clixon_path.c +++ b/lib/src/clixon_path.c @@ -75,7 +75,6 @@ #include #include #include -#include #include #include #include @@ -97,9 +96,12 @@ #include "clixon_xml_nsctx.h" #include "clixon_xml_vec.h" #include "clixon_xml_sort.h" +#include "clixon_xpath_ctx.h" +#include "clixon_xpath.h" #include "clixon_netconf_lib.h" #include "clixon_xml_map.h" -#include "clixon_yang_module.h" +#include "clixon_yang_module.h" +#include "clixon_yang_schema_mount.h" #include "clixon_path.h" #include "clixon_api_path_parse.h" #include "clixon_instance_id_parse.h" @@ -664,6 +666,8 @@ api_path2xpath_cvv(cvec *api_path, cvec *nsc = NULL; char *val1; char *decval; + int ret; + int root; cprintf(xpath, "/"); /* Initialize namespace context */ @@ -673,6 +677,7 @@ api_path2xpath_cvv(cvec *api_path, clicon_err(OE_UNIX, errno, "cbuf_new"); goto done; } + root = 1; /* root or mountpoint */ for (i=offset; i #include #include -#include /* cligen */ #include diff --git a/lib/src/clixon_sig.c b/lib/src/clixon_sig.c index 37fe7fc2..8580bb51 100644 --- a/lib/src/clixon_sig.c +++ b/lib/src/clixon_sig.c @@ -101,7 +101,6 @@ set_signal_flags(int signo, *oldhandler = sold.sa_handler; return 0; #elif defined(HAVE_SIGVEC) - assert(0); return 0; #endif } diff --git a/lib/src/clixon_xml_bind.c b/lib/src/clixon_xml_bind.c index 17aa7157..f8848e84 100644 --- a/lib/src/clixon_xml_bind.c +++ b/lib/src/clixon_xml_bind.c @@ -49,7 +49,6 @@ #include #include #include -#include #include #include #include diff --git a/lib/src/clixon_xml_default.c b/lib/src/clixon_xml_default.c index 849d8f12..3609dd1f 100644 --- a/lib/src/clixon_xml_default.c +++ b/lib/src/clixon_xml_default.c @@ -48,7 +48,6 @@ #include #include #include -#include #include #include #include diff --git a/lib/src/clixon_xml_io.c b/lib/src/clixon_xml_io.c index 76cdb59b..106caf77 100644 --- a/lib/src/clixon_xml_io.c +++ b/lib/src/clixon_xml_io.c @@ -52,7 +52,6 @@ #include #include #include -#include /* cligen */ #include diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index f2f9f1ab..d2788028 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -314,8 +314,8 @@ xml_diff1(cxobj *x0, cxobj *x1c = NULL; /* x1 child */ yang_stmt *yc0; yang_stmt *yc1; + char *b0; char *b1; - char *b2; int eq; /* Traverse x0 and x1 in lock-step */ @@ -366,12 +366,12 @@ xml_diff1(cxobj *x0, else if (yc0 && yang_keyword_get(yc0) == Y_LEAF){ /* if x0c and x1c are leafs w bodies, then they may be changed */ - b1 = xml_body(x0c); - b2 = xml_body(x1c); - if (b1 == NULL && b2 == NULL) + b0 = xml_body(x0c); + b1 = xml_body(x1c); + if (b0 == NULL && b1 == NULL) ; - else if (b1 == NULL || b2 == NULL - || strcmp(b1, b2) != 0 + else if (b0 == NULL || b1 == NULL + || strcmp(b0, b1) != 0 ){ if (cxvec_append(x0c, changed_x0, changedlen) < 0) goto done; @@ -396,7 +396,7 @@ xml_diff1(cxobj *x0, } /*! Compute differences between two xml trees - * @param[in] yspec Yang specification + * * @param[in] x0 First XML tree * @param[in] x1 Second XML tree * @param[out] first Pointervector to XML nodes existing in only first tree @@ -406,11 +406,13 @@ xml_diff1(cxobj *x0, * @param[out] changed_x0 Pointervector to XML nodes changed orig value * @param[out] changed_x1 Pointervector to XML nodes changed wanted value * @param[out] changedlen Length of changed vector + * @retval 0 OK + * @retval -1 Error * All xml vectors should be freed after use. + * @see xml_tree_equal same algorithm but do not bother with what has changed */ int -xml_diff(yang_stmt *yspec, - cxobj *x0, +xml_diff(cxobj *x0, cxobj *x1, cxobj ***first, int *firstlen, @@ -448,6 +450,85 @@ xml_diff(yang_stmt *yspec, return retval; } +/*! Compute if two XML trees are equal or not + * + * @param[in] x0 First XML tree + * @param[in] x1 Second XML tree + * @retval 1 Not equal + * @retval 0 Equal + * @see xml_diff + */ +int +xml_tree_equal(cxobj *x0, + cxobj *x1) +{ + int retval = 1; /* Not equal */ + int eq; + yang_stmt *yc0; + yang_stmt *yc1; + char *b0; + char *b1; + cxobj *x0c = NULL; /* x0 child */ + cxobj *x1c = NULL; /* x1 child */ + + /* Traverse x0 and x1 in lock-step */ + x0c = x1c = NULL; + x0c = xml_child_each(x0, x0c, CX_ELMNT); + x1c = xml_child_each(x1, x1c, CX_ELMNT); + for (;;){ + if (x0c == NULL && x1c == NULL) + goto ok; + else if (x0c == NULL){ + goto done; + } + else if (x1c == NULL){ + goto done; + } + /* Both x0c and x1c exists, check if they are yang-equal. */ + eq = xml_cmp(x0c, x1c, 0, 0, NULL); + if (eq < 0){ + goto done; + } + else if (eq > 0){ + goto done; + } + else{ /* equal */ + /* xml-spec NULL could happen with anydata children for example, + * if so, continute compare children but without yang + */ + yc0 = xml_spec(x0c); + yc1 = xml_spec(x1c); + if (yc0 && yc1 && yc0 != yc1){ /* choice */ + goto done; + } + else + if (yc0 && yang_keyword_get(yc0) == Y_LEAF){ + /* if x0c and x1c are leafs w bodies, then they may be changed */ + b0 = xml_body(x0c); + b1 = xml_body(x1c); + if (b0 == NULL && b1 == NULL) + ; + else if (b0 == NULL || b1 == NULL + || strcmp(b0, b1) != 0 + ){ + goto done; + } + } + else { + eq = xml_tree_equal(x0c, x1c); + if (eq) + goto done; + } + } + x0c = xml_child_each(x0, x0c, CX_ELMNT); + x1c = xml_child_each(x1, x1c, CX_ELMNT); + } + ok: + retval = 0; + done: + return retval; +} + /*! Prune everything that does not pass test or have at least a child* does not * * @param[in] xt XML tree with some node marked diff --git a/lib/src/clixon_xml_nsctx.c b/lib/src/clixon_xml_nsctx.c index 5040f7ab..087f2ce2 100644 --- a/lib/src/clixon_xml_nsctx.c +++ b/lib/src/clixon_xml_nsctx.c @@ -95,6 +95,7 @@ xml_nsctx_namespace_netconf_default(clicon_handle h) } /*! Create and initialize XML namespace context + * * @param[in] prefix Namespace prefix, or NULL for default * @param[in] ns Set this namespace. If NULL create empty nsctx * @retval nsc Return namespace context in form of a cvec @@ -126,6 +127,7 @@ xml_nsctx_init(char *prefix, } /*! Free XML namespace context + * * @param[in] prefix Namespace prefix, or NULL for default * @param[in] namespace Cached namespace to set (assume non-null?) * @retval nsc Return namespace context in form of a cvec @@ -142,6 +144,7 @@ xml_nsctx_free(cvec *nsc) } /*! Get namespace given prefix (or NULL for default) from namespace context + * * @param[in] cvv Namespace context * @param[in] prefix Namespace prefix, or NULL for default * @retval ns Cached namespace @@ -159,11 +162,12 @@ xml_nsctx_get(cvec *cvv, } /*! Reverse get prefix given namespace + * * @param[in] cvv Namespace context * @param[in] ns Namespace * @param[out] prefix Prefix (direct pointer) - * @retval 0 No prefix found * @retval 1 Prefix found + * @retval 0 No prefix found * @note NULL is a valid prefix (default) */ int @@ -188,6 +192,7 @@ xml_nsctx_get_prefix(cvec *cvv, } /*! Set or replace namespace in namespace context + * * @param[in] cvv Namespace context * @param[in] prefix Namespace prefix, or NULL for default * @param[in] ns Cached namespace to set (assume non-null?) @@ -261,11 +266,12 @@ xml_nsctx_node1(cxobj *xn, } /*! Create and initialize XML namespace from XML node context + * * Fully explore all prefix:namespace pairs from context of one node * @param[in] xn XML node * @param[out] ncp XML namespace context * @retval 0 OK - * @retval -1 Error + * @retval -1 Error * @code * cxobj *x; // must initialize * cvec *nsc = NULL; @@ -297,12 +303,13 @@ xml_nsctx_node(cxobj *xn, } /*! Create and initialize XML namespace context from Yang node (non-spec) + * * Primary use is Yang path statements, eg leafrefs and others * Fully explore all prefix:namespace pairs from context of one node * @param[in] yn Yang statement in module tree (or module itself) * @param[out] ncp XML namespace context * @retval 0 OK - * @retval -1 Error + * @retval -1 Error * @code * yang_stmt *y; // must initialize * cvec *nsc = NULL; @@ -394,7 +401,7 @@ xml_nsctx_yang(yang_stmt *yn, * Also add netconf base namespace: nc , urn:ietf:params:xml:ns:netconf:base:1.0 * Fully explore all prefix:namespace pairs of all yang modules * @param[in] yspec Yang spec - * @param[out] ncp XML namespace context + * @param[out] ncp XML namespace context (create if does not exist) * @retval 0 OK * @retval -1 Error * @code @@ -416,7 +423,9 @@ xml_nsctx_yangspec(yang_stmt *yspec, yang_stmt *yprefix; yang_stmt *ynamespace; - if ((nc = cvec_new(0)) == NULL){ + if (ncp && *ncp) + nc = *ncp; + else if ((nc = cvec_new(0)) == NULL){ clicon_err(OE_XML, errno, "cvec_new"); goto done; } @@ -443,6 +452,7 @@ xml_nsctx_yangspec(yang_stmt *yspec, } /*! Print a namespace context to a cbuf using xmlns notation + * * @param[in] *cb CLIgen buf written to * @param[in] *nsc Namespace context * @retval 0 OK @@ -531,6 +541,7 @@ xml2ns(cxobj *x, } /*! Recursively check prefix / namespaces (and populate ns cache) + * * @retval 1 OK * @retval 0 (Some) prefix not found * @retval -1 Error @@ -563,6 +574,7 @@ xml2ns_recurse(cxobj *xt) } /*! Add a namespace attribute to an XML node, either default or specific prefix + * * @param[in] x XML tree * @param[in] prefix prefix/ns localname. If NULL then set default xmlns * @param[in] ns URI namespace (or NULL). Will be copied @@ -636,12 +648,13 @@ xmlns_set_all(cxobj *x, } /*! Get prefix of given namespace recursively + * * @param[in] xn XML node * @param[in] namespace Namespace * @param[out] prefixp Pointer to prefix if found - * @retval -1 Error - * @retval 0 No namespace found * @retval 1 Namespace found, prefix returned in prefixp + * @retval 0 No namespace found + * @retval -1 Error * @note a namespace can have two or more prefixes, this just returns the first * @see xml2prefixexists to check a specific pair */ @@ -700,8 +713,8 @@ xml2prefix(cxobj *xn, goto done; } - /*! Add prefix:namespace pair to xml node, set cache, etc + * * @param[in] x XML node whose namespace should change * @param[in] xp XML node where namespace attribute should be declared (can be same) * @param[in] prefix1 Use this prefix diff --git a/lib/src/clixon_xml_vec.c b/lib/src/clixon_xml_vec.c index d640ce97..150a4dbf 100644 --- a/lib/src/clixon_xml_vec.c +++ b/lib/src/clixon_xml_vec.c @@ -48,7 +48,6 @@ #include #include #include -#include /* cligen */ #include diff --git a/lib/src/clixon_xpath.c b/lib/src/clixon_xpath.c index c794c332..e84103ad 100644 --- a/lib/src/clixon_xpath.c +++ b/lib/src/clixon_xpath.c @@ -90,12 +90,21 @@ #include "clixon_yang.h" #include "clixon_xml.h" #include "clixon_xml_nsctx.h" +#include "clixon_netconf_lib.h" #include "clixon_yang_module.h" +#include "clixon_yang_schema_mount.h" #include "clixon_xpath_ctx.h" #include "clixon_xpath.h" #include "clixon_xpath_parse.h" #include "clixon_xpath_eval.h" +/* Use apostrophe(') in xpath literals, eg a/[x='foo'], not double-quotes(") + * If not set, use ": a/[x="foo"] + * Advantage with ' is it works well in clispecs, " must be escaped + * @see https://www.w3.org/TR/xpath-10/#NT-Literal + */ +#define XPATH_USE_APOSTROPHE + /* * Variables */ @@ -174,7 +183,10 @@ xpath_tree_int2str(int nodetype) return (char*)clicon_int2str(xpath_tree_map, nodetype); } -/*! Print XPATH parse tree */ +/*! Print XPATH parse tree + * + * @note uses "" instead of '' in printing literals, rule [29] in https://www.w3.org/TR/xpath-10 + */ static int xpath_tree_print0(cbuf *cb, xpath_tree *xs, @@ -243,6 +255,8 @@ xpath_tree_print(FILE *f, /*! Create an xpath string from an xpath tree, ie "unparsing" * @param[in] xs XPATH tree * @param[out] xpath XPath string as CLIgen buf + * @retval 0 OK + * @retval -1 Error * @see xpath_tree_print */ int @@ -279,7 +293,11 @@ xpath_tree2cbuf(xpath_tree *xs, cprintf(xcb, "%s", xs->xs_strnr?xs->xs_strnr:"0"); break; case XP_PRIME_STR: +#ifdef XPATH_USE_APOSTROPHE cprintf(xcb, "'%s'", xs->xs_s0?xs->xs_s0:""); +#else + cprintf(xcb, "\"%s\"", xs->xs_s0?xs->xs_s0:""); +#endif break; case XP_PRIME_FN: if (xs->xs_s0) @@ -951,28 +969,31 @@ xpath_vec_bool(cxobj *xcur, * @retval 1 OK with nsc1 containing the transformed nsc * @retval 0 XPath failure with reason set to why * @retval -1 Fatal Error + * XXX Detects mountpoint but is not mountpoint aware, just copies prefixes */ static int -traverse_canonical(xpath_tree *xs, - yang_stmt *yspec, - cvec *nsc0, - cvec *nsc1, - cbuf **reason) +xpath_traverse_canonical(xpath_tree *xs, + yang_stmt *yspec, + cvec *nsc0, + cvec *nsc1, + cbuf **reason) { int retval = -1; char *prefix0; - char *prefix1; + char *prefix1 = NULL; char *namespace; yang_stmt *ymod; cbuf *cb = NULL; int ret; - + // char *name; + switch (xs->xs_type){ case XP_NODE: /* s0 is namespace prefix, s1 is name */ /* Nodetest = * needs no prefix */ if (xs->xs_s1 && strcmp(xs->xs_s1, "*") == 0) break; prefix0 = xs->xs_s0; + // name = xs->xs_s1; if ((namespace = xml_nsctx_get(nsc0, prefix0)) == NULL){ if ((cb = cbuf_new()) == NULL){ clicon_err(OE_UNIX, errno, "cbuf_new"); @@ -981,19 +1002,23 @@ traverse_canonical(xpath_tree *xs, cprintf(cb, "No namespace found for prefix: %s", prefix0); if (reason) *reason = cb; - goto failed; + goto fail; } if ((ymod = yang_find_module_by_namespace(yspec, namespace)) == NULL){ +#ifndef XPATH_CANONICAL_SKIP_CHECK if ((cb = cbuf_new()) == NULL){ clicon_err(OE_UNIX, errno, "cbuf_new"); goto done; } - cprintf(cb, "No modules found for namespace: %s", namespace); + cprintf(cb, "No yang found for namespace: %s", namespace); if (reason) *reason = cb; - goto failed; + goto fail; +#endif } - if ((prefix1 = yang_find_myprefix(ymod)) == NULL){ + if (ymod == NULL) + prefix1 = prefix0; + else if ((prefix1 = yang_find_myprefix(ymod)) == NULL){ if ((cb = cbuf_new()) == NULL){ clicon_err(OE_UNIX, errno, "cbuf_new"); goto done; @@ -1001,7 +1026,7 @@ traverse_canonical(xpath_tree *xs, cprintf(cb, "No prefix found in module: %s", yang_argument_get(ymod)); if (reason) *reason = cb; - goto failed; + goto fail; } if (xml_nsctx_get(nsc1, prefix1) == NULL) if (xml_nsctx_add(nsc1, prefix1, namespace) < 0) @@ -1019,21 +1044,21 @@ traverse_canonical(xpath_tree *xs, break; } if (xs->xs_c0){ - if ((ret = traverse_canonical(xs->xs_c0, yspec, nsc0, nsc1, reason)) < 0) + if ((ret = xpath_traverse_canonical(xs->xs_c0, yspec, nsc0, nsc1, reason)) < 0) goto done; if (ret == 0) - goto failed; + goto fail; } if (xs->xs_c1){ - if ((ret = traverse_canonical(xs->xs_c1, yspec, nsc0, nsc1, reason)) < 0) + if ((ret = xpath_traverse_canonical(xs->xs_c1, yspec, nsc0, nsc1, reason)) < 0) goto done; if (ret == 0) - goto failed; + goto fail; } retval = 1; done: return retval; - failed: + fail: retval = 0; goto done; } @@ -1092,10 +1117,10 @@ xpath2canonical(const char *xpath0, /* Traverse tree to find prefixes, transform them to canonical form and * create a canonical network namespace */ - if ((ret = traverse_canonical(xpt, yspec, nsc0, nsc1, cbreason)) < 0) + if ((ret = xpath_traverse_canonical(xpt, yspec, nsc0, nsc1, cbreason)) < 0) goto done; if (ret == 0) - goto failed; + goto fail; /* Print tree with new prefixes */ if ((xcb = cbuf_new()) == NULL){ clicon_err(OE_XML, errno, "cbuf_new"); @@ -1122,7 +1147,7 @@ xpath2canonical(const char *xpath0, if (xpt) xpath_tree_free(xpt); return retval; - failed: + fail: retval = 0; goto done; } @@ -1166,10 +1191,11 @@ xpath_count(cxobj *xcur, } /*! Given an XML node, build an xpath recursively to root, internal function + * * @param[in] x XML object * @param[in] nsc Namespace context * @param[in] spec If set, recursively continue only to root without spec - * @param[out] cb XPath string as cbuf. + * @param[in] apostrophe If set, use apostrophe in xpath literals, eg a/[x='foo'], not double-quotes(") * @param[out] cb XPath string as cbuf. * @retval 0 OK * @retval -1 Error. eg XML malformed */ @@ -1177,6 +1203,7 @@ static int xml2xpath1(cxobj *x, cvec *nsc, int spec, + int apostrophe, cbuf *cb) { int retval = -1; @@ -1196,7 +1223,7 @@ xml2xpath1(cxobj *x, goto ok; if (spec && xml_spec(x) == NULL) goto ok; - if (xml2xpath1(xp, nsc, spec, cb) < 0) + if (xml2xpath1(xp, nsc, spec, apostrophe, cb) < 0) goto done; if (nsc){ if (xml2ns(x, xml_prefix(x), &namespace) < 0) @@ -1219,10 +1246,18 @@ xml2xpath1(cxobj *x, keyword = yang_keyword_get(y); switch (keyword){ case Y_LEAF_LIST: - if ((b = xml_body(x)) != NULL) - cprintf(cb, "[.=\"%s\"]", b); - else - cprintf(cb, "[.=\"\"]"); + if (apostrophe){ + if ((b = xml_body(x)) != NULL) + cprintf(cb, "[.='%s']", b); + else + cprintf(cb, "[.='']"); + } + else{ + if ((b = xml_body(x)) != NULL) + cprintf(cb, "[.=\"%s\"]", b); + else + cprintf(cb, "[.=\"\"]"); + } break; case Y_LIST: cvk = yang_cvec_get(y); @@ -1234,10 +1269,17 @@ xml2xpath1(cxobj *x, if ((xb = xml_find(x, keyname)) == NULL) goto done; b = xml_body(xb); +#if 1 + if (b==NULL || strlen(b)==0) + continue; +#endif cprintf(cb, "["); if (prefix) cprintf(cb, "%s:", prefix); - cprintf(cb, "%s=\"%s\"]", keyname, b?b:""); + if (apostrophe) + cprintf(cb, "%s='%s']", keyname, b?b:""); + else + cprintf(cb, "%s=\"%s\"]", keyname, b?b:""); } break; default: @@ -1260,6 +1302,7 @@ xml2xpath1(cxobj *x, * @param[in] x XML object * @param[in] nsc Namespace context * @param[in] spec If set, recursively continue only to root without spec (added in 6.1 for yang mount) + * @param[in] apostrophe If set, use apostrophe in xpath literals, eg a/[x='foo'], not double-quotes(") * @param[out] xpath Malloced xpath string. Need to free() after use * @retval 0 OK * @retval -1 Error. (eg XML malformed) @@ -1267,16 +1310,18 @@ xml2xpath1(cxobj *x, * char *xpath = NULL; * cxobj *x; * ... x is inside an xml tree ... - * if (xml2xpath(x, nsc, 0, &xpath) < 0) + * if (xml2xpath(x, nsc, 0, 0, &xpath) < 0) * err; * free(xpath); * @endcode * @note x needs to be bound to YANG, see eg xml_bind_yang() + * @note namespaces of xpath is not well-defined, follows xml, should be canonical? */ int xml2xpath(cxobj *x, cvec *nsc, int spec, + int apostrophe, char **xpathp) { int retval = -1; @@ -1287,7 +1332,7 @@ xml2xpath(cxobj *x, clicon_err(OE_XML, errno, "cbuf_new"); goto done; } - if (xml2xpath1(x, nsc, spec, cb) < 0) + if (xml2xpath1(x, nsc, spec, apostrophe, cb) < 0) goto done; /* XXX: see xpath in test statement,.. */ xpath = cbuf_get(cb); @@ -1304,3 +1349,167 @@ xml2xpath(cxobj *x, cbuf_free(cb); return retval; } + +/*! Create xml tree from xpath as xpath-tree + * + * @param[in] xs Parsed xpath - xpath_tree + * @param[in] nsc Namespace context for xpath + * @param[in] x0 XML tree so far + * @param[out] xbotp Resulting xml tree (end of xpath) (optional) + * @param[out] xerr Netconf error message (if retval=0) + * @retval 1 OK + * @retval 0 Invalid xpath + * @retval -1 Fatal error, clicon_err called + * @see xpath_traverse_canonical + */ +static int +xpath2xml_traverse(xpath_tree *xs, + cvec *nsc, + cxobj *x0, + yang_stmt *y0, + cxobj **xbotp, + yang_stmt **ybotp, + cxobj **xerr) +{ + int retval = -1; + int ret; + char *name; + char *prefix; + char *namespace; + char *ns = NULL; + cbuf *cberr = NULL; + cxobj *xc; + yang_stmt *ymod; + yang_stmt *yc; + + *xbotp = x0; + *ybotp = y0; + switch (xs->xs_type){ + case XP_NODE: /* s0 is namespace prefix, s1 is name */ + prefix = xs->xs_s0; + name = xs->xs_s1; + if ((namespace = xml_nsctx_get(nsc, prefix)) == NULL){ + if ((cberr = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(cberr, "No namespace found for prefix: %s", prefix); + if (xerr && + netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0) + goto done; + goto fail; + } + if (yang_keyword_get(y0) == Y_SPEC){ /* top-node */ + if ((ymod = yang_find_module_by_namespace(y0, namespace)) == NULL){ + cprintf(cberr, "No such yang module namespace"); + if (xerr && + netconf_unknown_element_xml(xerr, "application", namespace, cbuf_get(cberr)) < 0) + goto done; + goto fail; + } + y0 = ymod; + } + if ((yc = yang_find_datanode(y0, name)) == NULL){ + if (xerr && + netconf_unknown_element_xml(xerr, "application", name, "Unknown element") < 0) + goto done; + goto fail; + } + if ((xc = xml_new(name, x0, CX_ELMNT)) == NULL) + goto done; + if (xml2ns(x0, prefix, &ns) < 0) + goto done; + if (ns == NULL) + if (xmlns_set(xc, NULL, namespace) < 0) + goto done; + *xbotp = xc; + *ybotp = yc; + break; + default: + break; + } + if (xs->xs_c0){ + if ((ret = xpath2xml_traverse(xs->xs_c0, nsc, x0, y0, xbotp, ybotp, xerr)) < 0) + goto done; + if (ret == 0){ + goto fail; + } + } + if (xs->xs_c1){ + x0 = *xbotp; + y0 = *ybotp; + if ((ret = xpath2xml_traverse(xs->xs_c1, nsc, x0, y0, xbotp, ybotp, xerr)) < 0) + goto done; + if (ret == 0){ + goto fail; + } + if (xs->xs_type == XP_STEP){ + *xbotp = x0; + *ybotp = y0; + } + } + retval = 1; + done: + clicon_debug(CLIXON_DBG_DETAIL, "%s retval:%d", __FUNCTION__, retval); + return retval; + fail: + retval = 0; + goto done; +} + +/*! Create xml tree from restricted xpath + * + * Create an XML tree from "scratch" using xpath. + * @param[in] xpath (Absolute) XPath + * @param[in] nsc Namespace context for xpath + * @param[in,out] xtop Incoming XML tree + * @param[in] yspec Yang spec for xtop + * @param[out] xbotp Resulting xml tree (end of xpath) (optional) + * @param[out] ybotp Yang spec matching xpathp + * @param[out] xerr Netconf error message (if retval=0) + * @retval 1 OK + * @retval 0 Invalid xpath + * @retval -1 Fatal error, clicon_err called + * @see api_path2xml + * @see xml2xpath + * @note xpath is restricted to absolute paths, and simple expressions, eg as "node-identifier" + */ +int +xpath2xml(char *xpath, + cvec *nsc, + cxobj *xtop, + yang_stmt *ytop, + cxobj **xbotp, + yang_stmt **ybotp, + cxobj **xerr) +{ + int retval = -1; + cbuf *cberr = NULL; + xpath_tree *xpt = NULL; + + clicon_debug(CLIXON_DBG_DETAIL, "%s xpath:%s", __FUNCTION__, xpath); + if ((cberr = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + if (*xpath != '/'){ + cprintf(cberr, "Invalid absolute xpath: %s (must start with '/')", xpath); + if (xerr && netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0) + goto done; + goto fail; + } + /* Parse input xpath into an xpath-tree */ + if (xpath_parse(xpath, &xpt) < 0) + goto done; + if ((retval = xpath2xml_traverse(xpt, nsc, xtop, ytop, xbotp, ybotp, xerr)) < 1) + goto done; + done: + if (xpt) + xpath_tree_free(xpt); + if (cberr) + cbuf_free(cberr); + return retval; + fail: + retval = 0; + goto done; +} diff --git a/lib/src/clixon_xpath_eval.c b/lib/src/clixon_xpath_eval.c index fa4c26f0..de56cc71 100644 --- a/lib/src/clixon_xpath_eval.c +++ b/lib/src/clixon_xpath_eval.c @@ -65,7 +65,6 @@ #include #include #include -#include #include #include #include /* NaN */ diff --git a/lib/src/clixon_xpath_function.c b/lib/src/clixon_xpath_function.c index 15174c98..5d718496 100644 --- a/lib/src/clixon_xpath_function.c +++ b/lib/src/clixon_xpath_function.c @@ -47,7 +47,6 @@ #include #include #include -#include #include #include #include /* NaN */ diff --git a/lib/src/clixon_xpath_optimize.c b/lib/src/clixon_xpath_optimize.c index e553d3e2..a9647c66 100644 --- a/lib/src/clixon_xpath_optimize.c +++ b/lib/src/clixon_xpath_optimize.c @@ -47,7 +47,6 @@ #include #include #include -#include #include #include #include /* NaN */ diff --git a/lib/src/clixon_yang_internal.h b/lib/src/clixon_yang_internal.h index ff809ccb..a92328fc 100644 --- a/lib/src/clixon_yang_internal.h +++ b/lib/src/clixon_yang_internal.h @@ -88,6 +88,7 @@ struct yang_stmt{ fraction-digits for fraction-digits revision (uint32) unknown-stmt (optional argument) + spec: mount-point xpath */ cvec *ys_cvec; /* List of stmt-specific variables Y_RANGE: range_min, range_max diff --git a/lib/src/clixon_yang_schema_mount.c b/lib/src/clixon_yang_schema_mount.c index 7c1edf2c..08799c39 100644 --- a/lib/src/clixon_yang_schema_mount.c +++ b/lib/src/clixon_yang_schema_mount.c @@ -45,6 +45,10 @@ * 4. yang_schema_mount_statedata(): from get_common/get_statedata to retrieve system state * 5. yang_schema_yanglib_parse_mount(): from xml_bind_yang to parse and mount * 6. yang_schema_get_child(): from xmldb_put/text_modify when adding new XML nodes + * + * Note: the xpath used as key in yang unknown cvec is "canonical" in the sense: + * - it uses prefixes of the yang spec of relevance + * - it uses '' not "" in prefixes (eg a[x='foo']. The reason is '' is easier printed in clispecs */ #ifdef HAVE_CONFIG_H @@ -133,8 +137,8 @@ yang_schema_mount_point(yang_stmt *y) /*! Get yangspec mount-point * - * @param[in] yu Yang unknown node to save the yspecs - * @param[in] xpath Key for yspec on yu + * @param[in] yu Yang unknown node to save the yspecs + * @param[in] xpath Key for yspec on yu * @param[out] yspec YANG stmt spec * @retval 0 OK * @retval -1 Error @@ -157,9 +161,9 @@ yang_mount_get(yang_stmt *yu, /*! Set yangspec mount-point on yang unknwon node * - * Stored in a separate structure (not in XML config tree) + * Mount-points are stored in unknown yang cvec * @param[in] yu Yang unknown node to save the yspecs - * @param[in] xpath Key for yspec on yu + * @param[in] xpath Key for yspec on yu, in canonical form * @param[in] yspec Yangspec for this mount-point (consumed) * @retval 0 OK * @retval -1 Error @@ -173,6 +177,7 @@ yang_mount_set(yang_stmt *yu, yang_stmt *yspec0; cvec *cvv; cg_var *cv; + cg_var *cv2; if ((cvv = yang_cvec_get(yu)) != NULL && (cv = cvec_find(cvv, xpath)) != NULL && @@ -184,6 +189,16 @@ yang_mount_set(yang_stmt *yu, } else if ((cv = yang_cvec_add(yu, CGV_VOID, xpath)) == NULL) goto done; + if ((cv2 = cv_new(CGV_STRING)) == NULL){ + clicon_err(OE_YANG, errno, "cv_new"); + goto done; + } + if (cv_string_set(cv2, xpath) == NULL){ + clicon_err(OE_UNIX, errno, "cv_string_set"); + goto done; + } + /* tag yspec with key/xpath */ + yang_cv_set(yspec, cv2); cv_void_set(cv, yspec); retval = 0; done: @@ -224,7 +239,7 @@ xml_yang_mount_get(clicon_handle h, // XXX hardcoded prefix: yangmnt if ((yu = yang_find(y, Y_UNKNOWN, "yangmnt:mount-point")) == NULL) goto ok; - if (xml2xpath(xt, NULL, 1, &xpath) < 0) + if (xml2xpath(xt, NULL, 1, 0, &xpath) < 0) goto done; if (yang_mount_get(yu, xpath, yspec) < 0) goto done; @@ -261,7 +276,7 @@ xml_yang_mount_set(cxobj *x, (yu = yang_find(y, Y_UNKNOWN, "yangmnt:mount-point")) == NULL){ goto done; } - if (xml2xpath(x, NULL, 1, &xpath) < 0) + if (xml2xpath(x, NULL, 1, 0, &xpath) < 0) goto done; if (yang_mount_set(yu, xpath, yspec) < 0) goto done; diff --git a/lib/src/clixon_yang_type.c b/lib/src/clixon_yang_type.c index 47e2da93..0a206c20 100644 --- a/lib/src/clixon_yang_type.c +++ b/lib/src/clixon_yang_type.c @@ -76,7 +76,6 @@ #include #include #include -#include #include #include #include diff --git a/test/test_xpath_canonical.sh b/test/test_xpath_canonical.sh index dad70c6a..7735fa00 100755 --- a/test/test_xpath_canonical.sh +++ b/test/test_xpath_canonical.sh @@ -59,8 +59,9 @@ expectpart "$($clixon_util_xpath -c -y $ydir -p "//x[.='42']" -n null:urn:exampl new "xpath canonical form (no default should fail)" expectpart "$($clixon_util_xpath -c -y $ydir -p /x/j:y -n i:urn:example:a -n j:urn:example:b 2>&1)" 0 "/x/j:y: No namespace found for prefix" -new "xpath canonical form (wrong namespace should fail)" -expectpart "$($clixon_util_xpath -c -y $ydir -p /i:x/j:y -n i:urn:example:c -n j:urn:example:b 2>&1)" 0 "/i:x/j:y: No modules found for namespace" +# comment as long as XPATH_CANONICAL_SKIP_CHECK +#new "xpath canonical form (wrong namespace should fail)" +#expectpart "$($clixon_util_xpath -c -y $ydir -p /i:x/j:y -n i:urn:example:c -n j:urn:example:b 2>&1)" 0 "/i:x/j:y: No yang found for namespace" rm -rf $dir diff --git a/util/clixon_netconf_ssh_callhome_client.c b/util/clixon_netconf_ssh_callhome_client.c index 816bf9af..54fdcd20 100644 --- a/util/clixon_netconf_ssh_callhome_client.c +++ b/util/clixon_netconf_ssh_callhome_client.c @@ -72,7 +72,6 @@ Example sshd-config (-c option):n #include #include #include -#include #include #include #include diff --git a/util/clixon_util_xpath.c b/util/clixon_util_xpath.c index da88f01b..44ddcb04 100644 --- a/util/clixon_util_xpath.c +++ b/util/clixon_util_xpath.c @@ -383,7 +383,7 @@ main(int argc, char *xpathi = NULL; for (i=0; ixc_size; i++){ xi = xc->xc_nodeset[i]; - if (xml2xpath(xi, nsc, 0, &xpathi) < 0) + if (xml2xpath(xi, nsc, 0, 0, &xpathi) < 0) goto done; fprintf(stdout, "Inverse: %s\n", xpathi); if (xpathi){