3470 lines
97 KiB
C
3470 lines
97 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>
|
|
#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 <libgen.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/param.h>
|
|
#include <netinet/in.h>
|
|
#include <libgen.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_data.h"
|
|
#include "clixon_options.h"
|
|
#include "clixon_yang_parse.h"
|
|
#include "clixon_yang_cardinality.h"
|
|
#include "clixon_yang_internal.h" /* internal */
|
|
#include "clixon_yang_type.h"
|
|
|
|
/* Size of json read buffer when reading from file*/
|
|
#define BUFLEN 1024
|
|
|
|
/*
|
|
* Local variables
|
|
*/
|
|
/* Mapping between yang keyword string <--> clicon constants
|
|
* Here is also the place where doc on some types store variables (cv)
|
|
*/
|
|
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}, /* cv: boolean config flag */
|
|
{"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}, /* cv: feature as boolean */
|
|
{"fraction-digits", Y_FRACTION_DIGITS}, /* cv: fraction-digits as uint8 */
|
|
{"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}, /* cv: store default value (if any)*/
|
|
{"leaf-list", Y_LEAF_LIST}, /* cv: store default value (if any)*/
|
|
{"length", Y_LENGTH},
|
|
{"list", Y_LIST},
|
|
{"mandatory", Y_MANDATORY}, /* cv: store mandatory boolean */
|
|
{"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}, /* cv: YYYY-MM-DD as uint32 */
|
|
{"revision-date", Y_REVISION_DATE}, /* cv: YYYY-MM-DD as uint32 */
|
|
{"rpc", Y_RPC},
|
|
{"status", Y_STATUS},
|
|
{"submodule", Y_SUBMODULE},
|
|
{"type", Y_TYPE},
|
|
{"typedef", Y_TYPEDEF},
|
|
{"unique", Y_UNIQUE},
|
|
{"units", Y_UNITS},
|
|
{"unknown", Y_UNKNOWN}, /* cv: store extra string */
|
|
{"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}
|
|
};
|
|
|
|
/* Access functions
|
|
*/
|
|
|
|
/*! Get yang statement parent
|
|
* @param[in] ys Yang statement node
|
|
*/
|
|
yang_stmt *
|
|
yang_parent_get(yang_stmt *ys)
|
|
{
|
|
return ys->ys_parent;
|
|
}
|
|
|
|
/*! Get yang statement keyword
|
|
* @param[in] ys Yang statement node
|
|
*/
|
|
enum rfc_6020
|
|
yang_keyword_get(yang_stmt *ys)
|
|
{
|
|
return ys->ys_keyword;
|
|
}
|
|
|
|
/*! Get yang statement context-dependent argument
|
|
* @param[in] ys Yang statement node
|
|
*/
|
|
char*
|
|
yang_argument_get(yang_stmt *ys)
|
|
{
|
|
return ys->ys_argument;
|
|
}
|
|
|
|
/*! Get yang statement CLIgen variable
|
|
* @param[in] ys Yang statement node
|
|
*/
|
|
cg_var*
|
|
yang_cv_get(yang_stmt *ys)
|
|
{
|
|
return ys->ys_cv;
|
|
}
|
|
|
|
/*! Get yang statement CLIgen variable vector
|
|
* @param[in] ys Yang statement node
|
|
*/
|
|
cvec*
|
|
yang_cvec_get(yang_stmt *ys)
|
|
{
|
|
return ys->ys_cvec;
|
|
}
|
|
|
|
/*! Set yang statement CLIgen variable vector
|
|
* @param[in] ys Yang statement node
|
|
* @param[in] cvec CLIgen vector
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*/
|
|
int
|
|
yang_cvec_set(yang_stmt *ys,
|
|
cvec *cvv)
|
|
{
|
|
if (ys->ys_cvec)
|
|
cvec_free(ys->ys_cvec);
|
|
ys->ys_cvec = cvv;
|
|
return 0;
|
|
}
|
|
|
|
/* End access functions */
|
|
|
|
/*! Create new yang specification
|
|
* @retval yspec Free with yspec_free()
|
|
* @retval NULL Error
|
|
*/
|
|
yang_stmt *
|
|
yspec_new(void)
|
|
{
|
|
yang_stmt *yspec;
|
|
|
|
if ((yspec = malloc(sizeof(*yspec))) == NULL){
|
|
clicon_err(OE_YANG, errno, "malloc");
|
|
return NULL;
|
|
}
|
|
memset(yspec, 0, sizeof(*yspec));
|
|
yspec->ys_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_stmt *yspec)
|
|
{
|
|
int i;
|
|
yang_stmt *ys;
|
|
|
|
for (i=0; i<yspec->ys_len; i++){
|
|
if ((ys = yspec->ys_stmt[i]) != NULL)
|
|
ys_free(ys);
|
|
}
|
|
if (yspec->ys_stmt)
|
|
free(yspec->ys_stmt);
|
|
free(yspec);
|
|
return 0;
|
|
}
|
|
|
|
/*! Allocate larger yang statement vector adding empty field last */
|
|
static int
|
|
yn_realloc(yang_stmt *yn)
|
|
{
|
|
yn->ys_len++;
|
|
|
|
if ((yn->ys_stmt = realloc(yn->ys_stmt, (yn->ys_len)*sizeof(yang_stmt *))) == 0){
|
|
clicon_err(OE_YANG, errno, "realloc");
|
|
return -1;
|
|
}
|
|
yn->ys_stmt[yn->ys_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 = 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] ys_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_stmt *ys_parent,
|
|
yang_stmt *ys_child)
|
|
{
|
|
int pos = ys_parent->ys_len;
|
|
|
|
if (yn_realloc(ys_parent) < 0)
|
|
return -1;
|
|
ys_parent->ys_stmt[pos] = ys_child;
|
|
ys_child->ys_parent = ys_parent;
|
|
return 0;
|
|
}
|
|
|
|
/*! Iterate through all yang statements from a yang node
|
|
*
|
|
* @param[in] yparent yang statement whose children should be iterated
|
|
* @param[in] yprev previous child, or NULL on init
|
|
* @code
|
|
* yang_stmt *yprev = NULL;
|
|
* while ((yprev = yn_each(yparent, yprev)) != NULL) {
|
|
* ...yprev...
|
|
* }
|
|
* @endcode
|
|
* @note makes uses _ys_vector_i:can be changed if list changed between calls
|
|
*/
|
|
yang_stmt *
|
|
yn_each(yang_stmt *yparent,
|
|
yang_stmt *yprev)
|
|
{
|
|
int i;
|
|
yang_stmt *yc = NULL;
|
|
|
|
if (yparent == NULL)
|
|
return NULL;
|
|
for (i=yprev?yprev->_ys_vector_i+1:0; i<yparent->ys_len; i++){
|
|
if ((yc = yparent->ys_stmt[i]) == NULL){
|
|
assert(yc); /* XXX Check if happens */
|
|
continue;
|
|
}
|
|
/* make room for other conditionals */
|
|
break; /* this is next object after previous */
|
|
}
|
|
if (i < yparent->ys_len) /* found */
|
|
yc->_ys_vector_i = i;
|
|
else
|
|
yc = NULL;
|
|
return yc;
|
|
}
|
|
|
|
/*! 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_stmt *yn,
|
|
int keyword,
|
|
const char *argument)
|
|
{
|
|
yang_stmt *ys = NULL;
|
|
int i;
|
|
yang_stmt *yret = NULL;
|
|
char *name;
|
|
yang_stmt *yspec;
|
|
yang_stmt *ym;
|
|
|
|
for (i=0; i<yn->ys_len; i++){
|
|
ys = yn->ys_stmt[i];
|
|
if (keyword == 0 || ys->ys_keyword == keyword){
|
|
if (argument == NULL ||
|
|
(ys->ys_argument && strcmp(argument, ys->ys_argument) == 0)){
|
|
yret = ys;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
/* Special case: if not match and yang node is module or submodule, extend
|
|
* search to include submodules */
|
|
if (yret == NULL &&
|
|
(yang_keyword_get(yn) == Y_MODULE ||
|
|
yang_keyword_get(yn) == Y_SUBMODULE)){
|
|
yspec = ys_spec(yn);
|
|
for (i=0; i<yn->ys_len; i++){
|
|
ys = yn->ys_stmt[i];
|
|
if (yang_keyword_get(ys) == Y_INCLUDE){
|
|
name = yang_argument_get(ys);
|
|
ym = yang_find_module_by_name(yspec, name);
|
|
if ((yret = yang_find(ym, keyword, argument)) != NULL)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return yret;
|
|
}
|
|
|
|
/*! 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_stmt *yn,
|
|
int keyword,
|
|
char *argument)
|
|
{
|
|
yang_stmt *ys = NULL;
|
|
int i;
|
|
int match = 0;
|
|
|
|
for (i=0; i<yn->ys_len; i++){
|
|
ys = yn->ys_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_stmt *yn,
|
|
char *argument)
|
|
{
|
|
yang_stmt *ys = NULL;
|
|
yang_stmt *yc = NULL;
|
|
yang_stmt *yspec;
|
|
yang_stmt *ysmatch = NULL;
|
|
char *name;
|
|
int i, j;
|
|
|
|
for (i=0; i<yn->ys_len; i++){
|
|
ys = yn->ys_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(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;
|
|
}
|
|
}
|
|
/* Special case: if not match and yang node is module or submodule, extend
|
|
* search to include submodules */
|
|
if (ysmatch == NULL &&
|
|
(yang_keyword_get(yn) == Y_MODULE ||
|
|
yang_keyword_get(yn) == Y_SUBMODULE)){
|
|
yspec = ys_spec(yn);
|
|
for (i=0; i<yn->ys_len; i++){
|
|
ys = yn->ys_stmt[i];
|
|
if (yang_keyword_get(ys) == Y_INCLUDE){
|
|
name = yang_argument_get(ys);
|
|
yc = yang_find_module_by_name(yspec, name);
|
|
if ((ysmatch = yang_find_datanode(yc, argument)) != NULL)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
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_stmt *yn,
|
|
char *argument)
|
|
{
|
|
yang_stmt *ys = NULL;
|
|
yang_stmt *yc = NULL;
|
|
yang_stmt *yspec;
|
|
yang_stmt *ysmatch = NULL;
|
|
char *name;
|
|
int i, j;
|
|
|
|
for (i=0; i<yn->ys_len; i++){
|
|
ys = yn->ys_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(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;
|
|
}
|
|
}
|
|
/* Special case: if not match and yang node is module or submodule, extend
|
|
* search to include submodules */
|
|
if (ysmatch == NULL &&
|
|
(yang_keyword_get(yn) == Y_MODULE ||
|
|
yang_keyword_get(yn) == Y_SUBMODULE)){
|
|
yspec = ys_spec(yn);
|
|
for (i=0; i<yn->ys_len; i++){
|
|
ys = yn->ys_stmt[i];
|
|
if (yang_keyword_get(ys) == Y_INCLUDE){
|
|
name = yang_argument_get(ys);
|
|
yc = yang_find_module_by_name(yspec, name);
|
|
if ((ysmatch = yang_find_schemanode(yc, argument)) != NULL)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
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;
|
|
|
|
/* Not good enough with submodule, must be actual module */
|
|
if ((ymod = ys_real_module(ys)) == NULL){
|
|
clicon_err(OE_YANG, 0, "My yang module not found");
|
|
goto done;
|
|
}
|
|
if ((yprefix = yang_find(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_real_module(ys)) == NULL){
|
|
clicon_err(OE_YANG, 0, "My yang module not found");
|
|
goto done;
|
|
}
|
|
if ((ynamespace = yang_find(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_stmt *
|
|
yang_choice(yang_stmt *y)
|
|
{
|
|
yang_stmt *yp;
|
|
|
|
if ((yp = y->ys_parent) != NULL){
|
|
switch (yang_keyword_get(yp)){
|
|
case Y_CHOICE:
|
|
return yp;
|
|
break;
|
|
case Y_CASE:
|
|
return yang_parent_get(yp);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*! Find matching y in yp:s children, "yang order" of y when y is choice
|
|
* @param[in] yp Choice node
|
|
* @param[in] y Yang datanode to find
|
|
* @param[out] index Index of y in yp:s list of children
|
|
* @retval 0 not found (must be datanode)
|
|
* @retval 1 found
|
|
* @see order1 the main function
|
|
* There are two distinct cases, either (1) the choice has case statements, or
|
|
* (2) it uses shortcut mode without case statements.
|
|
* In (1) we need to count how many sub-statements and keep a max
|
|
* In (2) we increment with only 1.
|
|
*/
|
|
static int
|
|
order1_choice(yang_stmt *yp,
|
|
yang_stmt *y,
|
|
int *index)
|
|
{
|
|
yang_stmt *ys;
|
|
yang_stmt *yc;
|
|
int i;
|
|
int j;
|
|
int shortcut=0;
|
|
int max=0;
|
|
|
|
for (i=0; i<yp->ys_len; i++){ /* Loop through choice */
|
|
ys = yp->ys_stmt[i];
|
|
if (ys->ys_keyword == Y_CASE){ /* Loop through case */
|
|
for (j=0; j<ys->ys_len; j++){
|
|
yc = ys->ys_stmt[j];
|
|
if (yang_datanode(yc) && yc == y){
|
|
*index += j;
|
|
return 1;
|
|
}
|
|
}
|
|
if (j>max)
|
|
max = j;
|
|
}
|
|
else {
|
|
shortcut = 1; /* Shortcut, no case */
|
|
if (yang_datanode(ys) && ys == y)
|
|
return 1;
|
|
}
|
|
}
|
|
if (shortcut)
|
|
(*index)++;
|
|
else
|
|
*index += max;
|
|
return 0;
|
|
}
|
|
|
|
/*! Find matching y in yp:s children, return "yang order" of y or -1 if not found
|
|
* @param[in] yp Parent
|
|
* @param[in] y Yang datanode to find
|
|
* @param[out] index Index of y in yp:s list of children
|
|
* @retval 0 not found (must be datanode)
|
|
* @retval 1 found
|
|
*/
|
|
static int
|
|
order1(yang_stmt *yp,
|
|
yang_stmt *y,
|
|
int *index)
|
|
{
|
|
yang_stmt *ys;
|
|
int i;
|
|
|
|
for (i=0; i<yp->ys_len; i++){
|
|
ys = yp->ys_stmt[i];
|
|
if (ys->ys_keyword == Y_CHOICE){
|
|
if (order1_choice(ys, y, index) == 1) /* If one of the choices is "y" */
|
|
return 1;
|
|
}
|
|
else {
|
|
if (!yang_datanode(ys))
|
|
continue;
|
|
if (ys==y)
|
|
return 1;
|
|
(*index)++;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*! Return order of yang statement y in parents child vector
|
|
* @param[in] y Find position of this data-node
|
|
* @param[out] index Index of y in yp:s list of children
|
|
* @retval >=0 Order of child with specified argument
|
|
* @retval -1 Not found
|
|
* @note special handling if y is child of (sub)module
|
|
*/
|
|
int
|
|
yang_order(yang_stmt *y)
|
|
{
|
|
yang_stmt *yp;
|
|
yang_stmt *ypp;
|
|
yang_stmt *ym;
|
|
int i;
|
|
int j=0;
|
|
int tot = 0;
|
|
|
|
if (y == NULL)
|
|
return -1;
|
|
/* Some special handling if yp is choice (or case)
|
|
* if so, the real parent (from an xml point of view) is the parents
|
|
* parent.
|
|
*/
|
|
yp = yang_parent_get(y);
|
|
while (yang_keyword_get(yp) == Y_CASE || yang_keyword_get(yp) == Y_CHOICE)
|
|
yp = yp->ys_parent;
|
|
|
|
/* XML nodes with yang specs that are children of modules are special -
|
|
* In clixon, they are seen as an "implicit" container where the XML can come from different
|
|
* modules. The order must therefore be global among yang top-symbols to be unique.
|
|
* Example: <x xmlns="foo"/><y xmlns="bar"/>
|
|
* The order of x and y cannot be compared within a single yang module since they belong to different
|
|
*/
|
|
if (yang_keyword_get(yp) == Y_MODULE || yang_keyword_get(yp) == Y_SUBMODULE){
|
|
ypp = yang_parent_get(yp); /* yang spec */
|
|
for (i=0; i<ypp->ys_len; i++){ /* iterate through other modules */
|
|
ym = ypp->ys_stmt[i];
|
|
if (yp == ym)
|
|
break;
|
|
tot += ym->ys_len;
|
|
}
|
|
}
|
|
if (order1(yp, y, &j) == 1)
|
|
return tot + j;
|
|
return -1;
|
|
}
|
|
|
|
/*! 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)
|
|
* Note that xt xml symbol may belong to submodule of ymod
|
|
*/
|
|
int
|
|
ys_module_by_xml(yang_stmt *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
|
|
* @see ys_real_module find the submodule's belongs-to module
|
|
* @note For an augmented node, the original module is returned
|
|
*/
|
|
yang_stmt *
|
|
ys_module(yang_stmt *ys)
|
|
{
|
|
yang_stmt *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){
|
|
if (ys->ys_mymodule){ /* shortcut due to augment */
|
|
ys = ys->ys_mymodule;
|
|
break;
|
|
}
|
|
yn = ys->ys_parent;
|
|
/* Some extra stuff to ensure ys is a stmt */
|
|
if (yn && yn->ys_keyword == Y_SPEC)
|
|
yn = NULL;
|
|
ys = (yang_stmt*)yn;
|
|
}
|
|
/* Here it is either NULL or is a typedef-kind yang-stmt */
|
|
return ys;
|
|
}
|
|
|
|
/*! Find real top module given a statement in a yang tree
|
|
* With "real" top module means that if sub-module is the top-node,
|
|
* the module that the sub-module belongs-to is found recursively
|
|
* @param[in] ys Any yang statement in a yang tree
|
|
* @retval ymod The top module or sub-module
|
|
* @see ys_module
|
|
* @note For an augmented node, the original module is returned
|
|
*/
|
|
yang_stmt *
|
|
ys_real_module(yang_stmt *ys)
|
|
{
|
|
yang_stmt *ym = NULL;
|
|
yang_stmt *yb;
|
|
char *name;
|
|
yang_stmt *yspec;
|
|
|
|
if ((ym = ys_module(ys)) != NULL){
|
|
yspec = ys_spec(ym);
|
|
while (yang_keyword_get(ym) == Y_SUBMODULE){
|
|
if ((yb = yang_find(ym, Y_BELONGS_TO, NULL)) == NULL){
|
|
clicon_err(OE_YANG, ENOENT, "No belongs-to statement of submodule %s", yang_argument_get(ym)); /* shouldnt happen */
|
|
goto done;
|
|
}
|
|
if ((name = yang_argument_get(yb)) == NULL){
|
|
clicon_err(OE_YANG, ENOENT, "Belongs-to statement of submodule %s has no argument", yang_argument_get(ym)); /* shouldnt happen */
|
|
goto done;
|
|
}
|
|
ym = yang_find_module_by_name(yspec, name);
|
|
}
|
|
}
|
|
return ym;
|
|
done:
|
|
return NULL;
|
|
}
|
|
|
|
/*! 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
|
|
* @see ys_yang_augment_node where shortcut is set
|
|
*/
|
|
yang_stmt *
|
|
ys_spec(yang_stmt *ys)
|
|
{
|
|
yang_stmt *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_stmt*)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_stmt *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;
|
|
}
|
|
myprefix = yang_find_myprefix(ys);
|
|
if (myprefix && strcmp(myprefix, prefix) == 0){
|
|
ymod = my_ymod;
|
|
goto done;
|
|
}
|
|
yimport = NULL;
|
|
while ((yimport = yn_each(my_ymod, yimport)) != NULL) {
|
|
if (yimport->ys_keyword != Y_IMPORT &&
|
|
yimport->ys_keyword != Y_INCLUDE)
|
|
continue;
|
|
if ((yprefix = yang_find(yimport, Y_PREFIX, NULL)) != NULL &&
|
|
strcmp(yprefix->ys_argument, prefix) == 0){
|
|
break;
|
|
}
|
|
}
|
|
if (yimport){
|
|
if ((ymod = yang_find(yspec, Y_MODULE, yimport->ys_argument)) == NULL &&
|
|
(ymod = yang_find(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_stmt *yspec,
|
|
char *namespace)
|
|
{
|
|
yang_stmt *ymod = NULL;
|
|
|
|
if (namespace == NULL)
|
|
goto done;
|
|
while ((ymod = yn_each(yspec, ymod)) != NULL) {
|
|
if (yang_find(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_stmt *yspec,
|
|
char *name)
|
|
{
|
|
yang_stmt *ymod = NULL;
|
|
|
|
while ((ymod = yn_each(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_stmt *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_stmt *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, 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(clicon_handle h,
|
|
yang_stmt *ys)
|
|
{
|
|
int retval = -1;
|
|
cg_var *cv = NULL;
|
|
yang_stmt *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, 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(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->ys_keyword == Y_LIST){
|
|
if ((ret = yang_key_match(yparent, ys->ys_argument)) < 0)
|
|
goto done;
|
|
}
|
|
ys->ys_cv = cv;
|
|
retval = 0;
|
|
done:
|
|
if (cv && retval < 0)
|
|
cv_free(cv);
|
|
return retval;
|
|
}
|
|
|
|
static int
|
|
ys_populate_list(clicon_handle h,
|
|
yang_stmt *ys)
|
|
{
|
|
yang_stmt *ykey;
|
|
|
|
if ((ykey = yang_find(ys, Y_KEY, NULL)) == NULL)
|
|
return 0;
|
|
if (ys->ys_cvec)
|
|
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,
|
|
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 (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;
|
|
}
|
|
|
|
/*! Common range length parsing of "x .. y | z..w " statements
|
|
*/
|
|
static int
|
|
range_parse(yang_stmt *ys,
|
|
enum cv_type cvtype,
|
|
uint8_t fraction_digits)
|
|
{
|
|
int retval = -1;
|
|
char **vec = NULL;
|
|
int nvec;
|
|
int i;
|
|
char *v;
|
|
char *v2;
|
|
|
|
if ((vec = clicon_strsep(ys->ys_argument, "|", &nvec)) == NULL)
|
|
goto done;
|
|
for (i=0; i<nvec; i++){
|
|
v = vec[i];
|
|
if ((v2 = strstr(v, "..")) != NULL){
|
|
*v2 = '\0';
|
|
v2 += 2;
|
|
v2 = clixon_trim(v2); /* trim blanks */
|
|
}
|
|
v = clixon_trim(v); /* trim blanks */
|
|
if (bound_add(ys, cvtype, "range_min", v, fraction_digits) < 0)
|
|
goto done;
|
|
if (v2)
|
|
if (bound_add(ys, cvtype, "range_max", v2, fraction_digits) < 0)
|
|
goto done;
|
|
}
|
|
retval = 0;
|
|
done:
|
|
if (vec)
|
|
free(vec);
|
|
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(clicon_handle h,
|
|
yang_stmt *ys)
|
|
{
|
|
int retval = -1;
|
|
yang_stmt *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;
|
|
|
|
yparent = ys->ys_parent; /* Find parent: type */
|
|
if (yparent->ys_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, 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 (!cv_isint(cvtype) && cvtype != CGV_DEC64){
|
|
clicon_err(OE_YANG, 0, "The range substatement only applies to int types, not to type: %s", origtype);
|
|
goto done;
|
|
}
|
|
if (range_parse(ys, cvtype, fraction_digits) < 0)
|
|
goto done;
|
|
retval = 0;
|
|
done:
|
|
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(clicon_handle h,
|
|
yang_stmt *ys)
|
|
{
|
|
int retval = -1;
|
|
yang_stmt *yparent; /* type */
|
|
enum cv_type cvtype = CGV_ERR;
|
|
|
|
yparent = ys->ys_parent; /* Find parent: type */
|
|
if (yparent->ys_keyword != Y_TYPE){
|
|
clicon_err(OE_YANG, 0, "parent should be type");
|
|
goto done;
|
|
}
|
|
cvtype = CGV_UINT64;
|
|
if (range_parse(ys, cvtype, 0) < 0)
|
|
goto done;
|
|
retval = 0;
|
|
done:
|
|
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(clicon_handle h,
|
|
yang_stmt *ys)
|
|
|
|
{
|
|
int retval = -1;
|
|
yang_stmt *ybase;
|
|
|
|
if (strcmp(ys->ys_argument, "decimal64") == 0){
|
|
if (yang_find(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(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(clicon_handle h,
|
|
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(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(h, ybaseid, idref) < 0)
|
|
goto done;
|
|
}
|
|
retval = 0;
|
|
done:
|
|
if (prefix)
|
|
free(prefix);
|
|
if (cb)
|
|
cbuf_free(cb);
|
|
return retval;
|
|
}
|
|
|
|
/*! Return 1 if feature is enabled, 0 if not using the populated yang tree
|
|
*
|
|
* @param[in] yspec yang specification
|
|
* @param[in] module Name of module
|
|
* @param[in] feature Name of feature
|
|
* @retval 0 Not found or not set
|
|
* @retval 1 Found and set
|
|
* XXX: should the in-param be h, ymod, or yspec?
|
|
*/
|
|
int
|
|
if_feature(yang_stmt *yspec,
|
|
char *module,
|
|
char *feature)
|
|
{
|
|
yang_stmt *ym; /* module */
|
|
yang_stmt *yf; /* feature */
|
|
cg_var *cv;
|
|
|
|
if ((ym = yang_find_module_by_name(yspec, module)) == NULL)
|
|
return 0;
|
|
if ((yf = yang_find(ym, Y_FEATURE, feature)) == NULL)
|
|
return 0;
|
|
if ((cv = yang_cv_get(yf)) == NULL)
|
|
return 0;
|
|
return cv_bool_get(cv);
|
|
}
|
|
|
|
/*! 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;
|
|
char *m;
|
|
char *f;
|
|
|
|
/* 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) {
|
|
m = NULL;
|
|
f = NULL;
|
|
if (strcmp(xml_name(xc), "CLICON_FEATURE") != 0)
|
|
continue;
|
|
/* CLICON_FEATURE is on the form <module>:<feature>.
|
|
* Split on colon to get module(m) and feature(f) respectively */
|
|
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 the unique statement with a cvec
|
|
*/
|
|
static int
|
|
ys_populate_unique(clicon_handle h,
|
|
yang_stmt *ys)
|
|
{
|
|
if (ys->ys_cvec)
|
|
cvec_free(ys->ys_cvec);
|
|
if ((ys->ys_cvec = yang_arg2cvec(ys, " ")) == NULL)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
/*! Populate unknown node with extension
|
|
*/
|
|
static int
|
|
ys_populate_unknown(clicon_handle h,
|
|
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(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(h, ys) < 0)
|
|
goto done;
|
|
break;
|
|
case Y_LIST:
|
|
if (ys_populate_list(h, ys) < 0)
|
|
goto done;
|
|
break;
|
|
case Y_RANGE:
|
|
if (ys_populate_range(h, ys) < 0)
|
|
goto done;
|
|
break;
|
|
case Y_LENGTH:
|
|
if (ys_populate_length(h, ys) < 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(h, ys) < 0)
|
|
goto done;
|
|
break;
|
|
case Y_IDENTITY:
|
|
if (ys_populate_identity(h, ys, NULL) < 0)
|
|
goto done;
|
|
break;
|
|
case Y_UNIQUE:
|
|
if (ys_populate_unique(h, ys) < 0)
|
|
goto done;
|
|
break;
|
|
case Y_UNKNOWN:
|
|
if (ys_populate_unknown(h, 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_stmt *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(ymodule, Y_GROUPING, name);
|
|
}
|
|
else
|
|
while (1){
|
|
/* Check upwards in hierarchy for matching groupings */
|
|
if ((yn = ys->ys_parent) == NULL || yn->ys_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.
|
|
* @param[in] ys The augment statement
|
|
* @param[in] yspec Yang specification
|
|
* @see RFC7950 Sec 7.17
|
|
* 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.
|
|
* All data nodes defined in the "augment" statement are defined as XML
|
|
* elements in the XML namespace of the module where the "augment" is
|
|
* specified.
|
|
*/
|
|
static int
|
|
yang_augment_node(yang_stmt *ys,
|
|
yang_stmt *ysp)
|
|
{
|
|
int retval = -1;
|
|
char *schema_nodeid;
|
|
yang_stmt *ytarget = NULL;
|
|
yang_stmt *yc;
|
|
int i;
|
|
yang_stmt *ymod;
|
|
|
|
if ((ymod = ys_module(ys)) == NULL){
|
|
clicon_err(OE_YANG, 0, "My yang module not found");
|
|
goto done;
|
|
}
|
|
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, &ytarget) < 0)
|
|
goto done;
|
|
if (ytarget == NULL)
|
|
goto ok;
|
|
/* Extend ytarget with ys' children
|
|
* First enlarge ytarget vector
|
|
*/
|
|
for (i=0; i<ys->ys_len; i++){
|
|
if ((yc = ys_dup(ys->ys_stmt[i])) == NULL)
|
|
goto done;
|
|
yc->ys_mymodule = ymod;
|
|
if (yn_insert(ytarget, 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_stmt *ysp,
|
|
int modnr)
|
|
{
|
|
int retval = -1;
|
|
yang_stmt *ym;
|
|
yang_stmt *ys;
|
|
int i;
|
|
int j;
|
|
|
|
i = modnr;
|
|
while (i<ysp->ys_len){ /* Loop through modules and sub-modules */
|
|
ym = ysp->ys_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_stmt *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->ys_len){
|
|
ys = yn->ys_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(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->ys_len - i - 1)*sizeof(struct yang_stmt *);
|
|
yn->ys_len += glen - 1;
|
|
if (glen && (yn->ys_stmt = realloc(yn->ys_stmt, (yn->ys_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->ys_stmt[i+glen],
|
|
&yn->ys_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->ys_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->ys_len; i++){
|
|
ys = yn->ys_stmt[i];
|
|
if (yang_expand(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_stmt *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, yspec) == NULL)
|
|
goto done;
|
|
if (strlen(str)){ /* Not empty */
|
|
if (yang_scan_init(&yy) < 0)
|
|
goto done;
|
|
if (yang_parse_init(&yy) < 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
|
|
* @note this function simply parse a yang spec, no dependencies or checks
|
|
*/
|
|
yang_stmt *
|
|
yang_parse_file(int fd,
|
|
const char *name,
|
|
yang_stmt *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 have 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
|
|
*/
|
|
yang_stmt *
|
|
yang_parse_filename(const char *filename,
|
|
yang_stmt *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_stmt *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_stmt *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(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(yi, Y_REVISION_DATE, NULL)) != NULL)
|
|
subrevision = yrev->ys_argument;
|
|
else
|
|
subrevision = NULL;
|
|
/* if already loaded, ignore, else parse the file */
|
|
if (yang_find(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_stmt *yspec;
|
|
yang_stmt *yres;
|
|
yang_stmt *yp;
|
|
|
|
yp = ys->ys_parent;
|
|
switch (ys->ys_keyword){
|
|
case Y_AUGMENT:
|
|
if (yp->ys_keyword == Y_MODULE || /* Not top-level */
|
|
yp->ys_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(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;
|
|
}
|
|
|
|
/*! 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_stmt *yspec,
|
|
int modnr)
|
|
{
|
|
int retval = -1;
|
|
int i;
|
|
|
|
/* 1: Parse from text to yang parse-tree.
|
|
* Iterate through modules and detect module/submodules to parse
|
|
* - note the list may grow on each iteration */
|
|
for (i=modnr; i<yspec->ys_len; i++)
|
|
if (yang_parse_recurse(h, yspec->ys_stmt[i], yspec) < 0)
|
|
goto done;
|
|
|
|
/* 2. Check cardinality maybe this should be done after grouping/augment */
|
|
for (i=modnr; i<yspec->ys_len; i++)
|
|
if (yang_cardinality(h, yspec->ys_stmt[i], yspec->ys_stmt[i]->ys_argument) < 0)
|
|
goto done;
|
|
|
|
/* 3: Check features: check if enabled and remove disabled features */
|
|
for (i=modnr; i<yspec->ys_len; i++) /* XXX */
|
|
if (yang_features(h, yspec->ys_stmt[i]) < 0)
|
|
goto done;
|
|
|
|
/* 4: Go through parse tree and populate it with cv types */
|
|
for (i=modnr; i<yspec->ys_len; i++)
|
|
if (yang_apply(yspec->ys_stmt[i], -1, ys_populate, (void*)h) < 0)
|
|
goto done;
|
|
|
|
/* 5: 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->ys_len; i++)
|
|
if (yang_apply(yspec->ys_stmt[i], Y_TYPE, ys_resolve_type, h) < 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.
|
|
*/
|
|
|
|
/* 6: Macro expansion of all grouping/uses pairs. Expansion needs marking */
|
|
for (i=modnr; i<yspec->ys_len; i++){
|
|
if (yang_expand(yspec->ys_stmt[i]) < 0)
|
|
goto done;
|
|
yang_apply(yspec->ys_stmt[i], -1, ys_flag_reset, (void*)YANG_FLAG_MARK);
|
|
}
|
|
|
|
/* 7: Top-level augmentation of all modules XXX: only new modules? */
|
|
if (yang_augment_spec(yspec, modnr) < 0)
|
|
goto done;
|
|
|
|
/* 8: sanity check of schemanode references, need more here */
|
|
for (i=modnr; i<yspec->ys_len; i++)
|
|
if (yang_apply(yspec->ys_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_stmt *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->ys_len;
|
|
/* Do not load module if it already exists */
|
|
if (yang_find(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,
|
|
char *filename,
|
|
yang_stmt *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->ys_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(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
|
|
* Load all yang files in a directory as primary objects.
|
|
* Some details if several same yang module x exists:
|
|
* 1) If x is already loaded (eg via direct file loading) skip it
|
|
* 2) Prefer x.yang over x@rev.yang (no revision)
|
|
* 3) If only x@rev.yang's found, prefer newest (newest revision)
|
|
* There is also an extra failsafe which may not be necessary, which removes
|
|
* the oldest module if 1-3 for some reason fails.
|
|
*/
|
|
int
|
|
yang_spec_load_dir(clicon_handle h,
|
|
char *dir,
|
|
yang_stmt *yspec)
|
|
{
|
|
int retval = -1;
|
|
int ndp;
|
|
struct dirent *dp = NULL;
|
|
int i;
|
|
int j;
|
|
char filename[MAXPATHLEN];
|
|
char *base = NULL; /* filename without dir */
|
|
int modnr;
|
|
yang_stmt *ym; /* yang module */
|
|
yang_stmt *ym0; /* (existing) yang module */
|
|
yang_stmt *yrev; /* yang revision */
|
|
uint32_t revf; /* revision in filename */
|
|
uint32_t revm; /* revision in parsed new module (same as revf) */
|
|
uint32_t rev0; /* revision in existing module */
|
|
char *s;
|
|
char *oldbase = NULL;
|
|
int taken = 0;
|
|
|
|
/* Get yang files names from yang module directory. Note that these
|
|
* are sorted alphatetically:
|
|
* a.yang,
|
|
* a@2000-01-01.yang,
|
|
* a@2111-11-11.yang
|
|
*/
|
|
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->ys_len;
|
|
/* Load all yang files in dir */
|
|
for (i = 0; i < ndp; i++) {
|
|
/* base = module name [+ @rev ] + .yang */
|
|
if (oldbase)
|
|
free(oldbase);
|
|
oldbase = base; base = NULL;
|
|
if ((base = strdup(dp[i].d_name)) == NULL){
|
|
clicon_err(OE_UNIX, errno, "strdup");
|
|
goto done;
|
|
}
|
|
clicon_debug(1, "%s %s", __FUNCTION__, base);
|
|
*rindex(base, '.') = '\0'; /* strip postfix .yang */
|
|
/* base = module name [+ @rev]
|
|
* if it hasnt @rev then prefer it (dont check other files w @rev)
|
|
*/
|
|
revf = 0;
|
|
if ((s = index(base, '@')) != NULL){
|
|
*s++ = '\0';
|
|
if (ys_parse_date_arg(s, &revf) < 0)
|
|
goto done;
|
|
}
|
|
if (oldbase && strcmp(base, oldbase)) /* new yang file basename */
|
|
taken = 0;
|
|
if (revf == 0){ /* No revision: a.yang - take that */
|
|
taken = 1;
|
|
}
|
|
else{ /* a@xxx.yang */
|
|
if (taken)
|
|
continue; /* skip if already taken */
|
|
/* is there anyone else later? */
|
|
if (i+1<ndp && strncmp(base, dp[i+1].d_name, strlen(base)) == 0)
|
|
continue; /* same base: skip; */
|
|
taken = 1; /* last in line and not taken */
|
|
}
|
|
/* Here only a single file is reached(taken)
|
|
* Check if module already exists -> ym0/rev0 */
|
|
rev0 = 0;
|
|
if ((ym0 = yang_find(yspec, Y_MODULE, base)) != NULL ||
|
|
(ym0 = yang_find(yspec, Y_SUBMODULE, base)) != NULL){
|
|
yrev = yang_find(ym0, Y_REVISION, NULL);
|
|
rev0 = cv_uint32_get(yrev->ys_cv);
|
|
continue; /* skip if already added by specific file or module */
|
|
}
|
|
/* Create full filename */
|
|
snprintf(filename, MAXPATHLEN-1, "%s/%s", dir, dp[i].d_name);
|
|
if ((ym = yang_parse_filename(filename, yspec)) == NULL)
|
|
goto done;
|
|
revm = 0;
|
|
if ((yrev = yang_find(ym, Y_REVISION, NULL)) != NULL)
|
|
revm = cv_uint32_get(yrev->ys_cv);
|
|
/* Sanity check that file revision does not match internal rev stmt */
|
|
if (revf && revm && revm != revf){
|
|
clicon_err(OE_YANG, EINVAL, "Yang module file revision and in yang does not match: %s vs %u", filename, revm);
|
|
goto done;
|
|
}
|
|
/* If ym0 and ym exists, delete the yang with oldest revision
|
|
* This is a failsafe in case anything else fails
|
|
*/
|
|
if (revm && rev0){
|
|
int size;
|
|
if (revm > rev0) /* Loaded module is older or eq -> remove ym */
|
|
ym = ym0;
|
|
for (j=0; j<yspec->ys_len; j++)
|
|
if (yspec->ys_stmt[j] == ym)
|
|
break;
|
|
size = (yspec->ys_len - j - 1)*sizeof(struct yang_stmt *);
|
|
memmove(&yspec->ys_stmt[j],
|
|
&yspec->ys_stmt[j+1],
|
|
size);
|
|
ys_free(ym);
|
|
yspec->ys_len--;
|
|
yspec->ys_stmt[yspec->ys_len] = NULL;
|
|
}
|
|
}
|
|
if (yang_parse_post(h, yspec, modnr) < 0)
|
|
goto done;
|
|
retval = 0;
|
|
done:
|
|
if (dp)
|
|
free(dp);
|
|
if (base)
|
|
free(base);
|
|
if (oldbase)
|
|
free(oldbase);
|
|
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(ys, Y_TYPE, ys_fn, NULL);
|
|
* @endcode
|
|
* @note do not delete or move around any children during this function
|
|
*/
|
|
int
|
|
yang_apply(yang_stmt *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->ys_len; i++){
|
|
ys = yn->ys_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(ys, keyword, fn, arg)) < 0)
|
|
goto done;
|
|
if (ret > 0){
|
|
retval = ret;
|
|
goto done;
|
|
}
|
|
}
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Check if a node is a yang "data node"
|
|
* @param[in] ys Yang statement node
|
|
* @retval 0 Yang node is NOT a data node
|
|
* @retval !=0 Yang node IS a data noed
|
|
* @see RFC7950 Sec 3:
|
|
* o data node: A node in the schema tree that can be instantiated in a
|
|
* data tree. One of container, leaf, leaf-list, list, anydata, and
|
|
* anyxml.
|
|
*/
|
|
int
|
|
yang_datanode(yang_stmt *ys)
|
|
{
|
|
return (yang_keyword_get(ys) == Y_CONTAINER ||
|
|
yang_keyword_get(ys) == Y_LEAF ||
|
|
yang_keyword_get(ys) == Y_LIST ||
|
|
yang_keyword_get(ys) == Y_LEAF_LIST ||
|
|
yang_keyword_get(ys) == Y_ANYXML ||
|
|
yang_keyword_get(ys) == Y_ANYDATA);
|
|
}
|
|
|
|
/*! 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_stmt *yn,
|
|
char **vec,
|
|
int nvec,
|
|
enum rfc_6020 keyword,
|
|
yang_stmt **yres)
|
|
{
|
|
int retval = -1;
|
|
char *arg;
|
|
yang_stmt *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->ys_keyword), yn->ys_argument,
|
|
arg, yn->ys_len);
|
|
if (strcmp(arg, "..") == 0)
|
|
ynext = yn->ys_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->ys_len; i++){
|
|
ys = yn->ys_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 = 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_stmt *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(yspec, ymod)) != NULL) {
|
|
if ((yprefix = yang_find(ymod, Y_PREFIX, NULL)) != NULL &&
|
|
strcmp(yprefix->ys_argument, prefix) == 0){
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (schema_nodeid_vec(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_stmt *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 yang date-arg string and return a uint32 useful for arithmetics
|
|
* @param[in] datearg yang revision string as "YYYY-MM-DD"
|
|
* @param[out] dateint Integer version as YYYYMMDD
|
|
* @retval 0 OK
|
|
* @retval -1 Error, eg str is not on the format "YYYY-MM-DD"
|
|
*/
|
|
int
|
|
ys_parse_date_arg(char *datearg,
|
|
uint32_t *dateint)
|
|
{
|
|
int retval = -1;
|
|
int i;
|
|
uint32_t d = 0;
|
|
|
|
if (strlen(datearg) != 10 || datearg[4] != '-' || datearg[7] != '-'){
|
|
clicon_err(OE_YANG, EINVAL, "Revision date %s, but expected: YYYY-MM-DD", datearg);
|
|
goto done;
|
|
}
|
|
if ((i = cligen_tonum(4, datearg)) < 0){
|
|
clicon_err(OE_YANG, EINVAL, "Revision date %s, but expected: YYYY-MM-DD", datearg);
|
|
goto done;
|
|
}
|
|
d = i*10000; /* year */
|
|
if ((i = cligen_tonum(2, &datearg[5])) < 0){
|
|
clicon_err(OE_YANG, EINVAL, "Revision date %s, but expected: YYYY-MM-DD", datearg);
|
|
goto done;
|
|
}
|
|
d += i*100; /* month */
|
|
if ((i = cligen_tonum(2, &datearg[8])) < 0){
|
|
clicon_err(OE_YANG, EINVAL, "Revision date %s, but expected: YYYY-MM-DD", datearg);
|
|
goto done;
|
|
}
|
|
d += i; /* day */
|
|
*dateint = d;
|
|
retval = 0;
|
|
done:
|
|
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)
|
|
* revision: cv as uint32 date: Integer version as YYYYMMDD
|
|
* min-elements: cv as uint32
|
|
* max-elements: cv as uint32, '0' means unbounded
|
|
* @see ys_populate
|
|
*/
|
|
int
|
|
ys_parse_sub(yang_stmt *ys,
|
|
char *extra)
|
|
{
|
|
int retval = -1;
|
|
uint8_t fd;
|
|
uint32_t date = 0;
|
|
char *arg;
|
|
enum rfc_6020 keyword;
|
|
char *reason = NULL;
|
|
int ret;
|
|
uint32_t minmax;
|
|
|
|
arg = yang_argument_get(ys);
|
|
keyword = yang_keyword_get(ys);
|
|
switch (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_REVISION:
|
|
case Y_REVISION_DATE: /* YYYY-MM-DD encoded as uint32 YYYYMMDD */
|
|
if (ys_parse_date_arg(arg, &date) < 0)
|
|
goto done;
|
|
if ((ys->ys_cv = cv_new(CGV_UINT32)) == NULL){
|
|
clicon_err(OE_YANG, errno, "cv_new");
|
|
goto done;
|
|
}
|
|
cv_uint32_set(ys->ys_cv, date);
|
|
break;
|
|
case Y_STATUS: /* RFC7950 7.21.2: "current", "deprecated", or "obsolete". */
|
|
if (strcmp(arg, "current") &&
|
|
strcmp(arg, "deprecated") &&
|
|
strcmp(arg, "obsolete")){
|
|
clicon_err(OE_YANG, errno, "Invalid status: \"%s\", expected current, deprecated, or obsolete", arg);
|
|
goto done;
|
|
|
|
}
|
|
break;
|
|
case Y_MAX_ELEMENTS:
|
|
case Y_MIN_ELEMENTS:
|
|
if ((ys->ys_cv = cv_new(CGV_UINT32)) == NULL){
|
|
clicon_err(OE_YANG, errno, "cv_new");
|
|
goto done;
|
|
}
|
|
if (keyword == Y_MAX_ELEMENTS &&
|
|
strcmp(arg, "unbounded") == 0)
|
|
cv_uint32_set(ys->ys_cv, 0); /* 0 means unbounded for max */
|
|
else{
|
|
if ((ret = parse_uint32(arg, &minmax, &reason)) < 0){
|
|
clicon_err(OE_YANG, errno, "parse_uint32");
|
|
goto done;
|
|
}
|
|
if (ret == 0){
|
|
clicon_err(OE_YANG, EINVAL, "element-min/max parse error: %s", reason);
|
|
if (reason)
|
|
free(reason);
|
|
goto done;
|
|
}
|
|
cv_uint32_set(ys->ys_cv, minmax);
|
|
}
|
|
break;
|
|
case Y_MODIFIER:
|
|
if (strcmp(yang_argument_get(ys), "invert-match")){
|
|
clicon_err(OE_YANG, EINVAL, "modifier %s, expected invert-match", yang_argument_get(ys));
|
|
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;
|
|
cg_var *cv;
|
|
|
|
/* 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(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(ys, Y_MIN_ELEMENTS, NULL)) != NULL){
|
|
cv = yang_cv_get(ym);
|
|
return cv_uint32_get(cv) > 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(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(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_stmt *yn,
|
|
char *name)
|
|
{
|
|
int retval = -1;
|
|
yang_stmt *ys = NULL;
|
|
int i;
|
|
cvec *cvv = NULL;
|
|
cg_var *cv;
|
|
|
|
for (i=0; i<yn->ys_len; i++){
|
|
ys = yn->ys_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;
|
|
}
|