From 7f0b9909b357ff676bd7824eccaefb316bd26013 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Tue, 31 Jan 2017 22:36:14 +0100 Subject: [PATCH] Library functions in clixon_cli_api.h (e.g cli_commit) is rewritten in new for (eg cli_commitv). See clixon_cli_api.h for new names. Use restconf format for internal xmldb keys. Eg /a/b=3,4 Changed example to use multiple cli callbacks --- CHANGELOG | 6 +- apps/backend/backend_client.c | 4 +- apps/backend/backend_commit.c | 4 +- apps/cli/cli_common.c | 897 ++++++++++++++++++---------------- apps/cli/cli_common.h | 18 + apps/cli/cli_generate.c | 10 +- apps/cli/cli_plugin.c | 4 - apps/cli/cli_show.c | 847 ++++++++++++++++++++++---------- apps/cli/clixon_cli_api.h | 37 +- apps/netconf/netconf_rpc.c | 4 +- apps/restconf/restconf_main.c | 2 +- apps/xmldb/xmldb_main.c | 7 +- clixon.conf.cpp.cpp | 3 +- configure | 7 - configure.ac | 7 - doc/clixon_example_sdk.odg | Bin 0 -> 30267 bytes example/README | 5 + example/routing.conf.local | 5 + example/routing_cli.c | 5 +- example/routing_cli.cli | 52 +- include/clixon_config.h.in | 3 + lib/clixon/clixon_xml.h | 1 + lib/clixon/clixon_xml_db.h | 2 +- lib/clixon/clixon_yang.h | 1 - lib/src/clixon_xml.c | 27 +- lib/src/clixon_xml_db.c | 467 ++++++++---------- lib/src/clixon_xml_db_rpc.c | 5 +- lib/src/clixon_xml_db_rpc.h | 2 +- lib/src/clixon_xsl.c | 2 +- lib/src/clixon_yang.c | 64 +-- 30 files changed, 1444 insertions(+), 1054 deletions(-) create mode 100644 doc/clixon_example_sdk.odg diff --git a/CHANGELOG b/CHANGELOG index df6b8045..0db1efeb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -29,13 +29,17 @@ # # ***** END LICENSE BLOCK ***** +- Use restconf format for internal xmldb keys. Eg /a/b=3,4 - List keys with special characters are RFC 3986 encoded. +- Changed example to use multiple cli callbacks - 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. - + If you change to multiple argument callbacks change all cli callback functions. + Library functions in clixon_cli_api.h (e.g cli_commit) is rewritten in new + for (eg cli_commitv). See clixon_cli_api.h for new names. - 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_client.c b/apps/backend/backend_client.c index fe26baa3..3f5c9d48 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -373,7 +373,7 @@ config_snapshot(clicon_handle h, clicon_err(OE_CFG, errno, "Creating file %s", filename0); return -1; } - if (xmldb_get(h, db, "/", 0, &xn, NULL, NULL) < 0) + if (xmldb_get(h, db, "/", &xn, NULL, NULL) < 0) goto done; if (xml_print(f, xn) < 0) goto done; @@ -439,7 +439,7 @@ from_client_save(clicon_handle h, clicon_err(OE_CFG, errno, "Creating file %s", filename); return -1; } - if (xmldb_get(h, db, "/", 0, &xn, NULL, NULL) < 0) + if (xmldb_get(h, db, "/", &xn, NULL, NULL) < 0) goto done; if (xml_print(f, xn) < 0) goto done; diff --git a/apps/backend/backend_commit.c b/apps/backend/backend_commit.c index ad2369cf..b8d826ab 100644 --- a/apps/backend/backend_commit.c +++ b/apps/backend/backend_commit.c @@ -143,9 +143,9 @@ validate_common(clicon_handle h, goto done; } /* 2. Parse xml trees */ - if (xmldb_get(h, "running", "/", 0, &td->td_src, NULL, NULL) < 0) + if (xmldb_get(h, "running", "/", &td->td_src, NULL, NULL) < 0) goto done; - if (xmldb_get(h, candidate, "/", 0, &td->td_target, NULL, NULL) < 0) + if (xmldb_get(h, candidate, "/", &td->td_target, NULL, NULL) < 0) goto done; /* 3. Compute differences */ diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c index f613d94a..90e06c7c 100644 --- a/apps/cli/cli_common.c +++ b/apps/cli/cli_common.c @@ -95,9 +95,8 @@ init_candidate_db(clicon_handle h) return retval; } -/* - * exit_candidate_db - * private canddidates should be removed +/*! Exit candidate db + * (private canddidates should be removed?) */ int exit_candidate_db(clicon_handle h) @@ -105,78 +104,66 @@ exit_candidate_db(clicon_handle h) return 0; } -#if 1 /* OBSOLETE */ -/*! Set debug level - * The level is either what is specified in arg as int argument. - * _or_ if a 'level' variable is present in vars use that value instead. - * OBSOLETE +/*! Register log notification stream + * @param[in] h Clicon handle + * @param[in] stream Event stream. CLICON is predefined, others are application-defined + * @param[in] filter Filter. For xml notification ie xpath: .[name=kalle] + * @param[in] status 0 for stop, 1 to start + * @param[in] fn Callback function called when notification occurs + * @param[in] arg Argumnent to function */ int -cli_debug(clicon_handle h, - cvec *vars, - cg_var *arg) +cli_notification_register(clicon_handle h, + char *stream, + enum format_enum format, + char *filter, + int status, + int (*fn)(int, void*), + void *arg) { - cg_var *cv; - int level; + int retval = -1; + char *logname; + void *p; + int s; + clicon_hash_t *cdat = clicon_data(h); + size_t len; + int s_exist = -1; - if ((cv = cvec_find_var(vars, "level")) == NULL) - cv = arg; - level = cv_int32_get(cv); - /* cli */ - clicon_debug_init(level, NULL); /* 0: dont debug, 1:debug */ - /* config daemon */ - if (clicon_rpc_debug(h, level) < 0) + if ((logname = chunk_sprintf(__FUNCTION__, "log_socket_%s", stream)) == NULL){ + clicon_err(OE_PLUGIN, errno, "%s: chunk_sprintf", __FUNCTION__); goto done; + } + if ((p = hash_value(cdat, logname, &len)) != NULL) + s_exist = *(int*)p; + + if (status){ /* start */ + if (s_exist!=-1){ + clicon_err(OE_PLUGIN, 0, "%s: result log socket already exists", __FUNCTION__); + goto done; + } + if (clicon_rpc_subscription(h, status, stream, format, filter, &s) < 0) + goto done; + if (cligen_regfd(s, fn, arg) < 0) + goto done; + if (hash_add(cdat, logname, &s, sizeof(s)) == NULL) + goto done; + } + else{ /* stop */ + if (s_exist != -1){ + cligen_unregfd(s_exist); + } + hash_del(cdat, logname); + if (clicon_rpc_subscription(h, status, stream, format, filter, NULL) < 0) + goto done; + + } + retval = 0; done: - return 0; -} -#endif - -/*! Set debug level on CLI client (not backend daemon) - * @param[in] h Clicon handle - * @param[in] vars If variable "level" exists, its integer value is used - * @param[in] arg Else use the integer value of argument - * @note The level is either what is specified in arg as int argument. - * _or_ if a 'level' variable is present in vars use that value instead. - */ -int -cli_debug_cli(clicon_handle h, - cvec *vars, - cg_var *arg) -{ - cg_var *cv; - int level; - - if ((cv = cvec_find_var(vars, "level")) == NULL) - cv = arg; - level = cv_int32_get(cv); - /* cli */ - clicon_debug_init(level, NULL); /* 0: dont debug, 1:debug */ - return 0; -} - -/*! Set debug level on backend daemon (not CLI) - * @param[in] h Clicon handle - * @param[in] vars If variable "level" exists, its integer value is used - * @param[in] arg Else use the integer value of argument - * @note The level is either what is specified in arg as int argument. - * _or_ if a 'level' variable is present in vars use that value instead. - */ -int -cli_debug_backend(clicon_handle h, - cvec *vars, - cg_var *arg) -{ - cg_var *cv; - int level; - - if ((cv = cvec_find_var(vars, "level")) == NULL) - cv = arg; - level = cv_int32_get(cv); - /* config daemon */ - return clicon_rpc_debug(h, level); + unchunk_group(__FUNCTION__); + return retval; } +/* Signal functions, not exported to API */ void cli_signal_block(clicon_handle h) { @@ -221,79 +208,191 @@ cli_signal_flush(clicon_handle h) cli_signal_block (h); } - -/* Code for recording which CLI commands have been issued */ - -static FILE *_recordf = NULL; -static int _isrecording = 0; - -int -isrecording(void) -{ - return _isrecording; -} - -int -cli_record(clicon_handle h, cvec *vars, cg_var *arg) -{ - _isrecording = cv_int32_get(arg); - return 0; -} - +/*! 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] 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" + * argv[0] = "/interfaces/interface/%s/type" + * op: OP_MERGE + * @see cli_callback_generate where arg is generated + */ static int -record_open(void) +cli_dbxmlv(clicon_handle h, + cvec *cvv, + cvec *argv, + enum operation_type op) { - char file[] = "/tmp/cli.record.XXXXXX"; - int fd; + 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 ((fd = mkstemp(file)) < 0 || (_recordf = fdopen(fd, "w")) < 0) { - clicon_err(OE_UNIX, errno, "mkstemp/fdopen"); - return -1; + if (cvec_len(argv) != 1){ + clicon_err(OE_PLUGIN, 0, "%s: Requires one element to be xml key format string", __FUNCTION__); + goto done; } - return 0; + 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; } -/* - * record commands in file - */ -int -record_command(char *str) +int +cli_setv(clicon_handle h, cvec *cvv, cvec *argv) { - if (_recordf==NULL) - if (record_open() < 0) - return -1; - fprintf(_recordf, "%s\n", str); - fflush(_recordf); - return 0; + 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; -/* - * Callback to set syntax mode + 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; +} + +/*! Set debug level on CLI client (not backend daemon) + * @param[in] h Clicon handle + * @param[in] vars If variable "level" exists, its integer value is used + * @param[in] arg Else use the integer value of argument + * @note The level is either what is specified in arg as int argument. + * _or_ if a 'level' variable is present in vars use that value instead. */ int -cli_set_mode(clicon_handle h, cvec *vars, cg_var *arg) +cli_debug_cliv(clicon_handle h, + cvec *vars, + cvec *argv) +{ + int retval = -1; + cg_var *cv; + int level; + + if ((cv = cvec_find_var(vars, "level")) == NULL){ + if (cvec_len(argv) != 1){ + clicon_err(OE_PLUGIN, 0, "%s: Requires either label var or single arg: 0|1", __FUNCTION__); + goto done; + } + cv = cvec_i(argv, 0); + } + level = cv_int32_get(cv); + /* cli */ + clicon_debug_init(level, NULL); /* 0: dont debug, 1:debug */ + retval = 0; + done: + return retval; +} + +/*! Set debug level on backend daemon (not CLI) + * @param[in] h Clicon handle + * @param[in] vars If variable "level" exists, its integer value is used + * @param[in] arg Else use the integer value of argument + * @note The level is either what is specified in arg as int argument. + * _or_ if a 'level' variable is present in vars use that value instead. + */ +int +cli_debug_backendv(clicon_handle h, + cvec *vars, + cvec *argv) +{ + int retval = -1; + cg_var *cv; + int level; + + if ((cv = cvec_find_var(vars, "level")) == NULL){ + if (cvec_len(argv) != 1){ + clicon_err(OE_PLUGIN, 0, "%s: Requires either label var or single arg: 0|1", __FUNCTION__); + goto done; + } + cv = cvec_i(argv, 0); + } + level = cv_int32_get(cv); + /* config daemon */ + retval = clicon_rpc_debug(h, level); + done: + return retval; +} + +/*! Set syntax mode + */ +int +cli_set_modev(clicon_handle h, + cvec *vars, + cvec *argv) { int retval = -1; char *str = NULL; - if (arg == NULL || (str = cv_string_get(arg)) == NULL){ - clicon_err(OE_PLUGIN, 0, "%s: requires string argument", __FUNCTION__); + if (cvec_len(argv) != 1){ + clicon_err(OE_PLUGIN, 0, "%s: Requires one element to be cli mode", __FUNCTION__); goto done; } + str = cv_string_get(cvec_i(argv, 0)); cli_set_syntax_mode(h, str); retval = 0; done: return retval; } -/* +/*! Start bash from cli callback * XXX Application specific?? - * cli_start_shell - * Start bash from cli callback */ int -cli_start_shell(clicon_handle h, cvec *vars, cg_var *arg) +cli_start_shellv(clicon_handle h, + cvec *vars, + cvec *argv) { char *cmd; struct passwd *pw; @@ -344,11 +443,12 @@ cli_start_shell(clicon_handle h, cvec *vars, cg_var *arg) return 0; } -/* - * Generic quit callback +/*! Generic quit callback */ int -cli_quit(clicon_handle h, cvec *vars, cg_var *arg) +cli_quitv(clicon_handle h, + cvec *vars, + cvec *argv) { cli_set_exiting(h, 1); return 0; @@ -358,13 +458,21 @@ cli_quit(clicon_handle h, cvec *vars, cg_var *arg) * @param[in] arg If 1, then snapshot and copy to startup config */ int -cli_commit(clicon_handle h, - cvec *vars, - cg_var *arg) +cli_commitv(clicon_handle h, + cvec *vars, + cvec *argv) { int retval = -1; - int snapshot = arg?cv_int32_get(arg):0; - + int snapshot; + + if (cvec_len(argv) > 1){ + clicon_err(OE_PLUGIN, 0, "%s: Requires 0 or 1 element. If given: snapshot flag 0|1", __FUNCTION__); + goto done; + } + if (cvec_len(argv)) + snapshot = cv_int32_get(cvec_i(argv, 0)); + else + snapshot = 0; if ((retval = clicon_rpc_commit(h, "candidate", "running", @@ -378,11 +486,12 @@ cli_commit(clicon_handle h, return retval; } -/* - * Generic validatecallback +/*! Generic validate callback */ int -cli_validate(clicon_handle h, cvec *vars, cg_var *arg) +cli_validatev(clicon_handle h, + cvec *vars, + cvec *argv) { int retval = -1; @@ -459,17 +568,28 @@ compare_xmls(cxobj *xc1, * @param[in] arg arg: 0 as xml, 1: as text */ int -compare_dbs(clicon_handle h, cvec *cvv, cg_var *arg) +compare_dbsv(clicon_handle h, + cvec *cvv, + cvec *argv) { cxobj *xc1 = NULL; /* running xml */ cxobj *xc2 = NULL; /* candidate xml */ int retval = -1; + int astext; - if (xmldb_get(h, "running", "/", 0, &xc1, NULL, NULL) < 0) + if (cvec_len(argv) > 1){ + clicon_err(OE_PLUGIN, 0, "%s: Requires 0 or 1 element. If given: astext flag 0|1", __FUNCTION__); goto done; - if (xmldb_get(h, "candidate", "/", 0, &xc2, NULL, NULL) < 0) + } + if (cvec_len(argv)) + astext = cv_int32_get(cvec_i(argv, 0)); + else + astext = 0; + if (xmldb_get(h, "running", "/", &xc1, NULL, NULL) < 0) goto done; - if (compare_xmls(xc1, xc2, arg?cv_int32_get(arg):0) < 0) /* astext? */ + if (xmldb_get(h, "candidate", "/", &xc2, NULL, NULL) < 0) + goto done; + if (compare_xmls(xc1, xc2, astext) < 0) /* astext? */ goto done; retval = 0; done: @@ -481,207 +601,31 @@ compare_dbs(clicon_handle h, cvec *cvv, cg_var *arg) return retval; } - -/*! 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] 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" - * 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, - cg_var *arg, - 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; - - 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_set(clicon_handle h, cvec *cvv, cg_var *arg) -{ - int retval = 1; - - if (cli_dbxml(h, cvv, arg, OP_REPLACE) < 0) - goto done; - retval = 0; - done: - return retval; -} - -int -cli_merge(clicon_handle h, cvec *cvv, cg_var *arg) -{ - int retval = -1; - - if (cli_dbxml(h, cvv, arg, OP_MERGE) < 0) - goto done; - retval = 0; - done: - return retval; -} - -int -cli_del(clicon_handle h, cvec *cvv, cg_var *arg) -{ - int retval = -1; - - if (cli_dbxml(h, cvv, arg, OP_REMOVE) < 0) - goto done; - retval = 0; - done: - return retval; -} - - /*! Load a configuration file to candidate database * Utility function used by cligen spec file * @param[in] h CLICON handle - * @param[in] cvv Vector of variables (where is found) - * @param[in] arg A string: " (merge|replace)" + * @param[in] cvv Vector of variables (where is found) + * @param[in] argv A string: " (merge|replace)" * is name of a variable occuring in "cvv" containing filename * @note that "filename" is local on client filesystem not backend. * @note file is assumed to have a dummy top-tag, eg * @code * # cligen spec - * load file , load_config_file("name2 merge"); + * load file , load_config_filev("name2","merge"); * @endcode * @see save_config_file */ int -load_config_file(clicon_handle h, - cvec *cvv, - cg_var *arg) +load_config_filev(clicon_handle h, + cvec *cvv, + cvec *argv) { int ret = -1; struct stat st; - char **vec; char **vecp; char *filename; int replace; - char *str; cg_var *cv; - int nvec; char *opstr; char *varstr; int fd = -1; @@ -690,20 +634,15 @@ load_config_file(clicon_handle h, cxobj *x; cbuf *cbxml; - if (arg == NULL || (str = cv_string_get(arg)) == NULL){ - clicon_err(OE_PLUGIN, 0, "%s: requires string argument", __FUNCTION__); + if (cvec_len(argv) != 2){ + if (cvec_len(argv)==1) + clicon_err(OE_PLUGIN, 0, "Got single argument:\"%s\". Expected \",\"", cv_string_get(cvec_i(argv,0))); + else + clicon_err(OE_PLUGIN, 0, "Got %d arguments. Expected: ,", cvec_len(argv)); goto done; } - if ((vec = clicon_strsplit(str, " ", &nvec, __FUNCTION__)) == NULL){ - clicon_err(OE_PLUGIN, errno, "clicon_strsplit"); - goto done; - } - if (nvec != 2){ - clicon_err(OE_PLUGIN, 0, "Arg syntax is "); - goto done; - } - varstr = vec[0]; - opstr = vec[1]; + varstr = cv_string_get(cvec_i(argv, 0)); + opstr = cv_string_get(cvec_i(argv, 1)); if (strcmp(opstr, "merge") == 0) replace = 0; else @@ -750,6 +689,7 @@ load_config_file(clicon_handle h, } ret = 0; done: + unchunk_group(__FUNCTION__); if (xt) xml_free(xt); if (fd != -1) @@ -773,22 +713,28 @@ load_config_file(clicon_handle h, * @see load_config_file */ int -save_config_file(clicon_handle h, - cvec *cvv, - cg_var *arg) +save_config_filev(clicon_handle h, + cvec *cvv, + cvec *argv) { int retval = -1; - char **vec; char **vecp; char *filename; cg_var *cv; - int nvec; - char *str; char *dbstr; char *varstr; cxobj *xt = NULL; FILE *f = NULL; + if (cvec_len(argv) != 2){ + if (cvec_len(argv)==1) + clicon_err(OE_PLUGIN, 0, "%s: Got single argument:\"%s\". Expected \",\"", cv_string_get(cvec_i(argv,0))); + else + clicon_err(OE_PLUGIN, 0, "%s: Got %d arguments. Expected: ,", cvec_len(argv)); + + goto done; + } +#if 0 if (arg == NULL || (str = cv_string_get(arg)) == NULL){ clicon_err(OE_PLUGIN, 0, "%s: requires string argument", __FUNCTION__); goto done; @@ -801,8 +747,9 @@ save_config_file(clicon_handle h, clicon_err(OE_PLUGIN, 0, "Arg syntax is "); goto done; } - dbstr = vec[0]; - varstr = vec[1]; +#endif + dbstr = cv_string_get(cvec_i(argv, 0)); + varstr = cv_string_get(cvec_i(argv, 1)); if (strcmp(dbstr, "running") != 0 && strcmp(dbstr, "candidate") != 0) { clicon_err(OE_PLUGIN, 0, "No such db name: %s", dbstr); goto done; @@ -816,7 +763,7 @@ save_config_file(clicon_handle h, goto done; } filename = vecp[0]; - if (xmldb_get(h, dbstr, "/", 0, &xt, NULL, NULL) < 0) + if (xmldb_get(h, dbstr, "/", &xt, NULL, NULL) < 0) goto done; if ((f = fopen(filename, "wb")) == NULL){ clicon_err(OE_CFG, errno, "Creating file %s", filename); @@ -839,15 +786,18 @@ save_config_file(clicon_handle h, * Utility function used by cligen spec file */ int -delete_all(clicon_handle h, cvec *cvv, cg_var *arg) +delete_allv(clicon_handle h, + cvec *cvv, + cvec *argv) { char *dbstr; int retval = -1; - if (arg == NULL || (dbstr = cv_string_get(arg)) == NULL){ - clicon_err(OE_PLUGIN, 0, "%s: requires string argument", __FUNCTION__); + if (cvec_len(argv) != 1){ + clicon_err(OE_PLUGIN, 0, "%s: Requires one element: dbname", __FUNCTION__); goto done; } + dbstr = cv_string_get(cvec_i(argv, 0)); if (strcmp(dbstr, "running") != 0 && strcmp(dbstr, "candidate") != 0){ clicon_err(OE_PLUGIN, 0, "No such db name: %s", dbstr); goto done; @@ -864,7 +814,9 @@ delete_all(clicon_handle h, cvec *cvv, cg_var *arg) /*! Discard all changes in candidate and replace with running */ int -discard_changes(clicon_handle h, cvec *cvv, cg_var *arg) +discard_changesv(clicon_handle h, + cvec *cvv, + cvec *argv) { return clicon_rpc_copy(h, "running", "candidate"); } @@ -880,7 +832,8 @@ static const char *SHOWAS_XML2JSON = "xml2json"; * param[in] arg format: txt, xml, xml2txt, xml2json */ static int -cli_notification_cb(int s, void *arg) +cli_notification_cb(int s, + void *arg) { struct clicon_msg *reply; enum clicon_msg_type type; @@ -953,7 +906,6 @@ cli_notification_cb(int s, void *arg) } - /*! Make a notify subscription to backend and un/register callback for return messages. * * @param[in] h Clicon handle @@ -963,40 +915,29 @@ cli_notification_cb(int s, void *arg) * and is XXX * Example code: Start logging of mystream and show logs as xml * @code - * cmd("comment"), cli_notify("mystream 1 xml"); + * cmd("comment"), cli_notifyv("mystream","1","xml"); * @endcode * XXX: format is a memory leak */ int -cli_notify(clicon_handle h, - cvec *cvv, - cg_var *arg) +cli_notifyv(clicon_handle h, + cvec *cvv, + cvec *argv) { char *stream = NULL; int retval = -1; - char **vec = NULL; - int nvec; - char *str; int status; char *formatstr = NULL; enum format_enum format = MSG_NOTIFY_TXT; - if (arg==NULL || (str = cv_string_get(arg)) == NULL){ - clicon_err(OE_PLUGIN, 0, "%s: requires string argument", __FUNCTION__); + if (cvec_len(argv) != 2 && cvec_len(argv) != 3){ + clicon_err(OE_PLUGIN, 0, "%s Requires arguments: []", __FUNCTION__); goto done; } - if ((vec = clicon_strsplit(str, " ", &nvec, __FUNCTION__)) == NULL){ - clicon_err(OE_PLUGIN, errno, "clicon_strsplit"); - goto done; - } - if (nvec < 2){ - clicon_err(OE_PLUGIN, 0, "format error \"%s\" - expected ", str); - goto done; - } - stream = vec[0]; - status = atoi(vec[1]); - if (nvec > 2){ - formatstr = strdup(vec[2]); /* memory leak */ + stream = cv_string_get(cvec_i(argv, 0)); + status = atoi(cv_string_get(cvec_i(argv, 1))); + if (cvec_len(argv) > 2){ + formatstr = cv_string_get(cvec_i(argv, 2)); if (strcmp(formatstr, "SHOWAS_TXT") != 0) format = MSG_NOTIFY_XML; } @@ -1011,65 +952,205 @@ cli_notify(clicon_handle h, retval = 0; done: - unchunk_group(__FUNCTION__); return retval; } -/*! Register log notification stream - * @param[in] h Clicon handle - * @param[in] stream Event stream. CLICON is predefined, others are application-defined - * @param[in] filter Filter. For xml notification ie xpath: .[name=kalle] - * @param[in] status 0 for stop, 1 to start - * @param[in] fn Callback function called when notification occurs - * @param[in] arg Argumnent to function +/* Here are backward compatible cligen callback functions used when + * the option: CLICON_CLIGEN_CALLBACK_SINGLE_ARG is set. */ -int -cli_notification_register(clicon_handle h, - char *stream, - enum format_enum format, - char *filter, - int status, - int (*fn)(int, void*), - void *arg) -{ - int retval = -1; - char *logname; - void *p; - int s; - clicon_hash_t *cdat = clicon_data(h); - size_t len; - int s_exist = -1; - if ((logname = chunk_sprintf(__FUNCTION__, "log_socket_%s", stream)) == NULL){ - clicon_err(OE_PLUGIN, errno, "%s: chunk_sprintf", __FUNCTION__); +cb_single_arg(cli_set) +cb_single_arg(cli_merge) +cb_single_arg(cli_del) +cb_single_arg(cli_debug_cli) +cb_single_arg(cli_debug_backend) +cb_single_arg(cli_set_mode) +cb_single_arg(cli_start_shell) +cb_single_arg(cli_quit) +//cb_single_arg(cli_commit) +int cli_commit(clicon_handle h, cvec *cvv, cg_var *arg) +{ + int retval=-1; + cvec *argv = NULL; + + if (arg){ + if (cv_type_get(arg) > CGV_EMPTY){ + cligen_output(stderr, "%s: Illegal cvtype. This is most probably a single-argument cligen callback being used in a multi-argument setting. This can happen if option CLICON_CLIGEN_CALLBACK_SINGLE_ARG is 0 but you call a single argument callback (eg %s) from a .cli file. Please change to a multi-argument callback\n", __FUNCTION__, __FUNCTION__); + goto done; + } + if ((argv = cvec_from_var(arg)) == NULL){ + clicon_err(OE_UNIX, errno, "cvec_from_var"); + goto done; + } + } + retval = cli_commitv(h, cvv, argv); + done: + if (argv) cvec_free(argv); + return retval; +} + +cb_single_arg(cli_validate) +cb_single_arg(compare_dbs) +cb_single_arg(delete_all) +cb_single_arg(discard_changes) + +/* Follows some functions not covered by translation macro */ +int +load_config_file(clicon_handle h, + cvec *cvv, + cg_var *arg) +{ + int retval=-1; + cvec *argv; + cg_var *cv; + char *str; + char **vec; + int nvec; + + /* Split string into two parts and build a cvec of it and supply that to + the multi-arg callback */ + if (arg == NULL || (str = cv_string_get(arg)) == NULL){ + clicon_err(OE_PLUGIN, 0, "%s: requires string argument", __FUNCTION__); goto done; } - if ((p = hash_value(cdat, logname, &len)) != NULL) - s_exist = *(int*)p; - - if (status){ /* start */ - if (s_exist!=-1){ - clicon_err(OE_PLUGIN, 0, "%s: result log socket already exists", __FUNCTION__); - goto done; - } - if (clicon_rpc_subscription(h, status, stream, format, filter, &s) < 0) - goto done; - if (cligen_regfd(s, fn, arg) < 0) - goto done; - if (hash_add(cdat, logname, &s, sizeof(s)) == NULL) - goto done; + if ((vec = clicon_strsplit(str, " ", &nvec, __FUNCTION__)) == NULL){ + clicon_err(OE_PLUGIN, errno, "clicon_strsplit"); + goto done; } - else{ /* stop */ - if (s_exist != -1){ - cligen_unregfd(s_exist); - } - hash_del(cdat, logname); - if (clicon_rpc_subscription(h, status, stream, format, filter, NULL) < 0) - goto done; - + if (nvec != 2){ + clicon_err(OE_PLUGIN, 0, "Arg syntax is "); + goto done; } - retval = 0; - done: + if ((argv = cvec_new(nvec)) == NULL){ + clicon_err(OE_UNIX, errno, "cvec_from_var"); + goto done; + } + cv = cvec_i(argv, 0); + cv_type_set(cv, CGV_STRING); + cv_string_set(cv, vec[0]); + cv = cvec_i(argv, 1); + cv_type_set(cv, CGV_STRING); + cv_string_set(cv, vec[1]); + + retval = load_config_filev(h, cvv, argv); + done: unchunk_group(__FUNCTION__); return retval; } +int +save_config_file(clicon_handle h, + cvec *cvv, + cg_var *arg) +{ + int retval=-1; + cvec *argv; + cg_var *cv; + char *str; + char **vec; + int nvec; + + /* Split string into two parts and build a cvec of it and supply that to + the multi-arg callback */ + if (arg == NULL || (str = cv_string_get(arg)) == NULL){ + clicon_err(OE_PLUGIN, 0, "%s: requires string argument", __FUNCTION__); + goto done; + } + if ((vec = clicon_strsplit(str, " ", &nvec, __FUNCTION__)) == NULL){ + clicon_err(OE_PLUGIN, errno, "clicon_strsplit"); + goto done; + } + if (nvec != 2){ + clicon_err(OE_PLUGIN, 0, "Arg syntax is "); + goto done; + } + if ((argv = cvec_new(nvec)) == NULL){ + clicon_err(OE_UNIX, errno, "cvec_from_var"); + goto done; + } + cv = cvec_i(argv, 0); + cv_type_set(cv, CGV_STRING); + cv_string_set(cv, vec[0]); + cv = cvec_i(argv, 1); + cv_type_set(cv, CGV_STRING); + cv_string_set(cv, vec[1]); + + retval = save_config_filev(h, cvv, argv); + done: + unchunk_group(__FUNCTION__); + return retval; +} + + + +int +cli_notify(clicon_handle h, + cvec *cvv, + cg_var *arg) +{ + int retval=-1; + cvec *argv; + cg_var *cv; + char *str; + char **vec; + int nvec; + + /* Split string into two parts and build a cvec of it and supply that to + the multi-arg callback */ + if (arg == NULL || (str = cv_string_get(arg)) == NULL){ + clicon_err(OE_PLUGIN, 0, "%s: requires string argument", __FUNCTION__); + goto done; + } + if ((vec = clicon_strsplit(str, " ", &nvec, __FUNCTION__)) == NULL){ + clicon_err(OE_PLUGIN, errno, "clicon_strsplit"); + goto done; + } + if (nvec != 2 && nvec != 3){ + clicon_err(OE_PLUGIN, 0, "Arg syntax is []"); + goto done; + } + if ((argv = cvec_new(nvec)) == NULL){ + clicon_err(OE_UNIX, errno, "cvec_from_var"); + goto done; + } + cv = cvec_i(argv, 0); + cv_type_set(cv, CGV_STRING); + cv_string_set(cv, vec[0]); + + cv = cvec_i(argv, 1); + cv_type_set(cv, CGV_STRING); + cv_string_set(cv, vec[1]); + if (nvec > 2){ + cv = cvec_i(argv, 2); + cv_type_set(cv, CGV_STRING); + cv_string_set(cv, vec[2]); + } + retval = cli_notifyv(h, cvv, argv); + done: + unchunk_group(__FUNCTION__); + return retval; +} + +/* + * cli_debug + * set debug level on stderr (not syslog). + * The level is either what is specified in arg as int argument. + * _or_ if a 'level' variable is present in vars use that value instead. + * XXX obsolete. Use cli_debug_cliv or cli_debug_backendv instead + */ +int +cli_debug(clicon_handle h, cvec *vars, cg_var *arg) +{ + cg_var *cv; + int level; + + if ((cv = cvec_find_var(vars, "level")) == NULL) + cv = arg; + level = cv_int32_get(cv); + /* cli */ + clicon_debug_init(level, NULL); /* 0: dont debug, 1:debug */ + /* config daemon */ + if (clicon_rpc_debug(h, level) < 0) + goto done; + done: + return 0; +} diff --git a/apps/cli/cli_common.h b/apps/cli/cli_common.h index cc71041e..6e2d122b 100644 --- a/apps/cli/cli_common.h +++ b/apps/cli/cli_common.h @@ -37,6 +37,24 @@ #ifndef _CLI_COMMON_H_ #define _CLI_COMMON_H_ +/*! macro to create a single-argument callback from multiple */ +#define cb_single_arg(fn) \ +int fn(clicon_handle h, cvec *cvv, cg_var *arg) \ +{ \ + int retval=-1; \ + cvec *argv = NULL; \ + \ + if (arg && (argv = cvec_from_var(arg)) == NULL){ \ + clicon_err(OE_UNIX, errno, "cvec_from_var"); \ + goto done; \ + } \ + retval = fn##v(h, cvv, argv); \ + done: \ + if (argv) cvec_free(argv); \ + return retval; \ +} + + void cli_signal_block(clicon_handle h); void cli_signal_unblock(clicon_handle h); diff --git a/apps/cli/cli_generate.c b/apps/cli/cli_generate.c index fb57a6e5..bf7d9630 100644 --- a/apps/cli/cli_generate.c +++ b/apps/cli/cli_generate.c @@ -33,11 +33,11 @@ * * Translation between database specs - * dbspec_key yang_spec CLIgen parse_tree - * +-------------+ key2yang +-------------+ yang2cli +-------------+ - * | keyspec | -------------> | | ------------> | cli | - * | A[].B !$a | yang2key | list{key A;}| | syntax | - * +-------------+ <------------ +-------------+ +-------------+ + * yang_spec CLIgen parse_tree + * +-------------+ yang2cli +-------------+ + * | | ------------> | cli | + * | list{key A;}| | syntax | + * +-------------+ +-------------+ */ #ifdef HAVE_CONFIG_H #include "clixon_config.h" /* generated by config & autoconf */ diff --git a/apps/cli/cli_plugin.c b/apps/cli/cli_plugin.c index 684d55ba..d60c052f 100644 --- a/apps/cli/cli_plugin.c +++ b/apps/cli/cli_plugin.c @@ -660,10 +660,6 @@ int clicon_eval(clicon_handle h, char *cmd, cg_obj *match_obj, cvec *vr) { cli_output_reset(); -#ifdef notyet - if (isrecording()) - record_command(cmd); -#endif if (!cli_exiting(h)) { clicon_err_reset(); if (cligen_eval(cli_cligen(h), match_obj, vr) < 0) { diff --git a/apps/cli/cli_show.c b/apps/cli/cli_show.c index 3df2a35c..0b006126 100644 --- a/apps/cli/cli_show.c +++ b/apps/cli/cli_show.c @@ -72,6 +72,7 @@ /* Exported functions in this file are in clixon_cli_api.h */ #include "clixon_cli_api.h" +#include "cli_common.h" /* internal functions */ static int xml2csv(FILE *f, cxobj *x, cvec *cvv); //static int xml2csv_raw(FILE *f, cxobj *x); @@ -140,7 +141,7 @@ expandv_dbvar(void *h, */ if (xmlkeyfmt2xpath(xkfmt, cvv, &xkpath) < 0) goto done; - if (xmldb_get(h, dbstr, xkpath, 1, &xt, &xvec, &xlen) < 0) + if (xmldb_get(h, dbstr, xkpath, &xt, &xvec, &xlen) < 0) goto done; /* One round to detect duplicates * XXX The code below would benefit from some cleanup @@ -196,116 +197,6 @@ expandv_dbvar(void *h, } -/*! This is obsolete version of expandv_dbvar - * If CLICON_CLIGEN_EXPAND_SINGLE_ARG is set -*/ -int -expand_dbvar(void *h, - char *name, - cvec *cvv, - cg_var *arg, - int *nr, - char ***commands, - char ***helptexts) -{ - int nvec; - char **vec = NULL; - int retval = -1; - char *xkfmt; - char *str; - 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; - int i0; - - if (arg == NULL || (str = cv_string_get(arg)) == NULL){ - clicon_err(OE_PLUGIN, 0, "%s: requires string argument", __FUNCTION__); - goto done; - } - /* In the example, str = "candidate /x/m1/%s/b" */ - if ((vec = clicon_strsplit(str, " ", &nvec, __FUNCTION__)) == NULL){ - clicon_err(OE_PLUGIN, errno, "clicon_strsplit"); - goto done; - } - dbstr = vec[0]; - if (strcmp(dbstr, "running") != 0 && - strcmp(dbstr, "candidate") != 0){ - clicon_err(OE_PLUGIN, 0, "No such db name: %s", dbstr); - goto done; - } - xkfmt = vec[1]; - /* 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 0){ + str = cv_string_get(cvec_i(argv, 0)); + yn = (yang_node*)yang_find((yang_node*)yspec, 0, str); + } + else + yn = (yang_node*)yspec; + yang_print(stdout, yn, 0); + return 0; +} + + +#ifdef notused +/*! XML to CSV raw variant + * @see xml2csv + */ +static int +xml2csv_raw(FILE *f, cxobj *x) +{ + cxobj *xc; + cxobj *xb; + int retval = -1; + int i = 0; + + xc = NULL; + while ((xc = xml_child_each(x, xc, CX_ELMNT)) != NULL) { + if (xml_child_nr(xc)){ + xb = xml_child_i(xc, 0); + if (xml_type(xb) == CX_BODY){ + if (i++) + fprintf(f, ";"); + fprintf(f, "%s", xml_value(xb)); + } + } + } + fprintf(f, "\n"); + retval = 0; + return retval; +} +#endif + +/*! Translate XML -> CSV commands + * Can only be made in a 'flat tree', ie on the form: + * B --> + * Type, A + * X, B + * @param[in] f Output file + * @param[in] x XML tree + * @param[in] cvv A vector of field names present in XML + * This means that only fields in x that are listed in cvv will be printed. + */ +static int +xml2csv(FILE *f, cxobj *x, cvec *cvv) +{ + cxobj *xe, *xb; + int retval = -1; + cg_var *vs; + + fprintf(f, "%s", xml_name(x)); + xe = NULL; + + vs = NULL; + while ((vs = cvec_each(cvv, vs))) { + if ((xe = xml_find(x, cv_name_get(vs))) == NULL){ + fprintf(f, ";"); + continue; + } + if (xml_child_nr(xe)){ + xb = xml_child_i(xe, 0); + fprintf(f, ";%s", xml_value(xb)); + } + } + fprintf(f, "\n"); + retval = 0; + return retval; +} + +/*! Generic function for showing configurations. + * Utility function used by cligen spec file + * @param[in] h CLICON handle + * @param[in] cvv Vector of variables from CLIgen command-line + * @param[in] arg A string: [] + * @param[out] xt Configuration as xml tree. + * Format of arg: + * "running", "candidate" + * xpath expression + * optional name of variable in cvv. If set, xpath must have a '%s' + * @code + * show config id , show_conf_as("running interfaces/interface[name=%s] n"); + * @endcode + */ +static int +show_confv_as(clicon_handle h, + cvec *cvv, + cvec *argv, + cxobj **xt) /* top xml */ +{ + int retval = -1; + char *db; + char *xpath; + char *attr = NULL; + cbuf *cbx = NULL; + int i; + int j; + cg_var *cvattr; + char *val = NULL; + + if (cvec_len(argv) != 2 && cvec_len(argv) != 3){ + if (cvec_len(argv)==1) + clicon_err(OE_PLUGIN, 0, "Got single argument:\"%s\". Expected \",[,]\"", cv_string_get(cvec_i(argv,0))); + else + clicon_err(OE_PLUGIN, 0, "Got %d arguments. Expected: ,[,]", cvec_len(argv)); + + goto done; + } + /* Dont get attr here, take it from arg instead */ + db = cv_string_get(cvec_i(argv, 0)); + if (strcmp(db, "running") != 0 && strcmp(db, "candidate") != 0) { + clicon_err(OE_PLUGIN, 0, "No such db name: %s", db); + goto done; + } + xpath = cv_string_get(cvec_i(argv, 1)); + if ((cbx = cbuf_new()) == NULL){ + clicon_err(OE_PLUGIN, errno, "cbuf_new"); + goto done; + } + if (cvec_len(argv) == 3){ + attr = cv_string_get(cvec_i(argv, 2)); + j = 0; + for (i=0; i [] + * @param[in] netconf If set print as netconf edit-config, otherwise just xml + * @see show_conf_as the main function + */ +static int +show_confv_as_xml1(clicon_handle h, + cvec *cvv, + cvec *argv, + int netconf) +{ + cxobj *xt = NULL; + cxobj *xc; + int retval = -1; + + if (show_confv_as(h, cvv, argv, &xt) < 0) + goto done; + if (netconf) /* netconf prefix */ + fprintf(stdout, "\n"); + xc = NULL; /* Dont print xt itself */ + while ((xc = xml_child_each(xt, xc, -1)) != NULL) + clicon_xml2file(stdout, xc, netconf?2:0, 1); + if (netconf) /* netconf postfix */ + fprintf(stdout, "]]>]]>\n"); + retval = 0; + done: + if (xt) + xml_free(xt); + return retval; +} + +/*! Show configuration as prettyprinted xml + * Utility function used by cligen spec file + * @param[in] h CLICON handle + * @param[in] cvv Vector of variables from CLIgen command-line + * @param[in] arg A string: [] + * @see show_conf_as the main function + */ +int +show_confv_as_xml(clicon_handle h, + cvec *cvv, + cvec *argv) +{ + return show_confv_as_xml1(h, cvv, argv, 0); +} + +/*! Show configuration as prettyprinted xml with netconf hdr/tail + * Utility function used by cligen spec file + * @param[in] h CLICON handle + * @param[in] cvv Vector of variables from CLIgen command-line + * @param[in] arg A string: [] + * @see show_conf_as the main function + */ +int +show_confv_as_netconf(clicon_handle h, + cvec *cvv, + cvec *argv) +{ + return show_confv_as_xml1(h, cvv, argv, 1); +} + +/*! Show configuration as JSON + * Utility function used by cligen spec file + * @param[in] h CLICON handle + * @param[in] cvv Vector of variables from CLIgen command-line + * @param[in] arg A string: [] + * @see show_conf_as the main function + */ +int +show_confv_as_json(clicon_handle h, + cvec *cvv, + cvec *argv) +{ + cxobj *xt = NULL; + int retval = -1; + + if (show_confv_as(h, cvv, argv, &xt) < 0) + goto done; + xml2json(stdout, xt, 1); + retval = 0; + done: + if (xt) + xml_free(xt); + return retval; +} + + +/*! Show configuration as text + * Utility function used by cligen spec file + */ +static int +show_confv_as_text1(clicon_handle h, + cvec *cvv, + cvec *argv) +{ + cxobj *xt = NULL; + cxobj *xc; + int retval = -1; + + if (show_confv_as(h, cvv, argv, &xt) < 0) + goto done; + xc = NULL; /* Dont print xt itself */ + while ((xc = xml_child_each(xt, xc, -1)) != NULL) + xml2txt(stdout, xc, 0); /* tree-formed text */ + retval = 0; + done: + if (xt) + xml_free(xt); + unchunk_group(__FUNCTION__); + return retval; +} + + +/* Show configuration as commands, ie not tree format but as one-line commands + */ +static int +show_confv_as_command(clicon_handle h, + cvec *cvv, + cvec *argv, + char *prepend) +{ + cxobj *xt = NULL; + cxobj *xc; + enum genmodel_type gt; + int retval = -1; + + if ((xt = xml_new("tmp", NULL)) == NULL) + goto done; + if (show_confv_as(h, cvv, argv, &xt) < 0) + goto done; + xc = NULL; /* Dont print xt itself */ + while ((xc = xml_child_each(xt, xc, -1)) != NULL){ + if ((gt = clicon_cli_genmodel_type(h)) == GT_ERR) + goto done; + xml2cli(stdout, xc, prepend, gt, __FUNCTION__); /* cli syntax */ + } + retval = 0; + done: + if (xt) + xml_free(xt); + unchunk_group(__FUNCTION__); + return retval; +} + +int +show_confv_as_text(clicon_handle h, + cvec *cvv, + cvec *argv) +{ + return show_confv_as_text1(h, cvv, argv); +} + +int +show_confv_as_cli(clicon_handle h, + cvec *cvv, + cvec *argv) +{ + return show_confv_as_command(h, cvv, argv, NULL); /* XXX: how to set prepend? */ +} + +static int +show_confv_as_csv1(clicon_handle h, + cvec *cvv0, + cvec *argv) +{ + cxobj *xt = NULL; + cxobj *xc; + int retval = -1; + cvec *cvv=NULL; + char *str; + + if (show_confv_as(h, cvv0, argv, &xt) < 0) + goto done; + xc = NULL; /* Dont print xt itself */ + while ((xc = xml_child_each(xt, xc, -1)) != NULL){ + if ((str = chunk_sprintf(__FUNCTION__, "%s[]", xml_name(xc))) == NULL) + goto done; +#ifdef NOTYET /* yang-spec? */ + if (ds==NULL && (ds = key2spec_key(dbspec, str)) != NULL){ + cg_var *vs; + fprintf(stdout, "Type"); + cvv = db_spec2cvec(ds); + vs = NULL; + while ((vs = cvec_each(cvv, vs))) + fprintf(stdout, ";%s", cv_name_get(vs)); + fprintf(stdout, "\n"); + } /* Now values just need to follow,... */ +#endif /* yang-spec? */ + if (cvv== NULL) + goto done; + xml2csv(stdout, xc, cvv); /* csv syntax */ + } + retval = 0; + done: + if (xt) + xml_free(xt); + unchunk_group(__FUNCTION__); + return retval; +} + +int +show_confv_as_csv(clicon_handle h, + cvec *cvv, + cvec *argv) +{ + return show_confv_as_csv1(h, cvv, argv); +} + +/*! Show configuration as text given an xpath + * Utility function used by cligen spec file + * @param[in] h CLICON handle + * @param[in] cvv Vector of variables from CLIgen command-line + * @param[in] arg A string: + * @note Hardcoded that a variable in cvv is named "xpath" + */ +int +show_confv_xpath(clicon_handle h, + cvec *cvv, + cvec *argv) +{ + int retval = -1; + char *str; + char *xpath; + cg_var *cv; + cxobj *xt = NULL; + cxobj **xv = NULL; + size_t xlen; + int i; + + if (cvec_len(argv) != 1){ + clicon_err(OE_PLUGIN, 0, "%s: Requires one element to be ", __FUNCTION__); + goto done; + } + str = cv_string_get(cvec_i(argv, 0)); + /* Dont get attr here, take it from arg instead */ + if (strcmp(str, "running") != 0 && strcmp(str, "candidate") != 0){ + clicon_err(OE_PLUGIN, 0, "No such db name: %s", str); + goto done; + } + cv = cvec_find_var(cvv, "xpath"); + xpath = cv_string_get(cv); + if (xmldb_get(h, str, xpath, &xt, &xv, &xlen) < 0) + goto done; + for (i=0; i /interface=eth0/address=1.2.3.4 + */ + if (xmlkeyfmt2xpath(xkfmt, cvv, &xkpath) < 0) + goto done; + if (xmldb_get(h, dbstr, xkpath, &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 []", str); + clicon_err(OE_PLUGIN, 0, "format error \"%s\" - expected [] got %d arg", str, nvec); goto done; } /* Dont get attr here, take it from arg instead */ @@ -498,7 +940,7 @@ show_conf_as(clicon_handle h, } else cprintf(cbx, "%s", xpath); - if (xmldb_get(h, db, cbuf_get(cbx), 0, xt, NULL, NULL) < 0) + if (xmldb_get(h, db, cbuf_get(cbx), xt, NULL, NULL) < 0) goto done; retval = 0; done: @@ -601,54 +1043,6 @@ show_conf_as_json(clicon_handle h, return retval; } -/*! Show configuration as text givebn an xpath - * Utility function used by cligen spec file - * @param[in] h CLICON handle - * @param[in] cvv Vector of variables from CLIgen command-line - * @param[in] arg A string: - * @note Hardcoded that a variable in cvv is named "xpath" - */ -int -show_conf_xpath(clicon_handle h, - cvec *cvv, - cg_var *arg) -{ - int retval = -1; - char *str; - char *xpath; - cg_var *cv; - cxobj *xt = NULL; - cxobj **xv = NULL; - size_t xlen; - int i; - - if (arg == NULL || (str = cv_string_get(arg)) == NULL){ - clicon_err(OE_PLUGIN, 0, "%s: requires string argument", __FUNCTION__); - goto done; - } - /* Dont get attr here, take it from arg instead */ - if (strcmp(str, "running") != 0 && strcmp(str, "candidate") != 0){ - clicon_err(OE_PLUGIN, 0, "No such db name: %s", str); - goto done; - } - cv = cvec_find_var(cvv, "xpath"); - xpath = cv_string_get(cv); - if (xmldb_get(h, str, xpath, 1, &xt, &xv, &xlen) < 0) - goto done; - for (i=0; i CSV commands - * Can only be made in a 'flat tree', ie on the form: - * B --> - * Type, A - * X, B - * @param[in] f Output file - * @param[in] x XML tree - * @param[in] cvv A vector of field names present in XML - * This means that only fields in x that are listed in cvv will be printed. - */ -static int -xml2csv(FILE *f, cxobj *x, cvec *cvv) -{ - cxobj *xe, *xb; - int retval = -1; - cg_var *vs; - - fprintf(f, "%s", xml_name(x)); - xe = NULL; - - vs = NULL; - while ((vs = cvec_each(cvv, vs))) { - if ((xe = xml_find(x, cv_name_get(vs))) == NULL){ - fprintf(f, ";"); - continue; - } - if (xml_child_nr(xe)){ - xb = xml_child_i(xe, 0); - fprintf(f, ";%s", xml_value(xb)); - } - } - fprintf(f, "\n"); - retval = 0; - return retval; -} - static int show_conf_as_csv1(clicon_handle h, cvec *cvv0, cg_var *arg) { @@ -841,3 +1151,50 @@ show_conf_as_csv(clicon_handle h, cvec *cvv, cg_var *arg) { return show_conf_as_csv1(h, cvv, arg); } + +/*! Show configuration as text given an xpath + * Utility function used by cligen spec file + * @param[in] h CLICON handle + * @param[in] cvv Vector of variables from CLIgen command-line + * @param[in] arg A string: + * @note Hardcoded that a variable in cvv is named "xpath" + */ +int +show_conf_xpath(clicon_handle h, + cvec *cvv, + cg_var *arg) +{ + int retval = -1; + char *str; + char *xpath; + cg_var *cv; + cxobj *xt = NULL; + cxobj **xv = NULL; + size_t xlen; + int i; + + if (arg == NULL || (str = cv_string_get(arg)) == NULL){ + clicon_err(OE_PLUGIN, 0, "%s: requires string argument", __FUNCTION__); + goto done; + } + /* Dont get attr here, take it from arg instead */ + if (strcmp(str, "running") != 0 && strcmp(str, "candidate") != 0){ + clicon_err(OE_PLUGIN, 0, "No such db name: %s", str); + goto done; + } + cv = cvec_find_var(cvv, "xpath"); + xpath = cv_string_get(cv); + if (xmldb_get(h, str, xpath, &xt, &xv, &xlen) < 0) + goto done; + for (i=0; iselect"); goto done; } - if (xmldb_get(h, source, selector, 0, &xdb, NULL, NULL) < 0){ + if (xmldb_get(h, source, selector, &xdb, NULL, NULL) < 0){ netconf_create_rpc_error(cb_err, xorig, "operation-failed", "application", diff --git a/apps/restconf/restconf_main.c b/apps/restconf/restconf_main.c index 0abfe8ef..6a260aa4 100644 --- a/apps/restconf/restconf_main.c +++ b/apps/restconf/restconf_main.c @@ -217,7 +217,7 @@ api_data_get(clicon_handle h, } } clicon_debug(1, "%s path:%s", __FUNCTION__, cbuf_get(path)); - if (xmldb_get(h, "running", cbuf_get(path), 1, &xt, &vec, &veclen) < 0) + if (xmldb_get(h, "running", cbuf_get(path), &xt, &vec, &veclen) < 0) goto done; if ((cbx = cbuf_new()) == NULL) diff --git a/apps/xmldb/xmldb_main.c b/apps/xmldb/xmldb_main.c index f5ab7c73..78a39b55 100644 --- a/apps/xmldb/xmldb_main.c +++ b/apps/xmldb/xmldb_main.c @@ -117,7 +117,6 @@ xmldb_from_get(clicon_handle h, cxobj *x; cbuf *cb = NULL; /* Outgoing return message */ char *db; - int vector = 0; char *xpath = "/"; cxobj *xt = NULL; /* Top of return tree */ cxobj *xc; /* Child */ @@ -135,17 +134,15 @@ xmldb_from_get(clicon_handle h, } if ((x = xpath_first(xr, "xpath")) != NULL) xpath = xml_body(x); - if (xpath_first(xr, "vector") != NULL) - vector++; /* Actual get call */ - if (xmldb_get(h, db, xpath, vector, &xt, &xvec, &xlen) < 0) + if (xmldb_get(h, db, xpath, &xt, &xvec, &xlen) < 0) goto done; xml_name_set(xt, "config"); if ((cb = cbuf_new()) == NULL){ clicon_err(OE_UNIX, errno, "cbuf_new"); goto done; } - if (vector){ + if (xvec){ for (i=0; i cmd, callback("two" "args"); -# But there are still many pre-defined in callbacks, eg in cli_common.c that are not made -# for this. +# And change predefined callbacks, eg cli_commit -> cli_commitv in all cli files CLICON_CLIGEN_CALLBACK_SINGLE_ARG 1 diff --git a/configure b/configure index 27fa3d26..0158ff79 100755 --- a/configure +++ b/configure @@ -2328,8 +2328,6 @@ test -n "$target_alias" && -# Some stuff installed in /usr/local/. Such as qdbm - # ac_ext=c ac_cpp='$CPP $CPPFLAGS' @@ -3529,9 +3527,6 @@ AR_SUFFIX=".a" SH_SUFFIX=".so" AR="ar" -CPPFLAGS="-I${prefix}/include ${CPPFLAGS}" -LDFLAGS="-L${prefix}/lib ${LDFLAGS}" - # This is for cligen # Check whether --with-cligen was given. @@ -3884,8 +3879,6 @@ if test "${with_qdbm}"; then LDFLAGS="-L${with_qdbm}/lib ${LDFLAGS}" fi -LIBS="${LIBS} ${LDFLAGS}" - # Problem: depot.h may be in qdbm/depot.h. for ac_header in depot.h do : diff --git a/configure.ac b/configure.ac index b1eb08db..4589e06f 100644 --- a/configure.ac +++ b/configure.ac @@ -78,8 +78,6 @@ AC_SUBST(EXE_SUFFIX) AC_SUBST(AR) AC_SUBST(RANLIB) -# Some stuff installed in /usr/local/. Such as qdbm - # AC_PROG_CC() AC_PROG_CPP @@ -113,9 +111,6 @@ AR_SUFFIX=".a" SH_SUFFIX=".so" AR="ar" -CPPFLAGS="-I${prefix}/include ${CPPFLAGS}" -LDFLAGS="-L${prefix}/lib ${LDFLAGS}" - # This is for cligen AC_ARG_WITH(cligen, [ --with-cligen=dir Use CLIGEN here ] ) if test "${with_cligen}"; then @@ -137,8 +132,6 @@ if test "${with_qdbm}"; then LDFLAGS="-L${with_qdbm}/lib ${LDFLAGS}" fi -LIBS="${LIBS} ${LDFLAGS}" - # Problem: depot.h may be in qdbm/depot.h. AC_CHECK_HEADERS(depot.h,,[AC_CHECK_HEADERS(qdbm/depot.h,,AC_MSG_ERROR(libqdbm-dev required))]) AC_CHECK_LIB(qdbm, dpopen,, AC_MSG_ERROR(libqdbm-dev required)) diff --git a/doc/clixon_example_sdk.odg b/doc/clixon_example_sdk.odg new file mode 100644 index 0000000000000000000000000000000000000000..5ad8909d674025c4892d672cc7b9e863ac246bfa GIT binary patch literal 30267 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 cmd, callback("two" "args"); +# And change predefined callbacks, eg cli_commit -> cli_commitv in all cli files +CLICON_CLIGEN_CALLBACK_SINGLE_ARG 0 diff --git a/example/routing_cli.c b/example/routing_cli.c index 9aa5ea61..eb3a3a9a 100644 --- a/example/routing_cli.c +++ b/example/routing_cli.c @@ -68,7 +68,7 @@ plugin_init(clicon_handle h) /*! Example cli function */ int -mycallback(clicon_handle h, cvec *cvv, cg_var *arg) +mycallback(clicon_handle h, cvec *cvv, cvec *argv) { int retval = -1; cxobj *xt = NULL; @@ -77,12 +77,11 @@ mycallback(clicon_handle h, cvec *cvv, cg_var *arg) /* Access cligen callback variables */ myvar = cvec_find(cvv, "var"); /* get a cligen variable from vector */ cli_output(stderr, "%s: %d\n", __FUNCTION__, cv_int32_get(myvar)); /* get int value */ - cli_output(stderr, "arg = %s\n", cv_string_get(arg)); /* get string value */ + cli_output(stderr, "arg = %s\n", cv_string_get(cvec_i(argv,0))); /* get string value */ /* Show eth0 interfaces config using XPATH */ if (xmldb_get(h, "candidate", "/interfaces/interface[name=eth0]", - 0, &xt, NULL, NULL) < 0) goto done; xml_print(stdout, xt); diff --git a/example/routing_cli.cli b/example/routing_cli.cli index 690fbebc..ec1eac7a 100644 --- a/example/routing_cli.cli +++ b/example/routing_cli.cli @@ -4,43 +4,41 @@ CLICON_PROMPT="%U@%H> "; CLICON_PLUGIN="routing_cli"; # Note, when switching to PT, change datamodel to only @datamodel -#set @datamodel:ietf-routing, cli_merge(); -#set @datamodel:ietf-ipv4-unicast-routing, cli_merge(); -set @datamodel:ietf-ip, cli_merge(); +set @datamodel:ietf-ip, cli_mergev(); #delete("Delete a configuration item") @datamodel:ietf-ipv4-unicast-routing, cli_del(); -delete("Delete a configuration item") @datamodel:ietf-ip, cli_del(); +delete("Delete a configuration item") @datamodel:ietf-ip, cli_delv(); -validate("Validate changes"), cli_validate(); -commit("Commit the changes"), cli_commit((int)0); # snapshot -quit("Quit Hello"), cli_quit(); -delete("Delete a configuration item") all("Delete whole candidate configuration"), delete_all("candidate"); +validate("Validate changes"), cli_validatev(); +commit("Commit the changes"), cli_commitv((int32)0); # snapshot +quit("Quit Hello"), cli_quitv(); +delete("Delete a configuration item") all("Delete whole candidate configuration"), delete_allv("candidate"); -no("Negate or remove") debug("Debugging parts of the system"), cli_debug((int)0); -debug("Debugging parts of the system"), cli_debug((int)1);{ - level("Set debug level: 1..n") ("Set debug level (0..n)"), cli_debug(); +no("Negate or remove") debug("Debugging parts of the system"), cli_debug_cliv((int32)0); +debug("Debugging parts of the system"), cli_debug_cliv((int32)1);{ + level("Set debug level: 1..n") ("Set debug level (0..n)"), cli_debug_cliv(); } -discard("Discard edits (rollback 0)"), discard_changes(); +discard("Discard edits (rollback 0)"), discard_changesv(); show("Show a particular state of the system"){ - xpath("Show configuration") ("XPATH expression"), show_conf_xpath("candidate"); - compare("Compare candidate and running databases"), compare_dbs((int32)0);{ - xml("Show comparison in xml"), compare_dbs((int32)0); - text("Show comparison in text"), compare_dbs((int32)1); + xpath("Show configuration") ("XPATH expression"), show_confv_xpath("candidate"); + compare("Compare candidate and running databases"), compare_dbsv((int32)0);{ + xml("Show comparison in xml"), compare_dbsv((int32)0); + text("Show comparison in text"), compare_dbsv((int32)1); } - configuration("Show configuration"), show_conf_as_text("candidate /");{ - xml("Show configuration as XML"), show_conf_as_xml("candidate /"); - netconf("Show configuration as netconf edit-config operation"), show_conf_as_netconf("candidate /"); - text("Show configuration as text"), show_conf_as_text("candidate /"); - cli("Show configuration as cli commands"), show_conf_as_cli("candidate /"); - json("Show configuration as cli commands"), show_conf_as_json("candidate /"); + configuration("Show configuration"), show_confv_as_text("candidate","/");{ + xml("Show configuration as XML"), show_confv_as_xml("candidate","/"); + netconf("Show configuration as netconf edit-config operation"), show_confv_as_netconf("candidate","/"); + text("Show configuration as text"), show_confv_as_text("candidate","/"); + cli("Show configuration as cli commands"), show_confv_as_cli("candidate","/"); + json("Show configuration as cli commands"), show_confv_as_json("candidate","/"); } } -save("Save candidate configuration to XML file") ("Filename (local filename)"), save_config_file("candidate filename"); -load("Load configuration from XML file") ("Filename (local filename)"),load_config_file("filename replace");{ - replace("Replace candidate with file contents"), load_config_file("filename replace"); - merge("Merge file with existent candidate"), load_config_file("filename merge"); +save("Save candidate configuration to XML file") ("Filename (local filename)"), save_config_filev("candidate","filename"); +load("Load configuration from XML file") ("Filename (local filename)"),load_config_filev("filename","replace");{ + replace("Replace candidate with file contents"), load_config_filev("filename","replace"); + merge("Merge file with existent candidate"), load_config_filev("filename","merge"); } -example("This is a comment") ("Just a random number"), mycallback("myarg"); \ No newline at end of file +example("This is a comment") ("Just a random number"), mycallback("myarg"); diff --git a/include/clixon_config.h.in b/include/clixon_config.h.in index 0c853a0b..c7651da8 100644 --- a/include/clixon_config.h.in +++ b/include/clixon_config.h.in @@ -37,6 +37,9 @@ /* Define to 1 if you have the `crypt' library (-lcrypt). */ #undef HAVE_LIBCRYPT +/* Define to 1 if you have the `curl' library (-lcurl). */ +#undef HAVE_LIBCURL + /* Define to 1 if you have the `dl' library (-ldl). */ #undef HAVE_LIBDL diff --git a/lib/clixon/clixon_xml.h b/lib/clixon/clixon_xml.h index e7d624c0..98b6b097 100644 --- a/lib/clixon/clixon_xml.h +++ b/lib/clixon/clixon_xml.h @@ -131,6 +131,7 @@ cxobj *xml_dup(cxobj *x0); int cxvec_dup(cxobj **vec0, size_t len0, cxobj ***vec1, size_t *len1); int cxvec_append(cxobj *x, cxobj ***vec, size_t *len); int xml_apply(cxobj *xn, enum cxobj_type type, xml_applyfn_t fn, void *arg); +int xml_apply0(cxobj *xn, enum cxobj_type type, xml_applyfn_t fn, void *arg); int xml_apply_ancestor(cxobj *xn, xml_applyfn_t fn, void *arg); int xml_body_parse(cxobj *xb, enum cv_type type, cg_var **cvp); diff --git a/lib/clixon/clixon_xml_db.h b/lib/clixon/clixon_xml_db.h index 245adc2d..e1c6d474 100644 --- a/lib/clixon/clixon_xml_db.h +++ b/lib/clixon/clixon_xml_db.h @@ -42,7 +42,7 @@ int yang2xmlkeyfmt(yang_stmt *ys, int inclkey, char **xkfmt); int xmlkeyfmt2key(char *xkfmt, cvec *cvv, char **xk); int xmlkeyfmt2xpath(char *xkfmt, cvec *cvv, char **xk); -int xmldb_get(clicon_handle h, char *db, char *xpath, int vector, +int xmldb_get(clicon_handle h, char *db, char *xpath, cxobj **xtop, cxobj ***xvec, size_t *xlen); int xmldb_put(clicon_handle h, char *db, cxobj *xt, enum operation_type op); int xmldb_put_tree(clicon_handle h, char *db, char *api_path, diff --git a/lib/clixon/clixon_yang.h b/lib/clixon/clixon_yang.h index 41596484..332b43dc 100644 --- a/lib/clixon/clixon_yang.h +++ b/lib/clixon/clixon_yang.h @@ -209,7 +209,6 @@ int yang_print(FILE *f, yang_node *yn, int marginal); int yang_parse(clicon_handle h, const char *yang_dir, const char *module, const char *revision, yang_spec *ysp); int yang_apply(yang_node *yn, yang_applyfn_t fn, void *arg); -yang_stmt *dbkey2yang(yang_node *yn, char *dbkey); yang_node *yang_xpath_abs(yang_node *yn, char *xpath); yang_node *yang_xpath(yang_node *yn, char *xpath); cg_var *ys_parse(yang_stmt *ys, enum cv_type cvtype); diff --git a/lib/src/clixon_xml.c b/lib/src/clixon_xml.c index 2d91e15f..2a1aa8f9 100644 --- a/lib/src/clixon_xml.c +++ b/lib/src/clixon_xml.c @@ -494,7 +494,12 @@ xml_new(char *name, return xn; } -/*! Create new xml node given a name, parent and spec. Free it with xml_free(). +/*! Create new xml node given a name, parent and spec. + * @param[in] name Name of new xml node + * @param[in] xp XML parent + * @param[in] spec Yang spec + * @retval NULL Error + * @retval x XML tree. Free with xml_free(). */ cxobj * xml_new_spec(char *name, @@ -1229,6 +1234,7 @@ cxvec_append(cxobj *x, * @endcode * @note do not delete or move around any children during this function * @note It does not apply fn to the root node,.. + * @see xml_apply0 including top object */ int xml_apply(cxobj *xn, @@ -1250,6 +1256,25 @@ xml_apply(cxobj *xn, return retval; } +/*! Apply a function call on top object and all xml node children recursively + * @see xml_apply not including top object + */ +int +xml_apply0(cxobj *xn, + enum cxobj_type type, + xml_applyfn_t fn, + void *arg) +{ + int retval = -1; + + if (fn(xn, arg) < 0) + goto done; + retval = xml_apply(xn, type, fn, arg); + done: + return retval; +} + + /*! Apply a function call recursively on all ancestors * Recursively traverse upwards to all ancestor nodes in a parse-tree and apply fn(arg) for * each object found. The function is called with the xml node and an diff --git a/lib/src/clixon_xml_db.c b/lib/src/clixon_xml_db.c index fc33f866..e9dfa23e 100644 --- a/lib/src/clixon_xml_db.c +++ b/lib/src/clixon_xml_db.c @@ -30,10 +30,59 @@ the terms of any one of the Apache License version 2 or the GPL. ***** END LICENSE BLOCK ***** - - * XML database - * TODO: xmldb_del: or dbxml_put_xkey delete */ +/* + * An xml database consists of key-value pairs for xml-trees. + * Each node in an xml-tree has a key and an optional value. + * The key (xmlkey) is constructed from the xml node name concatenated + * with its ancestors and any eventual list keys. + * A xmlkeyfmt is a help-structure used when accessing the XML database. + * It consists of an xmlkey but with the key fields replaced with wild-chars(%s) + * Example: /aaa/bbb/%s/%s/ccc + * Such an xmlkeyfmt can be obtained from a yang-statement by following + * its ancestors to the root module. If one of the ancestors is a list, + * a wildchar (%s) is inserted for each key. + * These xmlkeyfmt keys are saved and used in cli callbacks such as when + * modifying syntax (eg cli_merge/cli_delete) or when completing for sub-symbols + * In this case, the variables are set and the wildcards can be instantiated. + * An xml tree can then be formed that can be used to the xmldb_get() or + * xmldb_put() functions. + * The relations between the functions and formats are as follows: + * + * +-----------------+ +-----------------+ + * | yang-stmt | yang2xmlkeyfmt | xmlkeyfmt | xmlkeyfmt2xpath + * | list aa,leaf k | ----------------->| /aa=%s |----------------> + * +-----------------+ +-----------------+ + * | + * | xmlkeyfmt2key + * | k=17 + * v + * +-------------------+ +-----------------+ + * | xml-tree/cxobj | xmlkey2xml | xmlkey RFC3986| + * | 17| <------------- | /aa=17 | + * +-------------------+ +-----------------+ + * + * Alternative for xmlkeyfmt would be eg: + * RESTCONF: /interfaces/interface=%s/ipv4/address/ip=%s (used) + * XPATH: /interfaces/interface[name=%s]/ipv4/address/[ip=%s] + * + * Paths through the code (for coverage) + * cli_callback_generate +----------------+ + * cli_expand_var_generate | yang2xmlkeyfmt | + * yang -------------> | | + * +----------------+ + * xmldb_get_tree + * - compare_dbs + * - netconf + * - validate + * - from_client_save + * + * xmldb_get_vec + * - restconf + * - expand_dbvar + * - show_conf_xpath + */ + #ifdef HAVE_CONFIG_H #include "clixon_config.h" /* generated by config & autoconf */ #endif @@ -75,50 +124,13 @@ #include "clixon_xml_db_rpc.h" #include "clixon_xml_db.h" -/* - * An xml database consists of key-value pairs for xml-trees. - * Each node in an xml-tree has a key and an optional value. - * The key (xmlkey) is constructed from the xml node name concatenated - * with its ancestors and any eventual list keys. - * A xmlkeyfmt is a help-structure used when accessing the XML database. - * It consists of an xmlkey but with the key fields replaced with wild-chars(%s) - * Example: /aaa/bbb/%s/%s/ccc - * Such an xmlkeyfmt can be obtained from a yang-statement by following - * its ancestors to the root module. If one of the ancestors is a list, - * a wildchar (%s) is inserted for each key. - * These xmlkeyfmt keys are saved and used in cli callbacks such as when - * modifying syntax (eg cli_merge/cli_delete) or when completing for sub-symbols - * In this case, the variables are set and the wildcards can be instantiated. - * An xml tree can then be formed that can be used to the xmldb_get() or - * xmldb_put() functions. - * The relations between the functions and formats are as follows: - * - * +-----------------+ +-----------------+ - * | yang-stmt | yang2xmlkeyfmt | xmlkeyfmt | - * | list aa,leaf k | ----------------->| /aa/%s | - * | | | XXX: /aa=%s | - * +-----------------+ +-----------------+ - * | - * | xmlkeyfmt2key - * | k=17 - * v - * +-------------------+ +-----------------+ - * | xml-tree/cxobj | xmlkey2xml | xmlkey RFC3986| - * | 17| <------------- | /aa/17 | - * | | | XXX: /aa=17 | - * +-------------------+ +-----------------+ - * - * Alternative for xmlkeyfmt would be xpath: eg - * instead of /interfaces/interface/%s/ipv4/address/ip/%s - * you can have: /interfaces/interface[name=%s]/ipv4/address/[ip=%s] - */ /*! Construct an xml key format from yang statement using wildcards for keys * Recursively construct it to the top. * Example: * yang: container a -> list b -> key c -> leaf d * xpath: /a/b/%s/d * @param[in] ys Yang statement - * @param[in] inclkey If !inclkey then dont include key leaf + * @param[in] inclkey If inclkey then include key leaf (eg last leaf d in ex) * @param[out] cbuf keyfmt */ static int @@ -126,17 +138,17 @@ yang2xmlkeyfmt_1(yang_stmt *ys, int inclkey, cbuf *cb) { - yang_node *yn; + yang_node *yp; /* parent */ yang_stmt *ykey; int i; cvec *cvk = NULL; /* vector of index keys */ int retval = -1; - yn = ys->ys_parent; - if (yn != NULL && - yn->yn_keyword != Y_MODULE && - yn->yn_keyword != Y_SUBMODULE){ - if (yang2xmlkeyfmt_1((yang_stmt *)yn, 1, cb) < 0) + yp = ys->ys_parent; + if (yp != NULL && + yp->yn_keyword != Y_MODULE && + yp->yn_keyword != Y_SUBMODULE){ + if (yang2xmlkeyfmt_1((yang_stmt *)yp, 1, cb) < 0) goto done; } if (inclkey){ @@ -144,8 +156,8 @@ yang2xmlkeyfmt_1(yang_stmt *ys, cprintf(cb, "/%s", ys->ys_argument); } else{ - if (ys->ys_keyword == Y_LEAF && yn && yn->yn_keyword == Y_LIST){ - if (yang_key_match(yn, ys->ys_argument) == 0) + if (ys->ys_keyword == Y_LEAF && yp && yp->yn_keyword == Y_LIST){ + if (yang_key_match(yp, ys->ys_argument) == 0) cprintf(cb, "/%s", ys->ys_argument); /* Not if leaf and key */ } else @@ -163,12 +175,17 @@ yang2xmlkeyfmt_1(yang_stmt *ys, /* The value is a list of keys: [ ]* */ if ((cvk = yang_arg2cvec(ykey, " ")) == NULL) goto done; + if (cvec_len(cvk)) + cprintf(cb, "="); /* Iterate over individual keys */ - for (i=0; i list b -> key c -> leaf d - * xpath: /a/b/%s/d + * xpath: /a/b=%s/d * @param[in] ys Yang statement * @param[in] inclkey If !inclkey then dont include key leaf * @param[out] xkfmt XML key format. Needs to be freed after use. @@ -300,7 +317,8 @@ xmlkeyfmt2key(char *xkfmt, * Used to input xmldb_get() or xmldb_get_vec * Add .* in last %s position. * Example: - * xmlkeyfmt: /interface/%s/address/%s + * xmlkeyfmt: /interface/%s/address/%s OLDXXX + * xmlkeyfmt: /interface=%s/address=%s * cvv: name=eth0 * xmlkey: /interface/[name=eth0]/address * Example2: @@ -352,20 +370,11 @@ xmlkeyfmt2xpath(char *xkfmt, esc = 0; if (c!='s') continue; - if (j == cvec_len(cvv)) /* last element */ - skip++; - else{ - /* XXX: remove preceding '/' : - a/[x=y] -> a[x=y] */ - if ((str = strdup(cbuf_get(cb))) == NULL){ - clicon_err(OE_UNIX, errno, "strdup"); - goto done; - } - str[strlen(str)-1] = '\0'; - cbuf_reset(cb); - cprintf(cb, "%s", str); - free(str); + if (j == cvec_len(cvv)) /* last element */ + //skip++; + ; + else{ cv = cvec_i(cvv, j++); if ((str = cv2str_dup(cv)) == NULL){ clicon_err(OE_UNIX, errno, "cv2str_dup"); @@ -382,7 +391,10 @@ xmlkeyfmt2xpath(char *xkfmt, if (skip) skip=0; else - cprintf(cb, "%c", c); + if ((c == '=' || c == ',') && xkfmt[i+1]=='%') + ; /* skip */ + else + cprintf(cb, "%c", c); } } if ((*xk = strdup4(cbuf_get(cb))) == NULL){ @@ -504,6 +516,7 @@ append_listkeys(cbuf *ckey, cvec *cvk = NULL; /* vector of index keys */ char *keyname; char *bodyenc; + int i=0; if ((ykey = yang_find((yang_node*)ys, Y_KEY, NULL)) == NULL){ clicon_err(OE_XML, errno, "%s: List statement \"%s\" has no key", @@ -526,7 +539,11 @@ append_listkeys(cbuf *ckey, clicon_err(OE_UNIX, errno, "curl_easy_escape"); goto done; } - cprintf(ckey, "/%s", bodyenc); + if (i++) + cprintf(ckey, ","); + else + cprintf(ckey, "="); + cprintf(ckey, "%s", bodyenc); free(bodyenc); bodyenc = NULL; } retval = 0; @@ -622,8 +639,12 @@ get(char *dbname, int retval = -1; char **vec; int nvec; + char **valvec; + int nvalvec; int i; + int j; char *name; + char *restval; yang_stmt *y; cxobj *x; cxobj *xc; @@ -638,7 +659,7 @@ get(char *dbname, // clicon_debug(1, "%s xkey:%s val:%s", __FUNCTION__, xk, val); x = xt; - if (xk == NULL || *xk!='/'){ + if (xk == NULL || *xk!='/'){ clicon_err(OE_DB, 0, "Invalid key: %s", xk); goto done; } @@ -654,7 +675,11 @@ get(char *dbname, } i = 1; while (i name:x restval=1,2 */ + if ((restval = index(name, '=')) != NULL){ + *restval = '\0'; + restval++; + } if (i == 1){ /* spec->module->node */ if ((y = yang_find_topnode(ys, name)) == NULL){ clicon_err(OE_UNIX, errno, "No yang node found: %s", name); @@ -672,14 +697,12 @@ get(char *dbname, * If xml element is a leaf-list, then the next element is expected to * be a value */ - i++; - if (i>=nvec){ - clicon_err(OE_XML, errno, "Leaf-list %s without argument", name); + if ((argdec = curl_easy_unescape(NULL, restval, 0, NULL)) == NULL){ + clicon_err(OE_UNIX, errno, "curl_easy_escape"); goto done; } - arg = vec[i]; if ((xc = xml_find(x, name))==NULL || - (xb = xml_find(xc, arg))==NULL){ + (xb = xml_find(xc, argdec))==NULL){ if ((xc = xml_new_spec(name, x, y)) == NULL) goto done; /* Assume body is created at end of function */ @@ -706,17 +729,17 @@ get(char *dbname, cvi = NULL; /* Iterate over individual yang keys */ cprintf(cb, "%s", name); - if (cvec_len(cvk) > 1 && i+cvec_len(cvk) >= nvec-1){ + if ((valvec = clicon_strsplit(restval, ",", &nvalvec, __FUNCTION__)) == NULL) + goto done; + if (cvec_len(cvk)!=nvalvec){ retval = 0; goto done; } + j = 0; while ((cvi = cvec_each(cvk, cvi)) != NULL){ - i++; - if (i>=nvec){ - clicon_err(OE_XML, errno, "List %s without argument", name); - goto done; - } - arg = vec[i]; + if (j>=nvalvec) + break; + arg = valvec[j++]; if ((argdec = curl_easy_unescape(NULL, arg, 0, NULL)) == NULL){ clicon_err(OE_UNIX, errno, "curl_easy_escape"); goto done; @@ -729,12 +752,14 @@ get(char *dbname, if ((xc = xml_new_spec(name, x, y)) == NULL) goto done; cvi = NULL; - i -= cvec_len(cvk); + // i -= cvec_len(cvk); /* Iterate over individual yang keys */ + j=0; while ((cvi = cvec_each(cvk, cvi)) != NULL) { + if (j>=nvalvec) + break; + arg = valvec[j++]; keyname = cv_string_get(cvi); - i++; - arg = vec[i]; if ((argdec = curl_easy_unescape(NULL, arg, 0, NULL)) == NULL){ clicon_err(OE_UNIX, errno, "curl_easy_escape"); goto done; @@ -908,96 +933,14 @@ xml_order(cxobj *x, return retval; } -/*! Get content of database using xpath. return a single tree - * The function returns a minimal tree that includes all sub-trees that match - * xpath. - * @param[in] dbname Name of database to search in (filename including dir path - * @param[in] xpath String with XPATH syntax (or NULL for all) - * @param[in] yspec Yang specification - * @param[in] vector If set, return list of results in xvec - * @param[out] xtop XML tree. Freed by xml_free() - * @retval 0 OK - * @retval -1 Error - */ -static int -xmldb_get_tree(char *dbname, - char *xpath, - yang_spec *yspec, - cxobj **xtop) -{ - int retval = -1; - int i; - int npairs; - struct db_pair *pairs; - cxobj *xt = NULL; - cxobj **xvec=NULL; - size_t len; - - /* Read in complete database (this can be optimized) */ - if ((npairs = db_regexp(dbname, "", __FUNCTION__, &pairs, 0)) < 0) - goto done; - if ((xt = xml_new("clicon", NULL)) == NULL) - goto done; - for (i = 0; i < npairs; i++) - clicon_debug(2, "%s %s", pairs[i].dp_key, pairs[i].dp_val?pairs[i].dp_val:""); - // clicon_debug(1, "%s npairs:%d", __FUNCTION__, npairs); - for (i = 0; i < npairs; i++) { - if (get(dbname, - yspec, - pairs[i].dp_key, /* xml key */ - pairs[i].dp_val, /* may be NULL */ - xt) < 0) - goto done; - } - /* - * 1. Read whole tree, call xpath, then retrace 'back' - * 2. only read necessary parts,...? - */ - if (xpath){ - if (xpath_vec(xt, xpath, &xvec, &len) < 0) - goto done; - if (xvec != NULL){ - /* Prune everything except nodes in xvec and how to get there - * Alt: Create a new subtree from xt with that property,...(no) - */ - for (i=0; iys_argument); if (debug){ @@ -1235,7 +1170,11 @@ put(char *dbname, goto done; break; case Y_LEAF_LIST: - cprintf(cbxk, "/%s", body); + if ((bodyenc = curl_easy_escape(NULL, body, 0)) == NULL){ + clicon_err(OE_UNIX, errno, "curl_easy_escape"); + goto done; + } + cprintf(cbxk, "=%s", bodyenc); break; default: break; @@ -1283,6 +1222,8 @@ put(char *dbname, done: if (cbxk) cbuf_free(cbxk); + if (bodyenc) + free(bodyenc); return retval; } @@ -1580,7 +1521,16 @@ xmldb_put_tree(clicon_handle h, return xmldb_put_tree_local(h, db, api_path, xt, op); } -/*! Local variant of xmldb_put_xkey */ +/*! Modify database provided an XML database key and an operation + * @param[in] h CLICON handle + * @param[in] db Database name + * @param[in] xk XML Key, eg /aa/bb/17/name + * @param[in] val Key value, eg "17" + * @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc + * @retval 0 OK + * @retval -1 Error + * Local variant of xmldb_put_xkey + */ static int xmldb_put_xkey_local(clicon_handle h, char *db, @@ -1594,8 +1544,12 @@ xmldb_put_xkey_local(clicon_handle h, yang_stmt *ykey; char **vec; int nvec; + char **valvec; + int nvalvec; int i; + int j; char *name; + char *restval; cg_var *cvi; cvec *cvk = NULL; /* vector of index keys */ char *val2 = NULL; @@ -1632,11 +1586,15 @@ xmldb_put_xkey_local(clicon_handle h, } i = 1; while (i name:x restval=1,2 */ + if ((restval = index(name, '=')) != NULL){ + *restval = '\0'; + restval++; + } if (i==1){ - if (!strlen(name) && (op==OP_DELETE || op == OP_REMOVE)){ + if (strlen(name)==0 && (op==OP_DELETE || op == OP_REMOVE)){ /* Special handling of "/" */ - cprintf(ckey, "/%s", name); + cprintf(ckey, "/"); break; } else @@ -1660,13 +1618,7 @@ xmldb_put_xkey_local(clicon_handle h, i++; switch (y->ys_keyword){ case Y_LEAF_LIST: - val2 = vec[i]; - if (i>=nvec){ - clicon_err(OE_XML, errno, "Leaf-list %s without argument", name); - goto done; - } - i++; - cprintf(ckey, "/%s", val2); + cprintf(ckey, "=%s", restval); break; case Y_LIST: if ((ykey = yang_find((yang_node*)y, Y_KEY, NULL)) == NULL){ @@ -1677,16 +1629,25 @@ xmldb_put_xkey_local(clicon_handle h, /* The value is a list of keys: [ ]* */ if ((cvk = yang_arg2cvec(ykey, " ")) == NULL) goto done; + if ((valvec = clicon_strsplit(restval, ",", &nvalvec, __FUNCTION__)) == NULL) + goto done; + + if (cvec_len(cvk) != nvalvec){ + clicon_err(OE_XML, errno, "List %s key length mismatch", name); + goto done; + } cvi = NULL; /* Iterate over individual yang keys */ + j = 0; while ((cvi = cvec_each(cvk, cvi)) != NULL) { keyname = cv_string_get(cvi); - val2 = vec[i++]; - if (i>nvec){ /* XXX >= ? */ - clicon_err(OE_XML, errno, "List %s without argument", name); - goto done; - } - cprintf(ckey, "/%s", val2); + if (j) + cprintf(ckey, ","); + else + cprintf(ckey, "="); + val2 = valvec[j++]; + + cprintf(ckey, "%s", val2); cbuf_reset(csubkey); cprintf(csubkey, "%s/%s", cbuf_get(ckey), keyname); if (op == OP_MERGE || op == OP_REPLACE || op == OP_CREATE) @@ -2124,7 +2085,7 @@ main(int argc, char **argv) if (argc < 5) usage(argv[0]); xpath = argc>5?argv[5]:NULL; - if (xmldb_get(h, db, xpath, 0, &xt, NULL, NULL) < 0) + if (xmldb_get(h, db, xpath, &xt, NULL, NULL) < 0) goto done; clicon_xml2file(stdout, xt, 0, 1); } diff --git a/lib/src/clixon_xml_db_rpc.c b/lib/src/clixon_xml_db_rpc.c index b183ba0f..65121549 100644 --- a/lib/src/clixon_xml_db_rpc.c +++ b/lib/src/clixon_xml_db_rpc.c @@ -243,7 +243,6 @@ int xmldb_get_rpc(clicon_handle h, char *db, char *xpath, - int vector, cxobj **xtop, cxobj ***xvec, size_t *xlen) @@ -263,8 +262,6 @@ xmldb_get_rpc(clicon_handle h, cprintf(cb, "<%s/>", db); if (xpath) cprintf(cb, "%s", xpath); - if (vector) - cprintf(cb, ""); cprintf(cb, "]]>]]>"); if (xmldb_rpc(h, cbuf_get(cb), @@ -273,7 +270,7 @@ xmldb_get_rpc(clicon_handle h, goto done; if (clicon_xml_parse_str(rb, &xt) < 0) goto done; - if (vector){ + if (xvec){ i=0; if ((*xvec = calloc(xml_child_nr(xt), sizeof(cxobj*))) ==NULL){ clicon_err(OE_UNIX, errno, "calloc"); diff --git a/lib/src/clixon_xml_db_rpc.h b/lib/src/clixon_xml_db_rpc.h index fe98ecbe..561c4935 100644 --- a/lib/src/clixon_xml_db_rpc.h +++ b/lib/src/clixon_xml_db_rpc.h @@ -39,7 +39,7 @@ * Prototypes */ int xmldb_get_rpc(clicon_handle h, char *db, - char *xpath, int vector, + char *xpath, cxobj **xtop, cxobj ***xvec, size_t *xlen); int xmldb_put_rpc(clicon_handle h, char *db, cxobj *xt, enum operation_type op); int xmldb_put_xkey_rpc(clicon_handle h, char *db, char *xk, char *val, diff --git a/lib/src/clixon_xsl.c b/lib/src/clixon_xsl.c index 698a53d4..ff1334c0 100644 --- a/lib/src/clixon_xsl.c +++ b/lib/src/clixon_xsl.c @@ -938,7 +938,7 @@ xpath_each(cxobj *cxtop, * cxobj **vec; * size_t veclen; * if (xpath_vec(cxtop, "//symbol/foo", &vec, &veclen) < 0) - * got err; + * goto err; * for (i=0; iyn_keyword == Y_LIST){ - ret = parse_int64(key, &i, NULL); - if (ret != 1){ - clicon_err(OE_YANG, errno, "strtol"); - goto done; - } - if (nvec == 1) - return (yang_stmt*)yn; - vec++; - nvec--; - key = vec[0]; - } - if ((ys = yang_find_syntax(yn, key)) == NULL) - goto done; - if (nvec == 1) - return ys; - return yang_dbkey_vec((yang_node*)ys, vec+1, nvec-1); - done: - return NULL; -} - -/*! Given a dbkey (eg a.b.0) recursively find matching yang specification - * - * e.g. a.0 matches the db_spec corresponding to a[]. - * Input args: - * @param[in] yn top-of yang tree where to start finding - * @param[in] dbkey database key to match in yang spec tree - * @see yang_dbkey_get - */ -yang_stmt * -dbkey2yang(yang_node *yn, - char *dbkey) -{ - char **vec; - int nvec; - yang_stmt *ys; - - /* Split key into parts, eg "a.0.b" -> "a" "0" "b" */ - if ((vec = clicon_strsplit(dbkey, ".", &nvec, __FUNCTION__)) == NULL){ - clicon_err(OE_YANG, errno, "%s: strsplit", __FUNCTION__); - return NULL; - } - ys = yang_dbkey_vec(yn, vec, nvec); - unchunk_group(__FUNCTION__); - return ys; -} - /*! All the work for yang_xpath. Ignore prefixes, see _abs */ static yang_node *