clixon/lib/src/clixon_yang.c

4390 lines
127 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>
/* clixon */
#include "clixon_string.h"
#include "clixon_map.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_err.h"
#include "clixon_log.h"
#include "clixon_debug.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(clixon_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}
};
/* XXX: Global variables, should really be on handle but not available in some cases */
static map_ptr2ptr *_yang_when_map = NULL;
static map_ptr2ptr *_yang_mymodule_map = NULL;
/* Forward static */
static int yang_type_cache_free(yang_type_cache *ycache);
/* 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, note must be malloced
* 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 original back-pointer if node is grouping or augmented
*
* Defined only if this statement was created as part of a uses/grouping or augment expansion
* @param[in] ys Yang statement node
* @retval y0 Original yang statement node
* @retval NULL Not part of grouping
*/
yang_stmt *
yang_orig_get(yang_stmt *ys)
{
return ys->ys_orig;
}
/*! Set original back-pointer if any
*
* Will recursively evaluate to set to original node in case of chain of groupings
* @param[in] ys Yang statement node
* @param[in] y0 Original yang statement node
* @retval NULL Not part of grouping
*/
int
yang_orig_set(yang_stmt *ys,
yang_stmt *y0)
{
if (y0->ys_orig){
yang_orig_set(ys, y0->ys_orig);
}
else
ys->ys_orig = y0;
return 0;
}
/*! Get yang statement CLIgen variable
*
* See comment under ys_cv for how this is used
* @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
*
* See comment under ys_cvec for how this is used
* @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
*/
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){
clixon_err(OE_YANG, errno, "cvec_new");
return NULL;
}
yang_cvec_set(ys, cvv);
}
if ((cv = cvec_add(cvv, type)) == NULL){
clixon_err(OE_YANG, errno, "cvec_add");
return NULL;
}
if (cv_name_set(cv, name) == NULL){
clixon_err(OE_YANG, errno, "cv_name_set(%s)", name);
return NULL;
}
return cv;
}
/*! Get yang object reference count
*
* @param[in] ys Yang statement
* @retval ref Reference coun t
*/
int
yang_ref_get(yang_stmt *ys)
{
// assert(ys->ys_keyword == Y_SPEC);
return ys->ys_ref;
}
/*! Increment yang object reference count with +1
*
* @param[in] ys Yang statement
* @retval 0
*/
int
yang_ref_inc(yang_stmt *ys)
{
// assert(ys->ys_keyword == Y_SPEC);
ys->ys_ref++;
return 0;
}
/*! Decrement yang object reference count with -1
*
* @param[in] ys Yang statement
* @retval 0 Ok
* @retval -1 Error
*/
int
yang_ref_dec(yang_stmt *ys)
{
int retval = -1;
// assert(ys->ys_keyword == Y_SPEC);
if (ys->ys_ref > 0)
ys->ys_ref--;
retval = 0;
// done:
return retval;
}
/*! 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 when statement from external map
*
* @param[in] h Clixon handle (may be NULL)
* @param[in] ys Yang statement
* @retval ywhen Yang when statement
* @retval NULL No yang when
* @note h may be NULL since may not be available by caller
*/
yang_stmt *
yang_when_get(clixon_handle h,
yang_stmt *ys)
{
map_ptr2ptr *mp = _yang_when_map;
if (mp == NULL){
clixon_log(h, LOG_WARNING, "when_map not defined, yang_init() not called?");
return NULL;
}
else {
if (yang_flag_get(ys, YANG_FLAG_WHEN) != 0x0 && mp != NULL)
return clixon_ptr2ptr(mp, ys);
}
return NULL;
}
/*! Set Yang when statement to external map
*
* @param[in] h Clixon handle
* @param[in] ys Yang statement
* @param[int] ywhen Yang when statement
* @retval 0 OK
* @retval -1 Error
*/
int
yang_when_set(clixon_handle h,
yang_stmt *ys,
yang_stmt *ywhen)
{
int retval = -1;
map_ptr2ptr *mp = _yang_when_map;
if (mp == NULL){
clixon_log(h, LOG_WARNING, "when_map not defined, yang_init() not called?");
goto done;
}
else {
if (clixon_ptr2ptr(mp, ys) != NULL) {
clixon_err(OE_YANG, 0, "when pointer already set");
goto done;
}
if (clixon_ptr2ptr_add(&_yang_when_map, ys, ywhen) < 0)
goto done;
yang_flag_set(ys, YANG_FLAG_WHEN);
}
retval = 0;
done:
return retval;
}
/*! 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)
{
yang_stmt *ywhen;
if ((ywhen = yang_when_get(NULL, ys)) != NULL)
return yang_argument_get(ywhen);
return NULL;
}
/*! 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 (caller frees with cvec_free)
* @note retval is direct pointer, may need to be copied
*/
cvec *
yang_when_nsc_get(yang_stmt *ys)
{
yang_stmt *ywhen;
cvec *wnsc = NULL;
if ((ywhen = yang_when_get(NULL, ys)) != NULL) {
if (xml_nsctx_yang(ywhen, &wnsc) < 0)
wnsc = NULL;
}
return wnsc;
}
/*! Get yang filename for error/debug purpose (only modules)
*
* @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)
{
// assert(ys->ys_keyword == Y_MODULE || ys->ys_keyword == Y_SUBMODULE);
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)
{
// assert(ys->ys_keyword == Y_MODULE || ys->ys_keyword == Y_SUBMODULE);
if ((ys->ys_filename = strdup(filename)) == NULL){
clixon_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
*/
uint32_t
yang_linenum_get(yang_stmt *ys)
{
#ifdef YANG_SPEC_LINENR
return ys->ys_linenum;
#else
return 0;
#endif
}
/*! 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,
uint32_t linenum)
{
#ifdef YANG_SPEC_LINENR
ys->ys_linenum = linenum;
#endif
return 0;
}
/*! Get type cache
*
* @param[in] ys Yang statement
* @retval ycache
*/
void *
yang_typecache_get(yang_stmt *ys)
{
return ys->ys_typecache;
}
/*! Set type cache
*
* @param[in] ys Yang statement
* @param[in] ycache Type cache
* @retval 0 OK
*/
int
yang_typecache_set(yang_stmt *ys,
void *ycache)
{
ys->ys_typecache = ycache;
return 0;
}
/*! Get mymodule
*
* Shortcut to "my" module. Used by augmented and unknown nodes
* @param[in] ys YANG statement
* @retval ymod YANG module
*/
yang_stmt*
yang_mymodule_get(yang_stmt *ys)
{
map_ptr2ptr *mp = _yang_mymodule_map;
if (mp == NULL){
clixon_log(NULL, LOG_WARNING, "mymodule_map not defined, yang_init() not called?");
return NULL;
}
else if (yang_flag_get(ys, YANG_FLAG_MYMODULE) == 0x0)
return NULL;
else
return clixon_ptr2ptr(mp, ys);
}
/*! Set mymodule
*
* Shortcut to "my" module. Used by augmented and unknown nodes
* @param[in] ys YANG statement
* @param[in] ymod YANG module
* @retval 0 OK
* @retval -1 Error
*/
int
yang_mymodule_set(yang_stmt *ys,
yang_stmt *ym)
{
int retval = -1;
map_ptr2ptr *mp = _yang_mymodule_map;
if (mp == NULL){
clixon_log(NULL, LOG_WARNING, "mymodule_map not defined, yang_init() not called?");
goto done;
}
else {
if (clixon_ptr2ptr(mp, ys) == NULL) {
if (clixon_ptr2ptr_add(&_yang_mymodule_map, ys, ym) < 0)
goto done;
}
}
yang_flag_set(ys, YANG_FLAG_MYMODULE);
retval = 0;
done:
return retval;
}
/* 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 *ys,
size_t *szp)
{
size_t sz = 0;
yang_type_cache *yc;
sz += sizeof(struct yang_stmt);
sz += ys->ys_len*sizeof(struct yang_stmt*);
if (ys->ys_argument)
sz += strlen(ys->ys_argument) + 1;
if (ys->ys_cvec)
sz += cvec_size(ys->ys_cvec);
switch (ys->ys_keyword) {
case Y_TYPE:
if ((yc = yang_typecache_get(ys)) != 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);
}
break;
case Y_MODULE:
case Y_SUBMODULE:
if (ys->ys_filename)
sz += strlen(ys->ys_filename) + 1;
break;
default:
break;
}
if (szp)
*szp = sz;
return 0;
}
/*! Return statistics of an YANG-stmt tree recursively
*
* @param[in] yt YANG object
* @param[in] keyw YANG keyword, or 0 for all
* @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,
enum rfc_6020 keyw,
uint64_t *nrp,
size_t *szp)
{
int retval = -1;
size_t sz = 0;
yang_stmt *ys;
int inext;
if (yt == NULL){
clixon_err(OE_XML, EINVAL, "yang spec is NULL");
goto done;
}
if (keyw == 0 || yang_keyword_get(yt) == keyw){
*nrp += 1;
yang_stats_one(yt, &sz);
if (szp)
*szp += sz;
}
inext = 0;
while ((ys = yn_iter(yt, &inext)) != NULL) {
sz = 0;
yang_stats(ys, keyw, 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){
clixon_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 given size
*
* Size parameter for variable size, eg extended YANG struct
* @param[in] keyw Yang stmt keyword
* @param[in] sz Size of created struct
* @retval ys New yang-stmt. Free with ys_free()
* @retval NULL Error
*/
static yang_stmt *
ys_new_sz(enum rfc_6020 keyw,
size_t sz)
{
yang_stmt *ys;
if ((ys = malloc(sz)) == NULL){
clixon_err(OE_YANG, errno, "malloc");
return NULL;
}
memset(ys, 0, sz);
ys->ys_keyword = keyw;
_stats_yang_nr++;
return ys;
}
/*! Create new yang node/statement
*
* @param[in] keyw Yang stmt keyword
* @retval ys New yang-stmt. Free with ys_free()
* @retval NULL Error
* @note UNKNOWN nodes are always extended
*/
yang_stmt *
ys_new(enum rfc_6020 keyw)
{
return ys_new_sz(keyw, sizeof(struct yang_stmt));
}
/*! Free a single yang statement, dont remove children, called after children freed
*
* @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 Also free children
*/
int
ys_free1(yang_stmt *ys,
int self)
{
rpc_callback_t *rc;
cg_var *cv;
if ((cv = ys->ys_cv) != NULL){ // To not trigger asserts
ys->ys_cv = NULL;
cv_free(cv);
}
if (ys->ys_cvec){
/* Schema mount uses cvec in unknown to keep track of all yspecs
* Freed here once.
*/
if (yang_flag_get(ys, YANG_FLAG_MOUNTPOINT))
yang_mount_freeall(ys);
cvec_free(ys->ys_cvec);
ys->ys_cvec = NULL;
}
if (ys->ys_argument){
free(ys->ys_argument);
ys->ys_argument = NULL;
}
if (ys->ys_stmt)
free(ys->ys_stmt);
switch (ys->ys_keyword) { /* type-specifi union fields */
case Y_ACTION:
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);
}
break;
case Y_TYPE:
if (ys->ys_typecache){
yang_type_cache_free(ys->ys_typecache);
ys->ys_typecache = NULL;
}
break;
case Y_MODULE:
case Y_SUBMODULE:
if (ys->ys_filename)
free(ys->ys_filename);
break;
default:
break;
}
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 yc returned orphaned yang node
* @retval NULL No such node, nothing done
* @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;
int inext;
if ((yp = yang_parent_get(ys)) != NULL){
i = 0;
inext = 0;
/* Find order of ys in child-list */
while ((yc = yn_iter(yp, &inext)) != 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)
{
yang_stmt *yc;
int i;
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
* @note special case of keeping track of reference counts if yang-spec
*/
int
ys_free(yang_stmt *ys)
{
if (yang_keyword_get(ys) == Y_SPEC &&
yang_ref_get(ys) > 0){
yang_ref_dec(ys);
}
else {
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){
clixon_err(OE_YANG, errno, "realloc");
return -1;
}
yn->ys_stmt[yn->ys_len - 1] = NULL; /* init field */
return 0;
}
#ifdef YANG_ORIG_PTR_SKIP
/*! Return 1 if yang stmt should be skipped in derived trees
*
* @param[in] keyword YANG keyword
* @retval 1 Yes, use orig-ptr if exists to fetch original object
* @retval 0 No, use existing object
* Comments includes for:
* - nodes that could not be skipped and the failed test
* - nodes that have children in turn
* - nodes that can be refined
* @note RFC 7950 Sec 7.13.2. The "refine" Statement can change the following nodes
* - default values
* - description
* - reference
* - config
* - mandatory
* - presence
* - min/max-elements
* - if-feature
*/
static int
uses_orig_ptr(enum rfc_6020 keyword)
{
return
// keyword == Y_CONFIG // NO (test_openconfig.sh) + refine
// keyword == Y_DEFAULT // NO (test_augment.sh) + refine
keyword == Y_DESCRIPTION // XXX refine
|| keyword == Y_ENUM // children
|| keyword == Y_ERROR_APP_TAG
|| keyword == Y_ERROR_MESSAGE
|| keyword == Y_FRACTION_DIGITS
// || keyword == Y_KEY // NO
|| keyword == Y_LENGTH // children
|| keyword == Y_MANDATORY // XXX refine
|| keyword == Y_MAX_ELEMENTS // XXX refine
|| keyword == Y_MIN_ELEMENTS // XXX refine
|| keyword == Y_MODIFIER
|| keyword == Y_ORDERED_BY
|| keyword == Y_PATH
|| keyword == Y_PATTERN // children
|| keyword == Y_POSITION
|| keyword == Y_PREFIX
|| keyword == Y_PRESENCE // XXX refine
|| keyword == Y_RANGE // children
|| keyword == Y_REQUIRE_INSTANCE
|| keyword == Y_STATUS
|| keyword == Y_UNIQUE
|| keyword == Y_UNITS
// || keyword == Y_UNKNOWN // NO (test_snmp_ifmib.sh)
|| keyword == Y_VALUE
|| keyword == Y_YIN_ELEMENT
|| keyword == Y_WHEN // children
;
}
#endif /* YANG_ORIG_PTR_SKIP */
/*! Copy single yang statement no children
*
* @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_one(new, old) < 0)
* err;
* @endcode
* @see ys_cp for recursive copy
*/
int
ys_cp_one(yang_stmt *ynew,
yang_stmt *yold)
{
int retval = -1;
cvec *cvv;
cg_var *cvn;
cg_var *cvo;
size_t sz;
sz = sizeof(*yold);
memcpy(ynew, yold, sz);
yang_flag_reset(ynew, YANG_FLAG_WHEN); /* Dont inherit WHENs */
ynew->ys_parent = NULL;
if (yold->ys_stmt)
if ((ynew->ys_stmt = calloc(yold->ys_len, sizeof(yang_stmt *))) == NULL){
clixon_err(OE_YANG, errno, "calloc");
goto done;
}
if (yold->ys_argument)
if ((ynew->ys_argument = strdup(yold->ys_argument)) == NULL){
clixon_err(OE_YANG, errno, "strdup");
goto done;
}
if ((cvo = yold->ys_cv) != NULL){ // Note direct access to avoid wrappings
yang_cv_set(ynew, NULL);
if ((cvn = cv_dup(cvo)) == NULL){
clixon_err(OE_YANG, errno, "cv_dup");
goto done;
}
yang_cv_set(ynew, cvn);
}
if ((cvv = yold->ys_cvec) != NULL){ // Note direct access to avoid wrappings
ynew->ys_cvec = NULL; // Note direct access to avoid cvec_free
if ((cvv = cvec_dup(cvv)) == NULL){
clixon_err(OE_YANG, errno, "cvec_dup");
goto done;
}
yang_cvec_set(ynew, cvv);
}
switch (yold->ys_keyword) { /* type-specifi union fields */
case Y_TYPE:
if (yang_typecache_get(yold)) /* Dont copy type cache, use only original */
yang_typecache_set(ynew, NULL);
break;
default:
break;
}
if (yang_flag_get(yold, YANG_FLAG_MYMODULE) != 0x0)
yang_mymodule_set(ynew, yang_mymodule_get(yold));
retval = 0;
done:
return retval;
}
/*! 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;
int j;
yang_stmt *ycn; /* new child */
yang_stmt *yco; /* old child */
if (ys_cp_one(ynew, yold) < 0)
goto done;
for (i=0,j=0; i<yold->ys_len; i++){
yco = yold->ys_stmt[i];
#ifdef YANG_ORIG_PTR_SKIP
if (uses_orig_ptr(yang_keyword_get(yco))) {
ynew->ys_len--;
continue;
}
#endif
if ((ycn = ys_dup(yco)) == NULL)
goto done;
ynew->ys_stmt[j++] = 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 (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 */
yp = yang_parent_get(yorig);
/* Remove old yangs all children */
ys_freechildren(yorig);
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 using an integer iterator
*
* @param[in] yparent yang statement whose children are iterated
* @param[in,out] inext Iterator, or 0 on init
* @code
* yang_stmt *yprev;
* int inext = 0;
* while ((ynext = yn_iter(yparent, &inext)) != NULL) {
* ...ynext...
* }
* @endcode
* @see yn_each
*/
yang_stmt *
yn_iter(yang_stmt *yparent,
int *inext)
{
yang_stmt *yc = NULL;
if (yparent == NULL || *inext < 0 || (*inext)>=yparent->ys_len)
return NULL;
for (; (*inext)<yparent->ys_len;){
if ((yc = yparent->ys_stmt[*inext]) == NULL){
(*inext)++;
continue;
}
(*inext)++;
break; /* this is next object after previous */
}
return yc;
}
/*! Find first child yang_stmt with matching keyword and argument
*
* Find child given keyword and argument.
* Special case: look in imported INPUTs as well (for (sub)modules.
* This could need an optimized lookup, especially the INPUT case.
* Most common use for the special case, ie in openconfig, is grouping and identity
* @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
* @see yang_find_datanode
*/
yang_stmt *
yang_find(yang_stmt *yn,
int keyword,
const char *argument)
{
yang_stmt *ys = NULL;
int i;
yang_stmt *yret = NULL;
yang_stmt *yretsub = NULL;
char *name;
yang_stmt *yspec;
yang_stmt *ym;
#ifdef YANG_ORIG_PTR_SKIP
yang_stmt *yorig;
if ((yorig = yang_orig_get(yn)) != NULL) {
if (uses_orig_ptr(keyword))
return yang_find(yorig, keyword, argument);
}
#endif
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 (yretsub == NULL &&
yang_keyword_get(ys) == Y_INCLUDE &&
keyword != Y_NAMESPACE &&
(yang_keyword_get(yn) == Y_MODULE ||
yang_keyword_get(yn) == Y_SUBMODULE)){
yspec = ys_spec(yn);
name = yang_argument_get(ys);
if ((ym = yang_find_module_by_name(yspec, name)) != NULL) {
yretsub = yang_find(ym, keyword, argument);
}
}
}
return yret?yret:yretsub;
}
/*! 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;
int inext;
int inext2;
inext = 0;
while ((ys = yn_iter(yn, &inext)) != NULL){
if (yang_keyword_get(ys) == Y_CHOICE){ /* Look for its children */
inext2 = 0;
while ((yc = yn_iter(ys, &inext2)) != 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);
inext = 0;
while ((ys = yn_iter(yn, &inext)) != 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 prefix OK: Prefix as char* pointer into yang tree
* @retval NULL No prefix found. This is an error
* @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){
clixon_err(OE_YANG, ENOENT, "Internal error: no module");
goto done;
}
if ((yprefix = yang_find(ymod, Y_PREFIX, NULL)) == NULL){
clixon_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 ns Namspace URI as char* pointer into yang tree
* @retval NULL Error: No namespace found. This is an error
* @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){
clixon_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 Found
* @retval 0 Not found
* @retval -1 Error
* @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;
int inext;
clixon_debug(CLIXON_DBG_YANG | CLIXON_DBG_DETAIL, "namespace %s", ns);
if (prefix == NULL){
clixon_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 */
inext = 0;
while ((yimport = yn_iter(my_ymod, &inext)) != 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 Found
* @retval 0 Not found
* @retval -1 Error
* @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){
clixon_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;
}
/*! Given a YANG node, return closest yang case and choice, if any
*
* @param[in] yc Yang node
* @param[out] ycase case parent of yc
* @param[out] ychoice choice parent of yc (or grandparent)
* @retval 1 yc has case or choice parent (or both) as returned in ycase/ychoice
* @retval 0 yc has no parent or parent is neither case or choice
*/
int
yang_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
*
*
* @see yang_choice_case_get
*/
yang_stmt *
yang_choice(yang_stmt *y)
{
yang_stmt *yp = NULL;
yang_choice_case_get(y, NULL, &yp);
return yp;
}
/*! 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 1 found
* @retval 0 not found (must be datanode)
* @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){
clixon_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 (yang_mymodule_get(ys)){ /* shortcut due to augment */
ys = yang_mymodule_get(ys);
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){
clixon_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){
clixon_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){
clixon_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){
clixon_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){
clixon_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;
yang_stmt *yrev;
int inext;
if (yspec == NULL || yang_keyword_get(yspec) != Y_SPEC){
clixon_err(OE_YANG, EINVAL, "yspec is not of type YSPEC");
return -1;
}
inext = 0;
while ((ym = yn_iter(yspec, &inext)) != NULL) {
fprintf(f, "%s", yang_key2str(ym->ys_keyword));
fprintf(f, " %s", yang_argument_get(ym));
if ((yrev = yang_find(ym, Y_REVISION, NULL)) != NULL){
fprintf(f, "@%s", yang_argument_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;
yang_stmt *yrev;
cbuf *cb = NULL;
int inext;
if ((cb = cbuf_new()) ==NULL){
clixon_err(OE_YANG, errno, "cbuf_new");
goto done;
}
inext = 0;
while ((ym = yn_iter(yspec, &inext)) != NULL) {
cprintf(cb, "%s", yang_key2str(ym->ys_keyword));
cprintf(cb, " %s", yang_argument_get(ym));
if ((yrev = yang_find(ym, Y_REVISION, NULL)) != NULL){
cprintf(cb, "@%u", cv_uint32_get(yang_cv_get(yrev)));
}
cprintf(cb, ".yang");
clixon_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;
int inext;
if (yn == NULL || cb == NULL){
clixon_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");
inext = 0;
while ((ys = yn_iter(yn, &inext)) != 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 Clixon handle
* @retval 0 OK
* @retval -1 Error
* @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;
clixon_handle h = (clixon_handle)arg;
enum rfc_6020 kw;
int min;
int max;
int inext;
if (yang_keyword_get(ys) != Y_DEVIATION)
goto ok;
/* Absolute schema node identifier identifying target node */
if ((nodeid = yang_argument_get(ys)) == NULL){
clixon_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){
clixon_log(h, LOG_WARNING, "deviation %s: target not found", nodeid);
goto ok;
/* The RFC does not explicitly say the target node MUST exist
clixon_err(OE_YANG, 0, "schemanode sanity check of %s", nodeid);
goto done;
*/
}
/* Go through deviates of deviation */
inext = 0;
while ((yd = yn_iter(ys, &inext)) != 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){
inext = 0;
while ((yc = yn_iter(yd, &inext)) != 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){
clixon_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){
inext = 0;
while ((yc = yn_iter(yd, &inext)) != 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){
clixon_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){
inext = 0;
while ((yc = yn_iter(yd, &inext)) != 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){
clixon_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 */
clixon_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 Clixon handle
* @param[in] ys The yang statement to populate.
* @retval 0 OK
* @retval -1 Error with clixon_err called
*/
static int
ys_populate_leaf(clixon_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 = NULL; /* resolved type */
char *restype; /* resolved type */
char *origtype=NULL; /* original type */
uint8_t fraction_digits = 0;
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?yang_argument_get(yrestype):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){
clixon_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, yang_argument_get(ys)) == NULL){
clixon_err(OE_YANG, errno, "cv_name_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(yang_argument_get(ydef), cv, &reason)) < 0){ /* error */
clixon_err(OE_YANG, errno, "parsing cv");
goto done;
}
if (cvret == 0){ /* parsing failed */
clixon_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(yang_argument_get(ydef), cv, &reason)) < 0){ /* error */
clixon_err(OE_YANG, errno, "parsing cv");
goto done;
}
if (cvret == 0){ /* parsing failed */
clixon_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, yang_argument_get(ys), 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 Clixon handle
* @param[in] ys The yang statement (type) to populate.
* @retval 0 OK
* @retval -1 Error
*/
static int
ys_populate_list(clixon_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
* @retval 0 OK
* @retval -1 Error
*/
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){
clixon_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){
clixon_err(OE_YANG, errno, "cv_parse1");
goto done;
}
if (ret == 0){ /* parsing failed */
clixon_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
*
* @retval 0 OK
* @retval -1 Error
*/
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(yang_argument_get(ys), "|", &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 Clixon handle
* @param[in] ys The yang statement (range) to populate.
* @retval 0 OK
* @retval -1 Error
* 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(clixon_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){
clixon_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){
clixon_err(OE_YANG, 0, "result-type should not be NULL");
goto done;
}
restype = yrestype?yang_argument_get(yrestype):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){
clixon_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 Clixon handle
* @param[in] ys The yang statement (length) to populate.
* @retval 0 OK
* @retval -1 Error
*
* 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(clixon_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){
clixon_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;
}
/*! Assign enum values
*
* @param[in] h Clixon handle
* @param[in] ys The yang statement (type) to populate.
* @retval 0 OK
* @retval -1 Error
*/
static int
ys_populate_type_enum(clixon_handle h,
yang_stmt *ys)
{
int retval = -1;
yang_stmt *yenum = NULL;
yang_stmt *yval;
char *valstr;
int v;
int i = 0;
cg_var *cv = NULL;
int inext;
inext = 0;
while ((yenum = yn_iter(ys, &inext)) != NULL) {
if ((cv = cv_new(CGV_INT32)) == NULL){
clixon_err(OE_YANG, errno, "cv_new");
goto done;
}
if ((yval = yang_find(yenum, Y_VALUE, NULL)) != NULL){
valstr = yang_argument_get(yval);
if (parse_int32(valstr, &v, NULL) < 0)
goto done;
if (v > i)
i = v;
}
cv_int32_set(cv, i++);
yang_cv_set(yenum, cv);
}
retval = 0;
done:
return retval;
}
/*! Sanity check yang type statement
*
* XXX: Replace with generic parent/child type-check
* @param[in] h Clixon handle
* @param[in] ys The yang statement (type) to populate.
* @retval 0 OK
* @retval -1 Error
*/
static int
ys_populate_type(clixon_handle h,
yang_stmt *ys)
{
int retval = -1;
yang_stmt *ybase;
if (strcmp(yang_argument_get(ys), "decimal64") == 0){
if (yang_find(ys, Y_FRACTION_DIGITS, NULL) == NULL){
clixon_err(OE_YANG, 0, "decimal64 type requires fraction-digits sub-statement");
goto done;
}
}
else if (strcmp(yang_argument_get(ys), "identityref") == 0){
if ((ybase = yang_find(ys, Y_BASE, NULL)) == NULL){
clixon_err(OE_YANG, 0, "identityref type requires base sub-statement");
goto done;
}
if ((yang_find_identity(ys, yang_argument_get(ybase))) == NULL){
clixon_err(OE_YANG, 0, "Identity %s not found (base type of %s)",
yang_argument_get(ybase), yang_argument_get(ys));
goto done;
}
}
else if (strcmp(yang_argument_get(ys), "enumeration") == 0){
if (ys_populate_type_enum(h, ys) < 0)
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 Clixon handle
* @param[in] ys The yang identity to populate.
* @param[in] idref If set contains the derived identifier(NULL on top call)
* @retval 0 OK
* @retval -1 Error
* @see validate_identityref which in runtime validates actual values
*/
static int
ys_populate_identity(clixon_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)**/
int inext;
/* 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){
clixon_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){
clixon_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
*/
inext = 0;
while ((yc = yn_iter(ys, &inext)) != 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){
clixon_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){
clixon_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 1 Found and set
* @retval 0 Not found or not 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 Clixon handle
* @param[in] ys Feature yang statement to populate.
* @retval 0 OK
* @retval -1 Error
* Bootstrapping: A feature is enabled if found in clixon-config
*/
int
ys_populate_feature(clixon_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){
clixon_err(OE_YANG, 0, "module not found");
goto done;
}
module = yang_argument_get(ymod);
feature = yang_argument_get(ys);
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){
clixon_err(OE_YANG, errno, "cv_new");
goto done;
}
cv_name_set(cv, feature);
cv_bool_set(cv, found);
if (found)
clixon_debug(CLIXON_DBG_YANG, "%s:%s", module, feature);
yang_cv_set(ys, cv);
ok:
retval = 0;
done:
return retval;
}
/*! Populate the unique statement with a cvec
*
* @param[in] h Clixon handle
* @param[in] ys The yang statement (unique) to populate.
* @retval 0 OK
* @retval -1 Error
*/
static int
ys_populate_unique(clixon_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 Clixon 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(clixon_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){
clixon_err(OE_YANG, ENOENT, "Extension \"%s:%s\", module not found", prefix, id);
goto done;
}
/* To find right binding eg after grouping/uses */
yang_mymodule_set(ys, ys_module(ys));
if ((yext = yang_find(ymod, Y_EXTENSION, id)) == NULL){
clixon_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){
clixon_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) {
clixon_debug(CLIXON_DBG_YANG, "plugin_extension() failed");
return -1;
}
#endif
/* Make extension callbacks that may alter yang structure
* Note: this may be a "layering" violation: assuming plugins are loaded
* at yang parse time
*/
if (clixon_plugin_extension_all(h, yext, ys) < 0)
goto done;
/* 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);
retval = 0;
done:
if (prefix)
free(prefix);
if (id)
free(id);
return retval;
}
/*! Populate modules / submodules
*
* Check RFC 7950: 7.1.4: All prefixes, including the prefix for the module itself,
* MUST be unique within the module or submodule.
* @param[in] h Clixon handle
* @param[in] ys The yang statement (module/submodule) to populate.
* @retval 0 OK
* @retval -1 Error
*/
static int
ys_populate_module_submodule(clixon_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 */
int inext;
int inext2;
/* Modules but not submodules have prefixes */
if ((yp = yang_find(ym, Y_PREFIX, NULL)) != NULL)
p0 = yang_argument_get(yp);
inext = 0;
while ((yi = yn_iter(ym, &inext)) != 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 */
clixon_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 */
inext2 = inext;
while ((yi2 = yn_iter(ym, &inext2)) != 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){
clixon_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 Clixon handle
* @retval 0 OK
* @retval -1 Error
* 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;
clixon_handle h = (clixon_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
*
* Run in yang_apply but also other places
* @param[in] yn yang node
* @param[in] arg Argument
* @retval n OK, abort traversal and return to caller with "n"
* @retval 0 OK, continue with next
* @retval -1 Error, abort
* @see ys_populate run before grouping expand and augment
*/
int
ys_populate2(yang_stmt *ys,
void *arg)
{
int retval = -1;
clixon_handle h = (clixon_handle)arg;
cg_var *cv;
int ret;
switch(ys->ys_keyword){
case Y_AUGMENT:
case Y_GROUPING:
retval = 2; /* Skip sub-tree */
goto done;
break;
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;
if (ys->ys_keyword == Y_CONFIG){
if ((cv = yang_cv_get(ys)) != NULL && !cv_bool_get(cv))
yang_flag_set(yang_parent_get(ys), YANG_FLAG_STATE_LOCAL);
}
break;
default:
break;
}
/* RFC 8528 Yang schema mount flag for optimization */
if ((ret = yang_schema_mount_point0(ys)) < 0)
goto done;
if (ret == 1)
yang_flag_set(ys, YANG_FLAG_MTPOINT_POTENTIAL);
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 OK
* @retval 0 Feature not enabled: remove yt
* @retval -1 Error
* @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(clixon_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, h) < 0)
goto done;
clixon_debug(CLIXON_DBG_YANG | CLIXON_DBG_DETAIL, "%s %d", 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 filter 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 2 OK, yn was skipped
* @retval 1 OK, aborted at first encounter of first match
* @retval 0 OK, all nodes traversed
* @retval -1 Error, aborted at first error encounter
* @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 == 2)
continue;
else 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 0 OK
* @retval -1 Error, with clixon_err called
* 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){
clixon_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){
clixon_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){
clixon_debug(CLIXON_DBG_YANG, "%s not found, last id found:%s",
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 0 OK , with result in yres
* @retval -1 Error, with clixon_err called
* 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){
clixon_err(OE_YANG, EINVAL, "No yang spec");
goto done;
}
*yres = NULL;
/* check absolute schema_nodeid */
if (schema_nodeid[0] != '/'){
clixon_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){
clixon_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){
clixon_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){
clixon_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 clixon_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){
clixon_err(OE_YANG, EINVAL, "nodeid is empty");
goto done;
}
if ((yspec = ys_spec(yn)) == NULL){
clixon_err(OE_YANG, EINVAL, "No yang spec");
goto done;
}
*yres = NULL;
/* check absolute schema_nodeid */
if (schema_nodeid[0] == '/'){
clixon_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){
clixon_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 1 If node has not config sub-statement or it is true
* @retval 0 If node has a config sub-statement and it is false
* @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 1 Neither node nor any of its ancestors has config false
* @retval 0 Node or one of its ancestor has config false or is RPC or notification
*/
int
yang_config_ancestor(yang_stmt *ys)
{
yang_stmt *yp;
enum rfc_6020 keyw;
yp = ys;
do {
if (yang_flag_get(yp, YANG_FLAG_STATE_LOCAL) != 0)
return 0;
else {
keyw = yang_keyword_get(yp);
if (keyw == Y_INPUT || keyw == Y_OUTPUT || keyw == Y_NOTIFICATION){
return 0;
}
}
} while((yp = yang_parent_get(yp)) != NULL);
return 1;
}
/*! Given a yang node, translate the argument string to a cv vector
*
* @param[in] ys Yang statement
* @param[in] delimiter Delimiter character (eg ' ' or ',')
* @retval cvec Vector of strings. Free with cvec_free()
* @retval NULL Error
* @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(yang_argument_get(ys), " ", &nvec)) == NULL)
goto done;
if ((cvv = cvec_new(nvec)) == NULL){
clixon_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){
clixon_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 Yes match
* @retval 0 No match
* @retval -1 Error
*/
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 Which regexp engine to use, see enum regexp_mode
* @retval 0 OK
* @retval -1 Error
*/
int
yang_type_cache_set2(yang_stmt *ys,
yang_stmt *resolved,
int options,
cvec *cvv,
cvec *patterns,
uint8_t fraction,
int rxmode,
cvec *regexps)
{
int retval = -1;
yang_type_cache *ycache;
if (yang_typecache_get(ys) != NULL){
clixon_err(OE_YANG, EEXIST, "yang type cache");
goto done;
}
if ((ycache = (yang_type_cache *)malloc(sizeof(*ycache))) == NULL){
clixon_err(OE_UNIX, errno, "malloc");
goto done;
}
yang_typecache_set(ys, ycache);
memset(ycache, 0, sizeof(*ycache));
ycache->yc_resolved = resolved;
ycache->yc_options = options;
if (cvv){
if ((ycache->yc_cvv = cvec_dup(cvv)) == NULL){
clixon_err(OE_UNIX, errno, "cvec_dup");
goto done;
}
}
if (patterns && (ycache->yc_patterns = cvec_dup(patterns)) == NULL){
clixon_err(OE_UNIX, errno, "cvec_dup");
goto done;
}
ycache->yc_fraction = fraction;
if (regexps != NULL) {
ycache->yc_rxmode = rxmode;
if ((ycache->yc_regexps = cvec_dup(regexps)) == NULL){
clixon_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
* @param[out] rxmode Which regexp engine to use, see enum regexp_mode
* @retval 1 OK
* @retval 0 No cache
* @retval -1 Error
*/
int
yang_type_cache_get2(yang_stmt *ytype,
yang_stmt **resolved,
int *options,
cvec **cvv,
cvec *patterns,
cvec *regexps,
uint8_t *fraction)
{
int retval = -1;
cg_var *cv = NULL;
yang_type_cache *ycache;
ycache = yang_typecache_get(ytype);
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 (fraction)
*fraction = ycache->yc_fraction;
retval = 1; /* cache exists and is returned OK */
done:
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 clixon_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){
clixon_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 Extension value if any
* @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 argument
* }
* @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;
int inext;
if (ys == NULL){
clixon_err(OE_YANG, EINVAL, "ys is NULL");
goto done;
}
if (exist)
*exist = 0;
if ((cb = cbuf_new()) == NULL){
clixon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
inext = 0; /* This loop gets complicated in the case the extension is augmented */
while ((yext = yn_iter(ys, &inext)) != 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;
}
#if 0 /* NOTE this may need to reinserted with new implementation */
/* 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;
}
#endif
/*! 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)
{
#if 1
return 0;
#else
int retval = -1;
int inext;
if ((yang_keyword_get(ys) == Y_CONTAINER ||
yang_keyword_get(ys) == Y_LIST)){
/* This enumerates _ys_vector_i in ys->ys_stmt vector */
inext = 0;
while (yn_iter(ys, &inext) != NULL) ;
// qsort(ys->ys_stmt, ys->ys_len, sizeof(ys), yang_sort_subelements_fn);
}
retval = 0;
// done:
return retval;
#endif
}
#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){
clixon_log(NULL, 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(clixon_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;
clixon_debug(CLIXON_DBG_YANG, "Enabled extension:%s:%s", 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 1 Yes, node has single child of specified type
* @retval 0 No, node does not have 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;
int i;
int inext;
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 */
inext = 0;
while ((yc = yn_iter(ys, &inext)) != 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)
{
assert(ys->ys_keyword == Y_ACTION);
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;
assert(ys->ys_keyword == Y_ACTION);
if (rc == NULL){
clixon_err(OE_YANG, EINVAL, "arg is NULL");
return -1;
}
ADDQ(rc, ys->ys_action_cb);
return 0;
}
/*! Init yang code
*
* Add two external tables for YANGs
* @param[in] h Clixon handle
* @retval 0 OK
* @retval -1 Error
*/
int
yang_init(clixon_handle h)
{
int retval = -1;
map_ptr2ptr *mp;
if ((mp = calloc(1, sizeof(*mp))) == NULL){
clixon_err(OE_UNIX, errno, "calloc");
goto done;
}
_yang_when_map = mp;
if ((mp = calloc(1, sizeof(*mp))) == NULL){
clixon_err(OE_UNIX, errno, "calloc");
goto done;
}
_yang_mymodule_map = mp;
if (yang_cardinality_init(h) < 0)
goto done;
retval = 0;
done:
return retval;
}
/*! Exit yang code
*
* @param[in] h Clixon handle
*/
int
yang_exit(clixon_handle h)
{
if (_yang_when_map != NULL) {
free(_yang_when_map);
_yang_when_map = NULL;
}
if (_yang_mymodule_map != NULL) {
free(_yang_mymodule_map);
_yang_mymodule_map = NULL;
}
return 0;
}