574 lines
16 KiB
C
574 lines
16 KiB
C
/*
|
|
*
|
|
***** BEGIN LICENSE BLOCK *****
|
|
|
|
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 *****
|
|
*
|
|
* C-code corresponding to clixon-autocli.yang
|
|
*/
|
|
#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 <fnmatch.h>
|
|
#include <sys/param.h>
|
|
|
|
/* cligen */
|
|
#include <cligen/cligen.h>
|
|
|
|
/* libclixon */
|
|
#include <clixon/clixon.h>
|
|
|
|
#include "clixon_cli_api.h"
|
|
#include "cli_autocli.h"
|
|
|
|
/* Mapping from YANG autocli-op to C enum */
|
|
static const map_str2int autocli_op_map[] = {
|
|
{"enable", AUTOCLI_OP_ENABLE},
|
|
{"disable", AUTOCLI_OP_DISABLE},
|
|
{"compress", AUTOCLI_OP_COMPRESS},
|
|
{NULL, -1}
|
|
};
|
|
|
|
/* Mapping from YANG list-keyword-type to C enum */
|
|
static const map_str2int list_kw_map[] = {
|
|
{"kw-none", AUTOCLI_LISTKW_NONE},
|
|
{"kw-nokey", AUTOCLI_LISTKW_NOKEY},
|
|
{"kw-all", AUTOCLI_LISTKW_ALL},
|
|
{NULL, -1}
|
|
};
|
|
|
|
|
|
static int
|
|
autocli_str2op(char *str)
|
|
{
|
|
return clicon_str2int(autocli_op_map, str);
|
|
}
|
|
|
|
#ifdef NOTUSED
|
|
static const char *
|
|
autocli_op2str(int op)
|
|
{
|
|
return clicon_int2str(autocli_op_map, op);
|
|
}
|
|
#endif
|
|
|
|
static int
|
|
autocli_listkw_str2int(char *str)
|
|
{
|
|
return clicon_str2int(list_kw_map, str);
|
|
}
|
|
|
|
#ifdef NOTUSED
|
|
static const char *
|
|
autocli_listkw_int2str(int listkw)
|
|
{
|
|
return clicon_int2str(list_kw_map, listkw);
|
|
}
|
|
#endif
|
|
|
|
/*! Filter module name according to cli_autocli.yang setting
|
|
*
|
|
* Traverse and find module/include/exclude rules
|
|
* @param[in] h Clixon handle
|
|
* @param[in] modname Name of YANG module, or NULL for ANY module (eg default)
|
|
* @param[out] enablep Include this module in autocli
|
|
* @retval 0 OK, and enablep set
|
|
* @retval -1 Error
|
|
* Special rule: module-default=false and no operation=enable rules is disable
|
|
*/
|
|
int
|
|
autocli_module(clixon_handle h,
|
|
char *modname,
|
|
int *enablep)
|
|
{
|
|
int retval = -1;
|
|
cxobj *xrule;
|
|
cxobj *xmod;
|
|
char *str;
|
|
char *element;
|
|
int enable = 0;
|
|
int op;
|
|
cxobj *xautocli;
|
|
char *body;
|
|
|
|
if (enablep == NULL){
|
|
clixon_err(OE_YANG, EINVAL, "Argument is NULL");
|
|
goto done;
|
|
}
|
|
enable = 0;
|
|
if ((xautocli = clicon_conf_autocli(h)) == NULL)
|
|
goto ok;
|
|
/* Default rule */
|
|
if ((str = xml_find_body(xautocli, "module-default")) == NULL){
|
|
clixon_err(OE_XML, EINVAL, "No module-default rule");
|
|
goto done;
|
|
}
|
|
enable = strcmp(str, "true") == 0;
|
|
if (enable && modname == NULL)
|
|
goto ok;
|
|
xrule = NULL;
|
|
while ((xrule = xml_child_each(xautocli, xrule, CX_ELMNT)) != NULL) {
|
|
if (strcmp(xml_name(xrule), "rule") != 0)
|
|
continue;
|
|
if ((str = xml_find_body(xrule, "operation")) == NULL)
|
|
continue;
|
|
/* Skip other than enable/disable */
|
|
op = autocli_str2op(str);
|
|
if (!enable && op == AUTOCLI_OP_ENABLE) {
|
|
/* Enable rules logic is:
|
|
* - If match, break, done
|
|
*/
|
|
xmod = NULL;
|
|
while ((xmod = xml_child_each(xrule, xmod, CX_ELMNT)) != NULL) {
|
|
if ((element = xml_name(xmod)) == NULL)
|
|
continue;
|
|
if (strcmp(element, "module-name") == 0){
|
|
if (modname == NULL)
|
|
break; /* match */
|
|
if ((body = xml_body(xmod)) == NULL)
|
|
continue; /* invalid rule? */
|
|
if (fnmatch(body, modname, 0) == 0)
|
|
break; /* match */
|
|
}
|
|
}
|
|
if (xmod != NULL){ /* break: found match */
|
|
enable = 1;
|
|
break;
|
|
}
|
|
}
|
|
else if (enable && op == AUTOCLI_OP_DISABLE) {
|
|
xmod = NULL;
|
|
while ((xmod = xml_child_each(xrule, xmod, CX_ELMNT)) != NULL) {
|
|
if ((element = xml_name(xmod)) == NULL)
|
|
continue;
|
|
if (strcmp(element, "module-name") == 0){
|
|
if ((body = xml_body(xmod)) == NULL)
|
|
continue; /* invalid rule? */
|
|
if (fnmatch(body, modname, 0) == 0)
|
|
break; /* match */
|
|
}
|
|
}
|
|
if (xmod != NULL){ /* break: found match */
|
|
enable = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
ok:
|
|
*enablep = enable;
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Specialization of autocli_compress for extension element
|
|
*/
|
|
static int
|
|
autocli_compress_extension(yang_stmt *ys,
|
|
yang_stmt *ymod,
|
|
char *body,
|
|
int *match)
|
|
{
|
|
int retval = -1;
|
|
char *prefix = NULL;
|
|
char *id = NULL;
|
|
char *ns = NULL;
|
|
int exist = 0;
|
|
int ret;
|
|
|
|
if (nodeid_split(body, &prefix, &id) < 0)
|
|
goto done;
|
|
if (prefix != NULL){
|
|
if ((ret = yang_find_namespace_by_prefix(ys, prefix, &ns)) < 0)
|
|
goto done;
|
|
if (ret == 1){
|
|
/* First try local node, then try module */
|
|
if (yang_extension_value(ys, id, ns, &exist, NULL) < 0)
|
|
goto done;
|
|
if (exist == 0)
|
|
if (yang_extension_value(ymod, id, ns, &exist, NULL) < 0)
|
|
goto done;
|
|
if (exist == 0)
|
|
*match = 0;
|
|
}
|
|
}
|
|
retval = 0;
|
|
done:
|
|
if (prefix)
|
|
free(prefix);
|
|
if (id)
|
|
free(id);
|
|
return retval;
|
|
}
|
|
|
|
/*! Check if yang symbol shpuld be compressed, ie skipped for its child(ren)
|
|
*
|
|
* For each Traverse "compress" rule, check:
|
|
* 1. Yang keyword match
|
|
* 2. Schema-nodeid match (yang argument)
|
|
* 3. module-name match
|
|
* 4. Single list child?
|
|
* The rules are OR:d, which means:
|
|
* - At least one compress rules that match
|
|
*
|
|
* @param[in] h Clixon handle
|
|
* @param[out] compress
|
|
* @retval 0 OK, and compress set
|
|
* @retval -1 Error
|
|
* Canonical examples:
|
|
The config and state containers are "compressed" out of the schema.
|
|
+ op=COMPRESS
|
|
- node-id is config/state
|
|
+ yang keyword is CONTAINER
|
|
+ module-name=openconfig*
|
|
|
|
The surrounding container entities are removed from list nodes.
|
|
+ op=COMPRESS
|
|
+ yang keyword is CONTAINER
|
|
- Only one child + Child keyword is LIST
|
|
*/
|
|
int
|
|
autocli_compress(clixon_handle h,
|
|
yang_stmt *ys,
|
|
int *compress)
|
|
{
|
|
int retval = -1;
|
|
cxobj *xautocli = NULL;
|
|
cxobj *xrule;
|
|
cxobj *xmod;
|
|
yang_stmt *ymod;
|
|
char *modname;
|
|
char *str;
|
|
char *element;
|
|
char *nodeid;
|
|
enum rfc_6020 keyw;
|
|
char *keywstr;
|
|
int match = 0;
|
|
char *body;
|
|
|
|
if (compress == NULL){
|
|
clixon_err(OE_YANG, EINVAL, "Argument is NULL");
|
|
goto done;
|
|
}
|
|
if ((xautocli = clicon_conf_autocli(h)) == NULL){
|
|
clixon_err(OE_YANG, 0, "No clixon-autocli");
|
|
goto done;
|
|
}
|
|
ymod = ys_module(ys);
|
|
modname = yang_argument_get(ymod);
|
|
keyw = yang_keyword_get(ys);
|
|
keywstr = yang_key2str(keyw);
|
|
nodeid = yang_argument_get(ys);
|
|
xrule = NULL;
|
|
while ((xrule = xml_child_each(xautocli, xrule, CX_ELMNT)) != NULL) {
|
|
if (strcmp(xml_name(xrule), "rule") != 0)
|
|
continue;
|
|
if ((str = xml_find_body(xrule, "operation")) == NULL)
|
|
continue;
|
|
/* Peek in element of rule to skip non-compress operations */
|
|
if (autocli_str2op(str) != AUTOCLI_OP_COMPRESS)
|
|
continue;
|
|
/* At this point this rule is a compress rule
|
|
* compress rule logic is "OR", the logic is:
|
|
* - If match, break, done
|
|
* - If not match, continue to next rule
|
|
*/
|
|
match = 1;
|
|
xmod = NULL;
|
|
while ((xmod = xml_child_each(xrule, xmod, CX_ELMNT)) != NULL) {
|
|
if ((element = xml_name(xmod)) == NULL)
|
|
continue;
|
|
if (strcmp(element, "name") == 0 ||
|
|
strcmp(element, "operation") == 0)
|
|
continue;
|
|
if ((body = xml_body(xmod)) == NULL)
|
|
continue;
|
|
if (strcmp(element, "yang-keyword") == 0){
|
|
if (strcmp(body, keywstr) != 0){
|
|
match = 0;
|
|
break;
|
|
}
|
|
}
|
|
else if (strcmp(element, "schema-nodeid") == 0){
|
|
if (strcmp(body, nodeid) != 0){
|
|
match = 0;
|
|
break;
|
|
}
|
|
}
|
|
else if (strcmp(element, "module-name") == 0){
|
|
if (fnmatch(body, modname, 0) != 0){
|
|
match = 0;
|
|
break;
|
|
}
|
|
}
|
|
else if (strcmp(element, "extension") == 0){
|
|
if (autocli_compress_extension(ys, ymod, body, &match) < 0)
|
|
goto done;
|
|
if (match == 0)
|
|
break;
|
|
}
|
|
else if (strcmp(element, "yang-keyword-child") == 0){
|
|
enum rfc_6020 subkeyw;
|
|
subkeyw = yang_str2key(body);
|
|
if (yang_single_child_type(ys, subkeyw) == 0){
|
|
match = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (match) /* At least one compress rule matches */
|
|
break;
|
|
}
|
|
*compress = match;
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Return default autocli completion setting
|
|
*
|
|
* Currently only returns list-keyword-default, could be extended to rules
|
|
* @param[in] h Clixon handle
|
|
* @param[out] completion Completion enabled
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*/
|
|
int
|
|
autocli_completion(clixon_handle h,
|
|
int *completion)
|
|
{
|
|
int retval = -1;
|
|
char *str;
|
|
uint8_t val;
|
|
char *reason = NULL;
|
|
int ret;
|
|
cxobj *xautocli;
|
|
|
|
if (completion == NULL){
|
|
clixon_err(OE_YANG, EINVAL, "Argument is NULL");
|
|
goto done;
|
|
}
|
|
if ((xautocli = clicon_conf_autocli(h)) == NULL){
|
|
clixon_err(OE_YANG, 0, "No clixon-autocli");
|
|
goto done;
|
|
}
|
|
if ((str = xml_find_body(xautocli, "completion-default")) == NULL){
|
|
clixon_err(OE_XML, EINVAL, "No completion-default rule");
|
|
goto done;
|
|
}
|
|
if ((ret = parse_bool(str, &val, &reason)) < 0){
|
|
clixon_err(OE_CFG, errno, "parse_bool");
|
|
goto done;
|
|
}
|
|
*completion = val;
|
|
retval = 0;
|
|
done:
|
|
if (reason)
|
|
free(reason);
|
|
return retval;
|
|
}
|
|
|
|
/*! Return autocli grouping treeref option
|
|
*
|
|
* When false replaces uses with grouping, when true use tree reference
|
|
* @param[in] h Clixon handle
|
|
* @param[out] treeref grouping using treerefs enabled
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*/
|
|
int
|
|
autocli_grouping_treeref(clixon_handle h,
|
|
int *treeref)
|
|
{
|
|
int retval = -1;
|
|
char *str;
|
|
uint8_t val;
|
|
char *reason = NULL;
|
|
int ret;
|
|
cxobj *xautocli;
|
|
|
|
if (treeref == NULL){
|
|
clixon_err(OE_YANG, EINVAL, "Argument is NULL");
|
|
goto done;
|
|
}
|
|
if ((xautocli = clicon_conf_autocli(h)) == NULL){
|
|
clixon_err(OE_YANG, 0, "No clixon-autocli");
|
|
goto done;
|
|
}
|
|
if ((str = xml_find_body(xautocli, "grouping-treeref")) == NULL){
|
|
clixon_err(OE_XML, EINVAL, "No grouping-treeref rule");
|
|
goto done;
|
|
}
|
|
if ((ret = parse_bool(str, &val, &reason)) < 0){
|
|
clixon_err(OE_CFG, errno, "parse_bool");
|
|
goto done;
|
|
}
|
|
*treeref = val;
|
|
retval = 0;
|
|
done:
|
|
if (reason)
|
|
free(reason);
|
|
return retval;
|
|
}
|
|
|
|
/*! Return default autocli list keyword setting
|
|
*
|
|
* Currently only returns list-keyword-default, could be extended to rules
|
|
* @param[in] h Clixon handle
|
|
* @param[out] listkw List keyword setting
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*/
|
|
int
|
|
autocli_list_keyword(clixon_handle h,
|
|
autocli_listkw_t *listkw)
|
|
{
|
|
int retval = -1;
|
|
char *str;
|
|
cxobj *xautocli = NULL;
|
|
|
|
if (listkw == NULL){
|
|
clixon_err(OE_YANG, EINVAL, "Argument is NULL");
|
|
goto done;
|
|
}
|
|
if ((xautocli = clicon_conf_autocli(h)) == NULL){
|
|
clixon_err(OE_YANG, 0, "No clixon-autocli");
|
|
goto done;
|
|
}
|
|
if ((str = xml_find_body(xautocli, "list-keyword-default")) == NULL){
|
|
clixon_err(OE_XML, EINVAL, "No list-keyword-default rule");
|
|
goto done;
|
|
}
|
|
*listkw = autocli_listkw_str2int(str);
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Return default autocli treeref state setting, ie generate CLI from YANG non-config
|
|
*
|
|
* @param[in] h Clixon handle
|
|
* @param[out] treeref_state If true, generate CLI from state
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*/
|
|
int
|
|
autocli_treeref_state(clixon_handle h,
|
|
int *treeref_state)
|
|
{
|
|
int retval = -1;
|
|
char *str;
|
|
uint8_t val;
|
|
char *reason = NULL;
|
|
int ret;
|
|
cxobj *xautocli;
|
|
|
|
if (treeref_state == NULL){
|
|
clixon_err(OE_YANG, EINVAL, "Argument is NULL");
|
|
goto done;
|
|
}
|
|
if ((xautocli = clicon_conf_autocli(h)) == NULL){
|
|
clixon_err(OE_YANG, 0, "No clixon-autocli");
|
|
goto done;
|
|
}
|
|
if ((str = xml_find_body(xautocli, "treeref-state-default")) == NULL){
|
|
clixon_err(OE_XML, EINVAL, "No treeref-state-default rule");
|
|
goto done;
|
|
}
|
|
if ((ret = parse_bool(str, &val, &reason)) < 0){
|
|
clixon_err(OE_CFG, errno, "parse_bool");
|
|
goto done;
|
|
}
|
|
*treeref_state = val;
|
|
retval = 0;
|
|
done:
|
|
if (reason)
|
|
free(reason);
|
|
return retval;
|
|
}
|
|
|
|
/*! Return default autocli edit-mode setting
|
|
*
|
|
* @param[in] h Clixon handle
|
|
* @param[in] keyw YANG keyword
|
|
* @param[out] flag If 0 keyw is not a part of default edit-mode, if 1 it is.
|
|
* @retval 0 OK, see result in keyw
|
|
* @retval -1 Error
|
|
* @note keyw is a sub/superset of RFC 6020, see clixon-autocli.yang on which are defined
|
|
*/
|
|
int
|
|
autocli_edit_mode(clixon_handle h,
|
|
char *keyw,
|
|
int *flag)
|
|
{
|
|
int retval = -1;
|
|
char *str;
|
|
cxobj *xautocli;
|
|
char **vec = NULL;
|
|
int nvec;
|
|
char *v;
|
|
int i;
|
|
|
|
if (flag == NULL){
|
|
clixon_err(OE_YANG, EINVAL, "Argument is NULL");
|
|
goto done;
|
|
}
|
|
*flag = 0;
|
|
if ((xautocli = clicon_conf_autocli(h)) == NULL){
|
|
clixon_err(OE_YANG, 0, "No clixon-autocli");
|
|
goto done;
|
|
}
|
|
if ((str = xml_find_body(xautocli, "edit-mode-default")) == NULL){
|
|
clixon_err(OE_XML, EINVAL, "No edit-mode-default rule");
|
|
goto done;
|
|
}
|
|
if ((vec = clicon_strsep(str, " ", &nvec)) == NULL)
|
|
goto done;
|
|
for (i=0; i<nvec; i++){
|
|
v = vec[i];
|
|
if (strcmp(v, keyw) == 0){
|
|
*flag = 1;
|
|
break;
|
|
}
|
|
}
|
|
retval = 0;
|
|
done:
|
|
if (vec)
|
|
free(vec);
|
|
return retval;
|
|
}
|