diff --git a/CHANGELOG.md b/CHANGELOG.md index 8eccb09d..83df7faa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,9 @@ ## 3.4.0 (Upcoming) -* Datastore cache introduced: cache XML tree in memory for faster get access +* Better semantic versioning, eg MAJOR/MINOR/PATCH, where increment in PATCH does not change API. + +* Datastore cache introduced: cache XML tree in memory for faster get access. Use CLICON_XMLDB_CACHE configuration option. Default is 1. * Moved XML_CHILD_HASH to variable instead of constant. diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c index cb0cc3b5..e7be22f5 100644 --- a/apps/backend/backend_main.c +++ b/apps/backend/backend_main.c @@ -617,6 +617,7 @@ main(int argc, char **argv) char *sock; int sockfamily; char *xmldb_plugin; + int xml_cache; /* In the startup, logs to stderr & syslog and debug flag set later */ clicon_log_init(__PROGRAM__, LOG_INFO, CLICON_LOG_STDERR|CLICON_LOG_SYSLOG); @@ -807,7 +808,8 @@ main(int argc, char **argv) "or create the group and add the user to it. On linux for example:" " sudo groupadd %s\n" " sudo usermod -a -G %s user\n", - config_group, clicon_configfile(h), config_group, config_group); + config_group, clicon_configfile(h), + config_group, config_group); return -1; } @@ -829,6 +831,9 @@ main(int argc, char **argv) goto done; if (xmldb_setopt(h, "yangspec", clicon_dbspec_yang(h)) < 0) goto done; + if ((xml_cache = clicon_option_bool(h, "CLICON_XMLDB_CACHE")) >= 0) + if (xmldb_setopt(h, "xml_cache", (void*)xml_cache) < 0) + goto done; /* 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 */ diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c index 564ce423..d7de2953 100644 --- a/apps/cli/cli_common.c +++ b/apps/cli/cli_common.c @@ -837,7 +837,7 @@ save_config_file(clicon_handle h, clicon_rpc_generate_error("Get configuration", xerr); goto done; } - if ((f = fopen(filename, "wb")) == NULL){ + if ((f = fopen(filename, "w")) == NULL){ clicon_err(OE_CFG, errno, "Creating file %s", filename); goto done; } diff --git a/apps/cli/cli_main.c b/apps/cli/cli_main.c index 972ee5d9..f03c5284 100644 --- a/apps/cli/cli_main.c +++ b/apps/cli/cli_main.c @@ -356,7 +356,7 @@ main(int argc, char **argv) clicon_option_str_set(h, "CLICON_CLI_MODE", optarg); break; case 'q' : /* Quiet mode */ - clicon_option_str_set(h, "CLICON_QUIET", "on"); + clicon_quiet_mode_set(h, 1); break; case 'p' : /* Print spec */ printspec++; diff --git a/datastore/text/clixon_xmldb_text.c b/datastore/text/clixon_xmldb_text.c index 429329f8..11b65e54 100644 --- a/datastore/text/clixon_xmldb_text.c +++ b/datastore/text/clixon_xmldb_text.c @@ -95,7 +95,7 @@ struct db_element{ * Assumes single backend * Experimental */ -static int datastore_cache = 1; +static int xmltree_cache = 1; /*! Check struct magic number for sanity checks * return 0 if OK, -1 if fail. @@ -193,7 +193,7 @@ text_disconnect(xmldb_handle xh) if (th->th_dbdir) free(th->th_dbdir); if (th->th_dbs){ - if (datastore_cache){ + if (xmltree_cache){ if ((keys = hash_keys(th->th_dbs, &klen)) == NULL) return 0; for(i = 0; i < klen; i++) @@ -264,6 +264,9 @@ text_setopt(xmldb_handle xh, goto done; } } + else if (strcmp(optname, "xml_cache") == 0){ + xmltree_cache = (intptr_t)value; + } else{ clicon_err(OE_PLUGIN, 0, "Option %s not implemented by plugin", optname); goto done; @@ -389,21 +392,6 @@ xml_copy_marked(cxobj *x0, * xpath. * This is a clixon datastore plugin of the the xmldb api * @see xmldb_get -#ifdef DATASTORE_CACHE -text_get 90% - clixon_xml_parse_file 74% (100x) - xml_parse 70% (100x) - clicon_xml_parseparse 70% (300x) - xml_value_append 13% (800K) - xml_new - xml_purge - clicon_xml_parselex 12% (1M) - clixon_tree_prune_flagged_sub 12% (100x) - xml_purge 12% (58000) - yang_key_match 7% (77000) - yang_arg2cvec -#endif - * */ int text_get(xmldb_handle xh, @@ -427,7 +415,7 @@ text_get(xmldb_handle xh, clicon_err(OE_YANG, ENOENT, "No yang spec"); goto done; } - if (datastore_cache){ + if (xmltree_cache){ if ((de = hash_value(th->th_dbs, db, NULL)) != NULL) xt = de->de_xml; } @@ -471,11 +459,11 @@ text_get(xmldb_handle xh, if (xvec != NULL) for (i=0; ith_dbs, db, NULL)) != NULL) x0 = de->de_xml; } @@ -921,7 +909,7 @@ text_put(xmldb_handle xh, goto done; /* Write back to datastore cache if first time */ - if (datastore_cache){ + if (xmltree_cache){ struct db_element de0 = {0,}; if (de != NULL) de0 = *de; @@ -930,7 +918,28 @@ text_put(xmldb_handle xh, hash_add(th->th_dbs, db, &de0, sizeof(de0)); } } - + if (dbfile == NULL){ + if (text_db2file(th, db, &dbfile) < 0) + goto done; + if (dbfile==NULL){ + clicon_err(OE_XML, 0, "dbfile NULL"); + goto done; + } + } +#if 1 + if (fd != -1) + close(fd); + { + FILE *f; + if ((f = fopen(dbfile, "w")) == NULL){ + clicon_err(OE_CFG, errno, "Creating file %s", dbfile); + goto done; + } + if (xml_print(f, x0) < 0) + goto done; + fclose(f); + } +#else // output: /* Print out top-level xml tree after modification to file */ if ((cb = cbuf_new()) == NULL){ @@ -942,14 +951,6 @@ text_put(xmldb_handle xh, /* Reopen file in write mode */ if (fd != -1) close(fd); - if (dbfile == NULL){ - if (text_db2file(th, db, &dbfile) < 0) - goto done; - if (dbfile==NULL){ - clicon_err(OE_XML, 0, "dbfile NULL"); - goto done; - } - } if ((fd = open(dbfile, O_WRONLY | O_TRUNC, S_IRWXU)) < 0) { clicon_err(OE_UNIX, errno, "open(%s)", dbfile); goto done; @@ -958,6 +959,7 @@ text_put(xmldb_handle xh, clicon_err(OE_UNIX, errno, "write(%s)", dbfile); goto done; } +#endif retval = 0; done: if (dbfile) @@ -966,7 +968,7 @@ text_put(xmldb_handle xh, close(fd); if (cb) cbuf_free(cb); - if (!datastore_cache && x0) + if (!xmltree_cache && x0) xml_free(x0); return retval; } @@ -981,23 +983,45 @@ text_put(xmldb_handle xh, int text_copy(xmldb_handle xh, const char *from, - const char *to) + const char *to) { int retval = -1; struct text_handle *th = handle(xh); char *fromfile = NULL; char *tofile = NULL; struct db_element *de = NULL; + struct db_element *de2 = NULL; /* XXX lock */ - if (datastore_cache){ - /* Just invalidate xml if exists in TO */ + if (xmltree_cache){ + /* 1. Free xml tree in "to" + + */ if ((de = hash_value(th->th_dbs, to, NULL)) != NULL){ if (de->de_xml != NULL){ xml_free(de->de_xml); de->de_xml = NULL; } } + /* 2. Copy xml tree from "from" to "to" + * 2a) create "to" if it does not exist + */ + if ((de2 = hash_value(th->th_dbs, from, NULL)) != NULL){ + if (de2->de_xml != NULL){ + struct db_element de0 = {0,}; + cxobj *x, *xcopy; + x = de2->de_xml; + if (de != NULL) + de0 = *de; + if ((xcopy = xml_new(xml_name(x), NULL, xml_spec(x))) == NULL) + goto done; + if (xml_copy(x, xcopy) < 0) + goto done; + de0.de_xml = xcopy; + hash_add(th->th_dbs, to, &de0, sizeof(de0)); + } + } + } if (text_db2file(th, from, &fromfile) < 0) goto done; @@ -1153,7 +1177,7 @@ text_delete(xmldb_handle xh, struct db_element *de = NULL; cxobj *xt = NULL; - if (datastore_cache){ + if (xmltree_cache){ if ((de = hash_value(th->th_dbs, db, NULL)) != NULL){ if ((xt = de->de_xml) != NULL){ xml_free(xt); @@ -1192,7 +1216,7 @@ text_create(xmldb_handle xh, struct db_element *de = NULL; cxobj *xt = NULL; - if (datastore_cache){ /* XXX This should nt really happen? */ + if (xmltree_cache){ /* XXX This should nt really happen? */ if ((de = hash_value(th->th_dbs, db, NULL)) != NULL){ if ((xt = de->de_xml) != NULL){ assert(xt==NULL); /* XXX */ diff --git a/lib/clixon/clixon_options.h b/lib/clixon/clixon_options.h index cbd2647f..a94206a5 100644 --- a/lib/clixon/clixon_options.h +++ b/lib/clixon/clixon_options.h @@ -74,53 +74,92 @@ enum startup_mode_t{ /* * Prototypes */ + +/* Print registry on file. For debugging. */ +void clicon_option_dump(clicon_handle h, int dblevel); /* Initialize options: set defaults, read config-file, etc */ int clicon_options_main(clicon_handle h); - -void clicon_option_dump(clicon_handle h, int dblevel); - +/*! Check if a clicon option has a value */ int clicon_option_exists(clicon_handle h, const char *name); -/* Get a single option via handle */ +/* String options, default NULL */ char *clicon_option_str(clicon_handle h, const char *name); -int clicon_option_int(clicon_handle h, const char *name); -/* Set a single option via handle */ int clicon_option_str_set(clicon_handle h, const char *name, char *val); + +/* Option values gixen as int, default -1 */ +int clicon_option_int(clicon_handle h, const char *name); int clicon_option_int_set(clicon_handle h, const char *name, int val); + +/* Option values gixen as bool, default false */ +int clicon_option_bool(clicon_handle h, const char *name); +int clicon_option_bool_set(clicon_handle h, const char *name, int val); + /* Delete a single option via handle */ int clicon_option_del(clicon_handle h, const char *name); -char *clicon_configfile(clicon_handle h); -char *clicon_yang_dir(clicon_handle h); -char *clicon_yang_module_main(clicon_handle h); -char *clicon_yang_module_revision(clicon_handle h); -char *clicon_backend_dir(clicon_handle h); -char *clicon_cli_dir(clicon_handle h); -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); -char *clicon_backend_pidfile(clicon_handle h); -char *clicon_sock_group(clicon_handle h); +/*-- Standard option access functions for YANG options --*/ +static inline char *clicon_configfile(clicon_handle h){ + return clicon_option_str(h, "CLICON_CONFIGFILE"); +} +static inline char *clicon_yang_dir(clicon_handle h){ + return clicon_option_str(h, "CLICON_YANG_DIR"); +} +static inline char *clicon_yang_module_main(clicon_handle h){ + return clicon_option_str(h, "CLICON_YANG_MODULE_MAIN"); +} +static inline char *clicon_yang_module_revision(clicon_handle h){ + return clicon_option_str(h, "CLICON_YANG_MODULE_REVISION"); +} +static inline char *clicon_backend_dir(clicon_handle h){ + return clicon_option_str(h, "CLICON_BACKEND_DIR"); +} +static inline char *clicon_netconf_dir(clicon_handle h){ + return clicon_option_str(h, "CLICON_NETCONF_DIR"); +} +static inline char *clicon_restconf_dir(clicon_handle h){ + return clicon_option_str(h, "CLICON_RESTCONF_DIR"); +} +static inline char *clicon_cli_dir(clicon_handle h){ + return clicon_option_str(h, "CLICON_CLI_DIR"); +} +static inline char *clicon_clispec_dir(clicon_handle h){ + return clicon_option_str(h, "CLICON_CLISPEC_DIR"); +} +static inline char *clicon_cli_mode(clicon_handle h){ + return clicon_option_str(h, "CLICON_CLI_MODE"); +} +static inline char *clicon_sock(clicon_handle h){ + return clicon_option_str(h, "CLICON_SOCK"); +} +static inline char *clicon_sock_group(clicon_handle h){ + return clicon_option_str(h, "CLICON_SOCK_GROUP"); +} +static inline char *clicon_backend_pidfile(clicon_handle h){ + return clicon_option_str(h, "CLICON_BACKEND_PIDFILE"); +} +static inline char *clicon_master_plugin(clicon_handle h){ + return clicon_option_str(h, "CLICON_MASTER_PLUGIN"); +} +static inline char *clicon_xmldb_dir(clicon_handle h){ + return clicon_option_str(h, "CLICON_XMLDB_DIR"); +} +static inline char *clicon_xmldb_plugin(clicon_handle h){ + return clicon_option_str(h, "CLICON_XMLDB_PLUGIN"); +} -char *clicon_master_plugin(clicon_handle h); -char *clicon_cli_mode(clicon_handle h); +/*-- Specific option access functions for YANG options w type conversion--*/ int clicon_cli_genmodel(clicon_handle h); -int clicon_cli_varonly(clicon_handle h); -int clicon_cli_varonly_set(clicon_handle h, int val); int clicon_cli_genmodel_completion(clicon_handle h); - -char *clicon_xmldb_dir(clicon_handle h); - -char *clicon_quiet_mode(clicon_handle h); enum genmodel_type clicon_cli_genmodel_type(clicon_handle h); +int clicon_cli_varonly(clicon_handle h); +int clicon_sock_family(clicon_handle h); +int clicon_sock_port(clicon_handle h); +int clicon_autocommit(clicon_handle h); +int clicon_startup_mode(clicon_handle h); -int clicon_autocommit(clicon_handle h); -int clicon_autocommit_set(clicon_handle h, int val); +/*-- Specific option access functions for non-yang options --*/ +int clicon_quiet_mode(clicon_handle h); +int clicon_quiet_mode_set(clicon_handle h, int val); yang_spec * clicon_dbspec_yang(clicon_handle h); int clicon_dbspec_yang_set(clicon_handle h, struct yang_spec *ys); @@ -128,16 +167,13 @@ int clicon_dbspec_yang_set(clicon_handle h, struct yang_spec *ys); char *clicon_dbspec_name(clicon_handle h); int clicon_dbspec_name_set(clicon_handle h, char *name); +plghndl_t clicon_xmldb_plugin_get(clicon_handle h); int clicon_xmldb_plugin_set(clicon_handle h, plghndl_t handle); -plghndl_t clicon_xmldb_plugin_get(clicon_handle h); - +void *clicon_xmldb_api_get(clicon_handle h); int clicon_xmldb_api_set(clicon_handle h, void *xa_api); -void *clicon_xmldb_api_get(clicon_handle h); - +void *clicon_xmldb_handle_get(clicon_handle h); int clicon_xmldb_handle_set(clicon_handle h, void *xh); -void *clicon_xmldb_handle_get(clicon_handle h); - #endif /* _CLIXON_OPTIONS_H_ */ diff --git a/lib/src/clixon_options.c b/lib/src/clixon_options.c index 14146f92..89247bfa 100644 --- a/lib/src/clixon_options.c +++ b/lib/src/clixon_options.c @@ -495,6 +495,49 @@ clicon_option_int_set(clicon_handle h, return clicon_option_str_set(h, name, s); } +/*! Get options as bool but stored as string + * + * @param h clicon handle + * @param name name of option + * @retval 0 false, or does not exist, or does not have a boolean value + * @retval 1 true + * @code + * if (clicon_option_exists(h, "X") + * return clicon_option_bool(h, "X"); + * else + * return 0; # default false? + * @endcode + * Note that 0 can be both error and false. + * This means that it should be used together with clicon_option_exists() and + * supply a default value as shown in the example. + */ +int +clicon_option_bool(clicon_handle h, + const char *name) +{ + char *s; + + if ((s = clicon_option_str(h, name)) == NULL) + return 0; + if (strcmp(s,"true")==0) + return 1; + return 0; /* Hopefully false, but anything else than "true" */ +} + +/*! Set option given as bool + */ +int +clicon_option_bool_set(clicon_handle h, + const char *name, + int val) +{ + char s[64]; + + if (snprintf(s, sizeof(s)-1, "%u", val) < 0) + return -1; + return clicon_option_str_set(h, name, s); +} + /*! Delete option */ int @@ -507,146 +550,18 @@ clicon_option_del(clicon_handle h, } /*----------------------------------------------------------------- - * Specific option access functions. - * These should be commonly accessible for all users of clicon lib + * Specific option access functions for YANG configuration variables. + * Sometimes overridden by command-line options, + * such as -f for CLICON_CONFIGFILE + * @see yang/clixon-config@.yang + * You can always use the basic access functions, such as + * clicon_option_str[_set] + * But sometimes there are type conversions, etc which makes it more + * convenient to make wrapper functions. Or not? *-----------------------------------------------------------------*/ - -char * -clicon_configfile(clicon_handle h) -{ - return clicon_option_str(h, "CLICON_CONFIGFILE"); -} - -/*! YANG database specification directory */ -char * -clicon_yang_dir(clicon_handle h) -{ - return clicon_option_str(h, "CLICON_YANG_DIR"); -} - -/*! YANG main module or absolute file name */ -char * -clicon_yang_module_main(clicon_handle h) -{ - return clicon_option_str(h, "CLICON_YANG_MODULE_MAIN"); -} - -/*! YANG revision */ -char * -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) -{ - return clicon_option_str(h, "CLICON_BACKEND_DIR"); -} - -/* contains .so files CLICON_CLI_DIR */ -char * -clicon_cli_dir(clicon_handle h) -{ - return clicon_option_str(h, "CLICON_CLI_DIR"); -} - -/* contains .cli files - CLICON_CLISPEC_DIR */ -char * -clicon_clispec_dir(clicon_handle h) -{ - return clicon_option_str(h, "CLICON_CLISPEC_DIR"); -} - -char * -clicon_netconf_dir(clicon_handle h) -{ - return clicon_option_str(h, "CLICON_NETCONF_DIR"); -} - -char * -clicon_restconf_dir(clicon_handle h) -{ - return clicon_option_str(h, "CLICON_RESTCONF_DIR"); -} - -char * -clicon_xmldb_plugin(clicon_handle h) -{ - return clicon_option_str(h, "CLICON_XMLDB_PLUGIN"); -} - -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) -{ - char *s; - - if ((s = clicon_option_str(h, "CLICON_SOCK_FAMILY")) == NULL) - return AF_UNIX; - else if (strcmp(s, "IPv4")==0) - return AF_INET; - else if (strcmp(s, "IPv6")==0) - return AF_INET6; - else - return AF_UNIX; /* default */ -} - -/*! Get information about socket: unix domain filepath, or addr:path */ -char * -clicon_sock(clicon_handle h) -{ - return clicon_option_str(h, "CLICON_SOCK"); -} - -/*! Get port for backend socket in case of AF_INET or AF_INET6 */ -int -clicon_sock_port(clicon_handle h) -{ - char *s; - - if ((s = clicon_option_str(h, "CLICON_SOCK_PORT")) == NULL) - return -1; - return atoi(s); -} - -char * -clicon_backend_pidfile(clicon_handle h) -{ - return clicon_option_str(h, "CLICON_BACKEND_PIDFILE"); -} - -char * -clicon_sock_group(clicon_handle h) -{ - return clicon_option_str(h, "CLICON_SOCK_GROUP"); -} - -char * -clicon_master_plugin(clicon_handle h) -{ - return clicon_option_str(h, "CLICON_MASTER_PLUGIN"); -} - -/*! Return initial clicon cli mode */ -char * -clicon_cli_mode(clicon_handle h) -{ - return clicon_option_str(h, "CLICON_CLI_MODE"); -} - /*! Whether to generate CLIgen syntax from datamodel or not (0 or 1) * Must be used with a previous clicon_option_exists(). + * @see clixon-config@.yang CLICON_CLI_GENMODEL */ int clicon_cli_genmodel(clicon_handle h) @@ -659,7 +574,23 @@ clicon_cli_genmodel(clicon_handle h) return 0; } -/*! How to generate and show CLI syntax: VARS|ALL */ +/*! Generate code for CLI completion of existing db symbols + * @see clixon-config@.yang CLICON_CLI_GENMODEL_COMPLETION + */ +int +clicon_cli_genmodel_completion(clicon_handle h) +{ + char const *opt = "CLICON_CLI_GENMODEL_COMPLETION"; + + if (clicon_option_exists(h, opt)) + return clicon_option_int(h, opt); + else + return 0; +} + +/*! How to generate and show CLI syntax: VARS|ALL + * @see clixon-config@.yang CLICON_CLI_GENMODEL_TYPE + */ enum genmodel_type clicon_cli_genmodel_type(clicon_handle h) { @@ -683,32 +614,8 @@ clicon_cli_genmodel_type(clicon_handle h) return gt; } - -/* eg -q option, dont print notifications on stdout */ -char * -clicon_quiet_mode(clicon_handle h) -{ - return clicon_option_str(h, "CLICON_QUIET"); -} - -int -clicon_autocommit(clicon_handle h) -{ - char const *opt = "CLICON_AUTOCOMMIT"; - - if (clicon_option_exists(h, opt)) - return clicon_option_int(h, opt); - else - return 0; -} - -int -clicon_autocommit_set(clicon_handle h, int val) -{ - return clicon_option_int_set(h, "CLICON_AUTOCOMMIT", val); -} - -/*! Dont include keys in cvec in cli vars callbacks +/*! Get Dont include keys in cvec in cli vars callbacks + * @see clixon-config@.yang CLICON_CLI_VARONLY */ int clicon_cli_varonly(clicon_handle h) @@ -721,16 +628,43 @@ clicon_cli_varonly(clicon_handle h) return 0; } +/*! Get family of backend socket: AF_UNIX, AF_INET or AF_INET6 + * @see clixon-config@.yang CLICON_SOCK_FAMILY + */ int -clicon_cli_varonly_set(clicon_handle h, int val) +clicon_sock_family(clicon_handle h) { - return clicon_option_int_set(h, "CLICON_CLI_VARONLY", val); + char *s; + + if ((s = clicon_option_str(h, "CLICON_SOCK_FAMILY")) == NULL) + return AF_UNIX; + else if (strcmp(s, "IPv4")==0) + return AF_INET; + else if (strcmp(s, "IPv6")==0) + return AF_INET6; + else + return AF_UNIX; /* default */ } +/*! Get port for backend socket in case of AF_INET or AF_INET6 + * @see clixon-config@.yang CLICON_SOCK_PORT + */ int -clicon_cli_genmodel_completion(clicon_handle h) +clicon_sock_port(clicon_handle h) { - char const *opt = "CLICON_CLI_GENMODEL_COMPLETION"; + char *s; + + if ((s = clicon_option_str(h, "CLICON_SOCK_PORT")) == NULL) + return -1; + return atoi(s); +} + +/*! Set if all configuration changes are committed automatically + */ +int +clicon_autocommit(clicon_handle h) +{ + char const *opt = "CLICON_AUTOCOMMIT"; if (clicon_option_exists(h, opt)) return clicon_option_int(h, opt); @@ -738,11 +672,36 @@ clicon_cli_genmodel_completion(clicon_handle h) return 0; } -/*! Where are "running" and "candidate" databases? */ -char * -clicon_xmldb_dir(clicon_handle h) +/*! Which method to boot/start clicon backen + */ +int +clicon_startup_mode(clicon_handle h) { - return clicon_option_str(h, "CLICON_XMLDB_DIR"); + char *mode; + if ((mode = clicon_option_str(h, "CLICON_STARTUP_MODE")) == NULL) + return -1; + return clicon_str2int(startup_mode_map, mode); +} + +/*--------------------------------------------------------------------- + * Specific option access functions for non-yang options + * Typically dynamic values and more complex datatypes, + * Such as handles to plugins, API:s and parsed structures + *--------------------------------------------------------------------*/ + +/* eg -q option, dont print notifications on stdout */ +int +clicon_quiet_mode(clicon_handle h) +{ + char *s; + if ((s = clicon_option_str(h, "CLICON_QUIET")) == NULL) + return 0; /* default */ + return atoi(s); +} +int +clicon_quiet_mode_set(clicon_handle h, int val) +{ + return clicon_option_int_set(h, "CLICON_QUIET", val); } /*! Get YANG specification @@ -796,6 +755,19 @@ clicon_dbspec_name_set(clicon_handle h, char *name) return clicon_option_str_set(h, "dbspec_name", name); } +/*! Get xmldb datastore plugin handle, as used by dlopen/dlsym/dlclose */ +plghndl_t +clicon_xmldb_plugin_get(clicon_handle h) +{ + clicon_hash_t *cdat = clicon_data(h); + size_t len; + void *p; + + if ((p = hash_value(cdat, "xmldb_plugin", &len)) != NULL) + return *(plghndl_t*)p; + return NULL; +} + /*! Set xmldb datastore plugin handle, as used by dlopen/dlsym/dlclose */ int clicon_xmldb_plugin_set(clicon_handle h, @@ -808,16 +780,20 @@ clicon_xmldb_plugin_set(clicon_handle h, return 0; } -/*! Get xmldb datastore plugin handle, as used by dlopen/dlsym/dlclose */ -plghndl_t -clicon_xmldb_plugin_get(clicon_handle h) +/*! Get XMLDB API struct pointer + * @param[in] h Clicon handle + * @retval xa XMLDB API struct + * @note xa is really of type struct xmldb_api* + */ +void * +clicon_xmldb_api_get(clicon_handle h) { clicon_hash_t *cdat = clicon_data(h); size_t len; - void *p; + void *xa; - if ((p = hash_value(cdat, "xmldb_plugin", &len)) != NULL) - return *(plghndl_t*)p; + if ((xa = hash_value(cdat, "xmldb_api", &len)) != NULL) + return *(void**)xa; return NULL; } @@ -840,20 +816,19 @@ clicon_xmldb_api_set(clicon_handle h, return 0; } -/*! Get XMLDB API struct pointer +/*! Get XMLDB storage handle * @param[in] h Clicon handle - * @retval xa XMLDB API struct - * @note xa is really of type struct xmldb_api* + * @retval xh XMLDB storage handle. If not connected return NULL */ void * -clicon_xmldb_api_get(clicon_handle h) +clicon_xmldb_handle_get(clicon_handle h) { clicon_hash_t *cdat = clicon_data(h); size_t len; - void *xa; + void *xh; - if ((xa = hash_value(cdat, "xmldb_api", &len)) != NULL) - return *(void**)xa; + if ((xh = hash_value(cdat, "xmldb_handle", &len)) != NULL) + return *(void**)xh; return NULL; } @@ -873,18 +848,4 @@ clicon_xmldb_handle_set(clicon_handle h, return 0; } -/*! Get XMLDB storage handle - * @param[in] h Clicon handle - * @retval xh XMLDB storage handle. If not connected return NULL - */ -void * -clicon_xmldb_handle_get(clicon_handle h) -{ - clicon_hash_t *cdat = clicon_data(h); - size_t len; - void *xh; - if ((xh = hash_value(cdat, "xmldb_handle", &len)) != NULL) - return *(void**)xh; - return NULL; -} diff --git a/lib/src/clixon_sig.c b/lib/src/clixon_sig.c index 1dfe63df..f633c046 100644 --- a/lib/src/clixon_sig.c +++ b/lib/src/clixon_sig.c @@ -182,7 +182,7 @@ pidfile_write(char *pidfile) int retval = -1; /* Here, there should be no old agent and no pidfile */ - if ((f = fopen(pidfile, "wb")) == NULL){ + if ((f = fopen(pidfile, "w")) == NULL){ if (errno == EACCES) clicon_err(OE_DEMON, errno, "Creating pid-file %s (Try run as root?)", pidfile); else diff --git a/lib/src/clixon_xml.c b/lib/src/clixon_xml.c index 2c002a68..515f64ae 100644 --- a/lib/src/clixon_xml.c +++ b/lib/src/clixon_xml.c @@ -69,6 +69,7 @@ * Constants */ #define BUFLEN 1024 /* Size of xml read buffer */ +#define XML_INDENT 3 /* maybe we should set this programmatically? */ /* * Types @@ -104,7 +105,7 @@ static const map_str2int xsmap[] = { }; /* Hash for XML trees list entries - * Experimental + * Experimental XXX DOES NOT WORK */ int xml_child_hash = 0; @@ -442,7 +443,7 @@ xml_child_each(cxobj *xparent, xn = xparent->x_childvec[i]; if (xn == NULL) continue; - if (type != CX_ERROR && xml_type(xn) != type) + if (type != CX_ERROR && xn->x_type != type) continue; break; /* this is next object after previous */ } @@ -931,27 +932,89 @@ xml_free(cxobj *x) * @param[in] level how many spaces to insert before each line * @param[in] prettyprint insert \n and spaces tomake the xml more readable. * @see clicon_xml2cbuf + * One can use clicon_xml2cbuf to get common code, but using fprintf is + * much faster than using cbuf and then printing that,... */ int clicon_xml2file(FILE *f, - cxobj *xn, + cxobj *x, int level, int prettyprint) { int retval = -1; - cbuf *cb = NULL; + char *name; + char *namespace; + cxobj *xc; + int hasbody; + int haselement; - if ((cb = cbuf_new()) == NULL){ - clicon_err(OE_XML, errno, "cbuf_new"); - goto done; - } - if (clicon_xml2cbuf(cb, xn, level, prettyprint) < 0) - goto done; - fprintf(f, "%s", cbuf_get(cb)); + name = xml_name(x); + namespace = xml_namespace(x); + switch(xml_type(x)){ + case CX_BODY: + fprintf(f, "%s", xml_value(x)); + break; + case CX_ATTR: + fprintf(f, " "); + if (namespace) + fprintf(f, "%s:", namespace); + fprintf(f, "%s=\"%s\"", name, xml_value(x)); + break; + case CX_ELMNT: + fprintf(f, "%*s<", prettyprint?(level*XML_INDENT):0, ""); + if (namespace) + fprintf(f, "%s:", namespace); + fprintf(f, "%s", name); + hasbody = 0; + haselement = 0; + xc = NULL; + /* print attributes only */ + while ((xc = xml_child_each(x, xc, -1)) != NULL) { + switch (xc->x_type){ + case CX_ATTR: + if (clicon_xml2file(f, xc, level+1, prettyprint) <0) + goto done; + break; + case CX_BODY: + hasbody=1; + break; + case CX_ELMNT: + haselement=1; + break; + default: + break; + } + } + /* Check for special case instead of : + * Ie, no CX_BODY or CX_ELMNT child. + */ + if (hasbody==0 && haselement==0) + fprintf(f, "/>"); + else{ + fprintf(f, ">"); + if (prettyprint && hasbody == 0) + fprintf(f, "\n"); + xc = NULL; + while ((xc = xml_child_each(x, xc, -1)) != NULL) { + if (xml_type(xc) != CX_ATTR) + if (clicon_xml2file(f, xc, level+1, prettyprint) <0) + goto done; + } + if (prettyprint && hasbody==0) + fprintf(f, "%*s", level*XML_INDENT, ""); + fprintf(f, "", name); + } + if (prettyprint) + fprintf(f, "\n"); + break; + default: + break; + }/* switch */ retval = 0; - done: - if (cb) - cbuf_free(cb); + done: return retval; } @@ -971,7 +1034,6 @@ xml_print(FILE *f, return clicon_xml2file(f, xn, 0, 1); } -#define XML_INDENT 3 /* maybe we should set this programmatically? */ /*! Print an XML tree structure to a cligen buffer * @@ -987,7 +1049,7 @@ xml_print(FILE *f, * goto err; * cbuf_free(cb); * @endcode - * See also clicon_xml2file + * @see clicon_xml2file */ int clicon_xml2cbuf(cbuf *cb, @@ -995,48 +1057,67 @@ clicon_xml2cbuf(cbuf *cb, int level, int prettyprint) { + int retval = -1; cxobj *xc; char *name; + int hasbody; + int haselement; + char *namespace; name = xml_name(x); + namespace = xml_namespace(x); switch(xml_type(x)){ case CX_BODY: cprintf(cb, "%s", xml_value(x)); break; case CX_ATTR: cprintf(cb, " "); - if (xml_namespace(x)) - cprintf(cb, "%s:", xml_namespace(x)); + if (namespace) + cprintf(cb, "%s:", namespace); cprintf(cb, "%s=\"%s\"", name, xml_value(x)); break; case CX_ELMNT: cprintf(cb, "%*s<", prettyprint?(level*XML_INDENT):0, ""); - if (xml_namespace(x)) - cprintf(cb, "%s:", xml_namespace(x)); + if (namespace) + cprintf(cb, "%s:", namespace); cprintf(cb, "%s", name); + hasbody = 0; + haselement = 0; xc = NULL; /* print attributes only */ - while ((xc = xml_child_each(x, xc, CX_ATTR)) != NULL) - clicon_xml2cbuf(cb, xc, level+1, prettyprint); + while ((xc = xml_child_each(x, xc, -1)) != NULL) + switch (xc->x_type){ + case CX_ATTR: + if (clicon_xml2cbuf(cb, xc, level+1, prettyprint) < 0) + goto done; + break; + case CX_BODY: + hasbody=1; + break; + case CX_ELMNT: + haselement=1; + break; + default: + break; + } + /* Check for special case instead of */ - if (xml_body(x)==NULL && xml_child_nr_type(x, CX_ELMNT)==0) + if (hasbody==0 && haselement==0) cprintf(cb, "/>"); else{ cprintf(cb, ">"); - if (prettyprint && xml_body(x)==NULL) + if (prettyprint && hasbody == 0) cprintf(cb, "\n"); xc = NULL; - while ((xc = xml_child_each(x, xc, -1)) != NULL) { - if (xml_type(xc) == CX_ATTR) - continue; - else - clicon_xml2cbuf(cb, xc, level+1, prettyprint); - } - if (prettyprint && xml_body(x)==NULL) + while ((xc = xml_child_each(x, xc, -1)) != NULL) + if (xml_type(xc) != CX_ATTR) + if (clicon_xml2cbuf(cb, xc, level+1, prettyprint) < 0) + goto done; + if (prettyprint && hasbody == 0) cprintf(cb, "%*s", level*XML_INDENT, ""); cprintf(cb, "", name); } if (prettyprint) @@ -1045,7 +1126,9 @@ clicon_xml2cbuf(cbuf *cb, default: break; }/* switch */ - return 0; + retval = 0; + done: + return retval; } /*! Basic xml parsing function. diff --git a/test/test_perf.sh b/test/test_perf.sh index fcadc6f0..57559981 100755 --- a/test/test_perf.sh +++ b/test/test_perf.sh @@ -86,18 +86,21 @@ echo "$clixon_netconf -qf $clixon_cf -y $fyang" new "netconf edit large config" expecteof_file "time -p $clixon_netconf -qf $clixon_cf -y $fyang" "$fconfig" "^]]>]]>$" -echo ']]>]]>' | $clixon_netconf -qf $clixon_cf -y $fyang +#echo ']]>]]>' | $clixon_netconf -qf $clixon_cf -y $fyang new "netconf edit large config again" expecteof_file "time -p $clixon_netconf -qf $clixon_cf -y $fyang" "$fconfig" "^]]>]]>$" -echo ']]>]]>' | $clixon_netconf -qf $clixon_cf -y $fyang +#echo ']]>]]>' | $clixon_netconf -qf $clixon_cf -y $fyang rm $fconfig new "netconf commit large config" expecteof "time -p $clixon_netconf -qf $clixon_cf -y $fyang" "]]>]]>" "^]]>]]>$" +new "netconf commit same config again" +expecteof "time -p $clixon_netconf -qf $clixon_cf -y $fyang" "]]>]]>" "^]]>]]>$" + new "netconf add one small config" expecteof "time -p $clixon_netconf -qf $clixon_cf -y $fyang" "xy]]>]]>" "^]]>]]>$" @@ -107,9 +110,6 @@ time -p for (( i=0; i<$req; i++ )); do echo "$rnd$rnd]]>]]>" done | $clixon_netconf -qf $clixon_cf -y $fyang > /dev/null -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" diff --git a/yang/Makefile.in b/yang/Makefile.in index 8642d2e4..cd99bda0 100644 --- a/yang/Makefile.in +++ b/yang/Makefile.in @@ -38,7 +38,7 @@ bindir = @bindir@ includedir = @includedir@ datarootdir = @datarootdir@ -YANGSPECS = clixon-config@2017-07-02.yang +YANGSPECS = clixon-config@2017-12-03.yang YANGSPECS += ietf-netconf@2011-06-01.yang APPNAME = clixon # subdir ehere these files are installed diff --git a/yang/clixon-config@2017-07-02.yang b/yang/clixon-config@2017-07-02.yang index 21497713..cbc6e86c 100644 --- a/yang/clixon-config@2017-07-02.yang +++ b/yang/clixon-config@2017-07-02.yang @@ -38,7 +38,7 @@ ***** END LICENSE BLOCK *****"; - revision 2017-11-12 { + revision 2017-07-02 { description "Added startup config"; } diff --git a/yang/clixon-config@2017-12-03.yang b/yang/clixon-config@2017-12-03.yang new file mode 100644 index 00000000..5ffa1a5c --- /dev/null +++ b/yang/clixon-config@2017-12-03.yang @@ -0,0 +1,256 @@ +module clixon-config { + + prefix cc; + + organization + "Clicon / Clixon"; + + contact + "Olof Hagsand "; + + description + "Clixon configuration file + ***** 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 *****"; + + revision 2017-12-03 { + description + "Added startup config for Clixon 1.3.3 and xmldb_cache in 1.4.0"; + } + 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; + description + "Location of configuration-file for default values (this file)"; + } + leaf CLICON_YANG_DIR { + type string; + mandatory true; + description + "Location of YANG module and submodule files."; + } + leaf CLICON_YANG_MODULE_MAIN { + type string; + default "clicon"; + description + "Option used to construct initial yang file: + [@]"; + } + leaf CLICON_YANG_MODULE_REVISION { + type string; + description + "Option used to construct initial yang file: + [@]"; + } + leaf CLICON_BACKEND_DIR { + type string; + description + "Location of backend .so plugins. Load all .so + plugins in this dir as backend plugins"; + } + leaf CLICON_NETCONF_DIR { + type string; + description "Location of netconf (frontend) .so plugins"; + } + leaf CLICON_RESTCONF_DIR { + type string; + 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; + description + "Location of cli frontend .so plugins. Load all .so + plugins in this dir as CLI object plugins"; + } + leaf CLICON_CLISPEC_DIR { + type string; + description + "Location of frontend .cli cligen spec files. Load all .cli + files in this dir as CLI specification files"; + } + 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 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"; + description + "Address family for communicating with clixon_backend + (UNIX|IPv4|IPv6)"; + } + leaf CLICON_SOCK { + type string; + 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_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 of backend daemon"; + } + leaf CLICON_AUTOCOMMIT { + type int32; + default 0; + description + "Set if all configuration changes are committed automatically + on every edit change. Explicit commit commands unnecessary"; + } + leaf CLICON_MASTER_PLUGIN { + type string; + 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. (Obsolete?)"; + } + leaf CLICON_XMLDB_DIR { + type string; + mandatory true; + description + "Directory where \"running\", \"candidate\" and \"startup\" are placed"; + } + leaf CLICON_XMLDB_PLUGIN { + type string; + mandatory true; + description + "XMLDB datastore plugin filename + (see datastore/ and clixon_xml_db.[ch])"; + } + leaf CLICON_XMLDB_CACHE { + type boolean; + default true; + description + "XMLDB datastore cache. + If set, XML parsed tree is stored in memory"; + } + 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 1.3.3 and CLICON_STARTUP_MODE"; + } + leaf CLICON_STARTUP_MODE { + type startup_mode; + description "Which method to boot/start clicon backend"; + } + } +}