Merge branch 'develop'

This commit is contained in:
Olof hagsand 2017-05-07 19:06:56 +02:00
commit 9d8a2f73c2
85 changed files with 6664 additions and 3692 deletions

View file

@ -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()

View file

@ -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

View file

@ -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*/
};

102
README.md
View file

@ -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
===
<img src="doc/clixon_example_sdk.png" alt="clixon sdk" style="width: 200px;"/>
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

View file

@ -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)

View file

@ -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, "<rpc-reply><rpc-error>"
"<error-tag>operation-failed</error-tag>"
@ -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, "<rpc-reply><ok/></rpc-reply>") < 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, "<rpc-reply><rpc-error>"
@ -622,7 +621,6 @@ from_client_delete_config(clicon_handle h,
piddb);
goto ok;
}
if (xmldb_delete(h, target) < 0){
cprintf(cbret, "<rpc-reply><rpc-error>"
"<error-tag>operation-failed</error-tag>"
@ -633,7 +631,7 @@ from_client_delete_config(clicon_handle h,
"</rpc-error></rpc-reply>", clicon_err_reason);
goto ok;
}
if (xmldb_init(h, target) < 0){
if (xmldb_create(h, target) < 0){
cprintf(cbret, "<rpc-reply><rpc-error>"
"<error-tag>operation-failed</error-tag>"
"<error-type>protocol</error-type>"
@ -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;

View file

@ -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);

View file

@ -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 <level>\tdebug\n"
" -f <file>\tCLICON config file (mandatory)\n"
" -d <dir>\tSpecify backend plugin directory (default: %s)\n"
" -b <dir>\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 <file>\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 <group>\tClient membership required to this group (default: %s)\n"
"\t-y <file>\tOverride yang spec file (dont include .yang suffix)\n",
" -y <file>\tOverride yang spec file (dont include .yang suffix)\n"
" -x <plugin>\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;
}

View file

@ -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;
}

View file

@ -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);

View file

@ -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;

View file

@ -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_ */

View file

@ -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: <rpc><xn></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: <rpc><xn></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.

View file

@ -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;
}

View file

@ -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
*----------------------------------------------------------*/

View file

@ -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());

View file

@ -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

View file

@ -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 */

View file

@ -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",

View file

@ -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 <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdarg.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <time.h>
#include <syslog.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/in.h>
/* cligen */
#include <cligen/cligen.h>
/* clicon */
#include <clixon/clixon.h>
/* 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 <db>\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 \"<key> <val>\" Add database entry\n"
"\t-r <key>\tRemove database entry\n"
"\t-m <regexp key>\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 <db>): %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, "<config>%s</config>", 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;
}

16
apps/netconf/README.md Normal file
View file

@ -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

View file

@ -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;
}

View file

@ -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/*
```

View file

@ -33,7 +33,6 @@
*/
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
@ -46,9 +45,9 @@
#include <fcgi_stdio.h>
#include <signal.h>
#include <dlfcn.h>
#include <sys/param.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <curl/curl.h>
/* cligen */
#include <cligen/cligen.h>
@ -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;
}

View file

@ -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);

View file

@ -58,7 +58,6 @@
#include <signal.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <curl/curl.h>
#include <libgen.h>
/* cligen */

View file

@ -109,7 +109,6 @@ Mapping netconf error-tag -> status code
#include <signal.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <curl/curl.h>
/* cligen */
#include <cligen/cligen.h>
@ -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<cvec_len(pcvec); i++){
cv = cvec_i(pcvec, i);
name = cv_name_get(cv);
clicon_debug(1, "[%d] cvname:%s", i, name);
clicon_debug(1, "cv2str%d", cv2str(cv, NULL, 0));
if (i == pi){
if ((y = yang_find_topnode(yspec, name)) == NULL){
clicon_err(OE_UNIX, errno, "No yang node found: %s", name);
goto done;
}
}
else{
assert(y!=NULL);
if ((y = yang_find_syntax((yang_node*)y, name)) == NULL){
clicon_err(OE_UNIX, errno, "No yang node found: %s", name);
goto done;
}
}
/* Check if has value, means '=' */
if (cv2str(cv, NULL, 0) > 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: <key>[ <key>]* */
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, "</config>");
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: <edit-config> (nc:operation="create") | invoke an RPC operation
* Netconf: <edit-config> (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,

View file

@ -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 <b> k <c>' ignored
# CLICON_CLI_VARONLY 1

239
configure vendored
View file

@ -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;;

View file

@ -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 <clixon_custom.h>])
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
)

View file

@ -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 -

94
datastore/README.md Normal file
View file

@ -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);
```

View file

@ -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 '<config>eth66</config>'
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 / '<config><interfaces><interface><name>eth0</name><enabled>true</enabled></interface></interfaces></config>'
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdarg.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <time.h>
#include <syslog.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/in.h>
/* cligen */
#include <cligen/cligen.h>
/* clicon */
#include <clixon/clixon.h>
/* 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 <options>* [<command>]\n"
"where options are\n"
"\t-h\t\tHelp\n"
"\t-D\t\tDebug\n"
"\t-d <db>\t\tDatabase name. Default: running. Alt: candidate,startup\n"
"\t-b <dir>\tDatabase directory. Mandatory\n"
"\t-p <plugin>\tDatastore plugin. Mandatory\n"
"\t-y <dir>\tYang directory (where modules are stored). Mandatory\n"
"\t-m <module>\tYang module. Mandatory\n"
"and command is either:\n"
"\tget <xpath>\n"
"\tput (merge|replace|create|delete|remove) <api_path> <xml>\n"
"\tcopy <todb>\n"
"\tlock <pid>\n"
"\tunlock\n"
"\tunlock_all <pid>\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;
}

View file

@ -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

View file

@ -36,6 +36,7 @@
ensure errno is set and return -1/NULL */
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
@ -45,7 +46,11 @@
#include <sys/types.h>
/* clicon */
#include "clixon_queue.h"
#include <cligen/cligen.h>
/* clicon */
#include <clixon/clixon.h>
#include "clixon_chunk.h"
/*

File diff suppressed because it is too large Load diff

View file

@ -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 */

View file

@ -78,9 +78,8 @@
#include <cligen/cligen.h>
/* clicon */
#include "clixon_log.h"
#include "clixon_err.h"
#include "clixon_queue.h"
#include <clixon/clixon.h>
#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;
}
}

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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 */

View file

@ -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 ---'
```

View file

@ -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

View file

@ -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

View file

@ -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<len; i++) /* Loop over added i/fs */
clicon_xml2file(stdout, vec[i], 0, 1); /* Print the added interface */
You can look for added, deleted and changed entries in this way.
## Can I 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
```
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 I access the XML tree?
--------------------------------
Using XPATH, find and iteration functions defined in the XML library. Example library functions:
xml_child_each(),
xml_find(),
xml_body(),
clicon_xml2file(),
xml_apply()
More are found in the doxygen reference.
Q: How do you write a CLI callback function?
--------------------------------------------
(1) You add an entry in routing_cli.cli
example("This is a comment") <var:int32>("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 <host>
```
## 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
<rpc><create-subscription><stream>ROUTING</stream></create-subscription></rpc>]]>]]>
<rpc-reply><ok/></rpc-reply>]]>]]>
<notification><event>Routing notification</event></notification>]]>]]>
<notification><event>Routing notification</event></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<len; i++) /* Loop over added i/fs */
clicon_xml2file(stdout, vec[i], 0, 1); /* Print the added interface */
```
You can look for added, deleted and changed entries in this way.
## How do I access the XML tree?
Using XPATH, find and iteration functions defined in the XML library. Example library functions:
```
xml_child_each(),
xml_find(),
xml_body(),
xml_print(),
xml_apply()
```
More are found in the doxygen reference.
## How do I write a CLI callback function?
1. You add an entry in routing_cli.cli
> example("This is a comment") <var:int32>("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.

BIN
doc/clixon_example_sdk.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

View file

@ -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
```
<rpc><edit-config><target><candidate/></target><config>
<interfaces>
<interface>
@ -28,40 +34,39 @@ clixon_netconf -f /usr/local/etc/routing.conf
</interface>
</interfaces>
</config></edit-config></rpc>]]>]]>
```
2. Getting data using netconf
-----------------------------
## Getting data using netconf
```
<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>
<rpc><get-config><source><candidate/></source><filter/></get-config></rpc>]]>]]>
<rpc><get-config><source><candidate/></source><filter type="xpath"/></get-config></rpc>]]>]]>
<rpc><get-config><source><candidate/></source><filter type="subtree"><configuration><interfaces><interface><ipv4/></interface></interfaces></configuration></filter></get-config></rpc>]]>]]>
<rpc><get-config><source><candidate/></source><filter type="xpath" select="/interfaces/interface/ipv4"/></get-config></rpc>]]>]]>
<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>
```
## 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:
```
<rpc><create-subscription><stream>ROUTING</stream></create-subscription></rpc>]]>]]>
<rpc-reply><ok/></rpc-reply>]]>]]>
<notification><event>Routing notification</event></notification>]]>]]>
<notification><event>Routing notification</event></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
```

View file

@ -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
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

View file

@ -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

View file

@ -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 <clixon/clixon_handle.h>
#include <clixon/clixon_yang.h>
#include <clixon/clixon_yang_type.h>
#include <clixon/clixon_chunk.h>
#include <clixon/clixon_event.h>
#include <clixon/clixon_string.h>
#include <clixon/clixon_file.h>
#include <clixon/clixon_xml.h>
#include <clixon/clixon_proto.h>
#include <clixon/clixon_proto_encode.h>
#include <clixon/clixon_proto_client.h>
#include <clixon/clixon_proc.h>
#include <clixon/clixon_plugin.h>
#include <clixon/clixon_options.h>
#include <clixon/clixon_xml_map.h>
#include <clixon/clixon_xml_db.h>
#include <clixon/clixon_xsl.h>
#include <clixon/clixon_json.h>
#include <clixon/clixon_plugin.h>
#include <clixon/clixon_plugin.h>
/*
* Global variables generated by Makefile

View file

@ -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_ */

View file

@ -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_ */

View file

@ -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_ */

View file

@ -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 */

View file

@ -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);

View file

@ -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 */

View file

@ -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_ */

View file

@ -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 \

View file

@ -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;
}

View file

@ -51,6 +51,7 @@
#include <sys/param.h>
#include <unistd.h>
#include <netinet/in.h>
#include <grp.h>
/* cligen */
#include <cligen/cligen.h>
@ -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), &gtmp) < 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;
}

View file

@ -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;
}

View file

@ -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);

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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 <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <grp.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/param.h>
#include <sys/user.h>
#include <sys/sysctl.h>
#include <sys/time.h>
#include <sys/resource.h>
/* 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), &gtmp) < 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;
}

View file

@ -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;
}

View file

@ -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"

View file

@ -47,9 +47,10 @@
#include <regex.h>
#include <ctype.h>
#include <cligen/cligen.h>
/* 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<strlen(str); i++){
if (unreserved(str[i]))
esc[j++] = str[i];
else{
snprintf(&esc[j], 4, "%%%02X", str[i]&0xff);
j += 3;
}
}
*escp = esc;
retval = 0;
done:
if (retval < 0 && esc)
free(esc);
return retval;
}
/*! Percent decoding according to RFC 3896
* @param[out] str Deallocate with free()
*/
int
percent_decode(char *esc,
char **strp)
{
int retval = -1;
char *str = NULL;
int i, j;
char hstr[3];
int len;
char *ptr;
/* This is max */
len = strlen(esc)+1;
if ((str = malloc(len)) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
goto done;
}
memset(str, 0, len);
j = 0;
for (i=0; i<strlen(esc); i++){
if (esc[i] == '%' && strlen(esc)-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

View file

@ -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 <a/> instead of <a></a> */
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; i<level*XML_INDENT; i++)
cprintf(cb, " ");
cprintf(cb, "%s", xml_type2str(xml_type(x)));
if (xml_namespace(x)==NULL)
cprintf(cb, " %s", xml_name(x));
else
cprintf(cb, " %s:%s", xml_namespace(x), xml_name(x));
if (xml_value(x))
cprintf(cb, " value:\"%s\"", xml_value(x));
if (x->x_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<level*XML_INDENT; i++)
cprintf(cb, " ");
cprintf(cb, "}\n");
}
return 0;
}
/*
* FSM to detect a substring
*/
@ -1009,6 +1070,7 @@ FSM(char *tag,
* Note, xt will add a top-level symbol called "top" meaning that <tree../> will look as:
* <top><tree.../></tree>
* 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 "<a><b/></a>" | 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, "</config>") < 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 */

File diff suppressed because it is too large Load diff

View file

@ -75,10 +75,11 @@
#include "clixon_string.h"
#include "clixon_queue.h"
#include "clixon_hash.h"
#include "clixon_chunk.h"
#include "clixon_handle.h"
#include "clixon_string.h"
#include "clixon_yang.h"
#include "clixon_yang_type.h"
#include "clixon_plugin.h"
#include "clixon_options.h"
#include "clixon_xml.h"
#include "clixon_xsl.h"
@ -89,9 +90,6 @@
/* Something to do with reverse engineering of junos syntax? */
#undef SPECIAL_TREATMENT_OF_NAME
/*
* A node is a leaf if it contains a body.
*/
@ -127,8 +125,9 @@ xml2txt(FILE *f, cxobj *x, int level)
{
cxobj *xe = NULL;
int children=0;
char *term;
char *term = NULL;
int retval = -1;
int encr=0;
#ifdef SPECIAL_TREATMENT_OF_NAME
cxobj *xname;
#endif
@ -140,15 +139,17 @@ xml2txt(FILE *f, cxobj *x, int level)
if (xml_type(x) == CX_BODY){
/* Kludge for escaping encrypted passwords */
if (strcmp(xml_name(xml_parent(x)), "encrypted-password")==0)
term = chunk_sprintf(__FUNCTION__, "\"%s\"", xml_value(x));
else
term = xml_value(x);
encr++;
term = xml_value(x);
}
else{
fprintf(f, "%*s", 4*level, "");
term = xml_name(x);
}
fprintf(f, "%s;\n", term);
if (encr)
fprintf(f, "\"%s\";\n", term);
else
fprintf(f, "%s;\n", term);
retval = 0;
goto done;
}
@ -240,7 +241,7 @@ xml2cli(FILE *f,
!index GT_VARS T
!index GT_ALL T
*/
bool = !leaf(x) || gt == GT_ALL || (gt == GT_VARS && !xml_index(x));
bool = !leaf(x) || gt == GT_ALL || (gt == GT_VARS);
// bool = (!x->xn_index || gt == GT_ALL);
if (bool){
if (cbuf_len(cbpre))
@ -252,12 +253,12 @@ xml2cli(FILE *f,
i = 0;
while ((xe = xml_child_each(x, xe, -1)) != NULL){
/* Dont call this if it is index and there are other following */
if (xml_index(xe) && i < nr-1)
if (0 && i < nr-1)
;
else
if (xml2cli(f, xe, cbuf_get(cbpre), gt) < 0)
goto done;
if (xml_index(xe)){ /* assume index is first, otherwise need one more while */
if (0){ /* assume index is first, otherwise need one more while */
if (gt == GT_ALL)
cprintf(cbpre, " %s", xml_name(xe));
cprintf(cbpre, " %s", xml_value(xml_child_i(xe, 0)));
@ -791,3 +792,608 @@ xml_diff(yang_spec *yspec,
done:
return retval;
}
/*! Construct an xml key format from yang statement using wildcards for keys
* Recursively construct it to the top.
* Example:
* yang: container a -> list b -> key c -> leaf d
* xpath: /a/b/%s/d
* @param[in] ys Yang statement
* @param[in] inclkey If inclkey then include key leaf (eg last leaf d in ex)
* @param[out] cbuf keyfmt
*/
static int
yang2xmlkeyfmt_1(yang_stmt *ys,
int inclkey,
cbuf *cb)
{
yang_node *yp; /* parent */
yang_stmt *ykey;
int i;
cvec *cvk = NULL; /* vector of index keys */
int retval = -1;
yp = ys->ys_parent;
if (yp != NULL &&
yp->yn_keyword != Y_MODULE &&
yp->yn_keyword != Y_SUBMODULE){
if (yang2xmlkeyfmt_1((yang_stmt *)yp, 1, cb) < 0)
goto done;
}
if (inclkey){
if (ys->ys_keyword != Y_CHOICE && ys->ys_keyword != Y_CASE)
cprintf(cb, "/%s", ys->ys_argument);
}
else{
if (ys->ys_keyword == Y_LEAF && yp && yp->yn_keyword == Y_LIST){
if (yang_key_match(yp, ys->ys_argument) == 0)
cprintf(cb, "/%s", ys->ys_argument); /* Not if leaf and key */
}
else
if (ys->ys_keyword != Y_CHOICE && ys->ys_keyword != Y_CASE)
cprintf(cb, "/%s", ys->ys_argument);
}
switch (ys->ys_keyword){
case Y_LIST:
if ((ykey = yang_find((yang_node*)ys, Y_KEY, NULL)) == NULL){
clicon_err(OE_XML, errno, "%s: List statement \"%s\" has no key",
__FUNCTION__, ys->ys_argument);
goto done;
}
/* The value is a list of keys: <key>[ <key>]* */
if ((cvk = yang_arg2cvec(ykey, " ")) == NULL)
goto done;
if (cvec_len(cvk))
cprintf(cb, "=");
/* Iterate over individual keys */
for (i=0; i<cvec_len(cvk); i++){
if (i)
cprintf(cb, ",");
cprintf(cb, "%%s");
}
break;
case Y_LEAF_LIST:
cprintf(cb, "=%%s");
break;
default:
break;
} /* switch */
retval = 0;
done:
if (cvk)
cvec_free(cvk);
return retval;
}
/*! Construct an xml key format from yang statement using wildcards for keys
* Recursively construct it to the top.
* Example:
* yang: container a -> list b -> key c -> leaf d
* xpath: /a/b=%s/d
* @param[in] ys Yang statement
* @param[in] inclkey If !inclkey then dont include key leaf
* @param[out] xkfmt XML key format. Needs to be freed after use.
*/
int
yang2xmlkeyfmt(yang_stmt *ys,
int inclkey,
char **xkfmt)
{
int retval = -1;
cbuf *cb = NULL;
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
if (yang2xmlkeyfmt_1(ys, inclkey, cb) < 0)
goto done;
if ((*xkfmt = strdup(cbuf_get(cb))) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
retval = 0;
done:
if (cb)
cbuf_free(cb);
return retval;
}
/*! Transform an xml key format and a vector of values to an XML key
* Used for actual key, eg in clicon_rpc_change(), xmldb_put_xkey()
* Example:
* xmlkeyfmt: /aaa/%s
* cvv: key=17
* xmlkey: /aaa/17
* @param[in] xkfmt XML key format, eg /aaa/%s
* @param[in] cvv cligen variable vector, one for every wildchar in xkfmt
* @param[out] xk XML key, eg /aaa/17. Free after use
* @note first and last elements of cvv are not used,..
* @see cli_dbxml where this function is called
*/
int
xmlkeyfmt2key(char *xkfmt,
cvec *cvv,
char **xk)
{
int retval = -1;
char c;
int esc=0;
cbuf *cb = NULL;
int i;
int j;
char *str;
char *strenc=NULL;
/* Sanity check */
#if 1
j = 0; /* Count % */
for (i=0; i<strlen(xkfmt); i++)
if (xkfmt[i] == '%')
j++;
if (j+2 < cvec_len(cvv)) {
clicon_log(LOG_WARNING, "%s xmlkey format string mismatch(j=%d, cvec_len=%d): %s",
xkfmt,
j,
cvec_len(cvv),
cv_string_get(cvec_i(cvv, 0)));
goto done;
}
#endif
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
j = 1; /* j==0 is cli string */
for (i=0; i<strlen(xkfmt); i++){
c = xkfmt[i];
if (esc){
esc = 0;
if (c!='s')
continue;
if ((str = cv2str_dup(cvec_i(cvv, j++))) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
if (percent_encode(str, &strenc) < 0)
goto done;
cprintf(cb, "%s", strenc);
free(strenc); strenc = NULL;
free(str); str = NULL;
}
else
if (c == '%')
esc++;
else
cprintf(cb, "%c", c);
}
if ((*xk = strdup(cbuf_get(cb))) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
retval = 0;
done:
if (cb)
cbuf_free(cb);
return retval;
}
/*! Transform an xml key format and a vector of values to an XML path
* Used to input xmldb_get() or xmldb_get_vec
* Add .* in last %s position.
* Example:
* xmlkeyfmt: /interface/%s/address/%s OLDXXX
* xmlkeyfmt: /interface=%s/address=%s
* cvv: name=eth0
* xmlkey: /interface/[name=eth0]/address
* Example2:
* xmlkeyfmt: /ip/me/%s (if key)
* cvv: -
* xmlkey: /ipv4/me/a
* @param[in] xkfmt XML key format
* @param[in] cvv cligen variable vector, one for every wildchar in xkfmt
* @param[out] xk XPATH
*/
int
xmlkeyfmt2xpath(char *xkfmt,
cvec *cvv,
char **xk)
{
int retval = -1;
char c;
int esc=0;
cbuf *cb = NULL;
int i;
int j;
char *str;
cg_var *cv;
int skip = 0;
/* Sanity check: count '%' */
#if 1
j = 0; /* Count % */
for (i=0; i<strlen(xkfmt); i++)
if (xkfmt[i] == '%')
j++;
if (j < cvec_len(cvv)-1) {
clicon_log(LOG_WARNING, "%s xmlkey format string mismatch(j=%d, cvec_len=%d): %s",
xkfmt,
j,
cvec_len(cvv),
cv_string_get(cvec_i(cvv, 0)));
// goto done;
}
#endif
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
j = 1; /* j==0 is cli string */
for (i=0; i<strlen(xkfmt); i++){
c = xkfmt[i];
if (esc){
esc = 0;
if (c!='s')
continue;
if (j == cvec_len(cvv)) /* last element */
//skip++;
;
else{
cv = cvec_i(cvv, j++);
if ((str = cv2str_dup(cv)) == NULL){
clicon_err(OE_UNIX, errno, "cv2str_dup");
goto done;
}
cprintf(cb, "[%s=%s]", cv_name_get(cv), str);
free(str);
}
}
else /* regular char */
if (c == '%')
esc++;
else{
if (skip)
skip=0;
else
if ((c == '=' || c == ',') && xkfmt[i+1]=='%')
; /* skip */
else
cprintf(cb, "%c", c);
}
}
if ((*xk = strdup4(cbuf_get(cb))) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
retval = 0;
done:
if (cb)
cbuf_free(cb);
return retval;
}
/*! Prune everything that does not pass test
* @param[in] xt XML tree with some node marked
* @param[in] flag Which flag to test for
* @param[in] test 1: test that flag is set, 0: test that flag is not set
* @param[out] upmark Set if a child (recursively) has marked set.
* The function removes all branches that does not a child that pass the test
* Purge all nodes that dont have MARK flag set recursively.
* Save all nodes that is MARK:ed or have at least one (grand*)child that is MARKed
* @code
* xml_tree_prune_flagged(xt, XML_FLAG_MARK, 1, NULL);
* @endcode
*/
int
xml_tree_prune_flagged(cxobj *xt,
int flag,
int test,
int *upmark)
{
int retval = -1;
int submark;
int mark;
cxobj *x;
cxobj *xprev;
mark = 0;
x = NULL;
xprev = x = NULL;
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
if (xml_flag(x, flag) == test?flag:0){
mark++;
xprev = x;
continue; /* mark and stop here */
}
if (xml_tree_prune_flagged(x, flag, test, &submark) < 0)
goto done;
if (submark)
mark++;
else{ /* Safe with xml_child_each if last */
if (xml_purge(x) < 0)
goto done;
x = xprev;
}
xprev = x;
}
retval = 0;
done:
if (upmark)
*upmark = mark;
return retval;
}
/*! Add default values (if not set)
* @param[in] xt XML tree with some node marked
*/
int
xml_default(cxobj *xt,
void *arg)
{
int retval = -1;
yang_stmt *ys;
yang_stmt *y;
int i;
cxobj *xc;
cxobj *xb;
char *str;
if ((ys = (yang_stmt*)xml_spec(xt)) == NULL){
clicon_log(LOG_WARNING, "%s: no xml_spec(%s)", __FUNCTION__, xml_name(xt));
retval = 0;
goto done;
}
/* Check leaf defaults */
if (ys->ys_keyword == Y_CONTAINER || ys->ys_keyword == Y_LIST){
for (i=0; i<ys->ys_len; i++){
y = ys->ys_stmt[i];
if (y->ys_keyword != Y_LEAF)
continue;
assert(y->ys_cv);
if (!cv_flag(y->ys_cv, V_UNSET)){ /* Default value exists */
if (!xml_find(xt, y->ys_argument)){
if ((xc = xml_new_spec(y->ys_argument, xt, y)) == NULL)
goto done;
if ((xb = xml_new("body", xc)) == NULL)
goto done;
xml_type_set(xb, CX_BODY);
if ((str = cv2str_dup(y->ys_cv)) == NULL){
clicon_err(OE_UNIX, errno, "cv2str_dup");
goto done;
}
if (xml_value_set(xb, str) < 0)
goto done;
free(str);
}
}
}
}
retval = 0;
done:
return retval;
}
/*! Order XML children according to YANG
* @param[in] xt XML top of tree
*/
int
xml_order(cxobj *xt,
void *arg)
{
int retval = -1;
yang_stmt *y;
yang_stmt *yc;
int i;
int j0;
int j;
cxobj *xc;
cxobj *xj;
char *yname; /* yang child name */
char *xname; /* xml child name */
if ((y = (yang_stmt*)xml_spec(xt)) == NULL){
clicon_log(LOG_WARNING, "%s: no xml_spec(%s)", __FUNCTION__, xml_name(xt));
retval = 0;
goto done;
}
j0 = 0;
/* Go through xml children and ensure they are same order as yspec children */
for (i=0; i<y->ys_len; i++){
yc = y->ys_stmt[i];
if (!yang_is_syntax(yc))
continue;
yname = yc->ys_argument;
/* First go thru xml children with same name */
for (; j0<xml_child_nr(xt); j0++){
xc = xml_child_i(xt, j0);
if (xml_type(xc) != CX_ELMNT)
continue;
xname = xml_name(xc);
if (strcmp(xname, yname))
break;
}
/* Now we have children not with same name */
for (j=j0; j<xml_child_nr(xt); j++){
xc = xml_child_i(xt, j);
if (xml_type(xc) != CX_ELMNT)
continue;
xname = xml_name(xc);
if (strcmp(xname, yname))
continue;
/* reorder */
xj = xml_child_i(xt, j0);
xml_child_i_set(xt, j0, xc);
xml_child_i_set(xt, j, xj);
j0++;
}
}
retval = 0;
done:
return retval;
}
/*! Sanitize an xml tree: xml node has matching yang_stmt pointer
* @param[in] xt XML top of tree
*/
int
xml_sanity(cxobj *xt,
void *arg)
{
int retval = -1;
yang_stmt *ys;
char *name;
if ((ys = (yang_stmt*)xml_spec(xt)) == NULL){
clicon_log(LOG_WARNING, "%s: no xml_spec(%s)", __FUNCTION__, xml_name(xt));
retval = 0;
goto done;
}
name = xml_name(xt);
if (ys==NULL){
clicon_err(OE_XML, 0, "No spec for xml node %s", name);
goto done;
}
if (strstr(ys->ys_argument, name)==NULL){
clicon_err(OE_XML, 0, "xml node name '%s' does not match yang spec arg '%s'",
name, ys->ys_argument);
goto done;
}
retval = 0;
done:
return retval;
}
/*! Translate from restconf api-path in cvv form to xml xpath
* eg a/b=c -> a/[b=c]
* @param[in] yspec Yang spec
* @param[in] pcvec api-path as cvec
* @param[in] pi Offset of cvec, where api-path starts
* @param[out] path The xpath as cbuf variable string, must be initializeed
* The api-path has some wierd encoding, use api_path2xpath() if you are
* confused.
* It works like this:
* Assume origin incoming path is
* "www.foo.com/restconf/a/b=c", pi is 2 and pcvec is:
* ["www.foo.com" "restconf" "a" "b=c"]
* which means the api-path is ["a" "b=c"] corresponding to "a/b=c"
* @code
* cbuf *xpath = cbuf_new();
* if (api_path2xpath_cvv(yspec, cvv, i, xpath)
* err;
* ... access xpath as cbuf_get(xpath)
* cbuf_free(xpath)
* @endcode
* @see api_path2xpath for string api-path argument
*/
int
api_path2xpath_cvv(yang_spec *yspec,
cvec *cvv,
int offset,
cbuf *xpath)
{
int retval = -1;
int i;
cg_var *cv;
char *name;
cvec *cvk = NULL; /* vector of index keys */
yang_stmt *y = NULL;
char *val;
char *v;
yang_stmt *ykey;
cg_var *cvi;
for (i=offset; i<cvec_len(cvv); i++){
cv = cvec_i(cvv, i);
name = cv_name_get(cv);
clicon_debug(1, "[%d] cvname:%s", i, name);
clicon_debug(1, "cv2str%d", cv2str(cv, NULL, 0));
if (i == offset){
if ((y = yang_find_topnode(yspec, name)) == NULL){
clicon_err(OE_UNIX, errno, "No yang node found: %s", name);
goto done;
}
}
else{
assert(y!=NULL);
if ((y = yang_find_syntax((yang_node*)y, name)) == NULL){
clicon_err(OE_UNIX, errno, "No yang node found: %s", name);
goto done;
}
}
/* Check if has value, means '=' */
if (cv2str(cv, NULL, 0) > 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);
goto done;
}
clicon_debug(1, "ykey:%s", ykey->ys_argument);
/* The value is a list of keys: <key>[ <key>]* */
if ((cvk = yang_arg2cvec(ykey, " ")) == NULL)
goto done;
cvi = NULL;
/* Iterate over individual yang keys */
cprintf(xpath, "/%s", name);
v = val;
while ((cvi = cvec_each(cvk, cvi)) != NULL){
cprintf(xpath, "[%s=%s]", cv_string_get(cvi), v);
v += strlen(v)+1;
}
if (val)
free(val);
}
else{
cprintf(xpath, "%s%s", (i==offset?"":"/"), name);
}
}
retval = 0;
done:
return retval;
}
/*! Translate from restconf api-path to xml xpath
* eg a/b=c -> a/[b=c]
* @param[in] yspec Yang spec
* @param[in] str API-path as string
* @param[out] path The xpath as cbuf variable string, must be initializeed
* @code
* cbuf *xpath = cbuf_new();
* if (api_path2xpath(yspec, "a/b=c", xpath)
* err;
* ... access xpath as cbuf_get(xpath)
* cbuf_free(xpath)
* @endcode
*/
int
api_path2xpath(yang_spec *yspec,
char *api_path,
cbuf *xpath)
{
int retval = -1;
cvec *api_path_cvv = NULL;
/* rest url eg /album=ricky/foo */
if (str2cvec(api_path, '/', '=', &api_path_cvv) < 0)
goto done;
if (api_path2xpath_cvv(yspec, api_path_cvv, 0, xpath) < 0)
goto done;
retval = 0;
done:
if (api_path_cvv)
cvec_free(api_path_cvv);
return retval;
}

View file

@ -175,8 +175,10 @@ xml_parse_endslash_post(struct xml_parse_yacc_arg *ya)
return 0;
}
/*! Called at </name> */
static int
xml_parse_bslash1(struct xml_parse_yacc_arg *ya, char *name)
xml_parse_bslash1(struct xml_parse_yacc_arg *ya,
char *name)
{
int retval = -1;
cxobj *x = ya->ya_xelement;
@ -199,8 +201,10 @@ xml_parse_bslash1(struct xml_parse_yacc_arg *ya, char *name)
;
else{
xc = NULL;
while ((xc = xml_child_each(x, xc, CX_BODY)) != NULL)
xml_value_set(xc, ""); /* XXX remove */
while ((xc = xml_child_each(x, xc, CX_BODY)) != NULL) {
xml_purge(xc);
xc = NULL; /* reset iterator */
}
}
}
retval = 0;
@ -209,8 +213,11 @@ xml_parse_bslash1(struct xml_parse_yacc_arg *ya, char *name)
return retval;
}
/*! Called at </namespace:name> */
static int
xml_parse_bslash2(struct xml_parse_yacc_arg *ya, char *namespace, char *name)
xml_parse_bslash2(struct xml_parse_yacc_arg *ya,
char *namespace,
char *name)
{
int retval = -1;
cxobj *x = ya->ya_xelement;

View file

@ -358,6 +358,13 @@ xpath_parse(char *xpath,
else if (strncmp(s,"descendant-or-self::", strlen("descendant-or-self::"))==0){
xpath_element_new(A_DESCENDANT_OR_SELF, s+strlen("descendant-or-self::"), &xpnext);
}
#if 1
else if (strncmp(s,"..", strlen(".."))==0) /* abbreviatedstep */
xpath_element_new(A_PARENT, s+strlen(".."), &xpnext);
#else
else if (strncmp(s,"..", strlen(s))==0) /* abbreviatedstep */
xpath_element_new(A_PARENT, NULL, &xpnext);
#endif
#if 1 /* Problems with .[userid=1321] */
else if (strncmp(s,".", strlen("."))==0)
xpath_element_new(A_SELF, s+strlen("."), &xpnext);
@ -368,13 +375,7 @@ xpath_parse(char *xpath,
else if (strncmp(s,"self::", strlen("self::"))==0)
xpath_element_new(A_SELF, s+strlen("self::"), &xpnext);
#if 1
else if (strncmp(s,"..", strlen(".."))==0) /* abbreviatedstep */
xpath_element_new(A_PARENT, s+strlen(".."), &xpnext);
#else
else if (strncmp(s,"..", strlen(s))==0) /* abbreviatedstep */
xpath_element_new(A_PARENT, NULL, &xpnext);
#endif
else if (strncmp(s,"parent::", strlen("parent::"))==0)
xpath_element_new(A_PARENT, s+strlen("parent::"), &xpnext);
else if (strncmp(s,"ancestor::", strlen("ancestor::"))==0)
@ -1076,7 +1077,7 @@ int
main(int argc, char **argv)
{
int i;
cxobj **xv;
cxobj **xv
cxobj *x;
cxobj *xn;
size_t xlen = 0;

View file

@ -66,7 +66,7 @@
#include "clixon_file.h"
#include "clixon_yang.h"
#include "clixon_hash.h"
#include "clixon_chunk.h"
#include "clixon_plugin.h"
#include "clixon_options.h"
#include "clixon_yang_type.h"
#include "clixon_yang_parse.h"
@ -160,6 +160,10 @@ static const struct map_str2int ykmap[] = {
{NULL, -1}
};
/*! Create new yang specification
* @retval yspec Free with yspec_free()
* @retval NULL Error
*/
yang_spec *
yspec_new(void)
{
@ -174,6 +178,10 @@ yspec_new(void)
return yspec;
}
/*! Create new yang node/statement
* @retval ys Free with ys_free()
* @retval NULL Error
*/
yang_stmt *
ys_new(enum rfc_6020 keyw)
{
@ -1382,20 +1390,21 @@ yang_parse_file(clicon_handle h,
* @retval 1 Match founbd, Most recent entry returned in fbuf
* @retval 0 No matching entry found
* @retval -1 Error
*/static int
*/
static int
yang_parse_find_match(clicon_handle h,
const char *yang_dir,
const char *module,
cbuf *fbuf)
{
int retval = -1;
struct dirent *dp;
struct dirent *dp = NULL;
int ndp;
cbuf *regex = NULL;
char *regexstr;
if ((regex = cbuf_new()) == NULL){
clicon_err(OE_YANG, errno, "%s: cbuf_new", __FUNCTION__);
clicon_err(OE_YANG, errno, "cbuf_new");
goto done;
}
cprintf(regex, "^%s.*(.yang)$", module);
@ -1403,8 +1412,7 @@ yang_parse_find_match(clicon_handle h,
if ((ndp = clicon_file_dirent(yang_dir,
&dp,
regexstr,
S_IFREG,
__FUNCTION__)) < 0)
S_IFREG)) < 0)
goto done;
/* Entries are sorted, last entry should be most recent date */
if (ndp != 0){
@ -1416,7 +1424,8 @@ yang_parse_find_match(clicon_handle h,
done:
if (regex)
cbuf_free(regex);
unchunk_group(__FUNCTION__);
if (dp)
free(dp);
return retval;
}
@ -1540,11 +1549,11 @@ yang_parse1(clicon_handle h,
/*! Parse top yang module including all its sub-modules. Expand and populate yang tree
*
* @param h CLICON handle
* @param yang_dir Directory where all YANG module files reside
* @param module Name of main YANG module. More modules may be parsed if imported
* @param revision Optional module revision date
* @param ysp Yang specification. Should ave been created by caller using yspec_new
* @param[in] h CLICON handle
* @param[in] yang_dir Directory where all YANG module files reside
* @param[in] module Name of main YANG module. More modules may be parsed if imported
* @param[in] revision Optional module revision date
* @param[out] ysp Yang specification. Should ave been created by caller using yspec_new
* @retval 0 Everything OK
* @retval -1 Error encountered
* The database symbols are inserted in alphabetical order.

View file

@ -63,7 +63,7 @@
#include "clixon_handle.h"
#include "clixon_yang.h"
#include "clixon_hash.h"
#include "clixon_chunk.h"
#include "clixon_plugin.h"
#include "clixon_options.h"
#include "clixon_yang.h"
#include "clixon_yang_type.h"

View file

@ -1,9 +0,0 @@
This directory contains testing code for clixon and the example
routing application:
clixon A top-level script clones clixon in /tmp and starts all.sh
You can _copy_ this file (review it) and place as cron script
all.sh Run through all tests named 'test*.sh' in this directory.
Therefore, if you place a test in this directory matching
'test*.sh' it will be run automatically.
test1.sh First test
test2.sh Second test,...

11
test/README.md Normal file
View file

@ -0,0 +1,11 @@
# Clixon tests
This directory contains testing code for clixon and the example
routing application:
- clixon A top-level script clones clixon in /tmp and starts all.sh. You can copy this file (review it first) and place as cron script
- all.sh Run through all tests named 'test*.sh' in this directory. Therefore, if you place a test in this directory matching 'test*.sh' it will be run automatically.
- test1.sh CLI tests
- test2.sh Netconf tests
- test3.sh Restconf tests
- test4.sh Yang tests
- test5.sh Datastore tests

View file

@ -17,9 +17,8 @@ new(){
# sleep 1
}
# clicon_cli tester. First arg is command and second is expected outcome
# clixon tester. First arg is command and second is expected outcome
expectfn(){
cmd=$1
expect=$2
ret=`$cmd`
@ -40,7 +39,8 @@ expectfn(){
fi
}
# clicon_cli tester. First arg is command and second is expected outcome
# clixon tester. First arg is command second is stdin and
# third is expected outcome
expecteof(){
cmd=$1
input=$2
@ -61,7 +61,8 @@ EOF
fi
}
# clicon_cli tester. First arg is command and second is expected outcome
# clixon tester. First arg is command second is stdin and
# third is expected outcome, fourth is how long to wait
expectwait(){
cmd=$1
input=$2

View file

@ -26,6 +26,8 @@ sudo clixon_backend -If $clixon_cf
if [ $? -ne 0 ]; then
err
fi
new "cli tests"
new "cli configure top"
expectfn "$clixon_cli -1f $clixon_cf set interfaces" ""

View file

@ -20,14 +20,44 @@ sudo clixon_backend -If $clixon_cf
if [ $? -ne 0 ]; then
err
fi
new "netconf tests"
new "netconf get empty config"
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc message-id=\"101\"><get-config><source><candidate/></source></get-config></rpc>]]>]]>" "^<rpc-reply message-id=\"101\"><data/></rpc-reply>]]>]]>$"
expecteof "$clixon_netconf -qf $clixon_cf" '<rpc message-id="101"><get-config><source><candidate/></source></get-config></rpc>]]>]]>' '^<rpc-reply message-id="101"><data/></rpc-reply>]]>]]>$'
new "Add subtree eth0 using none which should not change anything"
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><edit-config><default-operation>none</default-operation><target><candidate/></target><config><interfaces><interface><name>eth0</name></interface></interfaces></config></edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "Check nothing added"
expecteof "$clixon_netconf -qf $clixon_cf" '<rpc message-id="101"><get-config><source><candidate/></source></get-config></rpc>]]>]]>' '^<rpc-reply message-id="101"><data/></rpc-reply>]]>]]>$'
new "Add subtree eth0 using none and create which should add eth0"
expecteof "$clixon_netconf -qf $clixon_cf" '<rpc><edit-config><target><candidate/></target><config><interfaces><interface operation="create"><name>eth0</name><type>eth</type></interface></interfaces></config><default-operation>none</default-operation> </edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "Check eth0 added using xpath"
expecteof "$clixon_netconf -qf $clixon_cf" '<rpc><get-config><source><candidate/></source><filter type="xpath" select="/interfaces/interface[name=eth0]"/></get-config></rpc>]]>]]>' "^<rpc-reply><data><config><interfaces><interface><name>eth0</name><type>eth</type><enabled>true</enabled></interface></interfaces></config></data></rpc-reply>]]>]]>$"
new "Re-create same eth0 which should generate error"
expecteof "$clixon_netconf -qf $clixon_cf" '<rpc><edit-config><target><candidate/></target><config><interfaces><interface operation="create"><name>eth0</name><type>eth</type></interface></interfaces></config><default-operation>none</default-operation> </edit-config></rpc>]]>]]>' "^<rpc-reply><rpc-error>"
new "Delete eth0 using none config"
expecteof "$clixon_netconf -qf $clixon_cf" '<rpc><edit-config><target><candidate/></target><config><interfaces><interface operation="delete"><name>eth0</name><type>eth</type></interface></interfaces></config><default-operation>none</default-operation> </edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "Check deleted eth0"
expecteof "$clixon_netconf -qf $clixon_cf" '<rpc message-id="101"><get-config><source><candidate/></source></get-config></rpc>]]>]]>' '^<rpc-reply message-id="101"><data><config><interfaces/></config></data></rpc-reply>]]>]]>$'
new "Re-Delete eth0 using none should generate error"
expecteof "$clixon_netconf -qf $clixon_cf" '<rpc><edit-config><target><candidate/></target><config><interfaces><interface operation="delete"><name>eth0</name><type>eth</type></interface></interfaces></config><default-operation>none</default-operation> </edit-config></rpc>]]>]]>' "^<rpc-reply><rpc-error>"
new "netconf edit config"
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><edit-config><target><candidate/></target><config><interfaces><interface><name>eth0</name></interface><interface><name>eth1</name><enabled>true</enabled><ipv4><address><ip>9.2.3.4</ip><prefix-length>24</prefix-length></address></ipv4></interface></interfaces></config></edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf get config xpath"
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><get-config><source><candidate/></source><filter type=\"xpath\" select=\"/interfaces/interface[name=eth1]/enabled\"/></get-config></rpc>]]>]]>" "^<rpc-reply><data><config><interfaces><interface><enabled>true</enabled></interface></interfaces></config></data></rpc-reply>]]>]]>$"
expecteof "$clixon_netconf -qf $clixon_cf" '<rpc><get-config><source><candidate/></source><filter type="xpath" select="/interfaces/interface[name=eth1]/enabled"/></get-config></rpc>]]>]]>' "^<rpc-reply><data><config><interfaces><interface><enabled>true</enabled></interface></interfaces></config></data></rpc-reply>]]>]]>$"
new "netconf get config xpath parent"
expecteof "$clixon_netconf -qf $clixon_cf" '<rpc><get-config><source><candidate/></source><filter type="xpath" select="/interfaces/interface[name=eth1]/enabled/../.."/></get-config></rpc>]]>]]>' "^<rpc-reply><data><config><interfaces><interface><name>eth0</name><enabled>true</enabled></interface><interface><name>eth1</name><enabled>true</enabled><ipv4><enabled>true</enabled><forwarding>false</forwarding><address><ip>9.2.3.4</ip><prefix-length>24</prefix-length></address></ipv4></interface></interfaces></config></data></rpc-reply>]]>]]>$"
new "netconf validate missing type"
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><rpc-error>"
@ -48,23 +78,11 @@ new "netconf commit"
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf edit config replace"
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><edit-config><target><candidate/></target><config><interfaces><interface><name>eth2</name><type>eth</type></interface></interfaces></config><default-operation>merge</default-operation> </edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><edit-config><target><candidate/></target><config><interfaces><interface><name>eth2</name><type>eth</type></interface></interfaces></config><default-operation>merge</default-operation></edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf get replaced config"
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>" "^<rpc-reply><data><config><interfaces><interface><name>eth1</name><type>eth</type><enabled>true</enabled></interface><interface><name>eth2</name><type>eth</type><enabled>true</enabled></interface></interfaces></config></data></rpc-reply>]]>]]>$"
new "netconf edit config create"
expecteof "$clixon_netconf -qf $clixon_cf" '<rpc><edit-config><target><candidate/></target><config><interfaces><interface operation="create"><name>eth3</name><type>eth</type></interface></interfaces></config><default-operation>none</default-operation> </edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf edit config create 2nd"
expecteof "$clixon_netconf -qf $clixon_cf" '<rpc><edit-config><target><candidate/></target><config><interfaces><interface operation="create"><name>eth3</name><type>eth</type></interface></interfaces></config><default-operation>merge</default-operation> </edit-config></rpc>]]>]]>' "^<rpc-reply><rpc-error>"
new "netconf edit config delete"
expecteof "$clixon_netconf -qf $clixon_cf" '<rpc><edit-config><target><candidate/></target><config><interfaces><interface operation="delete"><name>eth3</name><type>eth</type></interface></interfaces></config><default-operation>none</default-operation> </edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf get delete config"
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>" "^<rpc-reply><data><config><interfaces><interface><name>eth1</name><type>eth</type><enabled>true</enabled></interface><interface><name>eth2</name><type>eth</type><enabled>true</enabled></interface></interfaces></config></data></rpc-reply>]]>]]>$"
new "netconf discard-changes"
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><discard-changes/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"

View file

@ -24,37 +24,46 @@ sudo start-stop-daemon -S -q -o -b -x /www-data/clixon_restconf -d /www-data -c
sleep 1
new "restconf tests"
new "restconf options"
expectfn "curl -i -s -X OPTIONS http://localhost/restconf/data" "Allow: OPTIONS,HEAD,GET,POST,PUT,DELETE"
new "restconf get empty config"
expectfn "curl -sG http://localhost/restconf/data" "^null $"
new "restconf put config"
expectfn 'curl -sX POST -d {"interfaces":{"interface":[{"name":"eth1","type":"eth","enabled":"true"},{"name":"eth0","type":"eth","enabled":"true"}]}} http://localhost/restconf/data' ""
new "restconf get config"
expectfn "curl -sG http://localhost/restconf/data" '{"interfaces": {"interface": \[{"name": "eth1","type": "eth","enabled": "true"},{ "name": "eth0","type": "eth","enabled": "true"}\]}}
$'
new "restconf head"
expectfn "curl -s -I http://localhost/restconf/data" "Content-Type: application/yang.data\+json"
new "restconf POST config"
expectfn 'curl -sX POST -d {"type":"eth"} http://localhost/restconf/data/interfaces/interface=eth4' ""
new "restconf get empty config"
expectfn "curl -sG http://localhost/restconf/data" "^null $"
new "restconf DELETE config"
expectfn 'curl -sX DELETE http://localhost/restconf/data/interfaces/interface=eth0' ""
#
new "Add subtree eth0,eth1 using POST"
expectfn 'curl -sX POST -d {"interfaces":{"interface":[{"name":"eth0","type":"eth","enabled":"true"},{"name":"eth1","type":"eth","enabled":"true"}]}} http://localhost/restconf/data' ""
new "restconf get config"
expectfn "curl -sG http://localhost/restconf/data" '{"interfaces": {"interface": \[{"name": "eth1","type": "eth","enabled": "true"},{ "name": "eth4","type": "eth","enabled": "true"}\]}}
new "Check eth0 added"
expectfn "curl -sG http://localhost/restconf/data" '{"interfaces": {"interface": \[{"name": "eth0","type": "eth","enabled": "true"},{ "name": "eth1","type": "eth","enabled": "true"}\]}}
$'
new "restconf PATCH config"
expectfn 'curl -sX PATCH -d {"type":"eth"} http://localhost/restconf/data/interfaces/interface=eth4' ""
new "Re-post eth0 which should generate error"
expectfn 'curl -sX POST -d {"interfaces":{"interface":{"name":"eth0","type":"eth","enabled":"true"}}} http://localhost/restconf/data' "Not Found"
new "restconf PUT"
expectfn 'curl -sX PUT -d {"type":"eth"} http://localhost/restconf/data/interfaces/interface=eth5' ""
new "delete eth0"
expectfn 'curl -sX DELETE http://localhost/restconf/data/interfaces/interface=eth0' ""
new "Check deleted eth0"
expectfn 'curl -sG http://localhost/restconf/data' '{"interfaces": {"interface": {"name": "eth1","type": "eth","enabled": "true"}}}
$'
new "Re-Delete eth0 using none should generate error"
expectfn 'curl -sX DELETE http://localhost/restconf/data/interfaces/interface=eth0' "Not Found"
if false; then # XXX restconf dont support patch and put fully
new "restconf PATCH config"
expectfn 'curl -sX PATCH -d {"type":"eth"} http://localhost/restconf/data/interfaces/interface=eth4' ""
new "restconf PUT"
expectfn 'curl -sX PUT -d {"type":"eth"} http://localhost/restconf/data/interfaces/interface=eth5' ""
fi
new "Kill restconf daemon"
#sudo pkill -u www-data clixon_restconf

View file

@ -1,5 +1,5 @@
#!/bin/bash
# Test2: backend and netconf basic functionality
# Test4: Yang specifics: multi-keys and empty type
# include err() and new() functions
. ./lib.sh
@ -26,6 +26,19 @@ module ietf-ip{
leaf d {
type empty;
}
container f {
leaf-list e {
type string;
}
}
leaf g {
type string;
}
container h {
leaf j {
type string;
}
}
}
}
EOF
@ -52,6 +65,15 @@ expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test" "<rpc><commit/></rpc>]]>
new "netconf get config xpath"
expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test" "<rpc><get-config><source><candidate/></source><filter type=\"xpath\" select=\"/x/y[a=1][b=2]\"/></get-config></rpc>]]>]]>" "^<rpc-reply><data><config><x><y><a>1</a><b>2</b><c>5</c></y></x></config></data></rpc-reply>]]>]]>$"
new "netconf edit leaf-list"
expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test" "<rpc><edit-config><target><candidate/></target><config><x><f><e>hej</e><e>hopp</e></f></x></config></edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf get leaf-list"
expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test" "<rpc><get-config><source><candidate/></source><filter type=\"xpath\" select=\"/x/f/e\"/></get-config></rpc>]]>]]>" "^<rpc-reply><data><config><x><f><e>hej</e><e>hopp</e></f></x></config></data></rpc-reply>]]>]]>$"
new "netconf get leaf-list path"
expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test" "<rpc><get-config><source><candidate/></source><filter type=\"xpath\" select=\"/x/f[e=hej]\"/></get-config></rpc>]]>]]>" "^<rpc-reply><data><config><x><f><e>hej</e><e>hopp</e></f></x></config></data></rpc-reply>]]>]]>$"
new "Kill backend"
# Check if still alive
pid=`pgrep clixon_backend`

150
test/test5.sh Executable file
View file

@ -0,0 +1,150 @@
#!/bin/bash
# Test5: datastore
# include err() and new() functions
. ./lib.sh
datastore=datastore_client
cat <<EOF > /tmp/ietf-ip.yang
module ietf-ip{
container x {
list y {
key "a b";
leaf a {
type string;
}
leaf b {
type string;
}
leaf c {
type string;
}
}
leaf d {
type empty;
}
container f {
leaf-list e {
type string;
}
}
leaf g {
type string;
}
container h {
leaf j {
type string;
}
}
}
}
EOF
db='<config><x><y><a>1</a><b>2</b><c>first-entry</c></y><y><a>1</a><b>3</b><c>second-entry</c></y><y><a>2</a><b>3</b><c>third-entry</c></y><d/><f><e>a</e><e>b</e><e>c</e></f><g>astring</g></x></config>'
run(){
name=$1
dir=/tmp/$name
if [ ! -d $dir ]; then
mkdir $dir
fi
rm -rf $dir/*
conf="-d candidate -b $dir -p ../datastore/$name/$name.so -y /tmp -m ietf-ip"
# echo "conf:$conf"
new "datastore $name init"
expectfn "$datastore $conf init" ""
# Whole tree operations
new "datastore $name put all replace"
expectfn "$datastore $conf put replace / $db" ""
new "datastore $name get"
expectfn "$datastore $conf get /" "^$db$"
new "datastore $name put all remove"
expectfn "$datastore $conf put remove /"
new "datastore $name get"
expectfn "$datastore $conf get /" "^<config/>$"
new "datastore $name put all merge"
expectfn "$datastore $conf put merge / $db" ""
new "datastore $name get"
expectfn "$datastore $conf get /" "^$db$"
new "datastore $name put all delete"
expectfn "$datastore $conf put remove /"
new "datastore $name get"
expectfn "$datastore $conf get /" "^<config/>$"
new "datastore $name put all create"
expectfn "$datastore $conf put create / $db" ""
new "datastore $name get"
expectfn "$datastore $conf get /" "^$db$"
new "datastore $name put top create"
expectfn "$datastore $conf put create / <config><x/></config>" "" # error
# Single key operations
# leaf
new "datastore $name put all delete"
expectfn "$datastore $conf delete" ""
new "datastore $name init"
expectfn "$datastore $conf init" ""
new "datastore $name create leaf"
expectfn "$datastore $conf put create /x/y=1,3/c <c>newentry</c>"
new "datastore $name create leaf"
expectfn "$datastore $conf put create /x/y=1,3/c <c>newentry</c>"
new "datastore $name delete leaf"
expectfn "$datastore $conf put delete /x/y=1,3"
new "datastore $name replace leaf"
expectfn "$datastore $conf put create /x/y=1,3/c <c>newentry</c>"
new "datastore $name remove leaf"
expectfn "$datastore $conf put remove /x/g"
new "datastore $name remove leaf"
expectfn "$datastore $conf put remove /x/y=1,3/c"
new "datastore $name delete leaf"
expectfn "$datastore $conf put delete /x/g"
new "datastore $name merge leaf"
expectfn "$datastore $conf put merge /x/g <g>nalle</g>"
new "datastore $name replace leaf"
expectfn "$datastore $conf put replace /x/g <g>nalle</g>"
new "datastore $name merge leaf"
expectfn "$datastore $conf put merge /x/y=1,3/c <c>newentry</c>"
new "datastore $name replace leaf"
expectfn "$datastore $conf put replace /x/y=1,3/c <c>newentry</c>"
new "datastore $name create leaf"
expectfn "$datastore $conf put create /x/h <h><j>aaa</j></h>"
new "datastore $name create leaf"
expectfn "$datastore $conf put create /x/y=1,3/c <c>newentry</c>"
#leaf-list
rm -rf $dir
}
#run keyvalue # cant get the put to work
run text