clixon/lib/src/clixon_yang.c

3073 lines
86 KiB
C

/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 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
* @see https://tools.ietf.org/html/rfc6020 YANG 1.0
* @see https://tools.ietf.org/html/rfc7950 YANG 1.1
*
* yang_spec_parse_module
* \
* yang_spec_parse_file-> yang_parse_post->yang_parse_recurse->yang_parse_module
* \ / v
* yang_spec_load_dir ------------------------------------> yang_parse_filename
* v
* yang_parse_file
* v
* yang_parse_str
*/
#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>
#include <unistd.h>
#define __USE_GNU /* strverscmp */
#include <string.h>
#include <arpa/inet.h>
#include <regex.h>
#include <dirent.h>
#include <sys/types.h>
#include <fcntl.h>
#include <syslog.h>
#include <assert.h>
#include <sys/stat.h>
#include <sys/param.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_xml.h"
#include "clixon_plugin.h"
#include "clixon_options.h"
#include "clixon_yang_type.h"
#include "clixon_yang_parse.h"
#include "clixon_yang_cardinality.h"
/* Size of json read buffer when reading from file*/
#define BUFLEN 1024
/*
* Local variables
*/
/* Mapping between yang keyword string <--> clicon constants */
static const map_str2int ykmap[] = {
{"anydata", Y_ANYDATA},
{"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},
{"modifier", Y_MODIFIER},
{"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},
{"unknown", Y_UNKNOWN},
{"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, "malloc");
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, "malloc");
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, "cvec_new");
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_extra)
free(ys->ys_extra);
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, "realloc");
return -1;
}
yn->yn_stmt[yn->yn_len - 1] = NULL; /* init field */
return 0;
}
/*! Copy yang statement recursively from old to new
* @param[in] ynew New empty (but created) yang statement (to)
* @param[in] yold Old existing yang statement (from)
* @code
* yang_stmt *new = ys_new(Y_LEAF);
* if (ys_cp(new, old) < 0)
* err;
* @endcode
*/
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, "calloc");
goto done;
}
if (yold->ys_argument)
if ((ynew->ys_argument = strdup(yold->ys_argument)) == NULL){
clicon_err(OE_YANG, errno, "strdup");
goto done;
}
if (yold->ys_extra)
if ((ynew->ys_extra = strdup(yold->ys_extra)) == NULL){
clicon_err(OE_YANG, errno, "strdup");
goto done;
}
if (yold->ys_cv)
if ((ynew->ys_cv = cv_dup(yold->ys_cv)) == NULL){
clicon_err(OE_YANG, errno, "cv_dup");
goto done;
}
if (yold->ys_cvec)
if ((ynew->ys_cvec = cvec_dup(yold->ys_cvec)) == NULL){
clicon_err(OE_YANG, errno, "cvec_dup");
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. *
* @param[in] old Old existing yang statement (from)
* @retval NULL Error
* @retval new New created yang statement
* 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
*
* @param[in] yn_parent Add child to this parent
* @param[in] ys_child Add this child
* 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 argument. if NULL, match any.
* @retval ys Yang statement, if 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
* @see yang_match returns number of matches
*/
yang_stmt *
yang_find(yang_node *yn,
int keyword,
const 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;
}
/*! Count number of children that matches 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 argument. if NULL, match any.
* @retval n Number of matches
* 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
*/
int
yang_match(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++;
}
}
return match;
}
/*! 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)
* @param[in] yn Yang node, current context node.
* @param[in] argument if NULL, match any(first) argument.
* @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;
}
/*! Given a yang statement, find the prefix associated to this module
* @param[in] ys Yang statement in module tree (or module itself)
* @retval NULL Not found
* @retval prefix Prefix as char* pointer into yang tree
* @code
* char *myprefix;
* myprefix = yang_find_myprefix(ys);
* @endcode
*/
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)
goto done;
prefix = yprefix->ys_argument;
done:
return prefix;
}
/*! Given a yang statement, find the namespace URI associated to this module
* @param[in] ys Yang statement in module tree (or module itself)
* @retval NULL Not found
* @retval namespace Namspace URI as char* pointer into yang tree
* @code
* char *myns = yang_find_mynamespace(ys);
* @endcode
* @see yang_find_module_by_namespace
*/
char *
yang_find_mynamespace(yang_stmt *ys)
{
yang_stmt *ymod; /* My module */
yang_stmt *ynamespace;
char *namespace = NULL;
if ((ymod = ys_module(ys)) == NULL){
clicon_err(OE_YANG, 0, "My yang module not found");
goto done;
}
if ((ynamespace = yang_find((yang_node*)ymod, Y_NAMESPACE, NULL)) == NULL)
goto done;
namespace = ynamespace->ys_argument;
done:
return namespace;
}
/*! If a given yang stmt has a choice/case as parent, return the choice statement
*/
yang_node *
yang_choice(yang_stmt *y)
{
yang_node *yp;
if ((yp = y->ys_parent) != NULL){
switch (yp->yn_keyword){
case Y_CHOICE:
return yp;
break;
case Y_CASE:
return yp->yn_parent;
break;
default:
break;
}
}
return NULL;
}
/*! Find matching y in yp:s children, return 0 and index or -1 if not found.
* @retval 0 not found
* @retval 1 found
*/
static int
order1(yang_node *yp,
yang_stmt *y,
int *index)
{
yang_stmt *ys;
int i;
for (i=0; i<yp->yn_len; i++){
ys = yp->yn_stmt[i];
if (!yang_datanode(ys))
continue;
if (ys==y)
return 1;
(*index)++;
}
return 0;
}
/*! Return order of yang statement y in parents child vector
* @retval i Order of child with specified argument
* @retval -1 Not found
*/
int
yang_order(yang_stmt *y)
{
yang_node *yp;
yang_node *ypp;
yang_node *yn;
int i;
int j=0;
yp = y->ys_parent;
if (yp->yn_keyword == Y_MODULE || yp->yn_keyword == Y_SUBMODULE){
ypp = yp->yn_parent;
for (i=0; i<ypp->yn_len; i++){
yn = (yang_node*)ypp->yn_stmt[i];
if (order1(yn, y, &j) == 1)
return j;
}
}
order1(yp, y, &j);
return j;
}
/*! 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 data node among all modules by namespace in xml tree
* @param[in] ysp Yang specification
* @param[in] xt XML node
* @param[out] ymod Yang module (NULL if not found)
* @retval 0 OK
* @retval -1 Error
* @note works for xml namespaces (xmlns / xmlns:ns)
*/
int
ys_module_by_xml(yang_spec *ysp,
cxobj *xt,
yang_stmt **ymodp)
{
int retval = -1;
yang_stmt *ym = NULL; /* module */
char *prefix = NULL;
char *namespace = NULL; /* namespace URI */
if (ymodp)
*ymodp = NULL;
prefix = xml_prefix(xt);
if (prefix){
/* Get namespace for prefix */
if (xml2ns(xt, prefix, &namespace) < 0)
goto done;
}
else{
/* Get default namespace */
if (xml2ns(xt, NULL, &namespace) < 0)
goto done;
}
/* No namespace found, give up */
if (namespace == NULL)
goto ok;
/* We got the namespace, now get the module */
ym = yang_find_module_by_namespace(ysp, namespace);
/* Set result param */
if (ymodp && ym)
*ymodp = ym;
ok:
retval = 0;
done:
return retval;
}
/*! Find the top module or sub-module given a statement from within a yang tree
* Ultimate top is yang spec, dont return that
* The routine recursively finds ancestors.
* @param[in] ys Any yang statement in a yang tree
* @retval ymod The top module or sub-module
* @see ys_spec
*/
yang_stmt *
ys_module(yang_stmt *ys)
{
yang_node *yn;
if (ys==NULL || ys->ys_keyword==Y_SPEC)
return NULL;
if (ys->ys_keyword == Y_MODULE || ys->ys_keyword == Y_SUBMODULE)
return ys;
while (ys != NULL &&
ys->ys_keyword != Y_MODULE &&
ys->ys_keyword != Y_SUBMODULE){
yn = ys->ys_parent;
/* Some extra stuff to ensure ys is a stmt */
if (yn && yn->yn_keyword == Y_SPEC)
yn = NULL;
ys = (yang_stmt*)yn;
}
/* Here it is either NULL or is a typedef-kind yang-stmt */
return ys;
}
/*! Find top of tree, the yang specification from within the tree
* @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;
}
/* Assume argument is id on the type: <[prefix:]id>, return 'id'
* Just return string from id
* @param[in] ys A yang statement
* @retval NULL No id (argument is NULL)
* @retval id Pointer to identifier
* @see yarg_prefix
*/
char*
yarg_id(yang_stmt *ys)
{
char *id;
if ((id = strchr(ys->ys_argument, ':')) == NULL)
id = ys->ys_argument;
else
id++;
return id;
}
/*! Assume argument is id on the type: <[prefix:]id>, return 'prefix'
* @param[in] ys A yang statement
* @retval NULL No prefix
* @retval prefix Malloced string that needs to be freed by caller.
* @see yarg_id
*/
char*
yarg_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 relative 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
* @node Prefixes are relative to the module they are defined
* @see yang_find_module_by_name
* @see yang_find_module_by_namespace
*/
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;
}
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
myprefix = yang_find_myprefix(ys);
if (myprefix && 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 &&
yimport->ys_keyword != Y_INCLUDE)
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 &&
(ymod = yang_find((yang_node*)yspec, Y_SUBMODULE, yimport->ys_argument)) == NULL){
clicon_err(OE_YANG, 0, "No module or sub-module found with prefix %s",
prefix);
yimport = NULL;
goto done; /* unresolved */
}
}
done:
return ymod;
}
/*! Given a yang spec and a namespace, return yang module
*
* @param[in] yspec A yang specification
* @param[in] namespace namespace
* @retval ymod Yang module statement if found
* @retval NULL not found
* @see yang_find_module_by_name
* @see yang_find_module_by_prefix module-specific prefix
*/
yang_stmt *
yang_find_module_by_namespace(yang_spec *yspec,
char *namespace)
{
yang_stmt *ymod = NULL;
if (namespace == NULL)
goto done;
while ((ymod = yn_each((yang_node*)yspec, ymod)) != NULL) {
if (yang_find((yang_node*)ymod, Y_NAMESPACE, namespace) != NULL)
break;
}
done:
return ymod;
}
/*! Given a yang spec and a module name, return yang module
*
* @param[in] yspec A yang specification
* @param[in] name Name of module
* @retval ymod Yang module statement if found
* @retval NULL not found
* @see yang_find_module_by_namespace
* @see yang_find_module_by_prefix module-specific prefix
*/
yang_stmt *
yang_find_module_by_name(yang_spec *yspec,
char *name)
{
yang_stmt *ymod = NULL;
while ((ymod = yn_each((yang_node*)yspec, ymod)) != NULL)
if ((ymod->ys_keyword == Y_MODULE || ymod->ys_keyword == Y_SUBMODULE) &&
strcmp(ymod->ys_argument, name)==0)
return ymod;
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;
}
/*! 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, "cbuf_new");
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) {
if (ys->ys_keyword == Y_UNKNOWN){ /* dont print unknown - proxy for extension*/
cprintf(cb, "%*s", marginal-1, "");
}
else
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, &fraction_digits)
< 0)
goto done;
restype = yrestype?yrestype->ys_argument:NULL;
if (clicon_type2cv(type, restype, ys, &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, "cv_new");
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, "cv_new_set");
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;
}
static int
ys_populate_list(yang_stmt *ys,
void *arg)
{
yang_stmt *ykey;
if ((ykey = yang_find((yang_node*)ys, Y_KEY, NULL)) == NULL)
return 0;
cvec_free(ys->ys_cvec);
if ((ys->ys_cvec = yang_arg2cvec(ykey, " ")) == NULL)
return -1;
return 0;
}
/*! Set range or length boundary for built-in yang types
* Help functions to range and length statements
*/
static int
bound_add(yang_stmt *ys,
enum cv_type cvtype,
char *name,
char *val,
int options,
uint8_t fraction_digits
)
{
int retval = -1;
cg_var *cv;
char *reason = NULL;
int ret = 1;
if ((cv = cvec_add(ys->ys_cvec, cvtype)) == NULL){
clicon_err(OE_YANG, errno, "cvec_add");
goto done;
}
if (cv_name_set(cv, name) == NULL){
clicon_err(OE_YANG, errno, "cv_name_set(%s)", name);
goto done;
}
if (options & YANG_OPTIONS_FRACTION_DIGITS && cvtype == CGV_DEC64)
cv_dec64_n_set(cv, fraction_digits);
if (strcmp(val, "min") == 0)
cv_min_set(cv);
else if (strcmp(val, "max") == 0)
cv_max_set(cv);
else if ((ret = cv_parse1(val, cv, &reason)) < 0){
clicon_err(OE_YANG, errno, "cv_parse1");
goto done;
}
if (ret == 0){ /* parsing failed */
clicon_err(OE_YANG, errno, "range statement %s: %s", val, reason);
free(reason);
goto done;
}
retval = 0;
done:
return retval;
}
/*! Populate string built-in range statement
*
* Create cvec variables "range_min" and "range_max". Assume parent is type.
* Actually: bound[..bound] (| bound[..bound])*
* where bound is integer, decimal or keywords 'min' or 'max.
* RFC 7950 9.2.4:
* A range consists of an explicit value, or a lower-inclusive bound,
* two consecutive dots "..", and an upper-inclusive bound. Multiple
* values or ranges can be given, separated by "|". If multiple values
* or ranges are given, they all MUST be disjoint and MUST be in
* ascending order
*/
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 **vec = NULL;
char *v;
char *v2;
int nvec;
int i;
yparent = ys->ys_parent; /* Find parent: type */
if (yparent->yn_keyword != Y_TYPE){
clicon_err(OE_YANG, 0, "parent should be type");
goto done;
}
if (yang_type_resolve(ys, ys, (yang_stmt*)yparent, &yrestype,
&options, NULL, NULL, &fraction_digits) < 0)
goto done;
restype = yrestype?yrestype->ys_argument:NULL;
origtype = yarg_id((yang_stmt*)yparent);
/* This handles non-resolved also */
if (clicon_type2cv(origtype, restype, ys, &cvtype) < 0)
goto done;
if ((vec = clicon_strsep(ys->ys_argument, "|", &nvec)) == NULL)
goto done;
for (i=0; i<nvec; i++){
v = vec[i++];
v = clixon_trim(v); /* trim blanks */
if ((v2 = strstr(v, "..")) != NULL){
*v2 = '\0';
v2 += 2;
v2 = clixon_trim(v2); /* trim blanks */
}
if (bound_add(ys, cvtype, "range_min", v,
options, fraction_digits) < 0)
goto done;
if (v2)
if (bound_add(ys, cvtype, "range_max",v2,
options, fraction_digits) < 0)
goto done;
}
retval = 0;
done:
if (vec)
free(vec);
return retval;
}
/*! Populate integer built-in length statement
*
* Create cvec variables "range_min" and "range_max". Assume parent is type.
* Actually: len[..len] (| len[..len])*
* len is unsigned integer or keywords 'min' or 'max.
* RFC 7950 9.4.4
* A length range consists of an explicit value, or a lower bound, two
* consecutive dots "..", and an upper bound. Multiple values or ranges
* can be given, separated by "|". Length-restricting values MUST NOT
* be negative. If multiple values or ranges are given, they all MUST
* be disjoint and MUST be in ascending order.
*/
static int
ys_populate_length(yang_stmt *ys,
void *arg)
{
int retval = -1;
yang_node *yparent; /* type */
enum cv_type cvtype = CGV_ERR;
char **vec = NULL;
char *v;
int nvec;
int i;
char *v2;
yparent = ys->ys_parent; /* Find parent: type */
if (yparent->yn_keyword != Y_TYPE){
clicon_err(OE_YANG, 0, "parent should be type");
goto done;
}
cvtype = CGV_UINT64;
if ((vec = clicon_strsep(ys->ys_argument, "|", &nvec)) == NULL)
goto done;
for (i=0; i<nvec; i++){
v = vec[i++];
v = clixon_trim(v); /* trim blanks */
if ((v2 = strstr(v, "..")) != NULL){
*v2 = '\0';
v2 += 2;
v2 = clixon_trim(v2); /* trim blanks */
}
if (bound_add(ys, cvtype, "range_min", v, 0, 0) < 0)
goto done;
if (v2)
if (bound_add(ys, cvtype, "range_max",v2, 0, 0) < 0)
goto done;
}
retval = 0;
done:
if (vec)
free(vec);
return retval;
}
/*! Sanity check yang type statement
* XXX: Replace with generic parent/child type-check
* @param[in] ys The yang statement (type) to populate.
* @
*/
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 identity statement recursively
*
* Find base identities if any and add this identity to derived list.
* Do this recursively
* @param[in] ys The yang identity to populate.
* @param[in] arg If set contains a derived identifier
* @see validate_identityref which in runtime validates actual values
*/
static int
ys_populate_identity(yang_stmt *ys,
char *idref)
{
int retval = -1;
yang_stmt *yc = NULL;
yang_stmt *ybaseid;
cg_var *cv;
char *derid;
char *baseid;
char *prefix = NULL;
cbuf *cb = NULL;
char *p;
if (idref == NULL){
/* Create derived identity through prefix:id if not recursively called*/
derid = ys->ys_argument; /* derived id */
if ((prefix = yarg_prefix(ys)) == NULL){
if ((p = yang_find_myprefix(ys)) != NULL)
prefix = strdup(yang_find_myprefix(ys));
}
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
if (prefix)
cprintf(cb, "%s:%s", prefix, derid);
else
cprintf(cb, "%s", derid);
idref = cbuf_get(cb);
}
/* Iterate through all base statements and check the base identity exists
* AND populate the base identity recursively
*/
yc = NULL;
while ((yc = yn_each((yang_node*)ys, yc)) != NULL) {
if (yc->ys_keyword != Y_BASE)
continue;
baseid = yc->ys_argument;
if (((ybaseid = yang_find_identity(ys, baseid))) == NULL){
clicon_err(OE_YANG, 0, "No such identity: %s", baseid);
goto done;
}
// continue; /* root identity */
/* Check if derived id is already in base identifier */
if (cvec_find(ybaseid->ys_cvec, idref) != NULL)
continue;
/* Add derived id to ybaseid */
if ((cv = cv_new(CGV_STRING)) == NULL){
clicon_err(OE_UNIX, errno, "cv_new");
goto done;
}
/* add prefix */
cv_name_set(cv, idref);
cvec_append_var(ybaseid->ys_cvec, cv); /* cv copied */
if (cv){
cv_free(cv);
cv = NULL;
}
/* Transitive to the root */
if (ys_populate_identity(ybaseid, idref) < 0)
goto done;
}
retval = 0;
done:
if (prefix)
free(prefix);
if (cb)
cbuf_free(cb);
return retval;
}
/*! Populate yang feature statement - set cv to 1 if enabled
*
* @param[in] ys Feature yang statement to populate.
* @param[in] h Clicon handle
*/
static int
ys_populate_feature(clicon_handle h,
yang_stmt *ys)
{
int retval = -1;
cxobj *x;
yang_stmt *ymod;
int found = 0;
cg_var *cv;
char *module;
char *feature;
cxobj *xc;
/* get clicon config file in xml form */
if ((x = clicon_conf_xml(h)) == NULL)
goto ok;
if ((ymod = ys_module(ys)) == NULL){
clicon_err(OE_YANG, 0, "module not found");
goto done;
}
module = ymod->ys_argument;
feature = ys->ys_argument;
xc = NULL;
while ((xc = xml_child_each(x, xc, CX_ELMNT)) != NULL && found == 0) {
char *m = NULL;
char *f = NULL;
if (strcmp(xml_name(xc), "CLICON_FEATURE") != 0)
continue;
/* get m and f from configuration feature rules */
if (nodeid_split(xml_body(xc), &m, &f) < 0)
goto done;
if (m && f &&
(strcmp(m,"*")==0 ||
strcmp(m, module)==0) &&
(strcmp(f,"*")==0 ||
strcmp(f, feature)==0))
found = 1;
if (m) free(m);
if (f) free(f);
}
if ((cv = cv_new(CGV_BOOL)) == NULL){
clicon_err(OE_YANG, errno, "cv_new");
goto done;
}
cv_name_set(cv, feature);
cv_bool_set(cv, found);
if (found)
clicon_debug(2, "%s %s:%s", __FUNCTION__, module, feature);
ys->ys_cv = cv;
ok:
retval = 0;
done:
return retval;
}
/*! Populate unknown node with extension
*/
static int
ys_populate_unknown(yang_stmt *ys)
{
int retval = -1;
int cvret;
char *reason = NULL;
yang_stmt *ymod;
char *prefix = NULL;
char *name;
char *extra;
if ((extra = ys->ys_extra) == NULL)
goto ok;
/* Find extension, if found, store it as unknown, if not,
break for error */
prefix = yarg_prefix(ys); /* And this its prefix */
name = yarg_id(ys); /* This is the type to resolve */
if ((ymod = yang_find_module_by_prefix(ys, prefix)) == NULL)
goto ok; /* shouldnt happen */
if (yang_find((yang_node*)ymod, Y_EXTENSION, name) == NULL){
clicon_err(OE_YANG, errno, "Extension %s:%s not found", prefix, name);
goto done;
}
if ((ys->ys_cv = cv_new(CGV_STRING)) == NULL){
clicon_err(OE_YANG, errno, "cv_new");
goto done;
}
if ((cvret = cv_parse1(extra, ys->ys_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);
goto done;
}
ok:
retval = 0;
done:
if (prefix)
free(prefix);
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
*/
int
ys_populate(yang_stmt *ys,
void *arg)
{
int retval = -1;
// clicon_handle h = (clicon_handle)arg;
switch(ys->ys_keyword){
case Y_LEAF:
case Y_LEAF_LIST:
if (ys_populate_leaf(ys, NULL) < 0)
goto done;
break;
case Y_LIST:
if (ys_populate_list(ys, NULL) < 0)
goto done;
break;
case Y_RANGE:
if (ys_populate_range(ys, NULL) < 0)
goto done;
break;
case Y_LENGTH:
if (ys_populate_length(ys, NULL) < 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, NULL) < 0)
goto done;
break;
case Y_IDENTITY:
if (ys_populate_identity(ys, NULL) < 0)
goto done;
break;
case Y_UNKNOWN:
if (ys_populate_unknown(ys) < 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(2, "%s %s", __FUNCTION__, schema_nodeid);
/* Find the target */
if (yang_abs_schema_nodeid(ysp, ys, schema_nodeid, -1, &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 = yarg_id(ys); /* This is uses/grouping name to resolve */
prefix = yarg_prefix(ys); /* And this its prefix */
if (ys_grouping_resolve(ys, prefix, name, &ygrouping) < 0)
goto done;
if (ygrouping == NULL){
clicon_log(LOG_NOTICE, "%s: Yang error : grouping \"%s\" not found in module \"%s\"",
__FUNCTION__, ys->ys_argument, ys_module(ys)->ys_argument);
goto done;
break;
}
if (prefix)
free(prefix); /* XXX move up */
/* 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, "realloc");
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[in] h CLICON handle
* @param[in] str String of yang statements
* @param[in] name Log string, typically filename
* @param[in] 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_filename # Read yang file into a string
* yang_parse_file # Read yang open file descriptor 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(char *str,
const char *name, /* just for errs */
yang_spec *yspec)
{
struct clicon_yang_yacc_arg yy = {0,};
yang_stmt *ymod = NULL;
if (yspec == NULL){
clicon_err(OE_YANG, 0, "Yang parse need top level yang spec");
goto done;
}
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);
if (yy.yy_stack)
free (yy.yy_stack);
return ymod; /* top-level (sub)module */
}
/*! Parse yang spec from an open file descriptor
* @param[in] fd File descriptor containing the YANG file as ASCII characters
* @param[in] name For debug, eg filename
* @param[in] ysp Yang specification. Should have been created by caller using yspec_new
* @retval ymod Top-level yang (sub)module
* @retval NULL Error
*/
yang_stmt *
yang_parse_file(int fd,
const char *name,
yang_spec *ysp)
{
char *buf = NULL;
int i;
int c;
int len;
yang_stmt *ymod = NULL;
int ret;
len = BUFLEN; /* 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 ((ret = read(fd, &c, 1)) < 0){
clicon_err(OE_XML, errno, "read");
break;
}
if (ret == 0)
break; /* eof */
if (len==i){
if ((buf = realloc(buf, 2*len)) == NULL){
clicon_err(OE_XML, errno, "realloc");
goto done;
}
memset(buf+len, 0, len);
len *= 2;
}
buf[i++] = (char)(c&0xff);
} /* read a line */
if ((ymod = yang_parse_str(buf, name, ysp)) < 0)
goto done;
done:
if (buf)
free(buf);
return ymod; /* top-level (sub)module */
}
/*! No specific revision give. Match a yang file given module
* @param[in] h CLICON handle
* @param[in] dir Directory, if NULL, look in YANG_DIR path
* @param[in] module Name of main YANG module.
* @param[in] revision Revision or NULL
* @param[out] fbuf Buffer containing filename
* @note for bootstrapping, dir may have to be set.
* @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 *module,
const char *revision,
cbuf *fbuf)
{
int retval = -1;
struct dirent *dp = NULL;
int ndp;
cbuf *regex = NULL;
cxobj *x;
cxobj *xc;
char *dir;
/* get clicon config file in xml form */
if ((x = clicon_conf_xml(h)) == NULL)
goto ok;
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
*/
if (revision)
cprintf(regex, "^%s@%s(.yang)$", module, revision);
else
cprintf(regex, "^%s(@[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9])?(.yang)$",
module);
xc = NULL;
while ((xc = xml_child_each(x, xc, CX_ELMNT)) != NULL) {
if (strcmp(xml_name(xc), "CLICON_YANG_DIR") != 0)
continue;
dir = xml_body(xc);
/* get all matching files in this directory */
if ((ndp = clicon_file_dirent(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", dir, dp[ndp-1].d_name);
retval = 1;
goto done;
}
}
ok:
retval = 0;
done:
if (regex)
cbuf_free(regex);
if (dp)
free(dp);
return retval;
}
/*! Open a file, read into a string and invoke yang parsing
*
* Similar to clicon_yang_str(), just read a file first
* (cloned from cligen)
* @param[in] h CLICON handle
* @param[in] filename Name of file
* @param[in] 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_filename # Read yang file into a string
* yang_parse_file # Read yang open file descriptor 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_filename(const char *filename,
yang_spec *ysp)
{
yang_stmt *ymod = NULL;
int fd = -1;
struct stat st;
clicon_debug(1, "%s %s", __FUNCTION__, filename);
if (stat(filename, &st) < 0){
clicon_err(OE_YANG, errno, "%s not found", filename);
goto done;
}
if ((fd = open(filename, O_RDONLY)) < 0){
clicon_err(OE_YANG, errno, "open(%s)", filename);
goto done;
}
if ((ymod = yang_parse_file(fd, filename, ysp)) < 0)
goto done;
done:
if (fd != -1)
close(fd);
return ymod; /* top-level (sub)module */
}
static yang_stmt *
yang_parse_module(clicon_handle h,
const char *module,
const char *revision,
yang_spec *ysp)
{
cbuf *fbuf = NULL;
int nr;
yang_stmt *ymod = NULL;
if ((fbuf = cbuf_new()) == NULL){
clicon_err(OE_YANG, errno, "cbuf_new");
goto done;
}
/* Match a yang file with or without revision in yang-dir list */
if ((nr = yang_parse_find_match(h, module, revision, fbuf)) < 0)
goto done;
if (nr == 0){
clicon_err(OE_YANG, errno, "No yang files found matching \"%s\" in the list of CLICON_YANG_DIRs", module);
goto done;
}
if ((ymod = yang_parse_filename(cbuf_get(fbuf), ysp)) == NULL)
goto done;
done:
if (fbuf)
cbuf_free(fbuf);
return ymod; /* top-level (sub)module */
}
/*! Given a (sub)module, parse all (sub)modules in turn recursively
*
* @param[in] h CLICON handle
* @param[in] module Name of main YANG module. Or absolute file name.
* @param[in] revision Module revision date or NULL
* @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_filename # Read yang file into a string
* yang_parse_file # Read yang open file descriptor 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 int
yang_parse_recurse(clicon_handle h,
yang_stmt *ymod,
yang_spec *ysp)
{
int retval = -1;
yang_stmt *yi = NULL; /* import */
yang_stmt *yrev;
char *submodule;
char *subrevision;
yang_stmt *subymod;
enum rfc_6020 keyw;
/* go through all import (modules) and include(submodules) of ysp */
while ((yi = yn_each((yang_node*)ymod, yi)) != NULL){
keyw = yi->ys_keyword;
if (keyw != Y_IMPORT && keyw != Y_INCLUDE)
continue;
/* common part */
submodule = yi->ys_argument;
/* Is there a specific revision (or just latest)? */
if ((yrev = yang_find((yang_node*)yi, Y_REVISION_DATE, NULL)) != NULL)
subrevision = yrev->ys_argument;
else
subrevision = NULL;
/* if already loaded, ignore, else parse the file */
if (yang_find((yang_node*)ysp,
keyw==Y_IMPORT?Y_MODULE:Y_SUBMODULE,
submodule) == NULL){
/* recursive call */
if ((subymod = yang_parse_module(h, submodule, subrevision, ysp)) == NULL)
goto done;
/* Go through its sub-modules recursively */
if (yang_parse_recurse(h, subymod, ysp) < 0){
ymod = NULL;
goto done;
}
}
}
retval = 0;
done:
return retval; /* 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 */
yp->yn_keyword == Y_SUBMODULE)
break;
/* fallthru */
case Y_REFINE:
case Y_UNIQUE:
if (yang_desc_schema_nodeid(yp, ys->ys_argument, -1, &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->ys_argument, -1, &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;
}
/*! Find feature and if-feature nodes, check features and remove disabled nodes
* @retval -1 Error
* @retval 0 Feature not enabled: remove yt
* @retval 1 OK
* @note On return 1 the over-lying function need to remove yt from its parent
* @note cannot use yang_apply here since child-list is modified (destructive)
*/
static int
yang_features(clicon_handle h,
yang_stmt *yt)
{
int retval = -1;
int i;
int j;
yang_stmt *ys = NULL;
char *prefix = NULL;
char *feature = NULL;
yang_stmt *ymod; /* module yang node */
yang_stmt *yfeat; /* feature yang node */
i = 0;
while (i<yt->ys_len){ /* Note, children may be removed */
ys = yt->ys_stmt[i];
if (ys->ys_keyword == Y_IF_FEATURE){
if (nodeid_split(ys->ys_argument, &prefix, &feature) < 0)
goto done;
/* Specifically need to handle? strcmp(prefix, myprefix)) */
if (prefix == NULL)
ymod = ys_module(ys);
else
ymod = yang_find_module_by_prefix(yt, prefix);
/* Check if feature exists, and is set, otherwise remove */
if ((yfeat = yang_find((yang_node*)ymod, Y_FEATURE, feature)) == NULL ||
yfeat->ys_cv == NULL || !cv_bool_get(yfeat->ys_cv)){
retval = 0; /* feature not enabled */
goto done;
}
if (prefix){
free(prefix);
prefix = NULL;
}
if (feature){
free(feature);
feature = NULL;
}
}
else
if (ys->ys_keyword == Y_FEATURE){
if (ys_populate_feature(h, ys) < 0)
goto done;
} else switch (yang_features(h, ys)){
case -1: /* error */
goto done;
break;
case 0: /* disabled: remove ys */
for (j=i+1; j<yt->ys_len; j++)
yt->ys_stmt[j-1] = yt->ys_stmt[j];
yt->ys_len--;
yt->ys_stmt[yt->ys_len] = NULL;
ys_free(ys);
continue; /* Don't increment i */
break;
default: /* ok */
break;
}
i++;
}
retval = 1;
done:
if (prefix)
free(prefix);
if (feature)
free(feature);
return retval;
}
/*! Merge yang submodule into the module it belongs to
* Skip submodule header fields
* @param[in] h Clicon handle
* @param[in] yspec Yang spec
* @param[in] ysubm Yang submodule
*/
static int
yang_merge_submodules(clicon_handle h,
yang_spec *yspec,
yang_stmt *ysubm)
{
int retval = -1;
yang_stmt *yb; /* belongs-to */
yang_stmt *ymod; /* parent yang module */
yang_stmt *yc; /* yang child */
char *modname;
int i;
assert(ysubm->ys_keyword == Y_SUBMODULE);
/* Get parent name (via belongs-to) and find parent module */
if ((yb = yang_find((yang_node*)ysubm, Y_BELONGS_TO, NULL)) == NULL){
clicon_err(OE_YANG, ENOENT, "submodule %s does not have a mandatory belongs-to statement", ysubm->ys_argument);
goto done;
}
modname = yb->ys_argument;
if ((ymod = yang_find((yang_node*)yspec, Y_MODULE, modname)) == NULL){
clicon_err(OE_YANG, ENOENT, "Submodule %s is loaded before/without its main module %s (you need to load the submodule together with or after the main module)",
ysubm->ys_argument,
modname);
goto done;
}
/* Move sub-module statements to modules
* skip belongs-to, revision, organization, reference, yang-version)
* since main module has its own and may only have one
* XXX: use queue,...
*/
for (i=0; i<ysubm->ys_len; i++){
yc = ysubm->ys_stmt[i];
if (yc->ys_keyword == Y_BELONGS_TO ||
yc->ys_keyword == Y_CONTACT ||
yc->ys_keyword == Y_DESCRIPTION ||
yc->ys_keyword == Y_ORGANIZATION ||
yc->ys_keyword == Y_REVISION ||
yc->ys_keyword == Y_REFERENCE ||
yc->ys_keyword == Y_YANG_VERSION)
ys_free(yc);
else{
if (yn_insert((yang_node*)ymod, yc) < 0)
goto done;
}
}
if (ysubm->ys_stmt){
free(ysubm->ys_stmt);
ysubm->ys_stmt = NULL;
}
ysubm->ys_len = 0;
ys_free(ysubm);
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] filename File name containing Yang specification. Overrides module
* @param[in] module Name of main YANG module. Or absolute file name.
* @param[in] revision Main module revision date string or NULL
* @param[in,out] ysp Yang specification. Should have 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_filename # Read yang file into a string
* yang_parse_file # Read yang open file descriptor 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 int
yang_parse_post(clicon_handle h,
yang_spec *yspec,
int modnr)
{
int retval = -1;
int i;
/* 1: Parse from text to yang parse-tree. */
/* Iterate through modules */
for (i=modnr; i<yspec->yp_len; i++)
if (yang_parse_recurse(h, yspec->yp_stmt[i], yspec) < 0)
goto done;
/* 2. Check cardinality maybe this should be done after grouping/augment */
for (i=modnr; i<yspec->yp_len; i++)
if (yang_cardinality(h, yspec->yp_stmt[i], yspec->yp_stmt[i]->ys_argument) < 0)
goto done;
/* 3: Merge sub-modules with modules - after this step, no submodules exist
* In the merge, remove submodule headers
*/
i = modnr;
while (i<yspec->yp_len){
int j;
if (yspec->yp_stmt[i]->ys_keyword != Y_SUBMODULE){
i++;
continue;
}
if (yang_merge_submodules(h, yspec, yspec->yp_stmt[i]) < 0)
goto done;
/* shift down one step */
for (j=i; j<yspec->yp_len-1; j++)
yspec->yp_stmt[j] = yspec->yp_stmt[j+1];
yspec->yp_len--;
}
/* 4: Check features: check if enabled and remove disabled features */
for (i=modnr; i<yspec->yp_len; i++) /* XXX */
if (yang_features(h, yspec->yp_stmt[i]) < 0)
goto done;
/* 5: Go through parse tree and populate it with cv types */
for (i=modnr; i<yspec->yp_len; i++)
if (yang_apply((yang_node*)yspec->yp_stmt[i], -1, ys_populate, (void*)h) < 0)
goto done;
/* 6: Resolve all types: populate type caches. Requires eg length/range cvecs
* from ys_populate step.
* Must be done using static binding.
*/
for (i=modnr; i<yspec->yp_len; i++)
if (yang_apply((yang_node*)yspec->yp_stmt[i], Y_TYPE, ys_resolve_type, NULL) < 0)
goto done;
/* Up to here resolving is made in the context they are defined, rather
* than the context they are used (except for submodules being merged w
* modules). Like static scoping.
* After this we expand all grouping/uses and unfold all macros into a
*single tree as they are used.
*/
/* 7: Macro expansion of all grouping/uses pairs. Expansion needs marking */
for (i=modnr; i<yspec->yp_len; i++){
if (yang_expand((yang_node*)yspec->yp_stmt[i]) < 0)
goto done;
yang_apply((yang_node*)yspec->yp_stmt[i], -1, ys_flag_reset, (void*)YANG_FLAG_MARK);
}
/* 8: Top-level augmentation of all modules XXX: only new modules? */
if (yang_augment_spec(yspec) < 0)
goto done;
/* 9: sanity check of schemanode references, need more here */
for (i=modnr; i<yspec->yp_len; i++)
if (yang_apply((yang_node*)yspec->yp_stmt[i], -1, ys_schemanode_check, NULL) < 0)
goto done;
retval = 0;
done:
return retval;
}
/*! Parse yang specification and its dependencies recursively given module
* @param[in] h clicon handle
* @param[in] module Module name, or absolute filename (including dir)
* @param[in] dir Directory where to look for modules and sub-modules
* @param[in] revision Revision, or NULL
* @param[in,out] yspec Modules parse are added to this yangspec
* @retval 0 OK
* @retval -1 Error
* @see yang_spec_parse_file
*/
int
yang_spec_parse_module(clicon_handle h,
const char *module,
const char *revision,
yang_spec *yspec)
{
int retval = -1;
int modnr; /* Existing number of modules */
char *base = NULL;;
if (yspec == NULL){
clicon_err(OE_YANG, EINVAL, "yang spec is NULL");
goto done;
}
if (module == NULL){
clicon_err(OE_YANG, EINVAL, "yang module not set");
goto done;
}
/* Apply steps 2.. on new modules, ie ones after modnr. */
modnr = yspec->yp_len;
/* Do not load module if it already exists */
if (yang_find((yang_node*)yspec, Y_MODULE, module) != NULL)
goto ok;
if (yang_parse_module(h, module, revision, yspec) == NULL)
goto done;
if (yang_parse_post(h, yspec, modnr) < 0)
goto done;
ok:
retval = 0;
done:
if (base)
free(base);
return retval;
}
/*! Parse yang specification and its dependencies recursively given filename
* @param[in] h clicon handle
* @param[in] filename Actual filename (including dir and revision)
* @param[in] dir Directory for sub-modules
* @param[in,out] yspec Modules parse are added to this yangspec
* @retval 0 OK
* @retval -1 Error
* @see yang_spec_parse_module for yang dir,module,revision instead of
* actual filename
* @see yang_spec_load_dir For loading all files in a directory
*/
int
yang_spec_parse_file(clicon_handle h,
const char *filename,
yang_spec *yspec)
{
int retval = -1;
int modnr; /* Existing number of modules */
char *base = NULL;;
/* Apply steps 2.. on new modules, ie ones after modnr. */
modnr = yspec->yp_len;
/* Find module, and do not load file if module already exists */
if (basename(filename) == NULL){
clicon_err(OE_YANG, errno, "No basename");
goto done;
}
if ((base = strdup(basename(filename))) == NULL){
clicon_err(OE_YANG, errno, "strdup");
goto done;
}
if (index(base, '@') != NULL)
*index(base, '@') = '\0';
if (yang_find((yang_node*)yspec, Y_MODULE, base) != NULL)
goto ok;
if (yang_parse_filename(filename, yspec) == NULL)
goto done;
if (yang_parse_post(h, yspec, modnr) < 0)
goto done;
ok:
retval = 0;
done:
if (base)
free(base);
return retval;
}
/*! Load all yang modules in directory
* @param[in] h Clicon handle
* @param[in] dir Load all yang modules in this directory
* @param[in,out] yspec Modules parse are added to this yangspec
* @retval 0 OK
* @retval -1 Error
* @see yang_spec_parse_file
*/
int
yang_spec_load_dir(clicon_handle h,
char *dir,
yang_spec *yspec)
{
int retval = -1;
int ndp;
struct dirent *dp = NULL;
int i;
char filename[MAXPATHLEN];
char *base = NULL; /* filename without dir */
char *b;
int j;
int modnr;
/* Get plugin objects names from plugin directory */
if((ndp = clicon_file_dirent(dir, &dp, "(.yang)$", S_IFREG)) < 0)
goto done;
if (ndp == 0)
clicon_log(LOG_WARNING, "%s: No yang files found in %s",
__FUNCTION__, dir);
/* Apply post steps on new modules, ie ones after modnr. */
modnr = yspec->yp_len;
/* Load all yang files in dir */
for (i = 0; i < ndp; i++) {
/* base = module name [+ @rev ] + .yang */
if (base)
free(base);
if ((base = strdup(dp[i].d_name)) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
*rindex(base, '.') = '\0'; /* strip postfix .yang */
/* base = module name [+ @rev]
* if it hasnt @rev then prefer it (dont check other files w @rev)
* Otherwise see if there is a newer
*/
if ((b = index(base, '@')) != NULL){
*b = '\0';
/* base = module name */
/* Entries are sorted, see if later entry exists (include @), if so
* skip this one and take last.
* Assume file without @ is last
*/
for (j = (i+1); j < ndp; j++)
if (strncmp(base, dp[j].d_name, strlen(base)) == 0)
break;
if (j<ndp){ /* exists later entry */
clicon_debug(1, "%s skip %s", __FUNCTION__, dp[i].d_name);
continue;
}
}
snprintf(filename, MAXPATHLEN-1, "%s/%s", dir, dp[i].d_name);
/* Dont parse if already exists */
if (yang_find((yang_node*)yspec, Y_MODULE, base) != NULL ||
yang_find((yang_node*)yspec, Y_SUBMODULE, base) != NULL)
continue;
if (yang_parse_filename(filename, yspec) == NULL)
goto done;
}
if (yang_parse_post(h, yspec, modnr) < 0)
goto done;
retval = 0;
done:
if (base)
free(base);
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[in] keyword A schemode of this type, or -1 if any
* @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,
enum rfc_6020 keyword,
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;
if (keyword != -1 && keyword != ys->ys_keyword)
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, keyword, 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] yn Original yang stmt (where call is made) if any
* @param[in] schema_nodeid Absolute schema-node-id, ie /a/b
* @param[in] keyword A schemode of this type, or -1 if any
* @param[out] yres Result yang statement node, or NULL if not found
* @retval -1 Error, with clicon_err called
* @retval 0 OK (if yres set then found, if yres=0 then not found)
* Assume schema nodeid:s have prefixes, (actually the first).
* @see yang_desc_schema_nodeid
* @see RFC7950 6.5
* o schema node: A node in the schema tree. One of action, container,
* leaf, leaf-list, list, choice, case, rpc, input, output,
* notification, anydata, and anyxml.
* Used in yang: deviation, top-level augment
*/
int
yang_abs_schema_nodeid(yang_spec *yspec,
yang_stmt *yn,
char *schema_nodeid,
enum rfc_6020 keyword,
yang_stmt **yres)
{
int retval = -1;
char **vec = NULL;
int nvec;
yang_stmt *ymod = NULL;
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, "strsep");
goto done;
}
/* Assume schema nodeid looks like: "/prefix:id[/prefix:id]*" */
if (nvec < 2){
clicon_err(OE_YANG, 0, "NULL or truncated path: %s",
schema_nodeid);
goto done;
}
/* split <prefix>:<id> */
if ((id = strchr(vec[1], ':')) == NULL){ /* no prefix */
clicon_log(LOG_WARNING, "%s: Absolute schema nodeid %s must have prefix", __FUNCTION__, schema_nodeid);
goto ok;
}
if ((prefix = strdup(vec[1])) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
prefix[id-vec[1]] = '\0';
id++;
if (yn) /* Find module using local prefix definition */
ymod = yang_find_module_by_prefix(yn, prefix);
if (ymod == NULL){ /* Try (global) prefix the module itself uses */
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 (schema_nodeid_vec((yang_node*)ymod, vec+1, nvec-1, keyword, yres) < 0)
goto done;
ok: /* yres may not be set */
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
* @param[in] keyword A schemode of this type, or -1 if any
* @param[out] yres First yang node matching schema nodeid
* @retval 0 OK
* @retval -1 Error, with clicon_err called
* @see yang_schema_nodeid
* Used in yang: unique, refine, uses augment
*/
int
yang_desc_schema_nodeid(yang_node *yn,
char *schema_nodeid,
enum rfc_6020 keyword,
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, keyword, 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, "cv_new");
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.
* @param[in] ys yang statement
* @param[in] extra Yang extra for cornercases (unknown/extension).
*
* 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,
char *extra)
{
int retval = -1;
uint8_t fd;
switch (ys->ys_keyword){
case Y_FRACTION_DIGITS:
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;
case Y_UNKNOWN: /* XXX This code assumes ymod already loaded
but it may not be */
if (extra == NULL)
break;
ys->ys_extra = extra;
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.
* @see RFC7950 Sec 3:
* o mandatory node: A mandatory node is one of:
* 1) A leaf, choice, anydata, or anyxml node with a "mandatory"
* statement with the value "true".
* 2) A list or leaf-list node with a "min-elements" statement with a
* value greater than zero.
* 3) A container node without a "presence" statement and that has at
* least one mandatory node as a child.
*/
int
yang_mandatory(yang_stmt *ys)
{
yang_stmt *ym;
char *reason = NULL;
uint8_t min_elements; /* XXX change to 32 (need new cligen version) */
/* 1) A leaf, choice, anydata, or anyxml node with a "mandatory"
* statement with the value "true". */
if (ys->ys_keyword == Y_LEAF || ys->ys_keyword == Y_CHOICE ||
ys->ys_keyword == Y_ANYDATA || ys->ys_keyword == Y_ANYXML){
if ((ym = yang_find((yang_node*)ys, Y_MANDATORY, NULL)) != NULL){
if (ym->ys_cv != NULL) /* shouldnt happen */
return cv_bool_get(ym->ys_cv);
}
}
/* 2) A list or leaf-list node with a "min-elements" statement with a
* value greater than zero. */
else if (ys->ys_keyword == Y_LIST || ys->ys_keyword == Y_LEAF_LIST){
if ((ym = yang_find((yang_node*)ys, Y_MIN_ELEMENTS, NULL)) != NULL){
/* XXX change to 32 (need new cligen version) */
if (parse_uint8(ym->ys_argument, &min_elements, &reason) != 1){
clicon_err(OE_YANG, EINVAL, "%s", reason?reason:"parse_uint8");
return 0; /* XXX ignore error */
}
return min_elements > 0;
}
}
/* 3) A container node without a "presence" statement and that has at
* least one mandatory node as a child. */
else if (ys->ys_keyword == Y_CONTAINER &&
yang_find((yang_node*)ys, Y_PRESENCE, NULL) == NULL){
yang_stmt *yc;
int i;
for (i=0; i<ys->ys_len; i++){
yc = ys->ys_stmt[i];
if (yang_mandatory(yc))
return 1;
}
}
return 0;
}
/*! Return config state of this node
* config statement is default true.
* @note a node with config=false may not have a sub statement where
* config=true. And this function does not check the status 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;
}
/*! 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;
}