* Improved unknown handling
* Configure option `CLICON_YANG_DIR` is changed from a single directory to a path of directories
* Note CLIXON_DATADIR (=/usr/local/share/clixon) need to be in the list
2946 lines
81 KiB
C
2946 lines
81 KiB
C
/*
|
|
*
|
|
***** BEGIN LICENSE BLOCK *****
|
|
|
|
Copyright (C) 2009-2018 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
|
|
*/
|
|
|
|
#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 <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,
|
|
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;
|
|
}
|
|
#ifdef NOTYET
|
|
/*! Prototype more generic than yang_find_datanode and yang_find_schemanode
|
|
*/
|
|
yang_stmt *
|
|
yang_find_class(yang_node *yn,
|
|
char *argument,
|
|
yang_class class)
|
|
{
|
|
yang_stmt *ys = NULL;
|
|
yang_stmt *yc = NULL;
|
|
yang_stmt *ysmatch = NULL;
|
|
int i, j;
|
|
int ok;
|
|
|
|
for (i=0; i<yn->yn_len; i++){
|
|
ys = yn->yn_stmt[i];
|
|
switch(class){
|
|
case YC_NONE:
|
|
ok = 1;
|
|
break;
|
|
case YC_DATANODE:
|
|
ok = yang_datanode(ys);
|
|
break;
|
|
case YC_DATADEFINITION:
|
|
ok = yang_datadefinition(ys);
|
|
break;
|
|
case YC_SCHEMANODE:
|
|
ok = yang_schemanode(ys);
|
|
break;
|
|
}
|
|
if (!ok)
|
|
continue;
|
|
switch(class){
|
|
case YC_NONE:
|
|
if (argument == NULL)
|
|
ysmatch = ys;
|
|
else
|
|
if (ys->ys_argument && strcmp(argument, ys->ys_argument) == 0)
|
|
ysmatch = ys;
|
|
if (ysmatch)
|
|
goto match;
|
|
break;
|
|
case YC_DATANODE:
|
|
case YC_DATADEFINITION:
|
|
if (argument == NULL)
|
|
ysmatch = ys;
|
|
else
|
|
if (ys->ys_argument && strcmp(argument, ys->ys_argument) == 0)
|
|
ysmatch = ys;
|
|
if (ysmatch)
|
|
goto match;
|
|
break;
|
|
case YC_SCHEMANODE:
|
|
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_class((yang_node*)yc, argument, class);
|
|
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 (argument == NULL)
|
|
ysmatch = ys;
|
|
else
|
|
if (ys->ys_argument && strcmp(argument, ys->ys_argument) == 0)
|
|
ysmatch = ys;
|
|
if (ysmatch)
|
|
goto match;
|
|
|
|
}
|
|
break;
|
|
} /* switch */
|
|
} /* for */
|
|
match:
|
|
return ysmatch;
|
|
}
|
|
#endif /* NOTYET */
|
|
|
|
/*! 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;
|
|
}
|
|
|
|
/*! Find first matching data node in all (sub)modules in a yang spec
|
|
*
|
|
* @param[in] ysp Yang specification
|
|
* @param[in] argument Name of node. If NULL match first
|
|
* @param[in] class See yang_class for class of yang nodes
|
|
* A yang specification has modules as children which in turn can have
|
|
* syntax-nodes as children. This function goes through all the modules to
|
|
* look for nodes. Note that if a child to a module is a choice,
|
|
* the search is made recursively made to the choice's children.
|
|
*/
|
|
yang_stmt *
|
|
yang_find_topnode(yang_spec *ysp,
|
|
char *nodeid,
|
|
yang_class class)
|
|
{
|
|
yang_stmt *ymod = NULL; /* module */
|
|
yang_stmt *yres = NULL; /* result */
|
|
char *prefix = NULL;
|
|
char *id = NULL;
|
|
int i;
|
|
|
|
if (yang_nodeid_split(nodeid, &prefix, &id) < 0)
|
|
goto done;
|
|
if (prefix){
|
|
if ((ymod = yang_find((yang_node*)ysp, Y_MODULE, prefix)) != NULL ||
|
|
(ymod = yang_find((yang_node*)ysp, Y_SUBMODULE, prefix)) != NULL){
|
|
if ((yres = yang_find((yang_node*)ymod, 0, id)) != NULL)
|
|
goto ok;
|
|
goto done;
|
|
}
|
|
}
|
|
else /* No prefix given - loop through and find first */
|
|
for (i=0; i<ysp->yp_len; i++){
|
|
ymod = ysp->yp_stmt[i];
|
|
switch (class){
|
|
case YC_NONE:
|
|
if ((yres = yang_find((yang_node*)ymod, 0, id)) != NULL)
|
|
goto ok;
|
|
break;
|
|
case YC_DATANODE:
|
|
if ((yres = yang_find_datanode((yang_node*)ymod, id)) != NULL)
|
|
goto ok;
|
|
break;
|
|
case YC_SCHEMANODE:
|
|
if ((yres = yang_find_schemanode((yang_node*)ymod, id)) != NULL)
|
|
goto ok;
|
|
break;
|
|
case YC_DATADEFINITION:
|
|
break; /* nyi */
|
|
}
|
|
}
|
|
ok:
|
|
done:
|
|
if (prefix)
|
|
free(prefix);
|
|
if (id)
|
|
free(id);
|
|
return yres;
|
|
}
|
|
|
|
/*! Given a yang statement, find the prefix associated to this module
|
|
* @param[in] ys Yang statement
|
|
* @retval NULL Not found
|
|
* @retval prefix Prefix as char* pointer into yang tree
|
|
* @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;
|
|
}
|
|
|
|
/*! 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 module or sub-module given a statement.
|
|
* 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
|
|
* @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;
|
|
}
|
|
|
|
/*! Split yang node identifier into prefix and identifer.
|
|
* @param[in] node-id
|
|
* @param[out] prefix Malloced string. May be NULL.
|
|
* @param[out] id Malloced identifier.
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
* @code
|
|
* char *prefix = NULL;
|
|
* char *id = NULL;
|
|
* if (yang_nodeid_split(nodeid, &prefix, &id) < 0)
|
|
* goto done;
|
|
* if (prefix)
|
|
* free(prefix);
|
|
* if (id)
|
|
* free(id);
|
|
* @note caller need to free id and prefix after use
|
|
*/
|
|
int
|
|
yang_nodeid_split(char *nodeid,
|
|
char **prefix,
|
|
char **id)
|
|
{
|
|
int retval = -1;
|
|
char *str;
|
|
|
|
if ((str = strchr(nodeid, ':')) == NULL){
|
|
if ((*id = strdup(nodeid)) == NULL){
|
|
clicon_err(OE_YANG, errno, "strdup");
|
|
goto done;
|
|
}
|
|
}
|
|
else{
|
|
if ((*prefix = strdup(nodeid)) == NULL){
|
|
clicon_err(OE_YANG, errno, "strdup");
|
|
goto done;
|
|
}
|
|
(*prefix)[str-nodeid] = '\0';
|
|
str++;
|
|
if ((*id = strdup(str)) == NULL){
|
|
clicon_err(OE_YANG, errno, "strdup");
|
|
goto done;
|
|
}
|
|
}
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Given a yang statement and a prefix, return yang module to that prefix
|
|
* Note, not the other module but the proxy import statement only
|
|
* @param[in] ys A yang statement
|
|
* @param[in] prefix prefix
|
|
* @retval ymod Yang module statement if found
|
|
* @retval NULL not found
|
|
*/
|
|
yang_stmt *
|
|
yang_find_module_by_prefix(yang_stmt *ys,
|
|
char *prefix)
|
|
{
|
|
yang_stmt *yimport;
|
|
yang_stmt *yprefix;
|
|
yang_stmt *my_ymod;
|
|
yang_stmt *ymod = NULL;
|
|
yang_spec *yspec;
|
|
char *myprefix;
|
|
|
|
if ((yspec = ys_spec(ys)) == NULL){
|
|
clicon_err(OE_YANG, 0, "My yang spec not found");
|
|
goto done;
|
|
}
|
|
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;
|
|
}
|
|
|
|
/*! 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, 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, "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;
|
|
}
|
|
|
|
/*! 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, "parent should be type");
|
|
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 = yarg_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>",
|
|
ys->ys_argument);
|
|
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
|
|
* @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
|
|
*/
|
|
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 (yang_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);
|
|
clicon_debug(1, "%s %s:%s %d", __FUNCTION__, module, feature, found);
|
|
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:
|
|
case Y_LENGTH:
|
|
if (ys_populate_range(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(1, "%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
|
|
* Found
|
|
*/
|
|
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;
|
|
|
|
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 (yang_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, "Module %s which submodule %s belongs to is not found", modname, ysubm->ys_argument);
|
|
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
|
|
* @param[out] ymodp Yang module of first, topmost Yang module, if given.
|
|
* @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
|
|
*/
|
|
int
|
|
yang_parse(clicon_handle h,
|
|
const char *filename,
|
|
const char *module,
|
|
const char *revision,
|
|
yang_spec *ysp,
|
|
yang_stmt **ymodp)
|
|
{
|
|
int retval = -1;
|
|
yang_stmt *ymod = NULL; /* Top-level yang (sub)module */
|
|
int i;
|
|
int modnr; /* Existing number of modules */
|
|
|
|
/* Apply steps 2.. on new modules, ie ones after modnr. */
|
|
modnr = ysp->yp_len;
|
|
if (filename){
|
|
if ((ymod = yang_parse_filename(filename, ysp)) == NULL)
|
|
goto done;
|
|
}
|
|
else
|
|
if ((ymod = yang_parse_module(h, module, revision, ysp)) == NULL)
|
|
goto done;
|
|
|
|
/* 1: Parse from text to yang parse-tree. */
|
|
/* Iterate through modules */
|
|
if (yang_parse_recurse(h, ymod, ysp) < 0)
|
|
goto done;
|
|
|
|
/* 2. Check cardinality maybe this should be done after grouping/augment */
|
|
for (i=modnr; i<ysp->yp_len; i++) /* XXX */
|
|
if (yang_cardinality(h, ysp->yp_stmt[i], ysp->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
|
|
*/
|
|
for (i=modnr; i<ysp->yp_len; i++){
|
|
if (ysp->yp_stmt[i]->ys_keyword != Y_SUBMODULE)
|
|
continue;
|
|
}
|
|
i = 0;
|
|
while (i<ysp->yp_len){
|
|
int j;
|
|
if (ysp->yp_stmt[i]->ys_keyword != Y_SUBMODULE){
|
|
i++;
|
|
continue;
|
|
}
|
|
if (yang_merge_submodules(h, ysp, ysp->yp_stmt[i]) < 0)
|
|
goto done;
|
|
/* shift down one step */
|
|
for (j=i; j<ysp->yp_len-1; j++)
|
|
ysp->yp_stmt[j] = ysp->yp_stmt[j+1];
|
|
ysp->yp_len--;
|
|
}
|
|
|
|
/* 4: Check features: check if enabled and remove disabled features */
|
|
for (i=modnr; i<ysp->yp_len; i++) /* XXX */
|
|
if (yang_features(h, ysp->yp_stmt[i]) < 0)
|
|
goto done;
|
|
|
|
/* 5: Go through parse tree and populate it with cv types */
|
|
for (i=modnr; i<ysp->yp_len; i++)
|
|
if (yang_apply((yang_node*)ysp->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<ysp->yp_len; i++)
|
|
if (yang_apply((yang_node*)ysp->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<ysp->yp_len; i++){
|
|
if (yang_expand((yang_node*)ysp->yp_stmt[i]) < 0)
|
|
goto done;
|
|
yang_apply((yang_node*)ysp->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(ysp) < 0)
|
|
goto done;
|
|
|
|
/* 9: sanity check of schemanode references, need more here */
|
|
for (i=modnr; i<ysp->yp_len; i++)
|
|
if (yang_apply((yang_node*)ysp->yp_stmt[i], -1, ys_schemanode_check, NULL) < 0)
|
|
goto done;
|
|
/* Return main module parsed in step 1 */
|
|
if (ymodp)
|
|
*ymodp = ymod;
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Apply a function call recursively on all yang-stmt s recursively
|
|
*
|
|
* Recursively traverse all yang-nodes in a parse-tree and apply fn(arg) for
|
|
* each object found. The function is called with the yang-stmt and an
|
|
* argument as args.
|
|
* The tree is traversed depth-first, which at least guarantees that a parent is
|
|
* traversed before a child.
|
|
* @param[in] yn yang node
|
|
* @param[in] key yang keyword to use as filer or -1 for all
|
|
* @param[in] fn Callback
|
|
* @param[in] arg Argument
|
|
* @retval -1 Error, aborted at first error encounter
|
|
* @retval 0 OK, all nodes traversed
|
|
* @retval n OK, aborted at first encounter of first match
|
|
* @code
|
|
* int ys_fn(yang_stmt *ys, void *arg)
|
|
* {
|
|
* return 0;
|
|
* }
|
|
* yang_apply((yang_node*)ys, Y_TYPE, ys_fn, NULL);
|
|
* @endcode
|
|
* @note do not delete or move around any children during this function
|
|
*/
|
|
int
|
|
yang_apply(yang_node *yn,
|
|
enum rfc_6020 keyword,
|
|
yang_applyfn_t fn,
|
|
void *arg)
|
|
{
|
|
int retval = -1;
|
|
yang_stmt *ys = NULL;
|
|
int i;
|
|
int ret;
|
|
|
|
for (i=0; i<yn->yn_len; i++){
|
|
ys = yn->yn_stmt[i];
|
|
if (keyword == -1 || keyword == ys->ys_keyword){
|
|
if ((ret = fn(ys, arg)) < 0)
|
|
goto done;
|
|
if (ret > 0){
|
|
retval = ret;
|
|
goto done;
|
|
}
|
|
}
|
|
if ((ret = yang_apply((yang_node*)ys, keyword, fn, arg)) < 0)
|
|
goto done;
|
|
if (ret > 0){
|
|
retval = ret;
|
|
goto done;
|
|
}
|
|
}
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! All the work for schema_nodeid functions both absolute and descendant
|
|
* Ignore prefixes, see _abs
|
|
* @param[in] yn Yang node. Find next yang stmt and return that if match.
|
|
* @param[in] vec Vector of nodeid's in a schema node identifier, eg a/b
|
|
* @param[in] nvec Length of vec
|
|
* @param[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;
|
|
yang_stmt *ys;
|
|
|
|
/* 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 (ymod == NULL){ /* Try find id from topnode without prefix XXX remove?*/
|
|
if ((ys = yang_find_topnode(yspec, id, YC_SCHEMANODE)) == NULL){
|
|
clicon_err(OE_YANG, 0, "Module with id:\"%s:%s\" not found", prefix,id);
|
|
goto done;
|
|
}
|
|
if ((ymod = ys_module(ys)) == NULL){
|
|
clicon_err(OE_YANG, 0, "Module with id:%s:%s not found2", prefix,id);
|
|
goto done;
|
|
}
|
|
if ((yprefix = yang_find((yang_node*)ymod, Y_PREFIX, NULL)) != NULL &&
|
|
strcmp(yprefix->ys_argument, prefix) != 0){
|
|
clicon_err(OE_YANG, 0, "Module with id:\"%s:%s\" not found", prefix,id);
|
|
goto done;
|
|
}
|
|
}
|
|
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.
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/*! 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
|
|
* @param[out] ymodp Yang module of first, topmost Yang module, if given.
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
* @see yang_spec_parse_file
|
|
*/
|
|
int
|
|
yang_spec_parse_module(clicon_handle h,
|
|
char *module,
|
|
char *revision,
|
|
yang_spec *yspec,
|
|
yang_stmt **ymodp)
|
|
{
|
|
int retval = -1;
|
|
|
|
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;
|
|
}
|
|
/* Sanity check - use yang_spec_parse_file instead */
|
|
if (strlen(module) && module[0] == '/'){
|
|
clicon_err(OE_YANG, EINVAL, "yang module illegal format");
|
|
goto done;
|
|
}
|
|
if (yang_parse(h, NULL, module, revision, yspec, ymodp) < 0)
|
|
goto done;
|
|
retval = 0;
|
|
done:
|
|
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
|
|
* @param[out] ymodp Yang module of first, topmost Yang module, if given.
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
* @see yang_spec_parse_module for yang dir,module,revision instead of actual filename
|
|
*/
|
|
int
|
|
yang_spec_parse_file(clicon_handle h,
|
|
char *filename,
|
|
yang_spec *yspec,
|
|
yang_stmt **ymodp)
|
|
{
|
|
int retval = -1;
|
|
|
|
if (yspec == NULL){
|
|
clicon_err(OE_YANG, EINVAL, "yang spec is NULL");
|
|
goto done;
|
|
}
|
|
if (yang_parse(h, filename, NULL, NULL, yspec, ymodp) < 0)
|
|
goto done;
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Given a yang node, translate the argument string to a cv vector
|
|
*
|
|
* @param[in] ys Yang statement
|
|
* @param[in] delimiter Delimiter character (eg ' ' or ',')
|
|
* @retval NULL Error
|
|
* @retval cvec Vector of strings. Free with cvec_free()
|
|
* @code
|
|
* cvec *cvv;
|
|
* cg_var *cv = NULL;
|
|
* if ((cvv = yang_arg2cvec(ys, " ")) == NULL)
|
|
* goto err;
|
|
* while ((cv = cvec_each(cvv, cv)) != NULL)
|
|
* ...cv_string_get(cv);
|
|
* cvec_free(cvv);
|
|
* @endcode
|
|
* @note must free return value after use w cvec_free
|
|
*/
|
|
cvec *
|
|
yang_arg2cvec(yang_stmt *ys,
|
|
char *delim)
|
|
{
|
|
char **vec = NULL;
|
|
int i;
|
|
int nvec;
|
|
cvec *cvv = NULL;
|
|
cg_var *cv;
|
|
|
|
if ((vec = clicon_strsep(ys->ys_argument, " ", &nvec)) == NULL)
|
|
goto done;
|
|
if ((cvv = cvec_new(nvec)) == NULL){
|
|
clicon_err(OE_YANG, errno, "cvec_new");
|
|
goto done;
|
|
}
|
|
for (i = 0; i < nvec; i++) {
|
|
cv = cvec_i(cvv, i);
|
|
cv_type_set(cv, CGV_STRING);
|
|
if ((cv_string_set(cv, vec[i])) == NULL){
|
|
clicon_err(OE_YANG, errno, "cv_string_set");
|
|
cvv = NULL;
|
|
goto done;
|
|
}
|
|
}
|
|
done:
|
|
if (vec)
|
|
free(vec);
|
|
return cvv;
|
|
}
|
|
|
|
/*! Check if yang node yn has key-stmt as child which matches name
|
|
*
|
|
* @param[in] yn Yang node with sub-statements (look for a key child)
|
|
* @param[in] name Check if this name (eg "b") is a key in the yang key statement
|
|
*
|
|
* @retval -1 Error
|
|
* @retval 0 No match
|
|
* @retval 1 Yes match
|
|
*/
|
|
int
|
|
yang_key_match(yang_node *yn,
|
|
char *name)
|
|
{
|
|
int retval = -1;
|
|
yang_stmt *ys = NULL;
|
|
int i;
|
|
cvec *cvv = NULL;
|
|
cg_var *cv;
|
|
|
|
for (i=0; i<yn->yn_len; i++){
|
|
ys = yn->yn_stmt[i];
|
|
if (ys->ys_keyword == Y_KEY){
|
|
if ((cvv = yang_arg2cvec(ys, " ")) == NULL)
|
|
goto done;
|
|
cv = NULL;
|
|
while ((cv = cvec_each(cvv, cv)) != NULL) {
|
|
if (strcmp(name, cv_string_get(cv)) == 0){
|
|
retval = 1; /* match */
|
|
goto done;
|
|
}
|
|
}
|
|
cvec_free(cvv);
|
|
cvv = NULL;
|
|
}
|
|
}
|
|
retval = 0;
|
|
done:
|
|
if (cvv)
|
|
cvec_free(cvv);
|
|
return retval;
|
|
}
|