1796 lines
58 KiB
C
1796 lines
58 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 *****
|
|
|
|
*
|
|
* Translation between database specs
|
|
* yang_spec CLIgen parse_tree
|
|
* +-------------+ yang2cli +-------------+
|
|
* | | ------------> | cli |
|
|
* | list{key A;}| | syntax |
|
|
* +-------------+ +-------------+
|
|
|
|
* YANG generate CLI
|
|
|
|
* A special tree called @datamodel is generated by the yang2cli function.
|
|
* This tree contains generated CLIgen syntax for loaded YANG modules, according to the
|
|
* include/exclude logic in clixon-autocli.yang defined by the following fields:
|
|
* module-default
|
|
* rule/module-name
|
|
* rule/operation=exclude|include
|
|
* The @datamodel tree can be used using the CLIgen "tree reference" functionality as described in
|
|
* the cligen tutorial Secion 2.7.
|
|
* The tree can be modified by removing labels.
|
|
* By default "ac-state" are removed.
|
|
* This means that using @datamodel without modifiers is a "clean" config tree.
|
|
|
|
This is an example yang module:
|
|
module m {
|
|
container x {
|
|
namespace "urn:example:m";
|
|
prefix m;
|
|
list m1 {
|
|
key "a";
|
|
leaf a {
|
|
type string;
|
|
}
|
|
leaf b {
|
|
type string;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
You can see which CLISPEC it generates via clixon_cli -D 2:
|
|
x,cli_set("/example:x");{
|
|
m1 a (<a:string>|<a:string expand_dbvar("candidate","/example:x/m1=%s/a")>),overwrite_me("/example:x/m1=%s/");
|
|
{
|
|
b (<b:string>|<b:string expand_dbvar("candidate","/example:x/m1=%s/b")>),overwrite_me("/example:x/m1=%s/b");
|
|
}
|
|
}
|
|
*/
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "clixon_config.h" /* generated by config & autoconf */
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <syslog.h>
|
|
#include <signal.h>
|
|
#include <sys/param.h>
|
|
|
|
/* cligen */
|
|
#include <cligen/cligen.h>
|
|
|
|
/* libclixon */
|
|
#include <clixon/clixon.h>
|
|
|
|
#include "clixon_cli_api.h"
|
|
#include "cli_plugin.h"
|
|
#include "cli_autocli.h"
|
|
#include "cli_generate.h"
|
|
|
|
/*! Create cligen variable expand entry with api-pathfmt format string as argument
|
|
*
|
|
* Add api-fmt as first argument to callback, eg:
|
|
* /ex:table
|
|
* Then if mountpoint add special entry:
|
|
* /ctrl:dev/ctrl:config
|
|
* @param[in] h Clixon handle
|
|
* @param[in] ys yang_stmt of the node at hand
|
|
* @param[in] cvtype Type of the cligen variable
|
|
* @param[in] options
|
|
* @param[in] fraction_digits
|
|
* @param[out] cb The string where the result format string is inserted.
|
|
* @retval 1 OK
|
|
* @retval 0 Hide, dont show helptext etc
|
|
* @retval -1 Error
|
|
* @see expand_dbvar This is where the expand string is used
|
|
* @note XXX only fraction_digits handled,should also have mincv, maxcv, pattern
|
|
*/
|
|
static int
|
|
cli_expand_var_generate(clixon_handle h,
|
|
yang_stmt *ys,
|
|
const char *cvtypestr,
|
|
int options,
|
|
uint8_t fraction_digits,
|
|
int pre,
|
|
cbuf *cb)
|
|
{
|
|
int retval = -1;
|
|
char *api_path_fmt = NULL;
|
|
int extvalue = 0;
|
|
yang_stmt *yspec;
|
|
cg_var *cv = NULL;
|
|
|
|
if ((yspec = ys_spec(ys)) != NULL)
|
|
cv = yang_cv_get(yspec);
|
|
if (yang_extension_value(ys, "hide", CLIXON_AUTOCLI_NS, &extvalue, NULL) < 0)
|
|
goto done;
|
|
if (extvalue || yang_find(ys, Y_STATUS, "deprecated") != NULL)
|
|
goto hide;
|
|
if (yang2api_path_fmt(ys, 1, &api_path_fmt) < 0)
|
|
goto done;
|
|
if (pre)
|
|
cprintf(cb, "|");
|
|
cprintf(cb, "<%s:%s", yang_argument_get(ys), cvtypestr);
|
|
if (options & YANG_OPTIONS_FRACTION_DIGITS)
|
|
cprintf(cb, " fraction-digits:%u", fraction_digits);
|
|
cprintf(cb, " %s(\"candidate\",\"%s\"",
|
|
GENERATE_EXPAND_XMLDB,
|
|
api_path_fmt);
|
|
if (cv){ /* Add optional mountpoint */
|
|
cprintf(cb, ",\"%s%s\"", MTPOINT_PREFIX, cv_string_get(cv));
|
|
}
|
|
cprintf(cb, ")>");
|
|
retval = 1;
|
|
done:
|
|
if (api_path_fmt)
|
|
free(api_path_fmt);
|
|
return retval;
|
|
hide:
|
|
retval = 0;
|
|
goto done;
|
|
}
|
|
|
|
/*! Create callback with api_path format string as argument
|
|
*
|
|
* @param[in] h Clixon handle
|
|
* @param[in] ys yang_stmt of the node at hand
|
|
* @param[out] cb The string where the result format string is inserted.
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
* @see cli_dbxml This is where the xmlkeyfmt string is used
|
|
* @see pt_callback_reference in CLIgen where the actual callback overwrites the template
|
|
*/
|
|
static int
|
|
cli_callback_generate(clixon_handle h,
|
|
yang_stmt *ys,
|
|
cbuf *cb)
|
|
{
|
|
int retval = -1;
|
|
char *api_path_fmt = NULL;
|
|
yang_stmt *yspec;
|
|
cg_var *cv = NULL;
|
|
|
|
if ((yspec = ys_spec(ys)) != NULL)
|
|
cv = yang_cv_get(yspec);
|
|
if (yang2api_path_fmt(ys, 0, &api_path_fmt) < 0)
|
|
goto done;
|
|
cprintf(cb, ",%s(\"%s\"", GENERATE_CALLBACK, api_path_fmt);
|
|
if (cv){ /* Add optional mountpoint */
|
|
cprintf(cb, ",\"%s%s\"", MTPOINT_PREFIX, cv_string_get(cv));
|
|
}
|
|
cprintf(cb, ")");
|
|
retval = 0;
|
|
done:
|
|
if (api_path_fmt)
|
|
free(api_path_fmt);
|
|
return retval;
|
|
}
|
|
|
|
/*! Print cligen help string as ("<helpstring>")
|
|
*
|
|
* @param[in] cb CLIgen buf holding generated CLIspec
|
|
* @param[in] helptext Help text
|
|
*/
|
|
static int
|
|
yang2cli_helptext(cbuf *cb,
|
|
char *helptext)
|
|
{
|
|
if (helptext)
|
|
cprintf(cb, "(\"%s\")", helptext);
|
|
return 0;
|
|
}
|
|
|
|
/*! Print yang argument to cbuf, or aliased via extension
|
|
*
|
|
* This is only implemented for leafs.
|
|
* the reason it does not work for non-terminals is somewhat complex:
|
|
* If alias, the CLIgen command is instead given as a "keyword" variable: <orig:string keyword:alias>
|
|
* This in turn is treated as a single-value choice in cligen_parse.y
|
|
* But then in cli_dbxml, choice is treated as a variable which is correct on other cases.
|
|
* So one needs to distinguish between "keyword" as given here, and a choice with one argument.
|
|
*/
|
|
static int
|
|
yang2cli_print_alias(cbuf *cb,
|
|
yang_stmt *ys)
|
|
{
|
|
int retval = -1;
|
|
int extvalue = 0;
|
|
char *name;
|
|
char *alias = NULL;
|
|
|
|
if (yang_extension_value(ys, "alias", CLIXON_AUTOCLI_NS, &extvalue, &alias) < 0)
|
|
goto done;
|
|
name = yang_argument_get(ys);
|
|
if (extvalue)
|
|
cprintf(cb, "<%s:string keyword:%s>", name, alias);
|
|
else
|
|
cprintf(cb, "%s", name);
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Generate identityref statements for CLI variables
|
|
*
|
|
* @param[in] ys Yang statement
|
|
* @param[in] ytype Resolved yang type.
|
|
* @param[in] helptext CLI help text
|
|
* @param[out] cb Buffer where cligen code is written
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
* @see yang2cli_var_sub Its sub-function
|
|
*/
|
|
static int
|
|
yang2cli_var_identityref(yang_stmt *ys,
|
|
yang_stmt *ytype,
|
|
const char *cvtypestr,
|
|
char *helptext,
|
|
cbuf *cb)
|
|
{
|
|
int retval = -1;
|
|
yang_stmt *ybaseref;
|
|
yang_stmt *ybaseid;
|
|
cg_var *cv = NULL;
|
|
char *prefix = NULL;
|
|
char *id = NULL;
|
|
int i;
|
|
cvec *idrefvec;
|
|
yang_stmt *ymod;
|
|
yang_stmt *yprefix;
|
|
yang_stmt *yspec;
|
|
|
|
if ((ybaseref = yang_find(ytype, Y_BASE, NULL)) == NULL)
|
|
goto ok;
|
|
if ((ybaseid = yang_find_identity(ytype, yang_argument_get(ybaseref))) == NULL)
|
|
goto ok;
|
|
idrefvec = yang_cvec_get(ybaseid);
|
|
if (cvec_len(idrefvec) > 0){
|
|
/* Add a wildchar string first -let validate take it for default prefix */
|
|
cprintf(cb, ">");
|
|
yang2cli_helptext(cb, helptext);
|
|
cprintf(cb, "|<%s:%s choice:", yang_argument_get(ys), cvtypestr);
|
|
yspec = ys_spec(ys);
|
|
i = 0;
|
|
while ((cv = cvec_each(idrefvec, cv)) != NULL){
|
|
if (nodeid_split(cv_name_get(cv), &prefix, &id) < 0)
|
|
goto done;
|
|
/* Translate from module-name(prefix) to global prefix
|
|
* This is actually quite complicated: the cli needs to generate
|
|
* a netconf statement with correct xmlns binding
|
|
*/
|
|
if ((ymod = yang_find_module_by_name(yspec, prefix)) != NULL &&
|
|
(yprefix = yang_find(ymod, Y_PREFIX, NULL)) != NULL){
|
|
if (i++)
|
|
cprintf(cb, "|");
|
|
cprintf(cb, "%s:%s", yang_argument_get(yprefix), id);
|
|
}
|
|
if (prefix){
|
|
free(prefix);
|
|
prefix = NULL;
|
|
}
|
|
if (id){
|
|
free(id);
|
|
id = NULL;
|
|
}
|
|
}
|
|
}
|
|
ok:
|
|
retval = 0;
|
|
done:
|
|
if (prefix)
|
|
free(prefix);
|
|
if (id)
|
|
free(id);
|
|
return retval;
|
|
}
|
|
|
|
/*! Generate range check statements for CLI variables
|
|
*
|
|
* @param[in] ys Yang statement
|
|
* @param[in] options Flags field of optional values, eg YANG_OPTIONS_RANGE
|
|
* @param[in] cvv Cvec with array of range_min/range_max cv:s (if YANG_OPTIONS_RANGE is set in options)
|
|
* @param[out] cb Buffer where cligen code is written
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
* @see yang2cli_var_sub which is the main function
|
|
* In yang ranges are given as range 1 or range 1 .. 16, encoded in a cvv
|
|
* 0 : range_min = x
|
|
* and
|
|
* 0 : range_min = x
|
|
* 1 : range_max = y
|
|
* Multiple ranges are given as: range x..y | x1..y1
|
|
* This is encode in clixon as a cvec as:
|
|
* 0 : range_min = x
|
|
* 1 : range_max = y
|
|
* 0 : range_min = x1
|
|
* 1 : range_max = y1
|
|
*
|
|
* Generation of cli code
|
|
* Single range is made by eg:
|
|
* <n:uint8 range[1:16]>
|
|
* Multiple ranges is made by generating code eg:
|
|
* <n:uint8 range[1:16] range[32:64]>
|
|
*/
|
|
static int
|
|
yang2cli_var_range(yang_stmt *ys,
|
|
int options,
|
|
cvec *cvv,
|
|
cbuf *cb)
|
|
{
|
|
int retval = -1;
|
|
int i;
|
|
cg_var *cv1; /* lower limit */
|
|
cg_var *cv2; /* upper limit */
|
|
|
|
/* Loop through range_min and range_min..range_max */
|
|
i = 0;
|
|
while (i<cvec_len(cvv)){
|
|
cv1 = cvec_i(cvv, i++);
|
|
if (strcmp(cv_name_get(cv1),"range_min") == 0){
|
|
cprintf(cb, " %s[", (options&YANG_OPTIONS_RANGE)?"range":"length");
|
|
cv2cbuf(cv1, cb);
|
|
cprintf(cb,":");
|
|
/* probe next */
|
|
if (i<cvec_len(cvv) &&
|
|
(cv2 = cvec_i(cvv, i)) != NULL &&
|
|
strcmp(cv_name_get(cv2),"range_max") == 0){
|
|
i++;
|
|
cv2cbuf(cv2, cb);
|
|
}
|
|
else /* If not, it is a single number range [x:x]*/
|
|
cv2cbuf(cv1, cb);
|
|
cprintf(cb,"]");
|
|
}
|
|
}
|
|
retval = 0;
|
|
// done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Generate CLI code for Yang variable pattern statement
|
|
*
|
|
* @param[in] h Clixon handle
|
|
* @param[in] patterns Cvec of regexp patterns
|
|
* @param[out] cb Buffer where cligen code is written
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
* @see cv_validate_pattern for netconf validate code
|
|
* @note for cligen, need to escape " -> \"
|
|
*/
|
|
static int
|
|
yang2cli_var_pattern(clixon_handle h,
|
|
cvec *patterns,
|
|
cbuf *cb)
|
|
{
|
|
int retval = -1;
|
|
enum regexp_mode mode;
|
|
cg_var *cvp;
|
|
char *pattern;
|
|
int invert;
|
|
char *posix = NULL;
|
|
int i;
|
|
|
|
mode = clicon_yang_regexp(h);
|
|
cvp = NULL; /* Loop over compiled regexps */
|
|
while ((cvp = cvec_each(patterns, cvp)) != NULL){
|
|
pattern = cv_string_get(cvp);
|
|
invert = cv_flag(cvp, V_INVERT);
|
|
cprintf(cb, " regexp:%s\"", invert?"!":"");
|
|
if (mode == REGEXP_POSIX){
|
|
posix = NULL;
|
|
if (regexp_xsd2posix(pattern, &posix) < 0)
|
|
goto done;
|
|
for (i=0; i<strlen(posix); i++){
|
|
if (posix[i] == '\"')
|
|
cbuf_append(cb, '\\');
|
|
cbuf_append(cb, posix[i]);
|
|
}
|
|
if (posix){
|
|
free(posix);
|
|
posix = NULL;
|
|
}
|
|
|
|
}
|
|
else{
|
|
for (i=0; i<strlen(pattern); i++){
|
|
if (pattern[i] == '\"')
|
|
cbuf_append(cb, '\\');
|
|
cbuf_append(cb, pattern[i]);
|
|
}
|
|
}
|
|
cprintf(cb, "\"");
|
|
}
|
|
retval = 0;
|
|
done:
|
|
if (posix)
|
|
free(posix);
|
|
return retval;
|
|
}
|
|
|
|
/* Forward */
|
|
static int yang2cli_stmt(clixon_handle h, yang_stmt *ys, int level, cbuf *cb);
|
|
|
|
static int yang2cli_var_union(clixon_handle h, yang_stmt *ys, char *origtype,
|
|
yang_stmt *ytype, char *helptext, cbuf *cb);
|
|
|
|
/*! Generate CLI code for Yang leaf state ment to CLIgen variable of specific type
|
|
*
|
|
* Check for completion (of already existent values), ranges (eg range[min:max]) and
|
|
* patterns, (eg regexp:"[0.9]*").
|
|
* @param[in] h Clixon handle
|
|
* @param[in] ys Yang statement
|
|
* @param[in] ytype Resolved yang type.
|
|
* @param[in] helptext CLI help text
|
|
* @param[in] cvtype
|
|
* @param[in] options Flags field of optional values, see YANG_OPTIONS_*
|
|
* @param[in] cvv Cvec with array of range_min/range_max cv:s
|
|
* @param[in] patterns Cvec of regexp patterns
|
|
* @param[in] fraction for decimal64, how many digits after period
|
|
* @param[out] cb Buffer where cligen code is written
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
* @see yang_type_resolve for options and other arguments
|
|
*/
|
|
static int
|
|
yang2cli_var_sub(clixon_handle h,
|
|
yang_stmt *ys,
|
|
yang_stmt *ytype, /* resolved type */
|
|
char *helptext,
|
|
enum cv_type cvtype,
|
|
int options,
|
|
cvec *cvv,
|
|
cvec *patterns,
|
|
uint8_t fraction_digits,
|
|
cbuf *cb
|
|
)
|
|
{
|
|
int retval = -1;
|
|
char *type;
|
|
yang_stmt *yi = NULL;
|
|
int i;
|
|
int inext;
|
|
int j;
|
|
const char *cvtypestr;
|
|
char *arg;
|
|
size_t len;
|
|
|
|
if (cvtype == CGV_VOID){
|
|
retval = 0;
|
|
goto done;
|
|
}
|
|
type = ytype?yang_argument_get(ytype):NULL;
|
|
cvtypestr = cv_type2str(cvtype);
|
|
if (type && strcmp(type, "identityref") == 0)
|
|
cprintf(cb, "(");
|
|
cprintf(cb, "<%s:%s", yang_argument_get(ys), cvtypestr);
|
|
/* enumeration special case completion */
|
|
if (type){
|
|
if (strcmp(type, "enumeration") == 0 || strcmp(type, "bits") == 0){
|
|
cprintf(cb, " choice:");
|
|
i = 0;
|
|
inext = 0;
|
|
while ((yi = yn_iter(ytype, &inext)) != NULL){
|
|
if (yang_keyword_get(yi) != Y_ENUM && yang_keyword_get(yi) != Y_BIT)
|
|
continue;
|
|
if (i)
|
|
cprintf(cb, "|");
|
|
/* Encode by escaping delimiters */
|
|
arg = yang_argument_get(yi);
|
|
len = strlen(arg);
|
|
for (j=0; j<len; j++){
|
|
if (index(CLIGEN_DELIMITERS, arg[j]))
|
|
cprintf(cb, "\\");
|
|
cprintf(cb, "%c", arg[j]);
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
else if (strcmp(type, "identityref") == 0){
|
|
if (yang2cli_var_identityref(ys, ytype, cvtypestr, helptext, cb) < 0)
|
|
goto done;
|
|
}
|
|
}
|
|
if (options & YANG_OPTIONS_FRACTION_DIGITS)
|
|
cprintf(cb, " fraction-digits:%u", fraction_digits);
|
|
|
|
if (options & (YANG_OPTIONS_RANGE|YANG_OPTIONS_LENGTH)){
|
|
if (yang2cli_var_range(ys, options, cvv, cb) < 0)
|
|
goto done;
|
|
}
|
|
if (patterns && cvec_len(patterns)){
|
|
if (yang2cli_var_pattern(h, patterns, cb) < 0)
|
|
goto done;
|
|
}
|
|
cprintf(cb, ">");
|
|
yang2cli_helptext(cb, helptext);
|
|
if (type && strcmp(type, "identityref") == 0)
|
|
cprintf(cb, ")");
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Resolve a single Yang union and generate code
|
|
*
|
|
* Part of generating CLI code for Yang leaf statement to CLIgen variable
|
|
* @param[in] h Clixon handle
|
|
* @param[in] ys Yang statement (caller of type)
|
|
* @param[in] origtype Name of original type in the call
|
|
* @param[in] ytsub Yang type invocation, a sub-type of a resolved union type
|
|
* @param[in] cb Buffer where cligen code is written
|
|
* @param[in] helptext CLI help text
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*/
|
|
static int
|
|
yang2cli_var_union_one(clixon_handle h,
|
|
yang_stmt *ys,
|
|
char *origtype,
|
|
yang_stmt *ytsub,
|
|
char *helptext,
|
|
cbuf *cb)
|
|
{
|
|
int retval = -1;
|
|
int options = 0;
|
|
cvec *cvv = NULL;
|
|
cvec *patterns = NULL;
|
|
uint8_t fraction_digits = 0;
|
|
enum cv_type cvtype;
|
|
yang_stmt *ytype; /* resolved type */
|
|
char *restype;
|
|
|
|
if ((patterns = cvec_new(0)) == NULL){
|
|
clixon_err(OE_UNIX, errno, "cvec_new");
|
|
goto done;
|
|
}
|
|
/* Resolve the sub-union type to a resolved type */
|
|
if (yang_type_resolve(ys, ys, ytsub, /* in */
|
|
&ytype, &options, /* resolved type */
|
|
&cvv, patterns, NULL, &fraction_digits) < 0)
|
|
goto done;
|
|
if (ytype == NULL){
|
|
clixon_err(OE_YANG, 0, "result-type should not be NULL");
|
|
goto done;
|
|
}
|
|
restype = ytype?yang_argument_get(ytype):NULL;
|
|
|
|
if (restype && strcmp(restype, "union") == 0){ /* recursive union */
|
|
if (yang2cli_var_union(h, ys, origtype, ytype, helptext, cb) < 0)
|
|
goto done;
|
|
}
|
|
/* XXX leafref inside union ? */
|
|
else {
|
|
if (clicon_type2cv(origtype, restype, ys, &cvtype) < 0)
|
|
goto done;
|
|
if ((retval = yang2cli_var_sub(h, ys, ytype, helptext, cvtype,
|
|
options, cvv, patterns, fraction_digits, cb)) < 0)
|
|
goto done;
|
|
}
|
|
retval = 0;
|
|
done:
|
|
if (patterns)
|
|
cvec_free(patterns);
|
|
return retval;
|
|
}
|
|
|
|
/*! Loop over all sub-types of a Yang union
|
|
*
|
|
* Part of generating CLI code for Yang leaf statement to CLIgen variable
|
|
* @param[in] h Clixon handle
|
|
* @param[in] ys Yang statement (caller)
|
|
* @param[in] origtype Name of original type in the call
|
|
* @param[in] ytype Yang resolved type (a union in this case)
|
|
* @param[in] helptext CLI help text
|
|
* @param[out] cb Buffer where cligen code is written
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*/
|
|
static int
|
|
yang2cli_var_union(clixon_handle h,
|
|
yang_stmt *ys,
|
|
char *origtype,
|
|
yang_stmt *ytype,
|
|
char *helptext,
|
|
cbuf *cb)
|
|
{
|
|
int retval = -1;
|
|
yang_stmt *ytsub;
|
|
int i;
|
|
int inext;
|
|
|
|
i = 0;
|
|
inext = 0;
|
|
/* Loop over all sub-types in the resolved union type, note these are
|
|
* not resolved types (unless they are built-in, but the resolve call is
|
|
* made in the union_one call.
|
|
*/
|
|
while ((ytsub = yn_iter(ytype, &inext)) != NULL){
|
|
if (yang_keyword_get(ytsub) != Y_TYPE)
|
|
continue;
|
|
if (i++)
|
|
cprintf(cb, "|");
|
|
if (yang2cli_var_union_one(h, ys, origtype, ytsub, helptext, cb) < 0)
|
|
goto done;
|
|
}
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
static int
|
|
yang2cli_var_leafref(clixon_handle h,
|
|
yang_stmt *ys,
|
|
yang_stmt *yrestype,
|
|
char *helptext,
|
|
enum cv_type cvtype,
|
|
int options,
|
|
cvec *cvv,
|
|
cvec *patterns,
|
|
uint8_t fraction_digits,
|
|
cbuf *cb)
|
|
{
|
|
int retval = -1;
|
|
char *type;
|
|
int completionp;
|
|
const char *cvtypestr;
|
|
int ret;
|
|
int flag;
|
|
int regular_value = 1; /* if strict-expand==0 then regular-value is false */
|
|
|
|
/* Give up: use yreferred
|
|
* XXX: inline of else clause below
|
|
*/
|
|
type = yrestype?yang_argument_get(yrestype):NULL;
|
|
cvtypestr = cv_type2str(cvtype);
|
|
if (autocli_completion(h, &completionp) < 0)
|
|
goto done;
|
|
if (type && completionp){
|
|
completionp = strcmp(type, "enumeration") != 0 &&
|
|
strcmp(type, "identityref") != 0 &&
|
|
strcmp(type, "bits") != 0;
|
|
}
|
|
if (yang_extension_value(ys, "strict-expand", CLIXON_AUTOCLI_NS, &flag, NULL) < 0)
|
|
goto done;
|
|
regular_value = !flag;
|
|
if (completionp && regular_value)
|
|
cprintf(cb, "(");
|
|
if (regular_value)
|
|
if (yang2cli_var_sub(h, ys, yrestype, helptext, cvtype,
|
|
options, cvv, patterns, fraction_digits, cb) < 0)
|
|
goto done;
|
|
if (completionp){
|
|
if ((ret = cli_expand_var_generate(h, ys, cvtypestr,
|
|
options, fraction_digits, regular_value,
|
|
cb)) < 0)
|
|
goto done;
|
|
if (ret == 1)
|
|
yang2cli_helptext(cb, helptext);
|
|
}
|
|
if (completionp && regular_value)
|
|
cprintf(cb, ")");
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Generate CLI code for Yang leaf statement to CLIgen variable
|
|
*
|
|
* @param[in] h Clixon handle
|
|
* @param[in] ys Yang statement of original leaf
|
|
* @param[in] yreferred Yang statement of referred node for type (leafref)
|
|
* @param[in] helptext CLI help text
|
|
* @param[out] cb Buffer where cligen code is written
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*
|
|
* Make a type lookup and complete a cligen variable expression such as <a:string>.
|
|
* One complication is yang union, that needs a recursion since it consists of
|
|
* sub-types.
|
|
* eg type union{ type int32; type string } --> (<x:int32>| <x:string>)
|
|
* Another is multiple ranges
|
|
* @note leafrefs are troublesome. In this code their cligen type are string, but they should really
|
|
* be the type of the referred node. But since the path pointing to the referred node is XML, and
|
|
* only YANG is known here, we cannot easily determine the YANG node of the referred XML node,
|
|
* and thus its type.
|
|
*/
|
|
static int
|
|
yang2cli_var(clixon_handle h,
|
|
yang_stmt *ys,
|
|
yang_stmt *yreferred,
|
|
char *helptext,
|
|
cbuf *cb)
|
|
{
|
|
int retval = -1;
|
|
char *origtype = NULL;
|
|
yang_stmt *yrestype; /* resolved type */
|
|
char *restype; /* resolved type */
|
|
cvec *cvv = NULL;
|
|
cvec *patterns = NULL;
|
|
uint8_t fraction_digits = 0;
|
|
enum cv_type cvtype;
|
|
const char *cvtypestr;
|
|
int options = 0;
|
|
int completionp;
|
|
int ret;
|
|
|
|
if ((patterns = cvec_new(0)) == NULL){
|
|
clixon_err(OE_UNIX, errno, "cvec_new");
|
|
goto done;
|
|
}
|
|
if (yang_type_get(yreferred, &origtype, &yrestype,
|
|
&options, &cvv, patterns, NULL, &fraction_digits) < 0)
|
|
goto done;
|
|
restype = yang_argument_get(yrestype);
|
|
if (strcmp(restype, "empty") == 0)
|
|
goto ok;
|
|
if (clicon_type2cv(origtype, restype, yreferred, &cvtype) < 0)
|
|
goto done;
|
|
cvtypestr = cv_type2str(cvtype);
|
|
/* Note restype can be NULL here for example with unresolved hardcoded uuid */
|
|
|
|
if (strcmp(restype, "union") == 0){
|
|
/* Union: loop over resolved type's sub-types (can also be recursive unions) */
|
|
cprintf(cb, "(");
|
|
if (yang2cli_var_union(h, ys, origtype, yrestype, helptext, cb) < 0)
|
|
goto done;
|
|
if (autocli_completion(h, &completionp) < 0)
|
|
goto done;
|
|
if (completionp){
|
|
if ((ret = cli_expand_var_generate(h, ys, cvtypestr,
|
|
options, fraction_digits, 1, cb)) < 0)
|
|
goto done;
|
|
if (ret == 1)
|
|
yang2cli_helptext(cb, helptext);
|
|
}
|
|
cprintf(cb, ")");
|
|
}
|
|
else if (strcmp(restype, "leafref")==0){
|
|
yang_stmt *ypath;
|
|
char *path_arg;
|
|
yang_stmt *yref = NULL;
|
|
|
|
if ((ypath = yang_find(yrestype, Y_PATH, NULL)) == NULL){
|
|
clixon_err(OE_YANG, 0, "No Y_PATH for leafref");
|
|
goto done;
|
|
}
|
|
if ((path_arg = yang_argument_get(ypath)) == NULL){
|
|
clixon_err(OE_YANG, 0, "No argument for Y_PATH");
|
|
goto done;
|
|
}
|
|
if (yang_path_arg(yreferred, path_arg, &yref) < 0)
|
|
goto done;
|
|
if (yref == NULL){
|
|
/* Give up: use yreferred
|
|
*/
|
|
if (yang2cli_var_leafref(h, ys, yrestype, helptext, cvtype, options,
|
|
cvv, patterns, fraction_digits, cb) < 0)
|
|
goto done;
|
|
}
|
|
else {
|
|
if (yreferred == yref){
|
|
clixon_err(OE_YANG, 0, "Referred YANG node for leafref path %s points to self", path_arg);
|
|
goto done;
|
|
}
|
|
/* recurse call with new referred node */
|
|
if (yang2cli_var(h, ys, yref, helptext, cb) < 0)
|
|
goto done;
|
|
}
|
|
}
|
|
else{
|
|
if (yang2cli_var_leafref(h, ys, yrestype, helptext, cvtype, options,
|
|
cvv, patterns, fraction_digits, cb) < 0)
|
|
goto done;
|
|
}
|
|
ok:
|
|
retval = 0;
|
|
done:
|
|
if (origtype)
|
|
free(origtype);
|
|
if (patterns)
|
|
cvec_free(patterns);
|
|
return retval;
|
|
}
|
|
|
|
/*! Generate CLI code for Yang leaf statement
|
|
*
|
|
* @param[in] h Clixon handle
|
|
* @param[in] ys Yang statement
|
|
* @param[in] level Indentation level
|
|
* @param[in] callback If set, include a "; cli_set()" callback, otherwise not
|
|
* @param[in] key_leaf 0: ordinary leaf, 1:prekey, 2: lastkey
|
|
* @param[out] cb Buffer where cligen code is written
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
* Some complexity in callback, key_leaf and extralevel logic.
|
|
* If extralevel -> add extra { } level
|
|
* + if callbacks add: cb();{}
|
|
*/
|
|
static int
|
|
yang2cli_leaf(clixon_handle h,
|
|
yang_stmt *ys,
|
|
int level,
|
|
int callback,
|
|
int key_leaf,
|
|
cbuf *cb)
|
|
{
|
|
int retval = -1;
|
|
yang_stmt *yd; /* description */
|
|
char *helptext = NULL;
|
|
char *s;
|
|
autocli_listkw_t listkw;
|
|
int hideext = 0;
|
|
int extralevel = 0;
|
|
yang_stmt *yrestype; /* resolved type */
|
|
|
|
/* description */
|
|
if ((yd = yang_find(ys, Y_DESCRIPTION, NULL)) != NULL){
|
|
if ((helptext = strdup(yang_argument_get(yd))) == NULL){
|
|
clixon_err(OE_UNIX, errno, "strdup");
|
|
goto done;
|
|
}
|
|
if ((s = strstr(helptext, "\n\n")) != NULL)
|
|
*s = '\0';
|
|
}
|
|
cprintf(cb, "%*s", level*3, "");
|
|
/* Called a second time in yang2cli_var, room for optimization */
|
|
if (yang_type_get(ys, NULL, &yrestype,
|
|
NULL, NULL, NULL, NULL, NULL) < 0)
|
|
goto done;
|
|
if (key_leaf == 0 && strcmp(yang_argument_get(yrestype), "empty") != 0)
|
|
extralevel = 1;
|
|
if (autocli_list_keyword(h, &listkw) < 0)
|
|
goto done;
|
|
if (listkw == AUTOCLI_LISTKW_ALL ||
|
|
(key_leaf==0 && listkw == AUTOCLI_LISTKW_NOKEY)){
|
|
if (yang2cli_print_alias(cb, ys) < 0)
|
|
goto done;
|
|
yang2cli_helptext(cb, helptext);
|
|
cprintf(cb, " ");
|
|
if (yang_extension_value(ys, "hide", CLIXON_AUTOCLI_NS, &hideext, NULL) < 0)
|
|
goto done;
|
|
if (hideext || yang_find(ys, Y_STATUS, "deprecated") != NULL){
|
|
cprintf(cb, ", hide"); /* XXX ensure always { */
|
|
}
|
|
if (extralevel){
|
|
if (callback){
|
|
if (cli_callback_generate(h, ys, cb) < 0)
|
|
goto done;
|
|
cprintf(cb, ", ac-leaf");
|
|
cprintf(cb, ", act-leafconst");
|
|
cprintf(cb, ";\n");
|
|
}
|
|
cprintf(cb, "{"); /* termleaf label + extra level around leaf */
|
|
}
|
|
if (yang2cli_var(h, ys, ys, helptext, cb) < 0)
|
|
goto done;
|
|
}
|
|
else{
|
|
if (yang2cli_var(h, ys, ys, helptext, cb) < 0)
|
|
goto done;
|
|
}
|
|
if (callback){
|
|
if (cli_callback_generate(h, ys, cb) < 0)
|
|
goto done;
|
|
switch (key_leaf){
|
|
case 0:
|
|
cprintf(cb, ", ac-leaf");
|
|
cprintf(cb, ", act-leafvar");
|
|
break;
|
|
case 1:
|
|
cprintf(cb, ", ac-leaf");
|
|
cprintf(cb, ", act-prekey");
|
|
break;
|
|
case 2:
|
|
/* Dont mark as leaf since it represents a (single) list entry */
|
|
cprintf(cb, ", act-lastkey");
|
|
break;
|
|
}
|
|
cprintf(cb, ";\n");
|
|
}
|
|
if (extralevel)
|
|
cprintf(cb, "}\n");
|
|
retval = 0;
|
|
done:
|
|
if (helptext)
|
|
free(helptext);
|
|
return retval;
|
|
}
|
|
|
|
/*! Generate CLI code for Yang container statement
|
|
*
|
|
* @param[in] h Clixon handle
|
|
* @param[in] ys Yang statement
|
|
* @param[in] level Indentation level
|
|
* @param[out] cb Buffer where cligen code is written
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*/
|
|
static int
|
|
yang2cli_container(clixon_handle h,
|
|
yang_stmt *ys,
|
|
int level,
|
|
cbuf *cb)
|
|
{
|
|
int retval = -1;
|
|
yang_stmt *yc;
|
|
yang_stmt *yd;
|
|
char *helptext = NULL;
|
|
char *s;
|
|
int compress = 0;
|
|
yang_stmt *ymod = NULL;
|
|
int extvalue = 0;
|
|
int inext;
|
|
int ret;
|
|
|
|
if (ys_real_module(ys, &ymod) < 0)
|
|
goto done;
|
|
/* If non-presence container && HIDE mode && only child is
|
|
* a list, then skip container keyword
|
|
* See also clixon_cli2file
|
|
*/
|
|
if (autocli_compress(h, ys, &compress) < 0)
|
|
goto done;
|
|
if (!compress){
|
|
cprintf(cb, "%*s%s", level*3, "", yang_argument_get(ys));
|
|
if ((yd = yang_find(ys, Y_DESCRIPTION, NULL)) != NULL){
|
|
if ((helptext = strdup(yang_argument_get(yd))) == NULL){
|
|
clixon_err(OE_UNIX, errno, "strdup");
|
|
goto done;
|
|
}
|
|
if ((s = strstr(helptext, "\n\n")) != NULL)
|
|
*s = '\0';
|
|
yang2cli_helptext(cb, helptext);
|
|
}
|
|
if (cli_callback_generate(h, ys, cb) < 0)
|
|
goto done;
|
|
if (yang_extension_value(ys, "hide", CLIXON_AUTOCLI_NS, &extvalue, NULL) < 0)
|
|
goto done;
|
|
if (extvalue || yang_find(ys, Y_STATUS, "deprecated") != NULL){
|
|
cprintf(cb, ", hide");
|
|
}
|
|
cprintf(cb, ", act-container;{\n");
|
|
}
|
|
/* Is schema mount-point? */
|
|
if (clicon_option_bool(h, "CLICON_YANG_SCHEMA_MOUNT")){
|
|
if ((ret = yang_schema_mount_point(ys)) < 0)
|
|
goto done;
|
|
if (ret){
|
|
cprintf(cb, "%*s%s", (level+1)*3, "", "@mountpoint;\n");
|
|
}
|
|
}
|
|
inext = 0;
|
|
while ((yc = yn_iter(ys, &inext)) != NULL)
|
|
if (yang2cli_stmt(h, yc, level+1, cb) < 0)
|
|
goto done;
|
|
if (!compress)
|
|
cprintf(cb, "%*s}\n", level*3, "");
|
|
retval = 0;
|
|
done:
|
|
if (helptext)
|
|
free(helptext);
|
|
return retval;
|
|
}
|
|
|
|
/*! Generate CLI code for Yang list statement
|
|
*
|
|
* @param[in] h Clixon handle
|
|
* @param[in] ys Yang statement
|
|
* @param[in] level Indentation level
|
|
* @param[out] cb Buffer where cligen code is written
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*/
|
|
static int
|
|
yang2cli_list(clixon_handle h,
|
|
yang_stmt *ys,
|
|
int level,
|
|
cbuf *cb)
|
|
{
|
|
int retval = -1;
|
|
yang_stmt *yc;
|
|
yang_stmt *yd;
|
|
yang_stmt *yleaf;
|
|
cg_var *cvi;
|
|
char *keyname;
|
|
cvec *cvk = NULL; /* vector of index keys */
|
|
char *helptext = NULL;
|
|
char *s;
|
|
int last_key = 0;
|
|
int exist = 0;
|
|
int keynr = 0;
|
|
int inext;
|
|
|
|
cprintf(cb, "%*s%s", level*3, "", yang_argument_get(ys));
|
|
if ((yd = yang_find(ys, Y_DESCRIPTION, NULL)) != NULL){
|
|
if ((helptext = strdup(yang_argument_get(yd))) == NULL){
|
|
clixon_err(OE_UNIX, errno, "strdup");
|
|
goto done;
|
|
}
|
|
if ((s = strstr(helptext, "\n\n")) != NULL)
|
|
*s = '\0';
|
|
yang2cli_helptext(cb, helptext);
|
|
}
|
|
if (yang_extension_value(ys, "hide", CLIXON_AUTOCLI_NS, &exist, NULL) < 0)
|
|
goto done;
|
|
if (exist || yang_find(ys, Y_STATUS, "deprecated") != NULL){
|
|
cprintf(cb, ",hide");
|
|
}
|
|
/* Loop over all key variables */
|
|
cvk = yang_cvec_get(ys); /* Use Y_LIST cache, see ys_populate_list() */
|
|
cvi = NULL;
|
|
/* Iterate over individual keys */
|
|
while ((cvi = cvec_each(cvk, cvi)) != NULL) {
|
|
keyname = cv_string_get(cvi);
|
|
if ((yleaf = yang_find(ys, Y_LEAF, keyname)) == NULL){
|
|
clixon_err(OE_XML, 0, "List statement \"%s\" has no key leaf \"%s\"",
|
|
yang_argument_get(ys), keyname);
|
|
goto done;
|
|
}
|
|
/* Print key variable now, and skip it in loop below
|
|
* Note, only print callback on last statement
|
|
*/
|
|
last_key = cvec_next(cvk, cvi)?0:1;
|
|
if (cli_callback_generate(h, ys, cb) < 0)
|
|
goto done;
|
|
if (keynr == 0)
|
|
cprintf(cb, ",act-list");
|
|
else
|
|
cprintf(cb, ",act-prekey");
|
|
cprintf(cb, ";\n");
|
|
cprintf(cb, "{\n");
|
|
if (yang2cli_leaf(h, yleaf,
|
|
level+1,
|
|
last_key, /* callback */
|
|
last_key?2:1, /* key_leaf */
|
|
cb) < 0)
|
|
goto done;
|
|
keynr++;
|
|
}
|
|
cprintf(cb, "{\n");
|
|
inext = 0;
|
|
while ((yc = yn_iter(ys, &inext)) != NULL) {
|
|
/* cvk is a cvec of strings containing variable names
|
|
yc is a leaf that may match one of the values of cvk.
|
|
*/
|
|
cvi = NULL;
|
|
while ((cvi = cvec_each(cvk, cvi)) != NULL) {
|
|
keyname = cv_string_get(cvi);
|
|
if (strcmp(keyname, yang_argument_get(yc)) == 0)
|
|
break;
|
|
}
|
|
if (cvi != NULL)
|
|
continue;
|
|
if (yang2cli_stmt(h, yc, level+1, cb) < 0)
|
|
goto done;
|
|
}
|
|
cprintf(cb, "%*s}\n", level*3, "");
|
|
/* Close with } for each key */
|
|
while (keynr--)
|
|
cprintf(cb, "%*s}\n", level*3, "");
|
|
retval = 0;
|
|
done:
|
|
if (helptext)
|
|
free(helptext);
|
|
return retval;
|
|
}
|
|
|
|
/*! Generate CLI code for Yang choice statement
|
|
*
|
|
* @param[in] h Clixon handle
|
|
* @param[in] ys Yang statement
|
|
* @param[in] level Indentation level
|
|
* @param[out] cb Buffer where cligen code is written
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
* @code
|
|
choice interface-type {
|
|
container ethernet { ... }
|
|
container fddi { ... }
|
|
}
|
|
* @code.end
|
|
@note Removes 'meta-syntax' from cli syntax. They are not shown when xml is
|
|
translated to cli. and therefore input-syntax != output syntax. Which is bad
|
|
*/
|
|
static int
|
|
yang2cli_choice(clixon_handle h,
|
|
yang_stmt *ys,
|
|
int level,
|
|
cbuf *cb)
|
|
{
|
|
int retval = -1;
|
|
yang_stmt *yc;
|
|
int inext;
|
|
|
|
inext = 0;
|
|
while ((yc = yn_iter(ys, &inext)) != NULL) {
|
|
switch (yang_keyword_get(yc)){
|
|
case Y_CASE:
|
|
if (yang2cli_stmt(h, yc, level+2, cb) < 0)
|
|
goto done;
|
|
break;
|
|
case Y_CONTAINER:
|
|
case Y_LEAF:
|
|
case Y_LEAF_LIST:
|
|
case Y_LIST:
|
|
default:
|
|
if (yang2cli_stmt(h, yc, level+1, cb) < 0)
|
|
goto done;
|
|
break;
|
|
}
|
|
}
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Generate clispec for all modules in a grouping
|
|
*
|
|
* Called in cli main function for top-level yangs. But may also be called dynamically for
|
|
* mountpoints.
|
|
* @param[in] h Clixon handle
|
|
* @param[in] ys Top-level Yang statement
|
|
* @param[in] treename Name of tree
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
* @note Tie-break of same top-level symbol: prefix is NYI
|
|
* @see yang2cli_yspec for original
|
|
*/
|
|
static int yang2cli_grouping(clixon_handle h, yang_stmt *ys, char *treename);
|
|
|
|
/*! Generate CLI code for Yang uses statement
|
|
*
|
|
* @param[in] h Clixon handle
|
|
* @param[in] ys Yang statement
|
|
* @param[in] level Indentation level
|
|
* @param[out] cb Buffer where cligen code is written
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*/
|
|
static int
|
|
yang2cli_uses(clixon_handle h,
|
|
yang_stmt *ys,
|
|
int level,
|
|
cbuf *cb)
|
|
{
|
|
int retval = -1;
|
|
char *id = NULL;
|
|
char *prefix = NULL;
|
|
char *ns;
|
|
yang_stmt *ygrouping;
|
|
cbuf *cbtree = NULL;
|
|
char *api_path_fmt = NULL;
|
|
yang_stmt *yp;
|
|
int ret;
|
|
|
|
if (nodeid_split(yang_argument_get(ys), &prefix, &id) < 0)
|
|
goto done;
|
|
if (ys_grouping_resolve(ys, prefix, id, &ygrouping) < 0)
|
|
goto done;
|
|
if (ygrouping == NULL){
|
|
clixon_err(OE_YANG, 0, "grouping %s not found in \n", yang_argument_get(ys));
|
|
goto done;
|
|
}
|
|
if ((cbtree = cbuf_new()) == NULL){
|
|
clixon_err(OE_XML, errno, "cbuf_new");
|
|
goto done;
|
|
}
|
|
/* prefix is not globally unique, need namespace */
|
|
if ((ns = yang_find_mynamespace(ygrouping)) == NULL)
|
|
goto done;
|
|
cprintf(cbtree, "grouping-%s-%s", ns, id);
|
|
if (cligen_ph_find(cli_cligen(h), cbuf_get(cbtree)) == NULL){
|
|
/* No such tree, generate it */
|
|
if ((ret = yang2cli_grouping(h, ygrouping, cbuf_get(cbtree))) < 0)
|
|
goto done;
|
|
if (ret == 0) /* tree empty */
|
|
goto ok;
|
|
}
|
|
cprintf(cb, "%*s@%s", level*3, "", cbuf_get(cbtree));
|
|
/* get api-path to parent, do not include "uses" argument since it is replaced */
|
|
yp = yang_parent_get(ys);
|
|
if (yang2api_path_fmt(yp, 0, &api_path_fmt) < 0)
|
|
goto done;
|
|
/* This adds a "prepend" callback with decendant argument */
|
|
cprintf(cb, ",%s(\"%s\")", GROUPING_CALLBACK, api_path_fmt);
|
|
cprintf(cb, ";\n");
|
|
ok:
|
|
retval = 0;
|
|
done:
|
|
if (api_path_fmt)
|
|
free(api_path_fmt);
|
|
if (prefix)
|
|
free(prefix);
|
|
if (id)
|
|
free(id);
|
|
if (cbtree)
|
|
cbuf_free(cbtree);
|
|
return retval;
|
|
}
|
|
|
|
/*! Generate CLI code for Yang statement
|
|
*
|
|
* @param[in] h Clixon handle
|
|
* @param[in] ys Yang statement
|
|
* @param[in] level Indentation level
|
|
* @param[out] cb Buffer where cligen code is written
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*/
|
|
static int
|
|
yang2cli_stmt(clixon_handle h,
|
|
yang_stmt *ys,
|
|
int level,
|
|
cbuf *cb)
|
|
{
|
|
int retval = -1;
|
|
yang_stmt *yc;
|
|
int treeref_state = 0;
|
|
int grouping_treeref = 0;
|
|
int extvalue = 0;
|
|
int inext;
|
|
|
|
if (ys == NULL){
|
|
clixon_err(OE_YANG, EINVAL, "No yang spec");
|
|
goto done;
|
|
}
|
|
if (yang_find(ys, Y_STATUS, "obsolete") != NULL){
|
|
clixon_debug(CLIXON_DBG_CLI | CLIXON_DBG_DETAIL, "obsolete: %s %s, skipped", yang_argument_get(ys), yang_argument_get(ys_module(ys)));
|
|
goto ok;
|
|
}
|
|
if (yang_find(ys, Y_STATUS, "deprecated") != NULL){
|
|
clixon_debug(CLIXON_DBG_CLI | CLIXON_DBG_DETAIL, "deprecated: %s %s", yang_argument_get(ys), yang_argument_get(ys_module(ys)));
|
|
}
|
|
/* Check if autocli skip */
|
|
if (yang_extension_value(ys, "skip", CLIXON_AUTOCLI_NS, &extvalue, NULL) < 0)
|
|
goto done;
|
|
if (extvalue == 1)
|
|
goto ok;
|
|
/* Only produce autocli for YANG non-config only if autocli-treeref-state is true */
|
|
if (autocli_treeref_state(h, &treeref_state) < 0)
|
|
goto done;
|
|
if (autocli_grouping_treeref(h, &grouping_treeref) < 0)
|
|
goto done;
|
|
if (treeref_state || yang_config(ys)){
|
|
int cornercase = 0;
|
|
if (grouping_treeref){
|
|
#ifdef AUTOCLI_GROUPING_TOPLEVEL_SKIP
|
|
cornercase = yang_keyword_get(yang_parent_get(ys)) == Y_MODULE || yang_keyword_get(yang_parent_get(ys)) == Y_SUBMODULE;
|
|
#endif
|
|
if (yang_keyword_get(ys) != Y_USES && yang_flag_get(ys, YANG_FLAG_GROUPING)
|
|
&& !cornercase
|
|
)
|
|
goto ok;
|
|
}
|
|
switch (yang_keyword_get(ys)){
|
|
case Y_CONTAINER:
|
|
if (yang2cli_container(h, ys, level, cb) < 0)
|
|
goto done;
|
|
break;
|
|
case Y_LIST:
|
|
if (yang2cli_list(h, ys, level, cb) < 0)
|
|
goto done;
|
|
break;
|
|
case Y_CHOICE:
|
|
if (yang2cli_choice(h, ys, level, cb) < 0)
|
|
goto done;
|
|
break;
|
|
case Y_LEAF_LIST:
|
|
case Y_LEAF:
|
|
if (yang2cli_leaf(h, ys, level,
|
|
1, /* callback */
|
|
0, /* keyleaf */
|
|
cb) < 0)
|
|
goto done;
|
|
break;
|
|
case Y_USES:
|
|
if (grouping_treeref && !cornercase)
|
|
if (yang2cli_uses(h, ys, level, cb) < 0)
|
|
goto done;
|
|
break;
|
|
case Y_CASE:
|
|
case Y_SUBMODULE:
|
|
case Y_MODULE:
|
|
inext = 0;
|
|
while ((yc = yn_iter(ys, &inext)) != NULL)
|
|
if (yang2cli_stmt(h, yc, level+1, cb) < 0)
|
|
goto done;
|
|
break;
|
|
default: /* skip */
|
|
break;
|
|
}
|
|
}
|
|
ok:
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Add cv with name to cvec
|
|
*
|
|
* @param[in] cvv Either existing or NULL
|
|
* @param[in] name Name of cv to add
|
|
* @retval cvv Either same as in cvv parameter or new
|
|
*/
|
|
static cvec*
|
|
cvec_add_name(cvec *cvv,
|
|
char *name)
|
|
{
|
|
cg_var *cv= NULL;
|
|
|
|
if (cvv == NULL &&
|
|
(cvv = cvec_new(0)) == NULL){
|
|
clixon_err(OE_UNIX, errno, "cvec_new");
|
|
return NULL;
|
|
}
|
|
if ((cv = cvec_add(cvv, CGV_STRING)) == NULL){
|
|
clixon_err(OE_UNIX, errno, "cvec_add");
|
|
return NULL;
|
|
}
|
|
/* Filter out state data, use "nonconfig" as defined in RFC8040 4.8.1
|
|
*/
|
|
cv_name_set(cv, name);
|
|
return cvv;
|
|
}
|
|
|
|
/*! Recursive post processing of generated cligen parsetree: populate with co_cvec labels
|
|
*
|
|
* This function adds labels to the generated CLIgen tree using YANG as follows:
|
|
* These labels can be filtered when applying them with the @treeref, @add:<label> syntax.
|
|
* (terminal entry means eg "a ;" where ; is an "empty" child of "a" representing a terminal)
|
|
* 1. Add "act-prekey" label on terminal entries of LIST keys, except last
|
|
* 2. Add "act-lastkey" label on terminal entries of last LIST keys,
|
|
* 3. Add "act-list" label on terminal entries of LIST
|
|
* 4. Add "act-leafconst" label on terminal entries of non-empty LEAF/LEAF_LISTs
|
|
* 5. Add "act-leafvar" label on nodes which are children of non-key LEAFs, eg "a <a>" -> "a <a>,leaf"
|
|
* 6. Add "ac-state" label on nodes which has YANG "config false" as child
|
|
* 7. Add "ac-config" label on nodes which have no config false children recursively
|
|
*
|
|
* @param[in] h Clixon handle
|
|
* @param[in] cop Parent cliegn object (if any)
|
|
* @param[in] pt CLIgen parse-tree (generated syntax)
|
|
* @param[in] i0 Offset into pt
|
|
* @param[in] yp YANG parent node of "pt"
|
|
* @param[in] ykey Special case, If y is list, yc can be a leaf key
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
* @note A labels set as : "A, label;" is set on "A" not on ";", there is no way to set the
|
|
* label on the empty terminal ";". Therefore this function moves them all from the
|
|
* parent to the ";" child.
|
|
* XXX: the above kludge can be fixed by:
|
|
* (1) change cligen syntax
|
|
* (2) rewrite yang2cli code to create pt directly instead of via a cbuf.
|
|
*/
|
|
static int
|
|
yang2cli_post(clixon_handle h,
|
|
cg_obj *cop,
|
|
parse_tree *pt,
|
|
int i0,
|
|
yang_stmt *yp,
|
|
yang_stmt *ykey,
|
|
int *configp)
|
|
{
|
|
int retval = -1;
|
|
cg_obj *co;
|
|
int i;
|
|
yang_stmt *yc;
|
|
int yciskey;
|
|
enum rfc_6020 ypkeyword;
|
|
int config;
|
|
int state = 0;
|
|
|
|
ypkeyword = yang_keyword_get(yp);
|
|
for (i = i0; i<pt_len_get(pt); i++){
|
|
if ((co = pt_vec_i_get(pt, i)) == NULL){
|
|
clixon_err(OE_YANG, 0, "Empty object in parsetreelist"); /* shouldnt happen */
|
|
goto done;
|
|
}
|
|
if (co->co_type == CO_EMPTY){
|
|
char *name;
|
|
cg_var *cv = NULL;
|
|
int j=0;
|
|
|
|
cv = NULL;
|
|
while ((cv = cvec_each(cop->co_cvec, cv)) != NULL){
|
|
name = cv_name_get(cv);
|
|
if (strncmp(name, "act-", 4) == 0){
|
|
if ((co->co_cvec = cvec_add_name(co->co_cvec, name)) == NULL)
|
|
goto done;
|
|
cv_reset(cv);
|
|
cvec_del_i(cop->co_cvec, j);
|
|
if (cvec_len(cop->co_cvec) == 0){
|
|
cvec_free(cop->co_cvec);
|
|
cop->co_cvec = NULL;
|
|
}
|
|
cv = NULL; // trigger rerun
|
|
j = 0;
|
|
}
|
|
j++;
|
|
}
|
|
continue;
|
|
}
|
|
/* Filters out eg "name <name>" second instance if kw-all / kw-nokey
|
|
* But if only "<name>" it passes
|
|
*/
|
|
if ((yc = yang_find_datanode(yp, co->co_command)) == NULL){
|
|
#if 1
|
|
/* XXX In case of compress, look at next level */
|
|
yang_stmt *y;
|
|
int inext = 0;
|
|
|
|
while ((y = yn_iter(yp, &inext)) != NULL){
|
|
if (yang_datanode(y)){
|
|
if ((yc = yang_find_datanode(y, co->co_command)) != NULL)
|
|
break;
|
|
}
|
|
}
|
|
if (y == NULL)
|
|
continue;
|
|
#endif
|
|
}
|
|
yciskey = ypkeyword == Y_LIST && yang_key_match(yp, co->co_command, NULL);
|
|
/* If state: Add nonconfig label*/
|
|
config = *configp;
|
|
if (!yang_config(yc)){
|
|
if ((co->co_cvec = cvec_add_name(co->co_cvec, "ac-state")) == NULL)
|
|
goto done;
|
|
config = 0;
|
|
}
|
|
/* If y is list and yc is key, then call with y */
|
|
if (yciskey){
|
|
if (yang2cli_post(h, co, co_pt_get(co), 0, yp, yc, &config) < 0) // note y not yc
|
|
goto done;
|
|
}
|
|
else if (yang2cli_post(h, co, co_pt_get(co), 0, yc, NULL, &config) < 0)
|
|
goto done;
|
|
if (config){
|
|
if ((co->co_cvec = cvec_add_name(co->co_cvec, "ac-config")) == NULL)
|
|
goto done;
|
|
}
|
|
else
|
|
state++;
|
|
} /* for */
|
|
if (state)
|
|
*configp = 0;
|
|
else { /* Clear all ac-config labels */
|
|
for (i = i0; i<pt_len_get(pt); i++){
|
|
cg_var *cv;
|
|
int j=0;
|
|
|
|
co = pt_vec_i_get(pt, i);
|
|
cv = NULL;
|
|
while ((cv = cvec_each(co->co_cvec, cv)) != NULL){
|
|
if (strcmp(cv_name_get(cv), "ac-config") == 0){
|
|
cv_reset(cv);
|
|
cvec_del_i(co->co_cvec, j);
|
|
break;
|
|
}
|
|
j++;
|
|
}
|
|
}
|
|
}
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Generate clispec for all modules in a grouping
|
|
*
|
|
* Called in cli main function for top-level yangs. But may also be called dynamically for
|
|
* mountpoints.
|
|
* @param[in] h Clixon handle
|
|
* @param[in] ys Top-level Yang statement
|
|
* @param[in] treename Name of tree
|
|
* @retval 1 OK
|
|
* @retval 0 OK but empty clispec, no tree produced
|
|
* @retval -1 Error
|
|
* @note Tie-break of same top-level symbol: prefix is NYI
|
|
* @see yang2cli_yspec and yang2cli_stmt for original
|
|
* XXX merge with yang2cli_yspec
|
|
*/
|
|
static int
|
|
yang2cli_grouping(clixon_handle h,
|
|
yang_stmt *ys,
|
|
char *treename)
|
|
{
|
|
int retval = -1;
|
|
parse_tree *pt0 = NULL;
|
|
parse_tree *pt = NULL;
|
|
yang_stmt *yc;
|
|
pt_head *ph;
|
|
cbuf *cb = NULL;
|
|
int treeref_state = 0;
|
|
char *prefix;
|
|
cg_obj *co;
|
|
int config;
|
|
int i;
|
|
int inext;
|
|
|
|
if ((pt0 = pt_new()) == NULL){
|
|
clixon_err(OE_UNIX, errno, "pt_new");
|
|
goto done;
|
|
}
|
|
if ((cb = cbuf_new()) == NULL){
|
|
clixon_err(OE_XML, errno, "cbuf_new");
|
|
goto done;
|
|
}
|
|
/* Traverse YANG, loop through all modules and generate CLI, inline of yang2cli_stmt */
|
|
if (yang_find(ys, Y_STATUS, "obsolete") != NULL){
|
|
clixon_debug(CLIXON_DBG_CLI | CLIXON_DBG_DETAIL, "obsolete: %s %s, skipped", yang_argument_get(ys), yang_argument_get(ys_module(ys)));
|
|
goto empty;
|
|
}
|
|
if (yang_find(ys, Y_STATUS, "deprecated") != NULL){
|
|
clixon_debug(CLIXON_DBG_CLI | CLIXON_DBG_DETAIL, "deprecated: %s %s", yang_argument_get(ys), yang_argument_get(ys_module(ys)));
|
|
}
|
|
/* Only produce autocli for YANG non-config only if autocli-treeref-state is true */
|
|
if (autocli_treeref_state(h, &treeref_state) < 0)
|
|
goto done;
|
|
if (treeref_state || yang_config(ys)){
|
|
inext = 0;
|
|
while ((yc = yn_iter(ys, &inext)) != NULL)
|
|
if (yang2cli_stmt(h, yc, 1, cb) < 0)
|
|
goto done;
|
|
}
|
|
if (cbuf_len(cb) == 0)
|
|
goto empty;
|
|
/* Note Tie-break of same top-level symbol: prefix is NYI
|
|
* Needs to move cligen_parse_str() call here instead of later
|
|
*/
|
|
if ((prefix = yang_find_myprefix(ys)) == NULL){
|
|
clixon_err(OE_YANG, 0, "Module %s lacks prefix", yang_argument_get(ys)); /* shouldnt happen */
|
|
goto done;
|
|
}
|
|
if ((pt = pt_new()) == NULL){
|
|
clixon_err(OE_UNIX, errno, "pt_new");
|
|
goto done;
|
|
}
|
|
/* Parse the buffer using cligen parser. load cli syntax */
|
|
if (clispec_parse_str(cli_cligen(h), cbuf_get(cb), (char*)__FUNCTION__, NULL, pt, NULL) < 0){
|
|
clixon_err(OE_PLUGIN, 0, "%s", cbuf_get(cb));
|
|
goto done;
|
|
}
|
|
clixon_debug(CLIXON_DBG_CLI, "Generated auto-cli for grouping:%s",
|
|
yang_argument_get(ys));
|
|
/* Add prefix: assume new are appended */
|
|
for (i=0; i<pt_len_get(pt); i++){
|
|
if ((co = pt_vec_i_get(pt, i)) != NULL){
|
|
clixon_debug(CLIXON_DBG_CLI, "command: %s",
|
|
co->co_command);
|
|
co_prefix_set(co, prefix);
|
|
}
|
|
}
|
|
/* Post-processing, iterate over the generated cligen parse-tree with corresponding yang
|
|
* Note cannot do it inline in yang2cli above since:
|
|
* 1. labels cannot be set on "empty"
|
|
* 2. a; <a>, fn() cannot be set properly
|
|
*/
|
|
config = 1;
|
|
if (yang2cli_post(h, NULL, pt, 0, ys, NULL, &config) < 0){
|
|
goto done;
|
|
}
|
|
if (clicon_data_int_get(h, "autocli-print-debug") == 1)
|
|
clixon_log(h, LOG_NOTICE, "%s: Top-level cli-spec %s:\n%s",
|
|
__FUNCTION__, treename, cbuf_get(cb));
|
|
else
|
|
clixon_debug(CLIXON_DBG_CLI | CLIXON_DBG_DETAIL, "Top-level cli-spec %s:\n%s",
|
|
treename, cbuf_get(cb));
|
|
if (cligen_parsetree_merge(pt0, NULL, pt) < 0){
|
|
clixon_err(OE_YANG, errno, "cligen_parsetree_merge");
|
|
goto done;
|
|
}
|
|
pt_free(pt, 1);
|
|
pt = NULL;
|
|
|
|
/* Resolve the expand callback functions in the generated syntax.
|
|
* This "should" only be GENERATE_EXPAND_XMLDB
|
|
* handle=NULL for global namespace, this means expand callbacks must be in
|
|
* CLICON namespace, not in a cli frontend plugin.
|
|
*/
|
|
if (cligen_expandv_str2fn(pt0, (expandv_str2fn_t*)clixon_str2fn, NULL) < 0)
|
|
goto done;
|
|
/* Append cligen tree and name it */
|
|
if ((ph = cligen_ph_add(cli_cligen(h), treename)) == NULL){
|
|
clixon_err(OE_UNIX, 0, "cligen_ph_add");
|
|
goto done;
|
|
}
|
|
if (cligen_ph_parsetree_set(ph, pt0) < 0){
|
|
clixon_err(OE_UNIX, 0, "cligen_ph_parsetree_set");
|
|
goto done;
|
|
}
|
|
pt0 = NULL;
|
|
retval = 1;
|
|
done:
|
|
if (pt)
|
|
pt_free(pt, 1);
|
|
if (pt0)
|
|
pt_free(pt0, 1);
|
|
if (cb)
|
|
cbuf_free(cb);
|
|
return retval;
|
|
empty:
|
|
retval = 0;
|
|
goto done;
|
|
}
|
|
|
|
/*! Generate clispec for all modules in yspec (except excluded)
|
|
*
|
|
* Called in cli main function for top-level yangs. But may also be called dynamically for
|
|
* mountpoints.
|
|
* @param[in] h Clixon handle
|
|
* @param[in] yspec Top-level Yang statement of type Y_SPEC
|
|
* @param[in] treename Name of tree
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
* @note Tie-break of same top-level symbol: prefix is NYI
|
|
*/
|
|
int
|
|
yang2cli_yspec(clixon_handle h,
|
|
yang_stmt *yspec,
|
|
char *treename)
|
|
{
|
|
int retval = -1;
|
|
parse_tree *pt0 = NULL;
|
|
parse_tree *pt = NULL;
|
|
yang_stmt *ymod;
|
|
pt_head *ph;
|
|
int enable;
|
|
cbuf *cb = NULL;
|
|
char *prefix;
|
|
cg_obj *co;
|
|
int i;
|
|
int config;
|
|
int inext;
|
|
|
|
if ((pt0 = pt_new()) == NULL){
|
|
clixon_err(OE_UNIX, errno, "pt_new");
|
|
goto done;
|
|
}
|
|
if ((cb = cbuf_new()) == NULL){
|
|
clixon_err(OE_XML, errno, "cbuf_new");
|
|
goto done;
|
|
}
|
|
/* Traverse YANG, loop through all modules and generate CLI */
|
|
inext = 0;
|
|
while ((ymod = yn_iter(yspec, &inext)) != NULL){
|
|
/* Filter module name according to cli_autocli.yang setting
|
|
* Default is pass and ordering is significant
|
|
*/
|
|
if (autocli_module(h, yang_argument_get(ymod), &enable) < 0)
|
|
goto done;
|
|
if (!enable)
|
|
continue;
|
|
cbuf_reset(cb);
|
|
if (yang2cli_stmt(h, ymod, 0, cb) < 0)
|
|
goto done;
|
|
if (cbuf_len(cb) == 0)
|
|
continue;
|
|
/* Note Tie-break of same top-level symbol: prefix is NYI
|
|
* Needs to move cligen_parse_str() call here instead of later
|
|
*/
|
|
if ((prefix = yang_find_myprefix(ymod)) == NULL){
|
|
clixon_err(OE_YANG, 0, "Module %s lacks prefix", yang_argument_get(ymod)); /* shouldnt happen */
|
|
goto done;
|
|
}
|
|
if ((pt = pt_new()) == NULL){
|
|
clixon_err(OE_UNIX, errno, "pt_new");
|
|
goto done;
|
|
}
|
|
/* Parse the buffer using cligen parser. load cli syntax */
|
|
if (clispec_parse_str(cli_cligen(h), cbuf_get(cb), "yang2cli", NULL, pt, NULL) < 0){
|
|
fprintf(stderr, "%s\n", cbuf_get(cb));
|
|
goto done;
|
|
}
|
|
clixon_debug(CLIXON_DBG_CLI, "Generated auto-cli for module:%s",
|
|
yang_argument_get(ymod));
|
|
/* Add prefix: assume new are appended */
|
|
for (i=0; i<pt_len_get(pt); i++){
|
|
if ((co = pt_vec_i_get(pt, i)) != NULL){
|
|
clixon_debug(CLIXON_DBG_CLI, "command: %s",
|
|
co->co_command);
|
|
co_prefix_set(co, prefix);
|
|
}
|
|
}
|
|
/* Post-processing, iterate over the generated cligen parse-tree with corresponding yang
|
|
* Note cannot do it inline in yang2cli above since:
|
|
* 1. labels cannot be set on "empty"
|
|
* 2. a; <a>, fn() cannot be set properly
|
|
*/
|
|
config = 1;
|
|
if (yang2cli_post(h, NULL, pt, 0, ymod, NULL, &config) < 0){
|
|
goto done;
|
|
}
|
|
// pt_print(stderr,pt);
|
|
if (clicon_data_int_get(h, "autocli-print-debug") == 1)
|
|
clixon_log(h, LOG_NOTICE, "%s: Top-level cli-spec %s:\n%s",
|
|
__FUNCTION__, treename, cbuf_get(cb));
|
|
else
|
|
clixon_debug(CLIXON_DBG_CLI | CLIXON_DBG_DETAIL, "Top-level cli-spec %s:\n%s",
|
|
treename, cbuf_get(cb));
|
|
if (cligen_parsetree_merge(pt0, NULL, pt) < 0){
|
|
clixon_err(OE_YANG, errno, "cligen_parsetree_merge");
|
|
goto done;
|
|
}
|
|
pt_free(pt, 1);
|
|
pt = NULL;
|
|
} /* ymod */
|
|
/* Resolve the expand callback functions in the generated syntax.
|
|
* This "should" only be GENERATE_EXPAND_XMLDB
|
|
* handle=NULL for global namespace, this means expand callbacks must be in
|
|
* CLICON namespace, not in a cli frontend plugin.
|
|
*/
|
|
if (cligen_expandv_str2fn(pt0, (expandv_str2fn_t*)clixon_str2fn, NULL) < 0)
|
|
goto done;
|
|
/* Append cligen tree and name it */
|
|
if ((ph = cligen_ph_add(cli_cligen(h), treename)) == NULL){
|
|
clixon_err(OE_UNIX, 0, "cligen_ph_add");
|
|
goto done;
|
|
}
|
|
if (cligen_ph_parsetree_set(ph, pt0) < 0){
|
|
clixon_err(OE_UNIX, 0, "cligen_ph_parsetree_set");
|
|
goto done;
|
|
}
|
|
pt0 = NULL;
|
|
#if 0
|
|
if (clicon_data_int_get(h, "autocli-print-debug") == 1){
|
|
clixon_log(h, LOG_NOTICE, "%s: Top-level cli-spec %s", __FUNCTION__, treename);
|
|
pt_print1(stderr, pt0, 0);
|
|
}
|
|
#endif
|
|
retval = 0;
|
|
done:
|
|
if (pt)
|
|
pt_free(pt, 1);
|
|
if (pt0)
|
|
pt_free(pt0, 1);
|
|
if (cb)
|
|
cbuf_free(cb);
|
|
return retval;
|
|
}
|
|
|
|
/*! Init yang2cli
|
|
*
|
|
* Initialize CLIgen generation from YANG models.
|
|
* Nothing now
|
|
*
|
|
* @param[in] h Clixon handle
|
|
*/
|
|
int
|
|
yang2cli_init(clixon_handle h)
|
|
{
|
|
return 0;
|
|
}
|