* Added stricter check on schema-node identifier checking, such as for augments.

* These checks are now made at YANG loading time
This commit is contained in:
Olof hagsand 2020-09-29 20:53:24 +02:00
parent 101a8cb6e0
commit b3545871c0
8 changed files with 383 additions and 202 deletions

View file

@ -46,7 +46,8 @@ Users may have to change how they access the system
### Minor changes
*
* Added stricter check on schema-node identifier checking, such as for augments.
* These checks are now made at YANG loading time
* Added sanity check that a yang module name matches the filename
### Corrected Bugs
@ -58,7 +59,7 @@ Users may have to change how they access the system
14 September 2020
This release is primarily a bugfix and usability improvement release, no major new features.
ppp
### API changes on existing protocol/config features
Users may have to change how they access the system

View file

@ -51,9 +51,10 @@
/*
* Yang flags used in
*/
#define YANG_FLAG_MARK 0x01 /* (Dynamic) marker for dynamic algorithms, eg expand */
#define YANG_FLAG_MARK 0x01 /* (Dynamic) marker for dynamic algorithms, eg expand and DAG */
#define YANG_FLAG_TMP 0x02 /* (Dynamic) marker for dynamic algorithms, eg DAG detection */
#ifdef XML_EXPLICIT_INDEX
#define YANG_FLAG_INDEX 0x02 /* This yang node under list is (extra) index. --> you can access
#define YANG_FLAG_INDEX 0x04 /* This yang node under list is (extra) index. --> you can access
* list elements using this index with binary search */
#endif
@ -240,11 +241,8 @@ int ys_populate2(yang_stmt *ys, void *arg);
int yang_apply(yang_stmt *yn, enum rfc_6020 key, yang_applyfn_t fn,
void *arg);
int yang_datanode(yang_stmt *ys);
int yang_abs_schema_nodeid(yang_stmt *yspec, yang_stmt *ys,
char *schema_nodeid,
enum rfc_6020 keyword, yang_stmt **yres);
int yang_desc_schema_nodeid(yang_stmt *yn, char *schema_nodeid,
enum rfc_6020 keyword, yang_stmt **yres);
int yang_abs_schema_nodeid(yang_stmt *ys, char *schema_nodeid, yang_stmt **yres);
int yang_desc_schema_nodeid(yang_stmt *yn, char *schema_nodeid, yang_stmt **yres);
int yang_mandatory(yang_stmt *ys);
int yang_config(yang_stmt *ys);
int yang_config_ancestor(yang_stmt *ys);

View file

@ -707,6 +707,7 @@ ys_replace(yang_stmt *yorig,
* @retval 0 OK
* @retval -1 Error
* Also add parent to child as up-pointer
* @see ys_prune
*/
int
yn_insert(yang_stmt *ys_parent,
@ -732,6 +733,7 @@ yn_insert(yang_stmt *ys_parent,
* }
* @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,
@ -997,6 +999,10 @@ yang_find_myprefix(yang_stmt *ys)
/* Not good enough with submodule, must be actual module */
if (ys_real_module(ys, &ymod) < 0)
goto done;
if (ymod == NULL){
clicon_err(OE_YANG, ENOENT, "Internal error: no module");
goto done;
}
if ((yprefix = yang_find(ymod, Y_PREFIX, NULL)) == NULL){
clicon_err(OE_YANG, ENOENT, "No prefix found for module %s", yang_argument_get(ymod));
goto done;
@ -2523,80 +2529,90 @@ yang_datanode(yang_stmt *ys)
}
/*! All the work for schema_nodeid functions both absolute and descendant
* Ignore prefixes, see _abs
* @param[in] yn Yang node. Find next yang stmt and return that if match.
* @param[in] vec Vector of nodeid's in a schema node identifier, eg a/b
* @param[in] nvec Length of vec
* @param[in] keyword A schemode of this type, or -1 if any
*
* @param[in] yn Yang node. For absolute schemanodeids this should be a module, otherwise any yang
* @param[in] cvv Schema-node path encoded as a name/value pair list.
* @param[in] nsc Namespace context from yang for the prefixes (names) of cvv
* @param[out] yres Result yang statement node, or NULL if not found
* @retval -1 Error, with clicon_err called
* @retval 0 OK
* A schema node identifier consists of a path of identifiers, separated by slashes ("/").
* References to identifiers defined in external modules MUST be
* qualified with appropriate prefixes, and references to identifiers
* defined in the current module and its submodules MAY use a prefix.
* prefixes are implemented by cvv names, and id:s by cvv strings.
* A namespace context of the original module is nsc as prefix context.
*
* @see RFC7950 Sec 6.5
*/
static int
schema_nodeid_vec(yang_stmt *yn,
char **vec,
int nvec,
enum rfc_6020 keyword,
schema_nodeid_iterate(yang_stmt *yn,
cvec *nodeid_cvv,
cvec *nsc,
yang_stmt **yres)
{
int retval = -1;
char *arg;
yang_stmt *ynext;
char *nodeid = NULL;
int i;
yang_stmt *ymod;
char *prefix; /* node-identifier = [prefix ":"] identifier */
char *id;
yang_stmt *ys;
int match;
yang_stmt *ym2;
yang_stmt *yp;
cg_var *cv;
char *ns;
yang_stmt *yspec;
if (nvec <= 0)
yspec = ys_spec(yn);
yp = yn;
/* Iterate over node identifiers /prefix:id/... */
cv = NULL;
while ((cv = cvec_each(nodeid_cvv, cv)) != NULL){
prefix = cv_name_get(cv);
id = cv_string_get(cv);
/* Top level is repeated from abs case, but here this is done to match with
* matching module below
* Get namespace */
if ((ns = xml_nsctx_get(nsc, prefix)) == NULL){
clicon_err(OE_YANG, EFAULT, "No namespace for prefix: %s in schema node identifier in module %s",
prefix,
yang_argument_get(ys_module(yn)));
goto done;
arg = vec[0];
clicon_debug(2, "%s: key=%s arg=%s match=%s len=%d",
__FUNCTION__, yang_key2str(yn->ys_keyword), yn->ys_argument,
arg, yn->ys_len);
if (strcmp(arg, "..") == 0)
ynext = yn->ys_parent; /* This could actually be a MODULE */
else{
/* ignore prefixes */
if ((nodeid = strchr(arg, ':')) == NULL)
nodeid = arg;
else
nodeid++;
match = 0;
}
/* Get yang module */
if ((ymod = yang_find_module_by_namespace(yspec, ns)) == NULL){
clicon_err(OE_YANG, EFAULT, "No module for namespace: %s", ns);
goto done;
}
/* Iterate over children of current node to get a match
* XXX namespace?????
*/
ys = NULL;
for (i=0; i<yn->ys_len; i++){
ys = yn->ys_stmt[i];
while ((ys = yn_each(yp, ys)) != NULL) {
if (!yang_schemanode(ys))
continue;
if ((int)keyword != -1 && keyword != ys->ys_keyword)
continue;
/* some keys dont have arguments, match on key */
if (ys->ys_keyword == Y_INPUT || ys->ys_keyword == Y_OUTPUT){
if (strcmp(nodeid, yang_key2str(ys->ys_keyword)) == 0){
match++;
break;
}
} else
if (ys->ys_argument && strcmp(nodeid, ys->ys_argument) == 0){
match++;
if (strcmp(id, yang_key2str(ys->ys_keyword)) == 0){
break;
}
}
if (!match){
clicon_debug(1, "%s: %s not found", __FUNCTION__, nodeid);
else {
if (ys->ys_argument && strcmp(id, ys->ys_argument) == 0){
/* Also check for right prefix/module */
ym2 = ys->ys_mymodule?ys->ys_mymodule:ys_module(ys);
if (ym2 == ymod)
break;
}
}
} /* while ys */
if (ys == NULL){
clicon_debug(1, "%s: %s not found", __FUNCTION__, id);
goto ok;
}
ynext = ys;
}
if (nvec == 1){ /* match */
if (yang_schemanode((yang_stmt*)ynext))
*yres = (yang_stmt*)ynext;
else
clicon_debug(1, "%s not schema node", arg);
goto ok;
}
/* recursive call using ynext */
if (schema_nodeid_vec(ynext, vec+1, nvec-1, keyword, yres) < 0)
goto done;
yp = ys;
} /* while cv */
assert(yp && yang_schemanode((yang_stmt*)yp));
*yres = (yang_stmt*)yp;
ok:
retval = 0;
done:
@ -2610,80 +2626,82 @@ schema_nodeid_vec(yang_stmt *yn,
* @param[in] keyword A schemode of this type, or -1 if any
* @param[out] yres Result yang statement node, or NULL if not found
* @retval -1 Error, with clicon_err called
* @retval 0 OK (if yres set then found, if yres=0 then not found)
* @retval 0 OK , with result in yres
* Assume schema nodeid:s have prefixes, (actually the first).
* @see yang_desc_schema_nodeid
* @see RFC7950 6.5
* o schema node: A node in the schema tree. One of action, container,
* leaf, leaf-list, list, choice, case, rpc, input, output,
* notification, anydata, and anyxml.
* Used in yang: deviation, top-level augment
* @see yang_desc_schema_nodeid
*/
int
yang_abs_schema_nodeid(yang_stmt *yspec,
yang_stmt *yn,
yang_abs_schema_nodeid(yang_stmt *yn,
char *schema_nodeid,
enum rfc_6020 keyword,
yang_stmt **yres)
{
int retval = -1;
char **vec = NULL;
int nvec;
yang_stmt *ymod = NULL;
char *id;
char *prefix = NULL;
yang_stmt *yprefix;
cvec *nodeid_cvv = NULL;
cvec *nsc = NULL;
cg_var *cv;
char *prefix;
char *ns;
yang_stmt *yspec;
yang_stmt *ymod;
char *str;
*yres = NULL;
yspec = ys_spec(yn);
/* check absolute schema_nodeid */
if (schema_nodeid[0] != '/'){
clicon_err(OE_YANG, EINVAL, "absolute schema nodeid should start with /");
goto done;
}
if ((vec = clicon_strsep(schema_nodeid, "/", &nvec)) == NULL){
clicon_err(OE_YANG, errno, "strsep");
/* Split nodeid on the form /p0:i0/p1:i1 to a cvec with [name:p0 value:i0][...]
*/
if (str2cvec(schema_nodeid, '/', ':', &nodeid_cvv) < 0)
goto done;
}
/* Assume schema nodeid looks like: "/prefix:id[/prefix:id]*" */
if (nvec < 2){
clicon_err(OE_YANG, 0, "NULL or truncated path: %s", schema_nodeid);
goto done;
}
/* split <prefix>:<id> */
if ((id = strchr(vec[1], ':')) == NULL){ /* no prefix */
clicon_log(LOG_WARNING, "%s: Absolute schema nodeid %s must have prefix", __FUNCTION__, schema_nodeid);
if (cvec_len(nodeid_cvv) == 0)
goto ok;
}
if ((prefix = strdup(vec[1])) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
/* 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 ((str = cv_string_get(cv)) == NULL || !strlen(str)){
if (cv_string_set(cv, cv_name_get(cv)) < 0){
clicon_err(OE_UNIX, errno, "cv_string_set");
goto done;
}
prefix[id-vec[1]] = '\0';
id++;
if (yn) /* Find module using local prefix definition */
ymod = yang_find_module_by_prefix(yn, prefix);
if (ymod == NULL){ /* Try (global) prefix the module itself uses */
ymod = NULL;
while ((ymod = yn_each(yspec, ymod)) != NULL) {
if ((yprefix = yang_find(ymod, Y_PREFIX, NULL)) != NULL &&
strcmp(yprefix->ys_argument, prefix) == 0){
break;
cv_name_set(cv, NULL);
}
}
}
if (ymod == NULL){
clicon_err(OE_YANG, 0, "No module with prefix %s found", prefix);
/* Make a namespace context from yang for the prefixes (names) of nodeid_cvv */
if (xml_nsctx_yang(yn, &nsc) < 0)
goto done;
/* Since this is an _absolute_ schema nodeid start from top
* Get namespace */
cv = cvec_i(nodeid_cvv, 0);
prefix = cv_name_get(cv);
if ((ns = xml_nsctx_get(nsc, prefix)) == NULL){
clicon_err(OE_YANG, EFAULT, "No namespace for prefix: %s in schema node identifier: %s in module %s",
prefix, schema_nodeid, yang_argument_get(ys_module(yn)));
goto done;
}
if (schema_nodeid_vec(ymod, vec+1, nvec-1, keyword, yres) < 0)
/* Get yang module */
if ((ymod = yang_find_module_by_namespace(yspec, ns)) == NULL){
clicon_err(OE_YANG, EFAULT, "No module for namespace: %s in schema node identifier: %s",
ns, schema_nodeid);
goto done;
ok: /* yres may not be set */
}
/* Iterate through cvv to find schemanode using ymod as starting point (since it is absolute) */
if (schema_nodeid_iterate(ymod, nodeid_cvv, nsc, yres) < 0)
goto done;
ok:
retval = 0;
done:
if (vec)
free(vec);
if (prefix)
free(prefix);
if (nodeid_cvv)
cvec_free(nodeid_cvv);
if (nsc)
cvec_free(nsc);
return retval;
}
@ -2694,35 +2712,60 @@ yang_abs_schema_nodeid(yang_stmt *yspec,
* @param[out] yres First yang node matching schema nodeid
* @retval 0 OK
* @retval -1 Error, with clicon_err called
* @see yang_abs_schema_nodeid
* Used in yang: unique, refine, uses augment
* @see yang_abs_schema_nodeid
*/
int
yang_desc_schema_nodeid(yang_stmt *yn,
char *schema_nodeid,
enum rfc_6020 keyword,
yang_stmt **yres)
{
int retval = -1;
char **vec = NULL;
int nvec;
cvec *nodeid_cvv = NULL;
cg_var *cv;
char *str;
cvec *nsc = NULL;
*yres = NULL;
if (strlen(schema_nodeid) == 0)
if (schema_nodeid == NULL || strlen(schema_nodeid) == 0){
clicon_err(OE_YANG, EINVAL, "nodeid is empty");
goto done;
}
*yres = NULL;
/* check absolute schema_nodeid */
if (schema_nodeid[0] == '/'){
clicon_err(OE_YANG, EINVAL, "descendant schema nodeid should not start with /");
goto done;
}
if ((vec = clicon_strsep(schema_nodeid, "/", &nvec)) == NULL)
/* Split nodeid on the form /p0:i0/p1:i1 to a cvec with [name:p0 value:i0][...]
*/
if (str2cvec(schema_nodeid, '/', ':', &nodeid_cvv) < 0)
goto done;
if (schema_nodeid_vec(yn, vec, nvec, keyword, yres) < 0)
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 ((str = cv_string_get(cv)) == NULL || !strlen(str)){
if (cv_string_set(cv, cv_name_get(cv)) < 0){
clicon_err(OE_UNIX, errno, "cv_string_set");
goto done;
}
cv_name_set(cv, NULL);
}
}
/* Make a namespace context from yang for the prefixes (names) of nodeid_cvv */
if (xml_nsctx_yang(yn, &nsc) < 0)
goto done;
/* Iterate through cvv to find schemanode using yn as relative starting point */
if (schema_nodeid_iterate(yn, nodeid_cvv, nsc, yres) < 0)
goto done;
ok:
retval = 0;
done:
if (vec)
free(vec);
if (nsc)
cvec_free(nsc);
if (nodeid_cvv)
cvec_free(nodeid_cvv);
return retval;
}

View file

@ -212,8 +212,7 @@ ys_grouping_resolve(yang_stmt *yuses,
* struct to the yang statements being inserted.
*/
static int
yang_augment_node(yang_stmt *ys,
yang_stmt *ysp)
yang_augment_node(yang_stmt *ys)
{
int retval = -1;
char *schema_nodeid;
@ -229,14 +228,15 @@ yang_augment_node(yang_stmt *ys,
clicon_err(OE_YANG, 0, "My yang module not found");
goto done;
}
/* */
schema_nodeid = yang_argument_get(ys);
clicon_debug(2, "%s %s", __FUNCTION__, schema_nodeid);
/* Find the target */
if (yang_abs_schema_nodeid(ysp, ys, schema_nodeid, -1, &ytarget) < 0)
if (yang_abs_schema_nodeid(ys, schema_nodeid, &ytarget) < 0)
goto done;
if (ytarget == NULL){
#if 0
#if 0 /* Lots of yang models fail here */
clicon_err(OE_YANG, 0, "Augment failed in module %s: target node %s not found",
yang_argument_get(ys_module(ys)),
schema_nodeid);
@ -281,33 +281,31 @@ yang_augment_node(yang_stmt *ys,
return retval;
}
/*! Find all top-level augments and change original datamodels. */
/*! Find all top-level augments in a module and change original datamodels.
* @param[in] ymod Yang statement of type module/sub-module
* @retval 0 OK
* @retval -1 Error
* Note there is an ordering problem, where an augment in one module depends on an augment in
* another module not yet augmented.
*/
static int
yang_augment_spec(yang_stmt *ysp,
int modnr)
yang_augment_module(yang_stmt *ymod)
{
int retval = -1;
yang_stmt *ym;
yang_stmt *ys;
int i;
int j;
i = modnr; /* cant use yang_each here since you dont start at 0 */
while (i < yang_len_get(ysp)){ /* Loop through modules and sub-modules */
ym = ysp->ys_stmt[i++];
j = 0;
while (j < yang_len_get(ym)){ /* Top-level symbols in modules */
ys = ym->ys_stmt[j++];
ys = NULL;
while ((ys = yn_each(ymod, ys)) != NULL){
switch (yang_keyword_get(ys)){
case Y_AUGMENT: /* top-level */
if (yang_augment_node(ys, ysp) < 0)
if (yang_augment_node(ys) < 0)
goto done;
break;
default:
break;
}
}
}
retval = 0;
done:
return retval;
@ -457,9 +455,12 @@ yang_expand_grouping(yang_stmt *yn)
goto done;
}
/* Make a copy of the grouping, then make refinements to this copy
* Note this ygrouping2 object does not gave a parent and does not work in many
* functions which assume a full hierarchy, use the original ygrouping in those cases.
*/
if ((ygrouping2 = ys_dup(ygrouping)) == NULL)
goto done;
/* Only replace data/schemanodes:
* Compute the number of such nodes, and extend the child vector with that below
*/
@ -495,9 +496,8 @@ yang_expand_grouping(yang_stmt *yn)
if (yang_keyword_get(yr) != Y_REFINE)
continue;
/* Find a node */
if (yang_desc_schema_nodeid(ygrouping2,
if (yang_desc_schema_nodeid(ygrouping, /* Cannot use ygrouping2 */
yang_argument_get(yr),
-1,
&yrt) < 0)
goto done;
/* Not found, try next */
@ -865,7 +865,7 @@ yang_parse_module(clicon_handle h,
* RFC 7950 Sec 5.2
*/
if (strcmp(yang_argument_get(ymod), module) != 0){
clicon_err(OE_YANG, EINVAL, "File %s contains yang module \"%s\" which does not expected module %s",
clicon_err(OE_YANG, EINVAL, "File %s contains yang module \"%s\" which does not match expected module %s",
filename,
yang_argument_get(ymod),
module);
@ -953,7 +953,6 @@ ys_schemanode_check(yang_stmt *ys,
void *dummy)
{
int retval = -1;
yang_stmt *yspec;
yang_stmt *yres = NULL;
yang_stmt *yp;
char *arg;
@ -973,7 +972,7 @@ ys_schemanode_check(yang_stmt *ys,
break;
/* fallthru */
case Y_REFINE:
if (yang_desc_schema_nodeid(yp, arg, -1, &yres) < 0)
if (yang_desc_schema_nodeid(yp, arg, &yres) < 0)
goto done;
if (yres == NULL){
clicon_err(OE_YANG, 0, "schemanode sanity check of %s %s",
@ -988,7 +987,7 @@ ys_schemanode_check(yang_stmt *ys,
goto done;
for (i=0; i<nvec; i++){
v = vec[i];
if (yang_desc_schema_nodeid(yp, v, -1, &yres) < 0)
if (yang_desc_schema_nodeid(yp, v, &yres) < 0)
goto done;
if (yres == NULL){
clicon_err(OE_YANG, 0, "schemanode sanity check of %s %s",
@ -999,8 +998,7 @@ ys_schemanode_check(yang_stmt *ys,
break;
}
case Y_DEVIATION:
yspec = ys_spec(ys);
if (yang_abs_schema_nodeid(yspec, ys, arg, -1, &yres) < 0)
if (yang_abs_schema_nodeid(ys, arg, &yres) < 0)
goto done;
if (yres == NULL){
clicon_err(OE_YANG, 0, "schemanode sanity check of %s", arg);
@ -1086,11 +1084,121 @@ ys_list_check(clicon_handle h,
return retval;
}
/*! Depth-first topological sort
* Topological sort of a DAG
* @param[in] yn Yang module node
* @param[out] ylist Result list of sorted nodes with "least significant" first
* @param[out] ylen Length of ylist
* @retval 0 OK
* @retval -1 Error. Eg circular
* see eg https://en.wikipedia.org/wiki/Topological_sorting#Depth-first_search
*/
static int
ys_visit(struct yang_stmt *yn,
struct yang_stmt ***ylist,
int *ylen)
{
int retval = -1;
int i;
struct yang_stmt *yi; /* import / include */
struct yang_stmt *yspec;
struct yang_stmt *ymod;
if (yn == NULL ||
(yang_keyword_get(yn) != Y_MODULE && yang_keyword_get(yn) != Y_SUBMODULE)){
clicon_err(OE_YANG, EINVAL, "Expected module or submodule");
goto done;
}
yspec = ys_spec(yn);
/* if n has a permanent mark then return */
if (yang_flag_get(yn, YANG_FLAG_MARK))
return 0;
/* if n has a temporary mark then stop (not a DAG) */
if (yang_flag_get(yn, YANG_FLAG_TMP)){
clicon_err(OE_YANG, EFAULT, "Yang module %s import/include is circular", yang_argument_get(yn));
goto done;
}
/* mark n with a temporary mark */
yang_flag_set(yn, YANG_FLAG_TMP);
/* Loop through import and include statements and visit each */
yi = NULL;
for (i=0; i<yang_len_get(yn); i++){
yi = yang_child_i(yn, i);
if (yang_keyword_get(yi) != Y_IMPORT &&
yang_keyword_get(yi) != Y_INCLUDE)
continue;
if ((ymod = yang_find(yspec, Y_MODULE, yang_argument_get(yi))) == NULL &&
(ymod = yang_find(yspec, Y_SUBMODULE, yang_argument_get(yi))) == NULL){
clicon_err(OE_YANG, EFAULT, "Yang module %s import/include not found",
yang_argument_get(yi)); /* shouldnt happen */
goto done;
}
if (ys_visit(ymod, ylist, ylen) < 0)
goto done;
}
/* remove temporary mark from n */
yang_flag_reset(yn, YANG_FLAG_TMP);
/* mark n with a permanent mark */
yang_flag_set(yn, YANG_FLAG_MARK);
/* add n to head of L. NB reversed */
(*ylen)++;
if ((*ylist = realloc(*ylist, (*ylen)*sizeof(yang_stmt *))) == 0){
clicon_err(OE_YANG, errno, "realloc");
goto done;
}
(*ylist)[*ylen - 1] = yn;
retval = 0;
done:
return retval;
}
/*! Sort module/submodules according to import/include order and cycle detect
* Topological sort of a DAG
* @param[in] yspec Yang specification.
* @param[in] modmin Start of interval of yspec:s module children
* @param[in] modmax End of interval
* @param[out] ylist Result list of sorted nodes with "least significant" first
* @param[out] ylen Length of ylist
* see eg https://en.wikipedia.org/wiki/Topological_sorting#Depth-first_search
*/
static int
yang_sort_modules(yang_stmt *yspec,
int modmin,
int modmax,
struct yang_stmt ***ylist,
int *ylen)
{
int retval = -1;
int i;
struct yang_stmt *yn;
for (i=modmin; i<modmax; i++){
yn = yang_child_i(yspec, i);
/* select an unmarked node n */
if (yang_flag_get(yn, YANG_FLAG_MARK|YANG_FLAG_TMP) == 0){
if (ys_visit(yn, ylist, ylen) < 0)
goto done;
}
}
if (*ylen != modmax-modmin){
clicon_err(OE_YANG, EFAULT, "Internal error: mismatch sort vector lengths");
}
/* Unmark all nodes */
for (i=modmin; i<modmax; i++){
yn = yang_child_i(yspec, i);
yang_flag_set(yn, YANG_FLAG_MARK|YANG_FLAG_TMP);
}
retval = 0;
done:
return retval;
}
/*! Parse top yang module including all its sub-modules. Expand and populate yang tree
*
* Perform secondary actions after yang parsing. These actions cannot be made at
* parse-time for various reasons.
* These includes:
* parse-time for various reasons:
* - Detect imported yang specs that are not loaded and load and parse them too
* - Check cardinality of yang (that nr of children match)
* - Check features: remove disabled
@ -1098,43 +1206,59 @@ ys_list_check(clicon_handle h,
* - Resolve types
* - Augments
* - Defaults
* There is some complexity in how modules are loaded vs how they need to be augmented
* Therefore, after full loading, a topological sort is made to ensure the modules are
* non-circular (a DAG) and that the rest of the operations are made in the topology order.
* The loading order of the yang models (under yang spec) is kept.
*
* @param[in] h CLICON handle
* @param[in] yspec Yang specification.
* @param[in] modnr Perform checks after this number, prior are already complete
* @param[in] modmin Perform checks after this number, prior are already complete
* @retval 0 Everything OK
* @retval -1 Error encountered
*/
static int
yang_parse_post(clicon_handle h,
yang_stmt *yspec,
int modnr)
int modmin)
{
int retval = -1;
int i;
int modmax;
struct yang_stmt **ylist = NULL; /* Topology sorted modules */
int ylen = 0; /* Length of ylist */
/* 1: Parse from text to yang parse-tree.
* Iterate through modules and detect module/submodules to parse
* - note the list may grow on each iteration */
for (i=modnr; i<yang_len_get(yspec); i++)
if (yang_parse_recurse(h, yspec->ys_stmt[i], yspec) < 0)
* NOTE: the list may grow on each iteration */
for (i=modmin; i<yang_len_get(yspec); i++)
if (yang_parse_recurse(h, yang_child_i(yspec, i), yspec) < 0)
goto done;
/* 2. Check cardinality maybe this should be done after grouping/augment */
for (i=modnr; i<yang_len_get(yspec); i++)
if (yang_cardinality(h, yspec->ys_stmt[i], yang_argument_get(yspec->ys_stmt[i])) < 0)
modmax = yang_len_get(yspec);
/* The set of modules [modmin..maxmax] is here complete wrt imports/includes and is a DAG
* Example: A imports B, C and D, and C and D imports B
* In some operations below (eg augment) need to be in topology order, eg B first.
* Therefore the modules are sorted into a separate list that is used henceforth
*/
if (yang_sort_modules(yspec, modmin, yang_len_get(yspec), &ylist, &ylen) < 0)
goto done;
/* 2. Check cardinality a first time (done again last) */
for (i=modmin; i<modmax; i++)
if (yang_cardinality(h, yang_child_i(yspec, i), yang_argument_get(yspec->ys_stmt[i])) < 0)
goto done;
/* 3: Check features/if-features: check if enabled and remove disabled features */
for (i=modnr; i<yang_len_get(yspec); i++)
if (yang_features(h, yspec->ys_stmt[i]) < 0)
for (i=modmin; i<modmax; i++)
if (yang_features(h, yang_child_i(yspec, i)) < 0)
goto done;
/* 4: Go through parse tree and populate it with cv types */
for (i=modnr; i<yang_len_get(yspec); i++){
if (ys_populate(yspec->ys_stmt[i], h) < 0) /* Alt: make a yang_apply0 */
for (i=modmin; i<modmax; i++){
if (ys_populate(yang_child_i(yspec, i), h) < 0) /* Alt: make a yang_apply0 */
goto done;
if (yang_apply(yspec->ys_stmt[i], -1, ys_populate, (void*)h) < 0)
if (yang_apply(yang_child_i(yspec, i), -1, ys_populate, (void*)h) < 0)
goto done;
}
@ -1142,8 +1266,8 @@ yang_parse_post(clicon_handle h,
* from ys_populate step.
* Must be done using static binding.
*/
for (i=modnr; i<yang_len_get(yspec); i++)
if (yang_apply(yspec->ys_stmt[i], Y_TYPE, ys_resolve_type, h) < 0)
for (i=modmin; i<modmax; i++)
if (yang_apply(yang_child_i(yspec, i), Y_TYPE, ys_resolve_type, h) < 0)
goto done;
/* Up to here resolving is made in the context they are defined, rather
@ -1154,36 +1278,43 @@ yang_parse_post(clicon_handle h,
*/
/* 6: Macro expansion of all grouping/uses pairs. Expansion needs marking */
for (i=modnr; i<yang_len_get(yspec); i++){
if (yang_expand_grouping(yspec->ys_stmt[i]) < 0)
for (i=0; i<ylen; i++){
if (yang_expand_grouping(ylist[i]) < 0)
goto done;
yang_apply(yspec->ys_stmt[i], -1, (yang_applyfn_t*)yang_flag_reset, (void*)YANG_FLAG_MARK);
yang_apply(ylist[i], -1, (yang_applyfn_t*)yang_flag_reset, (void*)YANG_FLAG_MARK);
}
/* 7: Top-level augmentation of all modules. (Augment also in uses) */
if (yang_augment_spec(yspec, modnr) < 0)
/* 7: Top-level augmentation of all modules.
* Note: Clixon does not implement augment in USES
* Note: There is an ordering problem, where an augment in one module depends on an augment in
* another module not yet augmented.
*/
for (i=0; i<ylen; i++)
if (yang_augment_module(ylist[i]) < 0)
goto done;
/* 4: Go through parse tree and do 2nd step populate (eg default) */
for (i=modnr; i<yang_len_get(yspec); i++)
if (yang_apply(yspec->ys_stmt[i], -1, ys_populate2, (void*)h) < 0)
for (i=0; i<ylen; i++)
if (yang_apply(ylist[i], -1, ys_populate2, (void*)h) < 0)
goto done;
/* 8: sanity checks of expanded yangs need more here */
for (i=modnr; i<yang_len_get(yspec); i++){
for (i=0; i<ylen; i++){
/* Check schemanode references */
if (yang_apply(yspec->ys_stmt[i], -1, ys_schemanode_check, NULL) < 0)
if (yang_apply(ylist[i], -1, ys_schemanode_check, NULL) < 0)
goto done;
/* Check list key values */
if (ys_list_check(h, yspec->ys_stmt[i]) < 0)
if (ys_list_check(h, ylist[i]) < 0)
goto done;
}
/* 9. Check cardinality a second time after grouping/augment etc */
for (i=modnr; i<yang_len_get(yspec); i++)
if (yang_cardinality(h, yspec->ys_stmt[i], yang_argument_get(yspec->ys_stmt[i])) < 0)
for (i=0; i<ylen; i++)
if (yang_cardinality(h, ylist[i], yang_argument_get(ylist[i])) < 0)
goto done;
retval = 0;
done:
if (ylist)
free(ylist);
return retval;
}
@ -1203,7 +1334,7 @@ yang_spec_parse_module(clicon_handle h,
yang_stmt *yspec)
{
int retval = -1;
int modnr; /* Existing number of modules */
int modmin; /* Existing number of modules */
char *base = NULL;;
if (yspec == NULL){
@ -1214,14 +1345,14 @@ yang_spec_parse_module(clicon_handle h,
clicon_err(OE_YANG, EINVAL, "yang module not set");
goto done;
}
/* Apply steps 2.. on new modules, ie ones after modnr. */
modnr = yang_len_get(yspec);
/* Apply steps 2.. on new modules, ie ones after modmin. */
modmin = yang_len_get(yspec);
/* Do not load module if it already exists */
if (yang_find(yspec, Y_MODULE, module) != NULL)
goto ok;
if (yang_parse_module(h, module, revision, yspec) == NULL)
goto done;
if (yang_parse_post(h, yspec, modnr) < 0)
if (yang_parse_post(h, yspec, modmin) < 0)
goto done;
ok:
retval = 0;
@ -1247,11 +1378,11 @@ yang_spec_parse_file(clicon_handle h,
yang_stmt *yspec)
{
int retval = -1;
int modnr; /* Existing number of modules */
int modmin; /* Existing number of modules */
char *base = NULL;;
/* Apply steps 2.. on new modules, ie ones after modnr. */
modnr = yang_len_get(yspec);
/* Apply steps 2.. on new modules, ie ones after modmin. */
modmin = yang_len_get(yspec);
/* Find module, and do not load file if module already exists */
if (basename(filename) == NULL){
clicon_err(OE_YANG, errno, "No basename");
@ -1267,7 +1398,7 @@ yang_spec_parse_file(clicon_handle h,
goto ok;
if (yang_parse_filename(filename, yspec) == NULL)
goto done;
if (yang_parse_post(h, yspec, modnr) < 0)
if (yang_parse_post(h, yspec, modmin) < 0)
goto done;
ok:
retval = 0;
@ -1304,7 +1435,7 @@ yang_spec_load_dir(clicon_handle h,
int j;
char filename[MAXPATHLEN];
char *base = NULL; /* filename without dir */
int modnr;
int modmin;
yang_stmt *ym; /* yang module */
yang_stmt *ym0; /* (existing) yang module */
yang_stmt *yrev; /* yang revision */
@ -1324,8 +1455,8 @@ yang_spec_load_dir(clicon_handle h,
goto done;
if (ndp == 0)
goto ok;
/* Apply post steps on new modules, ie ones after modnr. */
modnr = yang_len_get(yspec);
/* Apply post steps on new modules, ie ones after modmin. */
modmin = yang_len_get(yspec);
/* Load all yang files in dir */
for (i = 0; i < ndp; i++) {
/* base = module name [+ @rev ] + .yang */
@ -1392,7 +1523,7 @@ yang_spec_load_dir(clicon_handle h,
ys_free(ym);
}
}
if (yang_parse_post(h, yspec, modnr) < 0)
if (yang_parse_post(h, yspec, modmin) < 0)
goto done;
ok:
retval = 0;

View file

@ -38,6 +38,9 @@ cat <<EOF > $ydir/moda.yang
module moda{
namespace "urn:example:a";
prefix a;
import modb{
prefix b;
}
container x1{
description "list with single string key";
list y{
@ -150,6 +153,7 @@ done
echo -n '</x1>' >> $xml1
new "api-path single string key k1=a$rnd"
echo "$clixon_util_path -f $xml1 -y $ydir -p /moda:x1/y=a$rnd"
expectpart "$($clixon_util_path -f $xml1 -y $ydir -p /moda:x1/y=a$rnd)" 0 "^0: <y><k1>a$rnd</k1><z>foo$rnd</z></y>$"
new "api-path single string key /x1"

View file

@ -60,7 +60,7 @@ module B{
import A {
prefix "a";
}
augment "/a:x/ngrt:y" {
augment "/a:x/a:y" {
container z {
leaf w {
type string;

View file

@ -30,6 +30,9 @@ cat <<EOF > $ydir/moda.yang
module moda{
namespace "urn:example:a";
prefix a;
import modb {
prefix b;
}
container x1{
description "list with single string key";
list y{

View file

@ -34,6 +34,7 @@ fi
cat <<EOF > $cfg
<clixon-config xmlns="http://clicon.org/config">
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
<CLICON_FEATURE>ni-ieee1588-ptp:cmlds</CLICON_FEATURE>
<CLICON_YANG_DIR>$YANGMODELS/standard/ietf/RFC</CLICON_YANG_DIR>
<CLICON_YANG_DIR>$YANGMODELS/standard/ieee/draft/802.1/Qcr</CLICON_YANG_DIR>
<CLICON_YANG_DIR>$YANGMODELS/standard/ieee/draft/802</CLICON_YANG_DIR>