diff --git a/CHANGELOG.md b/CHANGELOG.md index 42152378..e528ef5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,61 @@ -# Clixon CHANGELOG +# Clixon Changelog +## 3.3.3 (Upcoming) + +### Known issues +### Major changes: +* Clixon can now be compiled and run on Apple Darwin. + +* Performance improvements + * Added xml hash lookup instead of linear search for better performance of large lists. To disable, undefine XML_CHILD_HASH in clixon_custom.h + * netconf client was limited to 8K byte messages. Now limit is 2^32 bytes + +* XML and YANG-based configuration file. + * New configuration files have .xml suffix, old have .conf. Old config files till work for backward compatibility. + * The yang model is yang/clixon-config.yang. + * A migration utility is clixon_cli -x to print new format, eg: +``` +clixon_cli -f /usr/local/etc/routing.conf -1x +``` + +* Introducing backend daemon startup modes. The flags -IRCr and option CLICON_USE_STARTUP_CONFIG are replaced with command-line option -s and option CLICON_STARTUP_MODE. You need to replace the starting of clixon_backend as follows: + * -I replace with -s "init" (or use of CLICON_STARTUP_MODE option) + * -CIr replace with -s "running" + * (no-option) replace with -s "none" + * CLICON_USE_STARTUP_CONFIG=1 replace with -s "startup" +Backward compatibility is enabled by defining BACKEND_STARTUP_BACKWARD_COMPAT in include/clixon_custom.h + +### Minor changes: +* Disabled key-value datastore. Enable with --with-keyvalue +* Removed mandatory requirements for BACKEND, NETCONF, RESTCONF and CLI dirs. * When user callbacks such as statedata() call returns -1, clixon_backend no longer silently exits. Instead a log is printed and an RPC error is returned. +* Added Floating point and negative number support to JSON +* Restconf: http cookie sent as attribute in rpc restconf_post operations to backend. +* Added option CLICON_CLISPEC_FILE as complement to CLICON_CLISPEC_DIR to + specify single CLI specification file, not only directory containing files. + +* Replaced the following cli_ functions with their original cligen_functions: + cli_exiting, cli_set_exiting, cli_comment, + cli_set_comment, cli_tree_add, cli_tree_active, + cli_tree_active_set, cli_tree. + +* Added a format parameter to clicon_rpc_generate_error() and changed error + printouts for backend errors, such as commit and validate. Example of the + new format: + +``` +> commit +Sep 27 18:11:58: Commit failed. Edit and try again or discard changes: +protocol invalid-value Missing mandatory variable: type +``` + +* Added event_poll function. * Support for non-line scrolling in CLI, eg wrap lines. Set with: CLICON_CLI_LINESCROLLING 0 -## 3.3.2 Aug 27 2017 +## 3.3.2 (Aug 27 2017) ### Known issues * Please use text datastore, key-value datastore no up-to-date @@ -19,6 +68,7 @@ * If you use direct netconf get or get-config calls, you may need to handle the return XML differently. * RESTCONF and CLI is not affected. * Example: + ``` Query: @@ -42,6 +92,7 @@ * Added support for yang presence and no-presence containers. Previous default was "presence". * Empty containers will be removed unless you have used the "presence" yang declaration. * Example YANG without presence: + ``` container nopresence { @@ -50,7 +101,9 @@ } } ``` + If you submit "nopresence" without a leaf, it will automatically be removed: + ``` # removed # not removed @@ -81,6 +134,7 @@ If you submit "nopresence" without a leaf, it will automatically be removed: * Validation for leafref forward and backward references; * CLI completion for generated cli leafrefs for both absolute and relative paths. * Example, relative path: + ``` leaf ifname { type leafref { @@ -94,7 +148,7 @@ If you submit "nopresence" without a leaf, it will automatically be removed: * Restconf GET will return state data also, if defined. * You need to define state data in a backend callback. See the example and documentation for more details. -### Minor changes: +### Minor Changes * Added xpath support for predicate: current(), eg /interface[name=current()/../name] * Added prefix parsing of xpath, allowing eg /p:x/p:y, but prefix ignored. * Corrected Yang union CLI generation and type validation. Recursive unions did not work. @@ -110,7 +164,7 @@ If you submit "nopresence" without a leaf, it will automatically be removed: * Removed vector return values from xmldb_get() * Generalized yang type resolution to all included (sub)modules not just the topmost -## 3.3.1 June 7 2017 +## 3.3.1 (June 7 2017) * Fixed yang leafref cli completion for absolute paths. @@ -118,9 +172,7 @@ If you submit "nopresence" without a leaf, it will automatically be removed: * Strings in xmldb_put not properly encoded, eg eth/0 became eth.00000 -## 3.3.0 - -May 2017 +## 3.3.0 (May 2017) * Datastore text module is now default. @@ -161,7 +213,7 @@ May 2017 Instead use the rpc calls in clixon_proto_client.[ch] In clients (eg cli/netconf) replace xmldb_get() in client code with clicon_rpc_get_config(). - If you use the vector arguments of xmldb_get(), replace as follows: + pIf you use the vector arguments of xmldb_get(), replace as follows: xmldb_get(h, db, api_path, &xt, &xvec, &xlen); with clicon_rpc_get_config(h, dbstr, api_path, &xt); @@ -211,9 +263,13 @@ May 2017 `load("Comment") ,load_config_file("filename", "replace");` If you write your own, you need to change the callback signature from; +``` int cli_callback(clicon_handle h, cvec *vars, cg_var *arg) +``` to: +``` int cli_callback(clicon_handle h, cvec *vars, cvec *argv) +``` and rewrite the code to handle argv instead of arg. These are the system functions affected: cli_set, cli_merge, cli_del, cli_debug_backend, cli_set_mode, @@ -225,7 +281,9 @@ May 2017 * Added union type check for non-cli (eg xml) input * Empty yang type. Relaxed yang types for unions, eg two strings with different length. -Dec 2016: Dual license: both GPLv3 and APLv2 +## (Dec 2016) +* Dual license: both GPLv3 and APLv2 -Feb 2016: Forked new clixon repository from clicon +## (Feb 2016) +* Forked new clixon repository from clicon diff --git a/Makefile.in b/Makefile.in index 3447442c..d467bcc6 100644 --- a/Makefile.in +++ b/Makefile.in @@ -52,7 +52,7 @@ LIBS = @LIBS@ INCLUDES = -I. -I@srcdir@ @INCLUDES@ SHELL = /bin/sh -SUBDIRS = lib apps include etc datastore +SUBDIRS = lib apps include etc datastore yang .PHONY: doc all clean depend $(SUBDIRS) install loc TAGS .config.status docker diff --git a/apps/backend/Makefile.in b/apps/backend/Makefile.in index 002a0172..f04dacdc 100644 --- a/apps/backend/Makefile.in +++ b/apps/backend/Makefile.in @@ -47,6 +47,7 @@ libexecdir = @libexecdir@ localstatedir = @localstatedir@ sysconfdir = @sysconfdir@ includedir = @includedir@ +HOST_VENDOR = @host_vendor@ SH_SUFFIX = @SH_SUFFIX@ CLIXON_MAJOR = @CLIXON_VERSION_MAJOR@ @@ -61,7 +62,7 @@ CLIXON_BACKEND_SYSDIR = $(libdir)/clixon/plugins/backend # even though it may exist in $(libdir). But the new version may not have been installed yet. LIBDEPS = $(top_srcdir)/lib/src/$(CLIXON_LIB) -LIBS = -L$(top_srcdir)/lib/src @LIBS@ -l:$(CLIXON_LIB) -lpthread +LIBS = -L$(top_srcdir)/lib/src @LIBS@ $(top_srcdir)/lib/src/$(CLIXON_LIB) -lpthread CPPFLAGS = @CPPFLAGS@ -fPIC INCLUDES = -I. -I$(top_srcdir)/lib/src -I$(top_srcdir)/lib -I$(top_srcdir)/include -I$(top_srcdir) @INCLUDES@ @@ -125,13 +126,17 @@ test.c : echo "int main(){}" > $@ test: test.c $(LIBOBJ) - $(CC) $(INCLUDES) $(LDFLAGS) $< $(LIBOBJ) -L. -l:$(MYLIB) $(LIBS) -o $@ + $(CC) $(INCLUDES) $(LDFLAGS) $< $(LIBOBJ) -L. $(MYLIB) $(LIBS) -o $@ $(APPL) : $(APPOBJ) $(MYLIBLINK) $(LIBDEPS) - $(CC) $(LDFLAGS) $(APPOBJ) $(OBJS) -L. -l:$(MYLIB) $(LIBS) -o $@ + $(CC) $(LDFLAGS) $(APPOBJ) $(OBJS) -L. $(MYLIB) $(LIBS) -o $@ $(MYLIB): $(LIBOBJ) +ifeq ($(HOST_VENDOR),apple) + $(CC) $(LDFLAGS) -shared -undefined dynamic_lookup -o $@ -lc $(LIBOBJ) +else $(CC) $(LDFLAGS) -shared -Wl,-soname,$(MYLIBSO) -o $@ -lc $(LIBOBJ) -Wl,-soname=$(MYLIBSO) +endif # link-name is needed for application linking, eg for clixon_cli and clixon_backend $(MYLIBLINK) : $(MYLIB) diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index 9f0c805e..0916cff4 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -1011,8 +1011,14 @@ from_client_msg(clicon_handle h, } } reply: - assert(cbuf_len(cbret)); - clicon_debug(1, "%s %s", __FUNCTION__, cbuf_get(cbret)); + if (cbuf_len(cbret) == 0) + cprintf(cbret, "" + "operation-failed" + "rpc" + "error" + "Internal error %s" + "",clicon_err_reason); + clicon_debug(1, "%s cbret:%s", __FUNCTION__, cbuf_get(cbret)); if (send_msg_reply(ce->ce_s, cbuf_get(cbret), cbuf_len(cbret)+1) < 0){ switch (errno){ case EPIPE: @@ -1041,6 +1047,7 @@ from_client_msg(clicon_handle h, if (retval < 0 && clicon_errno < 0) clicon_log(LOG_NOTICE, "%s: Internal error: No clicon_err call on error (message: %s)", __FUNCTION__, name?name:""); + // clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); return retval;// -1 here terminates backend } @@ -1073,5 +1080,6 @@ from_client(int s, done: if (msg) free(msg); + clicon_debug(1, "%s retval=%d", __FUNCTION__, retval); return retval; /* -1 here terminates backend */ } diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c index ccfae5cc..efa8a8f0 100644 --- a/apps/backend/backend_main.c +++ b/apps/backend/backend_main.c @@ -73,7 +73,11 @@ #include "backend_handle.h" /* Command line options to be passed to getopt(3) */ -#define BACKEND_OPTS "hD:f:d:b:Fzu:P:1IRCc:rg:py:x:" +#ifdef BACKEND_STARTUP_BACKWARD_COMPAT +#define BACKEND_OPTS "hD:f:d:b:Fzu:P:1s:c:IRCrg:py:x:" /* substitute s: for IRCc:r */ +#else +#define BACKEND_OPTS "hD:f:d:b:Fzu:P:1s:c:g:py:x:" /* substitute s: for IRCc:r */ +#endif /*! Terminate. Cannot use h after this */ static int @@ -114,8 +118,7 @@ backend_sig_term(int arg) clicon_exit_set(); /* checked in event_loop() */ } -/* - * usage +/*! usage */ static void usage(char *argv0, clicon_handle h) @@ -128,20 +131,23 @@ usage(char *argv0, clicon_handle h) fprintf(stderr, "usage:%s\n" "where options are\n" " -h\t\tHelp\n" - " -D \tdebug\n" + " -D \tDebug level\n" " -f \tCLICON config file (mandatory)\n" " -d \tSpecify backend plugin directory (default: %s)\n" " -b \tSpecify XMLDB database directory\n" " -z\t\tKill other config daemon and exit\n" - " -F\t\tforeground\n" - " -1\t\tonce (dont wait for events)\n" - " -u \tconfig UNIX domain path / ip address (default: %s)\n" + " -F\t\tRun in foreground, do not run as daemon\n" + " -1\t\tRun once and then quit (dont wait for events)\n" + " -u \tConfig UNIX domain path / ip address (default: %s)\n" " -P \tPid filename (default: %s)\n" + " -s \tSpecify backend startup mode: none|startup|running|init (replaces -IRCr\n" + " -c \tLoad extra xml configuration, but don't commit.\n" +#ifdef BACKEND_STARTUP_BACKWARD_COMPAT " -I\t\tInitialize running state database\n" " -R\t\tCall plugin_reset() in plugins to reset system state in running db (use with -I)\n" " -C\t\tCall plugin_reset() in plugins to reset system state in candidate db (use with -I)\n" - " -c \tLoad specified application config.\n" " -r\t\tReload running database\n" +#endif /* BACKEND_STARTUP_BACKWARD_COMPAT */ " -p \t\tPrint database yang specification\n" " -g \tClient membership required to this group (default: %s)\n" " -y \tOverride yang spec file (dont include .yang suffix)\n" @@ -166,69 +172,26 @@ db_reset(clicon_handle h, return 0; } -/*! Initialize running-config from file application configuration - * - * @param[in] h clicon handle - * @param[in] app_config_file clicon application configuration file - * @param[in] running_db Name of running db - * @retval 0 OK - * @retval -1 Error. clicon_err set +/*! Merge db1 into db2 without commit */ static int -rundb_main(clicon_handle h, - char *app_config_file) +db_merge(clicon_handle h, + const char *db1, + const char *db2) { - int retval = -1; - int fd = -1; - cxobj *xt = NULL; - cxobj *xn; + int retval = -1; + cxobj *xt = NULL; - if (xmldb_create(h, "tmp") < 0) + /* Get data as xml from db1 */ + if (xmldb_get(h, (char*)db1, NULL, 1, &xt) < 0) goto done; - if (xmldb_copy(h, "running", "tmp") < 0){ - clicon_err(OE_UNIX, errno, "file copy"); - goto done; - } - if ((fd = open(app_config_file, O_RDONLY)) < 0){ - clicon_err(OE_UNIX, errno, "open(%s)", app_config_file); - goto done; - } - if (clicon_xml_parse_file(fd, &xt, "") < 0) - goto done; - if ((xn = xml_child_i(xt, 0)) != NULL) - if (xmldb_put(h, "tmp", OP_MERGE, xn) < 0) - goto done; - if (candidate_commit(h, "tmp") < 0) - goto done; - if (xmldb_delete(h, "tmp") < 0) + /* Merge xml into db2. WIthout commit */ + if (xmldb_put(h, (char*)db2, OP_MERGE, xt) < 0) goto done; retval = 0; -done: + done: if (xt) xml_free(xt); - if (fd != -1) - close(fd); - return retval; -} - -static int -candb_reset(clicon_handle h) -{ - int retval = -1; - - if (xmldb_copy(h, "running", "tmp") < 0){ - clicon_err(OE_UNIX, errno, "file copy"); - goto done; - } - /* Request plugins to reset system state, eg initiate running from system - * -R - */ - if (plugin_reset_state(h, "tmp") < 0) - goto done; - if (candidate_commit(h, "tmp") < 0) - goto done; - retval = 0; - done: return retval; } @@ -291,6 +254,337 @@ backend_log_cb(int level, return retval; } +/*! Call plugin_start with -- user options */ +static int +plugin_start_useroptions(clicon_handle h, + char *argv0, + int argc, + char **argv) +{ + char *tmp; + + tmp = *(argv-1); + *(argv-1) = argv0; + if (plugin_start_argv(h, argc+1, argv-1) < 0) + return -1; + *(argv-1) = tmp; + return 0; +} + +#ifdef BACKEND_STARTUP_BACKWARD_COMPAT +/*! Initialize running-config from file application configuration + * + * @param[in] h clicon handle + * @param[in] extraxml_file clicon application configuration file + * @param[in] running_db Name of running db + * @retval 0 OK + * @retval -1 Error. clicon_err set + */ +static int +rundb_main(clicon_handle h, + char *extraxml_file) +{ + int retval = -1; + int fd = -1; + cxobj *xt = NULL; + cxobj *xn; + + if (xmldb_create(h, "tmp") < 0) + goto done; + if (xmldb_copy(h, "running", "tmp") < 0) + goto done; + if ((fd = open(extraxml_file, O_RDONLY)) < 0){ + clicon_err(OE_UNIX, errno, "open(%s)", extraxml_file); + goto done; + } + if (clicon_xml_parse_file(fd, &xt, "") < 0) + goto done; + if ((xn = xml_child_i(xt, 0)) != NULL) + if (xmldb_put(h, "tmp", OP_MERGE, xn) < 0) + goto done; + if (candidate_commit(h, "tmp") < 0) + goto done; + if (xmldb_delete(h, "tmp") < 0) + goto done; + retval = 0; +done: + if (xt) + xml_free(xt); + if (fd != -1) + close(fd); + return retval; +} + +static int +candb_reset(clicon_handle h) +{ + int retval = -1; + + if (xmldb_copy(h, "running", "tmp") < 0) + goto done; + /* Request plugins to reset system state, eg initiate running from system + * -R + */ + if (plugin_reset_state(h, "tmp") < 0) + goto done; + if (candidate_commit(h, "tmp") < 0) + goto done; + retval = 0; + done: + return retval; +} + +/*! Legacy (old-style) startup mode where flags -IRCcr was used + */ +static int +fragmented_startup_mode(clicon_handle h, + char *argv0, + int argc, + char *argv[], + int reload_running, + int init_rundb, + int reset_state_candidate, + int reset_state_running, + char *extraxml_file) +{ + int retval = -1; + + /* First check for startup config */ + if (clicon_option_int(h, "CLICON_USE_STARTUP_CONFIG") > 0){ + if (xmldb_exists(h, "startup") == 1){ + /* copy startup config -> running */ + if (xmldb_copy(h, "startup", "running") < 0) + goto done; + } + else + if (db_reset(h, "running") < 0) + goto done; + if (xmldb_create(h, "candidate") < 0) + goto done; + if (xmldb_copy(h, "running", "candidate") < 0) + goto done; + } + /* If running exists and reload_running set, make a copy to candidate */ + if (reload_running){ + if (xmldb_exists(h, "running") != 1){ + clicon_log(LOG_NOTICE, "%s: -r (reload running) option given but no running_db found, proceeding without", __PROGRAM__); + reload_running = 0; /* void it, so we dont commit candidate below */ + } + else + if (xmldb_copy(h, "running", "candidate") < 0) + goto done; + } + /* Init running db + * -I or if it isnt there + */ + if (init_rundb || xmldb_exists(h, "running") != 1){ + if (db_reset(h, "running") < 0) + goto done; + } + /* If candidate does not exist, create it from running */ + if (xmldb_exists(h, "candidate") != 1){ + if (xmldb_create(h, "candidate") < 0) + goto done; + if (xmldb_copy(h, "running", "candidate") < 0) + goto done; + } + + /* Load plugins and call plugin_init() */ + if (plugin_initiate(h) != 0) + goto done; + + if (reset_state_candidate){ + if (candb_reset(h) < 0) + goto done; + } + else + if (reset_state_running){ + if (plugin_reset_state(h, "running") < 0) + goto done; + } + + if (plugin_start_useroptions(h, argv0, argc, argv) <0) + goto done; + + if (reload_running){ + /* This could be a failed validation, and we should not fail for that */ + (void)candidate_commit(h, "candidate"); + } + + /* Have we specified a config file to load? eg + * -c [] + */ + if (extraxml_file) + if (rundb_main(h, extraxml_file) < 0) + goto done; + /* Initiate the shared candidate. */ + if (xmldb_copy(h, "running", "candidate") < 0) + goto done; + retval = 0; + done: + return retval; +} +#endif /* BACKEND_STARTUP_BACKWARD_COMPAT */ + +/*! Merge xml in filename into database + */ +static int +load_extraxml(clicon_handle h, + char *filename, + const char *db) +{ + int retval = -1; + cxobj *xt = NULL; + int fd = -1; + + if (filename == NULL) + return 0; + if ((fd = open(filename, O_RDONLY)) < 0){ + clicon_err(OE_UNIX, errno, "open(%s)", filename); + goto done; + } + if (clicon_xml_parse_file(fd, &xt, "") < 0) + goto done; + /* Replace parent w first child */ + if (xml_rootchild(xt, 0, &xt) < 0) + goto done; + /* Merge user reset state */ + if (xmldb_put(h, (char*)db, OP_MERGE, xt) < 0) + goto done; + retval = 0; + done: + if (fd != -1) + close(fd); + if (xt) + xml_free(xt); + return retval; +} + +/*! Clixon none startup modes Do not touch running state + */ +static int +startup_mode_none(clicon_handle h) +{ + int retval = -1; + + /* If it is not there, create candidate from running */ + if (xmldb_exists(h, "candidate") != 1) + if (xmldb_copy(h, "running", "candidate") < 0) + goto done; + /* Load plugins and call plugin_init() */ + if (plugin_initiate(h) != 0) + goto done; + retval = 0; + done: + return retval; +} + +/*! Clixon init startup modes Start with a completely clean running state + */ +static int +startup_mode_init(clicon_handle h) +{ + int retval = -1; + + /* Reset running, regardless */ + if (db_reset(h, "running") < 0) + goto done; + /* If it is not there, create candidate from running */ + if (xmldb_exists(h, "candidate") != 1) + if (xmldb_copy(h, "running", "candidate") < 0) + goto done; + /* Load plugins and call plugin_init() */ + if (plugin_initiate(h) != 0) + goto done; + retval = 0; + done: + return retval; +} + +/*! Clixon running startup mode: Commit running db configuration into running + * + copy reset commit merge +running----+ |--------------------+-----+------> + \ / / +candidate +--------------------+ / + / +tmp |-------+-----+---------+ + reset extra file + */ +static int +startup_mode_running(clicon_handle h, + char *extraxml_file) +{ + int retval = -1; + + /* Stash original running to candidate for later commit */ + if (xmldb_copy(h, "running", "candidate") < 0) + goto done; + /* Load plugins and call plugin_init() */ + if (plugin_initiate(h) != 0) + goto done; + /* Clear tmp db */ + if (db_reset(h, "tmp") < 0) + goto done; + /* Application may define extra xml in its reset function*/ + if (plugin_reset_state(h, "tmp") < 0) + goto done; + /* Get application extra xml from file */ + if (load_extraxml(h, extraxml_file, "tmp") < 0) + goto done; + /* Commit original running */ + if (candidate_commit(h, "candidate") < 0) + goto done; + /* Merge user reset state and extra xml file (no commit) */ + if (db_merge(h, "tmp", "running") < 0) + goto done; + retval = 0; + done: + return retval; +} + +/*! Clixon startup startup mode: Commit startup configuration into running state + reset commit merge +running |--------------------+-----+------> + / / +startup --------------------+ / + / +tmp |-------+-----+---------+ + reset extra file + */ +static int +startup_mode_startup(clicon_handle h, + char *extraxml_file) +{ + int retval = -1; + + /* If startup does not exist, clear it */ + if (xmldb_exists(h, "startup") != 1) /* diff */ + if (xmldb_create(h, "startup") < 0) /* diff */ + return -1; + /* Load plugins and call plugin_init() */ + if (plugin_initiate(h) != 0) + goto done; + /* Clear tmp db */ + if (db_reset(h, "tmp") < 0) + goto done; + /* Application may define extra xml in its reset function*/ + if (plugin_reset_state(h, "tmp") < 0) + goto done; + /* Get application extra xml from file */ + if (load_extraxml(h, extraxml_file, "tmp") < 0) + goto done; + /* Commit startup */ + if (candidate_commit(h, "startup") < 0) /* diff */ + goto done; + /* Merge user reset state and extra xml file (no commit) */ + if (db_merge(h, "tmp", "running") < 0) + goto done; + retval = 0; + done: + return retval; +} + int main(int argc, char **argv) { @@ -298,14 +592,16 @@ main(int argc, char **argv) int zap; int foreground; int once; - int init_rundb; - int reload_running; - int reset_state_running; - int reset_state_candidate; - char *app_config_file = NULL; + enum startup_mode_t startup_mode; +#ifdef BACKEND_STARTUP_BACKWARD_COMPAT + int init_rundb = 0; + int reset_state_running = 0; + int reset_state_candidate = 0; + int reload_running = 0; +#endif + char *extraxml_file; char *config_group; char *argv0 = argv[0]; - char *tmp; struct stat st; clicon_handle h; int help = 0; @@ -317,7 +613,6 @@ main(int argc, char **argv) char *xmldb_plugin; /* In the startup, logs to stderr & syslog and debug flag set later */ - clicon_log_init(__PROGRAM__, LOG_INFO, CLICON_LOG_STDERR|CLICON_LOG_SYSLOG); /* Initiate CLICON handle */ if ((h = backend_handle_init()) == NULL) @@ -327,10 +622,7 @@ main(int argc, char **argv) foreground = 0; once = 0; zap = 0; - init_rundb = 0; - reload_running = 0; - reset_state_running = 0; - reset_state_candidate = 0; + extraxml_file = NULL; /* * Command-line options for help, debug, and config-file @@ -402,32 +694,41 @@ main(int argc, char **argv) case 'z': /* Zap other process */ zap++; break; - case 'u': /* config unix domain path / ip address */ + case 'u': /* config unix domain path / ip address */ if (!strlen(optarg)) usage(argv[0], h); clicon_option_str_set(h, "CLICON_SOCK", optarg); - break; - case 'P': /* pidfile */ - clicon_option_str_set(h, "CLICON_BACKEND_PIDFILE", optarg); - break; - case 'I': /* Initiate running db */ - init_rundb++; - break; - case 'R': /* Reset state directly into running */ - reset_state_running++; - break; - case 'C': /* Reset state into candidate and then commit it */ - reset_state_candidate++; - break; - case 'c': /* Load application config */ - app_config_file = optarg; - break; - case 'r': /* Reload running */ - reload_running++; - break; - case 'g': /* config socket group */ - clicon_option_str_set(h, "CLICON_SOCK_GROUP", optarg); - break; + break; + case 'P': /* pidfile */ + clicon_option_str_set(h, "CLICON_BACKEND_PIDFILE", optarg); + break; + case 's' : /* startup mode */ + clicon_option_str_set(h, "CLICON_STARTUP_MODE", optarg); + if (clicon_startup_mode(h) < 0){ + fprintf(stderr, "Invalid startup mode: %s\n", optarg); + usage(argv[0], h); + } + break; + case 'c': /* Load application config */ + extraxml_file = optarg; + break; +#ifdef BACKEND_STARTUP_BACKWARD_COMPAT + case 'I': /* Initiate running db */ + init_rundb++; + break; + case 'R': /* Reset state directly into running */ + reset_state_running++; + break; + case 'C': /* Reset state into candidate and then commit it */ + reset_state_candidate++; + break; + case 'r': /* Reload running */ + reload_running++; + break; +#endif /* BACKEND_STARTUP_BACKWARD_COMPAT */ + case 'g': /* config socket group */ + clicon_option_str_set(h, "CLICON_SOCK_GROUP", optarg); + break; case 'p' : /* Print spec */ printspec++; break; @@ -522,87 +823,53 @@ main(int argc, char **argv) goto done; if (xmldb_setopt(h, "yangspec", clicon_dbspec_yang(h)) < 0) goto done; - - /* First check for startup config - XXX the options below have become out-of-hand. - Too complex, need to simplify*/ - if (clicon_option_int(h, "CLICON_USE_STARTUP_CONFIG") > 0){ - if (xmldb_exists(h, "startup") == 1){ - /* copy startup config -> running */ - if (xmldb_copy(h, "startup", "running") < 0) - goto done; - } - else - if (db_reset(h, "running") < 0) - goto done; - if (xmldb_create(h, "candidate") < 0) + /* If startup mode is not defined, eg via OPTION or -s, assume old method */ + startup_mode = clicon_startup_mode(h); + if (startup_mode == -1){ /* Old style, fragmented mode, phase out */ +#ifdef BACKEND_STARTUP_BACKWARD_COMPAT + if (fragmented_startup_mode(h, + argv0, argc, argv, + reload_running, init_rundb, + reset_state_candidate, reset_state_running, + extraxml_file + ) < 0) goto done; +#else + clicon_log(LOG_ERR, "Startup mode undefined. Specify option CLICON_STARTUP_MODE or specify -s option to clicon_backend.\n"); + goto done; +#endif + } + else { + /* Init running db if it is not there + */ + if (xmldb_exists(h, "running") != 1) + if (xmldb_create(h, "running") < 0) + return -1; + switch (startup_mode){ + case SM_NONE: + if (startup_mode_none(h) < 0) + goto done; + break; + case SM_INIT: /* -I */ + if (startup_mode_init(h) < 0) + goto done; + break; + case SM_RUNNING: /* -CIr */ + if (startup_mode_running(h, extraxml_file) < 0) + goto done; + break; + case SM_STARTUP: /* startup configuration */ + if (startup_mode_startup(h, extraxml_file) < 0) + goto done; + break; + } + /* Initiate the shared candidate. */ if (xmldb_copy(h, "running", "candidate") < 0) goto done; - } - /* If running exists and reload_running set, make a copy to candidate */ - if (reload_running){ - if (xmldb_exists(h, "running") != 1){ - clicon_log(LOG_NOTICE, "%s: -r (reload running) option given but no running_db found, proceeding without", __PROGRAM__); - reload_running = 0; /* void it, so we dont commit candidate below */ - } - else - if (xmldb_copy(h, "running", "candidate") < 0) - goto done; - } - /* Init running db - * -I or if it isnt there - */ - if (init_rundb || xmldb_exists(h, "running") != 1){ - if (db_reset(h, "running") < 0) + /* Call plugin_start with user -- options */ + if (plugin_start_useroptions(h, argv0, argc, argv) <0) goto done; } - /* If candidate does not exist, create it from running */ - if (xmldb_exists(h, "candidate") != 1){ - if (xmldb_create(h, "candidate") < 0) - goto done; - if (xmldb_copy(h, "running", "candidate") < 0) - goto done; - } - - /* Initialize plugins - (also calls plugin_init() and plugin_start(argc,argv) in each plugin */ - if (plugin_initiate(h) != 0) - goto done; - - if (reset_state_candidate){ - if (candb_reset(h) < 0) - goto done; - } - else - if (reset_state_running){ - if (plugin_reset_state(h, "running") < 0) - goto done; - } - /* Call plugin_start */ - tmp = *(argv-1); - *(argv-1) = argv0; - if (plugin_start_hooks(h, argc+1, argv-1) < 0) - goto done; - *(argv-1) = tmp; - - if (reload_running){ - /* This could be a failed validation, and we should not fail for that */ - (void)candidate_commit(h, "candidate"); - } - - /* Have we specified a config file to load? eg - * -c [] - */ - if (app_config_file) - if (rundb_main(h, app_config_file) < 0) - goto done; - - /* Initiate the shared candidate. Maybe we should not do this? - * Too strict access - */ - if (xmldb_copy(h, "running", "candidate") < 0) - goto done; if (once) goto done; diff --git a/apps/backend/backend_plugin.c b/apps/backend/backend_plugin.c index 9bb6b131..b194931d 100644 --- a/apps/backend/backend_plugin.c +++ b/apps/backend/backend_plugin.c @@ -71,7 +71,7 @@ * @note the following should match the prototypes in clixon_backend.h */ #define PLUGIN_RESET "plugin_reset" -typedef int (plgreset_t)(clicon_handle h, char *dbname); /* Reset system status */ +typedef int (plgreset_t)(clicon_handle h, const char *db); /* Reset system status */ /*! Plugin callback, if defined called to get state data from plugin * @param[in] h Clicon handle @@ -115,8 +115,8 @@ struct plugin { /* * Local variables */ -static int nplugins = 0; -static struct plugin *plugins = NULL; +static int _nplugins = 0; +static struct plugin *_plugins = NULL; /*! Find a plugin by name and return the dlsym handl * Used by libclicon code to find callback funcctions in plugins. @@ -132,8 +132,8 @@ config_find_plugin(clicon_handle h, int i; struct plugin *p; - for (i = 0; i < nplugins; i++){ - p = &plugins[i]; + for (i = 0; i < _nplugins; i++){ + p = &_plugins[i]; if (strcmp(p->p_name, name) == 0) return p->p_handle; } @@ -253,19 +253,18 @@ backend_plugin_load (clicon_handle h, * @retval -1 Error */ int -plugin_reset_state(clicon_handle h, - char *dbname) +plugin_reset_state(clicon_handle h, + const char *db) { int i; struct plugin *p; - - for (i = 0; i < nplugins; i++) { - p = &plugins[i]; + for (i = 0; i < _nplugins; i++) { + p = &_plugins[i]; if (p->p_reset) { clicon_debug(1, "Calling plugin_reset() for %s\n", p->p_name); - if (((p->p_reset)(h, dbname)) < 0) { + if (((p->p_reset)(h, db)) < 0) { clicon_err(OE_FATAL, 0, "plugin_reset() failed for %s\n", p->p_name); return -1; @@ -283,15 +282,15 @@ plugin_reset_state(clicon_handle h, * @retval -1 Error */ int -plugin_start_hooks(clicon_handle h, +plugin_start_argv(clicon_handle h, int argc, char **argv) { int i; struct plugin *p; - for (i = 0; i < nplugins; i++) { - p = &plugins[i]; + for (i = 0; i < _nplugins; i++) { + p = &_plugins[i]; if (p->p_start) { optind = 0; if (((p->p_start)(h, argc, argv)) < 0) { @@ -314,15 +313,15 @@ plugin_append(struct plugin *p) { struct plugin *new; - if ((new = realloc(plugins, (nplugins+1) * sizeof (*p))) == NULL) { + if ((new = realloc(_plugins, (_nplugins+1) * sizeof (*p))) == NULL) { clicon_err(OE_UNIX, errno, "realloc"); return -1; } - memset (&new[nplugins], 0, sizeof(new[nplugins])); - memcpy (&new[nplugins], p, sizeof(new[nplugins])); - plugins = new; - nplugins++; + memset (&new[_nplugins], 0, sizeof(new[_nplugins])); + memcpy (&new[_nplugins], p, sizeof(new[_nplugins])); + _plugins = new; + _nplugins++; return 0; } @@ -400,14 +399,15 @@ backend_plugin_load_dir(clicon_handle h, quit: if (retval != 0) { /* XXX p is always NULL */ - if (plugins) { + if (_plugins) { while (--np >= 0){ - if ((p = &plugins[np]) == NULL) + if ((p = &_plugins[np]) == NULL) continue; backend_plugin_unload(h, p); free(p); } - free(plugins); + free(_plugins); + _plugins=0; } } if (dp) @@ -431,11 +431,9 @@ plugin_initiate(clicon_handle h) return -1; /* Then load application plugins */ - if ((dir = clicon_backend_dir(h)) == NULL){ - clicon_err(OE_PLUGIN, 0, "backend_dir not defined"); - return -1; - } - if (backend_plugin_load_dir(h, dir) < 0) + dir = clicon_backend_dir(h); + /* If backend directory, load the backend plugisn */ + if (dir && backend_plugin_load_dir(h, dir) < 0) return -1; return 0; @@ -452,15 +450,15 @@ plugin_finish(clicon_handle h) int i; struct plugin *p; - for (i = 0; i < nplugins; i++) { - p = &plugins[i]; + for (i = 0; i < _nplugins; i++) { + p = &_plugins[i]; backend_plugin_unload(h, p); } - if (plugins){ - free(plugins); - plugins = NULL; + if (_plugins){ + free(_plugins); + _plugins = NULL; } - nplugins = 0; + _nplugins = 0; return 0; } @@ -516,8 +514,8 @@ plugin_transaction_begin(clicon_handle h, int retval = 0; struct plugin *p; - for (i = 0; i < nplugins; i++) { - p = &plugins[i]; + for (i = 0; i < _nplugins; i++) { + p = &_plugins[i]; if (p->p_trans_begin) if ((retval = (p->p_trans_begin)(h, (transaction_data)td)) < 0){ if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */ @@ -546,8 +544,8 @@ plugin_transaction_validate(clicon_handle h, struct plugin *p; - for (i = 0; i < nplugins; i++){ - p = &plugins[i]; + for (i = 0; i < _nplugins; i++){ + p = &_plugins[i]; if (p->p_trans_validate) if ((retval = (p->p_trans_validate)(h, (transaction_data)td)) < 0){ if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */ @@ -576,8 +574,8 @@ plugin_transaction_complete(clicon_handle h, int retval = 0; struct plugin *p; - for (i = 0; i < nplugins; i++){ - p = &plugins[i]; + for (i = 0; i < _nplugins; i++){ + p = &_plugins[i]; if (p->p_trans_complete) if ((retval = (p->p_trans_complete)(h, (transaction_data)td)) < 0){ if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */ @@ -623,7 +621,7 @@ plugin_transaction_revert(clicon_handle h, tr.td_tcvec = td->td_scvec; for (i = nr-1; i>=0; i--){ - p = &plugins[i]; + p = &_plugins[i]; if (p->p_trans_commit) if ((p->p_trans_commit)(h, (transaction_data)&tr) < 0){ clicon_log(LOG_NOTICE, "Plugin '%s' %s revert callback failed", @@ -651,8 +649,8 @@ plugin_transaction_commit(clicon_handle h, int i; struct plugin *p; - for (i = 0; i < nplugins; i++){ - p = &plugins[i]; + for (i = 0; i < _nplugins; i++){ + p = &_plugins[i]; if (p->p_trans_commit) if ((retval = (p->p_trans_commit)(h, (transaction_data)td)) < 0){ if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */ @@ -680,8 +678,8 @@ plugin_transaction_end(clicon_handle h, int i; struct plugin *p; - for (i = 0; i < nplugins; i++) { - p = &plugins[i]; + for (i = 0; i < _nplugins; i++) { + p = &_plugins[i]; if (p->p_trans_end) if ((retval = (p->p_trans_end)(h, (transaction_data)td)) < 0){ if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */ @@ -708,8 +706,8 @@ plugin_transaction_abort(clicon_handle h, int i; struct plugin *p; - for (i = 0; i < nplugins; i++) { - p = &plugins[i]; + for (i = 0; i < _nplugins; i++) { + p = &_plugins[i]; if (p->p_trans_abort) (p->p_trans_abort)(h, (transaction_data)td); /* dont abort on error */ } @@ -750,8 +748,8 @@ backend_statedata_call(clicon_handle h, clicon_err(OE_CFG, ENOENT, "XML tree expected"); goto done; } - for (i = 0; i < nplugins; i++) { - p = &plugins[i]; + for (i = 0; i < _nplugins; i++) { + p = &_plugins[i]; if (p->p_statedata) { if ((x = xml_new("config", NULL)) == NULL) goto done; diff --git a/apps/backend/backend_plugin.h b/apps/backend/backend_plugin.h index 320f8b47..c2d2b8a0 100644 --- a/apps/backend/backend_plugin.h +++ b/apps/backend/backend_plugin.h @@ -66,8 +66,8 @@ int backend_plugin_init(clicon_handle h); int plugin_initiate(clicon_handle h); int plugin_finish(clicon_handle h); -int plugin_reset_state(clicon_handle h, char *dbname); -int plugin_start_hooks(clicon_handle h, int argc, char **argv); +int plugin_reset_state(clicon_handle h, const char *db); +int plugin_start_argv(clicon_handle h, int argc, char **argv); int backend_statedata_call(clicon_handle h, char *xpath, cxobj *xml); diff --git a/apps/backend/clixon_backend.h b/apps/backend/clixon_backend.h index 49eb5654..f9b0d28f 100644 --- a/apps/backend/clixon_backend.h +++ b/apps/backend/clixon_backend.h @@ -70,7 +70,7 @@ int plugin_exit(clicon_handle h); /*! Reset system state to original state. Eg at reboot before running thru config. * @see plgreset_t */ -int plugin_reset(clicon_handle h, char *dbname); +int plugin_reset(clicon_handle h, const char *db); /*! Retreive statedata, add statedata to XML tree * @see plgstatedata_ t diff --git a/apps/backend/clixon_backend_handle.h b/apps/backend/clixon_backend_handle.h index 0bca3835..171d5dd0 100644 --- a/apps/backend/clixon_backend_handle.h +++ b/apps/backend/clixon_backend_handle.h @@ -45,7 +45,7 @@ */ struct client_entry; typedef int (*backend_rpc_cb)( - clicon_handle h, + clicon_handle h, /* CLicon handle */ cxobj *xe, /* Request: */ struct client_entry *ce, /* Client session */ cbuf *cbret,/* Reply eg ... */ diff --git a/apps/cli/Makefile.in b/apps/cli/Makefile.in index 0ba702bd..e9e7c75d 100644 --- a/apps/cli/Makefile.in +++ b/apps/cli/Makefile.in @@ -48,6 +48,7 @@ libexecdir = @libexecdir@ localstatedir = @localstatedir@ sysconfdir = @sysconfdir@ includedir = @includedir@ +HOST_VENDOR = @host_vendor@ SH_SUFFIX = @SH_SUFFIX@ CLIXON_MAJOR = @CLIXON_VERSION_MAJOR@ @@ -62,7 +63,8 @@ CLIXON_CLI_SYSDIR = $(libdir)/clixon/plugins/cli # even though it may exist in $(libdir). But the new version may not have been installed yet. LIBDEPS = $(top_srcdir)/lib/src/$(CLIXON_LIB) -LIBS = -L$(top_srcdir)/lib/src @LIBS@ -l:$(CLIXON_LIB) -lpthread +LIBS = -L$(top_srcdir)/lib/src @LIBS@ $(top_srcdir)/lib/src/$(CLIXON_LIB) -lpthread + CPPFLAGS = @CPPFLAGS@ -fPIC INCLUDES = -I. -I$(top_srcdir)/lib -I$(top_srcdir)/include -I$(top_srcdir) @INCLUDES@ @@ -121,13 +123,17 @@ test.c : echo "int main(){}" > $@ test: test.c $(LIBOBJ) - $(CC) $(INCLUDES) $(LDFLAGS) $< $(LIBOBJ) -L. -l:$(MYLIB) $(LIBS) -o $@ + $(CC) $(INCLUDES) $(LDFLAGS) $< $(LIBOBJ) -L. $(MYLIB) $(LIBS) -o $@ $(APPL): $(OBJS) $(MYLIBLINK) $(LIBDEPS) - $(CC) $(LDFLAGS) $(OBJS) -L. -l:$(MYLIB) $(LIBS) -o $@ + $(CC) $(LDFLAGS) $(OBJS) -L. $(MYLIB) $(LIBS) -o $@ $(MYLIB) : $(LIBOBJS) +ifeq ($(HOST_VENDOR),apple) + $(CC) $(LDFLAGS) -shared -undefined dynamic_lookup -o $@ $(LIBOBJS) $(LIBS) +else $(CC) $(LDFLAGS) -shared -Wl,-soname,$(MYLIBSO) -o $@ $(LIBOBJS) $(LIBS) -Wl,-soname=$(MYLIBSO) +endif # link-name is needed for application linking, eg for clixon_cli and clixon_config $(MYLIBLINK) : $(MYLIB) diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c index adc94bd2..ff3d046e 100644 --- a/apps/cli/cli_common.c +++ b/apps/cli/cli_common.c @@ -520,7 +520,7 @@ cli_quit(clicon_handle h, cvec *vars, cvec *argv) { - cli_set_exiting(h, 1); + cligen_exiting_set(cli_cligen(h), 1); return 0; } int cli_quitv(clicon_handle h, cvec *vars, cvec *argv) @@ -1243,3 +1243,13 @@ cli_debug(clicon_handle h, done: return 0; } + +int +cli_help(clicon_handle h, cvec *vars, cvec *argv) +{ + cligen_handle ch = cli_cligen(h); + parse_tree *pt; + + pt = cligen_tree_active_get(ch); + return cligen_help(stdout, *pt); +} diff --git a/apps/cli/cli_handle.c b/apps/cli/cli_handle.c index 97be41ef..695efc8c 100644 --- a/apps/cli/cli_handle.c +++ b/apps/cli/cli_handle.c @@ -86,9 +86,7 @@ struct cli_handle { }; -/* - * cli_handle_init - * returns a clicon handle for other CLICON API calls +/*! Return a clicon handle for other CLICON API calls */ clicon_handle cli_handle_init(void) @@ -153,85 +151,14 @@ cli_syntax_set(clicon_handle h, return 0; } -/*---------------------------------------------------------- - * cligen access functions - *----------------------------------------------------------*/ + +/*! Return clicon handle */ cligen_handle cli_cligen(clicon_handle h) { return cligen(h); } -/* - * cli_interactive and clicon_eval - */ -int -cli_exiting(clicon_handle h) -{ - cligen_handle ch = cligen(h); - - return cligen_exiting(ch); -} -/* - * cli_common.c: cli_quit - * cli_interactive() - */ -int -cli_set_exiting(clicon_handle h, int exiting) -{ - cligen_handle ch = cligen(h); - - return cligen_exiting_set(ch, exiting); -} - -char -cli_comment(clicon_handle h) -{ - cligen_handle ch = cligen(h); - - return cligen_comment(ch); -} - -char -cli_set_comment(clicon_handle h, char c) -{ - cligen_handle ch = cligen(h); - - return cligen_comment_set(ch, c); -} - -char -cli_tree_add(clicon_handle h, char *tree, parse_tree pt) -{ - cligen_handle ch = cligen(h); - - return cligen_tree_add(ch, tree, pt); -} - -char * -cli_tree_active(clicon_handle h) -{ - cligen_handle ch = cligen(h); - - return cligen_tree_active(ch); -} - -int -cli_tree_active_set(clicon_handle h, char *treename) -{ - cligen_handle ch = cligen(h); - - return cligen_tree_active_set(ch, treename); -} - -parse_tree * -cli_tree(clicon_handle h, char *name) -{ - cligen_handle ch = cligen(h); - - return cligen_tree(ch, name); -} - int cli_parse_file(clicon_handle h, FILE *f, diff --git a/apps/cli/cli_handle.h b/apps/cli/cli_handle.h index c2f4614b..9d67532a 100644 --- a/apps/cli/cli_handle.h +++ b/apps/cli/cli_handle.h @@ -41,20 +41,12 @@ * Prototypes * Internal prototypes. For exported functions see clicon_cli_api.h */ -char cli_tree_add(clicon_handle h, char *tree, parse_tree pt); - int cli_parse_file(clicon_handle h, FILE *f, char *name, /* just for errs */ parse_tree *pt, cvec *globals); -char *cli_tree_active(clicon_handle h); - -int cli_tree_active_set(clicon_handle h, char *treename); - -parse_tree *cli_tree(clicon_handle h, char *name); - int cli_susp_hook(clicon_handle h, cli_susphook_t *fn); char *cli_nomatch(clicon_handle h); diff --git a/apps/cli/cli_main.c b/apps/cli/cli_main.c index dc31fa71..972ee5d9 100644 --- a/apps/cli/cli_main.c +++ b/apps/cli/cli_main.c @@ -70,7 +70,7 @@ #include "cli_handle.h" /* Command line options to be passed to getopt(3) */ -#define CLI_OPTS "hD:f:F:1u:d:m:qpGLl:y:" +#define CLI_OPTS "hD:f:xl:F:1u:d:m:qpGLy:c:" /*! terminate cli application */ static int @@ -109,25 +109,86 @@ cli_signal_init (clicon_handle h) * @param[in] h CLICON handle * @see cligen_loop */ -static void +static int cli_interactive(clicon_handle h) { + int retval = -1; int res; char *cmd; char *new_mode; int result; /* Loop through all commands */ - while(!cli_exiting(h)) { -// save_mode = + while(!cligen_exiting(cli_cligen(h))) { new_mode = cli_syntax_mode(h); if ((cmd = clicon_cliread(h)) == NULL) { - cli_set_exiting(h, 1); /* EOF */ - break; + cligen_exiting_set(cli_cligen(h), 1); /* EOF */ + retval = -1; + goto done; } if ((res = clicon_parse(h, cmd, &new_mode, &result)) < 0) - break; + goto done; } + retval = 0; + done: + return retval; +} + +/*! Read file as configuration file and print xml file for migrating to new fmt + * @see clicon_option_readfile_xml + */ +static int +dump_configfile_xml_fn(FILE *fout, + const char *filename) +{ + struct stat st; + char opt[1024]; + char val[1024]; + char line[1024]; + char *cp; + FILE *f = NULL; + int retval = -1; + char *suffix; + + if (filename == NULL || !strlen(filename)){ + clicon_err(OE_UNIX, 0, "Not specified"); + goto done; + } + if ((suffix = rindex(filename, '.')) != NULL && + strcmp((suffix+1), "xml") == 0){ + clicon_err(OE_CFG, 0, "Configfile %s should not be XML", filename); + goto done; + } + if (stat(filename, &st) < 0){ + clicon_err(OE_UNIX, errno, "%s", filename); + goto done; + } + if (!S_ISREG(st.st_mode)){ + clicon_err(OE_UNIX, 0, "%s is not a regular file", filename); + goto done; + } + if ((f = fopen(filename, "r")) == NULL) { + clicon_err(OE_UNIX, errno, "configure file: %s", filename); + return -1; + } + clicon_debug(2, "Reading config file %s", __FUNCTION__, filename); + fprintf(fout, "\n"); + while (fgets(line, sizeof(line), f)) { + if ((cp = strchr(line, '\n')) != NULL) /* strip last \n */ + *cp = '\0'; + /* Trim out comments, strip whitespace, and remove CR */ + if ((cp = strchr(line, '#')) != NULL) + memcpy(cp, "\n", 2); + if (sscanf(line, "%s %s", opt, val) < 2) + continue; + fprintf(fout, "\t<%s>%s\n", opt, val, opt); + } + fprintf(fout, "\n"); + retval = 0; + done: + if (f) + fclose(f); + return retval; } static void @@ -142,6 +203,7 @@ usage(char *argv0, clicon_handle h) "\t-h \t\tHelp\n" "\t-D \tDebug\n" "\t-f \tConfig-file (mandatory)\n" + "\t-x\t\tDump configuration file as XML on stdout (migration utility)\n" "\t-F \tRead commands from file (default stdin)\n" "\t-1\t\tDo not enter interactive mode\n" "\t-u \tconfig UNIX domain path (default: %s)\n" @@ -152,7 +214,8 @@ usage(char *argv0, clicon_handle h) "\t-G \t\tPrint CLI syntax generated from dbspec (if CLICON_CLI_GENMODEL enabled)\n" "\t-L \t\tDebug print dynamic CLI syntax including completions and expansions\n" "\t-l \tLog on (s)yslog, std(e)rr or std(o)ut (stderr is default)\n" - "\t-y \tOverride yang spec file (dont include .yang suffix)\n", + "\t-y \tOverride yang spec file (dont include .yang suffix)\n" + "\t-c \tSpecify cli spec file.\n", argv0, confsock ? confsock : "none", plgdir ? plgdir : "none" @@ -178,6 +241,7 @@ main(int argc, char **argv) int len; int logdst = CLICON_LOG_STDERR; char *restarg = NULL; /* what remains after options */ + int dump_configfile_xml = 0; /* Defaults */ @@ -186,10 +250,11 @@ main(int argc, char **argv) /* Initiate CLICON handle */ if ((h = cli_handle_init()) == NULL) goto done; + if (cli_plugin_init(h) != 0) goto done; once = 0; - cli_set_comment(h, '#'); /* Default to handle #! clicon_cli scripts */ + cligen_comment_set(cli_cligen(h), '#'); /* Default to handle #! clicon_cli scripts */ /* * First-step command-line options for help, debug, config-file and log, @@ -198,7 +263,6 @@ main(int argc, char **argv) opterr = 0; while ((c = getopt(argc, argv, CLI_OPTS)) != -1) switch (c) { - case '?': case 'h': /* Defer the call to usage() to later. Reason is that for helpful text messages, default dirs, etc, are not set until later. @@ -216,6 +280,9 @@ main(int argc, char **argv) usage(argv[0], h); clicon_option_str_set(h, "CLICON_CONFIGFILE", optarg); break; + case 'x': /* dump config file as xml (migration from .conf file)*/ + dump_configfile_xml++; + break; case 'l': /* Log destination: s|e|o */ switch (optarg[0]){ case 's': @@ -239,6 +306,14 @@ main(int argc, char **argv) clicon_debug_init(debug, NULL); + /* Use cli as util tool to dump config file as xml for migration */ + if (dump_configfile_xml) { + clicon_hash_t *copt = clicon_options(h); + char *configfile = hash_value(copt, "CLICON_CONFIGFILE", NULL); + if (dump_configfile_xml_fn(stdout, configfile) < 0) + goto done; + } + /* Find and read configfile */ if (clicon_options_main(h) < 0){ if (help) @@ -253,6 +328,7 @@ main(int argc, char **argv) switch (c) { case 'D' : /* debug */ case 'f': /* config file */ + case 'x': /* dump config file as xml */ case 'l': /* Log destination */ break; /* see above */ case 'F': /* read commands from file */ @@ -295,6 +371,10 @@ main(int argc, char **argv) clicon_option_str_set(h, "CLICON_YANG_MODULE_MAIN", optarg); break; } + case 'c' :{ /* Overwrite clispec with absolute filename */ + clicon_option_str_set(h, "CLICON_CLISPEC_FILE", optarg); + break; + } default: usage(argv[0], h); break; @@ -320,13 +400,9 @@ main(int argc, char **argv) if (yang_spec_main(h, stdout, printspec) < 0) goto done; - /* Check plugin directory */ - if (clicon_cli_dir(h) == NULL){ - clicon_err(OE_PLUGIN, 0, "clicon_cli_dir not defined"); - goto done; - } - - /* Create tree generated from dataspec */ + /* Create tree generated from dataspec. If no other trees exists, this is + * the only one. + */ if (clicon_cli_genmodel(h)){ yang_spec *yspec; /* yang spec */ parse_tree pt = {0,}; /* cli parse tree */ @@ -345,7 +421,7 @@ main(int argc, char **argv) goto done; } snprintf(treename, len, "datamodel:%s", clicon_dbspec_name(h)); - cli_tree_add(h, treename, pt); + cligen_tree_add(cli_cligen(h), treename, pt); if (printgen) cligen_print(stdout, pt, 1); @@ -367,17 +443,15 @@ main(int argc, char **argv) fprintf (stderr, "FATAL: No cli mode set (use -m or CLICON_CLI_MODE)\n"); goto done; } - if (cli_tree(h, cli_syntax_mode(h)) == NULL){ - fprintf (stderr, "FATAL: No such cli mode: %s\n", cli_syntax_mode(h)); - goto done; - } + if (cligen_tree_find(cli_cligen(h), cli_syntax_mode(h)) == NULL) + clicon_log(LOG_WARNING, "No such cli mode: %s (Specify cli mode with CLICON_CLI_MODE in config file or -m on command line", cli_syntax_mode(h)); if (logclisyntax) cli_logsyntax_set(h, logclisyntax); if (debug) clicon_option_dump(h, debug); - + /* Join rest of argv to a single command */ restarg = clicon_strjoin(argc, argv, " "); @@ -410,7 +484,8 @@ main(int argc, char **argv) // Gets in your face if we log on stderr clicon_log_init(__PROGRAM__, LOG_INFO, 0); /* Log on syslog no stderr */ clicon_log(LOG_NOTICE, "%s: %u Terminated\n", __PROGRAM__, getpid()); - cli_terminate(h); + if (h) + cli_terminate(h); return 0; } diff --git a/apps/cli/cli_plugin.c b/apps/cli/cli_plugin.c index 66b3a3e9..ef3dfcaa 100644 --- a/apps/cli/cli_plugin.c +++ b/apps/cli/cli_plugin.c @@ -67,9 +67,9 @@ #include "cli_handle.h" -/* - * Name of plugin functions +/*! Name of master plugin functions * More in clicon_plugin.h + * @note not really used consider documenting or remove */ #define PLUGIN_PROMPT_HOOK "plugin_prompt_hook" #define PLUGIN_PARSE_HOOK "plugin_parse_hook" @@ -113,8 +113,7 @@ syntax_mode_find(cli_syntax_t *stx, const char *mode, int create) return m; } -/* - * Find plugin by name +/*! Find plugin by name */ static struct cli_plugin * plugin_find_cli(cli_syntax_t *stx, char *plgnam) @@ -131,19 +130,17 @@ plugin_find_cli(cli_syntax_t *stx, char *plgnam) return NULL; } -/* - * Generate parse tree for syntax mode +/*! Generate parse tree for syntax mode */ static int gen_parse_tree(clicon_handle h, cli_syntaxmode_t *m) { - cli_tree_add(h, m->csm_name, m->csm_pt); + cligen_tree_add(cli_cligen(h), m->csm_name, m->csm_pt); return 0; } -/* - * Append syntax +/*! Append syntax */ static int syntax_append(clicon_handle h, @@ -162,8 +159,7 @@ syntax_append(clicon_handle h, return 0; } -/* - * Unload all plugins in a group +/*! Unload all plugins in a group */ static int cli_syntax_unload(clicon_handle h) @@ -271,13 +267,15 @@ quit: return cp; } -/* - * Append to syntax mode from file - * Arguments: - * filename : Name of file where syntax is specified (in syntax-group dir) +/*! Append to syntax mode from file + * @param[in] h Clixon handle + * @param[in] filename Name of file where syntax is specified (in syntax-group dir) + * @param[in] dir Name of dir, or NULL */ static int -cli_load_syntax(clicon_handle h, const char *filename, const char *clispec_dir) +cli_load_syntax(clicon_handle h, + const char *filename, + const char *dir) { void *handle = NULL; /* Handle to plugin .so module */ char *mode = NULL; /* Name of syntax mode to append new syntax */ @@ -285,15 +283,18 @@ cli_load_syntax(clicon_handle h, const char *filename, const char *clispec_dir) int retval = -1; FILE *f; char filepath[MAXPATHLEN]; - cvec *vr = NULL; + cvec *cvv = NULL; char *prompt = NULL; char **vec = NULL; int i, nvec; char *plgnam; struct cli_plugin *p; - snprintf(filepath, MAXPATHLEN-1, "%s/%s", clispec_dir, filename); - if ((vr = cvec_new(0)) == NULL){ + if (dir) + snprintf(filepath, MAXPATHLEN-1, "%s/%s", dir, filename); + else + snprintf(filepath, MAXPATHLEN-1, "%s", filename); + if ((cvv = cvec_new(0)) == NULL){ clicon_err(OE_PLUGIN, errno, "cvec_new"); goto done; } @@ -304,7 +305,7 @@ cli_load_syntax(clicon_handle h, const char *filename, const char *clispec_dir) } /* Assuming this plugin is first in queue */ - if (cli_parse_file(h, f, filepath, &pt, vr) < 0){ + if (cli_parse_file(h, f, filepath, &pt, cvv) < 0){ clicon_err(OE_PLUGIN, 0, "failed to parse cli file %s", filepath); fclose(f); goto done; @@ -312,9 +313,9 @@ cli_load_syntax(clicon_handle h, const char *filename, const char *clispec_dir) fclose(f); /* Get CLICON specific global variables */ - prompt = cvec_find_str(vr, "CLICON_PROMPT"); - plgnam = cvec_find_str(vr, "CLICON_PLUGIN"); - mode = cvec_find_str(vr, "CLICON_MODE"); + prompt = cvec_find_str(cvv, "CLICON_PROMPT"); + plgnam = cvec_find_str(cvv, "CLICON_PLUGIN"); + mode = cvec_find_str(cvv, "CLICON_MODE"); if (plgnam != NULL) { /* Find plugin for callback resolving */ if ((p = plugin_find_cli (cli_syntax(h), plgnam)) != NULL) @@ -355,18 +356,19 @@ cli_load_syntax(clicon_handle h, const char *filename, const char *clispec_dir) retval = 0; done: - if (vr) - cvec_free(vr); + if (cvv) + cvec_free(cvv); if (vec) free(vec); return retval; } -/* - * Load plugins within a directory +/*! Load plugins within a directory */ static int -cli_plugin_load_dir(clicon_handle h, char *dir, cli_syntax_t *stx) +cli_plugin_load_dir(clicon_handle h, + char *dir, + cli_syntax_t *stx) { int i; int ndp; @@ -431,8 +433,8 @@ cli_plugin_load_dir(clicon_handle h, char *dir, cli_syntax_t *stx) } -/* - * Load a syntax group. +/*! Load a syntax group. + * @param[in] h Clicon handle */ int cli_syntax_load (clicon_handle h) @@ -440,6 +442,7 @@ cli_syntax_load (clicon_handle h) int retval = -1; char *plugin_dir = NULL; char *clispec_dir = NULL; + char *clispec_file = NULL; int ndp; int i; struct dirent *dp = NULL; @@ -451,14 +454,9 @@ cli_syntax_load (clicon_handle h) return 0; /* Format plugin directory path */ - if ((plugin_dir = clicon_cli_dir(h)) == NULL){ - clicon_err(OE_FATAL, 0, "clicon_cli_dir not set"); - goto quit; - } - if ((clispec_dir = clicon_clispec_dir(h)) == NULL){ - clicon_err(OE_FATAL, 0, "clicon_clispec_dir not set"); - goto quit; - } + plugin_dir = clicon_cli_dir(h); + clispec_dir = clicon_clispec_dir(h); + clispec_file = clicon_option_str(h, "CLICON_CLISPEC_FILE"); /* Allocate plugin group object */ if ((stx = malloc(sizeof(*stx))) == NULL) { @@ -475,20 +473,25 @@ cli_syntax_load (clicon_handle h) goto quit; /* Then load application plugins */ - if (cli_plugin_load_dir(h, plugin_dir, stx) < 0) + if (plugin_dir && cli_plugin_load_dir(h, plugin_dir, stx) < 0) goto quit; - /* load syntaxfiles */ - if ((ndp = clicon_file_dirent(clispec_dir, &dp, "(.cli)$", S_IFREG)) < 0) - goto quit; - /* Load the rest */ - for (i = 0; i < ndp; i++) { - clicon_debug(1, "DEBUG: Loading syntax '%.*s'", - (int)strlen(dp[i].d_name)-4, dp[i].d_name); - if (cli_load_syntax(h, dp[i].d_name, clispec_dir) < 0) + if (clispec_file){ + if (cli_load_syntax(h, clispec_file, NULL) < 0) goto quit; } - + if (clispec_dir){ + /* load syntaxfiles */ + if ((ndp = clicon_file_dirent(clispec_dir, &dp, "(.cli)$", S_IFREG)) < 0) + goto quit; + /* Load the rest */ + for (i = 0; i < ndp; i++) { + clicon_debug(1, "DEBUG: Loading syntax '%.*s'", + (int)strlen(dp[i].d_name)-4, dp[i].d_name); + if (cli_load_syntax(h, dp[i].d_name, clispec_dir) < 0) + goto quit; + } + } /* Did we successfully load any syntax modes? */ if (stx->stx_nmodes <= 0) { retval = 0; @@ -519,8 +522,7 @@ quit: return retval; } -/* - * Call plugin_start() in all plugins +/*! Call plugin_start() in all plugins */ int cli_plugin_start(clicon_handle h, int argc, char **argv) @@ -544,7 +546,6 @@ cli_plugin_start(clicon_handle h, int argc, char **argv) } /* - */ int cli_plugin_finish(clicon_handle h) @@ -575,16 +576,20 @@ cli_handler_err(FILE *f) } -/* - * Evaluate a matched command +/*! Evaluate a matched command + * @param[in] h Clicon handle + * @param[in] cmd The command string */ int -clicon_eval(clicon_handle h, char *cmd, cg_obj *match_obj, cvec *vr) +clicon_eval(clicon_handle h, + char *cmd, + cg_obj *match_obj, + cvec *cvv) { cli_output_reset(); - if (!cli_exiting(h)) { + if (!cligen_exiting(cli_cligen(h))) { clicon_err_reset(); - if (cligen_eval(cli_cligen(h), match_obj, vr) < 0) { + if (cligen_eval(cli_cligen(h), match_obj, cvv) < 0) { #if 0 /* This is removed since we get two error messages on failure. But maybe only sometime? Both a real log when clicon_err is called, and the here again. @@ -606,24 +611,24 @@ clicon_eval(clicon_handle h, char *cmd, cg_obj *match_obj, cvec *vr) * the new mode string. * * @param[in] h Clicon handle - * @param[in] cmd The command string - * @param[in,out] mode A pointer to the mode string pointer - * @param[out] result -2 On eof (shouldnt happen) - * -1 On parse error - * >=0 Number of matches + * @param[in] cmd Command string + * @param[in,out] modenamep Pointer to the mode string pointer + * @param[out] result -2 On eof (shouldnt happen) + * -1 On parse error + * >=0 Number of matches */ int clicon_parse(clicon_handle h, char *cmd, - char **mode, + char **modenamep, int *result) { - char *m, *msav; + char *modename; + char *modename0; int res = -1; int r; cli_syntax_t *stx = NULL; cli_syntaxmode_t *smode; - char *treename; parse_tree *pt; /* Orig */ cg_obj *match_obj; cvec *cvv = NULL; @@ -634,26 +639,27 @@ clicon_parse(clicon_handle h, else f = stderr; stx = cli_syntax(h); - m = *mode; - if (m == NULL) { + if ((modename = *modenamep) == NULL) { smode = stx->stx_active_mode; - m = smode->csm_name; + modename = smode->csm_name; } else { - if ((smode = syntax_mode_find(stx, m, 0)) == NULL) { - cli_output(f, "Can't find syntax mode '%s'\n", m); + if ((smode = syntax_mode_find(stx, modename, 0)) == NULL) { + cli_output(f, "Can't find syntax mode '%s'\n", modename); return -1; } } - msav = NULL; while(smode) { - if (cli_tree_active(h)) - msav = strdup(cli_tree_active(h)); - cli_tree_active_set(h, m); - treename = cli_tree_active(h); - if ((pt = cli_tree(h, treename)) == NULL){ - fprintf(stderr, "No such parse-tree registered: %s\n", treename); - goto done;; + modename0 = NULL; + if ((pt = cligen_tree_active_get(cli_cligen(h))) != NULL) + modename0 = pt->pt_name; + if (cligen_tree_active_set(cli_cligen(h), modename) < 0){ + fprintf(stderr, "No such parse-tree registered: %s\n", modename); + goto done; + } + if ((pt = cligen_tree_active_get(cli_cligen(h))) == NULL){ + fprintf(stderr, "No such parse-tree registered: %s\n", modename); + goto done; } if ((cvv = cvec_new(0)) == NULL){ clicon_err(OE_UNIX, errno, "cvec_new"); @@ -662,12 +668,10 @@ clicon_parse(clicon_handle h, res = cliread_parse(cli_cligen(h), cmd, pt, &match_obj, cvv); if (res != CG_MATCH) pt_expand_cleanup_1(pt); /* XXX change to pt_expand_treeref_cleanup */ - if (msav){ - cli_tree_active_set(h, msav); - free(msav); + if (modename0){ + cligen_tree_active_set(cli_cligen(h), modename0); + modename0 = NULL; } - msav = NULL; - switch (res) { case CG_EOF: /* eof */ case CG_ERROR: @@ -677,11 +681,11 @@ clicon_parse(clicon_handle h, smode = NULL; if (stx->stx_parse_hook) { /* Try to find a match in upper modes, a'la IOS. */ - if ((m = stx->stx_parse_hook(h, cmd, m)) != NULL) { - if ((smode = syntax_mode_find(stx, m, 0)) != NULL) + if ((modename = stx->stx_parse_hook(h, cmd, modename)) != NULL) { + if ((smode = syntax_mode_find(stx, modename, 0)) != NULL) continue; else - cli_output(f, "Can't find syntax mode '%s'\n", m); + cli_output(f, "Can't find syntax mode '%s'\n", modename); } } /* clicon_err(OE_CFG, 0, "CLI syntax error: \"%s\": %s", @@ -690,9 +694,9 @@ clicon_parse(clicon_handle h, cmd, cli_nomatch(h)); break; case CG_MATCH: - if (m != *mode){ /* Command in different mode */ - *mode = m; - cli_set_syntax_mode(h, m); + if (strcmp(modename, *modenamep)){ /* Command in different mode */ + *modenamep = modename; + cli_set_syntax_mode(h, modename); } if ((r = clicon_eval(h, cmd, match_obj, cvv)) < 0) cli_handler_err(stdout); @@ -713,8 +717,9 @@ done: return res; } -/* - * Read command from CLIgen's cliread() using current syntax mode. +/*! Read command from CLIgen's cliread() using current syntax mode. + * @retval string char* buffer containing CLIgen command + * @retval NULL Fatal error */ char * clicon_cliread(clicon_handle h) @@ -733,7 +738,7 @@ clicon_cliread(clicon_handle h) cli_prompt_set(h, ""); else cli_prompt_set(h, cli_prompt(pfmt ? pfmt : mode->csm_prompt)); - cli_tree_active_set(h, mode->csm_name); + cligen_tree_active_set(cli_cligen(h), mode->csm_name); ret = cliread(cli_cligen(h)); if (pfmt) free(pfmt); @@ -901,102 +906,6 @@ cli_prompt(char *fmt) return prompt; } - -/* - * Run command in CLI engine - */ -int -cli_exec(clicon_handle h, char *cmd, char **mode, int *result) -{ - return clicon_parse(h, cmd, mode, result); -} - - -/* - * push_one - * nifty code that 'pushes' a syntax one ore more levels - * op: eg "set" - * cmd: eg "edit policy-options" - */ -int -cli_ptpush(clicon_handle h, char *mode, char *string, char *op) -{ - cg_obj *co, *co_cmd, *cc; - parse_tree *pt; - char **vec = NULL; - int i, j, nvec; - int found; - parse_tree pt_top; - cli_syntaxmode_t *m; - - if ((m = syntax_mode_find(cli_syntax(h), mode, 0)) == NULL) - return 0; - pt_top = m->csm_pt; - if ((co_cmd = co_find_one(pt_top, op)) == NULL) - return 0; - pt = &co_cmd->co_pt; - /* vec is the command, eg 'edit policy_option' */ - if ((vec = clicon_strsep(string, " ", &nvec)) == NULL) - goto catch; - co = NULL; - found = 0; - for (i=0; ipt_len; j++){ - co = pt->pt_vec[j]; - if (co && co->co_type == CO_COMMAND && - (strcmp(co->co_command, vec[i])==0)){ - pt = &co->co_pt; - found++; - break; - } - } - if (!found) - break;//not found on this level - } - if (found){ // match all levels - if (!co_cmd->co_pushed){ - co_cmd->co_pt_push = co_cmd->co_pt; - co_cmd->co_pushed++; - } - co_cmd->co_pt = co->co_pt; - pt = &co_cmd->co_pt; - for (i=0; ipt_len; i++) /* set correct parent */ - if ((cc = pt->pt_vec[i]) != NULL) - co_up_set(cc, co_cmd); - } - catch: - if (vec) - free(vec); - return 0; -} - -int -cli_ptpop(clicon_handle h, char *mode, char *op) -{ - cg_obj *co_cmd, *cc; - int i; - parse_tree *pt; - parse_tree pt_top; - cli_syntaxmode_t *m; - - if ((m = syntax_mode_find(cli_syntax(h), mode, 0)) == NULL) - return 0; - pt_top = m->csm_pt; - if ((co_cmd = co_find_one(pt_top, op)) == NULL) //set - return 0; - if (!co_cmd->co_pushed) - return 0; - co_cmd->co_pushed = 0; - co_cmd->co_pt = co_cmd->co_pt_push; - pt = &co_cmd->co_pt; - for (i=0; ipt_len; i++) /* set correct parent */ - if ((cc = pt->pt_vec[i]) != NULL) - co_up_set(cc, co_cmd); - return 0; -} - - /*! Find a cli plugin based on name and resolve a function pointer in it. * Callback from clicon_dbvars_parse() * Find a cli plugin based on name if given and use dlsym to resolve a diff --git a/apps/cli/cli_show.c b/apps/cli/cli_show.c index 252c7610..174952a3 100644 --- a/apps/cli/cli_show.c +++ b/apps/cli/cli_show.c @@ -99,7 +99,8 @@ expand_dbvar(void *h, cvec *helptexts) { int retval = -1; - char *api_path; + char *api_path_fmt; + char *api_path = NULL; char *dbstr; cxobj *xt = NULL; char *xpath = NULL; @@ -145,13 +146,16 @@ expand_dbvar(void *h, clicon_err(OE_PLUGIN, 0, "%s: Error when accessing argument "); goto done; } - api_path = cv_string_get(cv); - /* api_path = /interface/%s/address/%s + api_path_fmt = cv_string_get(cv); + /* api_path_fmt = /interface/%s/address/%s --> ^/interface/eth0/address/.*$ --> /interface/[name=eth0]/address */ - if (api_path_fmt2xpath(api_path, cvv, &xpath) < 0) - goto done; + if (api_path_fmt2xpath(api_path_fmt, cvv, &xpath) < 0) + goto done; + if (api_path_fmt2api_path(api_path_fmt, cvv, &api_path) < 0) + goto done; + /* XXX read whole configuration, why not send xpath? */ if (clicon_rpc_get_config(h, dbstr, "/", &xt) < 0) goto done; @@ -165,7 +169,7 @@ expand_dbvar(void *h, if ((xtop = xml_new("config", NULL)) == NULL) goto done; xbot = xtop; - /* This is primarily to get "y", XXX xbot can be broken (contains =%s) + /* This is primarily to get "y", * xpath2xml would have worked!! */ if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0) @@ -236,6 +240,8 @@ expand_dbvar(void *h, } retval = 0; done: + if (api_path) + free(api_path); if (xvec) free(xvec); if (xtop) @@ -322,6 +328,7 @@ expand_dir(char *dir, strerror(errno)); goto quit; } +#ifndef __APPLE__ if (0 &&detail){ if ((pw = getpwuid(st.st_uid)) == NULL){ fprintf(stderr, "expand_dir: getpwuid(%d): %s\n", @@ -355,6 +362,7 @@ expand_dir(char *dir, ); cmd = str; } +#endif /* __APPLE__ */ if (((*commands) = realloc(*commands, ((*nr)+1)*sizeof(char**))) == NULL){ perror("expand_dir: realloc"); diff --git a/apps/cli/clixon_cli_api.h b/apps/cli/clixon_cli_api.h index 8d3c7be2..e24e9053 100644 --- a/apps/cli/clixon_cli_api.h +++ b/apps/cli/clixon_cli_api.h @@ -54,15 +54,10 @@ int cli_syntax_load(clicon_handle h); int cli_handler_err(FILE *fd); int cli_set_prompt(clicon_handle h, const char *mode, const char *prompt); char *cli_prompt(char *fmt); -int cli_exec(clicon_handle h, char *cmd, char **mode, int *result); int cli_ptpush(clicon_handle h, char *mode, char *string, char *op); int cli_ptpop(clicon_handle h, char *mode, char *op); /* cli_handle.c */ -char cli_set_comment(clicon_handle h, char c); -char cli_comment(clicon_handle h); -int cli_set_exiting(clicon_handle h, int exiting); -int cli_exiting(clicon_handle h); clicon_handle cli_handle_init(void); int cli_handle_exit(clicon_handle h); cligen_handle cli_cligen(clicon_handle h); @@ -133,6 +128,8 @@ int cli_lock(clicon_handle h, cvec *cvv, cvec *argv); int cli_unlock(clicon_handle h, cvec *cvv, cvec *argv); int cli_copy_config(clicon_handle h, cvec *cvv, cvec *argv); +int cli_help(clicon_handle h, cvec *vars, cvec *argv); + /* In cli_show.c */ int expand_dir(char *dir, int *nr, char ***commands, mode_t flags, int detail); int expand_dbvar(void *h, char *name, cvec *cvv, cvec *argv, diff --git a/apps/netconf/Makefile.in b/apps/netconf/Makefile.in index c1b0f5be..81e7cce6 100644 --- a/apps/netconf/Makefile.in +++ b/apps/netconf/Makefile.in @@ -47,6 +47,7 @@ libexecdir = @libexecdir@ localstatedir = @localstatedir@ sysconfdir = @sysconfdir@ includedir = @includedir@ +HOST_VENDOR = @host_vendor@ SH_SUFFIX = @SH_SUFFIX@ CLIXON_MAJOR = @CLIXON_VERSION_MAJOR@ @@ -58,7 +59,8 @@ CLIXON_LIB = libclixon.so.$(CLIXON_MAJOR).$(CLIXON_MINOR) # For dependency LIBDEPS = $(top_srcdir)/lib/src/$(CLIXON_LIB) -LIBS = -L$(top_srcdir)/lib/src @LIBS@ -l:$(CLIXON_LIB) +LIBS = -L$(top_srcdir)/lib/src @LIBS@ $(top_srcdir)/lib/src/$(CLIXON_LIB) + CPPFLAGS = @CPPFLAGS@ -fPIC INCLUDES = -I. -I$(top_srcdir)/lib/src -I$(top_srcdir)/lib -I$(top_srcdir)/include -I$(top_srcdir) @INCLUDES@ @@ -112,10 +114,14 @@ uninstall: $(CC) $(INCLUDES) $(CPPFLAGS) -D__PROGRAM__=\"$(APPL)\" $(CFLAGS) -c $< $(APPL) : $(OBJS) $(MYLIBLINK) $(LIBDEPS) - $(CC) $(LDFLAGS) $(OBJS) -L. -l:$(MYLIB) $(LIBS) -o $@ + $(CC) $(LDFLAGS) $(OBJS) -L. $(MYLIB) $(LIBS) -o $@ $(MYLIB) : $(LIBOBJS) +ifeq ($(HOST_VENDOR),apple) + $(CC) $(LDFLAGS) -shared -undefined dynamic_lookup -o $@ $(LIBOBJS) $(LIBS) +else $(CC) $(LDFLAGS) -shared -Wl,-soname,$(MYLIBSO) -o $@ $(LIBOBJS) $(LIBS) -Wl,-soname=$(MYLIBSO) +endif # link-name is needed for application linking, eg for clixon_cli and clixon_config $(MYLIBLINK) : $(MYLIB) diff --git a/apps/netconf/netconf_main.c b/apps/netconf/netconf_main.c index 52e8be2c..ed2f1373 100644 --- a/apps/netconf/netconf_main.c +++ b/apps/netconf/netconf_main.c @@ -121,8 +121,7 @@ process_incoming_packet(clicon_handle h, if (xpath_first(xreq, "//hello") != NULL) ; else{ - clicon_log(LOG_WARNING, "Invalid netconf msg: neither rpc or hello: dropp\ -ed"); + clicon_log(LOG_WARNING, "Invalid netconf msg: neither rpc or hello: dropped"); goto done; } if (!isrpc){ /* hello */ @@ -170,6 +169,9 @@ ed"); /*! Get netconf message: detect end-of-msg * @param[in] s Socket where input arrived. read from this. * @param[in] arg Clicon handle. + * This routine continuously reads until no more data on s. There could + * be risk of starvation, but the netconf client does little else than + * read data so I do not see a danger of true starvation here. */ static int netconf_input_cb(int s, @@ -182,43 +184,51 @@ netconf_input_cb(int s, int len; cbuf *cb=NULL; int xml_state = 0; + int poll; if ((cb = cbuf_new()) == NULL){ clicon_err(OE_XML, errno, "%s: cbuf_new", __FUNCTION__); return retval; } memset(buf, 0, sizeof(buf)); - if ((len = read(s, buf, sizeof(buf))) < 0){ - if (errno == ECONNRESET) - len = 0; /* emulate EOF */ - else{ - clicon_log(LOG_ERR, "%s: read: %s", __FUNCTION__, strerror(errno)); - goto done; - } - } /* read */ - if (len == 0){ /* EOF */ - cc_closed++; - close(s); - retval = 0; - goto done; - } - - for (i=0; i]]>", - buf[i], - &xml_state)) { - /* OK, we have an xml string from a client */ - if (process_incoming_packet(h, cb) < 0){ + while (1){ + if ((len = read(s, buf, sizeof(buf))) < 0){ + if (errno == ECONNRESET) + len = 0; /* emulate EOF */ + else{ + clicon_log(LOG_ERR, "%s: read: %s", __FUNCTION__, strerror(errno)); goto done; } - if (cc_closed) - break; - cbuf_reset(cb); + } /* read */ + if (len == 0){ /* EOF */ + cc_closed++; + close(s); + retval = 0; + goto done; } - } + + for (i=0; i]]>", + buf[i], + &xml_state)) { + /* OK, we have an xml string from a client */ + if (process_incoming_packet(h, cb) < 0){ + goto done; + } + if (cc_closed) + break; + cbuf_reset(cb); + } + } + /* poll==1 if more, poll==0 if none */ + if ((poll = event_poll(s)) < 0) + goto done; + if (poll == 0) + break; /* No data to read */ + } /* while */ retval = 0; done: if (cb) @@ -276,8 +286,6 @@ static void usage(clicon_handle h, char *argv0) { - char *netconfdir = clicon_netconf_dir(h); - fprintf(stderr, "usage:%s\n" "where options are\n" "\t-h\t\tHelp\n" @@ -288,7 +296,7 @@ usage(clicon_handle h, "\t-S\t\tLog on syslog\n" "\t-y \tOverride yang spec file (dont include .yang suffix)\n", argv0, - netconfdir + clicon_netconf_dir(h) ); exit(0); } diff --git a/apps/netconf/netconf_plugin.c b/apps/netconf/netconf_plugin.c index cd169b77..64aa8e87 100644 --- a/apps/netconf/netconf_plugin.c +++ b/apps/netconf/netconf_plugin.c @@ -92,8 +92,9 @@ netconf_plugin_load(clicon_handle h) char filename[MAXPATHLEN]; plghndl_t *handle; + /* If no DIR defined, then dont load plugins */ if ((dir = clicon_netconf_dir(h)) == NULL){ - clicon_err(OE_PLUGIN, 0, "clicon_netconf_dir not defined"); + retval = 0; goto quit; } diff --git a/apps/netconf/netconf_rpc.c b/apps/netconf/netconf_rpc.c index 773afb85..6733f416 100644 --- a/apps/netconf/netconf_rpc.c +++ b/apps/netconf/netconf_rpc.c @@ -312,9 +312,7 @@ netconf_edit_config(clicon_handle h, cxobj *x; cxobj *xfilter; char *ftype = NULL; - cxobj *xcc; /* child of config */ char *target; /* db */ - cbuf *cbxml = NULL; /* must have target, and it should be candidate */ if ((target = netconf_get_target(xn, "target")) == NULL || @@ -371,19 +369,12 @@ netconf_edit_config(clicon_handle h, goto ok; } #endif - if ((cbxml = cbuf_new()) == NULL) - goto done; - if ((xcc = xml_child_i(xc, 0)) != NULL) - if (clicon_xml2cbuf(cbxml, xcc, 0, 0) < 0) - goto done; if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) goto done; } ok: retval = 0; done: - if (cbxml) - cbuf_free(cbxml); return retval; } diff --git a/apps/restconf/README.md b/apps/restconf/README.md index ab941091..807483d3 100644 --- a/apps/restconf/README.md +++ b/apps/restconf/README.md @@ -31,7 +31,7 @@ sudo /etc/init.d nginx start Start clixon restconf daemon ``` -olof@vandal> sudo su -c "/www-data/clixon_restconf -f /usr/local/etc/routing.conf " -s /bin/sh www-data +olof@vandal> sudo su -c "/www-data/clixon_restconf -f /usr/local/etc/routing.xml " -s /bin/sh www-data ``` Make restconf calls with curl @@ -67,7 +67,7 @@ curl -sX POST -d '{"interfaces":{"interface":{"name":"eth1","type":"eth","enable Start the restconf fastcgi program with debug flag: ``` -sudo su -c "/www-data/clixon_restconf -Df /usr/local/etc/routing.conf" -s /bin/sh www-data +sudo su -c "/www-data/clixon_restconf -Df /usr/local/etc/routing.xml" -s /bin/sh www-data ``` Look at syslog: ``` diff --git a/apps/restconf/restconf_lib.c b/apps/restconf/restconf_lib.c index 80571eac..7fc1d0ac 100644 --- a/apps/restconf/restconf_lib.c +++ b/apps/restconf/restconf_lib.c @@ -316,7 +316,7 @@ restconf_plugin_load(clicon_handle h) char filename[MAXPATHLEN]; if ((dir = clicon_restconf_dir(h)) == NULL){ - clicon_err(OE_PLUGIN, 0, "clicon_restconf_dir not defined"); + retval = 0; goto quit; } /* Get plugin objects names from plugin directory */ @@ -330,7 +330,7 @@ restconf_plugin_load(clicon_handle h) (int)strlen(filename), filename); if ((handle = plugin_load(h, filename, RTLD_NOW)) == NULL) goto quit; - p_credentials = dlsym(handle, "restconf_credentials"); + p_credentials = dlsym(handle, PLUGIN_CREDENTIALS); if ((plugins = realloc(plugins, (nplugins+1) * sizeof (*plugins))) == NULL) { clicon_err(OE_UNIX, errno, "realloc"); goto quit; @@ -406,3 +406,30 @@ plugin_credentials(clicon_handle h, done: return retval; } + +/*! Parse a cookie string and return value of cookie attribute + * @param[in] cookiestr cookie string according to rfc6265 (modified) + * @param[in] attribute cookie attribute + * @param[out] val malloced cookie value, free with free() + */ +int +get_user_cookie(char *cookiestr, + char *attribute, + char **val) +{ + int retval = -1; + cvec *cvv = NULL; + char *c; + + if (str2cvec(cookiestr, ';', '=', &cvv) < 0) + goto done; + if ((c = cvec_find_str(cvv, attribute)) != NULL){ + if ((*val = strdup(c)) == NULL) + goto done; + } + retval = 0; + done: + if (cvv) + cvec_free(cvv); + return retval; +} diff --git a/apps/restconf/restconf_lib.h b/apps/restconf/restconf_lib.h index 1ebcb74a..fa5459c8 100644 --- a/apps/restconf/restconf_lib.h +++ b/apps/restconf/restconf_lib.h @@ -53,10 +53,11 @@ int clicon_debug_xml(int dbglevel, char *str, cxobj *cx); int test(FCGX_Request *r, int dbg); cbuf *readdata(FCGX_Request *r); - int restconf_plugin_load(clicon_handle h); int restconf_plugin_start(clicon_handle h, int argc, char **argv); int restconf_plugin_unload(clicon_handle h); int plugin_credentials(clicon_handle h, FCGX_Request *r, int *auth); +int get_user_cookie(char *cookiestr, char *attribute, char **val); + #endif /* _RESTCONF_LIB_H_ */ diff --git a/apps/restconf/restconf_main.c b/apps/restconf/restconf_main.c index 97cf3f36..70201edd 100644 --- a/apps/restconf/restconf_main.c +++ b/apps/restconf/restconf_main.c @@ -39,7 +39,7 @@ * sudo apt-get install libfcgi-dev * gcc -o fastcgi fastcgi.c -lfcgi - * sudo su -c "/www-data/clixon_restconf -Df /usr/local/etc/routing.conf " -s /bin/sh www-data + * sudo su -c "/www-data/clixon_restconf -Df /usr/local/etc/routing.xml " -s /bin/sh www-data * This is the interface: * api/data/profile=/metric= PUT data:enable= @@ -205,9 +205,8 @@ request_process(clicon_handle h, if (strcmp(method, "data") == 0) /* restconf, skip /api/data */ retval = api_data(h, r, path, pcvec, 2, qvec, data); - else - if (strcmp(method, "operations") == 0) /* rpc */ - retval = api_operations(h, r, path, pcvec, 2, qvec, data); + else if (strcmp(method, "operations") == 0) /* rpc */ + retval = api_operations(h, r, path, pcvec, 2, qvec, data); else if (strcmp(method, "test") == 0) retval = test(r, 0); else @@ -270,8 +269,6 @@ usage(clicon_handle h, char *argv0) { - char *restconfdir = clicon_restconf_dir(h); - fprintf(stderr, "usage:%s [options]\n" "where options are\n" "\t-h \t\tHelp\n" @@ -280,7 +277,7 @@ usage(clicon_handle h, "\t-d \tSpecify restconf plugin directory dir (default: %s)\n" "\t-y \tOverride yang spec file (dont include .yang suffix)\n", argv0, - restconfdir + clicon_restconf_dir(h) ); exit(0); } diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index e2d7412f..857fbc30 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -39,7 +39,7 @@ * sudo apt-get install libfcgi-dev * gcc -o fastcgi fastcgi.c -lfcgi - * sudo su -c "/www-data/clixon_restconf -Df /usr/local/etc/routing.conf " -s /bin/sh www-data + * sudo su -c "/www-data/clixon_restconf -Df /usr/local/etc/routing.xml " -s /bin/sh www-data * This is the interface: * api/data/profile=/metric= PUT data:enable= @@ -171,6 +171,7 @@ api_data_get_gen(clicon_handle h, yspec = clicon_dbspec_yang(h); if ((path = cbuf_new()) == NULL) goto done; + cprintf(path, "/"); if (api_path2xpath_cvv(yspec, pcvec, pi, path) < 0){ notfound(r); goto done; @@ -691,13 +692,12 @@ api_operation_post(clicon_handle h, char *media_accept; int use_xml = 0; /* By default return JSON */ - clicon_debug(1, "%s json:\"%s\"", __FUNCTION__, data); - media_accept = FCGX_GetParam("HTTP_ACCEPT", r->envp); - if (strcmp(media_accept, "application/yang-data+xml")==0) + clicon_debug(1, "%s json:\"%s\" path:\"%s\"", __FUNCTION__, data, path); + if ((media_accept = FCGX_GetParam("HTTP_ACCEPT", r->envp)) && + strcmp(media_accept, "application/yang-data+xml")==0) use_xml++; - - media_content_type = FCGX_GetParam("HTTP_CONTENT_TYPE", r->envp); - if (strcmp(media_content_type, "application/yang-data+xml")==0) + if ((media_content_type = FCGX_GetParam("HTTP_CONTENT_TYPE", r->envp)) && + strcmp(media_content_type, "application/yang-data+xml")==0) parse_xml++; clicon_debug(1, "%s accept:\"%s\" content-type:\"%s\"", __FUNCTION__, media_accept, media_content_type); @@ -707,6 +707,8 @@ api_operation_post(clicon_handle h, } for (i=0; ipath */ x = NULL; - while ((x = xml_child_each(xinput, x, -1)) != NULL) + while (xml_child_nr(xinput)){ + x = xml_child_i(xinput, 0); if (xml_addsub(xbot, x) < 0) goto done; + } if ((yinput = yang_find((yang_node*)yrpc, Y_INPUT, NULL)) != NULL){ xml_spec_set(xinput, yinput); /* needed for xml_spec_populate */ if (xml_apply(xinput, CX_ELMNT, xml_spec_populate, yinput) < 0) @@ -755,6 +759,25 @@ api_operation_post(clicon_handle h, } } } + /* Non-standard: add cookie as attribute for backend + */ + { + cxobj *xa; + char *cookie; + char *cookieval = NULL; + + if ((cookie = FCGX_GetParam("HTTP_COOKIE", r->envp)) != NULL && + get_user_cookie(cookie, "c-user", &cookieval) ==0){ + if ((xa = xml_new("id", xtop)) == NULL) + goto done; + xml_type_set(xa, CX_ATTR); + if (xml_value_set(xa, cookieval) < 0) + goto done; + if (cookieval) + free(cookieval); + } + } + /* Send to backend */ if (clicon_rpc_netconf_xml(h, xtop, &xret, NULL) < 0) goto done; if ((cbx = cbuf_new()) == NULL) diff --git a/clixon.conf.cpp.cpp b/clixon.conf.cpp.cpp index 3c384ad1..f8d9d8f7 100644 --- a/clixon.conf.cpp.cpp +++ b/clixon.conf.cpp.cpp @@ -105,7 +105,7 @@ CLICON_BACKEND_PIDFILE localstatedir/APPNAME/APPNAME.pidfile # CLICON_CLI_GENMODEL 1 # Generate code for CLI completion of existing db symbols -# CLICON_CLI_GENMODEL_COMPLETION 0 +# CLICON_CLI_GENMODEL_COMPLETION 1 # How to generate and show CLI syntax: VARS|ALL # CLICON_CLI_GENMODEL_TYPE VARS diff --git a/configure b/configure index 7670870b..7412f276 100755 --- a/configure +++ b/configure @@ -681,6 +681,7 @@ infodir docdir oldincludedir includedir +runstatedir localstatedir sharedstatedir sysconfdir @@ -757,6 +758,7 @@ datadir='${datarootdir}' sysconfdir='${prefix}/etc' sharedstatedir='${prefix}/com' localstatedir='${prefix}/var' +runstatedir='${localstatedir}/run' includedir='${prefix}/include' oldincludedir='/usr/include' docdir='${datarootdir}/doc/${PACKAGE}' @@ -1009,6 +1011,15 @@ do | -silent | --silent | --silen | --sile | --sil) silent=yes ;; + -runstatedir | --runstatedir | --runstatedi | --runstated \ + | --runstate | --runstat | --runsta | --runst | --runs \ + | --run | --ru | --r) + ac_prev=runstatedir ;; + -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \ + | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \ + | --run=* | --ru=* | --r=*) + runstatedir=$ac_optarg ;; + -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) ac_prev=sbindir ;; -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ @@ -1146,7 +1157,7 @@ fi for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ datadir sysconfdir sharedstatedir localstatedir includedir \ oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ - libdir localedir mandir + libdir localedir mandir runstatedir do eval ac_val=\$$ac_var # Remove trailing slashes. @@ -1299,6 +1310,7 @@ Fine tuning of the installation directories: --sysconfdir=DIR read-only single-machine data [PREFIX/etc] --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] --localstatedir=DIR modifiable single-machine data [PREFIX/var] + --runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run] --libdir=DIR object code libraries [EPREFIX/lib] --includedir=DIR C header files [PREFIX/include] --oldincludedir=DIR C header files for non-gcc [/usr/include] @@ -1332,7 +1344,7 @@ Optional Packages: --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) --with-cligen=dir Use CLIGEN here --without-restconf disable support for restconf - --without-keyvalue disable support for key-value xmldb datastore + --with-keyvalue enable support for key-value xmldb datastore --with-qdbm=dir Use QDBM here, if keyvalue Some influential environment variables: @@ -2136,7 +2148,7 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu CLIXON_VERSION_MAJOR="3" CLIXON_VERSION_MINOR="3" -CLIXON_VERSION_PATCH="2" +CLIXON_VERSION_PATCH="3" CLIXON_VERSION="\"${CLIXON_VERSION_MAJOR}.${CLIXON_VERSION_MINOR}.${CLIXON_VERSION_PATCH}\"" # Fix to specific version (eg 3.5) or head (3) CLIGEN_VERSION="3" @@ -2165,6 +2177,7 @@ cat >>confdefs.h <<_ACEOF _ACEOF + # clixon versions spread to Makefile's (.so files) and variable in build.c @@ -3816,14 +3829,13 @@ fi done -as_ac_Lib=`$as_echo "ac_cv_lib_:libcligen.so.${CLIGEN_VERSION}''_cligen_init" | $as_tr_sh` -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for cligen_init in -l:libcligen.so.${CLIGEN_VERSION}" >&5 -$as_echo_n "checking for cligen_init in -l:libcligen.so.${CLIGEN_VERSION}... " >&6; } -if eval \${$as_ac_Lib+:} false; then : +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for cligen_init in -lcligen" >&5 +$as_echo_n "checking for cligen_init in -lcligen... " >&6; } +if ${ac_cv_lib_cligen_cligen_init+:} false; then : $as_echo_n "(cached) " >&6 else ac_check_lib_save_LIBS=$LIBS -LIBS="-l:libcligen.so.${CLIGEN_VERSION} $LIBS" +LIBS="-lcligen $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -3843,23 +3855,22 @@ return cligen_init (); } _ACEOF if ac_fn_c_try_link "$LINENO"; then : - eval "$as_ac_Lib=yes" + ac_cv_lib_cligen_cligen_init=yes else - eval "$as_ac_Lib=no" + ac_cv_lib_cligen_cligen_init=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi -eval ac_res=\$$as_ac_Lib - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 -$as_echo "$ac_res" >&6; } -if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then : +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_cligen_cligen_init" >&5 +$as_echo "$ac_cv_lib_cligen_cligen_init" >&6; } +if test "x$ac_cv_lib_cligen_cligen_init" = xyes; then : cat >>confdefs.h <<_ACEOF -#define `$as_echo "HAVE_LIB:libcligen.so.${CLIGEN_VERSION}" | $as_tr_cpp` 1 +#define HAVE_LIBCLIGEN 1 _ACEOF - LIBS="-l:libcligen.so.${CLIGEN_VERSION} $LIBS" + LIBS="-lcligen $LIBS" else as_fn_error $? "CLIgen${CLIGEN_VERSION} missing. Try: git clone https://github.com/olofhagsand/cligen.git" "$LINENO" 5 @@ -3932,10 +3943,10 @@ fi if test "${with_keyvalue+set}" = set; then : withval=$with_keyvalue; else - with_keyvalue=yes + with_keyvalue=no fi - +echo "keyvalue:${with_keyvalue}" if test "x${with_keyvalue}" == xyes; then echo "yes keyvalue" # This is for qdbm @@ -4264,9 +4275,17 @@ fi done +# This is to find clixon system files in the source code. +# same as clixon_DATADIR defined in clixon.mk.cpp for Makefiles + +cat >>confdefs.h <<_ACEOF +#define CLIXON_DATADIR "${prefix}/share/clixon" +_ACEOF -ac_config_files="$ac_config_files Makefile lib/Makefile lib/src/Makefile lib/clixon/Makefile apps/Makefile apps/cli/Makefile apps/backend/Makefile apps/netconf/Makefile apps/restconf/Makefile include/Makefile etc/Makefile etc/clixonrc example/Makefile example/docker/Makefile docker/Makefile docker/cli/Makefile docker/cli/Dockerfile docker/backend/Makefile docker/backend/Dockerfile docker/netconf/Makefile docker/netconf/Dockerfile datastore/Makefile datastore/keyvalue/Makefile datastore/text/Makefile doc/Makefile" + + +ac_config_files="$ac_config_files Makefile lib/Makefile lib/src/Makefile lib/clixon/Makefile apps/Makefile apps/cli/Makefile apps/backend/Makefile apps/netconf/Makefile apps/restconf/Makefile include/Makefile etc/Makefile etc/clixonrc example/Makefile example/docker/Makefile docker/Makefile docker/cli/Makefile docker/cli/Dockerfile docker/backend/Makefile docker/backend/Dockerfile docker/netconf/Makefile docker/netconf/Dockerfile datastore/Makefile datastore/keyvalue/Makefile datastore/text/Makefile yang/Makefile doc/Makefile" cat >confcache <<\_ACEOF # This file is a shell script that caches the results of configure @@ -4983,6 +5002,7 @@ do "datastore/Makefile") CONFIG_FILES="$CONFIG_FILES datastore/Makefile" ;; "datastore/keyvalue/Makefile") CONFIG_FILES="$CONFIG_FILES datastore/keyvalue/Makefile" ;; "datastore/text/Makefile") CONFIG_FILES="$CONFIG_FILES datastore/text/Makefile" ;; + "yang/Makefile") CONFIG_FILES="$CONFIG_FILES yang/Makefile" ;; "doc/Makefile") CONFIG_FILES="$CONFIG_FILES doc/Makefile" ;; *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;; diff --git a/configure.ac b/configure.ac index 78ec5525..a5076d40 100644 --- a/configure.ac +++ b/configure.ac @@ -43,7 +43,7 @@ AC_INIT(lib/clixon/clixon.h.in) CLIXON_VERSION_MAJOR="3" CLIXON_VERSION_MINOR="3" -CLIXON_VERSION_PATCH="2" +CLIXON_VERSION_PATCH="3" CLIXON_VERSION="\"${CLIXON_VERSION_MAJOR}.${CLIXON_VERSION_MINOR}.${CLIXON_VERSION_PATCH}\"" # Fix to specific version (eg 3.5) or head (3) CLIGEN_VERSION="3" @@ -55,6 +55,7 @@ AC_DEFINE_UNQUOTED(CLIXON_VERSION_MAJOR, $CLIXON_VERSION_MAJOR, [Clixon major re AC_DEFINE_UNQUOTED(CLIXON_VERSION_MINOR, $CLIXON_VERSION_MINOR, [Clixon minor release]) AC_DEFINE_UNQUOTED(CLIXON_VERSION_PATCH, $CLIXON_VERSION_PATCH, [Clixon path version]) + # clixon versions spread to Makefile's (.so files) and variable in build.c AC_SUBST(CLIXON_VERSION) AC_SUBST(CLIXON_VERSION_STRING) @@ -123,7 +124,7 @@ fi AC_CHECK_HEADERS(cligen/cligen.h,, AC_MSG_ERROR(cligen missing. Try: git clone https://github.com/olofhagsand/cligen.git)) -AC_CHECK_LIB(:libcligen.so.${CLIGEN_VERSION}, cligen_init,, AC_MSG_ERROR([CLIgen${CLIGEN_VERSION} missing. Try: git clone https://github.com/olofhagsand/cligen.git])) +AC_CHECK_LIB(cligen, cligen_init,, AC_MSG_ERROR([CLIgen${CLIGEN_VERSION} missing. Try: git clone https://github.com/olofhagsand/cligen.git])) # This is for restconf (and fastcgi) AC_ARG_WITH([restconf], @@ -137,10 +138,10 @@ fi # This is for keyvalue datastore (and qdbm) AC_ARG_WITH([keyvalue], - [AS_HELP_STRING([--without-keyvalue],[disable support for key-value xmldb datastore])], - [], - [with_keyvalue=yes]) - + [AS_HELP_STRING([--with-keyvalue],[enable support for key-value xmldb datastore])], + [], + [with_keyvalue=no]) +echo "keyvalue:${with_keyvalue}" if test "x${with_keyvalue}" == xyes; then echo "yes keyvalue" # This is for qdbm @@ -172,6 +173,10 @@ AC_CHECK_LIB(dl, dlopen) AC_CHECK_FUNCS(inet_aton sigaction sigvec strlcpy strsep strndup alphasort versionsort strverscmp) +# This is to find clixon system files in the source code. +# same as clixon_DATADIR defined in clixon.mk.cpp for Makefiles +AC_DEFINE_UNQUOTED(CLIXON_DATADIR, "${prefix}/share/clixon", [Clixon data dir for system yang files etc]) + AH_BOTTOM([#include ]) AC_OUTPUT(Makefile @@ -198,6 +203,7 @@ AC_OUTPUT(Makefile datastore/Makefile datastore/keyvalue/Makefile datastore/text/Makefile + yang/Makefile doc/Makefile ) diff --git a/datastore/Makefile.in b/datastore/Makefile.in index 907321e3..8660a609 100644 --- a/datastore/Makefile.in +++ b/datastore/Makefile.in @@ -60,7 +60,7 @@ CLIXON_LIB = libclixon.so.$(CLIXON_MAJOR).$(CLIXON_MINOR) # For dependency LIBDEPS = $(top_srcdir)/lib/src/$(CLIXON_LIB) -LIBS = -L$(top_srcdir)/lib/src @LIBS@ -l:$(CLIXON_LIB) +LIBS = -L$(top_srcdir)/lib/src @LIBS@ $(top_srcdir)/lib/src/$(CLIXON_LIB) CPPFLAGS = @CPPFLAGS@ diff --git a/datastore/keyvalue/clixon_keyvalue.c b/datastore/keyvalue/clixon_keyvalue.c index 0c1ff566..35a65c40 100644 --- a/datastore/keyvalue/clixon_keyvalue.c +++ b/datastore/keyvalue/clixon_keyvalue.c @@ -150,8 +150,8 @@ static int _startup_locked = 0; */ static int kv_db2file(struct kv_handle *kh, - char *db, - char **filename) + const char *db, + char **filename) { int retval = -1; cbuf *cb; @@ -565,7 +565,7 @@ kv_setopt(xmldb_handle xh, */ int kv_get(xmldb_handle xh, - char *db, + const char *db, char *xpath, int config, cxobj **xtop) @@ -783,7 +783,7 @@ put(char *dbfile, */ int kv_put(xmldb_handle xh, - char *db, + const char *db, enum operation_type op, cxobj *xt) { @@ -836,8 +836,8 @@ kv_put(xmldb_handle xh, */ int kv_copy(xmldb_handle xh, - char *from, - char *to) + const char *from, + const char *to) { int retval = -1; struct kv_handle *kh = handle(xh); @@ -869,8 +869,8 @@ kv_copy(xmldb_handle xh, */ int kv_lock(xmldb_handle xh, - char *db, - int pid) + const char *db, + int pid) { int retval = -1; // struct kv_handle *kh = handle(xh); @@ -900,7 +900,7 @@ kv_lock(xmldb_handle xh, */ int kv_unlock(xmldb_handle xh, - char *db) + const char *db) { int retval = -1; // struct kv_handle *kh = handle(xh); @@ -949,7 +949,7 @@ kv_unlock_all(xmldb_handle xh, */ int kv_islocked(xmldb_handle xh, - char *db) + const char *db) { int retval = -1; // struct kv_handle *kh = handle(xh); @@ -974,7 +974,7 @@ kv_islocked(xmldb_handle xh, */ int kv_exists(xmldb_handle xh, - char *db) + const char *db) { int retval = -1; struct kv_handle *kh = handle(xh); @@ -1001,7 +1001,7 @@ kv_exists(xmldb_handle xh, */ int kv_delete(xmldb_handle xh, - char *db) + const char *db) { int retval = -1; struct kv_handle *kh = handle(xh); @@ -1026,7 +1026,7 @@ kv_delete(xmldb_handle xh, */ int kv_create(xmldb_handle xh, - char *db) + const char *db) { int retval = -1; struct kv_handle *kh = handle(xh); diff --git a/datastore/keyvalue/clixon_keyvalue.h b/datastore/keyvalue/clixon_keyvalue.h index 83654df0..33da4962 100644 --- a/datastore/keyvalue/clixon_keyvalue.h +++ b/datastore/keyvalue/clixon_keyvalue.h @@ -39,16 +39,16 @@ /* * Prototypes */ -int kv_get(xmldb_handle h, char *db, char *xpath, int config, cxobj **xtop); -int kv_put(xmldb_handle h, char *db, enum operation_type op, cxobj *xt); +int kv_get(xmldb_handle h, const char *db, char *xpath, int config, cxobj **xtop); +int kv_put(xmldb_handle h, const char *db, enum operation_type op, cxobj *xt); int kv_dump(FILE *f, char *dbfilename, char *rxkey); -int kv_copy(xmldb_handle h, char *from, char *to); -int kv_lock(xmldb_handle h, char *db, int pid); -int kv_unlock(xmldb_handle h, char *db); +int kv_copy(xmldb_handle h, const char *from, const char *to); +int kv_lock(xmldb_handle h, const char *db, int pid); +int kv_unlock(xmldb_handle h, const char *db); int kv_unlock_all(xmldb_handle h, int pid); -int kv_islocked(xmldb_handle h, char *db); -int kv_exists(xmldb_handle h, char *db); -int kv_delete(xmldb_handle h, char *db); -int kv_init(xmldb_handle h, char *db); +int kv_islocked(xmldb_handle h, const char *db); +int kv_exists(xmldb_handle h, const char *db); +int kv_delete(xmldb_handle h, const char *db); +int kv_init(xmldb_handle h, const char *db); #endif /* _CLIXON_KEYVALUE_H */ diff --git a/datastore/text/Makefile.in b/datastore/text/Makefile.in index 5cc95cb2..6a5e69f9 100644 --- a/datastore/text/Makefile.in +++ b/datastore/text/Makefile.in @@ -42,6 +42,7 @@ mandir = @mandir@ libexecdir = @libexecdir@ localstatedir = @localstatedir@ sysconfdir = @sysconfdir@ +HOST_VENDOR = @host_vendor@ VPATH = @srcdir@ CC = @CC@ @@ -64,7 +65,11 @@ all: $(PLUGIN) -include $(DESTDIR)$(datarootdir)/clixon/clixon.mk $(PLUGIN): $(SRC) +ifeq ($(HOST_VENDOR),apple) + $(CC) $(CPPFLAGS) $(INCLUDES) $(CFLAGS) $(LDFLAGS) -shared -undefined dynamic_lookup -o $@ -lc $^ $(LIBS) +else $(CC) $(CPPFLAGS) $(INCLUDES) $(CFLAGS) $(LDFLAGS) -shared -o $@ -lc $^ $(LIBS) +endif clean: rm -f $(PLUGIN) $(OBJS) *.core diff --git a/datastore/text/clixon_xmldb_text.c b/datastore/text/clixon_xmldb_text.c index d255d69f..4df8133b 100644 --- a/datastore/text/clixon_xmldb_text.c +++ b/datastore/text/clixon_xmldb_text.c @@ -99,7 +99,7 @@ text_handle_check(xmldb_handle xh) */ static int text_db2file(struct text_handle *th, - char *db, + const char *db, char **filename) { int retval = -1; @@ -281,7 +281,7 @@ singleconfigroot(cxobj *xt, */ int text_get(xmldb_handle xh, - char *db, + const char *db, char *xpath, int config, cxobj **xtop) @@ -363,6 +363,11 @@ text_get(xmldb_handle xh, if (xml_apply(xt, CX_ELMNT, xml_order, NULL) < 0) goto done; +#if (XML_CHILD_HASH==1) + /* Add hash */ + if (xml_apply0(xt, CX_ELMNT, xml_hash_op, (void*)1) < 0) + goto done; +#endif if (debug>1) clicon_xml2file(stderr, xt, 0, 1); *xtop = xt; @@ -380,83 +385,6 @@ text_get(xmldb_handle xh, return retval; } -/*! Given a modification tree, check existing matching child in the base tree - * param[in] x0 Base tree node - * param[in] x1c Modification tree child - * param[in] yc Yang spec of tree child - * param[out] x0cp Matching base tree child (if any) -*/ -static int -match_base_child(cxobj *x0, - cxobj *x1c, - yang_stmt *yc, - cxobj **x0cp) -{ - int retval = -1; - cxobj *x0c = NULL; - char *keyname; - cvec *cvk = NULL; - cg_var *cvi; - char *b0; - char *b1; - yang_stmt *ykey; - char *cname; - int ok; - char *x1bstr; /* body string */ - - cname = xml_name(x1c); - switch (yc->ys_keyword){ - case Y_LEAF_LIST: /* Match with name and value */ - x1bstr = xml_body(x1c); - x0c = NULL; - while ((x0c = xml_child_each(x0, x0c, CX_ELMNT)) != NULL) { - if (strcmp(cname, xml_name(x0c)) == 0 && - strcmp(xml_body(x0c), x1bstr)==0) - break; - } - break; - case Y_LIST: /* Match with key values */ - if ((ykey = yang_find((yang_node*)yc, Y_KEY, NULL)) == NULL){ - clicon_err(OE_XML, errno, "%s: List statement \"%s\" has no key", - __FUNCTION__, yc->ys_argument); - goto done; - } - /* The value is a list of keys: [ ]* */ - if ((cvk = yang_arg2cvec(ykey, " ")) == NULL) - goto done; - x0c = NULL; - while ((x0c = xml_child_each(x0, x0c, CX_ELMNT)) != NULL) { - if (strcmp(xml_name(x0c), cname)) - continue; - cvi = NULL; - ok = 0; - while ((cvi = cvec_each(cvk, cvi)) != NULL) { - keyname = cv_string_get(cvi); - ok = 1; /* if we come here */ - if ((b0 = xml_find_body(x0c, keyname)) == NULL) - break; /* error case */ - if ((b1 = xml_find_body(x1c, keyname)) == NULL) - break; /* error case */ - if (strcmp(b0, b1)) - break; - ok = 2; /* and reaches here for all keynames, x0c is found. */ - } - if (ok == 2) - break; - } - break; - default: /* Just match with name */ - x0c = xml_find(x0, cname); - break; - } - *x0cp = x0c; - retval = 0; - done: - if (cvk) - cvec_free(cvk); - return retval; -} - /*! Modify a base tree x0 with x1 with yang spec y according to operation op * @param[in] x0 Base xml tree (can be NULL in add scenarios) * @param[in] y0 Yang spec corresponding to xml-node x0. NULL if x0 is NULL @@ -482,6 +410,8 @@ text_modify(cxobj *x0, cxobj *x1c; /* mod child */ char *x1bstr; /* mod body string */ yang_stmt *yc; /* yang child */ + cxobj **x0vec = NULL; + int i; assert(x1 && xml_type(x1) == CX_ELMNT); assert(y0); @@ -529,6 +459,10 @@ text_modify(cxobj *x0, if (xml_value_set(x0b, x1bstr) < 0) goto done; } +#if (XML_CHILD_HASH==1) + if (xml_apply0(x0, CX_ELMNT, xml_hash_op, (void*)1) < 0) + goto done; +#endif break; case OP_DELETE: if (x0==NULL){ @@ -536,8 +470,9 @@ text_modify(cxobj *x0, goto done; } case OP_REMOVE: /* fall thru */ - if (x0) + if (x0){ xml_purge(x0); + } break; default: break; @@ -562,8 +497,9 @@ text_modify(cxobj *x0, if (y0->yn_keyword == Y_ANYXML){ if (op == OP_NONE) break; - if (x0) + if (x0){ xml_purge(x0); + } if ((x0 = xml_new_spec(x1name, x0p, y0)) == NULL) goto done; if (xml_copy(x1, x0) < 0) @@ -576,8 +512,18 @@ text_modify(cxobj *x0, if (op==OP_NONE) xml_flag_set(x0, XML_FLAG_NONE); /* Mark for potential deletion */ } +#if (XML_CHILD_HASH==1) + if (xml_apply0(x0, CX_ELMNT, xml_hash_op, (void*)1) < 0) + goto done; +#endif + /* First pass: mark existing children in base */ /* Loop through children of the modification tree */ - x1c = NULL; + if ((x0vec = calloc(xml_child_nr(x1), sizeof(x1))) == NULL){ + clicon_err(OE_UNIX, errno, "calloc"); + goto done; + } + x1c = NULL; + i = 0; while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) { x1cname = xml_name(x1c); /* Get yang spec of the child */ @@ -587,9 +533,17 @@ text_modify(cxobj *x0, } /* See if there is a corresponding node in the base tree */ x0c = NULL; - if (yc && match_base_child(x0, x1c, yc, &x0c) < 0) + if (match_base_child(x0, x1c, &x0c, yc) < 0) goto done; - if (text_modify(x0c, (yang_node*)yc, x0, x1c, op) < 0) + x0vec[i++] = x0c; + } + /* Second pass: modify tree */ + x1c = NULL; + i = 0; + while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) { + x1cname = xml_name(x1c); + yc = yang_find_datanode(y0, x1cname); + if (text_modify(x0vec[i++], (yang_node*)yc, x0, x1c, op) < 0) goto done; } break; @@ -609,6 +563,8 @@ text_modify(cxobj *x0, // ok: retval = 0; done: + if (x0vec) + free(x0vec); return retval; } @@ -668,7 +624,7 @@ text_modify_top(cxobj *x0, goto done; } /* See if there is a corresponding node in the base tree */ - if (match_base_child(x0, x1c, yc, &x0c) < 0) + if (match_base_child(x0, x1c, &x0c, yc) < 0) goto done; if (text_modify(x0c, (yang_node*)yc, x0, x1c, op) < 0) goto done; @@ -725,7 +681,7 @@ xml_container_presence(cxobj *x, */ int text_put(xmldb_handle xh, - char *db, + const char *db, enum operation_type op, cxobj *x1) { @@ -787,6 +743,12 @@ text_put(xmldb_handle xh, if (xml_apply(x1, CX_ELMNT, xml_spec_populate, yspec) < 0) goto done; +#if (XML_CHILD_HASH==1) + /* Add hash */ + if (xml_apply0(x0, CX_ELMNT, xml_hash_op, (void*)1) < 0) + goto done; +#endif + /* * Modify base tree x with modification x1 */ @@ -845,8 +807,8 @@ text_put(xmldb_handle xh, */ int text_copy(xmldb_handle xh, - char *from, - char *to) + const char *from, + const char *to) { int retval = -1; struct text_handle *th = handle(xh); @@ -878,7 +840,7 @@ text_copy(xmldb_handle xh, */ int text_lock(xmldb_handle xh, - char *db, + const char *db, int pid) { struct text_handle *th = handle(xh); @@ -898,7 +860,7 @@ text_lock(xmldb_handle xh, */ int text_unlock(xmldb_handle xh, - char *db) + const char *db) { struct text_handle *th = handle(xh); int zero = 0; @@ -919,7 +881,7 @@ text_unlock_all(xmldb_handle xh, int pid) { struct text_handle *th = handle(xh); - char **keys; + char **keys = NULL; size_t klen; int i; int *val; @@ -931,6 +893,8 @@ text_unlock_all(xmldb_handle xh, if ((val = hash_value(th->th_dbs, keys[i], &vlen)) != NULL && *val == pid) hash_del(th->th_dbs, keys[i]); + if (keys) + free(keys); return 0; } @@ -943,7 +907,7 @@ text_unlock_all(xmldb_handle xh, */ int text_islocked(xmldb_handle xh, - char *db) + const char *db) { struct text_handle *th = handle(xh); size_t vlen; @@ -964,7 +928,7 @@ text_islocked(xmldb_handle xh, */ int text_exists(xmldb_handle xh, - char *db) + const char *db) { int retval = -1; @@ -992,7 +956,7 @@ text_exists(xmldb_handle xh, */ int text_delete(xmldb_handle xh, - char *db) + const char *db) { int retval = -1; char *filename = NULL; @@ -1012,6 +976,7 @@ text_delete(xmldb_handle xh, } /*! Create / init database + * If it exists dont change. * @param[in] xh XMLDB handle * @param[in] db Database * @retval 0 OK @@ -1019,7 +984,7 @@ text_delete(xmldb_handle xh, */ int text_create(xmldb_handle xh, - char *db) + const char *db) { int retval = -1; struct text_handle *th = handle(xh); diff --git a/datastore/text/clixon_xmldb_text.h b/datastore/text/clixon_xmldb_text.h index c3c24c22..36d33633 100644 --- a/datastore/text/clixon_xmldb_text.h +++ b/datastore/text/clixon_xmldb_text.h @@ -39,16 +39,16 @@ /* * Prototypes */ -int text_get(xmldb_handle h, char *db, char *xpath, int config, cxobj **xtop); -int text_put(xmldb_handle h, char *db, enum operation_type op, cxobj *xt); +int text_get(xmldb_handle h, const char *db, char *xpath, int config, cxobj **xtop); +int text_put(xmldb_handle h, const char *db, enum operation_type op, cxobj *xt); int text_dump(FILE *f, char *dbfilename, char *rxkey); -int text_copy(xmldb_handle h, char *from, char *to); -int text_lock(xmldb_handle h, char *db, int pid); -int text_unlock(xmldb_handle h, char *db); +int text_copy(xmldb_handle h, const char *from, const char *to); +int text_lock(xmldb_handle h, const char *db, int pid); +int text_unlock(xmldb_handle h, const char *db); int text_unlock_all(xmldb_handle h, int pid); -int text_islocked(xmldb_handle h, char *db); -int text_exists(xmldb_handle h, char *db); -int text_delete(xmldb_handle h, char *db); -int text_init(xmldb_handle h, char *db); +int text_islocked(xmldb_handle h, const char *db); +int text_exists(xmldb_handle h, const char *db); +int text_delete(xmldb_handle h, const char *db); +int text_init(xmldb_handle h, const char *db); #endif /* _CLIXON_XMLDB_TEXT_H */ diff --git a/doc/FAQ.md b/doc/FAQ.md index 92269257..cd726c3b 100644 --- a/doc/FAQ.md +++ b/doc/FAQ.md @@ -23,8 +23,8 @@ APIs. There are currently plugins for: CLI, Netconf, Restconf, the datastore an Clixon is written in C. The plugins are written in C. The CLI specification uses cligen (http://cligen.se) -There is a project for writing plugins in Python. It is reasonable -simple to spawn an external script from a backend. +It is possible ro write plugins in Python. It is reasonable +simple to spawn an external script from a backend (but needs to be done). ## How to best understand Clixon? Run the ietf yang routing example, in the example directory. @@ -44,20 +44,28 @@ The example: sudo make install ``` +## Do I need to setup anything? + +The config demon requires a valid group to create a server UNIX socket. +Define a valid CLICON_SOCK_GROUP in the config file or via the -g option +or create the group and add the user to it. The default group is 'clicon'. +On linux: + sudo groupadd clicon + sudo usermod -a -G clicon user + ## What about reference documentation? Clixon uses Doxygen for reference documentation. Build using 'make doc' and aim your browser at doc/html/index.html or use the web resource: http://clicon.org/ref/index.html ## How do you run the example? -- Start a backend server: 'clixon_backend -Ff /usr/local/etc/routing.conf' -- Start a cli session: clixon_cli -f /usr/local/etc/routing.conf -- Start a netconf session: clixon_netconf -f /usr/local/etc/routing.conf +- Start a backend server: 'clixon_backend -Ff /usr/local/etc/routing.xml' +- Start a cli session: clixon_cli -f /usr/local/etc/routing.xml +- Start a netconf session: clixon_netconf -f /usr/local/etc/routing.xml ## How is configuration data stored? Configuration data is stored in an XML datastore. The default is a -text-based datastore, but there also exists a key-value datastore -using qdbm. In the example the datastore are regular files found in +text-based datastore. In the example the datastore are regular files found in /usr/local/var/routing/. ## What is validate and commit? @@ -73,10 +81,7 @@ is the core functionality of a clixon system. ## What is a Clixon configuration file? Clixon options are stored in a configuration file you must specify when you start a backend or client using -f. The example configuration -file is /usr/local/etc/routing.conf. -This file is generated from the base source clixon.conf.cpp.cpp and -is merged with local configuration files, such as routing.conf.local. -This is slightly confusing and could be improved. +file is installed at /usr/local/etc/routing.xml. ## Can I run Clixon as docker containers? Yes, the example works as docker containers as well. backend and cli needs a @@ -98,16 +103,16 @@ You may also push the containers with 'make push' but you may then consider chan As an alternative to cli configuration, you can use netconf. Easiest is to just pipe netconf commands to the clixon_netconf application. Example: - echo "]]>]]>" | clixon_netconf -f /usr/local/etc/routing.conf + echo "]]>]]>" | clixon_netconf -f /usr/local/etc/routing.xml However, more useful is to run clixon_netconf as an SSH subsystem. Register the subsystem in /etc/sshd_config: ``` - Subsystem netconf /usr/local/bin/clixon_netconf + Subsystem netconf /usr/local/bin/clixon_netconf -f /usr/local/etc/routing.xml ``` and then invoke it from a client using ``` - ssh -s netconf + ssh -s netconf ``` ## How do I use restconf? @@ -138,7 +143,7 @@ cli> ``` or via netconf: ``` -clixon_netconf -qf /usr/local/etc/routing.conf +clixon_netconf -qf /usr/local/etc/routing.xml ROUTING]]>]]> ]]>]]> Routing notification]]>]]> @@ -146,8 +151,47 @@ clixon_netconf -qf /usr/local/etc/routing.conf ... ``` +## How should I start the backend daemon? + +There are four different backend startup modes. There is differences in running state treatment, ie what state the machine is when you startthe daemon and how loading the configuration affects it: +- none - Do not touch running state. Typically after crash when running state and db are synched. +- init - Initialize running state. Start with a completely clean running state. +- running - Commit running db configuration into running state. Typically after reboot if a persistent running db exists. +- startup - Commit startup configuration into running state. After reboot when no persistent running db exists. + +You use the -s to select the mode: +``` +clixon_backend ... -s running +``` + +You may also add a default method in the configuration file: +``` + + ... + init +``` + +## How can I add extra XML? + +There are two ways to add extra XML to running database after start. Note that this XML is not "committed" into running. + +The first way is via a file. Assume you want to add this xml (the config tag is a necessary top-level tag): +``` + + extra + +``` +You add this via the -c option: +``` +clixon_backend ... -c extra.xml +``` + +The second way is by programming the plugin_reset() in the backend +plugin. The example code contains an example on how to do this (see plugin_reset() in routing_backend.c). + ## I want to program. How do I extend the example? -- routing.conf.local - Override default settings +- routing.xml - Change the configuration file - The yang specifications - This is the central part. It changes the XML, database and the config cli. - routing_cli.cli - Change the fixed part of the CLI commands - routing_cli.c - Cli C-commands are placed here. diff --git a/example/Makefile.in b/example/Makefile.in index 6c94b660..d7aedb34 100644 --- a/example/Makefile.in +++ b/example/Makefile.in @@ -50,7 +50,7 @@ NETCONF_PLUGIN = $(APPNAME)_netconf.so PLUGINS = $(BE_PLUGIN) $(CLI_PLUGIN) $(NETCONF_PLUGIN) -all: $(PLUGINS) $(APPNAME).conf +all: $(PLUGINS) # Note: clixon.mk has a rule for: # $(APPNAME.conf) @@ -91,16 +91,16 @@ SRC = $(BE_SRC) $(CLI_SRC) $(NETCONF_SRC) OBJS = $(BE_OBJ) $(CLI_OBJ) $(NETCONF_OBJ) clean: - rm -f $(PLUGINS) $(OBJS) $(APPNAME).conf + rm -f $(PLUGINS) $(OBJS) (cd docker && $(MAKE) $(MFLAGS) $@) distclean: clean rm -f Makefile *~ .depend (cd docker && $(MAKE) $(MFLAGS) $@) -install: $(YANGSPECS) $(CLISPECS) $(BE_PLUGIN) $(CLI_PLUGIN) $(NETCONF_PLUGIN) $(APPNAME).conf +install: $(YANGSPECS) $(CLISPECS) $(BE_PLUGIN) $(CLI_PLUGIN) $(NETCONF_PLUGIN) $(APPNAME).xml install -d $(DESTDIR)$(clixon_SYSCONFDIR) - install $(APPNAME).conf $(DESTDIR)$(clixon_SYSCONFDIR) + install $(APPNAME).xml $(DESTDIR)$(clixon_SYSCONFDIR) install -d $(DESTDIR)$(clixon_DBSPECDIR)/yang install $(YANGSPECS) $(DESTDIR)$(clixon_DBSPECDIR)/yang install -d $(DESTDIR)$(clixon_LIBDIR)/cli @@ -115,7 +115,7 @@ install: $(YANGSPECS) $(CLISPECS) $(BE_PLUGIN) $(CLI_PLUGIN) $(NETCONF_PLUGIN) $ (cd docker && $(MAKE) $(MFLAGS) $@) uninstall: - rm -rf $(DESTDIR)$(clixon_SYSCONFDIR)/$(APPNAME).conf + rm -rf $(DESTDIR)$(clixon_SYSCONFDIR)/$(APPNAME).xml rm -rf $(DESTDIR)$(clixon_DBSPECDIR) rm -rf $(DESTDIR)$(clixon_LOCALSTATEDIR) rm -rf $(DESTDIR)$(clixon_LIBDIR) diff --git a/example/README.md b/example/README.md index 31fa83a7..e2cf9fe6 100644 --- a/example/README.md +++ b/example/README.md @@ -7,15 +7,15 @@ ``` Start backend: ``` - clixon_backend -f /usr/local/etc/routing.conf -I + clixon_backend -f /usr/local/etc/routing.xml -I ``` Edit cli: ``` - clixon_cli -f /usr/local/etc/routing.conf + clixon_cli -f /usr/local/etc/routing.xml ``` Send netconf command: ``` - clixon_netconf -f /usr/local/etc/routing.conf + clixon_netconf -f /usr/local/etc/routing.xml ``` ## Setting data example using netconf @@ -71,8 +71,7 @@ Clixon implements Yang RPC operations by an extension mechanism. The extension mechanism enables you to add application-specific operations. It works by adding user-defined callbacks for added netconf operations. It is possible to use the extension mechanism -independent of the yang rpc construct, but it is recommended to use -that, and the example includes such an example: +independent of the yang rpc construct, but it is recommended. The example includes an example: Example: ``` @@ -91,7 +90,7 @@ The example works by creating a netconf rpc call and sending it to the backend: ``` -The backend in turn registers a callback (fib_route()) which handles the RPC. +In the backend, a callback is registered (fib_route()) which handles the RPC. ``` static int fib_route(clicon_handle h, @@ -122,7 +121,7 @@ To return state data, you need to write a backend state data callback with the name "plugin_statedata" where you return an XML tree with state. This is then merged with config data by the system. -pA static example of returning state data is in the example. Note that +A static example of returning state data is in the example. Note that a real example would poll or get the interface counters via a system call, as well as use the "xpath" argument to identify the requested state data. diff --git a/example/routing.conf.local b/example/routing.conf.local index 9c4c75ad..a6caa01a 100644 --- a/example/routing.conf.local +++ b/example/routing.conf.local @@ -12,27 +12,6 @@ CLICON_YANG_MODULE_MAIN example # [@] #CLICON_YANG_MODULE_REVISION 2014-06-16 -# Generate code for CLI completion of existing db symbols -# CLICON_CLI_GENMODEL_COMPLETION 0 -CLICON_CLI_GENMODEL_COMPLETION 1 - -# How to generate and show CLI syntax: VARS|ALL -# CLICON_CLI_GENMODEL_TYPE VARS -CLICON_CLI_GENMODEL_TYPE VARS - -# 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"); -# And change predefined callbacks, eg cli_commit -> cli_commitv in all cli files -CLICON_CLIGEN_CALLBACK_SINGLE_ARG 0 - -# Enabled uses "startup" configuration on boot -CLICON_USE_STARTUP_CONFIG 0 - -# XMLDB datastore plugin filename (see datastore/ and clixon_xml_db.[ch]) -CLICON_XMLDB_PLUGIN /usr/local/lib/xmldb/text.so -#CLICON_XMLDB_PLUGIN /usr/local/lib/xmldb/keyvalue.so - # Set to 0 if you want CLI to wrap to next line. # Set to 1 if you want CLI to scroll sideways when approaching right margin CLICON_CLI_LINESCROLLING 0 diff --git a/example/routing.xml b/example/routing.xml new file mode 100644 index 00000000..6ed38f96 --- /dev/null +++ b/example/routing.xml @@ -0,0 +1,18 @@ + + /usr/local/etc/routing.xml + /usr/local/share/routing/yang + example + routing + /usr/local/lib/routing/backend + /usr/local/lib/routing/netconf + /usr/local/lib/routing/restconf + /usr/local/lib/routing/cli + /usr/local/lib/routing/clispec + /usr/local/var/routing/routing.sock + /usr/local/var/routing/routing.pidfile + 1 + /usr/local/var/routing + /usr/local/lib/xmldb/text.so + 0 + init + diff --git a/example/routing_backend.c b/example/routing_backend.c index d5bbc1f3..a0f23b0a 100644 --- a/example/routing_backend.c +++ b/example/routing_backend.c @@ -153,6 +153,8 @@ route_count(clicon_handle h, * @retval 0 OK * @retval -1 Error * @see xmldb_get + * @note this example code returns a static statedata used in testing. + * Real code would poll state */ int plugin_statedata(clicon_handle h, @@ -163,14 +165,11 @@ plugin_statedata(clicon_handle h, cxobj **xvec = NULL; /* Example of (static) statedata, real code would poll state */ - if (0 && (xml_parse("" + if (xml_parse("" "eth0" "eth" - "up" - "up" "42" - "1000000000" - "", xstate)) < 0) + "", xstate) < 0) goto done; retval = 0; done: @@ -179,8 +178,10 @@ plugin_statedata(clicon_handle h, return retval; } -/* - * Plugin initialization +/*! Plugin initialization. Create rpc callbacks + * plugin_init is called as soon as the plugin has been loaded and is + * assumed initialize the plugin's internal state if any as well as register + * any callbacks, configuration dependencies. */ int plugin_init(clicon_handle h) @@ -205,3 +206,58 @@ plugin_init(clicon_handle h) return retval; } +/*! Plugin state reset. Add xml or set state in backend machine. + * Called in each backend plugin. plugin_reset is called after all plugins + * have been initialized. This give the application a chance to reset + * system state back to a base state. + * This is generally done when a system boots up to + * make sure the initial system state is well defined. This can be creating + * default configuration files for various daemons, set interface flags etc. + * @param[in] h Clicon handle + * @param[in] db Name of database. Not may be other than "running" + * In this example, a loopback interface is added + * @note This assumes example yang with interfaces/interface + */ +int +plugin_reset(clicon_handle h, + const char *db) +{ + int retval = -1; + cxobj *xt = NULL; + + if (clicon_xml_parse_str("" + "lolocal" + "", &xt) < 0) + goto done; + /* Replace parent w fiorst child */ + if (xml_rootchild(xt, 0, &xt) < 0) + goto done; + /* Merge user reset state */ + if (xmldb_put(h, (char*)db, OP_MERGE, xt) < 0) + goto done; + retval = 0; + done: + if (xt != NULL) + xml_free(xt); + return retval; +} + +/*! Plugin start. + * @param[in] h Clicon handle + * @param[in] argc Argument vector length (args after -- to backend_main) + * @param[in] argv Argument vector + * + * plugin_start is called once everything has been initialized, right before + * the main event loop is entered. Command line options can be passed to the + * plugins by using "-- " where is any choice of + * options specific to the application. These options are passed to the + * plugin_start function via the argc and argv arguments which + * can be processed with the standard getopt(3). + */ +int +plugin_start(clicon_handle h, + int argc, + char **argv) +{ + return 0; +} diff --git a/example/routing_cli.cli b/example/routing_cli.cli index 1b36c611..3c8a6a2d 100644 --- a/example/routing_cli.cli +++ b/example/routing_cli.cli @@ -11,7 +11,7 @@ delete("Delete a configuration item") @datamodel:example, cli_del(); validate("Validate changes"), cli_validate(); commit("Commit the changes"), cli_commit(); -quit("Quit Hello"), cli_quit(); +quit("Quit"), cli_quit(); delete("Delete a configuration item") all("Delete whole candidate configuration"), delete_all("candidate"); startup("Store running as startup config"), db_copy("running", "startup"); diff --git a/include/clixon_config.h.in b/include/clixon_config.h.in index 1b450323..9840a65d 100644 --- a/include/clixon_config.h.in +++ b/include/clixon_config.h.in @@ -1,5 +1,8 @@ /* include/clixon_config.h.in. Generated from configure.ac by autoheader. */ +/* Clixon data dir for system yang files etc */ +#undef CLIXON_DATADIR + /* Clixon major release */ #undef CLIXON_VERSION_MAJOR diff --git a/include/clixon_custom.h b/include/clixon_custom.h index f2a817ad..473ae77c 100644 --- a/include/clixon_custom.h +++ b/include/clixon_custom.h @@ -43,3 +43,14 @@ int strverscmp (__const char *__s1, __const char *__s2); #endif +/* Hash for XML trees list entries + * Experimental + */ +#define XML_CHILD_HASH 1 + +/* Backward compatible clixon backend daemon startup sequences + * This has been replaced with -s and CLICON_STARTUP_MODE + * define if enable backward compatible behaviour + * Remove in 3.3.4 + */ +#undef BACKEND_STARTUP_BACKWARD_COMPAT diff --git a/lib/clixon/clixon_event.h b/lib/clixon/clixon_event.h index a7eebbca..fd19e583 100644 --- a/lib/clixon/clixon_event.h +++ b/lib/clixon/clixon_event.h @@ -54,6 +54,8 @@ int event_reg_timeout(struct timeval t, int (*fn)(int, void*), int event_unreg_timeout(int (*fn)(int, void*), void *arg); +int event_poll(int fd); + int event_loop(void); int event_exit(void); diff --git a/lib/clixon/clixon_options.h b/lib/clixon/clixon_options.h index 7f3bfbc4..cbd2647f 100644 --- a/lib/clixon/clixon_options.h +++ b/lib/clixon/clixon_options.h @@ -63,6 +63,13 @@ enum genmodel_type{ GT_ALL, /* Keywords on all variables */ }; +/*! See clixon-config.yang type startup_mode */ +enum startup_mode_t{ + SM_NONE=0, + SM_STARTUP, + SM_RUNNING, + SM_INIT +}; /* * Prototypes @@ -93,6 +100,7 @@ char *clicon_clispec_dir(clicon_handle h); char *clicon_netconf_dir(clicon_handle h); char *clicon_restconf_dir(clicon_handle h); char *clicon_xmldb_plugin(clicon_handle h); +int clicon_startup_mode(clicon_handle h); int clicon_sock_family(clicon_handle h); char *clicon_sock(clicon_handle h); int clicon_sock_port(clicon_handle h); diff --git a/lib/clixon/clixon_plugin.h b/lib/clixon/clixon_plugin.h index 10d3d082..61e14fb2 100644 --- a/lib/clixon/clixon_plugin.h +++ b/lib/clixon/clixon_plugin.h @@ -69,11 +69,16 @@ typedef int (plginit_t)(clicon_handle); /* Plugin Init */ typedef int (plgstart_t)(clicon_handle, int, char **); /* Plugin start */ /* Called just before plugin unloaded. - * @see plgexit_t */ #define PLUGIN_EXIT "plugin_exit" typedef int (plgexit_t)(clicon_handle); /* Plugin exit */ +/*! Called by restconf + * Returns 0 if credentials OK, -1 if failed + */ +#define PLUGIN_CREDENTIALS "plugin_credentials" +typedef int (plgcredentials_t)(clicon_handle, void *); /* Plugin credentials */ + /* Find a function in global namespace or a plugin. XXX clicon internal */ void *clicon_find_func(clicon_handle h, char *plugin, char *func); diff --git a/lib/clixon/clixon_proto.h b/lib/clixon/clixon_proto.h index 711f835a..8012082c 100644 --- a/lib/clixon/clixon_proto.h +++ b/lib/clixon/clixon_proto.h @@ -51,7 +51,7 @@ enum format_enum{ /* Protocol message header */ struct clicon_msg { - uint16_t op_len; /* length of message. */ + uint32_t op_len; /* length of message. network byte order. */ char op_body[0]; /* rest of message, actual data */ }; @@ -85,7 +85,7 @@ int clicon_msg_rcv(int s, struct clicon_msg **msg, int *eof); int send_msg_notify(int s, int level, char *event); -int send_msg_reply(int s, char *data, uint16_t datalen); +int send_msg_reply(int s, char *data, uint32_t datalen); int detect_endtag(char *tag, char ch, int *state); diff --git a/lib/clixon/clixon_xml.h b/lib/clixon/clixon_xml.h index 207b8654..0ff7903f 100644 --- a/lib/clixon/clixon_xml.h +++ b/lib/clixon/clixon_xml.h @@ -57,7 +57,12 @@ enum cxobj_type {CX_ERROR=-1, typedef struct xml cxobj; /* struct defined in clicon_xml.c */ -/*! Callback function type for xml_apply */ +/*! Callback function type for xml_apply + * @retval -1 Error, aborted at first error encounter + * @retval 0 OK, continue + * @retval 1 Abort, dont continue with others + * @retval 2 Locally, just abort this subtree, continue with others + */ typedef int (xml_applyfn_t)(cxobj *yn, void *arg); /* @@ -118,6 +123,7 @@ char *xml_body(cxobj *xn); cxobj *xml_body_get(cxobj *xn); char *xml_find_value(cxobj *xn_parent, char *name); char *xml_find_body(cxobj *xn, char *name); +cxobj *xml_find_body_obj(cxobj *xt, char *name, char *val); int xml_free(cxobj *xn); @@ -146,5 +152,12 @@ int xml_body_int32(cxobj *xb, int32_t *val); int xml_body_uint32(cxobj *xb, uint32_t *val); int xml_operation(char *opstr, enum operation_type *op); char *xml_operation2str(enum operation_type op); +#if (XML_CHILD_HASH==1) +clicon_hash_t *xml_hash(cxobj *x); +int xml_hash_init(cxobj *x); +int xml_hash_rm(cxobj *x); +int xml_hash_key(cxobj *x, yang_stmt *y, cbuf *key); +int xml_hash_op(cxobj *x, void *arg); +#endif #endif /* _CLIXON_XML_H */ diff --git a/lib/clixon/clixon_xml_db.h b/lib/clixon/clixon_xml_db.h index 23a04ca0..647dfe04 100644 --- a/lib/clixon/clixon_xml_db.h +++ b/lib/clixon/clixon_xml_db.h @@ -75,34 +75,34 @@ typedef int (xmldb_getopt_t)(xmldb_handle xh, char *optname, void **value); typedef int (xmldb_setopt_t)(xmldb_handle xh, char *optname, void *value); /* Type of xmldb get function */ -typedef int (xmldb_get_t)(xmldb_handle xh, char *db, char *xpath, int config, cxobj **xtop); +typedef int (xmldb_get_t)(xmldb_handle xh, const char *db, char *xpath, int config, cxobj **xtop); /* Type of xmldb put function */ -typedef int (xmldb_put_t)(xmldb_handle xh, char *db, enum operation_type op, cxobj *xt); +typedef int (xmldb_put_t)(xmldb_handle xh, const char *db, enum operation_type op, cxobj *xt); /* Type of xmldb copy function */ -typedef int (xmldb_copy_t)(xmldb_handle xh, char *from, char *to); +typedef int (xmldb_copy_t)(xmldb_handle xh, const char *from, const char *to); /* Type of xmldb lock function */ -typedef int (xmldb_lock_t)(xmldb_handle xh, char *db, int pid); +typedef int (xmldb_lock_t)(xmldb_handle xh, const char *db, int pid); /* Type of xmldb unlock function */ -typedef int (xmldb_unlock_t)(xmldb_handle xh, char *db); +typedef int (xmldb_unlock_t)(xmldb_handle xh, const char *db); /* Type of xmldb unlock_all function */ typedef int (xmldb_unlock_all_t)(xmldb_handle xh, int pid); /* Type of xmldb islocked function */ -typedef int (xmldb_islocked_t)(xmldb_handle xh, char *db); +typedef int (xmldb_islocked_t)(xmldb_handle xh, const char *db); /* Type of xmldb exists function */ -typedef int (xmldb_exists_t)(xmldb_handle xh, char *db); +typedef int (xmldb_exists_t)(xmldb_handle xh, const char *db); /* Type of xmldb delete function */ -typedef int (xmldb_delete_t)(xmldb_handle xh, char *db); +typedef int (xmldb_delete_t)(xmldb_handle xh, const char *db); /* Type of xmldb init function */ -typedef int (xmldb_create_t)(xmldb_handle xh, char *db); +typedef int (xmldb_create_t)(xmldb_handle xh, const char *db); /* plugin init struct for the api */ struct xmldb_api{ @@ -133,20 +133,20 @@ struct xmldb_api{ int xmldb_plugin_load(clicon_handle h, char *filename); int xmldb_plugin_unload(clicon_handle h); -int xmldb_validate_db(char *db); +int xmldb_validate_db(const char *db); int xmldb_connect(clicon_handle h); int xmldb_disconnect(clicon_handle h); int xmldb_getopt(clicon_handle h, char *optname, void **value); int xmldb_setopt(clicon_handle h, char *optname, void *value); -int xmldb_get(clicon_handle h, char *db, char *xpath, int config, cxobj **xtop); -int xmldb_put(clicon_handle h, char *db, enum operation_type op, cxobj *xt); -int xmldb_copy(clicon_handle h, char *from, char *to); -int xmldb_lock(clicon_handle h, char *db, int pid); -int xmldb_unlock(clicon_handle h, char *db); +int xmldb_get(clicon_handle h, const char *db, char *xpath, int config, cxobj **xtop); +int xmldb_put(clicon_handle h, const char *db, enum operation_type op, cxobj *xt); +int xmldb_copy(clicon_handle h, const char *from, const char *to); +int xmldb_lock(clicon_handle h, const char *db, int pid); +int xmldb_unlock(clicon_handle h, const char *db); int xmldb_unlock_all(clicon_handle h, int pid); -int xmldb_islocked(clicon_handle h, char *db); -int xmldb_exists(clicon_handle h, char *db); -int xmldb_delete(clicon_handle h, char *db); -int xmldb_create(clicon_handle h, char *db); +int xmldb_islocked(clicon_handle h, const char *db); +int xmldb_exists(clicon_handle h, const char *db); +int xmldb_delete(clicon_handle h, const char *db); +int xmldb_create(clicon_handle h, const char *db); #endif /* _CLIXON_XML_DB_H */ diff --git a/lib/clixon/clixon_xml_map.h b/lib/clixon/clixon_xml_map.h index 1cff0413..1a1ded10 100644 --- a/lib/clixon/clixon_xml_map.h +++ b/lib/clixon/clixon_xml_map.h @@ -65,6 +65,7 @@ int api_path2xpath_cvv(yang_spec *yspec, cvec *cvv, int offset, cbuf *xpath); int api_path2xpath(yang_spec *yspec, char *api_path, cbuf *xpath); int api_path2xml(char *api_path, yang_spec *yspec, cxobj *xtop, int schemanode, cxobj **xpathp, yang_node **ypathp); +int match_base_child(cxobj *x0, cxobj *x1c, cxobj **x0cp, yang_stmt *yc); int xml_merge(cxobj *x0, cxobj *x1, yang_spec *yspec); int yang_enum_int_value(cxobj *node, int32_t *val); diff --git a/lib/src/Makefile.in b/lib/src/Makefile.in index 8c390233..6ded16d8 100644 --- a/lib/src/Makefile.in +++ b/lib/src/Makefile.in @@ -42,6 +42,7 @@ mandir = @mandir@ libexecdir = @libexecdir@ localstatedir = @localstatedir@ sysconfdir = @sysconfdir@ +HOST_VENDOR = @host_vendor@ SH_SUFFIX = @SH_SUFFIX@ CLIXON_VERSION = @CLIXON_VERSION@ @@ -162,7 +163,12 @@ build.c: $(MYLIB) : $(GENOBJS) $(OBJS) - $(CC) $(LDFLAGS) -shared -Wl,-soname,$(MYLIBSO) -o $@ $(GENOBJS) $(OBJS) $(LIBS) -Wl,-soname=$(MYLIBSO) +ifeq ($(HOST_VENDOR),apple) + $(CC) $(LDFLAGS) -shared -o $@ $(GENOBJS) $(OBJS) $(LIBS) -undefined dynamic_lookup -o $@ +else + $(CC) $(LDFLAGS) -shared -Wl,-soname,$(MYLIBSO) -o $@ $(GENOBJS) $(OBJS) $(LIBS) -Wl,-soname=$(MYLIBSO) +endif + # link-name is needed for application linking, eg for clixon_cli and clixon_config $(MYLIBLINK) : $(MYLIB) # ln -sf $(MYLIB) $@ diff --git a/lib/src/clixon_event.c b/lib/src/clixon_event.c index 53d52f34..9569d44e 100644 --- a/lib/src/clixon_event.c +++ b/lib/src/clixon_event.c @@ -251,6 +251,26 @@ event_unreg_timeout(int (*fn)(int, void*), return found?0:-1; } +/*! Poll to see if there is any data available on this file descriptor. + * @param[in] fd File descriptor + * @retval -1 Error + * @retval 0 Nothing to read/empty fd + * @retval 1 Something to read on fd + */ +int +event_poll(int fd) +{ + int retval = -1; + fd_set fdset; + struct timeval tnull = {0,}; + + FD_ZERO(&fdset); + FD_SET(fd, &fdset); + if ((retval = select(FD_SETSIZE, &fdset, NULL, NULL, &tnull)) < 0) + clicon_err(OE_EVENTS, errno, "%s select1: %s", __FUNCTION__, strerror(errno)); + return retval; +} + /*! Dispatch file descriptor events (and timeouts) by invoking callbacks. * There is an issue with fairness that timeouts may take over all events * One could try to poll the file descriptors after a timeout? @@ -258,11 +278,14 @@ event_unreg_timeout(int (*fn)(int, void*), int event_loop(void) { - struct event_data *e, *e_next; - int n; - struct timeval t, t0, tnull={0,}; - fd_set fdset; - int retval = -1; + struct event_data *e; + struct event_data *e_next; + int n; + struct timeval t; + struct timeval t0; + struct timeval tnull = {0,}; + fd_set fdset; + int retval = -1; while (!clicon_exit_get()){ FD_ZERO(&fdset); @@ -286,7 +309,6 @@ event_loop(void) clicon_debug(1, "%s select: %s", __FUNCTION__, strerror(errno)); clicon_err(OE_EVENTS, errno, "%s select1: %s", __FUNCTION__, strerror(errno)); retval = 0; - goto err; } else clicon_err(OE_EVENTS, errno, "%s select2", __FUNCTION__); @@ -311,8 +333,11 @@ event_loop(void) if(e->e_type == EVENT_FD && FD_ISSET(e->e_fd, &fdset)){ clicon_debug(2, "%s: FD_ISSET: %s[%x]", __FUNCTION__, e->e_string, e->e_arg); - if ((*e->e_fn)(e->e_fd, e->e_arg) < 0) + if ((*e->e_fn)(e->e_fd, e->e_arg) < 0){ + clicon_debug(1, "%s Error in: %s", __FUNCTION__, e->e_string); goto err; + + } if (_ee_unreg){ _ee_unreg = 0; break; @@ -323,7 +348,7 @@ event_loop(void) err: break; } - clicon_debug(1, "%s done:", __FUNCTION__); + clicon_debug(1, "%s done:%d", __FUNCTION__, retval); return retval; } diff --git a/lib/src/clixon_file.c b/lib/src/clixon_file.c index bb1868dd..7282aece 100644 --- a/lib/src/clixon_file.c +++ b/lib/src/clixon_file.c @@ -185,7 +185,7 @@ quit: return retval; } -/*! Make a copy of file src +/*! Make a copy of file src. Overwrite existing * @retval 0 OK * @retval -1 Error */ diff --git a/lib/src/clixon_hash.c b/lib/src/clixon_hash.c index 3ad70036..d7bb94de 100644 --- a/lib/src/clixon_hash.c +++ b/lib/src/clixon_hash.c @@ -94,6 +94,7 @@ #include "clixon_hash.h" #define HASH_SIZE 1031 /* Number of hash buckets. Should be a prime */ +#define align4(s) (((s)/4)*4 + 4) /*! A very simplistic algorithm to calculate a hash bucket index */ @@ -235,7 +236,7 @@ hash_add(clicon_hash_t *hash, } /* Make copy of lvalue */ - newval = malloc (vlen+3); /* XXX: qdbm needs aligned mallocs? */ + newval = malloc(align4(vlen+3)); /* XXX: qdbm needs aligned mallocs? */ if (newval == NULL){ clicon_err(OE_UNIX, errno, "malloc: %s", strerror(errno)); goto catch; diff --git a/lib/src/clixon_json.c b/lib/src/clixon_json.c index c2c0934a..265c80f8 100644 --- a/lib/src/clixon_json.c +++ b/lib/src/clixon_json.c @@ -624,3 +624,79 @@ json_parse_str(char *str, return json_parse(str, "", *xt); } + +/* + * Turn this on to get a json parse and pretty print test program + * Usage: xpath + * read xml from input + * Example compile: + gcc -g -o json -I. -I../clixon ./clixon_json.c -lclixon -lcligen + * Example run: + echo '{"foo": -23}' | ./json +*/ +#if 0 /* Test program */ + +static int +usage(char *argv0) +{ + fprintf(stderr, "usage:%s.\n\tInput on stdin\n", argv0); + exit(0); +} + +int +main(int argc, char **argv) +{ + cxobj *xt; + cxobj *xc; + cbuf *cb = cbuf_new(); + char *buf = NULL; + int i; + int c; + int len; + FILE *f = stdin; + + if (argc != 1){ + usage(argv[0]); + return 0; + } + clicon_log_init(__FILE__, LOG_INFO, CLICON_LOG_STDERR); + len = 1024; /* any number is fine */ + if ((buf = malloc(len)) == NULL){ + perror("malloc"); + return -1; + } + memset(buf, 0, len); + + i = 0; /* position in buf */ + while (1){ /* read the whole file */ + if ((c = fgetc(f)) == EOF) + break; + if (len==i){ + if ((buf = realloc(buf, 2*len)) == NULL){ + fprintf(stderr, "%s: realloc: %s\n", __FUNCTION__, strerror(errno)); + goto done; + } + memset(buf+len, 0, len); + len *= 2; + } + buf[i++] = (char)(c&0xff); + } /* read a line */ + + if (json_parse_str(buf, &xt) < 0) + return -1; + xc = NULL; + while ((xc = xml_child_each(xt, xc, -1)) != NULL) { + xmltree2cbuf(cb, xc, 0); /* dump data structures */ + //clicon_xml2cbuf(cb, xc, 0, 1); /* print xml */ + } + fprintf(stdout, "%s", cbuf_get(cb)); + if (xt) + xml_free(xt); + if (cb) + cbuf_free(cb); + done: + return 0; +} + +#endif /* Test program */ + diff --git a/lib/src/clixon_json_parse.l b/lib/src/clixon_json_parse.l index 64ce04ae..7e0252ad 100644 --- a/lib/src/clixon_json_parse.l +++ b/lib/src/clixon_json_parse.l @@ -47,6 +47,10 @@ #include +#include "clixon_queue.h" +#include "clixon_hash.h" +#include "clixon_handle.h" +#include "clixon_yang.h" #include "clixon_log.h" #include "clixon_xml.h" #include "clixon_json_parse.h" @@ -73,6 +77,11 @@ clixon_json_parsewrap(void) %} +digit [0-9] +integer {digit}+ +real ({digit}+[.]{digit}*)|({digit}*[.]{digit}+) +exp ({integer}|{real})[eE][+-]{integer} + %x START %s STRING %s ESCAPE @@ -91,8 +100,7 @@ clixon_json_parsewrap(void) null { return J_NULL; } false { return J_FALSE; } true { return J_TRUE; } -[-+]?[0-9]+ { clixon_json_parselval.string = strdup(yytext); - return J_NUMBER;} +\-?({integer}|{real}|{exp}) { clixon_json_parselval.string = strdup(yytext); return J_NUMBER;} . { return -1; } \" { BEGIN(START); return J_DQ; } \\ { BEGIN(ESCAPE); } diff --git a/lib/src/clixon_json_parse.y b/lib/src/clixon_json_parse.y index 245e31be..26c6b998 100644 --- a/lib/src/clixon_json_parse.y +++ b/lib/src/clixon_json_parse.y @@ -124,6 +124,10 @@ object. #include "clixon_err.h" #include "clixon_log.h" +#include "clixon_queue.h" +#include "clixon_hash.h" +#include "clixon_handle.h" +#include "clixon_yang.h" #include "clixon_xml.h" #include "clixon_json_parse.h" diff --git a/lib/src/clixon_options.c b/lib/src/clixon_options.c index c239daf9..e8eef2c2 100644 --- a/lib/src/clixon_options.c +++ b/lib/src/clixon_options.c @@ -58,6 +58,7 @@ /* clicon */ #include "clixon_err.h" +#include "clixon_string.h" #include "clixon_queue.h" #include "clixon_hash.h" #include "clixon_handle.h" @@ -65,13 +66,25 @@ #include "clixon_yang.h" #include "clixon_plugin.h" #include "clixon_options.h" +#include "clixon_xml.h" +#include "clixon_xsl.h" +#include "clixon_xml_map.h" -/* - * clicon_option_dump - * Print registry on file. For debugging. +/* Mapping between Clicon startup modes string <--> constants, + see clixon-config.yang type startup_mode */ +static const map_str2int startup_mode_map[] = { + {"none", SM_NONE}, + {"running", SM_RUNNING}, + {"startup", SM_STARTUP}, + {"init", SM_INIT}, + {NULL, -1} +}; + +/*! Print registry on file. For debugging. */ void -clicon_option_dump(clicon_handle h, int dbglevel) +clicon_option_dump(clicon_handle h, + int dbglevel) { clicon_hash_t *hash = clicon_options(h); int i; @@ -101,29 +114,106 @@ clicon_option_dump(clicon_handle h, int dbglevel) } -/*! Read filename and set values to global options registry +/*! Read filename and set values to global options registry. XML variant. + * @see clicon_option_readfile */ static int -clicon_option_readfile(clicon_hash_t *copt, const char *filename) +clicon_option_readfile_xml(clicon_hash_t *copt, + const char *filename, + yang_spec *yspec) { struct stat st; - char opt[1024], val[1024]; - char line[1024], *cp; - FILE *f; - int retval = -1; + FILE *f = NULL; + int retval = -1; + int fd; + cxobj *xt = NULL; + cxobj *xc = NULL; + cxobj *x = NULL; + char *name; + char *body; if (filename == NULL || !strlen(filename)){ clicon_err(OE_UNIX, 0, "Not specified"); - return -1; + goto done; } if (stat(filename, &st) < 0){ clicon_err(OE_UNIX, errno, "%s", filename); - return -1; + goto done; } if (!S_ISREG(st.st_mode)){ clicon_err(OE_UNIX, 0, "%s is not a regular file", filename); + goto done; + } + if ((f = fopen(filename, "r")) == NULL) { + clicon_err(OE_UNIX, errno, "configure file: %s", filename); return -1; } + clicon_debug(2, "Reading config file %s", __FUNCTION__, filename); + fd = fileno(f); + if (clicon_xml_parse_file(fd, &xt, "") < 0) + goto done; + if (xml_child_nr(xt)==1 && xml_child_nr_type(xt, CX_BODY)==1){ + clicon_err(OE_CFG, 0, "Config file %s: Expected XML but is probably old sh style", filename); + goto done; + } + if ((xc = xpath_first(xt, "config")) == NULL) { + clicon_err(OE_CFG, 0, "Config file %s: Lacks top-level \"config\" element", filename); + goto done; + } + if (xml_apply0(xc, CX_ELMNT, xml_spec_populate, yspec) < 0) + goto done; + if (xml_apply0(xc, CX_ELMNT, xml_default, yspec) < 0) + goto done; + if (xml_apply0(xc, CX_ELMNT, xml_yang_validate_add, NULL) < 0) + goto done; + while ((x = xml_child_each(xc, x, CX_ELMNT)) != NULL) { + name = xml_name(x); + body = xml_body(x); + if (name && body && + (hash_add(copt, + name, + body, + strlen(body)+1)) == NULL) + goto done; + } + retval = 0; + done: + if (xt) + xml_free(xt); + if (f) + fclose(f); + return retval; +} + + +/*! Read filename and set values to global options registry + * For legacy configuration file, ie not xml + * @see clicon_option_readfile_xml + */ +static int +clicon_option_readfile(clicon_hash_t *copt, + const char *filename) +{ + struct stat st; + char opt[1024]; + char val[1024]; + char line[1024]; + char *cp; + FILE *f = NULL; + int retval = -1; + + if (filename == NULL || !strlen(filename)){ + clicon_err(OE_UNIX, 0, "Not specified"); + goto done; + } + if (stat(filename, &st) < 0){ + clicon_err(OE_UNIX, errno, "%s", filename); + goto done; + } + if (!S_ISREG(st.st_mode)){ + clicon_err(OE_UNIX, 0, "%s is not a regular file", filename); + goto done; + } if ((f = fopen(filename, "r")) == NULL) { clicon_err(OE_UNIX, errno, "configure file: %s", filename); return -1; @@ -141,11 +231,12 @@ clicon_option_readfile(clicon_hash_t *copt, const char *filename) opt, val, strlen(val)+1)) == NULL) - goto catch; + goto done; } retval = 0; - catch: - fclose(f); + done: + if (f) + fclose(f); return retval; } @@ -159,50 +250,50 @@ clicon_option_default(clicon_hash_t *copt) if (!hash_lookup(copt, "CLICON_YANG_MODULE_MAIN")){ if (hash_add(copt, "CLICON_YANG_MODULE_MAIN", "clicon", strlen("clicon")+1) < 0) - goto catch; + goto done; } if (!hash_lookup(copt, "CLICON_SOCK_GROUP")){ val = CLICON_SOCK_GROUP; if (hash_add(copt, "CLICON_SOCK_GROUP", val, strlen(val)+1) < 0) - goto catch; + goto done; } if (!hash_lookup(copt, "CLICON_CLI_MODE")){ if (hash_add(copt, "CLICON_CLI_MODE", "base", strlen("base")+1) < 0) - goto catch; + goto done; } if (!hash_lookup(copt, "CLICON_MASTER_PLUGIN")){ val = CLICON_MASTER_PLUGIN; if (hash_add(copt, "CLICON_MASTER_PLUGIN", val, strlen(val)+1) < 0) - goto catch; + goto done; } if (!hash_lookup(copt, "CLICON_CLI_GENMODEL")){ if (hash_add(copt, "CLICON_CLI_GENMODEL", "1", strlen("1")+1) < 0) - goto catch; + goto done; } if (!hash_lookup(copt, "CLICON_CLI_GENMODEL_TYPE")){ if (hash_add(copt, "CLICON_CLI_GENMODEL_TYPE", "VARS", strlen("VARS")+1) < 0) - goto catch; + goto done; } if (!hash_lookup(copt, "CLICON_AUTOCOMMIT")){ if (hash_add(copt, "CLICON_AUTOCOMMIT", "0", strlen("0")+1) < 0) - goto catch; + goto done; } /* Legacy is 1 but default should really be 0. New apps should use 0 */ if (!hash_lookup(copt, "CLICON_CLI_VARONLY")){ if (hash_add(copt, "CLICON_CLI_VARONLY", "1", strlen("1")+1) < 0) - goto catch; + goto done; } if (!hash_lookup(copt, "CLICON_CLI_GENMODEL_COMPLETION")){ - if (hash_add(copt, "CLICON_CLI_GENMODEL_COMPLETION", "0", strlen("0")+1) < 0) - goto catch; + if (hash_add(copt, "CLICON_CLI_GENMODEL_COMPLETION", "1", strlen("1")+1) < 0) + goto done; } /* Default is to use line-scrolling */ if (!hash_lookup(copt, "CLICON_CLI_LINESCROLLING")){ if (hash_add(copt, "CLICON_CLI_LINESCROLLING", "1", strlen("1")+1) < 0) - goto catch; + goto done; } retval = 0; - catch: + done: return retval; } @@ -222,7 +313,7 @@ clicon_option_sanity(clicon_hash_t *copt) goto done; } if (!hash_lookup(copt, "CLICON_BACKEND_DIR")){ - clicon_err(OE_UNIX, 0, "CLICON_BACKEND_PIDFILE not defined in config file"); + clicon_err(OE_UNIX, 0, "CLICON_BACKEND_DIR not defined in config file"); goto done; } if (!hash_lookup(copt, "CLICON_NETCONF_DIR")){ @@ -254,7 +345,6 @@ clicon_option_sanity(clicon_hash_t *copt) return retval; } - /*! Initialize option values * * Set default options, Read config-file, Check that all values are set. @@ -266,6 +356,9 @@ clicon_options_main(clicon_handle h) int retval = -1; char *configfile; clicon_hash_t *copt = clicon_options(h); + char *suffix; + char xml = 0; /* Configfile is xml, otherwise legacy */ + yang_spec *yspec = NULL; /* * Set configure file if not set by command-line above @@ -276,26 +369,44 @@ clicon_options_main(clicon_handle h) } configfile = hash_value(copt, "CLICON_CONFIGFILE", NULL); clicon_debug(1, "CLICON_CONFIGFILE=%s", configfile); - /* Set default options */ - if (clicon_option_default(copt) < 0) /* init registry from file */ - goto done; - - /* Read configfile */ - if (clicon_option_readfile(copt, configfile) < 0) - goto done; - - if (clicon_option_sanity(copt) < 0) - goto done; + /* If file ends with .xml, assume it is new format */ + if ((suffix = rindex(configfile, '.')) != NULL){ + suffix++; + xml = strcmp(suffix,"xml") == 0; + } + if (xml){ /* Read clixon yang file */ + if ((yspec = yspec_new()) == NULL) + goto done; + if (yang_parse(h, CLIXON_DATADIR, "clixon-config", NULL, yspec) < 0) + goto done; + /* Read configfile */ + if (clicon_option_readfile_xml(copt, configfile, yspec) < 0) + goto done; + if (yspec) + yspec_free(yspec); + } + else { + /* Set default options */ + if (clicon_option_default(copt) < 0) /* init registry from file */ + goto done; + /* Read configfile */ + if (clicon_option_readfile(copt, configfile) < 0) + goto done; + if (clicon_option_sanity(copt) < 0) + goto done; + } retval = 0; done: return retval; - } /*! Check if a clicon option has a value + * @param[in] h clicon_handle + * @param[in] name option name */ int -clicon_option_exists(clicon_handle h, const char *name) +clicon_option_exists(clicon_handle h, + const char *name) { clicon_hash_t *copt = clicon_options(h); @@ -339,7 +450,7 @@ clicon_option_str_set(clicon_handle h, } /*! Get options as integer but stored as string - + * * @param h clicon handle * @param name name of option * @retval int An integer as aresult of atoi @@ -355,7 +466,8 @@ clicon_option_str_set(clicon_handle h, * supply a defualt value as shown in the example. */ int -clicon_option_int(clicon_handle h, const char *name) +clicon_option_int(clicon_handle h, + const char *name) { char *s; @@ -364,10 +476,12 @@ clicon_option_int(clicon_handle h, const char *name) return atoi(s); } -/*! set option given as int. +/*! Set option given as int. */ int -clicon_option_int_set(clicon_handle h, const char *name, int val) +clicon_option_int_set(clicon_handle h, + const char *name, + int val) { char s[64]; @@ -376,10 +490,11 @@ clicon_option_int_set(clicon_handle h, const char *name, int val) return clicon_option_str_set(h, name, s); } -/*! delete option +/*! Delete option */ int -clicon_option_del(clicon_handle h, const char *name) +clicon_option_del(clicon_handle h, + const char *name) { clicon_hash_t *copt = clicon_options(h); @@ -418,6 +533,7 @@ clicon_yang_module_revision(clicon_handle h) return clicon_option_str(h, "CLICON_YANG_MODULE_REVISION"); } +/*! Directory of backend plugins. If null, no plugins are loaded */ char * clicon_backend_dir(clicon_handle h) { @@ -456,7 +572,16 @@ clicon_xmldb_plugin(clicon_handle h) return clicon_option_str(h, "CLICON_XMLDB_PLUGIN"); } -/* get family of backend socket: AF_UNIX, AF_INET or AF_INET6 */ +int +clicon_startup_mode(clicon_handle h) +{ + char *mode; + if ((mode = clicon_option_str(h, "CLICON_STARTUP_MODE")) == NULL) + return -1; + return clicon_str2int(startup_mode_map, mode); +} + +/*! Get family of backend socket: AF_UNIX, AF_INET or AF_INET6 */ int clicon_sock_family(clicon_handle h) { @@ -508,7 +633,7 @@ clicon_master_plugin(clicon_handle h) return clicon_option_str(h, "CLICON_MASTER_PLUGIN"); } -/* return initial clicon cli mode */ +/*! Return initial clicon cli mode */ char * clicon_cli_mode(clicon_handle h) { @@ -529,7 +654,7 @@ clicon_cli_genmodel(clicon_handle h) return 0; } -/* How to generate and show CLI syntax: VARS|ALL */ +/*! How to generate and show CLI syntax: VARS|ALL */ enum genmodel_type clicon_cli_genmodel_type(clicon_handle h) { @@ -608,7 +733,7 @@ clicon_cli_genmodel_completion(clicon_handle h) return 0; } -/* Where are "running" and "candidate" databases? */ +/*! Where are "running" and "candidate" databases? */ char * clicon_xmldb_dir(clicon_handle h) { diff --git a/lib/src/clixon_proto.c b/lib/src/clixon_proto.c index ba1a4e34..fbd73cb9 100644 --- a/lib/src/clixon_proto.c +++ b/lib/src/clixon_proto.c @@ -66,6 +66,9 @@ #include "clixon_err.h" #include "clixon_log.h" #include "clixon_queue.h" +#include "clixon_hash.h" +#include "clixon_handle.h" +#include "clixon_yang.h" #include "clixon_sig.h" #include "clixon_xml.h" #include "clixon_xsl.h" @@ -130,8 +133,8 @@ struct clicon_msg * clicon_msg_encode(char *format, ...) { va_list args; - int xmllen; - int len; + uint32_t xmllen; + uint32_t len; struct clicon_msg *msg = NULL; int hdrlen = sizeof(*msg); @@ -146,7 +149,7 @@ clicon_msg_encode(char *format, ...) } memset(msg, 0, len); /* hdr */ - msg->op_len = htons(len); + msg->op_len = htonl(len); /* body */ va_start(args, format); @@ -267,7 +270,7 @@ msg_dump(struct clicon_msg *msg) memset(buf2, 0, sizeof(buf2)); snprintf(buf2, sizeof(buf2), "%s:", __FUNCTION__); - for (i=0; iop_len); i++){ + for (i=0; iop_len); i++){ snprintf(buf, sizeof(buf), "%s%02x", buf2, ((char*)msg)[i]&0xff); if ((i+1)%32==0){ clicon_debug(2, buf); @@ -294,13 +297,13 @@ clicon_msg_send(int s, int retval = -1; clicon_debug(2, "%s: send msg len=%d", - __FUNCTION__, ntohs(msg->op_len)); + __FUNCTION__, ntohl(msg->op_len)); if (debug > 2) msg_dump(msg); if (atomicio((ssize_t (*)(int, void *, size_t))write, - s, msg, ntohs(msg->op_len)) < 0){ + s, msg, ntohl(msg->op_len)) < 0){ clicon_err(OE_CFG, errno, "%s", __FUNCTION__); - clicon_log(LOG_WARNING, "%s: write: %s len:%d msg:%s", __FUNCTION__, + clicon_log(LOG_WARNING, "%s: write: %s len:%u msg:%s", __FUNCTION__, strerror(errno), ntohs(msg->op_len), msg->op_body); goto done; } @@ -309,7 +312,6 @@ clicon_msg_send(int s, return retval; } - /*! Receive a CLICON message * * XXX: timeout? and signals? @@ -333,9 +335,9 @@ clicon_msg_rcv(int s, int retval = -1; struct clicon_msg hdr; int hlen; - int len2; + uint32_t len2; sigfn_t oldhandler; - uint16_t mlen; + uint32_t mlen; *eof = 0; if (0) @@ -354,7 +356,7 @@ clicon_msg_rcv(int s, clicon_err(OE_CFG, errno, "%s: header too short (%d)", __FUNCTION__, hlen); goto done; } - mlen = ntohs(hdr.op_len); + mlen = ntohl(hdr.op_len); clicon_debug(2, "%s: rcv msg len=%d", __FUNCTION__, mlen); if ((*msg = (struct clicon_msg *)malloc(mlen)) == NULL){ @@ -362,8 +364,8 @@ clicon_msg_rcv(int s, goto done; } memcpy(*msg, &hdr, hlen); - if ((len2 = read(s, (*msg)->op_body, mlen - sizeof(hdr))) < 0){ - clicon_err(OE_CFG, errno, "%s: read", __FUNCTION__); + if ((len2 = atomicio(read, s, (*msg)->op_body, mlen - sizeof(hdr))) < 0){ + clicon_err(OE_CFG, errno, "%s: read", __FUNCTION__); goto done; } if (len2 != mlen - sizeof(hdr)){ @@ -533,17 +535,17 @@ clicon_rpc(int s, int send_msg_reply(int s, char *data, - uint16_t datalen) + uint32_t datalen) { int retval = -1; struct clicon_msg *reply = NULL; - uint16_t len; + uint32_t len; len = sizeof(*reply) + datalen; if ((reply = (struct clicon_msg *)malloc(len)) == NULL) goto done; memset(reply, 0, len); - reply->op_len = htons(len); + reply->op_len = htonl(len); if (datalen > 0) memcpy(reply->op_body, data, datalen); if (clicon_msg_send(s, reply) < 0) diff --git a/lib/src/clixon_proto_client.c b/lib/src/clixon_proto_client.c index 195feb67..8ca60598 100644 --- a/lib/src/clixon_proto_client.c +++ b/lib/src/clixon_proto_client.c @@ -100,7 +100,7 @@ clicon_rpc_msg(clicon_handle h, if (errno == ESHUTDOWN) /* Maybe could reconnect on a higher layer, but lets fail loud and proud */ - cli_set_exiting(1); + cligen_exiting_set(cli_cligen(h), 1); #endif goto done; } @@ -478,7 +478,6 @@ clicon_rpc_unlock(clicon_handle h, } /*! Get database configuration and state data - * Same as clicon_proto_change just with a cvec instead of lvec * @param[in] h CLICON handle * @param[in] xpath XPath (or "") * @param[out] xt XML tree. Free with xml_free. diff --git a/lib/src/clixon_xml.c b/lib/src/clixon_xml.c index 6416e342..2149c409 100644 --- a/lib/src/clixon_xml.c +++ b/lib/src/clixon_xml.c @@ -34,6 +34,10 @@ * XML support functions. */ +#ifdef HAVE_CONFIG_H +#include "clixon_config.h" /* generated by config & autoconf */ +#endif + #include #include #include @@ -51,7 +55,11 @@ #include "clixon_err.h" #include "clixon_log.h" #include "clixon_string.h" + #include "clixon_queue.h" +#include "clixon_hash.h" +#include "clixon_handle.h" +#include "clixon_yang.h" #include "clixon_xml.h" #include "clixon_xml_parse.h" @@ -81,6 +89,9 @@ struct xml{ void *x_spec; /* Pointer to specification, eg yang, by reference, dont free */ cg_var *x_cv; /* If body this contains the typed value */ +#if (XML_CHILD_HASH==1) + clicon_hash_t *x_hash; /* Hash of children */ +#endif }; /* Mapping between xml type <--> string */ @@ -647,6 +658,9 @@ xml_purge(cxobj *xc) int i; cxobj *xp; +#if (XML_CHILD_HASH==1) + xml_hash_op(xc, 0); +#endif if ((xp = xml_parent(xc)) != NULL){ /* Find child order i in parent*/ for (i=0; i xb (x_type=CX_BODY) + * return xb.x_value */ char * xml_body(cxobj *xn) @@ -782,12 +799,18 @@ xml_body(cxobj *xn) return NULL; } +/*! Get (first) body of xml node, note could be many + * @param[in] xt xml tree node + * Explaining picture: + * xt --> xb (x_type=CX_BODY) + * return xb + */ cxobj * -xml_body_get(cxobj *xn) +xml_body_get(cxobj *xt) { cxobj *xb = NULL; - while ((xb = xml_child_each(xn, xb, CX_BODY)) != NULL) + while ((xb = xml_child_each(xt, xb, CX_BODY)) != NULL) return xb; return NULL; } @@ -795,22 +818,27 @@ xml_body_get(cxobj *xn) /*! Find and return the value of a sub xml node * * The value can be of an attribute or body. - * @param[in] xn xml tree node + * @param[in] xt xml tree node * @param[in] name name of xml tree nod (eg attr name or "body") - * @retval The returned value as a pointer to the name string - * @retval NULL if no such node or no value in found node + * @retval val Pointer to the name string + * @retval NULL No such node or no value in node * * Note, make a copy of the return value to use it properly * See also xml_find_body + * Explaining picture: + * xt --> x + * x_name=name + * return x_value */ char * -xml_find_value(cxobj *x_up, +xml_find_value(cxobj *xt, char *name) { - cxobj *x; + cxobj *x = NULL; - if ((x = xml_find(x_up, name)) != NULL) - return xml_value(x); + while ((x = xml_child_each(xt, x, -1)) != NULL) + if (strcmp(name, xml_name(x)) == 0) + return xml_value(x); return NULL; } @@ -821,18 +849,56 @@ xml_find_value(cxobj *x_up, * @retval NULL if no such node or no body in found node * @note, make a copy of the return value to use it properly * @see xml_find_value + * Explaining picture: + * xt --> x --> bx (x_type=CX_BODY) + * x_name=name return x_value + */ char * -xml_find_body(cxobj *xn, +xml_find_body(cxobj *xt, char *name) { - cxobj *x; + cxobj *x=NULL; - if ((x = xml_find(xn, name)) != NULL) - return xml_body(x); + while ((x = xml_child_each(xt, x, -1)) != NULL) + if (strcmp(name, xml_name(x)) == 0) + return xml_body(x); return NULL; } +/*! Find xml object with matching name and value. + * + * This can be useful if x is a leaf-list with many subs with same name, + * but you need to pick the object with a specific value + * @param[in] xt XML tree + * @param[in] name Name of child (there can be many with same name) + * @param[in] val Value. Must be equal to body of child. + * @retval x Child with matching name and body + * + * Explaining picture: + * xt --> x --> bx (x_type=CX_BODY) + * x_name=name x_value=val + * return x + */ +cxobj * +xml_find_body_obj(cxobj *xt, + char *name, + char *val) +{ + cxobj *x = NULL; + char *bstr; + + while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) { + if (strcmp(name, xml_name(x))) + continue; + if ((bstr = xml_body(x)) == NULL) + continue; + if (strcmp(bstr, val) == 0) + break; /* x is returned */ + } + return x; +} + /*! Free an xl sub-tree recursively, but do not remove it from parent * @param[in] x the xml tree to be freed. * @see xml_purge where x is also removed from parent @@ -852,10 +918,15 @@ xml_free(cxobj *x) if (x->x_cv) cv_free(x->x_cv); for (i=0; ix_childvec_len; i++){ - xc = x->x_childvec[i]; - xml_free(xc); - x->x_childvec[i] = NULL; + if ((xc = x->x_childvec[i]) != NULL){ + xml_free(xc); + x->x_childvec[i] = NULL; + } } +#if (XML_CHILD_HASH==1) + if (x->x_hash) + hash_free(x->x_hash); +#endif if (x->x_childvec) free(x->x_childvec); free(x); @@ -995,7 +1066,7 @@ clicon_xml2cbuf(cbuf *cb, */ int xml_parse(char *str, - cxobj *x_up) + cxobj *xt) { int retval = -1; struct xml_parse_yacc_arg ya = {0,}; @@ -1004,7 +1075,7 @@ xml_parse(char *str, clicon_err(OE_XML, errno, "%s: strdup", __FUNCTION__); return -1; } - ya.ya_xparent = x_up; + ya.ya_xparent = xt; ya.ya_skipspace = 1; /* remove all non-terminal bodies (strip pretty-print) */ if (clixon_xml_parsel_init(&ya) < 0) goto done; @@ -1033,7 +1104,8 @@ xmltree2cbuf(cbuf *cb, for (i=0; i"); * xml_free(xt); * @endcode @@ -1375,8 +1447,8 @@ cxvec_append(cxobj *x, * @param[in] fn Callback * @param[in] arg Argument * @retval -1 Error, aborted at first error encounter - * @retval 0 OK, all nodes traversed - * @retval n OK, aborted at first encounter of first match + * @retval 0 OK, all nodes traversed (subparts may have been skipped) + * @retval 1 OK, aborted on first fn returned 1 * * @code * int x_fn(cxobj *x, void *arg) @@ -1401,12 +1473,18 @@ xml_apply(cxobj *xn, x = NULL; while ((x = xml_child_each(xn, x, type)) != NULL) { - if (fn(x, arg) < 0) + if ((ret = fn(x, arg)) < 0) goto done; + if (ret == 2) + continue; /* Abort this node, dont recurse */ + else if (ret == 1){ + retval = 1; + goto done; + } if ((ret = xml_apply(x, type, fn, arg)) < 0) goto done; - if (ret > 0){ - retval = ret; + if (ret == 1){ + retval = 1; goto done; } } @@ -1417,10 +1495,9 @@ xml_apply(cxobj *xn, /*! Apply a function call on top object and all xml node children recursively * @retval -1 Error, aborted at first error encounter - * @retval 0 OK, all nodes traversed - * @retval n OK, aborted at first encounter of first match + * @retval 0 OK, all nodes traversed (subparts may have been skipped) + * @retval 1 OK, aborted on first fn returned 1 * @see xml_apply not including top object - */ int xml_apply0(cxobj *xn, @@ -1431,11 +1508,13 @@ xml_apply0(cxobj *xn, int retval = -1; int ret; - if ((ret = fn(xn, arg)) < 0) + if ((ret = fn(xn, arg)) < 0) /* -1, 0, 1, 2 */ goto done; - if (ret > 0) - retval = ret; - else + if (ret == 1) + retval = 1; + else if (ret > 1) + retval = 0; + else /* 0 */ retval = xml_apply(xn, type, fn, arg); done: return retval; @@ -1641,6 +1720,160 @@ xml_operation2str(enum operation_type op) } } +#if (XML_CHILD_HASH==1) +/*! Return yang hash + * Not necessarily set. Either has not been set yet (by xml_spec_set( or anyxml. + */ +clicon_hash_t * +xml_hash(cxobj *x) +{ + return x->x_hash; +} + +int +xml_hash_init(cxobj *x) +{ + if ((x->x_hash = hash_init()) < 0) + return -1; + return 0; +} + +int +xml_hash_rm(cxobj *x) +{ + if (x && x->x_hash){ + hash_free(x->x_hash); + x->x_hash = NULL; + } + return 0; +} + +/* Compute hash key for xml entry + * @param[in] x + * @param[in] y + * @param[out] key + * key: yangtype+x1name + * LEAFLIST: b0 + * LIST: b2vec+b0 -> x0c + */ +int +xml_hash_key(cxobj *x, + yang_stmt *y, + cbuf *key) +{ + int retval = -1; + yang_stmt *ykey; + cvec *cvk = NULL; /* vector of index keys */ + cg_var *cvi; + char *keyname; + char *b; + char *str; + + switch (y->ys_keyword){ + case Y_CONTAINER: str = "c"; break; + case Y_LEAF: str = "e"; break; + case Y_LEAF_LIST: str = "l"; break; + case Y_LIST: str = "i"; break; + default: + str = "xx"; break; + break; + } + cprintf(key, "%s%s", str, xml_name(x)); + switch (y->ys_keyword){ + case Y_LEAF_LIST: /* Match with name and value */ + if ((b = xml_body(x)) == NULL){ + cbuf_reset(key); + goto ok; + } + cprintf(key, "%s", xml_body(x)); + break; + case Y_LIST: /* Match with key values */ + if ((ykey = yang_find((yang_node*)y, Y_KEY, NULL)) == NULL){ + clicon_err(OE_XML, errno, "%s: List statement \"%s\" has no key", + __FUNCTION__, y->ys_argument); + goto done; + } + /* The value is a list of keys: [ ]* */ + if ((cvk = yang_arg2cvec(ykey, " ")) == NULL) + goto done; + cvi = NULL; + while ((cvi = cvec_each(cvk, cvi)) != NULL){ + keyname = cv_string_get(cvi); + if ((b = xml_find_body(x, keyname)) == NULL){ + cbuf_reset(key); + goto ok; + } + cprintf(key, "/%s", b); + } + break; + default: + break; + } + ok: + retval = 0; + done: + if (cvk) + cvec_free(cvk); + return retval; +} + +/*! XML hash add. Create hash and add key/value to parent + * + * @param[in] arg -1: rm only hash 0: rm entry, 1: add + * Typically called for a whole tree. + */ +int +xml_hash_op(cxobj *x, + void *arg) +{ + int retval = -1; + cxobj *xp; + clicon_hash_t *ph; + yang_stmt *y; + cbuf *key = NULL; /* cligen buffer hash key */ + int op = (intptr_t)arg; + + if (xml_hash(x)==NULL){ + if (op==1) + xml_hash_init(x); + } + else if (op==-1|| op==0) + xml_hash_rm(x); + if (op==-1) + goto ok; + if ((xp = xml_parent(x)) == NULL) + goto ok; + if ((ph = xml_hash(xp))==NULL) + goto ok; + if ((y = xml_spec(x)) == NULL) + goto ok; + if ((key = cbuf_new()) == NULL){ + clicon_err(OE_XML, errno, "cbuf_new"); + goto done; + } + if (xml_hash_key(x, y, key) < 0) + goto done; + if (cbuf_len(key)){ + // fprintf(stderr, "%s add %s = 0x%x\n", __FUNCTION__, cbuf_get(key), (unsigned int)x); + if (op == 1){ + if (hash_add(ph, cbuf_get(key), &x, sizeof(x)) == NULL) + goto done; + } + else + if (hash_del(ph, cbuf_get(key)) < 0) + goto done; + } + ok: + retval = 0; + done: + if (key) + cbuf_free(key); + return retval; +} + +#endif + + /* * Turn this on to get a xml parse and pretty print test program * Usage: xpath diff --git a/lib/src/clixon_xml_db.c b/lib/src/clixon_xml_db.c index 19b2e12e..7e85f44c 100644 --- a/lib/src/clixon_xml_db.c +++ b/lib/src/clixon_xml_db.c @@ -42,7 +42,6 @@ #include #include #include -#include #include #include #include @@ -176,7 +175,7 @@ xmldb_plugin_unload(clicon_handle h) * @retval -1 Failed validate, xret set to error */ int -xmldb_validate_db(char *db) +xmldb_validate_db(const char *db) { if (strcmp(db, "running") != 0 && strcmp(db, "candidate") != 0 && @@ -337,7 +336,7 @@ xmldb_setopt(clicon_handle h, */ int xmldb_get(clicon_handle h, - char *db, + const char *db, char *xpath, int config, cxobj **xtop) @@ -394,7 +393,7 @@ xmldb_get(clicon_handle h, */ int xmldb_put(clicon_handle h, - char *db, + const char *db, enum operation_type op, cxobj *xt) { @@ -440,8 +439,8 @@ xmldb_put(clicon_handle h, */ int xmldb_copy(clicon_handle h, - char *from, - char *to) + const char *from, + const char *to) { int retval = -1; xmldb_handle xh; @@ -473,7 +472,7 @@ xmldb_copy(clicon_handle h, */ int xmldb_lock(clicon_handle h, - char *db, + const char *db, int pid) { int retval = -1; @@ -507,7 +506,7 @@ xmldb_lock(clicon_handle h, */ int xmldb_unlock(clicon_handle h, - char *db) + const char *db) { int retval = -1; xmldb_handle xh; @@ -556,7 +555,7 @@ xmldb_unlock_all(clicon_handle h, clicon_err(OE_DB, 0, "Not connected to datastore plugin"); goto done; } - retval =xa->xa_unlock_all_fn(xh, pid); + retval = xa->xa_unlock_all_fn(xh, pid); done: return retval; } @@ -570,7 +569,7 @@ xmldb_unlock_all(clicon_handle h, */ int xmldb_islocked(clicon_handle h, - char *db) + const char *db) { int retval = -1; xmldb_handle xh; @@ -602,7 +601,7 @@ xmldb_islocked(clicon_handle h, */ int xmldb_exists(clicon_handle h, - char *db) + const char *db) { int retval = -1; xmldb_handle xh; @@ -633,7 +632,7 @@ xmldb_exists(clicon_handle h, */ int xmldb_delete(clicon_handle h, - char *db) + const char *db) { int retval = -1; xmldb_handle xh; @@ -664,7 +663,7 @@ xmldb_delete(clicon_handle h, */ int xmldb_create(clicon_handle h, - char *db) + const char *db) { int retval = -1; xmldb_handle xh; diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 2f1ad129..bce2968f 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -464,14 +464,11 @@ xml2cvec(cxobj *xt, char *body; char *reason = NULL; int ret; - int i = 0; - int len = 0; char *name; xc = NULL; - while ((xc = xml_child_each(xt, xc, CX_ELMNT)) != NULL) - len++; - if ((cvv = cvec_new(len)) == NULL){ + /* Tried to allocate whole cvv here,but some cg_vars may be invalid */ + if ((cvv = cvec_new(0)) == NULL){ clicon_err(OE_UNIX, errno, "cvec_new"); goto err; } @@ -483,8 +480,10 @@ xml2cvec(cxobj *xt, clicon_debug(0, "%s: yang sanity problem: %s in xml but not present in yang under %s", __FUNCTION__, name, yt->ys_argument); if ((body = xml_body(xc)) != NULL){ - cv = cvec_i(cvv, i++); - cv_type_set(cv, CGV_STRING); + if ((cv = cvec_add(cvv, CGV_STRING)) == NULL){ + clicon_err(OE_PLUGIN, errno, "cvec_add"); + goto err; + } cv_name_set(cv, name); if ((ret = cv_parse1(body, cv, &reason)) < 0){ clicon_err(OE_PLUGIN, errno, "cv_parse"); @@ -498,11 +497,13 @@ xml2cvec(cxobj *xt, } } } - else - if ((ycv = ys->ys_cv) != NULL){ + else if ((ycv = ys->ys_cv) != NULL){ if ((body = xml_body(xc)) != NULL){ /* XXX: cvec_add uses realloc, can we avoid that? */ - cv = cvec_i(cvv, i++); + if ((cv = cvec_add(cvv, CGV_STRING)) == NULL){ + clicon_err(OE_PLUGIN, errno, "cvec_add"); + goto err; + } if (cv_cp(cv, ycv) < 0){ clicon_err(OE_PLUGIN, errno, "cv_cp"); goto err; @@ -567,6 +568,8 @@ cvec2xml_1(cvec *cvv, cv = NULL; i = 0; while ((cv = cvec_each(cvv, cv)) != NULL) { + if (cv_type_get(cv)==CGV_ERR || cv_name_get(cv) == NULL) + continue; if ((xn = xml_new(cv_name_get(cv), NULL)) == NULL) /* this leaks */ goto err; xml_parent_set(xn, xt); @@ -587,271 +590,262 @@ cvec2xml_1(cvec *cvv, return retval; } -/*! Return 1 if value is a body of one of the named children of xt */ -static int -xml_is_body(cxobj *xt, - char *name, - char *val) -{ - cxobj *x; - char *bx; - - x = NULL; - while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) { - if (strcmp(name, xml_name(x))) - continue; - if ((bx = xml_body(x)) == NULL) - continue; - if (strcmp(xml_body(x), val) == 0) - return 1; - } - return 0; -} - -/*! Recursive help function to compute differences between two xml trees - * @see dbdiff_vector. - */ -static int -xml_diff1(yang_stmt *ys, - cxobj *xt1, - cxobj *xt2, - cxobj ***first, - size_t *firstlen, - cxobj ***second, - size_t *secondlen, - cxobj ***changed1, - cxobj ***changed2, - size_t *changedlen) +/*! Given child tree x1c, find matching child in base tree x0 + * param[in] x0 Base tree node + * param[in] x1c Modification tree child + * param[in] yc Yang spec of tree child + * param[out] x0cp Matching base tree child (if any) + * @note XXX: room for optimization? on 1K calls we have 1M body calls and + 500K xml_child_each/cvec_each calls. + The outer loop is large for large lists + The inner loop is small + Major time in xml_find_body() + Can one do a binary search in the x0 list? +*/ +int +match_base_child(cxobj *x0, + cxobj *x1c, + cxobj **x0cp, + yang_stmt *yc) { int retval = -1; - cxobj *x1 = NULL; - cxobj *x2 = NULL; - yang_stmt *y; - yang_stmt *ykey; - char *name; - cg_var *cvi; + char *x1cname; + cxobj *x0c = NULL; /* x0 child */ cvec *cvk = NULL; /* vector of index keys */ + cg_var *cvi; + char *b0; + char *b1; + yang_stmt *ykey; char *keyname; int equal; - char *body1; - char *body2; + char **b1vec = NULL; + int i; +#if (XML_CHILD_HASH==1) + cxobj **p; + cbuf *key = NULL; /* cligen buffer hash key */ + size_t vlen; - clicon_debug(2, "%s: %s", __FUNCTION__, ys->ys_argument?ys->ys_argument:"yspec"); - /* Check nodes present in xt1 and xt2 + nodes only in xt1 - * Loop over xt1 - */ - x1 = NULL; - while ((x1 = xml_child_each(xt1, x1, CX_ELMNT)) != NULL){ - name = xml_name(x1); - if (ys->ys_keyword == Y_SPEC) - y = yang_find_topnode((yang_spec*)ys, name, 0); - else - y = yang_find_datanode((yang_node*)ys, name); - if (y == NULL){ - clicon_err(OE_UNIX, errno, "No yang node found: %s", name); + *x0cp = NULL; /* return value */ + if (xml_hash(x0) == NULL) + goto nohash; + if ((key = cbuf_new()) == NULL){ + clicon_err(OE_XML, errno, "cbuf_new"); + goto done1; + } + if (xml_hash_key(x1c, yc, key) < 0) + goto done; + x0c = NULL; + if (cbuf_len(key)) + if ((p = hash_value(xml_hash(x0), cbuf_get(key), &vlen)) != NULL){ + assert(vlen == sizeof(x0c)); + x0c = *p; + } + // fprintf(stderr, "%s get %s = 0x%x\n", __FUNCTION__, cbuf_get(key), (unsigned int)x0c); + *x0cp = x0c; + retval = 0; + done1: + if (key) + cbuf_free(key); + return retval; + nohash: +#endif /* XML_CHILD_HASH */ + *x0cp = NULL; /* return value */ + x1cname = xml_name(x1c); + switch (yc->ys_keyword){ + case Y_CONTAINER: /* Equal regardless */ + case Y_LEAF: /* Equal regardless */ + x0c = xml_find(x0, x1cname); + break; + case Y_LEAF_LIST: /* Match with name and value */ + if ((b1 = xml_body(x1c)) == NULL) + goto ok; + x0c = xml_find_body_obj(x0, x1cname, b1); + break; + case Y_LIST: /* Match with key values */ + if ((ykey = yang_find((yang_node*)yc, Y_KEY, NULL)) == NULL){ + clicon_err(OE_XML, errno, "%s: List statement \"%s\" has no key", + __FUNCTION__, yc->ys_argument); goto done; } - switch (y->ys_keyword){ - case Y_LIST: - if ((ykey = yang_find((yang_node*)y, Y_KEY, NULL)) == NULL){ - clicon_err(OE_XML, errno, "%s: List statement \"%s\" has no key", - __FUNCTION__, y->ys_argument); + /* The value is a list of keys: [ ]* */ + if ((cvk = yang_arg2cvec(ykey, " ")) == NULL) + goto done; + cvi = NULL; i = 0; + while ((cvi = cvec_each(cvk, cvi)) != NULL) + i++; + if ((b1vec = calloc(i, sizeof(b1))) == NULL){ + clicon_err(OE_UNIX, errno, "calloc"); + goto done; + } + cvi = NULL; i = 0; + while ((cvi = cvec_each(cvk, cvi)) != NULL) { + keyname = cv_string_get(cvi); + if ((b1 = xml_find_body(x1c, keyname)) == NULL){ + clicon_err(OE_UNIX, errno, "key %s not found", keyname); goto done; } - /* The value is a list of keys: [ ]* */ - if ((cvk = yang_arg2cvec(ykey, " ")) == NULL) - goto done; - /* Iterate over xt2 tree to (1) find a child that matches name - (2) that have keys that matches */ + b1vec[i++] = b1; + } + /* Iterate over x0 tree to (1) find a child that matches name + (2) that have keys that matches */ + x0c = NULL; + while ((x0c = xml_child_each(x0, x0c, CX_ELMNT)) != NULL){ equal = 0; - x2 = NULL; - while ((x2 = xml_child_each(xt2, x2, CX_ELMNT)) != NULL){ - if (strcmp(xml_name(x2), name)) - continue; - cvi = NULL; - equal = 0; - /* (2) Match keys between x1 and x2 */ - while ((cvi = cvec_each(cvk, cvi)) != NULL) { - keyname = cv_string_get(cvi); - if ((body1 = xml_find_body(x1, keyname)) == NULL) - continue; /* may be error */ - if ((body2 = xml_find_body(x2, keyname)) == NULL) - continue; /* may be error */ - if (strcmp(body1, body2)==0) - equal=1; - else{ - equal=0; /* stop as soon as inequal key found */ - break; - } - } - if (equal) /* found x1 and x2 equal, otherwise look - for other x2 */ - break; - } - if (cvk){ - cvec_free(cvk); - cvk = NULL; - } - if (equal){ - if (xml_diff1(y, x1, x2, - first, firstlen, - second, secondlen, - changed1, changed2, changedlen)< 0) - goto done; - break; - } - else - if (cxvec_append(x1, first, firstlen) < 0) - goto done; - - break; - case Y_CONTAINER: - /* Equal regardless */ - if ((x2 = xml_find(xt2, name)) == NULL){ - if (cxvec_append(x1, first, firstlen) < 0) - goto done; - break; - } - if (xml_diff1(y, x1, x2, - first, firstlen, - second, secondlen, - changed1, changed2, changedlen)< 0) - goto done; - break; - case Y_LEAF: - if ((x2 = xml_find(xt2, name)) == NULL){ - if (cxvec_append(x1, first, firstlen) < 0) - goto done; - break; - } - body1 = xml_body(x1); - body2 = xml_body(x2); - if (body1 == NULL || body2 == NULL) /* empty type */ - break; - if (strcmp(xml_body(x1), xml_body(x2))){ - if (cxvec_append(x1, changed1, changedlen) < 0) - goto done; - (*changedlen)--; /* append two vectors */ - if (cxvec_append(x2, changed2, changedlen) < 0) - goto done; - } - break; - case Y_LEAF_LIST: - if ((body1 = xml_body(x1)) == NULL) + if (strcmp(xml_name(x0c), x1cname)) continue; - if (!xml_is_body(xt2, name, body1)) /* where body is */ - if (cxvec_append(x1, first, firstlen) < 0) - goto done; - break; - default: - break; - } - } /* while xt1 */ - /* Check nodes present only in xt2 - * Loop over xt2 - */ - x2 = NULL; - while ((x2 = xml_child_each(xt2, x2, CX_ELMNT)) != NULL){ - name = xml_name(x2); - if (ys->ys_keyword == Y_SPEC) - y = yang_find_topnode((yang_spec*)ys, name, 0); - else - y = yang_find_datanode((yang_node*)ys, name); - if (y == NULL){ - clicon_err(OE_UNIX, errno, "No yang node found: %s", name); - goto done; - } - switch (y->ys_keyword){ - case Y_LIST: - if ((ykey = yang_find((yang_node*)y, Y_KEY, NULL)) == NULL){ - clicon_err(OE_XML, errno, "%s: List statement \"%s\" has no key", - __FUNCTION__, y->ys_argument); - goto done; - } - /* The value is a list of keys: [ ]* */ - if ((cvk = yang_arg2cvec(ykey, " ")) == NULL) - goto done; - /* Iterate over xt1 tree to (1) find a child that matches name - (2) that have keys that matches */ - equal = 0; - x1 = NULL; - while ((x1 = xml_child_each(xt1, x1, CX_ELMNT)) != NULL){ - if (strcmp(xml_name(x1), name)) - continue; - cvi = NULL; + /* Must be inner loop */ + cvi = NULL; i = 0; + while ((cvi = cvec_each(cvk, cvi)) != NULL) { + b1 = b1vec[i++]; equal = 0; - /* (2) Match keys between x2 and x1 */ - while ((cvi = cvec_each(cvk, cvi)) != NULL) { - keyname = cv_string_get(cvi); - if ((body2 = xml_find_body(x2, keyname)) == NULL) - continue; /* may be error */ - if ((body1 = xml_find_body(x1, keyname)) == NULL) - continue; /* may be error */ - if (strcmp(body2, body1)==0) - equal=1; - else{ - equal=0; /* stop as soon as inequal key found */ - break; - } - } - if (equal) /* found x1 and x2 equal, otherwise look - for other x2 */ - break; + keyname = cv_string_get(cvi); + if ((b0 = xml_find_body(x0c, keyname)) == NULL) + break; /* error case */ + if (strcmp(b0, b1)) + break; /* stop as soon as inequal key found */ + equal=1; /* reaches here for all keynames, x0c is found. */ } - if (cvk){ - cvec_free(cvk); - cvk = NULL; - } - if (!equal) - if (cxvec_append(x2, second, secondlen) < 0) - goto done; - break; - case Y_CONTAINER: - /* Equal regardless */ - if ((x1 = xml_find(xt1, name)) == NULL) - if (cxvec_append(x2, second, secondlen) < 0) - goto done; - break; - case Y_LEAF: - if ((x1 = xml_find(xt1, name)) == NULL) - if (cxvec_append(x2, second, secondlen) < 0) - goto done; - break; - case Y_LEAF_LIST: - body2 = xml_body(x2); - if (!xml_is_body(xt1, name, body2)) /* where body is */ - if (cxvec_append(x2, second, secondlen) < 0) - goto done; - break; - default: - break; - } - } /* while xt1 */ + if (equal) /* x0c and x1c equal, otherwise look for other */ + break; + } /* while x0c */ + break; + default: + break; + } + ok: + *x0cp = x0c; retval = 0; done: + if (b1vec) + free(b1vec); if (cvk) cvec_free(cvk); return retval; } +/*! Find next yang node, either start from yang_spec or some yang-node + * @param[in] y Node spec or sny yang-node + * @param[in] name Name of childnode to find + * @retval ys yang statement + * @retval NULL Error: no node found + */ +static yang_stmt * +yang_next(yang_node *y, + char *name) +{ + yang_stmt *ys; + + if (y->yn_keyword == Y_SPEC) + ys = yang_find_topnode((yang_spec*)y, name, 0); + else + ys = yang_find_datanode(y, name); + if (ys == NULL) + clicon_err(OE_UNIX, errno, "No yang node found: %s", name); + return ys; +} + +/*! Recursive help function to compute differences between two xml trees + * @param[in] x1 First XML tree + * @param[in] x2 Second XML tree + * @param[out] x1vec Pointervector to XML nodes existing in only first tree + * @param[out] x1veclen Length of first vector + * @param[out] x2vec Pointervector to XML nodes existing in only second tree + * @param[out] x2veclen Length of x2vec vector + * @param[out] changed_x1 Pointervector to XML nodes changed orig value + * @param[out] changed_x2 Pointervector to XML nodes changed wanted value + * @param[out] changedlen Length of changed vector + */ +static int +xml_diff1(yang_stmt *ys, + cxobj *x1, + cxobj *x2, + cxobj ***x1vec, + size_t *x1veclen, + cxobj ***x2vec, + size_t *x2veclen, + cxobj ***changed_x1, + cxobj ***changed_x2, + size_t *changedlen) +{ + int retval = -1; + cxobj *x1c = NULL; /* x1 child */ + cxobj *x2c = NULL; /* x2 child */ + yang_stmt *yc; + char *b1; + char *b2; + + clicon_debug(2, "%s: %s", __FUNCTION__, ys->ys_argument?ys->ys_argument:"yspec"); + /* Check nodes present in x1 and x2 + nodes only in x1 + * Loop over x1 + * XXX: room for improvement. Compare with match_base_child() + */ + x1c = NULL; + while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL){ + if ((yc = yang_next((yang_node*)ys, xml_name(x1c))) == NULL) + goto done; + if (match_base_child(x2, x1c, &x2c, yc) < 0) + goto done; + if (x2c == NULL){ + if (cxvec_append(x1c, x1vec, x1veclen) < 0) + goto done; + } + else{ + if (yc->ys_keyword == Y_LEAF){ + if ((b1 = xml_body(x1c)) == NULL) /* empty type */ + break; + if ((b2 = xml_body(x2c)) == NULL) /* empty type */ + break; + if (strcmp(b1, b2)){ + if (cxvec_append(x1c, changed_x1, changedlen) < 0) + goto done; + (*changedlen)--; /* append two vectors */ + if (cxvec_append(x2c, changed_x2, changedlen) < 0) + goto done; + } + } + if (xml_diff1(yc, x1c, x2c, + x1vec, x1veclen, + x2vec, x2veclen, + changed_x1, changed_x2, changedlen)< 0) + goto done; + } + } /* while x1 */ + /* Check nodes present only in x2 + * Loop over x2 + */ + x2c = NULL; + while ((x2c = xml_child_each(x2, x2c, CX_ELMNT)) != NULL){ + if ((yc = yang_next((yang_node*)ys, xml_name(x2c))) == NULL) + goto done; + if (match_base_child(x1, x2c, &x1c, yc) < 0) + goto done; + if (x1c == NULL) + if (cxvec_append(x2c, x2vec, x2veclen) < 0) + goto done; + } /* while x1 */ + retval = 0; + done: + return retval; +} + /*! Compute differences between two xml trees * @param[in] yspec Yang specification - * @param[in] xt1 First XML tree - * @param[in] xt2 Second XML tree + * @param[in] x1 First XML tree + * @param[in] x2 Second XML tree * @param[out] first Pointervector to XML nodes existing in only first tree * @param[out] firstlen Length of first vector * @param[out] second Pointervector to XML nodes existing in only second tree * @param[out] secondlen Length of second vector - * @param[out] changed1 Pointervector to XML nodes changed value - * @param[out] changed2 Pointervector to XML nodes changed value + * @param[out] changed1 Pointervector to XML nodes changed orig value + * @param[out] changed2 Pointervector to XML nodes changed wanted value * @param[out] changedlen Length of changed vector * All xml vectors should be freed after use. * Bot xml trees should be freed with xml_free() */ int xml_diff(yang_spec *yspec, - cxobj *xt1, - cxobj *xt2, + cxobj *x1, + cxobj *x2, cxobj ***first, size_t *firstlen, cxobj ***second, @@ -865,19 +859,19 @@ xml_diff(yang_spec *yspec, *firstlen = 0; *secondlen = 0; *changedlen = 0; - if (xt1 == NULL && xt2 == NULL) + if (x1 == NULL && x2 == NULL) return 0; - if (xt2 == NULL){ - if (cxvec_append(xt1, first, firstlen) < 0) + if (x2 == NULL){ + if (cxvec_append(x1, first, firstlen) < 0) goto done; goto ok; } - if (xt1 == NULL){ - if (cxvec_append(xt1, second, secondlen) < 0) + if (x1 == NULL){ + if (cxvec_append(x1, second, secondlen) < 0) goto done; goto ok; } - if (xml_diff1((yang_stmt*)yspec, xt1, xt2, + if (xml_diff1((yang_stmt*)yspec, x1, x2, first, firstlen, second, secondlen, changed1, changed2, changedlen) < 0) @@ -1002,16 +996,22 @@ yang2api_path_fmt(yang_stmt *ys, /*! Transform an xml key format and a vector of values to an XML key * Used for actual key, eg in clicon_rpc_change(), xmldb_put_xkey() * Example: - * xmlkeyfmt: /aaa/%s/name - * cvv: key=17 - * xmlkey: /aaa/17/name + * xmlkeyfmt: /interfaces/interface=%s/ipv4/address=%s + * cvv: 0 : set interfaces interface e ipv4 address 1.2.3.4 + * 1 : name = "e" + * 2 : ip = "1.2.3.4" + * api_path: /interfaces/interface=e/ipv4/address=1.2.3.4 * @param[in] api_path_fmt XML key format, eg /aaa/%s/name - * @param[in] cvv cligen variable vector, one for every wildchar in api_path_fmt + * @param[in] cvv cligen variable vector, one for every wildchar in + * api_path_fmt * @param[out] api_path api_path, eg /aaa/17. Free after use * @param[out] yang_arg yang-stmt argument name. Free after use * @note first and last elements of cvv are not used,.. - * @see cli_dbxml where this function is called - */ + * @see api_path_fmt2xpath + * + * /interfaces/interface=%s/name --> /interfaces/interface/name + * /interfaces/interface=%s/ipv4/address=%s --> /interfaces/interface=e/ipv4/address + */ int api_path_fmt2api_path(char *api_path_fmt, cvec *cvv, @@ -1025,19 +1025,19 @@ api_path_fmt2api_path(char *api_path_fmt, int j; char *str; char *strenc=NULL; - + cg_var *cv; + +#if 1 /* Sanity check */ -#if 0 j = 0; /* Count % */ for (i=0; i cvec_len(cvv)) { //cvec_len can be longer + clicon_log(LOG_WARNING, "%s api_path_fmt number of %% is %d, does not match number of cvv entries %d", api_path_fmt, j, - cvec_len(cvv), - cv_string_get(cvec_i(cvv, 0))); + cvec_len(cvv)); goto done; } #endif @@ -1052,24 +1052,30 @@ api_path_fmt2api_path(char *api_path_fmt, esc = 0; if (c!='s') continue; - if ((str = cv2str_dup(cvec_i(cvv, j++))) == NULL){ - clicon_err(OE_UNIX, errno, "strdup"); - goto done; + if (j == cvec_len(cvv)) /* last element */ + ; + else{ + cv = cvec_i(cvv, j++); + if ((str = cv2str_dup(cv)) == NULL){ + clicon_err(OE_UNIX, errno, "cv2str_dup"); + goto done; + } + if (percent_encode(str, &strenc) < 0) + goto done; + cprintf(cb, "%s", strenc); + free(strenc); strenc = NULL; + free(str); str = NULL; } - if (percent_encode(str, &strenc) < 0) - goto done; - cprintf(cb, "%s", strenc); - free(strenc); strenc = NULL; - free(str); str = NULL; } else if (c == '%') esc++; - else if (c == '/'){ - cprintf(cb, "%c", c); + else{ + if ((c == '=' || c == ',') && api_path_fmt[i+1]=='%' && j == cvec_len(cvv)) + ; /* skip */ + else + cprintf(cb, "%c", c); } - else - cprintf(cb, "%c", c); } if ((*api_path = strdup(cbuf_get(cb))) == NULL){ clicon_err(OE_UNIX, errno, "strdup"); @@ -1082,12 +1088,12 @@ api_path_fmt2api_path(char *api_path_fmt, return retval; } + /*! Transform an xml key format and a vector of values to an XML path * Used to input xmldb_get() or xmldb_get_vec * Add .* in last %s position. * Example: - * xmlkeyfmt: /interface/%s/address/%s OLDXXX - * xmlkeyfmt: /interface=%s/address=%s + * api_path_fmt: /interface/%s/address/%s * cvv: name=eth0 * xmlkey: /interface/[name=eth0]/address * Example2: @@ -1111,21 +1117,20 @@ api_path_fmt2xpath(char *api_path_fmt, int j; char *str; cg_var *cv; - int skip = 0; /* Sanity check: count '%' */ -#if 0 +#if 1 j = 0; /* Count % */ for (i=0; i cvec_len(cvv)) { clicon_log(LOG_WARNING, "%s xmlkey format string mismatch(j=%d, cvec_len=%d): %s", api_path_fmt, j, cvec_len(cvv), cv_string_get(cvec_i(cvv, 0))); - // goto done; + goto done; } #endif if ((cb = cbuf_new()) == NULL){ @@ -1139,9 +1144,7 @@ api_path_fmt2xpath(char *api_path_fmt, esc = 0; if (c!='s') continue; - if (j == cvec_len(cvv)) /* last element */ - //skip++; ; else{ cv = cvec_i(cvv, j++); @@ -1157,13 +1160,10 @@ api_path_fmt2xpath(char *api_path_fmt, if (c == '%') esc++; else{ - if (skip) - skip=0; + if ((c == '=' || c == ',') && api_path_fmt[i+1]=='%') + ; /* skip */ else - if ((c == '=' || c == ',') && api_path_fmt[i+1]=='%') - ; /* skip */ - else - cprintf(cb, "%c", c); + cprintf(cb, "%c", c); } } if ((*xpath = strdup4(cbuf_get(cb))) == NULL){ @@ -1460,8 +1460,10 @@ xml_non_config_data(cxobj *xt, /*! Add yang specification backpoint to XML node * @param[in] xt XML tree node - * @note This should really be unnecessary since yspec should be set on creation + * @param[in] arg Yang spec + * @note This may be unnecessary if yspec us set on creation * @note For subs to anyxml nodes will not have spec set + * @note No validation is done,... XXX * @code * xml_apply(xc, CX_ELMNT, xml_spec_populate, yspec) * @endcode @@ -1483,14 +1485,6 @@ xml_spec_populate(cxobj *x, y = yang_find_datanode((yang_node*)yp, xml_name(x)); else y = yang_find_topnode(yspec, name, 0); /* still NULL for config */ -#ifdef XXX_OBSOLETE /* Add validate elsewhere */ - if (y==NULL){ - clicon_err(OE_XML, EBADF, "yang spec not found for xml node '%s' xml parent name: '%s' yangspec:'%s']", - name, - xp?xml_name(xp):"", yp?yp->ys_argument:""); - goto done; - } -#endif if (y) xml_spec_set(x, y); retval = 0; @@ -1702,7 +1696,7 @@ api_path2xml_vec(char **vec, } switch (y->ys_keyword){ case Y_LEAF_LIST: - if (restval==NULL){ + if (0 && restval==NULL){ clicon_err(OE_XML, 0, "malformed key, expected '='"); goto done; } @@ -1712,7 +1706,7 @@ api_path2xml_vec(char **vec, if ((xb = xml_new("body", x)) == NULL) goto done; xml_type_set(xb, CX_BODY); - if (xml_value_set(xb, restval) < 0) + if (restval && xml_value_set(xb, restval) < 0) goto done; break; case Y_LIST: @@ -1788,11 +1782,11 @@ api_path2xml_vec(char **vec, } /*! Create xml tree from api-path - * @param[in] api_path API-path as defined in RFC 8040 - * @param[in] yspec Yang spec + * @param[in] api_path API-path as defined in RFC 8040 + * @param[in] yspec Yang spec * @param[in] schemanode If set use schema nodes otherwise data nodes. - * @param[out] xpathp Resulting xml tree - * @param[out] ypathp Yang spec matching xpathp + * @param[out] xpathp Resulting xml tree + * @param[out] ypathp Yang spec matching xpathp * @see api_path2xml_vec */ int @@ -1833,82 +1827,6 @@ api_path2xml(char *api_path, return retval; } -/*! Given a modification tree, check existing matching child in the base tree - * param[in] x0 Base tree node - * param[in] x1c Modification tree child - * param[in] yc Yang spec of tree child - * param[out] x0cp Matching base tree child (if any) -*/ -static int -match_base_child(cxobj *x0, - cxobj *x1c, - yang_stmt *yc, - cxobj **x0cp) -{ - int retval = -1; - cxobj *x0c = NULL; - char *keyname; - cvec *cvk = NULL; - cg_var *cvi; - char *b0; - char *b1; - yang_stmt *ykey; - char *cname; - int ok; - char *x1bstr; /* body string */ - - cname = xml_name(x1c); - switch (yc->ys_keyword){ - case Y_LEAF_LIST: /* Match with name and value */ - x1bstr = xml_body(x1c); - x0c = NULL; - while ((x0c = xml_child_each(x0, x0c, CX_ELMNT)) != NULL) { - if (strcmp(cname, xml_name(x0c)) == 0 && - strcmp(xml_body(x0c), x1bstr)==0) - break; - } - break; - case Y_LIST: /* Match with key values */ - if ((ykey = yang_find((yang_node*)yc, Y_KEY, NULL)) == NULL){ - clicon_err(OE_XML, errno, "%s: List statement \"%s\" has no key", - __FUNCTION__, yc->ys_argument); - goto done; - } - /* The value is a list of keys: [ ]* */ - if ((cvk = yang_arg2cvec(ykey, " ")) == NULL) - goto done; - x0c = NULL; - while ((x0c = xml_child_each(x0, x0c, CX_ELMNT)) != NULL) { - if (strcmp(xml_name(x0c), cname)) - continue; - cvi = NULL; - ok = 0; - while ((cvi = cvec_each(cvk, cvi)) != NULL) { - keyname = cv_string_get(cvi); - ok = 1; /* if we come here */ - if ((b0 = xml_find_body(x0c, keyname)) == NULL) - break; /* error case */ - if ((b1 = xml_find_body(x1c, keyname)) == NULL) - break; /* error case */ - if (strcmp(b0, b1)) - break; - ok = 2; /* and reaches here for all keynames, x0c is found. */ - } - if (ok == 2) - break; - } - break; - default: /* Just match with name */ - x0c = xml_find(x0, cname); - break; - } - *x0cp = x0c; - retval = 0; - done: - if (cvk) - cvec_free(cvk); - return retval; -} /*! Merge a base tree x0 with x1 with yang spec y * @param[in] x0 Base xml tree (can be NULL in add scenarios) @@ -1975,7 +1893,7 @@ xml_merge1(cxobj *x0, } /* See if there is a corresponding node in the base tree */ x0c = NULL; - if (yc && match_base_child(x0, x1c, yc, &x0c) < 0) + if (yc && match_base_child(x0, x1c, &x0c, yc) < 0) goto done; if (xml_merge1(x0c, (yang_node*)yc, x0, x1c) < 0) goto done; @@ -2012,7 +1930,7 @@ xml_merge(cxobj *x0, goto done; } /* See if there is a corresponding node in the base tree */ - if (match_base_child(x0, x1c, yc, &x0c) < 0) + if (match_base_child(x0, x1c, &x0c, yc) < 0) goto done; if (xml_merge1(x0c, (yang_node*)yc, x0, x1c) < 0) goto done; @@ -2074,3 +1992,70 @@ done: return retval; } +/* + * Turn this on for uni-test programs + * Usage: clixon_string join + * Example compile: + gcc -g -o clixon_xml_map -I. -I../clixon ./clixon_xml_map.c -lclixon -lcligen + * Example run: +/interfaces/interface=%s/name --> interfaces/interface/name +/interfaces/interface=%s/ipv4/address=%s e --> /interfaces/interface=e/ipv4/address +/interfaces/interface=%s,%s/ipv4/address=%s e f --> /interfaces/interface=e,f/ipv4/address +/interfaces/interface=%s/ipv4/address=%s,%s e f --> /interfaces/interface=e/ipv4/address=f + +/interfaces/interface=%s/ipv4/address=%s/prefix-length eth 1.2.3.4 --> +/interfaces/interface=eth/ipv4/address=1.2.3.4/prefix-length + +*/ +#if 0 /* Test program */ + +static int +usage(char *argv0) +{ + fprintf(stderr, "usage:%s , ,...\n", argv0); + exit(0); +} + +int +main(int argc, char **argv) +{ + int nvec; + char **vec; + char *str0; + char *str1; + int i; + char *api_path_fmt; + cg_var *cv; + cvec *cvv; + char *api_path=NULL; + + clicon_log_init(__FILE__, LOG_INFO, CLICON_LOG_STDERR); + if (argc < 2){ + usage(argv[0]); + return 0; + } + api_path_fmt = argv[1]; + if ((cvv = cvec_new(0)) == NULL){ + perror("cvec_new"); + return -1; + } + cv = cv_new(CGV_STRING); + cv_string_set(cv, "CLI base command"); + cvec_append_var(cvv, cv); + for (i=2; i /* clicon */ +#include "clixon_queue.h" +#include "clixon_hash.h" +#include "clixon_handle.h" +#include "clixon_yang.h" #include "clixon_xml.h" #include "clixon_xml_parse.h" diff --git a/lib/src/clixon_xsl.c b/lib/src/clixon_xsl.c index ff21d008..d7731de2 100644 --- a/lib/src/clixon_xsl.c +++ b/lib/src/clixon_xsl.c @@ -93,6 +93,7 @@ in #include #include #include +#include /* cligen */ #include @@ -107,7 +108,6 @@ in /* Constants */ #define XPATH_VEC_START 128 - /* * Types */ @@ -422,10 +422,12 @@ recursive_find(cxobj *xn, cxobj *xsub; cxobj **vec = *vec0; size_t veclen = *vec0len; + char *name; xsub = NULL; while ((xsub = xml_child_each(xn, xsub, node_type)) != NULL) { - if (fnmatch(pattern, xml_name(xsub), 0) == 0){ + name = xml_name(xsub); + if (fnmatch(pattern, name, 0) == 0){ clicon_debug(2, "%s %x %x", __FUNCTION__, flags, xml_flag(xsub, flags)); if (flags==0x0 || xml_flag(xsub, flags)) if (cxvec_append(xsub, &vec, &veclen) < 0) @@ -449,8 +451,8 @@ xpath_exec(cxobj *xcur, char *xpath, cxobj **vec0, size_t vec0len, /*! XPath predicate expression check * @param[in] xcur xml-tree where to search - * @param[in] predicate_expression xpath expression as a string - * @param[in] flags Extra xml flag checks that must match (apart from predicate) + * @param[in] predicate_expression xpath expression as a string + * @param[in] flags Extra xml flag checks that must match (apart from predicate) * @param[in,out] vec0 Vector or xml nodes that are checked. Not matched are filtered * @param[in,out] vec0len Length of vector or matches * On input, vec0 contains a list of xml nodes to match. @@ -483,6 +485,7 @@ xpath_expr(cxobj *xcur, char *val; char *e0; char *e; + char *name; if ((e0 = strdup(predicate_expression)) == NULL){ clicon_err(OE_UNIX, errno, "strdup"); @@ -569,7 +572,8 @@ xpath_expr(cxobj *xcur, xv = (*vec0)[i]; x = NULL; while ((x = xml_child_each(xv, x, CX_ELMNT)) != NULL) { - if (strcmp(tag, xml_name(x)) != 0) + name = xml_name(x); + if (name==NULL || strcmp(tag, name) != 0) continue; if ((val = xml_body(x)) != NULL && strcmp(val, ebody) == 0){ @@ -588,7 +592,8 @@ xpath_expr(cxobj *xcur, /* Check if more may match,... */ x = NULL; while ((x = xml_child_each(xv, x, CX_ELMNT)) != NULL) { - if (strcmp(tag, xml_name(x)) != 0) + name = xml_name(x); + if (name==NULL || strcmp(tag, name) != 0) continue; if ((val = xml_body(x)) != NULL && strcmp(val, e) == 0){ @@ -644,12 +649,11 @@ xpath_find(cxobj *xcur, cxobj *xparent; size_t vec1len = 0; struct xpath_predicate *xp; + char *name; if (xe == NULL){ - /* append */ for (i=0; ixe_str, xml_name(x), 0) == 0){ - clicon_debug(2, "%s %x %x", __FUNCTION__, flags, xml_flag(x, flags)); - if (flags==0x0 || xml_flag(x, flags)) - if (cxvec_append(x, &vec1, &vec1len) < 0) - goto done; - } + name = xml_name(x); + if (name && fnmatch(xe->xe_str, name, 0) == 0) { + clicon_debug(2, "%s %x %x", __FUNCTION__, flags, xml_flag(x, flags)); + if (flags==0x0 || xml_flag(x, flags)) + if (cxvec_append(x, &vec1, &vec1len) < 0) + goto done; + } } } + } free(vec0); vec0 = vec1; vec0len = vec1len; @@ -718,20 +724,24 @@ xpath_find(cxobj *xcur, default: break; } - /* remove duplicates */ - for (i=0; ixe_predicate; xp; xp = xp->xp_next){ if (xpath_expr(xcur, xp->xp_expr, flags, &vec0, &vec0len) < 0) goto done; } + if (xpath_find(xcur, xe->xe_next, descendants, vec0, vec0len, flags, vec2, vec2len) < 0) @@ -910,8 +920,8 @@ xpath_first0(cxobj *xcur, * ... * } * @endcode - * Note that the returned pointer points into the original tree so should not be freed - * after use. + * @note the returned pointer points into the original tree so should not be freed after use. + * @note return value does not see difference between error and not found * @see also xpath_vec. */ cxobj * @@ -978,8 +988,10 @@ xpath_each(cxobj *xcur, int i; if (xprev == NULL){ - if (vec1) // XXX - free(vec1); // XXX + if (vec1) { + free(vec1); + vec1 = NULL; + } vec1len = 0; if (xpath_choice(xcur, xpath, 0, &vec1, &vec1len) < 0) goto done; @@ -1022,10 +1034,10 @@ xpath_each(cxobj *xcur, * xn = xvec[i]; * ... * } - * free(vec); + * free(xvec); * @endcode - * @Note that although the returned vector must be freed after use, the returned xml - * trees need not be. + * @note Although the returned vector must be freed after use, + * the returned xml trees should not. * @see also xpath_first, xpath_each. */ int @@ -1035,7 +1047,7 @@ xpath_vec(cxobj *xcur, size_t *veclen, ...) { - int retval = -1; + int retval = -1; va_list ap; size_t len; char *xpath; diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index fa9e6867..c6ee54ef 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -1504,7 +1504,7 @@ yang_parse_recurse(clicon_handle h, yang_spec *ysp) { yang_stmt *yi = NULL; /* import */ - yang_stmt *ymod; + yang_stmt *ymod = NULL; yang_stmt *yrev; char *modname; char *subrevision; @@ -1527,7 +1527,7 @@ yang_parse_recurse(clicon_handle h, if ((nr = yang_parse_find_match(h, yang_dir, module, fbuf)) < 0) goto done; if (nr == 0){ - clicon_err(OE_YANG, errno, "No matching %s yang files found", module); + clicon_err(OE_YANG, errno, "No matching %s yang files found (expected modulenameor absolute filename)", module); goto done; } } @@ -1779,7 +1779,7 @@ schema_nodeid_vec(yang_node *yn, } } if (!match){ - clicon_debug(1, "%s not found", nodeid); + clicon_debug(1, "%s: %s not found", __FUNCTION__, nodeid); goto ok; } ynext = (yang_node*)ys; diff --git a/test/README.md b/test/README.md index 7d078fde..7598af48 100644 --- a/test/README.md +++ b/test/README.md @@ -1,7 +1,7 @@ # Clixon tests This directory contains testing code for clixon and the example -routing application: +routing application. Assumes setup of http daemon as describe under apps/restonf - clixon A top-level script clones clixon in /tmp and starts all.sh. You can copy this file (review it first) and place as cron script - all.sh Run through all tests named 'test*.sh' in this directory. Therefore, if you place a test in this directory matching 'test*.sh' it will be run automatically. - test_cli.sh CLI tests diff --git a/test/lib.sh b/test/lib.sh index 7194396d..46ee84c2 100755 --- a/test/lib.sh +++ b/test/lib.sh @@ -2,7 +2,7 @@ testnr=0 testnname= -clixon_cf=/usr/local/etc/routing.conf +clixon_cf=/usr/local/etc/routing.xml # error and exit, arg is optional extra errmsg err(){ echo "Error in Test$testnr [$testname]:" @@ -15,7 +15,7 @@ err(){ new(){ testnr=`expr $testnr + 1` testname=$1 - echo "Test$testnr [$1]" + >&2 echo "Test$testnr [$1]" # sleep 1 } @@ -80,6 +80,24 @@ EOF fi } +# clixon tester read from file for large tests +expecteof_file(){ + cmd=$1 + file=$2 + expect=$3 + +# Do while read stuff +ret=$($cmd<$file) + # Match if both are empty string + if [ -z "$ret" -a -z "$expect" ]; then + return + fi + match=`echo "$ret" | grep -Eo "$expect"` + if [ -z "$match" ]; then + err "$expect" "$ret" + fi +} + # clixon tester. First arg is command second is stdin and # third is expected outcome, fourth is how long to wait expectwait(){ diff --git a/test/test_cli.sh b/test/test_cli.sh index e9ca8d3c..c5d224f5 100755 --- a/test/test_cli.sh +++ b/test/test_cli.sh @@ -22,7 +22,7 @@ if [ $? -ne 0 ]; then fi new "start backend" # start new backend -sudo clixon_backend -If $clixon_cf +sudo clixon_backend -s init -f $clixon_cf if [ $? -ne 0 ]; then err fi @@ -46,8 +46,6 @@ expectfn "$clixon_cli -1f $clixon_cf set interfaces interface eth/0/0" "^$" new "cli show configuration" expectfn "$clixon_cli -1f $clixon_cf show conf cli" "^interfaces interface name eth/0/0" "interfaces interface enabled true$" - - new "cli failed validate" expectfn "$clixon_cli -1f $clixon_cf -l o validate" "Missing mandatory variable" diff --git a/test/test_leafref.sh b/test/test_leafref.sh index eea36dbc..546400cb 100755 --- a/test/test_leafref.sh +++ b/test/test_leafref.sh @@ -32,6 +32,17 @@ module example{ } } } + list sender{ + key name; + leaf name{ + type string; + } + leaf template{ + type leafref{ + path "/sender/name"; + } + } + } } EOF @@ -44,14 +55,16 @@ fi new "start backend" # start new backend -sudo clixon_backend -If $clixon_cf -y /tmp/leafref.yang +sudo clixon_backend -s init -f $clixon_cf -y /tmp/leafref.yang if [ $? -ne 0 ]; then err fi new "leafref base config" expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/leafref.yang" " -eth0 eth
192.0.2.1
192.0.2.2
lolo
127.0.0.1
]]>]]>" "^]]>]]>$" +eth0 eth
192.0.2.1
192.0.2.2
+lolo
127.0.0.1
+
]]>]]>" "^]]>]]>$" new "leafref get config" expecteof "$clixon_netconf -qf $clixon_cf" ']]>]]>' '^eth0' @@ -94,6 +107,12 @@ expectfn "$clixon_cli -1f $clixon_cf -y /tmp/leafref.yang -l o set default-addre new "cli leafref validate" expectfn "$clixon_cli -1f $clixon_cf -y /tmp/leafref.yang -l o validate" "^$" +new "cli sender" +expectfn "$clixon_cli -1f $clixon_cf -y /tmp/leafref.yang -l o set sender a" "^$" + +new "cli sender template" +expectfn "$clixon_cli -1f $clixon_cf -y /tmp/leafref.yang -l o set sender b template a" "^$" + new "Kill backend" # Check if still alive pid=`pgrep clixon_backend` diff --git a/test/test_netconf.sh b/test/test_netconf.sh index 39fd6772..67889412 100755 --- a/test/test_netconf.sh +++ b/test/test_netconf.sh @@ -16,7 +16,7 @@ if [ $? -ne 0 ]; then fi new "start backend" # start new backend -sudo clixon_backend -If $clixon_cf +sudo clixon_backend -s init -f $clixon_cf if [ $? -ne 0 ]; then err fi @@ -83,7 +83,7 @@ expecteof "$clixon_netconf -qf $clixon_cf" "< new "netconf commit" expecteof "$clixon_netconf -qf $clixon_cf" "]]>]]>" "^]]>]]>$" -new "netconf edit config replace" +new "netconf edit config replace XXX is merge?" expecteof "$clixon_netconf -qf $clixon_cf" "eth2ethmerge]]>]]>" "^]]>]]>$" new "netconf get replaced config" @@ -96,7 +96,7 @@ new "netconf edit state operation should fail" expecteof "$clixon_netconf -qf $clixon_cf" "eth1eth]]>]]>" "^invalid-value" new "netconf get state operation" -expecteof "$clixon_netconf -qf $clixon_cf" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $clixon_cf" "]]>]]>" "^eth0eth42]]>]]>$" new "netconf lock/unlock" expecteof "$clixon_netconf -qf $clixon_cf" "]]>]]>]]>]]>" "^]]>]]>]]>]]>$" diff --git a/test/test_perf.sh b/test/test_perf.sh new file mode 100755 index 00000000..fa31f4d8 --- /dev/null +++ b/test/test_perf.sh @@ -0,0 +1,119 @@ +#!/bin/bash +# Scaling test + +if [ $# = 0 ]; then + number=1000 +elif [ $# = 1 ]; then + number=$1 +else + echo "Usage: $0 []" + exit 1 +fi + +fyang=/tmp/scaling.yang +fconfig=/tmp/config + +# include err() and new() functions +. ./lib.sh + +# For memcheck +# clixon_netconf="valgrind --leak-check=full --show-leak-kinds=all clixon_netconf" +# clixon_netconf="valgrind --tool=callgrind clixon_netconf +clixon_netconf=clixon_netconf + +cat < $fyang +module ietf-ip{ + container x { + list y { + key "a"; + leaf a { + type string; + } + leaf b { + type string; + } + } + leaf-list c { + type string; + } + } +} +EOF + +# kill old backend (if any) +new "kill old backend" +sudo clixon_backend -zf $clixon_cf -y $fyang +if [ $? -ne 0 ]; then + err +fi + +new "start backend" +# start new backend +sudo clixon_backend -s init -f $clixon_cf -y $fyang +if [ $? -ne 0 ]; then + err +fi + +new "generate large list config" +echo -n "" > $fconfig +for (( i=0; i<$number; i++ )); do + echo -n "$i$i" >> $fconfig +done +echo "]]>]]>" >> $fconfig + +new "netconf edit large config" +expecteof_file "time -p $clixon_netconf -qf $clixon_cf -y $fyang" "$fconfig" "^]]>]]>$" + +new "netconf edit large config again" +expecteof_file "time -p $clixon_netconf -qf $clixon_cf -y $fyang" "$fconfig" "^]]>]]>$" + +rm $fconfig + +new "netconf commit large config" +expecteof "time -p $clixon_netconf -qf $clixon_cf -y $fyang" "]]>]]>" "^]]>]]>$" + +new "netconf add small config" +expecteof "time -p $clixon_netconf -qf $clixon_cf -y $fyang" "xy]]>]]>" "^]]>]]>$" + +new "netconf commit small config" +expecteof "time -p $clixon_netconf -qf $clixon_cf -y $fyang" "]]>]]>" "^]]>]]>$" + +new "netconf get large config" +expecteof "time -p $clixon_netconf -qf $clixon_cf -y $fyang" "]]>]]>" "^0011" + + +new "generate large leaf-list config" +echo -n "replace" > $fconfig +for (( i=0; i<$number; i++ )); do + echo -n "$i" >> $fconfig +done +echo "]]>]]>" >> $fconfig + +new "netconf replace large list-leaf config" +expecteof_file "time -p $clixon_netconf -qf $clixon_cf -y $fyang" "$fconfig" "^]]>]]>$" + +rm $fconfig + +new "netconf commit large leaf-list config" +expecteof "time -p $clixon_netconf -qf $clixon_cf -y $fyang" "]]>]]>" "^]]>]]>$" + +new "netconf add small leaf-list config" +expecteof "time -p $clixon_netconf -qf $clixon_cf -y $fyang" "x]]>]]>" "^]]>]]>$" + +new "netconf commit small leaf-list config" +expecteof "time -p $clixon_netconf -qf $clixon_cf -y $fyang" "]]>]]>" "^]]>]]>$" + +new "netconf get large leaf-list config" +expecteof "time -p $clixon_netconf -qf $clixon_cf -y $fyang" "]]>]]>" "^01" + +new "Kill backend" +# Check if still alive +pid=`pgrep clixon_backend` +if [ -z "$pid" ]; then + err "backend already dead" +fi +# kill backend +sudo clixon_backend -zf $clixon_cf +if [ $? -ne 0 ]; then + err "kill backend" +fi diff --git a/test/test_restconf.sh b/test/test_restconf.sh index 9bad8f9f..a8fc45d0 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -1,9 +1,13 @@ #!/bin/bash -# Test3: backend and restconf basic functionality +# Restconf basic functionality +# Assume http server setup, such as nginx described in apps/restconf/README.md # include err() and new() functions . ./lib.sh +# This is a fixed 'state' implemented in routing_backend. It is always there +state='{"interfaces-state": {"interface": {"name": "eth0","type": "eth","if-index": "42"}}}' + # kill old backend (if any) new "kill old backend" sudo clixon_backend -zf $clixon_cf @@ -11,7 +15,7 @@ if [ $? -ne 0 ]; then err fi new "start backend" -sudo clixon_backend -If $clixon_cf +sudo clixon_backend -s init -f $clixon_cf if [ $? -ne 0 ]; then err fi @@ -20,7 +24,7 @@ new "kill old restconf daemon" sudo pkill -u www-data clixon_restconf new "start restconf daemon" -sudo start-stop-daemon -S -q -o -b -x /www-data/clixon_restconf -d /www-data -c www-data -- -Df /usr/local/etc/routing.conf # -D +sudo start-stop-daemon -S -q -o -b -x /www-data/clixon_restconf -d /www-data -c www-data -- -Df /usr/local/etc/routing.xml # -D sleep 1 @@ -33,27 +37,30 @@ new "restconf head" expectfn "curl -sS -I http://localhost/restconf/data" "HTTP/1.1 200 OK" #Content-Type: application/yang-data+json" -new "restconf get empty config" -expectfn "curl -sSG http://localhost/restconf/data" "null" +new "restconf get empty config + state" +expectfn "curl -sSG http://localhost/restconf/data" $state + +new "restconf get state operation" +expectfn "curl -sS -G http://localhost/restconf/data/interfaces-state" $state new "restconf Add subtree to datastore using POST" expectfn 'curl -sS -X POST -d {"interfaces":{"interface":{"name":"eth/0/0","type":"eth","enabled":"true"}}} http://localhost/restconf/data' "" new "restconf Check interfaces eth/0/0 added" -expectfn "curl -sS -G http://localhost/restconf/data" '{"interfaces": {"interface": {"name": "eth/0/0","type": "eth","enabled": "true"}}} +expectfn "curl -sS -G http://localhost/restconf/data" '{"interfaces": {"interface": {"name": "eth/0/0","type": "eth","enabled": "true"}},"interfaces-state": {"interface": {"name": "eth0","type": "eth","if-index": "42"}}} $' new "restconf delete interfaces" expectfn 'curl -sS -X DELETE http://localhost/restconf/data/interfaces' "" new "restconf Check empty config" -expectfn "curl -sSG http://localhost/restconf/data" "null" +expectfn "curl -sSG http://localhost/restconf/data" $state new "restconf Add interfaces subtree eth/0/0 using POST" expectfn 'curl -sS -X POST -d {"interface":{"name":"eth/0/0","type":"eth","enabled":"true"}} http://localhost/restconf/data/interfaces' "" new "restconf Check eth/0/0 added" -expectfn "curl -sS -G http://localhost/restconf/data" '{"interfaces": {"interface": {"name": "eth/0/0","type": "eth","enabled": "true"}}} +expectfn "curl -sS -G http://localhost/restconf/data" '{"interfaces": {"interface": {"name": "eth/0/0","type": "eth","enabled": "true"}},"interfaces-state": {"interface": {"name": "eth0","type": "eth","if-index": "42"}}} $' new "restconf Re-post eth/0/0 which should generate error" @@ -70,7 +77,7 @@ new "restconf delete eth/0/0" expectfn 'curl -sS -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' "" new "Check deleted eth/0/0" -expectfn 'curl -sS -G http://localhost/restconf/data' "null" +expectfn 'curl -sS -G http://localhost/restconf/data' $state new "restconf Re-Delete eth/0/0 using none should generate error" expectfn 'curl -sS -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' "Not Found" @@ -79,10 +86,10 @@ new "restconf Add subtree eth/0/0 using PUT" expectfn 'curl -sS -X PUT -d {"interface":{"name":"eth/0/0","type":"eth","enabled":"true"}} http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' "" new "restconf get subtree" -expectfn "curl -sS -G http://localhost/restconf/data" '{"interfaces": {"interface": {"name": "eth/0/0","type": "eth","enabled": "true"}}} +expectfn "curl -sS -G http://localhost/restconf/data" '{"interfaces": {"interface": {"name": "eth/0/0","type": "eth","enabled": "true"}},"interfaces-state": {"interface": {"name": "eth0","type": "eth","if-index": "42"}}} $' -new "restconf rpc using POST json" +new "restconf operation rpc using POST json" expectfn 'curl -sS -X POST -d {"input":{"routing-instance-name":"ipv4"}} http://localhost/restconf/operations/rt:fib-route' '{ "output": { "route": { "address-family": "ipv4", "next-hop": { "next-hop-list": "2.3.4.5" } } } } ' new "restconf rpc using POST xml" diff --git a/test/test_startup.sh b/test/test_startup.sh new file mode 100755 index 00000000..99cf1afa --- /dev/null +++ b/test/test_startup.sh @@ -0,0 +1,90 @@ +#!/bin/bash +# Startup test: Start clicon daemon in the (four) different startup modes +# and the dbs and files are setup as follows: +# - The example reset_state callback adds "lo" interface +# - An extra xml configuration file starts with an "extra" interface +# - running db starts with a "run" interface +# - startup db starts with a "start" interface + +# include err() and new() functions +. ./lib.sh + +# For memcheck +# clixon_netconf="valgrind --leak-check=full --show-leak-kinds=all clixon_netconf" +clixon_netconf=clixon_netconf +clixon_cli=clixon_cli + +run(){ + mode=$1 + expect=$2 + + cat < /tmp/db + + + + run + eth + + + +EOF + sudo mv /tmp/db /usr/local/var/routing/running_db + + cat < /tmp/db + + + + startup + eth + + + +EOF + sudo mv /tmp/db /usr/local/var/routing/startup_db + + cat < /tmp/config + + + + extra + eth + + + +EOF + + # kill old backend (if any) + new "kill old backend" + sudo clixon_backend -zf $clixon_cf + if [ $? -ne 0 ]; then + err + fi + + new "start backend" + # start new backend + sudo clixon_backend -f $clixon_cf -s $mode -c /tmp/config + if [ $? -ne 0 ]; then + err + fi + + new "Check $mode" + expecteof "$clixon_netconf -qf $clixon_cf" ']]>]]>' "^$expect]]>]]>$" + + new "Kill backend" + # Check if still alive + pid=`pgrep clixon_backend` + if [ -z "$pid" ]; then + err "backend already dead" + fi + # kill backend + sudo clixon_backend -zf $clixon_cf + if [ $? -ne 0 ]; then + err "kill backend" + fi +} + +run init '' +run none 'runethtrue' +run running 'runethtruelolocaltrueextraethtrue' +run startup 'startupethtruelolocaltrueextraethtrue' + diff --git a/test/test_type.sh b/test/test_type.sh index 8cfb3305..988dedc6 100755 --- a/test/test_type.sh +++ b/test/test_type.sh @@ -69,7 +69,7 @@ if [ $? -ne 0 ]; then fi new "start backend" # start new backend -sudo clixon_backend -If $clixon_cf -y /tmp/type.yang +sudo clixon_backend -s init -f $clixon_cf -y /tmp/type.yang if [ $? -ne 0 ]; then err fi diff --git a/test/test_yang.sh b/test/test_yang.sh index 018bf446..779cb84a 100755 --- a/test/test_yang.sh +++ b/test/test_yang.sh @@ -3,12 +3,29 @@ # include err() and new() functions . ./lib.sh +clixon_cf=/tmp/conf_yang.xml # For memcheck # clixon_netconf="valgrind --leak-check=full --show-leak-kinds=all clixon_netconf" clixon_netconf=clixon_netconf clixon_cli=clixon_cli +cat < /tmp/conf_yang.xml + + /tmp/test_yang.xml + /usr/local/share/routing/yang + example + /usr/local/lib/routing/clispec + /usr/local/lib/routing/cli + routing + /usr/local/var/routing/routing.sock + /usr/local/var/routing/routing.pidfile + 1 + /usr/local/var/routing + /usr/local/lib/xmldb/text.so + +EOF + cat < /tmp/test.yang module example{ container x { @@ -70,7 +87,7 @@ fi new "start backend" # start new backend -sudo clixon_backend -If $clixon_cf -y /tmp/test.yang +sudo clixon_backend -s init -f $clixon_cf -y /tmp/test.yang if [ $? -ne 0 ]; then err fi diff --git a/yang/Makefile.in b/yang/Makefile.in new file mode 100644 index 00000000..59bc5c6e --- /dev/null +++ b/yang/Makefile.in @@ -0,0 +1,69 @@ +# +# ***** BEGIN LICENSE BLOCK ***** +# +# Copyright (C) 2009-2017 Olof Hagsand and Benny Holmgren +# +# This file is part of CLIXON +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Alternatively, the contents of this file may be used under the terms of +# the GNU General Public License Version 3 or later (the "GPL"), +# in which case the provisions of the GPL are applicable instead +# of those above. If you wish to allow use of your version of this file only +# under the terms of the GPL, and not to allow others to +# use your version of this file under the terms of Apache License version 2, +# indicate your decision by deleting the provisions above and replace them with +# the notice and other provisions required by the GPL. If you do not delete +# the provisions above, a recipient may use your version of this file under +# the terms of any one of the Apache License version 2 or the GPL. +# +# ***** END LICENSE BLOCK ***** +# +VPATH = @srcdir@ +srcdir = @srcdir@ +top_srcdir = @top_srcdir@ +prefix = @prefix@ +bindir = @bindir@ +includedir = @includedir@ +datarootdir = @datarootdir@ + +YANGSPECS = clixon-config@2017-07-02.yang +YANGSPECS += ietf-netconf@2011-06-01.yang + +APPNAME = clixon # subdir ehere these files are installed + +all: + +# Note: clixon.mk has a rule for: +-include $(DESTDIR)$(datarootdir)/clixon/clixon.mk + +clean: + +distclean: clean + rm -f Makefile *~ .depend + +install: $(YANGSPECS) + install -d $(DESTDIR)/yang + install $(YANGSPECS) $(DESTDIR)$(clixon_DATADIR) + +uninstall: + (cd $(DESTDIR)$(clixon_DATADIR); rm -rf $(YANGSPECS)) + +install-include: + +depend: + + +#include .depend + diff --git a/yang/clixon-config@2017-07-02.yang b/yang/clixon-config@2017-07-02.yang index ed704545..21497713 100644 --- a/yang/clixon-config@2017-07-02.yang +++ b/yang/clixon-config@2017-07-02.yang @@ -38,18 +38,41 @@ ***** END LICENSE BLOCK *****"; - revision 2017-07-02 { + revision 2017-11-12 { description - "Initial revision"; + "Added startup config"; } + typedef startup_mode{ + description "Which method to boot/start clicon backend. + The methods differ in how they reach a running state + Which source database to commit from, if any."; + type enumeration{ + enum none{ + description "Do not touch running state + Typically after crash when running state and db are synched"; + } + enum init{ + description "Initialize running state. + Start with a completely clean running state"; + } + enum running{ + description "Commit running db configuration into running state + After reboot if a persistent running db exists"; + } + enum startup{ + description "Commit startup configuration into running state + After reboot when no persistent running db exists"; + } + } + } + container config { leaf CLICON_CONFIGFILE{ type string; - default "sysconfdir/$APPNAME.conf"; description "Location of configuration-file for default values (this file)"; } leaf CLICON_YANG_DIR { type string; - default "prefix/share/$APPNAME/yang"; + mandatory true; description "Location of YANG module and submodule files. Only if CLICON_DBSPEC_TYPE is YANG"; } leaf CLICON_YANG_MODULE_MAIN { @@ -66,34 +89,73 @@ } leaf CLICON_BACKEND_DIR { type string; - default "libdir/$APPNAME/backend"; - description "Location of backend .so plugins"; + description "Location of backend .so plugins. Load all .so + plugins in this dir as backend plugins"; } leaf CLICON_NETCONF_DIR { type string; - default "libdir/$APPNAME/netconf"; description "Location of netconf (frontend) .so plugins"; } leaf CLICON_RESTCONF_DIR { type string; - default "libdir/$APPNAME/restconf"; - description "Location of restconf (frontend) .so plugins"; + description "Location of restconf (frontend) .so plugins. Load all .so + plugins in this dir as restconf code plugins"; + } + leaf CLICON_RESTCONF_PATH { + type string; + default "/www-data/fastcgi_restconf.sock"; + description "FastCGI unix socket. Should be specified in webserver + Eg in nginx: fastcgi_pass unix:/www-data/clicon_restconf.sock"; } leaf CLICON_CLI_DIR { type string; - default "libdir/$APPNAME/cli"; - description "Location of cli frontend .so plugins"; + description "Location of cli frontend .so plugins. Load all .so + plugins in this dir as CLI object plugins"; } leaf CLICON_CLISPEC_DIR { type string; - default "libdir/$APPNAME/clispec"; - description "Location of frontend .cli cligen spec files"; + description "Location of frontend .cli cligen spec files. Load all .cli + files in this dir as CLI specification files"; } - leaf CLICON_USE_STARTUP_CONFIG { + leaf CLICON_CLISPEC_FILE { + type string; + description "Specific frontend .cli cligen spec file."; + } + leaf CLICON_CLI_MODE { + type string; + default "base"; + description "Startup CLI mode. This should match a CLICON_MODE set in + one of the clispec files"; + } + leaf CLICON_CLI_GENMODEL { type int32; - default 0; - description "Enabled uses \"startup\" configuration on boot"; + default 1; + description "Generate code for CLI completion of existing db symbols. + Example: Add name=\"myspec\" in datamodel spec and reference + as @myspec"; } + leaf CLICON_CLI_GENMODEL_COMPLETION { + type int32; + default 1; + description "Generate code for CLI completion of existing db symbols"; + } + leaf CLICON_CLI_GENMODEL_TYPE { + type string; + default "VARS"; + description "How to generate and show CLI syntax: VARS|ALL"; + } + leaf CLICON_CLI_VARONLY { + type int32; + default 1; + description "Dont include keys in cvec in cli vars callbacks, ie a & k in 'a k ' ignored"; + } + leaf CLICON_CLI_LINESCROLLING { + type int32; + default 1; + description "Set to 0 if you want CLI to wrap to next line. + Set to 1 if you want CLI to scroll sideways when approaching right margin"; + } + leaf CLICON_SOCK_FAMILY { type string; default "UNIX"; @@ -101,25 +163,25 @@ } leaf CLICON_SOCK { type string; - default "localstatedir/$APPNAME/$APPNAME.sock"; - description "If family above is AF_UNIX: Unix socket for communicating with - clixon_backend. If family above is AF_INET: IPv4 address"; + mandatory true; + description "If family above is AF_UNIX: Unix socket for communicating + with clixon_backend. If family is AF_INET: IPv4 address"; } leaf CLICON_SOCK_PORT { type int32; default 4535; description "Inet socket port for communicating with clixon_backend (only IPv4|IPv6)"; } - leaf CLICON_BACKEND_PIDFILE { - type string; - default "localstatedir/$APPNAME/$APPNAME.pidfile"; - description "Process-id file"; - } leaf CLICON_SOCK_GROUP { type string; default "clicon"; description "Group membership to access clixon_backend unix socket"; } + leaf CLICON_BACKEND_PIDFILE { + type string; + mandatory true; + description "Process-id file"; + } leaf CLICON_AUTOCOMMIT { type int32; default 0; @@ -131,49 +193,28 @@ default "master"; description "Name of master plugin (both frontend and backend). Master plugin has special callbacks for frontends. - See clicon user manual for more info."; - } - leaf CLICON_CLI_MODE { - type string; - default "base"; - description "Startup CLI mode. This should match the CLICON_MODE in your startup clispec file"; - } - leaf CLICON_CLI_GENMODEL { - type int32; - default 1; - description "Generate code for CLI completion of existing db symbols. - Add name=\"myspec\" in datamodel spec and reference as @myspec"; - } - leaf CLICON_CLI_GENMODEL_COMPLETION { - type int32; - default 0; - description "Generate code for CLI completion of existing db symbols"; - } - leaf CLICON_CLI_GENMODEL_TYPE { - type string; - default "VARS"; - description "How to generate and show CLI syntax: VARS|ALL"; + See clicon user manual for more info. (Obsolete)"; } leaf CLICON_XMLDB_DIR { type string; - default "localstatedir/$APPNAME"; + mandatory true; description "Directory where \"running\", \"candidate\" and \"startup\" are placed"; } + leaf CLICON_USE_STARTUP_CONFIG { + type int32; + default 0; + description "Enabled uses \"startup\" configuration on boot. It is called + startup_db and exists in XMLDB_DIR. NOTE: Obsolete with CLICON_STARTUP_MODE"; + } leaf CLICON_XMLDB_PLUGIN { type string; - default "libdir/xmldb/text.so"; + mandatory true; description "XMLDB datastore plugin filename (see datastore/ and clixon_xml_db.[ch])"; } - leaf CLICON_CLI_VARONLY { - type int32; - default 1; - description "Dont include keys in cvec in cli vars callbacks, ie a & k in 'a k ' ignored"; + leaf CLICON_STARTUP_MODE { + type startup_mode; + description "Which method to boot/start clicon backend"; } - leaf CLICON_RESTCONF_PATH { - type string; - default "/www-data/fastcgi_restconf.sock"; - description "FastCGI unix socket. Should be specified in webserver - Eg in nginx: fastcgi_pass unix:/www-data/clicon_restconf.sock;"; - } + } }