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,33 +1,27 @@
# ***** BEGIN LICENSE BLOCK ***** # Clixon CHANGELOG
#
# Copyright (C) 2009-2017 Olof Hagsand and Benny Holmgren ## 3.3.0
#
# This file is part of CLIXON May 2017
#
# Licensed under the Apache License, Version 2.0 (the "License"); - Datastore text module is now default.
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at - Refined netconf "none" semantics in tests and text datastore
#
# http://www.apache.org/licenses/LICENSE-2.0 - Moved apps/dbctrl to datastore/
#
# Unless required by applicable law or agreed to in writing, software - Added connect/disconnect/getopt/setopt and handle to xmldb API
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - Added datastore 'text'
# See the License for the specific language governing permissions and
# limitations under the License. - Configure (autoconf) changes
# Removed libcurl dependency
# Alternatively, the contents of this file may be used under the terms of Disable restconf (and fastcgi) with configure --disable-restconf
# the GNU General Public License Version 3 or later (the "GPL"), Disable keyvalue datastore (and qdbm) with configure --disable-keyvalue
# 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 - Created xmldb plugin api
# under the terms of the GPL, and not to allow others to Moved qdbm, chunk and xmldb to datastore keyvalue directories
# use your version of this file under the terms of Apache License version 2, Removed all other clixon dependency on chunk code
# 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 *****
- cli_copy_config added as generic cli command - cli_copy_config added as generic cli command
- cli_show_config added as generic cli command - cli_show_config added as generic cli command

View file

@ -52,7 +52,7 @@ LIBS = @LIBS@
INCLUDES = -I. -I@srcdir@ @INCLUDES@ INCLUDES = -I. -I@srcdir@ @INCLUDES@
SHELL = /bin/sh 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 .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 Clixon is an automatic configuration manager where you generate
specification generate interactive CLI, NETCONF, RESTCONF and embedded interactive CLI, NETCONF, RESTCONF and embedded databases with
databases with transaction support. transaction support from a YANG specification.
CLIXON is a fork of CLICON where legacy key specification has been Table of contents
replaced completely by YANG. This means that legacy CLICON =================
applications such as CLICON/ROST does not run on CLIXON. * [Documentation](#documentation)
* [Installation](#installation)
* [Dependencies](#dependencies)
* [Licenses](#licenses)
* [Background](#background)
* [Clixon SDK](#SDK)
Presentations and tutorial is found on the [CLICON project Documentation
page](http://www.clicon.org) =============
- [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: 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 One [example application](example/README.md) is provided, a IETF IP YANG datamodel with
> make # Compile generated CLI and configuration interface.
> sudo make install # Install libs, binaries, and config-files
> sudo make install-include # Install include files (for compiling)
One example applications is provided, the IETF IP YANG datamodel with generated CLI and configuration interface. It all origins from work at Dependencies
[KTH](http://www.csc.kth.se/~olofh/10G_OSR) ============
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 - [CLIgen](http://www.cligen.se) is required for building Clixon. If you need
to build and install CLIgen: to build and install CLIgen:
```
git clone https://github.com/olofhagsand/cligen.git git clone https://github.com/olofhagsand/cligen.git
cd cligen; configure; make; make install 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. 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@ CFLAGS = @CFLAGS@
LDFLAGS = @LDFLAGS@ LDFLAGS = @LDFLAGS@
LIBS = @LIBS@ LIBS = @LIBS@
with_restconf = @with_restconf@
SHELL = /bin/sh 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) .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){ if ((xc = xpath_first(xn, "config")) != NULL){
/* XXX see from_client_xmlput() */
if (xmldb_put(h, target, operation, api_path, xc) < 0){ if (xmldb_put(h, target, operation, api_path, xc) < 0){
cprintf(cbret, "<rpc-reply><rpc-error>" cprintf(cbret, "<rpc-reply><rpc-error>"
"<error-tag>operation-failed</error-tag>" "<error-tag>operation-failed</error-tag>"
@ -438,7 +437,7 @@ from_client_unlock(clicon_handle h,
goto ok; goto ok;
} }
else{ else{
xmldb_unlock(h, db, pid); xmldb_unlock(h, db);
if (cprintf(cbret, "<rpc-reply><ok/></rpc-reply>") < 0) if (cprintf(cbret, "<rpc-reply><ok/></rpc-reply>") < 0)
goto done; goto done;
} }
@ -496,7 +495,7 @@ from_client_kill_session(clicon_handle h,
if (1 || (kill (pid, 0) != 0 && errno == ESRCH)){ /* Nothing there */ if (1 || (kill (pid, 0) != 0 && errno == ESRCH)){ /* Nothing there */
/* clear from locks */ /* clear from locks */
if (xmldb_islocked(h, db) == pid) if (xmldb_islocked(h, db) == pid)
xmldb_unlock(h, db, pid); xmldb_unlock(h, db);
} }
else{ /* failed to kill client */ else{ /* failed to kill client */
cprintf(cbret, "<rpc-reply><rpc-error>" cprintf(cbret, "<rpc-reply><rpc-error>"
@ -622,7 +621,6 @@ from_client_delete_config(clicon_handle h,
piddb); piddb);
goto ok; goto ok;
} }
if (xmldb_delete(h, target) < 0){ if (xmldb_delete(h, target) < 0){
cprintf(cbret, "<rpc-reply><rpc-error>" cprintf(cbret, "<rpc-reply><rpc-error>"
"<error-tag>operation-failed</error-tag>" "<error-tag>operation-failed</error-tag>"
@ -633,7 +631,7 @@ from_client_delete_config(clicon_handle h,
"</rpc-error></rpc-reply>", clicon_err_reason); "</rpc-error></rpc-reply>", clicon_err_reason);
goto ok; goto ok;
} }
if (xmldb_init(h, target) < 0){ if (xmldb_create(h, target) < 0){
cprintf(cbret, "<rpc-reply><rpc-error>" cprintf(cbret, "<rpc-reply><rpc-error>"
"<error-tag>operation-failed</error-tag>" "<error-tag>operation-failed</error-tag>"
"<error-type>protocol</error-type>" "<error-type>protocol</error-type>"
@ -872,10 +870,22 @@ from_client_msg(clicon_handle h,
assert(cbuf_len(cbret)); assert(cbuf_len(cbret));
clicon_debug(1, "%s %s", __FUNCTION__, cbuf_get(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 (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"); clicon_log(LOG_WARNING, "client rpc reset");
break;
default:
goto done; goto done;
} }
}
// ok: // ok:
retval = 0; retval = 0;
done: done:

View file

@ -41,7 +41,7 @@
* Prototypes * Prototypes
* not exported. * not exported.
*/ */
/* backend handles */ /* backend handles. Defined in clixon_backend_handle.c */
clicon_handle backend_handle_init(void); clicon_handle backend_handle_init(void);
int backend_handle_exit(clicon_handle h); int backend_handle_exit(clicon_handle h);

View file

@ -73,11 +73,11 @@
#include "backend_handle.h" #include "backend_handle.h"
/* Command line options to be passed to getopt(3) */ /* 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 */ /*! Terminate. Cannot use h after this */
static int static int
config_terminate(clicon_handle h) backend_terminate(clicon_handle h)
{ {
yang_spec *yspec; yang_spec *yspec;
char *pidfile = clicon_backend_pidfile(h); char *pidfile = clicon_backend_pidfile(h);
@ -91,19 +91,18 @@ config_terminate(clicon_handle h)
unlink(pidfile); unlink(pidfile);
if (sockpath) if (sockpath)
unlink(sockpath); unlink(sockpath);
xmldb_plugin_unload(h); /* unload storage plugin */
backend_handle_exit(h); /* Cannot use h after this */ backend_handle_exit(h); /* Cannot use h after this */
event_exit(); event_exit();
clicon_log_register_callback(NULL, NULL); clicon_log_register_callback(NULL, NULL);
clicon_debug(1, "%s done", __FUNCTION__); clicon_debug(1, "%s done", __FUNCTION__);
if (debug)
chunk_check(stderr, NULL);
return 0; return 0;
} }
/*! Unlink pidfile and quit /*! Unlink pidfile and quit
*/ */
static void static void
config_sig_term(int arg) backend_sig_term(int arg)
{ {
static int i=0; static int i=0;
@ -130,6 +129,7 @@ usage(char *argv0, clicon_handle h)
" -D <level>\tdebug\n" " -D <level>\tdebug\n"
" -f <file>\tCLICON config file (mandatory)\n" " -f <file>\tCLICON config file (mandatory)\n"
" -d <dir>\tSpecify backend plugin directory (default: %s)\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" " -z\t\tKill other config daemon and exit\n"
" -F\t\tforeground\n" " -F\t\tforeground\n"
" -1\t\tonce (dont wait for events)\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" " -c <file>\tLoad specified application config.\n"
" -r\t\tReload running database\n" " -r\t\tReload running database\n"
" -p \t\tPrint database yang specification\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" " -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, argv0,
plgdir ? plgdir : "none", plgdir ? plgdir : "none",
confsock ? confsock : "none", confsock ? confsock : "none",
@ -159,7 +159,7 @@ db_reset(clicon_handle h,
{ {
if (xmldb_delete(h, db) != 0 && errno != ENOENT) if (xmldb_delete(h, db) != 0 && errno != ENOENT)
return -1; return -1;
if (xmldb_init(h, db) < 0) if (xmldb_create(h, db) < 0)
return -1; return -1;
return 0; return 0;
} }
@ -181,7 +181,7 @@ rundb_main(clicon_handle h,
cxobj *xt = NULL; cxobj *xt = NULL;
cxobj *xn; cxobj *xn;
if (xmldb_init(h, "tmp") < 0) if (xmldb_create(h, "tmp") < 0)
goto done; goto done;
if (xmldb_copy(h, "running", "tmp") < 0){ if (xmldb_copy(h, "running", "tmp") < 0){
clicon_err(OE_UNIX, errno, "file copy"); clicon_err(OE_UNIX, errno, "file copy");
@ -206,7 +206,6 @@ done:
xml_free(xt); xml_free(xt);
if (fd != -1) if (fd != -1)
close(fd); close(fd);
unchunk_group(__FUNCTION__);
return retval; return retval;
} }
@ -240,11 +239,11 @@ server_socket(clicon_handle h)
int ss; int ss;
/* Open control socket */ /* Open control socket */
if ((ss = config_socket_init(h)) < 0) if ((ss = backend_socket_init(h)) < 0)
return -1; return -1;
/* ss is a server socket that the clients connect to. The callback /* ss is a server socket that the clients connect to. The callback
therefore accepts clients on ss */ 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); close(ss);
return -1; return -1;
} }
@ -256,19 +255,20 @@ server_socket(clicon_handle h)
* log event. * log event.
*/ */
static int 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; size_t n;
char *ptr; char *ptr;
char *nptr; char *nptr;
char *newmsg = NULL; char *newmsg = NULL;
int retval = -1;
/* backend_notify() will go through all clients and see if any has registered "CLICON", /* backend_notify() will go through all clients and see if any has
and if so make a clicon_proto notify message to those clients. */ registered "CLICON", and if so make a clicon_proto notify message to
those clients.
Sanitize '%' into "%%" to prevent segvfaults in vsnprintf later.
/* Sanitize '%' into "%%" to prevent segvfaults in vsnprintf later.
At this stage all formatting is already done */ At this stage all formatting is already done */
n = 0; n = 0;
for(ptr=msg; *ptr; ptr++) for(ptr=msg; *ptr; ptr++)
@ -283,7 +283,6 @@ config_log_cb(int level, char *msg, void *arg)
if (*ptr == '%') if (*ptr == '%')
*nptr++ = '%'; *nptr++ = '%';
} }
retval = backend_notify(arg, "CLICON", level, newmsg); retval = backend_notify(arg, "CLICON", level, newmsg);
free(newmsg); free(newmsg);
@ -309,11 +308,11 @@ main(int argc, char **argv)
clicon_handle h; clicon_handle h;
int help = 0; int help = 0;
int printspec = 0; int printspec = 0;
int printalt = 0;
int pid; int pid;
char *pidfile; char *pidfile;
char *sock; char *sock;
int sockfamily; int sockfamily;
char *xmldb_plugin;
/* In the startup, logs to stderr & syslog and debug flag set later */ /* In the startup, logs to stderr & syslog and debug flag set later */
@ -321,7 +320,7 @@ main(int argc, char **argv)
/* Initiate CLICON handle */ /* Initiate CLICON handle */
if ((h = backend_handle_init()) == NULL) if ((h = backend_handle_init()) == NULL)
return -1; return -1;
if (config_plugin_init(h) != 0) if (backend_plugin_init(h) != 0)
return -1; return -1;
foreground = 0; foreground = 0;
once = 0; once = 0;
@ -387,6 +386,11 @@ main(int argc, char **argv)
usage(argv[0], h); usage(argv[0], h);
clicon_option_str_set(h, "CLICON_BACKEND_DIR", optarg); clicon_option_str_set(h, "CLICON_BACKEND_DIR", optarg);
break; 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 */ case 'F' : /* foreground */
foreground = 1; foreground = 1;
break; break;
@ -425,9 +429,6 @@ main(int argc, char **argv)
case 'p' : /* Print spec */ case 'p' : /* Print spec */
printspec++; printspec++;
break; break;
case 't' : /* Print alternative dbspec format (eg if YANG, print KEY) */
printalt++;
break;
case 'y' :{ /* yang module */ case 'y' :{ /* yang module */
/* Set revision to NULL, extract dir and module */ /* Set revision to NULL, extract dir and module */
char *str = strdup(optarg); char *str = strdup(optarg);
@ -437,6 +438,10 @@ main(int argc, char **argv)
clicon_option_str_set(h, "CLICON_YANG_DIR", strdup(dir)); clicon_option_str_set(h, "CLICON_YANG_DIR", strdup(dir));
break; break;
} }
case 'x' :{ /* xmldb plugin */
clicon_option_str_set(h, "CLICON_XMLDB_PLUGIN", optarg);
break;
}
default: default:
usage(argv[0], h); usage(argv[0], h);
break; break;
@ -502,10 +507,25 @@ main(int argc, char **argv)
return -1; 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 */ /* Parse db spec file */
if (yang_spec_main(h, stdout, printspec) < 0) if (yang_spec_main(h, stdout, printspec) < 0)
goto done; 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 /* First check for startup config
XXX the options below have become out-of-hand. XXX the options below have become out-of-hand.
Too complex, need to simplify*/ Too complex, need to simplify*/
@ -518,7 +538,7 @@ main(int argc, char **argv)
else else
if (db_reset(h, "running") < 0) if (db_reset(h, "running") < 0)
goto done; goto done;
if (xmldb_init(h, "candidate") < 0) if (xmldb_create(h, "candidate") < 0)
goto done; goto done;
if (xmldb_copy(h, "running", "candidate") < 0) if (xmldb_copy(h, "running", "candidate") < 0)
goto done; goto done;
@ -542,7 +562,7 @@ main(int argc, char **argv)
} }
/* If candidate does not exist, create it from running */ /* If candidate does not exist, create it from running */
if (xmldb_exists(h, "candidate") != 1){ if (xmldb_exists(h, "candidate") != 1){
if (xmldb_init(h, "candidate") < 0) if (xmldb_create(h, "candidate") < 0)
goto done; goto done;
if (xmldb_copy(h, "running", "candidate") < 0) if (xmldb_copy(h, "running", "candidate") < 0)
goto done; goto done;
@ -605,14 +625,14 @@ main(int argc, char **argv)
goto done; goto done;
/* Register log notifications */ /* 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; goto done;
clicon_log(LOG_NOTICE, "%s: %u Started", __PROGRAM__, getpid()); 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"); clicon_err(OE_DEMON, errno, "Setting signal");
goto done; 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"); clicon_err(OE_DEMON, errno, "Setting signal");
goto done; goto done;
} }
@ -628,7 +648,7 @@ main(int argc, char **argv)
goto done; goto done;
done: done:
clicon_log(LOG_NOTICE, "%s: %u Terminated", __PROGRAM__, getpid()); 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; return 0;
} }

View file

@ -132,7 +132,7 @@ config_find_plugin(clicon_handle h,
* @retval -1 Error * @retval -1 Error
*/ */
int int
config_plugin_init(clicon_handle h) backend_plugin_init(clicon_handle h)
{ {
find_plugin_t *fp = config_find_plugin; find_plugin_t *fp = config_find_plugin;
clicon_hash_t *data = clicon_data(h); clicon_hash_t *data = clicon_data(h);
@ -152,7 +152,7 @@ config_plugin_init(clicon_handle h)
* @retval -1 Error * @retval -1 Error
*/ */
static int static int
plugin_unload(clicon_handle h, backend_plugin_unload(clicon_handle h,
struct plugin *plg) struct plugin *plg)
{ {
char *error; char *error;
@ -178,44 +178,21 @@ plugin_unload(clicon_handle h,
* @param[in] h Clicon handle * @param[in] h Clicon handle
* @param[in] file The plugin (.so) to load * @param[in] file The plugin (.so) to load
* @param[in] dlflags Arguments to dlopen(3) * @param[in] dlflags Arguments to dlopen(3)
* @param[in] label Chunk label
* @retval plugin Plugin struct * @retval plugin Plugin struct
* @retval NULL Error * @retval NULL Error
*/ */
static struct plugin * static struct plugin *
plugin_load (clicon_handle h, backend_plugin_load (clicon_handle h,
char *file, char *file,
int dlflags, int dlflags)
const char *label)
{ {
char *error;
void *handle; void *handle;
char *name; char *name;
struct plugin *new; struct plugin *new = NULL;
plginit_t *initfun;
dlerror(); /* Clear any existing error */ if ((handle = plugin_load(h, file, dlflags)) == NULL)
if ((handle = dlopen (file, dlflags)) == NULL) { goto done;
error = (char*)dlerror(); if ((new = malloc(sizeof(*new))) == NULL) {
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) {
clicon_err(OE_UNIX, errno, "dhunk: %s", strerror(errno)); clicon_err(OE_UNIX, errno, "dhunk: %s", strerror(errno));
dlclose(handle); dlclose(handle);
return NULL; return NULL;
@ -226,7 +203,6 @@ plugin_load (clicon_handle h,
snprintf(new->p_name, sizeof(new->p_name), "%*s", snprintf(new->p_name, sizeof(new->p_name), "%*s",
(int)strlen(name)-2, name); (int)strlen(name)-2, name);
new->p_handle = handle; new->p_handle = handle;
new->p_init = initfun;
if ((new->p_start = dlsym(handle, PLUGIN_START)) != NULL) if ((new->p_start = dlsym(handle, PLUGIN_START)) != NULL)
clicon_debug(2, "%s callback registered.", PLUGIN_START); clicon_debug(2, "%s callback registered.", PLUGIN_START);
if ((new->p_exit = dlsym(handle, PLUGIN_EXIT)) != NULL) 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) if ((new->p_trans_abort = dlsym(handle, PLUGIN_TRANS_ABORT)) != NULL)
clicon_debug(2, "%s callback registered.", PLUGIN_TRANS_ABORT); clicon_debug(2, "%s callback registered.", PLUGIN_TRANS_ABORT);
clicon_debug(2, "Plugin '%s' loaded.\n", name); clicon_debug(2, "Plugin '%s' loaded.\n", name);
done:
return new; return new;
} }
@ -319,8 +295,8 @@ plugin_append(struct plugin *p)
{ {
struct plugin *new; struct plugin *new;
if ((new = rechunk(plugins, (nplugins+1) * sizeof (*p), NULL)) == NULL) { if ((new = realloc(plugins, (nplugins+1) * sizeof (*p))) == NULL) {
clicon_err(OE_UNIX, errno, "chunk"); clicon_err(OE_UNIX, errno, "realloc");
return -1; return -1;
} }
@ -340,7 +316,7 @@ plugin_append(struct plugin *p)
* @retval -1 Error * @retval -1 Error
*/ */
static int static int
config_plugin_load_dir(clicon_handle h, backend_plugin_load_dir(clicon_handle h,
const char *dir) const char *dir)
{ {
int retval = -1; int retval = -1;
@ -348,11 +324,11 @@ config_plugin_load_dir(clicon_handle h,
int np = 0; int np = 0;
int ndp; int ndp;
struct stat st; struct stat st;
char *filename; char filename[MAXPATHLEN];
struct dirent *dp; struct dirent *dp = NULL;
struct plugin *new; struct plugin *new;
struct plugin *p = NULL; struct plugin *p = NULL;
char *master; char master[MAXPATHLEN];
char *master_plugin; char *master_plugin;
/* Format master plugin path */ /* 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"); clicon_err(OE_PLUGIN, 0, "clicon_master_plugin option not set");
goto quit; goto quit;
} }
master = chunk_sprintf(__FUNCTION__, "%s.so", master_plugin); snprintf(master, MAXPATHLEN-1, "%s.so", master_plugin);
if (master == NULL) {
clicon_err(OE_PLUGIN, errno, "chunk_sprintf master plugin");
goto quit;
}
/* Allocate plugin group object */ /* Allocate plugin group object */
/* Get plugin objects names from plugin directory */ /* 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; goto quit;
/* reset num plugins */ /* reset num plugins */
np = 0; np = 0;
/* Master plugin must be loaded first if it exists. */ /* Master plugin must be loaded first if it exists. */
filename = chunk_sprintf(__FUNCTION__, "%s/%s", dir, master); snprintf(filename, MAXPATHLEN-1, "%s/%s", dir, master);
if (filename == NULL) {
clicon_err(OE_UNIX, errno, "chunk");
goto quit;
}
if (stat(filename, &st) == 0) { if (stat(filename, &st) == 0) {
clicon_debug(1, "Loading master plugin '%.*s' ...", clicon_debug(1, "Loading master plugin '%.*s' ...",
(int)strlen(filename), filename); (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) if (new == NULL)
goto quit; goto quit;
if (plugin_append(new) < 0) if (plugin_append(new) < 0)
goto quit; goto quit;
} }
/* Now load the rest */ /* Now load the rest. Note plugins is the global variable */
for (i = 0; i < ndp; i++) { for (i = 0; i < ndp; i++) {
if (strcmp(dp[i].d_name, master) == 0) if (strcmp(dp[i].d_name, master) == 0)
continue; /* Skip master now */ 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); clicon_debug(1, "Loading plugin '%.*s' ...", (int)strlen(filename), filename);
if (filename == NULL) { new = backend_plugin_load(h, filename, RTLD_NOW);
clicon_err(OE_UNIX, errno, "chunk");
goto quit;
}
new = plugin_load (h, filename, RTLD_NOW, __FUNCTION__);
if (new == NULL) if (new == NULL)
goto quit; goto quit;
/* Append to 'plugins' */
if (plugin_append(new) < 0) if (plugin_append(new) < 0)
goto quit; goto quit;
} }
@ -413,13 +378,19 @@ config_plugin_load_dir(clicon_handle h,
quit: quit:
if (retval != 0) { if (retval != 0) {
if (p) { /* XXX p is always NULL */
while (--np >= 0) if (plugins) {
plugin_unload (h, &p[np]); while (--np >= 0){
unchunk(p); if ((p = &plugins[np]) == NULL)
continue;
backend_plugin_unload(h, p);
free(p);
}
free(plugins);
} }
} }
unchunk_group(__FUNCTION__); if (dp)
free(dp);
return retval; return retval;
} }
@ -435,7 +406,7 @@ plugin_initiate(clicon_handle h)
char *dir; char *dir;
/* First load CLICON system plugins */ /* 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; return -1;
/* Then load application plugins */ /* Then load application plugins */
@ -443,7 +414,7 @@ plugin_initiate(clicon_handle h)
clicon_err(OE_PLUGIN, 0, "backend_dir not defined"); clicon_err(OE_PLUGIN, 0, "backend_dir not defined");
return -1; return -1;
} }
if (config_plugin_load_dir(h, dir) < 0) if (backend_plugin_load_dir(h, dir) < 0)
return -1; return -1;
return 0; return 0;
@ -462,10 +433,12 @@ plugin_finish(clicon_handle h)
for (i = 0; i < nplugins; i++) { for (i = 0; i < nplugins; i++) {
p = &plugins[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; nplugins = 0;
return 0; return 0;
} }

View file

@ -62,7 +62,7 @@ typedef struct {
/* /*
* Prototypes * Prototypes
*/ */
int config_plugin_init(clicon_handle h); int backend_plugin_init(clicon_handle h);
int plugin_initiate(clicon_handle h); int plugin_initiate(clicon_handle h);
int plugin_finish(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 int
config_socket_init(clicon_handle h) backend_socket_init(clicon_handle h)
{ {
char *sock; char *sock;
@ -197,7 +197,7 @@ config_socket_init(clicon_handle h)
* XXX: credentials not properly implemented * XXX: credentials not properly implemented
*/ */
int int
config_accept_client(int fd, backend_accept_client(int fd,
void *arg) void *arg)
{ {
int retval = -1; int retval = -1;

View file

@ -40,7 +40,7 @@
/* /*
* Prototypes * Prototypes
*/ */
int config_socket_init(clicon_handle h); int backend_socket_init(clicon_handle h);
int config_accept_client(int fd, void *arg); int backend_accept_client(int fd, void *arg);
#endif /* _BACKEND_SOCKET_H_ */ #endif /* _BACKEND_SOCKET_H_ */

View file

@ -75,14 +75,20 @@
* This file should only contain access functions for the _specific_ * This file should only contain access functions for the _specific_
* entries in the struct below. * 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 { struct backend_handle {
int cb_magic; /* magic (HDR)*/ int bh_magic; /* magic (HDR)*/
clicon_hash_t *cb_copt; /* clicon option list (HDR) */ clicon_hash_t *bh_copt; /* clicon option list (HDR) */
clicon_hash_t *cb_data; /* internal clicon data (HDR) */ clicon_hash_t *bh_data; /* internal clicon data (HDR) */
/* ------ end of common handle ------ */ /* ------ end of common handle ------ */
struct client_entry *cb_ce_list; /* The client list */ struct client_entry *bh_ce_list; /* The client list */
int cb_ce_nr; /* Number of clients, just increment */ int bh_ce_nr; /* Number of clients, just increment */
struct handle_subscription *cb_subscription; /* Event subscription list */ struct handle_subscription *bh_subscription; /* Event subscription list */
}; };
/*! Creates and returns a clicon config handle for other CLICON API calls /*! 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 (strcmp(su->su_stream, stream) == 0){
if (strlen(su->su_filter)==0 || fnmatch(su->su_filter, event, 0) == 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 (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); clicon_log(LOG_WARNING, "client %d reset", ce->ce_nr);
#if 0 #if 0
/* We should remove here but removal is not possible /* We should remove here but removal is not possible
@ -219,7 +225,7 @@ backend_notify_xml(clicon_handle h,
goto done; goto done;
} }
if (send_msg_notify(ce->ce_s, level, cbuf_get(cb)) < 0){ 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); clicon_log(LOG_WARNING, "client %d reset", ce->ce_nr);
#if 0 #if 0
/* We should remove here but removal is not possible /* 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 * struct client_entry *
backend_client_add(clicon_handle h, backend_client_add(clicon_handle h,
struct sockaddr *addr) struct sockaddr *addr)
{ {
struct backend_handle *cb = handle(h); struct backend_handle *bh = handle(h);
struct client_entry *ce; struct client_entry *ce;
if ((ce = (struct client_entry *)malloc(sizeof(*ce))) == NULL){ if ((ce = (struct client_entry *)malloc(sizeof(*ce))) == NULL){
@ -269,24 +281,28 @@ backend_client_add(clicon_handle h,
return NULL; return NULL;
} }
memset(ce, 0, sizeof(*ce)); 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)); memcpy(&ce->ce_addr, addr, sizeof(*addr));
ce->ce_next = cb->cb_ce_list; ce->ce_next = bh->bh_ce_list;
cb->cb_ce_list = ce; bh->bh_ce_list = ce;
return ce; return ce;
} }
/*! Return client list
* @param[in] h Clicon handle
* @retval ce_list Client entry list (all sessions)
*/
struct client_entry * struct client_entry *
backend_client_list(clicon_handle h) 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 /*! Actually remove client from client list
* @param[in] h Clicon handle * @param[in] h Clicon handle
* @param[in] ce Client hadnle * @param[in] ce Client handle
* @see backend_client_rm which is more high-level * @see backend_client_rm which is more high-level
*/ */
int int
@ -295,9 +311,9 @@ backend_client_delete(clicon_handle h,
{ {
struct client_entry *c; struct client_entry *c;
struct client_entry **ce_prev; 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){ for (c = *ce_prev; c; c = c->ce_next){
if (c == ce){ if (c == ce){
*ce_prev = c->ce_next; *ce_prev = c->ce_next;
@ -328,7 +344,7 @@ subscription_add(clicon_handle h,
subscription_fn_t fn, subscription_fn_t fn,
void *arg) void *arg)
{ {
struct backend_handle *cb = handle(h); struct backend_handle *bh = handle(h);
struct handle_subscription *hs = NULL; struct handle_subscription *hs = NULL;
if ((hs = malloc(sizeof(*hs))) == NULL){ if ((hs = malloc(sizeof(*hs))) == NULL){
@ -339,10 +355,10 @@ subscription_add(clicon_handle h,
hs->hs_stream = strdup(stream); hs->hs_stream = strdup(stream);
hs->hs_format = format; hs->hs_format = format;
hs->hs_filter = filter?strdup(filter):NULL; 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_fn = fn;
hs->hs_arg = arg; hs->hs_arg = arg;
cb->cb_subscription = hs; bh->bh_subscription = hs;
done: done:
return hs; return hs;
} }
@ -362,11 +378,11 @@ subscription_delete(clicon_handle h,
subscription_fn_t fn, subscription_fn_t fn,
void *arg) void *arg)
{ {
struct backend_handle *cb = handle(h); struct backend_handle *bh = handle(h);
struct handle_subscription *hs; struct handle_subscription *hs;
struct handle_subscription **hs_prev; 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){ for (hs = *hs_prev; hs; hs = hs->hs_next){
/* XXX arg == hs->hs_arg */ /* XXX arg == hs->hs_arg */
if (strcmp(hs->hs_stream, stream)==0 && hs->hs_fn == fn){ if (strcmp(hs->hs_stream, stream)==0 && hs->hs_fn == fn){
@ -404,15 +420,16 @@ struct handle_subscription *
subscription_each(clicon_handle h, subscription_each(clicon_handle h,
struct handle_subscription *hprev) struct handle_subscription *hprev)
{ {
struct backend_handle *cb = handle(h); struct backend_handle *bh = handle(h);
struct handle_subscription *hs = NULL; struct handle_subscription *hs = NULL;
if (hprev) if (hprev)
hs = hprev->hs_next; hs = hprev->hs_next;
else else
hs = cb->cb_subscription; hs = bh->bh_subscription;
return hs; return hs;
} }
/* Database dependency description */ /* Database dependency description */
struct backend_netconf_reg { struct backend_netconf_reg {
qelem_t nr_qelem; /* List header */ qelem_t nr_qelem; /* List header */
@ -456,10 +473,9 @@ catch:
/*! See if there is any callback registered for this tag /*! See if there is any callback registered for this tag
* *
* @param[in] h clicon handle * @param[in] h clicon handle
* @param[in] xn Sub-tree (under xorig) at child of rpc: <rpc><xn></rpc>. * @param[in] xe Sub-tree (under xorig) at child of rpc: <rpc><xn></rpc>.
* @param[out] cb Output xml stream. For reply * @param[in] ce Client (session) entry
* @param[out] cb_err Error xml stream. For error reply * @param[out] cbret Return XML, error or OK as cbuf
* @param[out] xret Return XML, error or OK
* *
* @retval -1 Error * @retval -1 Error
* @retval 0 OK, not found handler. * @retval 0 OK, not found handler.

View file

@ -94,17 +94,19 @@ cli_notification_register(clicon_handle h,
void *arg) void *arg)
{ {
int retval = -1; int retval = -1;
char *logname; char *logname = NULL;
void *p; void *p;
int s; int s;
clicon_hash_t *cdat = clicon_data(h); clicon_hash_t *cdat = clicon_data(h);
size_t len; size_t len;
int s_exist = -1; int s_exist = -1;
if ((logname = chunk_sprintf(__FUNCTION__, "log_socket_%s", stream)) == NULL){ len = strlen("log_socket_") + strlen(stream) + 1;
clicon_err(OE_PLUGIN, errno, "%s: chunk_sprintf", __FUNCTION__); if ((logname = malloc(len)) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
goto done; goto done;
} }
snprintf(logname, len, "log_socket_%s", stream);
if ((p = hash_value(cdat, logname, &len)) != NULL) if ((p = hash_value(cdat, logname, &len)) != NULL)
s_exist = *(int*)p; s_exist = *(int*)p;
@ -132,7 +134,8 @@ cli_notification_register(clicon_handle h,
} }
retval = 0; retval = 0;
done: done:
unchunk_group(__FUNCTION__); if (logname)
free(logname);
return retval; return retval;
} }

View file

@ -69,12 +69,11 @@
#define handle(h) (assert(clicon_handle_check(h)==0),(struct cli_handle *)(h)) #define handle(h) (assert(clicon_handle_check(h)==0),(struct cli_handle *)(h))
#define cligen(h) (handle(h)->cl_cligen) #define cligen(h) (handle(h)->cl_cligen)
/* /*! CLI specific handle added to header CLICON handle
* 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]
* This file should only contain access functions for the _specific_ * This file should only contain access functions for the _specific_
* entries in the struct below. * 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 { struct cli_handle {
int cl_magic; /* magic (HDR)*/ int cl_magic; /* magic (HDR)*/
@ -112,21 +111,23 @@ cli_handle_init(void)
return h; return h;
} }
/* /*! Free clicon handle
* cli_handle_exit
* frees clicon handle
*/ */
int int
cli_handle_exit(clicon_handle h) cli_handle_exit(clicon_handle h)
{ {
cligen_handle ch = cligen(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 */ clicon_handle_exit(h); /* frees h and options */
cligen_exit(ch); cligen_exit(ch);
return 0; return 0;
} }
/*---------------------------------------------------------- /*----------------------------------------------------------
* cli-specific handle access functions * cli-specific handle access functions
*----------------------------------------------------------*/ *----------------------------------------------------------*/

View file

@ -174,7 +174,8 @@ main(int argc, char **argv)
int printgen = 0; int printgen = 0;
int logclisyntax = 0; int logclisyntax = 0;
int help = 0; int help = 0;
char *treename; char *treename = NULL;
int len;
int logdst = CLICON_LOG_STDERR; int logdst = CLICON_LOG_STDERR;
char *restarg = NULL; /* what remains after options */ 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) if (yang2cli(h, yspec, &pt, clicon_cli_genmodel_type(h)) < 0)
goto done; 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); cli_tree_add(h, treename, pt);
if (printgen) if (printgen)
cligen_print(stdout, pt, 1); cligen_print(stdout, pt, 1);
} }
@ -400,9 +407,10 @@ main(int argc, char **argv)
if (!once) if (!once)
cli_interactive(h); cli_interactive(h);
done: done:
if (treename)
free(treename);
if (restarg) if (restarg)
free(restarg); free(restarg);
unchunk_group(__FUNCTION__);
// Gets in your face if we log on stderr // Gets in your face if we log on stderr
clicon_log_init(__PROGRAM__, LOG_INFO, 0); /* Log on syslog no stderr */ clicon_log_init(__PROGRAM__, LOG_INFO, 0); /* Log on syslog no stderr */
clicon_log(LOG_NOTICE, "%s: %u Terminated\n", __PROGRAM__, getpid()); 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 * static cli_syntaxmode_t *
syntax_mode_find(cli_syntax_t *stx, const char *mode, int create) 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) if (create == 0)
return NULL; return NULL;
if ((m = chunk(sizeof(cli_syntaxmode_t), stx->stx_cnklbl)) == NULL) { if ((m = malloc(sizeof(cli_syntaxmode_t))) == NULL) {
perror("chunk"); perror("malloc");
return NULL; return NULL;
} }
memset (m, 0, sizeof (*m)); memset (m, 0, sizeof (*m));
@ -163,39 +162,15 @@ syntax_append(clicon_handle h,
return 0; 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 * Unload all plugins in a group
*/ */
static int 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) if (stx == NULL)
return 0; return 0;
@ -204,15 +179,18 @@ syntax_unload(clicon_handle h)
p = stx->stx_plugins; p = stx->stx_plugins;
plugin_unload(h, p->cp_handle); plugin_unload(h, p->cp_handle);
clicon_debug(1, "DEBUG: Plugin '%s' unloaded.", p->cp_name); 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--; stx->stx_nplugins--;
} }
while (stx->stx_nmodes > 0) { 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--; stx->stx_nmodes--;
} }
unchunk_group(stx->stx_cnklbl);
return 0; return 0;
} }
@ -265,48 +243,31 @@ clixon_str2fn(char *name,
return NULL; 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 * Note 'file' may be destructively modified
* @retval plugin-handle should be freed after use
*/ */
static plghndl_t 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; char *name;
void *handle = NULL; plghndl_t handle = NULL;
plginit_t *initfun;
struct cli_plugin *cp = NULL; struct cli_plugin *cp = NULL;
dlerror(); /* Clear any existing error */ if ((handle = plugin_load(h, file, dlflags)) == NULL)
if ((handle = dlopen (file, dlflags)) == NULL) {
error = (char*)dlerror();
cli_output (stderr, "dlopen: %s\n", error ? error : "Unknown error");
goto quit; goto quit;
} if ((cp = malloc(sizeof (struct cli_plugin))) == NULL) {
/* call plugin_init() if defined */ perror("malloc");
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");
goto quit; goto quit;
} }
memset (cp, 0, sizeof(*cp)); memset (cp, 0, sizeof(*cp));
name = basename(file); name = basename(file);
snprintf(cp->cp_name, sizeof(cp->cp_name), "%.*s", (int)strlen(name)-3, name); snprintf(cp->cp_name, sizeof(cp->cp_name), "%.*s", (int)strlen(name)-3, name);
cp->cp_handle = handle; cp->cp_handle = handle;
quit: quit:
if (cp == NULL) {
if (handle)
dlclose(handle);
}
return cp; return cp;
} }
@ -323,7 +284,7 @@ cli_load_syntax(clicon_handle h, const char *filename, const char *clispec_dir)
parse_tree pt = {0,}; parse_tree pt = {0,};
int retval = -1; int retval = -1;
FILE *f; FILE *f;
char *filepath; char filepath[MAXPATHLEN];
cvec *vr = NULL; cvec *vr = NULL;
char *prompt = NULL; char *prompt = NULL;
char **vec = NULL; char **vec = NULL;
@ -331,12 +292,7 @@ cli_load_syntax(clicon_handle h, const char *filename, const char *clispec_dir)
char *plgnam; char *plgnam;
struct cli_plugin *p; struct cli_plugin *p;
if ((filepath = chunk_sprintf(__FUNCTION__, "%s/%s", snprintf(filepath, MAXPATHLEN-1, "%s/%s", clispec_dir, filename);
clispec_dir,
filename)) == NULL){
clicon_err(OE_PLUGIN, errno, "chunk");
goto done;
}
if ((vr = cvec_new(0)) == NULL){ if ((vr = cvec_new(0)) == NULL){
clicon_err(OE_PLUGIN, errno, "cvec_new"); clicon_err(OE_PLUGIN, errno, "cvec_new");
goto done; goto done;
@ -403,7 +359,6 @@ done:
cvec_free(vr); cvec_free(vr);
if (vec) if (vec)
free(vec); free(vec);
unchunk_group(__FUNCTION__);
return retval; return retval;
} }
@ -415,10 +370,10 @@ cli_plugin_load_dir(clicon_handle h, char *dir, cli_syntax_t *stx)
{ {
int i; int i;
int ndp; int ndp;
struct dirent *dp; struct dirent *dp = NULL;
char *file;
char *master_plugin; char *master_plugin;
char *master; char master[MAXPATHLEN];
char filename[MAXPATHLEN];
struct cli_plugin *cp; struct cli_plugin *cp;
struct stat st; struct stat st;
int retval = -1; 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"); clicon_err(OE_PLUGIN, 0, "clicon_master_plugin option not set");
goto quit; goto quit;
} }
if ((master = chunk_sprintf(__FUNCTION__, "%s.so", master_plugin)) == NULL){ snprintf(master, MAXPATHLEN-1, "%s.so", master_plugin);
clicon_err(OE_PLUGIN, errno, "chunk_sprintf master plugin");
goto quit;
}
/* Get plugin objects names from plugin directory */ /* 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) if (ndp < 0)
goto quit; goto quit;
/* Load master plugin first */ /* Load master plugin first */
file = chunk_sprintf(__FUNCTION__, "%s/%s", dir, master); snprintf(filename, MAXPATHLEN-1, "%s/%s", dir, master);
if (file == NULL) { if (stat(filename, &st) == 0) {
clicon_err(OE_UNIX, errno, "chunk_sprintf dir");
goto quit;
}
if (stat(file, &st) == 0) {
clicon_debug(1, "DEBUG: Loading master plugin '%s'", master); 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) if (cp == NULL)
goto quit; goto quit;
/* Look up certain call-backs in master plugin */ /* 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); INSQ(cp, stx->stx_plugins);
stx->stx_nplugins++; stx->stx_nplugins++;
} }
unchunk (file);
/* Load the rest */ /* Load the rest */
for (i = 0; i < ndp; i++) { for (i = 0; i < ndp; i++) {
if (strcmp (dp[i].d_name, master) == 0) if (strcmp (dp[i].d_name, master) == 0)
continue; /* Skip master now */ continue; /* Skip master now */
file = chunk_sprintf(__FUNCTION__, "%s/%s", dir, dp[i].d_name); snprintf(filename, MAXPATHLEN-1, "%s/%s", dir, dp[i].d_name);
if (file == NULL) {
clicon_err(OE_UNIX, errno, "chunk_sprintf dir");
goto quit;
}
clicon_debug(1, "DEBUG: Loading plugin '%s'", 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; goto quit;
INSQ(cp, stx->stx_plugins); INSQ(cp, stx->stx_plugins);
stx->stx_nplugins++; stx->stx_nplugins++;
unchunk (file);
} }
if (dp)
unchunk(dp);
retval = 0; retval = 0;
quit: quit:
unchunk_group(__FUNCTION__); if (dp)
free(dp);
return retval; return retval;
} }
@ -501,8 +442,7 @@ cli_syntax_load (clicon_handle h)
char *clispec_dir = NULL; char *clispec_dir = NULL;
int ndp; int ndp;
int i; int i;
char *cnklbl = "__CLICON_CLI_SYNTAX_CNK_LABEL__"; struct dirent *dp = NULL;
struct dirent *dp;
cli_syntax_t *stx; cli_syntax_t *stx;
cli_syntaxmode_t *m; cli_syntaxmode_t *m;
@ -521,13 +461,11 @@ cli_syntax_load (clicon_handle h)
} }
/* Allocate plugin group object */ /* Allocate plugin group object */
if ((stx = chunk(sizeof(*stx), cnklbl)) == NULL) { if ((stx = malloc(sizeof(*stx))) == NULL) {
clicon_err(OE_UNIX, errno, "chunk"); clicon_err(OE_UNIX, errno, "malloc");
goto quit; goto quit;
} }
memset (stx, 0, sizeof (*stx)); /* Zero out all */ 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); cli_syntax_set(h, stx);
@ -541,7 +479,7 @@ cli_syntax_load (clicon_handle h)
goto quit; goto quit;
/* load syntaxfiles */ /* 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; goto quit;
/* Load the rest */ /* Load the rest */
for (i = 0; i < ndp; i++) { 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) if (cli_load_syntax(h, dp[i].d_name, clispec_dir) < 0)
goto quit; goto quit;
} }
if (dp)
unchunk(dp);
/* Did we successfully load any syntax modes? */ /* Did we successfully load any syntax modes? */
if (stx->stx_nmodes <= 0) { if (stx->stx_nmodes <= 0) {
@ -576,11 +511,11 @@ cli_syntax_load (clicon_handle h)
quit: quit:
if (retval != 0) { if (retval != 0) {
syntax_unload(h); cli_syntax_unload(h);
unchunk_group(cnklbl);
cli_syntax_set(h, NULL); cli_syntax_set(h, NULL);
} }
unchunk_group(__FUNCTION__); if (dp)
free(dp);
return retval; return retval;
} }
@ -614,7 +549,7 @@ cli_plugin_start(clicon_handle h, int argc, char **argv)
int int
cli_plugin_finish(clicon_handle h) cli_plugin_finish(clicon_handle h)
{ {
syntax_unload(h); cli_syntax_unload(h);
cli_syntax_set(h, NULL); cli_syntax_set(h, NULL);
return 0; return 0;
} }
@ -663,39 +598,36 @@ clicon_eval(clicon_handle h, char *cmd, cg_obj *match_obj, cvec *vr)
} }
/* /*! Given a command string, parse and evaluate.
* clicon_parse * Parse and evaluate the string according to
* Given a command string, parse and evaluate the string according to
* the syntax parse tree of the syntax mode specified by *mode. * 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 * 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 * 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 * match is found in another mode, the mode variable is updated to point at
* the new mode string. * the new mode string.
* *
* INPUT: * @param[in] h Clicon handle
* cmd The command string * @param[in] cmd The command string
* match_obj Pointer to CLIgen match object * @param[in,out] mode A pointer to the mode string pointer
* mode A pointer to the mode string pointer * @param[out] result -2 On eof (shouldnt happen)
* OUTPUT: * -1 On parse error
* kr Keyword vector * >=0 Number of matches
* vr Variable vector
* RETURNS:
* -2 : on eof (shouldnt happen)
* -1 : In parse error
* >=0 : Number of matches
*/ */
int 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; char *m, *msav;
int res = -1; int res = -1;
int r; int r;
cli_syntax_t *stx; cli_syntax_t *stx = NULL;
cli_syntaxmode_t *smode; cli_syntaxmode_t *smode;
char *treename; char *treename;
parse_tree *pt; /* Orig */ parse_tree *pt; /* Orig */
cg_obj *match_obj; cg_obj *match_obj;
cvec *vr = NULL; cvec *cvv = NULL;
stx = cli_syntax(h); stx = cli_syntax(h);
m = *mode; 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); fprintf(stderr, "No such parse-tree registered: %s\n", treename);
goto done;; goto done;;
} }
if ((vr = cvec_new(0)) == NULL){ if ((cvv = cvec_new(0)) == NULL){
fprintf(stderr, "%s: cvec_new: %s\n", __FUNCTION__, strerror(errno)); clicon_err(OE_UNIX, errno, "cvec_new");
goto done;; 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) if (res != CG_MATCH)
pt_expand_cleanup_1(pt); pt_expand_cleanup_1(pt);
if (msav){ if (msav){
@ -755,7 +687,7 @@ clicon_parse(clicon_handle h, char *cmd, char **mode, int *result)
*mode = m; *mode = m;
cli_set_syntax_mode(h, 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); cli_handler_err(stdout);
pt_expand_cleanup_1(pt); pt_expand_cleanup_1(pt);
if (result) if (result)
@ -769,8 +701,8 @@ clicon_parse(clicon_handle h, char *cmd, char **mode, int *result)
} }
} }
done: done:
if (vr) if (cvv)
cvec_free(vr); cvec_free(cvv);
return res; return res;
} }
@ -891,9 +823,7 @@ cli_set_prompt(clicon_handle h, const char *name, const char *prompt)
return 0; return 0;
} }
/* /*! Format prompt
* Format prompt
* XXX: HOST_NAME_MAX from sysconf()
*/ */
static int static int
prompt_fmt (char *prompt, size_t plen, char *fmt, ...) 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 *s = fmt;
char hname[1024]; char hname[1024];
char tty[32]; char tty[32];
char *new;
char *tmp; char *tmp;
int ret = -1; int ret = -1;
cbuf *cb = NULL;
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
/* Start with empty string */ /* Start with empty string */
if((new = chunk_sprintf(__FUNCTION__, "%s", ""))==NULL) cprintf(cb, "");
goto done;
while(*s) { while(*s) {
if (*s == '%' && *++s) { if (*s == '%' && *++s) {
switch(*s) { switch(*s) {
case 'H': /* Hostname */ case 'H': /* Hostname */
if (gethostname (hname, sizeof (hname)) != 0) if (gethostname (hname, sizeof (hname)) != 0)
strncpy(hname, "unknown", sizeof(hname)-1); strncpy(hname, "unknown", sizeof(hname)-1);
if((new = chunk_strncat(new, hname, 0, __FUNCTION__))==NULL) cprintf(cb, "%s", hname);
goto done;
break; break;
case 'U': /* Username */ case 'U': /* Username */
tmp = getenv("USER"); tmp = getenv("USER");
if((new = chunk_strncat(new, (tmp ? tmp : "nobody"), 0, __FUNCTION__))==NULL) cprintf(cb, "%s", tmp?tmp:"nobody");
goto done;
break; break;
case 'T': /* TTY */ case 'T': /* TTY */
if(ttyname_r(fileno(stdin), tty, sizeof(tty)-1) < 0) if(ttyname_r(fileno(stdin), tty, sizeof(tty)-1) < 0)
strcpy(tty, "notty"); strcpy(tty, "notty");
if((new = chunk_strncat(new, tty, strlen(tty), __FUNCTION__))==NULL) cprintf(cb, "%s", tty);
goto done;
break; break;
default: default:
if((new = chunk_strncat(new, "%", 1, __FUNCTION__))==NULL || cprintf(cb, "%%");
(new = chunk_strncat(new, s, 1, __FUNCTION__))) cprintf(cb, "%c", *s);
goto done;
} }
} }
else { else
if ((new = chunk_strncat(new, s, 1, __FUNCTION__))==NULL) cprintf(cb, "%c", *s);
goto done;
}
s++; s++;
} }
done: done:
if (new) if (cb)
fmt = new; fmt = cbuf_get(cb);
va_start(ap, fmt); va_start(ap, fmt);
ret = vsnprintf(prompt, plen, fmt, ap); ret = vsnprintf(prompt, plen, fmt, ap);
va_end(ap); va_end(ap);
if (cb)
unchunk_group(__FUNCTION__); cbuf_free(cb);
return ret; return ret;
} }
/* /*! Return a formatted prompt string
* Return a formatted prompt string
*/ */
char * char *
cli_prompt(char *fmt) cli_prompt(char *fmt)
@ -1069,11 +990,10 @@ cli_ptpop(clicon_handle h, char *mode, char *op)
} }
/* /*! Find a cli plugin based on name and resolve a function pointer in it.
* clicon_valcb
* Callback from clicon_dbvars_parse() * Callback from clicon_dbvars_parse()
* Find a cli plugin based on name if given and * Find a cli plugin based on name if given and use dlsym to resolve a
* use dlsym to resolve a function pointer in it. * function pointer in it.
* Call the resolved function to get the cgv populated * Call the resolved function to get the cgv populated
*/ */
int int

View file

@ -74,7 +74,6 @@ struct cli_plugin {
/* Plugin group object */ /* Plugin group object */
typedef struct { typedef struct {
char stx_cnklbl[128]; /* Plugin group name */
int stx_nplugins; /* Number of plugins */ int stx_nplugins; /* Number of plugins */
struct cli_plugin *stx_plugins; /* List of plugins */ struct cli_plugin *stx_plugins; /* List of plugins */
int stx_nmodes; /* Number of syntax modes */ int stx_nmodes; /* Number of syntax modes */

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; 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 int nplugins = 0;
static plghndl_t *plugins = NULL; static plghndl_t *plugins = NULL;
static netconf_reg_t *deps = NULL; static netconf_reg_t *deps = NULL;
@ -141,9 +87,9 @@ netconf_plugin_load(clicon_handle h)
int retval = -1; int retval = -1;
char *dir; char *dir;
int ndp; int ndp;
struct dirent *dp; struct dirent *dp = NULL;
int i; int i;
char *filename; char filename[MAXPATHLEN];
plghndl_t *handle; plghndl_t *handle;
if ((dir = clicon_netconf_dir(h)) == NULL){ if ((dir = clicon_netconf_dir(h)) == NULL){
@ -152,30 +98,26 @@ netconf_plugin_load(clicon_handle h)
} }
/* Get plugin objects names from plugin directory */ /* 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; goto quit;
/* Load all plugins */ /* Load all plugins */
for (i = 0; i < ndp; i++) { 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' ...", clicon_debug(1, "DEBUG: Loading plugin '%.*s' ...",
(int)strlen(filename), filename); (int)strlen(filename), filename);
if (filename == NULL) { if ((handle = plugin_load(h, filename, RTLD_NOW)) == NULL)
clicon_err(OE_UNIX, errno, "chunk");
goto quit; goto quit;
} if ((plugins = realloc(plugins, (nplugins+1) * sizeof (*plugins))) == NULL) {
if ((handle = plugin_load (h, filename, RTLD_NOW, __FUNCTION__)) == NULL) clicon_err(OE_UNIX, errno, "realloc");
goto quit;
if ((plugins = rechunk(plugins, (nplugins+1) * sizeof (*plugins), NULL)) == NULL) {
clicon_err(OE_UNIX, errno, "chunk");
goto quit; goto quit;
} }
plugins[nplugins++] = handle; plugins[nplugins++] = handle;
unchunk (filename);
} }
retval = 0; retval = 0;
quit: quit:
unchunk_group(__FUNCTION__); if (dp)
free(dp);
return retval; return retval;
} }
@ -194,8 +136,10 @@ netconf_plugin_unload(clicon_handle h)
} }
for (i = 0; i < nplugins; i++) for (i = 0; i < nplugins; i++)
plugin_unload(h, plugins[i]); plugin_unload(h, plugins[i]);
if (plugins) if (plugins){
unchunk(plugins); free(plugins);
plugins = NULL;
}
nplugins = 0; nplugins = 0;
return 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 Clixon restconf is a daemon based on FASTCGI. Instructions are available to
run with NGINX. run with NGINX.
The implementatation supports plain OPTIONS, HEAD, GET, POST, PUT, PATCH, DELETE. The implementatation supports plain OPTIONS, HEAD, GET, POST, PUT, PATCH, DELETE.
and is based on draft-ietf-netconf-restconf-13. 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: including:
- query parameters (section 4.9) - query parameters (section 4.9)
- notifications (sec 6) - notifications (sec 6)
- only rudimentary error reporting exists (sec 7) - 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 { server {
... ...
location /restconf { location /restconf {
@ -30,13 +23,19 @@ server {
include fastcgi_params; include fastcgi_params;
} }
} }
# Start nginx daemon ```
Start nginx daemon
```
sudo /etc/init.d nginx start 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 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 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 curl -sX POST -d '{"clicon":{"interfaces":{"interface":{"name":"eth1","type":"eth","enabled":"true"}}}}' http://localhost/restconf/data
```
### Debugging
3. DEBUGGING Start the restconf fastcgi program with debug flag:
++++++++++++ ```
Start the restconf programs with debug flag: sudo su -c "/www-data/clixon_restconf -Df /usr/local/etc/routing.conf" -s /bin/sh www-
sudo su -c "/www-data/clixon_restconf -D" -s /bin/sh www-data data
```
Look at syslog: Look at syslog:
```
tail -f /var/log/syslog | grep clixon_restconf tail -f /var/log/syslog | grep clixon_restconf
```
Send command: Send command:
```
curl -G http://127.0.0.1/restconf/data/* curl -G http://127.0.0.1/restconf/data/*
```

View file

@ -33,7 +33,6 @@
*/ */
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
@ -46,9 +45,9 @@
#include <fcgi_stdio.h> #include <fcgi_stdio.h>
#include <signal.h> #include <signal.h>
#include <dlfcn.h> #include <dlfcn.h>
#include <sys/param.h>
#include <sys/time.h> #include <sys/time.h>
#include <sys/wait.h> #include <sys/wait.h>
#include <curl/curl.h>
/* cligen */ /* cligen */
#include <cligen/cligen.h> #include <cligen/cligen.h>
@ -111,98 +110,6 @@ clicon_debug_xml(int dbglevel,
return retval; 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 * @param[in] r Fastcgi request handle
*/ */
@ -272,45 +179,6 @@ static int nplugins = 0;
static plghndl_t *plugins = NULL; static plghndl_t *plugins = NULL;
static credentials_t *p_credentials = NULL; /* Credentials callback */ 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 /*! Load all plugins you can find in CLICON_RESTCONF_DIR
*/ */
int int
@ -319,65 +187,40 @@ restconf_plugin_load(clicon_handle h)
int retval = -1; int retval = -1;
char *dir; char *dir;
int ndp; int ndp;
struct dirent *dp; struct dirent *dp = NULL;
int i; int i;
char *filename;
plghndl_t *handle; plghndl_t *handle;
char filename[MAXPATHLEN];
if ((dir = clicon_restconf_dir(h)) == NULL){ if ((dir = clicon_restconf_dir(h)) == NULL){
clicon_err(OE_PLUGIN, 0, "clicon_restconf_dir not defined"); clicon_err(OE_PLUGIN, 0, "clicon_restconf_dir not defined");
goto quit; goto quit;
} }
/* Get plugin objects names from plugin directory */ /* 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; goto quit;
/* Load all plugins */ /* Load all plugins */
for (i = 0; i < ndp; i++) { 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' ...", clicon_debug(1, "DEBUG: Loading plugin '%.*s' ...",
(int)strlen(filename), filename); (int)strlen(filename), filename);
if (filename == NULL) { if ((handle = plugin_load(h, filename, RTLD_NOW)) == NULL)
clicon_err(OE_UNIX, errno, "chunk");
goto quit; goto quit;
} p_credentials = dlsym(handle, "restconf_credentials");
if ((handle = plugin_load (h, filename, RTLD_NOW, __FUNCTION__)) == NULL) if ((plugins = realloc(plugins, (nplugins+1) * sizeof (*plugins))) == NULL) {
goto quit; clicon_err(OE_UNIX, errno, "realloc");
if ((plugins = rechunk(plugins, (nplugins+1) * sizeof (*plugins), NULL)) == NULL) {
clicon_err(OE_UNIX, errno, "chunk");
goto quit; goto quit;
} }
plugins[nplugins++] = handle; plugins[nplugins++] = handle;
unchunk (filename);
} }
retval = 0; retval = 0;
quit: quit:
unchunk_group(__FUNCTION__); if (dp)
free(dp);
return retval; 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 */ /*! Unload all restconf plugins */
int int
@ -387,8 +230,10 @@ restconf_plugin_unload(clicon_handle h)
for (i = 0; i < nplugins; i++) for (i = 0; i < nplugins; i++)
plugin_unload(h, plugins[i]); plugin_unload(h, plugins[i]);
if (plugins) if (plugins){
unchunk(plugins); free(plugins);
plugins = NULL;
}
nplugins = 0; nplugins = 0;
return 0; return 0;
} }

View file

@ -46,7 +46,6 @@
int notfound(FCGX_Request *r); int notfound(FCGX_Request *r);
int badrequest(FCGX_Request *r); int badrequest(FCGX_Request *r);
int clicon_debug_xml(int dbglevel, char *str, cxobj *cx); 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); int test(FCGX_Request *r, int dbg);
cbuf *readdata(FCGX_Request *r); cbuf *readdata(FCGX_Request *r);

View file

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

View file

@ -109,7 +109,6 @@ Mapping netconf error-tag -> status code
#include <signal.h> #include <signal.h>
#include <sys/time.h> #include <sys/time.h>
#include <sys/wait.h> #include <sys/wait.h>
#include <curl/curl.h>
/* cligen */ /* cligen */
#include <cligen/cligen.h> #include <cligen/cligen.h>
@ -153,87 +152,20 @@ api_data_get_gen(clicon_handle h,
int head) int head)
{ {
int retval = -1; int retval = -1;
cg_var *cv;
char *val;
char *v;
int i;
cbuf *path = NULL; cbuf *path = NULL;
cbuf *path1 = NULL;
cbuf *cbx = NULL; cbuf *cbx = NULL;
cxobj **vec = NULL; cxobj **vec = NULL;
yang_spec *yspec; 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; cxobj *xret = NULL;
clicon_debug(1, "%s", __FUNCTION__); clicon_debug(1, "%s", __FUNCTION__);
yspec = clicon_dbspec_yang(h); yspec = clicon_dbspec_yang(h);
if ((path = cbuf_new()) == NULL) if ((path = cbuf_new()) == NULL)
goto done; goto done;
if ((path1 = cbuf_new()) == NULL) /* without [] qualifiers */ if (api_path2xpath_cvv(yspec, pcvec, pi, path) < 0){
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); notfound(r);
goto done; 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);
}
}
clicon_debug(1, "%s path:%s", __FUNCTION__, cbuf_get(path)); clicon_debug(1, "%s path:%s", __FUNCTION__, cbuf_get(path));
if (clicon_rpc_get_config(h, "running", cbuf_get(path), &xret) < 0){ if (clicon_rpc_get_config(h, "running", cbuf_get(path), &xret) < 0){
notfound(r); notfound(r);
@ -268,8 +200,6 @@ api_data_get_gen(clicon_handle h,
cbuf_free(cbx); cbuf_free(cbx);
if (path) if (path)
cbuf_free(path); cbuf_free(path);
if (path1)
cbuf_free(path1);
if (xret) if (xret)
xml_free(xret); xml_free(xret);
return retval; return retval;
@ -366,7 +296,7 @@ api_data_edit(clicon_handle h,
goto done; goto done;
} }
cprintf(cbx, "</config>"); 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", if (clicon_rpc_edit_config(h, "candidate",
operation, operation,
api_path, api_path,
@ -407,7 +337,8 @@ api_data_edit(clicon_handle h,
If the data resource already exists, then the POST request MUST fail If the data resource already exists, then the POST request MUST fail
and a "409 Conflict" status-line MUST be returned. 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 int
api_data_post(clicon_handle h, 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] pi Offset, where to start pcvec
* @param[in] qvec Vector of query string (QUERY_STRING) * @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] data Stream input data * @param[in] data Stream input data
* Example: * @example
curl -X PUT -d {\"enabled\":\"false\"} http://127.0.0.1/restconf/data/interfaces/interface=eth1 curl -X PUT -d '{"enabled":"false"}' http://127.0.0.1/restconf/data/interfaces/interface=eth1
* *
PUT: PUT:
if the PUT request creates a new resource, 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 # Location of frontend .cli cligen spec files
CLICON_CLISPEC_DIR libdir/APPNAME/clispec 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 # Enabled uses "startup" configuration on boot
CLICON_USE_STARTUP_CONFIG 0 CLICON_USE_STARTUP_CONFIG 0
@ -121,6 +117,9 @@ CLICON_BACKEND_PIDFILE localstatedir/APPNAME/APPNAME.pidfile
# Directory where "running", "candidate" and "startup" are placed # Directory where "running", "candidate" and "startup" are placed
CLICON_XMLDB_DIR localstatedir/APPNAME 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 # Dont include keys in cvec in cli vars callbacks, ie a & k in 'a <b> k <c>' ignored
# CLICON_CLI_VARONLY 1 # CLICON_CLI_VARONLY 1

233
configure vendored
View file

@ -632,6 +632,8 @@ CPP
OBJEXT OBJEXT
EXEEXT EXEEXT
ac_ct_CC ac_ct_CC
with_keyvalue
with_restconf
RANLIB RANLIB
AR AR
EXE_SUFFIX EXE_SUFFIX
@ -702,8 +704,9 @@ ac_subst_files=''
ac_user_opts=' ac_user_opts='
enable_option_checking enable_option_checking
with_cligen with_cligen
with_restconf
with_keyvalue
with_qdbm with_qdbm
enable_keycontent
' '
ac_precious_vars='build_alias ac_precious_vars='build_alias
host_alias host_alias
@ -1324,17 +1327,13 @@ if test -n "$ac_init_help"; then
cat <<\_ACEOF 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: Optional Packages:
--with-PACKAGE[=ARG] use PACKAGE [ARG=yes] --with-PACKAGE[=ARG] use PACKAGE [ARG=yes]
--without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no)
--with-cligen=dir Use CLIGEN here --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: Some influential environment variables:
CC C compiler command CC C compiler command
@ -2136,7 +2135,7 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu
: ${CFLAGS="-O2"} : ${CFLAGS="-O2"}
CLIXON_VERSION_MAJOR="3" CLIXON_VERSION_MAJOR="3"
CLIXON_VERSION_MINOR="2" CLIXON_VERSION_MINOR="3"
CLIXON_VERSION_PATCH="0" CLIXON_VERSION_PATCH="0"
CLIXON_VERSION="\"${CLIXON_VERSION_MAJOR}.${CLIXON_VERSION_MINOR}.${CLIXON_VERSION_PATCH}\"" CLIXON_VERSION="\"${CLIXON_VERSION_MAJOR}.${CLIXON_VERSION_MINOR}.${CLIXON_VERSION_PATCH}\""
# Fix to specific version (eg 3.5) or head (3) # 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 ac_ext=c
@ -3541,7 +3542,6 @@ if test "${with_cligen}"; then
fi fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for grep that handles long lines and -e" >&5 { $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; } $as_echo_n "checking for grep that handles long lines and -e... " >&6; }
if ${ac_cv_path_GREP+:} false; then : if ${ac_cv_path_GREP+:} false; then :
@ -3866,21 +3866,92 @@ else
fi 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. # Check whether --with-qdbm was given.
if test "${with_qdbm+set}" = set; then : if test "${with_qdbm+set}" = set; then :
withval=$with_qdbm; withval=$with_qdbm;
fi fi
if test "${with_qdbm}"; then if test "${with_qdbm}"; then
echo "Using QDBM here: ${with_qdbm}" echo "Using QDBM here: ${with_qdbm}"
CPPFLAGS="-I${with_qdbm}/include ${CPPFLAGS}" CPPFLAGS="-I${with_qdbm}/include ${CPPFLAGS}"
LDFLAGS="-L${with_qdbm}/lib ${LDFLAGS}" LDFLAGS="-L${with_qdbm}/lib ${LDFLAGS}"
fi fi
# Problem: depot.h may be in qdbm/depot.h.
# Problem: depot.h may be in qdbm/depot.h. for ac_header in depot.h
for ac_header in depot.h
do : do :
ac_fn_c_check_header_mongrel "$LINENO" "depot.h" "ac_cv_header_depot_h" "$ac_includes_default" 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 : if test "x$ac_cv_header_depot_h" = xyes; then :
@ -3907,7 +3978,7 @@ fi
done 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; } $as_echo_n "checking for dpopen in -lqdbm... " >&6; }
if ${ac_cv_lib_qdbm_dpopen+:} false; then : if ${ac_cv_lib_qdbm_dpopen+:} false; then :
$as_echo_n "(cached) " >&6 $as_echo_n "(cached) " >&6
@ -3954,6 +4025,7 @@ else
as_fn_error $? "libqdbm-dev required" "$LINENO" 5 as_fn_error $? "libqdbm-dev required" "$LINENO" 5
fi fi
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for crypt in -lcrypt" >&5 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for crypt in -lcrypt" >&5
$as_echo_n "checking for crypt in -lcrypt... " >&6; } $as_echo_n "checking for crypt in -lcrypt... " >&6; }
@ -4179,55 +4251,6 @@ _ACEOF
fi 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 for ac_func in inet_aton sigaction sigvec strlcpy strsep strndup alphasort versionsort strverscmp
do : do :
as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh`
@ -4241,81 +4264,9 @@ fi
done 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 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"
# 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"
cat >confcache <<\_ACEOF cat >confcache <<\_ACEOF
# This file is a shell script that caches the results of configure # 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/backend/Makefile") CONFIG_FILES="$CONFIG_FILES apps/backend/Makefile" ;;
"apps/netconf/Makefile") CONFIG_FILES="$CONFIG_FILES apps/netconf/Makefile" ;; "apps/netconf/Makefile") CONFIG_FILES="$CONFIG_FILES apps/netconf/Makefile" ;;
"apps/restconf/Makefile") CONFIG_FILES="$CONFIG_FILES apps/restconf/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" ;; "include/Makefile") CONFIG_FILES="$CONFIG_FILES include/Makefile" ;;
"etc/Makefile") CONFIG_FILES="$CONFIG_FILES etc/Makefile" ;; "etc/Makefile") CONFIG_FILES="$CONFIG_FILES etc/Makefile" ;;
"etc/clixonrc") CONFIG_FILES="$CONFIG_FILES etc/clixonrc" ;; "etc/clixonrc") CONFIG_FILES="$CONFIG_FILES etc/clixonrc" ;;
@ -5030,6 +4980,9 @@ do
"docker/backend/Dockerfile") CONFIG_FILES="$CONFIG_FILES docker/backend/Dockerfile" ;; "docker/backend/Dockerfile") CONFIG_FILES="$CONFIG_FILES docker/backend/Dockerfile" ;;
"docker/netconf/Makefile") CONFIG_FILES="$CONFIG_FILES docker/netconf/Makefile" ;; "docker/netconf/Makefile") CONFIG_FILES="$CONFIG_FILES docker/netconf/Makefile" ;;
"docker/netconf/Dockerfile") CONFIG_FILES="$CONFIG_FILES docker/netconf/Dockerfile" ;; "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" ;; "doc/Makefile") CONFIG_FILES="$CONFIG_FILES doc/Makefile" ;;
*) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;; *) 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"} : ${CFLAGS="-O2"}
CLIXON_VERSION_MAJOR="3" CLIXON_VERSION_MAJOR="3"
CLIXON_VERSION_MINOR="2" CLIXON_VERSION_MINOR="3"
CLIXON_VERSION_PATCH="0" CLIXON_VERSION_PATCH="0"
CLIXON_VERSION="\"${CLIXON_VERSION_MAJOR}.${CLIXON_VERSION_MINOR}.${CLIXON_VERSION_PATCH}\"" CLIXON_VERSION="\"${CLIXON_VERSION_MAJOR}.${CLIXON_VERSION_MINOR}.${CLIXON_VERSION_PATCH}\""
# Fix to specific version (eg 3.5) or head (3) # Fix to specific version (eg 3.5) or head (3)
@ -77,6 +77,8 @@ AC_SUBST(SH_SUFFIX)
AC_SUBST(EXE_SUFFIX) AC_SUBST(EXE_SUFFIX)
AC_SUBST(AR) AC_SUBST(AR)
AC_SUBST(RANLIB) AC_SUBST(RANLIB)
AC_SUBST(with_restconf) # If yes, compile apps/restconf
AC_SUBST(with_keyvalue) # If yes, compile datastore/keyvalue
# #
AC_PROG_CC() AC_PROG_CC()
@ -119,23 +121,40 @@ if test "${with_cligen}"; then
LDFLAGS="-L${with_cligen}/lib ${LDFLAGS}" LDFLAGS="-L${with_cligen}/lib ${LDFLAGS}"
fi fi
AC_CHECK_HEADERS(cligen/cligen.h,, AC_MSG_ERROR(cligen missing. Try: git clone https://github.com/olofhagsand/cligen.git)) AC_CHECK_HEADERS(cligen/cligen.h,, AC_MSG_ERROR(cligen missing. Try: git clone https://github.com/olofhagsand/cligen.git))
AC_CHECK_LIB(:libcligen.so.${CLIGEN_VERSION}, cligen_init,, AC_MSG_ERROR([CLIgen${CLIGEN_VERSION} missing. Try: git clone https://github.com/olofhagsand/cligen.git])) AC_CHECK_LIB(: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 # This is for restconf (and fastcgi)
AC_ARG_WITH(qdbm, [ --with-qdbm=dir Use QDBM here ] ) AC_ARG_WITH([restconf],
if test "${with_qdbm}"; then [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
# 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}" echo "Using QDBM here: ${with_qdbm}"
CPPFLAGS="-I${with_qdbm}/include ${CPPFLAGS}" CPPFLAGS="-I${with_qdbm}/include ${CPPFLAGS}"
LDFLAGS="-L${with_qdbm}/lib ${LDFLAGS}" 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 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))
AC_CHECK_LIB(crypt, crypt) AC_CHECK_LIB(crypt, crypt)
AC_CHECK_HEADERS(crypt.h) AC_CHECK_HEADERS(crypt.h)
@ -151,32 +170,8 @@ AC_CHECK_LIB(socket, socket)
AC_CHECK_LIB(nsl, xdr_char) AC_CHECK_LIB(nsl, xdr_char)
AC_CHECK_LIB(dl, dlopen) 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) 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>]) AH_BOTTOM([#include <clixon_custom.h>])
AC_OUTPUT(Makefile AC_OUTPUT(Makefile
@ -188,7 +183,6 @@ AC_OUTPUT(Makefile
apps/backend/Makefile apps/backend/Makefile
apps/netconf/Makefile apps/netconf/Makefile
apps/restconf/Makefile apps/restconf/Makefile
apps/dbctrl/Makefile
include/Makefile include/Makefile
etc/Makefile etc/Makefile
etc/clixonrc etc/clixonrc
@ -201,6 +195,9 @@ AC_OUTPUT(Makefile
docker/backend/Dockerfile docker/backend/Dockerfile
docker/netconf/Makefile docker/netconf/Makefile
docker/netconf/Dockerfile docker/netconf/Dockerfile
datastore/Makefile
datastore/keyvalue/Makefile
datastore/text/Makefile
doc/Makefile doc/Makefile
) )

View file

@ -31,18 +31,24 @@
# ***** END LICENSE BLOCK ***** # ***** END LICENSE BLOCK *****
# #
VPATH = @srcdir@ VPATH = @srcdir@
prefix = @prefix@
datarootdir = @datarootdir@
srcdir = @srcdir@ srcdir = @srcdir@
top_srcdir = @top_srcdir@ top_srcdir = @top_srcdir@
CC = @CC@
CFLAGS = @CFLAGS@
LDFLAGS = @LDFLAGS@
prefix = @prefix@
exec_prefix = @exec_prefix@ exec_prefix = @exec_prefix@
bindir = @bindir@ bindir = @bindir@
libdir = @libdir@
dbdir = @prefix@/db
mandir = @mandir@
libexecdir = @libexecdir@ libexecdir = @libexecdir@
localstatedir = @localstatedir@ localstatedir = @localstatedir@
sysconfdir = @sysconfdir@ sysconfdir = @sysconfdir@
CC = @CC@
CFLAGS = @CFLAGS@
LDFLAGS = @LDFLAGS@
LIBS = @LIBS@
with_restconf = @with_restconf@
with_keyvalue = @with_keyvalue@
SH_SUFFIX = @SH_SUFFIX@ SH_SUFFIX = @SH_SUFFIX@
CLIXON_MAJOR = @CLIXON_VERSION_MAJOR@ 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@ 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) APPOBJ = $(APPSRC:.c=.o)
APPL = clixon_dbctrl APPL = datastore_client
all: $(APPL) all: $(SUBDIRS) $(APPL)
clean: -include $(DESTDIR)$(datarootdir)/clixon/clixon.mk
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)
.SUFFIXES: .SUFFIXES:
.SUFFIXES: .c .o .SUFFIXES: .c .o
@ -95,14 +88,41 @@ uninstall:
.c.o: .c.o:
$(CC) $(INCLUDES) -D__PROGRAM__=\"$(APPL)\" $(CPPFLAGS) $(CFLAGS) -c $< $(CC) $(INCLUDES) -D__PROGRAM__=\"$(APPL)\" $(CPPFLAGS) $(CFLAGS) -c $<
$(APPL) : $(APPOBJ) $(OBJS) $(LIBDEPS) $(APPL) : $(APPOBJ) $(LIBDEPS)
$(CC) $(LDFLAGS) $(APPOBJ) $(OBJS) $(LIBS) -o $@ $(CC) $(LDFLAGS) $(APPOBJ) $(LIBS) -o $@
TAGS:
find . -name '*.[chyl]' -print | etags -
depend: 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 */ ensure errno is set and return -1/NULL */
#include <stdio.h> #include <stdio.h>
#include <stdint.h> #include <stdint.h>
#include <stdlib.h>
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
#include <errno.h> #include <errno.h>
@ -45,7 +46,11 @@
#include <sys/types.h> #include <sys/types.h>
/* clicon */ /* clicon */
#include "clixon_queue.h" #include <cligen/cligen.h>
/* clicon */
#include <clixon/clixon.h>
#include "clixon_chunk.h" #include "clixon_chunk.h"
/* /*

File diff suppressed because it is too large Load diff

View file

@ -31,16 +31,26 @@
***** END LICENSE BLOCK ***** ***** END LICENSE BLOCK *****
Key-value store
*/ */
#ifndef _CLIXON_KEYVALUE_H
#ifndef _CLIXON_PROC_H_ #define _CLIXON_KEYVALUE_H
#define _CLIXON_PROC_H_
/* /*
* Prototypes * Prototypes
*/ */
int clicon_proc_run (char *, void (outcb)(char *), int doerr); int kv_get(xmldb_handle h, char *db, char *xpath,
int clicon_proc_daemon (char *); cxobj **xtop, cxobj ***xvec, size_t *xlen);
int group_name2gid(char *name, gid_t *gid); 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> #include <cligen/cligen.h>
/* clicon */ /* clicon */
#include "clixon_log.h" #include <clixon/clixon.h>
#include "clixon_err.h"
#include "clixon_queue.h"
#include "clixon_chunk.h" #include "clixon_chunk.h"
#include "clixon_qdb.h" #include "clixon_qdb.h"
@ -425,7 +424,7 @@ db_regexp(char *file,
/* Retrieve value if required */ /* Retrieve value if required */
if ( ! noval) { if ( ! noval) {
if((val = dpget(iterdp, key, -1, 0, -1, &vlen)) == NULL) { 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; 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 1. How to document the code
2. How to work in git (branching) 2. How to work in git (branching)
3. How the meta-configure stuff works 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 small comment on one line
* *
* This is a detailed description * 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 * @retval FALSE This is a description of another return value
* @see See also this function * @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/ Basically follows: http://nvie.com/posts/a-successful-git-branching-model/
only somewhat simplified: only somewhat simplified:
Do commits in develop branch. When done, merge with master. Do commits in develop branch. When done, merge with master.
$ git checkout develop ## How the meta-configure stuff works
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
+++++++++++++++++++++++++++++++++++++
configure.ac --. configure.ac --.
| .------> autoconf* -----> configure | .------> autoconf* -----> configure
[aclocal.m4] --+---+ [aclocal.m4] --+---+
@ -64,4 +46,4 @@ configure.ac --.
[config.h.in] -. v .-> [config.h] -. [config.h.in] -. v .-> [config.h] -.
+--> config.status* -+ +--> make* +--> config.status* -+ +--> make*
Makefile.in ---' `-> Makefile ---' Makefile.in ---' `-> Makefile ---'
```

View file

@ -32,7 +32,7 @@ DOXYFILE_ENCODING = UTF-8
# title of most generated pages and in a few other places. # title of most generated pages and in a few other places.
# The default value is: My Project. # 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 # 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 # could be handy for archiving the generated documentation or if some version
@ -743,7 +743,7 @@ WARN_LOGFILE =
# spaces. # spaces.
# Note: If this tag is empty the current directory is searched. # 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 # 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 # 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. # title of most generated pages and in a few other places.
# The default value is: My Project. # 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 # 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 # could be handy for archiving the generated documentation or if some version
@ -743,7 +743,7 @@ WARN_LOGFILE =
# spaces. # spaces.
# Note: If this tag is empty the current directory is searched. # 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 # 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 # 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? ## What is Clixon?
------------------
CliXon is a configuration management tool including CLI generation,
Yang parser, netconf interface and an embedded databases.
Q: Why should you use CliXon? Clixon is a configuration management tool including a generated CLI ,
----------------------------- Yang parser, netconf and restconf interface and an embedded databases.
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.
Q: What license is available? ## Why should I use Clixon?
-----------------------------
The basic license is open-source, GPLv3. Contact authors for commercial license.
Q: Is CliXon extendible? If you want an easy-to-use configuration frontend based on yang with an
------------------------ open-source license. Typically for embedded devices requiring a
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. config interface such as routers and switches.
Q: Which language is CliXon implemented in? ## What license is available?
------------------------------------------- CLIXON is dual license. Either Apache License, Version 2.0 or GNU
CliXon is written in C. The plugins are written in C. The CLI 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) specification uses cligen (http://cligen.se)
There is a project for writing plugins in Python. It is reasonable There is a project for writing plugins in Python. It is reasonable
simple to spawn an external script from a backend. simple to spawn an external script from a backend.
Q: Is CliXon different from Clicon? ## How to best understand Clixon?
----------------------------------- Run the ietf yang routing example, in the example directory.
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.
The commit transaction mechanism has been simplified too. But CliXon ## How do you build and install Clixon (and the example)?
is not backward compliant with key-based Clixon applications, such as Clixon:
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:
./configure; ./configure;
make; make;
sudo make install; sudo make install;
sudo make install-include sudo make install-include
```
The example: The example:
```
cd example; cd example;
make; make;
sudo make install sudo make install
```
Q: What about reference documentation? ## What about reference documentation?
-------------------------------------- Clixon uses Doxygen for reference documentation.
CliXon uses Doxygen for reference documentation.
Build using 'make doc' and aim your browser at doc/html/index.html or Build using 'make doc' and aim your browser at doc/html/index.html or
use the web resource: http://clicon.org/ref/index.html 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 backend server: 'clixon_backend -Ff /usr/local/etc/routing.conf'
- Start a cli session: clixon_cli -f /usr/local/etc/routing.conf - Start a cli session: clixon_cli -f /usr/local/etc/routing.conf
- Start a netconf session: clixon_netconf -f /usr/local/etc/routing.conf - Start a netconf session: clixon_netconf -f /usr/local/etc/routing.conf
Q: Can you run CliXon as docker containers? ## How is configuration data stored?
------------------------------------------- Configuration data is stored in an XML datastore. The default is a
Yes, the example works as docker containers as well. backend and cli needs a text-based addatastore, but there also exists a key-value datastore
common file-system so they need to run as a composed pair. using qdbm. In the example the datastore are regular files found in
cd example/docker /usr/local/var/routing/.
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 ## What is validate and commit?
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?
-------------------------------
Clixon follows netconf in its validate and commit semantics. Clixon follows netconf in its validate and commit semantics.
In short, you edit a 'candidate' configuration, which is first In short, you edit a 'candidate' configuration, which is first
'validated' for consistency and then 'committed' to the 'running' '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 system state based on configuration changes. Writing commit callbacks
is the core functionality of a clixon system. is the core functionality of a clixon system.
Q: How do you write a commit function? ## What is a Clixon configuration file?
-------------------------------------- Clixon options are stored in a configuration file you must specify
You write a commit function in routing_backend.c. when you start a backend or client using -f. The example configuration
Every time a commit is made, transaction_commit() is called in the file is /usr/local/etc/routing.conf.
backend. It has a 'transaction_data td' argument which is used to fetch This file is generated from the base source clixon.conf.cpp.cpp and
information on added, deleted and changed entries. You access this is merged with local configuration files, such as routing.conf.local.
information using access functions as defined in clixon_backend_transaction.h This is slightly confusing and could be improved.
Q: How do you check what has changed on commit? ## Can I run Clixon as docker containers?
----------------------------------------------- Yes, the example works as docker containers as well. backend and cli needs a
You use XPATHs on the XML trees in the transaction commit callback. common file-system so they need to run as a composed pair.
Suppose you want to print all added interfaces: ```
cxobj *target = transaction_target(td); # wanted XML tree cd example/docker
vec = xpath_vec_flag(target, "//interface", &len, XML_FLAG_ADD); /* Get added i/fs */ make docker # Prepares /data as shared file-system mount
for (i=0; i<len; i++) /* Loop over added i/fs */ run.sh # Starts an example backend and a cli
clicon_xml2file(stdout, vec[i], 0, 1); /* Print the added interface */ ```
You can look for added, deleted and changed entries in this way. 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? ## How do I use netconf?
--------------------------------
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?
--------------------------
As an alternative to cli configuration, you can use netconf. Easiest is to just pipe netconf commands to the clixon_netconf application. As an alternative to cli configuration, you can use netconf. Easiest is to just pipe netconf commands to the clixon_netconf application.
Example: Example:
@ -176,25 +102,82 @@ Example:
However, more useful is to run clixon_netconf as an SSH However, more useful is to run clixon_netconf as an SSH
subsystem. Register the subsystem in /etc/sshd_config: subsystem. Register the subsystem in /etc/sshd_config:
```
Subsystem netconf /usr/local/bin/clixon_netconf Subsystem netconf /usr/local/bin/clixon_netconf
```
and then invoke it from a client using and then invoke it from a client using
```
ssh -s netconf <host> 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. The example has a prebuilt notification stream called "ROUTING" that triggers every 10s.
You enable the notification either via the cli or via netconf: You enable the notification either via the cli or via netconf:
cli> notify cli> notify
cli> Routing notification cli> Routing notification
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><create-subscription><stream>ROUTING</stream></create-subscription></rpc>]]>]]>
<rpc-reply><ok/></rpc-reply>]]>]]> <rpc-reply><ok/></rpc-reply>]]>]]>
<notification><event>Routing notification</event></notification>]]>]]> <notification><event>Routing notification</event></notification>]]>]]>
<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 ## Compile and run
------------------ ```
cd example cd example
make && sudo make install make && sudo make install
# Start backend ```
clixon_backend -f /usr/local/etc/routing.conf -I Start backend:
# Edit cli ```
clixon_cli -f /usr/local/etc/routing.conf clixon_backend -f /usr/local/etc/routing.conf -I
# Send netconf command ```
clixon_netconf -f /usr/local/etc/routing.conf 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> <rpc><edit-config><target><candidate/></target><config>
<interfaces> <interfaces>
<interface> <interface>
@ -28,40 +34,39 @@ clixon_netconf -f /usr/local/etc/routing.conf
</interface> </interface>
</interfaces> </interfaces>
</config></edit-config></rpc>]]>]]> </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></get-config></rpc>]]>]]>
<rpc><get-config><source><candidate/></source><filter/></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="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="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><get-config><source><candidate/></source><filter type="xpath" select="/interfaces/interface/ipv4"/></get-config></rpc>]]>]]>
<rpc><validate><source><candidate/></source></validate></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 The example has an example notification triggering every 10s. To start a notification
stream in the session, create a subscription: stream in the session, create a subscription:
```
<rpc><create-subscription><stream>ROUTING</stream></create-subscription></rpc>]]>]]> <rpc><create-subscription><stream>ROUTING</stream></create-subscription></rpc>]]>]]>
<rpc-reply><ok/></rpc-reply>]]>]]> <rpc-reply><ok/></rpc-reply>]]>]]>
<notification><event>Routing notification</event></notification>]]>]]> <notification><event>Routing notification</event></notification>]]>]]>
<notification><event>Routing notification</event></notification>]]>]]> <notification><event>Routing notification</event></notification>]]>]]>
... ...
```
This can also be triggered via the CLI: This can also be triggered via the CLI:
```
cli> notify cli> notify
cli> Routing notification cli> Routing notification
Routing notification Routing notification
... ...
```
## Extending
4. Downcall
-----------
Clixon has an extension mechanism which can be used to make extended internal 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 netconf messages to the backend configuration engine. You may need this to
make some special operation that is not covered by standard 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. reading some status.
Example: Example:
```
cli> downcall "This is a string" cli> downcall "This is a string"
This is a string This is a string
cli>p ```
5. Run as docker container ## Run as docker container
-------------------------- ```
cd docker cd docker
# look in README # look in README
```

View file

@ -27,3 +27,7 @@ CLICON_CLIGEN_CALLBACK_SINGLE_ARG 0
# Enabled uses "startup" configuration on boot # 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 */ /* Clixon version string */
#undef 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. */ /* Define to 1 if you have the `alphasort' function. */
#undef HAVE_ALPHASORT #undef HAVE_ALPHASORT
@ -37,9 +33,6 @@
/* Define to 1 if you have the `crypt' library (-lcrypt). */ /* Define to 1 if you have the `crypt' library (-lcrypt). */
#undef HAVE_LIBCRYPT #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). */ /* Define to 1 if you have the `dl' library (-ldl). */
#undef HAVE_LIBDL #undef HAVE_LIBDL

View file

@ -31,6 +31,9 @@
***** END LICENSE BLOCK ***** ***** 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 * Meta-include file that includes all sub-files in control-lib
* Note: this include files is for external purposes. Do not include this * Note: this include files is for external purposes. Do not include this
* file in clicon lib-routines. * file in clicon lib-routines.
@ -67,22 +70,18 @@
#include <clixon/clixon_handle.h> #include <clixon/clixon_handle.h>
#include <clixon/clixon_yang.h> #include <clixon/clixon_yang.h>
#include <clixon/clixon_yang_type.h> #include <clixon/clixon_yang_type.h>
#include <clixon/clixon_chunk.h>
#include <clixon/clixon_event.h> #include <clixon/clixon_event.h>
#include <clixon/clixon_string.h> #include <clixon/clixon_string.h>
#include <clixon/clixon_file.h> #include <clixon/clixon_file.h>
#include <clixon/clixon_xml.h> #include <clixon/clixon_xml.h>
#include <clixon/clixon_proto.h> #include <clixon/clixon_proto.h>
#include <clixon/clixon_proto_encode.h>
#include <clixon/clixon_proto_client.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_options.h>
#include <clixon/clixon_xml_map.h> #include <clixon/clixon_xml_map.h>
#include <clixon/clixon_xml_db.h> #include <clixon/clixon_xml_db.h>
#include <clixon/clixon_xsl.h> #include <clixon/clixon_xsl.h>
#include <clixon/clixon_json.h> #include <clixon/clixon_json.h>
#include <clixon/clixon_plugin.h>
#include <clixon/clixon_plugin.h>
/* /*
* Global variables generated by Makefile * Global variables generated by Makefile

View file

@ -38,10 +38,10 @@
int clicon_file_dirent(const char *dir, struct dirent **ent, int clicon_file_dirent(const char *dir, struct dirent **ent,
const char *regexp, mode_t type, const char *label); const char *regexp, mode_t type);
char *clicon_tmpfile(const char *label);
int clicon_file_copy(char *src, char *target); int clicon_file_copy(char *src, char *target);
int group_name2gid(char *name, gid_t *gid);
#endif /* _CLIXON_FILE_H_ */ #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_clispec_dir(clicon_handle h);
char *clicon_netconf_dir(clicon_handle h); char *clicon_netconf_dir(clicon_handle h);
char *clicon_restconf_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); int clicon_sock_family(clicon_handle h);
char *clicon_sock(clicon_handle h); char *clicon_sock(clicon_handle h);
int clicon_sock_port(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); char *clicon_dbspec_name(clicon_handle h);
int clicon_dbspec_name_set(clicon_handle h, char *name); 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_ */ #endif /* _CLIXON_OPTIONS_H_ */

View file

@ -51,7 +51,7 @@ typedef void *(find_plugin_t)(clicon_handle, char *);
* Prototypes * Prototypes
*/ */
/* Common plugin function names, function types and signatures. /* 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 * Cli see cli_plugin.c
* Backend see config_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 */ /* Find a function in global namespace or a plugin. XXX clicon internal */
void *clicon_find_func(clicon_handle h, char *plugin, char *func); 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_ */ #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_strsep(char *string, char *delim, int *nvec0);
char *clicon_strjoin (int argc, char **argv, char *delim); 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 #ifndef HAVE_STRNDUP
char *clicon_strndup (const char *, size_t); char *clicon_strndup (const char *, size_t);
#endif /* ! HAVE_STRNDUP */ #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_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_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_CHANGE 0x08 /* Node is changed (commits) or child changed rec */
#define XML_FLAG_NONE 0x10 /* Node is added as NONE */
/* /*
* Prototypes * Prototypes
*/ */
char *xml_type2str(enum cxobj_type type);
char *xml_name(cxobj *xn); char *xml_name(cxobj *xn);
int xml_name_set(cxobj *xn, char *name); int xml_name_set(cxobj *xn, char *name);
char *xml_namespace(cxobj *xn); 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); char *xml_value_append(cxobj *xn, char *val);
enum cxobj_type xml_type(cxobj *xn); enum cxobj_type xml_type(cxobj *xn);
int xml_type_set(cxobj *xn, enum cxobj_type type); 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); cg_var *xml_cv_get(cxobj *xn);
int xml_cv_set(cxobj *xn, cg_var *cv); 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(char *name, cxobj *xn_parent);
cxobj *xml_new_spec(char *name, cxobj *xn_parent, void *spec); cxobj *xml_new_spec(char *name, cxobj *xn_parent, void *spec);
void *xml_spec(cxobj *x); void *xml_spec(cxobj *x);
void *xml_spec_set(cxobj *x, void *spec);
cxobj *xml_find(cxobj *xn_parent, char *name); cxobj *xml_find(cxobj *xn_parent, char *name);
int xml_addsub(cxobj *xp, cxobj *xc); 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); int xml_rootchild(cxobj *xp, int i, cxobj **xcp);
char *xml_body(cxobj *xn); char *xml_body(cxobj *xn);
cxobj *xml_body_get(cxobj *xn);
char *xml_find_value(cxobj *xn_parent, char *name); char *xml_find_value(cxobj *xn_parent, char *name);
char *xml_find_body(cxobj *xn, 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_str(char *str, cxobj **xml_top);
int clicon_xml_parse(cxobj **cxtop, char *format, ...); int clicon_xml_parse(cxobj **cxtop, char *format, ...);
int xmltree2cbuf(cbuf *cb, cxobj *x, int level);
int xml_copy(cxobj *x0, cxobj *x1); int xml_copy(cxobj *x0, cxobj *x1);
cxobj *xml_dup(cxobj *x0); 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. the terms of any one of the Apache License version 2 or the GPL.
***** END LICENSE BLOCK ***** ***** END LICENSE BLOCK *****
* XML support functions.
*/ */
#ifndef _CLIXON_XML_DB_H #ifndef _CLIXON_XML_DB_H
#define _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 * Prototypes
* API
*/ */
int yang2xmlkeyfmt(yang_stmt *ys, int inclkey, char **xkfmt); int xmldb_plugin_load(clicon_handle h, char *filename);
int xmlkeyfmt2key(char *xkfmt, cvec *cvv, char **xk); int xmldb_plugin_unload(clicon_handle h);
int xmlkeyfmt2xpath(char *xkfmt, cvec *cvv, char **xk);
int xmldb_connect(clicon_handle h);
int xmldb_disconnect(clicon_handle h);
int xmldb_getopt(clicon_handle h, char *optname, void **value);
int xmldb_setopt(clicon_handle h, char *optname, void *value);
int xmldb_get(clicon_handle h, char *db, char *xpath, int xmldb_get(clicon_handle h, char *db, char *xpath,
cxobj **xtop, cxobj ***xvec, size_t *xlen); cxobj **xtop, cxobj ***xvec, size_t *xlen);
int xmldb_put(clicon_handle h, char *db, enum operation_type op, int xmldb_put(clicon_handle h, char *db, enum operation_type op,
char *api_path, cxobj *xt); 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_copy(clicon_handle h, char *from, char *to);
int xmldb_lock(clicon_handle h, char *db, int pid); 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_unlock_all(clicon_handle h, int pid);
int xmldb_islocked(clicon_handle h, char *db); int xmldb_islocked(clicon_handle h, char *db);
int xmldb_exists(clicon_handle h, char *db); int xmldb_exists(clicon_handle h, char *db);
int xmldb_delete(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 */ #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 ***first, size_t *firstlen,
cxobj ***second, size_t *secondlen, cxobj ***second, size_t *secondlen,
cxobj ***changed1, cxobj ***changed2, size_t *changedlen); 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_ */ #endif /* _CLIXON_XML_MAP_H_ */

View file

@ -61,12 +61,10 @@ CPPFLAGS = @CPPFLAGS@
INCLUDES = -I. @INCLUDES@ -I$(top_srcdir)/lib/clixon -I$(top_srcdir)/include -I$(top_srcdir) INCLUDES = -I. @INCLUDES@ -I$(top_srcdir)/lib/clixon -I$(top_srcdir)/include -I$(top_srcdir)
SRC = clixon_sig.c clixon_qdb.c clixon_log.c clixon_err.c clixon_event.c \ SRC = clixon_sig.c clixon_log.c clixon_err.c clixon_event.c \
clixon_chunk.c clixon_proc.c \
clixon_string.c clixon_handle.c \ clixon_string.c clixon_handle.c \
clixon_xml.c clixon_xml_map.c clixon_file.c \ clixon_xml.c clixon_xml_map.c clixon_file.c \
clixon_json.c \ clixon_json.c clixon_yang.c clixon_yang_type.c \
clixon_yang.c clixon_yang_type.c \
clixon_hash.c clixon_options.c clixon_plugin.c \ clixon_hash.c clixon_options.c clixon_plugin.c \
clixon_proto.c clixon_proto_client.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

View file

@ -57,7 +57,6 @@
#include "clixon_log.h" #include "clixon_log.h"
#include "clixon_queue.h" #include "clixon_queue.h"
#include "clixon_chunk.h"
#include "clixon_err.h" #include "clixon_err.h"
/* /*
@ -217,7 +216,7 @@ clicon_err_save(void)
{ {
struct err_state *es; struct err_state *es;
if ((es = chunk(sizeof(*es), NULL)) == NULL) if ((es = malloc(sizeof(*es))) == NULL)
return NULL; return NULL;
es->es_errno = clicon_errno; es->es_errno = clicon_errno;
es->es_suberrno = clicon_suberrno; es->es_suberrno = clicon_suberrno;
@ -232,10 +231,11 @@ clicon_err_restore(void* handle)
{ {
struct err_state *es; struct err_state *es;
es = (struct err_state *)handle; if ((es = (struct err_state *)handle) != NULL){
clicon_errno = es->es_errno; clicon_errno = es->es_errno;
clicon_suberrno = es->es_suberrno; clicon_suberrno = es->es_suberrno;
strncpy(clicon_err_reason, es->es_reason, ERR_STRLEN-1); strncpy(clicon_err_reason, es->es_reason, ERR_STRLEN-1);
unchunk(es); free(es);
}
return 0; return 0;
} }

View file

@ -51,6 +51,7 @@
#include <sys/param.h> #include <sys/param.h>
#include <unistd.h> #include <unistd.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <grp.h>
/* cligen */ /* cligen */
#include <cligen/cligen.h> #include <cligen/cligen.h>
@ -58,7 +59,6 @@
/* clicon */ /* clicon */
#include "clixon_err.h" #include "clixon_err.h"
#include "clixon_queue.h" #include "clixon_queue.h"
#include "clixon_chunk.h"
#include "clixon_string.h" #include "clixon_string.h"
#include "clixon_file.h" #include "clixon_file.h"
@ -66,7 +66,8 @@
* qsort function * qsort function
*/ */
static int 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 *d1 = (struct dirent *)arg1;
struct dirent *d2 = (struct dirent *)arg2; 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 /*! Return sorted matching files from a directory
* @param[in] dir Directory path * @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] regexp Regexp filename matching
* @param[in] type File type matching, see stat(2) * @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 n Number of matching files in directory
* @retval -1 Error * @retval -1 Error
@ -92,27 +93,26 @@ clicon_file_dirent_sort(const void* arg1, const void* arg2)
* @code * @code
* char *dir = "/root/fs"; * char *dir = "/root/fs";
* struct dirent *dp; * 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; * return -1;
* for (i = 0; i < ndp; i++) * for (i = 0; i < ndp; i++)
* do something with dp[i].d_name; * do something with dp[i].d_name;
* unchunk_group(__FUNCTION__); * free(dp);
* @endcode * @endcode
*/ */
int int
clicon_file_dirent(const char *dir, clicon_file_dirent(const char *dir,
struct dirent **ent, struct dirent **ent,
const char *regexp, const char *regexp,
mode_t type, mode_t type)
const char *label)
{ {
DIR *dirp;
int retval = -1; int retval = -1;
DIR *dirp;
int res; int res;
int nent; int nent;
char *filename;
regex_t re; regex_t re;
char errbuf[128]; char errbuf[128];
char filename[MAXPATHLEN];
struct stat st; struct stat st;
struct dirent dent; struct dirent dent;
struct dirent *dresp; struct dirent *dresp;
@ -120,6 +120,7 @@ clicon_file_dirent(const char *dir,
struct dirent *new = NULL; struct dirent *new = NULL;
struct dirent *dvecp = NULL; struct dirent *dvecp = NULL;
*ent = NULL; *ent = NULL;
nent = 0; nent = 0;
@ -137,7 +138,9 @@ clicon_file_dirent(const char *dir,
goto quit; 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) { if (res != 0) {
clicon_err(OE_UNIX, 0, "readdir: %s", strerror(errno)); clicon_err(OE_UNIX, 0, "readdir: %s", strerror(errno));
goto quit; goto quit;
@ -150,12 +153,8 @@ clicon_file_dirent(const char *dir,
} }
/* File type matching */ /* File type matching */
if (type) { if (type) {
if ((filename = chunk_sprintf(__FUNCTION__, "%s/%s", dir, dent.d_name)) == NULL) { snprintf(filename, MAXPATHLEN-1, "%s/%s", dir, dent.d_name);
clicon_err(OE_UNIX, 0, "chunk: %s", strerror(errno));
goto quit;
}
res = lstat(filename, &st); res = lstat(filename, &st);
unchunk (filename);
if (res != 0) { if (res != 0) {
clicon_err(OE_UNIX, 0, "lstat: %s", strerror(errno)); clicon_err(OE_UNIX, 0, "lstat: %s", strerror(errno));
goto quit; goto quit;
@ -164,8 +163,8 @@ clicon_file_dirent(const char *dir,
continue; continue;
} }
if ((tmp = rechunk(new, (nent+1)*sizeof(*dvecp), label)) == NULL) { if ((tmp = realloc(new, (nent+1)*sizeof(*dvecp))) == NULL) {
clicon_err(OE_UNIX, 0, "chunk: %s", strerror(errno)); clicon_err(OE_UNIX, errno, "realloc");
goto quit; goto quit;
} }
new = tmp; new = tmp;
@ -183,30 +182,9 @@ quit:
closedir(dirp); closedir(dirp);
if (regexp) if (regexp)
regfree(&re); regfree(&re);
unchunk_group(__FUNCTION__);
return retval; 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 /*! Make a copy of file src
* @retval 0 OK * @retval 0 OK
* @retval -1 Error * @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_handle.h"
#include "clixon_err.h" #include "clixon_err.h"
#include "clixon_yang.h" #include "clixon_yang.h"
#include "clixon_plugin.h"
#include "clixon_options.h" #include "clixon_options.h"
#define CLICON_MAGIC 0x99aafabe #define CLICON_MAGIC 0x99aafabe
#define handle(h) (assert(clicon_handle_check(h)==0),(struct clicon_handle *)(h)) #define handle(h) (assert(clicon_handle_check(h)==0),(struct clicon_handle *)(h))
/* /*! Internal structure of basic handle. Also header of all other handles.
* clicon_handle * @note If you change here, you must also change the structs below:
* Internal structire of basic handle. Also header of all other handles. * @see struct cli_handle, struct backend_handle
* see struct clicon_cli_handle, struct clicon_backend_handle, etc
*/ */
struct clicon_handle { struct clicon_handle {
int ch_magic; /* magic (HDR) */ int ch_magic; /* magic (HDR) */
@ -69,7 +69,6 @@ struct clicon_handle {
clicon_hash_t *ch_data; /* internal clicon data (HDR) */ clicon_hash_t *ch_data; /* internal clicon data (HDR) */
}; };
/*! Internal call to allocate a CLICON handle. /*! Internal call to allocate a CLICON handle.
* *
* There may be different variants of handles with some common options. * There may be different variants of handles with some common options.
@ -166,3 +165,4 @@ clicon_data(clicon_handle h)
return ch->ch_data; return ch->ch_data;
} }

View file

@ -200,8 +200,8 @@ hash_value(clicon_hash_t *hash,
/*! Copy value and add hash entry. /*! Copy value and add hash entry.
* *
* @param[in] hash Hash table * @param[in] hash Hash table
* @param[in] key New variable name * @param[in] key Variable name
* @param[in] val New variable value * @param[in] val Variable value
* @param[in] vlen Length of variable value * @param[in] vlen Length of variable value
* @retval variable New hash structure on success * @retval variable New hash structure on success
* @retval NULL Failure * @retval NULL Failure
@ -213,7 +213,8 @@ hash_add(clicon_hash_t *hash,
size_t vlen) size_t vlen)
{ {
void *newval; void *newval;
clicon_hash_t h, new = NULL; clicon_hash_t h;
clicon_hash_t new = NULL;
/* If variable exist, don't allocate a new. just replace value */ /* If variable exist, don't allocate a new. just replace value */
h = hash_lookup (hash, key); h = hash_lookup (hash, key);

View file

@ -61,9 +61,9 @@
#include "clixon_queue.h" #include "clixon_queue.h"
#include "clixon_hash.h" #include "clixon_hash.h"
#include "clixon_handle.h" #include "clixon_handle.h"
#include "clixon_chunk.h"
#include "clixon_log.h" #include "clixon_log.h"
#include "clixon_yang.h" #include "clixon_yang.h"
#include "clixon_plugin.h"
#include "clixon_options.h" #include "clixon_options.h"
/* /*
@ -198,7 +198,6 @@ clicon_option_default(clicon_hash_t *copt)
} }
retval = 0; retval = 0;
catch: catch:
unchunk_group(__FUNCTION__);
return retval; 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"); clicon_err(OE_UNIX, 0, "CLICON_YANG_DIR not defined in config file");
goto done; goto done;
} }
if (!hash_lookup(copt, "CLICON_ARCHIVE_DIR")){ if (!hash_lookup(copt, "CLICON_XMLDB_DIR")){
clicon_err(OE_UNIX, 0, "CLICON_ARCHIVE_DIR not defined in config file"); clicon_err(OE_UNIX, 0, "CLICON_XMLDB_DIR not defined in config file");
goto done; goto done;
} }
if (!hash_lookup(copt, "CLICON_SOCK")){ if (!hash_lookup(copt, "CLICON_SOCK")){
@ -448,9 +447,9 @@ clicon_restconf_dir(clicon_handle h)
} }
char * 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 */ /* get family of backend socket: AF_UNIX, AF_INET or AF_INET6 */
@ -627,12 +626,12 @@ clicon_dbspec_yang(clicon_handle h)
return NULL; return NULL;
} }
/* /*! Set yang database specification
* Set dbspec (YANG variant)
* ys must be a malloced pointer * ys must be a malloced pointer
*/ */
int 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); clicon_hash_t *cdat = clicon_data(h);
@ -644,8 +643,7 @@ clicon_dbspec_yang_set(clicon_handle h, struct yang_spec *ys)
return 0; 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,... * XXX: this we muśt change,...
*/ */
char * char *
@ -656,11 +654,103 @@ clicon_dbspec_name(clicon_handle h)
return clicon_option_str(h, "dbspec_name"); 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 int
clicon_dbspec_name_set(clicon_handle h, char *name) clicon_dbspec_name_set(clicon_handle h, char *name)
{ {
return clicon_option_str_set(h, "dbspec_name", 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_err.h"
#include "clixon_queue.h" #include "clixon_queue.h"
#include "clixon_hash.h" #include "clixon_hash.h"
#include "clixon_log.h"
#include "clixon_handle.h" #include "clixon_handle.h"
#include "clixon_plugin.h" #include "clixon_plugin.h"
@ -84,3 +85,76 @@ clicon_find_func(clicon_handle h, char *plugin, char *func)
return dlsym(dlhandle, 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_err.h"
#include "clixon_log.h" #include "clixon_log.h"
#include "clixon_queue.h" #include "clixon_queue.h"
#include "clixon_chunk.h"
#include "clixon_sig.h" #include "clixon_sig.h"
#include "clixon_xml.h" #include "clixon_xml.h"
#include "clixon_xsl.h" #include "clixon_xsl.h"
@ -298,6 +297,8 @@ clicon_msg_send(int s,
if (atomicio((ssize_t (*)(int, void *, size_t))write, if (atomicio((ssize_t (*)(int, void *, size_t))write,
s, msg, ntohs(msg->op_len)) < 0){ s, msg, ntohs(msg->op_len)) < 0){
clicon_err(OE_CFG, errno, "%s", __FUNCTION__); 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; goto done;
} }
retval = 0; retval = 0;
@ -532,11 +533,11 @@ send_msg_reply(int s,
uint16_t datalen) uint16_t datalen)
{ {
int retval = -1; int retval = -1;
struct clicon_msg *reply; struct clicon_msg *reply = NULL;
uint16_t len; uint16_t len;
len = sizeof(*reply) + datalen; len = sizeof(*reply) + datalen;
if ((reply = (struct clicon_msg *)chunk(len, __FUNCTION__)) == NULL) if ((reply = (struct clicon_msg *)malloc(len)) == NULL)
goto done; goto done;
memset(reply, 0, len); memset(reply, 0, len);
reply->op_len = htons(len); reply->op_len = htons(len);
@ -546,7 +547,8 @@ send_msg_reply(int s,
goto done; goto done;
retval = 0; retval = 0;
done: done:
unchunk_group(__FUNCTION__); if (reply)
free(reply);
return retval; return retval;
} }

View file

@ -56,11 +56,11 @@
/* clicon */ /* clicon */
#include "clixon_queue.h" #include "clixon_queue.h"
#include "clixon_chunk.h"
#include "clixon_log.h" #include "clixon_log.h"
#include "clixon_hash.h" #include "clixon_hash.h"
#include "clixon_handle.h" #include "clixon_handle.h"
#include "clixon_yang.h" #include "clixon_yang.h"
#include "clixon_plugin.h"
#include "clixon_options.h" #include "clixon_options.h"
#include "clixon_xml.h" #include "clixon_xml.h"
#include "clixon_xsl.h" #include "clixon_xsl.h"

View file

@ -47,9 +47,10 @@
#include <regex.h> #include <regex.h>
#include <ctype.h> #include <ctype.h>
#include <cligen/cligen.h>
/* clicon */ /* clicon */
#include "clixon_queue.h" #include "clixon_queue.h"
#include "clixon_chunk.h"
#include "clixon_string.h" #include "clixon_string.h"
#include "clixon_err.h" #include "clixon_err.h"
@ -138,6 +139,200 @@ clicon_strjoin(int argc,
return str; 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 /*! strndup() for systems without it, such as xBSD
*/ */
@ -163,6 +358,8 @@ clicon_strndup (const char *str,
} }
#endif /* ! HAVE_STRNDUP */ #endif /* ! HAVE_STRNDUP */
/* /*
* Turn this on for uni-test programs * Turn this on for uni-test programs
* Usage: clixon_string join * Usage: clixon_string join

View file

@ -51,7 +51,6 @@
#include "clixon_err.h" #include "clixon_err.h"
#include "clixon_log.h" #include "clixon_log.h"
#include "clixon_queue.h" #include "clixon_queue.h"
#include "clixon_chunk.h"
#include "clixon_xml.h" #include "clixon_xml.h"
#include "clixon_xml_parse.h" #include "clixon_xml_parse.h"
@ -76,7 +75,6 @@ struct xml{
int x_childvec_len; /* length of vector */ int x_childvec_len; /* length of vector */
enum cxobj_type x_type; /* type of node: element, attribute, body */ enum cxobj_type x_type; /* type of node: element, attribute, body */
char *x_value; /* attribute and body nodes have values */ 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_vector_i; /* internal use: xml_child_each */
int x_flags; /* Flags according to XML_FLAG_* above */ int x_flags; /* Flags according to XML_FLAG_* above */
void *x_spec; /* Pointer to specification, eg yang, by 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 */ 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 * Access functions
*/ */
@ -294,33 +322,6 @@ xml_type_set(cxobj *xn,
return old; 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 /*! Get cligen variable associated with node
* @param[in] xn xml node * @param[in] xn xml node
* @retval cv Cligen variable if set * @retval cv Cligen variable if set
@ -520,12 +521,21 @@ xml_new_spec(char *name,
return x; return x;
} }
void * void *
xml_spec(cxobj *x) xml_spec(cxobj *x)
{ {
return x->x_spec; 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. /*! Find an XML node matching name among a parent's children.
* *
* Get first XML node directly under x_up in the xml hierarchy with * Get first XML node directly under x_up in the xml hierarchy with
@ -758,6 +768,16 @@ xml_body(cxobj *xn)
return NULL; 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 /*! Find and return the value of a sub xml node
* *
* The value can be of an attribute or body. * The value can be of an attribute or body.
@ -844,8 +864,8 @@ clicon_xml2file(FILE *f,
int level, int level,
int prettyprint) int prettyprint)
{ {
cbuf *cb;
int retval = -1; int retval = -1;
cbuf *cb = NULL;
if ((cb = cbuf_new()) == NULL){ if ((cb = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new"); clicon_err(OE_XML, errno, "cbuf_new");
@ -882,8 +902,8 @@ xml_print(FILE *f,
/*! Print an XML tree structure to a cligen buffer /*! Print an XML tree structure to a cligen buffer
* *
* @param[in,out] cb Cligen buffer to write to * @param[in,out] cb Cligen buffer to write to
* @param[in] xn clicon xml tree * @param[in] xn Clicon xml tree
* @param[in] level how many spaces to insert before each line * @param[in] level Indentation level
* @param[in] prettyprint insert \n and spaces tomake the xml more readable. * @param[in] prettyprint insert \n and spaces tomake the xml more readable.
* *
* @code * @code
@ -897,47 +917,50 @@ xml_print(FILE *f,
*/ */
int int
clicon_xml2cbuf(cbuf *cb, clicon_xml2cbuf(cbuf *cb,
cxobj *cx, cxobj *x,
int level, int level,
int prettyprint) int prettyprint)
{ {
cxobj *xc; cxobj *xc;
char *name;
switch(xml_type(cx)){ name = xml_name(x);
switch(xml_type(x)){
case CX_BODY: case CX_BODY:
cprintf(cb, "%s", xml_value(cx)); cprintf(cb, "%s", xml_value(x));
break; break;
case CX_ATTR: case CX_ATTR:
cprintf(cb, " "); cprintf(cb, " ");
if (xml_namespace(cx)) if (xml_namespace(x))
cprintf(cb, "%s:", xml_namespace(cx)); cprintf(cb, "%s:", xml_namespace(x));
cprintf(cb, "%s=\"%s\"", xml_name(cx), xml_value(cx)); cprintf(cb, "%s=\"%s\"", name, xml_value(x));
break; break;
case CX_ELMNT: case CX_ELMNT:
cprintf(cb, "%*s<", prettyprint?(level*XML_INDENT):0, ""); cprintf(cb, "%*s<", prettyprint?(level*XML_INDENT):0, "");
if (xml_namespace(cx)) if (xml_namespace(x))
cprintf(cb, "%s:", xml_namespace(cx)); cprintf(cb, "%s:", xml_namespace(x));
cprintf(cb, "%s", xml_name(cx)); cprintf(cb, "%s", name);
xc = NULL; 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); clicon_xml2cbuf(cb, xc, level+1, prettyprint);
/* Check for special case <a/> instead of <a></a> */ /* 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, "/>"); cprintf(cb, "/>");
else{ else{
cprintf(cb, ">"); cprintf(cb, ">");
if (prettyprint && xml_body(cx)==NULL) if (prettyprint && xml_body(x)==NULL)
cprintf(cb, "\n"); cprintf(cb, "\n");
xc = NULL; 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) if (xml_type(xc) == CX_ATTR)
continue; continue;
else else
clicon_xml2cbuf(cb, xc, level+1, prettyprint); 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", level*XML_INDENT, "");
cprintf(cb, "</%s>", xml_name(cx)); cprintf(cb, "</%s>", name);
} }
if (prettyprint) if (prettyprint)
cprintf(cb, "\n"); cprintf(cb, "\n");
@ -976,6 +999,44 @@ xml_parse(char *str,
return retval; 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 * 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: * Note, xt will add a top-level symbol called "top" meaning that <tree../> will look as:
* <top><tree.../></tree> * <top><tree.../></tree>
* XXX: There is a potential leak here on some return values. * XXX: There is a potential leak here on some return values.
* XXX: What happens if endtag is different?
* May block * May block
*/ */
int int
@ -1016,59 +1078,69 @@ clicon_xml_parse_file(int fd,
cxobj **cx, cxobj **cx,
char *endtag) char *endtag)
{ {
int retval = -1;
int ret;
int len = 0; int len = 0;
char ch; char ch;
int retval; char *xmlbuf = NULL;
char *xmlbuf;
char *ptr; char *ptr;
int maxbuf = BUFLEN; int maxbuf = BUFLEN;
int endtaglen = strlen(endtag); int endtaglen = strlen(endtag);
int state = 0; int state = 0;
int oldmaxbuf;
if (endtag == NULL){ if (endtag == NULL){
clicon_err(OE_XML, 0, "%s: endtag required\n", __FUNCTION__); clicon_err(OE_XML, 0, "%s: endtag required\n", __FUNCTION__);
return -1; goto done;
} }
*cx = NULL; *cx = NULL;
if ((xmlbuf = malloc(maxbuf)) == NULL){ if ((xmlbuf = malloc(maxbuf)) == NULL){
clicon_err(OE_XML, errno, "%s: malloc", __FUNCTION__); clicon_err(OE_XML, errno, "%s: malloc", __FUNCTION__);
return -1; goto done;
} }
memset(xmlbuf, 0, maxbuf); memset(xmlbuf, 0, maxbuf);
ptr = xmlbuf; ptr = xmlbuf;
while (1){ 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", clicon_err(OE_XML, errno, "%s: read: [pid:%d]\n",
__FUNCTION__, __FUNCTION__,
(int)getpid()); (int)getpid());
break; break;
} }
if (retval != 0){ if (ret != 0){
state = FSM(endtag, ch, state); state = FSM(endtag, ch, state);
xmlbuf[len++] = ch; xmlbuf[len++] = ch;
} }
if (retval == 0 || state == endtaglen){ if (ret == 0 || state == endtaglen){
state = 0; state = 0;
if ((*cx = xml_new("top", NULL)) == NULL) if ((*cx = xml_new("top", NULL)) == NULL)
break; break;
if (xml_parse(ptr, *cx) < 0) if (xml_parse(ptr, *cx) < 0){
return -1; goto done;
}
break; break;
} }
if (len>=maxbuf-1){ /* Space: one for the null character */ if (len>=maxbuf-1){ /* Space: one for the null character */
int oldmaxbuf = maxbuf; oldmaxbuf = maxbuf;
maxbuf *= 2; maxbuf *= 2;
if ((xmlbuf = realloc(xmlbuf, maxbuf)) == NULL){ if ((xmlbuf = realloc(xmlbuf, maxbuf)) == NULL){
clicon_err(OE_XML, errno, "%s: realloc", __FUNCTION__); clicon_err(OE_XML, errno, "%s: realloc", __FUNCTION__);
return -1; goto done;
} }
memset(xmlbuf+oldmaxbuf, 0, maxbuf-oldmaxbuf); memset(xmlbuf+oldmaxbuf, 0, maxbuf-oldmaxbuf);
ptr = xmlbuf; ptr = xmlbuf;
} }
} /* while */ } /* while */
retval = 0;
done:
if (retval < 0 && *cx){
free(*cx);
*cx = NULL;
}
if (xmlbuf)
free(xmlbuf); free(xmlbuf);
return (*cx)?0:-1; return retval;
// return (*cx)?0:-1;
} }
/*! Read an XML definition from string and parse it into a parse-tree. /*! 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, * x1 should be a created placeholder. If x1 is non-empty,
* the copied tree is appended to the existing tree. * the copied tree is appended to the existing tree.
* @code * @code
* x1 = xml_new("new", xc); * x1 = xml_new("new", xparent);
* xml_copy(x0, x1); * xml_copy(x0, x1);
* @endcode * @endcode
*/ */
@ -1462,8 +1534,12 @@ xml_body_uint32(cxobj *xb,
} }
/*! Map xml operation from string to enumeration /*! Map xml operation from string to enumeration
* @param[in] xn XML node * @param[in] opstr String, eg "merge"
* @param[out] op "operation" attribute may change operation * @param[out] op Enumeration, eg OP_MERGE
* @code
* enum operation_type op;
* xml_operation("replace", &op)
* @endcode
*/ */
int int
xml_operation(char *opstr, xml_operation(char *opstr,
@ -1488,6 +1564,14 @@ xml_operation(char *opstr,
return 0; 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 * char *
xml_operation2str(enum operation_type op) xml_operation2str(enum operation_type op)
{ {
@ -1511,3 +1595,52 @@ xml_operation2str(enum operation_type op)
return "none"; 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_string.h"
#include "clixon_queue.h" #include "clixon_queue.h"
#include "clixon_hash.h" #include "clixon_hash.h"
#include "clixon_chunk.h"
#include "clixon_handle.h" #include "clixon_handle.h"
#include "clixon_string.h"
#include "clixon_yang.h" #include "clixon_yang.h"
#include "clixon_yang_type.h" #include "clixon_yang_type.h"
#include "clixon_plugin.h"
#include "clixon_options.h" #include "clixon_options.h"
#include "clixon_xml.h" #include "clixon_xml.h"
#include "clixon_xsl.h" #include "clixon_xsl.h"
@ -89,9 +90,6 @@
/* Something to do with reverse engineering of junos syntax? */ /* Something to do with reverse engineering of junos syntax? */
#undef SPECIAL_TREATMENT_OF_NAME #undef SPECIAL_TREATMENT_OF_NAME
/* /*
* A node is a leaf if it contains a body. * A node is a leaf if it contains a body.
*/ */
@ -127,8 +125,9 @@ xml2txt(FILE *f, cxobj *x, int level)
{ {
cxobj *xe = NULL; cxobj *xe = NULL;
int children=0; int children=0;
char *term; char *term = NULL;
int retval = -1; int retval = -1;
int encr=0;
#ifdef SPECIAL_TREATMENT_OF_NAME #ifdef SPECIAL_TREATMENT_OF_NAME
cxobj *xname; cxobj *xname;
#endif #endif
@ -140,14 +139,16 @@ xml2txt(FILE *f, cxobj *x, int level)
if (xml_type(x) == CX_BODY){ if (xml_type(x) == CX_BODY){
/* Kludge for escaping encrypted passwords */ /* Kludge for escaping encrypted passwords */
if (strcmp(xml_name(xml_parent(x)), "encrypted-password")==0) if (strcmp(xml_name(xml_parent(x)), "encrypted-password")==0)
term = chunk_sprintf(__FUNCTION__, "\"%s\"", xml_value(x)); encr++;
else
term = xml_value(x); term = xml_value(x);
} }
else{ else{
fprintf(f, "%*s", 4*level, ""); fprintf(f, "%*s", 4*level, "");
term = xml_name(x); term = xml_name(x);
} }
if (encr)
fprintf(f, "\"%s\";\n", term);
else
fprintf(f, "%s;\n", term); fprintf(f, "%s;\n", term);
retval = 0; retval = 0;
goto done; goto done;
@ -240,7 +241,7 @@ xml2cli(FILE *f,
!index GT_VARS T !index GT_VARS T
!index GT_ALL 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); // bool = (!x->xn_index || gt == GT_ALL);
if (bool){ if (bool){
if (cbuf_len(cbpre)) if (cbuf_len(cbpre))
@ -252,12 +253,12 @@ xml2cli(FILE *f,
i = 0; i = 0;
while ((xe = xml_child_each(x, xe, -1)) != NULL){ while ((xe = xml_child_each(x, xe, -1)) != NULL){
/* Dont call this if it is index and there are other following */ /* 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 else
if (xml2cli(f, xe, cbuf_get(cbpre), gt) < 0) if (xml2cli(f, xe, cbuf_get(cbpre), gt) < 0)
goto done; 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) if (gt == GT_ALL)
cprintf(cbpre, " %s", xml_name(xe)); cprintf(cbpre, " %s", xml_name(xe));
cprintf(cbpre, " %s", xml_value(xml_child_i(xe, 0))); cprintf(cbpre, " %s", xml_value(xml_child_i(xe, 0)));
@ -791,3 +792,608 @@ xml_diff(yang_spec *yspec,
done: done:
return retval; 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; return 0;
} }
/*! Called at </name> */
static int 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; int retval = -1;
cxobj *x = ya->ya_xelement; cxobj *x = ya->ya_xelement;
@ -199,8 +201,10 @@ xml_parse_bslash1(struct xml_parse_yacc_arg *ya, char *name)
; ;
else{ else{
xc = NULL; xc = NULL;
while ((xc = xml_child_each(x, xc, CX_BODY)) != NULL) while ((xc = xml_child_each(x, xc, CX_BODY)) != NULL) {
xml_value_set(xc, ""); /* XXX remove */ xml_purge(xc);
xc = NULL; /* reset iterator */
}
} }
} }
retval = 0; retval = 0;
@ -209,8 +213,11 @@ xml_parse_bslash1(struct xml_parse_yacc_arg *ya, char *name)
return retval; return retval;
} }
/*! Called at </namespace:name> */
static int 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; int retval = -1;
cxobj *x = ya->ya_xelement; 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){ 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); 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] */ #if 1 /* Problems with .[userid=1321] */
else if (strncmp(s,".", strlen("."))==0) else if (strncmp(s,".", strlen("."))==0)
xpath_element_new(A_SELF, s+strlen("."), &xpnext); xpath_element_new(A_SELF, s+strlen("."), &xpnext);
@ -368,13 +375,7 @@ xpath_parse(char *xpath,
else if (strncmp(s,"self::", strlen("self::"))==0) else if (strncmp(s,"self::", strlen("self::"))==0)
xpath_element_new(A_SELF, s+strlen("self::"), &xpnext); 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) else if (strncmp(s,"parent::", strlen("parent::"))==0)
xpath_element_new(A_PARENT, s+strlen("parent::"), &xpnext); xpath_element_new(A_PARENT, s+strlen("parent::"), &xpnext);
else if (strncmp(s,"ancestor::", strlen("ancestor::"))==0) else if (strncmp(s,"ancestor::", strlen("ancestor::"))==0)
@ -1076,7 +1077,7 @@ int
main(int argc, char **argv) main(int argc, char **argv)
{ {
int i; int i;
cxobj **xv; cxobj **xv
cxobj *x; cxobj *x;
cxobj *xn; cxobj *xn;
size_t xlen = 0; size_t xlen = 0;

View file

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

View file

@ -63,7 +63,7 @@
#include "clixon_handle.h" #include "clixon_handle.h"
#include "clixon_yang.h" #include "clixon_yang.h"
#include "clixon_hash.h" #include "clixon_hash.h"
#include "clixon_chunk.h" #include "clixon_plugin.h"
#include "clixon_options.h" #include "clixon_options.h"
#include "clixon_yang.h" #include "clixon_yang.h"
#include "clixon_yang_type.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 # 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(){ expectfn(){
cmd=$1 cmd=$1
expect=$2 expect=$2
ret=`$cmd` ret=`$cmd`
@ -40,7 +39,8 @@ expectfn(){
fi 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(){ expecteof(){
cmd=$1 cmd=$1
input=$2 input=$2
@ -61,7 +61,8 @@ EOF
fi 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(){ expectwait(){
cmd=$1 cmd=$1
input=$2 input=$2

View file

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

View file

@ -20,14 +20,44 @@ sudo clixon_backend -If $clixon_cf
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
err err
fi fi
new "netconf tests"
new "netconf get empty config" 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" 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>]]>]]>$" 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" 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" new "netconf validate missing type"
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><rpc-error>" 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>]]>]]>$" expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf edit config replace" 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" 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>]]>]]>$" 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" new "netconf discard-changes"
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><discard-changes/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$" 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 sleep 1
new "restconf tests"
new "restconf options" new "restconf options"
expectfn "curl -i -s -X OPTIONS http://localhost/restconf/data" "Allow: OPTIONS,HEAD,GET,POST,PUT,DELETE" 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" new "restconf head"
expectfn "curl -s -I http://localhost/restconf/data" "Content-Type: application/yang.data\+json" expectfn "curl -s -I http://localhost/restconf/data" "Content-Type: application/yang.data\+json"
new "restconf POST config" new "restconf get empty config"
expectfn 'curl -sX POST -d {"type":"eth"} http://localhost/restconf/data/interfaces/interface=eth4' "" 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" new "Check eth0 added"
expectfn "curl -sG http://localhost/restconf/data" '{"interfaces": {"interface": \[{"name": "eth1","type": "eth","enabled": "true"},{ "name": "eth4","type": "eth","enabled": "true"}\]}} 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" new "Re-post eth0 which should generate error"
expectfn 'curl -sX PATCH -d {"type":"eth"} http://localhost/restconf/data/interfaces/interface=eth4' "" expectfn 'curl -sX POST -d {"interfaces":{"interface":{"name":"eth0","type":"eth","enabled":"true"}}} http://localhost/restconf/data' "Not Found"
new "restconf PUT" new "delete eth0"
expectfn 'curl -sX PUT -d {"type":"eth"} http://localhost/restconf/data/interfaces/interface=eth5' "" 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" new "Kill restconf daemon"
#sudo pkill -u www-data clixon_restconf #sudo pkill -u www-data clixon_restconf

View file

@ -1,5 +1,5 @@
#!/bin/bash #!/bin/bash
# Test2: backend and netconf basic functionality # Test4: Yang specifics: multi-keys and empty type
# include err() and new() functions # include err() and new() functions
. ./lib.sh . ./lib.sh
@ -26,6 +26,19 @@ module ietf-ip{
leaf d { leaf d {
type empty; type empty;
} }
container f {
leaf-list e {
type string;
}
}
leaf g {
type string;
}
container h {
leaf j {
type string;
}
}
} }
} }
EOF EOF
@ -52,6 +65,15 @@ expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test" "<rpc><commit/></rpc>]]>
new "netconf get config xpath" 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>]]>]]>$" 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" new "Kill backend"
# Check if still alive # Check if still alive
pid=`pgrep clixon_backend` 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