/* * ***** 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 an xml node of type leafref, ensure the value is one of that path's reference * @param[in] xt XML leaf node of type leafref * @param[in] ytype Yang type statement belonging to the XML node */ static int validate_leafref(cxobj *xt, yang_stmt *ytype) { int retval = -1; yang_stmt *ypath; cxobj **xvec = NULL; cxobj *x; int i; size_t xlen = 0; char *leafrefbody; char *leafbody; if ((leafrefbody = xml_body(xt)) == NULL) return 0; if ((ypath = yang_find((yang_node*)ytype, Y_PATH, NULL)) == NULL){ clicon_err(OE_DB, 0, "Leafref %s requires path statement", ytype->ys_argument); goto done; } if (xpath_vec(xt, ypath->ys_argument, &xvec, &xlen) < 0) goto done; for (i = 0; i < xlen; i++) { x = xvec[i]; if ((leafbody = xml_body(x)) == NULL) continue; if (strcmp(leafbody, leafrefbody) == 0) break; } if (i==xlen){ clicon_err(OE_DB, 0, "Leafref validation failed, no such leaf: %s", leafrefbody); goto done; } retval = 0; done: if (xvec) free(xvec); return retval; } /*! Validate a single XML node with yang specification for added entry * 1. Check if mandatory leafs present as subs. * 2. Check leaf values, eg int ranges and string regexps. * @param[in] xt XML node to be validated * @retval 0 Valid OK * @retval -1 Validation failed * @see xml_yang_validate_all */ int xml_yang_validate_add(cxobj *xt, void *arg) { 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 */ if ((ys = xml_spec(xt)) != NULL){ 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; } /*! Validate a single XML node with yang specification for all (not only added) entries * 1. Check leafrefs. Eg you delete a leaf and a leafref references it. * @param[in] xt XML node to be validated * @retval 0 Valid OK * @retval -1 Validation failed * @see xml_yang_validate_add */ int xml_yang_validate_all(cxobj *xt, void *arg) { int retval = -1; yang_stmt *ys; yang_stmt *ytype; /* if not given by argument (overide) use default link */ if ((ys = xml_spec(xt)) != NULL){ switch (ys->ys_keyword){ case Y_LEAF: /* fall thru */ case Y_LEAF_LIST: /* Special case if leaf is leafref, then first check against current xml tree */ if ((ytype = yang_find((yang_node*)ys, Y_TYPE, NULL)) != NULL && strcmp(ytype->ys_argument, "leafref") == 0) if (validate_leafref(xt, ytype) < 0) goto done; break; default: break; } } retval = 0; done: 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_datanode((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; } /*! Given child tree x1c, find matching child in base tree x0 * param[in] x0 Base tree node * param[in] x1c Modification tree child * param[in] yc Yang spec of tree child * param[out] x0cp Matching base tree child (if any) * @note XXX: room for optimization? on 1K calls we have 1M body calls and 500K xml_child_each/cvec_each calls. The outer loop is large for large lists The inner loop is small Major time in xml_find_body() Can one do a binary search in the x0 list? */ int match_base_child(cxobj *x0, cxobj *x1c, cxobj **x0cp, yang_stmt *yc) { int retval = -1; char *x1cname; cxobj *x0c = NULL; /* x0 child */ cvec *cvk = NULL; /* vector of index keys */ cg_var *cvi; char *b0; char *b1; yang_stmt *ykey; char *keyname; int equal; char **b1vec = NULL; int i; #if (XML_CHILD_HASH==1) cxobj **p; cbuf *key = NULL; /* cligen buffer hash key */ size_t vlen; *x0cp = NULL; /* return value */ if (xml_hash(x0) == NULL) goto nohash; if ((key = cbuf_new()) == NULL){ clicon_err(OE_XML, errno, "cbuf_new"); goto done1; } if (xml_hash_key(x1c, yc, key) < 0) goto done; x0c = NULL; if (cbuf_len(key)) if ((p = hash_value(xml_hash(x0), cbuf_get(key), &vlen)) != NULL){ assert(vlen == sizeof(x0c)); x0c = *p; } // fprintf(stderr, "%s get %s = 0x%x\n", __FUNCTION__, cbuf_get(key), (unsigned int)x0c); *x0cp = x0c; retval = 0; done1: if (key) cbuf_free(key); return retval; nohash: #endif /* XML_CHILD_HASH */ *x0cp = NULL; /* return value */ x1cname = xml_name(x1c); switch (yc->ys_keyword){ case Y_CONTAINER: /* Equal regardless */ case Y_LEAF: /* Equal regardless */ x0c = xml_find(x0, x1cname); break; case Y_LEAF_LIST: /* Match with name and value */ if ((b1 = xml_body(x1c)) == NULL) goto ok; x0c = xml_find_body_obj(x0, x1cname, b1); break; case Y_LIST: /* Match with key values */ if ((ykey = yang_find((yang_node*)yc, Y_KEY, NULL)) == NULL){ clicon_err(OE_XML, errno, "%s: List statement \"%s\" has no key", __FUNCTION__, yc->ys_argument); goto done; } /* The value is a list of keys: [ ]* */ if ((cvk = yang_arg2cvec(ykey, " ")) == NULL) goto done; cvi = NULL; i = 0; while ((cvi = cvec_each(cvk, cvi)) != NULL) i++; if ((b1vec = calloc(i, sizeof(b1))) == NULL){ clicon_err(OE_UNIX, errno, "calloc"); goto done; } cvi = NULL; i = 0; while ((cvi = cvec_each(cvk, cvi)) != NULL) { keyname = cv_string_get(cvi); if ((b1 = xml_find_body(x1c, keyname)) == NULL){ clicon_err(OE_UNIX, errno, "key %s not found", keyname); goto done; } b1vec[i++] = b1; } /* Iterate over x0 tree to (1) find a child that matches name (2) that have keys that matches */ x0c = NULL; while ((x0c = xml_child_each(x0, x0c, CX_ELMNT)) != NULL){ equal = 0; if (strcmp(xml_name(x0c), x1cname)) continue; /* Must be inner loop */ cvi = NULL; i = 0; while ((cvi = cvec_each(cvk, cvi)) != NULL) { b1 = b1vec[i++]; equal = 0; keyname = cv_string_get(cvi); if ((b0 = xml_find_body(x0c, keyname)) == NULL) break; /* error case */ if (strcmp(b0, b1)) break; /* stop as soon as inequal key found */ equal=1; /* reaches here for all keynames, x0c is found. */ } if (equal) /* x0c and x1c equal, otherwise look for other */ break; } /* while x0c */ break; default: break; } ok: *x0cp = x0c; retval = 0; done: if (b1vec) free(b1vec); if (cvk) cvec_free(cvk); return retval; } /*! Find next yang node, either start from yang_spec or some yang-node * @param[in] y Node spec or sny yang-node * @param[in] name Name of childnode to find * @retval ys yang statement * @retval NULL Error: no node found */ static yang_stmt * yang_next(yang_node *y, char *name) { yang_stmt *ys; if (y->yn_keyword == Y_SPEC) ys = yang_find_topnode((yang_spec*)y, name, 0); else ys = yang_find_datanode(y, name); if (ys == NULL) clicon_err(OE_UNIX, errno, "No yang node found: %s", name); return ys; } /*! Recursive help function to compute differences between two xml trees * @param[in] x1 First XML tree * @param[in] x2 Second XML tree * @param[out] x1vec Pointervector to XML nodes existing in only first tree * @param[out] x1veclen Length of first vector * @param[out] x2vec Pointervector to XML nodes existing in only second tree * @param[out] x2veclen Length of x2vec vector * @param[out] changed_x1 Pointervector to XML nodes changed orig value * @param[out] changed_x2 Pointervector to XML nodes changed wanted value * @param[out] changedlen Length of changed vector */ static int xml_diff1(yang_stmt *ys, cxobj *x1, cxobj *x2, cxobj ***x1vec, size_t *x1veclen, cxobj ***x2vec, size_t *x2veclen, cxobj ***changed_x1, cxobj ***changed_x2, size_t *changedlen) { int retval = -1; cxobj *x1c = NULL; /* x1 child */ cxobj *x2c = NULL; /* x2 child */ yang_stmt *yc; char *b1; char *b2; clicon_debug(2, "%s: %s", __FUNCTION__, ys->ys_argument?ys->ys_argument:"yspec"); /* Check nodes present in x1 and x2 + nodes only in x1 * Loop over x1 * XXX: room for improvement. Compare with match_base_child() */ x1c = NULL; while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL){ if ((yc = yang_next((yang_node*)ys, xml_name(x1c))) == NULL) goto done; if (match_base_child(x2, x1c, &x2c, yc) < 0) goto done; if (x2c == NULL){ if (cxvec_append(x1c, x1vec, x1veclen) < 0) goto done; } else{ if (yc->ys_keyword == Y_LEAF){ if ((b1 = xml_body(x1c)) == NULL) /* empty type */ break; if ((b2 = xml_body(x2c)) == NULL) /* empty type */ break; if (strcmp(b1, b2)){ if (cxvec_append(x1c, changed_x1, changedlen) < 0) goto done; (*changedlen)--; /* append two vectors */ if (cxvec_append(x2c, changed_x2, changedlen) < 0) goto done; } } if (xml_diff1(yc, x1c, x2c, x1vec, x1veclen, x2vec, x2veclen, changed_x1, changed_x2, changedlen)< 0) goto done; } } /* while x1 */ /* Check nodes present only in x2 * Loop over x2 */ x2c = NULL; while ((x2c = xml_child_each(x2, x2c, CX_ELMNT)) != NULL){ if ((yc = yang_next((yang_node*)ys, xml_name(x2c))) == NULL) goto done; if (match_base_child(x1, x2c, &x1c, yc) < 0) goto done; if (x1c == NULL) if (cxvec_append(x2c, x2vec, x2veclen) < 0) goto done; } /* while x1 */ retval = 0; done: return retval; } /*! Compute differences between two xml trees * @param[in] yspec Yang specification * @param[in] x1 First XML tree * @param[in] x2 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 orig value * @param[out] changed2 Pointervector to XML nodes changed wanted 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 *x1, cxobj *x2, 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 (x1 == NULL && x2 == NULL) return 0; if (x2 == NULL){ if (cxvec_append(x1, first, firstlen) < 0) goto done; goto ok; } if (x1 == NULL){ if (cxvec_append(x1, second, secondlen) < 0) goto done; goto ok; } if (xml_diff1((yang_stmt*)yspec, x1, x2, 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 0 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){ 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_datanode(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; } /*! Mark all nodes that are not configure data and set return * @param[in] xt XML tree * @param[out] arg If set, set to 1 as int* if not config data */ int xml_non_config_data(cxobj *xt, void *arg) /* Set to 1 if state node */ { int retval = -1; yang_stmt *ys; if ((ys = (yang_stmt*)xml_spec(xt)) == NULL){ retval = 0; goto done; } if (!yang_config(ys)){ /* config == false means state data: mark for remove */ xml_flag_set(xt, XML_FLAG_MARK); if (arg) (*(int*)arg) = 1; } retval = 0; done: return retval; } /*! Add yang specification backpoint to XML node * @param[in] xt XML tree node * @note This should really be unnecessary since yspec should be set on creation * @note For subs to anyxml nodes will not have spec set * @code * xml_apply(xc, CX_ELMNT, xml_spec_populate, yspec) * @endcode */ int xml_spec_populate(cxobj *x, void *arg) { int retval = -1; yang_spec *yspec = (yang_spec*)arg; char *name; yang_stmt *y=NULL; /* yang node */ cxobj *xp; /* xml parent */ yang_stmt *yp; /* parent yang */ name = xml_name(x); if ((xp = xml_parent(x)) != NULL && (yp = xml_spec(xp)) != NULL) y = yang_find_datanode((yang_node*)yp, xml_name(x)); else y = yang_find_topnode(yspec, name, 0); /* still NULL for config */ #ifdef XXX_OBSOLETE /* Add validate elsewhere */ if (y==NULL){ clicon_err(OE_XML, EBADF, "yang spec not found for xml node '%s' xml parent name: '%s' yangspec:'%s']", name, xp?xml_name(xp):"", yp?yp->ys_argument:""); goto done; } #endif if (y) xml_spec_set(x, y); 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[in] schemanode If set use schema nodes otherwise data nodes. * @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, int schemanode, 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; char *local; 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; } /* Split into prefix and localname, ignore prefix for now */ if ((local = index(name, ':')) != NULL){ *local = '\0'; local++; name = local; } if (y0->yn_keyword == Y_SPEC){ /* top-node */ clicon_debug(1, "%s 1 %s", __FUNCTION__, name); y = yang_find_topnode((yang_spec*)y0, name, schemanode); } else { clicon_debug(1, "%s 2 %s", __FUNCTION__, name); y = schemanode?yang_find_schemanode((yang_node*)y0, name): yang_find_datanode((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 (valvec){ free(valvec); valvec = NULL; } if (restval==NULL){ // XXX patch to allow for lists without restval tobe backward compat // clicon_err(OE_XML, 0, "malformed key, expected '='"); // goto done; } else{ 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); 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); val2 = valvec?valvec[j++]:NULL; 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, schemanode, 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[in] yspec Yang spec * @param[in] schemanode If set use schema nodes otherwise data nodes. * @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, int schemanode, 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, schemanode, xpathp, ypathp) < 0) goto done; retval = 0; done: if (vec) free(vec); 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 * Assume x0 and x1 are same on entry and that y is the spec * @see put in clixon_keyvalue.c */ static int xml_merge1(cxobj *x0, yang_node *y0, cxobj *x0p, cxobj *x1) { int retval = -1; char *x1name; char *x1cname; /* child name */ cxobj *x0c; /* base child */ cxobj *x0b; /* base body */ cxobj *x1c; /* mod child */ char *x1bstr; /* mod body string */ yang_stmt *yc; /* yang child */ assert(x1 && xml_type(x1) == CX_ELMNT); assert(y0); x1name = xml_name(x1); if (y0->yn_keyword == Y_LEAF_LIST || y0->yn_keyword == Y_LEAF){ x1bstr = xml_body(x1); if (x0==NULL){ if ((x0 = xml_new_spec(x1name, x0p, y0)) == NULL) goto done; if (x1bstr){ /* empty type does not have body */ if ((x0b = xml_new("body", x0)) == NULL) goto done; xml_type_set(x0b, CX_BODY); } } if (x1bstr){ if ((x0b = xml_body_get(x0)) == NULL){ if ((x0b = xml_new("body", x0)) == NULL) goto done; xml_type_set(x0b, CX_BODY); } if (xml_value_set(x0b, x1bstr) < 0) goto done; } } /* if LEAF|LEAF_LIST */ else { /* eg Y_CONTAINER, Y_LIST */ if (x0==NULL){ if ((x0 = xml_new_spec(x1name, x0p, y0)) == NULL) goto done; } /* Loop through children of the modification tree */ x1c = NULL; while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) { x1cname = xml_name(x1c); /* Get yang spec of the child */ if ((yc = yang_find_datanode(y0, x1cname)) == NULL){ clicon_err(OE_YANG, errno, "No yang node found: %s", x1cname); goto done; } /* See if there is a corresponding node in the base tree */ x0c = NULL; if (yc && match_base_child(x0, x1c, &x0c, yc) < 0) goto done; if (xml_merge1(x0c, (yang_node*)yc, x0, x1c) < 0) goto done; } } /* else Y_CONTAINER */ // ok: retval = 0; done: return retval; } /*! Merge XML trees x1 into x0 according to yang spec yspec * @note both x0 and x1 need to be top-level trees * @see text_modify_top as more generic variant (in datastore text) */ int xml_merge(cxobj *x0, cxobj *x1, yang_spec *yspec) { int retval = -1; char *x1cname; /* child name */ cxobj *x0c; /* base child */ cxobj *x1c; /* mod child */ yang_stmt *yc; /* Loop through children of the modification tree */ x1c = NULL; while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) { x1cname = xml_name(x1c); /* Get yang spec of the child */ if ((yc = yang_find_topnode(yspec, x1cname, 0)) == NULL){ clicon_err(OE_YANG, ENOENT, "No yang spec"); goto done; } /* See if there is a corresponding node in the base tree */ if (match_base_child(x0, x1c, &x0c, yc) < 0) goto done; if (xml_merge1(x0c, (yang_node*)yc, x0, x1c) < 0) goto done; } retval = 0; done: return retval; } /*! Get integer value from xml node from yang enumeration * @param[in] node XML node in a tree * @param[out] val Integer value returned * @retval 0 OK, value parsed * @retval -1 Error, value not obtained or parsed, no reason given * @note assume yang spec set * @note The function only returns assigned values, but according to RFC: If a value is not specified, then one will be automatically assigned. If the "enum" substatement is the first one defined, the assigned value is zero (0); otherwise, the assigned value is one greater than the current highest enum value (i.e., the highest enum value, * Thanks: Matthew Smith */ int yang_enum_int_value(cxobj *node, int32_t *val) { int retval = -1; yang_spec *yspec; yang_stmt *ys; yang_stmt *ytype; yang_stmt *yrestype; /* resolved type */ yang_stmt *yenum; yang_stmt *yval; char *reason = NULL; if (node == NULL) goto done; if ((ys = (yang_stmt *) xml_spec(node)) == NULL) goto done; if ((yspec = ys_spec(ys)) == NULL) goto done; if ((ytype = yang_find((yang_node *)ys, Y_TYPE, NULL)) == NULL) goto done; if (yang_type_resolve(ys, ytype, &yrestype, NULL, NULL, NULL, NULL, NULL) < 0) goto done; if (yrestype==NULL || strcmp(yrestype->ys_argument, "enumeration")) goto done; if ((yenum = yang_find((yang_node *)yrestype, Y_ENUM, xml_body(node))) == NULL) goto done; /* Should assign value if yval not found */ if ((yval = yang_find((yang_node *)yenum, Y_VALUE, NULL)) == NULL) goto done; /* reason is string containing why int could not be parsed */ if (parse_int32(yval->ys_argument, val, &reason) < 0) goto done; retval = 0; done: return retval; }