4107 lines
120 KiB
C
4107 lines
120 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_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}
|
|
};
|
|
|
|
/* Forward static */
|
|
static int yang_type_cache_free(yang_type_cache *ycache);
|
|
static int yang_type_cache_cp(yang_stmt *ynew, yang_stmt *yold);
|
|
|
|
/* Access functions
|
|
*/
|
|
|
|
/*! Get Number of children yang statements
|
|
*
|
|
* @param[in] ys Yang statement node
|
|
*/
|
|
int
|
|
yang_len_get(yang_stmt *ys)
|
|
{
|
|
return ys->ys_len;
|
|
}
|
|
|
|
/*! Get Specific Yang statement child
|
|
*
|
|
* @param[in] ys Yang statement node
|
|
* @param[in] i Return this child
|
|
*/
|
|
yang_stmt *
|
|
yang_child_i(yang_stmt *ys,
|
|
int i)
|
|
{
|
|
return ys->ys_stmt[i];
|
|
}
|
|
|
|
/*! Get yang statement parent
|
|
*
|
|
* @param[in] ys Yang statement node
|
|
*/
|
|
yang_stmt *
|
|
yang_parent_get(yang_stmt *ys)
|
|
{
|
|
return ys->ys_parent;
|
|
}
|
|
|
|
/*! Get yang statement keyword
|
|
*
|
|
* @param[in] ys Yang statement node
|
|
*/
|
|
enum rfc_6020
|
|
yang_keyword_get(yang_stmt *ys)
|
|
{
|
|
return ys->ys_keyword;
|
|
}
|
|
|
|
/*! Get yang statement context-dependent argument
|
|
*
|
|
* @param[in] ys Yang statement node
|
|
*/
|
|
char*
|
|
yang_argument_get(yang_stmt *ys)
|
|
{
|
|
return ys->ys_argument;
|
|
}
|
|
|
|
/*
|
|
* Note on cvec on XML nodes:
|
|
* 1. It is always created in xml_new. It could be lazily created on use to save a little memory
|
|
* 2. Only some yang statements use the cvec, as follows:
|
|
* 2a. ranges and lengths: [min, max]
|
|
* 2b. list: keys
|
|
* 2c. identity types: derived instances: identityrefs, save <module>:<idref>
|
|
* 2d. type: leafref types: derived instances.
|
|
*/
|
|
/*! Set yang argument, not not copied
|
|
*
|
|
* @param[in] ys Yang statement node
|
|
* @param[in] arg Argument, 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 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)
|
|
{
|
|
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)
|
|
{
|
|
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;
|
|
|
|
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 xpath for "when"-associated augment
|
|
*
|
|
* Ie, for yang structures like: augment <path> { when <xpath>; ... }
|
|
* Will insert new yang nodes at <path> with this special "when" struct (not yang node)
|
|
* @param[in] ys Yang statement
|
|
* @retval xpath xpath should evaluate to true at validation
|
|
* @retval NULL Not set
|
|
* @note xpath context is PARENT which is different from when actual when child which is
|
|
* child itself
|
|
*/
|
|
char*
|
|
yang_when_xpath_get(yang_stmt *ys)
|
|
{
|
|
return ys->ys_when_xpath;
|
|
}
|
|
|
|
/*! Set yang xpath and namespace context for "when"-associated augment
|
|
*
|
|
* Ie, for yang structures like: augment <path> { when <xpath>; ... }
|
|
* Will insert new yang nodes at <path> with this special "when" struct (not yang node)
|
|
* @param[in] ys Yang statement
|
|
* @param[in] xpath If set, this xpath should evaluate to true at validation, copied
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*/
|
|
int
|
|
yang_when_xpath_set(yang_stmt *ys,
|
|
char *xpath)
|
|
{
|
|
int retval = -1;
|
|
|
|
if (xpath == NULL){
|
|
clixon_err(OE_YANG, EINVAL, "xpath is NULL");
|
|
goto done;
|
|
}
|
|
if ((ys->ys_when_xpath = strdup(xpath)) == NULL){
|
|
clixon_err(OE_YANG, errno, "strdup");
|
|
goto done;
|
|
}
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Get yang namespace context for "when"-associated augment
|
|
*
|
|
* Ie, for yang structures like: augment <path> { when <xpath>; ... }
|
|
* Will insert new yang nodes at <path> with this special "when" struct (not yang node)
|
|
* @param[in] ys Yang statement
|
|
* @retval nsc Namespace context
|
|
* @note retval is direct pointer, may need to be copied
|
|
*/
|
|
cvec *
|
|
yang_when_nsc_get(yang_stmt *ys)
|
|
{
|
|
return ys->ys_when_nsc;
|
|
}
|
|
|
|
/*! Set yang namespace context for "when"-associated augment
|
|
*
|
|
* Ie, for yang structures like: augment <path> { when <xpath>; ... }
|
|
* Will insert new yang nodes at <path> with this special "when" struct (not yang node)
|
|
* @param[in] ys Yang statement
|
|
* @param[in] nsc Namespace context for when xpath
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*/
|
|
int
|
|
yang_when_nsc_set(yang_stmt *ys,
|
|
cvec *nsc)
|
|
{
|
|
int retval = -1;
|
|
|
|
if (nsc && (ys->ys_when_nsc = cvec_dup(nsc)) == NULL){
|
|
clixon_err(OE_YANG, errno, "cvec_dup");
|
|
goto done;
|
|
}
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Get yang filename for error/debug purpose
|
|
*
|
|
* @param[in] ys Yang statement
|
|
* @retval filename
|
|
* @note there maye not always be a "filename" in case the yang is read from memory
|
|
*/
|
|
const char *
|
|
yang_filename_get(yang_stmt *ys)
|
|
{
|
|
return ys->ys_filename;
|
|
}
|
|
|
|
/*! Set yang filename for error/debug purpose
|
|
*
|
|
* @param[in] ys Yang statement
|
|
* @param[in] filename
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
* @note there maye not always be a "filename" in case the yang is read from memory
|
|
*/
|
|
int
|
|
yang_filename_set(yang_stmt *ys,
|
|
const char *filename)
|
|
{
|
|
if ((ys->ys_filename = strdup(filename)) == NULL){
|
|
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
|
|
*/
|
|
int
|
|
yang_linenum_get(yang_stmt *ys)
|
|
{
|
|
return ys->ys_linenum;
|
|
}
|
|
|
|
/*! Set line number of yang filename for error/debug purpose
|
|
*
|
|
* @param[in] ys Yang statement
|
|
* @param[in] linenum
|
|
*/
|
|
int
|
|
yang_linenum_set(yang_stmt *ys,
|
|
int linenum)
|
|
{
|
|
ys->ys_linenum = linenum;
|
|
return 0;
|
|
}
|
|
|
|
/* End access functions */
|
|
|
|
/* Stats */
|
|
static uint64_t _stats_yang_nr = 0;
|
|
|
|
/*! Get global statistics about YANG statements: created - freed
|
|
*
|
|
* @param[out] nr Number of existing YANG objects (created - freed)
|
|
*/
|
|
int
|
|
yang_stats_global(uint64_t *nr)
|
|
{
|
|
if (nr)
|
|
*nr = _stats_yang_nr;
|
|
return 0;
|
|
}
|
|
|
|
/*! Return the alloced memory of a single YANG obj
|
|
*
|
|
* @param[in] y YANG object
|
|
* @param[out] szp Size of this YANG obj
|
|
* @retval 0 OK
|
|
* (baseline: )
|
|
*/
|
|
static int
|
|
yang_stats_one(yang_stmt *y,
|
|
size_t *szp)
|
|
{
|
|
size_t sz = 0;
|
|
yang_type_cache *yc;
|
|
|
|
sz += sizeof(struct yang_stmt);
|
|
sz += y->ys_len*sizeof(struct yang_stmt*);
|
|
if (y->ys_argument)
|
|
sz += strlen(y->ys_argument) + 1;
|
|
if (y->ys_cv)
|
|
sz += cv_size(y->ys_cv);
|
|
if (y->ys_cvec)
|
|
sz += cvec_size(y->ys_cvec);
|
|
if ((yc = y->ys_typecache) != NULL){
|
|
sz += sizeof(struct yang_type_cache);
|
|
if (yc->yc_cvv)
|
|
sz += cvec_size(yc->yc_cvv);
|
|
if (yc->yc_patterns)
|
|
sz += cvec_size(yc->yc_patterns);
|
|
if (yc->yc_regexps)
|
|
sz += cvec_size(yc->yc_regexps);
|
|
}
|
|
if (y->ys_when_xpath)
|
|
sz += strlen(y->ys_when_xpath) + 1;
|
|
if (y->ys_when_nsc)
|
|
sz += cvec_size(y->ys_when_nsc);
|
|
if (y->ys_filename)
|
|
sz += strlen(y->ys_filename) + 1;
|
|
if (szp)
|
|
*szp = sz;
|
|
return 0;
|
|
}
|
|
|
|
/*! Return statistics of an YANG-stmt tree recursively
|
|
*
|
|
* @param[in] yt YANG object
|
|
* @param[out] nrp Number of YANG objects recursively
|
|
* @param[out] szp Size of this YANG stmt recursively
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*/
|
|
int
|
|
yang_stats(yang_stmt *yt,
|
|
uint64_t *nrp,
|
|
size_t *szp)
|
|
{
|
|
int retval = -1;
|
|
size_t sz = 0;
|
|
yang_stmt *ys;
|
|
|
|
if (yt == NULL){
|
|
clixon_err(OE_XML, EINVAL, "yang spec is NULL");
|
|
goto done;
|
|
}
|
|
*nrp += 1;
|
|
yang_stats_one(yt, &sz);
|
|
if (szp)
|
|
*szp += sz;
|
|
ys = NULL;
|
|
while ((ys = yn_each(yt, ys)) != NULL) {
|
|
sz = 0;
|
|
yang_stats(ys, nrp, &sz);
|
|
if (szp)
|
|
*szp += sz;
|
|
}
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/* stats end */
|
|
|
|
/*! Create new yang specification
|
|
*
|
|
* @retval yspec Free with ys_free()
|
|
* @retval NULL Error
|
|
*/
|
|
yang_stmt *
|
|
yspec_new(void)
|
|
{
|
|
yang_stmt *yspec;
|
|
|
|
if ((yspec = malloc(sizeof(*yspec))) == NULL){
|
|
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
|
|
*
|
|
* @retval ys Free with ys_free()
|
|
* @retval NULL Error
|
|
*/
|
|
yang_stmt *
|
|
ys_new(enum rfc_6020 keyw)
|
|
{
|
|
yang_stmt *ys;
|
|
|
|
if ((ys = malloc(sizeof(*ys))) == NULL){
|
|
clixon_err(OE_YANG, errno, "malloc");
|
|
return NULL;
|
|
}
|
|
memset(ys, 0, sizeof(*ys));
|
|
ys->ys_keyword = keyw;
|
|
_stats_yang_nr++;
|
|
return ys;
|
|
}
|
|
|
|
/*! Free a single yang statement, dont remove children, 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)
|
|
{
|
|
cg_var *cv;
|
|
rpc_callback_t *rc;
|
|
|
|
if ((cv = yang_cv_get(ys)) != NULL){
|
|
yang_cv_set(ys, NULL); /* only frees on replace */
|
|
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_typecache){
|
|
yang_type_cache_free(ys->ys_typecache);
|
|
ys->ys_typecache = NULL;
|
|
}
|
|
if (ys->ys_when_xpath)
|
|
free(ys->ys_when_xpath);
|
|
if (ys->ys_when_nsc)
|
|
cvec_free(ys->ys_when_nsc);
|
|
if (ys->ys_stmt)
|
|
free(ys->ys_stmt);
|
|
if (ys->ys_filename)
|
|
free(ys->ys_filename);
|
|
while((rc = ys->ys_action_cb) != NULL) {
|
|
DELQ(rc, ys->ys_action_cb, rpc_callback_t *);
|
|
if (rc->rc_namespace)
|
|
free(rc->rc_namespace);
|
|
if (rc->rc_name)
|
|
free(rc->rc_name);
|
|
free(rc);
|
|
}
|
|
if (self){
|
|
free(ys);
|
|
_stats_yang_nr--;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*! Remove child i from parent yp (dont free)
|
|
*
|
|
* @param[in] yp Parent node
|
|
* @param[in] i Order of child to remove
|
|
* @retval 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;
|
|
|
|
if ((yp = yang_parent_get(ys)) != NULL){
|
|
yc = NULL;
|
|
i = 0;
|
|
/* Find order of ys in child-list */
|
|
while ((yc = yn_each(yp, yc)) != NULL) {
|
|
if (ys == yc)
|
|
break;
|
|
i++;
|
|
}
|
|
if (yc != NULL){
|
|
assert(yc == ys);
|
|
ys_prune(yp, i);
|
|
}
|
|
}
|
|
retval = 0;
|
|
// done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Free all yang children tree recursively
|
|
*
|
|
* @param[in] ys Yang node to its children recursively
|
|
*/
|
|
static int
|
|
ys_freechildren(yang_stmt *ys)
|
|
{
|
|
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;
|
|
}
|
|
|
|
/*! Copy yang statement recursively from old to new
|
|
*
|
|
* @param[in] ynew New empty (but created) yang statement (to)
|
|
* @param[in] yold Old existing yang statement (from)
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
* @code
|
|
* yang_stmt *new = ys_new(Y_LEAF);
|
|
* if (ys_cp(new, old) < 0)
|
|
* err;
|
|
* @endcode
|
|
* @see ys_replace
|
|
*/
|
|
int
|
|
ys_cp(yang_stmt *ynew,
|
|
yang_stmt *yold)
|
|
{
|
|
int retval = -1;
|
|
int i;
|
|
yang_stmt *ycn; /* new child */
|
|
yang_stmt *yco; /* old child */
|
|
cg_var *cvn;
|
|
cg_var *cvo;
|
|
|
|
memcpy(ynew, yold, sizeof(*yold));
|
|
ynew->ys_parent = NULL;
|
|
if (yold->ys_stmt)
|
|
if ((ynew->ys_stmt = calloc(yold->ys_len, sizeof(yang_stmt *))) == NULL){
|
|
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;
|
|
}
|
|
yang_cv_set(ynew, NULL);
|
|
if ((cvo = yang_cv_get(yold)) != NULL){
|
|
if ((cvn = cv_dup(cvo)) == NULL){
|
|
clixon_err(OE_YANG, errno, "cv_dup");
|
|
goto done;
|
|
}
|
|
yang_cv_set(ynew, cvn);
|
|
}
|
|
if (yold->ys_cvec)
|
|
if ((ynew->ys_cvec = cvec_dup(yold->ys_cvec)) == NULL){
|
|
clixon_err(OE_YANG, errno, "cvec_dup");
|
|
goto done;
|
|
}
|
|
if (yold->ys_typecache){
|
|
ynew->ys_typecache = NULL;
|
|
if (yang_type_cache_cp(ynew, yold) < 0)
|
|
goto done;
|
|
}
|
|
if (yold->ys_when_xpath)
|
|
if ((ynew->ys_when_xpath = strdup(yold->ys_when_xpath)) == NULL){
|
|
clixon_err(OE_YANG, errno, "strdup");
|
|
goto done;
|
|
}
|
|
if (yold->ys_when_nsc){
|
|
if ((ynew->ys_when_nsc = cvec_dup(yold->ys_when_nsc)) == NULL){
|
|
clixon_err(OE_YANG, errno, "cvec_dup");
|
|
goto done;
|
|
}
|
|
}
|
|
for (i=0; i<ynew->ys_len; i++){
|
|
yco = yold->ys_stmt[i];
|
|
if ((ycn = ys_dup(yco)) == NULL)
|
|
goto done;
|
|
ynew->ys_stmt[i] = ycn;
|
|
ycn->ys_parent = ynew;
|
|
}
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Create a new yang node and copy the contents recursively from the original. *
|
|
*
|
|
* @param[in] old Old existing yang statement (from)
|
|
* @retval NULL Error
|
|
* @retval nw New created yang statement
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
* This may involve duplicating strings, etc.
|
|
* The new yang tree needs to be freed by ys_free().
|
|
* The parent of new is NULL, it needs to be explicityl inserted somewhere
|
|
*/
|
|
yang_stmt *
|
|
ys_dup(yang_stmt *old)
|
|
{
|
|
yang_stmt *nw;
|
|
|
|
if ((nw = ys_new(old->ys_keyword)) == NULL)
|
|
return NULL;
|
|
if (nw->ys_cvec){
|
|
cvec_free(nw->ys_cvec);
|
|
nw->ys_cvec = NULL;
|
|
}
|
|
if (ys_cp(nw, old) < 0){
|
|
ys_free(nw);
|
|
return NULL;
|
|
}
|
|
return nw;
|
|
}
|
|
|
|
/*! Replace yold with ynew (insert ynew at the exact place of yold). Keep yold pointer as-is.
|
|
*
|
|
* @param[in] yorig Existing yang statement
|
|
* @param[in] yfrom New empty (but created) yang statement
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
* @code
|
|
* if (ys_replace(new, old) < 0)
|
|
* err;
|
|
* @endcode
|
|
* @see ys_cp
|
|
* @note yfrom is left in its original state
|
|
*/
|
|
int
|
|
ys_replace(yang_stmt *yorig,
|
|
yang_stmt *yfrom)
|
|
{
|
|
int retval = -1;
|
|
yang_stmt *yp; /* parent */
|
|
|
|
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
|
|
*
|
|
* @param[in] yparent yang statement whose children are iterated
|
|
* @param[in] yprev previous child, or NULL on init
|
|
* @code
|
|
* yang_stmt *yprev = NULL;
|
|
* while ((yprev = yn_each(yparent, yprev)) != NULL) {
|
|
* ...yprev...
|
|
* }
|
|
* @endcode
|
|
* @note makes uses _ys_vector_i:can be changed if list changed between calls
|
|
* @note also does not work in recursive calls (to same node)
|
|
*/
|
|
yang_stmt *
|
|
yn_each(yang_stmt *yparent,
|
|
yang_stmt *yprev)
|
|
{
|
|
int i;
|
|
yang_stmt *yc = NULL;
|
|
|
|
if (yparent == NULL)
|
|
return NULL;
|
|
for (i=yprev?yprev->_ys_vector_i+1:0; i<yparent->ys_len; i++){
|
|
if ((yc = yparent->ys_stmt[i]) == NULL){
|
|
continue;
|
|
}
|
|
/* make room for other conditionals */
|
|
break; /* this is next object after previous */
|
|
}
|
|
if (i < yparent->ys_len) /* found */
|
|
yc->_ys_vector_i = i;
|
|
else
|
|
yc = NULL;
|
|
return yc;
|
|
}
|
|
|
|
/*! Find first child yang_stmt with matching keyword and argument
|
|
*
|
|
* 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;
|
|
|
|
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;
|
|
|
|
ys = NULL;
|
|
while ((ys = yn_each(yn, ys)) != NULL){
|
|
if (yang_keyword_get(ys) == Y_CHOICE){ /* Look for its children */
|
|
yc = NULL;
|
|
while ((yc = yn_each(ys, yc)) != NULL){
|
|
if (yang_keyword_get(yc) == Y_CASE) /* Look for its children */
|
|
ysmatch = yang_find_datanode(yc, argument);
|
|
else
|
|
if (yang_datanode(yc)){
|
|
if (yc->ys_argument && strcmp(argument, yc->ys_argument) == 0)
|
|
ysmatch = yc;
|
|
}
|
|
if (ysmatch)
|
|
goto done; // maybe break?
|
|
}
|
|
} /* Y_CHOICE */
|
|
else if (yang_keyword_get(ys) == Y_INPUT ||
|
|
yang_keyword_get(ys) == Y_OUTPUT){ /* Look for its children */
|
|
if ((ysmatch = yang_find_datanode(ys, argument)) != NULL)
|
|
break;
|
|
}
|
|
else if (yang_datanode(ys)){
|
|
if (argument == NULL)
|
|
ysmatch = ys;
|
|
else
|
|
if (ys->ys_argument && strcmp(argument, ys->ys_argument) == 0)
|
|
ysmatch = ys;
|
|
if (ysmatch)
|
|
goto done; // maybe break?
|
|
}
|
|
}
|
|
/* Special case: if not match and yang node is module or submodule, extend
|
|
* search to include submodules */
|
|
if (ysmatch == NULL &&
|
|
(yang_keyword_get(yn) == Y_MODULE ||
|
|
yang_keyword_get(yn) == Y_SUBMODULE)){
|
|
yspec = ys_spec(yn);
|
|
ys = NULL;
|
|
while ((ys = yn_each(yn, ys)) != NULL){
|
|
if (yang_keyword_get(ys) == Y_INCLUDE){
|
|
name = yang_argument_get(ys);
|
|
yc = yang_find_module_by_name(yspec, name);
|
|
if ((ysmatch = yang_find_datanode(yc, argument)) != NULL)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
done:
|
|
return ysmatch;
|
|
}
|
|
|
|
/*! Find child schema node with matching argument (container, leaf, etc)
|
|
*
|
|
* @param[in] yn Yang node, current context node.
|
|
* @param[in] argument if NULL, match any(first) argument.
|
|
* @note XXX unify code with yang_find_datanode?
|
|
* @see yang_find_datanode
|
|
* @see yang_abs_schema_nodeid Top level function
|
|
*/
|
|
yang_stmt *
|
|
yang_find_schemanode(yang_stmt *yn,
|
|
char *argument)
|
|
{
|
|
yang_stmt *ys = NULL;
|
|
yang_stmt *yc = NULL;
|
|
yang_stmt *yspec;
|
|
yang_stmt *ysmatch = NULL;
|
|
char *name;
|
|
int i, j;
|
|
|
|
for (i=0; i<yn->ys_len; i++){
|
|
ys = yn->ys_stmt[i];
|
|
if (yang_keyword_get(ys) == Y_CHOICE){
|
|
/* First check choice itself */
|
|
if (ys->ys_argument && strcmp(argument, ys->ys_argument) == 0){
|
|
ysmatch = ys;
|
|
goto match;
|
|
}
|
|
/* Then look for its children (case) */
|
|
for (j=0; j<ys->ys_len; j++){
|
|
yc = ys->ys_stmt[j];
|
|
if (yang_keyword_get(yc) == Y_CASE) /* Look for its children */
|
|
ysmatch = yang_find_schemanode(yc, argument);
|
|
else
|
|
if (yang_schemanode(yc)){
|
|
if (argument == NULL)
|
|
ysmatch = yc;
|
|
else
|
|
if (yc->ys_argument && strcmp(argument, yc->ys_argument) == 0)
|
|
ysmatch = yc;
|
|
}
|
|
if (ysmatch)
|
|
goto match;
|
|
}
|
|
} /* Y_CHOICE */
|
|
else
|
|
if (yang_schemanode(ys)){
|
|
if (strcmp(argument, "input") == 0 && yang_keyword_get(ys) == Y_INPUT)
|
|
ysmatch = ys;
|
|
else if (strcmp(argument, "output") == 0 && yang_keyword_get(ys) == Y_OUTPUT)
|
|
ysmatch = ys;
|
|
else if (argument == NULL)
|
|
ysmatch = ys;
|
|
else
|
|
if (ys->ys_argument && strcmp(argument, ys->ys_argument) == 0)
|
|
ysmatch = ys;
|
|
if (ysmatch)
|
|
goto match;
|
|
}
|
|
}
|
|
/* Special case: if not match and yang node is module or submodule, extend
|
|
* search to include submodules */
|
|
if (ysmatch == NULL &&
|
|
(yang_keyword_get(yn) == Y_MODULE ||
|
|
yang_keyword_get(yn) == Y_SUBMODULE)){
|
|
yspec = ys_spec(yn);
|
|
for (i=0; i<yn->ys_len; i++){
|
|
ys = yn->ys_stmt[i];
|
|
if (yang_keyword_get(ys) == Y_INCLUDE){
|
|
name = yang_argument_get(ys);
|
|
yc = yang_find_module_by_name(yspec, name);
|
|
if ((ysmatch = yang_find_schemanode(yc, argument)) != NULL)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
match:
|
|
return ysmatch;
|
|
}
|
|
|
|
/*! Given a yang statement, or module itself, find the prefix associated to this module
|
|
*
|
|
* @param[in] ys Yang statement in module tree (or module itself)
|
|
* @retval 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;
|
|
|
|
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 */
|
|
yimport = NULL;
|
|
while ((yimport = yn_each(my_ymod, yimport)) != NULL) {
|
|
if (yang_keyword_get(yimport) == Y_IMPORT &&
|
|
strcmp(modname, yang_argument_get(yimport)) == 0){ /* match */
|
|
yprefix = yang_find(yimport, Y_PREFIX, NULL);
|
|
*prefix = yang_argument_get(yprefix);
|
|
goto found;
|
|
}
|
|
}
|
|
notfound:
|
|
retval = 0; /* not found */
|
|
done:
|
|
return retval;
|
|
found:
|
|
assert(*prefix);
|
|
retval = 1;
|
|
goto done;
|
|
}
|
|
|
|
/*! Given a yang statement and local prefix valid in module, find namespace
|
|
*
|
|
* @param[in] ys Yang statement in module tree (or module itself)
|
|
* @param[in] prefix Local prefix to access module with (direct pointer)
|
|
* @param[out] ns Namespace URI as char* pointer into yang tree
|
|
* @retval 1 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 (ys->ys_mymodule){ /* shortcut due to augment */
|
|
ys = ys->ys_mymodule;
|
|
break;
|
|
}
|
|
yn = ys->ys_parent;
|
|
/* Some extra stuff to ensure ys is a stmt */
|
|
if (yn && yn->ys_keyword == Y_SPEC)
|
|
yn = NULL;
|
|
ys = (yang_stmt*)yn;
|
|
}
|
|
/* Here it is either NULL or is a typedef-kind yang-stmt */
|
|
return ys;
|
|
}
|
|
|
|
/*! Find real top module given a statement in a yang tree
|
|
*
|
|
* With "real" top module means that if sub-module is the top-node,
|
|
* the module that the sub-module belongs-to is found recursively
|
|
* @param[in] ys Any yang statement in a yang tree
|
|
* @param[out] ymod The top module or sub-module
|
|
* @retval 0 OK, returned module in ymod
|
|
* @retval -1 YANG validation error: all yang statements should have a "real" module
|
|
* @see ys_module
|
|
* @note For an augmented node, the original module is returned
|
|
*/
|
|
int
|
|
ys_real_module(yang_stmt *ys,
|
|
yang_stmt **ymod)
|
|
{
|
|
int retval = -1;
|
|
yang_stmt *ym = NULL;
|
|
yang_stmt *ysubm;
|
|
yang_stmt *yb;
|
|
char *name;
|
|
yang_stmt *yspec;
|
|
|
|
if (ymod == NULL){
|
|
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 = NULL;
|
|
yang_stmt *yrev;
|
|
|
|
if (yspec == NULL || yang_keyword_get(yspec) != Y_SPEC){
|
|
clixon_err(OE_YANG, EINVAL, "yspec is not of type YSPEC");
|
|
return -1;
|
|
}
|
|
while ((ym = yn_each(yspec, ym)) != NULL) {
|
|
fprintf(f, "%s", yang_key2str(ym->ys_keyword));
|
|
fprintf(f, " %s", ym->ys_argument);
|
|
if ((yrev = yang_find(ym, Y_REVISION, NULL)) != NULL){
|
|
fprintf(f, "@%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 = NULL;
|
|
yang_stmt *yrev;
|
|
cbuf *cb = NULL;
|
|
|
|
if ((cb = cbuf_new()) ==NULL){
|
|
clixon_err(OE_YANG, errno, "cbuf_new");
|
|
goto done;
|
|
}
|
|
while ((ym = yn_each(yspec, ym)) != NULL) {
|
|
cprintf(cb, "%s", yang_key2str(ym->ys_keyword));
|
|
cprintf(cb, " %s", ym->ys_argument);
|
|
if ((yrev = yang_find(ym, Y_REVISION, NULL)) != NULL){
|
|
cprintf(cb, "@%u", cv_uint32_get(yang_cv_get(yrev)));
|
|
}
|
|
cprintf(cb, ".yang");
|
|
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;
|
|
|
|
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");
|
|
ys = NULL;
|
|
while ((ys = yn_each(yn, ys)) != NULL) {
|
|
if (yang_print_cbuf(cb, ys, marginal + PRETTYPRINT_INDENT, pretty) < 0)
|
|
goto done;
|
|
}
|
|
if (pretty)
|
|
cprintf(cb, "%*s%s\n", marginal, "", "}");
|
|
else
|
|
cprintf(cb, "}");
|
|
}
|
|
else{
|
|
cprintf(cb, ";");
|
|
if (pretty)
|
|
cprintf(cb, "\n");
|
|
}
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Yang deviation/deviate
|
|
*
|
|
* Identify deviation target node, and go through all its deviate statements.
|
|
* Features/if-feature must have run before
|
|
* @param[in] ys The yang deviation to populate.
|
|
* @param[in] h 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;
|
|
|
|
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 */
|
|
yd = NULL;
|
|
while ((yd = yn_each(ys, yd)) != NULL) {
|
|
/* description / if-feature / reference */
|
|
if (yang_keyword_get(yd) != Y_DEVIATE)
|
|
continue;
|
|
devop = yang_argument_get(yd);
|
|
if (strcmp(devop, "not-supported") == 0){
|
|
if (ys_prune_self(ytarget) < 0)
|
|
goto done;
|
|
if (ys_free(ytarget) < 0)
|
|
goto done;
|
|
goto ok; /* Target node removed, no other deviates possible */
|
|
}
|
|
else if (strcmp(devop, "add") == 0){
|
|
yc = NULL;
|
|
while ((yc = yn_each(yd, yc)) != NULL) {
|
|
/* If a property can only appear once, the property MUST NOT exist in the target node. */
|
|
kw = yang_keyword_get(yc);
|
|
if (yang_find(ytarget, kw, NULL) != NULL){
|
|
if (yang_cardinality_interval(h,
|
|
yang_keyword_get(ytarget),
|
|
kw,
|
|
&min,
|
|
&max) < 0)
|
|
goto done;
|
|
if (max == 1){
|
|
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){
|
|
yc = NULL;
|
|
while ((yc = yn_each(yd, yc)) != NULL) {
|
|
/* The properties to replace MUST exist in the target node.*/
|
|
kw = yang_keyword_get(yc);
|
|
ytc = yang_find(ytarget, kw, NULL);
|
|
switch (kw){
|
|
case Y_CONFIG: /* special case: implicit default is config true */
|
|
break;
|
|
default:
|
|
if (ytc == NULL){
|
|
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){
|
|
yc = NULL;
|
|
while ((yc = yn_each(yd, yc)) != NULL) {
|
|
/* The substatement's keyword MUST match a corresponding keyword in the target node, and the
|
|
* argument's string MUST be equal to the corresponding keyword's argument string in the
|
|
* target node. */
|
|
kw = yang_keyword_get(yc);
|
|
if ((ytc = yang_find(ytarget, kw, NULL)) == NULL){
|
|
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; /* resolved type */
|
|
char *restype; /* resolved type */
|
|
char *origtype=NULL; /* original type */
|
|
uint8_t fraction_digits;
|
|
int options = 0x0;
|
|
yang_stmt *ytypedef; /* where type is define */
|
|
|
|
yparent = ys->ys_parent; /* Find parent: list/container */
|
|
/* 1. Find type specification and set cv type accordingly */
|
|
if (yang_type_get(ys, &origtype, &yrestype, &options, NULL, NULL, NULL, &fraction_digits) < 0)
|
|
goto done;
|
|
restype = yrestype?yrestype->ys_argument:NULL;
|
|
if (clicon_type2cv(origtype, restype, ys, &cvtype) < 0) /* This handles non-resolved also */
|
|
goto done;
|
|
/* 2. Create the CV using cvtype and name it */
|
|
if ((cv = cv_new(cvtype)) == NULL){
|
|
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, ys->ys_argument) == 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(ydef->ys_argument, 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(ydef->ys_argument, 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, ys->ys_argument, NULL)) < 0)
|
|
goto done;
|
|
}
|
|
yang_cv_set(ys, cv);
|
|
retval = 0;
|
|
done:
|
|
if (origtype)
|
|
free(origtype);
|
|
if (cv && retval < 0)
|
|
cv_free(cv);
|
|
return retval;
|
|
}
|
|
|
|
/*! Populate list yang statement
|
|
*
|
|
* @param[in] h 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(ys->ys_argument, "|", &nvec)) == NULL)
|
|
goto done;
|
|
for (i=0; i<nvec; i++){
|
|
v = vec[i];
|
|
if ((v2 = strstr(v, "..")) != NULL){
|
|
*v2 = '\0';
|
|
v2 += 2;
|
|
v2 = clixon_trim(v2); /* trim blanks */
|
|
}
|
|
v = clixon_trim(v); /* trim blanks */
|
|
if (bound_add(ys, cvtype, "range_min", v, fraction_digits) < 0)
|
|
goto done;
|
|
if (v2)
|
|
if (bound_add(ys, cvtype, "range_max", v2, fraction_digits) < 0)
|
|
goto done;
|
|
}
|
|
retval = 0;
|
|
done:
|
|
if (vec)
|
|
free(vec);
|
|
return retval;
|
|
}
|
|
|
|
/*! Populate string built-in range statement
|
|
*
|
|
* Create cvec variables "range_min" and "range_max". Assume parent is type.
|
|
* @param[in] h 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?yrestype->ys_argument:NULL;
|
|
if (nodeid_split(yang_argument_get(yparent), NULL, &origtype) < 0)
|
|
goto done;
|
|
/* This handles non-resolved also */
|
|
if (clicon_type2cv(origtype, restype, ys, &cvtype) < 0)
|
|
goto done;
|
|
if (!cv_isint(cvtype) && cvtype != CGV_DEC64){
|
|
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;
|
|
|
|
yenum = NULL;
|
|
while ((yenum = yn_each(ys, yenum)) != 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(ys->ys_argument, "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(ys->ys_argument, "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, ybase->ys_argument)) == NULL){
|
|
clixon_err(OE_YANG, 0, "Identity %s not found (base type of %s)",
|
|
ybase->ys_argument, ys->ys_argument);
|
|
goto done;
|
|
}
|
|
}
|
|
else if (strcmp(ys->ys_argument, "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)**/
|
|
|
|
/* 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
|
|
*/
|
|
yc = NULL;
|
|
while ((yc = yn_each(ys, yc)) != NULL) {
|
|
if (yc->ys_keyword != Y_BASE)
|
|
continue;
|
|
baseid = yang_argument_get(yc); /* on the form: prefix:id */
|
|
if (((ybaseid = yang_find_identity(ys, baseid))) == NULL){
|
|
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 = ymod->ys_argument;
|
|
feature = ys->ys_argument;
|
|
xc = NULL;
|
|
while ((xc = xml_child_each(x, xc, CX_ELMNT)) != NULL && found == 0) {
|
|
m = NULL;
|
|
f = NULL;
|
|
if (strcmp(xml_name(xc), "CLICON_FEATURE") != 0)
|
|
continue;
|
|
/* CLICON_FEATURE is on the form <module>:<feature>.
|
|
* Split on colon to get module(m) and feature(f) respectively */
|
|
if (nodeid_split(xml_body(xc), &m, &f) < 0)
|
|
goto done;
|
|
if (m && f &&
|
|
(strcmp(m,"*")==0 ||
|
|
strcmp(m, module)==0) &&
|
|
(strcmp(f,"*")==0 ||
|
|
strcmp(f, feature)==0))
|
|
found = 1;
|
|
if (m) free(m);
|
|
if (f) free(f);
|
|
}
|
|
if ((cv = cv_new(CGV_BOOL)) == NULL){
|
|
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 */
|
|
ys->ys_mymodule = 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;
|
|
#if 1
|
|
/* Add unknown to vector in extension */
|
|
if ((cv = yang_cvec_add(yext, CGV_VOID, yang_argument_get(ys))) == NULL)
|
|
goto done;
|
|
cv_void_set(cv, ys);
|
|
#endif
|
|
retval = 0;
|
|
done:
|
|
if (prefix)
|
|
free(prefix);
|
|
if (id)
|
|
free(id);
|
|
return retval;
|
|
}
|
|
|
|
/*! Populate modules / submodules
|
|
*
|
|
* 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 */
|
|
|
|
/* Modules but not submodules have prefixes */
|
|
if ((yp = yang_find(ym, Y_PREFIX, NULL)) != NULL)
|
|
p0 = yang_argument_get(yp);
|
|
yi = NULL;
|
|
while ((yi = yn_each(ym, yi)) != NULL) {
|
|
if (yang_keyword_get(yi) != Y_IMPORT)
|
|
continue;
|
|
yp = yang_find(yi, Y_PREFIX, NULL);
|
|
pi = yang_argument_get(yp);
|
|
if (p0 && strcmp(p0, pi) == 0){ /* Check top-level */
|
|
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 */
|
|
yi2 = yi;
|
|
while ((yi2 = yn_each(ym, yi2)) != NULL) {
|
|
if (yang_keyword_get(yi2) != Y_IMPORT)
|
|
continue;
|
|
yp = yang_find(yi2, Y_PREFIX, NULL);
|
|
pi2 = yang_argument_get(yp);
|
|
if (strcmp(pi2, pi) == 0){
|
|
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
|
|
*
|
|
* @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_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 8525 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 filer or -1 for all
|
|
* @param[in] fn Callback
|
|
* @param[in] depth Depth argument: where to start. If <=0 call the calling node yn, if 1 start with its children, etc
|
|
* @param[in] arg Argument
|
|
* @retval n 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 > 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(ys->ys_argument, " ", &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 Kludge to know which regexp engine is used
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
* @see yang_type_cache_regexp_set where cache is extended w compiled regexps
|
|
*/
|
|
int
|
|
yang_type_cache_set(yang_stmt *ys,
|
|
yang_stmt *resolved,
|
|
int options,
|
|
cvec *cvv,
|
|
cvec *patterns,
|
|
uint8_t fraction)
|
|
{
|
|
int retval = -1;
|
|
yang_type_cache *ycache;
|
|
|
|
if (ys->ys_typecache != NULL){
|
|
clixon_err(OE_YANG, EEXIST, "yang type cache");
|
|
goto done;
|
|
}
|
|
if ((ys->ys_typecache = (yang_type_cache *)malloc(sizeof(*ycache))) == NULL){
|
|
clixon_err(OE_UNIX, errno, "malloc");
|
|
goto done;
|
|
}
|
|
ycache = ys->ys_typecache;
|
|
memset(ycache, 0, sizeof(*ycache));
|
|
ycache->yc_resolved = resolved;
|
|
ycache->yc_options = options;
|
|
if (cvv){
|
|
if ((ycache->yc_cvv = cvec_dup(cvv)) == NULL){
|
|
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;
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Extend yang type cache with compiled regexps
|
|
*
|
|
* Compiled Regexps are computed in validate code - after initial cache set
|
|
* @param[in] regexps
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*/
|
|
int
|
|
yang_type_cache_regexp_set(yang_stmt *ytype,
|
|
int rxmode,
|
|
cvec *regexps)
|
|
{
|
|
int retval = -1;
|
|
yang_type_cache *ycache;
|
|
|
|
assert(regexps);
|
|
assert(yang_keyword_get(ytype) == Y_TYPE);
|
|
assert((ycache = ytype->ys_typecache) != NULL);
|
|
assert(ycache->yc_regexps == NULL);
|
|
ycache->yc_rxmode = rxmode;
|
|
if ((ycache->yc_regexps = cvec_dup(regexps)) == NULL){
|
|
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
|
|
* @retval 1 OK
|
|
* @retval 0 No cache
|
|
* @retval -1 Error
|
|
*/
|
|
int
|
|
yang_type_cache_get(yang_stmt *ytype,
|
|
yang_stmt **resolved,
|
|
int *options,
|
|
cvec **cvv,
|
|
cvec *patterns,
|
|
int *rxmode,
|
|
cvec *regexps,
|
|
uint8_t *fraction)
|
|
{
|
|
int retval = -1;
|
|
cg_var *cv = NULL;
|
|
yang_type_cache *ycache;
|
|
|
|
ycache = ytype->ys_typecache;
|
|
if (ycache == NULL){ /* No cache return 0 */
|
|
retval = 0;
|
|
goto done;
|
|
}
|
|
if (resolved)
|
|
*resolved = ycache->yc_resolved;
|
|
if (options)
|
|
*options = ycache->yc_options;
|
|
if (cvv)
|
|
*cvv = ycache->yc_cvv;
|
|
if (patterns){
|
|
cv = NULL;
|
|
while ((cv = cvec_each(ycache->yc_patterns, cv)) != NULL)
|
|
cvec_append_var(patterns, cv);
|
|
}
|
|
if (regexps){
|
|
cv = NULL;
|
|
while ((cv = cvec_each(ycache->yc_regexps, cv)) != NULL)
|
|
cvec_append_var(regexps, cv);
|
|
}
|
|
if (rxmode)
|
|
*rxmode = ycache->yc_rxmode;
|
|
if (fraction)
|
|
*fraction = ycache->yc_fraction;
|
|
retval = 1; /* cache exists and is returned OK */
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Copy yang type cache
|
|
*/
|
|
static int
|
|
yang_type_cache_cp(yang_stmt *ynew,
|
|
yang_stmt *yold)
|
|
{
|
|
int retval = -1;
|
|
int options;
|
|
cvec *cvv;
|
|
cvec *patterns = NULL;
|
|
uint8_t fraction;
|
|
yang_stmt *resolved;
|
|
int ret;
|
|
|
|
if ((patterns = cvec_new(0)) == NULL){
|
|
clixon_err(OE_UNIX, errno, "cvec_new");
|
|
goto done;
|
|
}
|
|
/* Note, regexps are not copied since they are voids, if they were, they
|
|
* could not be freed in a simple way since copies are made at augment/group
|
|
*/
|
|
if ((ret = yang_type_cache_get(yold,
|
|
&resolved, &options, &cvv, patterns, NULL, NULL, &fraction)) < 0)
|
|
goto done;
|
|
if (ret == 1 &&
|
|
yang_type_cache_set(ynew, resolved, options, cvv, patterns, fraction) < 0)
|
|
goto done;
|
|
retval = 0;
|
|
done:
|
|
if (patterns)
|
|
cvec_free(patterns);
|
|
return retval;
|
|
}
|
|
|
|
/*! Free yang type cache
|
|
*/
|
|
static int
|
|
yang_type_cache_free(yang_type_cache *ycache)
|
|
{
|
|
cg_var *cv;
|
|
void *p;
|
|
|
|
if (ycache->yc_cvv)
|
|
cvec_free(ycache->yc_cvv);
|
|
if (ycache->yc_patterns)
|
|
cvec_free(ycache->yc_patterns);
|
|
if (ycache->yc_regexps){
|
|
cv = NULL;
|
|
while ((cv = cvec_each(ycache->yc_regexps, cv)) != NULL){
|
|
/* need to store mode since 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;
|
|
|
|
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;
|
|
}
|
|
yext = NULL; /* This loop gets complicated in the case the extension is augmented */
|
|
while ((yext = yn_each(ys, yext)) != NULL) {
|
|
if (yang_keyword_get(yext) != Y_UNKNOWN)
|
|
continue;
|
|
if ((ymod = ys_module(yext)) == NULL)
|
|
continue;
|
|
if ((ret = yang_find_prefix_by_namespace(ymod, ns, &prefix)) < 0)
|
|
goto done;
|
|
if (ret == 0) /* not found (this may happen in augment and maybe should be treated otherwise) */
|
|
continue;
|
|
cbuf_reset(cb);
|
|
cprintf(cb, "%s:%s", prefix, name);
|
|
if (strcmp(yang_argument_get(yext), cbuf_get(cb)) != 0)
|
|
continue;
|
|
break;
|
|
}
|
|
if (yext != NULL){ /* Found */
|
|
if (exist)
|
|
*exist = 1;
|
|
if (value &&
|
|
(cv = yang_cv_get(yext)) != NULL)
|
|
*value = cv_string_get(cv);
|
|
}
|
|
retval = 0;
|
|
done:
|
|
if (cb)
|
|
cbuf_free(cb);
|
|
return retval;
|
|
}
|
|
|
|
/* Sort substatements with when:s last */
|
|
static int
|
|
yang_sort_subelements_fn(const void* arg1,
|
|
const void* arg2)
|
|
{
|
|
yang_stmt *ys1 = *(yang_stmt **)arg1;
|
|
yang_stmt *ys2 = *(yang_stmt **)arg2;
|
|
int w1; /* ys1 has when substatement */
|
|
int w2;
|
|
|
|
w1 = yang_find(ys1, Y_WHEN, NULL) != NULL;
|
|
w2 = yang_find(ys2, Y_WHEN, NULL) != NULL;
|
|
if (w1 == w2)
|
|
return ys1->_ys_vector_i - ys2->_ys_vector_i;
|
|
else if (w1)
|
|
return 1;
|
|
else if (w2)
|
|
return -1;
|
|
else return ys1->_ys_vector_i - ys2->_ys_vector_i;
|
|
}
|
|
|
|
/*! Experimental code for sorting YANG children
|
|
*
|
|
* RFC 7950 7.5.7 and 7.8.5 says that containers and list sub elements are encoded in any order.
|
|
* with some exceptions, eg rpc/action input/output or list key elements
|
|
*
|
|
* Clixon by default encodes them in yang order. But this function can change that.
|
|
* For example, openconfig-network-instances.yang requires subelements with "when" statements last.
|
|
*/
|
|
int
|
|
yang_sort_subelements(yang_stmt *ys)
|
|
{
|
|
int retval = -1;
|
|
yang_stmt *yc = NULL;
|
|
|
|
if ((yang_keyword_get(ys) == Y_CONTAINER ||
|
|
yang_keyword_get(ys) == Y_LIST)){
|
|
/* This enumerates _ys_vector_i in ys->ys_stmt vector */
|
|
while ((yc = yn_each(ys, yc)) != NULL) ;
|
|
qsort(ys->ys_stmt, ys->ys_len, sizeof(ys), yang_sort_subelements_fn);
|
|
}
|
|
retval = 0;
|
|
// done:
|
|
return retval;
|
|
}
|
|
|
|
int
|
|
yang_init(clixon_handle h)
|
|
{
|
|
return yang_cardinality_init(h);
|
|
}
|
|
|
|
#ifdef XML_EXPLICIT_INDEX
|
|
/*! Mark element as search_index in list
|
|
*
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*/
|
|
int
|
|
yang_list_index_add(yang_stmt *ys)
|
|
{
|
|
int retval = -1;
|
|
yang_stmt *yp;
|
|
|
|
if ((yp = yang_parent_get(ys)) == NULL ||
|
|
yang_keyword_get(yp) != Y_LIST){
|
|
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 = NULL;
|
|
int i;
|
|
enum rfc_6020 keyw;
|
|
|
|
/* Match parent */
|
|
/* If container, check it is Non-presence */
|
|
if (yang_keyword_get(ys) == Y_CONTAINER){
|
|
if (yang_find(ys, Y_PRESENCE, NULL) != NULL)
|
|
return 0;
|
|
}
|
|
/* Ensure a single list child and no other data nodes */
|
|
i = 0; /* Number of list nodes */
|
|
while ((yc = yn_each(ys, yc)) != NULL) {
|
|
keyw = yang_keyword_get(yc);
|
|
/* case/choice could hide anything so disqualify those */
|
|
if (keyw == Y_CASE || keyw == Y_CHOICE)
|
|
break;
|
|
if (!yang_datanode(yc)) /* Allowed, check next */
|
|
continue;
|
|
if (keyw != subkeyw) /* Another datanode than subkeyw */
|
|
break;
|
|
if (i++>0) /* More than one list (or could this work?) */
|
|
break;
|
|
}
|
|
if (yc != NULL) /* break from loop */
|
|
return 0;
|
|
if (i != 1) /* List found */
|
|
return 0;
|
|
return 1; /* Passed all tests: yes you can hide this keyword */
|
|
}
|
|
|
|
/*! Get action callback list
|
|
*
|
|
* XXX rc shouldnt really be void* but the type definitions in .h file got complicated
|
|
*/
|
|
void *
|
|
yang_action_cb_get(yang_stmt *ys)
|
|
{
|
|
return ys->ys_action_cb;
|
|
}
|
|
|
|
/*! Add an action callback to YANG node
|
|
*
|
|
* XXX rc shouldnt really be void* but the type definitions in .h file got complicated
|
|
*/
|
|
int
|
|
yang_action_cb_add(yang_stmt *ys,
|
|
void *arg)
|
|
{
|
|
rpc_callback_t *rc = (rpc_callback_t *)arg;
|
|
|
|
if (rc == NULL){
|
|
clixon_err(OE_YANG, EINVAL, "arg is NULL");
|
|
return -1;
|
|
}
|
|
ADDQ(rc, ys->ys_action_cb);
|
|
return 0;
|
|
}
|