clixon/lib/src/clixon_yang.c
Olof hagsand 0c7f2043f3 * Pagination according to new draft
* count/skip -> limit/offset
* ietf-yang-metadata RFC 7952 support, placeholder parsing and extension
2021-09-21 11:22:41 +02:00

3672 lines
100 KiB
C

/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand
Copyright (C) 2020-2021 Olof Hagsand and Rubicon Communications, LLC(Netgate)
This file is part of CLIXON.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Alternatively, the contents of this file may be used under the terms of
the GNU General Public License Version 3 or later (the "GPL"),
in which case the provisions of the GPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of the GPL, and not to allow others to
use your version of this file under the terms of Apache License version 2,
indicate your decision by deleting the provisions above and replace them with
the notice and other provisions required by the GPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the Apache License version 2 or the GPL.
***** END LICENSE BLOCK *****
* Yang functions
* @see https://tools.ietf.org/html/rfc6020 YANG 1.0
* @see https://tools.ietf.org/html/rfc7950 YANG 1.1
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>
#include <ctype.h>
#include <unistd.h>
#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_xml_nsctx.h"
#include "clixon_yang_module.h"
#include "clixon_plugin.h"
#include "clixon_data.h"
#include "clixon_options.h"
#include "clixon_yang_parse.h"
#include "clixon_yang_parse_lib.h"
#include "clixon_yang_cardinality.h"
#include "clixon_yang_type.h"
#include "clixon_yang_internal.h" /* internal included by this file only, not API*/
#ifdef XML_EXPLICIT_INDEX
static int yang_search_index_extension(clicon_handle h, yang_stmt *yext, yang_stmt *ys);
#endif
/*
* 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[] = {
{"action", Y_ACTION},
{"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}
};
/* Forward static */
static int yang_type_cache_free(yang_type_cache *ycache);
static int yang_type_cache_cp(yang_stmt *ynew, yang_stmt *yold);
/* Access functions
*/
/*! Get Number of children yang statements
* @param[in] ys Yang statement node
*/
int
yang_len_get(yang_stmt *ys)
{
return ys->ys_len;
}
/*! Get Specific Yang statement child
* @param[in] ys Yang statement node
* @param[in] i Return this child
*/
yang_stmt *
yang_child_i(yang_stmt *ys,
int i)
{
return ys->ys_stmt[i];
}
/*! 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;
}
/*
* Note on cvec on XML nodes:
* 1. It is always created in xml_new. It could be lazily created on use to save a little memory
* 2. Only some yang statements use the cvec, as follows:
* 2a. ranges and lengths: [min, max]
* 2b. list: keys
* 2c. identity types: derived instances: identityrefs, save <module>:<idref>
* 2d. type: leafref types: derived instances.
*/
/*! Set yang argument, not not copied
* @param[in] ys Yang statement node
* @param[in] arg Argument
* Typically only done at parsing / initiation
*/
int
yang_argument_set(yang_stmt *ys,
char *arg)
{
ys->ys_argument = arg; /* not strdup/copied */
return 0;
}
/*! Get yang statement CLIgen variable
* @param[in] ys Yang statement node
*/
cg_var*
yang_cv_get(yang_stmt *ys)
{
return ys->ys_cv;
}
/*! Set yang statement CLIgen variable
* @param[in] ys Yang statement node
* @param[in] cv cligen variable
* @note: Frees on replace, not if cv is NULL. This is for some ys_cp/ys_dup cases, which means
* you need to free it explicitly to set it to NULL proper.
*/
int
yang_cv_set(yang_stmt *ys,
cg_var *cv)
{
if (cv != NULL && ys->ys_cv != NULL)
cv_free(ys->ys_cv);
ys->ys_cv = cv;
return 0;
}
/*! 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;
}
/*! Get yang stmt flags, used for internal algorithms
* @param[in] ys Yang statement
* @param[in] flag Flags value(s) to get, see YANG_FLAG_*
* @retval value Flags value masked by "flag" parameter, see YANG_FLAG_*
*/
uint16_t
yang_flag_get(yang_stmt *ys,
uint16_t flag)
{
return ys->ys_flags&flag;
}
/*! Set yang stmt flags, used for internal algorithms
* @param[in] ys Yang statement
* @param[in] flag Flag value(s) to set, see YANG_FLAG_*
*/
int
yang_flag_set(yang_stmt *ys,
uint16_t flag)
{
ys->ys_flags |= flag;
return 0;
}
/*! Reset yang stmt flags, used for internal algorithms
* @param[in] ys Yang statement
* @param[in] flag Flag value(s) to reset, see YANG_FLAG_*
*/
int
yang_flag_reset(yang_stmt *ys,
uint16_t flag)
{
ys->ys_flags &= ~flag;
return 0;
}
/*! Get yang xpath for "when"-associated augment
*
* Ie, for yang structures like: augment <path> { when <xpath>; ... }
* Will insert new yang nodes at <path> with this special "when" struct (not yang node)
* @param[in] ys Yang statement
* @retval xpath xpath should evaluate to true at validation
* @retval NULL Not set
* Note xpath context is PARENT which is different from when actual when child which is
* child itself
*/
char*
yang_when_xpath_get(yang_stmt *ys)
{
return ys->ys_when_xpath;
}
/*! Set yang xpath and namespace context for "when"-associated augment
*
* Ie, for yang structures like: augment <path> { when <xpath>; ... }
* Will insert new yang nodes at <path> with this special "when" struct (not yang node)
* @param[in] ys Yang statement
* @param[in] xpath If set, this xpath should evaluate to true at validation, copied
* @retval 0 OK
* @retval -1 Error
*/
int
yang_when_xpath_set(yang_stmt *ys,
char *xpath)
{
int retval = -1;
if (xpath == NULL){
clicon_err(OE_YANG, EINVAL, "xpath is NULL");
goto done;
}
if ((ys->ys_when_xpath = strdup(xpath)) == NULL){
clicon_err(OE_YANG, errno, "strdup");
goto done;
}
retval = 0;
done:
return retval;
}
/*! Get yang namespace context for "when"-associated augment
*
* Ie, for yang structures like: augment <path> { when <xpath>; ... }
* Will insert new yang nodes at <path> with this special "when" struct (not yang node)
* @param[in] ys Yang statement
* @retval nsc Namespace context
* @note retval is direct pointer, may need to be copied
*/
cvec *
yang_when_nsc_get(yang_stmt *ys)
{
return ys->ys_when_nsc;
}
/*! Set yang namespace context for "when"-associated augment
*
* Ie, for yang structures like: augment <path> { when <xpath>; ... }
* Will insert new yang nodes at <path> with this special "when" struct (not yang node)
* @param[in] ys Yang statement
* @param[in] nsc Namespace context for when xpath
* @retval 0 OK
* @retval -1 Error
*/
int
yang_when_nsc_set(yang_stmt *ys,
cvec *nsc)
{
int retval = -1;
if (nsc && (ys->ys_when_nsc = cvec_dup(nsc)) == NULL){
clicon_err(OE_YANG, errno, "cvec_dup");
goto done;
}
retval = 0;
done:
return retval;
}
/*! Get yang filename for error/debug purpose
*
* @param[in] ys Yang statement
* @retval filename
* @note there maye not always be a "filename" in case the yang is read from memory
*/
const char *
yang_filename_get(yang_stmt *ys)
{
return ys->ys_filename;
}
/*! Set yang filename for error/debug purpose
*
* @param[in] ys Yang statement
* @param[in] filename
* @retval 0 OK
* @retval -1 Error
* @note there maye not always be a "filename" in case the yang is read from memory
*/
int
yang_filename_set(yang_stmt *ys,
const char *filename)
{
if ((ys->ys_filename = strdup(filename)) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
return -1;
}
return 0;
}
/*! Get line number of yang filename for error/debug purpose
*
* @param[in] ys Yang statement
* @retval linenum
*/
int
yang_linenum_get(yang_stmt *ys)
{
return ys->ys_linenum;
}
/*! Set line number of yang filename for error/debug purpose
*
* @param[in] ys Yang statement
* @param[in] linenum
*/
int
yang_linenum_set(yang_stmt *ys,
int linenum)
{
ys->ys_linenum = linenum;
return 0;
}
/* End access functions */
/*! Create new yang specification
* @retval yspec Free with ys_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;
cvec *cvv;
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 ((cvv = cvec_new(0)) == NULL){
clicon_err(OE_YANG, errno, "cvec_new");
return NULL;
}
yang_cvec_set(ys, cvv);
return ys;
}
/*! Free a single yang statement, dont remove children
*
* @param[in] ys Yang node to remove
* @param[in] self Free own node including child vector
* @retval 0 OK
* @retval -1 Error
* @see ys_free
*/
int
ys_free1(yang_stmt *ys,
int self)
{
cg_var *cv;
if (ys->ys_argument){
free(ys->ys_argument);
ys->ys_argument = NULL;
}
if ((cv = yang_cv_get(ys)) != NULL){
yang_cv_set(ys, NULL); /* only frees on replace */
cv_free(cv);
}
if (ys->ys_cvec){
cvec_free(ys->ys_cvec);
ys->ys_cvec = NULL;
}
if (ys->ys_typecache){
yang_type_cache_free(ys->ys_typecache);
ys->ys_typecache = NULL;
}
if (ys->ys_when_xpath)
free(ys->ys_when_xpath);
if (ys->ys_when_nsc)
cvec_free(ys->ys_when_nsc);
if (ys->ys_stmt)
free(ys->ys_stmt);
if (ys->ys_filename)
free(ys->ys_filename);
if (self)
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)
*/
yang_stmt *
ys_prune(yang_stmt *yp,
int i)
{
size_t size;
yang_stmt *yc = NULL;
if (i >= yp->ys_len)
goto done;
yc = yp->ys_stmt[i];
if (i < yp->ys_len - 1){
size = (yp->ys_len - i - 1)*sizeof(struct yang_stmt *);
memmove(&yp->ys_stmt[i],
&yp->ys_stmt[i+1],
size);
}
yp->ys_len--;
yp->ys_stmt[yp->ys_len] = NULL;
done:
return yc;
}
/*! Remove yang node from parent (dont free
* @param[in] ys Yang node to remove
* @retval 0 OK
* @retval -1 Error
* @see ys_prune if parent and position is know
* @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 int
ys_prune_self(yang_stmt *ys)
{
int retval = -1;
yang_stmt *yp;
yang_stmt *yc;
int i;
if ((yp = yang_parent_get(ys)) != NULL){
yc = NULL;
i = 0;
/* Find order of ys in child-list */
while ((yc = yn_each(yp, yc)) != NULL) {
if (ys == yc)
break;
i++;
}
if (yc != NULL){
assert(yc == ys);
ys_prune(yp, i);
}
}
retval = 0;
// done:
return retval;
}
/*! 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);
}
ys_free1(ys, 1);
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)
* @retval 0 OK
* @retval -1 Error
* @code
* yang_stmt *new = ys_new(Y_LEAF);
* if (ys_cp(new, old) < 0)
* err;
* @endcode
* @see ys_replace
*/
int
ys_cp(yang_stmt *ynew,
yang_stmt *yold)
{
int retval = -1;
int i;
yang_stmt *ycn; /* new child */
yang_stmt *yco; /* old child */
cg_var *cvn;
cg_var *cvo;
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;
}
yang_cv_set(ynew, NULL);
if ((cvo = yang_cv_get(yold)) != NULL){
if ((cvn = cv_dup(cvo)) == NULL){
clicon_err(OE_YANG, errno, "cv_dup");
goto done;
}
yang_cv_set(ynew, cvn);
}
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, yold) < 0)
goto done;
}
if (yold->ys_when_xpath)
if ((ynew->ys_when_xpath = strdup(yold->ys_when_xpath)) == NULL){
clicon_err(OE_YANG, errno, "strdup");
goto done;
}
if (yold->ys_when_nsc){
if ((ynew->ys_when_nsc = cvec_dup(yold->ys_when_nsc)) == NULL){
clicon_err(OE_YANG, errno, "cvec_dup");
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 nw New created yang statement
* @retval 0 OK
* @retval -1 Error
* 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 *nw;
if ((nw = ys_new(old->ys_keyword)) == NULL)
return NULL;
if (nw->ys_cvec){
cvec_free(nw->ys_cvec);
nw->ys_cvec = NULL;
}
if (ys_cp(nw, old) < 0){
ys_free(nw);
return NULL;
}
return nw;
}
/*! Replace yold with ynew (insert ynew at the exact place of yold). Keep yold pointer as-is.
*
* @param[in] yorig Existing yang statement
* @param[in] yfrom New empty (but created) yang statement
* @retval 0 OK
* @retval -1 Error
* @code
* if (ys_replace(new, old) < 0)
* err;
* @endcode
* @see ys_cp
* @note yfrom is left in its original state
*/
int
ys_replace(yang_stmt *yorig,
yang_stmt *yfrom)
{
int retval = -1;
yang_stmt *yp; /* parent */
yang_stmt *yc; /* child */
yp = yang_parent_get(yorig);
/* Remove old yangs all children */
yc = NULL;
while ((yc = yn_each(yorig, yc)) != NULL)
ys_free(yc);
if (yorig->ys_stmt){
free(yorig->ys_stmt);
yorig->ys_stmt = NULL;
yorig->ys_len = 0;
}
ys_free1(yorig, 0); /* Remove all in yold except the actual object */
if (ys_cp(yorig, yfrom) < 0)
goto done;
yorig->ys_parent = yp;
retval = 0;
done:
return retval;
}
/*! Append 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
* @retval 0 OK
* @retval -1 Error
* Also add parent to child as up-pointer
* @see ys_prune
*/
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;
}
/*! Variant of yn_insert where parent is not set
*/
int
yn_insert1(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;
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
* @note also does not work in recursive calls (to same node)
*/
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);
if ((ym = yang_find_module_by_name(yspec, name)) != NULL &&
(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, list, leaf-list)
*
* @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;
ys = NULL;
while ((ys = yn_each(yn, ys)) != NULL){
if (yang_keyword_get(ys) == Y_CHOICE){ /* Look for its children */
yc = NULL;
while ((yc = yn_each(ys, yc)) != NULL){
if (yang_keyword_get(yc) == 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);
ys = NULL;
while ((ys = yn_each(yn, ys)) != NULL){
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 (yang_keyword_get(ys) == Y_CHOICE){
/* First check choice itself */
if (ys->ys_argument && strcmp(argument, ys->ys_argument) == 0){
ysmatch = ys;
goto match;
}
/* Then look for its children (case) */
for (j=0; j<ys->ys_len; j++){
yc = ys->ys_stmt[j];
if (yang_keyword_get(yc) == 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 (yang_keyword_get(ys) == Y_INPUT || yang_keyword_get(ys) == Y_OUTPUT)
ysmatch = ys;
else 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 No prefix found. This is an error
* @retval prefix OK: 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 = NULL; /* My module */
yang_stmt *yprefix;
char *prefix = NULL;
/* Not good enough with submodule, must be actual module */
if (ys_real_module(ys, &ymod) < 0)
goto done;
if (ymod == NULL){
clicon_err(OE_YANG, ENOENT, "Internal error: no module");
goto done;
}
if ((yprefix = yang_find(ymod, Y_PREFIX, NULL)) == NULL){
clicon_err(OE_YANG, ENOENT, "No prefix found for module %s", yang_argument_get(ymod));
goto done;
}
prefix = yang_argument_get(yprefix);
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 Error: No namespace found. This is an error
* @retval ns 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 = NULL; /* My module */
yang_stmt *ynamespace;
char *ns = NULL;
if (ys_real_module(ys, &ymod) < 0)
goto done;
if ((ynamespace = yang_find(ymod, Y_NAMESPACE, NULL)) == NULL){
clicon_err(OE_YANG, ENOENT, "No namespace found for module %s", yang_argument_get(ymod));
goto done;
}
ns = yang_argument_get(ynamespace);
done:
return ns;
}
/*! Given a yang statement and namespace, find local prefix valid in module
* This is useful if you want to make a "reverse" lookup, you know the
* (global) namespace of a module, but you do not know the local prefix
* used to access it in XML.
* @param[in] ys Yang statement in module tree (or module itself)
* @param[in] ns Namspace URI as char* pointer into yang tree
* @param[out] prefix Local prefix to access module with (direct pointer)
* @retval 0 not found
* @retval -1 found
* @note prefix NULL is not returned, if own module, then return its prefix
* @code
* char *prefix = NULL;
* if (yang_find_prefix_by_namespace(ys, "urn:example:clixon", &prefix) < 0)
* err;
* @endcode
*/
int
yang_find_prefix_by_namespace(yang_stmt *ys,
char *ns,
char **prefix)
{
int retval = 0; /* not found */
yang_stmt *my_ymod; /* My module */
char *myns; /* My ns */
yang_stmt *yspec;
yang_stmt *ymod;
char *modname = NULL;
yang_stmt *yimport;
yang_stmt *yprefix;
clicon_debug(2, "%s", __FUNCTION__);
/* First check if namespace is my own module */
myns = yang_find_mynamespace(ys);
if (strcmp(myns, ns) == 0){
*prefix = yang_find_myprefix(ys); /* or NULL? */
goto found;
}
/* Next, find namespaces in imported modules */
yspec = ys_spec(ys);
if ((ymod = yang_find_module_by_namespace(yspec, ns)) == NULL)
goto notfound;
modname = yang_argument_get(ymod);
my_ymod = ys_module(ys);
/* Loop through import statements to find a match with ymod */
yimport = NULL;
while ((yimport = yn_each(my_ymod, yimport)) != NULL) {
if (yang_keyword_get(yimport) == Y_IMPORT &&
strcmp(modname, yang_argument_get(yimport)) == 0){ /* match */
yprefix = yang_find(yimport, Y_PREFIX, NULL);
*prefix = yang_argument_get(yprefix);
goto found;
}
}
notfound:
return retval;
found:
assert(*prefix);
return 1;
}
/*! Return topmost yang root node directly under module/submodule
*
* @param[in] ys Yang statement
* @retval ytop Topmost yang node (can be ys itself)
* @retval NULL ys is spec, module, NULL etc with no reasonable rootnode
*/
yang_stmt *
yang_myroot(yang_stmt *ys)
{
yang_stmt *yp;
enum rfc_6020 kw;
kw = yang_keyword_get(ys);
if (ys==NULL || kw==Y_SPEC || kw == Y_MODULE || kw == Y_SUBMODULE)
return NULL;
yp = yang_parent_get(ys);
while((yp = yang_parent_get(ys)) != NULL) {
kw = yang_keyword_get(yp);
if (kw == Y_MODULE || kw == Y_SUBMODULE)
return ys;
ys = yp;
}
return NULL;
}
/*! 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;
}
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] yspec 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 *yspec,
cxobj *xt,
yang_stmt **ymodp)
{
int retval = -1;
yang_stmt *ym = NULL; /* module */
char *prefix = NULL;
char *ns = NULL; /* namespace URI */
if (ymodp)
*ymodp = NULL;
prefix = xml_prefix(xt);
if (xml2ns(xt, prefix, &ns) < 0) /* prefix may be NULL */
goto done;
/* No namespace found, give up */
if (ns == NULL)
goto ok;
/* We got the namespace, now get the module */
ym = yang_find_module_by_namespace(yspec, ns);
/* 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
* @param[out] ymod The top module or sub-module
* @retval 0 OK, returned module in ymod
* @retval -1 YANG validation error: all yang statements should have a "real" module
* @see ys_module
* @note For an augmented node, the original module is returned
*/
int
ys_real_module(yang_stmt *ys,
yang_stmt **ymod)
{
int retval = -1;
yang_stmt *ym = NULL;
yang_stmt *ysubm;
yang_stmt *yb;
char *name;
yang_stmt *yspec;
if (ymod == NULL){
clicon_err(OE_YANG, EINVAL, "ymod is NULL");
goto done;
}
if ((ym = ys_module(ys)) != NULL){
yspec = ys_spec(ym);
while (ym && 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;
}
if ((ysubm = yang_find_module_by_name(yspec, name)) == NULL){
clicon_err(OE_YANG, ENOENT, "submodule %s references non-existent module %s in its belongs-to statement",
yang_argument_get(ym), name);
goto done;
}
ym = ysubm;
}
}
*ymod = ym;
retval = 0;
done:
return retval;
}
/*! 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 yang_augment_node where shortcut is set for augment
* @see yang_myroot for first node under (sub)module
*/
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;
}
/*! 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
* @param[in] fn Callback to make print function
* @see yang_print_cbuf
*/
int
yang_print_cb(FILE *f,
yang_stmt *yn,
clicon_output_cb *fn)
{
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;
(*fn)(f, "%s", cbuf_get(cb));
if (cb)
cbuf_free(cb);
retval = 0;
done:
return retval;
}
/*! 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)
{
return yang_print_cb(f, yn, fprintf);
}
/* Log/debug info about top-level (sub)modules no recursion
* @param[in] yspec Yang spec
* @param[in] dbglevel Debug level
*/
int
yang_spec_dump(yang_stmt *yspec,
int dbglevel)
{
int retval = -1;
yang_stmt *ym = NULL;
yang_stmt *yrev;
cbuf *cb = NULL;
if ((cb = cbuf_new()) ==NULL){
clicon_err(OE_YANG, errno, "cbuf_new");
goto done;
}
while ((ym = yn_each(yspec, ym)) != NULL) {
cprintf(cb, "%s", yang_key2str(ym->ys_keyword));
cprintf(cb, " %s", ym->ys_argument);
if ((yrev = yang_find(ym, Y_REVISION, NULL)) != NULL){
cprintf(cb, "@%u", cv_uint32_get(yang_cv_get(yrev)));
}
cprintf(cb, ".yang");
clicon_debug(dbglevel, "%s", cbuf_get(cb));
cbuf_reset(cb);
}
retval = 0;
done:
if (cb)
cbuf_free(cb);
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;
}
/*! Yang deviation/deviate
*
* Identify deviation target node, and go through all its deviate statements.
* Features/if-feature must have run before
* @param[in] ys The yang deviation to populate.
* @param[in] h Clicon handle
* @see RFC 7950 5.6.3 and 7.20.3
*/
int
yang_deviation(yang_stmt *ys,
void *arg)
{
int retval = -1;
char *nodeid;
yang_stmt *ytarget = NULL;
yang_stmt *yd;
yang_stmt *yc;
yang_stmt *yc1;
yang_stmt *ytc;
char *devop;
clicon_handle h = (clicon_handle)arg;
enum rfc_6020 kw;
int min;
int max;
if (yang_keyword_get(ys) != Y_DEVIATION)
goto ok;
/* Absolute schema node identifier identifying target node */
if ((nodeid = yang_argument_get(ys)) == NULL){
clicon_err(OE_YANG, EINVAL, "No argument to deviation");
goto done;
}
/* Get target node */
if (yang_abs_schema_nodeid(ys, nodeid, &ytarget) < 0)
goto done;
if (ytarget == NULL){
clicon_log(LOG_WARNING, "deviation %s: target not found", nodeid);
goto ok;
/* The RFC does not explicitly say the target node MUST exist
clicon_err(OE_YANG, 0, "schemanode sanity check of %s", nodeid);
goto done;
*/
}
/* Go through deviates of deviation */
yd = NULL;
while ((yd = yn_each(ys, yd)) != NULL) {
/* description / if-feature / reference */
if (yang_keyword_get(yd) != Y_DEVIATE)
continue;
devop = yang_argument_get(yd);
if (strcmp(devop, "not-supported") == 0){
if (ys_prune_self(ytarget) < 0)
goto done;
if (ys_free(ytarget) < 0)
goto done;
goto ok; /* Target node removed, no other deviates possible */
}
else if (strcmp(devop, "add") == 0){
yc = NULL;
while ((yc = yn_each(yd, yc)) != NULL) {
/* If a property can only appear once, the property MUST NOT exist in the target node. */
kw = yang_keyword_get(yc);
if (yang_find(ytarget, kw, NULL) != NULL){
if (yang_cardinality_interval(h,
yang_keyword_get(ytarget),
kw,
&min,
&max) < 0)
goto done;
if (max == 1){
clicon_err(OE_YANG, 0, "deviation %s: \"%s %s\" added but node already exist in target %s",
nodeid,
yang_key2str(kw), yang_argument_get(yc),
yang_argument_get(ytarget));
goto done;
}
}
/* Make a copy of deviate child and insert. */
if ((yc1 = ys_dup(yc)) == NULL)
goto done;
if (yn_insert(ytarget, yc1) < 0)
goto done;
}
}
else if (strcmp(devop, "replace") == 0){
yc = NULL;
while ((yc = yn_each(yd, yc)) != NULL) {
/* The properties to replace MUST exist in the target node.*/
kw = yang_keyword_get(yc);
if ((ytc = yang_find(ytarget, kw, NULL)) == NULL){
clicon_err(OE_YANG, 0, "deviation %s: \"%s %s\" replaced but node does not exist in target %s",
nodeid,
yang_key2str(kw), yang_argument_get(yc),
yang_argument_get(ytarget));
goto done;
}
/* Remove old */
if (ys_prune_self(ytc) < 0)
goto done;
if (ys_free(ytc) < 0)
goto done;
/* Make a copy of deviate child and insert. */
if ((yc1 = ys_dup(yc)) == NULL)
goto done;
if (yn_insert(ytarget, yc1) < 0)
goto done;
}
}
else if (strcmp(devop, "delete") == 0){
yc = NULL;
while ((yc = yn_each(yd, yc)) != NULL) {
/* The substatement's keyword MUST match a corresponding keyword in the target node, and the
* argument's string MUST be equal to the corresponding keyword's argument string in the
* target node. */
kw = yang_keyword_get(yc);
if ((ytc = yang_find(ytarget, kw, NULL)) == NULL){
clicon_err(OE_YANG, 0, "deviation %s: \"%s %s\" replaced but node does not exist in target %s",
nodeid,
yang_key2str(kw), yang_argument_get(yc),
yang_argument_get(ytarget));
goto done;
}
if (ys_prune_self(ytc) < 0)
goto done;
if (ys_free(ytc) < 0)
goto done;
}
}
else{ /* Shouldnt happen, lex/yacc takes it */
clicon_err(OE_YANG, EINVAL, "%s: invalid deviate operator", devop);
goto done;
}
}
ok:
retval = 0;
done:
return retval;
}
/*! 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] h Clicon handle
* @param[in] ys The yang statement to populate.
* @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 *origtype=NULL; /* original type */
uint8_t fraction_digits;
int options = 0x0;
yang_stmt *ytypedef; /* where type is define */
yparent = ys->ys_parent; /* Find parent: list/container */
/* 1. Find type specification and set cv type accordingly */
if (yang_type_get(ys, &origtype, &yrestype, &options, NULL, NULL, NULL, &fraction_digits) < 0)
goto done;
restype = yrestype?yrestype->ys_argument:NULL;
if (clicon_type2cv(origtype, 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;
}
/* get parent of where type is defined, can be original object */
ytypedef = yrestype?yang_parent_get(yrestype):ys;
/* 3. Check if default value. Here we parse the string in the default-stmt
* and add it to the leafs cv.
* 3a) First check local default
*/
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;
}
}
/* 2. then check typedef default */
else if (ytypedef != ys &&
(ydef = yang_find(ytypedef, 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;
}
yang_cv_set(ys, cv);
retval = 0;
done:
if (origtype)
free(origtype);
if (cv && retval < 0)
cv_free(cv);
return retval;
}
/*! Populate list yang statement
* @param[in] h Clicon handle
* @param[in] ys The yang statement (type) to populate.
*/
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.
* @param[in] h Clicon handle
* @param[in] ys The yang statement (range) to populate.
* 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 = NULL; /* 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;
if (yrestype == NULL){
clicon_err(OE_YANG, 0, "result-type should not be NULL");
goto done;
}
restype = yrestype?yrestype->ys_argument:NULL;
if (nodeid_split(yang_argument_get(yparent), NULL, &origtype) < 0)
goto done;
/* 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:
if (origtype)
free(origtype);
return retval;
}
/*! Populate integer built-in length statement
*
* Create cvec variables "range_min" and "range_max". Assume parent is type.
* @param[in] h Clicon handle
* @param[in] ys The yang statement (length) to populate.
*
* Actually: len[..len] (| len[..len])*
* len is unsigned integer or keywords 'min' or 'max'
*
* From RFC 7950 Sec 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] h Clicon handle
* @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 and create derived id list
*
* Find base identities if any and add this identity to derived identity list.
* Do this recursively
* The derived identity list is a list of <module>:<id> pairs. Prefixes cannot
* be used since they are local in scope.
* @param[in] h Clicon handle
* @param[in] ys The yang identity to populate.
* @param[in] idref If set contains the derived identifier(NULL on top call)
* @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 *baseid;
char *prefix = NULL;
char *id = NULL;
cbuf *cb = NULL;
yang_stmt *ymod;
cvec *idrefvec; /* Derived identityref list: (module:id)**/
/* Top-call (no recursion) create idref
* The idref is (here) in "canonical form": <module>:<id>
*/
if (idref == NULL){
/* Create derived identity through prefix:id if not recursively called*/
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
if (nodeid_split(yang_argument_get(ys), &prefix, &id) < 0)
goto done;
if ((ymod = ys_module(ys)) == NULL){
clicon_err(OE_YANG, ENOENT, "No module found");
goto done;
}
cprintf(cb, "%s:%s", yang_argument_get(ymod), id);
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 = yang_argument_get(yc); /* on the form: prefix:id */
if (((ybaseid = yang_find_identity(ys, baseid))) == NULL){
clicon_err(OE_YANG, ENOENT, "No such identity: %s", baseid);
goto done;
}
// continue; /* root identity */
/* Check if derived id is already in base identifier
* note that cvec is always created in ys_new()
*/
idrefvec = yang_cvec_get(ybaseid);
if (cvec_find(idrefvec, 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(idrefvec, 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 (id)
free(id);
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] h Clicon handle
* @param[in] ys Feature yang statement to populate.
* Bootstrapping: A feature is enabled if found in clixon-config
*/
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.
* Bootstrapping: A feature is enabled if found in clixon-config
*/
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(1, "%s %s:%s", __FUNCTION__, module, feature);
yang_cv_set(ys, cv);
ok:
retval = 0;
done:
return retval;
}
/*! Populate the unique statement with a cvec
* @param[in] h Clicon handle
* @param[in] ys The yang statement (unique) to populate.
*/
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
* @param[in] h Clicon handle
* @param[in] ys The yang statement (unknown) to populate.
* RFC 7950 Sec 7.19:
* If no "argument" statement is present, the keyword expects no argument when
* it is used.
*/
static int
ys_populate_unknown(clicon_handle h,
yang_stmt *ys)
{
int retval = -1;
yang_stmt *ymod;
yang_stmt *yext; /* extension */
char *prefix = NULL;
char *id = NULL;
char *argument; /* This is the unknown optional argument */
cg_var *cv;
/* Find extension, if found, store it as unknown, if not,
break for error */
if (nodeid_split(yang_argument_get(ys), &prefix, &id) < 0)
goto done;
if ((ymod = yang_find_module_by_prefix(ys, prefix)) == NULL){
clicon_err(OE_YANG, ENOENT, "Extension \"%s:%s\", module not found", prefix, id);
goto done;
}
if ((yext = yang_find(ymod, Y_EXTENSION, id)) == NULL){
clicon_err(OE_YANG, ENOENT, "Extension \"%s:%s\" not found", prefix, id);
goto done;
}
/* Optional argument (only if "argument") - save it in ys_cv */
if ((cv = yang_cv_get(ys)) != NULL &&
(argument = cv_string_get(cv)) != NULL){
if (yang_find(yext, Y_ARGUMENT, NULL) == NULL &&
argument != NULL){
clicon_err(OE_YANG, 0, "No argument specified in extension %s, but argument %s present when used", yang_argument_get(ys), argument);
goto done;
}
}
#ifdef XML_EXPLICIT_INDEX
/* Add explicit index extension */
if ((retval = yang_search_index_extension(h, yext, ys)) < 0) {
clicon_debug(1, "plugin_extension() failed");
return -1;
}
#endif
/* Make extension callbacks that may alter yang structure
* Note: this may be a "layering" violation: assuming plugins are loaded
* at yang parse time
*/
if (clixon_plugin_extension_all(h, yext, ys) < 0)
goto done;
retval = 0;
done:
if (prefix)
free(prefix);
if (id)
free(id);
return retval;
}
/*! Populate modules / submodules
* @param[in] h Clicon handle
* @param[in] ys The yang statement (module/submodule) to populate.
*
* Check RFC 7950: 7.1.4: All prefixes, including the prefix for the module itself,
* MUST be unique within the module or submodule.
*/
static int
ys_populate_module_submodule(clicon_handle h,
yang_stmt *ym)
{
int retval = -1;
yang_stmt *yp;
yang_stmt *yi;
yang_stmt *yi2; /* remaining */
char *p0 = NULL;
char *pi;
char *pi2; /* remaining */
/* Modules but not submodules have prefixes */
if ((yp = yang_find(ym, Y_PREFIX, NULL)) != NULL)
p0 = yang_argument_get(yp);
yi = NULL;
while ((yi = yn_each(ym, yi)) != NULL) {
if (yang_keyword_get(yi) != Y_IMPORT)
continue;
yp = yang_find(yi, Y_PREFIX, NULL);
pi = yang_argument_get(yp);
if (p0 && strcmp(p0, pi) == 0){ /* Check top-level */
clicon_err(OE_YANG, EFAULT, "Prefix %s in module %s is not unique but should be (see RFC 7950 7.1.4)",
pi, yang_argument_get(ym));
goto done;
}
/* Check rest of imports */
yi2 = yi;
while ((yi2 = yn_each(ym, yi2)) != NULL) {
if (yang_keyword_get(yi2) != Y_IMPORT)
continue;
yp = yang_find(yi2, Y_PREFIX, NULL);
pi2 = yang_argument_get(yp);
if (strcmp(pi2, pi) == 0){
clicon_err(OE_YANG, EFAULT, "Prefix %s in module %s is not unique but should be (see RFC 7950 7.1.4)",
pi, yang_argument_get(ym));
goto done;
}
}
}
retval = 0;
done:
return retval;
}
/*! Populate with cligen-variables, default values, etc. Sanity checks on complete tree.
*
* @param[in] ys Yang statement
* @param[in] arg Argument - in effect 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_IDENTITY:
if (ys_populate_identity(h, ys, NULL) < 0)
goto done;
break;
case Y_LENGTH:
if (ys_populate_length(h, ys) < 0)
goto done;
break;
case Y_LIST:
if (ys_populate_list(h, ys) < 0)
goto done;
break;
case Y_MODULE:
case Y_SUBMODULE:
if (ys_populate_module_submodule(h, ys) < 0)
goto done;
break;
case Y_RANGE:
if (ys_populate_range(h, ys) < 0)
goto done;
break;
case Y_TYPE:
if (ys_populate_type(h, ys) < 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
*/
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:
case Y_REQUIRE_INSTANCE:
if (ys_parse(ys, CGV_BOOL) == NULL)
goto done;
break;
default:
break;
}
retval = 0;
done:
return retval;
}
/*! Handle complexity of if-feature node
* @param[in] h Clixon handle
* @param[in] ys Yang if-feature statement
* @retval -1 Error
* @retval 0 Feature not enabled: remove yt
* @retval 1 OK
* @note if-feature syntax is restricted to single, and, or, syntax, such as "a or b"
* but RFC7950 allows for nested expr/term/factor syntax.
* XXX This should really be parsed in yang/lex.
*/
static int
yang_if_feature(clicon_handle h,
yang_stmt *ys)
{
int retval = -1;
char **vec = NULL;
int nvec;
char *f;
int i;
int j;
char *prefix = NULL;
char *feature = NULL;
yang_stmt *ymod; /* module yang node */
yang_stmt *yfeat; /* feature yang node */
int opand = -1; /* -1:not set, 0:or, 1:and */
int enabled = 0;
cg_var *cv;
if ((vec = clicon_strsep(ys->ys_argument, " \t\r\n", &nvec)) == NULL)
goto done;
/* Two steps: first detect operators
* Step 1: support "a" or "a or b or c" or "a and b and c "
*/
j = 0;
for (i=0; i<nvec; i++){
f = vec[i];
if (strcmp(f, "") == 0) /* skip empty */
continue;
if ((j++)%2==0) /* only keep odd i:s 1,3,... */
continue;
/* odd i: operator "and" or "or" */
if (strcmp(f, "or") == 0){
switch (opand){
case -1:
if (i != 1){
clicon_err(OE_YANG, EINVAL, "Syntax error IF_FEATURE \"%s\" (only single if-feature-expr and/or lists allowed)", ys->ys_argument);
goto done;
}
opand = 0;
break;
case 0:
break;
case 1:
clicon_err(OE_YANG, EINVAL, "Syntax error IF_FEATURE \"%s\" (only single if-feature-expr and/or lists allowed)", ys->ys_argument);
goto done;
break;
}
}
else if (strcmp(f, "and") == 0){
switch (opand){
case -1:
if (i != 1){
clicon_err(OE_YANG, EINVAL, "Syntax error IF_FEATURE \"%s\" (only single if-feature-expr and/or lists allowed)", ys->ys_argument);
goto done;
}
opand = 1;
break;
case 0:
clicon_err(OE_YANG, EINVAL, "Syntax error IF_FEATURE \"%s\" (only single if-feature-expr and/or lists allowed)", ys->ys_argument);
goto done;
break;
case 1:
break;
}
}
else{
clicon_err(OE_YANG, EINVAL, "Syntax error IF_FEATURE \"%s\" (only single if-feature-expr and/or lists allowed)", ys->ys_argument);
goto done;
}
} /* for step 1 */
if (j%2 == 0){ /* Must be odd: eg a / "a or b" etc */
clicon_err(OE_YANG, EINVAL, "Syntax error IF_FEATURE \"%s\" (only single if-feature-expr and/or lists allowed)", ys->ys_argument);
goto done;
}
if (opand == -1) /* Uninitialized means single operand */
opand = 1;
if (opand) /* if AND, start as enabled, if OR start as disabled */
enabled = 1;
else
enabled = 0;
/* Step 2: Boolean operations on operands */
j = 0;
for (i=0; i<nvec; i++){
f = vec[i];
if (strcmp(f, "") == 0) /* skip empty */
continue;
if ((j++)%2==1) /* only keep even i:s 0,2,... */
continue;
if (nodeid_split(f, &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(ys, prefix);
/* Check if feature exists, and is set, otherwise remove */
if ((yfeat = yang_find(ymod, Y_FEATURE, feature)) == NULL){
clicon_err(OE_YANG, EINVAL, "Yang module %s has IF_FEATURE %s, but no such FEATURE statement exists",
ymod?yang_argument_get(ymod):"none",
feature);
goto done;
}
/* Check if this feature is enabled or not
* Continue loop to catch unbound features and make verdict at end
*/
cv = yang_cv_get(yfeat);
if (cv == NULL || !cv_bool_get(cv)){ /* disabled */
/* if AND then this is permanently disabled */
if (opand && enabled)
enabled = 0;
}
else{ /* enabled */
/* if OR then this is permanently enabled */
if (!opand && !enabled)
enabled = 1;
}
if (prefix){
free(prefix);
prefix = NULL;
}
if (feature){
free(feature);
feature = NULL;
}
}
if (!enabled)
goto disabled;
retval = 1;
done:
if (vec)
free(vec);
if (prefix)
free(prefix);
if (feature)
free(feature);
return retval;
disabled:
retval = 0; /* feature not enabled */
goto done;
}
/*! Find feature and if-feature nodes, check features and remove disabled nodes
* @param[in] h Clixon handle
* @param[in] yt Yang statement
* @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)
* @note if-feature syntax is restricted to single, and, or, syntax, such as "a or b"
*/
int
yang_features(clicon_handle h,
yang_stmt *yt)
{
int retval = -1;
int i;
int j;
yang_stmt *ys = NULL;
int ret;
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 ((ret = yang_if_feature(h, ys)) < 0)
goto done;
if (ret == 0)
goto disabled;
}
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:
return retval;
disabled:
retval = 0; /* feature not enabled */
goto done;
}
/*! 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] depth Depth argument: where to start. If <=0 call the calling node yn, if 1 start with its children, etc
* @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, 1, NULL); // Call all yn:s children recursively
* @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,
int depth,
void *arg)
{
int retval = -1;
yang_stmt *ys = NULL;
int i;
int ret;
if (depth <= 0){
if ((int)keyword == -1 || keyword == yn->ys_keyword){
if ((ret = fn(yn, arg)) < 0)
goto done;
if (ret > 0){
retval = ret;
goto done;
}
}
}
for (i=0; i<yn->ys_len; i++){
ys = yn->ys_stmt[i];
if ((ret = yang_apply(ys, keyword, fn, depth-1, 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)
{
enum rfc_6020 keyw;
keyw = yang_keyword_get(ys);
return (keyw == Y_CONTAINER ||
keyw == Y_LEAF ||
keyw == Y_LIST ||
keyw == Y_LEAF_LIST ||
keyw == Y_ANYXML ||
keyw == Y_ANYDATA);
}
/*! All the work for schema_nodeid functions both absolute and descendant
*
* @param[in] yn Yang node. For absolute schemanodeids this should be a module, otherwise any yang
* @param[in] cvv Schema-node path encoded as a name/value pair list.
* @param[in] nsc Namespace context from yang for the prefixes (names) of cvv
* @param[out] yres Result yang statement node, or NULL if not found
* @retval -1 Error, with clicon_err called
* @retval 0 OK
* A schema node identifier consists of a path of identifiers, separated by slashes ("/").
* References to identifiers defined in external modules MUST be
* qualified with appropriate prefixes, and references to identifiers
* defined in the current module and its submodules MAY use a prefix.
* prefixes are implemented by cvv names, and id:s by cvv strings.
* A namespace context of the original module is nsc as prefix context.
*
* @see RFC7950 Sec 6.5
*/
static int
schema_nodeid_iterate(yang_stmt *yn,
cvec *nodeid_cvv,
cvec *nsc,
yang_stmt **yres)
{
int retval = -1;
yang_stmt *ymod;
char *prefix; /* node-identifier = [prefix ":"] identifier */
char *id;
yang_stmt *ys;
yang_stmt *yp;
cg_var *cv;
char *ns;
yang_stmt *yspec;
yspec = ys_spec(yn);
yp = yn;
/* Iterate over node identifiers /prefix:id/... */
cv = NULL;
while ((cv = cvec_each(nodeid_cvv, cv)) != NULL){
prefix = cv_name_get(cv);
id = cv_string_get(cv);
/* Top level is repeated from abs case, but here this is done to match with
* matching module below
* Get namespace */
if ((ns = xml_nsctx_get(nsc, prefix)) == NULL){
clicon_err(OE_YANG, EFAULT, "No namespace for prefix: %s in schema node identifier in module %s",
prefix,
yang_argument_get(ys_module(yn)));
goto done;
}
/* Get yang module */
if ((ymod = yang_find_module_by_namespace(yspec, ns)) == NULL){
clicon_err(OE_YANG, EFAULT, "No module for namespace: %s", ns);
goto done;
}
ys = yang_find_schemanode(yp, id);
/* Special case: if rpc/action, an empty input/output may need to be created, it is optional but may
* still be referenced.
* XXX: maybe input/output should always be created when rpc/action is created?
*/
if (ys == NULL &&
(yang_keyword_get(yp) == Y_RPC || yang_keyword_get(yp) == Y_ACTION) &&
(strcmp(id, "input") == 0 || strcmp(id, "output") == 0)){
enum rfc_6020 kw;
kw = clicon_str2int(ykmap, id);
/* Add ys as id to yp */
if ((ys = ys_new(kw)) == NULL)
goto done;
if (yn_insert(yp, ys) < 0) /* Insert into hierarchy */
goto done;
}
if (ys == NULL){
clicon_debug(1, "%s: %s not found", __FUNCTION__, id);
goto ok;
}
yp = ys; /* ys is matched */
} /* while cv */
assert(yp && yang_schemanode((yang_stmt*)yp));
*yres = (yang_stmt*)yp;
ok:
retval = 0;
done:
return retval;
}
/*! Given an absolute schema-nodeid (eg /a/b/c) find matching yang spec
* @param[in] yn Original yang stmt (where call is made)
* @param[in] schema_nodeid Absolute schema-node-id, ie /a/b
* @param[out] yres Result yang statement node, or NULL if not found
* @retval -1 Error, with clicon_err called
* @retval 0 OK , with result in yres
* Assume schema nodeid:s have prefixes, (actually the first).
* @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
* @see yang_desc_schema_nodeid
*/
int
yang_abs_schema_nodeid(yang_stmt *yn,
char *schema_nodeid,
yang_stmt **yres)
{
int retval = -1;
cvec *nodeid_cvv = NULL;
cvec *nsc = NULL;
cg_var *cv;
char *prefix;
char *ns;
yang_stmt *yspec;
yang_stmt *ymod;
char *str;
*yres = NULL;
yspec = ys_spec(yn);
/* check absolute schema_nodeid */
if (schema_nodeid[0] != '/'){
clicon_err(OE_YANG, EINVAL, "absolute schema nodeid should start with /");
goto done;
}
/* Split nodeid on the form /p0:i0/p1:i1 to a cvec with [name:p0 value:i0][...]
*/
if (uri_str2cvec(schema_nodeid, '/', ':', 1, &nodeid_cvv) < 0)
goto done;
if (cvec_len(nodeid_cvv) == 0)
goto ok;
/* If p0 is NULL an entry will be: [i0] which needs to be transformed to [NULL:i0] */
cv = NULL;
while ((cv = cvec_each(nodeid_cvv, cv)) != NULL){
if (cv_type_get(cv) != CGV_STRING)
cv_type_set(cv, CGV_STRING);
if ((str = cv_string_get(cv)) == NULL || !strlen(str)){
if (cv_string_set(cv, cv_name_get(cv)) < 0){
clicon_err(OE_UNIX, errno, "cv_string_set");
goto done;
}
cv_name_set(cv, NULL);
}
}
/* Make a namespace context from yang for the prefixes (names) of nodeid_cvv */
if (yang_keyword_get(yn) == Y_SPEC){
if (xml_nsctx_yangspec(yn, &nsc) < 0)
goto done;
}
else if (xml_nsctx_yang(yn, &nsc) < 0)
goto done;
/* Since this is an _absolute_ schema nodeid start from top
* Get namespace */
cv = cvec_i(nodeid_cvv, 0);
prefix = cv_name_get(cv);
if ((ns = xml_nsctx_get(nsc, prefix)) == NULL){
clicon_err(OE_YANG, EFAULT, "No namespace for prefix: %s in schema node identifier: %s",
prefix, schema_nodeid);
goto done;
}
/* Get yang module */
if ((ymod = yang_find_module_by_namespace(yspec, ns)) == NULL){
clicon_err(OE_YANG, EFAULT, "No module for namespace: %s in schema node identifier: %s",
ns, schema_nodeid);
goto done;
}
/* Iterate through cvv to find schemanode using ymod as starting point (since it is absolute) */
if (schema_nodeid_iterate(ymod, nodeid_cvv, nsc, yres) < 0)
goto done;
ok:
retval = 0;
done:
if (nodeid_cvv)
cvec_free(nodeid_cvv);
if (nsc)
cvec_free(nsc);
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
* Used in yang: unique, refine, uses augment
* @see yang_abs_schema_nodeid
*/
int
yang_desc_schema_nodeid(yang_stmt *yn,
char *schema_nodeid,
yang_stmt **yres)
{
int retval = -1;
cvec *nodeid_cvv = NULL;
cg_var *cv;
char *str;
cvec *nsc = NULL;
if (schema_nodeid == NULL || strlen(schema_nodeid) == 0){
clicon_err(OE_YANG, EINVAL, "nodeid is empty");
goto done;
}
*yres = NULL;
/* check absolute schema_nodeid */
if (schema_nodeid[0] == '/'){
clicon_err(OE_YANG, EINVAL, "descendant schema nodeid should not start with /");
goto done;
}
/* Split nodeid on the form /p0:i0/p1:i1 to a cvec with [name:p0 value:i0][...]
*/
if (uri_str2cvec(schema_nodeid, '/', ':', 1, &nodeid_cvv) < 0)
goto done;
if (cvec_len(nodeid_cvv) == 0)
goto ok;
/* If p0 is NULL an entry will be: [i0] which needs to be transformed to [NULL:i0] */
cv = NULL;
while ((cv = cvec_each(nodeid_cvv, cv)) != NULL){
if (cv_type_get(cv) != CGV_STRING)
cv_type_set(cv, CGV_STRING);
if ((str = cv_string_get(cv)) == NULL || !strlen(str)){
if (cv_string_set(cv, cv_name_get(cv)) < 0){
clicon_err(OE_UNIX, errno, "cv_string_set");
goto done;
}
cv_name_set(cv, NULL);
}
}
/* Make a namespace context from yang for the prefixes (names) of nodeid_cvv */
if (xml_nsctx_yang(yn, &nsc) < 0)
goto done;
/* Iterate through cvv to find schemanode using yn as relative starting point */
if (schema_nodeid_iterate(yn, nodeid_cvv, nsc, yres) < 0)
goto done;
ok:
retval = 0;
done:
if (nsc)
cvec_free(nsc);
if (nodeid_cvv)
cvec_free(nodeid_cvv);
return retval;
}
/*! Check 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) # see below
* 3) A container node without a "presence" statement and that has at
* least one mandatory node as a child.
*
* @note There is also this statement
* 2) A list or leaf-list node with a "min-elements" statement with a
* value greater than zero.
* which we ignore here since:
* (a) it does not consider the XML siblings and therefore returns false positives
* (b) where the actual check is catched by check_list_unique_minmax()
*/
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 ((cv = yang_cv_get(ym)) != NULL) /* shouldnt happen */
return cv_bool_get(cv);
}
}
#if 0 /* See note above */
/* 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; /* Not true if XML considered */
}
}
#endif
/* 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
* @param[in] ys Yang statement
* @retval 0 If node has a config sub-statement and it is false
* @retval 1 If node has not config sub-statement or it is true
* @see yang_config_ancestor which also takes ancestors into account, which you should normally do.
*/
int
yang_config(yang_stmt *ys)
{
yang_stmt *ym;
if ((ym = yang_find(ys, Y_CONFIG, NULL)) != NULL){
if (yang_cv_get(ym) == NULL) /* shouldnt happen */
return 1;
return cv_bool_get(yang_cv_get(ym));
}
return 1;
}
/*! Return config state of this node taking parents/ancestors into account
*
* config statement is default true.
* @param[in] ys Yang statement
* @retval 0 Node or one of its ancestor has config false or is RPC or notification
* @retval 1 Neither node nor any of its ancestors has config false
*/
int
yang_config_ancestor(yang_stmt *ys)
{
yang_stmt *yp;
yp = ys;
do {
if (yang_config(yp) == 0)
return 0;
if (yang_keyword_get(yp) == Y_INPUT || yang_keyword_get(yp) == Y_OUTPUT || yang_keyword_get(yp) == Y_NOTIFICATION)
return 0;
} while((yp = yang_parent_get(yp)) != NULL);
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 is subject to generated cli GT_HIDE boolean
* The yang should be:
* 1) a non-presence container
* 2) parent of a (single) list XXX: or could multiple lists work?
* 3) no other data node children
* @retval 0 No, does not satisfy the GT_HIDE condition
* @retval 1 Yes, satisfies the GT_HIDE condition
* @see clixon-config.yang HIDE enumeration type
*/
int
yang_container_cli_hide(yang_stmt *ys,
enum genmodel_type gt)
{
yang_stmt *yc = NULL;
int i;
enum rfc_6020 keyw;
keyw = yang_keyword_get(ys);
/* HIDE mode */
if (gt != GT_HIDE)
return 0;
/* A container */
if (yang_keyword_get(ys) != Y_CONTAINER)
return 0;
/* Non-presence */
if (yang_find(ys, Y_PRESENCE, NULL) != NULL)
return 0;
/* Ensure a single list child and no other data nodes */
i = 0; /* Number of list nodes */
while ((yc = yn_each(ys, yc)) != NULL) {
keyw = yang_keyword_get(yc);
/* case/choice could hide anything so disqualify those */
if (keyw == Y_CASE || keyw == Y_CHOICE)
break;
if (!yang_datanode(yc)) /* Allowed, check next */
continue;
if (keyw != Y_LIST) /* Another datanode than list */
break;
if (i++>0) /* More than one list (or could this work?) */
break;
}
if (yc != NULL) /* break from loop */
return 0;
if (i != 1) /* List found */
return 0;
return 1; /* Passed all tests: yes you can hide this keyword */
}
/*! 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 list 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;
}
/*! Set type cache for yang type
* @param[in] rxmode Kludge to know which regexp engine is used
* @see yang_type_cache_regexp_set where cache is extended w compiled regexps
*/
int
yang_type_cache_set(yang_stmt *ys,
yang_stmt *resolved,
int options,
cvec *cvv,
cvec *patterns,
uint8_t fraction)
{
int retval = -1;
yang_type_cache *ycache;
if (ys->ys_typecache != NULL){
clicon_err(OE_YANG, EEXIST, "yang type cache");
goto done;
}
if ((ys->ys_typecache = (yang_type_cache *)malloc(sizeof(*ycache))) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
goto done;
}
ycache = ys->ys_typecache;
memset(ycache, 0, sizeof(*ycache));
ycache->yc_resolved = resolved;
ycache->yc_options = options;
if (cvv){
if ((ycache->yc_cvv = cvec_dup(cvv)) == NULL){
clicon_err(OE_UNIX, errno, "cvec_dup");
goto done;
}
}
if (patterns && (ycache->yc_patterns = cvec_dup(patterns)) == NULL){
clicon_err(OE_UNIX, errno, "cvec_dup");
goto done;
}
ycache->yc_fraction = fraction;
retval = 0;
done:
return retval;
}
/*! Extend yang type cache with compiled regexps
* Compiled Regexps are computed in validate code - after initial cache set
* @param[in] regexps
*/
int
yang_type_cache_regexp_set(yang_stmt *ytype,
int rxmode,
cvec *regexps)
{
int retval = -1;
yang_type_cache *ycache;
assert(regexps);
assert(yang_keyword_get(ytype) == Y_TYPE);
assert((ycache = ytype->ys_typecache) != NULL);
assert(ycache->yc_regexps == NULL);
ycache->yc_rxmode = rxmode;
if ((ycache->yc_regexps = cvec_dup(regexps)) == NULL){
clicon_err(OE_UNIX, errno, "cvec_dup");
goto done;
}
retval = 0;
done:
return retval;
}
/*! Get individual fields (direct/destructively) from yang type cache.
* @param[out] patterns Initialized cvec of regexp patterns strings
* @retval -1 Error
* @retval 0 No cache
* @retval 1 OK
*/
int
yang_type_cache_get(yang_stmt *ytype,
yang_stmt **resolved,
int *options,
cvec **cvv,
cvec *patterns,
int *rxmode,
cvec *regexps,
uint8_t *fraction)
{
int retval = -1;
cg_var *cv = NULL;
yang_type_cache *ycache;
ycache = ytype->ys_typecache;
if (ycache == NULL){ /* No cache return 0 */
retval = 0;
goto done;
}
if (resolved)
*resolved = ycache->yc_resolved;
if (options)
*options = ycache->yc_options;
if (cvv)
*cvv = ycache->yc_cvv;
if (patterns){
cv = NULL;
while ((cv = cvec_each(ycache->yc_patterns, cv)) != NULL)
cvec_append_var(patterns, cv);
}
if (regexps){
cv = NULL;
while ((cv = cvec_each(ycache->yc_regexps, cv)) != NULL)
cvec_append_var(regexps, cv);
}
if (rxmode)
*rxmode = ycache->yc_rxmode;
if (fraction)
*fraction = ycache->yc_fraction;
retval = 1; /* cache exists and is returned OK */
done:
return retval;
}
/*! Copy yang type cache
*/
static int
yang_type_cache_cp(yang_stmt *ynew,
yang_stmt *yold)
{
int retval = -1;
int options;
cvec *cvv;
cvec *patterns = NULL;
uint8_t fraction;
yang_stmt *resolved;
int ret;
if ((patterns = cvec_new(0)) == NULL){
clicon_err(OE_UNIX, errno, "cvec_new");
goto done;
}
/* Note, regexps are not copied since they are voids, if they were, they
* could not be freed in a simple way since copies are made at augment/group
*/
if ((ret = yang_type_cache_get(yold,
&resolved, &options, &cvv, patterns, NULL, NULL, &fraction)) < 0)
goto done;
if (ret == 1 &&
yang_type_cache_set(ynew, resolved, options, cvv, patterns, fraction) < 0)
goto done;
retval = 0;
done:
if (patterns)
cvec_free(patterns);
return retval;
}
/*! Free yang type cache
*/
static int
yang_type_cache_free(yang_type_cache *ycache)
{
cg_var *cv;
void *p;
if (ycache->yc_cvv)
cvec_free(ycache->yc_cvv);
if (ycache->yc_patterns)
cvec_free(ycache->yc_patterns);
if (ycache->yc_regexps){
cv = NULL;
while ((cv = cvec_each(ycache->yc_regexps, cv)) != NULL){
/* need to store mode since clicon_handle is not available */
switch (ycache->yc_rxmode){
case REGEXP_POSIX:
cligen_regex_posix_free(cv_void_get(cv));
if ((p = cv_void_get(cv)) != NULL){
free(p);
cv_void_set(cv, NULL);
}
break;
case REGEXP_LIBXML2:
cligen_regex_libxml2_free(cv_void_get(cv));
/* Note, already freed in libxml2 case */
if ((p = cv_void_get(cv)) != NULL){
cv_void_set(cv, NULL);
}
break;
default:
break;
}
}
cvec_free(ycache->yc_regexps);
}
free(ycache);
return 0;
}
/*! Add a simple anydata-node
*
* One usecase is CLICON_YANG_UNKNOWN_ANYDATA when unknown data is treated as anydata
* @param[in] yp Yang parent statement
* @param[in] name Node name, will be copied
* @retval ys OK
* @retval NULL Error
* @see ysp_add
*/
yang_stmt *
yang_anydata_add(yang_stmt *yp,
char *name0)
{
yang_stmt *ys = NULL;
char *name = NULL;
if ((ys = ys_new(Y_ANYDATA)) == NULL)
goto done;
if ((name = strdup(name0)) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
yang_argument_set(ys, name);
if (yn_insert(yp, ys) < 0){ /* Insert into hierarchy */
ys = NULL;
goto done;
}
done:
return ys;
}
/*! Find extension argument and return extension argument value
* @param[in] ys Yang statement
* @param[in] name Name of the extension
* @param[in] ns The namespace
* @param[out] value clispec operator (hide/none) - direct pointer into yang, dont free
* This is for extensions with an argument
* @code
* char *value = NULL;
* if (yang_extension_value(ys, "mymode", "urn:example:lib", &value) < 0)
* err;
* if (value != NULL){
* // use extension value
* }
* @endcode
*/
int
yang_extension_value(yang_stmt *ys,
char *name,
char *ns,
char **value)
{
int retval = -1;
yang_stmt *yext;
yang_stmt *ymod;
cg_var *cv;
char *prefix = NULL;
cbuf *cb = NULL;
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
yext = NULL; /* This loop gets complicated in trhe case the extension is augmented */
while ((yext = yn_each(ys, yext)) != NULL) {
if (yang_keyword_get(yext) != Y_UNKNOWN)
continue;
if ((ymod = ys_module(yext)) == NULL)
continue;
if (yang_find_prefix_by_namespace(ymod, ns, &prefix) < 0)
goto ok;
cprintf(cb, "%s:%s", prefix, name);
if (strcmp(yang_argument_get(yext), cbuf_get(cb)) != 0)
continue;
break;
}
if (yext != NULL){ /* Found */
if ((cv = yang_cv_get(yext)) == NULL)
goto ok;
if (value)
*value = cv_string_get(cv);
}
ok:
retval = 0;
done:
if (cb)
cbuf_free(cb);
return retval;
}
#ifdef XML_EXPLICIT_INDEX
/*! Mark element as search_index in list
* @retval 0 OK
* @retval -1 Error
*/
int
yang_list_index_add(yang_stmt *ys)
{
int retval = -1;
yang_stmt *yp;
if ((yp = yang_parent_get(ys)) == NULL ||
yang_keyword_get(yp) != Y_LIST){
clicon_log(LOG_WARNING, "search_index should in a list");
goto ok;
}
yang_flag_set(ys, YANG_FLAG_INDEX);
ok:
retval = 0;
// done:
return retval;
}
/*! Callback for yang clixon search_index extension
*
* @param[in] h Clixon handle
* @param[in] yext Yang node of extension
* @param[in] ys Yang node of (unknown) statement belonging to extension
* @retval 0 OK (warnings may appear)
* @retval -1 Error
*/
int
yang_search_index_extension(clicon_handle h,
yang_stmt *yext,
yang_stmt *ys)
{
int retval = -1;
char *extname;
char *modname;
yang_stmt *ymod;
yang_stmt *yp;
ymod = ys_module(yext);
modname = yang_argument_get(ymod);
extname = yang_argument_get(yext);
if (strcmp(modname, "clixon-config") != 0 || strcmp(extname, "search_index") != 0)
goto ok;
clicon_debug(1, "%s Enabled extension:%s:%s", __FUNCTION__, modname, extname);
yp = yang_parent_get(ys);
if (yang_list_index_add(yp) < 0)
goto done;
ok:
retval = 0;
done:
return retval;
}
#endif /* XML_EXPLICIT_INDEX */