/* * ***** BEGIN LICENSE BLOCK ***** Copyright (C) 2009-2017 Olof Hagsand and Benny Holmgren This file is part of CLIXON. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Alternatively, the contents of this file may be used under the terms of the GNU General Public License Version 3 or later (the "GPL"), in which case the provisions of the GPL are applicable instead of those above. If you wish to allow use of your version of this file only under the terms of the GPL, and not to allow others to use your version of this file under the terms of Apache License version 2, indicate your decision by deleting the provisions above and replace them with the notice and other provisions required by the GPL. If you do not delete the provisions above, a recipient may use your version of this file under the terms of any one of the Apache License version 2 or the GPL. ***** END LICENSE BLOCK ***** * * XML code * * file * +---------+ db2xml_key-> save_db_to_xml-> * +-------------> | database| <------------+------------------+ * | +---------+ <-xml2db | <-load_xml_to_db | * | | | * | | | * v v v * +---------+ <-xml2cvec_key +-----------+ +---------+ * | cvec | <---------------------> | xml cxobj |<--------->| xmlfile | * +---------+ cvec2xml-> +-----------+ +---------+ * cvec2xml_1(yang)-> xml2json->| * xml2txt-> | * xml2cli-> v * +---------+ * | file | * +---------+ */ #ifdef HAVE_CONFIG_H #include "clixon_config.h" /* generated by config & autoconf */ #endif #include #include #include #include #include #include #include #include #include #include #include /* cligen */ #include /* clicon */ #include "clixon_string.h" #include "clixon_queue.h" #include "clixon_hash.h" #include "clixon_handle.h" #include "clixon_string.h" #include "clixon_yang.h" #include "clixon_yang_type.h" #include "clixon_plugin.h" #include "clixon_options.h" #include "clixon_xml.h" #include "clixon_xsl.h" #include "clixon_log.h" #include "clixon_err.h" #include "clixon_xml_map.h" /* Something to do with reverse engineering of junos syntax? */ #undef SPECIAL_TREATMENT_OF_NAME /* * A node is a leaf if it contains a body. */ static cxobj * leaf(cxobj *xn) { cxobj *xc = NULL; while ((xc = xml_child_each(xn, xc, CX_BODY)) != NULL) break; return xc; } /*! x is element and has eactly one child which in turn has none */ static int tleaf(cxobj *x) { cxobj *c; if (xml_type(x) != CX_ELMNT) return 0; if (xml_child_nr(x) != 1) return 0; c = xml_child_i(x, 0); return (xml_child_nr(c) == 0); } /*! Translate XML -> TEXT * @param[in] level print 4 spaces per level in front of each line */ int xml2txt(FILE *f, cxobj *x, int level) { cxobj *xe = NULL; int children=0; char *term = NULL; int retval = -1; int encr=0; #ifdef SPECIAL_TREATMENT_OF_NAME cxobj *xname; #endif xe = NULL; /* count children */ while ((xe = xml_child_each(x, xe, -1)) != NULL) children++; if (!children){ if (xml_type(x) == CX_BODY){ /* Kludge for escaping encrypted passwords */ if (strcmp(xml_name(xml_parent(x)), "encrypted-password")==0) encr++; term = xml_value(x); } else{ fprintf(f, "%*s", 4*level, ""); term = xml_name(x); } if (encr) fprintf(f, "\"%s\";\n", term); else fprintf(f, "%s;\n", term); retval = 0; goto done; } fprintf(f, "%*s", 4*level, ""); #ifdef SPECIAL_TREATMENT_OF_NAME if (strcmp(xml_name(x), "name") != 0) fprintf(f, "%s ", xml_name(x)); if ((xname = xml_find(x, "name")) != NULL){ if (children > 1) fprintf(f, "%s ", xml_body(xname)); if (!tleaf(x)) fprintf(f, "{\n"); } else if (!tleaf(x)) fprintf(f, "{\n"); #else fprintf(f, "%s ", xml_name(x)); if (!tleaf(x)) fprintf(f, "{\n"); #endif /* SPECIAL_TREATMENT_OF_NAME */ xe = NULL; while ((xe = xml_child_each(x, xe, -1)) != NULL){ #ifdef SPECIAL_TREATMENT_OF_NAME if (xml_type(xe) == CX_ELMNT && (strcmp(xml_name(xe), "name")==0) && (children > 1)) /* skip if this is a name element (unless 0 children) */ continue; #endif if (xml2txt(f, xe, level+1) < 0) break; } if (!tleaf(x)) fprintf(f, "%*s}\n", 4*level, ""); retval = 0; done: return retval; } /*! Translate from XML to CLI commands * Howto: join strings and pass them down. * Identify unique/index keywords for correct set syntax. * Args: * @param[in] f Where to print cli commands * @param[in] x XML Parse-tree (to translate) * @param[in] prepend0 Print this text in front of all commands. * @param[in] gt option to steer cli syntax */ int xml2cli(FILE *f, cxobj *x, char *prepend0, enum genmodel_type gt) { int retval = -1; cxobj *xe = NULL; char *term; int bool; int nr; int i; cbuf *cbpre; /* Create prepend variable string */ if ((cbpre = cbuf_new()) == NULL){ clicon_err(OE_PLUGIN, errno, "cbuf_new"); goto done; } nr = xml_child_nr(x); if (!nr){ if (xml_type(x) == CX_BODY) term = xml_value(x); else term = xml_name(x); if (prepend0) fprintf(f, "%s ", prepend0); fprintf(f, "%s\n", term); retval = 0; goto done; } if (prepend0) cprintf(cbpre, "%s", prepend0); /* bool determines when to print a variable keyword: !leaf T for all (ie parameter) index GT_NONE F index GT_VARS F index GT_ALL T !index GT_NONE F !index GT_VARS T !index GT_ALL T */ bool = !leaf(x) || gt == GT_ALL || (gt == GT_VARS); // bool = (!x->xn_index || gt == GT_ALL); if (bool){ if (cbuf_len(cbpre)) cprintf(cbpre, " "); cprintf(cbpre, "%s", xml_name(x)); } xe = NULL; /* First child is unique, then add that, before looping. */ i = 0; while ((xe = xml_child_each(x, xe, -1)) != NULL){ /* Dont call this if it is index and there are other following */ if (0 && i < nr-1) ; else if (xml2cli(f, xe, cbuf_get(cbpre), gt) < 0) goto done; if (0){ /* assume index is first, otherwise need one more while */ if (gt == GT_ALL) cprintf(cbpre, " %s", xml_name(xe)); cprintf(cbpre, " %s", xml_value(xml_child_i(xe, 0))); } i++; } retval = 0; done: if (cbpre) cbuf_free(cbpre); return retval; } /*! Validate a single XML node with yang specification * - If no value and mandatory flag set in spec, report error. * - Validate value versus spec, and report error if no match. Currently * only int ranges and string regexp checked. * @retval 0 OK */ int xml_yang_validate(cxobj *xt, yang_stmt *ys0) { int retval = -1; cg_var *cv = NULL; char *reason = NULL; yang_stmt *yc; int i; yang_stmt *ys; char *body; /* if not given by argument (overide) use default link */ ys = ys0?ys0:xml_spec(xt); switch (ys->ys_keyword){ case Y_LIST: /* fall thru */ case Y_CONTAINER: for (i=0; iys_len; i++){ yc = ys->ys_stmt[i]; if (yc->ys_keyword != Y_LEAF) continue; if (yang_mandatory(yc) && xml_find(xt, yc->ys_argument)==NULL){ clicon_err(OE_CFG, 0,"Missing mandatory variable: %s", yc->ys_argument); goto done; } } break; case Y_LEAF: /* fall thru */ case Y_LEAF_LIST: /* validate value against ranges, etc */ if ((cv = cv_dup(ys->ys_cv)) == NULL){ clicon_err(OE_UNIX, errno, "cv_dup"); goto done; } /* In the union case, value is parsed as generic REST type, * needs to be reparsed when concrete type is selected */ if ((body = xml_body(xt)) != NULL){ if (cv_parse(body, cv) <0){ clicon_err(OE_UNIX, errno, "cv_parse"); goto done; } if ((ys_cv_validate(cv, ys, &reason)) != 1){ clicon_err(OE_DB, 0, "validation of %s failed %s", ys->ys_argument, reason?reason:""); if (reason) free(reason); goto done; } } break; default: break; } retval = 0; done: if (cv) cv_free(cv); return retval; } /*! Translate a single xml node to a cligen variable vector. Note not recursive * @param[in] xt XML tree containing one top node * @param[in] ys Yang spec containing type specification of top-node of xt * @param[out] cvv CLIgen variable vector. Should be freed by cvec_free() * @retval 0 Everything OK, cvv allocated and set * @retval -1 Something wrong, clicon_err() called to set error. No cvv returned * 'Not recursive' means that only one level of XML bodies is translated to cvec:s. * yang is needed to know which type an xml element has. * Example: 23 88 99 --> b:23, c:88 * @see cvec2xml */ int xml2cvec(cxobj *xt, yang_stmt *yt, cvec **cvv0) { int retval = -1; cvec *cvv = NULL; cxobj *xc; /* xml iteration variable */ yang_stmt *ys; /* yang spec */ cg_var *cv; cg_var *ycv; char *body; char *reason = NULL; int ret; int i = 0; int len = 0; char *name; xc = NULL; while ((xc = xml_child_each(xt, xc, CX_ELMNT)) != NULL) len++; if ((cvv = cvec_new(len)) == NULL){ clicon_err(OE_UNIX, errno, "cvec_new"); goto err; } xc = NULL; /* Go through all children of the xml tree */ while ((xc = xml_child_each(xt, xc, CX_ELMNT)) != NULL){ name = xml_name(xc); if ((ys = yang_find_syntax((yang_node*)yt, name)) == NULL){ clicon_debug(0, "%s: yang sanity problem: %s in xml but not present in yang under %s", __FUNCTION__, name, yt->ys_argument); if ((body = xml_body(xc)) != NULL){ cv = cvec_i(cvv, i++); cv_type_set(cv, CGV_STRING); cv_name_set(cv, name); if ((ret = cv_parse1(body, cv, &reason)) < 0){ clicon_err(OE_PLUGIN, errno, "cv_parse"); goto err; } if (ret == 0){ clicon_err(OE_PLUGIN, errno, "cv_parse: %s", reason); if (reason) free(reason); goto err; } } } else if ((ycv = ys->ys_cv) != NULL){ if ((body = xml_body(xc)) != NULL){ /* XXX: cvec_add uses realloc, can we avoid that? */ cv = cvec_i(cvv, i++); if (cv_cp(cv, ycv) < 0){ clicon_err(OE_PLUGIN, errno, "cv_cp"); goto err; } if ((ret = cv_parse1(body, cv, &reason)) < 0){ clicon_err(OE_PLUGIN, errno, "cv_parse"); goto err; } if (ret == 0){ clicon_err(OE_PLUGIN, errno, "cv_parse: %s", reason); if (reason) free(reason); goto err; } } } } if (debug > 1){ clicon_debug(2, "%s cvv:\n", __FUNCTION__); cvec_print(stderr, cvv); } *cvv0 = cvv; return 0; err: if (cvv) cvec_free(cvv); return retval; } /*! Translate a cligen variable vector to an XML tree with depth one * @param[in] cvv CLIgen variable vector. Should be freed by cvec_free() * @param[in] toptag The XML tree in xt will have this XML tag * @param[in] xt Parent, or NULL * @param[out] xt Pointer to XML tree containing one top node. Should be freed with xml_free * @retval 0 Everything OK, cvv allocated and set * @retval -1 Something wrong, clicon_err() called to set error. No xt returned * @see xml2cvec * @see cvec2xml This does more but has an internal xml2cvec translation */ int cvec2xml_1(cvec *cvv, char *toptag, cxobj *xp, cxobj **xt0) { int retval = -1; cxobj *xt = NULL; cxobj *xn; cxobj *xb; cg_var *cv; char *val; int len=0; int i; cv = NULL; while ((cv = cvec_each(cvv, cv)) != NULL) len++; if ((xt = xml_new(toptag, xp)) == NULL) goto err; if (xml_childvec_set(xt, len) < 0) goto err; cv = NULL; i = 0; while ((cv = cvec_each(cvv, cv)) != NULL) { if ((xn = xml_new(cv_name_get(cv), NULL)) == NULL) /* this leaks */ goto err; xml_parent_set(xn, xt); xml_child_i_set(xt, i++, xn); if ((xb = xml_new("body", xn)) == NULL) /* this leaks */ goto err; xml_type_set(xb, CX_BODY); val = cv2str_dup(cv); xml_value_set(xb, val); /* this leaks */ if (val) free(val); } *xt0 = xt; return 0; err: if (xt) xml_free(xt); return retval; } /*! Return 1 if value is a body of one of the named children of xt */ static int xml_is_body(cxobj *xt, char *name, char *val) { cxobj *x; x = NULL; while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) { if (strcmp(name, xml_name(x))) continue; if (strcmp(xml_body(x), val) == 0) return 1; } return 0; } /*! Recursive help function to compute differences between two xml trees * @see dbdiff_vector. */ static int xml_diff1(yang_stmt *ys, cxobj *xt1, cxobj *xt2, cxobj ***first, size_t *firstlen, cxobj ***second, size_t *secondlen, cxobj ***changed1, cxobj ***changed2, size_t *changedlen) { int retval = -1; cxobj *x1 = NULL; cxobj *x2 = NULL; yang_stmt *y; yang_stmt *ykey; char *name; cg_var *cvi; cvec *cvk = NULL; /* vector of index keys */ char *keyname; int equal; char *body1; char *body2; clicon_debug(2, "%s: %s", __FUNCTION__, ys->ys_argument?ys->ys_argument:"yspec"); /* Check nodes present in xt1 and xt2 + nodes only in xt1 * Loop over xt1 */ x1 = NULL; while ((x1 = xml_child_each(xt1, x1, CX_ELMNT)) != NULL){ name = xml_name(x1); if (ys->ys_keyword == Y_SPEC) y = yang_find_topnode((yang_spec*)ys, name); else y = yang_find_syntax((yang_node*)ys, name); if (y == NULL){ clicon_err(OE_UNIX, errno, "No yang node found: %s", name); goto done; } switch (y->ys_keyword){ case Y_LIST: if ((ykey = yang_find((yang_node*)y, Y_KEY, NULL)) == NULL){ clicon_err(OE_XML, errno, "%s: List statement \"%s\" has no key", __FUNCTION__, y->ys_argument); goto done; } /* The value is a list of keys: [ ]* */ if ((cvk = yang_arg2cvec(ykey, " ")) == NULL) goto done; /* Iterate over xt2 tree to (1) find a child that matches name (2) that have keys that matches */ equal = 0; x2 = NULL; while ((x2 = xml_child_each(xt2, x2, CX_ELMNT)) != NULL){ if (strcmp(xml_name(x2), name)) continue; cvi = NULL; equal = 0; /* (2) Match keys between x1 and x2 */ while ((cvi = cvec_each(cvk, cvi)) != NULL) { keyname = cv_string_get(cvi); if ((body1 = xml_find_body(x1, keyname)) == NULL) continue; /* may be error */ if ((body2 = xml_find_body(x2, keyname)) == NULL) continue; /* may be error */ if (strcmp(body1, body2)==0) equal=1; else{ equal=0; /* stop as soon as inequal key found */ break; } } if (equal) /* found x1 and x2 equal, otherwise look for other x2 */ break; } if (cvk){ cvec_free(cvk); cvk = NULL; } if (equal){ if (xml_diff1(y, x1, x2, first, firstlen, second, secondlen, changed1, changed2, changedlen)< 0) goto done; break; } else if (cxvec_append(x1, first, firstlen) < 0) goto done; break; case Y_CONTAINER: /* Equal regardless */ if ((x2 = xml_find(xt2, name)) == NULL){ if (cxvec_append(x1, first, firstlen) < 0) goto done; break; } if (xml_diff1(y, x1, x2, first, firstlen, second, secondlen, changed1, changed2, changedlen)< 0) goto done; break; case Y_LEAF: if ((x2 = xml_find(xt2, name)) == NULL){ if (cxvec_append(x1, first, firstlen) < 0) goto done; break; } if (strcmp(xml_body(x1), xml_body(x2))){ if (cxvec_append(x1, changed1, changedlen) < 0) goto done; (*changedlen)--; /* append two vectors */ if (cxvec_append(x2, changed2, changedlen) < 0) goto done; } break; case Y_LEAF_LIST: body1 = xml_body(x1); if (!xml_is_body(xt2, name, body1)) /* where body is */ if (cxvec_append(x1, first, firstlen) < 0) goto done; break; default: break; } } /* while xt1 */ /* Check nodes present only in xt2 * Loop over xt2 */ x2 = NULL; while ((x2 = xml_child_each(xt2, x2, CX_ELMNT)) != NULL){ name = xml_name(x2); if (ys->ys_keyword == Y_SPEC) y = yang_find_topnode((yang_spec*)ys, name); else y = yang_find_syntax((yang_node*)ys, name); if (y == NULL){ clicon_err(OE_UNIX, errno, "No yang node found: %s", name); goto done; } switch (y->ys_keyword){ case Y_LIST: if ((ykey = yang_find((yang_node*)y, Y_KEY, NULL)) == NULL){ clicon_err(OE_XML, errno, "%s: List statement \"%s\" has no key", __FUNCTION__, y->ys_argument); goto done; } /* The value is a list of keys: [ ]* */ if ((cvk = yang_arg2cvec(ykey, " ")) == NULL) goto done; /* Iterate over xt1 tree to (1) find a child that matches name (2) that have keys that matches */ equal = 0; x1 = NULL; while ((x1 = xml_child_each(xt1, x1, CX_ELMNT)) != NULL){ if (strcmp(xml_name(x1), name)) continue; cvi = NULL; equal = 0; /* (2) Match keys between x2 and x1 */ while ((cvi = cvec_each(cvk, cvi)) != NULL) { keyname = cv_string_get(cvi); if ((body2 = xml_find_body(x2, keyname)) == NULL) continue; /* may be error */ if ((body1 = xml_find_body(x1, keyname)) == NULL) continue; /* may be error */ if (strcmp(body2, body1)==0) equal=1; else{ equal=0; /* stop as soon as inequal key found */ break; } } if (equal) /* found x1 and x2 equal, otherwise look for other x2 */ break; } if (cvk){ cvec_free(cvk); cvk = NULL; } if (!equal) if (cxvec_append(x2, second, secondlen) < 0) goto done; break; case Y_CONTAINER: /* Equal regardless */ if ((x1 = xml_find(xt1, name)) == NULL) if (cxvec_append(x2, second, secondlen) < 0) goto done; break; case Y_LEAF: if ((x1 = xml_find(xt1, name)) == NULL) if (cxvec_append(x2, second, secondlen) < 0) goto done; break; case Y_LEAF_LIST: body2 = xml_body(x2); if (!xml_is_body(xt1, name, body2)) /* where body is */ if (cxvec_append(x2, second, secondlen) < 0) goto done; break; default: break; } } /* while xt1 */ retval = 0; done: if (cvk) cvec_free(cvk); return retval; } /*! Compute differences between two xml trees * @param[in] yspec Yang specification * @param[in] xt1 First XML tree * @param[in] xt2 Second XML tree * @param[out] first Pointervector to XML nodes existing in only first tree * @param[out] firstlen Length of first vector * @param[out] second Pointervector to XML nodes existing in only second tree * @param[out] secondlen Length of second vector * @param[out] changed1 Pointervector to XML nodes changed value * @param[out] changed2 Pointervector to XML nodes changed value * @param[out] changedlen Length of changed vector * All xml vectors should be freed after use. * Bot xml trees should be freed with xml_free() */ int xml_diff(yang_spec *yspec, cxobj *xt1, cxobj *xt2, cxobj ***first, size_t *firstlen, cxobj ***second, size_t *secondlen, cxobj ***changed1, cxobj ***changed2, size_t *changedlen) { int retval = -1; *firstlen = 0; *secondlen = 0; *changedlen = 0; if (xt1 == NULL && xt2 == NULL) return 0; if (xt2 == NULL){ if (cxvec_append(xt1, first, firstlen) < 0) goto done; goto ok; } if (xt1 == NULL){ if (cxvec_append(xt1, second, secondlen) < 0) goto done; goto ok; } if (xml_diff1((yang_stmt*)yspec, xt1, xt2, first, firstlen, second, secondlen, changed1, changed2, changedlen) < 0) goto done; ok: retval = 0; done: return retval; } /*! Construct an xml key format from yang statement using wildcards for keys * Recursively construct it to the top. * Example: * yang: container a -> list b -> key c -> leaf d * xpath: /a/b/%s/d * @param[in] ys Yang statement * @param[in] inclkey If set include key leaf (eg last leaf d in ex) * @param[out] cb api_path_fmt, */ static int yang2api_path_fmt_1(yang_stmt *ys, int inclkey, cbuf *cb) { yang_node *yp; /* parent */ yang_stmt *ykey; int i; cvec *cvk = NULL; /* vector of index keys */ int retval = -1; yp = ys->ys_parent; if (yp != NULL && yp->yn_keyword != Y_MODULE && yp->yn_keyword != Y_SUBMODULE){ if (yang2api_path_fmt_1((yang_stmt *)yp, 1, cb) < 0) goto done; } if (inclkey){ if (ys->ys_keyword != Y_CHOICE && ys->ys_keyword != Y_CASE) cprintf(cb, "/%s", ys->ys_argument); } else{ #if 1 if (ys->ys_keyword == Y_LEAF && yp && yp->yn_keyword == Y_LIST){ if (yang_key_match(yp, ys->ys_argument) == 0) cprintf(cb, "/%s", ys->ys_argument); /* Not if leaf and key */ } else #endif if (ys->ys_keyword != Y_CHOICE && ys->ys_keyword != Y_CASE) cprintf(cb, "/%s", ys->ys_argument); } switch (ys->ys_keyword){ case Y_LIST: if ((ykey = yang_find((yang_node*)ys, Y_KEY, NULL)) == NULL){ clicon_err(OE_XML, errno, "%s: List statement \"%s\" has no key", __FUNCTION__, ys->ys_argument); goto done; } /* The value is a list of keys: [ ]* */ if ((cvk = yang_arg2cvec(ykey, " ")) == NULL) goto done; if (cvec_len(cvk)) cprintf(cb, "="); /* Iterate over individual keys */ for (i=0; i list b -> key c -> leaf d * api_path: /a/b=%s/d * @param[in] ys Yang statement * @param[in] inclkey If set include key leaf (eg last leaf d in ex) * @param[out] api_path_fmt XML api path. Needs to be freed after use. */ int yang2api_path_fmt(yang_stmt *ys, int inclkey, char **api_path_fmt) { int retval = -1; cbuf *cb = NULL; if ((cb = cbuf_new()) == NULL){ clicon_err(OE_UNIX, errno, "cbuf_new"); goto done; } if (yang2api_path_fmt_1(ys, inclkey, cb) < 0) goto done; if ((*api_path_fmt = strdup(cbuf_get(cb))) == NULL){ clicon_err(OE_UNIX, errno, "strdup"); goto done; } retval = 0; done: if (cb) cbuf_free(cb); return retval; } /*! Transform an xml key format and a vector of values to an XML key * Used for actual key, eg in clicon_rpc_change(), xmldb_put_xkey() * Example: * xmlkeyfmt: /aaa/%s/name * cvv: key=17 * xmlkey: /aaa/17/name * @param[in] api_path_fmt XML key format, eg /aaa/%s/name * @param[in] cvv cligen variable vector, one for every wildchar in api_path_fmt * @param[out] api_path api_path, eg /aaa/17. Free after use * @param[out] yang_arg yang-stmt argument name. Free after use * @note first and last elements of cvv are not used,.. * @see cli_dbxml where this function is called */ int api_path_fmt2api_path(char *api_path_fmt, cvec *cvv, char **api_path) { int retval = -1; char c; int esc=0; cbuf *cb = NULL; int i; int j; char *str; char *strenc=NULL; /* Sanity check */ #if 1 j = 0; /* Count % */ for (i=0; iys_keyword == Y_CONTAINER || ys->ys_keyword == Y_LIST){ for (i=0; iys_len; i++){ y = ys->ys_stmt[i]; if (y->ys_keyword != Y_LEAF) continue; assert(y->ys_cv); if (!cv_flag(y->ys_cv, V_UNSET)){ /* Default value exists */ if (!xml_find(xt, y->ys_argument)){ if ((xc = xml_new_spec(y->ys_argument, xt, y)) == NULL) goto done; if ((xb = xml_new("body", xc)) == NULL) goto done; xml_type_set(xb, CX_BODY); if ((str = cv2str_dup(y->ys_cv)) == NULL){ clicon_err(OE_UNIX, errno, "cv2str_dup"); goto done; } if (xml_value_set(xb, str) < 0) goto done; free(str); } } } } retval = 0; done: return retval; } /*! Order XML children according to YANG * @param[in] xt XML top of tree */ int xml_order(cxobj *xt, void *arg) { int retval = -1; yang_stmt *y; yang_stmt *yc; int i; int j0; int j; cxobj *xc; cxobj *xj; char *yname; /* yang child name */ char *xname; /* xml child name */ if ((y = (yang_stmt*)xml_spec(xt)) == NULL){ clicon_log(LOG_WARNING, "%s: no xml_spec(%s)", __FUNCTION__, xml_name(xt)); retval = 0; goto done; } j0 = 0; /* Go through xml children and ensure they are same order as yspec children */ for (i=0; iys_len; i++){ yc = y->ys_stmt[i]; if (!yang_is_syntax(yc)) continue; yname = yc->ys_argument; /* First go thru xml children with same name */ for (; j0ys_argument, name)==NULL){ clicon_err(OE_XML, 0, "xml node name '%s' does not match yang spec arg '%s'", name, ys->ys_argument); goto done; } retval = 0; done: return retval; } /*! Translate from restconf api-path in cvv form to xml xpath * eg a/b=c -> a/[b=c] * @param[in] yspec Yang spec * @param[in] pcvec api-path as cvec * @param[in] pi Offset of cvec, where api-path starts * @param[out] path The xpath as cbuf variable string, must be initializeed * The api-path has some wierd encoding, use api_path2xpath() if you are * confused. * It works like this: * Assume origin incoming path is * "www.foo.com/restconf/a/b=c", pi is 2 and pcvec is: * ["www.foo.com" "restconf" "a" "b=c"] * which means the api-path is ["a" "b=c"] corresponding to "a/b=c" * @code * cbuf *xpath = cbuf_new(); * if (api_path2xpath_cvv(yspec, cvv, i, xpath) * err; * ... access xpath as cbuf_get(xpath) * cbuf_free(xpath) * @endcode * @see api_path2xpath for string api-path argument */ int api_path2xpath_cvv(yang_spec *yspec, cvec *cvv, int offset, cbuf *xpath) { int retval = -1; int i; cg_var *cv; char *name; cvec *cvk = NULL; /* vector of index keys */ yang_stmt *y = NULL; char *val; char *v; yang_stmt *ykey; cg_var *cvi; for (i=offset; iys_argument); goto done; } clicon_debug(1, "ykey:%s", ykey->ys_argument); /* The value is a list of keys: [ ]* */ if ((cvk = yang_arg2cvec(ykey, " ")) == NULL) goto done; cvi = NULL; /* Iterate over individual yang keys */ cprintf(xpath, "/%s", name); v = val; while ((cvi = cvec_each(cvk, cvi)) != NULL){ cprintf(xpath, "[%s=%s]", cv_string_get(cvi), v); v += strlen(v)+1; } if (val) free(val); } else{ cprintf(xpath, "%s%s", (i==offset?"":"/"), name); } } retval = 0; done: return retval; } /*! Translate from restconf api-path to xml xpath * eg a/b=c -> a/[b=c] * @param[in] yspec Yang spec * @param[in] str API-path as string * @param[out] path The xpath as cbuf variable string, must be initializeed * @code * cbuf *xpath = cbuf_new(); * if (api_path2xpath(yspec, "a/b=c", xpath) * err; * ... access xpath as cbuf_get(xpath) * cbuf_free(xpath) * @endcode */ int api_path2xpath(yang_spec *yspec, char *api_path, cbuf *xpath) { int retval = -1; cvec *api_path_cvv = NULL; /* rest url eg /album=ricky/foo */ if (str2cvec(api_path, '/', '=', &api_path_cvv) < 0) goto done; if (api_path2xpath_cvv(yspec, api_path_cvv, 0, xpath) < 0) goto done; retval = 0; done: if (api_path_cvv) cvec_free(api_path_cvv); return retval; } /*! Create xml tree from api-path as vector * @param[in] vec APIpath as char* vector * @param[in] nvec Length of vec * @param[in] x0 Xpath tree so far * @param[in] y0 Yang spec for x0 * @param[out] xpathp Resulting xml tree * @param[out] ypathp Yang spec matching xpathp * @see api_path2xml */ static int api_path2xml_vec(char **vec, int nvec, cxobj *x0, yang_node *y0, cxobj **xpathp, yang_node **ypathp) { int retval = -1; int j; char *name; char *restval = NULL; char *restval_enc; yang_stmt *ykey; cxobj *xn = NULL; /* new */ cxobj *xb; /* body */ cvec *cvk = NULL; /* vector of index keys */ cg_var *cvi; char *keyname; char *val2; char **valvec = NULL; int nvalvec; cxobj *x = NULL; yang_stmt *y = NULL; if ((name = vec[0]) == NULL){ if (xpathp) *xpathp = x0; if (ypathp) *ypathp = y0; return 0; } /* E.g "x=1,2" -> name:x restval=1,2 */ /* restval is RFC 3896 encoded */ if ((restval_enc = index(name, '=')) != NULL){ *restval_enc = '\0'; restval_enc++; if (percent_decode(restval_enc, &restval) < 0) goto done; } if (y0->yn_keyword == Y_SPEC) /* top-node */ y = yang_find_topnode((yang_spec*)y0, name); else y = yang_find_syntax((yang_node*)y0, name); if (y == NULL){ clicon_err(OE_YANG, errno, "No yang node found: %s", name); goto done; } switch (y->ys_keyword){ case Y_LEAF_LIST: if (restval==NULL){ clicon_err(OE_XML, 0, "malformed key, expected '='"); goto done; } if ((x = xml_new_spec(y->ys_argument, x0, y)) == NULL) goto done; xml_type_set(x, CX_ELMNT); if ((xb = xml_new("body", x)) == NULL) goto done; xml_type_set(xb, CX_BODY); if (xml_value_set(xb, restval) < 0) goto done; break; case Y_LIST: /* Get the yang list key */ if ((ykey = yang_find((yang_node*)y, Y_KEY, NULL)) == NULL){ clicon_err(OE_XML, errno, "%s: List statement \"%s\" has no key", __FUNCTION__, y->ys_argument); goto done; } /* The value is a list of keys: [ ]* */ if ((cvk = yang_arg2cvec(ykey, " ")) == NULL) goto done; if (restval==NULL){ clicon_err(OE_XML, 0, "malformed key, expected '='"); goto done; } if (valvec) free(valvec); if ((valvec = clicon_strsep(restval, ",", &nvalvec)) == NULL) goto done; if (cvec_len(cvk) != nvalvec){ clicon_err(OE_XML, errno, "List %s key length mismatch", name); goto done; } cvi = NULL; /* create list object */ if ((x = xml_new_spec(name, x0, y)) == NULL) goto done; xml_type_set(x, CX_ELMNT); j = 0; /* Create keys */ while ((cvi = cvec_each(cvk, cvi)) != NULL) { keyname = cv_string_get(cvi); val2 = valvec[j++]; if ((xn = xml_new(keyname, x)) == NULL) goto done; xml_type_set(xn, CX_ELMNT); if ((xb = xml_new("body", xn)) == NULL) goto done; xml_type_set(xb, CX_BODY); if (xml_value_set(xb, val2) <0) goto done; } if (cvk){ cvec_free(cvk); cvk = NULL; } break; default: /* eg Y_CONTAINER, Y_LEAF */ if ((x = xml_new_spec(name, x0, y)) == NULL) goto done; xml_type_set(x, CX_ELMNT); break; } if (api_path2xml_vec(vec+1, nvec-1, x, (yang_node*)y, xpathp, ypathp) < 0) goto done; retval = 0; done: if (restval) free(restval); if (valvec) free(valvec); return retval; } /*! Create xml tree from api-path * @param[in] api_path API-path as defined in RFC 8040 * @param[out] xpathp Resulting xml tree * @param[out] ypathp Yang spec matching xpathp * @see api_path2xml_vec */ int api_path2xml(char *api_path, yang_spec *yspec, cxobj *xpath, cxobj **xpathp, yang_node **ypathp) { int retval = -1; char **vec = NULL; int nvec; clicon_debug(1, "%s 0", __FUNCTION__); if (*api_path!='/'){ clicon_err(OE_DB, 0, "Invalid key: %s", api_path); goto done; } if ((vec = clicon_strsep(api_path, "/", &nvec)) == NULL) goto done; /* Remove trailing '/'. Like in /a/ -> /a */ if (nvec > 1 && !strlen(vec[nvec-1])) nvec--; if (nvec < 1){ clicon_err(OE_XML, 0, "Malformed key: %s", api_path); goto done; } nvec--; /* NULL-terminated */ if (api_path2xml_vec(vec+1, nvec, xpath, (yang_node*)yspec, xpathp, ypathp) < 0) goto done; retval = 0; done: if (vec) free(vec); return retval; }