From 4169bd8d30cc36a1632566c29ad14d654445b68a Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sat, 8 Apr 2017 20:39:04 +0200 Subject: [PATCH] Ongoing: xmldb datastore plugin framework --- CHANGELOG | 2 + Makefile.in | 2 +- apps/backend/backend_main.c | 17 +- configure | 4 +- configure.ac | 2 + datastore/Makefile.in | 78 + datastore/keyvalue/Makefile.in | 100 + datastore/keyvalue/clixon_keyvalue.c | 1746 +++++++++++++++ datastore/keyvalue/clixon_keyvalue.h | 56 + {lib/src => datastore/keyvalue}/clixon_qdb.c | 0 {lib/src => datastore/keyvalue}/clixon_qdb.h | 0 lib/clixon/clixon_options.h | 1 + lib/clixon/clixon_xml_db.h | 75 +- lib/clixon/clixon_xml_map.h | 3 + lib/src/Makefile.in | 7 +- lib/src/clixon_options.c | 10 + lib/src/clixon_xml_db.c | 2120 ++---------------- lib/src/clixon_xml_map.c | 289 ++- 18 files changed, 2588 insertions(+), 1924 deletions(-) create mode 100644 datastore/Makefile.in create mode 100644 datastore/keyvalue/Makefile.in create mode 100644 datastore/keyvalue/clixon_keyvalue.c create mode 100644 datastore/keyvalue/clixon_keyvalue.h rename {lib/src => datastore/keyvalue}/clixon_qdb.c (100%) rename {lib/src => datastore/keyvalue}/clixon_qdb.h (100%) diff --git a/CHANGELOG b/CHANGELOG index 3731f1f6..450f5b6c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -29,6 +29,8 @@ # # ***** END LICENSE BLOCK ***** +- Ongoiing: xmldb datastore plugin framework + - cli_copy_config added as generic cli command - cli_show_config added as generic cli command Replace all show_confv*() and show_conf*() with cli_show_config() diff --git a/Makefile.in b/Makefile.in index f858d94c..3447442c 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 +SUBDIRS = lib apps include etc datastore .PHONY: doc all clean depend $(SUBDIRS) install loc TAGS .config.status docker diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c index 6ee74fc9..fd23e266 100644 --- a/apps/backend/backend_main.c +++ b/apps/backend/backend_main.c @@ -73,7 +73,7 @@ #include "backend_handle.h" /* Command line options to be passed to getopt(3) */ -#define BACKEND_OPTS "hD:f:d:Fzu:P:1IRCc:rg:pty:" +#define BACKEND_OPTS "hD:f:d:Fzu:P:1IRCc:rg:pty:x:" /*! Terminate. Cannot use h after this */ static int @@ -143,7 +143,8 @@ usage(char *argv0, clicon_handle h) " -p \t\tPrint database yang specification\n" " -t \t\tPrint alternate spec translation (eg if YANG print KEY, if KEY print YANG)\n" " -g \tClient membership required to this group (default: %s)\n" - "\t-y \tOverride yang spec file (dont include .yang suffix)\n", + "\t-y \tOverride yang spec file (dont include .yang suffix)\n" + "\t-x \tXMLDB plugin\n", argv0, plgdir ? plgdir : "none", confsock ? confsock : "none", @@ -314,6 +315,7 @@ main(int argc, char **argv) char *pidfile; char *sock; int sockfamily; + char *xmldb_plugin; /* In the startup, logs to stderr & syslog and debug flag set later */ @@ -437,6 +439,10 @@ main(int argc, char **argv) clicon_option_str_set(h, "CLICON_YANG_DIR", strdup(dir)); break; } + case 'x' :{ /* xmldb plugin */ + clicon_option_str_set(h, "CLICON_XMLDB_PLUGIN", optarg); + break; + } default: usage(argv[0], h); break; @@ -502,6 +508,13 @@ main(int argc, char **argv) return -1; } + if ((xmldb_plugin = clicon_xmldb_plugin(h)) == NULL){ + clicon_log(LOG_ERR, "No xmldb plugin given (specify option CLICON_XMLDB_PLUGIN).\n"); + goto done; + } + if (xmldb_plugin_load(xmldb_plugin) < 0) + goto done; + /* Parse db spec file */ if (yang_spec_main(h, stdout, printspec) < 0) goto done; diff --git a/configure b/configure index 3e5cf739..e7a3ac5f 100755 --- a/configure +++ b/configure @@ -4315,7 +4315,7 @@ fi -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 apps/dbctrl/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 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 apps/dbctrl/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 doc/Makefile" cat >confcache <<\_ACEOF # This file is a shell script that caches the results of configure @@ -5030,6 +5030,8 @@ do "docker/backend/Dockerfile") CONFIG_FILES="$CONFIG_FILES docker/backend/Dockerfile" ;; "docker/netconf/Makefile") CONFIG_FILES="$CONFIG_FILES docker/netconf/Makefile" ;; "docker/netconf/Dockerfile") CONFIG_FILES="$CONFIG_FILES docker/netconf/Dockerfile" ;; + "datastore/Makefile") CONFIG_FILES="$CONFIG_FILES datastore/Makefile" ;; + "datastore/keyvalue/Makefile") CONFIG_FILES="$CONFIG_FILES datastore/keyvalue/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 9b394fa3..c156443f 100644 --- a/configure.ac +++ b/configure.ac @@ -201,6 +201,8 @@ AC_OUTPUT(Makefile docker/backend/Dockerfile docker/netconf/Makefile docker/netconf/Dockerfile + datastore/Makefile + datastore/keyvalue/Makefile doc/Makefile ) diff --git a/datastore/Makefile.in b/datastore/Makefile.in new file mode 100644 index 00000000..7ad7112f --- /dev/null +++ b/datastore/Makefile.in @@ -0,0 +1,78 @@ +# +# ***** 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@ +CC = @CC@ +CFLAGS = @CFLAGS@ +LDFLAGS = @LDFLAGS@ +LIBS = @LIBS@ + +SHELL = /bin/sh + +SUBDIRS = keyvalue + +.PHONY: all clean depend install $(SUBDIRS) + +all: $(SUBDIRS) + +depend: + for i in $(SUBDIRS); \ + do (cd $$i; $(MAKE) $(MFLAGS) $@); done + +$(SUBDIRS): + (cd $@; $(MAKE) $(MFLAGS) all) + +install-include: + for i in $(SUBDIRS); \ + do (cd $$i ; $(MAKE) $(MFLAGS) $@); done; + +install: + for i in $(SUBDIRS); \ + do (cd $$i; $(MAKE) $(MFLAGS) $@); done + +uninstall: + for i in $(SUBDIRS); \ + do (cd $$i; $(MAKE) $(MFLAGS) $@); done + +clean: + for i in $(SUBDIRS); \ + do (cd $$i; $(MAKE) $(MFLAGS) $@); done + +distclean: clean + rm -f Makefile *~ .depend + for i in $(SUBDIRS); \ + do (cd $$i; $(MAKE) $(MFLAGS) $@); done + +tags: + find $(srcdir) -name '*.[chyl]' -print | etags - diff --git a/datastore/keyvalue/Makefile.in b/datastore/keyvalue/Makefile.in new file mode 100644 index 00000000..7c79e7f5 --- /dev/null +++ b/datastore/keyvalue/Makefile.in @@ -0,0 +1,100 @@ +# +# ***** 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 ***** +# +prefix = @prefix@ +datarootdir = @datarootdir@ +srcdir = @srcdir@ +top_srcdir = @top_srcdir@ +exec_prefix = @exec_prefix@ +bindir = @bindir@ +libdir = @libdir@ +dbdir = @prefix@/db +mandir = @mandir@ +libexecdir = @libexecdir@ +localstatedir = @localstatedir@ +sysconfdir = @sysconfdir@ + +VPATH = @srcdir@ +CC = @CC@ +CFLAGS = @CFLAGS@ -rdynamic -fPIC +LDFLAGS = @LDFLAGS@ +LIBS = @LIBS@ +DATASTORE = keyvalue +CPPFLAGS = @CPPFLAGS@ + +INCLUDES = -I. @INCLUDES@ -I$(top_srcdir)/lib/clixon -I$(top_srcdir)/include -I$(top_srcdir) + +PLUGIN = $(DATASTORE).so + +SRC = clixon_keyvalue.c clixon_qdb.c + +OBJS = $(SRC:.c=.o) + +all: $(PLUGIN) + +-include $(DESTDIR)$(datarootdir)/clixon/clixon.mk + +#grideye_sysinfo.so.1: grideye_sysinfo.c +# $(CC) $(CFLAGS) -shared -o $@ -lc $^ -lm + +$(PLUGIN): $(SRC) + $(CC) $(CPPFLAGS) $(INCLUDES) $(CFLAGS) -shared -o $@ -lc $^ $(LIBS) + +clean: + rm -f $(PLUGIN) $(OBJS) *.core + +distclean: clean + rm -f Makefile *~ .depend + +.SUFFIXES: +.SUFFIXES: .c .o + +.c.o: $(SRC) + $(CC) $(INCLUDES) $(CPPFLAGS) $(CFLAGS) -c $< + +install: $(PLUGIN) + install -d $(DESTDIR)$(clixon_LIBDIR)/xmldb + install $(PLUGIN) $(DESTDIR)$(clixon_LIBDIR)/xmldb; + +install-include: + +uninstall: + rm -rf $(DESTDIR)$(clixon_LIBDIR)/xmldb/$(PLUGIN); + +TAGS: + find . -name '*.[chyl]' -print | etags - + +depend: + $(CC) $(DEPENDFLAGS) @DEFS@ $(INCLUDES) $(CFLAGS) -MM $(SRC) > .depend + +#include .depend + diff --git a/datastore/keyvalue/clixon_keyvalue.c b/datastore/keyvalue/clixon_keyvalue.c new file mode 100644 index 00000000..faaf1242 --- /dev/null +++ b/datastore/keyvalue/clixon_keyvalue.c @@ -0,0 +1,1746 @@ +/* + * + ***** 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 ***** + */ +/* + * An xml database consists of key-value pairs for xml-trees. + * Each node in an xml-tree has a key and an optional value. + * The key (xmlkey) is constructed from the xml node name concatenated + * with its ancestors and any eventual list keys. + * A xmlkeyfmt is a help-structure used when accessing the XML database. + * It consists of an xmlkey but with the key fields replaced with wild-chars(%s) + * Example: /aaa/bbb/%s/%s/ccc + * Such an xmlkeyfmt can be obtained from a yang-statement by following + * its ancestors to the root module. If one of the ancestors is a list, + * a wildchar (%s) is inserted for each key. + * These xmlkeyfmt keys are saved and used in cli callbacks such as when + * modifying syntax (eg cli_merge/cli_delete) or when completing for sub-symbols + * In this case, the variables are set and the wildcards can be instantiated. + * An xml tree can then be formed that can be used to the xmldb_get() or + * xmldb_put() functions. + * The relations between the functions and formats are as follows: + * + * +-----------------+ +-----------------+ + * | yang-stmt | yang2xmlkeyfmt | xmlkeyfmt | xmlkeyfmt2xpath + * | list aa,leaf k | ----------------->| /aa=%s |----------------> + * +-----------------+ +-----------------+ + * | + * | xmlkeyfmt2key + * | k=17 + * v + * +-------------------+ +-----------------+ + * | xml-tree/cxobj | xmlkey2xml | xmlkey RFC3986| + * | 17| <------------- | /aa=17 | + * +-------------------+ +-----------------+ + * + * Alternative for xmlkeyfmt would be eg: + * RESTCONF: /interfaces/interface=%s/ipv4/address/ip=%s (used) + * XPATH: /interfaces/interface[name=%s]/ipv4/address/[ip=%s] + * + * Paths through the code (for coverage) + * cli_callback_generate +----------------+ + * cli_expand_var_generate | yang2xmlkeyfmt | + * yang -------------> | | + * +----------------+ + * xmldb_get_tree + * - compare_dbs + * - netconf + * - validate + * - from_client_save + * + * xmldb_get_vec + * - restconf + * - expand_dbvar + * - show_conf_xpath + */ + +#ifdef HAVE_CONFIG_H +#include "clixon_config.h" /* generated by config & autoconf */ +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* cligen */ +#include + +/* clicon */ +#include + +#include "clixon_qdb.h" +#include "clixon_keyvalue.h" + +/*! Database locking for candidate and running non-persistent + * Store an integer for running and candidate containing + * the session-id of the client holding the lock. + */ +static int _running_locked = 0; +static int _candidate_locked = 0; +static int _startup_locked = 0; + +/*! Lock database + * @param[in] db Database name + * @param[in] pid process/session-id + */ +static int +db_lock(char *db, + int pid) +{ + if (strcmp("running", db) == 0) + _running_locked = pid; + else if (strcmp("candidate", db) == 0) + _candidate_locked = pid; + else if (strcmp("startup", db) == 0) + _startup_locked = pid; + clicon_debug(1, "%s: locked by %u", db, pid); + return 0; +} + +/*! Unlock database + * @param[in] db Database name + */ +static int +db_unlock(char *db) +{ + if (strcmp("running", db) == 0) + _running_locked = 0; + else if (strcmp("candidate", db) == 0) + _candidate_locked = 0; + else if (strcmp("startup", db) == 0) + _startup_locked = 0; + return 0; +} + +/*! Unlock all databases locked by pid (eg process dies) + * @param[in] pid process/session-id + */ +static int +db_unlock_all(int pid) +{ + if (_running_locked == pid) + _running_locked = 0; + if (_candidate_locked == pid) + _candidate_locked = 0; + if (_startup_locked == pid) + _startup_locked = 0; + return 0; +} + +/*! returns id of locker + * @retval 0 Not locked + * @retval >0 Id of locker + */ +static int +db_islocked(char *db) +{ + if (strcmp("running", db) == 0) + return (_running_locked); + else if (strcmp("candidate", db) == 0) + return(_candidate_locked); + else if (strcmp("startup", db) == 0) + return(_startup_locked); + return 0; +} + +/*! Translate from symbolic database name to actual filename in file-system + * @param[in] h Clicon handle + * @param[in] db Symbolic database name, eg "candidate", "running" + * @param[out] filename Filename. Unallocate after use with free() + * @retval 0 OK + * @retval -1 Error + * @note Could need a way to extend which databases exists, eg to register new. + * The currently allowed databases are: + * candidate, tmp, running, result + * The filename reside in CLICON_XMLDB_DIR option + */ +static int +db2file(clicon_handle h, + char *db, + char **filename) +{ + int retval = -1; + cbuf *cb; + char *dir; + + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_XML, errno, "cbuf_new"); + goto done; + } + if ((dir = clicon_xmldb_dir(h)) == NULL){ + clicon_err(OE_XML, errno, "CLICON_XMLDB_DIR not set"); + goto done; + } + if (strcmp(db, "running") != 0 && + strcmp(db, "candidate") != 0 && + strcmp(db, "startup") != 0 && + strcmp(db, "tmp") != 0){ + clicon_err(OE_XML, 0, "Unexpected database: %s", db); + goto done; + } + cprintf(cb, "%s/%s_db", dir, db); + if ((*filename = strdup4(cbuf_get(cb))) == NULL){ + clicon_err(OE_UNIX, errno, "strdup"); + goto done; + } + retval = 0; + done: + if (cb) + cbuf_free(cb); + return retval; +} + +/*! Help function to append key values from an xml list to a cbuf + * Example, a yang node x with keys a and b results in "x/a/b" + */ +static int +append_listkeys(cbuf *ckey, + cxobj *xt, + yang_stmt *ys) +{ + int retval = -1; + yang_stmt *ykey; + cxobj *xkey; + cg_var *cvi; + cvec *cvk = NULL; /* vector of index keys */ + char *keyname; + char *bodyenc; + int i=0; + + if ((ykey = yang_find((yang_node*)ys, Y_KEY, NULL)) == NULL){ + clicon_err(OE_XML, errno, "%s: List statement \"%s\" has no key", + __FUNCTION__, ys->ys_argument); + goto done; + } + /* The value is a list of keys: [ ]* */ + if ((cvk = yang_arg2cvec(ykey, " ")) == NULL) + goto done; + cvi = NULL; + /* Iterate over individual keys */ + while ((cvi = cvec_each(cvk, cvi)) != NULL) { + keyname = cv_string_get(cvi); + if ((xkey = xml_find(xt, keyname)) == NULL){ + clicon_err(OE_XML, errno, "XML list node \"%s\" does not have key \"%s\" child", + xml_name(xt), keyname); + goto done; + } + if ((bodyenc = curl_easy_escape(NULL, xml_body(xkey), 0)) == NULL){ + clicon_err(OE_UNIX, errno, "curl_easy_escape"); + goto done; + } + if (i++) + cprintf(ckey, ","); + else + cprintf(ckey, "="); + cprintf(ckey, "%s", bodyenc); + free(bodyenc); bodyenc = NULL; + } + retval = 0; + done: + if (cvk) + cvec_free(cvk); + return retval; +} + +/*! Help function to create xml key values + * @param[in,out] x Parent + * @param[in] ykey + * @param[in] arg + * @param[in] keyname yang key name + */ +static int +create_keyvalues(cxobj *x, + yang_stmt *ykey, + char *arg, + char *keyname) +{ + int retval = -1; + cxobj *xb; + + /* Check if key node exists */ + if ((xb = xml_new_spec(keyname, x, ykey)) == NULL) + goto done; + if ((xb = xml_new("body", xb)) == NULL) + goto done; + xml_type_set(xb, CX_BODY); + xml_value_set(xb, arg); + retval = 0; + done: + return retval; +} + +/*! Prune everything that has not been marked + * @param[in] xt XML tree with some node marked + * @param[out] upmark Set if a child (recursively) has marked set. + * The function removes all branches that does not contain a marked child + * XXX: maybe key leafs should not be purged if list is not purged? + * XXX: consider move to clicon_xml + */ +static int +xml_tree_prune_unmarked(cxobj *xt, + int *upmark) +{ + int retval = -1; + int submark; + int mark; + cxobj *x; + cxobj *xprev; + + mark = 0; + x = NULL; + xprev = x = NULL; + while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) { + if (xml_flag(x, XML_FLAG_MARK)){ + mark++; + xprev = x; + continue; /* mark and stop here */ + } + if (xml_tree_prune_unmarked(x, &submark) < 0) + goto done; + if (submark) + mark++; + else{ /* Safe with xml_child_each if last */ + if (xml_purge(x) < 0) + goto done; + x = xprev; + } + xprev = x; + } + retval = 0; + done: + if (upmark) + *upmark = mark; + return retval; +} + +/*! + * @param[in] xk xmlkey + * @param[out] xt XML tree as result + * XXX cannot handle top-level list + */ +static int +get(char *dbname, + yang_spec *ys, + char *xk, + char *val, + cxobj *xt) +{ + int retval = -1; + char **vec = NULL; + int nvec; + char **valvec = NULL; + int nvalvec; + int i; + int j; + char *name; + char *restval; + yang_stmt *y; + cxobj *x; + cxobj *xc; + cxobj *xb; + yang_stmt *ykey; + cg_var *cvi; + cvec *cvk = NULL; /* vector of index keys */ + char *keyname; + char *arg; + char *argdec; + cbuf *cb; + + // clicon_debug(1, "%s xkey:%s val:%s", __FUNCTION__, xk, val); + x = xt; + if (xk == NULL || *xk!='/'){ + clicon_err(OE_DB, 0, "Invalid key: %s", xk); + goto done; + } + if ((vec = clicon_strsep(xk, "/", &nvec)) == NULL) + goto done; + /* Element 0 is NULL '/', + Element 1 is top symbol and needs to find subs in all modules: + spec->module->syntaxnode + */ + if (nvec < 2){ + clicon_err(OE_XML, 0, "Malformed key: %s", xk); + goto done; + } + i = 1; + while (i name:x restval=1,2 */ + if ((restval = index(name, '=')) != NULL){ + *restval = '\0'; + restval++; + } + if (i == 1){ /* spec->module->node */ + if ((y = yang_find_topnode(ys, name)) == NULL){ + clicon_err(OE_UNIX, errno, "No yang node found: %s", name); + goto done; + } + } + else + if ((y = yang_find_syntax((yang_node*)y, name)) == NULL){ + clicon_err(OE_UNIX, errno, "No yang node found: %s", name); + goto done; + } + switch (y->ys_keyword){ + case Y_LEAF_LIST: + /* + * If xml element is a leaf-list, then the next element is expected to + * be a value + */ + if ((argdec = curl_easy_unescape(NULL, restval, 0, NULL)) == NULL){ + clicon_err(OE_UNIX, errno, "curl_easy_escape"); + goto done; + } + if ((xc = xml_find(x, name))==NULL || + (xb = xml_find(xc, argdec))==NULL){ + if ((xc = xml_new_spec(name, x, y)) == NULL) + goto done; + /* Assume body is created at end of function */ + } + free(argdec); + argdec = NULL; + break; + case Y_LIST: + /* + * If xml element is a list, then the next element(s) is expected to be + * a key value. Check if this key value is already in the xml tree, + * otherwise create it. + */ + 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; + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_XML, errno, "cbuf_new"); + goto done; + } + cvi = NULL; + /* Iterate over individual yang keys */ + cprintf(cb, "%s", name); + if (valvec) + free(valvec); + if ((valvec = clicon_strsep(restval, ",", &nvalvec)) == NULL) + goto done; + if (cvec_len(cvk)!=nvalvec){ + retval = 0; + goto done; + } + j = 0; + while ((cvi = cvec_each(cvk, cvi)) != NULL){ + if (j>=nvalvec) + break; + arg = valvec[j++]; + if ((argdec = curl_easy_unescape(NULL, arg, 0, NULL)) == NULL){ + clicon_err(OE_UNIX, errno, "curl_easy_escape"); + goto done; + } + cprintf(cb, "[%s=%s]", cv_string_get(cvi), argdec); + free(argdec); + argdec=NULL; + } + if ((xc = xpath_first(x, cbuf_get(cb))) == NULL){ + if ((xc = xml_new_spec(name, x, y)) == NULL) + goto done; + cvi = NULL; + // i -= cvec_len(cvk); + /* Iterate over individual yang keys */ + j=0; + while ((cvi = cvec_each(cvk, cvi)) != NULL) { + if (j>=nvalvec) + break; + arg = valvec[j++]; + keyname = cv_string_get(cvi); + if ((argdec = curl_easy_unescape(NULL, arg, 0, NULL)) == NULL){ + clicon_err(OE_UNIX, errno, "curl_easy_escape"); + goto done; + } + if (create_keyvalues(xc, + ykey, + argdec, + keyname) < 0) + goto done; + free(argdec); argdec = NULL; + } /* while */ + } + if (cb){ + cbuf_free(cb); + cb = NULL; + } + if (cvk){ + cvec_free(cvk); + cvk = NULL; + } + break; + case Y_LEAF: + case Y_CONTAINER: + default: + if ((xc = xml_find(x, name))==NULL) + if ((xc = xml_new_spec(name, x, y)) == NULL) + goto done; + break; + } /* switch */ + x = xc; + i++; + } + if (val && xml_body(x)==NULL){ + if ((x = xml_new("body", x)) == NULL) + goto done; + xml_type_set(x, CX_BODY); + xml_value_set(x, val); + } + if(debug>1){ + fprintf(stderr, "%s %s\n", __FUNCTION__, xk); + clicon_xml2file(stderr, xt, 0, 1); + } + retval = 0; + done: + if (vec) + free(vec); + if (valvec) + free(valvec); + if (cvk) + cvec_free(cvk); + return retval; +} + +/*! Sanitize an xml tree: xml node has matching yang_stmt pointer + */ +static int +xml_sanity(cxobj *x, + void *arg) +{ + int retval = -1; + yang_stmt *ys; + + ys = (yang_stmt*)xml_spec(x); + if (ys==NULL){ + clicon_err(OE_XML, 0, "No spec for xml node %s", xml_name(x)); + goto done; + } + if (strstr(ys->ys_argument, xml_name(x))==NULL){ + clicon_err(OE_XML, 0, "xml node name '%s' does not match yang spec arg '%s'", + xml_name(x), ys->ys_argument); + goto done; + } + retval = 0; + done: + return retval; +} + +/*! Add default values (if not set) + */ +static int +xml_default(cxobj *x, + void *arg) +{ + int retval = -1; + yang_stmt *ys; + yang_stmt *y; + int i; + cxobj *xc; + cxobj *xb; + char *str; + + ys = (yang_stmt*)xml_spec(x); + /* Check leaf defaults */ + if (ys->ys_keyword == Y_CONTAINER || ys->ys_keyword == Y_LIST){ + for (i=0; iys_len; i++){ + y = ys->ys_stmt[i]; + if (y->ys_keyword != Y_LEAF) + continue; + assert(y->ys_cv); + if (!cv_flag(y->ys_cv, V_UNSET)){ /* Default value exists */ + if (!xml_find(x, y->ys_argument)){ + if ((xc = xml_new_spec(y->ys_argument, x, y)) == NULL) + goto done; + if ((xb = xml_new("body", xc)) == NULL) + goto done; + xml_type_set(xb, CX_BODY); + if ((str = cv2str_dup(y->ys_cv)) == NULL){ + clicon_err(OE_UNIX, errno, "cv2str_dup"); + goto done; + } + if (xml_value_set(xb, str) < 0) + goto done; + free(str); + } + } + } + } + retval = 0; + done: + return retval; +} + +/*! Order XML children according to YANG + */ +static int +xml_order(cxobj *x, + void *arg) +{ + int retval = -1; + yang_stmt *y; + yang_stmt *yc; + int i; + int j0; + int j; + cxobj *xc; + cxobj *xj; + char *yname; /* yang child name */ + char *xname; /* xml child name */ + + y = (yang_stmt*)xml_spec(x); + j0 = 0; + /* Go through xml children and ensure they are same order as yspec children */ + for (i=0; iys_len; i++){ + yc = y->ys_stmt[i]; + if (!yang_is_syntax(yc)) + continue; + yname = yc->ys_argument; + /* First go thru xml children with same name */ + for (;j01) + clicon_xml2file(stderr, xt, 0, 1); + *xtop = xt; + retval = 0; + done: + if (dbname) + free(dbname); + if (xvec) + free(xvec); + unchunk_group(__FUNCTION__); + return retval; + +} + +/*! Add data to database internal recursive function + * @param[in] dbname Name of database to search in (filename incl dir path) + * @param[in] xt xml-node. + * @param[in] ys Yang statement corresponding to xml-node + * @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc + * @param[in] xkey0 aggregated xmlkey + * @retval 0 OK + * @retval -1 Error + * @note XXX op only supports merge + */ +static int +put(char *dbname, + cxobj *xt, + yang_stmt *ys, + enum operation_type op, + const char *xk0) +{ + int retval = -1; + cxobj *x = NULL; + char *xk; + cbuf *cbxk = NULL; + char *body; + yang_stmt *y; + int exists; + char *bodyenc=NULL; + char *opstr; + + clicon_debug(1, "%s xk0:%s ys:%s", __FUNCTION__, xk0, ys->ys_argument); + if (debug){ + xml_print(stderr, xt); + // yang_print(stderr, (yang_node*)ys, 0); + } + if ((opstr = xml_find_value(xt, "operation")) != NULL) + if (xml_operation(opstr, &op) < 0) + goto done; + body = xml_body(xt); + if ((cbxk = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(cbxk, "%s/%s", xk0, xml_name(xt)); + switch (ys->ys_keyword){ + case Y_LIST: /* Note: can have many keys */ + if (append_listkeys(cbxk, xt, ys) < 0) + goto done; + break; + case Y_LEAF_LIST: + if ((bodyenc = curl_easy_escape(NULL, body, 0)) == NULL){ + clicon_err(OE_UNIX, errno, "curl_easy_escape"); + goto done; + } + cprintf(cbxk, "=%s", bodyenc); + break; + default: + break; + } + xk = cbuf_get(cbxk); + // fprintf(stderr, "%s %s\n", key, body?body:""); + /* Write to database, key and a vector of variables */ + switch (op){ + case OP_CREATE: + if ((exists = db_exists(dbname, xk)) < 0) + goto done; + if (exists == 1){ + clicon_err(OE_DB, 0, "OP_CREATE: %s already exists in database", xk); + goto done; + } + case OP_MERGE: + case OP_REPLACE: + if (db_set(dbname, xk, body?body:NULL, body?strlen(body)+1:0) < 0) + goto done; + break; + case OP_DELETE: + if ((exists = db_exists(dbname, xk)) < 0) + goto done; + if (exists == 0){ + clicon_err(OE_DB, 0, "OP_DELETE: %s does not exists in database", xk); + goto done; + } + case OP_REMOVE: + if (db_del(dbname, xk) < 0) + goto done; + break; + case OP_NONE: + break; + } + /* For every node, create a key with values */ + while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL){ + if ((y = yang_find_syntax((yang_node*)ys, xml_name(x))) == NULL){ + clicon_err(OE_UNIX, 0, "No yang node found: %s", xml_name(x)); + goto done; + } + if (put(dbname, x, y, op, xk) < 0) + goto done; + } + retval = 0; + done: + if (cbxk) + cbuf_free(cbxk); + if (bodyenc) + free(bodyenc); + return retval; +} + +/*! Modify database provided an XML database key and an operation + * @param[in] h CLICON handle + * @param[in] db Database name + * @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc + * @param[in] xk XML Key, eg /aa/bb=17/name + * @param[in] val Key value, eg "17" + + * @retval 0 OK + * @retval -1 Error + * @code + * if (xmldb_put_xkey(h, db, OP_MERGE, "/aa/bb=17/name", "17") < 0) + * err; + * @endcode + * @see xmldb_put with xml-tree, no path + */ +static int +xmldb_put_xkey(clicon_handle h, + char *db, + enum operation_type op, + char *xk, + char *val) + +{ + int retval = -1; + cxobj *x = NULL; + yang_stmt *y = NULL; + yang_stmt *ykey; + char **vec = NULL; + int nvec; + char **valvec = NULL; + int nvalvec; + int i; + int j; + char *name; + char *restval; + cg_var *cvi; + cvec *cvk = NULL; /* vector of index keys */ + char *val2 = NULL; + cbuf *ckey=NULL; /* partial keys */ + cbuf *csubkey=NULL; /* partial keys */ + cbuf *crx=NULL; /* partial keys */ + char *keyname; + int exists; + int npairs; + struct db_pair *pairs; + yang_spec *yspec; + char *filename = NULL; + + yspec = clicon_dbspec_yang(h); + if (db2file(h, db, &filename) < 0) + goto done; + if (xk == NULL || *xk!='/'){ + clicon_err(OE_DB, 0, "Invalid key: %s", xk); + goto done; + } + if ((ckey = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + if ((csubkey = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + if ((vec = clicon_strsep(xk, "/", &nvec)) == NULL) + goto done; + if (nvec < 2){ + clicon_err(OE_XML, 0, "Malformed key: %s", xk); + goto done; + } + i = 1; + while (i name:x restval=1,2 */ + if ((restval = index(name, '=')) != NULL){ + *restval = '\0'; + restval++; + } + if (i==1){ + if (strlen(name)==0 && (op==OP_DELETE || op == OP_REMOVE)){ + /* Special handling of "/" */ + cprintf(ckey, "/"); + break; + } + else + if ((y = yang_find_topnode(yspec, name)) == NULL){ + clicon_err(OE_UNIX, errno, "No yang node found: %s", x?xml_name(x):""); + goto done; + } + } + else + if ((y = yang_find_syntax((yang_node*)y, name)) == NULL){ + clicon_err(OE_UNIX, errno, "No yang node found: %s", name); + goto done; + } + if ((op==OP_DELETE || op == OP_REMOVE) && + y->ys_keyword == Y_LEAF && + y->ys_parent->yn_keyword == Y_LIST && + yang_key_match(y->ys_parent, y->ys_argument)) + /* Special rule if key, dont write last key-name, rm whole*/; + else + cprintf(ckey, "/%s", name); + i++; + switch (y->ys_keyword){ + case Y_LEAF_LIST: + if (restval==NULL){ + clicon_err(OE_XML, 0, "malformed key, expected '='"); + goto done; + } + cprintf(ckey, "=%s", restval); + break; + 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; + if (restval==NULL){ + clicon_err(OE_XML, 0, "malformed key, expected '='"); + goto done; + } + if (valvec) + free(valvec); + if ((valvec = clicon_strsep(restval, ",", &nvalvec)) == NULL) + goto done; + + if (cvec_len(cvk) != nvalvec){ + clicon_err(OE_XML, errno, "List %s key length mismatch", name); + goto done; + } + cvi = NULL; + /* Iterate over individual yang keys */ + j = 0; + while ((cvi = cvec_each(cvk, cvi)) != NULL) { + keyname = cv_string_get(cvi); + if (j) + cprintf(ckey, ","); + else + cprintf(ckey, "="); + val2 = valvec[j++]; + + cprintf(ckey, "%s", val2); + cbuf_reset(csubkey); + cprintf(csubkey, "%s/%s", cbuf_get(ckey), keyname); + if (op == OP_MERGE || op == OP_REPLACE || op == OP_CREATE) + if (db_set(filename, cbuf_get(csubkey), val2, strlen(val2)+1) < 0) + goto done; + } + if (cvk){ + cvec_free(cvk); + cvk = NULL; + } + break; + default: + if (op == OP_MERGE || op == OP_REPLACE || op == OP_CREATE) + if (db_set(filename, cbuf_get(ckey), NULL, 0) < 0) + goto done; + break; + } + } + xk = cbuf_get(ckey); + /* final key */ + switch (op){ + case OP_CREATE: + if ((exists = db_exists(filename, xk)) < 0) + goto done; + if (exists == 1){ + clicon_err(OE_DB, 0, "OP_CREATE: %s already exists in database", xk); + goto done; + } + case OP_MERGE: + case OP_REPLACE: + if (y->ys_keyword == Y_LEAF || y->ys_keyword == Y_LEAF_LIST){ + if (db_set(filename, xk, val, val?strlen(val)+1:0) < 0) + goto done; + } + else + if (db_set(filename, xk, NULL, 0) < 0) + goto done; + break; + case OP_DELETE: + if ((exists = db_exists(filename, xk)) < 0) + goto done; + if (exists == 0){ + clicon_err(OE_DB, 0, "OP_DELETE: %s does not exists in database", xk); + goto done; + } + case OP_REMOVE: + /* Read in complete database (this can be optimized) */ + if ((crx = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(crx, "^%s.*$", xk); + if ((npairs = db_regexp(filename, cbuf_get(crx), __FUNCTION__, &pairs, 0)) < 0) + goto done; + for (i = 0; i < npairs; i++) { + if (db_del(filename, pairs[i].dp_key) < 0) + goto done; + } + break; + default: + break; + } + retval = 0; + done: + if (filename) + free(filename); + if (ckey) + cbuf_free(ckey); + if (csubkey) + cbuf_free(csubkey); + if (crx) + cbuf_free(crx); + if (cvk) + cvec_free(cvk); + if (vec) + free(vec); + if (valvec) + free(valvec); + + unchunk_group(__FUNCTION__); + return retval; +} + +/*! Modify database provided an xml tree, a restconf api_path and an operation + * + * @param[in] h CLICON handle + * @param[in] db running or candidate + * @param[in] op OP_MERGE: just add it. + * OP_REPLACE: first delete whole database + * OP_NONE: operation attribute in xml determines operation + * @param[in] api_path According to restconf (Sec 3.5.1.1 in [restconf-draft 13]) + * @param[in] xt xml-tree. Top-level symbol is dummy + * @retval 0 OK + * @retval -1 Error + * example: + * container top { + * list list1 { + * key "key1 key2 key3"; + * is referenced as + * /restconf/data/top/list1=a,,foo + * @see xmldb_put + */ +static int +xmldb_put_restconf_api_path(clicon_handle h, + char *db, + enum operation_type op, + char *api_path, + cxobj *xt) +{ + int retval = -1; + yang_stmt *y = NULL; + yang_stmt *ykey; + char **vec = NULL; + int nvec; + int i; + char *name; + cg_var *cvi; + cvec *cvk = NULL; /* vector of index keys */ + char *val2; + cbuf *ckey=NULL; /* partial keys */ + cbuf *csubkey=NULL; /* partial keys */ + cbuf *crx=NULL; /* partial keys */ + char *keyname; + int exists; + int npairs; + struct db_pair *pairs; + yang_spec *yspec; + yang_stmt *ys; + char *filename = NULL; + char *key; + char *keys; + + yspec = clicon_dbspec_yang(h); + if (db2file(h, db, &filename) < 0) + goto done; + if (api_path == NULL || *api_path!='/'){ + clicon_err(OE_DB, 0, "Invalid api path: %s", api_path); + goto done; + } + if ((ckey = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + if ((csubkey = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + if ((vec = clicon_strsep(api_path, "/", &nvec)) == NULL) + goto done; + if (nvec < 2){ + clicon_err(OE_XML, 0, "Malformed key: %s", api_path); + goto done; + } + i = 1; + while (iys_keyword == Y_LEAF && + y->ys_parent->yn_keyword == Y_LIST && + yang_key_match(y->ys_parent, y->ys_argument)) + /* Special rule if key, dont write last key-name, rm whole*/; + else + cprintf(ckey, "/%s", name); + i++; + switch (y->ys_keyword){ + case Y_LEAF_LIST: + /* For leaf-list 'keys' is value, see 3.5.1 in restconf draft */ + val2 = keys; + cprintf(ckey, "/%s", keys); + break; + 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; + cvi = NULL; + /* Iterate over individual yang keys */ + while ((cvi = cvec_each(cvk, cvi)) != NULL) { + keyname = cv_string_get(cvi); + // val2 = vec[i++]; /* No */ + val2 = keys; + if (i>nvec){ /* XXX >= ? */ + clicon_err(OE_XML, errno, "List %s without argument", name); + goto done; + } + cprintf(ckey, "=%s", val2); + cbuf_reset(csubkey); + cprintf(csubkey, "%s/%s", cbuf_get(ckey), keyname); + if (op == OP_MERGE || op == OP_REPLACE || op == OP_CREATE) + if (db_set(filename, cbuf_get(csubkey), val2, strlen(val2)+1) < 0) + goto done; + } + if (cvk){ + cvec_free(cvk); + cvk = NULL; + } + break; + default: + if (op == OP_MERGE || op == OP_REPLACE || op == OP_CREATE) + if (db_set(filename, cbuf_get(ckey), NULL, 0) < 0) + goto done; + break; + } + } + key = cbuf_get(ckey); + /* final key */ + switch (op){ + case OP_CREATE: + if ((exists = db_exists(filename, key)) < 0) + goto done; + if (exists == 1){ + clicon_err(OE_DB, 0, "OP_CREATE: %s already exists in database", key); + goto done; + } + case OP_MERGE: + case OP_REPLACE: + if (xt==NULL){ + clicon_err(OE_DB, 0, "%s: no xml when yang node %s required", + __FUNCTION__, y->ys_argument); + goto done; + } + if ((ys = yang_find_syntax((yang_node*)y, xml_name(xt))) == NULL){ + clicon_err(OE_DB, 0, "%s: child %s not found under node %s", + __FUNCTION__, xml_name(xt), y->ys_argument); + goto done; + } + y = ys; + if (put(filename, xt, y, op, key) < 0) + goto done; + break; + case OP_DELETE: + if ((exists = db_exists(filename, key)) < 0) + goto done; + if (exists == 0){ + clicon_err(OE_DB, 0, "OP_DELETE: %s does not exists in database", key); + goto done; + } + case OP_REMOVE: + /* Read in complete database (this can be optimized) */ + if ((crx = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(crx, "^%s.*$", key); + if ((npairs = db_regexp(filename, cbuf_get(crx), __FUNCTION__, &pairs, 0)) < 0) + goto done; + for (i = 0; i < npairs; i++) { + if (db_del(filename, pairs[i].dp_key) < 0) + goto done; + } + break; + default: + break; + } + retval = 0; + done: + // clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); + if (filename) + free(filename); + if (ckey) + cbuf_free(ckey); + if (csubkey) + cbuf_free(csubkey); + if (crx) + cbuf_free(crx); + if (cvk) + cvec_free(cvk); + if (vec) + free(vec); + unchunk_group(__FUNCTION__); + return retval; +} + +/*! Modify database provided an xml tree and an operation + * + * @param[in] h CLICON handle + * @param[in] db running or candidate + * @param[in] xt xml-tree. Top-level symbol is dummy + * @param[in] op OP_MERGE: just add it. + * OP_REPLACE: first delete whole database + * OP_NONE: operation attribute in xml determines operation + * @param[in] api_path According to restconf (Sec 3.5.1.1 in [restconf-draft 13]) + * @retval 0 OK + * @retval -1 Error + * The xml may contain the "operation" attribute which defines the operation. + * @code + * cxobj *xt; + * if (clicon_xml_parse_str("17", &xt) < 0) + * err; + * if (xmldb_put(h, "running", OP_MERGE, NULL, xt) < 0) + * err; + * @endcode + * @see xmldb_put_xkey for single key + */ +int +kv_put(clicon_handle h, + char *db, + enum operation_type op, + char *api_path, + cxobj *xt) +{ + int retval = -1; + cxobj *x = NULL; + yang_stmt *ys; + yang_spec *yspec; + char *dbfilename = NULL; + + if (xml_child_nr(xt)==0 || xml_body(xt)!= NULL) + return xmldb_put_xkey(h, db, op, api_path, xml_body(xt)); + yspec = clicon_dbspec_yang(h); + if (db2file(h, db, &dbfilename) < 0) + goto done; + if (op == OP_REPLACE){ + if (db_delete(dbfilename) < 0) + goto done; + if (db_init(dbfilename) < 0) + goto done; + } + while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL){ + if (api_path && strlen(api_path)){ + if (xmldb_put_restconf_api_path(h, db, op, api_path, x) < 0) + goto done; + continue; + } + if ((ys = yang_find_topnode(yspec, xml_name(x))) == NULL){ + clicon_err(OE_UNIX, errno, "No yang node found: %s", xml_name(x)); + goto done; + } + if (put(dbfilename, /* database name */ + x, /* xml root node */ + ys, /* yang statement of xml node */ + op, /* operation, eg merge/delete */ + "" /* aggregate xml key */ + ) < 0) + goto done; + } + retval = 0; + done: + if (dbfilename) + free(dbfilename); + return retval; +} + + +/*! Raw dump of database, just keys and values, no xml interpretation + * @param[in] f File + * @param[in] dbfile File-name of database. This is a local file + * @param[in] rxkey Key regexp, eg "^.*$" + * @note This function can only be called locally. + */ +int +kv_dump(FILE *f, + char *dbfilename, + char *rxkey) +{ + int retval = -1; + int npairs; + struct db_pair *pairs; + + /* Default is match all */ + if (rxkey == NULL) + rxkey = "^.*$"; + + /* Get all keys/values for vector */ + if ((npairs = db_regexp(dbfilename, rxkey, __FUNCTION__, &pairs, 0)) < 0) + goto done; + + for (npairs--; npairs >= 0; npairs--) + fprintf(f, "%s %s\n", pairs[npairs].dp_key, + pairs[npairs].dp_val?pairs[npairs].dp_val:""); + retval = 0; + done: + unchunk_group(__FUNCTION__); + return retval; +} + +/*! Copy database from db1 to db2 + * @param[in] h Clicon handle + * @param[in] from Source database copy + * @param[in] to Destination database + * @retval -1 Error + * @retval 0 OK + */ +int +kv_copy(clicon_handle h, + char *from, + char *to) +{ + int retval = -1; + char *fromfile = NULL; + char *tofile = NULL; + + /* XXX lock */ + if (db2file(h, from, &fromfile) < 0) + goto done; + if (db2file(h, to, &tofile) < 0) + goto done; + if (clicon_file_copy(fromfile, tofile) < 0) + goto done; + retval = 0; + done: + if (fromfile) + free(fromfile); + if (tofile) + free(tofile); + return retval; +} + +/*! Lock database + * @param[in] h Clicon handle + * @param[in] db Database + * @param[in] pid Process id + * @retval -1 Error + * @retval 0 OK + */ +int +kv_lock(clicon_handle h, + char *db, + int pid) +{ + int retval = -1; + + if (db_islocked(db)){ + if (pid != db_islocked(db)){ + clicon_err(OE_DB, 0, "lock failed: locked by %d", db_islocked(db)); + goto done; + } + } + else + db_lock(db, pid); + retval = 0; + done: + return retval; +} + +/*! Unlock database + * @param[in] h Clicon handle + * @param[in] db Database + * @param[in] pid Process id + * @retval -1 Error + * @retval 0 OK + */ +int +kv_unlock(clicon_handle h, + char *db, + int pid) +{ + int retval = -1; + int pid1; + + pid1 = db_islocked(db); + if (pid1){ + if (pid == pid1) + db_unlock(db); + else{ + clicon_err(OE_DB, 0, "unlock failed: locked by %d", pid1); + goto done; + } + } + retval = 0; + done: + return retval; +} + +/*! Unlock all databases locked by pid (eg process dies) + */ +int +kv_unlock_all(clicon_handle h, + int pid) +{ + return db_unlock_all(pid); +} + +/*! Check if database is locked + * @param[in] h Clicon handle + * @param[in] db Database + * @retval -1 Error + * @retval 0 Not locked + * @retval >0 Id of locker + */ +int +kv_islocked(clicon_handle h, + char *db) +{ + return db_islocked(db); +} + +/*! Check if db exists + * @param[in] h Clicon handle + * @param[in] db Database + * @retval -1 Error + * @retval 0 No it does not exist + * @retval 1 Yes it exists + */ +int +kv_exists(clicon_handle h, + char *db) +{ + int retval = -1; + char *filename = NULL; + struct stat sb; + + if (db2file(h, db, &filename) < 0) + goto done; + if (lstat(filename, &sb) < 0) + retval = 0; + else + retval = 1; + done: + if (filename) + free(filename); + return retval; +} + +/*! Delete database. Remove file + * @param[in] h Clicon handle + * @param[in] db Database + * @retval -1 Error + * @retval 0 OK + */ +int +kv_delete(clicon_handle h, + char *db) +{ + int retval = -1; + char *filename = NULL; + + if (db2file(h, db, &filename) < 0) + goto done; + if (db_delete(filename) < 0) + goto done; + retval = 0; + done: + if (filename) + free(filename); + return retval; +} + +/*! Initialize database + * @param[in] h Clicon handle + * @param[in] db Database + * @retval 0 OK + * @retval -1 Error + */ +int +kv_init(clicon_handle h, + char *db) +{ + int retval = -1; + char *filename = NULL; + + if (db2file(h, db, &filename) < 0) + goto done; + if (db_init(filename) < 0) + goto done; + retval = 0; + done: + if (filename) + free(filename); + return retval; +} + +/*! plugin init function */ +int +keyvalue_plugin_exit(void) +{ + return 0; +} + +static const struct xmldb_api api; + +/*! plugin init function */ +void * +clixon_xmldb_plugin_init(int version) +{ + if (version != XMLDB_API_VERSION){ + clicon_err(OE_DB, 0, "Invalid version %d expected %d", + version, XMLDB_API_VERSION); + goto done; + } + return (void*)&api; + done: + return NULL; +} + +static const struct xmldb_api api = { + 1, + XMLDB_API_MAGIC, + clixon_xmldb_plugin_init, + keyvalue_plugin_exit, + kv_get, + kv_put, + kv_dump, + kv_copy, + kv_lock, + kv_unlock, + kv_unlock_all, + kv_islocked, + kv_exists, + kv_delete, + kv_init, +}; + + +#if 0 /* Test program */ +/* + * Turn this on to get an xpath test program + * Usage: clicon_xpath [] + * read xml from input + * Example compile: + gcc -g -o xmldb -I. -I../clixon ./clixon_xmldb.c -lclixon -lcligen +*/ + +static int +usage(char *argv0) +{ + fprintf(stderr, "usage:\n%s\tget []\t\txml on stdin\n", argv0); + fprintf(stderr, "\tput set|merge|delete\txml to stdout\n"); + exit(0); +} + +int +main(int argc, char **argv) +{ + cxobj *xt; + cxobj *xn; + char *xpath; + enum operation_type op; + char *cmd; + char *db; + char *yangdir; + char *yangmod; + yang_spec *yspec = NULL; + clicon_handle h; + + if ((h = clicon_handle_init()) == NULL) + goto done; + clicon_log_init("xmldb", LOG_DEBUG, CLICON_LOG_STDERR); + if (argc < 4){ + usage(argv[0]); + goto done; + } + cmd = argv[1]; + db = argv[2]; + yangdir = argv[3]; + yangmod = argv[4]; + db_init(db); + if ((yspec = yspec_new()) == NULL) + goto done + if (yang_parse(h, yangdir, yangmod, NULL, yspec) < 0) + goto done; + if (strcmp(cmd, "get")==0){ + if (argc < 5) + usage(argv[0]); + xpath = argc>5?argv[5]:NULL; + if (xmldb_get(h, db, xpath, &xt, NULL, NULL) < 0) + goto done; + clicon_xml2file(stdout, xt, 0, 1); + } + else + if (strcmp(cmd, "put")==0){ + if (argc != 6) + usage(argv[0]); + if (clicon_xml_parse_file(0, &xt, "") < 0) + goto done; + if (xml_rootchild(xt, 0, &xn) < 0) + goto done; + if (strcmp(argv[5], "set") == 0) + op = OP_REPLACE; + else + if (strcmp(argv[4], "merge") == 0) + op = OP_MERGE; + else if (strcmp(argv[5], "delete") == 0) + op = OP_REMOVE; + else + usage(argv[0]); + if (xmldb_put(h, db, op, NULL, xn) < 0) + goto done; + } + else + usage(argv[0]); + printf("\n"); + done: + return 0; +} + +#endif /* Test program */ diff --git a/datastore/keyvalue/clixon_keyvalue.h b/datastore/keyvalue/clixon_keyvalue.h new file mode 100644 index 00000000..ed5cea51 --- /dev/null +++ b/datastore/keyvalue/clixon_keyvalue.h @@ -0,0 +1,56 @@ +/* + * + ***** 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 ***** + + Key-value store + */ +#ifndef _CLIXON_KEYVALUE_H +#define _CLIXON_KEYVALUE_H + +/* + * Prototypes + */ +int kv_get(clicon_handle h, char *db, char *xpath, + cxobj **xtop, cxobj ***xvec, size_t *xlen); +int kv_put(clicon_handle h, char *db, enum operation_type op, + char *api_path, cxobj *xt); +int kv_dump(FILE *f, char *dbfilename, char *rxkey); +int kv_copy(clicon_handle h, char *from, char *to); +int kv_lock(clicon_handle h, char *db, int pid); +int kv_unlock(clicon_handle h, char *db, int pid); +int kv_unlock_all(clicon_handle h, int pid); +int kv_islocked(clicon_handle h, char *db); +int kv_exists(clicon_handle h, char *db); +int kv_delete(clicon_handle h, char *db); +int kv_init(clicon_handle h, char *db); + +#endif /* _CLIXON_KEYVALUE_H */ diff --git a/lib/src/clixon_qdb.c b/datastore/keyvalue/clixon_qdb.c similarity index 100% rename from lib/src/clixon_qdb.c rename to datastore/keyvalue/clixon_qdb.c diff --git a/lib/src/clixon_qdb.h b/datastore/keyvalue/clixon_qdb.h similarity index 100% rename from lib/src/clixon_qdb.h rename to datastore/keyvalue/clixon_qdb.h diff --git a/lib/clixon/clixon_options.h b/lib/clixon/clixon_options.h index a86f618f..859bfb49 100644 --- a/lib/clixon/clixon_options.h +++ b/lib/clixon/clixon_options.h @@ -93,6 +93,7 @@ char *clicon_clispec_dir(clicon_handle h); char *clicon_netconf_dir(clicon_handle h); char *clicon_restconf_dir(clicon_handle h); char *clicon_archive_dir(clicon_handle h); +char *clicon_xmldb_plugin(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_xml_db.h b/lib/clixon/clixon_xml_db.h index 8889a8d8..04325360 100644 --- a/lib/clixon/clixon_xml_db.h +++ b/lib/clixon/clixon_xml_db.h @@ -30,18 +30,83 @@ the terms of any one of the Apache License version 2 or the GPL. ***** END LICENSE BLOCK ***** - - * XML support functions. */ #ifndef _CLIXON_XML_DB_H #define _CLIXON_XML_DB_H +/* Version of clixon datastore plugin API. */ +#define XMLDB_API_VERSION 1 + +/* Magic to ensure plugin sanity. */ +#define XMLDB_API_MAGIC 0xf386f730 + +/* Name of plugin init function (must be called this) */ +#define XMLDB_PLUGIN_INIT_FN "clixon_xmldb_plugin_init" + +/* Type of plugin init function */ +typedef void * (plugin_init_t)(int version); + +/* Type of plugin exit function */ +typedef int (plugin_exit_t)(void); + +/* Type of xmldb get function */ +typedef int (xmldb_get_t)(clicon_handle h, char *db, char *xpath, + cxobj **xtop, cxobj ***xvec, size_t *xlen); + +/* Type of xmldb put function */ +typedef int (xmldb_put_t)(clicon_handle h, char *db, enum operation_type op, + char *api_path, cxobj *xt); + +/* Type of xmldb dump function */ +typedef int (xmldb_dump_t)(FILE *f, char *dbfilename, char *rxkey); + +/* Type of xmldb copy function */ +typedef int (xmldb_copy_t)(clicon_handle h, char *from, char *to); + +/* Type of xmldb lock function */ +typedef int (xmldb_lock_t)(clicon_handle h, char *db, int pid); + +/* Type of xmldb unlock function */ +typedef int (xmldb_unlock_t)(clicon_handle h, char *db, int pid); + +/* Type of xmldb unlock_all function */ +typedef int (xmldb_unlock_all_t)(clicon_handle h, int pid); + +/* Type of xmldb islocked function */ +typedef int (xmldb_islocked_t)(clicon_handle h, char *db); + +/* Type of xmldb exists function */ +typedef int (xmldb_exists_t)(clicon_handle h, char *db); + +/* Type of xmldb delete function */ +typedef int (xmldb_delete_t)(clicon_handle h, char *db); + +/* Type of xmldb init function */ +typedef int (xmldb_init_t)(clicon_handle h, char *db); + +/* grideye agent plugin init struct for the api */ +struct xmldb_api{ + int xa_version; + int xa_magic; + plugin_init_t *xa_plugin_init_fn; /* XMLDB_PLUGIN_INIT_FN */ + plugin_exit_t *xa_plugin_exit_fn; + xmldb_get_t *xa_get_fn; + xmldb_put_t *xa_put_fn; + xmldb_dump_t *xa_dump_fn; + xmldb_copy_t *xa_copy_fn; + xmldb_lock_t *xa_lock_fn; + xmldb_unlock_t *xa_unlock_fn; + xmldb_unlock_all_t *xa_unlock_all_fn; + xmldb_islocked_t *xa_islocked_fn; + xmldb_exists_t *xa_exists_fn; + xmldb_delete_t *xa_delete_fn; + xmldb_init_t *xa_init_fn; +}; + /* * Prototypes */ -int yang2xmlkeyfmt(yang_stmt *ys, int inclkey, char **xkfmt); -int xmlkeyfmt2key(char *xkfmt, cvec *cvv, char **xk); -int xmlkeyfmt2xpath(char *xkfmt, cvec *cvv, char **xk); +int xmldb_plugin_load(char *filename); int xmldb_get(clicon_handle h, char *db, char *xpath, cxobj **xtop, cxobj ***xvec, size_t *xlen); diff --git a/lib/clixon/clixon_xml_map.h b/lib/clixon/clixon_xml_map.h index f90a8e71..7cbc4680 100644 --- a/lib/clixon/clixon_xml_map.h +++ b/lib/clixon/clixon_xml_map.h @@ -61,5 +61,8 @@ int xml_diff(yang_spec *yspec, cxobj *xt1, cxobj *xt2, cxobj ***first, size_t *firstlen, cxobj ***second, size_t *secondlen, cxobj ***changed1, cxobj ***changed2, size_t *changedlen); +int yang2xmlkeyfmt(yang_stmt *ys, int inclkey, char **xkfmt); +int xmlkeyfmt2key(char *xkfmt, cvec *cvv, char **xk); +int xmlkeyfmt2xpath(char *xkfmt, cvec *cvv, char **xk); #endif /* _CLIXON_XML_MAP_H_ */ diff --git a/lib/src/Makefile.in b/lib/src/Makefile.in index ac869185..35221e22 100644 --- a/lib/src/Makefile.in +++ b/lib/src/Makefile.in @@ -61,15 +61,14 @@ CPPFLAGS = @CPPFLAGS@ INCLUDES = -I. @INCLUDES@ -I$(top_srcdir)/lib/clixon -I$(top_srcdir)/include -I$(top_srcdir) -SRC = clixon_sig.c clixon_qdb.c clixon_log.c clixon_err.c clixon_event.c \ +SRC = clixon_sig.c clixon_log.c clixon_err.c clixon_event.c \ clixon_chunk.c clixon_proc.c \ clixon_string.c clixon_handle.c \ clixon_xml.c clixon_xml_map.c clixon_file.c \ - clixon_json.c \ - clixon_yang.c clixon_yang_type.c \ + clixon_json.c clixon_yang.c clixon_yang_type.c \ clixon_hash.c clixon_options.c clixon_plugin.c \ clixon_proto.c clixon_proto_client.c \ - clixon_xsl.c clixon_sha1.c clixon_xml_db.c + clixon_xsl.c clixon_sha1.c clixon_xml_db.c YACCOBJS := lex.clixon_xml_parse.o clixon_xml_parse.tab.o \ lex.clixon_yang_parse.o clixon_yang_parse.tab.o \ diff --git a/lib/src/clixon_options.c b/lib/src/clixon_options.c index 32df541e..11b6d403 100644 --- a/lib/src/clixon_options.c +++ b/lib/src/clixon_options.c @@ -237,6 +237,10 @@ clicon_option_sanity(clicon_hash_t *copt) clicon_err(OE_UNIX, 0, "CLICON_ARCHIVE_DIR not defined in config file"); goto done; } + if (!hash_lookup(copt, "CLICON_XMLDB_DIR")){ + clicon_err(OE_UNIX, 0, "CLICON_XMLDB_DIR not defined in config file"); + goto done; + } if (!hash_lookup(copt, "CLICON_SOCK")){ clicon_err(OE_UNIX, 0, "CLICON_SOCK not defined in config file"); goto done; @@ -453,6 +457,12 @@ clicon_archive_dir(clicon_handle h) return clicon_option_str(h, "CLICON_ARCHIVE_DIR"); } +char * +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_sock_family(clicon_handle h) diff --git a/lib/src/clixon_xml_db.c b/lib/src/clixon_xml_db.c index d79a93ff..66350e48 100644 --- a/lib/src/clixon_xml_db.c +++ b/lib/src/clixon_xml_db.c @@ -31,1974 +31,278 @@ ***** END LICENSE BLOCK ***** */ -/* - * An xml database consists of key-value pairs for xml-trees. - * Each node in an xml-tree has a key and an optional value. - * The key (xmlkey) is constructed from the xml node name concatenated - * with its ancestors and any eventual list keys. - * A xmlkeyfmt is a help-structure used when accessing the XML database. - * It consists of an xmlkey but with the key fields replaced with wild-chars(%s) - * Example: /aaa/bbb/%s/%s/ccc - * Such an xmlkeyfmt can be obtained from a yang-statement by following - * its ancestors to the root module. If one of the ancestors is a list, - * a wildchar (%s) is inserted for each key. - * These xmlkeyfmt keys are saved and used in cli callbacks such as when - * modifying syntax (eg cli_merge/cli_delete) or when completing for sub-symbols - * In this case, the variables are set and the wildcards can be instantiated. - * An xml tree can then be formed that can be used to the xmldb_get() or - * xmldb_put() functions. - * The relations between the functions and formats are as follows: - * - * +-----------------+ +-----------------+ - * | yang-stmt | yang2xmlkeyfmt | xmlkeyfmt | xmlkeyfmt2xpath - * | list aa,leaf k | ----------------->| /aa=%s |----------------> - * +-----------------+ +-----------------+ - * | - * | xmlkeyfmt2key - * | k=17 - * v - * +-------------------+ +-----------------+ - * | xml-tree/cxobj | xmlkey2xml | xmlkey RFC3986| - * | 17| <------------- | /aa=17 | - * +-------------------+ +-----------------+ - * - * Alternative for xmlkeyfmt would be eg: - * RESTCONF: /interfaces/interface=%s/ipv4/address/ip=%s (used) - * XPATH: /interfaces/interface[name=%s]/ipv4/address/[ip=%s] - * - * Paths through the code (for coverage) - * cli_callback_generate +----------------+ - * cli_expand_var_generate | yang2xmlkeyfmt | - * yang -------------> | | - * +----------------+ - * xmldb_get_tree - * - compare_dbs - * - netconf - * - validate - * - from_client_save - * - * xmldb_get_vec - * - restconf - * - expand_dbvar - * - show_conf_xpath - */ -#ifdef HAVE_CONFIG_H -#include "clixon_config.h" /* generated by config & autoconf */ -#endif -#include #include +#include #include #include -#include -#include -#include -#include -#include -#include -#include -#include +#include #include +#include +#include +#include +#include +#include +#include +#include #include +#include /* cligen */ #include -/* clicon */ +/* clixon */ #include "clixon_err.h" #include "clixon_log.h" -#include "clixon_file.h" #include "clixon_queue.h" -#include "clixon_string.h" -#include "clixon_chunk.h" #include "clixon_hash.h" #include "clixon_handle.h" -#include "clixon_qdb.h" -#include "clixon_yang.h" -#include "clixon_handle.h" #include "clixon_xml.h" -#include "clixon_options.h" -#include "clixon_xsl.h" -#include "clixon_xml_parse.h" #include "clixon_xml_db.h" -/*! Construct an xml key format from yang statement using wildcards for keys - * Recursively construct it to the top. - * Example: - * yang: container a -> list b -> key c -> leaf d - * xpath: /a/b/%s/d - * @param[in] ys Yang statement - * @param[in] inclkey If inclkey then include key leaf (eg last leaf d in ex) - * @param[out] cbuf keyfmt - */ -static int -yang2xmlkeyfmt_1(yang_stmt *ys, - int inclkey, - cbuf *cb) -{ - yang_node *yp; /* parent */ - yang_stmt *ykey; - int i; - cvec *cvk = NULL; /* vector of index keys */ - int retval = -1; +static struct xmldb_api *_xa_api = NULL; - yp = ys->ys_parent; - if (yp != NULL && - yp->yn_keyword != Y_MODULE && - yp->yn_keyword != Y_SUBMODULE){ - if (yang2xmlkeyfmt_1((yang_stmt *)yp, 1, cb) < 0) - goto done; - } - if (inclkey){ - if (ys->ys_keyword != Y_CHOICE && ys->ys_keyword != Y_CASE) - cprintf(cb, "/%s", ys->ys_argument); - } - else{ - if (ys->ys_keyword == Y_LEAF && yp && yp->yn_keyword == Y_LIST){ - if (yang_key_match(yp, ys->ys_argument) == 0) - cprintf(cb, "/%s", ys->ys_argument); /* Not if leaf and key */ - } - else - if (ys->ys_keyword != Y_CHOICE && ys->ys_keyword != Y_CASE) - cprintf(cb, "/%s", ys->ys_argument); - } - - switch (ys->ys_keyword){ - case Y_LIST: - if ((ykey = yang_find((yang_node*)ys, Y_KEY, NULL)) == NULL){ - clicon_err(OE_XML, errno, "%s: List statement \"%s\" has no key", - __FUNCTION__, ys->ys_argument); - goto done; - } - /* The value is a list of keys: [ ]* */ - if ((cvk = yang_arg2cvec(ykey, " ")) == NULL) - goto done; - if (cvec_len(cvk)) - cprintf(cb, "="); - /* Iterate over individual keys */ - for (i=0; i list b -> key c -> leaf d - * xpath: /a/b=%s/d - * @param[in] ys Yang statement - * @param[in] inclkey If !inclkey then dont include key leaf - * @param[out] xkfmt XML key format. Needs to be freed after use. - */ -int -yang2xmlkeyfmt(yang_stmt *ys, - int inclkey, - char **xkfmt) -{ - int retval = -1; - cbuf *cb = NULL; - - if ((cb = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - if (yang2xmlkeyfmt_1(ys, inclkey, cb) < 0) - goto done; - if ((*xkfmt = strdup(cbuf_get(cb))) == NULL){ - clicon_err(OE_UNIX, errno, "strdup"); - goto done; - } - retval = 0; - done: - if (cb) - cbuf_free(cb); - return retval; -} - - -/*! 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 - * cvv: key=17 - * xmlkey: /aaa/17 - * @param[in] xkfmt XML key format, eg /aaa/%s - * @param[in] cvv cligen variable vector, one for every wildchar in xkfmt - * @param[out] xk XML key, eg /aaa/17. Free after use - * @note first and last elements of cvv are not used,.. - * @see cli_dbxml where this function is called - */ -int -xmlkeyfmt2key(char *xkfmt, - cvec *cvv, - char **xk) -{ - int retval = -1; - char c; - int esc=0; - cbuf *cb = NULL; - int i; - int j; - char *str; - char *strenc=NULL; - - - /* Sanity check */ -#if 1 - j = 0; /* Count % */ - for (i=0; i0 Id of locker - */ -static int -db_islocked(char *db) -{ - if (strcmp("running", db) == 0) - return (_running_locked); - else if (strcmp("candidate", db) == 0) - return(_candidate_locked); - else if (strcmp("startup", db) == 0) - return(_startup_locked); - return 0; -} - - - -/*! Translate from symbolic database name to actual filename in file-system - * @param[in] h Clicon handle - * @param[in] db Symbolic database name, eg "candidate", "running" - * @param[out] filename Filename. Unallocate after use with free() - * @retval 0 OK - * @retval -1 Error - * @note Could need a way to extend which databases exists, eg to register new. - * The currently allowed databases are: - * candidate, tmp, running, result - * The filename reside in CLICON_XMLDB_DIR option - */ -static int -db2file(clicon_handle h, - char *db, - char **filename) -{ - int retval = -1; - cbuf *cb; - char *dir; - - if ((cb = cbuf_new()) == NULL){ - clicon_err(OE_XML, errno, "cbuf_new"); - goto done; - } - if ((dir = clicon_xmldb_dir(h)) == NULL){ - clicon_err(OE_XML, errno, "CLICON_XMLDB_DIR not set"); - goto done; - } - if (strcmp(db, "running") != 0 && - strcmp(db, "candidate") != 0 && - strcmp(db, "startup") != 0 && - strcmp(db, "tmp") != 0){ - clicon_err(OE_XML, 0, "Unexpected database: %s", db); - goto done; - } - cprintf(cb, "%s/%s_db", dir, db); - if ((*filename = strdup4(cbuf_get(cb))) == NULL){ - clicon_err(OE_UNIX, errno, "strdup"); - goto done; - } - retval = 0; - done: - if (cb) - cbuf_free(cb); - return retval; -} - -/*! Help function to append key values from an xml list to a cbuf - * Example, a yang node x with keys a and b results in "x/a/b" - */ -static int -append_listkeys(cbuf *ckey, - cxobj *xt, - yang_stmt *ys) -{ - int retval = -1; - yang_stmt *ykey; - cxobj *xkey; - cg_var *cvi; - cvec *cvk = NULL; /* vector of index keys */ - char *keyname; - char *bodyenc; - int i=0; - - if ((ykey = yang_find((yang_node*)ys, Y_KEY, NULL)) == NULL){ - clicon_err(OE_XML, errno, "%s: List statement \"%s\" has no key", - __FUNCTION__, ys->ys_argument); - goto done; - } - /* The value is a list of keys: [ ]* */ - if ((cvk = yang_arg2cvec(ykey, " ")) == NULL) - goto done; - cvi = NULL; - /* Iterate over individual keys */ - while ((cvi = cvec_each(cvk, cvi)) != NULL) { - keyname = cv_string_get(cvi); - if ((xkey = xml_find(xt, keyname)) == NULL){ - clicon_err(OE_XML, errno, "XML list node \"%s\" does not have key \"%s\" child", - xml_name(xt), keyname); - goto done; - } - if ((bodyenc = curl_easy_escape(NULL, xml_body(xkey), 0)) == NULL){ - clicon_err(OE_UNIX, errno, "curl_easy_escape"); - goto done; - } - if (i++) - cprintf(ckey, ","); - else - cprintf(ckey, "="); - cprintf(ckey, "%s", bodyenc); - free(bodyenc); bodyenc = NULL; - } - retval = 0; - done: - if (cvk) - cvec_free(cvk); - return retval; -} - -/*! Help function to create xml key values - * @param[in,out] x Parent - * @param[in] ykey - * @param[in] arg - * @param[in] keyname yang key name - */ -static int -create_keyvalues(cxobj *x, - yang_stmt *ykey, - char *arg, - char *keyname) -{ - int retval = -1; - cxobj *xb; - - /* Check if key node exists */ - if ((xb = xml_new_spec(keyname, x, ykey)) == NULL) - goto done; - if ((xb = xml_new("body", xb)) == NULL) - goto done; - xml_type_set(xb, CX_BODY); - xml_value_set(xb, arg); - retval = 0; - done: - return retval; -} - -/*! Prune everything that has not been marked - * @param[in] xt XML tree with some node marked - * @param[out] upmark Set if a child (recursively) has marked set. - * The function removes all branches that does not contain a marked child - * XXX: maybe key leafs should not be purged if list is not purged? - * XXX: consider move to clicon_xml - */ -static int -xml_tree_prune_unmarked(cxobj *xt, - int *upmark) -{ - int retval = -1; - int submark; - int mark; - cxobj *x; - cxobj *xprev; - - mark = 0; - x = NULL; - xprev = x = NULL; - while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) { - if (xml_flag(x, XML_FLAG_MARK)){ - mark++; - xprev = x; - continue; /* mark and stop here */ - } - if (xml_tree_prune_unmarked(x, &submark) < 0) - goto done; - if (submark) - mark++; - else{ /* Safe with xml_child_each if last */ - if (xml_purge(x) < 0) - goto done; - x = xprev; - } - xprev = x; - } - retval = 0; - done: - if (upmark) - *upmark = mark; - return retval; -} - -/*! - * @param[in] xk xmlkey - * @param[out] xt XML tree as result - * XXX cannot handle top-level list - */ -static int -get(char *dbname, - yang_spec *ys, - char *xk, - char *val, - cxobj *xt) -{ - int retval = -1; - char **vec = NULL; - int nvec; - char **valvec = NULL; - int nvalvec; - int i; - int j; - char *name; - char *restval; - yang_stmt *y; - cxobj *x; - cxobj *xc; - cxobj *xb; - yang_stmt *ykey; - cg_var *cvi; - cvec *cvk = NULL; /* vector of index keys */ - char *keyname; - char *arg; - char *argdec; - cbuf *cb; - - // clicon_debug(1, "%s xkey:%s val:%s", __FUNCTION__, xk, val); - x = xt; - if (xk == NULL || *xk!='/'){ - clicon_err(OE_DB, 0, "Invalid key: %s", xk); - goto done; - } - if ((vec = clicon_strsep(xk, "/", &nvec)) == NULL) - goto done; - /* Element 0 is NULL '/', - Element 1 is top symbol and needs to find subs in all modules: - spec->module->syntaxnode - */ - if (nvec < 2){ - clicon_err(OE_XML, 0, "Malformed key: %s", xk); - goto done; - } - i = 1; - while (i name:x restval=1,2 */ - if ((restval = index(name, '=')) != NULL){ - *restval = '\0'; - restval++; - } - if (i == 1){ /* spec->module->node */ - if ((y = yang_find_topnode(ys, name)) == NULL){ - clicon_err(OE_UNIX, errno, "No yang node found: %s", name); - goto done; - } - } - else - if ((y = yang_find_syntax((yang_node*)y, name)) == NULL){ - clicon_err(OE_UNIX, errno, "No yang node found: %s", name); - goto done; - } - switch (y->ys_keyword){ - case Y_LEAF_LIST: - /* - * If xml element is a leaf-list, then the next element is expected to - * be a value - */ - if ((argdec = curl_easy_unescape(NULL, restval, 0, NULL)) == NULL){ - clicon_err(OE_UNIX, errno, "curl_easy_escape"); - goto done; - } - if ((xc = xml_find(x, name))==NULL || - (xb = xml_find(xc, argdec))==NULL){ - if ((xc = xml_new_spec(name, x, y)) == NULL) - goto done; - /* Assume body is created at end of function */ - } - free(argdec); - argdec = NULL; - break; - case Y_LIST: - /* - * If xml element is a list, then the next element(s) is expected to be - * a key value. Check if this key value is already in the xml tree, - * otherwise create it. - */ - 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; - if ((cb = cbuf_new()) == NULL){ - clicon_err(OE_XML, errno, "cbuf_new"); - goto done; - } - cvi = NULL; - /* Iterate over individual yang keys */ - cprintf(cb, "%s", name); - if (valvec) - free(valvec); - if ((valvec = clicon_strsep(restval, ",", &nvalvec)) == NULL) - goto done; - if (cvec_len(cvk)!=nvalvec){ - retval = 0; - goto done; - } - j = 0; - while ((cvi = cvec_each(cvk, cvi)) != NULL){ - if (j>=nvalvec) - break; - arg = valvec[j++]; - if ((argdec = curl_easy_unescape(NULL, arg, 0, NULL)) == NULL){ - clicon_err(OE_UNIX, errno, "curl_easy_escape"); - goto done; - } - cprintf(cb, "[%s=%s]", cv_string_get(cvi), argdec); - free(argdec); - argdec=NULL; - } - if ((xc = xpath_first(x, cbuf_get(cb))) == NULL){ - if ((xc = xml_new_spec(name, x, y)) == NULL) - goto done; - cvi = NULL; - // i -= cvec_len(cvk); - /* Iterate over individual yang keys */ - j=0; - while ((cvi = cvec_each(cvk, cvi)) != NULL) { - if (j>=nvalvec) - break; - arg = valvec[j++]; - keyname = cv_string_get(cvi); - if ((argdec = curl_easy_unescape(NULL, arg, 0, NULL)) == NULL){ - clicon_err(OE_UNIX, errno, "curl_easy_escape"); - goto done; - } - if (create_keyvalues(xc, - ykey, - argdec, - keyname) < 0) - goto done; - free(argdec); argdec = NULL; - } /* while */ - } - if (cb){ - cbuf_free(cb); - cb = NULL; - } - if (cvk){ - cvec_free(cvk); - cvk = NULL; - } - break; - case Y_LEAF: - case Y_CONTAINER: - default: - if ((xc = xml_find(x, name))==NULL) - if ((xc = xml_new_spec(name, x, y)) == NULL) - goto done; - break; - } /* switch */ - x = xc; - i++; - } - if (val && xml_body(x)==NULL){ - if ((x = xml_new("body", x)) == NULL) - goto done; - xml_type_set(x, CX_BODY); - xml_value_set(x, val); - } - if(debug>1){ - fprintf(stderr, "%s %s\n", __FUNCTION__, xk); - clicon_xml2file(stderr, xt, 0, 1); - } - retval = 0; - done: - if (vec) - free(vec); - if (valvec) - free(valvec); - if (cvk) - cvec_free(cvk); - return retval; -} - -/*! Sanitize an xml tree: xml node has matching yang_stmt pointer - */ -static int -xml_sanity(cxobj *x, - void *arg) -{ - int retval = -1; - yang_stmt *ys; - - ys = (yang_stmt*)xml_spec(x); - if (ys==NULL){ - clicon_err(OE_XML, 0, "No spec for xml node %s", xml_name(x)); - goto done; - } - if (strstr(ys->ys_argument, xml_name(x))==NULL){ - clicon_err(OE_XML, 0, "xml node name '%s' does not match yang spec arg '%s'", - xml_name(x), ys->ys_argument); - goto done; - } - retval = 0; - done: - return retval; -} - -/*! Add default values (if not set) - */ -static int -xml_default(cxobj *x, - void *arg) -{ - int retval = -1; - yang_stmt *ys; - yang_stmt *y; - int i; - cxobj *xc; - cxobj *xb; - char *str; - - ys = (yang_stmt*)xml_spec(x); - /* Check leaf defaults */ - if (ys->ys_keyword == Y_CONTAINER || ys->ys_keyword == Y_LIST){ - for (i=0; iys_len; i++){ - y = ys->ys_stmt[i]; - if (y->ys_keyword != Y_LEAF) - continue; - assert(y->ys_cv); - if (!cv_flag(y->ys_cv, V_UNSET)){ /* Default value exists */ - if (!xml_find(x, y->ys_argument)){ - if ((xc = xml_new_spec(y->ys_argument, x, y)) == NULL) - goto done; - if ((xb = xml_new("body", xc)) == NULL) - goto done; - xml_type_set(xb, CX_BODY); - if ((str = cv2str_dup(y->ys_cv)) == NULL){ - clicon_err(OE_UNIX, errno, "cv2str_dup"); - goto done; - } - if (xml_value_set(xb, str) < 0) - goto done; - free(str); - } - } - } - } - retval = 0; - done: - return retval; -} - -/*! Order XML children according to YANG - */ -static int -xml_order(cxobj *x, - void *arg) -{ - int retval = -1; - yang_stmt *y; - yang_stmt *yc; - int i; - int j0; - int j; - cxobj *xc; - cxobj *xj; - char *yname; /* yang child name */ - char *xname; /* xml child name */ - - y = (yang_stmt*)xml_spec(x); - j0 = 0; - /* Go through xml children and ensure they are same order as yspec children */ - for (i=0; iys_len; i++){ - yc = y->ys_stmt[i]; - if (!yang_is_syntax(yc)) - continue; - yname = yc->ys_argument; - /* First go thru xml children with same name */ - for (;j01) - clicon_xml2file(stderr, xt, 0, 1); - *xtop = xt; - retval = 0; - done: - if (dbname) - free(dbname); - if (xvec) - free(xvec); - unchunk_group(__FUNCTION__); - return retval; - -} - -/*! Add data to database internal recursive function - * @param[in] dbname Name of database to search in (filename incl dir path) - * @param[in] xt xml-node. - * @param[in] ys Yang statement corresponding to xml-node - * @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc - * @param[in] xkey0 aggregated xmlkey - * @retval 0 OK - * @retval -1 Error - * @note XXX op only supports merge - */ -static int -put(char *dbname, - cxobj *xt, - yang_stmt *ys, - enum operation_type op, - const char *xk0) -{ - int retval = -1; - cxobj *x = NULL; - char *xk; - cbuf *cbxk = NULL; - char *body; - yang_stmt *y; - int exists; - char *bodyenc=NULL; - char *opstr; - - clicon_debug(1, "%s xk0:%s ys:%s", __FUNCTION__, xk0, ys->ys_argument); - if (debug){ - xml_print(stderr, xt); - // yang_print(stderr, (yang_node*)ys, 0); - } - if ((opstr = xml_find_value(xt, "operation")) != NULL) - if (xml_operation(opstr, &op) < 0) - goto done; - body = xml_body(xt); - if ((cbxk = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - cprintf(cbxk, "%s/%s", xk0, xml_name(xt)); - switch (ys->ys_keyword){ - case Y_LIST: /* Note: can have many keys */ - if (append_listkeys(cbxk, xt, ys) < 0) - goto done; - break; - case Y_LEAF_LIST: - if ((bodyenc = curl_easy_escape(NULL, body, 0)) == NULL){ - clicon_err(OE_UNIX, errno, "curl_easy_escape"); - goto done; - } - cprintf(cbxk, "=%s", bodyenc); - break; - default: - break; - } - xk = cbuf_get(cbxk); - // fprintf(stderr, "%s %s\n", key, body?body:""); - /* Write to database, key and a vector of variables */ - switch (op){ - case OP_CREATE: - if ((exists = db_exists(dbname, xk)) < 0) - goto done; - if (exists == 1){ - clicon_err(OE_DB, 0, "OP_CREATE: %s already exists in database", xk); - goto done; - } - case OP_MERGE: - case OP_REPLACE: - if (db_set(dbname, xk, body?body:NULL, body?strlen(body)+1:0) < 0) - goto done; - break; - case OP_DELETE: - if ((exists = db_exists(dbname, xk)) < 0) - goto done; - if (exists == 0){ - clicon_err(OE_DB, 0, "OP_DELETE: %s does not exists in database", xk); - goto done; - } - case OP_REMOVE: - if (db_del(dbname, xk) < 0) - goto done; - break; - case OP_NONE: - break; - } - /* For every node, create a key with values */ - while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL){ - if ((y = yang_find_syntax((yang_node*)ys, xml_name(x))) == NULL){ - clicon_err(OE_UNIX, 0, "No yang node found: %s", xml_name(x)); - goto done; - } - if (put(dbname, x, y, op, xk) < 0) - goto done; - } - retval = 0; - done: - if (cbxk) - cbuf_free(cbxk); - if (bodyenc) - free(bodyenc); - return retval; -} - -/*! Modify database provided an XML database key and an operation - * @param[in] h CLICON handle - * @param[in] db Database name - * @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc - * @param[in] xk XML Key, eg /aa/bb=17/name - * @param[in] val Key value, eg "17" - - * @retval 0 OK - * @retval -1 Error - * @code - * if (xmldb_put_xkey(h, db, OP_MERGE, "/aa/bb=17/name", "17") < 0) - * err; - * @endcode - * @see xmldb_put with xml-tree, no path - */ -static int -xmldb_put_xkey(clicon_handle h, - char *db, - enum operation_type op, - char *xk, - char *val) - -{ - int retval = -1; - cxobj *x = NULL; - yang_stmt *y = NULL; - yang_stmt *ykey; - char **vec = NULL; - int nvec; - char **valvec = NULL; - int nvalvec; - int i; - int j; - char *name; - char *restval; - cg_var *cvi; - cvec *cvk = NULL; /* vector of index keys */ - char *val2 = NULL; - cbuf *ckey=NULL; /* partial keys */ - cbuf *csubkey=NULL; /* partial keys */ - cbuf *crx=NULL; /* partial keys */ - char *keyname; - int exists; - int npairs; - struct db_pair *pairs; - yang_spec *yspec; - char *filename = NULL; - - yspec = clicon_dbspec_yang(h); - if (db2file(h, db, &filename) < 0) - goto done; - if (xk == NULL || *xk!='/'){ - clicon_err(OE_DB, 0, "Invalid key: %s", xk); - goto done; - } - if ((ckey = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - if ((csubkey = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - if ((vec = clicon_strsep(xk, "/", &nvec)) == NULL) - goto done; - if (nvec < 2){ - clicon_err(OE_XML, 0, "Malformed key: %s", xk); - goto done; - } - i = 1; - while (i name:x restval=1,2 */ - if ((restval = index(name, '=')) != NULL){ - *restval = '\0'; - restval++; - } - if (i==1){ - if (strlen(name)==0 && (op==OP_DELETE || op == OP_REMOVE)){ - /* Special handling of "/" */ - cprintf(ckey, "/"); - break; - } - else - if ((y = yang_find_topnode(yspec, name)) == NULL){ - clicon_err(OE_UNIX, errno, "No yang node found: %s", x?xml_name(x):""); - goto done; - } - } - else - if ((y = yang_find_syntax((yang_node*)y, name)) == NULL){ - clicon_err(OE_UNIX, errno, "No yang node found: %s", name); - goto done; - } - if ((op==OP_DELETE || op == OP_REMOVE) && - y->ys_keyword == Y_LEAF && - y->ys_parent->yn_keyword == Y_LIST && - yang_key_match(y->ys_parent, y->ys_argument)) - /* Special rule if key, dont write last key-name, rm whole*/; - else - cprintf(ckey, "/%s", name); - i++; - switch (y->ys_keyword){ - case Y_LEAF_LIST: - if (restval==NULL){ - clicon_err(OE_XML, 0, "malformed key, expected '='"); - goto done; - } - cprintf(ckey, "=%s", restval); - break; - 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; - if (restval==NULL){ - clicon_err(OE_XML, 0, "malformed key, expected '='"); - goto done; - } - if (valvec) - free(valvec); - if ((valvec = clicon_strsep(restval, ",", &nvalvec)) == NULL) - goto done; - - if (cvec_len(cvk) != nvalvec){ - clicon_err(OE_XML, errno, "List %s key length mismatch", name); - goto done; - } - cvi = NULL; - /* Iterate over individual yang keys */ - j = 0; - while ((cvi = cvec_each(cvk, cvi)) != NULL) { - keyname = cv_string_get(cvi); - if (j) - cprintf(ckey, ","); - else - cprintf(ckey, "="); - val2 = valvec[j++]; - - cprintf(ckey, "%s", val2); - cbuf_reset(csubkey); - cprintf(csubkey, "%s/%s", cbuf_get(ckey), keyname); - if (op == OP_MERGE || op == OP_REPLACE || op == OP_CREATE) - if (db_set(filename, cbuf_get(csubkey), val2, strlen(val2)+1) < 0) - goto done; - } - if (cvk){ - cvec_free(cvk); - cvk = NULL; - } - break; - default: - if (op == OP_MERGE || op == OP_REPLACE || op == OP_CREATE) - if (db_set(filename, cbuf_get(ckey), NULL, 0) < 0) - goto done; - break; - } - } - xk = cbuf_get(ckey); - /* final key */ - switch (op){ - case OP_CREATE: - if ((exists = db_exists(filename, xk)) < 0) - goto done; - if (exists == 1){ - clicon_err(OE_DB, 0, "OP_CREATE: %s already exists in database", xk); - goto done; - } - case OP_MERGE: - case OP_REPLACE: - if (y->ys_keyword == Y_LEAF || y->ys_keyword == Y_LEAF_LIST){ - if (db_set(filename, xk, val, val?strlen(val)+1:0) < 0) - goto done; - } - else - if (db_set(filename, xk, NULL, 0) < 0) - goto done; - break; - case OP_DELETE: - if ((exists = db_exists(filename, xk)) < 0) - goto done; - if (exists == 0){ - clicon_err(OE_DB, 0, "OP_DELETE: %s does not exists in database", xk); - goto done; - } - case OP_REMOVE: - /* Read in complete database (this can be optimized) */ - if ((crx = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - cprintf(crx, "^%s.*$", xk); - if ((npairs = db_regexp(filename, cbuf_get(crx), __FUNCTION__, &pairs, 0)) < 0) - goto done; - for (i = 0; i < npairs; i++) { - if (db_del(filename, pairs[i].dp_key) < 0) - goto done; - } - break; - default: - break; - } - retval = 0; - done: - if (filename) - free(filename); - if (ckey) - cbuf_free(ckey); - if (csubkey) - cbuf_free(csubkey); - if (crx) - cbuf_free(crx); - if (cvk) - cvec_free(cvk); - if (vec) - free(vec); - if (valvec) - free(valvec); - - unchunk_group(__FUNCTION__); - return retval; -} - -/*! Modify database provided an xml tree, a restconf api_path and an operation - * - * @param[in] h CLICON handle - * @param[in] db running or candidate - * @param[in] op OP_MERGE: just add it. - * OP_REPLACE: first delete whole database - * OP_NONE: operation attribute in xml determines operation - * @param[in] api_path According to restconf (Sec 3.5.1.1 in [restconf-draft 13]) - * @param[in] xt xml-tree. Top-level symbol is dummy - * @retval 0 OK - * @retval -1 Error - * example: - * container top { - * list list1 { - * key "key1 key2 key3"; - * is referenced as - * /restconf/data/top/list1=a,,foo - * @see xmldb_put - */ -static int -xmldb_put_restconf_api_path(clicon_handle h, - char *db, - enum operation_type op, - char *api_path, - cxobj *xt) -{ - int retval = -1; - yang_stmt *y = NULL; - yang_stmt *ykey; - char **vec = NULL; - int nvec; - int i; - char *name; - cg_var *cvi; - cvec *cvk = NULL; /* vector of index keys */ - char *val2; - cbuf *ckey=NULL; /* partial keys */ - cbuf *csubkey=NULL; /* partial keys */ - cbuf *crx=NULL; /* partial keys */ - char *keyname; - int exists; - int npairs; - struct db_pair *pairs; - yang_spec *yspec; - yang_stmt *ys; - char *filename = NULL; - char *key; - char *keys; - - yspec = clicon_dbspec_yang(h); - if (db2file(h, db, &filename) < 0) - goto done; - if (api_path == NULL || *api_path!='/'){ - clicon_err(OE_DB, 0, "Invalid api path: %s", api_path); - goto done; - } - if ((ckey = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - if ((csubkey = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - if ((vec = clicon_strsep(api_path, "/", &nvec)) == NULL) - goto done; - if (nvec < 2){ - clicon_err(OE_XML, 0, "Malformed key: %s", api_path); - goto done; - } - i = 1; - while (iys_keyword == Y_LEAF && - y->ys_parent->yn_keyword == Y_LIST && - yang_key_match(y->ys_parent, y->ys_argument)) - /* Special rule if key, dont write last key-name, rm whole*/; - else - cprintf(ckey, "/%s", name); - i++; - switch (y->ys_keyword){ - case Y_LEAF_LIST: - /* For leaf-list 'keys' is value, see 3.5.1 in restconf draft */ - val2 = keys; - cprintf(ckey, "/%s", keys); - break; - 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; - cvi = NULL; - /* Iterate over individual yang keys */ - while ((cvi = cvec_each(cvk, cvi)) != NULL) { - keyname = cv_string_get(cvi); - // val2 = vec[i++]; /* No */ - val2 = keys; - if (i>nvec){ /* XXX >= ? */ - clicon_err(OE_XML, errno, "List %s without argument", name); - goto done; - } - cprintf(ckey, "=%s", val2); - cbuf_reset(csubkey); - cprintf(csubkey, "%s/%s", cbuf_get(ckey), keyname); - if (op == OP_MERGE || op == OP_REPLACE || op == OP_CREATE) - if (db_set(filename, cbuf_get(csubkey), val2, strlen(val2)+1) < 0) - goto done; - } - if (cvk){ - cvec_free(cvk); - cvk = NULL; - } - break; - default: - if (op == OP_MERGE || op == OP_REPLACE || op == OP_CREATE) - if (db_set(filename, cbuf_get(ckey), NULL, 0) < 0) - goto done; - break; - } - } - key = cbuf_get(ckey); - /* final key */ - switch (op){ - case OP_CREATE: - if ((exists = db_exists(filename, key)) < 0) - goto done; - if (exists == 1){ - clicon_err(OE_DB, 0, "OP_CREATE: %s already exists in database", key); - goto done; - } - case OP_MERGE: - case OP_REPLACE: - if (xt==NULL){ - clicon_err(OE_DB, 0, "%s: no xml when yang node %s required", - __FUNCTION__, y->ys_argument); - goto done; - } - if ((ys = yang_find_syntax((yang_node*)y, xml_name(xt))) == NULL){ - clicon_err(OE_DB, 0, "%s: child %s not found under node %s", - __FUNCTION__, xml_name(xt), y->ys_argument); - goto done; - } - y = ys; - if (put(filename, xt, y, op, key) < 0) - goto done; - break; - case OP_DELETE: - if ((exists = db_exists(filename, key)) < 0) - goto done; - if (exists == 0){ - clicon_err(OE_DB, 0, "OP_DELETE: %s does not exists in database", key); - goto done; - } - case OP_REMOVE: - /* Read in complete database (this can be optimized) */ - if ((crx = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - cprintf(crx, "^%s.*$", key); - if ((npairs = db_regexp(filename, cbuf_get(crx), __FUNCTION__, &pairs, 0)) < 0) - goto done; - for (i = 0; i < npairs; i++) { - if (db_del(filename, pairs[i].dp_key) < 0) - goto done; - } - break; - default: - break; - } - retval = 0; - done: - // clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); - if (filename) - free(filename); - if (ckey) - cbuf_free(ckey); - if (csubkey) - cbuf_free(csubkey); - if (crx) - cbuf_free(crx); - if (cvk) - cvec_free(cvk); - if (vec) - free(vec); - unchunk_group(__FUNCTION__); - return retval; -} - -/*! Modify database provided an xml tree and an operation - * - * @param[in] h CLICON handle - * @param[in] db running or candidate - * @param[in] xt xml-tree. Top-level symbol is dummy - * @param[in] op OP_MERGE: just add it. - * OP_REPLACE: first delete whole database - * OP_NONE: operation attribute in xml determines operation - * @param[in] api_path According to restconf (Sec 3.5.1.1 in [restconf-draft 13]) - * @retval 0 OK - * @retval -1 Error - * The xml may contain the "operation" attribute which defines the operation. - * @code - * cxobj *xt; - * if (clicon_xml_parse_str("17", &xt) < 0) - * err; - * if (xmldb_put(h, "running", OP_MERGE, NULL, xt) < 0) - * err; - * @endcode - * @see xmldb_put_xkey for single key - */ -int -xmldb_put(clicon_handle h, - char *db, - enum operation_type op, - char *api_path, - cxobj *xt) -{ - int retval = -1; - cxobj *x = NULL; - yang_stmt *ys; - yang_spec *yspec; - char *dbfilename = NULL; - - if (xml_child_nr(xt)==0 || xml_body(xt)!= NULL) - return xmldb_put_xkey(h, db, op, api_path, xml_body(xt)); - yspec = clicon_dbspec_yang(h); - if (db2file(h, db, &dbfilename) < 0) - goto done; - if (op == OP_REPLACE){ - if (db_delete(dbfilename) < 0) - goto done; - if (db_init(dbfilename) < 0) - goto done; - } - while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL){ - if (api_path && strlen(api_path)){ - if (xmldb_put_restconf_api_path(h, db, op, api_path, x) < 0) - goto done; - continue; - } - if ((ys = yang_find_topnode(yspec, xml_name(x))) == NULL){ - clicon_err(OE_UNIX, errno, "No yang node found: %s", xml_name(x)); - goto done; - } - if (put(dbfilename, /* database name */ - x, /* xml root node */ - ys, /* yang statement of xml node */ - op, /* operation, eg merge/delete */ - "" /* aggregate xml key */ - ) < 0) - goto done; - } - retval = 0; - done: - if (dbfilename) - free(dbfilename); - return retval; -} - - -/*! Raw dump of database, just keys and values, no xml interpretation - * @param[in] f File - * @param[in] dbfile File-name of database. This is a local file - * @param[in] rxkey Key regexp, eg "^.*$" - * @note This function can only be called locally. - */ -int -xmldb_dump(FILE *f, - char *dbfilename, - char *rxkey) -{ - int retval = -1; - int npairs; - struct db_pair *pairs; - - /* Default is match all */ - if (rxkey == NULL) - rxkey = "^.*$"; - - /* Get all keys/values for vector */ - if ((npairs = db_regexp(dbfilename, rxkey, __FUNCTION__, &pairs, 0)) < 0) - goto done; - - for (npairs--; npairs >= 0; npairs--) - fprintf(f, "%s %s\n", pairs[npairs].dp_key, - pairs[npairs].dp_val?pairs[npairs].dp_val:""); - retval = 0; - done: - unchunk_group(__FUNCTION__); - return retval; -} - -/*! Copy database from db1 to db2 - * @param[in] h Clicon handle - * @param[in] from Source database copy - * @param[in] to Destination database - * @retval -1 Error - * @retval 0 OK - */ -int -xmldb_copy(clicon_handle h, - char *from, - char *to) -{ - int retval = -1; - char *fromfile = NULL; - char *tofile = NULL; - - /* XXX lock */ - if (db2file(h, from, &fromfile) < 0) - goto done; - if (db2file(h, to, &tofile) < 0) - goto done; - if (clicon_file_copy(fromfile, tofile) < 0) - goto done; - retval = 0; - done: - if (fromfile) - free(fromfile); - if (tofile) - free(tofile); - return retval; -} - -/*! Lock database - * @param[in] h Clicon handle - * @param[in] db Database - * @param[in] pid Process id - * @retval -1 Error - * @retval 0 OK - */ -int -xmldb_lock(clicon_handle h, - char *db, - int pid) -{ - int retval = -1; - - if (db_islocked(db)){ - if (pid != db_islocked(db)){ - clicon_err(OE_DB, 0, "lock failed: locked by %d", db_islocked(db)); - goto done; - } - } - else - db_lock(db, pid); - retval = 0; - done: - return retval; -} - -/*! Unlock database - * @param[in] h Clicon handle - * @param[in] db Database - * @param[in] pid Process id - * @retval -1 Error - * @retval 0 OK +/*! Load a specific plugin, call its init function and add it to plugins list + * If init function fails (not found, wrong version, etc) print a log and dont + * add it. + * @param[in] name Name of plugin + * @param[in] filename Actual filename with path + * @param[out] plugin Plugin data structure for invoking. Dealloc with free */ int -xmldb_unlock(clicon_handle h, - char *db, - int pid) +xmldb_plugin_load(char *filename) +{ + int retval = -1; + char *dlerrcode; + plugin_init_t *initfun; + void *handle = NULL; + char *error; + + dlerror(); /* Clear any existing error */ + if ((handle = dlopen(filename, RTLD_NOW|RTLD_GLOBAL)) == NULL) { + error = (char*)dlerror(); + clicon_err(OE_PLUGIN, errno, "dlopen: %s\n", error ? error : "Unknown error"); + goto done; + } + /* Try v1 */ + initfun = dlsym(handle, XMLDB_PLUGIN_INIT_FN); + if ((dlerrcode = (char*)dlerror()) != NULL) { + clicon_log(LOG_WARNING, "Error when loading init function %s: %s", + XMLDB_PLUGIN_INIT_FN, dlerrcode); + goto fail; + } + if ((_xa_api = initfun(XMLDB_API_VERSION)) == NULL) { + clicon_log(LOG_WARNING, "%s: failed when running init function %s: %s", + filename, XMLDB_PLUGIN_INIT_FN, errno?strerror(errno):""); + goto fail; + } + if (_xa_api->xa_version != XMLDB_API_VERSION){ + clicon_log(LOG_WARNING, "%s: Unexpected plugin version number: %d", + filename, _xa_api->xa_version); + goto fail; + } + if (_xa_api->xa_magic != XMLDB_API_MAGIC){ + clicon_log(LOG_WARNING, "%s: Wrong plugin magic number: %x", + filename, _xa_api->xa_magic); + goto fail; + } + clicon_log(LOG_WARNING, "xmldb plugin %s loaded", filename); + retval = 0; + done: + if (retval < 0 && handle) + dlclose(handle); + return retval; + fail: /* plugin load failed, continue */ + retval = 0; + goto done; +} + +int +xmldb_get(clicon_handle h, char *db, char *xpath, + cxobj **xtop, cxobj ***xvec, size_t *xlen) { int retval = -1; - int pid1; - pid1 = db_islocked(db); - if (pid1){ - if (pid == pid1) - db_unlock(db); - else{ - clicon_err(OE_DB, 0, "unlock failed: locked by %d", pid1); - goto done; - } + if (_xa_api == NULL){ + clicon_err(OE_DB, 0, "No xmldb plugin"); + goto done; } + if (_xa_api->xa_get_fn && + _xa_api->xa_get_fn(h, db, xpath, xtop, xvec, xlen) < 0) + goto done; + retval = 0; + done: + return retval; +} + +int +xmldb_put(clicon_handle h, char *db, enum operation_type op, + char *api_path, cxobj *xt) +{ + int retval = -1; + + if (_xa_api == NULL){ + clicon_err(OE_DB, 0, "No xmldb plugin"); + goto done; + } + if (_xa_api->xa_put_fn && + _xa_api->xa_put_fn(h, db, op, api_path, xt) < 0) + goto done; retval = 0; done: return retval; } -/*! Unlock all databases locked by pid (eg process dies) - */ int -xmldb_unlock_all(clicon_handle h, - int pid) +xmldb_dump(FILE *f, char *dbfilename, char *rxkey) { - return db_unlock_all(pid); -} + int retval = -1; -/*! Check if database is locked - * @param[in] h Clicon handle - * @param[in] db Database - * @retval -1 Error - * @retval 0 Not locked - * @retval >0 Id of locker - */ -int -xmldb_islocked(clicon_handle h, - char *db) -{ - return db_islocked(db); -} - -/*! Check if db exists - * @param[in] h Clicon handle - * @param[in] db Database - * @retval -1 Error - * @retval 0 No it does not exist - * @retval 1 Yes it exists - */ -int -xmldb_exists(clicon_handle h, - char *db) -{ - int retval = -1; - char *filename = NULL; - struct stat sb; - - if (db2file(h, db, &filename) < 0) + if (_xa_api == NULL){ + clicon_err(OE_DB, 0, "No xmldb plugin"); goto done; - if (lstat(filename, &sb) < 0) - retval = 0; - else - retval = 1; - done: - if (filename) - free(filename); - return retval; -} - -/*! Delete database. Remove file - * @param[in] h Clicon handle - * @param[in] db Database - * @retval -1 Error - * @retval 0 OK - */ -int -xmldb_delete(clicon_handle h, - char *db) -{ - int retval = -1; - char *filename = NULL; - - if (db2file(h, db, &filename) < 0) - goto done; - if (db_delete(filename) < 0) + } + if (_xa_api->xa_dump_fn && + _xa_api->xa_dump_fn(f, dbfilename, rxkey) < 0) goto done; retval = 0; done: - if (filename) - free(filename); return retval; } -/*! Initialize database - * @param[in] h Clicon handle - * @param[in] db Database - * @retval 0 OK - * @retval -1 Error - */ int -xmldb_init(clicon_handle h, - char *db) +xmldb_copy(clicon_handle h, char *from, char *to) { - int retval = -1; - char *filename = NULL; + int retval = -1; - if (db2file(h, db, &filename) < 0) + if (_xa_api == NULL){ + clicon_err(OE_DB, 0, "No xmldb plugin"); goto done; - if (db_init(filename) < 0) + } + if (_xa_api->xa_copy_fn && + _xa_api->xa_copy_fn(h, from, to) < 0) goto done; retval = 0; done: - if (filename) - free(filename); return retval; } - -#if 0 /* Test program */ -/* - * Turn this on to get an xpath test program - * Usage: clicon_xpath [] - * read xml from input - * Example compile: - gcc -g -o xmldb -I. -I../clixon ./clixon_xmldb.c -lclixon -lcligen -*/ - -static int -usage(char *argv0) +int +xmldb_lock(clicon_handle h, char *db, int pid) { - fprintf(stderr, "usage:\n%s\tget []\t\txml on stdin\n", argv0); - fprintf(stderr, "\tput set|merge|delete\txml to stdout\n"); - exit(0); + int retval = -1; + + if (_xa_api == NULL){ + clicon_err(OE_DB, 0, "No xmldb plugin"); + goto done; + } + if (_xa_api->xa_lock_fn && + _xa_api->xa_lock_fn(h, db, pid) < 0) + goto done; + retval = 0; + done: + return retval; +} + +int +xmldb_unlock(clicon_handle h, char *db, int pid) +{ + int retval = -1; + + if (_xa_api == NULL){ + clicon_err(OE_DB, 0, "No xmldb plugin"); + goto done; + } + if (_xa_api->xa_unlock_fn && + _xa_api->xa_unlock_fn(h, db, pid) < 0) + goto done; + retval = 0; + done: + return retval; } int -main(int argc, char **argv) +xmldb_unlock_all(clicon_handle h, int pid) { - cxobj *xt; - cxobj *xn; - char *xpath; - enum operation_type op; - char *cmd; - char *db; - char *yangdir; - char *yangmod; - yang_spec *yspec = NULL; - clicon_handle h; + int retval = -1; - if ((h = clicon_handle_init()) == NULL) - goto done; - clicon_log_init("xmldb", LOG_DEBUG, CLICON_LOG_STDERR); - if (argc < 4){ - usage(argv[0]); + if (_xa_api == NULL){ + clicon_err(OE_DB, 0, "No xmldb plugin"); goto done; } - cmd = argv[1]; - db = argv[2]; - yangdir = argv[3]; - yangmod = argv[4]; - db_init(db); - if ((yspec = yspec_new()) == NULL) - goto done - if (yang_parse(h, yangdir, yangmod, NULL, yspec) < 0) + if (_xa_api->xa_unlock_all_fn && + _xa_api->xa_unlock_all_fn(h, pid) < 0) goto done; - if (strcmp(cmd, "get")==0){ - if (argc < 5) - usage(argv[0]); - xpath = argc>5?argv[5]:NULL; - if (xmldb_get(h, db, xpath, &xt, NULL, NULL) < 0) - goto done; - clicon_xml2file(stdout, xt, 0, 1); - } - else - if (strcmp(cmd, "put")==0){ - if (argc != 6) - usage(argv[0]); - if (clicon_xml_parse_file(0, &xt, "") < 0) - goto done; - if (xml_rootchild(xt, 0, &xn) < 0) - goto done; - if (strcmp(argv[5], "set") == 0) - op = OP_REPLACE; - else - if (strcmp(argv[4], "merge") == 0) - op = OP_MERGE; - else if (strcmp(argv[5], "delete") == 0) - op = OP_REMOVE; - else - usage(argv[0]); - if (xmldb_put(h, db, op, NULL, xn) < 0) - goto done; - } - else - usage(argv[0]); - printf("\n"); + retval = 0; done: - return 0; + return retval; } -#endif /* Test program */ +int +xmldb_islocked(clicon_handle h, char *db) +{ + int retval = -1; + + if (_xa_api == NULL){ + clicon_err(OE_DB, 0, "No xmldb plugin"); + goto done; + } + if (_xa_api->xa_islocked_fn && + _xa_api->xa_islocked_fn(h, db) < 0) + goto done; + retval = 0; + done: + return retval; +} + +int +xmldb_exists(clicon_handle h, char *db) +{ + int retval = -1; + + if (_xa_api == NULL){ + clicon_err(OE_DB, 0, "No xmldb plugin"); + goto done; + } + if (_xa_api->xa_exists_fn && + _xa_api->xa_exists_fn(h, db) < 0) + goto done; + retval = 0; + done: + return retval; +} + +int +xmldb_delete(clicon_handle h, char *db) +{ + int retval = -1; + + if (_xa_api == NULL){ + clicon_err(OE_DB, 0, "No xmldb plugin"); + goto done; + } + if (_xa_api->xa_delete_fn && + _xa_api->xa_delete_fn(h, db) < 0) + goto done; + retval = 0; + done: + return retval; +} + +int +xmldb_init(clicon_handle h, char *db) +{ + int retval = -1; + + if (_xa_api == NULL){ + clicon_err(OE_DB, 0, "No xmldb plugin"); + goto done; + } + if (_xa_api->xa_init_fn && + _xa_api->xa_init_fn(h, db) < 0) + goto done; + retval = 0; + done: + return retval; +} diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 3379beb9..563cba7e 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -66,6 +66,7 @@ #include #include #include +#include /* cligen */ #include @@ -89,9 +90,6 @@ /* Something to do with reverse engineering of junos syntax? */ #undef SPECIAL_TREATMENT_OF_NAME - - - /* * A node is a leaf if it contains a body. */ @@ -791,3 +789,288 @@ xml_diff(yang_spec *yspec, done: return retval; } + +/*! Construct an xml key format from yang statement using wildcards for keys + * Recursively construct it to the top. + * Example: + * yang: container a -> list b -> key c -> leaf d + * xpath: /a/b/%s/d + * @param[in] ys Yang statement + * @param[in] inclkey If inclkey then include key leaf (eg last leaf d in ex) + * @param[out] cbuf keyfmt + */ +static int +yang2xmlkeyfmt_1(yang_stmt *ys, + int inclkey, + cbuf *cb) +{ + yang_node *yp; /* parent */ + yang_stmt *ykey; + int i; + cvec *cvk = NULL; /* vector of index keys */ + int retval = -1; + + yp = ys->ys_parent; + if (yp != NULL && + yp->yn_keyword != Y_MODULE && + yp->yn_keyword != Y_SUBMODULE){ + if (yang2xmlkeyfmt_1((yang_stmt *)yp, 1, cb) < 0) + goto done; + } + if (inclkey){ + if (ys->ys_keyword != Y_CHOICE && ys->ys_keyword != Y_CASE) + cprintf(cb, "/%s", ys->ys_argument); + } + else{ + if (ys->ys_keyword == Y_LEAF && yp && yp->yn_keyword == Y_LIST){ + if (yang_key_match(yp, ys->ys_argument) == 0) + cprintf(cb, "/%s", ys->ys_argument); /* Not if leaf and key */ + } + else + if (ys->ys_keyword != Y_CHOICE && ys->ys_keyword != Y_CASE) + cprintf(cb, "/%s", ys->ys_argument); + } + + switch (ys->ys_keyword){ + case Y_LIST: + if ((ykey = yang_find((yang_node*)ys, Y_KEY, NULL)) == NULL){ + clicon_err(OE_XML, errno, "%s: List statement \"%s\" has no key", + __FUNCTION__, ys->ys_argument); + goto done; + } + /* The value is a list of keys: [ ]* */ + if ((cvk = yang_arg2cvec(ykey, " ")) == NULL) + goto done; + if (cvec_len(cvk)) + cprintf(cb, "="); + /* Iterate over individual keys */ + for (i=0; i list b -> key c -> leaf d + * xpath: /a/b=%s/d + * @param[in] ys Yang statement + * @param[in] inclkey If !inclkey then dont include key leaf + * @param[out] xkfmt XML key format. Needs to be freed after use. + */ +int +yang2xmlkeyfmt(yang_stmt *ys, + int inclkey, + char **xkfmt) +{ + int retval = -1; + cbuf *cb = NULL; + + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + if (yang2xmlkeyfmt_1(ys, inclkey, cb) < 0) + goto done; + if ((*xkfmt = strdup(cbuf_get(cb))) == NULL){ + clicon_err(OE_UNIX, errno, "strdup"); + goto done; + } + retval = 0; + done: + if (cb) + cbuf_free(cb); + return retval; +} + + +/*! 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 + * cvv: key=17 + * xmlkey: /aaa/17 + * @param[in] xkfmt XML key format, eg /aaa/%s + * @param[in] cvv cligen variable vector, one for every wildchar in xkfmt + * @param[out] xk XML key, eg /aaa/17. Free after use + * @note first and last elements of cvv are not used,.. + * @see cli_dbxml where this function is called + */ +int +xmlkeyfmt2key(char *xkfmt, + cvec *cvv, + char **xk) +{ + int retval = -1; + char c; + int esc=0; + cbuf *cb = NULL; + int i; + int j; + char *str; + char *strenc=NULL; + + + /* Sanity check */ +#if 1 + j = 0; /* Count % */ + for (i=0; i