Added cli multiple callback and expand support.
This commit is contained in:
parent
41680474c7
commit
2db346abc8
11 changed files with 301 additions and 87 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -85,13 +85,121 @@ 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,
|
||||
char *name,
|
||||
cvec *cvv,
|
||||
|
|
@ -197,7 +305,6 @@ expand_dbvar(void *h,
|
|||
if (xkpath)
|
||||
free(xkpath);
|
||||
return retval;
|
||||
|
||||
}
|
||||
|
||||
/*! List files in a directory
|
||||
|
|
|
|||
|
|
@ -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_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_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 *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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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_ */
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue