/* * Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren This file is part of CLIXON. CLIXON is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. CLIXON is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with CLIXON; see the file LICENSE. If not, see . * Yang functions */ #ifdef HAVE_CONFIG_H #include "clixon_config.h" /* generated by config & autoconf */ #endif #include #include #include #include #include #define __USE_GNU /* strverscmp */ #include #include #include #include #include #include #include #include /* cligen */ #include /* clicon */ #include "clixon_log.h" #include "clixon_err.h" #include "clixon_string.h" #include "clixon_queue.h" #include "clixon_hash.h" #include "clixon_handle.h" #include "clixon_file.h" #include "clixon_yang.h" #include "clixon_hash.h" #include "clixon_chunk.h" #include "clixon_options.h" #include "clixon_yang_type.h" #include "clixon_yang_parse.h" /* Instead of using dynamic type lookup, use a cache that is evaluated early for static scope type binding */ #define YANG_TYPE_CACHE 1 /* * Private data types */ /* Struct used to map between int and strings. Used for: * - mapping yang types/typedefs (strings) and cligen types (ints). * - mapping yang keywords (strings) and enum (clicon) */ struct map_str2int{ char *ms_str; /* string as in 4.2.4 in RFC 6020 */ int ms_int; }; /* Mapping between yang keyword string <--> clicon constants */ static const struct map_str2int ykmap[] = { {"anyxml", Y_ANYXML}, {"argument", Y_ARGUMENT}, {"augment", Y_AUGMENT}, {"base", Y_BASE}, {"belongs-to", Y_BELONGS_TO}, {"bit", Y_BIT}, {"case", Y_CASE}, {"choice", Y_CHOICE}, {"config", Y_CONFIG}, {"contact", Y_CONTACT}, {"container", Y_CONTAINER}, {"default", Y_DEFAULT}, {"description", Y_DESCRIPTION}, {"deviate", Y_DEVIATE}, {"deviation", Y_DEVIATION}, {"enum", Y_ENUM}, {"error-app-tag", Y_ERROR_APP_TAG}, {"error_message", Y_ERROR_MESSAGE}, {"extension", Y_EXTENSION}, {"feature", Y_FEATURE}, {"fraction-digits", Y_FRACTION_DIGITS}, {"grouping", Y_GROUPING}, {"identity", Y_IDENTITY}, {"if-feature", Y_IF_FEATURE}, {"import", Y_IMPORT}, {"include", Y_INCLUDE}, {"input", Y_INPUT}, {"key", Y_KEY}, {"leaf", Y_LEAF}, {"leaf-list", Y_LEAF_LIST}, {"length", Y_LENGTH}, {"list", Y_LIST}, {"mandatory", Y_MANDATORY}, {"max-elements", Y_MAX_ELEMENTS}, {"min-elements", Y_MIN_ELEMENTS}, {"module", Y_MODULE}, {"must", Y_MUST}, {"namespace", Y_NAMESPACE}, {"notification", Y_NOTIFICATION}, {"ordered-by", Y_ORDERED_BY}, {"organization", Y_ORGANIZATION}, {"output", Y_OUTPUT}, {"path", Y_PATH}, {"pattern", Y_PATTERN}, {"position", Y_POSITION}, {"prefix", Y_PREFIX}, {"presence", Y_PRESENCE}, {"range", Y_RANGE}, {"reference", Y_REFERENCE}, {"refine", Y_REFINE}, {"require-instance", Y_REQUIRE_INSTANCE}, {"revision", Y_REVISION}, {"revision-date", Y_REVISION_DATE}, {"rpc", Y_RPC}, {"status", Y_STATUS}, {"submodule", Y_SUBMODULE}, {"type", Y_TYPE}, {"typedef", Y_TYPEDEF}, {"unique", Y_UNIQUE}, {"units", Y_UNITS}, {"uses", Y_USES}, {"value", Y_VALUE}, {"when", Y_WHEN}, {"yang-version", Y_YANG_VERSION}, {"yin-element", Y_YIN_ELEMENT}, {"yang-specification", Y_SPEC}, /* XXX: NOTE NOT YANG STATEMENT, reserved for top level spec */ {NULL, -1} }; yang_spec * yspec_new(void) { yang_spec *yspec; if ((yspec = malloc(sizeof(*yspec))) == NULL){ clicon_err(OE_YANG, errno, "%s: malloc", __FUNCTION__); return NULL; } memset(yspec, 0, sizeof(*yspec)); yspec->yp_keyword = Y_SPEC; return yspec; } yang_stmt * ys_new(enum rfc_6020 keyw) { yang_stmt *ys; if ((ys = malloc(sizeof(*ys))) == NULL){ clicon_err(OE_YANG, errno, "%s: malloc", __FUNCTION__); return NULL; } memset(ys, 0, sizeof(*ys)); ys->ys_keyword = keyw; /* The cvec contains stmt-specific variables. Only few stmts need variables so the cvec could be lazily created to save some heap and cycles. */ if ((ys->ys_cvec = cvec_new(0)) == NULL){ clicon_err(OE_YANG, errno, "%s: cvec_new", __FUNCTION__); return NULL; } return ys; } /*! Free a single yang statement */ static int ys_free1(yang_stmt *ys) { if (ys->ys_argument) free(ys->ys_argument); if (ys->ys_cv) cv_free(ys->ys_cv); if (ys->ys_cvec) cvec_free(ys->ys_cvec); if (ys->ys_typecache) yang_type_cache_free(ys->ys_typecache); free(ys); return 0; } /*! Free a tree of yang statements recursively */ int ys_free(yang_stmt *ys) { int i; yang_stmt *yc; for (i=0; iys_len; i++){ if ((yc = ys->ys_stmt[i]) != NULL) ys_free(yc); } if (ys->ys_stmt) free(ys->ys_stmt); ys_free1(ys); return 0; } /*! Free a yang specification recursively */ int yspec_free(yang_spec *yspec) { int i; yang_stmt *ys; for (i=0; iyp_len; i++){ if ((ys = yspec->yp_stmt[i]) != NULL) ys_free(ys); } if (yspec->yp_stmt) free(yspec->yp_stmt); free(yspec); return 0; } /*! Allocate larger yang statement vector adding empty field last */ static int yn_realloc(yang_node *yn) { yn->yn_len++; if ((yn->yn_stmt = realloc(yn->yn_stmt, (yn->yn_len)*sizeof(yang_stmt *))) == 0){ clicon_err(OE_YANG, errno, "%s: realloc", __FUNCTION__); return -1; } yn->yn_stmt[yn->yn_len - 1] = NULL; /* init field */ return 0; } /*! Copy yang statement recursively from old to new */ int ys_cp(yang_stmt *ynew, yang_stmt *yold) { int retval = -1; int i; yang_stmt *ycn; /* new child */ yang_stmt *yco; /* old child */ memcpy(ynew, yold, sizeof(*yold)); ynew->ys_parent = NULL; if (yold->ys_stmt) if ((ynew->ys_stmt = calloc(yold->ys_len, sizeof(yang_stmt *))) == NULL){ clicon_err(OE_YANG, errno, "%s: calloc", __FUNCTION__); goto done; } if (yold->ys_argument) if ((ynew->ys_argument = strdup(yold->ys_argument)) == NULL){ clicon_err(OE_YANG, errno, "%s: strdup", __FUNCTION__); goto done; } if (yold->ys_cv) if ((ynew->ys_cv = cv_dup(yold->ys_cv)) == NULL){ clicon_err(OE_YANG, errno, "%s: cv_dup", __FUNCTION__); goto done; } if (yold->ys_cvec) if ((ynew->ys_cvec = cvec_dup(yold->ys_cvec)) == NULL){ clicon_err(OE_YANG, errno, "%s: cvec_dup", __FUNCTION__); goto done; } if (yold->ys_typecache){ ynew->ys_typecache = NULL; if (yang_type_cache_cp(&ynew->ys_typecache, yold->ys_typecache) < 0) goto done; } for (i=0; iys_len; i++){ yco = yold->ys_stmt[i]; if ((ycn = ys_dup(yco)) == NULL) goto done; ynew->ys_stmt[i] = ycn; ycn->ys_parent = (yang_node*)ynew; } retval = 0; done: return retval; } /*! Create a new yang node and copy the contents recursively from the original. * * This may involve duplicating strings, etc. * The new yang tree needs to be freed by ys_free(). * The parent of new is NULL, it needs to be explicityl inserted somewhere */ yang_stmt * ys_dup(yang_stmt *old) { yang_stmt *new; if ((new = ys_new(old->ys_keyword)) == NULL) return NULL; if (new->ys_cvec){ cvec_free(new->ys_cvec); new->ys_cvec = NULL; } if (ys_cp(new, old) < 0){ ys_free(new); return NULL; } return new; } /*! Insert yang statement as child of a parent yang_statement, last in list * * Also add parent to child as up-pointer */ int yn_insert(yang_node *yn_parent, yang_stmt *ys_child) { int pos = yn_parent->yn_len; if (yn_realloc(yn_parent) < 0) return -1; yn_parent->yn_stmt[pos] = ys_child; ys_child->ys_parent = yn_parent; return 0; } /*! Iterate through all yang statements from a yang node * * Note that this is not optimized, one could use 'i' as index? * @code * yang_stmt *ys = NULL; * while ((ys = yn_each(yn, ys)) != NULL) { * ...ys... * } * @endcode */ yang_stmt * yn_each(yang_node *yn, yang_stmt *ys) { yang_stmt *yc = NULL; int i; for (i=0; iyn_len; i++){ yc = yn->yn_stmt[i]; if (ys==NULL) return yc; if (ys==yc) ys = NULL; } return NULL; } /*! Find first child yang_stmt with matching keyword and argument * * @param[in] yn Yang node, current context node. * @param[in] keyword if 0 match any keyword * @param[in] argument if NULL, match any argument. * This however means that if you actually want to match only a yang-stmt with * argument==NULL you cannot, but I have not seen any such examples. * @see yang_find_syntax */ yang_stmt * yang_find(yang_node *yn, int keyword, char *argument) { yang_stmt *ys = NULL; int i; int match = 0; for (i=0; iyn_len; i++){ ys = yn->yn_stmt[i]; if (keyword == 0 || ys->ys_keyword == keyword){ if (argument == NULL) match++; else if (ys->ys_argument && strcmp(argument, ys->ys_argument) == 0) match++; if (match) break; } } return match ? ys : NULL; } /*! Find a child syntax-node yang_stmt with matching argument (container, leaf, etc) * * @param[in] yn Yang node, current context node. * @param[in] argument if NULL, match any(first) argument. * * @see yang_find But this looks only for the yang specification nodes with * the following keyword: container, leaf, list, leaf-list * That is, basic syntax nodes. * @note check if argument==NULL really required? */ /*! Is this yang-stmt a container, list, leaf or leaf-list? */ #define yang_is_syntax(y) ((y)->ys_keyword == Y_CONTAINER || (y)->ys_keyword == Y_LEAF || (y)->ys_keyword == Y_LIST || (y)->ys_keyword == Y_LEAF_LIST) yang_stmt * yang_find_syntax(yang_node *yn, char *argument) { yang_stmt *ys = NULL; yang_stmt *yc = NULL; yang_stmt *ysmatch = NULL; int i, j; for (i=0; iyn_len; i++){ ys = yn->yn_stmt[i]; if (ys->ys_keyword == Y_CHOICE){ /* Look for its children */ for (j=0; jys_len; j++){ yc = ys->ys_stmt[j]; if (yc->ys_keyword == Y_CASE) /* Look for its children */ ysmatch = yang_find_syntax((yang_node*)yc, argument); else if (yang_is_syntax(yc)){ if (argument == NULL) ysmatch = yc; else if (yc->ys_argument && strcmp(argument, yc->ys_argument) == 0) ysmatch = yc; } if (ysmatch) goto match; } } else if (yang_is_syntax(ys)){ if (argument == NULL) ysmatch = ys; else if (ys->ys_argument && strcmp(argument, ys->ys_argument) == 0) ysmatch = ys; if (ysmatch) goto match; } } match: return ysmatch; } /*! Help function to check find 'top-node', eg first 'syntax' node in a spec * A yang specification has modules as children which in turn can have * syntax-nodes as children. This function goes through all the modules to * look for syntax-nodes. Note that if a child to a module is a choice, * the search is made recursively made to the choice's children. */ yang_stmt * yang_find_topnode(yang_spec *ysp, char *name) { yang_stmt *ys = NULL; yang_stmt *yc = NULL; int i; for (i=0; iyp_len; i++){ ys = ysp->yp_stmt[i]; if ((yc = yang_find_syntax((yang_node*)ys, name)) != NULL) return yc; } return NULL; } /*! Find a child spec-node yang_stmt with matching argument for xpath * * See also yang_find() but this looks only for the yang specification nodes with * the following keyword: container, leaf, list, leaf-list * That is, basic syntax nodes. * @see yang_find_syntax # Maybe this is the same as specnode? * @see clicon_dbget_xpath * @see xpath_vec */ static yang_stmt * yang_find_xpath_stmt(yang_node *yn, char *argument) { yang_stmt *ys = NULL; int i; int match = 0; for (i=0; iyn_len; i++){ ys = yn->yn_stmt[i]; /* some keys dont have arguments, match on key */ if (ys->ys_keyword == Y_INPUT || ys->ys_keyword == Y_OUTPUT){ if (strcmp(argument, yang_key2str(ys->ys_keyword)) == 0) match++; } else if (ys->ys_keyword == Y_CONTAINER || ys->ys_keyword == Y_LEAF || ys->ys_keyword == Y_LIST || ys->ys_keyword == Y_LEAF_LIST || ys->ys_keyword == Y_MODULE || ys->ys_keyword == Y_SUBMODULE || ys->ys_keyword == Y_RPC || ys->ys_keyword == Y_CHOICE || ys->ys_keyword == Y_CASE ){ if (ys->ys_argument && strcmp(argument, ys->ys_argument) == 0) match++; } if (match) break; } return match ? ys : NULL; } /*! Reset flag in complete tree, arg contains flag */ static int ys_flag_reset(yang_stmt *ys, void *arg) { int flags = (intptr_t)arg; ys->ys_flags |= ~flags; return 0; } /*! Translate from RFC 6020 keywords to printable string. linear search,... */ char * yang_key2str(int keyword) { const struct map_str2int *yk; for (yk = &ykmap[0]; yk->ms_str; yk++) if (yk->ms_int == keyword) return yk->ms_str; return NULL; } /*! Find top module or sub-module. Note that ultimate top is yang spec * @param[in] ys Any yang statement in a yang tree * @retval ymod The top module or sub-module * @see ys_spec */ yang_stmt * ys_module(yang_stmt *ys) { yang_node *yn; if (ys==NULL || ys->ys_keyword==Y_SPEC) return ys; while (ys != NULL && ys->ys_keyword != Y_MODULE && ys->ys_keyword != Y_SUBMODULE){ yn = ys->ys_parent; /* Some extra stuff to ensure ys is a stmt */ if (yn && yn->yn_keyword == Y_SPEC) yn = NULL; ys = (yang_stmt*)yn; } /* Here it is either NULL or is a typedef-kind yang-stmt */ return ys; } /*! Find top of tree, the yang specification * @param[in] ys Any yang statement in a yang tree * @retval yspec The top yang specification * @see ys_module */ yang_spec * ys_spec(yang_stmt *ys) { yang_node *yn; while (ys != NULL && ys->ys_keyword != Y_SPEC){ yn = ys->ys_parent; ys = (yang_stmt*)yn; } /* Here it is either NULL or is a typedef-kind yang-stmt */ return (yang_spec*)ys; } /* Extract id from type argument. two cases: * argument is prefix:id, * argument is id, * Just return string from id */ char* ytype_id(yang_stmt *ys) { char *id; if ((id = strchr(ys->ys_argument, ':')) == NULL) id = ys->ys_argument; else id++; return id; } /* Extract prefix from type argument. two cases: * argument is prefix:id, * argument is id, * return either NULL or a new prefix string that needs to be freed by caller. */ char* ytype_prefix(yang_stmt *ys) { char *id; char *prefix = NULL; if ((id = strchr(ys->ys_argument, ':')) != NULL){ prefix = strdup(ys->ys_argument); prefix[id-ys->ys_argument] = '\0'; } return prefix; } /*! Given a module and a prefix, find the import statement fo that prefix * Note, not the other module but the proxy import statement only * @param[in] ytop yang module */ yang_stmt * ys_module_import(yang_stmt *ymod, char *prefix) { yang_stmt *yimport = NULL; yang_stmt *yprefix; assert(ymod->ys_keyword == Y_MODULE || ymod->ys_keyword == Y_SUBMODULE); while ((yimport = yn_each((yang_node*)ymod, yimport)) != NULL) { if (yimport->ys_keyword != Y_IMPORT) continue; if ((yprefix = yang_find((yang_node*)yimport, Y_PREFIX, NULL)) != NULL && strcmp(yprefix->ys_argument, prefix) == 0) return yimport; } return NULL; } /*! string is quoted if it contains space or tab, needs double '' */ static int inline quotedstring(char *s) { int len = strlen(s); int i; for (i=0; iys_keyword)); fflush(f); if (ys->ys_argument){ if (quotedstring(ys->ys_argument)) fprintf(f, " \"%s\"", ys->ys_argument); else fprintf(f, " %s", ys->ys_argument); } if (ys->ys_len){ fprintf(f, " {\n"); yang_print(f, (yang_node*)ys, marginal+3); fprintf(f, "%*s%s\n", marginal, "", "}"); } else fprintf(f, ";\n"); } return 0; } /*! Print yang specification to cligen buf * @code * cbuf *cb = cbuf_new(); * yang_print_cbuf(cb, yn, 0); * // output is in cbuf_buf(cb); * cbuf_free(cb); * @endcode * @see yang_print */ int yang_print_cbuf(cbuf *cb, yang_node *yn, int marginal) { yang_stmt *ys = NULL; while ((ys = yn_each(yn, ys)) != NULL) { cprintf(cb, "%*s%s", marginal, "", yang_key2str(ys->ys_keyword)); if (ys->ys_argument){ if (quotedstring(ys->ys_argument)) cprintf(cb, " \"%s\"", ys->ys_argument); else cprintf(cb, " %s", ys->ys_argument); } if (ys->ys_len){ cprintf(cb, " {\n"); yang_print_cbuf(cb, (yang_node*)ys, marginal+3); cprintf(cb, "%*s%s\n", marginal, "", "}"); } else cprintf(cb, ";\n"); } return 0; } /*! Populate yang leafs after parsing. Create cv and fill it in. * * Populate leaf in 2nd round of yang parsing, now that context is complete: * 1. Find type specification and set cv type accordingly * 2. Create the CV using cvtype and name it * 3. Check if default value. Here we parse the string in the default-stmt and add it to leafs cv. * 4. Check if leaf is part of list, if key exists mark leaf as key/unique * XXX: extend type search * * @param[in] ys The yang statement to populate. * @param[in] arg A void argument not used * @retval 0 OK * @retval -1 Error with clicon_err called */ static int ys_populate_leaf(yang_stmt *ys, void *arg) { int retval = -1; cg_var *cv = NULL; yang_node *yparent; yang_stmt *ydef; enum cv_type cvtype = CGV_ERR; int cvret; int ret; char *reason = NULL; yang_stmt *yrestype; /* resolved type */ char *restype; /* resolved type */ char *type; /* original type */ uint8_t fraction_digits; int options = 0x0; yparent = ys->ys_parent; /* Find parent: list/container */ /* 1. Find type specification and set cv type accordingly */ if (yang_type_get(ys, &type, &yrestype, &options, NULL, NULL, NULL, &fraction_digits) < 0) goto done; restype = yrestype?yrestype->ys_argument:NULL; if (clicon_type2cv(type, restype, &cvtype) < 0) /* This handles non-resolved also */ goto done; /* 2. Create the CV using cvtype and name it */ if ((cv = cv_new(cvtype)) == NULL){ clicon_err(OE_YANG, errno, "%s: cv_new", __FUNCTION__); goto done; } if (options & YANG_OPTIONS_FRACTION_DIGITS && cvtype == CGV_DEC64) /* XXX: Seems misplaced? / too specific */ cv_dec64_n_set(cv, fraction_digits); if (cv_name_set(cv, ys->ys_argument) == NULL){ clicon_err(OE_YANG, errno, "%s: cv_new_set", __FUNCTION__); goto done; } /* 3. Check if default value. Here we parse the string in the default-stmt * and add it to the leafs cv. */ if ((ydef = yang_find((yang_node*)ys, Y_DEFAULT, NULL)) != NULL){ if ((cvret = cv_parse1(ydef->ys_argument, cv, &reason)) < 0){ /* error */ clicon_err(OE_YANG, errno, "parsing cv"); goto done; } if (cvret == 0){ /* parsing failed */ clicon_err(OE_YANG, errno, "Parsing CV: %s", reason); free(reason); goto done; } } else{ /* 3b. If not default value, indicate empty cv. */ cv_flag_set(cv, V_UNSET); /* no value (no default) */ } /* 4. Check if leaf is part of list, if key exists mark leaf as key/unique */ if (yparent && yparent->yn_keyword == Y_LIST){ if ((ret = yang_key_match(yparent, ys->ys_argument)) < 0) goto done; if (ret == 1) cv_flag_set(cv, V_UNIQUE); } ys->ys_cv = cv; retval = 0; done: if (cv && retval < 0) cv_free(cv); return retval; } /*! Populate range and length statements * * Create cvec variables "range_min" and "range_max". Assume parent is type. * Actually: min..max [| min..max]* * where min,max is integer or keywords 'min' or 'max. * We only allow one range, ie not 1..2|4..5 */ static int ys_populate_range(yang_stmt *ys, void *arg) { int retval = -1; yang_node *yparent; /* type */ char *origtype; /* orig type */ yang_stmt *yrestype; /* resolved type */ char *restype; /* resolved type */ int options = 0x0; uint8_t fraction_digits; enum cv_type cvtype = CGV_ERR; char *minstr = NULL; char *maxstr; cg_var *cv; char *reason = NULL; int cvret; yparent = ys->ys_parent; /* Find parent: type */ if (yparent->yn_keyword != Y_TYPE){ clicon_err(OE_YANG, 0, "%s: parent should be type", __FUNCTION__); goto done; } if (yang_type_resolve(ys, (yang_stmt*)yparent, &yrestype, &options, NULL, NULL, NULL, &fraction_digits) < 0) goto done; restype = yrestype?yrestype->ys_argument:NULL; origtype = ytype_id((yang_stmt*)yparent); /* This handles non-resolved also */ if (clicon_type2cv(origtype, restype, &cvtype) < 0) goto done; /* special case for strings, where limit is length, not a string */ if (cvtype == CGV_STRING) cvtype = CGV_UINT64; if ((minstr = strdup(ys->ys_argument)) == NULL){ clicon_err(OE_YANG, errno, "strdup"); goto done; } if ((maxstr = strstr(minstr, "..")) != NULL){ if (strlen(maxstr) < 2){ clicon_err(OE_YANG, 0, "range statement: %s not on the form: .."); goto done; } minstr[maxstr-minstr] = '\0'; maxstr += 2; /* minstr and maxstr need trimming */ if (isblank(minstr[strlen(minstr)-1])) minstr[strlen(minstr)-1] = '\0'; if (isblank(maxstr[0])) maxstr++; if ((cv = cvec_add(ys->ys_cvec, cvtype)) == NULL){ clicon_err(OE_YANG, errno, "cvec_add"); goto done; } if (cv_name_set(cv, "range_min") == NULL){ clicon_err(OE_YANG, errno, "cv_name_set"); goto done; } if (options & YANG_OPTIONS_FRACTION_DIGITS && cvtype == CGV_DEC64) cv_dec64_n_set(cv, fraction_digits); if ((cvret = cv_parse1(minstr, cv, &reason)) < 0){ clicon_err(OE_YANG, errno, "cv_parse1"); goto done; } if (cvret == 0){ /* parsing failed */ clicon_err(OE_YANG, errno, "range statement, min: %s", reason); free(reason); goto done; } } else maxstr = minstr; if (strcmp(maxstr, "max") != 0){ /* no range_max means max */ if ((cv = cvec_add(ys->ys_cvec, cvtype)) == NULL){ clicon_err(OE_YANG, errno, "cvec_add"); goto done; } if (cv_name_set(cv, "range_max") == NULL){ clicon_err(OE_YANG, errno, "cv_name_set"); goto done; } if (options & YANG_OPTIONS_FRACTION_DIGITS && cvtype == CGV_DEC64) cv_dec64_n_set(cv, fraction_digits); if ((cvret = cv_parse1(maxstr, cv, &reason)) < 0){ clicon_err(OE_YANG, errno, "cv_parse1"); goto done; } if (cvret == 0){ /* parsing failed */ clicon_err(OE_YANG, errno, "range statement, max: %s", reason); free(reason); goto done; } } retval = 0; done: if (minstr) free(minstr); return retval; } /*! Sanity check yang type statement * XXX: Replace with generic parent/child type-check */ static int ys_populate_type(yang_stmt *ys, void *arg) { int retval = -1; yang_stmt *ybase; if (strcmp(ys->ys_argument, "decimal64") == 0){ if (yang_find((yang_node*)ys, Y_FRACTION_DIGITS, NULL) == NULL){ clicon_err(OE_YANG, 0, "decimal64 type requires fraction-digits sub-statement"); goto done; } } else if (strcmp(ys->ys_argument, "identityref") == 0){ if ((ybase = yang_find((yang_node*)ys, Y_BASE, NULL)) == NULL){ clicon_err(OE_YANG, 0, "identityref type requires base sub-statement"); goto done; } if ((yang_find_identity(ys, ybase->ys_argument)) == NULL){ clicon_err(OE_YANG, 0, "Identity %s not found (base type of %s)", ybase->ys_argument, ys->ys_argument); goto done; } } else if (strcmp(ys->ys_argument, "leafref") == 0){ #ifdef notyet /* XXX: Do augment first */ yang_stmt *ypath; if ((ypath = yang_find((yang_node*)ys, Y_PATH, NULL)) == NULL){ clicon_err(OE_YANG, 0, "leafref type requires path sub-statement"); goto done; } if (yang_xpath((yang_node*)ys, ypath->ys_argument) == NULL){ clicon_err(OE_YANG, 0, "Leafref path %s not found", ypath->ys_argument); goto done; } #endif } retval = 0; done: return retval; } /*! Sanity check yang type statement */ static int ys_populate_identity(yang_stmt *ys, void *arg) { int retval = -1; yang_stmt *ybase; if ((ybase = yang_find((yang_node*)ys, Y_BASE, NULL)) == NULL) return 0; if ((yang_find_identity(ys, ybase->ys_argument)) == NULL){ clicon_err(OE_YANG, 0, "Identity %s not found (base type of %s)", ybase->ys_argument, ys->ys_argument); goto done; } retval = 0; done: return retval; } /*! Populate with cligen-variables, default values, etc. Sanity checks on complete tree. * * We do this in 2nd pass after complete parsing to be sure to have a complete parse-tree * See ys_parse_sub for first pass and what can be assumed * After this pass, cv:s are set for LEAFs and LEAF-LISTs */ static int ys_populate(yang_stmt *ys, void *arg) { int retval = -1; switch(ys->ys_keyword){ case Y_LEAF: case Y_LEAF_LIST: if (ys_populate_leaf(ys, arg) < 0) goto done; break; case Y_RANGE: case Y_LENGTH: if (ys_populate_range(ys, arg) < 0) goto done; break; case Y_MANDATORY: /* call yang_mandatory() to check if set */ case Y_CONFIG: if (ys_parse(ys, CGV_BOOL) == NULL) goto done; break; case Y_TYPE: if (ys_populate_type(ys, arg) < 0) goto done; break; case Y_IDENTITY: if (ys_populate_identity(ys, arg) < 0) goto done; break; default: break; } retval = 0; done: return retval; } /*! Resolve a grouping name from a point in the yang tree * @retval 0 OK, but ygrouping determines if a grouping was resolved or not * @retval -1 Error, with clicon_err called */ static int ys_grouping_resolve(yang_stmt *ys, char *prefix, char *name, yang_stmt **ygrouping0) { int retval = -1; yang_stmt *yimport; yang_spec *yspec; yang_stmt *ymodule; yang_stmt *ygrouping = NULL; yang_node *yn; yang_stmt *ymod; /* find the grouping associated with argument and expand(?) */ if (prefix){ /* Go to top and find import that matches */ ymod = ys_module(ys); if ((yimport = ys_module_import(ymod, prefix)) == NULL){ clicon_err(OE_DB, 0, "Prefix %s not defined not found", prefix); goto done; } yspec = ys_spec(ys); if ((ymodule = yang_find((yang_node*)yspec, Y_MODULE, yimport->ys_argument)) != NULL) ygrouping = yang_find((yang_node*)ymodule, Y_GROUPING, name); } else while (1){ /* Check upwards in hierarchy for matching groupings */ if ((yn = ys->ys_parent) == NULL || yn->yn_keyword == Y_SPEC) break; /* Here find grouping */ if ((ygrouping = yang_find(yn, Y_GROUPING, name)) != NULL) break; /* Proceed to next level */ ys = (yang_stmt*)yn; } *ygrouping0 = ygrouping; retval = 0; done: return retval; } /*! This is an augment node, augment the original datamodel. The target node MUST be either a container, list, choice, case, input, output, or notification node. If the "augment" statement is on the top level the absolute form MUST be used. XXX: Destructively changing a datamodel may affect outlying loop? */ static int yang_augment_node(yang_stmt *ys, yang_spec *ysp) { int retval = -1; char *path; yang_node *yn; yang_stmt *yc; int i; path = ys->ys_argument; clicon_debug(1, "%s %s", __FUNCTION__, path); /* Find the target */ if ((yn = yang_xpath_abs((yang_node*)ys, path)) == NULL){ clicon_err(OE_YANG, 0, "Augment path %s not found", path); // retval = 0; /* Ignore, continue */ goto done; } /* Extend yn with ys' children * First enlarge yn vector */ for (i=0; iys_len; i++){ if ((yc = ys_dup(ys->ys_stmt[i])) == NULL) goto done; /* XXX: use prefix of origin */ if (yn_insert(yn, yc) < 0) goto done; } retval = 0; done: return retval; } /*! Find all top-level augments and change original datamodels. */ static int yang_augment_spec(yang_spec *ysp) { int retval = -1; yang_stmt *ym; yang_stmt *ys; int i; int j; i = 0; while (iyp_len){ /* Loop through modules and sub-modules */ ym = ysp->yp_stmt[i++]; j = 0; while (jys_len){ /* Top-level symbols in modules */ ys = ym->ys_stmt[j++]; if (ys->ys_keyword != Y_AUGMENT) continue; if (yang_augment_node(ys, ysp) < 0) goto done; } } retval = 0; done: return retval; } /*! Macro expansion of grouping/uses done in step 2 of yang parsing NOTE RFC6020 says this: Identifiers appearing inside the grouping are resolved relative to the scope in which the grouping is defined, not where it is used. Prefix mappings, type names, grouping names, and extension usage are evaluated in the hierarchy where the "grouping" statement appears. But it will be very difficult to generate keys etc with this semantics. So for now I macro-expand them */ static int yang_expand(yang_node *yn) { int retval = -1; yang_stmt *ys = NULL; yang_stmt *ygrouping; yang_stmt *yg; int glen; int i; int j; char *name; char *prefix; size_t size; /* Cannot use yang_apply here since child-list is modified (is destructive) */ i = 0; while (iyn_len){ ys = yn->yn_stmt[i]; switch(ys->ys_keyword){ case Y_USES: /* Split argument into prefix and name */ name = ytype_id(ys); /* This is uses/grouping name to resolve */ prefix = ytype_prefix(ys); /* And this its prefix */ if (ys_grouping_resolve(ys, prefix, name, &ygrouping) < 0) goto done; if (prefix) free(prefix); if (ygrouping == NULL){ clicon_log(LOG_NOTICE, "%s: Yang error : grouping \"%s\" not found", __FUNCTION__, ys->ys_argument); goto done; break; } /* Check mark flag to see if this grouping (itself) has been expanded If not, this needs to be done before we can insert it into the 'uses' place */ if ((ygrouping->ys_flags & YANG_FLAG_MARK) == 0){ if (yang_expand((yang_node*)ygrouping) < 0) goto done; ygrouping->ys_flags |= YANG_FLAG_MARK; /* Mark as expanded */ } /* Replace ys with ygrouping,... * First enlarge parent vector */ glen = ygrouping->ys_len; /* * yn is parent: the children of ygrouping replaces ys. * Is there a case when glen == 0? YES AND THIS BREAKS */ if (glen != 1){ size = (yn->yn_len - i - 1)*sizeof(struct yang_stmt *); yn->yn_len += glen - 1; if (glen && (yn->yn_stmt = realloc(yn->yn_stmt, (yn->yn_len)*sizeof(yang_stmt *))) == 0){ clicon_err(OE_YANG, errno, "%s: realloc", __FUNCTION__); return -1; } /* Then move all existing elements up from i+1 (not uses-stmt) */ if (size) memmove(&yn->yn_stmt[i+glen], &yn->yn_stmt[i+1], size); } /* Then copy and insert each child element */ for (j=0; jys_stmt[j])) == NULL) goto done; yn->yn_stmt[i+j] = yg; yg->ys_parent = yn; } /* XXX: refine */ /* Remove 'uses' node */ ys_free(ys); break; /* Note same child is re-iterated since it may be changed */ default: i++; break; } } /* Second pass since length may have changed */ for (i=0; iyn_len; i++){ ys = yn->yn_stmt[i]; if (yang_expand((yang_node*)ys) < 0) goto done; } retval = 0; done: return retval; } /*! Parse a string containing a YANG spec into a parse-tree * * Syntax parsing. A string is input and a syntax-tree is returned (or error). * A variable record is also returned containing a list of (global) variable values. * (cloned from cligen) * @param h CLICON handle * @param str String of yang statements * @param name Log string, typically filename * @param ysp Yang specification. Should ave been created by caller using yspec_new * @retval 0 Everything OK * @retval -1 Error encountered * Calling order: * yang_parse # Parse top-level yang module. Expand and populate yang tree * yang_parse1 # Parse one yang module, go through its (sub)modules and parse them * yang_parse2 # Find file from yang (sub)module * yang_parse_file # Read yang file into a string * yang_parse_str # Set up yacc parser and call it given a string * clixon_yang_parseparse # Actual yang parsing using yacc */ static yang_stmt * yang_parse_str(clicon_handle h, char *str, const char *name, /* just for errs */ yang_spec *yspec) { struct clicon_yang_yacc_arg yy = {0,}; yang_stmt *ym = NULL; yy.yy_handle = h; yy.yy_name = (char*)name; yy.yy_linenum = 1; yy.yy_parse_string = str; yy.yy_stack = NULL; yy.yy_module = NULL; /* this is the return value - the module/sub-module */ if (ystack_push(&yy, (yang_node*)yspec) == NULL) goto done; if (strlen(str)){ /* Not empty */ if (yang_scan_init(&yy) < 0) goto done; if (yang_parse_init(&yy, yspec) < 0) goto done; if (clixon_yang_parseparse(&yy) != 0) { /* yacc returns 1 on error */ clicon_log(LOG_NOTICE, "Yang error: %s on line %d", name, yy.yy_linenum); if (clicon_errno == 0) clicon_err(OE_YANG, 0, "yang parser error with no error code (should not happen)"); yang_parse_exit(&yy); yang_scan_exit(&yy); goto done; } if (yang_parse_exit(&yy) < 0) goto done; if (yang_scan_exit(&yy) < 0) goto done; } ym = yy.yy_module; done: ystack_pop(&yy); return ym; } /*! Read an opened file into a string and call yang string parsing * * Similar to clicon_yang_str(), just read a file first * (cloned from cligen) * @param h CLICON handle * @param f Open file handle * @param name Log string, typically filename * @param ysp Yang specification. Should ave been created by caller using yspec_new * @retval 0 Everything OK * @retval -1 Error encountered * The database symbols are inserted in alphabetical order. * Calling order: * yang_parse # Parse top-level yang module. Expand and populate yang tree * yang_parse1 # Parse one yang module, go through its (sub)modules and parse them * yang_parse2 # Find file from yang (sub)module * yang_parse_file # Read yang file into a string * yang_parse_str # Set up yacc parser and call it given a string * clixon_yang_parseparse # Actual yang parsing using yacc */ static yang_stmt * yang_parse_file(clicon_handle h, FILE *f, const char *name, /* just for errs */ yang_spec *ysp ) { char *buf; int i; int c; int len; yang_stmt *ymodule = NULL; clicon_debug(1, "Yang parse file: %s", name); len = 1024; /* any number is fine */ if ((buf = malloc(len)) == NULL){ perror("pt_file malloc"); return NULL; } memset(buf, 0, len); i = 0; /* position in buf */ while (1){ /* read the whole file */ if ((c = fgetc(f)) == EOF) break; if (len==i){ if ((buf = realloc(buf, 2*len)) == NULL){ fprintf(stderr, "%s: realloc: %s\n", __FUNCTION__, strerror(errno)); goto done; } memset(buf+len, 0, len); len *= 2; } buf[i++] = (char)(c&0xff); } /* read a line */ if ((ymodule = yang_parse_str(h, buf, name, ysp)) < 0) goto done; done: if (buf) free(buf); return ymodule; } /*! No specific revision give. Match a yang file given dir and module * @param[in] h CLICON handle * @param[in] yang_dir Directory where all YANG module files reside * @param[in] module Name of main YANG module. * @param[out] fbuf Buffer containing filename * * @retval 1 Match founbd, Most recent entry returned in fbuf * @retval 0 No matching entry found * @retval -1 Error */static int yang_parse_find_match(clicon_handle h, const char *yang_dir, const char *module, cbuf *fbuf) { int retval = -1; struct dirent *dp; int ndp; cbuf *regex = NULL; char *regexstr; if ((regex = cbuf_new()) == NULL){ clicon_err(OE_YANG, errno, "%s: cbuf_new", __FUNCTION__); goto done; } cprintf(regex, "^%s.*(.yang)$", module); regexstr = cbuf_get(regex); if ((ndp = clicon_file_dirent(yang_dir, &dp, regexstr, S_IFREG, __FUNCTION__)) < 0) goto done; /* Entries are sorted, last entry should be most recent date */ if (ndp != 0){ cprintf(fbuf, "%s/%s", yang_dir, dp[ndp-1].d_name); retval = 1; } else retval = 0; done: if (regex) cbuf_free(regex); unchunk_group(__FUNCTION__); return retval; } /*! Find and open yang file and then parse it * * @param h CLICON handle * @param yang_dir Directory where all YANG module files reside * @param module Name of main YANG module. More modules may be parsed if imported * @param revision Optional module revision date * @param ysp Yang specification. Should ave been created by caller using yspec_new * @retval 0 Everything OK * @retval -1 Error encountered * module-or-submodule-name ['@' revision-date] ( '.yang' / '.yin' ) * Calling order: * yang_parse # Parse top-level yang module. Expand and populate yang tree * yang_parse1 # Parse one yang module, go through its (sub)modules and parse them * yang_parse2 # Find file from yang (sub)module * yang_parse_file # Read yang file into a string * yang_parse_str # Set up yacc parser and call it given a string * clixon_yang_parseparse # Actual yang parsing using yacc */ static yang_stmt * yang_parse2(clicon_handle h, const char *yang_dir, const char *module, const char *revision, yang_spec *ysp) { FILE *f = NULL; cbuf *fbuf = NULL; char *filename; yang_stmt *ys = NULL; struct stat st; int nr; if ((fbuf = cbuf_new()) == NULL){ clicon_err(OE_YANG, errno, "%s: cbuf_new", __FUNCTION__); goto done; } if (revision) cprintf(fbuf, "%s/%s@%s.yang", yang_dir, module, revision); else{ /* No specific revision, Match a yang file */ if ((nr = yang_parse_find_match(h, yang_dir, module, fbuf)) < 0) goto done; if (nr == 0){ clicon_err(OE_YANG, errno, "No matching %s yang files found", module); goto done; } } filename = cbuf_get(fbuf); if (stat(filename, &st) < 0){ clicon_err(OE_YANG, errno, "%s not found", filename); goto done; } if ((f = fopen(filename, "r")) == NULL){ clicon_err(OE_UNIX, errno, "fopen(%s)", filename); goto done; } if ((ys = yang_parse_file(h, f, filename, ysp)) == NULL) goto done; done: if (fbuf) cbuf_free(fbuf); if (f) fclose(f); return ys; } /*! Parse one yang module then go through (sub)modules and parse them recursively * * @param h CLICON handle * @param yang_dir Directory where all YANG module files reside * @param module Name of main YANG module. More modules may be parsed if imported * @param revision Optional module revision date * @param ysp Yang specification. Should ave been created by caller using yspec_new * @retval 0 Everything OK * @retval -1 Error encountered * Find a yang module file, and then recursively parse all its imported modules. * Calling order: * yang_parse # Parse top-level yang module. Expand and populate yang tree * yang_parse1 # Parse one yang module, go through its (sub)modules and parse them * yang_parse2 # Find file from yang (sub)module * yang_parse_file # Read yang file into a string * yang_parse_str # Set up yacc parser and call it given a string * clixon_yang_parseparse # Actual yang parsing using yacc */ static yang_stmt * yang_parse1(clicon_handle h, const char *yang_dir, const char *module, const char *revision, yang_spec *ysp) { yang_stmt *yi = NULL; /* import */ yang_stmt *ys; yang_stmt *yrev; char *modname; char *subrevision; if ((ys = yang_parse2(h, yang_dir, module, revision, ysp)) == NULL) goto done; /* go through all import statements of ysp (or its module) */ while ((yi = yn_each((yang_node*)ys, yi)) != NULL){ if (yi->ys_keyword != Y_IMPORT) continue; modname = yi->ys_argument; if ((yrev = yang_find((yang_node*)yi, Y_REVISION_DATE, NULL)) != NULL) subrevision = yrev->ys_argument; else subrevision = NULL; if (yang_find((yang_node*)ysp, Y_MODULE, modname) == NULL) if (yang_parse1(h, yang_dir, modname, subrevision, ysp) == NULL){ ys = NULL; goto done; } } done: return ys; } /*! Parse top yang module including all its sub-modules. Expand and populate yang tree * * @param h CLICON handle * @param yang_dir Directory where all YANG module files reside * @param module Name of main YANG module. More modules may be parsed if imported * @param revision Optional module revision date * @param ysp Yang specification. Should ave been created by caller using yspec_new * @retval 0 Everything OK * @retval -1 Error encountered * The database symbols are inserted in alphabetical order. * Find a yang module file, and then recursively parse all its imported modules. * Calling order: * yang_parse # Parse top-level yang module. Expand and populate yang tree * yang_parse1 # Parse one yang module, go through its (sub)modules and parse them * yang_parse2 # Find file from yang (sub)module * yang_parse_file # Read yang file into a string * yang_parse_str # Set up yacc parser and call it given a string * clixon_yang_parseparse # Actual yang parsing using yacc */ int yang_parse(clicon_handle h, const char *yang_dir, const char *module, const char *revision, yang_spec *ysp) { int retval = -1; yang_stmt *ys; /* Step 1: parse from text to yang parse-tree. */ if ((ys = yang_parse1(h, yang_dir, module, revision, ysp)) == NULL) goto done; /* Add top module name as dbspec-name */ clicon_dbspec_name_set(h, ys->ys_argument); #ifdef YANG_TYPE_CACHE /* Resolve all types */ yang_apply((yang_node*)ys, ys_resolve_type, NULL); #endif /* Step 2: Macro expansion of all grouping/uses pairs. Expansion needs marking */ if (yang_expand((yang_node*)ysp) < 0) goto done; yang_apply((yang_node*)ys, ys_flag_reset, (void*)YANG_FLAG_MARK); /* Step 4: Go through parse tree and populate it with cv types */ if (yang_apply((yang_node*)ysp, ys_populate, NULL) < 0) goto done; /* Step 3: Top-level augmentation of all modules */ if (yang_augment_spec(ysp) < 0) goto done; retval = 0; done: return retval; } /*! Apply a function call recursively on all yang-stmt s recursively * * Recursively traverse all yang-nodes in a parse-tree and apply fn(arg) for * each object found. The function is called with the yang-stmt and an * argument as args. * The tree is traversed depth-first, which at least guarantees that a parent is * traversed before a child. * @param[in] xn XML node * @param[in] type matching type or -1 for any * @param[in] fn Callback * @param[in] arg Argument * @code * int ys_fn(yang_stmt *ys, void *arg) * { * return 0; * } * yang_apply((yang_node*)ys, ys_fn, NULL); * @endcode * @note do not delete or move around any children during this function */ int yang_apply(yang_node *yn, yang_applyfn_t fn, void *arg) { int retval = -1; yang_stmt *ys = NULL; int i; for (i=0; iyn_len; i++){ ys = yn->yn_stmt[i]; if (fn(ys, arg) < 0) goto done; if (yang_apply((yang_node*)ys, fn, arg) < 0) goto done; } retval = 0; done: return retval; } static yang_stmt * yang_dbkey_vec(yang_node *yn, char **vec, int nvec) { char *key; yang_stmt *ys; int64_t i; int ret; if (nvec <= 0) return NULL; key = vec[0]; if (yn->yn_keyword == Y_LIST){ ret = parse_int64(key, &i, NULL); if (ret != 1){ clicon_err(OE_YANG, errno, "strtol"); goto done; } if (nvec == 1) return (yang_stmt*)yn; vec++; nvec--; key = vec[0]; } if ((ys = yang_find_syntax(yn, key)) == NULL) goto done; if (nvec == 1) return ys; return yang_dbkey_vec((yang_node*)ys, vec+1, nvec-1); done: return NULL; } /*! Given a dbkey (eg a.b.0) recursively find matching yang specification * * e.g. a.0 matches the db_spec corresponding to a[]. * Input args: * @param[in] yn top-of yang tree where to start finding * @param[in] dbkey database key to match in yang spec tree * @see yang_dbkey_get */ yang_stmt * dbkey2yang(yang_node *yn, char *dbkey) { char **vec; int nvec; yang_stmt *ys; /* Split key into parts, eg "a.0.b" -> "a" "0" "b" */ if ((vec = clicon_strsplit(dbkey, ".", &nvec, __FUNCTION__)) == NULL){ clicon_err(OE_YANG, errno, "%s: strsplit", __FUNCTION__); return NULL; } ys = yang_dbkey_vec(yn, vec, nvec); unchunk_group(__FUNCTION__); return ys; } /*! All the work for yang_xpath. Ignore prefixes, see _abs */ static yang_node * yang_xpath_vec(yang_node *yn, char **vec, int nvec) { char *arg; yang_stmt *ys; yang_node *yret = NULL; char *id; if (nvec <= 0) goto done; arg = vec[0]; clicon_debug(2, "%s: key=%s arg=%s match=%s len=%d", __FUNCTION__, yang_key2str(yn->yn_keyword), yn->yn_argument, arg, yn->yn_len); if (strcmp(arg, "..") == 0) ys = (yang_stmt*)yn->yn_parent; else{ /* ignore prefixes */ if ((id = strchr(arg, ':')) == NULL) id = arg; else id++; if ((ys = yang_find_xpath_stmt(yn, id)) == NULL){ clicon_debug(1, "%s %s not found", __FUNCTION__, id); goto done; } } if (nvec == 1){ yret = (yang_node*)ys; goto done; } yret = yang_xpath_vec((yang_node*)ys, vec+1, nvec-1); done: return yret; } /* Alternative to clicon_strsplit using malloc. Note delim can only be one char * Free return value after use */ static char ** clicon_strsplit_malloc(char *string, char *delim, int *nvec0) { char **vec = NULL; char *ptr; char *p; int nvec = 1; int i; for (i=0; i: */ if ((id = strchr(vec[1], ':')) == NULL){ id = vec[1]; } else{ /* other module - peek into first element to find module */ if ((prefix = strdup(vec[1])) == NULL){ clicon_err(OE_UNIX, errno, "%s: strdup", __FUNCTION__); goto done; } prefix[id-vec[1]] = '\0'; id++; if ((yimport = ys_module_import(ymod, prefix)) == NULL){ clicon_err(OE_DB, 0, "Prefix %s not defined not found", prefix); goto done; } yspec = ys_spec(ymod); if ((ymod = yang_find((yang_node*)yspec, Y_MODULE, yimport->ys_argument)) == NULL){ clicon_err(OE_DB, 0, "Module referred to with prefix %s not found", prefix); goto done; } } ys = yang_xpath_vec((yang_node*)ymod, vec+1, nvec-1); done: if (vec) free(vec); if (prefix) free(prefix); return ys; } /*! Given an xpath (eg /a/b/c or a/b/c) find matching yang specification * Note that xpath is defined for xml, and for instances of data, this is * for specifications, sp expect some differences. * @param[in] yn Yang node tree * @param[in] xpath A limited xpath expression on the type a/b/c * @retval NULL Error, with clicon_err called * @retval ys First yang node matching xpath * @note: the identifiers in the xpath (eg a, b in a/b) can match the nodes * defined in yang_xpath: container, leaf,list,leaf-list, modules, sub-modules * Example: * yn : module m { prefix b; container b { list c { key d; leaf d; }} } * xpath = m/b/c, returns the list 'c'. * @see xpath_vec * @see clicon_dbget_xpath */ yang_node * yang_xpath(yang_node *yn, char *xpath) { char **vec = NULL; yang_node *ys = NULL; int nvec; if (strlen(xpath) == 0) return NULL; /* check absolute path */ if (xpath[0] == '/') return yang_xpath_abs(yn, xpath); if ((vec = clicon_strsplit_malloc(xpath, "/", &nvec)) == NULL) goto err; ys = yang_xpath_vec((yang_node*)yn, vec, nvec); err: if (vec) free(vec); return ys; } /*! Parse argument as CV and save result in yang cv variable * * Note that some CV:s are parsed directly (eg fraction-digits) while others are parsed * in third pass (ys_populate). The reason being that all information is not * available in the first pass. Prefer to do stuff in ys_populate */ cg_var * ys_parse(yang_stmt *ys, enum cv_type cvtype) { int cvret; char *reason = NULL; assert(ys->ys_cv == NULL); /* Cv:s are parsed in different places, difficult to separate */ if ((ys->ys_cv = cv_new(cvtype)) == NULL){ clicon_err(OE_YANG, errno, "%s: cv_new", __FUNCTION__); goto done; } if ((cvret = cv_parse1(ys->ys_argument, ys->ys_cv, &reason)) < 0){ /* error */ clicon_err(OE_YANG, errno, "parsing cv"); ys->ys_cv = NULL; goto done; } if (cvret == 0){ /* parsing failed */ clicon_err(OE_YANG, errno, "Parsing CV: %s", reason); ys->ys_cv = NULL; goto done; } /* cvret == 1 means parsing is OK */ done: if (reason) free(reason); return ys->ys_cv; } /*! First round yang syntactic statement specific checks. No context checks. * * Specific syntax checks and variable creation for stand-alone yang statements. * That is, siblings, etc, may not be there. Complete checks are made in * ys_populate instead. * * The cv:s created in parse-tree as follows: * fraction-digits : Create cv as uint8, check limits [1:8] (must be made in 1st pass) * * @see ys_populate */ int ys_parse_sub(yang_stmt *ys) { int retval = -1; switch (ys->ys_keyword){ case Y_FRACTION_DIGITS:{ uint8_t fd; if (ys_parse(ys, CGV_UINT8) == NULL) goto done; fd = cv_uint8_get(ys->ys_cv); if (fd < 1 || fd > 18){ clicon_err(OE_YANG, errno, "%u: Out of range, should be [1:18]", fd); goto done; } break; } default: break; } retval = 0; done: return retval; } /*! Return if this leaf is mandatory or not * Note: one can cache this value in ys_cvec instead of functionally evaluating it. * @retval 1 yang statement is leaf and it has a mandatory sub-stmt with value true * @retval 0 The negation of conditions for return value 1. */ int yang_mandatory(yang_stmt *ys) { yang_stmt *ym; if (ys->ys_keyword != Y_LEAF) return 0; if ((ym = yang_find((yang_node*)ys, Y_MANDATORY, NULL)) != NULL){ if (ym->ys_cv == NULL) /* shouldnt happen */ return 0; return cv_bool_get(ym->ys_cv); } return 0; } /*! Return config state of this node * config statement is default true. * Note that a node with config=false may not have a sub * statement where config=true. And this function does not check the sttaus of a parent. * @retval 0 if node has a config sub-statement and it is false * @retval 1 node has not config sub-statement or it is true */ int yang_config(yang_stmt *ys) { yang_stmt *ym; if ((ym = yang_find((yang_node*)ys, Y_CONFIG, NULL)) != NULL){ if (ym->ys_cv == NULL) /* shouldnt happen */ return 1; return cv_bool_get(ym->ys_cv); } return 1; } /*! Utility function for handling yang parsing and translation to key format * @param h clicon handle * @param f file to print to (if printspec enabled) * @param printspec print database (YANG) specification as read from file */ int yang_spec_main(clicon_handle h, FILE *f, int printspec) { yang_spec *yspec; char *yang_dir; char *yang_module; char *yang_revision; int retval = -1; if ((yspec = yspec_new()) == NULL) goto done; if ((yang_dir = clicon_yang_dir(h)) == NULL){ clicon_err(OE_FATAL, 0, "CLICON_YANG_DIR option not set"); goto done; } if ((yang_module = clicon_yang_module_main(h)) == NULL){ clicon_err(OE_FATAL, 0, "CLICON_YANG_MODULE_MAIN option not set"); goto done; } yang_revision = clicon_yang_module_revision(h); if (yang_parse(h, yang_dir, yang_module, yang_revision, yspec) < 0) goto done; clicon_dbspec_yang_set(h, yspec); if (printspec) yang_print(f, (yang_node*)yspec, 0); retval = 0; done: return retval; } /*! Given a yang node, translate the argument string to a cv vector * * @param[in] ys Yang statement * @param[in] delimiter Delimiter character (eg ' ' or ',') * @retval NULL Error * @retval cvec Vector of strings. Free with cvec_free() * @code * cvec *cvv; * cg_var *cv = NULL; * if ((cvv = yang_arg2cvec(ys, " ")) == NULL) * goto err; * while ((cv = cvec_each(cvv, cv)) != NULL) * ...cv_string_get(cv); * cvec_free(cvv); * @endcode * Note: must free return value after use w cvec_free */ cvec * yang_arg2cvec(yang_stmt *ys, char *delim) { char **vec; int i; int nvec; cvec *cvv = NULL; cg_var *cv; if ((vec = clicon_strsplit(ys->ys_argument, " ", &nvec, __FUNCTION__)) == NULL){ clicon_err(OE_YANG, errno, "clicon_strsplit"); goto done; } if ((cvv = cvec_new(nvec)) == NULL){ clicon_err(OE_YANG, errno, "cvec_new"); goto done; } for (i = 0; i < nvec; i++) { cv = cvec_i(cvv, i); cv_type_set(cv, CGV_STRING); if ((cv_string_set(cv, vec[i])) == NULL){ clicon_err(OE_YANG, errno, "cv_string_set"); cvv = NULL; goto done; } } done: unchunk_group(__FUNCTION__); return cvv; } /*! Check if yang node yn has key-stmt as child which matches name * * @param[in] yn Yang node with sub-statements (look for a key child) * @param[in] name Check if this name (eg "b") is a key in the yang key statement * * @retval -1 Error * @retval 0 No match * @retval 1 Yes match */ int yang_key_match(yang_node *yn, char *name) { int retval = -1; yang_stmt *ys = NULL; int i; cvec *cvv = NULL; cg_var *cv; for (i=0; iyn_len; i++){ ys = yn->yn_stmt[i]; if (ys->ys_keyword == Y_KEY){ if ((cvv = yang_arg2cvec(ys, " ")) == NULL) goto done; cv = NULL; while ((cv = cvec_each(cvv, cv)) != NULL) { if (strcmp(name, cv_string_get(cv)) == 0){ retval = 1; /* match */ goto done; } } cvec_free(cvv); cvv = NULL; } } retval = 0; done: if (cvv) cvec_free(cvv); return retval; }