clixon/lib/src/clixon_yang.c
2019-06-08 16:32:56 +02:00

3632 lines
102 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;
}
/*! Remove child i from parent yp (dont free)
* @param[in] yp Parent node
* @param[in] i Order of child to remove
* @retval NULL No such node, nothing done
* @retval yc returned orphaned yang node
* @see ys_free Deallocate yang node
* @note Do not call this in a loop of yang children (unless you know what you are doing)
*/
static yang_stmt *
ys_prune(yang_stmt *yp,
int i)
{
size_t size;
yang_stmt *yc = NULL;
if (i >= yp->ys_len)
goto done;
size = (yp->ys_len - i - 1)*sizeof(struct yang_stmt *);
yc = yp->ys_stmt[i];
memmove(&yp->ys_stmt[i],
&yp->ys_stmt[i+1],
size);
yc = yp->ys_stmt[yp->ys_len--] = NULL;;
done:
return yc;
}
/*! Free a yang statement tree recursively
* @param[in] ys Yang node to remove and all its children recursively
* @note does not remove yang node from tree
* @see ys_prune Remove from parent
*/
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.
*
* @param[in] ys Yang statement
* @param[in] h Clicon handle
* Preferably run this command using yang_apply
* Done in 2nd pass after complete parsing to be sure to have a complete
* parse-tree
* After this pass, cv:s are set for LEAFs and LEAF-LISTs
* @see ys_parse_sub for first pass and what can be assumed
* @see ys_populate2 for after grouping expand and augment
* (there may be more functions (all?) that may be moved to ys_populate2)
*/
int
ys_populate(yang_stmt *ys,
void *arg)
{
int retval = -1;
clicon_handle h = (clicon_handle)arg;
switch(ys->ys_keyword){
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_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;
}
/*! Run after grouping expand and augment
* @see ys_populate run before grouping expand and augment
*/
static int
ys_populate2(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_MANDATORY: /* call yang_mandatory() to check if set */
case Y_CONFIG:
if (ys_parse(ys, CGV_BOOL) == NULL)
goto done;
break;
default:
break;
}
retval = 0;
done:
return retval;
}
/*! Resolve a grouping name from a point in the yang tree
* @param[in] ys Yang statement of "uses" statement doing the lookup
* @param[in] prefix Prefix of grouping to look for
* @param[in] name Name of grouping to look for
* @param[out] ygrouping0 A found grouping yang structure as result
* @retval 0 OK, ygrouping may be NULL
* @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;
}
/*! Given a refine node, perform the refinement action on the target refine node
* The RFC is somewhat complicate in the rules for refine.
* Most nodes will be replaced, but some are added
* @param[in] yr Refine node
* @param[in] yt Refine target node (will be modified)
* @see RFC7950 Sec 7.13.2
* There may be some missed cornercases
*/
static int
ys_do_refine(yang_stmt *yr,
yang_stmt *yt)
{
int retval = -1;
yang_stmt *yrc; /* refine child */
yang_stmt *yrc1;
yang_stmt *ytc; /* target child */
enum rfc_6020 keyw;
int i;
/* Loop through refine node children. First if remove do that first
* In some cases remove a set of nodes.
*/
yrc = NULL;
while ((yrc = yn_each(yr, yrc)) != NULL) {
keyw = yang_keyword_get(yrc);
switch (keyw){
case Y_DEFAULT: /* remove old, add new */
case Y_DESCRIPTION:
case Y_REFERENCE:
case Y_CONFIG:
case Y_MANDATORY:
case Y_PRESENCE:
case Y_MIN_ELEMENTS:
case Y_MAX_ELEMENTS:
case Y_EXTENSION:
/* Remove old matching, dont increment due to prune in loop */
for (i=0; i<yt->ys_len; ){
ytc = yt->ys_stmt[i];
if (keyw != yang_keyword_get(ytc)){
i++;
continue;
}
ys_prune(yt, i);
ys_free(ytc);
}
/* fall through and add if not found */
case Y_MUST: /* keep old, add new */
case Y_IF_FEATURE:
break;
default:
break;
}
}
/* Second, add the node(s) */
yrc = NULL;
while ((yrc = yn_each(yr, yrc)) != NULL) {
keyw = yang_keyword_get(yrc);
/* Make copy */
if ((yrc1 = ys_dup(yrc)) == NULL)
goto done;
if (yn_insert(yt, yrc1) < 0)
goto done;
}
retval = 0;
done:
return retval;
}
/*! Macro expansion of grouping/uses done in step 2 of yang parsing
* RFC7950:
* 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.
* The identifiers defined in the grouping are not bound to a namespace
* until the contents of the grouping are added to the schema tree via a
* "uses" statement that does not appear inside a "grouping" statement,
* at which point they are bound to the namespace of the current module.
*/
static int
yang_expand_grouping(yang_stmt *yn)
{
int retval = -1;
yang_stmt *ys = NULL;
yang_stmt *ygrouping; /* grouping original */
yang_stmt *ygrouping2; /* grouping copy */
yang_stmt *yg; /* grouping child */
yang_stmt *yr; /* refinement */
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 (prefix){
free(prefix);
prefix = NULL;
}
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;
}
/* 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_grouping(ygrouping) < 0)
goto done;
ygrouping->ys_flags |= YANG_FLAG_MARK; /* Mark as expanded */
}
/* Make a copy of the grouping, then make refinements to this copy
*/
if ((ygrouping2 = ys_dup(ygrouping)) == NULL)
goto done;
/* Replace ys with ygrouping,...
* First enlarge parent vector
*/
glen = ygrouping2->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");
goto done;
}
/* 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);
}
/* Iterate through refinements and modify grouping copy
* See RFC 7950 7.13.2 yrt is the refine target node
*/
yr = NULL;
while ((yr = yn_each(ys, yr)) != NULL) {
yang_stmt *yrt; /* refine target node */
if (yang_keyword_get(yr) != Y_REFINE)
continue;
/* Find a node */
if (yang_desc_schema_nodeid(ygrouping2,
yang_argument_get(yr),
-1,
&yrt) < 0)
goto done;
/* Not found, try next */
if (yrt == NULL)
continue;
/* Do the actual refinement */
if (ys_do_refine(yr, yrt) < 0)
goto done;
/* RFC: The argument is a string that identifies a node in the
* grouping. I interpret that as only one node --> break */
break;
}
/* Then copy and insert each child element */
for (j=0; j<glen; j++){
yg = ygrouping2->ys_stmt[j]; /* Child of refined copy */
yn->ys_stmt[i+j] = yg;
yg->ys_parent = yn;
}
/* Remove 'uses' node */
ys_free(ys);
/* Remove the grouping copy */
ygrouping2->ys_len = 0;
ys_free(ygrouping2);
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_grouping(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 %s %s",
yang_key2str(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_grouping(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. (Augment also in uses) */
if (yang_augment_spec(yspec, modnr) < 0)
goto done;
/* 4: Go through parse tree and do 2nd step populate (eg default) */
for (i=modnr; i<yspec->ys_len; i++)
if (yang_apply(yspec->ys_stmt[i], -1, ys_populate2, (void*)h) < 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){
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;
ys_prune(yspec, j);
ys_free(ym);
}
}
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_abs_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
*
* The function looks at the LIST argument string (not actual children)
* @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;
}