2034 lines
56 KiB
C
2034 lines
56 KiB
C
/*
|
|
*
|
|
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
|
|
<http://www.gnu.org/licenses/>.
|
|
|
|
* 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_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; 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 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; 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 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; 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_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; i<ysp->yp_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; i<yn->yn_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;
|
|
|
|
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; i<len; i++)
|
|
if (isblank(s[i]))
|
|
break;
|
|
return i < len;
|
|
}
|
|
|
|
int
|
|
yang_print(FILE *f, yang_node *yn, int marginal)
|
|
{
|
|
yang_stmt *ys = NULL;
|
|
|
|
while ((ys = yn_each(yn, ys)) != NULL) {
|
|
fprintf(f, "%*s%s", marginal, "", yang_key2str(ys->ys_keyword));
|
|
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;
|
|
}
|
|
|
|
|
|
/*! 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;
|
|
}
|
|
}
|
|
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; i<ys->ys_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 (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++];
|
|
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 (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 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; i<yn->yn_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(0, "%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<strlen(string); i++)
|
|
if (string[i]==*delim)
|
|
nvec++;
|
|
/* alloc vector and append copy of string */
|
|
if ((vec = (char**)malloc(nvec* sizeof(char*) + strlen(string)+1)) == NULL){
|
|
clicon_err(OE_YANG, errno, "malloc");
|
|
goto err;
|
|
}
|
|
ptr = (char*)vec + nvec* sizeof(char*); /* this is where ptr starts */
|
|
strncpy(ptr, string, strlen(string)+1);
|
|
i = 0;
|
|
while ((p = strsep(&ptr, delim)) != NULL)
|
|
vec[i++] = p;
|
|
*nvec0 = nvec;
|
|
err:
|
|
return vec;
|
|
}
|
|
|
|
/*! Given an absolute xpath (eg /a/b/c) find matching yang specification */
|
|
yang_node *
|
|
yang_xpath_abs(yang_node *yn, char *xpath)
|
|
{
|
|
char **vec = NULL;
|
|
int nvec;
|
|
yang_node *ys = NULL;
|
|
yang_stmt *ymod;
|
|
yang_spec *yspec;
|
|
yang_stmt *yimport;
|
|
char *id;
|
|
char *prefix = NULL;
|
|
|
|
if ((vec = clicon_strsplit_malloc(xpath, "/", &nvec)) == NULL){
|
|
clicon_err(OE_YANG, errno, "%s: strsplit", __FUNCTION__);
|
|
return NULL;
|
|
}
|
|
/* Assume path looks like: "/prefix:id[/prefix:id]*" */
|
|
if (nvec < 2){
|
|
clicon_err(OE_YANG, 0, "%s: NULL or truncated path: %s",
|
|
__FUNCTION__, xpath);
|
|
goto done;
|
|
}
|
|
/* Check for absolute path */
|
|
if (strcmp(vec[0], "") != 0){
|
|
clicon_err(OE_YANG, errno, "%s: %s: expected absolute path",
|
|
__FUNCTION__, vec[0]);
|
|
goto done;
|
|
}
|
|
/* Find correct module */
|
|
ymod = ys_module((yang_stmt*)yn); /* This is current module */
|
|
/* split <prefix>:<id> */
|
|
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 one of print options are 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; 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;
|
|
}
|