clixon/lib/src/clixon_yang.c
Olof Hagsand b3dcee9639 * YANG schema mount RFC 8528, Initial commit (work in progress)
* Keep track of YANG unknowns with ys_cvec of EXTENSION
* C-API: Init ys_cvec to NULL, added yang_cvec_add() and adjusted code to use it
2023-01-20 16:16:02 +01:00

3983 lines
118 KiB
C

/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand
Copyright (C) 2020-2022 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>
/* 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_sub_parse.h"
#include "clixon_yang_parse_lib.h"
#include "clixon_yang_cardinality.h"
#include "clixon_yang_type.h"
#include "clixon_yang_schema_mount.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 <--> clixon 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
* @note To add entries, use yang_cvec_add(), since this function may return NULL
*/
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;
}
/*! Add new value to yang cvec, create if not exist
*
* @param[in] ys Yang statement
* @param[in] type Append a new cv to the vector with this type
* @param[in] name Name of variable
* @retval cv The new cligen variable
* @retval NULL Error
*/
static cg_var *
yang_cvec_add(yang_stmt *ys,
enum cv_type type,
char *name)
{
cg_var *cv;
cvec *cvv;
if ((cvv = yang_cvec_get(ys)) == NULL){
if ((cvv = cvec_new(0)) == NULL){
clicon_err(OE_YANG, errno, "cvec_new");
return NULL;
}
yang_cvec_set(ys, cvv);
}
if ((cv = cvec_add(cvv, type)) == NULL){
clicon_err(OE_YANG, errno, "cvec_add");
return NULL;
}
if (cv_name_set(cv, name) == NULL){
clicon_err(OE_YANG, errno, "cv_name_set(%s)", name);
return NULL;
}
return cv;
}
/*! 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 */
/* Stats */
static uint64_t _stats_yang_nr = 0;
/*! Get global statistics about YANG statements: created - freed
*
* @param[out] nr Number of existing YANG objects (created - freed)
*/
int
yang_stats_global(uint64_t *nr)
{
if (nr)
*nr = _stats_yang_nr;
return 0;
}
/*! Return the alloced memory of a single YANG obj
* @param[in] y YANG object
* @param[out] szp Size of this YANG obj
* @retval 0 OK
* (baseline: )
*/
static int
yang_stats_one(yang_stmt *y,
size_t *szp)
{
size_t sz = 0;
yang_type_cache *yc;
sz += sizeof(struct yang_stmt);
sz += y->ys_len*sizeof(struct yang_stmt*);
if (y->ys_argument)
sz += strlen(y->ys_argument) + 1;
if (y->ys_cv)
sz += cv_size(y->ys_cv);
if (y->ys_cvec)
sz += cvec_size(y->ys_cvec);
if ((yc = y->ys_typecache) != NULL){
sz += sizeof(struct yang_type_cache);
if (yc->yc_cvv)
sz += cvec_size(yc->yc_cvv);
if (yc->yc_patterns)
sz += cvec_size(yc->yc_patterns);
if (yc->yc_regexps)
sz += cvec_size(yc->yc_regexps);
}
if (y->ys_when_xpath)
sz += strlen(y->ys_when_xpath) + 1;
if (y->ys_when_nsc)
sz += cvec_size(y->ys_when_nsc);
if (y->ys_filename)
sz += strlen(y->ys_filename) + 1;
if (szp)
*szp = sz;
return 0;
}
/*! Return statistics of an YANG-stmt tree recursively
* @param[in] yt YANG object
* @param[out] nrp Number of YANG objects recursively
* @param[out] szp Size of this YANG stmt recursively
* @retval 0 OK
* @retval -1 Error
*/
int
yang_stats(yang_stmt *yt,
uint64_t *nrp,
size_t *szp)
{
int retval = -1;
size_t sz = 0;
yang_stmt *ys;
if (yt == NULL){
clicon_err(OE_XML, EINVAL, "yang spec is NULL");
goto done;
}
*nrp += 1;
yang_stats_one(yt, &sz);
if (szp)
*szp += sz;
ys = NULL;
while ((ys = yn_each(yt, ys)) != NULL) {
sz = 0;
yang_stats(ys, nrp, &sz);
if (szp)
*szp += sz;
}
retval = 0;
done:
return retval;
}
/* stats end */
/*! 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;
_stats_yang_nr++;
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;
_stats_yang_nr++;
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;
rpc_callback_t *rc;
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);
while((rc = ys->ys_action_cb) != NULL) {
DELQ(rc, ys->ys_action_cb, rpc_callback_t *);
if (rc->rc_namespace)
free(rc->rc_namespace);
if (rc->rc_name)
free(rc->rc_name);
free(rc);
}
if (self){
free(ys);
_stats_yang_nr--;
}
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
* @see ys_prune_self Prune child itself
* @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)
*/
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 all yang children tree recursively
* @param[in] ys Yang node to its children recursively
*/
static int
ys_freechildren(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->ys_len = 0;
if (ys->ys_stmt){
free(ys->ys_stmt);
ys->ys_stmt = NULL;
}
return 0;
}
/*! 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)
{
ys_freechildren(ys);
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 are 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. Actual type: enum rfc_6020
* @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 Argument that child should match with
* @retval ymatch Matching child
* @retval NULL No match or error
*
* @see yang_find Looks for any node
* @note May deviate from RFC since it explores choice/case not just return it.
* XXX: differentiate between not found and error
*/
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;
#ifdef YANG_SCHEMA_MOUNT
int ret;
/* Sanity-check mount-point extension */
if ((ret = yang_schema_mount_point(yn)) < 0)
goto done;
if (ret == 1){
; // NYI
}
#endif
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 (yc->ys_argument && strcmp(argument, yc->ys_argument) == 0)
ysmatch = yc;
}
if (ysmatch)
goto done; // maybe break?
}
} /* Y_CHOICE */
else if (yang_keyword_get(ys) == Y_INPUT ||
yang_keyword_get(ys) == Y_OUTPUT){ /* Look for its children */
if ((ysmatch = yang_find_datanode(ys, argument)) != NULL)
break;
}
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 done; // maybe break?
}
}
/* 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;
}
}
}
done:
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
* @see yang_abs_schema_nodeid Top level function
*/
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 (strcmp(argument, "input") == 0 && yang_keyword_get(ys) == Y_INPUT)
ysmatch = ys;
else if (strcmp(argument, "output") == 0 && 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, or module itself, 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 Namespace URI as char* pointer into yang tree
* @param[out] prefix Local prefix to access module with (direct pointer)
* @retval -1 Error
* @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 ((found = yang_find_prefix_by_namespace(ys, "urn:example:clixon", &prefix)) < 0)
* err;
* if (found)
* // use prefix
* @endcode
* @see yang_find_module_by_namespace
*/
int
yang_find_prefix_by_namespace(yang_stmt *ys,
char *ns,
char **prefix)
{
int retval = -1;
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__);
if (prefix == NULL){
clicon_err(OE_YANG, EINVAL, "prefix is NULL");
goto done;
}
/* 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:
retval = 0; /* not found */
done:
return retval;
found:
assert(*prefix);
retval = 1;
goto done;
}
/*! Given a yang statement and local prefix valid in module, find namespace
*
* @param[in] ys Yang statement in module tree (or module itself)
* @param[in] prefix Local prefix to access module with (direct pointer)
* @param[out] ns Namespace URI as char* pointer into yang tree
* @retval -1 Error
* @retval 0 Not found
* @retval 1 Found
* @note prefix NULL is not returned, if own module, then return its prefix
* @code
* char *ns = NULL;
* if ((found = yang_find_namespace_by_prefix(ys, "ex", &ns)) < 0)
* err;
* if (found)
* // use ns *
* @endcode
* @see yang_find_module_by_prefix
*/
int
yang_find_namespace_by_prefix(yang_stmt *ys,
char *prefix,
char **ns)
{
int retval = -1;
yang_stmt *ym;
if (ns == NULL){
clicon_err(OE_YANG, EINVAL, "ns is NULL");
goto done;
}
if ((ym = yang_find_module_by_prefix(ys, prefix)) == NULL)
goto notfound;
if ((*ns = yang_find_mynamespace(ym)) == NULL)
goto notfound;
retval = 1; /* found */
done:
return retval;
notfound:
retval = 0;
goto done;
}
/*! 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;
}
/*! Get closest yang case and choice, if any
*/
int
choice_case_get(yang_stmt *yc,
yang_stmt **ycase,
yang_stmt **ychoice)
{
yang_stmt *yp;
if ((yp = yang_parent_get(yc)) == NULL)
return 0;
if (yang_keyword_get(yp) == Y_CASE){
if (ycase)
*ycase = yp;
*ychoice = yang_parent_get(yp);
return 1;
}
else if (yang_keyword_get(yp) == Y_CHOICE){
if (ycase)
*ycase = NULL;
*ychoice = yp;
return 1;
}
return 0;
}
/*! 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
yang_order1_choice(yang_stmt *yp,
yang_stmt *y,
int *index)
{
yang_stmt *ys;
yang_stmt *yc;
int i;
int j;
int max=0;
int index0;
index0 = *index;
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 */
*index = index0;
for (j=0; j<ys->ys_len; j++){
yc = ys->ys_stmt[j];
if (yc->ys_keyword == Y_CHOICE){
if (yang_order1_choice(yc, y, index) == 1) /* If one of the choices is "y" */
return 1;
}
else {
if (yang_datanode(yc) && yc == y){
// *index = index0 + j;
return 1;
}
(*index)++;
}
}
if (*index-index0 > max)
max = *index-index0;
}
else {
max = 1; /* Shortcut, no case */
if (yang_datanode(ys) && ys == y)
return 1;
}
}
*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 OK: found
* @retval -1 Error: not found (must be datanode)
*/
static int
yang_order1(yang_stmt *yp,
yang_stmt *y,
int *index)
{
int retval = -1;
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 (yang_order1_choice(ys, y, index) == 1) /* If one of the choices is "y" */
break;
}
else {
if (!yang_datanode(ys) &&
yang_keyword_get(ys) != Y_ACTION) /* action is special case */
continue;
if (ys == y)
break;
(*index)++;
}
}
if (i < yp->ys_len) /* break -> found */
retval = 0;
else
assert(0); // XXX
return retval;
}
/*! 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 No spec, y is NULL, which applies to eg attributes and are placed first
* @retval -2 Error: Not found
* @note special handling if y is child of (sub)module
*/
int
yang_order(yang_stmt *y)
{
int retval = -2;
yang_stmt *yp;
yang_stmt *ypp;
yang_stmt *ym;
int i;
int j=0;
int tot = 0;
if (y == NULL){
retval = -1;
goto done;
}
/* 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 (yang_order1(yp, y, &j) < 0){
clicon_err(OE_YANG, 0, "YANG node %s not ordered: not found", yang_argument_get(y));
goto done;
}
retval = tot + j;
done:
return retval;
}
/*! Map from YANG keywords ints to strings
* @param[in] int Integer representation of YANG keywords
* @retval str String representation of YANG keywords
*/
char *
yang_key2str(int keyword)
{
return (char*)clicon_int2str(ykmap, keyword);
}
/*! Map from yang keyword strings to ints
* @param[in] str String representation of YANG keywords
* @retval int Integer representation of YANG keywords
*/
int
yang_str2key(char *str)
{
return clicon_str2int(ykmap, str);
}
/*! 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
* @retval 0 OK
* @retval -1 Error
* @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, 1) < 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);
}
/*! Print yang top-level modules only
* @param[in] f File to print to.
* @param[in] yn Yang node to print
* @see yang_print_cbuf
*/
int
yang_spec_print(FILE *f,
yang_stmt *yspec)
{
yang_stmt *ym = NULL;
yang_stmt *yrev;
if (yspec == NULL || yang_keyword_get(yspec) != Y_SPEC){
clicon_err(OE_YANG, EINVAL, "yspec is not of type YSPEC");
return -1;
}
while ((ym = yn_each(yspec, ym)) != NULL) {
fprintf(f, "%s", yang_key2str(ym->ys_keyword));
fprintf(f, " %s", ym->ys_argument);
if ((yrev = yang_find(ym, Y_REVISION, NULL)) != NULL){
fprintf(f, "@%u", cv_uint32_get(yang_cv_get(yrev)));
}
fprintf(f, ".yang");
fprintf(f, "\n");
}
return 0;
}
/* 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.
* @param[in] pretty Pretty-print output
* @retval 0 OK
* @retval -1 Error
* @code
* cbuf *cb = cbuf_new();
* yang_print_cbuf(cb, yn, 0, 1);
* // output is in cbuf_buf(cb);
* cbuf_free(cb);
* @endcode
*/
int
yang_print_cbuf(cbuf *cb,
yang_stmt *yn,
int marginal,
int pretty)
{
int retval = -1;
yang_stmt *ys;
enum rfc_6020 keyw;
char *arg;
if (yn == NULL || cb == NULL){
clicon_err(OE_YANG, EINVAL, "cb or yn is NULL");
goto done;
}
keyw = yang_keyword_get(yn);
if (keyw == Y_UNKNOWN){ /* dont print unknown - proxy for extension*/
if (pretty)
cprintf(cb, "%*s", marginal-1, "");
}
else{
if (pretty)
cprintf(cb, "%*s%s", marginal, "", yang_key2str(keyw));
else
cprintf(cb, "%s", yang_key2str(keyw));
}
arg = yang_argument_get(yn);
if (arg){
if (quotedstring(arg))
cprintf(cb, " \"%s\"", arg);
else
cprintf(cb, " %s", arg);
}
if (yang_len_get(yn)){
cprintf(cb, " {");
if (pretty)
cprintf(cb, "\n");
ys = NULL;
while ((ys = yn_each(yn, ys)) != NULL) {
if (yang_print_cbuf(cb, ys, marginal + PRETTYPRINT_INDENT, pretty) < 0)
goto done;
}
if (pretty)
cprintf(cb, "%*s%s\n", marginal, "", "}");
else
cprintf(cb, "}");
}
else{
cprintf(cb, ";");
if (pretty)
cprintf(cb, "\n");
}
retval = 0;
done:
return retval;
}
/*! 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);
ytc = yang_find(ytarget, kw, NULL);
switch (kw){
case Y_CONFIG: /* special case: implicit default is config true */
break;
default:
if (ytc == 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;
}
break;
}
if (ytc){
/* 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, NULL)) < 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;
cvec *cvv;
if ((ykey = yang_find(ys, Y_KEY, NULL)) == NULL)
return 0;
if ((cvv = yang_arg2cvec(ykey, " ")) == NULL)
return -1;
yang_cvec_set(ys, cvv);
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 (ys == NULL){
clicon_err(OE_YANG, EINVAL, "ys is NULL");
goto done;
}
if ((cv = yang_cvec_add(ys, cvtype, name)) == NULL)
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;
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 (yang_cvec_add(ybaseid, CGV_STRING, idref) == NULL){
clicon_err(OE_UNIX, errno, "cv_new");
goto done;
}
/* 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)
{
cvec *cvv;
if ((cvv = yang_arg2cvec(ys, " ")) == NULL)
return -1;
yang_cvec_set(ys, cvv);
return 0;
}
/*! Populate unknown node with extension
*
* @param[in] h Clicon handle
* @param[in] ys The yang statement (unknown) to populate.
* @retval 0 OK
* @retval -1 Error
* RFC 7950 Sec 7.19:
* If no "argument" statement is present, the keyword expects no argument when
* it is used.
* A pointer to the unknwon node is stored in the cvec of the extension:
* 1 n
* yang-extension ext1 (cvec) ---> unknown of ext1
* (yext) (ys)
*
* @note this breaks if yangs are freed
* Note there is some complexity in different yang modules with unknown-statements.
* y0) The location of the extension definition. E.g. extension autocli-op
* y1) The location of the unknown-statement (ys). This is for example: cl:autocli-op hide.
* Lookup of "cl" is lexically scoped in this context (to find (y0)).
* y2) The unknown statement may be used by uses/grouping of (y1). But "cl" is declared
* in y1 context. This is fixed by setting ys_mymodule to y1 which is then used in
* lookups such as in yang_extension_value().
* @see yang_extension_value Called on expanded YANG, eg in context of (y2)
*/
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;
}
/* To find right binding eg after grouping/uses */
ys->ys_mymodule = ys_module(ys);
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
#ifdef YANG_SCHEMA_MOUNT
if (yang_schema_unknown(h, yext, ys) < 0)
goto done;
#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;
#if 1
/* Add unknown to vector in extension */
if ((cv = yang_cvec_add(yext, CGV_VOID, yang_argument_get(ys))) == NULL)
goto done;
cv_void_set(cv, ys);
#endif
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;
}
/*! 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 0 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-features is parsed in full context here, previous restricted pass in ys_parse_sub
*/
int
yang_features(clicon_handle h,
yang_stmt *yt)
{
int retval = -1;
int i;
int j;
yang_stmt *ys = NULL;
yang_stmt *ymod;
const char *mainfile = NULL;
int ret;
i = 0;
while (i<yt->ys_len){
ys = yt->ys_stmt[i];
if (ys->ys_keyword == Y_IF_FEATURE){
/* Parse the if-feature-expr string using yang sub-parser */
if ((ymod = ys_module(ys)) != NULL)
mainfile = yang_filename_get(ymod);
ret = 0;
if (yang_subparse(yang_argument_get(ys), ys, YA_IF_FEATURE, mainfile, 1, &ret) < 0)
goto done;
clicon_debug(2, "%s %s %d", __FUNCTION__, yang_argument_get(ys), ret);
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 */
/* Change datanodes YANG to ANYDATA, other nodes are removed
*/
if (yang_datanode(ys) && yang_config_ancestor(ys)){
ys->ys_keyword = Y_ANYDATA;
ys_freechildren(ys);
ys->ys_len = 0;
yang_flag_set(ys, YANG_FLAG_DISABLED);
break;
}
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] yspec Yang spec (Cornsercase if yn is pruned)
* @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,
yang_stmt *yspec,
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;
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, last id found:%s",
__FUNCTION__, id, yang_argument_get(yp));
goto ok;
}
yp = ys; /* ys is matched */
ys = NULL;
} /* 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;
if ((yspec = ys_spec(yn)) == NULL){
clicon_err(OE_YANG, EINVAL, "No yang spec");
goto done;
}
*yres = NULL;
/* 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, yspec, 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 (must be part of yspec tree, cannot be "dangling")
* @param[in] schema_nodeid Descendant schema-node-id, ie a/b
* @param[in] keyword A schemode of this type, or -1 if any
* @param[in] ns Namespace context
* @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;
yang_stmt *yspec;
cvec *nsc = NULL;
if (schema_nodeid == NULL || strlen(schema_nodeid) == 0){
clicon_err(OE_YANG, EINVAL, "nodeid is empty");
goto done;
}
if ((yspec = ys_spec(yn)) == NULL){
clicon_err(OE_YANG, EINVAL, "No yang spec");
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
* Requires yn exist in hierarchy
*/
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, yspec, 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;
}
/*! 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 {
#ifdef USE_CONFIG_FLAG_CACHE
if (yang_flag_get(yp, YANG_FLAG_CONFIG_CACHE))
return yang_flag_get(yp, YANG_FLAG_CONFIG_VALUE)?1:0;
#endif
if (yang_config(yp) == 0){
#ifdef USE_CONFIG_FLAG_CACHE
yang_flag_set(yp, YANG_FLAG_CONFIG_CACHE);
yang_flag_reset(yp, YANG_FLAG_CONFIG_VALUE);
#endif
return 0;
}
if (yang_keyword_get(yp) == Y_INPUT || yang_keyword_get(yp) == Y_OUTPUT || yang_keyword_get(yp) == Y_NOTIFICATION){
#ifdef USE_CONFIG_FLAG_CACHE
yang_flag_set(yp, YANG_FLAG_CONFIG_CACHE);
yang_flag_reset(yp, YANG_FLAG_CONFIG_VALUE);
#endif
return 0;
}
} while((yp = yang_parent_get(yp)) != NULL);
#ifdef USE_CONFIG_FLAG_CACHE
yang_flag_set(ys, YANG_FLAG_CONFIG_CACHE);
yang_flag_set(ys, YANG_FLAG_CONFIG_VALUE);
#endif
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 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
* @param[out] lastkey If 1 this is the last key in a multi-key list
*
* @retval -1 Error
* @retval 0 No match
* @retval 1 Yes match
*/
int
yang_key_match(yang_stmt *yn,
char *name,
int *lastkey)
{
int retval = -1;
yang_stmt *ys = NULL;
int i;
int j;
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;
j = 0;
cv = NULL;
while ((cv = cvec_each(cvv, cv)) != NULL) {
j++;
if (strcmp(name, cv_string_get(cv)) == 0){
if (j == cvec_len(cvv) && lastkey)
*lastkey = 1;
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 (yp && yn_insert(yp, ys) < 0){ /* Insert into hierarchy */
ys = NULL;
goto done;
}
done:
return ys;
}
/*! Find extension argument and return if extension exists and its argument value
*
* @param[in] ys Yang statement where unknown statement may occur referencing to extension
* @param[in] name Name of the extension
* @param[in] ns The namespace of the module where the extension is defined
* @param[out] exist The extension exists.
* @param[out] value clispec operator (hide/none) - direct pointer into yang, dont free
* @retval 0 OK: Look in exist and value for return value
* @retval -1 Error
* This is for extensions with an argument
* @code
* char *value = NULL;
* int exist = 0;
* if (yang_extension_value(ys, "mymode", "urn:example:lib", &exist, &value) < 0)
* err;
* if (value != NULL){
* // use extension value
* }
* @endcode
* @see ys_populate_unknown Called when parsing YANG
*/
int
yang_extension_value(yang_stmt *ys,
char *name,
char *ns,
int *exist,
char **value)
{
int retval = -1;
yang_stmt *yext;
yang_stmt *ymod;
cg_var *cv;
char *prefix = NULL;
cbuf *cb = NULL;
int ret;
if (ys == NULL){
clicon_err(OE_YANG, EINVAL, "ys is NULL");
goto done;
}
if (exist)
*exist = 0;
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
yext = NULL; /* This loop gets complicated in the 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 ((ret = yang_find_prefix_by_namespace(ymod, ns, &prefix)) < 0)
goto done;
if (ret == 0) /* not found (this may happen in augment and maybe should be treated otherwise) */
continue;
cbuf_reset(cb);
cprintf(cb, "%s:%s", prefix, name);
if (strcmp(yang_argument_get(yext), cbuf_get(cb)) != 0)
continue;
break;
}
if (yext != NULL){ /* Found */
if (exist)
*exist = 1;
if (value &&
(cv = yang_cv_get(yext)) != NULL)
*value = cv_string_get(cv);
}
retval = 0;
done:
if (cb)
cbuf_free(cb);
return retval;
}
/* Sort substatements with when:s last */
static int
yang_sort_subelements_fn(const void* arg1,
const void* arg2)
{
yang_stmt *ys1 = *(yang_stmt **)arg1;
yang_stmt *ys2 = *(yang_stmt **)arg2;
int w1; /* ys1 has when substatement */
int w2;
w1 = yang_find(ys1, Y_WHEN, NULL) != NULL;
w2 = yang_find(ys2, Y_WHEN, NULL) != NULL;
if (w1 == w2)
return ys1->_ys_vector_i - ys2->_ys_vector_i;
else if (w1)
return 1;
else if (w2)
return -1;
else return ys1->_ys_vector_i - ys2->_ys_vector_i;
}
/*! Experimental code for sorting YANG children
*
* RFC 7950 7.5.7 and 7.8.5 says that containers and list sub elements are encoded in any order.
* with some exceptions, eg rpc/action input/output or list key elements
*
* Clixon by default encodes them in yang order. But this function can change that.
* For example, openconfig-network-instances.yang requires subelements with "when" statements last.
*/
int
yang_sort_subelements(yang_stmt *ys)
{
int retval = -1;
yang_stmt *yc = NULL;
if ((yang_keyword_get(ys) == Y_CONTAINER ||
yang_keyword_get(ys) == Y_LIST)){
/* This enumerates _ys_vector_i in ys->ys_stmt vector */
while ((yc = yn_each(ys, yc)) != NULL) ;
qsort(ys->ys_stmt, ys->ys_len, sizeof(ys), yang_sort_subelements_fn);
}
retval = 0;
// done:
return retval;
}
int
yang_init(clicon_handle h)
{
return yang_cardinality_init(h);
}
#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 */
/*! Check if yang node has a single child of specific type
*
* Mainly used for condition for CLI compression
* The yang should be:
* 1) If container it should be non-presence
* 2) parent of a (single) specified type
* 3) no other data node children
* @param[in] ys Yang node
* @param[in] subkeyw Expected keyword of single child (typically Y_LIST)
* @retval 0 No, node does not have single child of specified type
* @retval 1 Yes, node has single child of specified type
* @see https://github.com/openconfig/ygot/blob/master/docs/design.md#openconfig-path-compression 2nd clause
*/
int
yang_single_child_type(yang_stmt *ys,
enum rfc_6020 subkeyw)
{
yang_stmt *yc = NULL;
int i;
enum rfc_6020 keyw;
/* Match parent */
/* If container, check it is Non-presence */
if (yang_keyword_get(ys) == Y_CONTAINER){
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 != subkeyw) /* Another datanode than subkeyw */
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 */
}
/*! Get action callback list
* XXX rc shouldnt really be void* but the type definitions in .h file got complicated
*/
void *
yang_action_cb_get(yang_stmt *ys)
{
return ys->ys_action_cb;
}
/*! Add an action callback to YANG node
* XXX rc shouldnt really be void* but the type definitions in .h file got complicated
*/
int
yang_action_cb_add(yang_stmt *ys,
void *arg)
{
rpc_callback_t *rc = (rpc_callback_t *)arg;
if (rc == NULL){
clicon_err(OE_YANG, EINVAL, "arg is NULL");
return -1;
}
ADDQ(rc, ys->ys_action_cb);
return 0;
}