Added cli multiple callback and expand support.

This commit is contained in:
Olof hagsand 2017-01-15 20:22:51 +01:00
parent 41680474c7
commit 2db346abc8
11 changed files with 301 additions and 87 deletions

View file

@ -29,6 +29,12 @@
#
# ***** END LICENSE BLOCK *****
- Added cli multiple callback and expand support. Use options
CLICON_CLIGEN_CALLBACK_SINGLE_ARG and CLICON_CLIGEN_EXPAND_SINGLE_ARG
to control these.
The multiple support for expand callbacks is enabled but not for callbacks
since this causes problems for legacy applications.
- Added --with-cligen and --with-qdbm configure options
- Added union type check for non-cli (eg xml) input
- Empty yang type. Relaxed yang types for unions, eg two strings with different length.

View file

@ -91,6 +91,7 @@ config_terminate(clicon_handle h)
if (sockpath)
unlink(sockpath);
backend_handle_exit(h); /* Cannot use h after this */
event_exit();
clicon_log_register_callback(NULL, NULL);
clicon_debug(1, "%s done", __FUNCTION__);
if (debug)
@ -104,6 +105,7 @@ static void
config_sig_term(int arg)
{
static int i=0;
if (i++ == 0)
clicon_log(LOG_NOTICE, "%s: %s: pid: %u Signal %d",
__PROGRAM__, __FUNCTION__, getpid(), arg);

View file

@ -485,7 +485,7 @@ compare_dbs(clicon_handle h, cvec *cvv, cg_var *arg)
/*! Modify xml database from a callback using xml key format strings
* @param[in] h Clicon handle
* @param[in] cvv Vector of cli string and instantiated variables
* @param[in] arg An xml key format string, eg /aaa/%s
* @param[in] argv Vector. First element xml key format string, eg "/aaa/%s"
* @param[in] op Operation to perform on database
* Cvv will contain forst the complete cli string, and then a set of optional
* instantiated variables.
@ -493,10 +493,91 @@ compare_dbs(clicon_handle h, cvec *cvv, cg_var *arg)
* cvv[0] = "set interfaces interface eth0 type bgp"
* cvv[1] = "eth0"
* cvv[2] = "bgp"
* arg = "/interfaces/interface/%s/type"
* argv[0] = "/interfaces/interface/%s/type"
* op: OP_MERGE
* @see cli_callback_generate where arg is generated
*/
static int
cli_dbxmlv(clicon_handle h,
cvec *cvv,
cvec *argv,
enum operation_type op)
{
int retval = -1;
char *str = NULL;
char *xkfmt; /* xml key format */
char *xk = NULL; /* xml key */
cg_var *cval;
int len;
cg_var *arg;
if (cvec_len(argv) == 1){
clicon_err(OE_PLUGIN, 0, "Requires one element to be xml key format stringf");
goto done;
}
arg = cvec_i(argv, 0);
xkfmt = cv_string_get(arg);
if (xmlkeyfmt2key(xkfmt, cvv, &xk) < 0)
goto done;
len = cvec_len(cvv);
if (len > 1){
cval = cvec_i(cvv, len-1);
if ((str = cv2str_dup(cval)) == NULL){
clicon_err(OE_UNIX, errno, "cv2str_dup");
goto done;
}
}
if (clicon_rpc_change(h, "candidate", op, xk, str) < 0)
goto done;
if (clicon_autocommit(h)) {
if (clicon_rpc_commit(h, "candidate", "running", 0, 0) < 0)
goto done;
}
retval = 0;
done:
if (str)
free(str);
if (xk)
free(xk);
return retval;
}
int
cli_setv(clicon_handle h, cvec *cvv, cvec *argv)
{
int retval = 1;
if (cli_dbxmlv(h, cvv, argv, OP_REPLACE) < 0)
goto done;
retval = 0;
done:
return retval;
}
int
cli_mergev(clicon_handle h, cvec *cvv, cvec *argv)
{
int retval = -1;
if (cli_dbxmlv(h, cvv, argv, OP_MERGE) < 0)
goto done;
retval = 0;
done:
return retval;
}
int
cli_delv(clicon_handle h, cvec *cvv, cvec *argv)
{
int retval = -1;
if (cli_dbxmlv(h, cvv, argv, OP_REMOVE) < 0)
goto done;
retval = 0;
done:
return retval;
}
static int
cli_dbxml(clicon_handle h,
cvec *cvv,
@ -572,6 +653,7 @@ cli_del(clicon_handle h, cvec *cvv, cg_var *arg)
return retval;
}
/*! Load a configuration file to candidate database
* Utility function used by cligen spec file
* @param[in] h CLICON handle

View file

@ -56,7 +56,7 @@
/* cligen */
#include <cligen/cligen.h>
/* clicon */
/* Clicon */
#include <clixon/clixon.h>
#include "clixon_cli_api.h"
@ -65,9 +65,10 @@
/* This is the default callback function. But this is typically overwritten */
#define GENERATE_CALLBACK "cli_set"
#define GENERATE_CALLBACKV "cli_setv"
/* variable expand function */
#define GENERATE_EXPAND_XMLDB "expand_dbvar"
#define GENERATE_EXPAND_XMLDB "expandv_dbvar"
/*=====================================================================
* YANG generate CLI
@ -127,7 +128,7 @@ cli_expand_var_generate(clicon_handle h,
cv_type2str(cvtype));
if (options & YANG_OPTIONS_FRACTION_DIGITS)
cprintf(cb0, " fraction-digits:%u", fraction_digits);
cprintf(cb0, " %s(\"candidate %s\")>",
cprintf(cb0, " %s(\"candidate\",\"%s\")>",
GENERATE_EXPAND_XMLDB,
xkfmt);
retval = 0;
@ -153,7 +154,10 @@ cli_callback_generate(clicon_handle h,
if (yang2xmlkeyfmt(ys, 0, &xkfmt) < 0)
goto done;
if (clicon_option_int(h, "CLICON_CLIGEN_CALLBACK_SINGLE_ARG")==1)
cprintf(cb0, ",%s(\"%s\")", GENERATE_CALLBACK, xkfmt);
else
cprintf(cb0, ",%s(\"%s\")", GENERATE_CALLBACKV, xkfmt);
retval = 0;
done:
if (xkfmt)
@ -658,10 +662,14 @@ yang2cli(clicon_handle h,
"yang2cli", ptnew, globals) < 0)
goto done;
cvec_free(globals);
/* handle=NULL for global namespace, this means expand callbacks must be in
CLICON namespace, not in a cli frontend plugin. */
if (cligen_expand_str2fn(*ptnew, expand_str2fn, NULL) < 0)
/* 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(*ptnew, (expandv_str2fn_t*)clixon_str2fn, NULL) < 0)
goto done;
retval = 0;
done:
cbuf_free(cbuf);

View file

@ -216,65 +216,27 @@ syntax_unload(clicon_handle h)
return 0;
}
/*! Dynamic string to function mapper
/*! Dynamic linking loader string to function mapper
*
* The cli load function uses this function to map from strings to names.
* handle is the dlopen handle, so it only looks in the current plugin being
* loaded. It should also look in libraries?
* Maps strings from the CLI specification file to real funtions using dlopen
* mapping.
* First look for function name in local namespace if handle given (given plugin)
* Then check global namespace, i.e.m lib*.so
*
* Returns a function pointer to the callback. Beware that this pointer
* can theoretically be NULL depending on where the callback is loaded
* into memory. Caller must check the error string which is non-NULL is
* an error occured
*
* Compare with expand_str2fn - essentially identical.
*/
cg_fnstype_t *
load_str2fn(char *name, void *handle, char **error)
{
cg_fnstype_t *fn = NULL;
/* Reset error */
*error = NULL;
/* First check given plugin if any */
if (handle) {
dlerror(); /* Clear any existing error */
fn = dlsym(handle, name);
if ((*error = (char*)dlerror()) == NULL)
return fn; /* If no error we found the address of the callback */
}
/* Now check global namespace which includes any shared object loaded
* into the global namespace. I.e. all lib*.so as well as the
* master plugin if it exists
*/
dlerror(); /* Clear any existing error */
fn = dlsym(NULL, name);
if ((*error = (char*)dlerror()) == NULL)
return fn; /* If no error we found the address of the callback */
/* Return value not really relevant here as the error string is set to
* signal an error. However, just checking the function pointer for NULL
* should work in most cases, although it's not 100% correct.
*/
return NULL;
}
/*
* expand_str2fn
* maps strings from the CLI specification file to real funtions using dlopen
* mapping. One could do something more elaborate with namespaces and plugins:
* x::a, x->a, but this is not done yet.
* Compare with load_str2fn - essentially identical.
* @param[in] name Name of function
* @param[in] handle Handle to plugin .so module as returned by dlopen, see cli_plugin_load
* @param[in] handle Handle to plugin .so module as returned by dlopen
* @param[out] error Static error string, if set indicates error
* @retval fn Function pointer
* @retval NULL FUnction not found or symbol NULL (check error for proper handling)
* @see see cli_plugin_load where (optional) handle opened
* @note the returned function is not type-checked which may result in segv at runtime
*/
expand_cb *
expand_str2fn(char *name, void *handle, char **error)
void *
clixon_str2fn(char *name,
void *handle,
char **error)
{
expand_cb *fn = NULL;
void *fn = NULL;
/* Reset error */
*error = NULL;
@ -303,7 +265,6 @@ expand_str2fn(char *name, void *handle, char **error)
return NULL;
}
/*
* Load a dynamic plugin object and call it's init-function
* Note 'file' may be destructively modified
@ -410,16 +371,27 @@ cli_load_syntax(clicon_handle h, const char *filename, const char *clispec_dir)
}
}
/* Resolve callback names to function pointers.
* XXX: consider using cligen_callback_str2fnv instead */
if (cligen_callback_str2fn(pt, load_str2fn, handle) < 0){
/* Resolve callback names to function pointers. */
if (clicon_option_int(h, "CLICON_CLIGEN_CALLBACK_SINGLE_ARG")==1){
if (cligen_callback_str2fn(pt, (cg_str2fn_t*)clixon_str2fn, handle) < 0){
clicon_err(OE_PLUGIN, 0, "Mismatch between CLIgen file '%s' and CLI plugin file '%s'. Some possible errors:\n\t1. A function given in the CLIgen file does not exist in the plugin (ie link error)\n\t2. The CLIgen spec does not point to the correct plugin .so file (CLICON_PLUGIN=\"%s\" is wrong)",
filename, plgnam, plgnam);
goto done;
}
if (cligen_expand_str2fn(pt, expand_str2fn, handle) < 0)
}
else
if (cligen_callbackv_str2fn(pt, (cgv_str2fn_t*)clixon_str2fn, handle) < 0){
clicon_err(OE_PLUGIN, 0, "Mismatch between CLIgen file '%s' and CLI plugin file '%s'. Some possible errors:\n\t1. A function given in the CLIgen file does not exist in the plugin (ie link error)\n\t2. The CLIgen spec does not point to the correct plugin .so file (CLICON_PLUGIN=\"%s\" is wrong)",
filename, plgnam, plgnam);
goto done;
}
if (clicon_option_int(h, "CLICON_CLIGEN_EXPAND_SINGLE_ARG")==1){
if (cligen_expand_str2fn(pt, (expand_str2fn_t*)clixon_str2fn, handle) < 0)
goto done;
}
else
if (cligen_expandv_str2fn(pt, (expandv_str2fn_t*)clixon_str2fn, handle) < 0)
goto done;
/* Make sure we have a syntax mode specified */
if (mode == NULL || strlen(mode) < 1) { /* may be null if not given in file */

View file

@ -86,7 +86,7 @@ typedef struct {
} cli_syntax_t;
expand_cb *expand_str2fn(char *name, void *handle, char **error);
void *clixon_str2fn(char *name, void *handle, char **error);
int cli_plugin_start(clicon_handle, int argc, char **argv);

View file

@ -85,11 +85,119 @@ static int xml2csv(FILE *f, cxobj *x, cvec *cvv);
* @param[in] h clicon handle
* @param[in] name Name of this function (eg "expand_dbvar")
* @param[in] cvv The command so far. Eg: cvec [0]:"a 5 b"; [1]: x=5;
* @param[in] arg Argument given at the callback "<db> <xmlkeyfmt>"
* @param[in] argv Arguments given at the callback ("<db>" "<xmlkeyfmt>")
* @param[out] len len of return commands & helptxt
* @param[out] commands vector of function pointers to callback functions
* @param[out] helptxt vector of pointers to helptexts
* @see cli_expand_var_generate This is where arg is generated
* XXX: helptexts?
*/
int
expandv_dbvar(void *h,
char *name,
cvec *cvv,
cvec *argv,
cvec *commands,
cvec *helptexts)
{
int retval = -1;
char *xkfmt;
char *dbstr;
cxobj *xt = NULL;
char *xkpath = NULL;
cxobj **xvec = NULL;
size_t xlen = 0;
cxobj *x;
char *bodystr;
int i;
int j;
int k;
cg_var *cv;
if (argv == NULL || cvec_len(argv) != 2){
clicon_err(OE_PLUGIN, 0, "%s: requires arguments: <db> <xmlkeyfmt>",
__FUNCTION__);
goto done;
}
if ((cv = cvec_i(argv, 0)) == NULL){
clicon_err(OE_PLUGIN, 0, "%s: Error when accessing argument <db>");
goto done;
}
dbstr = cv_string_get(cv);
if (strcmp(dbstr, "running") != 0 &&
strcmp(dbstr, "candidate") != 0){
clicon_err(OE_PLUGIN, 0, "No such db name: %s", dbstr);
goto done;
}
if ((cv = cvec_i(argv, 1)) == NULL){
clicon_err(OE_PLUGIN, 0, "%s: Error when accessing argument <xkfmt>");
goto done;
}
xkfmt = cv_string_get(cv);
/* xkfmt = /interface/%s/address/%s
--> ^/interface/eth0/address/.*$
--> /interface/[name=eth0]/address
*/
if (xmlkeyfmt2xpath(xkfmt, cvv, &xkpath) < 0)
goto done;
if (xmldb_get(h, dbstr, xkpath, 1, &xt, &xvec, &xlen) < 0)
goto done;
/* One round to detect duplicates
* XXX The code below would benefit from some cleanup
*/
j = 0;
for (i = 0; i < xlen; i++) {
char *str;
x = xvec[i];
if (xml_type(x) == CX_BODY)
bodystr = xml_value(x);
else
bodystr = xml_body(x);
if (bodystr == NULL){
clicon_err(OE_CFG, 0, "No xml body");
goto done;
}
/* detect duplicates */
for (k=0; k<j; k++){
if (xml_type(xvec[k]) == CX_BODY)
str = xml_value(xvec[k]);
else
str = xml_body(xvec[k]);
if (strcmp(str, bodystr)==0)
break;
}
if (k==j) /* not duplicate */
xvec[j++] = x;
}
xlen = j;
for (i = 0; i < xlen; i++) {
x = xvec[i];
if (xml_type(x) == CX_BODY)
bodystr = xml_value(x);
else
bodystr = xml_body(x);
if (bodystr == NULL){
clicon_err(OE_CFG, 0, "No xml body");
goto done;
}
/* XXX RFC3986 decode */
cvec_add_string(commands, NULL, bodystr);
}
retval = 0;
done:
unchunk_group(__FUNCTION__);
if (xvec)
free(xvec);
if (xt)
xml_free(xt);
if (xkpath)
free(xkpath);
return retval;
}
/*! This is obsolete version of expandv_dbvar
* If CLICON_CLIGEN_EXPAND_SINGLE_ARG is set
*/
int
expand_dbvar(void *h,
@ -197,7 +305,6 @@ expand_dbvar(void *h,
if (xkpath)
free(xkpath);
return retval;
}
/*! List files in a directory

View file

@ -71,10 +71,15 @@ cligen_handle cli_cligen(clicon_handle h);
int init_candidate_db(clicon_handle h);
int exit_candidate_db(clicon_handle h);
#define cli_output cligen_output
int cli_setv(clicon_handle h, cvec *vars, cvec *argv);
int cli_mergev(clicon_handle h, cvec *vars, cvec *argv);
int cli_delv(clicon_handle h, cvec *vars, cvec *argv);
int cli_set(clicon_handle h, cvec *vars, cg_var *arg);
int cli_merge(clicon_handle h, cvec *vars, cg_var *arg);
int cli_del(clicon_handle h, cvec *vars, cg_var *argv);
int cli_debug_cli(clicon_handle h, cvec *vars, cg_var *argv);
int cli_del(clicon_handle h, cvec *vars, cg_var *arg);
int cli_debug_cli(clicon_handle h, cvec *vars, cg_var *arg);
int cli_debug_backend(clicon_handle h, cvec *vars, cg_var *argv);
int cli_record(clicon_handle h, cvec *vars, cg_var *argv);
int isrecording(void);
@ -97,8 +102,8 @@ int cli_notification_register(clicon_handle h, char *stream, enum format_enum fo
/* In cli_show.c */
int expand_dir(char *dir, int *nr, char ***commands, mode_t flags, int detail);
int expand_dbvar(void *h, char *name, cvec *cvv, cg_var *arg,
int *nr, char ***commands, char ***helptexts);
int expandv_dbvar(void *h, char *name, cvec *cvv, cvec *argv,
cvec *commands, cvec *helptexts);
int show_conf_as_xml(clicon_handle h, cvec *vars, cg_var *arg);
int show_conf_as_netconf(clicon_handle h, cvec *vars, cg_var *arg);

View file

@ -138,4 +138,15 @@ CLICON_XMLDB_DIR localstatedir/APPNAME
# Eg in nginx: fastcgi_pass unix:/www-data/clicon_restconf.sock;
CLICON_RESTCONF_PATH /www-data/fastcgi_restconf.sock
# Set if you want to use old obsolete cligen expand variable syntax
# Migration: Set to 0 and change all user-defined cli completion callbacks
# E.g. expand_dbvar("db fmt") ->expandv_dbvar("db","fmt") in all your cli spec files
CLICON_CLIGEN_EXPAND_SINGLE_ARG 0
# Set if you want to use old obsolete cligen callback variable syntax
# Migration: Set to 0 and change all user-defined cli callbacks in your cli spec files
# E.g cmd, callback("single arg"); -> cmd, callback("two" "args");
# But there are still many pre-defined in callbacks, eg in cli_common.c that are not made
# for this.
CLICON_CLIGEN_CALLBACK_SINGLE_ARG 1

View file

@ -56,4 +56,6 @@ int event_unreg_timeout(int (*fn)(int, void*), void *arg);
int event_loop(void);
int event_exit(void);
#endif /* _CLIXON_EVENT_H_ */

View file

@ -327,3 +327,22 @@ event_loop(void)
return retval;
}
int
event_exit(void)
{
struct event_data *e, *e_next;
e_next = ee;
while ((e = e_next) != NULL){
e_next = e->e_next;
free(e);
}
ee = NULL;
e_next = ee_timers;
while ((e = e_next) != NULL){
e_next = e->e_next;
free(e);
}
ee_timers = NULL;
return 0;
}