clixon/lib/src/clixon_yang_parse_lib.c

1394 lines
42 KiB
C

/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
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
*
* CALLING ORDER OF YANG PARSE FILES
* =================================
* yang_spec_parse_module
* | |
* v v v
* yang_spec_parse_file-> yang_parse_post->yang_parse_recurse->yang_parse_module
* \ / v
* yang_spec_load_dir ------------------------------------> yang_parse_filename
* v
* yang_parse_file
* v
* yang_parse_str
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>
#include <ctype.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <regex.h>
#include <dirent.h>
#include <sys/types.h>
#include <fcntl.h>
#include <syslog.h>
#include <assert.h>
#include <libgen.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <netinet/in.h>
#include <libgen.h>
/* cligen */
#include <cligen/cligen.h>
/* clicon */
#include "clixon_log.h"
#include "clixon_err.h"
#include "clixon_string.h"
#include "clixon_queue.h"
#include "clixon_hash.h"
#include "clixon_handle.h"
#include "clixon_file.h"
#include "clixon_yang.h"
#include "clixon_yang_internal.h"
#include "clixon_hash.h"
#include "clixon_xml.h"
#include "clixon_yang_module.h"
#include "clixon_plugin.h"
#include "clixon_data.h"
#include "clixon_options.h"
#include "clixon_yang_type.h"
#include "clixon_yang_parse.h"
#include "clixon_yang_cardinality.h"
#include "clixon_yang_parse_lib.h"
/* Size of json read buffer when reading from file*/
#define BUFLEN 1024
/*! Resolve a grouping name from a point in the yang tree
* @param[in] ys Yang statement of "uses" statement doing the lookup
* @param[in] prefix Prefix of grouping to look for
* @param[in] name Name of grouping to look for
* @param[out] ygrouping0 A found grouping yang structure as result
* @retval 0 OK, ygrouping may be NULL
* @retval -1 Error, with clicon_err called
*/
static int
ys_grouping_resolve(yang_stmt *ys,
char *prefix,
char *name,
yang_stmt **ygrouping0)
{
int retval = -1;
yang_stmt *ymodule;
yang_stmt *ygrouping = NULL;
yang_stmt *yn;
/* find the grouping associated with argument and expand(?) */
if (prefix){ /* Go to top and find import that matches */
if ((ymodule = yang_find_module_by_prefix(ys, prefix)) != NULL)
ygrouping = yang_find(ymodule, Y_GROUPING, name);
}
else
while (1){
/* Check upwards in hierarchy for matching groupings */
if ((yn = yang_parent_get(ys)) == NULL || yang_keyword_get(yn) == Y_SPEC)
break;
/* Here find grouping */
if ((ygrouping = yang_find(yn, Y_GROUPING, name)) != NULL)
break;
/* Proceed to next level */
ys = (yang_stmt*)yn;
}
*ygrouping0 = ygrouping;
retval = 0;
// done:
return retval;
}
/*! This is an augment node, augment the original datamodel.
* @param[in] ys The augment statement
* @param[in] yspec Yang specification
* @see RFC7950 Sec 7.17
* The target node MUST be either a container, list, choice, case, input,
* output, or notification node.
* If the "augment" statement is on the top level the absolute form MUST be
* used.
* All data nodes defined in the "augment" statement are defined as XML
* elements in the XML namespace of the module where the "augment" is
* specified.
*/
static int
yang_augment_node(yang_stmt *ys,
yang_stmt *ysp)
{
int retval = -1;
char *schema_nodeid;
yang_stmt *ytarget = NULL;
yang_stmt *yc0;
yang_stmt *yc;
yang_stmt *ymod;
if ((ymod = ys_module(ys)) == NULL){
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)
goto done;
if (ytarget == NULL)
goto ok;
/* Extend ytarget with ys' children
* First enlarge ytarget vector
*/
yc0 = NULL;
while ((yc0 = yn_each(ys, yc0)) != NULL) {
if ((yc = ys_dup(yc0)) == NULL)
goto done;
yc->ys_mymodule = ymod;
if (yn_insert(ytarget, yc) < 0)
goto done;
}
ok:
retval = 0;
done:
return retval;
}
/*! Find all top-level augments and change original datamodels. */
static int
yang_augment_spec(yang_stmt *ysp,
int modnr)
{
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++];
switch (yang_keyword_get(ys)){
case Y_AUGMENT: /* top-level */
if (yang_augment_node(ys, ysp) < 0)
goto done;
break;
default:
break;
}
}
}
retval = 0;
done:
return retval;
}
/*! Given a refine node, perform the refinement action on the target refine node
* The RFC is somewhat complicate in the rules for refine.
* Most nodes will be replaced, but some are added
* @param[in] yr Refine node
* @param[in] yt Refine target node (will be modified)
* @see RFC7950 Sec 7.13.2
* There may be some missed cornercases
*/
static int
ys_do_refine(yang_stmt *yr,
yang_stmt *yt)
{
int retval = -1;
yang_stmt *yrc; /* refine child */
yang_stmt *yrc1;
yang_stmt *ytc; /* target child */
enum rfc_6020 keyw;
int i;
/* Loop through refine node children. First if remove do that first
* In some cases remove a set of nodes.
*/
yrc = NULL;
while ((yrc = yn_each(yr, yrc)) != NULL) {
keyw = yang_keyword_get(yrc);
switch (keyw){
case Y_DEFAULT: /* remove old, add new */
case Y_DESCRIPTION:
case Y_REFERENCE:
case Y_CONFIG:
case Y_MANDATORY:
case Y_PRESENCE:
case Y_MIN_ELEMENTS:
case Y_MAX_ELEMENTS:
case Y_EXTENSION:
/* Remove old matching, dont increment due to prune in loop */
for (i=0; i<yang_len_get(yt); ){
ytc = yt->ys_stmt[i];
if (keyw != yang_keyword_get(ytc)){
i++;
continue;
}
ys_prune(yt, i);
ys_free(ytc);
}
/* fall through and add if not found */
case Y_MUST: /* keep old, add new */
case Y_IF_FEATURE:
break;
default:
break;
}
}
/* Second, add the node(s) */
yrc = NULL;
while ((yrc = yn_each(yr, yrc)) != NULL) {
keyw = yang_keyword_get(yrc);
/* Make copy */
if ((yrc1 = ys_dup(yrc)) == NULL)
goto done;
if (yn_insert(yt, yrc1) < 0)
goto done;
}
retval = 0;
done:
return retval;
}
/*! Macro expansion of grouping/uses done in step 2 of yang parsing
* RFC7950:
* Identifiers appearing inside the grouping are resolved
* relative to the scope in which the grouping is defined, not where it is
* used. Prefix mappings, type names, grouping names, and extension usage are
* evaluated in the hierarchy where the "grouping" statement appears.
* The identifiers defined in the grouping are not bound to a namespace
* until the contents of the grouping are added to the schema tree via a
* "uses" statement that does not appear inside a "grouping" statement,
* at which point they are bound to the namespace of the current module.
*/
static int
yang_expand_grouping(yang_stmt *yn)
{
int retval = -1;
yang_stmt *ys = NULL;
yang_stmt *ygrouping; /* grouping original */
yang_stmt *ygrouping2; /* grouping copy */
yang_stmt *yg; /* grouping child */
yang_stmt *yr; /* refinement */
int glen;
int i;
int j;
char *id = NULL;
char *prefix = NULL;
size_t size;
yang_stmt *yp;
/* Cannot use yang_apply here since child-list is modified (is destructive) */
i = 0;
while (i < yang_len_get(yn)){
ys = yn->ys_stmt[i];
switch (yang_keyword_get(ys)){
case Y_USES:
/* Split argument into prefix and name */
if (nodeid_split(yang_argument_get(ys), &prefix, &id) < 0)
goto done;
if (ys_grouping_resolve(ys, prefix, id, &ygrouping) < 0)
goto done;
if (prefix){
free(prefix);
prefix = NULL;
}
if (id){
free(id);
id = NULL;
}
if (ygrouping == NULL){
clicon_log(LOG_NOTICE, "%s: Yang error : grouping \"%s\" not found in module \"%s\"",
__FUNCTION__, yang_argument_get(ys), yang_argument_get(ys_module(ys)));
goto done;
break;
}
/* Check so that this uses statement is not a decendant of the grouping
* Not that there may be other indirect recursions (I think?)
*/
yp = yn;
do {
if (yp == ygrouping){
clicon_err(OE_YANG, EFAULT, "Yang use of grouping %s in module %s is defined inside the grouping (recursion)",
yang_argument_get(ys),
yang_argument_get(ys_module(yn))
);
goto done;
}
} while((yp = yang_parent_get(yp)) != NULL);
if (yang_flag_get(ygrouping, YANG_FLAG_MARK) == 0){
/* Check mark flag to see if this grouping has been expanded before,
* here below in the traverse section
* A mark could be completely normal (several uses) or it could be a recursion.
*/
yang_flag_set(ygrouping, YANG_FLAG_MARK); /* Mark as (being) expanded */
if (yang_expand_grouping(ygrouping) < 0)
goto done;
}
/* Make a copy of the grouping, then make refinements to this copy
*/
if ((ygrouping2 = ys_dup(ygrouping)) == NULL)
goto done;
/* Replace ys with ygrouping,...
* First enlarge parent vector
*/
glen = yang_len_get(ygrouping2);
/*
* yn is parent: the children of ygrouping replaces ys.
* Is there a case when glen == 0? YES AND THIS BREAKS
*/
if (glen != 1){
size = (yang_len_get(yn) - i - 1)*sizeof(struct yang_stmt *);
yn->ys_len += glen - 1;
if (glen && (yn->ys_stmt = realloc(yn->ys_stmt, (yang_len_get(yn))*sizeof(yang_stmt *))) == 0){
clicon_err(OE_YANG, errno, "realloc");
goto done;
}
/* Then move all existing elements up from i+1 (not uses-stmt) */
if (size)
memmove(&yn->ys_stmt[i+glen],
&yn->ys_stmt[i+1],
size);
}
/* Iterate through refinements and modify grouping copy
* See RFC 7950 7.13.2 yrt is the refine target node
*/
yr = NULL;
while ((yr = yn_each(ys, yr)) != NULL) {
yang_stmt *yrt; /* refine target node */
if (yang_keyword_get(yr) != Y_REFINE)
continue;
/* Find a node */
if (yang_desc_schema_nodeid(ygrouping2,
yang_argument_get(yr),
-1,
&yrt) < 0)
goto done;
/* Not found, try next */
if (yrt == NULL)
continue;
/* Do the actual refinement */
if (ys_do_refine(yr, yrt) < 0)
goto done;
/* RFC: The argument is a string that identifies a node in the
* grouping. I interpret that as only one node --> break */
break;
}
/* Then copy and insert each child element */
for (j=0; j<glen; j++){
yg = ygrouping2->ys_stmt[j]; /* Child of refined copy */
yn->ys_stmt[i+j] = yg;
yg->ys_parent = yn;
}
/* Remove 'uses' node */
ys_free(ys);
/* Remove the grouping copy */
ygrouping2->ys_len = 0; /* Cant do with get access function */
ys_free(ygrouping2);
break; /* Note same child is re-iterated since it may be changed */
default:
i++;
break;
}
}
/* Second pass since length may have changed */
for (i=0; i<yang_len_get(yn); i++){
ys = yn->ys_stmt[i];
if (yang_keyword_get(ys) == Y_GROUPING){
/* Check mark flag to see if this grouping has been expanded before, here or in the
* 'uses' section
* A mark could be completely normal (several uses) or it could be a recursion.
*/
if (yang_flag_get(ys, YANG_FLAG_MARK) == 0){
yang_flag_set(ys, YANG_FLAG_MARK); /* Mark as (being) expanded */
if (yang_expand_grouping(ys) < 0)
goto done;
}
}
else{
if (yang_expand_grouping(ys) < 0)
goto done;
}
}
retval = 0;
done:
if (prefix)
free(prefix);
if (id)
free(id);
return retval;
}
/*! Parse a string containing a YANG spec into a parse-tree
*
* Syntax parsing. A string is input and a syntax-tree is returned (or error).
* A variable record is also returned containing a list of (global) variable values.
* (cloned from cligen)
* @param[in] str String of yang statements
* @param[in] name Log string, typically filename
* @param[in] yspec Yang specification.
* @retval ymod Top-level yang (sub)module
* @retval NULL Error encountered
* See top of file for diagram of calling order
*/
static yang_stmt *
yang_parse_str(char *str,
const char *name, /* just for errs */
yang_stmt *yspec)
{
clixon_yang_yacc yy = {0,};
yang_stmt *ymod = NULL;
if (yspec == NULL){
clicon_err(OE_YANG, 0, "Yang parse need top level yang spec");
goto done;
}
yy.yy_name = (char*)name;
yy.yy_linenum = 1;
yy.yy_parse_string = str;
yy.yy_stack = NULL;
yy.yy_module = NULL; /* this is the return value - the module/sub-module */
if (ystack_push(&yy, yspec) == NULL)
goto done;
if (strlen(str)){ /* Not empty */
if (yang_scan_init(&yy) < 0)
goto done;
if (yang_parse_init(&yy) < 0)
goto done;
if (clixon_yang_parseparse(&yy) != 0) { /* yacc returns 1 on error */
clicon_log(LOG_NOTICE, "Yang error: %s on line %d", name, yy.yy_linenum);
if (clicon_errno == 0)
clicon_err(OE_YANG, 0, "yang parser error with no error code (should not happen)");
yang_parse_exit(&yy);
yang_scan_exit(&yy);
goto done;
}
if (yang_parse_exit(&yy) < 0)
goto done;
if (yang_scan_exit(&yy) < 0)
goto done;
}
ymod = yy.yy_module;
done:
ystack_pop(&yy);
if (yy.yy_stack)
free (yy.yy_stack);
return ymod; /* top-level (sub)module */
}
/*! Parse yang spec from an open file descriptor
* @param[in] fd File descriptor containing the YANG file as ASCII characters
* @param[in] name For debug, eg filename
* @param[in] ysp Yang specification. Should have been created by caller using yspec_new
* @retval ymod Top-level yang (sub)module
* @retval NULL Error
* @note this function simply parse a yang spec, no dependencies or checks
*/
yang_stmt *
yang_parse_file(int fd,
const char *name,
yang_stmt *ysp)
{
char *buf = NULL;
int i;
int c;
int len;
yang_stmt *ymod = NULL;
int ret;
len = BUFLEN; /* any number is fine */
if ((buf = malloc(len)) == NULL){
perror("pt_file malloc");
return NULL;
}
memset(buf, 0, len);
i = 0; /* position in buf */
while (1){ /* read the whole file */
if ((ret = read(fd, &c, 1)) < 0){
clicon_err(OE_XML, errno, "read");
break;
}
if (ret == 0)
break; /* eof */
if (len==i){
if ((buf = realloc(buf, 2*len)) == NULL){
clicon_err(OE_XML, errno, "realloc");
goto done;
}
memset(buf+len, 0, len);
len *= 2;
}
buf[i++] = (char)(c&0xff);
} /* read a line */
if ((ymod = yang_parse_str(buf, name, ysp)) < 0)
goto done;
done:
if (buf)
free(buf);
return ymod; /* top-level (sub)module */
}
/*! Given a yang filename, extract the revision as an integer as YYYYMMDD
* @param[in] filename Filename on the form: name [+ @rev ] + .yang
* @param[out] basep "Base" filename, stripped: [+ @rev ] + .yang
* @param[out] revp Revision as YYYYMMDD (0 if not found)
*/
static int
filename2revision(const char *filename,
char **basep,
uint32_t *revp)
{
int retval = -1;
char *base = NULL;
char *p;
/* base = module name [+ @rev ] + .yang */
if ((base = strdup(filename)) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
clicon_debug(1, "%s %s", __FUNCTION__, base);
if ((p = rindex(base, '.')) != NULL) /* strip postfix .yang */
*p = '\0';
if ((p = index(base, '@')) != NULL){ /* extract revision date */
*p++ = '\0';
if (ys_parse_date_arg(p, revp) < 0)
goto done;
}
if (basep){
*basep = base;
base = NULL;
}
retval = 0;
done:
if (base)
free(base);
return retval;
}
/*! No specific revision give. Match a yang file given module
* @param[in] h CLICON handle
* @param[in] module Name of main YANG module.
* @param[in] revision Revision or NULL
* @param[out] revactual Actual revision (if retval=1)
* @param[out] fbuf Buffer containing filename (if retval=1)
* @retval 1 Match found, Most recent entry returned in fbuf and revactual
* @retval 0 No matching entry found
* @retval -1 Error
* @note for bootstrapping, dir may have to be set.
*/
static int
yang_parse_find_match(clicon_handle h,
const char *module,
const char *revision,
uint32_t *revactual,
cbuf *fbuf)
{
int retval = -1;
struct dirent *dp = NULL;
int ndp;
cbuf *regex = NULL;
cxobj *x;
cxobj *xc;
char *dir;
/* get clicon config file in xml form */
if ((x = clicon_conf_xml(h)) == NULL)
goto ok;
if ((regex = cbuf_new()) == NULL){
clicon_err(OE_YANG, errno, "cbuf_new");
goto done;
}
/* RFC 6020: The name of the file SHOULD be of the form:
* module-or-submodule-name ['@' revision-date] ( '.yang' / '.yin' )
* revision-date ::= 4DIGIT "-" 2DIGIT "-" 2DIGIT
*/
if (revision)
cprintf(regex, "^%s@%s(.yang)$", module, revision);
else
cprintf(regex, "^%s(@[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9])?(.yang)$",
module);
xc = NULL;
while ((xc = xml_child_each(x, xc, CX_ELMNT)) != NULL) {
if (strcmp(xml_name(xc), "CLICON_YANG_DIR") != 0)
continue;
dir = xml_body(xc);
/* get all matching files in this directory */
if ((ndp = clicon_file_dirent(dir,
&dp,
cbuf_get(regex),
S_IFREG)) < 0)
goto done;
/* Entries are sorted, last entry should be most recent date
*/
if (ndp != 0){
cprintf(fbuf, "%s/%s", dir, dp[ndp-1].d_name);
retval = 1;
goto done;
}
}
ok:
retval = 0;
done:
if (regex)
cbuf_free(regex);
if (dp)
free(dp);
return retval;
}
/*! Open a file, read into a string and invoke yang parsing
*
* Similar to clicon_yang_str(), just read a file first
* @param[in] filename Name of file
* @param[in] ysp Yang specification. Should have been created by caller using yspec_new
* @retval ymod Top-level yang (sub)module
* @retval NULL Error encountered
* The database symbols are inserted in alphabetical order.
* See top of file for diagram of calling order
*/
yang_stmt *
yang_parse_filename(const char *filename,
yang_stmt *ysp)
{
yang_stmt *ymod = NULL;
int fd = -1;
struct stat st;
clicon_debug(1, "%s %s", __FUNCTION__, filename);
if (stat(filename, &st) < 0){
clicon_err(OE_YANG, errno, "%s not found", filename);
goto done;
}
if ((fd = open(filename, O_RDONLY)) < 0){
clicon_err(OE_YANG, errno, "open(%s)", filename);
goto done;
}
if ((ymod = yang_parse_file(fd, filename, ysp)) < 0)
goto done;
done:
if (fd != -1)
close(fd);
return ymod; /* top-level (sub)module */
}
/*! Given a (sub)module, parse all (sub)modules in turn recursively
*
* Find a yang module file, and then recursively parse all its imported modules.
* @param[in] h CLICON handle
* @param[in] module Module name
* @param[in] revision Revision (or NULL)
* @param[in] ysp Yang statement
* @retval 0 OK
* @retval -1 Error
*
* See top of file for diagram of calling order
*/
static yang_stmt *
yang_parse_module(clicon_handle h,
const char *module,
const char *revision,
yang_stmt *ysp)
{
cbuf *fbuf = NULL;
char *filename;
int nr;
yang_stmt *ymod = NULL;
yang_stmt *yrev; /* yang revision */
uint32_t revf = 0; /* revision in filename */
uint32_t revm = 0; /* revision in parsed new module (should be same as revf) */
if ((fbuf = cbuf_new()) == NULL){
clicon_err(OE_YANG, errno, "cbuf_new");
goto done;
}
/* Match a yang file with or without revision in yang-dir list */
if ((nr = yang_parse_find_match(h, module, revision, &revf, fbuf)) < 0)
goto done;
if (nr == 0){
if (revision)
clicon_err(OE_YANG, errno, "No yang files found matching \"%s@%s\" in the list of CLICON_YANG_DIRs",
module, revision);
else
clicon_err(OE_YANG, errno, "No yang files found matching \"%s\" in the list of CLICON_YANG_DIRs", module);
goto done;
}
filename = cbuf_get(fbuf);
if ((ymod = yang_parse_filename(filename, ysp)) == NULL)
goto done;
if ((yrev = yang_find(ymod, Y_REVISION, NULL)) != NULL)
revm = cv_uint32_get(yang_cv_get(yrev));
if (filename2revision(filename, NULL, &revf) < 0)
goto done;
/* Sanity check that file revision does not match internal rev stmt */
if (revf && revm && revm != revf){
clicon_err(OE_YANG, EINVAL, "Yang module file revision and in yang does not match: %s vs %u", filename, revm);
ymod = NULL;
goto done;
}
done:
if (fbuf)
cbuf_free(fbuf);
return ymod; /* top-level (sub)module */
}
/*! Given a (sub)module, parse all (sub)modules in turn recursively
*
* Find a yang module file, and then recursively parse all its imported modules.
* @param[in] h CLICON handle
* @param[in] ymod Yang module.
* @param[in] yspec Yang specification.
* @retval 0 OK
* @retval -1 Error
*
* See top of file for diagram of calling order
*/
static int
yang_parse_recurse(clicon_handle h,
yang_stmt *ymod,
yang_stmt *ysp)
{
int retval = -1;
yang_stmt *yi = NULL; /* import */
yang_stmt *yrev;
char *submodule;
char *subrevision;
yang_stmt *subymod;
enum rfc_6020 keyw;
/* go through all import (modules) and include(submodules) of ysp */
while ((yi = yn_each(ymod, yi)) != NULL){
keyw = yang_keyword_get(yi);
if (keyw != Y_IMPORT && keyw != Y_INCLUDE)
continue;
/* common part */
submodule = yang_argument_get(yi);
/* Is there a specific revision (or just latest)? */
if ((yrev = yang_find(yi, Y_REVISION_DATE, NULL)) != NULL)
subrevision = yang_argument_get(yrev);
else
subrevision = NULL;
/* if already loaded, ignore, else parse the file */
if (yang_find(ysp,
keyw==Y_IMPORT?Y_MODULE:Y_SUBMODULE,
submodule) == NULL){
/* recursive call */
if ((subymod = yang_parse_module(h, submodule, subrevision, ysp)) == NULL)
goto done;
/* Go through its sub-modules recursively */
if (yang_parse_recurse(h, subymod, ysp) < 0){
ymod = NULL;
goto done;
}
}
}
retval = 0;
done:
return retval; /* top-level (sub)module */
}
/*!
* @param[in] ys Yang statement
* @param[in] dummy Necessary for called in yang_apply
* @see yang_apply_fn
*/
static int
ys_schemanode_check(yang_stmt *ys,
void *dummy)
{
int retval = -1;
yang_stmt *yspec;
yang_stmt *yres = NULL;
yang_stmt *yp;
char *arg;
enum rfc_6020 keyword;
char **vec = NULL;
char *v;
int nvec;
int i;
yp = yang_parent_get(ys);
arg = yang_argument_get(ys);
keyword = yang_keyword_get(ys);
switch (yang_keyword_get(ys)){
case Y_AUGMENT:
if (yang_keyword_get(yp) == Y_MODULE || /* Not top-level */
yang_keyword_get(yp) == Y_SUBMODULE)
break;
/* fallthru */
case Y_REFINE:
if (yang_desc_schema_nodeid(yp, arg, -1, &yres) < 0)
goto done;
if (yres == NULL){
clicon_err(OE_YANG, 0, "schemanode sanity check of %s %s",
yang_key2str(keyword), arg);
goto done;
}
break;
case Y_UNIQUE:{
/* Unique: Sec 7.8.3 It takes as an argument a string that contains a space-
separated list of schema node identifiers */
if ((vec = clicon_strsep(arg, " \t\n", &nvec)) == NULL)
goto done;
for (i=0; i<nvec; i++){
v = vec[i];
if (yang_desc_schema_nodeid(yp, v, -1, &yres) < 0)
goto done;
if (yres == NULL){
clicon_err(OE_YANG, 0, "schemanode sanity check of %s %s",
yang_key2str(yang_keyword_get(ys)), v);
goto done;
}
}
break;
}
case Y_DEVIATION:
yspec = ys_spec(ys);
if (yang_abs_schema_nodeid(yspec, ys, arg, -1, &yres) < 0)
goto done;
if (yres == NULL){
clicon_err(OE_YANG, 0, "schemanode sanity check of %s", arg);
goto done;
}
break;
default:
break;
}
retval = 0;
done:
if (vec)
free(vec);
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
* compile-time for various reasons.
* These includes:
* - 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
* - "Populate" yang, which means things like initiating caches, resolving references
* - Resolve types
* - Augments
* - Defaults
*
* @param[in] h CLICON handle
* @param[in] yspec Yang specification.
* @param[in] modnr 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 retval = -1;
int i;
/* 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)
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)
goto done;
/* 3: Check features: check if enabled and remove disabled features */
for (i=modnr; i<yang_len_get(yspec); i++) /* XXX */
if (yang_features(h, yspec->ys_stmt[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 (yang_apply(yspec->ys_stmt[i], -1, ys_populate, (void*)h) < 0)
goto done;
/* 5: Resolve all types: populate type caches. Requires eg length/range cvecs
* 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)
goto done;
/* Up to here resolving is made in the context they are defined, rather
* than the context they are used (except for submodules being merged w
* modules). Like static scoping.
* After this we expand all grouping/uses and unfold all macros into a
* single tree as they are used.
*/
/* 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)
goto done;
yang_apply(yspec->ys_stmt[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)
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)
goto done;
/* 8: sanity check of schemanode references, need more here */
for (i=modnr; i<yang_len_get(yspec); i++)
if (yang_apply(yspec->ys_stmt[i], -1, ys_schemanode_check, NULL) < 0)
goto done;
retval = 0;
done:
return retval;
}
/*! Parse yang specification and its dependencies recursively given module
* @param[in] h clicon handle
* @param[in] module Module name, or absolute filename (including dir)
* @param[in] revision Revision, or NULL
* @param[in] yspec Modules parse are added to this yangspec
* @retval 0 OK
* @retval -1 Error
* @see yang_spec_parse_file
*/
int
yang_spec_parse_module(clicon_handle h,
const char *module,
const char *revision,
yang_stmt *yspec)
{
int retval = -1;
int modnr; /* Existing number of modules */
char *base = NULL;;
if (yspec == NULL){
clicon_err(OE_YANG, EINVAL, "yang spec is NULL");
goto done;
}
if (module == NULL){
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);
/* 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)
goto done;
ok:
retval = 0;
done:
if (base)
free(base);
return retval;
}
/*! Parse yang specification and its dependencies recursively given filename
* @param[in] h clicon handle
* @param[in] filename Actual filename (including dir and revision)
* @param[in] yspec Modules parse are added to this yangspec
* @retval 0 OK
* @retval -1 Error
* @see yang_spec_parse_module for yang dir,module,revision instead of
* actual filename
* @see yang_spec_load_dir For loading all files in a directory
*/
int
yang_spec_parse_file(clicon_handle h,
char *filename,
yang_stmt *yspec)
{
int retval = -1;
int modnr; /* Existing number of modules */
char *base = NULL;;
/* Apply steps 2.. on new modules, ie ones after modnr. */
modnr = 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");
goto done;
}
if ((base = strdup(basename(filename))) == NULL){
clicon_err(OE_YANG, errno, "strdup");
goto done;
}
if (index(base, '@') != NULL)
*index(base, '@') = '\0';
if (yang_find(yspec, Y_MODULE, base) != NULL)
goto ok;
if (yang_parse_filename(filename, yspec) == NULL)
goto done;
if (yang_parse_post(h, yspec, modnr) < 0)
goto done;
ok:
retval = 0;
done:
if (base)
free(base);
return retval;
}
/*! Load all yang modules in directory
* @param[in] h Clicon handle
* @param[in] dir Load all yang modules in this directory
* @param[in] yspec Modules parse are added to this yangspec
* @retval 0 OK
* @retval -1 Error
* @see yang_spec_parse_file
* Load all yang files in a directory as primary objects.
* Some details if several same yang module x exists:
* 1) If x is already loaded (eg via direct file loading) skip it
* 2) Prefer x.yang over x@rev.yang (no revision)
* 3) If only x@rev.yang's found, prefer newest (newest revision)
* There is also an extra failsafe which may not be necessary, which removes
* the oldest module if 1-3 for some reason fails.
*/
int
yang_spec_load_dir(clicon_handle h,
char *dir,
yang_stmt *yspec)
{
int retval = -1;
int ndp;
struct dirent *dp = NULL;
int i;
int j;
char filename[MAXPATHLEN];
char *base = NULL; /* filename without dir */
int modnr;
yang_stmt *ym; /* yang module */
yang_stmt *ym0; /* (existing) yang module */
yang_stmt *yrev; /* yang revision */
uint32_t revf = 0; /* revision in filename */
uint32_t revm = 0; /* revision in parsed new module (should be same as revf) */
uint32_t rev0; /* revision in existing module */
char *oldbase = NULL;
int taken = 0;
/* Get yang files names from yang module directory. Note that these
* are sorted alphatetically:
* a.yang,
* a@2000-01-01.yang,
* a@2111-11-11.yang
*/
if((ndp = clicon_file_dirent(dir, &dp, "(.yang)$", S_IFREG)) < 0)
goto done;
if (ndp == 0)
clicon_log(LOG_WARNING, "%s: No yang files found in %s",
__FUNCTION__, dir);
/* Apply post steps on new modules, ie ones after modnr. */
modnr = yang_len_get(yspec);
/* Load all yang files in dir */
for (i = 0; i < ndp; i++) {
/* base = module name [+ @rev ] + .yang */
if (oldbase)
free(oldbase);
oldbase = base;
base = NULL;
revf = 0;
if (filename2revision(dp[i].d_name, &base, &revf) < 0)
goto done;
if (oldbase && strcmp(base, oldbase)) /* new yang file basename */
taken = 0;
if (revf == 0) /* No revision: a.yang - take that */
taken = 1;
else{ /* a@xxx.yang */
if (taken)
continue; /* skip if already taken */
/* Look forward: is there anyone else later? */
if (i+1<ndp && strncmp(base, dp[i+1].d_name, strlen(base)) == 0)
continue; /* same base: skip; */
taken = 1; /* last in line and not taken */
}
/* Here only a single file is reached(taken)
* Check if module already exists -> ym0/rev0 */
rev0 = 0;
if ((ym0 = yang_find(yspec, Y_MODULE, base)) != NULL ||
(ym0 = yang_find(yspec, Y_SUBMODULE, base)) != NULL){
yrev = yang_find(ym0, Y_REVISION, NULL);
rev0 = cv_uint32_get(yang_cv_get(yrev));
continue; /* skip if already added by specific file or module */
}
/* Create full filename */
snprintf(filename, MAXPATHLEN-1, "%s/%s", dir, dp[i].d_name);
if ((ym = yang_parse_filename(filename, yspec)) == NULL)
goto done;
revm = 0;
if ((yrev = yang_find(ym, Y_REVISION, NULL)) != NULL)
revm = cv_uint32_get(yang_cv_get(yrev));
/* Sanity check that file revision does not match internal rev stmt */
if (revf && revm && revm != revf){ /* XXX */
clicon_err(OE_YANG, EINVAL, "Yang module file revision and in yang does not match: %s(%u) vs %u", filename, revf, revm);
goto done;
}
/* If ym0 and ym exists, delete the yang with oldest revision
* This is a failsafe in case anything else fails
*/
if (revm && rev0){
if (revm > rev0) /* Loaded module is older or eq -> remove ym */
ym = ym0;
for (j=0; j<yang_len_get(yspec); j++)
if (yspec->ys_stmt[j] == ym)
break;
ys_prune(yspec, j);
ys_free(ym);
}
}
if (yang_parse_post(h, yspec, modnr) < 0)
goto done;
retval = 0;
done:
if (dp)
free(dp);
if (base)
free(base);
if (oldbase)
free(oldbase);
return retval;
}
/*! parse yang date-arg string and return a uint32 useful for arithmetics
* @param[in] datearg yang revision string as "YYYY-MM-DD"
* @param[out] dateint Integer version as YYYYMMDD
* @retval 0 OK
* @retval -1 Error, eg str is not on the format "YYYY-MM-DD"
*/
int
ys_parse_date_arg(char *datearg,
uint32_t *dateint)
{
int retval = -1;
int i;
uint32_t d = 0;
if (strlen(datearg) != 10 || datearg[4] != '-' || datearg[7] != '-'){
clicon_err(OE_YANG, EINVAL, "Revision date %s, but expected: YYYY-MM-DD", datearg);
goto done;
}
if ((i = cligen_tonum(4, datearg)) < 0){
clicon_err(OE_YANG, EINVAL, "Revision date %s, but expected: YYYY-MM-DD", datearg);
goto done;
}
d = i*10000; /* year */
if ((i = cligen_tonum(2, &datearg[5])) < 0){
clicon_err(OE_YANG, EINVAL, "Revision date %s, but expected: YYYY-MM-DD", datearg);
goto done;
}
d += i*100; /* month */
if ((i = cligen_tonum(2, &datearg[8])) < 0){
clicon_err(OE_YANG, EINVAL, "Revision date %s, but expected: YYYY-MM-DD", datearg);
goto done;
}
d += i; /* day */
*dateint = d;
retval = 0;
done:
return retval;
}
/*! Parse argument as CV and save result in yang cv variable
*
* Note that some CV:s are parsed directly (eg fraction-digits) while others are parsed
* in third pass (ys_populate). The reason being that all information is not
* available in the first pass. Prefer to do stuff in ys_populate
*/
cg_var *
ys_parse(yang_stmt *ys,
enum cv_type cvtype)
{
int cvret;
char *reason = NULL;
assert(yang_cv_get(ys) == NULL); /* Cv:s are parsed in different places, difficult to separate */
if ((ys->ys_cv = cv_new(cvtype)) == NULL){
clicon_err(OE_YANG, errno, "cv_new");
goto done;
}
if ((cvret = cv_parse1(yang_argument_get(ys), ys->ys_cv, &reason)) < 0){ /* error */
clicon_err(OE_YANG, errno, "parsing cv");
ys->ys_cv = NULL;
goto done;
}
if (cvret == 0){ /* parsing failed */
clicon_err(OE_YANG, errno, "Parsing CV: %s", reason);
ys->ys_cv = NULL;
goto done;
}
/* cvret == 1 means parsing is OK */
done:
if (reason)
free(reason);
return ys->ys_cv;
}
/*! First round yang syntactic statement specific checks. No context checks.
*
* Specific syntax checks and variable creation for stand-alone yang statements.
* That is, siblings, etc, may not be there. Complete checks are made in
* ys_populate instead.
* @param[in] ys yang statement
* @param[in] extra Yang extra for cornercases (unknown/extension). Is consumed
*
* The cv:s created in parse-tree as follows:
* fraction-digits : Create cv as uint8, check limits [1:8] (must be made in 1st pass)
* revision: cv as uint32 date: Integer version as YYYYMMDD
* min-elements: cv as uint32
* max-elements: cv as uint32, '0' means unbounded
* @see ys_populate
*/
int
ys_parse_sub(yang_stmt *ys,
char *extra)
{
int retval = -1;
uint8_t fd;
uint32_t date = 0;
char *arg;
enum rfc_6020 keyword;
char *reason = NULL;
int ret;
uint32_t minmax;
arg = yang_argument_get(ys);
keyword = yang_keyword_get(ys);
switch (keyword){
case Y_FRACTION_DIGITS:
if (ys_parse(ys, CGV_UINT8) == NULL)
goto done;
fd = cv_uint8_get(ys->ys_cv);
if (fd < 1 || fd > 18){
clicon_err(OE_YANG, errno, "%u: Out of range, should be [1:18]", fd);
goto done;
}
break;
case Y_REVISION:
case Y_REVISION_DATE: /* YYYY-MM-DD encoded as uint32 YYYYMMDD */
if (ys_parse_date_arg(arg, &date) < 0)
goto done;
if ((ys->ys_cv = cv_new(CGV_UINT32)) == NULL){
clicon_err(OE_YANG, errno, "cv_new");
goto done;
}
cv_uint32_set(ys->ys_cv, date);
break;
case Y_STATUS: /* RFC7950 7.21.2: "current", "deprecated", or "obsolete". */
if (strcmp(arg, "current") &&
strcmp(arg, "deprecated") &&
strcmp(arg, "obsolete")){
clicon_err(OE_YANG, errno, "Invalid status: \"%s\", expected current, deprecated, or obsolete", arg);
goto done;
}
break;
case Y_MAX_ELEMENTS:
case Y_MIN_ELEMENTS:
if ((ys->ys_cv = cv_new(CGV_UINT32)) == NULL){
clicon_err(OE_YANG, errno, "cv_new");
goto done;
}
if (keyword == Y_MAX_ELEMENTS &&
strcmp(arg, "unbounded") == 0)
cv_uint32_set(ys->ys_cv, 0); /* 0 means unbounded for max */
else{
if ((ret = parse_uint32(arg, &minmax, &reason)) < 0){
clicon_err(OE_YANG, errno, "parse_uint32");
goto done;
}
if (ret == 0){
clicon_err(OE_YANG, EINVAL, "element-min/max parse error: %s", reason);
if (reason)
free(reason);
goto done;
}
cv_uint32_set(ys->ys_cv, minmax);
}
break;
case Y_MODIFIER:
if (strcmp(yang_argument_get(ys), "invert-match")){
clicon_err(OE_YANG, EINVAL, "modifier %s, expected invert-match", yang_argument_get(ys));
goto done;
}
break;
case Y_UNKNOWN:{ /* save (optional) argument in ys_cv */
if (extra == NULL)
break;
if ((ys->ys_cv = cv_new(CGV_STRING)) == NULL){
clicon_err(OE_YANG, errno, "cv_new");
goto done;
}
if ((ret = cv_parse1(extra, ys->ys_cv, &reason)) < 0){ /* error */
clicon_err(OE_YANG, errno, "parsing cv");
goto done;
}
if (ret == 0){ /* parsing failed */
clicon_err(OE_YANG, errno, "Parsing CV: %s", reason);
goto done;
}
break;
}
default:
break;
}
retval = 0;
done:
if (extra)
free(extra);
return retval;
}