From 2db346abc826439ab3c42b2135705557de78c12a Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sun, 15 Jan 2017 20:22:51 +0100 Subject: [PATCH] Added cli multiple callback and expand support. --- CHANGELOG | 6 ++ apps/backend/backend_main.c | 2 + apps/cli/cli_common.c | 92 ++++++++++++++++++++++++++++-- apps/cli/cli_generate.c | 22 ++++--- apps/cli/cli_plugin.c | 104 ++++++++++++--------------------- apps/cli/cli_plugin.h | 2 +- apps/cli/cli_show.c | 111 +++++++++++++++++++++++++++++++++++- apps/cli/clixon_cli_api.h | 17 ++++-- clixon.conf.cpp.cpp | 11 ++++ lib/clixon/clixon_event.h | 2 + lib/src/clixon_event.c | 19 ++++++ 11 files changed, 301 insertions(+), 87 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 34d6ef13..1e036111 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -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. diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c index 65291be0..05dff9db 100644 --- a/apps/backend/backend_main.c +++ b/apps/backend/backend_main.c @@ -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); diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c index ff148c8e..f613d94a 100644 --- a/apps/cli/cli_common.c +++ b/apps/cli/cli_common.c @@ -485,18 +485,99 @@ 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. * Example: - * cvv[0] = "set interfaces interface eth0 type bgp" - * cvv[1] = "eth0" - * cvv[2] = "bgp" - * arg = "/interfaces/interface/%s/type" + * cvv[0] = "set interfaces interface eth0 type bgp" + * cvv[1] = "eth0" + * cvv[2] = "bgp" + * 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 diff --git a/apps/cli/cli_generate.c b/apps/cli/cli_generate.c index 177d58aa..fb57a6e5 100644 --- a/apps/cli/cli_generate.c +++ b/apps/cli/cli_generate.c @@ -56,7 +56,7 @@ /* cligen */ #include -/* clicon */ +/* Clicon */ #include #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; - cprintf(cb0, ",%s(\"%s\")", GENERATE_CALLBACK, xkfmt); + 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); diff --git a/apps/cli/cli_plugin.c b/apps/cli/cli_plugin.c index 6db0ed9b..684d55ba 100644 --- a/apps/cli/cli_plugin.c +++ b/apps/cli/cli_plugin.c @@ -216,24 +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? - * - * 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. + * 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 + * + * @param[in] name Name of function + * @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 */ -cg_fnstype_t * -load_str2fn(char *name, void *handle, char **error) +void * +clixon_str2fn(char *name, + void *handle, + char **error) { - cg_fnstype_t *fn = NULL; + void *fn = NULL; /* Reset error */ *error = NULL; @@ -262,48 +265,6 @@ load_str2fn(char *name, void *handle, char **error) 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 - */ -expand_cb * -expand_str2fn(char *name, void *handle, char **error) -{ - expand_cb *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; -} - - /* * 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){ - 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; + /* 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; + } + } + 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; } - if (cligen_expand_str2fn(pt, expand_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 */ diff --git a/apps/cli/cli_plugin.h b/apps/cli/cli_plugin.h index 9f58ac41..8823a052 100644 --- a/apps/cli/cli_plugin.h +++ b/apps/cli/cli_plugin.h @@ -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); diff --git a/apps/cli/cli_show.c b/apps/cli/cli_show.c index 428955a4..3df2a35c 100644 --- a/apps/cli/cli_show.c +++ b/apps/cli/cli_show.c @@ -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 " " + * @param[in] argv Arguments given at the callback ("" "") * @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: ", + __FUNCTION__); + goto done; + } + if ((cv = cvec_i(argv, 0)) == NULL){ + clicon_err(OE_PLUGIN, 0, "%s: Error when accessing argument "); + 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 "); + 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; kexpandv_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 diff --git a/lib/clixon/clixon_event.h b/lib/clixon/clixon_event.h index b40d182f..a7eebbca 100644 --- a/lib/clixon/clixon_event.h +++ b/lib/clixon/clixon_event.h @@ -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_ */ diff --git a/lib/src/clixon_event.c b/lib/src/clixon_event.c index cc88af99..53d52f34 100644 --- a/lib/src/clixon_event.c +++ b/lib/src/clixon_event.c @@ -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; +}