diff --git a/CHANGELOG b/CHANGELOG.md
similarity index 72%
rename from CHANGELOG
rename to CHANGELOG.md
index 3731f1f6..0083e82a 100644
--- a/CHANGELOG
+++ b/CHANGELOG.md
@@ -1,34 +1,28 @@
-# ***** 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 *****
+# Clixon CHANGELOG
+## 3.3.0
+
+May 2017
+
+- Datastore text module is now default.
+
+- Refined netconf "none" semantics in tests and text datastore
+
+- Moved apps/dbctrl to datastore/
+
+- Added connect/disconnect/getopt/setopt and handle to xmldb API
+
+- Added datastore 'text'
+
+- Configure (autoconf) changes
+ Removed libcurl dependency
+ Disable restconf (and fastcgi) with configure --disable-restconf
+ Disable keyvalue datastore (and qdbm) with configure --disable-keyvalue
+
+- Created xmldb plugin api
+ Moved qdbm, chunk and xmldb to datastore keyvalue directories
+ Removed all other clixon dependency on chunk code
+
- 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/README.doxygen b/README.doxygen
deleted file mode 100644
index 9ad563b4..00000000
--- a/README.doxygen
+++ /dev/null
@@ -1,31 +0,0 @@
-/*! This is a small comment on one line
- *
- * This is a detailed description
- * spanning several lines.
- *
- * Example usage:
- * @code
- * fn(a, &b);
- * @endcode
- *
- * @param[in] src This is a description of the first parameter
- * @param[in,out] dest This is a description of the second parameter
- * @retval TRUE This is a description of the return value
- * @retval FALSE This is a description of another return value
- * @see anotherfn()
- * @note This is just an example
- */
-
-/*! This is a documentation of a typedef or struct
- *
- * This is a detailed description
- * spanning several lines.
- *
- * Example usage:
- * @code
- * struct foo f;
- * @endcode
- */
-typedef foo{
- int a; /**< This is the a field*/
-};
diff --git a/README.md b/README.md
index 28c148b6..ec7ad802 100644
--- a/README.md
+++ b/README.md
@@ -1,38 +1,96 @@
-CLIXON
-======
+# Clixon
-CLIXON is an automatic configuration manager where you from a YANG
-specification generate interactive CLI, NETCONF, RESTCONF and embedded
-databases with transaction support.
+Clixon is an automatic configuration manager where you generate
+interactive CLI, NETCONF, RESTCONF and embedded databases with
+transaction support from a YANG specification.
-CLIXON is a fork of CLICON where legacy key specification has been
-replaced completely by YANG. This means that legacy CLICON
-applications such as CLICON/ROST does not run on CLIXON.
+Table of contents
+=================
+ * [Documentation](#documentation)
+ * [Installation](#installation)
+ * [Dependencies](#dependencies)
+ * [Licenses](#licenses)
+ * [Background](#background)
+ * [Clixon SDK](#SDK)
-Presentations and tutorial is found on the [CLICON project
-page](http://www.clicon.org)
+Documentation
+=============
+- [Frequently asked questions](doc/FAQ.md)
+- [XML datastore](datastore/README.md)
+- [Netconf support](apps/netconf/README.md)
+- [Restconf support](apps/restconf/README.md)
+- [Reference manual](http://www.clicon.org/doxygen/index.html) (Note the link may not be up-to-date. It is better to build your own: cd doc; make doc)
+- [Routing example](example/README.md)
+- [Clicon and Clixon project page](http://www.clicon.org)
+- [Tests](test/README.md)
+Installation
+============
A typical installation is as follows:
+```
+ configure # Configure clixon to platform
+ make # Compile
+ sudo make install # Install libs, binaries, and config-files
+ sudo make install-include # Install include files (for compiling)
+```
- > configure # Configure clixon to platform
- > make # Compile
- > sudo make install # Install libs, binaries, and config-files
- > sudo make install-include # Install include files (for compiling)
+One [example application](example/README.md) is provided, a IETF IP YANG datamodel with
+generated CLI and configuration interface.
-One example applications is provided, the IETF IP YANG datamodel with generated CLI and configuration interface. It all origins from work at
-[KTH](http://www.csc.kth.se/~olofh/10G_OSR)
-
-[CLIgen](http://www.cligen.se) is required for building CLIXON. If you need
+Dependencies
+============
+Clixon is dependend on the following software packages, which need to exist on the target machine.
+- [CLIgen](http://www.cligen.se) is required for building Clixon. If you need
to build and install CLIgen:
-
+```
git clone https://github.com/olofhagsand/cligen.git
cd cligen; configure; make; make install
+```
+- Yacc/bison
+- Lex/Flex
+- Fcgi (if restconf is enabled)
+- Qdbm key-value store (if keyvalue datastore is enabled)
-CLIXON is dual license. Either Apache License, Version 2.0 or GNU
+There is no yum/apt/ostree package for Clixon (please help?)
+
+Licenses
+========
+Clixon is dual license. Either Apache License, Version 2.0 or GNU
General Public License Version 2. You choose.
-See LICENSE.md for license, CHANGELOG for recent changes.
-
+See [LICENSE.md](LICENSE.md) for license, [CHANGELOG](CHANGELOG.md) for recent changes.
+
+Background
+==========
+We implemented Clixon since we needed a generic configuration tool in
+several projects, including
+[KTH](http://www.csc.kth.se/~olofh/10G_OSR). Most of these projects
+were for embedded network and measuring-probe devices. We started with
+something called Clicon which was based on a key-value specification
+and data-store. But as time passed new standards evaolved and we
+started adapting it to XML, Yang and netconf. Finally we made Clixon
+where the legacy key specification has been replaced completely by
+YANG and using XML as configuration data. This means that legacy
+Clicon applications do not run on Clixon.
+
+SDK
+===
+
+
+
+The figure shows the SDK runtime of Clixon.
+
+YANG and XML is at the heart of Clixon. Yang modules are used as a
+specification for handling XML configuration data. The spec is also
+used to generate an interactive CLI client as well as provide
+[Netconf](apps/netconf/README.md) and
+[Restconf](apps/restconf/README.md) clients.
+
+The [YANG RFC 6020](https://www.rfc-editor.org/rfc/rfc6020.txt) is implemented with the following exceptions:
+- object-references
+- if-feature
+- unique
+- rpc
diff --git a/apps/Makefile.in b/apps/Makefile.in
index a685dcaa..a67233bc 100644
--- a/apps/Makefile.in
+++ b/apps/Makefile.in
@@ -37,10 +37,16 @@ CC = @CC@
CFLAGS = @CFLAGS@
LDFLAGS = @LDFLAGS@
LIBS = @LIBS@
+with_restconf = @with_restconf@
SHELL = /bin/sh
-SUBDIRS = cli backend dbctrl netconf restconf
+SUBDIRS = backend
+SUBDIRS += cli
+SUBDIRS += netconf
+ifeq ($(with_restconf),yes)
+SUBDIRS += restconf
+endif
.PHONY: all clean depend install $(SUBDIRS)
diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c
index f347e6f9..022e61d6 100644
--- a/apps/backend/backend_client.c
+++ b/apps/backend/backend_client.c
@@ -307,7 +307,6 @@ from_client_edit_config(clicon_handle h,
}
}
if ((xc = xpath_first(xn, "config")) != NULL){
- /* XXX see from_client_xmlput() */
if (xmldb_put(h, target, operation, api_path, xc) < 0){
cprintf(cbret, ""
"operation-failed"
@@ -438,7 +437,7 @@ from_client_unlock(clicon_handle h,
goto ok;
}
else{
- xmldb_unlock(h, db, pid);
+ xmldb_unlock(h, db);
if (cprintf(cbret, "") < 0)
goto done;
}
@@ -496,7 +495,7 @@ from_client_kill_session(clicon_handle h,
if (1 || (kill (pid, 0) != 0 && errno == ESRCH)){ /* Nothing there */
/* clear from locks */
if (xmldb_islocked(h, db) == pid)
- xmldb_unlock(h, db, pid);
+ xmldb_unlock(h, db);
}
else{ /* failed to kill client */
cprintf(cbret, ""
@@ -622,7 +621,6 @@ from_client_delete_config(clicon_handle h,
piddb);
goto ok;
}
-
if (xmldb_delete(h, target) < 0){
cprintf(cbret, ""
"operation-failed"
@@ -633,7 +631,7 @@ from_client_delete_config(clicon_handle h,
"", clicon_err_reason);
goto ok;
}
- if (xmldb_init(h, target) < 0){
+ if (xmldb_create(h, target) < 0){
cprintf(cbret, ""
"operation-failed"
"protocol"
@@ -872,9 +870,21 @@ from_client_msg(clicon_handle h,
assert(cbuf_len(cbret));
clicon_debug(1, "%s %s", __FUNCTION__, cbuf_get(cbret));
if (send_msg_reply(ce->ce_s, cbuf_get(cbret), cbuf_len(cbret)+1) < 0){
- if (errno == ECONNRESET)
+ switch (errno){
+ case EPIPE:
+ /* man (2) write:
+ * EPIPE fd is connected to a pipe or socket whose reading end is
+ * closed. When this happens the writing process will also receive
+ * a SIGPIPE signal.
+ * In Clixon this means a client, eg restconf, netconf or cli closes
+ * the (UNIX domain) socket.
+ */
+ case ECONNRESET:
clicon_log(LOG_WARNING, "client rpc reset");
- goto done;
+ break;
+ default:
+ goto done;
+ }
}
// ok:
retval = 0;
diff --git a/apps/backend/backend_handle.h b/apps/backend/backend_handle.h
index 80251f08..4a81af16 100644
--- a/apps/backend/backend_handle.h
+++ b/apps/backend/backend_handle.h
@@ -41,7 +41,7 @@
* Prototypes
* not exported.
*/
-/* backend handles */
+/* backend handles. Defined in clixon_backend_handle.c */
clicon_handle backend_handle_init(void);
int backend_handle_exit(clicon_handle h);
diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c
index 6ee74fc9..4600eedd 100644
--- a/apps/backend/backend_main.c
+++ b/apps/backend/backend_main.c
@@ -73,11 +73,11 @@
#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:b:Fzu:P:1IRCc:rg:py:x:"
/*! Terminate. Cannot use h after this */
static int
-config_terminate(clicon_handle h)
+backend_terminate(clicon_handle h)
{
yang_spec *yspec;
char *pidfile = clicon_backend_pidfile(h);
@@ -91,19 +91,18 @@ config_terminate(clicon_handle h)
unlink(pidfile);
if (sockpath)
unlink(sockpath);
+ xmldb_plugin_unload(h); /* unload storage plugin */
backend_handle_exit(h); /* Cannot use h after this */
event_exit();
clicon_log_register_callback(NULL, NULL);
clicon_debug(1, "%s done", __FUNCTION__);
- if (debug)
- chunk_check(stderr, NULL);
return 0;
}
/*! Unlink pidfile and quit
*/
static void
-config_sig_term(int arg)
+backend_sig_term(int arg)
{
static int i=0;
@@ -130,6 +129,7 @@ usage(char *argv0, clicon_handle h)
" -D \tdebug\n"
" -f \tCLICON config file (mandatory)\n"
" -d \tSpecify backend plugin directory (default: %s)\n"
+ " -b \tSpecify XMLDB database directory\n"
" -z\t\tKill other config daemon and exit\n"
" -F\t\tforeground\n"
" -1\t\tonce (dont wait for events)\n"
@@ -141,9 +141,9 @@ usage(char *argv0, clicon_handle h)
" -c \tLoad specified application config.\n"
" -r\t\tReload running database\n"
" -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",
+ " -y \tOverride yang spec file (dont include .yang suffix)\n"
+ " -x \tXMLDB plugin\n",
argv0,
plgdir ? plgdir : "none",
confsock ? confsock : "none",
@@ -159,7 +159,7 @@ db_reset(clicon_handle h,
{
if (xmldb_delete(h, db) != 0 && errno != ENOENT)
return -1;
- if (xmldb_init(h, db) < 0)
+ if (xmldb_create(h, db) < 0)
return -1;
return 0;
}
@@ -181,7 +181,7 @@ rundb_main(clicon_handle h,
cxobj *xt = NULL;
cxobj *xn;
- if (xmldb_init(h, "tmp") < 0)
+ if (xmldb_create(h, "tmp") < 0)
goto done;
if (xmldb_copy(h, "running", "tmp") < 0){
clicon_err(OE_UNIX, errno, "file copy");
@@ -206,7 +206,6 @@ done:
xml_free(xt);
if (fd != -1)
close(fd);
- unchunk_group(__FUNCTION__);
return retval;
}
@@ -240,11 +239,11 @@ server_socket(clicon_handle h)
int ss;
/* Open control socket */
- if ((ss = config_socket_init(h)) < 0)
+ if ((ss = backend_socket_init(h)) < 0)
return -1;
/* ss is a server socket that the clients connect to. The callback
therefore accepts clients on ss */
- if (event_reg_fd(ss, config_accept_client, h, "server socket") < 0) {
+ if (event_reg_fd(ss, backend_accept_client, h, "server socket") < 0) {
close(ss);
return -1;
}
@@ -256,19 +255,20 @@ server_socket(clicon_handle h)
* log event.
*/
static int
-config_log_cb(int level, char *msg, void *arg)
+backend_log_cb(int level,
+ char *msg,
+ void *arg)
{
+ int retval = -1;
size_t n;
- char *ptr;
- char *nptr;
- char *newmsg = NULL;
- int retval = -1;
+ char *ptr;
+ char *nptr;
+ char *newmsg = NULL;
- /* backend_notify() will go through all clients and see if any has registered "CLICON",
- and if so make a clicon_proto notify message to those clients. */
-
-
- /* Sanitize '%' into "%%" to prevent segvfaults in vsnprintf later.
+ /* backend_notify() will go through all clients and see if any has
+ registered "CLICON", and if so make a clicon_proto notify message to
+ those clients.
+ Sanitize '%' into "%%" to prevent segvfaults in vsnprintf later.
At this stage all formatting is already done */
n = 0;
for(ptr=msg; *ptr; ptr++)
@@ -283,7 +283,6 @@ config_log_cb(int level, char *msg, void *arg)
if (*ptr == '%')
*nptr++ = '%';
}
-
retval = backend_notify(arg, "CLICON", level, newmsg);
free(newmsg);
@@ -309,11 +308,11 @@ main(int argc, char **argv)
clicon_handle h;
int help = 0;
int printspec = 0;
- int printalt = 0;
int pid;
char *pidfile;
char *sock;
int sockfamily;
+ char *xmldb_plugin;
/* In the startup, logs to stderr & syslog and debug flag set later */
@@ -321,7 +320,7 @@ main(int argc, char **argv)
/* Initiate CLICON handle */
if ((h = backend_handle_init()) == NULL)
return -1;
- if (config_plugin_init(h) != 0)
+ if (backend_plugin_init(h) != 0)
return -1;
foreground = 0;
once = 0;
@@ -387,6 +386,11 @@ main(int argc, char **argv)
usage(argv[0], h);
clicon_option_str_set(h, "CLICON_BACKEND_DIR", optarg);
break;
+ case 'b': /* XMLDB database directory */
+ if (!strlen(optarg))
+ usage(argv[0], h);
+ clicon_option_str_set(h, "CLICON_XMLDB_DIR", optarg);
+ break;
case 'F' : /* foreground */
foreground = 1;
break;
@@ -425,9 +429,6 @@ main(int argc, char **argv)
case 'p' : /* Print spec */
printspec++;
break;
- case 't' : /* Print alternative dbspec format (eg if YANG, print KEY) */
- printalt++;
- break;
case 'y' :{ /* yang module */
/* Set revision to NULL, extract dir and module */
char *str = strdup(optarg);
@@ -437,6 +438,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,10 +507,25 @@ 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(h, xmldb_plugin) < 0)
+ goto done;
+ /* Connect to plugin to get a handle */
+ if (xmldb_connect(h) < 0)
+ goto done;
/* Parse db spec file */
if (yang_spec_main(h, stdout, printspec) < 0)
goto done;
+ /* Set options: database dir aqnd yangspec (could be hidden in connect?)*/
+ if (xmldb_setopt(h, "dbdir", clicon_xmldb_dir(h)) < 0)
+ goto done;
+ if (xmldb_setopt(h, "yangspec", clicon_dbspec_yang(h)) < 0)
+ goto done;
+
/* First check for startup config
XXX the options below have become out-of-hand.
Too complex, need to simplify*/
@@ -518,7 +538,7 @@ main(int argc, char **argv)
else
if (db_reset(h, "running") < 0)
goto done;
- if (xmldb_init(h, "candidate") < 0)
+ if (xmldb_create(h, "candidate") < 0)
goto done;
if (xmldb_copy(h, "running", "candidate") < 0)
goto done;
@@ -542,7 +562,7 @@ main(int argc, char **argv)
}
/* If candidate does not exist, create it from running */
if (xmldb_exists(h, "candidate") != 1){
- if (xmldb_init(h, "candidate") < 0)
+ if (xmldb_create(h, "candidate") < 0)
goto done;
if (xmldb_copy(h, "running", "candidate") < 0)
goto done;
@@ -605,14 +625,14 @@ main(int argc, char **argv)
goto done;
/* Register log notifications */
- if (clicon_log_register_callback(config_log_cb, h) < 0)
+ if (clicon_log_register_callback(backend_log_cb, h) < 0)
goto done;
clicon_log(LOG_NOTICE, "%s: %u Started", __PROGRAM__, getpid());
- if (set_signal(SIGTERM, config_sig_term, NULL) < 0){
+ if (set_signal(SIGTERM, backend_sig_term, NULL) < 0){
clicon_err(OE_DEMON, errno, "Setting signal");
goto done;
}
- if (set_signal(SIGINT, config_sig_term, NULL) < 0){
+ if (set_signal(SIGINT, backend_sig_term, NULL) < 0){
clicon_err(OE_DEMON, errno, "Setting signal");
goto done;
}
@@ -628,7 +648,7 @@ main(int argc, char **argv)
goto done;
done:
clicon_log(LOG_NOTICE, "%s: %u Terminated", __PROGRAM__, getpid());
- config_terminate(h); /* Cannot use h after this */
+ backend_terminate(h); /* Cannot use h after this */
return 0;
}
diff --git a/apps/backend/backend_plugin.c b/apps/backend/backend_plugin.c
index 3477d5cb..f336701c 100644
--- a/apps/backend/backend_plugin.c
+++ b/apps/backend/backend_plugin.c
@@ -132,7 +132,7 @@ config_find_plugin(clicon_handle h,
* @retval -1 Error
*/
int
-config_plugin_init(clicon_handle h)
+backend_plugin_init(clicon_handle h)
{
find_plugin_t *fp = config_find_plugin;
clicon_hash_t *data = clicon_data(h);
@@ -152,8 +152,8 @@ config_plugin_init(clicon_handle h)
* @retval -1 Error
*/
static int
-plugin_unload(clicon_handle h,
- struct plugin *plg)
+backend_plugin_unload(clicon_handle h,
+ struct plugin *plg)
{
char *error;
@@ -178,44 +178,21 @@ plugin_unload(clicon_handle h,
* @param[in] h Clicon handle
* @param[in] file The plugin (.so) to load
* @param[in] dlflags Arguments to dlopen(3)
- * @param[in] label Chunk label
* @retval plugin Plugin struct
* @retval NULL Error
*/
static struct plugin *
-plugin_load (clicon_handle h,
- char *file,
- int dlflags,
- const char *label)
+backend_plugin_load (clicon_handle h,
+ char *file,
+ int dlflags)
{
- char *error;
void *handle;
char *name;
- struct plugin *new;
- plginit_t *initfun;
+ struct plugin *new = NULL;
- dlerror(); /* Clear any existing error */
- if ((handle = dlopen (file, dlflags)) == NULL) {
- error = (char*)dlerror();
- clicon_err(OE_UNIX, 0, "dlopen: %s", error?error:"Unknown error");
- return NULL;
- }
-
- initfun = dlsym(handle, PLUGIN_INIT);
- if ((error = (char*)dlerror()) != NULL) {
- clicon_err(OE_UNIX, 0, "dlsym: %s: %s", file, error);
- return NULL;
- }
-
- if (initfun(h) != 0) {
- dlclose(handle);
- if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */
- clicon_err(OE_DB, 0, "Unknown error: %s: plugin_init does not make clicon_err call on error",
- file);
- return NULL;
- }
-
- if ((new = chunk(sizeof(*new), label)) == NULL) {
+ if ((handle = plugin_load(h, file, dlflags)) == NULL)
+ goto done;
+ if ((new = malloc(sizeof(*new))) == NULL) {
clicon_err(OE_UNIX, errno, "dhunk: %s", strerror(errno));
dlclose(handle);
return NULL;
@@ -226,7 +203,6 @@ plugin_load (clicon_handle h,
snprintf(new->p_name, sizeof(new->p_name), "%*s",
(int)strlen(name)-2, name);
new->p_handle = handle;
- new->p_init = initfun;
if ((new->p_start = dlsym(handle, PLUGIN_START)) != NULL)
clicon_debug(2, "%s callback registered.", PLUGIN_START);
if ((new->p_exit = dlsym(handle, PLUGIN_EXIT)) != NULL)
@@ -246,7 +222,7 @@ plugin_load (clicon_handle h,
if ((new->p_trans_abort = dlsym(handle, PLUGIN_TRANS_ABORT)) != NULL)
clicon_debug(2, "%s callback registered.", PLUGIN_TRANS_ABORT);
clicon_debug(2, "Plugin '%s' loaded.\n", name);
-
+ done:
return new;
}
@@ -319,8 +295,8 @@ plugin_append(struct plugin *p)
{
struct plugin *new;
- if ((new = rechunk(plugins, (nplugins+1) * sizeof (*p), NULL)) == NULL) {
- clicon_err(OE_UNIX, errno, "chunk");
+ if ((new = realloc(plugins, (nplugins+1) * sizeof (*p))) == NULL) {
+ clicon_err(OE_UNIX, errno, "realloc");
return -1;
}
@@ -340,7 +316,7 @@ plugin_append(struct plugin *p)
* @retval -1 Error
*/
static int
-config_plugin_load_dir(clicon_handle h,
+backend_plugin_load_dir(clicon_handle h,
const char *dir)
{
int retval = -1;
@@ -348,11 +324,11 @@ config_plugin_load_dir(clicon_handle h,
int np = 0;
int ndp;
struct stat st;
- char *filename;
- struct dirent *dp;
+ char filename[MAXPATHLEN];
+ struct dirent *dp = NULL;
struct plugin *new;
struct plugin *p = NULL;
- char *master;
+ char master[MAXPATHLEN];
char *master_plugin;
/* Format master plugin path */
@@ -360,50 +336,39 @@ config_plugin_load_dir(clicon_handle h,
clicon_err(OE_PLUGIN, 0, "clicon_master_plugin option not set");
goto quit;
}
- master = chunk_sprintf(__FUNCTION__, "%s.so", master_plugin);
- if (master == NULL) {
- clicon_err(OE_PLUGIN, errno, "chunk_sprintf master plugin");
- goto quit;
- }
+ snprintf(master, MAXPATHLEN-1, "%s.so", master_plugin);
/* Allocate plugin group object */
/* Get plugin objects names from plugin directory */
- if((ndp = clicon_file_dirent(dir, &dp, "(.so)$", S_IFREG, __FUNCTION__))<0)
+ if((ndp = clicon_file_dirent(dir, &dp, "(.so)$", S_IFREG))<0)
goto quit;
/* reset num plugins */
np = 0;
/* Master plugin must be loaded first if it exists. */
- filename = chunk_sprintf(__FUNCTION__, "%s/%s", dir, master);
- if (filename == NULL) {
- clicon_err(OE_UNIX, errno, "chunk");
- goto quit;
- }
+ snprintf(filename, MAXPATHLEN-1, "%s/%s", dir, master);
if (stat(filename, &st) == 0) {
clicon_debug(1, "Loading master plugin '%.*s' ...",
(int)strlen(filename), filename);
- new = plugin_load(h, filename, RTLD_NOW|RTLD_GLOBAL, __FUNCTION__);
+ new = backend_plugin_load(h, filename, RTLD_NOW|RTLD_GLOBAL);
if (new == NULL)
goto quit;
if (plugin_append(new) < 0)
goto quit;
}
- /* Now load the rest */
+ /* Now load the rest. Note plugins is the global variable */
for (i = 0; i < ndp; i++) {
if (strcmp(dp[i].d_name, master) == 0)
continue; /* Skip master now */
- filename = chunk_sprintf(__FUNCTION__, "%s/%s", dir, dp[i].d_name);
+ snprintf(filename, MAXPATHLEN-1, "%s/%s", dir, dp[i].d_name);
clicon_debug(1, "Loading plugin '%.*s' ...", (int)strlen(filename), filename);
- if (filename == NULL) {
- clicon_err(OE_UNIX, errno, "chunk");
- goto quit;
- }
- new = plugin_load (h, filename, RTLD_NOW, __FUNCTION__);
+ new = backend_plugin_load(h, filename, RTLD_NOW);
if (new == NULL)
goto quit;
+ /* Append to 'plugins' */
if (plugin_append(new) < 0)
goto quit;
}
@@ -413,13 +378,19 @@ config_plugin_load_dir(clicon_handle h,
quit:
if (retval != 0) {
- if (p) {
- while (--np >= 0)
- plugin_unload (h, &p[np]);
- unchunk(p);
+ /* XXX p is always NULL */
+ if (plugins) {
+ while (--np >= 0){
+ if ((p = &plugins[np]) == NULL)
+ continue;
+ backend_plugin_unload(h, p);
+ free(p);
+ }
+ free(plugins);
}
}
- unchunk_group(__FUNCTION__);
+ if (dp)
+ free(dp);
return retval;
}
@@ -435,7 +406,7 @@ plugin_initiate(clicon_handle h)
char *dir;
/* First load CLICON system plugins */
- if (config_plugin_load_dir(h, CLIXON_BACKEND_SYSDIR) < 0)
+ if (backend_plugin_load_dir(h, CLIXON_BACKEND_SYSDIR) < 0)
return -1;
/* Then load application plugins */
@@ -443,7 +414,7 @@ plugin_initiate(clicon_handle h)
clicon_err(OE_PLUGIN, 0, "backend_dir not defined");
return -1;
}
- if (config_plugin_load_dir(h, dir) < 0)
+ if (backend_plugin_load_dir(h, dir) < 0)
return -1;
return 0;
@@ -462,10 +433,12 @@ plugin_finish(clicon_handle h)
for (i = 0; i < nplugins; i++) {
p = &plugins[i];
- plugin_unload(h, p);
+ backend_plugin_unload(h, p);
+ }
+ if (plugins){
+ free(plugins);
+ plugins = NULL;
}
- if (plugins)
- unchunk(plugins);
nplugins = 0;
return 0;
}
diff --git a/apps/backend/backend_plugin.h b/apps/backend/backend_plugin.h
index ca6e474e..459ff93b 100644
--- a/apps/backend/backend_plugin.h
+++ b/apps/backend/backend_plugin.h
@@ -62,7 +62,7 @@ typedef struct {
/*
* Prototypes
*/
-int config_plugin_init(clicon_handle h);
+int backend_plugin_init(clicon_handle h);
int plugin_initiate(clicon_handle h);
int plugin_finish(clicon_handle h);
diff --git a/apps/backend/backend_socket.c b/apps/backend/backend_socket.c
index b83f7ce3..2324de82 100644
--- a/apps/backend/backend_socket.c
+++ b/apps/backend/backend_socket.c
@@ -174,7 +174,7 @@ config_socket_init_unix(clicon_handle h, char *sock)
}
int
-config_socket_init(clicon_handle h)
+backend_socket_init(clicon_handle h)
{
char *sock;
@@ -197,7 +197,7 @@ config_socket_init(clicon_handle h)
* XXX: credentials not properly implemented
*/
int
-config_accept_client(int fd,
+backend_accept_client(int fd,
void *arg)
{
int retval = -1;
diff --git a/apps/backend/backend_socket.h b/apps/backend/backend_socket.h
index 735816b9..b88efb89 100644
--- a/apps/backend/backend_socket.h
+++ b/apps/backend/backend_socket.h
@@ -40,7 +40,7 @@
/*
* Prototypes
*/
-int config_socket_init(clicon_handle h);
-int config_accept_client(int fd, void *arg);
+int backend_socket_init(clicon_handle h);
+int backend_accept_client(int fd, void *arg);
#endif /* _BACKEND_SOCKET_H_ */
diff --git a/apps/backend/clixon_backend_handle.c b/apps/backend/clixon_backend_handle.c
index 720337e8..14808d1d 100644
--- a/apps/backend/clixon_backend_handle.c
+++ b/apps/backend/clixon_backend_handle.c
@@ -75,14 +75,20 @@
* This file should only contain access functions for the _specific_
* entries in the struct below.
*/
+/*! Backend specific handle added to header CLICON handle
+ * This file should only contain access functions for the _specific_
+ * entries in the struct below.
+ * @note The top part must be equivalent to struct clicon_handle in clixon_handle.c
+ * @see struct clicon_handle, struct cli_handle
+ */
struct backend_handle {
- int cb_magic; /* magic (HDR)*/
- clicon_hash_t *cb_copt; /* clicon option list (HDR) */
- clicon_hash_t *cb_data; /* internal clicon data (HDR) */
+ int bh_magic; /* magic (HDR)*/
+ clicon_hash_t *bh_copt; /* clicon option list (HDR) */
+ clicon_hash_t *bh_data; /* internal clicon data (HDR) */
/* ------ end of common handle ------ */
- struct client_entry *cb_ce_list; /* The client list */
- int cb_ce_nr; /* Number of clients, just increment */
- struct handle_subscription *cb_subscription; /* Event subscription list */
+ struct client_entry *bh_ce_list; /* The client list */
+ int bh_ce_nr; /* Number of clients, just increment */
+ struct handle_subscription *bh_subscription; /* Event subscription list */
};
/*! Creates and returns a clicon config handle for other CLICON API calls
@@ -141,7 +147,7 @@ backend_notify(clicon_handle h,
if (strcmp(su->su_stream, stream) == 0){
if (strlen(su->su_filter)==0 || fnmatch(su->su_filter, event, 0) == 0){
if (send_msg_notify(ce->ce_s, level, event) < 0){
- if (errno == ECONNRESET){
+ if (errno == ECONNRESET || errno == EPIPE){
clicon_log(LOG_WARNING, "client %d reset", ce->ce_nr);
#if 0
/* We should remove here but removal is not possible
@@ -219,7 +225,7 @@ backend_notify_xml(clicon_handle h,
goto done;
}
if (send_msg_notify(ce->ce_s, level, cbuf_get(cb)) < 0){
- if (errno == ECONNRESET){
+ if (errno == ECONNRESET || errno == EPIPE){
clicon_log(LOG_WARNING, "client %d reset", ce->ce_nr);
#if 0
/* We should remove here but removal is not possible
@@ -257,11 +263,17 @@ backend_notify_xml(clicon_handle h,
}
+/*! Add new client, typically frontend such as cli, netconf, restconf
+ * @param[in] h Clicon handle
+ * @param[in] addr Address of client
+ * @retval ce Client entry
+ * @retval NULL Error
+ */
struct client_entry *
backend_client_add(clicon_handle h,
struct sockaddr *addr)
{
- struct backend_handle *cb = handle(h);
+ struct backend_handle *bh = handle(h);
struct client_entry *ce;
if ((ce = (struct client_entry *)malloc(sizeof(*ce))) == NULL){
@@ -269,24 +281,28 @@ backend_client_add(clicon_handle h,
return NULL;
}
memset(ce, 0, sizeof(*ce));
- ce->ce_nr = cb->cb_ce_nr++;
+ ce->ce_nr = bh->bh_ce_nr++;
memcpy(&ce->ce_addr, addr, sizeof(*addr));
- ce->ce_next = cb->cb_ce_list;
- cb->cb_ce_list = ce;
+ ce->ce_next = bh->bh_ce_list;
+ bh->bh_ce_list = ce;
return ce;
}
+/*! Return client list
+ * @param[in] h Clicon handle
+ * @retval ce_list Client entry list (all sessions)
+ */
struct client_entry *
backend_client_list(clicon_handle h)
{
- struct backend_handle *cb = handle(h);
+ struct backend_handle *bh = handle(h);
- return cb->cb_ce_list;
+ return bh->bh_ce_list;
}
/*! Actually remove client from client list
* @param[in] h Clicon handle
- * @param[in] ce Client hadnle
+ * @param[in] ce Client handle
* @see backend_client_rm which is more high-level
*/
int
@@ -295,9 +311,9 @@ backend_client_delete(clicon_handle h,
{
struct client_entry *c;
struct client_entry **ce_prev;
- struct backend_handle *cb = handle(h);
+ struct backend_handle *bh = handle(h);
- ce_prev = &cb->cb_ce_list;
+ ce_prev = &bh->bh_ce_list;
for (c = *ce_prev; c; c = c->ce_next){
if (c == ce){
*ce_prev = c->ce_next;
@@ -328,7 +344,7 @@ subscription_add(clicon_handle h,
subscription_fn_t fn,
void *arg)
{
- struct backend_handle *cb = handle(h);
+ struct backend_handle *bh = handle(h);
struct handle_subscription *hs = NULL;
if ((hs = malloc(sizeof(*hs))) == NULL){
@@ -339,10 +355,10 @@ subscription_add(clicon_handle h,
hs->hs_stream = strdup(stream);
hs->hs_format = format;
hs->hs_filter = filter?strdup(filter):NULL;
- hs->hs_next = cb->cb_subscription;
+ hs->hs_next = bh->bh_subscription;
hs->hs_fn = fn;
hs->hs_arg = arg;
- cb->cb_subscription = hs;
+ bh->bh_subscription = hs;
done:
return hs;
}
@@ -362,11 +378,11 @@ subscription_delete(clicon_handle h,
subscription_fn_t fn,
void *arg)
{
- struct backend_handle *cb = handle(h);
+ struct backend_handle *bh = handle(h);
struct handle_subscription *hs;
struct handle_subscription **hs_prev;
- hs_prev = &cb->cb_subscription; /* this points to stack and is not real backpointer */
+ hs_prev = &bh->bh_subscription; /* this points to stack and is not real backpointer */
for (hs = *hs_prev; hs; hs = hs->hs_next){
/* XXX arg == hs->hs_arg */
if (strcmp(hs->hs_stream, stream)==0 && hs->hs_fn == fn){
@@ -404,15 +420,16 @@ struct handle_subscription *
subscription_each(clicon_handle h,
struct handle_subscription *hprev)
{
- struct backend_handle *cb = handle(h);
+ struct backend_handle *bh = handle(h);
struct handle_subscription *hs = NULL;
if (hprev)
hs = hprev->hs_next;
else
- hs = cb->cb_subscription;
+ hs = bh->bh_subscription;
return hs;
}
+
/* Database dependency description */
struct backend_netconf_reg {
qelem_t nr_qelem; /* List header */
@@ -456,10 +473,9 @@ catch:
/*! See if there is any callback registered for this tag
*
* @param[in] h clicon handle
- * @param[in] xn Sub-tree (under xorig) at child of rpc: .
- * @param[out] cb Output xml stream. For reply
- * @param[out] cb_err Error xml stream. For error reply
- * @param[out] xret Return XML, error or OK
+ * @param[in] xe Sub-tree (under xorig) at child of rpc: .
+ * @param[in] ce Client (session) entry
+ * @param[out] cbret Return XML, error or OK as cbuf
*
* @retval -1 Error
* @retval 0 OK, not found handler.
diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c
index 92882d5b..847f1d7f 100644
--- a/apps/cli/cli_common.c
+++ b/apps/cli/cli_common.c
@@ -94,17 +94,19 @@ cli_notification_register(clicon_handle h,
void *arg)
{
int retval = -1;
- char *logname;
+ char *logname = NULL;
void *p;
int s;
clicon_hash_t *cdat = clicon_data(h);
size_t len;
int s_exist = -1;
- if ((logname = chunk_sprintf(__FUNCTION__, "log_socket_%s", stream)) == NULL){
- clicon_err(OE_PLUGIN, errno, "%s: chunk_sprintf", __FUNCTION__);
+ len = strlen("log_socket_") + strlen(stream) + 1;
+ if ((logname = malloc(len)) == NULL){
+ clicon_err(OE_UNIX, errno, "malloc");
goto done;
- }
+ }
+ snprintf(logname, len, "log_socket_%s", stream);
if ((p = hash_value(cdat, logname, &len)) != NULL)
s_exist = *(int*)p;
@@ -132,7 +134,8 @@ cli_notification_register(clicon_handle h,
}
retval = 0;
done:
- unchunk_group(__FUNCTION__);
+ if (logname)
+ free(logname);
return retval;
}
diff --git a/apps/cli/cli_handle.c b/apps/cli/cli_handle.c
index 7090c304..7d2e422e 100644
--- a/apps/cli/cli_handle.c
+++ b/apps/cli/cli_handle.c
@@ -69,12 +69,11 @@
#define handle(h) (assert(clicon_handle_check(h)==0),(struct cli_handle *)(h))
#define cligen(h) (handle(h)->cl_cligen)
-/*
- * cli_handle
- * first part of this is header, same for clicon_handle and config_handle.
- * Access functions for common fields are found in clicon lib: clicon_options.[ch]
+/*! CLI specific handle added to header CLICON handle
* This file should only contain access functions for the _specific_
* entries in the struct below.
+ * @note The top part must be equivalent to struct clicon_handle in clixon_handle.c
+ * @see struct clicon_handle, struct backend_handle
*/
struct cli_handle {
int cl_magic; /* magic (HDR)*/
@@ -112,21 +111,23 @@ cli_handle_init(void)
return h;
}
-/*
- * cli_handle_exit
- * frees clicon handle
+/*! Free clicon handle
*/
int
cli_handle_exit(clicon_handle h)
{
cligen_handle ch = cligen(h);
+ struct cli_handle *cl = handle(h);
+ if (cl->cl_stx)
+ free(cl->cl_stx);
clicon_handle_exit(h); /* frees h and options */
+
cligen_exit(ch);
+
return 0;
}
-
/*----------------------------------------------------------
* cli-specific handle access functions
*----------------------------------------------------------*/
diff --git a/apps/cli/cli_main.c b/apps/cli/cli_main.c
index 23e179ce..f8c2a9c7 100644
--- a/apps/cli/cli_main.c
+++ b/apps/cli/cli_main.c
@@ -174,7 +174,8 @@ main(int argc, char **argv)
int printgen = 0;
int logclisyntax = 0;
int help = 0;
- char *treename;
+ char *treename = NULL;
+ int len;
int logdst = CLICON_LOG_STDERR;
char *restarg = NULL; /* what remains after options */
@@ -343,8 +344,14 @@ main(int argc, char **argv)
if (yang2cli(h, yspec, &pt, clicon_cli_genmodel_type(h)) < 0)
goto done;
- treename = chunk_sprintf(__FUNCTION__, "datamodel:%s", clicon_dbspec_name(h));
+ len = strlen("datamodel:") + strlen(clicon_dbspec_name(h)) + 1;
+ if ((treename = malloc(len)) == NULL){
+ clicon_err(OE_UNIX, errno, "malloc");
+ goto done;
+ }
+ snprintf(treename, len, "datamodel:%s", clicon_dbspec_name(h));
cli_tree_add(h, treename, pt);
+
if (printgen)
cligen_print(stdout, pt, 1);
}
@@ -400,9 +407,10 @@ main(int argc, char **argv)
if (!once)
cli_interactive(h);
done:
+ if (treename)
+ free(treename);
if (restarg)
free(restarg);
- unchunk_group(__FUNCTION__);
// Gets in your face if we log on stderr
clicon_log_init(__PROGRAM__, LOG_INFO, 0); /* Log on syslog no stderr */
clicon_log(LOG_NOTICE, "%s: %u Terminated\n", __PROGRAM__, getpid());
diff --git a/apps/cli/cli_plugin.c b/apps/cli/cli_plugin.c
index cbb68cbd..dcdb1730 100644
--- a/apps/cli/cli_plugin.c
+++ b/apps/cli/cli_plugin.c
@@ -81,8 +81,7 @@
*
*/
-/*
- * Find syntax mode named 'mode'. Create if specified
+/*! Find syntax mode named 'mode'. Create if specified
*/
static cli_syntaxmode_t *
syntax_mode_find(cli_syntax_t *stx, const char *mode, int create)
@@ -101,8 +100,8 @@ syntax_mode_find(cli_syntax_t *stx, const char *mode, int create)
if (create == 0)
return NULL;
- if ((m = chunk(sizeof(cli_syntaxmode_t), stx->stx_cnklbl)) == NULL) {
- perror("chunk");
+ if ((m = malloc(sizeof(cli_syntaxmode_t))) == NULL) {
+ perror("malloc");
return NULL;
}
memset (m, 0, sizeof (*m));
@@ -163,40 +162,16 @@ syntax_append(clicon_handle h,
return 0;
}
-/*
- * Unload a plugin
- */
-static int
-plugin_unload(clicon_handle h, void *handle)
-{
- int retval = 0;
- char *error;
- plgexit_t *exitfun;
-
- /* Call exit function is it exists */
- exitfun = dlsym(handle, PLUGIN_EXIT);
- if (dlerror() == NULL)
- exitfun(h);
-
- dlerror(); /* Clear any existing error */
- if (dlclose(handle) != 0) {
- error = (char*)dlerror();
- cli_output (stderr, "dlclose: %s\n", error ? error : "Unknown error");
- /* Just report */
- }
-
- return retval;
-}
-
/*
* Unload all plugins in a group
*/
static int
-syntax_unload(clicon_handle h)
+cli_syntax_unload(clicon_handle h)
{
- struct cli_plugin *p;
- cli_syntax_t *stx = cli_syntax(h);
-
+ cli_syntax_t *stx = cli_syntax(h);
+ struct cli_plugin *p;
+ cli_syntaxmode_t *m;
+
if (stx == NULL)
return 0;
@@ -204,15 +179,18 @@ syntax_unload(clicon_handle h)
p = stx->stx_plugins;
plugin_unload(h, p->cp_handle);
clicon_debug(1, "DEBUG: Plugin '%s' unloaded.", p->cp_name);
- DELQ(stx->stx_plugins, stx->stx_plugins, struct cli_plugin *);
+ DELQ(p, stx->stx_plugins, struct cli_plugin *);
+ if (p)
+ free(p);
stx->stx_nplugins--;
}
while (stx->stx_nmodes > 0) {
- DELQ(stx->stx_modes, stx->stx_modes, cli_syntaxmode_t *);
+ m = stx->stx_modes;
+ DELQ(m, stx->stx_modes, cli_syntaxmode_t *);
+ if (m)
+ free(m);
stx->stx_nmodes--;
}
-
- unchunk_group(stx->stx_cnklbl);
return 0;
}
@@ -265,48 +243,31 @@ clixon_str2fn(char *name,
return NULL;
}
-/*
- * Load a dynamic plugin object and call it's init-function
+/*! Load a dynamic plugin object and call it's init-function
* Note 'file' may be destructively modified
+ * @retval plugin-handle should be freed after use
*/
static plghndl_t
-cli_plugin_load (clicon_handle h, char *file, int dlflags, const char *cnklbl)
+cli_plugin_load(clicon_handle h,
+ char *file,
+ int dlflags)
{
- char *error;
char *name;
- void *handle = NULL;
- plginit_t *initfun;
+ plghndl_t handle = NULL;
struct cli_plugin *cp = NULL;
- dlerror(); /* Clear any existing error */
- if ((handle = dlopen (file, dlflags)) == NULL) {
- error = (char*)dlerror();
- cli_output (stderr, "dlopen: %s\n", error ? error : "Unknown error");
+ if ((handle = plugin_load(h, file, dlflags)) == NULL)
goto quit;
- }
- /* call plugin_init() if defined */
- if ((initfun = dlsym(handle, PLUGIN_INIT)) != NULL) {
- if (initfun(h) != 0) {
- cli_output (stderr, "Failed to initiate %s\n", strrchr(file,'/')?strchr(file, '/'):file);
- goto quit;
- }
- }
-
- if ((cp = chunk(sizeof (struct cli_plugin), cnklbl)) == NULL) {
- perror("chunk");
+ if ((cp = malloc(sizeof (struct cli_plugin))) == NULL) {
+ perror("malloc");
goto quit;
}
memset (cp, 0, sizeof(*cp));
-
name = basename(file);
snprintf(cp->cp_name, sizeof(cp->cp_name), "%.*s", (int)strlen(name)-3, name);
cp->cp_handle = handle;
quit:
- if (cp == NULL) {
- if (handle)
- dlclose(handle);
- }
return cp;
}
@@ -323,7 +284,7 @@ cli_load_syntax(clicon_handle h, const char *filename, const char *clispec_dir)
parse_tree pt = {0,};
int retval = -1;
FILE *f;
- char *filepath;
+ char filepath[MAXPATHLEN];
cvec *vr = NULL;
char *prompt = NULL;
char **vec = NULL;
@@ -331,12 +292,7 @@ cli_load_syntax(clicon_handle h, const char *filename, const char *clispec_dir)
char *plgnam;
struct cli_plugin *p;
- if ((filepath = chunk_sprintf(__FUNCTION__, "%s/%s",
- clispec_dir,
- filename)) == NULL){
- clicon_err(OE_PLUGIN, errno, "chunk");
- goto done;
- }
+ snprintf(filepath, MAXPATHLEN-1, "%s/%s", clispec_dir, filename);
if ((vr = cvec_new(0)) == NULL){
clicon_err(OE_PLUGIN, errno, "cvec_new");
goto done;
@@ -403,7 +359,6 @@ done:
cvec_free(vr);
if (vec)
free(vec);
- unchunk_group(__FUNCTION__);
return retval;
}
@@ -415,10 +370,10 @@ cli_plugin_load_dir(clicon_handle h, char *dir, cli_syntax_t *stx)
{
int i;
int ndp;
- struct dirent *dp;
- char *file;
+ struct dirent *dp = NULL;
char *master_plugin;
- char *master;
+ char master[MAXPATHLEN];
+ char filename[MAXPATHLEN];
struct cli_plugin *cp;
struct stat st;
int retval = -1;
@@ -429,24 +384,18 @@ cli_plugin_load_dir(clicon_handle h, char *dir, cli_syntax_t *stx)
clicon_err(OE_PLUGIN, 0, "clicon_master_plugin option not set");
goto quit;
}
- if ((master = chunk_sprintf(__FUNCTION__, "%s.so", master_plugin)) == NULL){
- clicon_err(OE_PLUGIN, errno, "chunk_sprintf master plugin");
- goto quit;
- }
+ snprintf(master, MAXPATHLEN-1, "%s.so", master_plugin);
+
/* Get plugin objects names from plugin directory */
- ndp = clicon_file_dirent(dir, &dp, "(.so)$", S_IFREG, __FUNCTION__);
+ ndp = clicon_file_dirent(dir, &dp, "(.so)$", S_IFREG);
if (ndp < 0)
goto quit;
/* Load master plugin first */
- file = chunk_sprintf(__FUNCTION__, "%s/%s", dir, master);
- if (file == NULL) {
- clicon_err(OE_UNIX, errno, "chunk_sprintf dir");
- goto quit;
- }
- if (stat(file, &st) == 0) {
+ snprintf(filename, MAXPATHLEN-1, "%s/%s", dir, master);
+ if (stat(filename, &st) == 0) {
clicon_debug(1, "DEBUG: Loading master plugin '%s'", master);
- cp = cli_plugin_load(h, file, RTLD_NOW|RTLD_GLOBAL, stx->stx_cnklbl);
+ cp = cli_plugin_load(h, filename, RTLD_NOW|RTLD_GLOBAL);
if (cp == NULL)
goto quit;
/* Look up certain call-backs in master plugin */
@@ -459,33 +408,25 @@ cli_plugin_load_dir(clicon_handle h, char *dir, cli_syntax_t *stx)
INSQ(cp, stx->stx_plugins);
stx->stx_nplugins++;
}
- unchunk (file);
/* Load the rest */
for (i = 0; i < ndp; i++) {
if (strcmp (dp[i].d_name, master) == 0)
continue; /* Skip master now */
- file = chunk_sprintf(__FUNCTION__, "%s/%s", dir, dp[i].d_name);
- if (file == NULL) {
- clicon_err(OE_UNIX, errno, "chunk_sprintf dir");
- goto quit;
- }
+ snprintf(filename, MAXPATHLEN-1, "%s/%s", dir, dp[i].d_name);
clicon_debug(1, "DEBUG: Loading plugin '%s'", dp[i].d_name);
- if ((cp = cli_plugin_load (h, file, RTLD_NOW, stx->stx_cnklbl)) == NULL)
+ if ((cp = cli_plugin_load (h, filename, RTLD_NOW)) == NULL)
goto quit;
INSQ(cp, stx->stx_plugins);
stx->stx_nplugins++;
- unchunk (file);
}
- if (dp)
- unchunk(dp);
retval = 0;
quit:
- unchunk_group(__FUNCTION__);
-
+ if (dp)
+ free(dp);
return retval;
}
@@ -501,8 +442,7 @@ cli_syntax_load (clicon_handle h)
char *clispec_dir = NULL;
int ndp;
int i;
- char *cnklbl = "__CLICON_CLI_SYNTAX_CNK_LABEL__";
- struct dirent *dp;
+ struct dirent *dp = NULL;
cli_syntax_t *stx;
cli_syntaxmode_t *m;
@@ -521,13 +461,11 @@ cli_syntax_load (clicon_handle h)
}
/* Allocate plugin group object */
- if ((stx = chunk(sizeof(*stx), cnklbl)) == NULL) {
- clicon_err(OE_UNIX, errno, "chunk");
+ if ((stx = malloc(sizeof(*stx))) == NULL) {
+ clicon_err(OE_UNIX, errno, "malloc");
goto quit;
}
memset (stx, 0, sizeof (*stx)); /* Zero out all */
- /* populate name and chunk label */
- strncpy (stx->stx_cnklbl, cnklbl, sizeof(stx->stx_cnklbl)-1);
cli_syntax_set(h, stx);
@@ -541,7 +479,7 @@ cli_syntax_load (clicon_handle h)
goto quit;
/* load syntaxfiles */
- if ((ndp = clicon_file_dirent(clispec_dir, &dp, "(.cli)$", S_IFREG, __FUNCTION__)) < 0)
+ if ((ndp = clicon_file_dirent(clispec_dir, &dp, "(.cli)$", S_IFREG)) < 0)
goto quit;
/* Load the rest */
for (i = 0; i < ndp; i++) {
@@ -550,9 +488,6 @@ cli_syntax_load (clicon_handle h)
if (cli_load_syntax(h, dp[i].d_name, clispec_dir) < 0)
goto quit;
}
- if (dp)
- unchunk(dp);
-
/* Did we successfully load any syntax modes? */
if (stx->stx_nmodes <= 0) {
@@ -576,11 +511,11 @@ cli_syntax_load (clicon_handle h)
quit:
if (retval != 0) {
- syntax_unload(h);
- unchunk_group(cnklbl);
+ cli_syntax_unload(h);
cli_syntax_set(h, NULL);
}
- unchunk_group(__FUNCTION__);
+ if (dp)
+ free(dp);
return retval;
}
@@ -614,7 +549,7 @@ cli_plugin_start(clicon_handle h, int argc, char **argv)
int
cli_plugin_finish(clicon_handle h)
{
- syntax_unload(h);
+ cli_syntax_unload(h);
cli_syntax_set(h, NULL);
return 0;
}
@@ -663,39 +598,36 @@ clicon_eval(clicon_handle h, char *cmd, cg_obj *match_obj, cvec *vr)
}
-/*
- * clicon_parse
- * Given a command string, parse and evaluate the string according to
+/*! Given a command string, parse and evaluate.
+ * Parse and evaluate the string according to
* the syntax parse tree of the syntax mode specified by *mode.
* If there is no match in the tree for the command, the parse hook
* will be called to see if another mode should be evaluated. If a
* match is found in another mode, the mode variable is updated to point at
* the new mode string.
*
- * INPUT:
- * cmd The command string
- * match_obj Pointer to CLIgen match object
- * mode A pointer to the mode string pointer
- * OUTPUT:
- * kr Keyword vector
- * vr Variable vector
- * RETURNS:
- * -2 : on eof (shouldnt happen)
- * -1 : In parse error
- * >=0 : Number of matches
+ * @param[in] h Clicon handle
+ * @param[in] cmd The command string
+ * @param[in,out] mode A pointer to the mode string pointer
+ * @param[out] result -2 On eof (shouldnt happen)
+ * -1 On parse error
+ * >=0 Number of matches
*/
int
-clicon_parse(clicon_handle h, char *cmd, char **mode, int *result)
+clicon_parse(clicon_handle h,
+ char *cmd,
+ char **mode,
+ int *result)
{
- char *m, *msav;
- int res = -1;
- int r;
- cli_syntax_t *stx;
+ char *m, *msav;
+ int res = -1;
+ int r;
+ cli_syntax_t *stx = NULL;
cli_syntaxmode_t *smode;
char *treename;
parse_tree *pt; /* Orig */
cg_obj *match_obj;
- cvec *vr = NULL;
+ cvec *cvv = NULL;
stx = cli_syntax(h);
m = *mode;
@@ -719,11 +651,11 @@ clicon_parse(clicon_handle h, char *cmd, char **mode, int *result)
fprintf(stderr, "No such parse-tree registered: %s\n", treename);
goto done;;
}
- if ((vr = cvec_new(0)) == NULL){
- fprintf(stderr, "%s: cvec_new: %s\n", __FUNCTION__, strerror(errno));
+ if ((cvv = cvec_new(0)) == NULL){
+ clicon_err(OE_UNIX, errno, "cvec_new");
goto done;;
}
- res = cliread_parse(cli_cligen(h), cmd, pt, &match_obj, vr);
+ res = cliread_parse(cli_cligen(h), cmd, pt, &match_obj, cvv);
if (res != CG_MATCH)
pt_expand_cleanup_1(pt);
if (msav){
@@ -755,7 +687,7 @@ clicon_parse(clicon_handle h, char *cmd, char **mode, int *result)
*mode = m;
cli_set_syntax_mode(h, m);
}
- if ((r = clicon_eval(h, cmd, match_obj, vr)) < 0)
+ if ((r = clicon_eval(h, cmd, match_obj, cvv)) < 0)
cli_handler_err(stdout);
pt_expand_cleanup_1(pt);
if (result)
@@ -769,8 +701,8 @@ clicon_parse(clicon_handle h, char *cmd, char **mode, int *result)
}
}
done:
- if (vr)
- cvec_free(vr);
+ if (cvv)
+ cvec_free(cvv);
return res;
}
@@ -891,9 +823,7 @@ cli_set_prompt(clicon_handle h, const char *name, const char *prompt)
return 0;
}
-/*
- * Format prompt
- * XXX: HOST_NAME_MAX from sysconf()
+/*! Format prompt
*/
static int
prompt_fmt (char *prompt, size_t plen, char *fmt, ...)
@@ -902,65 +832,56 @@ prompt_fmt (char *prompt, size_t plen, char *fmt, ...)
char *s = fmt;
char hname[1024];
char tty[32];
- char *new;
char *tmp;
int ret = -1;
+ cbuf *cb = NULL;
+
+ if ((cb = cbuf_new()) == NULL){
+ clicon_err(OE_XML, errno, "cbuf_new");
+ goto done;
+ }
/* Start with empty string */
- if((new = chunk_sprintf(__FUNCTION__, "%s", ""))==NULL)
- goto done;
-
+ cprintf(cb, "");
while(*s) {
if (*s == '%' && *++s) {
switch(*s) {
-
case 'H': /* Hostname */
if (gethostname (hname, sizeof (hname)) != 0)
strncpy(hname, "unknown", sizeof(hname)-1);
- if((new = chunk_strncat(new, hname, 0, __FUNCTION__))==NULL)
- goto done;
+ cprintf(cb, "%s", hname);
break;
-
case 'U': /* Username */
tmp = getenv("USER");
- if((new = chunk_strncat(new, (tmp ? tmp : "nobody"), 0, __FUNCTION__))==NULL)
- goto done;
+ cprintf(cb, "%s", tmp?tmp:"nobody");
break;
-
case 'T': /* TTY */
if(ttyname_r(fileno(stdin), tty, sizeof(tty)-1) < 0)
strcpy(tty, "notty");
- if((new = chunk_strncat(new, tty, strlen(tty), __FUNCTION__))==NULL)
- goto done;
+ cprintf(cb, "%s", tty);
break;
-
default:
- if((new = chunk_strncat(new, "%", 1, __FUNCTION__))==NULL ||
- (new = chunk_strncat(new, s, 1, __FUNCTION__)))
- goto done;
+ cprintf(cb, "%%");
+ cprintf(cb, "%c", *s);
}
}
- else {
- if ((new = chunk_strncat(new, s, 1, __FUNCTION__))==NULL)
- goto done;
- }
+ else
+ cprintf(cb, "%c", *s);
s++;
}
done:
- if (new)
- fmt = new;
+ if (cb)
+ fmt = cbuf_get(cb);
va_start(ap, fmt);
ret = vsnprintf(prompt, plen, fmt, ap);
va_end(ap);
-
- unchunk_group(__FUNCTION__);
-
+ if (cb)
+ cbuf_free(cb);
return ret;
}
-/*
- * Return a formatted prompt string
+/*! Return a formatted prompt string
*/
char *
cli_prompt(char *fmt)
@@ -1069,11 +990,10 @@ cli_ptpop(clicon_handle h, char *mode, char *op)
}
-/*
- * clicon_valcb
+/*! Find a cli plugin based on name and resolve a function pointer in it.
* Callback from clicon_dbvars_parse()
- * Find a cli plugin based on name if given and
- * use dlsym to resolve a function pointer in it.
+ * Find a cli plugin based on name if given and use dlsym to resolve a
+ * function pointer in it.
* Call the resolved function to get the cgv populated
*/
int
diff --git a/apps/cli/cli_plugin.h b/apps/cli/cli_plugin.h
index 8823a052..0a74e6a7 100644
--- a/apps/cli/cli_plugin.h
+++ b/apps/cli/cli_plugin.h
@@ -74,7 +74,6 @@ struct cli_plugin {
/* Plugin group object */
typedef struct {
- char stx_cnklbl[128]; /* Plugin group name */
int stx_nplugins; /* Number of plugins */
struct cli_plugin *stx_plugins; /* List of plugins */
int stx_nmodes; /* Number of syntax modes */
diff --git a/apps/cli/cli_show.c b/apps/cli/cli_show.c
index 392493f3..95df07b8 100644
--- a/apps/cli/cli_show.c
+++ b/apps/cli/cli_show.c
@@ -213,15 +213,15 @@ expand_dir(char *dir,
mode_t flags,
int detail)
{
- DIR *dirp;
+ DIR *dirp;
struct dirent *dp;
- struct stat st;
- char *str;
- char *cmd;
- int len;
- int retval = -1;
+ struct stat st;
+ char *str;
+ char *cmd;
+ int len;
+ int retval = -1;
struct passwd *pw;
- char filename[MAXPATHLEN];
+ char filename[MAXPATHLEN];
if ((dirp = opendir(dir)) == 0){
fprintf(stderr, "expand_dir: opendir(%s) %s\n",
diff --git a/apps/dbctrl/dbctrl_main.c b/apps/dbctrl/dbctrl_main.c
deleted file mode 100644
index 4910b958..00000000
--- a/apps/dbctrl/dbctrl_main.c
+++ /dev/null
@@ -1,241 +0,0 @@
-/*
- *
- ***** 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 *****
-
- */
-
-#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
-
-/* cligen */
-#include
-
-/* clicon */
-#include
-
-/* Command line options to be passed to getopt(3) */
-#define DBCTRL_OPTS "hDSd:pbn:r:m:Zi"
-
-/*
- * remove_entry
- */
-static int
-remove_entry(char *dbname, char *key)
-{
-#ifdef NOTYET /* This assumes direct access to database */
- return db_del(dbname, key);
-#else
- return 0;
-#endif
-}
-
-/*! usage
- */
-static void
-usage(char *argv0)
-{
- fprintf(stderr, "usage:%s\n"
- "where options are\n"
- "\t-h\t\tHelp\n"
- "\t-D\t\tDebug\n"
- "\t-S\t\tLog on syslog\n"
- "\t-d \t\tDatabase name (default: running)\n"
- "\t-p\t\tDump database on stdout\n"
- "\t-b\t\tBrief output, just print keys. Combine with -p or -m\n"
- "\t-n \" \" Add database entry\n"
- "\t-r \tRemove database entry\n"
- "\t-m \tMatch regexp key in database\n"
- "\t-Z\t\tDelete database\n"
- "\t-i\t\tInit database\n",
- argv0
- );
- exit(0);
-}
-
-int
-main(int argc, char **argv)
-{
- char c;
- int zapdb;
- int initdb;
- int dumpdb;
- int addent;
- int rment;
- char *matchkey = NULL;
- char *addstr;
- char rmkey[MAXPATHLEN];
- int brief;
- char db[MAXPATHLEN] = {0,};
- int use_syslog;
- clicon_handle h;
-
- /* In the startup, logs to stderr & debug flag set later */
- clicon_log_init(__PROGRAM__, LOG_INFO, CLICON_LOG_STDERR);
-
- /* Defaults */
- zapdb = 0;
- initdb = 0;
- dumpdb = 0;
- addent = 0;
- rment = 0;
- brief = 0;
- use_syslog = 0;
- addstr = NULL;
- memcpy(db, "running", strlen("running")+1);
- memset(rmkey, '\0', sizeof(rmkey));
-
- if ((h = clicon_handle_init()) == NULL)
- goto done;
- /* getopt in two steps, first find config-file before over-riding options. */
- while ((c = getopt(argc, argv, DBCTRL_OPTS)) != -1)
- switch (c) {
- case '?' :
- case 'h' : /* help */
- usage(argv[0]);
- break;
- case 'D' : /* debug */
- debug = 1;
- break;
- case 'S': /* Log on syslog */
- use_syslog = 1;
- break;
- }
- /*
- * Logs, error and debug to stderr or syslog, set debug level
- */
- clicon_log_init(__PROGRAM__, debug?LOG_DEBUG:LOG_INFO,
- use_syslog?CLICON_LOG_SYSLOG:CLICON_LOG_STDERR);
- clicon_debug_init(debug, NULL);
-
-
- /* Now rest of options */
- optind = 1;
- while ((c = getopt(argc, argv, DBCTRL_OPTS)) != -1)
- switch (c) {
- case 'Z': /* Zap database */
- zapdb++;
- break;
- case 'i': /* Init database */
- initdb++;
- break;
- case 'p': /* Dump/print database */
- dumpdb++;
- break;
- case 'b': /* Dump/print/match database brief (combone w -p or -m) */
- brief++;
- break;
- case 'd': /* db either db filename or symbolic: running|candidate */
- if (!optarg || sscanf(optarg, "%s", db) != 1)
- usage(argv[0]);
- break;
- case 'n': /* add database entry */
- if (!optarg || !strlen(optarg) || (addstr = strdup(optarg)) == NULL)
- usage(argv[0]);
- /* XXX addign both key and value, for now only key */
- addent++;
- break;
- case 'r':
- if (!optarg || sscanf(optarg, "%s", rmkey) != 1)
- usage(argv[0]);
- rment++;
- break;
- case 'm':
- if (!optarg || !strlen(optarg) || (matchkey = strdup(optarg)) == NULL)
- usage(argv[0]);
- dumpdb++;
- break;
- case 'D': /* Processed earlier, ignore now. */
- case 'S':
- break;
- default:
- usage(argv[0]);
- break;
- }
- argc -= optind;
- argv += optind;
-
- if (*db == '\0'){
- clicon_err(OE_FATAL, 0, "database not specified (with -d ): %s");
- goto done;
- }
- if (dumpdb){
- /* Here db must be local file-path */
- if (xmldb_dump(stdout, db, matchkey)) {
- fprintf(stderr, "Match error\n");
- goto done;
- }
- }
- if (addent){ /* add entry */
- cxobj *xml = NULL;
-
- if (clicon_xml_parse(&xml, "%s", addstr) < 0)
- goto done;
- if (xmldb_put(h, db, OP_REPLACE, NULL, xml) < 0)
- goto done;
- if (xml)
- xml_free(xml);
-
- }
- if (rment)
- if (remove_entry(db, rmkey) < 0)
- goto done;
- if (zapdb) /* remove databases */
- if (xmldb_delete(h, db) < 0){
- clicon_err(OE_FATAL, errno, "delete %s", db);
- goto done;
- }
- if (initdb)
- if (xmldb_init(h, db) < 0)
- goto done;
- done:
- return 0;
-}
diff --git a/apps/netconf/README.md b/apps/netconf/README.md
new file mode 100644
index 00000000..92e6a8b9
--- /dev/null
+++ b/apps/netconf/README.md
@@ -0,0 +1,16 @@
+# Clixon Netconf
+
+Clixon Netconf implements the following NETCONF proposals or standards:
+- [NETCONF Configuration Protocol](http://www.rfc-base.org/txt/rfc-4741.txt)
+- [Using the NETCONF Configuration Protocol over Secure SHell (SSH)](http://www.rfc-base.org/txt/rfc-4742.txt)
+- [NETCONF Event Notifications](http://www.rfc-base.org/txt/rfc-5277.txt)
+
+However, it needs to be updated to RFC 6241 and RFC 6242.
+
+Clixon NETCONF currently does not support the following features:
+
+- :url capability
+- copy-config source config
+- edit-config testopts
+- edit-config erropts
+- edit-config config-text
diff --git a/apps/netconf/netconf_plugin.c b/apps/netconf/netconf_plugin.c
index ce68c92b..9a2e611f 100644
--- a/apps/netconf/netconf_plugin.c
+++ b/apps/netconf/netconf_plugin.c
@@ -75,60 +75,6 @@ struct netconf_reg {
};
typedef struct netconf_reg netconf_reg_t;
-/*! Unload a plugin
- */
-static int
-plugin_unload(clicon_handle h, void *handle)
-{
- int retval = 0;
- char *error;
- plgexit_t *exitfn;
-
- /* Call exit function is it exists */
- exitfn = dlsym(handle, PLUGIN_EXIT);
- if (dlerror() == NULL)
- exitfn(h);
-
- dlerror(); /* Clear any existing error */
- if (dlclose(handle) != 0) {
- error = (char*)dlerror();
- clicon_err(OE_PLUGIN, errno, "dlclose: %s\n", error ? error : "Unknown error");
- /* Just report */
- }
- return retval;
-}
-
-
-
-/*
- * Load a dynamic plugin object and call it's init-function
- * Note 'file' may be destructively modified
- */
-static plghndl_t
-plugin_load (clicon_handle h, char *file, int dlflags, const char *cnklbl)
-{
- char *error;
- void *handle = NULL;
- plginit_t *initfn;
-
- dlerror(); /* Clear any existing error */
- if ((handle = dlopen (file, dlflags)) == NULL) {
- error = (char*)dlerror();
- clicon_err(OE_PLUGIN, errno, "dlopen: %s\n", error ? error : "Unknown error");
- goto quit;
- }
- /* call plugin_init() if defined */
- if ((initfn = dlsym(handle, PLUGIN_INIT)) != NULL) {
- if (initfn(h) != 0) {
- clicon_err(OE_PLUGIN, errno, "Failed to initiate %s\n", strrchr(file,'/')?strchr(file, '/'):file);
- goto quit;
- }
- }
-quit:
-
- return handle;
-}
-
static int nplugins = 0;
static plghndl_t *plugins = NULL;
static netconf_reg_t *deps = NULL;
@@ -141,9 +87,9 @@ netconf_plugin_load(clicon_handle h)
int retval = -1;
char *dir;
int ndp;
- struct dirent *dp;
+ struct dirent *dp = NULL;
int i;
- char *filename;
+ char filename[MAXPATHLEN];
plghndl_t *handle;
if ((dir = clicon_netconf_dir(h)) == NULL){
@@ -152,30 +98,26 @@ netconf_plugin_load(clicon_handle h)
}
/* Get plugin objects names from plugin directory */
- if((ndp = clicon_file_dirent(dir, &dp, "(.so)$", S_IFREG, __FUNCTION__))<0)
+ if((ndp = clicon_file_dirent(dir, &dp, "(.so)$", S_IFREG))<0)
goto quit;
/* Load all plugins */
for (i = 0; i < ndp; i++) {
- filename = chunk_sprintf(__FUNCTION__, "%s/%s", dir, dp[i].d_name);
+ snprintf(filename, MAXPATHLEN-1, "%s/%s", dir, dp[i].d_name);
clicon_debug(1, "DEBUG: Loading plugin '%.*s' ...",
(int)strlen(filename), filename);
- if (filename == NULL) {
- clicon_err(OE_UNIX, errno, "chunk");
+ if ((handle = plugin_load(h, filename, RTLD_NOW)) == NULL)
goto quit;
- }
- if ((handle = plugin_load (h, filename, RTLD_NOW, __FUNCTION__)) == NULL)
- goto quit;
- if ((plugins = rechunk(plugins, (nplugins+1) * sizeof (*plugins), NULL)) == NULL) {
- clicon_err(OE_UNIX, errno, "chunk");
+ if ((plugins = realloc(plugins, (nplugins+1) * sizeof (*plugins))) == NULL) {
+ clicon_err(OE_UNIX, errno, "realloc");
goto quit;
}
plugins[nplugins++] = handle;
- unchunk (filename);
}
retval = 0;
quit:
- unchunk_group(__FUNCTION__);
+ if (dp)
+ free(dp);
return retval;
}
@@ -194,8 +136,10 @@ netconf_plugin_unload(clicon_handle h)
}
for (i = 0; i < nplugins; i++)
plugin_unload(h, plugins[i]);
- if (plugins)
- unchunk(plugins);
+ if (plugins){
+ free(plugins);
+ plugins = NULL;
+ }
nplugins = 0;
return 0;
}
diff --git a/apps/restconf/README b/apps/restconf/README.md
similarity index 70%
rename from apps/restconf/README
rename to apps/restconf/README.md
index 8d750288..81905802 100644
--- a/apps/restconf/README
+++ b/apps/restconf/README.md
@@ -1,27 +1,20 @@
-Clixon Restconf
-===============
+# Clixon Restconf
+### Features
-Contents:
-1. Features
-2. Installation using NGINX
-3. Debugging
-
-1. FEATURES
-+++++++++++
Clixon restconf is a daemon based on FASTCGI. Instructions are available to
run with NGINX.
The implementatation supports plain OPTIONS, HEAD, GET, POST, PUT, PATCH, DELETE.
and is based on draft-ietf-netconf-restconf-13.
-There is currently (2017) a RFC 8040, many of those features are _not_ implemented,
+There is currently (2017) a [RFC 8040: RESTCONF Protocol](https://tools.ietf.org/html/rfc8040), many of those features are _not_ implemented,
including:
- query parameters (section 4.9)
- notifications (sec 6)
- only rudimentary error reporting exists (sec 7)
-2. INSTALLATION using NGINX
-+++++++++++++++++++++++++++
+### Installation using Nginx
-# Define nginx config file/etc/nginx/sites-available/default
+Define nginx config file/etc/nginx/sites-available/default
+```
server {
...
location /restconf {
@@ -30,13 +23,19 @@ server {
include fastcgi_params;
}
}
-# Start nginx daemon
+```
+Start nginx daemon
+```
sudo /etc/init.d nginx start
+```
-# Start clixon restconf daemon
+Start clixon restconf daemon
+```
olof@vandal> sudo su -c "/www-data/clixon_restconf -f /usr/local/etc/routing.conf " -s /bin/sh www-data
+```
-# Make restconf calls with curl
+Make restconf calls with curl
+```
olof@vandal> curl -G http://127.0.0.1/restconf/data/interfaces
[
{
@@ -62,16 +61,21 @@ olof@vandal> curl -G http://127.0.0.1/restconf/data/interfaces/interface/name=et
]
curl -sX POST -d '{"clicon":{"interfaces":{"interface":{"name":"eth1","type":"eth","enabled":"true"}}}}' http://localhost/restconf/data
+```
+### Debugging
-3. DEBUGGING
-++++++++++++
-Start the restconf programs with debug flag:
-sudo su -c "/www-data/clixon_restconf -D" -s /bin/sh www-data
-
+Start the restconf fastcgi program with debug flag:
+```
+sudo su -c "/www-data/clixon_restconf -Df /usr/local/etc/routing.conf" -s /bin/sh www-
+data
+```
Look at syslog:
+```
tail -f /var/log/syslog | grep clixon_restconf
+```
Send command:
+```
curl -G http://127.0.0.1/restconf/data/*
-
+```
diff --git a/apps/restconf/restconf_lib.c b/apps/restconf/restconf_lib.c
index adda4275..150c50c7 100644
--- a/apps/restconf/restconf_lib.c
+++ b/apps/restconf/restconf_lib.c
@@ -33,7 +33,6 @@
*/
-
#include
#include
#include
@@ -46,9 +45,9 @@
#include
#include
#include
+#include
#include
#include
-#include
/* cligen */
#include
@@ -111,98 +110,6 @@ clicon_debug_xml(int dbglevel,
return retval;
}
-/*! Split a string into a cligen variable vector using 1st and 2nd delimiter
- * Split a string first into elements delimited by delim1, then into
- * pairs delimited by delim2.
- * @param[in] string String to split
- * @param[in] delim1 First delimiter char that delimits between elements
- * @param[in] delim2 Second delimiter char for pairs within an element
- * @param[out] cvp Created cligen variable vector, NOTE: can be NULL
- * @retval 0 on OK
- * @retval -1 error
- *
- * Example, assuming delim1 = '&' and delim2 = '='
- * a=b&c=d -> [[a,"b"][c="d"]
- * kalle&c=d -> [[c="d"]] # Discard elements with no delim2
- * XXX differentiate between error and null cvec.
- */
-int
-str2cvec(char *string,
- char delim1,
- char delim2,
- cvec **cvp)
-{
- int retval = -1;
- char *s;
- char *s0 = NULL;;
- char *val; /* value */
- char *valu; /* unescaped value */
- char *snext; /* next element in string */
- cvec *cvv = NULL;
- cg_var *cv;
-
- clicon_debug(1, "%s %s", __FUNCTION__, string);
- if ((s0 = strdup(string)) == NULL){
- clicon_debug(1, "error strdup %s", strerror(errno));
- goto err;
- }
- s = s0;
- if ((cvv = cvec_new(0)) ==NULL){
- clicon_debug(1, "error cvec_new %s", strerror(errno));
- goto err;
- }
- while (s != NULL) {
- /*
- * In the pointer algorithm below:
- * name1=val1; name2=val2;
- * ^ ^ ^
- * | | |
- * s val snext
- */
- if ((snext = index(s, delim1)) != NULL)
- *(snext++) = '\0';
- if ((val = index(s, delim2)) != NULL){
- *(val++) = '\0';
- if ((valu = curl_easy_unescape(NULL, val, 0, NULL)) == NULL){
- clicon_debug(1, "curl_easy_unescape %s", strerror(errno));
- goto err;
- }
- if ((cv = cvec_add(cvv, CGV_STRING)) == NULL){
- clicon_debug(1, "error cvec_add %s", strerror(errno));
- goto err;
- }
- while ((strlen(s) > 0) && isblank(*s))
- s++;
- cv_name_set(cv, s);
- cv_string_set(cv, valu);
- free(valu);
- }
- else{
- if (strlen(s)){
- if ((cv = cvec_add(cvv, CGV_STRING)) == NULL){
- clicon_debug(1, "error cvec_add %s", strerror(errno));
- goto err;
- }
- cv_name_set(cv, s);
- cv_string_set(cv, "");
- }
- }
- s = snext;
- }
- retval = 0;
- done:
- *cvp = cvv;
- if (s0)
- free(s0);
- return retval;
- err:
- if (cvv){
- cvec_free(cvv);
- cvv = NULL;
- }
- goto done;
-}
-
/*!
* @param[in] r Fastcgi request handle
*/
@@ -272,45 +179,6 @@ static int nplugins = 0;
static plghndl_t *plugins = NULL;
static credentials_t *p_credentials = NULL; /* Credentials callback */
-/*! Load a dynamic plugin object and call it's init-function
- * Note 'file' may be destructively modified
- */
-static plghndl_t
-plugin_load (clicon_handle h,
- char *file,
- int dlflags,
- const char *cnklbl)
-{
- char *error;
- void *handle = NULL;
- plginit_t *initfn;
-
- clicon_debug(1, "%s", __FUNCTION__);
- dlerror(); /* Clear any existing error */
- if ((handle = dlopen (file, dlflags)) == NULL) {
- error = (char*)dlerror();
- clicon_err(OE_PLUGIN, errno, "dlopen: %s\n", error ? error : "Unknown error");
- goto done;
- }
- /* call plugin_init() if defined */
- if ((initfn = dlsym(handle, PLUGIN_INIT)) == NULL){
- clicon_err(OE_PLUGIN, errno, "Failed to find plugin_init when loading restconf plugin %s", file);
- goto err;
- }
- if (initfn(h) != 0) {
- clicon_err(OE_PLUGIN, errno, "Failed to initiate %s", strrchr(file,'/')?strchr(file, '/'):file);
- goto err;
- }
- p_credentials = dlsym(handle, "restconf_credentials");
- done:
- return handle;
- err:
- if (handle)
- dlclose(handle);
- return NULL;
-}
-
-
/*! Load all plugins you can find in CLICON_RESTCONF_DIR
*/
int
@@ -319,65 +187,40 @@ restconf_plugin_load(clicon_handle h)
int retval = -1;
char *dir;
int ndp;
- struct dirent *dp;
+ struct dirent *dp = NULL;
int i;
- char *filename;
plghndl_t *handle;
+ char filename[MAXPATHLEN];
if ((dir = clicon_restconf_dir(h)) == NULL){
clicon_err(OE_PLUGIN, 0, "clicon_restconf_dir not defined");
goto quit;
}
/* Get plugin objects names from plugin directory */
- if((ndp = clicon_file_dirent(dir, &dp, "(.so)$", S_IFREG, __FUNCTION__))<0)
+ if((ndp = clicon_file_dirent(dir, &dp, "(.so)$", S_IFREG))<0)
goto quit;
/* Load all plugins */
for (i = 0; i < ndp; i++) {
- filename = chunk_sprintf(__FUNCTION__, "%s/%s", dir, dp[i].d_name);
+ snprintf(filename, MAXPATHLEN-1, "%s/%s", dir, dp[i].d_name);
clicon_debug(1, "DEBUG: Loading plugin '%.*s' ...",
(int)strlen(filename), filename);
- if (filename == NULL) {
- clicon_err(OE_UNIX, errno, "chunk");
+ if ((handle = plugin_load(h, filename, RTLD_NOW)) == NULL)
goto quit;
- }
- if ((handle = plugin_load (h, filename, RTLD_NOW, __FUNCTION__)) == NULL)
- goto quit;
- if ((plugins = rechunk(plugins, (nplugins+1) * sizeof (*plugins), NULL)) == NULL) {
- clicon_err(OE_UNIX, errno, "chunk");
+ p_credentials = dlsym(handle, "restconf_credentials");
+ if ((plugins = realloc(plugins, (nplugins+1) * sizeof (*plugins))) == NULL) {
+ clicon_err(OE_UNIX, errno, "realloc");
goto quit;
}
plugins[nplugins++] = handle;
- unchunk (filename);
}
retval = 0;
quit:
- unchunk_group(__FUNCTION__);
+ if (dp)
+ free(dp);
return retval;
}
-/*! Unload a plugin
- */
-static int
-plugin_unload(clicon_handle h, void *handle)
-{
- int retval = 0;
- char *error;
- plgexit_t *exitfn;
-
- /* Call exit function is it exists */
- exitfn = dlsym(handle, PLUGIN_EXIT);
- if (dlerror() == NULL)
- exitfn(h);
-
- dlerror(); /* Clear any existing error */
- if (dlclose(handle) != 0) {
- error = (char*)dlerror();
- clicon_err(OE_PLUGIN, errno, "dlclose: %s\n", error ? error : "Unknown error");
- /* Just report */
- }
- return retval;
-}
/*! Unload all restconf plugins */
int
@@ -387,8 +230,10 @@ restconf_plugin_unload(clicon_handle h)
for (i = 0; i < nplugins; i++)
plugin_unload(h, plugins[i]);
- if (plugins)
- unchunk(plugins);
+ if (plugins){
+ free(plugins);
+ plugins = NULL;
+ }
nplugins = 0;
return 0;
}
diff --git a/apps/restconf/restconf_lib.h b/apps/restconf/restconf_lib.h
index 8eba97bb..f133b15f 100644
--- a/apps/restconf/restconf_lib.h
+++ b/apps/restconf/restconf_lib.h
@@ -46,7 +46,6 @@
int notfound(FCGX_Request *r);
int badrequest(FCGX_Request *r);
int clicon_debug_xml(int dbglevel, char *str, cxobj *cx);
-int str2cvec(char *string, char delim1, char delim2, cvec **cvp);
int test(FCGX_Request *r, int dbg);
cbuf *readdata(FCGX_Request *r);
diff --git a/apps/restconf/restconf_main.c b/apps/restconf/restconf_main.c
index 93936d18..90d0e3e2 100644
--- a/apps/restconf/restconf_main.c
+++ b/apps/restconf/restconf_main.c
@@ -58,7 +58,6 @@
#include
#include
#include
-#include
#include
/* cligen */
diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c
index 5cdb1208..3194c4d6 100644
--- a/apps/restconf/restconf_methods.c
+++ b/apps/restconf/restconf_methods.c
@@ -109,7 +109,6 @@ Mapping netconf error-tag -> status code
#include
#include
#include
-#include
/* cligen */
#include
@@ -153,86 +152,19 @@ api_data_get_gen(clicon_handle h,
int head)
{
int retval = -1;
- cg_var *cv;
- char *val;
- char *v;
- int i;
cbuf *path = NULL;
- cbuf *path1 = NULL;
cbuf *cbx = NULL;
cxobj **vec = NULL;
yang_spec *yspec;
- yang_stmt *y = NULL;
- yang_stmt *ykey;
- char *name;
- cvec *cvk = NULL; /* vector of index keys */
- cg_var *cvi;
cxobj *xret = NULL;
clicon_debug(1, "%s", __FUNCTION__);
yspec = clicon_dbspec_yang(h);
if ((path = cbuf_new()) == NULL)
goto done;
- if ((path1 = cbuf_new()) == NULL) /* without [] qualifiers */
- goto done;
- cv = NULL;
- cprintf(path1, "/");
- /* translate eg a/b=c -> a/[b=c] */
- for (i=pi; i 0){
- if ((val = cv2str_dup(cv)) == NULL)
- goto done;
- v = val;
- /* XXX sync with yang */
- while((v=index(v, ',')) != NULL){
- *v = '\0';
- v++;
- }
- /* Find keys */
- 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);
- notfound(r);
- goto done;
- }
- clicon_debug(1, "ykey:%s", ykey->ys_argument);
-
- /* The value is a list of keys: [ ]* */
- if ((cvk = yang_arg2cvec(ykey, " ")) == NULL)
- goto done;
- cvi = NULL;
- /* Iterate over individual yang keys */
- cprintf(path, "/%s", name);
- v = val;
- while ((cvi = cvec_each(cvk, cvi)) != NULL){
- cprintf(path, "[%s=%s]", cv_string_get(cvi), v);
- v += strlen(v)+1;
- }
- if (val)
- free(val);
- }
- else{
- cprintf(path, "%s%s", (i==pi?"":"/"), name);
- cprintf(path1, "/%s", name);
- }
+ if (api_path2xpath_cvv(yspec, pcvec, pi, path) < 0){
+ notfound(r);
+ goto done;
}
clicon_debug(1, "%s path:%s", __FUNCTION__, cbuf_get(path));
if (clicon_rpc_get_config(h, "running", cbuf_get(path), &xret) < 0){
@@ -268,8 +200,6 @@ api_data_get_gen(clicon_handle h,
cbuf_free(cbx);
if (path)
cbuf_free(path);
- if (path1)
- cbuf_free(path1);
if (xret)
xml_free(xret);
return retval;
@@ -366,7 +296,7 @@ api_data_edit(clicon_handle h,
goto done;
}
cprintf(cbx, "");
- clicon_debug(1, "%s cbx: %s api_path:%s",__FUNCTION__, cbuf_get(cbx), api_path);
+ clicon_debug(1, "%s xml: %s api_path:%s",__FUNCTION__, cbuf_get(cbx), api_path);
if (clicon_rpc_edit_config(h, "candidate",
operation,
api_path,
@@ -407,7 +337,8 @@ api_data_edit(clicon_handle h,
If the data resource already exists, then the POST request MUST fail
and a "409 Conflict" status-line MUST be returned.
- * Netconf: (nc:operation="create") | invoke an RPC operation
+ * Netconf: (nc:operation="create") | invoke an RPC operation * @example
+
*/
int
api_data_post(clicon_handle h,
@@ -429,8 +360,8 @@ api_data_post(clicon_handle h,
* @param[in] pi Offset, where to start pcvec
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] data Stream input data
- * Example:
- curl -X PUT -d {\"enabled\":\"false\"} http://127.0.0.1/restconf/data/interfaces/interface=eth1
+ * @example
+ curl -X PUT -d '{"enabled":"false"}' http://127.0.0.1/restconf/data/interfaces/interface=eth1
*
PUT:
if the PUT request creates a new resource,
diff --git a/clixon.conf.cpp.cpp b/clixon.conf.cpp.cpp
index 57923b95..1a6f441d 100644
--- a/clixon.conf.cpp.cpp
+++ b/clixon.conf.cpp.cpp
@@ -73,10 +73,6 @@ CLICON_CLI_DIR libdir/APPNAME/cli
# Location of frontend .cli cligen spec files
CLICON_CLISPEC_DIR libdir/APPNAME/clispec
-# Directory where to save configuration commit history (in XML). Snapshots
-# are saved chronologically
-CLICON_ARCHIVE_DIR localstatedir/APPNAME/archive
-
# Enabled uses "startup" configuration on boot
CLICON_USE_STARTUP_CONFIG 0
@@ -121,6 +117,9 @@ CLICON_BACKEND_PIDFILE localstatedir/APPNAME/APPNAME.pidfile
# Directory where "running", "candidate" and "startup" are placed
CLICON_XMLDB_DIR localstatedir/APPNAME
+# XMLDB datastore plugin filename (see datastore/ and clixon_xml_db.[ch])
+CLICON_XMLDB_PLUGIN libdir/xmldb/text.so
+
# Dont include keys in cvec in cli vars callbacks, ie a & k in 'a k ' ignored
# CLICON_CLI_VARONLY 1
diff --git a/configure b/configure
index 3e5cf739..d48a768e 100755
--- a/configure
+++ b/configure
@@ -632,6 +632,8 @@ CPP
OBJEXT
EXEEXT
ac_ct_CC
+with_keyvalue
+with_restconf
RANLIB
AR
EXE_SUFFIX
@@ -702,8 +704,9 @@ ac_subst_files=''
ac_user_opts='
enable_option_checking
with_cligen
+with_restconf
+with_keyvalue
with_qdbm
-enable_keycontent
'
ac_precious_vars='build_alias
host_alias
@@ -1324,17 +1327,13 @@ if test -n "$ac_init_help"; then
cat <<\_ACEOF
-Optional Features:
- --disable-option-checking ignore unrecognized --enable/--with options
- --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no)
- --enable-FEATURE[=ARG] include FEATURE [ARG=yes]
- --disable-keycontent Disable reverse lookup content keys
-
Optional Packages:
--with-PACKAGE[=ARG] use PACKAGE [ARG=yes]
--without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no)
--with-cligen=dir Use CLIGEN here
- --with-qdbm=dir Use QDBM here
+ --without-restconf disable support for restconf
+ --without-keyvalue disable support for key-value xmldb datastore
+ --with-qdbm=dir Use QDBM here, if keyvalue
Some influential environment variables:
CC C compiler command
@@ -2136,7 +2135,7 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu
: ${CFLAGS="-O2"}
CLIXON_VERSION_MAJOR="3"
-CLIXON_VERSION_MINOR="2"
+CLIXON_VERSION_MINOR="3"
CLIXON_VERSION_PATCH="0"
CLIXON_VERSION="\"${CLIXON_VERSION_MAJOR}.${CLIXON_VERSION_MINOR}.${CLIXON_VERSION_PATCH}\""
# Fix to specific version (eg 3.5) or head (3)
@@ -2327,6 +2326,8 @@ test -n "$target_alias" &&
+ # If yes, compile apps/restconf
+ # If yes, compile datastore/keyvalue
#
ac_ext=c
@@ -3541,7 +3542,6 @@ if test "${with_cligen}"; then
fi
-
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for grep that handles long lines and -e" >&5
$as_echo_n "checking for grep that handles long lines and -e... " >&6; }
if ${ac_cv_path_GREP+:} false; then :
@@ -3866,21 +3866,92 @@ else
fi
-# This is for qdbm
+# This is for restconf (and fastcgi)
+
+# Check whether --with-restconf was given.
+if test "${with_restconf+set}" = set; then :
+ withval=$with_restconf;
+else
+ with_restconf=yes
+fi
+
+if test "x${with_restconf}" == xyes; then
+ # Lives in libfcgi-dev
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for FCGX_Init in -lfcgi" >&5
+$as_echo_n "checking for FCGX_Init in -lfcgi... " >&6; }
+if ${ac_cv_lib_fcgi_FCGX_Init+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lfcgi $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char FCGX_Init ();
+int
+main ()
+{
+return FCGX_Init ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_fcgi_FCGX_Init=yes
+else
+ ac_cv_lib_fcgi_FCGX_Init=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_fcgi_FCGX_Init" >&5
+$as_echo "$ac_cv_lib_fcgi_FCGX_Init" >&6; }
+if test "x$ac_cv_lib_fcgi_FCGX_Init" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBFCGI 1
+_ACEOF
+
+ LIBS="-lfcgi $LIBS"
+
+else
+ as_fn_error $? "libfcgi-dev missing" "$LINENO" 5
+fi
+
+fi
+
+# This is for keyvalue datastore (and qdbm)
+
+# Check whether --with-keyvalue was given.
+if test "${with_keyvalue+set}" = set; then :
+ withval=$with_keyvalue;
+else
+ with_keyvalue=yes
+fi
+
+
+if test "x${with_keyvalue}" == xyes; then
+ echo "yes keyvalue"
+ # This is for qdbm
# Check whether --with-qdbm was given.
if test "${with_qdbm+set}" = set; then :
withval=$with_qdbm;
fi
-if test "${with_qdbm}"; then
- echo "Using QDBM here: ${with_qdbm}"
- CPPFLAGS="-I${with_qdbm}/include ${CPPFLAGS}"
- LDFLAGS="-L${with_qdbm}/lib ${LDFLAGS}"
-fi
-
-# Problem: depot.h may be in qdbm/depot.h.
-for ac_header in depot.h
+ if test "${with_qdbm}"; then
+ echo "Using QDBM here: ${with_qdbm}"
+ CPPFLAGS="-I${with_qdbm}/include ${CPPFLAGS}"
+ LDFLAGS="-L${with_qdbm}/lib ${LDFLAGS}"
+ fi
+ # Problem: depot.h may be in qdbm/depot.h.
+ for ac_header in depot.h
do :
ac_fn_c_check_header_mongrel "$LINENO" "depot.h" "ac_cv_header_depot_h" "$ac_includes_default"
if test "x$ac_cv_header_depot_h" = xyes; then :
@@ -3907,7 +3978,7 @@ fi
done
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for dpopen in -lqdbm" >&5
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dpopen in -lqdbm" >&5
$as_echo_n "checking for dpopen in -lqdbm... " >&6; }
if ${ac_cv_lib_qdbm_dpopen+:} false; then :
$as_echo_n "(cached) " >&6
@@ -3954,6 +4025,7 @@ else
as_fn_error $? "libqdbm-dev required" "$LINENO" 5
fi
+fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for crypt in -lcrypt" >&5
$as_echo_n "checking for crypt in -lcrypt... " >&6; }
@@ -4179,55 +4251,6 @@ _ACEOF
fi
-# restconf uses libcurl (I think?)
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for curl_global_init in -lcurl" >&5
-$as_echo_n "checking for curl_global_init in -lcurl... " >&6; }
-if ${ac_cv_lib_curl_curl_global_init+:} false; then :
- $as_echo_n "(cached) " >&6
-else
- ac_check_lib_save_LIBS=$LIBS
-LIBS="-lcurl $LIBS"
-cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h. */
-
-/* Override any GCC internal prototype to avoid an error.
- Use char because int might match the return type of a GCC
- builtin and then its argument prototype would still apply. */
-#ifdef __cplusplus
-extern "C"
-#endif
-char curl_global_init ();
-int
-main ()
-{
-return curl_global_init ();
- ;
- return 0;
-}
-_ACEOF
-if ac_fn_c_try_link "$LINENO"; then :
- ac_cv_lib_curl_curl_global_init=yes
-else
- ac_cv_lib_curl_curl_global_init=no
-fi
-rm -f core conftest.err conftest.$ac_objext \
- conftest$ac_exeext conftest.$ac_ext
-LIBS=$ac_check_lib_save_LIBS
-fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_curl_curl_global_init" >&5
-$as_echo "$ac_cv_lib_curl_curl_global_init" >&6; }
-if test "x$ac_cv_lib_curl_curl_global_init" = xyes; then :
- cat >>confdefs.h <<_ACEOF
-#define HAVE_LIBCURL 1
-_ACEOF
-
- LIBS="-lcurl $LIBS"
-
-else
- as_fn_error $? "libcurl missing" "$LINENO" 5
-fi
-
-
for ac_func in inet_aton sigaction sigvec strlcpy strsep strndup alphasort versionsort strverscmp
do :
as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh`
@@ -4241,81 +4264,9 @@ fi
done
-# Lives in libfcgi-dev
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for FCGX_Init in -lfcgi" >&5
-$as_echo_n "checking for FCGX_Init in -lfcgi... " >&6; }
-if ${ac_cv_lib_fcgi_FCGX_Init+:} false; then :
- $as_echo_n "(cached) " >&6
-else
- ac_check_lib_save_LIBS=$LIBS
-LIBS="-lfcgi $LIBS"
-cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h. */
-
-/* Override any GCC internal prototype to avoid an error.
- Use char because int might match the return type of a GCC
- builtin and then its argument prototype would still apply. */
-#ifdef __cplusplus
-extern "C"
-#endif
-char FCGX_Init ();
-int
-main ()
-{
-return FCGX_Init ();
- ;
- return 0;
-}
-_ACEOF
-if ac_fn_c_try_link "$LINENO"; then :
- ac_cv_lib_fcgi_FCGX_Init=yes
-else
- ac_cv_lib_fcgi_FCGX_Init=no
-fi
-rm -f core conftest.err conftest.$ac_objext \
- conftest$ac_exeext conftest.$ac_ext
-LIBS=$ac_check_lib_save_LIBS
-fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_fcgi_FCGX_Init" >&5
-$as_echo "$ac_cv_lib_fcgi_FCGX_Init" >&6; }
-if test "x$ac_cv_lib_fcgi_FCGX_Init" = xyes; then :
- cat >>confdefs.h <<_ACEOF
-#define HAVE_LIBFCGI 1
-_ACEOF
-
- LIBS="-lfcgi $LIBS"
-
-else
- as_fn_error $? "libfcgi-dev missing" "$LINENO" 5
-fi
-# Check if extra keys inserted for database lists containing content. Eg A.n.foo = 3
-# means A.3 $!a=foo exists
-
-# Check whether --enable-keycontent was given.
-if test "${enable_keycontent+set}" = set; then :
- enableval=$enable_keycontent;
- if test "$enableval" = no; then
- ac_enable_keycontent=no
- else
- ac_enable_keycontent=yes
- fi
-
-else
- ac_enable_keycontent=yes
-fi
-
-
-
-if test "$ac_enable_keycontent" = "yes"; then
- $as_echo "#define DB_KEYCONTENT 1" >>confdefs.h
-
-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 include/Makefile etc/Makefile etc/clixonrc example/Makefile example/docker/Makefile docker/Makefile docker/cli/Makefile docker/cli/Dockerfile docker/backend/Makefile docker/backend/Dockerfile docker/netconf/Makefile docker/netconf/Dockerfile datastore/Makefile datastore/keyvalue/Makefile datastore/text/Makefile doc/Makefile"
cat >confcache <<\_ACEOF
# This file is a shell script that caches the results of configure
@@ -5017,7 +4968,6 @@ do
"apps/backend/Makefile") CONFIG_FILES="$CONFIG_FILES apps/backend/Makefile" ;;
"apps/netconf/Makefile") CONFIG_FILES="$CONFIG_FILES apps/netconf/Makefile" ;;
"apps/restconf/Makefile") CONFIG_FILES="$CONFIG_FILES apps/restconf/Makefile" ;;
- "apps/dbctrl/Makefile") CONFIG_FILES="$CONFIG_FILES apps/dbctrl/Makefile" ;;
"include/Makefile") CONFIG_FILES="$CONFIG_FILES include/Makefile" ;;
"etc/Makefile") CONFIG_FILES="$CONFIG_FILES etc/Makefile" ;;
"etc/clixonrc") CONFIG_FILES="$CONFIG_FILES etc/clixonrc" ;;
@@ -5030,6 +4980,9 @@ 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" ;;
+ "datastore/text/Makefile") CONFIG_FILES="$CONFIG_FILES datastore/text/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..c7f30742 100644
--- a/configure.ac
+++ b/configure.ac
@@ -42,7 +42,7 @@ AC_INIT(lib/clixon/clixon.h.in)
: ${CFLAGS="-O2"}
CLIXON_VERSION_MAJOR="3"
-CLIXON_VERSION_MINOR="2"
+CLIXON_VERSION_MINOR="3"
CLIXON_VERSION_PATCH="0"
CLIXON_VERSION="\"${CLIXON_VERSION_MAJOR}.${CLIXON_VERSION_MINOR}.${CLIXON_VERSION_PATCH}\""
# Fix to specific version (eg 3.5) or head (3)
@@ -77,6 +77,8 @@ AC_SUBST(SH_SUFFIX)
AC_SUBST(EXE_SUFFIX)
AC_SUBST(AR)
AC_SUBST(RANLIB)
+AC_SUBST(with_restconf) # If yes, compile apps/restconf
+AC_SUBST(with_keyvalue) # If yes, compile datastore/keyvalue
#
AC_PROG_CC()
@@ -119,22 +121,39 @@ if test "${with_cligen}"; then
LDFLAGS="-L${with_cligen}/lib ${LDFLAGS}"
fi
-
AC_CHECK_HEADERS(cligen/cligen.h,, AC_MSG_ERROR(cligen missing. Try: git clone https://github.com/olofhagsand/cligen.git))
AC_CHECK_LIB(:libcligen.so.${CLIGEN_VERSION}, cligen_init,, AC_MSG_ERROR([CLIgen${CLIGEN_VERSION} missing. Try: git clone https://github.com/olofhagsand/cligen.git]))
-# This is for qdbm
-AC_ARG_WITH(qdbm, [ --with-qdbm=dir Use QDBM here ] )
-if test "${with_qdbm}"; then
- echo "Using QDBM here: ${with_qdbm}"
- CPPFLAGS="-I${with_qdbm}/include ${CPPFLAGS}"
- LDFLAGS="-L${with_qdbm}/lib ${LDFLAGS}"
+# This is for restconf (and fastcgi)
+AC_ARG_WITH([restconf],
+ [AS_HELP_STRING([--without-restconf],[disable support for restconf])],
+ [],
+ [with_restconf=yes])
+if test "x${with_restconf}" == xyes; then
+ # Lives in libfcgi-dev
+ AC_CHECK_LIB(fcgi, FCGX_Init,, AC_MSG_ERROR([libfcgi-dev missing]))
fi
-# Problem: depot.h may be in qdbm/depot.h.
-AC_CHECK_HEADERS(depot.h,,[AC_CHECK_HEADERS(qdbm/depot.h,,AC_MSG_ERROR(libqdbm-dev required))])
-AC_CHECK_LIB(qdbm, dpopen,, AC_MSG_ERROR(libqdbm-dev required))
+# This is for keyvalue datastore (and qdbm)
+AC_ARG_WITH([keyvalue],
+ [AS_HELP_STRING([--without-keyvalue],[disable support for key-value xmldb datastore])],
+ [],
+ [with_keyvalue=yes])
+
+if test "x${with_keyvalue}" == xyes; then
+ echo "yes keyvalue"
+ # This is for qdbm
+ AC_ARG_WITH(qdbm, [ --with-qdbm=dir Use QDBM here, if keyvalue ] )
+ if test "${with_qdbm}"; then
+ echo "Using QDBM here: ${with_qdbm}"
+ CPPFLAGS="-I${with_qdbm}/include ${CPPFLAGS}"
+ LDFLAGS="-L${with_qdbm}/lib ${LDFLAGS}"
+ fi
+ # Problem: depot.h may be in qdbm/depot.h.
+ AC_CHECK_HEADERS(depot.h,,[AC_CHECK_HEADERS(qdbm/depot.h,,AC_MSG_ERROR(libqdbm-dev required))])
+ AC_CHECK_LIB(qdbm, dpopen,, AC_MSG_ERROR(libqdbm-dev required))
+fi
AC_CHECK_LIB(crypt, crypt)
AC_CHECK_HEADERS(crypt.h)
@@ -151,32 +170,8 @@ AC_CHECK_LIB(socket, socket)
AC_CHECK_LIB(nsl, xdr_char)
AC_CHECK_LIB(dl, dlopen)
-# restconf uses libcurl (I think?)
-AC_CHECK_LIB(curl, curl_global_init,, AC_MSG_ERROR([libcurl missing]))
-
AC_CHECK_FUNCS(inet_aton sigaction sigvec strlcpy strsep strndup alphasort versionsort strverscmp)
-# Lives in libfcgi-dev
-AC_CHECK_LIB(fcgi, FCGX_Init,, AC_MSG_ERROR([libfcgi-dev missing]))
-
-# Check if extra keys inserted for database lists containing content. Eg A.n.foo = 3
-# means A.3 $!a=foo exists
-
-AC_ARG_ENABLE(keycontent, [ --disable-keycontent Disable reverse lookup content keys],[
- if test "$enableval" = no; then
- ac_enable_keycontent=no
- else
- ac_enable_keycontent=yes
- fi
- ],[ ac_enable_keycontent=yes])
-
-AH_TEMPLATE([DB_KEYCONTENT],
-[ Check if extra keys inserted for database lists containing content.
- Eg A.n.foo = 3 means A.3 $!a=foo exists])
-if test "$ac_enable_keycontent" = "yes"; then
- AC_DEFINE(DB_KEYCONTENT)
-fi
-
AH_BOTTOM([#include ])
AC_OUTPUT(Makefile
@@ -188,7 +183,6 @@ AC_OUTPUT(Makefile
apps/backend/Makefile
apps/netconf/Makefile
apps/restconf/Makefile
- apps/dbctrl/Makefile
include/Makefile
etc/Makefile
etc/clixonrc
@@ -201,6 +195,9 @@ AC_OUTPUT(Makefile
docker/backend/Dockerfile
docker/netconf/Makefile
docker/netconf/Dockerfile
+ datastore/Makefile
+ datastore/keyvalue/Makefile
+ datastore/text/Makefile
doc/Makefile
)
diff --git a/apps/dbctrl/Makefile.in b/datastore/Makefile.in
similarity index 70%
rename from apps/dbctrl/Makefile.in
rename to datastore/Makefile.in
index 2041e7d7..907321e3 100644
--- a/apps/dbctrl/Makefile.in
+++ b/datastore/Makefile.in
@@ -31,18 +31,24 @@
# ***** END LICENSE BLOCK *****
#
VPATH = @srcdir@
+prefix = @prefix@
+datarootdir = @datarootdir@
srcdir = @srcdir@
top_srcdir = @top_srcdir@
-CC = @CC@
-CFLAGS = @CFLAGS@
-LDFLAGS = @LDFLAGS@
-
-prefix = @prefix@
exec_prefix = @exec_prefix@
bindir = @bindir@
+libdir = @libdir@
+dbdir = @prefix@/db
+mandir = @mandir@
libexecdir = @libexecdir@
localstatedir = @localstatedir@
sysconfdir = @sysconfdir@
+CC = @CC@
+CFLAGS = @CFLAGS@
+LDFLAGS = @LDFLAGS@
+LIBS = @LIBS@
+with_restconf = @with_restconf@
+with_keyvalue = @with_keyvalue@
SH_SUFFIX = @SH_SUFFIX@
CLIXON_MAJOR = @CLIXON_VERSION_MAJOR@
@@ -60,34 +66,21 @@ CPPFLAGS = @CPPFLAGS@
INCLUDES = -I. -I$(top_srcdir)/lib/src -I$(top_srcdir)/lib -I$(top_srcdir)/include -I$(top_srcdir) @INCLUDES@
-SRC =
-OBJS = $(SRC:.c=.o)
+SUBDIRS = text
+ifeq ($(with_keyvalue),yes)
+SUBDIRS += keyvalue
+endif
-APPSRC = dbctrl_main.c
+.PHONY: all clean depend install $(SUBDIRS)
+
+APPSRC = datastore_client.c
APPOBJ = $(APPSRC:.c=.o)
-APPL = clixon_dbctrl
+APPL = datastore_client
-all: $(APPL)
+all: $(SUBDIRS) $(APPL)
-clean:
- rm -f $(OBJS) *.core $(APPL) $(APPOBJ)
-
-distclean: clean
- rm -f Makefile *~ .depend
-
-# Put demon in bin
-# Put other executables in libexec/
-# Also create a libexec/ directory for writeable/temporary files.
-# Put config file in etc/
-install: $(APPL)
- install -d $(DESTDIR)$(bindir)
- install $(APPL) $(DESTDIR)$(bindir)
-
-install-include:
-
-uninstall:
- rm -f $(bindir)/$(APPL)
+-include $(DESTDIR)$(datarootdir)/clixon/clixon.mk
.SUFFIXES:
.SUFFIXES: .c .o
@@ -95,14 +88,41 @@ uninstall:
.c.o:
$(CC) $(INCLUDES) -D__PROGRAM__=\"$(APPL)\" $(CPPFLAGS) $(CFLAGS) -c $<
-$(APPL) : $(APPOBJ) $(OBJS) $(LIBDEPS)
- $(CC) $(LDFLAGS) $(APPOBJ) $(OBJS) $(LIBS) -o $@
+$(APPL) : $(APPOBJ) $(LIBDEPS)
+ $(CC) $(LDFLAGS) $(APPOBJ) $(LIBS) -o $@
-TAGS:
- find . -name '*.[chyl]' -print | etags -
depend:
- $(CC) $(DEPENDFLAGS) @DEFS@ $(INCLUDES) $(CFLAGS) -MM $(SRC) $(APPSRC) > .depend
+ for i in $(SUBDIRS); \
+ do (cd $$i; $(MAKE) $(MFLAGS) $@); done
-#include .depend
+$(SUBDIRS):
+ (cd $@; $(MAKE) $(MFLAGS) all)
+install-include:
+ for i in $(SUBDIRS); \
+ do (cd $$i ; $(MAKE) $(MFLAGS) $@); done;
+
+install: $(APPL)
+ install -d $(DESTDIR)$(bindir)
+ install $(APPL) $(DESTDIR)$(bindir)
+ for i in $(SUBDIRS); \
+ do (cd $$i; $(MAKE) $(MFLAGS) $@); done
+
+uninstall:
+ rm -f $(bindir)/$(APPL)
+ for i in $(SUBDIRS); \
+ do (cd $$i; $(MAKE) $(MFLAGS) $@); done
+
+clean:
+ rm -f *.core $(APPL) $(APPOBJ)
+ 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/README.md b/datastore/README.md
new file mode 100644
index 00000000..a3283c6e
--- /dev/null
+++ b/datastore/README.md
@@ -0,0 +1,94 @@
+# Clixon datastore
+
+The Clixon datastore is a stand-alone XML based datastore. The idea is
+to be able to use different datastores backends with the same
+API. There is currently a key-value plugin based on qdbm and a plain
+text-file datastore.
+
+The datastore is primarily designed to be used by Clixon but can be used
+separately.
+
+A datastore is a dynamic plugin that is loaded at runtime with a
+well-defined API. This means it is possible to create your own
+datastore and plug it in a Clixon backend at runtime.
+
+### The functional API
+```
+int xmldb_plugin_load(clicon_handle h, char *filename);
+int xmldb_plugin_unload(clicon_handle h);
+int xmldb_connect(clicon_handle h);
+int xmldb_disconnect(clicon_handle h);
+int xmldb_getopt(clicon_handle h, char *optname, void **value);
+int xmldb_setopt(clicon_handle h, char *optname, void *value);
+int xmldb_get(clicon_handle h, char *db, char *xpath,
+ cxobj **xtop, cxobj ***xvec, size_t *xlen);
+int xmldb_put(clicon_handle h, char *db, enum operation_type op,
+ char *api_path, cxobj *xt);
+int xmldb_copy(clicon_handle h, char *from, char *to);
+int xmldb_lock(clicon_handle h, char *db, int pid);
+int xmldb_unlock(clicon_handle h, char *db);
+int xmldb_unlock_all(clicon_handle h, int pid);
+int xmldb_islocked(clicon_handle h, char *db);
+int xmldb_exists(clicon_handle h, char *db);
+int xmldb_delete(clicon_handle h, char *db);
+int xmldb_create(clicon_handle h, char *db);
+```
+
+### Using the API
+
+To use the API, a client needs the following:
+- A clicon handle.
+- A datastore plugin, such as a text.so or keyvalue.so. These are normally built and installed at Clixon make.
+- A directory where to store databases
+- A yang specification. This needs to be parsed using the Clixon yang_parse() method.
+
+A client calling the API needs to:
+1. Load a plugin and
+2. Connect to a datastore.
+You can connect to several datastores, even concurrently,
+but in practice in Clixon, you connect to a single store.
+
+After connecting to a datastore, you can create and modify databases
+within the datastore, and set and get options of the datastore itself.
+
+When done, you disconnect from the datastore and unload the plugin.
+
+Within a datastore, the following four databases may exist:
+- running
+- candidate
+- startup
+- tmp
+
+Initially, a database does not exist but is created by
+xmldb_create(). It is deleted by xmldb_delete(). You may check for
+existence with xmldb_exists(). You need to create a database before
+you can perform any data access on it.
+
+You may lock a database for exclusive modification according to
+Netconf semantics. You may also unlock a single dabase, unlock all frm
+a specific session.
+
+You can read a database with xmldb_get() and modify a database with
+xmldb_put(), and xmldb_copy().
+
+A typical datastore session can be as follows, see the source code of
+[datastore_client.c](datastore_client.c) for a more elaborate example.
+
+```
+ h = clicon_handle_init();
+ xmldb_plugin_load(h, plugin);
+ xmldb_connect(h);
+ xmldb_setopt(h, "dbdir", dbdir);
+ xmldb_setopt(h, "yangspec", yspec);
+ /* From here databases in the datastore may be accessed */
+ xmldb_create(h, "candidate");
+ xmldb_copy(h, "running", "candidate");
+ xmldb_lock(h, "candidate", 7878);
+ xmldb_put(h, "candidate", OP_CREATE, "/interfaces/interface=eth0", xml);
+ xmldb_unlock(h, "candidate");
+ xmldb_get(h, "candidate", "/", &xml, &xvec, &xlen);
+ xmldb_disconnect(h)
+ xmdlb_plugin_unload(h);
+```
+
+
diff --git a/datastore/datastore_client.c b/datastore/datastore_client.c
new file mode 100644
index 00000000..d9ceaf61
--- /dev/null
+++ b/datastore/datastore_client.c
@@ -0,0 +1,309 @@
+/*
+ *
+ ***** 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 *****
+
+ * Examples:
+
+./datastore_client -d candidate -b /usr/local/var/routing -p /home/olof/src/clixon/datastore/keyvalue/keyvalue.so -y /usr/local/share/routing/yang -m ietf-ip get /
+
+sudo ./datastore_client -d candidate -b /usr/local/var/routing -p /home/olof/src/clixon/datastore/keyvalue/keyvalue.so -y /usr/local/share/routing/yang -m ietf-ip put merge /interfaces/interface=eth66 'eth66'
+
+sudo ./datastore_client -d candidate -b /usr/local/var/routing -p /home/olof/src/clixon/datastore/keyvalue/keyvalue.so -y /usr/local/share/routing/yang -m ietf-ip put merge / 'eth0true'
+
+ */
+
+#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
+
+/* cligen */
+#include
+
+/* clicon */
+#include
+
+/* Command line options to be passed to getopt(3) */
+#define DATASTORE_OPTS "hDd:p:b:y:m:"
+
+/*! usage
+ */
+static void
+usage(char *argv0)
+{
+ fprintf(stderr, "usage:%s * []\n"
+ "where options are\n"
+ "\t-h\t\tHelp\n"
+ "\t-D\t\tDebug\n"
+ "\t-d \t\tDatabase name. Default: running. Alt: candidate,startup\n"
+ "\t-b \tDatabase directory. Mandatory\n"
+ "\t-p \tDatastore plugin. Mandatory\n"
+ "\t-y \tYang directory (where modules are stored). Mandatory\n"
+ "\t-m \tYang module. Mandatory\n"
+ "and command is either:\n"
+ "\tget \n"
+ "\tput (merge|replace|create|delete|remove) \n"
+ "\tcopy \n"
+ "\tlock \n"
+ "\tunlock\n"
+ "\tunlock_all \n"
+ "\tislocked\n"
+ "\texists\n"
+ "\tdelete\n"
+ "\tinit\n"
+ ,
+ argv0
+ );
+ exit(0);
+}
+
+int
+main(int argc, char **argv)
+{
+ char c;
+ clicon_handle h;
+ char *argv0;
+ char *db = "running";
+ char *plugin = NULL;
+ char *cmd = NULL;
+ yang_spec *yspec = NULL;
+ char *yangdir = NULL;
+ char *yangmodule = NULL;
+ char *dbdir = NULL;
+ int ret;
+ int pid;
+ enum operation_type op;
+ cxobj *xt = NULL;
+
+ /* In the startup, logs to stderr & debug flag set later */
+ clicon_log_init(__PROGRAM__, LOG_INFO, CLICON_LOG_STDERR);
+
+ argv0 = argv[0];
+ /* Defaults */
+ if ((h = clicon_handle_init()) == NULL)
+ goto done;
+ /* getopt in two steps, first find config-file before over-riding options. */
+ while ((c = getopt(argc, argv, DATASTORE_OPTS)) != -1)
+ switch (c) {
+ case '?' :
+ case 'h' : /* help */
+ usage(argv0);
+ break;
+ case 'D' : /* debug */
+ debug = 1;
+ break;
+ case 'd': /* db symbolic: running|candidate|startup */
+ if (!optarg)
+ usage(argv0);
+ db = optarg;
+ break;
+ case 'p': /* datastore plugin */
+ if (!optarg)
+ usage(argv0);
+ plugin = optarg;
+ break;
+ case 'b': /* db directory */
+ if (!optarg)
+ usage(argv0);
+ dbdir = optarg;
+ break;
+ case 'y': /* Yang directory */
+ if (!optarg)
+ usage(argv0);
+ yangdir = optarg;
+ break;
+ case 'm': /* Yang module */
+ if (!optarg)
+ usage(argv0);
+ yangmodule = optarg;
+ break;
+ }
+ /*
+ * Logs, error and debug to stderr, set debug level
+ */
+ clicon_log_init(__PROGRAM__, debug?LOG_DEBUG:LOG_INFO, CLICON_LOG_STDERR);
+ clicon_debug_init(debug, NULL);
+
+ argc -= optind;
+ argv += optind;
+ if (argc < 1)
+ usage(argv0);
+ cmd = argv[0];
+ if (plugin == NULL){
+ clicon_err(OE_DB, 0, "Missing plugin -p option");
+ goto done;
+ }
+ if (dbdir == NULL){
+ clicon_err(OE_DB, 0, "Missing dbdir -b option");
+ goto done;
+ }
+ if (yangdir == NULL){
+ clicon_err(OE_YANG, 0, "Missing yangdir -y option");
+ goto done;
+ }
+ if (yangmodule == NULL){
+ clicon_err(OE_YANG, 0, "Missing yang module -m option");
+ goto done;
+ }
+ /* Load datastore plugin */
+ if (xmldb_plugin_load(h, plugin) < 0)
+ goto done;
+ /* Connect to plugin to get a handle */
+ if (xmldb_connect(h) < 0)
+ goto done;
+ /* Create yang spec */
+ if ((yspec = yspec_new()) == NULL)
+ goto done;
+ /* Parse yang spec from given file */
+ if (yang_parse(h, yangdir, yangmodule, NULL, yspec) < 0)
+ goto done;
+ /* Set database directory option */
+ if (xmldb_setopt(h, "dbdir", dbdir) < 0)
+ goto done;
+ /* Set yang spec option */
+ if (xmldb_setopt(h, "yangspec", yspec) < 0)
+ goto done;
+ if (strcmp(cmd, "get")==0){
+ if (argc != 1 && argc != 2)
+ usage(argv0);
+ if (xmldb_get(h, db, argc==2?argv[1]:"/", &xt, NULL, 0) < 0)
+ goto done;
+ clicon_xml2file(stdout, xt, 0, 0);
+
+ fprintf(stdout, "\n");
+ }
+ else if (strcmp(cmd, "put")==0){
+ if (argc != 3 && argc != 4){
+ clicon_err(OE_DB, 0, "Unexpected nr of args: %d", argc);
+ usage(argv0);
+ }
+ if (xml_operation(argv[1], &op) < 0){
+ clicon_err(OE_DB, 0, "Unrecognized operation: %s", argv[1]);
+ usage(argv0);
+ }
+ if (argc == 4){
+ if (clicon_xml_parse_str(argv[3], &xt) < 0)
+ goto done;
+ if (xml_rootchild(xt, 0, &xt) < 0)
+ goto done;
+ }
+ if (xmldb_put(h, db, op, argv[2], xt) < 0)
+ goto done;
+ }
+ else if (strcmp(cmd, "copy")==0){
+ if (argc != 2)
+ usage(argv0);
+ if (xmldb_copy(h, db, argv[1]) < 0)
+ goto done;
+ }
+ else if (strcmp(cmd, "lock")==0){
+ if (argc != 2)
+ usage(argv0);
+ pid = atoi(argv[1]);
+ if (xmldb_lock(h, db, pid) < 0)
+ goto done;
+ }
+ else if (strcmp(cmd, "unlock")==0){
+ if (argc != 1)
+ usage(argv0);
+ if (xmldb_unlock(h, db) < 0)
+ goto done;
+ }
+ else if (strcmp(cmd, "unlock_all")==0){
+ if (argc != 2)
+ usage(argv0);
+ pid = atoi(argv[1]);
+ if (xmldb_unlock_all(h, pid) < 0)
+ goto done;
+ }
+ else if (strcmp(cmd, "islocked")==0){
+ if (argc != 1)
+ usage(argv0);
+ if ((ret = xmldb_islocked(h, db)) < 0)
+ goto done;
+ fprintf(stdout, "islocked: %d\n", ret);
+ }
+ else if (strcmp(cmd, "exists")==0){
+ if (argc != 1)
+ usage(argv0);
+ if ((ret = xmldb_exists(h, db)) < 0)
+ goto done;
+ fprintf(stdout, "exists: %d\n", ret);
+ }
+ else if (strcmp(cmd, "delete")==0){
+ if (argc != 1)
+ usage(argv0);
+ if (xmldb_delete(h, db) < 0)
+ goto done;
+ }
+ else if (strcmp(cmd, "init")==0){
+ if (argc != 1)
+ usage(argv0);
+ if (xmldb_create(h, db) < 0)
+ goto done;
+ }
+ else{
+ clicon_err(OE_DB, 0, "Unrecognized command: %s", cmd);
+ usage(argv0);
+ }
+ if (xmldb_disconnect(h) < 0)
+ goto done;
+ if (xmldb_plugin_unload(h) < 0)
+ goto done;
+ done:
+ if (xt)
+ xml_free(xt);
+ if (h)
+ clicon_handle_exit(h);
+ if (yspec)
+ yspec_free(yspec);
+ return 0;
+}
+
diff --git a/datastore/keyvalue/Makefile.in b/datastore/keyvalue/Makefile.in
new file mode 100644
index 00000000..9a116997
--- /dev/null
+++ b/datastore/keyvalue/Makefile.in
@@ -0,0 +1,98 @@
+#
+# ***** 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@
+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. -I$(top_srcdir)/lib -I$(top_srcdir)/include -I$(top_srcdir) @INCLUDES@
+
+PLUGIN = $(DATASTORE).so
+
+SRC = clixon_keyvalue.c clixon_qdb.c clixon_chunk.c
+
+OBJS = $(SRC:.c=.o)
+
+all: $(PLUGIN)
+
+-include $(DESTDIR)$(datarootdir)/clixon/clixon.mk
+
+$(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/lib/src/clixon_chunk.c b/datastore/keyvalue/clixon_chunk.c
similarity index 99%
rename from lib/src/clixon_chunk.c
rename to datastore/keyvalue/clixon_chunk.c
index 4d4782a0..bade74fa 100644
--- a/lib/src/clixon_chunk.c
+++ b/datastore/keyvalue/clixon_chunk.c
@@ -36,6 +36,7 @@
ensure errno is set and return -1/NULL */
#include
#include
+#include
#include
#include
#include
@@ -45,7 +46,11 @@
#include
/* clicon */
-#include "clixon_queue.h"
+#include
+
+/* clicon */
+#include
+
#include "clixon_chunk.h"
/*
diff --git a/lib/clixon/clixon_chunk.h b/datastore/keyvalue/clixon_chunk.h
similarity index 100%
rename from lib/clixon/clixon_chunk.h
rename to datastore/keyvalue/clixon_chunk.h
diff --git a/datastore/keyvalue/clixon_keyvalue.c b/datastore/keyvalue/clixon_keyvalue.c
new file mode 100644
index 00000000..68b2527d
--- /dev/null
+++ b/datastore/keyvalue/clixon_keyvalue.c
@@ -0,0 +1,1673 @@
+/*
+ *
+ ***** 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
+ *
+ * dependency on clixon handle:
+ * clixon_xmldb_dir()
+ * clicon_dbspec_yang(h)
+ */
+
+#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
+
+/* cligen */
+#include
+
+/* clicon */
+#include
+
+#include "clixon_chunk.h"
+#include "clixon_qdb.h"
+#include "clixon_keyvalue.h"
+
+#define handle(xh) (assert(kv_handle_check(xh)==0),(struct kv_handle *)(xh))
+
+/* Magic to ensure plugin sanity. */
+#define KV_HANDLE_MAGIC 0xfa61a402
+
+/*! Internal structure of keyvalue datastore handle.
+ */
+struct kv_handle {
+ int kh_magic; /* magic */
+ char *kh_dbdir; /* Directory of database files */
+ yang_spec *kh_yangspec; /* Yang spec if this datastore */
+};
+
+/*! Check struct magic number for sanity checks
+ * return 0 if OK, -1 if fail.
+ */
+static int
+kv_handle_check(xmldb_handle xh)
+{
+ /* Dont use handle macro to avoid recursion */
+ struct kv_handle *kh = (struct kv_handle *)(xh);
+
+ return kh->kh_magic == KV_HANDLE_MAGIC ? 0 : -1;
+}
+
+/*! 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;
+
+/*! Translate from symbolic database name to actual filename in file-system
+ * @param[in] xh XMLDB 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
+kv_db2file(struct kv_handle *kh,
+ 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 = kh->kh_dbdir) == NULL){
+ clicon_err(OE_XML, errno, "dbdir 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, "No such 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 (percent_encode(xml_body(xkey), &bodyenc) < 0)
+ 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 *xn;
+ cxobj *xb;
+
+ /* Check if key node exists */
+ if ((xn = xml_new_spec(keyname, x, ykey)) == NULL)
+ goto done;
+ if ((xb = xml_new("body", xn)) == NULL)
+ goto done;
+ xml_type_set(xb, CX_BODY);
+ xml_value_set(xb, arg);
+ retval = 0;
+ done:
+ 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 (percent_decode(restval, &argdec) < 0)
+ 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 (percent_decode(arg, &argdec) < 0)
+ 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 (percent_decode(arg, &argdec) < 0)
+ 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;
+}
+
+/*! Connect to a datastore plugin
+ * @retval handle Use this handle for other API calls
+ * @retval NULL Error
+ * @note You can do several connects, and have multiple connections to the same
+ * datastore
+ */
+xmldb_handle
+kv_connect(void)
+{
+ struct kv_handle *kh;
+ xmldb_handle xh = NULL;
+ int size;
+
+ size = sizeof(struct kv_handle);
+ if ((kh = malloc(size)) == NULL){
+ clicon_err(OE_UNIX, errno, "malloc");
+ goto done;
+ }
+ memset(kh, 0, size);
+ kh->kh_magic = KV_HANDLE_MAGIC;
+ xh = (xmldb_handle)kh;
+ done:
+ return xh;
+}
+
+/*! Disconnect from a datastore plugin and deallocate handle
+ * @param[in] handle Disconect and deallocate from this handle
+ * @retval 0 OK
+ */
+int
+kv_disconnect(xmldb_handle xh)
+{
+ int retval = -1;
+ struct kv_handle *kh = handle(xh);
+
+ if (kh){
+ if (kh->kh_dbdir)
+ free(kh->kh_dbdir);
+ free(kh);
+ }
+ retval = 0;
+ // done:
+ return retval;
+}
+
+/*! Get value of generic plugin option. Type of value is givenby context
+ * @param[in] xh XMLDB handle
+ * @param[in] optname Option name
+ * @param[out] value Pointer to Value of option
+ * @retval 0 OK
+ * @retval -1 Error
+ */
+int
+kv_getopt(xmldb_handle xh,
+ char *optname,
+ void **value)
+{
+ int retval = -1;
+ struct kv_handle *kh = handle(xh);
+
+ if (strcmp(optname, "yangspec") == 0)
+ *value = kh->kh_yangspec;
+ else if (strcmp(optname, "dbdir") == 0)
+ *value = kh->kh_dbdir;
+ else{
+ clicon_err(OE_PLUGIN, 0, "Option %s not implemented by plugin", optname);
+ goto done;
+ }
+ retval = 0;
+ done:
+ return retval;
+}
+
+/*! Set value of generic plugin option. Type of value is givenby context
+ * @param[in] xh XMLDB handle
+ * @param[in] optname Option name
+ * @param[in] value Value of option
+ * @retval 0 OK
+ * @retval -1 Error
+ */
+int
+kv_setopt(xmldb_handle xh,
+ char *optname,
+ void *value)
+{
+ int retval = -1;
+ struct kv_handle *kh = handle(xh);
+
+ if (strcmp(optname, "yangspec") == 0)
+ kh->kh_yangspec = (yang_spec*)value;
+ else if (strcmp(optname, "dbdir") == 0){
+ if (value && (kh->kh_dbdir = strdup((char*)value)) == NULL){
+ clicon_err(OE_UNIX, 0, "strdup");
+ goto done;
+ }
+ }
+ else{
+ clicon_err(OE_PLUGIN, 0, "Option %s not implemented by plugin", optname);
+ goto done;
+ }
+ retval = 0;
+ done:
+ return retval;
+}
+
+/*! Get content of database using xpath. return a set of matching sub-trees
+ * The function returns a minimal tree that includes all sub-trees that match
+ * xpath.
+ * @param[in] dbname Name of database to search in (filename including dir path
+ * @param[in] xpath String with XPATH syntax. or NULL for all
+ * @param[out] xtop Single XML tree which xvec points to. Free with xml_free()
+ * @param[out] xvec Vector of xml trees. Free after use.
+ * @param[out] xlen Length of vector.
+ * @retval 0 OK
+ * @retval -1 Error
+ * @code
+ * cxobj *xt;
+ * cxobj **xvec;
+ * size_t xlen;
+ * if (xmldb_get(xh, "running", "/interfaces/interface[name="eth"]",
+ * &xt, &xvec, &xlen) < 0)
+ * err;
+ * for (i=0; ikh_yangspec) == NULL){
+ clicon_err(OE_YANG, ENOENT, "No yang spec");
+ goto done;
+ }
+ /* Read in complete database (this can be optimized) */
+ if ((npairs = db_regexp(dbfile, "", __FUNCTION__, &pairs, 0)) < 0)
+ goto done;
+ if ((xt = xml_new_spec("config", NULL, yspec)) == NULL)
+ goto done;
+ /* Translate to complete xml tree */
+ for (i = 0; i < npairs; i++) {
+ if (get(dbfile,
+ yspec,
+ pairs[i].dp_key, /* xml key */
+ pairs[i].dp_val, /* may be NULL */
+ xt) < 0)
+ goto done;
+ }
+ if (xpath_vec(xt, xpath?xpath:"/", &xvec, &xlen) < 0)
+ goto done;
+ /* If vectors are specified then filter out everything else,
+ * otherwise return complete tree.
+ */
+ if (xvec != NULL){
+ for (i=0; i1)
+ clicon_xml2file(stderr, xt, 0, 1);
+ *xtop = xt;
+ retval = 0;
+ done:
+ if (dbfile)
+ free(dbfile);
+ if (xvec)
+ free(xvec);
+ unchunk_group(__FUNCTION__);
+ return retval;
+
+}
+
+/*! Add data to database internal recursive function
+ * @param[in] dbfile 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 *dbfile,
+ 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 (percent_encode(body, &bodyenc) < 0)
+ 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(dbfile, 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(dbfile, xk, body?body:NULL, body?strlen(body)+1:0) < 0)
+ goto done;
+ break;
+ case OP_DELETE:
+ if ((exists = db_exists(dbfile, 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(dbfile, 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(dbfile, 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] kh Keyvalue 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(struct kv_handle *kh,
+ char *db,
+ enum operation_type op,
+ char *xk,
+ char *val)
+
+{
+ int retval = -1;
+ 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;
+
+ // clicon_log(LOG_WARNING, "%s", __FUNCTION__);
+ if ((yspec = kh->kh_yangspec) == NULL){
+ clicon_err(OE_YANG, ENOENT, "No yang spec");
+ goto done;
+ }
+ if (kv_db2file(kh, db, &filename) < 0)
+ goto done;
+ if (xk == NULL || *xk!='/'){
+ clicon_err(OE_DB, 0, "Invalid api_path: %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;
+ /* Remove trailing '/'. Like in /a/ -> /a */
+ if (nvec > 1 && !strlen(vec[nvec-1]))
+ nvec--;
+ 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", 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;
+ }
+ 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 exist 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] kh Keyvalue 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(struct kv_handle *kh,
+ char *db,
+ enum operation_type op,
+ char *xk,
+ cxobj *xt)
+{
+ int retval = -1;
+ yang_stmt *y = NULL;
+ yang_stmt *ykey;
+ char **vec = NULL;
+ int nvec;
+#if 0
+ char **valvec = NULL;
+ int nvalvec;
+ int j;
+ char *restval;
+#endif
+ 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;
+
+ if ((yspec = kh->kh_yangspec) == NULL){
+ clicon_err(OE_YANG, ENOENT, "No yang spec");
+ goto done;
+ }
+ if (kv_db2file(kh, db, &filename) < 0)
+ goto done;
+ if (xk == NULL || *xk!='/'){
+ clicon_err(OE_DB, 0, "Invalid api path: %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;
+ /* Remove trailing '/'. Like in /a/ -> /a */
+ if (nvec > 1 && !strlen(vec[nvec-1]))
+ nvec--;
+ if (nvec < 2){
+ clicon_err(OE_XML, 0, "Malformed key: %s", xk);
+ 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;
+#if 0
+ 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;
+ }
+ j = 0;
+#endif
+ cvi = NULL;
+ /* Iterate over individual yang keys */
+ while ((cvi = cvec_each(cvk, cvi)) != NULL) {
+ keyname = cv_string_get(cvi);
+#if 0
+ if (j)
+ cprintf(ckey, ",");
+ else
+ cprintf(ckey, "=");
+ val2 = valvec[j++];
+ cprintf(ckey, "%s", val2);
+#else
+ // 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);
+#endif
+ 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);
+#if 0
+ if (valvec)
+ free(valvec);
+#endif
+ unchunk_group(__FUNCTION__);
+ return retval;
+}
+
+/*! Modify database provided an xml tree and an operation
+ *
+ * @param[in] xh XMLDB 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(xmldb_handle xh,
+ char *db,
+ enum operation_type op,
+ char *api_path,
+ cxobj *xt)
+{
+ int retval = -1;
+ struct kv_handle *kh = handle(xh);
+ cxobj *x = NULL;
+ yang_stmt *ys;
+ yang_spec *yspec;
+ char *dbfilename = NULL;
+
+ if (xt && (xml_child_nr(xt)==0 || xml_body(xt)!= NULL) &&
+ api_path && strlen(api_path) && strcmp(api_path,"/"))
+ return xmldb_put_xkey(kh, db, op, api_path, xml_body(xt));
+ if ((yspec = kh->kh_yangspec) == NULL){
+ clicon_err(OE_YANG, ENOENT, "No yang spec");
+ goto done;
+ }
+ if (kv_db2file(kh, db, &dbfilename) < 0)
+ goto done;
+ if (op == OP_REPLACE){
+ if (db_delete(dbfilename) < 0)
+ goto done;
+ if (db_init(dbfilename) < 0)
+ goto done;
+ }
+ if (api_path && strlen(api_path) && strcmp(api_path,"/")){
+ // clicon_log(LOG_WARNING, "xmldb_put_restconf_api_path");
+ while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL){
+ if (xmldb_put_restconf_api_path(kh, db, op, api_path, x) < 0)
+ goto done;
+ }
+ }
+ else{
+ // clicon_log(LOG_WARNING, "%s", __FUNCTION__);
+ while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL){
+ 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;
+}
+
+/*! Copy database from db1 to db2
+ * @param[in] xh XMLDB handle
+ * @param[in] from Source database copy
+ * @param[in] to Destination database
+ * @retval -1 Error
+ * @retval 0 OK
+ */
+int
+kv_copy(xmldb_handle xh,
+ char *from,
+ char *to)
+{
+ int retval = -1;
+ struct kv_handle *kh = handle(xh);
+ char *fromfile = NULL;
+ char *tofile = NULL;
+
+ /* XXX lock */
+ if (kv_db2file(kh, from, &fromfile) < 0)
+ goto done;
+ if (kv_db2file(kh, 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] xh XMLDB handle
+ * @param[in] db Database
+ * @param[in] pid Process id
+ * @retval -1 Error
+ * @retval 0 OK
+ */
+int
+kv_lock(xmldb_handle xh,
+ char *db,
+ int pid)
+{
+ int retval = -1;
+ // struct kv_handle *kh = handle(xh);
+ fprintf(stderr, "%s %s %d\n", __FUNCTION__, db, 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;
+ else{
+ clicon_err(OE_DB, 0, "No such database: %s", db);
+ goto done;
+ }
+ clicon_debug(1, "%s: locked by %u", db, pid);
+ fprintf(stderr, "running:%d candidate:%d startup:%d\n",
+ _running_locked, _candidate_locked, _startup_locked);
+ retval = 0;
+ done:
+ return retval;
+}
+
+/*! Unlock database
+ * @param[in] xh XMLDB handle
+ * @param[in] db Database
+ * @param[in] pid Process id
+ * @retval -1 Error
+ * @retval 0 OK
+ * Assume all sanity checks have been made
+ */
+int
+kv_unlock(xmldb_handle xh,
+ char *db)
+{
+ int retval = -1;
+ // struct kv_handle *kh = handle(xh);
+ fprintf(stderr, "%s %s\n", __FUNCTION__, 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;
+ else{
+ clicon_err(OE_DB, 0, "No such database: %s", db);
+ goto done;
+ }
+ retval = 0;
+ done:
+ return retval;
+}
+
+/*! Unlock all databases locked by pid (eg process dies)
+ * @param[in] xh XMLDB handle
+ * @param[in] pid Process / Session id
+ * @retval -1 Error
+ * @retval 0 Ok
+ */
+int
+kv_unlock_all(xmldb_handle xh,
+ int pid)
+{
+ // struct kv_handle *kh = handle(xh);
+
+ if (_running_locked == pid)
+ _running_locked = 0;
+ if (_candidate_locked == pid)
+ _candidate_locked = 0;
+ if (_startup_locked == pid)
+ _startup_locked = 0;
+ return 0;
+}
+
+/*! Check if database is locked
+ * @param[in] xh XMLDB handle
+ * @param[in] db Database
+ * @retval -1 Error
+ * @retval 0 Not locked
+ * @retval >0 Id of locker
+ */
+int
+kv_islocked(xmldb_handle xh,
+ char *db)
+{
+ int retval = -1;
+ // struct kv_handle *kh = handle(xh);
+
+ fprintf(stderr, "%s %s\n", __FUNCTION__, db);
+ fprintf(stderr, "running:%d candidate:%d startup:%d\n",
+ _running_locked, _candidate_locked, _startup_locked);
+ if (strcmp("running", db) == 0)
+ retval = _running_locked;
+ else if (strcmp("candidate", db) == 0)
+ retval = _candidate_locked;
+ else if (strcmp("startup", db) == 0)
+ retval = _startup_locked;
+ else
+ clicon_err(OE_DB, 0, "No such database: %s", db);
+ return retval;
+}
+
+/*! Check if db exists
+ * @param[in] xh XMLDB handle
+ * @param[in] db Database
+ * @retval -1 Error
+ * @retval 0 No it does not exist
+ * @retval 1 Yes it exists
+ */
+int
+kv_exists(xmldb_handle xh,
+ char *db)
+{
+ int retval = -1;
+ struct kv_handle *kh = handle(xh);
+ char *filename = NULL;
+ struct stat sb;
+
+ if (kv_db2file(kh, 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] xh XMLDB handle
+ * @param[in] db Database
+ * @retval -1 Error
+ * @retval 0 OK
+ */
+int
+kv_delete(xmldb_handle xh,
+ char *db)
+{
+ int retval = -1;
+ struct kv_handle *kh = handle(xh);
+ char *filename = NULL;
+
+ if (kv_db2file(kh, db, &filename) < 0)
+ goto done;
+ if (db_delete(filename) < 0)
+ goto done;
+ retval = 0;
+ done:
+ if (filename)
+ free(filename);
+ return retval;
+}
+
+/*! Create / Initialize database
+ * @param[in] xh XMLDB handle
+ * @param[in] db Database
+ * @retval 0 OK
+ * @retval -1 Error
+ */
+int
+kv_create(xmldb_handle xh,
+ char *db)
+{
+ int retval = -1;
+ struct kv_handle *kh = handle(xh);
+ char *filename = NULL;
+
+ if (kv_db2file(kh, 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
+kv_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,
+ kv_plugin_exit,
+ kv_connect,
+ kv_disconnect,
+ kv_getopt,
+ kv_setopt,
+ kv_get,
+ kv_put,
+ kv_copy,
+ kv_lock,
+ kv_unlock,
+ kv_unlock_all,
+ kv_islocked,
+ kv_exists,
+ kv_delete,
+ kv_create,
+};
+
+
+#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 keyvalue -I. -I../../lib ./clixon_keyvalue.c clixon_chunk.c clixon_qdb.c -lclixon -lcligen -lqdbm
+*/
+
+/*! 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
+main(int argc,
+ char **argv)
+{
+ int retval = -1;
+ int npairs;
+ struct db_pair *pairs;
+ char *rxkey = NULL;
+ char *dbfilename;
+
+ if (argc != 2 && argc != 3){
+ fprintf(stderr, "usage: %s [rxkey]\n", argv[0]);
+ goto done;
+ }
+ dbfilename = argv[1];
+ if (argc == 3)
+ rxkey = argv[2];
+ else
+ rxkey = "^.*$"; /* Default is match all */
+
+ /* Get all keys/values for vector */
+ if ((npairs = db_regexp(dbfilename, rxkey, __FUNCTION__, &pairs, 0)) < 0)
+ goto done;
+
+ for (npairs--; npairs >= 0; npairs--)
+ fprintf(stdout, "%s %s\n", pairs[npairs].dp_key,
+ pairs[npairs].dp_val?pairs[npairs].dp_val:"");
+ retval = 0;
+ done:
+ unchunk_group(__FUNCTION__);
+ return retval;
+}
+
+#endif /* Test program */
diff --git a/lib/clixon/clixon_proc.h b/datastore/keyvalue/clixon_keyvalue.h
similarity index 67%
rename from lib/clixon/clixon_proc.h
rename to datastore/keyvalue/clixon_keyvalue.h
index 2a85d9b0..88ae2621 100644
--- a/lib/clixon/clixon_proc.h
+++ b/datastore/keyvalue/clixon_keyvalue.h
@@ -31,16 +31,26 @@
***** END LICENSE BLOCK *****
+ Key-value store
*/
-
-#ifndef _CLIXON_PROC_H_
-#define _CLIXON_PROC_H_
+#ifndef _CLIXON_KEYVALUE_H
+#define _CLIXON_KEYVALUE_H
/*
* Prototypes
- */
-int clicon_proc_run (char *, void (outcb)(char *), int doerr);
-int clicon_proc_daemon (char *);
-int group_name2gid(char *name, gid_t *gid);
+ */
+int kv_get(xmldb_handle h, char *db, char *xpath,
+ cxobj **xtop, cxobj ***xvec, size_t *xlen);
+int kv_put(xmldb_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(xmldb_handle h, char *from, char *to);
+int kv_lock(xmldb_handle h, char *db, int pid);
+int kv_unlock(xmldb_handle h, char *db);
+int kv_unlock_all(xmldb_handle h, int pid);
+int kv_islocked(xmldb_handle h, char *db);
+int kv_exists(xmldb_handle h, char *db);
+int kv_delete(xmldb_handle h, char *db);
+int kv_init(xmldb_handle h, char *db);
-#endif /* _CLIXON_PROC_H_ */
+#endif /* _CLIXON_KEYVALUE_H */
diff --git a/lib/src/clixon_qdb.c b/datastore/keyvalue/clixon_qdb.c
similarity index 99%
rename from lib/src/clixon_qdb.c
rename to datastore/keyvalue/clixon_qdb.c
index 9b716951..5bfa19a5 100644
--- a/lib/src/clixon_qdb.c
+++ b/datastore/keyvalue/clixon_qdb.c
@@ -78,9 +78,8 @@
#include
/* clicon */
-#include "clixon_log.h"
-#include "clixon_err.h"
-#include "clixon_queue.h"
+#include
+
#include "clixon_chunk.h"
#include "clixon_qdb.h"
@@ -425,7 +424,7 @@ db_regexp(char *file,
/* Retrieve value if required */
if ( ! noval) {
if((val = dpget(iterdp, key, -1, 0, -1, &vlen)) == NULL) {
- clicon_log(OE_DB, "%s: dpget: %s", __FUNCTION__, dperrmsg(dpecode));
+ clicon_log(LOG_WARNING, "%s: dpget: %s", __FUNCTION__, dperrmsg(dpecode));
goto quit;
}
}
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/datastore/text/Makefile.in b/datastore/text/Makefile.in
new file mode 100644
index 00000000..7b7cf6e9
--- /dev/null
+++ b/datastore/text/Makefile.in
@@ -0,0 +1,97 @@
+#
+# ***** 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 = text
+CPPFLAGS = @CPPFLAGS@
+
+INCLUDES = -I. -I$(top_srcdir)/lib -I$(top_srcdir)/include -I$(top_srcdir) @INCLUDES@
+
+PLUGIN = $(DATASTORE).so
+
+SRC = clixon_xmldb_text.c
+
+OBJS = $(SRC:.c=.o)
+
+all: $(PLUGIN)
+
+-include $(DESTDIR)$(datarootdir)/clixon/clixon.mk
+
+$(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/text/clixon_xmldb_text.c b/datastore/text/clixon_xmldb_text.c
new file mode 100644
index 00000000..6ef443e8
--- /dev/null
+++ b/datastore/text/clixon_xmldb_text.c
@@ -0,0 +1,1377 @@
+/*
+ *
+ ***** 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 *****
+ */
+
+#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
+
+/* cligen */
+#include
+
+/* clicon */
+#include
+
+#include "clixon_xmldb_text.h"
+
+#define handle(xh) (assert(text_handle_check(xh)==0),(struct text_handle *)(xh))
+
+/* Magic to ensure plugin sanity. */
+#define TEXT_HANDLE_MAGIC 0x7f54da29
+
+/*! Internal structure of text datastore handle.
+ */
+struct text_handle {
+ int th_magic; /* magic */
+ char *th_dbdir; /* Directory of database files */
+ yang_spec *th_yangspec; /* Yang spec if this datastore */
+};
+
+/*! Check struct magic number for sanity checks
+ * return 0 if OK, -1 if fail.
+ */
+static int
+text_handle_check(xmldb_handle xh)
+{
+ /* Dont use handle macro to avoid recursion */
+ struct text_handle *th = (struct text_handle *)(xh);
+
+ return th->th_magic == TEXT_HANDLE_MAGIC ? 0 : -1;
+}
+
+/*! 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.
+ * @note This should probably be on file-system
+ */
+static int _running_locked = 0;
+static int _candidate_locked = 0;
+static int _startup_locked = 0;
+
+/*! Translate from symbolic database name to actual filename in file-system
+ * @param[in] th text handle 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
+text_db2file(struct text_handle *th,
+ 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 = th->th_dbdir) == NULL){
+ clicon_err(OE_XML, errno, "dbdir 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, "No such 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;
+}
+
+/*! Connect to a datastore plugin
+ * @retval handle Use this handle for other API calls
+ * @retval NULL Error
+ */
+xmldb_handle
+text_connect(void)
+{
+ struct text_handle *th;
+ xmldb_handle xh = NULL;
+ int size;
+
+ size = sizeof(struct text_handle);
+ if ((th = malloc(size)) == NULL){
+ clicon_err(OE_UNIX, errno, "malloc");
+ goto done;
+ }
+ memset(th, 0, size);
+ th->th_magic = TEXT_HANDLE_MAGIC;
+ xh = (xmldb_handle)th;
+ done:
+ return xh;
+
+}
+
+/*! Disconnect from to a datastore plugin and deallocate handle
+ * @param[in] xh XMLDB handle, disconect and deallocate from this handle
+ * @retval 0 OK
+ */
+int
+text_disconnect(xmldb_handle xh)
+{
+ int retval = -1;
+ struct text_handle *th = handle(xh);
+
+ if (th){
+ if (th->th_dbdir)
+ free(th->th_dbdir);
+ free(th);
+ }
+ retval = 0;
+ // done:
+ return retval;
+}
+
+/*! Get value of generic plugin option. Type of value is givenby context
+ * @param[in] xh XMLDB handle
+ * @param[in] optname Option name
+ * @param[out] value Pointer to Value of option
+ * @retval 0 OK
+ * @retval -1 Error
+ */
+int
+text_getopt(xmldb_handle xh,
+ char *optname,
+ void **value)
+{
+ int retval = -1;
+ struct text_handle *th = handle(xh);
+
+ if (strcmp(optname, "yangspec") == 0)
+ *value = th->th_yangspec;
+ else if (strcmp(optname, "dbdir") == 0)
+ *value = th->th_dbdir;
+ else{
+ clicon_err(OE_PLUGIN, 0, "Option %s not implemented by plugin", optname);
+ goto done;
+ }
+ retval = 0;
+ done:
+ return retval;
+}
+
+/*! Set value of generic plugin option. Type of value is givenby context
+ * @param[in] xh XMLDB handle
+ * @param[in] optname Option name
+ * @param[in] value Value of option
+ * @retval 0 OK
+ * @retval -1 Error
+ */
+int
+text_setopt(xmldb_handle xh,
+ char *optname,
+ void *value)
+{
+ int retval = -1;
+ struct text_handle *th = handle(xh);
+
+ if (strcmp(optname, "yangspec") == 0)
+ th->th_yangspec = (yang_spec*)value;
+ else if (strcmp(optname, "dbdir") == 0){
+ if (value && (th->th_dbdir = strdup((char*)value)) == NULL){
+ clicon_err(OE_UNIX, 0, "strdup");
+ goto done;
+ }
+ }
+ else{
+ clicon_err(OE_PLUGIN, 0, "Option %s not implemented by plugin", optname);
+ goto done;
+ }
+ retval = 0;
+ done:
+ return retval;
+}
+
+/*! Populate with spec
+ * @param[in] xt XML tree with some node marked
+ */
+int
+xml_spec_populate(cxobj *x,
+ void *arg)
+{
+ int retval = -1;
+ yang_spec *yspec = (yang_spec*)arg;
+ char *name;
+ yang_stmt *y; /* yang node */
+ cxobj *xp; /* xml parent */
+ yang_stmt *yp; /* parent yang */
+
+ name = xml_name(x);
+ if ((xp = xml_parent(x)) != NULL &&
+ (yp = xml_spec(xp)) != NULL)
+ y = yang_find_syntax((yang_node*)yp, xml_name(x));
+ else
+ y = yang_find_topnode(yspec, name); /* still NULL for config */
+ xml_spec_set(x, y);
+ retval = 0;
+ // done:
+ return retval;
+}
+
+/*! Get content of database using xpath. return a set of matching sub-trees
+ * The function returns a minimal tree that includes all sub-trees that match
+ * xpath.
+ * @param[in] xh XMLDB handle
+ * @param[in] dbname Name of database to search in (filename including dir path
+ * @param[in] xpath String with XPATH syntax. or NULL for all
+ * @param[out] xtop Single XML tree which xvec points to. Free with xml_free()
+ * @param[out] xvec Vector of xml trees. Free after use.
+ * @param[out] xlen Length of vector.
+ * @retval 0 OK
+ * @retval -1 Error
+ * @code
+ * cxobj *xt;
+ * cxobj **xvec;
+ * size_t xlen;
+ * if (xmldb_get(xh, "running", "/interfaces/interface[name="eth"]",
+ * &xt, &xvec, &xlen) < 0)
+ * err;
+ * for (i=0; ith_yangspec) == NULL){
+ clicon_err(OE_YANG, ENOENT, "No yang spec");
+ goto done;
+ }
+ if ((fd = open(dbfile, O_RDONLY)) < 0){
+ clicon_err(OE_UNIX, errno, "open(%s)", dbfile);
+ goto done;
+ }
+ /* Parse file into XML tree */
+ if ((clicon_xml_parse_file(fd, &xt, "")) < 0)
+ goto done;
+ /* Always assert a top-level called "config".
+ To ensure that, deal with two cases:
+ 1. File is empty -> rename top-level to "config" */
+ if (xml_child_nr(xt) == 0){
+ if (xml_name_set(xt, "config") < 0)
+ goto done;
+ }
+ /* 2. File is not empty ... -> replace root */
+ else{
+ assert(xml_child_nr(xt)==1);
+ if (xml_rootchild(xt, 0, &xt) < 0)
+ goto done;
+ }
+ /* XXX Maybe the below is general function and should be moved to xmldb? */
+ if (xpath_vec(xt, xpath?xpath:"/", &xvec, &xlen) < 0)
+ goto done;
+
+ /* If vectors are specified then filter out everything else,
+ * otherwise return complete tree.
+ */
+ if (xvec != NULL){
+ for (i=0; i1)
+ clicon_xml2file(stderr, xt, 0, 1);
+ if (xvec0 && xlen0){
+ *xvec0 = xvec;
+ xvec = NULL;
+ *xlen0 = xlen;
+ xlen = 0;
+ }
+ *xtop = xt;
+ xt = NULL;
+ retval = 0;
+ done:
+ if (xt)
+ xml_free(xt);
+ if (dbfile)
+ free(dbfile);
+ if (xvec)
+ free(xvec);
+ if (fd != -1)
+ close(fd);
+ return retval;
+}
+
+/*! Check if child with fullmatch exists
+ * param[in] cvk vector of index keys
+*/
+static cxobj *
+find_keys_vec(cxobj *xt,
+ char *name,
+ cvec *cvk,
+ char **valvec)
+{
+ cxobj *xi = NULL;
+ int j;
+ char *keyname;
+ char *val;
+ cg_var *cvi;
+ char *body;
+
+ while ((xi = xml_child_each(xt, xi, CX_ELMNT)) != NULL)
+ if (strcmp(xml_name(xi), name) == 0){
+ j = 0;
+ cvi = NULL;
+ while ((cvi = cvec_each(cvk, cvi)) != NULL) {
+ keyname = cv_string_get(cvi);
+ val = valvec[j++];
+ if ((body = xml_find_body(xi, keyname)) == NULL)
+ break;
+ if (strcmp(body, val))
+ break;
+ }
+ /* All keys must match: loop terminates. */
+ if (cvi==NULL)
+ return xi;
+ }
+ return NULL;
+}
+
+/*! Create 'modification' tree from api-path, ie fill in xml tree from the path
+ * @param[in] api_path api-path expression
+ * @param[in] xt XML tree. Find api-path (or create) in this tree
+ * @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc
+ * @param[in] yspec Yang spec
+ * @param[out] xp Resulting xml tree corresponding to xt
+ * @param[out] xparp Parent of xp (xp can be NULL)
+ * @param[out] yp Yang spec matching xp
+ * @see xmldb_put_xkey for example
+ */
+static int
+text_apipath_modify(char *api_path,
+ cxobj *xt,
+ enum operation_type op,
+ yang_spec *yspec,
+ cxobj **xp,
+ cxobj **xparp,
+ yang_node **yp)
+{
+ int retval = -1;
+ char **vec = NULL;
+ int nvec;
+ int i;
+ int j;
+ char *name;
+ char *restval;
+ yang_stmt *y = NULL;
+ yang_stmt *ykey;
+ cxobj *x = NULL;
+ cxobj *xpar = NULL;
+ cxobj *xn = NULL; /* new */
+ cxobj *xb; /* body */
+ cvec *cvk = NULL; /* vector of index keys */
+ char **valvec = NULL;
+ int nvalvec;
+ cg_var *cvi;
+ char *keyname;
+ char *val2;
+
+ x = xt;
+ xpar = xml_parent(xt);
+ if (api_path == NULL || *api_path!='/'){
+ clicon_err(OE_DB, 0, "Invalid key: %s", api_path);
+ goto done;
+ }
+ if ((vec = clicon_strsep(api_path, "/", &nvec)) == NULL)
+ goto done;
+ /* Remove trailing '/'. Like in /a/ -> /a */
+ if (nvec > 1 && !strlen(vec[nvec-1]))
+ nvec--;
+ if (nvec < 1){
+ clicon_err(OE_XML, 0, "Malformed key: %s", api_path);
+ goto done;
+ }
+ i = 1;
+ while (i name:x restval=1,2 */
+ if ((restval = index(name, '=')) != NULL){
+ *restval = '\0';
+ restval++;
+ }
+ if (y == NULL) /* top-node */
+ y = yang_find_topnode(yspec, name);
+ else
+ y = yang_find_syntax((yang_node*)y, name);
+ if (y == NULL){
+ clicon_err(OE_YANG, errno, "No yang node found: %s", name);
+ goto done;
+ }
+ i++;
+ switch (y->ys_keyword){
+ case Y_LEAF_LIST:
+ if (restval==NULL){
+ clicon_err(OE_XML, 0, "malformed key, expected '='");
+ goto done;
+ }
+ /* See if it exists */
+ xn = NULL;
+ while ((xn = xml_child_each(x, xn, CX_ELMNT)) != NULL)
+ if (strcmp(name, xml_name(xn)) == 0 &&
+ strcmp(xml_body(xn),restval)==0)
+ break;
+ if (xn == NULL){ /* Not found, does not exist */
+ switch (op){
+ case OP_DELETE: /* not here, should be here */
+ clicon_err(OE_XML, 0, "Object to delete does not exist");
+ goto done;
+ break;
+ case OP_REMOVE:
+ goto ok; /* not here, no need to remove */
+ break;
+ case OP_CREATE:
+ if (i==nvec) /* Last, dont create here */
+ break;
+ default:
+ //XXX create_keyvalues(cxobj *x,
+ if ((xn = xml_new_spec(y->ys_argument, x, y)) == NULL)
+ goto done;
+ // xml_type_set(xn, CX_ELMNT);
+ if ((xb = xml_new("body", xn)) == NULL)
+ goto done;
+ xml_type_set(xb, CX_BODY);
+ if (xml_value_set(xb, restval) < 0)
+ goto done;
+ break;
+ }
+ }
+ xpar = x;
+ x = xn;
+ break;
+ case Y_LIST:
+ /* Get the yang list key */
+ 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;
+ /* Check if exists, if not, create */
+ if ((xn = find_keys_vec(x, name, cvk, valvec)) == NULL){
+ /* create them, but not if delete op */
+ switch (op){
+ case OP_DELETE: /* not here, should be here */
+ clicon_err(OE_XML, 0, "Object to delete does not exist");
+ goto done;
+ break;
+ case OP_REMOVE:
+ goto ok; /* not here, no need to remove */
+ break;
+ default:
+ if ((xn = xml_new(name, x)) == NULL)
+ goto done;
+ xml_type_set(xn, CX_ELMNT);
+ break;
+ }
+ xpar = x;
+ x = xn;
+ j = 0;
+ while ((cvi = cvec_each(cvk, cvi)) != NULL) {
+ keyname = cv_string_get(cvi);
+ val2 = valvec[j++];
+ if ((xn = xml_new(keyname, x)) == NULL)
+ goto done;
+ xml_type_set(xn, CX_ELMNT);
+ if ((xb = xml_new("body", xn)) == NULL)
+ goto done;
+ xml_type_set(xb, CX_BODY);
+ if (xml_value_set(xb, val2) <0)
+ goto done;
+ }
+ }
+ else{
+ xpar = x;
+ x = xn;
+ }
+ if (cvk){
+ cvec_free(cvk);
+ cvk = NULL;
+ }
+ break;
+ default: /* eg Y_CONTAINER, Y_LEAF */
+ if ((xn = xml_find(x, name)) == NULL){
+ switch (op){
+ case OP_DELETE: /* not here, should be here */
+ clicon_err(OE_XML, 0, "Object to delete does not exist");
+ goto done;
+ break;
+ case OP_REMOVE:
+ goto ok; /* not here, no need to remove */
+ break;
+ case OP_CREATE:
+ if (i==nvec) /* Last, dont create here */
+ break;
+ default:
+ if ((xn = xml_new(name, x)) == NULL)
+ goto done;
+ xml_type_set(xn, CX_ELMNT);
+ break;
+ }
+ }
+ else{
+ if (op==OP_CREATE && i==nvec){ /* here, should not be here */
+ clicon_err(OE_XML, 0, "Object to create already exists");
+ goto done;
+ }
+ }
+ xpar = x;
+ x = xn;
+ break;
+ }
+ }
+ *xp = x;
+ *xparp = xpar;
+ *yp = (yang_node*)y;
+ ok:
+ retval = 0;
+ done:
+ if (vec)
+ free(vec);
+ if (valvec)
+ free(valvec);
+ return retval;
+}
+
+/*! Given a modification tree, check existing matching child in the base tree
+ * param[in] x0 Base tree node
+ * param[in] x1c Modification tree child
+ * param[in] yc Yang spec of tree child
+*/
+static cxobj *
+match_base_child(cxobj *x0,
+ cxobj *x1c,
+ yang_stmt *yc)
+{
+ cxobj *x0c = NULL;
+ char *keyname;
+ cvec *cvk = NULL;
+ cg_var *cvi;
+ char *b0;
+ char *b1;
+ yang_stmt *ykey;
+ char *cname;
+ int ok;
+ char *x1bstr; /* body string */
+
+ cname = xml_name(x1c);
+ switch (yc->ys_keyword){
+ case Y_LEAF_LIST: /* Match with name and value */
+ x1bstr = xml_body(x1c);
+ x0c = NULL;
+ while ((x0c = xml_child_each(x0, x0c, CX_ELMNT)) != NULL) {
+ if (strcmp(cname, xml_name(x0c)) == 0 &&
+ strcmp(xml_body(x0c), x1bstr)==0)
+ break;
+ }
+ break;
+ case Y_LIST: /* Match with key values */
+ if ((ykey = yang_find((yang_node*)yc, Y_KEY, NULL)) == NULL){
+ clicon_err(OE_XML, errno, "%s: List statement \"%s\" has no key",
+ __FUNCTION__, yc->ys_argument);
+ goto done;
+ }
+ /* The value is a list of keys: [ ]* */
+ if ((cvk = yang_arg2cvec(ykey, " ")) == NULL)
+ goto done;
+ x0c = NULL;
+ while ((x0c = xml_child_each(x0, x0c, CX_ELMNT)) != NULL) {
+ if (strcmp(xml_name(x0c), cname))
+ continue;
+ cvi = NULL;
+ ok = 0;
+ while ((cvi = cvec_each(cvk, cvi)) != NULL) {
+ keyname = cv_string_get(cvi);
+ ok = 1; /* if we come here */
+ if ((b0 = xml_find_body(x0c, keyname)) == NULL)
+ break; /* error case */
+ if ((b1 = xml_find_body(x1c, keyname)) == NULL)
+ break; /* error case */
+ if (strcmp(b0, b1))
+ break;
+ ok = 2; /* and reaches here for all keynames, x0c is found. */
+ }
+ if (ok == 2)
+ break;
+ }
+ break;
+ default: /* Just match with name */
+ x0c = xml_find(x0, cname);
+ break;
+ }
+ done:
+ if (cvk)
+ cvec_free(cvk);
+ return x0c;
+}
+
+/*! Modify a base tree x0 with x1 with yang spec y according to operation op
+ * @param[in] x0 Base xml tree (can be NULL in add scenarios)
+ * @param[in] x0p Parent of x0
+ * @param[in] x1 xml tree which modifies base
+ * @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc
+ * @param[in] y Yang spec corresponding to xml-node x0. NULL if x0 is NULL
+ * @param[in] yspec Top-level yang spec (if y is NULL)
+ * Assume x0 and x1 are same on entry and that y is the spec
+ * @see put in clixon_keyvalue.c
+ */
+static int
+text_modify(cxobj *x0,
+ cxobj *x0p,
+ cxobj *x1,
+ enum operation_type op,
+ yang_node *y,
+ yang_spec *yspec)
+{
+ int retval = -1;
+ char *opstr;
+ char *name;
+ char *cname; /* child name */
+ cxobj *x0c; /* base child */
+ cxobj *x0b; /* base body */
+ cxobj *x1c; /* mod child */
+ char *x1bstr; /* mod body string */
+ yang_stmt *yc; /* yang child */
+
+ clicon_debug(1, "%s %s", __FUNCTION__, x0?xml_name(x0):"");
+ /* Check for operations embedded in tree according to netconf */
+ if (x1 && (opstr = xml_find_value(x1, "operation")) != NULL)
+ if (xml_operation(opstr, &op) < 0)
+ goto done;
+ if (x1 == NULL){
+ switch(op){
+ case OP_REPLACE:
+ if (x0)
+ xml_purge(x0);
+ case OP_CREATE:
+ case OP_MERGE:
+ break;
+ default:
+ break;
+ }
+ }
+ else {
+ assert(xml_type(x1) == CX_ELMNT);
+ name = xml_name(x1);
+ if (y && (y->yn_keyword == Y_LEAF_LIST || y->yn_keyword == Y_LEAF)){
+ x1bstr = xml_body(x1);
+ switch(op){
+ case OP_CREATE:
+ if (x0){
+ clicon_err(OE_XML, 0, "Object to create already exists");
+ goto done;
+ }
+ /* Fall thru */
+ case OP_NONE: /* XXX */
+ case OP_MERGE:
+ case OP_REPLACE:
+ if (x0==NULL){
+ if ((x0 = xml_new_spec(name, x0p, y)) == NULL)
+ goto done;
+ if (op==OP_NONE)
+ xml_flag_set(x0, XML_FLAG_NONE); /* Mark for potential deletion */
+ if (x1bstr){ /* empty type does not have body */
+ if ((x0b = xml_new("body", x0)) == NULL)
+ goto done;
+ xml_type_set(x0b, CX_BODY);
+ }
+ }
+ if (x1bstr){
+ if ((x0b = xml_body_get(x0)) == NULL){
+ if ((x0b = xml_new("body", x0)) == NULL)
+ goto done;
+ xml_type_set(x0b, CX_BODY);
+ }
+ if (xml_value_set(x0b, x1bstr) < 0)
+ goto done;
+ }
+ break;
+ case OP_DELETE:
+ if (x0==NULL){
+ clicon_err(OE_XML, 0, "Object to delete does not exist");
+ goto done;
+ }
+ case OP_REMOVE:
+ if (x0)
+ xml_purge(x0);
+ break;
+ default:
+ break;
+ } /* switch op */
+ } /* if LEAF|LEAF_LIST */
+ else { /* eg Y_CONTAINER */
+ switch(op){
+ case OP_CREATE:
+ /* top-level object is a special case, ie when
+ * x0 parent is NULL
+ * or x1 is empty
+ */
+ if ((x0p && x0) ||
+ (x0p==NULL && xml_child_nr(x1) == 0)){
+ clicon_err(OE_XML, 0, "Object to create already exists");
+ goto done;
+ }
+ case OP_REPLACE:
+ /* top-level object is a special case, ie when
+ * x0 parent is NULL,
+ * or x1 is empty
+ */
+ if ((x0p && x0) ||
+ (x0p==NULL && xml_child_nr(x1) == 0)){
+ xml_purge(x0);
+ x0 = NULL;
+ }
+ case OP_NONE: /* XXX */
+ case OP_MERGE:
+ if (x0==NULL){
+ if ((x0 = xml_new_spec(name, x0p, y)) == NULL)
+ goto done;
+ if (op==OP_NONE)
+ xml_flag_set(x0, XML_FLAG_NONE); /* Mark for potential deletion */
+ }
+
+ /* Loop through children of the modification tree */
+ x1c = NULL;
+ while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) {
+ cname = xml_name(x1c);
+ /* Get yang spec of the child */
+ if (y == NULL)
+ yc = yang_find_topnode(yspec, cname); /* still NULL for config */
+ else{
+ if ((yc = yang_find_syntax(y, cname)) == NULL){
+ clicon_err(OE_YANG, errno, "No yang node found: %s", cname);
+ goto done;
+ }
+ }
+ /* See if there is a corresponding node in the base tree */
+ x0c = yc?match_base_child(x0, x1c, yc):NULL;
+ if (text_modify(x0c, x0, x1c, op, (yang_node*)yc, yspec) < 0)
+ goto done;
+ }
+ break;
+ case OP_DELETE:
+ if (x0==NULL){
+ clicon_err(OE_XML, 0, "Object to delete does not exist");
+ goto done;
+ }
+ case OP_REMOVE:
+ if (x0)
+ xml_purge(x0);
+ break;
+ default:
+ break;
+ } /* CONTAINER switch op */
+ } /* else Y_CONTAINER */
+ } /* x1 != NULL */
+ // ok:
+ retval = 0;
+ done:
+ return retval;
+}
+
+/*! Modify database provided an xml tree and an operation
+ *
+ * @param[in] xh XMLDB 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] xadd xml-tree to merge/replace. Top-level symbol is 'config'.
+ * Should be empty or '' if delete?
+ * @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, "/", xt) < 0)
+ * err;
+ * @endcode
+ */
+int
+text_put(xmldb_handle xh,
+ char *db,
+ enum operation_type op,
+ char *api_path,
+ cxobj *xmod)
+{
+ int retval = -1;
+ struct text_handle *th = handle(xh);
+ char *dbfile = NULL;
+ int fd = -1;
+ cbuf *cb = NULL;
+ cbuf *xpcb = NULL; /* xpath cbuf */
+ yang_spec *yspec;
+ cxobj *xt = NULL;
+ cxobj *xbase = NULL;
+ cxobj *xbasep = NULL; /* parent */
+ cxobj *xc;
+ cxobj *xnew = NULL;
+ yang_node *y = NULL;
+
+#if 0 /* Just ignore */
+ if ((op==OP_DELETE || op==OP_REMOVE) && xmod){
+ clicon_err(OE_XML, 0, "xml tree should be NULL for REMOVE/DELETE");
+ goto done;
+ }
+#endif
+ if (text_db2file(th, db, &dbfile) < 0)
+ goto done;
+ if (dbfile==NULL){
+ clicon_err(OE_XML, 0, "dbfile NULL");
+ goto done;
+ }
+ if ((yspec = th->th_yangspec) == NULL){
+ clicon_err(OE_YANG, ENOENT, "No yang spec");
+ goto done;
+ }
+ if ((fd = open(dbfile, O_RDONLY)) < 0) {
+ clicon_err(OE_UNIX, errno, "open(%s)", dbfile);
+ goto done;
+ }
+ /* Parse file into XML tree */
+ if ((clicon_xml_parse_file(fd, &xt, "")) < 0)
+ goto done;
+ /* Always assert a top-level called "config".
+ To ensure that, deal with two cases:
+ 1. File is empty -> rename top-level to "config" */
+ if (xml_child_nr(xt) == 0){
+ if (xml_name_set(xt, "config") < 0)
+ goto done;
+ }
+ /* 2. File is not empty ... -> replace root */
+ else{
+ assert(xml_child_nr(xt)==1);
+ if (xml_rootchild(xt, 0, &xt) < 0)
+ goto done;
+ }
+ /* here xt looks like: ... */
+ /* If xpath find first occurence or api-path (this is where we apply xml) */
+ if (api_path){
+ if (text_apipath_modify(api_path, xt, op, yspec, &xbase, &xbasep, &y) < 0)
+ goto done;
+ }
+ else{
+ xbase = xt; /* defer y since x points to config */
+ xbasep = xml_parent(xt); /* NULL */
+ assert(strcmp(xml_name(xbase),"config")==0);
+ }
+
+ /*
+ * Modify base tree x with modification xmod
+ */
+ if (op == OP_DELETE || op == OP_REMOVE){
+ /* special case if top-level, dont purge top-level */
+ if (xt == xbase){
+ xc = NULL;
+ while ((xc = xml_child_each(xt, xc, CX_ELMNT)) != NULL){
+ xml_purge(xc);
+ xc = NULL; /* reset iterator */
+ }
+ }
+ else
+ if (xbase)
+ xml_purge(xbase);
+ }
+ else
+ if (text_modify(xbase, xbasep, xmod, op, (yang_node*)y, yspec) < 0)
+ goto done;
+ /* Remove NONE nodes if all subs recursively are also NONE */
+ if (xml_tree_prune_flagged(xt, XML_FLAG_NONE, 0, NULL) <0)
+ goto done;
+ if (xml_apply(xt, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset,
+ (void*)XML_FLAG_NONE) < 0)
+ goto done;
+ // output:
+ /* Print out top-level xml tree after modification to file */
+ if ((cb = cbuf_new()) == NULL){
+ clicon_err(OE_XML, errno, "cbuf_new");
+ goto done;
+ }
+ if (clicon_xml2cbuf(cb, xt, 0, 0) < 0)
+ goto done;
+ /* Reopen file in write mode */
+ close(fd);
+ if ((fd = open(dbfile, O_WRONLY | O_TRUNC, S_IRWXU)) < 0) {
+ clicon_err(OE_UNIX, errno, "open(%s)", dbfile);
+ goto done;
+ }
+ if (write(fd, cbuf_get(cb), cbuf_len(cb)+1) < 0){
+ clicon_err(OE_UNIX, errno, "write(%s)", dbfile);
+ goto done;
+ }
+ retval = 0;
+ done:
+ if (dbfile)
+ free(dbfile);
+ if (fd != -1)
+ close(fd);
+ if (cb)
+ cbuf_free(cb);
+ if (xpcb)
+ cbuf_free(xpcb);
+ if (xt)
+ xml_free(xt);
+ if (xnew)
+ xml_free(xnew);
+ return retval;
+}
+
+/*! Copy database from db1 to db2
+ * @param[in] xh XMLDB handle
+ * @param[in] from Source database copy
+ * @param[in] to Destination database
+ * @retval -1 Error
+ * @retval 0 OK
+ */
+int
+text_copy(xmldb_handle xh,
+ char *from,
+ char *to)
+{
+ int retval = -1;
+ struct text_handle *th = handle(xh);
+ char *fromfile = NULL;
+ char *tofile = NULL;
+
+ /* XXX lock */
+ if (text_db2file(th, from, &fromfile) < 0)
+ goto done;
+ if (text_db2file(th, 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] xh XMLDB handle
+ * @param[in] db Database
+ * @param[in] pid Process id
+ * @retval -1 Error
+ * @retval 0 OK
+ */
+int
+text_lock(xmldb_handle xh,
+ char *db,
+ int pid)
+{
+ // struct text_handle *th = handle(xh);
+
+ 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] xh XMLDB handle
+ * @param[in] db Database
+ * @param[in] pid Process id
+ * @retval -1 Error
+ * @retval 0 OK
+ * Assume all sanity checks have been made
+ */
+int
+text_unlock(xmldb_handle xh,
+ char *db)
+{
+ // struct text_handle *th = handle(xh);
+
+ 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] xh XMLDB handle
+ * @param[in] pid Process / Session id
+ * @retval -1 Error
+ * @retval 0 Ok
+ */
+int
+text_unlock_all(xmldb_handle xh,
+ int pid)
+{
+ // struct text_handle *th = handle(xh);
+
+ if (_running_locked == pid)
+ _running_locked = 0;
+ if (_candidate_locked == pid)
+ _candidate_locked = 0;
+ if (_startup_locked == pid)
+ _startup_locked = 0;
+ return 0;
+}
+
+/*! Check if database is locked
+ * @param[in] xh XMLDB handle
+ * @param[in] db Database
+ * @retval -1 Error
+ * @retval 0 Not locked
+ * @retval >0 Id of locker
+ */
+int
+text_islocked(xmldb_handle xh,
+ char *db)
+{
+ // struct text_handle *th = handle(xh);
+
+ 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;
+}
+
+/*! Check if db exists
+ * @param[in] xh XMLDB handle
+ * @param[in] db Database
+ * @retval -1 Error
+ * @retval 0 No it does not exist
+ * @retval 1 Yes it exists
+ */
+int
+text_exists(xmldb_handle xh,
+ char *db)
+{
+
+ int retval = -1;
+ struct text_handle *th = handle(xh);
+ char *filename = NULL;
+ struct stat sb;
+
+ if (text_db2file(th, 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] xh XMLDB handle
+ * @param[in] db Database
+ * @retval -1 Error
+ * @retval 0 OK
+ */
+int
+text_delete(xmldb_handle xh,
+ char *db)
+{
+ int retval = -1;
+ char *filename = NULL;
+ struct text_handle *th = handle(xh);
+
+ if (text_db2file(th, db, &filename) < 0)
+ goto done;
+ if (unlink(filename) < 0){
+ clicon_err(OE_DB, errno, "unlink %s", filename);
+ goto done;
+ }
+ retval = 0;
+ done:
+ return retval;
+}
+
+/*! Create / init database
+ * @param[in] xh XMLDB handle
+ * @param[in] db Database
+ * @retval 0 OK
+ * @retval -1 Error
+ */
+int
+text_create(xmldb_handle xh,
+ char *db)
+{
+ int retval = -1;
+ struct text_handle *th = handle(xh);
+ char *filename = NULL;
+ int fd = -1;
+
+ if (text_db2file(th, db, &filename) < 0)
+ goto done;
+ if ((fd = open(filename, O_CREAT|O_WRONLY, S_IRWXU)) == -1) {
+ clicon_err(OE_UNIX, errno, "open(%s)", filename);
+ goto done;
+ }
+ retval = 0;
+ done:
+ if (filename)
+ free(filename);
+ if (fd != -1)
+ close(fd);
+ return retval;
+}
+
+/*! plugin exit function */
+int
+text_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,
+ text_plugin_exit,
+ text_connect,
+ text_disconnect,
+ text_getopt,
+ text_setopt,
+ text_get,
+ text_put,
+ text_copy,
+ text_lock,
+ text_unlock,
+ text_unlock_all,
+ text_islocked,
+ text_exists,
+ text_delete,
+ text_create,
+};
+
+
+#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/text/clixon_xmldb_text.h b/datastore/text/clixon_xmldb_text.h
new file mode 100644
index 00000000..c82765bc
--- /dev/null
+++ b/datastore/text/clixon_xmldb_text.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_XMLDB_TEXT_H
+#define _CLIXON_XMLDB_TEXT_H
+
+/*
+ * Prototypes
+ */
+int text_get(xmldb_handle h, char *db, char *xpath,
+ cxobj **xtop, cxobj ***xvec, size_t *xlen);
+int text_put(xmldb_handle h, char *db, enum operation_type op,
+ char *api_path, cxobj *xt);
+int text_dump(FILE *f, char *dbfilename, char *rxkey);
+int text_copy(xmldb_handle h, char *from, char *to);
+int text_lock(xmldb_handle h, char *db, int pid);
+int text_unlock(xmldb_handle h, char *db);
+int text_unlock_all(xmldb_handle h, int pid);
+int text_islocked(xmldb_handle h, char *db);
+int text_exists(xmldb_handle h, char *db);
+int text_delete(xmldb_handle h, char *db);
+int text_init(xmldb_handle h, char *db);
+
+#endif /* _CLIXON_XMLDB_TEXT_H */
diff --git a/README.develop b/develop.md
similarity index 65%
rename from README.develop
rename to develop.md
index c260d2a7..a5b07413 100644
--- a/README.develop
+++ b/develop.md
@@ -1,11 +1,12 @@
-This README contains information for developers:
+# README for developers Clixon developers
+
1. How to document the code
2. How to work in git (branching)
3. How the meta-configure stuff works
-1. How to document the code
-+++++++++++++++++++++++++++
+## How to document the code
+```
/*! This is a small comment on one line
*
* This is a detailed description
@@ -22,36 +23,17 @@ This README contains information for developers:
* @retval FALSE This is a description of another return value
* @see See also this function
*/
+```
+## How to work in git (branching)
-2. How to work in git (branching)
-+++++++++++++++++++++++++++++++++
Basically follows: http://nvie.com/posts/a-successful-git-branching-model/
only somewhat simplified:
Do commits in develop branch. When done, merge with master.
-$ git checkout develop
-Switch to branch develop
-$ git add ..
-$ git commit ..
-$ git push origin develop
-Add/commit stuff here (and push)
-
-Ready for tagging
------------------
-(This is somewhat simplified - no release branch)
-$ ./bump-version.sh 3.6.0
-Files modified successfully, version bumped to 3.6.0
-$ git checkout master
-Switch to master
-$ git merge --no-ff develop
-Merge made by recursive.
-(Summary of changes)
-$ git tag -a 3.6.0
-
-3. How the meta-configure stuff works
-+++++++++++++++++++++++++++++++++++++
+## How the meta-configure stuff works
+```
configure.ac --.
| .------> autoconf* -----> configure
[aclocal.m4] --+---+
@@ -64,4 +46,4 @@ configure.ac --.
[config.h.in] -. v .-> [config.h] -.
+--> config.status* -+ +--> make*
Makefile.in ---' `-> Makefile ---'
-
+```
diff --git a/doc/Doxyfile b/doc/Doxyfile
index fba55c6c..98375856 100644
--- a/doc/Doxyfile
+++ b/doc/Doxyfile
@@ -32,7 +32,7 @@ DOXYFILE_ENCODING = UTF-8
# title of most generated pages and in a few other places.
# The default value is: My Project.
-PROJECT_NAME = "CliXoN"
+PROJECT_NAME = "clixon"
# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
# could be handy for archiving the generated documentation or if some version
@@ -743,7 +743,7 @@ WARN_LOGFILE =
# spaces.
# Note: If this tag is empty the current directory is searched.
-INPUT = ../lib/src/ ../lib/clicon/ ../apps/cli/ ../apps/config/ ../apps/netconf ../apps/dbctrl
+INPUT = ../lib/src/ ../lib/clicon/ ../apps/cli/ ../apps/config/ ../apps/netconf ../apps/dbctrl ../datastore
# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
diff --git a/doc/Doxyfile.graphs b/doc/Doxyfile.graphs
index a264d0fc..03ca9505 100644
--- a/doc/Doxyfile.graphs
+++ b/doc/Doxyfile.graphs
@@ -32,7 +32,7 @@ DOXYFILE_ENCODING = UTF-8
# title of most generated pages and in a few other places.
# The default value is: My Project.
-PROJECT_NAME = "CLICON"
+PROJECT_NAME = "clixon"
# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
# could be handy for archiving the generated documentation or if some version
@@ -743,7 +743,7 @@ WARN_LOGFILE =
# spaces.
# Note: If this tag is empty the current directory is searched.
-INPUT = ../lib/src/ ../lib/clicon/ ../apps/cli/ ../apps/backend/ ../apps/restconf/ ../apps/netconf ../apps/dbctrl
+INPUT = ../lib/src/ ../lib/clicon/ ../apps/cli/ ../apps/backend/ ../apps/restconf/ ../apps/netconf ../apps/dbctrl ../datastore
# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
diff --git a/doc/FAQ.txt b/doc/FAQ.md
similarity index 52%
rename from doc/FAQ.txt
rename to doc/FAQ.md
index 79c38315..8a57496f 100644
--- a/doc/FAQ.txt
+++ b/doc/FAQ.md
@@ -1,112 +1,66 @@
-Frequently Asked Questions - CliXon
-===================================
+# Clixon FAQ
-Q: What is CliXon?
-------------------
-CliXon is a configuration management tool including CLI generation,
-Yang parser, netconf interface and an embedded databases.
+## What is Clixon?
-Q: Why should you use CliXon?
------------------------------
-If you want an easy-to-use config frontend based on yang with an open-source license.
-Typically for embedded devices requiring a config interface such as routers and switches.
+Clixon is a configuration management tool including a generated CLI ,
+Yang parser, netconf and restconf interface and an embedded databases.
-Q: What license is available?
------------------------------
-The basic license is open-source, GPLv3. Contact authors for commercial license.
+## Why should I use Clixon?
-Q: Is CliXon extendible?
-------------------------
-Yes. All application semantics is defined in plugins with well-defined APIs. There are currently three types of plugins: for CLI, for Netconf and for the backend.
+If you want an easy-to-use configuration frontend based on yang with an
+open-source license. Typically for embedded devices requiring a
+config interface such as routers and switches.
-Q: Which language is CliXon implemented in?
--------------------------------------------
-CliXon is written in C. The plugins are written in C. The CLI
+## What license is available?
+CLIXON is dual license. Either Apache License, Version 2.0 or GNU
+General Public License Version 2.
+
+## Is Clixon extendible?
+Yes. All application semantics is defined in plugins with well-defined
+APIs. There are currently plugins for: CLI, Netconf, Restconf, the datastore and the backend.
+
+## Which programming language is used?
+Clixon is written in C. The plugins are written in C. The CLI
specification uses cligen (http://cligen.se)
There is a project for writing plugins in Python. It is reasonable
simple to spawn an external script from a backend.
-Q: Is CliXon different from Clicon?
------------------------------------
-CliXon is a fork of Clicon focussing on Yang specification and XML
-database. The yang support in clicon was grown out of a key-based
-scheme that became more and more complex since it had to translate
-between many different formats. CliXon uses only XML internally.
+## How to best understand Clixon?
+Run the ietf yang routing example, in the example directory.
-The commit transaction mechanism has been simplified too. But CliXon
-is not backward compliant with key-based Clixon applications, such as
-Rost.
-
-Q: How to best understand CliXon?
----------------------------------
-Run the ietf yang routing example. It is used in many of the answers below.
-
-Q: How do you build and install CliXon (and the example)?
----------------------------------------------------------
-CliXon:
+## How do you build and install Clixon (and the example)?
+Clixon:
+```
./configure;
make;
sudo make install;
sudo make install-include
-
+```
The example:
+```
cd example;
make;
sudo make install
+```
-Q: What about reference documentation?
---------------------------------------
-CliXon uses Doxygen for reference documentation.
+## What about reference documentation?
+Clixon uses Doxygen for reference documentation.
Build using 'make doc' and aim your browser at doc/html/index.html or
use the web resource: http://clicon.org/ref/index.html
-Q: How do you run the example?
-------------------------------
+## How do you run the example?
- Start a backend server: 'clixon_backend -Ff /usr/local/etc/routing.conf'
- Start a cli session: clixon_cli -f /usr/local/etc/routing.conf
- Start a netconf session: clixon_netconf -f /usr/local/etc/routing.conf
-Q: Can you run CliXon as docker containers?
--------------------------------------------
-Yes, the example works as docker containers as well. backend and cli needs a
-common file-system so they need to run as a composed pair.
- cd example/docker
- make docker # Prepares /data as shared file-system mount
- run.sh # Starts an example backend and a cli
+## How is configuration data stored?
+Configuration data is stored in an XML datastore. The default is a
+text-based addatastore, but there also exists a key-value datastore
+using qdbm. In the example the datastore are regular files found in
+/usr/local/var/routing/.
-The containers are by default downloaded from dockerhib, but you may
-build the containers locally:
- cd docker
- make docker
-
-You may also push the containers with 'make push' but you may then consider changing the image name in the makefile.
-
-Q: How do you change the example?
----------------------------------
-- routing.conf.local - Override default settings
-- The yang specifications - This is the central part. It changes the XML, database and the config cli.
-- routing_cli.cli - Change the fixed part of the CLI commands
-- routing_cli.c - Cli C-commands are placed here.
-- routing_backend.c - Commit and validate functions.
-- routing_netconf.c - Modify semantics of netconf commands.
-
-
-Q: How do you check what is in a database?
-------------------------------------------
-Use clixon_dbctrl. The name of the running or candidate databases are found in the
-configuration file.
-Example:
- > clixon_dbctrl -d /usr/local/var/routing/candidate_db / -p
- /interfaces/interface/eth0/ipv4
- /interfaces/interface/eth0/type bgp
- /interfaces/interface/eth0
- /interfaces/interface/eth0/name eth0
-Each line corresponds to a database entry (node in an XML tree).
-If the node is a leaf, the value appears as the second entry ('bgp' and 'eth0').
-
-Q: What is validate and commit?
--------------------------------
+## What is validate and commit?
Clixon follows netconf in its validate and commit semantics.
In short, you edit a 'candidate' configuration, which is first
'validated' for consistency and then 'committed' to the 'running'
@@ -116,59 +70,31 @@ A clixon developer writes commit functions to incrementaly upgrade a
system state based on configuration changes. Writing commit callbacks
is the core functionality of a clixon system.
-Q: How do you write a commit function?
---------------------------------------
-You write a commit function in routing_backend.c.
-Every time a commit is made, transaction_commit() is called in the
-backend. It has a 'transaction_data td' argument which is used to fetch
-information on added, deleted and changed entries. You access this
-information using access functions as defined in clixon_backend_transaction.h
+## What is a Clixon configuration file?
+Clixon options are stored in a configuration file you must specify
+when you start a backend or client using -f. The example configuration
+file is /usr/local/etc/routing.conf.
+This file is generated from the base source clixon.conf.cpp.cpp and
+is merged with local configuration files, such as routing.conf.local.
+This is slightly confusing and could be improved.
-Q: How do you check what has changed on commit?
------------------------------------------------
-You use XPATHs on the XML trees in the transaction commit callback.
-Suppose you want to print all added interfaces:
- cxobj *target = transaction_target(td); # wanted XML tree
- vec = xpath_vec_flag(target, "//interface", &len, XML_FLAG_ADD); /* Get added i/fs */
- for (i=0; i("This is a variable"), mycallback("myarg");
-(2) Then define a function in routing_cli.c
- mycallback(clicon_handle h, cvec *cvv, cg_var *arg)
-where 'cvv' contains the value of the variable and 'arg' contains the
-function parameter 'myarg'.
-
-Q: What are cg_var and cvec used in CLI callbacks?
---------------------------------------------------
-Those are 'CLIgen variables' and vector of CLIgen variables.
-They are documented in CLIgen documentation. Some examples on usage is found in the
-routing_cli.c
-
-Q: How do you write a validation function?
-------------------------------------------
-Similar to a commit function, but instead write the transaction_validate() function.
-Check for inconsistencies in the XML trees and if they fail, make an clicon_err() call.
- clicon_err(OE_PLUGIN, 0, "Route %s lacks ipv4 addr", name);
- return -1;
-The validation or commit will then be aborted.
-
-Q: How do you use netconf?
---------------------------
+## How do I use netconf?
As an alternative to cli configuration, you can use netconf. Easiest is to just pipe netconf commands to the clixon_netconf application.
Example:
@@ -176,25 +102,82 @@ Example:
However, more useful is to run clixon_netconf as an SSH
subsystem. Register the subsystem in /etc/sshd_config:
-
+```
Subsystem netconf /usr/local/bin/clixon_netconf
-
+```
and then invoke it from a client using
+```
ssh -s netconf
+```
+
+## How do I use notifications?
-Q: How do you use notifications?
---------------------------------
The example has a prebuilt notification stream called "ROUTING" that triggers every 10s.
You enable the notification either via the cli or via netconf:
cli> notify
cli> Routing notification
Routing notification
-...
-
-> clixon_netconf -qf /usr/local/etc/routing.conf
+```
+clixon_netconf -qf /usr/local/etc/routing.conf
ROUTING]]>]]>
]]>]]>
Routing notification]]>]]>
Routing notification]]>]]>
...
+```
+
+## I want to program. How do I extend the example?
+- routing.conf.local - Override default settings
+- The yang specifications - This is the central part. It changes the XML, database and the config cli.
+- routing_cli.cli - Change the fixed part of the CLI commands
+- routing_cli.c - Cli C-commands are placed here.
+- routing_backend.c - Commit and validate functions.
+- routing_netconf.c - Modify semantics of netconf commands.
+
+## How do I write a commit function?
+You write a commit function in routing_backend.c.
+Every time a commit is made, transaction_commit() is called in the
+backend. It has a 'transaction_data td' argument which is used to fetch
+information on added, deleted and changed entries. You access this
+information using access functions as defined in clixon_backend_transaction.h
+
+## How do i check what has changed on commit?
+You use XPATHs on the XML trees in the transaction commit callback.
+Suppose you want to print all added interfaces:
+```
+ cxobj *target = transaction_target(td); # wanted XML tree
+ vec = xpath_vec_flag(target, "//interface", &len, XML_FLAG_ADD); /* Get added i/fs */
+ for (i=0; i example("This is a comment") ("This is a variable"), mycallback("myarg");
+2. Then define a function in routing_cli.c
+> mycallback(clicon_handle h, cvec *cvv, cvec *arv)
+where 'cvv' contains the value of the variable 'var' and 'argv' contains the string "myarg".
+
+The 'cvv' datatype is a 'CLIgen variable vector'.
+They are documented in [CLIgen tutorial](https://github.com/olofhagsand/cligen/blob/master/cligen_tutorial.pdf)
+
+## How do I write a validation function?
+Similar to a commit function, but instead write the transaction_validate() function.
+Check for inconsistencies in the XML trees and if they fail, make an clicon_err() call.
+ clicon_err(OE_PLUGIN, 0, "Route %s lacks ipv4 addr", name);
+ return -1;
+The validation or commit will then be aborted.
diff --git a/doc/clixon_example_sdk.png b/doc/clixon_example_sdk.png
new file mode 100644
index 00000000..ca5f91e1
Binary files /dev/null and b/doc/clixon_example_sdk.png differ
diff --git a/example/README b/example/README.md
similarity index 77%
rename from example/README
rename to example/README.md
index e449d864..8c325b60 100644
--- a/example/README
+++ b/example/README.md
@@ -1,19 +1,25 @@
-Clixon yang routing example
-+++++++++++++++++++++++++++
+# Clixon yang routing example
-0. Compile and run
-------------------
-cd example
-make && sudo make install
-# Start backend
-clixon_backend -f /usr/local/etc/routing.conf -I
-# Edit cli
-clixon_cli -f /usr/local/etc/routing.conf
-# Send netconf command
-clixon_netconf -f /usr/local/etc/routing.conf
+## Compile and run
+```
+ cd example
+ make && sudo make install
+```
+Start backend:
+```
+ clixon_backend -f /usr/local/etc/routing.conf -I
+```
+Edit cli:
+```
+ clixon_cli -f /usr/local/etc/routing.conf
+```
+Send netconf command:
+```
+ clixon_netconf -f /usr/local/etc/routing.conf
+```
-1. Setting data example using netconf
--------------------------------------
+## Setting data example using netconf
+```
@@ -28,40 +34,39 @@ clixon_netconf -f /usr/local/etc/routing.conf
]]>]]>
+```
-2. Getting data using netconf
------------------------------
-
+## Getting data using netconf
+```
]]>]]>
-
]]>]]>
-
]]>]]>
-
]]>]]>
-
]]>]]>
-
]]>]]>
+```
+
+## Creating notification
-3. Creating notification
-------------------------
The example has an example notification triggering every 10s. To start a notification
stream in the session, create a subscription:
+```
ROUTING]]>]]>
]]>]]>
Routing notification]]>]]>
Routing notification]]>]]>
...
-
+```
This can also be triggered via the CLI:
+```
cli> notify
cli> Routing notification
Routing notification
...
+```
+
+## Extending
-4. Downcall
------------
Clixon has an extension mechanism which can be used to make extended internal
netconf messages to the backend configuration engine. You may need this to
make some special operation that is not covered by standard
@@ -71,11 +76,13 @@ reference. A more realistic downcall would perform some action, such as
reading some status.
Example:
+```
cli> downcall "This is a string"
This is a string
-cli>p
+```
-5. Run as docker container
---------------------------
+## Run as docker container
+```
cd docker
# look in README
+```
\ No newline at end of file
diff --git a/example/routing.conf.local b/example/routing.conf.local
index 8d705aba..698e56f4 100644
--- a/example/routing.conf.local
+++ b/example/routing.conf.local
@@ -26,4 +26,8 @@ CLICON_CLI_GENMODEL_TYPE VARS
CLICON_CLIGEN_CALLBACK_SINGLE_ARG 0
# Enabled uses "startup" configuration on boot
-CLICON_USE_STARTUP_CONFIG 0
\ No newline at end of file
+CLICON_USE_STARTUP_CONFIG 0
+
+# XMLDB datastore plugin filename (see datastore/ and clixon_xml_db.[ch])
+CLICON_XMLDB_PLUGIN /usr/local/lib/xmldb/text.so
+#CLICON_XMLDB_PLUGIN /usr/local/lib/xmldb/keyvalue.so
diff --git a/include/clixon_config.h.in b/include/clixon_config.h.in
index c7651da8..1b450323 100644
--- a/include/clixon_config.h.in
+++ b/include/clixon_config.h.in
@@ -12,10 +12,6 @@
/* Clixon version string */
#undef CLIXON_VERSION_STRING
-/* Check if extra keys inserted for database lists containing content. Eg
- A.n.foo = 3 means A.3 $!a=foo exists */
-#undef DB_KEYCONTENT
-
/* Define to 1 if you have the `alphasort' function. */
#undef HAVE_ALPHASORT
@@ -37,9 +33,6 @@
/* Define to 1 if you have the `crypt' library (-lcrypt). */
#undef HAVE_LIBCRYPT
-/* Define to 1 if you have the `curl' library (-lcurl). */
-#undef HAVE_LIBCURL
-
/* Define to 1 if you have the `dl' library (-ldl). */
#undef HAVE_LIBDL
diff --git a/lib/clixon/clixon.h.in b/lib/clixon/clixon.h.in
index 5d39d819..26220770 100644
--- a/lib/clixon/clixon.h.in
+++ b/lib/clixon/clixon.h.in
@@ -31,6 +31,9 @@
***** END LICENSE BLOCK *****
+ * NOTE: clixon.h is a GENERATED FILE and should not be edited.
+ * clixon.h.in is the original
+ *
* Meta-include file that includes all sub-files in control-lib
* Note: this include files is for external purposes. Do not include this
* file in clicon lib-routines.
@@ -67,22 +70,18 @@
#include
#include
#include
-#include
#include
#include
#include
#include
#include
-#include
#include
-#include
+#include
#include
#include
#include
#include
#include
-#include
-#include
/*
* Global variables generated by Makefile
diff --git a/lib/clixon/clixon_file.h b/lib/clixon/clixon_file.h
index 7a57119b..08cc1f57 100644
--- a/lib/clixon/clixon_file.h
+++ b/lib/clixon/clixon_file.h
@@ -38,10 +38,10 @@
int clicon_file_dirent(const char *dir, struct dirent **ent,
- const char *regexp, mode_t type, const char *label);
-
-char *clicon_tmpfile(const char *label);
+ const char *regexp, mode_t type);
int clicon_file_copy(char *src, char *target);
+int group_name2gid(char *name, gid_t *gid);
+
#endif /* _CLIXON_FILE_H_ */
diff --git a/lib/clixon/clixon_options.h b/lib/clixon/clixon_options.h
index a86f618f..7f3bfbc4 100644
--- a/lib/clixon/clixon_options.h
+++ b/lib/clixon/clixon_options.h
@@ -92,7 +92,7 @@ char *clicon_cli_dir(clicon_handle h);
char *clicon_clispec_dir(clicon_handle h);
char *clicon_netconf_dir(clicon_handle h);
char *clicon_restconf_dir(clicon_handle h);
-char *clicon_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);
@@ -120,4 +120,16 @@ int clicon_dbspec_yang_set(clicon_handle h, struct yang_spec *ys);
char *clicon_dbspec_name(clicon_handle h);
int clicon_dbspec_name_set(clicon_handle h, char *name);
+int clicon_xmldb_plugin_set(clicon_handle h, plghndl_t handle);
+
+plghndl_t clicon_xmldb_plugin_get(clicon_handle h);
+
+int clicon_xmldb_api_set(clicon_handle h, void *xa_api);
+
+void *clicon_xmldb_api_get(clicon_handle h);
+
+int clicon_xmldb_handle_set(clicon_handle h, void *xh);
+
+void *clicon_xmldb_handle_get(clicon_handle h);
+
#endif /* _CLIXON_OPTIONS_H_ */
diff --git a/lib/clixon/clixon_plugin.h b/lib/clixon/clixon_plugin.h
index 7dc50ddf..10d3d082 100644
--- a/lib/clixon/clixon_plugin.h
+++ b/lib/clixon/clixon_plugin.h
@@ -51,7 +51,7 @@ typedef void *(find_plugin_t)(clicon_handle, char *);
* Prototypes
*/
/* Common plugin function names, function types and signatures.
- * This set of plugins is extended in
+ * This plugin code is exytended by backend, cli, netconf, restconf plugins
* Cli see cli_plugin.c
* Backend see config_plugin.c
*/
@@ -77,4 +77,8 @@ typedef int (plgexit_t)(clicon_handle); /* Plugin exit */
/* Find a function in global namespace or a plugin. XXX clicon internal */
void *clicon_find_func(clicon_handle h, char *plugin, char *func);
+plghndl_t plugin_load (clicon_handle h, char *file, int dlflags);
+
+int plugin_unload(clicon_handle h, plghndl_t *handle);
+
#endif /* _CLIXON_PLUGIN_H_ */
diff --git a/lib/clixon/clixon_string.h b/lib/clixon/clixon_string.h
index 86c22e6d..e0c9f1b7 100644
--- a/lib/clixon/clixon_string.h
+++ b/lib/clixon/clixon_string.h
@@ -56,6 +56,9 @@ static inline char * strdup4(char *str)
*/
char **clicon_strsep(char *string, char *delim, int *nvec0);
char *clicon_strjoin (int argc, char **argv, char *delim);
+int str2cvec(char *string, char delim1, char delim2, cvec **cvp);
+int percent_encode(char *str, char **escp);
+int percent_decode(char *esc, char **str);
#ifndef HAVE_STRNDUP
char *clicon_strndup (const char *, size_t);
#endif /* ! HAVE_STRNDUP */
diff --git a/lib/clixon/clixon_xml.h b/lib/clixon/clixon_xml.h
index 91da69e5..cf40ff39 100644
--- a/lib/clixon/clixon_xml.h
+++ b/lib/clixon/clixon_xml.h
@@ -67,10 +67,12 @@ typedef int (xml_applyfn_t)(cxobj *yn, void *arg);
#define XML_FLAG_ADD 0x02 /* Node is added (commits) or parent added rec*/
#define XML_FLAG_DEL 0x04 /* Node is deleted (commits) or parent deleted rec */
#define XML_FLAG_CHANGE 0x08 /* Node is changed (commits) or child changed rec */
+#define XML_FLAG_NONE 0x10 /* Node is added as NONE */
/*
* Prototypes
*/
+char *xml_type2str(enum cxobj_type type);
char *xml_name(cxobj *xn);
int xml_name_set(cxobj *xn, char *name);
char *xml_namespace(cxobj *xn);
@@ -87,8 +89,6 @@ int xml_value_set(cxobj *xn, char *val);
char *xml_value_append(cxobj *xn, char *val);
enum cxobj_type xml_type(cxobj *xn);
int xml_type_set(cxobj *xn, enum cxobj_type type);
-int xml_index(cxobj *xn);
-int xml_index_set(cxobj *xn, int index);
cg_var *xml_cv_get(cxobj *xn);
int xml_cv_set(cxobj *xn, cg_var *cv);
@@ -103,6 +103,7 @@ int xml_childvec_set(cxobj *x, int len);
cxobj *xml_new(char *name, cxobj *xn_parent);
cxobj *xml_new_spec(char *name, cxobj *xn_parent, void *spec);
void *xml_spec(cxobj *x);
+void *xml_spec_set(cxobj *x, void *spec);
cxobj *xml_find(cxobj *xn_parent, char *name);
int xml_addsub(cxobj *xp, cxobj *xc);
@@ -113,6 +114,7 @@ int xml_rm(cxobj *xc);
int xml_rootchild(cxobj *xp, int i, cxobj **xcp);
char *xml_body(cxobj *xn);
+cxobj *xml_body_get(cxobj *xn);
char *xml_find_value(cxobj *xn_parent, char *name);
char *xml_find_body(cxobj *xn, char *name);
@@ -127,6 +129,7 @@ int clicon_xml_parse_file(int fd, cxobj **xml_top, char *endtag);
int clicon_xml_parse_str(char *str, cxobj **xml_top);
int clicon_xml_parse(cxobj **cxtop, char *format, ...);
+int xmltree2cbuf(cbuf *cb, cxobj *x, int level);
int xml_copy(cxobj *x0, cxobj *x1);
cxobj *xml_dup(cxobj *x0);
diff --git a/lib/clixon/clixon_xml_db.h b/lib/clixon/clixon_xml_db.h
index 8889a8d8..87f65411 100644
--- a/lib/clixon/clixon_xml_db.h
+++ b/lib/clixon/clixon_xml_db.h
@@ -30,31 +30,126 @@
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
+/* The XMLDB has a handle to keep connected state. To users of the API it is
+ * a void* but it may have structure within a specific plugin.
+ * The handle is independent from clicon so that (in principle) the datastore
+ * can work in other contexts than clicon.
+ * The connect API call sets the handle and disconnect resets it. In principle
+ * there can be several handles at one time.
+ */
+#if 1 /* SANITY CHECK */
+typedef struct {int16_t a;} *xmldb_handle;
+#else
+typedef void *xmldb_handle;
+#endif
+
+/* 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 connect function */
+typedef xmldb_handle (xmldb_connect_t)(void);
+
+/* Type of xmldb disconnect function */
+typedef int (xmldb_disconnect_t)(xmldb_handle xh);
+
+/* Type of xmldb getopt function */
+typedef int (xmldb_getopt_t)(xmldb_handle xh, char *optname, void **value);
+
+/* Type of xmldb setopt function */
+typedef int (xmldb_setopt_t)(xmldb_handle xh, char *optname, void *value);
+
+/* Type of xmldb get function */
+typedef int (xmldb_get_t)(xmldb_handle xh, char *db, char *xpath,
+ cxobj **xtop, cxobj ***xvec, size_t *xlen);
+
+/* Type of xmldb put function */
+typedef int (xmldb_put_t)(xmldb_handle xh, char *db, enum operation_type op,
+ char *api_path, cxobj *xt);
+
+/* Type of xmldb copy function */
+typedef int (xmldb_copy_t)(xmldb_handle xh, char *from, char *to);
+
+/* Type of xmldb lock function */
+typedef int (xmldb_lock_t)(xmldb_handle xh, char *db, int pid);
+
+/* Type of xmldb unlock function */
+typedef int (xmldb_unlock_t)(xmldb_handle xh, char *db);
+
+/* Type of xmldb unlock_all function */
+typedef int (xmldb_unlock_all_t)(xmldb_handle xh, int pid);
+
+/* Type of xmldb islocked function */
+typedef int (xmldb_islocked_t)(xmldb_handle xh, char *db);
+
+/* Type of xmldb exists function */
+typedef int (xmldb_exists_t)(xmldb_handle xh, char *db);
+
+/* Type of xmldb delete function */
+typedef int (xmldb_delete_t)(xmldb_handle xh, char *db);
+
+/* Type of xmldb init function */
+typedef int (xmldb_create_t)(xmldb_handle xh, char *db);
+
+/* 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_connect_t *xa_connect_fn;
+ xmldb_disconnect_t *xa_disconnect_fn;
+ xmldb_getopt_t *xa_getopt_fn;
+ xmldb_setopt_t *xa_setopt_fn;
+ xmldb_get_t *xa_get_fn;
+ xmldb_put_t *xa_put_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_create_t *xa_create_fn;
+};
+
/*
* Prototypes
+ * API
*/
-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(clicon_handle h, char *filename);
+int xmldb_plugin_unload(clicon_handle h);
+int xmldb_connect(clicon_handle h);
+int xmldb_disconnect(clicon_handle h);
+int xmldb_getopt(clicon_handle h, char *optname, void **value);
+int xmldb_setopt(clicon_handle h, char *optname, void *value);
int xmldb_get(clicon_handle h, char *db, char *xpath,
cxobj **xtop, cxobj ***xvec, size_t *xlen);
int xmldb_put(clicon_handle h, char *db, enum operation_type op,
char *api_path, cxobj *xt);
-int xmldb_dump(FILE *f, char *dbfilename, char *rxkey);
int xmldb_copy(clicon_handle h, char *from, char *to);
int xmldb_lock(clicon_handle h, char *db, int pid);
-int xmldb_unlock(clicon_handle h, char *db, int pid);
+int xmldb_unlock(clicon_handle h, char *db);
int xmldb_unlock_all(clicon_handle h, int pid);
int xmldb_islocked(clicon_handle h, char *db);
int xmldb_exists(clicon_handle h, char *db);
int xmldb_delete(clicon_handle h, char *db);
-int xmldb_init(clicon_handle h, char *db);
+int xmldb_create(clicon_handle h, char *db);
#endif /* _CLIXON_XML_DB_H */
diff --git a/lib/clixon/clixon_xml_map.h b/lib/clixon/clixon_xml_map.h
index f90a8e71..437ac46f 100644
--- a/lib/clixon/clixon_xml_map.h
+++ b/lib/clixon/clixon_xml_map.h
@@ -61,5 +61,14 @@ 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);
+int xml_tree_prune_flagged(cxobj *xt, int flag, int test, int *upmark);
+int xml_default(cxobj *x, void *arg);
+int xml_order(cxobj *x, void *arg);
+int xml_sanity(cxobj *x, void *arg);
+int api_path2xpath_cvv(yang_spec *yspec, cvec *cvv, int offset, cbuf *xpath);
+int api_path2xpath(yang_spec *yspec, char *api_path, cbuf *xpath);
#endif /* _CLIXON_XML_MAP_H_ */
diff --git a/lib/src/Makefile.in b/lib/src/Makefile.in
index ac869185..8c390233 100644
--- a/lib/src/Makefile.in
+++ b/lib/src/Makefile.in
@@ -61,15 +61,13 @@ 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 \
- clixon_chunk.c clixon_proc.c \
+SRC = clixon_sig.c clixon_log.c clixon_err.c clixon_event.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_err.c b/lib/src/clixon_err.c
index fd47a155..9e900676 100644
--- a/lib/src/clixon_err.c
+++ b/lib/src/clixon_err.c
@@ -57,7 +57,6 @@
#include "clixon_log.h"
#include "clixon_queue.h"
-#include "clixon_chunk.h"
#include "clixon_err.h"
/*
@@ -217,7 +216,7 @@ clicon_err_save(void)
{
struct err_state *es;
- if ((es = chunk(sizeof(*es), NULL)) == NULL)
+ if ((es = malloc(sizeof(*es))) == NULL)
return NULL;
es->es_errno = clicon_errno;
es->es_suberrno = clicon_suberrno;
@@ -232,10 +231,11 @@ clicon_err_restore(void* handle)
{
struct err_state *es;
- es = (struct err_state *)handle;
- clicon_errno = es->es_errno;
- clicon_suberrno = es->es_suberrno;
- strncpy(clicon_err_reason, es->es_reason, ERR_STRLEN-1);
- unchunk(es);
+ if ((es = (struct err_state *)handle) != NULL){
+ clicon_errno = es->es_errno;
+ clicon_suberrno = es->es_suberrno;
+ strncpy(clicon_err_reason, es->es_reason, ERR_STRLEN-1);
+ free(es);
+ }
return 0;
}
diff --git a/lib/src/clixon_file.c b/lib/src/clixon_file.c
index 64ac8ee1..bb1868dd 100644
--- a/lib/src/clixon_file.c
+++ b/lib/src/clixon_file.c
@@ -51,6 +51,7 @@
#include
#include
#include
+#include
/* cligen */
#include
@@ -58,7 +59,6 @@
/* clicon */
#include "clixon_err.h"
#include "clixon_queue.h"
-#include "clixon_chunk.h"
#include "clixon_string.h"
#include "clixon_file.h"
@@ -66,7 +66,8 @@
* qsort function
*/
static int
-clicon_file_dirent_sort(const void* arg1, const void* arg2)
+clicon_file_dirent_sort(const void* arg1,
+ const void* arg2)
{
struct dirent *d1 = (struct dirent *)arg1;
struct dirent *d2 = (struct dirent *)arg2;
@@ -81,10 +82,10 @@ clicon_file_dirent_sort(const void* arg1, const void* arg2)
/*! Return sorted matching files from a directory
* @param[in] dir Directory path
- * @param[out] ent Entries pointer, will be filled in with dir entries
+ * @param[out] ent Entries pointer, will be filled in with dir entries. Free
+ * after use
* @param[in] regexp Regexp filename matching
* @param[in] type File type matching, see stat(2)
- * @param[in] label Clicon Chunk label for memory handling, unchunk after use
*
* @retval n Number of matching files in directory
* @retval -1 Error
@@ -92,34 +93,34 @@ clicon_file_dirent_sort(const void* arg1, const void* arg2)
* @code
* char *dir = "/root/fs";
* struct dirent *dp;
- * if ((ndp = clicon_file_dirent(dir, &dp, "(.so)$", S_IFREG, __FUNCTION__)) < 0)
+ * if ((ndp = clicon_file_dirent(dir, &dp, "(.so)$", S_IFREG)) < 0)
* return -1;
* for (i = 0; i < ndp; i++)
* do something with dp[i].d_name;
- * unchunk_group(__FUNCTION__);
+ * free(dp);
* @endcode
*/
int
clicon_file_dirent(const char *dir,
struct dirent **ent,
const char *regexp,
- mode_t type,
- const char *label)
+ mode_t type)
{
- DIR *dirp;
- int retval = -1;
- int res;
- int nent;
- char *filename;
- regex_t re;
- char errbuf[128];
- struct stat st;
- struct dirent dent;
+ int retval = -1;
+ DIR *dirp;
+ int res;
+ int nent;
+ regex_t re;
+ char errbuf[128];
+ char filename[MAXPATHLEN];
+ struct stat st;
+ struct dirent dent;
struct dirent *dresp;
struct dirent *tmp;
struct dirent *new = NULL;
struct dirent *dvecp = NULL;
+
*ent = NULL;
nent = 0;
@@ -137,7 +138,9 @@ clicon_file_dirent(const char *dir,
goto quit;
}
- for (res = readdir_r (dirp, &dent, &dresp); dresp; res = readdir_r (dirp, &dent, &dresp)) {
+ for (res = readdir_r (dirp, &dent, &dresp);
+ dresp;
+ res = readdir_r (dirp, &dent, &dresp)) {
if (res != 0) {
clicon_err(OE_UNIX, 0, "readdir: %s", strerror(errno));
goto quit;
@@ -150,12 +153,8 @@ clicon_file_dirent(const char *dir,
}
/* File type matching */
if (type) {
- if ((filename = chunk_sprintf(__FUNCTION__, "%s/%s", dir, dent.d_name)) == NULL) {
- clicon_err(OE_UNIX, 0, "chunk: %s", strerror(errno));
- goto quit;
- }
+ snprintf(filename, MAXPATHLEN-1, "%s/%s", dir, dent.d_name);
res = lstat(filename, &st);
- unchunk (filename);
if (res != 0) {
clicon_err(OE_UNIX, 0, "lstat: %s", strerror(errno));
goto quit;
@@ -164,8 +163,8 @@ clicon_file_dirent(const char *dir,
continue;
}
- if ((tmp = rechunk(new, (nent+1)*sizeof(*dvecp), label)) == NULL) {
- clicon_err(OE_UNIX, 0, "chunk: %s", strerror(errno));
+ if ((tmp = realloc(new, (nent+1)*sizeof(*dvecp))) == NULL) {
+ clicon_err(OE_UNIX, errno, "realloc");
goto quit;
}
new = tmp;
@@ -183,30 +182,9 @@ quit:
closedir(dirp);
if (regexp)
regfree(&re);
- unchunk_group(__FUNCTION__);
-
return retval;
}
-/*
- * Use mkstep() to create an empty temporary file, accessible only by this user.
- * A chunk:ed file name is returned so that caller can overwrite file safely.
- */
-char *
-clicon_tmpfile(const char *label)
-{
- int fd;
- char file[] = "/tmp/.tmpXXXXXX";
-
- if ((fd = mkstemp(file)) < 0){
- clicon_err(OE_UNIX, errno, "mkstemp");
- return NULL;
- }
- close(fd);
-
- return (char *)chunkdup(file, strlen(file)+1, label);
-}
-
/*! Make a copy of file src
* @retval 0 OK
* @retval -1 Error
@@ -252,3 +230,34 @@ clicon_file_copy(char *src,
}
+/*! Translate group name to gid. Return -1 if error or not found.
+ * @param[in] name Name of group
+ * @param[out] gid Group id
+ * @retval 0 OK
+ * @retval -1 Error. or not found
+ */
+int
+group_name2gid(char *name,
+ gid_t *gid)
+{
+ char buf[1024];
+ struct group g0;
+ struct group *gr = &g0;
+ struct group *gtmp;
+
+ gr = &g0;
+ /* This leaks memory in ubuntu */
+ if (getgrnam_r(name, gr, buf, sizeof(buf), >mp) < 0){
+ clicon_err(OE_UNIX, errno, "%s: getgrnam_r(%s): %s",
+ __FUNCTION__, name, strerror(errno));
+ return -1;
+ }
+ if (gtmp == NULL){
+ clicon_err(OE_UNIX, 0, "%s: No such group: %s", __FUNCTION__, name);
+ fprintf(stderr, "No such group %s\n", name);
+ return -1;
+ }
+ if (gid)
+ *gid = gr->gr_gid;
+ return 0;
+}
diff --git a/lib/src/clixon_handle.c b/lib/src/clixon_handle.c
index ade4350d..f5debeff 100644
--- a/lib/src/clixon_handle.c
+++ b/lib/src/clixon_handle.c
@@ -52,16 +52,16 @@
#include "clixon_handle.h"
#include "clixon_err.h"
#include "clixon_yang.h"
+#include "clixon_plugin.h"
#include "clixon_options.h"
#define CLICON_MAGIC 0x99aafabe
#define handle(h) (assert(clicon_handle_check(h)==0),(struct clicon_handle *)(h))
-/*
- * clicon_handle
- * Internal structire of basic handle. Also header of all other handles.
- * see struct clicon_cli_handle, struct clicon_backend_handle, etc
+/*! Internal structure of basic handle. Also header of all other handles.
+ * @note If you change here, you must also change the structs below:
+ * @see struct cli_handle, struct backend_handle
*/
struct clicon_handle {
int ch_magic; /* magic (HDR) */
@@ -69,7 +69,6 @@ struct clicon_handle {
clicon_hash_t *ch_data; /* internal clicon data (HDR) */
};
-
/*! Internal call to allocate a CLICON handle.
*
* There may be different variants of handles with some common options.
@@ -166,3 +165,4 @@ clicon_data(clicon_handle h)
return ch->ch_data;
}
+
diff --git a/lib/src/clixon_hash.c b/lib/src/clixon_hash.c
index c3f6d71d..fc914b60 100644
--- a/lib/src/clixon_hash.c
+++ b/lib/src/clixon_hash.c
@@ -200,8 +200,8 @@ hash_value(clicon_hash_t *hash,
/*! Copy value and add hash entry.
*
* @param[in] hash Hash table
- * @param[in] key New variable name
- * @param[in] val New variable value
+ * @param[in] key Variable name
+ * @param[in] val Variable value
* @param[in] vlen Length of variable value
* @retval variable New hash structure on success
* @retval NULL Failure
@@ -212,8 +212,9 @@ hash_add(clicon_hash_t *hash,
void *val,
size_t vlen)
{
- void *newval;
- clicon_hash_t h, new = NULL;
+ void *newval;
+ clicon_hash_t h;
+ clicon_hash_t new = NULL;
/* If variable exist, don't allocate a new. just replace value */
h = hash_lookup (hash, key);
diff --git a/lib/src/clixon_options.c b/lib/src/clixon_options.c
index 32df541e..ecd6863a 100644
--- a/lib/src/clixon_options.c
+++ b/lib/src/clixon_options.c
@@ -61,9 +61,9 @@
#include "clixon_queue.h"
#include "clixon_hash.h"
#include "clixon_handle.h"
-#include "clixon_chunk.h"
#include "clixon_log.h"
#include "clixon_yang.h"
+#include "clixon_plugin.h"
#include "clixon_options.h"
/*
@@ -198,7 +198,6 @@ clicon_option_default(clicon_hash_t *copt)
}
retval = 0;
catch:
- unchunk_group(__FUNCTION__);
return retval;
}
@@ -233,8 +232,8 @@ clicon_option_sanity(clicon_hash_t *copt)
clicon_err(OE_UNIX, 0, "CLICON_YANG_DIR not defined in config file");
goto done;
}
- if (!hash_lookup(copt, "CLICON_ARCHIVE_DIR")){
- clicon_err(OE_UNIX, 0, "CLICON_ARCHIVE_DIR not defined in config file");
+ 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")){
@@ -448,9 +447,9 @@ clicon_restconf_dir(clicon_handle h)
}
char *
-clicon_archive_dir(clicon_handle h)
+clicon_xmldb_plugin(clicon_handle h)
{
- return clicon_option_str(h, "CLICON_ARCHIVE_DIR");
+ return clicon_option_str(h, "CLICON_XMLDB_PLUGIN");
}
/* get family of backend socket: AF_UNIX, AF_INET or AF_INET6 */
@@ -627,12 +626,12 @@ clicon_dbspec_yang(clicon_handle h)
return NULL;
}
-/*
- * Set dbspec (YANG variant)
+/*! Set yang database specification
* ys must be a malloced pointer
*/
int
-clicon_dbspec_yang_set(clicon_handle h, struct yang_spec *ys)
+clicon_dbspec_yang_set(clicon_handle h,
+ struct yang_spec *ys)
{
clicon_hash_t *cdat = clicon_data(h);
@@ -644,8 +643,7 @@ clicon_dbspec_yang_set(clicon_handle h, struct yang_spec *ys)
return 0;
}
-/*
- * Get dbspec name as read from spec. Can be used in CLI '@' syntax.
+/*! Get dbspec name as read from spec. Can be used in CLI '@' syntax.
* XXX: this we muśt change,...
*/
char *
@@ -656,11 +654,103 @@ clicon_dbspec_name(clicon_handle h)
return clicon_option_str(h, "dbspec_name");
}
-/*
- * Set dbspec name as read from spec. Can be used in CLI '@' syntax.
+/*! Set dbspec name as read from spec. Can be used in CLI '@' syntax.
*/
int
clicon_dbspec_name_set(clicon_handle h, char *name)
{
return clicon_option_str_set(h, "dbspec_name", name);
}
+
+/*! Set xmldb datastore plugin handle, as used by dlopen/dlsym/dlclose */
+int
+clicon_xmldb_plugin_set(clicon_handle h,
+ plghndl_t handle)
+{
+ clicon_hash_t *cdat = clicon_data(h);
+
+ if (hash_add(cdat, "xmldb_plugin", &handle, sizeof(void*)) == NULL)
+ return -1;
+ return 0;
+}
+
+/*! Get xmldb datastore plugin handle, as used by dlopen/dlsym/dlclose */
+plghndl_t
+clicon_xmldb_plugin_get(clicon_handle h)
+{
+ clicon_hash_t *cdat = clicon_data(h);
+ size_t len;
+ void *p;
+
+ if ((p = hash_value(cdat, "xmldb_plugin", &len)) != NULL)
+ return *(plghndl_t*)p;
+ return NULL;
+}
+
+/*! Set or reset XMLDB API struct pointer
+ * @param[in] h Clicon handle
+ * @param[in] xa XMLDB API struct
+ * @note xa is really of type struct xmldb_api*
+ */
+int
+clicon_xmldb_api_set(clicon_handle h,
+ void *xa)
+{
+ clicon_hash_t *cdat = clicon_data(h);
+
+ /* It is the pointer to xa_api that should be copied by hash,
+ so we send a ptr to the ptr to indicate what to copy.
+ */
+ if (hash_add(cdat, "xmldb_api", &xa, sizeof(void*)) == NULL)
+ return -1;
+ return 0;
+}
+
+/*! Get XMLDB API struct pointer
+ * @param[in] h Clicon handle
+ * @retval xa XMLDB API struct
+ * @note xa is really of type struct xmldb_api*
+ */
+void *
+clicon_xmldb_api_get(clicon_handle h)
+{
+ clicon_hash_t *cdat = clicon_data(h);
+ size_t len;
+ void *xa;
+
+ if ((xa = hash_value(cdat, "xmldb_api", &len)) != NULL)
+ return *(void**)xa;
+ return NULL;
+}
+
+/*! Set or reset XMLDB storage handle
+ * @param[in] h Clicon handle
+ * @param[in] xh XMLDB storage handle. If NULL reset it
+ * @note Just keep note of it, dont allocate it or so.
+ */
+int
+clicon_xmldb_handle_set(clicon_handle h,
+ void *xh)
+{
+ clicon_hash_t *cdat = clicon_data(h);
+
+ if (hash_add(cdat, "xmldb_handle", &xh, sizeof(void*)) == NULL)
+ return -1;
+ return 0;
+}
+
+/*! Get XMLDB storage handle
+ * @param[in] h Clicon handle
+ * @retval xh XMLDB storage handle. If not connected return NULL
+ */
+void *
+clicon_xmldb_handle_get(clicon_handle h)
+{
+ clicon_hash_t *cdat = clicon_data(h);
+ size_t len;
+ void *xh;
+
+ if ((xh = hash_value(cdat, "xmldb_handle", &len)) != NULL)
+ return *(void**)xh;
+ return NULL;
+}
diff --git a/lib/src/clixon_plugin.c b/lib/src/clixon_plugin.c
index 24a5319b..729ada7d 100644
--- a/lib/src/clixon_plugin.c
+++ b/lib/src/clixon_plugin.c
@@ -45,6 +45,7 @@
#include "clixon_err.h"
#include "clixon_queue.h"
#include "clixon_hash.h"
+#include "clixon_log.h"
#include "clixon_handle.h"
#include "clixon_plugin.h"
@@ -84,3 +85,76 @@ clicon_find_func(clicon_handle h, char *plugin, char *func)
return dlsym(dlhandle, func);
}
+
+/*! Load a dynamic plugin object and call its init-function
+ * Note 'file' may be destructively modified
+ * @param[in] h Clicon handle
+ * @param[in] file Which plugin to load
+ * @param[in] dlflags See man(3) dlopen
+ */
+plghndl_t
+plugin_load(clicon_handle h,
+ char *file,
+ int dlflags)
+{
+ char *error;
+ void *handle = NULL;
+ plginit_t *initfn;
+
+ clicon_debug(1, "%s", __FUNCTION__);
+ dlerror(); /* Clear any existing error */
+ if ((handle = dlopen (file, dlflags)) == NULL) {
+ error = (char*)dlerror();
+ clicon_err(OE_PLUGIN, errno, "dlopen: %s\n", error ? error : "Unknown error");
+ goto done;
+ }
+ /* call plugin_init() if defined */
+ if ((initfn = dlsym(handle, PLUGIN_INIT)) == NULL){
+ clicon_err(OE_PLUGIN, errno, "Failed to find plugin_init when loading restconf plugin %s", file);
+ goto err;
+ }
+ if ((error = (char*)dlerror()) != NULL) {
+ clicon_err(OE_UNIX, 0, "dlsym: %s: %s", file, error);
+ goto done;
+ }
+ if (initfn(h) != 0) {
+ clicon_err(OE_PLUGIN, errno, "Failed to initiate %s", strrchr(file,'/')?strchr(file, '/'):file);
+ if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */
+ clicon_err(OE_DB, 0, "Unknown error: %s: plugin_init does not make clicon_err call on error",
+ file);
+ goto err;
+ }
+ done:
+ return handle;
+ err:
+ if (handle)
+ dlclose(handle);
+ return NULL;
+}
+
+
+/*! Unload a plugin
+ * @param[in] h Clicon handle
+ * @param[in] handle Clicon handle
+ */
+int
+plugin_unload(clicon_handle h,
+ plghndl_t *handle)
+{
+ int retval = 0;
+ char *error;
+ plgexit_t *exitfn;
+
+ /* Call exit function is it exists */
+ exitfn = dlsym(handle, PLUGIN_EXIT);
+ if (dlerror() == NULL)
+ exitfn(h);
+
+ dlerror(); /* Clear any existing error */
+ if (dlclose(handle) != 0) {
+ error = (char*)dlerror();
+ clicon_err(OE_PLUGIN, errno, "dlclose: %s\n", error ? error : "Unknown error");
+ /* Just report */
+ }
+ return retval;
+}
diff --git a/lib/src/clixon_proc.c b/lib/src/clixon_proc.c
deleted file mode 100644
index 41dd9f8b..00000000
--- a/lib/src/clixon_proc.c
+++ /dev/null
@@ -1,294 +0,0 @@
-/*
- *
- ***** 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 *****
-
- */
-
-#ifdef HAVE_CONFIG_H
-#include "clixon_config.h"
-#endif
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-/* clicon */
-#include "clixon_err.h"
-#include "clixon_log.h"
-#include "clixon_sig.h"
-#include "clixon_string.h"
-#include "clixon_queue.h"
-#include "clixon_chunk.h"
-#include "clixon_proc.h"
-
-/*
- * Macros
- */
-#define signal_set_mask(set) sigprocmask(SIG_SETMASK, (set), NULL)
-#define signal_get_mask(set) sigprocmask (0, NULL, (set))
-
-/*
- * Child process ID
- * XXX Really shouldn't be a global variable
- */
-static int _clicon_proc_child = 0;
-
-/*
- * Make sure child is killed by ctrl-C
- */
-static void
-clicon_proc_sigint(int sig)
-{
- if (_clicon_proc_child > 0)
- kill (_clicon_proc_child, SIGINT);
-}
-
-/*! Fork a child process, setup a pipe between parent and child.
- * Allowing parent to read the output of the child.
- * @param[in] doerr If non-zero, stderr will be directed to the pipe as well.
- * The pipe for the parent to write
- * to the child is closed and cannot be used.
- *
- * When child process is done with the pipe setup, execute the specified
- * command, execv(argv[0], argv).
- *
- * When parent is done with the pipe setup it will read output from the child
- * until eof. The read output will be sent to the specified output callback,
- * 'outcb' function.
- *
- * @retval number Matches (processes affected).
- * @retval -1 Error.
- */
-int
-clicon_proc_run (char *cmd,
- void (outcb)(char *),
- int doerr)
-{
- char
- **argv,
- buf[512];
- int
- outfd[2] = { -1, -1 };
- int
- n,
- argc,
- status,
- retval = -1;
- pid_t
- child;
- sigfn_t oldhandler = NULL;
- sigset_t oset;
-
- argv = clicon_strsep(cmd, " \t", &argc);
- if (!argv)
- return -1;
-
- if (pipe (outfd) == -1)
- goto done;
-
- signal_get_mask(&oset);
- set_signal(SIGINT, clicon_proc_sigint, &oldhandler);
-
-
- if ((child = fork ()) < 0) {
- retval = -1;
- goto done;
- }
-
- if (child == 0) { /* Child */
-
- /* Unblock all signals except TSTP */
- clicon_signal_unblock (0);
- signal (SIGTSTP, SIG_IGN);
-
- close (outfd[0]); /* Close unused read ends */
- outfd[0] = -1;
-
- /* Divert stdout and stderr to pipes */
- dup2 (outfd[1], STDOUT_FILENO);
- if (doerr)
- dup2 (outfd[1], STDERR_FILENO);
-
- execvp (argv[0], argv);
- perror("execvp");
- _exit(-1);
- }
-
- /* Parent */
-
- /* Close unused write ends */
- close (outfd[1]);
- outfd[1] = -1;
-
- /* Read from pipe */
- while ((n = read (outfd[0], buf, sizeof (buf)-1)) != 0) {
- if (n < 0) {
- if (errno == EINTR)
- continue;
- break;
- }
- buf[n] = '\0';
- /* Pass read data to callback function is defined */
- if (outcb)
- outcb (buf);
- }
-
- /* Wait for child to finish */
- if(waitpid (child, &status, 0) == child)
- retval = WEXITSTATUS(status);
- else
- retval = -1;
-
- done:
-
- /* Clean up all pipes */
- if (outfd[0] != -1)
- close (outfd[0]);
- if (outfd[1] != -1)
- close (outfd[1]);
-
- /* Restore sigmask and fn */
- signal_set_mask (&oset);
- set_signal(SIGINT, oldhandler, NULL);
-
- if(argv)
- free(argv);
- return retval;
-}
-
-/*! Spawn command and report exit status
- */
-int
-clicon_proc_daemon (char *cmd)
-{
- char
- **argv;
- int
- i,
- argc,
- retval = -1,
- status, status2;
- pid_t
- child,
- pid;
- struct rlimit
- rlim;
-
- argv = clicon_strsep(cmd, " \t", &argc);
- if (!argv)
- return -1;
-
- if ((child = fork ()) < 0) {
- clicon_err(OE_UNIX, errno, "fork");
- goto done;
- }
-
- if (child == 0) { /* Child */
-
- clicon_signal_unblock (0);
- if ((pid = fork ()) < 0) {
- clicon_err(OE_UNIX, errno, "fork");
- return -1;
- _exit(1);
- }
- if (pid == 0) { /* Grandchild, create new session */
- setsid();
- if (chdir("/") < 0){
- clicon_err(OE_UNIX, errno, "chdirq");
- _exit(1);
- }
- /* Close open descriptors */
- if ( ! getrlimit (RLIMIT_NOFILE, &rlim))
- for (i = 0; i < rlim.rlim_cur; i++)
- close(i);
-
- if (execv (argv[0], argv) < 0) {
- clicon_err(OE_UNIX, errno, "execv");
- _exit(1);
- }
- /* Not reached */
- }
-
- waitpid (pid, &status2, 0);
- _exit(status2);
-
- }
-
- if (waitpid (child, &status, 0) > 0)
- retval = 0;
-
- done:
- if (argv)
- free(argv);
- return (retval);
-}
-
-
-/*! Translate group name to gid. Return -1 if error or not found.
- */
-int
-group_name2gid(char *name, gid_t *gid)
-{
- char buf[1024];
- struct group g0;
- struct group *gr = &g0;
- struct group *gtmp;
-
- gr = &g0;
- /* This leaks memory in ubuntu */
- if (getgrnam_r(name, gr, buf, sizeof(buf), >mp) < 0){
- clicon_err(OE_UNIX, errno, "%s: getgrnam_r(%s): %s",
- __FUNCTION__, name, strerror(errno));
- return -1;
- }
- if (gtmp == NULL){
- clicon_err(OE_UNIX, 0, "%s: No such group: %s", __FUNCTION__, name);
- fprintf(stderr, "No such group %s\n", name);
- return -1;
- }
- if (gid)
- *gid = gr->gr_gid;
- return 0;
-}
diff --git a/lib/src/clixon_proto.c b/lib/src/clixon_proto.c
index 0e14eee2..bd782234 100644
--- a/lib/src/clixon_proto.c
+++ b/lib/src/clixon_proto.c
@@ -66,7 +66,6 @@
#include "clixon_err.h"
#include "clixon_log.h"
#include "clixon_queue.h"
-#include "clixon_chunk.h"
#include "clixon_sig.h"
#include "clixon_xml.h"
#include "clixon_xsl.h"
@@ -298,6 +297,8 @@ clicon_msg_send(int s,
if (atomicio((ssize_t (*)(int, void *, size_t))write,
s, msg, ntohs(msg->op_len)) < 0){
clicon_err(OE_CFG, errno, "%s", __FUNCTION__);
+ clicon_log(LOG_WARNING, "%s: write: %s len:%d msg:%s", __FUNCTION__,
+ strerror(errno), ntohs(msg->op_len), msg->op_body);
goto done;
}
retval = 0;
@@ -532,11 +533,11 @@ send_msg_reply(int s,
uint16_t datalen)
{
int retval = -1;
- struct clicon_msg *reply;
+ struct clicon_msg *reply = NULL;
uint16_t len;
len = sizeof(*reply) + datalen;
- if ((reply = (struct clicon_msg *)chunk(len, __FUNCTION__)) == NULL)
+ if ((reply = (struct clicon_msg *)malloc(len)) == NULL)
goto done;
memset(reply, 0, len);
reply->op_len = htons(len);
@@ -546,7 +547,8 @@ send_msg_reply(int s,
goto done;
retval = 0;
done:
- unchunk_group(__FUNCTION__);
+ if (reply)
+ free(reply);
return retval;
}
diff --git a/lib/src/clixon_proto_client.c b/lib/src/clixon_proto_client.c
index 65bbb29e..03558c57 100644
--- a/lib/src/clixon_proto_client.c
+++ b/lib/src/clixon_proto_client.c
@@ -56,11 +56,11 @@
/* clicon */
#include "clixon_queue.h"
-#include "clixon_chunk.h"
#include "clixon_log.h"
#include "clixon_hash.h"
#include "clixon_handle.h"
#include "clixon_yang.h"
+#include "clixon_plugin.h"
#include "clixon_options.h"
#include "clixon_xml.h"
#include "clixon_xsl.h"
diff --git a/lib/src/clixon_string.c b/lib/src/clixon_string.c
index 66c9c6b4..06b9fc49 100644
--- a/lib/src/clixon_string.c
+++ b/lib/src/clixon_string.c
@@ -47,9 +47,10 @@
#include
#include
+#include
+
/* clicon */
#include "clixon_queue.h"
-#include "clixon_chunk.h"
#include "clixon_string.h"
#include "clixon_err.h"
@@ -138,6 +139,200 @@ clicon_strjoin(int argc,
return str;
}
+static int
+unreserved(unsigned char in)
+{
+ switch(in) {
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ case 'a': case 'b': case 'c': case 'd': case 'e':
+ case 'f': case 'g': case 'h': case 'i': case 'j':
+ case 'k': case 'l': case 'm': case 'n': case 'o':
+ case 'p': case 'q': case 'r': case 's': case 't':
+ case 'u': case 'v': case 'w': case 'x': case 'y': case 'z':
+ case 'A': case 'B': case 'C': case 'D': case 'E':
+ case 'F': case 'G': case 'H': case 'I': case 'J':
+ case 'K': case 'L': case 'M': case 'N': case 'O':
+ case 'P': case 'Q': case 'R': case 'S': case 'T':
+ case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z':
+ case '-': case '.': case '_': case '~':
+ return 1;
+ default:
+ break;
+ }
+ return 0;
+}
+
+/*! Percent encoding according to RFC 3896
+ * @param[out] esc Deallocate with free()
+ */
+int
+percent_encode(char *str,
+ char **escp)
+{
+ int retval = -1;
+ char *esc = NULL;
+ int len;
+ int i, j;
+
+ /* This is max */
+ len = strlen(str)*3+1;
+ if ((esc = malloc(len)) == NULL){
+ clicon_err(OE_UNIX, errno, "malloc");
+ goto done;
+ }
+ memset(esc, 0, len);
+ j = 0;
+ for (i=0; i 2 &&
+ isxdigit(esc[i+1]) && isxdigit(esc[i+2])){
+ hstr[0] = esc[i+1];
+ hstr[1] = esc[i+2];
+ hstr[2] = 0;
+ str[j] = strtoul(hstr, &ptr, 16);
+ i += 2;
+ }
+ else
+ str[j] = esc[i];
+ j++;
+ }
+ str[j++] = '\0';
+ *strp = str;
+ retval = 0;
+ done:
+ if (retval < 0 && str)
+ free(str);
+ return retval;
+}
+
+/*! Split a string into a cligen variable vector using 1st and 2nd delimiter
+ * Split a string first into elements delimited by delim1, then into
+ * pairs delimited by delim2.
+ * @param[in] string String to split
+ * @param[in] delim1 First delimiter char that delimits between elements
+ * @param[in] delim2 Second delimiter char for pairs within an element
+ * @param[out] cvp Created cligen variable vector, deallocate w cvec_free
+ * @retval 0 on OK
+ * @retval -1 error
+ *
+ * @example,
+ * Assuming delim1 = '&' and delim2 = '='
+ * a=b&c=d -> [[a,"b"][c="d"]
+ * kalle&c=d -> [[c="d"]] # Discard elements with no delim2
+ * XXX differentiate between error and null cvec.
+ */
+int
+str2cvec(char *string,
+ char delim1,
+ char delim2,
+ cvec **cvp)
+{
+ int retval = -1;
+ char *s;
+ char *s0 = NULL;;
+ char *val; /* value */
+ char *valu; /* unescaped value */
+ char *snext; /* next element in string */
+ cvec *cvv = NULL;
+ cg_var *cv;
+
+ if ((s0 = strdup(string)) == NULL){
+ clicon_err(OE_UNIX, errno, "strdup");
+ goto err;
+ }
+ s = s0;
+ if ((cvv = cvec_new(0)) ==NULL){
+ clicon_err(OE_UNIX, errno, "cvec_new");
+ goto err;
+ }
+ while (s != NULL) {
+ /*
+ * In the pointer algorithm below:
+ * name1=val1; name2=val2;
+ * ^ ^ ^
+ * | | |
+ * s val snext
+ */
+ if ((snext = index(s, delim1)) != NULL)
+ *(snext++) = '\0';
+ if ((val = index(s, delim2)) != NULL){
+ *(val++) = '\0';
+ if (percent_decode(val, &valu) < 0)
+ goto err;
+ if ((cv = cvec_add(cvv, CGV_STRING)) == NULL){
+ clicon_err(OE_UNIX, errno, "cvec_add");
+ goto err;
+ }
+ while ((strlen(s) > 0) && isblank(*s))
+ s++;
+ cv_name_set(cv, s);
+ cv_string_set(cv, valu);
+ free(valu); valu = NULL;
+ }
+ else{
+ if (strlen(s)){
+ if ((cv = cvec_add(cvv, CGV_STRING)) == NULL){
+ clicon_err(OE_UNIX, errno, "cvec_add");
+ goto err;
+ }
+ cv_name_set(cv, s);
+ cv_string_set(cv, "");
+ }
+ }
+ s = snext;
+ }
+ retval = 0;
+ done:
+ *cvp = cvv;
+ if (s0)
+ free(s0);
+ return retval;
+ err:
+ if (cvv){
+ cvec_free(cvv);
+ cvv = NULL;
+ }
+ goto done;
+}
+
/*! strndup() for systems without it, such as xBSD
*/
@@ -163,6 +358,8 @@ clicon_strndup (const char *str,
}
#endif /* ! HAVE_STRNDUP */
+
+
/*
* Turn this on for uni-test programs
* Usage: clixon_string join
diff --git a/lib/src/clixon_xml.c b/lib/src/clixon_xml.c
index f8134c0e..0b4d3835 100644
--- a/lib/src/clixon_xml.c
+++ b/lib/src/clixon_xml.c
@@ -51,7 +51,6 @@
#include "clixon_err.h"
#include "clixon_log.h"
#include "clixon_queue.h"
-#include "clixon_chunk.h"
#include "clixon_xml.h"
#include "clixon_xml_parse.h"
@@ -76,7 +75,6 @@ struct xml{
int x_childvec_len; /* length of vector */
enum cxobj_type x_type; /* type of node: element, attribute, body */
char *x_value; /* attribute and body nodes have values */
- int x_index; /* key node, cf sql index */
int _x_vector_i; /* internal use: xml_child_each */
int x_flags; /* Flags according to XML_FLAG_* above */
void *x_spec; /* Pointer to specification, eg yang, by
@@ -84,6 +82,36 @@ struct xml{
cg_var *x_cv; /* If body this contains the typed value */
};
+/* Type to string conversion */
+struct map_str2int{
+ char *ms_str;
+ enum cxobj_type ms_type;
+};
+
+/* Mapping between xml type <--> string */
+static const struct map_str2int xsmap[] = {
+ {"error", CX_ERROR},
+ {"element", CX_ELMNT},
+ {"attr", CX_ATTR},
+ {"body", CX_BODY},
+ {NULL, -1}
+};
+
+/*! Translate from xml type in enum form to string keyword
+ * @param[in] type Xml type
+ * @retval str String keyword
+ */
+char *
+xml_type2str(enum cxobj_type type)
+{
+ const struct map_str2int *xs;
+
+ for (xs = &xsmap[0]; xs->ms_str; xs++)
+ if (xs->ms_type == type)
+ return xs->ms_str;
+ return NULL;
+}
+
/*
* Access functions
*/
@@ -223,7 +251,7 @@ xml_value(cxobj *xn)
/*! Set value of xml node, value is copied
* @param[in] xn xml node
- * @param[in] val new value, null-terminated string, copied by function
+ * @param[in] val new value, null-terminated string, copied by function
* @retval -1 on error with clicon-err set
* @retval 0 OK
*/
@@ -294,33 +322,6 @@ xml_type_set(cxobj *xn,
return old;
}
-/*! Get index/key of xnode
- * @param[in] xn xml node
- * @retval index of xml node
- * index/key is used in case of yang list constructs where one element is key
- */
-int
-xml_index(cxobj *xn)
-{
- return xn->x_index;
-}
-
-/*! Set index of xnode
- * @param[in] xn xml node
- * @param[in] index new index
- * @retval index old index
- * index/key is used in case of yang list constructs where one element is key
- */
-int
-xml_index_set(cxobj *xn,
- int index)
-{
- int old = xn->x_index;
-
- xn->x_index = index;
- return old;
-}
-
/*! Get cligen variable associated with node
* @param[in] xn xml node
* @retval cv Cligen variable if set
@@ -520,12 +521,21 @@ xml_new_spec(char *name,
return x;
}
+
void *
xml_spec(cxobj *x)
{
return x->x_spec;
}
+void *
+xml_spec_set(cxobj *x,
+ void *spec)
+{
+ x->x_spec = spec;
+ return 0;
+}
+
/*! Find an XML node matching name among a parent's children.
*
* Get first XML node directly under x_up in the xml hierarchy with
@@ -758,6 +768,16 @@ xml_body(cxobj *xn)
return NULL;
}
+cxobj *
+xml_body_get(cxobj *xn)
+{
+ cxobj *xb = NULL;
+
+ while ((xb = xml_child_each(xn, xb, CX_BODY)) != NULL)
+ return xb;
+ return NULL;
+}
+
/*! Find and return the value of a sub xml node
*
* The value can be of an attribute or body.
@@ -844,8 +864,8 @@ clicon_xml2file(FILE *f,
int level,
int prettyprint)
{
- cbuf *cb;
int retval = -1;
+ cbuf *cb = NULL;
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
@@ -882,8 +902,8 @@ xml_print(FILE *f,
/*! Print an XML tree structure to a cligen buffer
*
* @param[in,out] cb Cligen buffer to write to
- * @param[in] xn clicon xml tree
- * @param[in] level how many spaces to insert before each line
+ * @param[in] xn Clicon xml tree
+ * @param[in] level Indentation level
* @param[in] prettyprint insert \n and spaces tomake the xml more readable.
*
* @code
@@ -897,47 +917,50 @@ xml_print(FILE *f,
*/
int
clicon_xml2cbuf(cbuf *cb,
- cxobj *cx,
+ cxobj *x,
int level,
int prettyprint)
{
cxobj *xc;
+ char *name;
- switch(xml_type(cx)){
+ name = xml_name(x);
+ switch(xml_type(x)){
case CX_BODY:
- cprintf(cb, "%s", xml_value(cx));
+ cprintf(cb, "%s", xml_value(x));
break;
case CX_ATTR:
cprintf(cb, " ");
- if (xml_namespace(cx))
- cprintf(cb, "%s:", xml_namespace(cx));
- cprintf(cb, "%s=\"%s\"", xml_name(cx), xml_value(cx));
+ if (xml_namespace(x))
+ cprintf(cb, "%s:", xml_namespace(x));
+ cprintf(cb, "%s=\"%s\"", name, xml_value(x));
break;
case CX_ELMNT:
cprintf(cb, "%*s<", prettyprint?(level*XML_INDENT):0, "");
- if (xml_namespace(cx))
- cprintf(cb, "%s:", xml_namespace(cx));
- cprintf(cb, "%s", xml_name(cx));
+ if (xml_namespace(x))
+ cprintf(cb, "%s:", xml_namespace(x));
+ cprintf(cb, "%s", name);
xc = NULL;
- while ((xc = xml_child_each(cx, xc, CX_ATTR)) != NULL)
+ /* print attributes only */
+ while ((xc = xml_child_each(x, xc, CX_ATTR)) != NULL)
clicon_xml2cbuf(cb, xc, level+1, prettyprint);
/* Check for special case instead of */
- if (xml_body(cx)==NULL && xml_child_nr(cx)==0)
+ if (xml_body(x)==NULL && xml_child_nr(x)==0)
cprintf(cb, "/>");
else{
cprintf(cb, ">");
- if (prettyprint && xml_body(cx)==NULL)
+ if (prettyprint && xml_body(x)==NULL)
cprintf(cb, "\n");
xc = NULL;
- while ((xc = xml_child_each(cx, xc, -1)) != NULL) {
+ while ((xc = xml_child_each(x, xc, -1)) != NULL) {
if (xml_type(xc) == CX_ATTR)
continue;
else
clicon_xml2cbuf(cb, xc, level+1, prettyprint);
}
- if (prettyprint && xml_body(cx)==NULL)
+ if (prettyprint && xml_body(x)==NULL)
cprintf(cb, "%*s", level*XML_INDENT, "");
- cprintf(cb, "%s>", xml_name(cx));
+ cprintf(cb, "%s>", name);
}
if (prettyprint)
cprintf(cb, "\n");
@@ -976,6 +999,44 @@ xml_parse(char *str,
return retval;
}
+/*! Print actual xml tree datastructures (not xml), mainly for debugging
+ * @param[in,out] cb Cligen buffer to write to
+ * @param[in] xn Clicon xml tree
+ * @param[in] level Indentation level
+ */
+int
+xmltree2cbuf(cbuf *cb,
+ cxobj *x,
+ int level)
+{
+ cxobj *xc;
+ int i;
+
+ for (i=0; ix_flags)
+ cprintf(cb, " flags:0x%x", x->x_flags);
+ if (xml_child_nr(x))
+ cprintf(cb, " {");
+ cprintf(cb, "\n");
+ xc = NULL;
+ while ((xc = xml_child_each(x, xc, -1)) != NULL)
+ xmltree2cbuf(cb, xc, level+1);
+ if (xml_child_nr(x)){
+ for (i=0; i will look as:
*
* XXX: There is a potential leak here on some return values.
+ * XXX: What happens if endtag is different?
* May block
*/
int
@@ -1016,59 +1078,69 @@ clicon_xml_parse_file(int fd,
cxobj **cx,
char *endtag)
{
+ int retval = -1;
+ int ret;
int len = 0;
char ch;
- int retval;
- char *xmlbuf;
+ char *xmlbuf = NULL;
char *ptr;
int maxbuf = BUFLEN;
int endtaglen = strlen(endtag);
int state = 0;
+ int oldmaxbuf;
if (endtag == NULL){
clicon_err(OE_XML, 0, "%s: endtag required\n", __FUNCTION__);
- return -1;
+ goto done;
}
*cx = NULL;
if ((xmlbuf = malloc(maxbuf)) == NULL){
clicon_err(OE_XML, errno, "%s: malloc", __FUNCTION__);
- return -1;
+ goto done;
}
memset(xmlbuf, 0, maxbuf);
ptr = xmlbuf;
while (1){
- if ((retval = read(fd, &ch, 1)) < 0){
+ if ((ret = read(fd, &ch, 1)) < 0){
clicon_err(OE_XML, errno, "%s: read: [pid:%d]\n",
__FUNCTION__,
(int)getpid());
break;
}
- if (retval != 0){
+ if (ret != 0){
state = FSM(endtag, ch, state);
xmlbuf[len++] = ch;
}
- if (retval == 0 || state == endtaglen){
+ if (ret == 0 || state == endtaglen){
state = 0;
if ((*cx = xml_new("top", NULL)) == NULL)
break;
- if (xml_parse(ptr, *cx) < 0)
- return -1;
+ if (xml_parse(ptr, *cx) < 0){
+ goto done;
+ }
break;
}
if (len>=maxbuf-1){ /* Space: one for the null character */
- int oldmaxbuf = maxbuf;
-
+ oldmaxbuf = maxbuf;
maxbuf *= 2;
if ((xmlbuf = realloc(xmlbuf, maxbuf)) == NULL){
clicon_err(OE_XML, errno, "%s: realloc", __FUNCTION__);
- return -1;
+ goto done;
}
memset(xmlbuf+oldmaxbuf, 0, maxbuf-oldmaxbuf);
ptr = xmlbuf;
}
} /* while */
- free(xmlbuf);
- return (*cx)?0:-1;
+ retval = 0;
+ done:
+ if (retval < 0 && *cx){
+ free(*cx);
+ *cx = NULL;
+ }
+ if (xmlbuf)
+ free(xmlbuf);
+ return retval;
+ // return (*cx)?0:-1;
}
/*! Read an XML definition from string and parse it into a parse-tree.
@@ -1182,7 +1254,7 @@ copy_one(cxobj *xn0,
* x1 should be a created placeholder. If x1 is non-empty,
* the copied tree is appended to the existing tree.
* @code
- * x1 = xml_new("new", xc);
+ * x1 = xml_new("new", xparent);
* xml_copy(x0, x1);
* @endcode
*/
@@ -1190,7 +1262,7 @@ int
xml_copy(cxobj *x0,
cxobj *x1)
{
- int retval = -1;
+ int retval = -1;
cxobj *x;
cxobj *xcopy;
@@ -1462,8 +1534,12 @@ xml_body_uint32(cxobj *xb,
}
/*! Map xml operation from string to enumeration
- * @param[in] xn XML node
- * @param[out] op "operation" attribute may change operation
+ * @param[in] opstr String, eg "merge"
+ * @param[out] op Enumeration, eg OP_MERGE
+ * @code
+ * enum operation_type op;
+ * xml_operation("replace", &op)
+ * @endcode
*/
int
xml_operation(char *opstr,
@@ -1488,6 +1564,14 @@ xml_operation(char *opstr,
return 0;
}
+/*! Map xml operation from enumeration to string
+ * @param[in] op enumeration operation, eg OP_MERGE,...
+ * @retval str String, eg "merge". Static string, no free necessary
+ * @code
+ * enum operation_type op;
+ * xml_operation("replace", &op)
+ * @endcode
+ */
char *
xml_operation2str(enum operation_type op)
{
@@ -1511,3 +1595,52 @@ xml_operation2str(enum operation_type op)
return "none";
}
}
+
+/*
+ * Turn this on to get a xml parse and pretty print test program
+ * Usage: xpath
+ * read xml from input
+ * Example compile:
+ gcc -g -o xml -I. -I../clixon ./clixon_xml.c -lclixon -lcligen
+ * Example run:
+ echo "" | xml
+*/
+#if 0 /* Test program */
+
+static int
+usage(char *argv0)
+{
+ fprintf(stderr, "usage:%s.\n\tInput on stdin\n", argv0);
+ exit(0);
+}
+
+int
+main(int argc, char **argv)
+{
+ cxobj *xt;
+ cxobj *xc;
+ cbuf *cb = cbuf_new();
+
+ if (argc != 1){
+ usage(argv[0]);
+ return 0;
+ }
+ if (clicon_xml_parse_file(0, &xt, "") < 0){
+ fprintf(stderr, "parsing 2\n");
+ return -1;
+ }
+ xc = NULL;
+ while ((xc = xml_child_each(xt, xc, -1)) != NULL) {
+ xmltree2cbuf(cb, xc, 0); /* dump data structures */
+ //clicon_xml2cbuf(cb, xc, 0, 1); /* print xml */
+ }
+ fprintf(stdout, "%s", cbuf_get(cb));
+ if (xt)
+ xml_free(xt);
+ if (cb)
+ cbuf_free(cb);
+ return 0;
+}
+
+#endif /* Test program */
+
diff --git a/lib/src/clixon_xml_db.c b/lib/src/clixon_xml_db.c
index d79a93ff..9bc2c2c0 100644
--- a/lib/src/clixon_xml_db.c
+++ b/lib/src/clixon_xml_db.c
@@ -30,950 +30,281 @@
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
#include
-#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include