From 0d4263e32456c9e18c31514079bafdde1b45c031 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Fri, 17 Apr 2020 15:47:37 +0200 Subject: [PATCH] * `xml_merge()` changed to use 3-value return: 1:OK, 0:Yang failed, -1: Error * `clixon_netconf_error(category, xerr, msg, arg)` removed first argument -> `clixon_netconf_error(xerr, msg, arg)` * Renamed utility function `clixon_util_insert()` to `clixon_util_xml_mod()` and added merge functionality. * Fixed: Insertion of subtree leaf nodes were not made in the crrect place, always ended up last regardless of yang spec (if ordered-by system). --- CHANGELOG.md | 7 + apps/cli/cli_common.c | 8 +- apps/cli/cli_show.c | 10 +- example/main/example_cli.c | 2 +- lib/clixon/clixon_netconf_lib.h | 10 +- lib/src/clixon_err.c | 2 +- lib/src/clixon_netconf_lib.c | 8 +- lib/src/clixon_options.c | 2 +- lib/src/clixon_proto_client.c | 30 ++-- lib/src/clixon_xml.c | 3 +- lib/src/clixon_xml_map.c | 54 +++--- test/test_insert.sh | 16 +- util/Makefile.in | 4 +- util/clixon_util_insert.c | 202 ----------------------- util/clixon_util_xml.c | 8 +- util/clixon_util_xml_mod.c | 284 ++++++++++++++++++++++++++++++++ 16 files changed, 376 insertions(+), 274 deletions(-) delete mode 100644 util/clixon_util_insert.c create mode 100644 util/clixon_util_xml_mod.c diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c649c46..2931cd94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,14 +36,21 @@ Expected: May 2020 ### C-API changes on existing features (you may need to change your plugin C-code) +* `xml_merge()` changed to use 3-value return: 1:OK, 0:Yang failed, -1: Error +* `clixon_netconf_error(category, xerr, msg, arg)` removed first argument -> `clixon_netconf_error(xerr, msg, arg)` * CLI * `clicon_parse()`: Changed signature due to new cligen error and result handling: * Removed: `cli_nomatch()` ### Minor changes +* Renamed utility function `clixon_util_insert()` to `clixon_util_xml_mod()` and added merge functionality. * Sanity check of duplicates prefixes in Yang modules and submodules as defined in RFC 7950 Sec 7.1.4 +### Corrected Bugs + +* Fixed: Insertion of subtree leaf nodes were not made in the crrect place, always ended up last regardless of yang spec (if ordered-by system). + ## 4.4.0 5 April 2020 diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c index c69eb2c8..42f7d983 100644 --- a/apps/cli/cli_common.c +++ b/apps/cli/cli_common.c @@ -714,13 +714,13 @@ compare_dbs(clicon_handle h, if (clicon_rpc_get_config(h, NULL, "running", "/", NULL, &xc1) < 0) goto done; if ((xerr = xpath_first(xc1, NULL, "/rpc-error")) != NULL){ - clixon_netconf_error(OE_NETCONF, xerr, "Get configuration", NULL); + clixon_netconf_error(xerr, "Get configuration", NULL); goto done; } if (clicon_rpc_get_config(h, NULL, "candidate", "/", NULL, &xc2) < 0) goto done; if ((xerr = xpath_first(xc2, NULL, "/rpc-error")) != NULL){ - clixon_netconf_error(OE_NETCONF, xerr, "Get configuration", NULL); + clixon_netconf_error(xerr, "Get configuration", NULL); goto done; } if (compare_xmls(xc1, xc2, astext) < 0) /* astext? */ @@ -884,7 +884,7 @@ save_config_file(clicon_handle h, goto done; } if ((xerr = xpath_first(xt, NULL, "/rpc-error")) != NULL){ - clixon_netconf_error(OE_NETCONF, xerr, "Get configuration", NULL); + clixon_netconf_error(xerr, "Get configuration", NULL); goto done; } /* get-config returns a tree. Save as tree so it can be used @@ -1230,7 +1230,7 @@ cli_copy_config(clicon_handle h, if (clicon_rpc_get_config(h, NULL, db, cbuf_get(cb), nsc, &x1) < 0) goto done; if ((xerr = xpath_first(x1, NULL, "/rpc-error")) != NULL){ - clixon_netconf_error(OE_NETCONF, xerr, "Get configuration", NULL); + clixon_netconf_error(xerr, "Get configuration", NULL); goto done; } diff --git a/apps/cli/cli_show.c b/apps/cli/cli_show.c index a596bb5f..bd761e49 100644 --- a/apps/cli/cli_show.c +++ b/apps/cli/cli_show.c @@ -159,7 +159,7 @@ expand_dbvar(void *h, if (clicon_rpc_get_config(h, NULL, dbstr, xpath, nsc, &xt) < 0) /* XXX */ goto done; if ((xe = xpath_first(xt, NULL, "/rpc-error")) != NULL){ - clixon_netconf_error(OE_NETCONF, xe, "Get configuration", NULL); + clixon_netconf_error(xe, "Get configuration", NULL); goto ok; } xcur = xt; /* default top-of-tree */ @@ -176,7 +176,7 @@ expand_dbvar(void *h, if ((ret = api_path2xml(api_path, yspec, xtop, YC_DATANODE, 0, &xbot, &y, &xerr)) < 0) goto done; if (ret == 0){ - clixon_netconf_error(OE_NETCONF, xerr, "Expand datastore symbol", NULL); + clixon_netconf_error(xerr, "Expand datastore symbol", NULL); goto done; } } @@ -492,7 +492,7 @@ cli_show_config1(clicon_handle h, goto done; } if ((xerr = xpath_first(xt, NULL, "/rpc-error")) != NULL){ - clixon_netconf_error(OE_NETCONF, xerr, "Get configuration", NULL); + clixon_netconf_error(xerr, "Get configuration", NULL); goto done; } /* Print configuration according to format */ @@ -633,7 +633,7 @@ show_conf_xpath(clicon_handle h, if (clicon_rpc_get_config(h, NULL, str, xpath, nsc, &xt) < 0) goto done; if ((xerr = xpath_first(xt, NULL, "/rpc-error")) != NULL){ - clixon_netconf_error(OE_NETCONF, xerr, "Get configuration", NULL); + clixon_netconf_error(xerr, "Get configuration", NULL); goto done; } @@ -735,7 +735,7 @@ cli_show_auto1(clicon_handle h, goto done; } if ((xerr = xpath_first(xt, NULL, "/rpc-error")) != NULL){ - clixon_netconf_error(OE_NETCONF, xerr, "Get configuration", NULL); + clixon_netconf_error(xerr, "Get configuration", NULL); goto done; } if ((xp = xpath_first(xt, nsc, "%s", xpath)) != NULL) diff --git a/example/main/example_cli.c b/example/main/example_cli.c index 078aff06..714d1174 100644 --- a/example/main/example_cli.c +++ b/example/main/example_cli.c @@ -113,7 +113,7 @@ example_client_rpc(clicon_handle h, if (clicon_rpc_netconf_xml(h, xrpc, &xret, NULL) < 0) goto done; if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){ - clixon_netconf_error(OE_NETCONF, xerr, "Get configuration", NULL); + clixon_netconf_error(xerr, "Get configuration", NULL); goto done; } /* Print result */ diff --git a/lib/clixon/clixon_netconf_lib.h b/lib/clixon/clixon_netconf_lib.h index dc319ea3..7ec3fe37 100644 --- a/lib/clixon/clixon_netconf_lib.h +++ b/lib/clixon/clixon_netconf_lib.h @@ -57,7 +57,12 @@ typedef enum netconf_content netconf_content; /* * Macros */ -#define clixon_netconf_error(c, x, f, a) clixon_netconf_error_fn(__FUNCTION__, __LINE__, (c), (x), (f), (a)) +/*! Generate textual error log from Netconf error message + * @param[in] xerr Netconf error xml tree on the form: + * @param[in] format Format string + * @param[in] arg String argument to format (optional) + */ +#define clixon_netconf_error(x, f, a) clixon_netconf_error_fn(__FUNCTION__, __LINE__, (x), (f), (a)) /* * Prototypes @@ -103,7 +108,6 @@ const char *netconf_content_int2str(netconf_content nr); int netconf_hello_server(clicon_handle h, cbuf *cb, uint32_t session_id); int netconf_hello_req(clicon_handle h, cbuf *cb); -int clicon_err_fn(const char *fn, const int line, int level, int err, char *format, ...); -int clixon_netconf_error_fn(const char *fn, const int line, int category, cxobj *xerr, const char *fmt, const char *arg); +int clixon_netconf_error_fn(const char *fn, const int line, cxobj *xerr, const char *fmt, const char *arg); #endif /* _CLIXON_NETCONF_LIB_H */ diff --git a/lib/src/clixon_err.c b/lib/src/clixon_err.c index 37c57435..3eff10c2 100644 --- a/lib/src/clixon_err.c +++ b/lib/src/clixon_err.c @@ -140,7 +140,7 @@ clicon_err_reset(void) * * @param[in] fn Inline function name (when called from clicon_err() macro) * @param[in] line Inline file line number (when called from clicon_err() macro) - * @param[in] category See enum clicon_err + * @param[in] category Clixon error category, See enum clicon_err * @param[in] errno Error number, typically errno * @param[in] reason Error string, format with argv * @see clicon_err_reser Resetting the global error variables. diff --git a/lib/src/clixon_netconf_lib.c b/lib/src/clixon_netconf_lib.c index 02b9cefb..dc604a89 100644 --- a/lib/src/clixon_netconf_lib.c +++ b/lib/src/clixon_netconf_lib.c @@ -1503,13 +1503,12 @@ netconf_hello_req(clicon_handle h, return retval; } -/*! Generate clicon error from Netconf error message +/*! Generate textual error log from Netconf error message * * Get a text error message from netconf error message and generate error on the form: * : "": or : - * @param[in] fn Inline function name (when called from clicon_err() macro) - * @param[in] line Inline file line number (when called from clicon_err() macro) - * @param[in] err Error number, typically errno + * @param[in] fn Inline function name (when called from clicon_err() macro) + * @param[in] line Inline file line number (when called from clicon_err() macro) * @param[in] xerr Netconf error xml tree on the form: * @param[in] format Format string * @param[in] arg String argument to format (optional) @@ -1517,7 +1516,6 @@ netconf_hello_req(clicon_handle h, int clixon_netconf_error_fn(const char *fn, const int line, - int category, cxobj *xerr, const char *msg, const char *arg) diff --git a/lib/src/clixon_options.c b/lib/src/clixon_options.c index 3c50e11b..be0115c6 100644 --- a/lib/src/clixon_options.c +++ b/lib/src/clixon_options.c @@ -246,7 +246,7 @@ parse_configfile(clicon_handle h, if (netconf_err2cb(xerr, cbret) < 0) goto done; /* Here one could make it more relaxing to not quit on unrecognized option? */ - clixon_netconf_error(OE_CFG, xerr, NULL, NULL); + clixon_netconf_error(xerr, NULL, NULL); goto done; } if (xml_child_nr(xt)==1 && xml_child_nr_type(xt, CX_BODY)==1){ diff --git a/lib/src/clixon_proto_client.c b/lib/src/clixon_proto_client.c index 4bbfd0af..9f0ba898 100644 --- a/lib/src/clixon_proto_client.c +++ b/lib/src/clixon_proto_client.c @@ -301,7 +301,7 @@ clicon_rpc_netconf_xml(clicon_handle h, * if (clicon_rpc_get_config(h, NULL, "running", "/hello/world", nsc, &xt) < 0) * err; * if ((xerr = xpath_first(xt, NULL, "/rpc-error")) != NULL){ - * clixon_netconf_error(OE_NETCONF, xerr, "msg", "/hello/world"); + * clixon_netconf_error(xerr, "msg", "/hello/world"); * err; * } * if (xt) @@ -445,7 +445,7 @@ clicon_rpc_edit_config(clicon_handle h, if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) goto done; if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){ - clixon_netconf_error(OE_NETCONF, xerr, "Editing configuration", NULL); + clixon_netconf_error(xerr, "Editing configuration", NULL); goto done; } retval = 0; @@ -495,7 +495,7 @@ clicon_rpc_copy_config(clicon_handle h, if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) goto done; if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){ - clixon_netconf_error(OE_NETCONF, xerr, "Copying configuration", NULL); + clixon_netconf_error(xerr, "Copying configuration", NULL); goto done; } retval = 0; @@ -538,7 +538,7 @@ clicon_rpc_delete_config(clicon_handle h, if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) goto done; if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){ - clixon_netconf_error(OE_NETCONF, xerr, "Deleting configuration", NULL); + clixon_netconf_error(xerr, "Deleting configuration", NULL); goto done; } retval = 0; @@ -577,7 +577,7 @@ clicon_rpc_lock(clicon_handle h, if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) goto done; if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){ - clixon_netconf_error(OE_NETCONF, xerr, "Locking configuration", NULL); + clixon_netconf_error(xerr, "Locking configuration", NULL); goto done; } retval = 0; @@ -615,7 +615,7 @@ clicon_rpc_unlock(clicon_handle h, if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) goto done; if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){ - clixon_netconf_error(OE_NETCONF, xerr, "Configuration unlock", NULL); + clixon_netconf_error(xerr, "Configuration unlock", NULL); goto done; } retval = 0; @@ -649,7 +649,7 @@ clicon_rpc_unlock(clicon_handle h, * if (clicon_rpc_get(h, "/hello/world", nsc, CONTENT_ALL, -1, &xt) < 0) * err; * if ((xerr = xpath_first(xt, NULL, "/rpc-error")) != NULL){ - * clixon_netconf_error(OE_NETCONF, xerr, "clicon_rpc_get", NULL); + * clixon_netconf_error(xerr, "clicon_rpc_get", NULL); * err; * } * if (xt) @@ -779,7 +779,7 @@ clicon_rpc_close_session(clicon_handle h) if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) goto done; if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){ - clixon_netconf_error(OE_NETCONF, xerr, "Close session", NULL); + clixon_netconf_error(xerr, "Close session", NULL); goto done; } retval = 0; @@ -818,7 +818,7 @@ clicon_rpc_kill_session(clicon_handle h, if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) goto done; if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){ - clixon_netconf_error(OE_NETCONF, xerr, "Kill session", NULL); + clixon_netconf_error(xerr, "Kill session", NULL); goto done; } retval = 0; @@ -856,7 +856,7 @@ clicon_rpc_validate(clicon_handle h, if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) goto done; if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){ - clixon_netconf_error(OE_NETCONF, xerr, CLIXON_ERRSTR_VALIDATE_FAILED, NULL); + clixon_netconf_error(xerr, CLIXON_ERRSTR_VALIDATE_FAILED, NULL); goto done; } retval = 0; @@ -892,7 +892,7 @@ clicon_rpc_commit(clicon_handle h) if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) goto done; if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){ - clixon_netconf_error(OE_NETCONF, xerr, CLIXON_ERRSTR_COMMIT_FAILED, NULL); + clixon_netconf_error(xerr, CLIXON_ERRSTR_COMMIT_FAILED, NULL); goto done; } retval = 0; @@ -928,7 +928,7 @@ clicon_rpc_discard_changes(clicon_handle h) if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) goto done; if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){ - clixon_netconf_error(OE_NETCONF, xerr, "Discard changes", NULL); + clixon_netconf_error(xerr, "Discard changes", NULL); goto done; } retval = 0; @@ -978,7 +978,7 @@ clicon_rpc_create_subscription(clicon_handle h, if (clicon_rpc_msg(h, msg, &xret, s0) < 0) goto done; if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){ - clixon_netconf_error(OE_NETCONF, xerr, "Create subscription", NULL); + clixon_netconf_error(xerr, "Create subscription", NULL); goto done; } retval = 0; @@ -1016,7 +1016,7 @@ clicon_rpc_debug(clicon_handle h, if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) goto done; if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){ - clixon_netconf_error(OE_NETCONF, xerr, "Debug", NULL); + clixon_netconf_error(xerr, "Debug", NULL); goto done; } if (xpath_first(xret, NULL, "//rpc-reply/ok") == NULL){ @@ -1059,7 +1059,7 @@ clicon_hello_req(clicon_handle h, if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) goto done; if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){ - clixon_netconf_error(OE_NETCONF, xerr, "Hello", NULL); + clixon_netconf_error(xerr, "Hello", NULL); goto done; } if ((x = xpath_first(xret, NULL, "hello/session-id")) == NULL){ diff --git a/lib/src/clixon_xml.c b/lib/src/clixon_xml.c index a572a141..ebb1c683 100644 --- a/lib/src/clixon_xml.c +++ b/lib/src/clixon_xml.c @@ -1172,12 +1172,13 @@ xml_find(cxobj *xp, * @see xml_wrap * @see xml_insert * @note xc is not sorted correctly, need to call xml_sort on parent + * @see xml_insert which is a higher layer function including yang and sorting */ int xml_addsub(cxobj *xp, cxobj *xc) { - int retval = -1; + int retval = -1; cxobj *oldp; int i; char *pns = NULL; /* parent namespace */ diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 298a278d..61aaf4e1 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -1357,15 +1357,15 @@ assign_namespace_body(cxobj *x0, /* source */ return retval; } - /*! Merge a base tree x0 with x1 with yang spec y * @param[in] x0 Base xml tree (can be NULL in add scenarios) * @param[in] y0 Yang spec corresponding to xml-node x0. NULL if x0 is NULL * @param[in] x0p Parent of x0 * @param[in] x1 xml tree which modifies base * @param[out] reason If retval=0 a malloced string - * @retval 0 OK. If reason is set, Yang error - * @retval -1 Error + * @retval 1 OK + * @retval 0 Yang error, reason is set + * @retval -1 Error * Assume x0 and x1 are same on entry and that y is the spec */ static int @@ -1384,7 +1384,8 @@ xml_merge1(cxobj *x0, /* the target */ char *x1bstr; /* mod body string */ yang_stmt *yc; /* yang child */ cbuf *cbr = NULL; /* Reason buffer */ - int i; + int ret; + int i; struct { cxobj *w_x0c; cxobj *w_x1c; @@ -1398,7 +1399,7 @@ xml_merge1(cxobj *x0, /* the target */ if (yang_keyword_get(y0) == Y_LEAF_LIST || yang_keyword_get(y0) == Y_LEAF){ x1bstr = xml_body(x1); if (x0==NULL){ - if ((x0 = xml_new(x1name, x0p, CX_ELMNT)) == NULL) + if ((x0 = xml_new(x1name, NULL, CX_ELMNT)) == NULL) goto done; xml_spec_set(x0, y0); if (x1bstr){ /* empty type does not have body */ @@ -1414,6 +1415,9 @@ xml_merge1(cxobj *x0, /* the target */ if (xml_value_set(x0b, x1bstr) < 0) goto done; } + if (xml_parent(x0) == NULL && + xml_insert(x0p, x0, INS_LAST, NULL, NULL) < 0) + goto done; if (assign_namespace_element(x1, x0, x0p) < 0) goto done; } /* if LEAF|LEAF_LIST */ @@ -1447,7 +1451,7 @@ xml_merge1(cxobj *x0, /* the target */ goto done; } } - break; + goto fail; } /* See if there is a corresponding node in the base tree */ x0c = NULL; @@ -1466,40 +1470,41 @@ xml_merge1(cxobj *x0, /* the target */ x1c = NULL; i = 0; while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) { - if (xml_merge1(second_wave[i].w_x0c, + if ((ret = xml_merge1(second_wave[i].w_x0c, second_wave[i].w_yc, x0, second_wave[i].w_x1c, - reason) < 0) + reason)) < 0) goto done; - if (*reason != NULL) - goto ok; + if (ret == 0) + goto fail; i++; } if (xml_parent(x0) == NULL && xml_insert(x0p, x0, INS_LAST, NULL, NULL) < 0) goto done; } /* else Y_CONTAINER */ - ok: - retval = 0; + retval = 1; done: if (second_wave) free(second_wave); if (cbr) cbuf_free(cbr); return retval; + fail: + retval = 0; + goto done; } /*! Merge XML trees x1 into x0 according to yang spec yspec - * @param[in] x0 Base xml tree (can be NULL in add scenarios) - * @param[in] x1 xml tree which modifies base - * @param[in] yspec Yang spec + * @param[in] x0 Base xml tree (can be NULL in add scenarios) + * @param[in] x1 xml tree which modifies base + * @param[in] yspec Yang spec * @param[out] reason If retval=0, reason is set. Malloced. Needs to be freed by caller - * @retval 0 OK. If reason is set, Yang error - * @retval -1 Error + * @retval 1 OK + * @retval 0 Yang error, reason is set + * @retval -1 Error * @note both x0 and x1 need to be top-level trees - * @see text_modify_top as more generic variant (in datastore text) - * @note returns -1 if YANG do not match, you may want to have a softer error */ int xml_merge(cxobj *x0, @@ -1518,7 +1523,6 @@ xml_merge(cxobj *x0, if (x0 == NULL || x1 == NULL){ clicon_err(OE_UNIX, EINVAL, "parameters x0 or x1 is NULL"); goto done; - goto done; } /* Loop through children of the modification tree */ x1c = NULL; @@ -1532,7 +1536,7 @@ xml_merge(cxobj *x0, clicon_err(OE_UNIX, errno, "strdup"); goto done; } - goto ok; + goto fail; } /* Get yang spec of the child */ if ((yc = yang_find_datanode(ymod, x1cname)) == NULL){ @@ -1547,7 +1551,7 @@ xml_merge(cxobj *x0, goto done; } } - break; + goto fail; } /* See if there is a corresponding node (x1c) in the base tree (x0) */ if (match_base_child(x0, x1c, yc, &x0c) < 0) @@ -1561,12 +1565,14 @@ xml_merge(cxobj *x0, if (*reason != NULL) break; } - ok: - retval = 0; /* OK */ + retval = 1; /* OK */ done: if (cbr) cbuf_free(cbr); return retval; + fail: + retval = 0; + goto done; } /*! Get integer value from xml node from yang enumeration diff --git a/test/test_insert.sh b/test/test_insert.sh index 80180389..e5fa3570 100755 --- a/test/test_insert.sh +++ b/test/test_insert.sh @@ -6,7 +6,7 @@ # 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} +: ${clixon_util_xml_mod:=clixon_util_xml_mod -o insert} OPTS="-D $DBG" @@ -46,16 +46,20 @@ module example { } EOF +# Insert a sub tree into base tree. Verify its inserted in the right place +# Args: +# 1: base tree +# 2: sub tree testrun(){ x0=$1 xi="$2" xp=c - new "random list add leaf-list" - # First run sorted (assume this is the refernce == correct) - rs=$($clixon_util_insert -y $fyang -x "$xi" -b "$x0" -p $xp $OPTS -s) + new "insert list into $x0, verify order" + # First run sorted (assume this is the reference == correct) + rs=$($clixon_util_xml_mod -y $fyang -x "$xi" -b "$x0" -p $xp $OPTS -s) # Then run actual insert - r0=$($clixon_util_insert -y $fyang -x "$xi" -b "$x0" -p $xp $OPTS) + r0=$($clixon_util_xml_mod -y $fyang -x "$xi" -b "$x0" -p $xp $OPTS) # If both are null something is amiss if [ -z "$r0" -a -z "$rs" ]; then err "length of retval is zero" @@ -213,4 +217,4 @@ testrun "$x0" "32" rm -rf $dir # unset conditional parameters -unset clixon_util_insert +unset clixon_util_xml_mod diff --git a/util/Makefile.in b/util/Makefile.in index bc05aaa8..8d62bc2d 100644 --- a/util/Makefile.in +++ b/util/Makefile.in @@ -70,12 +70,12 @@ LIBDEPS = $(top_srcdir)/lib/src/$(CLIXON_LIB) # Utilities, unit testings. Not installed. APPSRC = clixon_util_xml.c +APPSRC += clixon_util_xml_mod.c APPSRC += clixon_util_json.c APPSRC += clixon_util_yang.c APPSRC += clixon_util_xpath.c APPSRC += clixon_util_path.c APPSRC += clixon_util_datastore.c -APPSRC += clixon_util_insert.c APPSRC += clixon_util_regexp.c ifeq ($(with_restconf),yes) APPSRC += clixon_util_stream.c # Needs curl @@ -114,7 +114,7 @@ clixon_util_path: clixon_util_path.c $(LIBDEPS) clixon_util_datastore: clixon_util_datastore.c $(LIBDEPS) $(CC) $(INCLUDES) $(CPPFLAGS) @CFLAGS@ $(LDFLAGS) $^ $(LIBS) -o $@ -clixon_util_insert: clixon_util_insert.c $(LIBDEPS) +clixon_util_xml_mod: clixon_util_xml_mod.c $(LIBDEPS) $(CC) $(INCLUDES) $(CPPFLAGS) @CFLAGS@ $(LDFLAGS) $^ $(LIBS) -o $@ clixon_util_regexp: clixon_util_regexp.c $(LIBDEPS) diff --git a/util/clixon_util_insert.c b/util/clixon_util_insert.c deleted file mode 100644 index cfc016e8..00000000 --- a/util/clixon_util_insert.c +++ /dev/null @@ -1,202 +0,0 @@ -/* - * - ***** BEGIN LICENSE BLOCK ***** - - Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren - Copyright (C) 2017-2019 Olof Hagsand - Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC - - 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 ***** - - * Utility for Inserting element in list - */ - -#ifdef HAVE_CONFIG_H -#include "clixon_config.h" /* generated by config & autoconf */ -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* cligen */ -#include - -/* clixon */ -#include "clixon/clixon.h" - -static int -usage(char *argv0) -{ - fprintf(stderr, "usage:%s [options]\n" - "where options are\n" - "\t-h \t\tHelp\n" - "\t-D \tDebug\n" - "\t-y \tYANG spec file (or stdin)\n" - "\t-b \tXML base expression\n" - "\t-x \tXML to insert\n" - "\t-p \tXpath to where in base and XML\n" - "\t-s \tSort output after insert\n" - "Assume insert xml is first child of xpath. Ie if xml=23 and xpath=a, then inserted element is 23\n", - argv0 - ); - exit(0); -} - -int -main(int argc, char **argv) -{ - int retval = -1; - char *argv0 = argv[0]; - int c; - char *filename = NULL; - int fd = 0; /* unless overriden by argv[1] */ - char *x0str = NULL; - char *xistr = NULL; - char *xpath = NULL; - yang_stmt *yspec = NULL; - cxobj *x0 = NULL; - cxobj *xb; - 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; - optind = 1; - opterr = 0; - while ((c = getopt(argc, argv, "hD:y:b:x:p:s")) != -1) - switch (c) { - case 'h': - usage(argv0); - break; - case 'D': - if (sscanf(optarg, "%d", &debug) != 1) - usage(argv0); - break; - case 'y': /* YANG spec file */ - filename = optarg; - if (0 && (fd = open(filename, O_RDONLY)) < 0){ - clicon_err(OE_UNIX, errno, "open(%s)", argv[1]); - goto done; - } - break; - case 'b': /* Base XML expression */ - x0str = optarg; - break; - case 'x': /* XML to insert */ - xistr = optarg; - break; - case 'p': /* XPATH base */ - xpath = optarg; - break; - case 's': /* sort output after insert */ - sort++; - break; - default: - usage(argv[0]); - break; - } - if (xistr == NULL || x0str == NULL) - usage(argv0); - if (xpath == NULL) - usage(argv0); - if (filename == NULL) - usage(argv0); - clicon_debug(1, "xistr:%s", xistr); - clicon_debug(1, "x0str:%s", x0str); - clicon_debug(1, "xpath:%s", xpath); - if ((yspec = yspec_new()) == NULL) - goto done; - if (yang_spec_parse_file(h, filename, yspec) < 0) - goto done; - /* Parse base XML */ - if (clixon_xml_parse_string(x0str, YB_MODULE, yspec, &x0, NULL) < 0){ - clicon_err(OE_XML, 0, "Parsing base xml: %s", x0str); - goto done; - } - if (xml_bind_yang(x0, YB_MODULE, yspec, NULL) < 0) - goto done; - if ((xb = xpath_first(x0, NULL, "%s", xpath)) == NULL){ - clicon_err(OE_XML, 0, "xpath: %s not found in x0", xpath); - goto done; - } - if (debug){ - clicon_debug(1, "xb:"); - xml_print(stderr, xb); - } - /* Parse insert XML */ - if (clixon_xml_parse_string(xistr, YB_MODULE, yspec, &xi, NULL) < 0){ - clicon_err(OE_XML, 0, "Parsing insert xml: %s", xistr); - goto done; - } - if (xml_bind_yang(xi, YB_MODULE, yspec, NULL) < 0) - goto done; - if ((xi = xpath_first(xi, NULL, "%s", xpath)) == NULL){ - clicon_err(OE_XML, 0, "xpath: %s not found in xi", xpath); - goto done; - } - /* Find first element child */ - if ((xi = xml_child_i_type(xi, 0, CX_ELMNT)) == NULL){ - clicon_err(OE_XML, 0, "xi has no element child"); - goto done; - } - /* Remove it from parent */ - if (xml_rm(xi) < 0) - goto done; - if (debug){ - clicon_debug(1, "xi:"); - xml_print(stderr, xi); - } - if (xml_insert(xb, xi, INS_LAST, NULL, NULL) < 0) - goto done; - if (debug){ - clicon_debug(1, "x0:"); - xml_print(stderr, x0); - } - if (sort) - xml_sort(xb, h); - clicon_xml2file(stdout, xb, 0, 0); - - retval = 0; - done: - if (x0) - xml_free(x0); - if (yspec) - yspec_free(yspec); - if (fd > 0) - close(fd); - return retval; -} diff --git a/util/clixon_util_xml.c b/util/clixon_util_xml.c index 465bbae8..d92fed77 100644 --- a/util/clixon_util_xml.c +++ b/util/clixon_util_xml.c @@ -65,7 +65,7 @@ /* clixon */ #include "clixon/clixon.h" -/* Command line options to be passed to getopt(3) */ +/* Command line options passed to getopt(3) */ #define UTIL_XML_OPTS "hD:f:Jjl:pvoy:Y:t:T:" static int @@ -265,7 +265,7 @@ main(int argc, goto done; } if (ret == 0){ - clixon_netconf_error(OE_NETCONF, xerr, "Parse top file", NULL); + clixon_netconf_error(xerr, "Parse top file", NULL); goto done; } if (validate_tree(h, xtop, yspec) < 0) @@ -291,7 +291,7 @@ main(int argc, if ((ret = clixon_json_parse_file(fd, top_input_filename?YB_PARENT:YB_MODULE, yspec, &xt, &xerr)) < 0) goto done; if (ret == 0){ - clixon_netconf_error(OE_NETCONF, xerr, "util_xml", NULL); + clixon_netconf_error(xerr, "util_xml", NULL); goto done; } } @@ -307,7 +307,7 @@ main(int argc, goto done; } if (ret == 0){ - clixon_netconf_error(OE_NETCONF, xerr, "util_xml", NULL); + clixon_netconf_error(xerr, "util_xml", NULL); goto done; } } diff --git a/util/clixon_util_xml_mod.c b/util/clixon_util_xml_mod.c new file mode 100644 index 00000000..b2a07462 --- /dev/null +++ b/util/clixon_util_xml_mod.c @@ -0,0 +1,284 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren + Copyright (C) 2017-2019 Olof Hagsand + Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC + + 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 ***** + + * Utility for manipulating XML trees. In all operations, there is a primary base tree x0/xb) and a + * secondary tree (x1/xs). There are several operations on how to modify the base tree using the + * secondary tree. Both x0 and x1 are root trees, whereas xb/xs are subytrees of x0/x1 respectively + * after path has been applied. + * This includes: + * - Insert subtree (last) in list: -b -x -p + * which gives xb and xs. The first element of xs is inserted under xb + * Example: xb := ; xs := + * Result is : xb = + * - Merging trees: -o merge -b -x <2nd> -p + */ + +#ifdef HAVE_CONFIG_H +#include "clixon_config.h" /* generated by config & autoconf */ +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* cligen */ +#include + +/* clixon */ +#include "clixon/clixon.h" + +/* Command line options passed to getopt(3) */ +#define UTIL_XML_MOD_OPTS "hD:o:y:b:x:p:s" + +enum opx{ + OPX_INSERT, + OPX_MERGE, + OPX_PARENT +}; + +static const map_str2int opx_map[] = { + {"insert", OPX_INSERT}, + {"merge", OPX_MERGE}, + {"parent", OPX_PARENT}, + {NULL, -1} +}; + +const enum opx +opx_str2int(char *opstr) +{ + return clicon_str2int(opx_map, opstr); +} + +static int +usage(char *argv0) +{ + fprintf(stderr, "usage:%s [options]\n" + "where options are\n" + "\t-h \t\tHelp\n" + "\t-D \tDebug\n" + "\t-o \tOperation: insert or merge\n" + "\t-y \tYANG spec file\n" + "\t-b \tXML base expression\n" + "\t-x \tXML to insert\n" + "\t-p \tXpath to where in base and XML\n" + "\t-s \tSort output after operation\n", + argv0 + ); + exit(0); +} + +int +main(int argc, char **argv) +{ + int retval = -1; + char *argv0 = argv[0]; + int c; + char *yangfile = NULL; + int fd = 0; /* unless overriden by argv[1] */ + char *x0str = NULL; + char *x1str = NULL; + char *xpath = NULL; + yang_stmt *yspec = NULL; + cxobj *x0 = NULL; + cxobj *x1 = NULL; + cxobj *xb; + cxobj *xi = NULL; + cxobj *xi1 = NULL; + cxobj *xerr = NULL; + int sort = 0; + int ret; + clicon_handle h; + enum opx opx = -1; + char *reason = NULL; + + clicon_log_init("clixon_insert", LOG_DEBUG, CLICON_LOG_STDERR); + if ((h = clicon_handle_init()) == NULL) + goto done; + optind = 1; + opterr = 0; + while ((c = getopt(argc, argv, UTIL_XML_MOD_OPTS)) != -1) + switch (c) { + case 'h': + usage(argv0); + break; + case 'D': + if (sscanf(optarg, "%d", &debug) != 1) + usage(argv0); + break; + case 'o': /* Operation */ + opx = opx_str2int(optarg); + break; + case 'y': /* YANG spec file */ + yangfile = optarg; + break; + case 'b': /* Base XML expression */ + x0str = optarg; + break; + case 'x': /* XML to insert */ + x1str = optarg; + break; + case 'p': /* XPATH base */ + xpath = optarg; + break; + case 's': /* sort output after insert */ + sort++; + break; + default: + usage(argv[0]); + break; + } + /* Sanity check: check mandatory arguments */ + if (x1str == NULL || x0str == NULL || yangfile == NULL) + usage(argv0); + if (opx == -1) + usage(argv0); + if ((yspec = yspec_new()) == NULL) + goto done; + if (yang_spec_parse_file(h, yangfile, yspec) < 0) + goto done; + /* Parse base XML */ + if ((ret = clixon_xml_parse_string(x0str, YB_MODULE, yspec, &x0, &xerr)) < 0){ + clicon_err(OE_XML, 0, "Parsing base xml: %s", x0str); + goto done; + } + if (ret == 0){ + clixon_netconf_error(xerr, "Parsing base xml", NULL); + goto done; + } + /* Get base subtree by xpath */ + if ((xb = xpath_first(x0, NULL, "%s", xpath)) == NULL){ + clicon_err(OE_XML, 0, "xpath: %s not found in x0", xpath); + goto done; + } + if (debug){ + clicon_debug(1, "xb:"); + xml_print(stderr, xb); + } + switch (opx){ + case OPX_PARENT: + /* Parse insert XML */ + if ((ret = clixon_xml_parse_string(x1str, YB_PARENT, yspec, &xb, &xerr)) < 0){ + clicon_err(OE_XML, 0, "Parsing insert xml: %s", x1str); + goto done; + } + if (ret == 0){ + clixon_netconf_error(xerr, "Parsing secondary xml", NULL); + goto done; + } + break; + case OPX_MERGE: + /* Parse insert XML */ + if ((ret = clixon_xml_parse_string(x1str, YB_MODULE, yspec, &x1, &xerr)) < 0){ + clicon_err(OE_XML, 0, "Parsing insert xml: %s", x1str); + goto done; + } + if (ret == 0){ + clixon_netconf_error(xerr, "Parsing secondary xml", NULL); + goto done; + } + if ((xi = xpath_first(x1, NULL, "%s", xpath)) == NULL){ + clicon_err(OE_XML, 0, "xpath: %s not found in xi", xpath); + goto done; + } + if ((ret = xml_merge(xb, xi, yspec, &reason)) < 0) + goto done; + if (ret == 0){ + clicon_err(OE_XML, 0, "%s", reason); + goto done; + } + break; + case OPX_INSERT: + /* Parse insert XML */ + if ((ret = clixon_xml_parse_string(x1str, YB_MODULE, yspec, &x1, &xerr)) < 0){ + clicon_err(OE_XML, 0, "Parsing insert xml: %s", x1str); + goto done; + } + if (ret == 0){ + clixon_netconf_error(xerr, "Parsing secondary xml", NULL); + goto done; + } + /* Get secondary subtree by xpath */ + if ((xi = xpath_first(x1, NULL, "%s", xpath)) == NULL){ + clicon_err(OE_XML, 0, "xpath: %s not found in xi", xpath); + goto done; + } + + /* Find first element child of secondary */ + if ((xi1 = xml_child_i_type(xi, 0, CX_ELMNT)) == NULL){ + clicon_err(OE_XML, 0, "xi has no element child"); + goto done; + } + /* Remove it from parent */ + if (xml_rm(xi1) < 0) + goto done; + if (xml_insert(xb, xi1, INS_LAST, NULL, NULL) < 0) + goto done; + break; + default: + usage(argv0); + } + if (debug){ + clicon_debug(1, "x0:"); + xml_print(stderr, x0); + } + if (sort) + xml_apply(xb, CX_ELMNT, xml_sort, h); + if (strcmp(xml_name(xb),"top")==0) + clicon_xml2file(stdout, xml_child_i_type(xb, 0, CX_ELMNT), 0, 0); + else + clicon_xml2file(stdout, xb, 0, 0); + fprintf(stdout, "\n"); + retval = 0; + done: + if (x0) + xml_free(x0); + if (x1) + xml_free(x1); + if (xerr) + xml_free(xerr); + if (reason) + free(reason); + if (yspec) + yspec_free(yspec); + if (fd > 0) + close(fd); + return retval; +}