clixon/lib/src/clixon_yang.c
2017-12-02 14:49:49 +01:00

2153 lines
59 KiB
C

/*
*
***** 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 *****
* Yang functions
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>
#include <ctype.h>
#define __USE_GNU /* strverscmp */
#include <string.h>
#include <arpa/inet.h>
#include <regex.h>
#include <dirent.h>
#include <syslog.h>
#include <assert.h>
#include <sys/stat.h>
#include <netinet/in.h>
/* cligen */
#include <cligen/cligen.h>
/* 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_plugin.h"
#include "clixon_options.h"
#include "clixon_yang_type.h"
#include "clixon_yang_parse.h"
/* Mapping between yang keyword string <--> clicon constants */
static const 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}
};
/*! Create new yang specification
* @retval yspec Free with yspec_free()
* @retval NULL Error
*/
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;
}
/*! Create new yang node/statement
* @retval ys Free with ys_free()
* @retval NULL Error
*/
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; i<ys->ys_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; i<yspec->yp_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; i<ynew->ys_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; i<yn->yn_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 String compare w wrgument. if NULL, match any.
* 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_datanode
*/
yang_stmt *
yang_find(yang_node *yn,
int keyword,
char *argument)
{
yang_stmt *ys = NULL;
int i;
int match = 0;
for (i=0; i<yn->yn_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 child data node with matching argument (container, leaf, etc)
*
* @param[in] yn Yang node, current context node.
* @param[in] argument if NULL, match any(first) argument. XXX is that really a case?
*
* @see yang_find Looks for any node
* @note May deviate from RFC since it explores choice/case not just return it.
*/
yang_stmt *
yang_find_datanode(yang_node *yn,
char *argument)
{
yang_stmt *ys = NULL;
yang_stmt *yc = NULL;
yang_stmt *ysmatch = NULL;
int i, j;
for (i=0; i<yn->yn_len; i++){
ys = yn->yn_stmt[i];
if (ys->ys_keyword == Y_CHOICE){ /* Look for its children */
for (j=0; j<ys->ys_len; j++){
yc = ys->ys_stmt[j];
if (yc->ys_keyword == Y_CASE) /* Look for its children */
ysmatch = yang_find_datanode((yang_node*)yc, argument);
else
if (yang_datanode(yc)){
if (argument == NULL)
ysmatch = yc;
else
if (yc->ys_argument && strcmp(argument, yc->ys_argument) == 0)
ysmatch = yc;
}
if (ysmatch)
goto match;
}
} /* Y_CHOICE */
else
if (yang_datanode(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;
}
/*! Find child schema node with matching argument (container, leaf, etc)
* @note XXX unify code with yang_find_datanode?
* @see yang_find_datanode
*/
yang_stmt *
yang_find_schemanode(yang_node *yn,
char *argument)
{
yang_stmt *ys = NULL;
yang_stmt *yc = NULL;
yang_stmt *ysmatch = NULL;
int i, j;
for (i=0; i<yn->yn_len; i++){
ys = yn->yn_stmt[i];
if (ys->ys_keyword == Y_CHOICE){ /* Look for its children */
for (j=0; j<ys->ys_len; j++){
yc = ys->ys_stmt[j];
if (yc->ys_keyword == Y_CASE) /* Look for its children */
ysmatch = yang_find_schemanode((yang_node*)yc, argument);
else
if (yang_schemanode(yc)){
if (argument == NULL)
ysmatch = yc;
else
if (yc->ys_argument && strcmp(argument, yc->ys_argument) == 0)
ysmatch = yc;
}
if (ysmatch)
goto match;
}
} /* Y_CHOICE */
else
if (yang_schemanode(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;
}
/*! Find first matching data node in all (sub)modules in a yang spec
*
* @param[in] ysp Yang specification
* @param[in] name if NULL, match any(first) argument. XXX is that really a case?
* @param[in] schemanode If set look for schema nodes, otherwise only data nodes
* 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 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,
int schemanode)
{
yang_stmt *ys = NULL;
yang_stmt *yc = NULL;
int i;
for (i=0; i<ysp->yp_len; i++){
ys = ysp->yp_stmt[i];
if (schemanode){
if ((yc = yang_find_schemanode((yang_node*)ys, name)) != NULL)
return yc;
}
else
if ((yc = yang_find_datanode((yang_node*)ys, name)) != NULL)
return yc;
}
return NULL;
}
/*! Given a yang statement, find the prefix associated to this module
* @param[in] ys Yang statement
* @retval NULL Not found
* @retval prefix Prefix as char* pointer into yang tree
*/
char *
yang_find_myprefix(yang_stmt *ys)
{
yang_stmt *ymod; /* My module */
yang_stmt *yprefix;
char *prefix = NULL;
if ((ymod = ys_module(ys)) == NULL){
clicon_err(OE_YANG, 0, "My yang module not found");
goto done;
}
if ((yprefix = yang_find((yang_node*)ymod, Y_PREFIX, NULL)) == NULL){
clicon_err(OE_YANG, 0, "No prefix in my module");
goto done;
}
prefix = yprefix->ys_argument;
done:
return prefix;
}
/*! 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;
}
char *
yang_key2str(int keyword)
{
return (char*)clicon_int2str(ykmap, keyword);
}
/*! 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 1
if (ys==NULL || ys->ys_keyword==Y_SPEC)
return NULL;
#else
if (ys==NULL || ys->ys_keyword==Y_SPEC)
return ys;
#endif
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 yang statement and a prefix, return yang module to that prefix
* Note, not the other module but the proxy import statement only
* @param[in] ys A yang statement
* @param[in] prefix prefix
* @retval ymod Yang module statement if found
* @retval NULL not found
*/
yang_stmt *
yang_find_module_by_prefix(yang_stmt *ys,
char *prefix)
{
yang_stmt *yimport;
yang_stmt *yprefix;
yang_stmt *my_ymod;
yang_stmt *ymod = NULL;
yang_spec *yspec;
char *myprefix;
if ((yspec = ys_spec(ys)) == NULL){
clicon_err(OE_YANG, 0, "My yang spec not found");
goto done;
}
myprefix = yang_find_myprefix(ys);
if ((my_ymod = ys_module(ys)) == NULL){
clicon_err(OE_YANG, 0, "My yang module not found");
goto done;
}
#if 0
if (my_ymod->ys_keyword != Y_MODULE &&
my_ymod->ys_keyword != Y_SUBMODULE){
clicon_err(OE_YANG, 0, "%s not module or sub-module", my_ymod->ys_argument);
goto done;
}
#endif
if (strcmp(myprefix, prefix) == 0){
ymod = my_ymod;
goto done;
}
yimport = NULL;
while ((yimport = yn_each((yang_node*)my_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){
break;
}
}
if (yimport){
if ((ymod = yang_find((yang_node*)yspec, Y_MODULE, yimport->ys_argument)) == NULL){
clicon_err(OE_YANG, 0, "No module or sub-module found with prefix %s",
yimport->ys_argument);
yimport = NULL;
goto done; /* unresolved */
}
}
done:
return ymod;
}
/*! 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; i<len; i++)
if (isblank(s[i]))
break;
return i < len;
}
/*! Print yang specification to file
* @param[in] f File to print to.
* @param[in] yn Yang node to print
* @see yang_print_cbuf
*/
int
yang_print(FILE *f,
yang_node *yn)
{
int retval = -1;
cbuf *cb = NULL;
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_YANG, errno, "%s: cbuf_new", __FUNCTION__);
goto done;
}
if (yang_print_cbuf(cb, yn, 0) < 0)
goto done;
fprintf(f, "%s", cbuf_get(cb));
if (cb)
cbuf_free(cb);
retval = 0;
done:
return retval;
}
/*! Print yang specification to cligen buf
* @param[in] cb Cligen buffer. This is where the pretty print is.
* @param[in] yn Yang node to print
* @param[in] marginal Tab indentation, mainly for recursion.
* @code
* cbuf *cb = cbuf_new();
* yang_print_cbuf(cb, yn, 0);
* // output is in cbuf_buf(cb);
* cbuf_free(cb);
* @endcode
*/
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: <int>..<int>");
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;
}
}
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 *ymodule;
yang_stmt *ygrouping = NULL;
yang_node *yn;
/* find the grouping associated with argument and expand(?) */
if (prefix){ /* Go to top and find import that matches */
if ((ymodule = yang_find_module_by_prefix(ys, prefix)) != 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.
@note Destructively changing a datamodel may affect outlying loop?
*/
static int
yang_augment_node(yang_stmt *ys,
yang_spec *ysp)
{
int retval = -1;
char *schema_nodeid;
yang_stmt *yss = NULL;
yang_stmt *yc;
int i;
schema_nodeid = ys->ys_argument;
clicon_debug(1, "%s %s", __FUNCTION__, schema_nodeid);
/* Find the target */
if (yang_abs_schema_nodeid(ysp, schema_nodeid, &yss) < 0)
goto done;
if (yss == NULL)
goto ok;
/* Extend yss with ys' children
* First enlarge yss vector
*/
for (i=0; i<ys->ys_len; i++){
if ((yc = ys_dup(ys->ys_stmt[i])) == NULL)
goto done;
/* XXX: use prefix of origin */
if (yn_insert((yang_node*)yss, yc) < 0)
goto done;
}
ok:
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 (i<ysp->yp_len){ /* Loop through modules and sub-modules */
ym = ysp->yp_stmt[i++];
j = 0;
while (j<ym->ys_len){ /* Top-level symbols in modules */
ys = ym->ys_stmt[j++];
switch (ys->ys_keyword){
case Y_AUGMENT: /* top-level */
if (yang_augment_node(ys, ysp) < 0)
goto done;
break;
default:
break;
}
}
}
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 (i<yn->yn_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; j<glen; j++){
if ((yg = ys_dup(ygrouping->ys_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; i<yn->yn_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 ymod Top-level yang (sub)module
* @retval NULL Error encountered
* Calling order:
* yang_parse # Parse top-level yang module. Expand and populate yang tree
* yang_parse_recurse # Parse one yang module, go through its (sub)modules, parse them and then recursively parse them
* 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 *ymod = 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;
}
ymod = yy.yy_module;
done:
ystack_pop(&yy);
return ymod; /* top-level (sub)module */
}
/*! 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 filename Name of file
* @param ysp Yang specification. Should ave been created by caller using yspec_new
* @retval ymod Top-level yang (sub)module
* @retval NULL 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_parse_recurse # Parse one yang module, go through its (sub)modules, parse them and then recursively parse them
* 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,
const char *filename,
yang_spec *ysp
)
{
char *buf = NULL;
int i;
int c;
int len;
yang_stmt *ymod = NULL;
FILE *f = NULL;
struct stat st;
clicon_debug(1, "Yang parse file: %s", filename);
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;
}
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 ((ymod = yang_parse_str(h, buf, filename, ysp)) < 0)
goto done;
done:
if (f)
fclose(f);
if (buf)
free(buf);
return ymod; /* top-level (sub)module */
}
/*! 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 found, 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 = NULL;
int ndp;
cbuf *regex = NULL;
if ((regex = cbuf_new()) == NULL){
clicon_err(OE_YANG, errno, "cbuf_new");
goto done;
}
/* RFC 6020: The name of the file SHOULD be of the form:
module-or-submodule-name ['@' revision-date] ( '.yang' / '.yin' )
revision-date ::= 4DIGIT "-" 2DIGIT "-" 2DIGIT
*/
cprintf(regex, "^%s(@[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9])?(.yang)$",
module);
if ((ndp = clicon_file_dirent(yang_dir,
&dp,
cbuf_get(regex),
S_IFREG)) < 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);
if (dp)
free(dp);
return retval;
}
/*! Parse one yang module then go through (sub)modules and parse them recursively
*
* @param[in] h CLICON handle
* @param[in] yang_dir Directory where all YANG module files reside
* @param[in] module Name of main YANG module. Or absolute file name.
* @param[in] revision Optional module revision date
* @param[in] ysp Yang specification. Should have been created by caller using yspec_new
* @retval ymod Top-level yang (sub)module
* @retval NULL 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_parse_recurse # Parse one yang module, go through its (sub)modules, parse them and then recursively parse them
* 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_recurse(clicon_handle h,
const char *yang_dir,
const char *module,
const char *revision,
yang_spec *ysp)
{
yang_stmt *yi = NULL; /* import */
yang_stmt *ymod = NULL;
yang_stmt *yrev;
char *modname;
char *subrevision;
cbuf *fbuf = NULL;
int nr;
if (module[0] == '/'){
if ((ymod = yang_parse_file(h, module, ysp)) == NULL)
goto done;
}
else {
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 (expected modulenameor absolute filename)", module);
goto done;
}
}
if ((ymod = yang_parse_file(h, cbuf_get(fbuf), ysp)) == NULL)
goto done;
}
/* go through all import statements of ysp (or its module) */
while ((yi = yn_each((yang_node*)ymod, 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)
/* recursive call */
if (yang_parse_recurse(h, yang_dir, modname, subrevision, ysp) == NULL){
ymod = NULL;
goto done;
}
}
done:
if (fbuf)
cbuf_free(fbuf);
return ymod; /* top-level (sub)module */
}
int
ys_schemanode_check(yang_stmt *ys,
void *arg)
{
int retval = -1;
yang_spec *yspec;
yang_stmt *yres;
yang_node *yp;
yp = ys->ys_parent;
switch (ys->ys_keyword){
case Y_AUGMENT:
if (yp->yn_keyword == Y_MODULE) /* Not top-level */
break;
/* fallthru */
case Y_REFINE:
case Y_UNIQUE:
if (yang_desc_schema_nodeid(yp, ys->ys_argument, &yres) < 0)
goto done;
if (yres == NULL){
clicon_err(OE_YANG, 0, "schemanode sanity check of %d %s",
ys->ys_keyword,
ys->ys_argument);
goto done;
}
break;
case Y_DEVIATION:
yspec = ys_spec(ys);
if (yang_abs_schema_nodeid(yspec, ys->ys_argument, &yres) < 0)
goto done;
if (yres == NULL){
clicon_err(OE_YANG, 0, "schemanode sanity check of %s", ys->ys_argument);
goto done;
}
break;
default:
break;
}
retval = 0;
done:
return retval;
}
/*! Parse top yang module including all its sub-modules. Expand and populate yang tree
*
* @param[in] h CLICON handle
* @param[in] yang_dir Directory where all YANG module files reside (except mainfile)
* @param[in] mainmod Name of main YANG module. Or absolute file name.
* @param[in] revision Optional main module revision date.
* @param[out] 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.
* @note if mainmod is filename, revision is not considered.
* Calling order:
* yang_parse # Parse top-level yang module. Expand and populate yang tree
* yang_parse_recurse # Parse one yang module, go through its (sub)modules, parse them and then recursively parse them
* 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 *mainmodule,
const char *revision,
yang_spec *ysp)
{
int retval = -1;
yang_stmt *ymod; /* Top-level yang (sub)module */
/* Step 1: parse from text to yang parse-tree. */
if ((ymod = yang_parse_recurse(h, yang_dir, mainmodule, revision, ysp)) == NULL)
goto done;
/* Add top module name as dbspec-name */
clicon_dbspec_name_set(h, ymod->ys_argument);
/* Step 2: Go through parse tree and populate it with cv types */
if (yang_apply((yang_node*)ysp, -1, ys_populate, NULL) < 0)
goto done;
/* Step 3: Resolve all types: populate type caches. Requires eg length/range cvecs
* from ys_populate step
*/
yang_apply((yang_node*)ysp, Y_TYPE, ys_resolve_type, NULL);
/* Up to here resolving is made in the context they are defined, rather than the
context they are used. Like static scoping. After this we expand all
grouping/uses and unfold all macros into a single tree as they are used.
*/
/* Step 4: Macro expansion of all grouping/uses pairs. Expansion needs marking */
if (yang_expand((yang_node*)ysp) < 0)
goto done;
yang_apply((yang_node*)ymod, -1, ys_flag_reset, (void*)YANG_FLAG_MARK);
/* Step 4: Top-level augmentation of all modules */
if (yang_augment_spec(ysp) < 0)
goto done;
/* sanity check of schemanode references, need more here */
if (yang_apply((yang_node*)ysp, -1, ys_schemanode_check, NULL) < 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] yn yang node
* @param[in] key yang keyword to use as filer or -1 for all
* @param[in] fn Callback
* @param[in] arg Argument
* @retval -1 Error, aborted at first error encounter
* @retval 0 OK, all nodes traversed
* @retval n OK, aborted at first encounter of first match
* @code
* int ys_fn(yang_stmt *ys, void *arg)
* {
* return 0;
* }
* yang_apply((yang_node*)ys, Y_TYPE, ys_fn, NULL);
* @endcode
* @note do not delete or move around any children during this function
*/
int
yang_apply(yang_node *yn,
enum rfc_6020 keyword,
yang_applyfn_t fn,
void *arg)
{
int retval = -1;
yang_stmt *ys = NULL;
int i;
int ret;
for (i=0; i<yn->yn_len; i++){
ys = yn->yn_stmt[i];
if (keyword == -1 || keyword == ys->ys_keyword){
if ((ret = fn(ys, arg)) < 0)
goto done;
if (ret > 0){
retval = ret;
goto done;
}
}
if ((ret = yang_apply((yang_node*)ys, keyword, fn, arg)) < 0)
goto done;
if (ret > 0){
retval = ret;
goto done;
}
}
retval = 0;
done:
return retval;
}
/*! All the work for schema_nodeid functions both absolute and descendant
* Ignore prefixes, see _abs
* @param[in] yn Yang node. Find next yang stmt and return that if match.
* @param[in] vec Vector of nodeid's in a schema node identifier, eg a/b
* @param[in] nvec Length of vec
* @param[out] yres Result yang statement node, or NULL if not found
* @retval -1 Error, with clicon_err called
* @retval 0 OK
*/
static int
schema_nodeid_vec(yang_node *yn,
char **vec,
int nvec,
yang_stmt **yres)
{
int retval = -1;
char *arg;
yang_node *ynext;
char *nodeid = NULL;
int i;
yang_stmt *ys;
int match;
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)
ynext = yn->yn_parent; /* This could actually be a MODULE */
else{
/* ignore prefixes */
if ((nodeid = strchr(arg, ':')) == NULL)
nodeid = arg;
else
nodeid++;
match = 0;
ys = NULL;
for (i=0; i<yn->yn_len; i++){
ys = yn->yn_stmt[i];
if (!yang_schemanode(ys))
continue;
/* some keys dont have arguments, match on key */
if (ys->ys_keyword == Y_INPUT || ys->ys_keyword == Y_OUTPUT){
if (strcmp(nodeid, yang_key2str(ys->ys_keyword)) == 0){
match++;
break;
}
} else
if (ys->ys_argument && strcmp(nodeid, ys->ys_argument) == 0){
match++;
break;
}
}
if (!match){
clicon_debug(1, "%s: %s not found", __FUNCTION__, nodeid);
goto ok;
}
ynext = (yang_node*)ys;
}
if (nvec == 1){ /* match */
if (yang_schemanode((yang_stmt*)ynext))
*yres = (yang_stmt*)ynext;
else
clicon_debug(1, "%s not schema node", arg);
goto ok;
}
/* recursive call using ynext */
if (schema_nodeid_vec(ynext, vec+1, nvec-1, yres) < 0)
goto done;
ok:
retval = 0;
done:
return retval;
}
/*! Given an absolute schema-nodeid (eg /a/b/c) find matching yang spec
* @param[in] yspec Yang specification.
* @param[in] schema_nodeid Absolute schema-node-id, ie /a/b
* @retval NULL Error, with clicon_err called
* @retval yres First yang node matching schema nodeid
* Assume schema nodeid:s have prefixes, (actually the first).
* @see yang_desc_schema_nodeid
* Used in yang: deviation, top-level augment
*/
int
yang_abs_schema_nodeid(yang_spec *yspec,
char *schema_nodeid,
yang_stmt **yres)
{
int retval = -1;
char **vec = NULL;
int nvec;
yang_stmt *ymod;
char *id;
char *prefix = NULL;
yang_stmt *yprefix;
/* check absolute schema_nodeid */
if (schema_nodeid[0] != '/'){
clicon_err(OE_YANG, EINVAL, "absolute schema nodeid should start with /");
goto done;
}
if ((vec = clicon_strsep(schema_nodeid, "/", &nvec)) == NULL){
clicon_err(OE_YANG, errno, "%s: strsep", __FUNCTION__);
goto done;
}
/* Assume schema nodeid looks like: "/prefix:id[/prefix:id]*" */
if (nvec < 2){
clicon_err(OE_YANG, 0, "%s: NULL or truncated path: %s",
__FUNCTION__, schema_nodeid);
goto done;
}
/* split <prefix>:<id> */
if ((id = strchr(vec[1], ':')) == NULL){ /* no prefix */
clicon_err(OE_YANG, 0, "Absolute schema nodeid must have prefix");
goto done;
}
if ((prefix = strdup(vec[1])) == NULL){
clicon_err(OE_UNIX, errno, "%s: strdup", __FUNCTION__);
goto done;
}
prefix[id-vec[1]] = '\0';
id++;
ymod = NULL;
while ((ymod = yn_each((yang_node*)yspec, ymod)) != NULL) {
if ((yprefix = yang_find((yang_node*)ymod, Y_PREFIX, NULL)) != NULL &&
strcmp(yprefix->ys_argument, prefix) == 0){
break;
}
}
if (ymod == NULL){ /* Try with topnode */
yang_stmt *ys;
if ((ys = yang_find_topnode(yspec, id, 1)) == NULL){
clicon_err(OE_YANG, 0, "Module with id:%s:%s not found", prefix,id);
goto done;
}
if ((ymod = ys_module(ys)) == NULL){
clicon_err(OE_YANG, 0, "Module with id:%s:%s not found2", prefix,id);
goto done;
}
}
if (schema_nodeid_vec((yang_node*)ymod, vec+1, nvec-1, yres) < 0)
goto done;
retval = 0;
done:
if (vec)
free(vec);
if (prefix)
free(prefix);
return retval;
}
/*! Given a descendant schema-nodeid (eg a/b/c) find matching yang spec
* @param[in] yn Yang node
* @param[in] schema_nodeid Descendant schema-node-id, ie a/b
* @retval NULL Error, with clicon_err called
* @retval yres First yang node matching schema nodeid
* @see yang_schema_nodeid
* Used in yang: unique, refine, uses augment
*/
int
yang_desc_schema_nodeid(yang_node *yn,
char *schema_nodeid,
yang_stmt **yres)
{
int retval = -1;
char **vec = NULL;
int nvec;
if (strlen(schema_nodeid) == 0)
goto done;
/* check absolute schema_nodeid */
if (schema_nodeid[0] == '/'){
clicon_err(OE_YANG, EINVAL, "descendant schema nodeid should not start with /");
goto done;
}
if ((vec = clicon_strsep(schema_nodeid, "/", &nvec)) == NULL)
goto done;
if (schema_nodeid_vec(yn, vec, nvec, yres) < 0)
goto done;
retval = 0;
done:
if (vec)
free(vec);
return retval;
}
/*! 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;
}
/* Yang module is either specific absolute filename, or main module */
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);
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 = NULL;
int i;
int nvec;
cvec *cvv = NULL;
cg_var *cv;
if ((vec = clicon_strsep(ys->ys_argument, " ", &nvec)) == NULL)
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:
if (vec)
free(vec);
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; i<yn->yn_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;
}