* 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).
This commit is contained in:
Olof hagsand 2020-04-17 15:47:37 +02:00
parent f401c07c4b
commit 0d4263e324
16 changed files with 376 additions and 274 deletions

View file

@ -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

View file

@ -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 <data> tree. Save as <config> 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;
}

View file

@ -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)

View file

@ -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 */

View file

@ -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: <rpc-error>
* @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 */

View file

@ -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.

View file

@ -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:
* <msg>: "<arg>": <netconf-error> or <msg>: <netconf-error>
* @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] xerr Netconf error xml tree on the form: <rpc-error>
* @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)

View file

@ -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){

View file

@ -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){

View file

@ -1172,6 +1172,7 @@ 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,

View file

@ -1357,14 +1357,14 @@ 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 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
*/
@ -1384,6 +1384,7 @@ xml_merge1(cxobj *x0, /* the target */
char *x1bstr; /* mod body string */
yang_stmt *yc; /* yang child */
cbuf *cbr = NULL; /* Reason buffer */
int ret;
int i;
struct {
cxobj *w_x0c;
@ -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,28 +1470,30 @@ 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
@ -1495,11 +1501,10 @@ xml_merge1(cxobj *x0, /* the target */
* @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 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

View file

@ -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="<c xmlns=\"urn:example:example\">$2</c>"
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" "<e>32</e>"
rm -rf $dir
# unset conditional parameters
unset clixon_util_insert
unset clixon_util_xml_mod

View file

@ -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)

View file

@ -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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <limits.h>
#include <stdint.h>
#include <syslog.h>
#include <fcntl.h>
/* cligen */
#include <cligen/cligen.h>
/* 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 <level>\tDebug\n"
"\t-y <file> \tYANG spec file (or stdin)\n"
"\t-b <base> \tXML base expression\n"
"\t-x <xml> \tXML to insert\n"
"\t-p <xpath>\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=<a><x>23 and xpath=a, then inserted element is <x>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;
}

View file

@ -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;
}
}

284
util/clixon_util_xml_mod.c Normal file
View file

@ -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 <x0> -x <x1> -p <path>
* which gives xb and xs. The first element of xs is inserted under xb
* Example: xb := <b><c/></b>; xs := <b><d/></b>
* Result is : xb = <b><c/><d/></b>
* - Merging trees: -o merge -b <base> -x <2nd> -p <path>
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <limits.h>
#include <stdint.h>
#include <syslog.h>
#include <fcntl.h>
/* cligen */
#include <cligen/cligen.h>
/* 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 <level>\tDebug\n"
"\t-o <op> \tOperation: insert or merge\n"
"\t-y <file> \tYANG spec file\n"
"\t-b <base> \tXML base expression\n"
"\t-x <xml> \tXML to insert\n"
"\t-p <xpath>\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;
}